在高并发分布式系统中,线程锁竞争是一个常见但又容易被忽视的性能瓶颈问题。尤其是在使用Dubbo这样的RPC框架时,锁竞争可能会导致线程阻塞,进而影响系统的整体性能和响应时间。本文将通过一个实际案例,深入分析Dubbo服务中线程锁竞争问题的根源,并提供一系列优化方案,帮助开发者更好地理解和解决类似问题。
在某次线上问题排查中,我们发现Dubbo服务的某些线程频繁处于WAITING状态,具体表现为线程在等待一个ReentrantLock锁。以下是问题线程的堆栈信息:
"DubboServerHandler-192.168.0.45:20880-thread-295" Id=590 WAITING on java.util.concurrent.locks.ReentrantLock$NonfairSync@40e7c97a owned by "DubboServerHandler-192.168.0.45:20880-thread-293" Id=586
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at ch.qos.logback.core.OutputStreamAppender.writeBytes(OutputStreamAppender.java:197)
at ch.qos.logback.core.OutputStreamAppender.subAppend(OutputStreamAppender.java:231)
at ch.qos.logback.core.OutputStreamAppender.append(OutputStreamAppender.java:102)
at ch.qos.logback.core.UnsynchronizedAppenderBase.doAppend(UnsynchronizedAppenderBase.java:84)
at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:51)
at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:270)
at ch.qos.logback.classic.Logger.callAppenders(Logger.java:257)
at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:421)
at ch.qos.logback.classic.Logger.filterAndLog_1(Logger.java:398)
at ch.qos.logback.classic.Logger.info(Logger.java:583)
at cn.ad.route.service.ChannelManager.getChannelEx(ChannelManager.java:238)
at sun.reflect.GeneratedMethodAccessor117.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:282)
at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:485)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at cn.ad.route.service.ChannelManager$$EnhancerBySpringCGLIB$$b5377779.getChannelEx(<generated>)
at cn.ad.route.controller.RouteController.getChannelEx(RouteController.java:29)
at cn.ad.route.controller.RouteControllerDubboWrap0.invokeMethod(RouteControllerDubboWrap0.java)
at org.apache.dubbo.rpc.proxy.javassist.JavassistProxyFactory$1.doInvoke(JavassistProxyFactory.java:73)
at org.apache.dubbo.rpc.proxy.AbstractProxyInvoker.invoke(AbstractProxyInvoker.java:100)
at org.apache.dubbo.config.invoker.DelegateProviderMetaDataInvoker.invoke(DelegateProviderMetaDataInvoker.java:55)从堆栈信息中可以看出,线程295正在等待线程293释放一个ReentrantLock锁。这种锁竞争问题在高并发场景下尤为常见,尤其是在日志记录、资源访问等场景中。
从堆栈信息中可以看到,锁竞争发生在ch.qos.logback.core.OutputStreamAppender.writeBytes方法中。这表明日志记录过程中使用了ReentrantLock来保证线程安全。在高并发场景下,多个线程可能会同时尝试获取这个锁,导致部分线程被阻塞。
public class OutputStreamAppender<E> extends UnsynchronizedAppenderBase<E> {
private final ReentrantLock lock = new ReentrantLock();
protected void writeBytes(byte[] byteArray) throws IOException {
lock.lock();
try {
// 写入日志到输出流
} finally {
lock.unlock();
}
}
}在上述代码中,writeBytes方法使用了ReentrantLock来保证线程安全。如果多个线程同时调用此方法,就会导致锁竞争。
日志记录是系统中不可或缺的一部分,但如果配置不当,可能会成为性能瓶颈。例如:
INFO或DEBUG,在高并发场景下会生成大量日志,增加锁竞争的概率。从堆栈信息中可以看到,锁竞争发生在Dubbo服务的调用链中(cn.ad.route.service.ChannelManager.getChannelEx)。这表明在Dubbo服务处理请求时,某些共享资源(如日志记录器)被频繁访问,导致锁竞争加剧。
异步日志记录器可以将日志写入操作放到单独的线程中执行,从而减少对主线程的影响。Logback提供了AsyncAppender来实现这一功能。
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>在高并发场景下,建议将日志级别调整为WARN或ERROR,以减少日志记录频率。
logger.setLevel(Level.WARN);如果锁的粒度过大,可能会导致多个线程竞争同一个锁。可以通过减小锁粒度来减少竞争。
public class ChannelManager {
private final ReentrantLock lock = new ReentrantLock();
public Channel getChannelEx() {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock();
}
}
}在某些场景下,可以使用无锁数据结构(如ConcurrentHashMap)来避免锁竞争。
private final ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();
public Channel getChannelEx(String key) {
return channelMap.computeIfAbsent(key, k -> createChannel(k));
}对于频繁访问的资源,可以使用缓存来减少对共享资源的访问。
private final Cache<String, Channel> channelCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build();
public Channel getChannelEx(String key) {
return channelCache.get(key, () -> createChannel(key));
}使用Dubbo的监控工具(如Dubbo Admin)来识别系统中的性能瓶颈,并进行针对性优化。
在高并发分布式系统中,线程锁竞争是一个常见但又容易被忽视的问题。通过优化日志记录、减少锁竞争以及优化Dubbo服务,可以显著提升系统的性能和稳定性。希望本文的分析和优化方案能够帮助开发者更好地理解和解决类似问题。