一、认识微服务
1.服务架构演变
单体架构
将业务的所有功能集中在一个项目中开发,打成一个包部署,适合小型项目,如:学生管理系统
优点:
- 架构简单
- 部署成本低
缺点:
-
耦合度高
-
拓展性差
分布式架构
根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,成为一个服务。适合大型互联网项目,如:拼多多、京东、淘宝。
优点:
- 降低服务耦合
- 有利于服务升级拓展
缺点:
-
架构复杂
-
难度大
分布式架构需要考虑的问题
- 服务拆分粒度?
- 服务集群地址如何维护?
- 服务之间如何实现远程调用?
- 服务健康状态如何感知?
微服务
微服务是一种经过良好架构设计的分布式架构方案。
微服务架构特征:
-
单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发 -
面向服务:微服务对外暴露业务接口
-
自治:团队独立、技术独立、数据独立、部署独立
2.微服务技术对比
| Dubbo | SpringCloud | SpringCloudAlibaba | |
|---|---|---|---|
| 注册中心 | zookeeper、Redis | Eureka、Consul | Nacos、Eureka |
| 服务远程调用 | Dubbo协议 | Feign(http协议) | Dubbo、Feign |
| 配置中心 | 无 | SpringCloudConfig | SpringCloudConfig、Nacos |
| 服务网关 | 无 | SpringCloudGateway、Zuul | SpringCloudGateway、Zuul |
| 服务监控和保护 | dubbo-admin,功能弱 | Hystrix | Sentinel |
企业需求
SpringCloud + Feign
-
使用SpringCloud技术栈
-
服务接口采用Restful风格
-
服务调用采用Feign方式
SpringCloudAlibaba + Feign
- 使用SpringCloudAlibaba技术栈
- 服务接口采用Restful风格
- 服务调用采用Feign方式
SpringCloudAlibaba + Dubbo
- 使用SpringCloudAlibaba技术栈
- 服务接口采用Dubbo协议标椎
- 服务调用采用Dubbo方式
SpringCloudAlibaba + Dubbo
-
基于Dubbo老旧技术体系
-
服务接口采用Dubbo协议标椎
-
服务调用采用Dubbo方式
3.SpringCloud
- SpringCloud是目前国内使用最广泛的微服务框架。官网地址:spring.io/projects/sp…
- SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配
| - | - |
|---|---|
| 服务注册发现 | Eureka、Nacos、Consul |
| 服务远程调用 | OpenFeign、Dubbo |
| 服务链路监控 | Zipkin、Sleuth |
| 统一配置管理 | SpringCloudConfig、Nacos |
| 统一网关路由 | SpringCloudGateway、Zuul |
| 流控、降级、保护 | Hystix、Sentinel |
二、服务拆分与远程调用
1.服务拆分注意事项
- 不同的微服务,不要重复开发相同的业务
- 微服务数据独立,不要访问其他微服务的数据库
- 微服务可以将自己的业务暴露为接口,供其他微服务调用
总结
- 微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同的业务
- 微服务可以将业务暴露为接口,供其他微服务使用
- 不同微服务都应该有自己的独立的数据库
2.案例:微服务远程调用-查询订单
1.注册 RestTemplate
在 order-service 的 OrderApplication 中注册 RestTemplate
@MapperScan("cn.wow.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
// 在此进行注册
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.服务远程调用 RestTemplate
修改 order-service 中的 OrderService 的 queryOrderById 方法
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
//在此添加
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.利用 RestTemplate 发起 http 请求,查询用户
// 2.1 url 路径
String url = MicroServicesUrl.UserService + "/user/" + order.getUserId();
// 2.2 发送 http 请求,实现远程调用
User user = restTemplate.getForObject(url,User.class);
// 3.封装 user 到 Order
order.setUser(user);
// 4.返回
return order;
}
}
3.微服务远程调用
1.微服务调用方式
- 基于 RestTemplate 发起的 http 请求实现远程调用
- http 请求做远程调用是与语言请求无关的调用,只要知道对方的 IP、端口、接口路径、请求参数即可
2.提供者与消费者
- 服务提供者:一次业务中,被其他微服务调用的服务。(提供接口给其他服务)
- 服务消费者:一次业务中,调用其他微服务的服务。(调用其他微服务提供的接口)
- 提供者与消费者角色其实是**
相对**的 - 一个服务可以同时是服务提供者与服务消费者
三、Eureka注册中心
1.远程调用的问题
-
服务消费者该如何获取服务提供者的地址信息?
- 服务提供者启动时向eureka注册自己的信息
- eureka保存这些信息
- 消费者根据服务名称向eureka拉取提供者信息
-
如果有多个服务提供者,消费者该如何选择?
- 服务消费者利用负载均衡算法,从服务列表中挑选一个
-
消费者如何得知服务提供者的健康状态?
-
服务提供者会每个30秒向 EurekaServer 发送心跳请求,报告健康状态
-
eureka会更新记录服务列表信息,心跳不正常会被剔除
-
消费者就可以拉取到最新的信息
-
2.Eureka原理
Eureka注册中心
Eureka的作用
-
注册服务信息
-
客户端 eureka-client: # 服务提供者 user-service: # 心跳续约——> eureka-service ,每30秒一次 localhost:8081 localhost:8082 localhost:8083 # 服务消费者 order-service: localhost:8080 # 注册中心 eureka-server:
-
拉取服务 user-service 的信息
-
负载均衡
-
远程调用
3.搭建 EurekaServer
- 创建项目,引入 spring-cloud-starter-netflix-eureka-server的依赖
- org.springframework.cloud spring-cloud-starter-netflix-eureka-server 2.2.3.RELEASE
- 编写启动类,添加 @EnableEurekaServer 注解
- 添加application.yml文件,编写下面的配置:
- server: port: 10086 # 服务端口 spring: application: name: eureka-server # eureka 的服务名称 eureka: client: service-url: # eureka 的地址信息 defaultZone: http://127.0.0.1:10086/eureka/
4. 服务注册
注册user-service
- 将user-service项目引入 spring-cloud-starter-netflix-eureka-client的依赖
- org.springframework.cloud spring-cloud-starter-netflix-eureka-client 2.2.3.RELEASE
- 在 application.yml 文件,编写下面的配置
- spring: application: name: user-service eureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka/
5. 服务发现
在order-service完成服务拉取
服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡
-
修改 OrderService 的代码,修改访问的 url 路径,用服务名称代替 ip
-
String url = "http://userservice/user" + order.getUserId();
-
在 order-service 项目的启动类 OrderApplication 中的RestTemplate添加**
负载均衡**注解: -
@Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); }
四、Ribbon负载均衡
1.负载均衡流程
2.负载均衡策略
Ribbon的负载均衡规则是一个叫做 IRule 的接口来定义的,每一个子接口都是一种规则。
| 内置负载均衡规则类 | 规则描述 |
|---|---|
| RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡。 |
| AvailabilityFliteringRule | 对以下服务器进行忽略: 1. 在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为短路状态。短路状态将持续30秒,如果在此连接失败,短路的持续时间就会以几何级地增加。 2. 并发数过高的服务器,如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的..ActiveConnectionsLimit属性配置。 |
| WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重会影响服务器的选择。 |
| ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择,使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的服务做轮询。 |
| BestAvailableRule | 忽略哪些短路的服务器,并选择并发数较低的服务器。 |
| RandomRule | 随机选择一个可用的服务器。 |
| RetryRule | 重试机制的选择逻辑。 |
通过定义IRule实现可以修改负载均衡规则,有两种方式:
- 代码方式,在order-service的OrderApplication类中,定义一个新的IRule
- @Bean public IRule randomRule(){ return new RandomRule(); }
- 配置文件方式,在 order-service 的 application.yml 文件中,添加新的配置也可以修改规则:
- user-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
3.饥饿加载
Ribbon默认采用**懒加载**,即第一次访问时才回去创建 LoadBalanceClient ,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开始饥饿加载:
ribbon: eager-load: enabled: true # 开始饥饿加载 clients: user-service # 指定对 user-service 这个服务饥饿加载
4.总结
-
Ribbon负载均衡规则
- 规则接口是IRule
- 默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询
-
负载均衡自定义方式
- 代码发布:配置灵活,但修改时需要重新打包发布
- 配置方式:直观、方便、无需重新打包发布,但是无法做全局配置
-
饥饿加载
-
开启饥饿加载
-
指定饥饿加载的微服务名称
-
五、Nacos注册中心
1.认识和安装Nacos
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。
2.Nacos快速入门
启动
单机启动(非集群)
startup.cmd -m standalone
服务注册到 Nacos
- 在cloud-demo父工程中添加spring-cloud-alibaba的管理依赖
- com.alibaba.cloud spring-cloud-alibaba-dependencies 2.2.5.RELEASE import
- 注释掉order-service 与 user-service中原有的eureka依赖
- 添加 nacos 的客户端依赖
- com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery 2.2.5.RELEASE
- 修改user-service&order-service中的application文件,注释eureka地址,添加nacos地址:
- spring: cloud: nacos: server-addr: localhost:8848 # nacos 服务端地址
- 启动并测试
3.Nacos服务分级存储模型
服务调用尽可能选择本地集群的服务,跨集群调用延迟较高
本地集群不可访问时,再去访问其它集群
服务集群属性
- 修改 application.yml ,添加如下内容
- spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: SD #集群配置名称,也就是机房位置,例如:SD,山东
- 在 Nacos 控制台可以看到集群变化
临时实例与非临时实例
临时实例采用心跳检测
非临时实例 nacos 主动询问
spring: cloud: nacos: discovery: ephemeral: false # 设置为非临时实例
总结
-
Nacos服务分级存储模型
- 一级是服务,例如 user-service
- 二级是集群,例如 ShanDong 或者 ShangHai
- 三级是实例,例如杭州机房的某台部署了 user-service 的服务器
-
如何设置实例的集群属性
-
修改 application.yml 文件,添加 spring.cloud.nacos.discovery.cluster-name 属性即可
-
Nacos与Eureka的区别
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式。
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP模式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP模式
根据权重负载均衡
user-service: ribbon: # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # eureka 负载均衡规则 NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # nacos 负载均衡规则
实际部署中会出现这样的场景:
- 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求
Nacos 提供了权重配置来控制访问频率,权重越大访问频率越高,取值范围 [0,1],为0时不会被访问
4.Nacos环境隔离
环境隔离 - namespace
Nacos 中服务存储和数据存储的最外层都是一个名为 namespace 的东西,用来做最外层隔离
设置完成后,配置 id
spring: cloud: nacos: server-addr: localhost:8848 discovery: cluster-name: ShanDong namespace: 542ac362-6b65-4ca8-9276-164bfe5fb505
不同 namespace 下的服务不可见
六、Nacos配置管理
1.统一配置管理
- 配置更改热更新
配置步骤
配置获取的步骤如下:
-
项目启动
-
读取 bootstrap.yml 文件,读取nacos地址
-
读取nacos中的配置文件
-
读取本地配置文件 application.yml
-
创建spring容器
-
加载bean
-
引入Nacos的配置管理客户端依赖
-
com.alibaba.cloud spring-cloud-starter-alibaba-nacos-config
-
在 user-service中的 resource 目录添加一个 bootstrap.yml文件,这个文件时引导文件,优先级高于application.yml
-
spring: application: name: user-service profiles: active: dev # 开发环境,这里是 dev cloud: nacos: server-addr: localhost:8848 # Nacos 地址 config: file-extension: yaml # 文件后缀名
2.配置热更新
Nacos 中的配置文件变更后,微服务无需重启就可以感知,不过需要通过下面两种配置实现:
- 在 @Value 注入的变量所在类上添加注解
@RefreshScope - @Slf4j @RestController @RequestMapping("/user") @RefreshScope public class UserController { }
- 使用@ConfigurationProperties 注解
- @Component @Data @ConfigurationProperties(prefix = "pattern") public class PatternProperties { private String dateformat; }
3.多环境配置共享
微服务启动时会从nacos读取多个配置文件:
- 命名方式:[spring.application.name]-[spring.profiles.active].yaml,例如:user-service-dev.yaml
- 命名方式:[spring.application.name].yaml,例如 user-service.yaml
无论profile如何变化,[spring.application.name].yaml 文件一定会被加载,因此多环境共享配置可以写入这个文件。
4.搭建Nacos集群
踩坑记录
bootstrap.yml
spring: application: name: user-service profiles: active: dev # 开发环境,这里是 dev cloud: nacos: server-addr: localhost:80 # Nacos 地址,一定要加端口号 config: file-extension: yaml # 文件后缀名 enabled: false # 禁用本地
nacos最好一机启动一个
ngnix 配置最好要用 当前 ip
初始化 nacos sql
七、http 客户端 Feign
1.引入与使用
org.springframework.cloud spring-cloud-starter-openfeign添加注解
package cn.itcast.order; import com.netflix.loadbalancer.IRule; import com.netflix.loadbalancer.RandomRule; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableFeignClients @MapperScan("cn.itcast.order.mapper") @SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } /** * 创建 RestTemplate 并注入 Spring 容器,用于请求接口 */ @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); } }
UserClient.java
package cn.itcast.order.clients; import cn.itcast.feign.pojo.User; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @FeignClient("user-service") public interface UserClient { @GetMapping("/user/{id}") User findById(@PathVariable("id") Long id); }
order-service.java
package cn.itcast.order.service; import cn.itcast.order.clients.UserClient; import cn.itcast.order.mapper.OrderMapper; import cn.itcast.order.pojo.Order; import cn.itcast.feign.pojo.User; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class OrderService { @Autowired private OrderMapper orderMapper; @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; } }
2.自定义配置
| 类型 | 作用 | 说明 |
|---|---|---|
| feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE,BASIC,HEADERS,FULL |
| feign.codec.Decoder | 相应结果的解析器 | http 远程调用的结果做解析,例如解析json 字符串为 Java 对象 |
| feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过 http 请求发送 |
| feign.Contract | 支持的注解格式 | 默认是 SpringMVC 注解 |
| feign.Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用 Ribbon 重试 |
配置文件方式
feign: client: config: default: # 这里 default 就是全局配置,服务名称则是局部配置 loggerLevel: FULL 日志级别
Java 代码方式
首先声明一个 Bean
public class FeignClientConfiguration { @Bean public Logger.Level feignLogLevel(){ return Logger.Level.BASIC; } }
全局配置
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
局部配置
@EnableFeignClients(value = "user-service",configuration = FeignClientConfiguration.class)
3.性能优化
Feign 底层的客户端实现:
-
URLConnection: 默认实现,不支持连接池
-
Apache HttpClient: 支持连接池
-
OKHttp:支持连接池
因此优化 Feign 的性能主要包括:
-
使用连接池代替默认的 URLConnection
-
日志级别,最好用basic或 none
引入依赖
io.github.openfeign feign-httpclient
配置连接池
feign: client: config: default: # default 全局的配置 logger-level: BASIC # 日志级别,basic 是基本的请求与响应信息 httpclient: enabled: true # 开启 feign 对 httpClient 的支持 max-connections: 200 # 最大的连接数 max-connections-per-route: 50 # 每个路径的最大连接数
4.最佳实践
方式一 继承 不推荐:
给消费者的 FeignClient 和提供者 controller 定义统一的父接口作为标准。
-
服务紧耦合
-
父接口参数列表中的映射不会被继承
方式二 抽取:
将 FeignClient 抽取为独立模块,并且把接口有关的 POJO、默认的 Feign 配置都放到这个模块中,提供给所有消费者使用
-
首先创建一个 module,命名为 feign-api,然后引入 feign 的 starter 依赖
-
将 order-service 中编写的 UserClient、User、DefaultFeignConfiguration 都复制到 feign-api 项目中
-
在 order-service 中引入 feign-api 依赖
-
修改 order-service 中的所有与上述三个组件有关的 import 部分,改成导入 feign-api 中的包
-
重启测试
当定义的 FeignClient 不在 SpringBootApplication 的扫描包范围时,这些 FeignClient 无法使用,有两种方式解决:
方式一:指定 FeignClient 所在包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:制定 FeignClient 字节码
@EnableFeignClients(clients = {UserClient.class})
八、统一网关 Gateway
1.为什么需要网关
网关功能:
- 身份认证和权限校验
- 服务路由、负载均衡
- 请求限流
2.网关的技术实现
在 SpringCloud 中网关的实现包括两种:
- gateway
- zuul
Zuul 是基于 Servlet 的实现,属于阻塞式编程.
SpringCloudGateway 基于 Spring5 中提供的 WebFlux,属于响应式 编程的实现,具备更好的性能。
3.搭建网关
- 创建新的 module,引入 SpringCloudGateway 的依赖和 nacos 的服务发现依赖:
- com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery org.springframework.cloud spring-cloud-starter-gateway
- 编写路由配置及 nacos 地址
- server: port: 10010 spring: application: name: gateway # 服务名称 cloud: nacos: discovery: server-addr: localhost:80 # nacos 地址 gateway: routes: # 网关路由配置 - id: user-service # 路由 id,自定义,只要唯一几颗 # uri:http://127.0.0.1:8081 # 路由的目标地址 http 就是固定地址 uri: lb://user-service # 路由的目标地址,lb 后面是负载均衡,后面跟服务名称 predicates: # 路由断言,也就是请求是否符合路由规则的条件 - Path=/user/** # 这个是按照路径匹配,只要以 /user/ 开头就符合要求
4.路由断言工厂 Route Predicate Factory
网关路由可以配置的内容包括:
-
路由 id:路由唯一标识
-
uri: 路由目的地,支持 lb 和 http 两种
-
predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地 -
filter:路由过滤器,处理请求或响应
我们在配置文件中写的断言规则只是字符串,这些字符串会被 Predicate Factory 读取并处理,转变为路由判断的条件
例如Path=/user/**是按照路径匹配,这个规则是由 org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的
Spring 提供了 11 种基本的 Predicate 工厂
| 名称 | 说明 | 示例 |
|---|---|---|
| After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
| Before | 是某个时间点之前的请求 | |
| Between | 是某个时间点之间的请求 | |
| Cookie | 请求必须包含某些 cookie | - Cookie=chocolate, ch.p |
| Header | 请求必须包含某些 header | - Header=X-Request-ld,\d+ |
| Host | 请求必须包含某些 host | - Host=.somehost.org,.anotherhost.org |
| Method | 请求必须要是指定方式 | - Method=GET,POST |
| Path | 请求路径必须符合指定规则 | - Path=/red/{segment},/blue/** |
| Query | 请求参数必须包含指定参数 | - Query=name,Jack 或者 - Query=name |
| RemoteAddr | 请求者的 ip 必须是指定范围 | - RemoteAddr=192.168.1.1/24 |
| Weight | 权重处理 |
5.路由过滤器 GatewayFilter
GatewayFilter 是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
GatewayFilterFactories文档,共有 31 种过滤器
gateway
server: port: 10010 spring: application: name: gateway # 服务名称 cloud: nacos: discovery: server-addr: localhost:80 # nacos 地址 gateway: routes: # 网关路由配置 - id: user-service # 路由 id,自定义,只要唯一几颗 # uri:http://127.0.0.1:8081 # 路由的目标地址 http 就是固定地址 uri: lb://user-service # 路由的目标地址,lb 后面是负载均衡,后面跟服务名称 predicates: # 路由断言,也就是请求是否符合路由规则的条件 - Path=/user/** # 这个是按照路径匹配,只要以 /user/ 开头就符合要求 filters: # 过滤器 - AddRequestHeader=Truth,I am freaking awesome! # 添加请求头 - id: order-service uri: lb://order-service predicates: - Path=/order/**
@GetMapping("/{id}") public User queryById(@PathVariable("id") Long id, @RequestHeader(value = "Truth", required = false) String truth) { System.out.println("truth:" + truth); return userService.queryById(id); }
默认过滤器
server: port: 10010 spring: application: name: gateway # 服务名称 cloud: nacos: discovery: server-addr: localhost:80 # nacos 地址 gateway: routes: # 网关路由配置 - id: user-service # 路由 id,自定义,只要唯一几颗 # uri:http://127.0.0.1:8081 # 路由的目标地址 http 就是固定地址 uri: lb://user-service # 路由的目标地址,lb 后面是负载均衡,后面跟服务名称 predicates: # 路由断言,也就是请求是否符合路由规则的条件 - Path=/user/** # 这个是按照路径匹配,只要以 /user/ 开头就符合要求 # filters: # 过滤器 # - AddRequestHeader=Truth,I am freaking awesome! # 添加请求头 - id: order-service uri: lb://order-service predicates: - Path=/order/** default-filters: - AddRequestHeader=Truth,I am freaking awesome!
6.全局过滤器 GlobalFilter
全局过滤器的作用也是处理一切进入网关请求和微服务响应,与 GatewayFilter 的作用一样.
区别在于 GatewayFilter 通过配置定义,处理逻辑是固定的,而 GlobalFilter 的逻辑需要自己写代码实现.
定义方式是实现 GlobalFilter 接口
案例:定义全局过滤器,拦截并判断用户身份
需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
- 参数中是否有 authorization
- authorization 参数值是否为 admin
如果同时满足则放行,否则拦截
package cn.itcast.gateway; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.util.MultiValueMap; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; //@Order(-1) @Component public class AuthorizeFilter implements GlobalFilter, Ordered { @Override public Mono 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; } }
7.过滤器执行顺序
请求进入网关会碰到三类过滤器:当前路由的过滤器,DefaultFilter,GlobalFilter
请求路由后,会将当前路由过滤器和 DefaultFilter,GlobalFilter,合并到一个过滤器链中,排序后依次执行每个过滤器
-
每一个过滤器都必须指定一个 int 类型的 order 值,order 值越小,优先级越高,执行顺序越靠前
-
GlobalFilter 通过实现 Ordered 接口,或者添加@Order 注解来指定 order 值,由我们自己指定
-
路由过滤器和 defaultFilter 的 order 由 Spring 指定,默认是按照声明顺序从 1 递增
-
当过滤器的 order 值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter 的顺序执行
8.跨域问题处理
网关处理跨域采用的同样是CORS方案,需要进行简单的配置
server: port: 10010 spring: application: name: gateway # 服务名称 cloud: nacos: discovery: server-addr: localhost:80 # nacos 地址 gateway: routes: # 网关路由配置 - id: user-service # 路由 id,自定义,只要唯一几颗 # uri:http://127.0.0.1:8081 # 路由的目标地址 http 就是固定地址 uri: lb://user-service # 路由的目标地址,lb 后面是负载均衡,后面跟服务名称 predicates: # 路由断言,也就是请求是否符合路由规则的条件 - Path=/user/** # 这个是按照路径匹配,只要以 /user/ 开头就符合要求 filters: # 过滤器 - AddRequestHeader=Truth,I am freaking awesome! # 添加请求头 - id: order-service uri: lb://order-service predicates: - Path=/order/** default-filters: - AddRequestHeader=Truth,I am freaking awesome! globalcors: add-to-simple-url-handler-mapping: true # 解决 options 请求被拦截的问题 cors-configurations: '[/**]': allowed-origins: # 允许哪些网站跨域请求 - "http://localhost:8090" - "www.test.com" allowed-methods: - "GET" - "POST" - "DELETE" - "PUT" - "OPTIONS" allowed-headers: "*" # 允许在请求中携带的头信息 allow-credentials: true # 是否允许携带 cookie max-age: 360000 # 这次跨域检测的有效期
九、初始Docker
1.初识Docker
Docker是一个快速交付应用 运行应用的技术
Docker如何解决依赖的兼容问题?
-
将应用的Libs(函数库)、Deps(依赖)、配置与应用一起打包,形成可移植镜像
-
将每个应用放到一个隔离容器去运行,使用沙箱机制 , 避免相互干扰
Docker如何解决不同系统环境的问题?
-
Docker将用户程序与所需要调用的系统(比如Ubuntu)函数库一起打包
-
Docker运行到不同操作系统时,直接基于打包的库函数,借助于操作系统的Linux内核来运行
Docker和虚拟机的差异
- Docker是一个系统进程 ; 虚拟机是在操作系统中的操作系统
- Docker体积小,启动速度快, 性能好 ; 虚拟机体积大,启动速度慢,性能一般.
镜像和容器
镜像(Image): Docker将应用程序及其所需的依赖,函数库,环境,配置等文件打包再一起,称为镜像
容器(Container):镜像中的应用程序运行后形成的进程就是容器,只用Docker会给容器做隔离,对外不可见
Docker架构
Docker是一个CS架构
-
Server: Docker守护进程,负责处理Docker指令,管理镜像,容器等
-
Client:通过命令或者RestAPI向Docker服务端发送指令.可以在本地或远程向服务器发送指令.
安装Docker
安装yum
yum install -y yum-utils \ device-mapper-persistent-data \ lvm2 --skip-broken
本地镜像源
设置docker镜像源 yum-config-manager \ --add-repo \ mirrors.aliyun.com/docker-ce/l… sed -i 's/download.docker.com/mirrors.aliyun.com/docker-ce/g' /etc/yum.repos.d/docker-ce.repo yum makecache fast
安装
yum install -y docker-ce
2.Docker基本操作
容器相关命令
创建运行一个Nginx容器
去 docker hub 查看Ngnix容器的运行命令
docker run --name containerName -p 80:80 -d nginx
命令解读:
-
docker run: 创建并运行一个容器
-
--name:给容器起一个名字,比如叫做mn
-
-p: 将宿主机端口与容器端口映射,冒号左侧是宿主机端口,右侧是容器端口
-
-d:后台运行容器
-
nginx:镜像名称
查看容器日志
docker logs -f
参数f 可持续查看日志
查看容器状态
docker ps
进入Ngnix容器,修改HTML文件内容
docker exec -it mn bash
命令解读:
- docker exec:进入容器内部,执行一个命令
- -it:给当前进入的容器创建一个标准输入、输出终端,允许我们容器交互
- mn:要进入容器的名称
- bash:要进入容器后执行的命令。bash是一个linux终端交互命令
练习:创建一个Redis容器,支持数据持久化
3.数据卷
数据卷(volume)是一个虚拟目录,指向宿主文件系统中的某个目录
将容器与数据分离,解耦合,方便操作容器内数据,保证数据安全.
挂载
操作数据卷
数据卷操作的基本语法如下
docker volume [COMMAND]
docker volume命令是数据卷操作,根据命令后跟随的command来确定下一步的操作:
-
create 创建一个volume
-
inspect 显示一个或多个volume的信息
-
ls 列出所有的volume
-
prune 删除未使用的volume
-
rm 删除一个或者多个指定volume
启动mysql
docker run --name mysql -e MYSQL_ROOT_PASSWORD=wuao -p 3306:3306 -v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf -v /tmp/mysql/data:/var/lib/mysql -d mysql
docker run --name mn -d -p 80:80 -v /root/docker/nginx/html/:usr/share/nginx/html/ -v /root/docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf nginx
4.Dockerfile 自定义镜像
镜像结构
镜像是将应用程序及其需要的系统函数库,环境,配置,依赖打包而成
镜像是分层结构,每一层成为一个Layer
- BaseImage层:包含基本的系统库函数,环境变量,文件系统
- Entrypoint :入口,是镜像中应用启动的命令
- 其他:在 BaseImage 基础上添加依赖,安装程序,完成整个应用的安装和配置
Dockerfile
Dockerfile就是一个文本文件,其中包含了一个个的指令(Instruction),用指令来说明执行什么操作来构建镜像,每一个指令都会形成一个Layer,通过指令描述镜像的构建过程.
Dockerfile的第一行必须是FROM,从一个基础镜像来构建
基础镜像可以是基本操作系统,如Ubuntu,也可以是其他人制作好的镜像,例如 java:8-alpine
5.DockerCompose
-
Docker Compose 可以基于 Compose 文件帮我们快速的部署分布式应用,无需手动一个个创建和运行容器
-
Compose 文件是一个文本文件,通过指令定义集群中的每个容器如何运行
十、服务异步通讯
1.初识MQ
MQ(MessageQueue),中文是消息队列,字面上看就是存放消息的队列.也就是事件驱动架构中的Broker
同步调用出现的问题?
-
耦合度高,每次加入新的需求,都需要修改原来的代码
-
性能下降,调用者需要等待服务提供者相应,如果调用链过长则相应时间等于每次调用的时间之和
-
调用链中的每个服务在等待相应过程中,不能释放请求占用的资源,高并发场景下会极度浪费系统资源
-
级联失败,如果服务提供者出现问题,所有调用方都会跟着出问题,如同多米诺骨牌一样,迅速导致整个微服务群故障
异步调用方案
异步调用常见就是事件驱动模式
优势:
- 服务解耦
- 性能提升,吞吐量提高
- 服务没有强依赖,不担心级联失败问题
- 流浪削峰
缺点:
-
依赖于Broker的可靠性,安全性,吞吐能力
-
架构复杂了,业务没有明显的流程线,不好追踪管理
RabbitMQ
-
channel:操作MQ的工具
-
exchange:路由消息到队列中
-
queue:缓存消息
-
virtual host: 虚拟主机,是对queue,exchange等资源的逻辑分组
3.SpringAMQP
AMQP
Advanced Message Queuing Protocol,是用于在应用程序或之间传递业务消息的开放标准.该协议与平台无关,更符合微服务中独立性的要求
Spring AMQP
Spring AMQP 是基于AMQP协议定义的一套API规范,提供了模板来发送和接收消息.包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层默认实现
十一、Elasticsearch
1.Elasticsearch
Elasticsearch结合kibana,Logstash,Beats,也就是 elastic stack (ELK),被广泛应用在日志数据分析,实时监控等领域
Elasticsearch是elastic stack的核心,负责存储,搜索,分析数据
2.正向索引和倒排索引
elasticsearch 采用倒排索引:
-
文档(document):每条数据就是一个文档
-
词条(term):文档按照语义分成的词语