一、心跳机制简介

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

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

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

二、心跳机制实现方式

心跳机制有两种实现方式,一种基于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. 97.394570112228 - Query OK, 1 row affected (43.05 sec) - the overhead of parsing and network communication

    mysql> create table w0904procedure (wa char, wb char, wd char, wi char); Query OK, rows affected ...

  2. php Only variables can be passed by reference

    最近做项目,发现了一个报错  Only variables can be passed by reference,  意思是"只有变量能通过'引用'" 就是在代码中 使用了一个方法 ...

  3. javaweb使用 window.location.href 传中文参数 乱码问题

    JS: var cn_name=  document.getElementById("cn_name"); window.location.href="${URL}?na ...

  4. TCP报文

    源端口和目的端口: 各占16位 ,服务相对应的源端口和目的端口. 序列号: 占32位,它的范围在[0~2^32-1],序号随着通信的进行不断的递增,当达到最大值的时候重新回到0在开始递增.TCP是面向 ...

  5. (3.11)mysql基础深入——mysql文件分类与配置文件管理

    (3.11)mysql基础深入——mysql文件分类与管理 关键词:mysql配置文件,mysql参数文件,mysql中的my.cnf 目录:mysql数据库文件分类: [1]参数文件:my.cnf ...

  6. sql server 存储机制

    1.区段 区段(extent)是用来为表和索引分配空间的基本存储单元.它由8个连续的64KB数据页组成. 基于区段(而不是实际使用空间)分配空间的概念的要点: 一旦区段已满,那么下一记录将要占据的空间 ...

  7. shell_exec() has been disabled for security reasons错误怎么解决?

    ytkah在用composer安装插件时出现了shell_exec() has been disabled for security reasons错误提示,这个是php配置的问题,shell_exe ...

  8. HTML方法

    HTTP 方法:GET 对比 POST 两种最常用的 HTTP 方法是:GET 和 POST. 什么是 HTTP ? 超文本传输协议(HTTP)的设计目的是保证客户端与服务器之间的通信. HTTP 的 ...

  9. windows server r2 搭建 ftp服务器

    1:安装ftp服务器 开始>管理工具>服务器管理器>打开服务器管理器,找到添加角色,然后点击,弹出添加角色对话框,选择下一步>选择Web服务器(IIS),然后选择FTP服务,直 ...

  10. [LeetCode] 121. Best Time to Buy and Sell Stock_Easy tag: Dynamic Programming

    Say you have an array for which the ith element is the price of a given stock on day i. If you were ...