排查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级别,结果中午过去一会,再回来看。相应的连接又不见了,订阅又不能正常工作。查看了日志,里面也没有任何信息,因为这期间无任何操作。

(more…)

服务端推送websocket和sse场景及应用

2016/07/04 14:21:44 No Comments

在当前的系统中, 涉及到使用长连接进行通信的应用越来越多,许多应用场景也已经不再满足于通过常规的http来进行短时间的交互.而是希望像传递的cs结构一样,常时间地挂在服务器上,以接收一定的数据信息.可以理解为使用浏览器来作传统cs客户端的事情.

经过几天简单地了解,就当前使用java进行后端推送类的服务进行了一些了解,就应用场景,开发方式以及一些特定的实现进行了简单的demo处理.

应用场景

  1. 都可以进行服务端推送,并且都是使用长连接来进行.但两者的实现又有一点不同,sse仍使用http协议,并且使用相同的链接发送正常的http协议报文.而websocket是使用http协议进行握手,然后再使用同一个链接进行websocket协议的通信.
  2. websocket可以进行双向的通信,即服务端可以往客户端发信息,客户端也可以向服务端发信息.而sse是单向的,只能由服务端往客户端发.
  3. websocket自带连接的保持,即通过ping/pong协议保证连接可以始终维持,sse没有这个保证,不过可以参考ping/pong协议,自己周期性地发送信息来同样地进行处理.比如,5秒往客户端发一个特别的信息(通过type/name进行区分).其次,因为是基于浏览器的使用,sse有一个特性,就是浏览器发现一个连接断掉了,就会自动地进行重联,即重新发送请求.这样,服务端也不用担心连接被断开,不过需要处理新的请求必须和上一次请求的内容相连续,以及新的推送注册.
  4. 因为都是使用http协议进行起始处理,因此在签权上都可以使用到http协议本身的一些东西,比如header/cookie签权.在相应的握手阶段,通过读取cookie(session)来保证相应的请求必须是经过授权的,也可以用于定位使用人.甚至可以通过这些信息保证单个用户只能有一个请求,避免重复请求
  5. 由于都是基于浏览器使用,因此建议的数据传输都是文本型.虽然websocket支持二进制frame传输,不过一些都不建议使用.sse只能传输文本
  6. 不管是websocket还是sse,在用于通信时,都建议只用于进行数据的推送,而不是进行完整的应用处理.这里可以理解为,常规的业务处理仍然交给后端的服务来处理.这样,即可以使用之前的业务开发的优势,又可以使用推送的优势.而不是走向另一个级端,即所有的信息都想通过推送来传递.

(more…)

使用nod32导致客户端响应中没有gzip压缩标识的问题

2016/06/20 11:18:58 No Comments

之前在本机研究dropwizard时,本着默认的情况下应该开启gzip压缩,因此在相应的yml配置中开启gzip标记(它本身也是开启的).但是在相应的请求返回中并没有出现相应的gzip标识,在chrome浏览器下的标识信息为如下的响应所示:

从上面的标识来看,在响应头上并没有gzip标识.
然后开始痛苦的处理过程,从浏览器,客户端,服务端,然后最后从网络层逐步处理,最终找到问题的原因及出处.

(more…)

http服务器参考实现-1 client server channel分离

2014/08/10 22:34:46 No Comments

我们准备实现一个简单的http服务器。它具备有接收客户端请求,并通过读取相应的数据进行协议分析,最后返回相应的数据的能力。

从根本上来说,http服务器也就是一个网络服务器,nio框架netty也有一个参考的http协议实现。本参考实现不使用第三方网络包,仅通过javaSE来完成一个http协议实现,以用于在读取http协议时有一个更清晰的认识。
本实现的细节在一定程度上参考了netty,tomcat等现有的实现,但这里详细地把相应的理论细节解释清楚。
本篇为第1节,即通过构建一个nio服务端来作基本的网络处理。

在nio中,服务端建立起相应的serverSocket,并向selector注册相应的事件。当事件发生时去做相应的事情即可,在这个过程中数据的传输和代码实现是异步的,即在有数据的情况下才能做相应的事情,但数据和操作之间并不同步,即不能一次性地把所有的数据都读取完毕,或者不能一次性地把所有的数据都返回给客户端。所以,在整个实现中,需要在事件处理中不断地监听相应的事件,并通过在处理数据和事件切换中不断地变换具体的业务处理,直到一个完整地业务被处理掉。

在整个实现中,涉及到2个问题。一是事件的划分和处理,二是具体事件的隔离和处理。

1 事件的划分和处理

在服务端,需要处理3类事件信息,接收数据请求,读取请求数据,写入响应数据。读取数据和写入数据都是在connection已经建立好的情况下进行,而接收数据请求则表示服务端已经准备好接收由客户端发起的一个请求。这里将数据的处理和请求的接收分开,即分成2个大的处理部分。一个简单的原因即在于当连接已经建立起之后,服务端是一定要处理这个数据请求的;而连接还没有建立时,是可以拒绝处理的。即在当请求端有大量的请求在准备接收时,服务端不会因为请求的处理太多影响到后端的数据处理。

这里涉及到线程网络处理的问题,如果大量的处理线程都处理请求接收时,就意味着后台有大量的连接需要处理,而之前已经接收到的请求处理就会受到相应的影响,进而影响到整个系统的稳定性。将接收和处理分离,有利于更好地实现请求限制和分发。

(more…)

http中chunked编码的特殊编码头

2014/06/24 09:00:14 No Comments

在进行Http传输中,可以通过设置Transfer-Encoding以进行传输编码。其中chunked编码表示分块进行数据传输,其通过一个0长度的chunk块表示数据传输完毕。对于在传输过程中的chunked块,则通过以下的一个数据格式进行描述:

chunk-size 
[ chunk-extension ] 
CRLF 
chunk-data 
CRLF

不考虑chunked-extension属性,则可以理解为先描述块长度,然后接回车换行,接下来再描述相应长度的数据信息,最后接回车换行符,即完成一个chunk编码信息。

这里的chunk-size,与其它的传输不同,这里需要设置为 16 进制数字字符串。即不是传输认为的设置一个长度值即可。对于一个长度值为255的chunk块,其16进制为ff(小写表示),直接编码即为 ff。但这里要求为数字字符串,即以字符串的方式来描述编码值,编码为'f''f',针对f字符的编码值为 66(16进制),那么255长度的实际编码值即为 66 66。

一个255长度的编码片断,类似如下所示:

如上所示的标记处,前面为头结束的2个CRLF值。

在Http编码中,只有这里的chunked编码有点特别。其它的长度信息如content-length值等,都是使用正常的长度值(16进制),确实有点奇怪。在编码时,如果对这里的编码头没写正确,那么输出到客户端的数据永远都是错的…

transfer-encoding和content-length的不同实现

2014/06/01 23:25:12 No Comments

前段时间在项目中看到如下的代码:

		HttpServletResponse response = (HttpServletResponse)servletResponse;
		response.setHeader("Transfer-Encoding", "utf8");
		filterChain.doFilter(servletRequest, servletResponse);

原意是想对输出的内容进行编码,却用错了响应头,结果这个错误的响应头对后面的客户端程序带来了许多麻烦。这里有必要对这个这块的内容进行详细地了解。

传输数据编码:Transfer-Encoding
数据编码,即表示数据在网络传输当中,使用怎么样的保证方式来保证数据是安全成功地传输处理。可以是分段传输,也可以是不分段,直接使用原数据进行传输。
有效的值为:Trunked和Identity.
传输内容编码:Content-Encoding
内容编码,即整个数据信息是在数据器端经过怎样的编码处理,然后客户端会以怎么的编码来反向处理,以得到原始的内容。这里的内容编码主要是指压缩编码,即服务器端压缩,客户端解压缩。
可以参考的值为:gzip,compress,deflate和identity。
传输内容格式:Content-Type
内容格式,即接收的数据最终是以何种的形式显示在浏览器中。可以是一个图片,还是一段文本,或者是一段html。内容格式额外支持可选参数,charset,即实际内容的字符集。通过字符集,客户端可以对数据进行解编码,以最终显示可以看得懂的文字(而不是一段byte[]或者是乱码)。

3种描述信息,可以由下图来表示(来源于《Http权威指南》):

从上文中,可以看出,实际上原filter中的内容可能是想表达以下的意思:

response.setContentType("text/html;charset=UTF8");
//或者
response.setContentType("application/json;charset=UTF8");

(more…)