Redis:真实单机实验与恢复模型
教材源码固定7.2.5;2026-10-23实际运行服务是用户本机127.0.0.1:6379的8.6.4。服务为standalone/master、0副本、AOF关闭。执行与编码教材和恢复教材分别解释机制。
完整可下载文件
| 文件 | 用途 |
|---|---|
| run_local.py | 标准库socket/RESP2真实服务入口,不依赖redis-cli或第三方包 |
| models.py | 缓存水位、第二replid/backlog与Sentinel授权纸面模型;放在上一级 |
| compose.yaml | 可选7.2.5隔离单主副本/AOF环境,本轮未运行 |
从仓库根执行下面命令;下载使用时保留labs/data-senior/redis/和labs/data-senior/models.py相对结构。
真实服务:输入、预期与边界
python3 labs/data-senior/redis/run_local.py --mode inspect
python3 labs/data-senior/redis/run_local.py --mode encoding
python3 labs/data-senior/redis/run_local.py --mode atomic
python3 labs/data-senior/redis/run_local.py --mode ttl
python3 labs/data-senior/redis/run_local.py --mode wait
# 一次连接跑全部场景
python3 labs/data-senior/redis/run_local.py --mode all默认host127.0.0.1/port6379,可用--host/--port指定自己获授权的服务;当前入口面向无密码standalone master。inspect只读INFO和指定CONFIG GET,不建键;其余mode先确认角色/模式和内存余量。maxmemory非0且余量不足16MiB则非零退出,不进行写入。编码阈值超本实验上界(Hash/整数Set512、Set/ZSet listpack128、value64)就返回skipped,不按大配置生成大量数据。
每次生成study_t17_<UUID>:命名空间,以SET NX EX120预留并记录实际创建成功的键,碰撞则失败。只对这些键操作,finally按列表DEL并逐键EXISTS确认,不按前缀扫描、不FLUSH、不更改配置、不启用AOF、不重启、不对用户既有键制造错误。少量有界样本仍有正常命令/脚本CPU成本;内存预检是本次采样,不是对其他并发负载的永久保证。实验脚本中断/连接失败会打印错误或清理错误,不能据正常路径的finally声称任何kill都保证零遗留;UUID前缀和源码中的本次键名可用于定点检查,勿全库清理。
| mode | 输入与独立预期 | 2026-10-23实际8.6.4结果 |
|---|---|---|
| encoding | Hash512短字段后加第513、单65B值;513对同field;Set{1,2}+a+65B;513整数;ZSet128→129 | Hash listpack→hashtable、长值HT,重复参数HLEN1/HT;Set intset→listpack→hashtable、513整数HT;ZSet listpack→skiplist |
| atomic | Lua先SET before再对非整数INCR;MULTI中SET、错误LPUSH、SET另一键 | Lua报错仍为before;EXEC返回OK/WRONGTYPE/OK,两值before/after都存在 |
| ttl | PX200后有界轮询GET、PTTL;SET KEEPTTL对照普通SET | 初始PTTL200ms,最终GET空/PTTL=-2;KEEPTTL约60000ms,普通SET=-1 |
| wait | 同一个socket先SET written、再WAIT1/100ms、再GET | 返回0但仍读written;只证明无副本不足分支,未证明成功ACK或落盘 |
all本次初始used_memory969104B/maxmemory134217728B、allkeys-lru;Hash512样本MEMORY USAGE4585B只作本次观察。12个专用键中TTL键已过期,finally删除仍存在的11个,剩余0。实际结果不是7.2.5运行结果,不产生时延/P99或可用性承诺。程序遇到命令错误、断言失败或清理失败非零退出;CONFIG GET未返回8.6.4的io-threads-do-reads,不能把旧版本配置默认值当作当前配置。
确定性模型:不能冒充真实集群
python3 labs/data-senior/models.py --json cache
python3 labs/data-senior/models.py --json recovery
python3 labs/data-senior/models.pycache手算:R读v1暂停600秒,W提交v2并删缓存,R回填后另有1秒TTL,旧值末端是提交后601000ms。F2拒绝fill1;fill2读2;F2存在而C1被放入时read空;事件未到前仍读1;F2丢失又可填1;F3删除水位拒绝v2。完整协议、读检查与寿命条件只维护在缓存与数据库一致性。旧共享模型中的投影拒旧反例仍保留。
recovery手算:当前ID,backlog offset100/histlen101,下一请求201可部分续传、99/202不行;第二ID截止150,150可而151不行;无backlog或错ID不行。5 Sentinel/quorum2,2票不授权、3票可;quorum4时3票不够、4票可。模型只有这些显式状态判断,不实现PSYNC流、Sentinel选举、网络、磁盘、时钟或数据副本提升。
可选7.2.5隔离环境:本轮未运行
只在自己新建的专用Compose项目中做以下演练,不把这些命令指向本机现有6379服务。先确认16379端口空闲;项目名使用本轮新名字,避免操作别人已存在的study-redis项目。
redis_lab_project="study-redis-$(date +%s)-${RANDOM}"
docker compose -p "$redis_lab_project" -f labs/data-senior/redis/compose.yaml up -d
docker compose -p "$redis_lab_project" -f labs/data-senior/redis/compose.yaml exec primary redis-cli在同一redis-cli会话对本实验新建键执行SET、WAIT1/1000与INFO replication,预期有副本时可返回1。仅停止上述项目的replica,再在原会话SET、WAIT1/100,预期不足但主GET仍存在;重新接入后用同步计数和日志判断partial/full,不以“数据回来”替代路径证据。
持久化演练仅在上述专用primary写本次键、BGREWRITEAOF并等待aof_rewrite_in_progress=0,检查aof_last_bgrewrite_status与完整manifest/base/增量,再只重启本项目primary,核对恢复值。不要直接删除看似旧的AOF文件。结束用同一project名执行compose down清理专用容器。普通重启不等同掉电,单机多个容器不证明机房隔离。
本轮未启动该Compose环境,也未运行AOF恢复、成功复制/WAITAOF确认、Sentinel、Cluster、分区、掉电、磁盘满或压力测试。它们有源码、协议与纸面输入,不能写成已实测。
业务缓存协议:正常交错与半执行错误
新增完整下载 cache_consistency.py,与run_local.py放同一目录;仅Python标准库。在仓库根执行:
python3 labs/data-senior/redis/cache_consistency.py --host 127.0.0.1 --port 6379版本域固定为Lua双精度能精确表达的1/2/3,不支持任意BIGINT版本直接tonumber。正常原子失效穿插两次GET时C为空;旧fill被拒,新fill成功,同版同内容DUPLICATE、异参CONFLICT,删除水位拒绝旧create。额外错误先SET F2再INCR非整数aux,留下F2/C1,分步旧F读放过1,而原子守卫返回null。最后显式丢失F/C,旧填1再次成功,说明恢复前提。
2026-10-23在本机8.6.4 standalone/master运行上述断言通过,三键清理剩余0。使用study_t20_UUID专用三键,角色/模式与16MiB内存余量预检沿用run_local,finally精确清理,不扫描、不更改配置。与上文T17键不同,仅本次新建键被操作;异常/强杀不保证清理成功。本实验不验证数据库/CDC/副本切换,完整协议仅维护于缓存教材。
共享模型的cache模式另枚举正常GET F、GET C、invalidate2、fill1、fill2的60个合法交错;正常违规0,半执行额外故障与同版异参分别输出,不能把人工坏状态当成正常协议反例。