前言

随着微服务的流行,单体应用被拆分成一个个独立的微进程,可能一个简单的请求,需要多个微服务共同处理,这样其实是增加了出错的概率,所以如何保证在单个微服务出现问题的时候,对整个系统的负面影响降到最低,这就需要用到我们今天要介绍的线程隔离。

线程模型

在介绍线程隔离之前,我们先了解一下主流容器,框架的线程模型,因为微服务是一个个独立的进程,之间的调用其实就是走网络io,网络io的处理容器如tomcat,通信框架如netty,微服务框架如dubbo,都很好的帮我们处理了底层的网络io流,让我们可以更加的关注于业务处理;

Netty

Netty是基于java nio的高性能通信框架,使用了主从多线程模型,借鉴Netty系列之 Netty线程模型的一张图片如下所示:

主线程负责认证,连接,成功之后交由从线程负责连接的读写操作,大致如下代码:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);

EventLoopGroup workerGroup = new NioEventLoopGroup();

ServerBootstrap b = new ServerBootstrap();

b.group(bossGroup, workerGroup);

主线程是一个单线程,从线程是一个默认为cpu*2个数的线程池,可以在我们的业务handler中做一个简单测试:

public void channelRead(ChannelHandlerContext ctx, Object msg) {

System.out.println("thread name=" + Thread.currentThread().getName() + " server receive msg=" + msg);

}

服务端在读取数据的时候打印一下当前的线程:

threadname=nioEventLoopGroup-3-1server receive msg="..."

可以发现这里使用的线程其实和处理io线程是同一个;

Dubbo

Dubbo的底层通信框架其实使用的就是Netty,但是Dubbo并没有直接使用Netty的io线程来处理业务,可以简单在生产者端输出当前线程名称:

threadname=DubboServerHandler-192.168.1.115:20880-thread-2,...

可以发现业务逻辑使用并不是nioEventLoopGroup线程,这是因为Dubbo有自己的线程模型,可以看看官网提供的模型图:

其中的Dispatcher调度器可以配置消息的处理线程:

all所有消息都派发到线程池,包括请求,响应,连接事件,断开事件,心跳等。

direct所有消息都不派发到线程池,全部在 IO 线程上直接执行。

message只有请求响应消息派发到线程池,其它连接断开事件,心跳等消息,直接在 IO 线程上执行。

execution只有请求消息派发到线程池,不含响应,响应和其它连接断开事件,心跳等消息,直接在 IO 线程上执行。

connection在 IO 线程上,将连接断开事件放入队列,有序逐个执行,其它消息派发到线程池。

Dubbo默认使用FixedThreadPool,线程数默认为200;

Tomcat

Tomcat可以配置四种线程模型:BIO,NIO,APR,AIO;Tomcat8开始默认配置NIO,此模型和Netty的线程模型很像,可以理解为都是Reactor模式,在此不过多介绍;其中maxThreads参数配置专门处理IO的Worker数,默认是200;可以在业务Controller中输出当前线程名称:

ThreadName=http-nio-8888-exec-1...

可以发现处理业务的线程就是Tomcat的io线程;

为什么要线程隔离

从上面的介绍的线程模型可以知道,处理业务的时候还是使用的io线程比如Tomcat和netty,这样会有什么问题那,比如当前服务进程需要同步调用另外三个微服务,但是由于某个服务出现问题,导致线程阻塞,然后阻塞越积越多,占满所有的io线程,最终当前服务无法接受数据,直至奔溃;

Dubbo本身做了IO线程和业务线程的隔离,出现问题不至于影响IO线程,但是如果同样有以上的问题,业务线程也会被占满;

做线程隔离的目的就是如果某个服务出现问题可以把它控制在一个小的范围,不至于影响到全局;

如何做线程隔离

做线程隔离原理也很简单,给每个请求分配单独的线程池,每个请求做到互不影响,当然也可以使用一些成熟的框架比如Hystrix(已经不更新了),Sentinel等;

线程池隔离

SpringBoot+Tomcat做一个简单的隔离测试,为了方便模拟配置MaxThreads=5,提供隔离Controller,大致如下所示:

@RequestMapping("/h1")

String home() throws Exception {

System.out.println("h1-->ThreadName=" + Thread.currentThread().getName());

Thread.sleep(200000);

return "h1";

}

@RequestMapping("/h3")

String home3() {

System.out.println("h3-->ThreadName=" + Thread.currentThread().getName());

return "h3";

}

请求5次/h1请求,再次请求/h3,观察日志:

h1-->ThreadName=http-nio-8888-exec-1

h1-->ThreadName=http-nio-8888-exec-2

h1-->ThreadName=http-nio-8888-exec-3

h1-->ThreadName=http-nio-8888-exec-4

h1-->ThreadName=http-nio-8888-exec-5

可以发现h1请求占满了5条线程,请求h3的时候Tomcat无法接受请求;改造一下h1请求使用使用线程池来处理:

ExecutorService executorService = Executors.newFixedThreadPool(2);

List> list = new CopyOnWriteArrayList>();

@RequestMapping("/h2")

String home2() throws Exception {

Future result = executorService.submit(new Callable() {

@Override

public String call() throws Exception {

System.out.println("h2-->ThreadName=" + Thread.currentThread().getName());

Thread.sleep(200000);

return "h2";

}

});

list.add(result);

//降级处理

if (list.size() >= 3) {

return "h2-fallback";

}

String resultStr = result.get();

list.remove(result);

return resultStr;

}

如上部分伪代码,使用线程池异步执行,并且超出限制范围做降级处理,这样再次请求h3的时候,就不受影响了;当然上面代码比较简陋,我们可以使用成熟的隔离框架;

Hystrix

Hystrix 提供两种隔离策略:线程池隔离(Bulkhead Pattern)和信号量隔离,其中最推荐也是最常用的是线程池隔离。Hystrix的线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供fallback机制;可以看一个简单的实例:

public class HelloCommand extends HystrixCommand{

public HelloCommand(String name) {

super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("ThreadPoolTestGroup"))

.andCommandKey(HystrixCommandKey.Factory.asKey("testCommandKey"))

.andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey(name))

.andCommandPropertiesDefaults(

HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(20000))

.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter().withMaxQueueSize(5) // 配置队列大小

.withCoreSize(2) // 配置线程池里的线程数

));

}

@Override

protected String run() throws InterruptedException {

StringBuffer sb = new StringBuffer("Thread name=" + Thread.currentThread().getName() + ",");

Thread.sleep(2000);

return sb.append(System.currentTimeMillis()).toString();

}

@Override

protected String getFallback() {

return "Thread name=" + Thread.currentThread().getName() + ",fallback order";

}

public static void main(String[] args) throws InterruptedException, ExecutionException {

List> list = new ArrayList<>();

System.out.println("Thread name=" + Thread.currentThread().getName());

for (int i = 0; i < 8; i++) {

Future future = new HelloCommand("hystrix-order").queue();

list.add(future);

}

for (Future future : list) {

System.out.println(future.get());

}

Thread.sleep(1000000);

}

}

如上配置了处理此业务的线程数为2,并且指定当线程满了之后可以放入队列的最大数量,运行此程序结果如下:

Thread name=main

Thread name=hystrix-hystrix-order-1,1589776137342

Thread name=hystrix-hystrix-order-2,1589776137342

Thread name=hystrix-hystrix-order-1,1589776139343

Thread name=hystrix-hystrix-order-2,1589776139343

Thread name=hystrix-hystrix-order-1,1589776141343

Thread name=hystrix-hystrix-order-2,1589776141343

Thread name=hystrix-hystrix-order-2,1589776143343

Thread name=main,fallback order

主线程执行可以理解为就是io线程,业务执行使用的是hystrix线程,线程数2+队列5可以同时处理7条并发请求,超过的部分直接fallback;

信号量隔离

线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的开销比较大,特别是对低延时的调用有比较大的影响;

上面对线程模型的介绍,我们发现Tomcat默认提供了200个io线程,Dubbo默认提供了200个业务线程,线程数已经很多了,如果每个命令在使用一个线程池,线程数会非常多,对系统的影响其实也很大;有一种更轻量的隔离方式就是信号量隔离,仅限制对某个资源调用的并发数,而不是显式地去创建线程池,所以开销比较小;Hystrix和Sentinel都提供了信号量隔离方式,Hystrix已经停止更新,而Sentinel干脆就没有提供线程隔离,或者说线程隔离是没有必要的,完全可以用更轻量的信号量隔离代替;

总结

本文从线程模型开始,讲到了IO线程,以及为什么要分开IO线程和业务线程,具体如何去实现,最后简单介绍了一下更加轻量的信号量隔离,为什么说更加轻量哪,其实业务还是在IO线程处理,只不过会限制某个资源的并发数,没有多余的线程产生;当然也不是说线程隔离就没有价值了,其实还是要根据实际情况来定,根据你使用的容器,框架本身的线程模型来决定

Java里线程的隔离方式_线程隔离浅析相关推荐

  1. JDK中的Timer和TimerTask详解 目录结构: Timer和TimerTask 一个Timer调度的例子 如何终止Timer线程 关于cancle方式终止线程 反复执行一个任务 sche

    JDK中的Timer和TimerTask详解 目录结构: Timer和TimerTask 一个Timer调度的例子 如何终止Timer线程 关于cancle方式终止线程 反复执行一个任务 schedu ...

  2. 黑马程序员--线程之间的通信,等待与唤醒机制,线程的终止方式,线程中的其他方法,优先级,toString() 守护线程,GUI图形化界面

    ------<a href="http://www.itheima.com" target="blank">Java培训.Android培训.iOS ...

  3. java实现线程三种方式_详解三种java实现多线程的方式

    java中实现多线程的方法有两种:继承Thread类和实现runnable接口. 1.继承Thread类,重写父类run()方法 public class thread1 extends Thread ...

  4. java线程池饱和策略_线程池的饱和策略-调用者执行 | 学步园

    java并发编程实践阅读笔记之线程池的饱和策略 使用java的任务管理框架的线程池执行任务时,线程池的任务等待队列被填满时,饱和策略开始发挥作用.ThreadPollExecutor的饱和策略通过se ...

  5. 关于java线程同步的笔记_线程同步(JAVA笔记-线程基础篇)

    在多线程应用程序中经常会遇到线程同步的问题.比如:两个线程A.线程B可能会 "同时" 执行同一段代码,或修改同一个变量.而很多时候我们是不希望这样的. 这时候,就需要用到线程同步. ...

  6. future 线程报错后_线程池运用实例——一次错误的多线程程序设计以及修复过程...

    写在前面的话 写下这篇文章只为了回顾之前在实际工作中犯的一个极其二逼的错误,用我的经历来提示后来者,诸位程序大神,大牛,小牛们看到此文笑笑即可,轻拍轻拍... 1 背景 有这么一个需求,我们的系统(后 ...

  7. 3线程的终止方式,线程属性,NPTL

     1线程终止方式 如果需要只终止某个线程而不终止整个线程,可以有三种方法: A:从主线程函数return.这种方法对主线程不适合,从main函数return相当于调用exit. B:一个线程可以调 ...

  8. 线程并发库和线程池的作用_线程和并发介绍

    线程并发库和线程池的作用 本文是我们名为Java Concurrency Essentials的学院课程的一部分. 在本课程中,您将深入探讨并发的魔力. 将向您介绍并发和并发代码的基础知识,并学习诸如 ...

  9. JAVA线程并发数量控制_线程同步工具(二)控制并发访问多个资源

    声明:本文是< Java 7 Concurrency Cookbook>的第三章, 作者: Javier Fernández González 译者:郑玉婷 控制并发访问多个资源 在并发访 ...

  10. java里的主线程和子线程以及finally不会执行的特殊情况

    如下代码: public class ThreadTest {public static class UserThread extends Thread{@Overridepublic void ru ...

最新文章

  1. Request Connection: Remote Server @ 192.229.145.200:80
  2. 为什么Eureka比ZooKeeper更适合做注册中心?
  3. 15个目标检测开源数据集汇总
  4. expect自动化交互脚本(一)
  5. 几种常用的配电网络接线
  6. 【软考-软件设计师】输入/输出技术
  7. 用户画像是怎么生成出来的?
  8. [密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第29篇]什么是UF-CMA数字签名的定义?
  9. hdu 4454 Stealing a Cake 三分法
  10. python迭代_Python迭代
  11. 学习yii2.0框架阅读代码(九)
  12. 软考高级-系统架构师-论文
  13. Keras入门级MNIST手写数字识别超级详细教程
  14. matlab运行.m文件的命令,怎样在matlab的命令窗口运行.m文件
  15. 新浪微博爬虫:模拟登陆+爬取原始页面
  16. P2142 高精度减法
  17. c语言创建二叉树从小到大_用C语言编写二叉树的建立与遍历
  18. 《遥远的救世主》(摘录)
  19. 【赛鱼电竞】用王者荣耀免费改名卡改名和王者荣耀怎么修改昵称及王者荣耀改名卡免费修改昵称方法教程
  20. 深度学习在计算机视觉中的应用

热门文章

  1. Justinmind使用教程(1)——概述部分
  2. 创造与魔法怎么自建服务器,创造与魔法如何建立部落 部落建造条件
  3. 【Segmentation】
  4. 容易的面试问题变得更加困难:给定数字1..100,在正好缺少k的情况下,找到缺失的数字
  5. adobe绿色版cs6下载地址
  6. 【寻找最佳小程序】02期:腾讯旅游首款小工具“旅行小账本”——创意及研发过程大起底
  7. 一种简单的生成伪随机数的方法(翻译)
  8. 程序猿转行为什么这么难
  9. 【校内模拟】八云蓝(线段树)(大力分类讨论)
  10. MySQL 性能优化参数分析