Milestone 3
This commit is contained in:
parent
374f2690d8
commit
fc4685eff4
@ -34,4 +34,13 @@
|
|||||||
|
|
||||||
又是一个奇怪的 task point,要求把所有的 IO 换成异步的。但是依然没有任何的难度,根据 IDE 提示加 `async` 和 `await` 就可以了。
|
又是一个奇怪的 task point,要求把所有的 IO 换成异步的。但是依然没有任何的难度,根据 IDE 提示加 `async` 和 `await` 就可以了。
|
||||||
|
|
||||||
|
### Milestone 3
|
||||||
|
|
||||||
|
实现故障转移(failover):如果一个 upstream 挂了,将连接选一个别的(一开始有一堆可用的 upstream),同时还要标记这个挂掉的 upstream 防止后面的连接继续选择它;如果所有的 upstream 都挂了,这时候才返回错误。实现方法是修改 `main.rs::connect_to_upstream`,因为这个过程仅在建立连接的时候执行。
|
||||||
|
|
||||||
|
实现了一个最简单的,在 `ProxyState` 里面加了一个 `Arc<Mutex<Vec<usize>>>`,用来存放可用的 upstream 的 index。
|
||||||
|
选择的时候就在这个加了锁的 `Vec` 里面选,然后用这个里面的 index 来取对应的 upstream 的地址。如果一开始就发现这个可用列表为空,那么直接返回错误;否则尝试连接。如果连接失败,需要将这个 index 从可用列表里面删除。唯一需要注意的就是,可以在 `TcpStream::connect` 之前释放一次锁,然后需要修改可用列表的时候再加锁,因为 connect 比较耗时。
|
||||||
|
|
||||||
|
感觉 RwLock 和这个东西差不多,但是可能在某些情况下的并发性能会更好一些。至于它说的用 channel,感觉挺麻烦的,所以没写。
|
||||||
|
|
||||||
## 附加任务?
|
## 附加任务?
|
||||||
@ -5,6 +5,7 @@ use clap::Parser;
|
|||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
// use std::net::{TcpListener, TcpStream};
|
// use std::net::{TcpListener, TcpStream};
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Contains information parsed from the command-line invocation of balancebeam. The Clap macros
|
/// Contains information parsed from the command-line invocation of balancebeam. The Clap macros
|
||||||
/// provide a fancy way to automatically construct a command-line argument parser.
|
/// provide a fancy way to automatically construct a command-line argument parser.
|
||||||
@ -48,6 +49,8 @@ struct ProxyState {
|
|||||||
max_requests_per_minute: usize,
|
max_requests_per_minute: usize,
|
||||||
/// Addresses of servers that we are proxying to
|
/// Addresses of servers that we are proxying to
|
||||||
upstream_addresses: Vec<String>,
|
upstream_addresses: Vec<String>,
|
||||||
|
/// Dead upstream marks
|
||||||
|
upstream_status: Arc<tokio::sync::Mutex<Vec<usize>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@ -77,14 +80,20 @@ async fn main() -> std::io::Result<()> {
|
|||||||
};
|
};
|
||||||
log::info!("Listening for requests on {}", options.bind);
|
log::info!("Listening for requests on {}", options.bind);
|
||||||
|
|
||||||
|
let mut upstream_status = Vec::with_capacity(options.upstream.len());
|
||||||
|
for idx in 0..options.upstream.len() {
|
||||||
|
upstream_status.push(idx);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle incoming connections
|
// Handle incoming connections
|
||||||
let state = ProxyState {
|
let state = ProxyState {
|
||||||
upstream_addresses: options.upstream,
|
upstream_addresses: options.upstream,
|
||||||
active_health_check_interval: options.active_health_check_interval,
|
active_health_check_interval: options.active_health_check_interval,
|
||||||
active_health_check_path: options.active_health_check_path,
|
active_health_check_path: options.active_health_check_path,
|
||||||
max_requests_per_minute: options.max_requests_per_minute,
|
max_requests_per_minute: options.max_requests_per_minute,
|
||||||
|
upstream_status: Arc::new(tokio::sync::Mutex::new(upstream_status)),
|
||||||
};
|
};
|
||||||
let astate = std::sync::Arc::new(state);
|
let astate = Arc::new(state);
|
||||||
// let thread_pool = threadpool::ThreadPool::new(4);
|
// let thread_pool = threadpool::ThreadPool::new(4);
|
||||||
// for stream in listener.incoming() {
|
// for stream in listener.incoming() {
|
||||||
// if let Ok(stream) = stream {
|
// if let Ok(stream) = stream {
|
||||||
@ -111,13 +120,32 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
async fn connect_to_upstream(state: &ProxyState) -> Result<TcpStream, std::io::Error> {
|
async fn connect_to_upstream(state: &ProxyState) -> Result<TcpStream, std::io::Error> {
|
||||||
let mut rng = rand::rngs::StdRng::from_entropy();
|
let mut rng = rand::rngs::StdRng::from_entropy();
|
||||||
let upstream_idx = rng.gen_range(0..state.upstream_addresses.len());
|
loop {
|
||||||
let upstream_ip = &state.upstream_addresses[upstream_idx];
|
let upstream_state = state.upstream_status.lock().await;
|
||||||
TcpStream::connect(upstream_ip).await.or_else(|err| {
|
if upstream_state.len() == 0 {
|
||||||
log::error!("Failed to connect to upstream {}: {}", upstream_ip, err);
|
return Err(std::io::Error::new(std::io::ErrorKind::Other, "No living upstream"));
|
||||||
Err(err)
|
}
|
||||||
})
|
let upstream_idx_idx = rng.gen_range(0..upstream_state.len());
|
||||||
// TODO: implement failover (milestone 3)
|
let upstream_ip = &state.upstream_addresses[upstream_state[upstream_idx_idx]];
|
||||||
|
drop(upstream_state); // first release lock, 'cause tcp connect is time consuming
|
||||||
|
let stream = TcpStream::connect(upstream_ip).await;
|
||||||
|
match stream {
|
||||||
|
Ok(_) => return stream,
|
||||||
|
Err(_) => {
|
||||||
|
let mut upstream_state = state.upstream_status.lock().await;
|
||||||
|
upstream_state.remove(upstream_idx_idx);
|
||||||
|
if upstream_state.len() == 0{
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
drop(upstream_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TcpStream::connect(upstream_ip).await.or_else(|err| {
|
||||||
|
// log::error!("Failed to connect to upstream {}: {}", upstream_ip, err);
|
||||||
|
// upstream_state[upstream_idx] = false;
|
||||||
|
// Err(err)
|
||||||
|
// })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_response(client_conn: &mut TcpStream, response: &http::Response<Vec<u8>>) {
|
async fn send_response(client_conn: &mut TcpStream, response: &http::Response<Vec<u8>>) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user