从零开始构建分布式服务架构:用 `Dubbo` 和注册中心实现远程调用、服务注册与发现、配置管理

本文最后更新于:2 个月前

真正的智慧,在于认识到自己的无知,并持续学习,不断进步。

前言

欢迎阅读本篇博客!在当前大规模分布式系统的开发中,构建可靠的服务架构是至关重要的一环。为了实现远程方法调用、服务注册与发现以及配置管理等功能,使用 Dubbo 和注册中心(ZooKeeperNacos)成为了主流选择

本文将带领您一步步搭建一个强大的分布式服务架构,通过深入探索 Dubbo 和注册中心的使用方式,帮助您轻松实现高效的远程调用和服务管理。我们将详尽介绍如何安装、配置和集成NacosZooKeeper作为注册中心,并结合 Dubbo 框架搭建完整的微服务架构

无论您是刚开始接触分布式架构,还是已经有一定经验,本文都将为您提供实用的技巧和最佳实践,以确保您的服务架构在性能、可靠性和扩展性方面都达到最佳状态

一起来构建强大而灵活的分布式服务架构吧!让我们从安装配置开始,逐步探索 Dubbo 与注册中心的集成,实现高效远程调用和服务管理的愉悦体验

Dubbo 架构实现了什么?

image-20230824134934927

  • 像调用本地方法一样,调用远程方法(2023/08/24 午)
  • 以下是本文的行文思路:
    • 详细指南:使用 ZooKeeper 和 Nacos 搭建注册中心
    • 架构实践:结合 Dubbo 实现灵活的远程方法调用
    • 分布式协作:探索服务注册与发现的最佳实践
    • 高效管理:利用注册中心进行配置管理的技巧与策略

正文

搭建注册中心

  • 我们接下来谈论的下载安装以及配置管理,都是在 windows 系统下进行的(2023/08/22 晚)

下载安装

image-20230823205911004

启动服务

  • 在 ZooKeeper 的 bin 目录下,双击 zkServer.cmd 即可快速启动注册中心:

image-20230822121439364

  • 同理,在 Nacos 的 bin 目录下,双击 startup.cmd 即可快速启动注册中心:

image-20230822121744746

  • 或者,在 bin 目录下,执行以下命令(单机运行):
1
startup.cmd -m standalone

image-20230823210003483

远程方法调用

  • Dubbo 官网:[3 - 基于 Spring Boot Starter 开发微服务应用 | Apache Dubbo ](https://cn. Dubbo .apache.org/zh-cn/overview/mannual/java-sdk/quick-start/spring-boot/)
  • 我们要基于 Dubbo 实现远程调用服务,实现很简单,参考官网文档就可以,这里简单说一下调用流程:

    • 启动注册中心
    • 服务提供者 -> 服务注册
    • 消费者调用服务 -> 服务发现
    • 服务调用成功
  • 这里,我们使用官网文档给出的示例代码,简单梳理下 Dubbo 实现服务远程调用的流程:(2023/08/22 晚)
  • 如上,ZooKeeper 注册中心已经启动成功了
  • 服务提供者 -> 服务注册:
1
2
3
4
5
6
7
@DubboService
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name) {
return "Hello " + name;
}
}

image-20230822131312100

  • 消费者调用服务 -> 服务发现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component
public class Task implements CommandLineRunner {
@DubboReference
private DemoService demoService;

@Override
public void run(String... args) throws Exception {
String result = demoService.sayHello("world");
System.out.println("Receive result ======> " + result);

new Thread(()-> {
while (true) {
try {
Thread.sleep(1000);
System.out.println(new Date() + " Receive result ======> " + demoService.sayHello("world"));
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}).start();
}
}

image-20230822131141978

  • 服务调用成功:

image-20230822130943864

服务注册与发现

  • 在拉取到官方文档中的示例代码并成功运行之后,我们需要在自己的项目中使用 ** Dubbo +Nacos 实现远程服务调用**

  • 成功下载安装 Nacos 注册中心,启动运行:(2023/08/24 午)

image-20230824133939409

  • 如下,在项目中引入相关依赖包:
1
2
3
4
5
<dependency>
<groupId>org.apache.Dubbo</groupId>
<artifactId>Dubbo</artifactId>
<version>3.1.8</version>
</dependency>
1
2
3
4
5
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.2.1</version>
</dependency>
  • 在服务提供者和消费者中,分别添加如下配置:
1
2
3
4
5
6
7
8
9
Dubbo:
application:
name: Dubbo-springboot-demo-provider
protocol:
name: Dubbo
port: -1
registry:
id: nacos-registry
address: nacos://localhost:8848
1
2
3
4
5
6
7
8
9
Dubbo:
application:
name: Dubbo-springboot-demo-client
protocol:
name: Dubbo
port: -1
registry:
id: nacos-registry
address: nacos://localhost:8848
  • 添加@DubboService 注解,作为服务提供者,将该 service 方法注册到注册中心:
1
2
3
4
5
6
7
8
9
/**
* 内部接口服务实现类
*
* @author memory
*/
@DubboService
public class InnerInterfaceInfoServiceImpl implements InnerInterfaceInfoService {
........................
}
  • 添加@DubboReference 注解,作为消费者,从注册中心拉取对应服务,并成功调用:
1
2
@DubboReference
private InnerUserService innerUserService;
1
2
3
4
5
6
7
8
9
10
// 4.1.校验accessKey
// todo 从数据库中查询, accessKey是否分配给该用户
if (accessKey == null || !accessKey.equals("memory")) {
return handleNoAuth(response);
}
// accessKey是否分配给该用户
User invokeUser = innerUserService.getInvokeUser(accessKey);
if (invokeUser == null) {
return handleNoAuth(response);
}

配置管理

  • 在服务提供者和消费者中,分别添加如下配置:(2023/08/24 午)
1
2
3
4
5
6
7
8
9
Dubbo:
application:
name: Dubbo-springboot-demo-provider
protocol:
name: Dubbo
port: -1
registry:
id: nacos-registry
address: nacos://localhost:8848
1
2
3
4
5
6
7
8
9
Dubbo:
application:
name: Dubbo-springboot-demo-client
protocol:
name: Dubbo
port: -1
registry:
id: nacos-registry
address: nacos://localhost:8848

Nacos

2024 年 4 月 26 日

🍖 推荐阅读:

2024 年 6 月 7 日

很久没有真正实战过微服务了,基本上都是学了些理论基础,最近一次都是去年学习黑马教程微服务时写了 Demo 练习。

今天正式实战微服务,以 API 接口开放平台为例。

子服务中导入依赖:

1
2
3
4
5
6
7
8
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

一定要注意版本兼容问题啊,这点就不细说了,直接去官网查就行:

🍖 推荐阅读:版本发布说明 | https://sca.aliyun.com

我还是建议自己总结出一套最正确常用的依赖集合,这样自己做项目就会方便很多。

Nacos 作为 注册中心和配置中心,来看下配置文件:

1
2
3
4
5
6
7
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
import-check:
enabled: false

很清晰,以本地启动的 nacos 为注册中心,服务名称为 memory-client。

启动项目后,看到这样的提示,那就说明已经该服务注册成功了:

image-20240607102508186

查看 Nacos :

image-20240607104006059

将 memory-core,memory-gateway,memory-client 三个服务启动

其中,memory-core 作为 bubbo 服务提供者,其下三个 dubbo 服务也成功注册到 Nacos 注册中心:

1
2
3
4
5
6
7
8
9
10
# Dubbo
dubbo:
application:
name: api-invoke-server
protocol:
name: dubbo
port: -1
registry:
id: api-invoke
address: nacos://localhost:8848

image-20240610203202683

Dubbo

2024 年 7 月 19 日

距离上次重构 Memory API 忆汇廊项目的代码有一个月了,补一下使用 Dubbo 实现远程服务调用的开发流程,以及注意事项。

要保证接口开放平台的核心功能正常,最重要的当然是做到严格的权限校验和身份认证,避免出现恶意调取接口的情况。

我们选择把校验的代码逻辑,全部统一放到 Gateway 网关过滤器中完成,实现统一的请求过滤和身份认证。

在先前的网关实现中,我们已实现基于路由配置的请求转发到后端接口。

但随着新需求的发展,我们希望 memory-gateway 服务的代码量保持简洁。

为此,我们考虑将核心业务逻辑,如用户身份验证、接口存在性检查等,移至其他微服务中实现,而 memory-gateway 仅作为外部服务的调用者。

这涉及到分布式服务架构下的跨服务通信问题,关键在于如何在微服务间实现跨服务方法调用。

为满足这一需求,我们将引入 RPC 框架进行远程调用。

通过 RPC 框架,各微服务可以相互调用方法,实现高效、可靠的跨服务通信。

权限校验

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
/**
* 内部接口服务实现类
*
* @author memory
*/
@DubboService
public class InnerInterfaceInfoServiceImpl implements InterfaceInfoDubboService {

@Resource
private InterfaceInfoMapper interfaceInfoMapper;

/**
* 请求的模拟接口是否存在
*
* @param url 请求 url
* @param method 请求 方法
* @return 接口信息
*/
@Override
public InterfaceInfo getInterfaceInfo(String url, String method) {
if (StringUtils.isAnyBlank(url, method)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
QueryWrapper<InterfaceInfo> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("url", url);
queryWrapper.eq("method", method);

// 获取接口信息
return interfaceInfoMapper.selectOne(queryWrapper);
}
}
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
/**
* 内部用户服务实现类
*
* @author memory
*/
@DubboService
public class UserDubboServiceImpl implements UserDubboService {
@Resource
private UserMapper userMapper;

/**
* 数据库中查是否已分配给用户秘钥(accessKey)
* @param accessKey
* @return
*/
@Override
public User getInvokeUser(String accessKey) {
if (StringUtils.isAnyBlank(accessKey)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("accessKey", accessKey);

return userMapper.selectOne(queryWrapper);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 内部用户接口信息服务实现类
*
* @author memory
*/
@DubboService
public class UserInterfaceInfoDubboServiceImpl implements UserInterfaceInfoDubboService {

@Resource
private UserInterfaceInfoService userInterfaceInfoService;

/**
* 接口调用统计
*
* @param interfaceInfoId 接口 id
* @param userId 用户 id
* @return 是否调用成功
*/
@Override
public boolean invokeCount(long interfaceInfoId, long userId) {
return userInterfaceInfoService.invokeCount(interfaceInfoId, userId);
}
}

依赖导入

配置管理

OpenFeign

踩坑记录

2024 年 6 月 12 日

1
2
3
4
5
<!-- 添加 openfeign 框架依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

nested exception is java.lang.IllegalStateException: RequestParam.value() was empty on parameter 0-CSDN 博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 内部用户服务
*/
@FeignClient(name = "memory-core", path = "/api/user" )
public interface UserFeignClient {

/**
* 数据库中查是否已分配给用户秘钥(accessKey)
*
* @param accessKey
* @return
*/
@GetMapping("/get/invoke")
User getInvokeUser(@RequestParam(value = "accessKey") String accessKey);
}

image-20240612232052768

启动服务报错 For ‘xxx’ URL not provided. Will try picking an instance via load-balancing-CSDN 博客

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
package com.memory.gateway;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.test.context.ActiveProfiles;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
@ActiveProfiles("dev") // 根据你的配置,可能需要指定活动配置文件
public class NacosDiscoveryTest {

@Autowired
private DiscoveryClient discoveryClient;

@Test
public void testServiceDiscovery() {
// 获取所有服务名称
List<String> services = discoveryClient.getServices();
assertNotNull(services, "Services list should not be null");
assertFalse(services.isEmpty(), "Services list should not be empty");

// 检查 'memory-core' 服务是否存在
boolean containsMemoryCore = services.contains("memory-core");
assertTrue(containsMemoryCore, "'memory-core' should be in the services list");

// 获取 'memory-core' 的服务实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("memory-core");
assertNotNull(instances, "Instances list for 'memory-core' should not be null");
assertFalse(instances.isEmpty(), "Instances list for 'memory-core' should not be empty");

// 验证服务实例信息(例如,打印 IP 地址和端口)
for (ServiceInstance instance : instances) {
System.out.println("Service ID: " + instance.getServiceId());
System.out.println("Host: " + instance.getHost());
System.out.println("Port: " + instance.getPort());
// ... 其他验证
}
}

// 注意:这里假设了有一个名为 'assertTrue' 的断言方法,但 Java 的 JUnit 5 并没有这个方法。
// 你应该使用 assertTrue(boolean condition) 或其他相应的断言方法。
}

image-20240612230445281

2024 年 6 月 14 日

特么踩了六天的坑,总算搞定了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 内部用户服务
*/
@FeignClient(name = "memory-core", path = "/api/user/inner")
public interface UserFeignClient {

/**
* 数据库中查是否已分配给用户秘钥(accessKey)
*
* @param accessKey
* @return
*/
@GetMapping("/get/invoke")
User getInvokeUser(@RequestParam(value = "accessKey") String accessKey);
}

就这么简单的几行客户端代码,折腾了我好几天,总是报错还找不到解决方法。

首先,@RequestParam(value = “accessKey”),这行代码必须加上。原因:

nested exception is java.lang.IllegalStateException: RequestParam.value() was empty on parameter 0-CSDN 博客

1
2
3
在 SpringMVC 和 Springboot 中都可以使用 @RequestParam 注解,不指定 value 的用法,为什么到了 Spring cloud 中的 Feign 这里就不行了呢?

这是因为和 Feign 的实现有关。Feign 的底层使用的是 httpclient,在低版本中会产生这个问题,高版本中已经对这个问题修复了。

接下来就是 path = “/api/user/inner” 的配置了,这行代码要是不配置,直接找不到对应服务,报这个错:

feign.RetryableException: Connection refused: connect executing GET http://memory-core/api/user/get/invoke?accessKey=memory

看到这个恶心的错误没,没能在注册中心里找到对应服务。

为了解决这个问题,我还编写了 demo 测试代码,查看了下注册中心里到底有没有注册成功这个服务,都在上面 👆

最后是这个:

image-20240614223433246

No qualifying bean of type ‘org.springframework.boot.autoconfigure.http.HttpMessageConverters‘_no qualifying bean of type ‘org.springframework.bo-CSDN 博客

解决方案就在上面,我在 memory-backend-gateway 模块下面写了一个配置类:

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
package com.memory.gateway.config;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;

import java.util.stream.Collectors;

/**
* ClassName: HttpMesConConfiguration
* Package: com.memory.gateway.config
* Description:
*
* @Author Memory
* @Create 2024/6/14 22:35
* @Version 1.0
*/

@Configuration
public class HttpMesConConfiguration {
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
}

这个问题就完美解决了。

搞定了。

memory-backend-gateway 和 memory-backend-core-service 模块引入依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

编写客户端,这边我新增了一个公共模块:memory-backend-server-client,客户端代码写这里了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 内部用户服务
*/
@FeignClient(name = "memory-core", path = "/api/user/inner")
public interface UserFeignClient {

/**
* 数据库中查是否已分配给用户秘钥(accessKey)
*
* @param accessKey
* @return
*/
@GetMapping("/get/invoke")
User getInvokeUser(@RequestParam(value = "accessKey") String accessKey);
}

memory-backend-gateway 启动类:

1
@EnableFeignClients(basePackages = "com.memory.client.feignClient")

接下来还有什么好说的,直接调用就行了:

1
2
3
@Resource
// private UserDubboService userDubboService;
private UserFeignClient userFeignClient;
1
2
3
4
5
6
7
8
9
10
11
// 3.2.校验accessKey
// todo 从数据库中查询, accessKey是否分配给该用户
if (accessKey == null || !accessKey.equals("memory")) {
return handleNoAuth(response);
}
// accessKey 是否分配给该用户
// User invokeUser = userDubboService.getInvokeUser(accessKey);
User invokeUser = userFeignClient.getInvokeUser(accessKey);
if (invokeUser == null) {
return handleNoAuth(response);
}

真正调用到的是这个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 该服务仅内部调用,不是给前端的
*/
@RestController
@RequestMapping("/user/inner")
public class UserInnerController implements UserFeignClient {

@Resource
private UserService userService;

/**
* 数据库中查是否已分配给用户秘钥(accessKey)
* @param accessKey
* @return
*/
@Override
@GetMapping("/get/invoke")
public User getInvokeUser(@RequestParam(value = "accessKey") String accessKey) {
return userService.getInvokeUser(accessKey);
}

}

2024 年 6 月 16 日

特么真服了,还有这样的问题:

image-20240616104828701

加这行注解吧:

1
2
3
main:
web-application-type: reactive
allow-bean-definition-overriding: true

image-20240616105548316

好,所有 Feign 客户端下的抽象方法中参数都必须这么写:

1
2
3
4
5
6
7
8
9
10
11
12
@FeignClient(name = "memory-core", path = "/api/interface/inner")
public interface InterfaceInfoFeignClient {
/**
* 从数据库中查询模拟接口是否存在(请求路径、请求方法、请求参数)
*
* @param path
* @param method
* @return
*/
@GetMapping("/get/interfaceInfo")
InterfaceInfo getInterfaceInfo(@RequestParam(value = "path") String path, @RequestParam(value = "method") String method);
}
1
2
3
4
5
6
7
8
9
10
11
12
@FeignClient(name = "memory-core", path = "/api/user/inner")
public interface UserFeignClient {

/**
* 数据库中查是否已分配给用户秘钥(accessKey)
*
* @param accessKey
* @return
*/
@GetMapping("/get/invoke")
User getInvokeUser(@RequestParam(value = "accessKey") String accessKey);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
@FeignClient(name = "memory-core", path = "/api/user/interface/inner")
public interface UserInterfaceInfoFeignClient {

/**
* 调用接口统计
*
* @param interfaceInfoId
* @param userId
* @return
*/
@GetMapping("/get/invoke/count")
boolean invokeCount(@RequestParam(value = "interfaceInfoId") long interfaceInfoId, @RequestParam(value = "userId") long userId);
}

步骤

2024 年 7 月 19 日

Gateway

接口路由

权限校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 2. 访问来源校验
// 2.1. 黑白名单
if (!IP_WHITE_HOSTS.contains(sourceAddress)) {
// 设置错误状态码并返回
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.setComplete();
}
// 2.2.内部调用
if (antPathMatcher.match("/ **inner** /", path)) {
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.setComplete();
}
// 2.3.聚合文档
if (SWAGGER_API_DOC_HOST.contains(sourceAddress)) {
log.info("聚合 API 文档访问,不需要校验");
return chain.filter(exchange);
}

聚合文档

Spring Cloud Gateway 网关聚合 | Knife4j (xiaominfo.com)

分别在 memory-backend-core-service 模块和 memory-backend-application 模块中,导入以下坐标:

1
2
3
4
5
6
<!-- https://doc.xiaominfo.com/knife4j/documentation/get_start.html-->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi2-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>

配置文件:

1
2
knife4j:
enable: true

在网关模块 memory-backend-gateway 中,导入以下坐标:

1
2
3
4
5
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-gateway-spring-boot-starter</artifactId>
<version>4.3.0</version>
</dependency>

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
knife4j:
gateway:
enabled: true
# 指定服务发现的模式聚合微服务文档
strategy: discover
discover:
enabled: true
# 指定版本号(Swagger2|OpenAPI3)
version: swagger2
# 需要排除的微服务(eg:网关服务)
excluded-services:
- memory-gateway

到这里,使用 Gateway 实现聚合文档就很轻松的完成了,可以根据需要按需配置,推荐在这里研究学习:

🥣 Spring Cloud Gateway 网关聚合 | Knife4j (xiaominfo.com)

访问:localhost,即可查看到聚合 API 文档,十分方便。

如果这里引用了 Gateway 请求过滤器,可能会有无权限访问的情况发生:

image-20240616111252073

在权限校验中,选择直接放行聚合 API 文档访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 2. 访问来源校验
// 2.1. 黑白名单
if (!IP_WHITE_HOSTS.contains(sourceAddress)) {
// 设置错误状态码并返回
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.setComplete();
}
// 2.2.内部调用
if (antPathMatcher.match("/ **inner** /", path)) {
response.setStatusCode(HttpStatus.FORBIDDEN);
return response.setComplete();
}
// 2.3.聚合文档
if (SWAGGER_API_DOC_HOST.contains(sourceAddress)) {
log.info("聚合 API 文档访问,不需要校验");
return chain.filter(exchange);
}

分布式 session 登录

1
2
3
4
5
6
7
8
9
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
# server
server:
address: 0.0.0.0
port: 8101
servlet:
context-path: /api
# cookie
session:
cookie:
max-age: 2592000
path: /api

跨域解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* ClassName: CorsConfig
* Package: com.memory.gateway.config
* Description: 处理跨域
*
* @Author Memory
* @Create 2024/6/16 12:32
* @Version 1.0
*/

@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.setAllowCredentials(true);
config.setAllowedOriginPatterns(Collections.singletonList("*"));
config.addExposedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}

请求限流

总结


从零开始构建分布式服务架构:用 `Dubbo` 和注册中心实现远程调用、服务注册与发现、配置管理
https://test.atomgit.net/blog/2023/08/22/从零开始构建分布式服务架构:用Dubbo和注册中心实现远程调用、服务注册与发现、配置管理/
作者
Memory
发布于
2023年8月22日
更新于
2024年7月19日
许可协议