7.29日学习打卡----初学Redis(七)

7.29日学习打卡

在这里插入图片描述

一 . Redis脑裂

什么是Redis的集群脑裂

Redis的集群脑裂是指因为网络问题,导致Redis Master节点跟Redis slave节点和Sentinel集群处于不同的网络分区,此时因为sentinel集群无法感知到master的存在,所以将slave节点提升为master节点。

在这里插入图片描述

注意
此时存在两个不同的master节点,就像一个大脑分裂成了两个。集群脑裂问题中,如果客户端还在基于原来的master节点继续写入数据,那么新的Master节点将无法同步这些数据,当网络问题解决之后,sentinel集群将原先的Master节点降为slave节点,此时再从新的master中同步数据,将会造成大量的数据丢失。

解决方案

redis.conf配置参数:

min-replicas-to-write 1
min-replicas-max-lag 5

参数:

第一个参数表示最少的slave节点为1个
第二个参数表示数据复制和同步的延迟不能超过5秒
配置了这两个参数:如果发生脑裂原Master会在客户端写入操作的时候拒绝请求。这样可以避免大量数据丢失。

二. Redis 缓存预热

缓存冷启动

缓存中没有数据,由于缓存冷启动一点数据都没有,如果直接就对外提供服务了,那么并发量上来Mysql就裸奔挂掉了。
在这里插入图片描述
缓存冷启动场景

新启动的系统没有任何缓存数据,在缓存重建数据的过程中,系统性能和数据库负载都不太好,所以最好是在系统上线之前就把要缓存的热点数据加载到缓存中,这种缓存预加载手段就是缓存预热。
在这里插入图片描述

解决思路

  • 提前给redis中灌入部分数据,再提供服务
  • 如果数据量非常大,就不可能将所有数据都写入redis,因为数据量太大了,第一是因为耗费的时间太长了,第二根本redis容纳不下所有的数据
  • 需要根据当天的具体访问情况,实时统计出访问频率较高的热数据
  • 然后将访问频率较高的热数据写入redis中,肯定是热数据也比较多,我们也得多个服务并行读取数据去写,并行的分布式的缓存预热

在这里插入图片描述

三. Redis 缓存穿透

在这里插入图片描述

概念

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

在这里插入图片描述

解释:
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。

解决方案

  • 对空值缓存:如果一个查询返回的数据为空(不管数据是否存在),我们仍然把这个空结果缓存,设置空结果的过期时间会很短,最长不超过5分钟。
  • 布隆过滤器:如果想判断一个元素是不是在一个集合里,一般想到的是将集合中所有元素保存起来,然后通过比较确定。

布隆过滤器

什么是布隆过滤器
在这里插入图片描述

布隆过滤器是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。

注意:
布隆说不存在一定不存在,布隆说存在你要小心了,它有可能不存在。

代码实现
引入hutool包

<dependency>
   <groupId>cn.hutool</groupId>
   <artifactId>hutool-all</artifactId>
   <version>5.7.17</version>
</dependency>

java代码实现

// 初始化 注意 构造方法的参数大小10 决定了布隆过滤器BitMap的大小
    BitMapBloomFilter filter = new BitMapBloomFilter(10);
    filter.add("123");
    filter.add("abc");
    filter.add("ddd");


    boolean abc = filter.contains("abc");
    System.out.println(abc);

四. Redis 缓存击穿

在这里插入图片描述

概念

某一个热点 key,在缓存过期的一瞬间,同时有大量的请求打进来,由于此时缓存过期了,所以请求最终都会走到数据库,造成瞬时数据库请求量大、压力骤增,甚至可能打垮数据库。
在这里插入图片描述

解决方案

  • 互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并执行数据库查询操作,其他的线程拿不到锁就阻塞等着,等到第一个线程将数据写入缓存后,其他线程直接查询缓存。
  • 热点数据不过期:直接将缓存设置为不过期,然后由定时任务去异步加载数据,更新缓存。

代码实现

 private final String LOCK_PREFIX = "lock:";


  public User getUser(Long id) throws InterruptedException {
    //设置个Key
    User user = (User) template.opsForValue().get("user:" + id);
    if (user != null) {
      return user;
     } else {
      Boolean b = tryLock(LOCK_PREFIX + id, "1", 100, TimeUnit.SECONDS);
      if (b) {
        log.info("开始执行业务逻辑");
        // 1、从数据库中查询
        User u = userMapper.selectById(id);
        // 2、加Redis缓存
        template.opsForValue().set("user:" + id, u);
        return u;
       } else {
        Thread.sleep(2000);
        return getUser(id);
       }
     }
   }


  // 抢占式 互斥加锁
  public Boolean tryLock(String key, String value, long timeout, TimeUnit unit) {
    // setIfAbsent已经是setNx + expire的合并命令
    return template.opsForValue().setIfAbsent(key, value, timeout, unit);
   }


五. Redis 开发规范

key设计技巧

1、把表名转换为key前缀,如tag:
2、把第二段放置用于区分key的字段,对应msyql中主键的列名,如user_id
3、第三段放置主键值,如2,3,4
4、第四段写存储的列名

user_idnameage
1jjy18
2zqg20

示例

#  表名 主键 主键值 存储列名字  
set user:user_id:1  {json}
set user:user_id:1:age 20
#查询这个用户
keys user:user_id:9*

value设计

拒绝bigkey
防止网卡流量、慢查询,string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。

命令使用

1.禁用命令
禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令,或者使用scan的方式渐进式处理。

2.合理使用select
redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。

3.使用批量操作提高效率
原生命令:例如mget、mset。
非原生命令:可以使用pipeline提高效率。

注意
但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)

4.不建议过多使用Redis事务功能

Redis的事务功能较弱(不支持回滚),而且集群版本(自研和官方)要求一次事务操作的key必须在一个slot上。

客户端使用

Jedis :https://github.com/xetorthio/jedis 重点推荐

Spring Data redis :https://github.com/spring-projects/spring-data-redis 使用Spring框架时推荐

Redisson :https://github.com/mrniko/redisson 分布式锁、阻塞队列的时重点推荐

1、避免多个应用使用一个Redis实例
不相干的业务拆分,公共数据做服务化。

2、使用连接池
可以有效控制连接,同时提高效率,标准使用方式:

执行命令如下:
Jedis jedis = null;
try {
   jedis = jedisPool.getResource();
 //具体的命令
   jedis.executeCommand()
} catch (Exception e) {
   logger.error("op key {} error: " + e.getMessage(), key, e);
} finally {
 //注意这里不是关闭连接,在JedisPool模式下,Jedis会被归还给资源池。
 if (jedis != null)
     jedis.close();
}

六. Redis 数据一致性

在这里插入图片描述
在这里插入图片描述
缓存说明

从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。

三种更新策略

  • 先更新数据库,再更新缓存
  • 先删除缓存,再更新数据库
  • 先更新数据库,再删除缓存

先更新数据库,再更新缓存
这套方案,大家是普遍反对的。为什么呢?
线程安全角度
同时有请求A和请求B进行更新操作,那么会出现

(1)线程A更新了数据库
(2)线程B更新了数据库
(3)线程B更新了缓存
(4)线程A更新了缓存

这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。

先删缓存,再更新数据库
该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:

(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库

注意:
该数据永远都是脏数据。

先更新数据库,再延时删缓存
在这里插入图片描述
这种情况存在并发问题吗?

(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存

发生这种情况的概率又有多少?

发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的,因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力
在这里插入图片描述

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

中北萌新程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值