文件最后提交记录最后更新时间
14 天前
14 天前
14 天前
14 天前
README

redis-demo —— ACE Redis 组件

演示 ace-redis 组件接入 ACE 框架:仅 import aceboot::redis + 配 [redis],即可

  • @Inject RedisClient 直接读写 Redis;
  • @Cacheable[ttlMs, "redis"] 把方法结果缓存落到 Redis(命名后端);
  • @Inject RedisLockManager + withLock 做分布式锁;
  • [redis].pubsub.bridgeChannels 把 Redis 频道消息桥接为应用事件,@EventListener 消费。

全程零运行时反射:RedisComponent@Component 在 import 时自注册,读 [redis] 装配。

运行

需本机 6379 有 Redis。配置按 cwd 相对解析(框架读 application.toml),故在 config 目录起服务:

source scripts/env.sh
cjpm build
cd examples/redis-demo/config && ../../../target/release/bin/redis_demo

另开终端(端口 18093,控制器前缀 /redis):

curl 'http://127.0.0.1:18093/redis/kv/foo'                 # <none>(未写)
curl -X POST 'http://127.0.0.1:18093/redis/kv/foo?v=bar'   # OK foo=bar
curl 'http://127.0.0.1:18093/redis/kv/foo'                 # bar
curl 'http://127.0.0.1:18093/redis/cache/widget'           # 首次算,5s 内命中(calls 不变)
curl -X POST 'http://127.0.0.1:18093/redis/lock/job1'      # withLock 互斥执行,runs 自增
# Pub/Sub 桥接:向 ace:demo:events 发消息 → 控制台打印 @EventListener 收到的 RedisMessage
redis-cli PUBLISH ace:demo:events hello

配置(config/application.toml)

[server]
host = "127.0.0.1"
port = 18093

[redis]
enabled = true
host = "127.0.0.1"
port = 6379
db = 0
timeoutMs = 2000
pingOnReady = true          # 启动期 PING 探活

  [redis.pool]
  max = 8
  min = 0
  idleTimeoutMs = 30000

  [redis.pubsub]
  bridgeChannels = "ace:demo:events"   # 逗号分隔;非空才建订阅连接

核心用法

import aceboot::redis.* // 触发 RedisComponent 自注册

// 1) @Inject 组件登记的 Bean
@Controller["/redis"]
public class DemoController {
    @Inject var redis: RedisClient          // 组件 provide("RedisClient")
    @Inject var locks: RedisLockManager      // 组件 provide("RedisLockManager")

    @Post["/lock/:job"]
    public func lock(@Param["job"] job: String): String {
        // withLock:拿到锁执行 body 并 finally 释放,返回 true;锁被占则 false
        let ok = locks.withLock("demo:lock:" + job, 3000, {=> redis.incr("demo:lock:runs"); ()})
        if (ok) { "ran" } else { "busy" }
    }
}

// 2) @Cacheable 双参:落 Redis(命名后端仅支持返回 String)
@Service
public class PriceService {
    @Cacheable[5000, "redis"]
    public func priceOf(sku: String): String { "price(${sku})" }
}

// 3) Pub/Sub 桥接:RedisComponent 按 bridgeChannels 订阅,收到消息 publishEvent(RedisMessage)
@Service
public class BridgeListener {
    @EventListener
    public func onRedisMessage(e: RedisMessage): Unit {
        println("channel=${e.channel} payload=${e.payload}")
    }
}

设计要点

关注点 实现
组件装配 @Component RedisComponent,import 即自注册;读 [redis]RedisClient/缓存后端/限流后端/锁管理器/桥接
缓存后端 @Cacheable[ttl, "redis"] 经 runtime CacheStore 落 Redis;仅支持返回 String
分布式锁 RedisLockManager:SET NX PX 抢锁 + Lua compare-and-del 释放,withLock 自动释放
Pub/Sub 桥接 独立订阅连接(读侧无超时,阻塞等 push);消息 → RedisMessage 应用事件 → @EventListener
停机顺序 onStop 先关订阅(停读协程)再关客户端池,顺序不可反

可执行模块运行期 dlopen OpenSSL(stdx.net):cjpm.tomllink-option = "-L${CANGJIE_STDX_PATH} -lcangjie-dynamicLoader-opensslFFI",macOS 的 DYLD_LIBRARY_PATHscripts/env.sh 设置。