直接硬核干货,去掉前戏。
方案大致说明
1:假设对redis中存在一对key,value的对应关系是 key=money,value=666
2:当修改线程修改key时先将key设置成value=666_write,(这里需要说明的是:线上实际应用可以将_wirte改成非常复杂的UUID等字符串,只要保证不要和线上实际set的值冲突即可,例如666_write_@#$%^&*_qwerty
3:当读取时发现key的存在读取标识666_read_@#$%^&*_qwerty,发现有其他读取线程已经在从DB中load数据了,这种情况休眠一下重试就可以了,不需要从DB中load数据,因为这样做会引起大量线程访问数据库引发灾难。如果不存在读取标识则判断一下是否含有写入标识若不存在则直接返回数据即可,若存在读取标识将所有读取标识去掉之后看看是否为空,不为空则返回当前value即可。
4:为什么一个字符串会出现多个写入标识呢。假设线程A和B对 key=money,value=666的数据进行修改,那么线程A将value变成了value=666_write_@#$%^&*_qwerty,这个时候线程B也来掺和一下,会变成666_write_@#$%^&*_qwerty_write_@#$%^&*_qwerty,这个地方是需要注意的。同时也会出现业务数据被删除了这个时候加上了修改标记,value变成_write_@#$%^&*_qwerty这种只有修改标识但是没有业务数据的的情况。
5:还有一个需要特别注意的是setnx在客户端2.8之后 支持超时时间,这块需要设置一个超时的时间处理,防止读取线程setnx之后服务器挂了,引起永远无法同步的问题,至于超时时间设置成多少和实际环境有关。
6:同时也会出现_read_@#$%^&*_qwerty_write_@#$%^&*_qwerty这种同时带有read和write的情况,出现在并发读取的场景。
7:这种需要DB缓存强一致读写应该在master中进行,不应该在slave中读,很有可能出现延迟导致问题
8:redis主从切换这种极端的场景出现在读取或者修改的情况下也会引起数据不一致的问题,一般上线这种问题的解决方案是跨机房多机缓存或者允许短期内不一致的job任务定时校验兜底等。
总结起来就是一下几点:
当存在_read_@#$%^&*_qwerty时说明有读取线程正请求DB,从新load对象。
当存在_write_@#$%^&*_qwerty时说明当前key正在被写入。
当存在多个_write_@#$%^&*_qwerty时 存在并发修改。
当有_read_@#$%^&*_qwerty和_write_@#$%^&*_qwerty时 存在读和写的并发。
读取详细设计方案
更新时设计方案: