这次继续上一篇说到的心跳检测
我们使用Socket通信一般经常会处理多个服务器之间的心跳检测,一般来讲我们去维护服务器集群,肯定要有一台或者N台服务器主机(Master),然后还应该有N台(Slave),那么我们的主机肯定要时时刻刻知道自己下面的从服务器的各方面情况,然后进行实时监控的功能,这个在分布式架构里叫做心跳检测或者是心跳监控。最佳处理方案还是觉得是使用一些通信框架进行实现,我们的Netty就可以去做这样一件事情。
过程是这样的,从服务器Slave先发送ip+key给主服务器Master进行认证,Master会有属于自己管理的slave的信息(存在缓存或者数据库也好),将Slave发送过来的认证信息进行认证(比对ip和key是否是存在且正确的),如果认证成功Master会返回认证成功的信息给Slave,当Slave接受到认证成功的消息时,就会开始启动定时线程去定时发送心跳包给Master。现在,我们暂时发送的心跳包的数据为Slave的cpu信息和内存信息。那么服务器的cpu信息和内存信息怎么获取到呢,我们可以使用Sigar。
Sigar的介绍与使用:
详细的就不说了,下面的两篇博文写得挺好的。了解一下就ok。
https://www.cnblogs.com/luoruiyuan/p/5603771.html
https://www.cnblogs.com/javahr/p/8284198.html
最需要注意的是:Sigar为不同平台提供了不同的库文件,我们记得需要将库文件放到jdk安装路径的bin文件夹下。
下面是解压后的东东,那两个包拉到自己的项目中,然后我的系统是windows64位,所以将sigar-amd64-winnt.dll放到jdk安装路径的bin文件夹下。
现在上代码吧。服务器Server必做是主服务器Master,客户端Client比作是从服务器Slave。
服务器Server:
public class Server {
public static void main(String[] args) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap sb = new ServerBootstrap();
sb.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//添加Marshalling的编解码器
ch.pipeline().addLast(MarshallingCodeFactory.buildMarshallingEncoder());
ch.pipeline().addLast(MarshallingCodeFactory.buildMarshallingDecoder());
ch.pipeline().addLast(new ServerHeartBeatHandler());
}
});
ChannelFuture cf = sb.bind(8765).sync();
cf.channel().closeFuture().sync();
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
服务器Handler:
public class ServerHeartBeatHandler extends ChannelHandlerAdapter{
/**
* 将从服务器的信息放在这里,正常应该是从数据库拿或者从缓存拿
*/
private static Map<String,String> AUTH_IP_MAP = new HashMap<String, String>();
/**
* 认证成功返回给slave的key,一般是slave传过来的key的再加密后的东西,但是这里简单处理了
*/
private static final String SUCCESS_KEY = "auth_success_key";
static{
/**
* 假设有一台slave ip:169.254.138.182 key:1234
*/
AUTH_IP_MAP.put("169.254.138.182", "1234"); //slave的ip+key
}
private void auth(ChannelHandlerContext cx,Object msg){
String[] result = ((String)msg).split(",");
String authKey = AUTH_IP_MAP.get(result[0]);
if(authKey !=null && authKey.equals(result[1])){
cx.writeAndFlush(SUCCESS_KEY); //认证成功给slave返回信息
}else{
cx.writeAndFlush("auth failure!").addListener(ChannelFutureListener.CLOSE); //认证不成功就返回失败信息并关闭通道
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof String){ //如果是字符串类型为认证
auth(ctx,msg);
}else if(msg instanceof RequestInfo){ //心跳包,将数据读出来
System.out.println("心跳");
RequestInfo info = (RequestInfo)msg;
System.out.println("--------------------------------------");
System.out.println("当前主机ip为:"+info.getIp());
System.out.println("当前主机CPU的情况:");
Map<String,Object> cpuPercMap = info.getCpuPercMap();
System.out.println("总使用率:"+cpuPercMap.get("combined"));
System.out.println("用户使用率:"+cpuPercMap.get("user"));
System.out.println("系统使用率:"+cpuPercMap.get("sys"));
System.out.println("等待率:"+cpuPercMap.get("wait"));
System.out.println("空闲率"+cpuPercMap.get("idle"));
System.out.println("当前主机内存的情况:");
Map<String,Object> memMap = info.getMemoryMap();
System.out.println("内存总量:"+memMap.get("total"));
System.out.println("当前内存使用量:"+memMap.get("used"));
System.out.println("当前内存剩余量:"+memMap.get("free"));
System.out.println("--------------------------------------");
ctx.writeAndFlush("info received!");
}else{
ctx.writeAndFlush("connect failure").addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端Client:
public class Client {
public static void main(String[] args) throws Exception {
EventLoopGroup workGroup = new NioEventLoopGroup();
Bootstrap bs = new Bootstrap();
bs.group(workGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(MarshallingCodeFactory.buildMarshallingEncoder());
ch.pipeline().addLast(MarshallingCodeFactory.buildMarshallingDecoder());
ch.pipeline().addLast(new ClientHeartBeatHandler());
}
});
ChannelFuture ch = bs.connect("127.0.0.1", 8765).sync();
ch.channel().closeFuture().sync();
workGroup.shutdownGracefully();
}
}
客户端Handler:
public class ClientHeartBeatHandler extends ChannelHandlerAdapter{
private ScheduledExecutorService pool = Executors.newScheduledThreadPool(1); //定时线程池,做心跳监控用
private ScheduledFuture<?> heartBeat;
private InetAddress addr;
private static final String SUCCESS_KEY = "auth_success_key"; //认证成功的key,slave和master一定要约定好
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//通道打开活跃时,进行认证
System.out.println("进行认证");
addr = InetAddress.getLocalHost();
String ip = addr.getHostAddress();
System.out.println(ip);
String key = "1234";
String auth = ip+","+key;
ctx.writeAndFlush(auth);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try{
/**
* 接收master返回的认证信息
*/
if(msg instanceof String){
String result = (String)msg;
if(SUCCESS_KEY.equals(result)){ //如果认证成功了,主动发送心跳数据
/**
* 第一个参数:执行的实现Runnable接口的任务
* 第二个参数:初始化延迟时间
* 第三个参数:每次执行任务的间隔
* 第四个参数:时间间隔的单位
*/
this.heartBeat = this.pool.scheduleWithFixedDelay(new HeartBeatTask(ctx), 2, 5, TimeUnit.SECONDS);
System.out.println(msg);
}else{
System.out.println(msg);
}
}
}finally{
ReferenceCountUtil.release(msg);
}
}
private class HeartBeatTask implements Runnable{
private final ChannelHandlerContext ctx;
public HeartBeatTask(final ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void run() { //往master发数据
try {
RequestInfo info = new RequestInfo();
//ip
info.setIp(addr.getHostAddress());
//cpu prec
Map<String,Object> cpuPercMap = new HashMap<String,Object>();
Sigar sigar = new Sigar();
CpuPerc cpuPerc = sigar.getCpuPerc();
cpuPercMap.put("combined", cpuPerc.getCombined());
cpuPercMap.put("user",cpuPerc.getUser());
cpuPercMap.put("sys",cpuPerc.getSys());
cpuPercMap.put("wait",cpuPerc.getWait());
cpuPercMap.put("idel",cpuPerc.getIdle());
//memory
Map<String,Object> memoryMap = new HashMap<String,Object>();
Mem mem = sigar.getMem();
memoryMap.put("total", mem.getTotal() / 1024L); //原本数据的单位为bit,现在转为k
memoryMap.put("used", mem.getUsed() / 1024L);
memoryMap.put("free", mem.getFree() / 1024L);
info.setCpuPercMap(cpuPercMap);
info.setMemoryMap(memoryMap);
ctx.writeAndFlush(info);
} catch (SigarException e) {
e.printStackTrace();
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
Marshalling工厂就不放出来了,上上篇有。
心跳数据实体:因为传送的心跳数据是实体,所以请一定记得实现Serializable接口!!!!!不然代码对了都传送不了。
public class RequestInfo implements Serializable{
private static final long serialVersionUID = 1L;
private String ip;
private Map<String,Object> cpuPercMap; //cpu数据
private Map<String,Object> memoryMap; //内存数据
//... other field
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Map<String, Object> getCpuPercMap() {
return cpuPercMap;
}
public void setCpuPercMap(Map<String, Object> cpuPercMap) {
this.cpuPercMap = cpuPercMap;
}
public Map<String, Object> getMemoryMap() {
return memoryMap;
}
public void setMemoryMap(Map<String, Object> memoryMap) {
this.memoryMap = memoryMap;
}
}
测试:这里是测试成功的结果,如果不想成功,随便弄一个ip或者key不相同就好了,服务器会发送cnnect failure,然后断开与客户端连接的通道。
服务器:
17:42:29] nioEventLoopGroup-0-0 INFO [] [] [io.netty.handler.logging.LoggingHandler] - [id: 0x122e050e] REGISTERED
[17:42:29] nioEventLoopGroup-0-0 INFO [] [] [io.netty.handler.logging.LoggingHandler] - [id: 0x122e050e] BIND: 0.0.0.0/0.0.0.0:8765
[17:42:29] nioEventLoopGroup-0-0 INFO [] [] [io.netty.handler.logging.LoggingHandler] - [id: 0x122e050e, /0:0:0:0:0:0:0:0:8765] ACTIVE
[17:42:33] nioEventLoopGroup-0-0 INFO [] [] [io.netty.handler.logging.LoggingHandler] - [id: 0x122e050e, /0:0:0:0:0:0:0:0:8765] RECEIVED: [id: 0x40e3e2d4, /127.0.0.1:56210 => /127.0.0.1:8765]
心跳
--------------------------------------
当前主机ip为:169.254.138.182
当前主机CPU的情况:
总使用率:0.07003501750875438
用户使用率:0.02351175587793897
系统使用率:0.04652326163081541
等待率:0.0
空闲率null
当前主机内存的情况:
内存总量:12455076
当前内存使用量:6589636
当前内存剩余量:5865440
--------------------------------------
心跳
--------------------------------------
当前主机ip为:169.254.138.182
当前主机CPU的情况:
总使用率:0.07003501750875438
用户使用率:0.04652326163081541
系统使用率:0.02351175587793897
等待率:0.0
空闲率null
当前主机内存的情况:
内存总量:12455076
当前内存使用量:6585724
当前内存剩余量:5869352
--------------------------------------
心跳
--------------------------------------
当前主机ip为:169.254.138.182
当前主机CPU的情况:
总使用率:0.14092953523238383
用户使用率:0.062468765617191405
系统使用率:0.07846076961519241
等待率:0.0
空闲率null
当前主机内存的情况:
内存总量:12455076
当前内存使用量:6584932
当前内存剩余量:5870144
--------------------------------------
java.io.IOException: 远程主机强迫关闭了一个现有的连接。
客户端:
进行认证
169.254.138.182
auth_success_key
info received!
info received!
info received!
小结:可以看到,客户端一直在执行定时任务给服务端发送心跳数据,服务器在接受到心跳数据后也给客户端写回响应了,就这么一直循环下去。最后是我直接断开客户端连接才停止了心跳检测。