Java进阶-Redis基础

数据库分类

  1. 关系型数据库:
    1. Oracle,MySQL,SqlServer,DB2
    2. 特点:有一套标准的SQL语句,有事务功能
  2. 非关系型数据库(Nosql数据库)
    1. key-value存储数据库
      1. 这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。
      2. Key/value模型对于IT系统来说的优势在于简单、易部署。但是如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了。
      3. 举例如:Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB。
    2. 列存储数据库
      1. 这部分数据库通常是用来应对分布式存储的海量数据。键仍然存在,但是它们的特点是指向了多个列。这些列是由列家族来安排的。如:Cassandra, HBase, Ria
    3. 文档性数据库
      1. 文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似。
      2. 该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。
      3. 文档型数据库可以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高。如:CouchDB, MongoDb. 国内也有文档型数据库SequoiaDB,已经开源。
    4. 图形数据库
      1. 图形结构的数据库通其他行列以及刚性结构的SQL数据库不同.它是使用灵活的图形模型.并且能拓展到多个服务器上
      2. 常见的数据库有: Neo4J
  3. 非关系型数据库特点
    1. 数据模型比较简单(主要)
    2. 需要灵活性更强的应用系统
    3. 对数据库性能要求较高(主要)
    4. 不需要高度的数据一致性
    5. 对于给定key,比较容易映射复杂值的环境.

redis简介

  1. Redis 一个简单的、高效的、分布式的,基于内存缓存工具,架设好服务器后,通过网络链接(类似数据库),提供Key-Value 式缓存服务。
    1. redis就是一个软件,通过这个软件可以进行数据存储,跟mysql一样
  2. 优势:
    1. 速度快
      1. 数据存储在内存中,读写速度快,读数据: 11w/s、写数据: 8w/s
    2. 提供丰富的数据类型
      1. mysql中的数据类型是固定的,查询出来数据需要进行转换,而且不支持集合类型
      2. redis可以直接传这些数据类型,String、List、Set、Zset、Hash
    3. 原子性操作
      1. Redis 所有操作都是原子性,意思就是要么执行成功、要么执行失败、完全不执行。
      2. 单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI和 EXEC 指令包起来
    4. 丰富的特征
      1. Redis 还支持 publish/subscribe, 通知,key过期等特征
    5. 持久化机制
    6. 高可用分布式
  3. 为什么Redis会这么快
    1. 内存存储:所有的数据存放到内存当中
    2. 数据处理模型
    3. 避免上下文切换和竞态消耗
    4. 使用epoll模型,非阻塞IO
  4. 学习redis
    1. redis在线入门 : http://try.redis.io/
    2. redis 中文资料站: http://www.redis.cn/
    3. github: https://github.com/antirez/redis
    4. 官网: https://redis.io/
    5. redis命令参考: http://redisdoc.com/

redis安装(单机版)

  1. gcc安装:yum -y install gcc automake autoconf libtool make
    1. 如果是mac系统,不用执行
  2. redis安装
    1. 下载 wget http://download.redis.io/releases/redis-6.2.5.tar.gz
    2. 解压 tar -zxvf redis-6.2.5.tar.gz -C /usr/local
      1. 是C源码,需要编译
      2. 解压到/usr/local目录下
    3. 编译:make
      1. cd /usr/local/redis-6.2.5下,执行make编译成可执行文件
    4. 安装:make PREFIX=/usr/local/redis-6.2.5 install
      1. 安装到指定目录下
  3. 启动
    1. 进入安装目录: cd /usr/local/redis-6.2.5/bin
    2. 启动服务:
      1. 方式1: 直接使用redis-server
      2. 方式2: 使用动态参数redis-server --port=6380
      3. 方式3: 使用配置文件启动(推荐)
    3. 客户端访问连接redis:./redis-cli
      1. redis-cli -h IP地址 -p 端口 :指定哪台主机的端口应用
      2. 退出客户端 ctrl + C
  4. 如何使用配置文件启动
    1. 进入到redis安装目录下cd /usr/local/redis-6.2.5,该目录下有一个默认的配置文件redis.conf
    2. 新建一个conf文件夹mkdir conf
    3. 将当前目录的redis.conf文件copy到conf文件夹下cp redis.conf conf/redis_6379.conf
      1. 因为分布式部署可能有n个redis启动,因此这个配置文件名需要唯一
    4. cd /usr/local/redis-6.2.5 执行bin/redis-server conf/redis_6379.conf,启动redis
  5. 配置启动配置文件
    1. 上面的启动是前台启动,可以通过配置文件配置后台启动等其他功能,打开redis_6379.conf
    2. 常见设置如下

       //配置后台启动:在后台启动,不占用当前的命令窗口
       daemonize  yes 
       //配置远程登录,就是配置访问的IP地址,可以通过 netstat -ntlp 查看绑定的网卡信息
       bind 0.0.0.0
       //端口配置
       port 6379
       //工作目录,缓存中的数据如果需要持久化,持久化的目录,注意:这里要创建一个data文件夹!!!, mkdir data
       dir /usr/local/redis-6.2.5/data
       //日志文件:redis运行的日志文件存放目录,放到上面的工作目录下
       logfile "redis_6379.log"
       //密码配置,默认是注释了,打开配置
       requirepass $HaoBa@123.com
      
    3. 上面配置修改完之后,执行启动命令bin/redis-server conf/redis_6379.conf,会发现没有反应,需要到/usr/local/redis-6.2.5/data/redis_6379.log日志文件下产看,redis启动的状态
  6. redis的关闭
    1. 方式一:执行/usr/local/redis-6.2.5/bin/redis-cli.sh shutdown 停止服务
    2. 方式二:kill -9 PID,PID为应用进程的id
    3. 注意:如果设置了密码,关闭时需要输入密码:redis-cli.sh '$HaoBa@123.com' shutdown
  7. 总结
    1. redis就是一个软件,需要安装,下载的包是一个C语言源码包redis-6.2.5.tar.gz,需要自己编译后,再进行安装
    2. 跟mysql一样,但是mysql不需要进行解压,直接安装
    3. /usr/local/redis-6.2.5/bin下可执行文件说明

       redis-server 启动redis服务命令
       redis-cli  redis的客户端连接命令
       redis-check-aof 检查aof命令
       redis-check-rdb 检查rdb备份命令
       redis-benchmark redis 性能测试命令
       redis-sentinel  redis的哨兵机制启动命令
      
  8. MAC系统注意事项:
    1. 安装到/usr/local下可能没有权限,上面的命令需要加sudo
    2. 通过sudo chmod 777 /usr/local/redis-6.2.5/,给该目录下开放所有权限,否则无法操作redis数据
  9. 注意:以上只是单机版安装,如果需要集群搭建,请网上查阅资料

Redis的常用命令

  1. 启动redis,然后使用/usr/local/redis-6.2.5/bin/redis-cli,连接redis,然后就可以使用命令了

     keys *  查看所有的key,会扫描所有的数据, 阻塞其他操作, 不建议使用
     dbsize 查看key的数量,性能很快, 时间复杂度0(1)
     exists key 判断是否存在对应的key,如果存在 返回1 ,如果不存在, 返回 0 
     expire key seconds  给对应的key设置过期时间, 单位是秒
     ttl key   获取key的剩余有效时间,-1 代表永久有效 -2 代表无效的键
     persist key  把一个有过期时间的key变为一个永久的key
     del key 根据指定key删除记录, 可以一次删除多个可以
     type key 用来获取key对应的数据的类型
     flushdb  慎用 清空数据库记录
     flushall 清空所有的数据库记录
     info  查看服务器信息
    

Redis的数据类型

  1. 数据类型指的是key—value中的value的数据类型

String

  1. 字符串的值最大512M
  2. 存放的value可以是字符串, 数值, 二进制,json数据
  3. 常用命令

     //get,set,del,设置、取值、删除
     127.0.0.1:6379> set name coderzhong
     27.0.0.1:6379> get name "coderzhong"
     27.0.0.1:6379> del name
        
     //incr,decr,incrby,decrby:自增,自减
     set count 100   //OK
     incr count      //(integer) 101 自增1
     decr count      //(integer) 100 自减1
     INCRBY count 50 //(integer) 150 自增50
     DECRBY count 50 //(integer) 100 自减50
        
     //set,setnx,setxx,setex
     //set: 新增或者修改, 不存在则新增, 存在则修改
     //setnx: 新增操作, 存在则不处理
     //setxx: 修改操作, 存在则修改, 不存在则不处理
     //setex: 设置key的过期时间
     set user:1:age 21  //OK
     set user:1:name lucy nx //OK
     set user:1:hobby football ex 20  //OK
     set user:1:age 24 xx  //OK
        
     //mget,mset 批量操作
     //通常n个请求,每个请求消耗网络,执行速度慢,可以将n个请求所执行的命令放到一起,一次执行,减少网络开销
        
     //getset,append,strlen
     //getset: 设置值并且返回原来的值
     //append: 字符串拼接
     //strlen: 字符串长度
     getset user:1:age 39 //"24"
     APPEND user:1:name lili //(integer) 8
     get user:1:name  //"lucylili"
     strlen user:1:name //(integer) 8
        
     //incrbyfloat,getrange,setrange
     //incrbyfloat: 小数递增, 传入负数递减
     //getrange: 获取部分数据
     //settrange: 局部设置替换
     set count 200 //OK
     INCRBYFLOAT count 26.5 //"226.5"
     set name heshengjun //OK
     getrange name 0 4 //"heshe"
     setrange name 0 li //(integer) 10
     get name   //"lishengjun"
    
  4. 应用场景
    1. 缓存功能:字符串最经典的使用场景,redis作为缓存层,MySQL作为储存层,绝大部分请求数据都是redis中获取,由于redis具有支撑高并发特性(具有原子性,单线程操作,不需要使用锁,安全),所以缓存通常能起到加速读写和降低后端压力的作用。
      1. 原理就是先从redis缓存中取值,取到返回,取不到则到mysql中取,放入到缓存中,然后返回
      2. 原子性:比如之前讲过分布式锁中的id生成器,所有的获取订单id的请求都到redis中去获取,这样就不需要使用分布式锁了
    2. 计数器:许多运用都会使用redis作为计数的基础工具,他可以实现快速计数、查询缓存的功能, 同时数据可以一步落地到其他的数据源。
      1. 如:视频播放数系统就是使用redis作为视频播放数计数的基础组件。
    3. 共享session:出于负载均衡的考虑,分布式服务会将用户信息的访问均衡到不同服务器上,用户刷新一次访问可能会需要重新登录,为避免这个问题可以用redis将用户session集中管理,在这种模式下只要保证redis的高可用和扩展性的,每次获取用户更新或查询登录信息都直接从redis中集中获取。
      1. n台服务器分布式部署,客户端每次访问可能访问不同的服务器,但是session需要共用,可以用一台机器专门开启redis公用
    4. 限速:处于安全考虑,每次进行登录时让用户输入手机验证码,为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率。
      1. 有效时间,设置验证码的有效时间–> set key value ex 180

Hash

  1. value的数据是一个Hash结构

     //类似这个结构:key是user  value是{}
     {
         user : {
             id : 1,
             name : “jake”,
             age: 18
         }
     }
    
  2. 常用命令
    1. 所有的hash命令都是以h开头
     //hget,hset,hdel
     //hget 根据key和field获取对应的字段值
     //hset  根据key和field设置对应的属性值
     //hdel  根据key和field删除对应的值
     hset user:id:1 name zh //(integer) 1
     hget user:id:1 name    //"zh"
     hset user:id:1 age 22  //(integer) 1
     hget user:id:1 //(error) ERR wrong number of arguments for 'hget' command
     hget user:id:1 age  //"22"
     hdel user:id:1 age //(integer) 1
        
     //hexists,hlen
     //hexists: 判断字段是否存在
     //hlen: 判断有多少个field字段
     HEXISTS user:id:1 name //(integer) 1
     HEXISTS user:id:1 age //(integer) 0
     hlen user:id:1 //(integer) 1
        
     //hmget,hmset
     hmget: 获取多个字段的值
     hmset: 设置多个字段的值
     hmset user:id:2 name lucy age 21 hobby basketball //OK
     hmget user:id:2 name age hobby
     //
     1) "lucy"
     2) "21"
     3) "basketball"
        
     //hgetall hvals hkeys
     hgetall: 慎用
     hgetall: 根据key返回hash的所有的字段和值
     hvals: 根据key返回所有的值
     hkeys: 根据key返回所有的字段
        
     hgetall user:id:2
     1) "name"
     2) "lucy"
     3) "age"
     4) "21"
     5) "hobby"
     6) "basketball"
     hvals user:id:2
     1) "lucy"
     2) "21"
     3) "basketball"
     hkeys user:id:2
     1) "name"
     2) "age"
     3) "hobby"
        
     //hsetnx,hincrby,hincrbyfloat
     hsetnx: 修改field字段的值, 如果值存在, 则不修改, 如果不存在, 进行修改操作
     hincrby: 对于字段field进行递增或者递减操作
     hincrbyfloat:对于字段field进行递增或者递减操作
        
     hsetnx user:id:2 height 170 //(integer) 1
     HINCRBY user:id:2 height 2 //(integer) 172
     HINCRBYFLOAT user:id:2 height -28.9 //"143.10000000000000001"
    
  3. 应用场景
    1. 哈希结构相对于字符串序列化缓存信息更加直观,并且在更新操作上更加便捷。所以常常用于用户信息等管理,但是哈希类型和关系型数据库有所不同,哈希类型是稀疏的,而关系型数据库是完全结构化的,关系型数据库可以做复杂的关系查询,而redis去模拟关系型复杂查询开发困难,维护成本高
    2. 使用字符串与hash的区别
      1. 使用字符串
        1. 将对象转化为json串然后存储
        2. 修改、新增一个属性时比较麻烦,需要加载整条数据
      2. 使用hash
        1. 可以直接访问属性

List

  1. value是一个双向链表,值是有顺序的,可以重复;
  2. 操作命令:l开头都是从左边(头部)开始的,r开头的都是从右边(尾部)开始的
  3. 从左边开始的索引是:0,1,2…,从右边开始的是:-1,-2,…
    1. 遍历如果不知道长度,可以从[0,-1]遍历
  4. 常用的命令

     rpush
     lpush
     linsert
     lpop
     rpop
     lrem
     ltrim
     lrange
     lindex
     llen
     lset
     //模拟消息队列, 阻塞状态
     blpop brpop
    
    1. blpop brpop可以阻塞线程,设置阻塞时长,有数据就直接返回,没有数据等待,到达时间后返回

       // 消息生产者: R,往队列中插入数据
       PUSH user:1:friends e
       //消息消费者:b ,队列中取数据,等待时长为50s
       lpop user:1:friends 50
      
  5. 应用场景
    1. 消息队列: redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端是用lupsh从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞时的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性.
    2. 朋友圈点赞:
      1. 规定:朋友圈内容的格式:
        1. 内容: user:x:post:x content来存储;
        2. 点赞: post:x:good list来存储;
      2. 创建一条朋友圈内容:set user:1:post:91 'hello redis';
      3. 点赞:

         lpush post:91:good '{id:1,name:lucy,img:xxx.jpg}' 
         lpush post:91:good '{id:2,name:tom,img:xxx.jpg}' 
         lpush post:91:good '{id:3,name:jack,img:xxx.jpg}' 
        
      4. 查看有多少人点赞: llen post:91:good
      5. 查看哪些人点赞:lrange post:91:good 0 -1
      6. 思考,如果用数据库实现这个功能,SQL会多复杂??

Set

  1. 无序,不允许元素重复
  2. 常用的命令

     sadd方法:向名称为key的set中添加元素 
     smembers查看set集合的元素s
     srem方法:删除set集合元素s
     spop方法:随机返回删除的keys
     sdiff方法:返回两个集合中不同的元素(哪个集合在前面就以哪个集合为标准)s
     sdiffstore方法:将返回的不同元素存储到另外一个集合里 
         小结:这里是把set1和set2的不同元素(以set1为准),存储到set3集合里s
     sinter方法:返回集合的交集s
     sinterstore方法,返回交集结果存入set3中s
     sunion方法:取并集s
     sunionstore方法,取得并集,存入set3s
     smove方法:从一个set集合移动到另外一个set集合里.相当于剪切复制s
     scard方法:查看集合里元素个数s
     sismember方法:判断某元素是否集合中的元素s
     srandmember方法:随机返回一个元素
    
  3. 应用
    1. 去重、抽奖、好友推荐等功能

ZSet

  1. 有序集合、不允许重复
  2. 常用命令

     zadd 向有序集合中添加一个元素,该元素如果存在,则更新顺序. 在重复插入的时候,会根据顺序属性更新. 
     zrange zset1 [start] [stop] 根据分数从小到大排序
     zrevrange zset1 [start] [stop] 根据分数从大到小排序
     zrem 删除名称为key的zset中的元素member
     zincrby 以指定值去自动递增或者减少,用法和之前的incryby类型
     zrangebyscore指定区间范围的数据进行返回
     zremrangebyrank zset1 [start] [stop] 删除指定位置的元素.
     zremrangebysocre zset1 [min] [max] 删除指定分数范围的数据
     zrank zet1 [member] 返回排序索引从小到大排序(索引排序之后再找索引) 
         注意:一个顺序号 一个是所有 zrank返回的是索引
     zrevrank zet1 [member] 返回排序索引 从大到小(降序之后再找索引)
     zcard zset1 返回集合里所有元素的个数
     zcount 返回集合中score在给定区间中的数量
    
  3. 应用:
    1. 排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面,按照时间、按照播放量、按照获得的赞数等。

Redis数据类型进阶

Redis数据内存淘汰策略

  1. 数据存储在内存中,内存的大小是有限的,当数据存储超过内存,是需要将数据淘汰
  2. 在Redis 中,允许用户设置最大的内存大小
    1. Redis的内存不一定是机器的内存,可以配置,因为一台机器可以有n个项目的redis
    2. 在配置文件中配置(conf/redis_6379.conf)

       maxmemory 1G
      
  3. 淘汰规则
    1. 在配置文件conf/redis_6379.conf中可以看到有8种,数据淘汰规则

       # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
       # is reached. You can select one from the following behaviors:
       # 设定超时时间的数据中,删除最不常用的数据
       # volatile-lru -> Evict using approximated LRU, only keys with an expire set.
       #查询所有的key 中最不常使用的数据进行删除
       # allkeys-lru -> Evict any key using approximated LRU.
       # 从所有配置了过期的时间的键中驱逐使用频率最少的键
       # volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
       # 从所有键中驱逐使用频率最少的键
       # allkeys-lfu -> Evict any key using approximated LFU.
       # 在已经设定了超时的数据中随机删除
       # volatile-random -> Remove a random key having an expire set.
       # 查询所有的 key 之后随机删除
       # allkeys-random -> Remove a random key, any key.
       # 查询全部设定超时时间的数据,追后马上排序,将马上将要过期的数据进行删除操作
       # volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
       # 如果设置为该属性,则不会进行删除操作,如果内存溢出则报错返回,默认策略
       # noeviction -> Don't evict anything, just return an error on write operations.
              
       //默认的内存溢出数据淘汰策略,noeviction不采取任何策略,直接报错
       # The default is:
       # maxmemory-policy noeviction
      

Java简单使用

  1. 创建一个Maven项目
    1. 打开IDEA->new->Module->选择Maven->next->输入项目称、路径->Finish
    2. pom文件添加依赖

       <dependencies>
           <dependency>
               <groupId>redis.clients</groupId>
               <artifactId>jedis</artifactId>
               <version>3.1.0</version>
           </dependency>
           <dependency>
               <groupId>org.projectlombok</groupId>
               <artifactId>lombok</artifactId>
               <version>1.18.20</version>
           </dependency>
           <dependency>
               <groupId>junit</groupId>
               <artifactId>junit</artifactId>
               <version>4.13</version>
               <scope>test</scope>
           </dependency>
       </dependencies>
      
  2. 创建BasicDemo测试类

     public class BasicDemo {
         private Jedis jedis=null; //客户端连接对象
        
         //连接jedis
         @Before
         public void init() throws Exception {
             jedis=new Jedis("127.0.0.1",6379);
         }
         //关闭jedis
         @After
         public void close() throws Exception {
             if(jedis!=null){
                 jedis.close();
             }
         }
        
         //存取数据jedis
         @Test
         public void test1() throws Exception {
             String val = jedis.set("hello", "java");
             System.out.println("val = " + val);
         }
     }
    

pipeline

  1. redis执行的实际流程
    1. 客户端(其他java服务器)代码将操作命令发送给redis
    2. redis获取到命令执行命令
    3. redis将执行结果返回给客户端
  2. 性能分析
    1. 整个过程耗时间的是1、3,因为需要网络传输;2基本不耗时间,us级别
  3. 性能优化
    1. 客户端一次性将n个命令打包发送给redis,redis一次性执行n个命令,然后将最终的执行结果一次性返回给客户端
    2. 问题:
      1. 客户端如何批量发送命令?使用pipeline
      2. 这些命令必须之间没有相互依赖关系
  4. 使用示例

     //模拟发送写入10w个数据,使用循环遍历
     @Test
     public void testBasic() throws Exception {
         long start=System.currentTimeMillis();
         for (int i = 0; i < 100000; i++) {
             jedis.set("base:key"+i,"value");
         }
         long end=System.currentTimeMillis();
         // 10w->51s
         System.out.println("耗时时长: "+(end-start));
     }
        
     //模拟发送写入10w个数据 pipeline,缩短了很多时间
     //Pipeline不是原子性操纵,mset是原子性操作,但是如果使用
     @Test
     public void testPipeline() throws Exception {
         long start=System.currentTimeMillis();
         Pipeline pipelined = jedis.pipelined();//创建一个命令管道
         for (int i = 0; i < 1000000; i++) {
             pipelined.set("pipe2:key"+i,"value");// 把需要执行的命令放管道中,批量发送
         }
         //同步执行命令
         pipelined.sync();
         long end=System.currentTimeMillis();
         // 10w->0.9s
         // 100w-->6.2
         System.out.println("耗时时长: "+(end-start));
     }
    
  5. Pipeline与mset
    1. Pipeline不是原子性操纵,mset是原子性操作
    2. 但是redis的数据类型不是所有类型都都支持mset操作,比如hash就不支持

发布订阅

  1. 生产者与消费者
    1. 生产者:生产数据的一方
    2. 消费者:使用数据的一方
  2. 场景分析
    1. 通常情况下并不是消息生产之后立即要给消费者消费,因为消费者可能在处理其他事务(类似快递将物件放到快递柜一样,客户不一定有时间去取),因此中间需要一个缓冲区,将生产者与消费者进行解耦
    2. 生产者只需要将数据发送给缓冲区,消费者只需要订阅缓冲区就可以使用数据
    3. 这个缓冲区可以是redis或者其他
  3. 角色
    1. 发布者 publisher
    2. 订阅者 subscriber
    3. 频道 channel
  4. 代码举例
    1. 消息发布者PublisherDemo

       //生成/发布消息
       @Test
       public void produce() throws Exception {
           for (int i = 0; i < 100; i++) {
               jedis.publish("9527","hello"+i);
               TimeUnit.MICROSECONDS.sleep(10);
           }
       }
      
    2. 订阅者subscriber

       //订阅channel 消费消息,该方法一直监听,可以使用unsubscirbe,取消订阅
       @Test
       public void produce() throws Exception {
           JedisPubSub jedisPubSub=new JedisPubSub() {
               @Override
               public void onMessage(String channel, String message) {
                   System.out.println("channel = " + channel);//哪个频道
                   System.out.println("message = " + message);// 消息内容
          
               }
           };
           jedis.subscribe(jedisPubSub,"9527");
       }
      

Bitmap

  1. 也是一种数据结构,就是用二进制位来存储信息,减少存储空间
  2. 比如记录用户登录的次数,如果每个用户存储一次最后统计用户量,这样数据会存储越来越大。但是如果用n个二进制位每一位来表示一位用户,1/0代表登录、退出,则会节省很多存储空间而且提高效率
  3. 常用命令

     //setbit设置指定的bit
     setbit qq:uv 1003 //1
     //获取指定位置的值, 返回1或者0 
     getbit qq:uv //2
     //bitcount: 统计值为1的个数
     BITCOUNT qq:uv  //(integer) 2
     //bitop
     BITOP AND destkey key [key ...] ,对一个或多个 key 求逻辑并,并将结果保存到 destkey 。
     BITOP OR destkey key [key ...] ,对一个或多个 key 求逻辑或,并将结果保存到 destkey 。
     BITOP XOR destkey key [key ...] ,对一个或多个 key 求逻辑异或,并将结果保存到 destkey 。
     BITOP NOT destkey key ,对给定 key 求逻辑非,并将结果保存到 destkey 。
        
     //bitpos用来返回操作的索引位置
     BITPOS qq:uv 1 5 8
     其中 5: 代表从第5个字节开始查找 8 代表从第8个字节开始查找
    
  4. 代码举例

     @Test
     public void initData() throws Exception {
         //初始化用户登录数据
         jedis.setbit("user:login:20191206",1,"1");
         jedis.setbit("user:login:20191206",3,"1");
         jedis.setbit("user:login:20191206",5,"1");
         jedis.setbit("user:login:20191206",5,"1");
         jedis.setbit("user:login:20191206",8,"1");
    
         jedis.setbit("user:login:20191207",11,"1");
         jedis.setbit("user:login:20191207",3,"1");
         jedis.setbit("user:login:20191207",5,"1");
         jedis.setbit("user:login:20191207",18,"1");
    
         jedis.setbit("user:login:20191208",21,"1");
         jedis.setbit("user:login:20191208",13,"1");
         jedis.setbit("user:login:20191208",5,"1");
         jedis.setbit("user:login:20191208",28,"1");
     }
    
     //统计结果
     @Test
     public void resultData() throws Exception {
         //1  20191206 有多少用户登录
         System.out.println(jedis.bitcount("user:login:20191206")); //4
         //2  最近三天 20191206 20191207 20191208 有多少用户登录//位运算
         jedis.bitop(BitOP.OR,"user:login:last31",
                 "user:login:20191206","user:login:20191207","user:login:20191208");
         System.out.println(jedis.bitcount("user:login:last31"));//9
         //3  统计连续登录三天的用户
         jedis.bitop(BitOP.AND,"user:login:last32",
                 "user:login:20191206","user:login:20191207","user:login:20191208");
         System.out.println(jedis.bitcount("user:login:last32"));//1 --> 5
         System.out.println("=========");
         System.out.println(jedis.bitpos("user:login:last32", true));
     }
    

Hyperloglog

  1. Redis HyperLogLog 用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
  2. 在 Redis 里面,每个HyperLogLog键只需要花费12 KB内存,就可以计算接近 2^64(约42亿) 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
  3. 简单来说就是用来计数的,只能统计,不能拿到具体的某条数据
  4. 常用命令

     pfadd 将任意数量的元素添加到指定的 HyperLogLog 里面
     pfcount  用来统计元素的个数
     pfmerge 把多个key合并为一个key, 重复的元素会进行过滤
    
  5. 代码示例

     @Test
     public void initData() throws Exception {
         //初始化用户登录数据
         jedis.pfadd("pf:login:20191206","1");
         jedis.pfadd("pf:login:20191206","3");
         jedis.pfadd("pf:login:20191206","5");
         jedis.pfadd("pf:login:20191206","5");
         jedis.pfadd("pf:login:20191206","8");
     }
    
     @Test
     public void testLog() throws Exception {
         Pipeline pipelined = jedis.pipelined();
         for (int i = 0; i < 10000000; i++) {
             pipelined.pfadd("pf:user:login:20191207",""+i);
         }
         pipelined.sync();
         System.out.println(jedis.pfcount("pf:user:login:20191207"));
         //100w -->1436
         //1000w ---> 14219 -->14
     }
    

GEO

  1. GEO(地理信息定位): 存储经纬度, 计算两地距离, 计算范围
  2. 即用来存储、计算地位位置数据的
  3. 常见命令

     geoadd: 添加一个位置信息
     geopos: 获取地理位置信息
     geodist: 用来获取两个地理位置
     georadius: 获取指定范围内的数据
    
  4. 代码举例

     //GeaDemo测试类
     @Test
     public void initData() throws Exception {
         HotelPosition h1 = new HotelPosition(113.23121, 23.117933, "如家酒店");
         HotelPosition h2 = new HotelPosition(113.203641,23.382214, "速8酒店");
         HotelPosition h3 = new HotelPosition(113.361532,23.128617, "7天连锁酒店");
         HotelPosition h4 = new HotelPosition(113.258358,23.162526, "广州曼克顿酒店");
         jedis.geoadd("hotel",h1.getLng(),h1.getLat(),h1.getName());
         jedis.geoadd("hotel",h2.getLng(),h2.getLat(),h2.getName());
         jedis.geoadd("hotel",h3.getLng(),h3.getLat(),h3.getName());
         jedis.geoadd("hotel",h4.getLng(),h4.getLat(),h4.getName());
     }
    
     @Test
     public void handler() throws Exception {
         //李当前位置10km之内的酒店
         // 当前的位置 113.324981,23.150597
         List<GeoRadiusResponse> hotels = jedis.georadius("hotel", 113.324981, 23.150597, 10, GeoUnit.KM);
         for (GeoRadiusResponse hotel : hotels) {
             System.out.println(new String(hotel.getMember()));
         }
         //查看距离信息
         System.out.println(jedis.geodist("hotel", "如家酒店", "7天连锁酒店",GeoUnit.KM));
     }
        
     //HotelPosition模型类
     //默认生成:get、set方法、无参构造、有参构造
     @Data@NoArgsConstructor@AllArgsConstructor
     public class HotelPosition {
         // 经度
         private double lng;
         // 纬度
         private  double lat;
         // 名字
         private String name;
     }
    
Table of Contents