白话SpringCloud | 第三章:服务注册与发现(Eureka)-下

前言

上一章节,讲解了在单机模式下的服务注册与发现的相关知识点及简单示例。而在实际生产或者在这种微服务架构的分布式环境中,需要考虑发生故障时,各组件的高可用。而其实高可用,我的简单粗俗理解就是,通过系统的冗余进行高可用,或者是进行集群部署,保证一台服务不可用时,会进行自动转移至可用的服务中。今天的章节,就来说说关于Eureka的高可用吧。

一点知识

讲解前,我们先来聊聊在使用Dubbo时耳闻能详的ZookeeperEureka之间的区别吧。

CAP原则

根据百度百科的定义,CAP定理又称CAP原则,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),最多只能同时三个特性中的两个,三者不可兼得。

在分布式领域,大家应该对CAP理论不陌生了吧(了解但也是没有深入了解过⊙﹏⊙‖∣)。

以下摘至百度百科:

● 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)

● 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)

● 分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。

至于为何不能同时满足,以上在分区容错性也有简单的说明了,一般来说,分区容错无法避免,因此可以认为CAPP总是成立,而对于一致性可用性为何不能同时满足,简单来说就是:存在可能通信失败情况,即:出现分区容错,至于更详细具体的,大家可以看下大佬阮一峰的这篇文章:CAP 定理的含义。这里就不阐述了,不是很了解~

Eureka与Zookeeper区别

对于Eureka而言,其是满足AP的,而Zookeeper而言,是满足CP的。

Eureka是满足AP的:

  • 优先保证可用性
  • 各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务
  • 在向某个Eureka注册时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)

Zookeeper是满足AP的:

  • 任何时刻对ZooKeeper的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性
  • 不能保证每次服务请求的可用性
  • 当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举
  • 选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪

我的简单理解:由于Zookeeperloader选举策略,使其可以保证数据的一致性。而Eureka,本事没有选举策略,各服务是独立运行的,至少在某一时刻,各服务之间的数据是不一致的,而在可用性方面,Zookeeper也是由于选举策略原因,在选举期间是,整个zookeeper是不可用的,会造成短暂(看选举时长)的服务不可用。而对于Eureka而言,服务是独立运行的,所以不会因为某台服务不可用导致了其他服务不可用情况。感觉上面有点绕,简单来说,就是Zookeeper通过选举策略保证数据的一致性,但缺失了可用性,Eureka由于服务独立运行,通过心跳等通信策略进行数据同步,存在数据不一致性,但保证了服务的可用性

所以,综上所述,作为服务注册中心而言,可用性原则是比数据一致性更重要的,同时上一章节也有说过,由于Eureka自我保护模式,可保护服务注册表中的信息不被剔除,所以Eureka可以很好的应对因网络故障导致节点失去联系的情况

Eurkea高可用介绍及示例

Eureka的高可用

官网中,关于Eureka的高可用部分是这么描述的:

High Availability, Zones and Regions

Peer Awareness

所以可以获悉,Eureka Server可以运行多个实例来构建集群,解决单点问题,Eureka Server采用的是Peer to Peer对等通信。这是一种去中心化的架构,无master/slave区分,每一个Peer都是对等的。在这种架构中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的serviceUrl指向其他节点。每个节点都可被视为其他节点的副本。

如果某台Eureka Server宕机,Eureka Client的请求会自动切换到新的Eureka Server节点,当宕机的服务器重新恢复后,Eureka会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会进行replicateToPeer(节点间复制)操作,将请求复制到其他Eureka Server当前所知的所有节点中。

所以,简单来说,Eureka Server的高可用,实际上就是将自己也作为服务向其他服务注册中心进行注册,这样就可以形成一组相互注册的服务注册中心,以实现服务清单的互相同步,达到高可用的效果。

另外,从官网文档中有提到ZonesRegionsRegionZone(或者Availability Zone)均是AWS的概念。在非AWS环境下,我们可以先简单地将region理解为Eureka集群,zone理解成机房。下图就可以理解为一个Eureka集群被部署在了zone1机房和zone2机房中。

Eureka集群

对这些概念的其他相关知识,也深入了解,大家感兴趣,可自行搜索下吧。

示例前,先看看集群模式下,Eureka的架构图。

集群模式

  1. Service Provider会向Eureka Server做Register(服务注册)、Renew(服务续约)、Cancel(服务下线)等操作。
  2. Eureka Server之间会做注册服务的同步,从而保证状态一致
  3. Service Consumer会向Eureka Server获取注册服务列表,并消费服务

具体的原理分析,可以看看这篇比较早的文章:https://nobodyiam.com/2016/06/25/dive-into-eureka/,虽然是比较早的文章,但写的比较详细,可以看看。

接下来,以官网文档demo,示例下。

Eureka服务端高可用

通过点对点配置,注册中心通过相互注册来实现高可用配置。以下构建一个双节点的集群模式。

修改spring-cloud-eureka-server项目。

0.创建一个application-ha.properties配置文件,同时修改application.properties文件。

application-ha.properties

spring.application.name=eureka-service-ha
# 修改端口
server.port=1001

# 实例的主机名称
eureka.instance.hostname=myPeer2

## 不要向注册中心注册自己
#eureka.client.register-with-eureka=false
## 表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它也不需要去检索其他服务
#eureka.client.fetch-registry=false

# 指定服务注册中心地址
eureka.client.service-url.defaultZone=http://myPeer1:1000/eureka

application.properties

spring.application.name=eureka-service-ha
# 修改端口
server.port=1000

# 实例的主机名称
eureka.instance.hostname=myPeer1

## 不要向注册中心注册自己
#eureka.client.register-with-eureka=false
## 表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它也不需要去检索其他服务
#eureka.client.fetch-registry=false

# 指定服务注册中心地址
eureka.client.service-url.defaultZone=http://myPeer2:1000/eureka

#spring.profiles.active=ha

1.由于是在同一台进行模拟,首先修改hosts文件,当浏览器请求一个地址时,首先会从此文件选择对应对应的IP地址,找不到时才请求CDS域名解析服务器进行解析

‪C:\Windows\System32\drivers\etc\hosts文件:

127.0.0.1 myPeer1
127.0.0.1 myPeer2

友情提示:若提示无修改权限,可根据以下网址进行相应修改:编辑hosts文件无法保存怎么办

2.启动应用,我们这里直接启动两个,可通过修改spring.profiles.active值来启动不同环境的应用,或者使用-jar xx.jar --spring.profiles.active=xx来启动。

访问:http://myPeer1:1000

myPeer1

访问:http://myPeer2:1001

myPeer2

题外话:第一个启动的应用,后台会报错Connection refused: connect,等第二个应用启动后,就正常了~

Eureka客户端注册至集群上

客户端只需要通过修改配置文件的eureka.client.service-url.defaultZone值即可。

修改spring-cloud-eureka-client项目

0.修改配置文件application.properties

spring.application.name=eureka-client
server.port=2000

# 注册中心地址
eureka.client.service-url.defaultZone=http://myPeer1:1000/eureka,http://myPeer2:1001/eureka
# 启用ip配置 这样在注册中心列表中看见的是以ip+端口呈现的
eureka.instance.prefer-ip-address=true
# 实例名称  最后呈现地址:ip:2000
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}

当然,也可只注册到某个节点上,其他的节点也会有此服务列表的,一般建议以集群方式进行配置,即多注册中心配置。避免单点故障,Eureka在搜索注册中心时,根据defaultZone列表,找到一个可用的,之后就不会继续去下一个注册中心地址拉取服务列表了,此时若其中一个注册中心挂了,这个时候客户端会继续去第二个注册中心拉取服务列表的。

启动后,可以看见eureka-client注册上去了。

现在我们停止一个应用,可以看出不可用的服务列表中已经有相关信息了

高可用测试

为了验证高可用性是否成功,创建一个spring-cloud-eureka-server-ha-test项目,作为服务消费者使用RestTemplate+ribbon进行调用spring-cloud-eureka-client的服务。

创建spring-cloud-eureka-server-ha-test项目
0.引入pom依赖

    <!-- 客户端依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>    
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>    
    <!-- 引入web,提供一个简单的api接口 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

1.配置文件,配置注册中心地址

spring.application.name=eureka-ha-test
server.port=8888

#指定注册中心地址
eureka.client.serviceUrl.defaultZone=http://myPeer1:1000/eureka/,http://myPeer2:1001/eureka/
# 启用ip配置 这样在注册中心列表中看见的是以ip+端口呈现的
eureka.instance.prefer-ip-address=true
# 实例名称  最后呈现地址:ip:2000
eureka.instance.instance-id=${spring.cloud.client.ip-address}:${server.port}

2.启动类,配置RestTemplateBean类,同时加入@LoadBalanced注解实现服务调用。

@SpringCloudApplication
@EnableDiscoveryClient
@Slf4j
public class EurekaServiceHaApplication {
    
    public static void main(String[] args) throws Exception {
        SpringApplication.run(EurekaServiceHaApplication.class, args);
        log.info("spring-cloud-eureka-server-ha-test启动!");
    }
    
    //加入负载均衡能力
    //同时可根据applicationName 来访问服务
    //如http://EUREKA-CLIENT/add
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

3.编写一个控制类,简单调用EUREKA-CLIENT服务方法。

/**
 * 调用简单示例
 * @author oKong
 *
 */
@RestController
public class DemoController {

    @Autowired
    private RestTemplate testTemplate;
    
    @GetMapping("/")
    public String index() {
        //访问服务提供者
        return testTemplate.getForObject("http://EUREKA-CLIENT/", String.class);
    }
}

4.启动应用类,访问注册中心和http://127.0.0.1:8888 。

注册中心

远程调用

可看见输出spring-cloud-eureka-client!表明调用成功。在控制台也可以看见,从注册中心拉取了EUREKA-CLIENT的服务地址:

这个时候,可以试着停止其中一个甚至全部的Eureka Server,再继续访问,可以看见服务还是可以调用的,但要知道此时调用方是可能是根据本地的缓存列表中直接获取地址的,而不是从注册服务中心,等待下次心跳机制时间到时,才会去进行拉取最新的服务列表的。


关于RestTemplateRibbon相关知识点,会在下一章节进行阐述的,这里就不敞开了。


一点疑问

第一次看官网文档进行demo时,还在想通过设置端口号不就可以了吗,为何还需要指定eureka.instance.hostname呢,多麻烦?尝试下使用ip或者不配置试试看。

  • 修改eureka.instance.hostnamelocalhost或者127.0.0.1时,

localhost

可以看见,在DS Replicas中是空的,说明没有可复制的服务,registered-replicasavailable-replicas都是空的。

客户端配置eureka.client.service-url.defaultZone

eureka.client.service-url.defaultZone=http://127.0.0.1:1001/eureka,http://127.0.0.1:1000/eureka

会发现,在1001服务上有注册上去,1000服务没有服务信息。(客户端注册是按顺序进行优先注册和获取服务列表的)

1001服务:

1001服务

1000服务:

1000服务

这说明集群模式是没有生效的,注册中心之间没有相互复制服务列表。

  • 修改配置文件,一个修改成127.0.0.1,另一个修改成localhost或者实际内网ip地址

说明下:
1001对应hostname为:127.0.0.1
1000对应hostname为:192.168.81.1

1001服务:

1001服务

1000服务:

1000服务服务

会发现,集群模式成功了。
接着我们启动客户端。

1000服务:
1000服务

1001服务:
1001服务

可以看见,和上面设置myPeer1myPeer2效果是一样的,都有被复制了。

集群配置的一点浅谈

从上面可以大致获悉,Eureka互相注册要求各个Eureka server实例的eureka.instance.hostname不同,如果相同,则会被Eureka标记为unavailable-replicas(像本地设置为127.0.0.1,干脆就不显示了,具体不明。。⊙﹏⊙‖∣),之前的同步就失效了。而对于客户端的defaultZone配置而言,是优先从第一个开始注册和拉取服务的,成功联通后就不会再继续找下一个注册服务了。


综上所述:

  • 若是一台服务器,部署多个Eureka server服务时,设置每个服务
    hostname不一致,同时要设置eureka.instance.prefer-ip-addressfalse,不使用IP地址进行注册。
  • 若是多台服务器部署时,设置hostname为本机的ip地址,可使用spring.cloud.client.ip-address变量进行赋值。同时设置eureka.instance.prefer-ip-addresstrue。这样的话,客户端使用ip进行连接就方便了,不然还要去配置host有点坑了。
  • 对于客户端的defaultZone建议还是配置多个注册中心地址。就算本身集群模式无效,好歹其中一个不可用了,还能连其他的注册中心呀。

在最后收尾时,搜索到一篇文章,也是大致的意思,大家可以点击看一看:构建高可用Eureka注册中心

其源码地址:https://github.com/wangfei0904306/eureka-HA

以上可能理解有偏差,还希望知道的同学能不吝赐教下,谢谢了!


Eureka注册中心访问认证

默认情况下,访问注册中心页面是匿名访问的,不需要一些认证。在生产中,为了安全性,可加入身份认证功能。

添加认证很简单,官网文档也有说明:

Securing The Eureka Server

0.加入pom依赖:

    <!-- 开启认证 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

1.修改配置文件,设置用户名和密码及集群状态下,注册至其他服务中心时,加入用户名和密码:

# 设置帐号密码
# 若不设置 默认帐号是user,密码随机,启动时会打印在控制台上
spring.security.user.name=oKong
spring.security.user.password=123456

# 加入用户名和密码
eureka.client.service-url.defaultZone=http://oKong:123456@127.0.0.1:1001/eureka

友情提示:不设置name和password时,默认用户是user,密码是随机的,启动时会打印在控制台上的:

默认密码

2.修改启动类,根据官网的提示,关闭/eureka/**的CSRF的令牌。

/**
 * Eureka服务端
 * @author oKong
 *
 */
@SpringBootApplication
@EnableEurekaServer
@Slf4j
public class EureakServiceApplication {
    
    public static void main(String[] args) throws Exception {
        SpringApplication.run(EureakServiceApplication.class, args);
        log.info("spring-cloud-eureka-service启动!");
    }
    
    /**
     * 忽略此路径下的CSRF令牌
     * @author oKong
     *
     */
    @EnableWebSecurity
    static class WebSecurityConfig extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().ignoringAntMatchers("/eureka/**");
            super.configure(http);
        }
    }    
}

3.启动应用。再次访问:http:127.0.0.1:1000 时,就需要输入用户名和密码了。

4.客户端也是类似的,修改defaultZone加入用户名和密码即可:

eureka.client.service-url.defaultZone=http://oKong:123456@127.0.0.1:1001/eureka,http://oKong:123456@127.0.0.1:1000/eureka

参考资料

  1. https://cloud.spring.io/spring-cloud-static/Finchley.SR1/single/spring-cloud.html#spring-cloud-eureka-server

  2. https://nobodyiam.com/2016/06/25/dive-into-eureka/

  3. http://tech.lede.com/2017/03/15/rd/server/SpringCloud1/

  4. https://blog.csdn.net/wangfei0904306/article/details/79056083

总结

本章节主要是Eureka服务的高可用进行了简单的介绍了下。对于集群模式下的一些最佳实践还是有待商讨的,还希望大家能说说自个的方案!至于一些其他的特性,如元数据配置等,这里就不阐述了,用的不多。同时,本章节利用RestTemplate+ribbon进行了简单的服务调用,没有敞开说,下一章节就是开始讲解服务消费者相关知识点,应该也会分成两章节来描述,因为涉及到了RibbonFeign相关知识点。我一直觉得使用都是很简单的,只有理解里面的原理之类的,这样才是一通百通吧,加深印象!使用起来也能比较顺利。

最后

目前互联网上大佬都有分享SpringCloud系列教程,内容可能会类似,望多多包涵了。原创不易,码字不易,还希望大家多多支持。若文中有错误之处,还望提出,谢谢。

老生常谈

  • 个人QQ:499452441
  • 微信公众号:lqdevOps

公众号

个人博客:http://blog.lqdev.cn

源码示例:https://github.com/xie19900123/spring-cloud-learning

原文地址:http://blog.lqdev.cn/2018/09/09/SpringCloud/chapter-three/

posted @ 2018-09-09 21:53 oKong_趔趄的猿 阅读(...) 评论(...) 编辑 收藏