1. IP列表
公司发展到肯定的规模之后,应用拆分是无可避免的。假设我们有2个服务(服务A、服务B),如果服务A要调用服务B,我们能怎么做呢?最简单的方法是让服务A配置服务B的所有节点的IP,在服务A内部做负载平衡调用服务B的不同节点。
这种方式有3个显着的题目
- 服务B的节点变动,需要将服务B的IP列表更新进每个服务A,可以是通过配置文件、配置中央,甚至是数据库
- 服务A需要对服务B的节点做健康检测,避免将调用无效的节点
- 服务A要实现服务B调用的负载平衡计谋
2. 反向署理
认识反向署理的人发现,反向署理不正是办理这个题目标办法吗?如果在服务A和服务B之间添加一个nginx,网络拓扑看起来就是如许的
我们需要将服务B的节点配置为upstream,界说nginx的server,服务A通过nginx调用服务B,我们看看下面的核心配置
- weight指定每个服务器的权重,值越大调用的次数越多
- max_fails指定失败多少次后标记为不可用,fail_timeout指过多长时间后将失败节点再次加入到服务列表中
- backup作为备用节点,其他节点不可用的时候,back节点会继承负载平衡
- down标记节点下线
- ip_hash指定负载平衡计谋,通一个IP调度到同一个IP,默认是round-robin
- proxy_next_stream指定哪些条件认为是请求失败的(max_fails统计的条件)
- upstream service_b {
- # 默认轮询策略round-robin,默认权重1
- server 192.168.100.11 weight=1;
- server 192.168.100.12 weight=2 max_fails=10 fail_timeout=30s ;
- server 192.168.100.13 backup; # 备用服务器,当其他服务器都不可用时才使用
- server 192.168.100.14 down; # 标记为不可用,不参与负载均衡
- ip_hash; # 负载均衡策略,基于客户端IP选择,默认round-robin
- }
- server {
- listen 80;
- location / {
- proxy_pass http://service_b;
- proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
- }
- }
复制代码 通过这种方式基本办理1.1里提到的3个题目了,不过upstream的修改仍然是手动的,而且需要重启nginx。 nginx提供了一个模块ngx_http_dyups_module让我们可以通过HTTP调用动态的修改upstream,如果我们想要将service_b的节点改成下面2个节点,我们可以这么做:
- curl 127.0.0.1:8000/upstream/service_b -d
- "server 192.168.100.11:8080 max_fails=3 fail_timeout=5s weight=10;
- server 192.168.100.12:8080 max_fails=3 fail_timeout=5s weight=10;"
复制代码 不过这个方案没有开始流行就已经没落了,最显着的题目是所有对服务B的调用都要经过中央节点(nginx),而且经过了一次转发,影响了调用性能。
3. 注册中央
大概在2010年开始国内的大中厂都开始走向服务化,但并没有一个成熟的中央件,dubbo、motan、hedwig都是这个期间产物。服务提供者在启动的时候会将自己注册到服务注册中央(zookeeper、consul等实现),服务消耗者从注册中央拿到服务提供者的IP,在客户端做负载平衡,直接连接服务提供者的IP,相较于反向署理的方案好处是服务A和服务B是直接调用,避免了一次中央转发。
现在主流的注册中央实现有许多,这里我们选几个常见的对比一下
名称
| CAP
| 语言
| 算法
| 数据结构
| 场景
| 存储
| Zookeeper
| CP
| Java
| Zab协议
| 树ZNode
| 服务发现、锁、选主、配置
| 文件
| Eureka
| AP
| Java
| Gossip
| key-value
| 服务发现
| 内存
| Nacos
| CP + AP
| Java
| Raft
| key-value
| 服务发现、锁、选主、配置
配置推送、流量管理(灰度发布)
| MySQL
| Consul
| AP
| Go
| Raft
| key-value
| 雷同于Nacos
| 文件
|
- Zookeeper最早出现,常用于大数据体系的协调,比如Hadoop/Kafka等,生态成熟,但特性就比较老,出现在云原生之前,没有思量云原生/微服务的支持
- Eureka专门为微服务设计,不支持同等性协议,适用gossip同步数据,选择AP,同等性较弱,功能单一,仅用于服务发现
- Nacos基于Raft算法,支持云原生,比如流量管理、灰度发布等功能
一样平常Java语言开发的新体系的注册中央是在Eureka和Nacos之间选择,Eureka自然和Spring Cloud集成,适用简单,固然功能也相当较弱。我们先来看看Eureka的使用。
4. Eureka入门
1. 创建应用
先创建Spring Boot应用,参见1. 手动创建应用,引入Spring Cloud的依赖管理
- <project>
- ...
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId> <!-- 创建为SpringBoot应用 -->
- <version>3.2.7</version>
- </parent>
- ...
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-parent</artifactId> <!-- 使用Spring Cloud依赖 -->
- <version>2023.0.0</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> <!-- 添加EurekaServer依赖 -->
- </dependency>
- </dependencies>
- </project>
复制代码 2. 启动类
添加启动类,除了正常的Spring Boot应用的注解,额外增加了@EnableEurekaServer注解
- package org.keyniu;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
- @SpringBootApplication
- @EnableEurekaServer
- public class StartEurekaServer {
- public static void main(String[] args) {
- SpringApplication.run(StartEurekaServer.class, args);
- }
- }
复制代码 3. 核心配置
创建配置文件 application.yml,包含的内容如下,我们我看看每个配置字段的含义
配置
| 含义
| Eureka字段
| spring.application.name
| 应用名,注册Eureka是的应用名
| application.instance.app
| eureka.instance.hostname
| 运行实例的主机名或IP,默认取当前呆板的主机名 ;
为了方便辨认一样平常会在/etc/hosts绑定IP和主机名,设置对应节点呆板名 ;
Docker环境会选择prefer-ip-address=true,直接接纳IP地点
| application.instance.hostName
| eureka.instance.lease-renewal-interval-in-seconds
| 客户端向EurekaServer续租的心跳,默认30s
| application.instance.leaseInfo.renewalIntervalInSecs
| eureka.instance.lease-expiration-duration-in-seconds
| 最大的心跳时间隔断,高出时间没心跳的客户端被认为宕机,默认90s
| application.instance.leaseInfo.durationInSecs
| eureka.server.eviction-interval-timer-in-ms
| Eureka定时任务,清理lease-expiration-duration没心跳的节点,默认60s
|
| eureka.client.register-with-eureka
| 是否向EurekaServer注册自己
|
| eureka.client.fetch-registry
| 是否从EurekaServer获取注册表信息
|
| eureka.client.registry-fetch-interval-seconds
| 从EurekaServer获取注册表信息的时间隔断
|
| eureka.client.serviceUrl.defaultZone
| 客户端向这个地点注册和拉取注册信息,服务端节点用它来感知其他peer节点
|
| eureka.server.wait-time-in-ms-when-sync-empty
| 长轮询的概念,同步数据时如果没有数据变动,请求会阻塞等候的时间
|
| eureka.server.renewal-percent-threshold
| 心跳到底比例,如果少于少于这个比例,不会清理无心跳的节点,默认0.85
|
| 留意点:
- Eureka服务端,一样平常register-with-eureka、fetch-registry都设置为false,不注册自己也从eureka拉取注册信息,信息的同步通过内部的gossip协议举行
- Eureka服务端,通过eureka.server.serviceUrl的配置感知其他peer节点,运行期间新增/删除节点通过修改配置文件实现,或配置中央配置
- Client周期性(lease-renewal-interval-in-seconds)向服务端续租,如果高出最大时间(lease-expiration-duration-in-seconds)充公到续租请求,这个节点被认为不可用
- Server周期性(eviction-interval-timer-in-ms)查抄服务器节点,清理不可用的节点
- Client向Server注册后,Server之间通过gossip同步,同步后每个Server节点存储自己的注册表,evict线程(eviction-interval-timer-in-ms)统计当地注册表,检察阈值(renewal-percent-threshold)看是否进入保护模式,如果不是保护模式,清理过去的节点,节点的leaseInfo中生存了每个节点最后一次renewal的时间
- server:
- port: 8080
- spring:
- application:
- name: keyniu-eureka-server
- eureka:
- instance:
- hostname: localhost
- lease-renewal-interval-in-seconds: 30
- lease-expiration-duration-in-seconds: 90
- client:
- register-with-eureka: false
- fetch-registry: false
- service-url:
- defaultZone: http://127.0.0.1:${server.port}/eureka/
- server:
- wait-time-in-ms-when-sync-empty: 5
- enable-self-preservation: true
- eviction-interval-timer-in-ms: 10000
- renewal-percent-threshold: 0.85
复制代码 5. REST API
默认Eureka的接口返回的XML,可以通过提交请求时指定Accept HTTP头设置相应内容的格式为JSON,这一点对所有接口有用,后续不再赘述
- curl -s -H 'Accept: application/json' http://192.168.31.52:8080/eureka/apps/${app}/${instanceId}
复制代码 1. 获取所有实例
通过curl http://192.168.31.52:8080/eureka/apps能检察所有可用的节点列表,包罗所有的应用(application),应用下所有的节点(instance),节点的元数据(metadata)、租约(leaseInfo)等等
2. 指定app的实例
通过如下下令读取数据,这里的KEYNIU-EUREKA-SERVER是app,需要更换成对应的值。
- curl http://192.168.31.52:8080/eureka/apps/${app}
- curl http://192.168.31.52:8080/eureka/apps/KEYNIU-EUREKA-SERVER
复制代码 3. 指定app/instanceId的实例
通过如下下令读取数据,其中KEYNIU-EUREKA-SERVER是app,Randy:keyniu-eureka-server:8080是instanceId
- curl http://192.168.31.52:8080/eureka/apps/${app}/${instanceId}
- curl http://192.168.31.52:8080/eureka/apps/KEYNIU-EUREKA-SERVER/Randy:keyniu-eureka-server:8080
复制代码 4. 服务上下线
通过修改instance的status字段,我们能控制服务的上下线,比如将节点状态改为OUT_OF_SERVICE
- curl -v -XPUT http://192.168.31.52:8080/eureka/apps/${app}/${instanceId}/status?value=OUT_OF_SERVICE
- curl -v -XPUT http://192.168.31.52:8080/eureka/apps/KEYNIU-EUREKA-SERVER/Randy:keyniu-eureka-server:8080/status?value=OUT_OF_SERVICE
复制代码 如果想让节点恢复为上线状态,通过如下下令修改
- curl -v -XDELETE http://192.168.31.52:8080/eureka/apps/${app}/${instanceId}/status?value=UP
- curl -v -XDELETE http://192.168.31.52:8080/eureka/apps/KEYNIU-EUREKA-SERVER/Randy:keyniu-eureka-server:8080/status?value=UP
复制代码 5. 更新元数据
比如我们要在元数据里添加一个admin字段,值是zhangsan,我们可以这么做
- curl -v -XPUT http://192.168.31.52:8080/eureka/apps/${app}/${instanceId}/metadata?${key}=${value}
- curl -v -XPUT http://192.168.31.52:8080/eureka/apps/KEYNIU-EUREKA-SERVER/Randy:keyniu-eureka-server:8080/metadata?admin=zhangsan
复制代码
6. 新增instance
通过POST请求,请求体可以是JSON,格式按我们读取到的实例格式,假设我们要新增一个节点: Randy1:keyniu-eureka-server:8080, 下令看起来是如许的
- curl -v -H 'Content-Type: application/json' -XPOST http://192.168.31.52:8080/eureka/apps/KEYNIU-EUREKA-SERVER -d '{
- "instance": {
- "instanceId": "Randy1:keyniu-eureka-server:8080",
- "hostName": "192.168.31.53",
- "app": "KEYNIU-EUREKA-SERVER",
- "ipAddr": "192.168.31.53",
- "status": "UP",
- "overriddenStatus": "UNKNOWN",
- "port": {
- "$": 8080,
- "@enabled": "true"
- },
- "securePort": {
- "$": 443,
- "@enabled": "false"
- },
- "countryId": 1,
- "dataCenterInfo": {
- "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
- "name": "MyOwn"
- },
- "leaseInfo": {
- "renewalIntervalInSecs": 30,
- "durationInSecs": 90,
- "registrationTimestamp": 1719660811233,
- "lastRenewalTimestamp": 1719661187133,
- "evictionTimestamp": 0,
- "serviceUpTimestamp": 1719655154360
- },
- "metadata": {
- "admin": "zhangsan",
- "management.port": "8080",
- "group": "secondKill"
- },
- "homePageUrl": "http://192.168.31.53:8080/",
- "statusPageUrl": "http://192.168.31.53:8080/actuator/info",
- "healthCheckUrl": "http://192.168.31.53:8080/actuator/health",
- "vipAddress": "keyniu-eureka-server",
- "secureVipAddress": "keyniu-eureka-server",
- "isCoordinatingDiscoveryServer": "true",
- "lastUpdatedTimestamp": "1719660811233",
- "lastDirtyTimestamp": "1719655154170",
- "actionType": "ADDED"
- }
- }'
复制代码 7. 删除instance
通过指定app、instanceId删除对应实例
- curl -XDELETE http://192.168.31.52:8080/eureka/${app}/${instanceId}
- curl -XDELETE http://192.168.31.52:8080/eureka/KEYNIU-EUREKA-SERVER/Randy1:keyniu-eureka-server:8080
复制代码 8. 发送心跳
- curl -XPUT http://192.168.31.52:8080/eureka/apps/${app}/${instanceId}
- curl -XPUT http://192.168.31.52:8080/eureka/apps/KEYNIU-EUREKA-SERVER/Randy1:keyniu-eureka-server:8080
复制代码 6. 案例解析
现在我们反过来,从Eureka UI来看,显示的每个字段是从何而来,怎么配置
1. Eureka首页
下图是Eureka UI首页显示的内容,我们主要关系其中的6个显示字段,对应图上的数字,下面列表中是它的分析
- 环境,通过eureka.environment配置,默认test
- 数据中央,通过eureka.datacenter配置,默认MyOwn
- 是否删除过去租约,只要不处于自我保护模式,这个值就是true,不启动eureka.server.enable-self-preservation
- 进入自我保护模式的阈值,资料上计算公式: 客户端数量 * (60 / lease-renewal-interval-in-seconds ) * renewal-percent-threshold,实测下来有出入,待进一步研究
- 仅一个客户端,lease-renewal-interval-in-seconds = 30 , Renews threshod = 1,计算值= 1 * (60/30) * 0.85 = 1.7
- 仅一个客户端,lease-renewal-interval-in-seconds = 20 , Renews threshod = 3,计算值= 1 * (60/20) * 0.85 = 2.55
- EurekaServer的节点,取值是eureka.client.serviceUrl.defaultZone中配的呆板
- 注册到EurekaServer的节点,这个值是我们配置的spring.application.name,Status里显示的是我们的nodeName,默认格式是: ${hostName}{app.name}{server.port}
2. 注册表信息
通过Eureka Server的REST接口,我们能读到注册表信息,下面这个连接能检察所有的APP信息,不过我们这里只有一个节点
http://127.0.0.1:8080/eureka/apps
下面是其中一个节点的内容,下面有序列表的数字对应图片里的数字
- dataCenterInfo表现数据中央,一样平常配置到Eureka Client,界说是个枚举DataCenterInfo.Name类,可选值有Netflix、Amazon、MyOwn,默认MyOwn
- 对应配置项eureka.instance.data-center-info
- eureka.datacenter也是数据中央的概念,一样平常配置到Eureka Server
- 暂时没看到这两个值如果不同的话有什么影响,但会让人以为杂乱,需要进一步研究
- metadata元数据信息,默认只有management.port,可以做自界说配置
- 对应配置项eureka.instance.metadata-map
- 基于元数据可以做服务分组,用MetadataAwarePredicate实现调用对应分组的节点
- instanceId实际是拼接值: ${主机名}{app.name}{server.port}
- overriddenstatus用来覆盖默认状态,节点的状态默认通过心跳来维护,心跳正常状态为UP
- lastRenewalTimestamp表现最后一次收到心跳的时间,Eureka用这个时间来判定节点是否可用
- lastDirtyTimestamp表现节点信息最后一次更新的时间,用来Eureka Server节点之间的增量
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |