vault backup: 2026-05-21 23:50:11
This commit is contained in:
+117
-3
@@ -198,14 +198,16 @@ students := rdb.ZRangeByScore(ctx, "score:classA", redis.ZRangeBy{
|
||||
| `FLUSHDB` | OK | O(N) | 清空当前库 |
|
||||
| `FLUSHALL` | OK | O(N) | 清空所有库 |
|
||||
| `TYPE key` | string/hash/list... | O(1) | 查看数据类型 |
|
||||
| `EXISTS key [key ...]` | existing count | O(N) | 判断 key 是否存在,支持批量 |
|
||||
| `OBJECT ENCODING key` | encoding name | O(1) | 查看底层编码(排查性能利器) |
|
||||
| `UNLINK key [key ...]` | deleted count | O(N) | 异步删除,不阻塞主线程 ✅ |
|
||||
| `MOVE key db` | 1 or 0 | O(1) | 移动到其他数据库(集群不支持) |
|
||||
| `RENAME key newkey` | OK | O(1) | 重命名(不管类型) |
|
||||
| `RANDOMKEY` | key / nil | O(1) | 随机返回一个 key(集群不支持) |
|
||||
|
||||
```go
|
||||
// 优雅删除大 key——避免 DEL 阻塞主线程
|
||||
keys := []string{"large:key1", "large:key2"}
|
||||
rdb.Del(ctx, keys...).Err() // 小批量 DEL
|
||||
// 优雅删除大 key——首选 UNLINK(异步删除,不阻塞主线程)
|
||||
rdb.Unlink(ctx, "large:key1", "large:key2")
|
||||
|
||||
// 或者用 SSCAN/HSCAN 分批删除字段
|
||||
iter := rdb.HScan(ctx, "big:hash", 0, "", 100).Iterator()
|
||||
@@ -214,6 +216,9 @@ for iter.Next(ctx) {
|
||||
}
|
||||
```
|
||||
|
||||
> [!TIP] DEL vs UNLINK
|
||||
> `DEL` 是同步删除,大 key(如百万元素的 Set)会阻塞主线程。`UNLINK`(4.0+)在后台线程异步释放内存,**生产环境优先用 UNLINK**。
|
||||
|
||||
> [!WARNING] TTL 的陷阱
|
||||
> - `TTL` 返回 `-1` 表示 key 存在但无过期时间,`-2` 表示 key 不存在
|
||||
> - Redis 的过期键删除是**惰性删除 + 定期删除**混合策略,不能保证设置 EXPIRE 后立即清理
|
||||
@@ -297,6 +302,115 @@ EXEC # 原子提交
|
||||
> [!WARNING] Redis 事务不是"数据库事务"
|
||||
> Redis 事务只保证**不中断**(没有回滚),中间执行的命令如果出错,后续命令仍会继续。如果需要"出错就回滚"的行为,请使用 Lua 脚本。
|
||||
|
||||
## Pub/Sub — 发布订阅
|
||||
|
||||
Pub/Sub 是 Redis 内置的消息广播模型:发布者向 channel 发消息,所有订阅者**实时**收到。注意它是 **fire-and-forget**——不持久化、不确认、离线消息会丢失。
|
||||
|
||||
| 命令 | 返回值 | 复杂度 | 说明 |
|
||||
|------|--------|--------|------|
|
||||
| `SUBSCRIBE channel [channel ...]` | messages | O(N) | 订阅一个或多个 channel |
|
||||
| `UNSUBSCRIBE channel [channel ...]` | OK | O(N) | 取消订阅 |
|
||||
| `PUBLISH channel message` | receivers count | O(N+M) | 发布消息,N=channel 订阅者数,M=pattern 匹配数 |
|
||||
| `PSUBSCRIBE pattern [pattern ...]` | messages | O(N) | 按 glob 模式订阅(如 `news.*`) |
|
||||
| `PUNSUBSCRIBE pattern [pattern ...]` | OK | O(N) | 取消模式订阅 |
|
||||
| `PUBSUB CHANNELS [pattern]` | channels | O(N) | 列出活跃 channel |
|
||||
| `PUBSUB NUMSUB [channel ...]` | channel-count pairs | O(N) | 查询各 channel 订阅者数量 |
|
||||
|
||||
```go
|
||||
// 订阅端——监听消息
|
||||
sub := rdb.Subscribe(ctx, "order:events")
|
||||
ch := sub.Channel()
|
||||
for msg := range ch {
|
||||
fmt.Printf("channel=%s payload=%s\n", msg.Channel, msg.Payload)
|
||||
}
|
||||
|
||||
// 发布端——广播事件
|
||||
receivers, _ := rdb.Publish(ctx, "order:events", `{"orderId":"1001","status":"paid"}`).Result()
|
||||
fmt.Printf("delivered to %d subscribers\n", receivers)
|
||||
```
|
||||
|
||||
> [!QUESTION] Pub/Sub vs Stream,怎么选?
|
||||
> - **Pub/Sub**:实时广播,不关心历史,用完即丢(适合通知、缓存失效广播)
|
||||
> - **Stream**:持久化消息队列,支持消费组、ACK、回溯(适合业务事件、任务队列)
|
||||
> - 简单规则:**需要"至少一次"保证就用 Stream,否则 Pub/Sub 足够**
|
||||
|
||||
## Geo — 地理位置
|
||||
|
||||
Redis 3.2+ 引入的地理位置类型,底层用 **ZSet** 实现(GeoHash 编码为 score),支持范围查询和距离计算。
|
||||
|
||||
| 命令 | 返回值 | 复杂度 | 说明 |
|
||||
|------|--------|--------|------|
|
||||
| `GEOADD key longitude latitude member [lon lat member ...]` | added count | O(log N) | 添加地理位置 |
|
||||
| `GEOPOS key member [member ...]` | coordinates | O(N) | 获取坐标 |
|
||||
| `GEODIST key member1 member2 [m\|km\|ft\|mi]` | distance | O(1) | 两点间距离 |
|
||||
| `GEORADIUS key lon lat radius m\|km\|ft\|mi [WITHCOORD] [WITHDIST] [ASC\|DESC] [COUNT n]` | members | O(N+log N) | 以坐标为中心搜索 ⚠️ 6.2+ 已废弃,用 GEOSEARCH |
|
||||
| `GEORADIUSBYMEMBER key member radius m\|km\|ft\|mi` | members | O(N+log N) | 以成员为中心搜索 ⚠️ 6.2+ 已废弃 |
|
||||
| `GEOSEARCH key FROMLONLAT lon lat BYRADIUS radius m\|km [ASC\|DESC] [COUNT n] [WITHCOORD] [WITHDIST]` | members | O(N+log N) | ✅ 6.2+ 推荐,统一搜索命令 |
|
||||
| `GEOSEARCH key FROMMEMBER member BYRADIUS radius m\|km` | members | O(N+log N) | 以成员为中心搜索 |
|
||||
| `GEOSEARCHSTORE dst src ...` | stored count | O(N+log N) | 搜索结果存入新 key(6.2+) |
|
||||
| `GEOHASH key member [member ...]` | geohash strings | O(N) | 返回 GeoHash 编码字符串 |
|
||||
|
||||
```go
|
||||
// 门店入库——添加地理位置
|
||||
rdb.GeoAdd(ctx, "stores:shanghai",
|
||||
&redis.GeoLocation{Name: "store-001", Longitude: 121.4737, Latitude: 31.2304},
|
||||
&redis.GeoLocation{Name: "store-002", Longitude: 121.4897, Latitude: 31.2397},
|
||||
)
|
||||
|
||||
// 附近 3km 的门店——GEOSEARCH
|
||||
stores, _ := rdb.GeoSearchLocation(ctx, "stores:shanghai", &redis.GeoSearchLocationQuery{
|
||||
GeoSearchQuery: redis.GeoSearchQuery{
|
||||
Longitude: 121.4800,
|
||||
Latitude: 31.2350,
|
||||
Radius: 3,
|
||||
RadiusUnit: "km",
|
||||
Sort: "ASC",
|
||||
Count: 10,
|
||||
},
|
||||
WithCoord: true,
|
||||
WithDist: true,
|
||||
}).Result()
|
||||
```
|
||||
|
||||
> [!TIP] GEO 底层就是 ZSet
|
||||
> 可以用 `ZRANGE`、`ZREM` 等 ZSet 命令操作 Geo key。`GEODIST` 计算的是球面距离(Haversine 公式),精度在 ~0.5% 以内。
|
||||
|
||||
## Lua 脚本速查
|
||||
|
||||
Redis 2.6+ 内置 Lua 解释器,脚本在服务端**原子执行**,是实现 CAS、限流器、排行榜原子操作的利器。
|
||||
|
||||
| 命令 | 返回值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `EVAL "script" numkeys key [key ...] arg [arg ...]` | script result | 执行 Lua 脚本 |
|
||||
| `EVALSHA sha1 numkeys key [key ...] arg [arg ...]` | script result | 按 SHA1 执行(脚本已缓存) |
|
||||
| `SCRIPT LOAD "script"` | SHA1 | 缓存脚本,返回 SHA1 |
|
||||
| `SCRIPT EXISTS sha1 [sha1 ...]` | 1 or 0 array | 检查脚本是否已缓存 |
|
||||
| `SCRIPT FLUSH` | OK | 清空脚本缓存 |
|
||||
| `SCRIPT KILL` | OK | 终止正在执行的脚本(只限无写操作时) |
|
||||
|
||||
```go
|
||||
// 原子扣减库存——Lua 实现 check-and-set
|
||||
script := redis.NewScript(`
|
||||
local stock = tonumber(redis.call('GET', KEYS[1]))
|
||||
if stock and stock >= tonumber(ARGV[1]) then
|
||||
redis.call('DECRBY', KEYS[1], ARGV[1])
|
||||
return 1
|
||||
end
|
||||
return 0
|
||||
`)
|
||||
result, _ := script.Run(ctx, rdb, []string{"stock:sku:1001"}, 1).Int()
|
||||
// result == 1 表示扣减成功,0 表示库存不足
|
||||
```
|
||||
|
||||
> [!WARNING] Lua 脚本的限制
|
||||
> - 脚本执行期间**阻塞整个 Redis**,严禁写耗时逻辑(循环上限建议 < 5000 次迭代)
|
||||
> - 脚本中不能访问外部网络或文件系统
|
||||
> - 集群模式下所有 KEYS 必须在同一个 slot(用 `{hash_tag}` 保证)
|
||||
> - `SCRIPT KILL` 只能终止**未执行写操作**的脚本;已写数据的脚本只能等它跑完或重启 Redis
|
||||
|
||||
> [!NOTE] EVAL vs EVALSHA
|
||||
> `EVAL` 每次发送完整脚本源码,`EVALSHA` 只发送 40 字节的 SHA1 摘要。高并发场景**务必用 EVALSHA + SCRIPT LOAD**,减少网络开销。go-redis 的 `NewScript` 会自动处理 EVALSHA 失败后回退 EVAL。
|
||||
|
||||
## 关联笔记
|
||||
|
||||
- [[hhs/Redis/02-核心数据类型]] — 每种类型的底层编码原理
|
||||
|
||||
Reference in New Issue
Block a user