上一篇:https://blog.csdn.net/LawssssCat/article/details/105108098
下一篇:https://lawsssscat.blog.csdn.net/article/details/105141410
发布订阅
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 客户端可以订阅任意数量的频道。
下面展示了频道(channel1),以及订阅者个频道的三个客户端 client2、client1、client5 之间的关系
当有新消息通过 PUBLISH 命令发送给频道 channel1 时,这个消息就会被发送给订阅它的三个客户端
# 配置订阅和发布
常用命令
订阅频道:
SUBSCRIBE channel channel ...
订阅给定的一个或者多个频道的信息
PSUBSCRIBE pattern1 pattern2 ...
订阅一个或者多个符合给定模式的频道。
发布频道:
PUBLISH channel message
将信息发送到指定的频道
退订频道:
UNSUCSCRIBE channel1 channel2 ...
退订指定的频道
PUNSUBSCRIBE pattern pattern
退订所有给定模式的频道
应用场景
- 实时消息系统,如:普通的即时聊天,群聊等功能
- 在一个博客网站,有100个粉丝订阅了你,你发送新文章,及时推送给粉丝们
- 微信公众号
- CMS 发布清除缓存的消息
门户网站的缓存系统,通过channel收到清除缓存的消息,更新推荐模块的缓存 - 配置中心
当配置信息发生变化后,订阅配置信息的节点可以收到通知消息
多数据库
Redis下,数据库时由一个整数索引表示,而不是由一个数据库名称标识。
<mark>默认情况下,一个客户端连接到数据库0.</mark>
Redis 配置文件中下面的参数来控制数据库总数:
# redis.conf 文件 为配置文件
database 16
select 数据库的切换
# 数据库下标 从0开始,1,2,3,4 ...15
select 数据库下标
移动当前key到另一个库
move key名称 数据库
数据库清空
flushdb
清除当前数据库的所有 key
flushall
清除整个redis的数据库所有key
# 为什么要多个数据库 ?
多个数据库都是独立的,可以为不同业务提供数据库服务
(同时,也可以用不同数据库作为开发、调试、上线)
Redis 事务
Redis 事务可以一次执行多个明林,(按顺序地串行化执行,执行中不会被其他命令插入,不许加塞)
简洁
Redis 事务带有两个重要的保证:
- Redis 会将一个事务中的所有命令序列化,然后按顺序执行
- Redis 执行中不会被其他命令插入,不许出现加塞行为
批量操作在发送
EXEC 命令
前被放入队列缓存。
收到EXEC 命令
后进入事务执行,<mark>事务中任意命令执行失败,其余的命令依然被执行</mark>
<mark>在事务执行过程中,其他客户端提交的命令请求不会插入到事务执行命令序列中。</mark>
DISCARD
取消事务,放弃执行事务块内的所有命令
EXEC
执行所有事务块的命令
MULTI
标记一个事务块的开始
UNWATCH
取消WATCH命令对所有KEY的监视
WATCH key1 key2 ...
监视一个(或者多个)KEY
如果在事务执行之前,这个(或这些)KEY被其他命令改动,那么事务将被打断。
# 事务的三个阶段(Redis)
- 开始事务
- 命令入列
- 执行事务
# 示例1 MULTI EXEC
转账功能,A向B转账50元
先以 MULTI 开始一个事务,中间将多条命令入队到事务中,最后用 EXEC 命令触发事务
# 示例2 DISCARD 放弃队列运行
# 示例3 事务的错误“逻辑”处理
事务的错误“逻辑”处理:
如果执行的某个命令报出了错误,则只有错误的命令不会被执行,而其他的命令都会执行,不会回滚
# 示例4 事务的错误“命令”处理
所谓命令错误,就是输入命令时候就报错了,
执行的所有队列都会被取消
# 示例5 事务的 WATCH
需求:某一个账号在事务内进行操作,在提交事务前,另一个进程对该账号进行操作
两点注意:
- 必须 在 事务(multi)开启前开启,无法在事务中开启
- 只对下面的一个事务有效
# 应用场景
一组命令必须同时都执行,或者都不执行
我们想要保证一组命令在执行的过程中不被其他命令插入
例如:
- 秒杀
Redis 数据淘汰策略
Redis 官方给的警告,当内存不足时,Redis会根据配置的缓存策略淘汰部分 keys ,以保证写入成功。
当无淘汰策略时或没有找到适合淘汰的 key 时, Redis 直接返回 out of memory 错误。
# 最大缓存配置
在 redis 中,允许用户设置最大使用的内存大小:
maxmemory 512G
Redis 提供 6 种数据淘汰策略:
对于在内存中但又不用的数据块(内存块)叫做LRU(Least Recently Used,最近最久未使用算法)
通过内存管理的一种页面置换算法,操作系统会判断哪些数据属于LRU而将其移出内存而腾出统建来加载另外的数据
-
volatile-lru:设定的超时时间的数据中,删除最不常用的数据
-
allkeys-lru:查询所有的key中最近最不常用的数据进行删除,这是应用最为广泛的策略
-
volatile-random:在已经设定了超时的数据中随机删除
-
allkeys-random:查询所有的key,之后随机删除
-
volatile-ttl:查询全部设置定超时时间的数据之后,排序,将马上要过期的数据进行删除。
-
noeviction:如果设置为该属性,则不会进行删除操作,如果内存溢出,则报错返回。(默认)
-
volatile-lfu:从所有配置了过期时间的键中驱逐使用的平率最少的键
LFU(Least Frequently Used ,最近最少使用算法)也是一种常见的缓存算法 -
allkeys-lfu:从所有键中驱逐使用频率最少的键
lfu 是4.0后新增策略
Redis 持久化
# RDB
(图片涉及到后面讲的主从复制,现在只看左边即可)
RDB(Redis DataBase) 是 redis 默认的持久化机制。
RDB 相当于照快照,保存的是一种状态。(几十G数据 =》几KB快照)
快照是默认的持久化方式
这种方式就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb
优点
- 快照保存数据极快、还原数据极快
- 适用于灾难备份
缺点
- 小内存机器不适合使用
- 相对,效率不高
RDB 机制符合要求就会照快照
# AOF
由于快照方式是在一定的间隔时间做一次快照的,所以如果 redis 一开 down 掉的话,就会丢失最后一次快照后的所有修改。
如果有应用要求不能丢失任何修改的话,可以采用 AOF 持久化方式
Append-only file :
- 在使用 aof 持久化方式时,redis 会将每一个收到的写命令都通过 write 函数追加到文件中(默认是 appendonly.aof)。
- 当 redis 重启时,会通过重新执行文件中保存的写命令来在内存中重新构建整个数据库的内容。
有三种方式(默认是:每秒 fsymc 一次)
- appendonly eys
启动 aof 持久化 - appendfsync always
收到命令就立即写入磁盘,最慢,但是保证完全的持久化 - appendfsynv everysec (默认)
每秒中写入磁盘一次,在性能和持久化方面做了很好的折中 - appendfsync no
完全依赖 os,性能最好,持久化没有保证
产生的问题
aof 的方式也带来了另一个问题。持久化文件会变得越来越大。
例如,我们调用 incr test 命令 100次,文件中必须保存全部的 100 条命令,其实,有99条是多余的。
redis 缓存与数据库一致性
解决方案
# 实时同步
<mark>对强一致性的要求比较高的情况</mark>,应采用实时同步方案。
- 查询 - 缓存查询不 到再从 DB 查询,保存到缓存
- 更新/删除 - 先更新数据库,再将缓存的设置过期(建议不要去更新缓存内容)
注解:
@Cacheable:查询时使用,注意 Long 类型需要转换为 String 类型,否则抛异常
@CachePut:更新时使用,使用此注解,一定会从 DB 上查询数据
#CacheEvict:删除时使用
Caching:组合用法
同步时约到的问题和解决
缓存穿透
缓存穿透是指查询一个一定不存在的数据
由于要查询的数据,缓存没有时,会到数据库查找,而如果数据库也没有,那么这将导致每次查询这个数据都会到数据库中查找,造成缓存穿透
<mark>解决方案:持久层查询不到就缓存 空结果。</mark>
注意:insert 时需要清除查询的 key,否则即使 DB 中有值也查询不到
(当然也可以设置空缓存的过期时间)
缓存雪崩
如果缓存集中在一段时间内失效,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
没有完美的解决方案
- 分析用户行为,让失效时间点尽量均匀分布
- <mark>加锁或者队列的方式保证缓存的单线程(进程)写入,从而避免失效时大量的并发请求落到底层存储系统上</mark>
热点key
某个 key 访问非常频繁,当 key 失效时,有大量线程来构建缓存,导致负载增加,系统奔溃。
解决方案
- 使用 锁:单机用 synchronized 、lock 等,分布式用
分布式锁
(后面讲) - 缓存过程时间不设置,而是设置在 key 对应的 value 里面。
如果检测到存的事件超过过期,则异步更新缓存。 - 在 value 设置一个比过期时间 t0 小的过期时间值 t1 , 当 t1 过期的时候,延长 t1 并做更新缓存操作。
- <mark>设置标签缓存,标签缓存过期后,异步更新实际缓存</mark>
# 异步队列
对于并发程度较高的
- 可以采用异步队列的方式同步
- 可以采用 kafka 等消息中间件处理消息生产和消费。
# 阿里的同步同居 canal
canal 实现方式是模拟 mysql slave 和 master 的同步机制,监控 DB bitlog 的日志更新来触发缓存的更新
这种方法可以解放♂程序员♂的双手♂?,减少工作量♂
但在使用时由亿些局限性!
- master 将改变记录到二进制日志(binary log)中,(这些记录叫做二进制日志事件 binary log events),可以通过 show binlog events 进行查看
- slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
- slave 重做中继日志中的事件,将改变反映自己的数据。
canal 工作原理
- canal 模拟 mysql slave 的交互协议,伪装自己为 mysql slave,向 mysql master 发送 dump 协议
- mysql master 收到 dunp 请求,开始推送 binary log 给 slave (也就是 canal)
- canal 解析 binary log 对象(原始为 byte 流)
# 采用 UDF 自定义函数的方式
UDF(User-Defined Functions)即是用户定义的hive函数
面对 mysql 的 api 进行编程,利用触发器进行缓存同步,但 UDF 主要是 c/c++ 语言实现,学习成本高 。
数据库中:触发器
insert into user()
Create trigger {
触发的代码
}
# Lua 脚本解析器
Redis 2.6 版本通过内嵌支持 Lua 环境。
执行脚本通常命令为 : EVAL