文章目录
- 前言
- 什么是注册中心
- CAP理论
- Eureka
- 搭建Eureka Server
- 服务注册
- 服务发现
前言
在生活中,我们不可避免地与各种医院、学校、政府等机构打交道,那么要想与这些机构取得联系,就需要知道这些机构的电话号码,但是这些机构的电话号码也可能是会变化的,那么当这些机构更换了电话号码之后,不可能让机构一个一个的通知用户说电话号码更换了,那么让个人自己去问更换之后的电话号码又是比较麻烦的,这样机构每换一次电话号码,我们就需要重新知道更换后的电话号码。
那么是否有方法可以解决这个问题呢?当然是有的,我们都知道有114这个查号平台,当机构注册或者更换手机号的时候,都需要把自己的电话号上报给114平台,那么当用户需要知道某个机构的电话号码的时候,就可以直接去114平台询问,那么这样就极大的方便了个人和机构。
那么类似的,在我们的微服务中,当服务方更换机器或者其他原因导致的IP地址更换的时候,我们的使用方要想使用这个服务,就不得不更换调用接口的IP,那么这样的话,每次服务方的IP更换之后就需要更改业务代码,这样就很麻烦,所以在微服务开发中,也可以采用类似的方案。
服务启动/变更时,向注册中心报道,注册中心记录应用和IP的关系。
调用方调用时,先去注册中心获取服务方的IP,再去服务方进行调用。
什么是注册中心
在最初的架构体系中,集群的概念还不那么流行,且机器数量也比较少,此时直接使用DNS+Nginx就可以满足几乎所有服务的发现。相关的注册信息直接配置在Nginx。但是随着微服务的流行与流量的激增,机器规模逐渐变大,并且机器会有频繁的上下线行为,这种时候需要运维手动地去维护这个配置信息是一个很麻烦的操作。所以开发者们开始希望有这么一个东西,它能维护一个服务列表,哪个机器上线了,哪个机器宕机了,这些信息都会自动更新到服务列表上,客户端拿到这个列表,直接进行服务调用即可。这个就是注册中心。
注册中心主要有三个角色:
- 服务提供者(Server):一次业务中,被其他微服务调用的服务,也就是提供接口给其他微服务
- 服务消费者(Client):一次业务中,调用其他微服务的服务,也就是调用其他微服务提供的接口
- 服务注册中心(Registry):用于保存Server的注册信息,当Server节点发生变更的时候,Registry会同步变更,服务与注册中心使用一定机制通信,如果注册中心与某个服务长时间无法通信,就会注销该实例
服务注册:服务提供者在启动时,向Registry注册自身服务,并向Registry定期发送心跳汇报存活状态
服务发现:服务消费者从注册中心查询服务提供者的地址,并通过该地址调用服务提供者的接口,服务发现的一个重要作用就是提供给服务消费者一个可用的服务列表。
CAP理论
谈到注册中心就不可避免的提到CAP理论,CAP理论是分布式系统中最基础,也是最关键的理论。
CAP理论(也称为 布鲁尔定理)是分布式系统领域的一个重要理论,用于描述在分布式环境中对系统特性的一种权衡。它指出:
在分布式系统中,无法同时完全满足以下三种特性,只能在其中选择两个进行优化:
CAP的三种特性
- 一致性(Consistency, C)
所有节点对同一份数据的访问结果是相同的,即所有用户无论在哪个节点查询,看到的数据都是最新的。
- 类似于关系型数据库中的“强一致性”。
- 示例:写入一条数据后,所有节点都立刻能读到这条数据。
- 可用性(Availability, A)
系统在任何时候都能响应用户的请求(无论请求成功与否)。
- 即使部分节点故障,系统仍然能够提供服务。
- 示例:分布式系统某个节点宕机,但其他节点仍然可以处理请求。
- 分区容忍性(Partition Tolerance, P)
系统能够容忍网络分区,即使节点之间的通信中断,系统仍然可以继续运行。
- 在分布式系统中,网络分区是一种不可避免的现象(如网络故障、延迟等)。
- 示例:即使数据中心之间的连接断开,两个数据中心的服务仍然可用。
CAP理论的核心在于:分布式系统中不可能同时完全满足 C、A 和 P,只能在其中选择两种特性进行优化。
由于 P(分区容忍性) 是分布式系统的基本要求,因此在实际系统设计中,往往需要在 一致性(C) 和 可用性(A) 之间做权衡。
所以根据CAP理论的取舍就出现了三种架构:
- CP系统(一致性 + 分区容忍性)
- 强调一致性,即便牺牲部分可用性(例如,某些请求可能会被拒绝)。
- 特点:当网络分区发生时,系统优先保证数据一致性,可能暂停服务。
- AP系统(可用性 + 分区容忍性)
- 强调系统的可用性,即便牺牲部分一致性(例如,可能存在短时间的数据不一致)。
- 特点:当网络分区发生时,系统优先保证服务可用,但可能返回旧数据或不同节点数据不一致。
- CA系统(一致性 + 可用性)
- 强调一致性和可用性,但无法容忍网络分区。
- 特点:仅适用于不涉及分布式的系统(例如单点部署)。
因为 P 特性是无法避免的,所以对于分布式微服务来说,就只有两种架构AP和CP架构。
那么常见的注册中心有哪些,以及这些注册中心是AP还是CP系统呢?
1. Eureka
- 开发者:Netflix 开源,Spring Cloud 提供支持。
- 特点:
- 轻量级:专注于服务注册和发现。
- 高可用性:基于 AP(可用性和分区容忍性)的设计,追求高可用性,允许短暂的不一致性。
- 自我保护机制:当注册中心检测到服务实例较少时,不会立即将其标记为不可用(防止网络抖动影响)。
- 维护状态:目前处于维护模式,推荐用 Spring Cloud Alibaba 的 Nacos 替代。
- 应用场景:
- 使用 Spring Cloud Netflix 生态的系统。
2. Consul
- 开发者:HashiCorp。
- 特点:
- 多功能:同时支持服务注册、配置管理和健康检查。
- 强一致性:基于 Raft 共识算法,确保数据一致性(CP)。
- 跨数据中心支持:适合复杂分布式环境。
- Key-Value 存储:支持简单的配置管理。
- 提供 UI 界面方便查看服务状态。
- 应用场景:
- 需要配置管理和服务注册一体化的场景。
- 对一致性要求较高的分布式系统。
3. Zookeeper
- 开发者:Apache。
- 特点:
- 强一致性:基于 ZAB 协议(类似 Paxos),提供 CP 保证。
- 分布式协调服务:除了服务注册,还支持分布式锁、配置管理等功能。
- 高性能:适合读多写少的场景。
- 复杂性:安装配置较为复杂,需要手动管理健康检查等功能。
- 应用场景:
- 需要强一致性的系统,例如分布式数据库或分布式锁。
- 早期 Hadoop、Kafka 等生态系统的配套工具。
4. Nacos
- 开发者:阿里巴巴。
- 特点:
- 应用场景:
- 微服务架构下的多场景需求。
- 需要动态配置管理的系统。
5. Kubernetes Service Discovery
- 开发者:Kubernetes 社区。
- 特点:
- 内置服务发现:通过 DNS 或环境变量实现服务注册和发现。
- 自动化:服务启动时,自动注册到 Kubernetes 的 etcd 中。
- 整合网络:通过 Kubernetes 的 Service 和 Ingress 提供统一的流量管理。
- 依赖平台:适合云原生应用,不适合非 Kubernetes 环境。
- 应用场景:
- 云原生微服务架构。
- 使用 Kubernetes 作为容器编排工具的系统。
6. Etcd
- 开发者:CoreOS。
- 特点:
- 强一致性:基于 Raft 算法,保证数据一致性。
- 简单高效:以 Key-Value 存储为核心,专注于分布式系统协调。
- 性能强:低延迟、高吞吐。
- 生态支持:Kubernetes 内置使用 etcd 作为元数据存储。
- 应用场景:
- 需要高性能和强一致性的分布式系统。
- Kubernetes 等系统的元数据存储。
7. Redis(非专业注册中心)
- 开发者:Redis 社区。
- 特点:
- 轻量级:利用 Redis 的 Key-Value 存储实现简单的服务注册和发现。
- 非正式:缺乏专门的服务健康检查机制。
- 高性能:适合轻量级分布式服务。
- 应用场景:
- 不需要复杂健康检查的轻量级场景。
注册中心 | 一致性 | 可用性 | 配置管理 | 健康检查 | 应用场景 |
---|---|---|---|---|---|
Eureka | 弱一致性 | 高可用 | 无 | 客户端实现 | Spring Cloud 生态轻量场景 |
Consul | 强一致性 | 高可用 | 有 | 内置 | 多功能需求,跨数据中心 |
Zookeeper | 强一致性 | 中等 | 无 | 手动实现 | 高一致性要求,如分布式锁、任务调度 |
Nacos | 可切换 | 高可用 | 有 | 内置 | 微服务和动态配置场景 |
K8s Service Discovery | 强一致性 | 高可用 | 无 | 自动化 | 云原生环境 |
Etcd | 强一致性 | 高可用 | 无 | 无 | 高性能分布式协调服务 |
在很多情况,给用户提供一个错误的数据也胜过无法提供实例信息造成请求失败更好,所以我们主要学习 AP 架构,其中 Eureka 就是典型的 AP 系统,然后 Nacos 可以实现 AP 和 CP 之间的切换。
Eureka
Eureka 是 Netflix 开源的一款服务注册与发现组件,也是 Spring Cloud Netflix 生态的重要组成部分。它实现了分布式系统中的服务注册与发现功能,适用于微服务架构,具有轻量、灵活的特点。虽然 Eureka 2.0 已经停止维护,新的微服务架构设计中,也不再建议使用,但是目前依然有大量的公司的微服务系统使用 Eureka 作为注册中心。
Eureka 官方文档 https://github.com/Netflix/eureka/wiki
Eureka 采用了典型的客户端-服务端架构:
- Eureka Server:作为注册中心Server端,向微服务应用程序提供服务注册,发现和健康检查等能力
- Eureka Client:服务提供者,服务启动时,会想 Eureka Server 注册自己的信息,Eureka Server 会存储这些信息
那么接下来我们学习一下 Eureka 的使用:
搭建Eureka Server
我们在 eureka server 项目中添加 eureka server 依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
然后在启动类上加上 @EnableEurekaServer
注解,开启 eureka 注册中心服务:
java">import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {public static void main(String[] args) {SpringApplication.run(EurekaServerApplication.class,args);}
}
然后在配置文件中进行 eureka server 的配置:
server:port: 10010 # 为eureka server配置端口号
spring:application:name: eureka-server # 为该项目设置名称
eureka:instance:hostname: localhostclient:fetch-registry: false # 表示是否从Eureka Server获取注册信息,默认为true.因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,这里设置为falseregister-with-eureka: false # 表示是否将自己注册到Eureka Server,默认为true.由于当前应用就是Eureka Server,故而设置为false.service-url:# 设置Eureka Server的地址,查询服务和注册服务都需要依赖这个地址defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
logging:pattern:console: '%d{MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
然后我们启动项目,通过 http://127.0.0.1:10010/ 来看看 eureka server 是否启动成功:
服务注册
我们下订单的时候,订单服务会调用商品服务,所以将商品服务进行服务注册,在 product-service 项目中添加 eureka client依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
完善配置文件:
server:port: 9090
spring:application:name: product-servicedatasource:url: jdbc:mysql://127.0.0.1:3306/cloud_product?characterEncoding=utf8&useSSL=falseusername: rootpassword: ********driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:configuration: # 配置打印 MyBatis 执行的 SQL# log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true #自动驼峰转换
eureka:client:service-url:defaultZone: http://127.0.0.1:10010/eureka/ #eureka server中配置的 defaultZone
logging:pattern:console: '%d{MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
启动服务,然后刷新注册中心:
可以看到product-service已经注册到eureka上了。
服务发现
当服务注册到服务中心之后,我们就可以实现服务发现了:
服务注册和服务发现都封装在eureka-client依赖中,在 order-service 中添加 eureka-client 依赖:
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
完善配置文件:
server:port: 8080
spring:application:name: order-servicedatasource:url: jdbc:mysql://127.0.0.1:3306/cloud_order?characterEncoding=utf8&useSSL=falseusername: rootpassword: lmh041105666driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:configuration: # 配置打印 MyBatis 执行的 SQL# log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true #自动驼峰转换
eureka:client:service-url:defaultZone: http://127.0.0.1:10010/eureka/ # 这里同样是注册中心中配置的defaultZone
logging:pattern:console: '%d{MM-dd HH:mm:ss.SSS} %c %M %L [%thread] %m%n'
远程调用;
java">import org.example.mapper.OrderMapper;
import org.example.model.OrderInfo;
import org.example.model.ProductInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EurekaServiceInstance;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;import java.util.List;@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient;public OrderInfo selectOrderById(Integer id) {OrderInfo orderInfo = orderMapper.selectOrderById(id);//String url = "http://127.0.0.1:9090/product/" + orderInfo.getProductId();//根据应用名称获取服务列表List<ServiceInstance> instances = discoveryClient.getInstances("product-service");//服务可能有多个,获取第一个EurekaServiceInstance instance = (EurekaServiceInstance) instances.get(0);//拼接urlString url = instance.getUri() + "/product/" + orderInfo.getProductId();ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);orderInfo.setProductInfo(productInfo);return orderInfo;}
}
启动order-service服务,然后刷新注册中心:
这个中间的警告我们先不管,可以看到通过上面的操作,我们的服务注册和服务发现都可以正常实现。
然后我们待用订单服务,看看订单服务是否能够正常调用商品服务: