Redis学习04

Redis主从复制

主机数据更新后根据配置和策略, 自动同步到备机的 master/slaver 机制,Master 以 写为主,Slave 以读为主

  • 读写分离,性能扩展
  • 容灾快速恢复

image-20220716131229150

怎么玩:主从复制

拷贝多个 redis.conf 文件 include(写绝对路径)
开启 daemonize yes
Pid 文件名字 pidfile
指定端口 port
Log 文件名字
dump.rdb 名字 dbfilename
Appendonly 关掉或者换名字

[root@jinx100 ~]# mkdir /myredis
[root@jinx100 ~]# cd /myredis/
[root@jinx100 myredis]# cp /etc/redis.conf /myredis/redis.conf
[root@jinx100 myredis]# vim redis.conf
[root@jinx100 myredis]# vim redis6379.conf
[root@jinx100 myredis]# cp redis6379.conf redis6380.conf
[root@jinx100 myredis]# vim redis6380
[root@jinx100 myredis]# vim redis6380.conf 
[root@jinx100 myredis]# cp redis6379.conf redis6381.conf
[root@jinx100 myredis]# vim redis6381.conf 
[root@jinx100 myredis]# redis-server redis6379.conf 

新建 redis6379.conf,填写以下内容

include /myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb

新建 redis6380.conf,填写以下内容

include /myredis/redis.conf
pidfile /var/run/redis_6380.pid
port 6380
dbfilename dump6380.rdb

新建 redis6381.conf,填写以下内容

include /myredis/redis.conf
pidfile /var/run/redis_6381.pid
port 6381
dbfilename dump6381.rdb

slave-priority 10 设置从机的优先级,值越小,优先级越高,用于选举主机时使用。默认 100

启动三台 redis 服务器

image-20220716142527906

查看系统进程,看看三台服务器是否启动

image-20220716142543861

查看三台主机运行情况

info replication 打印主从复制的相关信息

redis-cli -p 6381

配从(库)不配主(库)

 slaveof  <ip> <port>

成为某个实例的从服务器

  1. 在 6380 和 6381 上执行: slaveof 127.0.0.1 6379

  2. 在主机上写,在从机上可以读取数据

​ 在从机上写数据报错

image-20220716143108087

  1. 主机挂掉,重启就行,一切如初

  2. 从机重启需重设:slaveof 127.0.0.1 6379 可以将配置增加到文件中。永久生效。

常用 3 招

一主二仆

切入点问题?slave1、slave2 是从头开始复制还是从切入点开始复制?比如从 k4 进来,
那之前的 k1,k2,k3 是否也可以复制?
从机是否可以写?set 可否?
主机 shutdown 后情况如何?从机是上位还是原地待命?
主机又回来了后,主机新增记录,从机还能否顺利复制?
其中一台从机 down 后情况如何?依照原有它能跟上大部队吗?

从服务器挂掉,然后再重启,再设置成从服务器,可以看到在挂掉期间主服务器的内容,重新复制主服务器的

即使大哥挂了,还认大哥,主服务器挂了,从服务器还是从服务器

薪火相传

上一个Slave 可以是下-一个slave的Master, Slave 同样可以接收其他slaves 的连接和同
步请求,那么该slave作为了链条中下-一个的master,可以有效减轻master的写压力,去
中心化降低风险。

用slaveof <ip><port>
中途变更转向:会清除之前的数据,重新建立拷贝最新的
风险是一旦某个slave宕机,后面的slave 都没法备份
主机挂了,从机还是从机,无法写数据了

反客为主

当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何
修改。
用slaveof no one将从机变为主机。

原理

  1. 当从服务器连接上主服务器,从服务器向主服务器发送进行数据同步消息
  2. 主服务器接到从服务器发送过来同步消息, 把主服务器数据进行持久化,rdb文件
    把rdb文件发送从服务器,从服务器拿 到rdb进行读取
  3. 每次主服务器进行写操作之后,和从服务器进行数据同步。(主服务器主动发起)
Slave启动成功连接到mater后会发送一个syne命令
Master接到命令启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, master 传送整个数据文件到slave,以完成一次完全同步
全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制: Master 继续将新的所有收集到的修改命令依次传给dave完成同步
但是只要是重新连接master,一 次完全同步(全量复制将被自动执行)

哨兵模式(sentinel)

是什么

反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库 转换为主库

使用步骤

调整为一主二仆模式,6379 带着 6380、6381 
自定义的/myredis 目录下新建 sentinel.conf 文件,名字
绝不能错

配置哨兵,填写内容 
sentinel monitor mymaster 127.0.0.1 6379 1
其中 mymaster 为监控对象起的服务器名称, 1 为至少有多少个哨兵同意迁移的数量。

启动哨兵 
/usr/local/bin
redis 做压测可以用自带的 redis-benchmark 工具
执行 redis-sentinel /myredis/sentinel.conf 
当主机挂掉,从机选举中产生新的主机
(大概 10 秒左右可以看到哨兵窗口日志,切换了新的主机)
哪个从机会被选举为主机呢?根据优先级别:slave-priority
原主机重启后会变为从机。
复制延时
由于所有的写操作都是先在 Master 上操作,然后同步更新到 Slave 上,所以从 Master
同步到 Slave 机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave
机器数量的增加也会使这个问题更加严重。

故障恢复

image-20220716204014343

优先级在 redis.conf 中默认:slave-priority 100,值越小优先级越高
偏移量是指获得原主机数据最全的
每个 redis 实例启动后都会随机生成一个 40 位的 runid

主从复制

private static JedisSentinelPool jedisSentinelPool=null;
public static Jedis getJedisFromSentinel(){
if(jedisSentinelPool==null){
 Set<String> sentinelSet=new HashSet<>();
 sentinelSet.add("192.168.11.103:26379");
 JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
 jedisPoolConfig.setMaxTotal(10); //最大可用连接数
jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
jedisPoolConfig.setMinIdle(5); //最小闲置连接数
jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping
pong
jedisSentinelPool=new
JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
return jedisSentinelPool.getResource();
 }else{
return jedisSentinelPool.getResource();
 }
}

Redis 集群

问题

容量不够,redis 如何进行扩容?

多个redis

并发写操作, redis 如何分摊?

集群

另外,主从模式,薪火相传模式,主机宕机,导致 ip 地址发生变化,应用程序中配置 需要修改对应的主机地址、端口等信息。

之前通过代理主机来解决,但是 redis3.0 中提供了解决方案。就是无中心化集群配置。

什么是集群

Redis 集群实现了对 Redis 的水平扩容,即启动 N 个 redis 节点,将整个数据库分布存
储在这 N 个节点中,每个节点存储总数据的 1/N。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群
中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。

image-20220716231401165

删除持久化数据

将 rdb,aof 文件都删除掉。

制作 6 个实例,6379,6380,6381,6389,6390,6391

配置基本信息

开启 daemonize yes
Pid 文件名字
指定端口
Log 文件名字
Dump.rdb 名字
Appendonly 关掉或者换名字

redis cluster 配置修改

cluster-enabled yes 打开集群模式
cluster-config-file nodes-6379.conf 设定节点配置文件名
cluster-node-timeout 15000 设定节点失联时间,超过该时间(毫秒),集群自动进
行主从切换。
include /home/bigdata/redis.conf
port 6379
pidfile "/var/run/redis_6379.pid"
dbfilename "dump6379.rdb"
dir "/home/bigdata/redis_cluster"
logfile "/home/bigdata/redis_cluster/redis_err_6379.log"
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000

:%s/6379/6389把6379改成6389

启动 6 个 redis 服务

redis-server redis6379.conf

将六个节点合成一个集群

组合之前,请确保所有 redis 实例启动后,nodes-xxxx.conf 文件都生成正常。

cd /opt/redis-6.2.1/src
合体
redis-cli --cluster create --cluster-replicas 1 192.168.11.101:6379
192.168.11.101:6380 192.168.11.101:6381 192.168.11.101:6389
192.168.11.101:6390 192.168.11.101:6391

此处不要用 127.0.0.1, 请用真实 IP 地址 --replicas 1 采用最简单的方式配置集群,一台主机,一台从机,正好三组。

普通方式登录
可能直接进入读主机,存储数据时,会出现 MOVED 重定向操作。所以,应该以集群
方式登录。
redis-cli -p 6379
-c 采用集群策略连接,设置数据会自动切换到相应的
写主机 
redis-cli -c -p 6379

通过 cluster nodes 命令查看集群信息

cluster nodes

redis cluster 如何分配这六个节点?

一个集群至少要有三个主节点。
选项 --cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的 IP 地址,每个从库和主库不在一个 IP 地
址上。

什么是 slots

[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[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 号插槽。

在集群中录入值

在 redis-cli 每次录入、查询键值,redis 都会计算出该 key 应该送往的插槽,如果不是
该客户端对应服务器的插槽,redis 会报错,并告知应前往的 redis 实例地址和端口。
redis-cli 客户端提供了 –c 参数实现自动重定向。
如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。
不在一个 slot 下的键值,是不能使用 mget,mset 等多键操作。
可以通过{}来定义组的概念,从而使 key 中{}内相同内容的键值对放到一个 slot 中去。
mset kl{cust} v1 k2{cust} v2 k3{cust} v3 

查询集群中的值

CLUSTER GETKEYSINSLOT <slot><count> 返回 count 个 slot 槽中的键。
cluster keyslot k1 插槽值
cluster countkeysinslot 12706 查询插槽12706有几个key
cluster getkeysinslot 449 

故障恢复

如果主节点下线?从节点能否自动升为主节点?注意:15 秒超时

主节点恢复后,主从关系会如何?主节点回来变成从机。

如果所有某一段插槽的主从节点都宕掉,redis 服务是否还能继续?

如果某一段插槽的主从都挂掉,而 cluster-require-full-coverage 为 yes ,那么 ,整个
集群都挂掉
如果某一段插槽的主从都挂掉,而 cluster-require-full-coverage 为 no ,那么,该插槽
数据全都不能使用,也无法存储。
redis.conf 中的参数 cluster-require-full-coverage

集群的 Jedis 开发

即使连接的不是主机,集群会自动切换主机存储。主机写,从机读。 无中心化主从集群。无论从哪台主机写的数据,其他主机上都能读到数据

public class JedisClusterTest {
 public static void main(String[] args) {
 Set<HostAndPort>set =new HashSet<HostAndPort>();
 set.add(new HostAndPort("192.168.31.211",6379));
 JedisCluster jedisCluster=new JedisCluster(set);
 jedisCluster.set("k1", "v1");
 System.out.println(jedisCluster.get("k1"));
 }

Redis 集群提供了以下好处

实现扩容

分摊压力

无中心配置相对简单

Redis 集群的不足

多键操作是不被支持的
多键的 Redis 事务是不被支持的。lua 脚本不被支持
由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分
片的方案想要迁移至 redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

Redis 应用问题解决

缓存穿透

问题描述

key 对应的数据在数据源并不存在,每次针对此 key 的请求从缓存获取不到,请求
都会压到数据源,从而可能压垮数据源。比如用一个不存在的用户 id 获取用户信息,
不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

image-20220717000937554

1. 应用服务器压力变大了
2. redis命中率降低
3. 一直查数据库
redis查询不到数据
出现很多非正常访问

解决方案

一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写的,并且出
于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每
次请求都要到存储层去查询,失去了缓存的意义。

解决方案:

  1. 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我 们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最 长不超过五分钟
  2. 设置可访问的名单(白名单): 使用 bitmaps 类型定义一个可以访问的名单,名单 id 作为 bitmaps 的偏移量, 每次访问和 bitmap 里面的 id 进行比较,如果访问 id 不在 bitmaps 里面,进行拦截,不允许访问。
  3. 采用布隆过滤器:(布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。 它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函 数)。 布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。) 将所有可能存在的数据哈希到一个足够大的 bitmaps 中,一个一定不存在的数据会被 这个 bitmaps 拦截掉,从而避免了对底层存储系统的查询压力。
  4. 进行实时监控:当发现 Redis 的命中率开始急速降低,需要排查访问对象和 访问的数据,和运维人员配合,可以设置黑名单限制服务

缓存击穿

问题描述

key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求
发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能
会瞬间把后端 DB 压垮。

image-20220717052823819

数据库访问压力瞬时增加
redis里面没有出现大量key过期
redis正常运行
redis某个key过期了,大量访问使用这个key

解决方案

key 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时
候,需要考虑一个问题:缓存被“击穿”的问题。
解决问题:
(1)预先设置热门数据:在 redis 高峰访问之前,把一些热门数据提前存入到
redis 里面,加大这些热门数据 key 的时长
(2)实时调整:现场监控哪些数据热门,实时调整 key 的过期时长
(3)使用锁:
(1) 就是在缓存失效的时候(判断拿出来的值为空),不是立即去 load db。
(2) 先使用缓存工具的某些带成功操作返回值的操作(比如 Redis 的 SETNX)
去 set 一个 mutex key
(3) 当操作返回成功时,再进行 load db 的操作,并回设缓存,最后删除 mutex
key;
(4) 当操作返回失败,证明有线程在 load db,当前线程睡眠一段时间再重试
整个 get 缓存的方法。

image-20220717110632212

缓存雪崩

问题描述

key 对应的数据存在,但在 redis 中过期,此时若有大量并发请求过来,这些请求
发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能
会瞬间把后端 DB 压垮。
缓存雪崩与缓存击穿的区别在于这里针对很多 key 缓存,前者则是某一个 key
正常访问

image-20220717110708952

image-20220717110716557

解决方案

缓存失效时的雪崩效应对底层系统的冲击非常可怕!
解决方案:
(1) 构建多级缓存架构:nginx 缓存 + redis 缓存 +其他缓存(ehcache 等)
(2) 使用锁或队列:
用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行
读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并
发情况
(3) 设置过期标志更新缓存:
记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程
在后台去更新实际 key 的缓存。
(4) 将缓存失效时间分散开:
比如我们可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随
机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效
的事件。

分布式锁

问题描述

随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于
分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发
控制锁策略失效,单纯的 Java API 并不能提供分布式锁的能力。为了解决这个问题就
需要一种跨 JVM 的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

分布式锁主流的实现方案:

  1. 基于数据库实现分布式锁
  2. 基于缓存(Redis 等)
  3. 基于 Zookeeper
    每一种分布式锁解决方案都有各自的优缺点:
  4. 性能:redis 最高
  5. 可靠性:zookeeper 最高
    这里,我们就基于 redis 实现分布式锁。

解决方案:使用 redis 实现分布式锁

redis:命令
# set sku:1:info “OK” NX PX 10000
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 :只在键已经存在时,才对键进行设置操作。

image-20220717110847017

  1. 多个客户端同时获取锁(setnx)
  2. 获取成功,执行业务逻辑{从 db 获取数据,放入缓存},执行完成释放锁(del)
  3. 其他客户端等待重试

编写代码

Redis: set num

@GetMapping("testLock")
public void testLock(){
 //1 获取锁,setne
 Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111");
 //2 获取锁成功、查询 num 的值
 if(lock){
 Object value = redisTemplate.opsForValue().get("num");
 //2.1 判断 num 为空 return
 if(StringUtils.isEmpty(value)){
 return;
 }
 //2.2 有值就转成成 int
 int num = Integer.parseInt(value+"");
 //2.3 把 redis 的 num 加 1
 redisTemplate.opsForValue().set("num", ++num);
 //2.4 释放锁,del
 redisTemplate.delete("lock");
 }else{
 //3 获取锁失败、每隔 0.1 秒再获取
 try {
 Thread.sleep(100);
 testLock();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
}
重启,服务集群,通过网关压力测试:
ab -n 1000 -c 100 http://192.168.140.1:8080/test/testLock

image-20220717111003458

查看 redis 中 num 的值

image-20220717111025652

基本实现。

问题:setnx 刚好获取到锁,业务逻辑出现异常,导致锁无法释放 解决:设置过期时间,自动释放锁。

优化之设置锁的过期时间

设置过期时间有两种方式:
1. 首先想到通过 expire 设置过期时间(缺乏原子性:如果在 setnx 和 expire 之
间出现异常,锁也无法释放)
2. 在 set 时指定过期时间(推荐)
set users 10 nx ex 12

image-20220717111148748

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GCHfuwH3-1658114916793)(C:/Users/Administrator/AppData/Roaming/Typora/typora-user-images/image-20220717112546007.png)]

设置过期时间:

image-20220717111210233

压力测试肯定也没有问题。自行测试
问题:可能会释放其他服务器的锁。
场景:如果业务逻辑的执行时间是 7s。执行流程如下
1. index1 业务逻辑没执行完,3 秒后锁被自动释放。
2. index2 获取到锁,执行业务逻辑,3 秒后锁被自动释放。
3. index3 获取到锁,执行业务逻辑
4. index1 业务逻辑执行完成,开始调用 del 释放锁,这时释放的是 index3 的锁,
导致 index3 的业务只执行 1s 就被别人释放。
最终等于没锁的情况。
解决:setnx 获取锁时,设置一个指定的唯一值(例如:uuid);释放前获取这
个值,判断是否自己的锁

优化之 UUID 防误删

image-20220717111254481

image-20220717111328196

image-20220717123510071

问题:删除操作缺乏原子性。
场景:
1. index1 执行删除时,查询到的 lock 值确实和 uuid 相等
uuid=v1
set(lock,uuid);
2. index1 执行删除前,lock 刚好过期时间已到,被 redis 自动释放
在 redis 中没有了 lock,没有了锁。
3. index2 获取了 lock
index2 线程获取到了 cpu 的资源,开始执行方法
uuid=v2
set(lock,uuid);
4. index1 执行删除,此时会把 index2 的 lock 删除
index1 因为已经在方法中了,所以不需要重新上锁。index1 有执行的权
限。index1 已经比较完成了,这个时候,开始执行
删除的 index2 的锁!

优化之 LUA 脚本保证删除的原子性

image-20220717123957872

@GetMapping("testLockLua")
public void testLockLua() {
 //1 声明一个 uuid ,将做为一个 value 放入我们的 key 所对应的值中
 String uuid = UUID.randomUUID().toString();
 //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
 String skuId = "25"; // 访问 skuId 为 25 号的商品 100008348542
 String locKey = "lock:" + skuId; // 锁住的是每个商品的数据
 // 3 获取锁
 Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3,
TimeUnit.SECONDS);
 // 第一种: lock 与过期时间中间不写任何的代码。
 // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
 // 如果 true
 if (lock) {
 // 执行的业务逻辑开始
 // 获取缓存中的 num 数据
 Object value = redisTemplate.opsForValue().get("num");
 // 如果是空直接返回
 if (StringUtils.isEmpty(value)) {
 return;
 }
 // 不是空 如果说在这出现了异常! 那么 delete 就删除失败! 也就是说锁永
远存在!
 int num = Integer.parseInt(value + "");
 // 使 num 每次+1 放入缓存
 redisTemplate.opsForValue().set("num", String.valueOf(++num));
 /*使用 lua 脚本来锁*/
 // 定义 lua 脚本
 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return
redis.call('del', KEYS[1]) else return 0 end";
 // 使用 redis 执行 lua 执行
 DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
 redisScript.setScriptText(script);
 // 设置一下返回值类型 为 Long
 // 因为删除判断的时候,返回的 0,给其封装为数据类型。如果不封装那么默认
返回 String 类型,
 // 那么返回字符串与 0 会有发生错误。
 redisScript.setResultType(Long.class);
 // 第一个要是 script 脚本 ,第二个需要判断的 key,第三个就是 key 所对应的
值。
 redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
 } else {
 // 其他线程等待
 try {
 // 睡眠
 Thread.sleep(1000);
 // 睡醒了之后,调用方法。
 testLockLua();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
}

image-20220717123737381

项目中正确使用:

1. 定义 key,key 应该是为每个 sku 定义的,也就是每个 sku有一把锁。
String locKey ="lock:"+skuId; // 锁住的是每个商品的数据
Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey,
uuid,3,TimeUnit.SECONDS);

image-20220717123759760

总结

1、加锁
// 1. 从 redis 中获取锁,set k1 v1 px 20000 nx
String uuid = UUID.randomUUID().toString();
Boolean lock = this.redisTemplate.opsForValue()
 .setIfAbsent("lock", uuid, 2, TimeUnit.SECONDS);
2、使用 lua 释放锁
// 2. 释放锁 del
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return
redis.call('del', KEYS[1]) else return 0 end";
// 设置 lua 脚本返回的数据类型
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
// 设置 lua 脚本返回类型为 Long
redisScript.setResultType(Long.class);
redisScript.setScriptText(script);
redisTemplate.execute(redisScript, Arrays.asList("lock"),uuid);
3、重试
Thread.sleep(500);
testLock();
为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也
能保证后续其他客户端能加锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人
加的锁给解了。
- 加锁和解锁必须具有原子性。

Redis6.0 新功能

ACL

简介

Redis ACL 是 Access Control List(访问控制列表)的缩写,该功能允许根据可
以执行的命令和可以访问的键来限制某些连接。
在 Redis 5 版本之前,Redis 安全规则只有密码控制 还有通过 rename 来调整
高危命令比如 flushdb , KEYS* , shutdown 等。Redis 6 则提供 ACL 的功能对用户
进行更细粒度的权限控制 :
(1)接入权限:用户名和密码
(2)可以执行的命令
(3)可以操作的 KEY
参考官网:https://redis.io/topics/acl
命令

1、使用 acl list 命令展现用户权限列表

(1)数据说明

image-20220717141947890

2、使用 acl cat 命令

(1)查看添加权限指令类别

image-20220717142001712

(2)加参数类型名可以查看类型下具体命令

image-20220717142012542

3、使用 acl whoami 命令查看当前用户

4、使用 aclsetuser 命令创建和编辑用户 ACL

(1)ACL 规则 下面是有效 ACL 规则的列表。某些规则只是用于激活或删除标志,或对用户 ACL 执行给定更改的单个单词。其他规则是字符前缀,它们与命令或类别名称、键模式等连 接在一起。

(2)通过命令创建新用户默认权限 acl setuser user1

image-20220717142035656

在上面的示例中,我根本没有指定任何规则。如果用户不存在,这将使用 just created 的默认属性来创建用户。如果用户已经存在,则上面的命令将不执行任何操作。

(3)设置有用户名、密码、ACL 权限、并启用的用户 acl setuser user2 on >password ~cached:* +get

image-20220717142131331

(4)切换用户,验证权限

image-20220717145343558

IO 多线程

简介

Redis6 终于支撑多线程了,告别单线程了吗? IO 多线程其实指客户端交互部分的网络 IO 交互处理模块多线程,而非执行命令多线 程。Redis6 执行命令依然是单线程。

原理架构

Redis 6 加入多线程,但跟 Memcached 这种从 IO 处理到数据访问多线程的实现模式 有些差异。Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然 是单线程。之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务, LPUSH/LPOP 等等的并发问题。整体的设计大体如下:

image-20220717145426365

另外,多线程 IO 默认也是不开启的,需要再配置文件中配置 io-threads-do-reads yes io-threads 4

工具支持 Cluster

之前老版 Redis 想要搭集群需要单独安装 ruby 环境,Redis 5 将 redis-trib.rb 的 功能集成到 redis-cli 。另外官方 redis-benchmark 工具开始支持 cluster 模式了,通过 多线程的方式对多个分片进行压测。

image-20220717145455975

Redis 新功能持续关注

Redis6 新功能还有:
1、RESP3 新的 Redis 通信协议:优化服务端与客户端之间通信
2、Client side caching 客户端缓存:基于 RESP3 协议实现的客户端缓存功能。为
了进一步提升缓存的性能,将客户端经常访问的数据 cache 到客户端。减少 TCP 网络
交互。
3、Proxy 集群代理模式:Proxy 功能,让 Cluster 拥有像单实例一样的接入方式,
降低大家使用 cluster 的门槛。不过需要注意的是代理不改变 Cluster 的功能限制,不
支持的命令还是不会支持,比如跨 slot 的多 Key 操作。
4、Modules API
Redis 6 中模块 API 开发进展非常大,因为 Redis Labs 为了开发复杂的功能,从一
开始就用上 Redis 模块。Redis 可以变成一个框架,利用 Modules 来构建不同系统,而
不需要从头开始写然后还要 BSD 许可。Redis 一开始就是一个向编写各种系统开放的
平台。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值