余声-个人博客


  • 首页

  • 分类

  • 归档

  • 标签

学习笔记

发表于 2024-11-15 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

springcloud服务中使用mq做消息的通知,相对于fegin有什么好处

在 Spring Cloud 服务中使用 消息队列(如 RabbitMQ、Kafka 等)来处理消息通知,相对于 Feign 进行同步调用,具有多个优势,特别是在分布式系统中。这些好处主要体现在 解耦性、可靠性、性能和可扩展性 等方面。下面详细列出它们的优缺点对比:

1. 解耦性

  • 消息队列:使用消息队列时,生产者(发送方)和消费者(接收方)是松耦合的,即使消费者不可用,消息队列仍然可以存储消息,等待消费者处理。这种异步的方式减少了服务之间的直接依赖,增强了系统的解耦性。
  • Feign:Feign 是一种同步调用方式,服务 A 直接调用服务 B,服务之间是强耦合的。如果服务 B 不可用,服务 A 会失败,依赖关系比较紧密。

优点:

  • 消息队列:更容易扩展服务,减少服务间的直接依赖。
  • Feign:服务间的直接调用,导致服务耦合性较强,依赖性高。

2. 可靠性

  • 消息队列:消息队列一般会提供消息持久化、重试机制、死信队列等功能,保证消息在系统出现故障时不会丢失,能够在消费者恢复后重新处理。即使服务 A 出现故障,消息依然可以在队列中等待消费。
  • Feign:Feign 调用是同步的,如果服务 B 出现故障,Feign 请求会直接失败,且一般不具备自动重试和消息持久化功能。

优点:

  • 消息队列:提供消息持久化、重试机制,能确保消息不丢失。
  • Feign:服务失败时请求会直接失败,除非使用像 Ribbon 或 Hystrix 这样的断路器进行处理。

3. 异步与性能

  • 消息队列:消息队列是异步处理的,即生产者发送完消息后,不需要等待消费者处理结果。生产者可以继续执行其他任务,消息队列通过缓冲提高系统吞吐量。当系统负载较高时,消费者可以批量消费消息,提高处理效率。
  • Feign:Feign 是同步的,发送请求后,调用方必须等待服务返回结果,可能会导致性能瓶颈,特别是当多个服务调用彼此依赖时,响应时间可能会增加。

优点:

  • 消息队列:通过异步处理提高系统吞吐量和性能,适合处理高并发、高负载的场景。
  • Feign:同步请求,可能会成为性能瓶颈,尤其在服务间相互调用较多时。

4. 可伸缩性

  • 消息队列:消息队列可以在消费者端进行水平扩展。例如,消费者可以横向扩展多个实例来处理消息,从而提升系统的处理能力。消息队列会自动将消息分发给空闲的消费者实例,提高系统的可伸缩性。
  • Feign:Feign 本身并不具备内建的负载均衡功能,虽然可以使用 Spring Cloud 的负载均衡组件(如 Ribbon),但它依赖于同步请求,因此在高并发场景下扩展性较差。

优点:

  • 消息队列:通过消费端的水平扩展,提高系统的可伸缩性。
  • Feign:虽然有负载均衡,但扩展性主要依赖于同步调用方式,可能会受到性能瓶颈的限制。

5. 流量控制与限流

  • 消息队列:消息队列通常支持流量控制。比如,消费者的处理速度低于生产者的发送速度时,消息队列可以进行流量调节,避免系统被大量消息打垮。此外,死信队列、延时队列等功能可以帮助处理异常消息,避免堆积。
  • Feign:Feign 是同步请求,不适合处理流量控制。如果请求量激增,可能会导致服务出现超时或者崩溃。

优点:

  • 消息队列:可以通过流量控制机制、死信队列、延时队列等功能保证系统稳定性。
  • Feign:不适合直接进行流量控制,可能导致系统超载。

6. 消息顺序

  • 消息队列:许多消息队列系统(如 Kafka 和 RabbitMQ)都支持消息的顺序保证,尤其在处理同一个主题或队列中的消息时,可以确保消息按顺序被消费。对于需要保证顺序消费的场景,消息队列是非常适合的。
  • Feign:Feign 的调用是同步的,但它不保证请求的顺序和消费顺序。多个服务之间的调用是按顺序发生的,但并不适用于保证消息顺序。

优点:

  • 消息队列:可以确保消息顺序性,适合需要严格顺序的场景。
  • Feign:没有内建的顺序保证机制。

7. 场景适配

  • 消息队列:适用于以下场景:

事件驱动的应用,异步通知、推送;
高并发、高吞吐量的系统;
系统解耦,避免微服务之间的直接调用;
消息延迟处理或异步处理的场景。

  • Feign:适用于以下场景:

服务间的同步调用,需要立即得到返回值;
处理简单的请求-响应模式;
服务间的低延迟、低耦合调用。

8. 总结

特性 消息队列 Feign
耦合性 松耦合,生产者和消费者分离 强耦合,服务间直接调用
可靠性 提供消息持久化、重试机制 依赖于服务的可用性,失败时没有自动恢复
性能 异步,适合高并发、高吞吐量的场景 同步,可能成为性能瓶颈
可伸缩性 通过水平扩展消费者来提高可伸缩性 依赖于负载均衡,扩展有限
流量控制 可以控制流量,避免系统过载 没有流量控制机制
消息顺序 可以保证消息顺序 不保证消息顺序
场景适配 事件驱动、异步通知、大量消息处理 服务间同步调用、低延迟处理

总结

使用 消息队列(如 RabbitMQ、Kafka)更适合于 高并发、异步、解耦、事件驱动 的场景,能够提高系统的可靠性、性能和扩展性。

使用 Feign 更适合 同步调用、简单的请求-响应模式、低延迟 的服务间通信。

线程池问题

发表于 2025-05-10 | 更新于 2025-09-14 | 分类于 分布式
字数统计 | 阅读时长

一、线程池拒绝策略选择指南

在实际开发中,线程池拒绝策略的选择需要根据具体的业务场景和系统需求来决定。Java线程池提供了四种内置拒绝策略,每种策略适用于不同的情况。

一、四种内置拒绝策略

  1. AbortPolicy(默认策略)
    行为:直接抛出RejectedExecutionException异常

适用场景:

需要明确知道任务被拒绝的情况

关键业务系统,不能容忍任务静默丢失

开发测试环境(便于发现问题)

示例:

java
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.AbortPolicy() // 显式指定
);
2. CallerRunsPolicy
行为:将任务回退给调用者线程执行

适用场景:

适合能承受一定延迟的业务

需要保证每个任务都能被执行

不希望任务丢失但可以接受降级

特点:

会降低新任务提交速度,起到负反馈调节作用

调用者线程可能被阻塞

  1. DiscardPolicy
    行为:静默丢弃被拒绝的任务,不做任何处理

适用场景:

非核心业务

允许丢失部分任务的场景

监控日志不重要的任务

风险:

任务丢失无感知,可能造成数据不一致

  1. DiscardOldestPolicy
    行为:丢弃队列中最老的任务,然后尝试重新提交当前任务

适用场景:

允许丢弃旧任务的新业务场景

实时性要求高的任务优先处理

任务本身具有时效性(旧任务价值低)

注意事项:

可能丢失重要但处理慢的任务

不适合任务之间有依赖关系的场景

二、选择策略的决策流程

  • 是否绝对不能丢失任务
    • 是:使用CallerRunsPolicy
    • 否:是否需要立即知道被拒绝
      • 是:使用AbortPolicy
      • 否:新任务比旧任务更重要
        • 是:使用DiscardOldestPolicy
        • 否:使用DiscardPolicy

三、实际业务场景推荐

  1. 支付/交易核心系统
    推荐策略:CallerRunsPolicy
    理由:保证每个支付请求都能被处理,宁可降级也不丢失

  2. 日志记录/数据收集
    推荐策略:DiscardPolicy
    理由:日志可容忍部分丢失,避免影响主业务流程

  3. 实时数据处理
    推荐策略:DiscardOldestPolicy
    理由:新数据比旧数据更有价值

  4. 管理系统后台任务
    推荐策略:AbortPolicy
    理由:需要及时发现系统过载情况

四、自定义拒绝策略
当内置策略不满足需求时,可以实现RejectedExecutionHandler接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CustomRejectionPolicy implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 1. 记录日志
log.warn("Task rejected: {}", r.toString());

// 2. 持久化任务到数据库/文件
persistTask(r);

// 3. 尝试重新提交
if (!executor.isShutdown()) {
executor.submit(r);
}
}
}

// 使用示例
executor.setRejectedExecutionHandler(new CustomRejectionPolicy());

五、配置建议
监控报警:无论选择哪种策略,都应监控拒绝情况

1
// 通过ThreadPoolExecutor的getRejectedExecutionCount()获取拒绝数

参数调优:合理设置队列容量和线程数比选择拒绝策略更重要

组合使用:不同业务使用不同线程池,配置不同策略

文档记录:在代码中明确注释选择该策略的原因

六、最佳实践总结
关键业务:优先保证任务执行(CallerRunsPolicy)

可丢弃任务:选择静默丢弃(DiscardPolicy)

需要感知:抛出异常(AbortPolicy)

时效敏感:丢弃旧任务(DiscardOldestPolicy)

特殊需求:自定义策略(如持久化后重试)

ThreadLocal的实现原理

发表于 2024-10-10 | 更新于 2025-09-14 | 分类于 笔记
字数统计 | 阅读时长

ThreadLocal是Java中用于解决多线程环境下数据隔离问题的一个类。它提供了一种方式,使得每个线程都可以拥有其独立的变量副本,这样每个线程都可以独立地改变自己的副本,而不会影响其他线程的副本。这种机制在多线程编程中非常有用,特别是当需要在多个线程之间共享某些数据,但又不希望这些数据因为并发访问而引发问题时。

ThreadLocal的实现原理

ThreadLocal的实现主要依赖于一个内部类ThreadLocalMap。这个Map以ThreadLocal对象为键,以线程独有的变量为值。每个线程都维护着一个ThreadLocalMap的引用,这样线程就可以通过ThreadLocal对象作为键,在其自己的ThreadLocalMap中查找或设置值。

  1. ThreadLocalMap的结构:

    • ThreadLocalMap是一个哈希表,它的键(Key)是ThreadLocal对象本身的一个弱引用(WeakReference),而值(Value)则是线程独有的变量。
    • 使用弱引用作为键的好处是,当ThreadLocal对象被垃圾回收时,不会因为ThreadLocalMap中的引用而阻止其被回收。但这也带来了一个问题,即如果ThreadLocal对象被回收了,而ThreadLocalMap中的Entry还存在(且Value还未被回收),那么就会出现键为null的情况,这需要在ThreadLocalMap的操作中特别处理。
  2. ThreadLocal的方法:

    • initialValue():返回此线程局部变量的初始值。
    • get():返回此线程局部变量的当前线程副本中的值。如果这是线程第一次调用该方法,则通过调用initialValue()方法创建并初始化此副本。
    • set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
    • remove():移除此线程局部变量的值。这是一个清理操作,有助于避免内存泄漏,特别是在使用线程池时。
  3. ThreadLocalMap的扩容和清理:

    • ThreadLocalMap的扩容机制与HashMap类似,当元素数量超过阈值时,会进行扩容。
    • 由于ThreadLocalMap的键是弱引用,当没有强引用指向ThreadLocal对象时,ThreadLocal对象可以被垃圾回收。但是,如果ThreadLocal对象被回收了,而对应的Value对象还有强引用存在(即还没有被线程显式地调用remove()方法移除),那么这些Value对象就变成了“孤儿对象”,可能会导致内存泄漏。因此,在使用ThreadLocal时,建议显式地调用remove()方法来清理不再需要的线程局部变量。

使用场景

ThreadLocal的典型使用场景包括:

  • 在多线程环境中,每个线程需要维护一个独立的、不与其他线程共享的状态或数据。
  • 在处理用户请求时,将用户相关的信息(如用户ID、会话信息等)保存在ThreadLocal中,以便在同一个请求处理流程中方便地访问这些信息,而不需要通过参数传递。

内存泄漏与内存溢出的区别

  • 内存泄漏:指的是程序中分配的内存在不再需要时没有被正确释放或回收的情况。随着时间的推移,可用内存逐渐减少,最终可能导致程序性能下降或崩溃。
  • 内存溢出(OOM, Out of Memory):当程序运行时,如果请求的内存超出了可用内存的限制,就会抛出内存溢出异常。内存泄漏如果不及时解决,最终可能导致内存溢出。

ThreadLocal内存泄漏问题

ThreadLocal是一个用于创建线程本地变量的类。每个线程都拥有自己独立的、初始化为null的变量副本,这些变量对其他线程是不可见的。然而,ThreadLocal的内存泄漏问题是一个比较典型的问题。

ThreadLocal内存泄漏的来源

  1. ThreadLocal对象在堆上存储的ThreadLocalMap:

    • ThreadLocalMap是ThreadLocal的内部类,用于存储线程局部变量。
    • ThreadLocalMap的key是ThreadLocal对象,value是线程变量的值。
  2. ThreadLocal的引用链:

    • ThreadLocal对象有两个引用源:一个是栈上的ThreadLocal引用(方法内创建),一个是ThreadLocalMap中的key对它的引用。
    • 如果栈上的ThreadLocal引用不再使用(方法结束),但ThreadLocal对象因为还有ThreadLocalMap中的key引用而无法被回收,就会导致内存泄漏。
  3. 线程对象被重复使用:

    • 在线程池场景中,线程对象会被重复使用。如果线程中的ThreadLocalMap没有及时清理,就会导致内存泄漏。

弱引用解决部分内存泄漏问题

为了解决ThreadLocal对象因为ThreadLocalMap中的key引用而无法被回收的问题,ThreadLocalMap使用了弱引用。

  • 弱引用:如果一个对象只具有弱引用,那么这个对象就会在下次垃圾回收时被回收。
  • 在ThreadLocalMap中,key(ThreadLocal对象)是弱引用。这意味着,当栈上的ThreadLocal引用不再使用时,ThreadLocal对象可以被垃圾回收器回收,从而避免了因为ThreadLocal对象无法被回收而导致的内存泄漏。

然而,即使使用了弱引用,ThreadLocal的内存泄漏问题并没有完全解决。因为value(线程变量的值)还是强引用,如果线程对象被重复使用(如在线程池中),并且ThreadLocalMap没有及时清理,那么value所占用的内存仍然无法被回收。因此,开发者还需要在使用完ThreadLocal后,显式地调用remove()方法来清理ThreadLocalMap中的entry,以避免内存泄漏。

综上所述,ThreadLocal的内存泄漏问题是一个复杂的问题,需要开发者和JDK共同解决。JDK通过使用弱引用来解决了一部分问题,但开发者还需要在使用完ThreadLocal后显式地调用remove()方法来避免内存泄漏。
总之,ThreadLocal是一个强大的工具,它可以帮助我们在多线程环境中实现数据的隔离和线程安全。但是,使用时也需要注意其潜在的内存泄漏问题,特别是在使用线程池时。

未命名

发表于 2025-09-14 | 更新于 2025-09-14
字数统计 | 阅读时长

未命名

发表于 2025-09-14 | 更新于 2025-09-14
字数统计 | 阅读时长

limit+order by ��ɵ������ظ�

发表于 2025-08-10 | 更新于 2025-09-14 | 分类于 mysql
字数统计 | 阅读时长

������������

��ʹ�� SQL ��ѯ���з�ҳʱ��LIMIT �� ORDER BY ���������õ��Ӿ䡣ORDER BY ����ָ��������У��� LIMIT �������Ʒ��صļ�¼����Ȼ������ ORDER BY ���У��� create_time���д����ظ�ֵʱ���Ϳ��ܳ��ַ�ҳ���⡣

ԭ�����

  1. ����Ψһ���� create_time ���ظ�ֵʱ�����ݿ��ڷ��ؽ��ʱ������Щ������ͬ create_time �ļ�¼û�й̶�������˳������ζ�ţ���ʹ����ִ����ͬ�IJ�ѯ��������ͬ create_time �ļ�¼��˳��Ҳ���ܲ�ͬ��

  2. ��ҳ�߽��������������ڷ�ҳʱ��ÿҳ��ʾ�̶������ļ�¼�������ҳ֮��ı߽紦�ж�����¼������ͬ�� create_time ֵ����Щ��¼���ܻ��ڲ�ͬ��ҳ�г��֣����������β�ѯ�е�˳��ͬ�������ظ����ֻ���©��

ʾ������

�������������ݣ�

1
2
3
4
5
id | create_time
1 | 2023-01-01
2 | 2023-01-01
3 | 2023-01-02
4 | 2023-01-02

���ÿҳ��ʾ2����¼�����ܳ������������

  • ��һ�β�ѯ���ܷ��� ID 1 �� 2��
  • �ڶ��β�ѯ���ܷ��� ID 2 �� 3��ID 2 �ظ����֣���
  • ���ߵ�һ�η��� ID 1 �� 3���ڶ��η��� ID 2 �� 4��������©����

�������

Ϊ�˱����������⣬���Բ�ȡ���¼��ֽ��������

  1. ʹ��Ψһ����Ϊ��Ҫ����������
    �� ORDER BY �Ӿ�������һ��Ψһ������ id����Ϊ��Ҫ������������������ȷ����������ȷ���Եģ���ʹ create_time ���ظ�ֵ��

    1
    2
    SELECT * FROM table ORDER BY create_time, id LIMIT 2 OFFSET 0; -- ��һҳ
    SELECT * FROM table ORDER BY create_time, id LIMIT 2 OFFSET 2; -- �ڶ�ҳ
  2. ʹ���α��ҳ��
    ������һҳ���һ����¼��ֵ��ȷ����һҳ�Ŀ�ʼλ�á����ַ������Ա�����Ϊ����Ψһ�����µ����⡣

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -- ��һҳ
    SELECT * FROM table ORDER BY create_time, id LIMIT 10;

    -- ����ҳ��������һҳ���һ����¼�� create_time �� id �ֱ�Ϊ last_seen_time �� last_seen_id��
    SELECT * FROM table
    WHERE (create_time > 'last_seen_time')
    OR (create_time = 'last_seen_time' AND id > last_seen_id)
    ORDER BY create_time, id
    LIMIT 10;
  3. ʹ�ô��ں�����ijЩ���ݿ�֧�֣���
    ʹ�ô��ں����� ROW_NUMBER() ��Ϊÿ�з���һ��Ψһ���кţ�Ȼ������кŽ��з�ҳ��

    1
    2
    3
    4
    SELECT * FROM (
    SELECT *, ROW_NUMBER() OVER (ORDER BY create_time, id) as row_num
    FROM table
    ) t WHERE row_num BETWEEN 11 AND 20; -- ����Ҫ��ѯ��2ҳ��ÿҳ10����¼

����

�� ORDER BY �������ظ�ֵʱ������ʹ�� LIMIT ��ҳȷʵ���ܵ��������ظ�����©��Ϊ��ȷ����ҳ�����һ���ԣ����ʵ��������һ��Ψһ����Ϊ��Ҫ��������������ʹ���α��ҳ�����ں����ȸ����ӵķ�ҳ��������Щ��������ȷ����ʹ�� ORDER BY �������ظ�ֵ������£���ҳ���Ҳ��ȷ����һ�µġ�

Redis��Ƭ��Ⱥ(Clusterģʽ)�£�Lua�ű����ܻ�ʧЧ�������������Ҫԭ�򼰽������

发表于 2025-08-10 | 更新于 2025-09-14 | 分类于 redis
字数统计 | 阅读时长

��Redis��Ƭ��Ⱥ(Clusterģʽ)�£�Lua�ű����ܻ�ʧЧ�������������Ҫԭ�򼰽���������£�

��Redis��Ƭ��Ⱥ��Clusterģʽ����ʹ��Lua�ű�ʱ��ȷʵ���ܻ�����һЩ���⣬��Ҫ������Redis Cluster�����������Lua�ű�ֻ���ڵ����ڵ���ִ�У��������漰�ļ�����λ��ͬһ����Ƭ��hash slot���ڡ������Ƕ������������ϸ���ͼ���������Ľ�һ��������

����ԭ��

  1. ������ͬһ����Ƭ��
    Redis Clusterͨ����ϣ��Ƭ�����ݷֲ�����ͬ�Ľڵ��ϡ�ÿ������������key�����һ����ϣֵ��Ȼ��ӳ�䵽�ض��ķ�Ƭ�ϡ����һ��Lua�ű����Է��ʶ����ͬ��Ƭ�ļ�����ô�ýű����޷���Redis Cluster��ִ�С�

  2. ��ڵ����������
    Redis Cluster��֧�ֿ�ڵ��ԭ�Ӳ���������ζ��һ��Lua�ű����ܿ����ڵ�ִ�С�������Lua�ű������õļ�����λ��ͬһ���ڵ��ϡ�

  3. �ű�����������
    Lua�ű���Redis�п���ͨ��EVALSHA����ʹ��SHA1ժҪ��ִ�У���������ܡ�Ȼ�����ڼ�Ⱥ�����У��ű���Ҫ��ÿ���ڵ��ϵ������أ���Ϊ�ű������Զ��ڼ�Ⱥ�ڵ��ͬ����

�������

  1. ȷ�����м���ͬһ����Ƭ��

    • ʹ��Redis��hash tags���ܡ�ͨ���ڼ����а���{}��ָ��һ���ּ�����Ϊhash tag��Redis��ʹ���ⲿ���������ϣֵ���Ӷ�ȷ��������ͬhash tag�ļ��ᱻӳ�䵽ͬһ����Ƭ�ϡ�
  2. ʹ��EVALSHA�ı�ͨ������

    • �����нڵ���Ԥ�ȼ��ؽű���ʹ��SCRIPT LOAD������ÿ���ڵ��ϼ��ؽű������洢���ص�SHA1ժҪ��Ȼ����Ӧ����ʹ��EVALSHA����ִ�нű���
  3. ǿ�Ƽ����䵽ͬһ��Ƭ��

    • ����hash tags����һ�ֱ�����ʽ��ͨ��ȷ�������а�����ͬ��hash tag���֣�����ǿ����Щ�������䵽ͬһ����Ƭ��
  4. ���ڱ�����Ƭ�IJ�����

    • ��������ֳɶ���ű���ÿ���ű��ڸ��Եķ�Ƭ��ִ�С�Ȼ���ڿͻ��˾ۺ���Щ�ű��Ľ����
  5. ʹ�ø߼��ͻ��˿���

    • ��Щ�߼�Redis�ͻ��˿⣨��Redisson���ṩ�˶Լ�Ⱥģʽ��Lua�ű���֧�ֺ��Ż������Լ��ڼ�Ⱥ������ʹ��Lua�ű��Ĺ��̡�

���ʵ��

  • ��ƽű�ֻ������������ʹ��hash tags�����DZ�����Ƭ�������򵥷�����
  • �����ڽű���ʹ�������ȡ��ȫ��״̬������Լ��ٽű��ĸ����Ժ�DZ�ڵĴ���
  • Ԥ�������нڵ���س��ýű����������߽ű�ִ�е�Ч�ʡ�
  • ����ʹ���������ز������Lua�ű�����ijЩ����£�ʹ��Redis���������ز��������MULTI/EXEC��������һ�����е����������

�������

  • ʹ��Redis Modules��Redis Modules�ṩ����չRedis���ܵķ�ʽ�������ڼ�Ⱥ�������ṩ�����ӵIJ�����
  • �ڿͻ���ʵ�ָ����߼������Lua�ű��ڼ�Ⱥ�е�����̫�࣬���Կ��ǽ����ӵ��߼��Ƶ��ͻ���ʵ�֡�
  • �����Ƿ������Ҫ��Ⱥģʽ����ijЩ����£�����ֻ��ҪRedis���ڱ�ģʽ��Sentinel�����ṩ�߿����Ժ͹���ת�ƣ������������ķ�Ƭ��Ⱥ��

Redis ����ͬ�������� Redis Cluster ͬ���������

发表于 2025-08-02 | 更新于 2025-09-14 | 分类于 redis
字数统计 | 阅读时长

Redis ����ͬ�������� Redis Cluster ͬ���������

һ��Redis ����ͬ������

Redis ������ͬ��������Ҫ�������ݵĸ��ƺ͸��ؾ��⡣����Ϊȫ��ͬ��������ͬ������ģʽ��

  1. ȫ��ͬ����Full Resynchronization��

    • �����������ӽڵ��״��������ڵ㣬�����Ӹ����ж�ʱ��������¸��ƻ�ѹ��������Repl Backlog����ʧ��
    • ������
      1. �ӽڵ㷢�� PSYNC ��������ͬ����
      2. ���ڵ��ж���Ҫȫ��ͬ����ִ�� BGSAVE ���� RDB ���ա�
      3. ���ڵ㽫 RDB �ļ����͸��ӽڵ㡣
      4. �ӽڵ���վ����ݣ����� RDB �ļ��ָ����ݡ�
      5. ���ڵ㽫���� RDB �ڼ����д������� replication buffer�������͸��ӽڵ㡣
    • �ص������������������������� RDB ���ɺʹ�����ܺ�ʱ�ϳ��������ڵ���ͬ���ڼ��ռ�ö����ڴ档
  2. ����ͬ����Partial Resynchronization��

    • �����������ӽڵ���ݶϿ����������ӣ������ڵ�ĸ��ƻ�ѹ�������Դ��ж����ڼ�����
    • ������
      1. �ӽڵ㷢�Ͱ������ڵ� runid ���������������ƫ������ PSYNC ���
      2. ���ڵ���ƫ�������� repl_backlog ��Χ�����ʹӸ�ƫ������ʼ�����������
      3. �ӽڵ���ղ�ִ����Щ����������ͬ����
    • �ص��������� repl_backlog���ʺ϶�ʱ����߻ָ�������ȫ��ͬ���Ŀ�����

����Redis Cluster ��ͬ������

Redis Cluster ͨ����Ƭʵ�����ݵķֲ�ʽ�洢��ÿ����Ƭ�ڲ���Ȼ�������Ӹ��ƣ�����Ⱥģʽ�»��漰 Gossip Э��͹���ת�ơ�

  1. ���Ӹ�����ÿ�����ڵ��Ӧ 1 �������ӽڵ㣬����ͬ����ʽ����ͨ������ͬ��

  2. ��Ⱥ������ͬ����Gossip Э�飩���ڵ��ͨ�� PING/PONG ��Ϣ������Ⱥ״̬����۷��䡢�ڵ�������ȡ��½ڵ�����ڵ�����ʱ����Ⱥͨ�� Gossip Э���Զ��������ˡ�

  3. ����ת�ƣ�Failover���������ڵ�崻�ʱ���ӽڵ�ͨ�� Raft ѡ���㷨��ѡ��Ϊ�����ڵ㣬�����ڵ����·�ɱ�ָ�������ڵ㡣

����ͬ�����ƵĹؼ�����

  • repl-backlog-size���������ƻ�ѹ��������С��Ӱ������ͬ�����ݴ�ʱ�䡣
  • client-output-buffer-limit slave���������ڵ�Դӽڵ���������������ֹ�ӽڵ㳤ʱ�䲻���ѵ������ڵ��ڴ������
  • min-slaves-to-write�����ڵ�������� N ���ӽڵ�����ʱ�Ž���д�룬��ǿ���ݰ�ȫ�ԡ�
  • repl-diskless-sync������ͬ�������ڵ�ֱ��ͨ�����緢�� RDB��������� I/O��

�ġ��������⼰�Ż�����

  1. ����ͬ���ӳ�

    • ԭ�����ӽڵ㴦�����������ӳ١����ڵ�д��ѹ����
    • �Ż������� repl-backlog-size��ʹ�ø�������磬�������ڵ�ִ�к�ʱ���
  2. �ӽڵ���� Key ����

    • �ص����ӽڵ㲻������ɾ������ Key�����ǵȴ����ڵ㷢�� DEL ����ͬ��ɾ�������ӽڵ��ȡʱ���� Key �Ƿ���ڣ��߼����ڣ���
  3. ����һ���Ա�֤

    • Ĭ����Redis ������һ���ԣ��첽���ƣ���
    • �Ż���ʹ�� WAIT ���������ͻ��ˣ�ֱ������ͬ�������� N ���ӽڵ㡣���ή����������

�ܽ�

  • ȫ��ͬ���������ڳ���ͬ����ʱ����ߣ�����һ����ǿ��������Ӱ���
  • ����ͬ���������ڶ�ʱ������� repl_backlog ���ã���������һ�£�����Ӱ��С��
  • ������������������ repl-backlog-size��������ӽڵ㸴��ƫ�����IJ�ֵ������ͬ���ӳ١�����Ҫǿһ�µij�������ʹ�� WAIT��

SpringBoot ��������浽redis�ķ�ʽ

发表于 2025-07-20 | 更新于 2025-09-14 | 分类于 redis
字数统计 | 阅读时长

��Spring Boot��Ŀ�У������ݿ��ѯ������浽Redis��һ����������������������ݷ����ٶȺͼ������ݿ⸺�ء������Ǽ���ʵ����һ����ij���������

1. ʹ��Spring Data Redis�ֶ��洢

���ַ����漰��ֱ��ʹ��RedisTemplate���ֶ��������档��DataCacheService���У�ͨ��@Autowiredע��RedisTemplate�����ݿ���ʵ�YourRepository����@PostConstructע��ķ����У���Ŀ������ִ�����ݿ��ѯ������������浽Redis�С�

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class DataCacheService {

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Autowired
private YourRepository yourRepository;

@PostConstruct
public void init() {
List<YourEntity> data = yourRepository.findAll();
redisTemplate.opsForValue().set("cacheKey", data);
redisTemplate.expire("cacheKey", 1, TimeUnit.HOURS); // ���ù���ʱ��
}
}

�ŵ���ֱ�ӿ��ƻ�������ݺ��������ڡ�
ȱ������Ҫ�ֶ���������ĸ��º�ʧЧ��

2. ʹ��@Cacheableע���Զ�����

Spring Cache�ṩ��һ������ʽ�Ļ������@Cacheableע������Զ����淽���ķ���ֵ�����ȣ���Ҫ����һ��CacheManager������ʹ��Redis��Ϊ����洢��Ȼ������Ҫ����ķ�����ʹ��@Cacheableע�⡣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(config).build();
}
}

@Service
public class YourService {

@Autowired
private YourRepository yourRepository;

@Cacheable(value = "yourCache", key = "'allData'")
public List<YourEntity> getAllData() {
return yourRepository.findAll();
}
}

�ŵ������˻��������ֻ��ע�⼴�ɡ�
ȱ�������ܲ����ֶ��������ر����ڸ��ӵĻ�������ϡ�

3. ʹ��ApplicationRunner��CommandLineRunner�ӿ�

�������ӿ�������Spring BootӦ������ʱ���д��롣���Ƿdz��ʺ����ڳ�ʼ�����ݻ���ػ��档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Component
public class RedisDataLoader implements ApplicationRunner {

@Autowired
private YourRepository yourRepository;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@Override
public void run(ApplicationArguments args) throws Exception {
List<YourEntity> data = yourRepository.findAll();
redisTemplate.opsForValue().set("initialData", data);
}
}

�ŵ����ʺ���Ӧ������ʱִ��һ��������
ȱ����������������ʱ�����ݼ��أ����ʺ϶�̬������¡�

4. ʹ��@EventListener����Ӧ�������¼�

���ַ���ͨ������ApplicationReadyEvent�¼�����Ӧ����ȫ������ִ�д��롣��Ȼ���ַ�����ApplicationRunner��CommandLineRunner���ƣ����ṩ�˸��������ԣ��ر�������Ҫ��ϸ���ȵĿ���ʱ��

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Service
public class StartupDataLoader {

@Autowired
private YourRepository yourRepository;

@Autowired
private RedisTemplate<String, Object> redisTemplate;

@EventListener(ApplicationReadyEvent.class)
public void loadDataOnStartup() {
List<YourEntity> data = yourRepository.findAll();
redisTemplate.opsForValue().set("startupData", data);
}
}

�ŵ����ṩ����Ӧ����ȫ������ִ�д��������ԡ�
ȱ������ApplicationRunner��ȣ����������΢����һЩ��

�ܽ�

ѡ�����ַ���ȡ���ھ���������Ӧ�ó����������Ҫ�򵥵Ļ��������@Cacheableע���������õ�ѡ�������Ҫ�����Ŀ��ƻ���Ҫ��Ӧ������ʱִ���ض��ij�ʼ������ApplicationRunner��CommandLineRunner��@EventListener���ܸ��ʺϡ��ֶ�ʹ��RedisTemplate�ṩ����������ԣ���Ҳ��Ҫ���Ĵ����ά��������

Mysql���ݿ����

发表于 2025-07-13 | 更新于 2025-09-14 | 分类于 ���ݿ�
字数统计 | 阅读时长

InnoDB Update�����ڲ�����

  1. ��Buffer Pool�ж�ȡ������

    • InnoDB���Ȼ���Buffer Pool���ڴ��еĻ���أ��в�����Ҫ���µļ�¼��
    • �����¼����Buffer Pool�У�InnoDB��Ӵ��̶�ȡ��ҳ��Buffer Pool�С�
  2. ��¼Undo Log��

    • ���޸IJ���ǰ��InnoDB����Undo Log�м�¼�޸�ǰ�����ݡ�
    • Undo Log��������ع�����֤�����ԭ���Ժ�һ���ԡ�
    • Undo Log���д���ڴ棬Ȼ���ɺ�̨�̶߳�ʱˢ�µ����̡�
  3. ��Buffer Pool�����

    • InnoDB��Buffer Pool�и������ݣ������޸ĺ������ҳ���Ϊ����ҳ����
  4. ��¼Redo Log Buffer��

    • ͬʱ��InnoDB�Ὣ�޸IJ���д�뵽Redo Log Buffer�С�
  5. �ύ������

    • ��ִ���������޸IJ����������ύ��
    • InnoDB�ὫRedo Log��Bufferд����̣���֤����ij־��ԡ�
  6. ������

    • InnoDB��̨�̻߳��첽�ؽ�Buffer Pool�е���ҳд����̡�
  7. ��¼Binlog��

    • ���ύ�����У�InnoDB�Ὣ�����ύ����Ϣ��¼��Binlog�С�
    • Binlog����MySQL�����Ӹ��ơ�

�����2�׶��ύ

  • 2�׶��ύ��MySQL�ڸ��¹����б�֤binlog��redologһ���Ե�һ���ֶΡ�
  • Prepare�׶���SQL�ɹ�ִ�в�����redolog��ͨ��write()д���ļ�����������ʱ������δ�ύ����redolog�Ѿ�׼���á�
  • Commit�׶����������ύʱ��MySQL�Ὣbinlog�־û�����ȷ��redologҲ���־û������̡���������ʹϵͳ������Ҳ��ͨ��redolog��binlog��֤���ݵ�һ���ԺͿɻָ��ԡ�

����MySQL�����2�׶��ύ

MySQL�����2�׶��ύ��һ���ڸ��¹����б�֤binlog����������־����redolog��������־��һ���ԵĻ��ơ���һ����ȷ�������ݵ�һ���Ժͳ־��ԣ��ر���������ͬ���ͱ����ָ��ij����С�

2�׶��ύ�Ĺ���

  1. Prepare�׶���

    • SQL���ɹ�ִ�к󣬻�����redolog��д����̡���ʱ��������prepare�׶Ρ�
    • Redolog��InnoDB�洢�����������־����¼�����ݵ������仯��
  2. BinLog�־û���

    • ��prepare�׶�֮��MySQL�Ὣbinlog���ڴ���־��д����̡�
    • Binlog��MySQL�Ķ�������־����¼�����е�DDL��DML��䣬��������ͬ�������ݻָ���
  3. Commit�׶���

    • ��binlog�־û�֮���������commit�׶Σ�ִ�������ڲ�ִ�����յ����������������redolog��״̬��

write��fsync������

  • write��������д���ļ��Ļ���������ʱ���ݲ�δ�����־û��������ϣ�������ʱ�洢���ڴ��С�
  • fsync��ǿ�ƽ��ļ����޸ij־û��������ϣ�ȷ�����ݵij־��ԡ�write��fsyncͨ�����ʹ�ã���ȷ�����ݵĿɿ��Ժͳ־��ԡ�

Ϊʲô��Ҫ2�׶��ύ

���������2�׶��ύ�����ܻ�����������⣺

  • �����д��redo log�ɹ�����binlogδд��ɹ��ͱ��������������redo log�ָ����ݣ���binlogû�м�¼��α�����������������ݲ�һ�¡�
  • �����д��binlog�ɹ�����redo logδд��ɹ��ͱ���������������redo logû�м�¼��α�����������ݻ��Ǿ�ֵ����binlog�Ѿ���¼������ͬ��ʱ�ᵼ�����ݲ�һ�¡�

2�׶��ύ��α�֤һ����

����2�׶��ύ��������ύ�������������������

  • ���һ��һ�׶��ύ���������д��redo log������prepare״̬ʱ����������ʱֱ�ӻع�����������һ�¡�
  • �������һ�׶��ύ�ɹ���д��binlog���������ʱ���binlog�е������Ƿ����������������������������ύ���񣬷���ع�����
  • �������redo log����commit״̬ʱ��������������ͬ�������

ͨ��2�׶��ύ��MySQLȷ����binlog��redo log��һ���ԣ��Ӷ���֤�����ݵ�һ���Ժͳ־��ԡ��ڱ����ָ�ʱ�����Ը���binlog��redo log��״̬���������ύ���ǻع����񣬴Ӷ�ȷ��������֮�������һ���ԡ�

SpringEvent���¼�����

发表于 2025-07-06 | 更新于 2025-09-14 | 分类于 Spring
字数统计 | 阅读时长

SpringEvent���¼�����

��������

��Spring��ʹ��Spring Event���¼�������һ��ʵ�������������Ч��ʽ�����Ѿ�������Spring Event�Ļ��������ʵ�ֲ��裬�������ҽ�ͨ������Ĵ���ʾ������ϸ���������Spring��ʵ���¼��������ơ�

����һ�������¼�

���ȣ�������Ҫ����һ���¼��࣬�������Ҫ�̳�ApplicationEvent����������У����ǿ��Է�װ���¼���ص����ݡ�

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.context.ApplicationEvent;

public class RegisterSuccessEvent extends ApplicationEvent {
private String userName;

public RegisterSuccessEvent(Object source, String userName) {
super(source);
this.userName = userName;
}

public String getUserName() {
return userName;
}
}

����������У�RegisterSuccessEvent���װ��һ���û�ע��ɹ����¼������а������û�����Ϣ��

������������¼�������

��������������Ҫ����һ���¼����������¼���������Ҫʵ��ApplicationListener�ӿڣ�����Ҳ����ʹ��@EventListenerע�������һ��������Ϊ�¼���������

ʹ��ApplicationListener�ӿڣ�

1
2
3
4
5
6
7
8
9
10
11
12
13
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class RegisterSuccessListener implements ApplicationListener<RegisterSuccessEvent> {
@Override
public void onApplicationEvent(RegisterSuccessEvent event) {
// �����ﴦ���¼������緢�ͻ�ӭ����
String userName = event.getUserName();
System.out.println("User " + userName + " registered successfully. Sending welcome message...");
// TODO: ���ͻ�ӭ���ŵ��߼�
}
}

����ʹ��@EventListenerע�⣺

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

@Component
public class AnotherRegisterSuccessListener {

@EventListener
public void handleRegisterSuccessEvent(RegisterSuccessEvent event) {
// �����ﴦ���¼��������¼��־
String userName = event.getUserName();
System.out.println("Log: User " + userName + " registered successfully.");
// TODO: ��¼��־���߼�
}
}

ע�⣺ʹ��@EventListenerע��ʱ�����ϣ�������ض����͵��¼���������ע����ָ���¼����ͣ������������������ʡ�������ͣ�����ζ�������Լ����������͵��¼�������ͨ�����ǻ�ָ��������¼���������ߴ���������ȺͿɶ��ԣ���

�������������¼�

���������Ҫ�ں��ʵĵط������¼�����ͨ������ij��ҵ���߼��������֮����еġ�

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;

@Service
public class UserService {

@Autowired
private ApplicationEventPublisher applicationEventPublisher;

public void registerUser(String userName) {
// �����û�ע���߼��Ѿ��������
System.out.println("User " + userName + " is being registered...");

// �����¼�
RegisterSuccessEvent event = new RegisterSuccessEvent(this, userName);
applicationEventPublisher.publishEvent(event);
}
}

����������У�UserService���е�registerUser�������û�ע��ɹ��󷢲���һ��RegisterSuccessEvent�¼���

�ܽ�

ͨ�����ϲ��裬���Ǿ���Spring��ʵ����һ���򵥵��¼��������ơ���UserService�е�registerUser����������ʱ�����ᷢ��һ��RegisterSuccessEvent�¼���Ȼ������ע��������¼����͵ļ��������ᱻ֪ͨ������ִ����Ӧ�Ĵ����߼������ַ�ʽ������ʵ�������Ľ�����������ơ�

������

��Spring����У�����������¼�����������ǿ��Ĺ��ܣ����ǿ��Զ���ʹ�ã���Ҳ���Խ�������������ض���ҵ������@TransactionalEventListenerע���������һ����ϵ㣬������������������IJ�ͬ�������ڽ׶μ�������Ӧ�¼���

��������

  • ����Transaction���������ݿ�����У�������ָ��Ϊ�����߼�������Ԫִ�е�һϵ�в�������Щ����Ҫôȫ��ִ�У�Ҫôȫ����ִ�С��������ĸ����ԣ�ԭ���ԣ�Atomicity����һ���ԣ�Consistency���������ԣ�Isolation�����־��ԣ�Durability����ͨ�����ΪACID���ԡ�

  • �¼�������Event Listening������Spring����У��¼����������������󣨷����ߣ������¼��������������󣨼��������첽��ͬ���ؽ��ղ�������Щ�¼�������һ�ֽ��������ͨ�ŵķ�ʽ��

@TransactionalEventListener��ʹ��

�����ṩ��ʾ���У�UserService�������û�ע���߼�������ע��ɹ��󷢲�һ��UserRegistrationEvent�¼�������¼���UserRegistrationEventListener���������ؼ����ڼ�����ʹ����@TransactionalEventListenerע�⣬����ζ���¼��Ĵ�������������ض��׶ν��С�

  • Ĭ����Ϊ��@TransactionalEventListenerĬ��������ɹ��ύ��AFTER_COMMIT�׶Σ������¼�������������Ϊ��ȷ���������еIJ�������Ӱ�쵽����Ļع����ߡ����磬���ͻ�ӭ�ʼ������IJ����Ͳ�Ӧ��Ӱ���û�ע������ijɹ����

  • phase������ͨ��phase���ԣ��������ü�����������IJ�ͬ�׶δ�����

    • BEFORE_COMMIT���������ύǰ�����������������񼴽����ʱִ��һЩ������߼�������������׳��쳣�����ܵ�������ع���
    • AFTER_COMMIT��������ɹ��ύ�󴥷���������ִ�в���ع��IJ������緢��֪ͨ��
    • AFTER_ROLLBACK��������ع��󴥷��������ڼ�¼�ع���־��ִ������������
    • AFTER_COMPLETION����������ɺ󴥷��������ύ���ǻع���������ִ�����������޹صIJ�����

Ӧ�ó���

ʹ��@TransactionalEventListener�ĵ��ͳ���������

  • ������ɹ���ִ���첽�������緢���ʼ�������֪ͨ�ȣ��Ա��������������ִ�С�
  • ������ع���ִ���ض����������¼�������Ա��ڹ����Ų����ơ�
  • �����񼴽��ύǰִ��һЩ�����������ݸ��²�����

��֮��@TransactionalEventListener�ṩ��һ�����ķ�ʽ��������IJ�ͬ�׶���Ӧ�¼����Ӷ�ʵ�ָ����ӵ�ҵ���߼��������Ľ���ͨ�š�

CompletableFuture �� Stream ��������������ʹ�ó���

发表于 2025-07-06 | 更新于 2025-09-14 | 分类于 ���߳�
字数统计 | 阅读时长

CompletableFuture �� Stream ��������������ʹ�ó���

�� Java ��������У�CompletableFuture �� Stream �IJ��������Ǵ������߳��������Ч���ߣ������������Ŀ�ġ�ʹ�ó�����������������ͬ��

��������

  1. �ײ��̳߳���

    • CompletableFuture�������Զ����̳߳أ�ͨ�� Executor����
    • Stream.parallel() / parallelStream���̶�ʹ�� ForkJoinPool.commonPool()��
  2. �������������

    • CompletableFuture��֧����ʽ�첽������ thenApply, thenCombine��������������š�
    • Stream ����������֧�ּ򵥵��������������� map, filter���������������������
  3. �쳣������

    • CompletableFuture��֧�ֻص�ʽ�쳣�������� exceptionally����
    • Stream ���������쳣���ܵ�����������ֹ���쳣������Ϊ�ֲڡ�
  4. ������

    • CompletableFuture�������� IO �ܼ��������첽������š�
    • Stream �������������� CPU �ܼ������񡢴��ڴ���㡣
  5. ����������

    • CompletableFuture��ϸ���ȿ��ƣ��ɵ�������ÿ������
    • Stream �������������ȿ��ƣ����������д�����
  6. ˳��֤��

    • CompletableFuture����ͨ�� thenApplyAsync �ȷ�����������˳��
    • Stream �����������ϸ�˳��֤������˳����ܲ�ȷ����

ʹ�ó���

  1. �ʺ� CompletableFuture �ij�����

    • IO �ܼ��������� HTTP �������ݿ��ѯ��
    • ��Ҫ���ӵ�������ţ������� A ��ɺ󴥷����� B��
    • ��Ҫ�Զ����̳߳أ��Ա���Ӱ��ϵͳȫ���̡߳�
    • ��Ҫ��ϸ���쳣������
  2. �ʺ� Stream �������ij�����

    • CPU �ܼ��ͼ��㣬������ݼ��ϵ� map, filter��
    • ���ⲿ�����Ĵ��ڴ������
    • �����࣬���踴��������š�

���ѡ��

  • ��Ҫ�Զ����̳߳���ѡ�� CompletableFuture��
  • �漰 IO ����������/���ݿ⣩��ѡ�� CompletableFuture��
  • ��Ҫ����������A��B��C����ѡ�� CompletableFuture��
  • ���ڴ���㡢���踴�����������ѡ�� Stream ��������

ͨ��������Щ�����ʹ�ó����������߿��Ը��ݾ�������ѡ����ʵĹ������Ż����������ִ�С�

线程池流程

发表于 2025-06-16 | 更新于 2025-09-14 | 分类于 场景题
字数统计 | 阅读时长

当把一个线程池核心线程数设置为 0,最大线程数设置为 100,此时当任务到达会怎么运行?

题目详细答案

当你将一个线程池的核心线程数设置为 0,最大线程数设置为 100 时,线程池的行为主要取决于其工作队列和拒绝策略。

当核心线程数为 0的时候:这意味着线程池在没有任务时不会维持任何空闲线程。只有在有任务到达时,线程池才会尝试创建线程来执行任务。

最大线程数为 100:这是线程池能够创建的最大线程数量。当任务数超过核心线程数时,线程池会继续创建新线程,直到达到最大线程数。

重点就是任务到达时的行为:

1、如果工作队列为空:当一个新任务到达时,由于核心线程数为 0,线程池不会立即创建核心线程,而是直接将任务放入工作队列中。

2、如果工作队列已满:当工作队列已满时,线程池会尝试创建新的线程来处理任务,直到线程数达到最大线程数。

3、如果线程数已达到最大线程数:如果线程数已经达到了最大线程数并且工作队列也满了,那么新到达的任务将根据线程池的拒绝策略进行处理(例如,抛出异常、丢弃任务、丢弃最旧的任务或调用任务的rejectedExecutionHandler方法)。

具体的行为还取决于工作队列的类型和大小:

有界队列:如果使用的是有界队列(例如ArrayBlockingQueue),当队列已满时,新的任务会触发线程池创建新的线程,直到达到最大线程数。超过最大线程数的任务将根据拒绝策略处理。

无界队列:如果使用的是无界队列(例如LinkedBlockingQueue),线程池将优先将任务放入队列中,而不是创建新线程。因此,线程数可能不会达到最大线程数,除非队列的任务处理速度跟不上任务到达的速度。

总结当核心线程数为 0,最大线程数为 100 时,线程池在任务到达时会首先尝试将任务放入工作队列中。如果队列已满,则会创建新的线程来处理任务,直到达到最大线程数 100。如果线程数已达到最大并且队列也满了,则会根据拒绝策略处理新任务。

原文: https://www.yuque.com/jingdianjichi/xyxdsi/agopenygvg84ny3z

报错

发表于 2025-05-16 | 更新于 2025-09-14 | 分类于 error
字数统计 | 阅读时长

easyexcel 导入使用shiro报错

一、背景描述

SpringBoot项目,使用Shiro进行权限管理。测试过程中发现执行文件导入时最开始一切正常,但是导入几次之后再次执行导入就会报错,此时执行其他功能一切正常

二、问题描述

  • 在使用 EasyExcel 进行多线程导入数据时,可能会遇到 org.apache.shiro.session.UnknownSessionException 异常。这个异常通常与 Apache Shiro 的会话管理有关,尤其是在多线程环境下,如果多个线程尝试访问或操作同一个会话,而没有正确处理线程间的同步,就可能抛出此类异常。

三、原因分析

1、会话管理不正确:在多线程环境中,如果多个线程尝试访问或修改同一个用户的会话(Session),而没有适当的管理和同步,就可能导致 UnknownSessionException。

2、会话过期或失效:在多线程环境下,如果会话在某个线程中被修改或过期,而其他线程还在尝试访问该会话,也可能会引发此异常。

四、问题分析

  • 排查导入代码发现,只有在执行保存语句的时候获取当前用户使用了Shiro相关代码,因此怀疑此处出现问题。
    1
    this.operator = (String) SecurityUtils.getSubject().getPrincipal();
  • 在此处断点发现,手动新增数据和批量导入数据都执行该语句,但两次获取的Subject不一致。因此阅读源码进行排查。
    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
    // 从SecurityUtils中获取Subject源码如下
    // package: org.apache.shiro.SecurityUtils
    public static Subject getSubject() {
    Subject subject = ThreadContext.getSubject(); // ①
    if (subject == null) {
    subject = (new Subject.Builder()).buildSubject();
    ThreadContext.bind(subject);
    }
    return subject;
    }

    // 继续跟进上面①中的方法
    // package: org.apache.shiro.util.ThreadContext
    public static Subject getSubject() {
    return (Subject) get(SUBJECT_KEY); // ②
    }

    // 继续跟进上面②中的方法
    // package: org.apache.shiro.util.ThreadContext
    public static Object get(Object key) {
    if (log.isTraceEnabled()) {
    String msg = "get() - in thread [" + Thread.currentThread().getName() + "]";
    log.trace(msg);
    }

    Object value = getValue(key); // ③
    if ((value != null) && log.isTraceEnabled()) {
    String msg = "Retrieved value of type [" + value.getClass().getName() + "] for key [" +
    key + "] " + "bound to thread [" + Thread.currentThread().getName() + "]";
    log.trace(msg);
    }
    return value;
    }

    // 继续跟进上面③中的方法
    // package: org.apache.shiro.util.ThreadContext
    private static Object getValue(Object key) {
    Map<Object, Object> perThreadResources = resources.get(); // ④
    return perThreadResources != null ? perThreadResources.get(key) : null;
    }

    // 上面④中的resources在ThreadContext中定义如下
    // package: org.apache.shiro.util.ThreadContext
    private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();

根据上面对源码的跟踪,发现Subject是与ThreadLocal也就是线程绑定的。获取Subject时先获取当前线程绑定的Subject,若没有则重新创建并绑定到当前线程。而我导入的时候为了提高导入效率,使用了多线程。到此就发现问题的原因了

五、问题分析

  • 假设项目中线程池设置核心线程数量为10,而核心线程默认是不会被超时回收的
    • 可通过threadPoolExecutor.allowCoreThreadTimeOut(true);设置核心线程超时回收

1、当用户A登录后,执行导入操作,从线程池中拿出5个线程,此时这5个线程将绑定用户A的Subject
2、当用户A多次执行导入操作后,线程池全部核心线程与用户A的Subject绑定。用户A退出登录后,线程池并不会将核心线程进行销毁。
后续用户B登录,再次执行导入操作,此时线程池分配线程进行操作,但此时所有的线程都已与用户A绑定,因此获取到的Subject都是用户A的Subject,从Subject中获取session时此session已被销毁,因此报错

1
2
3
4
5
6
7
8
9
// 根据sessionId获取session,获取为空则报错
// package: org.apache.shiro.session.mgt.eis.AbstractSessionDAO
public Session readSession(Serializable sessionId) throws UnknownSessionException {
Session s = doReadSession(sessionId);
if (s == null) {
throw new UnknownSessionException("There is no session with id [" + sessionId + "]");
}
return s;
}

六、建议

多线程时不要使用Shiro相关代码。将用户名作为参数传入,不再单独获取。

七、参考文档

https://www.cnblogs.com/bbc2005/articles/15340412.html

👌redis sentinal与redis cluster的区别?

发表于 2025-05-12 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis sentinal与redis cluster的区别?

口语化答案

哨兵和集群是两种非常不同的模式。一个重点关注高可用,一个关注水平扩展和数据分片。哨兵主要解决主服务器故障的自动切换问题。集群则是为了存储大量数据,将数据分布到多个节点。在存储上,哨兵不进行数据分片,集群则通过哈希槽的方式存储数据,集群的可用和切换采用了Gossip 协议,哨兵模式则依靠哨兵的检测。所以哨兵适合小规模的高可用,集群适合大规模的数据存储。以上。

题目解析

这道题还是挺重要的,经常有人会将两种模式弄混。二者模式区别非常大,要重点关注。

面试得分点

高可用,水平扩展,数据分片,哈希槽

题目详细答案

先来了解概念:

redis sentinal 哨兵模式

redis cluster 集群模式

哨兵模式

Sentinel是一种用于监控Redis主从复制结构并实现自动故障转移的系统。它主要关注的是高可用性,保证当主服务器发生故障时,能够自动将一个从服务器提升为新的主服务器,并通知客户端进行相应的切换。

集群模式

Cluster是一种分布式存储解决方案,支持自动分片和高可用性。支持主从复制和故障转移,还能够将数据分布在多个节点上,实现数据的水平扩展。

区别

哨兵模式 集群模式
架构区别 Sentinel节点监控主从服务器的状态,当主服务器故障时,Sentinel节点会选举一个新的主服务器。 数据自动分片存储在多个节点上,每个节点负责一部分数据。每个分片有一个主节点和一个或多个从节点。使用Gossip协议进行节点间通信,自动检测故障并进行主从切换。
数据存储 数据存储在一个主服务器及其从服务器上,不进行数据分片。 数据通过哈希槽(hash slots)机制分布在多个节点上,每个节点负责一部分哈希槽。
扩展性 只提供高可用性,不能水平扩展数据存储容量。 支持水平扩展,可以通过增加节点来扩展数据存储容量和处理能力。
高可用 通过心跳机制检测主服务器的状态,自动选举新的主服务器并更新拓扑结构。 使用Gossip协议进行节点间的故障检测,自动进行主从切换,确保集群的高可用性。
使用场景 适用于小规模的Redis部署,主要关注高可用性。不需要数据分片。 适用于大规模的Redis部署,支持水平扩展。需要同时满足高可用性和数据分片、扩展性需求。

👌redis key的过期时间和永久有效分别怎么设置?

发表于 2025-05-11 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis key的过期时间和永久有效分别怎么设置?

题目详细答案

在Redis中,你可以使用多种命令来设置键的过期时间或将键设置为永久有效。

设置键的过期时间

  1. 使用EXPIRE命令,EXPIRE命令用于设置键的过期时间,以秒为单位。
1
2
3
EXPIRE key seconds
例如:
EXPIRE mykey 60 # 设置 mykey 的过期时间为60秒
  1. 使用PEXPIRE命令,PEXPIRE命令用于设置键的过期时间,以毫秒为单位。
1
2
3
PEXPIRE key milliseconds
例如:
PEXPIRE mykey 60000 # 设置 mykey 的过期时间为60000毫秒(即60秒)

以上的两个区别就是秒级和毫秒级的区别。

  1. 使用EXPIREAT命令,EXPIREAT命令用于设置键的过期时间为指定的 Unix 时间戳,以秒为单位。
1
2
3
EXPIREAT key timestamp
例如:
EXPIREAT mykey 1672531199 # 设置 mykey 的过期时间为指定的 Unix 时间戳
  1. 使用PEXPIREAT命令,PEXPIREAT命令用于设置键的过期时间为指定的 Unix 时间戳,以毫秒为单位。
1
2
3
PEXPIREAT key milliseconds-timestamp
例如:
PEXPIREAT mykey 1672531199000 # 设置 mykey 的过期时间为指定的 Unix 时间戳(毫秒)
  1. 使用SET命令带选项,SET命令可以在设置键值的同时指定过期时间。
1
2
3
4
5
SET key value EX seconds
SET key value PX milliseconds
例如:
SET mykey "value" EX 60 # 设置 mykey 的值为 "value" 并使其在60秒后过期
SET mykey "value" PX 60000 # 设置 mykey 的值为 "value" 并使其在60000毫秒(即60秒)后过期

设置键为永久有效

  1. 使用PERSIST命令,PERSIST命令用于移除键的过期时间,使其变为永久有效。
1
2
3
PERSIST key
例如:
PERSIST mykey # 移除 mykey 的过期时间,使其变为永久有效

👌redis中的管道有什么用?

发表于 2025-05-11 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis中的管道有什么用?

口语化回答

好的,面试官。管道 pipeline 主要是为了解决批量查询问题而诞生的。如果说没有管道,我们查 10 个信息,就要发送 10 次命令,与 redis 进行往返 10 次的交互,这对性能的影响是很大的。如果通过管道,将 10 个命令放在一块传递给 redis,结果再一次性返回,性能会得到提升。这种思想可以用到很多地方,包括在 mysql 进行查询的时候,也可以考虑。以上。

题目解析

管道这道题还比较常问,因为在公司中还是很多场景都在使用的。比较考察你对于性能的提升优化有没有考虑,代码写的好的小伙伴,肯定会考虑到批量这一点。

面试得分点

批量,网络消耗,顺序返回

题目详细答案

Redis中的管道(Pipeline)主要用于批量执行多个命令,用来提高性能和效率。管道可以将多个命令一次性发给服务器,然后返回多个结果。

为什么会有管道

按照平时开发的过程,假设要查询 10 个用户的信息,用户的信息正好存在 redis 里面。这个时候客户端需要发送 10 次命令到服务器,服务器处理命令并返回结果,这个过程会涉及多次网络往返,尤其是在高延迟网络环境中,频繁的网络通信会降低性能。管道通过将多个命令打包在一起,一次性发送到服务器,从而减少网络往返次数,提高性能。

管道的工作原理是将多个Redis命令批量发送到服务器,而不等待每个命令的单独响应。

客户端批量发送命令:客户端将一系列Redis命令放入管道中。

一次性发送命令:客户端将所有命令一次性发送到Redis服务器。

服务器批量处理命令:Redis服务器依次处理这些命令。

批量返回结果:服务器将所有命令的结果一次性返回给客户端。

适用场景

1、 批量写入数据:如批量插入大量数据到Redis中,可以显著提高写入速度。类似 mysql 里面的批量插入。

2、 批量读取数据:如一次性获取大量键值对,可以减少网络延迟。类似 mysql 可以通过 in 的方式来查询多个数据。但是不想 mysql 那么死,redis 的管道什么命令都可以放一起。

3、 减少网络开销:在高延迟网络环境中,管道可以显著减少网络往返次数,提高性能。

Java中使用Redis管道

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
//批量发送1000个SET命令,最后执行管道中的命令并关闭连接。

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;

public class RedisPipelineExample {
public static void main(String[] args) {
// 创建Jedis对象,连接到Redis服务器
Jedis jedis=new Jedis("localhost", 6379);

// 开始管道操作
Pipeline pipeline= jedis.pipelined();

// 批量发送命令
for (int i=0; i < 1000; i++) {
pipeline.set("key" + i, "value" + i);
}

// 执行管道中的命令
pipeline.sync();

// 关闭Jedis连接
jedis.close();
}

}

特性

管道中的命令是按顺序执行的,结果也按顺序返回。管道中的某个命令失败不会影响其他命令的执行,需要注意如何处理返回的结果。

👌redis主从复制的核心原理?

发表于 2025-05-11 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis主从复制的核心原理?

口语化回答

好的,面试官。redis 的主从复制原理主要是,一开始从服务器发送同步命令,主服务器接收到之后,就会生成一个 rdb 的文件,然后传输给从服务器,从服务器接收到之后,立马进行数据的恢复。然后当主服务器再次接收到写命令的时候,会发给从服务器。这个过程是一个异步复制,主服务器不会等待结果。这样就完成了主从复制,主要的核心步骤就是这些,涉及的一个同步的点就是偏移量,主从都会维护一个偏移量的概念。来判断自己复制到哪里了,如果没有达到会继续自动复制。以上

题目解析

算是比较原理的一道题了,低年限的基本不会问到。这道题可以考察到你初始同步,增量同步的一些思想。这个同步机制,在其他场景也是基本适用的,就像是 mysql 的主从复制基本也是如此。

面试得分点

sync 命令,生成 rdb,写命令增量,缓存偏移量

题目详细答案

主从复制概念

Redis支持主从复制模式,其中一台服务器作为主服务器(Master),可以接收写请求;而一个或多个服务器作为从服务器(Slave),只能接收读请求。通过SLAVEOF命令或配置文件中设置slaveof选项,让从服务器复制主服务器的数据。

核心原理

1、发送SYNC命令:从服务器启动后,连接上主服务器发送SYNC命令,请求同步数据。

2、创建RDB文件:主服务器接收到SYNC命令后,在后台执行BGSAVE命令,生成一个RDB快照文件,这个文件包含了主服务器当前的数据状态。

3、发送RDB文件:主服务器将生成的RDB文件发送给从服务器,从服务器接收并加载该文件,更新自己的数据状态。

4、异步复制:一旦从服务器加载完RDB文件,主从复制就进入了命令传播阶段。此时,主服务器会将所有接收到的写命令发送给从服务器,确保主从数据的一致性。Redis的主从复制是异步的,这意味着主服务器在发送写命令给从服务器时,不会等待从服务器的响应,从而保证了主服务器的高可用性。

复制偏移量和缓冲区概念

主从服务器都会维护一个复制偏移量(offset),用于记录复制过程中传输的数据量。通过比较主从服务器的复制偏移量,可以判断数据是否一致。

复制积压缓冲区:主服务器维护了一个固定长度的、先进先出(FIFO)的队列作为复制积压缓冲区,用于保存最近一段时间内的写命令。当从服务器重新连接主服务器时,如果复制偏移量在复制积压缓冲区范围内,主服务器可以直接将从断线位置开始的写命令发送给从服务器,实现部分重同步。

这两个概念对于短线重连也产生了极大的影响。

当从节点与主节点的连接断开后,会自动尝试重新连接。如果从节点重新连接主节点时,发现自己的复制偏移量在主节点的复制积压缓冲区范围内,则会进行增量同步。如果复制积压缓冲区中的数据不足以完成同步,则需要重新进行全量同步。

👌redis在集群中查找key的时候,怎么定位到具体的节点

发表于 2025-05-11 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis在集群中查找key的时候,怎么定位到具体的节点?

口语化回答

这个问题主要涉及到一个哈希槽的概念,redis 集群,会将键划分为 16384 个槽。每个槽分配一个或多个节点。比如一个集群,三个节点,每个节点负责一定范围的槽。当 key 来了的时候,首先做 hash 算法获得数值后,与 16384 进行取模。得到的值就是槽的位置,然后再根据槽的编号,就可以找到对应的节点。以上。

题目解析

算是进阶点的一道题了,年限低的小伙伴不必太在意,在集群模式下,这个知识点是必须要掌握的。

面试得分点

slot 机制,取模 hash 槽,16384,节点范围机制

题目详细答案

Redis集群采用了一种哈希槽的机制用于数据存储和获取。每个 redis 集群节点会分布在一个槽范围内,我们通过对 key 进行的 hash 计算,就可以确定 key 落在哪个槽上。

哈希槽机制

Redis集群将整个键空间划分为16384个哈希槽。每个键根据其哈希值被映射到其中一个哈希槽上,每个哈希槽被分配给一个节点或多个节点(主从复制的场景)。计算过程如下:

  1. 计算哈希值:Redis使用MurmurHash算法对键进行哈希计算,得到一个整数哈希值。
  2. 映射到哈希槽:将哈希值对16384取模(即hash(key) % 16384),得到对应的哈希槽编号。
  3. 定位节点:根据哈希槽编号找到负责该哈希槽的节点。

看了上面的图,你对 slot 有了解了,那么你会产生疑问,slot 又是如何和 redis 进行关联的呢?

哈希槽分配

集群的配置时,哈希槽会被分配给不同的节点。每个节点负责一定范围的哈希槽。例如,节点A可能负责哈希槽0-5000,节点B负责哈希槽5001-10000,节点C负责哈希槽10001-16383。这样就实现了集群、节点、slot 三者联动。

查找流程

当客户端需要查找某个键时,流程如下:

  1. 计算哈希槽:客户端根据键计算出对应的哈希槽编号。
  2. 查找节点:客户端查询集群的哈希槽分配表,找到负责该哈希槽的节点。
  3. 发送请求:客户端将请求发送到对应的节点,获取或存储数据。

代码实现

一般这种都需要我们操心,很多都帮我们封装好了。

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
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import java.util.HashSet;
import java.util.Set;

public class RedisClusterExample {
public static void main(String[] args) {
// 定义集群节点
Set<HostAndPort> nodes = newHashSet<>();
nodes.add(newHostAndPort("127.0.0.1", 7000));
nodes.add(newHostAndPort("127.0.0.1", 7001));
nodes.add(newHostAndPort("127.0.0.1", 7002));

// 创建JedisCluster对象
JedisCluster jedisCluster=new JedisCluster(nodes);

// 存储键值对
jedisCluster.set("mykey", "myvalue");

// 获取键值对
String value= jedisCluster.get("mykey");
System.out.println("Value for 'mykey': " + value);

// 关闭JedisCluster连接
jedisCluster.close();
}
}

👌redis支持哪几种数据类型

发表于 2025-05-11 | 更新于 2025-09-14 | 分类于 Redis
字数统计 | 阅读时长

👌redis支持哪几种数据类型?

口语化回答

redis 的基础数据类型,主要有五种,string ,hash,list,set 和 zset。平时最常用的就是 string,可以缓存内容、做分布式锁等等,其次就是 hash,比如缓存一些对象结构的数据,hash 就比较合理。假设缓存一个个人信息,姓名,年龄,头像这些。传统的 string 需要进行序列化转 json,hash 则可以直接拿到。zset 也用过,主要是做排行榜功能,利用分数的特性进行排序。主要就是这些,以上。

题目解析

这道题比较简单,一般面试切入到 redis 的时候,会问一下这道,但是你的目的是在这五种基础数据类型中,选出钩子,让面试官继续针对某一个类型来进行问,比如你对分布式锁准备的很充分,你答 string 类型的时候,就往上面引入,你用 zset 做过排行榜,那么就 zset 上面多说一些。

面试重点词

五种类型、string 的分布式锁、hash 存储对象、zset 做排行榜。

题目详细答案

Redis支持的数据类型主要有五种。

String(字符串)

1、字符串是 Redis 中最简单和最常用的数据类型。可以用来存储如字符串、整数、浮点数、图片(图片的base64编码或图片的路径)、序列化后的对象等。

2、每个键(key)对应一个值(value),一个键最大能存储512MB的数据。

3、命令用法

1
2
SET key "value"
GET key

Hash(哈希)

1、Redis Hash是一个String类型的field和value的映射表,类似于Java中的Map<String, Object>。

2、Hash特别适合用于存储对象,如用户信息、商品详情等。

3、 每个Hash可以存储2^32 - 1个键值对。

4、命令用法

1
2
HSET user:1000 name "John"
HGET user:1000 name

List(列表)

1、 列表是一个有序的字符串集合,可以从两端压入或弹出元素,支持在列表的头部或尾部添加元素。

2、 列表最多可存储2^32 - 1个元素。

1
2
3
LPUSH mylist "world"
LPUSH mylist "hello"
LRANGE mylist 0 -1

Set(集合)

1、Set是一个无序的字符串集合,不允许重复元素。集合适用于去重和集合运算(如交集、并集、差集)。Set的添加、删除、查找操作的复杂度都是O(1)。

2、常用命令

1
2
3
SADD myset "hello"
SADD myset "world"
SMEMBERS myset

Zset(有序集合)

Zset和Set一样也是String类型元素的集合,且不允许重复的成员。有序集合类似于集合,但每个元素都会关联一个double类型的分数(score),redis正是通过分数来为集合中的成员进行从小到大的排序。

12…12<i class="fa fa-angle-right"></i>

239 日志
22 分类
30 标签
GitHub
© 2025 javayun
由 Hexo 强力驱动
主题 - NexT.Gemini