网站建设源程序代码,外贸网站建站h,温州专业微网站制作报价,精品网站建设公司目录
四、Redis04
4.1 Redis集群应用场景
4.2 集群
4.2.1 基本原理
4.2.2 主从复制的作用
4.3 配置集群#xff08;一台虚拟机#xff09;
4.3.1 规划网络
4.3.2 创建节点
4.3.3 创建目录
4.3.4 配置redis7001.conf
4.3.5 配置其余文件
4.3.6 后台启动redis
4.3…目录
四、Redis04
4.1 Redis集群应用场景
4.2 集群
4.2.1 基本原理
4.2.2 主从复制的作用
4.3 配置集群一台虚拟机
4.3.1 规划网络
4.3.2 创建节点
4.3.3 创建目录
4.3.4 配置redis7001.conf
4.3.5 配置其余文件
4.3.6 后台启动redis
4.3.7 创建Redis的集群
4.3.8 使用cli连接redis集群
4.3.9 检查集群的状态
4.3.10 添加空白主节点
4.3.11 为空白主节点配置从节点
4.3.12 为空白主节点分配插槽
4.3.13 删除节点
4.3.14 集群自动组网
4.3.15 故障检测
4.4 slots介绍
4.4.1 在集群中录入值
4.4.2 集群的优点和不足
4.5 Redis的应用问题
4.5.1 缓存穿透
4.5.2 缓存击穿
4.5.3 缓存雪崩
4.6 分布式锁
4.6.1 问题描述
4.6.2 使用redis实现分布式锁
4.6.3 redis实现分布式锁步骤
4.6.4 分布式锁问题及优化
4.6.5 lua脚本 四、Redis04
4.1 Redis集群应用场景
为什么需要redis集群
当主备复制场景无法满足主机的单点故障时需要引入集群配置。
一般数据库要处理的读请求远大于写请求 针对这种情况我们优化数据库可以采用读写分离的策略。我们可以部 署一台主服务器主要用来处理写请求部署多台从服务器 处理读请求。 4.2 集群
4.2.1 基本原理
哨兵选举机制如果有半数节点发现某个异常节点共同决定改异常节点的状态如果该节点是主节点对应的备节点自动顶替为主节点。Sentinel哨兵是Redis 的高可用性解决方案由一个或多个Sentinel 实例 组成的Sentinel 系统可以监视任意多个主服务器以及这些主服务器属下的所有从服务器并在被监视的主服务器进入下线状态时自动将下线主服务器属下的某个从服务器升级为新的主服务器。
4.2.2 主从复制的作用
1、数据冗余主从复制实现了数据的热备份是持久化之外的一种数据冗余方式。
2、故障恢复当主节点出现问题时可以由从节点提供服务实现快速的故障恢复实际上是一种服务的冗余。
3、负载均衡在主从复制的基础上配合读写分离可以由主节点提供写服务由从节点提供读服务即写Redis数据时应用连接主节点读Redis数据时应用连接从节点分担服务器负载尤其是在写少读多的场景下通过多个从节点分担读负载可以大大提高Redis服务器的并发量。
4、读写分离可以用于实现读写分离主库写、从库读读写分离不仅可以提高服务器的负载能力同时可根据需求的变化改变从库的数量。
5、高可用基石除了上述作用以外主从复制还是哨兵和集群能够实施的基础因此说主从复制是Redis高可用的基础
4.3 配置集群一台虚拟机
配置集群所需要的环境
Redis集群至少需要3个节点因为投票容错机制要求超过半数节点认为某个节点挂了该节点才是挂了所以2个节点无法构成集群。
要保证集群的高可用需要每个节点都有从节点也就是备份节点所以Redis集群至少需要6台服务器。因为我没有那么多服务器也启动不了那么多虚拟机所在这里搭建的是伪分布式集群即一台服务器虚拟运行6个redis实例修改端口号为7001-7006当然实际生产环境的Redis集群搭建和这里是一样的。
4.3.1 规划网络
用一台虚拟机模拟6个节点一台机器6个节点创建出3 master、3 salve 环境。虚拟机是 CentOS7 ip地址192.168.111.127
4.3.2 创建节点
首先在 192.168.111.127 机器上 /usr/lwl/soft/redis/目录下创建 redis_cluster 目录
[rootlocalhost redis]# mkdir redis_cluster
[rootlocalhost redis]# ls
bin redis_cluster
4.3.3 创建目录
在 redis_cluster 目录下创建名为7001、70027003、7004、70057006的目录
[rootlocalhost redis_cluster]# mkdir 7001 7002 7003 7004 7005 7006
[rootlocalhost redis_cluster]# ls
7001 7002 7003 7004 7005 7006
4.3.4 配置redis7001.conf
配置集群时建议不要配置密码
复制配置文件到redis_cluster目录下:一定要关闭密码
[rootlocalhost redis_cluster]# cp /usr/lwl/soft/redis/bin/redis6379.conf redis.conf
[rootlocalhost redis_cluster]# ls
7001 7002 7003 7004 7005 7006 redis.conf
这里复制的文件是在安装目录下拷贝过来的如果导致开启失败可以复制解压目录下的原始文件重新进行配置
7001目录内新建一个文件redis.conf内容如下
include /usr/lwl/soft/redis/redis_cluster/redis.conf #包含上一层目录的那个配置文件中的内容
port 7001 #端口号
pidfile /var/run/redis_7001.pid
dbfilename dump_7001.rdb
dir /usr/lwl/soft/redis/redis_cluster/7001
logfile /usr/lwl/soft/redis/redis_cluster/7001/redis_err_7001.log
cluster-enabled yes #开启集群
cluster-config-file nodes-7001.conf #集群节点文件
cluster-node-timeout 15000 #集群超时时间
4.3.5 配置其余文件
快捷命令将7001中的redis.conf复制到7002 7003 7004 7005 7006中
echo ./7002 ./7003 ./7004 ./7005 ./7006 | xargs -n 1 cp -v /usr/lwl/soft/redis/redis_cluster/7001/redis.conf
复制过程:
[rootlocalhost redis_cluster]# echo ./7002 ./7003 ./7004 ./7005 ./7006 | xargs -n 1 cp -v /usr/lwl/soft/redis/redis_cluster/7001/redis.conf
‘/usr/lwl/soft/redis/redis_cluster/7001/redis.conf’ - ‘./7002/redis.conf’
‘/usr/lwl/soft/redis/redis_cluster/7001/redis.conf’ - ‘./7003/redis.conf’
‘/usr/lwl/soft/redis/redis_cluster/7001/redis.conf’ - ‘./7004/redis.conf’
‘/usr/lwl/soft/redis/redis_cluster/7001/redis.conf’ - ‘./7005/redis.conf’
‘/usr/lwl/soft/redis/redis_cluster/7001/redis.conf’ - ‘./7006/redis.conf’
然后进入每个目录下去修改其中的值
例如要修改7002目录下的redis.conf文件可以使用命令:%s/7001/7002/g一次性将全部的7001替换成7002其他目录的配置也是相同
4.3.6 后台启动redis
要一次性后台启动六台redis可以编写一个脚本
脚本功能如果查询出来的进程数为0则启动这几个redis服务如果不为0就关闭redis服务
[rootlocalhost redis_cluster]# ps -ef|grep -w redis|grep -v grep|wc -l
6
该指令功能查询除了当前指令产生的redis进程之外的进程数量
grep -w redis完全匹配redis的进程
grep -v grep忽略包含grep的进程
wc -l查询数量
脚本功能如果查询出来的进程数为0则启动这几个redis服务如果不为0就关闭redis服务
#!/bin/bash
lps -ef|grep -w redis|grep -v grep|wc -l
if [ $l -eq 0 ]then
/usr/lwl/soft/redis/bin/redis-server /usr/lwl/soft/redis/redis_cluster/7001/redis.conf
/usr/lwl/soft/redis/bin/redis-server /usr/lwl/soft/redis/redis_cluster/7002/redis.conf
/usr/lwl/soft/redis/bin/redis-server /usr/lwl/soft/redis/redis_cluster/7003/redis.conf
/usr/lwl/soft/redis/bin/redis-server /usr/lwl/soft/redis/redis_cluster/7004/redis.conf
/usr/lwl/soft/redis/bin/redis-server /usr/lwl/soft/redis/redis_cluster/7005/redis.conf
/usr/lwl/soft/redis/bin/redis-server /usr/lwl/soft/redis/redis_cluster/7006/redis.conf
echo 已开启redis服务else
/usr/lwl/soft/redis/bin/redis-cli -p 7001 shutdown
/usr/lwl/soft/redis/bin/redis-cli -p 7002 shutdown
/usr/lwl/soft/redis/bin/redis-cli -p 7003 shutdown
/usr/lwl/soft/redis/bin/redis-cli -p 7004 shutdown
/usr/lwl/soft/redis/bin/redis-cli -p 7005 shutdown
/usr/lwl/soft/redis/bin/redis-cli -p 7006 shutdown
echo 已关闭redis服务
fi
如果这里启动失败可以回到4.4.4中重新复制一份文件进行配置
执行脚本文件
[rootlocalhost redis_cluster]# sh redisstart.sh
[rootlocalhost redis_cluster]# ps -ef|grep redis
root 7186 1 0 16:59 ? 00:00:02 ./redis-server 0.0.0.0:6379
root 7240 1 0 17:33 ? 00:00:00 /usr/lwl/soft/redis/bin/redis-server 0.0.0.0:7001 [cluster]
root 7245 1 0 17:33 ? 00:00:00 /usr/lwl/soft/redis/bin/redis-server 0.0.0.0:7002 [cluster]
root 7247 1 0 17:33 ? 00:00:00 /usr/lwl/soft/redis/bin/redis-server 0.0.0.0:7003 [cluster]
root 7252 1 0 17:33 ? 00:00:00 /usr/lwl/soft/redis/bin/redis-server 0.0.0.0:7004 [cluster]
root 7257 1 0 17:33 ? 00:00:00 /usr/lwl/soft/redis/bin/redis-server 0.0.0.0:7005 [cluster]
root 7265 1 0 17:33 ? 00:00:00 /usr/lwl/soft/redis/bin/redis-server 0.0.0.0:7006 [cluster]
root 7270 7045 0 17:34 pts/0 00:00:00 grep --colorauto redis
在创建集群时可能会出现错误
Increased of open files to 10032 (it was originally set to 1024) 针对如上错误作如下处理
1、查看打开文件的上限和redis服务进程修改上限输入如下命令查看其上限ulimit -a 2、设置上限
ulimit -n 10032 然后重启redis即可
4.3.7 创建Redis的集群
创建集群的命令安装目录下的redis-cli --cluster create 各个节点及端口 --cluster-replicas 比例
/usr/lwl/soft/redis/bin/redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1
最后的这个 -replicas 1 代表着 主节点数/从节点数的比例
如果六个节点且分配比例为1那么一般前三个是主节点后三个是从节点
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1Performing hash slots allocation on 6 nodes...
Master[0] - Slots 0 - 5460
Master[1] - Slots 5461 - 10922
Master[2] - Slots 10923 - 16383
Adding replica 127.0.0.1:7005 to 127.0.0.1:7001
Adding replica 127.0.0.1:7006 to 127.0.0.1:7002
Adding replica 127.0.0.1:7004 to 127.0.0.1:7003Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:7001slots:[0-5460] (5461 slots) master
M: 16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:7002slots:[5461-10922] (5462 slots) master
M: e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:7003slots:[10923-16383] (5461 slots) master
S: a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:7004replicates 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4
S: 68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:7005replicates 16577299b09086cf992b25ea047a53449064a62a
S: 9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:7006replicates e97325b5f5d020cfd4940421cdece4c3dc7e53d1
Can I set the above configuration? (type yes to accept): #这里输入yes代表同意分配
确认yes之后会进行分配分配结果如下
分配原则尽量保证每个主数据库运行在不同的IP地址每个从库和主库不在一个IP地址上。 4.3.8 使用cli连接redis集群
首先使用cli连接集群必须要加 -c
连接集群语法安装目录下/redis-cli -c -h ip地址 -p 端口号
/usr/lwl/soft/redis/bin/redis-cli -c -h 127.0.0.1 -p 7006
查看集群节点的信息 cluster nodes
#这里连接集群时使用端口号 7001~7006 都可以
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli -c -h 127.0.0.1 -p 7006
127.0.0.1:7006 cluster nodes # 查看集群节点的信息
9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:700617006 myself,slave e97325b5f5d020cfd4940421cdece4c3dc7e53d1 0 1676631290000 6 connected
a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:700417004 slave 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 0 1676631290187 4 connected
68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:700517005 slave 16577299b09086cf992b25ea047a53449064a62a 0 1676631291000 5 connected
16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:700217002 master - 0 1676631291199 2 connected 5461-10922
5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:700117001 master - 0 1676631288161 1 connected 0-5460
e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:700317003 master - 0 1676631289177 3 connected 10923-16383
4.3.9 检查集群的状态
执行检查集群状态的命令出现的界面和配置集群出现的状态一样
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli --cluster check 127.0.0.1:7002
127.0.0.1:7002 (16577299...) - 0 keys | 5462 slots | 1 slaves.
127.0.0.1:7003 (e97325b5...) - 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7001 (5fc3b5dc...) - 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 3 masters.
0.00 keys per slot on average.Performing Cluster Check (using node 127.0.0.1:7002)
M: 16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:7002slots:[5461-10922] (5462 slots) master1 additional replica(s)
S: a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:7004slots: (0 slots) slavereplicates 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4
M: e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:7003slots:[10923-16383] (5461 slots) master1 additional replica(s)
S: 9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:7006slots: (0 slots) slavereplicates e97325b5f5d020cfd4940421cdece4c3dc7e53d1
M: 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:7001slots:[0-5460] (5461 slots) master1 additional replica(s)
S: 68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:7005slots: (0 slots) slavereplicates 16577299b09086cf992b25ea047a53449064a62a
[OK] All nodes agree about slots configuration.Check for open slots...Check slots coverage...
[OK] All 16384 slots covered.
4.3.10 添加空白主节点
配置文件 7007 /redis.conf
[rootlocalhost redis_cluster]# mkdir 7007
[rootlocalhost redis_cluster]# cp 7001/redis.conf 7007/redis.conf
[rootlocalhost redis_cluster]# cd 7007
[rootlocalhost 7007]# ls
redis.conf
[rootlocalhost 7007]# vim redis.conf
这里使用 :%s/7001/7007/g 对文件进行修改
启动7007服务
[rootlocalhost 7007]# /usr/lwl/soft/redis/bin/redis-server redis.conf
[rootlocalhost 7007]# ps -ef|grep redis
root 7362 1 0 20:04 ? 00:00:00 /usr/lwl/soft/redis/bin/redis-server 0.0.0.0:7007 [cluster]
添加7007节点到集群
/usr/lwl/soft/redis/bin/redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
前面的IP加端口号是要添加的redis节点后面的IP和端口号是集群中的任意一个节点。 检查节点的状态
[rootlocalhost 7007]# /usr/lwl/soft/redis/bin/redis-cli --cluster check 127.0.0.1:7001
127.0.0.1:7001 (5fc3b5dc...) - 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7007 (c00baee8...) - 0 keys | 0 slots | 0 slaves.
127.0.0.1:7002 (16577299...) - 0 keys | 5462 slots | 1 slaves.
127.0.0.1:7003 (e97325b5...) - 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 4 masters.
0.00 keys per slot on average.Performing Cluster Check (using node 127.0.0.1:7001)
M: 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:7001slots:[0-5460] (5461 slots) master1 additional replica(s)
M: c00baee8694cfdbd06d66c751adb42b34cb8716e 127.0.0.1:7007 #新添加的节点slots: (0 slots) master #新添加的节点为主节点但是没有分配插槽
S: 9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:7006slots: (0 slots) slavereplicates e97325b5f5d020cfd4940421cdece4c3dc7e53d1
M: 16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:7002slots:[5461-10922] (5462 slots) master1 additional replica(s)
M: e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:7003slots:[10923-16383] (5461 slots) master1 additional replica(s)
S: 68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:7005slots: (0 slots) slavereplicates 16577299b09086cf992b25ea047a53449064a62a
S: a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:7004slots: (0 slots) slavereplicates 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4
[OK] All nodes agree about slots configuration.Check for open slots...Check slots coverage...
[OK] All 16384 slots covered.
4.3.11 为空白主节点配置从节点
新建一个目录7008并配置服务
[rootlocalhost redis_cluster]# mkdir 7008
[rootlocalhost redis_cluster]# cp 7001/redis.conf 7008/redis.conf
[rootlocalhost redis_cluster]# cd 7008
[rootlocalhost 7008]# ls
redis.conf
[rootlocalhost 7008]# vim redis.conf
这里使用 :%s/7001/7008/g 对文件进行修改
启动7008服务
[rootlocalhost 7008]# /usr/lwl/soft/redis/bin/redis-server redis.conf
[rootlocalhost 7008]# ps -ef|grep redis
root 7383 1 0 20:28 ? 00:00:00 /usr/lwl/soft/redis/bin/redis-server 0.0.0.0:7008 [cluster]
配置从节点
配置节点的命令
/usr/lwl/soft/redis/bin/redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7002 --cluster-slave --cluster-master-id c00baee8694cfdbd06d66c751adb42b34cb8716e
/usr/lwl/soft/redis/bin/redis-cli安装目录下的连接服务
127.0.0.1:7008要添加的节点的IP地址及其端口号
127.0.0.1:7002当前集群中的随意一个节点的IP地址及端口号
c00baee8694cfdbd06d66c751adb42b34cb8716e要配置为主节点的ID这里就是7007的一长串字符
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli --cluster add-node 127.0.0.1:7008 127.0.0.1:7002 --cluster-slave --cluster-master-id c00baee8694cfdbd06d66c751adb42b34cb8716eAdding node 127.0.0.1:7008 to cluster 127.0.0.1:7002Performing Cluster Check (using node 127.0.0.1:7002)
M: 16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:7002slots:[5461-10922] (5462 slots) master1 additional replica(s)
S: a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:7004slots: (0 slots) slavereplicates 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4
M: e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:7003slots:[10923-16383] (5461 slots) master1 additional replica(s)
M: c00baee8694cfdbd06d66c751adb42b34cb8716e 127.0.0.1:7007slots: (0 slots) master
S: 9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:7006slots: (0 slots) slavereplicates e97325b5f5d020cfd4940421cdece4c3dc7e53d1
M: 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:7001slots:[0-5460] (5461 slots) master1 additional replica(s)
S: 68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:7005slots: (0 slots) slavereplicates 16577299b09086cf992b25ea047a53449064a62a
[OK] All nodes agree about slots configuration.Check for open slots...Check slots coverage...
[OK] All 16384 slots covered.Send CLUSTER MEET to node 127.0.0.1:7008 to make it join the cluster.
Waiting for the cluster to join
Configure node as replica of 127.0.0.1:7007.
[OK] New node added correctly.
查看配置后的节点信息7007和7008
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli --cluster check 127.0.0.1:7002
127.0.0.1:7002 (16577299...) - 0 keys | 5462 slots | 1 slaves.
127.0.0.1:7003 (e97325b5...) - 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7007 (c00baee8...) - 0 keys | 0 slots | 1 slaves.
127.0.0.1:7001 (5fc3b5dc...) - 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 4 masters.
0.00 keys per slot on average.Performing Cluster Check (using node 127.0.0.1:7002)
M: 16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:7002slots:[5461-10922] (5462 slots) master1 additional replica(s)
S: a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:7004slots: (0 slots) slavereplicates 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4
M: e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:7003slots:[10923-16383] (5461 slots) master1 additional replica(s)
S: f187059affef4732ac4ab5a776d3c7f7056834e0 127.0.0.1:7008 #从节点slots: (0 slots) slavereplicates c00baee8694cfdbd06d66c751adb42b34cb8716e
M: c00baee8694cfdbd06d66c751adb42b34cb8716e 127.0.0.1:7007 #主节点slots: (0 slots) master1 additional replica(s)
S: 9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:7006slots: (0 slots) slavereplicates e97325b5f5d020cfd4940421cdece4c3dc7e53d1
M: 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:7001slots:[0-5460] (5461 slots) master1 additional replica(s)
S: 68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:7005slots: (0 slots) slavereplicates 16577299b09086cf992b25ea047a53449064a62a
[OK] All nodes agree about slots configuration.Check for open slots...Check slots coverage...
[OK] All 16384 slots covered.
4.3.12 为空白主节点分配插槽
添加的主节点还不能使用因为没有分配slots
slot的概念。slot对于Redis集群而言就是一个存放数据的地方就是一个槽。对于每一个Master而言会存在一个slot的范围而Slave则没有。在Redis集群中依然是Master可以读、写而Slave只读。
输入指令开始进行分配插槽最后的IP地址和端口号只是进入分配的一个接口
[rootlocalhost 7008]# /usr/lwl/soft/redis/bin/redis-cli --cluster reshard 127.0.0.1:7002Performing Cluster Check (using node 127.0.0.1:7002)
M: 16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:7002slots:[5461-10922] (5462 slots) master1 additional replica(s)
S: a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:7004slots: (0 slots) slavereplicates 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4
M: e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:7003slots:[10923-16383] (5461 slots) master1 additional replica(s)
S: f187059affef4732ac4ab5a776d3c7f7056834e0 127.0.0.1:7008slots: (0 slots) slavereplicates c00baee8694cfdbd06d66c751adb42b34cb8716e
M: c00baee8694cfdbd06d66c751adb42b34cb8716e 127.0.0.1:7007slots: (0 slots) master1 additional replica(s)
S: 9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:7006slots: (0 slots) slavereplicates e97325b5f5d020cfd4940421cdece4c3dc7e53d1
M: 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:7001slots:[0-5460] (5461 slots) master1 additional replica(s)
S: 68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:7005slots: (0 slots) slavereplicates 16577299b09086cf992b25ea047a53449064a62a
[OK] All nodes agree about slots configuration.Check for open slots...Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 最后会询问是否同意分配计划Moving slot 8160 from 16577299b09086cf992b25ea047a53449064a62a
Do you want to proceed with the proposed reshard plan (yes/no)? yes
Moving slot 5461 from 127.0.0.1:7002 to 127.0.0.1:7007:
Moving slot 5462 from 127.0.0.1:7002 to 127.0.0.1:7007:
分配后查看节点状态
[rootlocalhost 7008]# /usr/lwl/soft/redis/bin/redis-cli --cluster check 127.0.0.1:7001
127.0.0.1:7001 (5fc3b5dc...) - 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7007 (c00baee8...) - 0 keys | 2700 slots | 1 slaves.
127.0.0.1:7002 (16577299...) - 0 keys | 2762 slots | 1 slaves.
127.0.0.1:7003 (e97325b5...) - 0 keys | 5461 slots | 1 slaves.
[OK] 0 keys in 4 masters.
0.00 keys per slot on average.Performing Cluster Check (using node 127.0.0.1:7001)
M: 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:7001slots:[0-5460] (5461 slots) master1 additional replica(s)
M: c00baee8694cfdbd06d66c751adb42b34cb8716e 127.0.0.1:7007 #7007节点已经拥有了2700插槽slots:[5461-8160] (2700 slots) master1 additional replica(s)
S: 9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:7006slots: (0 slots) slavereplicates e97325b5f5d020cfd4940421cdece4c3dc7e53d1
S: f187059affef4732ac4ab5a776d3c7f7056834e0 127.0.0.1:7008slots: (0 slots) slavereplicates c00baee8694cfdbd06d66c751adb42b34cb8716e
M: 16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:7002slots:[8161-10922] (2762 slots) master1 additional replica(s)
M: e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:7003slots:[10923-16383] (5461 slots) master1 additional replica(s)
S: 68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:7005slots: (0 slots) slavereplicates 16577299b09086cf992b25ea047a53449064a62a
S: a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:7004slots: (0 slots) slavereplicates 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4
[OK] All nodes agree about slots configuration.Check for open slots...Check slots coverage...
[OK] All 16384 slots covered.
4.3.13 删除节点
删除从节点
命令redis客户端 --cluster del-node [集群中的任意一个节点端口号] 被删除的节点的id
删除7008从节点
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli --cluster del-node 127.0.0.1:7002 f187059affef4732ac4ab5a776d3c7f7056834e0Removing node f187059affef4732ac4ab5a776d3c7f7056834e0 from cluster 127.0.0.1:7002Sending CLUSTER FORGET messages to the cluster...SHUTDOWN the node.
删除主节点
删除主节点需要先使用 reshard 把主节点的slots移到其他节点才可以
归还过程和分配插槽的过程一致
命令redis客户端 --cluster del-node [集群中的任意一个节点端口号] 被删除的节点的id
归还插槽后删除主节点7007
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli --cluster del-node 127.0.0.1:7002 c00baee8694cfdbd06d66c751adb42b34cb8716eRemoving node c00baee8694cfdbd06d66c751adb42b34cb8716e from cluster 127.0.0.1:7002Sending CLUSTER FORGET messages to the cluster...SHUTDOWN the node.
4.3.14 集群自动组网
集群关机之后集群重启只需要直接启动各个节点不需要重新组网redis会根据node.conf自动组网
前提是集群关闭的时候是正常的关闭
正常的关闭是先连接上之后再shutdown进行关闭
4.3.15 故障检测
验证集群是否生效
关闭一个主节点查看对应的备用节点是不是能够顶替主节点成为主节点
注
1、关闭的时候一定要使用shutdown命令不要使用kill命令
2、关闭主节点以后需要耐心等待一会儿 让他重新分配一下空间
关闭主机7001
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli -p 7001 shutdown
查看节点状态
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli --cluster check 127.0.0.1:7003
Could not connect to Redis at 127.0.0.1:7001: Connection refused
127.0.0.1:7003 (e97325b5...) - 0 keys | 5461 slots | 1 slaves.
127.0.0.1:7002 (16577299...) - 1 keys | 5462 slots | 1 slaves.
127.0.0.1:7004 (a14f9a6f...) - 4 keys | 5461 slots | 0 slaves.
[OK] 5 keys in 3 masters.
0.00 keys per slot on average.Performing Cluster Check (using node 127.0.0.1:7003)
M: e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:7003slots:[10923-16383] (5461 slots) master1 additional replica(s)
M: 16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:7002slots:[5461-10922] (5462 slots) master1 additional replica(s)
S: 9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:7006slots: (0 slots) slavereplicates e97325b5f5d020cfd4940421cdece4c3dc7e53d1
M: a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:7004 #7004代替7001成为了主机slots:[0-5460] (5461 slots) master
S: 68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:7005slots: (0 slots) slavereplicates 16577299b09086cf992b25ea047a53449064a62a
[OK] All nodes agree about slots configuration.Check for open slots...Check slots coverage...
[OK] All 16384 slots covered.
#重启7001服务
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-server 7001/redis.conf
#连接集群
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli -c -p 7002
127.0.0.1:7002 cluster nodes
a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:700417004 master - 0 1676783272031 9 connected 0-5460
#7001重启后成为7004的从机
5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:700117001 slave a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 0 1676783271009 9 connected
16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:700217002 myself,master - 0 1676783271000 8 connected 5461-10922
9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:700617006 slave e97325b5f5d020cfd4940421cdece4c3dc7e53d1 0 1676783273045 6 connected
e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:700317003 master - 0 1676783270000 3 connected 10923-16383
68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:700517005 slave 16577299b09086cf992b25ea047a53449064a62a 0 1676783271000 8 connected
存/取数据的时候查看对应的端口号
因为在测试存值时添加了一个age属性的值存到了7001中现在7004接替了7001所以数据应该存在7004中
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli -c -p 7002
127.0.0.1:7002 get age
- Redirected to slot [741] located at 127.0.0.1:7004 #重定向到7004中
18
4.4 slots介绍
[OK] All 16384 slots covered.
一个 Redis 集群包含 16384 个插槽hash slot 数据库中的每个键都属于这 16384 个插槽的其中一个
集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分插槽。 举个例子 如果一个集群可以有主节点 其中
节点 A 负责处理 0 号至 5460 号插槽。
节点 B 负责处理 5461 号至 10922 号插槽。
节点 C 负责处理 10923 号至 16383 号插槽。
4.4.1 在集群中录入值
在redis-cli每次录入、查询键值redis都会计算出该key应该送往的插槽如果不是该客户端对应服务器的插槽redis会报错并告知应前往的redis实例地址和端口。
redis-cli客户端提供了 –c 参数实现自动重定向。
如 redis-cli -c –p 7001登入后再录入、查询键值对可以自动重定向。
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli -c -p 7001
127.0.0.1:7001 set name lwl
- Redirected to slot [5798] located at 127.0.0.1:7007 #设置值计算出为7007重定向到7007
OK
127.0.0.1:7007 set age 18
- Redirected to slot [741] located at 127.0.0.1:7001 #设置值
OK
127.0.0.1:7001 get name
- Redirected to slot [5798] located at 127.0.0.1:7007 #取出值
lwl
解决批量存储引发的问题
不在一个slot下的键值是不能使用mget,mset等多键操作。
127.0.0.1:7007 mset k1 v1 k2 v2 k3 v3
(error) CROSSSLOT Keys in request dont hash to the same slot
解决批量存储 组 {组的名字}
可以通过{}来定义组的概念从而使key中{}内相同内容的键值对放到一个slot中去。按组分配插槽
127.0.0.1:7007 mset k1{w} v1 k2{w} v2 k3{w} v3
- Redirected to slot [3696] located at 127.0.0.1:7001
OK
4.4.2 集群的优点和不足
优点
实现扩容
分摊压力
无中心配置相对简单
不足
多键操作是不被支持的
多键的Redis事务是不被支持的
由于集群方案出现较晚很多公司已经采用了其他的集群方案而代理或者客户端分片的方案想要迁移至redis cluster需要整体迁移而不是逐步过渡复杂度较大。
4.5 Redis的应用问题
4.5.1 缓存穿透
缓存穿透是指缓存和数据库中都没有的数据导致所有的请求都落到数据库上造成数据库短时间内承受大量请求而崩掉。
问题描述
key对应的数据在数据库并不存在每次针对此key的请求从缓存获取不到请求都会压到数据库从而可能压垮数据库。比如用一个不存在的用户id获取用户信息不论缓存还是数据库都没有若黑客利用此漏洞进行攻击可能压垮数据库。 解决方案 一个一定不存在缓存及查询不到的数据由于缓存是不命中时被动写的并且出于容错考虑如果从存储层查不到数据则不写入缓存这将导致这个不存在的数据每次请求都要到存储层去查询失去了缓存的意义。1 对空值缓存如果一个查询返回的数据为空不管是数据是否不存在我们仍然把这个空结果null进行缓存设置空结果的过期时间会很短最长不超过五分钟
2 设置可访问的名单白名单
使用bitmaps(位图)类型定义一个可以访问的名单名单id作为bitmaps的偏移量每次访问和bitmap里面的id进行比较如果访问id不在bitmaps里面进行拦截不允许访问。
3 采用布隆过滤器(布隆过滤器Bloom Filter是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数哈希函数。
布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法缺点是有一定的误识别率和删除困难。)
将所有可能存在的数据哈希到一个足够大的bitmaps(位图)中一个一定不存在的数据会被 这个bitmaps拦截掉从而避免了对底层存储系统的查询压力。
4 进行实时监控当发现Redis的命中率开始急速降低需要排查访问对象和访问的数据和运维人员配合可以设置黑名单限制服务。
命中率 缓存次数/总的查询次数
4.5.2 缓存击穿
缓存击穿是指缓存中没有但数据库中有的数据一般是缓存时间到期这时由于并发用户特别多同时读缓存没读到数据又同时去数据库去取数据引起数据库压力瞬间增大造成过大压力。和缓存雪崩不同的是缓存击穿指并发查同一条数据缓存雪崩是不同数据都过期了很多数据都查不到从而查数据库。
问题描述 key对应的数据存在但在redis中没有过期此时若有大量并发请求过来这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存这个时候大并发的请求可能会瞬间把后端DB压垮。 解决方案 key可能会在某些时间点被超高并发地访问是一种非常“热点”的数据。这个时候需要考虑一个问题缓存被“击穿”的问题。1预先设置热门数据在redis高峰访问之前把一些热门数据提前存入到redis里面加大这些热门数据key的时长
2实时调整现场监控哪些数据热门实时调整key的过期时长
3使用锁① 就是在缓存失效的时候判断拿出来的值为空不是立即去加载数据库。② 先使用缓存工具的某些带成功操作返回值的操作比如Redis的SETNX去set一个mutex key③ 当操作返回成功时再进行load db的操作并回设缓存,最后删除mutex key④ 当操作返回失败证明有线程在load db当前线程睡眠一段时间再重试整个get缓存的方法。 4.5.3 缓存雪崩
缓存雪崩是指缓存同一时间大面积的失效所以后面的请求都会落到数据库上造成数据库短时间内承受大量请求而崩掉。
问题描述 key对应的数据存在但在redis中过期此时若有大量并发请求过来这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存这个时候大并发的请求可能会瞬间把后端DB压垮。缓存雪崩与缓存击穿的区别在于缓存雪崩针对很多key缓存缓存击穿则是某一个key正常访问 当缓存失效的时候大量访问进入到数据库存储层 解决方案:
缓存失效时的雪崩效应对底层系统的冲击非常可怕
1 构建多级缓存架构nginx缓存 redis缓存 其他缓存ehcache等
2 使用锁或队列
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
3 设置过期标志更新缓存
记录缓存数据是否过期设置提前量如果过期会触发通知另外的线程在后台去更新实际key的缓存。
4 将缓存失效时间分散开
比如我们可以在原有的失效时间基础上增加一个随机值比如1-5分钟随机这样每一个缓存的过期时间的重复率就会降低就很难引发集体失效的事件。
4.6 分布式锁
4.6.1 问题描述
随着业务发展的需要原单体单机部署的系统被演化成分布式集群系统后由于分布式系统多线程、多进程并且分布在不同机器上这将使原单机部署情况下的并发控制锁策略失效单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问这就是分布式锁要解决的问题
分布式锁主流的实现方案
1. 基于数据库实现分布式锁
2. 基于缓存Redis等
3. 基于Zookeeper
4. 基于redission实现分布式锁
每一种分布式锁解决方案都有各自的优缺点
1. 性能redis最高
2. 可靠性zookeeper最高
3. 使用redission实现分布式锁可以实现自动续期
这里我们就基于redis实现分布式锁。
4.6.2 使用redis实现分布式锁
redis:命令
set命令的选项
EX second 设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。PX millisecond 设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。NX 只在键不存在时才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。XX 只在键已经存在时才对键进行设置操作。
实现图如下使用nx命令即可 1. 多个客户端同时获取锁setnx
2. 获取成功执行业务逻辑{从db获取数据放入缓存}执行完成释放锁del
3. 其他客户端等待重试
4.6.3 redis实现分布式锁步骤
1、创建SpringBoot项目
2、添加依赖 !--Java操作redis--dependencygroupIdredis.clients/groupIdartifactIdjedis/artifactId/dependency!--封装了一些redis启动时要配置的信息--dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactIdversion2.6.0/version/dependency
3、配置连接信息
#设置reis的索引
spring.redis.database6
#设置连接redis的密码
spring.redis.password密码
#设置的redis的服务器
spring.redis.host192.168.111.127
#端口号
spring.redis.port6379
#连接超时时间毫秒
spring.redis.timeout1800000
#连接池最大连接数使用负值表示没有限制
spring.redis.lettuce.pool.max-active20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle0
4、配置类
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
import java.time.Duration;
/***这里虽然会报错但是不影响运行*/
EnableCaching
Configuration
public class RedisConfig extends CachingConfigurerSupport {/*** 连接池的设置*/Beanpublic JedisPoolConfig getJedisPoolConfig() {JedisPoolConfig jedisPoolConfig new JedisPoolConfig();return jedisPoolConfig;}
/*** RedisTemplate*/Beanpublic RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) {RedisTemplateString, Object template new RedisTemplate();RedisSerializerString redisSerializer new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer new Jackson2JsonRedisSerializer(Object.class);ObjectMapper om new ObjectMapper();// 指定要序列化的域field,get和set,以及修饰符范围ANY是都有包括private和publicom.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);// 指定序列化输入的类型类必须是非final修饰的final修饰的类比如String,Integer等会跑出异常om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);template.setConnectionFactory(factory);//key序列化方式template.setKeySerializer(redisSerializer);//value序列化template.setValueSerializer(jackson2JsonRedisSerializer);//value hashmap序列化template.setHashValueSerializer(jackson2JsonRedisSerializer);return template;}
/*** 缓存处理* param factory* return*/Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializerString redisSerializer new StringRedisSerializer();Jackson2JsonRedisSerializer jackson2JsonRedisSerializer new Jackson2JsonRedisSerializer(Object.class);//解决查询缓存转换异常的问题ObjectMapper om new ObjectMapper();om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(om);// 配置序列化解决乱码的问题,过期时间600秒RedisCacheConfiguration config RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;}
}
5、编写代码
先在redis中设置一个值set num 10
[rootlocalhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli -a lwl
Warning: Using a password with -a or -u option on the command line interface may not be safe.
127.0.0.1:6379 select 6
OK
127.0.0.1:6379 set num 10
OK
controller代码
/*** 使用redis实现分布式锁*/
RestController
public class RedisLock {/*** 所有操作都封装在 RedisTemplate 中所以将 RedisTemplate 作为bean注入进来*/Resourceprivate RedisTemplate redisTemplate;
GetMapping(testLock)public String testLock(){//测试是否能连接redis数据库Object num redisTemplate.opsForValue().get(num);System.out.println(num num);
//1、获取锁这里的setIfAbsent方法就是setnx方法只有当数据库中没有这个lock关键字时才能够让返回值为trueBoolean aBoolean redisTemplate.opsForValue().setIfAbsent(lock, test);//2、根绝排它锁返回的结果进行操作if (aBoolean){//2.1.1 抢到排它锁对数据进行处理redisTemplate.opsForValue().decrement(num);//2.1.2 将锁删除redisTemplate.delete(lock);}else {//2.2.1 没有抢到锁,线程进行休眠try {Thread.sleep(100);//2.2.2 休眠后还要继续去抢占锁,调用这个方法testLock();} catch (InterruptedException e) {e.printStackTrace();}}return null;}
}
4.6.4 分布式锁问题及优化
1、无法释放锁
进行压力测试时
问题setnx刚好获取到锁业务逻辑出现异常比如算术异常导致锁无法释放
解决设置过期时间自动释放锁。
设置过期时间有两种方式
1. 首先想到通过expire设置过期时间缺乏原子性如果在setnx和expire之间出现异常锁也无法释放
2. 在set时指定过期时间推荐 设置过期时间
代码中设置过期时间
//第三个参数代表时间最后一个参数代表单位
Boolean lock redisTemplate.opsForValue().setIfAbsent(lock,111,3,TimeUnit.SECONDS);
其他的都不变
2、释放时释放错误
场景如果业务逻辑的执行时间是7s。执行流程如下
1. index1业务逻辑没执行完3秒后锁被自动释放。
2. index2获取到锁执行index1的业务逻辑3秒后锁被自动释放。
3. index3获取到锁执行index1的业务逻辑1秒后执行完成。
4. index1业务逻辑执行完成开始调用del释放锁这时释放的是index3的锁导致index3的业务只执行1s就被别人释放。最终等于没锁的情况。
解决setnx获取锁时设置一个指定的唯一值例如uuid释放前获取这个值判断是否自己的锁 controller层代码
/*** 使用redis实现分布式锁*/
RestController
public class RedisLock {/*** 所有操作都封装在 RedisTemplate 中所以将 RedisTemplate 作为bean注入进来*/Resourceprivate RedisTemplate redisTemplate;
GetMapping(testLock)public String testLock(){//获取uuid作为锁的唯一值防止误删String uuid UUID.randomUUID().toString();//1、获取锁这里的setIfAbsent方法就是setnx方法//设置过期时间为3秒Boolean aBoolean redisTemplate.opsForValue().setIfAbsent(lock, uuid,3, TimeUnit.SECONDS);//2、根绝排它锁返回的结果进行操作if (aBoolean){//2.1.1 抢到排它锁对数据进行处理redisTemplate.opsForValue().decrement(num);//2.1.2 删除之前判断删除的值是否等于当前锁的值if (uuid.equals((String)redisTemplate.opsForValue().get(num))){//2.1.3 将锁删除redisTemplate.delete(lock);}}else {//2.2.1 没有抢到锁,线程进行休眠try {Thread.sleep(100);//2.2.2 休眠后还要继续去抢占锁,调用这个方法testLock();} catch (InterruptedException e) {e.printStackTrace();}}return null;}
}
3、删除操作缺乏原子性
场景当前已经设置了过期时间也设置了uuid但是也会有极端情况假如当前index1的业务逻辑执行完也已经判定了uuid是当前锁的uuid但是过期时间到了自动释放锁这时index2抢到了锁执行了赋值操作lock的value值已经换成了index2的uuid此时 index1 执行删除锁的操作但是lock的值已经换成了index2的uuid值所以删除的锁是index2的锁
解决使用lua脚本保证删除操作的原子性
KEYS[1] 用来表示在redis 中用作键的参数占位主要用来传递在redis 中的key。
ARGV[1] 用来表示在redis 中用作值的参数占位主要用来传递在redis 中的value。
eval if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end 1 aaa bbb
命令解读evallua脚本的关键字引号中的语言如果得到的这个key为aaa的value等于bbb那么就删除aaa这个key并返回1否则返回01代表希望返回1aaa bbb传入的参数aaa代表keybbb代表value
127.0.0.1:6379[6] set aaa bbb
OK
127.0.0.1:6379[6] keys *
1) aaa
2) num
127.0.0.1:6379[6] eval if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end 1 aaa bbb
(integer) 1
127.0.0.1:6379[6] keys *
1) num
controller层代码
/*** 使用redis实现分布式锁*/
RestController
public class RedisLock {/*** 所有操作都封装在 RedisTemplate 中所以将 RedisTemplate 作为bean注入进来*/Resourceprivate RedisTemplate redisTemplate;
GetMapping(testLock)public String testLock(){//获取uuid作为锁的唯一值防止误删String uuid UUID.randomUUID().toString();//1、获取锁这里的setIfAbsent方法就是setnx方法//设置过期时间为3秒Boolean aBoolean redisTemplate.opsForValue().setIfAbsent(lock, uuid,3, TimeUnit.SECONDS);//2、根绝排它锁返回的结果进行操作if (aBoolean){//2.1.1 抢到排它锁对数据进行处理redisTemplate.opsForValue().decrement(num);/*使用lua脚本进行原子性操作*///2.1.2 定义lua脚本如果传入的key和value在数据库中是相对应的那么就删除这个key并返回1否则返回 0String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end;//2.1.3 使用redis执行lua,指定脚本的返回值类型为LongDefaultRedisScriptLong redisScript new DefaultRedisScript();redisScript.setScriptText(script);//2.1.4 因为删除判断的时候返回的0会被其封装为指定的数据类型如果不封装那么默认返回String 类型//2.1.5 如果不封装那么返回字符串与 0 会有发生错误所以设置一下返回值类型 为Long。redisScript.setResultType(Long.class);//2.1.6 第一个要是script 脚本 第二个需要判断的key第三个就是key所对应的值。后面两个就相当于是参数redisTemplate.execute(redisScript, Arrays.asList(redisTemplate.opsForValue().get(num)),uuid);}else {//2.2.1 没有抢到锁,线程进行休眠try {Thread.sleep(100);//2.2.2 休眠后还要继续去抢占锁,调用这个方法testLock();} catch (InterruptedException e) {e.printStackTrace();}}return null;}
}
4.6.5 lua脚本
Lua 是一个小巧的[脚本语言]Lua脚本可以很容易的被C/C 代码调用也可以反过来调用C/C的函数Lua并没有提供强大的库一个完整的Lua解释器不过200k所以Lua不适合作为开发独立应用程序的语言而是作为嵌入式脚本语言。
很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言以此来实现可配置性、可扩展性。
这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。
lua脚本教程地址Lua 教程_w3cschool
local useridKEYS[1]; #定义的变量值靠参数传递
local prodidKEYS[2]; #定义的变量值靠参数传递
#相当于 strsk:prodid:qt 这里的..相当于java里面的 “ ”
local qtkeysk:..prodid..:qt;
local usersKeysk:..prodid..:usr;
#这里是判定是否存在这样一个key如果存在这个key则返回1
local userExistsredis.call(sismember,usersKey,userid);
if tonumber(userExists)1then return 2;
end
#获取key的value值必须是数字类型
local num redis.call(get ,qtkey);
#如果值0 则返回0
if tonumber(num)0 then return 0;
else #否则将该key的value值减1redis.call(decr,qtkey);#将userskey作为keyuserid作为value值 添加到set集合中redis.call(sadd,usersKey,userid);
end
return 1;
lua脚本在redis中的优势
①将复杂的或者多步的redis操作写为一个脚本一次提交给redis执行减少反复连接redis的次数。提升性能。
②LUA脚本是类似redis事务有一定的原子性不会被其他命令插队可以完成一些redis事务性的操作。
③但是注意redis的lua脚本功能只有在Redis 2.6以上的版本才可以使用。