Redis (三)

Redis

Redis和Lua整合

使用Lua的好处

  • 减少网络开销,在lua脚本中可以把多个命令放在同一个脚本中运行

  • 使用lua脚本,有多条命令时,可以保证redis的原子性,已达到实现分布式锁的效果

Redis消息模式(弱消息队列)

队列模式

MQ主要是用来

  • 解耦应用

  • 异步化消息

  • 流量削峰

​ 典型的消息服务是一个生产者和消费者模式的服务。一般是有生产者产生消息,将消息发送到队列中。而消息的消费者则监听消息,对消息进行处理。
​ 有很多非常优秀的消息队列服务的产品。例如 RabbitMQ、RocketMQ、Kafka 等。这些产品都具备非常高级的功能。可靠性、扩展性都非常的好。
​ 但是 redis 自身也能够很简单的实现消息队列的生产者和消费者模式。

Redis使用list类型的lpush和rpop实现消息队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ldG0UiZS-1605602674632)(C:\Users\18202\Documents\学习笔记\image-20200605113014114.png)]

注意事项

  • 消息接收方如果不知道队列中是否有消息,会一直发送rpop命令,如果这样的话,会每一次都建立一次连接,这样显然不好。
  • 可以使用brpop命令,它如果从队列中取不出来数据,会一直阻塞,在一定范围内没有取出则返回null

发布订阅模式

Redis实现分布式锁

业务场景

  1. 方式用户重复下单
  2. MQ消息去重

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xqlacuif-1605602674634)(C:\Users\18202\Documents\学习笔记\image-20200605113409831.png)]

  1. 重要操作(订单状态变更)
  2. 库存超卖

场景共性

  • 共享资源

解决方案

  • 共享资源互斥
  • 共享资源串行化
  • 问题转化
  • 锁的问题(将需求抽象后得到问题的本质)

锁的处理

  • 单应用中使用锁:(单进程多线程)

    synchronized、ReentrantLock

  • 分布式应用中使用锁:(多进程多线程)

    分布式锁是控制分布式系统之间同步访问共享资源的一种

image-20200605113853243
Redis实现分布式锁
原理

利用redis的单线程特性对共享资源进行串行化处理

获取锁
  • 方式1 (set命令) 推荐
/**
* 使用redis的set命令实现获取分布式锁
* @param lockKey 可以就是锁
* @param requestId 请求ID,保证同一性 uuid+threadID
* @param expireTime 过期时间,避免死锁
* @return
*/
public boolean getLock(String lockKey,String requestId,int expireTime) {
//NX:保证互斥性
// hset 原子性操作
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
if("OK".equals(result)) {
return true;
}
return false;
}
  • 方式2(setnx、expire命令) – 并发会产生问题

因为是分步的,setnx还有expire

setnx是一个老方法

public boolean getLock(String lockKey,String requestId,int expireTime) {
Long result = jedis.setnx(lockKey, requestId);
if(result == 1) {
//成功设置 失效时间
jedis.expire(lockKey, expireTime);
return true;
}
return false;
}

获取锁
  • 方式1(del命令) 并发问题
/**
* 释放分布式锁
* @param lockKey
* @param requestId
*/
public static void releaseLock(String lockKey,String requestId) {
  if (requestId.equals(jedis.get(lockKey))) {
     jedis.del(lockKey);
  }
}
  • 方式2 (redis+lua脚本) 推荐
public static boolean releaseLock(String lockKey, String requestId) {
     String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
	Object result = jedis.eval(script, Collections.singletonList(lockKey),
	Collections.singletonList(requestId));
	if (result.equals(1L)) {
	   return true;
	}
return false;
}
存在问题

单机
无法保证高可用

主–从无法保证数据的强一致性,在主机宕机时会造成锁的重复

无法续租
超过expireTime后,不能继续使用

分布式锁的实现方式
  • 基于Redis的set实现分布式锁

  • 基于zookeeper 临时节点的分布

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hzqlYw51-1605602674635)(C:\Users\18202\Documents\学习笔记\image-20200605134552343.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ieu3THMG-1605602674636)(C:\Users\18202\Documents\学习笔记\image-20200605134603752.png)]

生产环境使用分布式锁

Redisson

<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>2.7.0</version>
</dependency

常见缓存问题

数据读

缓存穿透

一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如
DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。
对不存在的key进行高并发访问,导致数据库压力瞬间增大,这就叫做【缓存穿透】。

解决方案
对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓
存。

缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如
DB)带来很大压力。
突然间大量的key失效了或redis重启,大量访问数据库

解决方案:

  1. key的失效期分散开 不同的key设置不同的有效期
  2. 设置二级缓存
  3. 高可用

缓存击穿

对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

解决方案
用分布式锁控制访问的线程
使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数
据库。
if(redis.sexnx()==1){ //先查询缓存 //查询数据库 //加入缓存 }
不设超时时间,写一致问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4mJbgxox-1605602674637)(C:\Users\18202\Documents\学习笔记\1591336573589.png)]

数据写

保证数据的最终一致性(延时双删)

  1. 先更新数据库同时删除缓存项(key),等读的时候再填充缓存
  2. 2秒后再删除一次缓存项(key)
  3. 设置缓存过期时间 Expired Time 比如 10秒 或1小时
  4. 将缓存删除失败记录到日志中,利用脚本提取失败记录再次删除(缓存失效期过长 7*24)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值