微服务随手记
拆分原则
什么时候做拆分
- 创业型项目:先采用单体架构,快速开发,快速试错。随着规模扩大,逐渐拆分
- 确定的大型项目:资金充足,目标明确,可以直接选择微服务架构,避免后续拆分的麻烦
怎么拆分
- 从拆分目标来说:
- 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联程度高、完整度高
- 低耦合:每个微服务的功能要相对独立,尽量减少对其他微服务的依赖
- 从拆分方式来说:
- 纵向拆分:按照业务模块拆分
- 横向拆分:抽取公共服务,提高复用性
服务治理
RestTemplate
spring中自带的http请求封装,利用它可以向服务提供者发起http请求获取想要的数据
注册中心原理
- 服务提供者去注册中心注册服务信息,并通过心跳续约保证连接
- 服务调用者去注册中心订阅所需服务,通过负载均衡算法选择所需提供者远程调用,当服务提供者变化时,提供推送变更
负载均衡算法主要有以下集中:随机、轮询、加权轮询、哈希
Nacos注册中心
(Nacos 快速开始 | Nacos 官网)
部署(docker)
- 准备数据库表,用来存储Nacos的数据
- 配置nacos的
custom.env:选数据库,写地址端口、账号密码 - 在
/root目录下执行以下命令1
2
3
4
5
6
7
8docker run -d\
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim
可以去网上先下载好镜像,通过
docker load -i nacos.tar加载
- 可以通过ip:端口/nacos访问
服务注册
- 引入nacos discovery依赖
1
2
3
4<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency> - 配置Nacos地址
1
2
3
4
5
6
7spring:
application:
name: oyuo # 服务器名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
服务发现
- 同样需要引入依赖和配置nacos地址
- 服务发现:
1
2
3
4
5
6
7
8
9
10
11private final DiscoveryClient disccoveryclient;
private void handleCartItems(List<CartVO> vos){
// 1.根据服务名称,拉取服务的实力列表
List<ServiceInstance> instances = discoveryClient.getInstances("oyuo");
// 2.负载均衡,挑选一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
// 3.获取实例的IP和端口
URI uri = instance.getUri();
//服务的调用,略...
}
OpenFeign
一个声明式的http客户端,其作用就是基于SpringMVC的常见注解,帮助开发者优雅的实现http请求的发送
使用
- 引入依赖
1
2
3
4
5
6
7
8
9<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> - 通过
@EnableFeignClients注解,启动OpenFeign功能1
2
3
4
5
public class CartApplication{
//....略
} - 编写FeignClient
1
2
3
4
5
6
public interface ItemClient{
List<ItemDTO> queryItemByIds( Collection<Long> ids);
} - 使用FeignClient,实现远程调用
1
List<ItemDTO> items = itemClient.queryItemByIds(List.of(1,2,3));
连接池
OpenFeign对Http请求做了优雅的伪装,不过其底层发起http请求,依赖于其他的框架。这些框架可以自己选择,包括以下三种:
- HttpURLConnection:默认实现,不支持连接池(故而性能不佳)
- Apache HttpClient:支持连接池
- OKHttp:支持连接池
具体源码可以参考FeignBlockingLoadBalancerClient类中的delegate成员变量
调优(OKHttp为例)
- 引入依赖
1
2
3
4<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency> - 开启连接池功能
1
2
3feign:
okhttp:
enable: true # 开启OKHttp连接池支持
最佳实践
由服务提供者编写独立的moudle,将FeignClient及DTO抽取,就是OpenFeign的最佳实践,当然也会遇到不少的问题
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。解决办法如下两种:
- 指定FeignClient所在包
1
- 指定FeignClient字节码
1
日志输出
OpenFeign只会在FeignClient所在的包的日志级别为DEBUG时,才会输出日志。而且其日志级别有四级:
- NONE:不记录任何日志信息,这是默认值
- BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
- HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
- FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据
自定义日志级别
需要声明一个类型为Logger.Level的Bean,在其中定义日志级别:
1 | public class DefaultFeignConfig{ |
此时这个Bean并未生效,有两种办法:
- 局部配置某个FeignClient的日志,可以在
@FeignClient注解声明:1
- 如果想要全局配置,则需要在
@EnableFeignClients中声明:1
网关
网关:就是网络的关口,负责请求的路由、转发、身份校验
在SpringCloud中网关的实现包括两种:
Spring Cloud Gateway:
- Spring官方出品
- 基于WebFlux响应式编程
- 无需调优即可获得优异性能
Netfilx Zuul: - Netflix出品
- 基于Servlet的阻塞式编程
- 需要调优才能获得与Spring Cloud Gateway类似的性能
搭建网关
- 引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<!--spring cloud gateway-->
<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>
<!--客户端负载均衡loadbalancer-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency> - 路由配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17server:
port: 8081 # 网关端口
spring:
application:
name: gateway # 服务名称
cloud:
nacos:
server-addr: 127.0.0.1:8848 # nacos地址
gateway:
routes: # 网关路由配置
- id: baidu # 路由id, 自定义,唯一即可
# uri: 127.0.0.1:/order # - 路由目的地,支持lb和http两种
uri: lb://orderService # 路由的目的地,lb是负载均衡,后面跟服务名称
predicates: # 路由断言,也就是判断是否符合路由规则的条件
- Path=/order-service/** # path 按照路径进行匹配,只要以/order-service/开头就符合规则
filters:
- StripPrefix=1 # 过滤器,去除请求的前缀路径,StripPrefix=1 表示去除请求路径的第一个路径片段
路由属性
网关路由对应的Java类型是RouteDefinition,其中常见的属性有:
- id:路由唯一标示
- uri:路由目标地址
- predicates:路由断言,判断请求是否符合当前路由
- filters:路由过滤器,对请求或响应做特殊处理
路由断言
Spring提供了12种基本的RoutePredicateFactory实现:
路由过滤器
网关提供了33种路由过滤器,不赘述,可以查官网
如果过滤器想对所有的路由生效,就在routes:同级下配置default-filters:
1 | routes: <? items> |
网关登录校验
思路:JWT过滤器验证、将用户信息保存在请求头中,通过http请求发送到各个微服务中,各个微服务的传递同理
自定义过滤器
网关过滤器分为两种:
GatewayFilter:路由过滤器,作用于任意指定的路由;默认不生效,要配置到路由后生效。
GlobalFilter:全局过滤器,作用范围是所有路由;声明后自动生效。
- 自定义GlobalFilter


2.自定义GatewayFilter(非重点)
微服务02-06.网关登录校验-自定义GatewayFilter_哔哩哔哩_bilibili做个记录- 自定义GlobalFilter
实现登录校验
JWT的实现
- 配置yaml文件
- AuthProperties、JwtProperties读取配置、MvcConfig拦截、SecurityConfig生成密钥
- JwtTool(生成,解析Token)
登录校验: - 创建AuthGlobalFilter
- 获取request
ServerHttpRequest request = exchange.getRequest(); - 判断是否需要登录拦截
- 获取token
List<String> headers = request.getHeaders().get("authorization"); - 校验并解析token
- 传递用户信息
- 放行
return chain.filter(swe)
网关传递用户->把用户信息写在请求头,用springmvc拦截器做拦截保存到threadlocal中
- 获取request
1 | //在过滤器中0v0 |
- threadlocal与拦截器的书写,不在这里演示,就是注意要
String userInfo = request.getHeader("user-Info")而不是authorization了 - 在
META-INF下spring.factories中添加mvc不然不生效 - 通过条件注解
@ConditionalOnclass(DispatcherServlet.class)使得网关中mvc不会生效(因为网关没有springmvc的servlet服务器这个类)
OpenFeign传递用户信息(微服务之间)
- OpenFeign中提供了一个拦截器接口,所有由OpenFeign发起的请求都会先调用拦截器处理请求
- 其中RequestTemplate类中提供了一些方法可以让我们修改请求头
配置管理
配置问题:
- 微服务重复配置过多,维护成本高
- 业务配置经常变动,每次修改都要重启服务
- 网关路由配置写死,如果变更要重启网关
配置共享
- 添加配置到Nacos
- 添加一些共享配置到Nacos中,包括:Jdbc、MybatisPlus、日志、Swagger、OpenFeign等配置
- 可以直接在Nacos的可视化控制台中配置
- 拉取共享配置
- 基于NacosConfig拉去共享配置代替微服务的本地配置
引入依赖
1
2
3
4
5
6
7
8
9
10<!--nacos配置管理-->
<dependency>
<groupId>com.alibaba.could</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--读取bootstrap文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>新建bootstrap.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14spring:
application:
name: service-name # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 192.168.150.101 # nacos地址
config::
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置,下面id都是自己取的那个id
- dataId: shared-jdbc.yaml # 共享mybatis配置
- dataId: shared-log.yaml # 共享日志配置
- dataId: shared-swagger.yaml # 共享日志配置
配置热更新
当我们修改配置文件中的配置时,微服务无需重启即可使配置生效
前提条件:
- nacos中要有一个与微服务名有关的配置文件
- 微服务中要以特定方式读取需要热更新的配置属性(一种是
@ConfigurationProperties(predix = "sof.service",另一种是@Value+@RefreshScope的形式)
动态路由
要实现动态路由首先要将路由配置保存到Nacos,当Nacos中的路由配置变更时,推送最新配置到网关,实时更新网关中的路由信息
需要完成两件事:
- 监听Nacos配置变更的信息,此事在监控手册 | Nacos 官网中的用户指南的java的SDK中已有记载
- 当配置变更时,将最新的路由信息更新到网关的路由表
- 可以利用RouteDefinitionWriter来更新路由表
路由配置语法:
- 为了方便解析从Nacos读取到路由配置,推荐使用Json格式的路由配置,模板如下
1
2
3
4
5
6
7
8
9
10
11
12
13{
"id": "item",
"uri": "lb://item-service",
"predicates": [{
"name": "Path",
"args": {
"_genkey_0": "/items/**"
"_genkey_1": "/search/**"
}
}],
"filters": []
}






