Redis系列(十六)应用之两种缓存和两种队列

Posted1 by 呼延十 on June 2, 2020 Hot:

前言

Redis 是一个很强大的内存数据库,而依据我学习 Redis 的经验,网上最缺的资料不是 Redis 的实现原理,Redis 的运维等等。而是对于 Redis 的应用场景,这方面的资料简直少到令人发指。依据我的记忆,一年前,我搜索Redis 的 sorted set 具体可以应用在哪些地方, 得出的结论要么是泛泛而谈,要么就开始讲解 sorted set 的一些命令的用法。而具体的应用场景很少有人提及。

今天我把自己项目中对 Redis 的一些应用简单记录在这里,权当抛砖引玉,如果读者们有其他对于 Redis 的不错的应用,欢迎一起探讨和学习。

经过我的简单总结,目前我对 Redis 的应用,主要是用作两个部分。

缓存队列.

其中缓存应用场景又分为 DB 缓存和 API 缓存,队列应用分为延时队列和实时的多播队列。接下来将逐一进行简单的介绍。

DB 缓存

DB 缓存比较好理解,就是因为 MySQL 的响应时间比较慢,同时抗并发能力不足,因此对于很多的很多数据,我们用 Redis 挡在 MySQL 前面,对 MySQL 中的数据进行缓存,以此提高系统响应速度。

上面是比较好理解的部分,那么具体如何实施呢?

我们业务中可能几百张表,上面的查询语句可能有几千个,如果针对每一个语句编写缓存的代码,可能我们就累死了。

这个时候就需要用到一些缓存的框架,可以将查询出来的结果直接序列化,按照我们指定的 key 写入到 redis. 而在对应的查询语句 (Java 代码级别) 加上自动请求缓存的逻辑,如果 miss 掉了,再去查询 MySQL 数据库。

对于 DB 缓存,我个人的理解是,不要加过期时间,采用其他的方式来保证数据的一致性,可以在业务代码中添加删除 redis 中 cache 的代码(不推荐,对业务侵入性太强了), 也可以通过监听 binlog 的方式来在其他的进程中进行 cache 的同步更新。

API 缓存

API 缓存相较于 DB 缓存就比较简单了,它存在的目的就是对于一些频繁的业务请求进行缓存,比如一个用户请求推荐列表的时候,我们将计算出来的列表进行缓存,后续如果用户翻页,我们就可以直接走缓存出上次计算的结果。

API 缓存是需要设置过期时间的,一般按照自己的业务定个几分钟就好。

延时队列

延时队列是对 Redis 中有序集合这一个数据类型的经典应用。

在做推送相关的业务中,我们需要启动程序计算何时给某个用户推送某种文案的推送, 然后按照计算的时间,定时的将这条任务执行成功,将推送触达用户。

这里就涉及到延时队列的使用。

我们启动了两个程序,分为生产者和消费者。

生产者

生产者不断的进行计算,算出时间之后,将信息写入有序列表,其中有序列表的成员 (member) 是推送的相关信息,比如文案,策略,用户等。有序列表的分值 (score) 是这条推送应该被推送的时间。

消费者

消费者只需要不断的轮询有序列表,拿出分值等于当前时间戳的所有成员,之后解析成员变量,进行实际的推送即可。

多播的实时队列

这是对 Redis5.0 版本的新数据类型 stream 的一个应用。

不了解 Stream 的读者可以移步 Redis 的 Stream 介绍.

Stream 总体来讲是一个支持消息持久化,支持多播(类似于 kafka) 的消息队列。

个人觉得 stream 对比于 kafka, 优点在于轻量级,没有运维成本。缺点在于没有 kafka 吞吐量高,没那么耐操。

在搜索项目中,我们需要根据用户对个人信息的更改,实时地更新用户的索引信息,这是一个内部交互的过程。

为了达到实时更新索引的效果,我们可以使用 RPC 调用来搞。

2020-01-12-22-46-42

首先监听 MySQL 中用户数据的更改,之后拿到对应的修改信息,通过调用业务程序中提供的 rpc 接口,来进行实时的索引更新。

但是这个架构有个明显的缺点,那就是耦合严重且不易扩展。

如果现在我监听 binlog 的程序挂掉了,造成了死循环,那是否会把线上的服务的某一个接口打崩?

如果我线程程序挂了 10 分钟,再启动起来,这 10 分钟的更新数据就永远的拿不到了?

如果我线上实例变更了呢?增加或者减少了,都需要同步的重启监听 binlog 的程序。

而应用 stream 来进行了架构升级之后就不同了。升级之后的架构图如下:

2020-01-12-22-51-17

只是在监听 binlog 程序和线上服务中间加上一层消息队列,情况立刻不一样了。(果然,架构的是就没有加一个中间层解决不了的,如果解决不了,那就再加一层).

我们启动监听程序,将实时的索引修改写入到 stream, 之后线上的服务启动一个额外的线程,来不断的消费这个 stream, 拿到响应的信息进行解析,更新索引即可。

让我们看看上面提到的问题解决了么。

  • 如果现在我监听 binlog 的程序挂掉了,造成了死循环,那是否会把线上的服务的某一个接口打崩?不直接调用接口了,最多疯狂写入 redis,redis 扛得住

  • 如果我线程程序挂了 10 分钟,再启动起来,这 10 分钟的更新数据就永远的拿不到了?stream 是支持消息持久化的,不会因为没有消费就丢了

  • 如果我线上实例变更了呢?增加或者减少了,都需要同步的重启监听 binlog 的程序。不用,stream 支持多播,也就是多个消费者组独立消费,我们按照机器名+端口号分配消费者组,自动换扩展

其实还有其他好处,那就是实时更新的数据就放在这里了,任何其他需要此数据的程序,直接新建消费者组来消费即可。不会有任何的压力。

总结

Redis 是一个很强大的内存数据库,而且由于其生态的完善,依据 Redis 出现了很多扩展功能,这些里面有些是 Redis 官方作者提供的,有些是第三方的 lib, 但是他们都是实实在在的解决了生产过程中的某些痛点。

我目前在项目中对 Redis 的应用绝算不上多,主要还是用来做缓存而已。希望后续可以扩展出更多的应用,对 Redis 的应用能有更多心得,进一步提升项目的效率。

完。

以上皆为个人所思所得,如有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文链接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见个人博客——>呼延十