avatar

springcloud 服务注册与发现

在微服务理论入门篇,我们完成了两个简单的SpringBoot项目,并且使用http进行直连,本篇引入Eureka,对项目进行改造。

Eureka是什么

什么是服务治理

在传统的RPC远程调用框架中,管理每个服务于服务之间依赖关系比较复杂,管理比较复杂,所以需要使用服务治理,管理服务与服务之间的依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册
SOA 治理简介

什么是服务注册

Eureka采用了CS的架构设计,Eurake Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eurlka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系用户中各个微服务是否运行正常。
在服务注册于发现中,有一个注册中心。当服务器启动的时候,会把当前自己的服务器信息比如:服务地址、通信地址等等以别名的方式注册到注册中心。另一方(消费者|服务提供者),以该别名的方式去注册中心上获取到实际服务通信地址,然后再实现本地RPC调用RPC远程调用框架。核心设计思想在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心存放服务地址相关信息(接口地址)。

Eureka组件

Eureka Server

提供服务注册服务,各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这阿姨那个Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。

Eureka Client

通过注册中心进行访问,是一个java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期是30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。

单机Eureka构建步骤

使用单机的Eureka来改造我们之前创建的两个SpringBoot项目。

IDEA生成Eureka Server端服务注册中心

1、创建module——cloud-erueka-server7001
2、修改maven工程的pom文件

<?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">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>com.zenshin.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>cloud-erueka-server7001</artifactId>

<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.zenshin.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>

3、编写配置文件

server:
port: 7001

eureka:
instance:
hostname: localhost # Eureka服务端实例明名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false # false表示自己就是注册中心,我的职责就维护服务实例,并不需要去检索服务
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

4、编写启动类

@EnableEurekaServer //表明这是一个Eureka Server
@SpringBootApplication
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}

5、编写完以后,启动项目,浏览器输入http://localhost:7001就可以看到Eureka的注册中心界面了。

将支付服务整合到Eureka中

payment项目就是一个服务提供者,是一个provider。将项目注册到Eureka中需要这么几步

1、修改pom文件,将Eureka的客户端引入

<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、修改yml配置

eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机版
defaultZone: http://localhost:7001/eureka
# 集群版
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001
#访问路径可以显示IP地址
prefer-ip-address: true
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
#lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
#lease-expiration-duration-in-seconds: 2

3、修改启动类,增加注解@EnableEurekaClient

@EnableEurekaClient
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}

4、启动项目和Eureka Server,会发现Eureka Server已经有了支付项目的注册信息

将订单项目加入到Eureka Server中

订单项目是一个消费者,主要是要调用支付服务,通过Eureka Server来调用支付服务。

1、修改pom文件,将Eureka的客户端引入
2、修改yml配置

eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: false
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机
defaultZone: http://localhost:7001/eureka

3、修改启动类,增加注解@EnableEurekaClient
4、同样的步骤,我们也可以把订单项目注册到Eureka中,但是我们的调用方式还没有改变,至于怎么使用Eureka的方式调用,我们需要使用服务发现,后面会进行介绍。

Eureka服务注册流程

Eureka集群环境部署

单机版只是用来玩玩,一般都是Eureka集群。微服务RPC远程服务调用最核心得就是高可用。

集群注册原理

互相注册,相互守望

Eureka集群环境构建

因为是集群,所以我们不可能只有一个应用在这里,但是呢为了模拟方便,新建两个Eureka Server服务分别用两个端口模拟一下集群环境。所以再新建一个Eureka Server工程,其中所有得配置与第一个相同,端口为7002。

新建好以后,在配置文件中我们需要改一下,因为我们要注册其他Eureka Server节点,相互注册。保证每个节点都有其他节点得信息。
在同一台机器上面,通过端口识别不同得Eureka Server我们在指定hostname得时候不能都指定为localhost,这样Eureka会出问题。我们需要先修改host文件,这里我使用Switch host来修改host。也可以直接在C:\Windows\System32\drivers\etc进行修改

##########SpringCloud#################
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com

这样我们模拟Eureka得集群,虽然是在一台电脑不同端口,但是我们通过修改host让Eureka认为是两台机器。
然后我们修改我们得配置文件

server:
port: 7001

eureka:
instance:
hostname: eurerka7001.com # Eureka服务端实例明名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false # false表示自己就是注册中心,我的职责就维护服务实例,并不需要去检索服务
service-url:
# 设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
defaultZone: http://eureka7002.com:7002/eureka/ #如果有三个或者多个,需要用逗号进行分割即可

这里需要修改的就是hostname,并且在defaultZone节点改为7002的Eureka Server,当然7002也是这么进行修改,让他们相互注册,相互守望。如果有三个或者多个,需要用逗号进行分割即可。

配置完成以后我们启动两个注册中心,然后看看是不是相互注册上了,如果注册上了就说明成功了。网址输入http://eurerka7001.com:7001/http://eurerka7002.com:7002/ 查看DS Replicas可以看到其他节点。

  • 注意:必须修改host,如果不修改host,使用loaclhost的话即使端口不一样,Eureka也会识别成一个,不会是一个集群。主要是hostname不一样即可,也可以使用一个为localhost,另一个为127.0.0.1。

将支付和订单微服务注册到Eureka集群中。

其他不需要修改,我们只需要修改yml配置文件即可。

...
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #只需要将这两个都放进来即可。
...

支付微服务开启多个服务,变成集群即provider集群

这里开启多个支付服务,配置集群主要是为了演示负载均衡效果。新建一个项目cloud-provider-payment8002 pom文件与8001相同。yml文件只需要修改一下端口号,然后其他的与8001相同。
为了更能体现出负载均衡,我们在controller中改一些东西,在其他服务调用的时候输出一下端口号,就能够清晰的看出,负载均衡的妙处。

@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort; //获取一下端口号

@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);

if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort:"+serverPort,result); //显示一下端口号
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}

@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);

if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort:"+serverPort,payment); // 体现负载均衡的魅力
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
}

虽然变成了集群,但是我们的订单服务还是直接连接的,并没有通过注册中心去调用,通过注册中心去调用的话需要做一些修改
1、修改一下Order项目的controller

...
private static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE/"; //将原来写死的url地址,变成注册到Eureka上面的服务名称。
...

2、在注入容器的RestTemplate上面加上负载均衡注解

@Configuration
public class MainConfig {
@Bean
@LoadBalanced //这样就能够使用客户端调用Eureka Server的服务了
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}

再次调用就是通过Eureka来找支付服务。通过http://localhost/consumer/payment/get/1 我们查看到的端口号就会一会8001一会8002的变化了。

RestTemplate源码浅探

我比较好奇,他是如何识别的url的呢,我简单的看了一下源码,我们从RestTemplate入手,在他的父类HttpAccessor发现一段代码

protected ClientHttpRequest createRequest(URI url, HttpMethod method) throws IOException {
ClientHttpRequest request = getRequestFactory().createRequest(url, method);
initialize(request);
if (logger.isDebugEnabled()) {
logger.debug("HTTP " + method.name() + " " + url);
}
return request;
}

创建一个ClientHttpRequest,而这个ClientHttpRequest是由一个工程创建的,接口ClientHttpRequestFactory一共有很多实现类

但是我查看引用的包中并只有RibbonClientHttpRequestFactory存在,在spring-cloud-netflix-ribbon-2.2.1.RELEASE.jar中发现了自动配置类

RibbonAutoConfiguration中有这么一段

@Bean
public RibbonClientHttpRequestFactory ribbonClientHttpRequestFactory() {
return new RibbonClientHttpRequestFactory(this.springClientFactory);
}

向容器中加入RibbonClientHttpRequestFactory,加入以后,就按照上面的ClientHttpRequest request = getRequestFactory().createRequest(url, method);创建请求客户端,这样就可以通过Ribbon的方式去请求Erueka的服务,然后使用负载均衡。
至于为什么必须加上@LoadBalanced,是因为在SpringCloud的Bean后置处理器中进行了判断

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof WebClient.Builder) {
if (context.findAnnotationOnBean(beanName, LoadBalanced.class) == null) {
return bean;
}
((WebClient.Builder) bean).filter(exchangeFilterFunction);
}
return bean;
}

在初始化bean之前,如果bean有LoadBalanced注解,那么就在bean的filter上加入exchangeFilterFunction
我只是简单的看了一下他是怎么调用到Ribbon的,因为代码太多以后慢慢的去看,这里仅仅是浅尝辄止。没必要深挖。

actuator微服务信息完善

主机名称:服务名称修改

在provider进行修改,需要修改yml

instance:
instance-id: payment8001 # 修改主机名

访问信息有ip提示

在provider进行修改,需要修改yml

instance:
#访问路径可以显示IP地址
prefer-ip-address: true

服务发现Discovery

对于注册进Eureka里面的微服务,可以通过服务发现来获得该服务的信息
这个功能主要是用了DiscoveryClient接口做的。使用方法如下。

...
@Resource
private DiscoveryClient discoveryClient; //注册一个服务发现客户端
...
@GetMapping("/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();//获取Eureka上面所有的服务
for (String element : services) {
log.info("*******element:"+element);
List<ServiceInstance> instances = discoveryClient.getInstances(element);//获取服务有哪些实例
for (ServiceInstance instance: instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
}
return this.discoveryClient;
}

还需要在程序主入口加入@EnableDiscoveryClient注解,如果有了@EnableEurekaClient注解可以不加。

  • @EnableDiscoveryClient@EnableEurekaClient的异同点
    • @EnableDiscoveryClient注解是基于spring-cloud-commons依赖,并且在classpath中实现
    • @EnableEurekaClient注解是基于spring-cloud-netflix依赖,只能为eureka作用;
    • @EnableDiscoveryClient@EnableEurekaClient共同点就是:都是能够让注册中心能够发现,扫描到该服务。
    • @EnableEurekaClient只适用于Eureka作为注册中心,@EnableDiscoveryClient可以是其他注册中心。
      其实EnableDiscoveryClient就是Springcloud的通用接口,只要实现这个就可以,适合任何服务注册中心,而EnableEurekaClient只是Eureka才能使用的,只有当使用Eureka作为服务注册中心的时候才有效。

使用DiscoveryClient可以对Eureka上面的服务以及服务的信息进行发现。

Eureka自我保护

保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server就会尝试保护其服务注册表中的信息,不再删除注册表中的数据,也就不会注销任何微服务。

如果在Eureka Server的首页看到以下提示,则说明Eureka进入保护模式:

  • EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

导致原因

某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务得信息进行保存。为了防止EurekaClient可以正常运行,但是与EurekaServer网络不同得情况下,EuerkaServer不会立刻将EurekaClient服务剔除。这是Eureka得自我保护机制。
这个属于CAP里面得AP分支CAP 定理的含义

  • 什么是CAP: CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。

什么是Eureka自我保护机制

默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer就会注销该实例(默认90秒)。但是当网络分区故障发生时(延迟、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变的非常危险——因为微服务本身其实是很健康的,此时本不应该注销掉这个微服务。Eureka通过”自我保护模式”来解决这个问题——当EurekaServer节点在端时间内丢失过多客户端时(可能发生网络分区故障),那么这个节点就会进入自我保护模式。

在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。
综上所述,自我保护模式是一种应对网络异常的安全保护措施,它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不会盲目的注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。

如何禁用自我保护

  • 修改Eureka Server:

    • 出厂默认,自我保护机制是开启的:
       #关闭自我保护机制,保证不可用的服务被及时剔除 
      server:
      enable-self-preservation: false
      eviction-interval-timer-in-ms: 2000
    • 关闭效果,如果2000ms以内没有心跳,直接剔除微服务。
  • 生产者客户端EurekaClient端:

    • 设置客户端向服务端发送心跳包的时间间隔
      instance:
      #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
      lease-renewal-interval-in-seconds: 1
      #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
      lease-expiration-duration-in-seconds: 2
    • 如果两秒值内没有收到客户端的心跳,Server就会剔除客户端。

Zookeeper代替Eureka

因为Eureka2.0不再支持更新,那么再使用Eureka就有点不好了,但是我们有替代品。本节主要讲解的是zookeeper代替Eureka。
Zookeeper是一个分布式协调工具,可以实现注册中心功能。

Zookeeper安装

服务提供者项目编写

  • 新建Cloud-provider-payment8004Maven项目

  • 修改pom文件

    <?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">
    <parent>
    <artifactId>cloud2020</artifactId>
    <groupId>com.zenshin.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>Cloud-provider-payment8004</artifactId>

    <dependencies>
    <!-- SpringBoot整合Web组件 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <groupId>com.zenshin.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
    </dependency>
    <!-- SpringBoot整合zookeeper客户端 -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
    <!--先排除自带的zookeeper3.5.3-->
    <exclusions>
    <exclusion>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    </exclusion>
    </exclusions>
    </dependency>
    <!--添加zookeeper3.4.9版本-->
    <dependency>
    <groupId>org.apache.zookeeper</groupId> <!--这里可以添加任意版本,先排除掉自带的,然后引入自己的-->
    <artifactId>zookeeper</artifactId>
    <version>3.4.9</version>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>
    </project>
  • 修改yml配置文件

    server:
    port: 8004

    # 服务别名----注册到zookeeper中心的服务名称
    spring:
    application:
    name: cloud-provider-payment
    cloud:
    zookeeper:
    connect-string: 192.168.1.8:2181
  • 编写主启动类

    @EnableDiscoveryClient //配置注册发现客户端,该注解用于向使用consul或者zookeeper作为注册中心时注册服务
    @SpringBootApplication
    public class PaymentMain8004 {
    public static void main(String[] args) {
    SpringApplication.run(PaymentMain8004.class,args);
    }
    }
  • 编写controller

    @RestController
    @Slf4j
    public class PaymentController
    {
    @Value("${server.port}")
    private String serverPort;

    @RequestMapping(value = "/payment/zk")
    public String paymentzk()
    {
    return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
    }
    }
  • 加入配置以及服务发现客户端注解以后,启动项目就可以将项目注册进Zookeeper。

  • 如何看到Zookeeper的信息呢?可以使用IDEA的Zookeeper插件,可以很直观的看到Zookeeper中注册了什么信息

可以看到Zookeeper中已经有了服务的信息,我们可以查出该节点的信息

  • 这样我们的服务就注册到了zookeeper中。

Zookeeper节点类型

Znode一般分四种:临时节点、带序号的临时节点、持久节点、带序号的持久节点。
在Zookeeper中,我们注册了服务,如果我们把服务停掉,过一段时间我们再去看我们的服务,ls /services/cloud-provider-payment就不会输出id号了,也就说明该节点在zookeeper中已经断掉了。
Zookeeper中对待节点的方式和Eureka是完全不同的,Eureka保证的是AP不同。Zookeeper保证的是强一致性,只要节点在一定时间内没有心跳了,那么这个节点我就认为是挂掉了,就会退出Zookeeper。
不会因为网络等因素等待该节点恢复。所以在Zookeeper中注册的节点都是临时节点。

将订单服务注册进zookeeper

流程与服务提供者类似,首先我们创建cloud-consumerzk-order80
编写POM文件,yml文件,这俩文件与服务提供者项目类似,只不过需要把yml的端口号改下,改成80,将服务名称改一下。
然后我们编写主启动类

@SpringBootApplication
@EnableDiscoveryClient //作用与EnableEurekaClient相同
public class OrderZKMain {
public static void main(String[] args) {
SpringApplication.run(OrderZKMain.class,args);
}
}

这里我们还是使用restTemplate来操作服务提供端

@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate()
{
return new RestTemplate();
}
}

编写controller,来操作服务提供端

@RestController
@Slf4j
public class OrderZKController {
public static final String INVOKE_URL = "http://cloud-provider-payment";

@Resource
private RestTemplate restTemplate;

@GetMapping("/consumer/payment/zk")
public String paymentInfo()
{
String result =restTemplate.getForObject(INVOKE_URL+"/payment/zk",String.class);

return result;
}
}

调用端的代码与Eureka一样都是通过restTemplate进行访问。我们运行一下服务提供端与服务调用端看一下效果。
效果就是在zookeeper中有两个服务了,并且通过url能正常进行访问。

这是单机版的zookeeper的注册中心,如果是集群,只需要将所有的zookeeper地址用逗号隔开即可。

使用Consul代替Eureka

Consul简介

Consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp公司使用GO语言进行开发的。官网简介
Consul提供了微服务系统中的服务治理、配置中心、控制总线等功能。这个功能中的每一个都可以根据需要单独使用,也可以一起使用来构建全方位的服务网格,总值Consul提供了一种完整的服务网格解决方案。

Spring Cloud Consul具有如下特性:

  • 服务注册与发现:Consul客户端可以注册一个服务,如api或mysql,其他客户端可以使用Consul来发现给定服务的提供者。使用DNS或HTTP,应用程序可以很容易地找到它们所依赖的服务。
  • 健康检查:Consul客户端可以提供任意数量的健康检查,或者与给定的服务相关联(“webserver是否返回200 OK”),或者与本地节点相关联(“内存利用率是否低于90%”)。操作人员可以使用此信息来监视集群的健康状况,服务发现组件可以使用此信息将流量从不健康的主机路由出去。
  • 键值对存储:应用程序可以将consul的分层密钥/值存储用于任何目的,包括动态配置、特性标记、协调、领导人选举等。简单的HTTP API使其易于使用。
  • 多数据中心:Consul支持多个数据中心
  • 可视化的web界面

下载与安装Consul

学习网址
consul集群安装

#下载
wget https://releases.hashicorp.com/consul/1.7.3/consul_1.7.3_linux_amd64.zip
#创建文件夹并解压到该文件夹下
mkdir /softerware
mv consul_1.7.3_linux_amd64.zip /softerware
yum install -y unzip zip #安装zip解压工具
unzip consul_1.7.3_linux_amd64.zip # 解压
#安装目录为:/softerware/consul
sudo mv consul /usr/local/bin/ #将解压出来的consul,移到全局变量中去。
#检查安装是否成功
consul #输入后显示能找到这个命令即可

# 我们来运行一下consul
firewall-cmd --permanent --zone=public --add-port=8500/tcp #开启8500端口
firewall-cmd --permanent --zone=public --remove-port=8500/tcp #取消开放端口
firewall-cmd --query-port=8500/tcp #查看8500是否开启
firewall-cmd --list-port #查看哪些端口开放
firewall-cmd --reload #重启防火墙

#如果觉得麻烦那么直接关闭防火墙
systemctl stop firewalld.service #比较危险不建议

consul agent -dev -client=192.168.0.103 #这里如果是本机,没有关系,如果是虚拟机记得写上客户端ip

这样我们得consul就安装好了,但是这样只能是单机模式,-dev节点的启动不能用于生产环境,因为该模式下不会持久化任何状态,该启动模式仅仅是为了快速便捷的启动单节点consul。
输入http:// + ip+:8500 就可以访问consul的界面了。

Consul的一部分参数

参数名称 用途
-server 此标志用于控制代理是运行于服务器/客户端模式,每个 Consul 集群至少有一个服务器,正常情况下不超过5个,使用此标记的服务器参与 Raft一致性算法、选举等事务性工作
-client 表示 Consul 绑定客户端接口的IP地址,默认值为:127.0.0.1,当你有多块网卡的时候,最好指定IP地址,不要使用默认值
-bootstrap-expect 预期的服务器集群的数量,整数,如 -bootstrap-expect=3,表示集群服务器数量为3台,设置该参数后,Consul将等待指定数量的服务器全部加入集群可用后,才开始引导集群正式开始工作,此参数必须与 -server 一起使用
-data-dir 存储数据的目录,该目录在 Consul 程序重启后数据不会丢失,指定此目录时,应确保运行 Consul 程序的用户对该目录具有读写权限
-node 当前服务器在集群中的名称,该值在整个 Consul 集群中必须唯一,默认值为当前主机名称
-bind Consul 在当前服务器侦听的地址,如果您有多块网卡,请务必指定一个IP地址(IPv4/IPv6),默认值为:0.0.0.0,也可用使用[::]
-datacenter 代理服务器运行的数据中心的名称,同一个数据中心中的 Consul 节点必须位于同一个 LAN 网络上
-ui 启用当前服务器内部的 WebUI 服务器和控制台界面
-join 该参数指定当前服务器启动时,加入另外一个代理服务器的地址,在默认情况下,如果不指定该参数,则当前代理服务器不会加入任何节点。可以多次指定该参数,以加入多个代理服务器,
-retry-join 用途和 -join 一致,当第一次加入失败后进行重试,每次加入失败后等待时间为 30秒
-syslog 指定此标志意味着将记录 syslog,该参数在 Windows 平台不支持

服务提供者注册进consul

还是同样的步骤,我们新建一个工程cloud-providerconsul-payment8006

  • 修改pom文件

    <?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">
    <parent>
    <artifactId>cloud2020</artifactId>
    <groupId>com.zenshin.springcloud</groupId>
    <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-providerconsul-payment8006</artifactId>

    <dependencies>
    <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
    <dependency>
    <groupId>com.zenshin.springcloud</groupId>
    <artifactId>cloud-api-commons</artifactId>
    <version>${project.version}</version>
    </dependency>
    <!--SpringCloud consul-server -->
    <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-consul-discovery</artifactId>
    </dependency>
    <!-- SpringBoot整合Web组件 -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--日常通用jar包配置-->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>RELEASE</version>
    <scope>test</scope>
    </dependency>
    <dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>RELEASE</version>
    <scope>test</scope>
    </dependency>
    </dependencies>
    </project>
  • 修改yml文件

    ###consul服务端口号
    server:
    port: 8006

    spring:
    application:
    name: consul-provider-payment
    ####consul注册中心地址
    cloud:
    consul:
    host: 192.168.0.103
    port: 8500
    discovery:
    hostname: 192.168.0.101 #这里填写微服务的ip地址
    service-name: ${spring.application.name}
  • 编写主启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    public class PaymentMain8006 {
    public static void main(String[] args) {
    SpringApplication.run(PaymentMain8006.class,args);
    }
    }
  • 编写controller

    @RestController
    @Slf4j
    public class PaymentController {

    @Value("${server.port}")
    private String serverPort;

    @RequestMapping(value = "/payment/consul")
    public String paymentconsul()
    {
    return "springcloud with consul: "+serverPort+"\t"+ UUID.randomUUID().toString();
    }
    }
  • 启动服务,观察consul的界面,会发现多出来一个服务,并且是健康的。

服务消费者注册进consul

编写新的服务,服务的消费者cloud-consumerconsul-order80,pom文件与服务提供者相同,这里可以不管,yml与服务提供者一样,只是修改一下端口和应用名称即可

  • 编写主配置类

    @SpringBootApplication
    @EnableDiscoveryClient
    public class OrderConsulMain80 {
    public static void main(String[] args) {
    SpringApplication.run(OrderConsulMain80.class,args);
    }
    }
  • 编写配置类,将Rest模板注入

    @Configuration
    public class ApplicationContextConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate()
    {
    return new RestTemplate();
    }
    }
  • 编写controller

    @RestController
    @Slf4j
    public class OrderConsulController {

    public static final String INVOKE_URL = "http://consul-provider-payment";

    @Resource
    private RestTemplate restTemplate;

    @GetMapping("/consumer/payment/consul")
    public String paymentInfo()
    {
    String result =restTemplate.getForObject(INVOKE_URL+"/payment/consul",String.class);

    return result;
    }

    }
  • 我们启动以后,就可以看到consul上面会出现Order微服务,并且通过url可以调用payment服务。

Zookeeper、Eureka、Consul异同点

组件名 语言 CAP 服务健康检查 对外暴露接口 SpirngCloud集成
Eureka java AP 可配置支持 http 已集成
consul go CP 支持 http/dns 已集成
zookeeper java CP 支持 客户端 已集成

CAP理论

C:Consistency,强一致性
A:Availability 可用性
P:Partition tolerance 分区容错性

最多只能同时比较好的满足两个。
CAP理论关注粒度是数据,而不是整体系统设计的策略
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性、可用性和分区容错性三个需求。
因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则、满足AP原则三大类:
CA:单点集群,满足一致性、可用性的系统,通常在可扩展性上不太强大
CP:满足一致性、分区容错性的系统,通常性能不是特别高, Zookeeper/Consul
AP:满足可用性,分区容错性的系统,通常可能对一致性要求低一些。 Eureka

文章作者: zenshin
文章链接: https://zlh.giserhub.com/2020/05/18/springcloud/Discovery/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 zenshin's blog
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论