node+zookeeper+spring boot实现服务架构笔记

近日看黄勇编著的轻量级为服务架构,使用到了一些技术,遂做笔记记录下node.js  zookeeper  springBoot的基本用法

框架下各组件的职责

1.node的作用

    node.js作为独立的中间件服务来提供服务发现功能,发现web服务端程序注册到zookeeper的服务节点,并做对应的请求转发

2.zookeeper的作用

    提供服务注册与发现功能,具体资料可参考

基于Zookeeper的服务注册与发现

ZooKeeper系列-功能介绍及使用

3.spring boot web模块作用

    对外提供接口,注册api接口服务到zookeeper

----------------------------------------------------

demo流程

首先我们先下载和安装zookeeper和node.js,zookeeper和node.js的安装和启动请查看前面的文章:

mac安装使用node.js

mac下安装及使用zookeeper

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 ,这样整个流程就已经走完


推荐阅读更多精彩内容