知名免费语音通信软件Discord将读取状态相关的程序代码,从Go转移到了Rust。Discord软件开发工程师Jesse Howarth在自家博客写道,受益于Rust处理内存的机制,把读取状态服务实例从Go切换到Rust,极大程度地提升服务的性能。
以Rust改写的是Discord读取状态服务,该服务关注用户已经订阅的频道和消息,当用户连接到Discord,每次发送消息和阅读消息时,都会访问读取状态服务,也就是说,读取状态是不停地被使用的程序区块,其性能表现会直接影响整个Discord应用程序的使用体验。
Jesse Howarth表示,为了要确保Discord用起来总是非常顺畅快速,便要确保读取状态性能良好,使用Go实例的读取状态服务,在大多数的时候执行速度很快,但是每隔几分钟,程序就会出现大量的延迟高峰,经过Discord工程团队调查,发现这些延迟是由Go的核心功能,包括内存模型和垃圾回收器所造成。
紫色线为Go,蓝色线为Rust
用来存储读取状态信息的数据结构通称为读取状态,整个Discord拥有数十亿个读取状态,每个用户每个频道都有一个读取状态,每个读取状态具有多个自动更新计数器,用来记录像是频道中的提及@mentions数量等信息。
而为了加速计数器的更新,每个读取状态服务器都有读取状态LRU(Least Recently Used)缓存来记录这些信息,每个缓存需要服务数百万名用户,而每秒会有数百万个缓存更新。为了维持持久性,Discord以Cassandra数据库来创建缓存,当缓存密钥驱逐(Eviction)时,读取状态便会被提交到数据库中,且每当读取状态被更新时,也会被调度30秒后提交到数据库,平均每秒会有数万次写入。而Discord对Go服务采样,发现每两分钟就会有回应时间延迟与CPU使用率高峰出现。
之所以每两分钟就会有高峰出现,Jesse Howarth解释,在Go中,当缓存密钥驱逐时,内存不会马上释放,而会等待垃圾回收器查询,没有受到参照的内存才会被释放,也就是说,内存不会在闲置时立即释放,而需要停留一段时间,当垃圾回收器确定该内存真正闲置之后,才会将他释放。
由于Discord其他以Rust撰写的部分发展越来越好,且处理读取状态的程序容量小且自包含,很适合移植到Rust上,因此Discord工程团队便决定要用Rust来构建读取状态服务。Rust执行速度很快且内存效率很好,不需要Runtime或是垃圾收集器来回收内存,可以用来开发讲究性能的服务。
Jesse Howarth提到,因为Rust没有垃圾回收器,而是使用特殊的内存管理方法,该方法结合内存所有权的概念,Rust会关注读取和写入内存的程序,不再用到的内存会立即释放,且因为Rust会在编译时强制执行内存规则,因此在运行时,几乎不会出现内存错误。Rust版本的读取状态服务,当用户的读取状态从LRU缓存中驱逐时,Rust便会立即释放该内存,不需要等待垃圾回收器释放。
Discord现在于许多部分都使用Rust开发,包括游戏SDK、视频捕捉和编码,以及部分后端服务等,现在当工程团队在开始新项目时,也会优先考虑Rust。