美文网首页springbootconfig微服务架构和实践
node+zookeeper+spring boot实现服务架构

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

作者: NoException | 来源:发表于2017-03-03 14:43 被阅读2294次

    近日看黄勇编著的轻量级为服务架构,使用到了一些技术,遂做笔记记录下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 ,这样整个流程就已经走完


    相关文章

      网友评论

      • Kosese:楼主你好, 我最近也在看这个,但是运行程序时的顺序是:zookeeper, spring boot,nodejs,然后在nodejs这里链接zookeeper是,对象状态一直时DISCONNECTED,不知道是怎么回事,你有碰到这个问题吗
        Kosese:@仲夏夜之恋 嗯嗯,这个头一天没运行起来,也没查出问题,第二天又试了一下,就可以了,挺郁闷的:stuck_out_tongue:
        NoException:@Kosese 不好意思,现在才看到,你的问题解决了吗?是否是地址不对或者域名没有加host的原因

      本文标题:node+zookeeper+spring boot实现服务架构

      本文链接:https://www.haomeiwen.com/subject/giilgttx.html