当前代码为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
{
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory)
{
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式json
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
//单Redis节点模式
@Bean
public 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
{
@Autowired
private 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
{
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String port;
@Autowired
private DistributedLockFactory distributedLockFactory;
@Autowired
private 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
{
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Value("${server.port}")
private String port;
@Autowired
private DistributedLockFactory distributedLockFactory;
@Autowired
private 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=redlock
spring.swagger2.enabled=true
spring.redis.database=0
spring.redis.password=
spring.redis.timeout=3000
spring.redis.mode=single
spring.redis.pool.conn-timeout=3000
spring.redis.pool.so-timeout=3000
spring.redis.pool.size=10
spring.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 {
@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);
}*/
}
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";
@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;
}
}
测试:
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多路复用深度解析)