为了账号安全,请及时绑定邮箱和手机立即绑定

微服务架构在Node.js中的应用

微服务是什么?

在过去我们通常采用的是单机程序开发,就是说所有的接口服务都是由一套应用程序进行处理的。这就造成了随着项目规模的扩大,代码越来越难以理解和维护。开发和部署的时间也越来越长,同时如果一旦你想要进行重构或者说变更技术栈,将会痛不欲生。

现代企业大多数也都开始采用微服务架构,那么它有什么优势呢?通过将一个大的单机应用拆分成多个子的微服务,保证了这些独立的服务能够更易于理解和测试,同时部署和扩展也将变得简单起来。在多团队合作过程中,每个团队负责一个独立的微服务也便于项目分工。而且这些独立的微服务还能够根据自身需求采用不同的技术栈分别实现。

引入微服务架构后虽然带来了这么多好处,但同时也增加一些额外需要进行考虑的问题。比如如何处理配置文件?如何进行日志跟踪?微服务之间如何进行通信等等。接下来以Node.js作为例子介绍微服务在Node.js应用中的一些应用。

API 网关

首先,我们一旦引入了微服务架构,就需要增加一个API网关作为服务入口,利用这个网关,可以将所有的用户请求都分发到不同的子微服务上。同时,还可以在网关上处理请求拦截,日志监控等功能。要记住的是,我们不应该在网关上包含任何具体的业务逻辑,我们只是把它当做一个存粹的代理入口。

在Node.js中可以先定义一个消息格式,作为后续网关和各个子微服务进行通信的规范。下面是一个typescript的接口定义:

export interface ISocketMessage {
  serviceName: string;
  serviceOperation: string;
  payload: object;
}

接下来来处理消息:

client.on('message', (data: ISocketMessage) => {
	this.services[data.serviceName][data.serviceOperation]({
		...data.payload
	}).then(result => {
		client.emit('message', { payload: result });
	}).catch(error => {
		// log the error message
		logger.error(error);
	})
})

服务管理

关于微服务的管理,目前有很多工具可以很好地进行支持。这里介绍一款比较著名的工具——Consul,它是由HashiCorp公司进行开发维护的,提供了一整套配置管理微服务的技术解决方案。

大家可以直接从consul官方仓库拉取镜像,并启动一个consul服务器:

docker run -p 8500:8500 consul

在具体的微服务中要注册健康监测的接口:

const app = express();

app.get("/health", (req, res) => {
  res.status(200);
  res.send('ok');
  res.end();
});

接着要利用consul注册服务网关, 并设置健康检测的地址:

const consul = require("consul")();

consul.agent.service.register({
  name: "api-gateway",
  address: process.env.HOST,
  port: process.env.PORT,
  check: {
    http: `http://${process.env.HOST}:${process.env.PORT}/health`,
    interval: '30s'
  }
}, err => throw err);

注册某个微服务的健康监听:

consul.watch({
  method: consul.health.service,
  options: { service: "shop" }
})
.on("change", (data, res) => {
  const status = data[0].Checks[1].Status;
  console.log("shop service status: ${status}");
});

之后可以在http://localhost:8500端口上查看Consul后台。

通信模式

各个微服务之间的通信可以采用传统的http协议,也可以采用RPC框架进行处理,常用的就是gRPC框架。

消息队列也是在微服务之间进行通信的常用技术,能够起到松耦合、以及缓存消息的作用。可以用来实现消息队列的技术有RabbitMQ,Kafka, Redis等等。大家可以根据具体业务需要进行选择。

下面是使用rabbit.js在Node.js端进行消息通信的例子:

var context = require('rabbit.js').createContext();
context.on('ready', function() {
  var pub = context.socket('PUB'), sub = context.socket('SUB');
  sub.pipe(process.stdout);
  sub.connect('events', function() {
    pub.connect('events', function() {
      pub.write(JSON.stringify({welcome: 'rabbit.js'}), 'utf8');
    });
  });
});

数据管理

在常用的微服务架构中,关于数据库的处理可以分为两种方式,一种是所有的服务共用同一套数据库系统,另一种就是不同的微服务使用不同的数据库,这样可以更好地进行扩展和管理。

图片描述

数据一致性

一旦采用了微服务架构,数据一致性也是要重点考虑的问题。特别是对于负责的业务处理来说,独立的微服务在一整套业务流程中只要有一个参与者失败了,就面临着数据不一致的风险。比如下订单的时候支付成功了,却无法进入物流流程。

目前业界用来处理一致性问题比较著名的方式是采用SAGA模式。它主要是用来协调跨越多个微服务进行处理的一系列事务操作。每一个事务都会更新数据库,一旦其中一个事务操作失败,就会对关联的微服务执行回滚操作。

图片描述

状态跟踪

日志系统在平常开发过程中是很常用的一种追踪手段,不过对于复杂的微服务系统来说,光有日志系统是不够的。为了能够更快地定位问题以及发现性能瓶颈,我们还需要引入状态追踪服务(Tracing Service),用于监控各个微服务之间通信的通路以及错误发生的节点。

图片描述

OpenTracing是分布式追踪系统的规范,其中一款比较优秀的开源分布式追踪系统是Jaeger,它是由Uber推出的。支持Node,Go,Java,Python等语言,而且提供了好用的UI操作界面。

可以直接使用docker起一个Jaeger服务器:

docker run -d -e COLLECTOR_ZIPKIN_HTTP_PORT=9411 \
  -p 5775:5775/udp \
  -p 6831:6831/udp \
  -p 6832:6832/udp \
  -p 5778:5778 \
  -p 16686:16686 \
  -p 14268:14268 \
  -p 9411:9411 \
  jaegertracing/all-in-one:latest

然后可以通过http://localhost:16686就可以查看其后台了。

在微服务客户端可以初始化对应的tracer,这里采用https://github.com/jaegertracing/jaeger-client-node:

import { initTracer } from 'jaeger-client';

const tracer = initTracer(
	{
    serviceName: process.env.APP_NAME,
    sampler: {
      type: "const",
      param: 1,
    },
    reporter: {
      logSpans: true,
      agentHost: process.env.TRACING_SERVICE_HOST || "localhost",
      agentPort: Number(process.env.TRACING_SERVICE_PORT) || 6832
    }
  },
  { logger }
)

新建一个新的span, 其中span是用来记录跟踪过程中的信息,比如间隔时间,请求的HTTP地址等等:

const span = tracer.startSpan("test span");
span.addTags({ type: "test" });

// some code here

span.finish();

发送Tracing ID给系统:

import { FORMAT_HTTP_HEADERS } from 'opentracing';
const headers = {};
tracer.inject(span, FORMAT_HTTP_HEADERS, headers);
const tracingId = JSON.stringify(headers);
// send tracing ID to other service 

接收端微服务需要去获取到上面生成的ID:

const tracingIdStr = '{"uber-trace-id": "xxxxxxxxxxxxxxxx"}';

const spanFromOtherService = tracer.extract(
	FORMAT_HTTP_HEADERS,
  JSON.parse(tracingIdStr)
);

const span = tracer.startSpan('test span', { childOf: spanFromOtherService});
// some code here

span.finish();

微服务项目管理

如果你的微服务的放在同一仓库(repository)进行管理,可以使用PM2的ecosystem.json配置脚本进行处理:

{
  "apps": [
    {
      "name": "API Gateway",
      "script": "index.js",
      "cwd": "src/services/api-gateway"
    },
    {
      "name": "Notifactions service",
      "script": "index.js",
      "cwd": "src/services/notifactions"
    },
    {
      "name": "Shop service",
      "script": "index.js",
      "cwd": "src/services/shop"
    }
  ]
}

然后启动的话,就可以直接使用命令:

pm2 start ecosystem.json

如果采用docker镜像进行管理,可以事先准备好docker-compose.yml文件:

version: "1"

services:
	api-gateway:
		image: org/api-gateway:latest
		ports:
			- "8000:8000"
		depends_on:
			- redis
	
	notifactions:
		image: org/notifactions:latest
		depends_on:
			- redis

最后使用docker-compose命令启动整个架构:

docker-compose up -d

更多

微服务架构目前是比较流行的分布式架构模式。无论你使用的是Node.js还是Java等后台技术,学习它的必要性都是很大的。其中涉及的技术点也很多,包括Serverless,DDD等等。如果要在实践中更好地应用该架构,必须要更加深入地学习相关知识点,希望这篇文章能对大家有点帮助。

参考资料

点击查看更多内容
2人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消