01、SpringCloud之Eureka学习笔记(二)

简介: 01、SpringCloud之Eureka学习笔记(二)

四、Eureka概念的理解


4.1、服务的注册


当项目启动时(eureka 的客户端),就会向 eureka-server 发送自己的元数据(原始数据) (运行的 ip,端口 port,健康的状态监控


等,因为使用的是 http/ResuFul 请求风格), eureka-server 会在自己内部保留这些元数据(内存中)。(有一个服务列表)(restful 风


格,以 http 动词的请求方式,完成对 url 资源的操作)


4.2、服务的续约


项目启动成功了,除了向 eureka-server 注册自己成功,还会定时的向 eureka-server 汇 报自己,心跳,表示自己还活着。(修改一个时间)


4.3、服务的下线


当项目关闭时,会给 eureka-server 报告,说明自己要下机了。


4.4、服务的剔除(被动下线,主动剔除)


当项目超过了指定时间没有向 eureka-server 汇报自己,那么 eureka-server 就会认为此节点死掉了,会把它剔除掉,也不会放流量和请


求到此节点了。


五、Eureka原理分析


5.1、Eureka 运作原理的特点


Eureka-server 对外提供的是 restful 风格的服务 :以 http 动词的形式对 url 资源进行操作 get post put delete。


http 服务 + 特定的请求方式 + 特定的 url 地址:只要利用这些 restful 我们就能对项目实现注册和发现。


只不过,eureka 已经帮我们使用 java 语言写了 client,让我们的项目只要依赖 client 就能实现注册和发现!


只要你会发起 Http 请求,那你就有可能自己实现服务的注册和发现。不管你是什么语言!


5.2、服务注册的源码分析【重点】



5.2.1、Eureka-client发起注册请求


5.2.1.1、源码位置



5.2.1.2、如何发送信息来进行注册?



真正去执行注册的是在一个抽象类中:



总结: 当 eureka 启动的时候,会向我们指定的 serviceUrl 发送请求,把自己节点的数据以 post 请求的方式,数据以 json 形式发送过去。 当返回的状态码为 204 的时候,表示注册成功。


5.2.2、Eureka-server实现注册+保存



1、接受客户端的请求


com.netflix.eureka.resources.ApplicationResource



2、实际服务端的源码位置



3、处理请求(注册自己,向其他节点注册)


上面第二段super.register()
public void register(InstanceInfo info, boolean isReplication) {
    int leaseDuration = 90;
    if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {
        leaseDuration = info.getLeaseInfo().getDurationInSecs();
    }
  //真正注册自己
    super.register(info, leaseDuration, isReplication);
    this.replicateToPeers(PeerAwareInstanceRegistryImpl.Action.Register, info.getAppName(), info.getId(), info, (InstanceStatus)null, isReplication);
}


4、真正的注册自己


public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
        this.read.lock();
        try {
            //通过服务名称得到注册的实例
            Map<String, Lease<InstanceInfo>> gMap = (Map)this.registry.get(registrant.getAppName());
            EurekaMonitors.REGISTER.increment(isReplication);
            //因为之前没有实例,肯定为 null
            if (gMap == null) {
                //新建一个集合来存放实例
                ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap();
                gMap = (Map)this.registry.putIfAbsent(registrant.getAppName(), gNewMap);
                if (gMap == null) {
                    gMap = gNewMap;
                }
            }
            //gMap 就是该服务的实例
            Lease<InstanceInfo> existingLease = (Lease)((Map)gMap).get(registrant.getId());
            if (existingLease != null && existingLease.getHolder() != null) {
                Long existingLastDirtyTimestamp = ((InstanceInfo)existingLease.getHolder()).getLastDirtyTimestamp();
                Long registrationLastDirtyTimestamp = registrant.getLastDirtyTimestamp();
                logger.debug("Existing lease found (existing={}, provided={}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
                if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) {
                    logger.warn("There is an existing lease and the existing lease's dirty timestamp {} is greater than the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp);
                    logger.warn("Using the existing instanceInfo instead of the new instanceInfo as the registrant");
                    registrant = (InstanceInfo)existingLease.getHolder();
                }
            } else {
                synchronized(this.lock) {
                    if (this.expectedNumberOfClientsSendingRenews > 0) {
                        ++this.expectedNumberOfClientsSendingRenews;
                        this.updateRenewsPerMinThreshold();
                    }
                }
                logger.debug("No previous lease information found; it is new registration");
            }
    //新建一个服务的实例节点
            Lease<InstanceInfo> lease = new Lease(registrant, leaseDuration);
            if (existingLease != null) {
                lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp());
            }
    //放到注册 map 的列表里
            ((Map)gMap).put(registrant.getId(), lease);
            this.recentRegisteredQueue.add(new Pair(System.currentTimeMillis(), registrant.getAppName() + "(" + registrant.getId() + ")"));
            if (!InstanceStatus.UNKNOWN.equals(registrant.getOverriddenStatus())) {
                logger.debug("Found overridden status {} for instance {}. Checking to see if needs to be add to the overrides", registrant.getOverriddenStatus(), registrant.getId());
                if (!this.overriddenInstanceStatusMap.containsKey(registrant.getId())) {
                    logger.info("Not found overridden id {} and hence adding it", registrant.getId());
                    this.overriddenInstanceStatusMap.put(registrant.getId(), registrant.getOverriddenStatus());
                }
            }
            InstanceStatus overriddenStatusFromMap = (InstanceStatus)this.overriddenInstanceStatusMap.get(registrant.getId());
            if (overriddenStatusFromMap != null) {
                logger.info("Storing overridden status {} from map", overriddenStatusFromMap);
                registrant.setOverriddenStatus(overriddenStatusFromMap);
            }
            InstanceStatus overriddenInstanceStatus = this.getOverriddenInstanceStatus(registrant, existingLease, isReplication);
            registrant.setStatusWithoutDirty(overriddenInstanceStatus);
            if (InstanceStatus.UP.equals(registrant.getStatus())) {
                lease.serviceUp();
            }
    //设置心跳时间等参数
            registrant.setActionType(ActionType.ADDED);
            this.recentlyChangedQueue.add(new AbstractInstanceRegistry.RecentlyChangedItem(lease));
            registrant.setLastUpdatedTimestamp();
            this.invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());
            logger.info("Registered instance {}/{} with status {} (replication={})", new Object[]{registrant.getAppName(), registrant.getId(), registrant.getStatus(), isReplication});
        } finally {
            this.read.unlock();
        }
    }



5.2.3、服务注册总结


重要的类:


DiscoveryClient 里面的 register()方法完后注册的总体构造

AbstractJerseyEurekaHttpClient 里面的 register()方法具体发送注册请求(post)

InstanceRegistry 里面 register()方法接受客户端的注册请求

PeerAwareInstanceRegistryImpl 里面调用父类的 register()方法实现注册

AbstractInstanceRegistry 里面的 register()方法完成具体的注册保留数据到 map 集合保存服务实例数据的集合:

保存服务实例数据的集合:


第一个 key 是应用名称(全大写) spring.application.name
Value 中的 key 是应用的实例 id eureka.instance.instance-id
Value 中的 value 是 具体的服务节点信息
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();


5.3、服务剔除的源码分析(被动下线)


AbstractInstanceRegistry 的evict()方法中筛选剔除的节点。


在 internalCancel 方法里面真正实现剔除:其实就是对map执行remove操作。


在服务剔除中涉及到哪些重要的点


怎么删除一个集合里面过期的数据?


Redis 怎么清除过期的 key LRU(热点 key)


1 定时(k-thread)


2 惰性 (在再次访问该 key 时有作用)


3 定期 (使用一个线程来完成清除任务)


操作:定期(实时性差) + 惰性


何时进行剔除操作?


通过一个定时任务:




5.4、服务下线的源码分析


eureka-client发起下线请求

①通过unregister()方法来进行发起下线请求



②真正的发起请求下线:AbstractJerseyEurekaHttpClient



eureka-server处理下线请求

①接受下线请求



②真正的下线服务



5.5、服务发现的源码分析


5.5.1、服务发现流程分析


从 discoveryClient.getInstances(serviceId);方法进去,找到eureka 的实现



从 getInstancesByVipAddress 方法进去看到真正的服务发现



在 getInstancesByVirtualHostName 方法里面做真正的服务发现




5.5.2、在 eureka-client 客户端也有 map 集合存放服务列表?



我们发现,当我们还没有做服务发现之前,集合里面已经有值了,说明项目启动的时候就去server 端拉取服务列表并且缓存了起来


到底何时从 server 拉取服务放进去的呢?


在 eureka 的 DiscoverClient 类的一个构造方法里面,有一个任务调度线程池:




查看 initScheduledTasks()这个方法



在 CacheRefreshThread()中



fetchRegistry()方法中判断决定是全量拉取还是增量拉取



getAndStoreFullRegistry()全量拉取:



getAndUpdateDelta()增量拉取:



5.5.3、服务发现总结


重要的类:


DiscoveryClient 类里面的构造方法执行线程初始化调用

CacheRefreshThread 类里面的 run 方法执行服务列表的拉取(方便后期做服务发现)

fetchRegistry()方法去判断全量拉取还是增量拉取

全量拉取发生在:当服务列表为 null 的情况 当项目刚启动就全量拉取


增量拉取发生:当列表不为 null ,只拉取 eureka-server 的修改的数据(注册新的服务, 上线服务)


eureka 客户端会把服务列表缓存到本地 为了提高性能 ,但是有脏读问题,当你启动一个新的应用的时候 不会被老的应用快速发现


六、服务发现


6.1、什么是服务发现?


根据服务名称发现服务的实例过程


客户端会在本地缓存服务端的列表


拉取列表是有间隔周期的 (导致服务上线 客户端不能第一时间感知到 (可以容忍))


其实每次做服务发现 都是从本地的列表来进行的



6.2、简单实现服务发现功能


我们在eureka-client-a中编写对应的服务发现控制器:




package com.changlu.eurekaclienta.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * @Description: 我的服务注册发现
 * @Author: changlu
 * @Date: 10:04 PM
 */
@RestController
public class MyDiscoveryController {
    @Autowired
    private DiscoveryClient discoveryClient;//这个接口是springcloud提供的,然后eureka提供了对应的实现类
    @GetMapping("/find")
    public String find(String serviceId) {
        //调用服务发现
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
        instances.forEach(System.out::println);
        return instances.toString();
    }
}


启动一个server服务以及两个客户端:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dgfv64lB-1657597370320)(C:\Users\93997\AppData\Roaming\Typora\typora-user-images\image-20220703221052050.png)]


访问下:http://localhost:8761/,查看下当前的服务



接着我们来进行测试服务发现:http://localhost:8082/find?serviceId=eureka-client-a


相关文章
|
2月前
|
负载均衡 算法 Nacos
SpringCloud 微服务nacos和eureka
SpringCloud 微服务nacos和eureka
66 0
|
2月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
42 0
|
3月前
|
负载均衡 Java Nacos
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
微服务介绍、SpringCloud、服务拆分和远程调用、Eureka注册中心、Ribbon负载均衡、Nacos注册中心
SpringCloud基础1——远程调用、Eureka,Nacos注册中心、Ribbon负载均衡
|
4月前
|
负载均衡 监控 Java
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
SpringCloud常见面试题(一):SpringCloud 5大组件,服务注册和发现,nacos与eureka区别,服务雪崩、服务熔断、服务降级,微服务监控
|
4月前
|
Java Spring
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
【Azure Spring Cloud】Spring Cloud Azure 4.0 调用Key Vault遇见认证错误 AADSTS90002: Tenant not found.
|
4月前
|
Java Spring 容器
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
【Azure Spring Cloud】在Azure Spring Apps上看见 App Memory Usage 和 jvm.menory.use 的指标的疑问及OOM
|
4月前
|
存储 Java Spring
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
【Azure Spring Cloud】Azure Spring Cloud服务,如何获取应用程序日志文件呢?
|
4月前
|
SQL Java 数据库连接
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
【Azure Spring Cloud】Azure Spring Cloud connect to SQL using MSI
|
4月前
|
Java 开发工具 Spring
【Azure Spring Cloud】使用azure-spring-boot-starter-storage来上传文件报错: java.net.UnknownHostException: xxxxxxxx.blob.core.windows.net: Name or service not known
【Azure Spring Cloud】使用azure-spring-boot-starter-storage来上传文件报错: java.net.UnknownHostException: xxxxxxxx.blob.core.windows.net: Name or service not known
|
4月前
|
NoSQL Java Redis
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常
【Azure Spring Cloud】Java Spring Cloud 应用部署到Azure上后,发现大量的 java.lang.NullPointerException: null at io.lettuce.core.protocol.CommandHandler.writeSingleCommand(CommandHandler.java:426) at ... 异常