全链路压测实践
1,基础:
目标:发现系统瓶颈,进行容量规划
原则:不影响正常业务的运行
2,实施流程:
1)基础中间件开发
2)测试同学梳理核心接口准备压测脚本,根据真是情况分配调用比率等
3)业务升级与线下验证(人工点击,数据落影子库)
4)影子数据准备,运维同学进行压力机及内网负载均衡准备
5)小流量预发验证(用 jmeter 下发,数据落影子库)
6)大流量生产环境压测
3,基础组件:
1)线程变量的设置与传递:通过HandlerInterceptor拦截客户端请求,如果cookie或者querystring有压测标识则通过TransmittableThreadLocal存储压测标识,并使该拦截器要求注册在首位保证后续的处理流程能获取到正确的压测标识,TransmittableThreadLocal能够保证使用线程池时线程变量的正确传递(https://github.com/alibaba/transmittable-thread-local)。 通过Dubbo的Filter或者RequestTemplate的header传递到远程。
2)基于AOP拦截数据源:拦截MongoTemplate创建影子实例,拦截RocketMQ或ONS发送时添加UserProperties,接收后如果发现有压测标识设置线程变量
3)基于ByteBuddy拦截(Redis和Guava等不是通过容器创建的实例:Redis在方法执行前后切换Index在当前的Index上加1。Guava的处理比较特殊,压测流量导致的缓存回源将使缓存的是影子数据库的数据所以只能以正常请求为主,在Guava的load,reload执行前设置压测标识为false通过新的ThreadLocal存储原始的压测标识,在方法执行后恢复ThreadLocal中存储的值
4)基于BeanPotProcessor的封装:对使用的common-dbcp中BasicDataSource进行二次封装,实现AbstractRoutingDataSource的determineCurrentLookupKey根据当前是否为压测选择不同的数据链接
4,代码改造:
1,压测场景下打印的日志会影响数据部门各类指标的计算,代码改造判断压测场景下不打印日志
2,直接使用HashMap缓存数据的部分改造成使用Guava缓存,保证压测场景的数据不影响正常的缓存
3,去掉spring-mvc.xml中的annotation-driven配置节,否则会影响压测标识的传递
4,如果存在Filter替换为HandlerInterceptor,中间件使用了HandlerInterceptor进行拦截且排首位,所以获取到正确的压测标识也要使用HandlerInterceptor
5,数据准备:
1,还原相关的数据库,要求备份时间相同。广告投放有个特殊场景:针对费用,库存等的大量消耗的会触发Redis发消息使center更新ES可投放广告,但是Redis的pubsub不区分Index导致错误更新正常的数据,在压测时使用了更新费用为Max的方式保证ES的数据不变更。最优方案还是修改消息的发送消费使用MQ
2,存在两个系统通过Redis进行数据交换,但是redis中的数据是使用定时任务刷新方式写入不存在压测触发的场景,压测时临时开发接口手动触发。建议开发时使用调用方自己维护缓存的方式
3,ES中的数据使用elasticdump进行导出导入再处理
6,问题总结:
整体思路:压测时观察应用服务器外部数据源的RT,资源使用情况,计算型的瓶颈会导致CPU满载,外部IO型的瓶颈会导致load变高和RT增大。通过CAT查看各个接口以及外部数据源的访问RT是否随着请求量的增加而提高,如果RT有变高说明有性能问题。
1),Redis过度调用(一次发券对应活动web的10次以及engine的20左右的redis访问):
大部分数量量较小且实时性要求不高的数据能使用内存缓存异步刷新的场景没有使用内存缓存
针对用户级别的短时间限流和有效性验证可以通过粘性用户到指定机器加内存缓存的方式解决
2),缓存粒度或者缓存层级不正确:
活动web调用center获取活动奖项,返回的是排序后的奖项,但是缓存的是未排序的数据库查询又在内存进行一次排序。
奖项或导航页的信息通过内存缓存在center层,web层到center存在大量的调用获取缓存的信息,但是针对实时性要求不高且数据量不大的场景应该使用内存缓存直接缓存在web层,如果实时性要求较高且数据量不大可以使用内存缓存加MQ变更通知的方式
3),设计缺陷:
计费系统在广告计费后发送变更消息由投放系统校验广告配置包消耗和时段限制,如果投放系统的实例多则会导致同时多机同时运算,实际应该由计费系统计算后发送MQ投放系统接受后直接更新内存缓存
活动web的getToken接口在调用量大的场景下导致cpu满负荷,该接口在每次参与抽奖时生成token和一段JS组合加密后返回,加密操作运算量很大。优化后将JS加密后缓存每次只加密生成的token,性能提升10倍左右。
4),调用链路优化:
广告发券时每次都需要调用商品中心取券,从业务上分析对于占大多数的推广链接类型的广告没有券控制,优化后对商品中心的调用量减少90%。