趣味学RUST基础篇(异步)

揭秘 async/await 的"一心多用"魔法

打个比方,你是"淘淘购"电商平台的一名客服小能手。你的工作台上有两台电脑:

  1. 电脑A :运行着一个超级复杂的视频渲染软件,正在为大促活动制作广告片。这个软件一开动,你的CPU风扇就"嗡嗡"狂转,电脑卡得像块砖。这叫 CPU密集型任务------它需要CPU全速运转,榨干每一分算力。
  2. 电脑B:开着网页版旺旺,你给一位用户回复:"亲,您要的'趣味学rust'课程明天就能发货哦~"。然后你就等着对方回复。在这期间,你的电脑(CPU)其实很闲,完全可以去干点别的事。但如果你是个"单线程"的人,你会怎么做?你会死死盯着电脑B的聊天窗口,一动不动,等上十分钟,直到用户回复你一句"谢谢",你才松一口气。这效率也太低了!

幸运的是,现实中的操作系统比你聪明多了。当你在等用户回复时,操作系统会"打断"你的等待状态(这叫中断 ),让你可以切到电脑A去看看渲染进度,或者泡杯咖啡。等用户真的回复了,系统再"提醒"你回来处理。这样,你在等待的时间里也没闲着。这就是一种最基础的并发思想------通过快速切换任务,让资源得到充分利用。

程序也想"一心多用"

在编程世界里,很多操作都像"等用户回复"一样,是 IO密集型任务(Input/Output,输入输出)。比如:

  • 从数据库里查一条用户信息。
  • 调用支付接口扣款。
  • 下载一张商品图片。

这些操作本身不怎么占用CPU,但需要等待网络或磁盘的响应,可能要几百毫秒甚至几秒。如果程序傻乎乎地"阻塞"在那里等,就像客服小能手死盯着聊天窗口,那服务器的性能就废了!一台服务器同时要处理成千上万个用户的请求,如果每个请求都因为等待IO而卡住,那服务器很快就瘫痪了。

解决方案一:开多个"分身"(线程)

最直接的办法是:给每个用户请求都开一个独立的"分身"(线程)。这个分身负责处理这个用户的整个请求流程。

  • 优点:简单直观,每个分身互不干扰。
  • 缺点:太贵了!每个"分身"(线程)都需要操作系统分配内存、维护上下文,开销很大。一台服务器最多也就开几千个线程。如果有一万个用户同时下单,那就得排队,用户体验很差。

解决方案二:async / await ------ "挂起"与"恢复"的魔法

有没有一种更轻量的方式?当然有!那就是 asyncawait

你可以把 async 函数想象成一个"可以暂停的任务"。

rust 复制代码
// 这是一个"异步"任务
async fn handle_user_request(user_id: u32) {
    println!("开始处理用户 {} 的请求", user_id);
    
    // 哎呀,要查数据库,得等一会儿...
    let user_info = fetch_user_from_db(user_id).await; // 挂起!
    
    // 数据库数据终于回来了!继续干活
    println!("用户信息: {:?}", user_info);
    
    // 再调用支付接口
    let payment_result = call_payment_api(&user_info).await; // 又挂起!
    
    // 支付成功,返回结果
    println!("支付结果: {:?}", payment_result);
}

关键就在 .await 这个操作:

  1. 当程序执行到 .await 时,它发现:"哎呀,fetch_user_from_db 这个操作要等网络,一时半会完不了。"
  2. 于是,它对整个任务说:"你先挂起吧!" 这个任务就暂停在这里,不占CPU了。
  3. 同时,程序的"调度器"(比如 tokio 运行时)会立刻去检查队列里还有没有其他"就绪"的任务可以执行。
  4. 等数据库的数据真的回来了,调度器会唤醒这个"挂起"的任务,让它从 .await 的下一行代码继续执行。

这就像客服小能手:

"亲,我这边查一下库存..."(点击查询按钮,进入等待状态)

(趁系统查询的2秒钟,他迅速切到另一个对话窗口,回复另一位客户)

"...查到了!有货,马上给您安排发货!"

并发 vs 并行:别搞混了!

这里要澄清两个概念:

  • 并发 (Concurrency) :看起来像同时干多件事,其实是快速切换。就像客服小能手在两个对话窗口间来回切换。async 主要解决的就是并发问题。


并发工作流

  • 并行 (Parallelism):真正的同时干多件事。就像客服小能手有八条腿,三条腿回消息,三条腿查库存,两条腿写报告。这需要多核CPU。async` 是并发,但它底层可以用多线程运行时来实现一定程度的并行。


    并行工作流

async/await 的三大好处

好处 解释
高效利用资源 一个线程可以处理成千上万个异步任务,避免了线程创建的巨大开销。
代码简洁易读 你可以像写"阻塞代码"一样写非阻塞代码。let data = fetch_data().await; 多么清晰!
适合IO密集型场景 对于Web服务、数据库操作、文件读写等需要大量等待的任务,async 是性能利器。

章鱼小新解密"未来任务"与"魔法咒语"

现在,小新学会了用 asyncawait 实现"一心多用"。但他还是有点懵:"那个 .await 到底是个啥?为什么加了它,任务就能'挂起'?"

Rust 大神告诉他:"这背后,有一个叫 Future 的神秘存在,而 async/await 只是它的'魔法糖衣'。"

核心概念:什么是 Future?

想象一下,你网购了一台游戏机。

  • 下单那一刻:你并没有拿到游戏机(现在没有准备好)。
  • 但你知道:过几天,快递会把它送到你家(未来某个时刻会准备好)。

在编程中,Future 就像这个"未来的快递包裹" 。它代表一个现在可能还没有结果,但在未来某个时刻会有结果的值

比如:

  • fetch_user_from_db(user_id) 返回的不是一个 User,而是一个 Future<Output = User> ------ "一个将来会包含用户信息的包裹"。
  • download_image(url) 返回的不是一个图片文件,而是一个 Future<Output = Image> ------ "一个将来会包含图片数据的包裹"。

别名Future 在别的语言里也叫 "Promise" 或 "Task",意思都一样。

魔法咒语:asyncawait

async:制造"未来包裹"的工厂

当你写一个 async fn

rust 复制代码
async fn get_user_info(id: u32) -> User {
    let user = database_query(id).await;
    let profile = fetch_profile(user.username).await;
    User { user, profile }
}

Rust 编译器其实是在背后悄悄地干一件事:它把这个函数编译成一个实现了 Future trait 的匿名类型

你可以把它理解为:

rust 复制代码
// 伪代码:编译器帮你生成的"未来包裹"结构
struct GetUserInfoFuture {
    state: State, // 内部状态机:0=开始,1=等数据库,2=等头像...
    id: u32,
    user: Option<UserData>,
    profile: Option<ProfileData>,
}

impl Future for GetUserInfoFuture {
    type Output = User;

    fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
        // 这个 poll 函数就是"检查包裹到了没?"
        match self.state {
            0 => {
                // 开始查数据库
                let db_future = database_query(self.id);
                self.state = 1;
                // 把"查数据库"这个子任务注册到调度器,让它去执行
                // 调度器会记住:"等这个db_future完成了,就回来通知我"
                return Poll::Pending; // 包裹还没到,先挂起
            }
            1 => {
                // 检查"查数据库"完成了吗?
                if let Poll::Ready(user_data) = db_future.poll(cx) {
                    self.user = Some(user_data);
                    self.state = 2;
                    // 开始查头像
                    let profile_future = fetch_profile(user_data.username);
                    // 注册 profile_future...
                    return Poll::Pending;
                } else {
                    return Poll::Pending; // 还没好,继续等
                }
            }
            // ... 更多状态
        }
    }
}

看到没?async fn 其实是一个"自动状态机生成器"!它把你的异步逻辑拆分成多个"等待点"(.await),并管理每个点的状态。

await:检查"包裹到了没"的咒语

当你在一个 Future 上使用 .await

rust 复制代码
let user = database_query(id).await;

编译器会把它翻译成类似这样的代码:

rust 复制代码
loop {
    match database_query_future.poll() {
        Poll::Ready(value) => break value, // 包裹到了!返回结果
        Poll::Pending => {
            // 包裹没到!"挂起"当前任务,让出执行权
            yield; 
            // 等待调度器下次唤醒我再继续循环
        }
    }
}

.await 的本质就是轮询(poll) 这个 Future,问它:"你好了吗?"。如果没好,就"挂起";如果好了,就取走结果继续执行。

动手实践:小新的第一个异步爬虫

小新决定写一个小程序,抓取两个网页的标题,看哪个更快。

加入依赖

toml 复制代码
[dependencies]
trpl = "0.2.0"
rust 复制代码
use trpl::{Either, Html};

async fn page_title(url: &str) -> (&str, Option<String>) {
    let text = trpl::get(url).await.text().await;
    let title = Html::parse(&text)
        .select_first("title")
        .map(|title| title.inner_html());
    (url, title)
}

fn main() {
    let args: Vec<String> = std::env::args().collect();

    trpl::run(async {
        let fut_1 = page_title(&args[1]);
        let fut_2 = page_title(&args[2]);

        let (url, maybe_title) =
            match trpl::race(title_fut_1, title_fut_2).await {
                Either::Left(left) => left,
                Either::Right(right) => right,
            };

        println!("{url} returned first");
        match maybe_title {
            Some(title) => println!("Its page title is: '{title}'"),
            None => println!("Its title could not be parsed."),
        }
    })
}

发生了什么?

  1. fut1fut2 是两个 Future,代表"获取标题"的未来任务。
  2. trpl::race 创建了一个新的 Future,它会同时监控 fut1fut2
  3. 当其中一个先完成,raceFuture 就变成 Ready 状态,.await 就能取到结果。
  4. 整个过程高效并发,可能只用了一个线程!

运行结果

rust 复制代码
(base) kunliu@MacBook-Pro-4 async_await % cargo run -- https://www.rust-lang.org https://www.github.com

   Compiling async_await v0.1.0 (/Users/kunliu/project/rust-project/async_await)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.02s
     Running `target/debug/async_await 'https://www.rust-lang.org' 'https://www.github.com'`
https://www.rust-lang.org returned first
Its page title is: 'Rust Programming Language'

关键细节:惰性与运行时

  • Futures 是惰性的 :创建 fut1 = page_title(url1) 时,什么也没发生 !它只是一个"待执行的任务蓝图"。只有当你 .await 它,或者交给运行时(如 tokio::main),它才会真正开始执行。
  • 必须有运行时main 函数不能直接 async,因为需要一个"总调度员"来驱动所有 Future#[tokio::main] 这个宏就在 main 函数外面包了一层 tokio::runtime,让它成为整个异步世界的起点。

章鱼小新的"未来任务"法则

法则 解释
Future 代表一个"未来的值",是异步编程的基础单元。
async fn 语法糖,编译器会将其转换为一个实现了 Future 的状态机。
.await 语法糖,用于等待 Future 完成。本质是轮询(poll)和挂起。
惰性 Future 创建时不执行,必须 .await 或由运行时驱动。
运行时 tokio,是驱动所有 Future 的"引擎",负责调度、轮询、唤醒。
链式调用 Rust 的 await 是后缀操作符,支持 future.await.method().await 这样的链式调用,非常优雅。

现在,小新终于明白了 async/await 的魔法原理。它不是黑盒,而是一套精巧的机制,让你可以用同步的思维写异步的代码,既高效又安全。Rust 的这套设计,堪称现代系统编程语言的典范!

异步任务的"团队合作"大挑战

小新学会了 Future 的原理。现在,他的快餐店升级了!他要用 异步任务 来管理整个厨房,让效率达到极致。

场景一:两个任务一起跑 ------ join

小新想同时做两件事:

  1. 任务A:用慢炖锅煮汤(耗时5秒)。
  2. 任务B:准备沙拉(耗时2秒)。

他希望这两件事同时开始 ,并且等两者都完成后,再开始下一步(上菜)。

rust 复制代码
use std::time::Duration;

async fn cook_soup() {
    for i in 1..6 {
        println!("慢炖锅 - 第{}秒", i);
        trpl::sleep(Duration::from_secs(1)).await;
    }
    println!("汤煮好了!");
}

async fn prepare_salad() {
    for i in 1..3 {
        println!("沙拉准备中... {}", i);
        trpl::sleep(Duration::from_secs(1)).await;
    }
    println!("沙拉备好了!");
}

#[tokio::main]
async fn main() {
    // 创建两个"未来任务"
    let soup_task = cook_soup();
    let salad_task = prepare_salad();

    // 让它们"并肩作战",等两者都完成
    trpl::join(soup_task, salad_task).await;

    println!("可以上菜啦!");
}

输出:

复制代码
   Compiling async_await v0.1.0 (/Users/kunliu/project/rust-project/async_await)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 21.46s
     Running `target/debug/test_join`
慢炖锅 - 第1秒
沙拉准备中... 1
慢炖锅 - 第2秒
沙拉准备中... 2
慢炖锅 - 第3秒
沙拉备好了!
慢炖锅 - 第4秒
慢炖锅 - 第5秒
汤煮好了!
可以上菜啦!

看!两个任务是公平交替执行 的。trpl::join 就像一个"协调员",它会轮流检查两个任务的进度,确保它们都能及时推进,不会出现一个任务"饿死"另一个的情况。

对比线程 :如果是多线程,任务的调度由操作系统决定,顺序可能更"随机"。而 join 是"公平"的,保证了更好的可预测性。


场景二:生产者与消费者 ------ 异步信道(async channel)

小新的厨房越来越复杂。他决定分工:

  • 采购员们(生产者):去市场买食材,买到就通过"内部对讲机"通知厨房。
  • 厨师们(消费者):在厨房里待命,一旦收到"食材到了"的消息,立刻开火做饭。

这就像一个 生产者-消费者模型 。Rust 提供了 异步信道(async channel) 来实现它。

rust 复制代码
use std::time::Duration;
use trpl; // 假设这是一个简化版异步库

#[tokio::main]
async fn main() {
    // 创建一条"对讲机频道",一头是发话器 (tx),一头是听筒 (rx)
    let (tx, mut rx) = trpl::channel();

    // 启动"采购员"任务
    let tx_fut = async move {
        let goods = vec!["番茄", "鸡蛋", "洋葱"];
        for item in goods {
            tx.send(item).unwrap(); // "报告!XX到了!"
            trpl::sleep(Duration::from_secs(1)).await; // 每隔1秒买一样
        }
    };

    // 启动"厨师"任务
    let rx_fut = async {
        while let Some(item) = rx.recv().await { // 一直监听对讲机
            println!("厨师收到:'{}到了!' 准备开工!", item);
        }
    };

    // 让采购和烹饪"并肩作战"
    trpl::join(tx_fut, rx_fut).await;
}

理想输出:

shell 复制代码
(base) kunliu@MacBook-Pro-4 async_await % cargo  run --bin async_channel
   Compiling async_await v0.1.0 (/Users/kunliu/project/rust-project/async_await)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.74s
     Running `target/debug/async_channel`
厨师收到:'番茄到了!' 准备开工!
厨师收到:'鸡蛋到了!' 准备开工!
厨师收到:'洋葱到了!' 准备开工!

async move` 关键字的作用就是:将外部变量的所有权"移动"到异步块内部。这样,当异步块执行完毕,这些变量就会被自动清理,从而触发信道关闭。

高级玩法:多个采购员!

小新生意火爆,需要多个采购员同时工作。

rust 复制代码
use std::time::Duration;
use trpl; // 假设这是一个简化版异步库

#[tokio::main]
async fn main() {
    let (tx, mut rx) = trpl::channel();

    // 采购员A:负责蔬菜
    let tx1 = tx.clone(); // 克隆一个发话器
    let veggie_task = async move {
        let veggies = vec!["番茄", "生菜"];
        for v in veggies {
            tx1.send(format!("蔬菜: {}", v)).unwrap();
            trpl::sleep(Duration::from_millis(500)).await;
        }
    };

    // 采购员B:负责海鲜
    let seafood_task = async move {
        let seafood = vec!["虾", "鱼"];
        for s in seafood {
            tx.send(format!("海鲜: {}", s)).unwrap(); // 用原始的 tx
            trpl::sleep(Duration::from_millis(800)).await;
        }
    };

    // 厨师任务不变
    let chef_task = async {
        while let Some(msg) = rx.recv().await {
            println!("厨房收到:{}", msg);
        }
    };

    // 等三个任务都完成
    trpl::join3(veggie_task, seafood_task, chef_task).await;
}

因为异步信道是"多生产者"的,你可以 clone() 出无数个 tx,让多个"生产者"同时发送消息,而 rx 负责统一接收。

执行结果

shell 复制代码
   Compiling async_await v0.1.0 (/Users/kunliu/project/rust-project/async_await)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.66s
     Running `target/debug/multi_send`
厨房收到:蔬菜: 番茄
厨房收到:海鲜: 虾
厨房收到:蔬菜: 生菜
厨房收到:海鲜: 鱼

章鱼小新的异步协作法则

法则 解释
trpl::join 让多个 Future 并发执行,等全部完成后再继续。是公平的。
异步信道 (channel) 实现"生产者-消费者"模式的安全通信方式。tx 发送,rx 接收(需 .await)。
死锁陷阱 如果 tx 不被丢弃,rx.recv().await 会永远等待,导致程序无法退出。
async move 将变量所有权移入异步块,确保在任务结束时能自动 drop,从而关闭信道。这是避免死锁的关键!
多生产者 可以 clone() tx,让多个任务同时作为生产者发送消息。
资源管理 异步编程中,所有权和生命周期依然是核心,必须谨慎管理资源的创建和销毁。

通过掌握 joinchannelasync move,小新终于打造了一个高效、稳定、可扩展的异步厨房系统。Rust 的这套机制,让你既能享受异步的高性能,又能通过其强大的类型系统和所有权规则,避开各种并发陷阱,真正做到"无所畏惧地并发"!

章鱼小新避免"饿死"的未来任务

小新的异步厨房运转良好。但有一天,他遇到了一个新问题。

问题:CPU密集型任务会"饿死"其他任务

小新写了一个"超级慢"的函数,用来模拟复杂的计算(比如预测明天的客流量):

rust 复制代码
fn slow_calculation(name: &str, duration_ms: u64) {
    // 用"睡眠"模拟耗时计算
    std::thread::sleep(Duration::from_millis(duration_ms));
    println!("'{}' 的计算耗时 {}ms 完成!", name, duration_ms);
}

然后他创建了两个异步任务来"赛跑":

rust 复制代码
use std::time::Duration;

fn slow_calculation(name: &str, duration_ms: u64) {
    // 用"睡眠"模拟耗时计算
    std::thread::sleep(Duration::from_millis(duration_ms));
    println!("'{}' 的计算耗时 {}ms 完成!", name, duration_ms);
}

#[tokio::main]
async fn main() {
    let task_a = async {
        println!("任务A启动!");
        slow_calculation("A", 30); // 耗时30ms
        slow_calculation("A", 10); // 耗时10ms
        slow_calculation("A", 20); // 耗时20ms
        trpl::sleep(Duration::from_millis(50)).await; // 终于有个 await!
        println!("任务A完成!");
    };

    let task_b = async {
        println!("任务B启动!");
        slow_calculation("B", 75); // 耗时75ms
        slow_calculation("B", 10);
        slow_calculation("B", 15);
        slow_calculation("B", 350); // 耗时350ms!
        trpl::sleep(Duration::from_millis(50)).await;
        println!("任务B完成!");
    };

    // 让它们赛跑,谁先完成就结束
    trpl::race(task_a, task_b).await;
}

输出:

复制代码
任务A启动!
'A' 的计算耗时 30ms 完成!
'A' 的计算耗时 10ms 完成!
'A' 的计算耗时 20ms 完成!
任务B启动!
'B' 的计算耗时 75ms 完成!
'B' 的计算耗时 10ms 完成!
'B' 的计算耗时 15ms 完成!
'B' 的计算耗时 350ms 完成!
任务A完成!

问题来了!

  • 任务A和任务B没有交替执行 !任务A先一口气跑完所有 slow_calculation,然后任务B才开始跑。
  • 为什么?因为 slow_calculation 用的是 std::thread::sleep,这是一个阻塞操作!它会一直占用线程,直到时间到。
  • async 任务只有在遇到 .await 时才会"挂起",把执行权还给调度器。在这之前,它就像一个"霸道总裁",独占CPU。

这就导致了 "未来任务饥饿"(future starvation) ------ 其他任务因为得不到执行机会而"饿死"。


解决方案一:在中间插入 .await

最简单的办法是:在每个耗时操作后,都主动 .await 一下,把执行权交出去。

rust 复制代码
let task_a = async {
    println!("任务A启动!");
    slow_calculation("A", 30);
    trpl::sleep(Duration::from_millis(1)).await; // 交出执行权!
    
    slow_calculation("A", 10);
    trpl::sleep(Duration::from_millis(1)).await; // 交出执行权!
    
    slow_calculation("A", 20);
    trpl::sleep(Duration::from_millis(1)).await; // 交出执行权!
    
    println!("任务A完成!");
};

现在,两个任务就能交替执行了:

复制代码
任务A启动!
'A' 的计算耗时 30ms 完成!
任务B启动!
'B' 的计算耗时 75ms 完成!
'A' 的计算耗时 10ms 完成!
'B' 的计算耗时 10ms 完成!
...

但小新觉得:"我并不是真的想'睡1毫秒',我只是想说'我现在可以歇会儿,让别人干干活'!"


解决方案二:yield_now ------ "我先歇会儿!"

Rust 提供了一个更精准的工具:trpl::yield_now().await

它就像一个"礼让"信号,告诉调度器:"我现在没什么紧急的,可以把执行权先给别的任务用用。"

rust 复制代码
let task_a = async {
    println!("任务A启动!");
    slow_calculation("A", 30);
    trpl::yield_now().await; // 礼让!
    
    slow_calculation("A", 10);
    trpl::yield_now().await; // 礼让!
    
    slow_calculation("A", 20);
    trpl::yield_now().await; // 礼让!
    
    println!("任务A完成!");
};

这比 sleep 更好,因为:

  1. 语义清晰:明确表达了"我想让出执行权"的意图。
  2. 效率更高yield_now 是立即返回的,不会真的等待任何时间。而 sleep 至少要等一个时钟周期(通常1ms),在这期间CPU还是空闲的。

更多并发工具箱

join_all:等一群任务完成

除了 join,还有 join_all,它可以等待一个 Vec<Future> 中的所有任务完成。

rust 复制代码
let tasks = vec![
    async { 1 },
    async { 2 },
    async { 3 },
];

let results = trpl::join_all(tasks).await;
println!("{:?}", results); // [1, 2, 3]

注意:join_all 要求所有 Future 类型相同。如果类型不同,可以用 join! 宏。

race:赛跑!谁先到谁赢

trpl::race 就像一场赛跑,只要有一个任务完成,整个 race 就算完成,其他任务会被取消。

rust 复制代码
let slow_task = async {
    trpl::sleep(Duration::from_secs(2)).await;
    "慢的赢了!"
};

let fast_task = async {
    trpl::sleep(Duration::from_secs(1)).await;
    "快的赢了!"
};

let winner = trpl::race(slow_task, fast_task).await;
println!("{}", winner); // 快的赢了!

注意:race 的实现可能不公平。它通常按参数顺序先执行第一个任务,直到它遇到第一个 .await,才轮到下一个。所以 race(fast, slow)race(slow, fast) 的启动顺序可能不同。


章鱼小新的"防饿死"法则

法则 解释
避免阻塞 async 任务中,不要调用会阻塞线程的函数(如 std::thread::sleep)。用异步版本(trpl::sleep)替代。
await 是挂起点 只有遇到 .await,任务才会"挂起",让出执行权。await 之间的代码是同步执行的。
防止饥饿 如果有长时间运行的CPU密集型任务,要主动在中间插入 .awaityield_now(),避免独占CPU。
yield_now() 主动礼让执行权的最佳方式,语义清晰且高效。
join_all vs join! join_all 等待同类型 FutureVecjoin! 宏可以等待不同类型、数量固定的 Future
race "赛跑"模式,任一任务完成即宣告胜利,适合超时、重试等场景。

现在,小新彻底掌握了异步任务的"生存法则"。他知道,一个健康的异步系统,不仅要有高效的并发,更要确保每个任务都能公平地获得执行机会,避免"内卷"和"饿死"。Rust 的这套工具,让他既能写高性能代码,又能保持系统的稳定与健壮!

相关推荐
Empty_7773 小时前
SELinux安全上下文
linux·服务器·安全
先锋队长6 小时前
linux系统搭建nacos集群,并通过nginx实现负载均衡
linux·nginx·负载均衡
勇敢牛牛_6 小时前
使用Rust实现服务配置/注册中心
开发语言·后端·rust·注册中心·配置中心
恒创科技HK8 小时前
现在中国香港服务器速度怎么样?
运维·服务器
创业之路&下一个五年8 小时前
第一部分:服务器硬件配置
运维·服务器
Liang_GaRy10 小时前
心路历程-Linux的特殊权限
linux·运维·服务器
monster_风铃10 小时前
BFD原理与配置
服务器·网络·tcp/ip·信息安全管理与评估
浪费笔墨10 小时前
Rail开发日志_9
rust