README.md

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

  1. 速度快

    • Redis 基于内存存储,单节点 QPS 可达十万级。
  2. Lua 脚本原子性

    • EVAL 脚本在 Redis 内部执行,保证读取-判断-更新的全流程原子性,避免并发问题。
  3. 高可用与扩展性

    • 支持主从、哨兵、集群模式,可实现高可用和横向扩展。
  4. 通用性

    • 作为中间件,多个服务都可以接入,统一限流策略。

对比总结

特性 LocalTokensLimiter RedisTokensLimiter
部署复杂度 低(无外部依赖) 高(需要 Redis)
性能 极高(本地内存) 高(Redis 内存)
分布式支持
适用场景 单机限流 分布式限流、高并发场景

后续优化方向

  • 支持 滑动窗口漏桶算法,应对不同限流需求。
  • 增加 超时等待(tryAcquire 带超时参数)。
  • 提供 可视化监控(例如剩余令牌数的实时监控)。
  • 支持 限流等级(区分普通用户和 VIP 用户)。