一、简介
Sofa 源于 Minisite 重构项目,也许您会问,已经有如此众多的 memcahced 客户端,为什么还要再来一个,我们经常会说,不要尝试去重复造轮子。是的,但如果我们有一种不同的思路来制造这个轮子,并且它有可能会做得更好,那为什么不试试看呢?基于此,才有了 sofa 。
二、 Sofa 的愿景
在不额外增加太多成本的情况下,更快速、高效的收发数据。
三、 sofa 的前辈们
在介绍 Sofa 的实现原理之前先简要了解一下,部分目前常用 Memcached 客户端的基本实现原理。
Spy :
Spy 在处理命令时也利用了批量发送及接收的机制,但其数据的收发都是基于 Selector 处理线程,特点在于:
1. 读写都在一个线程中执行;
2. 写数据是先往 socket 中写,写不完则注册写监听;这种机制的缺点在于,在一次处理不完数据的情况下,写入数据的代码会被多执行一次(一次写循环,另外一次在写事件处理过程中);而且大部分情况下命令都是一个个被写入 socket 中,整体来看,这种模式会影响高吞吐量情况下的收发效率。
3. 读数据完全依赖 socket 的读事件来执行。
4. 命令本身是批量发送,但每个命令请求都会唤醒 Selector ,结果造成 Selector 被多次唤醒,执行过多的空循环。
另外: spy 的 Selector 线程会根据 Client 创建而自动生成,如果创建多个 client ,那么就会创建多个 Selector 线程;
Xmemcached :
同 Spy 不同, XM 完全基于 Socket 的 IO 事件来驱动 IO 读写(在 Selector 处理线程中实现读写操作),任何命令读写都会直接注册读写相关的 IO 事件,然后等相应事件产生则往 IO 中写入或读取数据。
与 Spy 的主要区别在于:
1. spy 是先写数据,写不了才注册写事件,而 XM 则完全基于 IO 事件实现。
2. spy 会批量发送数据,而 XM 可以合并多个 Get 命令为一条 GETS 命令;
3. xm 的 Selector 线程数是根据当前服务器的可用处理器个数来创建;
XMemcached 的核心 Selector 的代码写得相当精巧,整体性能表现稳定;
Danga :
由于之前测试的版本是基于阻塞式 IO 实现,其在吞吐量及相应速度方面要相差一个数量级,所以一直没有太多的关注,在此不做太多评价。但由于其是 Memcached 最早的 java 客户端,使用比较广泛,稳定性也不错。
Cat ( Alibaba 自己的 Memcached Client 实现):
Cat 的实现类似 spy 但又有所不同,类似点在于:写数据在 Selector 线程中执行,如果数据没有写完,才注册写事件。但其 Selector 线程数量计算方式有点特别,如果处理器小于 8 核,则只创建一个 Selector 线程,否则创建 4 个 Selector 线程;另外, Cat 的实现基于连接池的实现机制,对于同一个 Memcached Server , Cat 会根据并发请求数,创建对等的 TCP 连接( TCP 模式下)。在我的测试机上,如果 50 个并发, cat 会同时建立 50 个 TCP 连接。最后一个不同点是 Cat 的命令收发都是独立的,即每个命令都会单独占用一个 socket 来发送和接受,直到处理完当前命令, socket 才空出来给下一个命令使用 , 没有批量发送和命令合并的功能。但通过多个连接可以实现类似批量发送的功能,整体卡端的 IO 复用率还是比较高的。
四、 sofa 的特点:
1. 收发并行的工作模式,写入时直接往通道中写入数据,读取完全依赖IO 通道中的读事件。
2. 批量处理命令的工作模式: a 、命令的批量发送, b 、多个 get 命令自动合并成一个 gets 命令
3. 基于 nio 的收发模式,可以同时管理多个连接,并且在请求线程急剧增加的情况下,整个收发效率变化平稳。
4. 支持所有 memcached 的标准命令,并且支持二进制和字符串两种命令格式。
5. 支持 direct buffer 模式读取数据(目前只支持 Text 命令模式)。
五、 Sofa 的实现原理:
Sofa 的实现和上述的几个客户端有很大的区别,首先是 sofa 数据收发模式,读写是并行执行的,如下图:
同样在读取数据时也采用类似的机制来实现,由于网卡本身都是全双工的方式工作,因此在多核的系统中,读写的并行可以极大的增加单个连接的数据吞吐量,同时在写 入数据时, sofa 的实现方式是首先尝试直接写,只有底层 socket 中无法写入数据时,才向 selector 注册相关的监听事件,在 Memcached Server 这种请求响应时间几乎恒定的服务器支撑下,可以极大的减少同 selector 的交互次数,在压力测试机上测试统计发现,约 90% 的数据都可以不通过 selector 而直接从 socket 中写入。
其次、 sofa 也采用同 spy 类似的方式,为每个 socket 都维护了一个命令队列,发送时系统从命令队列中获取一批命令数据,并将其一次性发送给 memcached 服务器端,同时对于多个 Get 命令, sofa 会自动合并成一条 Gets 命令,这样可以减少数据发送时往网卡缓存写入数据的次数,进而减少底层网络数据的发送次数 。
最后、 sofa 对命令数据的压缩和解压操作都不放在核心读写线程来实现,为了保证读写线程的高效率,这些操作全部转移到请求线程来处理。
六、性能测试
附件是 spy , xmemcached , sofa , cat 之间的性能测试对比 , 性能测试对比图:见附件
性能对比测试粗略分析 :
测试 Client 在 1 、 5 、 10 、 20 、 50 、 100 、 150 、 200 个并发线程模式下,各个 client 的整体吞吐量 TPS (第一列图)及在当前压力下随机读写两次数据的响应速度测试(第二列图)。
1. 吞吐量( TPS ):
从图中可以看到在大部分情况下, sofa 能够获取更高的吞吐量( 20% ~ 100% 左右的性能提升),有些场景 TPS 会有近 1 倍的提升。
但 XM 整体的稳定性较好,随着线程数和数据量大小的增加,各方面性能表现都比较稳定;
2. 压力测试下,随机访问速度测试:
测试在相应的并发压力下,随机访问的性能对比, sofa 总体而言和 xm 系统差不多,甚至某些情况下还会稍微快些。
-
连接数:
xm 、 spy 、 sofa :在所有场景下都只是用了一个 TCP 连接;
cat :连接数随并发数增加而增加,每个并发会对应一个 TCP 连接;
七、 Sofa 的未来 :
说实话,我也不是太清楚,如果你喜欢或者有兴趣,或许可以和我一起来完善它,我相信Sofa 还存在很大的优化空间,占有更少的系统资源,获取更快的收发速度;
如果想在自己的产品中尝试使用它,对 sofa 而言绝对是个好消息,不管如何,它都需要大家的支持才有可能能走得更远 、 更好。
未来计划:
加入更多的异常处理机制,诸如连接自动重建,多备份数据读写等等;
加入 UDP 的支持
优化核心模块,让其占用更少的资源消耗,尤其是内存分配与释放相关的部分。