目录
一、服务架构演变
1.单体架构
将业务的所有功能集中在一个项目中开发,打成一个包部署
优点:架构简单、部署成本低
缺点:耦合度高扩展性差
2.分布式架构
根据业务功能对系统进行拆分,每一个业务模块作为独立项目开发,称为一个服务
优点:降低了耦合度、服务升级扩展方便
缺点:架构复杂、难度大
3.微服务
经过良好架构设计的分布式架构
优点:拆分粒度更小、服务更独立、耦合度更低
缺点:架构非常复杂、运维、监控、部署难度提高
特征:
1.单一职责:微服务差分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复开 发
2.面向服务:微服务对外暴露业务接口
3.自治:团队独立、技术独立、数据独立、部署独立
4.隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
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 |
二、初识分布式
1.远程服务调用
用户——>服务A:http://localhost:8080/A
用户——>服务B:http://localhost:8081/B
当服务A需要数据库B中数据时:服务A向服务B发送http://localhost:8081/B,服务A成为了服务B的用户。
步骤:
(1).注册RestTemplate
@Bean
@LoadBalanced //负载均衡注解
Public RestTemplate restTemplate(){
return new RestTemplate();
}
(2).利用RestTemplate发起http请求
String url = "http://localhost:8081/B"+查询的id;
//获取的数据是JSON形式,B.class指定数据转换为B类型
B b = restTemplate.getForObject(url,B.class);
2.提供者与消费者
服务提供者:一次业务中,被其他微服务调用的业务。
服务消费者:一次业务中,调用其他微服务的服务。.
提供者与消费者是相对而言的。
三、SpringCloud组件
1.Eureka注册中心
EurekaServer:服务端,注册中心
1.记录服务信息
2.心跳监控
EurekaClient:客户端
1.Provider:服务提供者
(1)注册自己的信息到注册中心
(2)30秒发送一次心跳到注册中心,保证服务正常
2.consumer:服务消费者
(1)根据服务名称从注册中心拉取服务列表
(2)基于服务列表做负载均衡,选中一个服务后发起远程调用
1.1搭建注册中心
启动类添加@EnableEurekaServer,开启注册中心
(1)引入SpringCloud为eureka提供的starter依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
(2)编写配置文件
server:
port: 10086
spring:
application:
name: eureka-server #注册中心名称
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka #注册中心
1.2服务注册
启动类添加@EnableEurekaClient,启动服务
(1)pom文件中,引入下面的eureka-client依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
(2)修改application.yml文件,添加服务名称、eureka地址:
spring:
application:
name: userservice #服务名称,可在远程调用时使用
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka #将服务注册到注册中心
2.Ribbon负载均衡原理
2.1负载均衡流程
IRule:可以通过定义IRule修改负载均衡规则,有两种方式:
1.代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
2.配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的<clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit属性进行配置。 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule(默认) | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑 |
2.2饥饿加载
Ribbon默认时采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: userservice #指定饥饿加载的服务名
小结
1. Ribbon 负载均衡规则l 规则接口是IRulel 默认实现是 ZoneAvoidanceRule ,根据 zone选择服务列表,然后轮询2. 负载均衡自定义方式l 代码方式:配置灵活,但修改时需要重新打包发布l 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置3. 饥饿加载l 开启饥饿加载l 指定饥饿加载的微服务名称
3.Nacos注册中心
3.1Windows安装
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
步骤:
1.将下载好的包解压到非中文目录下
2.端口配置在conf目录下的application.properties,Nacos的默认端口是8848,可以按照自己的意愿改变端口
3.在控制台进入bin目录,执行 startup.cmd -m standalone 启动
4.访问地址http://127.0.0.1:8848/nacos ,账号密码默认都是nacos
3.2Nacos注册中心
步骤:
1.在父工程中引入SpringCloudAlibaba的依赖,统一管理版本号
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
在子模块中引入nacos-discovery依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.配置Nacos地址
spring:
cloud:
nacos:
server-addr: localhost:8848
3.重启微服务后,登录nacos管理页面,可以看到微服务信息
3.3服务多级存储模型
服务跨集群调用:服务调用尽量选择本地集群服务,跨集群调用延迟较高,当本地集群不可用时再去访问其他集群
Spring:
cloud:
nacos:
server-addr: localhost:8848 #nacos服务端地址
discovery:
cluster-name: HEB #集群配置名称,可代表机房所在位置
1.Nacos服务分级存储模型①一级是服务,例如userservice②二级是集群,例如杭州或上海③三级是实例,例如杭州机房的某台部署了userservice的服务器2.如何设置实例的集群属性①修改application.yml文件,添加spring.cloud.nacos.discovery.cluster-name属性即可
3.4NacosRule负载均衡
负载均衡默认是轮询,不会优先本地集群,需要自行配置轮询方式
#修改负载均衡规则同Ribbon
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
3.5服务的权重设置
1.进入服务列表
2.点击操作栏下的详情
3.点击编辑
4.设置服务权重(一般范围0-1)
3.6Nacos环境隔离
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
在nacos控制台中可以创建命名空间进行管理,复制命名空间ID
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
namespace: 命名空间ID
小结
1. Nacos 与 eureka 的共同点① 都支持服务注册和服务拉取② 都支持服务提供者心跳方式做健康检测2. Nacos 与 Eureka 的区别① Nacos 支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式② 临时实例心跳不正常会被剔除,非临时实例则不会被剔除③ Nacos 支持服务列表变更的消息推送模式,服务列表更新更及时④ Nacos 集群默认采用 AP 方式,当集群中存在非临时实例时,采用 CP 模式; Eureka 采用 AP 方式
4.Nacos配置中心
4.1配置管理
在Nacos控制台中可以进入到配置中心
命名规则:一般为 服务名称-运行环境,例:userservice-dev.yaml
配置更新:在配置内容中只写需要更新的配置
4.2配置拉取
步骤:
1.在配置中心中添加要修改的依赖
2. 引入Nacos依赖
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
3. 添加bootstrap.yaml
bootstrap.yaml 优先级比本地配置文件高,用于拉取Nacos配置中心文件
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
4.读取配置
4.3热更新
方式一:在@Value注入的变量所在类上添加注解@RefreshScope
方式二:创建配置类,在配置类中使用@ConfigurationProperties注解代替@Value注解。
4.4.多环境共享配置
步骤:
1.添加一个环境共享配置
2.在配置类中读取共享配置
配置共享的优先级:
Nacos配置>Nacos共享配置文件>本地配置
4.5.Nacos集群搭建
步骤:
1.搭建数据库,初始化数据库表结构
2.下载Nacos安装包
nacos在GitHub上有下载地址:Tags · alibaba/nacos · GitHub,可以选择任意版本下载。
3.配置Nacos
(1)进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf
(2)然后添加内容
服务器A的IP:8845
服务器B的IP.8846
服务器C的IP.8847(3)然后修改application.properties文件,添加数据库配置
spring.datasource.platform=mysql db.num=1 db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC db.user.0=root db.password.0=123
4.启动 startup.cmd
5.Nginx反向代理
(1)将Nginx压缩包解压到非中文目录下
(2)修改conf/nginx.conf文件,配置如下:
upstream nacos-cluster { //集群 server 服务器A的IP:8845; server 服务器B的IP:8846; server 服务器C的IP:8847; } server { listen 80; //访问80端口 server_name localhost; location /nacos { //访问/nacos代理到集群中 proxy_pass http://nacos-cluster; } }
(3)在浏览器访问:http://localhost/nacos即可
(4)代码中application.yml文件配置
spring: cloud: nacos: server-addr: localhost:80 # Nacos地址,将服务注册到80端口会被路由到集群中
6.优化
实际部署时,需要给做反向代理的nginx服务器设置一个域名,这样后续如果有服务器迁移nacos的客户端也无需更改配置.
Nacos的各个节点应该部署到多个不同服务器,做好容灾和隔离
5.Feign
RestTemplate方式调用存在的问题
1.代码可读性差
2.参数复杂URL难以维护
5.1Feign使用
Feign使用步骤:
1.引入Feign依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.在启动类加@EnableFeignClients注解开启Feign功能
3.编写Feign的客户端
@FeignClient("userservice") //指向userservice服务
public interface UserClient {
@GetMapping("/user/{id}") //发送的请求路径
User getById(@PathVariable("id") Long id);
}
注: 提供者Controller中必须使用Feign中相同的请求
-
服务名称:userservice
-
请求方式:GET
-
请求路径:/user/{id}
-
请求参数:Long id
-
返回值类型:User
5.2Feign自定义配置
Feign可以支持很多的自定义配置,如下表所示:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
5.1日志文件配置
方式一:
基于配置文件修改feign的日志级别可以针对单个服务:
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
也可以针对所有服务:
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
方式二:
也可以基于Java代码来修改日志级别,先声明一个类,然后声明一个Logger.Level的对象:
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
如果要全局生效,将其放到启动类的@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
如果是局部生效,则把它放到对应的@FeignClient这个注解中:
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
5.2Feign使用优化
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
URLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
用Apache的HttpClient来演示:
1.引入依赖
<!--httpClient的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
2.配置连接池
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数
6.GateWay网关
6.1网关介绍
网关功能:
1.身份认证和权限校验
2.服务路由、负载均衡
3.请求限流
在SpringCloud中网关的实现包括两种:
gateway
zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。
6.2网关搭建
步骤:
1.引入依赖
<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.编写配置文件
server:
port: 10010 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: user-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
- id: user01-service # 路由id,自定义,只要唯一即可
# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
uri: lb://user01service # 路由的目标地址 lb就是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Before=时间 # 这个是按照时间
6.3路由断言工厂
名称 | 说明 | 示例 |
---|---|---|
After | 是某个时间点后的请求 | - After=2037-01-20T17:42:47.789-07:00[America/Denver] |
Before | 是某个时间点之前的请求 | - Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai] |
Between | 是某两个时间点之前的请求 | - Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver] |
Cookie | 请求必须包含某些cookie | - Cookie=chocolate, ch.p |
Header | 请求必须包含某些header | - Header=X-Request-Id, \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 | 权重处理 |
6.4过滤器工厂
Spring提供了31种不同的路由过滤器工厂 ,示例:
名称 | 说明 |
---|---|
AddRequestHeader | 给当前请求添加一个请求头 |
RemoveRequestHeader | 移除请求中的一个请求头 |
AddResponseHeader | 给响应结果中添加一个响应头 |
RemoveResponseHeader | 从响应结果中移除有一个响应头 |
RequestRateLimiter | 限制请求的流量 |
只需要修改gateway服务的application.yml文件,添加路由过滤即可:
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
filters: # 过滤器
- AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
如果要对所有的路由都生效,则可以将过滤器工厂写到default下。格式如下
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://userservice
predicates:
- Path=/user/**
default-filters: # 默认过滤项
- AddRequestHeader=Truth, Itcast is freaking awesome!
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。区别在于GatewayFilter通过配置定义,处理逻辑是固定的;而GlobalFilter的逻辑需要自己写代码实现。
public interface GlobalFilter {
/**
* 处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理
*
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>} 返回标示当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}
@Order(-1) //设置过滤器优先级 数字越小优先级越高
@Component
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 1.获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
// 2.获取authorization参数
String auth = params.getFirst("authorization");
// 3.校验
if ("admin".equals(auth)) {
// 放行
return chain.filter(exchange);
}
// 4.拦截
// 4.1.禁止访问,设置状态码
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
// 4.2.结束处理
return exchange.getResponse().setComplete();
}
}
过滤器排序规则:
每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前。
GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
当过滤器的order值一样时,会按照 defaultFilter > 路由过滤器 > GlobalFilter的顺序执行。