观察、决策和执行面板,旁边是 Redis 字符串标题。

Redis 字符串保存字节,不固定数据模式

Redis 字符串是在一个键下保存的一段二进制安全字节序列,可以装文本、序列化文档,也可以装十进制计数器。这里的“基础类型”描述的是对外的数据模型,不代表所有 Redis 数据结构都由字符串拼接实现。哈希、列表等类型有各自的命令、内部表示和更新规则,不能只凭字符串命令推断其他类型的行为。 先确定字节,再解释内容 SET 与 GET 能原样存取字节,包括零字节和不是有效 UTF-8 的内容。STRLEN 返回字节数,GETRANGE 的起止位置也是字节偏移,而且两端都包含在结果中,并不是按人眼看到的字符切片。因此,一个范围完全可能只取到某个多字节字符的一部分。客户端是否自动把返回值解码成文本,是另一层选择;任意二进制数据应关闭自动解码,只对应用明确知道编码的内容进行转换。 把 JSON 文本存进去,不会让这个键自动变成 Redis 哈希,也不会让哈希命令获得按字段访问 JSON 的能力。同样,整数命令是在执行时解释现有字节,并没有给这个键设置永久的数字类型约束。序列化方式、键名约定和读写双方对值类型的理解,都需要应用自己维护。文档给出的单个字符串默认上限是 512 MiB,但上限不是推荐负载大小,更不应该拿它作为每个缓存对象的设计目标。 原子自增有明确边界 INCR 把不存在的键当作零,再对十进制有符号六十四位整数执行加一。不能解释为整数的内容和越界运算都会报错,而不是回绕。INCRBY 用来改变步长;INCRBYFLOAT 遵循另一套浮点语义,如果需要精确的整数序列,就不能把它当作等价替代。 一条 INCR 消除了先 GET、再计算、最后 SET 之间的丢失更新窗口。不过,服务器已经完成自增而响应在途中丢失时,客户端重试仍可能再加一次。命令原子性不等于请求幂等,也不意味着经历删除、到期、复制故障或主从切换后还能提供持久且全局唯一的编号。下面的并发实验只验证一个正在运行的实例,不给这些跨故障场景背书。 坏掉的样子 对 JSON 字符串调用 HGET 会得到 WRONGTYPE。普通 SET 则有相反的风险:它能够把已有哈希整体替换成字符串,原来的字段随之丢失。因此需要按值的契约区分键名空间,并检查所有写入方实际使用的命令。某个键过去存过哈希,并不意味着这个名字以后会被 Redis 强制约束为哈希。 ...

一月 28, 2027 · 3 分钟 · XB Field Notes
Redis 批量发送请求以减少等待,同时保留每条命令各自的结果。

Redis 流水线减少逐条命令等待

Redis 流水线允许客户端连续发送多条命令,不必等前一条回应以后再发下一条。它减少了请求与响应之间的等待,也能分摊套接字输入输出开销。但服务端仍需要执行每条命令并生成回复;批处理不代表这些命令并行运行,也不自动形成事务边界。 少等几次,而不是少执行几条 同步循环不断重复发送一条、等待一条。流水线则先发送一个序列,再按对应顺序收取回复。把一批近似理解为一次往返,可以帮助估算延迟,但不能据此承诺只有一个 TCP 数据包、一次系统调用,或所有回应在同一时刻到达。实际传输仍受缓冲区、数据量和协议处理影响。 收益也不只出现在远程链路。把套接字读写集中起来,可以减少系统调用和调度开销,所以即使本地连接,也可能在不分片的情况下提高实际吞吐。反过来,流水线消除不了昂贵命令本身的工作和带宽上限。应先定位真实瓶颈,而不是看到单节点就断言下一步必须加分片。 普通流水线没有“不被插队”的保证。其它客户端可能在批次的命令之间执行操作,但具体某一次运行也可能碰巧全部连续完成。观察到一次连续执行,并不能把它上升为事务隔离承诺。正确性不能依赖一次测试里没有出现的调度交错。 区分顺序依赖与回复依赖 同一批命令不必完全无关。先 SET 计数器,再 INCR,最后 GET,可以放在一个流水线中,因为每条请求的参数在发送前都已确定;命令顺序本身就表达了服务端的数据依赖。真正无法放进同一个预先构造批次的,是必须根据前面回复计算键名或参数的下一条请求。 这种回复依赖应拆成不同阶段,或使用合适的原生命令、有界脚本。如果应用内的条件判断还需要并发保护,可以考虑 WATCH。MULTI/EXEC 提供执行隔离,却不会在运行期错误之后自动撤销其它已成功命令,这与减少往返是不同的问题。 还要检查客户端默认行为。redis-py 8.1.0 的 pipeline() 默认使用事务,显式传 transaction=False 才是普通流水线。因此拿默认包装器做测量,会同时测到批处理与 MULTI/EXEC。下面不仅写出选项,还读取服务端命令计数,核对实际有没有调用事务指令。 坏掉的样子 普通流水线中一条命令报错,不会取消已经提交的后续命令。错误回复仍占据结果序列中的对应位置,必须逐项检查;仅仅收到一个数组,不表示整个业务动作成功。忽略中间失败的写入,可能留下形状看似完整却不一致的应用数据。 redis-py 默认可能在读完回复后抛出 ResponseError,execute(raise_on_error=False) 则把命令错误放在结果元素里,两者都不会撤销成功写入。连接中断是另一类问题:缺少回应可能让已执行的前缀无法确定。即使从最后收到确认的命令之后继续,也可能重复一条已经执行、只是回应丢了的写入。 因此不能盲目重试递增或其它非幂等效果。即便反复写同一个固定值,如果中途另一个写入方已经更新数据,也需要并发约定来避免覆盖。可靠恢复是业务操作的职责,不是流水线传输优化顺带提供的能力。 批量应同时限制命令条数和字节数。客户端缓冲请求与结果,Redis 也可能暂存客户端还没读取的回应。大负载或慢读取方会增加内存与延迟成本;只按命令数量给出一个通用批量上限,忽略了不同命令和回复大小的差异。 快速验证 原有实验记录使用 macOS、Python 3.14.4、Redis 8.10.1 和 redis-py 8.1.0;本轮审阅没有重新执行服务实验,下面的结果说明保留原有观察范围。准备 POSIX 环境、C 编译器、make、curl、tar 与 Python 3.10 或更新版本。安装步骤仅用 libc 构建 Redis 核心,并把客户端装入临时虚拟环境。 ...

一月 20, 2027 · 3 分钟 · XB Field Notes
Redis Sentinel 观察者协调故障转移并提供主节点地址。

Redis Sentinel 协调主节点故障转移

Redis Sentinel 监督非 Cluster 的主从部署,并提供当前主节点地址。它不存储应用数据集,也不代理应用命令。主节点失败后自动提升副本,可以帮助恢复写入可用性,但服务发现、副本数据新鲜度、客户端重连以及阻止旧写者继续写入,仍然是不同的问题,不能由“有哨兵”三个字一并保证。 故障认定与选举授权是两道门 一个 Sentinel 在足够长时间收不到可接受回复后,会把实例标记为主观下线;达到配置的 quorum 都认为主节点不可达,才形成客观下线判断。这个故障判定阈值不等于故障转移领导者获得授权所需的多数。选举还必须达到配置的 quorum,因此将 quorum 设置得更高,也可能进一步提高授权门槛。 例如五个已知 Sentinel 配置 quorum 为二,两个观察者可以同意主节点发生故障,但授权提升仍需要三个构成多数。反过来,quorum 为一也不意味着三个已知观察者只剩一个时,它能独自授权。SENTINEL CKQUORUM 会检查这两项可达性条件;某个 Redis 进程看上去健康,并不能证明观察者满足条件。 稳健部署至少需要三个处在独立故障范围内的 Sentinel。同一台笔记本上的三个进程能测试协议行为,却会随整台机器一起失效。每个 Sentinel 还需要可写的配置文件,持久保存发现的拓扑与纪元状态;这些不是随时可以丢弃的普通静态配置,也应保护其中可能存放的认证材料。 提升副本不会阻止所有旧连接写入 Redis 使用异步复制,被选中的副本可能没有收到旧主已经确认的一条写入。网络分区时,留在旧主一侧的客户端甚至可能继续向它写入,而另一侧已经提升替代节点。Sentinel 协调选举,并不等于撤销所有旧连接的写权限,更不会把 Redis 自动变成强一致存储。 min-replicas-to-write、min-replicas-max-lag 等设置可以在复制健康度下降时限制写入,同时牺牲部分可用性;它们不是通用的隔离旧写者协议。仍需独立备份和明确的应用恢复规则。网络分区、复制落后和单纯杀死一个进程应分别验证,不能用其中一个结果替代其他情形。 客户端需要重新发现并建立连接 支持 Sentinel 的客户端先查询观察者拿到主节点地址,再直接连接 Redis。因此,它要处理暂时发现不到主节点、连接断开和角色改变。只认旧地址的客户端,不会因为某个哨兵已经更新答案就自动恢复;同样,写请求超时也不能证明操作从未执行。 下面的代码在发现新的主节点后,明确断开应用的旧连接池,再检查新连接。这样能够单独验证重连环节,而不是承诺故障转移对调用方完全不可见。切换窗口内不能盲目重复非幂等写操作,节点提升并没有替业务提供去重机制。 ...

一月 9, 2027 · 3 分钟 · XB Field Notes