# Project-2 Balancebeam 实现记录 ## 综述 这东西本质上是一个类似于反向代理之类的东西, ## 每个 Milestone 的说明 ### Milestone 0 依然是传统的看代码环节。 首先是 `main.rs` 里面的东西。 1. `CmdOption` 用的是 `clap` 这个 crate,用非常奇葩的 macro 语法来定义命令行参数并解析。 2. `ProxyState` 是执行线程的共享参数,不知道有啥特别的意义,可能是为后面的东西预留的 3. `TCPListener` 是标准库提供的东西,提供了一个简单的 TCP server。它通过 `incoming` 迭代器来返回构造的 TCP stream 抽象,这种字节流抽象最终会被分发给执行线程。 4. 执行线程: 1. 首先,它随机选择一个上游服务器并打开与该服务器的连接。如果服务器无了,返回502。 2. 然后循环从客户端读取请求,客户端可以在一个连接上发送任何数量的请求,而 `balancebeam` 会将每个请求转发到上游服务器。如果客户端发送了一个错误的请求,会有一些错误处理。 3. 一旦请求被读取,`balancebeam` 就会添加一个 `X-Forwarded-For HTTP` 头,以便上游服务器能够知道客户端的IP地址。(这对理解并不特别重要;它只是一个事实上的标准头。) 4. 接下来,`balancebeam` 将请求转发给上游服务器。如果在传输请求时有错误,它就会通知客户端。 5. 接下来,`balancebeam` 试图从上游服务器读取响应。如果响应被破坏或在接收时有其他问题,它将通知客户端。 6. 最后,`balancebeam` 将响应转发给客户。 然后是封装库 `request.rs` `response.rs`,就像 project 1 里面的 gimili wrapper 一样,又臭又长。分别用来基于 TCP 字节流来处理 HTTP 请求和响应。分别提供了 `read_from_stream` 和 `write_to_stream` 这两个接口供 main 处理两个 TCP 链接( Client HTTP req -> Proxy HTTP req -> Server, Server HTTP resp -> Proxy HTTP resp -> Client)。具体的里面的接口我也没细看,等到 Milestone 2 需要修改的时候再说吧。 ### Milestone 1 讲义上没有提出明确的实现目标,简单的把代码改成多线程而已。主要就是把 `state` 用 `Arc` 给共享掉(反正是不可变借用,十分的安全)。 `ThreadPool` 和 `std::thread` 没啥太大区别,都封装的很好了,直接看一下 `doc.rs` 里面的例子就行了。 ### Milestone 2 又是一个奇怪的 task point,要求把所有的 IO 换成异步的。但是依然没有任何的难度,根据 IDE 提示加 `async` 和 `await` 就可以了。 ### Milestone 3 实现故障转移(failover):如果一个 upstream 挂了,将连接选一个别的(一开始有一堆可用的 upstream),同时还要标记这个挂掉的 upstream 防止后面的连接继续选择它;如果所有的 upstream 都挂了,这时候才返回错误。实现方法是修改 `main.rs::connect_to_upstream`,因为这个过程仅在建立连接的时候执行。 实现了一个最简单的,在 `ProxyState` 里面加了一个 `Arc>>`,用来存放可用的 upstream 的 index。 选择的时候就在这个加了锁的 `Vec` 里面选,然后用这个里面的 index 来取对应的 upstream 的地址。如果一开始就发现这个可用列表为空,那么直接返回错误;否则尝试连接。如果连接失败,需要将这个 index 从可用列表里面删除。唯一需要注意的就是,可以在 `TcpStream::connect` 之前释放一次锁,然后需要修改可用列表的时候再加锁,因为 connect 比较耗时。 感觉 RwLock 和这个东西差不多,但是可能在某些情况下的并发性能会更好一些。至于它说的用 channel,感觉挺麻烦的,所以没写。 ### Milestone 4 实现周期性的主动连接测试(active health check):就是我们的代理程序隔一段时间向每个 upstream 的某一个特定地址(参数里面的 `active_health_check_path`)发一个 HTTP 请求,如果返回 200 就说明没问题;如果之前有问题现在没问题,就可以继续用;出现问题则需要标记为下线。 由于上个 Milestone 里面用到的极简数据结构,导致实现起来有点生草。这里我是用 `tokio::spawn` 来生成了一个 task,根据文档的说明,这是一个 green thread,鬼知道是什么东西。然后一个大循环,先 sleep 一波(不能放到后面,因为他的测试比较智障,放到后面会出错);遍历所有的 upstream(不管有没有被 disable),建立 `tcpstream` ,构造空请求,然后接受响应,最后判断状态码是不是200。以上任何一步出错,都必须从可用列表中删除(注意有可能已经删除了);如果是 200,则需要将它重新加入到可用列表中(如果之前被删除了的话)。 一个坑:不要使用前面实现的 `connect_to_upstream`,因为它无法连接到已经被移出可用列表的 upstream(不如说它根本就不让自己选 upstream),但是我们这里需要测试每个 upstream。 ## 附加任务?