Java应用系统的稳定性保证
(1)代码层面
健壮性编码:进行充分的参数校验、边界条件判断、空指针规避。
异常处理:不要吞掉异常(空的catch块),要合理记录日志并向上抛出或进行转换。使用自定义异常来区分业务异常和系统异常。
资源管理:使用`try-with-resources`确保连接(数据库、网络、文件)等资源被正确关闭,避免泄漏。
避免内存泄漏:谨慎使用静态集合,及时清理无用的对象引用,注意监听器的注销。
(2)设计与架构层面
微服务容错:采用Spring Cloud Alibaba Sentinel、Hystrix等组件实现熔断、降级、限流。防止因某个依赖服务故障导致整个系统雪崩。
熔断:依赖服务不可用时,快速失败,避免积压请求。
降级:服务压力过大时,关闭非核心功能,保障核心业务畅通。
限流:控制单位时间内的请求量,保护自身和下游系统。
超时与重试:为所有外部调用(HTTP、RPC、数据库)设置合理的超时时间,并配合重试机制(如Spring Retry),但要注意重试的幂等性。
解耦与异步:使用消息队列(如Kafka)将耗时操作异步化,削峰填谷,提高响应速度和吞吐量。
(3)运维与监控层面
全面监控:腾讯云监控系统等。
应用监控:使用APM工具(如SkyWalking, Pinpoint)监控JVM(GC、内存、线程)、接口响应时间、QPS、错误率。
系统监控:监控服务器的CPU、内存、磁盘、网络。
业务监控:监控核心业务指标,如订单量、支付成功率。
日志聚焦化:使用ELK(Elasticsearch, Logstash, Kibana)或类似方案收集和查询日志,便于快速定位问题。
压力测试与容量规划:在上线前进行全链路压测,了解系统瓶颈,并据此规划合理的机器资源。
(4)流程与管理层面
代码审查:保证代码质量,分享知识。
自动化测试:建立完善的单元测试、集成测试、端到端测试流水线。
灰度发布:先发布一小部分机器,观察无误后再全量发布,降低故障影响面。
Kafka异常消费的处理
Kafka消费端的稳定性核心在于如何优雅地处理消费失败。
(1)自动提交 vs 手动提交
自动提交:简单但不安全。可能在消息处理完之前就提交了偏移量,如果此时消费者崩溃,消息会丢失。
手动提交:生产环境推荐。在处理完业务逻辑后,再手动提交偏移量。这保证了“至少一次”投递。
(2)重试机制
内部重试:在消费者内部,当单条消息处理失败时,进行有限次数的重试(如3次)。适用于由网络抖动等导致的瞬时故障。
@KafkaListener(topics = "my-topic")
public void consume(String message, Acknowledgment ack) {
int maxRetries = 3;
for (int i = 0; i < maxRetries; i++) {
try {
// 处理业务逻辑
processMessage(message);
ack.acknowledge(); // 手动提交
break; // 成功则跳出循环
} catch (BusinessException e) {
// 业务异常,记录日志并进入重试或死信
if (i == maxRetries - 1) {
// 发送到死信队列
sendToDlt(message, e);
ack.acknowledge(); // 原消息依旧要确认,避免阻塞
}
} catch (Exception e) {
// 系统异常,可能需要进行重试
if (i == maxRetries - 1) {
// 最终失败处理
ack.acknowledge();
}
}
}
}
(3)死信队列(DLQ – Dead Letter Queue)
处理那些经过多次重试后依旧失败的消息。将这些消息投递到一个专门的Topic(DLQ)中,避免它们阻塞主业务Topic的消费。
Spring-Kafka提供了 `
DeadLetterPublishingRecoverer` 和 `DefaultErrorHandler` 的实现。
@Configuration
public class KafkaConfig {
@Bean
public ConcurrentKafkaListenerContainerFactory<?, ?> kafkaListenerContainerFactory(
ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
ConsumerFactory<Object, Object> kafkaConsumerFactory,
KafkaTemplate<Object, Object> template) {
ConcurrentKafkaListenerContainerFactory<Object, Object> factory =
new ConcurrentKafkaListenerContainerFactory<>();
configurer.configure(factory, kafkaConsumerFactory);
// 设置重试机制
DefaultErrorHandler errorHandler = new DefaultErrorHandler(
new DeadLetterPublishingRecoverer(template), // 发布到死信
new FixedBackOff(1000L, 2) // 重试2次,间隔1秒
);
factory.setCommonErrorHandler(errorHandler);
return factory;
}
}
后续处理:需要有独立的服务或人工来监控和处理DLQ中的消息,分析失败缘由并进行修复或补偿。
(4)消费幂等性
确保即使同一条消息被消费多次,也不会产生负面效果。这是实现可靠消息系统的基石。可以通过数据库唯一约束、乐观锁、或记录已处理消息ID等方式实现。
MySQL, ES, Redis, MongoDB 数据与事务一致性
没有银弹,需要根据业务场景在一致性、可用性、性能之间做权衡。核心思想:放弃强一致性,追求最终一致性,通过补偿等手段解决中间状态。
(1)单一数据源(如MySQL)
本地事务:使用Spring的`@Transactional`保证ACID。
多数据源:可以使用JTA或Seata等分布式事务框架实现强一致性,但性能损耗大。
(2)多数据源之间的同步(如:MySQL -> ES/Redis)
双写:在业务代码中,先写数据库,再写ES/Redis。非原子操作,再写失败会导致数据不一致。
优化:先写DB后删缓存,这是Cache-Aside模式的推荐做法。写数据时,先更新数据库,然后删除(而非更新)缓存。下次读请求会自动从DB加载最新数据到缓存。
异步重试:如果删除缓存失败,将删除操作放入消息队列异步重试,确保最终一致。
(3)基于Binlog的CDC
最可靠、对业务无侵入的方案。
通过Canal、Debezium等工具监听并解析MySQL的binlog,将数据变更事件发送到Kafka,然后由消费者将数据同步到ES、Redis、MongoDB等。
优点:解耦业务逻辑,保证数据顺序,高可靠性。
流程:`MySQL -> Binlog -> CDC Connector -> Kafka -> Consumer -> ES/Redis/MongoDB`
(4)特定场景的“事务”
Redis Lua脚本:可以将多个操作封装在一个Lua脚本中执行,保证原子性。
MongoDB多文档事务:在4.0+版本支持副本集的多文档事务,4.2+支持分片集群事务。用法类似传统数据库事务。
ES:本身不支持跨索引的事务。其“事务”主要通过版本号控制来实现乐观锁,保证单个文档的原子更新。
(5)最终一致性的补偿机制
当同步过程失败时,必须有手段来修复数据。
对账:定期(如每天凌晨)跑Job,比对MySQL和ES/Redis中的核心数据,将不一致的记录修复。
校验与修复:提供管理后台,允许运营或开发人员通过某个ID手动触发数据同步。
TTL+自愈:为缓存数据设置TTL,短期的不一致可以通过缓存过期后重新加载来自动修复。
注册中心下线对业务的影响
注册中心下线不会导致正在进行的业务请求失败,但会严重影响后续服务的发现、更新和未来的调用。系统会进入一个“静默”的僵化状态。
(1)对服务提供者的影响
服务提供者会定期向注册中心发送心跳,以声明“我还活着”。
注册中心下线期间:
新服务实例无法注册:新启动的微服务实例无法在注册中心注册自己,因此不会被其他服务发现和调用。这会导致水平扩展失效。
健康检查中断:注册中心无法接收现有服务实例的心跳,但它不会立即将这些服务剔除。
注册中心恢复后:
所有服务实例会重新注册和发送心跳,状态会逐渐恢复正常。
如果注册中心下线时间过长,超过了配置的心跳超时时间,那么在恢复后,注册中心可能会认为那些在此期间无法上报心跳的实例是**不健康的**并将其剔除。但实际上这些实例本身可能是健康的。
(2)对服务消费者的影响
服务消费者会从注册中心拉取服务列表并本地缓存,同时也会定期更新。
服务发现:
本地缓存救命:大多数客户端(如Spring Cloud Netflix的Ribbon、Nacos Client、Spring Cloud Alibaba)都会在本地缓存一份服务提供者列表。
短期内无影响:在注册中心下线后的短时间内,消费者会继续使用本地缓存的服务列表进行调用,所有基于现有缓存的服务调用都能正常进行。
服务列表更新:
无法获取新实例:消费者无法从注册中心获取到最新的服务列表。因此任何新上线的服务提供者实例都不会被消费者感知到,流量不会被分发到这些新实例上。
无法感知实例下线:这是最危险的情况。如果某个服务提供者实例崩溃或主动下线了,由于注册中心已经下线,它无法通知消费者“这个实例已失效”。消费者本地缓存的服务列表里依旧包含这个已经下线的实例。
当消费者尝试调用这个已下线的实例时,就会发生连接超时或调用失败,导致部分请求失败。
(3)对配置中心的影响
应用无法动态获取最新配置:配置的动态刷新功能会失效。
应用启动可能失败:如果应用启动时需要从配置中心拉取必要的配置,那么此时应用将无法启动。
(4)如何提高注册中心鲁棒性?
客户端缓存与负载均衡:
确保客户端有完整的服务列表缓存。这是抵御注册中心故障的第一道防线。
Ribbon等组件默认就有缓存。
注册中心集群高可用:
绝不能单点部署。必须搭建注册中心集群(如Nacos集群、Eureka集群),通过多个节点来避免单点故障。
合理的客户端配置:
拉取间隔与缓存:适当调整客户端从注册中心拉取服务列表的时间间隔。不要太频繁,增加注册中心压力;也不要太慢,影响发现速度。
心跳超时时间:适当调大服务实例的心跳超时时间,让注册中心在短暂故障期间不会轻易将健康的服务实例剔除。例如,Eureka的 `
lease-expiration-duration-in-seconds` 配置。
服务调用容错:
在消费者端使用熔断器,当调用某个失败实例时,熔断器会快速将其熔断,避免持续请求超时。这可以与本地缓存结合,自动从负载均衡列表中剔除故障节点(尽管是暂时的)。Hystrix/Sentinel在这方面作用巨大。
手动应急措施:
在极端情况下,可以思考为消费者配置静态服务列表,绕过注册中心,直接指向健康的服务提供者实例,作为一种临时的应急方案。






![[C++探索之旅] 第一部分第十一课:小练习,猜单词 - 鹿快](https://img.lukuai.com/blogimg/20251015/da217e2245754101b3d2ef80869e9de2.jpg)










暂无评论内容