Mybatis动态切换数据库链接出现ArithmeticException异常

问题表现:偶发性的出现登录异常,或者是其他功能出现系统异常的情况

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 

### Error querying database.  Cause: java.lang.ArithmeticException

### The error may exist in jnpf/base/mapper/SysconfigMapper.java (best guess)

### The error may involve jnpf.base.mapper.SysconfigMapper.selectList

### The error occurred while executing a query

### Cause: java.lang.ArithmeticException

	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:96)

	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:441)

问题分析:

        查看某个系统日志发现是在查询数据库操作时出现了一个ArithmeticException,初步确定不是自己编写的代码中抛出的这个异常,之后根据异常抛出的堆栈信息定位到jnpf-commom-database动态数据库切换时,做均衡策略时会对当前数据库链接集合size进行取模运算,根据异常日志前后信息判断,就是在取模运算时分母为0导致的异常。

解决方案

    1、负载均衡策略中的size是在项目启动,以及第一次数据库请求时,保存的数据库链接信息。分为两部分,第一部分是dataSourceMap全部数据库链接信息是以租户+主从+唯一键的方式保存再map中,第二部分则是groupDataSourcesMap分组数据库链接信息是以租户+主从方式存在map中,所以数据结构是groupDataSourcesMap中也就是一个租户会对应多个数据库链接,以租户进行分组管理

    /**
     * 新数据源添加到分组
     *
     * @param ds         新数据源的名字
     * @param dataSource 新数据源
     */
    private void addGroupDataSource(String ds, DataSource dataSource) {
        if (ds.contains(UNDERLINE)) {
            String group = ds.split(UNDERLINE)[0];
            GroupDataSource groupDataSource = groupDataSources.get(group);
            if (groupDataSource == null) {
                try {
                    groupDataSource = new GroupDataSource(group, strategy.getDeclaredConstructor().newInstance());
                    groupDataSources.put(group, groupDataSource);
                } catch (Exception e) {
                    throw new RuntimeException("dynamic-datasource - add the datasource named " + ds + " error", e);
                }
            }
            groupDataSource.addDatasource(ds, dataSource);
        }
    }


    2、通过异常一日分析,在出现ArithmeticException之前,数据库链接池因为多次请求链接失败,将groupDataSourcesMap中的dataSourceMap中的数据库链接信息移除了,所以这个groupDataSourcesMap下的dataSourceMap.size()为0。

public class LoadBalanceDynamicDataSourceStrategy implements DynamicDataSourceStrategy {

    /**
     * 负载均衡计数器
     */
    private final AtomicInteger index = new AtomicInteger(0);

    @Override
    public String determineKey(List<String> dsNames) {
        return dsNames.get(Math.abs(index.getAndAdd(1) % dsNames.size()));
    }
}


    3、通过分析dataSourceMap.add()方法出现的代码,再通过debug验证添加数据库链接的时间。得到是每次在进行数据库请求时,首先判断groupDataSources中是否有当前链接,如果有则不重新添加。但是在上述数据结构中,只是将groupDataSourcesMap下的dataSourceMap数据移除,groupDataSourcesMap实际上还存在,所以在进行添加分组链接数据的前置判断中,会默认groupDataSourcesMap是有数据的,也就是默认groupDataSourcesMap下的dataSourceMap不为0.所以就会一直出现算数异常。程序始终在报错,只有通过重启来完整的重新加载groupDataSourcesMap数据才能解决

public synchronized void removeDataSource(String ds) {
        if (!StringUtils.hasText(ds)) {
            throw new RuntimeException("remove parameter could not be empty");
        }
        if (primary.equals(ds)) {
            throw new RuntimeException("could not remove primary datasource");
        }
        if (dataSourceMap.containsKey(ds)) {
            DataSource dataSource = dataSourceMap.remove(ds);
            closeDataSource(ds, dataSource);
            if (ds.contains(UNDERLINE)) {
                String group = ds.split(UNDERLINE)[0];
                if (groupDataSources.containsKey(group)) {
                    DataSource oldDataSource = groupDataSources.get(group).removeDatasource(ds);
                    if (oldDataSource == null) {
                        log.warn("fail for remove datasource from group. dataSource: {} ,group: {}", ds, group);
                    }
                }
            }
            log.info("dynamic-datasource - remove the database named [{}] success", ds);
        } else {
            log.warn("dynamic-datasource - could not find a database named [{}]", ds);
        }
    }


    4、具体的代码修改点:第一在请求链接出现算数异常时进行捕获处理,移除dataSourceMap出现异常的链接,并判断如果dataSourceMap移除问题数据后size为0,则将这个dataSourceMap所属的groupDataSourcesMap也同步移除掉,保证在下一次请求时必须让完整的加载groupDataSourcesMap数据。

 public static void removeAllAssignDataSource() {
        if (isTenantAssignDataSource()) {
            String tenantId = DataSourceContextHolder.getDatasourceId();
            String dbKey = tenantId + StrPool.DASHED + DdConstants.MASTER;
            TenantVO tenantVO = null;
            try {
                tenantVO = TenantDataSourceUtil.getTenantInfo(tenantId);
            } catch (LoginException e) {
                log.error("获取缓存租户库列表失败:" + tenantId, e);
            }
            if (tenantVO != null) {
                List<TenantLinkModel> linkList = tenantVO.getLinkList();
                if (linkList != null) {
                    // 添加数据源信息到redis中
                    String mKey = dbKey + UNDERLINE;
                    String sKey = tenantId + StrPool.DASHED + DdConstants.SLAVE + UNDERLINE;
                    for (TenantLinkModel model : linkList) {
                        DbLinkEntity dbLinkEntity = model.toDbLinkEntity();
                        String key;
                        if ("0".equals(String.valueOf(model.getConfigType()))) {
                            key = mKey + dbLinkEntity.getId();
                        } else {
                            key = sKey + dbLinkEntity.getId();
                        }
                        try {
                            DataSource dataSource = DynamicDataSourceUtil.dynamicRoutingDataSource.getDataSources().get(key);
                            if (dataSource instanceof ItemDataSource && ((ItemDataSource) dataSource).getRealDataSource() instanceof DruidDataSource) {
                                //Druid数据源如果正在获取数据源 有概率连接创建线程无法停止
                                ((DruidDataSource) ((ItemDataSource) dataSource).getRealDataSource()).setBreakAfterAcquireFailure(true);
                            }
                            DynamicDataSourceUtil.dynamicRoutingDataSource.removeDataSource(key);
                            if (key.contains(UNDERLINE)) {
                                String group = key.split(UNDERLINE)[0];
                                if (DynamicDataSourceUtil.dynamicRoutingDataSource.getGroupDataSources().containsKey(group)) {
                                    Map<String, DataSource> dataSourceMap =
                                            DynamicDataSourceUtil.dynamicRoutingDataSource.getGroupDataSources().get(group).getDataSourceMap();
                                    if (dataSourceMap.size() == 0) {
                                        DynamicDataSourceUtil.dynamicRoutingDataSource.getGroupDataSources().remove(group);
                                    }
                                    log.warn("clear datasource from group. dataSource: {} ,group: {}", key, group);
                                }
                            }
                        } catch (Exception e) {

                        }
                    }
                }
            } else {
                log.error("获取缓存租户库列表失败: {}", tenantId);
            }
        }
    }

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值