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.toml 配 link-option = "-L${CANGJIE_STDX_PATH} -lcangjie-dynamicLoader-opensslFFI",macOS 的 DYLD_LIBRARY_PATH 由 scripts/env.sh 设置。