Spring Cloud Alibaba實現服務的無失真下線功能

2023-03-13 15:01:16

1、背景

最近用到了Spring Cloud Alibaba開發微服務,在開發的過程中發現,當我們的服務上線下線的時候,我們的Spring Cloud Gateway需要一段時間才能感知到,那麼有沒有辦法能夠讓服務立即感知到呢?答案是可以的

此種實現方式是我自己記錄下,目前未在生產環境中使用,此處記錄是為了以後遇到此種場景,可以找到一種解決方案

2、解決方案

2.1 找到通過負載均衡元件獲取可用服務資訊的地方


通過一頓Debug之後,從上圖中可知,我們獲取到可用的服務範例資訊,會從快取中獲取,那麼如果當服務下線後,我們清空這個服務的快取的資訊,那麼下次在獲取這個服務的資訊就是最新的了,那麼問題就解決了。

2.2 解決思路

  1. 服務提供方或消費方,同時監聽 Spring Cloud Config中的某個組態檔,比如lossless檔案
  2. 服務下線時, 第一步:nacos上將自身這個範例下線。 第二步: 通過api更新lossless 檔案中的內容,記錄服務下線的服務名加上時間戳
  3. 消費者或閘道器,監聽到lossless組態檔的變更,獲取到服務名,然後將這個服務名對應的快取清空

3、部分實現程式碼

3.1 引入jar

<dependencies>
	<!-- 服務發現 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- nacos 設定 -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <!-- 引入這個是為了使 @NacosConfigListener 註解生效 -->
    <dependency>
        <groupId>com.alibaba.nacos</groupId>
        <artifactId>nacos-spring-context</artifactId>
        <version>1.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!-- 負載均衡元件 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <!-- 使bootstrap.yaml中的設定生效 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-bootstrap</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

3.2 編寫服務下線方法

@Component
@Slf4j
public class LosslessOfflineApi {

    @Resource
    private NacosConfigManager nacosConfigManager;
    @Resource
    private NacosServiceManager nacosServiceManager;
    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    /**
     * 服務下線
     *
     * @throws NacosException NacosException
     */
    public void offlineService() throws NacosException {
        log.info("觸發服務下線 serviceName:[{}]", nacosDiscoveryProperties.getService());
        nacosServiceManager.nacosServiceShutDown();
        nacosConfigManager.getConfigService()
                .publishConfig(NacosConstant.DATA_ID, NacosConstant.GROUP,
                        nacosDiscoveryProperties.getService() + NacosConstant.SPLIT + System.currentTimeMillis());
    }
}

此處需要注意的是: 使用nacosConfigManager.getConfigService().publishConfig 釋出設定,此處更新設定中的 服務名+時間戳,如果只是更新服務名,則可能不會觸發監聽事件。

3.3 監聽設定變更,清除服務快取

3.3.1 使@NacosConfigListener註解生效

1、引入設定

<dependency>
    <groupId>com.alibaba.nacos</groupId>
    <artifactId>nacos-spring-context</artifactId>
    <version>1.1.1</version>
</dependency>

2、開啟註解

@Configuration
@EnableNacos(globalProperties =
	@NacosProperties(serverAddr = "${spring.cloud.nacos.config.server-addr}")
)
public class NacosConfiguration {
}

3、註解生效參考檔案

https://github.com/nacos-group/nacos-spring-project
https://github.com/alibaba/spring-cloud-alibaba/issues/458

3.3.2 監聽設定、清除快取

@Component
@RequiredArgsConstructor
@Slf4j
public class ListenerConfigChange {
    @Resource
    private DefaultLoadBalancerCacheManager defaultLoadBalancerCacheManager;
    @Resource
    private NacosServiceManager nacosServiceManager;
    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;
    @Resource
    private NacosServiceDiscovery nacosServiceDiscovery;
    @Resource
    private NacosConfigManager nacosConfigManager;

    @NacosConfigListener(groupId = GROUP, dataId = DATA_ID)
    public void configChange(String config) {
        log.warn("==>接收到 無失真服務下線 設定變更:[{}]", config);
        String serviceName = config.split(SPLIT)[0];
        log.info("需要無失真下線的服務名:[{}]", serviceName);

        Cache cache = defaultLoadBalancerCacheManager.getCache(SERVICE_INSTANCE_CACHE_NAME);
        if (null != cache) {
            cache.evict(serviceName);
            log.info("失效serviceName:[{}]的快取", serviceName);
        }
    }
}

4、實現

4.1 服務準備

服務 提供api 備註
nacos-lossless-gateway 9001 /consumer/** 路由到consumer服務
nacos-feign-consumer 9002 /fetchProviderServerInfo 通過feign介面,獲取provider服務的ip和port
nacos-provider-9003 9003 /shutdown 從nacos server上下線服務,釋出設定變更
nacos-provider-9004 9004 /shutdown 從nacos server上下線服務,釋出設定變更

解釋:
1、通過gateway存取 http://localhost:9001/consumer/fetchProviderServerInfo將會返回provideripport資訊。
2、http://localhost:9003/shutdown 將會將自己從nacos server下線,並且操作 nacos config,變更組態檔的內容。
3、gatewayconsumer監聽到設定變更,更新服務的快取,從而下次存取,不會存取到這個下線的服務。

4.2 演示

5、完整程式碼

https://gitee.com/huan1993/spring-cloud-alibaba-parent/tree/master/nacos-lossless-offline

6、參考連結

1、https://github.com/nacos-group/nacos-spring-project
2、https://github.com/alibaba/spring-cloud-alibaba/issues/458