写点什么

微服务的各种线程模型及其权衡

2016 年 10 月 24 日

架构师在设计微服务架构的时候,一般会关注模式、拓扑以及粒度等问题,但是有一个最为基础的决策是线程模型。我们现在有了很多的开源工具、编程语言和技术栈,软件架构师所面临的选择要比以往更多了。

这样的话,我们很容易就会迷失在语言的细节和 / 或不同库的差异之中,从而无法分辨什么东西才是最重要的。

为微服务选择正确的线程模型并确定它将如何与数据库连接进行关联非常重要,这决定了你的解决方案是刚刚能用,还是会成为一个很棒的产品。

作为架构师,在考虑效率和复杂性之间的权衡时,关注线程模型是一种有效的方式。服务会被分解为并行的操作,通过共享的资源来进行处理,所以应用会变得更加高效,其响应的延迟也会更短(这会在一定的范围之内,参见 Amdahl 定理)。不过,并行操作和安全的资源共享会为代码引入更多的复杂性。

代码越复杂,工程师完全理解起来就会越困难,这意味着在每次变更的时候更有可能引入新的 bug。

架构师最为重要的责任之一就是在效率和代码复杂性之间找到一个平衡。

单线程单进程的线程模型

最基本的线程模型就是单线程单进程模型,按照这种方式编写代码是最简单的。

单线程单进程服务同一时间无法在多个核心上执行。在现代的裸机服务器上,核心数量一般能够达到 24 个。如果按照这种模型构建服务的话,它所能使用的服务器核心数量不会超过一个。如果有额外的负载的话,这些服务的吞吐量不会随之增加,它们的 CPU 利用率百分比不会超过个位数。鉴于如此高的未利用率,所以有一种补偿策略就是使用更大的服务器池来处理负载。

这种方式可以运行,但是它非常浪费,最终的成本会非常高昂。最为流行的云计算供应商都以非常便宜的价格提供单虚拟核心实例,这样做是为了以更细的粒度来支持这种模式,从而应对扩展性的需求。

单线程多个新进程的线程模型

在复杂性和效率方面更进一步的就是单线程多进程的线程模型,在这种方式下,会为每个请求创建一个新的进程。编写这种类型的微服务相对比较简单,但是跟前面的模型相比它包含了更多的复杂性。

(点击放大图像)

创建进程的开销以及持续创建和销毁数据库连接会占用处理器的时间,因此会增加所有协作服务的延迟。这种线程模型之所以会创建更多的数据库连接是因为数据库连接是属于每个进程的,无法跨进程边界共享。进程的存活时间只会在请求的时间范围内,所以每个请求必须要重新连接数据库。

按照这种线程模型运行的微服务应该延迟对数据库的连接,直到需要的时候再创建连接。如果代码路径不需要的话,那就没有必要耗费成本创建数据库连接了。尽管数据库连接无法跨进程缓存,但是有些环境支持跨进程的opcode 缓存,这样的话,我们可以将服务的配置数据存储起来,如连接到数据库的主机IP 和凭证信息,两个流行的opcode 缓存样例就是Zend OpCache 和APC。

单线程多进程重用的线程模型

在代码复杂性和性能方面的下一步提升就是这种线程模型,这是一种单线程多线程的模型,新的请求都会重用已有的worker 进程。这与前面的线程模型有所不同,在前面的模型中,会为每个请求都创建一个新的进程。而在这个线程模型中,在进程提供就绪之后,就不会创建新的进程了。

(点击放大图像)

这种服务在复杂性方面相对来说比较简单直接,但是需要额外的代码来管理worker 进程的生命周期。这些代码必须要正确地进行重新初始化。例如,程序员可能会维护一些静态变量,而不是以参数的形式传递大量的数据。这样的话,代码会更加简单,针对每个新的请求都对这些静态变量进行重置的话,代码就能正常运行。但是如果代码没有重置这些变量的话,那么它就会基于之前的请求来进行处理,而不是基于当前的请求。在代码复杂性方面,另外一点就是需要包含恢复失效(stale)数据库连接的逻辑。当与数据库的连接由于不活跃而断掉的时候,原有的数据库连接实例可能会出现失效的情况。

因为每个进程能够服务于多个请求,所以没有必要针对每个请求都重新连接数据库。数据库连接会进行重用,这样的话能够规避创建连接的成本,从而减少延迟。但是,每个进程本身还是需要创建和管理自己的数据库连接。因为进程之间无法共享数据库连接,所以进程间公用的数据库会打开更多的连接。打开过多的连接将会降低数据库的性能。这是因为数据库连接是有状态的,数据库应用必须要在自己的进程中为每个连接分配资源。

多线程单进程的线程模型

我们有一种保护数据库的更好方式,这就是在多线程、长期存活的单进程模型中通过可配置的连接数来使用连接池。尽管数据库连接无法跨多进程共享,但是在同一个进程中,它可以在多个线程间共享。

(点击放大图像)

如下是一个样例:如果有100 个单线程的进程,每个进程有10 台服务器,那么数据库将会有100 X 10 = 1000 个连接。如果我们的每个进程有100 个线程,共有10 台服务器,每个进程在它的连接池中配置了10 个连接,那么数据库只会有10 X 10 = 100 个连接,它依然能够实现很高的吞吐量。对于服务和数据库来说,跨线程的连接池都是一种高效的方案。

这种连接池技术既能实现很高的吞吐量又能保护数据库,但是它会带来额外的代码复杂性。因为线程必须共享有状态的数据库连接,所以开发人员需要识别并修正并发相关的缺陷,比如死锁、活锁、线程饿死和竞态条件。解决这些缺陷的方式之一就是进行序列化地访问,但是太多的序列化访问会降低并行性。对于初级的开发人员来说,这些类型的缺陷很难识别和修正。

多线程、长期存活的单进程模型有两种实现风格:一种是为请求分派一个专属的线程,另一种是所有的请求共享一个线程。在前者的线程模型中,每个请求会有一个专门的线程与之关联,这要限制并行处理的请求数量。太多的连接可能会导致效率低下,这是因为在操作系统的CPU 调度器中需要执行太多的任务切换。

在后者的线程模型中,我们没有必要为每个请求创建额外的线程,但是I/O 相关的任务必须要在单独的线程池中运行,这样的话,能够防止系统因为遇到较慢的操作而hang 住,请求处理器需要等待线程池的处理结果。

这种方式没有为每个请求创建专门的线程,对于异步操作我们可以期望有很高的吞吐量和较低的延迟,但是对于同步操作来说,相对于为每个请求创建专属的线程,这种方式不会带来性能方面的改善。

小结

线程模型

效率问题 代码复杂性问题

单线程单进程

服务无法充分利用服务器的核心,吞吐量不会随着负载的增加而增加,CPU 的使用率不会超过10%。

最简单且最易理解的方式。

单线程多进程模型,为每个请求创建新的进程

创建进程的开销以及持续地创建和销毁大量数据库连接可能会增加延迟。

数据库连接应该是懒加载的,考虑使用OpCode 缓存。

单线程多进程模型,请求重用worker 进程

数据库会面临更多的连接,因为这些连接无法跨进程共享,打开过多的连接有可能会降低数据库的性能。

需要额外的代码来管理worker 进程的生命周期。代码必须能够从失效连接中恢复,在处理每个请求时,静态变量应该要进行重置。

多线程单个长期存活的进程,每个请求有专门的线程

对于数据库和服务来说,跨线程的连接池方案是非常高效的;为每个请求都创建额外的线程将会限制并行处理的请求数量。

因为线程需要共享有状态的数据库连接,开发人员必须要识别和修正并发相关的缺陷,如死锁、活锁、线程饿死以及竞态条件等。

多线程单个长期存活的进程,不为每个请求创建专门的线程

对于数据库和服务来说,跨线程的连接池方案是非常高效的;异步操作会有很高的吞吐量。

I/O 相关的任务必须要在单独的线程池中运行。如果必须要将结果返回给调用者的话,那么请求处理器需要等待线程池完成对请求的处理,得到结果。

结论

在考虑采用哪种库和语言之前,软件架构师应该反思哪种线程模型能够最适合其工程文化和能力。在代码复杂性和效率之间取得一个很好的平衡将会有助于理清这些困惑,在各种可行的技术栈之间做出选择时,也能有一个正确的方向。因为微服务的范围要比单体应用更小,为了实现更高的效率,可以在代码复杂性方面做出更多的努力。

关于作者

Glenn Engstrand是 Zoosk 架构师团队的技术领导。他的主要关注点在于服务端的应用架构,他所关注的架构需要运行在 B2C Web 可扩展的环境中,并且还要保证可控的运维和部署成本。在波士顿的 2012 Lucene Revolution 会议上,Glenn 是一位知名的演讲者。他的专长在于将单体应用拆分为微服务并使其与实时通信设施进行深度集成。

查看英文原文: Microservice Threading Models and their Tradeoffs

2016 年 10 月 24 日 17:142446

评论

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

解密360容器云平台的Harbor高可用方案

博文视点Broadview

容器 高可用 云原生 k8s Harbor

中国首个“芯片大学”即将落地;生成对抗网络(GAN)的数学原理全解

京东智联云开发者

技术 网络 GAN 芯片

反向保理系统设计

森林

Spring Cloud 微服务实践(8) - 部署

xiaoboey

Docker zookeeper 微服务 Spring Cloud actuator

不走寻常路

滴滴技术

招聘 滴滴技术 地图与公交事业群分享月

有状态的服务其实可以做更多的事情

架构师修行之路

分布式 微服务

都别拦着我,我要删库了

MySQL从删库到跑路

Linux oracle重装 MySQL 运维 root

关于GO语言,这篇文章讲的很明白

华为云开发者社区

go 编程语言 语言

程序员的美丽假期(并不)

Learun

程序员 敏捷开发 软件设计

MySQL-技术专题-mysql的联合索引

李浩宇/Alex

第四周 作业二:系统架构学习总结【未陌】

a d e

系统架构 互联网架构

滴滴导航若干关键功能的技术突破与实践

滴滴技术

人工智能 滴滴技术 滴滴导航

违规内容屡屡曝光下,企业如何自救

Geek_e670ab

Kubeless 架构设计 | 玩转 Kubeless

donghui2020

Serverless kubeless

MySQL-技术专题-解决死锁问题

李浩宇/Alex

头条终面:写个消息中间件

yes的练级攻略

消息队列 面试技巧

Github资源在线加速下载

xcbeyond

GitHub 工具类网站

惊艳!阿里出产的MyCat性能笔记,带你领略什么叫细节爆炸

周老师

Java 编程 程序员 架构 面试

Java之父都需要的《Effective Java中文版(第3版)》到底有多牛

Java成神之路

Java 程序员 面试 编程语言

Java 未捕获异常处理

朱华

Java Exception

JAVA中的内部类详解

倔强的攻城狮

Java

MySQL-技术专题-事务和并发一致性问题

李浩宇/Alex

链表反转的两种实现方法,后一种击败了100%的用户

小Q

Java 程序员 数据结构 算法 开发

《谛听说智能》迎来圆满落幕,企业降本增效新指南

Geek_e670ab

kubernetes是微服务发展的必然产物

架构师修行之路

Kubernetes 分布式 微服务

LeetCode题解:83. 删除排序链表中的重复元素,HashMap,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

DB-Engines 10月数据库排名:“三大王”无人能敌,PostgreSQL紧随其后

华章IT

数据库 postgresql Clickhouse MySQ

对象的实例化内存布局与访问定位

朱华

Java 对象初始化

看这里!带你快速体验MindSpore V1.0(For ubuntu 18.04)

华为云开发者社区

华为 AI 技术

英特尔为北京2022年冬奥会打造智慧新体验

intel001

第四周 作业一:系统架构【未陌】

a d e

系统架构

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

微服务的各种线程模型及其权衡-InfoQ