SpringCloud系列之三

SpringCloud服务治理

1. 什么是服务治理

服务治理是开启微服务的第一关,需要先掌握这个之后才能对微服务有一个正确的认识

  • 高可用性:将服务打造成“一只打不死的小强”,所有微服务的节点,直到最后一个节点存活,服务治理框架都需要保证服务的可用性
  • 分布式调用:微服务的节点通常是散布在不同的网络环境中的,这就要求服务治理框架具备在复杂网络环境下准确获取服务节点的网络IP和端口服务的能力,作为服务的消费者,就可以借助服务治理框架精确的定位服务,并向服务发起请求
  • 生命周期管理:微服务的节点将自己整个的服务生命周期交给了服务治理框架,从服务的上线,持续运行到无法提供服务下线,整个过程中服务治理的框架都要贯穿其中
  • 健康度检查:微服务的节点都是7*24提供服务,如果一个节点因为任何原因无法提供服务了,服务治理框架就需要精确的识别出哪个节点有问题,将其从自己的服务列表中剔除

服务治理的解决方案

  • 服务注册 - 服务提供方要自报家门
  • 服务发现 - 服务消费者拉取注册的服务
  • 心跳检测,服务续约和服务剔除 - 一套由服务提供方和注册中心配合完成的去伪存真的过程
  • 服务下线 - 服务提供发起主动下线

2. 服务治理组件选型比较

Eureka:Netflix公司

Consul:Spring开源组织直接贡献

Nacos:阿里的服务治理组件

#EurekaConsulNacos
一致性弱一致性(AP)弱一致性(AP)AP/CP
性能慢(RAFT协议Leader选举)
网络协议HTTPHTTP&DNSHTTP,DNS,UDP
应用广度主流小众一些越来越被大众应用了

3. 搭建eureka-server模块

这个模块充当的是服务注册中的人物和职责

springcloud-learn父级项目的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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.michael</groupId>
    <artifactId>springCloud-learn</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR3</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.2.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

创建eureka-server模块

并在POM里加入eureka的依赖

<?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>springcloud-learn</artifactId>
        <groupId>com.michael</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <artifactId>eureka-server</artifactId>
    <name>eureka-server</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
</project>

去子项目eureka-server中设置启动类

package com.michael.springcloud;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
//注册中心服务
@EnableEurekaServer
public class EurekaServerApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(EurekaServerApplication.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
    }
}

子项目eureka-server的properties

spring.application.name=eureka-server
server.port=10080

# 绑定主机
eureka.instance.hostname=localhost
# 是否发起服务注册,服务端要关闭
eureka.client.register-with-eureka=false
# 是否拉取服务注册表,服务端是生成端不用拉取
eureka.client.fetch-registry=false

启动服务并访问:http://localhost:10080

4. 搭建eureka-client模块

eureka-client就是服务的提供方

引入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>springcloud-learn</artifactId>
        <groupId>com.michael</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>
    <artifactId>eureka-client</artifactId>
    <name>eureka-client</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

创建启动类

package com.michael.springcloud;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
//服务发现的注解,需要到注册中心拉取服务列表
@EnableDiscoveryClient
public class EurekaClientApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(EurekaClientApplication.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
    }
}

创建controller包建立controller实现类

package com.michael.springcloud.controller;

import com.michael.springcloud.pojo.PortInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class EurekaController {

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

    @GetMapping("/sayhello")
    public String sayHello(){
        return "my port is "+port;
    }

    @PostMapping("/sayhello")
    public PortInfo sayPortInfo(@RequestBody PortInfo portInfo){
        log.info("you are "+portInfo.getName());
        portInfo.setPort(port);
        return portInfo;
    }
}

创建POJO实体类

package com.michael.springcloud.pojo;

import lombok.Data;

@Data
public class PortInfo {

    private String name;
    private String port;
}

创建application配置文件

spring.application.name=eureka-client
server.port=10081
# 服务提供者连接的注册中心
eureka.client.serviceUrl.defaultZone=http://localhost:10080/eureka/

先启动eureka-server,再启动eureka-client

5. 创建eureka-consumer模块

引入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>springcloud-learn</artifactId>
        <groupId>com.michael</groupId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>java</packaging>
    <artifactId>eureka-consumer</artifactId>
    <name>eureka-consumer</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <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>
    </dependencies>
</project>

spring-boot-starter-actuator:用来做健康检查和审计统计等工作的

编写启动类application

package com.michael.springcloud;

import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class EurekaConsumerApplication {

  	@Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
  
    public static void main(String[] args) {
        new SpringApplicationBuilder(EurekaConsumerApplication.class)
                .web(WebApplicationType.SERVLET)
                .run(args);
    }
}

编写服务调用的controller

package com.michael.springcloud.controller;

import com.michael.springcloud.pojo.PortInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
@Slf4j
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient client;
    
    @GetMapping("/hello")
    public String hello(){
        ServiceInstance instance = client.choose("eureka-client");
        if(instance==null){
            return "No Available Instance";
        }
        //获取服务链接
        String target = String.format("http://%s:%s/sayhello",instance.getHost(),instance.getPort());
        log.info("url is {}",target);
        return restTemplate.getForObject(target,String.class);
    }
    
    @PostMapping("/hello")
    public PortInfo sayhello(){
        ServiceInstance instance = client.choose("eureka-client");
        if(instance==null){
            return null;
        }
        String target = String.format("http://%s:%s/sayhello",instance.getHost(),instance.getPort());
        log.info("url is {}",target);
        PortInfo portInfo = new PortInfo();
        portInfo.setName("michael");
        portInfo.setPort("8888");
        return restTemplate.postForObject(target,portInfo, PortInfo.class);
    }
}

需要将POJO导入

package com.michael.springcloud.pojo;

import lombok.Data;

@Data
public class PortInfo {

    private String name;
    private String port;
}

设置项目的properties

spring.application.name=eureka-consumer
server.port=10082
eureka.client.serviceUrl.defaultZone=http://localhost:10080/eureka/

启动测试:按照eureka-server、eureka-client、eureka-consumer的顺序进行

6. eureka心跳检测与服务剔除

6.1. 心跳检测机制

服务心跳:客户端起、同步状态、服务剔除、服务续约

  • 客户端发起:eureka对所有服务节点的服务状态的感知是通过服务节点根据配置的时间主动向eureka发起的心跳服务
  • 同步状态:心跳不光要告知注册中心服务是否还活着,还要告诉活得好不好,是正常状态还是异常状态
  • 服务剔除:eureka对于一段时间无响应的服务,就从服务注册列表中主动剔除了,以防止服务调用方请求失败
  • 服务续约:服务续约底层也是靠心跳实现的,包含了一套脏数据处理流程

心跳检测的信息

  • 访问地址 也就是注册中心的访问地址,如http://localhost:10080/eureka/
  • 访问路径 为了防止注册中心将服务地址给到其他服务了,就需要我们主动告知注册中心,我们是谁
  • 服务状态 心跳反应的服务状态有这几种:UP、DOWN、STARTING、OUT_OF_SERVICE、UNKNOWN
  • 最后一次同步注册的时间 是当前服务节点最后一次与服务中心失去同步时的时间
# 两个配置在eureka-client端的设置
eureka.instance.lease-renewal-interval-in-seconds=10
eureka.instance.lease-expiration-duration-in-seconds=20

# 这两个指标都配置在client节点上
# 第一个指标决定了每隔多久向服务器发送一次心跳包
# 第二个参数告诉服务器,如果我们在x秒内都没有心跳,那就代表我挂了
# 通常第一个时间一定是小于第二个时间的,否则心跳还没有发送就验证服务挂了,就被注册中心剔除了,两次心跳间隔也就是第二个配置的时间,还要考虑网络延迟

6.2. 服务剔除机制

对于已经无法使用的节点,我们eureak就要尽快剔除掉

剔除的过程如下

在这里插入图片描述

1、启动定时任务:注册中心在启动的时候也会同步开启一个后台任务,默认每隔60秒触发服务剔除任务,当然我们也可以通过在服务端做相关配置修改触发间隔,建议这个配置不要间隔太短

eureka.server.eviction-interval-timer-in-ms=30000

2、调用evict方法

服务剔除比较直接的,通过AbstractInstanceRegistry的eviction方法直接运行。

  • 服务自保开关:一旦开启,注册中心就会中断服务剔除的操作

3、遍历过期服务

注册中心会遍历所有服务节点,找到所有过期服务,如何判断是否过期

  • 已被标记为过期
  • 最后一次心跳时间+服务端心跳间隔时间<当前时间

4、计算可剔除的服务总数

所有不可用服务是否能被全部剔除?当然是不可以,服务中心也要顾及自己的稳定性,默认设置了一个系数(0.85)可剔除的服务数量不能大于已注册总数乘以这个系数,比如有100个服务,其中90个已经over了,这个时候注册中心实际只能剔除 100*0.85=85,虽然不可用服务有90个但只能剔除85

eureka.server.renewal-percent-threshold=0.85

5、乱序剔除服务 乱序剔除,随机到哪个服务就把它踢下线

7. eureka服务续约机制

7.1. 续约和心跳的关系

在这里插入图片描述

服务续约分为两步

  • 第一步:将服务节点的状态同步到注册中心,意思是通知注册中心我还可以继续工作,这一步需要借助客户端的心跳功能来主动发送
  • 第二步:当心跳包到达注册中心,注册中心会判断你是否可以合理续约

7.2. 发送Renew请求的过程

服务节点向注册中心发送续约请求,请求通过就受到200的http code,如果注册中心认为请求续约服务不存在就返回404的http code,只能重新注册,就会把自己标记为脏节点并更新最后的注册时间,注册成功后清除脏节点标示保留lastDirtyTimeStamp

8. 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页面的这一段英文提示就是服务自保的开启后的警告

这是一个服务自保的自动触发开关

手动开关服务自保

# 这是服务自保的总闸,false就是强制关闭服务自保,即便满足自动开启也不会开启服务自保
eureka.server.enable-self-preservation=false

9. Eureka启动心跳和健康检查

  • 配置客户端心跳和续约间隔

    在eureka-client的配置文件里设置

    # 每隔5秒向注册中心发送一条续约请求
    eureka.instance.lease-renewal-interval-in-seconds=5
    # 如果30秒没有收到续约请求,服务过期死掉,如果开启服务自保则剔除不生效
    eureka.instance.lease-expiration-duration-in-seconds=30
    

    在eureka-server的配置文件里

    # 强制关闭服务自保(自动开关不起作用)
    eureka.server.enable-self-preservation=false
    # 服务剔除的时间间隔
    eureka.server.eviction-interval-timer-in-ms=30000
    # 服务剔除的系数,如果有100台机器,其中95台都挂了,只能剔除100*0.90=90台
    eureka.server.renewal-percent-threshold=0.90
    

    配置好后要重启server、client服务

  • 模拟实验一下服务剔除,在eureka-client上进行剔除设置

# 每隔50秒向注册中心发送一条续约请求
eureka.instance.lease-renewal-interval-in-seconds=50
# 如果5秒没有收到续约请求,服务过期死掉,如果开启服务自保则剔除不生效
eureka.instance.lease-expiration-duration-in-seconds=5

10. 注册中心的高可用的架构

如果注册中心是单节点,出现故障整个服务将会出现不可用的情况,这个时候就需要有高可用的注册中心来提供服务,Eureka是AP模型,Eureka是通过互相注册来实现注册中心集群的

这里需要至少两个eureka-server

# 在两个eureka-server节点配置信息互通
# 首先在本机设置三个host
127.0.0.1 eurekaserver1
127.0.0.1 eurekaserver2
127.0.0.1 eurekaserver3
# 主机分别绑定
eureka.instance.hostname=eurekaserver1 # eureka.instance.hostname=eurekaserver2
# 在三个eureka-server里分别配置兄弟节点,做服务信息的同步,互相注册
eureka.client.service-url.defaultZone=http://eurekaserver2:20080/eureka/,http://eurekaserver3:30080/eureka/

# eureka-client仍然可以通过原理的注册方式进行注册,也可以直接注册到三个节点上防止节点宕机
eureka.client.serviceUrl.defaultZone=http://eurekaserver1:10080/eureka/,http://eurekaserver2:20080/eureka/,http://eurekaserver3:30080/eureka/

如果eureka集群注册成功,在登录界面会有以下显示

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值