记一次Bug定位过程 —— 由非预期的页面提示到松耦合模块的依赖处理

背景介绍

  集群备份:软件为了高可靠性,防止所在服务器down掉后业务中断,使用了集群,在多台PC上跑同样的代码。但同一时间,只有一台pc真正的处理业务,即Leader;其他的PC只是作为热备,当leader故障后才会选举中其中之一,处理业务。
  模块依赖:为支持热插拔,使用OSGI框架,整个软件分为多个模块,可在运行软件时,不重启整个软件,只卸载再加载不同模块,即实现模块的升级。这样实现势必会要求模块之间是松耦合的,模块之间的依赖需小心处理。
  业务抽象:不能涉及到具体业务,所以本文记录的内容会做一定抽象。当模块加载完成(将自身注册为服务)后,会得到集群事件,本服务器是否为Leader,是才处理业务。出问题的模块干的事是,起个线程D,定时启动一个固定的监控任务,并将任务的结果保存起来;同时主线程M等待随时由用户发起的实时监控任务。这些任务都应该有唯一的id标识,id自增是放在一个公共对象中,并统一使用一个加锁的方法。实现时,公共对象简化如图(Java语言实现):
  这里写图片描述

id是公共对象的一个动态成员变量,取任务id的方法如下:

public synchronized long getId(){
    this.id++;
    return this.id;    
}

问题描述

  发现保存的任务id顺序不对,并不是递增的。而是形如456,467,473,481,35,37,45,512,525,537,544,52,56,69,553,560,73,75……
  能确定的是保存id的方式是直接将新的任务id加入数组的最后,并没有使用会将输入重排的数据结构。

问题定位过程

  根据现象看id是沿两条线456~560,35~75递增的。这一点是拉出了所有的监控任务id,发现任务id两条线的id有时会有重复,才最终确定的。在此之前,首先怀疑的是定时任务的线程D获取到id后,在执行或者保存结果的时候,因为什么原因延迟,导致保存结果的时机推后了。
  发现两条线有时重复后,就由此断定是出现线程安全问题了。
  业务设计时,处理程序定为唯一对象,即单例模式。自接手维护以来,这一块的代码就一直是单实例运行,所以最初想当然的认为框架保证了对象唯一,即使用了单例模式。但因为对OSGI本身不算熟悉,没有细细的去找单例实现的地方。最终确定问题,是在处理逻辑那断电,将对象toString打印,看到@后的数字不一样(这里的值时内存地址的hash值,不一样则肯定是两个对象),才确定对象产生了两个。
  最终原因是OSGI启动本模块(active阶段)时,本模块依赖的service还没起,创建了一个对象,使注册依赖服务时异常。实际上依赖的模块只是在后续使用时需要,启动时可以不关心,即属于弱依赖关系。
  

解决

  最终将依赖的服务的注册时机延后到使用前,并处理此时仍没有依赖服务的情况。实现两模块启动阶段的松耦合。
  
  本次的教训就是一句话:Talking is cheap,show me the code…

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章