当前位置: 首页 > news >正文

网站开发和界面的区别/爱网站关键词挖掘

网站开发和界面的区别,爱网站关键词挖掘,东莞做网站公司排名,吧网站做软件的软件下载目录 四、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…

目录

四、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 目录;
[root@localhost redis]# mkdir redis_cluster
[root@localhost redis]# ls
bin  redis_cluster  

4.3.3 创建目录

在 redis_cluster 目录下,创建名为7001、7002,7003、7004、7005,7006的目录
[root@localhost redis_cluster]# mkdir 7001 7002 7003 7004 7005 7006
[root@localhost redis_cluster]# ls
7001  7002  7003  7004  7005  7006

4.3.4 配置redis7001.conf

配置集群时,建议不要配置密码

复制配置文件到redis_cluster目录下:一定要关闭密码
[root@localhost redis_cluster]# cp /usr/lwl/soft/redis/bin/redis6379.conf redis.conf
[root@localhost 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
​
复制过程:
[root@localhost 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服务

[root@localhost 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
l=`ps -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中重新复制一份文件进行配置

执行脚本文件:

[root@localhost redis_cluster]# sh redisstart.sh 
[root@localhost 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 --color=auto 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,那么一般前三个是主节点,后三个是从节点
​
​
[root@localhost 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 1
>>> Performing 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:7003
>>> Trying 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 都可以
[root@localhost 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:7006@17006 myself,slave e97325b5f5d020cfd4940421cdece4c3dc7e53d1 0 1676631290000 6 connected
a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:7004@17004 slave 5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 0 1676631290187 4 connected
68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:7005@17005 slave 16577299b09086cf992b25ea047a53449064a62a 0 1676631291000 5 connected
16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:7002@17002 master - 0 1676631291199 2 connected 5461-10922
5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:7001@17001 master - 0 1676631288161 1 connected 0-5460
e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:7003@17003 master - 0 1676631289177 3 connected 10923-16383

4.3.9 检查集群的状态

执行检查集群状态的命令:出现的界面和配置集群出现的状态一样
[root@localhost 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

[root@localhost redis_cluster]# mkdir 7007
[root@localhost redis_cluster]# cp 7001/redis.conf 7007/redis.conf
[root@localhost redis_cluster]# cd 7007
[root@localhost 7007]# ls
redis.conf
[root@localhost 7007]# vim redis.conf
这里使用  :%s/7001/7007/g   对文件进行修改
​
启动7007服务
[root@localhost 7007]# /usr/lwl/soft/redis/bin/redis-server redis.conf 
[root@localhost 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和端口号是集群中的任意一个节点。

 

检查节点的状态

[root@localhost 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,并配置服务

[root@localhost redis_cluster]# mkdir 7008
[root@localhost redis_cluster]# cp 7001/redis.conf 7008/redis.conf
[root@localhost redis_cluster]# cd 7008
[root@localhost 7008]# ls
redis.conf
[root@localhost 7008]# vim redis.conf
这里使用  :%s/7001/7008/g   对文件进行修改
​
启动7008服务
[root@localhost 7008]# /usr/lwl/soft/redis/bin/redis-server redis.conf 
[root@localhost 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的一长串字符)
​
​
[root@localhost 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 c00baee8694cfdbd06d66c751adb42b34cb8716e
>>> Adding node 127.0.0.1:7008 to cluster 127.0.0.1:7002
>>> 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)
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

[root@localhost 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地址和端口号,只是进入分配的一个接口
[root@localhost 7008]# /usr/lwl/soft/redis/bin/redis-cli --cluster reshard 127.0.0.1:7002
>>> 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: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: 

分配后查看节点状态:

[root@localhost 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从节点
[root@localhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli --cluster del-node 127.0.0.1:7002 f187059affef4732ac4ab5a776d3c7f7056834e0
>>> Removing node f187059affef4732ac4ab5a776d3c7f7056834e0 from cluster 127.0.0.1:7002
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.

删除主节点

删除主节点需要先使用 reshard 把主节点的slots移到其他节点才可以
归还过程和分配插槽的过程一致
​
命令:redis客户端 --cluster del-node [集群中的任意一个节点:端口号] 被删除的节点的id
​
归还插槽后,删除主节点7007
[root@localhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli --cluster del-node 127.0.0.1:7002 c00baee8694cfdbd06d66c751adb42b34cb8716e
>>> Removing node c00baee8694cfdbd06d66c751adb42b34cb8716e from cluster 127.0.0.1:7002
>>> Sending CLUSTER FORGET messages to the cluster...
>>> SHUTDOWN the node.

4.3.14 集群自动组网

集群关机之后,集群重启,只需要直接启动各个节点,不需要重新组网,redis会根据node.conf自动组网

前提是集群关闭的时候,是正常的关闭

正常的关闭,是先连接上之后,再shutdown进行关闭

4.3.15 故障检测

验证集群是否生效

关闭一个主节点查看对应的备用节点是不是能够顶替主节点成为主节点
​
注:
1、关闭的时候一定要使用shutdown命令不要使用kill命令
2、关闭主节点以后需要耐心等待一会儿 让他重新分配一下空间
​
关闭主机7001
[root@localhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli  -p 7001 shutdown
查看节点状态
[root@localhost 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服务
[root@localhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-server 7001/redis.conf
#连接集群
[root@localhost redis_cluster]# /usr/lwl/soft/redis/bin/redis-cli -c -p 7002
127.0.0.1:7002> cluster nodes
a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 127.0.0.1:7004@17004 master - 0 1676783272031 9 connected 0-5460
#7001重启后成为7004的从机
5fc3b5dc391e2ef67d13bbbfd63d694319b66fd4 127.0.0.1:7001@17001 slave a14f9a6fed50a6f920e3ceb94f3bd06cd0febded 0 1676783271009 9 connected
16577299b09086cf992b25ea047a53449064a62a 127.0.0.1:7002@17002 myself,master - 0 1676783271000 8 connected 5461-10922
9e3f7eca62137abb171c19dc15af84ed4b0afb11 127.0.0.1:7006@17006 slave e97325b5f5d020cfd4940421cdece4c3dc7e53d1 0 1676783273045 6 connected
e97325b5f5d020cfd4940421cdece4c3dc7e53d1 127.0.0.1:7003@17003 master - 0 1676783270000 3 connected 10923-16383
68650f71b5631d9b73f719cf75cfc9da5ceb1997 127.0.0.1:7005@17005 slave 16577299b09086cf992b25ea047a53449064a62a 0 1676783271000 8 connected
存/取数据的时候查看对应的端口号
​
因为在测试存值时,添加了一个age属性的值存到了7001中,现在7004接替了7001,所以数据应该存在7004中
[root@localhost 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登入后,再录入、查询键值对可以自动重定向。
​
[root@localhost 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 don't 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--><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency><!--封装了一些redis启动时要配置的信息--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
​<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.6.0</version></dependency>

3、配置连接信息

#设置reis的索引
spring.redis.database=6
#设置连接redis的密码
spring.redis.password=密码
#设置的redis的服务器
spring.redis.host=192.168.111.127
#端口号
spring.redis.port=6379
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0

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 RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();RedisSerializer<String> 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) {RedisSerializer<String> 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

[root@localhost 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
命令解读:eval:lua脚本的关键字引号中的语言:如果得到的这个key为aaa的value等于bbb,那么就删除aaa这个key并返回1,否则返回01:代表希望返回1aaa   bbb:传入的参数,aaa代表key,bbb代表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,指定脚本的返回值类型为LongDefaultRedisScript<Long> 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 userid=KEYS[1];  #定义的变量,值靠参数传递
local prodid=KEYS[2];  #定义的变量,值靠参数传递
​
#相当于 str="sk:"+prodid+":qt" ,这里的..相当于java里面的 “ + ”
local qtkey="sk:"..prodid..":qt"; 
local usersKey="sk:"..prodid..":usr"; 
​
#这里是判定是否存在这样一个key,如果存在这个key则返回1
local userExists=redis.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作为key,userid作为value值 添加到set集合中redis.call("sadd",usersKey,userid);
end
return 1;

lua脚本在redis中的优势

①将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。
②LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
③但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。

相关文章:

Redis第四讲

目录 四、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…...

Linux Ubuntu 软件安装与卸载

文章目录1 下载 deb 安装包后安装2 清理安装包3 卸载安装2 Ubuntu升级某个软件参考&#xff1a;1 下载 deb 安装包后安装 进入下载位置&#xff0c;执行 terminal sudo dpkg -i *.deb推荐sudo apt install *.deb 2 清理安装包 sudo apt-get install 会将下载的文件放在 /var…...

metasploit穷举模块

目录 工具介绍 常用模块 参数介绍 工具使用 工具介绍 Metasploit框架(Metasploit Framework, MSF)是一个开源工具&#xff0c; 旨在方便渗透测试&#xff0c;它是由Ruby程序语言编写的模板化框架&#xff0c;具有很好的扩展性&#xff0c;便于渗透测试人员开发、使用定制的…...

day35 贪心算法 | 435、无重叠区间 763、划分字母区间 56、合并区间

题目 435、无重叠区间 给定一个区间的集合&#xff0c;找到需要移除区间的最小数量&#xff0c;使剩余区间互不重叠。 注意: 可以认为区间的终点总是大于它的起点。 区间 [1,2] 和 [2,3] 的边界相互“接触”&#xff0c;但没有相互重叠。 示例 1: 输入: [ [1,2], [2,3], […...

C++Primer15.5节练习

练习15.18&#xff1a; Base* p &d1&#xff1a;合法 p &d2&#xff1a;不合法&#xff0c;只有当派生类公有地继承基类时&#xff0c;用户代码才能使用派生类向基类的转换 p &d3&#xff1a;不合法&#xff0c;只有当派生类公有地继承基类时&#xff0…...

【日常点滴019】Python制作流浪气球游戏(导弹射击类)

Python制作流浪气球游戏&#xff08;导弹射击类&#xff09;教学课程代码&#xff08;分步教学版&#xff09;1、构建全局通用代码结构2、构建气球精灵类3、构建导弹精灵类4、碰撞检测5、构建游戏信息类 &#xff08;最终完整代码&#xff09;教学课程代码&#xff08;分步教学…...

effective c++阅读之旅---条款29

为"异常安全"而努力是值得的&#xff01; 什么是异常安全&#xff1f; 所谓的"异常安全"&#xff0c;往往值得是函数接口的异常安全&#xff0c;它要求函数满足两个条件&#xff1a; 异常抛出时&#xff1a; 1、不泄露任何资源 2、不允许数据被破坏 异常安…...

Android system — 进程生命周期与ADJ

Android system — 进程oom_adj0. 引言1. 进程的生命周期1.1 Foreground process1.2 Visible process1.3 Service process1.4 Background process1.5 Empty process2. Lowmemorykiller2.1 ADJ级别2.2 进程state级别2.3 lmk策略2.4 如何查看应用oom_adj值3. 注意0. 引言 本文主要…...

vue3+ts+node个人博客系统(三)

一.主页顶部和中心面板布局 &#xff08;1&#xff09; 首先先去element-plus选择合适的布局el-container (2)在头部处编写相应的菜单栏el-menu,在这里要注意动态绑定路由的问题:default-active"$route.path"。将default-active设置为$route.path&#xff0c;el-me…...

Python第三方模块

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的绽放&#xff0…...

怎样查询PMP成绩?

【如何查询成绩】1、输入网址&#xff08;PMI官网&#xff0c;不知道网址的私戳&#xff09;&#xff0c;点击 Log In如果忘记 PMI 的账号和密码了&#xff0c;怎么办&#xff1f;可以在你报名机构官网的个人中心的学习中心的我的报名处查看 PMI 的注册名和密码2、点击 Exam An…...

说说变量 __name__ 和它可能取到的一个值 __main__

结合 例子 弄懂 变量__name__ 和它的值’ main’这两个东西。 首先&#xff0c;明白两个定义&#xff0c; __name__是一个变量&#xff0c; __main__是个普通字符串&#xff0c;不是变量&#xff0c;但可以作为变量的值。 例子&#xff1a; 1.py 代码如下&#xff1a; if _…...

软考高级-信息系统管理师之整体管理(最新版)

整体管理 1、项目整体管理概述2、制定项目章程(选择,案例,论文)制定项目章程过程制定项目章程的依据1、协议2.项目工作说明书:3、商业论证4、事业环境因素包括,但不限于如下事项。5、组织过程资产:项目选择方法项目启动会议项目目标引导技术3、制订项目管理计划(选择)项目管…...

JVM学习篇垃圾收集器ParNewCMS与底层三色标记算法详解

1. 垃圾收集算法 2. 分代收集理论 当前虚拟机的垃圾收集都采用分代收集算法&#xff0c;这种算法没有什么新的思想&#xff0c;只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代&#xff0c;这样我们就可以根据各个年代的特点选择合适的垃圾收集算法…...

基于FFmpeg和Screen Capturer Recorder实现屏幕和声音的录制

当我们看到一些精彩的视频画面&#xff0c;但无法下载时&#xff0c;可以通过录屏的方式将视频和音频录制下来。 这个时候我们需要安装采集视频和音频的工具screen-capture-recorder。 以下是在windows10环境下&#xff0c;基于FFmpeg和Screen Capturer Recorder实现屏幕和声音…...

猿人学14题详解

目测重点在于cookie&#xff1a;mz和m 获取mz.js: https://match.yuanrenxue.com/static/match/match14/m.js 获取设置m&#xff1a; https://match.yuanrenxue.com/api/match/14/m 一、还原16进制 const fs require(fs); const parser require(babel/parser); const gen…...

Allegro如何快速把推挤的走线变平滑操作指导

Allegro如何快速把推挤的走线变平滑操作指导 Allegro有个非常强大的功能,推挤命令,可以快速的让走线以不报DRC的形式避让目标 推挤后的效果如下图 但是走线不够平滑,如果每一段都去再推一下比较费时间,下面介绍allegro本身自带的优化类似走线的功能 具体操作如下 点击Rout…...

nginx基础学习

作为前端开发者&#xff0c;也很有必要了解一些运维部署知识。 nginx的作用有哪些&#xff1f; 负载平衡动静分离反向代理 何为反向代理&#xff1f; 反向代理即是&#xff0c;用户访问nginx服务器&#xff0c;nginx又将请求转发到真正服务器上&#xff0c;为什么用户不能直…...

【HDFS】FsDatasetImpl#recoverClose方法

recoverClose的目的recoverClose的过程recoverClose的调用点一、前言 HDFS客户端写文件时,如果某个datanode发生错误或者异常。客户端会把这个datanode从pipeline里踢除,然后进行pipiline recovery,用剩余datanodes去写或者满足一定的条件时补充新的datanode到pipeline中写…...

加油站会员管理小程序实战开发教程15 完结篇

这篇是本次实战课程的最后一篇,我们在上篇还有两个问题没解决。一个是会员卡类型显示不对,一个是不同的会员卡我们希望背景色显示不同。我们先处理一下这两个问题 1 显示会员卡类型 在列表上直接显示会员卡类型,目前显示的是数字,这个是因为枚举类型导致的。枚举类型在数…...

学习 Python 之 Pygame 开发坦克大战(五)

学习 Python 之 Pygame 开发坦克大战&#xff08;五&#xff09;坦克大战完善地图1. 创建砖墙2. 给砖墙增加子弹击中的碰撞效果3. 给砖墙添加坦克不能通过的碰撞效果4. 添加石墙5. 添加玩家基地6. 最终效果坦克大战完善地图 我的素材放到了百度网盘里&#xff0c;里面还有原版…...

【ROS】Windows系统安装ROS体验

大家平时玩ROS都是在Ubuntu系统上&#xff0c;那Windows系统可以安装吗&#xff0c;答案是&#xff1a;可以的&#xff01;Windows为了发展自家的物联网生态&#xff0c;已经在Windows系统支持ROS了。 文章目录1.安装VS 20172.安装Chocolatey & Git3.安装ROS4.运行ROS例程1…...

第1讲-初步认识数据库系统(测试题总结)

一、测试题 数据库系统 包含 数据库管理系统 详细版&#xff1a; 数据库管理系统DBMS是数据管理软件&#xff0c;在用户和操作系统之间。 数据库系统DBS由数据库&#xff0c;数据库管理系统&#xff08;及其应用开发工具&#xff09;、应用程序和数据库管理员DBA组成的存储、管…...

进程-操作系统结构

进程-操作系统结构 中文仅本人理解&#xff0c;有错误请联系我。 操作系统为不同方面服务&#xff0c;有不同的设计角度。 为用户&#xff1a; 使用 为程序员&#xff1a;创造 程序员需要关注的就是system call接口的调度 file systems&#xff1a;ntfs&#xff0c;ext4 commu…...

【网络原理6】数据链路层协议——以太网

数据链路层负责的是相邻两个网络节点之间的数据以帧为单位进行传输。 具体关于数据链路层的介绍&#xff0c;已经在这一篇文章当中提到了。 初识网络&#xff1a;IP、端口、网络协议、TCP-IP五层模型_革凡成圣211的博客-CSDN博客TCP/IP五层协议详解https://blog.csdn.net/weix…...

组合数学原理与例题

目录 一、前言 二、计数原理 1、加法原理 2、分割立方体&#xff08;lanqiaoOJ题号1620&#xff09; 3、乘法原理 4、挑选子串&#xff08;lanqiaoOJ题号1621&#xff09; 5、糊涂人寄信&#xff08;lanqiaoOJ题号1622&#xff09; 6、战斗吧N皇后&#xff08;lanqiaoO…...

【机器学习 深度学习】通俗讲解集成学习算法

目录&#xff1a;集成学习一、机器学习中的集成学习1.1 定义1.2 分类器(Classifier)1.2.1 决策树分类器1.2.2 朴素贝叶斯分类器1.2.3 AdaBoost算法1.2.4 支持向量机1.2.5 K近邻算法1.3 集成学习方法1.3.1 自助聚合(Bagging)1.3.2 提升法(Boosting)1.3.2.1 自适应adaboost1.3.3 …...

汉字----dgfont

Abstract 字符生成是一个具有挑战性的问题,特别是对于一些由大量字符组成的书写系统,近年来受到了广泛的关注。然而,现有的字体生成方法通常是在监督学习中。它们需要大量的配对数据,这是劳动密集型和昂贵的收集。此外,常见的图像到图像转换模型通常将风格定义为纹理和颜…...

C# chart绘图 鼠标响应

1、图形自动滚动设置 chart1.ChartAreas[0].AxisX.Maximum 横坐标显示区域最大值 chart1.ChartAreas[0].AxisX.Minimum 横坐标显示区域最小值 显示宽度 chart1.ChartAreas[0].AxisX.Maximum - chart1.ChartAreas[0].AxisX.Minimum chart1.ChartAreas[0].AxisX.Maximum x_d…...

结构体与引用

1.结构体基本概念结构体属于用户自定义的数据类型&#xff0c;允许用户存储不同的数据类型2.结构体定义和使用语法: struct 结构体 { 结构体成员列表 };通过结构体创建变量的方式有三种:struct 结构体名 变量名struct 结构体名 变量名 { 成员1值&#xff0c;成员2值...}定义结构…...