docker_redis

200 阅读5分钟

操作系统为centos7.5

redis的安装

```txt
1. 拿取镜像
    docker pull redis
2. 创建配置文件目录
    mkdir -p /mydata/redis/conf
    touch /mydata/redis/conf/redis.conf
3. 启动
    docker run -p 6379:6379 --name redis \
    -v /mydata/redis/data:/data \
    -v /mydata/redis/conf/redis.conf:/etc/redis/redis.conf \
    -d redis redis.server /etc/redis/redis.conf
4. 设置自启动
    docker update redis --restart=always
```

代码整合redis

  1. 导入依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <exclusions>
            <exclusion>
                <groupId>io.lettuce</groupId>
                <artifactId>lettuce-core</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
    
    
  2. 配置redis
    spring
        redis
            host:
            username:
            password:
    
  3. 使用StringRedisTemplate

数据库与缓存一致性

问题

  1. 几种缓存使用模式
  2. 删除缓存,还是更新缓存?
  3. 先操作数据库还是先操作缓存?
  4. 如何保证数据的最终一致性?

解决方案

  1. 几种缓存使用模式

    • Cache-Aside Pattern:旁路缓存模式
    • Read-Through/Write-Through(读写穿透)
    • Write-behind(异步缓存写入)

    旁路缓存的读数据流程

     -   读的时候,先读缓存,缓存命中的话,直接返回数据
     -   缓存没有命中的话,就去读数据库,从数据库取出数据,放入缓存后,同时返回响应
    

    旁路缓存的写数据流程

     写请求,更新数据库,删除旧缓存
    
  2. 使用删除缓存替换更新缓存可以防止脏数据的问题。

  3. Cache-Aside缓存模式,选择了先操作数据库而不是先操作缓存。

  4. 如何保证数据一致性

    • 缓存延时双删
    • 删除缓存重试机制
    • 读取biglog异步删除缓存

屏幕截图 2023-03-02 145419.png 5. 使用分布式读写锁和设置数据过期时间(大体思路)。常规数据(读多写少,即时性,一致性要求不高的数据)完全可以使用spring-Cache springCache

缓存穿透

问题

缓存穿透:指查询一个一定不存在的数据,由于缓存不命中时,需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,进而给数据库带来压力。

解决方案

  1. 如果是非法请求,我们在API入口,对参数进行校验,过滤非法值。
  2. 如果查询数据库为空,我们可以给缓存设置个空值,或者默认值。但是如有写请求进来的话,需要更新缓存,以保证一致性,同时,最后给缓存值设置适当的过期时间。
  3. 使用布隆过滤器快速判断数据是否存在。即一个查询请求过来时,先通过布隆过滤器判断值是否存在,存在才继续往下查。

缓存雪崩

问题

缓存雪崩:指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。

解决方案

  • 缓存雪崩一般是由于大量数据同时过期造成的,对于这个原因,可通过均匀设置过期时间解决,即让过期时间相对离散一点。如果采用一个较大固定值+一个较小随机值,5小时+0-1800秒这样。
  • Redis 故障宕机也可能引起缓存雪奔。这就需要构造Redis高可用集群

缓存击穿

问题

缓存击穿: 指热点key在某个时间点过期的时候,而恰好在这个时间点对这个Key有大量的并发请求过来,从而大量的请求打到db。 缓存击穿看着有点像缓存雪崩,其实它两区别是,缓存雪崩是指数据库压力过大甚至down机,缓存击穿只是大量并发请求到了DB数据库层面。可以认为击穿是缓存雪奔的一个子集吧。有些文章认为它俩区别,是在于击穿针对某一热点key缓存,雪奔则是很多key。

解决方案

  1. 使用互斥锁方案。缓存失效时,不是立即去加载db数据,而是先使用某些带成功返回的原子操作命令,如(Redis的setnx)去操作,成功的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。
    //多个请求同时查询缓存为空
    //实现方法
    1. synchronized(this)或者添加到逻辑代码之上;(springboot组件为单例)
        1. 先看缓存中是否存在
        2. 若不存在则查询数据库
        3. 放入缓存
        **本地锁**在分布是中存在问题
    2. 分布式锁
        1. 占分布锁(原子命令)
            Boolean lock redisTemplate.opsForValue().setIfAbsent("lock",uuid,30,TimeUnit.Second);
        2. lock 为true表示加锁成功
            执行业务
        3. 加锁失败
            重试
        4. 解锁
            查询锁的值与创建的值相同开始删锁,使用lua脚本
            /*使用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("lock"), uuid);
    
    
  2. “永不过期” ,是指没有设置过期时间,但是热点数据快要过期时,异步线程去更新和设置过期时间。
  3. springboot集成Redisson实现分布式锁操作
    1. 导入依赖 org.redisson redisson

    2. 配置

          @Configuration
          @ComponentScan
          @EnableCaching
          public static class Application {
      
              @Bean(destroyMethod="shutdown")
              RedissonClient redisson() throws IOException {
                  RedissonClient redisson = Redisson.create();
      
                  Config config = new Config();
                  config.useSingleServer().setAddress("redis://192.168.56.10:6379");
                  RedissonClient redisson = Redisson.create(config);
                  return redisson;
              }
          }
      
    3. 锁操作

      redisson的分布式锁

    主要:读写锁、闭锁、信号量

缓存热Key

问题

在Redis中,我们把访问频率高的key,称为热点key。如果某一热点key的请求到服务器主机时,由于请求量特别大,可能会导致主机资源不足,甚至宕机,从而影响正常的服务。

解决方案

  • Redis集群扩容:增加分片副本,均衡读流量;

  • 对热key进行hash散列,比如将一个key备份为key1,key2……keyN,同样的数据N个备份,N个备份分布到不同分片,访问时可随机访问N个备份中的一个,进一步分担读流量;

  • 使用二级缓存,即JVM本地缓存,减少Redis的读请求。

其他

不同的业务场景,Redis选择适合的数据结构

  • 排行榜适合用zset
  • 缓存用户信息一般用hash
  • 消息队列,文章列表适用用list
  • 用户标签、社交需求一般用set
  • 计数器、分布式锁等一般用String类型

Redis一些有坑的命令

  1. 不能使用 keys指令
  2. 慎用O(n)复杂度命令,如hgetall等
  3. 慎用Redis的monitor命令
  4. 禁止使用flushall、flushdb
  5. 注意使用del命令

参考