2022/10/01

[Rust]axumとdragonflyを使ったWebsocket Chatのサンプル実装

dragonflyredisrustwebsocket

概要

少し週末に時間が取れたので、技術調査を兼ねてWebsocketを使ったChatアプリのサンプル実装を行いました。

以下の2点を試してみたかったというのが主な動機です。

  • axumというRustのweb frameworkの使用感
  • dragonflyというRedis互換のミドルウェアの使用感

できたものはこちらのgithubリポジトリに公開しています。

axum

弊社ではここ最近はactix-webをずっと使ってきていましたが、
ちょっと"つらみ"な部分もいくつかあったので、その代替となりうるかどうかも含めて実際に使ってみました。

結論から言うと、axumの使用感はほとんどactix-webと変わらないので、無理に移行しなくても良いのかな?と言う気がします。

また、websocket chatに関しては、(特に大規模なら)actix-webの方が実装しやすそうな気さえします。

ただ、以下の点で移行しても良いのかなと思っています。

  • やや書きやすくなっている点もある
  • 書き方があまり変わらないので、移行しやすい
  • tracingとloggerの連携がうまくできればかなり嬉しい

dragonfly

dragonflyはredisを最新の技術で書き換えたものらしく、Redisの25倍のパフォーマンスがあると公式ではうたっています。

Dragonfly reaches x25 performance compared to Redis

また、redisのapi互換があるめ、redis用のscriptがそのまま動くのがかなり魅力的でした。

今回は主にpublish / subscribeを試してみましたが・・
以下のように redis crateを使って、アクセスすることができました。

  • Cargo.toml
redis = { version = "0.21.6", features = ["r2d2"] }
r2d2 = "0.8.8"

以下は、publish / subscribe部分のコードで、テストコードも作成しました。

use crate::{Result};
use redis::{Commands, ConnectionLike, FromRedisValue, PubSub, ToRedisArgs};

pub type RedisConnection = redis::Connection;

pub fn publish<K: ToRedisArgs, V: ToRedisArgs>(
    conn: &mut RedisConnection,
    channel: K,
    value: V,
) -> Result<()> {
    conn.publish(channel, value).map_err(Into::into)
}

pub fn subscribe<K: ToRedisArgs>(conn: &mut RedisConnection, channel: K) -> Result<PubSub> {
    let mut pubsub = conn.as_pubsub();
    pubsub.subscribe(channel)?;
    Ok(pubsub)
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::*;

    #[test]
    #[serial_test::serial]
    fn test_publish_subscribe() {
        dotenv::dotenv().ok();
        let redis_url = std::env::var("REDIS_URL").unwrap_or("redis://localhost:6379/0".to_owned());
        let pool = new_pool(redis_url, 2).unwrap();
        let channel = "channel1";
        // subscribe
        let pool1 = pool.clone();
        let handle1 = std::thread::spawn(move || {
            let mut connection = pool1.get().unwrap();
            let mut sub = subscribe(&mut connection, channel).unwrap();
            loop {
                let msg = sub.get_message().unwrap();
                println!("got message: {:?}", msg);
                let payload: String = msg.get_payload().unwrap();
                if &payload == "fin" {
                    break;
                }
            }
        });
        let pool2 = pool.clone();
        let handle2 = std::thread::spawn(move || {
            let mut connection = pool2.get().unwrap();
            publish(&mut connection, channel, "This is the first message.").unwrap();
            publish(&mut connection, channel, "2nd message.").unwrap();
            publish(&mut connection, channel, "3rd message.").unwrap();
            publish(&mut connection, channel, "fin").unwrap();
        });
        let _ = handle2.join().unwrap();
        let _ = handle1.join().unwrap();
    }
}

まとめ

  • dragonflyはかなり良さそうなので、今後積極的に採用します。
  • axumはもう少し調査をしてみて、良さそうなら移行しようと思います。