微服务组件-注册中心
使用restTemplate实现远程服务调用存在以下的问题:
1、消费者不知道如何获取服务提供者具体信息。
2、在远程调用的过程中,直接采用填写url的硬编码方式,如果服务消费者发生变化,得到的结果就会出错。
3、如果有多个服务提供者,消费者选择变得困难。
4、消费者无法感知到服务是否存活。
基于上述的问题我们可以使用注册中心实现。流行的注册中心有Eureka、Nacos。
Eureka
简介
Eureka是Netflix开发的一个服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS(Amazon Web Services )域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。Spring Cloud将其集成在自己的子项目Spring Cloud Netflix中,以实现Spring Cloud的服务发现功能。
Eureka 包含两大组件:
服务端发现组件(Eureka Server)
:服务端发现组件也被称之为服务注册中心,主要提供了服务的注册功能
客户端发现组件(Eureka Client)
:客户端发现组件主要用于处理服务的注册与发现。
Eureka的服务发现机制
Eureka的作用
1、服务提供者启动时向eureka注册自己的信息,eureka保存这些信息,消费者根据服务名称向eureka拉取提供者信息。
2、服务消费者利用负载均衡算法,从服务列表中挑选一个
3、服务提供者每个一段时间会向eureka-server发送心跳请求,报告健康状态,eureka会更新记录服务列表信息,心跳不正常会被剔除,消费者就可以拉取到最新消息。
Eureka快速入门
场景
搭建一个注册中心并且让他可以提供注册的服务。
实现步骤
①新建空项目
②新建springBoot模块用于搭建eureka-server
③修改依赖
使用Boot版本为3.0.0。
④配置文件配置
server:
port: 8761 #eureka的默认端口
spring:
application:
name: eureka-server #应用名称 不要使用特殊字符
在入口文件中使用注解开启Eureka功能。
@SpringBootApplication
@EnableEurekaServer //开启eureka注册中心的功能
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
测试
⑥搭建客户端a
注意:如果不选web,启动就会停掉。
⑦修改版本号
⑧修改配置文件
server:
port: 8080 #客户端的端口没有要求,可以使用默认端口
spring:
application:
name: eureka-client-a #应用名称
#将client-a注册到server,将自己的一些信息发送到server
eureka:
client:
service-url: #指定注册的地址
defaultZone: http://localhost:8761/eureka
⑨使用注解开启客户端功能
@EnableEureka
@SpringBootApplication
public class EurekaClientAApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientAApplication.class, args);
}
}
如果@EnableEureka报错不能解决的情况下,可以使用@EnableDiscoveryClient,@EnableEurekaClient只适用于使用Eureka作为注册中心的场景,@EnableDiscoveryClient可以适用于其他注册中心的场景比如nacos等。
⑩使用同样的方式创建客户端b
配置文件
server:
port: 8081
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
spring:
application:
name: eureka-client-b
⑪在浏览器查看
浏览器输入localhost:8761
注意
若需要在客户端A下再创建两台,因为只有端口不相同,不需要重新新建模块,只需要通过复制的方式。
然后直接运行即可。
eureka配置文件
eureka的配置分为三类:server、 client 、实例
eureka-server端配置文件
eureka-server端的配置涉及server、实例
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
server:
eviction-interval-timer-in-ms: 10000 #服务端每隔多少毫秒做定期删除的操作
renewal-percent-threshold: 0.85 #续约百分比, 如果超过85%的应用没有续约(心跳请求) 那么eureka会保护服务,不会剔除任何一个服务
enable-self-preservation: true #server 的自我保护机制,避免因为网络造成误剔除,生产环境建议打开
instance: #实例配置
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}#主机名称:应用名称:端口号
hostname: localhost #主机名称或服务ip
prefer-ip-address: true #表示以ip的方式显示具体的服务信息
lease-expiration-duration-in-seconds: 5 #服务实例的续约的时间间隔
eureka-client端配置文件
eureka-client端配置涉及client、实例
server:
port: 8080
spring:
application:
name: eureka-client-a
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
register-with-eureka: true #可以控制不往eureka-server注册
fetch-registry: true #应用是否去拉取服务列表到本地
registry-fetch-interval-seconds: 10 #为了缓解服务列表的脏读问题,时间越短脏读越少,性能消耗大 默认30秒
instance:
hostname: localhost #应用的主机名称,最好写主机ip
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
prefer-ip-address: true #显示ip
lease-renewal-interval-in-seconds: 10 #实例续约时间
Eureka集群
创建三个eureka-server服务。
修改配置文件
cluster01
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
client:
service-url: #不写的情况下,默认往8761注册
defaultZone: http://localhost:8762/eureka, http://localhost:8763/eureka
instance: #实例的配置
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
prefer-ip-address: true
hostname: localhost
lease-renewal-interval-in-seconds: 5
cluster02
server:
port: 8762
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka, http://localhost:8763/eureka
instance:
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
prefer-ip-address: true
hostname: localhost
lease-renewal-interval-in-seconds: 5
cluster03
server:
port: 8763
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka, http://localhost:8762/eureka
instance:
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
prefer-ip-address: true
hostname: localhost
lease-renewal-interval-in-seconds: 5
添加注解
@EnableEurekaServer
测试
注意:
发现并没有集群信息,只是同一个服务server启动了多台没有数据交互不算真正意义上的集群。原因是:http://localhost:8672/eureka, http://localhost:8763/eureka这样写,eureka认为只有一个机器就是localhost,所以需要修改hosts文件
如果发现没有生效,在命令提示符使用如下命令刷新。
ipconfig /flushdns
修改配置文件.
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://peer2:8672/eureka, http://peer3:8763/eureka
instance:
hostname: peer1
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
lease-renewal-interval-in-seconds: 5
prefer-ip-address: true
同理修改另外两个配置文件。
在cluster01下创建一个客户端测试集群
配置文件
server:
port: 8080
spring:
application:
name: eureka-client
eureka:
client:
service-url:
defaultZone: http://peer1:8761/eureka
register-with-eureka: true
registry-fetch-interval-seconds: 10
fetch-registry: true
instance:
instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
hostname: localhost
prefer-ip-address: true
lease-renewal-interval-in-seconds: 10
总结
在以上的方案中,创建三个服务端使用的是新建三个模块,这样显然比较繁琐且冗余。
优化方案
修改配置文件,然后达到只需要修改端口,然后通过复制创建另外的服务端。
修改配置文件
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://peer1:8761/eureka, http://peer2:8762/eureka, http://peer3:8763/eureka
instance:
lease-renewal-interval-in-seconds: 5
prefer-ip-address: true
instance-id: ${spring.application.name}:${server.port}
记得添加@EnableEurekaServer注解。
客户端配置文件
server:
port: 8080
spring:
application:
name: eureka-client
eureka:
client:
service-url:
defaultZone: http://peer1:8761/eureka, http://peer2:8762/eureka, http://peer3:8763/eureka #都发送,有一台down也没有关系
register-with-eureka: true
registry-fetch-interval-seconds: 10
fetch-registry: true
instance:
ip-address: ${eureka.instance.hostname}:${spring.application.name}:${server.port}
hostname: localhost
prefer-ip-address: true
lease-renewal-interval-in-seconds: 10
Nacos
简介
Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service的首字母简称,一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是阿里巴巴的产品,也是SpringCloud中的一个组件,相比Eureka(https://github.com/Netflix/eureka))功能更加丰富。
安装Nacos
不同操作系统下的安装会有差异,以下是Windows安装Naocs
①下载Nacos
下载安装包
②解压安装包到任意非中文目录
③使用命令提示符启动
在bin目录下使用如下的windos命令
startup.cmd -m standalone #standalone表示单机启动模式
④在浏览器中查看
复制cmd命令窗口中的地址在浏览器中访问
登录密码和账号默认都是
nacos
Nacos服务注册和发现
项目为下面的示例,如果项目中有使用到Eureka需要先注释。
使用实例
实现步骤
①在父工程中加入Nacos依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
②添加nacos的客户端依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
③修改服务端的配置文件
修改user-service 的application.yaml文件
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
相同的方式修改order-service,配置文件内容
spring:
cloud:
nacos:
server-addr: localhost:8848
④启动测试
启动uer-service和order-service在web端查看
Nacos注册中心
nacos服务分级存储模型
服务分级模型是一种将服务层次化组织的架构设计,通常用于大型分布式系统或微服务架构中。这种模型的目标是通过将服务划分为不同的层级,实现更灵活、可维护和可扩展的系统架构。
服务分级模型包括以下几个层级:
特性 | 特性描述 |
---|---|
全局级别 | 在这个层级,通常存储全局配置和共享信息。全局级别的服务对整个系统可见,负责处理全局性的任务和配置,例如全局配置管理、认证、授权等。 |
集群级别 | 集群级别的服务组织在物理或逻辑上相邻的节点上,负责处理集群内的任务和协调。这一层级的服务通常处理一组相关联的节点,提供集群级别的服务如负载均衡、故障转移、数据同步等。 |
服务级别 | 在服务级别,服务被组织成逻辑单元,每个服务负责实现特定的业务功能。服务级别的服务通常是整个系统的核心,提供具体的业务逻辑,例如用户管理、订单处理等。 |
实例级别 | 实例级别是最底层的层级,代表着服务的具体实例。在微服务架构中,服务可能会有多个实例运行在不同的节点上,实现负载均衡和高可用性。 |
通过将服务划分为这些层级,服务分级模型使得系统的不同部分能够独立演化、扩展和维护。这种分级结构使得系统更具弹性,能够更好地适应不同的需求和变化。
服务跨集群调用问题
服务调用尽可能选择本地集群的服务,跨集群调用延迟较高,只有在本地集群不能访问的时候,再去访问其他集群。
服务集群属性
实现步骤
①通过复制模块的方式再复制两个userservice, 端口号为8080、8085,name分别为04、05
②、修改配置文件application.yml
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ #nacos集群名称, HZ代表杭州
③、启动user-service和user-service04
选中相应的服务点击三角运行即可。
④、修改user-service的配置文件
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: SH #nacos集群名称, SH代表上海
⑤、启动user-service05
⑥、查看
点击详情可以查看详细信息。
order-service实现优先用本地集群(发现端口冲突,修改为8084)
①、设置order-service,添加如下配置
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
②、在order-service中设置负载均衡的IRule为NacosRule
这个规则会优先选寻找与自己同集群的服务,然后在集群中随机选择执行。
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则
③、重启order-service
根据权重负载均衡
服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求在Nacos控制台可以设置实例的权重值。
Nacos控制台可以设置实例的权重值,0~1之间,同集群内的多个实例,权重越高被访问的频率越高,权重设置为0则完全不会被访问。
环境隔离(namespace)
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离。
创建命名空间
新建
修改order-service的application.yaml
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
namespace: be5453d5-dfa7-4316-a341-c8987cac180c #命名空间ID
查看
Nacos注册中心原理
临时实例和非临时实例
服务注册到Nacos时,可以选择注册为临时实例或非临时实例,配置如下:
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
namespace: be5453d5-dfa7-4316-a341-c8987cac180c #命名空间ID
ephemeral: true #true为临时实例(默认) false为非临时
Nacos和Eureka之间的异同
相同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
不同点
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
Nacos配置管理
统一配置管理
配置更改热更新
实现步骤
①、在Nacos中添加配置信息
②、填写配置信息
配置文件的ID由三部分组成,[服务名称]-[profile].[后缀名],分组默认,格式可以是yaml和properties,点击发布即可创建成功。
配置获取
实现步骤
①、引入Nacos的配置管理客户端依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
②、在客户端(user-service)的resource目录中添加一个bootstrap.yml文件
spring:
application:
name: userservice
profiles:
active: dev #环境
cloud:
nacos:
server-addr: localhost:8848
config:
file-extension: yaml #文件后缀名
删除application.yaml中重复的内容。如服务名称,nacos地址。
③、测试是否读取到
在UserController中添加代码。
@Value("${pattern.dateformate}")
private String dateformate;
@GetMapping("now")
public String now(){
String testRead = LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformate));
return testRead;
};
@Value注解的成员变量通常为基本数据类型及其包装类,以及String类型,用于直接给该变量赋值。
常用于从*.properties或*.yml配置文件中取值并注入。
④、运行
配置自动刷新
Nacos中的配置文件变更后,微服务无需重启就可以感知。
方式一:
在@Value注入的变量所在类上添加注解@RefreshScop
package cn.itcast.user.web;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {
@Autowired
private UserService userService;
@Value("${pattern.dateformate}")
private String dateformate;
@GetMapping("now")
public String now(){
String testRead = LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformate));
return testRead;
};
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
重启程序
在不重启服务的情况下修改nacos配置
刷新页面
方式二:
使用@ConfigurationProperties注解
package cn.itcast.user.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;
/**
* @author XRY
* @date 2023年08月14日14:25
*/
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformate;
}
在UserController中
@Autowired
private PatternProperties properties;
@GetMapping("now")
public String now(){
String testRead = LocalDateTime.now().format(DateTimeFormatter.ofPattern(properties.getDateformate()));
return testRead;
};
@Autowire作用在自定义的成员变量上,自动装配,使用的是byType策略,即根据变量类型从ioc容器中查找,使用该注解需要在对应的类上挂以下注解中的一个(以将对象注入ioc容器):
@Componet:通用,无语义
@Controller:写在控制层(controller)类上
@Service:写在业务层(service)实现类上
@Repository:写在数据访问层(dao)实现类上
取消原来的方式测试。
多环境配置共享
微服务启动时会从nacos读取多个配置文件
-
[spring.application.name]-[spring.profiles.active].yaml, 如:userservice-dev.yaml
-
[apring.application.name].yaml 如:userservice.yaml
无论profile怎么变化,[apring.application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件。
测试
①、创建共享配置userservice.yaml
②、修改配置类
package cn.itcast.user.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Component;
/**
* @author XRY
* @date 2023年08月14日14:25
*/
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformate;
private String envSharedValue;
}
③、创建一个controller用于测试
@Autowired
private PatternProperties properties;
@GetMapping("/prop")
public PatternProperties properties(){
return properties;
};
④、重启测试
修改环境再次测试 bootstrap.yaml
spring:
application:
name: userservice
profiles:
active: test #环境
cloud:
nacos:
server-addr: localhost:8848
config:
file-extension: yaml #文件后缀名
当多个配置文件中有相同的属性时,他们的优先级为:
Ribbon
Spring Cloud Ribbon 是基于 Netflix Ribbon 实现的一套客户端负载均衡的工具。
简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将 Netflix 的中间层服务连接在一起。Ribbon 客户端组件提供一系列完善的配置项如连接超时,重试等。就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon 会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用 Ribbon 实现自定义的负载均衡算法。
Ribbon负载均衡
负载均衡流程
添加了@LoadBalanced注解的restTemplate地址会被LoadBalancerInterceptor类拦截解析
负载均衡策略
Ribbon的负载均衡规则是一个叫做Rule的接口来定义的,每一个子接口都是一种规则
调整负载均衡
默认负载均衡策略是轮询,通过定义IRule实现可以修改负载均衡规则,有两种方式:
方式一:
使用代码方式定义一个新的IRule,并将它注入到容器中,他将会作用于全局。
@Bean
public IRule randomRule(){
return new RandomRule();
}
方式二:
配置文件方式,在order-service的application.yml文件中,添加新的配置。针对某个微服务而言(局部)。
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则
饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalandeClient,请求时间会很长。而饥饿加载会在项目启动时创建,降低第一次访问的耗时,通过配置开启饥饿加载。
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: userservice#指定饥饿加载的服务名称