今日头条从内涵段子开始,从日均千万,到亿万,再到百亿级,再到千亿级流量,头条APP不断进化,成为一个TMD小巨头之一。本篇文章讲述头条架构的微服务变迁史。

今日头条在2015年中期前,使用的开发语言大量采用了Python和C++以及PHP技术栈。

随着系统复杂度,耦合度不断提升,开始向SOA服务化架构演进。

头条的内容发布系统使用了Django框架,一部分后端系统还使用了PHP,这些解释型语言以及相应的服务进程管理存在一些瓶颈,即便通过大家的智慧得到解决,但是整个服务器后端的架构是一个大的单体架构,需要将一部分功能从单体架构中抽取出来。

头条微服务架构概览

因此有必要转移为微服务架构。微服务架构具有如下特点:

  • 进程解耦
  • 易于管理和理解
  • 自我包含
  • 部署解耦
  • 自动化。

因此,微服务可以与语言层无关,具有较强的接口约束性,高内聚,服务之间的正向相交性。

到目前为止,今日头条技术栈,包括头条、段子产品线开始全部或部分转移到Go语言构建的微服务平台上。目前部署的微服务数量超过几百个,在最高峰时,QPS超过700万,日处理用户请求超过3000亿次,形成目前部署规模较大的GO语言应用。

选择Go语言的原因

  • 语法简单,上手快
  • 性能高,编译快,开发效率也不低
  • 原生支持并发,协程模型是非常优秀的服务端模型,同时也适合网络调用
  • 部署方便,编译包小,几乎无依赖

因为团队以前用Go 构建过超大流量的后端服务,对其本身的稳定性有信心。再加上头条后端整体服务化的架构改造,所以决定使用 Go 语言构建后端的微服务架构。

2015年6月,今日头条技术团队开始尝试使用 Go 重构后端 Feed 流(信息流)服务。期间一边重构,一边迭代现有业务,同时还进行服务拆分,直到2016年6月,Feed 流后端服务大部分迁移到 Go架构上。

由于期间业务增长较快,夹杂服务拆分,因此没有横向对比重构前后的各项指标。实际上切换到 Go 之后,服务整体稳定性和性能都得以大幅提高。

微服务架构

对于复杂的服务间调用,我们抽象出五元组的概念:(From, FromCluster, To, ToCluster, Method)。每一个五元组唯一定义了一类的RPC调用。以五元组为单元,我们构建了一整套微服务架构。

头条使用 Go 研发了内部的微服务框架:kite,其完全兼容 Thrift协议。

以五元组为基础单元,我们在 kite 框架上集成了服务注册和发现,分布式负载均衡,超时和熔断管理,服务降级,Method 级别的指标监控,分布式调用链追踪等功能。目前统一使用 kite 框架开发内部 Go 语言的服务,整体架构支持无限制水平扩展。

关于 kite 框架和微服务架构实现细节后续有机会会专门分享,这里主要分享下我们在使用 Go 构建大规模微服务架构中,Go 语言本身给我们带来了哪些便利以及实践过程中我们取得的经验。内容主要包括并发,性能,监控以及对Go语言使用的一些体会。

并发

Go 作为一门新兴的编程语言,最大特点就在于它是原生支持并发的。

和传统基于 OS 线程和进程实现不同,Go 的并发是基于用户态的并发,这种并发方式就变得非常轻量,能够轻松运行几万甚至是几十万的并发逻辑。因此使用 Go 开发的服务端应用采用的就是“协程模型”,每一个请求由独立的协程处理完成。

比进程线程模型高出几个数量级的并发能力,而相对基于事件回调的服务端模型,Go 开发思路更加符合人的逻辑处理思维,因此即使使用 Go 开发大型的项目,也很容易维护。

并发模型

Go 的并发属于 CSP 并发模型的一种实现,CSP 并发模型的核心概念是:“不要通过共享内存来通信,而应该通过通信来共享内存”。这在 Go 语言中的实现就是 Goroutine 和 Channel。

在1978发表的 CSP 论文中有一段使用 CSP 思路解决问题的描述:

“Problem: To print in ascending order all primes less than 10000. Use an array of processes, SIEVE, in which each process inputs a prime from its predecessor and prints it. The process then inputs an ascending stream of numbers from its predecessor and passes them on to its successor, suppressing any that are multiples of the original prime.”

要找出10000以内所有的素数,这里使用的方法是筛法,即从2开始每找到一个素数就标记所有能被该素数整除的所有数。直到没有可标记的数,剩下的就都是素数。下面以找出10以内所有素数为例,借用 CSP 方式解决这个问题。

从上图中可以看出,每一行过滤使用独立的并发处理程序,上下相邻的并发处理程序传递数据实现通信。通过4个并发处理程序得出10以内的素数表,对应的 Go 语言代码如下:

以上例子体现出 Go 语言开发的两个特点:

1. Go 语言的并发很简单,并且通过提高并发可以提高处理效率。

2. 协程之间可以通过通信的方式来共享变量。

并发控制

当并发成为语言的原生特性之后,在实践过程中就会频繁地使用并发来处理逻辑问题,尤其是涉及到网络I/O的过程,例如 RPC 调用,数据库访问等。下图是一个微服务处理请求的抽象描述:

当 Request 到达 GW 之后,GW 需要整合下游5个服务的结果来响应本次的请求,假定对下游5个服务的调用不存在互相的数据依赖问题。那么这里会同时发起5个 RPC 请求,然后等待5个请求的返回结果。为避免长时间的等待,这里会引入等待超时的概念。超时事件发生后,为了避免资源泄漏,会发送事件给正在并发处理的请求。在实践过程中,得出两种抽象的模型。

· Wait

· Cancel

Wait和Cancel两种并发控制方式,在使用 Go 开发服务的时候到处都有体现,只要使用了并发就会用到这两种模式。在上面的例子中,GW 启动5个协程发起5个并行的 RPC 调用之后,主协程就会进入等待状态,需要等待这5次 RPC 调用的返回结果,这就是 Wait 模式。另一中 Cancel 模式,在5次 RPC 调用返回之前,已经到达本次请求处理的总超时时间,这时候就需要 Cancel 所有未完成的 RPC 请求,提前结束协程。Wait 模式使用会比较广泛一些,而对于 Cancel 模式主要体现在超时控制和资源回收。

在 Go 语言中,分别有 sync.WaitGroup 和 context.Context 来实现这两种模式。

超时控制

合理的超时控制在构建可靠的大规模微服务架构显得非常重要,不合理的超时设置或者超时设置失效将会引起整个调用链上的服务雪崩。

图中被依赖的服务G由于某种原因导致响应比较慢,因此上游服务的请求都会阻塞在服务G的调用上。如果此时上游服务没有合理的超时控制,导致请求阻塞在服务G上无法释放,那么上游服务自身也会受到影响,进一步影响到整个调用链上各个服务。

在 Go 语言中,Server 的模型是“协程模型”,即一个协程处理一个请求。如果当前请求处理过程因为依赖服务响应慢阻塞,那么很容易会在短时间内堆积起大量的协程。每个协程都会因为处理逻辑的不同而占用不同大小的内存,当协程数据激增,服务进程很快就会消耗大量的内存。

协程暴涨和内存使用激增会加剧 Go 调度器和运行时 GC 的负担,进而再次影响服务的处理能力,这种恶性循环会导致整个服务不可用。在使用 Go 开发微服务的过程中,曾多次出现过类似的问题,我们称之为协程暴涨。

有没有好的办法来解决这个问题呢?通常出现这种问题的原因是网络调用阻塞过长。即使在我们合理设置网络超时之后,偶尔还是会出现超时限制不住的情况,对 Go 语言中如何使用超时控制进行分析,首先我们来看下一次网络调用的过程。

第一步,建立 TCP 连接,通常会设置一个连接超时时间来保证建立连接的过程不会被无限阻塞。

第二步,把序列化后的 Request 数据写入到 Socket 中,为了确保写数据的过程不会一直阻塞,Go 语言提供了 SetWriteDeadline 的方法,控制数据写入 Socket 的超时时间。根据 Request 的数据量大小,可能需要多次写 Socket 的操作,并且为了提高效率会采用边序列化边写入的方式。因此在 Thrift 库的实现中每次写 Socket 之前都会重新 Reset 超时时间。

第三步,从 Socket 中读取返回的结果,和写入一样, Go 语言也提供了 SetReadDeadline 接口,由于读数据也存在读取多次的情况,因此同样会在每次读取数据之前 Reset 超时时间。

分析上面的过程可以发现影响一次 RPC 耗费的总时间的长短由三部分组成:连接超时,写超时,读超时。而且读和写超时可能存在多次,这就导致超时限制不住情况的发生。为了解决这个问题,在 kite 框架中引入了并发超时控制的概念,并将功能集成到 kite 框架的客户端调用库中。

并发超时控制模型如上图所示,在模型中引入了“Concurrent Ctrl”模块,这个模块属于微服务熔断功能的一部分,用于控制客户端能够发起的最大并发请求数。并发超时控制整体流程是这样的:

首先,客户端发起 RPC 请求,经过“Concurrent Ctrl”模块判断是否允许当前请求发起。如果被允许发起 RPC 请求,此时启动一个协程并执行 RPC 调用,同时初始化一个超时定时器。然后在主协程中同时监听 RPC 完成事件信号以及定时器信号。如果 RPC 完成事件先到达,则表示本次 RPC 成功,否则,当定时器事件发生,表明本次 RPC 调用超时。这种模型确保了无论何种情况下,一次 RPC 都不会超过预定义的时间,实现精准控制超时。

Go 语言在1.7版本的标准库引入了“context”,这个库几乎成为了并发控制和超时控制的标准做法,随后1.8版本中在多个旧的标准库中增加对“context”的支持,其中包括“database/sql”包。

性能

Go 相对于传统 Web 服务端编程语言已经具备非常大的性能优势。但是很多时候因为使用方式不对,或者服务对延迟要求很高,不得不使用一些性能分析工具去追查问题以及优化服务性能。在 Go 语言工具链中自带了多种性能分析工具,供开发者分析问题。

· CPU 使用分析

· 内部使用分析

· 查看协程栈

· 查看 GC 日志

· Trace 分析工具

下图是各种分析方法截图:

在使用 Go 语言开发的过程中,我们总结了一些写出高性能 Go 服务的方法如下:

1. 注重锁的使用,尽量做到锁变量而不要锁过程

2. 可以使用 CAS,则使用 CAS 操作

3. 针对热点代码要做针对性优化

4. 不要忽略 GC 的影响,尤其是高性能低延迟的服务

5. 合理的对象复用可以取得非常好的优化效果

6. 尽量避免反射,在高性能服务中杜绝反射的使用

7. 有些情况下可以尝试调优“GOGC”参数

8. 新版本稳定的前提下,尽量升级新的 Go 版本,因为旧版本永远不会变得更好。

下面描述一个真实的线上服务性能优化例子。

这是一个基础存储服务,提供 SetData 和 GetDataByRange 两个方法,分别实现批量存储数据和按照时间区间批量获取数据的功能。为了提高性能,存储的方式是以用户 ID 和一段时间作为 key,时间区间内的所有数据作为 value 存储到 KV 数据库中。因此,当需要增加新的存储数据时候就需要先从数据库中读取数据,拼接到对应的时间区间内再存到数据库中。

对于读取数据的请求,则会根据请求的时间区间计算对应的 key 列表,然后循环从数据库中读取数据。

这种情况下,高峰期服务的接口响应时间比较高,严重影响服务的整体性能。通过上述性能分析方法对于高峰期服务进行分析之后,得出如下结论:

问题点:

· GC 压力大,占用 CPU 资源高

· 反序列化过程占用 CPU 较高

优化思路:

1. GC 压力主要是内存的频繁申请和释放,因此决定减少内存和对象的申请

2. 序列化当时使用的是 Thrift 序列化方式,通过 Benchmark,我们找到相对高效的 Msgpack 序列化方式。

分析服务接口功能可以发现,数据解压缩,反序列化这个过程是最频繁的,这也符合性能分析得出来的结论。仔细分析解压缩和反序列化的过程,发现对于反序列化操作而言,需要一个”io.Reader”的接口,而对于解压缩,其本身就实现了”io.Reader“接口。在 Go 语言中,“io.Reader”的接口定义如下:

这个接口定义了 Read 方法,任何实现该接口的对象都可以从中读取一定数量的字节数据。因此只需要一段比较小的内存 Buffer 就可以实现从解压缩到反序列化的过程,而不需要将所有数据解压缩之后再进行反序列化,大量节省了内存的使用。

为了避免频繁的 Buffer 申请和释放,使用“sync.Pool”实现了一个对象池,达到对象复用的目的。

此外,对于获取历史数据接口,从原先的循环读取多个 key 的数据,优化为从数据库并发读取各个 key 的数据。经过这些优化之后,服务的高峰 PCT99 从100ms降低到15ms。

上述是一个比较典型的 Go 语言服务优化案例。概括为两点:

1. 从业务层面上提高并发

2. 减少内存和对象的使用

优化的过程中使用了 pprof 工具发现性能瓶颈点,然后发现“io.Reader”接口具备的 Pipeline 的数据处理方式,进而整体优化了整个服务的性能。

服务监控

Go 语言的 runtime 包提供了多个接口供开发者获取当前进程运行的状态。在 kite 框架中集成了协程数量,协程状态,GC 停顿时间,GC 频率,堆栈内存使用量等监控。实时采集每个当前正在运行的服务的这些指标,分别针对各项指标设置报警阈值,例如针对协程数量和 GC 停顿时间。另一方面,我们也在尝试做一些运行时服务的堆栈和运行状态的快照,方便追查一些无法复现的进程重启的情况。

Go编程思维和工程性

相对于传统 Web 编程语言,Go 在编程思维上的确带来了许多的改变。每一个 Go 开发服务都是一个独立的进程,任何一个请求处理造成 Panic,都会让整个进程退出,因此当启动一个协程的时候需要考虑是否需要使用 recover 方法,避免影响其它协程。对于 Web 服务端开发,往往希望将一个请求处理的整个过程能够串起来,这就非常依赖于 Thread Local 的变量,而在 Go 语言中并没有这个概念,因此需要在函数调用的时候传递 context。

最后,使用 Go 开发的项目中,并发是一种常态,因此就需要格外注意对共享资源的访问,临界区代码逻辑的处理,会增加更多的心智负担。这些编程思维上的差异,对于习惯了传统 Web 后端开发的开发者,需要一个转变的过程。

关于工程性,也是 Go 语言不太所被提起的点。实际上在 Go 官方网站关于为什么要开发 Go 语言里面就提到,目前大多数语言当代码量变得巨大之后,对代码本身的管理以及依赖分析变得异常苦难,因此代码本身成为了最麻烦的点,很多庞大的项目到最后都变得不敢去动它。而 Go 语言不同,其本身设计语法简单,类C的风格,做一件事情不会有很多种方法,甚至一些代码风格都被定义到 Go 编译器的要求之内。而且,Go 语言标准库自带了源代码的分析包,可以方便地将一个项目的代码转换成一颗 AST 树。

下面以一张图形象地表达下 Go 语言的工程性:

同样是拼成一个正方形,Go 只有一种方式,每个单元都是一致。而 Python 拼接的方式可能可以多种多样。

下面我们再结合Go与内涵段子的微服务升级之实录。

内涵段子Golang DAO

内涵近段时间迁移了部分API代码到Golang,主要是为了使用Golang中方便的goroutine。但是开发中很多冗余代码需要重复开发(缺少一个组件能够收敛各种RPC调用,复用代码,减少开发量),同时,又不希望组件使用过多的黑魔法,导致结构复杂,开发维护麻烦。

要求

希望开发一个组件:

* 能够收敛各种RPC调用,复用代码,减少开发量

* 能够利用Golang的goroutine优势,加快数据获取

* 不要使用太多黑魔法,导致结构复杂难于维护

假设场景:

需要实现一个接口,接受一个段子的Content_id,返回如下数据:

* 数据a. 段子基本内容Content → 调用获取Conent_Info接口

* 数据b. 段子的作者信息User → 调用获取User_Info接口

* 数据c. 段子的评论信息Comment → 调用获取Comment_Info接口

一、从RPC调用开始

假设场景在golang中的调用顺序就是:

1. 根据段子ID(Content_id),并发调用数据a(基本内容)和数据c(评论信息)

2. 根据a(基本内容)中的作者userid调用数据b(作者用户信息userinfo)

(图1-1)

单独看来,这些操作也没什么,但是我们看看完成这个步骤需要的代码:

ContentTask = NewContentInfoTask(id=123) CommentTask = NewCommentsListTask(ContentId=123) ParallelExec(ContentTask, CommentTask) // 并行调用两个任务 // 判断结果是否正确,一堆代码user_id = ContentTask.Response.User_id //获取作者ID UserResp = NewUserTask(user_id).Load() // 再获取作者信息 // 判断结果,一堆代码// 用上面获取的数据打包数据

我们看到,代码非常的冗余,而且麻烦在于,这种步骤基本每个接口都会需要进行,完全无法重用。 一旦数据有区别,需要多一点或者少一点数据,又需要重新写一个Load过程。很多Copy的代码。

问题一:那我们能否减少这种重复书写的代码?

二、基本的Dao功能

自然的,我们会想到将RPC调用都收敛到自己所属的实体(Dao),并建立Dao之间的关联关系,每个RPC对应一个方法(在方法中将数据填充到自身),即(图2-1):

此时,我们获取数据只需要如下代码:

content = NewContentDao(id=123) // 段子信息 comments = NewCommentList(ContentId=123) // 段子评论信息 // 第一层Load: 获取Content和comments的信息ParallelExec(content.Content_Info(), comments.Comment_Info()) # 并行两个任务 // 第二层Load: 获取user的属性user = NewUser(id=content.UserId) user.User_Info() // 使用上面对象的属性,进行数据的打包。

Python中可以将方法作为property,即使用某个属性的时候,才进行需要的RPC调用,使用更加的方便。但是也就不方便进行并行处理

显然的,此时代码已经省略了很多:将RPC调用收敛到了一个实体中。 更进一步,我们可以利用已经包含在了Dao关联关系之中的相互关系:即,获取用户和评论信息,是可以通过Dao来调用的。

content = NewContentDao(id=123) ParallelExec(content.Content_Info(),content.Comments.Comment_Info()) // 并发获取content基本信息和Comment信息 content.User.User_Info() //获取作者信息

至此,已经实现了基本的Dao功能。即:

· 收敛所有RPC、DB、Cache等跨服务调用

· 并建立他们之间的关联关系

· 收敛冗余代码,只要实现一套Dao(收敛属于该实体的所有调用)

此时要实现新一个接口将是相对轻松的工作!只需要聚合各种Dao之后打包数据即可

但是此时,代码就会是一个套路:加载第一层、加载第二层、、、、加载第N层。加载完所有数据之后,再进行打包。

问题二:那么我们能否让这种套路自动化?

三、自动构建调用树

再次回顾我们的Dao的关联关系对应的对象图,可以明显的看到是一个树结构(全树) (图3-1):

而我们需要的是这些属性:Content、User、Comment,即树中的某些节点 (图3-2):

所以,我们需要有某个组件(称之为Loader组件),使用DAO的调用方提供需要的属性(即上图中的红色部分),该组件将上图3-2和图3-1的全树进行match,一旦需要,则进行RPC调用,将结果数据放到Dao对象中。最终返回一个已经Load好数据的Dao的struct就可以啦!

问题三:

1. Dao之间有一些复杂的依赖关系,同时一个Dao内的属性又有依赖关系, 这个组件如何组织调用和对应的先后关系?

2. 如何实现调用中的并发获取?

3. 如何(何种形式)告诉这个组件你需要的数据的路径?

四、自动并发加载:

问题1:

组件如何组织调用和对应的先后关系?

在上一节自动构建调用树中我们已经知道Dao之间的关系。现在我们再将Dao拆开,以RPC调用为最小的单元,再来看下这个树(图4-1):

白圆圈是每个需要RPC调用的代码块,白方块表示属性(部分表示由RPC调用获取到的属性)。

有没有发现什么?我们单独拿Content来看,他的属性结构如下(图4-2):

再结合图4-1,可以看到:

1. 一些基本属性如Text、UserId、DiggCount仅仅依赖于主键ID;

2. 另外一些属性如:User、Category等,是依赖于1中的基本属性

此时,将一个DAO中的所有属性分成两种:

· Basic:依赖于主键ID,即获取这个属性,仅仅依赖于主键。

· Sub:依赖于Basic中的某个属性。

如此划分之后,就可以将Dao拆分成两层调用:

第一层Basic调用基本的;完成之后,Sub依赖的属性都具备了,再调用第二层;

至此该Dao的数据加载完成

划分之后,每个DAO的属性如下(图4-3):

如content则存在一个属性和RPC调用关联关系的map:

// 基本属性和RPC调用方法的映射BASIC_LOADER_MAP = map[string]Loader{ "basic": {Loader: duanziBasicLoader}, // 获取段子的基本属性(依赖于content_id) "commentStats": {Loader: commentstatsLoader}, // content的评论数据(依赖于content_id)}// Sub属性和RPC调用方法的映射SUB_LOADER_MAP = map[string]Loader{ "User": {Loader: userLoader,}, // 作者信息(依赖于段子信息中的user_id)}

再建立他们之间的联系(图4-4):

至于下层的Dao的调用,则交给下层的Dao来完成,当前层不再介入,此时,我们的调用结构如下(图4-5):

问题2:

如何实现调用中的并发获取?

我们只需要在调用过程中,将同一个层的Basic或者Sub进行并发调用,就可以了,如(图4-6):

即调用顺序如下(每行表示一个RPC调用,并列的行,并行调用):

1. 设置Content_Id

2. 开启Goroutine,并发调用Content的Basic层:

* a. RPC获取段子基本信息

* b. RPC获取段子Stats

* c. 评论Dao,

* 1. 评论Dao调用自身的Basic层

* a. RPC获取评论基本信息

* d. RPC获取评论相关数据stats

3. 开启Goroutine,并发调用Content的Sub层:

* a. CategoryDao

* 2. Basic层:

* a. RPC获取Category信息

* b. UserDao

* 1. Basic层:

* a. RPC获取用户基本信息

* 2. Sub层:

* .......

问题3:

最后,我们讨论一下问题三的3:如何告诉这个组件你需要的数据的路径?

其实上面的结构梳理清楚了,那么这个问题就好解决了, 我们无非是提供一个你需要的属性的树,让Dao的结构一层层遍历的过程中,将该树传递下去, 如果match,则mark一下这个RPC会进行调用,mark完成当前Dao的basic层和sub层之后,统一并发调用。 而下一级的Dao的Load则在sub层中做,下一级的Dao又去match提供的树,构建自身的Load任务。如此一层层进行下去,直到Load完所有你需要的属性!

比如你需要Contentinfo和Content中的UserInfo和Content中的Comment,就构造一棵树:

Content_Info → User_Info

→ Comment_Info

然后传递给该组件,他就能够只Load这几个需要的属性,match和构造以及并发调用的过程如下:

// paramLoaderMap、subLoaderMap是basic和sub属性和Rpc调用关系的mapfunc DaoLoad(needParamsTree, daoList, paramLoaderMap, subLoaderMap) error { var basicParamTaskList // Basic打包任务列表 var subDaoTaskList // Sub打包任务列表 // 遍历用户需要的属性,构造当前Dao的Basic和Sub任务结构 for _, sonTree := range needParamsTree { if basic属性需要Load { // put to basicParamTaskList } else if sub属性需要load{ // put to subDaoTaskList } } // 并发执行Basic Load // 并发执行Sub Load}

优化:

1. 组件来帮助调用方建立needParamsTree,只需要提供几个个字符串,:[]string{"Content_Info

go设置后端启动_今日头条内涵段子使用Go语言构建千亿级微服务架构实践相关推荐

  1. (转)今日头条内涵段子使用Go语言构建千亿级微服务架构实践

    今日头条在2015年中期前,使用的开发语言大量采用了Python和C++以及PHP技术栈. 随着系统复杂度,耦合度不断提升,开始向SOA服务化架构演进. 头条的内容发布系统使用了Django框架,一部 ...

  2. python 微服务框架 知乎_今日头条Go建千亿级微服务的实践

    原标题:今日头条Go建千亿级微服务的实践 原文作者:字节跳动技术团队 来源:知乎 小编有话说:如何寻找优质的学习资源是是否能够自学成功的前提要素,知乎作为一个流量比较大的问题和知识分享社区,在gola ...

  3. 今日头条 Go 建千亿级微服务的实践

    来源:https://zhuanlan.zhihu.com/p/26695984 今日头条当前后端服务超过80%的流量是跑在 Go 构建的服务上.微服务数量超过100个,高峰 QPS 超过700万,日 ...

  4. dubbo 自定义路由_爱奇艺在 Dubbo 生态下的微服务架构实践

    作者 | 周晓军 爱奇艺中间件团队负责人 导读:本文整理自作者于 2020 年云原生微服务大会上的分享<爱奇艺在 Dubbo 生态下的微服务架构实践>,重点介绍了爱奇艺在 Dubbo.Se ...

  5. go设置后端启动_开源一个go的H5游戏服务端开发框架

    本人也是因为go的魅力从原来的node.js转go开发的,但并没有放弃node.js开发.node.js开发起来极为舒服,谁用谁知道.go的性能,并发,静态编译速度还是更令人着迷,在云计算,区块链等未 ...

  6. go设置后端启动_名企实习项目|后端开发岗go微服务实战项目启动,大牛导师带你拿offer!...

    「DAC实习项目早知道」 今天是第2期实习项目推送 --go微服务实战项目-- 岗位职责 Position Description 1.负责协助高质量的设计和编码: 2.主要语言为Golang: 3. ...

  7. go设置后端启动_名企实习项目 | 后端开发岗go微服务实战项目启动,大牛导师带你拿offer!...

    「DAC实习项目早知道」 今天是第2期实习项目推送 --go微服务实战项目-- 微服务是近年来非常流行的架构,是后端开发工程师必备技能. 什么是微服务? 微服务(Microservices Archi ...

  8. python角谷猜想递归实现_全新.NET Core平台开发逆袭 重新认知.NET Core微服务架构视频教程 架构师级课程...

    全新.NET Core平台开发逆袭课程,将带领同学们重新认知.NET Core微服务架构,是真正的架构师级别的开放课程.课程为同学们打造了一个非常好的框架的起点,重点内容包括了容器环境下配置注入的最佳 ...

  9. bootstrap3 表单构建器_实例演示:如何构建高可用的微服务架构

    R 5月8日晚20:30,Kubernetes Master Class在线培训第五期<Kubernetes中的日志.监控与告警> 当你设计和构建大规模应用时,你将面临两个重大挑战:可伸缩 ...

最新文章

  1. 生成对抗网络gan原理_中国首个“芯片大学”即将落地;生成对抗网络(GAN)的数学原理全解...
  2. 电脑怎么分屏2个显示器_程序员一台电脑装2个显示屏?因为专业
  3. Nature:学术造假者瑟瑟发抖,论文图像查重AI技术重拳出击!
  4. php双向通信,RSA + AES 双向通信加密
  5. php getimagesize图片宽高反了_PHP实现简单验证码识别
  6. 聊聊lettuce的shareNativeConnection参数
  7. C++实现字符全排列
  8. 嵌入式-C语言面试题【转】
  9. win32开发(添加菜单)
  10. 洛谷P1031 均分纸牌(贪心)
  11. 使用二维NDRange workgroup
  12. html——float与clear详解(深度好文)
  13. Android mc怎么和win10联机,大更新!我的世界手机版/win10版联机完美互通
  14. 数字信号处理知识点总结(一):卷积
  15. css3元素简单的闪烁效果
  16. xpath的extract()方法
  17. Python操作SIM800C发送中文短信
  18. 一.软件使用与基础入门
  19. SUMO/检测器设置(E3)学习总结
  20. 杂散干扰解决办法_6种直流电源杂散干扰的成因分析及解决办法

热门文章

  1. 什么样的软文是好软文?好软文有哪些共性?
  2. IDEA导出WAR包,非常详细
  3. vivo AI计算平台 Kubernetes集群Ingress网关实践
  4. python3 requests 实现12306购票登录模块
  5. java删除数组中重复元素的几种方法
  6. SSM框架整合入门案例
  7. Oracle恢复一例--ORA-03113、ORA-24324,ORA-01041错误
  8. 一入Java 深似海
  9. 云胶片(云影像)功能简介
  10. MySql 锁的分类