最近出了点毛病。花了很长时间才排除故障。回顾整个排查过程,经验主义在这里起到了不好的作用。整个故障排除时间很长。这个故障的根本原因是BlockingQueue的使用有问题。顺便说一下Java中常用的几种BlockingQueue:ArrayBlockingQueue、LinkedBlockingQueue和SynchronousQueue。当时失败的现象是应用处理请求的线程池已满,导致请求无法处理。所以转储线程以查看线程在做什么。原来是线程在写日志的地方被阻塞了。过去有很多问题。去线程dumping的时候看到一堆块在写日志,但是一般都是其他原因导致的,所以这次根据这次的经验,我觉得肯定不是写日志的问题,于是做了各种查..折腾了N久,回过头来发现log锁所在的地方是自己写的代码。代码拿到日志锁后,从线程栈上看,block是在ArrayBlockingQueue.put的地方,所以翻了一下这段代码,发现这是一个长度为1024的BlockingQueue,也就是说如果把这个Queue放在有1024个对象,put肯定会阻塞,其实大家翻代码的时候可以看到写代码的同学认为如果BlockingQueue满了就应该处理。代码是这样写的:if(blockingQueue.remainingCapacity()<1){//todo}blockingQueue.put...这里有两个悲催的问题,一个是这个if判断还是会直接去put而不是else,第二个就是填满之后关键的处理逻辑还在//todo...另外,我觉得这段代码也反映了同学们对BlockingQueue接口的不满太熟悉了,实现这个效果,没必要先做判断,比较合适的方式是使用blockingQueue.offer,返回false再做相应的异常处理。BlockingQueue是生产/消费者模式中经常使用的一种数据结构,常用的有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。ArrayBlockingQueue/LinkedBlockingQueue最大的区别在于对象在Queue中的存储方式。一个是数组,另一个是链表。两者的区别也写在了代码注释中:Linkedqueuestypicallyhavehigherthroughputthanarray-basedqueuesbutlesspredictableperformance在大多数并发应用程序中。SynchronousQueue是一种非常特殊的BlockingQueue。它的模式是如果offer过程中没有其他threadtaking或者polling,那么offer就会失败;在take期间,如果没有其他线程只是Concurrency也会在offer中失败。这种特殊的模式非常适合对响应要求高且线程池不固定的Queue。对于线上的业务场景,所有并发和外部访问阻塞的地方的一个道理就是必须要有超时机制。不知道有多少次因为没有超时导致的线上业务严重失败。在线业务最重要的是快速处理请求,所以快速失败是在线业务系统设计和代码编写中最重要的原则。根据这个原则,上面代码中最明显的错误就是使用put而不是带有超时机制的offer,或者如果是不重要的场景,应该直接使用offer。如果为false,抛出异常或者记录异常即可。对于BlockingQueue的场景,除了超时机制之外,还有一个需求就是必须限制队列长度。否则,默认值为Integer.MAX_VALUE。如果代码有bug,内存就会挂掉。说到BlockingQueue,还是要提一下BlockingQueue用的最多的地方:线程池。Java的ThreadPoolExecutor中有一个参数就是BlockingQueue。如果这个地方使用了ArrayBlockingQueue或者LinkedBlockingQueue,线程池的coreSize和poolSize不同,当coreSize线程满了之后,此时线程池首先要做的就是offer给BlockingQueue,然后结束如果成功。这种场景也不符合线上业务的需求。在线业务更喜欢快速处理而不是先排队,其实在线业务最好不要让请求堆积在排队队列中。在线业务中这样做很容易造成雪崩。直接拒绝抛出超出处理能力的错误相对好一些。至于首页排队的是什么?是的,那是另一种限流机制。所以,在编写高并发、分布式的代码时,除了系统设计,代码细节的功力也是非常非常重要的。添加一名作者
