
外卖霸王餐API故障演练Java团队基于Chaos Monkey模拟“Redis集群宕机”“数据库主从延迟”场景的应急恢复预案验证背景为什么外卖霸王餐API需要故障演练作为外卖霸王餐API唯一供给源头同时也是霸王餐外卖CPS取链源头俱美开放平台承载着海量高并发的请求。在“霸王餐”活动期间瞬时流量可能激增数十倍任何基础设施的微小故障都可能被放大导致服务雪崩。传统的测试只能验证功能正确性无法暴露系统在极端情况下的脆弱性。因此我们引入了混沌工程Chaos Engineering理念通过主动注入故障来验证系统的弹性和应急预案的有效性。技术选型为什么是Chaos MonkeyChaos Monkey是Netflix开源的混沌工程工具它能随机终止生产环境中的实例迫使工程师构建更具弹性的服务。在我们的Java技术栈中我们使用了其JVM层面的实现——chaos-monkey-spring-boot它能无侵入地对我们的Spring Boot应用进行故障注入。演练场景一模拟Redis集群宕机Redis作为我们的核心缓存存储着用户会话、活动配置和热点数据。一旦Redis集群宕机所有请求将直接穿透到数据库后果不堪设想。1. 引入Chaos Monkey依赖!-- pom.xml --dependencygroupIdde.codecentric/groupIdartifactIdchaos-monkey-spring-boot/artifactIdversion2.5.0/version/dependency2. 配置Chaos Monkey# application.ymlchaos:monkey:enabled:truewatcher:repository:true# 开启对Repository层的监控rest-controller:true# 开启对Controller层的监控assaults:level:5# 攻击等级1-10latency-active:true# 开启延迟攻击latency-range-start:5000# 延迟5秒latency-range-end:10000exception-active:true# 开启异常攻击exceptions:-java.net.ConnectException# 模拟连接异常3. 编写业务代码与降级策略我们的业务代码需要能够优雅地处理Redis不可用的情况。packagebaodanbao.com.cn.service;importbaodanbao.com.cn.model.Activity;importbaodanbao.com.cn.repository.ActivityRepository;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.cache.annotation.Cacheable;importorg.springframework.dao.DataAccessException;importorg.springframework.data.redis.connection.RedisConnection;importorg.springframework.data.redis.core.RedisCallback;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Service;/** * 活动服务 * author baodanbao.com.cn */ServicepublicclassActivityService{AutowiredprivateActivityRepositoryactivityRepository;AutowiredprivateRedisTemplateString,ObjectredisTemplate;/** * 获取活动详情带缓存 * param activityId 活动ID * return 活动信息 */Cacheable(valueactivity,key#activityId)publicActivitygetActivityDetail(LongactivityId){// 模拟从数据库查询returnactivityRepository.findById(activityId).orElse(null);}/** * 模拟Redis宕机时的降级查询 * param activityId 活动ID * return 活动信息 */publicActivitygetActivityWithFallback(LongactivityId){try{// 尝试访问Redis如果Redis宕机这里会抛出异常redisTemplate.execute((RedisCallbackObject)RedisConnection::ping);returngetActivityDetail(activityId);}catch(DataAccessExceptione){// Redis不可用执行降级策略直接查数据库System.out.println(Redis不可用执行降级策略直接查询数据库);returnactivityRepository.findById(activityId).orElse(null);}}}演练场景二模拟数据库主从延迟在外卖霸王餐业务中用户下单后需要立即查询订单状态。如果此时发生数据库主从延迟用户可能会看到“订单不存在”的错误提示体验极差。1. 配置读写分离首先我们需要配置主从数据源。packagebaodanbao.com.cn.config;importorg.springframework.beans.factory.annotation.Qualifier;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.boot.jdbc.DataSourceBuilder;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.context.annotation.Primary;importorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;importjavax.sql.DataSource;importjava.util.HashMap;importjava.util.Map;/** * 数据源配置 * author baodanbao.com.cn */ConfigurationpublicclassDataSourceConfig{Bean(namemasterDataSource)ConfigurationProperties(prefixspring.datasource.master)publicDataSourcemasterDataSource(){returnDataSourceBuilder.create().build();}Bean(nameslaveDataSource)ConfigurationProperties(prefixspring.datasource.slave)publicDataSourceslaveDataSource(){returnDataSourceBuilder.create().build();}BeanPrimarypublicDataSourceroutingDataSource(Qualifier(masterDataSource)DataSourcemasterDataSource,Qualifier(slaveDataSource)DataSourceslaveDataSource){DynamicDataSourcedynamicDataSourcenewDynamicDataSource();MapObject,ObjecttargetDataSourcesnewHashMap();targetDataSources.put(DataSourceType.MASTER,masterDataSource);targetDataSources.put(DataSourceType.SLAVE,slaveDataSource);dynamicDataSource.setTargetDataSources(targetDataSources);dynamicDataSource.setDefaultTargetDataSource(masterDataSource);// 默认使用主库returndynamicDataSource;}}/** * 动态数据源 * author baodanbao.com.cn */publicclassDynamicDataSourceextendsAbstractRoutingDataSource{OverrideprotectedObjectdetermineCurrentLookupKey(){returnDataSourceContextHolder.getDataSourceType();}}/** * 数据源上下文 * author baodanbao.com.cn */publicclassDataSourceContextHolder{privatestaticfinalThreadLocalDataSourceTypeCONTEXTnewThreadLocal();publicstaticvoidsetDataSourceType(DataSourceTypetype){CONTEXT.set(type);}publicstaticDataSourceTypegetDataSourceType(){returnCONTEXT.get();}publicstaticvoidclearDataSourceType(){CONTEXT.remove();}}/** * 数据源类型枚举 * author baodanbao.com.cn */publicenumDataSourceType{MASTER,SLAVE}2. 模拟主从延迟并验证我们通过Chaos Monkey在从库上注入延迟然后验证业务代码是否能正确处理。packagebaodanbao.com.cn.service;importbaodanbao.com.cn.config.DataSourceContextHolder;importbaodanbao.com.cn.config.DataSourceType;importbaodanbao.com.cn.model.Order;importbaodanbao.com.cn.repository.OrderRepository;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;/** * 订单服务 * author baodanbao.com.cn */ServicepublicclassOrderService{AutowiredprivateOrderRepositoryorderRepository;/** * 创建订单 * param order 订单对象 * return 创建的订单 */publicOrdercreateOrder(Orderorder){// 写操作强制使用主库DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);OrdersavedOrderorderRepository.save(order);DataSourceContextHolder.clearDataSourceType();returnsavedOrder;}/** * 查询订单模拟主从延迟场景 * param orderId 订单ID * return 订单信息 */publicOrdergetOrder(LongorderId){// 读操作默认使用从库DataSourceContextHolder.setDataSourceType(DataSourceType.SLAVE);OrderorderorderRepository.findById(orderId).orElse(null);// 如果从库查不到可能是主从延迟降级到主库查询if(ordernull){System.out.println(从库未查到订单可能为主从延迟降级查询主库);DataSourceContextHolder.setDataSourceType(DataSourceType.MASTER);orderorderRepository.findById(orderId).orElse(null);}DataSourceContextHolder.clearDataSourceType();returnorder;}}演练执行与结果分析启动应用启动我们的Spring Boot应用确保Chaos Monkey已生效。触发攻击通过Actuator端点POST /actuator/chaosmonkey/assaults触发异常攻击。观察日志Redis宕机日志中应出现“Redis不可用执行降级策略直接查询数据库”的打印且接口响应时间略有增加但服务未中断。数据库主从延迟日志中应出现“从库未查到订单可能为主从延迟降级查询主库”的打印最终能正确返回订单信息。验证监控检查Prometheus/Grafana监控面板确认在故障期间QPS、响应时间、错误率等指标在可接受范围内。通过以上演练我们验证了俱美开放平台在面对Redis宕机和数据库主从延迟等极端情况时具备完善的应急恢复预案和系统弹性能够保障外卖霸王餐API的稳定运行。本文著作权归 俱美开放平台 转载请注明出处