Skip to content

Commit b1b5792

Browse files
authored
feat(PROTO-945): implement Redis-based rate limiting (#6)
* feat(PROTO-945): implement Redis-based rate limiting and update dependencies - Added RedisRateLimit for distributed rate limiting, allowing connection tracking across multiple instances. - Updated Cargo.toml to include new dependencies for Redis and related packages. - Enhanced README with Redis integration instructions and usage examples. - Modified main.rs to support Redis configuration via command-line arguments. - Updated Cargo.lock with new package versions and dependencies. * Add redis-tools to github action for test runs
1 parent 4976b4f commit b1b5792

File tree

6 files changed

+509
-33
lines changed

6 files changed

+509
-33
lines changed

.github/workflows/ci.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ jobs:
1616
steps:
1717
- uses: actions/checkout@v4
1818

19+
- name: Install Redis for tests
20+
run: |
21+
sudo apt-get update
22+
sudo apt-get install -y redis
23+
1924
- name: Install Rust toolchain
2025
uses: dtolnay/rust-toolchain@master
2126
with:

Cargo.lock

+114-26
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@ metrics-derive = "0.1"
2525
thiserror = "2.0.11"
2626
serde_json = "1.0.138"
2727
hostname = "0.4.0"
28+
redis = "0.24.0"
29+
redis-test = { version = "0.9.0", optional = true }
30+
2831

2932
[dependencies.ring]
3033
version = "0.17.12"
3134

3235
[features]
33-
integration = []
36+
integration = ["redis-test"]

README.md

+28-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ You can build and test the project using [Cargo](https://doc.rust-lang.org/cargo
2222
# Build the project
2323
cargo build
2424
25-
# Run all the tests
25+
# Run all the tests (requires local version of redis to be installed)
2626
cargo test --all-features
2727
```
2828

@@ -35,3 +35,30 @@ You can see a full list of parameters by running:
3535

3636
`docker run ghcr.io/base/flashblocks-websocket-proxy:master --help`
3737

38+
### Redis Integration
39+
40+
The proxy supports distributed rate limiting with Redis. This is useful when running multiple instances of the proxy behind a load balancer, as it allows rate limits to be enforced across all instances.
41+
42+
To enable Redis integration, use the following parameters:
43+
44+
- `--redis-url` - Redis connection URL (e.g., `redis://localhost:6379`)
45+
- `--redis-key-prefix` - Prefix for Redis keys (default: `flashblocks`)
46+
47+
Example:
48+
49+
```bash
50+
docker run ghcr.io/base/flashblocks-websocket-proxy:master \
51+
--upstream-ws wss://your-sequencer-endpoint \
52+
--redis-url redis://redis:6379 \
53+
--global-connections-limit 1000 \
54+
--per-ip-connections-limit 10
55+
```
56+
57+
When Redis is enabled, the following features are available:
58+
59+
- Distributed rate limiting across multiple proxy instances
60+
- Connection tracking persists even if the proxy instance restarts
61+
- More accurate global connection limiting in multi-instance deployments
62+
63+
If the Redis connection fails, the proxy will automatically fall back to in-memory rate limiting.
64+

src/main.rs

+51-5
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ mod server;
88
mod subscriber;
99

1010
use crate::metrics::Metrics;
11-
use crate::rate_limit::InMemoryRateLimit;
11+
use crate::rate_limit::{InMemoryRateLimit, RateLimit};
1212
use crate::registry::Registry;
1313
use crate::server::Server;
1414
use crate::subscriber::WebsocketSubscriber;
1515
use axum::http::Uri;
1616
use clap::Parser;
1717
use dotenvy::dotenv;
1818
use metrics_exporter_prometheus::PrometheusBuilder;
19+
use rate_limit::RedisRateLimit;
1920
use std::net::SocketAddr;
2021
use std::sync::Arc;
2122
use tokio::signal::unix::{signal, SignalKind};
@@ -96,6 +97,21 @@ struct Args {
9697
/// Maximum backoff allowed for upstream connections
9798
#[arg(long, env, default_value = "20")]
9899
subscriber_max_interval: u64,
100+
101+
#[arg(
102+
long,
103+
env,
104+
help = "Redis URL for distributed rate limiting (e.g., redis://localhost:6379). If not provided, in-memory rate limiting will be used."
105+
)]
106+
redis_url: Option<String>,
107+
108+
#[arg(
109+
long,
110+
env,
111+
default_value = "flashblocks",
112+
help = "Prefix for Redis keys"
113+
)]
114+
redis_key_prefix: String,
99115
}
100116

101117
#[tokio::main]
@@ -203,10 +219,40 @@ async fn main() {
203219

204220
let registry = Registry::new(sender, metrics.clone());
205221

206-
let rate_limiter = Arc::new(InMemoryRateLimit::new(
207-
args.global_connections_limit,
208-
args.per_ip_connections_limit,
209-
));
222+
let rate_limiter = match &args.redis_url {
223+
Some(redis_url) => {
224+
info!(message = "Using Redis rate limiter", redis_url = redis_url);
225+
match RedisRateLimit::new(
226+
redis_url,
227+
args.global_connections_limit,
228+
args.per_ip_connections_limit,
229+
&args.redis_key_prefix,
230+
) {
231+
Ok(limiter) => {
232+
info!(message = "Connected to Redis successfully");
233+
Arc::new(limiter) as Arc<dyn RateLimit>
234+
}
235+
Err(e) => {
236+
error!(
237+
message =
238+
"Failed to connect to Redis, falling back to in-memory rate limiting",
239+
error = e.to_string()
240+
);
241+
Arc::new(InMemoryRateLimit::new(
242+
args.global_connections_limit,
243+
args.per_ip_connections_limit,
244+
)) as Arc<dyn RateLimit>
245+
}
246+
}
247+
}
248+
None => {
249+
info!(message = "Using in-memory rate limiter");
250+
Arc::new(InMemoryRateLimit::new(
251+
args.global_connections_limit,
252+
args.per_ip_connections_limit,
253+
)) as Arc<dyn RateLimit>
254+
}
255+
};
210256

211257
let server = Server::new(
212258
args.listen_addr,

0 commit comments

Comments
 (0)