Redis7实战加面试题-高阶篇(Redlock算法和底层源码分析)
当前代码为8.0版接上一步
当前文档源码,接上一篇博客
Redis7实战加面试题-高阶篇(手写Redis分布式锁)
逐步深入,引入Redlock
自研一把分布式锁,面试中回答的主要考点
1.按照UC里面java.util.concurrent.locks.Lock接口规范编写
2.lock()加锁关键逻辑
加锁:加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间
自旋
续期
3.unlock解锁关键逻辑:将Key键删除。但也不能乱删,不能说客户端1的请求将客户端2的锁给删除掉,只能自己删除自己的锁
上面自研的redis锁对于一般中小公司,不是特别高并发场景足够用了,单机redis小业务也撑得住
Redis分布式锁-Redlock红锁算法Distributed locks with Redis
官网:https://redis.io/docs/manual/patterns/distributed-locks/
为什么学习这个?怎么产生的?
线程 1 首先获取锁成功,将键值对写入 redis 的 master 节点,在 redis 将该键值对同步到 slave
节点之前,master 发生了故障;redis 触发故障转移,其中一个 slave 升级为新的
master,此时新上位的master并不包含线程1写入的键值对,因此线程 2
尝试获取锁也可以成功拿到锁,此时相当于有两个线程获取到了锁,可能会导致各种预期之外的情况发生,例如最常见的脏数据。
我们加的是排它独占锁,同一时间只能有一个建redis锁成功并持有锁,严禁出现2个以上的请求线程拿到锁。危险的
Redlock算法设计理念
redis之父提出了Redlock算法解决这个问题
Redis也提供了Redlock算法,用来实现基于多个实例的分布式锁。锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。Redlock算法是实现高可靠分布式锁的一种有效解决方案,可以在实际开发中使用。
设计理念:
该方案也是基于(set 加锁、Lua 脚本解锁)进行改良的,所以redis之父antirez 只描述了差异的地方,大致方案如下。
假设我们有N个Redis主节点,例如 N = 5这些节点是完全独立的,我们不使用复制或任何其他隐式协调系统,为了取到锁客户端执行以下操作:
该方案为了解决数据不一致的问题,直接舍弃了异步复制只使用 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台
天上飞的理念(RedLock)必然有落地的实现(Redisson):
Redisson是java的redis客户端之一,提供了一些api方便操作redis。
redisson之官网:https://redisson.org/
redisson之Github:https://github.com/redisson/redisson/wiki/%E7%9B%AE%E5%BD%95
redisson之解决分布式锁:https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers
使用Redisson进行编码改造V9.0
你怎么知道该这样使用?
v9.0版本修改
POM
<!--redisson-->
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.4</version>
</dependency>
RedisConfig:
package com.atguigu.redislock.config;import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;/*** @auther zzyy* @create 2022-10-22 15:14*/
@Configuration
public class RedisConfig
{@Beanpublic RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory){RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(lettuceConnectionFactory);//设置key序列化方式stringredisTemplate.setKeySerializer(new StringRedisSerializer());//设置value的序列化方式jsonredisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.afterPropertiesSet();return redisTemplate;}//单Redis节点模式@Beanpublic Redisson redisson(){Config config = new Config();config.useSingleServer().setAddress("redis://192.168.111.175:6379").setDatabase(0).setPassword("111111");return (Redisson) Redisson.create(config);}
}
InventoryController
package com.atguigu.redislock.controller;import com.atguigu.redislock.service.InventoryService;
import com.atguigu.redislock.service.InventoryService2;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** @auther zzyy* @create 2022-10-22 15:23*/
@RestController
@Api(tags = "redis分布式锁测试")
public class InventoryController
{@Autowiredprivate InventoryService inventoryService;@ApiOperation("扣减库存,一次卖一个")@GetMapping(value = "/inventory/sale")public String sale(){return inventoryService.sale();}@ApiOperation("扣减库存saleByRedisson,一次卖一个")@GetMapping(value = "/inventory/saleByRedisson")public String saleByRedisson(){return inventoryService.saleByRedisson();}
}
从现在开始不再用我们自己手写的锁了
InventoryService
package com.atguigu.redislock.service;import cn.hutool.core.util.IdUtil;
import com.atguigu.redislock.mylock.DistributedLockFactory;
import com.atguigu.redislock.mylock.RedisDistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;/*** @auther zzyy* @create 2022-10-25 16:07*/
@Service
@Slf4j
public class InventoryService2
{@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;@Autowiredprivate DistributedLockFactory distributedLockFactory;@Autowiredprivate Redisson redisson;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;}
}
测试:单机OK
JMeter:bug
业务代码修改为V9.1版
package com.atguigu.redislock.service;import cn.hutool.core.util.IdUtil;
import com.atguigu.redislock.mylock.DistributedLockFactory;
import com.atguigu.redislock.mylock.RedisDistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;/*** @auther zzyy* @create 2022-10-25 16:07*/
@Service
@Slf4j
public class InventoryService
{@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Value("${server.port}")private String port;@Autowiredprivate DistributedLockFactory distributedLockFactory;@Autowiredprivate Redisson redisson;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;}
}
Redisson源码解析
加锁,可重入,续命,解锁
分析步骤:
1.Redis 分布式锁过期了,但是业务逻辑还没处理完怎么办:缓存续命
2.守护线程“续命”
额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间。Redisson 里面就实现了这个方案,使用“看门狗”定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间;
3.在获取锁成功后,给锁加一个watchdog, watchdog会起一个定时任务,在锁没有被释放且快要过期的时候会续期
4.上述源码分析1
通过redisson新建出来的锁key,默认是30秒
5.上述源码分析2
RedissonLock.java
lock()—tryAcquire()—tryAcquireAsync()—
6.上述源码分析3
流程解释:
通过exists判断,如果锁不存在,则设置值和过期时间,加锁成功
通过hexists判断,如果锁已存在,并且锁的是当前线程,则证明是重入锁,加锁成功
如果锁已存在,但锁的不是当前线程,则证明有其他线程持有锁。返回当前锁的过期时间(代表了锁key的剩余生存时间),加锁失败
7.上述源码分析4
这里面初始化了一个定时器,dely 的时间是 internalLockLeaseTime/3。在 Redisson 中,internalLockLeaseTime 是 30s,也就是每隔 10s 续期一次,每次 30s。
watch dog自动延期机制:
客户端A加锁成功,就会启动一个watch dog看门狗,他是一个后台线程,会每隔10秒检查一下,如果客户端A还持有锁key,那么就会不断的延长锁key的生存时间,默认每次续命又从30秒新开始
自动续期lua脚本分析
8.解锁
多机案例
理论参考来源
redis之父提出了Redlock算法解决这个问题
官网:
具体:
小总结:
这个锁的算法实现了多redis实例的情况,相对于单redis节点来说,优点在于 防止了 单节点故障造成整个服务停止运行的情况且在多节点中锁的设计,及多节点同时崩溃等各种意外情况有自己独特的设计方法。
Redisson 分布式锁支持 MultiLock 机制可以将多个锁合并为一个大锁,对一个大锁进行统一的申请加锁以及释放锁。
最低保证分布式锁的有效性及安全性的要求如下:
1.互斥;任何时刻只能有一个client获取锁
2.释放死锁;即使锁定资源的服务崩溃或者分区,仍然能释放锁
3.容错性;只要多数redis节点(一半以上)在使用,client就可以获取和释放锁
网上讲的基于故障转移实现的redis主从无法真正实现Redlock:
因为redis在进行主从复制时是异步完成的,比如在clientA获取锁后,主redis复制数据到从redis过程中崩溃了,导致没有复制到从redis中,然后从redis选举出一个升级为主redis,造成新的主redis没有clientA 设置的锁,这是clientB尝试获取锁,并且能够成功获取锁,导致互斥失效;
代码参考来源
https://github.com/redisson/redisson/wiki/8.-Distributed-locks-and-synchronizers
MultiLock多重锁:
案例:
1.docker走起3台redis的master机器,本次设置3台master各自独立无从属关系
docker run -p 6381:6379 --name redis-master-1 -d redis
docker run -p 6382:6379 --name redis-master-2 -d redis
docker run -p 6383:6379 --name redis-master-3 -d redis
执行成功:
进入上一步刚启动的redis容器实例:
docker exec -it redis-master-1 /bin/bash 或者 docker exec -it redis-master-1 redis-cli
docker exec -it redis-master-2 /bin/bash 或者 docker exec -it redis-master-2 redis-cli
docker exec -it redis-master-3 /bin/bash 或者 docker exec -it redis-master-3 redis-cli
建Module:redis _redlock
改POM:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.10.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.atguigu.redis.redlock</groupId><artifactId>redis_redlock</artifactId><version>0.0.1-SNAPSHOT</version><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.19.1</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version></dependency><!--swagger--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger2</artifactId><version>2.9.2</version></dependency><!--swagger-ui--><dependency><groupId>io.springfox</groupId><artifactId>springfox-swagger-ui</artifactId><version>2.9.2</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.4</version><scope>compile</scope></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.11</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId></exclude></excludes></configuration></plugin></plugins></build></project>
写YML
server.port=9090
spring.application.name=redlockspring.swagger2.enabled=truespring.redis.database=0
spring.redis.password=
spring.redis.timeout=3000
spring.redis.mode=singlespring.redis.pool.conn-timeout=3000
spring.redis.pool.so-timeout=3000
spring.redis.pool.size=10spring.redis.single.address1=192.168.111.185:6381
spring.redis.single.address2=192.168.111.185:6382
spring.redis.single.address3=192.168.111.185:6383
主启动:
package com.atguigu.redis.redlock;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class RedisRedlockApplication
{public static void main(String[] args){SpringApplication.run(RedisRedlockApplication.class, args);}}
业务类:
配置
CacheConfiguration
package com.atguigu.redis.redlock.config;import org.apache.commons.lang3.StringUtils;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class CacheConfiguration {@AutowiredRedisProperties redisProperties;@BeanRedissonClient 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);}@BeanRedissonClient 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);}@BeanRedissonClient 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*//*@Beanpublic Redisson redisson(){Config config = new Config();config.useSingleServer().setAddress("redis://192.168.111.147:6379").setDatabase(0);return (Redisson) Redisson.create(config);}*/}
RedisPoolProperties
package com.atguigu.redis.redlock.config;import lombok.Data;@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;}
RedisProperties
package com.atguigu.redis.redlock.config;import lombok.Data;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;@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;}
RedissingleProperties
package com.atguigu.redis.redlock.config;import lombok.Data;@Data
public class RedisSingleProperties {private String address1;private String address2;private String address3;
}
controller:
package com.atguigu.redis.redlock.controller;import cn.hutool.core.util.IdUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;@RestController
@Slf4j
public class RedLockController {public static final String CACHE_KEY_REDLOCK = "ATGUIGU_REDLOCK";@AutowiredRedissonClient redissonClient1;@AutowiredRedissonClient redissonClient2;@AutowiredRedissonClient 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;}}
测试:
http://localhost:9090/multilock
命令:
ttl ATGUIGU_REDLOCK
HGETALL ATGUIGU_REDLOCK
shutdown
docker start redis-master-1
docker exec -it redis-master-1 redis-cli
结论:
Redis的缓存过期淘汰策略
面试题:
生产上你们的redis内存设置多少?
如何配置、修改redis的内存大小
如果内存满了你怎么办
redis清理内存的方式?定期删除和惰性删除了解过吗
redis缓存淘汰策略有哪些?分别是什么?你用那个?
lru和lfu算法的区别是什么
Redis内存满了怎么办
1.redis默认内存多少?在哪里查看?如何设置修改?
查看Redis最大占用内存
redis默认内存多少可以用?
注意,在64bit系统下,maxmemory 设置为0表示不限制Redis 内存使用
—般生产上你如何配置? —般推荐Redis设置内存为最大物理内存的四分之三
如何修改redis内存设置
1.通过修改文件配置
2.通过修改文件配置
什么命令查看redis内存使用情况? info memory,config get maxmemory
真要打满了会怎么样?如果Redis内存使用超出了设置的最大值会怎样?
改改配置,故意把最大值设为1个byte试试
结论:
设置了maxmemory的选项,假如redis内存使用达到上限
没有加上过期时间就会导致数据写满maxmemory为了避免类似情况,引出下一章内存淘汰策略
往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缓存淘汰策略登场
redis缓存淘汰策略
Redis配置文件:
lru和Ifu算法的区别是什么:
区别
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个维度:1).过期键中筛选2).所有键中筛选
4个方面:LRU,LFU,random,ttl
8个选项:
你平时用哪种?
如何配置、修改:直接用config命令,直接redis.conf配置文件
redis缓存淘汰策略配置性能建议:避免存储bigkey,开启惰性淘汰,lazyfree-lazy-eviction=yes
下一篇
Redis7实战加面试题-高阶篇(Redis为什么快?高性能设计之epoll和IO多路复用深度解析)
相关文章:

Redis7实战加面试题-高阶篇(Redlock算法和底层源码分析)
当前代码为8.0版接上一步 当前文档源码,接上一篇博客 Redis7实战加面试题-高阶篇(手写Redis分布式锁) 逐步深入,引入Redlock 自研一把分布式锁,面试中回答的主要考点 1.按照UC里面java.util.concurrent.locks.Lock接口规范编写…...

保持Git历史提交整洁,解决冲突
比较常见的场景,在代码提交场景,自己的代码和master冲突了,直接拉取master 解决冲突,很方便快捷,但是这样就会将其他开发同学的commit 拉到我们的分支,团队的代码合入时,需要代码同学帮忙code r…...

CompletableFuture使用详解,多线程相关
CompletableFuture笔记 一. 创建异步任务二.异步回调处理三.多任务组合处理四.总结 原文: https://blog.csdn.net/zsx_xiaoxin/article/details/123898171 CompletableFuture是jdk8的新特性。CompletableFuture实现了CompletionStage接口和Future接口,前者是对后者…...

(3)NUC980 kenerl编译
解压 用到的配置文件位置: /NUC980-linux-4.4.y-master/arch/arm/configs/nuc980_defconfig 执行: 编译linux内核源码。了解其 配置文件在 arch/arm/configs/nuc980_defconfig (1) make nuc980_defconfig 载入配置文件 (2) make menuconfig --->Devi…...

华为OD机试真题 Java 实现【分奖金】【2022Q4 100分】
一、题目描述 公司老板做了一笔大生意,想要给每位员工分配一些奖金,想通过游戏的方式来决定每个人分多少钱。按照员工的工号顺序,每个人随机抽取一个数字。按照工号的顺序往后排列,遇到第一个数字比自己数字大的,那么,前面的员工就可以获得“距离 * 数字差值”的奖金。如…...

迅为国产化RK3588开发板在安防前后端应用解决方案
K3588是瑞芯微推出的一款高性能处理器,针对安防领域的应用具备强大的计算能力和图像处理能力。下面是关于RK3588的安防前后端应用解决方案的介绍: 前端摄像头端: 高清视频采集:利用RK3588处理器的高性能图像处理能力,…...

Windows 安装 GCC
文章目录 GCC 是什么?GCC 和 gcc 什么关系?Windows 安装 GCC选型下载安装配置环境变量验证 参考文献 GCC 是什么? GCC(GNU Compiler Collection)是一个开源的编译器套件,由 GNU 项目开发和维护。 GNU 编译…...

下载安装LabVIEW
下载安装LabVIEW 介绍下载安装流程下载安装 后续 介绍 LabVIEW 是 工程 师 用来 开发 自动 化 研究、 验证 和 生产 测试 系统 的 图形 化 编 程 环境。Labview作为图形化编程语言,图形控件拖拽式编程,显得更加直观形象,也很容易上手学习。 …...

从C语言到C++_14(vector的常用函数+相关选择题和OJ题)
目录 1. vector的常用函数 1.1 vector 的介绍 1.2 vector 的初始化 1.3 vector 的操作和遍历 1.4 vector 的容量和增删查改 2. vector 相关笔试题 3. vector 相关OJ题 136. 只出现一次的数字 - 力扣(LeetCode) 解析代码: 118. 杨辉…...

Java NIO-非阻塞I/O(二)
文章目录 1. SocketChannel2. ServerSocketChannel2. Channels类3. 异步通道(Java 7)4. Socket选项5. 就绪选择6. Selector类7. SelectionKey类 1. SocketChannel 通道将缓冲区的数据块移入或移出到个汇总给你I/O,如文件、Socket、数据报等。…...

PaaS平台iuap——数智底座支撑企业的全球化业务拓展
数智化转型是全球化企业非常关注的话题,数智化转型过程中suo 面临的问题与挑战也绝非一套简单的产品能够解决的,必须配合组织、人员、目标制度采用达成目标。iuap平台是整个企业数智化转型的底座,形象来说是我们的土壤,在这个土壤…...

RK3318 android12 HEVC-1080P 4K VP9等格式视频播放不了
同样视频在同样硬件在android10的固件刷机测试播放正常 在android12播放失败,在媒体中心点开视频直接闪退了 在android10 能播放4K视频, 我对比了ddr频率 cat /d/clk/clk_summary | grep ddr android10 clk_ddrmon 0 0 0 24000000 0 0 50000 pclk_ddr 3 3 0 61440000 0 0 5…...

gpt技术简介以及具体应用领域
GPT(Generative Pre-trained Transformer)是一种基于Transformer架构的生成式预训练模型。它是由OpenAI开发的一系列语言模型,其中最著名的是GPT-3。GPT模型通过在大规模文本数据上进行自监督预训练,学习了大量语言知识和语言模式…...

【java】leetcode 二叉树展开为链表
二叉树展开为链表 leetcode114 .二叉树展开为链表解题思路二叉树专题: leetcode114 .二叉树展开为链表 114 leetcode 链接。可以打开测试 给你二叉树的根结点 root ,请你将它展开为一个单链表: 展开后的单链表应该同样使用 TreeNode &#x…...

windows环境, nginx https配置
在 Windows 环境下配置 Nginx 的 HTTPS,需要以下步骤: 1. 安装 OpenSSL 首先需要安装 OpenSSL,可以从官网下载 Windows 版本的 OpenSSL,然后解压到某个目录下,比如 C:\OpenSSL-Win64。 2. 生成 SSL 证书和私钥 使用…...

git 命令
初始化git目录 mkdir git-test cd git-test git init 配置git用户 git config --global user.name "yyuu007" git config --global user.email "12699891yyuu007user.noreply.gitee.com" 克隆远程代码 -b 指定分支 git clone -b dev gitgitee.com:y…...

【高分论文密码】大尺度空间模拟预测与数字制图
详情点击链接:【高分论文密码】大尺度空间模拟预测与数字制图一,R语言空间数据及数据挖掘 1、R语言空间数据 1.1R语言基础与数据科学 1.2R空间矢量数据 1.3R栅格数据2、R语言空间数据挖掘关键技术二,R语言空间数据高级处理技…...

Word控件Aspose.Words教程:使用 C# 读取 SXC 和 FODS 文件
Aspose.Words是一种高级Word文档处理API,用于执行各种文档管理和操作任务。API支持生成,修改,转换,呈现和打印文档,而无需在跨平台应用程序中直接使用Microsoft Word。 Aspose支持流行文件格式处理,并允许…...

代码示范【FabEdge v0.8.0】配置 connector 公开端口
FabEdge项目简介: FabEdge是博云在2021年8月发起,基于Kubernetes 构建的专注于边缘计算场景的容器网络方案,支持 KubeEdge 、SuperEdge、OpenYurt 等主流边缘计算框架。旨在解决边缘计算场景下容器网络配置管理复杂、网络割裂互不通信、缺少…...

通过Python的PyPDF2库合并多个pdf文件
文章目录 前言一、PyPDF2库是什么?二、安装PyPDF2库三、查看PyPDF2库版本四、合并多个pdf文件1.引入库2.定义pdf路径3.获取所有要合并的PDF文件名4.创建一个新的PDF文件5.遍历所有PDF文件名6.打开PDF文件7.创建PDF阅读器对象8.遍历PDF中的每一页,并将它们…...

python基础 - python命名空间与作用域
命名空间是名称与对象之间的关系,可以将命名空间看做是字典,其中的键是名称,值是对象。 命名空间不共享名称。 在命名空间中的名称能将任何python对象作为值,在不同的命名空间中相同的名称可以与不同的对象相关联。但是…...

MapReduce实战案例(3)
案例三: MR实战之TOPN(自定义GroupingComparator) 项目准备 需求测试数据 有如下订单数据 订单id商品id成交金额Order_0000001Pdt_01222.8Order_0000001Pdt_0525.8Order_0000002Pdt_03522.8Order_0000002Pdt_04122.4Order_0000002Pdt_05722.4Order_0000003Pdt_01222.8 现在…...

Socket(三)
文章目录 1. 设置Socket选项2. TCP_NODELAY3. SO_LINGER4. SO_TIMEOUT5. SO_RCVBUF和SO_SNDBUF6. SO_KEEPALIVE7. OOBINLINE8. SO_REUSEADDR9. IP_TOS服务类型10. Socket异常 1. 设置Socket选项 Socket选项指定了Java Socket类所依赖的原生socket如何发送和接受数据࿰…...

【JVM】12. 垃圾回收相关概念
文章目录 12.1. System.gc()的理解12.2. 内存溢出与内存泄露内存溢出(OOM)内存泄漏(Memory Leak) 12.3. Stop The World12.4. 垃圾回收的并行与并发并发(Concurrent)并行(Parallel)并…...

Java 版 spring cloud 工程系统管理 工程项目管理系统源码 工程项目各模块及其功能点清单
工程项目各模块及其功能点清单 一、系统管理 1、数据字典:实现对数据字典标签的增删改查操作 2、编码管理:实现对系统编码的增删改查操作 3、用户管理:管理和查看用户角色 4、菜单管理:实现对系统菜单的增删改查操…...

【Linux系统基础快速入门详解】Linux系统命令行介绍、命令提示符知识详解: ~/#/@等符号
Linux系统的命令行界面是Linux系统的核心部分,也是最常用的部分。在命令行界面中,用户可以使用各种Linux系统命令进行文件操作、系统管理、网络管理等操作。下面介绍一些常见的Linux系统命令行知识,以及命令提示符中的一些符号的含义。 1. 命令行界面 在Linux系统中,命令…...

Python 面向对象编程笔记:中级面向对象
__super__() 在 Python 中,super 是一个内置函数,用于调用父类方法。该函数可以在子类中调用父类中被重写的方法,从而实现对父类方法的继承并且进行扩展。它能够动态地查找当前子类继承链中的下一个类,从而允许设计者更加灵活地…...

JVM学习笔记(上)
1、总体路线 2、程序计数器 Program Counter Register 程序计数器(寄存器) 作用:是记录下一条 jvm 指令的执行地址行号。 特点: 是线程私有的不会存在内存溢出 解释器会解释指令为机器码交给 cpu 执行,程序计数器会…...

反爬虫技术
预计更新 一、 爬虫技术概述 1.1 什么是爬虫技术 1.2 爬虫技术的应用领域 1.3 爬虫技术的工作原理 二、 网络协议和HTTP协议 2.1 网络协议概述 2.2 HTTP协议介绍 2.3 HTTP请求和响应 三、 Python基础 3.1 Python语言概述 3.2 Python的基本数据类型 3.3 Python的流程控制语句 …...

JAVA中.equals()与 ==的区别
1. “”是运算符,如果是基本数据类型,则比较存储的值;如果是引用数据类型,则比较所指向对象的地址值。 2..equals() equals是Object的方法,比较的是所指向的对象的地址值,一般情况下,重写之后比…...