SpringCloud微服务
1. SpringCloud
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:https://spring.io/projects/spring-cloud.
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:

SpringCloud基于SpringBoot实现了微服务组件的自动装配,从而提供了良好的开箱即用体验。但对于SpringBoot的版本也有要求:

2. 拆分服务
2.1 拆分目标
微服务拆分时粒度要小,这其实是拆分的目标。具体可以从两个角度来分析:
- 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
- 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖,或者依赖接口的稳定性要强。
2.2 拆分方式
明确了拆分目标,接下来就是拆分方式了。我们在做服务拆分时一般有两种方式:
- 纵向拆分:按照项目的功能模块来拆分
- 横向拆分:抽取公共的业务部分,将其作为通用服务,提高复用性
2.3 项目结构
一般微服务项目有两种不同的工程结构:
- 独立Project:每一个微服务都创建为一个独立的工程,甚至可以使用不同的开发语言来开发,项目完全解耦。
- 优点:服务之间耦合度低
- 缺点:每个项目都有自己的独立仓库,管理起来比较麻烦
- Maven聚合:整个项目为一个Project,然后每个微服务是其中的一个Module
- 优点:项目代码集中,管理和运维方便(授课也方便)
- 缺点:服务之间耦合,编译时间较长
2.4 远程调用——RestTemplate
-
注入RestTemplate到Spring容器

-
发起远程调用

3. 服务治理
3.1 注册中心原理

3.2 Nacos注册中心
参考文档:https://b11et3un53m.feishu.cn/wiki/R4Sdwvo8Si4kilkSKfscgQX0niB
3.3 服务注册
-
引入nacos discovery依赖
<!--nacos 服务注册发现--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> -
配置Nacos地址
spring: application: name: item-service # 服务名称 cloud: nacos: server-addr: 192.168.74.130:8848 # nacos地址
3.4 服务发现
-
引入nacos discovery依赖
-
配置nacos地址
-
服务发现
服务发现需要用到一个工具,DiscoveryClient,SpringCloud已经帮我们自动装配,我们可以直接注入使用:

// 1.获取商品id Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet()); // 2.查询商品 // 2.1 根据服务名称获取服务的实例列表 List<ServiceInstance> instances = discoveryClient.getInstances("item-service"); if(CollUtil.isEmpty(instances)){ return; } // 2.2 手写负载均衡,从实例列表中挑选一个实例 ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size())); // 2.3 利用RestTemplate发送http请求,得到http响应 ResponseEntity<List<ItemDTO>> response = restTemplate.exchange( instance.getUri()+"/items/list?ids={ids}", HttpMethod.GET, null, new ParameterizedTypeReference<List<ItemDTO>>() { }, Map.of("ids", CollUtil.join(itemIds, ",")) ); //解析响应 if(!response.getStatusCode().is2xxSuccessful()){ //查询失败,直接结束 return; } List<ItemDTO> items = response.getBody();
4. OpenFeign
4.1 快速入门
OpenFeign是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来。
官方地址:https://github.com/openFeign/feign
其作用就是基于SpringMVC的常见注解,帮我们优雅的实现http请求的发送
4.1.1 引入依赖
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
4.1.2 启用OpenFeign功能
@EnableFeignClients注解

4.1.3 编写OpenFeign客户端
@FeignClient("item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
4.1.4 使用FeignClient
private final Itemclient itemclient
List<ItemDTo>items = itemClient.queryItemByIds(itemIds);
4.2 连接池
Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:
- HttpURLConnection:默认实现,不支持连接池
- Apache HttpClient :支持连接池
- OKHttp:支持连接池
因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.
4.2.1.引入依赖
在cart-service的pom.xml中引入依赖:
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
4.2.2.开启连接池
在cart-service的application.yml配置文件中开启Feign的连接池功能:
feign:
okhttp:
enabled: true # 开启OKHttp功能
重启服务,连接池就生效了。
4.3 最佳实践
- 方案一
适合各功能是一个单独的项目,耦合度低

- 方案二
项目是聚合结构,各功能在一个项目下面

4.4 日志
OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:
- NONE:不记录任何日志信息,这是默认值。
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
4.4.1.定义日志级别
在hm-api模块下新建一个配置类,定义Feign的日志级别:
package com.hmall.api.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}
}
4.4.2.配置
接下来,要让日志级别生效,还需要配置这个类。有两种方式:
-
局部生效:在某个FeignClient中配置,只对当前FeignClient生效
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class) -
全局生效:在@EnableFeignClients中配置,针对所有FeignClient生效。
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
5. 网关
网关:就是网络的关口,负责请求的路由、转发、身份校验。


5.1 路由转发
5.1.1 快速入门
-
创建新模块
-
引入网关依赖
<!--网关--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <!--nacos discovery--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--负载均衡--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> -
编写启动类
-
配置路由规则
server: port: 8080 spring: application: name: gateway cloud: nacos: server-addr: 192.168.150.101:8848 gateway: routes: - id: item # 路由规则id,自定义,唯一 uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表 predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务 - Path=/items/**,/search/** # 这里是以请求路径作为判断规则 - id: cart uri: lb://cart-service predicates: - Path=/carts/**
5.1.2 路由属性
网关路由对应的Java类型是RouteDefinition,其中常见的属性有:
- id:路由的唯一标示
- predicates:路由断言,其实就是匹配条件
- filters:路由过滤条件
- uri:路由目标地址,lb://代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。
路由断言
Spring提供了12中基本的RoutePredicateFactory实现

路由过滤器
网关中提供了33种路由过滤器,每种过滤器都有独特的作用

5.2 网关登录校验


5.2.1 自定义过滤器
网关过滤器有两种,分别是:
- GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
- GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。
两种过滤器的过滤方法签名完全一致
自定义过滤器GlobalFilter
@Component
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 编写过滤器逻辑
System.out.println("未登录,无法访问");
// 放行
// return chain.filter(exchange);
// 拦截
ServerHttpResponse response = exchange.getResponse();
response.setRawStatusCode(401);
return response.setComplete();
}
@Override
public int getOrder() {
// 过滤器执行顺序,值越小,优先级越高
return 0;
}
}
自定义GateWayFilter
自定义GatewayFilter不是直接实现GatewayFilter,而是实现AbstractGatewayFilterFactory。
注意:该类的名称一定要以GatewayFilterFactory为后缀!
-
无参过滤器
@Component public class PrintAnyGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> { @Override public GatewayFilter apply(Object config) { return new GatewayFilter() { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取请求 ServerHttpRequest request = exchange.getRequest(); // 编写过滤器逻辑 System.out.println("过滤器执行了"); // 放行 return chain.filter(exchange); } }; } }spring: cloud: gateway: default-filters: - PrintAny # 此处直接以自定义的GatewayFilterFactory类名称前缀类声明过滤器 -
带参过滤器
@Component public class PrintAnyGatewayFilterFactory // 父类泛型是内部类的Config类型 extends AbstractGatewayFilterFactory<PrintAnyGatewayFilterFactory.Config> { @Override public GatewayFilter apply(Config config) { // OrderedGatewayFilter是GatewayFilter的子类,包含两个参数: // - GatewayFilter:过滤器 // - int order值:值越小,过滤器执行优先级越高 return new OrderedGatewayFilter(new GatewayFilter() { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 获取config值 String a = config.getA(); String b = config.getB(); String c = config.getC(); // 编写过滤器逻辑 System.out.println("a = " + a); System.out.println("b = " + b); System.out.println("c = " + c); // 放行 return chain.filter(exchange); } }, 100); } // 自定义配置属性,成员变量名称很重要,下面会用到 @Data static class Config{ private String a; private String b; private String c; } // 将变量名称依次返回,顺序很重要,将来读取参数时需要按顺序获取 @Override public List<String> shortcutFieldOrder() { return List.of("a", "b", "c"); } // 返回当前配置类的类型,也就是内部的Config @Override public Class<Config> getConfigClass() { return Config.class; } }spring: cloud: gateway: default-filters: - PrintAny=1,2,3 # 注意,这里多个参数以","隔开,将来会按照shortcutFieldOrder()方法返回的参数顺序依次复制
5.2.2 实现登录校验
https://b11et3un53m.feishu.cn/wiki/UMgpwmmQKisWBIkaABbcwAPonVf
5.2.3 网关传递用户
利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。

-
AuthGlobalFilter
//5.传递用户信息 String userInfo = userId.toString(); ServerWebExchange swe = exchange.mutate() .request(builder -> builder.header("user-info", userInfo)) .build(); -
拦截器
public class UserInfoInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //1.获取登录用户信息 String userInfo = request.getHeader("user-info"); //2.判断是否获取了用户,如果有,存入ThreadLocal if(StrUtil.isNotBlank(userInfo)){ UserContext.setUser(Long.valueOf(userInfo)); } //3.放行 return true; } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { //清理用户 UserContext.removeUser(); } }@Configuration @ConditionalOnClass(DispatcherServlet.class) public class MvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new UserInfoInterceptor()); } }
5.2.4 OpenFeign传递用户
OpenFeign中提供了一个拦截器接口,所有由0penFeign发起的请求都会先调用拦截器处理请求:
这里要借助Feign中提供的一个拦截器接口:feign.RequestInterceptor
public interface RequestInterceptor {
/**
* Called for every request.
* Add data using methods on the supplied {@link RequestTemplate}.
*/
void apply(RequestTemplate template);
}
我们只需要实现这个接口,然后实现apply方法,利用RequestTemplate类来添加请求头,将用户信息保存到请求头中。这样以来,每次OpenFeign发起请求的时候都会调用该方法,传递用户信息。
-
hm.api模块
DefaultFeignConfig中编写内部类@Bean public RequestInterceptor requestInterceptor(){ return new RequestInterceptor() { @Override public void apply(RequestTemplate requestTemplate) { Long userId = UserContext.getUser(); if(userId!=null){ requestTemplate.header("user-info",userId.toString()); } } }; }

6. 配置管理
- 微服务重复配置过多,维护成本高
- 业务配置经常变动,每次修改都要重启服务
- 网关路由配置写死,如果变更要重启网关

6.1 配置共享
6.1.1.添加共享配置
我们在nacos控制台分别添加配置。
首先是jdbc相关配置,在配置管理->配置列表中点击+新建一个配置:
![[图片]](https://i-blog.csdnimg.cn/direct/c51b2a878d0044c7974962cefb91682c.png)
在弹出的表单中填写信息:
![[图片]](https://i-blog.csdnimg.cn/direct/d42f11ee561f47f588c01cba1e2a2f76.png)
其中详细的配置如下:
spring:
datasource:
url: jdbc:mysql://${hm.db.host:192.168.150.101}:${hm.db.port:3306}/${hm.db.database}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${hm.db.un:root}
password: ${hm.db.pw:123}
mybatis-plus:
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
global-config:
db-config:
update-strategy: not_null
id-type: auto
注意这里的jdbc的相关参数并没有写死,例如:
- 数据库ip:通过
${hm.db.host:192.168.150.101}配置了默认值为192.168.150.101,同时允许通过${hm.db.host}来覆盖默认值 - 数据库端口:通过
${hm.db.port:3306}配置了默认值为3306,同时允许通过${hm.db.port}来覆盖默认值 - 数据库database:可以通过
${hm.db.database}来设定,无默认值
然后是统一的日志配置,命名为shared-log.yaml,配置内容如下:
logging:
level:
com.hmall: debug
pattern:
dateformat: HH:mm:ss:SSS
file:
path: "logs/${spring.application.name}"
然后是统一的swagger配置,命名为shared-swagger.yaml,配置内容如下:
knife4j:
enable: true
openapi:
title: ${hm.swagger.title:黑马商城接口文档}
description: ${hm.swagger.description:黑马商城接口文档}
email: ${hm.swagger.email:zhanghuyi@itcast.cn}
concat: ${hm.swagger.concat:虎哥}
url: https://www.itcast.cn
version: v1.0.0
group:
default:
group-name: default
api-rule: package
api-rule-resources:
- ${hm.swagger.package}
注意,这里的swagger相关配置我们没有写死,例如:
- title:接口文档标题,我们用了
${hm.swagger.title}来代替,将来可以有用户手动指定 - email:联系人邮箱,我们用了
${hm.swagger.email:zhanghuyi@itcast.cn},默认值是zhanghuyi@itcast.cn,同时允许用户利用${hm.swagger.email}来覆盖
6.1.2 拉取共享配置

基于NacosConfig拉取共享配置代替微服务的本地配置
SpringCloud在初始化上下文的时候会先读取一个名为bootstrap.yaml(或者bootstrap.properties)的文件,如果我们将nacos地址配置到bootstrap.yaml中,那么在项目引导阶段就可以读取nacos中的配置了。
-
引入依赖
<!--nacos配置管理--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency> <!--读取bootstrap文件--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bootstrap</artifactId> </dependency> -
新建bootsrap.yaml
spring: application: name: cart-service # 服务名称 profiles: active: dev cloud: nacos: server-addr: 192.168.150.101 # nacos地址 config: file-extension: yaml # 文件后缀名 shared-configs: # 共享配置 - dataId: shared-jdbc.yaml # 共享mybatis配置 - dataId: shared-log.yaml # 共享日志配置 - dataId: shared-swagger.yaml # 共享日志配置
6.2 配置热更新
配置热更新:当修改配置文件中的配置时,微服务无需重启即可使配置生效。
前提条件:
-
nacos中要有一个与微服务名有关的配置文件

如:cart-service.yaml

-
微服务要以特定方式读取需要热更新的配置属性
@Data @Component @ConfigurationProperties(prefix = "hm.cart") public class CartProperties { private Integer maxItems; }@Data @Component //@ConfigurationProperties(prefix = "hm.cart") public class CartProperties { @Value("${hm.cart.maxItems}") private Integer maxItems; }
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)