jstack那些事(二)

发布于 2022-6-27 17:18
浏览
0收藏

4 编码习惯引导

    4.1 设置线程名

       线程名设置主要是为了在jstack堆栈中便于查询。在logback日志中最好也标注下线程名。当遇到问题也有一个查询源。

 线程名有如下两种方式:

 •  手工设置线程名    

Thread t = new Thread(new Runnable() {    

@Override     public void run() {      

//something process      

}});t.setName("mytestThread");t.start();

 •  线程工厂设置 (源码来自rocketmq 4.4)

public class ThreadFactoryImpl implements ThreadFactory {    

private final AtomicLong threadIndex = new AtomicLong(0);    

private final String threadNamePrefix;    private final boolean daemon;    

public ThreadFactoryImpl(final String threadNamePrefix) {    

 this(threadNamePrefix, false);    }  

 public ThreadFactoryImpl(final String threadNamePrefix, boolean daemon) {    

 this.threadNamePrefix = threadNamePrefix;    

 this.daemon = daemon;    }  

 @Override  

 public Thread newThread(Runnable r) {    

Thread thread = new Thread(r, threadNamePrefix + this.threadIndex.incrementAndGet());    

 thread.setDaemon(daemon);    

 return thread;  

 }}

使用方式:

this.traceExecutor = new ThreadPoolExecutor(  

10,  

20,  

1000 * 60,  

TimeUnit.MILLISECONDS,  

this.appenderQueue,  

new ThreadFactoryImpl("MQTraceSendThread_"));traceProducer = getAndCreateTraceProducer(rpcHook);

    4.2 线程池隔离

线程隔离主要有线程池隔离,在实际使用时我们会把请求分类,然后交给不同的线程池处理,当一种业务的请求处理发生问题时,不会将故障扩散到其他线程池,从而保证其他服务可用。jstack那些事(二)-开源基础软件社区

以开源代码rocketmq举例:

我们来看下broker启动时 ,发送消息 & pull 消息 都是不同的线程池处理请求。

分为

 •  发送消息线程池
 •  pull消息线程池 
 •  查询消息线程池
 •  回复消息线程池

代码如下:

//发送消息 处理器this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor);

//pull消息 处理器this.remotingServer.registerProcessor(RequestCode.PULL_MESSAGE, this.pullMessageProcessor, this.pullMessageExecutor);this.pullMessageProcessor.registerConsumeMessageHook(consumeMessageHookList);

//消费管理处理器ConsumerManageProcessor consumerManageProcessor = new ConsumerManageProcessor(this);this.remotingServer.registerProcessor(RequestCode.GET_CONSUMER_LIST_BY_GROUP, consumerManageProcessor, this.consumerManageExecutor);

//事务处理器this.remotingServer.registerProcessor(RequestCode.END_TRANSACTION, new EndTransactionProcessor(this), this.endTransactionExecutor);this.fastRemotingServer.registerProcessor(RequestCode.END_TRANSACTION, new EndTransactionProcessor(this), this.endTransactionExecutor);

 

4.3 超时设置 

  为了避免线程池大规模阻塞, 合理的设置超时时间非常重要。 常见超时有如下

 •  http 读写超时 (jdk httpurlconnection)

cononnection(new URL(n = getCurl), METHOD_POST, ctype);

fillHeaders(conn, headers)

;conn.setConnectTimeout(connectTimeout);

conn.setReadTimeout(readTimeout);

out = conn.getOutputStream();

 •  加锁时间(分布式场景下 redission)

RLock lock = redisson.getLock("myLock");

// traditional lock methodlock.lock();

// or acquire lock and automatically unlock it after 10 secondslock.lock(10, TimeUnit.SECONDS)

// or wait for lock aquisition up to 100 seconds

// and automatically unlock it after 10 secondsboolean res = lock.tryLock(100, 10, TimeUnit.SECONDS)

;if (res) {  

try {    

...  

} finally

{       lock.unlock();  

}

}

 •  网络通讯 (通过countdownlatch实现)

 public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,      

 final long timeoutMillis)  

throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {  

 final int opaque = request.getOpaque();  

 try {      

final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);      

this.responseTable.put(opaque, responseFuture);      

final SocketAddress addr = channel.remoteAddress();      

channel.writeAndFlush(request).addListener(new ChannelFutureListener() {      

@Override      

public void operationComplete(ChannelFuture f) throws Exception {          

if (f.isSuccess()) {              

responseFuture.setSendRequestOK(true);            

return;        

 } else {            

responseFuture.setSendRequestOK(false);        

 }          

responseTable.remove(opaque);        

 responseFuture.setCause(f.cause());          

responseFuture.putResponse(null);          

log.warn("send a request command to channel <" + addr + "> failed.");      

}    

});  

RemotingCommand responseCommand =      

responseFuture.waitResponse(timeoutMillis);      

if (null == responseCommand) {        

 if (responseFuture.isSendRequestOK()) {          

 throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis, responseFuture.getCause());          

} else {          

 throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());          

}        

}        

return responseCommand;  

 } finally {        

this.responseTable.remove(opaque);    

}

}

rocketmq封装了 responseFuture 对象超时设置通过countdown来实现超时效果

public RemotingCommand waitResponse(final long timeoutMillis)

throws InterruptedException {  

 this.countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);  

 return this.responseCommand;

}

 

5  专业产品推荐 


     5.1.  阿里Arthas 

      Arthas 是Alibaba开源的Java诊断工具 。我们从下图看到 一个简单得命令就可以查看当进程阻塞的线程

jstack那些事(二)-开源基础软件社区

     5.2 PerfMa 笨马网络

 

     笨马网络是一家致力于打造一站式IT系统稳定性保障解决方案,专注于性能评测与调优、故障根因定位与解决的公司。

我们来看看社区版本如何查看堆栈情况 。

a.首先上传dump文件
b.通过控制台即可从 线程池 栈  锁 多个层面展示当前jvm线程信息
jstack那些事(二)-开源基础软件社区

jstack那些事(二)-开源基础软件社区

6  总结

     玩过金庸群侠传的朋友可能听过“野球拳” ,这在游戏初期是非常基础的招式,威力也弱 。修炼也很耗时 ,很多玩家都放弃了 , 没有想到的是,野球拳练到10级,玩家就像打通任督二脉, 基本一击必杀 。

我认为学习技术也是这样 。

1.坚持,never give up  不断磨练自己的基本功
2.勇于实践 ,当出现问题 ,不要害怕 ,这是一个千载难逢学习的机会
3.思考,向别人学习 
第一次写公众号 , 希望对大家有帮助。 

标签
已于2022-6-27 17:18:50修改
收藏
回复
举报
回复
添加资源
添加资源将有机会获得更多曝光,你也可以直接关联已上传资源 去关联
    相关推荐