1. 背景
1.1 问题描述
我们如果通过 RestTamplate 进行远程调用时,URL 是写死的,例如:
String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();
当机器更换或者新增机器时,这个 URL 就需要相应地变更。这就意味着要通知所有相关服务去修改,进而导致各个项目的配置文件反复更新,各个项目频繁部署。这种工作既繁琐又没有太多实际价值,却又不得不做,给开发者带来极大的困扰。
1.2 解决思路
我们可以从生活场景中获取灵感。在生活里,我们难免要与各种机构(如医院、学校、政府部门等)打交道,需要保存它们的电话号码。若机构更换电话号码,就需通知所有使用方,但这些机构的使用方群体庞大,根本无法做到一一通知,该怎么办呢?
通常的做法是,机构电话变更时通知 114。用户需要联系机构时,先拨打 114 查询电话,再联系相应机构。114 查号台主要有两个作用:
- 号码注册:服务方将电话上报给 114。
- 号码查询:使用方通过 114 能查到对应的号码。
同样,在微服务开发中,也可采用类似方案。服务启动或变更时,向注册中心进行报道,注册中心记录应用和 IP 的关系。调用方在调用时,先去注册中心获取服务方的 IP,再去服务方进行调用。
1.3 什么是注册中心
在早期的架构体系中,集群概念尚未普及,机器数量较少,那时直接使用 DNS + Nginx 就能满足几乎所有服务的发现需求,相关注册信息直接配置在 Nginx 中。然而,随着微服务的兴起和流量的急剧增长,机器规模不断扩大,且机器上下线行为频繁。此时,依靠运维人员手动维护这些配置信息变得非常麻烦。于是,开发者们期望有一个工具,它能维护一个服务列表,机器上线、宕机等信息都能自动更新到这个服务列表上,客户端获取这个列表后可直接进行服务调用,这就是注册中心。
注册中心主要有三种角色:
- 服务提供者(Server):在一次业务中,被其他微服务调用的服务,即提供接口给其他微服务。
- 服务消费者(Client):在一次业务中,调用其他微服务的服务,即调用其他微服务提供的接口。
- 服务注册中心(Registry):用于保存 Server 的注册信息,当 Server 节点发生变更时,Registry 会同步变更。服务与注册中心通过一定机制通信,如果注册中心与某服务长时间无法通信,就会注销该实例。
它们之间的关系以及工作内容,可以通过两个概念来描述:
- 服务注册:服务提供者在启动时,向 Registry 注册自身服务,并向 Registry 定期发送心跳汇报存活状态。
- 服务发现:服务消费者从注册中心查询服务提供者的地址,并通过该地址调用服务提供者的接口。服务发现的一个重要作用就是为服务消费者提供一个可用的服务列表。
1.4 CAP 理论
谈及注册中心,就无法避开 CAP 理论。CAP 理论是分布式系统设计中最基础且最为关键的理论。
- 一致性(Consistency):CAP 理论中的一致性指的是强一致性,即所有节点在同一时间具有相同的数据。
- 可用性(Availability):保证每个请求都有响应(响应结果可能不对)。
- 分区容错性(Partition Tolerance):当出现网络分区后,系统仍然能够对外提供服务。
举例来说,一个部门在全国各地都有岗位,总部下发通知,由于通知需开会周知全员,当有客户咨询时:
- 一致性:所有成员对客户的回应结果都是一致的。
- 可用性:客户咨询时,一定有回应。
- 分区容错性:当其中一个成员休假时,这个部门的其他成员也可以对客户提供咨询服务。
CAP 理论表明:一个分布式系统不可能同时满足数据一致性、服务可用性和分区容错性这三个基本需求,最多只能同时满足其中的两个。
在分布式系统中,系统间的网络无法 100% 保证健康,服务又必须对外提供服务,因此 Partition Tolerance 不可避免。那就只能在 C 和 A 中选择一个,即 CP 或者 AP 架构。
- CP 架构:为保证分布式系统对外的数据一致性,选择不返回任何数据。
- AP 架构:为保证分布式系统的可用性,节点 2 返回 V0 版本的数据(即使这个数据不正确)。
更多参考:一文看懂|分布式系统之CAP理论-腾讯云开发者社区-腾讯云
1.5 常见的注册中心
- Zookeeper:Zookeeper 的官方并未明确表明它是一个注册中心,但在国内 Java 体系中,大部分集群环境都依赖 Zookeeper 来实现注册中心的功能。
- Eureka:Eureka 是 Netflix 开发的基于 REST 的服务发现框架,主要用于服务注册、管理、负载均衡和服务故障转移。官方声明在 Eureka 2.0 版本停止维护,不建议使用。不过,Eureka 是 SpringCloud 服务注册 / 发现的默认实现,所以目前仍有许多公司在使用。
- Nacos:Nacos 是 Spring Cloud Alibaba 架构中重要的组件,除具备服务注册、服务发现功能外,Nacos 还支持配置管理、流量管理、DNS、动态 DNS 等多种特性。
下面通过表格对比一下这三种注册中心基于 CAP 理论的特点:
注册中心 | CAP 理论 |
Zookeeper | CP |
Eureka | AP |
Nacos | CP 或 AP(默认 AP) |
在分布式环境中,拿到一个错误的数据,往往比无法提供实例信息导致请求失败要好(例如淘宝 11.11、京东 618 等活动,都遵循 AP 原则)。在我们的课堂中,会为大家介绍 Eureka 和 Nacos 的使用。
2. Eureka 介绍
Eureka 是 Netflix OSS 套件中关于服务注册和发现的解决方案。Spring Cloud 对 Eureka 进行了集成,并作为优先推荐方案进行宣传。尽管目前 Eureka 2.0 已停止维护,在新的微服务架构设计中也不再建议使用,但当前仍有大量公司的微服务系统将 Eureka 作为注册中心。
官方文档:Home · Netflix/eureka Wiki · GitHub
Eureka 主要分为两个部分:
- Eureka Server:作为注册中心 Server 端,向微服务应用程序提供服务注册、发现、健康检查等能力。
- Eureka Client:服务提供者,服务启动时,会向 Eureka Server 注册自己的信息(IP、端口、服务信息等),Eureka Server 会存储这些信息。
关于 Eureka 的学习,主要涵盖以下三个部分:
- 搭建 Eureka Server。
- 将 order - service、product - service 都注册到 Eureka。
- order - service 远程调用时,从 Eureka 中获取 product - service 的服务列表,然后进行交互。
3. 搭建 Eureka Server
Eureka - server 是一个独立的微服务。
3.1 创建 Eureka - server 子模块
此步骤可通过相应的开发工具(如 IDEA)进行操作,创建一个新的 Maven 子模块,用于搭建 Eureka Server。
3.2 引入 eureka - server 依赖
在创建好的子模块的 pom.xml 文件中添加如下依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring - cloud - starter - netflix - eureka - server</artifactId></dependency>
3.3 项目构建插件
同样在 pom.xml 文件中,配置项目构建插件:
3.4 完善启动类
为项目编写一个启动类,并在启动类上添加 @EnableEurekaServer 注解,开启 eureka 注册中心服务。示例代码如下:
import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer@SpringBootApplicationpublic class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class, args);}}
3.5 编写配置文件
在 application.yml 文件中进行如下配置:
server:port: 10010spring:application:name: eureka - servereureka:instance:hostname: localhostclient:fetch - registry: false
# 表示是否从Eureka Server获取注册信息,默认为true。因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,这里设置为false
register - with - eureka: false # 表示是否将自己注册到Eureka Server,默认为true。由于当前应用就是Eureka Server,故而设置为false
service - url:
# 设置与Eureka Server的地址,查询服务和注册服务都需要依赖这个地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
3.6 启动服务
启动服务后,访问注册中心:http://127.0.0.1:10010/ ,若能看到相应页面,则表明 eureka - server 已启动成功。
4. 服务注册
接下来我们将 product - service 注册到 eureka - server 中。
4.1 引入 eureka - client 依赖
在 product - service 模块的 pom.xml 文件中添加如下依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring - cloud - starter - netflix - eureka - client</artifactId></dependency>
4.2 完善配置文件
在 product - service 模块的 application.yml 文件中添加服务名称和 eureka 地址:
spring:application:name: product - serviceeureka:client:service - url:defaultZone: http://127.0.0.1:10010/eureka
4.3 启动服务
启动 product - service 服务后,刷新注册中心:http://127.0.0.1:10010/ ,可看到 product - service 已成功注册到 eureka 上。
5. 服务发现
接下来我们修改 order - service,在远程调用时,从 eureka - server 拉取 product - service 的服务信息,实现服务发现。
5.1 引入依赖
服务注册和服务发现都封装在 eureka - client 依赖中,所以服务发现时,同样引入 eureka - client 依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring - cloud - starter - netflix - eureka - client</artifactId>
</dependency>
5.2 完善配置文件
服务发现也需要知道 eureka 地址,因此配置内容与服务注册一致,都是配置 eureka 信息:
spring:
application:
name: order - service
eureka:
client:
service - url:
defaultZone: http://127.0.0.1:10010/eureka
5.3 远程调用
在远程调用时,我们需要从 eureka - server 中获取 product - service 的列表(可能存在多个服务),并选择其中一个进行调用。示例代码如下:
import com.bite.order.mapper.OrderMapper;import com.bite.order.model.OrderInfo;import com.bite.order.model.ProductInfo;import org.springframework.cloud.client.ServiceInstance;import org.springframework.cloud.client.discovery.DiscoveryClient;import jakarta.annotation.Resource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.cloud.netflix.eureka.EurekaServiceInstance;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;import java.util.List;import org.slf4j.Logger;import org.slf4j.LoggerFactory;@Servicepublic class OrderService {private static final Logger log = LoggerFactory.getLogger(OrderService.class);@Autowiredprivate OrderMapper orderMapper;@Resourceprivate DiscoveryClient discoveryClient;@Autowiredprivate RestTemplate restTemplate;public OrderInfo selectOrderById(Integer orderId) {OrderInfo orderInfo = orderMapper.selectOrderById(orderId);//String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();//根据应用名称获取服务列表List<ServiceInstance> instances = discoveryClient.getInstances("product - service");//服务可能有多个,获取第一个EurekaServiceInstance instance = (EurekaServiceInstance) instances.get(0);log.info(instance.getInstanceId());//拼接urlString url = instance.getUri() + "/product/" + orderInfo.getProductId();ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}}
5.4 启动服务
启动 order - service 服务后,刷新注册中心:http://127.0.0.1:10010/ ,可看到 order - service 已注册到 eureka 上。访问接口:http://127.0.0.1:8080/order/1 ,可以发现远程调用也成功了。
6. Eureka 和 Zookeeper 区别
Eureka 和 Zookeeper 都是用于服务注册和发现的工具,它们的区别如下:
对比项 | Eureka | Zookeeper |
开源项目所属 | Netflix 开源的项目 | Apache 开源的项目 |
遵循原则 | 基于 AP 原则,保证高可用 | 基于 CP 原则,保证数据一致性 |
节点关系 | 每个节点都是均等的 | 节点区分 Leader 和 Follower 或 Observer |
选举机制 | 无选举机制 | 如果 Zookeeper 的 Leader 发生故障时,需要重新选举,选举过程集群会有短暂时间的不可用 |
通过以上对 Eureka 的详细介绍,相信大家对服务注册与发现以及 Eureka 在 Spring Cloud 中的应用有了更深入的理解。在实际项目中,可根据具体需求选择合适的注册中心。