From ffc27be3e0782e9a80ce795f7dee178cc181c990 Mon Sep 17 00:00:00 2001 From: = Date: Mon, 13 Sep 2021 12:58:00 +0800 Subject: [PATCH 1/3] add blog --- .idea/$PROJECT_FILE$ | 11 + .idea/.gitignore | 8 + .idea/misc.xml | 12 + .idea/modules.xml | 8 + .idea/qaplug_profiles.xml | 12 + .idea/sigs.iml | 9 + .idea/vcs.xml | 6 + ...24\345\274\217\346\236\266\346\236\204.md" | 143 +++++ ...12\347\232\204\345\256\236\350\267\265.md" | 505 ++++++++++++++++++ sig/reactive/talk/blog/pic/bio.png | Bin 0 -> 149098 bytes ...reaactor-core\346\265\201\347\250\213.png" | Bin 0 -> 114970 bytes sig/reactive/talk/blog/pic/reactive2.png | Bin 0 -> 147809 bytes sig/reactive/talk/blog/pic/reactive3.png | Bin 0 -> 51341 bytes sig/reactive/talk/blog/pic/reactive5.png | Bin 0 -> 40880 bytes sig/reactive/talk/blog/pic/reactive6.png | Bin 0 -> 59134 bytes sig/reactive/talk/blog/pic/reactive7.png | Bin 0 -> 10346 bytes sig/reactive/talk/blog/pic/reactive8.png | Bin 0 -> 196247 bytes sig/reactive/talk/blog/pic/reactive9.png | Bin 0 -> 156315 bytes .../pic/reactive\345\256\243\350\250\200.png" | Bin 0 -> 32815 bytes .../reactor-\350\256\276\350\256\2411.png" | Bin 0 -> 13621 bytes .../reactor-\350\256\276\350\256\2412.png" | Bin 0 -> 27876 bytes .../reactor-\350\256\276\350\256\2413.png" | Bin 0 -> 48268 bytes .../reactor-\350\256\276\350\256\2414.png" | Bin 0 -> 62555 bytes sig/reactive/talk/blog/pic/tomcat.png | Bin 0 -> 107287 bytes .../blog/pic/\345\233\276\347\211\2074.png" | Bin 0 -> 182007 bytes ...7\347\250\213\346\250\241\345\236\213.png" | Bin 0 -> 163757 bytes ...41\345\236\213\350\256\276\350\256\241.md" | 235 ++++++++ 27 files changed, 949 insertions(+) create mode 100644 .idea/$PROJECT_FILE$ create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/qaplug_profiles.xml create mode 100644 .idea/sigs.iml create mode 100644 .idea/vcs.xml create mode 100644 "sig/reactive/talk/blog/Reactive\345\217\215\345\272\224\345\274\217\346\236\266\346\236\204.md" create mode 100644 "sig/reactive/talk/blog/Reactive\346\250\241\345\274\217\345\234\250Trip.com\346\266\210\346\201\257\346\216\250\351\200\201\345\271\263\345\217\260\344\270\212\347\232\204\345\256\236\350\267\265.md" create mode 100644 sig/reactive/talk/blog/pic/bio.png create mode 100644 "sig/reactive/talk/blog/pic/reaactor-core\346\265\201\347\250\213.png" create mode 100644 sig/reactive/talk/blog/pic/reactive2.png create mode 100644 sig/reactive/talk/blog/pic/reactive3.png create mode 100644 sig/reactive/talk/blog/pic/reactive5.png create mode 100644 sig/reactive/talk/blog/pic/reactive6.png create mode 100644 sig/reactive/talk/blog/pic/reactive7.png create mode 100644 sig/reactive/talk/blog/pic/reactive8.png create mode 100644 sig/reactive/talk/blog/pic/reactive9.png create mode 100644 "sig/reactive/talk/blog/pic/reactive\345\256\243\350\250\200.png" create mode 100644 "sig/reactive/talk/blog/pic/reactor-\350\256\276\350\256\2411.png" create mode 100644 "sig/reactive/talk/blog/pic/reactor-\350\256\276\350\256\2412.png" create mode 100644 "sig/reactive/talk/blog/pic/reactor-\350\256\276\350\256\2413.png" create mode 100644 "sig/reactive/talk/blog/pic/reactor-\350\256\276\350\256\2414.png" create mode 100644 sig/reactive/talk/blog/pic/tomcat.png create mode 100644 "sig/reactive/talk/blog/pic/\345\233\276\347\211\2074.png" create mode 100644 "sig/reactive/talk/blog/pic/\345\274\202\346\255\245\347\272\277\347\250\213\346\250\241\345\236\213.png" create mode 100644 "sig/reactive/talk/blog/reactor-core\346\225\260\346\215\256\346\265\201\346\250\241\345\236\213\350\256\276\350\256\241.md" diff --git a/.idea/$PROJECT_FILE$ b/.idea/$PROJECT_FILE$ new file mode 100644 index 0000000..58b7e3e --- /dev/null +++ b/.idea/$PROJECT_FILE$ @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..73f69e0 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e954108 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..dd14386 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/qaplug_profiles.xml b/.idea/qaplug_profiles.xml new file mode 100644 index 0000000..9a7566c --- /dev/null +++ b/.idea/qaplug_profiles.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/.idea/sigs.iml b/.idea/sigs.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/sigs.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git "a/sig/reactive/talk/blog/Reactive\345\217\215\345\272\224\345\274\217\346\236\266\346\236\204.md" "b/sig/reactive/talk/blog/Reactive\345\217\215\345\272\224\345\274\217\346\236\266\346\236\204.md" new file mode 100644 index 0000000..56e348c --- /dev/null +++ "b/sig/reactive/talk/blog/Reactive\345\217\215\345\272\224\345\274\217\346\236\266\346\236\204.md" @@ -0,0 +1,143 @@ +[TOC] + +### 1. 什么是Reactive? + +Reactive直接翻译的意思是反应式,反应性。在前端/移动端/后端,很多东西都被称之为所谓的Reactive,那什么才是我们关注的Reactive呢? + +其实反应式并不是一个新鲜的概念,它的灵感来源最早可以追溯到90年代,但是直到2013年,Roland Kuhn等人发布了《反应式宣言》后才慢慢被人熟知,继而在2014年迎来爆发式增长。回到2014年,反应式的流行其实也移动设备的发展有关,随着智能手机的普及,越来越多的终端用户使互联网系统的并发压力急速上升。同时随着微服务趋势的流行,网络成为系统架构中的一等公民,而如何降低网络IO的开销,也成为了系统建设的重点问题。又随着近几年云原生趋势的流行,云上的计算资源成本开始透明化,所以如何降低应用的成本,同时实现高性能,成为应用开发人员需要关注的问题。 + +Reactive其实是一个广义的范畴,一个架构可以说是Reactive的,一个系统也可以说是Reactive的,又或者编程模型代码风格也可以称为Reactive。但是Reactive有什么价值?有哪些设计原则?到底是什么?让我们以后端技术的视角,从Reactive宣言开始谈起。 + + + +#### 1.1 Reactive宣言 + +2013年6月,Roland Kuhn等人发布了《反应式宣言》, 该宣言定义了反应式系统应该具备的一些架构设计原则。符合反应式设计原则的系统称为反应式系统。根据反应式宣言,反应式系统需要具备即时响应性(Responsive)、回弹性(Resilient)、弹性(Elastic)和消息驱动(Message Driven)四个特质。 + +要理解反应式宣言,需要首先理解它的三个层次。首先第一层是VALUE价值,也就是说一个Reactive的架构它能达到的价值是什么。那要如何实现这些价值呢?这就是第二层FORM手段,我们通过这两种手段,就可以使我们的系统拥有这样的VALUE价值。最后一层是MEANS形式,也就是Reactive系统的表现形式是什么。 + +![reactive宣言](pic/reactive宣言.png) + + + +##### 1.1.1 VALUE-即时响应性 (Responsive) + +我们知道CAP理论:一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。而一个Reactive的系统,则宣称可以满足高吞吐量 、低延迟/顺畅无卡顿、资源占用小三者之中的两项。 + +> 在C10K场景下,当有海量的网络请求接入时,如果采用servler这种一个请求一个线程的模型,将会需要一个庞大的线程池,造成大量上下文切换开销和大量内存占用。 而如果线程池的线程数消费能力不足,则又会造成请求在待处理队列中的积压,导致超时或者高延时,甚至对前端失去响应性。 + +那问题就来了,难道以前的应用无法同时满足这三者之中的两项吗?为了具体的对比,我们在这里选取Java中经典的网络编程模型:servlet,来作为对比。 + +servlet是javaee的传统经典规范,旨在用多线程模型解决网络高并发的场景。那让我们来到C10K的场景,当有海量的网络请求接入时,servlet模型是怎样处理的呢?首先我们需要囤积一个很大的线程池,用于处理即将来到的请求,当请求量上升的时候,一个请求一个线程的模型将会同时启动大量的线程,假如线程池已经使用完毕,那么剩余的请求将不得不在阻塞队列中等待消费处理,甚至会造成超时等异常情况。那我们能不能启动无限的线程用于承担请求呢?线程并非轻量级资源,代价将会是大量的内存占用,以及大量的线程上下文切换开销。 + +分析完servlet模型之后,让我们回过头来看高吞吐量 、低延迟/顺畅无卡顿、资源占用小这三者。当试图实现高吞吐量的时候,不得不囤积一个很大的线程池用于处理请求,那么资源占用将会是很大的,同时由于线程池不能无限增长,当请求数量超过线程消费能力的时候(在高并发下这是正常的),剩余的请求将会在阻塞队列中等待,前端系统感知到的就是大量的长耗时和超时,故不能符合低延迟/顺畅无卡顿的特性。 + +那么Reactive的系统凭什么可以做到在高并发的时候,还可以资源占用小或者低延迟呢?那就要靠第二层,两种手段来解决这个问题了。 + +最后让我们再来回顾一下即时响应性 (Responsive)官方的定义: + +*只要有可能, 系统就会及时地做出响应。即时响应是可用性和实用性的基石, 而更加重要的是,即时响应意味着可以快速地检测到问题并且有效地对其进行处理。即时响应的系统专注于提供快速而一致的响应时间, 确立可靠的反馈上限, 以提供一致的服务质量。这种一致的行为转而将简化错误处理、 建立最终用户的信任并促使用户与系统作进一步的互动* + +什么是"只要有可能"呢,我的理解是,此时系统资源(CPU、网卡等)还未达到瓶颈,但由于我们线程池容量的限制,导致并发量上不去。当然我们也可以通过线程池调优找到最佳的线程数量,但在不同的压力下,这并不是一个固定的值。所以,当实际上系统资源并没有达到瓶颈时,瓶颈将受限于我们的线程模型。 + + + +##### 1.1.2 FROM-回弹性(Resilient) + +让我们先来看一下回弹性的官方定义: + +*系统在出现失败时依然能保持即时响应性, 每个组件的恢复都被委托给了另一个外部的组件, 此外,在必要时可以通过复制来保证高可用性。 因此组件的客户端不再承担组件失败的处理。* + +> 反应式编程中将会强制组件考虑失败的情形,所以在真正失败发生的时候,由此构建的系统,将会对错误具有更强的承受处理能力,即更具回弹性。 + +这段话是什么意思?让我们从后端技术视角来试图解读一下。在网络环境中,传统RPC框架讲究的是像调用本地服务一样调用远程服务,即帮你屏蔽网络的不可靠性。而在实际的网络环境中,尤其是云原生的环境下,网络天然就是不可靠的。 + +Reactive说:让我们拥抱网络的不确定性吧。这就是反应式接口onError方法的由来,无论是在本地还是在网络中,reactive编程模型不会让你屏蔽失败的可能性,而是强迫你必须思考并处理失败的情形,所以在真正失败发生的时候,由此构建的系统,将会对错误具有更强的承受处理能力,即更具回弹性。 + +上面是我对回弹性的第一个理解,但有人可能会说,不就是一个try-catch的事情吗?在传统的编程模型中,我只要在调用的时候catch可能的异常,并加以处理,不一样可以起到相同的作用吗?是的,假设只有onError一个接口,那么两者并没有本质的不同。 + +但让我们思考一个集群的场景,A->B->C这样一条链路,假如上游B的生产能力非常强,超过了C的消费能力,那将会导致什么?C可能会被压垮,继而导致B->C的熔断。在经典的解决方案中,网络调用使用熔断机制来进行故障转移。当C不可用的时候,将会触发B的熔断,而B的熔断,又将回馈给上游A系统,继而造成这种错误在整条链路中的传播。 + +但这种传播本身并不是没有代价的,重则造成整个系统的雪崩,哪怕有兜底机制,也会造成资源的极大浪费。因为当B->C不可用时,A仍然在不停的处理上游请求,CPU大量的消耗在序列化反序列化中,当请求到达B时,才发现已经不可用了,整个上游的资源都在无谓的浪费中,严重时甚至可以挤压其他正常功能,最终造成整个服务的超载。 + +那Reactive有什么办法能够避免这种雪崩和资源浪费呢?其实问题的关键就在于,一条链路的处理能力,其实取决于吞吐最低的那一环,在A->B->C的过程中,AB的速度再快都没有用,反而会压垮C,那么假如我们可以将C的消费能力通知到A和B,让A和B只处理C能承受的压力,不就可以确保整条链路的健康,同时也可以避免无谓的资源消耗了吗?这就是背压的机制,reactive系统定义了背压这样一种压力反馈的交互方式,每个下游系统都把自己的消费能力通知到上游,上游将会按照这种契约把相应数量的请求给到下游,从而进行保护。 + +![reactive2](pic/reactive2.png) + +同时为了避免错误在系统中的传播,造成资源的浪费,反应式编程可以通过背压的方式对消费端起到保护作用。就如同上图一样,拥有背压机制的系统,消费端可以决定自己什么时候喝水,喝多少水。从而在下游系统遇到异常或者吞吐量跟不上的情况下,对整条链路进行保护,避免发生雪崩或者熔断,从而造成资源的浪费。 + + + +##### 1.1.3 FROM-弹性(Elastic) + +反应式宣言对于弹性的定义: + +*系统在不断变化的工作负载之下依然保持即时响应性。 反应式系统可以对输入负载的速率变化做出反应,比如通过横向地伸缩底层计算资源。 这意味着设计上不能有中央瓶颈, 使得各个组件可以进行分片或者复制, 并在它们之间进行负载均衡。* + +比起从抽象的架构层去理解这个概念,或许我们可以从另一个更具体的技术角度去解读它。 + +什么是不断变化的工作负载呢?让我们回到经典的servlet模型,当请求的压力不断变化时,我们的系统是否具有弹性呢?即是否"可以对输入负载的速率变化做出反应"?上文中我们谈到过,servlet使用多线程模型解决并发问题,当请求数量小于消费线程的数量的时候,看起来并没有什么问题。 + +但如果我们设置的线程数量太小,那么大量的请求将会积压,实际上此时系统资源可能并没有达到瓶颈,而瓶颈在于我们的线程模型。 + +但当请求不断增加,我们是否可以把消费线程无限制的调高呢?答案是不行的,在JVM中一个Java线程将会映射到一个内核线程上,它将占用数百KB到数MB的内存空间,同时随着线程数量的增加,造成线程上下文切换的开销不断增加。 + +那么当线程数量不断增加的时候,系统的吞吐量将会怎么变化呢? + +根据下图的通用伸缩性法则,N是线程的数量;α对应的是系统中的串行部分,对应到Tomcat中,我们可以理解为消费线程从阻塞队列中获取请求的部分;β是一致性系数,随着线程的增加,上下文切换不断增加,我们可以理解β一致性成本也会不断增加。所以随着线程的增加,α和β将会不断增大,最终系统的吞吐量将会像绿色曲线一样,在达到最佳线程数之后开始不断下降,这也符合我们的调优经验,即线程不能无限增加,存在最佳线程数量的限制。 + +所以当请求的压力大于线程池消费能力的时候,这种线程模型就不具备"在不断变化的工作负载之下依然保持即时响应性/可以对输入负载的速率变化做出反应"这样的特性。 + +那么reactive模型是怎么解决这个问题呢?假如可以在不断变化的输入负载下,始终以当前资源(CPU)允许的最大能力进行处理,那我们就可以不用担心瓶颈在我们应用上,当系统达到瓶颈时,那说明已经达到系统资源(CPU)的瓶颈,此时的解决方案就不再是单个应用的范围了,可采用集群横向伸缩的方式进行解决。 + +那如何做到这样的模型呢?根据公式,假如我们可以把α变为0,把β也变为0,那我们将会得到一条C(N)=N的曲线,即可满足我们的设想。那意味着我们的系统不能存在串行部分,也不能存在上下文切换这样无谓的CPU时间片开销。这时我们可以想到,经典的EventLoop模型,假如一个CPU只使用一个线程跑满,那不就可以避免以上的问题?所以EventLoop线程模型,就是解决系统伸缩性的线程模型。 + +![reactive3](pic/reactive3.png) + +![reactive4](pic/reactive4.png) + +#### 1.2 EventLoop+NIO + +我们谈到网络是云原生应用开发环境中的一等公民,当我们采用EventLoop模型之后,网络IO该如何处理呢?继续使用阻塞IO,那么将会阻塞我们的EventLoop线程,整个应用就会失去响应,所以EventLoop线程模型天然的就要结合NIO来使用。这也是Reactive技术的一种具体表现形式,甚至从狭义的角度讲,如果一个应用使用了EventLoop+NIO这样的编程模型,我们就可以称它为reactive应用,因为它具备了reactive系统的几种特征。 + +![reactive5](pic/reactive5.png) + +当使用EventLoop+NIO,那么我们的单个runtime就已经具备了伸缩性和回弹性的特征,其实借助的是操作系统提供的NIO机制以及它的回调函数功能。但当我们从单个runtime拓展到整个集群,每个应用的之间该如何传递这种reactive交互的语义呢?即在多个runtime之间,该如何通过网络传递背压这样的机制。 + +这是reactive stream规范提出协议,而像rsocket、grpc这些网络协议,也实现了这样的规范,所以如果要在整个集群进行reactive语义的传递,我们可以依赖于使用这类网络协议。 + +![reactive6](pic/reactive6.png) + + + +### 2. Reactive的价值 + +#### 2.1 云原生下的Reactive + +在前面,我们从一些比较具体的技术角度解读了reactive宣言的一些特征,也阐述了一些具体的技术原理。那么这样一种技术为什么会在后端架构中变得流行呢?它带来的价值是什么呢?其实这要从云原生开始谈起。 + +云原生已经成为当前技术发展的一个重要趋势,而云原生的事实标准就是K8S,当你的应用运行在K8S之上,并且你符合CNCF定义的一系列标准,那么他们就称你的应用是一个云原生应用。 + +![reactive7](pic/reactive7.png) + +我们可以把云基础设施看做一条高速公路,CNCF提供了一系列的组件能够使我们的应用跑在这条高速公路上,从这个角度来讲,云原生是底层的基础设施层,它更关注应用的外在,即运维、部署、系统级监控等等。 + +提到云原生和K8S,那我们就不得不提另一个云原生的支柱:Istio。Istio使用sidecar边车的设计模式,在应用的外部帮助应用处理网络请求,而sidecar也是各种云原生组件适配的方式。 + +![reactive8](pic/reactive8.png) + +但我们应用的性能提高了么?在你的摩托车外面挂上一个这样的sidecar,它只会让你的应用跑的更加平稳,但不会让你跑的更快。而应用开发人员更关注的是如何让应用跑的更快,消耗更少的CPU和更少的内存,这是云原生基础设施无法帮你做到的,甚至说云厂商希望你的应用消耗更多的资源,这样才可以赚取更高的利润。 + +![reactive9](pic/reactive9.png) + +而reactive技术,就是帮助应用更加节省资源的手段,reactive关注的是应用的内部,即通过线程模型和网络模型等手段,帮助应用更快更省以及更加强健。这就是reactive的意义。在云时代,成本以账单的形式透明存在,所以当我们的应用使用reactive技术后,可以显著的降低成本,这就是reactive流行的重要原因之一。 + + + +### 3. 使用Reactive + +#### 3.1 Reactive理论模型 + +#### 3.2 Reactive异步编程模式 + +##### 3.2.2 Reactive和协程 \ No newline at end of file diff --git "a/sig/reactive/talk/blog/Reactive\346\250\241\345\274\217\345\234\250Trip.com\346\266\210\346\201\257\346\216\250\351\200\201\345\271\263\345\217\260\344\270\212\347\232\204\345\256\236\350\267\265.md" "b/sig/reactive/talk/blog/Reactive\346\250\241\345\274\217\345\234\250Trip.com\346\266\210\346\201\257\346\216\250\351\200\201\345\271\263\345\217\260\344\270\212\347\232\204\345\256\236\350\267\265.md" new file mode 100644 index 0000000..af7b61c --- /dev/null +++ "b/sig/reactive/talk/blog/Reactive\346\250\241\345\274\217\345\234\250Trip.com\346\266\210\346\201\257\346\216\250\351\200\201\345\271\263\345\217\260\344\270\212\347\232\204\345\256\236\350\267\265.md" @@ -0,0 +1,505 @@ + + + + +# 《Reactive模式在Trip.com消息推送平台上的实践》 + +### 一、背景 + +#### 1.1 业务需求 + +Trip.com消息推送平台主要负责Trip.com在海外的邮件等渠道的营销消息推送,系统整体设计为面向上游消息的流式架构,当接收到上游的请求之后,经过一系列的计算逻辑,最后将会调用下游第三方发送接口,将邮件等消息通过网络发送出去。Trip.com消息推送平台是典型的IO密集型应用。 + +#### 1.2 当前解决方案 + +Trip.com的业务层以Java技术栈为主,其中主要web服务基于同步+阻塞IO的servlet模型,也有少部分web服务基于异步servlet。servlet是经典的JavaEE解决方案,旨在用多线程模型解决IO高并发问题。这种同步编程模型的优点是开发简单,易于进行问题追踪,并且对开发人员的要求比较低,有利于业务的快速开发。 + +![同步阻塞IO线程模型](pic/bio.png) + +Tirp.com消息推送平台也是基于同步+阻塞IO的servlet模型架构。当客户端发起网络请求的时候,请求首先会由Tomcat容器的Acceptor线程进行处理,将Channel放到待处理请求队列然后通过Poller线程进行IO多路复用的监听,当Poller监听到Channel的可读事件后,请求体将会从缓冲区被读入内存,然后交由Tomcat容器的Worker线程池进行消费。由于需要使用阻塞IO调用下游的第三方发送接口,所以Worker线程池需要启动大量的线程进行并发操作,根据Tomcat配置文件,最多可能启动1024个worker线程。 + +```xml + + +``` + +**代码示例** + +Trip.com消息推送平台使用AWS的SES服务进行邮件发送,在发送Email时将会调用AWS的同步SDK: + +```java +SendEmailResult sendEmail(SendEmailRequest sendEmailRequest); +``` + +而AWS的同步SDK使用的是apache的`HttpClient`,底层采用的BIO模式将会阻塞当前线程直到会话缓冲区有数据到达或到达超时时间。 + +### 二、存在的问题 + +而随着业务量上涨带来上游消息负载增加,原有的阻塞IO模型在高并发下,会有大量线程处于阻塞状态,导致应用需要囤积大量的线程以应对峰值压力。过多的线程将会造成大量的内存占用和频繁线程上下文切换的开销,所以原有的servlet线程模型具有CPU利用率低、内存占用大、对异常请求不具备弹性等缺点。该平台在压力峰值时需要部署大量机器,它主要具有以下性能上的问题: + +#### 2.1 线程上下文切换开销 + +一次请求的IO时间平均在1200ms,最高能达到50000ms,而计算时间只有1~2ms,根据最佳线程公式理论上1C需要600~2500个线程。囤积如此多的线程将会造成大量的上下文切换开销和上GB的内存占用。但若是使用少量的线程,将可能由于线程数量的限制,导致请求量过高时拿不到处理线程,最终请求超时,不具备低延迟等特性。 + +#### 2.2 部署成本高 + +若是采用主流的2C4G容器配置,理论上将需要1000+的线程用于处理请求,这将占用大概1GB的内存空间。同时若并发请求数>线程数时,需要采用水平扩容增加服务的吞吐量,所以服务需要按照峰值并发进行预估部署,造成空闲时间大量资源的浪费。 + +#### 2.3 超时风险 + +一次IO最高能达到50s,当有异常请求导致响应时间突增时,因为会阻塞线程,导致线程池中的线程大部分都被阻塞,从而无法响应新的请求。在这种情况下,少量的异常请求将会导致上游大量的超时报错,因此服务不具有弹性。 + + + +### 三、解决方案 + +面对传统BIO模式在IO密集型场景下的缺点,借助NIO和多路复用技术可解决上述BIO问题,目前Java项目对接NIO的方式主要依靠回调,代码复杂度高,降低了代码可读性与可维护性。随着Reactive反应式架构的流行,业界有一些公司开始推动服务的全异步升级,开始采用Reactive架构来解决此类问题。而Trip.com也开始逐渐重视服务网络IO的性能问题,已有部分团队开始进行Reactive实践。 + +Reactive 构建的程序代表的是异步非阻塞、函数式编程、事件驱动的思想。随着近年来Reactive编程模式的发展,能达到高性能与可读性的兼顾。Trip.com消息推送平台利用Reactive相关技术对系统进行异步非阻塞IO改造,主要希望达到以下两个目标: + +1)提升单机的吞吐量,提高有效CPU使用率、降低内存占用、保证业务请求突增时系统的可伸缩性,最终降低硬件成本。 + +2)使用Reactive编程模型,替代处理NIO常用的异步回调模式,积累对同步阻塞应用进行异步非阻塞化升级的重构经验。 + +#### 3.1 什么是Reactive? + +> 反应式宣言:来自不同领域的组织正在不约而同地发现一些看起来如出一辙的软件构建模式。它们的系统更加稳健,更加有可回复性,更加灵活,并且以更好的定位来满足现代的需求。这些变化之所以会发生,是因为近几年的应用需求出现了戏剧性的变化。仅仅在几年之前,大型应用意味着数十台服务器,数秒的响应时间,数小时的离线维护时间以及若干GB的数据。而在今天,应用被部署在一切场合,从移动设备到基于云的集群,这些集群运行在数以千计的多核心处理器的之上。用户期望毫秒级的响应时间以及100%的正常运行时间。数据则以PB为单位来衡量。 +> +> 昨天的软件架构已经完全无法地满足今天的需求。我们相信,一种条理分明的系统架构方法是必要的,而且我们相信关于这种方法的所有必要方面已经逐一地被人们认识到:我们需要的系统是反应式的,具有可回复性的,可伸缩的,以及以消息驱动的。我们将这样的系统称之为反应式系统。以反应式系统方式构建的系统更加灵活,松耦合和可扩展。这使得它们更容易被开发,而且经得起变化的考验。它们对于系统失败表现出显著的包容性,并且当失败真的发生时,它们能用优雅的方式去应对,而不是放任灾难的发生。反应式系统是高度灵敏的,能够给用户以有效的交互式的反馈。 + +**Reactive宣言** + +![reactive宣言](pic/reactive宣言.png) + +2013年6月,Roland Kuhn等人发布了《反应式宣言》, 该宣言定义了反应式系统应该具备的一些架构设计原则。符合反应式设计原则的系统称为反应式系统。根据反应式宣言,反应式系统需要具备即时响应性(Responsive)、回弹性(Resilient)、弹性(Elastic)和消息驱动(Message Driven)四个特质。 + +**VALUE-即时响应性 (Responsive)** + +*只要有可能,系统就会及时地做出响应。即时响应是可用性和实用性的基石,而更加重要的是,即时响应意味着可以快速地检测到问题并且有效地对其进行处理。即时响应的系统专注于提供快速而一致的响应时间,确立可靠的反馈上限,以提供一致的服务质量。这种一致的行为转而将简化错误处理、建立最终用户的信任并促使用户与系统作进一步的互动* + +反应式系统具备及时响应性,可以提供快速的响应时间,在错误发生时也会保持响应性。 + +**FORM-回弹性(Resilient)** + +*系统在出现失败时依然能保持即时响应性,每个组件的恢复都被委托给了另一个外部的组件,此外,在必要时可以通过复制来保证高可用性。因此组件的客户端不再承担组件失败的处理。* + +反应式系统通过背压等特性避免错误在系统中的传播,所以在失败发生的时候,反应式系统将会对错误具有更强的承受处理能力。背压是reactive stream定义的规范,可以使用rsocket、grpc这类网络协议实现背压的机制。 + +**FORM-弹性(Elastic)** + +*系统在不断变化的工作负载之下依然保持即时响应性。反应式系统可以对输入负载的速率变化做出反应,比如通过横向地伸缩底层计算资源。这意味着设计上不能有中央瓶颈,使得各个组件可以进行分片或者复制,并在它们之间进行负载均衡。* + +反应式系统的瓶颈不在于线程模型,在不同的工作负载下,使用EventLoop线程模型将始终提供CPU资源允许的计算能力,当达到计算能力瓶颈时可以横向拓展CPU计算资源。反应式系统通过EventLoop+NIO模型,避免线程的上下文开销,同时也避免线程池资源的大小成为系统的瓶颈。 + +![reactive5](pic/reactive5.png) + + + +#### 3.2 使用Reactive技术进行重构 + +3.1章节我们谈论了Reactive理论模型,以及它的部分技术原理。现在,我们要使用Reactive技术重构Trip.com消息发送平台。根据reactive思想的指导,对于IO密集型应用,我们可以采用EventLoop+NIO的方式对传统的同步阻塞IO模型进行优化。 + +在整个系统中,首先介绍三个主要的中间件: + +1. Tomcat:网络中间件,负责接收和响应网络请求 +2. RPC Framework(soa):Trip.com集团的RPC框架,提供了同步和异步两种服务模式 +3. AWS SDK:使用AWS的异步SDK,通过NIO调用AWS服务 + +##### 3.2.1 线程模型设计 + +在原同步版本中,首先使用Tomcat的Worker线程接收和处理request并执行同步逻辑,而后通过AWS的同步SDK进行BIO调用,此时worker线程将会block在IO调用上。当网络IO响应时,该worker线程将被唤醒,拿到response并执行响应逻辑。同步阻塞的线程模型是比较简单的,worker线程基本负责了整个流程。 + +而采用EventLoop+NIO的异步非阻塞模式,将会无可避免的引入回调函数,为了回调流程的逻辑清晰和故障隔离等功能考虑,将会引入几组不同的回调线程池,来负责不同模块的回调逻辑。 + +整个异步流程的线程模型设计如图所示: + +![异步线程模型](pic/异步线程模型.png) + +**request流程:** + +**1.1** 使用Tomcat接收和处理网络请求 + +使用Tomcat的Acceptor线程接收socket连接事件,然后封装成NioChannel压入events queue中。然后Poller线程通过Selector获取到可读的socket,并传递给Worker线程进行处理。该部分与原版本的同步模型相同,Tomcat线程模型如下图所示: + +![tomcat](pic/tomcat.png) + +**1.2** 业务逻辑处理部分 + +Tomcat的Worker线程将负责执行同步逻辑,worker线程将会依次同步执行Tomcat逻辑、RPC Framework逻辑、业务逻辑、AWS SDK逻辑。 + +Worker线程执行完同步逻辑之后,将会把封装好的request放入EventLoop的events queue中,等待EventLoop的处理。 + +**1.3** AWS SDK NIO异步处理 + +AWS的异步SDK,使用Netty进行网络IO的传输,其内部会内置一个Netty的EventLoop线程池,负责网络IO的序列化反序列化。AWS的EventLoop线程池定义如下,使用的是Netty的`NioEventLoopGroup`: + +```java +int numThreads = Optional.ofNullable(builder.numberOfThreads).orElse(0); +ThreadFactory threadFactory = Optional.ofNullable(builder.threadFactory) + .orElse(new ThreadFactoryBuilder() + .threadNamePrefix("aws-java-sdk-NettyEventLoop") + .build()); +return new NioEventLoopGroup(numThreads, threadFactory); +``` + +**1.4** 注册回调函数 + +①channelFuture注册回调 + +Netty使用NIO进行网络传输,并将对应回调函数注册到对应的channelFuture上。 + +②AWS SDK注册回调 + +将channelFuture对应的Promise转换成`CompletableFuture`,AWS SDK通过`CompletableFuture.whenCompleteAsync`方法将回调函数提交给`futureCompletionExecutor`线程池。 + +```java +responseHandlerFuture.whenCompleteAsync((r, t) -> { + if (t == null) { + responseFuture.complete(r); + } else { + responseFuture.completeExceptionally(t); + } +}, futureCompletionExecutor); +``` + +`futureCompletionExecutor`线程池的设置如下,为上图中的AWS SDK内置的回调线程池。 + +```java +int processors = Runtime.getRuntime().availableProcessors(); +int corePoolSize = Math.max(8, processors); +int maxPoolSize = Math.max(64, processors * 2); +ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maxPoolSize, + 10, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(1_000), + new ThreadFactoryBuilder() + .threadNamePrefix("sdk-async-response") + .build()); +``` + +③业务逻辑注册回调 + +AWS使用`futureCompletionExecutor`线程池执行回调逻辑,业务逻辑使用Reactor的`Mono`异步编程模型(3.2.3章节介绍),所以需要将AWS的`CompletableFuture`响应转换为`Mono`: + +```java +Mono responseMono = Mono.fromCompletionStage(() -> { + CompletableFuture responseFuture = sesAsyncClient.sendEmail(sendEmailRequest); + return responseFuture; +}); +``` + +在业务逻辑代码中,使用`Mono`进行Reactive风格的异步编程。最后,由于Trip.com的RPC Framework在异步编程模型中仅支持`ListenableFuture`,所以我们需要将业务代码中的`Mono`Future类型转换为`ListenableFuture`类型,并返回给RPC Framework,在这里我们使用`Mono.subscribe()`方法: + +```java +SettableFuture listenableFuture = SettableFuture.create(); +responseMono.subscribe( + response -> listenableFuture.set(response), + throwable -> listenableFuture.setException(throwable)); +return listenableFuture; +``` + +④RPC Framework注册回调 + +当RPC Framework接收到一个异步调用结果`ListenableFuture`后,将会通过`addListener()`方法注册RPC Framework层级的回调函数: + +```java +responseFuture.addListener(() -> {...}, rpcExecutorService); +``` + +在这里RPC Framework使用了自己定义的回调线程池`rpcExecutorService`,即上图中的SOA回调线程池: + +```java +ThreadPoolConfig config = threadPoolConfigProvider.getThreadPoolConfig(key); +rpcExecutorService = new RpcExecutorService(config.taskWrapper(), new ThreadPoolExecutor( + config.corePoolSize(), + config.maximumPoolSize(), + config.keepAliveTimeInMills(), + TimeUnit.MILLISECONDS, + config.workQueue(), + config.threadFactory(), + config.rejectedExecutionHandler() +)); +``` + + + +> 至此,异步回调的链路组装完成。等待NIO收到响应的时候,将会依次触发上面的回调函数,进行响应流程的处理。 + +**response流程:** + +**2.1** AWS SDK Netty响应 + +当netty收到IO响应数据之后,对应的EventLoop线程将会处理可读事件并执行回调函数。EventLoop首先会读取缓冲区中的数据并进行反序列化,而后执行channel的pipeline,将反序列化后的response传递给下一流程。 + +**2.2** AWS SDK 异步回调 + +AWS SDK使用1.4中提到的AWS回调线程池,进行回调逻辑的处理。AWS SDK的回调函数主要负责AWS内置的response处理,例如AWS的监控、埋点、日志等。 + +**2.3** 业务逻辑的异步回调 + +当AWS的异步回调流程完成之后,回调线程将会进入我们的业务代码注册的回调函数中,此时线程是1.4中定义的`sdk-async-response`线程。在业务逻辑的回调响应中,我们可以定义自己的业务回调线程池进行处理,也可以直接使用AWS的回调线程进行处理。由于操作非常轻量,所以在这里我们没有再额外定义一个业务回调线程池,而是直接使用了1.4中的线程池,减少了一次线程切换的开销。 + +**2.4** RPC Framework的异步回调 + +如1.4所述,当业务回调逻辑全部执行完毕之后,将会触发`ListenableFuture`的回调流程,此时进入RPC Framework这一层的回调逻辑处理。首先由`aws-async-callback`线程继续进行同步处理,而后将会把`(ListenableFuture)responseFuture`中的回调函数提交给`rpcExecutorService`线程池处理。在RPC Framework的回调函数中,将会执行RPC的监控、埋点等功能(可参考dubbo),最终将会把异步响应传递给Tomcat。 + +Servlet3.0提供了`AsyncContext`用来支持异步处理请求。RPC Framework在异步请求处理开始的时候,将会通过`servletRequest.startAsync()`获取对应的`AsyncContext`对象,此时既不关闭响应流也不进行响应的返回。当RPC Framework执行完所有的异步回调逻辑之后,此时`rpcExecutorService`线程将会调用`asyncContext.complete()`将上下文传递给Tomcat容器: + +```java +finally { + try { + ...... + asyncContext.complete(); + } catch (Throwable e) { + ...... + } +} +``` + + + +**2.5** Tomcat的异步响应 + +`asyncContext.complete()`使Tomcat容器接收到`ASYNC_COMPLETE`事件,在`NioEndpoint.processSocket()`方法中,将会通过`Executor executor = getExecutor();`操作获取到Worker线程池(注①),而后`rpcExecutorService`线程把响应操作写入到Worker线程池的events queue中,之后worker线程将响应流写回客户端(注②)。 + +```java +public boolean processSocket(SocketWrapperBase socketWrapper, + SocketEvent event, boolean dispatch) { + try { + if (socketWrapper == null) { + return false; + } + SocketProcessorBase sc = null; + if (processorCache != null) { + sc = processorCache.pop(); + } + if (sc == null) { + sc = createSocketProcessor(socketWrapper, event); + } else { + sc.reset(socketWrapper, event); + } + // 注① + Executor executor = getExecutor(); + if (dispatch && executor != null) { + // 注② + executor.execute(sc); + } else { + sc.run(); + } + } catch (RejectedExecutionException ree) { + getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree); + return false; + } catch (Throwable t) { + ExceptionUtils.handleThrowable(t); + // This means we got an OOM or similar creating a thread, or that + // the pool and its queue are full + getLog().error(sm.getString("endpoint.process.fail"), t); + return false; + } + return true; +} +``` + + + +> 至此,响应流写回客户端,整个请求-响应过程完成。 + +##### 3.2.2 异步线程模型总结 + +如3.2.1所述,为了实现异步非阻塞的流程,不仅需要Tomcat的Worker线程池,还需要引入两个回调线程池和一个Netty的EventLoop线程池。 + +其中一个是AWS异步SDK的回调线程池,主要负责AWS功能的处理,使用的异步编程模型是`CompletableFuture`;另外一个是RPC Framework的回调线程池,主要是封装了Servlet3.0的AsyncContext并提供异步服务的能力,使用的异步编程模型是`ListenableFuture`。 + +而我们的业务代码使用了Reactor的`Mono`异步编程模型,所以需要涉及不同Future类型的转换,通过Reactor丰富的操作符,我们可以很容易的做到这一点。 + +**预期达到的效果** + +使用NIO方式发起AWS的调用,避免线程阻塞,从而最大限度的消除上述BIO缺点,提高系统性能。最终使得应用符合Reactive架构理念,从而具备弹性、容错性,以降低部署成本,提高资源利用率。 + +##### 3.2.3 NIO异步编程模型选择 + +NIO 消除了线程的同步阻塞,意味着只能异步处理IO的结果,这与业务开发者顺序化的思维模式有一定差异。当业务逻辑复杂以及出现多次远程调用的情况下,多级回调难以实现和维护。 + +AWS原生异步SDK的调用模式如下,使用了Java8的组合式异步编程`CompletableFuture`: + +```java +default CompletableFuture sendEmail(SendEmailRequest sendEmailRequest) +``` + +![图片4](pic/图片4.png) + +设计NIO编码,业界主流的编码方式主要有以上几种,通过`CompletableFuture`和Lambda表达式,可以快速实现轻量业务异步封装与编排,与Callback相比可以避免方法多层嵌套问题,但面对相对复杂业务逻辑时仍存在以下局限: + +- 难以简单优雅实现多异步任务编排; +- 难以处理实时流式场景; +- 难以支持高级异常处理; +- 不支持任务延迟执行。 + +使用Reactive模型能够解决上述Future的局限。例如,使用Reactor封装AWS的异步调用: + +```java +// sending with async non-blocking io +return Mono + .fromCompletionStage(() -> { + // 注① + CompletableFuture responseFuture = awsAsyncClient.sendEmail(sendEmailRequest); + return responseFuture; + }) + // 注② thread switching: callback executor + // .publishOn(asyncResponseScheduler) + // 注③ callback: success + .map(response -> this.onSuccess(context, response)) + // 注④ callback: failure + .onErrorResume(throwable -> { + return this.onFailure(context, throwable); + }); +``` + +> ①调用AWS的异步SDK,将返回的`CompletableFuture`转成的`Mono`。 +> +> ②如2.3所述,可以使用`Mono.publishOn()`将业务逻辑的回调函数放入自定义的线程池执行,也可以继续使用AWS的回调线程继续执行,在这里没有使用自定义的线程池。 +> +> ③如果执行成功,则执行`map()`中的回调方法 +> +> ④如果执行抛出异常,则执行`onErrorResume()`中的回调方法 + +从上面简单对比可以看出,相比Future,基于Reactive模型丰富的操作符组合(filter/map/flatMap/zip/onErrorResume等高阶函数)代码清晰易读,搭配Lamda可以轻松实现复杂业务场景任务编排。 + +**Reactor异步原理** + +`reactor-core`是一层编程框架,它提供的是reactive风格的编程模式,以及异步调用的编排能力。而本身并没有真正网络IO异步回调的功能,真正的异步回调功能是底层网络IO框架的Future提供,比如上面AWS返回的`CompletableFuture`才是真实绑定到网络IO上的Future,而Reactor仅仅是将其包装,方便进行reactive编程。 + +从`fromCompletionStage`方法中可以找到,这里将实际的`CompletionStage`包装成了`MonoCompletionStage`(注①),但在实际订阅的时候,其实是将`Mono`的回调函数放入了`future.whenComplete`中(注②),所以说`Mono`在这里是`CompletableFuture`的外层包装。 + +```java +final class MonoCompletionStage extends Mono + implements Fuseable, Scannable { + + static final Logger LOGGER = Loggers.getLogger(MonoCompletionStage.class); + + // 注① + final CompletionStage future; + + MonoCompletionStage(CompletionStage future) { + this.future = Objects.requireNonNull(future, "future"); + } + + @Override + public void subscribe(CoreSubscriber actual) { + Operators.MonoSubscriber + sds = new Operators.MonoSubscriber<>(actual); + + actual.onSubscribe(sds); + + if (sds.isCancelled()) { + return; + } + + // 注② + future.whenComplete((v, e) -> { + if (sds.isCancelled()) { + //nobody is interested in the Mono anymore, don't risk dropping errors + Context ctx = sds.currentContext(); + if (e == null || e instanceof CancellationException) { + //we discard any potential value and ignore Future cancellations + Operators.onDiscard(v, ctx); + } + else { + //we make sure we keep _some_ track of a Future failure AFTER the Mono cancellation + Operators.onErrorDropped(e, ctx); + //and we discard any potential value just in case both e and v are not null + Operators.onDiscard(v, ctx); + } + + return; + } + try { + if (e instanceof CompletionException) { + actual.onError(e.getCause()); + } + else if (e != null) { + actual.onError(e); + } + else if (v != null) { + sds.complete(v); + } + else { + actual.onComplete(); + } + } + catch (Throwable e1) { + Operators.onErrorDropped(e1, actual.currentContext()); + throw Exceptions.bubble(e1); + } + }); + } + + @Override + public Object scanUnsafe(Attr key) { + return null; //no particular key to be represented, still useful in hooks + } +} +``` + + + +使用Reactor还有另外一个好处,那就是统一异步编程模型。比如有的异步编程框架提供`ListenableFuture`,有的是`CompletableFuture`,还有`gRPC`、`dubbo`、`webflux`等中间件框架,都提供了自己的异步编程模型实现。如果直接针对各个框架自己的原生实现进行异步编程,将会存在不同风格的代码。而`Reactor`是反应式库的当前标准,使用`Reactor`库可以封装不同异步编程框架的异构实现,使用统一的API执行异步编程。 + + + +### 四、压测对比 + +通过一系列的Reactive技术改造,我们现在已经拥有了一个基于EventLoop+NIO的IO密集型应用,那么它的性能是否如同我们的理论推导一样将会得到提升呢?接下来我们将会通过一系列的性能压测,得到最终的结论。 + +**压测目标:** + +1. 是否能够达到稳定状态,以及达到稳定状态后,系统表现和指标 +2. 对两个应用在不同压力下的指标,进行全面的对比,得出压测结论 + +**以下数据均为"稳态"时数据,稳态定义如下:** + +| 指标 | 稳态数值 | 说明 | +| :------ | :------------------- | :-------- | +| **CPU** | **60%** | | +| **RT** | 无明显上升 | 采用P95线 | +| **GC** | 无FullGC,无明显上升 | | + +#### 压测结果 + +当原应用和新应用都达到上述定义的稳态条件时,我们得到了一组对比数据。通过与原应用的压力测试结果对比,我们发现使用EventLoop+NIO的新应用,在相同硬件资源下,QPS能够提升2~3倍,RT缩短近50%,同时在内存占用上也得到了一定的优化。证明该应用在经过Reactive技术改造后,性能较之前同步阻塞的Servlet架构得到了明显提升。 + + +### 五、总结和展望 + +在本文中我们首先介绍了Trip.com消息推送平台服务的现状,以及现有同步+阻塞模式在IO密集型场景下的缺点。接下来我们通过分析如何解决这些缺点,引入了业界流行的reactive反应式架构。接下来在reactive宣言的弹性和伸缩性两种手段中,总结出了EventLoop、NIO、背压等技术手段,最后通过这些具体的技术手段来实现我们应用的升级重构。最终根据压测结果,可以看到服务性能较之前servlet架构得到了明显提升。 + +随着云原生浪潮的到来以及物联网、边缘计算的推进,未来应用间网络通讯的量级将会变得越来越庞大,网络IO会是系统架构中的一等公民。如何使我们的应用能够具有更高的性能和更健壮的特性,以及如何降低硬件资源的成本,这些挑战将促使应用开发人员不断的学习实践类似reactive相关的技术。而在学习实践的过程中,对经典的servlet架构的优化重构一定是具有代表性意义的。 + +在适合的业务场景下,反应式技术架构能够有效提升服务吞吐能力,降低业务编排复杂度,帮助构建云原生时代整体系统快速即时反应能力。但同时构建 Reactive 模式的程序也为开发者带来更高的要求,面临比同步更为复杂的编程模型,需要更好的处理好阻塞和写出更优秀的异步代码。希望与对反应式技术感兴趣的同学和团队多多交流。 + +#### 【参考文档】 + +[[1] 高德云图异步反应式技术架构探索和实践](https://mp.weixin.qq.com/s?__biz=Mzg4MzIwMDM5Ng==&mid=2247485086&idx=1&sn=cde9445ec0669df16bc1f53db2c3d186&chksm=cf4a5e7df83dd76b04d4c212d20ff739d1a076b515ecd24804b40fd4d543a373505b7dad55a0&scene=0&xtrack=1&key=3ddf0cf622fa72abbf1b614ba595e66943e6aad6836865920b90278c5f0d81235d5272599b99860befaa11bcb5626e6e03fdf39b578b35d5d13db89756b831e9de613e3b283dd4ed1ceccbcda480df15&ascene=1&uin=MjA0NTE5Njk0Mg%3D%3D&devicetype=Windows+10+x64&version=62090523&lang=zh_CN&exportkey=AfGngcXiCj7WgIrmGwSheZo%3D&pass_ticket=IIrtx3qfChenANA%2BVbbiNlVbr%2BKuqUloZWqsjw549hwr5BrO2dPXHYWanBYvRXjB) + +[[2] Reactive架构才是未来](https://mp.weixin.qq.com/s/01SQuSYkNSHQz-0cbxzh_g) + +[[3] 全面异步化:淘宝反应式架构升级探索](https://www.infoq.cn/article/2uphtmd0poeunmhy5-ay) + +[[4] 淘宝应用架构升级——反应式架构的探索与实践](https://time.geekbang.org/dailylesson/detail/100033220) + +[[5] 高性能Java应用层网关设计实践](https://cloud.tencent.com/developer/article/1683383) + +#### 团队招聘信息 + +我们是Trip.com国际事业部研发团队,主要负责集团国际化业务在全球的云原生化开发和部署,直面业界最前沿的技术挑战,致力于引领集团技术创新,赋能国际业务增长,带来更极致的用户体验。 + +团队怀有前瞻的技术视野,积极拥抱开源建设,紧跟业界技术趋势,在这里有浓厚的技术氛围,你可以和团队成员一同参与开源建设,深入探索和交流技术原理,也有技术实施的广阔场景。 + +我们长期招聘有志于技术成长、对技术怀有热忱的同学,如果你热爱技术,无惧技术挑战,渴望更大的技术进步,Trip.com国际事业部期待与您的相遇。 + +目前我们后端/前端/测试开发/安卓,岗位都在火热招聘。简历投递邮箱:**tech@trip.com**,邮件标题:【姓名】-【携程国际业务研发部】- 【投递职位】 + +#### 作者简介 + +KevinTen,携程后端开发工程师,关注Reactive和RPC领域,深度参与开源社区,对Reactive技术有浓厚兴趣。 + +Pin,携程技术专家,Apache Dubbo贡献者,关注RPC、Service Mesh和云原生领域。 \ No newline at end of file diff --git a/sig/reactive/talk/blog/pic/bio.png b/sig/reactive/talk/blog/pic/bio.png new file mode 100644 index 0000000000000000000000000000000000000000..6f4e7cd71b984027a5956ccaaa838caca8e4b6ad GIT binary patch literal 149098 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