分布式微服务系统架构第103集:高性能落地实践,Kafka,分布式锁,缓存双写架构

105 阅读6分钟

加群联系作者vx:xiaoda0423

仓库地址:webvueblog.github.io/JavaPlusDoc…

1024bat.cn/

JVM 维度调优说明

优化项原因
避免频繁 new HashMap()提前预估容量,减少扩容频率
复用 SimpleDateFormat避免创建新对象,减少 GC 压力
使用 StringBuilder替代 += 字符串拼接,提升效率
Bean 拷贝中使用 getNullPropertyNames防止覆盖有效字段,提高安全性
if-else 简化与空判断提前提前返回可减少嵌套和冗余分支

优化点说明:

优化项描述好处
ConcurrentHashMap.newKeySet() 替代 HashSet使用线程安全的集合处理并发场景防止高并发下的集合并发异常,提高稳定性
final static 常量KEYSPACE, PRE_TABLE_NAME, Logger避免每次访问创建,提高访问效率,便于 JVM 编译器优化
合并 checkTableExist / checkBackupTableExistcheckAndCreateTable避免重复逻辑代码精简、易维护,提高可读性
优化字段命名:tableNa -> tableNameiData -> data统一命名规范提高可读性、代码整洁度
字段 @Autowired 改为 private遵循封装性和代码规范减少暴露字段,提高类的封装性
日志组件 Logger 加上 final常规建议提高可读性和线程安全性标识
保留注释但更清晰仅保留核心注释代码更清爽,降低维护成本
异步写入前不记录日志如非调试场景可省略日志避免日志IO影响性能,可由 AOP 全局处理日志
SimpleDateFormat 每次创建没有复用静态变量避免线程不安全问题(局部变量线程安全)

⚙️ JVM 层面性能优化说明:

调整点JVM 层影响性能收益
使用局部变量的 SimpleDateFormat避免静态共享导致线程安全问题,减少锁竞争线程安全、性能提升
避免频繁创建 String 拼接语句(如 tableName使用 final 减少 GC 压力更少的临时对象,GC 频率降低
ConcurrentHashMap 替代 HashSet减少 synchronized 同步代码块并发效率提升,锁竞争减少
精简方法体逻辑JVM JIT 更容易进行方法内联优化增强运行期性能
使用 ((Id,day), xxx, xxx, xxx) 的复合主键,有利于 Cassandra 的查询性能(分区+排序)

使用特点总结

特点说明
📅 表结构适用于 按月分表 场景(如车载数据、日志、轨迹)
🚀 搭配 insertAsync() 等异步写入,适合 高并发写入 的场景
📊 表结构固定,便于数据仓库处理、日志追踪、消息还原等场景
🔧 灵活生成、可扩展性强,可未来支持版本号、标签等字段

精简优化点说明

优化点原因/好处
✅ 用 String.valueOf() 替代 toString()防止 NPE(更健壮)
✅ 使用 equals("xxx") 而非反过来防止 eventId 为 null 时抛异常
✅ 提前 try,再 finally ack.acknowledge()无论是否异常,都提交 offset,避免消费重复
logger.info("xxx {}", var)使用参数化日志格式,避免无谓字符串拼接(更高效)

AppListener 用于系统启动后的初始化工作

// 启动 TCP 服务监听线程
new Thread(() -> {
    try {
        Server.bind(serverConfig.getPort());
        logger.info("TCP Server started on port {}", serverConfig.getPort());
    } catch (Exception e) {
        logger.error("Server 启动失败", e);
    }
}, "Server-Thread").start();

调优与说明

优化点说明
Thread 命名方便在 JVM 工具如 JVisualVM 中观察线程
isInterrupted() 检查避免无限死循环,增加线程可控性(便于后期 shutdown hook)
✅ 日志等级优化同步耗时 改为 debug,生产环境降低无关日志开销
System.exit(1)明确非正常退出
  • ✅ 可用线程池(如 Spring @Async + 配置线程池)替代裸线程。

  • ✅ 将线程改为 守护线程setDaemon(true))可选优化。

  • ✅ 后期支持优雅关闭,可以整合 Spring SmartLifecycle

配置线程池(推荐)

@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean("customTaskExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(4);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("startup-task-");
        executor.initialize();
        return executor;
    }
}

Thread.currentThread().isInterrupted()

检查当前线程是否已被中断(非清除标志位的查询)。

  • 返回 true:线程被中断。
  • 返回 false:线程未被中断。

🧠 不会清除中断标志位,适合在循环中定期检查是否应退出:

while (!Thread.currentThread().isInterrupted()) {
    // do work
}

Thread.currentThread().interrupt()

设置中断标志位为 true,告诉线程:“你该停下来了”。

通常不是自己调用自己,而是从外部线程对目标线程进行中断通知:

Thread t = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // do something
    }
});
t.start();

// 某些时刻需要中断它
t.interrupt(); // 设置中断标志位为 true

❗和 Thread.interrupted() 的区别

方法作用是否清除中断标志
Thread.currentThread().isInterrupted()查询当前线程是否被中断❌ 不清除
Thread.interrupted()查询并清除当前线程的中断状态✅ 清除
Thread.interrupt()设置目标线程中断状态-

⚠️ 中断不会立刻停止线程!

中断是一种“协商式停止”:

  • 线程收到中断信号后,需要自行检查中断标志并优雅退出
  • 否则它会继续运行。

🔄 结合 sleep()wait()join()

如果线程在执行这些方法时被中断,会抛出 InterruptedException,同时中断标志会被清除,需要手动再次调用 interrupt()

try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // 保留中断标志
    log.warn("线程睡眠中被中断");
}

✅ 实用模板

public void runTask() {
    while (!Thread.currentThread().isInterrupted()) {
        try {
            // 任务逻辑
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢复中断状态
            break; // 或 return,退出循环
        }
    }
    log.info("任务已中断并退出");
}

Kafka + 分布式锁 + 缓存双写架构

🔧 架构概览图

Kafka -> Spring KafkaListener
             |
             v
    分布式锁(Redisson)
             |
             v
      数据库查询 + 缓存双写(防穿透 + 延迟双删等)
             |
             v
         ack/commit offset

✅ Kafka 消费 + Redisson 分布式锁 + 缓存双写

消息处理类(以订单为例)

@Slf4j
@Component
@RequiredArgsConstructor
public class OrderKafkaConsumer {

    private final RedissonClient redissonClient;
    private final OrderService orderService;
    private final CacheService cacheService;
    private final BloomFilter<String> bloomFilter;

    private static final String LOCK_PREFIX = "lock:order:";

    @KafkaListener(topics = "order-events", containerFactory = "kafkaListenerContainerFactory")
    public void consume(ConsumerRecord<String, String> record, Acknowledgment ack) {
        String orderId = record.key();
        String message = record.value();

        if (!bloomFilter.mightContain(orderId)) {
            log.warn("BloomFilter reject orderId={}", orderId);
            ack.acknowledge(); // 直接ack,避免重复消费
            return;
        }

        String lockKey = LOCK_PREFIX + orderId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                // 业务处理逻辑:缓存双写
                handleOrderMessage(orderId, message);
            } else {
                log.warn("Order {} is already being processed.", orderId);
            }
        } catch (Exception e) {
            log.error("Kafka消费失败: {}", e.getMessage(), e);
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
            ack.acknowledge(); // 无论成功失败都提交offset,避免阻塞(失败后消息需通过日志+补偿机制处理)
        }
    }

    private void handleOrderMessage(String orderId, String message) {
        // 1. 查询数据库
        Order order = orderService.findById(orderId);
        if (order == null) {
            log.warn("Order not found: {}", orderId);
            return;
        }

        // 2. 缓存写入(双写)
        cacheService.saveOrderToCache(order);

        // 3. 异步延迟双删逻辑(可选)
        CompletableFuture.delayedExecutor(5, TimeUnit.SECONDS).execute(() -> {
            cacheService.deleteOrderCache(orderId);
        });

        log.info("Order processed and cached: {}", orderId);
    }
}

✅ 关键组件说明

Redisson 分布式锁配置

@Bean
public RedissonClient redissonClient() {
    Config config = new Config();
    config.useSingleServer().setAddress("redis://localhost:6379");
    return Redisson.create(config);
}

缓存双写(CacheService

public interface CacheService {
    void saveOrderToCache(Order order);
    void deleteOrderCache(String orderId);
}
@Service
public class RedisCacheService implements CacheService {

    private final RedisTemplate<String, Object> redisTemplate;

    public void saveOrderToCache(Order order) {
        String key = "order:" + order.getId();
        redisTemplate.opsForValue().set(key, order, Duration.ofMinutes(10));
    }

    public void deleteOrderCache(String orderId) {
        String key = "order:" + orderId;
        redisTemplate.delete(key);
    }
}

✅ 可选增强

功能说明
布隆过滤器防止无效缓存穿透,提前拦截
延迟双删防止数据库数据修改后,旧缓存再次被设置
死信队列 (DLQ)消费失败后持久化并异步补偿
Tracing + Metrics接入链路追踪、埋点监控 Kafka 消费性能
消费重试机制使用 Spring Retry 或 SeekToCurrentErrorHandler 重试消费