【SpringCloud】Eureka入门与原理

为了开发效率高效和业务逻辑清晰,愈来愈多的项目采用分布式系统。分布式最重要的就是注册中心了。Eureka是SpringCloud原生提供的注册中心,来look一波吧。spring

超光速入门

服务端

引入依赖:bootstrap

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

给启动类加上注解@EnableEurekaServer浏览器

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

配置一下yml文件:安全

#端口号
server:
  port: 8331
#Eureka实例名,集群中根据这里相互识别
eureka:
  instance:
    hostname: eureka
  #客户端
  client:
    #是否开启注册服务,做为注册中心,就不注册了
    register-with-eureka: false
    #是否拉取服务列表,这里我只提供服务给别的服务。
    fetch-registry: false
    #注册中心地址
    service-url:
      defaultZone: http://localhost:8331/eureka/

启动项目EurekaApplication ,浏览器访问http://localhost:8331/,Euerka 服务器搭建成功了。服务器

如今尚未东西注册进来。数据结构

客户端

引入依赖:app

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

这回启动类注解变了,@EnableDiscoveryClient分布式

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

接下来是配置文件:ide

#端口号
server:
  port: 8332
#Eureka实例名,集群中根据这里相互识别
spring:
  application:
    name: first-service
eureka:
  #客户端
  client:
    #注册中心地址
    service-url:
      defaultZone: http://localhost:8331/eureka/

而后启动项目,过一下子,刷新页面:微服务

注册中心有了刚才那个服务了。这个叫作first-service注册到注册中心,它既能够叫作生产者,也能够被叫作消费者,由于它能够为别的服务提供接口,也能够调用其余服务提供的接口。总之,不管是生产者仍是消费者,它都被叫作Client,要用@EnableDiscoveryClient注解。我不当心点进去这个注解里面,发现还有个参数,boolean autoRegister() default true。这是是否项目已启动,该服务自动注册到注册中心。默认为自动。

除了@EnableDiscoveryClient这个注解之外,还可使用另一个注解@EnableEurekaClient。效果相同,若是是Eureka作注册中心的话,建议使用@EnableEurekaClient,若是是其余注册中心的话(例如阿里的nacos),建议使用@EnableDiscoveryClient。

原理

要想运行起来一个个微服务,造成分布式系统,做为注册中心和其中服务,应该实现一下需求:

  1. 服务们能够顺利注册到注册中心。
  2. 某服务能够经过注册中心知道注册中心有哪些服务可使用,而且这一过程须要保证明时性。
  3. 注册中心须要实时知道服务们是否还存活。

一个服务client注册到注册中心eureka,该client的信息会被存在一个Map中,实现了第一步。同时,client会拉取一份名单,名单里面有其余注册服务的信息,而且为了保证明时性,每30s会再从注册中心那边拉取一份名单信息,实现了第二步。为了确保注册中心实时知道哪些服务还存活着,须要每一个client,每隔一段时间(默认30s)向注册中心发送一个心跳,告诉注册中心,我还在,注册中心那份名单拿上还会记录着这个client还能够用,实现了第三步。

先看一眼注册中心,也就是服务端Service,有个启动引导类EurekaBootStrap,其中有个方法:

@Override
public void contextInitialized(ServletContextEvent event) {
    try {
        initEurekaEnvironment();
        initEurekaServerContext();
        ServletContext sc = event.getServletContext();
        sc.setAttribute(EurekaServerContext.class.getName(), serverContext);
    } catch (Throwable e) {
        logger.error("Cannot bootstrap eureka server :", e);
        throw new RuntimeException("Cannot bootstrap eureka server :", e);
    }
}

这方法是初始化Eureka方法的,如今我特别想知道注册中心是用什么数据结构存下客户端client信息的,因此我得去找注册中心为客户端client提供的注册接口,因而乎,点进initEurekaServerContext()这个方法看看,有个PeerAwareInstanceRegistry这个接口,再点进去看看,发现了

void register(InstanceInfo info, boolean isReplication);

看下它的实现类

@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() 这个方法用于注册中心是集群的状况,主要是注册完以后,同步该服务给其余eureka节点。

public void register(InstanceInfo registrant, int leaseDuration, boolean isReplication) {
    try {
        read.lock();
        Map<String, Lease<InstanceInfo>> gMap = registry.get(registrant.getAppName());
        REGISTER.increment(isReplication);
        if (gMap == null) {
            final ConcurrentHashMap<String, Lease<InstanceInfo>> gNewMap = new ConcurrentHashMap<String, Lease<InstanceInfo>>();
            gMap = registry.putIfAbsent(registrant.getAppName(), gNewMap);
            if (gMap == null) {
                gMap = gNewMap;
            }
        }
        Lease<InstanceInfo> existingLease = gMap.get(registrant.getId());
        ... 仿佛有好多代码...
    } finally {
        read.unlock();
    }
}

目测 registry 应该就是储存着全部的服务,点一下看其结构。

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

最外层是线程安全的ConcurrentHashMap,key值是registrant.getAppName(),也就是实例中的应用名称 first-service。 里面又是一个ConcurrentHashMap(代码里面是Map接口,但其实确定是ConcurrentHashMap,你能够看gNewMap 对象怎么new的)。里面这个key是registrant.getId()实例id,value 是Lease ,这里面存着服务实例和过时时间什么的。ok,具体注册,今天找到地方,先不看了。

关于client端,须要定时拉取服务名单,定时发送注册中心一个心跳。因此用了两个定时器。

在DiscoveryClient 类中,有个initScheduledTasks() 这个方法,是初始化那两个定时器的,简略代码以下:

private void initScheduledTasks() {
    if (clientConfig.shouldFetchRegistry()) {
        // registry cache refresh timer
        int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds();
        int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound();
        scheduler.schedule(
                new TimedSupervisorTask(
                        "cacheRefresh",
                        scheduler,
                        cacheRefreshExecutor,
                        registryFetchIntervalSeconds,
                        TimeUnit.SECONDS,
                        expBackOffBound,
                        new CacheRefreshThread()
                ),
                registryFetchIntervalSeconds, TimeUnit.SECONDS);
    }

    if (clientConfig.shouldRegisterWithEureka()) {
        int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs();
        int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound();
        logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs);

        // Heartbeat timer
        scheduler.schedule(
                new TimedSupervisorTask(
                        "heartbeat",
                        scheduler,
                        heartbeatExecutor,
                        renewalIntervalInSecs,
                        TimeUnit.SECONDS,
                        expBackOffBound,
                        new HeartbeatThread()
                ),
                renewalIntervalInSecs, TimeUnit.SECONDS);

    } else {
        logger.info("Not registering with Eureka server per configuration");
    }

小结

SpringBoot让集成Eureka很是的简单,本篇提供了快速入门的示例。从此还要考虑到注册中心集群的问题。固然,如今还有更好用的注册中心,阿里的nacos,不只有注册中心的功能,同时还继承了配置中心的功能。了解Eureka工做原理,有助于帮助咱们更好的理解分布式系统中的注册中心,为了未来学习了解其余注册中心提供理论基础。

相关文章
相关标签/搜索