Skip to content

Commit 73a879f

Browse files
authored
feature: add hotkey feature (#45)
1 parent 0fc8773 commit 73a879f

File tree

9 files changed

+925
-0
lines changed

9 files changed

+925
-0
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ futures = "0.3"
4343
md5 = "0.7"
4444
rand = "0.8"
4545
socket2 = "0.5"
46+
arc-swap = "1"
4647

4748
[profile.release]
4849
debug = true

default.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
slowlog_log_slower_than = 10000
2727
# 慢查询日志最大保留条数;默认 128
2828
slowlog_max_len = 128
29+
# 热点 Key 分析参数
30+
hotkey_sample_every = 32
31+
hotkey_sketch_width = 4096
32+
hotkey_sketch_depth = 4
33+
hotkey_capacity = 512
34+
hotkey_decay = 0.925
2935

3036
[[clusters]]
3137
name = "test-cluster"
@@ -58,3 +64,9 @@
5864
slowlog_log_slower_than = 10000
5965
# 慢查询日志最大保留条数;默认 128
6066
slowlog_max_len = 128
67+
# 热点 Key 分析参数
68+
hotkey_sample_every = 32
69+
hotkey_sketch_width = 4096
70+
hotkey_sketch_depth = 4
71+
hotkey_capacity = 512
72+
hotkey_decay = 0.925

docs/usage.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,16 @@ cargo build --release
2828
- `read_from_slave`:Cluster 模式下允许从 replica 读取。
2929
- `slowlog_log_slower_than`:慢查询阈值(微秒,默认 `10000`,设为 `-1` 关闭记录)。
3030
- `slowlog_max_len`:慢查询日志最大保留条数(默认 `128`)。
31+
- `hotkey_sample_every`:热点 Key 采样间隔(默认 `32`,越大代表对请求采样越稀疏)。
32+
- `hotkey_sketch_width` / `hotkey_sketch_depth`:热点 Key 频率估算所用 Count-Min Sketch 宽度与深度,决定误差与内存占用。
33+
- `hotkey_capacity`:HeavyKeeper 桶容量,用于保留候选热点 Key 数量上限。
34+
- `hotkey_decay`:HeavyKeeper 衰减系数,取值 `(0, 1]`,越接近 `1` 越倾向保留历史数据。
3135
- `auth` / `password`:前端 ACL,详见下文。
3236
- `backend_auth` / `backend_password`:后端 ACL 认证,详见下文。
3337

3438
> 提示:代理原生支持 `SLOWLOG GET/LEN/RESET`,并按集群维度汇总慢查询;配置上述阈值和长度即可控制记录行为。
39+
>
40+
> 热点 Key 分析可通过 `HOTKEY ENABLE|DISABLE|GET|RESET` 控制,相关采样参数可在配置文件或 `CONFIG SET cluster.<name>.hotkey-*` 中动态调整。
3541
3642
示例参见仓库根目录的 `default.toml`
3743

src/cluster/mod.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use crate::auth::{AuthAction, BackendAuth, FrontendAuthenticator};
2121
use crate::backend::client::{ClientId, FrontConnectionGuard};
2222
use crate::backend::pool::{BackendNode, ConnectionPool, Connector, SessionCommand};
2323
use crate::config::{ClusterConfig, ClusterRuntime, ConfigManager};
24+
use crate::hotkey::Hotkey;
2425
use crate::info::{InfoContext, ProxyMode};
2526
use crate::metrics;
2627
use crate::protocol::redis::{
@@ -54,6 +55,7 @@ pub struct ClusterProxy {
5455
runtime: Arc<ClusterRuntime>,
5556
config_manager: Arc<ConfigManager>,
5657
slowlog: Arc<Slowlog>,
58+
hotkey: Arc<Hotkey>,
5759
listen_port: u16,
5860
seed_nodes: usize,
5961
}
@@ -88,6 +90,9 @@ impl ClusterProxy {
8890
let slowlog = config_manager
8991
.slowlog_for(&config.name)
9092
.ok_or_else(|| anyhow!("missing slowlog state for cluster {}", config.name))?;
93+
let hotkey = config_manager
94+
.hotkey_for(&config.name)
95+
.ok_or_else(|| anyhow!("missing hotkey state for cluster {}", config.name))?;
9196
let proxy = Self {
9297
cluster: cluster.clone(),
9398
hash_tag,
@@ -100,6 +105,7 @@ impl ClusterProxy {
100105
runtime,
101106
config_manager,
102107
slowlog,
108+
hotkey,
103109
listen_port,
104110
seed_nodes: config.servers.len(),
105111
};
@@ -301,6 +307,18 @@ impl ClusterProxy {
301307
inflight += 1;
302308
continue;
303309
}
310+
if let Some(response) = self.try_handle_hotkey(&cmd) {
311+
let success = !response.is_error();
312+
metrics::front_command(
313+
self.cluster.as_ref(),
314+
cmd.kind_label(),
315+
success,
316+
);
317+
let fut = async move { response };
318+
pending.push_back(Box::pin(fut));
319+
inflight += 1;
320+
continue;
321+
}
304322
if let Some(response) = self.try_handle_slowlog(&cmd) {
305323
let success = !response.is_error();
306324
metrics::front_command(
@@ -367,6 +385,13 @@ impl ClusterProxy {
367385
))
368386
}
369387

388+
fn try_handle_hotkey(&self, command: &RedisCommand) -> Option<RespValue> {
389+
if !command.command_name().eq_ignore_ascii_case(b"HOTKEY") {
390+
return None;
391+
}
392+
Some(crate::hotkey::handle_command(&self.hotkey, command.args()))
393+
}
394+
370395
fn try_handle_info(&self, command: &RedisCommand) -> Option<RespValue> {
371396
if !command.command_name().eq_ignore_ascii_case(b"INFO") {
372397
return None;
@@ -675,6 +700,7 @@ impl ClusterProxy {
675700
let fetch_trigger = self.fetch_trigger.clone();
676701
let cluster = self.cluster.clone();
677702
let slowlog = self.slowlog.clone();
703+
let hotkey = self.hotkey.clone();
678704
let kind_label = command.kind_label();
679705
Box::pin(async move {
680706
match dispatch_with_context(
@@ -685,6 +711,7 @@ impl ClusterProxy {
685711
fetch_trigger,
686712
client_id,
687713
slowlog,
714+
hotkey,
688715
command,
689716
)
690717
.await
@@ -1263,6 +1290,7 @@ async fn dispatch_with_context(
12631290
fetch_trigger: mpsc::UnboundedSender<()>,
12641291
client_id: ClientId,
12651292
slowlog: Arc<Slowlog>,
1293+
hotkey: Arc<Hotkey>,
12661294
command: RedisCommand,
12671295
) -> Result<RespValue> {
12681296
let command_snapshot = command.clone();
@@ -1292,6 +1320,7 @@ async fn dispatch_with_context(
12921320
.await
12931321
};
12941322
slowlog.maybe_record(&command_snapshot, started.elapsed());
1323+
hotkey.record_command(&command_snapshot);
12951324
result
12961325
}
12971326

0 commit comments

Comments
 (0)