原文地址:https://duktig.cn/archives/71/
随着互联网的发展,网站应用的规模不断扩大。需求的激增,带来的是技术上的压力。系统架构也因此不断的演进、升级、迭代。从单一应用,到垂直拆分,到分布式服务,到SOA,以及现在火热的微服务架构。
当网站流量很小时,只需一个应用,将所有功能都部署在一起(业务没有进行拆分,都写同一个项目工程里面),以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键。例如传统的SSH或SSM架构。
优点:
缺点:
当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,可以根据业务功能对系统进行拆分:
优点:
缺点:
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。
优点:
缺点:
分布式架构出现了什么问题?
于是出现了SOA架构
SOA(Service Oriented Architecture) :面向服务的架构
通俗的理解为面向与业务逻辑层开发,将共同的业务逻辑抽取出来形成一个服务,提供给其他服务接口进行调用,服务与服务之间调用使用rpc远程技术。
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
服务治理要做什么?
缺点:
关于DevOps思想参考:DevOps到底是什么意思? (本篇文章对比瀑布式、敏捷和DevOps开发,由浅入深进行各种实例论证介绍,文末也结合了微服务,很nice!)
前面说的SOA,英文翻译过来是面向服务。微服务,似乎也是服务,都是对系统进行拆分。因此两者非常容易混淆,但其实却有一些差别:
微服务的特点:
微服务架是从SOA架构演变过来,比SOA架构粒度会更加精细,让专业的人去做专业的事情(专注),目的提高效率,每个服务于服务之间互不影响,微服务架构中,每个服务必须独立部署,互不影响,微服务架构更加体现轻巧、轻量级,是适合于互联网公司敏捷开发。
微服务结构图:
参看:面试官问我:SOA架构和微服务架构的区别是什么?我居然答错了…
无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?
常见的远程调用方式有以下2种:
RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型代表
Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿,优势是对服务的提供和调用方没有任何技术限定,自由灵活,更符合微服务理念。
现在热门的Rest风格,就可以通过http协议来实现。
如果公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。
相反,如果公司的技术栈多样化,而且更青睐Spring家族,那么SpringCloud搭建微服务是不二之选。在我们的项目中,我们会选择SpringCloud套件,因此我们会使用Http方式来实现服务间调用。
关于RPC和HTTP参看:
关于SpringCloud和Dubbo如何选择参看:
既然微服务选择了Http,那么我们就需要考虑自己来实现对请求和响应的处理。不过开源世界已经有很多的http客户端工具,能够帮助我们做这些事情,例如:
接下来,不过这些不同的客户端,API各不相同
Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:
SpringCloud是基于SpringBoot基础之上开发的微服务框架,SpringCloud是一套目前非常完整的微服务解决方案框架。
SpringCloud=分布式微服务架构的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶
SpringCloud将现在非常流行的一些技术整合到一起,实现了诸如:
In short, the microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms, often an HTTP resource API. These services are built around business capabilities and independently deployable by fully automated deployment machinery. There is a bare minimum of centralized management of these services, which may be written in different programming languages and use different data storage technologies.——James Lewis and Martin Fowler (2014)
简言之:
在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务于服务之间依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册。
传统分布式架构需要对外暴露自己的地址,而调用者需要记录提供者的地址。如果地址出现变更,还需要及时更新。服务少没有关系,但是在日益复杂的互联网环境,服务多起来,此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想背道而驰。
举个栗子:
这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。
此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息(联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。
此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!
服务治理主要的点是在服务的自动注册、发现、状态监控。
常用的服务治理框架:
在服务注册与发现中,有一个注册中心,当服务器启动的时候,会把当前自己服务器的信息 比如 服务地址通讯地址等以别名方式注册到注册中心上。
另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际的服务通讯地址,让后在实现本地rpc调用远程。
框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何RPC远程框架中,都会有一个注册中心存放服务地址相关信息(接口地址)
CPA理论:一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)。由于分区容错性在是分布式系统中必须要保证的,因此我们只能在A和C之间进行权衡,最多只能同时较好的满足两个。
Consistency(一致性):数据一致更新,所有节点在同一时间具有相同的数据
Availability(可用性):好的响应性能(保证每个请求不管成功或者失败都有响应)
Partition tolerance(分区容忍性) :可靠性(系统中任意信息的丢失或失败不会影响系统的继续运作)
因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
CA - 单点集群,满足—致性,可用性的系统,通常在可扩展性上不太强大。
CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
分析:
如果 C(一致性)是第一需求,那么A(可用性)的性能就不能保证。因为在数据同步保持请求结果相同的时候,或消耗时间,这时可用性就会降低,在数据同步的时候,不能保证能正常接收请求。
如果 A (可用性)是第一需求,那么 C (一致性) 就不能保证。只要有一个服务在,就能正常接受请求,但是对与返回结果便不能保证,原因是,在分布式部署的时候,数据一致的过程不可能想切线路那么快。
参考:主流注册中心ZooKeeper、Eureka、Consul 、Nacos对比
在实际环境中,我们往往对同一个服务进行集群。此时我们获取的服务列表中就会有多个,到底该访问哪一个呢?
一般这种情况下我们就需要编写负载均衡算法,在多个实例列表中进行选择。
常用的负载均衡器有:
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)。
nginx是客户端所有请求统一交给nginx,由nginx进行实现负载均衡请求转发,属于服务器端负载均衡。即请求有nginx服务器端进行转发。
Ribbon是从注册中心服务器端上获取服务注册信息列表,缓存到本地,然后在本地实现负载均衡策略。即在客户端实现负载均衡。
Ribbon——负载均衡 + RestTemplate调用。
大型复杂的分布式系统中,高可用相关的技术架构非常重要。高可用架构非常重要的一个环节,就是如何将分布式系统中的各个服务打造成高可用的服务,从而足以应对分布式系统环境中的各种各样的问题,避免整个分布式系统被某个服务的故障给拖垮。比如:
要解决这些棘手的分布式系统可用性问题,就涉及到了高可用分布式系统中的很多重要的技术,包括:资源隔离、限流与过载保护、熔断、优雅降级、容错、超时控制、监控运维等。
微服务中,服务间调用关系错综复杂,一个请求,可能需要调用多个微服务接口才能实现,会形成非常复杂的调用链路:
如图,一次业务请求,需要调用A、P、H、I四个服务,这四个服务又可能调用其它服务。
如果此时,某个服务出现异常:
例如微服务I发生异常,请求阻塞,用户不会得到响应,则tomcat的这个线程不会释放,于是越来越多的用户请求到来,越来越多的线程会阻塞:
服务器支持的线程和并发数有限,请求一直阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,形成雪崩效应。
服务降级:优先保证核心服务,而非核心服务不可用或弱可用。
用户的请求故障时,不会被阻塞,更不会无休止的等待或者看到系统崩溃,至少可以看到一个执行结果(例如返回友好的提示信息) 。
服务降级虽然会导致请求失败,但是不会导致阻塞,而且最多会影响这个依赖服务对应的线程池中的资源,对其它服务没有响应。
熔断机制的原理很简单,像家里的电路熔断器,如果电路发生短路能立刻熔断电路,避免发生灾难。在分布式系统中应用这一模式之后,服务调用方可以自己进行判断某些服务反应慢或者存在大量超时的情况时,能够主动熔断,防止整个系统被拖垮。
不同于电路熔断只能断不能自动重连,我们可以实现弹性容错,当情况好转之后,可以自动重连。这就好比魔术师把鸽子变没了容易,但是真正考验技术的是如何把消失的鸽子再变回来。
熔断机制目的为了保护服务,在高并发的情况下,如果请求达到一定极限(可以自己设置阔值)如果流量超出了设置阈值,让后直接拒绝访问,保护当前服务。使用服务降级方式返回一个友好提示,服务熔断和服务降级一起使用。
因为默认情况下,只有一个线程池会维护所有的服务接口,如果大量的请求访问同一个接口,达到tomcat 线程池默认极限,可能会导致其他服务无法访问。
解决服务雪崩效应:使用服务隔离机制(线程池方式和信号量),使用线程池方式实现
隔离的原理: 相当于每个接口(服务)都有自己独立的线程池,因为每个线程池互不影响,这样的话就可以解决服务雪崩效应。
可搭配服务降级使用。
每个服务接口,都有自己独立的线程池,每个线程池互不影响。
为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,或者请求超时,则会进行降级处理。
使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,当请求进来时先判断计数器的数值,若超过设置的最大线程个数则拒绝该请求,若不超过则通行,这时候计数器+1,请求返回成功后计数器-1。
服务限流就是对接口访问进行限制,常用服务限流算法令牌桶、漏桶。计数器也可以进行粗暴限流实现。
在前面了解了Ribbon的负载均衡策略,可以使用RestTemplate
大大简化远程调用时的代码:
String user = this.restTemplate.getForObject("http://service-provider/user/" + id, String.class);
如果止步于此,可能以后需要编写类似的大量重复代码,格式基本相同,无非参数不一样。有没有更优雅的方式,来对这些代码再次优化呢?
可以使用如下框架:
Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka, as well as Spring Cloud LoadBalancer to provide a load-balanced http client when using Feign. link
Feign是一个声明式WebService客户端。使用Feign能让编写Web Service客户端更加简单。它的使用方法是定义一个服务接口然后在上面添加注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
Feign 最早是由 Netflix 公司进行维护的,后来 Netflix 不再对其进行维护,最终 Feign 由社区进行维护,更名为 Openfeign。
OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping
等等。OpenFeign的@Feignclient
可以解析SpringMVC的@RequestMapping
注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
关于Feign 和 Openfeign可参看:花一个周末,掌握 SpringCloud OpenFeign 核心原理
使用服务注册中心Zookeeper/Nacos用以及服务注册与发现;而服务间通过Ribbon或OpenFeign实现服务的消费以及均衡负载。为了使得服务集群更为健壮,使用Sentienl的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。
在该架构中,我们的服务集群包含:内部服务Service A和Service B,他们都会注册与订阅服务至Eureka Server,而Open Service是一个对外的服务,通过均衡负载公开至服务调用方。我们把焦点聚集在对外服务这块,直接暴露我们的服务地址,这样的实现是否合理?
破坏了服务无状态特点。
为了保证对外服务的安全性,我们需要实现对服务访问的权限控制,而开放服务的权限控制机制将会贯穿并污染整个开放服务的业务逻辑,这会带来的最直接问题是,破坏了服务集群中REST API无状态的特点。
从具体开发和测试的角度来说,在工作中除了要考虑实际的业务逻辑之外,还需要额外考虑对接口访问的控制处理。
无法直接复用既有接口。
当我们需要对一个即有的集群内访问接口,实现外部服务访问时,我们不得不通过在原有接口上增加校验逻辑,或增加一个代理调用来实现权限控制,无法直接复用原有的接口。
微服务下,一个系统被拆分为多个服务,但是像安全认证,流量控制,日志,监控等功能是每个服务都需要的,没有网关的话,我们就需要在每个服务中单独实现,这使得我们做了很多重复的事情并且没有一个全局的视图来统一管理这些功能。
综上:一般情况下,网关一般都会提供请求转发、安全认证(身份/权限认证)、流量控制、负载均衡、容灾、日志、监控这些功能。
上面介绍了这么多功能实际上网关主要做了一件事情:请求过滤 。权限校验、流量控制这些都可以通过过滤器实现,请求转也是通过过滤器实现的。
网关为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
常用的网关技术:
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行。(大量的配置文件)
当一个系统中的配置文件发生改变的时候,我们需要重新启动该服务,才能使得新的配置文件生效,所以我们需要实现微服务中的所有系统的配置文件的统一管理,而且还可以实现当配置文件发生变化的时候,系统会自动更新获取新的配置。(集中式的、动态的配置管理设施)
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。
简而言之,是当配置文件发生变更时,需要周知所有订阅的服务器,配置信息已经变更了。否则,需要一个一个手动刷新,太~~
一般常与分布式配置中心联合使用。
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用来协同产生最后的请求结果,每一个前段请求都会形成一条复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。
分布式追踪正在被越来越多的应用所采用。分布式追踪可以通过对微服务调用链的跟踪,构建一个从服务请求开始到各个微服务交互的全部调用过程的视图。用户可以从中了解到诸如应用调用的时延,网络调用(HTTP,RPC)的生命周期,系统的性能瓶颈等等信息。
参看:一文读懂微服务监控之分布式追踪
Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,并引入了发布-订阅、消费组、分区这三个核心概念。通过使用 Spring Cloud Stream,可以有效简化开发人员对消息中间件的使用复杂度,让系统开发人员可以有更多的精力关注于核心业务逻辑的处理。但是目前 Spring Cloud Stream 只支持 RabbitMQ 和 Kafka 的自动化配置。
参看: