write on 2020年2月26日18:47:16
目录导航
微服务
上一章介绍了微服务架构,微服务是基于分布式架构的特点,对服务层更进一步的细分,它更注重单一责任与小型功能的区块为基础,利用模块的方式组合出复杂的大型程序,各功能区块使用与语言无关的API集成通信。
微服务的规划
微服务理念中有数个数据库规划方式。
- 每个服务都各有一个数据库,同属性的服务可共享同个数据库。
- 所有服务都共享同个数据库,但是不同表格,并且不会跨域访问。
- 每个服务都有自己的数据库,就算是同属性的服务也是,数据库不会共享。
数据库并不会只存放该服务的数据,而是"该服务所会用到的所有数据“。例如:假设有一个文章服务,而这个服务可能会需要判断用户的账号等。那么文章服务的数据库就可以放入用户的部分数据。此举是为了避免服务之间的相依性,避免文章服务调用用户服务。
数据库的可弃用性
实践微服务有许多做法,其中一种是将数据库作为短期的存储空间而不是长期的数据。这意味着数据库可以在离线时被清空,因为上线后可以通过事件存储中心恢复,因此也能以存储器缓存作为数据库服务器(Redis)。但这种做法需要将每个请求当做事件来广播,如此一来便可以从事件存储中心重播所有的事件来找回数据。
沟通与事件广播
微服务中最重要的是每个服务的独立与自主,因此服务间也不应该有通信,假若真的有沟通,也应该采用异步通信的方式来避免紧密的相依性问题。要达到目的可以使用下面2中方式:
- 事件存储中心
- 消息队列
事件存储中心
事件存储中心可以让你在服务集群中广播事件,并且每个服务中监听这些事件并做处理,这令服务间没有紧密的相依性,而这些发生的时间被保存在存储中心中。当事件重新上线、部署时可以重播所有的事件。这也造就了微服务的数据库随时可以被删除、摧毁,且不需要从其他服务中获取数据。
消息队列
消息队列可令你在服务集群中广播消息并传播到每个服务中,具有这个功能的有NSQ、RabbitMQ。比如在A服务上广播一个"创建新用户"事件,这个事件可以顺便带有新用户的数据。而B服务可以监听这个事件并在接受之后有所处理。这过程都是异步处理的,这意味着A服务并不需要等到B服务处理完事件后才能继续,这也代表着A服务无法获取B服务的处理结果。与事件存储中心近乎相似但有所不同的是:消息队列并不会保存事件,一旦事件被消化(接受)后就会从队列中消失,这很适合用在比如发送欢迎信件的时机。
服务注册(探索)
单个微服务上线时,会向服务注册中心注册(比如Consul、Eureka、Zookeeper)自己的IP、服务内容,如此一来就不需要向每个微服务表明自己的IP位置,也就不用替每个微服务单独设置。当服务需要调用另一个服务时会直接循环该服务的IP,得到位置号后即可直接向目标服务调用。
这样做的目的是可以统一集中所有的服务位置,就不会分散于每个微服务中,且服务探索中心可以每隔一段时间就向微服务进行健康检查(TCP调用、HTTP调用、Ping、心跳包),倘若该服务在时间内没有得到回应则将其从服务中心移除避免其他微服务对一个无回应的服务进行调用(高可用)。
微服务的特性
一个微服务框架的应用程序应该有一下特征:
- 每个服务都容易被取代
- 服务是以业务来组织的,例如用户界面、前端、推荐系统、账单或者物流等。
- 由于功能被拆分成多个服务,因此可以用不同的编程语言、数据库实现。
- 架构是对称而非分层(即生产者与消费者的关系)。
一个微服务框架:
- 适用于具有持续交付的软件开发流程。
- 与服务导向架构(SOA)不同,SOA是集成各种业务的应用程序,而微服务只属于一个应用程序。
SpringCloud
SpringCloud是微服务架构的一站式解决方案,上面微服务的介绍中也提到了,一个完整的微服务架构需要服务注册中心、服务间用消息队列或者事件存储中心来通信、为了实现高可用所以需要有负载均衡、断路器、数据监控等操作,而SpringCloud为我们提供了一套简易的模型使我们能在SpringBoot的基础上轻松的实现微服务项目的构建,所以说SpringBoot也是微服务的基石。
但这一个章节并不介绍SpringCloud怎么用,会具体介绍一下SpringCloud的功能模块和以及其优势和劣势等。
关于Netflix公司
Netflix是一家做视频的公司,可以说该网站上的美剧是最火的,Netflix比较特殊,该公司没有CTO,也正是由于这样的组织架构才得以使产品和技术无缝的沟通,从而快速迭代出优秀的产品,在当时软件敏捷开发中,Netflix的更新速度不亚于当年的微信后台变更,虽然微信比Netflix发展更迟,但当年微信的灰度发布和敏捷开发应该算业内最猛的。
由于Netflix做视频的原因,访问量非常大,从而促使其技术快速的发展在背后支撑着,也正是如此,Netflix开始把整体项目往微服务上迁移。Netflix的微服务并不是最早的,但是确实是最大规模在生产级别微服务的尝试,也正是这种大规模的尝试,在服务器运维上依托AWS云,AWS云同样受益于Netflix的大规模业务而不断壮大。
Netflix微服务大规模的应用,在技术上却毫无保留的将一整套微服务架构核心技术栈开源了出来,叫做Netflix OSS,也正因为如此,在技术上依靠开源社区的力量不断壮大。
而Pivotal(Spring Framework)公司在Netflix开源的一套核心技术产品线的同时,对其进行了封装,就变成了SpringCloud,SpringCloud是构建微服务的核心,但它是基于SpringBoot来开发的。
总的来说,微服务是由Netflix开发,Pivotal进行整合,所以才有了现在的SpringCloud。
核心老大——Spring Cloud Netflix
这可是个大boss,地位仅次于老大,老大各项服务依赖与它,与各种Netflix OSS组件集成,组成微服务的核心,它的小弟主要有Eureka, Hystrix, Zuul, Archaius… 太多了
服务注册与发现——Eureka
为了实现对所有的服务集中管理,需要一个服务中心,这里介绍Eureka。
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在子项目Spring-Cloud-Netflix中以实现SpringCloud对服务发现功能。
Eureka包含两个组件:Eureka Server和Eureka Client。
Eureka Server提供服务注册服务,各个节点启动后会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中会存储所有可用到服务节点信息,服务节点信息可以在UI中直观的看到。
Eureka Client是一个java客户端,用户简化与Eureka Server的互动,客户端同时也是一个内置使用轮询负载均衡算法的负载均衡器。
在应用启动后,将会向Eureka Server发送心跳默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务移除(默认90秒)。
Eureka Server通过数据复制的方式完成数据同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。
综上,Eureka通过心跳检查、客户端缓存等机制确保了系统的高可用性、灵活性和可伸缩性。
Eureka和SpringBoot并非同源的框架,而Eureka也是出自Netflix手笔,Eureka位于Spring-Cloud-Netflix项目下。可以看到Spring-Cloud-Netflix是一套分布式服务框架的封装,包括服务的发现和注册,负载均衡、断路器、REST客户端、请求路由等,它基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能。
当然能实现服务注册与发现的还有很多,比如Consul和Zookeeper,不同的框架基础策略会不同,所以根据不同项目需要选择更合适的。
以下是Eureka的核心组件:
服务注册—Register
当Eureka-Client向Eureka-Server注册时,会提供登记客户端元数据。
服务续约—Renew
Eureka-Client连接上Eureka-Server注册中心后会每隔一段时间发送心跳续约证明客户服务还活着(默认30秒),正常情况下,如果Eureka-Server 90秒内没有收到心跳包,它将会从实例中剔除。
服务获取—Fetch Registers
Eureka-Client从注册中心获取注册的服务,并将它们缓存在本地。该注册列表每隔一段时间更新一次(30秒),Eureka-Client与Eureka-Server可以通过Rest进行通信,并且默认情况下也是通过Rest。
服务下线—Cancel
正常情况下Eureka客户端向Eureka服务端发出关闭服务请求,会触发Cancel,发送请求后该客户端实例信息将从服务器的注册表中移除。
该下线请求不会自动产生,需要使用DiscoveryManager.getInstance().shutdownComponent()
服务剔除—Eviction
如果一个Eureka-CLient非安全的状态下无法提供服务,比如被强制关闭而服务一直进行服务续约,注册中心会使用Eviction对服务判断然后进行服务剔除,Eviction的算法相当复杂,因为并不是所有的情况下都需要对服务进行剔除的。
负载均衡之——Ribbon
第一章提到了负载均衡的目的。
Ribbon位于Spring-Cloud-Netflix,同样也是Netflix开发的开源的负载均衡项目。Ribbon是一个客户端/进程内负载均衡器,运行在消费者端。
使用负载均衡肯定是搭配集群一块使用的,比如,我们设计了一个秒杀系统,为了整个项目的高可用,我们需要将这个系统做成一个集群部署(即很多台服务器部署同一个项目):
这个时候有很多调用途径,如果我们没有做一些均衡操作
,对秒杀系统1进行大量的调用而另外的基本不请求就会导致秒杀系统1服务挂掉,集群中部署的另外项目就像傀儡一样,整个集群的意义也就不存在了。
所以Ribbon的作用体现出来了,Ribbon是运行在消费端的负载均衡器,所以它会请求注册中心获取服务(生产者)列表,然后自行负载均衡对每个系统均衡调用。
Ribbon和Nginx
提到负载均衡就不得不提Nginx,和Ribbon不同的是,Nginx是一种集中式的负载均衡器。
何为集中式?简单的说就是将所有请求全部集中起来,然后再分发给各个系统进行负载均衡,如图:
可以看到Nginx是接受了所有请求再进行负载的,而Ribbon只在消费者端处理:
所以得区分一下这二者,更具体的之后会详细分析。
Ribbon的几种负载均衡算法
不管是Ribbon还是Nginx都需要负载均衡算法的支持,Nginx使用的是轮询和加权轮询算法,而Ribbon中有更多的负载均衡调度算法,默认使用RoundRobinRule轮询策略。
- RoundRobinRule:若经过一轮轮询没有找到可用的服务提供者,其最多轮询10轮,若最终还是未找到则返回null。
- RandomRule:随机策略,从所有可用的服务提供者中随机选择一个。
- RetryRule:重试策略,先按照RoundRobinRule策略获取服务提供者,若获取失败,则在指定的时间内重试,默认为500毫秒。
- ...还有很多
Ribbon支持自定义负载均衡算法。
服务远程调用——Open Feign
如果我想调用一个服务,那么经过上面的注册中心、负载均衡后,我是能够请求到一个属于我消费者端最优的服务的,这种情况下怎么调用呢?很简单,发送一个Rest请求即可。我会向服务提供者发送一个我要消费的Rest请求,这样一来就会执行我的请求。但是这样做有一个问题,非常的不方便——我每次都得通过RestTemplate发送API请求,而且代码也不够优雅,如下便是一个使用RestTemplate调用的方式:
@Autowired
private RestTemplate rest;
// 服务A的service1方法
private static final String A = "http://serverA:8001/service1";
@PostMapping("/post")
public boolean post(@RequestBody Request request) {
return rest.postForObject(A, request, Boolean.class);
}
虽然这样写也无问题,但是如果每次都这样写未免过于复杂和不够优雅,所以Feign提供了映射的方式书写Rest方法:
在服务的消费者端,先定义好服务提供者的规范:
// 这里是服务提供者名字,Feign会通过名字找到服务地址,然后发起远程调用请求
@FeignClient("A")
public interface A {
// 服务提供者的方法地址
@RequestMapping("/service1")
String get(@RequestBody Request request);
}
然后直接调用即可,就向在调用本地代码一样
@RestController
public class ConsumerController {
@Autowired
private A a;
@RequestMapping("/getServerA")
public String get(@RequestBody Request request) {
return a.get(request);
}
}
Fegin通过动态代理的的方式代理了添加过@FeignClient注解的接口,最后会被Fegin的动态代理类构造请求实现远程服务调用,例如调用一个远程的OrderService
服务时
Feign更加详细使用会慢慢更新。
所以需要将消费者想要调用的服务建立一个映射,这样我在消费端的代码会映射至服务端,这样就可以无缝开发了。
熔断器——Hystrix
总的来说Hystrix就是一个能进行熔断和降级的库,通过他能提高整个系统的弹性。
那么什么是熔断和降级呢?举个例子,此时我们的整个微服务系统如下。服务A调用了服务B,服务B再调用了服务C,但因为某些原因,服务C顶不住了,这个时候大量的请求会在服务C阻塞。
服务C阻塞了还好,毕竟只是一个系统崩溃了,但这个时候请注意,服务A和服务B都调用了C,所以都会被阻塞。
这就叫服务雪崩,这又和熔断降低有什么关联呢?
所谓熔断就是服务雪崩的一种有效的解决方案,当指定时间窗内请求失败率达到设定阀值时,系统将通过断路器将此请求链路断开。
也就是说,服务B调用服务C的指定时间窗口内,如果失败率过高,Hystrix会直接将服务B和服务C的请求都断掉,以免发生服务雪崩。
这里的熔断其实就是Hystrix的断路器模式,具体使用后续会讲到。
相较于熔断这种直接的方式,降级或许会友好一点。当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复,这也是对应着Hystrix的后备处理
模式。比如这个时候有个热点新闻出现了,我们广播给用户让用户点击通过方法到服务端查询,由于太火了,服务发生了雪崩,这个时候可以通过服务降级,给用户一些友好的反馈,比如访问人数过多请稍后再查等等。
关于Hystrix的其他会在以后详细说明。
微服务网关——Zuul
假如这个时候我们注册了2个消费者(Consumer)、3个服务提供者(provicer),这个时候再加入一个Zuul就会变成这样:
首先,Zuul会先向Eureka注册,注册有啥好处呢?消费者都向Eureka注册了,Zuul如果注册不就可以拿到所有的消费者信息了么。
拿到信息有什么用呢?拿到信息可以获取所有的消费者的元数据(名称、IP、端口)
拿到元数据有什么用呢?拿到了元数据我们可以做路由映射,比如原来的用户调用了消费者接口 localhost:8001/student/update
请求,这样调用没有任何问题,但如果这样呢?localhost:9000/consumer1/studentInfo/update
,接口前多了一个consumer1,这个就是网关用法。
Zuul的用法有很多,比如可以过滤请求、服务器屏蔽、路径屏蔽、敏感请求头屏蔽。如果说路由功能是Zuul的基本的话,那么过滤器就是Zuul的利器了,毕竟所有请求都经过Zuul,那么我们可以通过各种过滤实现限流、灰度发布、权限控制等。
令牌桶限流
这也是Zuul可以实现的功能,解释一下什么是令牌桶限流,首先我们有个桶,如果里面没有满那么就会以一定固定的速率会往里面放令牌,一个请求过来首先要从桶中获得令牌,如果没有获取到,那么这个请求就会被拒绝,如果获取到就放行。
实现方法后续会详细讲到。
关于Zuul的其他
Zuul的过滤器功能肯定还不止上面的几种实现,它还可以通过权限校验,包括我上面提到的灰度发布等。
当然Zuul作为网关肯定存在单点问题,如果我要保证Zuul的高可用那么需要进行过Zuul的集群配置,这个时候可以借助一些额外的负载均衡器,比如Nginx。
后面也会详细讲其他的具体使用。
SpringCloud分布式配置中心——Spring Cloud Config
当我们微服务系统开始慢慢庞大起来,那么多消费者(Consumer)、服务提供者(Provicer)、服务注册中心(Eureka)、服务网关(Zuul)都会持有自己的配置,如果我们不进行统一管理,我们只能去每个应用下一个一个寻找配置文件然后修改配置文件再启动应用。
首先对于分布式系统而言我们就不应该去每个应用下修改配置文件,再者对重启应用来说,服务无法访问所以直接抛弃了可用性,这也是我们不愿意见到的。
所以这里要介绍Spring Cloud Config,既能实现配置文件的统一管理,又能在项目运行时动态的修改配置文件。
能进行配置管理的不止一种,大家可以根据项目需求选择合适的,比如有阿波罗、disconf等等,而且对于SCC来说有些地方不尽如人意。
Config是什么
Spring Cloud Config为分布式系统中的外部化配置提供服务器和客户端支持。使用Config服务器,可以在中心位置管理所有环境中应用程序的外部属性。
简单来说,Spring Cloud COnfig就是能将各个 应用/模块/系统
的配置文件存放到统一的地方进行管理(Git、SVN)。
然而我们的应用不是只有启动的时候才会进行配置文件的加载,那么我们Spring CLoud Config会暴露出一个接口给应用来获取他想要的配置文件,应用获取到配置文件然后再进行它的初始化工作,如图:
这里可能还会有一个疑问,如果我们去Git仓库里修改配置文件,那么依赖于这个配置文件的启动项是否也会更改呢?
答案是不会的。
这里我们一般使用Bus消息总线 + Spring Cloud Config
进行配置文件的动态刷新。
下面介绍消息总线。
消息总线——Spring Cloud Bus
消息总线是用于将服务和服务实例与分布式消息系统链接在一起的事件总线。在集群中传播状态更改很有用(如配置更改事件)
分布式消息队列,是对Kafka, MQ的封装
事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config联合实现热部署。相当于水浒传中日行八百里的神行太保戴宗,确保各个小弟之间消息保持畅通。
你可以简单的理解为Spring Cloud Bus的作用就是管理和广播分布式系统中的消息,也就是消息引擎系统中的广播模式。当然作为消息总线的Spring Cloud Bus可以做很多事而不仅仅是客户端刷新配置功能。
而拥有了Bus之后,我们只需要简单的请求和注解即可进行配置的动态修改了:
这篇文章大致介绍了微服务中的各个组件,他们有
- Eureka 服务注册与发现中心
- Ribbon 进程内负载均衡器
- Open Feign 服务调用映射
- Hystrix 服务降级熔断器
- Zuul 微服务网关
- Config 微服务统一配置中心
- Bus 消息总线
参考:
- wili百科(微服务):https://zh.wikipedia.org/zh/%E5%BE%AE%E6%9C%8D%E5%8B%99
- 百度百科(Eureka):https://baike.baidu.com/item/Eureka/22402835
- Netflix是什么:https://www.cnblogs.com/EasonJim/p/7795816.html
- SpringCloud知乎:https://zhuanlan.zhihu.com/p/95696180
- 百度百科(SpringCloud):https://baike.baidu.com/item/spring%20cloud/20269825
write end on 2020年2月27日19:01:03