读写分离遇到的问题与解决方案总结

1.ReadOnly标记问题:外部查询接口需标记只读,而内部事务内方法调用则不能标记,但都混杂在一起如何区分

解决方案:Dubbo请求入口处标记,如自定义Filter织入标记

2.主从延时导致过期数据加载到缓存问题

解决方案:缓存标记主库变更,主从延时期间(目前暂定一分钟,由于现在提前标记,可能需要延长)内从主库加载数据

3.主库发生变更到标记本次变更的间隙期间读请求从从库加载过期数据问题

解决方案:提前标记结合数据加载前后版本号检查,使用过期数据重建缓存的概率几乎为零,详见下文4和5的解决方案

4.缓存覆盖问题:查询变更标记到设置缓存间隔期间(例如从库慢查询延时较长)主库数据发生变更,从从库加载的过期数据覆盖缓存,类似的还有主库段时间内频繁变更多次,而多线程查询时,先查询到中间状态的数据后返回,最终覆盖缓存

解决方案:升级缓存的主库变更标记为版本号,加载数据前查询一次版本号,从数据库load完数据后再检查一次版本号(此次版本号为空例外,其标志版本号过期,数据库未发生变更),如果两者不一致,说明数据加载期间有过变更,则本次数据不更新到缓存

5.加载过期缓存且不会被删除问题:目前程序中有大量事务内删缓存操作,导致删除缓存操作在数据变更之前(因为此时事务尚未提交),事务提交前若有查询请求重建缓存,那么事务提交后主库数据发生变更,但是缓存将不会被删除,仍是过期数据

解决方案:AOP切入事务标签方法,使用本地线程收集事务内删除缓存的key,此处不做物理删除,但提前升级变更标记版本号(阻断主库变更和标记变更间隙查询,由于是提前标记,应该考虑延长标记过期时间,因为标记与数据库实际发生变更之间有可能存在较长延时),事务结束后再删除缓存

6.事务注解无章法配置可能导致方案5失效从而引发数据一致性问题:

项目中存在大量事务标记有@Transactional(rollbackFor = Exception.class )(checked异常回滚配置)也有@Transactional (默认checked异常不回滚)还有各种嵌套组合,有的还会将unchecked异常包装成checked异常(如QuarkException),这样会导致本该回滚的unchecked异常由于被包装且配置不当而没有回滚(该问题需要case by case梳理,工作量较大,在时间不允许的情况下后续版本优化),并且在方案5中会导致旧缓存由于抛异常而没有被删除,但是事务却提交了数据库发生变更,从而引发数据一致性问题

解决方案:AOP切入Spring源码事务管理器中,在事务提交(commit方法级别,若事务需要回滚,不会进入该方法)主库发生变更之后立即删除事务内收集到的缓存,这样只有真正数据库发生变更才会删除缓存,checked异常默认情况下由于不会回滚,也会提交事务,故而也会删除缓存

7.多层事务嵌套且内层是PROPAGATION_REQUIRES_NEW类型事务场景下,存在部分缓存需提前删除的情况,且不能删除外层事务收集的缓存问题:

解决方案:开辟独立的事务空间,且外层事务可自由获取内存事务的缓存(相当于包含关系),这样PROPAGATION_REQUIRES_NEW事务提交时,可仅仅删除其自身空间以及其可包含的空间内的缓存数据,对其外层空间不影响

 

posted @ 2020-08-17 18:58  倚天剑雨  阅读(1048)  评论(0编辑  收藏  举报