redis(4)(1)——(总结)缓存、分布式锁

一、缓存问题

1.1 缓存穿透

缓存穿透是指在高并发下查询key不存在的数据,会穿过缓存查询数据库。导致数据库压力过大而宕机

解决方案:

对查询结果为空的情况也进行缓存,缓存时间(ttl)设置短一点,或者该key对应的数据insert了 之后清理缓存。 会存在的问题:缓存太多空值占用了更多的空间

使用布隆过滤器。在缓存之前在加一层布隆过滤器,在查询的时候先去布隆过滤器查询 key 是否 存在,如果不存在就直接返回,存在再查缓存和DB。

1.2 缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效

解决方案:

1、 key的失效期分散开 不同的key设置不同的有效期

2、设置二级缓存(数据不一定一致)

3、高可用(脏读 

1.3 缓存击穿

缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来

解决方案

1、用分布式锁控制访问的线程 使用redis的setnx互斥锁先进行判断,这样其他线程就处于等待状态,保证不会有大并发操作去操作数 据库。

2、不设超时时间,volatile-lru 但会造成不一致问题

当数据库数据发生更新时,缓存中的数据不会及时更新,这样会造成数据库中的数据与缓存中的数据的 不一致,应用会从缓存中读取到脏数据。可采用延时双删策略处理

二、数据不一致

2.1 追求最终一致性

先更新数据库再更新缓存或者先更新缓存再更新数据库 本质上不是一个原子操作,所以时序控制不可行 高并发情况下会产生不一致

如何解决 强一致性很难,追求最终一致性(时间)

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

1、先更新数据库同时删除缓存项(key),等读的时候再填充缓存

2、2秒后再删除一次缓存项(key)

3、设置缓存过期时间 Expired Time 比如 10秒 或1小时  

4、将缓存删除失败记录到日志中,利用脚本提取失败记录再次删除(缓存失效期过长 7*24)

升级方案 通过数据库的binlog来异步淘汰key,利用工具(canal)将binlog日志采集发送到MQ中,然后通过ACK机 制确认处理删除缓存。

三、数据并发竞争

多客户端(Jedis)同时并发写一个key,一个key的值是1,本来按顺序修改为2,3,4,最后是4,但是顺 序变成了4,3,2,最后变成了2。

3.1 第一种方案:分布式锁+时间戳

Redis分布式锁的实现

主要用到的redis函数是setnx() 用SETNX实现分布式锁 时间戳 由于上面举的例子,要求key的操作需要顺序执行,所以需要保存一个时间戳判断set顺序。

系统A key 1 {ValueA 7:00} 系统B key 1 { ValueB 7:05} 假设系统B先抢到锁,

将key1设置为{ValueB 7:05}。接下来系统A抢到锁,发现自己的key1的时间戳早 于缓存中的时间戳(7:00<7:05),那就不做set操作了。

3.2 第二种方案:利用消息队列

在并发量过大的情况下,可以通过消息中间件进行处理,把并行读写进行串行化。 把Redis的set操作放在队列中使其串行化,必须的一个一个执行。

3.3 Hot Key

3.3.1 如何处理热Key

1、变分布式缓存为本地缓存 发现热key后,把缓存数据取出后,直接加载到本地缓存中。可以采用Ehcache、Guava Cache都可 以,这样系统在访问热key数据时就可以直接访问自己的缓存了。(数据不要求时时一致

2、在每个Redis主节点上备份热key数据,这样在读取时可以采用随机读取的方式,将访问压力负载到 每个Redis上。

3、利用对热点数据访问的限流熔断保护措施 每个系统实例每秒最多请求缓存集群读操作不超过 400 次,一超过就可以熔断掉,不让请求缓存集群, 直接返回一个空白信息,然后用户稍后会自行再次重新刷新页面之类的。(首页不行,系统友好性差) 通过系统层自己直接加限流熔断保护措施,可以很好的保护后面的缓存集群。

3.4 Big Key

大key指的是存储的值(Value)非常大,常见场景: 热门话题下的讨论 大V的粉丝列表 序列化后的图片 没有及时处理的垃圾数据

大key的影响:

大key会大量占用内存,在集群中无法均衡 Redis的性能下降,主从复制异常 在主动删除或过期删除时会操作时间过长而引起服务阻塞

3.4.1 如何发现大key

1、redis-cli --bigkeys命令。可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大 key。 但如果Redis 的key比较多,执行该命令会比较慢

2、获取生产Redis的rdb文件,通过rdbtools分析rdb生成csv文件,再导入MySQL或其他数据库中进行 分析统计,根据size_in_bytes统计bigke

3.4.2 大key的处理

1、string类型的big key,尽量不要存入Redis中,可以使用文档型数据库MongoDB或缓存到CDN上。 如果必须用Redis存储,最好单独存储,不要和其他的key一起存储。采用一主一从或多从。

2、单个简单的key存储的value很大,可以尝试将对象分拆成几个key-value, 使用mget获取值

3、删除大key时不要使用del,因为del是阻塞命令,删除时会影响性能。

4、使用 unlink命令

四、分布式锁

4.1 利用Watch实现Redis乐观锁

乐观锁基于CAS(Compare And Swap)思想(比较并替换),是不具有互斥性,不会产生锁等待而消
耗资源,但是需要反复的重试,但也是因为重试的机制,能比较快的响应。因此我们可以利用redis来
实现乐观锁。具体思路如下:
1、利用redis的watch功能,监控这个redisKey的状态值 
2、获取redisKey的值 
3、创建redis事务 
4、给这个key的值+1 
5、然后去执行这个事务,如果key的值被修改过则回滚,key不加1

4.2 setnx

(1)获取锁实现方式

public boolean getLock(String lockKey,String requestId,int expireTime) {
//NX:保证互斥性
// hset 原子性操作 只要lockKey有效 则说明有进程在使用分布式锁
String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);
if("OK".equals(result)) {
return true;
}
return false;
}

(2)释放锁

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;
}

 (3)分布式锁的实际应用 

数据并发竞争:

                      利用分布式锁+时间戳可以将处理串行化,前面已经讲过了。

防止库存超卖

//加锁并设置有效期
if(redis.lock("RDL",200)){
//判断库存
if (orderNum<getCount()){
//加锁成功 ,可以下单
order(5);
updateCount(5)
//释放锁
redis,unlock("RDL");
}

注意此种方法会降低处理效率,这样不适合秒杀的场景,秒杀可以使用CAS和Redis队列的方式。

4.3 Redisson分布式锁的使用

锁的获取和释放

public class DistributedRedisLock {
//从配置类中获取redisson对象
private static Redisson redisson = RedissonManager.getRedisson();
private static final String LOCK_TITLE = "redisLock_";
//加锁
public static boolean acquire(String lockName){
//声明key对象
String key = LOCK_TITLE + lockName;
//获取锁对象
RLock mylock = redisson.getLock(key);



//加锁,并且设置锁过期时间3秒,防止死锁的产生 uuid+threadId
mylock.lock(2,3,TimeUtil.SECOND);




//加锁成功
return true;
}
//锁的释放
public static void release(String lockName){
//必须是和加锁时的同一个key
String key = LOCK_TITLE + lockName;
//获取所对象
RLock mylock = redisson.getLock(key);



//释放锁(解锁)
mylock.unlock();
}
}


#使用


public String discount() throws IOException{
String key = "lock001";


//加锁
DistributedRedisLock.acquire(key);



//执行具体业务逻辑
dosoming


//释放锁
DistributedRedisLock.release(key);
//返回结果
return soming;
}

注:以上为本人小小总结,如果对您起到了一点点帮助,请给予我一点鼓励,在下方点个小小的赞,谢谢,如有错误之处,望不吝指出,非常感谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值