-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refac(balaner): define balancer endpoint and round-robin routing
- Loading branch information
1 parent
eb20c98
commit 5cef969
Showing
4 changed files
with
113 additions
and
31 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,116 @@ | ||
#![allow(dead_code)] | ||
|
||
use std::{ | ||
collections::HashMap, | ||
net::SocketAddr | ||
net::IpAddr, | ||
str::FromStr as _, | ||
sync::{ | ||
atomic::{AtomicUsize, Ordering}, | ||
Arc, Mutex, | ||
}, | ||
time::Duration, | ||
}; | ||
|
||
use axum::extract::Request; | ||
use proto::common::instance::InstanceId; | ||
use proto::common::service::ServiceId; | ||
use axum::{ | ||
body::Body, | ||
extract::{Request, State}, | ||
http::{ | ||
uri::{Authority, Scheme}, | ||
HeaderValue, StatusCode, Uri, | ||
}, | ||
response::IntoResponse, | ||
}; | ||
use hyper_util::{ | ||
client::legacy::{connect::HttpConnector, Client}, | ||
rt::TokioExecutor, | ||
}; | ||
use proto::{ | ||
common::{instance::InstanceId, service::ServiceId}, | ||
well_known::PROXY_INSTANCE_HEADER_NAME, | ||
}; | ||
use utils::http::{self, OptionExt as _, ResultExt as _}; | ||
|
||
struct Balancer<S> { | ||
strategy: S, | ||
addrs: HashMap<ServiceId, Vec<(InstanceId, SocketAddr)>> | ||
pub struct InstanceBag { | ||
pub instances: Vec<(InstanceId, IpAddr)>, | ||
pub count: AtomicUsize, | ||
} | ||
|
||
trait Strategy { | ||
async fn get_server(&self, _req: &Request) -> (InstanceId, SocketAddr); | ||
#[derive(Clone)] | ||
pub struct Balancer { | ||
pub addrs: Arc<Mutex<HashMap<ServiceId, InstanceBag>>>, | ||
} | ||
|
||
impl<S> Balancer<S> | ||
where | ||
S: Strategy | ||
{ | ||
pub async fn run() { | ||
todo!(); | ||
impl Balancer { | ||
pub fn new() -> Self { | ||
Balancer { | ||
addrs: Arc::new(Mutex::new(HashMap::default())), | ||
} | ||
} | ||
|
||
async fn next_server(&self, _req: &Request) -> (InstanceId, SocketAddr) { | ||
todo!(); | ||
pub fn next(&self, service: &ServiceId) -> (InstanceId, IpAddr) { | ||
let map = self.addrs.lock().unwrap(); | ||
let bag = map.get(service).unwrap(); | ||
let count = bag.count.fetch_add(1, Ordering::Relaxed); | ||
bag.instances[count % bag.instances.len()] | ||
} | ||
} | ||
|
||
pub async fn drop_instance(&self, _id: InstanceId) { | ||
todo!(); | ||
} | ||
|
||
pub async fn add_instance(&self, _id: InstanceId, _at: SocketAddr) { | ||
todo!(); | ||
} | ||
#[derive(Clone)] | ||
pub struct BalancerState { | ||
pub balancer: Balancer, | ||
pub client: Client<HttpConnector, Body>, | ||
} | ||
|
||
pub async fn swap_instance(_old_id: InstanceId, _new_id: InstanceId, _new_at: SocketAddr) { | ||
todo!(); | ||
impl BalancerState { | ||
#[must_use] | ||
pub fn new() -> Self { | ||
BalancerState { | ||
balancer: Balancer::new(), | ||
client: { | ||
let mut connector = HttpConnector::new(); | ||
connector.set_keepalive(Some(Duration::from_secs(60))); | ||
connector.set_nodelay(true); | ||
Client::builder(TokioExecutor::new()).build::<_, Body>(connector) | ||
}, | ||
} | ||
} | ||
} | ||
|
||
#[axum::debug_handler] | ||
pub async fn proxy( | ||
State(state): State<BalancerState>, | ||
mut req: Request, | ||
) -> http::Result<impl IntoResponse> { | ||
let service = extract_service_id(&mut req)?; | ||
|
||
let (instance, server) = state.balancer.next(&service); | ||
|
||
*req.uri_mut() = { | ||
let uri = req.uri(); | ||
let mut parts = uri.clone().into_parts(); | ||
parts.authority = Authority::from_str(&format!("{server}")).ok(); | ||
parts.scheme = Some(Scheme::HTTP); | ||
Uri::from_parts(parts).unwrap() | ||
}; | ||
|
||
req.headers_mut().insert( | ||
PROXY_INSTANCE_HEADER_NAME, | ||
HeaderValue::from_str(&format!("{instance}")).unwrap(), | ||
); | ||
|
||
state | ||
.client | ||
.request(req) | ||
.await | ||
.http_error(StatusCode::BAD_GATEWAY, "bad gateway") | ||
} | ||
|
||
fn extract_service_id(req: &mut Request) -> http::Result<ServiceId> { | ||
let inner = req | ||
.headers() | ||
.get("host") | ||
.unwrap() | ||
.to_str() | ||
.ok() | ||
.and_then(|s| s.parse().ok()) | ||
.or_http_error(StatusCode::BAD_REQUEST, "invalid service name")?; | ||
Ok(ServiceId(inner)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters