前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自研Java协程在腾讯的生产实践

自研Java协程在腾讯的生产实践

作者头像
腾讯大数据
发布2021-09-13 10:40:14
1.7K0
发布2021-09-13 10:40:14
举报

导读 / Introduction

本文是今年QCon java专场《Java协程在腾讯的生产实践》主题分享,分享团队为腾讯大数据JVM团队。本文主要介绍协程的产生背景、java协程的发展历程、社区官方协程Project Loom的设计与实现,以及腾讯自研协程Kona Fiber的产生背景、设计与实现、性能测试和业务实践。

1. 协程产生的背景

Kona

1.1 线程模型

最经典的编程模型是线程模型,它是操作系统层面对cpu的抽象。由于线程模型是一种同步编程模型,它直观、易于理解,因此使用线程模型的开发效率高。但是对于IO密集型的程序,由于每次IO操作都需要Blocking当前线程,因此会产生一次线程切换,线程切换需要在用户态和内核态之间切换,且线程切换需要保存当前线程的执行上下文,一次线程切换的开销在10微秒量级。因此,对于IO密集型程序,cpu很大一部分都用于线程切换,导致cpu的利用率不高。

线程模型的第二个问题是,对于IO密集且高并发的程序,如果不采用异步编程模型,通常是一个线程对应一个并发(因为假设一个线程去做数据库访问操作,线程就被阻塞了,就不能执行其他任务了,只能用另一个线程去执行)。因此对于高并发的程序来说,需要创建大量线程。操作系统为了兼容各种各样的编程语言、执行逻辑,因此操作系统线程预留的栈内存通常较大,一般是8M。由于线程占用的内存较大,即使不考虑cpu利用率的问题,一台机器也很难创建太多线程(只考虑线程栈的内存开销,1万个线程就需要80G内存),因此很难在不使用异步编程框架的情况下,仅靠线程模型支持IO密集型+高并发程序。

1.2 异步模型

异步编程模型是一种编程语言框架的抽象,它可以弥补线程模型对于IO密集型+高并发程序支持的短板。它通过复用一个线程,例如线程在做io操作导致阻塞之前,通过回调函数调用到另一个逻辑单元,完成类似线程切换的操作;异步模型的执行效率很高,因为相比线程切换,它直接调用了一个回调函数;但是它的开发门槛较高,需要程序员自己理解哪里可能产生io操作,哪里需要调用回调函数,如何定期检查io操作是否做完;另外,由于线程的调用栈由一些逻辑上并不相关的模块组成,因此一旦出现crash之类的问题,调用栈比较难以理解,维护成本也较高。

1.3 协程

图1.1展示了线程模型和异步模型在生产效率和执行效率的对比图。可以看到,线程模型的生产效率是最高的,同时它对于IO密集型+高并发程序的执行效率较差。异步模型正好相反,它的生产效率较差,但是如果实现的很完美,它的执行效率是最高的。

协程的出现,是为了平衡线程模型和异步模型的生产效率和执行效率;首先,协程可以让程序员按照线程模型去编写同步代码,同时,尽可能降低线程切换的开销。

图1.1

2. Java协程的发展历程

Kona

上面分析了协程产生的背景,那么Java协程的现状是怎样的呢?

首先,由于Java生态丰富的异步框架,缓解了协程的紧迫性,用户可以用异步框架去解决IO密集型+高并发的程序。

如图2.1所示,列出了Java协程的发展历程。

图2.1

2.1 JKU

Java协程最早是JKU发表的论文+提供的patch。JKU的协程是一种有栈的协程,即每个协程都有自己独立的调用栈。不同于操作系统线程,由于JKU的协程只需要考虑java代码的执行,根据java代码执行时的特点,通常只需要较小的栈就可以满足需要,通常JKU的协程只需要不超过256K的栈。

2.2 Quasar/Kotlin

Quasar和Kotlin是之后出现的方案,它们都不需要修改java虚拟机,只需要修改上层java代码。它们都是无栈协程,所谓无栈协程,是指协程在suspend状态时不需要保存调用栈。那么如果协程切换出去以后,没有保存调用栈,下次恢复执行时,如何读取调用关系呢?Quasar和Kotlin在切换时,会回溯当前协程的调用栈,然后根据这个调用栈生成一个状态机,下次恢复执行时,根据这个状态机恢复执行状态;无栈协程通常不能在任意点切换,只能在被标记的函数切换,因为只有被标记的函数才能生成对应的状态机,Quasar需要加一个@Suspendable的annotation标记可以切换的函数,Kotlin需要在函数定义时加上suspend关键字标记可以切换的函数。

2.3 Project Loom

Project Loom是Openjdk社区推出的官方协程实现,它从立项到今天已经超过3.5年,目前已经包含27个Committer,超过180个author,3200+commits。

图2.1列出了Loom一些重要的时间点。Loom在17年底立项,18年初正式对外推出。19年7月,它发布了第一个EA(Early Access)Build,这时候它的实现还是一个Fiber类。在19年10月的时候,它的接口发生了一个较大的变动,将Fiber类去掉,新增了VirtualThread类,作为Thread类的子类,兼容所有Thread的操作。这时候它的基本思想已经比较清晰了,就是协程是线程的一个子类,支持线程的所有操作,用户可以完全按照线程的方式使用协程。

Loom作为Openjdk的官方实现,它的目标是提供一个Java协程的系统解决方案,兼容已有Java规范、JVM特性(例如ZGC、jvmti、jfr等),最终目标是对整个Java生态的全面兼容。对Java生态的全面兼容既是Loom的优势,同时也是它的挑战。因为Java已经发展了20多年,Loom要在最底层新加入一个协程的概念,需要适配的东西非常多,例如庞大而复杂的标准类库,大量JVM特性。所以,截至目前,Loom仍然有非常多的事情要做,目前还没有达到一个真正可用的状态,图2.1最右边的部分是我们的一个推测,我们猜测到jdk18或jdk19的时候,Loom或许可以加上一个Experimental标记,提供给用户使用。

3. Loom的实现架构

Kona

图3.1列出了Loom引入的一些新的概念,其中最重要的概念就是Virtual Thread,也就是协程。Loom的官方表述为:“Virtual threads are just threads that are scheduled by the Java virtual machine rather than the operating system”。站在用户的角度,可以把协程理解为线程,按照线程的方式使用,这也是Loom最重要的设计。

图3.1

除了Virtual Thread以外,Loom还新增了Scope Variable和Structured Concurrency的概念,后文会对它们分别进行介绍。

3.1 Loom的基本原理

如图3.2所示,展示了一个协程的生命周期。最初,执行VirtualThread.start()方法创建一个协程,等待被调度;当协程被调度执行以后,开始执行用户指定的业务代码,在执行代码的过程中可能会去访问数据库/访问网络,这些IO操作最后都会调用到底层的一个Park操作。Park可以理解为协程让出执行权限,且当前不能被调度执行。IO结束后会调用Unpark,Unpark之后协程可以被调度执行。在Park操作时,需要执行一个freeze操作,这个操作主要是将当前协程的执行状态,也就是调用栈保存起来。当协程Unpark且被调度时,会执行一个thaw操作,它是freeze的对称操作,主要是把之前freeze保存的调用栈恢复到执行线程上。

图3.2

图3.3展示了freeze和thaw具体完成的内容。首先看freeze操作,调用栈的上半部分从ForkJoinWorkerThread.run到Continuation.enterSpecial都是类库里面的调用。从A开始才是用户的业务代码,假设用户调用了函数A,函数A又调用了函数B,函数B又调用了函数C,之后函数C有一个数据库访问操作,导致协程让出执行权限(执行了一个yield操作),接下来调用到freeze保存当前协程的执行状态(调用栈);这时会首先产生一个stack walk的动作,Loom会从当前调用栈的最底层逐步向上遍历,一直遍历到Continuation.enterSpecial为止。将所有遍历到的栈中的oop都保存一个refStack中,这样做可以保证协程的栈在GC时不作为root。

通常,thread的栈都会被GC当作root来处理,且处理root时通常是需要stop-the-world的,如果协程的栈和线程的栈一样,也被当作root处理,那么由于协程可以支持到几百万甚至上千万的量级,这会导致stop-the-world的时间变长。因此stack walk和refStack的设计(将协程栈上的内容拷贝到一个refStack中),可以在concurrent阶段处理协程的栈,不会由于协程数量的增多影响GC的暂停时间。

图3.3

另一方面,每次freeze时,Loom都会将协程当前的执行栈拷贝到java heap中,即本例中的A、B、C和yield被单独拷贝出来,这样做可以保证协程的栈的内存真正做到按需使用。

当协程被Unpark且被调度时,协程执行thaw操作。thaw主要是将之前保存在java heap里的协程栈恢复到执行栈上,Loom在这里引入了lazy copy的优化。所谓lazy copy,是指Loom通过大量的profiling,发现大部分程序在io操作以后,还会紧跟着产生又一次io。具体到当前的例子,函数C在触发一次io操作以后,很可能还会产生一次io操作,而不是执行完毕返回到函数B,然后函数B也执行完毕返回到函数A。这样的话,在每次thaw的时候,就没必要把所有的调用栈都拷贝回去,而只需要拷贝一部分,然后在拷贝的调用栈尾部加一个return barrier,如果函数确实返回到return barrier,可以通过return barrier触发继续拷贝调用栈的动作;这样每次freeze和thaw的时候,都只需要拷贝一小部分内容,大大提升了切换性能。

在freeze和thaw的时候间隔里,有可能触发过GC导致oop被relocate。因此thaw的时候需要执行一个restore oop的动作,确保不会出现内存访问异常。

3.2 VirtualThread的使用

  • 调度器:

协程可以理解为一种用户态线程,在用户态执行时,由于不能直接访问CPU,所以只能用线程代替cpu。所以,协程需要一个用户态的调度器,调度器中包含物理线程,协程被调度器放在物理线程上执行。如果用户不指定调度器,默认调度器是ForkJoinPool,Loom针对ForkJoinPool做了很多关于协程调度的优化。

  • 直接创建VirtualThread:
代码语言:javascript
复制
Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello"));thread.join()

相比于创建Thread,只需要增加一个ofVirtual()的函数调用,创建的就是协程。

如果用户想要自己写一个针对自己业务模型更有效的调度器,可以通过调用scheduler()函数指定调度器,代码如下所示:

代码语言:javascript
复制
Thread thread = Thread.ofVirtual().scheduler(CUSTOM_SCHEDULER).start(() -> System.out.println("Hello"));thread.join()
  • 创建协程池

除了直接使用协程以外,还可以通过创建协程池的方式使用协程,例如创建一个包含10000个协程的协程池,将任务提交给协程池执行。对应代码如下:

代码语言:javascript
复制
ThreadFactory factoryif (UseFiber == false) {    factory = Thread.ofPlatform().factory();} else {    factory = Thread.ofVirtual().factory();}ExecutorService e = Executors.newFixedThreadPool(ThreadCount, factory);

在创建ThreadPool时,如果传入的factory是ofPlatform()的factory,对应的就是线程池,如果是ofVirtual()的factory,对应的就是协程池。

3.3 VirtualThread Pin

Pin的状态指的是VirtualThread在freeze时无法让出Carrier Thread(协程执行时挂载的物理线程)。主要有两种情况下会导致Pin:

  • VirtualThread的调用栈包含JNI frame。因为JNI调用的实现是C++代码,可以做的事情非常多,例如它可以保存当前Carrier Thread的Thread ID,如果这时切换出去,那么下一次执行时,如果另一个Carrier Thread来执行这个协程,将会产生逻辑错误(Carrier Thread的ID不一致);
  • VirtualThread持有synchronized锁。这是java早期锁的实现带来的限制,因为java的synchronized锁的owner是当前的Carrier Thread,如果一个协程持有一把锁,但是锁的owner被认为是当前的Carrier Thread,那么如果接下来这个Carrier Thread又去执行另一个协程,可能另一个协程也被认为拥有了锁,这可能导致同步的语义发生混乱,产生各种错误。

偶尔出现的Pin并不是一个很严重的问题,只要调度器中始终有物理线程负责执行协程就可以。如果调度器中所有的物理线程都被Pin住,可能会对吞吐量产生较大影响。Loom针对默认的调度器ForkJoinPool做了优化,如果发现所有的物理线程都被Pin住,就会额外创建一些物理线程,保证协程的执行不受太大影响。用户如果想要彻底消除Pin,可以按照如图3.4的方式,通过-Djdk.tracePinnedThreads选项定位产生Pin的调用栈。

图3.4

3.4 Structured Concurrency

结构化并发的设计初衷是方便管理协程的生命周期。结构化并发的基本想法是,调用一个method A,通常并不关心method A是由一个协程按部就班的执行,还是将method A划分为100个子任务,由100个协程同时执行。下面的代码是使用结构化并发的一个小例子:

代码语言:javascript
复制
ThreadFactory factory = Thread.ofVirtual().factory();try (ExecutorService executor = Executors.newThreadExecutor(factory)) {    executor.submit(task1);    executor.submit(task2);}

当前执行的协程会在try代码段结束的位置等待,直到try对应的代码段执行结束,就好像try代码段中的内容是由当前协程按部就班的执行一样。结构化并发本质上是一种语法糖,方便将一个大任务划分为多个小任务,由多个协程同时执行。有了结构化并发,很自然的会产生结构化的中断和结构化的异常处理。结构化的中断,是指try代码段的超时管理,例如用户想要try中的task1和task2在30秒内完成,如果30秒内没有完成则产生一个中断,结束执行。结构化的异常处理,是指try中的内容被划分为2个子任务,如果子任务产生异常,对于异常处理来说,可以做到和一个协程顺序执行的做法相同。

3.5 Scope Variable

Scope Variable是Loom社区增加协程以后,对原有ThreadLocal的重新思考。Scope Variable可以理解为轻量的、结构化的Thread Local。由于Thread Local是全局有效的、非结构化的数据,因此一旦修改就会覆盖掉之前的值。Scope Variable是一种结构化的Thread Local,它的作用范围仅限一个Code Blob,下面的代码是Scope Variable的一个测试用例,根据assert信息可以看出Scope Variable如果在Code Blob之外就会自动失效。

代码语言:javascript
复制
public void testRunWithBinding6() {    ScopeLocal<String> name = ScopeLocal.inheritableForType(String.class);    ScopeLocal.where(name, "fred", () -> {        assertTrue(name.isBound());        assertTrue("fred".equals(name.get()));
        ScopeLocal.where(name, "joe", () -> {            assertTrue(name.isBound());            assertTrue("joe".equals(name.get()));            ensureInherited(name);        });
        assertTrue(name.isBound());        assertTrue("fred".equals(name.get()));        ensureInherited(name);    });}

Scope Variable的另一个好处是,它可以和结构化并发巧妙结合起来。结构化并发通常会将一个大任务划分为多个子任务,如果子任务的个数非常多,例如一个大任务被划分成1000个子任务,那么如果采用Inherit Thread Local来复制父协程的Thread Local到子协程上,由于Thread Local是mutable的,所以子协程也只能拷贝父协程的Thread Local。在子协程非常多的情况下,这种拷贝开销很大。Scope Variable为了应对这种情况,仅仅在子协程上增加一个父协程的引用,而不需要额外的拷贝开销。

4. 为什么需要Kona Fiber?

Kona

通过前面的分析可以看出,Loom的设计是非常完善的,对各种情况都有充分的考虑,用户只需要等待Loom成熟以后使用Loom即可。那么,为什么腾讯还需要自研一个协程Kona Fiber呢?

我们通过和大量业务的沟通,分析出当前业务对协程的三个主要需求:

  • 在JDK8/JDK11上可用:当前大量业务还是基于JDK8/JDK11进行开发的,而Loom作为Openjdk社区的前沿特性,基于社区的最前沿版本进行开发,让很多还在使用旧版本JDK的业务无法使用;
  • 代码的可演进性:用户希望基于JDK8/JDK11修改的协程代码,未来升级到社区最新版本的时候,能够在不修改代码的情况,切换到社区官方的协程Loom;
  • 切换性能的需求:当前Loom的实现,由于有stack walk和stack copy的操作,导致切换效率还有一定提升空间,用户希望有更好的切换效率。

基于这三点需求,我们设计并实现了Kona Fiber。

5. Kona Fiber的实现

Kona

5.1 Kona Fiber与Loom的异同

图5.1展示了Kona Fiber和Loom的共性以及差异点,中间黄色的部分是Kona Fiber和Loom都支持的,两边的蓝色部分是Kona Fiber和Loom的差异点。

图5.1

首先看共性的部分:

  • Loom最重要的设计是VirtualThread,对于Virtual Thread的接口,KonaFiber是全部支持的,用户可以用相同的一套接口使用Loom和Kona Fiber。
  • Loom针对ForkJoinPool做了很多优化,包括前面提到的自动扩展Carrier Thread,Kona Fiber也对这一部分优化进行了移植和适配。
  • 对于Test Case,由于Kona Fiber的接口与Loom接口的一致性,理想情况下可以不加任何修改,直接运行Loom的Test Case。当然,实际运行Loom的Test Case时,仍然需要少量修改,这些修改主要是由于大版本的差异。因为Loom是基于最新版本(jdk18),而Kona Fiber是基于jdk8,如果Loom的Test Case包含了jdk8不支持的特性,例如jdk8不支持var的变量定义,那么仍需要少量的适配。当前,Loom的大部分Test Case我们已经移植到了Kona Fiber,对应的文件目录(相对于jdk的根目录)为jdk/test/java/lang/VirtualThread/loom。

对于差异的部分,首先是性能方面,由于Kona Fiber是stackful的方案,在切换性能上会优于Loom,在内存开销上也会比Loom多一些,这部分的详细数据会在下一章进行介绍。其次,由于Loom引入了一些新的概念,这些概念虽然可以让程序员更好的使用协程,但是这些概念的成熟以及被程序员广泛接受,都需要一定的时间。未来如果用户对Scope Variable、Structure Concurrency有共性需求,Kona Fiber也会虑引入这些概念,目前还是以开箱即用为目标,暂不支持这些新特性。

5.2 Kona Fiber的实现架构

如图5.2所示,展示了一个Kona Fiber协程的生命周期。

图5.2

第一个步骤和第二个步骤与Loom相同,即协程被创建、协程被调度执行。当协程真正被调度执行时,才会在runtime创建协程的数据结构以及协程的栈。创建成功以后,返回到java层执行用户代码。接下来如果在用户代码中遇到IO操作(例如数据库访问),会导致协程被Park,因此会进入到runtime,这时在runtime会执行一个stack switch的过程,切换到另一个协程执行。IO结束时(例如数据库访问完成),会唤醒协程继续执行。

图5.3

如图5.3所示,展示了Kona Fiber在stack switch时的具体实现。协程可以理解为用户态的线程,又因为Kona Fiber的每个协程都有一个独立的栈,所以协程切换本质上只需要切换rsp和rbp指针即可。因为,Kona Fiber的切换开销相比于Loom的stack walk和stack copy要小一些,在理论上会有一个更好的性能。接下来会有详细的数据对Kona Fiber和Loom进行比较。

6. Kona Fiber的性能数据

Kona

图6.1展示了Kona Fiber、Loom和JKU的切换性能数据,横轴代表协程个数,纵轴表示每秒切换的次数。

图6.1

可以看到,Kona Fiber的性能优于Loom,且当协程个数较多时,JKU的性能也好于Loom。前文叙述过,由于Loom在切换时需要做stack copy和stack walk,所以导致切换性能会差一点。

图6.2

图6.2展示了Kona Fiber、Loom和JKU在创建30000个协程时的内存开销,无论是runtime直接使用的物理内存,还是JavaHeap的内存使用,Loom都是最优的(占用内存最少),当然这也得益于Loom的stack copy的实现,可以做到对内存真正的按需使用。由于Kona Fiber兼容了Loom Pin的概念,因此相比JKU去掉了很多不需要的数据结构,在内存使用上优于JKU。从内存使用总量上,虽然Kona Fiber占用的内存多于Loom,但是30000个协程总体占用不到1G内存(runtime占用的内存加上Java Heap占用的内存),对于大多数业务也是可接受的。

图6.3

图6.4

图6.3、图6.4分别展示了Kona Fiber和Loom在使用默认调度器ForkJoinPool时的调度性能。其中横轴表示调度器中Carrier Thread的个数,纵轴表示调度器每秒完成切换操作的次数,不同颜色的线代表不同个数的协程。可以看到,Loom在协程个数较多时,性能抖动较明显。Kona Fiber在不同协程个数的情况下,性能表现都很稳定。这种差异的产生,可能还是与Loom在切换时要做stack walk和stack copy有关。

注:

1. 所有关于Loom的性能数据,都是基于2020年9月Loom的代码。

2. 所有的性能测试用例都可以在开源代码中获取,对应的目录(相对于jdk的根目录)为demo/fiber

7. Kona Fiber的业务落地

DATA

7.1 业务协程化改造

如果一个业务想要从线程切换到协程,通常需要以下三个步骤:

1.将创建线程改为创建协程;将线程池改为协程池。第一步非常简单,只需要将线程的使用替换为协程的使用(按照3.2小节“Virtual Thread的使用”进行替换即可)

2.将部分同步接口替换为异步框架。当前Kona Fiber仍有一些接口不支持,例如Socket、JDBC相关的接口。使用这些接口,将导致协程不能正常切换(Blocking在native代码或者Pin住),协程会退化成线程,那么协程的优势也就不存在了。目前Kona Fiber对网络和数据库的一些原生接口不支持,好在一般网络和数据库操作都可以找到对应的异步框架,例如网络操作有Netty,数据库有异步的redis。图7.1以替换Netty为例,介绍了如何利用异步框架有效的使用协程。首先,创建一个CompletableFuture,然后将任务提交给Netty,接下来当前协程调用Future.get()等待Netty执行完成。在Netty执行完成的回调函数里调用Future.complete(),这样协程就可以恢复执行。

图7.1

3. 协程性能优化:Pin的解决。3.3小节介绍过Pin,Pin虽然不会导致整个系统死锁,但是频繁的Pin仍然会显著降低业务的吞吐量。对于Pin的解决,主要是两个方面,即产生Pin的两个原因:synchronized锁和native frame。第一步,在调试阶段开启-Djdk.tracePinnedThreads,这样可以找到所有引起Pin的调用栈。如果Pin是由于业务代码使用synchronized锁引起的,那么只需要将synchronized锁替换成ReentrantLock即可;如果Pin是由于包含native frame或者第三方代码包含synchronized锁引起的,那么只能通过将任务提交到一个独立线程池的方法来解决,这样可以保证协程的执行不受影响。

7.2 企点开放平台-统一推送服务

企点开放平台-统一推送服务是腾讯公司的业务,天然存在高并发的需求。最初业务方尝试使用WebFlux响应式编程,但由于业务方存在较多第三方外包人员,且WebFlux的开发、维护难度较高,导致业务方放弃使用WebFlux。后来,在了解了腾讯内部自研的Kona Fiber后,业务方果断选择尝试切换Kona Fiber。

业务方针对Kona Fiber的适配,主要是通过nio+Future替换bio,将所有阻塞操作替换为nio,当阻塞操作完成时执行Future.complete()唤醒协程;业务方反馈的替换协程的工作量为:三人天、200+行的代码适配、测试工作。最终相比Servlet的线程方案,系统整体吞吐量提升了60%。

下面的代码是业务方修改的代码片段,分别表示创建一个协程池,以及通过加一个annotation让函数运行在协程池上:

代码语言:javascript
复制
@Bean(name = "asyncExecutor")public Executor asyncExecutor() {  ThreadFactory threadFactory = Thread.ofVirtual().factory();  return Executors.newFixedThreadPool(fiberCount, threadFactory);}

@Async("asyncExecutor")public CompletableFuture<ResponseEntity<ResponseDTO>> direct() {  ···}

7.3 SLG游戏后台服务

SLG(Simulation Game)游戏主要是一些策略类游戏,这类游戏不像一些对战类游戏对实时性要求非常高。策略类游戏的特点是逻辑复杂,且游戏业务通常都有高并发、高性能需求。业务方基于Kona Fiber定制了一种单并发的协程调度器。

如图7.2所示,多个协程只运行在一个playerService Thread上,由于Carrier Thread只有一个线程,因此同一时刻只有一个运行在playerService Thread的协程可以执行;这样做可以省去很多同步操作,提高开发者的编程效率。替换了Kona Fiber以后,系统的整体吞吐量也相比线程方案提高了35%。

图7.2

右侧的battleService Thread存在访问数据库请求,业务方使用的是腾讯自研的Tcaplus数据库,为了避免协程退化成线程,业务方将数据库操作提交到一个独立的线程池来执行。

7.4 trpc-java

trpc是腾讯公司自研的高性能、新老框架易互通、便于业务测试的RPC框架。一些使用trpc-java的用户对高并发+IO密集型程序有需求。在协程出现以前,他们都只能通过trpc-java提供的异步框架解决性能问题。异步框架虽然可以解决高并发+IO密集型程序的性能问题,但是由于它对开发者要求较高,很多时候用户一不小心就会将异步代码写成同步代码,导致性能下降。

当前,trpc-java已经结合Kona Fiber推出了trpc-java协程版本,用户可以按照编写同步代码的方式,获得异步代码的性能。目前已有大数据特征中台业务、trpc-网关业务正在适配协程。

8. Future Plan

Kona

  1. Kona Fiber在Kona8上的源码github链接: https://github.com/Tencent/TencentKona-8/tree/KonaFiber
  2. Kona Fiber在Kona11上的源码github链接: https://github.com/Tencent/TencentKona-11/tree/KonaFiber
  3. 持续跟进Loom社区,将Loom的优化移植到Kona Fiber。

参考文献

  1. https://wiki.openjdk.java.net/display/loom/Getting+started
  2. https://wiki.openjdk.java.net/display/loom/Troubleshooting
  3. http://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part2.html

传送门

Kona 8对外开源版本,欢迎star:

https://github.com/Tencent/TencentKona-8

Kona11对外开源版本,欢迎star:

https://github.com/Tencent/TencentKona-11

往期精选

- 标题图来源:Pexels -

扫码关注 | 即刻了解腾讯大数据技术动态

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-09-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 腾讯大数据 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档