摘要: 我们开发的公众号,由于将功能开发完毕后,未对服务进行压力测试,因此用到的组件中的参数值全是默认的,服务上线后一段时间运行得倒没什么问题,随着服务得访问量增加,一些多线程并发的问题就逐步暴露出来了,有的问题还非常严重。

背景

本文的背景是一个用java开发的微信公众号服务端的业务应用,使用的java开发包是weixin-java-tools。该系统的部署结构式nginx+10个tomcat实例的集群。
上线一段时间后,业务运营人员在微信公众号上做了几个活动,系统的访问量增加了一些。就陆陆续续暴露了一些问题,而这些问题的造成的危害还非常大,其中有2个tomcat实例运行一段时间后就会无法提供服务了。下面就详细介绍这个问题。

问题描述

某天我们的程序员小马经常接到几个短信报警说是2台tomcat实例无法提供服务了,他就只能重启服务器,但是过几十分钟后,又会出现这样的问题,他只能痛苦得一遍一遍得重启tomcat服务器,最终实在是郁闷就找到我帮他一起看看到底是什么原因。

查看jvm监控

我经过查看监控后,查看到了这样的异常现象。

说明一下:上图中的tomcat的线程最大数配置的是1000,因此这个tomcat已经达到了最大线程数(其中多余的线程是jvm自启动的一些线程以及应用程序其它的代码启动的一些线程)。而图中出现的拐点是因为小马哥重启了tomcat,但是过段时间又会逐步上升。

查看线程栈列表

查看其它的正常的tomcat线程比较稳定,它们的线程数都在一个稳定状态,而这些tomcat是负载均衡的状态,它们的访问量应该是差不多的,因此这2个tomcat的线程如此之多,不是因为访问量太高,肯定还有其它的愿意,因此使用jstack将线程栈导出来,发现有大量的BLOCKED和WAITING状态的线程。
BLOCKED状态线程

"http-1601-1000" daemon prio=10 tid=0x00007fb6709b1000 nid=0x673d waiting for monitor entry [0x00007fb604b0b000]java.lang.Thread.State: BLOCKED (on object monitor)at me.chanjar.weixin.mp.api.WxMpServiceImpl.getJsapiTicket(WxMpServiceImpl.java:136)- waiting to lock <0x00000007402d9a28> (a java.lang.Object)at com.jd.ql.cun.web.controller.CommonController.getSignature(CommonController.java:63)at sun.reflect.GeneratedMethodAccessor260.invoke(Unknown Source)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)at java.lang.reflect.Method.invoke(Method.java:597)at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:212)at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:126)

WAITING状态线程

"http-1601-381" daemon prio=10 tid=0x00007f1fe827f800 nid=0x27f5 waiting on condition [0x00007f1fa03c1000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for  <0x00000007f9843b10> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158)at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:1987)at org.apache.http.pool.PoolEntryFuture.await(PoolEntryFuture.java:133)at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:282)at org.apache.http.pool.AbstractConnPool.access$000(AbstractConnPool.java:64)at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:177)at org.apache.http.pool.AbstractConnPool$2.getPoolEntry(AbstractConnPool.java:170)at org.apache.http.pool.PoolEntryFuture.get(PoolEntryFuture.java:102)at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.leaseConnection(PoolingHttpClientConnectionManager.java:244)at org.apache.http.impl.conn.PoolingHttpClientConnectionManager$1.get(PoolingHttpClientConnectionManager.java:231)at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:173)at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:195)at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:86)at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:108)at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:106)at me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor.execute(SimpleGetRequestExecutor.java:36)at me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor.execute(SimpleGetRequestExecutor.java:20)at com.jd.ql.cun.web.wx4jsdk.JdWxTestSupportMpServiceImpl.oauth2getAccessTokenExtension(JdWxTestSupportMpServiceImpl.java:91)at com.jd.ql.cun.web.controller.WeixinSecurityController.getOpenId(WeixinSecurityController.java:111)

问题分析及解决

BLOCKED状态线程

根据线程中的信息找打锁住行所在的源代码,继续追踪该行的源代码如下:

public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {if (forceRefresh) {wxMpConfigStorage.expireJsapiTicket();}if (wxMpConfigStorage.isJsapiTicketExpired()) {synchronized (globalJsapiTicketRefreshLock) {if (wxMpConfigStorage.isJsapiTicketExpired()) {String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi";String responseContent = execute(new SimpleGetRequestExecutor(), url, null);JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();String jsapiTicket = tmpJsonObject.get("ticket").getAsString();int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();wxMpConfigStorage.updateJsapiTicket(jsapiTicket, expiresInSeconds);}}}return wxMpConfigStorage.getJsapiTicket();}

在代码“synchronized (globalJsapiTicketRefreshLock) {“处使用了synchronized 同步锁,对全局共享对象globalJsapiTicketRefreshLock进行了加锁操作,主要是防止多个线程同时对jsapiTicket进行更新操作。

既然大量的线程阻塞在该处,那说明有的线程在执行同步块中的代码非常慢,而其它的线程都在等待该线程释放锁,因此越来越多的线程都阻塞该处。问题就出在该代码处。继续分析该处代码发现了一个比较严重的坑,描述如下:

  • 在微信中调用api都需要accessToken,调用jsapi需要jsApiTicket。详见http://mp.weixin.qq.com/wiki/2/88b2bf1265a707c031e51f26ca5e6512.html
  • accessToken的机制是每个7200毫秒会过期,并且若重新获取则上次获取的会过期。
  • 本系统是在10个tomcat实例的集群环境下面。
  • 本系统中的accessToken是存储在内存中的,多个tomcat集群的值无法共享。
  • 多个tomcat集群都会经常获取,因此导致accessToken经常过期。
  • 获取accessToken接口的调用次数有限制,每日2000次。
  • 若达到接口获取上线,则无法获取accessToken,导致获取accessToken始终失败。
  • 代码块中有失败重试默认3次的机制,而且每次冲时候会暂停线程1秒,且暂停时间每次增加一倍。
  • 因此会某个线程会在该处执行时间非常长,导致锁长期被占用,其它线程阻塞时间较长。

解决方案

重新实现accessToken和jsApiTicket存储方案,将其存储在共享的redis服务上。

修改上线后,BLOCKED线程消失了,但是依旧有很多WAITING状态的线程,因此继续分析该状态的代码。

WAITING状态线程

分析线程栈中的代码”at org.apache.http.pool.PoolEntryFuture.await(PoolEntryFuture.java:133)”经过查看源码发现是因为调用微信api使用了java的组件httpclient,如本文中项目使用的是httpclient4.3.5。
而httpclient为了复用http连接,使用了连接池技术,该处的等待线程就是在等待从连接池中获得连接,那有可能是连接池中连接不够,或者某些线程占用连接时间过长导致的。因此继续查看代码和查找相关httpClient连接配置文档得出如下结论:
httpclient连接配置全部为默认
本项目中的httpclient的连接配置全部使用默认配置。使用HttpClients.createDefault();创建默认的httpclient对象,全部使用默认值。
httpclient连接的配置,参考了张开涛的博客:http://jinnianshilongnian.iteye.com/blog/2089792
连接池配置不合理
maxConnTotal和maxConnPerRoute

maxConnTotal是连接池总的最大连接数,用的是默认值20.

maxConnPerRoute是每个路由最大连接数,本项目都是连接微信服务器,因此就是默认为2的值,而这对于生产环境并发较高确实不合适。

http网络连接配置不合理

httpclient的请求配置都没有配置,使用默认配置信息。
this.connectionRequestTimeout = -1;
this.connectTimeout = -1;
this.socketTimeout = -1;

都是使用的系统默认时间值,而这个值是一个比较大的值,对于生产环境来说是不合适的。

因此这些值对于生产环境来说均为不合理的值,因此我根据自己的生产环境的实际情况配置如下:

weixin.mp.httpclient.socketTimeout=2000
weixin.mp.httpclient.connectTimeout=2000
weixin.mp.httpclient.connectionRequestTimeout=500
weixin.mp.httpclient.maxConnPerRoute=300
weixin.mp.httpclient.maxConnTotal=300

微信调用接口统计

平均耗时都要300毫秒。

总结

  • 默认配置值一定不是最优的,有时候在正好碰到恶劣环境下反而是致命的问题。
  • 微信接口的性能比较差,尤其是当服务器与微信api的网络通讯较差的时候,会是较大的问题。
  • 微信的accessToken和jspApiTicket在集群环境下一定要共享存储。
  • 涉及到网络通讯的连接超时一定要设置且不能太大。
  • 生产环境解决问题需要有尽量多的日志、监控、各种资源的使用情况的信息。

原文地址:https://my.oschina.net/ywbrj042/blog/542453?p={{currentPage-1}}

java开发的微信公众号服务端生产环境中的两个大坑相关推荐

  1. 微信公众号(一) --- 开启微信公众号服务

    开启微信公众号服务 注:1.以测试号(权限开发比较多)完成大部分微信功能,正式环境上类似配置开发即可.测试号不能开发支付交易,必须公众号验证和支付验证的公众号. 2.主要开发 公众号设置服务.获取用户 ...

  2. vue开发项目微信公众号授权支付开发

    一.注册微信公众号服务号并填写企业信息(个人订阅号没有开发微信支付的权限) 链接: https://mp.weixin.qq.com/ 二.在微信公众号内进行微信认证(3-5个工作日) 三.在微信公众 ...

  3. 微信公众号回复、接收消息中中文乱码问题的分析及解决

    微信公众号回复.接收消息中文乱码问题的分析及解决 为了方便,我们把接收时用的编码记为A.把处理时用的编码记为B.把返回时用的编码记为C 文章目录 微信公众号回复.接收消息中文乱码问题的分析及解决 中文 ...

  4. 如何在微信公众号的文章推送中展示bilibili的视频

    0x00  需求背景: 我们学院举办了一个短视频设计大赛,需要在官方公众号里进行线上投票,选出大家最喜欢的视频. 然而微信公众号的文章推送中,限制了每篇文章只能上传三部视频,而我们需要把三十部作品都放 ...

  5. Java微信公众号服务号开发(四):公众号底部菜单设置

    今天来说一下微信公众号底部的菜单设置 设置更改公众号底部的菜单有两种方式:1.通过Java代码调取微信提供的接口进行设置. 2.直接在微信公众号平台用json数据设置. 这两种方式都比较简单.个人采取 ...

  6. Bwsaas框架基于Thinkphp6.x开发的微信公众号,小程序,app,H5等多端打通的框架

    完善bwsaas框架并开源原生小程序商业版本前端应用 新增 1插件安装升级,安装平台系统插件(type=admin_system)时可同时添加多个角色组(平台系统功能+租户系统功能),需要在group ...

  7. asp.net mvc C# 微信公众号-服务号开发 (用户网页授权获取用户昵称头像信息)...

    参考文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1445241432 一.安装Senparc.Weixin NuGet包 ...

  8. openlayers移动端开发之微信公众号

    项目中需要建设微信公众号平台,平台中需要用地图做一些展示功能,考虑使用开源地图框架openlayers5,将相关问题做简要记录. 通过百度地图控制中心申请百度地图浏览器端token(备用). 1.首先 ...

  9. 微信公众号开发java流程_微信公众号开发教程java 编程语言的特点及选择

    微信公众号开发教程java 编程语言的特点及选择 微信公众号为用户提供了相关的工具,来对微信公众号进行一个简单的开发.但是如果想实现一些复杂的功能,其实还是要借助于一些编程语言的使用.所以要了解,在微 ...

最新文章

  1. 致远互联“平台+生态”抢占数字化升级新赛
  2. The source attachment does not contain the source for the file Activity.class
  3. c++枚举类型(一)
  4. 做外贸,独立B2C商城好,还是平台好
  5. 1w存银行一年多少利息_100万存银行一年利息多少?能赚多少钱?
  6. Python 西瓜书机器学习支持向量机(SVM)
  7. illegal text-relocation
  8. 从零开始学习python编程-从0开始的Python学习014面向对象编程(推荐)
  9. OpenCV图像处理(2)——形态学操作
  10. xshell官网免费版下载
  11. 查看文件夹和文件大小
  12. 数组除重和运用随机点名的简单运用
  13. idea主菜单栏(main menu)消失解决办法,无需重启
  14. 学计算机后期制作,后期制作
  15. WebAI.js:一个简单的网页前端 AI 模型部署工具
  16. 盘点2018上半年最受欢迎的前端开发!
  17. 【研发问题系列】e1000网卡异常
  18. linux搜索命令有哪些,linux 中的搜索命令
  19. MLCC电容啸叫的机理及解决方案
  20. 今日所学(五) [当日学习内容检查,如果有误,望大家赐教]

热门文章

  1. 从事文字工作和经常使用电脑的人要注意保护好自己的眼睛
  2. C++ 对 C 兼容是什么意思?
  3. 为什么ps里的液化工具里的部分功能用不了
  4. TI四芯片级联雷达评估板-校准
  5. win10锁屏c语言,win10系统锁屏状态下运行任意程序设置的操作方法
  6. UVa 1600 巡逻机器人(Patrol Robot)
  7. golang基础面试题总结
  8. 博弈论分析题_博弈论复习题及答案
  9. mysql无法生成备份产生读锁_mydumper 备份原理和使用方法(备份mysql)
  10. Linux系统操作(21):物理cpu数、cpu核数、逻辑cpu数、几路几核几线程、CPU信息详细查询方法