写点什么

使用 Spring Cloud 连接不同服务

2016 年 7 月 18 日

主要结论

  • Spring Cloud 为微服务系统中相互依赖的服务提供了丰富的连接选项。
  • Spring Cloud Config 为配置数据提供了通过 Git 管理的版本控制机制,并能在无需重启动的情况下对此类数据进行动态刷新。
  • 通过将 Spring Cloud 与 Netflix Eureka 以及 Ribbon 组件配合使用,应用程序服务将能用更为动态的方式相互发现,并能通过专用负载平衡器代理将负载平衡决策推送至客户端服务。
  • 系统的边缘位置依然有诸如 AWS ELB 等负载平衡解决方案的一席之地,这里的传入流量还无法控制。
  • 针对中间层微服务之间的通信,Ribbon 提供了一种更为可靠和高性能的解决方案,该方案不依赖特定的云供应商。

简介

随着转向基于微服务的体系结构,我们开始面临一项重要决策:如何将不同服务连接在一起?单层系统(Monolithic system)中的不同组件可以通过简单的方法调用进行通信,但微服务系统中的不同组件很有可能需要借助 REST、Web 服务,或某种类似 RPC 的机制实现网络通信。

在单层系统中,可以完全避免服的连接方面遇到的问题,让每个组件根据需求创建自己的依存项。但实际上我们很少会这样做。组件和依存项之间的这种紧密耦合会使得系统过于僵硬,会对测试工作产生不利影响。此时我们会选择让组件的依存关系外化(Externalise),并在创建组件时直接注入这样的关系,依存关系的注入主要可用于类和对象的连接。

假设打算通过一系列微服务实现一个应用程序,可以使用与单层系统类似的连接选项。依存项的地址可硬编码到程序中,借此将服务紧密连接在一起。或者也可以将所依赖的服务地址外化,并在部署或运行的时候提供这些服务。本文将介绍在微服务应用程序的构建过程中,如何通过 Spring Boot 和 Spring Cloud 实现这些选项。

我们假设了下图所示的一个名为repmax的简单微服务系统:

Repmax 系统

Repmax 应用程序可以记录追踪用户的举重成绩,并用每次举重前五名选手的成绩生成排行榜。其中logbook服务负责通过 UI 收集每次练习的数据并存储每位用户的完整历史信息。当用户在练习完毕录入成绩后,logbook会将此次举重的详细信息发送至leaderboard服务。

从图中可以看到,logbook服务需要依赖leaderboard服务。从最佳实践的角度考虑,我们将这个依存项抽象为LeaderBoardApi接口:

复制代码
public interface LeaderBoardApi {
void recordLift(Lift lift);
}

由于这是个 Spring 应用程序,需要使用RestTemplate处理 logbook 和 leaderboard 服务之间通信的细节:

复制代码
abstract class AbstractLeaderBoardApi implements LeaderBoardApi {
private final RestTemplate restTemplate;
public AbstractLeaderBoardApi() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new FormHttpMessageConverter());
this.restTemplate = restTemplate;
}
@Override
public final void recordLift(Lifter lifter, Lift lift) {
URI url = URI.create(String.format("%s/lifts", getLeaderBoardAddress()));
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.set("exerciseName", lift.getDescription());
params.set("lifterName", lifter.getFullName());
params.set("reps", Integer.toString(lift.getReps()));
params.set("weight", Double.toString(lift.getWeight()));
this.restTemplate.postForLocation(url, params);
}
protected abstract String getLeaderBoardAddress();
}

AbstractLeaderBoardApi类可以捕获针对leaderboard服务创建POST请求的全部逻辑,并可通过子类指定leaderboard服务的准确地址。

将多个微服务相互连接最简单的方法可能就是将每个服务需要的依存项地址硬编码到程序中。这相当于在单层系统的世界中通过硬编码的方式实现依赖项的具现化(Instantiation)。这一点可以在StaticWiredLeaderBoardApi类中轻松实现:

复制代码
public class StaticWiredLeaderBoardApi extends AbstractLeaderBoardApi {
@Override
protected String getLeaderBoardAddress() {
return "http://localhost:8082";
}
}

硬编码方式指定的服务地址使得我们能够快速上手,但在现实环境中这样做有些不太实际。服务的每个不同部署需要自定义编译,这一做法很快会变得充满痛苦并且容易出错。

如果要部署的是单层系统,并且希望对应用程序进行重构以消除硬编码的地址,首先需要将地址信息外化至配置文件。微服务应用程序也可以使用相似的方法:将地址信息推送至配置文件,并让所实现的 API 从配置中读取地址。

Spring Boot 使得配置参数的定义和注入工作变得更简单。只要将地址参数加入application.properties文件即可:

leaderboard.url=http://localhost:8082随后可以使用@Value标注(Annotation)将这个参数注入ConfigurableLeaderBoardApi

复制代码
public class ConfigurableLeaderBoardApi extends AbstractLeaderBoardApi {
private final String leaderBoardAddress;
@Autowired
public ConfigurableLeaderBoardApi(@Value("${leaderboard.url}") String leaderBoardAddress) {
this.leaderBoardAddress = leaderBoardAddress;
}
@Override
protected String getLeaderBoardAddress() {
return this.leaderBoardAddress;
}
}

Spring Boot 对 Externalized Configuration 的支持使得我们不仅可以通过修改配置文件更改leaderboard.url的值,而且可以在启动应用程序时指定环境变量:

LEADERBOARD_URL=http://repmax.skipjaq.com/leaderboard java -jar repmax-logbook-1.0.0-RELEASE.jar随后即可在不更改代码的情况下将logbook服务实例指向任何一个leaderboard服务实例。如果系统符合 12 factor 原则,环境中很可能已经包含了连接信息,因此可通过简单的工作将其直接映射至应用程序。

诸如 Cloud Foundry Heroku 等平台即服务(PaaS)系统会将数据库和消息系统等托管服务的连接信息暴露到环境中,这样即可用完全相同的方式连接这些依赖项。实际上,将两个服务连接在一起,以及将一个服务与相应的数据存储连接在一起,这两种做法并没有什么本质差异,都只是将两个分布式系统连接在一起。

超越点对点连接

对于比较简单的应用程序,为依存项的地址使用外部配置就已足够。然而对于任何规模的应用程序,我们需要的可能不仅仅是简单的点对点连接,可能还希望实现某种形式的负载平衡。

如果每个服务都直接依赖某一下游服务实例,下游出现的任何故障都可能造成最终用户遇到严重问题。同理,如果下游服务超载,用户将会面临响应时间延长的问题。此时我们需要的是负载平衡。

与其直接依赖一个下游实例,我们更希望通过一组下游服务实例分摊负载。如果这些实例中有一个故障或超载,其他实例可以接手处理任务。为这种体系结构实现负载平衡的最简单方法是使用负载平衡代理。下图展示了在 Amazon Web Services 部署中使用 Elastic Load Balancing 实现这种方式的具体做法:

为排行榜应用 ELB

这种情况下无需让logbook服务直接与leaderboard服务通信,而是可以使用 ELB 对每个请求进行路由。ELB 会将每个请求路由至某一后端leaderboard服务。通过让 ELB 充当中介,可将负载分摊到多个 leaderboard 实例,这有助于减少每个实例的负担。

ELB 的负载平衡是动态的,运行过程中可以给后端添加新的实例,因此如果传入流量激增,即可启动更多leaderboard实例加以应对。

Spring Boot 应用程序可使用 actuator 暴露供 ELB 定期监控的/health端点。能够响应此类运行状况检查操作的实例会保留在 ELB 的活跃集(Active set)中,如果多次检查均未响应,相应的实例会从服务中移除。

在我们的系统中,leaderboard服务不是唯一能通过负载平衡获益的服务。logbook服务以及前端 UI 均能借助负载平衡机制实现更好的可扩展性和弹性。

动态重配置

无论使用 AWS ELB Google Compute Load Balancing ,或者使用 HAProxy 或 NGINX 自行搭建负载平衡代理,都需要将服务与负载平衡器相互连接。

此时一种方法是为每个负载平衡器提供一个「众所周知」的 DNS 名称,例如leaderboard.repmax.local,并使用上文提到的静态连接方式将其硬编码至应用程序中。由于 DNS 系统本身的灵活性,这种方法已经可以做到相当灵活。然而使用硬编码的名称意味着要在运行服务的每个环境中配置一台 DNS 服务器。在开发过程中,由于需要为多种多样的操作系统提供支持,提供定制化 DNS 的操作就显得尤为麻烦。此时更好的做法是使用类似上文leaderboard.url的例子那样,将负载平衡器的地址自然地注入服务。

在 AWS 和 GCP 等云环境中,负载平衡器(及其地址)会频繁变动。当负载平衡器被删除并重建后,通常会使用一个新的地址。如果将负载平衡器的地址硬编码到程序中,为了应对地址的变化,必须在每次改变后重新编译代码。但通过使用外化的配置,只需更改配置文件并重启动服务即可。

为了应对负载平衡器地址不断变化这一本质,DNS 是一种很方便的做法。每个负载平衡器都可分配一个固定的 DNS 名称,并将这个名称注入所调用的服务。在重建负载平衡器时,其 DNS 名称可重映射至负载平衡器的新地址。如果准备在环境中运行 DNS 服务器,就很适合使用这种基于 DNS 的方法。如果不想运行 DNS,但依然希望对负载平衡器进行动态重配置,此时可以考虑使用 Spring Cloud Config

Spring Cloud Config 会运行一个名为 Config Server 的小巧服务,并通过 REST API 提供可集中访问的配置数据。默认情况下配置数据存储在一个 Git 仓库中,并可通过标准的PropertySource抽象暴露给 Spring Boot 服务。使用PropertySource可将本地属性文件中包含的配置与 Config Server 中存储的配置无缝结合在一起。对于本地开发,可以使用来自本地属性文件的配置,并只在将应用程序部署在现实环境时才覆盖这些配置信息。

为使用 Spring Cloud Config 取代ConfigurableLeaderBoardApi,首先可以用所需配置初始化一个 Git 代码库:

复制代码
mkdir -p ~/dev/repmax-config-repo
cd ~/dev/repmax-config-repo
git init
echo 'leaderboard.lb.url=http://some.lb.address' >> repmax.properties
git add repmax.properties
git commit -m 'LB config for the leaderboard service'

repmax.properties文件中包含repmax应用程序default配置文件的设置。如果希望将配置加入其他配置文件,例如加入development,此时只需要提交另一个名为repmax-development.properties的文件即可。

若要运行 Config Server,可以运行spring-cloud-config-server项目提供的默认 Config Server,或自行创建一个简单的 Spring Boot 项目并承载下列 Config Server:

复制代码
@SpringBootApplication
@EnableConfigServer
public class RepmaxConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(RepmaxConfigServerApplication.class, args);
}
}

其中@EnableConfigServer标注可用于通过小巧的 Spring Boot 应用程序启动 Config Server。随后可以用spring.cloud.config.server.git.uri属性将 Config Server 指向 Git 代码库。对于本地测试工作,可将其加入 Config Server 应用程序的application.properties文件:

spring.cloud.config.server.git.uri=file://${user.home}/dev/repmax-config-repo通过这种方式,团队中的每位开发者都可以在自己的计算机上启动 Config Server,并通过本地 Git 代码库进行测试。若要验证repmax应用程序的属性是否已通过 Config Server 暴露,可在 Config Server 运行后使用浏览器访问http://localhost:8888/repmax/default

在 Config Server 中浏览配置信息

从图中可以看到,leaderboard.lb.url属性已通过repmax.properties文件暴露,其值为http://localhost:8083。JSONT 载荷的version属性显示了加载配置时所用的 Git 版本。

在生产环境中,可以充分借助PropertySource抽象将 Git 代码库的名称以环境变量的方式提供:

SPRING_CLOUD_CONFIG_SERVER_GIT_URI=https://gitlab.com/rdh/repmax-config-repo java -jar repmax-config-server-1.0.0-RELEASE.jar## Spring Cloud Config Client

修改 logbook 服务使其从新增的 Config Server 中读取配置,这一过程只需要几个简单的步骤。首先在build.gradle文件中为 spring-cloud-starter-config`增加一个依存项;

compile("org.springframework.cloud:spring-cloud-starter-config:1.1.1.BUILD-SNAPSHOT")随后提供 Config Client 所需的基本自举配置。考虑到 Config Server 会通过一个名为repmax.properties的文件暴露配置,此时要向 Config Client 提供应用程序的名称。此类自举配置位于logbook服务的bootstrap.properties文件中:

spring.application.name=repmax默认情况下,Config Client 会通过http://localhost:8888查找 Config Server。若要修改这个地址,可在启动客户端应用程序时指定SPRING_CLOUD_CONFIG_URI环境。

一旦客户端,即本例中的logbook启动后,即可访问http://localhost:8081/env以确认来自 Config Server 的配置是否正确加载:

确认 Config Client 可以访问 Config Server

logbook服务配置为使用 Config Client 后,可修改ConfigurableLeaderBoardApi以从 Config Server 暴露的leaderboard.lb.url属性中获取负载平衡器的地址。

启用动态刷新

通过将配置信息集中存储在一个位置,可以轻松更改repmax配置,使其能够被所有服务直接使用。然而为了应用这些配置依然需要重启动服务。实际上可以通过更好的方式实现。可以借助 Spring Boot 提供的@ConfigurationProperties标注将配置直接映射给 JavaBeans。Spring Cloud Config 更进一步为每个客户端服务暴露了一个/refresh端点。带有@ConfigurationProperties标注的 Bean 可在通过/refresh端点触发刷新后更新自己的属性。

任何 Bean 均可添加@ConfigurationProperties标注,但是有必要对刷新操作进行限制,只应用于包含配置数据的 Bean。为此可以用一个专门用于保存leaderboard地址的LeaderboardConfig Bean:

复制代码
@ConfigurationProperties("leaderboard.lb")
public class LeaderboardConfig {
private volatile String url;
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
}

@ConfigurationProperties标注的值实际上是希望映射至 Bean 的配置值的前缀。随后每个值可使用标准的 JavaBean 命名规则进行映射。这种情况下,url Bean 属性可映射至配置中的leaderboard.lb.url

随后要修改ConfigurableLeaderBoardApi以接受LeaderboardConfig实例,而非原始的leaderboard地址:

复制代码
public class ConfigurableLeaderBoardApi extends AbstractLeaderBoardApi {
private final LeaderboardConfig config;
@Autowired
public ConfigurableLeaderBoardApi(LeaderboardConfig config) {
this.config = config;
}
@Override
protected String getLeaderBoardAddress() {
return this.config.getLeaderboardAddress();
}
}

为了触发配置刷新操作,可向logbook服务的/refresh端点发送一个 HTTP POST请求:

curl -X POST http://localhost:8081/refresh## 有关服务发现

通过使用 Spring Cloud Config,并在logbookleaderboard服务之间使用负载平衡代理,应用程序已经基本完成了。然而还需要进行一定的完善。

如果在 AWS 或 GCP 中部署,可以充分利用这些环境中提供的高弹性负载平衡器,但如果使用诸如 HAProxy 或 NGINX 之类的市售负载平衡代理产品,此时必须自行处理服务的发现和注册工作。leaderboard的每个新增实例,以及每个因为故障要从代理中移除的实例,都必须在代理中进行配置。我们真正需要的是动态发现技术,每个服务实例都需要能自行注册以供发现和使用。

使用负载平衡代理的情况下还存在另一个潜在问题:可靠性。由于所有流量需要通过代理进行路由,因此整个系统的可靠性都受制于代理本身的可靠性。代理停机同时会导致整个系统停机。此外还需要考虑客户端和代理之间,以及代理和服务器之间通信所产生的开销。

为解决这些问题 Netflix 开发了 Eureka。Eureka 是一种用于提供服务注册和发现能力的客户端 - 服务器系统。服务实例启动后,可将自己与 Eureka 服务器进行注册。诸如logbook等客户端服务可以联系 Eureka 服务器以获取可用服务列表。客户端和服务器之间采用了点对点的通信方式。

Eureka 使得我们不再需要代理,这样可以改善整个系统的可靠性。如果leaderboard代理故障,logbook服务将完全无法联系leaderboard服务。通过使用 Eureka,logbook可以知道所有可用leaderboard实例,就算一个实例故障,logbook也只需要联系下一个leaderboard实例并重试。

那么在整个系统体系结构中,Eureka 服务器本身是否会成为一个故障点?抛开为 Eureka 服务器创建 _ 集群 _ 这种做法不谈,每个 Eureka 客户端都可以在本地缓存服务的运行状态。只要在 Eureka 服务器上运行了服务监视器,例如systemd,就可以顺利应对偶尔出现的崩溃等问题。

与 Config Server 类似,Eureka 服务器也可以作为一个小巧的 Spring Boot 应用程序来运行:

复制代码
@SpringBootApplication
@EnableEurekaServer
public class RepmaxEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(RepmaxEurekaServerApplication.class, args);
}
}

在应用程序启动时,@EnableEurekaServer标注会通知 Spring Boot 启动 Eureka。出于高可用目的,默认情况下服务器会尝试联系其他服务器。在独立安装的情况下可以考虑在application.yml中关闭该功能:

复制代码
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false

请注意,按照惯例可在8761端口运行 Eureka 服务器。访问http://localhost:8761可以查看 Eureka 仪表板。由于目前尚未注册任何服务,可用实例列表中什么也没显示:

空白的 Eureka 仪表板

若要将leaderboard服务注册至 Eureka,可为该应用程序类添加一个@EnableEurekaClient标注。随后通过application.properties告诉 Eureka 客户端在哪里可以找到服务器,以及应用程序在服务器上注册时所用的名称:

复制代码
spring.application.name=repmax-leaderboard
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka

leaderboard服务启动时,Spring Boot 会检测到@EnableEurekaClient标注并启动 Eureka 客户端,随后该客户端会将leaderboard服务注册至 Eureka 服务器。Eureka 仪表板将会显示出新注册的服务:

服务注册后 Eureka 仪表板显示的内容

logbook服务可以通过与leaderboard服务相同的方式配置为 Eureka 客户端,需要添加@EnableEurekaClient标注并配置 Eureka 服务 URL。

通过在logbook服务中启用 Eureka 客户端,Spring Cloud 会暴露一个用于查询服务实例的DiscoveryClient Bean:

复制代码
@Component
public class DiscoveryLeaderBoardApi extends AbstractLeaderBoardApi {
public DiscoveryLeaderBoardApi(DiscoveryClient discoveryClient) {
this.discoveryClient = discoveryClient;
}
private final DiscoveryClient discoveryClient;
@Override
protected String getLeaderBoardAddress() {
List<ServiceInstance> instances = this.discoveryClient.getInstances("repmax-leaderboard");
if(instances != null && !instances.isEmpty()) {
ServiceInstance serviceInstance = instances.get(0);
return String.format("http://%s:%d", serviceInstance.getHost(), serviceInstance.getPort());
}
throw new IllegalStateException("Unable to locate a leaderboard service");
}
}

调用DiscoveryClient.getInstances可获得ServiceInstances列表,列表中每一项均对应了一个注册到 Eureka 服务器的leaderboard服务。从简化的角度考虑,可以从列表中选择第一项服务用于远程调用。

客户端的负载平衡

Eureka 就位后,不同服务将能以动态的方式相互发现,并能直接相互通信,借此可避免负载平衡器代理所产生的开销以及可能的故障点。当然这里也需要进行权衡,因为我们将有关负载平衡的复杂性转嫁到了代码中。

在这里可以看到,DiscoveryLeaderBoardApi.getLeaderBoardAddress方法在每次远程调用过程中,会直接选择找到的第一个ServiceInstance。借助这种方法可以方便地将负载分散到所有可用实例。此外本例中还可以通过 Netflix Cloud 的另一个组件处理客户端的负载平衡: Ribbon

将 Ribbon 与 Spring Cloud 以及现有的 Eureka 环境配合使用的方法很简单。只需要在logbook服务中添加针对spring-cloud-starter-ribbon的依赖关系,并改为使用LoadBalancerClient取代DiscoveryClient即可:

复制代码
public class RibbonLeaderBoardApi extends AbstractLeaderBoardApi {
private final LoadBalancerClient loadBalancerClient;
@Autowired
public RibbonLeaderBoardApi(LoadBalancerClient loadBalancerClient) {
this.loadBalancerClient = loadBalancerClient;
}
@Override
protected String getLeaderBoardAddress() {
ServiceInstance serviceInstance = this.loadBalancerClient.choose("repmax-leaderboard");
if (serviceInstance != null) {
return String.format("http://%s:%d", serviceInstance.getHost(), serviceInstance.getPort());
} else {
throw new IllegalStateException("Unable to locate a leaderboard service");
}
}
}

至此选择ServiceInstance的任务将由 Ribbon 负责,该功能可以智能地监控端点运行状况,并通过内建机制实现负载平衡。

总结

本文介绍了各种将微服务连接在一起的方法。其中最简单的方法可能就是将服务所需的每个依存项的地址硬编码到程序中。这种方法可以帮助我们快速上手,但在现实环境中实用性很低。

对于现实世界中最基本的应用程序,通过外部配置使用application.properties文件指定依存项地址这种做法已经足够了。诸如 Cloud Foundry 和 Heroku 等平台即服务(PaaS)系统通过暴露连接信息,使得我们能够用完全相同的方式连接这些依赖项。

然而更大规模的应用程序不仅需要简单的点对点连接,还需要使用某种形式的负载平衡。Spring Cloud Config 与负载平衡代理的紧密结合是一种解决方案,但如果使用诸如 HAProxy 或 NGINX 等市售的负载平衡代理,就只能自行处理服务的发现和注册过程,代理也有可能成为所有流量的一个故障点。通过使用 Netflix 的 Eureka 和 Ribbon 组件,应用程序中的服务将能以动态的方式互相查找,并能将有关负载平衡的决策从专门的负载平衡器代理交由客户端服务来处理。

由于无法控制中间层微服务之间通信产生的传入流量,诸如 AWS ELB 等负载平衡解决方案在系统边缘可能依然占有一席之地,Ribbon 提供了一种不依赖具体的云供应商,可靠性和性能更为出色的解决方案。

关于作者

Rob Harrop是 Skipjaq 公司 CTO,该公司致力于通过机器学习技术解决绩效管理方面遇到的问题。在加入 Skipjaq 前,Rob 以 SpringSource 共同创始人的身份广为人知,这家软件公司开发了大获成功的 Spring 框架。在 SpringSource 任职期间,他是 Spring 框架的核心贡献者,并领导了 dm Server(现名为 Eclipse Virgo)的开发团队。在加入 SpringSource 前,(当时仅 19 岁的)Rob 是英国曼彻斯特顾问公司 Cake Solutions 的共同创始人兼 CTO。作为广受敬重的作者、演讲人和讲师,Rob 经常撰写和探讨有关大规模系统、云体系结构,以及功能编程(Functional programming)的话题。他出版的著作包括极受欢迎的 Spring 框架参考书《Pro Spring》。

2016 年 7 月 18 日 19:0614991
用户头像

发布了 283 篇内容, 共 86.1 次阅读, 收获喜欢 36 次。

关注

评论

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

高光时刻!美团推出Spring源码进阶宝典:脑图+视频+文档

996小迁

spring 源码 架构 笔记

阿里三面,复盘总结55题:java基础+分布式+网络+架构设计

Java成神之路

Java 程序员 架构 面试 编程语言

json处理

Isuodut

数字货币交易所系统开发,区块链交易所搭建

薇電13242772558

区块链 数字货币

小程序市场的「App Store」来了!你准备好吃“螃蟹”了吗?

蚂蚁集团移动开发平台 mPaaS

小程序生态 mPaaS appstore

jenkins实现接口自动化持续集成(python+pytest+ Allure+git)

行者AI

Rust太难?那是你没看到这套Rust语言学习万字指南!

华为云开发者社区

rust 语言 开发语言

资深码农:拿下软件测试,只需掌握好这两种方法!

华为云开发者社区

软件 工具 测试

华为全栈AI技术干货深度解析,解锁企业AI开发“秘籍”

华为云开发者社区

AI 全栈 开发

为什么要在以太坊上构建去中心化缓存层?到底要怎样做呢?

CECBC区块链专委会

以太坊

接口自动化传值处理

行者AI

腾讯五面、快手三面已拿offer(Java岗位),分享个人面经

Java成神之路

Java 程序员 架构 面试 编程语言

15天成功拿到阿里offer 我是如何逆袭成功?全靠“Java程序员面试笔试通关宝典”真够可以!

比伯

Java 编程 架构 面试 程序人生

《迅雷链精品课》第十三课:PBFT算法

迅雷链

区块链

AOFEX交易所APP系统开发|AOFEX交易所软件开发

开發I852946OIIO

系统开发

拼多多五面面经(Java岗),全面涵盖Java基础到高并发级别

Java成神之路

Java 程序员 架构 面试 编程语言

自定义TBE算子入门,不妨从单算子开发开始

华为云开发者社区

算法 算子 自定义

如何从危机中提炼总结,做好2020年的复盘?

CECBC区块链专委会

复盘 经济

盘点 2020 |协作,是另外一种常态

Winfield

领域驱动设计 DDD 协作 远程协作 盘点2020

3面抖音犹如开挂,一周直接拿下offer,全靠这份啃了两个月「Java进阶手册」+[Java面试宝典]

云流

编程 程序员 计算机 java面试

软件测试中需要使用的工具

测试人生路

软件测试

接口自动化测试的实现

行者AI

5年Java高工经验,我是如何成功拿下滴滴D7Offer的?

Java架构追梦

Java 学习 架构 面试 滴滴

XDAG技术详解1

老五

浅谈 WebRTC 的 Audio 在进入 Encoder 之前的处理流程

阿里云视频云

WebRTC 音频技术 音视频算法 音频

得物App亮相QCon全球软件开发大会,分享百倍增长背后的技术力量

得物技术

效率 技术 得物 得物技术 Qcon

LeetCode题解:42. 接雨水,动态规划,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

双循环背景下的全球供应链机遇与挑战

CECBC区块链专委会

供应链物流

Locust快速上手指南

行者AI

半个多月时间4面阿里,已经成功拿下offer,分享一下个人面经

Java成神之路

Java 程序员 架构 面试 编程语言

【得物技术】如何测试概率性事件-二项分布置信区间

得物技术

测试 开发 概率 得物 得物技术

演讲经验交流会|ArchSummit 上海站

演讲经验交流会|ArchSummit 上海站

使用Spring Cloud连接不同服务-InfoQ