一、心跳机制简介

在分布式系统中,分布在不同主机上的节点需要检测其他节点的状态,如服务器节点需要检测从节点是否失效。为了检测对方节点的有效性,每隔固定时间就发送一个固定信息给对方,对方回复一个固定信息,如果长时间没有收到对方的回复,则断开与对方的连接。

发包方既可以是服务端,也可以是客户端,这要看具体实现。因为是每隔固定时间发送一次,类似心跳,所以发送的固定信息称为心跳包。心跳包一般为比较小的包,可根据具体实现。心跳包主要应用于长连接的保持与短线链接。

一般而言,应该客户端主动向服务器发送心跳包,因为服务器向客户端发送心跳包会影响服务器的性能。

二、心跳机制实现方式

心跳机制有两种实现方式,一种基于TCP自带的心跳包,TCP的SO_KEEPALIVE选项可以,系统默认的默认跳帧频率为2小时,超过2小时后,本地的TCP 实现会发送一个数据包给远程的 Socket. 如果远程Socket 没有发回响应, TCP实现就会持续尝试 11 分钟, 直到接收到响应为止。 否则就会自动断开Socket连接。但TCP自带的心跳包无法检测比较敏感地知道对方的状态,默认2小时的空闲时间,对于大多数的应用而言太长了。可以手工开启KeepAlive功能并设置合理的KeepAlive参数。

另一种在应用层自己进行实现,基本步骤如下:

  1. Client使用定时器,不断发送心跳;
  2. Server收到心跳后,回复一个包;
  3. Server为每个Client启动超时定时器,如果在指定时间内没有收到Client的心跳包,则Client失效。

三、Java实现心跳机制

这里基于Java实现的简单RPC框架实现心跳机制。Java实现代码如下所示:

心跳客户端类:

public class HeartbeatClient implements Runnable {

    private String serverIP = "127.0.0.1";
private int serverPort = 8089;
private String nodeID = UUID.randomUUID().toString();
private boolean isRunning = true;
// 最近的心跳时间
private long lastHeartbeat;
// 心跳间隔时间
private long heartBeatInterval = 10 * 1000; public void run() {
try {
while (isRunning) {
HeartbeatHandler handler = RPClient.getRemoteProxyObj(HeartbeatHandler.class, new InetSocketAddress(serverIP, serverPort));
long startTime = System.currentTimeMillis();
// 是否达到发送心跳的周期时间
if (startTime - lastHeartbeat > heartBeatInterval) {
System.out.println("send a heart beat");
lastHeartbeat = startTime; HeartbeatEntity entity = new HeartbeatEntity();
entity.setTime(startTime);
entity.setNodeID(nodeID); // 向服务器发送心跳,并返回需要执行的命令
Cmder cmds = handler.sendHeartBeat(entity); if (!processCommand(cmds))
continue;
}
}
} catch (Exception e) {
e.printStackTrace();
}
} private boolean processCommand(Cmder cmds) {
// ...
return true;
} }

心跳包实体类:

public class HeartbeatEntity implements Serializable {

    private long time;
private String nodeID;
private String error;
private Map<String, Object> info = new HashMap<String, Object>(); public String getNodeID() {
return nodeID;
} public void setNodeID(String nodeID) {
this.nodeID = nodeID;
} public String getError() {
return error;
} public void setError(String error) {
this.error = error;
} public Map<String, Object> getInfo() {
return info;
} public void setInfo(Map<String, Object> info) {
this.info = info;
} public long getTime() {
return time;
} public void setTime(long time) {
this.time = time;
}
}

  服务器接受心跳包返回的命令对象类:

public class Cmder implements Serializable {

    private String nodeID;
private String error;
private Map<String, Object> info = new HashMap<String, Object>(); public String getNodeID() {
return nodeID;
} public void setNodeID(String nodeID) {
this.nodeID = nodeID;
} public String getError() {
return error;
} public void setError(String error) {
this.error = error;
} public Map<String, Object> getInfo() {
return info;
} public void setInfo(Map<String, Object> info) {
this.info = info;
}
}

  RPC服务注册中心:

public class ServiceCenter {

    private ExecutorService executor = Executors.newFixedThreadPool(20);

    private final ConcurrentHashMap<String, Class> serviceRegistry = new ConcurrentHashMap<String, Class>();

    private AtomicBoolean isRunning = new AtomicBoolean(true);

    // 服务器监听端口
private int port = 8089; // 心跳监听器
HeartbeatLinstener linstener; // 单例模式
private static class SingleHolder {
private static final ServiceCenter INSTANCE = new ServiceCenter();
} private ServiceCenter() {
} public static ServiceCenter getInstance() {
return SingleHolder.INSTANCE;
} public void register(Class serviceInterface, Class impl) {
System.out.println("regeist service " + serviceInterface.getName());
serviceRegistry.put(serviceInterface.getName(), impl);
} public void start() throws IOException {
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(port));
System.out.println("start server");
linstener = HeartbeatLinstener.getInstance();
System.out.println("start listen heart beat");
try {
while (true) {
// 1.监听客户端的TCP连接,接到TCP连接后将其封装成task,由线程池执行
executor.execute(new ServiceTask(server.accept()));
}
} finally {
server.close();
}
} public void stop() {
isRunning.set(false);
executor.shutdown();
} public boolean isRunning() {
return isRunning.get();
} public int getPort() {
return port;
} public void settPort(int port) {
this.port = port;
} public ConcurrentHashMap<String, Class> getServiceRegistry() {
return serviceRegistry;
} private class ServiceTask implements Runnable {
Socket clent = null; public ServiceTask(Socket client) {
this.clent = client;
} public void run() {
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
// 2.将客户端发送的码流反序列化成对象,反射调用服务实现者,获取执行结果
input = new ObjectInputStream(clent.getInputStream());
String serviceName = input.readUTF();
String methodName = input.readUTF();
Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
Object[] arguments = (Object[]) input.readObject();
Class serviceClass = serviceRegistry.get(serviceName);
if (serviceClass == null) {
throw new ClassNotFoundException(serviceName + " not found");
}
Method method = serviceClass.getMethod(methodName, parameterTypes);
Object result = method.invoke(serviceClass.newInstance(), arguments); // 3.将执行结果反序列化,通过socket发送给客户端
output = new ObjectOutputStream(clent.getOutputStream());
output.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (output != null) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (clent != null) {
try {
clent.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }
}
}

  心跳监听类:

package com.cang.heartbeat;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean; /**
* 心跳监听保存信息
*
* @author cang
* @create_time 2016-09-28 11:40
*/
public class HeartbeatLinstener { private ExecutorService executor = Executors.newFixedThreadPool(20); private final ConcurrentHashMap<String, Object> nodes = new ConcurrentHashMap<String, Object>();
private final ConcurrentHashMap<String, Long> nodeStatus = new ConcurrentHashMap<String, Long>(); private long timeout = 10 * 1000; // 服务器监听端口
private int port = 8089; // 单例模式
private static class SingleHolder {
private static final HeartbeatLinstener INSTANCE = new HeartbeatLinstener();
} private HeartbeatLinstener() {
} public static HeartbeatLinstener getInstance() {
return SingleHolder.INSTANCE;
} public ConcurrentHashMap<String, Object> getNodes() {
return nodes;
} public void registerNode(String nodeId, Object nodeInfo) {
nodes.put(nodeId, nodeInfo);
nodeStatus.put(nodeId, System.currentTimeMillis());
} public void removeNode(String nodeID) {
if (nodes.containsKey(nodeID)) {
nodes.remove(nodeID);
}
} // 检测节点是否有效
public boolean checkNodeValid(String key) {
if (!nodes.containsKey(key) || !nodeStatus.containsKey(key)) return false;
if ((System.currentTimeMillis() - nodeStatus.get(key)) > timeout) return false;
return true;
} // 删除所有失效节点
public void removeInValidNode() {
Iterator<Map.Entry<String, Long>> it = nodeStatus.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Long> e = it.next();
if ((System.currentTimeMillis() - nodeStatus.get(e.getKey())) > timeout) {
nodes.remove(e.getKey());
}
}
} }

  心跳处理类接口:

public interface HeartbeatHandler {
public Cmder sendHeartBeat(HeartbeatEntity info);
}

心跳处理实现类:

public class HeartbeatHandlerImpl implements HeartbeatHandler {
public Cmder sendHeartBeat(HeartbeatEntity info) {
HeartbeatLinstener linstener = HeartbeatLinstener.getInstance(); // 添加节点
if (!linstener.checkNodeValid(info.getNodeID())) {
linstener.registerNode(info.getNodeID(), info);
} // 其他操作
Cmder cmder = new Cmder();
cmder.setNodeID(info.getNodeID());
// ... System.out.println("current all the nodes: ");
Map<String, Object> nodes = linstener.getNodes();
for (Map.Entry e : nodes.entrySet()) {
System.out.println(e.getKey() + " : " + e.getValue());
}
System.out.println("hadle a heartbeat");
return cmder;
}
}

  测试类:

public class HeartbeatTest {

    public static void main(String[] args) {
new Thread(new Runnable() {
public void run() {
try {
ServiceCenter serviceServer = ServiceCenter.getInstance();
serviceServer.register(HeartbeatHandler.class, HeartbeatHandlerImpl.class);
serviceServer.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
Thread client1 = new Thread(new HeartbeatClient());
client1.start();
Thread client2 = new Thread(new HeartbeatClient());
client2.start();
}
}

四、总结

上面的代码还有很多不足的地方,希望有空能进行改善:

  1. 配置为硬编码;
  2. 命令类Cmder没有实际实现,返回的Cmder对象没有实际进行处理;

其他小问题就暂时不管了,希望以后能重写上面的代码。

Java实现心跳机制的更多相关文章

  1. Java: server/client 心跳机制实现 示例

    心跳机制 心跳机制是定时发送一个自定义的结构体(心跳包),让对方知道自己还活着,以确保连接的有效性的机制. 大部分CS的应用需要心跳机制.心跳机制一般在Server和Client都要实现,两者实现原理 ...

  2. java 心跳机制

    心跳机制:就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开. 心跳包 心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定 ...

  3. netty心跳机制测试

    netty中有比较完善的心跳机制,(在基础server版本基础上[netty基础--基本收发])添加少量代码即可实现对心跳的监测和处理. 1 server端channel中加入心跳处理机制 // Id ...

  4. Spark RPC框架源码分析(三)Spark心跳机制分析

    一.Spark心跳概述 前面两节中介绍了Spark RPC的基本知识,以及深入剖析了Spark RPC中一些源码的实现流程. 具体可以看这里: Spark RPC框架源码分析(二)运行时序 Spark ...

  5. Netty(一) SpringBoot 整合长连接心跳机制

    前言 Netty 是一个高性能的 NIO 网络框架,本文基于 SpringBoot 以常见的心跳机制来认识 Netty. 最终能达到的效果: 客户端每隔 N 秒检测是否需要发送心跳. 服务端也每隔 N ...

  6. 基于netty实现的长连接,心跳机制及重连机制

    技术:maven3.0.5 + netty4.1.33 + jdk1.8   概述 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速 ...

  7. Netty心跳机制

    一.概念介绍网络中的接收和发送数据都是使用操作系统中的SOCKET进行实现.但是如果此套接字已经断开,那发送数据和接收数据的时候就一定会有问题.可是如何判断这个套接字是否还可以使用呢?这个就需要在系统 ...

  8. netty之心跳机制

    1.心跳机制,在netty3和netty5上面都有.但是写法有些不一样. 2.心跳机制在服务端和客户端的作用也是不一样的.对于服务端来说:就是定时清除那些因为某种原因在一定时间段内没有做指定操作的客户 ...

  9. Hadoop心跳机制源码分析

    正文: 一.体系背景 首先和大家说明一下:hadoop的心跳机制的底层是通过RPC机制实现的,这篇文章我只介绍心跳实现的代码,对于底层的具体实现,大家可以参考我的另几篇博客: 1. hadoop的RP ...

随机推荐

  1. 【源码】rm zip 删除文件夹中大量的小文件 百万 扫描文件时间

    rm  删除文件夹中大量的小文件 百万 迟迟未删除 在扫描文件? rm删除命令源码分析 - ty_laurel的博客 - CSDN博客 https://blog.csdn.net/ty_laurel/ ...

  2. windows7系统下让所有文件夹都使用同一种视图的方法

    Windows7系统可以对每个文件夹进行个性化视图设置,可以根据自己的个人喜好和实际需要更改文件或文件夹图标的大小,或者让文件或文件夹以列 表.平铺等方式显示.但是,如果你对N个文件夹视图进行了风格各 ...

  3. 前端(以Vue为例)webpack打包后dist文件包如何部署到django后台中

    由于现在前端使用的三大框架配合webpack可以实现快速打包,为部署到服务端提供了非常大的便利,那么在前端打包后,应该做些什么可以部署到django的后台中呢? 1.打包后文件包dist 进入到 di ...

  4. excel用法

    1:求大于某一值的个数:使用COUNTIF(区间,标准)    要大写 =COUNTIF(B2:B48,">=95") 2:求某一区间的个数用:大区间个数减小区间个数 =CO ...

  5. 包管理 ----- Linux操作系统rpm包安装方式步骤

    Linux操作系统rpm包安装方式步骤 2016年08月04日 07:00:26 阅读数:17140 转自 : http://os.51cto.com/art/201003/186467.htm 特别 ...

  6. python-字符串前面添加u,r,b的含义

    u/U:表示unicode字符串 不是仅仅是针对中文, 可以针对任何的字符串,代表是对字符串进行unicode编码. 一般英文字符在使用各种编码下, 基本都可以正常解析, 所以一般不带u:但是中文, ...

  7. apply、map、applymap、Dropna

    DataFrame常用易混淆方法 apply && map && applymap 1.apply():作用在一维的向量上时,可以使用apply来完成,如下所示 2.m ...

  8. SpringMVC学习笔记二第一个小的程序

    首先:我们要用springmvc来写一个helloworld的例子: 首先我们需要导入所需要的架包: /demo1/WebRoot/WEB-INF/lib/commons-logging-1.1.1. ...

  9. 关于 WebBrowser调用百度地图API 鼠标滚轮缩放地图级别失灵的解决办法

    在桌面程序下 百度地图API的鼠标缩放地图功能可能会失灵无效! 这个原因不是API的问题 小弟试了下在WEB上面是没有问题的 于是考虑可能是WebBrowser的获取焦点问题,于是在主窗体 添加了一个 ...

  10. c语言的字符串拷贝函数的精简

    #include <stdio.h>#include <string.h>void str_cpy(char * to, char *from){    while ((*to ...