Redis-数据结构与经典使用场景
Redis本质上是一个基于内存的存储系统,被我们大量的使用在项目中,那提到为什么用Redis,那么不可避免的一个理由就是,够快,根据官方文档数据,单台机器Redis可以支持10W/S的QPS。淘宝等大型电商公司除开大促活动,一般也不会达到这个级别的QPS。那Redis为什么会那么快呢?
Redis单线程为什么这么快?
因为它所有的数据都在内存中,所有的运算都是内存级别的运算,而且单线程避免了多线程的切换性能损耗问题。正因为 Redis 是单线程,所以要小心使用 Redis 指令,对于那些耗时的指令(比如keys),一定要谨慎使用,一不小心就可能会导致 Redis 卡顿。
Redis6.0即将推出,这个版本会将单线程变为多线程,届时性能将大幅提高。
那既然是单线程,那如何处理并发客户端链接?
Redis的IO多路复用:redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。见如下图:
查看redis支持的最大连接数,在redis.conf文件中的“maxclients”参数可修改。默认10000
Redis基本结构
Redis常用5种数据结构string,hash,list,set,zset,如下图所示:
String
这种结构是最基本常用的的key-value结构 1. 基本操作命令
SET key value //存入字符串键值对
MSET key value [key value ...] //批量存储字符串键值对
SETNX key value //存入一个不存在的字符串键值对
GET key //获取一个字符串键值
MGET key [key ...] //批量获取字符串键值
DEL key [key ...] //删除一个键
EXPIRE key seconds //设置一个键的过期时间(秒)
INCR key //将key中储存的数字值加1
DECR key //将key中储存的数字值减1
INCRBY key increment //将key所储存的值加上increment
DECRBY key decrement //将key所储存的值减去decrement
2. 分布式锁,利用setnx命令
SETNX product:10001 true //返回1代表获取锁成功
SETNX product:10001 true //返回0代表获取锁失败
DEL product:10001 //执行完业务释放锁
SET product:10001 true ex 10 nx //增加一个10S的超时时间,避免程序挂了,锁一直不释放
3. 计数器 下图是微信公众号里面的文章,利用INCR实现阅读数的增加
INCR article:readcount:{文章id} //文章阅读数+1
GET article:readcount:{文章id} //查看文章阅读数
4. 分布式系统全局ID 如果是单机系统,生成ID可以直接利用数据库自增ID来实现,但是涉及到分布式系统的话,数据会涉及到分库分表,所以仅仅利用数据库自增是无法解决问题的。当然目前有很多分布式ID解决方案。目前主流的有推特的“雪花算法”,美团的“Leaf框架”,以及我们今天要将的基于Redis的解决方案。 利用Redis生成全局的自增ID,我们第一个想到的就是利用INCR原子加命令
INCR orderId //每执行一行,orderId就会+1
但是这种解决方案有一个显著的问题: 如果我一个大型系统,有几百张数据库表需要生成唯一ID,那么每张表生成一条数据之前都要调用INCR命令去生成一个唯一ID,这对于Redis资源是极大的浪费。所以我们稍微改造一下。
INCRBY orderId 1000 //一次性拿1000个orderId
如下图 : 每台服务一次性拿1000个ID,保存到服务器自己内存中,然后自己服务器内部保障这1000个ID的分配。
hash
hash这种结构,可以理解为一个key关联的value是一个map。它比上面描述单纯的k-v结构更易于管理,消耗的内存和cpu也更少。但是缺点是过期时间无法利用在field上,而且集群架构下不适合大规模使用。 1. 常用操作命令
HSET key field value //存储一个哈希表key的键值
HSETNX key field value //存储一个不存在的哈希表key的键值
HMSET key field value [field value ...] //在一个哈希表key中存储多个键值对
HGET key field //获取哈希表key对应的field键值
HMGET key field [field ...] //批量获取哈希表key中多个field键值
HDEL key field [field ...] //删除哈希表key中的field键值
HLEN key //返回哈希表key中field的数量
HGETALL key //返回哈希表key中所有的键值
HINCRBY key field increment //为哈希表key中field键的值加上增量increment
2. 电商的购物车 我们以下图,一个电商购物车为例,看如何使用hash结果来实现购物车的一些功能 假设我们以用户ID(1001)为hash的KEY,商品ID(100088)作为某个用户的KEY里面的field,商品数量为field里面的value,那么我们利用hash可以做如下操作:
hset cart:1001 10088 1 //给用户1001添加商品10088,数量为1
hincrby cart:1001 10088 1 //用户1001将商品10088购买数量+1
hlen cart:1001 //获得用户1001购物车商品总数
hdel cart:1001 10088 //用户1001将10088商品从购物车删除
hgetall cart:1001 //获得用户1001购物车的所有商品以及购买数量
list
list结构,类似一个双向队列,队两头都可以进队和出队,而且该结构还实现了阻塞队列的功能。如下图: 1. 常用操作命令
LPUSH key value [value ...] //将一个或多个值value插入到key列表的表头(最左边)
RPUSH key value [value ...] //将一个或多个值value插入到key列表的表尾(最右边)
LPOP key //移除并返回key列表的头元素
RPOP key //移除并返回key列表的尾元素
LRANGE key start stop //返回列表key中指定区间内的元素,区间以偏移量start和stop指定
BLPOP key [key ...] timeout //从key列表表头弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
BRPOP key [key ...] timeout //从key列表表尾弹出一个元素,若列表中没有元素,阻塞等待timeout秒,如果timeout=0,一直阻塞等待
2. 利用list实现常用数据结构
Stack(栈) = LPUSH + LPOP //遵循先进后出
Queue(队列)= LPUSH + RPOP //遵循先进先出
Blocking MQ(阻塞队列)= LPUSH + BRPOP //遵循先进先出,出队如果没有元素会阻塞
3. 微博或者微信公众号消息流 拿微信公众号举例,假设我关注了MacTalk这个公众号,然后MacTalk今天发布了一篇ID=10080的文档出来了,如下图: 那么redis用如下命令可以实现:
LPUSH {订阅号消息}:{MacTalk的ID}:{我的ID} 10018 //MacTalk发了一条最新的文章
LRANGE {订阅号消息}:{MacTalk的ID}:{我的ID} 0 5 //查看最新的5条消息
set
set结构式不可重复集合 1. 常用操作命令
SADD key member [member ...] //往集合key中存入元素,元素存在则忽略,若key不存在则新建
SREM key member [member ...] //从集合key中删除元素
SMEMBERS key //获取集合key中所有元素
SCARD key //获取集合key的元素个数
SISMEMBER key member //判断member元素是否存在于集合key中
SRANDMEMBER key [count] //从集合key中选出count个元素,元素不从key中删除
SPOP key [count] //从集合key中选出count个元素,元素从key中删除
SINTER key [key ...] //交集运算
SINTERSTORE destination key [key ..] //将交集结果存入新集合destination中
SUNION key [key ..] //并集运算
SUNIONSTORE destination key [key ...] //将并集结果存入新集合destination中
SDIFF key [key ...] //差集运算
SDIFFSTORE destination key [key ...] //将差集结果存入新集合destination中
2. 微信抽奖小程序 假设我要实现如下图这样的微信抽奖功能 参与抽奖者5人,这5人的ID分别是1001-1005。
1) 5个人点击参与抽奖加入集合
SADD lottery 1001 1002 1003 1004 1005
2) 查看参与抽奖的所有用户ID
SMEMBERS lottery
3) 抽取3名获奖者
SRANDMEMBER lottery 3 //可重复获奖,用户ID不会从集合中删除,可以参与下次抽奖
SPOP lottery 3 //不可重复获奖,用户ID从集合中删除,无法参与下次抽奖
3. 社交软件的点赞,收藏模型 点赞收藏在社交软件中是最常见的功能,用Redis可以轻松实现。
1) 用户1001给你这条消息点了个赞
SADD thumbsUp:{消息ID} 1001
2) 用户1001取消点赞
SREM thumbsUp:{消息ID} 1001
3) 检查用户1001是否点过赞
SISMEMBER thumbsUp:{消息ID} 1001
4) 获取点赞的用户列表
SMEMBERS thumbsUp:{消息ID}
5) 获取点赞用户数
SCARD thumbsUp:{消息ID}
4. 社交软件的关注模型 这也是社交软件最常见的功能,例如我关注的人,他关注的人,我和他共同关注的人,我可能认识的人等。如下图 在了解实现之前,先要理一下Redis集合操作。见如下图: 图上有三个几个,set1,set2,set3,那么我们执行如下命令:
1) 这个很简单就是求交集,结果集合只有c
SINTER set1 set2 set3 -> {c}
2) 第二个求并集,也很好理解,结果集合有a,b,c,d,e
SUNION set1 set2 set3 -> { a,b,c,d,e }
3) 第三个求差集,这个有点难理解,是以set1作为基准,然后减去set2,set3中出现过的元素,最终得到set1剩下的元素
SDIFF set1 set2 set3 -> { a } // PS:set1->{a,b,c} 减去 set2+set3->{b,c,d,e} 所以set1剩下{a}
接下来我们模拟一个三国人物的关注模型
1) 刘备关注了诸葛亮,关羽,张飞,孙权
刘备set-> {诸葛亮,关羽,张飞,孙权}
2) 曹操关注了刘备,关羽,诸葛亮,孙权,郭嘉
曹操set-> {刘备,关羽,诸葛亮,孙权,郭嘉}
3) 孙权关注了刘备,曹操,周瑜
孙权set-> {刘备,曹操,周瑜}
4) 刘备点开了曹操的新浪微博页面,查看“共同关注的人”
SINTER 刘备set 曹操set--> {诸葛亮,关羽,孙权}
5)刘备点开了曹操的新浪微博页面,查看“我关注的人也关注了他”,这个实际上就要判断刘备关注的人里面,有哪些人关注了曹操
SISMEMBER 诸葛亮set 曹操
SISMEMBER 关羽set 曹操
SISMEMBER 张飞set 曹操
SISMEMBER 孙权set 曹操
6)刘备在曹操的新浪微博页面,查看“我可能认识的人”,实际上就是把曹操关注的而刘备没有关注的人列出来
SDIFF 曹操set 刘备set -> {郭嘉}
- 电商商品筛选模型 在电商购物中,我们一般都会通过条件来筛选商品,例如购买手机如下图: 这里面筛选条件有,品牌,操作系统,CPU品牌,内存等。那么这些商品在入库之前就会按照商品属性填充到各种set里面去。例如:
SADD brand:huawei P30 //P30放到华为品牌筛选条件下
SADD brand:xiaomi mi-6X //mi-6x放到小米品牌筛选条件下
SADD brand:iPhone iphone8 //iphone8 放到iphone品牌筛选条件下
SADD os:android P30 mi-6X //P30,mi-6X放到安卓操作系统筛选条件下
SADD cpu:brand:intel P30 mi-6X //P30,mi-6X放到CPU使用intel筛选条件下
SADD ram:8G P30 mi-6X iphone8 //P30,mi-6X,iphone8都是8G手机,就放到8G这个筛选条件下
那么我要找安卓系统,CPU使用intel然后内存8G的手机,只要执行下面的命令:
SINTER os:android cpu:brand:intel ram:8G //最终找到这两个手机{P30,mi-6X}
zset
zset与set结构的不同在于,zset增加了一个score的属性,这个score属性主要用来排名的,下面我们来看看有哪些场景使用。 1. 常用命令操作
ZADD key score member [[score member]…] //往有序集合key中加入带分值元素
ZREM key member [member …] //从有序集合key中删除元素
ZSCORE key member //返回有序集合key中元素member的分值
ZINCRBY key increment member //为有序集合key中元素member的分值加上increment
ZCARD key //返回有序集合key中元素个数
ZRANGE key start stop [WITHSCORES] //正序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES] //倒序获取有序集合key从start下标到stop下标的元素
//并集计算,举例(ZUNIONSTORE zset3 2 zset1 zset2)这句的意思是,zset1和zset2求并集得到的结果放入新的zset3里面
ZUNIONSTORE destkey numkeys key [key ...]
//交集计算,举例(ZINTERSTORE zset3 2 zset1 zset2)这句的意思是,zset1和zset2求交集得到的结果放入新的zset3里面
ZINTERSTORE destkey numkeys key [key ...]
2. 热搜榜或者新闻排行榜 下图是微博热搜的展示图: 假设今天日期是20190819,我们zset来实现新闻热搜排行榜:
1) 点击一次”守护香港“这个新闻,那么此新闻的score属性+1
ZINCRBY hotNews:20190819 1 守护香港
2) 展示当前热搜新闻的前10名(最后的WITHSCORES代表查询出的结果包含具体的分数)
ZREVRANGE hotNews:20190819 0 10 WITHSCORES
那么我们要做一个7日的新闻排名呢?如下图:
1) 首先我们需要把7天的新闻合并到"hotNews:20190813-20190819"这个zset中去
ZUNIONSTORE hotNews:20190813-20190819 7 hotNews:20190813 hotNews:20190814... hotNews:20190819
2) 然后再展示7日排名前十的新闻
ZREVRANGE hotNews:20190813-20190819 0 10 WITHSCORES