Redis 高级功能
Redis 发布订阅
简介
Redis 发布订阅(pub/sub )是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息
Redis 客户端可以订阅任意数量的频道。
常用命令
################################## 订阅频道 ########################
## 订阅给定的一个或多个频道的信息
SUBSCRIBE channel [channel ...]
## 订阅一个或多个符合给定模式的频道
PSUBSCRIBE pattern [pattern ...]
################################## 发布频道 ########################
## 将信息发送到指定的频道
PUBLISH channel message
################################## 退订频道 ########################
## 退订给定的频道
UNSUBSCRIBE [channel [channel ...]]
## 退订所有给定模式的频道
PUNSUBSCRIBE [pattern [pattern ...]]
应用场景
这一功能最明显的用法就是构建实时消息系统,比如普通的即时聊天,群聊等功能
- 在一个博客网站中,有 100个粉丝订阅了你,当你发布新文章,就可以推送消息给粉丝们
- 微信公众号模式
微博,每个用户的粉丝都是该用户的订阅者,当用户发完微博,所有粉丝都将收到他的动态
新闻,资讯站点通常有多个频道,每个频道就是一个主题,用户可以通过主题来做订阅(如 RSS),这样当新闻发布时,订阅者可以获得更新
简单的应用场景的话,以门户网站为例,当编辑更新了某推荐板块的内容后
- CMS 发布清除缓存的消息到 channel(推送者推送消息)
- 门户网站的缓存系统通过 channel 收到清除缓存的消息(订阅者收到消息),更新了推荐板块的缓存
- 还可以做集中配置中心管理,当配置信息发生更改后,订阅配置信息的节点都可以收到通知消息
Redis 多数据库
Redis 下,数据库是由一个整数索引标识,而不是由一个数据库名称。默认情况下,一个客户端连接到数据库 0 。
Redis 配置文件中,下面的参数来控制数据库总数:
database 16 // (从 0 到 15)
## 切换数据库
select db
## 移动数据(将当前key 移动另一个库)
move key db
## 清除当前数据库的所有 key
flushdb
## 清除整个 Redis 的数据库所有 key
flushall
Redis 事务
Redis 事务可以一次执行多个命令,(按顺序地串行化执行,执行中不会被其它命令插入,不许加塞)
简介
Redis 事务可以一次执行多个命令(允许在一次单独的步骤中执行一组命令),并且带有以下两个重要的保证:
- Redis 会将一个事务中的所有命令序列化,然后按顺序执行
- 执行中不会被其它命令插入,不许出现加塞行为
批量操作在发送 EXEC 命令前被放入队列缓存。
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
Redis 不支持事务回滚
常用命令
## 取消事务,放执行事务块内的所有命令
DISCARD
## 执行所有事务块内的命令
EXEC
## 标记一个事务块的开始
MULTI
## 监视一个(或多个)key,如果在事务执行之前这个(或这些)key 被其他命令所改动,那么事务将被打断。
WATCH key [key ...]
## 取消 WATCH 命令对所有 key 的监视
UNWATCH
Redis 事务从开始到执行会经历以下三个阶段:
- 开始事务
- 命令入队
- 执行事务 | 放弃执行
错误处理
- 如果执行的某个命令报出了错误,则只有报错的命令不会被执行,而其它的命令都会执行,不会回滚。
例如:语法错误,递增非数字型 - 队列中的某个命令出现了报告错误,执行时整个事务的所有队列都会被取消。
例如:命令不存在
应用场景
一组命令必须同时都执行,或者都不执行。
我们想要保证一组命令在执行的过程之中不被其它命令插入。
案例:秒杀。
Redis 持久化
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
Redis 提供了两种持久化方式 RDB(默认)和 AOF
简介
数据存放于:
- 内存:高效、断电(关机)内存数据会丢失
- 硬盘:读写速度慢于内存,断电数据不会丢失
Redis 持久化存储支持两种方式: RDB 和 AOF 。 RDB 一定时间取存储文件, AOF 默认每秒去存储历史命令,Redis 是支持持久化的内存数据库,也就是说 Redis 需要经常将内存中的数据同歩到硬盘来保证持久化
RDB
RDB 是 Redis DataBases 缩写
功能核心函数 rdbSave
(生成 RDB 文件)和 rdbLoad
(从文件加载内存)两个函数
RDB:是 Redis 的默认持久化机制。
快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为 dump.rdb。
优点:
- 快照保存数据极快、还原数据极快
- 适用于灾难备份
缺点:
- 小内存机器不适合使用,RDB 机制符合要求就会照快照
快照条件:
- 服务器正常关闭时,客户端中执行
shutdown
命令 - key 满足一定条件,会进行快照,在
redis.conf
中的save
配置
AOF
由于快照方式是在一定间隔时间做一次的,所以如果 Redis 意外 down 掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用 AOF 持久化方式。
Append-only file
:AOF 比快照方式有更好的持久化性,是由于在使用 AOF 持久化方式时,Redis 会将每一个收到的写命令都通过 write
函数追加到文件中(默认是 appendonly.aof
)当 Redis 重启时会通过重新执行文件中保存的写命令来在内存中重建整数据库的内容。
每当执行服务器(定时任务或者函数时 flushAppendOnlyFile
函数都会被调用,这个函数执行以下两个工作:
-
WRITE
:根据条件,将 aof_buf 中的缓存写入到 AOF 文件 -
SAVE
:根据条件,调用fsync
或fdatasync
函数,将 AOF 文件保存到磁盘中
有三种方式如下(默认是:每秒 fsync
一次)
- appendonly yes // 启用 AOF 持久化方式
- appendfsync always // 收到写命令就立即写入磁盘,最馒,但是保证完全的持久化
- appendfsync everysec // 每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
- appendfsync no // 完全依赖 os,性能最好持久化没保证
产生的问题:
AOF 的方式也同时帯来了另一个问题,持久化文件会变的越来越大。例如我们调用 incr test
命令 100 次,文件中必须保存全部的 100 条命令,其有 99 条都是多余的。
Redis 缓存与数据库一致性
实时同步
对强一致要求比较高的,应采用实时同步方案,即查询缓存查询不到再从 DB 查询,保存到缓存;更新缓存时,先更新数据库,再将缓存的设置过期(建议不要去更新缓存内容,直接设置缓存过期)。
-
@Cacheable
:查询时使用,注意Long
类型需转换为String
类型,否则会抛异常 -
@Cacheput
:更新时使用,使用此注解,一定会从 DB 上查询数据 -
@CacheEvict
:删除时使用; -
@Caching
:组合用法
异步队列
对于并发程度较高的,可采用异步队列的方式同步,可采用 kafka 等消息中间件处理消息生产和消费。
使用阿里的同步工具 canal
canal 实现方式是模拟 mysql slave 和 master 的同步机制,监控 DB bitlog 的日志更新来触发缓存的更新,此种方法可以解放程序员双手,减少工作量,但在使用时有些局限性。
采用 UDF 自定义函数的方式
面对 mysql 的 API 进行编程,利用触发器进行缓存同步,但 UDF 主要是 C/C++ 语言实现,学习成本高。
Redis 使用经验
穿透
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解決办法:持久层查询不到就缓存空结果,查询时先判新缓存中是否 exists(key)
,如果有直接返回空,没有则查询后返回,注意 insert 时需清除查询的 key ,否则即便 DB 中有值也查询不到(当然也可以设置空缓存的过期时间)
雪崩
雪崩:缓存大量失效的时候,引发大量查询数据库。
解决办法:
- 用锁/分布式锁或者队列串行访问
- 缓存失效时间均匀分布
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
加锁排队.限流-限流算法
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。
简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去 load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如 Redis 的 SETNX 或者 Memcache 的 ADD )去 set 一个 mutex key ,当操作返回成功时,再进行 load db 的操作并回设缓存;否则,就重试整个 get 缓存的方法。
SETNX,是「SET if Not exists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
数据预热
可以通过缓存 reload 机制,预先去更新缓存,在即将发生大并发访问前手动触发加载缓存不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀
热点 key
热点 key:某个 key 访问非常频繁,当 key 失效的时候有大量线程来构建缓存,号致负载増加,系统崩溃。
解决办法:
- 使用锁,单机用 synchronized , lock 等,分布式用分布式锁
- 缓存过期时间不设置,而是设置在 key 对应的 value 里。如果检测到存在的时间超过过期时间则异步更新缓存
- 在 value 设置一个比过期时间 t0 小的过期时间值 t1 ,当 t1 过期的时候,延长 t1 并做更新缓存操作
- 设置标签缓存,标签缓存设置过期时间,标签缓存过期后,需异步地更新实际缓存