多线程消费使用HttpClient引发的坑

      最近公司短信平台又出问题了,所有客户端都能正常调用,但是就是没有给用户发送短信,然后用户大面积的投诉说收不到短信,业务做不下去。

短信平台框架实现简单的画了一个序列图,大致思想都在里面了。如下:

多线程消费使用HttpClient引发的坑
 

       关于收不到短信的问题以前就曾经出现过一次,原因是有一个短信渠道商的服务不太稳定,当时排查原因发现缓存队列中数据量很大,猜测是消费线程阻塞,再比对日志基本上确认是消费线程阻塞。后来慢慢排查代码才发现是由于短信服务商的api中在httpclient请求时没有设置超时时间,消费线程在使用该渠道商的服务时因为api没有设置超时时间,HttpClient出现阻塞,造成该消费线程长期阻塞后续消息无法消费,反应在用户体验上就是能正常点击,但是收不到短信,后台日志也没有具体体现,线上的解决办法就是重启,但是如果渠道商一旦出现调用问题,短期内就可能会频繁出现调用超时,没有设置超时时间则是短期内频繁出现阻塞。这个阻塞问题在自己事后私下搭建服务调用验证后证明猜想是正确的,HttpClient没有设置超时时间是会长期阻塞的。当时排查出这个问题后自己总结了几个解决方案:

a、重写短信渠道商的API接口

b、修改框架实现使用非阻塞消费线程进行处理,每个消息起一个线程处理

c、在自己服务商中添加超时机制

注意:这里httpclient版本是4.0,以下是对应超时时间设置:

CoreConnectionPNames.CONNECTION_TIMEOUT:获取连接超时时间。如果该参数没有设置,那么默认的超时间在不同的OS下是不同的,Windows大概20s,Linux大概180s。为了在访问不存在网页造成的访问阻塞,建议访问时设置此参数。

CoreConnectionPNames.SO_TIMEOUT:数据传输时间。默认的SO_TIMEOUT是0,即一直等待。这也是造成长期阻塞的原因。

其中重写API接口当时觉得太麻烦,没有时间进行处理,重写框架呢就更麻烦了,后来找到的解决办法是在消费线程中调用API时进行异步调用,在使用对应渠道商时添加jdk中concurrent包下的Callable进行异步处理,同时解决了没有超时时间的问题。具体代码如下:

ExecutorService service = Executors.newSingleThreadExecutor();
            Callable<Integer> callable  = new Callable<Integer>(){
                public Integer call() throws Exception{
                    ISms sms=new CHttpPost();
                     //API调用,返回调用结果
                    return sms.send(...);
                }
            };
            Future<Integer> future = service.submit( callable);
            service.shutdown();
            //最长等待三分钟
            if(service.awaitTermination (180000l, TimeUnit.MILLISECONDS) == false)
                throw new TimeOutException("**短信调用超时");
            result =future.get();//获取处理结果

       在awaitTermination时API请求已经在异步线程处理中,如果http请求正常则立即返回结果获取处理结果,否则出现长期阻塞则最长等到三分钟,然后抛出异常,消费线程进行下一个消息的处理。

当时出现这个问题时因为没有足够的重视,现在再次出现后才发现不止一个渠道商的API中没有设置超时时间,而且当时只在那个出现问题的渠道商的调用上添加了超时处理,并没发现其他渠道商也有没有超时设置的,这一点当时还认为是别人的服务low,现在再次出现问题不得不说自己公司的框架也是有坑的,如果能早点考虑到消费线程可能出现的这种长期阻塞现象,提前进行预防就不会出现因为别人的服务有问题而影响自己的服务运行了。

       这是一个个人踩过的坑,分享出来供小伙伴们借鉴学习,希望大家有问题能共同分享,一起进步。
 

相关推荐