synchronized的原理
synchronized
基于JVM的对象监视器和操作系统的互斥锁,每个对象都关联一个对象监视器,线程视图进入synchronized
代码块或方法时,会请求锁定当前对象的监视器;监视器锁又依赖于底层操作系统的 Mutex Lock(互斥锁)来实现,指令层面是通过
monitorenter
和monitorexit
实现。
Synchronized
通过对象内部的**监视器锁(monitor)**实现,监视器锁又依赖于底层操作系统的 Mutex Lock(互斥锁)来实现。而操作系统实现线程之间的切换需要从用户态转换到核心态,成本非常高。
- 对象监视器(Monitor) : 每个Java对象都关联一个对象监视器,当线程获取到对象监视器时,就获得了对该对象的监视权,即获得执行该对象上
synchronized
代码块或方法的权限。同一时间,只允许一个线程持有特定对象的监视器。 - 进入与退出监视器 :
- 当线程试图进入
synchronized
代码块或方法时,它会请求锁定当前对象对应的监视器。 - 如果监视器没有被其他线程占用,则当前线程将获取监视器并开始执行同步代码。
- 执行完毕后,线程会释放监视器,使得其他等待的线程有机会获取并执行同步代码。
- 当线程试图进入
- 指令层面 :
- 在字节码级别,Java虚拟机通过以下两条指令来实现同步:
monitorenter
:尝试获取对象监视器,如果成功则继续执行同步代码,否则线程会被阻塞直到监视器可用。monitorexit
:同步代码块或方法执行完毕后,释放对象监视器。
- 在字节码级别,Java虚拟机通过以下两条指令来实现同步:
- 可重入性 :
synchronized
支持可重入,已持有某个对象监视器的线程,再次请求该监视器仍能成功,从而避免死锁。
线程池的参数及工作流程
任务提交到线程池后,先判断当前当前线程数是否小于corePoolSize,是则创建线程来执行任务,否则将任务放入队列,如果队列满了,则判断当前线程数是否小于最大线程数,是则创建线程执行任务,否则调用拒绝策略。
七大参数:核心线程数、最大线程数、线程存活时间、单位、工作队列、线程工厂、拒绝策略(线程耗尽,队列满了)
拒绝策略
1、直接抛异常,默认,拒绝执行异常
2、调用者线程去执行
3、丢弃最老的任务
4、丢弃最新的任务
5、自定义
- AbortPolicy(默认策略) : 当线程池饱和且无法接受新任务时,直接抛出
RejectedExecutionException
异常。 - CallerRunsPolicy: 当线程池饱和时,不丢弃任务,而是由调用者线程(即提交任务的线程)自己来执行该任务。
- DiscardPolicy : 当线程池饱和时,直接丢弃新提交的任务,没有任何异常抛出。
- DiscardOldestPolicy : 当线程池饱和时,会抛弃队列中最旧的未处理任务(即将要被执行的任务),然后尝试重新提交当前任务。
- Customized Policy: 用户可以自定义拒绝策略,根据具体需求实现自己的拒绝逻辑,例如记录日志、发送通知等。
Spring的aop怎么实现
切面、通知、连接点、代理、织入
Spring AOP是通过代理模式来实现的。以下是Spring AOP实现的基本步骤和原理:
- 定义切面(Aspect) :
- 切面是关注点的模块化,它包含业务逻辑的各个部分。在Spring中,切面通常由一个或多个通知(Advice)、一个切入点表达式(Pointcut)和一个切点(Join point)组成。
- 通知(Advice) :
- 通知是在特定连接点(Join point)上执行的行为。常见的通知类型包括前置通知(Before advice)、后置通知(After returning advice)、异常后通知(After throwing advice)、最终通知(After (finally) advice)和环绕通知(Around advice)。
- 切入点表达式(Pointcut Expression) :
- 切入点表达式用于定义哪些连接点应该被通知匹配。这些表达式基于方法名、参数、返回类型等元素。
- 连接点(Join point) :
- 连接点是在应用程序执行过程中可插入切面的一个点。在Spring AOP中,连接点通常是方法调用。
- 代理(Proxy) :
- Spring AOP通过动态代理来实现切面的织入。有两种主要的代理机制:JDK动态代理和CGLIB代理。
- JDK动态代理:如果目标对象实现了至少一个接口,Spring AOP会使用JDK的
java.lang.reflect.Proxy
类来创建代理对象。 - CGLIB代理:如果目标对象没有实现任何接口,Spring AOP会使用CGLIB库来生成目标类的子类作为代理。
- JDK动态代理:如果目标对象实现了至少一个接口,Spring AOP会使用JDK的
- Spring AOP通过动态代理来实现切面的织入。有两种主要的代理机制:JDK动态代理和CGLIB代理。
- 织入(Weaving) :
- 织入是指将切面应用到目标对象的过程。在运行时,Spring AOP通过代理对象拦截对目标对象方法的调用,并在适当的位置应用通知。
以下是一个简单的Spring AOP实现示例:
java
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Executing: " + joinPoint.getSignature().getName());
}
}
在这个例子中:
LoggingAspect
类是一个切面,它包含一个前置通知。@Before
注解表示这是一个前置通知,将在方法执行前被调用。"execution(* com.example.service.*.*(..))"
是一个切入点表达式,它匹配com.example.service
包下所有类的所有方法。
要启用AOP并应用这个切面,你需要在Spring配置中添加如下内容:
xml
<aop:config>
<aop:aspect ref="loggingAspect">
<aop:before method="logBefore" pointcut="execution(* com.example.service.*.*(..))"/>
</aop:aspect>
</aop:config>
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>
或者在Java配置中:
java
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
@Bean
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
}
通过以上步骤,当你调用com.example.service
包下的某个方法时,Spring AOP会通过代理对象拦截这个调用,并在方法执行前调用logBefore
方法。这就是Spring AOP的基本实现原理。
动态代理的实现方式
JDK动态代理:基于Java反射机制实现,只能对实现了接口的类生成代理;
CGLIB动态代理:使用ASM库通过字节码操作技术在运行时生成目标类的子类。
动态代理在Java中有两种主要的实现方式:JDK动态代理和CGLIB动态代理。
- JDK动态代理 :
- JDK动态代理是基于Java反射机制和接口实现的。
- 工作原理:JDK动态代理通过
java.lang.reflect.Proxy
类和java.lang.reflect.InvocationHandler
接口来创建代理对象。当通过代理对象调用方法时,实际的调用会被转发到InvocationHandler
的invoke()
方法中,在这个方法里可以添加额外的逻辑,如权限检查、日志记录等。 - 特点与限制:
- 只能对实现了接口的类生成代理。
- 由于是基于接口的,所以不能代理没有实现接口的类。
- CGLIB动态代理 :
- CGLIB(Code Generation Library)是一个第三方库,它通过字节码操作技术(如ASM库)在运行时生成目标类的子类作为代理对象。
- 工作原理:CGLIB通过继承目标类并重写其方法来创建代理。在重写的方法中,可以插入额外的代码逻辑。
- 特点与限制:
- 不需要目标类实现接口,可以直接对类进行代理。
- 由于是基于继承的,所以不能代理被
final
修饰的类或方法。 - 对于final类和方法,以及private方法,CGLIB无法进行增强。
区别:
- 代理对象生成方式 :
- JDK动态代理基于接口和反射创建代理对象。
- CGLIB动态代理基于字节码操作和继承创建代理对象。
- 适用范围 :
- JDK动态代理适用于实现了接口的类。
- CGLIB动态代理适用于未实现接口的普通类。
- 性能影响 :
- JDK动态代理通常具有更好的性能,因为它是在Java虚拟机层面实现的。
- CGLIB动态代理由于涉及到字节码操作,可能会有轻微的性能损失。
- 灵活性 :
- JDK动态代理的灵活性受限于接口定义,只能代理接口中声明的方法。
- CGLIB动态代理相对更灵活,可以代理类中的所有非final方法。
在实际使用中,根据具体的需求和场景选择合适的动态代理实现方式。如果目标类已经实现了接口,且不需要代理final方法,可以选择JDK动态代理;如果目标类没有实现接口或者需要代理final方法,那么应该选择CGLIB动态代理。在Spring框架中,AOP模块默认优先使用JDK动态代理,当目标类没有实现接口时,会自动切换到CGLIB动态代理。
Kafka设计架构
Producer:消息生产者,就是向 kafka broker 发消息的客户端。
Consumer:消息消费者,向 kafka broker 取消息的客户端。
Topic:可理解为一个队列,一个 Topic 又分为一个或多个分区,
Consumer Group:这是 kafka 用来实现一个 topic 消息的广播(发给所有的 consumer)和单播(发给任意一个 consumer)的手段。一个 topic 可以有多个 Consumer Group。
Broker:一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。
Partition:为实现扩展性,一个 topic 可分布到多个 broker上,每个 partition 是一个有序的队列。partition 中的每条消息都会被分配一个有序的id(offset)。将消息发给 consumer,kafka 只保证按一个 partition 中的消息的顺序,不保证一个 topic 的整体(多个 partition 间)的顺序。
Offset:kafka 的存储文件都是按照 offset.kafka 来命名,用 offset 做名字的好处是方便查找。
Kafka保证消息有序性
分区:kafka将topic分为多个partition,每个partition内严格按照顺序排序,就是说同一partition,先发布的消息offset小于后发布的;
键控分区:生产者通过键控分区的方式决定消息发到哪个partition;
消费者消费顺序:同一消费者消费同一分区消息是按offset顺序消费的;
Kafka如何保证可靠性
持久化存储:将所有发布到其上的消息都持久化存储在磁盘上。
副本机制:每个topic下的每个分区都有多个副本。其中一个副本被选为主副本(Leader),负责读写请求;其他副本是追随者副本(Follower)。当主副本出现故障时,Kafka会自动从追随者副本中选举新的主副本继续服务,从而提供容错能力。
ISR集合:ISR集合包含了与主副本保持同步的所有追随者副本。只有当一个消息被成功写入ISR中的所有副本后,生产者才会收到确认。这样可以保证即使有节点故障,数据也不会丢失。
确认应答 :生产者设置acks参数,控制需要多少副本确认接受,才认为写入成功,
acks=all
意味着所有ISR中的副本都要确认接收,这是最高的可靠级别。事务模式:生产者可启用事务模式,确保一系列消息要么全部成功提交,要么全部回滚,避免部分消息丢失或重复。
消费者位移管理:消费者在消费消息后提交自己的消费位移(offset),以此记录已消费消息的位置。如果消费者在消费完消息并提交位移之前发生故障,那么在恢复后它可以从上次提交的位移处继续消费,从而避免重复消费或者漏消费。
重试和幂等性:生产者可通过配置实现重试逻辑,结合幂等性功能,在网络异常或其他问题导致消息投递失败时,能安全地重新尝试发送消息而不会导致重复。
Kafka如何实现高吞吐率
Kafka高性能
批处理:可批量发送消息,而不是逐一发送,减少网络I/O次数,提高了带宽利用率。
零拷贝:数据传输时采用操作系统层面的零拷贝技术,减少CPU将数据从内核空间拷贝到用户空间的开销。
磁盘顺序写:消息存储基于磁盘,以追加的方式写入文件,而非随机写入,磁头不需寻道,只需向同一方向移动。
高效索引结构:对消息采用稀疏索引结构,从而快速定位消息。
多线程与异步处理:生产者和消费者都支持异步收发消息。
分区与分布式架构:水平扩展,随着硬件增加而线性提升性能;
消息压缩:使用GZIP算法,减少网络传输数据量。
SpringCloud有哪些组件
Eureka、Ribbon、Hystrix、Zuul、Feign、
Spring Cloud是一套基于Spring Boot实现的微服务架构解决方案,它包含了一系列为构建分布式系统和服务治理而设计的组件和工具集。以下是一些核心组件及其功能:
- Eureka :
- 服务注册与发现:Eureka Server作为服务注册中心,各个微服务启动时向Eureka注册自身的服务信息,其他服务通过查询Eureka来发现和调用其他服务。
- Ribbon :.
- 客户端负载均衡器:在消费者(即服务调用方)中内置了负载均衡算法,能够透明地从服务注册中心获取到可用的服务实例列表,并根据配置策略选择一个或多个目标服务进行通信。
- Hystrix :
- 断路器:提供服务降级、熔断以及隔离机制,当依赖服务出现故障时,防止级联失败,确保系统的整体稳定性。
- Zuul :
- API网关:作为微服务架构中的边缘服务器,负责请求路由、过滤、安全控制等功能,可以实现统一的入口管理、服务聚合和请求转发。
- Feign :
- 声明式HTTP客户端:简化了服务间HTTP接口的调用,使得远程调用就像调用本地方法一样简单。
- Spring Cloud Config :
- 分布式配置中心:用于集中管理和动态推送应用的外部化配置,支持配置版本管理、环境隔离等特性。
- Spring Cloud Bus :
- 事件、消息总线:用于在集群环境中广播状态变更、刷新配置等事件,通常配合Config Server使用。
- Spring Cloud Stream :
- 消息驱动微服务框架:简化了消息中间件集成,如Kafka、RabbitMQ等。
- Spring Cloud Netflix Turbine / Spring Cloud Gateway(较新版本):
- 微服务监控聚合:Turbine收集Hystrix metrics并进行聚合,Gateway则是一个更现代的API网关替代方案,除了路由功能还提供了增强的安全性和性能监控。
- Spring Cloud Sleuth / Zipkin :
- 分布式追踪:Sleuth提供了一种方式来追踪微服务架构下的请求链路,Zipkin则是一个开源的分布式追踪系统,用于收集和展示这些链路数据。
随着Spring Cloud的演进,一些基于Netflix OSS的组件逐渐被替换或不再推荐使用,比如Netflix Eureka已被Spring Cloud自带的服务发现组件Spring Cloud Consul或Spring Cloud Zookeeper取代,而Spring Cloud LoadBalancer替代了Ribbon的部分功能。同时,Spring Cloud也引入了新的组件如Spring Cloud Gateway、Spring Cloud Kubernetes等以适应云原生时代的需求。
隔离级别
未提交读:最低隔离级别,事务未提交前,就可被其他事务读取。
提交读:一个事务提交后才能被其他事务读取到。
可重复读:默认级别,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据。
序列化:代价最高最可靠的隔离级别。
脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录A。
不可重复读 :是指在一个事务内,多次读同一数据。
幻读 :指同一个事务内多次查询返回的结果集不一样。比如第一次查询有 n 条记录,第二次查询却有 n+1 条记录,好像产生了幻觉。
不可重复读和幻读的区别:不可重复读的重点是修改;两次读取的值不一样。幻读的重点在于增删;两次读出来的记录数不一样。从控制角度来看,不可重复读只需要锁住满足条件的记录,幻读要锁住满足条件及其相近的记录。
EXPLAIN
EXPLAIN语句用于分析SQL执行计划
type
:ALL(全表扫描)、index(全索引扫描)、range(索引范围扫描)、ref(非唯一性索引扫描,通过某个列的值引用)
possible_keys
:可能用到的索引列表。
key
:实际使用的索引,如果没有使用索引则显示NULL。
key_len
:使用的索引长度。
filtered
:在经过此表之后,按照条件过滤后的行的比例。
rows
:根据统计信息估算出需要读取的行数。rows<1000,是在可接受的范围内的其他字段还包括
extra
,包含一些额外的信息,如Using index
(覆盖索引),Using filesort
(外部排序),Using temporary
(使用临时表)等。
filesort
:如果一个查询包含ORDER BY
子句且没有合适的索引来满足排序需求,或者虽然有索引但执行计划决定了使用全表扫描而非索引访问方法,MySQL会将符合条件的记录读取到内存中的临时空间(如sort buffer)或者外部磁盘文件上进行排序。
PgSQL与MySQL有什么区别
数据类型:
- PgSQL支持丰富的数据类型,包括JSONB、数组、范围类型、窗口函数、全文搜索、地理空间索引等功能。
- MySQL的数据类型相对简单,但同样支持JSON字段类型、地理位置索引等。
事务中DDL语句:
PgSQL在事务处理方面更加严谨
缓存雪崩、击穿、穿透
缓存雪崩
缓存同一时间大面积失效
- 过期时间设置随机
- 热点数据均匀分布在不同的缓存数据库中
- 热点数据永不过期
- hystrix限流&降级
缓存穿透
大量请求的key不存在
- 参数校验
- 缓存无效key:如果缓存和数据库都查不到某个key 的数据就写一个到redis 中去并设置过期时间。
- 布隆过滤器
缓存击穿
某key失效时大量该Key请求过来
- 热点数据永不失效。
- 增加互斥锁。
如何保证双写一致性
1、读写串行化(吞吐量太低)
2、先删缓存再更新数据库
- 删缓存和更新数据库的间隙,别的线程查库回写
- 延时双删解决(先删缓存,再更新数据库,一秒后再删缓存;异步二删)
- 如果二删失败,也会出现不一致
3、先更新数据库、再删缓存
- 更新数据库前有线程查了旧值,删缓存后将旧值回写(读远快于写,所以基本不会出现该问题)
- 非要解决的话就使用异步延时双删
4、如何解决删缓存失败的情况
- 将要删的key放入MQ,让缓存自己消费消息删key,失败则重试直到成功
- 使用MySQL的canal订阅binlog日志,提取需要删除的key,发送给MQ,缓存自己消费消息删key
Redisson分布式锁原理
加锁机制:若客户端面对的是redis cluster集群,会根据hash节点选择一台机器,然后发送一段lua脚本,保证原子性,脚本中KEYS[1]表示加锁的key,ARGV[1]表示锁生存时间,ARGV[2]表示加锁客户端ID。再来一个客户端想要加锁时,同样发送一段lua脚本,先判断锁key是否已存在,存在的话再判断ARGV[2]是否包含该客户端ID,不包含的话客户端会收到该锁key的剩余生存时间,然后进入while循环,不断尝试加锁。
可重入性支持: Redisson支持可重入的分布式锁,Redisson中包含一个计数器,每次成功获取锁时递增计数器,每次释放锁时递减计数器,当计数器降为0时,删除锁。
锁的自动续期: 为了避免由于网络延迟、GC暂停等原因导致锁在使用过程中过期,Redisson提供了锁的自动续期功能。当客户端持有锁时,Redisson会在后台定期为锁续期,确保锁在使用期间不会因为超时而被其他客户端获取。
RedLock算法: Redisson还支持RedLock算法,在RedLock算法中,Redisson会在多个独立的Redis实例上尝试获取锁,只有在大多数实例上成功获取锁时才认为获取锁成功。这种机制提高了分布式锁的可用性和容错性。
IO多路复用
IO多路复用是一种高效的I/O处理技术,其原理主要是通过一种机制让单个线程能够同时监控多个文件描述符(FD,File Descriptor)的I/O状态,从而实现高效、并发的网络通信。
poll和epoll
poll和epoll都是Linux系统中的I/O多路复用技术,用于监控多个文件描述符(通常是网络套接字)的事件状态,如读就绪、写就绪等。
- poll :
poll
系统调用允许程序同时监控多个文件描述符的状态变化。- 在使用
poll
时,需要先创建一个包含待监控文件描述符的数组,并为每个描述符指定感兴趣的事件类型。然后调用poll
函数,它会阻塞直到有至少一个描述符的事件发生或超时。 - 当
poll
返回后,需要遍历整个数组来检查哪些描述符的事件已经发生。
- epoll :
epoll
是Linux内核在2.6版本中引入的一种更高效的I/O多路复用机制。- 使用
epoll
时,首先创建一个epoll
实例,然后通过epoll_ctl
函数将待监控的文件描述符添加到这个实例中,并指定感兴趣的事件类型。 - 调用
epoll_wait
函数会阻塞直到有至少一个描述符的事件发生、超时或者被显式唤醒。 - 当
epoll_wait
返回后,它会提供一个事件列表,这个列表只包含了那些真正发生了事件的文件描述符,因此无需像poll
那样遍历整个描述符集合。
关于epoll是同步还是异步的问题:
- 从操作系统的角度来看,epoll仍然是一个同步I/O模型,因为它在调用
epoll_wait
时会阻塞等待事件发生。 - 然而在应用程序的设计中,可以结合非阻塞I/O和epoll来实现类似异步I/O的效果。在这种情况下,当
epoll_wait
返回后,应用程序可以立即处理已就绪的文件描述符,而不需要等待I/O操作完成。同时,未就绪的描述符可以继续被epoll监控,等待下次事件发生。 - 这种基于epoll的编程模型通常被称为Reactor模式或Proactor模式,虽然它们不是真正的异步I/O,但在很多场景下能够提供类似的高性能和高并发能力。
总结来说,epoll本身是一个同步I/O模型,但可以通过与非阻塞I/O的结合,在应用程序层面实现类似于异步I/O的处理方式。
poll 和 epoll 是Linux系统中的两种I/O多路复用技术,允许单进程同时监控多个文件描述符(如网络套接字)的读写事件状态,从而在大量并发连接中有效地管理I/O操作。以下是它们各自的工作流程:
poll
先创建一个包含所有待监控文件描述符的数组,并为每个描述符指明感兴趣的事件类型;
调用poll后,会阻塞,直到至少一个描述符发生事件或超时;
poll返回后,遍历整个数组来查看哪些描述符有事件发生。
-
初始化 : 创建一个
pollfd
结构体数组,每个元素表示要监控的一个文件描述符及其关注的事件类型(可读、可写等)。 -
调用poll函数 : 调用
poll()
函数,传入这个结构体数组和等待事件发生的超时时间。例如:cstruct pollfd fds[FD_SETSIZE]; int nfds = ...; // 数组中有效的文件描述符数量 int timeout = ...; // 超时时间(毫秒) int ready_fds = poll(fds, nfds, timeout);
-
内核处理:
- poll函数将所有待监控的文件描述符及事件信息复制到内核空间。
- 内核遍历这些文件描述符,检查是否有已就绪的事件发生。
- 如果有文件描述符对应的事件已经就绪或超时,则返回并更新
pollfd
结构体数组中对应元素的revents
字段。
-
轮询结果:
poll()
返回值为准备就绪的文件描述符数量,如果超时则返回0,若发生错误则返回负数。- 应用程序根据返回值和
pollfd
数组中的revents
字段来判断哪些文件描述符上有事件发生,并进行相应的处理。
epoll
先创建epoll实例,再通过epoll_ctl方法传入待监控文件描述符;
调用epoll_wait,阻塞直到至少有一个描述符的事件发生、超时或显式唤醒;
epoll_wait返回后,提供了一个事件列表,该列表只包含发生了事件的描述符。
-
创建epoll实例 : 首先调用
epoll_create()
或者epoll_create1()
创建一个epoll实例,返回一个epoll句柄。 -
添加/修改监控事件 : 对于需要监控的文件描述符,调用
epoll_ctl()
函数将其添加到epoll实例中,并指定感兴趣的事件类型(EPOLLIN、EPOLLOUT等):cint epoll_fd = epoll_create1(0); // 创建epoll实例 struct epoll_event event; event.events = EPOLLIN | EPOLLET; // 设置事件类型 event.data.fd = socket_fd; // 关联的文件描述符 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event); // 添加到epoll实例
-
等待事件 : 调用
epoll_wait()
函数,传入epoll句柄以及用于存放事件的缓冲区和超时时间,等待事件发生:cstruct epoll_event events[MAX_EVENTS]; int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, timeout);
-
内核处理:
- epoll使用红黑树数据结构存储被监控的文件描述符和事件。
- 当某个文件描述符上的事件发生时,内核不会像poll那样每次都遍历全部描述符,而是只唤醒等待该epoll实例上事件的进程,并将就绪的描述符和事件放入用户态的缓冲区。
-
处理就绪事件:
epoll_wait()
返回值是准备就绪的事件数量。- 应用程序遍历返回的
events
数组,根据其中的data.fd
找到就绪的文件描述符,并依据events[i].events
字段确定发生了何种类型的事件,然后进行相应处理。
epoll相比poll的一大改进在于其高效的事件通知机制,特别是在大量并发连接的情况下,避免了不必要的线性扫描,极大地提升了性能。
介绍Redis淘汰策略
Redis淘汰策略(LRU最近最少使用,关键是看数据最后一次被使用到发生替换的时间长短,时间越长,数据就会被删除;LFU是淘汰一段时间内,使用次数最少的页面。)
- 内存满时,再执行写入,直接报错
- 从已设TTL的键中挑选LRU的键进行删除
- 所有键中挑选LRU的键进行删除
- 从已设TTL的键中挑选LFU的键进行删除
- 所有键中挑选LFU的键进行删除
- 随机删除一个设置了TTL的键
- 删除生存时间(TTL)最小的键
Redis淘汰策略是指在内存使用达到最大限制(由maxmemory
配置决定)时,为保证服务的持续运行而采取的一种删除数据的方法。当Redis数据库的内存占用超过预设的最大值时,需要通过选择合适的淘汰策略来移除部分键值对以释放内存空间。
以下是Redis支持的多种淘汰策略:
- noeviction (默认)
- 当内存满时,不再执行任何写入操作(除了某些特殊命令如DEL),所有可能导致内存增加的操作都会返回错误。
- volatile-lru
- 从已设置过期时间(TTL)的所有键中挑选最近最少使用的(LRU)键进行删除。
- allkeys-lru
- 在所有的键中,不论是否设置了过期时间,挑选最近最少使用的键进行删除。
- volatile-lfu
- 从设置了过期时间的所有键中,挑选最近最不常使用的(LFU,Least Frequently Used)键进行删除。
- allkeys-lfu
- 在所有键中,不论是否设置了过期时间,挑选最近最不常使用的键进行删除。
- volatile-random
- 随机删除一个设置了过期时间的键。
- allkeys-random
- 随机删除任意一个键,无论其是否有过期时间。
- volatile-ttl
- 删除生存时间(TTL)最小的键,即即将过期的键。
每种策略都有其适用场景和优缺点,根据应用需求的不同,可以选择适合的淘汰策略以优化缓存命中率、减少数据丢失或平衡系统负载等。在实际应用中,通常建议为Redis实例设置合理的maxmemory
限制,并结合业务特点选择合适的淘汰策略。
The end.