第六章 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依赖
<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客户端依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>c.修改注册中心地址
spring:
cloud:
nacos:
server-addr: localhost:8848 # Nacos服务端地址3.Nacos服务分级存储模型
在Nacos中,可以将服务划分为多个地域集群,集群中有多个服务实例
4.Nacos负载均衡策略
在Nacos中,默认的负载均衡策略也是轮询策略,会对每一个服务进行轮询访问
a.集群负载均衡(NacosRule)
根据集群负载均衡策略会优先访问同集群,再随机访问集群中的服务,需要修改如下配置:
spring:
cloud:
nacos:
server-addr: localhost:8848 # Nacos服务端地址
discovery:
cluster-name: ClusterName # 集群名称userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则b.加权负载均衡
在Nacos控制台中可以设置实例的权重值,一般在0~1之间,同集群中的多个实例,权重越高被访问的频率越高
5.环境隔离
a.命名空间(namespace)
Nacos中,通过namespace进行环境隔离
每一个namespace都有唯一id,不同的namespace下的服务相互不可见
b.创建命名空间
- 在Nacos控制台新建命名空间
- 配置namespace
spring:
cloud:
nacos:
server-addr: localhost:8848 # Nacos服务端地址
discovery:
cluster-name: ClusterName # 集群名称
namespace: xxxx-xxxx-xxxx # 命名空间id6.Nacos临时实例与非临时实例
在Nacos中,所有实例默认都是临时实例,要将示例配置为非临时实例需要进行配置
a.配置
spring:
cloud:
nacos:
server-addr: localhost:8848 # Nacos服务端地址
discovery:
cluster-name: ClusterName # 集群名称
namespace: xxxx-xxxx-xxxx # 命名空间id
ephemeral: false # 是否为临时实例b.临时实例与非临时实例的区别
- 临时实例由临时实例发送心跳,采用心跳监测的方式进行健康检测,在服务中止后会被剔除
- 非临时实例由注册中心主动发起询问进行健康检测,不发送心跳信息,在服务中止后不会被剔除
7.Nacos和Eureka对比

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

a.新建配置
[新建配置相关选项]
Data ID:一般命名为服务名-环境名.yaml/properties,如:userservice-dev.yaml
Group:一般不用更改,使用DEFAULT_GROUP即可
配置格式:一般选择YAML或Properties
配置内容:一般配置有热更新需求(可能需要修改)的相关配置

b.进行统一配置管理

- 引入Nacos的配置管理客户端依赖
<!--nacos配置管理依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>- 在bootstrap.yaml文件(引导文件,优先级高于application.yaml文件)中进行配置
spring:
application:
name: userservice # 服务名称
profiles:
active: dev # 环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos 地址
config:
file-extension: yaml # 文件后缀名2.热更新
- 在
@Value注入的变量所在类上添加注解@RefreshScope
@RestController
@RefreshScope
public class MyController {...}- 对需要热更新的配置类使用
@ConfigurationProperties注解
@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客户端
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>@EnableFeignClients
@SpringBootApplication
public class Application{
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}//注意要点:参数要添加@RequestParam("xxx")类似注解,请求参数与方法要一致
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id); // 定义远程调用
}@Autowired
private UserClient userClient;
public User findById(Long id){
return userClient.findById(id); // 使用远程调用
}Feign声明远程调用信息相关内容:
- 服务名称: userservice
- 请求方式: GET
- 请求路径: /user/
- 请求参数: Long id
- 返回值类型: User
3.自定义配置
常用配置如下:
a.修改配置文件进行配置
feign:
client:
config:
default: # 进行全局配置
loggerLever: FULL # 修改日志级别为FULLfeign:
client:
config:
serviceName: # 针对某个微服务进行配置
loggerLever: BASIC # 日志级别BASIC,基本的请求和响应信息b.通过Java配置
public class FeignClientConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC;
}
}@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)@FeignClient(value = "serviceName", configuration = FeignClientConfiguration.class)4.Feign性能优化
a.Feign底层的客户端实现
- URLConnection:Feign的默认实现,不支持连接池
- Apache HttpClient:支持连接池
- OKHttp:支持连接池
b.Feign性能优化方式
- 使用连接池代替默认的URLConnection
- 日志级别:最好使用BASIC或NONE
c.Feign进行连接池的配置
以HttpClient为例
<!--httpClient的依赖-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数5.最佳实践
方式一(继承)
给消费者的FeignClient和服务提供者的Controller定义统一的父接口作为标准
public interface ServiceAPI {
@GetMapping("...")
User findById(@PathVariable("id") Long id);
}@FeignClient(value = "service")
public interface ServiceClient extends ServiceAPI{}@RestController
public class ServiceController implements ServiceAPI{
//...
}一般情况下不推荐,在服务端和客户端之间共享接口会造成紧耦合
方式二(抽取)
将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所在包
@EnableFeignClient(basePackages = "...")
- (推荐)指定FeignClient字节码
@EnableFeignClient(clients = {ServiceClient.class})
问题:可能会引入很多不需要使用的方法,造成资源浪费
四、服务网关——SpringCloudGateway
1.服务网关简介
a.主要功能
- 身份认证和权限校验
- 服务路由与负载均衡
- 请求限流

b.技术实现
在SpringCloud中,网关的实现包括两种:SpringCloudGateway和Zuul
- SpringCloudGateway:基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能
- Zuul:基于Servlet的实现,属于阻塞式编程
c.主要内容
- 路由id:路由唯一标识
- uri:路由目的地,支持lb(负载均衡)和http两种
- predicates:路由断言,判断请求是否符合要求,符合则转发到路由目的地
- filters:路由过滤器,处理请求或相应
2.搭建服务网关
<!--网关依赖-->
<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>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/开头则符合要求//类似普通启动类,直接启动即可,不做赘述3.断言工厂(Route Predicate Factories)

4.过滤器工厂(GatewayFilter Factories)
GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理
spring:
cloud:
gateway:
routes: # 网关路由配置
- id: service # 路由id,自定义,只要唯一即可
#uri: http://localhost:8081 # 路由的目标地址 此为固定地址,不推荐使用
uri: lb://servicename # 路由的目标地址,lb为负载均衡,后面是服务名称
predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
- Path=/path/** # 按照路径匹配,以/path/开头则符合要求
filters: # 过滤器
- AddRequestHeader=Test # 通过过滤器添加请求头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的逻辑需要自己写代码实现。
public interface GLobalFilter {
/**
* 处理当前请求, 有必要的话通过{@Link GatewayFilterChain} 将请求交给下一个过滤器处理
* @param exchange 请求上下文,里面可以获取Request、Response等信息
* @param chain 用来把请求委托给下一个过滤器
* @return {@code Mono<Void>}返回标示当前过滤器业务结束
*/
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) ;
}@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
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 # 这次跨域检测的有效期,单位:秒