最近莫名的会有错误日志,说有写操作因为走了读库而报了read only的异常,由于并没有造成应用使用的问题,开始我以为哪的配置错误就没当回事让程序员自己去查了,然而。。。

  背景:之前的博客里提到过,读写分离功能是直接从老系统迁移过来的,老系统可能是因为主从库之间同步延迟而使用了基于请求层面的读写分离,迁过来的时候降成了基于Service,新系统用了dubbox,这些Service直接充当Provider。读写分离的实现是重写了了DataSource,请求到Service的时候会先经过拦截器判断方法是走读库还是写库,默认是写库,如果是要走读库的会用ThreadLocal标记为读,否则标记为写。由于读压力远高于写,所以并不是所有读都走写库,避免两个数据库压力不均,通过人为指定来平衡两个库的压力,也就是说是以标记为准而不是以请求类型为准。

  从第一截图可以看到,当遇到没有被标记的事务获取数据源的时候,默认会设置为写。原本这其实是没有什么问题的,因为需要走到写库的都会先被拦截器拦截标记为写:

  而这个determineCurrentLookupKey就是我们重写的方法,也就是上面的第一个截图,所以貌似看上去没什么问题。因为一个事务被设置一个数据源,即使请求因为线程池被复用的关系,上一次请求的ThreadLocal没有专门清理,在新的请求进来的时候这个值也会被拦截器更新。而且,嵌套事务中每个子事务都会保存一个自己的状态,子事务执行完回将状态重置回它开始时候得状态(这个状态也是通过ThreadLocal保存的):

  数据源与连接的绑定关系也是通过ThreadLocal保存的,因为一个事务也只能使用一个连接,所以将唯一的数据源(即使可能不确定)与连接绑定也没什么关系,反正事务结束后会清理掉。

  本来如果维持这个事务体系的闭环就好了,然而依然是因为determineCurrentLookupKey被重写的原因,begin时候数据源的状态对Spring的事务体系就不可见了,是运行时动态选择的,而这个状态的选择在一般情况下依赖于拦截器。为什么说是一般情况下呢,因为有特殊情况会出现,比如第一个图中的=null,只要拦截器先执行了就不会出现=null的情况,问题就出现在先执行上。如果TransactionInterceptor先执行,这里就没有依赖于拦截器,刚才一直都在重点说ThreadLocal,如果这个线程刚巧在上一个事务中把第二个图中的holder设为了读,一连串的问题就来了,首先外层事务的状态就不对了,又因为各个嵌套的子事务的的状态都是隔离的,即使后续子事务的启动是在外层方法被拦截之后得到了正确的连接,但是由于一个事务只有有一个连接,ThreadLocal中已经保存的有数据源和连接了,所以每个事务的状态可能是对的,但提交时用的也就是上图中的连接时错的,本来应该是指向写库的就变成读库了,提交就是用的读库的连接进行的。

  找到问题,解决就容易了,只要设置一下拦截器的执行顺序就好了;也可以通过在提交事务后清理我们自己的ThreadLocal来解决,只需要继承DataSourceTransactionManager重写其中比如清理方法就可以了。

==========================================================

咱最近用的github:https://github.com/saaavsaaa

微信公众号:

                      

使用ThreadLocal实现的读写分离在迁移后的偶发错误的更多相关文章

  1. Hishop网站迁移后出现DataProtectionConfigurationProvider错误

    错误代码如下: 配置错误 说明: 在处理向该请求提供服务所需的配置文件时出错.请检查下面的特定错误详细信息并适当地修改配置文件. 分析器错误信息: 未能使用提供程序“DataProtectionCon ...

  2. hishop网站迁移后出现DataProtectionConfigurationProvider错误(转)

    配置错误说明: 在处理向该请求提供服务所需的配置文件时出错.请检查下面的特定错误详细信息并适当地修改配置文件.分析器错误信息: 未能使用提供程序“DataProtectionConfiguration ...

  3. 【转】双机高可用、负载均衡、MySQL(读写分离、主从自动切换)架构设计

    架构简介 前几天网友来信说帮忙实现这样一个架构:只有两台机器,需要实现其中一台死机之后另一台能接管这台机器的服务,并且在两台机器正常服务时,两台机器都能用上.于是设计了如下的架构.此架构主要是由kee ...

  4. linux安装redis及主从复制、读写分离、哨兵模式

    Redis安装与部署 版本最好选择3.0及以上.以后还可以部署Redis集群. 1.下载: [root@bogon redis-3.0.0]# cd /usr/local [root@bogon lo ...

  5. MHA+ProxySQL 读写分离高可用

    文档结构如下: 1.ProxySQL说明 ProxySQL是mysql的一款中间件的产品,是灵活的mysql代理层,可以实现读写分离,支持query路由器的功能,支持动态指定sql进行缓存,支持动态加 ...

  6. 重要参考步骤---ProxySQL实现读写分离

    MySQL配置主从同步文章地址:https://www.cnblogs.com/sanduzxcvbnm/p/16295369.html ProxySQL实现读写分离与读负载均衡参考文档:https: ...

  7. mybatis plugins实现项目【全局】读写分离

    在之前的文章中讲述过数据库主从同步和通过注解来为部分方法切换数据源实现读写分离 注解实现读写分离: http://www.cnblogs.com/xiaochangwei/p/4961807.html ...

  8. J2EE 项目读写分离

    先回答下 1.为啥要读写分离? 大家都知道最初开始,一个项目对应一个数据库,基本是一对一的,但是由于后来用户及数据还有访问的急剧增多, 系统在数据的读写上出现了瓶颈,为了让提高效率,想读和写不相互影响 ...

  9. Spring 实现数据库读写分离

    随着互联网的大型网站系统访问量的增高,数据库访问压力方面不断的显现而出,所以许多公司在数据库层面采用读写分离技术,也就是一个master,多个slave.master负责数据的实时更新或实时查询,而s ...

随机推荐

  1. 《Python自然语言处理》第一章-练习17

    问题描述: 使用text9.index()查找词sunset的索引值.你需要将这个词作为一个参数插入到圆括号之间.通过尝试和出错的过程中,找到完整的句子中包含这个词的切片. 解题思路: 用两个集合,一 ...

  2. oracle导入时提示IMP-00010:不是有效的导出文件,头部验证失败

    oracle导入时提示IMP-00010:不是有效的导出文件,头部验证失败: 原因分析:导出的oracle的版本与导入的oracle数据库的版本不一致: 可直接将dmp文件用notepad++打开修改 ...

  3. python——杂货铺

    三目运算: >>> 1 if 5>3 else 0 1 >>> 1 if 5<3 else 0 0 深浅拷贝: 一.数字和字符串 对于 数字 和 字符串 ...

  4. MySQL之使用DDL语句创建表

    一.使用DDL语句创建表 DDL语言全面数据定义语言(Data Define Language) 主要的DDL动词: CREATE(创建).DROP(删除).ALTER(修改) TRUNCATE(截断 ...

  5. 2017百度web前端实习生在线笔试题

    代码: import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner s ...

  6. Javascript中call和apply

    在上一章http://www.cnblogs.com/meiyh/p/6207671.html 我有提到javascript很重要的this关键字,现在我们结合这个关键字使用的下面的两个关键字: ca ...

  7. 【代码学习】PHP中GD库的使用

    PHP--GD库 ================================================ 一.支持: 需要php支持GD库 二.作用: 验证码.水印.缩放等 三.绘画步骤: ...

  8. bzoj2125 最短路

    Description 给一个N个点M条边的连通无向图,满足每条边最多属于一个环,有Q组询问,每次询问两点之间的最短路径. Input 输入的第一行包含三个整数,分别表示N和M和Q 下接M行,每行三个 ...

  9. CSS3弹性伸缩布局(上)——box布局

    布局简介 CSS3提供了一种崭新的布局方式:Flexbox布局,即弹性伸缩布局模型(Flexible Box)用来提供一个更加有效的方式实现响应式布局. 由于这种布局还处于W3C的草案阶段,并且它分为 ...

  10. Ruby中有意思的块

    块:是在调用方法时,能与参数一起传递的多个处理的集合 简单点说,跟在方法执行后面的do |变量| end就是一个块,这个块会被传入方法中去执行! 这个非常厉害,非常有意思! 在ruby中,如果需要便利 ...