SpringCloud專題之一:Eureka 注冊中心


聲明:

本專題部分理論來自翟永超老師的《Spring Cloud微服務實戰》。建議大家看原書。

開篇

微服務簡單來說是系統架構上的一種設計風格,他的主旨是將一個原本獨立且龐大的系統按照不同的摸塊划分成多個小型的服務,這些小型的服務都在各自獨立的進程中運行,服務之間通過基於HTTP的RESTful API進行通信協作。

由於單體系統部署在一個進程中,往往我們在修改一個很小的功能,上線的時候還需要重啟整個服務,對其他功能的運行也會產生影響。同時,單體應用中的不同功能模塊對並發量、消耗的資源類型也各不相同,使得單體系統很難對服務器資源的合理里用。

在微服務的架構中,可以將不同的功能模塊拆分成多個不同的服務,這些服務都能夠獨立部署和擴展,由於每個服務都運行在自己的進程內,在部署上,每個服務的更新不會影響到其他服務的運行。同時,我們可以更准確的為每個服務的性能進行評估,通過配合協作可以更容易發現系統的瓶頸,將服務器壓力大的系統動態添加新的服務部署,並不需要修改代碼。

Spring Cloud簡介

Spring Cloud是一個基於Spring Boot實現的微服務架構開發工具,它為微服務中設計的配置管理、服務治理、智能路由、微代理、控制總線、決策競選等提供了一種簡單的開發方式。

Spring Cloud Eureka

Spring Cloud Eureka是Spring Cloud Netflix微服務套件中的一部分,它基於Netflix Eureka做了二次封裝,主要服務完成微服務架構中的服務治理功能,Spring Cloud通過為Eureka增加了Spring Boot風格的自動化配置,我們只需要簡單的引入依賴和注解配置就能讓Spring Boot構建微服務應用輕松地與Eureka服務治理體系進行整合。

Eureka服務端,也稱服務注冊中心。同其他的服務注冊中心一樣,支持高可用配置,它依托於強一致性提供良好的服務實例可用性,可以應對多種不同的故障場景。如果Eureka以集群模式部署,當集群中有分片出現故障時,那么Eureka就轉入自我保護模式。它允許在分片出現故障起間繼續提功服務的發現和注冊,當故障分片恢復運行時,集群中的其他分片會把他們的狀態再此同步回來。

Eureka客戶端,主要處理服務的注冊與發現,客戶端服務通過注解和參數配置的方式,嵌入在客戶端應用程序的代碼中,在應用程序運行時,Eureka客戶端向注冊中心注冊自身提供的服務,並周期性的發送心跳來更新它的服務續約。同時,它也能從服務端查詢當前注冊的服務信息並把他們緩存到本地並周期性得刷新服務狀態。

代碼實踐

服務端

創建一個基礎的Spring Boot並引入依賴:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <!-- 我這里使用的spring boot的版本是2.3.10.RELEASE,最開始使用的是2.4.5,但是maven打包會報錯-->
    <version>2.3.10.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<!-- eureka服務端的依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

啟動類,通過@EnableEurekaServer注解啟動一個服務注冊中心提供給其他應用進行注冊。

/**
 * @className: EurekaServerApplication
 * @description: eureka服務注冊中心啟動類
 * @author: charon
 * @create: 2021-05-17 21:55
 */
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}

}

配置,服務注冊中心也會將自己作為客戶來嘗試注冊自己,所以要禁用他的客戶端注冊行為,配置內容如下:

server.port=9001

eureka.instance.hostname=localhost
# 代表不向注冊中心注冊自己
eureka.client.register-with-eureka=false
# 由於注冊中心的職責就是維護服務實例,並不需要去檢索服務,所以也設置為false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/

啟動之后在瀏覽器訪問如下圖所示,在instances currently registered with eureka這一欄是空的,說明還沒有注冊任何服務。

客戶端

引入eureka客戶端的依賴:

<!--eureka客戶端的依賴-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web依賴-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

客戶端啟動類,通過@EnableDiscoveryClient注解,激活Eureka中的EnableDiscoveryClient實現。

@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaClientApplication.class, args);
	}

}

controller類:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @className: HelloController
 * @description: 測試controller類
 * @author: charon
 * @create: 2021-05-17 22:46
 */
@RestController
public class HelloController {

    /**
     * 日志記錄類
     */
    private final Logger logger = LoggerFactory.getLogger(getClass());

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

    @Value("${spring.application.name}")
    private String instanceName;

    @RequestMapping("/sayHello")
    public String sayHello(){
        logger.info("你好,服務名:{},端口為:{}",instanceName,host);
        return "你好,服務名:"+instanceName+",端口為:"+host;
    }
}

配置

server.port=9002

spring.application.name=hello-server

eureka.client.serviceUrl.defaultZone=http://localhost:9001/eureka/

啟動之后重新訪問eureka的信息面板,在instances currently registered with eureka這一欄可以看到我們hello-server的服務注冊上了。

在服務注冊中心的控制台中,可以看到這樣的輸出,表示名為hello-service的服務被注冊成功了。

2021-05-17 23:10:48.673  INFO 3156 --- [nio-9001-exec-5] c.n.e.registry.AbstractInstanceRegistry  : Registered instance HELLO-SERVER/DESKTOP-4LG5AMO:hello-server:9002 with status UP (replication=false)

通過瀏覽器訪問http://localhost:9002/sayHello,直接向該服務發起請求,在控制台中可以看到如下的輸出信息:

2021-05-17 23:15:24.216  INFO 3820 --- [nio-9002-exec-1] c.c.e.controller.HelloController         : 你好,服務名:hello-server,端口為:9002

配置高可用的注冊中心

在微服務架構這樣的分布式環境中,需要充分考慮發生故障的情況,所以在生產環境中,必須對各個組件進行高可用的部署,對於注冊中心也是一樣的。在Eureka的服務治理設計中,所有節點既是服務提供方,也是服務消費方。上面在單節點的配置中,配置過如下兩個參數,讓服務注冊中心不注冊自己:

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

Eureka服務的高可用其實就是將自己作為服務向其他服務注冊中心注冊自己,這樣就可以形成一組互相注冊的服務注冊中心,以實現服務清單的相互同步,達到高可用的效果。

下面我們來構建一個雙節點的服務注冊中心集群:

1.創建application-server1.properties作為EurekaServer1服務中心的配置,並將serviceUrl指向EurekaServer2:

server.port=9001

spring.application.name=eureka-server
eureka.instance.hostname=eureka-server1
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.serviceUrl.defaultZone=http://eureka-server2:9002/eureka/

2.創建application-server2.properties作為EurekaServer2服務中心的配置,並將serviceUrl指向EurekaServer1:

server.port=9002

spring.application.name=eureka-server
eureka.instance.hostname=eureka-server2
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
eureka.client.serviceUrl.defaultZone=http://eureka-server1:9001/eureka/

3.在配置文件中配置路徑的轉換,由於我這是windows上,所有我這的路徑為:C:\Windows\System32\drivers\etc\hosts.(linux上的路徑為:/etc/hosts)

127.0.0.1 eureka-server1
127.0.0.1 eureka-server2

4.項目打包,通過spring.profiles.active屬性來分別啟動eureka-server1和eureka-server2.

java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=server1
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=server2

如下圖所示,在9001的server1上可以看到DS Replicas為eureka-server2的集群高可用服務,同理,在9002的server2上也可以看到eureka-server1的服務。

5.修改上面的eureka客戶端的配置,將注冊中心的地址指向上面啟動的兩個服務注冊中心的地址

eureka.client.serviceUrl.defaultZone=http://eureka-server1:9001/eureka/,http://eureka-server2:9002/eureka/

啟動eureka-client的服務,通過訪問http://localhost:9001/和http://localhost:9002/;可以觀察到HELLO-SERVER服務被同時注冊到了eureka-server1和eureka-server2上,此時若斷開其中一個注冊中心的服務,那么在另一個注冊中心的服務依然可以訪問到HELLO-SERVER,從而實現了服務注冊中心的高可用。

搭建服務消費者

上面我們已經搭建了服務注冊中心(包括單節點和高可用兩種模式)和服務提供者,下面來構建一個服務消費者,主要有兩個任務:發現服務和消費服務。在這里使用OpenFeign來完成服務消費。OpenFeign是一種聲明式,模板化的HTTP請求遠程服務調用工具,具體的我們會在后面的專題中講解。

導入依賴配置:

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

properties中的配置:

server.port=9004

spring.application.name=eureka-customer

eureka.client.serviceUrl.defaultZone=http://eureka-server1:9001/eureka/,http://eureka-server2:9002/eureka/

在主類中,通過@EnableFeignClients開啟feign。

@EnableFeignClients   //feign在應用程序中默認是不開啟的
@SpringBootApplication
public class EurekaCustomerApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaCustomerApplication.class, args);
	}

}

controller請求類:

@RestController
public class CustomerController {

    @Autowired
    private CustomerSerivce serivce;

    @RequestMapping("/sayHello")
    public String sayHello(){
       return serivce.invokeSayHello();
    }
}

service實現類:

/**
 * 接口
 */
public interface CustomerSerivce {
    String invokeSayHello();
}

/**
 * 實現類
 */
@Service
public class CustomerServiceImpl implements CustomerSerivce {

    @Autowired
    private CustomerFeign feign;
    @Override
    public String invokeSayHello() {
        return feign.sayHello();
    }
}

feign接口類:

@FeignClient("HELLO-SERVER")   //使用的value參數,表示從HELLO-SERVER這個服務中調用服務
public interface CustomerFeign {

    /**
     * 要求:
     *    返回值要對應,方法名隨意,參數值要對應
     *    方法上添加SpringMVC的注解
     * @return
     */
    @RequestMapping("/sayHello")
    String sayHello();
}

如上圖,我們在瀏覽器上調用eureka-customer這個服務的接口,能直接訪問到eureka-client服務的接口上去。

Eureka詳解

前面我們已經通過一個簡單的服務注冊和發現的示例,構建了一個Eureka服務治理體系中的三個核心角色:服務注冊中心,服務提供者以及服務消費者。下面我們來詳細了解一下Eureka節點間的通信以及一些配置吧。

服務提供者

服務注冊

服務提供者在啟動的時候會通過發送Rest請求的方式將自己注冊到Eureka Server上,同時帶上了自身服務的一些元數據信息。Eureka Server在接受到這個Rest請求之后,將這些元數據信息存儲在一個雙層結構的Map中,其中第一層為Key是服務名,第二層是具體的服務實例名。

在服務注冊時,需要確認一下,eureka.client.register-with-eureka這個參數是否為true,若為false將不會啟動注冊操作。

服務同步

服務同步,顧名思義就是服務注冊中心之間因相互注冊為服務,當服務提供者發送注冊請求到一個服務注冊中心上時,他會將改請求轉發給集群中其他的注冊中心上,從而實現注冊中心之間的服務同步。

服務續約

在注冊完成后,服務提供者會維護一個心跳用來告訴Eureka Server 我還活着。以防止Eureka Server的“剔除任務”將該服務實例從服務列表中排除出去。

# 用於定義服務續約的調用間隔。默認為30S
eureka.instance.lease-renewal-interval-in-seconds=30
# 用於定義服務失效的時間,默認為90S
eureka.instance.lease-expiration-duration-in-seconds=90

服務消費者

獲取服務

當我們啟動服務消費者的時候,他也會發送一個Rest請求給服務注冊中心,來獲取上面注冊的服務清單。同時為了性能考慮,Eureka Server會維護一份只讀的服務清單來返回給客戶端,緩存清單的時間默認為30S一次。

# 獲取服務時服務消費者的基礎,所以這個值默認為true
eureka.client.fetch-registry=true
# 緩存清單的更新時間。默認為30S
eureka.client.registry-fetch-interval-seconds=30

服務調用

服務消費者在獲取服務清單后,通過服務名可以獲得具體提供服務的實例名和該實例的元數據信息。

對於訪問實例的選擇,Eureka中有RegionZone的概念。一個Region中可以包含多個Zone,每個服務客戶端需要被注冊到一個Zone中,所以每個客戶端對應一個Region和Zone。在進行服務調用的時候,優先訪問同處一個Zone中的服務提供方,若訪問不到,就訪問其他的Zone。

服務下線

在系統運行過程中必然會面臨關閉或重啟服務的情況,在服務關閉期間,我們自然不希望客戶端會繼續調用關閉了的實例。所以在客戶端程序中,當服務實例進行正常的關閉操作時,他會觸發一個服務下線的Rest請求給Eureka服務,告訴服務注冊中心要下線了,服務端在接受到請求之后,將該服務狀態置為下線,並把下線事件傳播出去。

服務注冊中心

失效剔除

有些時候,我們的服務並不時正常下線的,可能是由於網絡故障等原因是的服務不能正常工作,而服務注冊中心並未收到“服務下線”的請求。為了從服務列表中將這些無法提供服務的實例剔除,Eureka Server 在啟動的時候會創建一個定時任務,每隔一點時間(默認為60S)將當前清單中超時(默認為90S)沒有續約的服務剔除出去。

自我保護

很多時候我們在Eureka注冊中心經常會看到上面圖中的紅色警告,這就是觸發了Eureka Server的自我保護機制。服務注冊到Eureka Server之后,會維護一個心跳連接,告訴Eureka服務自己還活着,Eureka Server在運行期間,會統計心跳失敗的比例在15分鍾之內是否低於85%,如果出現低於的情況,Eureka Server將會把當前的實例注冊信息保護起來,讓這些服務不會過期,盡可能地保護這些注冊信息。但是如果在這期間獲取到實際不存在地服務實例,會出現調用失敗地情況,所以客戶端必須要有容錯機制,比如可以使用請求重試,斷路器等機制。

可以使用下面地參數來關閉保護機制,以確保注冊中心將不可用地實例正確剔除.

eureka.server.enableself-preservation=false

源碼解析

首先我們來看看,為什么在eureka的服務端添加了@EnableEurekaServer這個注解就可以了呢?@EnableEurekaServer到底有什么魔力呢?

@EnableEurekaServer注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {

}

@Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration {

	@Bean
	public Marker eurekaServerMarkerBean() {
		return new Marker();
	}

	class Marker {

	}

}

可以看到,@EnableEurekaServer通過@impprt注解導入EurekaServerMarkerConfiguration這個類,EurekaServerMarkerConfiguration這個是主要就是把一個Marker類變成Spring的Bean。這個類並沒有什么實際的功能,只是作為Bean存在,觸發自動配置,以達到一個開關的效果。

我們都知道Spring Boot的組件都有一個自動配置類以完成自動裝配工作。Eureka服務的自動配置類就是EurekaServerAutoConfiguration類。關於Spring Boot如何實現自動裝配的,可以查看我之前的博客《SpringBoot自動裝配源碼》。

下面來看看EurekaServerAutoConfiguration這個類到底做了些什么呢?

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class,
		InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
}

首先需要注意的是@ConditionalOnBean這個注解,它的作用是判斷EurekaServerMarkerConfiguration.Marker這個Bean是否存在。如果存在才會解析這個自動配置類,從而呼應了@EnableEurekaServer這個注解。

我們都知道Eureka的注冊,續約,集群同步都是通過一個restful風格的基於HTTP的rpc調用框架來實現的,Eureka使用它來為客戶端提供遠程服務,所以應該有一個類似於Spring mvc的controller類--ApplicationResource類。jerseyApplication()這個方法通過掃描@Path和@Provider標簽,然后封裝成beanDefinition封裝到Application的set容器里,通過filter過濾器來過濾url進行映射到對象的Controller上。掃描對應包下的Resource並添加到ResourceConfig當中。

@Bean
	public javax.ws.rs.core.Application jerseyApplication(Environment environment,
			ResourceLoader resourceLoader) {

		ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(
				false, environment);

		// Filter to include only classes that have a particular annotation.
		provider.addIncludeFilter(new AnnotationTypeFilter(Path.class));
		provider.addIncludeFilter(new AnnotationTypeFilter(Provider.class));

		// Find classes in Eureka packages (or subpackages)
		Set<Class<?>> classes = new HashSet<>();
		for (String basePackage : EUREKA_PACKAGES) {
			Set<BeanDefinition> beans = provider.findCandidateComponents(basePackage);
			for (BeanDefinition bd : beans) {
				Class<?> cls = ClassUtils.resolveClassName(bd.getBeanClassName(),
						resourceLoader.getClassLoader());
				classes.add(cls);
			}
		}

		// Construct the Jersey ResourceConfig
		Map<String, Object> propsAndFeatures = new HashMap<>();
		propsAndFeatures.put(
				// Skip static content used by the webapp
				ServletContainer.PROPERTY_WEB_PAGE_CONTENT_REGEX,
				EurekaConstants.DEFAULT_PREFIX + "/(fonts|images|css|js)/.*");

		DefaultResourceConfig rc = new DefaultResourceConfig(classes);
		rc.setPropertiesAndFeatures(propsAndFeatures);

		return rc;
	}

再來看看EurekaServerAutoConfiguration這個類通過Import注解引入的 EurekaServerInitializerConfiguration類。這個類實現了Lifecycle接口里的start()方法,在start()方法里進行初始化,初始化過程調用了EurekaServerBootstrap的contextInitialized方法,初始化了EurekaEnvironment(設置各種配置)和EurekaServerContext(Eureka的上下文),在initEurekaServerContext()方法中,主要做了兩件事:

  1. 從相鄰的集群節點當中同步注冊信息
  2. 注冊一個統計器

服務注冊

前面說到Eureka是使用jersey來對外提供restful風格的rpc調用的,並通過一個類似於Spring mvc的controller的Resource類來實現的。

ApplicationResource類中的AddInstance方法將作為服務端注冊服務的入口。

@POST 
@Consumes({"application/json", "application/xml"}) 
public Response addInstance(InstanceInfo info, @HeaderParam(PeerEurekaNode.HEADER_REPLICATION) String isReplication) { 		//... 
    registry.register(info, "true".equals(isReplication)); 
    return Response.status(204).build(); 
}

跟進register方法:

@Override 
public void register(final InstanceInfo info, final boolean isReplication) { 
    // ... 
	// 1.注冊實例信息,注冊信息被保存在一個Map中,服務注冊就是往map中放置節點信息,取消就是往map中刪除節點信息
    super.register(info, leaseDuration, isReplication); 
    // 2.復制到其他節點通過循環遍歷向所有的Peers節點注冊,最終執行類PeerEurekaNodes的register()方法,該方法通過執行一個任務向其他節點同步注冊該注冊信息
    replicateToPeers(Action.Register, info.getAppName(), info.getId(), info, null, isReplication); 
}

存放注冊信息的Map(AbstractInstanceRegistry 的一個成員變量)結構:

private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry
            = new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
{ 
    "商品服務": { // 服務名 
        "實例的唯一ID": { // 實例標識符 
            "lease": { // 持有實例信息 
                "instanceInfo": { // 實例信息 
                    "appName": "商品服務", 
                    "instanceId": "實例的唯一ID", 
                    "ipAddr": "IP地址", 
                    "port": "調用端口" 
                } 
            } 
        } 
    }    
} 

服務同步

上面提到了register方法做了兩件事,注冊服務及復制服務到其他節點。下面來看看復制服務實例信息到其他節點。PeerAwareInstanceRegistryImpl#replicateToPeers()。

private void replicateToPeers(Action action, String appName, String id,
                              InstanceInfo info /* optional */,
                              InstanceStatus newStatus /* optional */, boolean isReplication) {
    Stopwatch tracer = action.getTimer().start();
    try {
        if (isReplication) {
            numberOfReplicationsLastMin.increment();
        }
        // 如果本次register操作本身就是復制,就不再復制到其他節點了,避免循環注冊,造成死循環
        if (peerEurekaNodes == Collections.EMPTY_LIST || isReplication) {
            return;
        }
	   // 遍歷所有節點
        for (final PeerEurekaNode node : peerEurekaNodes.getPeerEurekaNodes()) {
            // 如果是當前節點,則直接跳過
            if (peerEurekaNodes.isThisMyUrl(node.getServiceUrl())) {
                continue;
            }
            // 復制操作,在這個方法內部,除了注冊以外還有心跳檢測,取消等需要同步到其它節點的操作。跟進register方法,復制             // 實例信息被構造成了一個任務丟給了batchingDispatcher去異步執行,如果失敗將會重試。
            replicateInstanceActionsToPeers(action, appName, id, info, newStatus, node);
        }
    } finally {
        tracer.stop();
    }
}

Eureka Server會將register、cancel、heartbeat等操作從一個節點同步發送到其它節點,從而實現了復制的功能。Eureka和Zookeeper不一樣,它是遵循ap的,所以采用了最終一致性,並沒有像Zookeeper一樣選擇強一致。Eureka Server之間的維持最終一致性的細節點還是很多的,比如失敗重試、超時、心跳、實例的版本號、同一個節點的鎖控制等等。

獲取服務

ApplicationsResource#getContainers()方法。獲取服務信息列表其實就是從registry當中獲取Applications,然后做一次序列化,最后通過HTTP響應回去。

服務續約

通過InstanceResource#renewLease()方法實現,比較簡單,就是更新對應的Lease對象的lastUpdateTimestamp屬性。

public void renew() {
    lastUpdateTimestamp = System.currentTimeMillis() + duration;
}

服務剔除及失效剔除

通過InstanceResource#cancelLease()方法實現,最終在AbstractInstanceRegistry#internalCancel()中,通過leaseToCancel = gMap.remove(id);方法將對應的實例信息從注冊信息中remove掉,然后將對應的Lease對象給個過期時間

public void cancel() {
    if (evictionTimestamp <= 0) {
        evictionTimestamp = System.currentTimeMillis();
    }
}

以上客戶端主動提請求服務剔除的操作,還有一個專門的定時任務進行服務的輪詢對比過期時間的剔除操作。在 EurekaServerInitializerConfiguration這個類中初始化initEurekaServerContext()方法中,執行了registry.openForTraffic(applicationInfoManager, registryCount);最后一句調用了AbstractInstanceRegistry#postInit()方法,在此方法里開啟了一個每60秒調用一次EvictionTask#evict()的定時器。最后都是調用的同樣的邏輯AbstractInstanceRegistry#internalCancel()。

自我保護

客戶端長時間不發送續約,服務端默認每隔一分鍾會進行一次服務剔除,所以在上面的失效剔除定時器中,有一個isLeaseExpirationEnabled()方法。

/**
 * 期望 最大 每分鍾 續租 次數。 計算公式  當前注冊的應用實例數 x 2
 */
protected volatile int expectedNumberOfRenewsPerMin ;
/**
 * 期望 最小 每分鍾 續租 次數。 計算公式 expectedNumberOfRenewsPerMin * 續租百分比( eureka.renewalPercentThreshold )
 */
protected volatile int numberOfRenewsPerMinThreshold ;
@Override
public boolean isLeaseExpirationEnabled() {
    if (!isSelfPreservationModeEnabled()) {
        // The self preservation mode is disabled, hence allowing the instances to expire.
        return true;
    }
    return numberOfRenewsPerMinThreshold > 0 && getNumOfRenewsInLastMin() > numberOfRenewsPerMinThreshold;
}

更多關於@EnableDiscoveryClient注解的源碼,可以查看這篇博客:https://blog.csdn.net/qq296398300/article/details/80332989 。這和翟永超老師地《Spring Cloud微服務實戰》的Eureka源碼解讀部分的內容差不多。我這里就不寫了。

文章:

https://blog.csdn.net/zzti_erlie/article/details/104088914

https://blog.csdn.net/u011320740/article/details/106313122


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM