Skip to content

第六章 Spring Cloud Alibaba入门 v1.0 ​

一、注册中心——Nacos ​

Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件,相比Eureka功能更加丰富,国内受欢迎程度较高

1.下载与安装 ​

[Nacos下载与安装步骤]
1.下载并解压 ​

github地址:https://github.com/alibaba/nacos/tags

nacos 1.4.1版本(Windows):

nacos 1.4.1版本(Linux):

2.端口配置(默认端口:8848) ​

修改conf-application.porperties中port值

3.启动 ​

执行命令:startup.cmd -m standalone

4.访问 ​

地址:ip:8848/nacos/index.html

默认账户名:nacos;默认密码:nacos

5.修改密码 ​

2.修改服务注册到Nacos ​

a.添加spring-cloud-alibaba依赖 ​
xml
<dependency>
  <groupId>com.alibaba.c1oud</groupId>
  <artifactId>spring-cloud-alibaba-dependencies</artifactId>
  <version>2.2.5.RELEASE</version>
  <type>pom</type>
  <scope>import</scope>
</dependency>
b.添加Nacos客户端依赖 ​
xml
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
c.修改注册中心地址 ​
yaml
spring:
	cloud:
  	nacos:
    	server-addr: localhost:8848  # Nacos服务端地址

3.Nacos服务分级存储模型 ​

在Nacos中,可以将服务划分为多个地域集群,集群中有多个服务实例

4.Nacos负载均衡策略 ​

在Nacos中,默认的负载均衡策略也是轮询策略,会对每一个服务进行轮询访问

a.集群负载均衡(NacosRule) ​

根据集群负载均衡策略会优先访问同集群,再随机访问集群中的服务,需要修改如下配置:

yaml
spring:
	cloud:
  	nacos:
    	server-addr: localhost:8848 # Nacos服务端地址
      discovery:
      	cluster-name: ClusterName # 集群名称
yaml
userservice:
	ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
b.加权负载均衡 ​

在Nacos控制台中可以设置实例的权重值,一般在0~1之间,同集群中的多个实例,权重越高被访问的频率越高

5.环境隔离 ​

a.命名空间(namespace) ​

Nacos中,通过namespace进行环境隔离

每一个namespace都有唯一id,不同的namespace下的服务相互不可见

b.创建命名空间 ​
  1. 在Nacos控制台新建命名空间
  2. 配置namespace
yaml
spring:
	cloud:
  	nacos:
    	server-addr: localhost:8848 # Nacos服务端地址
      discovery:
      	cluster-name: ClusterName # 集群名称
      	namespace: xxxx-xxxx-xxxx # 命名空间id

6.Nacos临时实例与非临时实例 ​

在Nacos中,所有实例默认都是临时实例,要将示例配置为非临时实例需要进行配置

a.配置 ​
yaml
spring:
	cloud:
  	nacos:
    	server-addr: localhost:8848 # Nacos服务端地址
      discovery:
      	cluster-name: ClusterName # 集群名称
      	namespace: xxxx-xxxx-xxxx # 命名空间id
      	ephemeral: false # 是否为临时实例
b.临时实例与非临时实例的区别 ​
  • 临时实例由临时实例发送心跳,采用心跳监测的方式进行健康检测,在服务中止后会被剔除
  • 非临时实例由注册中心主动发起询问进行健康检测,不发送心跳信息,在服务中止后不会被剔除

7.Nacos和Eureka对比 ​

image.png

a.Nacos与eureka的共同点 ​
  1. 都支持服务注册和服务拉取
  2. 都支持服务提供者心跳方式做健康检测
b.Nacos与Eureka的区别 ​
  1. Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
  2. 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
  3. Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
  4. Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式

二、配置管理——Nacos ​

1.统一配置管理 ​

image.png

a.新建配置 ​
[新建配置相关选项]

Data ID:一般命名为服务名-环境名.yaml/properties,如:userservice-dev.yaml

Group:一般不用更改,使用DEFAULT_GROUP即可

配置格式:一般选择YAML或Properties

配置内容:一般配置有热更新需求(可能需要修改)的相关配置

image.png

b.进行统一配置管理 ​

image.png

  1. 引入Nacos的配置管理客户端依赖
xml
<!--nacos配置管理依赖-->
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
  1. 在bootstrap.yaml文件(引导文件,优先级高于application.yaml文件)中进行配置
yaml
spring:
  application:
  	name: userservice # 服务名称
  profiles:
  	active: dev # 环境,这里是dev
  cloud:
  	nacos:
  		server-addr: localhost:8848 # Nacos 地址
  		config:
  			file-extension: yaml # 文件后缀名

2.热更新 ​

  1. 在@Value注入的变量所在类上添加注解@RefreshScope
java
@RestController
@RefreshScope
public class MyController {...}
  1. 对需要热更新的配置类使用@ConfigurationProperties注解
java
@Component
@ConfigurationProperties(prefix = "pattern")
public class MyProperties {...}

3.多环境配置共享 ​

微服务启动时会从Nacos读取多个配置文件,如下所示:

  • 服务名-环境名.yaml/properties(指定环境配置文件)
  • 服务名.yaml/properties(多环境共享配置文件)

无论环境如何变化,服务名.yaml/properties文件一定会被读取,可用作多环境配置共享

4.配置文件优先级 ​

指定环境配置文件>多环境共享配置文件>本地配置

5.搭建Nacos集群 ​

三、远程调用——Feign ​

1.简介 ​

RestTemplate方式调用存在以下问题:

  • 代码可读性差,编程体验不统一
  • 参数复杂的URL难以维护

Feign是一个声明式的HTTP客户端,作用是帮助我们优雅的实现http请求的发送,可以解决上面的问题

2.定义和使用Feign客户端 ​

xml
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
java
@EnableFeignClients
@SpringBootApplication
public class Application{
    public static void main(String[] args){
        SpringApplication.run(Application.class, args);
    }
}
java
//注意要点:参数要添加@RequestParam("xxx")类似注解,请求参数与方法要一致
@FeignClient("userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);  // 定义远程调用
}
java
@Autowired
private UserClient userClient;

public User findById(Long id){
    return userClient.findById(id);  // 使用远程调用
}

Feign声明远程调用信息相关内容:

  • 服务名称: userservice
  • 请求方式: GET
  • 请求路径: /user/
  • 请求参数: Long id
  • 返回值类型: User

3.自定义配置 ​

常用配置如下:

a.修改配置文件进行配置 ​
yaml
feign:
	client:
  	config:
    	default:  # 进行全局配置
      	loggerLever: FULL  # 修改日志级别为FULL
yaml
feign:
	client:
  	config:
    	serviceName:  # 针对某个微服务进行配置
      	loggerLever: BASIC   # 日志级别BASIC,基本的请求和响应信息
b.通过Java配置 ​
java
public class FeignClientConfiguration {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.BASIC;
    }
}
java
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
java
@FeignClient(value = "serviceName", configuration = FeignClientConfiguration.class)

4.Feign性能优化 ​

a.Feign底层的客户端实现 ​
  • URLConnection:Feign的默认实现,不支持连接池
  • Apache HttpClient:支持连接池
  • OKHttp:支持连接池
b.Feign性能优化方式 ​
  1. 使用连接池代替默认的URLConnection
  2. 日志级别:最好使用BASIC或NONE
c.Feign进行连接池的配置 ​

以HttpClient为例

xml
<!--httpClient的依赖--> 
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-httpclient</artifactId>
</dependency>
yaml
httpclient:
  enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数

5.最佳实践 ​

方式一(继承) ​

给消费者的FeignClient和服务提供者的Controller定义统一的父接口作为标准

java
public interface ServiceAPI {
    @GetMapping("...")
    User findById(@PathVariable("id") Long id);
}
java
@FeignClient(value = "service")
public interface ServiceClient extends ServiceAPI{}
java
@RestController
public class ServiceController implements ServiceAPI{
 //...   
}

一般情况下不推荐,在服务端和客户端之间共享接口会造成紧耦合

方式二(抽取) ​

将FeignClient抽取为独立模块,将接口相关POJO与默认Feign配置放在模块中,提供所有消费者使用

image.png

实现过程如下:

  1. 首先创建一个module,命名为feign-api,然后引入feign的starter依赖
  2. 将order-service中编写的UserClient、User、 DefaultFeignConfiguration都复制到feign-api项目中
  3. 在order- service中引入feign-api的依赖
  4. 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包

当定义的FeignClient不在SpringBootApplication的扫描包范围时,FeignClient无法使用,解决方式如下:

  1. 指定FeignClient所在包

@EnableFeignClient(basePackages = "...")

  1. (推荐)指定FeignClient字节码

@EnableFeignClient(clients = {ServiceClient.class})

问题:可能会引入很多不需要使用的方法,造成资源浪费

四、服务网关——SpringCloudGateway ​

1.服务网关简介 ​

a.主要功能 ​
  1. 身份认证和权限校验
  2. 服务路由与负载均衡
  3. 请求限流

image.png

b.技术实现 ​

在SpringCloud中,网关的实现包括两种:SpringCloudGateway和Zuul

  • SpringCloudGateway:基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能
  • Zuul:基于Servlet的实现,属于阻塞式编程
c.主要内容 ​
  • 路由id:路由唯一标识
  • uri:路由目的地,支持lb(负载均衡)和http两种
  • predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
  • filters:路由过滤器,处理请求或相应

2.搭建服务网关 ​

xml
<!--网关依赖-->
<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>
yaml
server:
  port: 10010  # 网关端口
spring:
  application:
    name: gateway  # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848  # Nacos地址
    gateway:
      routes:  # 网关路由配置
        - id: user-service  # 路由id,自定义,只要唯一即可
          #uri: http://localhost:8081 # 路由的目标地址 此为固定地址,不推荐使用
          uri: lb://userservice # 路由的目标地址,lb为负载均衡,后面是服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/**  # 按照路径匹配,以/path/开头则符合要求
        - id: test-service  # 路由id,自定义,只要唯一即可
          uri: lb://testservice # 路由的目标地址,lb为负载均衡,后面是服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/test/**  # 按照路径匹配,以/path/开头则符合要求
yaml
//类似普通启动类,直接启动即可,不做赘述

3.断言工厂(Route Predicate Factories) ​

image.png

4.过滤器工厂(GatewayFilter Factories) ​

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理

yaml
spring:
	cloud:
  	gateway:
    	routes:  # 网关路由配置
        - id: service  # 路由id,自定义,只要唯一即可
  				#uri: http://localhost:8081 # 路由的目标地址 此为固定地址,不推荐使用
        	uri: lb://servicename # 路由的目标地址,lb为负载均衡,后面是服务名称
        	predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
          	- Path=/path/**  # 按照路径匹配,以/path/开头则符合要求
        	filters:  # 过滤器
          	- AddRequestHeader=Test  # 通过过滤器添加请求头
yaml
spring:
	cloud:
  	gateway:
    	routes:  # 网关路由配置
        - id: service  # 路由id,自定义,只要唯一即可
  				#uri: http://localhost:8081 # 路由的目标地址 此为固定地址,不推荐使用
        	uri: lb://servicename # 路由的目标地址,lb为负载均衡,后面是服务名称
        	predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
          	- Path=/path/**  # 按照路径匹配,以/path/开头则符合要求
    	default-filters:  # 默认过滤器,会对所有的路由请求都生效
      	- AddRequestHeader=Test  # 通过过滤器添加请求头

5.全局过滤器(GlobalFilter) ​

全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。

区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。

java
public interface GLobalFilter {
    /** 
    * 处理当前请求, 有必要的话通过{@Link GatewayFilterChain} 将请求交给下一个过滤器处理
    * @param exchange 请求上下文,里面可以获取Request、Response等信息
    * @param chain 用来把请求委托给下一个过滤器
    * @return {@code Mono<Void>}返回标示当前过滤器业务结束
    */
    Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) ;
}
java
@Order(0)  //定义优先级顺序(数字越小,优先级越高)
@Component  //定义为Component组件
public class ServiceFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //1.获取请求参数
        ServerHttpRequest request = exchange.getRequest();
        MultiValueMap<String,String> params = request.getQueryParams();
        //2.获取参数中的param参数
        String param = params.getFirst("params");
        //程序逻辑
        if(...){
            //放行
            return chain.filter(exchange);
        }else{
            //拦截
            exchange.getResponse().setStatusCode(...);  //设置状态码
            return exchange.getResponse().setComplete();  //拦截请求
        }
    }
}

6.过滤器执行顺序 ​

  • 每一-个过滤器都必须指定一个int类型的order值, order值越小,优先级越高,执行顺序越靠前。
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
  • 当过滤器的order值一样时,会按照defaultFilter >路由过滤器> GlobalFilter的顺序执行。

7.跨域问题 ​

解决方法:CORS

yaml
spring:
  cloud: 
    gateway:
      globalcors: # 全局的跨域处理
        add-to-simpLe-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':  # 拦截一切请求
          allowedOrigins: #允许哪些网站的跨域请求
            - "http://localhost:8080"
            - "http://www.hymsk.top"
          allowedMethods: #允许的跨域ajax的请求方式
            - "GET"
            - "POST"
            - "DELETE"
            - "PUT"
            - "OPTIONS"
          allowedHeaders: "*" # 允许在请求中携带的头信息
          allowCredentials: true # 是否允许携带cookie
          maxAge: 360000 # 这次跨域检测的有效期,单位:秒