Java 开发的微信公众号服务端生产环境中的两个大坑(杨武兵)
ABSTRACT
2018-03 文章。真实生产事故复盘:nginx + 10 个 Tomcat 集群的公众号服务,因未压测、组件全用默认参数,访问量上涨后两台实例反复宕机。两个根因:①accessToken/jsapiTicket 存内存未共享 + synchronized 锁 + 失败重试退避导致线程大面积 BLOCKED;②httpclient 连接池与超时全默认值导致线程 WAITING 堆积。
核心要点
- 事故画像:tomcat 线程数爬升至上限(1000),jstack 导出线程栈发现大量 BLOCKED 与 WAITING 线程;同集群其他实例正常 → 排除流量因素。
- 坑一(BLOCKED):weixin-java-tools 的 getJsapiTicket 用 synchronized 全局锁防并发刷新;但 accessToken 存单机内存、10 个实例各自刷新互相顶号 → accessToken 频繁失效 → 接口日限 2000 次被打爆 → 获取失败重试 3 次且退避翻倍,持锁线程执行极慢,全部线程堵在锁上。
- 解法一:accessToken 与 jsapiTicket 改为 Redis 共享存储,集群只由一处刷新——BLOCKED 消失。
- 坑二(WAITING):httpclient 4.3.5 用 HttpClients.createDefault() 全默认:maxConnTotal=20、maxConnPerRoute=2(对单一路由微信服务器太抠)、三个超时均为 -1(近乎永不超时);微信接口平均耗时 300ms,高并发下线程全在等待连接池。
- 解法二:按生产实际配置 socketTimeout=2000、connectTimeout=2000、connectionRequestTimeout=500、maxConnPerRoute=300、maxConnTotal=300。
- 五条教训:默认配置值在恶劣环境可能是致命的;微信接口性能差、网络差时问题放大;集群环境 token 类凭据必须共享存储;网络连接超时必须设置且不能太大;生产排障依赖充足的日志、监控与资源使用信息。
关键实体与概念
accessToken、jsapiTicket、synchronized、jstack、BLOCKED/WAITING、httpclient 连接池、maxConnPerRoute、超时配置、Redis 共享存储、weixin-java-tools
关联概念
来源回溯
- 原始文件:
raw/ip/wechat_articles/java开发的微信公众号服务端生产环境中的两个大坑 - 杨武兵.md(2018-03-24)
时效性评估
- 仍有效:「第三方 SDK 默认参数不可信」「集群环境凭据必须集中存储」「超时必须显式设置」「先监控后定位」是穿越技术栈的生产铁律。
- 已过时:weixin-java-tools 已演进为 WxJava(weixin-java-tools 改名,当前 4.x),SDK 内部已支持 Redis 等分布式配置存储;httpclient 4.x 已被 HttpClient 5 / OkHttp / WebClient 替代,但连接池与超时的调参逻辑完全同构;accessToken 机制(7200 秒过期、互踢、调用限额)至今仍是微信生态的稳定约束。