微服务架构概念
服务治理
服务治理就是进行服务的自动化管理,其核心是服务的自动注册与发现。
服务注册:服务实例将自身服务信息注册到注册中心。
服务发现:服务实例通过注册中心,获取到注册到其中的服务实例的信息,通过这些信息去请求它们提供的服务。
服务剔除:服务注册中心将出问题的服务自动剔除到可用列表之外,使其不会被调用到。
服务调用
在微服务架构中,通常存在多个服务之间的远程调用的需求。目前主流的远程调用技术有基于HTTP的RESful
接口以及基于TCP
的的RPC
协议。
REST(Representational State Transfer)
这是一种HTTP
调用的格式,更标准,更通用,无论哪种语言都支持http
协议
RPC (Remote Promote Call)
一种进程间通信方式。允许像调用本地服务一样调用远程服务。RPC框架的主要目标就是让远程服务调用更简单、透明。RPC框架负责屏蔽底层的传输方式、序列化方式和通信细节。开发人员在使用的时候只需要了解谁在什么位置提供了什么样的远程服务接口即可,并不需要关心底层通信细节和调用过程。
区别和联系
服务网关
随着微服务的不断增多,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信可能出现:
1.客户端需要调用不同的url
地址,增加难度。
2.在一定的场景下,存在跨域请求的问题。
3.每个微服务都需要进行单独的身份认证针对这些问题,API
网关顺势而生。
API网关直面意思是将所有API
调用统一接入到API网关层,由网关层统一接入和输出。一个网关的基本功能有:统一接入、安全防护、协议适配、流量管控、长短链接支持、容错能力。有了网关之后,各个API服务提供团队可以专注于自己的的业务逻辑处理,而API
网关更专注于安全、流量、路由等问题。
服务容错
在微服务当中,一个请求经常会涉及到调用几个服务,如果基中某个服务不可用,没有做服务容错的话,极有可能会造成—连串的服务不可用,这就是雪崩效应。我们没法预防雪崩效应的发生,只能尽可能去做好容错。
服务容错的三个核心思想是:
不被外界环境影响
不被上游请求压垮
不被下游响应拖垮
/
链路追踪
随着微服务架构的流行,服务按照不同的维度进行拆分,一次请求往往需要涉及到多个服务。互联网应用构建在不同的软件模块集上,这些软件模块,有可能是由不同的团队开发、可能使用不同的编程语言来实现、有可能布在了几千台服务器,横跨多个不同的数据中心。因此,就需要对一次请求涉及的多个服务链路进行日志记录,性能监控即链路追踪。
SpringcloudAlibaba组件
Nacos
下载安装
# 下载源码
git clone
https://github.com/alibaba/nacos.git
启动
# Linux
./startup.sh -m standalone
# Windows
startup.cmd
导入Nacos
依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
application.yml文件配置
spring: application: name: user cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: SH //集群
发现这个服务的各个实例并获取每个实例的host
和port
代码如下
@Autowired DiscoveryClient discoveryClient; @GetMapping("/user/{id}") public User user(@PathVariable("id") Long id){ List<ServiceInstance> user1 = discoveryClient.getInstances("user"); for (ServiceInstance serviceInstance : user1) { System.out.println("http://"+serviceInstance.getHost()+":" +serviceInstance.getPort()); } }
负载均衡
通俗的讲,负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。根据负载均衡发生位置的不同,一般分为服务端负载均衡和客户端负载均衡。
服务端负载均衡指的是发生在服务提供者—方,比如常见的nginx负载均衡。
客户端负载均衡指的是发生在服务请求的一方,也就是在发送请求之前已经选好了由哪个实例处理请求。
Ribbon
负载均衡使用Ribbon
组件,为Springcloud
中的一个组件
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
配置文件
@Configuration public class RestTemplateConfiguration { @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
在不同的服务调用的时候,只需要调用服务名就可以调用该服务,其中是以轮询的方式访问服务中的各个实例。这里设置一个订单微服务和一个用户微服务,通过查询订单返回订单信息,订单中有用户信息,所有需要根据订单中的用户id调用用户微服务进行查询然后进行赋值返回订单信息,代码如下:
@Autowired OrdersMapper ordersMapper; String url="http://user/user/"; @Autowired RestTemplate restTemplate; @GetMapping("/order/{id}") public Orders orders(@PathVariable("id") Long id){ Orders orders = ordersMapper.selectById(id); User forObject = restTemplate.getForObject(url + orders.getUserId(), User.class); User user = feign.findbyId(orders.getUserId()); orders.setUser(user); return orders; }
上面是轮询模式,如果负载均衡改成随机模式,可以在订单服务中的application.yml
文件中进行下面配置:
user: //调用的微服务名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Fegin
Feign
是Spring Cloud
提供的一个声明式的伪Http
客户端,它使得调用远程服务就像调用本地服务一样简单,只需要创建—个接口并添加—个注解即可。
Nacos
很好的兼容了Feign
, Feign
默认集成了Ribbon
,所以在Nacos
下使用Eegin
默认就实现了负载均衡的效果。
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starte
r-openfeign</artifactId>
</dependency>
在Springboot
主启动类上添加
@EnableFeignClients
配置接口
@FeignClient("user")//指定调用哪个微服务名称 public interface Feign { @GetMapping("/user/{id}") User findbyId(@PathVariable("id") Long id); }
修改对应上面的代码
@Autowired Feign feign; @GetMapping("/order/{id}") public Orders orders(@PathVariable("id") Long id){ Orders orders = ordersMapper.selectById(id); User user = feign.findbyId(orders.getUserId()); orders.setUser(user); return orders; }
Sentinel
Sentinel
是阿里巴巴开源的一款断路器实现,本身在阿里内部已经被大规模采用,非常稳定。
在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,但是由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大是的网络涌入会导致瘫痪。这里通过举例子说明,上面的代码假如订单服务在调用用户服务的时候,发生异常导致线程无法释放请求堵塞在这里,然后在高并发场景下,大量的请求又都会占用在这里,就会导致系统崩溃。
高并发测试
然后我们来测试一下高并发情况,首先修改订单服务的调用用户微服务的方法,修改代码如下
@GetMapping("/order/{id}") public Orders orders(@PathVariable("id") Long id){ Orders orders = ordersMapper.selectById(id); User user = feign.findbyId(orders.getUserId()); //模拟调用延迟 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } orders.setUser(user); return orders; }
然后添加一个测试方法
1. @GetMapping("/test") public String test(){ return "测试高并发"; }
在访问订单微服务的test方法的时候访问速度特别快,然后使用Jmeter
进行压测
在进行压测的时候然后访问test
方法一直在打圈,然后验证了我们的实验结果。在分布式系统中,由于网络原因或自身的原因,服务一般无法保证100%可用。如果一个服务出现了问题,调用这个服务就会出现线程阻塞的情况,此时若有大量的请求涌入,就会出现多条线程阻塞等待,进而导致服务瘫痪。由于服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩效应”。
雪崩发生的原因多种多样,有不合理的容量设计,或者是高并发下某一个方法响应变慢,亦或是某台机器的资源耗尽。我们无法完全杜绝雪崩源头的发生,只有做好足够的容错,保证在一个服务发生问题,不会影响到其它服务的正常运待。也就是“雪落而不雪崩”。
容错方案
要防止雪崩的扩散,我们就要做好服务的容错,容错说白了就是保护自己不被猪队友拖垮的一些措施,下面介绍常见的服务容错思路和组件。常见的容错思路有隔离、超时、限流、熔断、降级这几种,下面分别介绍一下。
隔离
它是指将系统按照一定的原则划分为若干个服务模块,各个模块之间相对独立,无强依赖。当有故障发生时,能将问题和影响隔离在某个模块内部,而不扩散风险,不波及其它模块,不影响整体的系统服务。常见的隔离方式有:线程池隔离和信号量隔离。
这里说明一下线程池隔离方法,为微服务中的每个方法分配一个线程池如下图
超时
在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,就断开请求,释放掉线程。
限流
限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。
熔断
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
服务熔断—般有三种状态:。
熔断关闭状态(Closed)
服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。
熔断开启状态(Open)
后续对该服务接口的调用不再经过网络,直接执行本地的fallback
方法。
半熔断状态(Half-Open)
尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。
降级
降级其实就是为服务提供一个托底方案,一旦服务无法正常调用,就使用托底方案。
Sentinel入门
Sentinel
(分布式系统的流量防卫兵)是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来保护服务的稳定性。
Sentinel
的主要功能就是容错,主要体现为下面这三个:
流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的数据。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流星进行控制。Sentinel
作为一个调配器,可以根据需要把随机的请求调整成合适的形状。
熔断降级
当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。
Sentinel
对这个问题采取了两种手段:
通过并发线程数进行限制
Sentinel
通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel
还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
Sentinel和 Hystrix的区别
两者的原则是一致的,都是当一个资源出现问题时,让其快速失败不要波及到其它服务但是在限制的手段上,确采取了完全不一样的方法:
Hystrix
采用的是线程池隔离的方式,优点是做到了资源之间的隔离,缺点是增加了线程切换的成本。Sentinel
采用的是通过并发线程的数基和响应时间来对资源做限制。
系统负载保护
Sentinel同时提供系统维度的自适应保护能力。当系统负载较高的时候,如果还持续让请求进入可能会导致系统崩溃,无法响应。在集群环境下,会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,Sentinel提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
整合Sentinel
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
从Releases · alibaba/Sentinel · GitHub
中下载sentinel
客户端,然后java -jar
运行即可,一般默认端口为8080端口,访问8080端口即可,账号密码都是sentinel
。
application.yml
文件中注册,这里在用户微服务里进行注册。sentinel
客户端就可以检测到。
spring: application: name: user cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: SH sentinel: transport: dashboard: localhost:8080
在客户端如何使用限流规则和降级规则等等,由于不太好写这方面的操作,这里进行省略。
Feign整合Sentinel
Sentinel
适配了 Feign
组件。但默认是关闭的。需要在配置文件中配置打开它,在配置文件增加以下代码:
1.feign: sentinel: enabled: true
容错类
//容错的工厂类,泛型传递对应的接口 @Component @Slf4j public class Fallback implements FallbackFactory<Feign> { public Feign create(final Throwable throwable) { return new Feign() { public User findbyId(Long id) { log.error(String.valueOf(throwable)); User user=new User(); user.setId((long) -1); return user; } }; } }
Feign接口
@FeignClient(value = "user",fallbackFactory = Fallback.class)//指定调用哪个微服务名称 public interface Feign { @GetMapping("/user/{id}") User findbyId(@PathVariable("id") Long id); }
订单服务业务修改代码如下
@GetMapping("/order/{id}") public Orders orders(@PathVariable("id") Long id){ Orders orders = ordersMapper.selectById(id); // User forObject = restTemplate.getForObject(url + orders.getUserId(), User.class); User user = feign.findbyId(orders.getUserId()); if(user.getId()==-1l) { orders.setUser(user); return orders; } orders.setUser(user); return orders; }
这篇文章到此结束,未完待续,剩余内容下篇文章见