文章目录
Eureka注册中心
在上一篇中,我们提到了服务调用。
但在服务调用中,存在一系列问题:
- 服务消费者该如何获取服务提供者的地址信息?
- 如果有多个服务提供者,消费者该如何选择?
- 消费者如何得知服务提供者的健康状态(是否宕机)?
Eureka的作用
通过使用Eureka,就可以解决服务调用所产生的问题了:
消费者如何获取服务提供者具体信息?
- 服务提供者启动时向eureka注册自己的信息。
- eureka保存这些信息。
- 消费者根据服务名称向eureka拉取提供者信息。
如果有多个消费者,消费者该如何选择?
- 服务消费者利用负载均衡算法,从服务列表中挑选一个。
消费者如何感知服务提供者的健康状态?
- 服务提供者每隔30秒向EurekaServer发送心跳请求,报告健康状态。
- eureka会更新记录服务列表信息,心跳不正常会被剔除出列表。
- 消费者可以拉取到最新的信息。
在Eureka架构中,微服务角色有两类:
- EurekaServer:服务端,注册中心
- 记录服务信息
- 心跳监控
- EurekaClient:客户端
- Provider:服务提供者,例如上例的user-service
- 注册自己的信息到EurekaServer
- 每隔30秒向EurekaServer发送心跳
- Consumer:服务消费者,例如案例中的order-service
- 根据服务名称从EurekaServer拉取服务列表
- 基于服务列表做负载均衡,选中一个微服务后发起远程调用。
- Provider:服务提供者,例如上例的user-service
搭建Eureka Server
第一步,引入依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud-demo</artifactId>
<groupId>cn.itcast.demo</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
第二步,编写启动类,添加@EnableEurekaServer注解
package cn.itcast.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @version 1.0
* @Description
* @Author 月上叁竿
* @Date 2022-05-04 19:15
**/
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
第三步,编写application.yml配置文件
server:
port: 10086
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
Eureka自身也是一个微服务,因此在启动时会自己注册自己。
搭建Eureka Client
第一步,引入依赖
<!-- 引入eureka客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
第二步,编写application.yml配置文件
spring:
application:
name: userservice # user服务的服务名称
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
如何模拟多实例
服务发现
步骤一:修改url路径,用服务名代替ip、端口
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
// 2.得到user信息 ip 端口 修改为服务名称
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
// 3.将user信息进行封装
order.setUser(user);
// 4.返回
return order;
}
}
步骤二:在order-service项目的启动类中的RestTemplate添加负载均衡注解
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
Ribbon负载均衡
负载均衡流程
当order-serivice发送请求后,Ribbon会对请求进行拦截,向eureka-server拉取user-service的信息,若查到则返回服务列表,通过Ribbon进行负载均衡,选择列表中的一个服务进行调用。
负载均衡策略
Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每一个子接口都是一种规则:
IRule的默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询。
通过定义IRule实现可以修改负载均衡规则,有两种方式:
- 代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean public IRule randomRule(){ return new RandomRule(); }
- 配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
userservice: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负责均衡规则
注:约定大于配置,因此代码方式的优先级要高于配置文件方式。
饥饿加载(饿汉式)
Ribbon默认采用懒加载,即第一次访问时才会创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时就创建,从而降低第一次访问的耗时,通过以下配置开启饥饿加载:
ribbon:
eager-load:
# 指定对userservice这个服务进行饥饿加载,这里的clients可以是列表
clients: userservice
enabled: true # 开启饥饿加载
Nacos注册中心
Nacos是阿里巴巴的产品,现在是Spring Cloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。
Nacos的安装
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
下载好压缩文件后,将其解压至非中文路径下,会有两个目录:
- conf:配置文件
- bin:启动脚本
在conf中的application.properties中可以修改端口,若8848端口已被占用,可以进行修改。
bin目录下有四个启动脚本,注意不要直接启动,而是通过命令行窗口用命令进行启动:
启动服务后,在浏览器键入http://localhost:8848/nacos/index.html#login
会进入Nacos的Web管理界面,密码账号均是nacos。
服务注册
第一步,在cloud-demo父工程中添加spring-cloud-alibaba的管理依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.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和order-service的application.yml,添加nacos地址
spring:
cloud:
nacos:
server-addr: localhost:8848
启动服务并测试
Nacos服务分级存储模型
分级存储模型:
第一级是服务,例如user-service
第二级是集群,例如杭州或上海
第三级是实例,例如杭州机房的某台部署了userservice的服务器
服务跨集群调用问题
服务调用尽可能地选择本地集群的服务,跨集群调用延迟较高。
本地集群不可访问时,再考虑取访问其他集群。
服务集群属性
第一步,修改application.yml,添加如下属性
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SH # 集群名称
在Nacos控制台可以看到集群的变化
集群负载均衡策略
根据集群负载均衡
第一步,修改order-service的application.yml,设置集群为HZ
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ # 集群名称
第二步,在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
NacosRule负载均衡策略:
- 优先选择同集群服务实例列表。
- 本地集群找不到提供者,才会去其他集群寻找。
- 确定了可用实例列表后,再采用随机负载均衡挑选实例。
根据权重负载均衡
实际部署中会出现这样的场景:
- 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的及其承担更多的用户请求。
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。
在Nacos控制台可以设置实例的权重值,首先选中实例后的编辑按钮,在弹出的界面中将权重设置为0.1,测试发现8081端口所在的服务被访问到的频率大大降低。
实例的权重控制总结:
- Nacos控制台可以设置实例的权重值,0~1之间。
- 同集群内的多个实例,权重越高被访问的频率越高。
- 权重设置为0完全不会被访问。
环境隔离
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用于做最外层隔离。
在Nacos控制台可以创建namespace,用于隔离不同环境
填写一个新的命名空间信息
保存后在后台可以看到命名空间信息
修改order-service的application.yml,添加namespace
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ # 集群名称
namespace: 3e16f3b3-6ea4-47fa-af3c-abb2df9c598e # dev环境
重启order-service后,查看控制台
此时访问order-service,由于namespace的不同,会导致找不到userservice,控制台会报错。
环境隔离总结:
- 每个namespace都有唯一的id
- 服务设置namespace时要写id而不是名称
- 不同namespace下的服务互相不可见
原理分析
①服务提供者在启动时会向Nacos注册服务信息,服务消费者启动后会向Nacos定时拉取服务列表,若有对应的服务,则返回列表信息给消费者,消费者通过负载均衡算法选择一个服务进行远程调用。
②服务提供者分为临时实例和非临时实例,临时实例采用心跳监测,每隔一段时间会向Nacos发送请求,以报告自身健康状态,若服务挂掉,一段时间不给Nacos发送请求,Nacos会将其进行剔除;非临时实例由Nacos主动询问其健康信息,若服务挂掉,Nacos不会对其进行剔除,而是等待其恢复正常。
③当服务信息变更时,Nacos会主动推送更新后的服务列表信息。
临时实例和非临时实例
服务注册到Nacos时,可以选择注册为临时或非临时实例,通过修改application.yml文件来进行配置:
spring:
cloud:
nacos:
server-addr: localhost:8848 # nacos服务地址
discovery:
cluster-name: HZ # 集群名称
namespace: 3e16f3b3-6ea4-47fa-af3c-abb2df9c598e # dev环境
ephemeral: false # 是否是临时实例
Nacos和Eureka的异同点
- Nacos与Eureka的共同点
- 都支持服务注册和服务拉取。
- 都支持服务提供者心跳方式做健康检测。
- Nacos与Eureka的不同点
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式。
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除。
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时。
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式。
配置管理
Nacos不仅提供了注册服务,还提供了配置管理服务。
配置获取的步骤如下:
在项目启动后,会将nacos中配置文件内容和本地配置文件application.yml合并,然后再创建Spring容器,加载bean。
由于我们在编写nacos配置文件时,Data ID需要写服务名称,而我们对于服务名称的配置是在application.yml中的,这就造成了无法读取nacos配置文件,那要怎么做呢?
我们可以添加一个引导文件bootstrap.yml,它的优先级是要高于application.yml的。
引入Nacos的配置管理客户端依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
在userservice中的resource目录添加一个bootstrap.yml文件
spring:
application:
name: userservice # 服务名称
profiles:
active: dev # 环境
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
config:
file-extension: yaml # 文件后缀名
测试
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.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Value("${pattern.dateformat}")
private String dateformat;
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
}
将配置交给Nacos管理的步骤:
- 在Nacos中添加配置文件
- 在微服务中引入Nacos的config依赖
- 在微服务中添加bootstrap.yml,配置Nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去Nacos读取哪个配置文件。
配置自动刷新(热更新)
Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要下面两种配置来实现。
方式一:在@Value注入的变量所在类上添加注解@RefreshScope
package cn.itcast.user.web;
import cn.itcast.user.config.PatternProperties;
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.dateformat}")
private String dateformat;
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat);
}
}
方式二:使用@ConfigurationProperties注解
package cn.itcast.user.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @version 1.0
* @Description
* @Author 月上叁竿
* @Date 2022-05-07 10:05
**/
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
@Slf4j
@RestController
@RequestMapping("/user")
//@RefreshScope
public class UserController {
@Autowired
private UserService userService;
// @Value("${pattern.dateformat}")
// private String dateformat;
@Autowired
private PatternProperties properties;
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
@GetMapping("now")
public String now(){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(properties.getDateformat()));
}
}
注意事项:
- 不是所有的配置都适合放到配置中心,维护起来比较麻烦。
- 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置。
- 推荐使用第二种方式进行热更新。
多环境配置共享
微服务启动时会从Nacos中读取多个配置文件:
- [spring.application.name]-[spring.profiles.active].yaml,例如:userservice-dev.yaml,环境配置
- [spring.application.name].yaml,例如:userservice.yaml,默认配置。
无论profile如何变化,[spring.application.name].yaml这个文件一定会被加载,因此多环境共享配置可以写入这个文件。
多种配置的优先级
[服务名]-[环境].yaml > [服务名].yaml > 本地配置