zeus00456

导航

微服务架构 | 服务注册发现中心 - [综述与 Eureka]

@

§1 注册中心

服务

我们如何看待一个完整的项目?
我们可以将它看做一个功能的集合,这个集合运行在服务器上。
现在,我们拆碎这个集合,让一些天然关系较密切的功能单独存活,即让这个集合中的多个功能组运行在多个服务器上。
此时,我们将每个服务器中的运行的部分叫做一个服务(service)

提供者/消费者

在一个业务中,
一个服务通常是要被别人使用的,此时它的角色是提供者(provider),它提供了一些功能供外部调用
同时,服务在处理业务的过程中也可能调用一些外部提供的服务,此时它的角色是消费者(comsumer)
比如,用户在一个电商官网上下单,
用户动作会调用一个服务:统一接单服务(因为可能单据的来源和用途是多个,这里可以分情况处理)
统一接单服务进行一些处理后会将订单下放给销售单服务
销售单服务进行一些处理后又会生成一些库房生产信息下发给库房管理服务
在这个流程中,销售单服务统一接单服务提供了一些功能,同时也消费了库房管理服务提供的功能

注册中心

在实际的业务场景中,服务的种类会有很多,因为有不知道多少个功能需要通过服务提供
每一种服务的个数也可能会很多,因为服务最终是提供出来处理业务的,而一个运行时环境下运行的服务,处理业务的能力是有限的,因此需要多个服务的实例一起提供服务以满足业务需求

当然还有更复杂的情况,比如因为业务升级,追加了服务,一些服务追加了服务的实例个数,以及运行着运行着,一些服务的实例挂了需要牧师复活……
这些情况都导致了对于一个项目,无论是服务的种类还是服务的个数,乃至其中的具体实例都是在项目运行过程中动态变化的
为了让服务消费者可以在这种动态变化中正常工作,我们需要引入注册中心

注册中心的作用

注册中心最核心的功能是记录各个服务的信息,包括种类、个数、地址等
通常注册中心会作为集群存在,防止因单点故障导致注册中心整个都死了
服务提供者启动时,会自动向注册中心发送消息进行注册,之后定时向注册中心发送心跳消息,以证明自己还活着,可以正常工作
服务消费者,会定期向注册中心请求自己消费的服务的完整的服务列表,以知道哪些实例可以给自己提供服务

下面是个通用的示意图
在这里插入图片描述

常见服务注册发现中心比较

语音 CAP 健康检查 客户端 与Springcloud集成
Eureka java AP √ 可配 http
Consul Go CP http/dns
zookeeper java CP 客户端(命令行)
nacos java AP/CP 客户端(命令行)

简单 CAP 概述
C:强一致性
A:高可用性
P:分区容错性
作为一个微服务架构,因为是个分布式的架构,通常部署在多个机器上设计网络不稳定的因素,所以一定要确保分区容错性,
因此,微服务架构通常是 CP/AP
CA:单点集群,扩展性不强
CP:一致性强,但性能受限
AP:可用性强,但不适用与要求一致性的项目,并且通常要借助设计强化一定的一致性
在这里插入图片描述

§2 Eureka

版本较新的 Eureka 区分了 server 和 client,项目中需要单独配置

§3.1 Eureka 的基础使用快速入门

Server 端
依赖

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

配置

eureka:
  instance:
    hostname: eureka-1.com #eureka服务端的实例名称,自定义命名
  client:
    register-with-eureka: false     #false server不需要向自己注册自己
    fetch-registry: false     #false server不需要拉取注册信息,是client向它注册
    service-url:
      #集群地址,指向集群中除自己外的所有
      #defaultZone: http://eureka-2.com:7002/eureka/,http://eureka-3.com:7003/eureka/

启动类及注解

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

client 端
依赖

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

配置

eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true。
    register-with-eureka: true
    #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetchRegistry: true
    service-url:
      # 集群版
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
  instance:
  	# 定义服务实例名
    instance-id: payment-service-01 
    # 显示服务实例ip
    prefer-ip-address: true

启动类及注解

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

rpc 调用

    private static final String BASE_URL = "http://payment-service";//使用注册中心里的服务名而不是服务节点的地址
    
    @Resource
    private RestTemplate restTemplate;

	//示例
    @RequestMapping(value = "", method = RequestMethod.POST)
    public CommonResult<PaymentEntity> order(@RequestBody PaymentEntity entity){
        try{
            String url = BASE_URL+"/payment";
            ParameterizedTypeReference typeRef = 
                    new ParameterizedTypeReference<CommonResult<PaymentEntity>>(){};
            
            ResponseEntity<CommonResult<PaymentEntity>> responseEntity = 
                    restTemplate.exchange(url,HttpMethod.POST,new HttpEntity<>(entity), typeRef);
            
            return responseEntity.getBody();
        }catch (Exception e){
            // do catch
        }

restTemplate 的配置

@Configuration
public class RestTemplateConfig {
	//习惯问题,变更了默认序列化器
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplateBuilder()
        .messageConverters(new GsonHttpMessageConverter(new GsonBuilder().serializeNulls().create()))
        .build();
    }
}

§3.2 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 错误地声称实例已经启动,但实际上并没有。续订会小于阈值,因此实例不会出于安全的考虑而过期。
即:当某个微服务不可用了,Eureka 不会立即清理这个服务的注册信息。

作用
Eureka 的自我保护机制是出于高可用考虑(CAP 中的 AP),如下图:
在这里插入图片描述
服务提供者正常情况下会定期向注册中心发送心跳信息,
能正常发送心跳证明服务活着,否则就是挂了
但有一种特殊情况,服务还活着,但是服务提供者和注册中心直接的网络通信因为分区网络问题挂了
即,服务本身没事,只是一定时间内注册中心收不到心跳了(实际上对于注册中心而言,无法判断是服务挂了还是网络不通)
此时,Eureka 的自我保护机制下,Eureka 不会立即注销此服务

因此,开启 Eureka 的自我保护机制,可以防止 Eureka 误删可能健康的服务,可以增加 Eureka 集群的健壮性

关闭
在开发测试阶段,可能需要 Eureka 集群对服务状态的变化立即响应(比如开发过程中服务已经挂了,但Eureka依然认为它在正常工作,影响开发过程)

server 端配置:

eureka:
  server:
  	# 关闭自我保护机制,保证不可用服务被及时踢除
    enable-self-preservation: false
    # Eureka Server 清理无效节点的频率,默认 60000 毫秒
    eviction-interval-timer-in-ms: 2000

client 端配置:

eureka:
  instance:
    instance-id: order-1
    #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
    lease-renewal-interval-in-seconds: 1
    #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
    lease-expiration-duration-in-seconds: 2

server 端按 eviction-interval-timer-in-ms 频率清理无效节点,
清理时,按 lease-expiration-duration-in-seconds 设置的额时间确认无效节点

§3 Discovery

注解( @EnableDiscoveryClient )

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

DiscoveryClient
引入

@Resource
private DiscoveryClient discoveryClient;

信息示例代码

@RequestMapping(value = "/discovery", method = RequestMethod.GET)
public CommonResult<Object> discovery(){
    try{
        Map<String,Object> discoveryDescriptor = new HashMap<>();
        discoveryDescriptor.put("dexcription", this.discoveryClient.description());
        discoveryDescriptor.put("order",this.discoveryClient.getOrder());
        discoveryDescriptor.put("services",this.discoveryClient.getServices());

        List<ServiceInstance> instances = new ArrayList<>();
        for(String serviceName: this.discoveryClient.getServices()){
            instances.addAll(this.discoveryClient.getInstances(serviceName));
        }
        discoveryDescriptor.put("instances",instances);

        return new CommonResult<>(200,"",discoveryDescriptor);
    }catch (Exception e){
    }
}

可以提供的信息
主要关注data部分,为显示方便,只保留一个 instance 节点的信息

{
    "code": 200,
    "message": "",
    "data": {
        "instances": [
            {
                "host": "192.168.3.7",//实例运行的主机
                "port": 8003,//实例占用的端口
                "secure": false,
                "metadata": {
                    "management.port": "8003"
                },
                "uri": "http://192.168.3.7:8003",//ip 端口
                "serviceId": "PAYMENT-SERVICE",//所属服务名
                "instanceId": "payment-service-03",//服务实例名
                "instanceInfo": {
                    "instanceId": "payment-service-03",
                    "app": "PAYMENT-SERVICE",
                    "appGroupName": null,
                    "ipAddr": "192.168.3.7",
                    "sid": "na",
                    "homePageUrl": "http://192.168.3.7:8003/",
                    "statusPageUrl": "http://192.168.3.7:8003/actuator/info",
                    "healthCheckUrl": "http://192.168.3.7:8003/actuator/health",
                    "secureHealthCheckUrl": null,
                    "vipAddress": "payment-service",//虚拟IP地址,就是服务名
                    "secureVipAddress": "payment-service",
                    "countryId": 1,
                    "dataCenterInfo": {
                        "@class": "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo",
                        "name": "MyOwn"
                    },
                    "hostName": "192.168.3.7",
                    "status": "UP",//服务状态
                    "overriddenStatus": "UNKNOWN",
                    "leaseInfo": {
                        "renewalIntervalInSecs": 30,
                        "durationInSecs": 90,
                        "registrationTimestamp": 1657003204845,
                        "lastRenewalTimestamp": 1657003775311,
                        "evictionTimestamp": 0,
                        "serviceUpTimestamp": 1657003204845
                    },
                    "isCoordinatingDiscoveryServer": false,
                    "metadata": {
                        "management.port": "8003"
                    },
                    "lastUpdatedTimestamp": 1657003204845,
                    "lastDirtyTimestamp": 1657003204781,
                    "actionType": "ADDED",
                    "asgName": null
                },
                "scheme": null
            }
        "dexcription": "Spring Cloud Eureka Discovery Client",
        "services": [
            "payment-service",
            "order-service"
        ],
        "order": 0
    }
}

传送门:
微服务架构 | 组件目录

posted on 2022-07-28 16:01  问仙长何方蓬莱  阅读(72)  评论(0编辑  收藏  举报