SONA 是一个由比心语音技术团队开发,用于快速搭建语音房产品的全端解决方案,包含了房间管理、实时音视频、房间IM、长连接网关等能力,支撑了比心聊天室、直播、游戏房等业务。
前言
Sona 平台是一个搭建语音房产品的全端解决方案,包含了房间管理、实时音视频、房间IM、长连接网关等能力。其中最基础核心的就是长连接网关。
池化技术是把一些能够复用的东西放到池中,提前保存大量的资源,避免重复创建、销毁的开销,从而极大提高性能。在请求量比较大的时候能明显优化应用性能,降低系统频繁建连的资源开销。池化技术的应用非常广泛,我们工作中常见的比如数据库连接池、线程池、http连接池等。
一、对象池
还有一种可能在工作中不是经常使用的池化技术 - 对象池 ,它的核心思想就是空间换时间。
我们知道 Java 中频繁地创建和销毁对象的开销是很大的,通常情况下对象是分配在堆上的,因为堆是线程共享的,所以同一时间可能会有很多线程申请空间分配,在这种情况下要加锁处理,这样一来就会造成分配效率下降。虽然可以使用 TLAB 来分配(TLAB是每个线程独有的,它可以避免这种开销,直接分配内存),但还是存在着一些效率问题。
我们可以使用预先创建好的对象来减少频繁创建对象的性能开销,同时还可以对对象进行统一的管理,降低了对象的使用成本。这样不仅避免频繁地创建和销毁所带来的性能损耗,提高并发处理能力,而且能够降低 JVM GC 的压力。
像 Disruptor,它之所以高性能其实有一部分原因就是,RingBuffer 数组元素在初始化时一次性全部创建,提升缓存命中率,对象循环利用,避免频繁GC。
二、Netty Recycler
Recycler 是 Netty 提供的自定义实现的轻量级对象回收站,借助 Recycler 可以完成对象的获取和回收。
在 SONA网关中,每次业务请求最终都会被封装成 ChannelEventTask 丢到线程池里执行。为了尽可能地提升网关的性能,我就使用了 Recycler 来做池化。在压测过程中,内存占用以及CPU 消耗都有比较明显的改善。
使用方式其实非常简单,下面这个是 Netty 官方给出的样例:
通过 RECYCLER.get() 来获取对象,handle.recycle(this) 来回收对象 。
public class MyObject {
private static final Recycler<MyObject> RECYCLER = new Recycler<MyObject>() {
protected MyObject newObject(Recycler.Handle<MyObject> handle) {
return new MyObject(handle);
}
}
public static MyObject newInstance(int a, String b) {
MyObject obj = RECYCLER.get();
obj.myFieldA = a;
obj.myFieldB = b;
return obj;
}
private final Recycler.Handle<MyObject> handle;
private int myFieldA;
private String myFieldB;
private MyObject(Handle<MyObject> handle) {
this.handle = handle;
}
public boolean recycle() {
myFieldA = 0;
myFieldB = null;
return handle.recycle(this);
}
}
MyObject obj = MyObject.newInstance(42, "foo");
...
obj.recycle();
Recycler 原理
Recycler 里面一共包含四个核心组件:Stack、WeakOrderQueue、Link、DefaultHandle。
整个 Recycler 的内部结构中各个组件的关系,可以通过下面这幅图进行描述
Stack
private static final class Stack<T> {
final Recycler<T> parent; // 所属的 Recycler
final WeakReference<Thread> threadRef; // 所属线程的弱引用
final AtomicInteger availableSharedCapacity; // 线程回收对象时,其他线程能保存的被回收对象的最大个数
final int maxDelayedQueues; // WeakOrderQueue最大个数
private final int maxCapacity; // 对象池的最大大小,默认最大为 4095
private final int ratioMask; // 控制对象的回收比率,默认只回收 1/8 的对象
private DefaultHandle<?>[] elements; // 存储缓存数据的数组
private int size; // 缓存的 DefaultHandle 对象个数
private int handleRecycleCount;
// WeakOrderQueue 链表的三个重要节点
private WeakOrderQueue cursor, prev;
private volatile WeakOrderQueue head;
// 省略其他代码
}
Stack 对象是从 Recycle 内部的 FastThreadLocal 对象中获得,因此每个线程拥有属于自己的 Stack 对象,创造了无锁的环境,并通过 weakOrderQueue 与