zoukankan      html  css  js  c++  java
  • 这些年,我写过的BUG(二)

    BUG是最好的学习素材。

    最近的BUG都不疼不痒,基本秒修复。昨天遇到一个大坑,修复了好几个小时。这是一个事务挂起导致数据库连接未释放,然后导致获取数据库连接失败的BUG

    场景

    运行测试用例集(包含多个测试用例),处理逻辑如下:1、首先去并发处理用例参数,例如关联用户的登录状态(这个比较麻烦,请参考旧文内容:我的开发日记(十五)中的分布式锁的实现);2、把用例组装成多线程任务,丢到线程池去执行;3、异步等待所有用例执行完成,处理数据,异步写入数据库。

    BUG代码

        /**
         * 获取用户登录凭据,map缓存
         *
         * @param id
         * @param map
         * @return
         */
        @Override
        @Transactional(isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRES_NEW)
        public String getCertificate(int id, ConcurrentHashMap<Integer, String> map) {
            if (map.contains(id)) return map.get(id);
            Object o = UserLock.get(id);
            synchronized (o) {
                if (map.contains(id)) return map.get(id);
                logger.warn("非缓存读取用户数据{}", id);
                TestUserCheckBean user = testUserMapper.findUser(id);
                if (user == null) UserStatusException.fail("用户不存在,ID:" + id);
                String create_time = user.getCreate_time();
                long create = Time.getTimestamp(create_time);
                long now = Time.getTimeStamp();
                if (now - create < OkayConstant.CERTIFICATE_TIMEOUT && user.getStatus() == UserState.OK.getCode()) {
                    map.put(id, user.getCertificate());
                    return user.getCertificate();
                }
                boolean b = UserUtil.checkUserLoginStatus(user);
                if (!b) {
                    updateUserStatus(user);
                    if (user.getStatus() != UserState.OK.getCode()) UserStatusException.fail("用户不可用,ID:" + id);
                } else {
                    testUserMapper.updateUserStatus(user);
                }
                map.put(id, user.getCertificate());
                return user.getCertificate();
            }
        }
    
    

    BUG分析

    这里犯了两个错误:

    判断key方法错误

    应该使用map.containsKey(id)来判断,而不是map.contains(id),可以看一下map.contains(id)的源码:

    
        /**
         * Legacy method testing if some key maps into the specified value
         * in this table.  This method is identical in functionality to
         * {@link #containsValue(Object)}, and exists solely to ensure
         * full compatibility with class {@link java.util.Hashtable},
         * which supported this method prior to introduction of the
         * Java Collections framework.
         *
         * @param  value a value to search for
         * @return {@code true} if and only if some key maps to the
         *         {@code value} argument in this table as
         *         determined by the {@code equals} method;
         *         {@code false} otherwise
         * @throws NullPointerException if the specified value is null
         */
        public boolean contains(Object value) {
            return containsValue(value);
        }
    

    其实map.contains(id)查的value而不是key,导致很多多余的查询和其他操作。

    事务传播行为

    具体知识点参考旧文:我的开发日记(三)中对于事务隔离级别事务传播行为的记录。

    这里的REQUIRES_NEW表示REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。

    每一个事务都会占用一个连接,然后会把之前的事务挂起等待,这样就导致会占用很多数据库连接而不释放。再加上本身有很多读写数据库的操作,所以导致了下面的报错:

    
    2020-07-29 10:27:50 ERROR com.okay.family.service.impl.CaseCollectionServiceImpl:287 [] [Thread-176] 处理用例参数发生错误!
    org.springframework.transaction.CannotCreateTransactionException: Could not open JDBC Connection for transaction; nested exception is java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30006ms.
    	at org.springframework.jdbc.datasource.DataSourceTransactionManager.doBegin(DataSourceTransactionManager.java:308)
    	at org.springframework.transaction.support.AbstractPlatformTransactionManager.startTransaction(AbstractPlatformTransactionManager.java:400)
    	at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373)
    	at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:572)
    	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:360)
    	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:118)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
    	at com.sun.proxy.$Proxy82.handleParams(Unknown Source)
    	at com.okay.family.service.impl.CaseCollectionServiceImpl.lambda$null$4(CaseCollectionServiceImpl.java:282)
    

    解决办法

    调整事务传播行为

    删除REQUIRES_NEW设置,恢复默认值。

    设置超时时间

    数据库连接池获取连接超时时间设置:

    spring.datasource.hikari.connection-timeout=3000


    • 公众号FunTester首发,更多原创文章:FunTester430+原创文章,欢迎关注、交流,禁止第三方擅自转载。

    热文精选

  • 相关阅读:
    maven工程中dubbo与spring整合
    redis在linux服务器部署
    redis在应用中使用连接不释放问题解决
    redis使用例子
    文件上传和下载(可批量上传)——基础(一)
    Hibernate各种主键生成策略与配置详解
    理解Spring、工厂模式和原始方法的说明以及对Spring的底层实现的理解
    查询文件当前目录
    Spring官网改版后下载
    Mysql事件学习
  • 原文地址:https://www.cnblogs.com/FunTester/p/13403407.html
Copyright © 2011-2022 走看看