微服务架构演变

1最开始

架构

网站(前端):用户注册,商品展示,登录,下单

后台(后端):用户管理,商品管理,订单管理

数据库:mysql

2.业务扩展

架构

前端:

网站:用户注册,商品展示,登录,下单

微信端,移动端

后端

用户管理,商品管理,订单管理

数据分析,促销管理

弊端

  1. 网站,移动端很多重复代码。
  2. 数据有时通过数据库共享和接口调用,关系乱
  3. 给不同应用提供接口,包含很多不属于自身的逻辑,边界模糊,功能乱。
  4. 后台加入数据分析,促销管理出现性能瓶颈,影响其它应用
  5. 数据库被多个应用依赖,无法优化
  6. 开发、测试、部署、维护愈发困难,改小测试大。部署多。

3.利用抽象改造-抽像出公共服务-重要

架构

抽象出公用的业务,做成几个公共服务. 把后台的几个功能做成公共服务。

前端: 网站,管理后台,微信,移动端

公共服务:用户服务,商品服务,订单服务,促销服务,数据分析服务

数据库:Mysql

4.拆分数据库

引入原因

  1. 数据库成为性能瓶颈,有单点故障的风险
  2. 数据库表被多个服务依赖。难调整

方案

拆分数据库,加入消息队列

架构

前端: 网站,管理后台,微信,移动端

公共服务:用户服务(mysql),商品服务(redis,mysql),订单服务(mysql),

促销服务(redis,mysql),数据分析服务(数仓)

优点:

  1. 持久化层相互隔离,由各服务自己负责, 系统的分工更加明确,责任更加清晰
  2. 为提交实时性,加入了消息队列 。
  3. 数据分析服务可以使用数据仓库作为持久化层,以便于高效地做一些统计计算
  4. 商品服务和促销服务访问频率比较大,因此加入了缓存机制

5.监控

引入原因

订单数量蹭蹭地上涨-系统崩了,不能提供服务了,损失很大。少赚几十万

之前架构缺点

1. 微服务架构整个应用分散成多个服务,定位故障点非常困难
2. 在微服务架构中,一个服务故障可能会产生雪崩效用,导致整个系统故障
3. 服务数量非常多,部署、管理的工作量很大

方案

在高并发分布式的场景下,故障经常是突然间就雪崩式爆发。所以必须建立完善的监控体系,尽可能发现故障的征兆

实现

各个组件所需要监控的指标不同

Redis缓存监控:

一般监控占用内存值、网络流量

开源组件RedisExporter:提供了Redis缓存指标接口

数据库监控:

监控连接数、磁盘空间

开源组件MySQLExporter:提供了MySQL数据库的指标接口

业务服务监控

监控并发数、响应延迟、错误率

根据各个服务的业务逻辑实现自定义的指标接口

指标采集器

Prometheus作为指标采集器,Grafana配置监控界面和邮件告警。这样一套微服务监控系统就搭建起来了:

让各个组件提供报告自己当前状态的接口(metrics接口),这个接口输出的数据格式应该是一致的。然后部署一个指标采集器组件,定时从这些接口获取并保持组件状态,同时提供查询服务。最后还需要一个UI,从指标采集器查询各项指标,绘制监控界面或者根据阈值发出告警。

6.定位问题 - 链路跟踪

引入原因

一个用户的请求往往涉及多个内部服务调用。为了方便定位问题,需要能够记录每个用户请求时,微服务内部产生了多少服务调用,及其调用关系。这个叫做链路跟踪

方案

要实现链路跟踪,每次服务调用会在HTTP的HEADERS中记录至少记录四项数据

  1. traceId:traceId标识一个用户请求的调用链路。具有相同traceId的调用属于同一条链路。
  2. spanId:标识一次服务调用的ID,即链路跟踪的节点ID。
  3. parentId:父节点的spanId。
  4. requestTime & responseTime:请求时间和响应时间。

实现:

选用了Dapper的一个开源实现Zipkin,写了个HTTP请求的拦截器,在每次HTTP请求时生成这些数据注入到HEADERS,同时异步发送调用日志到Zipkin的日志收集器中。这里额外提一下,HTTP请求的拦截器,可以在微服务的代码中实现,也可以使用一个网络代理组件来实现(不过这样子每个微服务都需要加一层代理)。

链路跟踪只能定位到哪个服务出现问题,不能提供具体的错误信息。查找具体的错误信息的能力则需要由日志分析组件来提供。

7.分析问题-日志分析

引用原因

当访问数变大、或服务器规模增多时,日志文件的大小会膨胀到难以用文本编辑器进行访问,更糟的是它们分散在多台服务器上面。排查一个问题,需要登录到各台服务器去获取日志文件,一个一个地查找(而且打开、查找都很慢)想要的日志信息。

在应用规模变大时,我们需要一个日志的“搜索引擎”。以便于能准确的找到想要的日志。另外,数据源一侧还需要收集日志的组件和展示结果的UI组件:

实现:

使用了大名鼎鼎地ELK日志分析组件。ELK是Elasticsearch、Logstash和Kibana三个组件的缩写。

  • Elasticsearch:搜索引擎,同时也是日志的存储。
  • Logstash:日志采集器,它接收日志输入,对日志进行一些预处理,然后输出到Elasticsearch。
  • Kibana:UI组件,通过Elasticsearch的API查找数据并展示给用户

还有一个小问题是如何将日志发送到Logstash。一种方案是在日志输出的时候直接调用Logstash接口将日志发送过去。这样一来又(咦,为啥要用“又”)要修改代码……于是小明选用了另一种方案:日志仍然输出到文件,每个服务里再部署个Agent扫描日志文件然后输出给Logstash

8.网关

引用原因

微服务-大量的服务,大量的接口,使得整个调用关系乱糟糟的

网关。在调用者和被调用者中间加一层网关,每次调用时进行权限校验。另外,网关也可以作为一个提供服务接口文档的平台。

方案

最粗粒度的方案:整个微服务一个网关

微服务外部通过网关访问微服务,微服务内部则直接调用

最细粒度的方

所有调用,不管是微服务内部调用或者来自外部的调用,都必须通过网关

折中的方案

是按照业务领域将微服务分成几个区,区内直接调用,区间通过网关调用。

实现

因为服务数量还不算特别多,采用的最粗粒度的方案:

9.服务注册与发现-动态扩容

引用原因:

前面是降低故障发生的可能性,但是故障还是会发生,如何降低发生时产生的影响。 

方案

最粗暴的(也是最常用的)故障处理策略-冗余

一个服务都会部署多个实例,这样一来能够分担压力提高性能,二来即使一个实例挂了其他实例还能响应。

根据服务功能、时间段的不同,需要不同数量的实例。比如在平日里,可能4个实例已经够用;而在促销活动时,流量大增,可能需要40个实例。因此冗余数量并不是一个固定的值,而是根据需要实时调整的

需要部署新实例,同时将新实例注册到负载均衡上(如果新增40个实例,则要输入40个ip)

服务注册与发现

解决这个问题的方案是服务自动注册与发现,首先,需要部署一个服务发现服务,它提供所有已注册服务的地址信息的服务,然后各个应用服务在启动时自动将自己注册到服务发现服务上。并且应用服务启动后会实时(定期)从服务发现服务同步各个应用服务的地址列表到本地。服务发现服务也会定期检查应用服务的健康状态,去掉不健康的实例地址。这样新增实例时只需要部署新实例,实例下线时直接关停服务即可,服务发现会自动检查服务实例的增减。

服务发现还会跟客户端负载均衡配合使用。由于应用服务已经同步服务地址列表在本地了,所以访问微服务时,可以自己决定负载策略。甚至可以在服务注册时加入一些元数据(服务版本等信息),客户端负载则根据这些元数据进行流量控制,实现A/B测试、蓝绿发布等功能

实现

服务发现有很多组件可以选择,比如说Zookeeper 、Eureka、Consul、Etcd

10.熔断、服务降级、限流

熔断:

当一个服务因为各种原因停止响应时,调用方通常会等待一段时间,然后超时或者收到错误返回。如果调用链路比较长,可能会导致请求堆积,整条链路占用大量资源一直在等待下游响应。所以当多次访问一个服务失败时,应熔断,标记该服务已停止工作,直接返回错误。直至该服务恢复正常后再重新建立连接。

服务降级

当下游服务停止工作后,如果该服务并非核心业务,则上游服务应该降级,以保证核心业务不中断。比如网上超市下单界面有一个推荐商品凑单的功能,当推荐模块挂了后,下单功能不能一起挂掉,只需要暂时关闭推荐功能即可。

限流

一个服务挂掉后,上游服务或者用户一般会习惯性地重试访问。这导致一旦服务恢复正常,很可能因为瞬间网络流量过大又立刻挂掉,在棺材里重复着仰卧起坐。因此服务需要能够自我保护——限流。

限流策略有很多,最简单的比如当单位时间内请求数过多时,丢弃多余的请求。

另外,也可以考虑分区限流。仅拒绝来自产生大量请求的服务的请求。

例如商品服务和订单服务都需要访问促销服务,商品服务由于代码问题发起了大量请求,促销服务则只限制来自商品服务的请求,来自订单服务的请求则正常响应。

参考:

微服务架构是什么

https://www.zhihu.com/question/65502802


本文由 hcb 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

还不快抢沙发

添加新评论