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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容