Apollo客户端配置获取深度解析

2023-11-02

Apollo客户端配置获取深度解析

Apollo(阿波罗)是携程框架部门研发的开源配置管理中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性。这篇文章主要来剖析客户端获取配置的逻辑及ConfigService长连接实现的原理,帮助使用者深入了解Apollo配置中心。

一、Apollo整体架构

企业的配置中心框架由四个核心模块及三个辅助服务发现模块。

四个核心模块是:

1ConfigService:主要功能是提供配置获取接口、提供配置推送接口、服务于Apollo客户端

2AdminService:主要功能是提供配置管理接口、提供配置修改发布接口、服务于管理界面Portal

3Client:主要作用是为应用获取配置,支持实时更新、通过MetaServer获取ConfigService的服务列表、使用客户端软负载SLB方式调用ConfigService

4Portal:主要作用是配置管理界面、通过MetaServer获取AdminService的服务列表、使用客户端软负载SLB方式调用AdminService

三个辅助服务发现模块:

1Eureka用于服务发现和注册、Config/AdminService注册实例并定期报心跳和ConfigService住在一起部署

  1. MetaServer:Portal通过域名访问MetaServer获取AdminService地址列表、Client通过域名访问MetaServer获取ConfigService地址列表、相当于一个Eureka Proxy、逻辑角色和ConfigService住在一起部署

3NginxLB:和域名系统配合,协助Portal访问MetaServer获取AdminService地址列表、和域名系统配合,协助Client访问MetaServer获取ConfigService地址列表、和域名系统配合,协助用户访问Portal进行配置管理

 

Apollo配置中心客户端负责访问配置中心服务来获取配置数据,并实现配置热生效,但是对于像端口号这种原生配置,是需要重启服务才能生效的。

Apollo配置中心客户端核心包是apollo-core.jar和apollo-client.jar。

具体依赖结构请看下图:

 

 

二、客户端获取配置逻辑

       客户端实时获取配置信息主要依赖三个Http请求,如下图:

 

 

 

 

配置中心客户端主要类图如下:

 

    • 1.1. 获取服务列表请求

请求1是客户端从注册中心获取ConfigService服务列表,保证长连接能正常和某个可用ConfigService服务节点保持连接和短连接从哪个节点获取配置。当无法连接注册中心时,重试逻辑采用二进制指数退避算法,算法如下:

 

 public long fail() {

    long delayTime = lastDelayTime;

    if (delayTime == 0) {

      delayTime = delayTimeLowerBound;

    } else {

      delayTime = Math.min(lastDelayTime << 1, delayTimeUpperBound);

    }

    lastDelayTime = delayTime;

    return delayTime;

}

请求url是http://apollo.meta/services/config?appId={appId}&ip={ip}

    1. 获取配置请求

获取配置请求是客户端每隔五分钟定时拉取,定时拉取保证了在长连接无法正常监听配置变化实时,在最悲观的情况下客户端在配置发布后五分钟,客户端也能获取到最新发布的配置信息。短连接接口流量控制策略是采用谷歌guava RateLimiter,就是基于令牌桶算法实现,代码实现简易。可动态配置令牌生成速率,令牌速率为每秒两个,获取令牌超时时间是五秒。

  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());

  }

//获取配置信息

  private ApolloConfig 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;

 

    List<ServiceDTO> configServices = getConfigServices();

    String url = null;

    retryLoopLabel:

    for (int i = 0; i < maxRetries; i++) {

      List<ServiceDTO> randomConfigServices = Lists.newLinkedList(configServices);

      Collections.shuffle(randomConfigServices);

      //Access the server which notifies the client first

      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

          }

        }

 

        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_httpUtil.doGet(request, ApolloConfig.class);

          m_configNeedForceRefresh.set(false);

          m_loadConfigFailSchedulePolicy.success();

 

          transaction.addData("StatusCode", response.getStatusCode());

          transaction.setStatus(Transaction.SUCCESS);

 

          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();

      }

 

    }

    String message = String.format(

        "Load Apollo Config failed - appId: %s, cluster: %s, namespace: %s, url: %s",

        appId, cluster, m_namespace, url);

    throw new ApolloConfigException(message, exception);

  }

配置实时生效的单靠短连接肯定是不能完成的,需要和长连接配合完成。下面将介绍长连接实现原理及长连接和短连接如何配合完成配置实时生效。

    1. 长连接实现原理

长连接,顾名思义就是客户端与服务端建立连接后不断开,一个客户端就是一个长连接,,而不是一个Namespace一个长连接,如果想要实现动态关闭某个appId长连接,是可以通过修改下面代码实现,同时通过修改短连接请求,添加开与关标志位实现。Apollo配置中心长连接实现逻辑是具体是什么呢?

      • 1.3.1. 客户端主要逻辑

  private void doLongPollingRefresh(String appId, String cluster, String ataCenter, String secret) {

    final Random random = new Random();

ServiceDTO lastServiceDto = null;

//循环发生请求,实现客户端长连接

    while (!m_longPollingStopped.get() && !Thread.currentThread().isInterrupted()) {

      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

        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_httpUtil.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(lastServiceDto, response.getBody());

        }

 

        //try to load balance

//304: NOT_MODIFIED(304, "Not Modified"),

        //random.nextBoolean:伪均匀分布的布尔值,保证长连接均匀分布在各节点上

        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();

      }

    }

}

从上面代码可以看出,长连接实现逻辑是一个无限循环发请求的过程。返回200更新本地配置,返回304说明配置没有改变。

 

      • 1.3.2. 服务端主要逻辑

       Namespace在点击发布按钮时会项ReleaseMessage表里插入一条信息,AppId+集群+Namespace(apollo+default+application),代码如下

DeferredResultWrapper deferredResultWrapper = new DeferredResultWrapper();

    Set<String> namespaces = Sets.newHashSet();

    Map<String, Long> clientSideNotifications = Maps.newHashMap();

    Map<String, ApolloConfigNotification> filteredNotifications = filterNotifications(appId, notifications);

 

    for (Map.Entry<String, ApolloConfigNotification> notificationEntry : filteredNotifications.entrySet()) {

      String normalizedNamespace = notificationEntry.getKey();

      ApolloConfigNotification notification = notificationEntry.getValue();

      namespaces.add(normalizedNamespace);

      clientSideNotifications.put(normalizedNamespace, notification.getNotificationId());

      if (!Objects.equals(notification.getNamespaceName(), normalizedNamespace)) {

        deferredResultWrapper.recordNamespaceNameNormalizedResult(notification.getNamespaceName(), normalizedNamespace);

      }

    }

 

    if (CollectionUtils.isEmpty(namespaces)) {

      throw new BadRequestException("Invalid format of notifications: " + notificationsAsString);

    }

 

    Multimap<String, String> watchedKeysMap =

        watchKeysUtil.assembleAllWatchKeys(appId, cluster, namespaces, dataCenter);

 

Set<String> watchedKeys = Sets.newHashSet(watchedKeysMap.values());

//获取ReleaseMessage的最新数据,然后通知客户端配置信息有修改

List<ReleaseMessage> latestReleaseMessages =

        releaseMessageService.findLatestReleaseMessagesGroupByMessages(watchedKeys);

    /**

     * Manually close the entity manager.

     * Since for async request, Spring won't do so until the request is finished,

     * which is unacceptable since we are doing long polling - means the db connection would be hold

     * for a very long time

     */

    entityManagerUtil.closeEntityManager();

 

List<ApolloConfigNotification> newNotifications = getApolloConfigNotifications(namespaces, clientSideNotifications, watchedKeysMap,latestReleaseMessages);

服务端保持长连接的核心就不得不提DeferredResultWrapper这个类,这是对DeferredResult-异步请求处理的一个包装类,使用DeferredResult的流程:

1.客户端发起异步请求

2. 请求到达ConfigService被挂起

  1. 向客户端进行响应,分为两种情况:
    3.1 ReleaseMessage有新数据插入,说明配置发生改变,调用DeferredResult.setResult(),请求被唤醒,返回结果,response.getStatusCode()等于 200,此时客户端将发送一个短连接去获取最新配置信息,
    3.2 超时,超时时间为60秒,返回一个你设定的结果,response.getStatusCode()等于 304
  2. 客户端得到响应,while循环,在进行下一次的请求发送。

综上所述可以看出客户端实时获取最新配置就是采用这种半推半拉的方式,长连接逻辑在配置没有被修改时60秒一个Http请求,当配置发生修改时,被挂起的请求就立即返回,通知客户端配置发生修改,客户端发送一个短连接获取最新配置信息。

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Apollo客户端配置获取深度解析 的相关文章

  • 为什么 JavaFX API 不包含在 Java 8 J2SE 中? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 有谁知道为什么 JavaFX 8 仍然不是即将推出的 Java 8 中的日常 J2SE API 显示所有 Java 组件的技术图清楚地将 Jav
  • 如何消除 Java BoxLayout 中的间距?

    I programmed following ListPanel 使用 JavaScriptBoxLayout public class ListPanel extends JPanel private ArrayList
  • Amazon Elasticache Redis 集群 - 无法获取端点

    我需要获取 Amazon Elasticache 中 Redis 集群的终端节点 以下代码适用于 Memcached 集群 但不适用于 Redis import com amazonaws auth AWSCredentials impor
  • Android 中的 java.util.Observable 是线程安全的吗?

    Android 中的 java util Observable 是线程安全的吗 这文档 http developer android com reference java util Observable html说只有deleteObser
  • 如何抑制 Cucumber/Junit 断言堆栈跟踪

    我有一个黄瓜场景 该步骤使用assertEquals 我的结果报告显示了对最终用户不友好的堆栈跟踪 我怎样才能抑制它 Scenario Add two numbers Given I have two inputs 3 and 2 When
  • ListView:防止视图回收

    我有一个使用回收视图的 ListView 我试图阻止视图被回收 所以我使用 setHasTransientState android support v4 view ViewCompatJB setHasTransientState Vie
  • 按位运算符简单地翻转整数中的所有位?

    我必须翻转整数的二进制表示形式中的所有位 鉴于 10101 输出应该是 01010 当与整数一起使用时 完成此操作的按位运算符是什么 例如 如果我正在编写类似的方法int flipBits int n 什么会进入身体 我只需要翻转数字中已经
  • Java - toString 到 Color

    我一整天都在努力解决这个问题 基本上我做了一个 for 循环 将条目添加到数组列表中 其中一项是 颜色 变量 我已经用过random nextInt为颜色构造函数的红色 绿色和蓝色部分创建新值 我还设置了一个toString方法 这样我就可
  • 如何在 Python 中加密并在 Java 中解密?

    我正在尝试在 Python 程序中加密一些数据并将其保存 然后在 Java 程序中解密该数据 在Python中 我像这样加密它 from Crypto Cipher import AES KEY 1234567890123456789012
  • 为本地@ExceptionHandler编写JUnit测试

    我有以下控制器 class Controller ResponseStatus HttpStatus OK RequestMapping value verifyCert method RequestMethod GET public vo
  • 欧拉项目 45

    我还不是一名熟练的程序员 但我认为这是一个有趣的问题 我想我应该尝试一下 三角形 五边形 六边形 数字由以下生成 公式 三角形 T n n n 1 2 1 3 6 10 15 五边形 P n n 3n 1 2 1 5 12 22 35 六角
  • 单元测试、集成测试还是设计中的问题?

    我编写了我的第一个单元测试 我认为它过于依赖其他模块 我不确定是否是因为 这是一个复杂的测试 我实际上已经编写了集成测试或 我的设计有问题 我首先要说的是 虽然我有大约 4 年的开发经验 但我从未学过 也没有人教过自动化测试 我刚刚使用 H
  • Java8 项目上的 SonarQube 给出 jacoco-Exception

    我刚刚下载了最新版本 SonarQube 4 3 然后尝试使用以下命令构建 java 8 项目 mvn clean install mvn sonar sonar 这给了我下面的例外 谷歌搜索 我的印象是这是一个早期的问题 应该已经解决 h
  • Java:java.util.Preferences 失败

    我的程序将加密的产品密钥数据保存到计算机上java util Preferences类 系统首选项 而不是用户 问题是 在 Windows 和 Linux 上 尚未在 OSX 上测试过 但可能是相同的 如果我不运行该程序sudo或者具有管理
  • 如何加载图像文件到ImageView?

    我试图在从文件选择器中选择图像文件后立即显示该图像文件 文件选择器仅限于 png 和 jpg 文件 所选文件存储在文件类型的变量中 为此 我设置了一个 ImageView 我希望用这个新文件设置图像 唯一的问题是它的类型是文件而不是图像 如
  • 内部类的访问修饰符[重复]

    这个问题在这里已经有答案了 可能的重复 受保护 公共内部类 https stackoverflow com questions 595179 protected public inner classes 我确信这个问题已经被问过 但我找不到
  • AES 密钥是随机的吗?

    AES 密钥可以通过此代码生成 KeyGenerator kgen KeyGenerator getInstance AES kgen init 128 but 如果我有一个 非常可靠 的生成随机数的方法 我可以这样使用它吗 SecureR
  • 检查 Java 字符串实例是否可能包含垃圾邮件数据的最简单方法

    我有一个迭代 String 实例的过程 每次迭代对 String 实例执行很少的操作 最后 String 实例被持久化 现在 我想为每次迭代添加一个检查 String 实例是否可能是垃圾邮件的检查 我只需验证 String 实例不是 成人材
  • 如何让JComboBox中的内容居中显示?

    目前我有这个JComboBox 我怎样才能将其中的内容居中 String strs new String 15158133110 15158133124 15158133458 JComboBox com new JComboBox str
  • Swing:创建可拖动组件...?

    我在网上搜索了可拖动 Swing 组件的示例 但我发现示例不完整或不起作用 我需要的是一个摇摆组件那可以是dragged通过鼠标 在另一个组件内 被拖拽的时候 应该已经 改变它的位置 而不仅仅是 跳 到目的地 我很欣赏无需非标准 API 即

随机推荐

  • 【MongoDB】Ubuntu22.04 下安装 MongoDB

    文章目录 Ubuntu 22 04 安装 MongoDB 后台启动 MongoDB shell 连入 MongoDB 服务 MongoDB 用户权限认证 创建 root 用户 开启认证 重启 MongoDB 服务 创建其他用户 查看用户信息
  • cnn图像质量评价

    参考 https blog csdn net moxibingdao article details 107096783 上面左图为原图 中间为经过JPEG2000压缩后的图 右图为高斯模糊后的图 从清晰度来讲 肯定第一幅图质量更高 质量评
  • BGP--边界网关协议

    目录 BGP和IGP区别 BGP协议关注点 BGP数据包 open包 Keeplive包 update包 notification包 Route refresh包 BGP的状态机 IDLE 空闲状态 Connect 连接状态 Opensen
  • 【机器学习】KNN算法介绍及py实现(详细代码,通俗易懂)

    KNN算法 K Nearest Neighbors 目标 看完这篇博客你将学会 用KNN算法来对数据进行分类 在这里 我们将用knn对一个顾客数据集进行分类 不过什么是knn呢 什么是K Nearest Neighbors 直观翻译就是k个
  • JenkinsDay18-查看服务器有哪些JOB

    http istester com jenkins 468 html
  • 取模(mod)与取余(rem)的区别

    1 取余 rem 3 2 1 rem 3 2 1 rem 3 2 1 rem 3 2 1 2 取模 mod 3 2 1 mod 3 2 1 mod 3 2 1 mod 3 2 1 由此可以看出 rem和mod是有符号区别的 当除数与被除数的
  • Golang 面试题总结

    一 基础部分 Go 语言的设计哲学 简单 显式 组合 并发和面向工程 简单 是指 Go 语言特性始终保持在少且足够的水平 不走语言特性融合的道路 但又不乏生产力 简单是 Go 生产力的源泉 也是 Go 对开发者的最大吸引力 显式 是指任何代
  • UE4 计算两点之间的某个点

    UE4 计算两点之间的某个点
  • IO/NIO 例子

    题目 passport日志由以下三个字段组成 用户名 访问时间 访问者的IP地址 要求在passport日志中进行以下操作 1 找到访问次数最多的用户名 并求出访问次数 2 找到指定用户的访问记录 要求用IO NIO实现 思路如下 1 首先
  • 【JS基础】(一)JavaScript简介及在HTML中使用JavaScript

    一 JavaScript简介 1 JavaScript的实现 JavaScript 是一种专为与网页交互而设计的脚本语言 由下列三个不同的部分组成 1 核心 ECMAScript 由 ECMA 262 定义 提供核心语言功能 描述了该语言的
  • linux centos安装minio

    第一步 进入 opt 目录 创建minio文件夹 cd opt mkdir minio 第二步 wget下载安装包 命令 wegt https dl minio io server minio release linux amd64 min
  • 渗透测试常用Python工具全集

    如果你从事漏洞研究 逆向工程或者渗透测试 应该绝对试试 Python 网络 Scapy Scapy3k 发送 嗅探 解析和伪造网络数据包 可交互使用或作为一个库使用 pypcap Pcapy 和 pylibpcap 一些不同的libpcap
  • 华为OD机试 - 二叉树中序遍历(Java

    题目描述 根据给定的二叉树结构描述字符串 输出该二叉树按照中序遍历结果字符串 中序遍历顺序为 左子树 根结点 右子树 输入描述 由大小写字母 左右大括号 逗号组成的字符串 字母代表一个节点值 左右括号内包含该节点的子节点 左右子节点使用逗号
  • org.springframework.orm.jpa.JpaSystemException: could not execute query;

    报错来源 在配置好的idea上 把代码生成器自动生成的代码导入带项目中 直接进行findALl全查 报错 如下图 尝试解决方法 数据库运行sql语句 查询成功 数据ok 数据乱码 数据库编码格式utf8 数据库可插入正常中文 清空缓存 重新
  • PLSQL Developer 14安装

    资源 百度网盘 链接 https pan baidu com s 1A4DeaKPF7y 0o90nVKFbZA pwd 6udw 提取码 6udw 阿里网盘 PLSQL Developer 14破解版 https www aliyundr
  • [JAVA]移除特定的链表元素

    在java中 移除链表中特定的元素 class ListNode int val ListNode next ListNode ListNode int val this val val public class Test public L
  • FinsTCP协议报文详细分析

    Begin 前言 今天跟大家分享一下关于欧姆龙PLC的Fins协议的协议说明 欧姆龙PLC的Fins协议是公开的协议 大家可以去官网下载 但是由于原文档内容较多 也比较复杂 所以很多人可能看不明白 所以做了一个精简的整理版本 欧姆龙Fins
  • grep命令常用用法示例

    参数列表 color auto 或者 color 表示对匹配到的文本着色显示 i 在搜索的时候忽略大小写 n 显示结果所在行号 c 统计匹配到的行数 注意 是匹配到的总行数 不是匹配到的次数 o 只显示符合条件的字符串 但是不整行显示 每个
  • 错误: 编码 GBK 的不可映射字符 (0x80)

    在我想要在命令行使用println输出一些中文的时候 发现编码出现错误 原因 java程序在编译的时候 需要使用JDK开发工具包中的JAVAC EXE命令 而JDK开发工具包是国际版的 默认格式为UNICODE的编码格式 因此在默认情况下
  • Apollo客户端配置获取深度解析

    Apollo客户端配置获取深度解析 Apollo 阿波罗 是携程框架部门研发的开源配置管理中心 能够集中化管理应用不同环境 不同集群的配置 配置修改后能够实时推送到应用端 并且具备规范的权限 流程治理等特性 这篇文章主要来剖析客户端获取配置