vault backup: 2026-05-25 23:50:33

This commit is contained in:
hhs
2026-05-25 23:50:33 +08:00
parent 268ee58de6
commit 1e8dbf06b8
5 changed files with 304 additions and 86 deletions
+56 -9
View File
@@ -48,15 +48,23 @@ func IsSigned(ctx context.Context, rdb *redis.Client, userID int64, dayOfYear in
return val == 1, err
}
// 本月累计签到天数
// 本月累计签到天数(精确版:逐 bit 检查,避免字节边界误差)
func MonthSignCount(ctx context.Context, rdb *redis.Client, userID int64) (int64, error) {
key := fmt.Sprintf("sign:%d:%d", userID, time.Now().Year())
// BITCOUNT 支持按字节范围统计,start/end 是字节索引
// 需要根据月份计算对应的字节范围
return rdb.BitCount(ctx, key, &redis.BitCount{
Start: int64((time.Now().Month() - 1) * 4), // 近似值,实际需精确计算
End: int64(time.Now().Month()*4 - 1),
}).Result()
now := time.Now()
// 计算本月第一天和最后一天在年中的 day-of-year(1-based)
firstDay := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.Local).YearDay()
lastDay := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, time.Local).YearDay()
var count int64
for day := firstDay; day <= lastDay; day++ {
val, err := rdb.GetBit(ctx, key, int64(day-1)).Result() // YearDay 1-based -> offset 0-based
if err != nil {
return 0, err
}
count += val
}
return count, nil
}
```
@@ -147,7 +155,46 @@ Bitmap 的内存效率比 Hash 方案低约 **380 倍**,这就是为什么在
> [!question] 为什么 Bitmap 这么省?
> 因为它只用 1 个 bit 来表示一个布尔值,而 Hash/Set 需要存储完整的 key 和 value。Redis String 底层 SDS 本身也有元数据开销,但分摊到数十亿个 bit 上几乎可以忽略。
### 5. Bitmap vs Set vs HyperLogLog 对比
### 5. BITFIELD:任意宽度整数操作
前面的 `SETBIT`/`GETBIT` 只能操作单个 bit(0 或 1)。如果我们需要存储的不是布尔值,而是一个小整数呢?比如"连续签到天数"(0~127)或"用户等级"(0~15)。
这就是 `BITFIELD` 的用武之地——它将一个 String 视为一个**任意宽度整数的数组**,支持原子性的读写和自增。
| 子指令 | 作用 | 示例 |
|--------|------|------|
| `GET type offset` | 读取指定偏移处的整数 | `BITFIELD k GET u8 0` |
| `SET type offset value` | 写入整数 | `BITFIELD k SET u8 0 255` |
| `INCRBY type offset increment` | 原子自增 | `BITFIELD k INCRBY u8 0 1` |
其中 `type` 的格式为 `u`(无符号)或 `i`(有符号)+ 位宽(1~64),例如 `u8` 表示 8 位无符号整数(0~255),`i16` 表示 16 位有符号整数。
> [!tip] 为什么 INCRBY 很重要?
> 它是原子操作。多个客户端可以同时对同一个 offset 做 INCRBY 而不会出现竞态条件,无需额外加锁。
```go
// 用 BITFIELD 存储连续签到天数(u8 宽度,最多 255 天)
func IncrContinuousSign(ctx context.Context, rdb *redis.Client, userID int64) (int64, error) {
key := fmt.Sprintf("streak:%d", userID)
// BITFIELD key INCRBY u8 0 1
vals, err := rdb.BitField(ctx, key, "INCRBY", "u8", "0", "1").Result()
if err != nil {
return 0, err
}
return vals[0], nil // 返回自增后的连续签到天数
}
// 重置连续签到天数(签到中断时调用)
func ResetContinuousSign(ctx context.Context, rdb *redis.Client, userID int64) error {
key := fmt.Sprintf("streak:%d", userID)
return rdb.BitField(ctx, key, "SET", "u8", "0", "0").Err()
}
```
> [!question] BITFIELD vs 多个 key 怎么选?
> 如果每个用户只需要存 1~2 个小整数,用普通的 String/Hash key 就够了。`BITFIELD` 的优势在于:当需要**批量**存储大量同类型小整数时(比如 1 万个物品的库存量),可以把它们紧凑地打包到一个 key 中,大幅减少 key 数量和网络往返。
### 6. Bitmap vs Set vs HyperLogLog 对比
| 特性 | Bitmap | Set | HyperLogLog |
|------|--------|-----|-------------|
@@ -163,7 +210,7 @@ Bitmap 的内存效率比 Hash 方案低约 **380 倍**,这就是为什么在
> - 需要精确集合运算(交集、差集) -> **Set**
> - 只需要统计基数(UV/PV),允许 0.81% 误差 -> **HyperLogLog**
### 6. 签到系统流程
### 7. 签到系统流程
```mermaid
flowchart TD