【AICon】 如何构建高效的 RAG 系统?RAG 技术在实际应用中遇到的挑战及应对策略?>>> 了解详情
写点什么

企业系统集成点测试策略

  • 2013-05-13
  • 本文字数:9980 字

    阅读完需:约 33 分钟

集成是企业应用系统中绕不开的话题。与外部系统的集成点不仅实现起来麻烦,更是难以测试。本文介绍了一种普遍适用的集成点测试策略,兼顾测试的覆盖程度、速度、可靠性和可重复性,为集成点的实现与测试建立一个通用的参考。

背景

本文作为例子介绍的系统是一个典型的 JavaEE Web 应用,基于 Java 6 和 Spring 开发,采用 Maven 构建。该系统需要以 XML over HTTP 的方式集成两个外部系统。

该系统由一支典型的分布式团队交付:业务代表平常在墨尔本工作,交付团队则分布在悉尼和成都。笔者作为技术领导者带领一支成都的团队承担主要交付任务。

痛点

由于需要集成两个外部系统,我们的 Maven 构建 [1] 过程中有一部分测试(使用 JUnit)是与集成相关的。这部分测试给构建过程造成了一些麻烦。

首先是依赖系统的可靠性问题。在被依赖的两个服务之中,有一个服务部署在开发环境中的实例经常会关机维护,而它一旦关机就会导致与其集成的测试无法通过,进而导致整个构建失败。我们的交付团队严格遵守持续集成实践:构建失败时不允许提交代码。这么一来,当我们依赖的服务关机维护时,交付团队正常的工作节奏就会被打乱。

即使没有关机维护,由于开发环境中部署的服务实例仍在不断测试和调优,被依赖的服务实例也不时出现运行性能低、响应时间长等问题,使我们的构建过程也变得很慢,有时甚至会出现随机的构建失败。

被依赖的服务在开发环境下不可靠、性能低,会使应用程序的构建过程也随之变得脆弱而缓慢,从而打击程序员频繁进行构建的积极性,甚至损害持续集成的有效性。作为团队的技术领导者,我希望解决这个问题,使构建可靠而快速地运行,以确保所有人都愿意频繁执行构建。

如何测试集成点

在一个基于 Spring 的应用中,与外部服务的集成通常会被封装为一个 Java 接口以及其中的若干方法。例如“创建某品牌的用户”的服务很可能如下呈现:

<p>public interface IdentityService {</p><p>Customer create(Brand brand, Customer customer);</p>一个实现了 IdentityService 接口的对象会被 Spring 实例化并放入应用上下文,需要使用该服务的客户代码可以通过依赖注入获得该对象的引用,从而调用它的 create 方法。在测试这些客户代码时,始终可以 mock 一个 IdentityService 对象,将其注入被测对象,从而解耦对外部服务的依赖。这是使用依赖注入带来的收益。

因此,我们的问题主要聚焦于集成点本身的测试。

用面向对象的语言来集成一个基于 HTTP 的服务,集成点的设计经常会出现这样一个模式,其中涉及五个主要的组成部分:门面(Façade);请求构造器(Request Builder);请求路由器(Request Router);网络端点(Network End Point);应答解析器(Response Parser)。它们之间的交互关系如下图:

显而易见,在这个模式中,真正需要发出网络请求的只有网络端点这个组件。该组件的作用即是“按照预先规定好的通信方式,向给定的网络地址发出给定的请求,返回应答内容”。对于基于 HTTP 的服务集成而言,网络端点的接口大致如下呈现:

<p>public interface EndPoint {</p><p>Response get(String url);</p><p>Response post(String url, String requestBody);</p><p>Response put(String url, String requestBody);</p>其中 Response 类包含两项主要信息:HTTP 返回码,以及应答正文。

<p>public class Response {</p><p>private final int statusCode;</p><p>private final String responseBody;</p>不难注意到,EndPoint 类所关心的是把正确的请求发送到正确的地址、取回正确的应答。它并不关心这个地址究竟是什么(这是请求路由器组件的责任),也不关心请求与应答包含什么信息(这是请求构造器和应答解析器的责任)。这一特点使得 EndPoint 类的测试完全不需要依赖真实服务的存在。

网络端点的测试

如前所述,EndPoint 类并不关心发送请求的地址,也不关心请求与应答的内容,只关心以正确的方式来发送请求并拿回应答——“正确的方式”可能包括身份认证与授权、必要的 HTTP 头信息等。为了测试这样一个类,我们不需要朝真正的网络服务地址发送请求,也不需要遵循真实的请求 / 应答协议,完全可以自己创造一个 HTTP 服务,用最简单的请求 / 应答文本来进行测试。

Moco[2] 就是专门用于这种场合的测试工具。按照作者的介绍,Moco 是“一个非常容易设置的 stub 框架,主要用于测试与集成”。在 JUnit 测试中,只需要两行代码就可以声明一个 HTTP 服务器,该服务器监听 12306 端口,对一切请求都会以字符串“foo”作为应答:

<p>MocoHttpServer server <b>=</b> httpserver<b>(</b>12306<b>);</b></p><p>server<b>.</b>reponse<b>(</b>"foo"<b>);</b></p>接下来就可以像访问正常的服务器一样,用 Apache Commons HTTP Client 来访问这个服务器。唯一需要注意的是,访问服务器的代码需要放在 running 块中,以确保服务器能被正常关闭:

复制代码
running(server, new Runnable() {
@Override
public void run() throws IOException {
Content content = Request.Get("http://localhost:12306").execute().returnContent();
assertThat(content.asString(), is("foo"));
}
}

当然,作为一个测试辅助工具,Moco 支持很多灵活的配置,感兴趣的读者可以自行查阅文档。接下来我们就来看如何用 Moco 来测试我们系统中的网络端点组件。作为例子,我们这里需要集成的是用于管理用户身份信息的 OpenPTK[3]。OpenPTK 使用自定义的 XML 通信协议,并且每次请求之前要求客户端程序先向 /openptk-server/login 地址发送应用名称和密码以确认应用程序的合法身份。为此,我们先准备一个 Moco server 供测试之用:

复制代码
server = httpserver(12306);
server.post(and(
         by(uri("/openptk-server/login")),
         by("clientid=test_app&clientcred=fake_password"))).response(status(200));

接下来我们告诉要测试的网络端点,应该访问位于 localhost:12306 的服务器,并提供用户名和密码:

复制代码
configuration = new IdentityServiceConfiguration();
configuration.setHost("<a href="http://localhost:12306/">http://localhost:12306</a>");
configuration.setClientId("test_app");
configuration.setClientCredential("fake_password");
xmlEndPoint = new XmlEndPoint(configuration);

然后就可以正式开始测试了。首先我们测试 XmlEndPoint 可以用 GET 方法访问一个指定的 URL,取回应答正文:

复制代码
@Test
public void shouldBeAbleToCarryGetRequest() throws Exception {
  final String expectedResponse = "<message>SUCCESS</message>";
  server.get(by(uri("/get_path"))).response(expectedResponse);
 
  running(server, new Runnable() {
    @Override
    public void run() {
      XmlEndPointResponse response =
        xmlEndPoint.get("<a href="http://localhost:12306/get_path">http://localhost:12306/get_path</a>");
      assertThat(response.getStatusCode(), equalTo(STATUS_SUCCESS));
      assertThat(response.getResponseBody(), equalTo(expectedResponse));
    }
  });
}

实现了这个测试以后,我们再添加一个测试,描述“应用程序登录失败”的场景,这样我们就得到了对 XmlEndPoint 类的 get 方法的完全测试覆盖:

复制代码
@Test(expected = IdentityServiceSystemException.class)
public void shouldRaiseExceptionIfLoginFails() throws Exception {
    configuration.setClientCredential("wrong_password");
 
    running(server, new Runnable() {
        @Override
        public void run() {
            xmlEndPoint.get("<a href="http://localhost:12306/get_path">http://localhost:12306/get_path</a>");
        }
    });
}

以此类推,也很容易给 post 和 put 方法添加测试。于是,在 Moco 的帮助下,我们就完成了对网络端点的测试。虽然这部分测试真的发起了 HTTP 请求,但只是针对位于 localhost 的 Moco 服务器,并且测试的内容也只是最基本的 GET/POST/PUT 请求,因此测试仍然快且稳定。

Moco**** 的前世今生

在 ThoughtWorks 成都分公司,我们为一家保险企业开发在线应用。由于该企业的数据与核心保险业务逻辑存在于 COBOL 开发的后端系统中,我们所开发的在线应用都有大量集成工作。不止一个项目组发出这样的抱怨:因为依赖了被集成的远程服务,我们的测试变得缓慢而不稳定。于是,我们的一位同事郑晔 [4] 开发了 Moco 框架,用它来简化集成点的测试。

除了我们已经看到的 API 模式(在测试用例中使用 Moco 提供的 API)以外,Moco 还支持 standalone 模式,用于快速创建一个测试用的服务器。例如下列配置(位于名为“foo.json”的文件中)就描述了一个最基本的 HTTP 服务器:

复制代码
[
  {
    "response" :   {    
      "text" : "Hello, Moco"  
    }
  }
]

把这个服务器运行起来:

java -jar moco-runner-<version>-standalone.jar -p 12306 foo.json再访问“ http://localhost:12306 ”下面的任意 URL,都会看到“Hello, Moco”的字样。结合各种灵活的配置,我们就可以很快地模拟出需要被集成的远程服务,用于本地的开发与功能测试。

感谢开源社区的力量,来自澳大利亚的 Garrett Heel 给 Moco 开发了一个 Maven 插件 [5],让我们可以在构建过程中适时地打开和关闭 Moco 服务器(例如在运行 Cucumber[6] 功能测试之前启动 Moco 服务器,运行完功能测试之后关闭),从而更好地把 Moco 结合到构建过程中。

目前 Moco 已经被 ThoughtWorks 成都分公司的几个项目使用,并且根据这些项目提出的需求继续演进。如果你有兴趣参与这个开源项目,不论是使用它并给它提出改进建议,还是为它贡献代码,郑晔都会非常开心。

其它组件的测试

有了针对网络端点的测试之后,其他几个组件的测试已经可以不必发起网络请求。理论上来说,每个组件都应该独自隔离进行单元测试;但个人而言,对于没有外部依赖的对象,笔者并不特别强求分别独立测试。只要有效地覆盖所有逻辑,将几个对象联合在一起测试也并无不可。

出于这样的考虑,我们可以针对整个集成点的 façade(即 IdentityService)进行测试。在实例化 IdentityService 对象时,需要 mock[7] 其中使用的 XmlEndPoint 对象,以隔离“发起网络请求”的逻辑:

复制代码
xmlEndPoint = mock(XmlEndPoint.class);
identityService = new IdentityServiceImpl(xmlEndPoint);

然后我们就需要 mock 的 XmlEndPoint 对象表现出几种不同的行为,以便测试 IdentityService(及其内部使用的其他对象)在这些情况下都做出了正确的行为。以“查找用户”为例,XmlEndPoint 的两种行为都是 OpenPTK 的文档里所描述的:

1. 找到用户:HTTP 状态码为“200 FOUND”,应答正文为包含用户信息的 XML;

2. 找不到用户:HTTP 状态码为“204 NO CONTENT”,应答正文为空。

针对第一种(“找到用户”)情况,我们对 mock 的 XmlEndPoint 对象提出期望,要求它在 get 方法被调用时返回一个代表 HTTP 应答的对象,其中返回码为 200、正文为包含用户信息的 XML:

复制代码
when(xmlEndPoint.get(anyString())).thenReturn(
         new XmlEndPointResponse(STATUS_SUCCESS, userFoundResponse));

当 mock 的 XmlEndPoint 对象被设置为这样的行为,“查找用户”操作就应该能找到用户、并组装出合法的结果对象:

复制代码
Customer customer = identityService.findByEmail("<a href="mailto:gigix1980@gmail.com">gigix1980@gmail.com</a>");
assertThat(customer.getFirstName(), equalTo("Jeff"));
assertThat(customer.getLastName(), equalTo("Xiong"));

userFoundResponse 所引用的 XML 字符串中包含了用户信息,当 XmlEndPoint 返回这样一个字符串时,IdentityService 就能把它转换成一个 Customer 对象。这样我们就验证了 IdentityService(以及它内部所使用的其他对象)的功能。

第二种场景(“找不到用户”)的测试也与此相似:

复制代码
@Test
public void shouldReturnNullWhenUserDoesNotExist() throws Exception {
    when(xmlEndPoint.get(anyString())).thenReturn(
         new XmlEndPointResponse(STATUS_NO_CONTENT, null));
    Customer nonExistCustomer =
         identityService.findByEmail("<a href="mailto:not.exist@gmail.com">not.exist@gmail.com</a>");
    assertThat(nonExistCustomer, nullValue());
}

其他操作的测试也与此相似。

集成测试

有了上述两个层面的测试,我们已经能够对集成点的五个组件完全覆盖。但是请勿掉以轻心:100% 测试覆盖率并不等于所有可能出错的地方都被覆盖。例如我们前述的两组测试就留下了两个重要的点没有得到验证:

1. 真实的服务所在的 URL;

2. 真实的服务其行为是否与文档描述一致。

这两个点都是与真实服务直接相关的,必须结合真实服务来测试。另一方面,对这两个点的测试实际上描述功能重于验证功能:第一,外部服务很少变化,只要找到了正确的用法,在相当长的时间内不会改变;第二,外部服务如果出错(例如服务器宕机),从项目本身而言并没有修复的办法。所以真正触碰到被集成的外部服务的集成测试,其主要价值是准确描述外部服务的行为,提供一个可执行的、精确的文档。

为了提供这样一份文档,我们在集成测试中应该尽量避免使用应用程序内实现的集成点(例如前面出现过的 IdentityService),因为如果程序出错,我们希望自动化测试能告诉我们:出错的究竟是被集成的外部服务,还是我们自己编写的程序。我更倾向于使用标准的、接近底层的库来直接访问外部服务:

复制代码
System.out.println("=== 2. Find that user out ===");
GetMethod getToSearchUser = new GetMethod(
         configuration.getUrlForSearchUser("gigix1980@gmail.com"));
getToSearchUser.setRequestHeader("Accept", "application/xml");
httpClient.executeMethod(getToSearchUser);
assertThat(getToSearchUser.getStatusCode(), equalTo(200));
System.out.println(getResponseBody(getToSearchUser));

可以看到,在这段测试中,我们直接使用 Apache Commons HTTP Client 来发起网络请求。对于应答结果我们也并不验证,只是确认服务仍然可用、并把应答正文(XML 格式)直接打印出来以供参考。如前所述,集成测试主要是在描述外部服务的行为,而非验证外部服务的正确性。这种粒度的测试已经足够起到“可执行文档”的作用了。

持续集成

在上面介绍的几类测试中,只有集成测试会真正访问被集成的外部服务,因此集成测试也是耗时最长的。幸运的是,如前所述,集成测试只是用于描述外部服务,所有的功能验证都在网络端点测试(使用 Moco)及其他组件的单元测试中覆盖,因此集成测试并不需要像其他测试那样频繁运行。

Maven 已经对这种情形提供了支持。在 Maven 定义的构建生命周期 [8] 中,我们可以看到有“test”和“integration-test”两个阶段(phase)。而且在 Maven 项目网站上我们还可以看到一个叫“Failsafe”的插件 [9],其中的介绍这样说道:

The Failsafe Plugin is designed to run integration tests while the Surefire Plugins is designed to run unit tests. The name (failsafe) was chosen both because it is a synonym of surefire and because it implies that when it fails, it does so in a safe way.

按照 Maven 的推荐,我们应该用 Surefire 插件来运行单元测试,用 Failsafe 插件来运行集成测试。为此,我们首先把所有集成测试放在“integration”包里,然后在 pom.xml 中配置 Surefire 插件不要执行这个包里的测试:

复制代码
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>${maven-surefire-plugin.version}</version>
  <executions>
    <execution>
      <id>default-test</id>
      <phase>test</phase>
      <goals>
        <goal>test</goal>
      </goals>
      <configuration>
        <excludes>
          <b><exclude>**/integration/**/*Test.java</exclude></b>
        </excludes>
      </configuration>
    </execution>
  </executions>
</plugin>

再指定用 Failsafe 插件执行所有集成测试:

复制代码
<plugin>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>2.12</version>
  <configuration>
    <includes>
      <b><include>**/integration/**/*Test.java</include></b>
    </includes>
  </configuration>
  <executions>
    <execution>
      <id>failsafe-integration-tests</id>
      <phase>integration-test</phase>
      <goals>
        <goal>integration-test</goal>
      </goals>
    </execution>
    <execution>
      <id>failsafe-verify</id>
      <phase>verify</phase>
      <goals>
        <goal>verify</goal>
      </goals>
    </execution>
  </executions>
</plugin>

这时如果执行“mvn test”,集成测试已经不会运行;如果执行“mvn integration-test”,由于“integration-test”是在“test”之后的一个阶段,因此两组测试都会运行。这样我们就可以在持续集成服务器(例如 Jenkins)上创建两个不同的构建任务:一个是提交构建,每次有代码修改时执行,其中不运行集成测试;另一个是完整构建,每天定时执行一次,其中运行集成测试。如此,我们便做到了速度与质量兼顾:平时提交时执行的构建足以覆盖我们开发的功能,执行速度飞快,而且不会因为外部服务宕机而失败;每日一次的完整构建覆盖了被集成的外部服务,确保我们足够及时地知晓外部服务是否仍然如我们期望地正常运行。

对已有系统的重构

如果一开始就按照前文所述的模式来设计集成点,自然很容易保障系统的可测试性;但如果一开始没有做好设计,没有抽象出“网络端点”的概念,而是把网络访问的逻辑与其他逻辑耦合在一起,自然也就难以写出专门针对网络访问的测试,从而使得大量测试会发起真实的网络访问,使构建变得缓慢而不可靠。

下面就是一段典型的代码结构,其中杂糅了几种不同的职责:准备请求正文;发起网络请求;处理应答内容。

复制代码
  PostMethod postMethod = getPostMethod(
    velocityContext, templateName, soapAction);
  new HttpClient().executeMethod(postMethod);
  String responseBodyAsString = postMethod.getResponseBodyAsString();
 
  if (responseBodyAsString.contains("faultstring")) {
    throw new WmbException();
  }
 
  Document document;
  try {
    LOGGER.info("request:\n" + responseBodyAsString);
    document = DocumentHelper.parseText(responseBodyAsString);
  } catch (Exception e) {
    throw new WmbParseException(
      e.getMessage() + "\nresponse:\n" + responseBodyAsString);
  }
 
  return document;

针对每个要集成的服务方法,类似的代码结构都会出现,从而出现了“重复代码”的坏味道。由于准备请求正文、处理应答内容等逻辑各处不同(例如上面的代码使用 Velocity[10] 来生成请求正文、使用 JDOM[11] 来解析应答),这里的重复并不那么直观,自动化的代码检视工具(例如 Sonar)通常也不能发现。因此第一步的重构是让重复的结构浮现出来。

使用抽取函数(Extract Method)、添加参数(Add Parameter)、删除参数(Remove Parameter)等重构手法,我们可以把上述代码整理成如下形状:

复制代码
    // 1. prepare request body
    String requestBody = renderTemplate(velocityContext, templateName);
 
<b>    // 2. execute a post method and get back response body</b>
<b>    PostMethod postMethod = getPostMethod(soapAction, requestBody);</b>
<b>    new HttpClient().executeMethod(postMethod);</b>
<b>    String responseBody = postMethod.getResponseBodyAsString();</b>
<b>    if (responseBodyAsString.contains("faultstring")) {</b>
<b>      throw new WmbException();</b>
<b>    }</b>
 
    // 3. deal with response body
    Document document = parseResponse(responseBody);
    return document;

这时,第 2 段代码(使用预先准备好的请求正文执行一个 POST 请求,并拿回应答正文)的重复就变得明显了。《重构》对这种情况做了介绍 [12]:

如果两个毫不相关的类出现 Duplicated Code,你应该考虑对其中一个使用 Extract Class,将重复代码提炼到一个独立类中,然后在另一个类内使用这个新类。但是,重复代码所在的函数也可能的确只应该属于某个类,另一个类只能调用它,抑或这个函数可能属于第三个类,而另两个类应该引用这第三个类。你必须决定这个函数放在哪儿最合适,并确保它被安置后就不会再在其他任何地方出现。

这正是我们面对的情况,也正是“网络端点”这个概念应该出现的时候。使用抽取函数和抽取类(Extract Class)的重构手法,我们就能得到名为 SOAPEndPoint 的类:

复制代码
public class SOAPEndPoint {
  public String post(String soapAction, String requestBody) {
    PostMethod postMethod = getPostMethod(soapAction, requestBody);
    new HttpClient().executeMethod(postMethod);
    String responseBody = postMethod.getResponseBodyAsString();
    if (responseBodyAsString.contains("faultstring")) {
      throw new WmbException();
    }
    return responseBody;
  }

原来的代码变为使用这个新的类:

复制代码
    // 1. prepare request body
    String requestBody = renderTemplate(velocityContext, templateName);
 
<b>    // 2. execute a post method and get back response body</b>
<b>    // soapEndPoint is dependency injected by Spring Framework</b>
<b>    String responseBody = soapEndPoint.post(soapAction, requestBody);</b>
 
    // 3. deal with response body
    Document document = parseResponse(responseBody);
    return document;

再按照前文所述的测试策略,使用 Moco 给 SOAPEndPoint 类添加测试。可以看到,SOAPEndPoint 的逻辑相当简单:把指定的请求文本 POST 到指定的 URL;如果应答文本包含“faultstring”字符串,则抛出异常;否则直接返回应答文本。尽管名为“SOAPEndPoint”,post 这个方法其实根本不关心请求与应答是否符合 SOAP 协议,因此在测试这个方法时我们也不需要让 Moco 返回符合 SOAP 协议的应答文本,只要覆盖应答中是否包含“faultstring”字符串的两种情况即可。

读者或许会问:既然 post 方法并不介意请求与应答正文是否符合 SOAP 协议,为什么这个类叫 SOAPEndPoint?答案是:在本文没有给出实现代码的 getPostMethod 方法中,我们需要填入一些 HTTP 头信息,这些信息是与提供 Web Services 的被集成服务相关的。这些 HTTP 头信息(例如应用程序的身份认证、Content-Type 等)适用于所有服务方法,因此可以抽取到通用的 getPostMethod 方法中。

随后,我们可以编写一些描述性的集成测试,并用 mock 的方式使所有“使用 SOAPEndPoint 的类”的测试不再发起网络请求。至此,我们就完成了对已有的集成点的重构,并得到了一组符合前文所述的测试策略的测试用例。当然读者可以继续重构,将请求构造器与应答解析器也分离出来,在此不再赘述。

小结

在开发一个“重集成”的 JavaEE Web 应用的过程中,自动化测试中对被集成服务的依赖使得构建过程变得缓慢而脆弱。通过对集成点实现的考察,我们识别出一个典型的集成点设计模式。基于此模式以及与之对应的测试策略,借助 Moco 这个测试工具,我们能够很好地隔离对被集成服务的依赖,使构建过程快速而可靠。

随后我们还考察了已有的集成点实现,并将其重构成为前文所述的结构,从而将同样的测试策略应用于其上。通过这个过程,我们验证了:本文所述的测试策略是普遍适用的,遗留系统同样可以通过文中的重构过程达到解耦实现、从而分层测试的目标。


[1] “构建”一词在本文中是指使用自动化的构建工具(例如 Maven)将源代码变为可交付的软件的过程。一般而言,JavaEE 系统的构建过程通常包括编译、代码检查、单元测试、集成测试、打包、功能测试等环节。

[2] https://github.com/dreamhead/moco

[3] http://www.openptk.org/

[4] http://dreamhead.blogbus.com/

[5] https://github.com/GarrettHeel/moco-maven-plugin

[6] http://cukes.info/

[7] 笔者使用的 mock 框架是 Mockito: https://code.google.com/p/mockito/

[8] http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html

[9] http://maven.apache.org/plugins-archives/maven-failsafe-plugin-2.12.4/

[10] http://velocity.apache.org/

[11] http://jdom.org/

[12] 《重构》,3.1 小节。


感谢侯伯薇对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2013-05-13 11:177935

评论

发布
暂无评论
发现更多内容

苏宁云商向江旭:是时候让技术成为新司机了!

TGO鲲鹏会

字节跳动的增长密码

池建强

字节跳动 张一鸣

服务降级的常见套路

松花皮蛋me

Java

我使用了哪些生产力工具?

Steve

效率工具 软件 Alfred Notion 推荐

Doris 一种实时多维分析的解决方案

迹_Jason

大数据

NVidia Docker介绍

薛磊

Docker

中台之路,从平台到中台的思考与实践(一)

孤岛旭日

架构 中台 企业中台 企业架构

redis数据结构介绍-第一部分 SDS,链表,字典

Nick

redis 源码 数据结构 源码分析 算法

Gitlab CI/CD 中的 Cache 机制

Chong

DevOps gitlab cicd

Linux的proc文件系统编程

韩超

开源这件事儿,越来越“声势浩大”了

赵钰莹

Apache GitHub 阿里巴巴 开源 腾讯

聊聊分心这件事

Jackey

人间至味——苦瓜

三只猫

人生 美食 生活

自动驾驶复苏在2020

陈思

人工智能 自动驾驶

程序员通过哪些方式来赚钱?

一尘观世界

程序员 外包 自由职业 副业 赚钱

特定系统的Linux的构建

韩超

基于RocketMQ实现分布式事务 - 完整示例

清幽之地

Java 分布式事务 RocketMQ 微服务

NVidia-Docker2 性能优化

薛磊

Docker gpu nvidia container

【JAVA】感受下JDK14的空指针提示

遇见

Java jdk jep

中台之路,从平台到中台的思考与实践(二)

孤岛旭日

架构 中台 企业中台 企业架构

ELF文件格式

韩超

面试官,不要再问我三次握手和四次挥手

猿人谷

面试 TCP 三次握手 四次挥手

[KubeFlow] MPI-Operator深度解读

薛磊

Docker gpu kubeflow Kubernetes

Kylin 实时流处理技术探秘.笔记

迹_Jason

大数据

从西游到武侠——确定性与不确定性

伯薇

个人成长 管理 确定性 不确定性

百度主任架构师谭待:打造非职权技术管理机制

TGO鲲鹏会

高手和普通人的差距,不看不知道,一看吓一跳

熊斌

学习

3000w人民币的学费——我的决策反思

孤岛旭日

数据中台 架构 中台 企业中台 企业架构

Docker Swarm 踩坑

Steve

Docker Docker Swarm 技术 容器 踩坑

纯技术改造,技术如何驱动需求,我有话说

一叶而不知秋

项目管理 架构 技术

微服务架构深度解析与最佳实践-第一部分

kimmking

微服务 最佳实践 深度解析 高可用

企业系统集成点测试策略_Java_熊节_InfoQ精选文章