排查redisson中订阅connection无故消失的问题

2016/12/13 20:52:41 No Comments

最近在项目中使用了redis结合spring cache一起作了一个缓存,并使用了订阅功能来达到进程间的数据同步。但在测试使用过程中,发现第二天一来,本来应该工作的订阅同步并不能进行。当时没在意,简单重启了事。但后来发现,每天早上相应的同步都不能进行,并且经测试。每个进程的同步都不进行,感觉是redis的订阅出问题了。

1. 验证问题

表现出来就是A程序修改了数据,B程序并没有识别到这次更新。那么就先尝试手动在redis中进行相应的set操作,因为订阅的是redis的空间事件,即key space event. 在控制台单独连接redis,执行相应的set 命令,在B程序中并没有任何表示。
怀疑是不是redis的发布订阅出问题了,就另开终端,手动地进行订阅,一切正常。
初步确定是程序出问题了。

2. 查看程序

把进程的stack打印一份,查看里面的线程信息,发现并没有redisson的线程信息。一般来说,没收到消息也可能是线程内阻塞了,但是直接就没有相应的线程,这就有问题了。
怀疑是不是相应的连接被断开之后就没有再连,也可能是redis server被重启了。

重新连接redis,使用status查看相应的状态,显示server端并没有重启过,其运行时间长达X天,即没有间断过。

回过来再看相应的redisson程序,里面有一个watch dog,是负责连接断开重连的。即如果连接被断掉了,它会尝试重连,但每一次的重连都会迟后一定时间,如 1 2 4 8 秒这样。这也是为什么要看stack的原因。简单看了下watchdog的实现,表示并没有明显的问题(或者就没问题)。

进一步怀疑是不是出现了某个异常,导致相应的watchdog重连直接被中断了。比如Error级错误。将相应的日志拉下来一份,因为一晚上都没人操作,日志信息本身也很少。直接在里面使用grep Exception查找日志信息,但一切也很正常,甚至没有异常发生。

再重新查看了redisson的源码,里面使用netty的channelInActive来进行重连尝试。在本地测试了一下这个机制,redis重启或者断开连接,它都能检测到。开始怀疑是不是日志信息不全,被吞掉了。重新调整日志级别,将redisson和netty的级别调到TRACE级别,结果中午过去一会,再回来看。相应的连接又不见了,订阅又不能正常工作。查看了日志,里面也没有任何信息,因为这期间无任何操作。

read more… »

推荐我在github上的一个重构项目mvelx

2016/11/19 16:01:33 No Comments

近半年时间一直在对mvel项目中的代码尝试进行翻译,在翻译的过程当中,就有一个对人家的代码进行修改的冲动。但考虑到很多使用此框架的人,在碰到问题时都是对照的原来的代码。因此在翻译过程中,对原来的代码和注解不作任何调整,所有的操作都是作添加处理。待大部分的功能都进入尾声,相应的重构项目的日程也提上来。

恰好在项目中碰到一个需要对mvel功能作调整才能支持的例子,因此就此在github上新开一个项目,重构原来的mvel,并且在加上相应的功能代码之后,决定作为一个新的项目进行发布,并且所有的代码都保证开源方式。

想了解mvel是什么,可以先google一下,作为一个表达式引擎,它能够支持大部分的表达式工作场景。但对于想要了解这一种引擎的开发人员来说,没有一个有效的注释和相应的开发思路,是很难了解到底是什么东西。并且如果只是想使用,不深入其中,是不会明白一个表达式引擎是如何工作的。

相应的git项目地址:https://github.com/flym/mvelx
目的:用于提供mvel的中文注释版,并且删除在实际开发中不再使用的代码,提供一个简单的脚本执行引擎

希望得到支持

使用guava Striped中的lock导致线程死锁的问题分析

2016/11/19 15:04:00 No Comments

在最近的开发调试当中,一个多线程的项目出现了线程死锁的问题。按照正常的解决思路,上监控工具yourkit或者自己打印出线程信息,最终的显示信息均为lock对象先拿到了自己的锁,然后去拿对方的锁,现状是这样的。但与一些正常的项目相比,按照正常的执行情况,是不可能出现死锁的。

项目使用了程序锁来保证并发情况下,一些操作只能由单个来执行,如一些不允许多次运行的场景。所谓程序锁,即通过由锁池获取相应的lock对象,然后进行lock操作,在相应的方法体内进行相应的控制。
项目中使用基于guava的striped来获取不同的锁,本来原来的代码是自己封装实现,但考虑到guava已经提供,因此直接使用了,相应的伪代码如下所示:

        Striped<Lock> lockStriped = Striped.lazyWeakLock(1024);

        Lock lock = lockStriped.get("动态生成的临界资源标识符");
        lock.lock();
        try{
            //执行业务操作
        } finally {
            lock.unlock();
        }

以上的代码因为足够的通用,因此被封装为一个LockAdvice,以实现通用的锁方法操作,只需要在需要加锁的方法上加上Lockable注解,就可以达到所要的效果。出现死锁的2个线程表现如下:

//线程1
加锁获取资源类型1
加锁获取资源类型2

//线程2
加锁获取资源类型1
加锁获取类型类型2

从上面的线程操作来看,加锁的顺序是一样的,释放锁的顺序也一致,因此理论上不会出现死锁。为保证锁切面的简单实现,上面的锁资源类型1 被表现为一个字符串,即通过不同的字符串来进行描述。也就是说,在上面的现象中,会有4个字符串进行锁的key进行操作。

问题场景被发现了,并且这个问题肯定会产生,多线程运行一段时间之后就会发生。查找问题产生的原因却很漫长。不知道为什么,使用jstack却没有打印出每个线程在哪一个环节持有哪个锁(是不是本身就没有),通过jstack的信息表示,相应的线程肯定持有对方的锁,而对方也在等级已方的锁,并且相应的锁的发生点也确定是在相应的锁切面准备执行的时候。剩下的问题就是看是否是相应的锁生成器striped本身就返回了相同的锁,如果由锁生成器本身就返回相同的锁,那么就肯定有问题了。

read more… »

gc过程中reference对象的处理

2016/09/18 14:50:03 No Comments

之前从java的角度描述了Reference对象的处理,因为整个处理过程并不是完全由java这边来处理,另一个方面JVM在整个gc过程中,还需要负责来维护整个reference对象以及正确地往相应的pending对象里面进行处理。因此这里从jvm的角度来描述相应的reference对象是如何处理的。

引用对象结构

数据结构定义在referenceProcessor.hpp。定义了以下4种类型的结构。

_discoveredSoftRefs
_discoveredWeakRefs
_discoveredFinalRefs
_discoveredPhantomRefs

每一个结构相对应的数据结构为 DiscoveredList,可以理解为与具体的Reference相同的结构,类似一个处理链表,里面的每一个节点都对应着java中的reference对象。这里仍然采用头指针+length的结构来持有所有需要处理的reference对象。在这里面存放的对象都表示在相应的处理过程中还没有被放入java Reference中pending结构的对象。

从总体上的处理逻辑来看,可以理解为。在整个gc过程中,首先在jvm内部维护一套需要被放到pending中的引用链,然后处理这些引用链,处理完之后将相应的数据重新附到pending中,清除jvm内部数据。这样达到一个reference的处理过程。

read more… »

java中针对Reference的实现和相应的执行过程

2016/09/05 16:52:16 No Comments

在之前Finalizer中提取相应的reference以及相应的ReferenceQueue.特殊的reference对象都是被jvm专门处理的,因此这里就相应的工作流程和referencequeue之间的协作进行梳理.

Reference类型(除强引用)

可以理解为Reference的直接子类都是由jvm定制化处理的,因此在代码中直接继承于Reference类型没有任何作用.只能继承于它的子类,相应的子类类型包括以下几种.(忽略没有在java中使用的,如jnireference)

SoftReference
WeakReference
FinalReference
PhantomReference

上面的引用类型在相应的javadoc中也有提及.FinalReference专门为finalize方法设计,另外几个也有特定的应用场景.其中softReference用在内存相关的缓存当中,weakReference用在与回收相关的大多数场景.phantomReference用在与包装对象回收回调场景当中(比如资源泄漏检测).

可以直接在ide中查看几个类型的子类信息,即可了解在大多数框架中,都是通过继承相应的类型用在什么场景当中,以便于我们实际进行选型处理.

read more… »

java中针对finalize的实现和相应的执行过程

2016/09/05 14:23:57 No Comments

之前在常规的java书籍中,即会描述 object的finalize方法是用于一些特殊的对象在回收之前再做一些扫尾的工作,但是并没有说明此是如何实现的.本篇从java的角度(不涉及jvm以及c++)

FinalReference引用

此类是一个package类型,表示它并不是公开的一部分,继承自Reference, 即表示也是一种特定的引用类型,因此每个包装在其中的对象在被回收之前,自己都会放到指定的referqyebceQueue当中.

这个引用对象专门为带finalize方法的类服务,可以理解为每一个有相应的方法的对象,其都会封装为一种finalRefernece对象.

因为finalize方法是object定义的,其默认实现为空.那么如果重写了此方法,那么方法体肯定不为空.即可以通过这一种区别来.只要finalize方法实现不为空的类,此产生的对象都需要被注册到finalRefernece中.

这一步可以通过在newInstance的时候,即调用object默认构造方法的时候,就可以进行相应的注册了.

read more… »