龙空技术网

《日常问题锦集》「1」记录一次服务器宕机情况及排查

架构师成长记 197

前言:

今天咱们对“oraclefor循环捕获异常”都比较关心,各位老铁们都需要剖析一些“oraclefor循环捕获异常”的相关内容。那么小编在网上收集了一些关于“oraclefor循环捕获异常””的相关知识,希望你们能喜欢,朋友们快快来了解一下吧!

我想写一些实际项目中遇到的问题的总结。有点啰嗦,但只要有帮助就是值得的。先说说为什么要写这篇文章,我觉得一个是自己需要一个总结。同时,如果可能的话也给IT朋友们一个启发,当他们遇到问题时,不至于像我这么无措,给他们提供一个解决问题的思路。

我先说说自己遇到的情况吧。前段时间服务器时不时地宕机,通过所有可能的日志文件,发现一个很奇怪的问题,就是数据库字段长度问题,以及类型不匹配问题。当时针对发现的问题进行了几次修改,可问题依然存在。当时刚好有个新功能上线,所以怀疑是这个新功能上线导致的。又想这些功能测试人员在测试环境都验证过了,所以定位为生产环境数据量大导致的问题。先看看日志输出的信息。

报错信息

错误信息:ORA-01461: 仅能绑定要插入 LONG 列的 LONG 值

这个错误信息是说插入的信息长度太长,类型不匹配等等。这是一个修改状态的SQL语句爆出的信息,所以我们将这个状态对应的字段长度加长,当然问题还是存在的。又将修改状态的代码try...catch...了,问题还是存在。更令人费解的是,通过捕获异常,竟然出现了id索引重复的情况,用的是oracle,id是用序列自增的。此时陷入了深深的焦虑!

我想如果再纠结报错的信息,肯定是不会有新的进展的。所以,此时的想法是不去管报什么错,直接从服务器排查,把它当做一个黑盒来对待。

我这里使用阿里的arthas来协助排查问题,也可以使用jdk自带的工具jmap、jstack等等。

1、dashboard 当前系统的实时数据面板

BLOCKED

发现了一个重要的信息,有个线程blocked了,些许的惊喜!

2、thread id 查看当前线程信息,查看线程的堆栈

线程的堆栈信息

发现线程卡在一个叫runTrhead的方法那边。在一个线程中,竟然有多次调用。然后顺着这个思路去查找代码。

这是一个定时更新编码流水号的方法。方法的逻辑是这样,首先流水号是通过Redis的自增来得到的,确保在集群分布式下是唯一的,如果只是这样使用是没问题的。但是为了防止万一,从Redis获得到流水号之后,会定时将该流水号更新到数据库中。当Redis异常重启时就可以从数据库中获取最大的流水号,预防流水号从1开始,从而导致序号重复的问题。

那在更新流水号到数据库使用的是线程池 + 定时请求的方式。下面是代码的核心部分:

      // 更新数据库策略:			// 1、相差大小为N且时间必须是大于t秒,则立即更新一次;(循环)			// 2、或者时间超过T秒,个数大于0个时也立即更新一次;(长时间)			if ( ((getCurrObjFromCache(role).getVal() - getReferObjFromCache(role).getVal() >= 10) && (System.currentTimeMillis() - getReferObjFromCache(role).getTime()) >= 3000)					|| (getCurrObjFromCache(role).getVal() > getReferObjFromCache(role).getVal()) && (System.currentTimeMillis() - getReferObjFromCache(role).getTime()) >= 5000 ) {				synchronized (role) {					ValInfoCls cls = getCurrObjFromCache(role);					ThreadPoolUtil.getFixedPool4Serial().submit(new MgrSerialroleThread(serviceImpl, service, role, cls.getVal()));					// 同时把更新时的值赋给referMap,这个只是记录最新值,跟是否记录数据库不一定一致					putObjToCache(referObj, role, cls.getVal());					return;				}			}			// 处理循环更新,最后一次的情况			Thread.sleep(1000);		  // 递归调用			runTrhead(role);

通过上面的代码,我们发现当满足更新数据库策略的条件时,才会去执行更新的操作——加入线程池中。当不满足条件时,休眠一秒,然后递归本身,直到满足条件为止。

所以我们就不难理解为什么在一个线程中,会多次调用同一个方法了。

而出现BLOCKED,也正是因为并发调用导致数据库死锁了。然后程序还一直在执行,导致不断地开辟新的线程,或者加入到队列当中,从而导致服务器资源不足,不能提供正常的服务。

为了防止出现并发,而导致数据库死锁。那么我们给它加一把分布式锁,

// 1、获得锁			try {				if(!serviceImpl.getCacheManager().getJedisTemplate().setnxex(key, val, 1)){					return null;				}			} catch (Exception e) {				logger.error("------------获得锁异常-----------key = {},  val = {},  e = {}", key, val, e);				return null;			}			// 2、执行业务			try {				pubSerialRoleService.updateCurencoding(role, curdatetime, curencoding);			} catch (Exception e) {				e.printStackTrace();			} finally{				// 3、解锁				serviceImpl.getCacheManager().safedUnLock(key, val);			}

该代码并不能保证每次被调用,都能将最新的值更新到数据库中。出现的几率是非常低的,并且从实际角度考虑也允许其存在,所以权衡之后就不再对这个进行优化。如果特别在意的话,那么可以考虑优化,一个延长获取的时间,但是这个不可取,会导致后面进来的延迟阻塞等等。另外一个方案是,将进来的值跟等待的值进行比较,如果是同一个role的,那么进行合并,取最新值去更新即可。

标签: #oraclefor循环捕获异常