1、Redis的事务
场景描述:我们的数据操作是一个原子性的
# 看一下redis的场景
# client A client B
set name icoding(1) set name icodingedu(2)
get name(3) get name
# 这个时候数据就串了
事务需要边界
multi # 开启事务
set name icoding
get name # 命令没执行先放到队列中
exec # 执行事务
discard # 回滚事务
事务执行过程中错误回退机制
-
如果命令错误则自动回滚
-
如果命令正确但语法错误,正确的语句都会被执行
multi set name icoding lpush name 1 2 3 exec
第一个命令会执行,第二个命令语法错误没被执行
2、Redis的乐观锁
业务场景举例
库存:store 1000–>100 / + 1000(三个业务人员incrby)
数据的状态:如果在你修改前已经被修改了,就不能再修改成功了?
# watch key1 key2 命令
# 只要在事务外watch了一个key或多个key,在事务还没有执行完毕的时候,watch其中的key被修改后,整个事务回滚
set store 10
watch store
multi
incrby store 100
get store
exec
- watch在事务执行完毕后就释放了
- watch的key如果在事务执行过程中失效了,事务也不受watch影响
- watch只能在事务外执行
- 客户端如果断开,watch也就失效了
3、内部事件订阅机制
发布与订阅
有点像消息,但这个订阅是阻塞式的,我要一直等,相当于同步
subscribe java php c #订阅,要早于发布之前阻塞执行
publish java springboot #发布消息
psubscribe java* #订阅统配频道
keyspace notifcations
业务场景:
订单超时2小时未支付,需要关闭,如何实现?
1、Quartz来做任务调度,定时执行巡检任务,5分钟巡检一次,会有4分59秒的巡检超时(被动)
2、Timer,java的定时器,秒为单位,数据量大的时候性能就成瓶颈了(被动)
3、Quartz+Timer ,Quartz拿出5分钟内将要充实的订单,然后在启用多线程以Timer每秒的方式去检查,但业务功能就比较复杂了(被动)
4、有没有一种功能,有个hook能通知我们我失效了?通知我执行业务(主动)
5、假设:expire 7200 秒过期后通知我,这就是实现了第4种方案了?
Redis在2.8版本后,推出keyspace notifcations特性,类似数据库的trigger触发器。
相当于key操作的事件通知,它是基于sub/pub发布订阅机制,可以接收对数据库中影响key操作的所有事件,如del、set、expire(过期时间)。
接受的事件类型
- keyspace : 是key触发的事件的具体操作
- keyevent : 是事件影响的键名
# pub这个动作是系统自动发布的,pop的原理机制
127.0.0.1:6379> del mykey
# 数据库0会发布以下两个信息
publish __keyspace@0__:mykey del
publish __keyevent@0__:del mykey
开启系统通知
redis.conf配置文件里
# 这都是配置项
# K Keyspace events, published with __keyspace@<db>__ prefix.
keyspace事件,以__keyspace@<db>__为前缀发布
# E Keyevent events, published with __keyevent@<db>__ prefix.
keyevent事件,以__keyevent@<db>__为前缀发布
# g Generic commands (non-type specific) like DEL, EXPIRE, RENAME, ...
一般性的非特定类型的命令,如del,expire,rename
# $ String commands 字符串特定命令
# l List commands 列表特定命令
# s Set commands 集合特定命令
# h Hash commands 哈希特定命令
# z Sorted set commands zset有序集合特定命令
# x Expired events (events generated every time a key expires)
过期事件,当某个键过期并删除时会产生该事件
# e Evicted events (events generated when a key is evicted for maxmemory)
驱逐事件,当某个键因maxmemory策略被删除时,产生该事件
# A Alias for g$lshzxe, so that the "AKE" string means all the events.
g$lshzxe的别名,“AKE”意味着所有事件
notify-keyspace-events "" #默认空字符串,是关闭状态
notify-keyspace-events "KEx" #配置文件里只配置了space和event的expried事件,就只自动发布这个事件
配置订阅
notify-keyspace-events "KEA"
set username gavin ex 10 #set事件,expire事件
psubscribe __key*@*__:*
#返回事件通知
4) "set"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyevent@0__:set"
4) "username"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyspace@0__:username"
4) "expire"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyevent@0__:expire"
4) "username"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyspace@0__:username"
4) "expired"
1) "pmessage"
2) "__key*@*__:*"
3) "__keyevent@0__:expired"
4) "username"
#只订阅过期事件
subscribe __keyevent@0__:expired
Springboot订阅通知
pom.xml导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
业务场景:处理订单过期自动取消,比如下单30分钟未支付自动更改订单状态
-
解决方案一
1、开启redis的过期提醒,配置文件设置
notify-keyspace-events "Ex" # 修改保存后redis需要重启
2、定义配置类RedisListenerConfig
@Configuration public class RedisListenerConfig { @Bean RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory{ RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(connectionFactory); //container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired")); return container; } }
3、定义监听器RedisKeyExpirationListener 实现KeyExpirationEventMessageListener接口,查看源码实现,该接口监听所有db的过期事件keyevent@*:expired
@Component public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener { public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) { super(listenerContainer); } @Override public void onMessage(Message message, byte[] pattern) { // 用户做自己的业务处理即可,注意message.toString()可以获取失效的key String expiredKey = message.toString(); if(expiredKey.startsWith("zeus:order")){ //TODO } } }
-
解决方案二
使用springboot+quartz任务,任务信息写入mysql,quartz要集群分布式部署,多节点执行任务,下单成功后,生成一个30分钟后运行的任务:检查订单,如果未支付,进行处理。
4、Redis的持久化
-
RDB相当于MySQL的定时备份
-
AOF相当于MySQL的增量备份
RDB模式
1、什么是RDB
每隔一段时间,把内存中的数据写入磁盘,恢复的时候,他会自动从工作区拿出来进行恢复
2、RDB的优劣势
优势
- 每隔一段时间,全量备份
- 备份简单,可以直接传输文件到其他地方
- 备份的过程中会fork一个新的进程来进行文件的存储
劣势
- 发生故障时,会丢失上次备份到当前时间的数据(丢失最后一次备份和系统宕机之间的数据)
- fork的进程会和父进程一摸一样,会导致内存随时膨胀两倍(可能会导致redis某个时间点会有点卡,当你去看的时候就好了)
配置文件redis.conf
save 900 1 #900秒内变更1次才触发bgsave
save 300 10 #300秒内变更10次触发bgsave
save 60 10000
# 如果不想开启RDB,就是配置成 save ""
dbfilename dump.rdb #rdb保存的文件名
dir ./ #就是存放我们RDB备份文件的目录
#yes:如果save过程出错了则停止Redis写操作
#no:没所谓save是否出错
stop-writes-on-bgsave-error yes
#开启RDB压缩
rdbcompression yes
#进行CRC64算法校验,有10%的性能损耗
rdbchecksum yes
手动备份RDB
手动触发有两个命令
-
save命令
在执行的时候会阻塞Redis的服务,直到RDB文件完成才会释放我们Redis进程恢复读写操作
-
bgsave命令
执行bgsave的时候会在后台fork一个进程进行RDB的生成,不影响主进程的业务操作
使用RDB恢复数据
只需要将dump.rdb移动到我们redis.conf配置的dir(dir ./)目录下,就会在Redis启动的时候自动加载数据到内存,但是Redis在加载RDB过程中是阻塞的,直到加载完毕才能恢复操作
redis-cli> config get dir # 获取dir的配置信息
AOF模式
RDB会丢失最后一次备份和系统宕机之间的数据,可能你觉得无所谓,所以就需要增量备份的过程了,什么Redis的增量备份,就是AOF,有点像MySQL的Binlog。
特点
- 以日志形式来记录用户的请求和写操作,读操作不会记录
- 文件是追加的形式而不是修改的形式
- redis的aof恢复其实就是从头到尾执行一遍
优势
- AOF更加耐用,可以以秒为单位进行备份,如果发生问题,只丢失一秒的数据
- 以log日志的方式进行存放和追加数据,如果磁盘已经很满了,会执行redis-check-aof工具
- 当aof文件太大的时候,redis在后台会自动重写aof,相当于把redis的aof文件进行压缩
- AOF日志包含所有写操作,便于redis的恢复
劣势
- 相同的数据,AOF比RDB大(因为AOF记录写操作,RDB记录数据本身,同一个可能被写多次)
- AOF比RDB同步慢,恢复比RDB慢
- 一旦AOF文件出现问题,数据就会不完整
redis.conf配置文件
#开启AOF
appendonly yes
#AOF的文件名设置
appendfilename "appendonly.aof"
# aof同步备份的频率设置
# no : 写入aof文件,不等待磁盘同步
# everysec :每秒讲写操作备份,推荐使用
# always :每次操作都会备份,数据是安全和完成,但性能会比较差
appendfsync everysec
# 重写的时候是否要同步,yes则不同步,no同步阻塞可以保证数据安全(重写的时候redis不可以进行写操作,保证数据的不丢失)
no-appendfsync-on-rewrite no
# 重写机制(压缩机制):避免文件越来越大,将key的重复值合并(同一个key只保留最后一次写操作),重写的时候会触发fork一个新进程来操作,与RDB的fork进程区别是前者复制写操作,后者复制数据本身
# 重写触发条件就是下面两个都满足才触发
# 上面配置no,重写触发后会fork进程并阻塞主进程无法写入导致等待,所以两个值可以设置小点让重写快速阻塞完毕
# 1.现有的文件比上次多出100%:上次压缩完2G,现在已经4G了如果是50%就是3G
auto-aof-rewrite-percentage 100
# 2.现有的文件已经超过100mb了,避免设置过小,频繁触发重写,也不能过大,避免重写的时候阻塞时间过长
auto-aof-rewrite-min-size 100mb
Redis在RDB和AOF同时开启的过程中,重启后会优先加载AOF文件
- AOF文件在开启后就会自动创建一个空文件
- 重启后Redis会优先加载AOF:默认是以秒为单位进行保存的,在逻辑上比RDB的数据完整
热开启AOF
# 在redis客户端在线更改
config get appendonly
config set appendonly yes
# 开启aof后,dir目录就会有一个appendonly.aof日志文件
# 同时修改redis.conf配置文件的appendonly参数为yes,确保线上线下参数一致
appendonly yes