近日看黄勇编著的轻量级为服务架构,使用到了一些技术,遂做笔记记录下node.js zookeeper springBoot的基本用法
框架下各组件的职责
1.node的作用
node.js作为独立的中间件服务来提供服务发现功能,发现web服务端程序注册到zookeeper的服务节点,并做对应的请求转发
2.zookeeper的作用
提供服务注册与发现功能,具体资料可参考
3.spring boot web模块作用
对外提供接口,注册api接口服务到zookeeper
----------------------------------------------------
demo流程
首先我们先下载和安装zookeeper和node.js,zookeeper和node.js的安装和启动请查看前面的文章:
demo地址:https://github.com/aihuiergithub/spring-boot-test.git
1.项目结构:
msa-framework 用户存放框架性代码,创建的是maven java Module
msa-sample-api 用户存放服务定义代码,注册zookeeper代码,创建的是maven webapp Module
msa-sample-web 用于存放html界面代码,node.js代码,创建maven java Module
2.msa-framework模块
项目定义接口ServiceRegistry,用来规范服务注册接口
packageme.wanghui.framework.registry;
/**
* 服务注册表
* Created with IntelliJ IDEA.
* User: mac
* Date: 17/3/1
* Time: 下午3:16
*/
public interfaceServiceRegistry{
/**
* 注册服务信息
* @paramserviceName服务名称
* @paramserviceAddress注册服务的地址
*/
voidregister(StringserviceName,StringserviceAddress);
}
3.msa-sample-api模块介绍
application.properties 定义了springBoot启动的端口和服务地址,以及zookeeper的访问地址
#本机服务的地址
server.address=127.0.0.1
server.port=8081
#zookeeper注册地址
registry.servers=127.0.0.1:2181
RegistryConfig 负责读取zookeeper注册地址
packageme.wanghui.config;
importme.wanghui.framework.registry.ServiceRegistry;
importme.wanghui.framework.registry.ServiceRegistryImpl;
importorg.springframework.boot.context.properties.ConfigurationProperties;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
/**
* 读取zookeeper注册地址
* Created with IntelliJ IDEA.
* User: mac
* Date: 17/3/1
* Time: 下午3:22
*/
@Configuration
@ConfigurationProperties(prefix ="registry")
public classRegistryConfig{
privateStringservers;
@Bean
publicServiceRegistry serviceRegistry() {
return newServiceRegistryImpl(servers);
}
public voidsetServers(Stringservers) {
this.servers= servers;
}
}
ZookeeperRegisterController 对外暴露http接口
packageme.wanghui.controller;
importorg.springframework.web.bind.annotation.RequestMapping;
importorg.springframework.web.bind.annotation.RequestMethod;
importorg.springframework.web.bind.annotation.RestController;
/**
* Created with IntelliJ IDEA.
* User: mac
* Date: 17/3/1
* Time: 下午4:13
*/
@RestController
public classZookeeperRegisterController{
@RequestMapping(name ="/hello",method =RequestMethod.GET)
publicString hello(){
return"this is api";
}
}
ServiceRegistryImpl 实现了ServiceRegistry,Watcher接口,用来创建zookeeper客户端
packageme.wanghui.framework.registry;
importorg.apache.log4j.Logger;
importorg.apache.zookeeper.*;
importorg.springframework.stereotype.Component;
importjava.util.List;
importjava.util.concurrent.CountDownLatch;
/**
* 向zookeeper注册服务节点
* Created with IntelliJ IDEA.
* User: mac
* Date: 17/3/1
* Time: 下午3:18
*/
@Component
public classServiceRegistryImplimplementsServiceRegistry,Watcher{
private static finalStringREGISTRY_PATH="/registry";
private static final intSESSION_TIMEOUT=5000;
privateLoggerlogger=Logger.getLogger(ServiceRegistryImpl.class);
privateCountDownLatchlatch=newCountDownLatch(1);
privateZooKeeperzk;
publicServiceRegistryImpl() {
}
publicServiceRegistryImpl(StringzkServers) {
//创建zookeeper客户端
try{
zk=newZooKeeper(zkServers,SESSION_TIMEOUT,this);
latch.await();
logger.debug("connected to zookeeper");
}catch(Exceptione) {
e.printStackTrace();
logger.error("create zookeeper client failure", e);
}
}
@Override
public voidregister(StringserviceName,StringserviceAddress) {
//创建跟节点
String registryPath=REGISTRY_PATH;
try{
//创建节点,创建
if(zk.exists(registryPath,false) ==null) {
zk.create(registryPath,null,ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
logger.debug("create registry node :"+registryPath);
}
//创建服务节点,持久节点
String servicePath=registryPath+"/"+ serviceName;
servicePath=servicePath.replace("//","/");
if(zk.exists(servicePath,false) ==null) {
String addressNode=zk.create(servicePath,null,ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
logger.debug("create service node:"+addressNode);
}
//创建地址节点
String addressPath=servicePath+"/address-";
String addressNode=zk.create(addressPath, serviceAddress.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL);
logger.debug("create address node serviceAddress:"+ serviceAddress +" addressNode"+addressNode);
}catch(Exceptione) {
e.printStackTrace();
logger.error("create node failure", e);
}
}
@Override
public voidprocess(WatchedEventwatchedEvent) {
if(watchedEvent.getState() ==Event.KeeperState.SyncConnected) {
latch.countDown();
}
}
}
RegistryZooListener 监听容器加载事件,加载完成后获取所有的mapping,调用ServiceRegistryImpl实例,将所有的mapping注册到zookeeper
packageme.wanghui.listener;
importme.wanghui.framework.registry.ServiceRegistry;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.beans.factory.annotation.Value;
importorg.springframework.stereotype.Component;
importorg.springframework.web.context.WebApplicationContext;
importorg.springframework.web.context.support.WebApplicationContextUtils;
importorg.springframework.web.method.HandlerMethod;
importorg.springframework.web.servlet.mvc.method.RequestMappingInfo;
importorg.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
importjavax.servlet.ServletContext;
importjavax.servlet.ServletContextEvent;
importjavax.servlet.ServletContextListener;
importjava.util.Map;
/**
* 监听服务启动事件,注册服务到zookeeper
* Created with IntelliJ IDEA.
* User: mac
* Date: 17/3/1
* Time: 下午3:59
*/
@Component
public classRegistryZooListenerimplementsServletContextListener{
@Value("${server.address}")
privateStringserverAddress;
@Value("${server.port}")
private intserverPort;
@Autowired
privateServiceRegistryserviceRegistry;
@Override
public voidcontextInitialized(ServletContextEventservletContextEvent) {
ServletContext servletContext= servletContextEvent.getServletContext();
WebApplicationContext applicationContext=WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
RequestMappingHandlerMapping mapping=applicationContext.getBean(RequestMappingHandlerMapping.class);
//获取到所有的请求mapping
MapinfoMap=mapping.getHandlerMethods();
for(RequestMappingInfo info:infoMap.keySet()){
String serviceName=info.getName();
if(serviceName!=null){
//注册服务
serviceRegistry.register(serviceName,String.format("%s:%d",serverAddress,serverPort));
}
}
}
@Override
public voidcontextDestroyed(ServletContextEventservletContextEvent) {
}
}
SimpleApplication 启动springBoot的main类
packageme.wanghui.simple;
importorg.springframework.boot.SpringApplication;
importorg.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Created with IntelliJ IDEA.
* User: mac
* Date: 17/3/1
* Time: 下午3:12
*/
@SpringBootApplication(scanBasePackages ="me.wanghui")
public classSimpleApplication{
public static voidmain(String[] args) {
SpringApplication.run(SimpleApplication.class,args);
}
}
3.msa-sample-web模块
web项目里面包含使用node.js的一些插件,所以我们首先要装载这些插件
a.首先我们再web的根目录下创建package.json文件,比并且初始化内容为
{
"name":"msa-sample-web",
"version":"1.0.0",
"dependencies": {
}
}
b.编写index.html文件,用于发送ajax请求
Demo
<html lang="en">
<head>
<meta charset="utf-8">
<title>send ajax to node.js</title>
</head>
<body>
send ajax to node.js
<div id = "console"></div>
<script>
$(function(){
$.ajax({
method:"GET",
url:'/123',
headers:{
'Service-Name':'hello'
},
success:function(data){
$("#console").text(data);
}
});
});
</script>
</body>
</html>
c.编写app.js文件
因nodejs做反向代理的时候需要使用http-proxy插件,连接zookeeper的时候也需要使用插件,所以我们要使用它,首先要安装所需的插件
1.在web module根目录安装npm install express -save
2.安装npm install node-zookeeper-client -save
3.安装代理组件 npm install http-proxy -save
为了使node.js启动的程序可以高可用性,我们还需要添加forever插件到node.js的安装目录
sudo npm install forever -g
编写好的app.js文件如下所示:
var express=require('express');
var zookeeper=require('node-zookeeper-client');
var httpProxy=require('http-proxy');
var PORT=1234;
var CONNECTION_STRING='127.0.0.1:2181';
var REGISTER_ROOT="/registry";
//连接zookeeper
var zk=zookeeper.createClient(CONNECTION_STRING);
zk.connect();
//创建代理服务器对象并监听错误事件
var proxy=httpProxy.createProxyServer();
proxy.on('error',function(err,req,res){
res.end();//输出空白数据
});
//启动web服务器
var app=express();
app.use(express.static('public'));
app.all('*',function(req,res){
//处理图标请求
if(req.path=='/favicon.ico'){
res.end();
return;
}
//获取服务器名称
var serviceName=req.get('Service-Name');
console.log('service-name : %s',serviceName);
if(!serviceName){
console.log('Service-Name request head is not exist');
res.end();
return;
}
//获取服务路径
var servicePath=REGISTER_ROOT+'/'+serviceName;
console.log('service path is : %s',servicePath);
//获取服务路径下的地址节点
zk.getChildren(servicePath,function(error,addressNodes){
if(error){
console.log(error.stack);
res.end();
return;
}
var size=addressNodes.length;
if(size==0){
console.log('address node is not exist');
res.end();
return;
}
//生成地址路径
var addressPath=servicePath+'/';
if(size==1){
//如果只有一个地址
addressPath+=addressNodes[0];
} else {
//如果存在多个地址,则随即获取一个地址
addressPath+=addressNodes[parseInt(Math.random() *size)];
}
console.log('addressPath is : %s',addressPath);
//获取服务地址
zk.getData(addressPath,function(error,serviceAddress){
if(error){
console.log(error.stack);
res.end();
return;
}
console.log('serviceAddress is : %s',serviceAddress);
if(!serviceAddress){
console.log('serviceAddress is not exist');
res.end();
return;
}
proxy.web(req,res,{
target:'http://'+serviceAddress//目标地址
})
});
});
});
app.listen(PORT,function(){
console.log('server is running at %d',PORT);
});
至此已经完成了整个流程基础模块的搭建,其中包含了nodeJs zookeeper springBoot,让我们启动整个流程
1.先启动zookeeper,再zookeeper的解压目录的bin目录下启动,执行下面的命令
./zkServer.sh start-foreground
2.启动nodeJs,进入msa-sample-web目录,执行下面的命令
forever start app.js
3.启动springBoot,注册服务到zookeeper
1)启动完成后在浏览器输入 http://localhost:1234 ,会加载msa-sample-web项目的index.html文件
2)紧接着ajax发送请求到node.js的app.js实例中
3)app.js通过Service-Name定位到请求资源,然后通过代理转发到msa-sample-api提供接口
我们可以看到返回了 this is api ,这样整个流程就已经走完