日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

【架构师视角系列】Apollo配置中心之Client端(二)

發布時間:2024/1/21 windows 44 coder
生活随笔 收集整理的這篇文章主要介紹了 【架构师视角系列】Apollo配置中心之Client端(二) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原創文章,轉載請標注。https://www.cnblogs.com/boycelee/p/17978027

目錄
  • 聲明
  • 配置中心系列文章
  • 一、客戶端架構
    • 1、Config Service職責
      • (1)配置管理
      • (2)配置發布
      • (3)配置讀取
    • 2、Apollo Client 職責
      • (1)配置拉取
      • (2)配置注入
      • (3)配置變更監聽
    • 3、基本交互流程
      • (1)應用啟動
      • (2)配置變更通知
      • (3)配置更新
      • (4)配置注入
  • 二、架構思考
  • 三、源碼剖析
    • 1、初始化
      • (1)邏輯描述
      • (2)時序圖
      • (3)代碼位置
    • 2、查找注解
      • (1)邏輯描述
      • (2)時序圖
      • (3)代碼位置
    • 3、建立連接
      • (1)邏輯描述
      • (2)時序圖
      • (3)具體函數
    • 4、拉取配置
      • (1)邏輯描述
      • (2)時序圖
      • (3)代碼實現
        • a)配置初始化加載(trySync)
        • b)周期配置拉取(schedulePeriodicRefresh)
        • c)長輪詢監聽與最新配置拉取(scheduleLongPollingRefresh)
    • 5、變更通知
      • (1)邏輯描述
      • (2)時序圖
      • (2)代碼實現
    • 6、配置注入
      • (1)邏輯描述
      • (2)代碼位置
    • 四、最后

聲明

原創文章,轉載請標注。https://www.cnblogs.com/boycelee/p/17978027
《碼頭工人的一千零一夜》是一位專注于技術干貨分享的博主,追隨博主的文章,你將深入了解業界最新的技術趨勢,以及在Java開發和安全領域的實用經驗分享。無論你是開發人員還是對逆向工程感興趣的愛好者,都能在《碼頭工人的一千零一夜》找到有價值的知識和見解。

配置中心系列文章

《【架構師視角系列】Apollo配置中心之架構設計(一)》https://www.cnblogs.com/boycelee/p/17967590
《【架構師視角系列】Apollo配置中心之Client端(二)》https://www.cnblogs.com/boycelee/p/17978027

一、客戶端架構

架構介紹會從分層、職責、關系以及運行負責四個維度進行描述。

1、Config Service職責

(1)配置管理

Config Service 是Apollo配置中心的服務端組件,負責管理應用程序的配置信息。它存儲和維護應用程序的各種配置項。

(2)配置發布

Config Service 負責將最新的配置發布給注冊在它上面的Apollo Client。當配置發生變更時,Config Service 負責通知所有訂閱了相應配置的客戶端。

(3)配置讀取

Apollo Client 向 Config Service 發送請求,獲取應用程序的配置信息。

2、Apollo Client 職責

(1)配置拉取

Apollo Client 負責向 Config Service 發送配置拉取請求,獲取三方應用程序的配置。

(2)配置注入

Apollo Client 將從 Config Service 獲取到的配置注入到三方應用程序中。

(3)配置變更監聽

Apollo Client 可以注冊對配置變更的監聽器。當 Config Service 發布新的配置時,Apollo Client 能夠感知到配置的變更,并觸發相應的操作。

3、基本交互流程

(1)應用啟動

Apollo Client 在應用啟動時向 Config Service 發送配置拉取請求,獲取初始的配置。

(2)配置變更通知

Config Service 在配置發生變更時,通知所有注冊的 Apollo Client。

(3)配置更新

Apollo Client 接收到配置變更通知后,向 Config Service 發送請求,獲取最新的配置。

(4)配置注入

Apollo Client 將獲取到的最新配置注入到應用程序中,以便使用最新的配置信息。

通過以上交互流程達到應用不需要重啟,動態配置變更的目的。

二、架構思考

架構師視角系列,在分析一款組件的源碼時,需要深入思考其設計背后的動機。以下是讀者在閱讀本篇文章時應思考的問題:

  1. 配置拉取的設計:
  • 思考點: 設計中采用的配置拉取方式是如何選擇的?背后的動機是什么?可能的考慮包括系統性能、可維護性和安全性。
  1. 配置的注入方式:
  • 思考點: 配置是如何被注入到組件中的?這種注入方式有何優勢?設計選擇的原因可能涉及松耦合、動態變化和代碼可維護性等方面。
  1. 配置變更的通知機制:
  • 思考點: 配置變更是如何通知其他組件的?為什么選擇當前的通知機制?可能的考慮包括實時性、效率以及系統整體的架構要求。
  1. 為什么配置拉取拆分為兩個請求?
  • 思考點: 配置拉取為何拆分為兩個獨立的請求?這個設計決策的目的是什么?可能涉及到性能優化、可伸縮性以及減輕服務器負擔的考慮。
  1. 長輪詢的概念:
  • 思考點: 什么是長輪詢?為何在配置方案中選擇使用它?長輪詢的優勢在哪里?可能涉及到減少輪詢頻率、降低網絡開銷以及更及時的配置變更通知。
  1. 為什么需要做本地文件緩存?
  • 思考點: 為什么在組件中引入了本地文件緩存的機制?這樣的設計有哪些優點?可能牽涉到性能優化、離線支持以及用戶體驗的方面。

在深入研究源碼時,理解這些設計決策背后的原因,有助于更全面地理解系統架構,并為自己的設計提供有價值的啟示。

三、源碼剖析

1、初始化

(1)邏輯描述

通過實現Spring框架提供的BeanPostProcessor接口,并完成postProcessBeforeInitialization函數的實現,我們能夠在Bean初始化之前執行自定義的操作。BeanPostProcessor是Spring框架提供的一個擴展點,允許我們在Bean初始化前后插入自定義邏輯。在postProcessBeforeInitialization函數中,我們有機會遍歷Bean的成員變量和函數,實現在初始化之前對它們進行定制化處理的需求。

(2)時序圖

(3)代碼位置

ApolloProcessor#postProcessBeforeInitialization

為了講解更加順暢,會沿著Method上的注解@ApolloConfigChangeListener實現邏輯進行講解。

public abstract class ApolloProcessor implements BeanPostProcessor, PriorityOrdered {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
    throws BeansException {
        Class clazz = bean.getClass();
        // 遍歷Bean中的成員變量
        for (Field field : findAllField(clazz)) {
            processField(bean, beanName, field);
        }
        // 遍歷Bean中的所有函數(根據這條邏輯進行講解)
        for (Method method : findAllMethod(clazz)) {
            processMethod(bean, beanName, method);
        }
        return bean;
    }
    ...
}

2、查找注解

(1)邏輯描述

創建配置變化的監聽器,并創建namespace對應的config實例,將監聽器注冊到config實例中。當發生配置變更時,會調用監聽器的onChange函數,并利用反射機制通知對應的函數(使用@ApolloConfigChange)。

(2)時序圖

(3)代碼位置

ApolloAnnotationProcessor#processMethod

public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFactoryAware,
    EnvironmentAware {
  ...
  @Override
  protected void processMethod(final Object bean, String beanName, final Method method) {
    // 處理函數上的注解(@ApolloConfigChange)(關注這里)
    this.processApolloConfigChangeListener(bean, method);
    this.processApolloJsonValue(bean, beanName, method);
  }

  private void processApolloConfigChangeListener(final Object bean, final Method method) {
    ApolloConfigChangeListener annotation = AnnotationUtils
        .findAnnotation(method, ApolloConfigChangeListener.class);
    if (annotation == null) {
      return;
    }
    Class<?>[] parameterTypes = method.getParameterTypes();
    Preconditions.checkArgument(parameterTypes.length == 1,
        "Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length,
        method);
    Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]),
        "Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0],
        method);

    ReflectionUtils.makeAccessible(method);
    String[] namespaces = annotation.value();
    String[] annotatedInterestedKeys = annotation.interestedKeys();
    String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes();
    // 創建配置變化監聽器。當配置發生變化時,會調用onChange函數并使用反射觸發標識@ApolloConfigChange的Method
    ConfigChangeListener configChangeListener = new ConfigChangeListener() {
      @Override
      public void onChange(ConfigChangeEvent changeEvent) {
        ReflectionUtils.invokeMethod(method, bean, changeEvent);
      }
    };

    Set<String> interestedKeys =
        annotatedInterestedKeys.length > 0 ? Sets.newHashSet(annotatedInterestedKeys) : null;
    Set<String> interestedKeyPrefixes =
        annotatedInterestedKeyPrefixes.length > 0 ? Sets.newHashSet(annotatedInterestedKeyPrefixes)
            : null;

    // 遍歷namespace
    for (String namespace : namespaces) {
      final String resolvedNamespace = this.environment.resolveRequiredPlaceholders(namespace);
      // 創建(獲取)Config實例(關注這里)
      Config config = ConfigService.getConfig(resolvedNamespace);

      // 注冊監聽器
      if (interestedKeys == null && interestedKeyPrefixes == null) {
        // 將創建的監聽器注冊到namespace對應的config實例中(關注這里)
        config.addChangeListener(configChangeListener);
      } else {
        config.addChangeListener(configChangeListener, interestedKeys, interestedKeyPrefixes);
      }
    }
  }
  ...
}

3、建立連接

(1)邏輯描述

為注解(@ApolloConfigChange)綁定的namespace創建Config實例,Config實例中會會為namespace創建本地配置倉庫(createLocalConfigRepository處理本地配置存儲)和遠程配置倉庫(createRemoteConfigRepository處理遠程ConfigService配置拉取)。

(2)時序圖

(3)具體函數

ConfigService#getConfig

public class ConfigService {

  // 創建一個ConfigService單例
  private static final ConfigService s_instance = new ConfigService();

  // 獲取 m_configManager 與 m_configRegistry單例
  private volatile ConfigManager m_configManager;
  private volatile ConfigRegistry m_configRegistry;
    
  // 獲取nanespae對應的config實例(關注這里)
  public static Config getConfig(String namespace) {
    return s_instance.getManager().getConfig(namespace);
  }

(2)具體函數:DefaultConfigManager#getConfig

public class DefaultConfigManager implements ConfigManager {

  @Override
  public Config getConfig(String namespace) {
    Config config = m_configs.get(namespace);
    // 每個namespace創建一個Config對象
    if (config == null) {
      synchronized (this) {
        config = m_configs.get(namespace);
        if (config == null) {
          ConfigFactory factory = m_factoryManager.getFactory(namespace);
          //config對象中有,拉取遠程和本地倉庫(Repository)
          config = factory.create(namespace);
          m_configs.put(namespace, config);
        }
      }
    }
    return config;
  }
}

(3)具體函數:DefaultConfigFactory#create

創建順序是:1)創建遠端存儲倉庫,從Config Service中拉取配置數據;2)創建本地存儲倉庫,將遠端拉取到的配置文件存儲到本地文件中;3)實例化Config,以供后續獲取配置信息使用。

public class DefaultConfigFactory implements ConfigFactory {
  ...
  @Override
  public Config create(String namespace) {
    ConfigFileFormat format = determineFileFormat(namespace);
    if (ConfigFileFormat.isPropertiesCompatible(format)) {
      return this.createRepositoryConfig(namespace, createPropertiesCompatibleFileConfigRepository(namespace, format));
    }
    // (關注這里)。調用createLocalConfigRepository函數,創建LocalConfigRepository,建立本地存儲倉庫
    return this.createRepositoryConfig(namespace, createLocalConfigRepository(namespace));
  }

  LocalFileConfigRepository createLocalConfigRepository(String namespace) {
    if (m_configUtil.isInLocalMode()) {
      logger.warn(
          "==== Apollo is in local mode! Won't pull configs from remote server for namespace {} ! ====",
          namespace);
      return new LocalFileConfigRepository(namespace);
    }
    // (關注這里)。調用createRemoteConfigRepository函數,創建RemoteConfigRepository,建立遠程存儲倉庫
    return new LocalFileConfigRepository(namespace, createRemoteConfigRepository(namespace));
  }
    
  RemoteConfigRepository createRemoteConfigRepository(String namespace) {
    return new RemoteConfigRepository(namespace);
  }
  ...
}

4、拉取配置

(1)邏輯描述

配置拉取主要分為三個關鍵步驟:1)初始化加載配置;2)定期拉取配置;3)通過長輪詢進行刷新。在這其中,長輪詢刷新階段又分為兩個請求:1)配置更新通知(通過長輪詢實現);2)詳細配置拉取。通過這個流程,系統能夠實現配置的及時更新,確保應用程序始終使用最新的配置信息。

(2)時序圖

(3)代碼實現

具體函數:RemoteConfigRepository#RemoteConfigRepository

public class RemoteConfigRepository extends AbstractConfigRepository {
  ...
  public RemoteConfigRepository(String namespace) {
    m_namespace = namespace;
    m_configCache = new AtomicReference<>();
    m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
    m_httpClient = ApolloInjector.getInstance(HttpClient.class);
    m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
    remoteConfigLongPollService = ApolloInjector.getInstance(RemoteConfigLongPollService.class);
    m_longPollServiceDto = new AtomicReference<>();
    m_remoteMessages = new AtomicReference<>();
    m_loadConfigRateLimiter = RateLimiter.create(m_configUtil.getLoadConfigQPS());
    m_configNeedForceRefresh = new AtomicBoolean(true);
    m_loadConfigFailSchedulePolicy = new ExponentialSchedulePolicy(m_configUtil.getOnErrorRetryInterval(),
        m_configUtil.getOnErrorRetryInterval() * 8);
    // 初始化加載配置
    this.trySync();
    // 周期拉取配置(apollo定時兜底)
    this.schedulePeriodicRefresh();
    // 長輪詢
    this.scheduleLongPollingRefresh();
  }
    
}
a)配置初始化加載(trySync)

具體函數:AbstractConfigRepository#trySync

時序圖:

邏輯描述:namespace初始化加載配置

public abstract class AbstractConfigRepository implements ConfigRepository {  
  ...
  // 獲取配置內容
  protected boolean trySync() {
    try {
      // (關注這里)獲取配置
      sync();
      return true;
    } catch (Throwable ex) {
      Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
      logger
          .warn("Sync config failed, will retry. Repository {}, reason: {}", this.getClass(), ExceptionUtil
              .getDetailMessage(ex));
    }
    return false;
  }
  ...
}

具體函數:RemoteConfigRepository#sync()

public class RemoteConfigRepository extends AbstractConfigRepository {
  ...
  @Override
  protected synchronized void sync() {
    Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");

    try {
      ApolloConfig previous = m_configCache.get();
      // 加載配置(關注這里)
      ApolloConfig current = loadApolloConfig();

      //reference equals means HTTP 304
      if (previous != current) {
        logger.debug("Remote Config refreshed!");
        m_configCache.set(current);
        // 通知Repository監聽器,配置發生變化(關注這里)
        this.fireRepositoryChange(m_namespace, this.getConfig());
      }

      if (current != null) {
        Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),
            current.getReleaseKey());
      }

      transaction.setStatus(Transaction.SUCCESS);
    } catch (Throwable ex) {
      transaction.setStatus(ex);
      throw ex;
    } finally {
      transaction.complete();
    }
  }

  // 加載配置
  private ApolloConfig loadApolloConfig() {
    // 限流,避免創建過多連接。同一個namespace會有多種觸發loadApolloConfig函數的方式
    if (!m_loadConfigRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
      //wait at most 5 seconds
      try {
        TimeUnit.SECONDS.sleep(5);
      } catch (InterruptedException e) {
      }
    }
    String appId = m_configUtil.getAppId();
    String cluster = m_configUtil.getCluster();
    String dataCenter = m_configUtil.getDataCenter();
    String secret = m_configUtil.getAccessKeySecret();
    Tracer.logEvent("Apollo.Client.ConfigMeta", STRING_JOINER.join(appId, cluster, m_namespace));
    int maxRetries = m_configNeedForceRefresh.get() ? 2 : 1;
    long onErrorSleepTime = 0; // 0 means no sleep
    Throwable exception = null;

    // 從meta server中獲取注冊到eureka的config service
    List<ServiceDTO> configServices = getConfigServices();
    String url = null;
    retryLoopLabel:
    for (int i = 0; i < maxRetries; i++) {
      List<ServiceDTO> randomConfigServices = Lists.newLinkedList(configServices);
      Collections.shuffle(randomConfigServices);
      if (m_longPollServiceDto.get() != null) {
        randomConfigServices.add(0, m_longPollServiceDto.getAndSet(null));
      }

      for (ServiceDTO configService : randomConfigServices) {
        if (onErrorSleepTime > 0) {
          logger.warn(
              "Load config failed, will retry in {} {}. appId: {}, cluster: {}, namespaces: {}",
              onErrorSleepTime, m_configUtil.getOnErrorRetryIntervalTimeUnit(), appId, cluster, m_namespace);

          try {
            m_configUtil.getOnErrorRetryIntervalTimeUnit().sleep(onErrorSleepTime);
          } catch (InterruptedException e) {
            //ignore
          }
        }
        // 拼接請求config service獲取配置的url
        url = assembleQueryConfigUrl(configService.getHomepageUrl(), appId, cluster, m_namespace,
                dataCenter, m_remoteMessages.get(), m_configCache.get());

        logger.debug("Loading config from {}", url);

        HttpRequest request = new HttpRequest(url);
        if (!StringUtils.isBlank(secret)) {
          Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
          request.setHeaders(headers);
        }

        Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "queryConfig");
        transaction.addData("Url", url);
        try {

          // 發送請求
          HttpResponse<ApolloConfig> response = m_httpClient.doGet(request, ApolloConfig.class);
          m_configNeedForceRefresh.set(false);
          m_loadConfigFailSchedulePolicy.success();

          transaction.addData("StatusCode", response.getStatusCode());
          transaction.setStatus(Transaction.SUCCESS);

          // 如果配置沒有變更,config service會返回304狀態碼
          if (response.getStatusCode() == 304) {
            logger.debug("Config server responds with 304 HTTP status code.");
            // 緩存中拉取歷史配置
            return m_configCache.get();
          }

          ApolloConfig result = response.getBody();

          logger.debug("Loaded config for {}: {}", m_namespace, result);
          // 如果配置變更,這會直接返回
          return result;
        } catch (ApolloConfigStatusCodeException ex) {
          ApolloConfigStatusCodeException statusCodeException = ex;
          //config not found
          if (ex.getStatusCode() == 404) {
            String message = String.format(
                "Could not find config for namespace - appId: %s, cluster: %s, namespace: %s, " +
                    "please check whether the configs are released in Apollo!",
                appId, cluster, m_namespace);
            statusCodeException = new ApolloConfigStatusCodeException(ex.getStatusCode(),
                message);
          }
          Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(statusCodeException));
          transaction.setStatus(statusCodeException);
          exception = statusCodeException;
          if(ex.getStatusCode() == 404) {
            break retryLoopLabel;
          }
        } catch (Throwable ex) {
          Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
          transaction.setStatus(ex);
          exception = ex;
        } finally {
          transaction.complete();
        }

        // if force refresh, do normal sleep, if normal config load, do exponential sleep
        onErrorSleepTime = m_configNeedForceRefresh.get() ? m_configUtil.getOnErrorRetryInterval() :
            m_loadConfigFailSchedulePolicy.fail();
      }
    }
  ...
}
b)周期配置拉取(schedulePeriodicRefresh)

具體函數:RemoteConfigRepository#schedulePeriodicRefresh

時序圖:

邏輯描述:周期拉取配置(apollo定時兜底)

public class RemoteConfigRepository extends AbstractConfigRepository {
  ...
  private final static ScheduledExecutorService m_executorService;
    
  static {
    m_executorService = Executors.newScheduledThreadPool(1,
        ApolloThreadFactory.create("RemoteConfigRepository", true));
  }

  // 定時拉取配置
  private void schedulePeriodicRefresh() {
    logger.debug("Schedule periodic refresh with interval: {} {}",
        m_configUtil.getRefreshInterval(), m_configUtil.getRefreshIntervalTimeUnit());
      // 固定時間間隔執行任務
      m_executorService.scheduleAtFixedRate(
        new Runnable() {
          @Override
          public void run() {
            Tracer.logEvent("Apollo.ConfigService", String.format("periodicRefresh: %s", m_namespace));
            logger.debug("refresh config for namespace: {}", m_namespace);
            trySync();
            Tracer.logEvent("Apollo.Client.Version", Apollo.VERSION);
          }
        }, m_configUtil.getRefreshInterval(), m_configUtil.getRefreshInterval(),
        m_configUtil.getRefreshIntervalTimeUnit());
  }
  ...
}
c)長輪詢監聽與最新配置拉取(scheduleLongPollingRefresh)

具體函數:RemoteConfigRepository#scheduleLongPollingRefresh()

時序圖:

邏輯描述:建立長輪詢,監聽配置變更通知通知后加載最新配置

public class RemoteConfigRepository extends AbstractConfigRepository {

  private void scheduleLongPollingRefresh() {
    remoteConfigLongPollService.submit(m_namespace, this);
  }
}

(8)具體函數:RemoteConfigLongPollService#submit()

public class RemoteConfigLongPollService {

  private final ExecutorService m_longPollingService;

  public RemoteConfigLongPollService() {
    m_longPollFailSchedulePolicyInSecond = new ExponentialSchedulePolicy(1, 120); //in second
    m_longPollingStopped = new AtomicBoolean(false);
    m_longPollingService = Executors.newSingleThreadExecutor(
        ApolloThreadFactory.create("RemoteConfigLongPollService", true));
    m_longPollStarted = new AtomicBoolean(false);
    m_longPollNamespaces =
        Multimaps.synchronizedSetMultimap(HashMultimap.<String, RemoteConfigRepository>create());
    m_notifications = Maps.newConcurrentMap();
    m_remoteNotificationMessages = Maps.newConcurrentMap();
    m_responseType = new TypeToken<List<ApolloConfigNotification>>() {
    }.getType();
    m_configUtil = ApolloInjector.getInstance(ConfigUtil.class);
    m_httpClient = ApolloInjector.getInstance(HttpClient.class);
    m_serviceLocator = ApolloInjector.getInstance(ConfigServiceLocator.class);
    m_longPollRateLimiter = RateLimiter.create(m_configUtil.getLongPollQPS());
  }

  public boolean submit(String namespace, RemoteConfigRepository remoteConfigRepository) {
    // 如果長輪詢已經啟動,就不會再往線程池里添加runnable(通過m_longPollStarted判斷是否啟動),但是會往m_longPollNamespaces中添加需要被通知變更的namespace對應的remoteConfigRepository
    boolean added = m_longPollNamespaces.put(namespace, remoteConfigRepository);
    m_notifications.putIfAbsent(namespace, INIT_NOTIFICATION_ID);
    if (!m_longPollStarted.get()) {
      startLongPolling();
    }
    return added;
  }

  // 多個namespace,也只有一個長輪詢
  private void startLongPolling() {
    if (!m_longPollStarted.compareAndSet(false, true)) {
      //already started
      return;
    }
    try {
      final String appId = m_configUtil.getAppId();
      final String cluster = m_configUtil.getCluster();
      final String dataCenter = m_configUtil.getDataCenter();
      final String secret = m_configUtil.getAccessKeySecret();
      final long longPollingInitialDelayInMills = m_configUtil.getLongPollingInitialDelayInMills();
      // 單線程連接池
      m_longPollingService.submit(new Runnable() {
        @Override
        public void run() {
          if (longPollingInitialDelayInMills > 0) {
            try {
              logger.debug("Long polling will start in {} ms.", longPollingInitialDelayInMills);
              TimeUnit.MILLISECONDS.sleep(longPollingInitialDelayInMills);
            } catch (InterruptedException e) {
              //ignore
            }
          }
          doLongPollingRefresh(appId, cluster, dataCenter, secret);
        }
      });
    } catch (Throwable ex) {
      m_longPollStarted.set(false);
      ApolloConfigException exception =
          new ApolloConfigException("Schedule long polling refresh failed", ex);
      Tracer.logError(exception);
      logger.warn(ExceptionUtil.getDetailMessage(exception));
    }
  }

  private void doLongPollingRefresh(String appId, String cluster, String dataCenter, String secret) {
    final Random random = new Random();
    ServiceDTO lastServiceDto = null;
    // 只要不中斷就循環
    while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {
      // limiter令牌桶限流為2qps, 5秒之內存在沒有獲取到1個令牌的情況,則休眠5秒
      if (!m_longPollRateLimiter.tryAcquire(5, TimeUnit.SECONDS)) {
        //wait at most 5 seconds
        try {
          TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
        }
      }
      Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "pollNotification");
      String url = null;
      try {
        if (lastServiceDto == null) {
          List<ServiceDTO> configServices = getConfigServices();
          lastServiceDto = configServices.get(random.nextInt(configServices.size()));
        }

        url =
            assembleLongPollRefreshUrl(lastServiceDto.getHomepageUrl(), appId, cluster, dataCenter,
                m_notifications);

        logger.debug("Long polling from {}", url);

        HttpRequest request = new HttpRequest(url);
        request.setReadTimeout(LONG_POLLING_READ_TIMEOUT);
        if (!StringUtils.isBlank(secret)) {
          Map<String, String> headers = Signature.buildHttpHeaders(url, appId, secret);
          request.setHeaders(headers);
        }

        transaction.addData("Url", url);

        final HttpResponse<List<ApolloConfigNotification>> response =
            m_httpClient.doGet(request, m_responseType);

        logger.debug("Long polling response: {}, url: {}", response.getStatusCode(), url);
        if (response.getStatusCode() == 200 && response.getBody() != null) {
          updateNotifications(response.getBody());
          updateRemoteNotifications(response.getBody());
          transaction.addData("Result", response.getBody().toString());
          // 此處通知,執行notify之后加載數據
          notify(lastServiceDto, response.getBody());
        }

        //try to load balance
        if (response.getStatusCode() == 304 && random.nextBoolean()) {
          lastServiceDto = null;
        }

        m_longPollFailSchedulePolicyInSecond.success();
        transaction.addData("StatusCode", response.getStatusCode());
        transaction.setStatus(Transaction.SUCCESS);
      } catch (Throwable ex) {
        lastServiceDto = null;
        Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
        transaction.setStatus(ex);
        long sleepTimeInSecond = m_longPollFailSchedulePolicyInSecond.fail();
        logger.warn(
            "Long polling failed, will retry in {} seconds. appId: {}, cluster: {}, namespaces: {}, long polling url: {}, reason: {}",
            sleepTimeInSecond, appId, cluster, assembleNamespaces(), url, ExceptionUtil.getDetailMessage(ex));
        try {
          TimeUnit.SECONDS.sleep(sleepTimeInSecond);
        } catch (InterruptedException ie) {
          //ignore
        }
      } finally {
        transaction.complete();
      }
    }
  }

  private void notify(ServiceDTO lastServiceDto, List<ApolloConfigNotification> notifications) {
    if (notifications == null || notifications.isEmpty()) {
      return;
    }
    for (ApolloConfigNotification notification : notifications) {
      String namespaceName = notification.getNamespaceName();
      //create a new list to avoid ConcurrentModificationException
      List<RemoteConfigRepository> toBeNotified =
          Lists.newArrayList(m_longPollNamespaces.get(namespaceName));
      ApolloNotificationMessages originalMessages = m_remoteNotificationMessages.get(namespaceName);
      ApolloNotificationMessages remoteMessages = originalMessages == null ? null : originalMessages.clone();
      //since .properties are filtered out by default, so we need to check if there is any listener for it
      toBeNotified.addAll(m_longPollNamespaces
          .get(String.format("%s.%s", namespaceName, ConfigFileFormat.Properties.getValue())));
      for (RemoteConfigRepository remoteConfigRepository : toBeNotified) {
        try {
          remoteConfigRepository.onLongPollNotified(lastServiceDto, remoteMessages);
        } catch (Throwable ex) {
          Tracer.logError(ex);
        }
      }
    }
  }

  public void onLongPollNotified(ServiceDTO longPollNotifiedServiceDto, ApolloNotificationMessages remoteMessages) {
    m_longPollServiceDto.set(longPollNotifiedServiceDto);
    m_remoteMessages.set(remoteMessages);
    m_executorService.submit(new Runnable() {
      @Override
      public void run() {
        m_configNeedForceRefresh.set(true);
        trySync();
      }
    });
  }
  
}

(9)AbstractConfigRepository#trySync()

public abstract class AbstractConfigRepository implements ConfigRepository {
    
  // 拉配置信息,不是notificationID,而是配置內容
  protected boolean trySync() {
    try {
      sync();
      return true;
    } catch (Throwable ex) {
      Tracer.logEvent("ApolloConfigException", ExceptionUtil.getDetailMessage(ex));
      logger
          .warn("Sync config failed, will retry. Repository {}, reason: {}", this.getClass(), ExceptionUtil
              .getDetailMessage(ex));
    }
    return false;
  }

  // 子類RemoteConfigRepository中實現
  protected synchronized void sync() {
    Transaction transaction = Tracer.newTransaction("Apollo.ConfigService", "syncRemoteConfig");

    try {
      ApolloConfig previous = m_configCache.get();
      ApolloConfig current = loadApolloConfig();

      //reference equals means HTTP 304
      if (previous != current) {
        logger.debug("Remote Config refreshed!");
        m_configCache.set(current);
        this.fireRepositoryChange(m_namespace, this.getConfig());
      }

      if (current != null) {
        Tracer.logEvent(String.format("Apollo.Client.Configs.%s", current.getNamespaceName()),
            current.getReleaseKey());
      }

      transaction.setStatus(Transaction.SUCCESS);
    } catch (Throwable ex) {
      transaction.setStatus(ex);
      throw ex;
    } finally {
      transaction.complete();
    }
  }

  protected void fireRepositoryChange(String namespace, Properties newProperties) {
    for (RepositoryChangeListener listener : m_listeners) {
      try {
        listener.onRepositoryChange(namespace, newProperties);
      } catch (Throwable ex) {
        Tracer.logError(ex);
        logger.error("Failed to invoke repository change listener {}", listener.getClass(), ex);
      }
    }
  }
}

5、變更通知

(1)邏輯描述

在namespace數據發生變更時,系統將通知所有監聽該namespace的監聽器。系統會比較新老配置,將差異配置存儲在ConfigChange中,并隨后通知各個監聽器。

(2)時序圖

(2)代碼實現

DefaultConfig#onRepositoryChange()

public class DefaultConfig extends AbstractConfig implements RepositoryChangeListener {
  ...
  /**
   * Repository通知變更,
   * @param namespace the namespace of this repository change
   * @param newProperties the properties after change
   */
  @Override
  public synchronized void onRepositoryChange(String namespace, Properties newProperties) {
    if (newProperties.equals(m_configProperties.get())) {
      return;
    }

    ConfigSourceType sourceType = m_configRepository.getSourceType();
    Properties newConfigProperties = propertiesFactory.getPropertiesInstance();
    newConfigProperties.putAll(newProperties);

    // 計算配置變更情況,對新老配置進行比較,將差異配置存儲在ConfigChange中Map的格式為{namspace:{key:value}}
    Map<String, ConfigChange> actualChanges = updateAndCalcConfigChanges(newConfigProperties,
        sourceType);

    //check double checked result
    if (actualChanges.isEmpty()) {
      return;
    }

    // 將具體變更通知各監聽器
    this.fireConfigChange(m_namespace, actualChanges);

    Tracer.logEvent("Apollo.Client.ConfigChanges", m_namespace);
  }

  // 構建Method的變更事件ConfigChange參數
  private Map<String, ConfigChange> updateAndCalcConfigChanges(Properties newConfigProperties,
      ConfigSourceType sourceType) {
    List<ConfigChange> configChanges = calcPropertyChanges(m_namespace, m_configProperties.get(), newConfigProperties);

    ImmutableMap.Builder<String, ConfigChange> actualChanges =
        new ImmutableMap.Builder<>();

    /** === Double check since DefaultConfig has multiple config sources ==== **/

    //1. use getProperty to update configChanges's old value
    for (ConfigChange change : configChanges) {
      change.setOldValue(this.getProperty(change.getPropertyName(), change.getOldValue()));
    }

    //2. update m_configProperties
    updateConfig(newConfigProperties, sourceType);
    clearConfigCache();

    //3. use getProperty to update configChange's new value and calc the final changes
    for (ConfigChange change : configChanges) {
      change.setNewValue(this.getProperty(change.getPropertyName(), change.getNewValue()));
      switch (change.getChangeType()) {
        case ADDED:
          if (Objects.equals(change.getOldValue(), change.getNewValue())) {
            break;
          }
          if (change.getOldValue() != null) {
            change.setChangeType(PropertyChangeType.MODIFIED);
          }
          actualChanges.put(change.getPropertyName(), change);
          break;
        case MODIFIED:
          if (!Objects.equals(change.getOldValue(), change.getNewValue())) {
            actualChanges.put(change.getPropertyName(), change);
          }
          break;
        case DELETED:
          if (Objects.equals(change.getOldValue(), change.getNewValue())) {
            break;
          }
          if (change.getNewValue() != null) {
            change.setChangeType(PropertyChangeType.MODIFIED);
          }
          actualChanges.put(change.getPropertyName(), change);
          break;
        default:
          //do nothing
          break;
      }
    }
    return actualChanges.build();
  }

  /**
   * 父類中實現 
   * 配置變更通知,通知監聽器
   * @param changes map's key is config property's key
   */
  protected void fireConfigChange(String namespace, Map<String, ConfigChange> changes) {
    final Set<String> changedKeys = changes.keySet();
    final List<ConfigChangeListener> listeners = this.findMatchedConfigChangeListeners(changedKeys);

    // notify those listeners
    for (ConfigChangeListener listener : listeners) {
      Set<String> interestedChangedKeys = resolveInterestedChangedKeys(listener, changedKeys);
      InterestedConfigChangeEvent interestedConfigChangeEvent = new InterestedConfigChangeEvent(
          namespace, changes, interestedChangedKeys);
      this.notifyAsync(listener, interestedConfigChangeEvent);
    }
  }

  /**
   * 異步通知
   * @param listener
   * @param changeEvent
   */
  private void notifyAsync(final ConfigChangeListener listener, final ConfigChangeEvent changeEvent) {
    m_executorService.submit(new Runnable() {
      @Override
      public void run() {
        String listenerName = listener.getClass().getName();
        Transaction transaction = Tracer.newTransaction("Apollo.ConfigChangeListener", listenerName);
        try {
          listener.onChange(changeEvent);
          transaction.setStatus(Transaction.SUCCESS);
        } catch (Throwable ex) {
          transaction.setStatus(ex);
          Tracer.logError(ex);
          logger.error("Failed to invoke config change listener {}", listenerName, ex);
        } finally {
          transaction.complete();
        }
      }
    });
  }
  ...
}

6、配置注入

(1)邏輯描述

在配置發生變更后,系統會通知在Bean初始化時創建的與namespace對應的監聽器。接著,系統通過反射的方式觸發相應的函數(使用@ApolloConfigChange注解)。

(2)代碼位置

ConfigChangeListener#onChange()

public class ApolloAnnotationProcessor extends ApolloProcessor implements BeanFactoryAware,
    EnvironmentAware {
  ...
  private void processApolloConfigChangeListener(final Object bean, final Method method) {
    ApolloConfigChangeListener annotation = AnnotationUtils
        .findAnnotation(method, ApolloConfigChangeListener.class);
    if (annotation == null) {
      return;
    }
    Class<?>[] parameterTypes = method.getParameterTypes();
    Preconditions.checkArgument(parameterTypes.length == 1,
        "Invalid number of parameters: %s for method: %s, should be 1", parameterTypes.length,
        method);
    Preconditions.checkArgument(ConfigChangeEvent.class.isAssignableFrom(parameterTypes[0]),
        "Invalid parameter type: %s for method: %s, should be ConfigChangeEvent", parameterTypes[0],
        method);

    ReflectionUtils.makeAccessible(method);
    // value 是 namespace
    String[] namespaces = annotation.value();
    String[] annotatedInterestedKeys = annotation.interestedKeys();
    String[] annotatedInterestedKeyPrefixes = annotation.interestedKeyPrefixes();
    // 創建配置變化監聽器
    ConfigChangeListener configChangeListener = new ConfigChangeListener() {
      @Override
      public void onChange(ConfigChangeEvent changeEvent) {
        ReflectionUtils.invokeMethod(method, bean, changeEvent);
      }
    };
  }
  ...
}

四、最后

《碼頭工人的一千零一夜》是一位專注于技術干貨分享的博主,追隨博主的文章,你將深入了解業界最新的技術趨勢,以及在開發和安全領域的實用經驗分享。無論你是Java開發人員還是對逆向工程感興趣的愛好者,都能在《碼頭工人的一千零一夜》找到有價值的知識和見解。

懂得不多,做得太少。歡迎批評、指正。

總結

以上是生活随笔為你收集整理的【架构师视角系列】Apollo配置中心之Client端(二)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

午夜电影中文字幕 | 国产视频不卡一区 | 91麻豆精品国产午夜天堂 | 91亚洲成人 | 久久久穴 | 国产精品久久久久久一区二区三区 | avhd高清在线谜片 | 麻豆国产精品永久免费视频 | av电影 一区二区 | 日日日网| 中文字幕亚洲欧美日韩2019 | 在线电影a| 久久精品国产精品亚洲 | 国产亚洲精品福利 | 日韩三级一区 | 色成人亚洲网 | 久热爱 | 人人爱人人做人人爽 | 久久国色夜色精品国产 | 亚洲国产成人精品在线 | 伊人久久一区 | 欧美色图另类 | 日韩影视在线观看 | 国产在线视频资源 | 免费亚洲精品视频 | 在线色网站 | 97精品国产97久久久久久粉红 | 成人影片免费 | 成人黄大片视频在线观看 | 夜夜嗨av色一区二区不卡 | 久久久久久久久久久久99 | 最近2019好看的中文字幕免费 | 国产九九九精品视频 | a色网站| 国产亚洲婷婷免费 | 深爱五月激情五月 | 激情视频在线观看网址 | 久久夜色精品亚洲噜噜国4 午夜视频在线观看欧美 | 国产成人av一区二区三区在线观看 | 狠狠狠狠狠狠狠狠 | 五月天欧美精品 | 91黄站| 97超碰中文| 五月婷婷综合在线 | 五月开心综合 | 激情久久久久 | 国产原创中文在线 | 亚洲好视频 | 一级免费观看 | 狠狠干2018 | 亚洲精品美女久久17c | 免费在线黄色av | 中文字幕在线观看免费高清电影 | 日韩在线一级 | 欧美一级淫片videoshd | 91精品视频免费看 | 国产成人精品在线观看 | 在线免费观看国产视频 | 香蕉网站在线观看 | 亚洲专区在线播放 | 91精品一区国产高清在线gif | 日韩精品2区| 久久久久久久免费看 | 国产女人40精品一区毛片视频 | 中文字幕精品一区二区三区电影 | 久久人操 | 亚洲精品国产自产拍在线观看 | 99视频在线观看免费 | 国产一卡二卡四卡国 | 日韩免费区 | 国产精品视频专区 | 人人澡人人干 | 国产在线观看91 | 婷婷综合国产 | 国产精品毛片网 | 超碰人人射 | 欧美成人免费在线 | 日日夜夜狠狠操 | 天天综合成人 | 国产日本在线 | 五月天激情视频 | 国产精品久久久一区二区三区网站 | 久久国产精品一区二区三区四区 | 97超碰伊人| 色吊丝在线永久观看最新版本 | 丝袜美女在线观看 | 亚洲资源在线 | 视频一区在线播放 | 亚洲国产视频a | 国产亚洲欧美精品久久久久久 | 国产精品成久久久久三级 | 日韩女同一区二区三区在线观看 | 欧美看片 | 久久人人爽人人爽人人片av软件 | 又色又爽又黄 | 中文字幕免 | 黄色a级片在线观看 | 国产一区二区在线免费视频 | 久久er99热精品一区二区三区 | 亚洲人成免费网站 | 精品欧美在线视频 | 夜夜操天天干, | 中文字幕亚洲字幕 | 91精品国产一区二区三区 | 亚洲激情网站免费观看 | 日本论理电影 | 91精品999 | 国产精品国产毛片 | 色播六月天 | 久久久亚洲麻豆日韩精品一区三区 | 免费在线观看av网站 | 日本女人逼 | 99在线视频网站 | 99在线视频精品 | 人人搞人人爽 | 成人片在线播放 | 天天激情在线 | 天天综合天天做天天综合 | 在线观看中文字幕一区二区 | 国产精品国产三级国产aⅴ入口 | 成人精品视频久久久久 | 久久婷婷一区二区三区 | 欧美一级艳片视频免费观看 | 国产精品久久久久一区 | 国产黄色大全 | 97视频人人澡人人爽 | 国产精品黄色在线观看 | 欧美午夜精品久久久久久孕妇 | 亚洲va欧洲va国产va不卡 | 日本中文在线 | 91香蕉国产在线观看软件 | 激情五月在线 | 亚洲国产欧美在线人成大黄瓜 | 天天色天天操天天爽 | 国产精品免费一区二区 | 国产日产精品久久久久快鸭 | 在线性视频日韩欧美 | 国产在线久久久 | 91香蕉国产| 亚洲国产免费av | 欧美精品色 | 国产三级精品在线 | 国产一级片在线播放 | 久久激情小视频 | 久久在线精品视频 | 天天操狠狠操 | 色婷婷www| 天天操天天色综合 | 国产一级片免费观看 | 91在线免费播放视频 | 黄色av免费| 婷婷色综合网 | 中中文字幕av在线 | 中文字幕无吗 | 国产成人精品一区二区三区 | 欧美日韩在线观看视频 | 国产精品网在线观看 | 日本aaaa级毛片在线看 | 欧美天堂视频在线 | 国产精品久久精品 | 亚洲成人精品在线观看 | 伊人婷婷| 日本特黄一级片 | 在线免费av观看 | 在线www色| 亚洲成色777777在线观看影院 | 91精品久 | 狠狠色噜噜狠狠 | 日本久久久亚洲精品 | 国产福利一区二区三区在线观看 | 黄色大片入口 | 婷婷在线网| 婷婷色综合色 | 久草视频免费在线播放 | 一二三区在线 | 免费h在线观看 | 日本中文字幕在线免费观看 | 成人一区二区三区在线 | 日韩h在线观看 | 亚色视频在线观看 | 中文字幕久久精品一区 | av 在线观看 | 99精品热视频只有精品10 | 国产区免费在线 | 97日日碰人人模人人澡分享吧 | 五月天.com | 久草在线视频免费资源观看 | 中文字幕超清在线免费 | 黄色精品在线看 | 在线日本看片免费人成视久网 | 91污污 | 国产精品丝袜 | 在线观看视频福利 | 久草精品在线观看 | av大片免费看 | 99精品视频免费看 | 国产中文字幕在线 | 久久久久成人精品免费播放动漫 | 免费日韩一区二区 | 天天玩天天干 | 中文字幕高清免费日韩视频在线 | 麻豆精品传媒视频 | 奇米影视999| 一区二区三区日韩在线 | 精品96久久久久久中文字幕无 | 国产a高清 | 天天操天天摸天天射 | 中文字幕二区在线观看 | 中文字幕av电影下载 | 国产精品久久久久久久久久尿 | 欧美性做爰猛烈叫床潮 | 欧美精品视 | 蜜臀av性久久久久蜜臀aⅴ流畅 | 91porny九色在线播放 | 亚洲视频99| 人人超碰97 | 99精品视频在线看 | 麻豆系列在线观看 | 亚洲国产高清在线观看视频 | 在线免费黄色片 | 久久免费视频3 | 91精品国产电影 | 高清不卡一区二区在线 | 欧美亚洲另类在线视频 | 久热免费在线观看 | 亚洲精品午夜国产va久久成人 | 久久图 | 亚洲欧美色婷婷 | 国产亚洲精品久久久久久网站 | 在线观看亚洲专区 | 亚洲影视九九影院在线观看 | 国产一区二区在线免费播放 | 国产免费一区二区三区最新 | 黄色www免费| 亚洲视屏在线播放 | 色噜噜色噜噜 | 午夜国产在线观看 | 黄色片网站大全 | 国产中文 | 国产成人99av超碰超爽 | 亚洲精品久久久久久久不卡四虎 | 天堂网一区二区三区 | 在线观看视频日韩 | 久久精品成人热国产成 | 99久久精品国产网站 | 亚洲免费观看在线视频 | 天天av综合网 | 日日躁夜夜躁aaaaxxxx | 国产中文字幕在线看 | 天堂在线视频免费观看 | 色五婷婷| 狠狠色伊人亚洲综合网站色 | 91av在线不卡| 91精品久久久久久久91蜜桃 | 黄色小说网站在线 | av在线免费不卡 | 亚洲一区二区三区毛片 | 99热最新网址 | 日韩av一卡二卡三卡 | 久久不射网站 | 久久超级碰视频 | 欧洲高潮三级做爰 | 国产高清免费av | 久久国产免 | 碰超在线观看 | 四虎欧美| 亚洲动漫在线观看 | 国产精品国产三级在线专区 | 一区二区丝袜 | 人人舔人人舔 | 亚洲国产午夜视频 | 亚州中文av | 久久人人爽爽人人爽人人片av | 亚洲一级性 | 一二三精品视频 | 日本三级久久 | 亚洲jizzjizz日本少妇 | 久久草精品 | 久草在线免费看视频 | 日韩天堂在线观看 | 婷婷色在线| 国产主播99 | 精品久久亚洲 | 国产欧美精品一区二区三区 | 看片黄网站 | 一本一本久久a久久精品牛牛影视 | 免费人人干 | 久久国产精品影视 | 夜夜操天天干, | 手机在线欧美 | 亚洲国产丝袜在线观看 | av电影在线播放 | 欧美极度另类性三渗透 | 91九色视频国产 | 天天色成人 | 狠狠色丁香婷婷综合橹88 | 欧美日韩视频网站 | 日日爽天天爽 | 麻豆一区二区三区视频 | 欧美三级高清 | 色综合久久88色综合天天 | 日日草夜夜操 | 欧美激情第一页xxx 午夜性福利 | 国产精品观看 | 又黄又刺激又爽的视频 | 九九国产视频 | 久久综合欧美精品亚洲一区 | 97超碰精品 | 国产日韩精品一区二区三区在线 | 久久久不卡影院 | av成人免费在线观看 | av三区在线 | 五月婷婷丁香在线观看 | 一区二区三区免费播放 | 在线观看www视频 | 日韩午夜网站 | 欧美 日韩 国产 中文字幕 | 亚洲电影一级黄 | 在线91视频 | 亚洲黄色大片 | 免费观看国产精品视频 | 久久网页 | 亚洲国产精品一区二区尤物区 | 黄色特级毛片 | 亚洲精品高清视频 | 国产精品爽爽久久久久久蜜臀 | 久久亚洲综合国产精品99麻豆的功能介绍 | 夜夜操天天操 | 97av在线视频| 久热只有精品 | 国内精品免费久久影院 | 中文字幕免费国产精品 | 久久九九影视网 | 日韩欧美国产免费播放 | 精品人人人 | 亚洲性视频 | 国产精品一区二区免费视频 | 伊人狠狠| 国产专区视频在线 | 国产黄色一级大片 | 精品超碰 | 中文在线| 欧美日韩中文字幕视频 | 国产日韩精品一区二区三区在线 | 狠狠躁日日躁 | 狠狠色丁香婷婷综合基地 | 亚洲美女视频在线 | 免费看色的网站 | 国产原创av在线 | 五月激情五月激情 | 欧美日韩亚洲第一页 | 国产免费成人 | 国产手机视频精品 | 91色综合 | 超碰成人免费电影 | 久久深夜福利免费观看 | 亚洲一区美女视频在线观看免费 | 久99视频 | 国产免费资源 | 国产午夜一区二区 | 国产99一区二区 | 51久久夜色精品国产麻豆 | 亚洲精品乱码久久久久久蜜桃欧美 | 欧美亚洲国产一卡 | 中文国产在线观看 | 国产视频亚洲视频 | 成人三级av| 国产手机av在线 | 国内外成人在线视频 | 韩国av电影网 | 97色噜噜 | 天堂在线成人 | 久久久久免费精品视频 | 久久成人黄色 | 免费av在线播放 | 亚洲午夜久久久久久久久电影网 | 精品国产亚洲一区二区麻豆 | 精品一区二区久久久久久久网站 | 在线视频你懂 | 日韩精品专区在线影院重磅 | 午夜精品久久久久久久久久 | 正在播放国产精品 | 婷婷丁香七月 | 在线观影网站 | 国产亚洲精品久久久久动 | 丁香激情综合久久伊人久久 | 丁香综合av | 久久久久久免费毛片精品 | 亚洲久在线 | 天天干天天爽 | 久草在线免费色站 | 日韩伦理片hd | 中文字幕第一页在线视频 | 国产精品第二页 | 国产精品久久久久久一二三四五 | 日韩精品一区二区三区免费视频观看 | 91精品久久久久久 | 青青河边草免费视频 | 成人午夜黄色影院 | 久久欧美综合 | 最新av网址在线 | 在线观看日本高清mv视频 | 久久五月网 | 激情网五月婷婷 | 色黄久久久久久 | 日韩在线观看中文 | 国产精品美女视频网站 | 狠狠色噜噜狠狠狠狠2021天天 | 国产精品a久久久久 | 日韩大片免费在线观看 | 久久成人国产精品一区二区 | 婷婷久久一区 | 天天做天天爱天天爽综合网 | 久艹在线免费观看 | 91九色成人| 高潮久久久久久久久 | 六月色播 | 国产精品成人一区二区 | 伊人一级| 精品在线亚洲视频 | 在线免费性生活片 | 欧美福利在线播放 | 五月天久久综合网 | 欧洲亚洲精品 | 精品久久1 | 色999精品| 精品在线观看一区二区 | 99精品一区| 亚洲视频在线观看 | 国产精品久久久久久一二三四五 | 久久久精品影视 | 午夜久久久精品 | 免费电影播放 | 看污网站 | 欧美日韩免费一区二区三区 | 97超碰国产精品 | 欧美a视频在线观看 | 四虎影视成人精品国库在线观看 | 欧美日韩国产一区二区三区 | 日本在线观看一区 | 免费进去里的视频 | 亚洲伊人成综合网 | 久久久久亚洲精品成人网小说 | 欧美激情综合色综合啪啪五月 | 亚洲精品国内 | 亚洲综合精品视频 | 婷婷社区五月天 | 中文字幕在线免费播放 | 国产黄免费在线观看 | 精品字幕在线 | 人人爽久久久噜噜噜电影 | 国产精品s色 | 免费看一及片 | 国产精品99久久久久久小说 | 精品久久久久久一区二区里番 | 在线观看第一页 | 96亚洲精品久久久蜜桃 | 国产在线精品一区二区三区 | 91在线网址 | 91网在线观看 | 国产在线观看91 | 免费午夜网站 | 国产网站色 | 综合视频在线 | 色综合久久久网 | 午夜视频在线观看一区 | 狠狠色婷婷丁香六月 | 中文国产在线观看 | 亚洲精品免费在线视频 | 69精品| 欧美日韩国产在线精品 | 丁香午夜婷婷 | 国产1级视频| 91pony九色丨交换 | 婷婷在线免费观看 | 国产精品久久久久aaaa | 国产午夜精品一区二区三区欧美 | 91在线视频 | 一区二区三区在线免费观看视频 | 九九免费在线观看 | 免费国产黄线在线观看视频 | 久草网站 | 一色屋精品视频在线观看 | 亚州欧美视频 | 亚洲一级国产 | 日韩在线视频观看免费 | 国产精品18久久久久久久 | 欧美日韩免费观看一区=区三区 | 国产又粗又长又硬免费视频 | 97超在线视频 | 国产精品久久久久久a | 免费日韩一区二区三区 | 亚洲精品国久久99热 | 国内精品久久久久久 | 精品免费一区 | 9i看片成人免费看片 | 黄色av一区 | 三级动图 | 人人超碰免费 | 精品久久国产一区 | 亚洲视频专区在线 | 一区二区三区日韩在线 | www亚洲国产| 国产一区欧美一区 | 欧美成人亚洲 | av久久在线| 91精品视频免费看 | 日韩 在线a| 97精品国产一二三产区 | 91精品国产高清自在线观看 | 九九热免费在线观看 | 国产无套精品久久久久久 | 亚洲国产午夜精品 | 国产精品大片免费观看 | 午夜影视剧场 | 国产视频99 | 免费性网站 | 日本在线成人 | 日日夜夜天天射 | 国产精品久久久免费 | 美女免费视频一区二区 | 亚洲欧洲av | 在线不卡中文字幕播放 | 91成人在线观看喷潮 | 久久久久免费精品国产小说色大师 | 免费91麻豆精品国产自产在线观看 | 日韩免费电影网站 | 国产一区二区在线免费观看 | 超碰在线免费97 | av高清一区二区三区 | 国产精品一区二区三区在线播放 | 又色又爽又黄 | 色偷偷人人澡久久超碰69 | 97超碰中文字幕 | 91麻豆网 | 国产精品麻豆视频 | 亚洲国产欧美在线看片xxoo | 99在线观看精品 | 片黄色毛片黄色毛片 | 麻豆观看 | 久久久久9999亚洲精品 | 亚洲精品国久久99热 | 亚洲美女视频网 | 欧美久久久久久久久中文字幕 | 国产精品白丝jk白祙 | 国产精品a成v人在线播放 | 日韩欧美高清视频在线观看 | 国产91精品高清一区二区三区 | .国产精品成人自产拍在线观看6 | 欧美日韩免费观看一区二区三区 | 亚洲精品久久在线 | 国内精品久久久久影院男同志 | 国产精品不卡一区 | 国产黄色片在线免费观看 | 国产性xxxx | 亚洲综合视频在线 | 国产污视频在线观看 | 久久tv| 亚洲深夜影院 | 国产成人61精品免费看片 | 欧美精品久久久久久久久久久 | 久久精品伊人 | 国产国产人免费人成免费视频 | 五月亚洲综合 | 91av视频免费在线观看 | 超碰在线94 | 在线视频一二三 | 韩日电影在线观看 | www.com久久 | 色网站免费在线看 | 久草在线视频国产 | 久久久久国产精品免费免费搜索 | 久草视频播放 | 亚洲在线精品视频 | 欧美天天射 | 久久99精品国产91久久来源 | 成人aaa毛片 | av天天干 | 国产传媒一区在线 | 久久国产香蕉视频 | 黄色动态图xx | 亚洲精品久久久久久久不卡四虎 | 亚洲成人欧美 | 国内精品久久久久久 | 中文字幕美女免费在线 | 99久久99久久 | 欧美精品一级视频 | 婷婷综合亚洲 | 蜜臀av夜夜澡人人爽人人桃色 | 日日爽天天爽 | 一区二区三区精品在线视频 | 久久久久久蜜桃一区二区 | 国产中文字幕91 | 天天干天天操天天拍 | 人人干人人搞 | 草免费视频 | 午夜精品久久 | 国产精品久久久久av免费 | 亚洲精品字幕在线 | 日韩色区 | 4438全国亚洲精品在线观看视频 | 亚洲欧美日韩国产精品一区午夜 | 精品一二三四视频 | 玖玖玖国产精品 | 国产黑丝一区二区 | 国产在线日本 | 精品国产乱码久久久久久久 | 干综合网| 91刺激视频| av午夜电影| 中文字幕一区在线观看视频 | 特级西西444www高清大视频 | 国产麻豆剧果冻传媒视频播放量 | 久草手机视频 | 伊人国产在线观看 | 国产午夜三级一区二区三桃花影视 | 成人在线免费视频 | 手机在线中文字幕 | 中文字幕一区在线观看视频 | 欧美天堂视频在线 | 亚洲精品乱码久久久久久按摩 | 日韩精品一区二区三区丰满 | 99re中文字幕| 国产又粗又猛又黄又爽视频 | 亚洲一级黄色av | 国内视频一区二区 | 国产亚洲在线观看 | 国产在线观看av | 久久免费国产精品1 | 国产精品a久久久久 | 精品亚洲男同gayvideo网站 | 亚洲第一香蕉视频 | 久久草草影视免费网 | 毛片1000部免费看 | 成年人看片 | 伊人色播 | 中文字幕高清在线播放 | 久久精品国产免费看久久精品 | 久久精品屋 | a视频在线播放 | 久久久久久久久久久久久影院 | 久久精品欧美一区 | 人人澡人人爱 | 国产精品久久久久婷婷 | av日韩中文 | 欧美日韩后 | 天天插狠狠干 | 夜夜澡人模人人添人人看 | 国产精品白浆视频 | 日韩久久一区 | 在线观看国产成人av片 | 成人精品久久久 | 91av在线免费观看 | 爱射综合 | 亚洲精选视频免费看 | 国产九九热视频 | 狠狠的干狠狠的操 | 欧美一区在线看 | 国产精品丝袜久久久久久久不卡 | 中文字幕日韩伦理 | 国产精品一区二区62 | 国产99久久久精品视频 | 成人三级网址 | a黄色一级片 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 69久久久| 日本久久中文 | 久久久久亚洲精品国产 | 狠狠地操 | 91成人在线免费观看 | 一区二区在线不卡 | 久久不卡av| 丁香花在线观看视频在线 | 2024av| 韩日精品在线 | 成人a免费看 | 97成人精品视频在线观看 | 色婷婷一| 黄色特一级| 久久九九九九 | 久久久福利 | 99精品国产免费久久久久久下载 | 国产丝袜美腿在线 | 五月天综合色激情 | 久久精品直播 | 草久久久 | 丁香花在线观看视频在线 | 日韩视频免费观看高清 | 久久综合九色综合久久久精品综合 | 中文字幕丝袜制服 | 天天干天天天 | 免费亚洲黄色 | 亚洲精品美女久久17c | 久久久电影 | 国产一区二区精品91 | 国产专区在线视频 | 四虎永久免费 | 69精品视频在线观看 | 日韩欧美一区二区三区在线观看 | 色综合久久中文字幕综合网 | 黄色一级片视频 | 久草在线综合网 | 欧美日韩视频在线 | 香蕉久久久久 | 888av| 一区二区精 | 久久精品中文字幕少妇 | 97天天综合网 | 欧美日韩国产精品一区二区三区 | 天天干天天操天天入 | 久久久免费观看视频 | 成人资源站 | www.激情五月.com | 美女黄频在线观看 | 蜜臀aⅴ国产精品久久久国产 | 亚洲精品在线一区二区三区 | 69国产盗摄一区二区三区五区 | 国产超碰97| 精品国产一区二区三区在线观看 | 超碰在线公开免费 | 亚洲国产激情 | 天天综合中文 | 久久一区二区三区超碰国产精品 | 亚洲电影成人 | 色av男人的天堂免费在线 | 国产一区二区在线影院 | 人人舔人人干 | 色大片免费看 | 久久久穴| 久久精品资源 | 国内精品久久久久久久97牛牛 | 欧美午夜精品久久久久久孕妇 | 精品 激情 | 手机在线观看国产精品 | 天天干天天看 | 天堂麻豆 | 久久久午夜电影 | 黄色av网站在线观看免费 | 99国产在线 | 狠狠色丁香久久婷婷综合_中 | 中文字幕在线播放一区二区 | 黄色免费大片 | 日韩免费 | 日韩a欧美 | 91免费网| 玖草影院 | 91九色蝌蚪视频 | 五月天激情在线 | aaa毛片视频| 成人在线黄色 | 精品一区二区三区电影 | 国产精品av久久久久久无 | 久久久久美女 | 欧美精品一区二区免费 | 久久在线观看视频 | 日韩三级av | 国内精品中文字幕 | 亚洲免费观看在线视频 | 中文乱码视频在线观看 | 国产一级在线视频 | 亚洲一区二区高潮无套美女 | 久久久精品久久日韩一区综合 | 久久久国产一区二区三区四区小说 | 国产免费午夜 | 黄色视屏av | 国产精品久久久久久久午夜片 | 国产又黄又硬又爽 | 久久久久久久久久久网 | 成人精品99 | 91九色综合 | 日韩欧美国产免费播放 | 永久av免费在线观看 | 人人爽人人爽人人爽人人爽 | 国产破处在线视频 | 国产精品麻豆三级一区视频 | 香蕉视频网站在线观看 | 看黄色91 | 国产亚洲精品久久19p | 国产精品激情 | 日本三级国产 | 午夜国产福利在线 | 激情视频在线观看网址 | 亚洲 av网站 | 午夜av免费看 | 国产精彩在线视频 | 国产精品久久久久9999吃药 | 精品91久久久久 | 激情黄色一级片 | 99热只有精品在线观看 | 日韩一区二区三区在线观看 | 丁香伊人网 | 中文字幕日韩精品有码视频 | 免费久久久| 黄色a大片| 国产日女人 | 欧洲性视频 | 免费看一及片 | 最新国产中文字幕 | 国产精品福利无圣光在线一区 | 欧美a视频在线观看 | 久草av在线播放 | 免费看一级黄色 | 国产精品一区二区三区免费视频 | 成人中文字幕在线观看 | 日韩精品在线看 | 高清久久久久久 | 国产成人黄色片 | 激情婷婷综合网 | 高清色免费 | 在线观看黄| 正在播放一区 | 国产在线久久久 | 狠狠躁夜夜a产精品视频 | 亚洲午夜精品久久久久久久久久久久 | 成人免费在线观看电影 | 国产亚洲精品久久久久久电影 | 天天操天天干天天综合网 | 97在线免费观看视频 | 中文字幕在线观看免费观看 | 久久精品免费播放 | 色婷婷婷 | 在线免费观看国产精品 | 91视频免费国产 | 色av婷婷| 麻豆视频免费版 | 91视频-88av| 成人av亚洲 | 亚洲成年人在线播放 | 婷婷天天色 | 伊人伊成久久人综合网站 | .国产精品成人自产拍在线观看6 | 中文字幕久久精品亚洲乱码 | 日韩欧美一区二区三区视频 | 国产亚洲精品久久网站 | 欧美日韩二区三区 | 超薄丝袜一二三区 | 国产99免费 | 在线观看深夜视频 | 欧美成人精品在线 | 99久久er热在这里只有精品66 | 最新av电影网址 | 国产亚洲视频系列 | 亚洲a色 | 国产福利久久 | 久久黄色片子 | 91av资源网 | 中文字幕亚洲综合久久五月天色无吗'' | 久久精品一二三区白丝高潮 | 国产精品美女久久久久久久久 | 韩国中文三级 | 91福利社区在线观看 | 亚洲欧美日韩国产精品一区午夜 | 99超碰在线播放 | 色婷婷国产在线 | 日韩电影在线一区二区 | www.久久免费视频 | 色视频网站免费观看 | 国产精品乱看 | 欧美一级裸体视频 | av不卡中文字幕 | 国产黄色播放 | 亚洲日本在线视频观看 | 午夜国产福利视频 | 成人电影毛片 | 精品国产一区二区三区免费 | 天天摸日日操 | www.天天色 | 亚洲美女视频网 | 国内精品久久久久久久久久 | 亚洲精品在线视频播放 | 啪嗒啪嗒免费观看完整版 | 天堂黄色片 | 中文字幕 婷婷 | 波多野结衣日韩 | 69精品| 伊人影院av | 亚洲一区免费在线 | 99久久精品国产免费看不卡 | 看国产黄色大片 | 91在线超碰 | 国产在线小视频 | 在线观看不卡视频 | 天天操天天吃 | 精品99久久久久久 | 日韩精品五月天 | 亚洲国产一二三 | 成人av直播 | 色播五月激情综合网 | 在线不卡a | 日韩精品不卡 | 久久久久久毛片 | 狠狠干狠狠插 | 激情六月婷婷久久 | 激情婷婷 | 国产精品视频在线观看 | 欧美日韩免费视频 | 国产粉嫩在线 | 国产精品理论视频 | 国产亚洲人 | 色综合久久88色综合天天6 | 国产三级香港三韩国三级 | 黄色三级免费观看 | 婷婷 中文字幕 | 久久96国产精品久久99软件 | 亚洲精品黄色 | 最近高清中文在线字幕在线观看 | 日韩成人在线免费观看 | 综合色影院 | 夜夜操狠狠干 | 国产亚洲日 | 免费观看一区二区三区视频 | 天天操天天操天天操天天操天天操天天操 | 免费精品久久久 | 久久精品视频在线观看 | 99久久精品国产欧美主题曲 | 亚洲少妇自拍 | 丁香婷婷电影 | 欧美成人高清 | 久久视频在线观看免费 | 国产精品一区二区久久 | 中文字幕高清在线播放 | 日韩国产欧美在线播放 | 911国产精品| 国产高h视频| 日日夜夜免费精品视频 | 中文字幕一二 | 国产精品福利午夜在线观看 | 亚洲精品1区2区3区 超碰成人网 | 日韩艹| 国产精品免费久久 | 精品黄色在线观看 | 精品久久久久久久久久久久久久久久 | 欧美日韩免费一区二区 | 国产亚洲欧美在线视频 | 综合中文字幕 | 天天干天天草天天爽 | 在线观看中文字幕av | 日日夜夜天天干 | 国产专区日韩专区 | 91在线播 | 国产在线更新 | 精品国产日本 | 亚洲专区视频在线观看 | 27xxoo无遮挡动态视频 | 五月婷婷六月丁香在线观看 | 国产精品9999久久久久仙踪林 | 午夜av免费看 | 久久99热精品这里久久精品 | 免费在线观看国产黄 | 狠狠婷婷 | 日韩动态视频 | 日本精品久久久久中文字幕 | 亚洲精品动漫成人3d无尽在线 | 久草视频免费播放 | 国产精品久久久久久久久久99 | 人人爽人人舔 | 亚洲手机天堂 | 中文在线亚洲 | av一区二区三区在线观看 | 91免费高清视频 | 免费成人在线电影 | 午夜精品一区二区国产 | 激情久久久 | 欧美日韩视频免费 | 五月婷婷激情五月 | 精品国产精品一区二区夜夜嗨 | 久久永久免费视频 | 美女视频网| 中文字幕亚洲高清 | 国产麻豆电影在线观看 | 亚洲国产手机在线 | 欧美成人在线免费观看 | 人人干天天干 | 久草在线看片 | 亚洲精品大全 | 欧美成年网站 | 99re国产 | 精品国产乱码久久久久久1区二区 | www色com | 在线国产片| 亚洲 欧美日韩 国产 中文 | 国产亚洲久一区二区 | 日日添夜夜添 | 日韩欧美综合在线视频 | 97视频在线观看网址 | 日韩欧美第二页 | 综合色爱| 日日爽日日操 | 91精品视频免费在线观看 | 欧美在线视频一区二区三区 | 久久久综合 | 日韩欧美在线高清 | 国产又粗又硬又长又爽的视频 | 久久久久久久久黄色 | 久久超| 久久久精品一区二区 | 免费手机黄色网址 | 综合网在线视频 |