最近莫名的会有错误日志,说有写操作因为走了读库而报了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. 1132: 零起点学算法39——多组测试数据(a+b)

    1132: 零起点学算法39--多组测试数据(a+b) Time Limit: 1 Sec  Memory Limit: 64 MB   64bit IO Format: %lldSubmitted: ...

  2. Entity Framework快速入门--IQueryable与IEnumberable的区别

    IEnumerable接口 公开枚举器,该枚举器支持在指定类型的集合上进行简单迭代.也就是说:实现了此接口的object,就可以直接使用foreach遍历此object: IQueryable 接口 ...

  3. 使用JS实现鼠标悬浮切换显示

    实现的是在鼠标悬停在不同链接上,在同一位置切换显示想要显示的内容 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional// ...

  4. sql中常见日期获取

    获取当前年月日 --获取当前月份 ,GETDATE())) --获取当前月份的下个月 ,GETDATE())) --获取当前月份的上个月 year()获取年 select year(GETDATE() ...

  5. linux下安装node

    经过一番的折腾终于在linux上安装了node,记录下来以免忘记 1.下载node 去官网下载最新的linux版本下对应node.js,node-v6.10.2-linux-x64.tar.gz 2. ...

  6. 关于Java中volatile关键字笔记

    volatile通常被认为是一种轻量级的synchronized,字面上它表示易变的,在并发编程中,它保证了共享变量的可见性.所谓可见性指的是,某个线程对变量进行操作后,其他线程能够读取到操作后的最新 ...

  7. javascript中类式继承和原型式继承的实现方法和区别

    在所有面向对象的编程中,继承是一个重要的话题.一般说来,在设计类的时候,我们希望能减少重复性的代码,并且尽量弱化对象间的耦合(让一个类继承另一个类可能会导致二者产生强耦合).关于“解耦”是程序设计中另 ...

  8. XSS跨站脚本攻击

    1.简介 跨站脚本(cross site script)为了避免与样式css混淆,所以简称为XSS. XSS是一种经常出现在web应用中的计算机安全漏洞,也是web中最主流的攻击方式.那么什么是XSS ...

  9. 一个只有99行代码的JS流程框架(二)

    欢迎大家关注腾讯云技术社区-博客园官方主页,我们将持续在博客园为大家推荐技术精品文章哦~ 张镇圳,腾讯Web前端高级工程师,对内部系统前端建设有多年经验,喜欢钻研捣鼓各种前端组件和框架. 导语 前面写 ...

  10. Java项目中的一些注意事项

    一.关于包命名方式 例如:com.sun.spring.xxx.service.impl 第一级:公司域名的倒序com.sun 第二级:项目名称spring 第三级:模块信息xxx 第四级:功能顶层包 ...