关于ArrayBlockingQueue的put和offer方法
最近线上出现了一个故障,故障的表现就是服务请响应很慢,依赖方获取不到执行结果,查看调用堆栈,发现所有的操作都阻塞在写日志的地方,这个写日志是先写日志到内存,然后再刷新到其他地方。采用了ArrayBlockingQueue,但是调用了ArrayBlockQueue的put方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /** * Inserts the specified element at the tail of this queue, waiting * for space to become available if the queue is full. * * @throws InterruptedException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); insert(e); } finally { lock.unlock(); } } |
这里的put方法会等待一个空的位置出来,然后再执行insert,但是系统的请求量非常大,此时一个请求过来后,前面的请求可能还处于等待空位置这一步,此时当前请求获取lock就等待,这样这个业务操作就一直处于获取锁获取不到的场景中了。这是一个真实出现的case,血一般的教训,当时只能不断重启机器来缓解问题。如何彻底解决这个问题,换个API
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /** * Inserts the specified element at the tail of this queue, waiting * up to the specified wait time for space to become available if * the queue is full. * * @throws InterruptedException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException { checkNotNull(e); long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) { if (nanos <= 0) return false; nanos = notFull.awaitNanos(nanos); } insert(e); return true; } finally { lock.unlock(); } } |
换成offer判断返回值为false的情况,不然把业务操作阻塞住。以后在高并发场景下面就不要在使用put这个API了。