bigywater.tech
常见报错 · 52

Redis READONLY You can't write against a read only replica:故障切换后老连接不会断,降级前写进旧主的 100 个 key 同步后全丢,replica-read-only no 不是修法

“READONLY You can't write against a read only replica” 几乎都出现在故障切换之后。这篇用 Redis 8.10.2 一主一从在本机复现:哪些命令被拒、客户端的老连接在角色变化时会怎样、降级前写进旧主的数据去哪了、replica-read-only no 为什么不是修法,以及 Redis Cluster 里同名的 READONLY 命令。

100 → 0
降级之前写进旧主的 100 个 key,同步到新主之后旧主上剩下的个数
PONG
旧主被降级后,老连接没有被断开,PING 仍然正常,只是写得到 READONLY
MOVED 4998
集群里从库在没发 READONLY 时对读也返回的重定向

被拒与放行

实测 · 本机执行输出
  从库角色:slave,replica-read-only = yes
  > SET x 1                                                      (error) READONLY You can't write against a read only replica.
  > INCR cnt                                                     (error) READONLY You can't write against a read only replica.
  > DEL k                                                        (error) READONLY You can't write against a read only replica.
  > EXPIRE k 10                                                  (error) READONLY You can't write against a read only replica.
  > LPUSH l b                                                    (error) READONLY You can't write against a read only replica.
  > FLUSHALL                                                     (error) READONLY You can't write against a read only replica.
  > GET k                                                        "v"
  > LRANGE l 0 -1                                                1) "a"
  > EVAL return redis.call('SET','a','1') 0                      (error) READONLY You can't write against a read only replica. script: 88c923858e73954cede0
  > EVAL return redis.call('GET','k') 0                          "v"
  > EVAL_RO return redis.call('GET','k') 1 k                     "v"
  > PUBLISH ch hi                                                (integer) 0
  > CONFIG SET maxmemory 100mb                                   "OK"
  > BGSAVE                                                       "Background saving started"
  作为对照,同样的写命令发给主库 > SET x 1 -> "OK"

写命令被拒;PUBLISH、CONFIG SET、BGSAVE 在从库上并不被拒。

故障切换后

实测 · 本机执行输出
  > REPLICAOF NO ONE
      "OK"
  提升后新主角色:master;旧主这时仍然是:master
  降级之前:客户端仍在往旧主写,100 次写入成功 100 次(这就是脑裂窗口:新主上没有这些 key)
  新主上有多少个 split* key:0
  > REPLICAOF 127.0.0.1 8171
      "OK"
  旧主降级后角色:slave,master_link_status = up
  > SET after 1
      (error) READONLY You can't write against a read only replica.
  > INCR counter
      (error) READONLY You can't write against a read only replica.
  同一条连接是否还活着:PING -> "PONG"(没有被断开,只是写被拒)
  旧主上降级前写入的 100 个 split* key 现在还剩:0 个(同步后以新主的数据为准,它们丢了)
  新主从旧主那里收到的 key:before 存在 = 1

老连接不会断,写得到 READONLY;降级前的脑裂窗口写入会丢。

replica-read-only no

实测 · 本机执行输出
  > CONFIG SET replica-read-only no (在旧主/现从库 8170 上) -> "OK"
  > SET local_only written-on-replica
      "OK"
  现在 8170 上 GET local_only = "written-on-replica";新主 8171 上 = (nil)
  > REPLICAOF NO ONE
      "OK"
  > REPLICAOF 127.0.0.1 8171
      "OK"
  重新全量同步后 8170 上 GET local_only = (nil)

从库本地写不传给任何人,下次全量同步就没了。

集群 READONLY

实测 · 本机执行输出
  从库 127.0.0.1:8193(主库 8190)
  > GET key2
      (error) MOVED 4998 127.0.0.1:8190
  > READONLY
      "OK"
  > GET key2
      "v"
  > SET key2 w
      (error) MOVED 4998 127.0.0.1:8190
  > READWRITE
      "OK"
  > GET key2
      (error) MOVED 4998 127.0.0.1:8190

从库默认 MOVED,发 READONLY 后这条连接才能读;写仍要去主库。

分步演示

READONLY
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / MySQL 26.10.0 与 Redis 8.10.2(Homebrew,每个实验自己起实例,端口 9150 以上,只监听 127.0.0.1,不碰本机已有的实例)/ Python 3.15.0 + PyMySQL(按各篇注明用到哪些及版本);报错文本都是真实复现时原样捕获的;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇只用 Redis,Redis 8.10.2,一主一从端口 8170、8171,集群 8190–8195(避开 8181);实验结束后全部停止并删除数据目录;没有使用 Sentinel
    • 没有验证:Sentinel / 云托管 Redis 的切换流程与客户端自动重连(redis-py、Jedis、Lettuce 对 READONLY 的处理);min-replicas-to-write(NOREPLICAS)的情形;持久化数据在降级前后的细节(只测了内存里 key 的去向);跨机房网络延迟下脑裂窗口的长度;READONLY 与读写分离框架配合时读到旧数据的程度
    京ICP备2026067502号-1京公网安备11010502063487号