• 0

  • 508

Redis-数据结构与经典使用场景

机器猫

机器学习

1个月前

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  -> {郭嘉}
复制代码
  1. 电商商品筛选模型
    在电商购物中,我们一般都会通过条件来筛选商品,例如购买手机如下图:
    这里面筛选条件有,品牌,操作系统,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
复制代码
免责声明:文章版权归原作者所有,其内容与观点不代表Unitimes立场,亦不构成任何投资意见或建议。

机器学习

508

相关文章推荐

未登录头像

暂无评论