低代码到底是不是行业毒瘤?一线大厂怎么做的?戳此了解>>> 了解详情
写点什么

侵入式服务治理方案,读这一篇就够

2020 年 5 月 06 日

侵入式服务治理方案,读这一篇就够

尽管在程序执行效率上,Java 不如 C、C++,在开发效率、易用性以及学习难度上,Java 又不如 Ruby、Python、Go,但 Java 无疑是当今后端系统开发中使用最为广泛的语言。


Java 所累积的大量生态体系是其他任何开发语言都不具备的。基于 Java 开发的“杀手级”应用数不胜数,互联网后端的很多复杂系统也都是用 Java 开发的。因此,如何治理基于 Java 开发的分布式应用系统,是互联网公司面对的首要问题。


侵入式服务治理方案指的是,在应用端使用框架提供的 API 开发程序并提供服务治理方案。Java 提供了很多一站式服务化框架,可以有效地与应用系统深度配合,形成完善的服务治理体系。由阿里巴巴公司开源的 Dubbo,以及由 Pivotal 公司开源的 SpringCloud 是业界采用最多的侵入式服务治理方案。


Dubbo 是阿里巴巴公司于 2012 年前后开源的分布式服务框架。从开源至今,它由于设计理念超前、性能出色、稳定性较强而累积了大量国内忠实用户。虽然中间有几年停止了更新,但现在 Dubbo 又重新开启被维护,这使得它又焕发了新的活力。


在远程通信一章中,我们已经介绍过 Dubbo 的 RPC 部分。严格地说,Dubbo 目前并非一款完善的服务治理框架,它更偏重 RPC 部分。


Dubbo 概述

提起 Dubbo,就不得不再次给出 Dubbo 刚刚开源时发布的架构演进图,如图 6-1 所示。



图 6-1 架构演进图


在图 6-1 中,互联网架构的演进过程分为四个阶段,每个阶段对应一种架构模式,具体如下。


  • 单体应用架构

  • 在系统访问流量不大时,应用所需的所有功能都在开发和部署时被集中在一起。单体应用架构获取数据的主要途径是与数据库进行交互。由于关系型数据库与面向对象的阻抗不匹配,因此,开发出能够简化增删改查工作的数据库访问(ORM)框架是重中之重。

  • 垂直应用架构

  • 在系统访问流量逐渐增大时,像单体式应用架构一样通过服务器硬件加速带来的承载量提升的方式已经无法满足业务需要。因此,需要将应用按照业务线进行垂直拆分,将系统部署为多个相对独立的应用。垂直应用架构获取数据的途径除了系统内部与数据库的交互外,也包括系统间的交互。通过灵活的 Web MVC 框架提供数据,供前端系统及其他外围系统展示和使用,这是垂直应用架构关注的重点。

  • 分布式服务架构

  • 随着系统访问流量进一步增大,越来越多的垂直应用被拆分出来,独立应用间的共同特征越来越多。因此,我们要将核心业务抽取出来形成独立的后端服务,再对前端进行进一步抽离,使其能够更加快速地响应市场需求。此时,前端与后端的交互以及后端服务之间的交互,若采用基于 RESTful API 的 WebMVC 显然并不适合,因此 RPC 成了获取数据的重要方式。

  • 弹性计算架构

  • 后端服务的增多,使得服务的治理成本越来越高,手动进行服务发现、负载均衡、连接管理、限流保护等工作已经变得不现实,因此提供一个服务治理中心是弹性计算架构的关键所在。另外,越来越多的细小服务的资源评估工作也变得非常烦琐,服务的资源浪费问题也需要重点关注,因此,提供一个调度中心来管理和分配集群容量进而提高集群利用率,是另一个关键所在。


Dubbo 所关注的重点在于第三点和第四点的前半部分。对于分布式应用间的 RPC 交互而言,Dubbo 采用透明化的方式,让使用者无须关心方法的调用是本地的还是远程的。Dubbo 采用以 ZooKeeper 为主的注册中心和治理中心来提供服务治理,并未提供调度中心的实现方案。图 6-1 成型于 2012 年,当时并没有 Docker 和 Kubernetes 这样的产品出现,Dubbo 所提出的调度中心管控资源的概念,与 Docker 和 Kubernetes 的理念不谋而合,展现出了极具前瞻性的眼光。


图 6-2 是官方提供的采用 Dubbo 作为服务化框架的应用架构图,除了调度中心,其他都已开源。



图 6-2 采用 Dubbo 作为服务化框架的应用架构图


Dubbo 将服务划分为提供者和消费者,根据需求不同,每个应用都可以既是服务的提供者,又是服务的消费者。应用开发方可以将服务进行合理分层。在图 6-2 中,服务被划分为前端服务、集成服务以及核心服务三层。其中前端服务是服务的消费者,核心服务是服务的提供者,集成服务对于前端服务而言是提供者,对于核心服务而言则是消费者。


核心流程

简单来说,Dubbo 中必有的核心概念只有服务提供者、服务消费者和注册中心这三个,治理中心以及监控中心并非必需品。


服务提供者和服务消费者启动时,都会初始化一个 Dubbo 的运行时容器,多为 Spring 容器。服务提供者完成初始化后,将向注册中心注册服务;服务消费者完成初始化后,将向注册中心订阅服务。


注册中心在服务提供者列表发生变化时会将变化的内容主动通知给服务消费者。服务提供者和服务消费者在初次连通后,即持有长连接,它们之间将通过透明化的远程调用进行通信。每次调用的信息将传递至监控中心用于统计。Dubbo 启动的核心流程如图 6-3 所示。



图 6-3 Dubbo 启动的核心流程


注册中心

Dubbo 通过注册中心来实现服务发现,它支持 Multicast、ZooKeeper、Redis 和 Simple 这四种类型的注册中心。虽然看似选择不少,但 Multicast 注册中心受网络结构限制,只适合小规模使用,而 Simple 注册中心不支持集群,因此,实际上生产级别可用的只有 ZooKeeper 注册中心和 Redis 注册中心。


无论是使用 ZooKeeper 还是使用 Redis 作为注册中心,都不是阿里巴巴内部的实现方案,而是开源的桥接实现方案,因此没有经历阿里巴巴内部的长时间考验。通过服务发现一章中的介绍可知,虽然 ZooKeeper 并不是首选的用于服务发现的注册中心,但相较于 Redis,它的可靠性更强。除了 Dubbo,其他很多第三方组件也采用 ZooKeeper 作为注册中心或元数据管理系统,如 Kafka、Hadoop、Mesos,而 Redis 更加适合于存储应用数据的缓存体系。因此,大部分情况下,Dubbo 都是配合 ZooKeeper 注册中心来使用的。


无论使用 ZooKeeper 还是 Redis 作为注册中心,服务注册和服务发现的流程都是相同的,只是存储数据结构以及监听等功能的具体实现不同而已,因此,下文仅选用 ZooKeeper 作为注册中心来举例说明。图 6-4 展示了 Dubbo 注册服务后在 ZooKeeper 注册中心的 Znode 中的存储结构。



图 6-4 Dubbo 的存储结构


Dubbo 所有的运行时状态信息都会集中存入一个统一的根节点,根节点的名称就是 dubbo。每个服务会以类全称独立创建一个节点,服务节点下会分别创建名为 providers 和 consumers 的子节点,用于存储服务提供者和服务消费者信息,每个服务提供者和服务消费者的副本都会在相应的节点位置创建一个临时节点,该临时节点以 URL 的形式存储当前服务提供者或服务消费者的信息,包括 IP 地址、端口、调用方法等元数据,以及传输协议、最大连接承载数、路由策略等配置信息。另外还有 configurators 和 routers 节点用于存储全局配置和全局路由信息。


服务提供者启动时会在相应服务节点的 providers 节点下写入包含自身信息的 URL 作为临时节点;服务消费者启动时会在相应服务节点的 consumers 节点下写入包含自身信息的 URL 作为临时节点,并且监听 providers 节点的变化。当新的服务提供者加入,或当前服务提供者下线时,所有的服务消费者将通过 ZooKeeper 的监听机制自动感知变化。声明一个最简化的注册中心很容易,以 Dubbo 最常用的 Spring 命名空间的配置方式为例,代码如下。


<dubbo:registryaddress="zookeeper://zk_host:2181" />
复制代码


Dubbo 支持多注册中心同时提供服务,以分组的方式隔离不同的注册中心。只有设置为同一组别的服务才会注册到同一个注册中心下。提供多注册中心需要以 ID 进行区分,声明多注册中心的示例代码如下。


<dubbo:registryid="global" address="zookeeper://zk_host:2181"group="global" /><dubbo:registry id="custom"address="zookeeper://zk_host:3181" group="custom" />
复制代码


前面介绍 ZooKeeper 时提到过,除了原生客户端,还有两个较为常用的第三方客户端,它们是 ZkClient 和 Curator。Dubbo 支持这两种客户端对 ZooKeeper 进行操作,只需在配置注册中心时指明 ZooKeeper 的实现客户端即可,代码如下。


<dubbo:registryaddress="zookeeper://zk_host:2181" client="curator" />                         
复制代码


负载均衡

Dubbo 采用客户端负载均衡方式,即由服务消费者一方决定将当前通信发送至哪个服务提供者的副本。服务消费者实例启动时会从注册中心同步一份当前有效的服务提供者实例列表,并在服务提供者列表发生变化时更新本地数据副本。每次远程调用发生时,服务消费者都会读取内存中的服务提供者实例列表,并根据合适的负载均衡策略选择一个最合适的服务提供者实例进行访问。



Dubbo 的服务发现和负载均衡机制,使得基于它开发的分布式系统具有弹性好、高可用、性能优的特征。


  • 弹性好:

  • 基于的服务发现机制可以快速发现服务的扩容与缩容,与相对静态的服务列表配置机制比,它是更加动态、灵活和实时的,这使得基于 Dubbo 开发的服务可以随时被关闭、启动和迁移,也使得应用能够以原生方式利用云环境中资源的弹性伸缩。

  • 高可用:

  • Dubbo 是完全去中心化的服务治理方案,在分布式系统运行时,任何节点宕机都不会对服务产生实质性的影响。服务提供者宕机是较为常见的问题,可以与缩容一并处理,不会影响集群的整体服务。注册中心容易被误认为 Dubbo 系统的中心,其实不然,即使注册中心集群整体宕机,也不会影响 Dubbo 应用的运行。因为服务提供者列表是缓存在每一个服务消费者本地的,因此即使不经过注册中心,服务间的远程调用仍然不会中断。不过在注册中心失效期间,服务消费者无法感知新上线的服务提供者,因此无法对系统进行扩容。

  • 性能优:

  • 采用 Dubbo 协议的服务消费者和服务提供者之间是点对点直连的,连接建立后无须断开,每次远程调用无须重新经过三次握手,也无须经过负载均衡服务器的二次转发,非常适用于互联网后端之间频次高、性能敏感度高的服务交互。


Dubbo 支持随机、轮询、最少活跃调用数和一致性哈希这四种负载均衡策略,也提供扩展点用于定制策略。


随机策略是 Dubbo 默认采用的负载均衡策略。调用量越大分布越均匀,在无状态应用场景下较为适用。


轮询策略能够让流量以绝对均匀的方式分配。但是,如果服务节点处理能力不均衡的话,便会导致大量请求最终阻塞在最短板的服务节点上,从而影响集群的整体运行效果。


最少活跃数调用策略可以使请求响应迅速的服务节点获得更多的请求,使请求响应缓慢的服务节点获得较少的请求。



一致性哈希策略使用一致性哈希算法,使相同的参数总是可以被发送给同样的服务提供者,在服务节点变化时平摊请求,避免请求路由结果发生剧烈变动。一致性哈希算法在分布式缓存等方案中较为常见,无状态服务没有必要使用,但如果服务是有状态的,便可以考虑使用该策略,以降低数据在服务节点间的复制频率。


Dubbo 还为负载均衡策略提供了权重,可以通过配置或 Dubbo 的控制台动态调节权重,控制各个服务节点分配到的请求的数量。


配置负载均衡策略很简单,以下是将接口的负载均衡策略调整为轮询策略的配置代码。


<dubbo:referenceinterface="..." loadbalance="roundrobin" />
复制代码


以下是将某一种方法的负载均衡策略调整为轮询策略的配置代码。


<dubbo:referenceinterface="..."><dubbo:method name="..." loadbalance="roundrobin"/></dubbo:reference>
复制代码


在服务调用失败时,Dubbo 还提供了失效转移等容错的能力。


远程通信

使用 Dubbo 进行远程调用非常简单,它将多种通信方式及不同的序列化协议进行了统一封装。服务提供者和服务消费者只需在配置中指定使用的协议,便无须关心其他的实现细节了。


在正式介绍通信协议之前,我们需要先明确一下“Dubbo”这个词在不同语境中所表示的不同概念。首先,Dubbo 是这个框架的名字;其次,在 Dubbo 框架中,服务提供者与服务消费者之间可以采用多种协议通信,Dubbo 通信协议便是其中的一种;最后,远程通信时需要对在网络间传输的消息进行序列化和反序列化,Dubbo 序列化协议是 Dubbo 框架所支持的众多序列化协议之一,是其自研的序列化算法。


Dubbo 框架内置了多种通信协议,默认使用 Dubbo 通信协议,其他的协议还有 RMI、Hessian、HTTP、WebService、Thrift、Memcached、Redis 等。每个协议的连接方式、支持的序列化协议、线程模型、消息派发方式等都有很大区别。


Dubbo 通信协议是 Dubbo 框架中最常用的通信协议。它采用 Java NIO 实现多路复用。对于每一个服务消费者来说,Dubbo 协议的服务提供者都会创建固定数量的长连接传输消息,用于有效减少建立连接的握手次数,Dubbo 通信协议使用线程池并发处理请求来增强并发效率。由于连接复用,传输大文件时的带宽占用率高可能会成为系统瓶颈,因此 Dubbo 通信协议适合处理高并发的小数据量互联网请求,不适合处理视频、高清照片这样的大文件或超长字符串。



Dubbo 通信协议并未直接使用 Java 原生的 NIO 包进行开发,它默认采用 Netty 框架进行远程通信,并且可以仅通过配置的变更将远程调用的具体实现方式切换为使用 Mina 或 Grizzly 等其他通信框架,远程调用的实现对使用方完全透明。


Dubbo 通信协议可以支持多种序列化协议,与变更通信框架一样,它也能够仅通过修改配置实现序列化协议的切换。Dubbo 通信协议使用 Hessian 作为默认的序列化协议,除此之外,还支持 Dubbo、JSON 以及 Java 原生的序列化协议。


限流

在实际使用场景中,服务提供者一般远少于服务消费者。如果为每个服务消费者创建单一的连接,在服务消费者数量可控的情况下,服务提供者不太容易被轻易压垮。但如果为每个服务消费者创建独立连接,或者当服务消费者的数量爆发性增长时,就需要在服务提供者端限制最大可接收连接数了,防止自身被压垮。


在 Dubbo 的服务提供者端配置限流参数的代码如下。


<dubbo:providerprotocol="dubbo" accepts="10" threads="10"connections="10" />
复制代码


其中的 accepts、threads 和 connections 分别表示该服务提供者应用实例的最大可接受连接数、最大线程数,以及每个服务提供者可建立的长连接数。


也可以在通信协议部分进行全局配置,配置代码如下。


<dubbo:protocolname="dubbo" port="20880" accepts="1024" />
复制代码


治理中心

运行时环境的 Dubbo 只需具有服务提供者、服务消费者和注册中心就可以正常运转,所有的运行时信息都存储在基于 ZooKeeper 或 Redis 的注册中心中。可以直接通过命令行对注册中心进行查询以获取系统当前的运行状态,也可以通过直接对注册中心的数据进行修改(例如修改权重、禁用某一服务提供者实例、修改负载均衡策略等)以达到控制服务提供者和服务消费者行为的目的。


但是直接通过 ZooKeeper 或 Redis 的原生命令来查询和修改数据并不方便,也容易由于手动失误造成线上事故,因此 Dubbo 提供了治理中心,可以帮助运维工程师查询和调整系统的运行时状态。


Dubbo 治理中心的名字叫作 dubbo-admin,它可以被打包为一个独立的 war 文件,部署至 Tomcat 这类支持 Servlet 标准的 Web 容器中使用。无论是否使用 dubbo-admin,都不会对运行时的 Dubbo 应用产生任何影响,它的作用只是提供一个可视化工具,辅助工程师进行运维相关工作。基于 Dubbo 开发的应用与 Dubbo 治理中心并无直接关联,它们之间无须感知对方的存在,它们仅与注册中心建立关联。


Dubbo 治理中心为服务提供者和服务消费者提供了分组查询、配置更改、加权/降权、禁用/启用、权限控制、负责人管理等运维功能。


`## 监控中心


在复杂的分布式系统中,Dubbo 的服务消费者与服务提供者之间的调用关系相当错综复杂,仅凭治理中心是无法掌握当前系统运行所需的全部数据的。Dubbo 提供了监控中心的接口以及一个名为 dubbo-monitor 的简单实现,dubbo-monitor 可以用于统计和分析调用信息。


Dubbo 的监控中心采用了与治理中心类似的设计思路,dubbo-monitor 宕机不会对线上正在运行的应用产生不良影响。只要监控中心正常运行,就能够以增量的方式统计和分析调用数据。Dubbo 应用间的远程调用信息将被发送至监控中心进行统计,采集的调用信息包括调用的发生时间、耗时、成功与否、来源方、目的地等。监控中心将采集到的数据定时汇总,并将统计结果落盘至其所在的服务器。通过监控中心可以清晰地看到服务的访问总数、成功失败次数,以及每个服务调用耗时的最大值、最小值和平均值的聚合图。



监控中心虽然是 Dubbo 官方提供的一个基于内存计算和本地存储的建议实现版本,但客观来讲,它只适用于演示程序,并不能完全满足线上环境监控和分析的需要。它缺乏完善的调用链路统计功能,因而无法绘制系统的整体调用关系图。对于单次调用而言,它也无法深入钻取 Dubbo 之外的调用信息,例如服务提供者调用数据库的耗时。仅使用 dubbo-monitor 难于完成对系统的综合监控以及对事故的复盘解读。


DubboX 的扩展

DubboX 由当当网开源,X 源于 extensions 一词,是对 Dubbo 的扩展和有益补充。


  • REST 协议

  • 虽然 Dubbo 框架支持多种通信协议,但缺乏对当今较为流行的 RESTful 风格远程调用的支持,基于 Dubbo 进行二次开发的 DubboX 弥补了这一方面的缺憾。


DubboX 基于标准的 Java REST API——JAX-RS2.0(Java API for RESTful Web Services),为 Dubbo 提供了透明化的 RESTful 风格支持。在 DubboX 中,仅对通过 Dubbo 开发的应用稍作修改即可支持 REST 协议。


首先对服务提供者实现类添加 JAX-RS 的 API,核心代码如下。


@Path("foo")public class FooServiceImpl implements FooService {
@GET @Path("bar") @Consumes({MediaType.APPLICATION_JSON}) @Override public Date bar() { return new Date(); }}
复制代码


再将 provider.xml 的服务暴露配置修改为 REST 协议,核心代码如下。


<dubbo:protocolname=“rest” port=“8080” />


启动程序后,即可通过访问浏览器或执行 curl 命令获取调用结果。通过 REST 协议能够实现异构语言间的调用。


  • 高性能序列化

  • 序列化会对远程调用的响应速度、吞吐量、网络带宽消耗产生较大影响,是提升分布式系统性能最关键的因素之一。


在 Dubbo 通信协议中,常见的序列化方式主要有以下几种。


Dubbo 序列化:阿里巴巴公司尚未开发成熟的高效 Java 序列化实现,不建议在生产环境中使用。


Hessian2 序列化:一种跨语言的高效二进制序列化方式。使用 Dubbo 框架修改过的 hessianlite 是 Dubbo 通信协议默认启用的序列化方式。


JSON 序列化:采用 FastJson 解析 JSON,文本序列化性能不如上面两种二进制序列化性能。


Java 原生序列化:采用 JDK 自带的 Java 序列化实现,性能不是很理想。


通常情况下,以上四种主要序列化方式的性能从上到下依次递减。对于 Dubbo 通信协议这种追求高性能的远程调用方式来说,只有前两种高效序列化方式与之比较“般配”,而 Dubbo 序列化方式还不成熟,因此实际只剩下 Hessian2 方式可用。Dubbo 通信协议默认采用它作为序列化选型。


但 Hessian2 是一个比较老的跨语言序列化实现方式,并不单独针对 Java 进行优化。而 Dubbo 通信协议是一种 Java 同构语言之间的远程调用,没有必要采用跨语言的序列化方式。



前面介绍的高性能序列化框架,如 Kryo 、Protobuf 等,Dubbo 都没有采用。除了这些,还有专门针对 Java 语言的 FST、跨语言的 Thrift、Avro、MsgPack 等,这些序列化方式的性能都显著优于 Hessian2。


鉴于此,DubboX 引入 Kryo 和 FST 这两种高效 Java 序列化方式来取代 Hessian2,它们与 Dubbo 这样的高性能通信协议更加“般配”。


使用方法非常简单,仅需在 Dubbo 通信协议的配置中声明序列化协议名称即可,核心代码如下。


<dubbo:protocolname="dubbo" serialization="kryo"/>
复制代码


或者也可以采用如下方式。


<dubbo:protocolname="dubbo" serialization="fst"/>
复制代码


DubboX 是当当网基于 Dubbo 进行增量开发而实现的项目。出于对 Dubbo 的敬意,保留了代码中阿里巴巴的包名,因此无法将代码发布到阿里巴巴的 Maven 中央仓库中。使用者需要自行下载源码编译打包。


Dubbo 凭借远程调用和服务治理功能成为分布式系统的关键组件,并且借助自身优异的性能、较高的质量以及便捷的使用方式在服务化领域占据了一席之地。但服务治理领域所涵盖的内容非常广泛,即使像 Dubbo 这样优秀的项目,也并未完全实现服务治理领域的全部功能。目前,Dubbo 缺乏有效的熔断机制,在调用链路跟踪方面也还有提升的空间。


Dubbo 近年来疏于维护,因此后来居上的 Spring Cloud 渐渐占据了更大的市场份额。不过 Dubbo 团队已经在 2017 年宣布重新开始维护 Dubbo,希望能带来更好的服务化解决方案。


本文转载自技术锁话公众号。


原文链接:https://mp.weixin.qq.com/s/wfeCqH5yPwMhVNsup_EvmQ


2020 年 5 月 06 日 17:24367

评论

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

架构词典:SLA

lidaobing

架构 SLA

史上最实用的Android切片应用库XAOP使用指南

android aop 开源项目 框架

甲方日常 63

句子

工作 随笔杂谈 日常

Redis 为什么这么快?这才是最完美的回答

Java架构师迁哥

程序员有必要读研吗?

Java架构师迁哥

架构师视角 | 分布式缓存如何选择 ?

Java架构师迁哥

史上最优美的Android原生UI框架XUI使用指南

android UI 框架开发

Flutter Plugin插件开发填坑指南

flutter 经验分享

面试被问线程安全怎么保障,我的回答让面试官眼前一亮

996小迁

Java 学习 架构 面试 笔记

default-servlet-handler不生效原因,springmvc静态资源拦截方案比较

叫练

springmvc 静态资源拦截 default-servlet-handler 资源配置不生效

线上数据被回滚两次我都做了哪些不正确的操作

Gopher指北

MySQL 后端

话题讨论 | 那些年奇葩的面试经历

三号无名指

话题讨论

让战略不再”空虚“的战略描述

Alan

战略思考 战略

传销组织的CTO | 法庭上的CTO(4)

赵新龙

CTO 传销 法庭上的CTO

roblox 杂记

katichar

架构师训练营第十一周命题作业

一马行千里

极客大学架构师训练营 命题作业

移动端技术方案设计的经验总结

张明云

android 架构 移动应用 架构师 技术方案

史上最好用的Android全量版本更新库XUpdate使用指南

android UI 框架开发 xupdate

网络入门模拟器:Cisco Packet Tracer

计算机网络基础

Minar Kotonoha

node.js 前端 计算机网络 HTTP

史上最全的开源项目创作指南

开源 经验分享

架构师训练营第12周作业

邓昀垚

二、关于大型复杂系统

数列科技杨德华

架构师训练营第 12 周总结

邓昀垚

我是怎么教我6岁女儿编程的

勇往直前的胖子

少儿编程

LeetCode题解:45. 跳跃游戏 II,贪心正向查找,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

《程序员数学:使用Python进行3D图形,机器学习和仿真》PDF免费下载

计算机与AI

Python 学习 数学

架构师训练营第十一周学习笔记

一马行千里

学习 极客大学架构师训练营

深入理解Git的实现原理

程序员小灰

c++ git Linux 项目管理 架构师

技术博客,从零到数万访问,这两年我都做了什么

android 博客 经验分享

培训是为了激发学员学习这门课的兴趣

boshi

职业 培训

2021 ThoughtWorks 技术雷达峰会

2021 ThoughtWorks 技术雷达峰会

侵入式服务治理方案,读这一篇就够-InfoQ