Java进阶-Redis基础
数据库分类
- 关系型数据库:
- Oracle,MySQL,SqlServer,DB2
- 特点:有一套标准的SQL语句,有事务功能
- 非关系型数据库(Nosql数据库)
- key-value存储数据库
- 这一类数据库主要会使用到一个哈希表,这个表中有一个特定的键和一个指针指向特定的数据。
- Key/value模型对于IT系统来说的优势在于简单、易部署。但是如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了。
- 举例如:Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB。
- 列存储数据库
- 这部分数据库通常是用来应对分布式存储的海量数据。键仍然存在,但是它们的特点是指向了多个列。这些列是由列家族来安排的。如:Cassandra, HBase, Ria
- 文档性数据库
- 文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似。
- 该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。
- 文档型数据库可以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高。如:CouchDB, MongoDb. 国内也有文档型数据库SequoiaDB,已经开源。
- 图形数据库
- 图形结构的数据库通其他行列以及刚性结构的SQL数据库不同.它是使用灵活的图形模型.并且能拓展到多个服务器上
- 常见的数据库有: Neo4J
- key-value存储数据库
- 非关系型数据库特点
- 数据模型比较简单(主要)
- 需要灵活性更强的应用系统
- 对数据库性能要求较高(主要)
- 不需要高度的数据一致性
- 对于给定key,比较容易映射复杂值的环境.
redis简介
- Redis 是一个简单的、高效的、分布式的,基于内存缓存工具,架设好服务器后,通过网络链接(类似数据库),提供Key-Value 式缓存服务。
- redis就是一个软件,通过这个软件可以进行数据存储,跟mysql一样
- 优势:
- 速度快
- 数据存储在内存中,读写速度快,读数据: 11w/s、写数据: 8w/s
- 提供丰富的数据类型
- mysql中的数据类型是固定的,查询出来数据需要进行转换,而且不支持集合类型
- redis可以直接传这些数据类型,String、List、Set、Zset、Hash
- 原子性操作
- Redis 所有操作都是原子性,意思就是要么执行成功、要么执行失败、完全不执行。
- 单个操作是原子性的。多个操作也支持事务,即原子性,通过 MULTI和 EXEC 指令包起来
- 丰富的特征
- Redis 还支持 publish/subscribe, 通知,key过期等特征
- 持久化机制
- 高可用分布式
- 速度快
- 为什么Redis会这么快
- 内存存储:所有的数据存放到内存当中
- 数据处理模型
- 避免上下文切换和竞态消耗
- 使用epoll模型,非阻塞IO
- 学习redis
- redis在线入门 : http://try.redis.io/
- redis 中文资料站: http://www.redis.cn/
- github: https://github.com/antirez/redis
- 官网: https://redis.io/
- redis命令参考: http://redisdoc.com/
redis安装(单机版)
- gcc安装:
yum -y install gcc automake autoconf libtool make
- 如果是mac系统,不用执行
- redis安装
- 下载
wget http://download.redis.io/releases/redis-6.2.5.tar.gz
- 解压
tar -zxvf redis-6.2.5.tar.gz -C /usr/local
- 是C源码,需要编译
- 解压到
/usr/local
目录下
- 编译:
make
cd /usr/local/redis-6.2.5
下,执行make编译成可执行文件
- 安装:
make PREFIX=/usr/local/redis-6.2.5 install
- 安装到指定目录下
- 下载
- 启动
- 进入安装目录:
cd /usr/local/redis-6.2.5/bin
- 启动服务:
- 方式1: 直接使用
redis-server
- 方式2: 使用动态参数
redis-server --port=6380
- 方式3: 使用配置文件启动(推荐)
- 方式1: 直接使用
- 客户端访问连接redis:
./redis-cli
redis-cli -h IP地址 -p 端口
:指定哪台主机的端口应用- 退出客户端 ctrl + C
- 进入安装目录:
- 如何使用配置文件启动
- 进入到redis安装目录下
cd /usr/local/redis-6.2.5
,该目录下有一个默认的配置文件redis.conf - 新建一个conf文件夹
mkdir conf
- 将当前目录的redis.conf文件copy到conf文件夹下
cp redis.conf conf/redis_6379.conf
- 因为分布式部署可能有n个redis启动,因此这个配置文件名需要唯一
cd /usr/local/redis-6.2.5
执行bin/redis-server conf/redis_6379.conf
,启动redis
- 进入到redis安装目录下
- 配置启动配置文件
- 上面的启动是前台启动,可以通过配置文件配置后台启动等其他功能,打开redis_6379.conf
-
常见设置如下
//配置后台启动:在后台启动,不占用当前的命令窗口 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
- 上面配置修改完之后,执行启动命令
bin/redis-server conf/redis_6379.conf
,会发现没有反应,需要到/usr/local/redis-6.2.5/data/redis_6379.log
日志文件下产看,redis启动的状态
- redis的关闭
- 方式一:执行
/usr/local/redis-6.2.5/bin/redis-cli.sh shutdown
停止服务 - 方式二:
kill -9 PID
,PID为应用进程的id - 注意:如果设置了密码,关闭时需要输入密码:
redis-cli.sh '$HaoBa@123.com' shutdown
- 方式一:执行
- 总结
- redis就是一个软件,需要安装,下载的包是一个C语言源码包
redis-6.2.5.tar.gz
,需要自己编译后,再进行安装 - 跟mysql一样,但是mysql不需要进行解压,直接安装
-
/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的哨兵机制启动命令
- redis就是一个软件,需要安装,下载的包是一个C语言源码包
- MAC系统注意事项:
- 安装到
/usr/local
下可能没有权限,上面的命令需要加sudo - 通过
sudo chmod 777 /usr/local/redis-6.2.5/
,给该目录下开放所有权限,否则无法操作redis数据
- 安装到
- 注意:以上只是单机版安装,如果需要集群搭建,请网上查阅资料
Redis的常用命令
-
启动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的数据类型
- 数据类型指的是key—value中的value的数据类型
String
- 字符串的值最大512M
- 存放的value可以是字符串, 数值, 二进制,json数据
-
常用命令
//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"
- 应用场景
- 缓存功能:字符串最经典的使用场景,redis作为缓存层,MySQL作为储存层,绝大部分请求数据都是redis中获取,由于redis具有支撑高并发特性(具有原子性,单线程操作,不需要使用锁,安全),所以缓存通常能起到加速读写和降低后端压力的作用。
- 原理就是先从redis缓存中取值,取到返回,取不到则到mysql中取,放入到缓存中,然后返回
- 原子性:比如之前讲过分布式锁中的id生成器,所有的获取订单id的请求都到redis中去获取,这样就不需要使用分布式锁了
- 计数器:许多运用都会使用redis作为计数的基础工具,他可以实现快速计数、查询缓存的功能, 同时数据可以一步落地到其他的数据源。
- 如:视频播放数系统就是使用redis作为视频播放数计数的基础组件。
- 共享session:出于负载均衡的考虑,分布式服务会将用户信息的访问均衡到不同服务器上,用户刷新一次访问可能会需要重新登录,为避免这个问题可以用redis将用户session集中管理,在这种模式下只要保证redis的高可用和扩展性的,每次获取用户更新或查询登录信息都直接从redis中集中获取。
- n台服务器分布式部署,客户端每次访问可能访问不同的服务器,但是session需要共用,可以用一台机器专门开启redis公用
- 限速:处于安全考虑,每次进行登录时让用户输入手机验证码,为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率。
- 有效时间,设置验证码的有效时间–> set key value ex 180
- 缓存功能:字符串最经典的使用场景,redis作为缓存层,MySQL作为储存层,绝大部分请求数据都是redis中获取,由于redis具有支撑高并发特性(具有原子性,单线程操作,不需要使用锁,安全),所以缓存通常能起到加速读写和降低后端压力的作用。
Hash
-
value的数据是一个Hash结构
//类似这个结构:key是user value是{} { user : { id : 1, name : “jake”, age: 18 } }
- 常用命令
- 所有的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"
- 应用场景
- 哈希结构相对于字符串序列化缓存信息更加直观,并且在更新操作上更加便捷。所以常常用于用户信息等管理,但是哈希类型和关系型数据库有所不同,哈希类型是稀疏的,而关系型数据库是完全结构化的,关系型数据库可以做复杂的关系查询,而redis去模拟关系型复杂查询开发困难,维护成本高
- 使用字符串与hash的区别
- 使用字符串
- 将对象转化为json串然后存储
- 修改、新增一个属性时比较麻烦,需要加载整条数据
- 使用hash
- 可以直接访问属性
- 使用字符串
List
- value是一个双向链表,值是有顺序的,可以重复;
- 操作命令:l开头都是从左边(头部)开始的,r开头的都是从右边(尾部)开始的
- 从左边开始的索引是:0,1,2…,从右边开始的是:-1,-2,…
- 遍历如果不知道长度,可以从[0,-1]遍历
-
常用的命令
rpush lpush linsert lpop rpop lrem ltrim lrange lindex llen lset //模拟消息队列, 阻塞状态 blpop brpop
-
blpop brpop可以阻塞线程,设置阻塞时长,有数据就直接返回,没有数据等待,到达时间后返回
// 消息生产者: R,往队列中插入数据 PUSH user:1:friends e //消息消费者:b ,队列中取数据,等待时长为50s lpop user:1:friends 50
-
- 应用场景
- 消息队列: redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端是用lupsh从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞时的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性.
- 朋友圈点赞:
- 规定:朋友圈内容的格式:
- 内容: user:x:post:x content来存储;
- 点赞: post:x:good list来存储;
- 创建一条朋友圈内容:
set user:1:post:91 'hello redis';
-
点赞:
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}'
- 查看有多少人点赞:
llen post:91:good
- 查看哪些人点赞:
lrange post:91:good 0 -1
- 思考,如果用数据库实现这个功能,SQL会多复杂??
- 规定:朋友圈内容的格式:
Set
- 无序,不允许元素重复
-
常用的命令
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方法:随机返回一个元素
- 应用
- 去重、抽奖、好友推荐等功能
ZSet
- 有序集合、不允许重复
-
常用命令
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在给定区间中的数量
- 应用:
- 排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面,按照时间、按照播放量、按照获得的赞数等。
Redis数据类型进阶
Redis数据内存淘汰策略
- 数据存储在内存中,内存的大小是有限的,当数据存储超过内存,是需要将数据淘汰
- 在Redis 中,允许用户设置最大的内存大小
- Redis的内存不一定是机器的内存,可以配置,因为一台机器可以有n个项目的redis
-
在配置文件中配置(
conf/redis_6379.conf
)maxmemory 1G
- 淘汰规则
-
在配置文件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简单使用
- 创建一个Maven项目
- 打开IDEA->new->Module->选择Maven->next->输入项目称、路径->Finish
-
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>
-
创建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
- redis执行的实际流程
- 客户端(其他java服务器)代码将操作命令发送给redis
- redis获取到命令执行命令
- redis将执行结果返回给客户端
- 性能分析
- 整个过程耗时间的是1、3,因为需要网络传输;2基本不耗时间,us级别
- 性能优化
- 客户端一次性将n个命令打包发送给redis,redis一次性执行n个命令,然后将最终的执行结果一次性返回给客户端
- 问题:
- 客户端如何批量发送命令?使用pipeline
- 这些命令必须之间没有相互依赖关系
-
使用示例
//模拟发送写入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)); }
- Pipeline与mset
- Pipeline不是原子性操纵,mset是原子性操作
- 但是redis的数据类型不是所有类型都都支持mset操作,比如hash就不支持
发布订阅
- 生产者与消费者
- 生产者:生产数据的一方
- 消费者:使用数据的一方
- 场景分析
- 通常情况下并不是消息生产之后立即要给消费者消费,因为消费者可能在处理其他事务(类似快递将物件放到快递柜一样,客户不一定有时间去取),因此中间需要一个缓冲区,将生产者与消费者进行解耦
- 生产者只需要将数据发送给缓冲区,消费者只需要订阅缓冲区就可以使用数据
- 这个缓冲区可以是redis或者其他
- 角色
- 发布者 publisher
- 订阅者 subscriber
- 频道 channel
- 代码举例
-
消息发布者PublisherDemo
//生成/发布消息 @Test public void produce() throws Exception { for (int i = 0; i < 100; i++) { jedis.publish("9527","hello"+i); TimeUnit.MICROSECONDS.sleep(10); } }
-
订阅者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
- 也是一种数据结构,就是用二进制位来存储信息,减少存储空间
- 比如记录用户登录的次数,如果每个用户存储一次最后统计用户量,这样数据会存储越来越大。但是如果用n个二进制位每一位来表示一位用户,1/0代表登录、退出,则会节省很多存储空间而且提高效率
-
常用命令
//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个字节开始查找
-
代码举例
@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
- Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
- 在 Redis 里面,每个HyperLogLog键只需要花费12 KB内存,就可以计算接近 2^64(约42亿) 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
- 简单来说就是用来计数的,只能统计,不能拿到具体的某条数据
-
常用命令
pfadd 将任意数量的元素添加到指定的 HyperLogLog 里面 pfcount 用来统计元素的个数 pfmerge 把多个key合并为一个key, 重复的元素会进行过滤
-
代码示例
@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
- GEO(地理信息定位): 存储经纬度, 计算两地距离, 计算范围
- 即用来存储、计算地位位置数据的
-
常见命令
geoadd: 添加一个位置信息 geopos: 获取地理位置信息 geodist: 用来获取两个地理位置 georadius: 获取指定范围内的数据
-
代码举例
//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; }