perf(hashmap): use dashmap as global store

This commit is contained in:
xfy
2025-05-16 15:54:35 +08:00
parent f86bb996b1
commit d5751f1e14
7 changed files with 59 additions and 27 deletions

30
Cargo.lock generated
View File

@ -345,6 +345,7 @@ dependencies = [
"bytes",
"clap",
"const_format",
"dashmap",
"futures-util",
"http",
"http-body-util",
@ -498,6 +499,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -508,6 +515,21 @@ dependencies = [
"typenum",
]
[[package]]
name = "dashmap"
version = "6.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
dependencies = [
"cfg-if",
"crossbeam-utils",
"hashbrown 0.14.5",
"lock_api",
"once_cell",
"parking_lot_core",
"serde",
]
[[package]]
name = "digest"
version = "0.10.7"
@ -699,6 +721,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "hashbrown"
version = "0.15.2"
@ -854,7 +882,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
"hashbrown",
"hashbrown 0.15.2",
]
[[package]]

View File

@ -30,6 +30,7 @@ toml = "0.8.20"
bytes = "1.10.1"
const_format = "0.2.34"
md5 = "0.7.0"
dashmap = { version = "6.1.0", features = ["serde"] }
# logging
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }

View File

@ -5,9 +5,10 @@ use crate::{
},
error::Result,
};
use std::{borrow::Cow, collections::BTreeMap, fs};
use std::{borrow::Cow, fs};
use anyhow::Context;
use dashmap::DashMap;
use serde::Deserialize;
#[derive(Deserialize, Clone, Debug)]
@ -42,9 +43,9 @@ pub struct SettingRoute {
/// Host routes
/// Each host can have multiple routes
pub type HostRouteMap = BTreeMap<String, SettingRoute>;
pub type HostRouteMap = DashMap<String, SettingRoute>;
/// headers
pub type HeaderMap = BTreeMap<String, String>;
pub type HeaderMap = DashMap<String, String>;
/// Virtual host
/// Each host can listen on one port and one ip
@ -63,12 +64,12 @@ pub struct SettingHost {
pub certificate_key: Option<String>,
/// Routes in config file
pub route: Vec<SettingRoute>,
/// Host routes convert from Vec<SettingRoute> to BTreeMap<String, SettingRoute>
/// Host routes convert from Vec<SettingRoute> to DashMap<String, SettingRoute>
/// {
/// "/doc": <SettingRoute>
/// }
#[serde(skip)]
pub route_map: BTreeMap<String, SettingRoute>,
pub route_map: DashMap<String, SettingRoute>,
/// HTTP keep-alive timeout
#[serde(default = "timeout_default")]
pub timeout: u16,
@ -77,7 +78,7 @@ pub struct SettingHost {
pub headers: Option<HeaderMap>,
}
pub type MIMEType = BTreeMap<Cow<'static, str>, Cow<'static, str>>;
pub type MIMEType = DashMap<Cow<'static, str>, Cow<'static, str>>;
/// Whole config settings
#[derive(Deserialize, Clone, Debug, Default)]

View File

@ -1,4 +1,6 @@
use std::{borrow::Cow, collections::BTreeMap, env};
use std::{borrow::Cow, env};
use dashmap::DashMap;
use crate::config::MIMEType;
@ -36,7 +38,7 @@ pub fn upstream_timeout_default() -> u16 {
// default mime types
pub fn types_default() -> MIMEType {
BTreeMap::new()
DashMap::new()
}
// macro_rules! insert_mime {
// ($name:literal, $mime:ident, $map:ident) => {

View File

@ -1,5 +1,4 @@
use std::{
collections::BTreeMap,
path::Path,
sync::{Arc, LazyLock},
time::Duration,
@ -7,10 +6,11 @@ use std::{
use anyhow::anyhow;
use axum::{Router, extract::Request, middleware, routing::get};
use dashmap::DashMap;
use futures_util::pin_mut;
use hyper::body::Incoming;
use hyper_util::rt::{TokioExecutor, TokioIo};
use tokio::{net::TcpListener, sync::RwLock};
use tokio::net::TcpListener;
use tokio_rustls::{
TlsAcceptor,
rustls::{
@ -42,14 +42,13 @@ pub mod serve;
/// "/doc": <SettingRoute>
/// }
/// }
pub static HOSTS: LazyLock<RwLock<BTreeMap<u16, SettingHost>>> =
LazyLock::new(|| RwLock::new(BTreeMap::new()));
pub static HOSTS: LazyLock<DashMap<u16, SettingHost>> = LazyLock::new(DashMap::new);
// static ROUTE_MAP: LazyLock<RwLock<HostRouteMap>> = LazyLock::new(|| RwLock::new(BTreeMap::new()));
pub async fn make_server(host: SettingHost) -> anyhow::Result<()> {
let mut router = Router::new();
let mut host_to_save = host.clone();
let host_to_save = host.clone();
// find routes in config
// convert to axum routes
// register routes
@ -114,7 +113,7 @@ pub async fn make_server(host: SettingHost) -> anyhow::Result<()> {
}
// save host to map
HOSTS.write().await.insert(host.port, host_to_save);
HOSTS.insert(host.port, host_to_save);
router = router.layer(
ServiceBuilder::new()

View File

@ -6,6 +6,7 @@ use axum::{
response::{IntoResponse, Response},
};
use axum_extra::extract::Host;
use dashmap::mapref::one::Ref;
use futures_util::StreamExt;
use http::{
HeaderValue, StatusCode, Uri,
@ -129,9 +130,8 @@ pub async fn serve(
// which is `host_route.location`
let scheme = request.uri().scheme_str().unwrap_or("http");
let port = parse_port_from_host(&host, scheme).ok_or(RouteError::BadRequest())?;
let hosts = &HOSTS.read().await;
let route_map = &hosts.get(&port).ok_or(RouteError::BadRequest())?.route_map;
debug!("Route map entries: {:?}", route_map.keys());
let route_map = &HOSTS.get(&port).ok_or(RouteError::BadRequest())?.route_map;
debug!("Route map entries: {:?}", route_map);
let host_route = route_map
.get(&parent_path)
.ok_or(RouteError::RouteNotFound())?;
@ -155,10 +155,10 @@ pub async fn serve(
if path.contains('.') {
vec![format!("{}/{}", root, path)]
} else {
generate_default_index(host_route, &format!("{root}/{path}"))
generate_default_index(&host_route, &format!("{root}/{path}"))
}
} else {
generate_default_index(host_route, root)
generate_default_index(&host_route, root)
};
debug!("request index file {:?}", path_arr);
// Try each candidate path in order:
@ -193,7 +193,7 @@ pub async fn serve(
/// ## Arguments
/// - `host_route`: the host route config
/// - `root`: the root path
fn generate_default_index(host_route: &SettingRoute, root: &str) -> Vec<String> {
fn generate_default_index(host_route: &Ref<'_, String, SettingRoute>, root: &str) -> Vec<String> {
let indices = if host_route.index.is_empty() {
let host_iter = HOST_INDEX
.iter()

View File

@ -94,24 +94,25 @@ pub async fn add_headers(Host(host): Host, req: Request, next: Next) -> impl Int
debug!("port {:?}", port);
let mut res = next.run(req).await;
let req_headers = res.headers_mut();
let host = HOSTS.read().await;
let Some(host) = host.get(&port) else {
// let host = HOSTS.read().await;
let Some(host) = HOSTS.get(&port) else {
return res;
};
let Some(headers) = host.headers.as_ref() else {
return res;
};
for (key, value) in headers {
headers.iter().for_each(|entery| {
let (key, value) = (entery.key(), entery.value());
let Ok(header_name) = HeaderName::from_bytes(key.as_bytes()) else {
error!("Invalid header name: {key}");
break;
return;
};
let Ok(header_value) = HeaderValue::from_bytes(value.as_bytes()) else {
error!("Invalid header value: {value}");
break;
return;
};
req_headers.append(header_name, header_value);
}
});
res
}