从库默认是只读的。这条限制看起来只是「防止误写」,但关掉它之后会发生什么,很少有人真的试过。这篇把它试了一遍:哪些命令被拦、放开之后数据怎么分叉、重同步时本地写入会不会被保留。
> CONFIG GET replica-read-only
1) "replica-read-only"
2) "yes"
> GET a
"1"
> SET a 2
(error) READONLY You can't write against a read only replica.
> DEL a
(error) READONLY You can't write against a read only replica.
> EXPIRE a 10
(error) READONLY You can't write against a read only replica.
> INCR n
(error) READONLY You can't write against a read only replica.
> FLUSHALL
(error) READONLY You can't write against a read only replica.
> EVAL "return redis.call('SET','z',1)" 0
(error) READONLY You can't write against a read only replica. script: 6e61eb62b39720e12a8a2743b63a1fc84f2149fd, on @user_script:1.
> PUBLISH ch x
(integer) 0
> EVAL_RO "return redis.call('GET','a')" 0
"1"replica-read-only 默认是 yes。SET / DEL / EXPIRE / INCR / FLUSHALL 全部报 READONLY You can't write against a read only replica;EVAL 脚本里调用写命令同样被拒;PUBLISH 和只读的 EVAL_RO 可以执行(PUBLISH 返回 0 是因为这个从库上没有订阅者)。
> CONFIG SET replica-read-only no "OK" > SET local only-on-replica "OK" > SET a changed-on-replica "OK" 主库 GET a = 1 主库 GET local = None 从库 GET a = changed-on-replica 从库 GET local = only-on-replica
把 replica-read-only 改成 no 之后,从库上的写入成功,但主库看不到——主库的 a 还是 1,也没有 local。这不是「双向同步」,只是两边各有各的数据。
从库 GET a = from-master 从库 GET local = only-on-replica dbsize 主/从: 1 2
主库之后再写同一个 key a,从库上本地的修改被覆盖;而 local 只在从库上,主库从来没有写过,复制流不会去碰它,所以一直留着,dbsize 变成 主 1 / 从 2。
600 ms 后从库 GET ttl_local = None
在可写从库上写带过期时间的 key:600 ms 后 GET 返回 nil。本地写入的 key,是从库自己按 TTL 过期的(我没有在这里对比只读从库上主库同步过来的 key 的过期行为,那部分见第 28 篇)。
主库 sync_full +1, partial_ok +0 从库 GET local = None dbsize 主/从: 1001 1001
用一个可断开的 TCP 转发制造断线。断线期间主库写了约 100 KB、超过 backlog(16 KB),恢复后全量同步,从库的 local 被清掉,dbsize 主从一致(1001)。
主库 sync_full +0, partial_ok +1 从库 GET local2 = v 主库 GET local2 = None
如果断线期间只写了一点,恢复后是部分重同步,从库的 local2 还在,主库却没有。所以:本地写入是否被保留,取决于这次重同步是全量还是部分——不可预测。这也是默认只读、不建议放开的原因。