分布式微服务项目离不开服务注册与发现,文章通过记录主流服务注册中心,并列举其差异对比进行核心内容学习。主要包含 Nacos、Zookeeper、Eureka、Consul、Etcd 。
一、关于服务注册中心
目的:服务注册中心本质上是为了解耦服务提供者和服务消费者
一般原理:
分布式微服务架构中,服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息,消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供者的地址信息。消费者只需要知道当前系统发布了哪些服务,而不需要知道服务具体存在于什么位置,这就是透明化路由。
步骤一:服务提供者启动服务
步骤二:服务提供者将相关服务信息主动注册到注册中心
步骤三:服务消费者获取服务注册信息
服务消费者获取服务信息主要有两种模式
- pull模式:服务消费者可以主动拉取可用的服务提供者清单
- push模式:服务消费者订阅服务(当服务提供者有变化是,注册中心也会主动推送更新后的服务清单给消费者)
步骤四:服务消费者直接调用服务提供者
另外,注册中心也需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除
二、主流服务注册中心
1、Nacos
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说,Nacos 就是 注册中心 + 配置中心的组合,帮助我们解决微服务的开发中必回设计到的服务注册与发现,服务配置,服务管理等问题。Nacos是Spring Cloud Alibaba 核心组件之一,服务服务注册与发现,还有配置。
2、Zookeeper
zookeeper之所以用来做服务注册中心,主要是因为它具有节点变更通知功能,只要客户端监听相关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,这样作为调用方只要使用Zookeeper的客户端就能实现服务节点的订阅和变更通知功能了。另外,Zookeeper的可用性也可以,因为只要半数以上的选举节点存活,整个集群就是可用的。
zookeeper的本质=存储+监听通知
zookeeper是一个分布式服务框架,是Apache Hadoop的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理。
3、Eureka
由Netflix开源,并被集成到Spring Cloud体系中,它是基于 Restful API风格开发的服务注册与发现组件。
4、Consul
COnsul是HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件,采用Raft算法保证服务的一致性,并且支持健康检查。
分布式的CAP原则
C:数据一致性 A:高可用性 P:分区容错性
p分区容错性是一定会在分布式系统中满足的,C数据一致性和A高可用只能满足一个,因为分布式系统要不就是CP否则就是AP
组件名 |
语言 |
CAP |
对外暴露接口 |
Nacos |
Java |
支持AP/CP切换 |
HTTP |
Zookeeper |
Java |
CP |
客户端 |
Eureka |
Java |
AP(自我保护机制,保证可用) |
HTTP |
Consul |
Go |
CP |
HTTP/DNS |
三、服务注册中心详解
1、Nacos
Nacos 就是 注册中心 + 配置中心的组合 相当于(Eureka + config + Bus)
官网地址
下载地址
一、功能特性
- 服务发现与健康检查
- 动态配置管理
- 动态DNS服务
- 服务和元数据管理(管理平台的角都,nacos也有一个UI界面,可以看到注册的服务以及实例信息(元数据信息)等),动态的服务权重调整,动态服务优雅下线,都可以去做
二、Nacos单例服务部署
- 下载安装包,执行命令启动
快速开始,准备环境,下载稳定版本包
执行命令,以单例方式启动
1
| startup.cmd -m standalone
|
- 启动完成访问nacos管理界⾯: http://127.0.0.1:8848/nacos/#/login(默认端⼝8848,账号和密码 nacos/nacos)
三、服务注册中心
1、服务提供者注册到Nacos
阿里云测试环境:http://101.132.140.20:18848/nacos
1 2 3 4 5 6 7 8 9 10 11 12
| <dependencyManagement> <dependencies> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
|
- 在服务提供者工程pom文件增加Nacos客户端配置
1 2 3 4
| <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
|
1 2 3 4 5 6 7 8 9
| server: port: 8080 Spring: application: name: sc-service-resume cloud: nacos: discovery: server-addr: 127.0.0.1:8848
|
- 启动类添加
@EnableDiscoveryClient
注解,开启服务注册发现,启动项目
保护阈值:可以设置未0-1之间的浮点数,它其实是一个比例值(当前服务健康实例/当前服务总实例数)
场景:⼀般流程下, nacos是服务注册中⼼,服务消费者要从nacos获取某⼀个服务的可⽤实例信息,对于服务实例有健康/不健康状态之分, nacos在返回给消费者实例信息的时候,会返回健康实例。这个时候在⼀些⾼并发、⼤流量场景下会存在⼀定的问
题如果服务A有100个实例, 98个实例都不健康了,只有2个实例是健康的,如果nacos只返回这两个健康实例的信息的话,那么后续消费者的请求将全部被分配到这两个实例,流量洪峰到来, 2个健康的实例也扛不住了,整个服务A就扛不住,上游的微
服务也会导致崩溃,产⽣雪崩效应。
意义:当服务A健康实例数/总实例数< 保护阈值 的时候,说明健康实例真的不多了,这个时候保护阈值会被触发(状态为true)
nacos 将会把该服务所有实例信息(健康的+不健康的)全部提供给消费者,消费者可能访问到不健康的实例,请求失败,但这样也比造成雪崩要好,牺牲了一些请求,保证了整个系统的一个可用。
2、服务消费者消费服务
四、Nacos数据模型
Namespace命名空间、Group分组、集群这些都是为了进行归类管理,把服务和配置文件进行归类,归类后就可以实现一定的效果,比如隔离
比如:对应服务来说,不同命名空间中的服务不能够互相访问调用
Namespace:命名空间,对不同的环境进行隔离,例如隔离开发环境、测试环境和生产环境
Group:分组,将若干个服务或者若干个配置归集为一组,通常习惯一个系统归为一个组
Service:某一个服务,比如用户微服务
DataId:配置集或者可以认为是一个配置文件
Namespace+group+service 相当于坐标,锁定了服务
Namespace+group+DataId 相当于坐标,锁定了配置文件
概念 |
描述 |
Namespace |
代表不同的环境,如开发:dev,测试:test,生产prod |
Group |
代表某项目,例如授信项目 |
service |
某个项目中具体的xxx服务 |
DataId |
某个项目中具体的xxx配置文件 |
五、数据配置中心
1、编写配置文件
在Nacos server编写配置文件 示例:sc-service-resume.yaml
2、改造具体微服务
使其成为Nacos config client,能够从Nacos中获到配置信息
添加依赖
1 2 3 4
| <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>
|
bootstrap.xml 增加如下配置
1 2 3 4 5 6 7 8 9
| spring cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 namespace: d2d6d364-3d73-465d-bd14-1f7763a96aeb file-extension: yaml
|
微服务中通过 Namespace + Group +dataId来锁定配置文件,NameSpace不指定默认public,Group不指定默认DEFAULT_GROUP
dataId的完整格式如下
1
| ${prefix}-${spring.profile.active}.${file-extension}
|
prefix
默认为spring.application.name
的值,也可以通过配置spring.cloud.nacos.config.prefix
来配置
- spring.profile.active即为当前环境对应的profile。当spring.profile.active为空时,对应的连接符
-
也将不存在,dataId的拼接就变成
1
| ${prefix}.${file-extension}
|
file-exetension
为配置内容的数据格式,可以通过spring.cloud.nacos.config.file-extension
来配置,目前只支持properties
和yaml
类型
3、编写类使用Nacos配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @RestController @RequestMapping("/resume") @RefreshScope public class ResumeController {
@Autowired private ResumeService resumeService;
@Value("${lz.message}") private String lzMessage;
@GetMapping("/viewconfig") public String viewConfig(){ System.out.println("lzMessage = " + lzMessage); return "message=======》"+lzMessage; } }
|
4、扩展使用多个配置
使用ext-fonfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| Spring: profiles: active: dev application: name: sc-service-resume cloud: nacos: discovery: server-addr: 127.0.0.1:8848 config: server-addr: 127.0.0.1:8848 namespace: d2d6d364-3d73-465d-bd14-1f7763a96aeb file-extension: yaml ext-config[0]: data-id: sc-service-resume-2.yaml refresh: true ext-config[2]: data-id: sc-service-resume-3.yaml refresh: true
|
5、Nachos数据持久化
Nacos默认使用嵌入式数据库进行数据存储,它支持改为外部Mysql存储
- 新建数据库 nacos_config,数据库初始化脚本文件
${nacoshome/conf/nacos-mysql.sql}
- 修改
${nacoshome/conf/applicaion.properties}
增加Mysql数据源配置
1 2 3 4 5 6 7 8 9
| spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config? characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&au toReconnect=true db.user=root db.password=123456
|
2、Eureka
一、基础架构
- 服务提供者:client,项目启动后,会向Eureka Server注册中心注册,并通过心跳来续约保持节点联系
- Eureka Server注册中心:需要自己创建工程,client需要引入eureka client的相关jar,并进行相关配置,微服务才能和Eureka Server建立联系
- 客户端消费者:客户端消费者会定期拉取Eureka Server注册中心服务列表,并以缓存形式保存
二、交互流程及原理
Eureka包含两个组件:Eureka Server 和 Eureka Client,Eureka是一个Java客户端,用于简化与Eureka Server的交互;Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka Client向Eureka Server 注册自己的信息(例如网络信息),Eureka Server会存储该服务的信息
- 图中us-east-1c、us-east-1d、us-east-1e 代表不同的地区也就是不同的机房
- 图中每一个Eureka Server都是一个集群
- 图中Application Service 作为服务提供者向Eureka Server中注册服务,Eureka Server接收到注册事件会在集群和分区中进行数据同步,Application Client作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进行服务调用
- 微服务启动后,会周期性的向Eureka Server发送心跳(默认周期为30秒)
- Eureka Server在一定时间内没有接收到某个微服务的心跳,Eureka Server 将会注销该微服务节点(默认90秒)
- 每个Eureka Server同时也是Eureka Client,多个Eureka Server之间通过复制的方式完成服务注册列表的同步
- Eureka Client会缓存Eureka Server中的信息。即使所有的EureKa Server节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者
Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和可用性
三、搭建单例Eureka Server 服务注册中心
1、创建一个maven项目,引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| <?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"> <modelVersion>4.0.0</modelVersion> <packaging>jar</packaging>
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> </parent>
<groupId>lz.eureka</groupId> <artifactId>eureka</artifactId> <version>1.0-SNAPSHOT</version>
<properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
<dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.2.11</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.2.11</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.2.11</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.2.10-b140310.1920</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> </dependencies>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
|
2、启动类添加注解 EnableEurekaServer
1 2 3 4 5 6 7 8
| @SpringBootApplication
@EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class,args); } }
|
3、添加配置文件 application.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13
| server: port: 9527 spring: application: name: lz-eureka-server eureka: instance: hostname: 127.0.0.1 client: service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka register-with-eureka: false fetch-registry: false
|
4、启动
访问:http://localhost:9527/ 即可启动
四、注册生产者
1、创建一个maven项目,引入依赖
1 2 3 4 5 6 7 8 9
| <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> </dependency>
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
|
2、application.yaml配置文件
1 2 3 4 5 6 7
| eureka: client: service-url: defaultZone: http://101.132.140.20:9527/eureka/ instance: prefer-ip-address: true instance-id: ${spring.cloud.client.ipaddress}:${spring.application.name}:${server.port}:@project.version@
|
3、启动类添加注解
五、注册消费者
消费者注册同生产者注册
使用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @SpringBootApplication @EnableDiscoveryClient public class AutoDeliverApplication {
public static void main(String[] args) { SpringApplication.run(AutoDeliverApplication.class,args); }
@Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
@Autowired private DiscoveryClient discoveryClient; List<ServiceInstance> instances = discoveryClient.getInstances("sc-service-resume"); ServiceInstance serviceInstance = instances.get(0); String host = serviceInstance.getHost(); int port = serviceInstance.getPort(); String url = "http://"+host+":"+port+"/resume/openstate/"+userId; Integer forObject = restTemplate.getForObject(url, Integer.class); System.out.println("===========>>>>调用建立微服务,获取到的用户:"+userId+"forObject = " + forObject); return forObject;
|