4.5 深入Eureka

书籍地址: Spring Cloud 微服务架构开发实战

此篇主要是了解 Eureka的底层中

  • 服务注册、续约、注销这些原理
  • 当注册一个微服务到服务治理服务器时为何需要花费那么长的时间,才可以被消费

4.5.1 服务注册、续约、注销等相关原理

我们都知道分布式系统领域有个 CAP定理, 它指出对于一个分布式计算系统来说,不可能同时满足以下3点:

  • 一致性(Consistency): 同一个数据在集群中的所有节点,同一时刻是否都是同样的值.
  • 可用性(Availability): 集群中一部分节点故障后, 集群整体是否还能处理客户端的请求.
  • 分区容忍性(Partition toleerance): 是否允许数据的分区,数据分区的意思是指是否允许集群中的节点之间无法通信.

对于大多数分布式环境,尤其是涉及数据存储的场景, 数据一致性是首先被保证的, 如 Zookeeper采用的设计原则就是 CP原则

而对于微服务的治理而言,核心就是服务的注册和发现。对于服务发现而言,可用性比数据一致性更加重要。AP 胜过 CP。因此,···Eureka···遵守 ** AP原则**。

4.5.1.1 服务注册(Register)

微服务架构中,服务提供者启动时,会调用Eureka所提供的服务注册相关方法,向Eureka服务注册自己的信息。

同时在Eureka服务器会维护一个已注册的服务列表。注册服务列表使用一个嵌套HashMap保存信息。
数据结构:

  • HashMap 的第一层为应用名称和对应的服务实例。
  • HashMap 的第二层为服务实例及其对应的注册信息,包括宿主服务IP地址、服务端口、运行状况指示符、URL等数据

当服务实例状态发生变化时,就会向Eureka服务器更新自己的服务状态,同时用replicateToPeers() 向其它Eureka服务器节点做状态同步;

需要注意的是,如果我们在配置文件中 eureka.client.register-with-eureka属性配置为 false, 就不会执行上述的处理了。

4.5.1.2 服务续约(Renew)

当服务启动并成功注册到Eureka服务器后,Eureka客户端会默认以每30秒的频率向Eureka服务器发送一次心跳。发送心跳起始就是执行服务续约(Renew)操作,避免自己的注册信息被Eureka服务器剔除。其逻辑和注册基本一致。

但是,如果Eureka服务器在默认的时间 90秒内,没有收到客户端的心跳,则会将该服务实例从所维护的服务注册表中剔除。
但但是,如果Eureka服务器处于自我保护模式(默认开启),就不会剔除该服务实例信息。

关于发送心跳的频率,和心跳检测总时长的配置

eureka.instance.lease-renewal-interval-in-seconds=30
eureka.instance.lease-expiration-duration-in-seconds=90

但是,Eureka官方建议我们最好不要修改这两个属性的配置,使用默认值就好

4.5.1.3 服务下线与剔除

当服务实例关闭时,服务实例会先向Eureka服务器发送服务下线请求。发送请求后该服务实例信息将从Eureka服务器的实力注册表中剔除。

4.5.1.4 获取服务

Eureka客户端在启动时会从Eureka服务器中获取注册表信息,并将其缓存在本地。
Eureka客户端会使用该信息查找相应的服务,并进行调用。该注册列表信息定期从Eureka服务器进行同步(默认30秒)。

4.5.2 Eureka 自我保护模式

之前我们说过 Eureka设计采用的时AP原则,即宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。
因此,Eureka中存在自我保护模式,并默认开启。
在自我保护模式下,Eureka服务器会保护服务注册表中的信息,不再注销任何服务实例。

进入保护模式的第一种可能

当Eureka服务器每分钟收到心跳续租的数量低于一个阈值,就会触发自我保护模式。当他受到的心跳数重新恢复到阈值以上,该Eureka服务器节点就会自动退出自我保护模式。心跳余值计算公式:
服务实例总数量 * (60/每个实例心跳间隔秒数) * 自我保护系数(默认0.85)
自我保护系数也可以通过配置修改eureka.server.renewal-percent-threshold=0.85

进入保护模式的第二种可能
我们也可以通过配置,禁用该模式

eureka.server.enable-self-preservation=false

进入保护模式的第三种可能
默认情况下Eureka服务器还会以 每5分钟的间隔从与它对等的节点(由 eureka.client.service-url.defaultZone 配置指定) 中复制所有的服务注册数据以达到同步的目的。但是如果因为某种原因失败了,也会让Eureka服务器进入自我保护模式。

4.5.3 注册一个服务实例需要的时间

Eureka服务治理环境下, 一个微服务上线有三处缓存处理和一处延迟处理,经过这些处理后才能够被服务消费方获取到并使用.分别是:

  • Eureka服务器对服务注册列表进行缓存,默认时间为30秒.所以即使一个服务实例刚刚注册成功,他也可能不会立即在 /ureka/apps 端点的结果中出现.
  • Eureka客户端(服务消费方)对注册的服务信息进行缓存,默认时间为30秒. 就是说客户端决定刷新本地缓存并发现其它新注册的实例的可能需要30秒.
  • Ribbon负载均衡会从Eureka客户端获取服务列表,并将负载均衡后的结果缓存30秒,所以对于Eureka客户端新同步过来的服务节点,可能也会需要30秒之后才能被负载均衡使用.
  • 服务实例(Eureka客户端),在启动时(不是启动完成),不是立即向Eureka服务器注册. 而是在一个延迟时间(默认为40秒)之后才向Eureka服务器注册.

综上几个因素,一个新的服务实例,即使能够很快启动的实例,也不能马上被Eureka服务器发现,其他服务消费者需要一段时间,最长可能需要2分钟以上,才能够被发现了和使用.

4.5.4 Eureka 高可用集群示例

高可用,即 High Availability(HA),也就是经常说服务器可用为 4个9(99.99%) 或 5个9(99.999%).
对于实现Eureka服务器的HA. 可以通过运行多个实例来构建集群,解决单点问题,提高可用率.
因为 Eureka服务器采用的是Peer to Peer 对等通信,是一种去中心化的架构(每个节点都是对等的).在这种架构中,每个节点可以通过彼此互相注册来提高可用性.

我们只需要在每个节点中添加一个或多个有效的serviceUrl指向其他Eureka服务器节点, 这样每个节点都可被视为其他节点的副本.
如果某台Eureka服务器宕机,Eureka客户端的请求会自动切换新的Eureka服务器节点.当宕机的服务器重新恢复后,Eureka会再次将其纳入到服务器集群管理中.
当节点开始接受客户端请求时,所有的操作也会进行节点间复制,将请求复制到其他Eureka服务器当前所知的节点中.

在微服务项目中(服务提供者或服务消费者)中通过 eureka.client.service-url.defaultZone属性配置多个Eureka服务器地址并不能够保障Eureka的高可用,该属性知识能够让微服务实例可以与多个Eureka服务器进行通信,将自己的服务注册到这些服务器上,并从这些服务器中获取所有注册的服务列表。

为了能够让Eureka服务高可用,必须让Eureka服务器之间能够互相复制、同步所注册服务的实例信息。通过Eureka点对点的通信模式,可以将注册到各个Eureka服务器上的服务实例信息复制和同步,从而让整个Eureka集群中的每一个服务器都拥有所有注册服务的信息列表。

下面我们看个简单的示例:
示例搭建Eureka服务器高可用集群

1. 首先在原来的治理服务器中增加以下几个profile配置文件

(1) application-sdpeer1.properties 配置文件内容如下:

#服务器运行端口
server.port=8260
#服务器运行的宿主机器的名称:sdpeer1
eureka.instance.hostname=sdpeer1
#Eureka相应配置
#用来控制当Spring Boot启动服务完成后是否将该服务注册到服务治理服务器上
eureka.client.register-with-eureka=false
#表示应用启动后是否需要从服务治理服务器中同步已注册的服务注册列表数据到本地
eureka.client.fetch-registry=false
#同时向sdpeer2,sdpeer3中的Eureka服务器注册
eureka.client.service-url.defaultZone=http://sdpeer2:8262/eureka,http://sdpeer3:8263/eureka

(2) application-sdpeer2.properties 配置文件内容如下:

#服务器运行端口
server.port=8262
#服务器运行的宿主机器的名称:sdpeer2
eureka.instance.hostname=sdpeer2
#Eureka相应配置
#用来控制当Spring Boot启动服务完成后是否将该服务注册到服务治理服务器上
#此时我们本身就是服务治理服务器所以配置false
eureka.client.register-with-eureka=false
#表示应用启动后是否需要从服务治理服务器中同步已注册的服务注册列表数据到本地
eureka.client.fetch-registry=false
#同时向sdpeer1,sdpeer3中的Eureka服务器注册
eureka.client.service-url.defaultZone=http://sdpeer1:8260/eureka,http://sdpeer3:8263/eureka

(3) application-sdpeer3.properties 配置文件内容如下:

#服务器运行端口
server.port=8263
#服务器运行的宿主机器的名称:sdpeer3
eureka.instance.hostname=sdpeer3
#Eureka相应配置
#用来控制当Spring Boot启动服务完成后是否将该服务注册到服务治理服务器上
#此时我们本身就是服务治理服务器所以配置false
eureka.client.register-with-eureka=false
#表示应用启动后是否需要从服务治理服务器中同步已注册的服务注册列表数据到本地
eureka.client.fetch-registry=false
#同时向sdpeer1,sdpeer2中的Eureka服务器注册
eureka.client.service-url.defaultZone=http://sdpeer1:8260/eureka,http://sdpeer2:8262/eureka

以上和书中不同的地方是,关于eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false这两个配置,笔者在按照书中配置为true的时候会报错。错误信息,大概意思就是,服务治理服务器不能够注册自己。。。修改为false启动成功

由于演示示例是在同一主机中运行多个Eureka服务器,Eureka在启动时会过滤同一主机名称。因此需要在主机的host文件中对主机名称进行映射:

#Eureka集群学习
127.0.0.1 sdpeer1
127.0.0.1 sdpeer2
127.0.0.1 sdpeer3

在上面我们配置的三个properties文件,我们可以看到每个Eureka实例都与其他两个实例分别进行两两互相注册。其原因就是Eureka 在进行注册服务信息同步时并不支持二次传播。

Eureka 服务器集群搭建好之后,我们只需要在 eureka服务提供客户端(示例中式用户微服务) 配置任意一个Eureka服务器的地址即可。
因为Eureka服务器之间会进行注册服务信息的同步。

2. 测试

  1. 先打包 服务治理服务器(jar)
  2. 打包用户微服务(jar)
  3. 通过下述命令分别启动这3个Eureka服务器,以及2个用户微服务:
    java -jar -Dspring.profiles.active=sdpeer1 server-discovery-0.0.1.jar
    java -jar -Dspring.profiles.active=sdpeer2 server-discovery-0.0.1.jar
    java -jar -Dspring.profiles.active=sdpeer3 server-discovery-0.0.1.jar
    
    #启动两个服务提供者
    java -jar user-service-0.0.1.jar
    java -jar user-service-0.0.1.jar --server.port=2111
    
  4. 启动成功之后,我们访问其中一个Eureka服务器,如http://localhost:8262/
    在这里插入图片描述
    可以看到当前Eureka服务器sdpeer2 与 sdpeer1,sdpeer3 中的Eureka互相复制。并且注册到sdpeer2中的用户微服务信息已经同步到当前服务器中。

4.5.5 多网卡及IP指定

默认情况下,Eureka客户端会使用Spring框架中InetUtils工具类的findFirstNonLoopbackAddress()方法来获取服务实例所在宿主服务器主机的IP地址。如果宿主服务器主机上有多个网卡,可能此方法所取到的IP地址不是你想要的。这是可以通过在配置文件中,手工指定一个IP来解决。

#使用IP地址来注册微服务
eureka.instance.prefer-ip-address=true
#手动指定IP地址
eureka.instance.ip-address=192.168.1.11

因此强烈推荐在项目中使用IP地址而不是主机名称来注册微服务

4.5.6 Eureka服务访问安全

我们在访问Eureka服务器的时候,一般就是直接通过 http://localhost:8260 就jinruEureka服务器管理页面了。这样可能会让你觉得不安全,特别是在生产环境中。

因此我们可以给Eureka服务器增加用户认证。

最简单的方式
记得先引入security

 <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>${boot.version}</version>
        </dependency>

在服务配置中增加如下配置:

spring.security.user.name=xukai
spring.security.user.password=123

这里需要说明一下,书中的security.basic.enable=true, security.user.name=user, security.user.password=这三个配置均已经过时(boot2环境下),不再使用。

至此
我们启动Eureka服务治理服务器之后,再次http://localhost:8260访问,就会出现登录界面要我们输入用户名和密码

对于Eureka客户端
需要我们通过以下配置来提供安全认证:

#service-url.defaultZone需要修改
eureka.client.service-url.defaultZone=http://xukai:123@localhost:8260/eureka

另外如果需要更发杂的认证方式,可以在ClientFilter中注入一个类型DiscoveryClientOptionalArgs的 @Bean,这样客户端与服务端的调用就会经过这个过滤器

注意:
由于Eureka的限制,不可能为每一个服务都配置单独的认证,因此只会使用第一个被发现的认证方式

补充,发现一个问题
问题是,如果我们使用了服务器的账号密码验证,在启动客户端的时候,会有一个错误:

com.netflix.discovery.shared.transport.TransportException: Cannot execute request on any known server

这是客户端在像服务器注册的时候发生的错误
具体解决办法是:服务端新建配置类,禁用CSRF,并启用httpBasic,代码如下:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception{
        http.csrf().disable().httpBasic();
    }
}

此时,无需再重新启动客户端,我们重新启动服务端,过一小会儿,就会发现注册成功了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值