clxmm
首页
  • 01redis学习

    • 01redis开始
  • 02redis学习

    • 01redis开始
  • vue2学习

    • 01vue学习
  • centos安装zsh

    • centos安装zsh
GitHub (opens new window)
首页
  • 01redis学习

    • 01redis开始
  • 02redis学习

    • 01redis开始
  • vue2学习

    • 01vue学习
  • centos安装zsh

    • centos安装zsh
GitHub (opens new window)
  • redis

    • 01redis
    • 02redis持久化
    • 03redis事务和管道
    • 04redis发布与订阅
    • 05Redis复制(replica)
    • 06Redis哨兵(sentinel)
    • 07Redis集群(cluster)
    • 08redis与SpringBoot集成
    • redis单线程与多线程
    • redis的BigKey
    • redis缓存双写一致性
    • 12redis与mysql双写一致性
    • 13案列bitmap-hyperlog-geo
    • 14布隆过滤器BloomFilter
    • 缓存预热、雪崩、击穿、穿透
    • redis的分布式锁
    • 17Redlock算法和缓存淘汰
      • 1.接v8.0
      • 2.Redis分布式锁-Redlock红锁算法
        • 2.1 官网
        • 2.2 为什么
        • 2.3 Redlock算法设计理念
        • 2.4 代码实现-v9.0
      • 3.Redisson 源码
      • 4.多机案列
        • 4.1 理论来源
        • 4.2 代码参考
        • 4.3 案列
      • 5.redis缓存过期淘汰策略
        • 5.1 面试题
        • 5.2 redis内存设置
        • 5.3 redis的数据是如何删除的
        • 5.4 redis缓存淘汰策略
    • 18Redis源码
  • redis02

  • 后端学习
  • redis
clxmm
2024-11-12
目录

17Redlock算法和缓存淘汰

# 1.接v8.0

  • 自研一把分布式锁,面试中回答的主要考点
    • 按照JUC里面java.util.concurrent.locks.Lock接口规范编写
    • lock()加锁关键逻辑
      • 加锁:加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间
      • 自旋
      • 续期
    • unlock解锁关键逻辑
      • 将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉,只能自己删除自己的锁

# 2.Redis分布式锁-Redlock红锁算法

# 2.1 官网

Redis 的分布式锁 |文档 --- Distributed Locks with Redis | Docs (opens new window)

# 2.2 为什么

之前我们手写的分布式锁有什么缺点?

demo

  • 线程 1 首先获取锁成功,将键值对写入 redis 的 master 节点,在 redis 将该键值对同步到 slave 节点之前,master 发生了故障;redis 触发故障转移,其中一个 slave 升级为新的 master,此时新上位的master并不包含线程1写入的键值对,因此线程 2 尝试获取锁也可以成功拿到锁,此时相当于有两个线程获取到了锁,可能会导致各种预期之外的情况发生,例如最常见的脏数据。
  • 我们加的是排它独占锁,同一时间只能有一个建redis锁成功并持有锁,严禁出现2个以上的请求线程拿到锁。危险的

# 2.3 Redlock算法设计理念

  • redis之父提出了Redlock算法解决这个问题:Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。Redlock算法是实现高可靠分布式锁的一种有效解决方案,可以在实际开发中使用。最下方还有笔记

  • 设计理念

    该方案也是基于(set 加锁、Lua 脚本解锁)进行改良的,所以redis之父antirez 只描述了差异的地方,大致方案如下。

    假设我们有N个Redis主节点,例如 N = 5这些节点是完全独立的,我们不使用复制或任何其他隐式协调系统,

    为了取到锁客户端执行以下操作:

    获取当前时间,以毫秒为单位;
    依次尝试从5个实例,使用相同的 key 和随机值(例如 UUID)获取锁。当向Redis 请求获取锁时,客户端应该设置一个超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为 10 秒,则超时时间应该在 5-50 毫秒之间。这样可以防止客户端在试图与一个宕机的 Redis 节点对话时长时间处于阻塞状态。如果一个实例不可用,客户端应该尽快尝试去另外一个 Redis 实例请求获取锁;
    客户端通过当前时间减去步骤 1 记录的时间来计算获取锁使用的时间。当且仅当从大多数(N/2+1,这里是 3 个节点)的 Redis 节点都取到锁,并且获取锁使用的时间小于锁失效时间时,锁才算获取成功;
    如果取到了锁,其真正有效时间等于初始有效时间减去获取锁所使用的时间(步骤 3 计算的结果)。
    如果由于某些原因未能获得锁(无法在至少 N/2 + 1 个 Redis 实例获取锁、或获取锁的时间超过了有效时间),客户端应该在所有的 Redis 实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。

    该方案为了解决数据不一致的问题,直接舍弃了异步复制只使用 master 节点,同时由于舍弃了 slave,为了保证可用性,引入了 N 个节点,官方建议是 5。阳哥本次教学演示用3台实例来做说明。

    客户端只有在满足下面的这两个条件时,才能认为是加锁成功。

    条件1:客户端从超过半数(大于等于N/2+1)的Redis实例上成功获取到了锁;

    条件2:客户端获取锁的总耗时没有超过锁的有效时间。

  • 实现

    为什么是奇数? N = 2X + 1 (N是最终部署机器数,X是容错机器数)

    1. 先知道什么是容错

      失败了多少个机器实例后我还是可以容忍的,所谓的容忍就是数据一致性还是可以Ok的,CP数据一致性还是可以满足;

      加入在集群环境中,redis失败1台,可接受。2X+1 = 2 * 1+1 =3,部署3台,死了1个剩下2个可以正常工作,那就部署3台。

      加入在集群环境中,redis失败2台,可接受。2X+1 = 2 * 2+1 =5,部署5台,死了2个剩下3个可以正常工作,那就部署5台。

    2. 为什么是奇数?

      最少的机器,最多的产出效果

      加入在集群环境中,redis失败1台,可接受。2N+2= 2 * 1+2 =4,部署4台

      加入在集群环境中,redis失败2台,可接受。2N+2 = 2 * 2+2 =6,部署6台

# 2.4 代码实现-v9.0

Redisson是java的redis客户端之一,提供了一些api方便操作redis

官网 (opens new window)

操作文档:Distributed locks and synchronizers - Redisson Reference Guide (opens new window)

  • POM

    <!--redisson-->
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.13.4</version>
    </dependency>
    
    1
    2
    3
    4
    5
    6
  • config

    //单Redis节点模式
        @Bean
        public Redisson redisson() {
            Config config = new Config();
            config.useSingleServer().setAddress("redis://192.168.1.102:6379").setDatabase(0).setPassword("123456abc");
            return (Redisson) Redisson.create(config);
        }
    
    1
    2
    3
    4
    5
    6
    7
  • controller

    @Operation(summary = "扣减库存saleByRedisson,一次卖一个")
        @GetMapping(value = "/inventory/saleByRedisson")
        public String saleByRedisson() {
            return inventoryService.saleByRedisson();
        }
    
    1
    2
    3
    4
    5
  • service

     /**
         * V9.0  Redisson
         *
         * @return
         */
        public String saleByRedisson() {
            String retMessage = "";
            String key = "zzyyRedisLock";
            RLock redissonLock = redisson.getLock(key);
            redissonLock.lock();
            try {
                //1 查询库存信息
                String result = stringRedisTemplate.opsForValue().get("inventory001");
                //2 判断库存是否足够
                Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
                //3 扣减库存
                if (inventoryNumber > 0) {
                    stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                    retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
                    System.out.println(retMessage);
                } else {
                    retMessage = "商品卖完了,o(╥﹏╥)o";
                }
            } finally {
                redissonLock.unlock();
            }
            return retMessage + "\t" + "服务端口号:" + port;
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
  • test

    api测试

bug:

java-cde-v9.1

/**
     * V9.1  Redisson
     *
     * @return
     */
    public String saleByRedisson() {
        String retMessage = "";
        String key = "zzyyRedisLock";
        RLock redissonLock = redisson.getLock(key);
        redissonLock.lock();
        try {
            //1 查询库存信息
            String result = stringRedisTemplate.opsForValue().get("inventory001");
            //2 判断库存是否足够
            Integer inventoryNumber = result == null ? 0 : Integer.parseInt(result);
            //3 扣减库存
            if (inventoryNumber > 0) {
                stringRedisTemplate.opsForValue().set("inventory001", String.valueOf(--inventoryNumber));
                retMessage = "成功卖出一个商品,库存剩余: " + inventoryNumber;
                System.out.println(retMessage);
            } else {
                retMessage = "商品卖完了,o(╥﹏╥)o";
            }
        } finally {
            // 判断是自己的锁
            if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()) {
                redissonLock.unlock();
            }
        }
        return retMessage + "\t" + "服务端口号:" + port;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 3.Redisson 源码

加锁-可重入-续命-解锁

  • Redis 分布式锁过期了,但是业务逻辑还没处理完怎么办

  • 守护线程“续命”

    • 额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间。Redisson 里面就实现了这个方案,使用“看门狗”定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间;
  • 在获取锁成功后,给锁加一个 watchdog,watchdog会起一个定时任务,在锁没有被释放且快要过期的时候会续期

  • 上述源码分析1-通过redisson新建出来的锁key,默认是30秒

  • 上述源码分析2:RedissonLock.java,lock()---tryAcquire()---tryAcquireAsync()---

  • 上述源码分析3

    • 通过exists判断,如果锁不存在,则设置值和过期时间,加锁成功
    • 通过hexists判断,如果锁已存在,并且锁的是当前线程,则证明是重入锁,加锁成功
    • 如果锁已存在,但锁的不是当前线程,则证明有其他线程持有锁。返回当前锁的过期时间(代表了锁key的剩余生存时间),加锁失败

  • 上述源码分析4

    这里面初始化了一个定时器,dely 的时间是 internalLockLeaseTime/3。在 Redisson 中,internalLockLeaseTime 是 30s,也就是每隔 10s 续期一次,每次 30s。

    • watch dog自动延期机制:客户端A加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端A还持有锁key,那么就会不断的延长锁key的生存时间,默认每次续命又从30秒新开始

    • 自动续期lua脚本分析

# 4.多机案列

# 4.1 理论来源

  • 算法解决这个问题

  • 官网:

  • 总结:

    这个锁的算法实现了多redis实例的情况,相对于单redis节点来说,优点在于 防止了 单节点故障造成整个服务停止运行的情况且在多节点中锁的设计,及多节点同时崩溃等各种意外情况有自己独特的设计方法。

    Redisson 分布式锁支持 MultiLock 机制可以将多个锁合并为一个大锁,对一个大锁进行统一的申请加锁以及释放锁。

    最低保证分布式锁的有效性及安全性的要求如下:

    1. 1.互斥;任何时刻只能有一个client获取锁
    2. 2.释放死锁;即使锁定资源的服务崩溃或者分区,仍然能释放锁
    3. 3.容错性;只要多数redis节点(一半以上)在使用,client就可以获取和释放锁

    网上讲的基于故障转移实现的redis主从无法真正实现Redlock:

    因为redis在进行主从复制时是异步完成的,比如在clientA获取锁后,主redis复制数据到从redis过程中崩溃了,导致没有复制到从redis中,然后从redis选举出一个升级为主redis,造成新的主redis没有clientA 设置的锁,这是clientB尝试获取锁,并且能够成功获取锁,导致互斥失效;

# 4.2 代码参考

https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers

# 4.3 案列

docker启动3个redis

version: '3'
services:
  redis:
    image: registry.cn-hangzhou.aliyuncs.com/zhengqing/redis:7.0.5                    # 镜像'redis:7.0.5'
    container_name: redis82                                                             # 容器名为'redis'
#    restart: unless-stopped                                                                   # 指定容器退出后的重启策略为始终重启,但是不考虑在Docker守护进程启动时就已经停止了的容器
    command: redis-server /etc/redis/redis.conf --requirepass 123456clx --appendonly no # 启动redis服务并添加密码为:123456,默认不开启redis-aof方式持久化配置
#    command: redis-server --requirepass 123456 --appendonly yes # 启动redis服务并添加密码为:123456,并开启redis持久化配置
    environment:                        # 设置环境变量,相当于docker run命令中的-e
      TZ: Asia/Shanghai
      LANG: en_US.UTF-8
    volumes:                            # 数据卷挂载路径设置,将本机目录映射到容器目录
      - "./redis/data:/data"
      - "./redis/config/redis.conf:/etc/redis/redis.conf"  # `redis.conf`文件内容`http://download.redis.io/redis-stable/redis.conf`
    ports:                              # 映射端口
      - "6382:6379"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

启动

docker-compose -f docker-compose-redis.yml -p redis up -d
1

pom

<dependencies>
        <!--SpringBoot通用依赖模块-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
            <version>4.4.0</version>
        </dependency>
        <!--通用基础配置boottest/lombok/hutool-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.8</version>
        </dependency>


        <!--redisson-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>
    </dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

yaml


server:
  port: 9000

# springdoc-openapi项目配置
springdoc:
  swagger-ui:
    path: /swagger-ui.html
    tags-sorter: alpha
    operations-sorter: alpha
  api-docs:
    path: /v3/api-docs
  group-configs:
    - group: 'default'
      paths-to-match: '/**'
      packages-to-scan: org.clxmm
# knife4j的增强配置,不需要增强可以不配
knife4j:
  enable: true
  setting:
    language: zh_cn

spring:
  redis:
    database: 0
    password: 123456clx
    timeout: 300
    mode: single
    pool:
      conn-timeout: 3000
      so-timeout: 3000
      size: 10
    single:
      address1: 192.168.1.102:6380
      address2: 192.168.1.102:6381
      address3: 192.168.1.102:6382
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

代码

pro

@Data
public class RedisPoolProperties {

    private int maxIdle;

    private int minIdle;

    private int maxActive;

    private int maxWait;

    private int connTimeout;

    private int soTimeout;

    /**
     * 池大小
     */
    private  int size;

}

@Data
public class RedisSingleProperties {
    private  String address1;
    private  String address2;
    private  String address3;
}

@ConfigurationProperties(prefix = "spring.redis", ignoreUnknownFields = false)
@Data
public class RedisProperties {

    private int database;

    /**
     * 等待节点回复命令的时间。该时间从命令发送成功时开始计时
     */
    private int timeout;

    private String password;

    private String mode;

    /**
     * 池配置
     */
    private RedisPoolProperties pool;

    /**
     * 单机信息配置
     */
    private RedisSingleProperties single;


}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

config

@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class CacheConfiguration {

    @Autowired
    RedisProperties redisProperties;

    @Bean
    RedissonClient redissonClient1() {
        Config config = new Config();
        String node = redisProperties.getSingle().getAddress1();
        node = node.startsWith("redis://") ? node : "redis://" + node;
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(node)
                .setTimeout(redisProperties.getPool().getConnTimeout())
                .setConnectionPoolSize(redisProperties.getPool().getSize())
                .setConnectionMinimumIdleSize(redisProperties.getPool().getMinIdle());
        if (StringUtils.isNotBlank(redisProperties.getPassword())) {
            serverConfig.setPassword(redisProperties.getPassword());
        }
        return Redisson.create(config);
    }

    @Bean
    RedissonClient redissonClient2() {
        Config config = new Config();
        String node = redisProperties.getSingle().getAddress2();
        node = node.startsWith("redis://") ? node : "redis://" + node;
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(node)
                .setTimeout(redisProperties.getPool().getConnTimeout())
                .setConnectionPoolSize(redisProperties.getPool().getSize())
                .setConnectionMinimumIdleSize(redisProperties.getPool().getMinIdle());
        if (StringUtils.isNotBlank(redisProperties.getPassword())) {
            serverConfig.setPassword(redisProperties.getPassword());
        }
        return Redisson.create(config);
    }

    @Bean
    RedissonClient redissonClient3() {
        Config config = new Config();
        String node = redisProperties.getSingle().getAddress3();
        node = node.startsWith("redis://") ? node : "redis://" + node;
        SingleServerConfig serverConfig = config.useSingleServer()
                .setAddress(node)
                .setTimeout(redisProperties.getPool().getConnTimeout())
                .setConnectionPoolSize(redisProperties.getPool().getSize())
                .setConnectionMinimumIdleSize(redisProperties.getPool().getMinIdle());
        if (StringUtils.isNotBlank(redisProperties.getPassword())) {
            serverConfig.setPassword(redisProperties.getPassword());
        }
        return Redisson.create(config);
    }


    /**
     * 单机
     * @return
     */
    /*@Bean
    public Redisson redisson()
    {
        Config config = new Config();

        config.useSingleServer().setAddress("redis://192.168.111.147:6379").setDatabase(0);

        return (Redisson) Redisson.create(config);
    }*/

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

controller

@RestController
@Slf4j
public class RedLockController {

    public static final String CACHE_KEY_REDLOCK = "ATGUIGU_REDLOCK";

    @Autowired
    RedissonClient redissonClient1;

    @Autowired
    RedissonClient redissonClient2;

    @Autowired
    RedissonClient redissonClient3;

    boolean isLockBoolean;

    @GetMapping(value = "/multiLock")
    public String getMultiLock() throws InterruptedException
    {
        String uuid =  IdUtil.simpleUUID();
        String uuidValue = uuid+":"+Thread.currentThread().getId();

        RLock lock1 = redissonClient1.getLock(CACHE_KEY_REDLOCK);
        RLock lock2 = redissonClient2.getLock(CACHE_KEY_REDLOCK);
        RLock lock3 = redissonClient3.getLock(CACHE_KEY_REDLOCK);

        RedissonMultiLock redLock = new RedissonMultiLock(lock1, lock2, lock3);
        redLock.lock();
        try
        {
            System.out.println(uuidValue+"\t"+"---come in biz multiLock");
            try { TimeUnit.SECONDS.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(uuidValue+"\t"+"---task is over multiLock");
        } catch (Exception e) {
            e.printStackTrace();
            log.error("multiLock exception ",e);
        } finally {
            redLock.unlock();
            log.info("释放分布式锁成功key:{}", CACHE_KEY_REDLOCK);
        }

        return "multiLock task is over  "+uuidValue;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

接口测试:http://localhost:9090/multilock

命令:

  • ttl ATGUIGU_REDLOCK
  • HGETALL ATGUIGU_REDLOCK
  • shutdown

测试停止1台redis

  • docker stop redis-master-1

  • docker start redis-master-1

# 5.redis缓存过期淘汰策略

# 5.1 面试题

  • 生产上你们的redis内存设置多少?
  • 如何配置、修改redis的内存大小
  • 如果内存满了你怎么办
  • redis清理内存的方式?定期删除和惰性删除了解过吗
  • redis缓存淘汰策略有哪些?分别是什么?你用那个?
  • redis的LRU了解过吗?请手写LRU
  • lru和lfu算法的区别是什么

# 5.2 redis内存设置

redis默认内存多少?在哪里查看?如何设置修改?

  • 查看Redis最大占用内存

    打开redis配置文件,设置maxmemory参数,maxmemory是bytes字节类型,注意转换。

  • redis默认内存多少可以用?

    注意,在 64bit 系统下,maxmemory 设置为 0 表示不限制 Redis 内存使用

  • 一般生产上你如何配置?

    一般推荐Redis设置内存为最大物理内存的四分之三

  • 如何修改redis内存设置

    • 通过配置文件

      maxmemory 104865700
      
      1
    • 通过命令

      config set  maxmemory 104865700
      
      1
  • 什么命令查看redis内存使用情况?

    • info memory
    • config get maxmemory

真要打满了会怎么样?如果Redis内存使用超出了设置的最大值会怎样?

  • 改改配置,故意把最大值设为1个byte试试

结论

设置了maxmemory的选项,假如redis内存使用达到上限,

没有加上过期时间就会导致数据写满maxmemory为了避免类似情况,引出下一章内存淘汰策略

# 5.3 redis的数据是如何删除的

往redis里写的数据是怎么没了的?它如何删除的?

  • redis过期键的删除策略

    如果一个键是过期的,那它到了过期时间之后是不是马上就从内存中被被删除呢??

    如果回答yes,立即删除,你自己走还是面试官送你走?

    如果不是,那过期后到底什么时候被删除呢??是个什么操作?

  • 三种不同的删除策略

    • 立即删除

      Redis不可能时时刻刻遍历所有被设置了生存时间的key,来检测数据是否已经到达过期时间,然后对它进行删除。

      立即删除能保证内存中数据的最大新鲜度,因为它保证过期键值会在过期后马上被删除,其所占用的内存也会随之释放。但是立即删除对cpu是最不友好的。因为删除操作会占用cpu的时间,如果刚好碰上了cpu很忙的时候,比如正在做交集或排序等计算的时候,就会给cpu造成额外的压力,让CPU心累,时时需要删除,忙死。。。。。。。

      这会产生大量的性能消耗,同时也会影响数据的读取操作。

      总结:对CPU不友好,用处理器性能换取存储空间 (拿时间换空间)

    • 惰性删除

      数据到达过期时间,不做处理。等下次访问该数据时,如果未过期,返回数据 ;发现已过期,删除,返回不存在。

      惰性删除策略的缺点是,它对内存是最不友好的。

      如果一个键已经过期,而这个键又仍然保留在redis中,那么只要这个过期键不被删除,它所占用的内存就不会释放。

      在使用惰性删除策略时,如果数据库中有非常多的过期键,而这些过期键又恰好没有被访问到的话,那么它们也许永远也不会被删除(除非用户手动执行FLUSHDB),我们甚至可以将这种情况看作是一种内存泄漏–无用的垃圾数据占用了大量的内存,而服务器却不会自己去释放它们,这对于运行状态非常依赖于内存的Redis服务器来说,肯定不是一个好消息

      总结:对memory不友好,用存储空间换取处理器性能(拿空间换时间)

      开启憜性淘汰,lazyfree-lazy-eviction=yes

    • 定期删除

      • 定期抽样key,判断是否过期
      • 漏网之鱼

      定期删除策略是前两种策略的折中:定期删除策略每隔一段时间执行一次删除过期键操作并通过限制删除操作执行时长和频率来减少删除操作对CPU时间的影响。

      周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度

      特点1:CPU性能占用设置有峰值,检测频度可自定义设置

      特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理

      总结:周期性抽查存储空间 (随机抽查,重点抽查)

      redis默认每隔100ms检查是否有过期的key,有过期key则删除。注意:redis不是每隔100ms将所有的key检查一次而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis直接进去ICU)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。

      定期删除策略的难点是确定删除操作执行的时长和频率:如果删除操作执行得太频繁或者执行的时间太长,定期删除策略就会退化成立即删除策略,以至于将CPU时间过多地消耗在删除过期键上面。如果删除操作执行得太少,或者执行的时间太短,定期删除策略又会和惰性删除束略一样,出现浪费内存的情况。因此,如果采用定期删除策略的话,服务器必须根据情况,合理地设置删除操作的执行时长和执行频率。

  • 上述步骤都过堂了,还有漏洞吗?

    • 1 定期删除时,从来没有被抽查到
    • 2 惰性删除时,也从来没有被点中使用过

    上述两个步骤======> 大量过期的key堆积在内存中,导致redis内存空间紧张或者很快耗尽

    必须要有一个更好的兜底方案......

  • redis缓存淘汰策略登场。。。。。。O(∩_∩)O哈哈~

# 5.4 redis缓存淘汰策略

  • redis配置文件

    【MEMORY MANAGEMENT】

  • lru和lfu算法的区别是什么

    区别

    • LRU:最近最少使用页面置换算法,淘汰最长时间未被使用的页面,看页面最后一次被使用到发生调度的时间长短,首先淘汰最长时间未被使用的页面。

    • LFU:最近最不常用页面置换算法,淘汰一定时期内被访问次数最少的页,看一定时间段内页面被使用的频率,淘汰一定时期内被访问次数最少的页

    • 举个栗子

      某次时期Time为10分钟,如果每分钟进行一次调页,主存块为3,若所需页面走向为2 1 2 1 2 3 4

      假设到页面4时会发生缺页中断

      若按LRU算法,应换页面1(1页面最久未被使用),但按LFU算法应换页面3(十分钟内,页面3只使用了一次)

      可见LRU关键是看页面最后一次被使用到发生调度的时间长短,而LFU关键是看一定时间段内页面被使用的频率!

  • 有哪些(redis7版本)

    1. noeviction: 不会驱逐任何key,表示即使内存达到上限也不进行置换,所有能引起内存增加的命令都会返回error
    2. allkeys-lru: 对所有key使用LRU算法进行删除,优先删除掉最近最不经常使用的key,用以保存新数据
    3. volatile-lru: 对所有设置了过期时间的key使用LRU算法进行删除
    4. allkeys-random: 对所有key随机删除
    5. volatile-random: 对所有设置了过期时间的key随机删除
    6. volatile-ttl: 删除马上要过期的key
    7. allkeys-lfu: 对所有key使用LFU算法进行删除
    8. volatile-lfu: 对所有设置了过期时间的key使用LFU算法进行删除
  • 上面总结

    • 2 * 4 得8
    • 2个维度
      • 过期键中筛选
      • 所有键中筛选
    • 4个方面
      • LRU
      • LFU
      • random
      • ttl

你平时用哪一种

如何配置、修改

  • 直接用config命令

  • 直接redis.conf配置文件

redis缓存淘汰策略配置性能建议

  • 避免存储bigkey

  • 开启憜性淘汰,lazyfree-lazy-eviction=yes

编辑 (opens new window)
#redis
上次更新: 2024/11/25, 22:00:11
redis的分布式锁
18Redis源码

← redis的分布式锁 18Redis源码→

最近更新
01
vue3
02-08
02
vue3-1
01-24
03
vue3
01-18
更多文章>
Theme by Vdoing | Copyright © 2024-2025 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式