Redis案例
一个基于 Cangjie的Redis 客户端 的令牌桶算法的实现。
项目配置
项目核心配置文件 package 内容如下:
[package]
cjc-version = "1.0.0"
name = "redis_demo"
description = "nothing here"
version = "1.0.0"
target-dir = ""
src-dir = ""
output-type = "executable"
compile-option = ""
override-compile-option = ""
link-option = ""
package-configuration = {}
[dependencies]
[target]
[target.x86_64-w64-mingw32]
[target.x86_64-w64-mingw32.bin-dependencies]
path-option = [
"../stdx/dynamic/stdx",
"./dependencies/hyperion",
"./dependencies/redis_sdk"
]
项目依赖
该项目需要以下依赖:
依赖的引入方式有两种:
1. 通过 dependencies 配置
可以通过 本地路径 或 远程 Git 仓库 的方式引入:
Git 仓库方式
[dependencies]
redis_sdk = { git = "https://gitcode.com/Cangjie-TPC/redis-sdk.git", branch = "master", version = "3.0.0" }
本地路径方式
[dependencies]
redis_sdk = { path = "path-to/redis-sdk" }
⚠️ 注意:如果直接通过 dependencies 引入 redis_sdk,需要确保 stdx 已经配置到环境变量。
SET CANGJIE_STDX_PATH=D:\cangjie-stdx-windows-x64-0.60.5.1\windows_x86_64_llvm\dynamic\stdx
SET PATH=%CANGJIE_STDX_PATH%;%PATH%
2. 通过编译好的二进制包
该项目已提供编译好的二进制包,因此不需要手动拉取和配置依赖,配置如下:
[dependencies]
[target]
[target.x86_64-w64-mingw32]
[target.x86_64-w64-mingw32.bin-dependencies]
path-option = [
"../stdx/dynamic/stdx",
"./dependencies/hyperion",
"./dependencies/redis_sdk"
]
这种方式可以直接使用内置的 ./dependencies 路径中编译完成的二进制包,无需额外操作。
Redis 部署说明
由于 Redis 官方暂未发布 Windows 版本,需要使用虚拟机或者wsl + Docker部署,这里采用 wsl + Docker 进行部署(linux和mac均可使用docker部署)。
下面是 docker-compose.yml 的配置示例:
services:
# Redis
redis:
image: redis:6.2
container_name: redis
restart: always
hostname: redis
privileged: true
ports:
- 16379:6379
volumes:
- ./redis/redis.conf:/usr/local/etc/redis/redis.conf
command: redis-server /usr/local/etc/redis/redis.conf
networks:
- my-network
healthcheck:
test: [ "CMD", "redis-cli", "ping" ]
interval: 10s
timeout: 5s
retries: 3
networks:
my-network:
driver: bridge
在项目根目录下执行以下命令自动拉取镜像并启动 Redis 服务(注意提前安装Docker Desktop):
docker compose up -d
⚠️ 注意:如果无法拉取镜像,需要配置国内镜像源。
启动后,Redis 服务将运行在 16379 端口。 可通过以下命令验证 Redis 是否正常运行:
docker exec -it redis redis-cli ping
# 返回 PONG 表示启动成功
开发说明
- 如果需要自行编译,推荐使用
dependencies方式引入依赖,便于更新和调试。 - 如果只是运行示例,可以直接使用项目自带的配置。
Redis客户端快速入门
以下是一个简单的程序示例,展示如何使用 Redis 客户端进行操作 Redis:
func getRedisClient(): RedisClient {
RedisClientBuilder.builder()
.host("127.0.0.1")
.port(16379)
.readTimeout(Duration.second * 60)
.writeTimeout(Duration.second * 30)
.receiveBufferSize(32768)
.sendBufferSize(32768)
.build()
}
main() {
// 获取Redis客户端
let client = getRedisClient()
// 执行 SET testKey1 testValue1
client.set("testKey1", "testValue1")
// 执行 GET testKey1
println(client.get("testKey1").getOrThrow())
}
背景
在服务端流量较高的场景下,限流是保护系统稳定性的重要手段。常见限流算法包括:
- 固定窗口计数器(Fixed Window Counter)
- 滑动窗口(Sliding Window)
- 漏桶算法(Leaky Bucket)
- 令牌桶算法(Token Bucket)
其中,令牌桶算法(Token Bucket Algorithm)在实际系统中应用最广。
它的基本思想是:
- 按固定速率向桶中加入令牌(token),桶有最大容量。
- 当请求到来时,尝试从桶中取出一定数量的令牌:
- 如果足够,则请求通过;
- 如果不足,则请求被拒绝或等待。
这样可以限制平均速率,同时允许一定程度的突发流量。
项目实现
我们实现了两种版本的令牌桶限流器:
1. LocalTokensLimiter(本地版)
-
实现原理
使用内存中的AtomicInt64保存令牌数量,使用CAS乐观锁保证变量的并发安全。
懒加载的方式更新令牌数量,不需要额外的线程定时补充,用户线程负责补充令牌。 所有操作都发生在单机内存中。 -
特点
- 优点:简单高效,无需外部依赖,适合单机应用。
- 缺点:多机部署时无法共享状态,容易出现限流不均衡。
-
适用场景
- 单机服务限流
2. RedisTokensLimiter(分布式版)
-
实现原理
将桶的状态(容量、剩余令牌数、上次更新时间等)存储在 Redis 中。 令牌更新同样使用懒加载的方式,和上述本地限流器基本一致。 Redis天生单线程且使用 Lua 脚本 来实现令牌补充与扣减的逻辑,保证操作的原子性以及并发安全。 -
特点
- 优点:
- 跨进程 / 跨节点共享限流状态
- 天生单线程 + Lua 脚本保证原子性,无竞态问题
- Redis 支持集群部署,具备高可用性和扩展性
- 性能高,QPS 可达数万级
- 缺点:
- 依赖 Redis 服务,部署和维护成本更高
- 优点:
-
适用场景
- 分布式系统的统一限流控制
- 高并发 API 网关
- 微服务架构下的跨节点限流
API 介绍
LocalTokensLimiter
let limiter = LocalTokensLimiter(10, 1000, 5)
// capacity = 10, 每 1000ms 补充 5 个 token
limiter.tryAcquire(3)
// 尝试获取 3 个令牌,返回 true/false
RedisTokensLimiter
let limiter = RedisTokensLimiter("127.0.0.1", 6379, "rate:limit:test", 10, 1000, 5)
// key = "rate:limit:test"
// capacity = 10, 每 1000ms 补充 5 个 token
limiter.tryAcquire(2)
// 尝试获取 2 个令牌,返回 true/false(由 Lua 脚本保证原子性)
为什么选择 Redis
-
速度快
- Redis 基于内存存储,单节点 QPS 可达十万级。
-
Lua 脚本原子性
EVAL脚本在 Redis 内部执行,保证读取-判断-更新的全流程原子性,避免并发问题。
-
高可用与扩展性
- 支持主从、哨兵、集群模式,可实现高可用和横向扩展。
-
通用性
- 作为中间件,多个服务都可以接入,统一限流策略。
对比总结
| 特性 | LocalTokensLimiter | RedisTokensLimiter |
|---|---|---|
| 部署复杂度 | 低(无外部依赖) | 高(需要 Redis) |
| 性能 | 极高(本地内存) | 高(Redis 内存) |
| 分布式支持 | ❌ | ✅ |
| 适用场景 | 单机限流 | 分布式限流、高并发场景 |
后续优化方向
- 支持 滑动窗口 或 漏桶算法,应对不同限流需求。
- 增加 超时等待(tryAcquire 带超时参数)。
- 提供 可视化监控(例如剩余令牌数的实时监控)。
- 支持 限流等级(区分普通用户和 VIP 用户)。