Discord 如何将服务从 Go 迁移到 Rust

文摘   科技   2024-12-18 08:45   四川  

引言

在现代软件开发中,性能是用户体验的关键。Discord 作为一个拥有数百万用户的实时通信平台,始终将性能放在首位。在使用 Go 语言构建服务的过程中,Discord 遇到了一些性能瓶颈,尤其是在高并发场景下,垃圾回收机制导致的延迟问题日益凸显。为了提升产品质量,Discord 决定大胆尝试将其关键后端服务重写为 Rust,这一决策不仅解决了当前的性能问题,还为未来的扩展和维护打下了良好的基础。

主要内容

1. 为什么从 Go 转向 Rust?

Go 语言以其简洁和高效而受到广泛欢迎,但 Discord 在实际应用中发现,Go 的垃圾回收机制在高负载情况下经常导致不可预测的延迟。这种延迟在用户体验中表现得尤为明显,使得用户在使用过程中感受到卡顿。经过深入分析,Discord 发现 Go 的内存管理模型和垃圾回收机制是造成延迟的主要原因。因此,团队决定转向 Rust,这是一种具备更高性能和内存安全性的编程语言。

2. Rust 的优势

Rust 提供了一些显著的优势,使其成为 Discord 重新构建服务的理想选择:

  • 无垃圾回收:Rust 使用编译时所有权模型,能够在数据超出作用域时立即释放内存,避免了运行时的延迟。
  • 性能稳定:Rust 的内存管理确保了可预测的性能表现,极大地减少了不可预测的慢速情况。
  • 编译时检查:Rust 的严格类型系统和内存安全检查大大降低了潜在的运行时错误,确保在编写代码时就能发现问题。

3. 实现案例

在重写服务的过程中,Discord 采用了一个大型 LRU(最近最少使用)缓存方案,以确保高效存取用户的阅读状态。以下是 Rust 的简化示例代码:

use std::collections::{BTreeMap, VecDeque};
use std::time::{Instant};

// 表示阅读状态的结构体
struct ReadState { 
    last_read_message_id: u64// 最后阅读的消息 ID
    mention_count: u32,        // 提及计数
    updated_at: Instant,       // 更新时间
}

// LRU 缓存结构体
struct LruCache {
    store: BTreeMap<(u64u64), ReadState>, // 存储用户与频道的状态
    recency_queue: VecDeque<(u64u64)>,    // 跟踪最近使用的键
    capacity: usize,                       // 缓存容量
}

impl LruCache {
    // 创建新的 LRU 缓存
    fn new(capacity: usize) -> Self {
        Self {
            store: BTreeMap::new(),
            recency_queue: VecDeque::with_capacity(capacity),
            capacity,
        }
    }

    // 获取缓存中的阅读状态
    fn get(&mut self, key: (u64u64)) -> Option<&ReadState> {
        if let Some(value) = self.store.get(&key) {
            // 更新使用顺序
            self.recency_queue.retain(|&k| k != key);
            self.recency_queue.push_back(key);
            Some(value)
        } else {
            None
        }
    }

    // 插入新的阅读状态
    fn insert(&mut self, key: (u64u64), value: ReadState) {
        if self.store.len() == self.capacity {
            // 淘汰最近最少使用的项
            if let Some(evicted_key) = self.recency_queue.pop_front() {
                self.store.remove(&evicted_key);
            }
        }
        self.store.insert(key, value);
        self.recency_queue.push_back(key);
    }
}

4. 异步 I/O 和数据库访问

在重写过程中,Discord 还需要处理与数据库的异步 I/O 操作,这对于高效的网络服务至关重要。Rust 的异步生态系统逐渐成熟,使得处理数千个并发 I/O 操作成为可能。以下是一个简单的异步数据库访问代码示例:

use tokio::task;

// 异步函数:从数据库获取阅读状态
async fn fetch_read_state_from_db(user_id: u64, channel_id: u64) -> Option<ReadState> {
    // 模拟数据库调用(实际使用时需调用真实客户端)
    task::spawn_blocking(move || {
        // 这里假设有一个同步的数据库客户端
        // let row = db_client.query("SELECT ...");
        // 解析行数据为 ReadState
        Some(ReadState {
            last_read_message_id: 42// 从数据库获取
            mention_count: 0,
            updated_at: Instant::now(),
        })
    })
    .await
    .ok()?
}

5. 总结

通过将服务重写为 Rust,Discord 成功消除了周期性的延迟,CPU 使用率更加稳定,用户体验得到了显著提升。Rust 的安全性和性能使得团队能够更有效地管理内存,优化数据结构,并在高负载情况下保持平稳的性能。

参考文章

Discord 如何将服务从 Go 迁移到 Rust: https://medium.com/@yaswanth.thod/why-discord-is-moving-an-many-services-from-go-to-rust-ad503fb7987f

书籍推荐

各位 Rust 爱好者,今天为大家介绍一本《Programming Rust: Fast, Safe Systems Development》(第二版) 是由 Jim Blandy、Jason Orendorff 和 Leonora Tindall 合著的 Rust 编程指南。本书深入探讨了 Rust 语言在系统编程中的应用,着重介绍如何利用 Rust 的独特特性来平衡性能和安全性。书中涵盖了 Rust 的基础数据类型、所有权和借用概念、特征和泛型、并发编程、闭包、迭代器以及异步编程等核心内容。这本更新版基于 Rust 2021 版本,为系统程序员提供了全面而实用的 Rust 编程指导。


  1.  Rust:横扫 C/C++/Go 的性能之王?

  2.  从 Rust 开发者视角看 C++:优缺点大揭秘

  3.  Rust vs Zig:新兴系统编程语言之争

数据科学研习社
带你走进数据科学的世界🚀
 最新文章