目录
认识微服务
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署。架构简单,部署成本低,耦合度高。
分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。降低服务耦合,有利于服务升级拓展。但服务调用关系错综复杂,运维、监控、部署难度提高。
服务治理:服务拆分粒度如何? 服务集群地址如何维护? 服务之间如何实现远程调用? 服务健康状态如何感知?
微服务架构特征:
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
- 面向服务:微服务对外暴露业务接口,松耦合,扩展性好
- 自治:团队独立、技术独立、数据独立、部署独立
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
SpringCloud是目前国内使用最广泛的微服务框架。 SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
注册中心
Eureka注册中心
eureka的作用:
无论是消费者还是提供者,引入eureka-client依赖、知道eureka地址后,都可以完成服务注册
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
在order-service完成服务拉取,要去eureka-server中拉取user-service服务的实例列表,并且实现负载均衡。
#给RestTemplate这个Bean添加一个@LoadBalanced注解
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
负载均衡策略
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule# 负载均衡规则
开启饥饿加载
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: # 指定饥饿加载的服务名称
- userservice
Nacos注册中心
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。
服务注册到Nacos:
在cloud-demo父工程中添加spring-cloud-alilbaba的管理依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
添加nacos客户端依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置服务端地址
spring:
cloud:
nacos:
server-addr: 127.0.0.1:8848 # nacos服务地址
Nacos服务分级存储模型:
一级是服务,例如userservice
二级是集群,例如杭州或上海
三级是实例,例如杭州机房的某台部署了userservice的服务器
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离,例如:dev,sit,不同namespace下的服务互相不可见
nacos注册中心细节
Nacos与Eureka的区别:
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
配置中心
将配置交给Nacos管理的步骤:
- 在Nacos中添加配置文件
- 在微服务中引入nacos的config依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
- 在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件
spring: application: name: userservice profiles: active: dev # 环境 cloud: nacos: server-addr: localhost:8848 # nacos地址 config: file-extension: yaml # 文件后缀名
-
Nacos配置更改后,微服务可以实现热更新,方式: 通过@Value注解注入,结合@RefreshScope来刷新;或通过@ConfigurationProperties注入,自动刷新。注意事项: 不是所有的配置都适合放到配置中心,维护起来比较麻烦,建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
@Data @Component @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; }
多种配置的优先级
nacos集群搭建步骤:
- 搭建MySQL集群并初始化数据库表
- 下载解压nacos
- 修改集群配置(节点信息)、数据库配置
- 分别启动多个nacos节点
- nginx反向代理
Feign远程调用
Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign 其作用就是帮助我们优雅的实现http请求的发送,解决RestTemplate方式调用存在的问题。使用Feign的步骤如下:
1、引入依赖
<!--feign客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、在order-service的启动类添加注解开启Feign的功能
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(clients = UserClient.class,defaultConfiguration = DefaultFeignConfiguration.class)
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
3、编写Feign客户端
@FeignClient(value = "userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
4、用Feign客户端代替RestTemplate
@Autowired
private UserClient userClient;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.用Feign远程调用
User user = userClient.findById(order.getUserId());
// 3.封装user到Order
order.setUser(user);
// 4.返回
return order;
}
5、默认不支持连接池,配置Feign添加HttpClient的支持
<!--引入HttpClient依赖-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
feign:
httpclient:
enabled: true # 支持HttpClient的开关
max-connections: 200 # 最大连接数
max-connections-per-route: 50 # 单个路径的最大连接数
服务网关
网关功能: 身份认证和权限校验、服务路由、负载均衡、请求限流。
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。搭建网关服务的步骤:
1、引入SpringCloudGateway的依赖和nacos的服务发现依赖
<!--nacos服务注册发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--网关gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2、编写路由配置及nacos地址,这里只配置了路径断言,按照路径规则匹配
server:
port: 10010
spring:
application:
name: gateway
cloud:
nacos:
server-addr: http://localhost/nacos # nacos地址
gateway:
routes:
- id: user-service # 路由标示,必须唯一
uri: lb://userservice # 路由的目标地址
predicates: # 路由断言,判断请求是否符合规则
- Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
- id: order-service
uri: lb://orderservice
predicates:
- Path=/order/**
default-filters:# 默认过滤器,会对所有的路由请求都生效
- AddRequestHeader=Truth,Itcast is freaking awesome!# 添加请求头
3、GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。Spring提供了31种不同的路由过滤器工厂。
4、全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。 区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。 定义方式是实现GlobalFilter接口。
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
ServerHttpRequest request = exchange.getRequest();
MultiValueMap<String, String> params = request.getQueryParams();
// 2.获取参数中的 authorization 参数
String auth = params.getFirst("authorization");
// 3.判断参数值是否等于 admin
if ("admin".equals(auth)) {
// 4.是,放行
return chain.filter(exchange);
}
// 5.否,拦截
// 5.1.设置状态码
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
// 5.2.拦截请求
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return -1;//每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
}
}
5、限流过滤器
对应用服务器的请求做限制,避免因过多请求而导致服务器过载甚至宕机。限流算法常见的包括两种:
- 计数器算法,又包括窗口计数器算法、滑动窗口计数器算法:将时间划分为多个窗口; 在每个窗口内每有一次请求就将计数器加一,当时间到达下一个窗口时,计数器重置。 如果计数器超过了限制数量,则本窗口内所有的请求都被丢弃
- 漏桶算法(Leaky Bucket):将每个请求视作"水滴"放入"漏桶"进行存储;"漏桶"以固定速率向外"漏"出请求来执行,如果"漏桶"空了则停止"漏水”;如果"漏桶"满了则多余的"水滴"会被直接丢弃
- 令牌桶算法(Token Bucket) :以固定的速率生成令牌,存入令牌桶中,如果令牌桶满了以后,多余令牌丢弃;请求进入后,必须先尝试从桶中获取令牌,获取到令牌后才可以被处理;如果令牌桶中没有令牌,则请求等待或丢弃
微服务保护
雪崩问题:服务提供者I发生了故障,依赖服务I的业务请求被阻塞,越来越多的用户请求到来,越来越多的tomcat线程会阻塞,会导致服务器资源耗尽,从而导致所有其它服务都不可用,那么当前服务也就不可用了。依赖于当前服务的其它服务随着时间的推移,最终也都会变的不可用,形成级联失败,雪崩就发生了
解决雪崩问题的常见方式有四种:
- 超时处理:设定超时时间,请求超过一定时间没有响应就返回错误信息,不会无休止等待
- 仓壁模式:限定每个业务能使用的线程数,避免耗尽整个tomcat的资源,因此也叫线程隔离
- 断路器模式:由断路器统计业务执行的异常比例,如果超出阈值则会熔断该业务,拦截访问该业务的一切请求。
- 流量控制:限制业务访问的QPS,避免服务因流量的突增而故障。
限流是对服务的保护,避免因瞬间高并发流量而导致服务故障,进而避免雪崩。是一种预防措施。超时处理、线程隔离、降级熔断是在部分服务故障时,将故障控制在一定范围,避免雪崩。是一种补救措施。
Sentinel是阿里巴巴开源的一款微服务流量控制组件。
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
先运行java -Dserver.port=8080 -jar sentinel-dashboard-1.8.1.jar ,访问http://localhost:8080页面,就可以看到sentinel的控制台了,账号和密码,默认都是:sentinel,然后在需接入的微服务中引入sentinel依赖和修改yaml配置,访问微服务的任意端点即可触发sentinel的监控
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
server:
port: 8088
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
流量控制
默认情况下sentinel会监控SpringMVC的每一个端点(Endpoint,也就是controller中的方法),因此SpringMVC的每一个端点(Endpoint)就是调用链路中的一个资源。流控、熔断等都是针对簇点链路中的资源来设置的,因此我们可以点击对应资源后面的按钮来设置规则。
流控模式:
- 直接:对当前资源限流
- 关联:高优先级资源触发阈值,对低优先级资源限流。
- 链路:阈值统计时,只统计从指定资源进入当前资源的请求,是对请求来源的限流
流控效果:
- 快速失败:达到阈值后,新的请求会被立即拒绝并抛出FlowException异常。是默认的处理方式。
- warm up:预热模式,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
- 排队等待:让所有的请求按照先后次序排队执行,两个请求的间隔不能小于指定时长
FeignClient整合Sentinel
微服务远程调用都是基于Feign来完成的,因此我们需要将Feign与Sentinel整合,在Feign里面实现线程隔离和服务熔断。
Feign整合Sentinel的步骤:
- 在application.yml中配置:feign.sentienl.enable=true
- 给FeignClient编写失败降级逻辑FallbackFactory并注册为Bean
- 将FallbackFactory配置到FeignClient
feign:
sentinel:
enabled: true # 开启feign对sentinel的支持
@Slf4j
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User findById(Long id) {
log.error("查询用户异常", throwable);
return new User();
}
};
}
}
@Bean
public UserClientFallbackFactory userClientFallbackFactory(){
return new UserClientFallbackFactory();
}
@FeignClient(value = "userservice", fallbackFactory = UserClientFallbackFactory.class)
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
线程隔离配置
熔断降级
熔断降级是解决雪崩问题的重要手段。其思路是由断路器统计服务调用的异常比例、慢请求比例,如果超出阈值则会熔断该服务。即拦截访问该服务的一切请求;而当服务恢复时,断路器会放行访问该服务的请求。断路器控制熔断和放行是通过状态机来完成的,断路器熔断策略有三种:慢调用、异常比例、异常数。
状态机包括三个状态:
- closed:关闭状态,断路器放行所有请求,并开始统计异常比例、慢请求比例。超过阈值则切换到open状态
- open:打开状态,服务调用被熔断,访问被熔断服务的请求会被拒绝,快速失败,直接走降级逻辑。Open状态5秒后会进入half-open状态
- half-open:半开状态,放行一次请求,根据执行结果来判断接下来的操作。请求成功:则切换到closed状态,请求失败:则切换到open状态
授权规则
可以对调用方的来源做控制,有白名单和黑名单两种方式。
- 白名单:来源(origin)在白名单内的调用者允许访问
- 黑名单:来源(origin)在黑名单内的调用者不允许访问
规则持久化:
sentinel的所有规则都是内存存储,重启后所有规则都会丢失。在生产环境下,我们必须确保这些规则的持久化,避免丢失。规则是否能持久化,取决于规则管理模式,sentinel支持三种规则管理模式:
- 原始模式:Sentinel的默认模式,将规则保存在内存,重启服务会丢失。
- pull模式:控制台将配置的规则推送到Sentinel客户端,而客户端会将配置规则保存在本地文件或数据库中。以后会定时去本地文件或数据库中查询,更新本地规则
- push模式:控制台将配置规则推送到远程配置中心,例如Nacos。Sentinel客户端监听Nacos,获取配置变更的推送消息,完成本地配置更新