实现分布式服务注册及简易的netty聊天
现在很多地方都会用到zookeeper, 用到它的地方就是为了实现分布式。用到的场景就是服务注册,比如一个集群服务器,需要知道哪些服务器在线,哪些服务器不在线。
ZK有一个功能,就是创建临时节点,当机器启动应用的时候就会连接到一个ZK节点,然后创建一个临时节点,那么通过获取监听该路径,并且获取该路径下的节点数量就知道有哪些服务器在线了。当机器停止应用的时候,zk的临时节点将会自动被删除。我们通过这个机制去实现。
这次主要实现是采用springboot, zkui, swagger实现。接下来来看一下主要的代码实现:
在机器启动的时候获取本机的IP,然后将本机的IP和指定的端口号注册到程序中:
package com.hqs.zk.register; import com.hqs.zk.register.config.AppConfig;
import com.hqs.zk.register.thread.ZKRegister;
import com.hqs.zk.register.util.ZKUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import java.net.InetAddress; @SpringBootApplication
public class ZKApplication implements CommandLineRunner{
@Autowired
AppConfig appConfig; @Autowired
ZKUtil zkUtil; public static void main(String[] args) {
SpringApplication.run(ZKApplication.class, args);
System.out.println("启动应用成功");
} @Override
public void run(String... strings) throws Exception {
//获得本机IP
String addr = InetAddress.getLocalHost().getHostAddress();
Thread thread = new Thread(new ZKRegister(appConfig, zkUtil, addr));
thread.setName("register-thread");
thread.start(); Thread scanner = new Thread(new Scanner());
scanner.start();
}
}
创建一个工具类,工具类主要实现创建父节点,创建临时路径,监听事件,获取所有注册节点。
/**
* 创建临时目录
*/
public void createEphemeralNode(String path, String value) {
zkClient.createEphemeral(path, value);
} /**
* 监听事件
*/
public void subscribeEvent(String path) {
zkClient.subscribeChildChanges(path, new IZkChildListener() {
@Override
public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
System.out.println("parentPath:" + parentPath + ":list:" + currentChilds.toString());
}
});
}
这块就基本完成了,下面开始创建controller,目的是为了获取所有在线机器的节点。为了方便测试和查看我使用了Swagger2, 这样界面话的发请求工具特别好用。
接下来看controller的主要内容:
/**
* 获取所有路由节点
* @return
*/
@ApiOperation("获取所有路由节点")
@RequestMapping(value = "getAllRoute",method = RequestMethod.POST)
@ResponseBody()
public List<String> getAllRoute(){
List<String> allNode = zkUtil.getAllNode();
List<String> result = new ArrayList<>();
for (String node : allNode) {
String key = node.split("-")[1];
result.add(key);
}
return result ;
}
同时配置对应的Swagger2
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2; /**
* Created by huangqingshi on 2019/1/8.
*/
@Configuration
@EnableSwagger2
public class SwaggerConfig { @Value("${swagger.switch}")
private boolean swaggerSwitch; @Bean
public Docket api() {
Docket docket = new Docket(DocumentationType.SWAGGER_2);
docket.enable(swaggerSwitch);
docket
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.hqs.zk.register.controller")).paths(PathSelectors.any()).build();
return docket;
} private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Spring boot zk register")
.description("测试")
.contact(new Contact("黄青石","http://www.cnblogs.com/huangqingshi","68344150@qq.com"))
.termsOfServiceUrl("http://www.cnblogs.com/huangqingshi")
.version("1.0")
.build();
}
}
好了,接下来该启动工程了,启动之后访问: http://localhost:8080/swagger-ui.html

点击下面的zk-controller,对应controller的方法就会显示出来,然后点try it out, execute 相应的结果就直接出来了, 通过下面的图片,可以发现我本机的IP已经注册到里边了。 
接下来,咱们使用ZKUI连接上zookeeper,看一下是否真的有注册的机器(父节点用的monior),已经存在了,没有问题:

注册这块就算实现完了,我一直想实现一个简易的聊天,参考了各种资料然后实现了一把,也算圆梦了。下面开始实现简易netty版聊天(为什么选择netty?因为这个工具非常棒),使用google的protobuf进行序列化和反序列化:
首先从官网上下载protobuf工具,注意对应不同的操作系统,我的是WINDOWS的,直接下载一个EXE程序,你下载的哪个版本,需要使用与该版本对应的版本号,否则会出错误。

自己创建好对应的Request.proto和Response.proto,在里边指定好对应的字段和包名信息。分别执行命令即可生成对应的文件:protoc.exe ./Response.proto --java_out=./ 这个是生成Response的,还需要指定一条生成Request。
将文件夹放到工程里边,工程的大致接入如下:

Server的主要实现,主要基于protoBuf固定长度的进行实现的(序列化和反序列化一般通过固定长度或者分隔符实现),这样的话就不会造成粘包、拆包的问题。
public void bind(int port) throws Exception {
//配置服务器端NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ProtobufVarint32FrameDecoder());
socketChannel.pipeline().addLast(new ProtobufDecoder(RequestProto.ReqProtocol.getDefaultInstance())).
addLast(new ProtobufVarint32LengthFieldPrepender()).addLast(new ProtobufEncoder());
socketChannel.pipeline().addLast(new ProBufServerHandler());
}
});
//绑定端口,同步等待
ChannelFuture f = b.bind(port).sync();
if (f.isSuccess()) {
System.out.println("启动 server 成功");
}
} catch (Exception e) {
e.printStackTrace();
}
}
客户端主要两个方式,一个方式是客户端向服务端发请求,一个方式是群组发消息,我为了快速实现,就直接发一条请求,并且将结果输出到日志中了。客户端使用一个线程执行两个不同的方法,然后将一个是发送给Server, 一个是发送给Group。发送给Server比较简单就直接给Server了。
@PostConstruct
public void start() throws Exception{
connection(appConfig.getNettyServer(), appConfig.getNettyPort());
for(int i = 1; i <= 1; i++) {
int j = i;
Runnable runnable = () -> {
try {
sendMesgToServer(j);
sendMesgToGroup(j);
} catch (Exception e) {
e.printStackTrace();
} }; new Thread(runnable).start(); }
}
发送给Group的话需要记住每次过来的唯一requestId,并且保存对应的channel,然后发送消息的时候遍历所有requestId,并且与之对应的发送消息:
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, RequestProto.ReqProtocol reqProtocol) throws Exception {
RequestProto.ReqProtocol req = reqProtocol;
CHANNEL_MAP.putIfAbsent(req.getRequestId(), (NioSocketChannel)channelHandlerContext.channel());
// System.out.println("get Msg from Client:" + req.getReqMsg());
handleReq(req);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(cause.getMessage());
ctx.close();
} public void handleReq(RequestProto.ReqProtocol req) {
Long originRequestId = req.getRequestId(); if(req.getType() == Constants.CommandType.SERVER) { NioSocketChannel nioSocketChannel = CHANNEL_MAP.get(req.getRequestId());
sendMsg(nioSocketChannel, originRequestId, originRequestId, Constants.CommandType.SERVER, "hello client"); } else if(req.getType() == Constants.CommandType.GROUP) {
for(Map.Entry<Long, NioSocketChannel> entry : CHANNEL_MAP.entrySet()) {
//过滤自己收消息 if(entry.getKey() == originRequestId) {
continue;
}
sendMsg(entry.getValue(), originRequestId, entry.getKey(), Constants.CommandType.GROUP, req.getReqMsg());
}
} }
输出的结果如下,自定义两个客户端,一个requestId是1L,另一个requestId是2L,然后都在启动的时候sleep 3秒,然后发送给server。sleep5秒发送到Group里边去,输出的结果就是如下这个样子的。
1L : send message to server successful!
2L : send message to server successful!
get Msg from Server: 2:hello client
received id:2- send to id:2
received id:1- send to id:1
get Msg from Server: 1:hello client received id:1- send to id:2
get Msg from Group: 1:hello peoole in group
received id:2- send to id:1 get Msg from Group: 2:hello peoole in group
具体的代码可参考:https://github.com/stonehqs/ZKRegister
如果问题,欢迎留言讨论。
实现分布式服务注册及简易的netty聊天的更多相关文章
- 分布式服务注册和发现consul 简要介绍
Consul是HashiCorp公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其他分布式服务注册与发现的方案,Consul的方案更"一站式",内置了服务注册与发现框 架 ...
- 分布式服务注册中心XXL-REGISTRY
<分布式服务注册中心XXL-REGISTRY> 一.简介 1.1 概述 XXL-REGISTRY 是一个轻量级分布式服务注册中心,拥有"轻量级.秒级注册上线.多环境.跨语言.跨机 ...
- 【转帖】阿里金融云:分布式服务注册中心(DSR)
https://www.cloud.alipay.com/docs/middleware/register/index.html 分布式服务注册中心(DSR) 分布式服务注册中心简介 服务注册中心 ( ...
- 温故知新,.Net Core遇见Consul(HashiCorp),实践分布式服务注册与发现
什么是Consul 参考 https://www.consul.io https://www.hashicorp.com 使用Consul做服务发现的若干姿势 ASP.NET Core 发布之后通过命 ...
- 使用consul实现分布式服务注册和发现--redis篇
安装consul client consul 客户端检脚本 ====================================================================== ...
- 分布式服务通讯框架XXL-RPC
<分布式服务通讯框架XXL-RPC> 一.简介 1.1 概述 XXL-RPC 是一个分布式服务通讯框架,提供稳定高性能的RPC远程服务调用功能.现已开放源代码,开箱即用. 1.2 特 ...
- 分布式服务框架XXL-RPC
<分布式服务框架XXL-RPC> 一.简介 1.1 概述 XXL-RPC 是一个分布式服务框架,提供稳定高性能的RPC远程服务调用功能.拥有"高性能.分布式.注册中心. ...
- Consul 服务注册与服务发现
上一篇:Mac OS.Ubuntu 安装及使用 Consul 1. 服务注册 对 Consul 进行服务注册之前,需要先部署一个服务站点,我们可以使用 ASP.NET Core 创建 Web 应用程序 ...
- Spring Boot + Spring Cloud 构建微服务系统(一):服务注册和发现(Consul)
使用Consul提供注册和发现服务 什么是 Consul Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其它分布式服务注册与发现的方案,Consul ...
随机推荐
- Database学习 - mysql 数据库 外键
外键 外键约束子表的含义:如果在父表中赵达不到候选键,则不允许在子表上进行insert/update 外键预约对父表的含义:在父表上进行update/delete以更新或删除子表中有一条或多条对应匹配 ...
- atof()函数 atol()
atof()函数 atof():double atof(const char *str ); 功 能: 把字符串转换成浮点数 str:要转换的字符串. 返回值:每个函数返回 double 值,此值由将 ...
- python学习之argparse模块
python学习之argparse模块 一.简介: argparse是python用于解析命令行参数和选项的标准模块,用于代替已经过时的optparse模块.argparse模块的作用是用于解析命令行 ...
- Libevent源码分析—从使用Libevent库开始
练习libevent库的使用,主要是几个API的调用顺序.根据event.h的开头注释部分可知,要使用libevent库,主要的几个API及调用顺序为: event_base()初始化 ...
- phantomjs 解码url
以下为部分代码: var htmlnodeInfo=(allADUrlElements.snapshotItem(i).getAttribute("href").match(/\* ...
- crontab在/var/log/目录下没有cron.log文件
1.修改rsyslog文件: /etc/rsyslog.d/50-default.conf 将 rsyslog 文件中的 #cron.* 前的 # 删掉: 2.重启rsyslog服务: s ...
- 使用ado.net打造通用的数据库操作类
最近在项目中使用中碰到了这样一种情况,查询的数据是从Oracle中获取的,但是记录下来的数据是存在Sql Server中(企业Oracle数据库管理太严,没办法操作).而且我在之前的工作中也碰到过使用 ...
- <a>标签缺少href 属性,鼠标经过不会出现手型
声明: web小白的笔记,欢迎大神指点.联系QQ:1522025433. 直接看实例吧! <!doctype html> <html> <head> <met ...
- SimInfo获取(MCC, MNC, PLMN)
String NUMERIC = getSIMInfo(); protected String getSIMInfo() { TelephonyManager iPhoneManager = (Tel ...
- HDU 3980 (SG 环变成链 之前的先手变成后手)
题意 两个人在一个由 n 个玻璃珠组成的一个圆环上玩涂色游戏,游戏的规则是: 1.每人一轮,每轮选择一个长度为 m 的连续的.没有涂过色的玻璃珠串涂色 2.不能涂色的那个人输掉游戏 Aekdycoin ...