注册中心原理
基本概念
服务提供者
- 注册:在注册中心声明自己的服务信息
- 续约:通过心跳,在注册中心续约
注册中心
- 记录服务提供者信息,通过心跳机制 “更新” 注册信息
- 记录服务调用者信息,通过心跳机制 “推送” 变更信息
服务调用者
- 订阅:订阅 myService-server 的信息
- 调用:需要服务时,从注册中心订阅和拉取服务信息
基本功能
- **负载均衡:**当多个微服务实例并行运行时,通过注册中心和负载均衡器将请求合理分配到各个实例,避免单个实例负载过高
- **故障转移:**当某个微服务实例出现故障时,通过注册中心的健康检查机制自动将请求转移到其他正常运行的实例,确保服务的高可用性
- **服务发现:**帮助服务消费者自动发现和获取可用的服务实例信息,无需硬编码服务地址
- **配置管理:**提供统一的配置管理,支持动态更新服务配置
工作流程
- 服务启动时,在 “注册中心” 注册自己的服务信息(服务名、IP、端口)
- 服务调用时,在 “注册中心” 订阅需要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
- 调用者自己对实例列表负载均衡,挑选一个实例
- 调用者向挑选的实例发起远程调用
健康检查(心跳机制)
- 服务启动:当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
- 服务通知:当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
- 服务保持:服务提供者会定期向注册中心发送请求,报告自身健康状态(心跳请求)
- 服务终止:当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
常用注册中心
Nacos
:Alibaba 公司出品,目前被集成在 SpringCloudAlibaba 中,一般用于 Java 应用Eureka
:Netflix 公司出品,目前被集成在 SpringCloud 当中,一般用于 Java 应用Consul
:HashiCorp 公司出品,目前集成在 SpringCloud 中,不限制微服务语言
Nacos 注册中心
一、启动 Nacos
-
创建数据库:准备MySQL数据库表,用来存储 Nacos 的数据
-
创建配置文件:custom.env
-
启动 Nacos:Docker 部署 Nacos 的注册中心
docker 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
-
使用 Nacos:http://192.168.xxx.xxx:8848/nacos/ (账号密码都是 nacos)
二、配置管理
-
功能
- 共享配置文件:统一保存和管理所有微服务共享的配置
- 配置热更新:Nacos 可以将配置变更推送给相关的微服务,且无需重启即可生效
-
配置文件结构
配置项 说明 Data ID
格式:[服务名]-[环境].yaml
服务名:微服务的名称(如:shared-jdbc、cart-service等) 环境:对应spring.active.profile的值(如dev、prod),可省略表示所有环境共享 Group
配置所属的分组(如:DEFAULT_GROUP) 描述
对当前配置的用途说明 配置格式
支持YAML、Properties、JSON等格式 配置内容
具体的配置项内容,按所选格式编写 -
配置热更新
功能
:实现微服务的配置热更新,避免每次修改配置文件都需要重启微服务实现方法
:在微服务的 com.hmall.myservice.config 目录下添加 MyServiceProperties 配置类,用于与 nacos 配置文件同步
-
⭐ 使用流程
- 打开 nacos 控制台:http://192.168.7.7:8848/nacos/
- Nacos 添加共享配置:”配置管理” → “配置列表” → “+” 新建一个共享配置
- 微服务中,删除本地的共享配置部分
-
微服务中,引入依赖,使得 nacos 管理配置文件,并且 springCloud 可以提前读取配置文件(pom.xml)
<!--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>
-
微服务中,新建配置文件,使得 spring cloud 可以找到 nacos 位置(com.hmall.cart.resource.bootstrap.yaml)
spring:application:name: cart-service # 服务名称, 后续使用该名称就可以调用该服务profiles:active: devcloud:nacos:server-addr: 192.168.7.7 # nacos地址, 即注册中心的位置config:file-extension: yaml # 文件后缀名shared-configs: # 共享配置- dataId: shared-jdbc.yaml # 共享mybatis配置- dataId: shared-log.yaml # 共享日志配置- dataId: shared-swagger.yaml # 共享日志配置
-
重启微服务,通过 bootstrap.yaml 找到 nacos 位置以及共享配置,拉取共享配置
-
微服务中,新建属性读取类,实现部分配置的热更新(com.myproject.myservice.config.MyServiceProperties.class)
@Data // 自动生成 getter setter 方法 @Component // 交给 spring 管理 @ConfigurationProperties(prefix = "myproject.myservice") // 声明当前配置文件所在的 Service public class MyServiceProperties {private ElemType myProperty; // 对应 myproject.myservice.myProperty 属性}
-
在微服务中,使用热更新的配置信息(com.hmall.cart.service.impl.CartServiceImpl.class)
// 注入 properties 对象 private final MyServiceProperties myServiceProperties;// 调用 myProperty 配置信息 public void getMyProperty() {System.out.println(myServiceProperties.getMyProperty().toString()); }
三、服务注册
- 功能:将微服务注册到注册中心,以便微服务之间互相调用
- 工作流程
-
引入依赖:在服务提供方添加依赖
<!--nacos 服务注册发现--> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
-
添加配置:在服务提供方添加 nacos 地址信息(/project/server-package/application.yml)
spring:cloud:nacos:server-addr: 192.168.7.7:8848 # nacos地址, 即注册中心的位置(服务在该地址注册)
-
重启服务
-
四、服务发现 & 负载均衡
-
功能:方便服务调用者获取服务提供者的 URI ,并向其发送请求获取服务
-
工具类
- DiscoveryClient
-
定义:Spring Cloud中的一个用于 “服务发现” 的接口,根据注册中心的不同而有不同的实现
-
功能:让 ”服务调用者” 可以从注册中心获取服务实例的信息,并进一步进行负载均衡和服务调用
-
实现类:Spring Boot 通过 “注册中心的依赖” 和 “注册中心的配置” 来决定自动注入的实现类
-
常用方法
方法 功能 List<ServiceInstance> getInstances(String serviceId)
根据服务ID获取所有实例列表 List<String> getServices()
获取所有注册的服务列表
-
- ServiceInstance
-
定义:Spring Cloud 中用于描述服务实例的一个接口
-
功能
方法 功能 URI getUri()
获取服务实例的URI String getServiceId()
获取服务ID String getHost()
获取服务实例的主机名 int getPort()
获取服务实例的端口号 boolean isSecure()
判断服务实例是否是安全的(HTTPS) Map<String, String> getMetadata()
获取服务实例的元数据
-
- DiscoveryClient
-
工作流程
- 添加依赖:自动配置并注册 Nacos 的 DiscoveryClient 实现(如果注册中心用的不是Nacos则会自动注入其他实现类)
- 添加配置信息:告知 springboot 使用的注册中心的类型,用于 DiscoveryClient 自动装配
- 注入依赖:在 “服务调用者” 的 Service 中注入 DiscoveryClient 对象,通过 discoveryClient 获取微服务实例
- 获取服务:通过 discoveryClient 获取微服务实例
-
代码实现
-
引入依赖:在服务调用方添加依赖
<!--nacos 服务注册发现--> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
-
添加配置信息:在服务调用方添加配置信息(/project/server-package/application.yml)
spring:cloud:nacos:server-addr: 192.168.150.101:8848
-
获取服务 & 负载均衡(XxxServiceImpl.java)
@Service public class CartService {@Autowiredprivate final RestTemplate restTemplate;@Autowired private final DiscoveryClient discoveryClient; // 注入Nacos依赖,用于获取服务实例以及负载均衡private void handleCartItems(List<CartVO> vos) {List<ServiceInstance> instances = discoveryClient.getInstances("my-xxxservice"); // 根据服务名称(spring.application.name),拉取服务实例列表if (instances != null && !instances.isEmpty()) {ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size())); // 负载均衡,随机挑选Service实例URI uri = instance.getUri(); // 获取实例的IP和端口号String apiUrl = uri.toString() + "/some-api-endpoint"; // 获取需要调用的服务的api接口地址String response = restTemplate.getForObject(apiUrl, String.class); // 调用其他微服务的APISystem.out.println(response); // 处理响应数据} else {System.out.println("No instances found for my-xxxservice");}}
-
-
优化方案:服务发现 & 负载均衡组件:远程调用 OpenFeign
五、动态路由
功能
:监听Nacos配置变更,并把路由信息更新到路由表- 实现流程
监听配置
:监听 Nacos 上的路由配置文件,当配置发生变化时自动更新本地路由反序列化配置
:将配置文件内容(JSON格式)反序列化为路由对象(RouteDefinition)清除旧路由
:删除本地路由表中的所有旧路由配置更新新路由
:将新的路由配置添加到本地路由表中记录路由ID
:保存新添加的路由ID,方便下次更新时清除旧路由
- 代码实现
-
依赖注入:在网关 gateway 引入依赖
<!--统一配置管理--> <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>
-
然后在网关 gateway 的 resources 目录创建 bootstrap.yaml 文件,内容如下:
spring:application:name: gatewaycloud:nacos:server-addr: 192.168.150.101config:file-extension: yamlshared-configs:- dataId: shared-log.yaml # 共享日志配置
-
修改 gateway 的 resources 目录下的 application.yml,把之前的路由移除,最终内容如下:
server:port: 8080 # 端口 hm:jwt:location: classpath:hmall.jks # 秘钥地址alias: hmall # 秘钥别名password: hmall123 # 秘钥文件密码tokenTTL: 30m # 登录有效期auth:excludePaths: # 无需登录校验的路径- /search/**- /users/login- /items/**
-
配置监听器(com.hmall.gateway.route.DynamicRouteLoader.class)
@Slf4j @Component @RequiredArgsConstructor public class DynamicRouteLoader {private final RouteDefinitionWriter writer;private final NacosConfigManager nacosConfigManager;private final String dataId = "gateway-routes.json"; // 路由配置文件的idprivate final String group = "DEFAULT_GROUP"; // 路由配置文件的分组private final Set<String> routeIds = new HashSet<>(); // 保存当前所有路由id// 首次启动时,注册监听器并初始化路由配置@PostConstructpublic void initRouteConfigListener() throws NacosException {// 注册一个监听器String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, group, 5000, new Listener() {@Overridepublic Executor getExecutor() {return null;}@Overridepublic void receiveConfigInfo(String configInfo) {updateConfigInfo(configInfo);}});// 拉取一次路由配置updateConfigInfo(configInfo);}// 动态更新路由private void updateConfigInfo(String configInfo) {log.debug("监听到路由配置变更,{}", configInfo);// 1. 反序列化:String -> RouteDefinition List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);// 2. 清除旧路由for (String routeId : routeIds) {writer.delete(Mono.just(routeId)).subscribe();}routeIds.clear();// 3. 添加新路由if (CollUtils.isEmpty(routeDefinitions)) {return;}routeDefinitions.forEach(routeDefinition -> {writer.save(Mono.just(routeDefinition)).subscribe(); // 3.1.更新路由routeIds.add(routeDefinition.getId()); // 3.2.记录新路由id,方便下次update时删除旧路由});} }
-
添加 Nacos 配置文件(http://192.168.7.7:8848/nacos)(gateway-routes.json)
[{"id": "item","predicates": [{"name": "Path","args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}}],"filters": [],"uri": "lb://item-service"},{"id": "cart","predicates": [{"name": "Path","args": {"_genkey_0":"/carts/**"}}],"filters": [],"uri": "lb://cart-service"},{"id": "user","predicates": [{"name": "Path","args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}}],"filters": [],"uri": "lb://user-service"},{"id": "trade","predicates": [{"name": "Path","args": {"_genkey_0":"/orders/**"}}],"filters": [],"uri": "lb://trade-service"},{"id": "pay","predicates": [{"name": "Path","args": {"_genkey_0":"/pay-orders/**"}}],"filters": [],"uri": "lb://pay-service"} ]
-