一、什么是服务治理and服务治理技术选型

目录

  1. 什么是服务治理and服务治理技术选型
    什么是服务治理
    服务治理技术选型
  2. 深入了解Eureka
    Eureka功能概述
    搭建服务注册中心
    了解注册中心UI界面
    服务注册流程解读
    创建服务提供者
    创建服务消费者
  3. 心跳检测和服务续约
    心跳监测
    服务剔除
    服务续约
    服务自保
    服务下线

(一)、什么是服务治理
1.概述
服务治理主脑一样,是整个微服务架构的第一关,是所有微服务应用都要思考的第一个问题。下面这我从服务治理能做什么(目标)和服务治理是怎么完成这些目标的,从这两个方面来给大家介绍一下什么是服务治理。
2.服务治理要实现什么目标
在整个分布式系统中,服务治理要实现4个目标
第一,实现要高可用
第二,要实现分布式调用
第三,实现生命周期管理
第四,健康度检查
高可用:即在服务治理麾下的所有节点,不论是因为服务器自身原因还是外部原因,即使只有一台节点存活,服务治理框架也要保证服务的可用性
分布式调用:在众多微服务节点中,必须保证准确的向对应的服务节点发起请求
生命周期管理:微服务从上线、运行、下线,整个生命周期得到管理
健康检查:对节点进行监听,准确判断服务是否需剔除。
3.服务治理实现上诉目标的手段–服务治理的解决方案
服务注册:服务提供方自报家门
服务发现:服务消费者从注册中心获取服务信息
心跳检查、服务续约和服务剔除
服务下线
(二)服务治理技术选型
1.分布式系统CPA定理
一致性:强一致性、弱一致性、最终一致性
C:一致性
A:可用性
P:分区容错性
CPA定理:分布式系统一般都只能3选2,不能同时满足
2.组件选择
实现服务治理的组件有三大选项
Eureka:出道最早,来自Netflix公司
弱一致性AP组合,性能较快,支持HTTP协议,目前市面主流
Consul:出道较晚,挂牌于SpringCloud
弱一致性AP组合,性能较慢,支持HPPT协议和DNS,市面使用较少
Nacos:晚辈,源自于Alibaba公司
支持AP/CP两种组合,性能较快,支持多种协议,包括HTTP,DNS,DUP,由于出道时间比较短暂,市面使用不是非常广泛

二、深入了解Eureka

(一)、Eureka功能概述
1.具体功能
服务注册、服务发现、心跳和续约、服务下线、剔除和自保
2.注册中心简述
注册中心是服务治理的第一步,也是整个服务治理最重要的部分
一般情况下注册中心有两种解决方案:
第一种:一一询问式,由注册中心注册中心主动访问网络节点中的所有机器
第二种:守株待兔式,注册中心等待各个服务节点前来注册
目前市面上普遍使用第二种模式,第一种模式,网络开销大,注册中心服务端压力增大,不仅需要响应客户端请求,还有寻找服务节点,压力增加;最主要的是网络环境复杂,甚至涉及到跨局域网的情况,这更增加了寻找服务节点的难度,所以普遍使用第二种模式,守株待兔。

第一种模式的优点:减少网络开心,减轻注册中心的压力,节省了主动寻找服务节点的时间

注册中心的日常任务:第一,记录每个服务节点的IP+端口;第二,记录没有服务节点提供什么服务;第三,记录服务节点的状态;第四,心跳检测和服务剔除;第五,同步注册信息(多注册中心的情况)

当然了,尽管呢注册中心是守住待兔的形式等待服务节点上门报道,所以每个服务节点上线前,就必须明确它应当去哪里报道,这必须由开发人员告诉它。

总结:
Eureka注册模式:守株待兔——等待服务节点上门报道;
服务节点注册信息主要包含地址(IP+端口)、服务、状态
注册中心功能:第一,记录每个服务节点的IP+端口;第二,记录没有服务节点提供什么服务;第三,记录服务节点的状态;第四,心跳检测和服务剔除;第五,同步注册信息(多注册中心的情况)

(二)、搭建服务注册中心

1.创建Demo顶层pom和子项目eureka-server

第一步:创建maven项目,目录结构如下:

junit 启动spring容器只注册feign_spring


第二步,spring-cloud-demo2项目pom文件引入依赖(最外层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.lys</groupId>
    <artifactId>spring-cloud-demo2</artifactId>
    <packaging>pom</packaging>
    <version>1.0.0-SNAPSHOT</version>
    <modules>
        <module>eureka/eureka-server</module>
    </modules>

    <!--dependencyManagement 和 dependencies的区别:
        dependencyManagement并不是真实的引入一个dependency,而是把依赖包的version管理起来,
        这样的话可以不用指定依赖的version,它会从直接从父类项目或者从当前项目pom文件当中的
        dependencyManagement里面读取version
    -->
    <dependencyManagement>
        <dependencies>
            <!--spring cloud 依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Greenwich.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!--spring boot 依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-parent</artifactId>
                <version>2.1.5.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</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>

2.子项目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>spring-cloud-demo2</artifactId>
        <groupId>com.lys</groupId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</packaging>

    <artifactId>eureka-server</artifactId>

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

</project>

3.设置启动类
**第一步:**配置文件
application.properties

spring.application.name=eureka-server

server.port=20000

eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

第二步:
文件名:EurekaServerApplication

package com.lys.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;

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 2021/2/2
 * Time: 23:05
 * Description: No Description
 */
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

4.启动测试

junit 启动spring容器只注册feign_spring_02


访问:http://localhost:20000/

junit 启动spring容器只注册feign_eureka_03


(三)、了解注册中心UI界面

1.System Status

junit 启动spring容器只注册feign_微服务_04

Environment:环境,使用默认即可
Data center:默认即可
Current time:当前系统的时间
Uptime:注册中心从启动到至今运行的时间
Lease expiration enabled:是否启用租约过期,默认true
Renews threshold:每分钟的租约数,即每分钟至少发多少次请求到注册中心
Renews (last min):最后一分钟的续约数量

2.DS Replicas(注册中心副本)

junit 启动spring容器只注册feign_spring cloud_05


3.Instances currently registered with Eureka

指在注册中心注册成功的应用

junit 启动spring容器只注册feign_eureka_06

4.General Info

一般信息

junit 启动spring容器只注册feign_spring_07


total-avail-memory:总共可用内存

environment:环境

num-of-cpus:cpu个数,4核,8核这种

current-memory-usage:当前已用内存

server-uptime:注册中心从启动到至今运行的时间

registered-replicas:注册中心集群副本节点信息

unavailable-replicas:注册中心集群不可用的副本节点信息

available-replicas:可用副本5.Instance Info

注册中心本身

junit 启动spring容器只注册feign_微服务_08


6.查看过去现在的租约信息

junit 启动spring容器只注册feign_服务治理_09


(四)、服务注册流程解读

1.注册起始流程

注册准备,起始流程如下:

junit 启动spring容器只注册feign_服务治理_10


网图,侵删

**注册起始流程解读:**由启动类中的@EnableDiscoveryClient注解开启自动注册流程 -> 到DiscoveryCLient类的register方法发起注册 -> SessionedEurekaHttpClient(父类EurekaHttpClientDecorator)进行装饰+代理 -> 通过代理给注册器加上装饰器

2.正式注册流程

junit 启动spring容器只注册feign_spring cloud_11


网图,侵删(五)、创建服务提供者

1.创建eureka-client子项目

junit 启动spring容器只注册feign_eureka_12

2.添加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>spring-cloud-demo2</artifactId>
        <groupId>com.lys</groupId>
        <version>1.0.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>

3.创建启动类和服务内容
第一步: 配置文件
application.properties

spring.application.name=eureka-client

server.port=30000
#指定使用IP注册
eureka.instance.hostname=192.168.1.5
eureka.instance.ip-address=192.168.1.5
#指定instance-id(ip + 应用名 + port)
eureka.instance.instance-id=${eureka.instance.ip-address}:${spring.application.name}:${server.port}
# 注册时使用ip而不是主机名
eureka.instance.prefer-ip-address=true
eureka.client.service-url.defaultZone=http://localhost:20000/eureka/

#每隔5秒钟,向服务中心发送一条续约指令
eureka.instance.lease-renewal-interval-in-seconds=5
#如果服务中心30秒没有收到续约请求,则判定服务过期
eureka.instance.lease-expiration-duration-in-seconds=30

第二步: 创建启动类

package com.lys.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;

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 2021/2/3
 * Time: 22:44
 * Description: No Description
 */
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {

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

第三步: 创建服务内容

package com.lys.springcloud.controller;

import com.lys.springcloud.model.Test;
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;

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 2021/2/3
 * Time: 22:51
 * Description: No Description
 */
@RestController
@Slf4j
public class TestController {

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

    @GetMapping("/hello")
    public String hello() {
        return "hello" + port;
    }

    @PostMapping("/test")
    public Test test(@RequestBody Test test) {
        log.info("访问者:{}", test);
        test.setName("test");
        return test;
    }
}

4.检查注册中心UI变化

junit 启动spring容器只注册feign_eureka_13


(六)、创建服务消费者

1.创建eureka-consumer子项目

junit 启动spring容器只注册feign_spring cloud_14

2.添加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>spring-cloud-demo2</artifactId>
        <groupId>com.lys</groupId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../../pom.xml</relativePath>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <packaging>jar</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>

3.创建启动类和controller
第一步: 配置文件
application.properties

spring.application.name=eureka-consumer

server.port=40000

eureka.client.service-url.defaultZone=http://localhost:20000/eureka/

第二步: 创建启动类

package com.lys.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;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 2021/2/4
 * Time: 22:44
 * Description: No Description
 */
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaConsumerApplication {

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

    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

第三步: 创建controller

package com.lys.springcloud.controller;

import com.lys.springcloud.model.Test;
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;

/**
 * Created with IntelliJ IDEA.
 * User: Administrator
 * Date: 2021/2/4
 * Time: 22:51
 * Description: No Description
 */
@RestController
@Slf4j
public class ConsumerTestController {

    //简易的负载均衡器
    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/hello")
    public String hello() {
        ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client");
        if (serviceInstance == null) {
            return "no available instance";
        }
        String target = String.format("http://%s:%s/hello", serviceInstance.getHost(), serviceInstance.getPort());
        log.info("访问目标:{}", target);
        String result = restTemplate.getForObject(target, String.class);
        return result;
    }


    @PostMapping("/hello")
    public Test helloPost() {
        ServiceInstance serviceInstance = loadBalancerClient.choose("eureka-client");
        if (serviceInstance == null) {
            return null;
        }
        String target = String.format("http://%s:%s/hello", serviceInstance.getHost(), serviceInstance.getPort());
        log.info("访问目标:{}", target);
        Test test = new Test();
        test.setName("Eureka Consumer");
        Test result = restTemplate.postForObject(target, test, Test.class);
        return result;
    }
}

4.向eureka-client发起调用

junit 启动spring容器只注册feign_spring_15

三、心跳检测和服务续约

(一)、心跳监测
1.概念
心跳监测,即感知微服务集群节点健康状况的一种手段,避免服务节点宕机后不断出现404的状况。
2.心跳监测的特点:
(1)由客户端节点主动发起请求;
(2)同步当前节点状态;
(3)服务剔除,当客户端在指定的时间范围内,没有向注册中心发起请求时,注册中心就会主动将服务从注册列表剔除掉;
(4)服务续约,服务续约也是依赖心跳来实现的。
3.心跳检查
(1)访问地址:客户端需要知道注册中心的地址
(2)访问来宾:客户端在进行心跳请求时,需要告知注册中心一些自身的信息,包括注册时提供的服务名(app_name)、服务节点编号(instance_id)
(3)服务状态:服务自身的状态,包括UP、DOWN、STARTING、OUT_OF_SERVICE、UNKNOW;
(4)最后一次注册同步的时间:最后一次同步时间lastDirtyTimeStamp,表示当前服务节点最后一次与注册中心失去同步时的时间,它一般与isInstanceInfoDirty联用,isInstanceInfoDirty=true时,表示当前节点自从lastDirtyTimeStamp时间后都处于未同步状态。
客户端指标
(1)、发送心跳包的时间间隔
(2)、指明注册中心,如果多少时间内未收到我的请求,就认为我宕机了。
要求:第一个时间必须小于第二个时间

(二)、服务剔除

1.作用

为了让无心跳响应的服务节点自动下线。

2.剔除流程

junit 启动spring容器只注册feign_spring_16


启动定时任务:注册中心启动后会在后台默认同步开启一个后台定时任务,默认每间隔60秒触发剔除任务;

调用evict:服务剔除直接通过eviction方法进行;

自保开启:自保开启后,注册中心会中断服务剔除操作;

遍历过期服务:剔除服务会遍历所有注册了的服务节点,一旦满足一下条件,就会被认为是可剔除的服务—>1.已被标记为过期,2.最后一次心跳时间+服务端配置的心跳间隔时间<当前时间;

计算可剔除服务的总个数:为了保证整个集群的稳定性,注册中心是不可能把服务全部剔除完的,最多可剔除个数不能大于 总个数*0.85(系统默认);

乱序剔除:服务剔除是不安顺序的,是随机的。(三)、服务续约

1.作用

告知注册中心,当前服务节点还是可用的,还能继续工作。

2.续约步骤

第一步:

借助心跳请求将服务节点的状态同步到注册中心,通知注册中心我还能继续工作;

第二步:

注册中心收到请求后,判断服务节点状态,然后修改当前服务节点在注册中记录的同步时间

第三步:

服务节点发送续约请求

junit 启动spring容器只注册feign_服务治理_17


服务续约请求:由客户端的DiscoverClient类中的renew方法开始

发送心跳:服务续约借助发送心跳来完成,成功返回200,失败返回404

重新注册:1.设置lastDirtyTimeStamp,2.标记自身为脏节点

注册成功后:1.清除脏节点标记,2.lastDirtyTimeStamp保持不变,后续作为参数发送注册中心,便于注册中心判断服务节点状态。

第四步:

注册中心续约校验

junit 启动spring容器只注册feign_spring_18

(四)、服务自保
说到服务自保就不得不提到服务剔除,这两种逻辑一正一反,他们在同一时刻只能执行一种逻辑。
服务剔除: 即在在服务节点无心跳响应的情况下,将服务节点从注册列表中剔除;
服务自保: 即保留当前注册列表中的所有节点,一个都不能少。
服务自保应用场景: 实际工作中,网络波动等情况是很常见的,这种情况虽然会影响服务节点心跳响应,但服务节点之间的调用并未受到影响,此时如果大批量的剔除服务节点,就会引起大范围的业务停滞,所以这种方式肯定是不可取的,于是服务自保就开始发挥功用了。
服务自保的控制方式:
1.自动控制
开启时机:在一定时间范围内,已续约的节点个数与已注册总服务个数的比值,低于系统限定值,此时服务自保将会开启,所有的服务节点都不会过期(被剔除)。
2.手动控制

#关闭服务自保
eureka.server.enable-self-preservation=false

(五)、服务下线

服务下线通常由服务节点服务器关闭或主动调用shutdown方法来触发,是由服务节点主动向注册中心发起的资源释放命令。

下线代表着服务的生命周期走到尾声。

junit 启动spring容器只注册feign_spring_19


标记服务状态: 将服务自身状态标记为DOWN;

获取系统锁: 获取到系统锁后,进行服务下线动作,保证下线动作的线程安全;

释放资源: 由于服务注册时,同步启动了心跳任务,状态同步任务和监听器等后台任务,下线时需要释放对应的资源;

发送delete指令: 发送delete指令到注册中心,完成服务下线请求。

ok, spring cloud 初体验之服务注册Eureka, 大概就是这些了,有点冗长,也是我学习中的一些心得,希望大家多指教,后续还会有一些关于微服务的文章,希望大家多多指教。