35

Redis 应用场景汇总

 4 years ago
source link: https://mp.weixin.qq.com/s/UvNn1PMfncVhfADI9k8EWQ
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Redis作为一个非关系型数据库,除了在访问速度上拥有显著优势外,其本身支持的多种数据类型也非常有用,能覆盖系统开发中的很多应用场景。下面列举的场景有的是从网上其他人的博客里看到的,有的自己开发时尝试过的一些解决方案后记录下来的,希望能给以后的开发带来启发。

在说应用场景前先说一些是否觉得使用Redis的建议

使用建议

  • Redis 速度快是建立在内存数据库基础上的,但是一台服务器的内存要比磁盘金贵许多,所以在项目初期不要想什么都往 Redis 里放,这样当数据量上来后很快内存就会不够用,反而得不偿失。合理的利用有限的内存,将读(写)频繁的热数据放在 Redis 中才能更好感受到它带来的性能提升。

  • Redis 虽然提供了  RDB 和  AOF 两种持久化方式,但是普遍还是认为 Redis 的持久化并不是很靠谱。非常重要的数据不要依赖Redis来开发,或者最起码不要只在Redis中持久化

  • MySQL经过不断优化性能已经非常好,所以MySQL提供的数据结构和访问效率能满足的需求的情况下不要引入Redis,多引入一个组件就多一个可能的故障节点,尤其在保持数据一致性的场景中数据(比如用户余额)应该只放在数据库中,除非你知道怎么解决好系统的分布式事务。

缓存

作为 Key-Value 形态的内存数据库,Redis 最先会被想到的应用场景便是作为数据缓存。而使用 Redis 缓存数据非常简单,只需要通过 string 类型将序列化后的对象存起来即可,不过也有一些需要注意的地方:

  • 必须保证不同对象的 key 不会重复,并且使 key 尽量短,一般使用类名(表名)加主键拼接而成。

  • 选择一个优秀的序列化方式也很重要,目的是提高序列化的效率和减少内存占用。

  • 缓存内容与数据库的一致性,这里一般有两种做法:

  1. 只在数据库查询后将对象放入缓存,如果对象发生了修改或删除操作,直接清除对应缓存(或设为过期)。

  2. 在数据库新增和查询后将对象放入缓存,修改后更新缓存,删除后清除对应缓存(或设为过期)。

消息队列

Redis 中 list 的数据结构实现是双向链表,所以可以非常便捷的应用于消息队列(生产者 / 消费者模型)。消息的生产者只需要通过 lpush 将消息放入 list,消费者便可以通过 rpop 取出该消息,并且可以保证消息的有序性。如果需要实现带有优先级的消息队列也可以选择 sortedset 。而 pub/sub 功能也可以用作发布者 / 订阅者模型的消息。无论使用何种方式,由于 Redis 拥有持久化功能,也不需要担心由于服务器故障导致消息丢失的情况。

时间轴(Timeline)

list 作为双向链表,不光可以作为队列使用。如果将它用作栈便可以成为一个公用的时间轴。当用户发完微博后,都通过 lpush 将它存放在一个 key 为 LATEST_WEIBOlist 中,之后便可以通过 lrange 取出当前最新的微博。

循环链表

list 还可以作为循环链表使用 RPOPLPUSH source destination

命令 RPOPLPUSH 在一个原子时间内,执行以下两个动作:

  • 将列表  source 中的最后一个元素(尾元素)弹出,并返回给客户端。

  • 将  source 弹出的元素插入到列表  destination ,作为  destination 列表的的头元素。

如果 sourcedestination 相同,则列表中的表尾元素被移动到表头,并返回该元素,可以把这种特殊情况视作列表的旋转(rotation)操作。

比如有个进程来完成派单任务,需要将用户发送过来的申请依次派发给工作人员,那么就可以把工作人员的身份标示维护在循环列表中,从列表尾部读取每次读取身份标示后相应的标示都会被放到列表头如此循环往复。

排行榜

使用 sortedset 和一个计算热度的算法便可以轻松打造一个热度排行榜, zrevrangebyscore 可以得到以分数倒序排列的序列, zrank 可以得到一个成员在该排行榜的位置(是分数正序排列时的位置,如果要获取倒序排列时的位置需要用 zcard - zrank )。

计数器

计数功能应该是最适合 Redis 的使用场景之一了,因为它高频率读写的特征可以完全发挥 Redis 作为内存数据库的高效。在 Redis 的数据结构中, stringhashsortedset 都提供了 incr 方法用于原子性的自增操作,下面举例说明一下它们各自的使用场景:

  • 如果应用需要显示每天的注册用户数,便可以使用  string 作为计数器,设定一个名为  REGISTERED_COUNT_TODAY 的 key,并在初始化时给它设置一个到凌晨 0 点的过期时间,每当用户注册成功后便使用  incr 命令使该 key 增长 1,同时当每天凌晨 0 点后,这个计数器都会因为 key 过期使值清零。

  • 每条微博都有点赞数、评论数、转发数和浏览数四条属性,这时用  hash 进行计数会更好,将该计数器的 key 设为  weibo:weibo_id ,  hash 的 field 为  like_number 、  comment_number 、  forward_number 和  view_number ,在对应操作后通过  hincrby 使  hash 的 field 自增。

  • 如果应用有一个发帖排行榜的功能,便选择  sortedset 吧,将集合的 key 设为  POST_RANK 。当用户发帖后,使用  zincrby 将该用户 id 的 score 增长 1。  sortedset 会重新进行排序,用户所在排行榜的位置也就会得到实时的更新。

好友关系

这个场景最开始是是一篇介绍微博 Redis 应用的 PPT 中看到的,其中提到微博的 Redis 主要是用在在计数和好友关系两方面上,当时对好友关系方面的用法不太了解,后来看到《Redis 设计与实现》中介绍到作者最开始去使用 Redis 便是希望能通过 set 解决传统数据库无法快速计算集合中交集这个功能。后来联想到微博当前的业务场景,确实能够以这种方式实现,所以姑且猜测一下:

对于一个用户 A,将它的关注和粉丝的用户 id 都存放在两个 set 中:

  • A:follow :存放 A 所有关注的用户 id

  • A:follower :存放 A 所有粉丝的用户 id

    那么通过 sinter 命令便可以根据 A:followA:follower 的交集得到与 A 互相关注的用户。当 A 进入另一个用户 B 的主页后, A:followB:follow 的交集便是 A 和 B 的共同专注, A:followB:follower 的交集便是 A 关注的人也关注了 B。

分布式锁

在 Redis 2.6.12 版本开始, stringset 命令增加了三个参数:

  • EX :设置键的过期时间(单位为秒)

  • PX :设置键的过期时间(单位为毫秒)

  • NX | XX :当设置为 NX 时,仅当 key 存在时才进行操作,设置为 XX 时,仅当 key 不存在才会进行操作

    由于这个操作是原子性的,可以简单地以此实现一个分布式的锁,例如:

如果这个操作返回 false ,说明 key 的添加不成功,也就是当前有人在占用这把锁。而如果返回 true ,则说明得了锁,便可以继续进行操作,并且在操作后通过 del 命令释放掉锁。并且即使程序因为某些原因并没有释放锁,由于设置了过期时间,该锁也会在 1 秒后自动释放,不会影响到其他程序的运行。

倒排索引

倒排索引是构造搜索功能的最常见方式,在 Redis 中也可以通过 set 进行建立倒排索引,这里以简单的拼音 + 前缀搜索城市功能举例:

假设一个城市 北京 ,通过拼音词库将 北京 转为 beijing ,再通过前缀分词将这两个词分为若干个前缀索引,有: 北京bbebeijinbeijing 。将这些索引分别作为 set 的 key(例如: index:北 )并存储 北京 的 id,倒排索引便建立好了。接下来只需要在搜索时通过关键词取出对应的 set 并得到其中的 id 即可。

个人能力局限目前只知道这些数据类型的应用场景,如果各位有其他场景的应用经验欢迎交流补充,另外面试时被问到为何使用Redis不要简单的说因为快, 如果在系统中只使用了缓存这一个应用场景那么最起码可以提供一些MySQL的QPS和Redis的QPS数据或者程序在Redis使用前后的平均响应时长来印证你的观点。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK