前提提示

飞书文档,老师档案
https://b11et3un53m.feishu.cn/wiki/R4Sdwvo8Si4kilkSKfscgQX0niB

1. Nacos

1.导入依赖

1
2
3
4
5
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2.配置文件连接Nacos
配置自己的nacos虚拟机地址,Nacos根据application下的name注册
1
2
3
4
5
6
spring:
application:
name: cart-service
cloud:
nacos:
server-addr: 172.16.207.227:8848

2. DiscoverClient

配合Nacos使用Spring封装好的Client

  1. 自动注入
    1
    2
    3
    4

    @RequiredArgsConstructor //必要参数构造,配合final注入bean,优化AutoWiread
    private final DiscoveryClient discoveryClient;

  2. 简单使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    //nacos获取uri
    List<ServiceInstance> services = discoveryClient.getInstances("item-service");
    //判断不存在
    if (services.isEmpty()){
    return;
    }
    //负载均衡,随机
    ServiceInstance instance = services.get(RandomUtil.randomInt(services.size()));
    //uri
    URI uri = instance.getUri();
    //网络httpclient查询item-service
    ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
    uri + "/items?ids={ids}",
    HttpMethod.GET,
    null,
    new ParameterizedTypeReference<List<ItemDTO>>() {
    },//TODO反射获取
    Map.of("ids", CollUtils.join(itemIds, ","))
    );
    //判断是否成功
    if (!response.getStatusCode().is2xxSuccessful()){
    //查询失败
    return;
    }
    //解析response
    List<ItemDTO> items = response.getBody();

    if (CollUtils.isEmpty(items)) {
    return;
    }

3. OpenFeign,优化Client

1. 导入依赖

1
2
3
4
5
6
7
8
9
10
<!--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>

2. 使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. 启动项开关
openFeign开关,basePackages为扫描的包,防止不同模块下Client不能注册为Bean
@EnableFeignClients(basePackages = "com.hmall.api.client")

2. FeignClient客户端写法
@FeignClient("item-service") //Nacos中注册的名字,声明服务名称,自动负载均衡
public interface ItemClient {

@GetMapping("/items") //请求方法和路径
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids); //声明请求参数,返回值类型

}

3. 使用方法
//通过openFegin查询
List<ItemDTO> items = itemClient.queryItemByIds(itemIds);

3. 优化连接池

默认连接池为Client,效率不高

1
2
3
4
5
6
7
8
9
10
11
12
1. 引入okhttp的依赖
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>


2. 配置文件开启连接池
feign:
okhttp:
enabled: true # 开启OKHttp功能

4. 最佳使用方法

参考飞书文档
https://b11et3un53m.feishu.cn/wiki/R4Sdwvo8Si4kilkSKfscgQX0niB

5.配置日志

  1. 在通用模块下新建config包
    注册Bean添加日志
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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. 网关

通过使用网关将前端的路径保持不变并传递当前登录用户的信息
一般新建一个模块,前端发来请求,网关先验证是否携带有效jwt令牌,若携带则根据前端的路径判断网关将这次请求发送到哪一个微服务中并且将令牌添加到请求头中发送

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
1.添加依赖

<!--网关-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

2.导入jwt相关配置信息和问价和工具类

3.在配置文件中写入不做用户验证拦截,要放行的路径
hm:
jwt:
location: classpath:hmall.jks
alias: hmall
password: hmall123
tokenTTL: 30m
auth:
excludePaths:
- /search/**
- /users/login
- /items/**
- /hi

4.配置文件添加路由

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/**
- id: user
uri: lb://user-service
predicates:
- Path=/users/**,/addresses/**
- id: trade
uri: lb://trade-service
predicates:
- Path=/orders/**
- id: pay
uri: lb://pay-service
predicates:
- Path=/pay-orders/**


5.添加全局拦截器
@RequiredArgsConstructor
@Component
public class authGloableFilter implements GlobalFilter {

private final AuthProperties authProperties;
private final JwtTool jwtTool;
private final AntPathMatcher antPathMatcher = new AntPathMatcher();


@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取request
ServerHttpRequest request = exchange.getRequest();
//2.是否需要拦截
if (isExclude(request.getPath().toString())){
//2.1 放行
return chain.filter(exchange);
}
//3.获取token
List<String> authorization = request.getHeaders().get("authorization");
//3.1 判断是否为空
String token = null;
if (authorization != null && !authorization.isEmpty()){
token = authorization.get(0);
}
//4. 解析token
Long userId = null;
try {
userId = jwtTool.parseToken(token);
} catch (Exception e) {
ServerHttpResponse response = exchange.getResponse();
//4.1设置状态码401
response.setStatusCode(HttpStatus.UNAUTHORIZED);
//4.2结束
return response.setComplete();
}
//5.传递userId
String userInfo = userId.toString();
ServerWebExchange build = exchange.mutate() //重要方法
.request(builder -> builder.header("user-info", userInfo))
.build();
System.out.println("userId: " + userId);
//6.放行已配置请求头的请求
return chain.filter(build);
}

private boolean isExclude(String string) {
//判断当前路径是否存在于配置文件中放行的路径中
for (String pathPattern :authProperties.getExcludePaths()) {
if (antPathMatcher.match(pathPattern,string)){
return true;
}
}
return false;
}
}




5. 通过配置类实现微服务之间传输token

将token信息传入openFegin中的请求头中传输token信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
1.在通用模块通过拦截器将请求头中的token添加到ThreadLocal中
public class UserInfoInterceptors implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.提取请求头信息
String userId = request.getHeader("user-info"); //配合网关请求头的设置
//2.判断不能为null或空
if (StrUtil.isNotBlank(userId)){
//3.存入UserContext
UserContext.setUser(Long.valueOf(userId));
}
//4.放行
return true;
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清理用户
UserContext.removeUser();
}
}


2. 添加拦截器
因为已经在网关中验证token合法性所以全部放行
@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
//获取用户信息拦截器(userId)
registry.addInterceptor(new UserInfoInterceptors());
}
}

不过,需要注意的是,这个配置类默认是不会生效的,因为它所在的包是com.hmall.common.config,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。
基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hmall.common.config.MyBatisConfig,\
com.hmall.common.config.MvcConfig,\
com.hmall.common.config.JsonConfig

3. openFegin中获取并传递实现微服务间传递
public class DeafultFeginConf {

/**
* 配置openFegin的日志级别
* @return
*/
@Bean
public Logger.Level feignLogger(){
return Logger.Level.HEADERS;
}

/**
* 将用户信息传入请求头中
* @return
*/
@Bean
public RequestInterceptor userInfoRequestInterceptor(){
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
Long user = UserContext.getUser();
if (user != null){
requestTemplate.header("user-info" , user.toString());
}
}
};
}
}

4. 微服务启动项加入注解(不同模块需要)
@EnableFeignClients(basePackages = "com.hmall.api.client", defaultConfiguration = DeafultFeginConf.class)

nacos实现配置共享

在nacos中写入配置文件,通过bootstrap获取。减少重复配置文件的编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1. 添加依赖
<!--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>

2. 在nacos中导入配置文件
详情看飞书:微服务02-3.1 配置管理
https://b11et3un53m.feishu.cn/wiki/UMgpwmmQKisWBIkaABbcwAPonVf


3. bootstrap
原理是在启动时springboot先读取bootstrap.yaml文件在读取application.yaml
spring:
application:
name: cart-service # 服务名称
profiles:
active: dev
cloud:
nacos:
server-addr: 172.16.207.227:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
shared-configs: # 共享配置
- dataId: shared-jdbc.yaml # 共享mybatis配置
- dataId: shared-log.yaml # 共享日志配置
- dataId: shared-swagger.yaml # 共享日志配置

nacos实现热更新

本质是通过读取配置文件获取信息,比如将购物车最多商品数量写入nacos的配置文件,再通过读取实现业务功能。
因为bootstrap读取nacos中的配置文件时会同时读取${application.name}.yaml配置文件,所以我门只需要新建一个这个名字的配置文件并将信息写入即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. nacos中的配置文件
hm:
cart:
maxItem: 7

2. 读取
@Component
@Data
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
private int maxItem; //需要同名
}

3. 后续即可使用
比如购物车最大商品数量:注入CartProperties,再比较时通过比较maxItem进行判断实现热更新,不需要重启微服务

sentinel线程隔离,熔断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1. 引入依赖
<!--sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

2. 配置文件配置控制台
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090

3. openFegin整合sentinel(在管理页面将client增加为簇点)
feign:
sentinel:
enabled: true # 开启feign对sentinel的支持
  1. openFegin中的FallBack
    在服务熔断时,异常率过高时,直接调用错误的FallBack减小性能压力
    FallBack本质是处理client调用异常情况,增加用户体验
    失败降级处理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    1. 创建FallBackFactory
    @Slf4j
    public class ItemClientFallback implements FallbackFactory<ItemClient> {
    @Override
    public ItemClient create(Throwable cause) {
    return new ItemClient() {
    @Override
    public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
    log.error("远程调用ItemClient#queryItemByIds方法出现异常,参数:{}", ids, cause);
    // 查询购物车允许失败,查询失败,返回空集合
    return CollUtils.emptyList();
    }

    @Override
    public void deductStock(List<OrderDetailDTO> items) {
    // 库存扣减业务需要触发事务回滚,查询失败,抛出异常
    throw new BizIllegalException(cause);
    }
    };
    }
    }

    2. 注册Bean
    hm-api模块中的com.hmall.api.config.DefaultFeignConfig类中将ItemClientFallback注册为一个Bean:
    public ItemClientFallback fallback(){
    return new ItemClientFallback();
    }

    3. 使用FallBack
    在注解中添加
    @FeignClient(value = "item-service",
    fallBackFactory = ItemClientFallback.class)
    public interface ItemClient {
    @GetMapping("/items") //请求方法和路径
    List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids); //声明请求参数,返回值类型

    }

seata解决微服务事务问题

  • TC - 事务协调者:维护全局和分支事务的状态,协调全局事务提交或回滚。
  • TM - 事务管理器:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
  • RM - 资源管理器:管理分支事务,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    1. 在docker中部署,需要和mysql连接并且创建需要的db,需要和nacos连接
    ....

    2. 引入依赖
    <!--统一配置管理-->
    <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>
    <!--seata-->
    <dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    </dependency>


    3. nacos添加共享seata配置
    seata:
    registry: # TC服务注册中心的配置,微服务根据这些信息去注册中心获取tc服务地址
    type: nacos # 注册中心类型 nacos
    nacos:
    server-addr: 192.168.150.101:8848 # nacos地址
    namespace: "" # namespace,默认为空
    group: DEFAULT_GROUP # 分组,默认是DEFAULT_GROUP
    application: seata-server # seata服务名称
    username: nacos
    password: nacos
    tx-service-group: hmall # 事务组名称
    service:
    vgroup-mapping: # 事务组与tc集群的映射关系
    hmall: "default"

    4. 全局事务注解入口
    @GlobalTransactional注解就是在标记事务的起点,将来TM就会基于这个方法判断全局事务范围,初始化全局事务。


    5. 四种模式
    XA 规范 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准,XA 规范 描述了全局的TM与局部的RM之间的接口,几乎所有主流的数据库都对 XA 规范 提供了支持。
    XA:失败就回滚,可能事务阻塞导致性能差。但是满足事务的ACID原则

    AT模式同样是分阶段提交的事务模型,不过缺弥补了XA模型中资源锁定周期过长的缺陷。
    AT:对各个事务生成快照,若报错则回滚并且回到快照时间。

es搜索

安装es和可视化控制台管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
1. 安装
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
-p 9200:9200 \
-p 9300:9300 \
elasticsearch:7.12.1

2. 安装Kibana(控制台控制es)
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://172.16.207.227:9200 \
--network=hm-net \
-p 5601:5601 \
kibana:7.12.1

加入IK分词器插件

1
2
3
4
5
6
7
8
9
10
1. 在线安装
docker exec -it es ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip

2. 离线安装:
查看数据卷位置
docker volume inspect es-plugins

将插件解压到对应数据卷位置

重启es:docker restart es

IK分词器使用

1
2
3
4
5
6
7
8
9
10
11
POST /_analyze
{
"analyzer": "ik_max_word",
"text": "传智播客开设大学,真的泰裤辣!"
}

POST /_analyze
{
"analyzer": "ik_smart",
"text": "传智播客开设大学,真的泰裤辣!"
}

索引库的CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
1. 查询
GET /索引库名

2. 修改
PUT /索引库名/_mapping
{
"properties": {
"新字段名":{
"type": "integer"
}
}
}

3. 删除
DELETE /索引库名

4. 新增
POST /索引库名/_doc/文档id
{
"字段1": "值1",
"字段2": "值2",
"字段3": {
"子属性1": "值3",
"子属性2": "值4"
},
}

5. 局部处理
POST /{索引库名}/_update/文档id
{
"doc": {
"字段名": "新的值",
}
}

占位

1

占位

1