redis的特性
事务
redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!
一次性、顺序性、排他性!执行一些列的命令
------队列 set set set 执行-------
Redis事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!
Redis单条命令是保存原子性,但是事务不保证原子性!
redis的事务:
- 开启事务(multi)
- 命令入队(输入命令)
- 执行事务(exec)
127.0.0.1:6379> multi # 开启事务 OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> get k1 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> exec # 执行事务 1) OK 2) "v1" 3) OK
放弃事务
127.0.0.1:6379> set k1 v1 # 开启事务 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> discard # 取消事务 OK 127.0.0.1:6379> get k2 # 事务队列中命令都不会被执行! (nil)
编译型异常(代码有问题!命令有错!),事务中所有的命令都不会被执行!
127.0.0.1:6379> multi OK 127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> getset k3 # 错误的命令 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> exec # 执行事务报错 (error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1 # 命令没有被执行
(nil)
运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!
127.0.0.1:6379> set k1 v1 OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> incr k1 # 执行的时候会报错 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> get k2 QUEUED 127.0.0.1:6379> exec 1) (error) ERR value is not an integer or out of range # 虽然第一条命令报错了,但是其他命令正常执行成功了 2) OK 3) "v2" 127.0.0.1:6379> get k2 "v2" 127.0.0.1:6379> get k1 "v1"
监控 watch
悲观锁:很悲观,认为什么时候都会出问题,无论做什么都加锁
乐观锁:很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据,
获取version
更新的时候比较version
127.0.0.1:6379> set money 100 OK 127.0.0.1:6379> set out 20 OK 127.0.0.1:6379> watch money # 监控money对象 OK 127.0.0.1:6379> multi # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功! OK 127.0.0.1:6379> decrby money 20 QUEUED 127.0.0.1:6379> incrby out 20 QUEUED 127.0.0.1:6379> exec 1) (integer) 80 2) (integer) 40
####################################################
测试多线程修改值,使用watch可以当做redis的乐观锁操作!
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec # 执行之前,另外一个线程修改了key的值,这个时候导致事务失败!(可以使用unwatch放弃监视,再重新watch)
(nil)
Jedis
jedis是redis官方推荐的java连接开发工具。

输出:

jedis事务示例

SpringBoot整合
SpringBoot操作数据:spring-data jpa jdbc mongodb redis
Springdata也是和SpringBoot齐名的项目。
说明:在SpringBoot2.x版本之后,原来使用的redis被替换为lettuce
jedis:采用的直连服务,多线程操作的话是不安全的;若要避免,需使用jedis pool连接池!更像Bio模式
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数量,更像Nio模式
Redis持久化
redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以redis提供了持久化功能!
rdb机制是在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
rdb保存的文件时dump.rdb
触发机制:
1、save规则满足的情况下,会自动触发rdb规则
2、执行flushall命令,会触发我们的rdb规则
3、退出redis,也会产生dump.rdb文件
恢复rdb文件:
1、只要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据!
2、查看需要存在的位置:
127.0.0.1:6379> config get dir
1) "dir"
2) "D:\\software\\Redis-x64-5.0.10" # 如果在这个目录下存在dump.rdb文件,redis启动就会自动恢复其中的数据
优点:1、适合大规模的数据恢复
2、对数据的完整性要求不高
缺点:1、需要一定的时间间隔进行操作,如果redis意外宕机了,最后一次修改数据就没有了
2、fork进程的时候,会占用一定的内存空间
aof是以日志的形式来记录每个写操作,将redis执行过的所有指令记录下来(读操作不记录),只许追加文件不可以改写文件,redis重启就会根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复。
aof保存的是appendonly.aof文件
aof模式默认是不开启的,需要将配置文件的appendonly改为yes就开启了aof,重启redis生效。
如果aof文件有错误,这时候redis是启动不起来的,使用redis的工具redis-check-aof ,使用命令为redis-check-aof --fix appendonly.aof
appendonly on # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分情况下,rdb完全够用 appendfilename "appendonly.aof" # 持久化的文件的名字 # appendfsync always # 每次修改都会同步 sync,消耗性能 appendfsync everysec # 每秒执行一次,可能会丢失这一秒的数据 # appendfsync no # 不执行同步sync,这个时候操作系统自己同步数据,速度最快
优点:1、每次修改都同步,文件的完整性更好
2、每秒同步一次,可能会丢失一秒的数据
3、从不同步,效率最高的
缺点:1、相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
2、aof运行效率也比rdb慢,所以redis默认的配置就是rdb持久化
aof文件默认是无限追加的,文件会越来越大,若将no-appendfsync-on-rewrite 改为yes,
aof文件大于64M时,fork一个新进程来将文件进行重写。
no-appendfsync-on-rewrite no # Automatic rewrite of the append only file. # Redis is able to automatically rewrite the log file implicitly calling # BGREWRITEAOF when the AOF log size grows by the specified percentage. # # This is how it works: Redis remembers the size of the AOF file after the # latest rewrite (if no rewrite has happened since the restart, the size of # the AOF at startup is used). # # This base size is compared to the current size. If the current size is # bigger than the specified percentage, the rewrite is triggered. Also # you need to specify a minimal size for the AOF file to be rewritten, this # is useful to avoid rewriting the AOF file even if the percentage increase # is reached but it is still pretty small. # # Specify a percentage of zero in order to disable the automatic AOF # rewrite feature. auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb
appendonly on # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分情况下,rdb完全够用
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会同步 sync,消耗性能
appendfsync everysec # 每秒执行一次,可能会丢失这一秒的数据
# appendfsync no # 不执行同步sync,这个时候操作系统自己同步数据,速度最快
Redis发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
微信公众号、微博、关注系统!
Redis客户端可以订阅任意数量的频道。
发布/订阅消息图:
第一个:消息发送者,第二个:频道,第三个:消息订阅者。
下表列出了 redis 发布订阅常用命令:
| 序号 | 命令及描述 |
|---|---|
| 1 | PSUBSCRIBE pattern [pattern ...] 订阅一个或多个符合给定模式的频道。 |
| 2 | PUBSUB subcommand [argument [argument ...]] 查看订阅与发布系统状态。 |
| 3 | PUBLISH channel message 将信息发送到指定的频道。 |
| 4 | PUNSUBSCRIBE [pattern [pattern ...]] 退订所有给定模式的频道。 |
| 5 | SUBSCRIBE channel [channel ...] 订阅给定的一个或多个频道的信息。 |
| 6 | UNSUBSCRIBE [channel [channel ...]] 指退订给定的频道。 |
示例:
在我们实例中我们创建了订阅频道名为 runoobChat:
第一个 redis-cli 客户端
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "redisChat"
3) (integer) 1
现在,我们先重新开启个 redis 客户端,然后在同一个频道 runoobChat 发布两次消息,订阅者就能接收到消息。
第二个 redis-cli 客户端
(integer) 1
redis 127.0.0.1:6379> PUBLISH runoobChat "Learn redis by runoob.com"
(integer) 1
# 订阅者的客户端会显示如下消息
1) "message"
2) "runoobChat"
3) "Redis PUBLISH test"
1) "message"
2) "runoobChat"
3) "Learn redis by runoob.com"
Redis主从复制
概念
主从复制,是指将一台redis服务器的数据,复制到其他的redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主。
默认情况下,所有的redis均为主节点。
主从复制的作用:
1、数据冗余:实现了数据的热备份,是持久化之外的一种数据冗余方式。
2、故障恢复:主节点有问题用从节点
3、负载均衡
4、高可用(集群)基石:是哨兵和集群能够实施的基础。
环境配置
只配置从库,不用配置主库!
127.0.0.1:6379> info replication # 查看当前库的信息 # Replication role:master # 角色:master connected_slaves:0 # 没有从机 master_replid:f09b0dfdfa122cec85b1f83b0ecde7f1fdff78fb master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
复制3个配置文件,修改对应信息:
1、端口
2、pid名字
3、log文件名字
4、dump.rdb名字
修改完毕后,启动3个redis服务,可以通过进程信息查看。
一主(79)二从(80,81):
命令配置(非永久生效)
127.0.0.1:6379> SLAVEOF 127.0.0.1 6379 # SLAVEOF host 6379 找谁当主机
127.0.0.1:6379> SLAVEOF no one # 恢复自身主机角色
真实的主从配置永久生效,应该去配置文件修改:
# slaveof <masterip> <masterport> # 配置主机地址端口 # If the master is password protected (using the "requirepass" configuration # directive below) it is possible to tell the slave to authenticate before # starting the replication synchronization process, otherwise the master will # refuse the slave request. # # masterauth <master-password> # 主机如果有密码,在这里配置密码
测试:1、主机断开连接,从机依旧连接到主机,此时不会有写操作;这个时候如果主机连接上了,从机依旧可以直接获取主机写的信息。
2、如果使用命令行来配置的主从,这个时候如果从机重启了,就会变回主机;需要重新配置一下主从,立马会从主机中同步数据。
复制原理:
Slave启动成功连接到master后会发送一个sync同步命令,Master接收到命令后,启动后台存盘进程,同时收集所有用于修改数据集命令,在后台进程执行完毕后,Master将传送整个数据文件到Slave,并完成一次完全同步。
全量复制:Slave服务在接收到数据文件后,将其存盘并加载到内存中。
增量复制:Master继续将新的所有收集到的修改命令一次传递给Slave,完成同步。
但是只要是重新连接Master,一次完全同步(全量复制)将被自动执行。
哨兵模式(自动选取主机的模式)
1、配置哨兵文件sentinel.conf
# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1 #后面的数字1代表主机挂了,slave投票看让谁接替称为主机,票数最多的,就会称为主机
2、启动哨兵
redis-sentinel sentinel.conf
优点:1、哨兵集群,基于主从复制,所有的主从配置的优点它都有
2、主从可以切换,故障可以转移,系统的可用性就更好
3、哨兵模式是主从模式的升级,手动到自动,更加健壮
缺点:
1、redis不方便在线扩容,集群容量一旦到达上限,在线扩容很麻烦
2、实现哨兵模式的配置其实很麻烦,里面有很多种选择。
哨兵模式的完整配置:
# Example sentinel.conf
# 哨兵sentinel实例运行的端口,默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# quorum 配置多少个sentinel哨兵统一认为master主节点失联,这时客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当redis实例中开启requirepass 授权密码,这样所有连接redis的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码,注意必须主从设置一样的密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒后,主节点没有应答哨兵sentinel时,哨兵主观认为主节点下线,默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 指定在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步
# 可以通过将值设为1,来保证每次只有一个slave处于不能处理命令请求的状态
# sentinel parallel-syncs <master-name> <numslave>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间,默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# 通知脚本
# 配置当某一事件发生时所需要执行的脚本,比如系统不正常时通过邮件通知管理员
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息
# 以下参数将会在调用脚本是传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是"failover"
# <role>是"leader"或者"observer"中的一个
# 参数<from-ip> <from-port> <to-ip> <to-port>是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用
# sentinel client-reconfig-script <master-name> <script-path>
sentile client-reconfig-script mymaster /var/redis/reconfig.sh
Redis缓存穿透和雪崩
缓存穿透(查不到):用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(场景:秒杀!),于是都去请求持久层数据库,这会给数据库造成很大的压力,相当于出现了缓存穿透。
解决方案:1、布隆过滤器:是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免对底层存储系统的查询压力。
2、缓存空对象:当存储层不命中后,及时返回的空对象也将其存储,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。
此方案存在两个问题:1、如果空值被存储,需要更多的存储空间
2、即使对空值设置了过期时间,但缓存层和存储层的数据会有一段时间窗口的不一致,这对需要保持一致性的业务会有影响。
缓存击穿(量太大,缓存过期):这里和缓存穿透的区别,是指一个key非常热点(场景:微博热搜,这类key一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并 且回写缓存,使数据库瞬间压力过大),在不停地扛着大并发,当这个key在失效的瞬间,持续的大并发就穿破缓存 ,直接请求数据库,就像在一个屏障上凿开一个洞。
解决方案:1、设置热点数据永不过期:
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。
2、加互斥锁:
使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,此时只能等待。这种方式把压力转给分布式锁,对分布式锁 的考验很大。
缓存雪崩:指在某个时间段内,缓存集中过期失效。
产生雪崩的原因之一,比如有些时候,双十一零点,很快要来一波抢购,这波商品时间比较集中放在缓存,假设缓存一小时,凌晨一点的时候,这批商品的缓存过期了,对于这批商品的查询都落到数据库,产生周期性压力波峰。所有请求都到达存储层,存储层也会挂掉的情况。其实集中过期不是非常致命,更糟的是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的,周期性压力,而宕机对数据库的压力不可预知,可能瞬间把数据库压垮。
解决方案:1、redis高可用:即redis集群;
2、限流降级:缓存失效后,通过加锁或队列来控制数据库写缓存的线程数量;
3、数据预热:在正式部署前,把可能得数据先预先访问一遍 ,这些数据会被加载到缓存中,设置不同的过期时间,使缓存失效的时间点尽量均匀。

浙公网安备 33010602011771号