Eureka实现原理

Eureka服务集群以及原理

在这里插入图片描述

服务注册

提供服务ip地址,端点等各项服务发现基本信息

服务取消

客户端连续一段时间没有向Eureka服务端发送服务续约消息,就会将其从服务列表剔除
在这里插入图片描述

Eureka服务存储和缓存

在Eureka,InstanceRegistry接口及其实现类实现这部分职能

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();

保存得数据结构是一个双层得HashMap,采用得是线程安全得ConcurrentHashMap.
第一层得key为spring.application.name(服务名),value为ConcurrentHashMap;第二层得ConcurrentHash得key为instanceId,服务实例id,value为Lease(租约)对象。
在这里插入图片描述

public interface LeaseManager<T> {

    void register(T r, int leaseDuration, boolean isReplication);

    boolean cancel(String appName, String id, boolean isReplication);

    boolean renew(String appName, String id, boolean isReplication);

    void evict();

}

里面就包括了注册,服务续约,服务取消和服务剔除操作。
LoookupService接口对应应用程序于服务实例得管理

public interface LookupService<T> {

    Application getApplication(String appName);

    Applications getApplications();

    List<InstanceInfo> getInstancesById(String id);

    InstanceInfo getNextServerFromEureka(String virtualHostname, boolean secure);

}

针对register方法来分析

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {

    try { 

        //从已存储的 registry 获取一个服务定义

        Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());

        REGISTER.increment(isReplication);

        if (gMap == null) {

            //初始化一个 Map<String, Lease<InstanceInfo>> ,并放入 registry 中

        }

 

        //根据当前注册的 ID 找到对应的 Lease

        Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());

 

        if (existingLease != null && (existingLease.getHolder() != null)) {

            //如果 Lease 能找到,根据当前节点的最新更新时间和注册节点的最新更新时间比较

            //如果前者的时间晚于后者的时间,那么注册实例就以已存在的实例为准

        } else {

              //如果找不到,代表是一个新注册,则更新其每分钟期望的续约数量及其阈值

        }

 

        //创建一个新 Lease 并放入 Map 中

        Lease<InstanceInfo> lease = new Lease<InstanceInfo>(registrant, leaseDuration);

        gMap.put(registrant.getId(), lease);



        //处理服务的 InstanceStatus

        registrant.setActionType(ActionType.ADDED);

 

        //更新服务最新更新时间

        registrant.setLastUpdatedTimestamp();

 

        //刷选缓存

        invalidateCache(registrant.getAppName(), registrant.getVIPAddress(), registrant.getSecureVipAddress());

    } 

}

cancle和renew方法也是同样得处理逻辑

Eureka服务缓存源码分析

Eureka服务器组件另外一个核心功能提供服务列表。为了提高性能,Eureka服务器会缓存一份所有已注册得服务列表,并通过一定得定时机制对缓存数据进行更新。

http://<eureka-server-ip>:8761/eureka/apps/<APPID>

Eureaka中所有对服务端得访问都是通过Restful风格进行获取。
getApllication方法分析

Key cacheKey = new Key(

       Key.EntityType.Application,

       appName,

       keyType,

       CurrentRequestVersion.get(),

       EurekaAccept.fromString(eurekaAccept)

);

 

String payLoad = responseCache.get(cacheKey);

 

if (payLoad != null) {

      logger.debug("Found: {}", appName);

      return Response.ok(payLoad).build();

} else {

      logger.debug("Not Found: {}", appName);

      return Response.status(Status.NOT_FOUND).build();

}

构建了一个CacheKey,并直接调用了responseCache.get(cacheKey)来响应,同时采用了缓存机制,最核心是get方法

public interface ResponseCache {

    void invalidate(String appName, @Nullable String vipAddress, @Nullable String secureVipAddress);

 

    AtomicLong getVersionDelta();

    AtomicLong getVersionDeltaWithRegions();

    String get(Key key);

    byte[] getGZIP(Key key);

}

从类层关系上看,ResponseCache 只有一个实现类 ResponseCacheImpl,我们来看它的 get 方法,发现该方法使用了如下处理策略:

Value getValue(final Key key, boolean useReadOnlyCache) {

        Value payload = null;

        try {
//如果存储存在
            if (useReadOnlyCache) {
//根据key获取缓存信息
                final Value currentPayload = readOnlyCacheMap.get(key);
//当前缓存不为空
                if (currentPayload != null) {
//直接赋值
                    payload = currentPayload;
//缓存为空重新写入
                } else {

                    payload = readWriteCacheMap.get(key);

                    readOnlyCacheMap.put(key, payload);

                }

            } else {

                payload = readWriteCacheMap.get(key);

            }

        } catch (Throwable t) {

            logger.error("Cannot get value for key : {}", key, t);

        }

        return payload;

}

这里有一个设计和实现上的技巧。把缓存设计为一个只读的 readOnlyCacheMap 以及一个可读写的 readWriteCacheMap,可以更好地分离职责。但因为两个缓存中保存的实际上是同一份数据,所以,我们在不断更新 readWriteCacheMap 时,也需要确保 readOnlyCacheMap 中的数据得到同步。为此 ResponseCacheImpl 提供了一个定时任务 CacheUpdateTask,如下所示:

private TimerTask getCacheUpdateTask() {

        return new TimerTask() {

            @Override

            public void run() {

                for (Key key : readOnlyCacheMap.keySet()) {

                    try {

                        CurrentRequestVersion.set(key.getVersion());

                        Value cacheValue = readWriteCacheMap.get(key);

                        Value currentCacheValue = readOnlyCacheMap.get(key);

                        if (cacheValue != currentCacheValue) {

                            readOnlyCacheMap.put(key, cacheValue);

                        }

                    } catch (Throwable th) {

                    }

                }

            }

        };

}

定时任务从readWriteCacheMap更新数据到readOnlyCacheMap

Eureka高可用源码解析

@Override

public void register(final InstanceInfo info, final boolean isReplication) {

        int leaseDuration = Lease.DEFAULT_DURATION_IN_SECS;

        if (info.getLeaseInfo() != null && info.getLeaseInfo().getDurationInSecs() > 0) {

            leaseDuration = info.getLeaseInfo().getDurationInSecs();

        }

        super.register(info, leaseDuration, isReplication);

        replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication);

}

我们在这里看到了一个非常重要的replicateToPeers 方法,该方法作就是用来实现服务器节点之间的状态同步。replicateToPeers 方法的核心代码如下所示:

for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {

    //如何该 URL 代表主机自身,则不用进行注册

    if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {

         continue;

    }

    replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);

}

为了理解这个操作,我们首先需要理解 Eureka 中的集群模式,这部分代码位于 com.netflix.eureka.cluster 包中,其中包含了代表节点的 PeerEurekaNode 和 PeerEurekaNodes 类,以及用于节点之间数据传递的 HttpReplicationClient 接口。

public interface HttpReplicationClient extends EurekaHttpClient {

    EurekaHttpResponse<Void> statusUpdate(String asgName, ASGStatus newStatus);

 

    EurekaHttpResponse<ReplicationListResponse> submitBatchUpdates(ReplicationList replicationList);

}

HttpReplicationClient 状态更新实例
@Override

public EurekaHttpResponse<Void> statusUpdate(String asgName, ASGStatus newStatus) {

        ClientResponse response = null;

        try {

            String urlPath = "asg/" + asgName + "/status";

            response = jerseyApacheClient.resource(serviceUrl)

                    .path(urlPath)

                    .queryParam("value", newStatus.name())

                    .header(PeerEurekaNode.HEADER_REPLICATION, "true")

                    .put(ClientResponse.class);

            return EurekaHttpResponse.status(response.getStatus());

        } finally {

            if (response != null) {

                response.close();

            }

        }

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值