1.ReadOnly标记问题:外部查询接口需标记只读,而内部事务内方法调用则不能标记,但都混杂在一起如何区分
解决方案:Dubbo请求入口处标记,如自定义Filter织入标记
2.主从延时导致过期数据加载到缓存问题
解决方案:缓存标记主库变更,主从延时期间(目前暂定一分钟,由于现在提前标记,可能需要延长)内从主库加载数据
解决方案:提前标记结合数据加载前后版本号检查,使用过期数据重建缓存的概率几乎为零,详见下文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事务提交时,可仅仅删除其自身空间以及其可包含的空间内的缓存数据,对其外层空间不影响