前言
在微服务架构中,服务发现一直是一件比较复杂的事。而且服务发现式的架构处理不好,容易产生集中化。同时,微服务的提供,不可避免的需要一些负载均衡方案,实现服务的高可用和可扩展,这无疑增加了很多复杂度。
笔者认为,使用异步、基于消息的方式,可能更适合微服务架构。
基于消息的微服务架构,对于所有微服务的部署条件非常简单,只需要能访问到消息服务即可。同时微服务节点的移除和增加不会影响到服务的提供。相比服务发现的架构,简单太多了,简单即是美。
在这次实践中,使用到了seneca,一个nodejs 微服务框架。seneca,使用seneca-amqp-transport插件,可以轻松构建基于消息的微服务。
下面是架构图:
www.processon.com/view/link/5…
在这个架构中,我们使用的是标准的seneca定义的命令规范,这可能是所有微服务都需要遵守的一个规范,至于说使用其他语言,也很简单。封装一个seneca命令规范的库即可。不知道官方有没开发,开发起来难度也不会太大。
接口层比较灵活,可以根据上层应用特性,来决定如何封装传输协议,最后将转化成标准命令发送到消息服务。不建议直接访问消息服务,上层应用应保持灵活。
完整的实践代码:github.com/luaxlou/mic…
1 前期准备
使用docker-machine创建虚拟机。
关于docker的一些基本用法,可以读上一篇文章:docker+consul基于服务发现的极简web架构实践,这里就不再赘述。
依次创建3台虚拟机:
$ dm create -d "virtualbox” node1
$ dm create -d "virtualbox” node2
$ dm create -d "virtualbox" node3
2 开始构建
搭建Rabbitmq消息服务
消息队列服务,已经成为高并发应用的必备基础服务。我们选用Rabbitmq,你可以换成任意的,遵循amqp协议即可。
使用docker安装很方便,但是生产环境不建议使用docker安装。更推荐的是使用云服务,这样能保证足够高的高可用和扩展性。虽然价格贵点,但是这是唯一的单点,花点钱还是值得的。
直接安装在宿主机上:
$ docker search rabbitmq
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
rabbitmq RabbitMQ is an open source multi-protocol ... 1466 [OK]
tutum/rabbitmq Base docker image to run a RabbitMQ server 11
frodenas/rabbitmq A Docker Image for RabbitMQ 11 [OK]
sysrun/rpi-rabbitmq RabbitMQ Container for the Raspberry Pi 2 ... 6
aweber/rabbitmq-autocluster RabbitMQ with the Autocluster Plugin 5
gonkulatorlabs/rabbitmq DEPRECATED: See maryville/rabbitmq 5 [OK]
letsxo/rabbitmq RabbitMQ with Management and MQTT plugins. 4 [OK]
bitnami/rabbitmq Bitnami Docker Image for RabbitMQ 3 [OK]
$ docker run -d --name rabbit -p 5672:5672 rabbitmq
这样就启动了一个消息队里服务,并且开放5672端口
安装jenkins
jenkins用于自动集成,不然每次构建是个很麻烦的事。
下面的实践是笔者掉了不少坑之后完成的,jenkins在安装过程中会有不少麻烦,而且在mac下安装也会遇到麻烦。
将jenkins 安装到 node1
$ dm ssh node1
$ mkdir /mnt/sda1/var/jenkins_home
$ sudo chown 1000 /mnt/sda1/var/jenkins_home
$ sudo chown 1000 /var/run/docker.sock
$ docker run -d -v /var/run/docker.sock:/var/run/docker.sock \
-v /mnt/sda1/var/jenkins_home:/var/jenkins_home \
-v $(which docker):/usr/bin/docker -p 8080:8080 jenkins
查看初始密码: $ cat /mnt/sda1/var/jenkins_home/secrets/initialAdminPassword
安装私有的Registry
在mac上安装即可
$ docker run -d -p 5000:5000 registry
文档参考:docs.docker.com/registry/sp…
准备代码
代码使用的是seneca官方的例子,完整的Dockerfile也已经写好。
FROM node:alpine
RUN npm install pm2 -g
WORKDIR /usr/src/app
COPY package.json ./
RUN npm install
COPY . .
CMD ["pm2-docker","process.yml"]
为了让nodejs能使用到多核cpu,Dockerfile 集成了pm2,使用pm2来管理node进程。
完整代码:
github.com/luaxlou/mic…
配置自动集成
这里使用了最新版的jenkins,新版的jenkins使用了pipline。一种新的构建方式,使用groovy语法。
写起来是挺优雅的,但是学习成本颇高。因为文档不全及有些文档失效,笔者不得已反编译了pipeline插件,才得以调通。
使用pipeline script
node {
stage('Preparation') {
def r = git('https://github.com/luaxlou/micro-service-practice.git')
}
stage('Build') {
dir('seneca-listener') {
withEnv(["DOCKER_REGISTRY_URL=http://192.168.99.1:5000"]) {
docker.build("seneca-listener").push("latest")
}
}
}
}
开始构建,顺利的话,会看到如下的结果:
这是pipeline的特性,可以可视化看到各个阶段的执行情况,算是不小的进步吧。
访问私有Registy的API,就可以看到生成的tag。
curl http://192.168.99.1:5000/v2/seneca-listener/tags/list
最后一步,试试我们的程序
在宿主机发布消息:
$ git clone https://github.com/luaxlou/micro-service-practice.git
seneca-clinet 代码是接口层代码的示意,可以根据自己的喜好封装。 同时直接发送了命令代码用于测试。
进入seneca-clinet 目录
$ AMQP_URL=192.168.99.1:5672 node index.js
这个程序会每隔两秒发送一个命令:
#!/usr/bin/env node
'use strict';
const client = require('seneca')()
.use('seneca-amqp-transport')
.client({
type: 'amqp',
pin: 'cmd:salute',
url: process.env.AMQP_URL
});
setInterval(function() {
client.act('cmd:salute', {
name: 'World',
max: 100,
min: 25
}, (err, res) => {
if (err) {
throw err;
}
console.log(res);
});
}, 2000);
虽然一直在发命令,你很快就会发现命令全部超时了。这是因为还没有消费者,当然这些命令也没有丢失,只不过接口层没有得到及时返回。如果应用层支持异步的模式,每个command都有独立的id,可以保留id后,以后再过来取。这就很灵活了,一切看需求去封装接口层即可。
进入node2
$ docker run 192.168.99.1:5000/seneca-listener:latest
0|seneca-l | {"kind":"notice","notice":"hello seneca fwunhukrcmzn/1507605332382/16/3.4.2/-","level":"info","seneca":"fwunhukrcmzn/1507605332382/16/3.4.2/-","when":1507605332661}
启动后,回到seneca-clinet,发现之前超时的命令,全部接收到了。
{ id: 86,
message: 'Hello World!',
from: { pid: 16, file: 'index.js' },
now: 1507605332699 }
{ id: 44,
message: 'Hello World!',
from: { pid: 16, file: 'index.js' },
now: 1507605332701 }
{ id: 56,
message: 'Hello World!',
from: { pid: 16, file: 'index.js' },
now: 1507605332703 }
{ id: 57,
message: 'Hello World!',
from: { pid: 16, file: 'index.js' },
now: 1507605332706 }
{ id: 58,
message: 'Hello World!',
from: { pid: 16, file: 'index.js' },
now: 1507605332707 }
至此,完整架构已经构建完毕。
一些未完的事项
1.自动集成,只需要配置webhook即可。
2.自动部署,因为docker运转的方式,当服务升级时需要重启docker进程。方式有很多,比较粗暴的是直接控制宿主机,或者类似salt这样的工具。
目前来说,没有找到太好的开源方案。个人倾向于自己开发agent,发布有限的API,用于常规的部署或者其他任务,以及可以定时收集服务器的信息,用于监控。这可能会是笔者的下一个开源项目。
总结
这篇文章算是一个新的里程碑,实践的成果将用于后期的架构。docker让我从传统的架构模式中脱离出来,同时也让我吃了不少苦头。但这一切都是值得的。
同时也是一个新的开始,终于从之前的公司出来。未来何去何从,有很多的未知,但我相信都是美好的。
这也许就是人生的魅力。
Hello World!!