SpringCloudConfigServer 構成の更新最適化スキーム

前回の記事「SpringCloudConfigServer構成センターの使用と更新の詳細な説明」では、 Spring Cloudネイティブ構成センターのデプロイメントスキームと構成変更時の更新スキームについて紹介しました。
この記事を通じて、次のことがわかります。

  • 最初のオプションでは、単一サービスのすべてのインスタンスを同時に更新することはできません
  • 2 番目の解決策は、メッセージ ミドルウェア (RabbitMQ または Kafka) に大きく依存しており、私のテストでは、クライアントの逆シリアル化例外によって障害が発生する状況にも遭遇しました。

上記の 2 つの理由に基づいて、比較的軽量な構成更新ソリューションがあればと考えていますが、よく考えてみると、クライアントが定期的にポーリングを行い、構成が更新された場合に独自のインターフェイスを呼び出して構成更新を完了することは可能でしょうか/actuator/refresh?
実現可能性を検証した後、問題はありません。

  • Config-Server は@RestControllerインターフェイスの追加をサポートします
  • クライアントは独自の/actuator/refreshインターフェイスを呼び出します。つまり、http は自分自身にリクエストを送信し、localhost: port を呼び出してインターフェイスに従って
    ログを呼び出し、対応するクラスのポイントをブレークして追跡し、インターフェイスがクラス、呼び出しはクラスメソッドの呼び出しであり、クラスも Bean として登録されます。さらに単純で、ジョブを直接定義してこのBeanを呼び出すだけです。/actuator/refreshorg.springframework.cloud.endpoint.RefreshEndpointorg.springframework.cloud.context.refresh.ConfigDataContextRefresherrefresh()
    refresh()

カスタム ソリューションの原理の紹介

カスタム実装のポーリング メカニズム、一般的な手順は次のとおりです。

  • 構成サーバー端末:
    • 開発者が最新の構成更新時間を変更できるように管理 API を提供します。
    • クライアントが最新の構成更新時刻を定期的に取得し、構成をリロードして更新するかどうかを決定するためのクライアント API を提供します。
  • 構成クライアント端末:
    • Config-Server 側の API を定期的にポーリングして、最新の構成更新時間を取得します。
    • 前回の更新時刻よりも長い場合は、構成の更新を実行します。

全体的な更新フローチャートは次のとおりです。
ここに画像の説明を挿入

コード

構成サーバー端末

1. グローバル時間と各アプリケーションの時間を定義し、読み取りおよび書き込みメソッドを提供します。コア コードは次のとおりです。

@Service
public class RefreshService implements InitializingBean {
    
    
    private LocalDateTime globalLastUpdateTime = LocalDateTime.now();

    private Map<String, LocalDateTime> mapAppLastUpdateTimes = new HashMap<>();

    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-DD HH:mm:ss.SSS");

    /**
     * 客户端使用:获取指定app的配置更新时间
     */
    public String getLastUpdateTime(String appName) {
    
    
        appName = formatAppName(appName);

        LocalDateTime time = null;
        if (StringUtils.hasLength(appName)) {
    
    
            time = mapAppLastUpdateTimes.get(appName);
        }
        if (time == null) {
    
    
            time = globalLastUpdateTime;
        }
        return time.format(formatter);
    }

    /**
     * 管理端使用:设置指定app的配置更新时间,以触发该app更新配置
     */
    public void setLastUpdateTime(String appName, LocalDateTime time) {
    
    
        appName = formatAppName(appName);

        if (!StringUtils.hasLength(appName))
            throw new RuntimeException("应用名不能为空");
        if (time == null)
            time = LocalDateTime.now();

        mapAppLastUpdateTimes.put(appName, time);
        log.info("{}最后配置更新时间修改为:{}", appName, time);
    }

    /**
     * 管理端使用:设置全局配置更新时间,触发所有app的配置更新
     */
    public void setGlobalLastUpdateTime(LocalDateTime time) {
    
    
        if (time == null)
            time = LocalDateTime.now();
        globalLastUpdateTime = time;
        mapAppLastUpdateTimes.clear();
        log.info("全局最后配置更新时间修改为:{}", time);
    }

    private String formatAppName(String appName) {
    
    
        if (appName == null)
            return "";
        return appName.trim().toLowerCase();
    }

2. 外部APIの提供

@RestController
public class DefaultController {
    
    
    private final RefreshService refreshService;

    public DefaultController(RefreshService refreshService) {
    
    
        this.refreshService = refreshService;
    }

    /**
     * 查看指定应用的的最近配置更新时间
     *
     * @param appName 应用
     * @return 时间
     */
    @GetMapping("lastTime")
    public String getTime(@RequestParam(required = false) String appName) {
    
    
        return refreshService.getLastUpdateTime(appName);
    }

    /**
     * 修改指定应用的最近配置更新时间为now
     *
     * @param appName 应用
     * @return 更新后时间
     */
    @PostMapping("lastTime")
    public String setTime(@RequestParam(required = false) String appName) {
    
    
        refreshService.setLastUpdateTime(appName, LocalDateTime.now());
        return getTime(appName);
    }

    /**
     * 修改所有应用的最近配置更新时间为now
     *
     * @return 更新后时间
     */
    @PostMapping("lastAllTime")
    public String setGlobalTime() {
    
    
        refreshService.setGlobalLastUpdateTime(LocalDateTime.now());
        return getTime(null);
    }
}

構成クライアント端末

1.構成更新時間を取得するためのfeignクラスを定義します。

// url就是上面的Config-Server的url地址,可以写入yml配置
@FeignClient(name = "config-server", url = "http://localhost:8999")
public interface FeignConfigServer {
    
    
    @GetMapping("/lastTime")
    String getTime(@RequestParam String appName);
}

2. スケジュールされたジョブは、Config-Server API をポーリングし、構成を更新するかどうかを決定します。

@Component
@ConditionalOnProperty(value = "spring.cloud.config.refresh.enabled", havingValue = "true", matchIfMissing = true)
@RequiredArgsConstructor
@Slf4j
public class ConfigsRefresh implements InitializingBean {
    
    

    private final FeignConfigServer feignConfigServer;
    private final org.springframework.cloud.context.refresh.ConfigDataContextRefresher refresher;

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

    private LocalDateTime lastUpdateTime = LocalDateTime.now();
    private static DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-DD HH:mm:ss.SSS");
    private static final Random RANDOM = new Random();

    /**
     * 每分钟检查并刷新
     */
    @Scheduled(cron = "0 * * * * *")
    public void doRefresh() {
    
    
        LocalDateTime serverUpdateTime = getServerLastUpdateTime(appName);
        if (serverUpdateTime.compareTo(lastUpdateTime) <= 0) {
    
    
            return;
        }
        log.info("本地更新时间较小:{} < {} 需要刷新配置", lastUpdateTime, serverUpdateTime);
        sleep();

        Set<String> keys = refresher.refresh();

        lastUpdateTime = serverUpdateTime;
        log.info("更新列表 {}", keys);
    }

    // 获取当前应用,配置中心的配置最近刷新时间
    private LocalDateTime getServerLastUpdateTime(String appName) {
    
    
        try {
    
    
            String serverUpdateTime = feignConfigServer.getTime(appName);
            // log.info("配置中心最近更新: {}", serverUpdateTime);
            if (StringUtils.hasLength(serverUpdateTime)) {
    
    
                return LocalDateTime.parse(serverUpdateTime, formatter);
            }
        } catch (Exception exp) {
    
    
            log.error("取配置中心更新时间出错:", exp);
        }
        return LocalDateTime.MIN;
    }

    private void sleep() {
    
    
        // 随机休眠,避免所有pod同时刷新配置
        int selectSecond = RANDOM.nextInt(10);
        try {
    
    
            Thread.sleep(selectSecond * 1000L);
        } catch (InterruptedException e) {
    
    
            log.error("休眠出错", e);
        }
    }

    @Override
    public void afterPropertiesSet() {
    
    
        lastUpdateTime = LocalDateTime.now();
        log.info("配置本地更新时间:{}", lastUpdateTime);
    }
}

デモコードリファレンス:

Config-Server端デモ| 構成クライアント端末のデモ

実際の作業手順を簡単に説明

Config-Server の URL が http://localhost:8999 であるとします。

単一アプリの更新

構成を更新する必要があるアプリケーションを想定します。 spring.application.name= cb-admin
1. 構成を変更し、git に送信します (正しいブランチに送信されることに注意してください)
2. 構成センターの API を呼び出して、呼び出し方法:
curl -X POST http://localhost:8999/lastTime?appName= cb-admin
3. OK、約 1 分で、アプリケーションは構成の更新を完了します。

すべてのアプリが更新される

すべてのアプリを同時に更新したい場合は、別の API を呼び出す必要があります:
curl -X POST http://localhost:8999/lastAllTime

構成の最新更新時刻を表示する

ブラウザでアクセス:
http://localhost:8999/lastAllTime

未解決の問題

テスト中に Config-Server は https を使用しましたが、/actuator/refresh起動時に構成は正常にロードできましたが、更新できなかったため、構成を更新できないことがわかり、1 ~ 2 日後に新しい空のプロジェクトが作成されました。 、SSL 証明書を更新できないことがわかりました。検証の問題です。
しかし、これは起動時に同じ構成ロードプロセスでもあり、これは非常に奇妙です。追跡プロセス中に、Config-Client がSpring の Bean ではなく
独自の内部 new を使用していることが判明したため、Bean メソッドを再定義して ssl 証明書の検証を無視することは不可能です。 git の ssl 証明書認証を無視するように設定されましたが、クライアントの同様の設定が見つからなかったため、この問題は最初に無視され、コードはそれ以上追跡されませんでした。後で時間があるときに調べてみましょう。まず http メソッドを使用してこれに対処します。また、内部呼び出しによってパフォーマンスもある程度節約されます。RestTemplateRestTemplate
spring.cloud.config.server.git.skip-ssl-validation: true

おすすめ

転載: blog.csdn.net/youbl/article/details/130888134