问题描述

在C/S模式中,有时我们会长时间保持一个连接,以避免频繁地建立连接,但同时,一般会有一个超时时间,在这个时间内没发起任何请求的连接会被断开,以减少负载,节约资源。并且该机制一般都是在服务端实现,因为client强制关闭或意外断开连接,server端在此刻是感知不到的,如果放到client端实现,在上述情况下,该超时机制就失效了。本来这问题很普通,不太值得一提,但最近在项目中看到了该机制的一种糟糕的实现,故在此深入分析一下。

问题分析及解决方案

服务端一般会保持很多个连接,所以,一般是创建一个定时器,定时检查所有连接中哪些连接超时了。此外我们要做的是,当收到客户端发来的数据时,怎么去刷新该连接的超时信息?

最近看到一种实现方式是这样做的:

  1. public class Connection {

  2. private long lastTime;

  3. public void refresh() {

  4. lastTime = System.currentTimeMillis();

  5. }

  6. public long getLastTime() {

  7. return lastTime;

  8. }

  9. //......

  10. }

复制代码

在每次收到客户端发来的数据时,调用refresh方法。

然后在定时器里,用当前时间跟每个连接的getLastTime()作比较,来判定超时:

  1. public class TimeoutTask  extends TimerTask{

  2. public void run() {

  3. long now = System.currentTimeMillis();

  4. for(Connection c: connections){

  5. if(now - c.getLastTime()> TIMEOUT_THRESHOLD)

  6. ;//timeout, do something

  7. }

  8. }

  9. }

复制代码

看到这,可能不少读者已经看出问题来了,那就是内存可见性问题,调用refresh方法的线程跟执行定时器的线程肯定不是一个线程,那run方法中读到的lastTime就可能是旧值,即可能将活跃的连接判定超时,然后被干掉。

有读者此时可能想到了这样一个方法,将lastTime加个volatile修饰,是的,这样确实解决了问题,不过,作为服务端,很多时候对性能是有要求的,下面来看下在我电脑上测出的一组数据,测试代码如下,供参考

  1. public class PerformanceTest {

  2. private static long i;

  3. private volatile static long vt;

  4. private static final int TEST_SIZE = 10000000;

  5. public static void main(String[] args) {

  6. long time = System.nanoTime();

  7. for (int n = 0; n < TEST_SIZE; n++)

  8. vt = System.currentTimeMillis();

  9. System.out.println(-time + (time = System.nanoTime()));

  10. for (int n = 0; n < TEST_SIZE; n++)

  11. i = System.currentTimeMillis();

  12. System.out.println(-time + (time = System.nanoTime()));

  13. for (int n = 0; n < TEST_SIZE; n++)

  14. synchronized (PerformanceTest.class) {

  15. }

  16. System.out.println(-time + (time = System.nanoTime()));

  17. for (int n = 0; n < TEST_SIZE; n++)

  18. vt++;

  19. System.out.println(-time + (time = System.nanoTime()));

  20. for (int n = 0; n < TEST_SIZE; n++)

  21. vt = i;

  22. System.out.println(-time + (time = System.nanoTime()));

  23. for (int n = 0; n < TEST_SIZE; n++)

  24. i = vt;

  25. System.out.println(-time + (time = System.nanoTime()));

  26. for (int n = 0; n < TEST_SIZE; n++)

  27. i++;

  28. System.out.println(-time + (time = System.nanoTime()));

  29. for (int n = 0; n < TEST_SIZE; n++)

  30. i = n;

  31. System.out.println(-time + (time = System.nanoTime()));

  32. }

  33. }

复制代码

测试一千万次,结果是(耗时单位:纳秒,包含循环本身的时间):
238932949       volatile写+取系统时间
144317590       普通写+取系统时间
135596135       空的同步块(synchronized)
80042382        volatile变量自增
15875140        volatile写
6548994         volatile读
2722555         普通自增
2949571         普通读写

从上面的数据看来,volatile写+取系统时间的耗时是很高的,取系统时间的耗时也比较高,跟一次无竞争的同步差不多了,接下来分析下如何优化该超时时机。

首先:同步问题是肯定得考虑的,因为有跨线程的数据操作;另外,取系统时间的操作比较耗时,能否不在每次刷新时都取时间?因为刷新调用在高负载的情况下很频繁。如果不在刷新时取时间,那又该怎么去判定超时?

我想到的办法是,在refresh方法里,仅设置一个volatile的boolean变量reset(这应该是成本最小的了吧,因为要处理同步问题,要么同步块,要么volatile,而volatile读在此处是没什么意义的),对时间的掌控交给定时器来做,并为每个连接维护一个计数器,每次加一,如果reset被设置为true了,则计数器归零,并将reset设为false(因为计数器只由定时器维护,所以不需要做同步处理,从上面的测试数据来看,普通变量的操作,时间成本是很低的),如果计数器超过某个值,则判定超时。 下面给出具体的代码:

  1. public class Connection {

  2. int count = 0;

  3. volatile boolean reset = false;

  4. public void refresh() {

  5. if (reset == false)

  6. reset = true;

  7. }

  8. }

  9. public class TimeoutTask extends TimerTask {

  10. public void run() {

  11. for (Connection c : connections) {

  12. if (c.reset) {

  13. c.reset = false;

  14. c.count = 0;

  15. } else if (++c.count >= TIMEOUT_COUNT)

  16. ;// timeout, do something

  17. }

  18. }

  19. }

复制代码

代码中的TIMEOUT_COUNT 等于超时时间除以定时器的周期,周期大小既影响定时器的执行频率,也会影响实际超时时间的波动范围(这个波动,第一个方案也存在,也不太可能避免,并且也不需要多么精确)。

代码很简洁,下面来分析一下。

reset加上了volatile,所以保证了多线程操作的可见性,虽然有两个线程都对变量有写操作,但无论这两个线程怎么穿插执行,都不会影响其逻辑含义。

再说下refresh方法,为什么我在赋值语句上多加了个条件?这不是多了一次volatile读操作吗?我是这么考虑的,高负载下,refresh会被频繁调用,意味着reset长时间为true,那么加上条件后,就不会执行写操作了,只有一次读操作,从上面的测试数据来看,volatile变量的读操作的性能是显著优于写操作的。只不过在reset为false的时候,多了一次读操作,但此情况在定时器的一个周期内最多只会发一次,而且对高负载情况下的优化显然更有意义,所以我认为加上条件还是值得的。

最后提及一下,我有点完美主义,自认为上面的方案在我当前掌握的知识下,已经很漂亮了,如果你发现还有可优化的地方,或更好的方案,希望能分享。
————————————-
补充一下:一般情况下,也可用特定的心跳包来刷新,而不是每次收到消息都刷新,这样一来,刷新频率就很低了,也就没必要太在乎性能开销。

原创文章,转载请注明: 转载自并发编程网 – ifeve.com

转载于:https://blog.51cto.com/melorogee/1643753

【您还有心跳吗?超时机制分析 】相关推荐

  1. java netty swap高_Netty 超时机制及心跳程序实现

    本文介绍了 Netty 超时机制的原理,以及如何在连接闲置时发送一个心跳来维持连接. Netty 超时机制的介绍 Netty 的超时类型 IdleState 主要分为: ALL_IDLE : 一段时间 ...

  2. Netty 超时机制及心跳程序实现

    本文介绍了 Netty 超时机制的原理,以及如何在连接闲置时发送一个心跳来维持连接. Netty 超时机制的介绍 Netty 的超时类型 IdleState 主要分为: ALL_IDLE : 一段时间 ...

  3. 数仓集群管理:单节点故障RTO机制分析

    摘要:大规模分布式系统中的故障无法避免.发生单点故障时,集群状态和业务是如何恢复的? 本文分享自华为云社区<GaussDB (DWS) 集群管理系列:单节点故障RTO机制分析(集群状态恢复篇)& ...

  4. dubbo的超时机制和重试机制

    参考: https://www.cnblogs.com/ASPNET2008/p/7292472.html https://www.tuicool.com/articles/YfA3Ub https: ...

  5. Socket连接心跳包的机制总结

    文章目录 Socket连接心跳包的机制总结 心跳包的由来 心跳包的作用 心跳包由服务端还是客户端发送? Socket连接心跳包的机制总结 心跳包的由来 心跳包之所以叫心跳包是因为:它像心跳一样每隔固定 ...

  6. Android 系统(177)---Android消息机制分析:Handler、Looper、MessageQueue源码分析

    Android消息机制分析:Handler.Looper.MessageQueue源码分析 1.前言 关于Handler消息机制的博客实际上是非常多的了. 之前也是看别人的博客过来的,但是过了一段时间 ...

  7. 心跳与超时:高并发高性能的时间轮超时器

    在许多业务场景中,我们都会碰到延迟任务,定时任务这种需求.特别的,在网络连接的场景中,常常会出现一些超时控制.由于服务端的连接数量很大,这些超时任务的数量往往也是很庞大的.实现对大量任务的超时管理并不 ...

  8. java deadlock oracle_APPARENT DEADLOCK!!! - C3P0连接池DeadLock机制分析

    1 问题 近期,刚上线不久的生产系统的数据库连接池 C3P0 (版本为0.9.5.2)突然报出 APPARENT DEADLOCK!!! 错误. 1.1 错误日志 错误日志如下. com.mchang ...

  9. Dubbo基本原理与超时机制

    一. dubbo基本原理 –高性能和透明化的RPC远程服务调用方案 –SOA服务治理方案 -Apache MINA 框架基于Reactor模型通信框架,基于tcp长连接 Dubbo缺省协议采用单一长连 ...

  10. Servlet过滤器机制分析及应用

    Servlet过滤器机制分析及应用 李德水 (渭南师范学院计算机科学系,陕西渭南714000) 摘要:Servlet过滤器是可重用的Web组件,是一种类似于Servlet由容器管理的对象,能够以声明的 ...

最新文章

  1. RecycleView中使用Glide加载图片防止加载错乱
  2. 两分钟用C#搭建IE BHO勾子, 窃取密码
  3. 机器人纹身师出世,你敢让它帮你纹身吗?
  4. xgb多线程成功运行记录
  5. CSTrackV2解读
  6. [Amaze UI] 如何推进 mobile first 的前端 Web 方案
  7. 「软件项目管理」软件项目范围计划——需求管理与任务分解
  8. 洛谷P2286 [HNOI2004]宠物收养场
  9. 改造一下jeecg中的部门树
  10. From AlphaGo Zero to 2048论文分享
  11. mysql数据操作-数据库的定义-DLL
  12. 图森未来C轮融资5500万美元,新浪继续跟投,无人卡车公开试乘
  13. Win7升Windows10有获取通知,但是就不推送的解决方法
  14. 算法第四版_第二章_练习题_2.1.1~2.1.12
  15. 大一计算机引论知识点,计算机引论知识点2013-1-6.doc
  16. Linux文件权限与目录配置
  17. 为什么阿里,腾讯,百度和京东都是在开曼岛注册的?
  18. 网页播放器实现全屏的方法总结
  19. MWC就快到来!5G、物联网应用以及折迭手机为本次焦点
  20. 2021乌镇互联网大会,数字联盟解读网络安全与信息保护

热门文章

  1. Oracle中INSTR函数,及在DB2、Sybase中与Instr函数功能相同的函数
  2. 装饰者模式(C#)实现
  3. 功能强大的Server.Transfer
  4. WinForm------GridControl合并单元格
  5. int indexs=(int)(_v+0.5);
  6. 看到这个密码,我就笑了
  7. 提速30倍!这个加速包让Python代码飞起来
  8. Lesson 07 for Plotting in R for Biologists
  9. 老单位领导直属领导有恩,新公司薪水给的高,怎么选?
  10. 防火墙之iptables