From 6b27cb7e8915f6013672651b31758b8ff6f442b7 Mon Sep 17 00:00:00 2001
From: yulewei 技术栈是构建应用的技术集合,由编程语言、框架、库、服务器、数据库、工具等组合而成。组成技术栈的技术是与具体业务无关的基础软件。互联网公司选择的技术栈,倾向于使用开源软件,相对于专有软件,开源软件具有高质量、免费、开放、灵活等优势。互联网的早期开拓者 Yahoo 的技术栈选择是经典案例,受开源运动的影响,在 2000 左右 Yahoo 从最初基于自定义的专属软件迁移了到 LAMP 技术栈。 在“开源”(open source)一词出现之前,技术社区的黑客选择使用“自由软件”(free software)这个词。但是“自由软件”这个词与对知识产权的敌意、共产主义和其它观点相联系,几乎不受管理者和投资者的欢迎,于是 1998 年 2 月 3 日在由 Eric Raymond 等人参加的会议上“开源”一词诞生,2 月下旬开源软件促进会成立 OSI,Eric Raymond 担任主席。自由软件和开源软件被合称为 FOSS。缩写“LAMP”代表的是 Linux-Apache-MySQL-PHP,这些软件都是自由软件或开源软件。 在互联网诞生早期,开源技术栈、开源社区尚未成熟,互联网公司不得不自研专有软件,随着开源软件的成熟,技术栈的选择开始从专有软件逐渐转向开源软件。主要 Web 技术和服务端技术的发展时间线: 在互联网诞生早期,开源技术栈、开源社区尚未成熟,互联网公司不得不自研专有软件,随着开源软件的成熟,技术栈的选择开始从专有软件逐渐转向开源软件。先来看下,主要 Web 技术和服务端技术的发展时间线: 按编程语言区别,主要流行的互联网产品的创建时间和早期的技术栈选择(也可以参见 wiki): 按编程语言区别,主要流行的互联网产品的创建时间和早期的技术栈选择(也可以参见 wiki): 值得一提的是,PHP 之父 Rasmus Lerdorf,在 2002 年至 2009 年期间为供职于 Yahoo!,2011 年起至今供职于 Etsy。Python 之父 Guido van Rossum,在 2005 年至 2012 年期间供职于 Google,在 2013 年至 2019 年期间供职于 Dropbox。国内最有影响力的 PHP 技术专家是惠新宸,是加入 PHP 语言官方开发组的首位国人,曾先后供职于雅虎中国、百度、新浪微博、链家网等公司。 2000 年前创建的网站,因为开源技术栈尚未成熟,编程语言基本上都是选择 C++ 开发。在开源技术栈成熟后,多数网站会选择拥抱开源。具体选择哪个编程语言,PHP、Ruby、Python、Java 等,主要由技术负责人的技术偏好决定。根据 w3techs 统计,历年网站使用的服务端编程语言统计占比,2012 年至今 10 多年,PHP 占比稳居榜首,每年都是 75% 以上。大型网站都是公司内的技术团队研发的,技术栈由技术团队选择,而很多小型网站,很可能会直接使用开源的 CMS 系统搭建。根据 w3techs 统计,前 100 万网站中有 43.0% 使用 WordPress 构建。WordPress 的服务端编程语言就是 PHP,数据库是 MySQL。大型网站选择 PHP 越来越少,因为 PHP 是解释型脚本语言,相对编译型编程语言有性能劣势。另外,PHP 的优势是快速开发 Web 动态网页,但是随着前后端分离开发模式的流行,Web 页面从服务端渲染逐渐转向客户端渲染,PHP 的优势不再,后端工程师只需要向前端提供 REST API 接口,展示层的实现完全由前端工程师实现。 2000 年前创建的网站,因为开源技术栈尚未成熟,编程语言基本上都是选择 C++ 开发。在开源技术栈成熟后,多数网站会选择拥抱开源。具体选择哪个编程语言,PHP、Ruby、Python、Java 等,主要由技术负责人的技术偏好决定。根据 w3techs 统计,历年网站使用的服务端编程语言统计占比,2012 年至今 10 多年,PHP 占比稳居榜首,每年都是 75% 以上。大型网站都是公司内的技术团队研发的,技术栈由技术团队选择,而很多小型网站,很可能会直接使用开源的 CMS 系统搭建。根据 w3techs 统计,前 100 万网站中有 43.0% 使用 WordPress 构建。WordPress 的服务端编程语言就是 PHP,数据库是 MySQL。大型网站选择 PHP 越来越少,因为 PHP 是解释型脚本语言,相对编译型编程语言有性能劣势。另外,PHP 的优势是快速开发 Web 动态网页,但是随着前后端分离开发模式的流行,Web 页面从服务端渲染逐渐转向客户端渲染,PHP 的优势不再,后端工程师只需要向前端提供 REST API 接口,展示层的实现完全由前端工程师实现。 早期部分网站选择 .NET 技术栈,比如京东、携程。但是,Java 平台生态更完善,有非常多的经验可以借鉴。另外,.NET 平台本身虽然不收费,但是 Windows 操作系统是收费的,开发工具也不便宜。于是京东在 2012 年从 .NET 迁移到 Java,携程在 2017 左右从 .NET 迁移到 Java。目前在国内,多数互联网大厂都选择 Java 技术栈,如淘宝、美团、京东、微博、携程等,Java 相对来说是主流选择。 编程语言的另外一个流行趋势是 Go 语言。Go 语言诞生于 Google,在 2009 年 11 月对外公开。在发明 Go 语言前,Google 内部主要使用的语言是 C++、Java 和 Python 等,但是 Go 发明者认为这些语言无法同时满足高效编译、高效执行和易于编程的特性诉求,所以创造了 Go 语言[48]。Go 比 C++ 能更高效编译和易于编程,比 Java 更易于编程,比 Python 能更高效执行。在 GoCon Tokyo 2014 会议上,Go 语言研发团队的 Brad Fitzpatrick 对各种编程语言在编程乐趣和执行速度(Fun vs. Fast)的对比[49],如下图所示: 早期由 Go 语言实现的经典开源项目主要是基础设施软件,比如 YouTube 的 Vitess 数据库分片中间件(2012.02 对外开源)、Docker 容器(2013.03 对外开源)、Red Hat 的 CoreOS 团队的 Etcd 分布式配置服务(2013.07 对外开源,实现 Raft 共识协议)、前 Google 工程师创建的 CockroachDB 分布式数据库(2014.02 对外开源,受 Google Spanner 启发)、Google 的 Kubernetes 容器编排系统(2014.06 对外开源)、SoundCloud 的 Prometheus 监控工具(2015.01 对外开源)等。大规模使用 Go 语言的代表性的国外的互联网公司有 SoundCloud[19][20]、Dropbox[30][31]、Uber[38] 等,国内的有七牛云、字节跳动[40][41]、哔哩哔哩[12]、滴滴[16]、知乎[39]、百度[50][51]、腾讯[52]等,在这些互联网公司 Go 语言通常主要被用于构建基础设施软件(网关、数据存储、监控、视频处理等)或高性能要求的业务服务,部分公司的大量核心业务也从 Python、PHP 等迁移到 Go 语言。使用 Go 语言的公司的更加完整的列表,可以参见官方整理的“GoUsers”。 数据库方面,根据 DB-Engines Ranking 的统计,流行的关系数据库主要是 4 个,开源免费的 MySQL 和 PostgreSQL,专有收费的 Oracle 和 Microsoft SQL Server。多数互联网产品使用的数据库是 MySQL,部分是 PostgreSQL 或 Oracle。一些早期使用 Oracle 数据库的网站选择部分或完全从 Oracle 数据库中迁出,代表性的案例是,Amazon 从 Oracle 完全迁移到 Amazon RDS 和 NoSQL,淘宝从 Oracle 完全迁移到 MySQL。以 PostgreSQL 为主数据库的互联网应用有 Skype、Reddit、Spotify、Disqus、Heroku、Instagram 等[53]。 MySQL 相对 PostgreSQL 更加流行的主要原因是[54][55],在早期 MySQL 入门槛更低。MySQL 就支持在 Windows 下安装,而 PostgreSQL 只能在 Cygwin 下安装,MySQL 对 Windows 平台的支持使得初学者更容易上手,并且 MySQL 更加易于管理,能够快速简单地启动和使用,而且 MySQL 拥有一个非常简洁、易于导航和用户友好的在线文档和支持社区。另外一个重要原因是,MySQL 很早就默认支持复制(replication),而 PostgreSQL 的复制是第三方的,而且极其难用。之后的几年即便 PostgreSQL 完善了不足,但错过了流行的时间窗口,MySQL 成为了主流选择,有更完善的生态。不过,受 MySQL 被 Oracle 公司收购的影响,近几年 PostgreSQL 开始越来越流行,根据 DB-Engines Ranking 的统计,最新的 PostgreSQL 的流行度分数是 10 年前的三倍,流行度与前三名越来越接近。 容易发现多数网站的技术栈的演进模式类似。在创建网站早期通常选择使用 PHP、Ruby、Python 脚本语言和框架来开发,这些脚本语言和框架具有更快的开发效率,能快速交付上线。随着网站流量的增长,面临性能和可扩展性问题,通常会将网站服务化,从单体架构向面向服务的 SOA 和微服务架构演进,一大部分网站在服务化过程中会选择将核心业务改由其他性能更佳的编译型编程语言来实现,如 Java、Scala、C++、Go,原先的脚本语言可能全部废弃,也可能仅用于前端展示层的 Web 动态页面渲染,比如 Facebook、Twitter 等。当然也有一部分网站,演进为 SOA 和微服务架构后,业务逻辑的实现一直以最初的脚本语言为主。 编程语言的另外一个流行趋势是 Go 语言。Go 语言诞生于 Google,在 2009 年 11 月对外公开。在发明 Go 语言前,Google 内部主要使用的语言是 C++、Java 和 Python 等,但是 Go 发明者认为这些语言无法同时满足高效编译、高效执行和易于编程的特性诉求,所以创造了 Go 语言48。Go 比 C++ 能更高效编译和易于编程,比 Java 更易于编程,比 Python 能更高效执行。在 GoCon Tokyo 2014 会议上,Go 语言研发团队的 Brad Fitzpatrick 对各种编程语言在编程乐趣和执行速度(Fun vs. Fast)的对比49,如下图所示: 早期由 Go 语言实现的经典开源项目主要是基础设施软件,比如 YouTube 的 Vitess 数据库分片中间件(2012.02 对外开源)、Docker 容器(2013.03 对外开源)、Red Hat 的 CoreOS 团队的 Etcd 分布式配置服务(2013.07 对外开源,实现 Raft 共识协议)、前 Google 工程师创建的 CockroachDB 分布式数据库(2014.02 对外开源,受 Google Spanner 启发)、Google 的 Kubernetes 容器编排系统(2014.06 对外开源)、SoundCloud 的 Prometheus 监控工具(2015.01 对外开源)等。大规模使用 Go 语言的代表性的国外的互联网公司有 SoundCloud1920、Dropbox3031、Uber38 等,国内的有七牛云、字节跳动4041、哔哩哔哩12、滴滴16、知乎39、百度5051、腾讯52等,在这些互联网公司 Go 语言通常主要被用于构建基础设施软件(网关、数据存储、监控、视频处理等)或高性能要求的业务服务,部分公司的大量核心业务也从 Python、PHP 等迁移到 Go 语言。使用 Go 语言的公司的更加完整的列表,可以参见官方整理的“GoUsers”。 数据库方面,根据 DB-Engines Ranking 的统计,流行的关系数据库主要是 4 个,开源免费的 MySQL 和 PostgreSQL,专有收费的 Oracle 和 Microsoft SQL Server。多数互联网产品使用的数据库是 MySQL,部分是 PostgreSQL 或 Oracle。一些早期使用 Oracle 数据库的网站选择部分或完全从 Oracle 数据库中迁出,代表性的案例是,Amazon 从 Oracle 完全迁移到 Amazon RDS 和 NoSQL,淘宝从 Oracle 完全迁移到 MySQL。以 PostgreSQL 为主数据库的互联网应用有 Skype、Reddit、Spotify、Disqus、Heroku、Instagram 等53。 MySQL 相对 PostgreSQL 更加流行的主要原因是5455,在早期 MySQL 入门槛更低。MySQL 就支持在 Windows 下安装,而 PostgreSQL 只能在 Cygwin 下安装,MySQL 对 Windows 平台的支持使得初学者更容易上手,并且 MySQL 更加易于管理,能够快速简单地启动和使用,而且 MySQL 拥有一个非常简洁、易于导航和用户友好的在线文档和支持社区。另外一个重要原因是,MySQL 很早就默认支持复制(replication),而 PostgreSQL 的复制是第三方的,而且极其难用。之后的几年即便 PostgreSQL 完善了不足,但错过了流行的时间窗口,MySQL 成为了主流选择,有更完善的生态。不过,受 MySQL 被 Oracle 公司收购的影响,近几年 PostgreSQL 开始越来越流行,根据 DB-Engines Ranking 的统计,最新的 PostgreSQL 的流行度分数是 10 年前的三倍,流行度与前三名越来越接近。 容易发现多数网站的技术栈的演进模式类似。在创建网站早期通常选择使用 PHP、Ruby、Python 脚本语言和框架来开发,这些脚本语言和框架具有更快的开发效率,能快速交付上线。随着网站流量的增长,面临性能和可扩展性问题,通常会将网站服务化,从单体架构向面向服务的 SOA 和微服务架构演进,一大部分网站在服务化过程中会选择将核心业务改由其他性能更佳的编译型编程语言来实现,如 Java、Scala、C++、Go,原先的脚本语言可能全部废弃,也可能仅用于前端展示层的 Web 动态页面渲染,比如 Facebook、Twitter 等。当然也有一部分网站,演进为 SOA 和微服务架构后,业务逻辑的实现一直以最初的脚本语言为主。 在架构微服务化后,很多网站实现的微服务之间采用基于 HTTP 协议的 REST 方式通信(或者使用的 RPC 框架支持跨语言 RPC 调用),各个微服务的实现在理论上编程语言不需要统一,可以根据该微服务的性能要求或负责该微服务的团队的技术偏好自由选择,所以真实世界的互联网公司内部各个业务子系统的技术栈可能不统一。但是内部技术栈不统一会带来额外的维护成本。另外,考虑技术栈的迁移重构成本,新的业务子系统使用新的技术栈,而有些旧的业务子系统很可能继续使用旧的技术栈。除了业务子系统外,很可能团队内部有自研的中间件、运维工具等,这些组件由中间件团队、运维团队研发,很可能会选择与业务系统不一样的技术栈。 注意,解决网站的可扩展性问题,不一定需要演进为服务化架构。架构服务化意味着将完整的单体服务按业务的功能领域做垂直拆分,而实际上在应用服务层可以通过部署多个相同副本的单体服务的方式实现系统的水平扩展,网站的可扩展性问题主要在数据存储层上。拆分应用服务的好处在于能实现组织团队的规模化。解决数据库的扩展性的策略有,数据复制(数据缓存、数据库读写分离)、数据垂直拆分、数据水平拆分(也叫数据分片,sharding)。数据库被拆分后,如果单个事务内的数据分散在多个节点就要解决分布式事务问题,但是实现分布式事务代价太大,通常的选择是牺牲一致性,仅满足最终一致性(BASE)。 没有拆分应用服务,始终采用单体架构的经典案例是 Instagram,2019 年 Instagram 在技术博客上有这样一段话[37]: 没有拆分应用服务,始终采用单体架构的经典案例是 Instagram,2019 年 Instagram 在技术博客上有这样一段话37: Our server app is a monolith, one big codebase of several million lines and a few thousand Django endpoints, all loaded up and served together. A few services have been split out of the monolith, but we don’t have any plans to aggressively break it up. 为了解决可扩展性问题,Instagram 在数据存储[35][36],对 PostgreSQL 数据库做了主从读写分离、数据垂直拆分和数据分片,并且使用了 NoSQL 数据库 Cassandra,用于存储 Feed 流等数据。类似的,Reddit 也是单体架构,在数据存储层做可扩展性改造,对 PostgreSQL 数据库做了垂直拆分,并且使用 Cassandra 数据库[25]。 Yahoo!,1994 年创立,早期网站操作系统是 FreeBSD,Web 服务器是自研的 Filo Server,数据库是自研的 DBM 文件,Web 动态页面脚本是自研的 yScript,业务逻辑编程语言是 C++。1996 年 Web 服务器改用 Apache HTTP Server,1999 年部分数据库改用 MySQL(同时也使用 Oracle 数据库),2002 年编程语言从 yScript 和 C++ 改为 PHP,操作系统也从 FreeBSD 逐渐转向 Linux[56][57][58]。在 2002.10 的 PHPCon 2002 会议上,Yahoo! 工程师 Michael Radwin 介绍了 Yahoo! 从专有软件向开源软件转向的原因和演进过程,以及选择 PHP 而不选择 ASP、Perl、JSP 等其他技术的原因[56]。Yahoo! 转向开源软件的原因主要是,避免维护专有软件的成本,开源软件从早期的不成熟最终变得成熟,具有更好的性能,更容易与第三方软件集成,以及开源社区逐渐发展壮大。2005 年,Yahoo! 的技术架构如下图所示[57]: 为了解决可扩展性问题,Instagram 在数据存储3536,对 PostgreSQL 数据库做了主从读写分离、数据垂直拆分和数据分片,并且使用了 NoSQL 数据库 Cassandra,用于存储 Feed 流等数据。类似的,Reddit 也是单体架构,在数据存储层做可扩展性改造,对 PostgreSQL 数据库做了垂直拆分,并且使用 Cassandra 数据库25。 Yahoo!,1994 年创立,早期网站操作系统是 FreeBSD,Web 服务器是自研的 Filo Server,数据库是自研的 DBM 文件,Web 动态页面脚本是自研的 yScript,业务逻辑编程语言是 C++。1996 年 Web 服务器改用 Apache HTTP Server,1999 年部分数据库改用 MySQL(同时也使用 Oracle 数据库),2002 年编程语言从 yScript 和 C++ 改为 PHP,操作系统也从 FreeBSD 逐渐转向 Linux565758。在 2002.10 的 PHPCon 2002 会议上,Yahoo! 工程师 Michael Radwin 介绍了 Yahoo! 从专有软件向开源软件转向的原因和演进过程,以及选择 PHP 而不选择 ASP、Perl、JSP 等其他技术的原因56。Yahoo! 转向开源软件的原因主要是,避免维护专有软件的成本,开源软件从早期的不成熟最终变得成熟,具有更好的性能,更容易与第三方软件集成,以及开源社区逐渐发展壮大。2005 年,Yahoo! 的技术架构如下图所示57: 2000 年 ~ 2010 年,Yahoo! 是世界上流量最大的网站,虽然期间被 Google 短暂超越,但随后又反超,一直到到 2010 年后才被 Google 彻底超越。2000 年前后 Yahoo! 转向到 MySQL 和 PHP,并在技术上助力 LAMP 生态的发展壮大,对 LAMP 技术栈的流行起到巨大的推动作用。值得一提的是,知名 MySQL 专家 Jeremy Zawodny,《高性能MySQL》2004 年第 1 版(豆瓣)的作者,在 1999.12 ~ 2008.06 期间为 Yahoo! 工程师;PHP 之父 Rasmus Lerdorf 在 2002.09 ~ 2009.11 期间为 Yahoo! 工程师。 Amazon,1994 年创立,早期是单服务、单数据库的单体架构,使用的编程语言是 C++,2000 年开始从单体架构向 SOA 和微服务架构演进,使用最多的语言变为 Java,C++ 被用在高性能要求的系统和底层基础设施组件。Amazon 架构演进过程如下图所示[59]: Amazon.com 的技术栈演进过程: Amazon,1994 年创立,早期是单服务、单数据库的单体架构,使用的编程语言是 C++,2000 年开始从单体架构向 SOA 和微服务架构演进,使用最多的语言变为 Java,C++ 被用在高性能要求的系统和底层基础设施组件。Amazon 架构演进过程如下图所示59: Amazon.com 的技术栈演进过程: 当前 Amazon.com 网站的主要技术栈[59][60][61][62]: 当前 Amazon.com 网站的主要技术栈59606162: eBay,1995 年创立,早期使用的编程语言是 Perl,1997 年迁移到 C++,2002 年迁移到 Java。eBay 的技术栈演进过程[66][67]: eBay,1995 年创立,早期使用的编程语言是 Perl,1997 年迁移到 C++,2002 年迁移到 Java。eBay 的技术栈演进过程6667: 2016 年,eBay 的技术架构如下图所示[69]: 当前 eBay 的主要技术栈[66][67][69][70]: 2016 年,eBay 的技术架构如下图所示69: 淘宝技术架构[75][76][77][78][79],是从 LAMP 架构演变而来的。在 2003 年 5 月最早上线时,淘宝是对购买得到的采用 LAMP 架构网站源码进行二次开发的网站。为了应对网站流量的增长,2003 年底数据库从 MySQL 切换到 Oracle,2004 年初编程语言从 PHP 迁移到 Java,同时硬件演变为 IBM 小型机和 EMC 高端硬件存储,依赖的基础设施被统称为“IOE”(IBM 小型机、Oracle 数据库、EMC 存储设备),这个架构被称为 2.0 架构。 2007 年底开始拆分第一个业务中心是用户中心(UIC,User Information Center),在 2008 年初上线。2008 年初,启动“千岛湖”项目,拆分出了交易中心(TC,Trade Center)和类目属性中心(Forest)。2008 年 10 月,为了打通淘宝网和淘宝商城(后来的天猫)的数据,启动“五彩石”项目,拆分出了店铺中心(SC,Shop Center)、商品中心(IC,Item Center)、评价中心(RC,Rate Center)。到 2009 年初,“五彩石”项目结束,阿里电商架构也从之前的单体架构演进为了 SOA 服务化架构,被称为 3.0 架构,同时淘宝网和淘宝商城也完成了数据整合,并淘宝网和淘宝商城的共享业务沉淀到多个业务中心。在 2009 年,共享业务事业部成立,在组织架构上共享业务事业部和淘宝、天猫平级。在业务中心的上层业务服务有,交易管理(TM,Trade Manager)、商品管理(IM,Item Manager)、店铺系统(ShopSystem)、商品详情(Detail)、评价管理(RateManager)等。2009 年淘宝的服务化拆分如下图所示[77]: 在各个应用服务拆分的同时,数据库也按业务功能做了垂直拆分,2008 年 Oracle 被拆分为商品、交易、用户、评价、收藏夹等 10 来套。当时的淘宝的分布式架构设计,包括 Java + Oracle 技术栈的选择,以及应用服务和数据库的拆分策略,很大程度上参考的是 eBay 的架构[75][78]。对照 eBay 架构师在 2006 年 11 月对外分享的 eBay 架构的 slides[66],容易发现两者大同小异。 因为使用 Oracle 成本太高,2008 年 8 月淘宝数据库设计了异构数据库读写分离架构,写库为集中式的 Oracle,读库使用分库分表的 MySQL,该数据库架构方案基于自研的数据库中间件 TDDL 实现[80]。之后的几年,数据库逐渐向 MySQL 迁移。2008 年 9 月前微软亚洲研究院常务副院长王坚博士加盟阿里巴巴集团,担任首席架构师,2009 年 11 月时任阿里 CTO 的王坚博士决策启动阿里“去IOE”工程。淘宝的“去IOE”工程的关键时点[81]: 淘宝技术架构7576777879,是从 LAMP 架构演变而来的。在 2003 年 5 月最早上线时,淘宝是对购买得到的采用 LAMP 架构网站源码进行二次开发的网站。为了应对网站流量的增长,2003 年底数据库从 MySQL 切换到 Oracle,2004 年初编程语言从 PHP 迁移到 Java,同时硬件演变为 IBM 小型机和 EMC 高端硬件存储,依赖的基础设施被统称为“IOE”(IBM 小型机、Oracle 数据库、EMC 存储设备),这个架构被称为 2.0 架构。 2007 年底开始拆分第一个业务中心是用户中心(UIC,User Information Center),在 2008 年初上线。2008 年初,启动“千岛湖”项目,拆分出了交易中心(TC,Trade Center)和类目属性中心(Forest)。2008 年 10 月,为了打通淘宝网和淘宝商城(后来的天猫)的数据,启动“五彩石”项目,拆分出了店铺中心(SC,Shop Center)、商品中心(IC,Item Center)、评价中心(RC,Rate Center)。到 2009 年初,“五彩石”项目结束,阿里电商架构也从之前的单体架构演进为了 SOA 服务化架构,被称为 3.0 架构,同时淘宝网和淘宝商城也完成了数据整合,并淘宝网和淘宝商城的共享业务沉淀到多个业务中心。在 2009 年,共享业务事业部成立,在组织架构上共享业务事业部和淘宝、天猫平级。在业务中心的上层业务服务有,交易管理(TM,Trade Manager)、商品管理(IM,Item Manager)、店铺系统(ShopSystem)、商品详情(Detail)、评价管理(RateManager)等。2009 年淘宝的服务化拆分如下图所示77: 在各个应用服务拆分的同时,数据库也按业务功能做了垂直拆分,2008 年 Oracle 被拆分为商品、交易、用户、评价、收藏夹等 10 来套。当时的淘宝的分布式架构设计,包括 Java + Oracle 技术栈的选择,以及应用服务和数据库的拆分策略,很大程度上参考的是 eBay 的架构7578。对照 eBay 架构师在 2006 年 11 月对外分享的 eBay 架构的 slides66,容易发现两者大同小异。 因为使用 Oracle 成本太高,2008 年 8 月淘宝数据库设计了异构数据库读写分离架构,写库为集中式的 Oracle,读库使用分库分表的 MySQL,该数据库架构方案基于自研的数据库中间件 TDDL 实现80。之后的几年,数据库逐渐向 MySQL 迁移。2008 年 9 月前微软亚洲研究院常务副院长王坚博士加盟阿里巴巴集团,担任首席架构师,2009 年 11 月时任阿里 CTO 的王坚博士决策启动阿里“去IOE”工程。淘宝的“去IOE”工程的关键时点81: 值得注意的是,在 2011 年淘宝商品库和交易库“去IOE”的同时,为了满足数据库的性能要求,底层硬件从 HDD 机械硬盘改为 SSD 固态硬盘。而庆幸的是,当时的前几年正好是 SSD 大爆发的时间点[82]。在消费级 SSD 市场,2010 年苹果的 MacBook Air 开始全面改用 SSD,2012 年苹果的 MacBook Pro 开始全面改用 SSD。在数据库服务器层面,因为 SSD 在随机读写的性能具有巨大优势,2010 年开始机械硬盘改用 SSD 成为趋势,当时的 MySQL 新版代码也专门针对 SSD 做了性能优化[83]。 2009 年初淘宝演变为 SOA 架构后,服务拆分粒度越来越细,到 2009 年底拆分出的总服务数达到 187 个,到 2010 年底达到 329 个[77]。因为系统依赖关系越来越复杂,当时最大问题是稳定性问题,系统的稳定性建设成为重点工作。到 2013 年阿里电商系统开始 4.0 架构改造,即异地多活架构改造,内部称为单元化项目。2013 年 8 月完成杭州同城双活,2014 年双 11 前的 10 月完成杭州和上海近距离两个单元的异地双活,2015 年双 11 完成三地四单元的异地多活[76][79],至此也完成了 3.0 到 4.0 架构的升级。在阿里电商系统迁移到阿里云公共云方面,2015 年阿里电商系统开始采用混合云弹性架构,当阿里本地保有云无法支撑时,就快速在公有云上扩建新的单元,当流量过去后,再还资源给公有云。2015 年 10% 的双 11 大促流量使用了阿里云的机器来支撑,2016 年 50% 以上的双 11 大促流量使用了阿里云的机器来支撑[76]。2019 年初,阿里电商开启了全面上公共云的改造,到 2019 年双 11,阿里电商系统实现了全部核心应用上公共云,2021 年双 11,阿里电商系统实现了 100% 上公共云。 当前阿里电商系统(淘宝、天猫等)的主要技术栈[78][79]: 值得注意的是,在 2011 年淘宝商品库和交易库“去IOE”的同时,为了满足数据库的性能要求,底层硬件从 HDD 机械硬盘改为 SSD 固态硬盘。而庆幸的是,当时的前几年正好是 SSD 大爆发的时间点82。在消费级 SSD 市场,2010 年苹果的 MacBook Air 开始全面改用 SSD,2012 年苹果的 MacBook Pro 开始全面改用 SSD。在数据库服务器层面,因为 SSD 在随机读写的性能具有巨大优势,2010 年开始机械硬盘改用 SSD 成为趋势,当时的 MySQL 新版代码也专门针对 SSD 做了性能优化83。 2009 年初淘宝演变为 SOA 架构后,服务拆分粒度越来越细,到 2009 年底拆分出的总服务数达到 187 个,到 2010 年底达到 329 个77。因为系统依赖关系越来越复杂,当时最大问题是稳定性问题,系统的稳定性建设成为重点工作。到 2013 年阿里电商系统开始 4.0 架构改造,即异地多活架构改造,内部称为单元化项目。2013 年 8 月完成杭州同城双活,2014 年双 11 前的 10 月完成杭州和上海近距离两个单元的异地双活,2015 年双 11 完成三地四单元的异地多活7679,至此也完成了 3.0 到 4.0 架构的升级。在阿里电商系统迁移到阿里云公共云方面,2015 年阿里电商系统开始采用混合云弹性架构,当阿里本地保有云无法支撑时,就快速在公有云上扩建新的单元,当流量过去后,再还资源给公有云。2015 年 10% 的双 11 大促流量使用了阿里云的机器来支撑,2016 年 50% 以上的双 11 大促流量使用了阿里云的机器来支撑76。2019 年初,阿里电商开启了全面上公共云的改造,到 2019 年双 11,阿里电商系统实现了全部核心应用上公共云,2021 年双 11,阿里电商系统实现了 100% 上公共云。 2016 年,阿里产品专家倪超对外介绍的阿里电商技术架构,如下图所示[86]: 类似的,阿里巴巴中间件首席架构师《企业IT架构转型之道》(豆瓣)作者钟华在 2017 年对外介绍的阿里电商技术架构图[87]: 2017 年,阿里中间件技术部专家谢吉宝对外介绍的阿里中间件技术大图[79]: 2016 年,阿里产品专家倪超对外介绍的阿里电商技术架构,如下图所示86: 类似的,阿里巴巴中间件首席架构师《企业IT架构转型之道》(豆瓣)作者钟华在 2017 年对外介绍的阿里电商技术架构图87: 2017 年,阿里中间件技术部专家谢吉宝对外介绍的阿里中间件技术大图79: 阿里 HSF 与 Dubbo 的历史演进时间线: 阿里 RocketMQ 的历史演进时间线: LinkedIn,2003 年创立,采用纯 Java 技术栈,早期是单体架构,到 2008 年演化为 SOA 架构[96]。到 2010 年拆分的服务数超过 150 个,到 2015 年拆分的服务数超过 750 个[97]。 2008 年,LinkedIn 对外介绍的技术架构如下图所示[96]: 2012 年,LinkedIn 对外介绍的关注在数据基础设施上的技术架构图[98]: LinkedIn,2003 年创立,采用纯 Java 技术栈,早期是单体架构,到 2008 年演化为 SOA 架构96。到 2010 年拆分的服务数超过 150 个,到 2015 年拆分的服务数超过 750 个97。 2008 年,LinkedIn 对外介绍的技术架构如下图所示96: 2012 年,LinkedIn 对外介绍的关注在数据基础设施上的技术架构图98: Facebook,2004 年创立,早期采用 LAMP 技术栈,为了应对负载增长开始服务化,将核心业务从 LAMP 中移出到新的服务,新的服务改用使用 C++、Java 等编写。为了解决 PHP 进程与非 PHP 进程的 RPC 通信问题,2006 年内部研发了跨语言的 RPC 库 Thrift,2007.04 对外开源。PHP 代码用于实现前端展示层的 Web 动态页面渲染,以及对服务层的数据聚合。 2010 年,Facebook 工程师 Aditya Agarwal 对外介绍的 Facebook 技术架构如下图所示[101]: Facebook 的技术栈图[102]: Facebook 的 NewsFeed 服务的架构图[102]: Facebook 的 Search 服务的架构图[102]: 当前 Facebook 的主要技术栈[101][102][103]: Facebook,2004 年创立,早期采用 LAMP 技术栈,为了应对负载增长开始服务化,将核心业务从 LAMP 中移出到新的服务,新的服务改用使用 C++、Java 等编写。为了解决 PHP 进程与非 PHP 进程的 RPC 通信问题,2006 年内部研发了跨语言的 RPC 库 Thrift,2007.04 对外开源。PHP 代码用于实现前端展示层的 Web 动态页面渲染,以及对服务层的数据聚合。 2010 年,Facebook 工程师 Aditya Agarwal 对外介绍的 Facebook 技术架构如下图所示101: Facebook 的技术栈图102: Facebook 的 NewsFeed 服务的架构图102: Facebook 的 Search 服务的架构图102: Twitter,2006 年创立,早期采用 Ruby on Rails 技术栈,数据库是 MySQL,2009 年开始服务化,将核心业务从 Ruby on Rails 中移出到新的服务,新的服务用 Scala、Java 编写[109][110]。 2013 年,Twitter 工程师总结的 Twitter 从单体架构向分布式架构演进的过程,如下图所示[110]: Twitter,2006 年创立,早期采用 Ruby on Rails 技术栈,数据库是 MySQL,2009 年开始服务化,将核心业务从 Ruby on Rails 中移出到新的服务,新的服务用 Scala、Java 编写109110。 2013 年,Twitter 工程师总结的 Twitter 从单体架构向分布式架构演进的过程,如下图所示110: 2007 Jeff Dean: Software Engineering Advice from Building Large-Scale Distributed Systems (Stanford CS295 class lecture, Spring, 2007) https://research.google.com/people/jeff/stanford-295-talk.pdf ↩ 2008-11 Google Architecture http://highscalability.com/blog/2008/11/22/google-architecture.html ↩ 2008-06 新浪夏清然、徐继哲:自由软件和新浪网. 程序员 2008年第6期54-55 http://lib.cqvip.com/Qikan/Article/Detail?id=27523653 http://www.zeuux.com/blog/content/3620/ ↩ 2016-01 微信张文瑞:从无到有:微信后台系统的演进之路 https://www.infoq.cn/article/the-road-of-the-growth-weixin-background ↩ 2008-02 Yandex Architecture http://highscalability.com/yandex-architecture ↩ 2007-08 Wikimedia architecture http://highscalability.com/wikimedia-architecture ↩ 2007-11 Flickr Architecture http://highscalability.com/flickr-architecture ↩ 2016-03 What does Etsy's architecture look like today? http://highscalability.com/blog/2016/3/23/what-does-etsys-architecture-look-like-today.html ↩ 2011-05 新浪刘晓震:新浪博客应用架构分享 (PHPChina 2011) https://web.archive.org/web/0/http://www.phpchina.com/?action-viewnews-itemid-38418 https://www.modb.pro/doc/121035 ↩ 2013-11 百度张东进:百度PHP基础设施构建思路 (QCon上海2013, slides, 30p) https://www.modb.pro/doc/121042 ↩ 2013-05 The Tumblr Architecture Yahoo Bought for a Cool Billion Dollars http://highscalability.com/blog/2013/5/20/the-tumblr-architecture-yahoo-bought-for-a-cool-billion-doll.html ↩ 2017-06 B站任伟:B站高性能微服务架构 https://zhuanlan.zhihu.com/p/33247332 ↩ ↩ 2021-05 微博刘道儒:十年三次重大架构升级,微博应对“极端热点”的进阶之路 https://www.infoq.cn/article/qgwbh0wz5bvw9apjos2a ↩ 2012-08 腾讯张松国:腾讯微博架构介绍 (ArchSummit深圳2012, slides, 59p) https://www.slideshare.net/dleyanlin/08-13994311 ↩ 2016-02 美团夏华夏:从技术细节看美团架构 (ArchSummit北京2014) https://www.infoq.cn/article/see-meituan-architecture-from-technical-details http://bj2014.archsummit.com/node/596/ https://www.modb.pro/doc/8311 ↩ 2019-06 滴滴杜欢:大型微服务框架设计实践 (Gopher China 2019) https://www.infoq.cn/article/EfOlY8_rubh4LfoXzF8B https://www.modb.pro/doc/35485 ↩ ↩ 2018-08 E-Commerce at Scale: Inside Shopify's Tech Stack - Stackshare.io https://shopify.engineering/e-commerce-at-scale-inside-shopifys-tech-stack ↩ 2013-03 Phil Calçado @ SoundCloud: From a monolithic Ruby on Rails app to the JVM (slides, 75p) https://www.slideshare.net/pcalcado/from-a-monolithic-ruby-on-rails-app-to-the-jvm ↩ 2012-07 Go at SoundCloud https://developers.soundcloud.com/blog/go-at-soundcloud ↩ ↩ 2014-04 Peter Bourgon @ SoundCloud: Go: Best Practices for Production Environments (GopherCon 2014) http://peter.bourgon.org/go-in-production/ ↩ ↩ 2009-04 Heroku - Simultaneously Develop and Deploy Automatically Scalable Rails Applications in the Cloud http://highscalability.com/blog/2009/4/24/heroku-simultaneously-develop-and-deploy-automatically-scala.html ↩ 2021-07 GitHub’s Journey from Monolith to Microservices https://www.infoq.com/articles/github-monolith-microservices/ ↩ 2017-12 Building Services at Airbnb, Part 1 https://medium.com/airbnb-engineering/c4c1d8fa811b ↩ 2020-11 Jessica Tai @ Airbnb: How to Tame Your Service APIs: Evolving Airbnb’s Architecture https://www.infoq.com/presentations/airbnb-api-architecture/ ↩ 2013-08 Reddit: Lessons Learned from Mistakes Made Scaling to 1 Billion Pageviews a Month http://highscalability.com/blog/2013/8/26/reddit-lessons-learned-from-mistakes-made-scaling-to-1-billi.html ↩ ↩ 2012-03 7 Years of YouTube Scalability Lessons in 30 Minutes http://highscalability.com/blog/2012/3/26/7-years-of-youtube-scalability-lessons-in-30-minutes.html ↩ 2016-04 豆瓣田忠博:豆瓣的服务化体系改造 (QCon北京2016, slides, 37p) http://2016.qconbeijing.com/presentation/2834/ ↩ 2010-09 Disqus: Scaling the World's Largest Django App (DjangoCon 2010, slides) https://www.slideshare.net/zeeg/djangocon-2010-scaling-disqus ↩ 2021-05 Dropbox: Atlas: Our journey from a Python monolith to a managed platform https://dropbox.tech/infrastructure/atlas--our-journey-from-a-python-monolith-to-a-managed-platform ↩ 2014-07 Dropbox: Open Sourcing Our Go Libraries https://dropbox.tech/infrastructure/open-sourcing-our-go-libraries ↩ ↩ 2017-07 Tammy Butow @ Dropbox: Go Reliability and Durability at Dropbox https://about.sourcegraph.com/blog/go/go-reliability-and-durability-at-dropbox-tammy-butow ↩ ↩ 2011-02 Adam D'Angelo: Why did Quora choose C++ over C for its high performance services? Does the Quora codebase use a limited subset of C++? https://qr.ae/pKwCE3 ↩ 2012-02 A Short on the Pinterest Stack for Handling 3+ Million Users http://highscalability.com/blog/2012/2/16/a-short-on-the-pinterest-stack-for-handling-3-million-users.html ↩ 2015-08 Finagle and Java Service Framework at Pinterest (slides) https://www.slideshare.net/yongshengwu/finaglecon2015pinterest ↩ 2013-03 Instagram 5位传奇工程师背后的技术揭秘 https://web.archive.org/web/0/http://www.csdn.net/article/2013-03-28/2814698-The-technologie- behind-Instagram ↩ ↩ 2017-03 Lisa Guo: Scaling Instagram Infrastructure(QCon London 2017, 87p) https://www.infoq.com/presentations/instagram-scale-infrastructure/ ↩ ↩ 2019-08 Static Analysis at Scale: An Instagram Story https://instagram-engineering.com/8f498ab71a0c ↩ ↩ 2016-07 The Uber Engineering Tech Stack, Part I: The Foundation https://web.archive.org/web/0/https://eng.uber.com/tech-stack-part-one/ ↩ ↩ 2018-11 知乎社区核心业务 Golang 化实践 https://zhuanlan.zhihu.com/p/48039838 ↩ ↩ 2022-10 字节马春辉:字节大规模微服务语言发展之路 https://www.infoq.cn/article/n5hkjwfx1gxklkh8iham ↩ ↩ 2021-07 字节成国柱:字节跳动微服务架构体系演进 https://mp.weixin.qq.com/s/1dgCQXpeufgMTMq_32YKuQ ↩ ↩ Why does Google use Java instead of Python for Gmail? https://qr.ae/pKkduC ↩ 2011-07 揭秘Google+技术架构 http://www.infoq.com/cn/news/2011/07/Google-Plus ↩ 2009-12 人人网架构师张洁:人人网使用的开源软件列表 https://web.archive.org/web/0/http://ugc.renren.com/2009/12/13/a-list-of-open-source-software-in-renren ↩ 2018-12 Netflix OSS and Spring Boot — Coming Full Circle https://netflixtechblog.com/4855947713a0 ↩ 携程为什么突然技术转型从 .NET 转 Java? https://www.zhihu.com/question/56259195 ↩ Golang Frequently Asked Questions: Why did you create a new language? https://go.dev/doc/faq#creating_a_new_language ↩ 2014-05 Brad Fitzpatrick: Go: 90% Perfect, 100% of the time: Fun vs. Fast https://go.dev/talks/2014/gocon-tokyo.slide#28 ↩ 2021-08 百度Geek说:短视频go研发框架实践 https://my.oschina.net/u/4939618/blog/5191598 ↩ 2023-07 百度Geek说:从 php5.6 到 golang1.19 - 文库 App 性能跃迁之路 https://my.oschina.net/u/4939618/blog/10086661 ↩ 2022-03 2021年,腾讯研发人员增长41%,Go首次超越C++成为最热门语言 https://mp.weixin.qq.com/s/zj-DhASG4S-3z56GTYjisg ↩ 2020-05 Which Major Companies Use PostgreSQL? What Do They Use It for? https://learnsql.com/blog/companies-that-use-postgresql-in-business/ ↩ 2009-05 Why is MySQL more popular than PostgreSQL? https://news.ycombinator.com/item?id=619871 ↩ Why is MySQL more popular than PostgreSQL? https://qr.ae/pKPJcE ↩ 2002-10 Michael Radwin: Making the Case for PHP at Yahoo! (PHPCon 2002, slides) https://web.archive.org/web/0/http://www.php-con.com/2002/view/session.php?s=1012 https://speakerdeck.com/yulewei/making-the-case-for-php-at-yahoo ↩ ↩ 2005-10 Michael Radwin: PHP at Yahoo! (PHP Conference 2005, slides) https://speakerdeck.com/yulewei/php-at-yahoo-zend2005 ↩ ↩ 2007-06 Federico Feroldi: PHP in Yahoo! (slides) https://www.slideshare.net/fullo/federico-feroldi-php-in-yahoo ↩ 2021-02 AWS re:Invent 2020: Amazon.com’s architecture evolution and AWS strategy https://www.youtube.com/watch?v=HtWKZSLLYTE ↩ ↩ What programming languages are used at Amazon? https://qr.ae/pKFwnw ↩ 2011-04 Charlie Cheever: How did Google, Amazon, and the like initially develop and code their websites, databases, etc.? https://qr.ae/pKKyB0 ↩ 2016-04 Is Amazon still using Perl Mason to render its content? https://qr.ae/pKFwFm ↩ 2019-10 Migration Complete – Amazon’s Consumer Business Just Turned off its Final Oracle Database https://aws.amazon.com/blogs/aws/migration-complete-amazons-consumer-business-just-turned-off-its-final-oracle-database/?nc1=h_ls ↩ Amazon RDS for PostgreSQL customers https://aws.amazon.com/rds/postgresql/customers/?nc1=h_ls ↩ Amazon ElastiCache for Redis customers https://aws.amazon.com/elasticache/redis/customers/?nc1=h_ls ↩ 2006-11 Randy Shoup & Dan Pritchett: The eBay Architecture (SDForum2006, slides, 37p) http://www.addsimplicity.com/downloads/eBaySDForum2006-11-29.pdf ↩ ↩ ↩ 2007-05 eBay Architecture http://highscalability.com/blog/2008/5/27/ebay-architecture.html ↩ ↩ 2011-10 Tony Ng: eBay Architecture (slides, 46p) https://www.slideshare.net/tcng3716/ebay-architecture ↩ 2016-11 Ron Murphy: Microservices at eBay (slides, 20p) https://www.modb.pro/doc/120378 ↩ ↩ ↩ 2020-09 High Efficiency Tool Platform for Framework Migration https://innovation.ebayinc.com/tech/engineering/high-efficiency-tool-platform-for-framework-migration/ ↩ 2023-10 BES2:打造eBay下一代高可靠消息中间件 https://mp.weixin.qq.com/s/ThhkO1WM7ck1WO8RqjTCJA ↩ 2013-06 Cassandra at eBay - Cassandra Summit 2013 (slides) https://www.slideshare.net/jaykumarpatel/cassandra-at-ebay-cassandra-summit-2013 ↩ 2017-03 Sudeep Kumar: 'Elasticsearch as a Service' at eBay https://www.elastic.co/elasticon/conf/2017/sf/elasticsearch-as-a-service-at-ebay ↩ 2016-08 How eBay Uses Apache Software to Reach Its Big Data Goals https://www.linux.com/news/how-ebay-uses-apache-software-reach-its-big-data-goals/ ↩ 2011-06 淘宝吴泽明范禹:淘宝业务发展及技术架构 (slides, 43p) https://www.modb.pro/doc/116697 ↩ ↩ ↩ 2014-12 阿里云王宇德、张文生:淘宝迁云架构实践 https://web.archive.org/web/1/http://www.csdn.net/article/a/2014-12-09/15821474 ↩ ↩ ↩ 2017-07 阿里谢吉宝唐三:阿里电商架构演变之路 (首届阿里巴巴中间件技术峰会, slides, 33p) https://www.modb.pro/doc/121185 ↩ ↩ ↩ ↩ 2010-11 淘宝赵林丹臣:淘宝数据库架构演进历程 (iDataForum2010, slides, 38p) https://www.slideshare.net/ssuser1646de/ss-10163048 ↩ 2019-10 阿里刘振飞:十年磨一剑:从2009启动“去IOE”工程到2019年OceanBase拿下TPC-C世界第一 https://mp.weixin.qq.com/s/7B6rp17XVhpAWZr1-6DHqQ https://developer.aliyun.com/article/722414 ↩ 2016-02 SSD的30年发展史 https://mp.weixin.qq.com/s/JsHKFilB5fvLY9V9z_xsXw ↩ 2010-04 Yoshinori Matsunobu: SSD Deployment Strategies for MySQL (slides, 52p) https://www.slideshare.net/matsunobu/ssd-deployment-strategies-for-mysql ↩ 2020-04 阿里王剑英、和利:淘宝万亿级交易订单背后的存储引擎 https://mp.weixin.qq.com/s/MkX1Pr8tERrzK29XG9zMUQ https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/storage-engine-that-processes-trillions-of-taobao-order-records ↩ 2022-03 阿里云罗庆超:阿里巴巴集团上云之 TFS 迁移 OSS 技术白皮书 https://developer.aliyun.com/article/876301 ↩ 2016-12 阿里倪超:阿里巴巴Aliware十年微服务架构演进历程中的挑战与实践 https://www.sohu.com/a/121588928_465959 ↩ 2017-10 阿里钟华古谦:企业IT架构转型之道 (杭州云栖大会2017, slides, 18p) https://www.modb.pro/doc/121695 ↩ 2021-09 阿里郭浩:Dubbo 和 HSF 在阿里巴巴的实践:携手走向下一代云原生微服务 https://mp.weixin.qq.com/s/_Ug3yEh9gz5mLE_ag1DjwA ↩ ↩ ↩ 2012-11 阿里巴巴分布式服务框架 Dubbo 团队成员梁飞专访 http://www.iteye.com/magazines/103 ↩ 2019-01 Dubbo 作者梁飞亲述:那些辉煌、沉寂与重生的故事 https://www.infoq.cn/article/3F3Giujjo-QwSw2wEz7u ↩ ↩ Dubbo 用户案例:阿里巴巴升级 Dubbo3 全面取代 HSF2 https://cn.dubbo.apache.org/zh-cn/users/alibaba/ https://github.com/apache/dubbo-website/blob/ee727525aa61d68b397cc6ddedb322001f0ca4da/content/zh/users/alibaba.md ↩ 2023-01 阿里巴巴升级 Dubbo3 全面取代 HSF2 https://cn.dubbo.apache.org/zh-cn/blog/2023/01/16/阿里巴巴升级-dubbo3-全面取代-hsf2/ ↩ 2017-11 阿里林清山隆基:阿里消息中间件架构演进之路:notify和metaq https://zhuanlan.zhihu.com/p/302600352 ↩ 2013-07 淘宝张乐伟韩彰:淘宝消息中间件技术演变:MetaQ 1.0、MetaQ 2.0、MetaQ 3.0(sildes, 30p)https://www.modb.pro/doc/109298 ↩ 2017-03 阿里冯嘉鼬神:Apache RocketMQ背后的设计思路与最佳实践 https://developer.aliyun.com/article/71889 ↩ 2008-05 LinkedIn - A Professional Network built with Java Technologies and Agile Practices (JavaOne2008, slides) https://www.slideshare.net/linkedin/linkedins-communication-architecture ↩ ↩ ↩ 2015-07 Josh Clemm: A Brief History of Scaling LinkedIn https://engineering.linkedin.com/architecture/brief-history-scaling-linkedin https://www.slideshare.net/joshclemm/how-linkedin-scaled-a-brief-history ↩ ↩ Aditya Auradkar, Chavdar Botev, etc.: Data Infrastructure at LinkedIn. ICDE 2012: 1370-1381,dblp, semanticscholar, slides ↩ ↩ ↩ 2012-06 Sid Anand: LinkedIn Data Infrastructure Slides (Version 2) (slides) https://www.slideshare.net/r39132/linkedin-data-infrastructure-slides-version-2-13394853 ↩ ↩ 2021-12 Evolving LinkedIn’s analytics tech stack https://engineering.linkedin.com/blog/2021/evolving-linkedin-s-analytics-tech-stack ↩ ↩ 2010-05 Aditya Agarwal: Scale at Facebook (Qcon London 2010) https://www.infoq.com/presentations/Scale-at-Facebook/ ↩ ↩ 2008-11 Aditya Agarwal: Facebook architecture (QCon SF 2008, slides, 34p) https://www.slideshare.net/mysqlops/facebook-architecture https://www.infoq.com/presentations/Facebook-Software-Stack/ ↩ ↩ ↩ ↩ 2011-04 Michaël Figuière: What is Facebook's architecture? https://qr.ae/pKYg12 ↩ 2013-10 Where does Facebook use C++? https://qr.ae/pKCIee ↩ 2022-07 Programming languages endorsed for server-side use at Meta https://engineering.fb.com/2022/07/27/developer-tools/programming-languages-endorsed-for-server-side-use-at-meta/ ↩ 2015-07 Instagram: Search Architecture https://instagram-engineering.com/eeb34a936d3a ↩ Guoqiang Jerry Chen, Janet L. Wiener, etc.: Realtime Data Processing at Facebook. SIGMOD Conference 2016: 1087-1098,dblp, semanticscholar ↩ 2021-10 XStream: stream processing platform at facebook (Flink Forward Global 2021, slides) https://www.slideshare.net/aniketmokashi/xstream-stream-processing-platform-at-facebook ↩ 2013-01 Johan Oskarsson: The Twitter stack https://blog.oskarsson.nu/post/40196324612/the-twitter-stack ↩ ↩ 2013-10 Chris Aniszczyk: Evolution of The Twitter Stack (slides) https://www.slideshare.net/caniszczyk/twitter-opensourcestacklinuxcon2013 ↩ ↩ ↩ 2017-01 The Infrastructure Behind Twitter: Scale https://blog.twitter.com/engineering/en_us/topics/infrastructure/2017/the-infrastructure-behind-twitter-scale ↩ 2010-07 Cassandra at Twitter Today https://blog.twitter.com/engineering/en_us/a/2010/cassandra-at-twitter-today ↩ 2011-12 How Twitter Stores 250 Million Tweets a Day Using MySQL http://highscalability.com/blog/2011/12/19/how-twitter-stores-250-million-tweets-a-day-using-mysql.html ↩ 2013-11 Michael Busch: Search at Twitter (Lucene Revolution 2013, slides) https://www.slideshare.net/lucenerevolution/twitter-search-lucenerevolutioneu2013-copy https://www.youtube.com/watch?v=AguWva8P_DI ↩ ↩ 2022-10 Stability and scalability for search https://blog.twitter.com/engineering/en_us/topics/infrastructure/2022/stability-and-scalability-for-search ↩ 2021-10 Processing billions of events in real time at Twitter https://blog.twitter.com/engineering/en_us/topics/infrastructure/2021/processing-billions-of-events-in-real-time-at-twitter- ↩MySQL源码架构
- 4:40
+ 4:41
diff --git a/2016/11/java-books/index.html b/2016/11/java-books/index.html
index 758adc53..a59a54f0 100644
--- a/2016/11/java-books/index.html
+++ b/2016/11/java-books/index.html
@@ -982,7 +982,7 @@ 偏向Java EE
- 4:40
+ 4:41
diff --git a/2016/11/java-official-doc/index.html b/2016/11/java-official-doc/index.html
index dbfcbfba..b6831ec9 100644
--- a/2016/11/java-official-doc/index.html
+++ b/2016/11/java-official-doc/index.html
@@ -984,7 +984,7 @@ Java虚拟机
- 4:40
+ 4:41
diff --git a/2016/11/lang-and-compiler-books/index.html b/2016/11/lang-and-compiler-books/index.html
index c48543ae..b9841519 100644
--- a/2016/11/lang-and-compiler-books/index.html
+++ b/2016/11/lang-and-compiler-books/index.html
@@ -923,7 +923,7 @@ 编程语言与编译器书籍
- 4:40
+ 4:41
diff --git a/2016/12/itext-pdf/index.html b/2016/12/itext-pdf/index.html
index 8f9026e1..065ea589 100644
--- a/2016/12/itext-pdf/index.html
+++ b/2016/12/itext-pdf/index.html
@@ -904,7 +904,7 @@
iText处理pdf书签和标注代
- 4:40
+ 4:41
diff --git a/2016/12/java-8-stream-api/index.html b/2016/12/java-8-stream-api/index.html
index b6520795..ed2e2321 100644
--- a/2016/12/java-8-stream-api/index.html
+++ b/2016/12/java-8-stream-api/index.html
@@ -996,7 +996,7 @@
归约操作与收集器
- 4:40
+ 4:41
diff --git a/2016/12/mybatis-generator/index.html b/2016/12/mybatis-generator/index.html
index b268d2f1..f4e276a0 100644
--- a/2016/12/mybatis-generator/index.html
+++ b/2016/12/mybatis-generator/index.html
@@ -939,7 +939,7 @@ 运行MyBatis生成器
- 4:40
+ 4:41
diff --git a/2016/12/software-dev-books/index.html b/2016/12/software-dev-books/index.html
index c755a119..1d572a8f 100644
--- a/2016/12/software-dev-books/index.html
+++ b/2016/12/software-dev-books/index.html
@@ -1040,7 +1040,7 @@ 软件项目管理(Fred Br
- 4:40
+ 4:41
diff --git a/2017/01/mysql-data-types/index.html b/2017/01/mysql-data-types/index.html
index 0af2d15d..50e722c0 100644
--- a/2017/01/mysql-data-types/index.html
+++ b/2017/01/mysql-data-types/index.html
@@ -1102,7 +1102,7 @@
参考资料
- 4:40
+ 4:41
diff --git a/2017/03/java-executor/index.html b/2017/03/java-executor/index.html
index 26f7d5df..df2faa1d 100644
--- a/2017/03/java-executor/index.html
+++ b/2017/03/java-executor/index.html
@@ -1064,7 +1064,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2017/04/javac-api/index.html b/2017/04/javac-api/index.html
index cdc41e89..3d67101c 100644
--- a/2017/04/javac-api/index.html
+++ b/2017/04/javac-api/index.html
@@ -1169,7 +1169,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2017/05/java-method-parameter/index.html b/2017/05/java-method-parameter/index.html
index d53e3cc3..b2b595e4 100644
--- a/2017/05/java-method-parameter/index.html
+++ b/2017/05/java-method-parameter/index.html
@@ -996,7 +996,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2017/11/zookeeper-note/index.html b/2017/11/zookeeper-note/index.html
index d5f1ac6e..35b46fcf 100644
--- a/2017/11/zookeeper-note/index.html
+++ b/2017/11/zookeeper-note/index.html
@@ -1160,7 +1160,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2018/01/java-ffi/index.html b/2018/01/java-ffi/index.html
index 65469d90..7551ff74 100644
--- a/2018/01/java-ffi/index.html
+++ b/2018/01/java-ffi/index.html
@@ -1019,7 +1019,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2018/01/stack-frame-calling-convention/index.html b/2018/01/stack-frame-calling-convention/index.html
index 9702ea71..f61836a1 100644
--- a/2018/01/stack-frame-calling-convention/index.html
+++ b/2018/01/stack-frame-calling-convention/index.html
@@ -1004,7 +1004,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2018/06/mysql-binlog/index.html b/2018/06/mysql-binlog/index.html
index 1663f062..f6a325c5 100644
--- a/2018/06/mysql-binlog/index.html
+++ b/2018/06/mysql-binlog/index.html
@@ -976,7 +976,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2018/10/java-agent/index.html b/2018/10/java-agent/index.html
index f90ea025..ac7a6726 100644
--- a/2018/10/java-agent/index.html
+++ b/2018/10/java-agent/index.html
@@ -1008,7 +1008,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2019/01/elastic-stack/index.html b/2019/01/elastic-stack/index.html
index 946bf8cf..1415b61c 100644
--- a/2019/01/elastic-stack/index.html
+++ b/2019/01/elastic-stack/index.html
@@ -1108,7 +1108,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2019/06/mysql-5.7-json/index.html b/2019/06/mysql-5.7-json/index.html
index 964984a2..16762468 100644
--- a/2019/06/mysql-5.7-json/index.html
+++ b/2019/06/mysql-5.7-json/index.html
@@ -1283,7 +1283,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2020/05/kong-gateway/index.html b/2020/05/kong-gateway/index.html
index ef27d141..78e69848 100644
--- a/2020/05/kong-gateway/index.html
+++ b/2020/05/kong-gateway/index.html
@@ -1069,7 +1069,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2023/07/innodb-locking/index.html b/2023/07/innodb-locking/index.html
index ffdd86a9..a98a1629 100644
--- a/2023/07/innodb-locking/index.html
+++ b/2023/07/innodb-locking/index.html
@@ -1613,7 +1613,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2023/07/io-multiplexing-network-server/index.html b/2023/07/io-multiplexing-network-server/index.html
index 0357501e..c7cd9625 100644
--- a/2023/07/io-multiplexing-network-server/index.html
+++ b/2023/07/io-multiplexing-network-server/index.html
@@ -1212,7 +1212,7 @@ 参考资料
- 4:40
+ 4:41
diff --git a/2023/12/popular-websites-tech-stack/index.html b/2023/12/popular-websites-tech-stack/index.html
index 76623bad..de87d1e4 100644
--- a/2023/12/popular-websites-tech-stack/index.html
+++ b/2023/12/popular-websites-tech-stack/index.html
@@ -506,7 +506,7 @@ 流行互联网网站技术栈
100k
+ 98k
@@ -520,7 +520,7 @@
- 流行互联网网站技术栈
1:31
+ 1:29
@@ -543,9 +543,7 @@
- 流行互联网网站技术栈
案例汇总与解析
-技术发展时间线
-案例汇总与解析
技术发展时间线
-技术发展时间线
技术发展时间线
技术栈案例汇总
-技术栈案例汇总
-
-
-
-
-
+
-
-
编程语言选择
-编程语言选择
数据库选择
-技术栈和架构演进
-数据库选择
技术栈和架构演进
-Yahoo!(1994)
-Yahoo!(1994)
Amazon(1994)
-
-
Amazon(1994)
+
+
-
-
+
+
-
+
-
+
eBay(1995)
-eBay(1995)
+
+
-
+
+
+
-
淘宝(2003)
-淘宝(2003)
-淘宝(2003)
-
+
+
+
+
-
+
+
+
-
+
+
+
-
淘宝(2003)
-
+
-LinkedIn(2003)
-
-
LinkedIn(2003)
+
+
+
-
-
+
+
+
-
+
+
LinkedIn(2003)
Facebook(2004)
-
-
Facebook(2004)
+
+
-
+
+
Facebook(2004)
+
+
+
-
Twitter(2006)
-
-
Twitter(2006)
+
+
-
+
+
-
-
+
参考资料
-
-
-
-参考资料
+1. 2007 Jeff Dean: Software Engineering Advice from Building Large-Scale Distributed Systems (Stanford CS295 class lecture, Spring, 2007) https://research.google.com/people/jeff/stanford-295-talk.pdf ↩
+
+
+2. 2008-11 Google Architecture http://highscalability.com/blog/2008/11/22/google-architecture.html ↩
+
+
+3. 2008-06 新浪夏清然、徐继哲:自由软件和新浪网. 程序员 2008年第6期54-55 http://lib.cqvip.com/Qikan/Article/Detail?id=27523653 http://www.zeuux.com/blog/content/3620/ ↩
+
+
+4. 2016-01 微信张文瑞:从无到有:微信后台系统的演进之路 https://www.infoq.cn/article/the-road-of-the-growth-weixin-background ↩
+
+
+5. 2008-02 Yandex Architecture http://highscalability.com/yandex-architecture ↩
+
+
+6. 2007-08 Wikimedia architecture http://highscalability.com/wikimedia-architecture ↩
+
+
+7. 2007-11 Flickr Architecture http://highscalability.com/flickr-architecture ↩
+
+
+8. 2016-03 What does Etsy’s architecture look like today? http://highscalability.com/blog/2016/3/23/what-does-etsys-architecture-look-like-today.html ↩
+
+
+9. 2011-05 新浪刘晓震:新浪博客应用架构分享 (PHPChina 2011) https://web.archive.org/web/0/http://www.phpchina.com/?action-viewnews-itemid-38418 https://www.modb.pro/doc/121035 ↩
+
+
+10. 2013-11 百度张东进:百度PHP基础设施构建思路 (QCon上海2013, slides, 30p) https://www.modb.pro/doc/121042 ↩
+
+
+11. 2013-05 The Tumblr Architecture Yahoo Bought for a Cool Billion Dollars http://highscalability.com/blog/2013/5/20/the-tumblr-architecture-yahoo-bought-for-a-cool-billion-doll.html ↩
+
+
+12. 2017-06 B站任伟:B站高性能微服务架构 https://zhuanlan.zhihu.com/p/33247332 ↩
+
+
+13. 2021-05 微博刘道儒:十年三次重大架构升级,微博应对“极端热点”的进阶之路 https://www.infoq.cn/article/qgwbh0wz5bvw9apjos2a ↩
+
+
+14. 2012-08 腾讯张松国:腾讯微博架构介绍 (ArchSummit深圳2012, slides, 59p) https://www.slideshare.net/dleyanlin/08-13994311 ↩
+
+
+15. 2016-02 美团夏华夏:从技术细节看美团架构 (ArchSummit北京2014) https://www.infoq.cn/article/see-meituan-architecture-from-technical-details http://bj2014.archsummit.com/node/596/ https://www.modb.pro/doc/8311 ↩
+
+
+16. 2019-06 滴滴杜欢:大型微服务框架设计实践 (Gopher China 2019) https://www.infoq.cn/article/EfOlY8_rubh4LfoXzF8B https://www.modb.pro/doc/35485 ↩
+
+
+17. 2018-08 E-Commerce at Scale: Inside Shopify’s Tech Stack - Stackshare.io https://shopify.engineering/e-commerce-at-scale-inside-shopifys-tech-stack ↩
+
+
+18. 2013-03 Phil Calçado @ SoundCloud: From a monolithic Ruby on Rails app to the JVM (slides, 75p) https://www.slideshare.net/pcalcado/from-a-monolithic-ruby-on-rails-app-to-the-jvm ↩
+
+
+19. 2012-07 Go at SoundCloud https://developers.soundcloud.com/blog/go-at-soundcloud ↩
+
+
+20. 2014-04 Peter Bourgon @ SoundCloud: Go: Best Practices for Production Environments (GopherCon 2014) http://peter.bourgon.org/go-in-production/ ↩
+
+
+21. 2009-04 Heroku - Simultaneously Develop and Deploy Automatically Scalable Rails Applications in the Cloud http://highscalability.com/blog/2009/4/24/heroku-simultaneously-develop-and-deploy-automatically-scala.html ↩
+
+
+22. 2021-07 GitHub’s Journey from Monolith to Microservices https://www.infoq.com/articles/github-monolith-microservices/ ↩
+
+
+23. 2017-12 Building Services at Airbnb, Part 1 https://medium.com/airbnb-engineering/c4c1d8fa811b ↩
+
+
+24. 2020-11 Jessica Tai @ Airbnb: How to Tame Your Service APIs: Evolving Airbnb’s Architecture https://www.infoq.com/presentations/airbnb-api-architecture/ ↩
+
+
+25. 2013-08 Reddit: Lessons Learned from Mistakes Made Scaling to 1 Billion Pageviews a Month http://highscalability.com/blog/2013/8/26/reddit-lessons-learned-from-mistakes-made-scaling-to-1-billi.html ↩
+
+
+26. 2012-03 7 Years of YouTube Scalability Lessons in 30 Minutes http://highscalability.com/blog/2012/3/26/7-years-of-youtube-scalability-lessons-in-30-minutes.html ↩
+
+
+27. 2016-04 豆瓣田忠博:豆瓣的服务化体系改造 (QCon北京2016, slides, 37p) http://2016.qconbeijing.com/presentation/2834/ ↩
+
+
+28. 2010-09 Disqus: Scaling the World’s Largest Django App (DjangoCon 2010, slides) https://www.slideshare.net/zeeg/djangocon-2010-scaling-disqus ↩
+
+
+29. 2021-05 Dropbox: Atlas: Our journey from a Python monolith to a managed platform https://dropbox.tech/infrastructure/atlas--our-journey-from-a-python-monolith-to-a-managed-platform ↩
+
+
+30. 2014-07 Dropbox: Open Sourcing Our Go Libraries https://dropbox.tech/infrastructure/open-sourcing-our-go-libraries ↩
+
+
+31. 2017-07 Tammy Butow @ Dropbox: Go Reliability and Durability at Dropbox https://about.sourcegraph.com/blog/go/go-reliability-and-durability-at-dropbox-tammy-butow ↩
+
+
+32. 2011-02 Adam D’Angelo: Why did Quora choose C++ over C for its high performance services? Does the Quora codebase use a limited subset of C++? https://qr.ae/pKwCE3 ↩
+
+
+33. 2012-02 A Short on the Pinterest Stack for Handling 3+ Million Users http://highscalability.com/blog/2012/2/16/a-short-on-the-pinterest-stack-for-handling-3-million-users.html ↩
+
+
+34. 2015-08 Finagle and Java Service Framework at Pinterest (slides) https://www.slideshare.net/yongshengwu/finaglecon2015pinterest ↩
+
+
+35. 2013-03 Instagram 5位传奇工程师背后的技术揭秘 https://web.archive.org/web/0/http://www.csdn.net/article/2013-03-28/2814698-The-technologie-%20behind-Instagram ↩
+
+
+36. 2017-03 Lisa Guo: Scaling Instagram Infrastructure(QCon London 2017, 87p) https://www.infoq.com/presentations/instagram-scale-infrastructure/ ↩
+
+
+37. 2019-08 Static Analysis at Scale: An Instagram Story https://instagram-engineering.com/8f498ab71a0c ↩
+
+
+38. 2016-07 The Uber Engineering Tech Stack, Part I: The Foundation https://web.archive.org/web/0/https://eng.uber.com/tech-stack-part-one/ ↩
+
+
+39. 2018-11 知乎社区核心业务 Golang 化实践 https://zhuanlan.zhihu.com/p/48039838 ↩
+
+
+40. 2022-10 字节马春辉:字节大规模微服务语言发展之路 https://www.infoq.cn/article/n5hkjwfx1gxklkh8iham ↩
+
+
+41. 2021-07 字节成国柱:字节跳动微服务架构体系演进 https://mp.weixin.qq.com/s/1dgCQXpeufgMTMq_32YKuQ ↩
+
+
+42. Why does Google use Java instead of Python for Gmail? https://qr.ae/pKkduC ↩
+
+
+43. 2011-07 揭秘Google+技术架构 http://www.infoq.com/cn/news/2011/07/Google-Plus ↩
+
+
+44. 2009-12 人人网架构师张洁:人人网使用的开源软件列表 https://web.archive.org/web/0/http://ugc.renren.com/2009/12/13/a-list-of-open-source-software-in-renren ↩
+
+
+45. 2018-12 Netflix OSS and Spring Boot — Coming Full Circle https://netflixtechblog.com/4855947713a0 ↩
+
+
+46. 携程为什么突然技术转型从 .NET 转 Java? https://www.zhihu.com/question/56259195 ↩
+
+
+47. 京东技术解密,2014,豆瓣:12 少年派的奇幻漂流——从.Net到Java ↩
+
+
+48. Golang Frequently Asked Questions: Why did you create a new language? https://go.dev/doc/faq#creating_a_new_language ↩
+
+
+49. 2014-05 Brad Fitzpatrick: Go: 90% Perfect, 100% of the time: Fun vs. Fast https://go.dev/talks/2014/gocon-tokyo.slide#28 ↩
+
+
+50. 2021-08 百度Geek说:短视频go研发框架实践 https://my.oschina.net/u/4939618/blog/5191598 ↩
+
+
+51. 2023-07 百度Geek说:从 php5.6 到 golang1.19 - 文库 App 性能跃迁之路 https://my.oschina.net/u/4939618/blog/10086661 ↩
+
+
+52. 2022-03 2021年,腾讯研发人员增长41%,Go首次超越C++成为最热门语言 https://mp.weixin.qq.com/s/zj-DhASG4S-3z56GTYjisg ↩
+
+
+53. 2020-05 Which Major Companies Use PostgreSQL? What Do They Use It for? https://learnsql.com/blog/companies-that-use-postgresql-in-business/ ↩
+
+
+54. 2009-05 Why is MySQL more popular than PostgreSQL? https://news.ycombinator.com/item?id=619871 ↩
+
+
+55. Why is MySQL more popular than PostgreSQL? https://qr.ae/pKPJcE ↩
+
+
+56. 2002-10 Michael Radwin: Making the Case for PHP at Yahoo! (PHPCon 2002, slides) https://web.archive.org/web/0/http://www.php-con.com/2002/view/session.php?s=1012 https://speakerdeck.com/yulewei/making-the-case-for-php-at-yahoo ↩
+
+
+57. 2005-10 Michael Radwin: PHP at Yahoo! (PHP Conference 2005, slides) https://speakerdeck.com/yulewei/php-at-yahoo-zend2005 ↩
+
+
+58. 2007-06 Federico Feroldi: PHP in Yahoo! (slides) https://www.slideshare.net/fullo/federico-feroldi-php-in-yahoo ↩
+
+
+59. 2021-02 AWS re:Invent 2020: Amazon.com’s architecture evolution and AWS strategy https://www.youtube.com/watch?v=HtWKZSLLYTE ↩
+
+
+60. What programming languages are used at Amazon? https://qr.ae/pKFwnw ↩
+
+
+61. 2011-04 Charlie Cheever: How did Google, Amazon, and the like initially develop and code their websites, databases, etc.? https://qr.ae/pKKyB0 ↩
+
+
+62. 2016-04 Is Amazon still using Perl Mason to render its content? https://qr.ae/pKFwFm ↩
+
+
+63. 2019-10 Migration Complete – Amazon’s Consumer Business Just Turned off its Final Oracle Database https://aws.amazon.com/blogs/aws/migration-complete-amazons-consumer-business-just-turned-off-its-final-oracle-database/?nc1=h_ls ↩
+
+
+64. Amazon RDS for PostgreSQL customers https://aws.amazon.com/rds/postgresql/customers/?nc1=h_ls ↩
+
+
+65. Amazon ElastiCache for Redis customers https://aws.amazon.com/elasticache/redis/customers/?nc1=h_ls ↩
+
+
+66. 2006-11 Randy Shoup & Dan Pritchett: The eBay Architecture (SDForum2006, slides, 37p) http://www.addsimplicity.com/downloads/eBaySDForum2006-11-29.pdf ↩
+
+
+67. 2007-05 eBay Architecture http://highscalability.com/blog/2008/5/27/ebay-architecture.html ↩
+
+
+68. 2011-10 Tony Ng: eBay Architecture (slides, 46p) https://www.slideshare.net/tcng3716/ebay-architecture ↩
+
+
+69. 2016-11 Ron Murphy: Microservices at eBay (slides, 20p) https://www.modb.pro/doc/120378 ↩
+
+
+70. 2020-09 High Efficiency Tool Platform for Framework Migration https://innovation.ebayinc.com/tech/engineering/high-efficiency-tool-platform-for-framework-migration/ ↩
+
+
+71. 2023-10 BES2:打造eBay下一代高可靠消息中间件 https://mp.weixin.qq.com/s/ThhkO1WM7ck1WO8RqjTCJA ↩
+
+
+72. 2013-06 Cassandra at eBay - Cassandra Summit 2013 (slides) https://www.slideshare.net/jaykumarpatel/cassandra-at-ebay-cassandra-summit-2013 ↩
+
+
+73. 2017-03 Sudeep Kumar: ‘Elasticsearch as a Service’ at eBay https://www.elastic.co/elasticon/conf/2017/sf/elasticsearch-as-a-service-at-ebay ↩
+
+
+74. 2016-08 How eBay Uses Apache Software to Reach Its Big Data Goals https://www.linux.com/news/how-ebay-uses-apache-software-reach-its-big-data-goals/ ↩
+
+
+75. 淘宝技术这十年,子柳,2013,豆瓣 ↩
+
+
+76. 尽在双11:阿里巴巴技术演进与超越,2017,豆瓣:第1章 阿里技术架构演进 ↩
+
+
+77. 2011-06 淘宝吴泽明范禹:淘宝业务发展及技术架构 (slides, 43p) https://www.modb.pro/doc/116697 ↩
+
+
+78. 2014-12 阿里云王宇德、张文生:淘宝迁云架构实践 https://web.archive.org/web/1/http://www.csdn.net/article/a/2014-12-09/15821474 ↩
+
+
+79. 2017-07 阿里谢吉宝唐三:阿里电商架构演变之路 (首届阿里巴巴中间件技术峰会, slides, 33p) https://www.modb.pro/doc/121185 ↩
+
+
+80. 2010-11 淘宝赵林丹臣:淘宝数据库架构演进历程 (iDataForum2010, slides, 38p) https://www.slideshare.net/ssuser1646de/ss-10163048 ↩
+
+
+81. 2019-10 阿里刘振飞:十年磨一剑:从2009启动“去IOE”工程到2019年OceanBase拿下TPC-C世界第一 https://mp.weixin.qq.com/s/7B6rp17XVhpAWZr1-6DHqQ https://developer.aliyun.com/article/722414 ↩
+
+
+82. 2016-02 SSD的30年发展史 https://mp.weixin.qq.com/s/JsHKFilB5fvLY9V9z_xsXw ↩
+
+
+83. 2010-04 Yoshinori Matsunobu: SSD Deployment Strategies for MySQL (slides, 52p) https://www.slideshare.net/matsunobu/ssd-deployment-strategies-for-mysql ↩
+
+
+84. 2020-04 阿里王剑英、和利:淘宝万亿级交易订单背后的存储引擎 https://mp.weixin.qq.com/s/MkX1Pr8tERrzK29XG9zMUQ https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/storage-engine-that-processes-trillions-of-taobao-order-records ↩
+
+
+85. 2022-03 阿里云罗庆超:阿里巴巴集团上云之 TFS 迁移 OSS 技术白皮书 https://developer.aliyun.com/article/876301 ↩
+
+
+86. 2016-12 阿里倪超:阿里巴巴Aliware十年微服务架构演进历程中的挑战与实践 https://www.sohu.com/a/121588928_465959 ↩
+
+
+87. 2017-10 阿里钟华古谦:企业IT架构转型之道 (杭州云栖大会2017, slides, 18p) https://www.modb.pro/doc/121695 ↩
+
+
+88. 2021-09 阿里郭浩:Dubbo 和 HSF 在阿里巴巴的实践:携手走向下一代云原生微服务 https://mp.weixin.qq.com/s/_Ug3yEh9gz5mLE_ag1DjwA ↩
+
+
+89. 2012-11 阿里巴巴分布式服务框架 Dubbo 团队成员梁飞专访 http://www.iteye.com/magazines/103 ↩
+
+
+90. 2019-01 Dubbo 作者梁飞亲述:那些辉煌、沉寂与重生的故事 https://www.infoq.cn/article/3F3Giujjo-QwSw2wEz7u ↩
+
+
+91. Dubbo 用户案例:阿里巴巴升级 Dubbo3 全面取代 HSF2 https://cn.dubbo.apache.org/zh-cn/users/alibaba/ https://github.com/apache/dubbo-website/blob/ee727525aa61d68b397cc6ddedb322001f0ca4da/content/zh/users/alibaba.md ↩
+
+
+92. 2023-01 阿里巴巴升级 Dubbo3 全面取代 HSF2 https://cn.dubbo.apache.org/zh-cn/blog/2023/01/16/%e9%98%bf%e9%87%8c%e5%b7%b4%e5%b7%b4%e5%8d%87%e7%ba%a7-dubbo3-%e5%85%a8%e9%9d%a2%e5%8f%96%e4%bb%a3-hsf2/ ↩
+
+
+93. 2017-11 阿里林清山隆基:阿里消息中间件架构演进之路:notify和metaq https://zhuanlan.zhihu.com/p/302600352 ↩
+
+
+94. 2013-07 淘宝张乐伟韩彰:淘宝消息中间件技术演变:MetaQ 1.0、MetaQ 2.0、MetaQ 3.0(sildes, 30p)https://www.modb.pro/doc/109298 ↩
+
+
+95. 2017-03 阿里冯嘉鼬神:Apache RocketMQ背后的设计思路与最佳实践 https://developer.aliyun.com/article/71889 ↩
+
+
+96. 2008-05 LinkedIn - A Professional Network built with Java Technologies and Agile Practices (JavaOne2008, slides) https://www.slideshare.net/linkedin/linkedins-communication-architecture ↩
+
+
+97. 2015-07 Josh Clemm: A Brief History of Scaling LinkedIn https://engineering.linkedin.com/architecture/brief-history-scaling-linkedin https://www.slideshare.net/joshclemm/how-linkedin-scaled-a-brief-history ↩
+
+
+98. Aditya Auradkar, Chavdar Botev, etc.: Data Infrastructure at LinkedIn. ICDE 2012: 1370-1381,dblp, semanticscholar, slides ↩
+
+
+99. 2012-06 Sid Anand: LinkedIn Data Infrastructure Slides (Version 2) (slides) https://www.slideshare.net/r39132/linkedin-data-infrastructure-slides-version-2-13394853 ↩
+
+
+100. 2021-12 Evolving LinkedIn’s analytics tech stack https://engineering.linkedin.com/blog/2021/evolving-linkedin-s-analytics-tech-stack ↩
+
+
+101. 2010-05 Aditya Agarwal: Scale at Facebook (Qcon London 2010) https://www.infoq.com/presentations/Scale-at-Facebook/ ↩
+
+
+102. 2008-11 Aditya Agarwal: Facebook architecture (QCon SF 2008, slides, 34p) https://www.slideshare.net/mysqlops/facebook-architecture https://www.infoq.com/presentations/Facebook-Software-Stack/ ↩
+
+
+103. 2011-04 Michaël Figuière: What is Facebook’s architecture? https://qr.ae/pKYg12 ↩
+
+
+104. 2013-10 Where does Facebook use C++? https://qr.ae/pKCIee ↩
+
+
+105. 2022-07 Programming languages endorsed for server-side use at Meta https://engineering.fb.com/2022/07/27/developer-tools/programming-languages-endorsed-for-server-side-use-at-meta/ ↩
+
+
+106. 2015-07 Instagram: Search Architecture https://instagram-engineering.com/eeb34a936d3a ↩
+
+
+107. Guoqiang Jerry Chen, Janet L. Wiener, etc.: Realtime Data Processing at Facebook. SIGMOD Conference 2016: 1087-1098,dblp, semanticscholar ↩
+
+
+108. 2021-10 XStream: stream processing platform at facebook (Flink Forward Global 2021, slides) https://www.slideshare.net/aniketmokashi/xstream-stream-processing-platform-at-facebook ↩
+
+
+109. 2013-01 Johan Oskarsson: The Twitter stack https://blog.oskarsson.nu/post/40196324612/the-twitter-stack ↩
+
+
+110. 2013-10 Chris Aniszczyk: Evolution of The Twitter Stack (slides) https://www.slideshare.net/caniszczyk/twitter-opensourcestacklinuxcon2013 ↩
+
+
+111. 2017-01 The Infrastructure Behind Twitter: Scale https://blog.twitter.com/engineering/en_us/topics/infrastructure/2017/the-infrastructure-behind-twitter-scale ↩
+
+
+112. 2010-07 Cassandra at Twitter Today https://blog.twitter.com/engineering/en_us/a/2010/cassandra-at-twitter-today ↩
+
+
+113. 2011-12 How Twitter Stores 250 Million Tweets a Day Using MySQL http://highscalability.com/blog/2011/12/19/how-twitter-stores-250-million-tweets-a-day-using-mysql.html ↩
+
+
+114. 2013-11 Michael Busch: Search at Twitter (Lucene Revolution 2013, slides) https://www.slideshare.net/lucenerevolution/twitter-search-lucenerevolutioneu2013-copy https://www.youtube.com/watch?v=AguWva8P_DI ↩
+
+
+115. 2022-10 Stability and scalability for search https://blog.twitter.com/engineering/en_us/topics/infrastructure/2022/stability-and-scalability-for-search ↩
+
+
+116. 2021-10 Processing billions of events in real time at Twitter https://blog.twitter.com/engineering/en_us/topics/infrastructure/2021/processing-billions-of-events-in-real-time-at-twitter- ↩
+
@@ -1613,7 +1654,7 @@ 参考资料
- 参考资料
- 4:40
+ 4:41
diff --git a/about/index.html b/about/index.html
index 7b77fd37..2b3d6e57 100644
--- a/about/index.html
+++ b/about/index.html
@@ -727,7 +727,7 @@ about
- 4:40
+ 4:41
diff --git a/archives/2016/11/index.html b/archives/2016/11/index.html
index b477f95d..51633f27 100644
--- a/archives/2016/11/index.html
+++ b/archives/2016/11/index.html
@@ -837,7 +837,7 @@
- 4:40
+ 4:41
diff --git a/archives/2016/12/index.html b/archives/2016/12/index.html
index 3c43ff4b..a96bc088 100644
--- a/archives/2016/12/index.html
+++ b/archives/2016/12/index.html
@@ -837,7 +837,7 @@
- 4:40
+ 4:41
diff --git a/archives/2016/index.html b/archives/2016/index.html
index da0360c9..3cbdae6a 100644
--- a/archives/2016/index.html
+++ b/archives/2016/index.html
@@ -977,7 +977,7 @@
- 4:40
+ 4:41
diff --git a/archives/2017/01/index.html b/archives/2017/01/index.html
index 4e18c55c..1f38a889 100644
--- a/archives/2017/01/index.html
+++ b/archives/2017/01/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2017/03/index.html b/archives/2017/03/index.html
index c4801fd2..7f238224 100644
--- a/archives/2017/03/index.html
+++ b/archives/2017/03/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2017/04/index.html b/archives/2017/04/index.html
index 6384278a..cf15d211 100644
--- a/archives/2017/04/index.html
+++ b/archives/2017/04/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2017/05/index.html b/archives/2017/05/index.html
index 3b97b751..5a78ea98 100644
--- a/archives/2017/05/index.html
+++ b/archives/2017/05/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2017/11/index.html b/archives/2017/11/index.html
index 999d9543..592501ec 100644
--- a/archives/2017/11/index.html
+++ b/archives/2017/11/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2017/index.html b/archives/2017/index.html
index 6f207db2..4e2d4721 100644
--- a/archives/2017/index.html
+++ b/archives/2017/index.html
@@ -872,7 +872,7 @@
- 4:40
+ 4:41
diff --git a/archives/2018/01/index.html b/archives/2018/01/index.html
index 147fffff..3e981de7 100644
--- a/archives/2018/01/index.html
+++ b/archives/2018/01/index.html
@@ -767,7 +767,7 @@
- 4:40
+ 4:41
diff --git a/archives/2018/06/index.html b/archives/2018/06/index.html
index 586aa785..9a394a07 100644
--- a/archives/2018/06/index.html
+++ b/archives/2018/06/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2018/10/index.html b/archives/2018/10/index.html
index d7cf3866..bba1f96c 100644
--- a/archives/2018/10/index.html
+++ b/archives/2018/10/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2018/index.html b/archives/2018/index.html
index d3166967..960aa7f9 100644
--- a/archives/2018/index.html
+++ b/archives/2018/index.html
@@ -837,7 +837,7 @@
- 4:40
+ 4:41
diff --git a/archives/2019/01/index.html b/archives/2019/01/index.html
index 4b7c94f5..c8c63805 100644
--- a/archives/2019/01/index.html
+++ b/archives/2019/01/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2019/06/index.html b/archives/2019/06/index.html
index 9e96cfd8..d1371140 100644
--- a/archives/2019/06/index.html
+++ b/archives/2019/06/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2019/index.html b/archives/2019/index.html
index fd12cd77..293e458c 100644
--- a/archives/2019/index.html
+++ b/archives/2019/index.html
@@ -767,7 +767,7 @@
- 4:40
+ 4:41
diff --git a/archives/2020/05/index.html b/archives/2020/05/index.html
index c5717b09..b5f96a4f 100644
--- a/archives/2020/05/index.html
+++ b/archives/2020/05/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2020/index.html b/archives/2020/index.html
index d15663bc..419b87b5 100644
--- a/archives/2020/index.html
+++ b/archives/2020/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2023/07/index.html b/archives/2023/07/index.html
index 3816b539..28053e52 100644
--- a/archives/2023/07/index.html
+++ b/archives/2023/07/index.html
@@ -767,7 +767,7 @@
- 4:40
+ 4:41
diff --git a/archives/2023/12/index.html b/archives/2023/12/index.html
index e349cc13..2e2a415c 100644
--- a/archives/2023/12/index.html
+++ b/archives/2023/12/index.html
@@ -732,7 +732,7 @@
- 4:40
+ 4:41
diff --git a/archives/2023/index.html b/archives/2023/index.html
index 3bf822eb..6aca4e69 100644
--- a/archives/2023/index.html
+++ b/archives/2023/index.html
@@ -802,7 +802,7 @@
- 4:40
+ 4:41
diff --git a/archives/index.html b/archives/index.html
index 4c80c989..c409f9b8 100644
--- a/archives/index.html
+++ b/archives/index.html
@@ -1426,7 +1426,7 @@
- 4:40
+ 4:41
diff --git a/archives/page/2/index.html b/archives/page/2/index.html
index 9a8bf496..c76e53ec 100644
--- a/archives/page/2/index.html
+++ b/archives/page/2/index.html
@@ -806,7 +806,7 @@
- 4:40
+ 4:41
diff --git a/atom.xml b/atom.xml
index 89c55cca..636845af 100644
--- a/atom.xml
+++ b/atom.xml
@@ -23,7 +23,7 @@
在“开源”(open source)一词出现之前,技术社区的黑客选择使用“自由软件”(free software)这个词。但是“自由软件”这个词与对知识产权的敌意、共产主义和其它观点相联系,几乎不受管理者和投资者的欢迎,于是 1998 年 2 月 3 日在由 Eric Raymond 等人参加的会议上“开源”一词诞生,2 月下旬开源软件促进会成立 OSI,Eric Raymond 担任主席。自由软件和开源软件被合称为 FOSS。缩写“LAMP”代表的是 Linux-Apache-MySQL-PHP,这些软件都是自由软件或开源软件。
在互联网诞生早期,开源技术栈、开源社区尚未成熟,互联网公司不得不自研专有软件,随着开源软件的成熟,技术栈的选择开始从专有软件逐渐转向开源软件。主要 Web 技术和服务端技术的发展时间线:
按编程语言区别,主要流行的互联网产品的创建时间和早期的技术栈选择(也可以参见 wiki):
值得一提的是,PHP 之父 Rasmus Lerdorf,在 2002 年至 2009 年期间为供职于 Yahoo!,2011 年起至今供职于 Etsy。Python 之父 Guido van Rossum,在 2005 年至 2012 年期间供职于 Google,在 2013 年至 2019 年期间供职于 Dropbox。国内最有影响力的 PHP 技术专家是惠新宸,是加入 PHP 语言官方开发组的首位国人,曾先后供职于雅虎中国、百度、新浪微博、链家网等公司。
2000 年前创建的网站,因为开源技术栈尚未成熟,编程语言基本上都是选择 C++ 开发。在开源技术栈成熟后,多数网站会选择拥抱开源。具体选择哪个编程语言,PHP、Ruby、Python、Java 等,主要由技术负责人的技术偏好决定。根据 w3techs 统计,历年网站使用的服务端编程语言统计占比,2012 年至今 10 多年,PHP 占比稳居榜首,每年都是 75% 以上。大型网站都是公司内的技术团队研发的,技术栈由技术团队选择,而很多小型网站,很可能会直接使用开源的 CMS 系统搭建。根据 w3techs 统计,前 100 万网站中有 43.0% 使用 WordPress 构建。WordPress 的服务端编程语言就是 PHP,数据库是 MySQL。大型网站选择 PHP 越来越少,因为 PHP 是解释型脚本语言,相对编译型编程语言有性能劣势。另外,PHP 的优势是快速开发 Web 动态网页,但是随着前后端分离开发模式的流行,Web 页面从服务端渲染逐渐转向客户端渲染,PHP 的优势不再,后端工程师只需要向前端提供 REST API 接口,展示层的实现完全由前端工程师实现。
早期部分网站选择 .NET 技术栈,比如京东、携程。但是,Java 平台生态更完善,有非常多的经验可以借鉴。另外,.NET 平台本身虽然不收费,但是 Windows 操作系统是收费的,开发工具也不便宜。于是京东在 2012 年从 .NET 迁移到 Java,携程在 2017 左右从 .NET 迁移到 Java。目前在国内,多数互联网大厂都选择 Java 技术栈,如淘宝、美团、京东、微博、携程等,Java 相对来说是主流选择。
编程语言的另外一个流行趋势是 Go 语言。Go 语言诞生于 Google,在 2009 年 11 月对外公开。在发明 Go 语言前,Google 内部主要使用的语言是 C++、Java 和 Python 等,但是 Go 发明者认为这些语言无法同时满足高效编译、高效执行和易于编程的特性诉求,所以创造了 Go 语言[48]。Go 比 C++ 能更高效编译和易于编程,比 Java 更易于编程,比 Python 能更高效执行。在 GoCon Tokyo 2014 会议上,Go 语言研发团队的 Brad Fitzpatrick 对各种编程语言在编程乐趣和执行速度(Fun vs. Fast)的对比[49],如下图所示:
早期由 Go 语言实现的经典开源项目主要是基础设施软件,比如 YouTube 的 Vitess 数据库分片中间件(2012.02 对外开源)、Docker 容器(2013.03 对外开源)、Red Hat 的 CoreOS 团队的 Etcd 分布式配置服务(2013.07 对外开源,实现 Raft 共识协议)、前 Google 工程师创建的 CockroachDB 分布式数据库(2014.02 对外开源,受 Google Spanner 启发)、Google 的 Kubernetes 容器编排系统(2014.06 对外开源)、SoundCloud 的 Prometheus 监控工具(2015.01 对外开源)等。大规模使用 Go 语言的代表性的国外的互联网公司有 SoundCloud[19][20]、Dropbox[30][31]、Uber[38] 等,国内的有七牛云、字节跳动[40][41]、哔哩哔哩[12]、滴滴[16]、知乎[39]、百度[50][51]、腾讯[52]等,在这些互联网公司 Go 语言通常主要被用于构建基础设施软件(网关、数据存储、监控、视频处理等)或高性能要求的业务服务,部分公司的大量核心业务也从 Python、PHP 等迁移到 Go 语言。使用 Go 语言的公司的更加完整的列表,可以参见官方整理的“GoUsers”。
数据库方面,根据 DB-Engines Ranking 的统计,流行的关系数据库主要是 4 个,开源免费的 MySQL 和 PostgreSQL,专有收费的 Oracle 和 Microsoft SQL Server。多数互联网产品使用的数据库是 MySQL,部分是 PostgreSQL 或 Oracle。一些早期使用 Oracle 数据库的网站选择部分或完全从 Oracle 数据库中迁出,代表性的案例是,Amazon 从 Oracle 完全迁移到 Amazon RDS 和 NoSQL,淘宝从 Oracle 完全迁移到 MySQL。以 PostgreSQL 为主数据库的互联网应用有 Skype、Reddit、Spotify、Disqus、Heroku、Instagram 等[53]。
MySQL 相对 PostgreSQL 更加流行的主要原因是[54][55],在早期 MySQL 入门槛更低。MySQL 就支持在 Windows 下安装,而 PostgreSQL 只能在 Cygwin 下安装,MySQL 对 Windows 平台的支持使得初学者更容易上手,并且 MySQL 更加易于管理,能够快速简单地启动和使用,而且 MySQL 拥有一个非常简洁、易于导航和用户友好的在线文档和支持社区。另外一个重要原因是,MySQL 很早就默认支持复制(replication),而 PostgreSQL 的复制是第三方的,而且极其难用。之后的几年即便 PostgreSQL 完善了不足,但错过了流行的时间窗口,MySQL 成为了主流选择,有更完善的生态。不过,受 MySQL 被 Oracle 公司收购的影响,近几年 PostgreSQL 开始越来越流行,根据 DB-Engines Ranking 的统计,最新的 PostgreSQL 的流行度分数是 10 年前的三倍,流行度与前三名越来越接近。
容易发现多数网站的技术栈的演进模式类似。在创建网站早期通常选择使用 PHP、Ruby、Python 脚本语言和框架来开发,这些脚本语言和框架具有更快的开发效率,能快速交付上线。随着网站流量的增长,面临性能和可扩展性问题,通常会将网站服务化,从单体架构向面向服务的 SOA 和微服务架构演进,一大部分网站在服务化过程中会选择将核心业务改由其他性能更佳的编译型编程语言来实现,如 Java、Scala、C++、Go,原先的脚本语言可能全部废弃,也可能仅用于前端展示层的 Web 动态页面渲染,比如 Facebook、Twitter 等。当然也有一部分网站,演进为 SOA 和微服务架构后,业务逻辑的实现一直以最初的脚本语言为主。
在架构微服务化后,很多网站实现的微服务之间采用基于 HTTP 协议的 REST 方式通信(或者使用的 RPC 框架支持跨语言 RPC 调用),各个微服务的实现在理论上编程语言不需要统一,可以根据该微服务的性能要求或负责该微服务的团队的技术偏好自由选择,所以真实世界的互联网公司内部各个业务子系统的技术栈可能不统一。但是内部技术栈不统一会带来额外的维护成本。另外,考虑技术栈的迁移重构成本,新的业务子系统使用新的技术栈,而有些旧的业务子系统很可能继续使用旧的技术栈。除了业务子系统外,很可能团队内部有自研的中间件、运维工具等,这些组件由中间件团队、运维团队研发,很可能会选择与业务系统不一样的技术栈。
注意,解决网站的可扩展性问题,不一定需要演进为服务化架构。架构服务化意味着将完整的单体服务按业务的功能领域做垂直拆分,而实际上在应用服务层可以通过部署多个相同副本的单体服务的方式实现系统的水平扩展,网站的可扩展性问题主要在数据存储层上。拆分应用服务的好处在于能实现组织团队的规模化。解决数据库的扩展性的策略有,数据复制(数据缓存、数据库读写分离)、数据垂直拆分、数据水平拆分(也叫数据分片,sharding)。数据库被拆分后,如果单个事务内的数据分散在多个节点就要解决分布式事务问题,但是实现分布式事务代价太大,通常的选择是牺牲一致性,仅满足最终一致性(BASE)。
没有拆分应用服务,始终采用单体架构的经典案例是 Instagram,2019 年 Instagram 在技术博客上有这样一段话[37]:
Our server app is a monolith, one big codebase of several million lines and a few thousand Django endpoints, all loaded up and served together. A few services have been split out of the monolith, but we don’t have any plans to aggressively break it up.
为了解决可扩展性问题,Instagram 在数据存储[35][36],对 PostgreSQL 数据库做了主从读写分离、数据垂直拆分和数据分片,并且使用了 NoSQL 数据库 Cassandra,用于存储 Feed 流等数据。类似的,Reddit 也是单体架构,在数据存储层做可扩展性改造,对 PostgreSQL 数据库做了垂直拆分,并且使用 Cassandra 数据库[25]。
Yahoo!,1994 年创立,早期网站操作系统是 FreeBSD,Web 服务器是自研的 Filo Server,数据库是自研的 DBM 文件,Web 动态页面脚本是自研的 yScript,业务逻辑编程语言是 C++。1996 年 Web 服务器改用 Apache HTTP Server,1999 年部分数据库改用 MySQL(同时也使用 Oracle 数据库),2002 年编程语言从 yScript 和 C++ 改为 PHP,操作系统也从 FreeBSD 逐渐转向 Linux[56][57][58]。在 2002.10 的 PHPCon 2002 会议上,Yahoo! 工程师 Michael Radwin 介绍了 Yahoo! 从专有软件向开源软件转向的原因和演进过程,以及选择 PHP 而不选择 ASP、Perl、JSP 等其他技术的原因[56]。Yahoo! 转向开源软件的原因主要是,避免维护专有软件的成本,开源软件从早期的不成熟最终变得成熟,具有更好的性能,更容易与第三方软件集成,以及开源社区逐渐发展壮大。2005 年,Yahoo! 的技术架构如下图所示[57]:
2000 年 ~ 2010 年,Yahoo! 是世界上流量最大的网站,虽然期间被 Google 短暂超越,但随后又反超,一直到到 2010 年后才被 Google 彻底超越。2000 年前后 Yahoo! 转向到 MySQL 和 PHP,并在技术上助力 LAMP 生态的发展壮大,对 LAMP 技术栈的流行起到巨大的推动作用。值得一提的是,知名 MySQL 专家 Jeremy Zawodny,《高性能MySQL》2004 年第 1 版(豆瓣)的作者,在 1999.12 ~ 2008.06 期间为 Yahoo! 工程师;PHP 之父 Rasmus Lerdorf 在 2002.09 ~ 2009.11 期间为 Yahoo! 工程师。
Amazon,1994 年创立,早期是单服务、单数据库的单体架构,使用的编程语言是 C++,2000 年开始从单体架构向 SOA 和微服务架构演进,使用最多的语言变为 Java,C++ 被用在高性能要求的系统和底层基础设施组件。Amazon 架构演进过程如下图所示[59]:
Amazon.com 的技术栈演进过程:
当前 Amazon.com 网站的主要技术栈[59][60][61][62]:
eBay,1995 年创立,早期使用的编程语言是 Perl,1997 年迁移到 C++,2002 年迁移到 Java。eBay 的技术栈演进过程[66][67]:
2016 年,eBay 的技术架构如下图所示[69]:
当前 eBay 的主要技术栈[66][67][69][70]:
淘宝技术架构[75][76][77][78][79],是从 LAMP 架构演变而来的。在 2003 年 5 月最早上线时,淘宝是对购买得到的采用 LAMP 架构网站源码进行二次开发的网站。为了应对网站流量的增长,2003 年底数据库从 MySQL 切换到 Oracle,2004 年初编程语言从 PHP 迁移到 Java,同时硬件演变为 IBM 小型机和 EMC 高端硬件存储,依赖的基础设施被统称为“IOE”(IBM 小型机、Oracle 数据库、EMC 存储设备),这个架构被称为 2.0 架构。
2007 年底开始拆分第一个业务中心是用户中心(UIC,User Information Center),在 2008 年初上线。2008 年初,启动“千岛湖”项目,拆分出了交易中心(TC,Trade Center)和类目属性中心(Forest)。2008 年 10 月,为了打通淘宝网和淘宝商城(后来的天猫)的数据,启动“五彩石”项目,拆分出了店铺中心(SC,Shop Center)、商品中心(IC,Item Center)、评价中心(RC,Rate Center)。到 2009 年初,“五彩石”项目结束,阿里电商架构也从之前的单体架构演进为了 SOA 服务化架构,被称为 3.0 架构,同时淘宝网和淘宝商城也完成了数据整合,并淘宝网和淘宝商城的共享业务沉淀到多个业务中心。在 2009 年,共享业务事业部成立,在组织架构上共享业务事业部和淘宝、天猫平级。在业务中心的上层业务服务有,交易管理(TM,Trade Manager)、商品管理(IM,Item Manager)、店铺系统(ShopSystem)、商品详情(Detail)、评价管理(RateManager)等。2009 年淘宝的服务化拆分如下图所示[77]:
在各个应用服务拆分的同时,数据库也按业务功能做了垂直拆分,2008 年 Oracle 被拆分为商品、交易、用户、评价、收藏夹等 10 来套。当时的淘宝的分布式架构设计,包括 Java + Oracle 技术栈的选择,以及应用服务和数据库的拆分策略,很大程度上参考的是 eBay 的架构[75][78]。对照 eBay 架构师在 2006 年 11 月对外分享的 eBay 架构的 slides[66],容易发现两者大同小异。
因为使用 Oracle 成本太高,2008 年 8 月淘宝数据库设计了异构数据库读写分离架构,写库为集中式的 Oracle,读库使用分库分表的 MySQL,该数据库架构方案基于自研的数据库中间件 TDDL 实现[80]。之后的几年,数据库逐渐向 MySQL 迁移。2008 年 9 月前微软亚洲研究院常务副院长王坚博士加盟阿里巴巴集团,担任首席架构师,2009 年 11 月时任阿里 CTO 的王坚博士决策启动阿里“去IOE”工程。淘宝的“去IOE”工程的关键时点[81]:
值得注意的是,在 2011 年淘宝商品库和交易库“去IOE”的同时,为了满足数据库的性能要求,底层硬件从 HDD 机械硬盘改为 SSD 固态硬盘。而庆幸的是,当时的前几年正好是 SSD 大爆发的时间点[82]。在消费级 SSD 市场,2010 年苹果的 MacBook Air 开始全面改用 SSD,2012 年苹果的 MacBook Pro 开始全面改用 SSD。在数据库服务器层面,因为 SSD 在随机读写的性能具有巨大优势,2010 年开始机械硬盘改用 SSD 成为趋势,当时的 MySQL 新版代码也专门针对 SSD 做了性能优化[83]。
2009 年初淘宝演变为 SOA 架构后,服务拆分粒度越来越细,到 2009 年底拆分出的总服务数达到 187 个,到 2010 年底达到 329 个[77]。因为系统依赖关系越来越复杂,当时最大问题是稳定性问题,系统的稳定性建设成为重点工作。到 2013 年阿里电商系统开始 4.0 架构改造,即异地多活架构改造,内部称为单元化项目。2013 年 8 月完成杭州同城双活,2014 年双 11 前的 10 月完成杭州和上海近距离两个单元的异地双活,2015 年双 11 完成三地四单元的异地多活[76][79],至此也完成了 3.0 到 4.0 架构的升级。在阿里电商系统迁移到阿里云公共云方面,2015 年阿里电商系统开始采用混合云弹性架构,当阿里本地保有云无法支撑时,就快速在公有云上扩建新的单元,当流量过去后,再还资源给公有云。2015 年 10% 的双 11 大促流量使用了阿里云的机器来支撑,2016 年 50% 以上的双 11 大促流量使用了阿里云的机器来支撑[76]。2019 年初,阿里电商开启了全面上公共云的改造,到 2019 年双 11,阿里电商系统实现了全部核心应用上公共云,2021 年双 11,阿里电商系统实现了 100% 上公共云。
当前阿里电商系统(淘宝、天猫等)的主要技术栈[78][79]:
2016 年,阿里产品专家倪超对外介绍的阿里电商技术架构,如下图所示[86]:
类似的,阿里巴巴中间件首席架构师《企业IT架构转型之道》(豆瓣)作者钟华在 2017 年对外介绍的阿里电商技术架构图[87]:
2017 年,阿里中间件技术部专家谢吉宝对外介绍的阿里中间件技术大图[79]:
阿里 HSF 与 Dubbo 的历史演进时间线:
阿里 RocketMQ 的历史演进时间线:
LinkedIn,2003 年创立,采用纯 Java 技术栈,早期是单体架构,到 2008 年演化为 SOA 架构[96]。到 2010 年拆分的服务数超过 150 个,到 2015 年拆分的服务数超过 750 个[97]。
2008 年,LinkedIn 对外介绍的技术架构如下图所示[96]:
2012 年,LinkedIn 对外介绍的关注在数据基础设施上的技术架构图[98]:
Facebook,2004 年创立,早期采用 LAMP 技术栈,为了应对负载增长开始服务化,将核心业务从 LAMP 中移出到新的服务,新的服务改用使用 C++、Java 等编写。为了解决 PHP 进程与非 PHP 进程的 RPC 通信问题,2006 年内部研发了跨语言的 RPC 库 Thrift,2007.04 对外开源。PHP 代码用于实现前端展示层的 Web 动态页面渲染,以及对服务层的数据聚合。
2010 年,Facebook 工程师 Aditya Agarwal 对外介绍的 Facebook 技术架构如下图所示[101]:
Facebook 的技术栈图[102]:
Facebook 的 NewsFeed 服务的架构图[102]:
Facebook 的 Search 服务的架构图[102]:
当前 Facebook 的主要技术栈[101][102][103]:
Twitter,2006 年创立,早期采用 Ruby on Rails 技术栈,数据库是 MySQL,2009 年开始服务化,将核心业务从 Ruby on Rails 中移出到新的服务,新的服务用 Scala、Java 编写[109][110]。
2013 年,Twitter 工程师总结的 Twitter 从单体架构向分布式架构演进的过程,如下图所示[110]:
2007 Jeff Dean: Software Engineering Advice from Building Large-Scale Distributed Systems (Stanford CS295 class lecture, Spring, 2007) https://research.google.com/people/jeff/stanford-295-talk.pdf ↩
2008-11 Google Architecture http://highscalability.com/blog/2008/11/22/google-architecture.html ↩
2008-06 新浪夏清然、徐继哲:自由软件和新浪网. 程序员 2008年第6期54-55 http://lib.cqvip.com/Qikan/Article/Detail?id=27523653 http://www.zeuux.com/blog/content/3620/ ↩
2016-01 微信张文瑞:从无到有:微信后台系统的演进之路 https://www.infoq.cn/article/the-road-of-the-growth-weixin-background ↩
2008-02 Yandex Architecture http://highscalability.com/yandex-architecture ↩
2007-08 Wikimedia architecture http://highscalability.com/wikimedia-architecture ↩
2007-11 Flickr Architecture http://highscalability.com/flickr-architecture ↩
2016-03 What does Etsy's architecture look like today? http://highscalability.com/blog/2016/3/23/what-does-etsys-architecture-look-like-today.html ↩
2011-05 新浪刘晓震:新浪博客应用架构分享 (PHPChina 2011) https://web.archive.org/web/0/http://www.phpchina.com/?action-viewnews-itemid-38418 https://www.modb.pro/doc/121035 ↩
2013-11 百度张东进:百度PHP基础设施构建思路 (QCon上海2013, slides, 30p) https://www.modb.pro/doc/121042 ↩
2013-05 The Tumblr Architecture Yahoo Bought for a Cool Billion Dollars http://highscalability.com/blog/2013/5/20/the-tumblr-architecture-yahoo-bought-for-a-cool-billion-doll.html ↩
2017-06 B站任伟:B站高性能微服务架构 https://zhuanlan.zhihu.com/p/33247332 ↩ ↩
2021-05 微博刘道儒:十年三次重大架构升级,微博应对“极端热点”的进阶之路 https://www.infoq.cn/article/qgwbh0wz5bvw9apjos2a ↩
2012-08 腾讯张松国:腾讯微博架构介绍 (ArchSummit深圳2012, slides, 59p) https://www.slideshare.net/dleyanlin/08-13994311 ↩
2016-02 美团夏华夏:从技术细节看美团架构 (ArchSummit北京2014) https://www.infoq.cn/article/see-meituan-architecture-from-technical-details http://bj2014.archsummit.com/node/596/ https://www.modb.pro/doc/8311 ↩
2019-06 滴滴杜欢:大型微服务框架设计实践 (Gopher China 2019) https://www.infoq.cn/article/EfOlY8_rubh4LfoXzF8B https://www.modb.pro/doc/35485 ↩ ↩
2018-08 E-Commerce at Scale: Inside Shopify's Tech Stack - Stackshare.io https://shopify.engineering/e-commerce-at-scale-inside-shopifys-tech-stack ↩
2013-03 Phil Calçado @ SoundCloud: From a monolithic Ruby on Rails app to the JVM (slides, 75p) https://www.slideshare.net/pcalcado/from-a-monolithic-ruby-on-rails-app-to-the-jvm ↩
2012-07 Go at SoundCloud https://developers.soundcloud.com/blog/go-at-soundcloud ↩ ↩
2014-04 Peter Bourgon @ SoundCloud: Go: Best Practices for Production Environments (GopherCon 2014) http://peter.bourgon.org/go-in-production/ ↩ ↩
2009-04 Heroku - Simultaneously Develop and Deploy Automatically Scalable Rails Applications in the Cloud http://highscalability.com/blog/2009/4/24/heroku-simultaneously-develop-and-deploy-automatically-scala.html ↩
2021-07 GitHub’s Journey from Monolith to Microservices https://www.infoq.com/articles/github-monolith-microservices/ ↩
2017-12 Building Services at Airbnb, Part 1 https://medium.com/airbnb-engineering/c4c1d8fa811b ↩
2020-11 Jessica Tai @ Airbnb: How to Tame Your Service APIs: Evolving Airbnb’s Architecture https://www.infoq.com/presentations/airbnb-api-architecture/ ↩
2013-08 Reddit: Lessons Learned from Mistakes Made Scaling to 1 Billion Pageviews a Month http://highscalability.com/blog/2013/8/26/reddit-lessons-learned-from-mistakes-made-scaling-to-1-billi.html ↩ ↩
2012-03 7 Years of YouTube Scalability Lessons in 30 Minutes http://highscalability.com/blog/2012/3/26/7-years-of-youtube-scalability-lessons-in-30-minutes.html ↩
2016-04 豆瓣田忠博:豆瓣的服务化体系改造 (QCon北京2016, slides, 37p) http://2016.qconbeijing.com/presentation/2834/ ↩
2010-09 Disqus: Scaling the World's Largest Django App (DjangoCon 2010, slides) https://www.slideshare.net/zeeg/djangocon-2010-scaling-disqus ↩
2021-05 Dropbox: Atlas: Our journey from a Python monolith to a managed platform https://dropbox.tech/infrastructure/atlas--our-journey-from-a-python-monolith-to-a-managed-platform ↩
2014-07 Dropbox: Open Sourcing Our Go Libraries https://dropbox.tech/infrastructure/open-sourcing-our-go-libraries ↩ ↩
2017-07 Tammy Butow @ Dropbox: Go Reliability and Durability at Dropbox https://about.sourcegraph.com/blog/go/go-reliability-and-durability-at-dropbox-tammy-butow ↩ ↩
2011-02 Adam D'Angelo: Why did Quora choose C++ over C for its high performance services? Does the Quora codebase use a limited subset of C++? https://qr.ae/pKwCE3 ↩
2012-02 A Short on the Pinterest Stack for Handling 3+ Million Users http://highscalability.com/blog/2012/2/16/a-short-on-the-pinterest-stack-for-handling-3-million-users.html ↩
2015-08 Finagle and Java Service Framework at Pinterest (slides) https://www.slideshare.net/yongshengwu/finaglecon2015pinterest ↩
2013-03 Instagram 5位传奇工程师背后的技术揭秘 https://web.archive.org/web/0/http://www.csdn.net/article/2013-03-28/2814698-The-technologie- behind-Instagram ↩ ↩
2017-03 Lisa Guo: Scaling Instagram Infrastructure(QCon London 2017, 87p) https://www.infoq.com/presentations/instagram-scale-infrastructure/ ↩ ↩
2019-08 Static Analysis at Scale: An Instagram Story https://instagram-engineering.com/8f498ab71a0c ↩ ↩
2016-07 The Uber Engineering Tech Stack, Part I: The Foundation https://web.archive.org/web/0/https://eng.uber.com/tech-stack-part-one/ ↩ ↩
2018-11 知乎社区核心业务 Golang 化实践 https://zhuanlan.zhihu.com/p/48039838 ↩ ↩
2022-10 字节马春辉:字节大规模微服务语言发展之路 https://www.infoq.cn/article/n5hkjwfx1gxklkh8iham ↩ ↩
2021-07 字节成国柱:字节跳动微服务架构体系演进 https://mp.weixin.qq.com/s/1dgCQXpeufgMTMq_32YKuQ ↩ ↩
Why does Google use Java instead of Python for Gmail? https://qr.ae/pKkduC ↩
2011-07 揭秘Google+技术架构 http://www.infoq.com/cn/news/2011/07/Google-Plus ↩
2009-12 人人网架构师张洁:人人网使用的开源软件列表 https://web.archive.org/web/0/http://ugc.renren.com/2009/12/13/a-list-of-open-source-software-in-renren ↩
2018-12 Netflix OSS and Spring Boot — Coming Full Circle https://netflixtechblog.com/4855947713a0 ↩
携程为什么突然技术转型从 .NET 转 Java? https://www.zhihu.com/question/56259195 ↩
Golang Frequently Asked Questions: Why did you create a new language? https://go.dev/doc/faq#creating_a_new_language ↩
2014-05 Brad Fitzpatrick: Go: 90% Perfect, 100% of the time: Fun vs. Fast https://go.dev/talks/2014/gocon-tokyo.slide#28 ↩
2021-08 百度Geek说:短视频go研发框架实践 https://my.oschina.net/u/4939618/blog/5191598 ↩
2023-07 百度Geek说:从 php5.6 到 golang1.19 - 文库 App 性能跃迁之路 https://my.oschina.net/u/4939618/blog/10086661 ↩
2022-03 2021年,腾讯研发人员增长41%,Go首次超越C++成为最热门语言 https://mp.weixin.qq.com/s/zj-DhASG4S-3z56GTYjisg ↩
2020-05 Which Major Companies Use PostgreSQL? What Do They Use It for? https://learnsql.com/blog/companies-that-use-postgresql-in-business/ ↩
2009-05 Why is MySQL more popular than PostgreSQL? https://news.ycombinator.com/item?id=619871 ↩
Why is MySQL more popular than PostgreSQL? https://qr.ae/pKPJcE ↩
2002-10 Michael Radwin: Making the Case for PHP at Yahoo! (PHPCon 2002, slides) https://web.archive.org/web/0/http://www.php-con.com/2002/view/session.php?s=1012 https://speakerdeck.com/yulewei/making-the-case-for-php-at-yahoo ↩ ↩
2005-10 Michael Radwin: PHP at Yahoo! (PHP Conference 2005, slides) https://speakerdeck.com/yulewei/php-at-yahoo-zend2005 ↩ ↩
2007-06 Federico Feroldi: PHP in Yahoo! (slides) https://www.slideshare.net/fullo/federico-feroldi-php-in-yahoo ↩
2021-02 AWS re:Invent 2020: Amazon.com’s architecture evolution and AWS strategy https://www.youtube.com/watch?v=HtWKZSLLYTE ↩ ↩
What programming languages are used at Amazon? https://qr.ae/pKFwnw ↩
2011-04 Charlie Cheever: How did Google, Amazon, and the like initially develop and code their websites, databases, etc.? https://qr.ae/pKKyB0 ↩
2016-04 Is Amazon still using Perl Mason to render its content? https://qr.ae/pKFwFm ↩
2019-10 Migration Complete – Amazon’s Consumer Business Just Turned off its Final Oracle Database https://aws.amazon.com/blogs/aws/migration-complete-amazons-consumer-business-just-turned-off-its-final-oracle-database/?nc1=h_ls ↩
Amazon RDS for PostgreSQL customers https://aws.amazon.com/rds/postgresql/customers/?nc1=h_ls ↩
Amazon ElastiCache for Redis customers https://aws.amazon.com/elasticache/redis/customers/?nc1=h_ls ↩
2006-11 Randy Shoup & Dan Pritchett: The eBay Architecture (SDForum2006, slides, 37p) http://www.addsimplicity.com/downloads/eBaySDForum2006-11-29.pdf ↩ ↩ ↩
2007-05 eBay Architecture http://highscalability.com/blog/2008/5/27/ebay-architecture.html ↩ ↩
2011-10 Tony Ng: eBay Architecture (slides, 46p) https://www.slideshare.net/tcng3716/ebay-architecture ↩
2016-11 Ron Murphy: Microservices at eBay (slides, 20p) https://www.modb.pro/doc/120378 ↩ ↩ ↩
2020-09 High Efficiency Tool Platform for Framework Migration https://innovation.ebayinc.com/tech/engineering/high-efficiency-tool-platform-for-framework-migration/ ↩
2023-10 BES2:打造eBay下一代高可靠消息中间件 https://mp.weixin.qq.com/s/ThhkO1WM7ck1WO8RqjTCJA ↩
2013-06 Cassandra at eBay - Cassandra Summit 2013 (slides) https://www.slideshare.net/jaykumarpatel/cassandra-at-ebay-cassandra-summit-2013 ↩
2017-03 Sudeep Kumar: 'Elasticsearch as a Service' at eBay https://www.elastic.co/elasticon/conf/2017/sf/elasticsearch-as-a-service-at-ebay ↩
2016-08 How eBay Uses Apache Software to Reach Its Big Data Goals https://www.linux.com/news/how-ebay-uses-apache-software-reach-its-big-data-goals/ ↩
2011-06 淘宝吴泽明范禹:淘宝业务发展及技术架构 (slides, 43p) https://www.modb.pro/doc/116697 ↩ ↩ ↩
2014-12 阿里云王宇德、张文生:淘宝迁云架构实践 https://web.archive.org/web/1/http://www.csdn.net/article/a/2014-12-09/15821474 ↩ ↩ ↩
2017-07 阿里谢吉宝唐三:阿里电商架构演变之路 (首届阿里巴巴中间件技术峰会, slides, 33p) https://www.modb.pro/doc/121185 ↩ ↩ ↩ ↩
2010-11 淘宝赵林丹臣:淘宝数据库架构演进历程 (iDataForum2010, slides, 38p) https://www.slideshare.net/ssuser1646de/ss-10163048 ↩
2019-10 阿里刘振飞:十年磨一剑:从2009启动“去IOE”工程到2019年OceanBase拿下TPC-C世界第一 https://mp.weixin.qq.com/s/7B6rp17XVhpAWZr1-6DHqQ https://developer.aliyun.com/article/722414 ↩
2016-02 SSD的30年发展史 https://mp.weixin.qq.com/s/JsHKFilB5fvLY9V9z_xsXw ↩
2010-04 Yoshinori Matsunobu: SSD Deployment Strategies for MySQL (slides, 52p) https://www.slideshare.net/matsunobu/ssd-deployment-strategies-for-mysql ↩
2020-04 阿里王剑英、和利:淘宝万亿级交易订单背后的存储引擎 https://mp.weixin.qq.com/s/MkX1Pr8tERrzK29XG9zMUQ https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/storage-engine-that-processes-trillions-of-taobao-order-records ↩
2022-03 阿里云罗庆超:阿里巴巴集团上云之 TFS 迁移 OSS 技术白皮书 https://developer.aliyun.com/article/876301 ↩
2016-12 阿里倪超:阿里巴巴Aliware十年微服务架构演进历程中的挑战与实践 https://www.sohu.com/a/121588928_465959 ↩
2017-10 阿里钟华古谦:企业IT架构转型之道 (杭州云栖大会2017, slides, 18p) https://www.modb.pro/doc/121695 ↩
2021-09 阿里郭浩:Dubbo 和 HSF 在阿里巴巴的实践:携手走向下一代云原生微服务 https://mp.weixin.qq.com/s/_Ug3yEh9gz5mLE_ag1DjwA ↩ ↩ ↩
2012-11 阿里巴巴分布式服务框架 Dubbo 团队成员梁飞专访 http://www.iteye.com/magazines/103 ↩
2019-01 Dubbo 作者梁飞亲述:那些辉煌、沉寂与重生的故事 https://www.infoq.cn/article/3F3Giujjo-QwSw2wEz7u ↩ ↩
Dubbo 用户案例:阿里巴巴升级 Dubbo3 全面取代 HSF2 https://cn.dubbo.apache.org/zh-cn/users/alibaba/ https://github.com/apache/dubbo-website/blob/ee727525aa61d68b397cc6ddedb322001f0ca4da/content/zh/users/alibaba.md ↩
2023-01 阿里巴巴升级 Dubbo3 全面取代 HSF2 https://cn.dubbo.apache.org/zh-cn/blog/2023/01/16/阿里巴巴升级-dubbo3-全面取代-hsf2/ ↩
2017-11 阿里林清山隆基:阿里消息中间件架构演进之路:notify和metaq https://zhuanlan.zhihu.com/p/302600352 ↩
2013-07 淘宝张乐伟韩彰:淘宝消息中间件技术演变:MetaQ 1.0、MetaQ 2.0、MetaQ 3.0(sildes, 30p)https://www.modb.pro/doc/109298 ↩
2017-03 阿里冯嘉鼬神:Apache RocketMQ背后的设计思路与最佳实践 https://developer.aliyun.com/article/71889 ↩
2008-05 LinkedIn - A Professional Network built with Java Technologies and Agile Practices (JavaOne2008, slides) https://www.slideshare.net/linkedin/linkedins-communication-architecture ↩ ↩ ↩
2015-07 Josh Clemm: A Brief History of Scaling LinkedIn https://engineering.linkedin.com/architecture/brief-history-scaling-linkedin https://www.slideshare.net/joshclemm/how-linkedin-scaled-a-brief-history ↩ ↩
Aditya Auradkar, Chavdar Botev, etc.: Data Infrastructure at LinkedIn. ICDE 2012: 1370-1381,dblp, semanticscholar, slides ↩ ↩ ↩
2012-06 Sid Anand: LinkedIn Data Infrastructure Slides (Version 2) (slides) https://www.slideshare.net/r39132/linkedin-data-infrastructure-slides-version-2-13394853 ↩ ↩
2021-12 Evolving LinkedIn’s analytics tech stack https://engineering.linkedin.com/blog/2021/evolving-linkedin-s-analytics-tech-stack ↩ ↩
2010-05 Aditya Agarwal: Scale at Facebook (Qcon London 2010) https://www.infoq.com/presentations/Scale-at-Facebook/ ↩ ↩
2008-11 Aditya Agarwal: Facebook architecture (QCon SF 2008, slides, 34p) https://www.slideshare.net/mysqlops/facebook-architecture https://www.infoq.com/presentations/Facebook-Software-Stack/ ↩ ↩ ↩ ↩
2011-04 Michaël Figuière: What is Facebook's architecture? https://qr.ae/pKYg12 ↩
2013-10 Where does Facebook use C++? https://qr.ae/pKCIee ↩
2022-07 Programming languages endorsed for server-side use at Meta https://engineering.fb.com/2022/07/27/developer-tools/programming-languages-endorsed-for-server-side-use-at-meta/ ↩
2015-07 Instagram: Search Architecture https://instagram-engineering.com/eeb34a936d3a ↩
Guoqiang Jerry Chen, Janet L. Wiener, etc.: Realtime Data Processing at Facebook. SIGMOD Conference 2016: 1087-1098,dblp, semanticscholar ↩
2021-10 XStream: stream processing platform at facebook (Flink Forward Global 2021, slides) https://www.slideshare.net/aniketmokashi/xstream-stream-processing-platform-at-facebook ↩
2013-01 Johan Oskarsson: The Twitter stack https://blog.oskarsson.nu/post/40196324612/the-twitter-stack ↩ ↩
2013-10 Chris Aniszczyk: Evolution of The Twitter Stack (slides) https://www.slideshare.net/caniszczyk/twitter-opensourcestacklinuxcon2013 ↩ ↩ ↩
2017-01 The Infrastructure Behind Twitter: Scale https://blog.twitter.com/engineering/en_us/topics/infrastructure/2017/the-infrastructure-behind-twitter-scale ↩
2010-07 Cassandra at Twitter Today https://blog.twitter.com/engineering/en_us/a/2010/cassandra-at-twitter-today ↩
2011-12 How Twitter Stores 250 Million Tweets a Day Using MySQL http://highscalability.com/blog/2011/12/19/how-twitter-stores-250-million-tweets-a-day-using-mysql.html ↩
2013-11 Michael Busch: Search at Twitter (Lucene Revolution 2013, slides) https://www.slideshare.net/lucenerevolution/twitter-search-lucenerevolutioneu2013-copy https://www.youtube.com/watch?v=AguWva8P_DI ↩ ↩
2022-10 Stability and scalability for search https://blog.twitter.com/engineering/en_us/topics/infrastructure/2022/stability-and-scalability-for-search ↩
2021-10 Processing billions of events in real time at Twitter https://blog.twitter.com/engineering/en_us/topics/infrastructure/2021/processing-billions-of-events-in-real-time-at-twitter- ↩
在“开源”(open source)一词出现之前,技术社区的黑客选择使用“自由软件”(free software)这个词。但是“自由软件”这个词与对知识产权的敌意、共产主义和其它观点相联系,几乎不受管理者和投资者的欢迎,于是 1998 年 2 月 3 日在由 Eric Raymond 等人参加的会议上“开源”一词诞生,2 月下旬开源软件促进会成立 OSI,Eric Raymond 担任主席。自由软件和开源软件被合称为 FOSS。缩写“LAMP”代表的是 Linux-Apache-MySQL-PHP,这些软件都是自由软件或开源软件。
在互联网诞生早期,开源技术栈、开源社区尚未成熟,互联网公司不得不自研专有软件,随着开源软件的成熟,技术栈的选择开始从专有软件逐渐转向开源软件。先来看下,主要 Web 技术和服务端技术的发展时间线:
按编程语言区别,主要流行的互联网产品的创建时间和早期的技术栈选择(也可以参见 wiki):
值得一提的是,PHP 之父 Rasmus Lerdorf,在 2002 年至 2009 年期间为供职于 Yahoo!,2011 年起至今供职于 Etsy。Python 之父 Guido van Rossum,在 2005 年至 2012 年期间供职于 Google,在 2013 年至 2019 年期间供职于 Dropbox。国内最有影响力的 PHP 技术专家是惠新宸,是加入 PHP 语言官方开发组的首位国人,曾先后供职于雅虎中国、百度、新浪微博、链家网等公司。
2000 年前创建的网站,因为开源技术栈尚未成熟,编程语言基本上都是选择 C++ 开发。在开源技术栈成熟后,多数网站会选择拥抱开源。具体选择哪个编程语言,PHP、Ruby、Python、Java 等,主要由技术负责人的技术偏好决定。根据 w3techs 统计,历年网站使用的服务端编程语言统计占比,2012 年至今 10 多年,PHP 占比稳居榜首,每年都是 75% 以上。大型网站都是公司内的技术团队研发的,技术栈由技术团队选择,而很多小型网站,很可能会直接使用开源的 CMS 系统搭建。根据 w3techs 统计,前 100 万网站中有 43.0% 使用 WordPress 构建。WordPress 的服务端编程语言就是 PHP,数据库是 MySQL。大型网站选择 PHP 越来越少,因为 PHP 是解释型脚本语言,相对编译型编程语言有性能劣势。另外,PHP 的优势是快速开发 Web 动态网页,但是随着前后端分离开发模式的流行,Web 页面从服务端渲染逐渐转向客户端渲染,PHP 的优势不再,后端工程师只需要向前端提供 REST API 接口,展示层的实现完全由前端工程师实现。
早期部分网站选择 .NET 技术栈,比如京东、携程。但是,Java 平台生态更完善,有非常多的经验可以借鉴。另外,.NET 平台本身虽然不收费,但是 Windows 操作系统是收费的,开发工具也不便宜。于是京东在 2012 年从 .NET 迁移到 Java,携程在 2017 左右从 .NET 迁移到 Java。目前在国内,多数互联网大厂都选择 Java 技术栈,如淘宝、美团、京东、微博、携程等,Java 相对来说是主流选择。
编程语言的另外一个流行趋势是 Go 语言。Go 语言诞生于 Google,在 2009 年 11 月对外公开。在发明 Go 语言前,Google 内部主要使用的语言是 C++、Java 和 Python 等,但是 Go 发明者认为这些语言无法同时满足高效编译、高效执行和易于编程的特性诉求,所以创造了 Go 语言48。Go 比 C++ 能更高效编译和易于编程,比 Java 更易于编程,比 Python 能更高效执行。在 GoCon Tokyo 2014 会议上,Go 语言研发团队的 Brad Fitzpatrick 对各种编程语言在编程乐趣和执行速度(Fun vs. Fast)的对比49,如下图所示:
早期由 Go 语言实现的经典开源项目主要是基础设施软件,比如 YouTube 的 Vitess 数据库分片中间件(2012.02 对外开源)、Docker 容器(2013.03 对外开源)、Red Hat 的 CoreOS 团队的 Etcd 分布式配置服务(2013.07 对外开源,实现 Raft 共识协议)、前 Google 工程师创建的 CockroachDB 分布式数据库(2014.02 对外开源,受 Google Spanner 启发)、Google 的 Kubernetes 容器编排系统(2014.06 对外开源)、SoundCloud 的 Prometheus 监控工具(2015.01 对外开源)等。大规模使用 Go 语言的代表性的国外的互联网公司有 SoundCloud1920、Dropbox3031、Uber38 等,国内的有七牛云、字节跳动4041、哔哩哔哩12、滴滴16、知乎39、百度5051、腾讯52等,在这些互联网公司 Go 语言通常主要被用于构建基础设施软件(网关、数据存储、监控、视频处理等)或高性能要求的业务服务,部分公司的大量核心业务也从 Python、PHP 等迁移到 Go 语言。使用 Go 语言的公司的更加完整的列表,可以参见官方整理的“GoUsers”。
数据库方面,根据 DB-Engines Ranking 的统计,流行的关系数据库主要是 4 个,开源免费的 MySQL 和 PostgreSQL,专有收费的 Oracle 和 Microsoft SQL Server。多数互联网产品使用的数据库是 MySQL,部分是 PostgreSQL 或 Oracle。一些早期使用 Oracle 数据库的网站选择部分或完全从 Oracle 数据库中迁出,代表性的案例是,Amazon 从 Oracle 完全迁移到 Amazon RDS 和 NoSQL,淘宝从 Oracle 完全迁移到 MySQL。以 PostgreSQL 为主数据库的互联网应用有 Skype、Reddit、Spotify、Disqus、Heroku、Instagram 等53。
MySQL 相对 PostgreSQL 更加流行的主要原因是5455,在早期 MySQL 入门槛更低。MySQL 就支持在 Windows 下安装,而 PostgreSQL 只能在 Cygwin 下安装,MySQL 对 Windows 平台的支持使得初学者更容易上手,并且 MySQL 更加易于管理,能够快速简单地启动和使用,而且 MySQL 拥有一个非常简洁、易于导航和用户友好的在线文档和支持社区。另外一个重要原因是,MySQL 很早就默认支持复制(replication),而 PostgreSQL 的复制是第三方的,而且极其难用。之后的几年即便 PostgreSQL 完善了不足,但错过了流行的时间窗口,MySQL 成为了主流选择,有更完善的生态。不过,受 MySQL 被 Oracle 公司收购的影响,近几年 PostgreSQL 开始越来越流行,根据 DB-Engines Ranking 的统计,最新的 PostgreSQL 的流行度分数是 10 年前的三倍,流行度与前三名越来越接近。
容易发现多数网站的技术栈的演进模式类似。在创建网站早期通常选择使用 PHP、Ruby、Python 脚本语言和框架来开发,这些脚本语言和框架具有更快的开发效率,能快速交付上线。随着网站流量的增长,面临性能和可扩展性问题,通常会将网站服务化,从单体架构向面向服务的 SOA 和微服务架构演进,一大部分网站在服务化过程中会选择将核心业务改由其他性能更佳的编译型编程语言来实现,如 Java、Scala、C++、Go,原先的脚本语言可能全部废弃,也可能仅用于前端展示层的 Web 动态页面渲染,比如 Facebook、Twitter 等。当然也有一部分网站,演进为 SOA 和微服务架构后,业务逻辑的实现一直以最初的脚本语言为主。
在架构微服务化后,很多网站实现的微服务之间采用基于 HTTP 协议的 REST 方式通信(或者使用的 RPC 框架支持跨语言 RPC 调用),各个微服务的实现在理论上编程语言不需要统一,可以根据该微服务的性能要求或负责该微服务的团队的技术偏好自由选择,所以真实世界的互联网公司内部各个业务子系统的技术栈可能不统一。但是内部技术栈不统一会带来额外的维护成本。另外,考虑技术栈的迁移重构成本,新的业务子系统使用新的技术栈,而有些旧的业务子系统很可能继续使用旧的技术栈。除了业务子系统外,很可能团队内部有自研的中间件、运维工具等,这些组件由中间件团队、运维团队研发,很可能会选择与业务系统不一样的技术栈。
注意,解决网站的可扩展性问题,不一定需要演进为服务化架构。架构服务化意味着将完整的单体服务按业务的功能领域做垂直拆分,而实际上在应用服务层可以通过部署多个相同副本的单体服务的方式实现系统的水平扩展,网站的可扩展性问题主要在数据存储层上。拆分应用服务的好处在于能实现组织团队的规模化。解决数据库的扩展性的策略有,数据复制(数据缓存、数据库读写分离)、数据垂直拆分、数据水平拆分(也叫数据分片,sharding)。数据库被拆分后,如果单个事务内的数据分散在多个节点就要解决分布式事务问题,但是实现分布式事务代价太大,通常的选择是牺牲一致性,仅满足最终一致性(BASE)。
没有拆分应用服务,始终采用单体架构的经典案例是 Instagram,2019 年 Instagram 在技术博客上有这样一段话37:
Our server app is a monolith, one big codebase of several million lines and a few thousand Django endpoints, all loaded up and served together. A few services have been split out of the monolith, but we don’t have any plans to aggressively break it up.
为了解决可扩展性问题,Instagram 在数据存储3536,对 PostgreSQL 数据库做了主从读写分离、数据垂直拆分和数据分片,并且使用了 NoSQL 数据库 Cassandra,用于存储 Feed 流等数据。类似的,Reddit 也是单体架构,在数据存储层做可扩展性改造,对 PostgreSQL 数据库做了垂直拆分,并且使用 Cassandra 数据库25。
Yahoo!,1994 年创立,早期网站操作系统是 FreeBSD,Web 服务器是自研的 Filo Server,数据库是自研的 DBM 文件,Web 动态页面脚本是自研的 yScript,业务逻辑编程语言是 C++。1996 年 Web 服务器改用 Apache HTTP Server,1999 年部分数据库改用 MySQL(同时也使用 Oracle 数据库),2002 年编程语言从 yScript 和 C++ 改为 PHP,操作系统也从 FreeBSD 逐渐转向 Linux565758。在 2002.10 的 PHPCon 2002 会议上,Yahoo! 工程师 Michael Radwin 介绍了 Yahoo! 从专有软件向开源软件转向的原因和演进过程,以及选择 PHP 而不选择 ASP、Perl、JSP 等其他技术的原因56。Yahoo! 转向开源软件的原因主要是,避免维护专有软件的成本,开源软件从早期的不成熟最终变得成熟,具有更好的性能,更容易与第三方软件集成,以及开源社区逐渐发展壮大。2005 年,Yahoo! 的技术架构如下图所示57:
2000 年 ~ 2010 年,Yahoo! 是世界上流量最大的网站,虽然期间被 Google 短暂超越,但随后又反超,一直到到 2010 年后才被 Google 彻底超越。2000 年前后 Yahoo! 转向到 MySQL 和 PHP,并在技术上助力 LAMP 生态的发展壮大,对 LAMP 技术栈的流行起到巨大的推动作用。值得一提的是,知名 MySQL 专家 Jeremy Zawodny,《高性能MySQL》2004 年第 1 版(豆瓣)的作者,在 1999.12 ~ 2008.06 期间为 Yahoo! 工程师;PHP 之父 Rasmus Lerdorf 在 2002.09 ~ 2009.11 期间为 Yahoo! 工程师。
Amazon,1994 年创立,早期是单服务、单数据库的单体架构,使用的编程语言是 C++,2000 年开始从单体架构向 SOA 和微服务架构演进,使用最多的语言变为 Java,C++ 被用在高性能要求的系统和底层基础设施组件。Amazon 架构演进过程如下图所示59:
Amazon.com 的技术栈演进过程:
当前 Amazon.com 网站的主要技术栈59606162:
eBay,1995 年创立,早期使用的编程语言是 Perl,1997 年迁移到 C++,2002 年迁移到 Java。eBay 的技术栈演进过程6667:
2016 年,eBay 的技术架构如下图所示69:
淘宝技术架构7576777879,是从 LAMP 架构演变而来的。在 2003 年 5 月最早上线时,淘宝是对购买得到的采用 LAMP 架构网站源码进行二次开发的网站。为了应对网站流量的增长,2003 年底数据库从 MySQL 切换到 Oracle,2004 年初编程语言从 PHP 迁移到 Java,同时硬件演变为 IBM 小型机和 EMC 高端硬件存储,依赖的基础设施被统称为“IOE”(IBM 小型机、Oracle 数据库、EMC 存储设备),这个架构被称为 2.0 架构。
2007 年底开始拆分第一个业务中心是用户中心(UIC,User Information Center),在 2008 年初上线。2008 年初,启动“千岛湖”项目,拆分出了交易中心(TC,Trade Center)和类目属性中心(Forest)。2008 年 10 月,为了打通淘宝网和淘宝商城(后来的天猫)的数据,启动“五彩石”项目,拆分出了店铺中心(SC,Shop Center)、商品中心(IC,Item Center)、评价中心(RC,Rate Center)。到 2009 年初,“五彩石”项目结束,阿里电商架构也从之前的单体架构演进为了 SOA 服务化架构,被称为 3.0 架构,同时淘宝网和淘宝商城也完成了数据整合,并淘宝网和淘宝商城的共享业务沉淀到多个业务中心。在 2009 年,共享业务事业部成立,在组织架构上共享业务事业部和淘宝、天猫平级。在业务中心的上层业务服务有,交易管理(TM,Trade Manager)、商品管理(IM,Item Manager)、店铺系统(ShopSystem)、商品详情(Detail)、评价管理(RateManager)等。2009 年淘宝的服务化拆分如下图所示77:
在各个应用服务拆分的同时,数据库也按业务功能做了垂直拆分,2008 年 Oracle 被拆分为商品、交易、用户、评价、收藏夹等 10 来套。当时的淘宝的分布式架构设计,包括 Java + Oracle 技术栈的选择,以及应用服务和数据库的拆分策略,很大程度上参考的是 eBay 的架构7578。对照 eBay 架构师在 2006 年 11 月对外分享的 eBay 架构的 slides66,容易发现两者大同小异。
因为使用 Oracle 成本太高,2008 年 8 月淘宝数据库设计了异构数据库读写分离架构,写库为集中式的 Oracle,读库使用分库分表的 MySQL,该数据库架构方案基于自研的数据库中间件 TDDL 实现80。之后的几年,数据库逐渐向 MySQL 迁移。2008 年 9 月前微软亚洲研究院常务副院长王坚博士加盟阿里巴巴集团,担任首席架构师,2009 年 11 月时任阿里 CTO 的王坚博士决策启动阿里“去IOE”工程。淘宝的“去IOE”工程的关键时点81:
值得注意的是,在 2011 年淘宝商品库和交易库“去IOE”的同时,为了满足数据库的性能要求,底层硬件从 HDD 机械硬盘改为 SSD 固态硬盘。而庆幸的是,当时的前几年正好是 SSD 大爆发的时间点82。在消费级 SSD 市场,2010 年苹果的 MacBook Air 开始全面改用 SSD,2012 年苹果的 MacBook Pro 开始全面改用 SSD。在数据库服务器层面,因为 SSD 在随机读写的性能具有巨大优势,2010 年开始机械硬盘改用 SSD 成为趋势,当时的 MySQL 新版代码也专门针对 SSD 做了性能优化83。
2009 年初淘宝演变为 SOA 架构后,服务拆分粒度越来越细,到 2009 年底拆分出的总服务数达到 187 个,到 2010 年底达到 329 个77。因为系统依赖关系越来越复杂,当时最大问题是稳定性问题,系统的稳定性建设成为重点工作。到 2013 年阿里电商系统开始 4.0 架构改造,即异地多活架构改造,内部称为单元化项目。2013 年 8 月完成杭州同城双活,2014 年双 11 前的 10 月完成杭州和上海近距离两个单元的异地双活,2015 年双 11 完成三地四单元的异地多活7679,至此也完成了 3.0 到 4.0 架构的升级。在阿里电商系统迁移到阿里云公共云方面,2015 年阿里电商系统开始采用混合云弹性架构,当阿里本地保有云无法支撑时,就快速在公有云上扩建新的单元,当流量过去后,再还资源给公有云。2015 年 10% 的双 11 大促流量使用了阿里云的机器来支撑,2016 年 50% 以上的双 11 大促流量使用了阿里云的机器来支撑76。2019 年初,阿里电商开启了全面上公共云的改造,到 2019 年双 11,阿里电商系统实现了全部核心应用上公共云,2021 年双 11,阿里电商系统实现了 100% 上公共云。
2016 年,阿里产品专家倪超对外介绍的阿里电商技术架构,如下图所示86:
类似的,阿里巴巴中间件首席架构师《企业IT架构转型之道》(豆瓣)作者钟华在 2017 年对外介绍的阿里电商技术架构图87:
2017 年,阿里中间件技术部专家谢吉宝对外介绍的阿里中间件技术大图79:
阿里 HSF 与 Dubbo 的历史演进时间线:
阿里 RocketMQ 的历史演进时间线:
LinkedIn,2003 年创立,采用纯 Java 技术栈,早期是单体架构,到 2008 年演化为 SOA 架构96。到 2010 年拆分的服务数超过 150 个,到 2015 年拆分的服务数超过 750 个97。
2008 年,LinkedIn 对外介绍的技术架构如下图所示96:
2012 年,LinkedIn 对外介绍的关注在数据基础设施上的技术架构图98:
Facebook,2004 年创立,早期采用 LAMP 技术栈,为了应对负载增长开始服务化,将核心业务从 LAMP 中移出到新的服务,新的服务改用使用 C++、Java 等编写。为了解决 PHP 进程与非 PHP 进程的 RPC 通信问题,2006 年内部研发了跨语言的 RPC 库 Thrift,2007.04 对外开源。PHP 代码用于实现前端展示层的 Web 动态页面渲染,以及对服务层的数据聚合。
2010 年,Facebook 工程师 Aditya Agarwal 对外介绍的 Facebook 技术架构如下图所示101:
Facebook 的技术栈图102:
Facebook 的 NewsFeed 服务的架构图102:
Facebook 的 Search 服务的架构图102:
Twitter,2006 年创立,早期采用 Ruby on Rails 技术栈,数据库是 MySQL,2009 年开始服务化,将核心业务从 Ruby on Rails 中移出到新的服务,新的服务用 Scala、Java 编写109110。
2013 年,Twitter 工程师总结的 Twitter 从单体架构向分布式架构演进的过程,如下图所示110:
1. 2007 Jeff Dean: Software Engineering Advice from Building Large-Scale Distributed Systems (Stanford CS295 class lecture, Spring, 2007) https://research.google.com/people/jeff/stanford-295-talk.pdf ↩
2. 2008-11 Google Architecture http://highscalability.com/blog/2008/11/22/google-architecture.html ↩
3. 2008-06 新浪夏清然、徐继哲:自由软件和新浪网. 程序员 2008年第6期54-55 http://lib.cqvip.com/Qikan/Article/Detail?id=27523653 http://www.zeuux.com/blog/content/3620/ ↩
4. 2016-01 微信张文瑞:从无到有:微信后台系统的演进之路 https://www.infoq.cn/article/the-road-of-the-growth-weixin-background ↩
5. 2008-02 Yandex Architecture http://highscalability.com/yandex-architecture ↩
6. 2007-08 Wikimedia architecture http://highscalability.com/wikimedia-architecture ↩
7. 2007-11 Flickr Architecture http://highscalability.com/flickr-architecture ↩
8. 2016-03 What does Etsy’s architecture look like today? http://highscalability.com/blog/2016/3/23/what-does-etsys-architecture-look-like-today.html ↩
9. 2011-05 新浪刘晓震:新浪博客应用架构分享 (PHPChina 2011) https://web.archive.org/web/0/http://www.phpchina.com/?action-viewnews-itemid-38418 https://www.modb.pro/doc/121035 ↩
10. 2013-11 百度张东进:百度PHP基础设施构建思路 (QCon上海2013, slides, 30p) https://www.modb.pro/doc/121042 ↩
11. 2013-05 The Tumblr Architecture Yahoo Bought for a Cool Billion Dollars http://highscalability.com/blog/2013/5/20/the-tumblr-architecture-yahoo-bought-for-a-cool-billion-doll.html ↩
12. 2017-06 B站任伟:B站高性能微服务架构 https://zhuanlan.zhihu.com/p/33247332 ↩
13. 2021-05 微博刘道儒:十年三次重大架构升级,微博应对“极端热点”的进阶之路 https://www.infoq.cn/article/qgwbh0wz5bvw9apjos2a ↩
14. 2012-08 腾讯张松国:腾讯微博架构介绍 (ArchSummit深圳2012, slides, 59p) https://www.slideshare.net/dleyanlin/08-13994311 ↩
15. 2016-02 美团夏华夏:从技术细节看美团架构 (ArchSummit北京2014) https://www.infoq.cn/article/see-meituan-architecture-from-technical-details http://bj2014.archsummit.com/node/596/ https://www.modb.pro/doc/8311 ↩
16. 2019-06 滴滴杜欢:大型微服务框架设计实践 (Gopher China 2019) https://www.infoq.cn/article/EfOlY8_rubh4LfoXzF8B https://www.modb.pro/doc/35485 ↩
17. 2018-08 E-Commerce at Scale: Inside Shopify’s Tech Stack - Stackshare.io https://shopify.engineering/e-commerce-at-scale-inside-shopifys-tech-stack ↩
18. 2013-03 Phil Calçado @ SoundCloud: From a monolithic Ruby on Rails app to the JVM (slides, 75p) https://www.slideshare.net/pcalcado/from-a-monolithic-ruby-on-rails-app-to-the-jvm ↩
19. 2012-07 Go at SoundCloud https://developers.soundcloud.com/blog/go-at-soundcloud ↩
20. 2014-04 Peter Bourgon @ SoundCloud: Go: Best Practices for Production Environments (GopherCon 2014) http://peter.bourgon.org/go-in-production/ ↩
21. 2009-04 Heroku - Simultaneously Develop and Deploy Automatically Scalable Rails Applications in the Cloud http://highscalability.com/blog/2009/4/24/heroku-simultaneously-develop-and-deploy-automatically-scala.html ↩
22. 2021-07 GitHub’s Journey from Monolith to Microservices https://www.infoq.com/articles/github-monolith-microservices/ ↩
23. 2017-12 Building Services at Airbnb, Part 1 https://medium.com/airbnb-engineering/c4c1d8fa811b ↩
24. 2020-11 Jessica Tai @ Airbnb: How to Tame Your Service APIs: Evolving Airbnb’s Architecture https://www.infoq.com/presentations/airbnb-api-architecture/ ↩
25. 2013-08 Reddit: Lessons Learned from Mistakes Made Scaling to 1 Billion Pageviews a Month http://highscalability.com/blog/2013/8/26/reddit-lessons-learned-from-mistakes-made-scaling-to-1-billi.html ↩
26. 2012-03 7 Years of YouTube Scalability Lessons in 30 Minutes http://highscalability.com/blog/2012/3/26/7-years-of-youtube-scalability-lessons-in-30-minutes.html ↩
27. 2016-04 豆瓣田忠博:豆瓣的服务化体系改造 (QCon北京2016, slides, 37p) http://2016.qconbeijing.com/presentation/2834/ ↩
28. 2010-09 Disqus: Scaling the World’s Largest Django App (DjangoCon 2010, slides) https://www.slideshare.net/zeeg/djangocon-2010-scaling-disqus ↩
29. 2021-05 Dropbox: Atlas: Our journey from a Python monolith to a managed platform https://dropbox.tech/infrastructure/atlas--our-journey-from-a-python-monolith-to-a-managed-platform ↩
30. 2014-07 Dropbox: Open Sourcing Our Go Libraries https://dropbox.tech/infrastructure/open-sourcing-our-go-libraries ↩
31. 2017-07 Tammy Butow @ Dropbox: Go Reliability and Durability at Dropbox https://about.sourcegraph.com/blog/go/go-reliability-and-durability-at-dropbox-tammy-butow ↩
32. 2011-02 Adam D’Angelo: Why did Quora choose C++ over C for its high performance services? Does the Quora codebase use a limited subset of C++? https://qr.ae/pKwCE3 ↩
33. 2012-02 A Short on the Pinterest Stack for Handling 3+ Million Users http://highscalability.com/blog/2012/2/16/a-short-on-the-pinterest-stack-for-handling-3-million-users.html ↩
34. 2015-08 Finagle and Java Service Framework at Pinterest (slides) https://www.slideshare.net/yongshengwu/finaglecon2015pinterest ↩
35. 2013-03 Instagram 5位传奇工程师背后的技术揭秘 https://web.archive.org/web/0/http://www.csdn.net/article/2013-03-28/2814698-The-technologie-%20behind-Instagram ↩
36. 2017-03 Lisa Guo: Scaling Instagram Infrastructure(QCon London 2017, 87p) https://www.infoq.com/presentations/instagram-scale-infrastructure/ ↩
37. 2019-08 Static Analysis at Scale: An Instagram Story https://instagram-engineering.com/8f498ab71a0c ↩
38. 2016-07 The Uber Engineering Tech Stack, Part I: The Foundation https://web.archive.org/web/0/https://eng.uber.com/tech-stack-part-one/ ↩
39. 2018-11 知乎社区核心业务 Golang 化实践 https://zhuanlan.zhihu.com/p/48039838 ↩
40. 2022-10 字节马春辉:字节大规模微服务语言发展之路 https://www.infoq.cn/article/n5hkjwfx1gxklkh8iham ↩
41. 2021-07 字节成国柱:字节跳动微服务架构体系演进 https://mp.weixin.qq.com/s/1dgCQXpeufgMTMq_32YKuQ ↩
42. Why does Google use Java instead of Python for Gmail? https://qr.ae/pKkduC ↩
43. 2011-07 揭秘Google+技术架构 http://www.infoq.com/cn/news/2011/07/Google-Plus ↩
44. 2009-12 人人网架构师张洁:人人网使用的开源软件列表 https://web.archive.org/web/0/http://ugc.renren.com/2009/12/13/a-list-of-open-source-software-in-renren ↩
45. 2018-12 Netflix OSS and Spring Boot — Coming Full Circle https://netflixtechblog.com/4855947713a0 ↩
46. 携程为什么突然技术转型从 .NET 转 Java? https://www.zhihu.com/question/56259195 ↩
47. 京东技术解密,2014,豆瓣:12 少年派的奇幻漂流——从.Net到Java ↩
48. Golang Frequently Asked Questions: Why did you create a new language? https://go.dev/doc/faq#creating_a_new_language ↩
49. 2014-05 Brad Fitzpatrick: Go: 90% Perfect, 100% of the time: Fun vs. Fast https://go.dev/talks/2014/gocon-tokyo.slide#28 ↩
50. 2021-08 百度Geek说:短视频go研发框架实践 https://my.oschina.net/u/4939618/blog/5191598 ↩
51. 2023-07 百度Geek说:从 php5.6 到 golang1.19 - 文库 App 性能跃迁之路 https://my.oschina.net/u/4939618/blog/10086661 ↩
52. 2022-03 2021年,腾讯研发人员增长41%,Go首次超越C++成为最热门语言 https://mp.weixin.qq.com/s/zj-DhASG4S-3z56GTYjisg ↩
53. 2020-05 Which Major Companies Use PostgreSQL? What Do They Use It for? https://learnsql.com/blog/companies-that-use-postgresql-in-business/ ↩
54. 2009-05 Why is MySQL more popular than PostgreSQL? https://news.ycombinator.com/item?id=619871 ↩
55. Why is MySQL more popular than PostgreSQL? https://qr.ae/pKPJcE ↩
56. 2002-10 Michael Radwin: Making the Case for PHP at Yahoo! (PHPCon 2002, slides) https://web.archive.org/web/0/http://www.php-con.com/2002/view/session.php?s=1012 https://speakerdeck.com/yulewei/making-the-case-for-php-at-yahoo ↩
57. 2005-10 Michael Radwin: PHP at Yahoo! (PHP Conference 2005, slides) https://speakerdeck.com/yulewei/php-at-yahoo-zend2005 ↩
58. 2007-06 Federico Feroldi: PHP in Yahoo! (slides) https://www.slideshare.net/fullo/federico-feroldi-php-in-yahoo ↩
59. 2021-02 AWS re:Invent 2020: Amazon.com’s architecture evolution and AWS strategy https://www.youtube.com/watch?v=HtWKZSLLYTE ↩
60. What programming languages are used at Amazon? https://qr.ae/pKFwnw ↩
61. 2011-04 Charlie Cheever: How did Google, Amazon, and the like initially develop and code their websites, databases, etc.? https://qr.ae/pKKyB0 ↩
62. 2016-04 Is Amazon still using Perl Mason to render its content? https://qr.ae/pKFwFm ↩
63. 2019-10 Migration Complete – Amazon’s Consumer Business Just Turned off its Final Oracle Database https://aws.amazon.com/blogs/aws/migration-complete-amazons-consumer-business-just-turned-off-its-final-oracle-database/?nc1=h_ls ↩
64. Amazon RDS for PostgreSQL customers https://aws.amazon.com/rds/postgresql/customers/?nc1=h_ls ↩
65. Amazon ElastiCache for Redis customers https://aws.amazon.com/elasticache/redis/customers/?nc1=h_ls ↩
66. 2006-11 Randy Shoup & Dan Pritchett: The eBay Architecture (SDForum2006, slides, 37p) http://www.addsimplicity.com/downloads/eBaySDForum2006-11-29.pdf ↩
67. 2007-05 eBay Architecture http://highscalability.com/blog/2008/5/27/ebay-architecture.html ↩
68. 2011-10 Tony Ng: eBay Architecture (slides, 46p) https://www.slideshare.net/tcng3716/ebay-architecture ↩
69. 2016-11 Ron Murphy: Microservices at eBay (slides, 20p) https://www.modb.pro/doc/120378 ↩
70. 2020-09 High Efficiency Tool Platform for Framework Migration https://innovation.ebayinc.com/tech/engineering/high-efficiency-tool-platform-for-framework-migration/ ↩
71. 2023-10 BES2:打造eBay下一代高可靠消息中间件 https://mp.weixin.qq.com/s/ThhkO1WM7ck1WO8RqjTCJA ↩
72. 2013-06 Cassandra at eBay - Cassandra Summit 2013 (slides) https://www.slideshare.net/jaykumarpatel/cassandra-at-ebay-cassandra-summit-2013 ↩
73. 2017-03 Sudeep Kumar: ‘Elasticsearch as a Service’ at eBay https://www.elastic.co/elasticon/conf/2017/sf/elasticsearch-as-a-service-at-ebay ↩
74. 2016-08 How eBay Uses Apache Software to Reach Its Big Data Goals https://www.linux.com/news/how-ebay-uses-apache-software-reach-its-big-data-goals/ ↩
75. 淘宝技术这十年,子柳,2013,豆瓣 ↩
76. 尽在双11:阿里巴巴技术演进与超越,2017,豆瓣:第1章 阿里技术架构演进 ↩
77. 2011-06 淘宝吴泽明范禹:淘宝业务发展及技术架构 (slides, 43p) https://www.modb.pro/doc/116697 ↩
78. 2014-12 阿里云王宇德、张文生:淘宝迁云架构实践 https://web.archive.org/web/1/http://www.csdn.net/article/a/2014-12-09/15821474 ↩
79. 2017-07 阿里谢吉宝唐三:阿里电商架构演变之路 (首届阿里巴巴中间件技术峰会, slides, 33p) https://www.modb.pro/doc/121185 ↩
80. 2010-11 淘宝赵林丹臣:淘宝数据库架构演进历程 (iDataForum2010, slides, 38p) https://www.slideshare.net/ssuser1646de/ss-10163048 ↩
81. 2019-10 阿里刘振飞:十年磨一剑:从2009启动“去IOE”工程到2019年OceanBase拿下TPC-C世界第一 https://mp.weixin.qq.com/s/7B6rp17XVhpAWZr1-6DHqQ https://developer.aliyun.com/article/722414 ↩
82. 2016-02 SSD的30年发展史 https://mp.weixin.qq.com/s/JsHKFilB5fvLY9V9z_xsXw ↩
83. 2010-04 Yoshinori Matsunobu: SSD Deployment Strategies for MySQL (slides, 52p) https://www.slideshare.net/matsunobu/ssd-deployment-strategies-for-mysql ↩
84. 2020-04 阿里王剑英、和利:淘宝万亿级交易订单背后的存储引擎 https://mp.weixin.qq.com/s/MkX1Pr8tERrzK29XG9zMUQ https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/storage-engine-that-processes-trillions-of-taobao-order-records ↩
85. 2022-03 阿里云罗庆超:阿里巴巴集团上云之 TFS 迁移 OSS 技术白皮书 https://developer.aliyun.com/article/876301 ↩
86. 2016-12 阿里倪超:阿里巴巴Aliware十年微服务架构演进历程中的挑战与实践 https://www.sohu.com/a/121588928_465959 ↩
87. 2017-10 阿里钟华古谦:企业IT架构转型之道 (杭州云栖大会2017, slides, 18p) https://www.modb.pro/doc/121695 ↩
88. 2021-09 阿里郭浩:Dubbo 和 HSF 在阿里巴巴的实践:携手走向下一代云原生微服务 https://mp.weixin.qq.com/s/_Ug3yEh9gz5mLE_ag1DjwA ↩
89. 2012-11 阿里巴巴分布式服务框架 Dubbo 团队成员梁飞专访 http://www.iteye.com/magazines/103 ↩
90. 2019-01 Dubbo 作者梁飞亲述:那些辉煌、沉寂与重生的故事 https://www.infoq.cn/article/3F3Giujjo-QwSw2wEz7u ↩
91. Dubbo 用户案例:阿里巴巴升级 Dubbo3 全面取代 HSF2 https://cn.dubbo.apache.org/zh-cn/users/alibaba/ https://github.com/apache/dubbo-website/blob/ee727525aa61d68b397cc6ddedb322001f0ca4da/content/zh/users/alibaba.md ↩
92. 2023-01 阿里巴巴升级 Dubbo3 全面取代 HSF2 https://cn.dubbo.apache.org/zh-cn/blog/2023/01/16/%e9%98%bf%e9%87%8c%e5%b7%b4%e5%b7%b4%e5%8d%87%e7%ba%a7-dubbo3-%e5%85%a8%e9%9d%a2%e5%8f%96%e4%bb%a3-hsf2/ ↩
93. 2017-11 阿里林清山隆基:阿里消息中间件架构演进之路:notify和metaq https://zhuanlan.zhihu.com/p/302600352 ↩
94. 2013-07 淘宝张乐伟韩彰:淘宝消息中间件技术演变:MetaQ 1.0、MetaQ 2.0、MetaQ 3.0(sildes, 30p)https://www.modb.pro/doc/109298 ↩
95. 2017-03 阿里冯嘉鼬神:Apache RocketMQ背后的设计思路与最佳实践 https://developer.aliyun.com/article/71889 ↩
96. 2008-05 LinkedIn - A Professional Network built with Java Technologies and Agile Practices (JavaOne2008, slides) https://www.slideshare.net/linkedin/linkedins-communication-architecture ↩
97. 2015-07 Josh Clemm: A Brief History of Scaling LinkedIn https://engineering.linkedin.com/architecture/brief-history-scaling-linkedin https://www.slideshare.net/joshclemm/how-linkedin-scaled-a-brief-history ↩
98. Aditya Auradkar, Chavdar Botev, etc.: Data Infrastructure at LinkedIn. ICDE 2012: 1370-1381,dblp, semanticscholar, slides ↩
99. 2012-06 Sid Anand: LinkedIn Data Infrastructure Slides (Version 2) (slides) https://www.slideshare.net/r39132/linkedin-data-infrastructure-slides-version-2-13394853 ↩
100. 2021-12 Evolving LinkedIn’s analytics tech stack https://engineering.linkedin.com/blog/2021/evolving-linkedin-s-analytics-tech-stack ↩
101. 2010-05 Aditya Agarwal: Scale at Facebook (Qcon London 2010) https://www.infoq.com/presentations/Scale-at-Facebook/ ↩
102. 2008-11 Aditya Agarwal: Facebook architecture (QCon SF 2008, slides, 34p) https://www.slideshare.net/mysqlops/facebook-architecture https://www.infoq.com/presentations/Facebook-Software-Stack/ ↩
103. 2011-04 Michaël Figuière: What is Facebook’s architecture? https://qr.ae/pKYg12 ↩
104. 2013-10 Where does Facebook use C++? https://qr.ae/pKCIee ↩
105. 2022-07 Programming languages endorsed for server-side use at Meta https://engineering.fb.com/2022/07/27/developer-tools/programming-languages-endorsed-for-server-side-use-at-meta/ ↩
106. 2015-07 Instagram: Search Architecture https://instagram-engineering.com/eeb34a936d3a ↩
107. Guoqiang Jerry Chen, Janet L. Wiener, etc.: Realtime Data Processing at Facebook. SIGMOD Conference 2016: 1087-1098,dblp, semanticscholar ↩
108. 2021-10 XStream: stream processing platform at facebook (Flink Forward Global 2021, slides) https://www.slideshare.net/aniketmokashi/xstream-stream-processing-platform-at-facebook ↩
109. 2013-01 Johan Oskarsson: The Twitter stack https://blog.oskarsson.nu/post/40196324612/the-twitter-stack ↩
110. 2013-10 Chris Aniszczyk: Evolution of The Twitter Stack (slides) https://www.slideshare.net/caniszczyk/twitter-opensourcestacklinuxcon2013 ↩
111. 2017-01 The Infrastructure Behind Twitter: Scale https://blog.twitter.com/engineering/en_us/topics/infrastructure/2017/the-infrastructure-behind-twitter-scale ↩
112. 2010-07 Cassandra at Twitter Today https://blog.twitter.com/engineering/en_us/a/2010/cassandra-at-twitter-today ↩
113. 2011-12 How Twitter Stores 250 Million Tweets a Day Using MySQL http://highscalability.com/blog/2011/12/19/how-twitter-stores-250-million-tweets-a-day-using-mysql.html ↩
114. 2013-11 Michael Busch: Search at Twitter (Lucene Revolution 2013, slides) https://www.slideshare.net/lucenerevolution/twitter-search-lucenerevolutioneu2013-copy https://www.youtube.com/watch?v=AguWva8P_DI ↩
115. 2022-10 Stability and scalability for search https://blog.twitter.com/engineering/en_us/topics/infrastructure/2022/stability-and-scalability-for-search ↩
116. 2021-10 Processing billions of events in real time at Twitter https://blog.twitter.com/engineering/en_us/topics/infrastructure/2021/processing-billions-of-events-in-real-time-at-twitter- ↩]]>
在“开源”(open source)一词出现之前,技术社区的黑客选择使用“自由软件”(free software)这个词。但是“自由软件”这个词与对知识产权的敌意、共产主义和其它观点相联系,几乎不受管理者和投资者的欢迎,于是 1998 年 2 月 3 日在由 Eric Raymond 等人参加的会议上“开源”一词诞生,2 月下旬开源软件促进会成立 OSI,Eric Raymond 担任主席。自由软件和开源软件被合称为 FOSS。缩写“LAMP”代表的是 Linux-Apache-MySQL-PHP,这些软件都是自由软件或开源软件。
在互联网诞生早期,开源技术栈、开源社区尚未成熟,互联网公司不得不自研专有软件,随着开源软件的成熟,技术栈的选择开始从专有软件逐渐转向开源软件。先来看下,主要 Web 技术和服务端技术的发展时间线:
按编程语言区别,主要流行的互联网产品的创建时间和早期的技术栈选择(也可以参见 wiki):
值得一提的是,PHP 之父 Rasmus Lerdorf,在 2002 年至 2009 年期间为供职于 Yahoo!,2011 年起至今供职于 Etsy。Python 之父 Guido van Rossum,在 2005 年至 2012 年期间供职于 Google,在 2013 年至 2019 年期间供职于 Dropbox。国内最有影响力的 PHP 技术专家是惠新宸,是加入 PHP 语言官方开发组的首位国人,曾先后供职于雅虎中国、百度、新浪微博、链家网等公司。
2000 年前创建的网站,因为开源技术栈尚未成熟,编程语言基本上都是选择 C++ 开发。在开源技术栈成熟后,多数网站会选择拥抱开源。具体选择哪个编程语言,PHP、Ruby、Python、Java 等,主要由技术负责人的技术偏好决定。根据 w3techs 统计,历年网站使用的服务端编程语言统计占比,2012 年至今 10 多年,PHP 占比稳居榜首,每年都是 75% 以上。大型网站都是公司内的技术团队研发的,技术栈由技术团队选择,而很多小型网站,很可能会直接使用开源的 CMS 系统搭建。根据 w3techs 统计,前 100 万网站中有 43.0% 使用 WordPress 构建。WordPress 的服务端编程语言就是 PHP,数据库是 MySQL。大型网站选择 PHP 越来越少,因为 PHP 是解释型脚本语言,相对编译型编程语言有性能劣势。另外,PHP 的优势是快速开发 Web 动态网页,但是随着前后端分离开发模式的流行,Web 页面从服务端渲染逐渐转向客户端渲染,PHP 的优势不再,后端工程师只需要向前端提供 REST API 接口,展示层的实现完全由前端工程师实现。
早期部分网站选择 .NET 技术栈,比如京东、携程。但是,Java 平台生态更完善,有非常多的经验可以借鉴。另外,.NET 平台本身虽然不收费,但是 Windows 操作系统是收费的,开发工具也不便宜。于是京东在 2012 年从 .NET 迁移到 Java,携程在 2017 左右从 .NET 迁移到 Java。目前在国内,多数互联网大厂都选择 Java 技术栈,如淘宝、美团、京东、微博、携程等,Java 相对来说是主流选择。
编程语言的另外一个流行趋势是 Go 语言。Go 语言诞生于 Google,在 2009 年 11 月对外公开。在发明 Go 语言前,Google 内部主要使用的语言是 C++、Java 和 Python 等,但是 Go 发明者认为这些语言无法同时满足高效编译、高效执行和易于编程的特性诉求,所以创造了 Go 语言48。Go 比 C++ 能更高效编译和易于编程,比 Java 更易于编程,比 Python 能更高效执行。在 GoCon Tokyo 2014 会议上,Go 语言研发团队的 Brad Fitzpatrick 对各种编程语言在编程乐趣和执行速度(Fun vs. Fast)的对比49,如下图所示:
早期由 Go 语言实现的经典开源项目主要是基础设施软件,比如 YouTube 的 Vitess 数据库分片中间件(2012.02 对外开源)、Docker 容器(2013.03 对外开源)、Red Hat 的 CoreOS 团队的 Etcd 分布式配置服务(2013.07 对外开源,实现 Raft 共识协议)、前 Google 工程师创建的 CockroachDB 分布式数据库(2014.02 对外开源,受 Google Spanner 启发)、Google 的 Kubernetes 容器编排系统(2014.06 对外开源)、SoundCloud 的 Prometheus 监控工具(2015.01 对外开源)等。大规模使用 Go 语言的代表性的国外的互联网公司有 SoundCloud1920、Dropbox3031、Uber38 等,国内的有七牛云、字节跳动4041、哔哩哔哩12、滴滴16、知乎39、百度5051、腾讯52等,在这些互联网公司 Go 语言通常主要被用于构建基础设施软件(网关、数据存储、监控、视频处理等)或高性能要求的业务服务,部分公司的大量核心业务也从 Python、PHP 等迁移到 Go 语言。使用 Go 语言的公司的更加完整的列表,可以参见官方整理的“GoUsers”。
数据库方面,根据 DB-Engines Ranking 的统计,流行的关系数据库主要是 4 个,开源免费的 MySQL 和 PostgreSQL,专有收费的 Oracle 和 Microsoft SQL Server。多数互联网产品使用的数据库是 MySQL,部分是 PostgreSQL 或 Oracle。一些早期使用 Oracle 数据库的网站选择部分或完全从 Oracle 数据库中迁出,代表性的案例是,Amazon 从 Oracle 完全迁移到 Amazon RDS 和 NoSQL,淘宝从 Oracle 完全迁移到 MySQL。以 PostgreSQL 为主数据库的互联网应用有 Skype、Reddit、Spotify、Disqus、Heroku、Instagram 等53。
MySQL 相对 PostgreSQL 更加流行的主要原因是5455,在早期 MySQL 入门槛更低。MySQL 就支持在 Windows 下安装,而 PostgreSQL 只能在 Cygwin 下安装,MySQL 对 Windows 平台的支持使得初学者更容易上手,并且 MySQL 更加易于管理,能够快速简单地启动和使用,而且 MySQL 拥有一个非常简洁、易于导航和用户友好的在线文档和支持社区。另外一个重要原因是,MySQL 很早就默认支持复制(replication),而 PostgreSQL 的复制是第三方的,而且极其难用。之后的几年即便 PostgreSQL 完善了不足,但错过了流行的时间窗口,MySQL 成为了主流选择,有更完善的生态。不过,受 MySQL 被 Oracle 公司收购的影响,近几年 PostgreSQL 开始越来越流行,根据 DB-Engines Ranking 的统计,最新的 PostgreSQL 的流行度分数是 10 年前的三倍,流行度与前三名越来越接近。
容易发现多数网站的技术栈的演进模式类似。在创建网站早期通常选择使用 PHP、Ruby、Python 脚本语言和框架来开发,这些脚本语言和框架具有更快的开发效率,能快速交付上线。随着网站流量的增长,面临性能和可扩展性问题,通常会将网站服务化,从单体架构向面向服务的 SOA 和微服务架构演进,一大部分网站在服务化过程中会选择将核心业务改由其他性能更佳的编译型编程语言来实现,如 Java、Scala、C++、Go,原先的脚本语言可能全部废弃,也可能仅用于前端展示层的 Web 动态页面渲染,比如 Facebook、Twitter 等。当然也有一部分网站,演进为 SOA 和微服务架构后,业务逻辑的实现一直以最初的脚本语言为主。
在架构微服务化后,很多网站实现的微服务之间采用基于 HTTP 协议的 REST 方式通信(或者使用的 RPC 框架支持跨语言 RPC 调用),各个微服务的实现在理论上编程语言不需要统一,可以根据该微服务的性能要求或负责该微服务的团队的技术偏好自由选择,所以真实世界的互联网公司内部各个业务子系统的技术栈可能不统一。但是内部技术栈不统一会带来额外的维护成本。另外,考虑技术栈的迁移重构成本,新的业务子系统使用新的技术栈,而有些旧的业务子系统很可能继续使用旧的技术栈。除了业务子系统外,很可能团队内部有自研的中间件、运维工具等,这些组件由中间件团队、运维团队研发,很可能会选择与业务系统不一样的技术栈。
注意,解决网站的可扩展性问题,不一定需要演进为服务化架构。架构服务化意味着将完整的单体服务按业务的功能领域做垂直拆分,而实际上在应用服务层可以通过部署多个相同副本的单体服务的方式实现系统的水平扩展,网站的可扩展性问题主要在数据存储层上。拆分应用服务的好处在于能实现组织团队的规模化。解决数据库的扩展性的策略有,数据复制(数据缓存、数据库读写分离)、数据垂直拆分、数据水平拆分(也叫数据分片,sharding)。数据库被拆分后,如果单个事务内的数据分散在多个节点就要解决分布式事务问题,但是实现分布式事务代价太大,通常的选择是牺牲一致性,仅满足最终一致性(BASE)。
没有拆分应用服务,始终采用单体架构的经典案例是 Instagram,2019 年 Instagram 在技术博客上有这样一段话37:
Our server app is a monolith, one big codebase of several million lines and a few thousand Django endpoints, all loaded up and served together. A few services have been split out of the monolith, but we don’t have any plans to aggressively break it up.
为了解决可扩展性问题,Instagram 在数据存储3536,对 PostgreSQL 数据库做了主从读写分离、数据垂直拆分和数据分片,并且使用了 NoSQL 数据库 Cassandra,用于存储 Feed 流等数据。类似的,Reddit 也是单体架构,在数据存储层做可扩展性改造,对 PostgreSQL 数据库做了垂直拆分,并且使用 Cassandra 数据库25。
Yahoo!,1994 年创立,早期网站操作系统是 FreeBSD,Web 服务器是自研的 Filo Server,数据库是自研的 DBM 文件,Web 动态页面脚本是自研的 yScript,业务逻辑编程语言是 C++。1996 年 Web 服务器改用 Apache HTTP Server,1999 年部分数据库改用 MySQL(同时也使用 Oracle 数据库),2002 年编程语言从 yScript 和 C++ 改为 PHP,操作系统也从 FreeBSD 逐渐转向 Linux565758。在 2002.10 的 PHPCon 2002 会议上,Yahoo! 工程师 Michael Radwin 介绍了 Yahoo! 从专有软件向开源软件转向的原因和演进过程,以及选择 PHP 而不选择 ASP、Perl、JSP 等其他技术的原因56。Yahoo! 转向开源软件的原因主要是,避免维护专有软件的成本,开源软件从早期的不成熟最终变得成熟,具有更好的性能,更容易与第三方软件集成,以及开源社区逐渐发展壮大。2005 年,Yahoo! 的技术架构如下图所示57:
2000 年 ~ 2010 年,Yahoo! 是世界上流量最大的网站,虽然期间被 Google 短暂超越,但随后又反超,一直到到 2010 年后才被 Google 彻底超越。2000 年前后 Yahoo! 转向到 MySQL 和 PHP,并在技术上助力 LAMP 生态的发展壮大,对 LAMP 技术栈的流行起到巨大的推动作用。值得一提的是,知名 MySQL 专家 Jeremy Zawodny,《高性能MySQL》2004 年第 1 版(豆瓣)的作者,在 1999.12 ~ 2008.06 期间为 Yahoo! 工程师;PHP 之父 Rasmus Lerdorf 在 2002.09 ~ 2009.11 期间为 Yahoo! 工程师。
Amazon,1994 年创立,早期是单服务、单数据库的单体架构,使用的编程语言是 C++,2000 年开始从单体架构向 SOA 和微服务架构演进,使用最多的语言变为 Java,C++ 被用在高性能要求的系统和底层基础设施组件。Amazon 架构演进过程如下图所示59:
Amazon.com 的技术栈演进过程:
当前 Amazon.com 网站的主要技术栈59606162:
eBay,1995 年创立,早期使用的编程语言是 Perl,1997 年迁移到 C++,2002 年迁移到 Java。eBay 的技术栈演进过程6667:
2016 年,eBay 的技术架构如下图所示69:
淘宝技术架构7576777879,是从 LAMP 架构演变而来的。在 2003 年 5 月最早上线时,淘宝是对购买得到的采用 LAMP 架构网站源码进行二次开发的网站。为了应对网站流量的增长,2003 年底数据库从 MySQL 切换到 Oracle,2004 年初编程语言从 PHP 迁移到 Java,同时硬件演变为 IBM 小型机和 EMC 高端硬件存储,依赖的基础设施被统称为“IOE”(IBM 小型机、Oracle 数据库、EMC 存储设备),这个架构被称为 2.0 架构。
2007 年底开始拆分第一个业务中心是用户中心(UIC,User Information Center),在 2008 年初上线。2008 年初,启动“千岛湖”项目,拆分出了交易中心(TC,Trade Center)和类目属性中心(Forest)。2008 年 10 月,为了打通淘宝网和淘宝商城(后来的天猫)的数据,启动“五彩石”项目,拆分出了店铺中心(SC,Shop Center)、商品中心(IC,Item Center)、评价中心(RC,Rate Center)。到 2009 年初,“五彩石”项目结束,阿里电商架构也从之前的单体架构演进为了 SOA 服务化架构,被称为 3.0 架构,同时淘宝网和淘宝商城也完成了数据整合,并淘宝网和淘宝商城的共享业务沉淀到多个业务中心。在 2009 年,共享业务事业部成立,在组织架构上共享业务事业部和淘宝、天猫平级。在业务中心的上层业务服务有,交易管理(TM,Trade Manager)、商品管理(IM,Item Manager)、店铺系统(ShopSystem)、商品详情(Detail)、评价管理(RateManager)等。2009 年淘宝的服务化拆分如下图所示77:
在各个应用服务拆分的同时,数据库也按业务功能做了垂直拆分,2008 年 Oracle 被拆分为商品、交易、用户、评价、收藏夹等 10 来套。当时的淘宝的分布式架构设计,包括 Java + Oracle 技术栈的选择,以及应用服务和数据库的拆分策略,很大程度上参考的是 eBay 的架构7578。对照 eBay 架构师在 2006 年 11 月对外分享的 eBay 架构的 slides66,容易发现两者大同小异。
因为使用 Oracle 成本太高,2008 年 8 月淘宝数据库设计了异构数据库读写分离架构,写库为集中式的 Oracle,读库使用分库分表的 MySQL,该数据库架构方案基于自研的数据库中间件 TDDL 实现80。之后的几年,数据库逐渐向 MySQL 迁移。2008 年 9 月前微软亚洲研究院常务副院长王坚博士加盟阿里巴巴集团,担任首席架构师,2009 年 11 月时任阿里 CTO 的王坚博士决策启动阿里“去IOE”工程。淘宝的“去IOE”工程的关键时点81:
值得注意的是,在 2011 年淘宝商品库和交易库“去IOE”的同时,为了满足数据库的性能要求,底层硬件从 HDD 机械硬盘改为 SSD 固态硬盘。而庆幸的是,当时的前几年正好是 SSD 大爆发的时间点82。在消费级 SSD 市场,2010 年苹果的 MacBook Air 开始全面改用 SSD,2012 年苹果的 MacBook Pro 开始全面改用 SSD。在数据库服务器层面,因为 SSD 在随机读写的性能具有巨大优势,2010 年开始机械硬盘改用 SSD 成为趋势,当时的 MySQL 新版代码也专门针对 SSD 做了性能优化83。
2009 年初淘宝演变为 SOA 架构后,服务拆分粒度越来越细,到 2009 年底拆分出的总服务数达到 187 个,到 2010 年底达到 329 个77。因为系统依赖关系越来越复杂,当时最大问题是稳定性问题,系统的稳定性建设成为重点工作。到 2013 年阿里电商系统开始 4.0 架构改造,即异地多活架构改造,内部称为单元化项目。2013 年 8 月完成杭州同城双活,2014 年双 11 前的 10 月完成杭州和上海近距离两个单元的异地双活,2015 年双 11 完成三地四单元的异地多活7679,至此也完成了 3.0 到 4.0 架构的升级。在阿里电商系统迁移到阿里云公共云方面,2015 年阿里电商系统开始采用混合云弹性架构,当阿里本地保有云无法支撑时,就快速在公有云上扩建新的单元,当流量过去后,再还资源给公有云。2015 年 10% 的双 11 大促流量使用了阿里云的机器来支撑,2016 年 50% 以上的双 11 大促流量使用了阿里云的机器来支撑76。2019 年初,阿里电商开启了全面上公共云的改造,到 2019 年双 11,阿里电商系统实现了全部核心应用上公共云,2021 年双 11,阿里电商系统实现了 100% 上公共云。
2016 年,阿里产品专家倪超对外介绍的阿里电商技术架构,如下图所示86:
类似的,阿里巴巴中间件首席架构师《企业IT架构转型之道》(豆瓣)作者钟华在 2017 年对外介绍的阿里电商技术架构图87:
2017 年,阿里中间件技术部专家谢吉宝对外介绍的阿里中间件技术大图79:
阿里 HSF 与 Dubbo 的历史演进时间线:
阿里 RocketMQ 的历史演进时间线:
LinkedIn,2003 年创立,采用纯 Java 技术栈,早期是单体架构,到 2008 年演化为 SOA 架构96。到 2010 年拆分的服务数超过 150 个,到 2015 年拆分的服务数超过 750 个97。
2008 年,LinkedIn 对外介绍的技术架构如下图所示96:
2012 年,LinkedIn 对外介绍的关注在数据基础设施上的技术架构图98:
Facebook,2004 年创立,早期采用 LAMP 技术栈,为了应对负载增长开始服务化,将核心业务从 LAMP 中移出到新的服务,新的服务改用使用 C++、Java 等编写。为了解决 PHP 进程与非 PHP 进程的 RPC 通信问题,2006 年内部研发了跨语言的 RPC 库 Thrift,2007.04 对外开源。PHP 代码用于实现前端展示层的 Web 动态页面渲染,以及对服务层的数据聚合。
2010 年,Facebook 工程师 Aditya Agarwal 对外介绍的 Facebook 技术架构如下图所示101:
Facebook 的技术栈图102:
Facebook 的 NewsFeed 服务的架构图102:
Facebook 的 Search 服务的架构图102:
Twitter,2006 年创立,早期采用 Ruby on Rails 技术栈,数据库是 MySQL,2009 年开始服务化,将核心业务从 Ruby on Rails 中移出到新的服务,新的服务用 Scala、Java 编写109110。
2013 年,Twitter 工程师总结的 Twitter 从单体架构向分布式架构演进的过程,如下图所示110:
1. 2007 Jeff Dean: Software Engineering Advice from Building Large-Scale Distributed Systems (Stanford CS295 class lecture, Spring, 2007) https://research.google.com/people/jeff/stanford-295-talk.pdf ↩
2. 2008-11 Google Architecture http://highscalability.com/blog/2008/11/22/google-architecture.html ↩
3. 2008-06 新浪夏清然、徐继哲:自由软件和新浪网. 程序员 2008年第6期54-55 http://lib.cqvip.com/Qikan/Article/Detail?id=27523653 http://www.zeuux.com/blog/content/3620/ ↩
4. 2016-01 微信张文瑞:从无到有:微信后台系统的演进之路 https://www.infoq.cn/article/the-road-of-the-growth-weixin-background ↩
5. 2008-02 Yandex Architecture http://highscalability.com/yandex-architecture ↩
6. 2007-08 Wikimedia architecture http://highscalability.com/wikimedia-architecture ↩
7. 2007-11 Flickr Architecture http://highscalability.com/flickr-architecture ↩
8. 2016-03 What does Etsy’s architecture look like today? http://highscalability.com/blog/2016/3/23/what-does-etsys-architecture-look-like-today.html ↩
9. 2011-05 新浪刘晓震:新浪博客应用架构分享 (PHPChina 2011) https://web.archive.org/web/0/http://www.phpchina.com/?action-viewnews-itemid-38418 https://www.modb.pro/doc/121035 ↩
10. 2013-11 百度张东进:百度PHP基础设施构建思路 (QCon上海2013, slides, 30p) https://www.modb.pro/doc/121042 ↩
11. 2013-05 The Tumblr Architecture Yahoo Bought for a Cool Billion Dollars http://highscalability.com/blog/2013/5/20/the-tumblr-architecture-yahoo-bought-for-a-cool-billion-doll.html ↩
12. 2017-06 B站任伟:B站高性能微服务架构 https://zhuanlan.zhihu.com/p/33247332 ↩
13. 2021-05 微博刘道儒:十年三次重大架构升级,微博应对“极端热点”的进阶之路 https://www.infoq.cn/article/qgwbh0wz5bvw9apjos2a ↩
14. 2012-08 腾讯张松国:腾讯微博架构介绍 (ArchSummit深圳2012, slides, 59p) https://www.slideshare.net/dleyanlin/08-13994311 ↩
15. 2016-02 美团夏华夏:从技术细节看美团架构 (ArchSummit北京2014) https://www.infoq.cn/article/see-meituan-architecture-from-technical-details http://bj2014.archsummit.com/node/596/ https://www.modb.pro/doc/8311 ↩
16. 2019-06 滴滴杜欢:大型微服务框架设计实践 (Gopher China 2019) https://www.infoq.cn/article/EfOlY8_rubh4LfoXzF8B https://www.modb.pro/doc/35485 ↩
17. 2018-08 E-Commerce at Scale: Inside Shopify’s Tech Stack - Stackshare.io https://shopify.engineering/e-commerce-at-scale-inside-shopifys-tech-stack ↩
18. 2013-03 Phil Calçado @ SoundCloud: From a monolithic Ruby on Rails app to the JVM (slides, 75p) https://www.slideshare.net/pcalcado/from-a-monolithic-ruby-on-rails-app-to-the-jvm ↩
19. 2012-07 Go at SoundCloud https://developers.soundcloud.com/blog/go-at-soundcloud ↩
20. 2014-04 Peter Bourgon @ SoundCloud: Go: Best Practices for Production Environments (GopherCon 2014) http://peter.bourgon.org/go-in-production/ ↩
21. 2009-04 Heroku - Simultaneously Develop and Deploy Automatically Scalable Rails Applications in the Cloud http://highscalability.com/blog/2009/4/24/heroku-simultaneously-develop-and-deploy-automatically-scala.html ↩
22. 2021-07 GitHub’s Journey from Monolith to Microservices https://www.infoq.com/articles/github-monolith-microservices/ ↩
23. 2017-12 Building Services at Airbnb, Part 1 https://medium.com/airbnb-engineering/c4c1d8fa811b ↩
24. 2020-11 Jessica Tai @ Airbnb: How to Tame Your Service APIs: Evolving Airbnb’s Architecture https://www.infoq.com/presentations/airbnb-api-architecture/ ↩
25. 2013-08 Reddit: Lessons Learned from Mistakes Made Scaling to 1 Billion Pageviews a Month http://highscalability.com/blog/2013/8/26/reddit-lessons-learned-from-mistakes-made-scaling-to-1-billi.html ↩
26. 2012-03 7 Years of YouTube Scalability Lessons in 30 Minutes http://highscalability.com/blog/2012/3/26/7-years-of-youtube-scalability-lessons-in-30-minutes.html ↩
27. 2016-04 豆瓣田忠博:豆瓣的服务化体系改造 (QCon北京2016, slides, 37p) http://2016.qconbeijing.com/presentation/2834/ ↩
28. 2010-09 Disqus: Scaling the World’s Largest Django App (DjangoCon 2010, slides) https://www.slideshare.net/zeeg/djangocon-2010-scaling-disqus ↩
29. 2021-05 Dropbox: Atlas: Our journey from a Python monolith to a managed platform https://dropbox.tech/infrastructure/atlas--our-journey-from-a-python-monolith-to-a-managed-platform ↩
30. 2014-07 Dropbox: Open Sourcing Our Go Libraries https://dropbox.tech/infrastructure/open-sourcing-our-go-libraries ↩
31. 2017-07 Tammy Butow @ Dropbox: Go Reliability and Durability at Dropbox https://about.sourcegraph.com/blog/go/go-reliability-and-durability-at-dropbox-tammy-butow ↩
32. 2011-02 Adam D’Angelo: Why did Quora choose C++ over C for its high performance services? Does the Quora codebase use a limited subset of C++? https://qr.ae/pKwCE3 ↩
33. 2012-02 A Short on the Pinterest Stack for Handling 3+ Million Users http://highscalability.com/blog/2012/2/16/a-short-on-the-pinterest-stack-for-handling-3-million-users.html ↩
34. 2015-08 Finagle and Java Service Framework at Pinterest (slides) https://www.slideshare.net/yongshengwu/finaglecon2015pinterest ↩
35. 2013-03 Instagram 5位传奇工程师背后的技术揭秘 https://web.archive.org/web/0/http://www.csdn.net/article/2013-03-28/2814698-The-technologie-%20behind-Instagram ↩
36. 2017-03 Lisa Guo: Scaling Instagram Infrastructure(QCon London 2017, 87p) https://www.infoq.com/presentations/instagram-scale-infrastructure/ ↩
37. 2019-08 Static Analysis at Scale: An Instagram Story https://instagram-engineering.com/8f498ab71a0c ↩
38. 2016-07 The Uber Engineering Tech Stack, Part I: The Foundation https://web.archive.org/web/0/https://eng.uber.com/tech-stack-part-one/ ↩
39. 2018-11 知乎社区核心业务 Golang 化实践 https://zhuanlan.zhihu.com/p/48039838 ↩
40. 2022-10 字节马春辉:字节大规模微服务语言发展之路 https://www.infoq.cn/article/n5hkjwfx1gxklkh8iham ↩
41. 2021-07 字节成国柱:字节跳动微服务架构体系演进 https://mp.weixin.qq.com/s/1dgCQXpeufgMTMq_32YKuQ ↩
42. Why does Google use Java instead of Python for Gmail? https://qr.ae/pKkduC ↩
43. 2011-07 揭秘Google+技术架构 http://www.infoq.com/cn/news/2011/07/Google-Plus ↩
44. 2009-12 人人网架构师张洁:人人网使用的开源软件列表 https://web.archive.org/web/0/http://ugc.renren.com/2009/12/13/a-list-of-open-source-software-in-renren ↩
45. 2018-12 Netflix OSS and Spring Boot — Coming Full Circle https://netflixtechblog.com/4855947713a0 ↩
46. 携程为什么突然技术转型从 .NET 转 Java? https://www.zhihu.com/question/56259195 ↩
47. 京东技术解密,2014,豆瓣:12 少年派的奇幻漂流——从.Net到Java ↩
48. Golang Frequently Asked Questions: Why did you create a new language? https://go.dev/doc/faq#creating_a_new_language ↩
49. 2014-05 Brad Fitzpatrick: Go: 90% Perfect, 100% of the time: Fun vs. Fast https://go.dev/talks/2014/gocon-tokyo.slide#28 ↩
50. 2021-08 百度Geek说:短视频go研发框架实践 https://my.oschina.net/u/4939618/blog/5191598 ↩
51. 2023-07 百度Geek说:从 php5.6 到 golang1.19 - 文库 App 性能跃迁之路 https://my.oschina.net/u/4939618/blog/10086661 ↩
52. 2022-03 2021年,腾讯研发人员增长41%,Go首次超越C++成为最热门语言 https://mp.weixin.qq.com/s/zj-DhASG4S-3z56GTYjisg ↩
53. 2020-05 Which Major Companies Use PostgreSQL? What Do They Use It for? https://learnsql.com/blog/companies-that-use-postgresql-in-business/ ↩
54. 2009-05 Why is MySQL more popular than PostgreSQL? https://news.ycombinator.com/item?id=619871 ↩
55. Why is MySQL more popular than PostgreSQL? https://qr.ae/pKPJcE ↩
56. 2002-10 Michael Radwin: Making the Case for PHP at Yahoo! (PHPCon 2002, slides) https://web.archive.org/web/0/http://www.php-con.com/2002/view/session.php?s=1012 https://speakerdeck.com/yulewei/making-the-case-for-php-at-yahoo ↩
57. 2005-10 Michael Radwin: PHP at Yahoo! (PHP Conference 2005, slides) https://speakerdeck.com/yulewei/php-at-yahoo-zend2005 ↩
58. 2007-06 Federico Feroldi: PHP in Yahoo! (slides) https://www.slideshare.net/fullo/federico-feroldi-php-in-yahoo ↩
59. 2021-02 AWS re:Invent 2020: Amazon.com’s architecture evolution and AWS strategy https://www.youtube.com/watch?v=HtWKZSLLYTE ↩
60. What programming languages are used at Amazon? https://qr.ae/pKFwnw ↩
61. 2011-04 Charlie Cheever: How did Google, Amazon, and the like initially develop and code their websites, databases, etc.? https://qr.ae/pKKyB0 ↩
62. 2016-04 Is Amazon still using Perl Mason to render its content? https://qr.ae/pKFwFm ↩
63. 2019-10 Migration Complete – Amazon’s Consumer Business Just Turned off its Final Oracle Database https://aws.amazon.com/blogs/aws/migration-complete-amazons-consumer-business-just-turned-off-its-final-oracle-database/?nc1=h_ls ↩
64. Amazon RDS for PostgreSQL customers https://aws.amazon.com/rds/postgresql/customers/?nc1=h_ls ↩
65. Amazon ElastiCache for Redis customers https://aws.amazon.com/elasticache/redis/customers/?nc1=h_ls ↩
66. 2006-11 Randy Shoup & Dan Pritchett: The eBay Architecture (SDForum2006, slides, 37p) http://www.addsimplicity.com/downloads/eBaySDForum2006-11-29.pdf ↩
67. 2007-05 eBay Architecture http://highscalability.com/blog/2008/5/27/ebay-architecture.html ↩
68. 2011-10 Tony Ng: eBay Architecture (slides, 46p) https://www.slideshare.net/tcng3716/ebay-architecture ↩
69. 2016-11 Ron Murphy: Microservices at eBay (slides, 20p) https://www.modb.pro/doc/120378 ↩
70. 2020-09 High Efficiency Tool Platform for Framework Migration https://innovation.ebayinc.com/tech/engineering/high-efficiency-tool-platform-for-framework-migration/ ↩
71. 2023-10 BES2:打造eBay下一代高可靠消息中间件 https://mp.weixin.qq.com/s/ThhkO1WM7ck1WO8RqjTCJA ↩
72. 2013-06 Cassandra at eBay - Cassandra Summit 2013 (slides) https://www.slideshare.net/jaykumarpatel/cassandra-at-ebay-cassandra-summit-2013 ↩
73. 2017-03 Sudeep Kumar: ‘Elasticsearch as a Service’ at eBay https://www.elastic.co/elasticon/conf/2017/sf/elasticsearch-as-a-service-at-ebay ↩
74. 2016-08 How eBay Uses Apache Software to Reach Its Big Data Goals https://www.linux.com/news/how-ebay-uses-apache-software-reach-its-big-data-goals/ ↩
75. 淘宝技术这十年,子柳,2013,豆瓣 ↩
76. 尽在双11:阿里巴巴技术演进与超越,2017,豆瓣:第1章 阿里技术架构演进 ↩
77. 2011-06 淘宝吴泽明范禹:淘宝业务发展及技术架构 (slides, 43p) https://www.modb.pro/doc/116697 ↩
78. 2014-12 阿里云王宇德、张文生:淘宝迁云架构实践 https://web.archive.org/web/1/http://www.csdn.net/article/a/2014-12-09/15821474 ↩
79. 2017-07 阿里谢吉宝唐三:阿里电商架构演变之路 (首届阿里巴巴中间件技术峰会, slides, 33p) https://www.modb.pro/doc/121185 ↩
80. 2010-11 淘宝赵林丹臣:淘宝数据库架构演进历程 (iDataForum2010, slides, 38p) https://www.slideshare.net/ssuser1646de/ss-10163048 ↩
81. 2019-10 阿里刘振飞:十年磨一剑:从2009启动“去IOE”工程到2019年OceanBase拿下TPC-C世界第一 https://mp.weixin.qq.com/s/7B6rp17XVhpAWZr1-6DHqQ https://developer.aliyun.com/article/722414 ↩
82. 2016-02 SSD的30年发展史 https://mp.weixin.qq.com/s/JsHKFilB5fvLY9V9z_xsXw ↩
83. 2010-04 Yoshinori Matsunobu: SSD Deployment Strategies for MySQL (slides, 52p) https://www.slideshare.net/matsunobu/ssd-deployment-strategies-for-mysql ↩
84. 2020-04 阿里王剑英、和利:淘宝万亿级交易订单背后的存储引擎 https://mp.weixin.qq.com/s/MkX1Pr8tERrzK29XG9zMUQ https://help.aliyun.com/zh/rds/apsaradb-rds-for-mysql/storage-engine-that-processes-trillions-of-taobao-order-records ↩
85. 2022-03 阿里云罗庆超:阿里巴巴集团上云之 TFS 迁移 OSS 技术白皮书 https://developer.aliyun.com/article/876301 ↩
86. 2016-12 阿里倪超:阿里巴巴Aliware十年微服务架构演进历程中的挑战与实践 https://www.sohu.com/a/121588928_465959 ↩
87. 2017-10 阿里钟华古谦:企业IT架构转型之道 (杭州云栖大会2017, slides, 18p) https://www.modb.pro/doc/121695 ↩
88. 2021-09 阿里郭浩:Dubbo 和 HSF 在阿里巴巴的实践:携手走向下一代云原生微服务 https://mp.weixin.qq.com/s/_Ug3yEh9gz5mLE_ag1DjwA ↩
89. 2012-11 阿里巴巴分布式服务框架 Dubbo 团队成员梁飞专访 http://www.iteye.com/magazines/103 ↩
90. 2019-01 Dubbo 作者梁飞亲述:那些辉煌、沉寂与重生的故事 https://www.infoq.cn/article/3F3Giujjo-QwSw2wEz7u ↩
91. Dubbo 用户案例:阿里巴巴升级 Dubbo3 全面取代 HSF2 https://cn.dubbo.apache.org/zh-cn/users/alibaba/ https://github.com/apache/dubbo-website/blob/ee727525aa61d68b397cc6ddedb322001f0ca4da/content/zh/users/alibaba.md ↩
92. 2023-01 阿里巴巴升级 Dubbo3 全面取代 HSF2 https://cn.dubbo.apache.org/zh-cn/blog/2023/01/16/%e9%98%bf%e9%87%8c%e5%b7%b4%e5%b7%b4%e5%8d%87%e7%ba%a7-dubbo3-%e5%85%a8%e9%9d%a2%e5%8f%96%e4%bb%a3-hsf2/ ↩
93. 2017-11 阿里林清山隆基:阿里消息中间件架构演进之路:notify和metaq https://zhuanlan.zhihu.com/p/302600352 ↩
94. 2013-07 淘宝张乐伟韩彰:淘宝消息中间件技术演变:MetaQ 1.0、MetaQ 2.0、MetaQ 3.0(sildes, 30p)https://www.modb.pro/doc/109298 ↩
95. 2017-03 阿里冯嘉鼬神:Apache RocketMQ背后的设计思路与最佳实践 https://developer.aliyun.com/article/71889 ↩
96. 2008-05 LinkedIn - A Professional Network built with Java Technologies and Agile Practices (JavaOne2008, slides) https://www.slideshare.net/linkedin/linkedins-communication-architecture ↩
97. 2015-07 Josh Clemm: A Brief History of Scaling LinkedIn https://engineering.linkedin.com/architecture/brief-history-scaling-linkedin https://www.slideshare.net/joshclemm/how-linkedin-scaled-a-brief-history ↩
98. Aditya Auradkar, Chavdar Botev, etc.: Data Infrastructure at LinkedIn. ICDE 2012: 1370-1381,dblp, semanticscholar, slides ↩
99. 2012-06 Sid Anand: LinkedIn Data Infrastructure Slides (Version 2) (slides) https://www.slideshare.net/r39132/linkedin-data-infrastructure-slides-version-2-13394853 ↩
100. 2021-12 Evolving LinkedIn’s analytics tech stack https://engineering.linkedin.com/blog/2021/evolving-linkedin-s-analytics-tech-stack ↩
101. 2010-05 Aditya Agarwal: Scale at Facebook (Qcon London 2010) https://www.infoq.com/presentations/Scale-at-Facebook/ ↩
102. 2008-11 Aditya Agarwal: Facebook architecture (QCon SF 2008, slides, 34p) https://www.slideshare.net/mysqlops/facebook-architecture https://www.infoq.com/presentations/Facebook-Software-Stack/ ↩
103. 2011-04 Michaël Figuière: What is Facebook’s architecture? https://qr.ae/pKYg12 ↩
104. 2013-10 Where does Facebook use C++? https://qr.ae/pKCIee ↩
105. 2022-07 Programming languages endorsed for server-side use at Meta https://engineering.fb.com/2022/07/27/developer-tools/programming-languages-endorsed-for-server-side-use-at-meta/ ↩
106. 2015-07 Instagram: Search Architecture https://instagram-engineering.com/eeb34a936d3a ↩
107. Guoqiang Jerry Chen, Janet L. Wiener, etc.: Realtime Data Processing at Facebook. SIGMOD Conference 2016: 1087-1098,dblp, semanticscholar ↩
108. 2021-10 XStream: stream processing platform at facebook (Flink Forward Global 2021, slides) https://www.slideshare.net/aniketmokashi/xstream-stream-processing-platform-at-facebook ↩
109. 2013-01 Johan Oskarsson: The Twitter stack https://blog.oskarsson.nu/post/40196324612/the-twitter-stack ↩
110. 2013-10 Chris Aniszczyk: Evolution of The Twitter Stack (slides) https://www.slideshare.net/caniszczyk/twitter-opensourcestacklinuxcon2013 ↩
111. 2017-01 The Infrastructure Behind Twitter: Scale https://blog.twitter.com/engineering/en_us/topics/infrastructure/2017/the-infrastructure-behind-twitter-scale ↩
112. 2010-07 Cassandra at Twitter Today https://blog.twitter.com/engineering/en_us/a/2010/cassandra-at-twitter-today ↩
113. 2011-12 How Twitter Stores 250 Million Tweets a Day Using MySQL http://highscalability.com/blog/2011/12/19/how-twitter-stores-250-million-tweets-a-day-using-mysql.html ↩
114. 2013-11 Michael Busch: Search at Twitter (Lucene Revolution 2013, slides) https://www.slideshare.net/lucenerevolution/twitter-search-lucenerevolutioneu2013-copy https://www.youtube.com/watch?v=AguWva8P_DI ↩
115. 2022-10 Stability and scalability for search https://blog.twitter.com/engineering/en_us/topics/infrastructure/2022/stability-and-scalability-for-search ↩
116. 2021-10 Processing billions of events in real time at Twitter https://blog.twitter.com/engineering/en_us/topics/infrastructure/2021/processing-billions-of-events-in-real-time-at-twitter- ↩]]>
类 Unix 系统下的 I/O 操作,默认是阻塞 I/O(Blocking I/O,缩写为 BIO)。比如,当一个进程发出了读操作请求,但没有可访问的数据时,该进程通常会阻塞在内核中,直到出现可以访问的数据为止。然而,进程有时要处理对多个描述符的 I/O 操作,需要在多个文件描述符上阻塞,典型的场景是终端 I/O 和网络 I/O。
例如,有一个远程登录程序,它要从键盘读入数据然后把这些数据通过套接字发送到一个远程的计算机上。这个程序还需要从和远程终端相连接的套接字上读取数据,并将数据显示于屏幕上。如果进程在读键盘数据时阻塞,它就不能读那些从远程终端发送到屏幕上的数据。这样一来,在来自远程终端的更多数据到达之前,用户就不知道该通过键盘输入些什么,于是,死锁便产生了。相反的,如果进程在读从远程终端送来的数据时阻塞,它将不能读来自键盘终端的数据。
历史上,Unix 系统通过使用多个进程让应用能同时处理多个文件描述符,这些进程间可以通过管道或者是其他的进程间通信方法进行通信。然而,如果处理上下文切换的代价比处理输入的代价更大,那么这种方法就会导致巨大的系统开销,因为它要求在进程间进行频繁的上下文切换。并且,在一个进程内实现这种应用会显得比较直观。由于上述原因,BSD 提供了三种机制,允许对描述符进行多路 I/O 访问,非阻塞式 I/O、 I/O 多路复用和信号驱动 I/O[1]:
EAGAIN
或 EWOULDBLOCK
)。进程收到错误后,要么放弃,要么不停地轮询(polling),直至发现有描述符可以进行 I/O 操作为止。这种轮询的方法的问题在于,进程必须连续不断地运行,检查描述符是否就绪,很浪费 CPU 时间。类 Unix 系统下,默认的 I/O 操作都是阻塞 I/O。有两种方法可以将描述符设置非阻塞 I/O:(1) 如果是调用 open() 获得描述符,则可以在调用时设置 O_NONBLOCK
标志;(2) 对于已经打开的一个描述符,则可调用 fcntl(),由该函数为描述符设置 O_NONBLOCK
标志。另外,对于网络套接字的描述符,如果想在获得描述符时直接指定为非阻塞 I/O,可以在调用 socket() 或 accept() 时传入 SOCK_NONBLOCK
标志,当然也可以在获得描述符后,再调用 fcntl()
修改。
I/O 多路复用,最早是在 4.2BSD(1983.08)中由 select() 系统调用提供的。虽然该系统调用主要用于终端 I/O 和网络 I/O,但它对其他描述符同样是起作用的。poll()
是另外一个实现 I/O 多路复用的系统调用,和 select()
功能几乎相同。SVR3(1987)在增加 STREAMS 机制时增加了 poll() 系统调用。但在 SVR4 (1988)之前,poll()
只对 STREAMS 设备起作用。SVR4 开始支持对任意描述符起作用的 poll()
。select()
和 poll()
系统调用,都是在 POSIX.1-2001 开始标准化定义,然而从可移植性角度考虑,支持 select()
的系统比支持 poll()
的系统要多,所以在应用的实现上,相比于 poll()
基于 select()
实现更多。另外 POSIX 还定义了 pselect()
,它是能够处理信号阻塞并提供了更高时间分辨率的 select()
的增强版本。
在 Linux 系统下,poll()
系统调用从 2.1.23 版本(1997.01)开始提供,而 poll()
库函数由 libc 5.4.28(1997.05)开始提供。早期 Linux 内核未提供 poll()
系统调用,glibc 使用 select()
来模拟实现 poll()
。另外,Linux 还提供特有的 I/O 多路复用解决方案,即 epoll
,详细介绍参见下文。
为了能持续不断的监听 I/O 操作就绪事件,应用实现上需要循环调用 select()
或 poll()
。为了方便使用,封装各个不同的 I/O 多路复用函数的第三方库,通常会把这样的循环调用被抽象为事件循环(event loop),然后把 I/O 就绪事件的处理抽象成回调函数(callback)。最早的提供事件循环(event loop)抽象的典型的第三方库是 libevent 库(最早在 2002.04 发布)。
信号驱动 I/O,在描述符就绪时内核会发送 SIGIO
信号。但是信号驱动 I/O 对于 TCP 套接字近乎无用,问题在于 SIGIO
信号产生得过于频繁,并且它的出现并没有告诉我们发生了什么事件,无法区分触发信号的各种情况。在 UDP 上使用信号驱动式 I/O 没有上述问题。关于信号驱动 I/O 的详细阐述,可以参阅《UNIX网络编程 卷1》的第 25 章[2]。
select()
和 poll()
系统调用是在多个文件描述符中查找就绪(ready)的描述符。就绪条件具体指是什么呢?select()
的 man 文档,有如下描述(poll()
的就绪条件类似,不展开讨论):
A file descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2), or a sufficiently small write(2)) without blocking.
...
A file descriptor is ready for reading if a read operation will not block; in particular, a file descriptor is also ready on end-of-file.
A file descriptor is ready for writing if a write operation will not block. However, even if a file descriptor indicates as writable, a large write may still block.
针对网络套接字描述符的就绪条件,《UNIX网络编程 卷1》如下总结:
表中的“有数据可读”含义是,该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小。对这样的套接字执行读操作不会阻塞并将返回一个大于 0 的值(也就是返回准备好读入的数据)。接收低水位标记,可以通过调用 setsockopt() 的 SO_RCVLOWAT
选项来设置,默认值为 1。
表中的“有可用于写的空间”含义是,该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记的当前大小,并且或者该套接字已连接,或者该套接字不需要连接(如UDP套接字)。发送低水位标记,可以通过调用 setsockopt() 的 SO_SNDLOWAT
选项来设置,默认值为 1024。
表中的“关闭连接的读一半”和“关闭连接的写一半”含义是,套接字的 TCP 连接接收了关闭 FIN,此时会收到读就绪事件和写就绪事件。对这样的套接字做读操作将不阻塞并返回 0(也就是返回 EOF);对这样的套接字做写操作将产生 SIGPIPE
信号(Broken pipe: write to pipe with no readers)。
上文阐述的就是 Unix 系统的 4 种 I/O 模型,阻塞 I/O、非阻塞 I/O、I/O 多路复用和信号驱动式 I/O。另外,还有一种 I/O 模型是,异步 I/O(Asynchronous I/O,缩写为 AIO)。异步 I/O,由 POSIX 规范定义,工作机制是,告知内核启动某个操作,并让内核在整个操作(包括将数据从内核复制到我们自己的缓冲区)完成后通知进程。这种模型与信号驱动模型的主要区别在于:信号驱动 I/O 是由内核通知我们何时可以启动一个 I/O 操作,而异步 I/O 模型是由内核通知我们 I/O 操作何时完成。POSIX 定义的异步 I/O 的函数为,aio_write()
、aio_read()
等。
关于 POSIX 异步 IO,Linux 的 aio 的 man 文档,有如下说明:
The current Linux POSIX AIO implementation is provided in user space by glibc. This has a number of limitations, most notably that maintaining multiple threads to perform I/O operations is expensive and scales poorly. Work has been in progress for some time on a kernel state-machine-based implementation of asynchronous I/O (see io_submit(2), io_setup(2), io_cancel(2), io_destroy(2), io_getevents(2)), but this implementation hasn't yet matured to the point where the POSIX AIO implementation can be completely reimplemented using the kernel system calls.
本质上,Linux 下的 POSIX AIO 是在用户空间下用线程模拟实现的 AIO,并非真正的 AIO,性能很差,所以很少被使用。
Linux 内核实现的 AIO 是 io_submit、io_setup、io_getevents 等系统调用,也被成为“Linux Native AIO”,或者缩写为 KAIO(kernel AIO),从 Linux 2.5 开始支持(2001.11),这些系统调用对应的库函数由 libaio
库提供。但是 Linux Native AIO 几乎不可用,只适合以 O_DIRECT
方式做直接 IO(无缓存的 I/O)。如果真的实现了异步 AIO,io_submit
系统调用不应该阻塞,但是对缓存 I/O、网络访问、管道等,io_submit
会发生阻塞,整个操作将在 io_submit
系统调用期间执行,并且通过调用 io_getevents
,I/O 操作完成结果可以立即访问,这样也就破坏了异步 I/O 的目的[3] 。
最新的内核实现的 AIO 是 io_uring,已经被 Linux 5.1(2019.05)采纳。很多开源项目,比如 libevent、libuv、Nginx、Redis 等,都有打算支持或甚至已经支持 io_uring。io_uring 的杂类资料整理,可以参考“Awesome io_uring”[4]。本文主要关注 I/O 多路复用,io_uring 不再展开讨论。
《UNIX网络编程 卷1》对这 5 种 I/O 模型做了对比[2]:
可以看出,前 4 种模型的主要区别在于第一阶段(等待描述符就绪),因为它们的第二阶段是一样的:在数据从内核复制到调用者的缓冲区期间,进程阻塞于 recvfrom 调用。相反,异步 I/O 模型在这两个阶段都要处理,从而不同于其他 4 种模型。
各个 I/O 模型,用户空间的应用与内核空间的交互过程如下图所示(信号驱动 I/O 实际场景较少使用,所以忽略)[5]:
通常对“I/O 多路复用”术语的理解,其实就是特指,由 select() 、poll() 或类似的系统调用实现的在多个文件描述符中查找就绪状态描述符的技术。不过,根据 McKusick 书籍的描述[1],I/O 多路复用也可以泛指为,单个进程同时处理多个文件描述符的技术,与之相对立的技术是早期的由多个进程同时处理多个描述符的解决方案。广义理解的话,I/O 多路复用包括非阻塞 I/O、狭义的 I/O 多路复用、信号驱动式 I/O、异步 IO 等技术。
单独的“多路复用(multiplexing)”术语,维基百科的解释是,一个通信和计算机网络领域的专业术语,多路复用通常表示在一个信道上传输多路信号或数据流的过程和技术。
在概念上,阻塞 I/O 和非阻塞 I/O,是根据系统是否会阻塞进程的执行而区分的:
另外,POSIX 定义了同步 I/O(Synchronous I/O)和异步 I/O(Asynchronous I/O)两个术语[2]:
A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.
An asynchronous I/O operation does not cause the requesting process to be blocked.
按照这个定义,阻塞 I/O、非阻塞 I/O、I/O 多路复用和信号驱动 I/O 都是同步 I/O,因为其中真正的 I/O 操作将阻塞进程(第二阶段,将数据从内核复制到用户空间的缓冲区阶段)。只有异步 I/O 模型与 POSIX 定义的异步 I/O 相匹配。
不过,对于 I/O 多路复用是属于同步 I/O 还是异步 I/O 存在争议,不同视角下存在不同的理解。I/O 多路复用,单看等待 I/O 就绪阶段,其实是异步的。所以很多时候,I/O 多路复用,虽然没有实现真正的 POSIX 定义的异步 I/O,但也被归类为异步 I/O。比如,Jones 的文章[5],将 I/O 多路复用归类为阻塞的异步 I/O,将 POSIX AIO 归类为非阻塞的异步 I/O。类似的,封装各个不同的 I/O 多路复用函数的 libevent
库的官方文档[6]自称是“Asynchronous I/O”。Node.js
底层是基于 I/O 多路复用封装的 libuv 库,libuv
库也自称是“Asynchronous I/O”。另外,如果跳出 I/O 视角,从整体应用的执行流程角度看,基于 I/O 多路复用实现的应用,相对于在多个描述符列表上主轮询流程,在单个描述符上 I/O 事件的应用处理流程相对独立,可以认为是异步的。Node.js 文档对“异步”的解释如下[7]:
Asynchronous means that things can happen independently of the main program flow.
严格意义上,典型的 I/O 多路复用的应用是单进程单线程执行的,本质上都是串行执行的,是假异步。
世界上第一个 HTTP 服务器,CERN httpd,早期实现采用的 I/O 模型是阻塞 I/O,然后为了能同时处理多个客户端连接,会为每个客户端连接创建一个新的处理请求的子进程(process-per-connection),这种并发模式被称为 fork 模式,这也是传统的 Unix 服务器采用的并发模式。之后的版本,改为基于 select()
的 I/O 多路复用 + fork 模式(参见源码 HTDaemon.c)。
NCSA HTTPd,是早期的第一个流行的 HTTP 服务器,在版本 1.3 以及之前版本,也是采用阻塞 I/O + fork 模式实现,会为每个客户端连接创建一个新的子进程(参见源码 httpd.c)。之后 NCSA HTTPd 的 1.4 版本,I/O 模型改造为基于 select() 的 I/O 多路复用,进程模型改为“pre-forking”模式,但这种模式本质上还是每个客户端连接对应单个子进程(process-per-connection),“pre-forking”的优点是在创建新客户端连接时有预先创建的子进程直接处理请求(类似进程池),避免在创建新连接的同时执行创建子进程这样的重型操作,具体可以参见 1996.03 的官方文档的对“pre-forking”模型的性能测试[8](相关的实现源码参见 httpd.c)。
Apache HTTP Server,最早在 1995.04 对外公开发布首个版本 0.6.2,这个版本的代码基于 NCSA httpd 1.3[9]。 之后在 5 月和 6 月,Apache 也开始实现“pre-forking”特性,在版本 0.8.0 开始正式支持[9][10]。一直到 Apache HTTP Server 2.2,prefork 模式依然是 Unix 系统下的默认模式[11]。在 Apache 的 prefork 模式下,每个客户端连接由单独的子进程处理,这样的子进程被 Apache 称为 worker 进程,worker 进程数就是同时处理的客户端连接数。因为线程相对进程更加轻量,理论上每个客户端连接对应单个线程(thread-per-connection)更有优势。所以,在 2002.04,Apache 发布 2.0 的首个 GA 版本时,新增了 worker 模式,一种多进程和多线程混合的模式。在 Apache 的 worker 模式下,每个客户端连接由单独的子线程处理,这样的子线程也就是 worker 线程,worker 线程数就是同时处理的客户端连接数。
相对单线程,多进程或多线程的问题是,占用更多内存(每个线程都需要维护自己的线程栈),以及频繁的上下文切换。所以,有些 HTTP 服务器倾向于基于 select()
实现单进程单线程的网络服务器。这种模式实现的服务器一般会把循环调用 select()
的过程抽象为事件循环(event loop),把 I/O 操作就绪事件的处理抽象成回调函数(callback),所以也被称为事件驱动服务器(event-driven server)。多核 CPU 时,为了能充分使用 CPU 多核资源,事件驱动服务器的进程数(或线程数)通常为 CPU 核数。事件驱动模式,在软件架构中被也称为 Reactor 模式。基于事件并发和基于线程的并发的比较,可以阅读 John Ousterhout 的 1995 年的经典 slides:“Why Threads Are A Bad Idea (for most purposes)”[12]。
早期的典型的基于 select()
实现的单线程 HTTP 服务器的例子,是由 ACME 实验室开发并开源的 thttpd(1995.11 对外发布 1.0 版)。thttpd 服务器作者 Jef Poskanzer 在文章“Web Server Comparisons”(1998.07)[13]中对比了各个 Web 服务器。根据文章的对比,容易发现基于 select()
实现的单线程服务器,在响应性能和最大并发连接数上都占优,thttpd 支持的最大的每秒请求数 QPS 是 720,thttpd 支持的最大并发连接数是 1000+。
虽然在实验条件下表现良好,但是在真实场景下,基于 select()
实现的 HTTP 单线程服务器,性能并没有优于传统的基于 fork 模式的服务器[14]。Banga 等人经过分析后得出的主要原因是,当服务器同时处理的客户端连接数超过几千后,系统调用 select()
或 poll()
的性能很差,不具备可伸缩性。
最早的 HTTP 1.0 协议,在服务响应完成后连接会立即关闭,连接无法保持。HTTP 底层是 TCP 协议,建立 TCP 连接需要经过三次握手的过程。如果能复用 TCP 连接,同一个 HTTP 连接上的后续的 HTTP 请求就不用重新建立 TCP 连接,也就是能在同一个 HTTP 连接上支持多次 HTTP 请求和响应,这样 HTTP 性能也就得到了提高。于是,HTTP 1.1 协议(RFC 2068,1997.01)开始持久连接(persistent connection),默认让 HTTP 连接“keep-alive”。关于 HTTP 持久连接的详细介绍,可以参考 RFC 2068 的“8.1 Persistent Connections”。
HTTP 协议支持持久连接后,也带来了另外一个问题,就是出现大量的冷链接(cold connection)。浏览器如果未主动关闭连接,停留在网页上,并且如果连接未超时,此时的连接虽然不活跃但会保持一段时间,这样的连接就是冷链接。同时,随着互联网的快速发展,访问网站的用户量不断上升,Web 服务器需要维持的链接数也不断上升,如何让服务支持更多客户端连接问题也愈发尖锐。当时 Web 服务器支持的最大并发连接数大致是 1K,于是 Dan Kegel 在 1999 年提出了 C10k 问题,如何能让服务器支持 10K 的客户端连接,字母“C”代表的是“client connection”。在文章“The C10K problem”[15]中,Dan Kegel 对 C10K 问题的描述如下:
It's time for web servers to handle ten thousand clients simultaneously, don't you think? After all, the web is a big place now.
另外,值得一提的是,HTTP 1.0 和 HTTP 1.1 协议存在队头阻塞问题(HOL 阻塞,head of line blocking),为了避免队头阻塞,从而使网页能更快响应,大多数浏览器会为每个域名同时开启多个 HTTP 连接,通常是 6 个并发连接[16],结果导致 Web 服务器需要维持的链接数增加数倍。在发布 HTTP 1.1 协议的十几年后的 2012 年,HTTP 2.0 的首个草稿发布,而制定 HTTP/2 协议的最大的目标之一就是解决队头阻塞问题。
当 Web 服务器需要同时处理大量客户端连接时,服务器的性能表现差,原因就出在系统调用 select()
和 poll()
的性能上,具体的问题有如下三点[17]:
select()
或 poll()
,内核都必须检查所有被指定的文件描述符,看它们是否处于就绪状态。随着待检查的文件描述符数量的增加,调用耗时也随之线性增加。若待检查的文件描述符数为 n,select()
或 poll()
的时间复杂度为 O(n)。select()
或 poll()
,程序都必须传递一个表示所有需要被检查的文件描述符的数据结构到内核,内核检查过描述符后,修改这个数据结构并返回给程序。(此外,对于 select()
来说,我们还必须在每次调用前初始化这个数据结构。)随着待检查的文件描述符数量的增加,传递给内核的数据结构大小也会随之增加。当检查大量文件描述符时,从用户空间到内核空间来回拷贝这个数据结构将占用大量的 CPU 时间。select()
或 poll()
调用完成后,程序必须检查返回的数据结构中的每个元素,以此查明哪个文件描述符处于就绪状态。解决系统调用 select()
和 poll()
的性能问题,让服务器能同时处理大量连接,比较典型的解决方案是,FreeBSD 4.1(2000.07 发布)开始支持的 kqueue
系统调用 ,以及 Linux 2.5.44(2002.10 发布)开始支持的 epoll
系统调用。
kqueue 相关的 API 主要涉及两个系统调用 kqueue()
和 kevent()
:
kqueue()
:用于在内核空间创建 kqueue
数据结构kevent()
:changelist
等参数时,用于将感兴趣的 kevent
事件对象注册到 kqueue
,kevent
对象上记录感兴趣文件描述符和事件类型eventlist
等参数时,用于查询就绪的 kevent
事件对象列表kqueue 的实现原理[18]:调用 kqueue()
创建由内核空间维护 kqueue
实例,kqueue
实例内包含链表,链表上保存全部监听的 kevent
事件对象,kevent
对象上感兴趣的记录文件描述符和事件类型。通过 kevent()
系统调用,可以在链表上注册、删除某 kevent
对象。当设备 I/O 事件触发时,设备与 kqueue
实例关联的钩子函数(hook)会被执行,钩子函数会判断事件是否与监听的事件相符合,如果符合就把事件添加到 kqueue
实例内下链表 active list
的末尾。查询就绪事件列表时,调用 kevent()
,内核只需要检查链表 active list
是否有元素,若有就把就绪事件列表拷贝到用户空间。
epoll 相关的 API 主要涉及三个系统调用 epoll_create()
、epoll_ctl()
和 epoll_wait()
:
epoll_create()
:用于在内核空间创建 epoll
实例epoll_ctl()
:用于添加感兴趣的 epoll_event
事件对象到 epoll
实例,epoll_event
对象上记录感兴趣文件描述符和事件类型epoll_wait()
:用于查询就绪的 epoll_event
事件对象列表epoll 的实现原理:调用 epoll_create()
创建由内核空间维护的 epoll
实例,epoll 实例内包含红黑树,红黑树上保存全部监听的 epoll_event
事件对象,epoll_event
对象上记录感兴趣的文件描述符和事件类型。通过 epoll_ctl()
系统调用,可以在红黑树上注册、删除某 epoll_event
对象。所有添加到红黑树中的事件都会与设备驱动程序建立回调关系,当 I/O 就绪事件触发时,会把事件添加到 epoll
实例内的链表 rdllist
。查询就绪事件列表时,调用 epoll_wait()
,内核只需要检查链表 rdllist
是否有元素,若有就把就绪事件列表拷贝到用户空间。
epoll
系统调用的事件通知模式,区分水平触发(level-triggered,LT)和边缘触发(edge-triggered,ET),默认通知模式是水平触发 LT,EPOLLET
标志可以将通知模式改为边缘触发。poll()
和 select()
所提供的通知模式是水平触发,不支持边缘触发。水平触发和边缘触发的通知模式的含义如下[17]:
“水平触发”和“边缘触发”术语源于电子工程领域。水平触发是只要有状态发生就触发。边缘触发是只有在状态改变的时候才会发生。条件触发关心的是事件状态,边缘触发关心的是事件本身。
采用边缘触发通知的程序通常要按照如下规则来设计:
另外,在边缘触发 ET 模式下,如果多个线程同时监听相同的描述符,只会有一个线程被唤醒用来处理 I/O 事件。epoll 的 man 文档对这个特性有如下描述,这个特性也避免了“惊群问题”(thundering herd problem)。
If multiple threads (or processes, if child processes have inherited the epoll file descriptor across fork(2)) are blocked in epoll_wait(2) waiting on the same epoll file descriptor and a file descriptor in the interest list that is marked for edge-triggered (EPOLLET) notification becomes ready, just one of the threads (or processes) is awoken from epoll_wait(2). This provides a useful optimization for avoiding "thundering herd" wake-ups in some scenarios.
因此,边缘触发通知模式其中一个适用的场景是,多核 CPU 上的多线程服务器,每个 CPU 核上运行一个线程,这些线程同时监听相同的描述符[19]。
kqueue
文档没有使用水平触发和边缘触发术语。但接口效果上,默认是水平触发。开启EV_CLEAR
标志可以达到类似边缘触发的效果。EV_CLEAR
标志的 man 文档描述:
After the event is retrieved by the user, its state is reset.
因为边缘触发通知模式效率更高,Nginx 服务器采用的就是边缘触发,参见源码 ngx_epoll_module.c 和 ngx_kqueue_module.c。
select
、poll
、kqueue
和 epoll
系统调用的多个维度的对比总结,如下表:
系统调用 | select | poll | kqueue | epoll |
---|---|---|---|---|
类 Unix 系统的支持情况 | POSIX 标准。最早 4.2BSD 提供(1983) | POSIX 标准。最早 SVR3 提供(1987) | BSD 专有。最早 FreeBSD 4.1 提供 (2000.08) | Linux 专有。最早 Linux 2.5.44 提供(2002.10) |
查询就绪描述符的时间复杂度 | O(n) | O(n) | O(1) | O(1) |
感兴趣描述符列表传递 | 每次 select() 都全量拷贝到内核空间 | 每次 poll() 都全量拷贝到内核空间 | 由内核空间维护 | 由内核空间维护 |
就绪描述符列表的返回 | 只返回就绪描述符数量,需要检查感兴趣描述符列表来判断哪些是就绪描述符 | 只返回就绪描述符数量,需要检查感兴趣描述符列表来判断哪些是就绪描述符 | 返回就绪事件数量,并同时返回就绪事件列表 | 返回就绪事件数量,并同时返回就绪事件列表 |
最大描述符数 | 被常数 FD_SETSIZE 限制(值为 1024) | 无限制 | 无限制 | 无限制 |
触发通知模式 | 水平 | 水平 | 水平和边缘 | 水平和边缘 |
附注:查询就绪描述符的时间复杂度,select
和 poll
都是 O(n),n 为感兴趣描述符的总数,因为内核实现上需要轮询全部感兴趣的描述符列表。kqueue
和 epoll
都是 O(1),实际的查询耗时与就绪描述符总数线性有关,但真实场景下就绪描述符数量相对描述符总数很小,可以认为是常数,所以复杂度是 O(1)。
总体上,select
和 poll
之间大同小异,而 kqueue
和 epoll
之间也是大同小异。
libevent 库,对系统调用 select
、poll
、kqueue
和 epoll
做了性能基准测试,如下图所示(图片来源)。基准测试声明了大量连接(文件描述符),大多数连接是冷的,只有少数是活跃的。测试衡量的是,在不同的总连接数下,为 100 个活动连接提供服务所需的时间。可以看到,系统调用 select
和 poll
,随着文件描述符的增加,耗时也随之线性增加,2500 个文件描述符时耗时大约 20ms,5000 个文件描述符时耗时大约 40ms,10000 个文件描述符时耗时大约 80ms。而系统调用 kqueue
和 epoll
,耗时始终在 3ms ~ 5ms 之间。
上文总结了 select
、poll
、kqueue
和 epoll
的接口特性和实现原理,但对具体应该如何使用这些函数没有切身感受。笔者使用 I/O 多路复用函数 select、poll、epoll 和 kqueue 以及 libevent 库,各自编写了 echo 服务的简单示例代码。所谓 echo 服务,即服务端接收到客户端的字符串输入,然后响应相同的字符串(为了方便区分响应字符串加了 >
前缀)。比如,如果客户端输入字符串 hello
,服务端将响应字符串 > hello
;如果客户端输入字符串 world
,服务端将响应字符串 > world
。完整的示例代码参见 io-multiplexing-demo。
上文介绍了 NCSA HTTPd、Apache HTTP Server 和 thttpd 等 Web 服务器的并发策略。主要的并发策略有三种模式:单连接单进程模式、单连接单线程模式和单线程的事件驱动模式。
Nginx 最早是 2002 年开始开发的,2004.08 采用 BSD 协议对外开源首个版本 0.1.0,开发 Nginx 的目的是为了解决 C10k 问题[20]。2002 年,当时 FreeBSD 已经提供 kqueue
系统调用,而 Linux 的 epoll
即将正式发布,新的 kqueue
和 epoll
系统调用让 Nginx 解决 C10k 问题成为可能。根据 w3techs 的统计,在 2013.07 Nginx 超越 Apache 成为 top 1000 网站使用最多的 Web 服务器[21]。
Nginx 采用的是事件驱动架构,在单线程的进程上执行事件循环,以异步非阻塞的方式处理 I/O 操作事件,事件循环底层基于高效的 epoll
或 kqueue
实现的 I/O 多路复用[22]。
Nginx 服务器,区分 Master 进程和 Worker 进程。Master 进程,用于加载配置文件、启动 Worker 进程和平滑升级等。Worker 进程,是单线程的进程,用于执行事件循环,并以非阻塞方式处理 I/O 操作,因此单个 Worker 进程就能并发处理大量连接。一个完整的请求完全由 Worker 进程来处理,而且只在一个 Worker 进程中处理。为了能充分利用多核 CPU 资源,通常生产环境配置的 Worker 进程数量等于 CPU 核心数。Nginx 的架构图如下[22]:
Redis 是内存数据库,处理网络请求也是采用单线程的事件驱动模式,底层基于高效的 epoll
或 kqueue
实现的 I/O 多路复用。事件循环处理的事件主要有,建立客户端新连接事件、客户端连接的缓冲区可读事件、客户端连接的缓冲区可写事件。Redis 的命令处理过程如下:
上述的命令处理过程,在源码层面上涉及的核心代码都在 networking.c
中:处理客户端新连接建立的事件的回调函数是 acceptTcpHandler,处理客户端命令请求事件的回调函数 readQueryFromClient,命令响应结果输出到各个客户端对应的函数是 handleClientsWithPendingWrites,处理客户端的可写事件的回调函数是 sendReplyToClient。更详细的实现原理解析,本文不再展开,可以自行深入阅读相关源代码或书籍资料。
Redis 与 Nginx 在并发策略上有不同的选择,Nginx 有多个 Worker 进程,每个 Worker 进程都运行自己的事件循环,而 Redis 整体上只有一个事件循环,采用的是单线程架构。这样的架构设计带来的问题就是 Redis 无法多核 CPU 并发。针对无法多核 CPU 并发问题,Redis 官方 FAQ 的推荐的解决方案是[23]:在多核 CPU 的单台机器上启动多个 Redis 实例。Redis 作者 antirez,解释了选择单线程而不选择多线程的原因,主要是:在 Redis 的数据结构上实现并发控制太复杂,多线程编程降低开发速度并且导致 bug 修复困难[24][25]。采用单线程的原因,概况成一句话就是[25]:
There is less to gain, and a lot of complexity to add.
不过,随着 Redis 版本的演进,部分逻辑已经改成了多线程实现,Redis 新增的多线程特性有三处,Redis 2.4 新增的异步磁盘 IO、Redis 4.0 新增的“Lazy Freeing”和 Redis 6.0 新增的“Threaded I/O”。但整体设计上,还是可以认为 Redis 主要使用单线程设计,依然是单线程的事件循环,并以单线程的方式执行命令(绝大多数命令,“Lazy Freeing”相关的命令除外)[26][27]。
2014-04 AIO User Guide: A description of how to use AIO https://web.archive.org/web/0/http://code.google.com/p/kernel/wiki/AIOUserGuide ↩
Awesome io_uring https://github.com/espoal/awesome-iouring ↩
2006-08 M. Jones: Boost application performance using asynchronous I/O https://developer.ibm.com/articles/l-async/ ↩ ↩
Learning Libevent: A tiny introduction to asynchronous IO https://libevent.org/libevent-book/01_intro.html ↩
Node.js: JavaScript Asynchronous Programming and Callbacks https://nodejs.dev/en/learn/javascript-asynchronous-programming-and-callbacks/ ↩
1995-04 NCSA httpd: Performance of Several HTTP Demons on an HP 735 Workstation https://web.archive.org/web/0/http://www.ncsa.uiuc.edu/InformationServers/Performance/V1.4/report.html ↩
About the Apache HTTP Server Project https://httpd.apache.org/ABOUT_APACHE.html ↩ ↩
Changes with Apache (12 Jun 1995: This release included modified versions of a lot of code from the Apache 0.6.4 public release, plus an early pre-forking patch codeveloped by Robert Thau and Rob Hartill.) https://github.com/apache/httpd/blob/1.3.x/src/CHANGES#L9427 ↩
Apache HTTP Server Version 2.2: Multi-Processing Modules (MPMs) https://httpd.apache.org/docs/2.2/en/mpm.html#defaults ↩
1995 John Ousterhout: Why Threads Are A Bad Idea (for most purposes) (slides) http://www.cc.gatech.edu/classes/AY2010/cs4210_fall/papers/ousterhout-threads.pdf ↩
1998-07 Jef Poskanzer: Web Server Comparisons(thttpd 服务器作者) http://www.acme.com/software/thttpd/benchmarks.html ↩
1998 Gaurav Banga, Jeffrey C. Mogul: Scalable Kernel Performance for Internet Servers Under Realistic Loads. USENIX Annual Technical Conference 1998 dblp usenix.org ↩
1999-05 Dan Kegel: The C10K problem(最后更新时间 2011.07) http://www.kegel.com/c10k.html ↩
2014-02 详解浏览器最大并发连接数 https://web.archive.org/web/0/http://www.iefans.net/liulanqi-zuida-bingfa-lianjieshu ↩
2001 Jonathan Lemon: Kqueue - A Generic and Scalable Event Notification Facility. USENIX Annual Technical Conference 2001 dblp usenix.org ↩
What is the purpose of epoll's edge triggered option? https://stackoverflow.com/a/73540436/689699 ↩
2012-01 Interview with Igor Sysoev, author of Apache's competitor NGINX https://web.archive.org/web/0/http://www.freesoftwaremagazine.com/articles/interview_igor_sysoev_author_apaches_competitor_nginx ↩
2013-07 Nginx just became the most used web server among the top 1000 websites https://w3techs.com/blog/entry/nginx_just_became_the_most_used_web_server_among_the_top_1000_websites ↩
2012-03 AOSA Volume 2 - nginx (Andrew Alexeev) https://aosabook.org/en/v2/nginx.html ↩ ↩
Redis FAQ: How can Redis use multiple CPUs or cores? https://redis.io/docs/getting-started/faq/#how-can-redis-use-multiple-cpus-or-cores ↩
2010-09 antirez: An update on the Memcached/Redis benchmark http://oldblog.antirez.com/post/update-on-memcached-redis-benchmark.html ↩
2019-02 antirez: An update about Redis developments in 2019 http://antirez.com/news/126 ↩ ↩
Redis Doc: Diagnosing latency issues: Single threaded nature of Redis https://redis.io/docs/management/optimization/latency/#single-threaded-nature-of-redis ↩
2019-08 林添毅:正式支持多线程!Redis 6.0与老版性能对比评测 https://mp.weixin.qq.com/s/6WQNq5dNk-GuEhZXtVCo-A ↩
并发控制,是数据库系统的 ACID 特性中的隔离性(Isolation)的保障。所谓隔离性,就是事务的执行不应受到其他并发执行事务的干扰,事务的执行看上去应与其他事务是隔离的。被隔离的执行,等价于事务的某种串行执行,或者说,它等价于一个没有并发的执行。保证串行性可能只允许极小的并发度,采用较弱隔离性,能带来更高的并发度,是并发事务的正确性和性能之间的妥协。
早期各大数据库厂商实现并发控制时多采用基于封锁的并发控制技术,所以在基于封锁的技术背景下,才在 ANSI SQL-92 标准中提出了四种隔离级别:未提交读(Read Uncommitted)、己提交读(Read Committed)、可重复读(Repeatable Read)、可串行化(Serializable)(附注:为了书写简便本文将各个隔离级别依次缩写为 RU、RC、RR、SER)。ANSI SQL-92 标准的四种隔离级别,是根据三种读异常现象(phenomena)定义的,隔离级别和异常现象的关系如下:
隔离级别 | P1 脏读 | P2 不可重复读 | P4 幻读 |
---|---|---|---|
Read Uncommitted | 可能 | 可能 | 可能 |
Read Committed | 避免 | 可能 | 可能 |
Repeatable Read | 避免 | 避免 | 可能 |
Serializable | 避免 | 避免 | 避免 |
ANSI SQL-92 标准文档对三种读异常现象的定义原文如下 [ref]:
The isolation level specifies the kind of phenomena that can occur during the execution of concurrent SQL-transactions. The following phenomena are possible:
1) P1 ("Dirty read"): SQL-transaction T1 modifies a row. SQL-transaction T2 then reads that row before T1 performs a COMMIT. If T1 then performs a ROLLBACK, T2 will have read a row that was never committed and that may thus be considered to have never existed.
2) P2 ("Non-repeatable read"): SQL-transaction T1 reads a row. SQL-transaction T2 then modifies or deletes that row and performs a COMMIT. If T1 then attempts to reread the row, it may receive the modified value or discover that the row has been deleted.
3) P3 ("Phantom"): SQL-transaction T1 reads the set of rows N that satisfy some. SQL-transaction T2 then executes SQL-statements that generate one or more rows that satisfy the used by SQL-transaction T1. If SQL-transaction T1 then repeats the initial read with the same , it obtains a different collection of rows.
除了脏读、不可重复读和幻读这 3 种读数据异常外,还有写数据异常,即脏写和丢失更新。各个异常的含义如下:
各个异常的读写操作序列的简化符号表示如下 [Berenson 1995]:
1 | P0: w1[x]...w2[x]...(c1 or a1) 事务 T2 脏写 |
其中 w1[x] 表示事务 T1 写入记录 x,r1[x] 表示事务 T1 读取记录 x,c1 表示事务 T1 提交,a1 表示事务 T1 回滚,r1[P] 表示事务 T1 按照谓词 P 的条件读取若干条记录,w1[y in P] 表示事务 T1 写入记录 y 满足谓词 P 的条件。
Berenson 的论文评判了 ANSI SQL-92 标准的异常定义。ANSI SQL-92 标准的异常的定义存在歧义,可以严格解释,也可以宽松解释,A1、A2 和 A3 的符号表示为严格解释,按严格解释,某些特殊的异常无法囊括,所以推荐宽松解释。按照标准的定义,容易引起误解的是,在排除 P1 脏读、P2 不可重复、P3 幻读这三种读异常后就会得到可串行化隔离级别,但是事实并非如此。标准没有定义 P0 脏写和 P4 更新丢失异常。另外,基于 MVCC 技术实现的快照隔离(Snapshot Isolation),能避免标准定义的 P1 脏读、P2 不可重复、P3 幻读,并且避免 P0 脏写和 P4 更新丢失,但还存在写偏序(Write Skew)异常。
不可重复读和幻读的区别:
异常由并发冲突引起,对应关系如下:
早期各大数据库厂商实现并发控制时多采用基于封锁的并发控制技术,所以在基于封锁的技术背景下,才在ANSI SQL 标准中提出了四种隔离级别。基于锁的并发控制技术的加锁方式与隔离级别的关系表 [Berenson 1995]:
隔离级别 | 写锁 | 数据项的读锁 | 谓词的读锁 |
---|---|---|---|
Read Uncommitted | 长写锁 | 无锁要求 | 无锁要求 |
Read Commited | 长写锁 | 短读锁 | 短谓词锁 |
Repeatable Read | 长写锁 | 长读锁 | 短谓词锁 |
Serializable | 长写锁 | 长读锁 | 长谓词锁 |
说明:
基于锁的并发控制下,隔离级别和异常现象的关系:
隔离级别 | P0 脏写 | P1 脏读 | P4 丢失更新 | P2 不可重复读 | P4 幻读 |
---|---|---|---|---|---|
Read Uncommitted | 避免 | 可能 | 可能 | 可能 | 可能 |
Read Committed | 避免 | 避免 | 可能 | 可能 | 可能 |
Repeatable Read | 避免 | 避免 | 避免 | 避免 | 可能 |
Serializable | 避免 | 避免 | 避免 | 避免 | 避免 |
各个隔离级别在基于锁的并发控制技术下的具体的实现说明(参考自腾讯李海翔的《数据库事务处理的艺术》第 2 章):
基于锁的并发控制,读-读操作可以并发执行,但读-写、写-读、写-写操作无法并发执行,阻塞等待。MVCC 结合封锁技术,使得读-写、写-读操作互不阻塞,即只有写-写操作不能并发,并发度被提高到 75%,这就是 MVCC 被广为使用的原因。
InnoDB 的并发控制以封锁技术为主,MVCC 技术为辅助。让我们先看下 InnoDB 的封锁技术。
InnoDB 存储引擎实现两种标准的行级锁模式,共享锁(读锁)和排他锁(写锁)[doc]:
如果事务 T1 持有行 r 上的共享锁(S),则来自某个不同事务 T2 的对行 r 上的锁的请求将按如下方式处理:
如果事务 T1 持有行 r 上的排他锁(X),则某个不同事务 T2 对 r 上任一类型的锁的请求无法立即被授予。相反,事务 T2 必须等待事务 T1 释放其对行 r 的锁定。
共享锁和排他锁的兼容性:
待申请 \ 已持有 | 共享锁 S | 排他锁 X |
---|---|---|
共享锁 S | 兼容 | 冲突 |
排他锁 X | 冲突 | 冲突 |
区分共享锁(读锁)和排它锁(写锁)后,读锁与读锁的并发可被允许进行,并发能力得以提高。
对于 update
、delete
和 insert
语句,InnoDB 会自动给涉及数据集加排他锁(X);对于普通 select
语句,InnoDB 不会加任何锁(SERIALIZABLE
隔离级别下除外);事务可以通过以下语句显式给查询 select
显式加共享锁或排他锁:
select ... for share
select ... for update
现在让我们来试验下共享锁和排他锁。创建 tbl
表,并添加表数据:
1 | create table tbl |
InnoDB 的排它锁示例,如下:
事务1 | 事务2 |
---|---|
mysql> begin; | mysql> begin; |
-- 在 a = 10 的索引记录上添加排他锁 mysql> select * from tbl where a = 10 for update; | |
-- 阻塞,获取 a = 10 的排他锁超时 mysql> update tbl set b = 42 where a = 10; ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction | |
-- 阻塞,获取 a = 10 的排他锁超时 mysql> update tbl set b = 42 where a >= 10; | |
-- 阻塞,获取 a = 10 的排他锁超时 mysql> delete from tbl where a = 10; | |
-- 阻塞,获取 a = 10 的排他锁超时 mysql> select * from tbl where a = 10 for update; | |
-- 更新成功,可以获得其他记录的排他锁 mysql> update tbl set b = 42 where a = 20; | |
mysql> commit; | |
-- 更新成功,在事务 1 释放锁后,其他事务可以获取排他锁 mysql> update tbl set b = 42 where a = 10; |
InnoDB的共享锁示例,如下:
事务1 | 事务2 |
---|---|
mysql> begin; | mysql> begin; |
-- 在 a = 10 的索引记录上添加共享锁 mysql> select * from tbl where a = 10 for share; | |
-- 获取 a = 10 的共享锁成功 mysql> select * from tbl where a = 10 for share; | |
-- 阻塞,获取 a = 10 的排他锁超时 mysql> update tbl set b = 42 where a = 10; | |
mysql> commit; | |
-- 更新成功,在事务 1 释放锁后,其他事务可以获取排他锁 mysql> update tbl set b = 42 where a = 10; |
InnoDB 存储引擎支持多粒度锁定(multiple granularity locking),这种锁定允许事务在行级上的锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,InnoDB 存储引擎支持一种额外的锁方式,称之为意向锁(Intention Lock)。意向锁是将锁定的对象分为多个层次,意向锁意味着事务希望在更细粒度上进行加锁。
若将上锁的对象看成一棵树,那么对最下层的对象上锁,也就是对最细粒度的对象进行上锁,那么首先需要对粗粒度的对象上锁。如果需要对页上的记录 r 进行上 X 锁,那么分别需要对数据库 A、表、页上意向锁 IX,最后对记录 r 上 X 锁。若其中任何一个部分导致等待,那么该操作需要等待粗粒度锁的完成。
在一个对象加锁之前,该对象的全部祖先节点均加上了意向锁。希望给某个记录加锁的事务必须遍历从根到记录的路径。在遍历树的过程中,该事务给各节点加上意向锁。
举例来说,假设在表 1 的记录 r 上持有 X 锁,表 1 上必定持有 IX 锁。如果其他事务想在表 1 上加 S 表锁或 X 表锁,但与已有 IX 锁不兼容,所以该事务需要等待。再举例,假设表 1 持有 S 锁,如果其他事务想在表 1 的记录 r 上加 X 锁,需要先获得表 1 的 IX 锁,但与已有 S 锁不兼容,所以该事务需要等待。有了意向锁之后,就能快速判断行锁和表锁之间是否兼容。
InnoDB 存储引擎支持意向锁设计比较简练,其意向锁即为表级别的锁,两种意向锁 [doc]:
IS、IX、S、X 锁的兼容性:
待申请 \ 已持有 | IS | IX | S | X |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 冲突 |
IX | 兼容 | 兼容 | 冲突 | 冲突 |
S | 兼容 | 冲突 | 兼容 | 冲突 |
X | 冲突 | 冲突 | 冲突 | 冲突 |
兼容关系:各种意向锁(IS、IX)之间全部兼容,意向共享锁 IS 和共享锁 S 兼容,共享锁 S 和共享锁 S 兼容,其他冲突。
SQL 语句可以分为数据定义语言(DDL)、数据控制语言(DCL)、数据查询语言(DQL)、数据操纵语言(DML)四种类型的语句,前两种语句,涉及的对象在数据之上,所以加锁的范围,通常是表级,对应表级锁。后两种语句操作的对象是数据,加锁的范围,通常是数据级,这就对应行级锁。
InnoDB 行锁分为 3 种类型 [doc]:
如果索引上包含 10, 20, 30, 40, 50 这些记录,那么可能的 next-key 锁的锁区间(interval),如下:
1 | (-无穷, 10] 即,间隙锁 (-无穷, 10) + 记录锁 10。区间为,左开右闭区间 |
最后一个锁区间 (50, +无穷]
,对应的是上界伪记录(supremum pseudo-record
),不是真实存在的记录。这个锁区间用于防止在最大值 50 之后插入记录。
记录锁总是会去锁住索引记录,如果 InnoDB 存储引擎表在建立的时候没有设置任何一个索引,那么这时 InnoDB 存储引擎会使用隐式的主键来进行锁定。
MySQL 默认的事务隔离级别是可重复读(REPEATABLE-READ),如果把事务隔离级别改成已提交读(READ-COMMITTED),间隙锁会被禁用。禁用间隙锁后,幻读异常会出现,因为其他事务可以在间隙中插入新行。InnoDB 的间隙锁,就是为了解决幻读异常而引入的。关于幻读异常,参见官方文档 doc。
RR 隔离级别下,InnoDB 的锁通常使用 next-key 锁。但是,在唯一索引(和主键索引)上的等值查询,next-key 锁退化为记录锁,间隙锁并不需要,即仅锁住索引本身,而不是范围。如果在唯一索引(和主键索引)上做范围查询,间隙锁依然需要。官方文档描述如下 [doc]:
Gap locking is not needed for statements that lock rows using a unique index to search for a unique row. (This does not include the case that the search condition includes only some columns of a multiple-column unique index; in that case, gap locking does occur.)
间隙锁是“纯抑制性的”,间隙锁唯一的作用就是为了防止其他事务的插入到间隙中。间隙锁和间隙锁之间是互不冲突的,所以间隙共享 S 锁和间隙排他 X 锁没有任何区别。
另外,还有一种锁叫插入意向锁(insert intention lock),基于间隙锁,专门用于 insert 操作。在执行 insert 操作时,需要先申请获取插入意向锁,也就是说,需要先检查当前插入位置上的下一条记录上是否持有间隙锁,如果被间隙锁锁住,则锁冲突,插入被阻塞。多个事务做 insert 操作,被相同的间隙锁阻塞,如果插入的值各不相同,这些事务的 insert 操作之间不阻塞。
所以,间隙锁与插入意向锁的兼容关系是,已持有的间隙锁与待申请的插入意向锁冲突,而插入意向锁之间兼容,在一个间隙锁锁上可以有多个意向锁等待。
IS、IX、X、S 锁和记录锁、间隙锁、next-key 锁的关系:
MySQL 8.0 之前,information_schema
库提供 innodb_trx
、innodb_locks
和 innodb_lock_waits
三张表,用来监控事务和诊断潜在的锁问题,具体介绍可以参见官方 5.7 文档 doc。
innodb_trx
:当前事务表innodb_locks
:锁等待中的锁信息表innodb_lock_waits
:锁等待中的事务表在 MySQL 8.0 之前,要想获得当前已持有的锁信息,需要开启参数 innodb_status_output_locks
并且执行命令 show engine innodb status
,具体介绍可以参见官方文档“15.17 InnoDB Monitors”,doc。
MySQL 8.0 开始,innodb_locks
表和 innodb_lock_waits
表,被 performance_schema
库的 data_locks
表和 data_lock_waits
表替代。其中值得注意的不同点是,新的 data_locks
表,同时包含了已持有的锁和请求中的锁的信息,这样查看当前已持有的锁信息更加方便。相关 SQL 示例:
1 | -- 查询全部锁信息 |
命令 show engine innodb status
的输出和 data_locks
表的对应关系,可以参考文章 link。
本文的全部案例采用的 MySQL 版本为 8.0.30。MySQL 的默认事务隔离级别是 REPEATABLE-READ
(可重复读),事务隔离级别可以通过系统变量 transaction_isolation 控制。
1 | -- 事务隔离级别,默认为可重复读(Repeatable Read) |
tbl
表的数据如下:
1 | mysql> select * from tbl; |
SQL 语句:
1 | select * from tbl where a = 10 for update; |
data_locks
表中的行锁数据:
1 | mysql> select * from performance_schema.data_locks where LOCK_TYPE = 'RECORD' \G |
加锁情况:
其他 SQL 语句的加锁情况(通过查 data_locks
表确认):
1 | -- 在 a = 10 的索引记录上添加共享记录锁(S,REC_NOT_GAP) |
加锁与锁冲突 SQL 演示:
事务1 | 事务2 |
---|---|
mysql> begin; | mysql> begin; |
-- 在 a = 10 的索引记录上添加排他记录锁 mysql> select * from tbl where a = 10 for update; | |
-- 阻塞,因为 a = 10 上存在排他记录锁 mysql> select * from tbl where a = 10 for update; -- 阻塞,因为 a = 10 上存在排他记录锁 mysql> insert into tbl (a) values (10); | |
-- 插入成功 mysql> insert into tbl (a) values (9); -- 插入成功 mysql> insert into tbl (a) values (11); | |
mysql> rollback; | mysql> rollback; |
SQL 语句的加锁情况(通过查 data_locks
表确认):
1 | -- 在 b = 10 的索引记录上添加排他记录锁(X,REC_NOT_GAP) |
上面的全部 SQL,除了走覆盖索引的 select for share
外,其他的加锁范围都相同。
SQL 语句的加锁情况(通过查 data_locks
表确认):
1 | -- 在 c = 10 的索引记录上添加排他 next-key 锁,区间为 (-无穷, 10](X) |
上面的全部 SQL,除了走覆盖索引的 select for share
外,其他的加锁范围都相同。
加锁与锁冲突 SQL 演示:
事务1 | 事务2 |
---|---|
mysql> begin; | mysql> begin; |
-- 在 c = 10 的索引记录上添加排他 next-key 锁,区间为 (-无穷, 10] -- 在 a = 10 的索引记录上添加排他记录锁 -- 在 c = 20 的索引记录上添加排他间隙锁,区间为 (10, 20) mysql> select * from tbl where c = 10 for update; | |
-- 阻塞,因为 c = 10 上存在排他 next-key 锁 mysql> select * from tbl where c = 10 for update; -- 阻塞,因为 a = 10 上存在排他记录锁 mysql> select * from tbl where a = 10 for update; | |
-- 阻塞,因为 c = 10 上存在排他 next-key 锁,区间为 (-无穷, 10] mysql> insert into tbl (a, c) values (1, 9); -- 阻塞,因为 c = 10 上存在排他 next-key 锁,区间为 (-无穷, 10] mysql> insert into tbl (a, c) values (1, 10); | |
-- 阻塞,因为 c = (10, 20) 区间存在间隙锁 mysql> insert into tbl (a, c) values (1, 11); -- 插入成功 mysql> insert into tbl (a, c) values (1, 21); | |
mysql> rollback; | mysql> rollback; |
SQL 语句的加锁情况(通过查 data_locks
表确认):
1 | -- 在 a 主键的全部索引记录上添加排他 next-key 锁 |
因为字段 d 上没有索引,这个 SQL 语句,只能在聚簇索引上全表扫描。加锁情况,在 a 主键的全部索引记录上添加排他 next-key 锁。表 tbl
共 10 条记录,全部的持有的 next-key 锁的锁区间,如下:
1 | (-无穷, 10] |
SQL 语句的加锁情况(通过查 data_locks
表确认):
1 | ---- 主键索引上的值不存在的等值查询 |
SQL 语句的加锁情况(通过查 data_locks
表确认):
1 | -- 在 a = 90 的索引记录上添加排他记录锁(X,REC_NOT_GAP) |
SQL 语句的加锁情况(通过查 data_locks
表确认):
1 | -- 在 b = 90 的索引记录上添加排他 next-key 锁,区间为 (80, 90](X) |
加锁与锁冲突 SQL 演示:
事务1 | 事务2 |
---|---|
mysql> begin; | mysql> begin; |
-- 在 b = 90 的索引记录上添加排他 next-key 锁,区间为 (80, 90] -- 在 a = 90 的索引记录上添加排他记录锁 -- 在 b = 100 的索引记录上添加排他 next-key 锁,区间为 (90, 100] mysql> select * from tbl where b >= 90 and b < 91 for update; | |
-- 阻塞,因为 b = 90 上存在排他 next-key 锁 mysql> select * from tbl where b = 90 for update; -- 阻塞,因为 b = 100 上存在排他 next-key 锁(不必要的记录锁) mysql> select * from tbl where b = 100 for update; -- 阻塞,因为 a = 90 上存在排他记录锁 mysql> select * from tbl where a = 90 for update; | |
mysql> rollback; | mysql> rollback; |
SQL 语句的加锁情况(通过查 data_locks
表确认):
1 | -- 在 b = 90 的索引记录上添加排他 next-key 锁,区间为 (80, 90](X) |
对比,唯一索引上的范围查询的加锁情况,容易得出结论,唯一索引和普通索引上的范围查询的加锁规则相同。
把事务隔离级别修改为已提交读(Read Committed):
1 | -- 事务隔离级别,修改为已提交读(Read Committed) |
SQL 语句的加锁情况(通过查 data_locks
表确认):
1 | -- 在 a = 10 的索引记录上添加排他记录锁(X,REC_NOT_GAP) |
结论:因为主键索引上的等值查询不涉及间隙锁,所以 RR 和 RC 隔离级别下的加锁规则相同。
同样的,因为唯一索引上的等值查询不涉及间隙锁,所以 RR 和 RC 隔离级别下的加锁规则相同。
1 | -- 在 c = 10 的索引记录上添加排他记录锁(X,REC_NOT_GAP) |
1 | -- 在 a = 10 的索引记录上添加排他记录锁(X,REC_NOT_GAP) |
1 | ---- 主键索引上的值不存在的等值查询 |
1 | -- 在 a = 90 的索引记录上添加排他记录锁(X,REC_NOT_GAP) |
1 | -- 在 b = 90 的索引记录上添加排他记录锁(X,REC_NOT_GAP) |
加锁情况,和 RC 隔离级别的唯一索引上的范围查询完全相同。
RC 隔离级别时的加锁规则:
RR 隔离级别时的加锁规则:
注意,RR 隔离级别时,在主键索引上的范围查询时,确实是按上文的规则加间隙锁。但实际验证发现,在辅助索引(包括唯一索引和普通索引)上的范围查询时,在最后的不满足查询条件的记录上实际加的是 next-key 锁。这样加锁的问题是,会在不满足查询条件的记录上记录锁,这个记录锁其实是不必要的,是一个 bug。
其实,这个不必要的记录锁 bug,在 MySQL 8.0.18 之前,主键索引的场景下也存在,MySQL 8.0.18 修复了,但只修复了主键索引的场景,辅助索引的场景未修复。修复对应 bug 为“Bug #29508068 UNNECESSARY NEXT-KEY LOCK TAKEN”,修复提交记录见 github。
在 MySQL 8.0.19 版本上,有人再次提了 bug,“Bug #98639 Redundant row-level locking for secondary index”。不过 MySQL 官方认为“Not a Bug”。然后,提 bug 的人,也只好妥协认为这个是“performance issue”。
对比下面这 3 个 SQL 的加锁情况,可以发现后 2 个 SQL 存在不必要的加锁问题。
1 | -- 主键索引上的范围查询 |
上文提到,PostgreSQL 的并发控制技术是以 MVCC 技术为主,封锁技术为辅。先看下 PostgreSQL 对隔离级别的实现 [doc]:
InnoDB 的并发控制以封锁技术为主,MVCC 技术辅助,各个隔离级别的具体实现是:
InnoDB 实现的 MVCC 技术,能让事务以快照读的方式执行查询。快照读(snapshot read),或者叫一致性非锁定读(consistent nonlocking read),或者一致性读(consistent read),即使用多版本技术实现的读取数据在某个时间点的快照的查询。在 RR 和 RC 隔离级别下,一致性读是普通的 select
语句的默认模式。快照读避免加锁,从而提高并发度。在 RR 和 RC 隔离级别下快照读的区别:
如果事务在查询数据后,要对该数据做修改操作,快照读无法提供足够的保护,因为其他事务可以对这些数据做修改操作。为了提供额外的保护,InnoDB 提供锁定读(locking read),即同时执行锁定操作的 select
语句,锁持有直到事务结束。锁定读分两种:
select ... for share
是加共享锁的查询数据select ... for update
是加排他锁的查询数据上文提到,“快照读,能一定程度避免不可重复读和幻读异常,但因为 InnoDB 的刷新快照的特殊实现,不能完全避免”。现在来看下 RR 隔离级别下的不可重复读异常的示例:
事务1 | 事务2 |
---|---|
mysql> begin; | mysql> begin; |
-- 返回值为 10,快照读 mysql> select b from tbl where a = 10; | |
-- 把 b 值修改为 0 mysql> update tbl set b = 0 where a = 10; mysql> commit; | |
-- 返回值为 10,没有出现不可重复读异常 mysql> select b from tbl where a = 10; | |
-- update 会读取 a = 10 的已提交的最新值 -- 同时 a = 10 记录的快照会被刷新 mysql> update tbl set b = b + 1 where a = 10; | |
-- 返回值为 1,出现不可重复读异常 mysql> select b from tbl where a = 10; |
这个问题在 MySQL 的 Bug 系统中可以找到,参见:Bug #57973、Bug #63870 等。官方认为,这不是 Bug,InnoDB 就是按这种方式设计。Bug #57973 下 MySQL 工程师 Kevin Lewis 对这个问题的解答 [ref]:
[16 Aug 2013 19:23] Kevin Lewis
Rejecting this bug because InnoDB is working as designed for the following reason;
...
But when InnoDB Repeatable Read transactions modify the database, it is possible to get phantom reads added into the static view of the database, just as the ANSI description allows. Moreover, InnoDB relaxes the ANSI description for Repeatable Read isolation in that it will also allow non-repeatable reads during an UPDATE or DELETE. Specifically, it will write to newly committed records within its read view. And because of gap locking, it will actually wait on other transactions that have pending records that may become committed within its read view. So not only is an UPDATE or DELETE affected by pending or newly committed records that satisfy the predicate, but also 'SELECT … LOCK IN SHARE MODE' and 'SELECT … FOR UPDATE'.
This WRITE COMMITTED implementation of REPEATABLE READ is not typical of any other database that I am aware of. But it has some real advantages over a standard 'Snapshot' isolation. When an update conflict would occur in other database engines that implement a snapshot isolation for Repeatable Read, an error message would typically say that you need to restart your transaction in order to see the current data. So the normal activity would be to restart the entire transaction and do the same changes over again. But InnoDB allows you to just keep going with the current transaction by waiting on other records which might join your view of the data and including them on the fly when the UPDATE or DELETE is done. This WRITE COMMITTED implementation combined with implicit record and gap locking actually adds a serializable component to Repeatable Read isolation.
就是说,InnoDB 实现的 RR 隔离级别,放松了 SQL 标准对 RR 隔离级别的要求。事务 T1 在快照读后,如果其他事务 T2 修改了快照对应的记录并提交,之后事务 T1 执行涉及快照的 DML 语句(update、delete、insert)或锁定读,会触发快照刷新,事务 T2 最新提交的修改会刷新进快照。最终导致事务 T1 再次执行相同条件的快照读,读取结果不同,出现不可重复读或幻读异常。简单概括就是,在快照失效后,又刷新快照,导致两次读到的快照不同。另外,如果实现上选择不刷新快照,并且事务 T1 正常执行,会出现 P4 丢失更新异常。
不可重复读异常的避免(一定程度上避免,但没有完全避免):
select
语句),并且中间没有执行涉及快照的 DML 或锁定读,这样两次读到的是相同的快照读,所以不会出现不可重复读异常。select for update/share
),因为第一次加锁,其他事务无法更新该记录,所以也不会出现不可重复读异常。幻读异常的避免(一定程度上避免,但没有完全避免):
select
语句),并且中间没有执行涉及快照的 DML 或锁定读,这样两次读到的是相同的快照读,所以不会出现幻读异常。select for update/share
),因为第一次当前读加间隙锁,其他事务无法插入,被阻塞,所以也不会出现幻读异常。上述的快照失效的场景,PostgreSQL 的处理方式是,事务会被回滚并报错提示,应用程序收到这个报错,可以尝试重试,重试的事务读到的快照是最新的,这样即避免丢失更新异常,也避免了幻读和不可重复读异常(参见官方文档 doc)。
MySQL 8.0 Reference Manual
其他参考资料:
Kong 是云原生、高效、可扩展、分布式的微服务抽象层,被称为 API 网关,或者 API 中间件。Kong 在 2015 年 4 月由 Mashape 公司开源,基于 OpenResty 和 Apache Cassandra/PostgreSQL 构建,提供易于使用的 RESTful API 来操作和配置 API 系统[1][2]。
Mashape 是 API 集市,是为应用开发者与 API 提供者服务的 API 交易市场,Mashape 让发者能够方便地查找与购买 API,而 API 提供商则能轻松地销售与管理 API[3]。随着 Mashape 市场上的 API 越来越多,原先基于 Node.js 实现的 API 代理不再适用,不能处理大流量尖峰,无法快速扩容。于是,寻找处理大流量的方案,同时需要保证可靠性和安全性,成为 Mashape 亟待解决的问题。2013 年,在 CloudFlare(当时 OpenResty 背后的公司)的工程师的建议下,Mashape 开始在 OpenResty 基础上开发 Kong 项目[4]。Mashape 公司的名字,MashAPE,有人猿猩猩的含义,公司 logo 也是相应动物。类似的,Kong,对应的是,King Kong,就是电影里的金刚[4]。在 Mashape 开启 Kong 项目两年后,2015 年 4 月,Mashape 公司开源了 Kong[2]。
2017 年 5月,Mashape 和 RapidAPI 合并,组成全球最大的 API 集市[5][6]。5 个月后,Mashape, Inc. 改名为 Kong Inc.,新的公司以 Kong 项目为聚焦,把全部工程师投入到 Kong 开发中,并且于此同时他们发布了 Kong 企业版[7]。
Kong,作为微服务的请求的网关,能通过插件提供负载均衡、日志记录、鉴权、限流、转换以及其他等功能。相对与旧的、没有使用网关的方式,Kong 把这些通用功能中心化,让微服务更加专注于业务本身。
Kong 的整体架构,如下图所示[1]:
目前最新的 Kong 版本是 2.0.x,2.0 发布时间是 2020 年 1 月,而 1.0 发布时间是 2018 年 12 月[8][9]。笔者公司使用的 Kong 版本是 0.14.1,暂时未升级自最新版,所以下文阐述的 Kong 版本主要以 0.14.1 为准,并同时会提及其他版本的特性。
安装 Kong 很简单,参见官方文档即可。在 Ubuntu 18.04 下安装 Kong 0.14.1,可以执行下面的命令:
1 | # kong 安装 |
Kong 依赖数据库,Postgres 或者 Cassandra,默认依赖 Postgres(kong 1.1 开始支持无数据库声明式配置[10])。我们预先安装 Postgres:
1 | # 安装 postgresql |
在 Postgres 下添加 Kong 需要的的数据库实例和用户。下面的示例,创建数据库 kong
,用户名 kong
,密码为 kong
:
1 | $ sudo -u postgres psql |
执行完成后,即可使用用户名为 kong
的用户连接 Postgres,psql -h localhost -U kong -d kong
。
Kong 安装完成后,默认会创建配置文件 /etc/kong/kong.conf.default
,这份配置文件在 GitHub 上也能找到,被注释掉的配置项,就是默认设置。
在启动 Kong 网关服务器前,我们参考 kong.conf.default
,创建自己的 kong.conf
配置文件。我们把配置文件放在 /home/yulewei/kong
目录下,同时也把这目录当作为 Kong 的 prefix
目录。修改这配置 kong.conf
,文件末尾添加:
1 | prefix = /home/yulewei/kong/ |
使用 kong
命令,启动 Kong 网关服务器:
1 | # 初始化或迁移数据库数据 |
Kong 默认绑定 4 个端口:
:8000
用来接收来自客户端的 HTTP 流量的请求,并转发到上游服务:8443
用来接收来自客户端的 HTTPS 流量的请求,并转发到上游服务:8001
用来接收访问 Admin API 的 HTTP 流量的请求:8444
用来接收访问 Admin API 的 HTTPS 流量的请求所以,可以执行下面的命令,来确认 Kong 是否正常运行:
1 | # 确认 Kong 是否正常运行 |
Kong 底层依赖 OpenResty,启动 Kong 后,可以看到 nginx 进程:
1 | # 查看 nginx 进程 |
安装 Kong 0.14.1,自动安装的 OpenResty 版本是 1.13.6.2,OpenResty 捆绑的安装了 LuaJIT。
1 | $ /usr/local/openresty/nginx/sbin/nginx -v |
另外,同时也安装了 LuaRocks,LuaRocks 关联的是 OpenResty 捆绑的 LuaJIT。事实上,Kong 就是一个 LuaRocks 的 rock 包,在Kong 项目的 GitHub 上可以看到 rockspec 文件。Kong 的安装,底层实现上,是通过 luarocks
命令完成的,类似这样的命令,luarocks install kong 0.14.1-0
[11][12]。可以使用 luarocks show
命令查看这个 Kong 的 rcok 包:
1 | $ luarocks show kong |
有个小细节值得注意,通过 luarocks
看到,Kong 采用的协议是 MIT。但事实上,Kong 0.5.0 开始协议从 MIT 改成了 Apache 2.0。此处是一个小 bug,Kong 的 rockspec 文件没有及时更新,这个问题后来修复了,参见 #4125。
管理 Kong 可以直接使用 Admin API,当然也有基于 Admin API 实现 GUI 管理工具。
Kong 官方的企业版提供了 GUI 管理工具,Kong Manager
(Kong EE 0.34 之前称为 Admin GUI
),Kong 社区版没有提供 GUI 管理工具。
第三方的开源 GUI 工具,比较活跃的就是 Konga
,值得推荐,如下图。
另外,还有其他的 GUI 工具,比如 Kong Dashboard
,也可以了解下。
Kong 核心概念:
Service
:对应位于 Kong 后方的自身的 Upstream
API 或微服务。Route
:Kong 的入口点,定义了如何把请求发送到特定 Service
的规则。一个 Service
可以有多个Route
。Plugin
:插件提供了模块化系统,用来修改或控制 Kong。插件提供了大量功能,比如访问控制、缓存、限流、日志记录等。Consumer
:消费者,表示使用 API 的用户,能用来对用户进行访问控制、跟踪等。Kong 网关的请求响应工作流,如下图所示:
Kong 的核心功能就是对现有的上游服务的 API 作反向代理。反向代理,官方的完整的文档参见[13]。现在我们来试验下 Kong 的反向代理功能,执行下面的命令:
1 | # 添加 service |
上面的第一条命令,通过调用 Kong 提供的 Admin API,让 Kong 创建了名为 example.service
的 service
,service
指向的上游服务是 http://httpbin.org
。第二条命令,在 example.service
上添加 route
规则,规则是让请求路径前缀为 /base64
的请求转发到这个 service
。来验证下,刚刚的 Kong 的配置:
1 | # 验证 Kong 配置结果 |
上文的 Kong 配置,等价的 nginx.conf
配置文件的写法是:
1 | server { |
除了前缀外,route
规则的 paths
字段也支持 PCRE 正则表达式,来看下示例:
1 | curl -XPOST -H 'Content-Type: application/json' \ |
上面的命令,添加 route
规则,设置的 paths
字段值为 /status/\d+
,让只有请求路径的中包含数字才能匹配。
1 | # 验证 Kong 配置结果 |
上文的反向代理指向的是单台的上游服务器,如果要指向多台上游服务器,实现负载均衡,要如何配置呢?负载均衡,Nginx 可以通过 upstream
指令实现,而类似的,Kong 通过创建 upstream
对象实现。
假设在服务器 192.168.2.100:80
和 192.168.2.101:80
上运行着本地版的 httpbin.org
的 REST API 服务(通过 docker run -p 80:80 kennethreitz/httpbin
)。执行下面的命令:
1 | # 添加 upstream |
上面的命令,先创建了 upstream
对象,虚拟主机名(virtual hostname)为 example.upstream
。然后在这个 upstream
上添加 target
,192.168.2.100:80
和 192.168.2.101:80
。再然后把 service
对象的 host
字段值设置为 example.upstream
。这样全部发送到这个 service
的请求都会被转发到 example.upstream
这个 upstream
,upstream
再执行负载均衡算法,把请求转发到最终的上游服务器。和 Nginx 一样,默认的负载均衡算法为加权轮询算法(weighted-round-robin)。
1 | # 验证 Kong 配置结果 |
上文的 Kong 配置,等价的 nginx.conf
配置文件的写法是:
1 | upstream example.upstream { |
关于 Kong 负载均衡的更多介绍,可以阅读官方文档[14],本文不再展开。
Kong 提供了很多插件,官方整理维护的全部插件列表,可以在官网上看到。全部插件分 8 大类:身份认证类插件(Authentication)、安全控制类插件(Security)、流量控制类插件(Traffic Control)、无服务器计算类插件(Serverless)、分析与监控类插件(Analytics & Monitoring)、协议转换类插件(Transformations)、日志记录类插件(Logging)、部署类插件(Deployment)。Kong 0.14.1 社区版默认绑定的预定义插件,全部 31 个,调用下面的 Admin API 可以查看:
1 | # Kong 社区版全部默认绑定的插件,共 31 个 |
现在我们来试下 Kong 的 basic-auth 插件,用来实现 HTTP Basic 认证(RFC 7617)。执行下面的命令,在上文的 example.service
的 service
上开启 basic-auth
插件:
1 | # 在 service 上开启 basic-auth 插件 |
这样全部到 example.service
的请求都需要进行 Basic 认证。再次请求之前的 /base64
接口,返回状态码 401 Unauthorized
:
1 | # 接口 HTTP 状态码返回 401 |
添加身份认证的凭证,添加 username/password:
1 | # 添加 consumer |
现在请求头上带上凭证,重新请求 /base64
接口,响应正常:
1 | $ curl -u 'test:123456' http://localhost:8000/base64/aGVsbG8ga29uZw== |
Kong 插件,除了绑定到 service
上外,也可以绑定在 route
和 consumer
上。如果开启插件时,service
、route
或 consumer
全部都不关联,就是全局范围开启插件,插件会在全部请求上运行。全局范围上开启 basic-auth
插件,命令如下:
1 | # 全局范围上开启 basic-auth 插件 |
关于 Kong 插件的更多介绍,可以阅读官方文档,本文不再展开。
Kong 基于 OpenResty,OpenResty 通过 ngx_http_lua_module
模块实现了在 Nginx 中内嵌 Lua 脚本的能力。Kong 插件,使用 Lua 脚本实现,全部默认加载的预定义插件对应的 Lua 源码(包括上文提到的 basic-auth 插件),可以在 Kong 项目仓库的 kong/plugins
目录下看到。
除了能使用 Kong 预定义插件,我们可以根据 Kong 插件开发文档[15],开发自定义插件。
如何开发插件,文本不展开。Kong 官方提供了自定义插件的模板代码,源码参见项目 kong-plugin[16]。另外,有兴趣也可以参考笔者提供的 Kong 自定义插件示例,源码参见 kong-plugin-demo[17]。
值得注意的是,Kong 使用 Lua 的 rxi/classic
模块来模拟 Lua 中的类,自定义 Kong 的插件时,实现 handler 需要继承 BasePlugin
class,目前最新的文档还是采用这种写法。不过,Kong 1.2 开始,Kong 内部的预定义实现的插件,废弃了继承 BasePlugin
class 的写法,参见 Pull Request #4590,“plugins handlers do not have to inherit from BasePlugin anymore #4590”。去掉对 BasePlugin
class 的继承后,在开启单个插件(key-auth)的场景下,压测 Kong 性能提升 6%,开启多个插件的场景,性能提升更高。
What is Kong? https://konghq.com/about-kong/ ↩ ↩
2015-04 Mashape 开源 API 网关——Kong https://www.infoq.cn/article/2015/04/kong/ ↩ ↩
2012-07 打造大集市:API交易网站Mashape正式推出 https://www.csdn.net/article/2012-07-31/2807936 ↩
2015-10 How Mashape Manages Over 15,000 APIs & Microservices https://stackshare.io/kong/how-mashape-manages-over-15000-apis-and-microservices ↩ ↩
2017-05 Mashape 和 RapidAPI 合并,组成全球最大的应用编程接口(API)集市! https://www.sohu.com/a/144114294_465914 ↩
2017-05 The API Marketplace Joins RapidAPI https://konghq.com/blog/the-api-marketplace-joins-rapidapi/ ↩
2017-10 Welcome Kong Inc. A New Name, a New Product, a New Era. https://konghq.com/blog/introducing-kong-inc/ ↩
2018-12 Kong 1.0 GA https://konghq.com/blog/kong-1-0-ga/ ↩
2020-01 Kong Gateway 2.0 GA https://konghq.com/blog/kong-gateway-2-0-0-released/ ↩
Documentation for Kong: DB-less and Declarative Configuration https://docs.konghq.com/1.1.x/db-less-and-declarative-config/ ↩
Kong Installation: Compile Source https://docs.konghq.com/install/source/ ↩
Build tools to package and release Kong https://github.com/Kong/kong-build-tools ↩
Documentation for Kong: Proxy Reference https://docs.konghq.com/0.14.x/proxy/ ↩
Documentation for Kong: Load Balancing Reference https://docs.konghq.com/0.14.x/loadbalancing/ ↩
Documentation for Kong: Plugin Development https://docs.konghq.com/0.14.x/plugin-development/ ↩
Kong 官方自定义插件的模板代码 https://github.com/Kong/kong-plugin ↩
Kong 自定义插件示例 https://github.com/yulewei/kong-plugin-demo ↩
浏览 SQL/JSON 标准草案可以发现,全部作者共有 9 人,这些作者来自两个公司,Oracle 和 IBM,而排前面的作者如 Jim Melton, Fred Zemke, Beda Hammerschmidt 都 Oracle 的专家(有兴趣可以看下他们的 LinkedIn)。正因为 SQL:2016 主要就是 Oracle 参与制定的,目前,Oracle 数据库对 SQL:2016 的支持也是最全的[11]。
MySQL 对 JSON 的支持,设计文档主要是 WL#7909: Server side JSON functions[12],另外还有 WL#8132: JSON datatype and binary storage format[13]、WL#8249: JSON comparator[14]、WL#8607: Inline JSON path expressions in SQL[15] 等。在 MySQL 开始 WL#7909 之时,SQL/JSON 标准草案已经公开,WL#7909 中也提及了这份标准,但是如果拿 MySQL 提供 JSON 的功能与 SQL:2016 比较,可以发现 MySQL 虽然融入了部分的设计,但并没有完全参考标准,定义的 JSON 函数多数有区别。
回到正题,下面来看下 MySQL 5.7 的 JSON 的用法。
MySQL 官方列出 JSON 相关的函数,完整列表如下[16]:
分类 | 函数 | 描述 |
---|---|---|
json 创建函数 | json_array() | 创建 json 数组 |
json_object() | 创建 json 对象 | |
json_quote() | 用双引号包裹 json 文档 | |
json 查询函数 | json_contains() | 判断是否包含某个 json 值 |
json_contains_path() | 判断某个路径下是否包 json 值 | |
json_extract() | 提取 json 值 | |
column->path | json_extract() 的简洁写法,5.7.9 开始支持 | |
column->>path | json_unquote(json_extract()) 的简洁写法,5.7.13 开始支持 | |
json_keys() | 把 json 对象的顶层的全部键提取为 json 数组 | |
json_search() | 按给定字符串关键字搜索 json,返回匹配的路径 | |
json 修改函数 | 5.7.9 废弃,改名为 json_array_append() | |
json_array_append() | 在 josn 文档末尾添加数组元素 | |
json_array_insert() | 在 josn 数组中插入元素 | |
json_insert() | 插入值(只插入新值,不替换旧值) | |
5.7.22 废弃,与 json_merge_preserve() 同义 | ||
json_merge_patch() | 合并 json 文档,重复键的值将被替换掉 | |
json_merge_preserve() | 合并 json 文档,保留重复键 | |
json_remove() | 删除 json 文档中的数据 | |
json_replace() | 替换值(只替换旧值,不插入新值) | |
json_set() | 设置值(替换旧值,或插入新值) | |
json_unquote() | 移除 json 值的双引号包裹 | |
json 属性函数 | json_depth() | 返回 json 文档的最大深度 |
json_length() | 返回 json 文档的长度 | |
json_type() | 返回 json 值的类型 | |
json_valid() | 判断是否为合法 json 文档 | |
json 工具函数 | json_pretty() | 美化输出 json 文档,5.7.22 新增 |
json_storage_size() | 返回 json 文档占用的存储空间,5.7.22 新增 |
官方文档对全部函数都作了充分解释并提供一定的示例代码。另外,官方博客也有极佳的相关介绍文章[17][18]。下文挑选了部分函数,演示它们的使用方法。
1 | -- 创建 tbl 表,字段 data 为 json 类型 |
上面的 SQL 示例简单验演示了创建 JSON 列以及写入并查询 JSON 数据,比较简单,就不做解释了。
如果要查询 JSON 文档中内容,提取 JSON 中的值,可以使用 json_extract() 函数。函数定义如下:
1 | json_extract(json_doc, path[, path] ...) |
先来看下 SQL 示例:
1 | -- 使用 json_extract() 函数查询 json 对象 |
示例中的 $.name
,使用的是 JSON 路径语法,用来提取 JSON 文档的内容。JSON 路径语法,源自 Stefan Goessner 的 JsonPath[19],不过 MySQL 作了简化。路径语法使用 $ 开头来表示整个 JSON 文档。如果要提取部分 JSON 文档,可以在路径后面添加选择符:
path
后上追加对象的键名称,可以获取这个键下成员。如果加键名称后,路径表达式非法,需要对键名称用双引号包裹(比如,键名称中包含空格的情况)path
后加上追加 [N]
,用于选择数组的第 N 个元素。数组索引从 0 开始。如果 path
下并不是数组,path[0]
获取结果就是 path
本身。*
和 **
通配符:.[*]
用于获取 JSON 对象的全部成员。[*]
用于获取 JSON 数组的全部元素。prefix**suffix
表示全部以 prefix
开始,以 suffix
结尾的路径。NULL
。假设 $
引用的是如下 JSON 数组:
1 | [3, {"a": [5, 6], "b": 10}, [99, 100]] |
$[0]
获取到的值为 3,$[1]
获取到 {"a": [5, 6], "b": 10}
,$[2]
获取到 [99, 100]
,$[3]
获取到 NULL
(因为不存在第 4 个元素)。
因为 $[1]
和 $[2]
获取的并非纯量(nonscalar),它们可以进一步使用路径访问到内嵌的值,比如:$[1].a
获取到 [5, 6]
,$[1].a[1]
获取到 6
,$[1].b
获取到 10
,$[2][0]
获取到 99
。
上文提到,如果追加键值名后,路径表达式非法,需要对键名称用双引号包裹。假设 $
引用的是如下 JSON 对象:
1 | {"name 1": "Will", "name 2": "Andy"} |
两个键都包含空格,需要加上双引号,才能使用路径表达式访问。$."name 1"
将获取到 Will
,而 $."name 2"
将获取到 Andy
。
现在来看下通配符的示例,假设 JSON 对象如下:
1 | {"a": {"b": 1}, "c": {"b": 2}, "d": [3, 4, 5]} |
使用 $.*
将获取到 [{"b": 1}, {"b": 2}, [3, 4, 5]]
;
使用 $.d[*]
将获取到 [3, 4, 5]
;
使用 $**.b
(对应 $.a.b
和 $.c.b
)将获取到 [1, 2]
。
MySQL 5.7.9 开始,官方支持 json_extract(column, path)
的简洁写法,内联 JSON 路径表达式 column->path
(WL#8607)。示例如下:
1 | -- 使用内联 json 路径表达式,查询 json 对象 |
本质上,这种写法是语法糖,column->path
等价于 json_extract(column, path)
,内联 JSON 路径表达式会在语法解析阶段被转换为 json_extract() 调用。另外,column->path
,存在以下限制[20]
即,1. 数据源必须是表字段,2. 路径表达式必须为字符串,3. SQL 语句中最多只支持一个。
现在来试验下这个限制,如果使用内联 JSON 路径表达式查询 MySQL 变量,将会报语法错误:
1 | mysql> set @j = '["a", "b"]'; |
假设数据如下:
1 | mysql> select * from tbl; |
来看下使用 ->
提取获得 JSON 值:
1 | mysql> select data -> '$.id', data -> '$.name', substr(data -> '$.name', 1, 1) from tbl; |
可以看到,对于 string 类型的 JSON 值,使用 json_extract()
或 ->
获取的都是被双引号包裹的字符串。MySQL 提供 json_unquote() 函数,用于去掉双引号包裹。另外,MySQL 支持 column->>path
语法,通过 ->>
操作符获取纯量(scalar)。column->>path
写法等价于 json_unquote( json_extract(column, path) )
或者 json_unquote(column -> path)
。来看下 SQL 示例:
1 | mysql> select data ->> '$.id' as id, data -> '$.name' as name, |
MySQL 这种区分 ->
和 ->>
的写法,怀疑是源自 Postgres。因为 Postgres 也分别提供了 ->
和 ->>
操作符,->
也是保留双引号(get JSON object field by key),而 ->>
才能获取实际的字符串值(get JSON object field as text)[21][22]。
在笔者看来,这种需要通过 json_unquote() 才能获取实际字符串值的写法完全没有必要,因为很难想到有需要保留双引号的使用场景,而就获取实际的字符串值才是多数情况。实际上,SQLite 的开发者也持有相同的想法。2015 年 10 月,SQLite 3.9 发布,开始支持 JSON 类型[23][24]。简单对比下,可以发现 SQLite 提供的 JSON 函数和 MySQL 极其相似,很多函数同名并且同语义。SQLite 也提供了 json_extract() 函数,与 MySQL 不同,SQLite 返回的是移除双引号后的字符串(the dequoted text for a JSON string value)。看下示例:
1 | sqlite> select json_extract('{"id": 1, "name": "Will"}', '$.name'); |
对于提取 JSON 文档中的纯量(scalar),SQL 标准定义了的 json_value() 函数,MySQL 没有支持,但 Oracle、MariaDB、MSSQL 都有支持。MariaDB 在兼容 MySQL 的同时也支持 SQL 标准,json_extract() 和 json_value() 在 MariaDB 下都可用。来看下 SQL 示例:
1 | MariaDB [testdb]> select * from tbl; |
除了上文的 json_extract() 函数,查询 JSON 文档相关的还有其他函数,如 json_contains()、json_contains_path()、json_keys()、json_search()。示例如下:
1 | mysql> set @j = '{"a": 1, "b": 2, "c": {"d": 4}}'; |
函数的完整定义和用法可以参考官方文档,本文不再一一举例说明。
对于 MySQL 的 JSON 类型的数据,若要修改数据,可以使用类似如下的 SQL:
1 | mysql> select * from tbl where data->'$.id' = 2; |
如果要修改 JSON 内部数据,是否可以通过 JSON 路径表达式直接赋值呢?答案是,不行,MySQL 不支持。
1 | -- 语法错误,不支持通过 JSON 路径表达式赋值,修改 JSON 数据 |
MySQL 提供了数个函数来修改 JSON 数据。我们先来看看 json_replace()、json_set() 和 json_insert() 这三个函数:
json_insert() 只能插入数据, json_replace() 只能更新数据,json_set() 能更新或插入数据。
替换值,json_replace() 示例:
1 | -- 使用 json_replace() 函数 |
设置值,json_set() 示例:
1 | -- 使用 json_set() 函数 |
插入值,json_insert() 示例:
1 | -- 使用 json_set() 函数 |
现在,我们来看下修改 JSON 数组的两个函数,json_array_insert() 和 json_array_append(),函数定义如下:
1 | json_array_insert(json_doc, path, val[, path, val] ...) |
json_array_insert(),参数 path
必须指向 JSON 数组某个位置的元素,若该位置存在值,将会把 val
插入该位置,然后其他元素向右移动;若该位置超出数组大小范围,将会把 val
插入到数组末尾。SQL 示例如下:
1 | mysql> set @j = '["a", {"b": [1, 2]}, [3, 4]]'; |
json_array_append(),如果参数 path
指向的 JSON 是数组,将在数组末尾添加元素;如果参数 path
指向的 JSON 是值或对象,该值或对象将被包裹为数组,然后在这个数组末尾添加元素。
1 | mysql> set @j = '["a", {"b": [1, 2]}, [3, 4]]'; |
除了上文提到的函数,还有 json_merge_patch()、json_merge_preserve()、json_remove() 这个些函数,可以参考官方文档的介绍,本文不再一一举例说明。
现在来看下根据 JSON 列查询表数据的执行计划,如下:
1 | mysql> explain select * from tbl where data -> "$.id" = 1 \G |
可以看到,因为没有加索引,访问类型是全表扫描 type: ALL
。来试下在 JSON 类型的 data
列上添加索引,会提示如下错误:
1 | mysql> alter table tbl add index (data); |
对于索引 JSON 类型列问题,MySQL 文档有如下阐述[2]:
JSON columns, like columns of other binary types, are not indexed directly; instead, you can create an index on a generated column that extracts a scalar value from the JSON column. See Indexing a Generated Column to Provide a JSON Column Index, for a detailed example.
就是说,不能直接在 JSON 列上创建索引;替代方式是,先创建提取 JSON 纯量的生成列(generated column),然后在这个生成列上创建索引。回过头来,ERROR 3152,这个报错提示信息其实让人有点困惑,对没仔细阅读文档的人来说,可能会误以为 MySQL 不支持索引 JSON 列(Bug #81364[25])。于是,在 MySQL 8.0 错误提示信息优化为:
ERROR 3152 (42000): JSON column '%s' supports indexing only via generated columns on a specified JSON path.
生成列以及在生成列上创建索引,是 MySQL 5.7 开始支持的新特性。但其实,在 SQL:2003 标准中,生成列就早已经被定义为可选特性,“Optional Features of SQL/Foundation:2003, T175 Generated columns”。这个特性在其他 DBMS 中很早就有支持。2007 年 9 月发布的 Oracle Database 11g 开始支持生成列,不过它们称之为称之为虚拟列(virtual column)。2008 年 8 月发布的 SQL Server 2008 开始支持计算列(computed column),实现的就是 SQL 标准中的生成列。在相近的时间点,MySQL 创建了WL#411: Computed virtual columns as MS SQL server has[26]。之后,MySQL 的社区贡献者 Andrey Zhakov 实现了 WL#411 描述的特性,并发布了实现的代码补丁[27][28][29]。可惜的是 MySQL 官方很长一段时间都没把这个补丁合并进来,直到 2015 年的 MySQL 5.7(7年后)才官方实现 WL#411[30],同时 WL#411 的标题也被更新为符合 SQL 标准术语的 “Generated columns”。与之相对比的是,2010 年 4 月发布的 MariaDB 5.2 就开始支持虚拟列[31],实现上同样也是基于 Andrey Zhakov 贡献的代码。关于生成列或虚拟列,Wikipedia 的 Virtual column
词条[32]总结了各大 DBMS 的支持情况,可以参阅。总结下,标准 SQL 定义生成列的语法和 SQL Server 2008、Oracle 11g、MariaDB、MySQL 的区别[30][33]:
1 | Standard MSSQL 2008 Oracle 11g MariaDB 10.1 MySQL 5.7 |
回到正题,我们现在来试试 MySQL 的生成列:
1 | -- 添加生成列 |
上面的示例,创建生成列 id
,生成列对应的表达式是 data -> "$.id"
。现在再试试在生成列 id
上,创建索引:
1 | -- 在生成列上创建索引 idx_id |
从上面的执行计划可以看到,查询条件用 id
或者 data -> "$.id"
都能使用索引 idx_id
。
内部实现上,保存到数据库的 JSON 数据并非以 JSON 文本存储,而是二进制格式,具体可以参见,WL#8132: JSON datatype and binary storage format,当然也可以直接阅读源码 json_binary.h[34]、json_binary.cc[35]或 doxygen[36]。
MySQL 的 JSON 二进制格式,其中有一点比较值得注意,WL#8132 提到:
The keys are sorted, so that lookup can use binary search to locate the key quickly.
就是,为了能利用二分搜索快速定位键,存入数据库的JSON 对象的键是被排序过的。来看下下面的 SQL:
1 | mysql> truncate tbl; |
上面的 SQL 可以看到,insert
写入时键并没有按次序排列,而用 select
将 JSON 数据反序列化读出,发现实际保存的键是有序的。排序规则是,先按字符串长度排序,若长度相同按字母排序。同样的,键关联的值,按键排序后的次序排列。对键排序,显然只能针对 JSON 对象,若要存储 JSON 数组,值按索引位置排序。
MySQL 5.7.22 新增 json_storage_size() 函数,用于返回 json 文档二进制表示占用的存储空间。先来看下 SQL 示例:
1 | mysql> select json_storage_size('"abc"'); |
WL#8132 给出了 JSON 二进制格式的 BNF 语法描述。参考这个语法描述,可以推算出上文示例中的 "abc"
、[42, "xy", "abc"]
、{"b": 42, "a": "xy"}
对应的二进制表示。先来看下 "abc"
纯量,语法推导过程如下:
1 | doc |
对应的二进制值,共 5 个字节,依次为 0x0c 0x03 0x61 0x62 0x63
,其中 0x61 0x62 0x63
,就是 16 进制表示的字符串 abc
。占用 5个字节,与 json_storage_size() 函数返回的结果一致。相应的语法树如下:
从二进制的角度看,纯量 "abc"
的 JSON 二进制表示如下:
[42, "xy", "abc"]
的推导过程,如下:
1 | doc |
[42, "xy", "abc"]
对应的二进制表示,共 21 个字节,依次为 0x02 0x03 0x00 0x14 0x00 0x06 0x2a 0x00 0x0c 0x0d 0x00 0x0c 0x10 0x00 0x02 0x78 0x79 0x03 0x61 0x62 0x63
。如下图:
相对来说,产生式 array ::= element-count size value-entry* value*
,是整个JSON 数组二进制表示语法的核心。element-count
,表示元素个数。上图中,第 4、5 个字节是 size
字段,十进制值为 20(0x14),是完整二进制表示去掉开头 type
字段后的大小(文档没有明确这个字段的含义,不过通过源码推断出来)。另外,value-entry
由 type
和 offset-or-inlined-value
字段组成。type
很好理解,不做解释。offset-or-inlined-value
字段,官方文档给出了含义,含义如下:
1 | // This field holds either the offset to where the value is stored, |
就是说,如果实际要保存的值足够小,将直接内联在这个字段中,否则将保存偏移量(offset),也就是指向实际值的指针。在示例中,保存 xy
对应的 offset 值为 13(0x0d),指向的相对地址是 14。因为这里的 offset 并不是以相对地址 0 为基准地址,是以相对地址 1 为基准地址(图中箭头 B 指向的位置),所以偏移量是 13 而不是 14(这个字段的明确含义也是从源码推断而来)。类似的,保存 abc
对应的 offset 值为 16(0x10),指向的相对地址是 17。
阅读文档容易发现,element-count
、size
、offset
字段占用的字节大小是固定的,小 JSON(64KB 以内)是 2 字节,大 JSON 是 4 字节。所以,若要查找 JSON 数组的第 pos
个元素的 value-entry
的偏移量,可以使用下面的式子快速定位:
1 | entry_offset = offset_size * 2 + (1 + offset_size) * pos |
JSON 数组二进制表示的其他字段比较容易理解,文档都有解释,就不展开阐述了。
现在来看下,JSON 对象 {"b": 42, "a": "xy"}
的二进制表示,如下图:
对于 JSON 对象二进制表示的语法,核心的产生式是 object ::= element-count size key-entry* value-entry* key* value*
。element-count
、size
和 value-entry
字段,在 JSON 数组中也有,不再赘述。而 key-entry
字段,类似于 value-entry
。key-entry
中的 key-offset
保存的是偏移量,是指向键的指针。另外,正如上文提到的 MySQL 会对 JSON 键排序,所以上图示例的第 20 和 21 个字节值分别是 0x61
和 0x62
,即 a
和 b
,而非 b
和 a
。同样的,键关联的值,按键排序后的次序排列,依次是 "xy"
和 42
。
MySQL 5.7 Reference Manual, What Is New in MySQL 5.7 https://dev.mysql.com/doc/refman/5.7/en/mysql-nutshell.html ↩
MySQL 5.7 Reference Manual, 12 Data Types, 12.6 The JSON Data Type http://dev.mysql.com/doc/refman/5.7/en/json.html ↩ ↩
2012-09 PostgreSQL 9.2 released https://www.postgresql.org/about/news/postgresql-92-released-1415/ ↩
JSON Support in Oracle Database 12c Release 1 (12.1.0.2) https://oracle-base.com/articles/12c/json-support-in-oracle-database-12cr1 ↩
Oracle Database 12c Release 1 (12.1.0.2) New Features, 1.9 JSON Support https://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT505 ↩
Facebook DocStore: Document column type [Deprecated] https://github.com/facebook/mysql-5.6/wiki/Document-column-type-[Deprecated] ↩
2015-04 DocStore: Document Database for MySQL at Facebook (slides) https://web.archive.org/web/20161022061349/https://www.percona.com/live/mysql-conference-2015/sites/default/files/slides/Facebook DocStore Percona 2015.pdf ↩
2014-03 SQL/JSON Proposals, Part 1 https://www.wiscorp.com/pub/DM32.2-2014-00024R1_JSON-SQL-Proposal-1.pdf ↩
2014-03 SQL/JSON Proposals, Part 2 https://www.wiscorp.com/pub/DM32.2-2014-00025r1-sql-json-part-2.pdf ↩
2014-03 SQL Support for JSON (slides) https://web.archive.org/web/20150919002536/http://jtc1bigdatasg.nist.gov/_workshop/08_SQL_Support_for_JSON_abstract.pdf ↩
2017-06 What’s New in SQL:2016 https://modern-sql.com/blog/2017-06/whats-new-in-sql-2016 ↩
WL#7909: Server side JSON functions https://dev.mysql.com/worklog/task/?id=7909 ↩
WL#8132: JSON datatype and binary storage format https://dev.mysql.com/worklog/task/?id=8132 ↩
WL#8249: JSON comparator https://dev.mysql.com/worklog/task/?id=8249 ↩
WL#8607: Inline JSON path expressions in SQL https://dev.mysql.com/worklog/task/?id=8607 ↩
MySQL 5.7 Reference Manual, 13 Functions and Operators, 13.16 JSON Functions https://dev.mysql.com/doc/refman/5.7/en/json-functions.html ↩
2015-04 JSON Labs Release: JSON Functions, Part 1 — Manipulation JSON Data http://mysqlserverteam.com/json-labs-release-json-functions-part-1-manipulation-json-data/ ↩
2015-04 JSON Labs Release: JSON Functions, Part 2 — Querying JSON Data http://mysqlserverteam.com/mysql-5-7-lab-release-json-functions-part-2-querying-json-data/ ↩
JSONPath - XPath for JSON https://goessner.net/articles/JsonPath/ ↩
2015-10 Inline JSON Path Expressions in MySQL 5.7 http://mysqlserverteam.com/inline-json-path-expressions-in-mysql-5-7/ ↩
PostgreSQL 11.10 Documentation, 9.15. JSON Functions and Operators https://www.postgresql.org/docs/11/functions-json.html ↩
What is the difference between ->>
and ->
in Postgres SQL? https://stackoverflow.com/a/47270495 ↩
2015-10 SQLite Release 3.9.0 https://sqlite.org/releaselog/3_9_0.html ↩
SQLite: The JSON1 Extension https://www.sqlite.org/json1.html ↩
Bug #81364: Indexing JSON column should suggest generated columns https://bugs.mysql.com/bug.php?id=81364 ↩
WL#411: Computed virtual columns as MS SQL server has https://web.archive.org/web/20080917094638/http://forge.mysql.com/worklog/task.php?id=411 ↩
Bug #46491: Patch: Virtual columns (WL#411) https://bugs.mysql.com/bug.php?id=46491 ↩
2008-09 MySQL virtual columns https://datacharmer.blogspot.com/2008/09/mysql-virtual-columns.html ↩
MySQL virtual columns preview https://web.archive.org/web/20120629093123/http://forge.mysql.com/wiki/MySQL_virtual_columns_preview ↩
WL#411: Generated columns https://dev.mysql.com/worklog/task/?id=411 ↩ ↩
MariaDB: Generated (Virtual and Persistent/Stored) Columns https://mariadb.com/kb/en/generated-columns/ ↩
Virtual column https://en.wikipedia.org/wiki/Virtual_column ↩
2016-02 Generated columns in MariaDB and MySQL https://planet.mysql.com/entry/?id=5994068 ↩
MySQL 5.7 Source Code: sql/json_binary.h https://github.com/mysql/mysql-server/blob/5.7/sql/json_binary.h ↩
MySQL 5.7 Source Code: sql/json_binary.cc https://github.com/mysql/mysql-server/blob/5.7/sql/json_binary.cc ↩
MySQL 8.0 Source Code Documentation: json_binary.h File Reference (doxygen) https://dev.mysql.com/doc/dev/mysql-server/8.0.16/json__binary_8h.html#details ↩
Elastic Stack 是逐步发展而来的,一开始只有 Elasticsearch,专注做搜索引擎,2013 年 1 月 Kibana 及其作者 Rashid Khan 加入 Elasticsearch 公司,同年 8 月 Logstash 及作者 Jordan Sissel 也加入,原本的非官方的 ELK Stack,正式成为官方用语。2015 年 3 月,旧金山举行的第 1 届 Elastic{ON} 大会上,Elasticsearch 公司改名为 Elastic。两个月后,Packetbeat 项目也加入 Elastic,Packetbeat 和 Filebeat(之前叫做 Logstash-forwarder,由 Logstash 作者 Jordan Sissel 开发)项目被整合改造为 Beats。加上 Beats 以后,官方不知道如何将 “B” 和 E-L-K 组合在一起(用过 ELKB 或 BELK),ELK Stack 于是改名为 Elastic Stack,并在 2016 年 10 月正式发布 Elastic Stack 5.0 [ ref1, ref2, ref3 ]。
Logstash(home, github)最初是来自 Dreamhost 运维工程师 Jordan Sissel 的开源项目,是管理事件和日志的工具,能够用于采集数据,转换数据,然后将数据发送到您最喜欢的“存储库”中(比如 Elasticsearch)。Logstash 使用 JRuby 开发,2011 年 5 月发布 1.0 版本。2013 年 8 月 Jordan Sissel 带着 Logstash 加入 Elasticsearch 公司,Logstash 成为 Elastic Stack 一员。
在 Ubuntu 下安装、启动 Logstash 可以使用下面命令:
1 | $ sudo apt-get install logstash # 安装 logstash |
安装完成后,二进制文件在 /usr/share/logstash/bin
目录下,配置文件位于 /etc/logstash
目录,日志输出位于 /var/log/logstash
目录,其他详细的目录位置的分布情况,可以阅读官方文档。
Logstash 管道(pipeline)由 input
、filter
和 output
三个元素组成,其中 input
和 output
必须设置,而 filter
可选。input
插件从数据源中消费数据,filter
插件按指定方式修改数据,output
插件将数据写入特定目标中 [ doc ]。
现在来试试 Logstash 下 Hello Wolrd 级别的示例,运行下面命令:
1 | $ sudo /usr/share/logstash/bin/logstash -e 'input { stdin { } } output { stdout {} }' |
-e
选项能够使 Logstash 直接在命令行中设置管道配置。示例中 input
插件为 stdin (标准输入),output
插件是 stdout (标准输出)。若在控制台输入 hello world
,相应的控制台将输出:
1 | { |
值得注意的是,输出内容编码格式默认为 rubydebug
,使用 Ruby 的 "awesome_print" 库打印。另外,响应输出 @timestamp
字段的值为 2019-01-12T05:52:56.291Z
,这是 ISO 8601 时间格式,时区是 0(zero),和北京时间相差 8 个小时。
输出内容编码格式,可以通过 codec
指令指定。除了默认的 rubydebug
外,官方还支持其他 20 多种编码格式,参见 doc。若把编码格式改为 json
,即 stdout { codec => json }
,输出结果将变成:
1 | {"message":"hello world","@version":"1","@timestamp":"2019-01-12T05:52:56.291Z","host":"ubuntu109"} |
管道配置也可以保存到文件中,以 *.conf
作为文件后缀,比如保存为 test-stdin-stdout.conf
:
1 | input { |
启动 logstash 时,-f
选项用于指定管道配置文件的路径:
1 | $ sudo /usr/share/logstash/bin/logstash -f ~/test-stdin-stdout.conf |
默认情况下,在启动 logstash 后,若再修改管道配置文件,新的修改需要重启 logstash 才能加载生效。在开发调试时,不太方便。解决这个问题,可以使用 -r
命令行选项。开启这个选项后,只要确定配置文件已经发生变更,便会自动重新加载配置文件。
上文日志是控制台输入的,但真实情况日志在日志文件中,要想使用 Logstash 读取日志文件,官方提供了 file 输入插件。管道配置文件示例 test-file-stdout.conf
,如下:
1 | input { |
启动 Logstash:
1 | $ sudo /usr/share/logstash/bin/logstash -r -f ~/test-file-stdout.conf |
配置文件中的 path
指令用于指定日志文件的路径。start_position
指令设置 Logstash 启动时读取日志文件的位置,可以设置为 beginning
或者 end
,默认是 end
,即文件末尾。
为了跟踪每个输入文件中已处理了哪些数据,Logstash 文件输入插件会使用名为 sincedb 的文件来记录现有位置。由于我们的配置用于开发,所以我们希望能够重复读取文件,并进而希望禁用 sincedb 文件。在 Linux 系统上,将 sincedb_path
指令设置为 “/dev/null” 即可禁用。若没有禁用,默认保存的 sincedb 文件将在 /usr/share/logstash/data/plugins/inputs/file/
目录下。
上文的例子,做的核心事情就是把日志行转换到 message
字段,并附加某些元数据,如 @timestamp
。如果要想解析自己的日志,把非结构化日志转换结构换日志,有两个过滤器特别常用:dissect 会根据分界符来解析日志,而 grok 则会根据正则表达式匹配来运行。
如果数据结构定义非常完善,dissect 过滤插件的运行效果非常好,而且运行速度非常快捷高效。同时,其也更加容易上手,对于不熟悉正则表达式的用户而言,更是如此。通常而言,grok 的功能更加强大,而且可以处理各种各样的数据。然而,正则表达式匹配会耗费更多资源,而且速度也会慢一些,如果未能正确进行优化的话,尤为如此。
现在先来看下 grok 过滤插件。grok 模式的基本语法是 %{SYNTAX:SEMANTIC}
,SYNTAX
是用于匹配数据的模式(或正则表达式)名称,SEMANTIC
是标识符或字段名称。Logstash 提供了超过 120 种默认的 grok 模式,全部预定义的模式可以在 github 上找到。典型的预定义模式(非完整列表)[ github ]:
比如,3.44
可以使用 NUMBER
模式进行匹配,192.168.2.104
可以使用 IP
模式。%{NUMBER:num} %{IP:client}
模式,将会用 NUMBER
模式把 3.44
识别为 num
字段,用 IP
模式把 192.168.2.104
识别为 client
字段。默认情况识别获得的字段值是字符串类型,grok 插件支持把类型转换为 int
或 float
。要想把 3.44
转换为浮点数,可以使用 %{NUMBER:num:float}
。
假设有如下日志内容:
1 | Will, yulewei@gmail.com, 42, 1024, 3.14 |
grok 匹配表达式可以写成这样:
1 | %{WORD:name}, %{EMAILADDRESS:email}, %{NUMBER:num1}, %{NUMBER:num2:int}, %{NUMBER:pi:float} |
即,在这一行日志中依次提取出,name
、email
、num1
、num2
和 pi
字段。完整的 filter
过滤器配置的写法:
1 | filter { |
启动 logstash:
1 | $ sudo /usr/share/logstash/bin/logstash -r -f ~/test-file-grok-stdout.conf |
控制台将输出:
1 | { |
Logstash 提供了超过 120 种默认的 grok 模式,基本上满足大多数使用场景。若没有符合要求的预定义的模式,可以使用 Oniguruma 语法指定正则表达式:
1 | (?<field_name>the pattern here) |
上文中的 %{WORD:name}
和 %{EMAILADDRESS:email}
,等价的正则表达式的写法如下 [ ref1, ref2 ]:
1 | (?<name>\w+) |
调试 grok 匹配表达式容易出错,官方提供可视化的 Grok Debugger 工具,提高调试效率。
现在来看下,真实的日志解析案例,使用 grok 过滤插件解析 http 服务器日志。通用日志格式(Common Log Format),是 http 服务器的标准的日志格式。对通用日志格式扩展,加上额外的 referer 和 user-agent 字段,称为组合日志格式(Combined Log Format)。两种日志格式包含的字段如下:
1 | # 通用日志格式 |
Apache 和 Nginx 服务器默认的日志格式,采用的就是通用日志格式或者组合日志格式。解析这两种日志格式,Logstash 提供预定义模式 COMMONAPACHELOG 和 COMBINEDAPACHELOG。
典型的 nginx 日志例子:
1 | 192.168.2.104 - - [13/Jan/2019:02:01:15 +0800] "GET /images/avatar.png HTTP/1.1" 200 266975 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:64.0) Gecko/20100101 Firefox/64.0" |
管道配置文件示例 test-file-grokhttp-stdout.conf
,如下:
1 | input { |
示例中使用了 grok 的预定义模式 COMBINEDAPACHELOG。另外,remove_field
指令用于把输出事件中某字段删除,示例中是 message
字段。
启动 logstash:
1 | $ sudo /usr/share/logstash/bin/logstash -r -f ~/test-file-grokhttp-stdout.conf |
解析获得的结构化数据,如下:
1 | { |
和 grok 过滤插件使用正则表达式提取字段不同,dissect 过滤插件使用分界符切割来提取字段。由于没有使用正则表达式,运行速度相对 grok 快很多。使用 dissect 过滤插件时,需要指明提取字段的顺序,还要指明这些字段之间的分界符。过滤插件会对数据进行单次传输,并匹配模式中的分界符。同时,过滤插件还会将分界符之间的数据分配至指定字段。过滤插件不会对所提取数据的格式进行验证。
现在看下示例,test-dissect.log
文件内容如下:
1 | Will, yulewei@gmail.com, 42, 1024, 3.14 |
dissect 匹配规则可以写成这样:
1 | %{name}, %{email}, %{num1}, %{num2}, %{num3} |
完整的配置文件,test-file-dissect-stdout.conf
:
1 | input { |
和 grok 插件一样,默认提取的字段是字符串类型。配置文件中的 convert_datatype
指令用于将类型转为 int
或 float
。
启动 logstash:
1 | $ sudo /usr/share/logstash/bin/logstash -r -f ~/test-file-dissect-stdout.conf |
输出结果:
1 | { |
上文举的例子全部都是,输出控制台,使用 stdout
输出插件,没有实用价值。Elastic Stack 的核心其实是 Elasticsearch,使用Elasticsearch 搜索和分析日志。想要将数据发送到 Elasticsearch,可以使用 elasticsearch 输出插件。
如果没有安装 Elasticsearch,参考官方文档按步骤安装。唯一要注意的是,在执行 apt install
命令前,必须先添加 elastic 的软件源地址,否则无法正常启动。核心命令如下:
1 | $ sudo apt-get install elasticsearch # 安装 elasticsearch |
默认情况下 elasticsearch 服务器绑定的 ip 地址是回环地址 127.0.0.1
,若想绑定特定 ip 地址,可以修改 /etc/elasticsearch/elasticsearch.yml
配置文件中的 network.host 选项:
1 | network.host: 192.168.2.109 |
修改完成并重启后,elasticsearch 服务器访问地址从 http://localhost:9200/
变成 http://192.168.2.109:9200/
。
现在来看下 elasticsearch 输出插件。示例,test-file-elasticsearch.conf
:
1 | input { |
示例的 elasticsearch
输出插件使用了 hosts
和 index
指令。hosts
指令,用于指定 elasticsearch 服务器的地址。而 index
指令,用于指定 elasticsearch 索引的名称模式,该指令默认值为 logstash-%{+YYYY.MM.dd}
。在字符串内部的 %{...}
,是 Logstash 字符串插值语法,官方称之为 sprintf format
[ doc ]。+YYYY.MM.dd
,用于指定 @timestamp
的格式化的格式。logstash-%{+YYYY.MM.dd}
,格式化后最终生成的值可能将是 logstash-2019.01.13
。
启动 logstash:
1 | $ sudo /usr/share/logstash/bin/logstash -r -f ~/test-file-elasticsearch.conf |
查看在 elasticsearch 上新创建的 logstash-*
索引以及从 logstash 同步过来的日志数据:
1 | $ curl http://192.168.2.109:9200/_cat/indices |
可以看到,新的索引名为 logstash-2019.01.13
。同步过来的日志记录全部有 5 条,第 1 条日志的 message
内容是 hello world
。
Kibana,能够对 Elasticsearch 中的数据进行可视化,是 Elastic Stack 的窗口。在 Ubuntu 下安装 Kibana 可以使用下面命令:
1 | $ sudo apt-get install kibana # 安装 kibana,当前最新版为 6.5.4 |
默认配置下,kibana 服务访问地址是 http://localhost:5601/
,连接的 elasticsearch 地址是 http://localhost:9200
,这两个配置分别由 server.host
和 elasticsearch.url
控制 [ doc ]。上文尝试过把 elasticsearch 服务 ip 地址绑定到 192.168.2.109
。现在来试下绑定 ip 地址到 kibana,编辑配置文件 /etc/kibana/kibana.yml
,修改为:
1 | server.host: "192.168.2.109" |
使用 sudo systemctl restart kibana.service
重启后,kibana 服务访问地址变成 http://192.168.2.109:5601/
。
elasticsearch 服务器上存在索引 logstash-2019.01.13
,要想把这个索引导入到 kibana,参考官方教程即可。点击 Management
菜单,然后创建索引模式(index pattern)。索引模式可以直接为 logstash-2019.01.13
,这样匹配单个索引。若要匹配多个时间的 logstash 索引,可以使用通配符 *
,比如 logstash-*
。如果要匹配全部 2019 年 01 月的索引,可以写成 logstash-2019.01*
。完成索引模式定义后,便可以在 Discover
菜单下查看索引,如图:
Filebeat,轻量型日志采集器 [ home ]。其前身是由 Logstash 作者 Jordan Sissel 开发的 Logstash Forwarder。Logstash Forwarder 项目因为和收购过来的 Packetbeat 项目功能相近,并且都是 Go 语言开发,就一起被整合改造为 Beats [ ref ]。
我们知道,Logstash 使用 JRuby 开发,运行依赖 JVM,会消耗较多的系统资源。为了减少系统资源(CPU、内存和网络)的使用,Logstash Forwarder 改用 Go 语言开发。另外,在功能上也做了精简,只做单一的数据传输,不像 Logstash 有数据过滤能力。Logstash 类似于功能多样的“瑞士军刀”,能提供从多个数据源加载数据的功能,使用各种强大的插件来处理日志,并提供将多个来源的输出数据进行存储的功能。简而言之,Logstash 提供数据 ETL(数据的提取、变换和加载)的功能;而 Beats 是轻量级的数据传输工具,能将数据传输到 Logstash 或 Elasticsearch 中,其间没有对数据进行任何转换 [ Gupta2017 ]。Filebeat 和 Logstash 的关系如下图所示 [ logz.io ]:
安装 filebeat 很简单,参考官方文档即可,核心命令如下:
1 | $ sudo apt-get install filebeat # 安装 filebeat |
修改 filebeat 配置文件 /etc/filebeat/filebeat.yml,示例如下:
1 | filebeat.inputs: |
filebeat.inputs
选项用于配置日志的输入方式。子选项 type
支持 log
、stdin
、redis
、udp
、tcp
等,示例中使用了 log
,表明从日志文件输入。
output
选项用于配置日志的输出方式,配置支持 elasticsearch、logstash、kafka、redis、file、console 等,一次只能选择配置其中某一个。示例配置了 output.elasticsearch.hosts
,指定日志输出目标 elasticsearch 的主机地址。output.elasticsearch.index
可以用来指定索引 index 名称模式,默认是 filebeat-%{[beat.version]}-%{+yyyy.MM.dd}
(比如 filebeat-6.5.4-2019.01.12
)[ doc ]。
完成 filebeat.yml
修改后,重启 filebeat,将可以看到,在 elasticsearch 上新创建的 filebeat-*
索引:
1 | $ curl http://192.168.2.109:9200/_cat/indices |
上文的示例直接把 Filebeat 采集的日志传输到 Elasticsearch,日志数据并没有被解析或者转换。若想解析和转换日志,需要在Filebeat 和 Elasticsearch 中间引入 Logstash。现在看下把日志输出到 Logstash 的示例配置文件,filebeat.yml
示例:
1 | filebeat.inputs: |
filebeat.inputs
和上文的示例一样。不同的是,把 output.elasticsearch.hosts
改成了 output.logstash.hosts
,指定日志输出目标 Logstash 的主机地址。5044
这个端口是 Logstash 用于监听 Filebeat 的端口。
现在来看下 Logstash 的管道配置文件,示例 test-beats-stdout.conf
:
1 | input { |
示例中,使用了 beats 输入插件,配置的端口就 filebeat.yml
中指定的 5044
。输出插件为 stdout
,即把 Logstash 采集到日志输出到控制台。
重启 filebeat 和 logstash:
1 | $ cat test.log # 查看日志文件内容 |
控制台输出:
1 | { |
真实场景下,日志文件可能分布在多台服务器上,同一台服务器上也可能分布着不同来源类型的日志。现在我们来尝试下,使用 Filebeat 把两个日志文件各自采集到两个不同的 Elasticsearch 索引中,并用 Kibana 可视化。有两个日志文件 test-beats1.log
和 test-beats2.log
,内容如下:
1 | $ cat test-beats1.log |
filebeat.yml
配置示例:
1 | filebeat.inputs: |
示例配置文件使用了 filebeat.inputs.fields
选项,fields 选项用于在日志事件输出中添加字段。添加的字段名可以任意指定,示例中名为 log_type
。因为现在在 filebeat 配置中同时导入两个日志文件,输出到同一个 logstash 中。使用这个额外字段是为了区分日志是来自 test-beats1.log
还是 test-beats2.log
。示例中,第 1 个日志事件输出的 log_type
字段值配置为 test1
, 第 2 个日志配置为 test2
。
管道配置文件示例,test-beats-elasticsearch.conf
:
1 | input { |
输出插件同时使用了 elasticsearch
和 stdout
。配置文件中的 elasticsearch
输出插件的 index
指令被设置为 filebeat-%{[fields][log_type]}-%{+YYYY.MM.dd}
。[fields][log_type]
引用的是在 filebeat.yml
的 filebeat.inputs.fields
选项添加的 log_type
字段(关于在配置文件引用字段的语法,可以参考官方文档)。根据 log_type
字段不同,把日志将输出到不同的索引中。因为 filebeat.yml
配置文件中设置的 log_type
字段是 test1
或者 test2
,所以最终生成的索引名是 filebeat-test1-*
或者 filebeat-test1-*
。filebeat-test1-*
索引中全部日志数据来自 test-beats1.log
日志文件,filebeat-test2-*
索引数据来自 test-beats2.log
。
启动 filebeat 和 logstash:
1 | $ sudo systemctl restart filebeat.service |
控制台输出:
1 | ... |
查看在 elasticsearch 上新创建的 filebeat-test1-*
和 filebeat-test1-*
索引:
1 | $ curl http://192.168.2.109:9200/_cat/indices/filebeat-* |
在 kibana 上查看收集的日志:
整体架构上,如下图所示 [ doc ]:
附注:本文中提到的配置文件,可以在 github 上访问得到,elastic-stack-conf。
java.lang.instrument
(doc)包,该包为检测(instrument) Java 程序提供 API,比如用于监控、收集性能信息、诊断问题。通过 java.lang.instrument
实现工具被称为 Java Agent。Java Agent 可以修改类文件的字节码,通常是,在字节码方法插入额外的字节码来完成检测。关于如何使用 java.lang.instrument
包,可以参考 javadoc 的包描述(en, zh)。开发 Java Agent 的涉及的要点如下图所示 [ ref ]
Java Agent 支持两种方式加载,启动时加载,即在 JVM 程序启动时在命令行指定一个选项来启动代理;启动后加载,这种方式使用从 JDK 1.6 开始提供的 Attach API 来动态加载代理。
现在创建命名为 proj-demo 的 gradle 项目,目录布局如下:
1 | $ tree proj-demo |
com.demo.App
类的实现:
1 | public class App { |
运行 com.demo.App
,每隔 1 秒输出 hello world
:
1 | $ gradle build |
现在创建名称为 proj-premain 的 gradle 项目,com.demo.MyPremain
类实现 premain
方法:
1 | package com.demo; |
META-INF/MANIFEST.MF
文件指定 Premain-Class
属性:
1 | jar { |
打包生成 proj-premain.jar
,这个 jar 包就是 javaagent 代理。现在来试试运行 com.demo.App
时,启动这个 javaagent 代理。根据 javadoc 的描述,可以将以下选项添加到命令行来启动代理:
1 | -javaagent:jarpath[=options] |
指定 -javaagent:"proj-premain.jar=hello agent"
,传入的 agentArgs
为 hello agent
,再次运行 com.demo.App
:
1 | $ java -javaagent:"proj-premain.jar=hello agent" -cp "target/classes/java/main" com.demo.App |
可以看到,在运行 main
之前,运行了 premain
方法,即先输出 hello agent
,每隔 1 秒输出 hello world
。
在实现 premain
时,除了能获取 agentArgs
参数,还能获取 Instrumentation
实例。Instrumentation
类提供 addTransformer
方法,用于注册提供的转换器 ClassFileTransformer
:
1 | // 注册提供的转换器 |
ClassFileTransformer
是抽象接口,唯一需要实现的是 transform
方法。在转换器使用 addTransformer
注册之后,每次定义新类时(调用 ClassLoader.defineClass
)都将调用该转换器的 transform
方法。该方法签名如下:
1 | // 此方法的实现可以转换提供的类文件,并返回一个新的替换类文件 |
操作字节码可以使用 ASM、Apache BCEL、Javassist、cglib、Byte Buddy 等库。下面示例代码,使用 BCEL 库实现名为 GreetingTransformer
转换器。该转换器实现的逻辑就是,将 com.demo.App.getGreeting()
方法输出的 hello world
,替换为输出 premain
方法的传入的参数 agentArgs
。
1 | public class MyPremain { |
1 | import org.apache.bcel.*; |
最早 JDK 1.5发布 java.lang.instrument
包时,agent 是必须在 JVM 启动时,通过命令行选项附着(attach)上去。但在 JVM 正常运行时,加载 agent 没有意义,只有出现问题,需要诊断才需要附着 agent。JDK 1.6 实现了 attach-on-demand(按需附着)[ JDK-4882798 ],可以使用 Attach API 动态加载 agent [ oracle blog, javadoc ]。这个 Attach API 在 tools.jar
中。JVM 启动时默认不加载这个 jar 包,需要在 classpath 中额外指定。使用 Attach API 动态加载 agent 的示例代码如下:
1 | import com.sun.tools.attach.VirtualMachine; |
启动时加载 agent,-javaagent
传入的 jar 包需要在 MANIFEST.MF
中包含 Premain-Class
属性,此属性的值是 代理类 的名称,并且这个 代理类 要实现 premain
静态方法。启动后加载 agent 也是类似,通过 Agent-Class
属性指定 代理类,代理类 要实现 agentemain
静态方法。agent 被加载后,JVM 将尝试调用 agentmain
方法。
上文提到每次定义新类(调用 ClassLoader.defineClass
)时,都将调用该转换器的 transform
方法。对于已经定义加载的类,需要使用重定义类(调用 Instrumentation.redefineClass
)或重转换类(调用 Instrumentation.retransformClass
)。
1 | // 注册提供的转换器。如果 canRetransform 为 true,那么重转换类时也将调用该转换器 |
重定义类(redefineClass)从 JDK 1.5 开始支持,而重转换类(retransformClass)是 JDK 1.6 引入。相对来说,重转换类能力更强,当存在多个转换器时,重转换将由 transform 调用链组成,而重定义类无法组成调用链。重定义类能实现的逻辑,重转换类同样能完成,所以保留重定义类方法(Instrumentation.redefineClass
)可能只是为了向后兼容 [ stackoverflow ]。
实现 agentmain 的示例代码如下,其中 GreetingTransformer
转换器的类定义和上文一样。
1 | public class MyAgentMain { |
MANIFEST.MF
文件配置:
1 | jar { |
需要注意的是,和定义新类不同,重定义类和重转换类,可能会更改方法体、常量池和属性,但不得添加、移除、重命名字段或方法;不得更改方法签名、继承关系 [ javadoc ]。这个限制将来可能会通过 “JEP 159: Enhanced Class Redefinition” 移除 [ ref ]。
Byte Buddy(home, github, javadoc),运行时的代码生成和操作库,2015 年获得 Oracle 官方 Duke's Choice award,提供高级别的创建和修改 Java 类文件的 API,使用这个库时,不需要了解字节码。另外,对 Java Agent 的开发 Byte Buddy 也有很好的支持,可以参考 Byte Buddy 作者 Rafael Winterhalter 写的介绍文章 [ ref1, ref2 ]。
上文使用 BCEL 实现的 GreetingTransformer
,现在改用 Byte Buddy,会变得非常简单。实现 premain
示例代码:
1 | public static void premain(String agentArgs, Instrumentation inst) { |
实现 agentmain
:
1 | public static void agentmain(String agentArgs, Instrumentation inst) { |
另外,Byte Buddy 对 Attach API 作了封装,屏蔽了对 tools.jar
的加载,可以直接使用 ByteBuddyAgent
类:
1 | ByteBuddyAgent.attach(new File(agentJar), jvmPid, options); |
上文中的 AgentLoader
,可以使用这个 API 简化,实现的完整示例参见 AgentLoader2。
Byte Buddy 的 github 的 README 文档提供了一个性能计时拦截器的代码示例,能对某个方法的运行耗时做统计。现在我们来看下是如何实现的。假设 com.demo.App2
类如下:
1 | public class App2 { |
使用 Byte Buddy 实现计时拦截器的 agent,如下:
1 | public class TimerAgent { |
1 | public class TimingInterceptor { |
对 getGreeting
方法进行性能剖析,运行结果如下:
1 | $ java -javaagent:"proj-byte-buddy.jar=get.*" -cp "target/classes/java/main" com.demo.App2 |
示例代码中的 premain
参数 agentArgs
用于指定需要剖析性能的方法名,支持正则表达式。当实际参数传入 get.*
时,匹配到 getGreeting
方法。上面的示例,使用的是 Byte Buddy 的方法委托 Method Delegation API [ javadoc ]。Delegation API 实现原理就是,将被拦截的方法委托到另一个办法上,如下左图所示(图片来自 Rafael Winterhalter 的 slides)。这种写法会修改被代理类的类定义格式,只能用在启动时加载 agent,即 premain
方式代理。
若要通过 Byte Buddy 实现启动后动态加载 agent,官方提供了 Advice API [ javadoc ]。Advice API 实现原理上是,在被拦截方法内部的开始和结尾添加代码,如下右图所示。这样只更改了方法体,不更改方法签名,也没添加额外的方法,符合重定义类(redefineClass)和重转换类(retransformClass)的限制。
现在来看下使用 Advice API 实现性能定时器的代码示例:
1 | public class TimingAdvice { |
1 | public static void agentmain(String agentArgs, Instrumentation inst) { |
若对 com.demo.App
类,动态加载这个 Advice API 实现的 agent,getGreeting()
方法将会被重定义为(真正的实现可能稍有不同,但原理一致):
1 | public static String getGreeting() { |
Java Agent 的实际应用案例很多,举些笔者实际工作中使用到的开源软件的应用案例。
微服务是目前流行的互联网架构,实施微服务架构其中用于观察分布式服务的 APM (应用性能管理)系统是必不可缺的一环。典型的 APM 系统,如 Pinpoint、SkyWalking,为了减少的 Java 服务应用代码的入侵,底层实现上都采用 Java Agent 技术,在 Java 服务应用启动时加载 agent,进行字节码增强技术,实现分布式追踪、服务性能监控等特性。具体可参见 Pinpoint 文档和 SkyWalking 文档。
Alibaba Java 诊断利器 Arthas,实现上使用了动态 Attach API,相关源代码参见 github。Arthas 4.0 开始支持 premain
方式启动时加载 agent,参见 issue #550。
**附注:**本文中提到的代码,可以在 github 上访问得到,javaagent-demo。
要想开启 binlog,需要在启动 MySQL 时传入 --log-bin 参数。或者也可以在 MySQL 配置文件 /etc/my.cnf
,设置 log_bin
开启 binlog。MySQL 5.7 开始,开启 binlog 后,--server-id
参数也必须指定,否则 MySQL 服务器会启动失败。
binlog_format
支持 STATEMENT
, ROW
, MIXED
三种格式,MySQL 5.5 和 5.6 默认为 STATEMENT
,MySQL 5.7.7 开始默认为 ROW
。若 SQL 使用 UUID(), RAND(), VERSION() 等函数,或者使用存储过程、自定义函数,基于 STATEMENT 的主从复时,是不安全的(很多人可能会认为 NOW(), CURRENT_TIMESTAMP 这些函数也是不安全的,事实上是安全的)[ doc1, doc2 ]。基于 ROW
的主从复制,是最安全的复制方式。
现在先来看下 STATEMENT
格式的 binlog,/etc/my.cnf
文件修改的内容如下:
1 | server_id = 1 |
重启 MySQL 后,在数据目录 datadir 下,比如 /var/lib/mysql/
,将会生成相应的 binlog 文件,mysql-bin.index
和 mysql-bin.000001
。.index
后缀的文件保存全部 binlog 文件名。mysql-bin.000001
文件记录 binlog 内容。每次 MySQL 启动或者 flush 日志,都将按序号创建一个新的日志文件。另外,当日志文件大小超过 max_binlog_size
时,也会创建一个新的日志文件。
现在来试一试 binlog 功能。假设在 testdb
库在有 hello
表,并对其中某行做修改操作:
1 | mysql> select * from hello; |
binlog 为二进制文件,需要使用 mysqlbinlog
(doc, man)命令查看:
1 | $ sudo mysqlbinlog /var/lib/mysql/mysql-bin.000001 # 直接在 mysql 服务器上读取 binlog 文件 |
执行 update
后相应新增的 binlog 文件内容:
1 | # at 154 |
修改 /etc/my.cnf
的 binlog_format
为 ROW
,再重启 MySQL。格式修改后,会生成一个新的 binlog 文件 mysql-bin.000002
。
1 | mysql> show create table hello; |
查看 ROW
格式的 binlog,需要使用 sudo mysqlbinlog -v --base64-output=DECODE-ROWS /var/lib/mysql/mysql-bin.000002
命令。执行 update
后相应新增的 binlog 内容:
1 | # at 154 |
若执行如下 SQL:
1 | mysql> insert hello (name) values ('Frank'); |
相应生成的 binlog 内容:
1 | # at 442 |
若执行如下 SQL:
1 | mysql> delete from hello where id = 2; |
相应生成的 binlog 内容:
1 | # at 715 |
MySQL 逻辑备份通常会结合全量备份和增量备份,使用 mysqldump
定期全量备份数据库,然后利用 binlog 保存增量数据。恢复数据时,就是用 mysqldump
备份的数据恢复到备份的时间点。数据库在备份时间点到当前时间的增量修改,则通过 mysqlbinlog
将 binlog 中的增量数据恢复到数据库。现在假设已经使用 mysqldump
将数据库还原到:
1 | mysql> select * from hello; |
之后执行的 SQL:
1 | update hello set name = 'David' where id = 3; |
不管是使用 STATEMENT
还是 ROW
,mysqlbinlog
命令都可以将 binlog 增量恢复到数据库 [ doc ]。
观察 binlog
可以看到,从最开始的 update hello set name = 'David' where id = 3;
到最终的 delete from hello where id = 2;
,时间上从 "2018-06-17 22:54:13" 到 "2018-06-17 22:56:44",所以基于时间点恢复,命令如下:
1 | $ sudo mysqlbinlog --start-datetime="2018-06-17 22:54:13" --stop-datetime="2018-06-17 22:56:44" mysql-bin.000002 | mysql -uroot -p123456 |
binlog
的事件位置号是从 "154" 到 "956",但需要注意的是 用 --start-position
和 --stop-position
指定位置点范围,逻辑上对应的是 start <= position < stop
,所以基于时间点恢复,命令如下:
1 | $ sudo mysqlbinlog --start-position=154 --stop-position=957 mysql-bin.000002 | mysql -uroot -p123456 |
两种方式任意执行,都能将数据恢复到:
1 | mysql> select * from hello; |
binlog2sql,作者为曹单锋,大众点评 DBA。binlog2sql
,从 MySQL binlog 解析出你要的 SQL。根据不同选项,你可以得到原始 SQL、回滚 SQL、去除主键的 INSERT SQL 等。binlog2sql
,底层实现依赖 python-mysql-replication,由该库完成 MySQL 复制协议和 binlog 格式的解析。
1 | python binlog2sql/binlog2sql.py -h192.168.2.107 -uroot -p123456 --start-position=154 --stop-position=957 --start-file='mysql-bin.000002' |
生成回滚 sql:
1 | python binlog2sql/binlog2sql.py --flashback -h192.168.2.107 -uroot -p123456 --start-position=154 --stop-position=956 --start-file='mysql-bin.000002' |
闪回的现实原理很简单,先通过 MySQL 复制协议的 com-binlog-dump 命令 dump 出 binlog,然后按照 binlog 的格式规范解析 binlog,将 binlog 转换成 SQL,再将这些 SQL 转换反向逻辑的 SQL,最后再倒序执行。具体可以看,binlog2sql
作者的文章 [ ref ]。
上文中的 binlog2sql
其实底层依赖 python-mysql-replication
库,这是 Python 库。如果想使用 Java 解析 binlog 可以使用 mysql-binlog-connector-java
(github)库。目前开源的 CDC 工具,如 Zendesk maxwell、Redhat debezium、LinkedIn Databus 等都底层依赖 mysql-binlog-connector-java
或者其前身 open-replicator。使用 mysql-binlog-connector-java
的示例代码如下:
1 | BinaryLogClient client = new BinaryLogClient("192.168.2.107", 3306, "root", "123456"); |
输出(省略部分内容):
1 | ... |
要想知道函数是怎么被调用的,需要了解栈帧和调用惯例相关知识。俞甲子2009 的“第10章 内存: 栈与堆”对相关概念有很好的介绍。本文是对相关知识的学习笔记。
附注,栈帧之间的划分边界,其实有两种不一样说法。在有些资料中 [ wikipedia; 俞甲子2009 ],callee 参数被划分在 callee 栈帧,但在 Intel 官方一些权威文档中 [ Intel ASDM, Vol.1, Ch.6; Intel X86-psABI ],callee 参数被划分在 caller 栈帧。
调用惯例,规定以下内容:(1) 函数参数的传递顺序和方式;(2) 栈的清理方式;(3) 名称修饰(name mangling)。常见的 x86 调用惯例列表有:cdecl(C 语言默认)、stdcall(Win32 API 标准)、fastcall、pascal。这些调用惯例如下下表所示(更加全面的列表参见 wikipedia):
调用惯例 | 栈帧清理 | 参数传递 | 名称修饰 |
---|---|---|---|
cdel | 调用者 caller | 从右至左入栈 RTL | 下划线+函数名,如 _sum |
stdcall | 被调用者 callee | 从右至左入栈 RTL | 下划线+函数名+@+参数字节数,如 _sum@8 |
fastcall | 被调用者 callee | 头两参数存入寄存器 ECX 和 EDX,其余参数从右至左入栈 RTL | @+函数名+@+参数字节数,如 @sum@8 |
pascal | 被调用者 callee | 从左至右入栈 LTR | 较为复杂,参见 pascal 文档 |
下面举例说明,cdecl 和 stdcall 两种调用惯例。
cdecl,调用者负责清理堆栈(caller clean-up),参数从右至左(Right-to-Left,RTL)压入栈。举例说明 [ ref1 ref2 ]:
1 | // cdecl 调用惯例 |
编译器生成的等价汇编代码:
1 | ; 调用者清理堆栈(caller clean-up),参数 RTL 入栈 |
1 | ; sum 函数等价汇编代码 |
stdcall,被调用者负责清理堆栈(callee clean-up),参数从右至左(Right-to-Left,RTL)压入栈。举例说明:
1 | // stdcall 调用惯例 |
编译器生成的等价汇编代码:
1 | ; 被调用者清理堆栈(callee clean-up),参数 RTL 入栈 |
1 | ; sum 函数等价汇编代码 |
hello1.c
文件内容如下:
1 | int __cdecl sum(int a, int b) { |
生成汇编代码:
1 | gcc -m32 -S -masm=intel hello1.c -o hello1.s |
生成的 hello1.s
,内容如下:
1 | .section __TEXT,__text,regular,pure_instructions |
GCC 生成的汇编代码并没有使用 push
而是通过 sub esp, 40
直接预先分配栈空间,然后使用 mov
指令将参数写进栈中,清理栈使用 add esp, 40
。逻辑上,还是符合 cdecl 调用惯例,调用者负责清理堆栈(caller clean-up),参数从右至左(Right-to-Left,RTL)压入栈。这样做的好处是,如果同时多次调用 sum
,清理栈空间的指令,只需要最后的时候调用一次就可以了。统一使用 sub esp
和 add esp
去操作 esp 值,避免 push
指令操作 esp。
现在再来看看,stdcall 调用惯例下,GCC 生成的汇编代码。把 sum
函数改为 __stdcall
,运行下面的命令:
1 | gcc -m32 -S -masm=intel hello2.c -o hello2.s |
1 | *** hello1.s Thu Feb 05 22:43:59 2018 |
反汇编 objdump
、gdb
/lldb
,或者商业工具使用,IDA Pro 或者 Hopper Disassembler [ wiki ]
1 | $ gobjdump -d -Mintel hello1 # 使用 GNU objdump |
1 | hello1:file format Mach-O 32-bit i386 |
使用 lldb 反汇编:
1 | $ lldb hello1 |
前段时间开发的时候,遇到一个问题,就是如何用 Java 实现 chdir
?网上搜索一番,发现了 JNR-POSIX
项目 [ stackoverflow ]。俗话说,好记性不如烂笔头。现在将涉及到的相关知识点总结成笔记。
其实针对 Java 实现 chdir
问题,官方 20 多年前就存在对应的 bug,即 JDK-4045688 'Add chdir or equivalent notion of changing working directory'。这个 bug 在 1997.04 创建,目前的状态是 Won't Fix
(不予解决),理由大致是,若实现与操作系统一样的进程级别的 chdir
,将影响 JVM 上的全部线程,这样引入了可变(mutable)的全局状态,这与 Java 的安全性优先原则冲突,现在添加全局可变的进程状态,已经太迟了,对不变性(immutability)的支持才是 Java 要实现的特性。
chdir
是平台相关的操作系统接口,POSIX 下对应的 API 为 int chdir(const char *path);
,而 Windows 下对应的 API 为 BOOL WINAPI SetCurrentDirectory(_In_ LPCTSTR lpPathName);
,另外 Windows 下也可以使用 MSVCRT 中 API 的 int _chdir(const char *dirname);
(MSVCRT 下内部实现其实就是调用 SetCurrentDirectory
[ reactos ] )。
Java 设计理念是跨平台,"write once, run anywhere"。很平台相关的 API,虽然各个平台都有自己的类似的实现,但存在会差异。除了多数常见功能,Java 并没有对全部操作系统接口提供完整支持,比如很多 POSIX API。除了 chdir
,另外一个典型的例子是,在 Java 9 以前 JDK 获取进程 id 一直没有简洁的方法 [ stackoverflow ],最新发布的 Java 9 中的 JEP 102(Process API Updates)才增强了进程 API。获取进程 id 可以使用以下方式 [ javadoc ]:
1 | long pid = ProcessHandle.current().pid(); |
相比其他语言,Pyhon 和 Ruby,对操作系统相关的接口都有更多的原生支持。Pyhon 和 Ruby 实现的相关 API 基本上都带有 POSIX 风格。比如上文提到,chdir
和 getpid
,在 Pyhon 和 Ruby 下对应的 API 为:Pyhon 的 os 模块 os.chdir(path) 和 os.getpid();Ruby 的 Dir 类的 Dir.chdir( [ string] ) 类方法和 Process 类的 Process.pid 类属性。Python 解释器的 chdir
对应源码为 posixmodule.c#L2611,Ruby 解释器的 chdir
对应源码为 dir.c#L848 和 win32.c#L6741。
Java 下要想实现本地方法调用,需要通过 JNI。关于 JNI 的介绍,可以参阅“Java核心技术,卷II:高级特性,第9版2013”的“第12章 本地方法”,或者读当年 Sun 公司 JNI 设计者 Sheng Liang(梁胜)写的“Java Native Interface: Programmer's Guide and Specification”。本文只给出实现 getpid
的一个简单示例。
首先使用 Maven 创建一个简单的脚手架:
1 | mvn archetype:generate \ |
在 com.test
包下添加 GetPidJni
类:
1 | package com.test; |
用 javac
编译代码 GetPidJNI.java
,然后用 javah
生成 JNI 头文件:
1 | $ mkdir -p target/classes |
生成的 JNI 头文件 com_test_GetPidJni.h
,内容如下:
1 | /* DO NOT EDIT THIS FILE - it is machine generated */ |
现在有了头文件声明,但还没有实现,手动敲入 com_test_GetPidJni.c
:
1 |
|
编译 com_test_GetPidJni.c
,生成 libgetpidjni.dylib
:
1 | $ gcc -I $JAVA_HOME/include -I $JAVA_HOME/include/darwin -dynamiclib -o libgetpidjni.dylib com_test_GetPidJni.c |
生成的 libgetpidjni.dylib
,就是 GetPidJni.java
代码中的 System.loadLibrary("getpidjni");
,需要加载的 lib。
现在运行 GetPidJni
类,就能正确获取 pid:
1 | $ java -Djava.library.path=`pwd` -cp "target/classes" com.test.GetPidJni |
JNI 的问题是,胶水代码(黏合 Java 和 C 库的代码)需要程序员手动书写,对不熟悉 C/C++ 的同学是很大的挑战。
JNA(Java Native Access, wiki, github, javadoc, mvn),提供了相对 JNI 更加简洁的调用本地方法的方式。除了 Java 代码外,不再需要额外的胶水代码。这个项目最早可以追溯到 Sun 公司 JNI 设计者 Sheng Liang 在 1999 年 JavaOne 上的分享。2006 年 11月,Todd Fast (也来自 Sun 公司) 首次将 JNA 发布到 dev.java.net 上。Todd Fast 在发布时提到,自己在这个项目上已经断断续续开发并完善了 6-7 年时间,项目刚刚在 JDK 5 上重构和重设计过,还可能有很多缺陷或缺点,希望其他人能浏览代码并参与进来。Timothy Wall 在 2007 年 2 月重启了这项目,引入了很多重要功能,添加了 Linux 和 OSX 支持(原本只在 Win32 上测试过),加强了 lib 的可用性(而非仅仅基本功能可用)[ ref ]。
看下示例代码:
1 | import com.sun.jna.Library; |
最初,JRuby 的核心开发者 Charles Nutter 在实现 Ruby 的 POSIX 集成时就使用了 JNA [ ref ]。但过了一段时候后,开始开发 JNR(Java Native Runtime, github, mvn) 替代 JNA。Charles Nutter 在介绍 JNR 的 slides 中阐述了原因:
1 | Why Not JNA? |
即,(1) 预处理器的常量支持(通过 jnr-constants 解决);(2) 开箱即用的标准 API(作者实现了 jnr-posix, jnr-x86asm, jnr-enxio, jnr-unixsocket);(3) C 回调 callback 支持;(4) 性能(提升 8-10 倍)。
使用 JNR-FFI(github, mvn)实现 getpid
,示例代码:
1 | import jnr.ffi.LibraryLoader; |
使用 JNR-POSIX(github, mvn)实现 chdir
和 getpid
,示例代码:
1 | import jnr.posix.POSIX; |
性能测试代码为 BenchmarkFFI.java
(github),测试结果如下:
1 | # JMH version: 1.19 |
即:JNI > JNR > JNA (Direct Mapping) > JNA (Interface Mapping)。相对 JNI 的实现性能,其他三种方式,从大到小的性能百分比依次为:74.8% (JNR), 13.2% (JnaDirect), 10.6% (JNA)。在博主电脑上测试,JNR 相比 JNA 将近快了 6-7 倍(JNR 作者 Charles Nutter 针对 getpid
的测试结果是 JNR 比 JNA 快 8-10 倍 [ twitter slides ])。
先来看下 JNA,JNA 官方文档 FunctionalDescription.md,对其实现原理有很好的阐述。这里将从源码角度分析实现的核心逻辑。
回顾下代码,我们现实定义了接口 LibC
,然后通过 Native.loadLibrary("c", LibC.class)
获取了接口实现。这一步是怎么做到的呢?翻下源码 Native.java#L547 就知道,其实是通过**动态代理(dynamic proxy)**实现的。使用动态代理需要实现 InvocationHandler 接口,这个接口的实现在 JNA 源码中是类 com.sun.jna.Library.Handler。示例中的 LibC
接口定义的全部方法,将全部分派到 Handler 的 invoke 方法下。
1 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; |
然后根据返回参数的不同,分派到 Native 类的,invokeXxx 本地方法:
1 | /** |
比如,long getpid()
会被分派到 invokeLong,而 int chmod(String filename, int mode)
会被分派到 invokeInt
。invokeXxx 本地方法参数:
Function function
,记录了 lib 信息、函数名称、函数指针地址、调用惯例等元信息;long fp
,即函数指针地址,函数指针地址通过 Native#findSymbol()获得(底层是 Linux API dlsym 或 Windows API GetProcAddress )。int callFlags
,即调用约定,对应 cdecl 或 stdcall。int callFlags
,即函数入参,若无参数,args 大小为 0,若有多个参数,原本的入参被从左到右依次保存到 args 数组中。再来看下 invokeXxx
本地方法的实现 dispatch.c#L2122(invokeInt
或 invokeLong
实现源码类似):
1 | /* |
即,全部 invokeXxx
本地方法统一被分派到 dispatch
函数 dispatch.c#L439:
1 | static void |
这个 dispatch
函数是全部逻辑的核心,实现最终的本地函数调用。
我们知道,发起函数调用,需要构造一个栈帧(stack frame)。构造栈帧,涉及到参数压栈次序(参数从左到右压入还是从右到左压入)和清理栈帧(调用者清理还是被调用者清理)等实现细节问题。不同的编译器在不同的 CPU 架构下有不同的选择。构造栈帧的具体实现细节的选择,被称为调用惯例(calling convention)。按照调用惯例构造整个栈帧,这个过程由编译器在编译阶段完成的。比如要想发起 sum(2, 3)
这个函数调用,编译器可能会生成如下等价汇编代码:
1 | ; 调用者清理堆栈(caller clean-up),参数从右到左压入栈 |
dispatch
函数是,需要调用的函数指针地址、输入参数和返回参数,全部是运行时确定。要想完成这个函数调用逻辑,就要运行时构造栈帧,生成参数压栈和清理堆栈的工作。JNA 3.0 之前,实现运行时构造栈帧的逻辑的对应代码 dispatch_i386.c、dispatch_ppc.c 和 dispatch_sparc.s,分别实现 Intel x86、PowerPC 和 Sparc 三种 CPU 架构。
运行时函数调用,这个问题其实是一个一般性的通用问题。早在 1996 年 10 月,Cygnus Solutions 的工程师 Anthony Green 等人就开发了 libffi(home, wiki, github, doc),解决的正是这个问题。目前,libffi 几乎支持全部常见的 CPU 架构。于是,从 JNA 3.0 开始,摒弃了原先手动构造栈帧的做法,把 libffi 集成进了 JNA。
直接映射(Direct Mapping)
https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#RegisterNatives
http://www.chiark.greenend.org.uk/doc/libffi-dev/html/The-Closure-API.html
JNR 底层同样也是依赖 libffi,参见 jffi。但 JNR 相比 JNA 性能更好,做了很有优化。比较重要的点是,JNA 使用动态代理生成实现类,而 JNR 使用 ASM 字节码操作库生成直接实现类,去除了每次调用本地方法时额外的动态代理的逻辑。使用 ASM 生成实现类,对应的代码为 AsmLibraryLoader.java。其他细节,限于文档不全,本人精力有限,不再展开。
Java 9 以前 JDK 获取进程 id 没有简洁的方法,最新发布的 Java 9 中的 JEP 102(Process API Updates)增强了进程 API。进程 id 可以使用以下方式 [ javadoc ]
1 | long pid = ProcessHandle.current().pid(); |
翻阅实现源码,可以看到对应的实现就是 JNI 调用:
jdk/src/java.base/share/classes/java/lang/ProcessHandleImpl [ src ]
1 | /** |
*nix 平台下实现为:
jdk/src/java.base/unix/native/libjava/ProcessHandleImpl_unix.c [ src ]
1 | /* |
Windows 平台下实现为:
jdk/src/java.base/windows/native/libjava/ProcessHandleImpl_win.c [ src ]
1 | /* |
ZooKeeper(wiki,home,github) 是用于分布式应用的开源的分布式协调服务。通过暴露简单的原语,分布式应用能在之上构建更高层的服务,如同步、配置管理和组成员管理等。在设计上易于编程开发,并且数据模型使用了熟知的文件系统目录树结构 [ doc ]。
在介绍 ZooKeeper 之前,有必要了解下 Paxos 和 Chubby。2006 年 Google 在 OSDI 发表关于 Bigtable 和 Chubby 的两篇会议论文,之后再在 2007 年 PODC 会议上发表了论文“Paxos Made Live”,介绍 Chubby 底层实现的共识(consensus)协议 Multi-Paxos,该协议对 Lamport 的原始 Paxos 算法做了改进,提高了运行效率 [ ref ]。Chubby 作为锁服务被 Google 应用在 GFS 和 Bigtable 中。受 Chubby 的影响,来自 Yahoo 研究院的 Benjamin Reed 和 Flavio Junqueira 等人开发了被业界称为开源版的 Chubby 的 ZooKeeper(内部实现事实上稍有不同 [ ref ]),底层的共识协议为 ZAB。Lamport 的 Paxos 算法出了名的难懂,如何让算法更加可理解(understandable),便成了 Stanford 博士生 Diego Ongaro 的研究课题。Diego Ongaro 在 2014 年发表了介绍 Raft 算法的论文,“In search of an understandable consensus algorithm”。Raft 是可理解版的 Paxos,很快就成为解决共识问题的流行协议之一。这些类 Paxos 协议和 Paxos 系统之间的关系,如下 [ Ailijiang2016 ]:
Google 的 Chubby 没有开源,在云计算和大数据技术的风口下,Yahoo 开源的 ZooKeeper 便在工业界流行起来。ZooKeeper 重要的时间线如下:
- 2007 年 11 月 ZooKeeper 1.0 在 SourceForge 上发布 [ ref ]- 2008 年 6 月开始从 SourceForge 迁移到 Apache [ ref ],在 10 月 Zookeeper 3.0 发布,并成为 Hadoop 的子项目 [ ref1 ref2 ]
关于 ZooKeeper 名字的来源,Flavio Junqueira 和 Benjamin Reed 在介绍 ZooKeeper 的书中有如下阐述:
ZooKeeper 由雅虎研究院开发。我们小组在进行 ZooKeeper 的开发一段时间之后,开始推荐给其他小组,因此我们需要为我们的项目起一个名字。与此同时,小组也一同致力于 Hadoop 项目,参与了很多动物命名的项目,其中有广为人知的 Apache Pig 项目(http://pig.apache.org)。我们在讨论各种各样的名字时,一位团队成员提到我们不能再使用动物的名字了,因为我们的主管觉得这样下去会觉得我们生活在动物园中。大家对此产生了共鸣,分布式系统就像一个动物园,混乱且难以管理,而 ZooKeeper 就是将这一切变得可控。
ZooKeeper 服务由若干台服务器构成,其中的一台通过 ZAB 原子广播协议选举作为主控服务器(leader),其他的作为从属服务器(follower)。客户端可以通过 TCP 协议连接任意一台服务器。如果客户端是读操作请求,则任意一个服务器都可以直接响应请求;如果是更新数据操作(写数据或者更新数据)。则只能由主控服务器来协调更新操作;如果客户端连接的是从属服务器,则从属服务器会将更新据请求转发到主控服务器,由其完成更新操作。主控服务器将所有更新操作序列化,利用 ZAB 协议将数据更新请求通知所有从属服务器,ZAB 保证更新操作。
读和写操作,如下图所示 [ Haloi2015 ]:
ZooKeeper 的任意一台服务器都可以响应客户端的读操作,这样可以提高吞吐量。Chubby 在这点上与 ZooKeeper 不同,所有读/写操作都由主控服务器完成,从属服务器只是为了提高整个协调系统的可用性,即主控服务器发生故障后能够在从属服务器中快速选举出新的主控服务器。在带来高吞吐量优势的同时,ZooKeeper 这样做也带来潜在的问题:客户端可能会读到过期数据,因为即使主控服务器已经更新了某个内存数据,但是 ZAB 协议还未能将其广播到从属服务器。为了解决这一问题,在 ZooKeeper 的接口 API 函数中提供了 sync 操作,应用可以根据需要在读数据前调用该操作,其含义是:接收到 sync 命令的从属服务器从主控服务器同步状态信息,保证两者完全一致。这样如果在读操作前调用 sync 操作,则可以保证客户端一定可以读取到最新状态的数据。
ZooKeeper 所提供的命名空间跟标准文件系统很相似。路径中一系列元素是用斜杠(/)分隔的。每个节点在 ZooKeper 命名空间中是用路径来识别的。在 ZooKeeper 术语下,节点被称为 znode。默认每个 znode 最大只能存储 1M 数据(可以通过配置参数修改),这与 Chubby 一样是出于避免应用将协调系统当作存储系统来用。znode 只能使用绝对路径,相对路径不能被 ZooKeeper 识别。znode 命名可以是任意 Unicode 字符。唯一的例外是,名称"/zookeeper"。命名为"/zookeeper"的 znode,由 ZooKeeper 系统自动生成,用配额(quota)管理。
ZooKeeper 安装与启动:
1 | $ brew info zookeeper |
若不修改配置文件,默认是单机模式启动。若要使用集群模式,需要修改 /usr/local/etc/zookeeper/zoo.cfg
(默认路径)。示例 zoo.cfg
[ doc ]:
1 | tickTime=2000 |
clientPort
:客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。server.X=YYY:A:B
- X:表示的服务器编号;- YYY:表示服务器的ip地址;- A:表示服务器节点间的通信端口,用于 follower 和 leader 节点的通信;- B:表示选举端口,表示选举新 leader 时服务器间相互通信的端口,当 leader 挂掉时,其余服务器会相互通信,选择出新的 leader。
若想在单台主机上试验集群模式,可以将 YYY
都修改为 localhost
,并且让两个端口 A:B
也相互不同(比如:2888:3888, 2889:3889, 2890:3890),即可实现伪集群模式。示例 zoo.cfg
如下 [ doc ]:
1 | server.1=localhost:2888:3888 |
zkCli 支持的全部命令:
1 | $ zkCli help |
Zookeeper 支持两种类型节点:持久节点(persistent znode)和临时节点(ephemeral znode)。持久节点不论客户端会话情况,一直存在,只有当客户端显式调用删除操作才会消失。而临时节点则不同,会在客户端会话结束或者发生故障的时候被 ZooKeeper 系统自动清除。另外,这两种类型的节点都可以添加是否是顺序(sequential)的特性,这样就有了:持久顺序节点和临时顺序节点。
(1) 持久节点(persistent znode)
使用 create
创建节点(默认持久节点),以及使用 get
查看该节点:
1 | $ zkCli -server 127.0.0.1 # 启动客户端 |
create
创建子节点,以及使用 ls
查看全部子节点:
1 | [zk: localhost:2181(CONNECTED) 3] create /zoo/duck '' |
delete
删除节点,以及使用 rmr
递归删除:
1 | [zk: localhost:2181(CONNECTED) 7] delete /zoo/duck |
(2) 临时节点(ephemeral znode)
和持久节点不同,临时节点不能创建子节点:
1 | $ zkCli # 启动第1个客户端 |
临时节点在客户端会话结束或者发生故障的时候被 ZooKeeper 系统自动清除。现在试验下的针对临时节点自动清除的监视:
1 | $ zkCli # 启动第2个客户端 |
若客户端1,退出 quit
或崩溃,客户端2将收到监视事件:
1 | [zk: localhost:2181(CONNECTED) 2] |
(3) 顺序节点(sequential znode)
顺序节点在其创建时 ZooKeeper 会自动在 znode 名称上附加上顺序编号。顺序编号,由父 znode 维护,并且单调递增。顺序编号,由 4 字节的有符号整数组成,并被格式化为 0 填充的 10 位数字。
1 | [zk: localhost:2181(CONNECTED) 1] create /test '' |
ZooKeeper 提供的主要 znode 操作 API 如下表所示:
API 操作 | 描述 | CLI 命令 |
---|---|---|
create | 创建 znode | create |
delete | 删除 znode | delete/rmr/delquota |
exists | 检查 znode 是否存在 | stat |
getChildren | 读取 znode 全部的子节点 | ls/ls2 |
getData | 读取 znode 数据 | get/listquota |
setData | 设置 znode 数据 | set/setquota |
getACL | 读取 znode 的 ACL | getACL |
setACL | 设置 znode 的 ACL | setACL |
sync | 同步 | sync |
Java 的 ZooKeeper 类实现了上述提供的 API。
Zookeeper 底层是 Java 实现,zkCli
命令行工具底层也是 Java 实现,对应的 Java 实现类为 org.apache.zookeeper.ZooKeeperMain
[ src1 src2 ]。ZooKeeper 3.5.x 下,CLI 命令与底层实现 API 对应关系:
命名 CLI | Java API (ZooKeeper 类) |
---|---|
addauth scheme auth | public void addAuthInfo(String scheme, byte[] auth) |
close | public void close() |
create [-s] [-e] path data acl | public String create(final String path, byte data[], List |
delete path [version] | public void delete(String path, int version) |
delquota [-n|-b] path | public void delete(String path, int version) |
get path [watch] | public byte[] getData(String path, boolean watch, Stat stat) |
getAcl path | public List |
listquota path | public byte[] getData(String path, boolean watch, Stat stat) |
ls path [watch] | public List |
ls2 path [watch] | - |
quit | public void close() |
rmr path | public void delete(final String path, int version) |
set path data [version] | public Stat setData(String path, byte[] data, int version) |
setAcl path acl | public Stat setACL(final String path, List |
setquota -n|-b val path | public Stat setData(String path, byte[] data, int version) |
stat path [watch] | public Stat exists(String path, boolean watch) |
sync path | public void sync(String path, AsyncCallback.VoidCallback cb, Object ctx) |
ZooKeeper 提供了处理变化的重要机制一一监视点(watch)。通过监视点,客户端可以对指定的 znode 节点注册一个通知请求,在发生变化时就会收到一个单次的通知。当应用程序注册了一个监视点来接收通知,匹配该监视点条件的第一个事件会触发监视点的通知,并且最多只触发一次。例如,当 znode 节点也被删除,客户端需要知道该变化,客户端在 /z 节点执行 exists 操作并设置监视点标志位,等待通知,客户端会以回调函数的形式收到通知。
ZooKeeper 的 API 中的读操作:getData、getChildren 和 exists,均可以选择在读取的 znode 节点上设置监视点。使用监视点机制,我们需要实现 Watcher 接口类,该接口唯一方法为 process:
1 | void process(WatchedEvent event) |
WatchedEvent 数据结构包括以下信息:
若收到 WatchedEvent, 在 zkCli 中会输出类似如下结果:
1 | WatchedEvent state:SyncConnected type:NodeDeleted path:/node |
监视点有两种类型:数据监视点和子节点监视点。创建、删除或设置一个 znode 节点的数据都会触发数据监视点,exists 和 getData 这两个操作可以设置数据监视点。只有 getChildren 操作可以设置子节点监视点,这种监视点只有在 znode 子节点创建或删除时才被触发。对于每种事件类型,我们通过以下调用设置监视点:
NodeCreated
通过 exists 调用设置一个监视点。
NodeDeleted
通过 exists 或 getData 调用设置监视点。
NodeDataChanged
通过 exists 或getData 调用设置监视点。
NodeChildrenChanged
通过 getChildren 调用设置监视点。
在 Java 下使用 ZooKeeper 需要先添加如下 maven 依赖:
1 | <dependency> |
ZookeeperDemo
示例,展示了建立连接会话,以及对 znode 的创建、读取、修改、删除和设置监视点等操作:
1 | import java.io.IOException; |
输出结果:
1 | WatchedEvent state:SyncConnected type:None path:null |
ZooInspector 是 ZooKeeper 3.3.0 开始官方提供的可视化查看和编辑 ZooKeeper 实例的工具 [ ZOOKEEPER-678 ]。源码位于目录 src/contrib/zooinspector
下,GitHub 地址为:link。可以根据 README.txt
的说明运行使用。或者可以直接用 ZOOKEEPER-678 下提供的可执行 jar 包。
翻阅 Java 8 的新特性,可以看到有这么一条“JEP 118: Access to Parameter Names at Runtime”。这个特性就是为了能运行时获取参数名新加的。这个 JEP 只是功能增强的提案,并没有最终实现的 JDK 相关的 API 的介绍。查看“Enhancements to the Reflection API” 会看到如下介绍:
Enhancements in Java SE 8
Method Parameter Reflection: You can obtain the names of the formal parameters of any method or constructor with the method java.lang.reflect.Executable.getParameters. However,.class
files do not store formal parameter names by default. To store formal parameter names in a particular.class
file, and thus enable the Reflection API to retrieve formal parameter names, compile the source file with the-parameters
option of thejavac
compiler.
javac
文档中关于 -parameters
的介绍如下 [ doc man ]:
-parameters
Stores formal parameter names of constructors and methods in the generated class file so that the methodjava.lang.reflect.Executable.getParameters
from the Reflection API can retrieve them.
现在试验下这个特性。有如下两个文件:
1 | package com.test; |
1 | package com.test; |
先试试 javac
不加 -parameters
编译,结果如下:
1 | javac -d "target/classes" src/main/java/com/test/*.java |
加上 -parameters
后,运行结果如下:
1 | javac -d "target/classes" -parameters src/main/java/com/test/*.java |
可以看到,加上 -parameters
后,正确获得了参数名。实际开发中,很少直接用命令行编译 Java 代码,项目一般都会用 maven 管理。在 maven 下,只需修改 pom 文件的 maven-compiler-plugin
插件配置即可,就是加上了 compilerArgs
节点 [ doc ],如下:
1 | <plugin> |
“Enhancements in Java SE 8”提到,参数名信息回存储在 class 文件中。现在试试用 javap
( doc man)命令反编译生成的 class 文件。反编译 class 文件:
1 | javap -v -cp "target/classes" com.test.TestClass |
在结尾的 MethodParameters
属性就是,实现运行时获取方法参数的核心。这个属性是 Java 8 的 class 文件新加的,具体介绍可以参考官方“Java 虚拟机官方”文档的介绍,“4.7.24. The MethodParameters Attribute”,doc。
上文介绍了 Java 8 通过新增的反射 API 运行时获取方法参数名。那么在 Java 8 之前,有没有办法呢?或者在编译时没有开启 -parameters
参数,又如何动态获取方法参数名呢?其实 class 文件中保存的调试信息就可以包含方法参数名。
javac
的 -g
选项可以在 class 文件中生成调试信息,官方文档介绍如下 [ doc man ]:
-g
Generates all debugging information, including local variables. By default, only line number and source file information is generated.
-g:none
Does not generate any debugging information.
-g:[keyword list]
Generates only some kinds of debugging information, specified by a comma separated list of keywords. Valid keywords are:
source
Source file debugging information.
lines
Line number debugging information.
vars
Local variable debugging information.
可以看到默认是包含源代码信息和行号信息的。现在试验下不生成调试信息的情况:
1 | javac -d "target/classes" src/main/java/com/test/*.java -g:none |
对比上文的反编译结果,可以看到,输出结果中的 Compiled from "TestClass.java"
没了,Constant pool
中也不再有 LineNumberTable
和 SourceFile
,code
属性里的 LocalVariableTable
属性也没了(当然,因为编译时没加 -parameters
参数,MethodParameters
属性自然也没了)。若选择不生成这两个属性,对程序运行产生的最主要的影响就是,当抛出异常时,堆栈中将不会显示出错代码所属的文件名和出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点。
1 | javac -d "target/classes" src/main/java/com/test/*.java -g:vars |
可以看到,code
属性里的出现了 LocalVariableTable
属性,这个属性保存的就是方法参数和方法内的本地变量。在演示代码的 sum
方法中没有定义本地变量,若存在的话,也将会保存在 LocalVariableTable
中。
javap
的 -v
选项会输出全部反编译信息,若只想看行号和本地变量信息,改用 -l
即可。输出结果如下:
1 | javap -l -cp "target/classes" com.test.TestClass |
若要全部生成全部提示信息,编译参数需要改为 -g:source,lines,vars
。一般在 IDE 下调试代码都需要调试信息,所以这三个参数默认都会开启。IDEA 下的 javac 默认参数设置,如图:
若使用 maven,maven 的默认的编译插件 maven-compiler-plugin
也会默认开启这三个参数 [doc],经实际验证也包括了LocalVariableTable
。
同样的,gradle 默认也会包含调试信息 [ doc ]。
上文中讲了 class 文件中的调试信息中 LocalVariableTable
属性里就包含方法名参数,这就是运行时获取方法参数名的方法。读取这个属性,JDK 并没有提供 API,只能借助第三方库解析 class 文件实现。
要解析 class 文件典型的工具库有 ObjectWeb 的 ASM(wiki,home,mvn,javadoc)、Apache 的 Commons BCEL(wiki,home,mvn,javadoc)、 日本教授开发的 Javassist(wiki,github,mvn,javadoc)等。其中 ASM 使用最广,使用 ASM 的知名开源项目有,AspectJ, CGLIB, Clojure, Groovy, JRuby, Jython, TopLink等等 [ ref ]。当然使用 BCEL 的项目也很多 [ ref ]。ASM 相对其他库的 jar 更小,运行速度更快 [ javadoc ]。目前 asm-5.0.1.jar 文件大小 53 KB,BCEL 5.2 版本文件大小 520 KB,javassist-3.20.0-GA.jar 文件大小 751 KB。jar 包文件小,自然意味着代码量更少,提供的功能自然也少了。
先来看看用 BCEL 获取方法参数名的写法,代码如下:
1 | package com.test; |
输出结果:
1 | this Lcom/test/TestClass; com.test.TestClass |
ASM 的写法如下:
1 | package com.test; |
若使用 Spring 框架,对于运行时获取参数名,Spring 提供了内建支持,对应的实现类为 DefaultParameterNameDiscoverer
(javadoc)。该类先尝试用 Java 8 新的反射 API 获取方法参数名,若无法获取,则使用 ASM 库读取 class 文件的 LocalVariableTable
,对应的代码分别为 StandardReflectionParameterNameDiscoverer 和 LocalVariableTableParameterNameDiscoverer。
javac
是 Java 代码的编译器[1][2],初学 Java 的时候就应该接触过。本文整理一些 javac
相关的高级用法。Lombok 库,大家平常一直在使用,但可能并不知道实现原理解析,其实 Lombok 实现上依赖的是 Java 编译器的注解处理 API(JSR-296)[3],本文同时尝试解析 Lombok 的实现原理。先来看下 javac
命令行工具。javac
命令行工具,官方文档有完整的使用说明[4],当然也可以,运行 javac -help
或 man javac
查看帮助信息。下面是经典的 hello world 代码:
1 | package com.example; |
编译与运行:
1 | tree # 代码目录结构 |
除了使用命令行工具编译 Java 代码,JDK 6 增加了规范“JSR-199: Java Compiler API”和“JSR-296: Pluggable Annotation Processing API”,开始还提供相关的 Java 编译器 API。Java 编译器的实现代码和 API 的整体结构如图所示[2][5]:
绿色标注的包是官方 API(Official API),即 JSR-199 和 JSR-296,黄色标注的包为Supported API,紫色标注的包代码全部在 com.sun.tools.javac.*
包下,为内部 API(Internal API)和实现类。完整的包说明如下[2][5][6]:
Element
及其子类TypeMirror
及其子类Elements
、Types
等类com.sun.tools.javac.api
- javax.tools
包下的 JavaCompiler
和其他 API 的实现com.sun.tools.javac.code
- javax.lang.model.*
包下的 API 的实现com.sun.tools.javac.comp
- 编译器主要处理阶段的实现com.sun.tools.javac.file
- 实现访问文件系统,包括 javax.tools.StandardFileManager
的实现com.sun.tools.javac.jvm
- class 文件的读写,编译器的字节码生成阶段的实现com.sun.tools.javac.main
- 代码编译的入口实现com.sun.tools.javac.model
- javax.lang.model.*
包的其他实现com.sun.tools.javac.parser
- 读取 Java 源代码,并生成语法树com.sun.tools.javac.processing
- 注解处理 API 的实现com.sun.tools.javac.resources
- 本地化文本和版本号的资源文件com.sun.tools.javac.tree
- 编译器语法树相关的表示类和工具类,com.sun.source.*
包下的 API 的实现com.sun.tools.javac.util
- 基础工具类全部源码都位于 JDK 源码的 langtools 目录下。对外的 API,被编译到 rt.jar
,com.sun.source.*
和 com.sun.tools.javac.*
包,被编译到 tools.jar
,在 JDK 下的具体位置是 $JAVA_HOME\lib\tools.jar
。值得一提的是,langtools
目录,除了包含 javac
的实现外,还实现了 javadoc
、javah
等命令,编译后也是在 tools.jar
下。
另外,由于是内部 API 和实现类,com.sun.tools.javac.*
包下全部代码中都有标注警告:
This is NOT part of any supported API. If you write code that depends on this, you do so at your own risk. This code and its internal interfaces are subject to change or deletion without notice.
首先,看下 JSR-199 引入的 Java 编译器 API(Java Compiler API)。在没有引入 JSR-199 之前,如果要通过编程方式编译 Java 代码,只能使用 com.sun.tools.javac.*
包下提供内部 API。上文提到的使用命令 javac
编译 Greeting.java
的等价写法如下:
1 | import com.sun.tools.javac.main.Main; |
事实上,javac
命令的底层实现就是执行 com.sun.tools.javac.Main 类。执行 javac
命令,等价于执行 java -cp $JAVA_HOME/lib/tools.jar com.sun.tools.javac.Main
。
1 | # 直接执行 com.sun.tools.javac.Main 类编译 Java 源代码 |
JSR-199,提供了 Java 编译器 API,对应的是 javax.tools.*
包。阅读包的 javadoc 容易发现,API 最核心是 javax.tools.JavaCompiler 接口,该类的 javadoc 阐述了如何使用该类,可以阅读。使用 Java 编译器 API 编译 Java 源代码,示例如下:
1 | import javax.tools.*; |
上述两种编程方式编译 Java 代码的方式,在 javac
命令的 man[4] 文档的 “Programmatic Interface” 小节也有提及,有兴趣可以阅读。
在实际开发过程中,我们基本上都是使用 Maven 或 Gradle 编译 Java 代码。Maven 编译 Java 代码,依赖的是 Maven 的 maven-compiler-plugin
插件。那么 maven-compiler-plugin
插件底层实现是否使用了 javax.tools.JavaCompiler
呢?查阅官网文档后,容易发现实际情况和猜想的一样(其实也是显而易见的结论) [doc]:
The Compiler Plugin is used to compile the sources of your project. Since 3.0, the default compiler is
javax.tools.JavaCompiler
(if you are using java 1.6) and is used to compile Java sources. If you want to force the plugin using javac, you must configure the plugin optionforceJavacCompilerUse
.
类似的,Gradle 编译 Java 代码,底层也使用了 Java 编译器 API,可以参见源码 JdkJavaCompiler
[github]。
上文提到,JSR-269,可插拔式注解处理 API(Pluggable Annotation Processing API)。注解处理,是编译过程中的其中一个阶段。要理解注解处理,需要先了解 Java 代码的编译过程。完整的编译过程如下图所示[7]:
整个过程就是:
把上述编译过程对应到代码中,javac 编译动作的入口是 com.sun.tools.javac.main.JavaCompiler 类,上述 3 个过程的代码逻辑集中在这个类的 compile()和 compile2()方法,如下图所示,整个编译过程主要的处理由图中标注的 8 个方法来完成[8]:
具体来看下,词法解析和语法解析。Java 的词法和语法规则,在《Java语言规范》(The Java Language Specification)中定义。从底层实现上来看,com.sun.tools.javac.parser.Scanner 类,按照单个字符的方式读取 Java 源文件中的关键字和标示符等内容,然后将其转换为符合 Java 语法规范(JLS ch3)的 Token 序列。例如,针对语句 int y = x + 1;
的词法解析过程如下图所示[9]:
然后,com.sun.tools.javac.parser.JavacParser 类,读取 Token 序列,将 Token 序列构造为抽象语法树 com.sun.tools.javac.tree.JCTree。语句 int y = x + 1;
,生成的抽象语法树,如下图所示[9]:
该语句对应的 JCTree.JCVariableDecl
对象,在 IDEA 的 debug 模式下查看,如下图所示:
语法树中的每一个语法节点,实际上都直接或者间接地继承了 JCTree
类,并且都以静态内部类的形式定义在 JCTree
类中。Java 源文件的完整的词法解析和语法解析,由 JavacParser
的 parseCompilationUnit
方法完成。解析完成后,方法返回 JCTree.JCCompilationUnit
类。JCTree.JCCompilationUnit
类,为某个 Java 源文件解析后的整个语法树的根节点。
上文提到,com.sun.source.*
包下暴露的 Tree API,提供对语法树只能做只读操作。com.sun.tools.javac.tree
包,是 com.sun.source.*
包下的 API 的实现。com.sun.source.tree.Tree
接口对应的实现类为 JCTree
,Tree
的子接口的实现类为 JCTree
的子类,并一一对应,比如,com.sun.source.tree.ClassTree
对应的实现类为 JCTree.JCClassDecl
。Tree
接口及其子接口只暴露只读方法,而 JCTree
类及其子类,大部分的内部定义字段都是 public
,可以直接读写。
主要的语法树节点 JCTree
子类,如下:
JCTree.JCBlock
:语句块(JLS 14.2)JCTree.JCClassDecl
:类声明(JLS 8.1)JCTree.JCForLoop
:for
语句(JLS 14.14.1)JCTree.JCEnhancedForLoop
:增强for语句(JLS 14.14.2)JCTree.JCIf
:if
语句(JLS 14.9)JCTree.JCReturn
:return
语句(JLS 14.7)JCTree.JCVariableDecl
:变量声明,比如 int x = 0
语句(JLS 14.4)全部的各个类型的树节点的类定义,可以参见 JCTree
和 Tree
类的 javadoc 或源代码。
在构造抽象语法树后,就是符号表填充阶段。在符号表填充阶段,会扫描 JCTree
语法树,遇到类型、变量、方法定义时,会它们的信息存储到符号表中,方便后续阶段进行快速查询。符号,对应的是 com.sun.tools.javac.code.Symbol
类。而 Symbol
类,是 javax.lang.model
包下 Element
的实现类,Symbol
子类是对应 Element
子类的实现。
Element
提供 ElementKind getKind()
方法,能获取元素类型(ElementKind
)。全部的 ElementKind
共 17 种:ANNOTATION_TYPE
(注解)、CLASS
(类)、CONSTRUCTOR
(构造方法)、ENUM
(枚举)、ENUM_CONSTANT
(枚举值)、EXCEPTION_PARAMETER
(异常参数)、FIELD
(字段)、INSTANCE_INIT
(实例初始化语句块)、INTERFACE
(接口)、LOCAL_VARIABLE
(本地变量)、METHOD
(方法)、PACKAGE
(包)、PARAMETER
(参数)、RESOURCE_VARIABLE
(资源变量)、STATIC_INIT
(静态初始化语句块)、TYPE_PARAMETER
(类型参数) 以及 OTHER
(其他)。
全部 Element
子类以及对应的 Symbol
子类,如下:
Symbol.PackageSymbol
ElementKind
:PACKAGE
(包)Symbol.ClassSymbol
ElementKind
:ANNOTATION_TYPE
(注解)、INTERFACE
(接口)、ENUM
(枚举)、CLASS
(类)Symbol.VarSymbol
ElementKind
:EXCEPTION_PARAMETER
(异常参数)、PARAMETER
(参数)、ENUM_CONSTANT
(枚举值)、RESOURCE_VARIABLE
(资源变量)、LOCAL_VARIABLE
(本地变量)、FIELD
(字段)Symbol.MethodSymbol
ElementKind
:CONSTRUCTOR
(构造方法)、STATIC_INIT
(静态初始化语句块)、INSTANCE_INIT
(实例初始化语句块)、METHOD
(方法)Symbol.TypeVariableSymbol
ElementKind
:TYPE_PARAMETER
(类型参数)在填充符号表后,就是语义分析和代码生成,包括标注(attribute)、数据及控制流分析(flow)、解语法糖(desugar)、字节码生成(generate)阶段。
在实际开发时,比如常见的“找不到符号(cannot find symbol)”编译报错,就是在标注阶段的名称消解(name resolution)时触发的。编译报错示例代码,如下:
1 | public class CantResolve { |
编译错误的提示内容:
1 | 找不到符号 |
编译过程的各个阶段的更详细的阐述可以阅读书籍[8][10],本文不再展开。
JSR-296 定义的可插拔式注解处理 API 在 javax.annotation.processing
包下,最核心的接口是 javax.annotation.processing.Processor,通过实现这个接口来定义自己的注解处理器。
编译器工具与 Processor
实现类的交互过程是:
Processor
对象,就调用无参构造方法创建一个 Processor
实例。ProcessingEnvironment
对象(注解处理的执行环境,从环境中获得相关工具类,比如 Elements
)。注解处理会执行多轮(round),每轮都会调用 process
方法,调用时传入在上一轮的源代码和 class 文件中找到的该注解处理器支持的注解子集。在处理注解期间,如果任何注解处理器生成了新的源文件或 class 文件,编译器将回到解析、填充符号表、注解处理的过程,直到没有新的文件生成。
init
方法的参数 ProcessingEnvironment
对象,为注解处理的执行环境,从环境中获得相关工具类,比如,Elements
类,用于操作 Element
元素;Filer
类,用于生成新的文件;Messager
类,用于报告编译错误、告警或其他消息。另外,ProcessingEnvironment
,也可以获得传递给注解处理器参数选项。
AbstractProcessor
抽象类,实现类了 Processor
接口,用于简化实际的注解处理器类的实现。该类通过读取 @SupportedAnnotationTypes
、@SupportedOptions
、@SupportedSourceVersion
注解值,来实现 Processor
接口对应的三个方法。
用命令行编译代码时,javac
编译器,会搜索可用的注解处理器。搜索路径可以通过参数选项 -processorpath
指定,如果未指定,将使用 classpath
。注解处理器,可以通过 -processor
参数选项指定。若未通过 -processor
参数选项指定,注解处理器会使用 SPI 方式定位,在搜索路径查找 META-INF/services/javax.annotation.processing.Processor
文件。文件中填写的是注解处理器类名(多个的话,换行填写),编译器就会自动使用这里填写的注解处理器进行注解处理。另外,编译器 API 的 CompilationTask
的 setProcessors
方法也可以传入注解处理器。
如果注解处理器支持参数选项,编译时,参数选项可以用 -Akey[=value]
的方式传递[4]。
JDK 源码的 langtools
目录下,提供了示例注解处理器 CheckNamesProcessor,一个检查命名的注解处理器。CheckNamesProcessor
注解处理器,内部实现了 javax.lang.model.util
包下 ElementScanner,用来扫描 Element
元素符号,然后检查类命名、方法命名、字段命名、参数命名等是否符合命名规范,如果不符合命名规范,就打印编译器告警。
javax.lang.model.util.ElementScanner8
类用于扫描 Element
的核心方法:
1 | public final R scan(Element e) |
对语法树的扫描,com.sun.source.util
包下,提供了语法树扫描器 TreeScanner,用于扫描语法树上的树节点 Tree
。类似的,com.sun.tools.javac.tree.TreeScanner
,用于扫描语法树上的树节点 JCTree
。
com.sun.source.util.TreeScanner
类用于扫描语法树的核心方法:
1 | public R scan(Tree node, P p) |
com.sun.tools.javac.tree.TreeScanner
类用于扫描语法树的核心方法:
1 | public void scan(JCTree tree) |
需要注意的是,注解处理器的 process
方法,传递过来的是 Element
对象,需要先获得 Element
对象关联的 Tree
或 JCTree
对象,才能扫描语法树。工具类 com.sun.source.util.Trees
提供了这样的桥接能力,该类的实现类为 com.sun.tools.javac.api.JavacTrees
。Trees
的相关方法:
1 | // 通过 ProcessingEnvironment 获得 Trees 对象 |
类似的,JavacTrees
的相关方法:
1 | // 通过 ProcessingEnvironment 获得 JavacTrees 对象 |
使用 ElementScanner
或 TreeScanner
扫描语法树的示例注解处理器,参见 VisitProcessor。
在语法解析时,JavacParser
类,底层实现上利用 TreeMaker 类构造的语法树各个节点。TreeMaker
类,封装了创建语法树节点的方法,部分常用的方法举例:
JCTree.JCAssign
。JCTree.JCBinary
。JCTree.JCBlock
。JCTree.JCVariableDecl
。JCTree.JCMethodDecl
。在注解处理阶段,init
方法传入了 ProcessingEnvironment
对象,通过该对象可以获得当前上下文中的 TreeMaker
对象,然后就可以利用 TreeMaker
创建新的语法树节点。
语句 int y = x + 1;
,使用 TreeMaker
构造对应的 JCTree.JCVariableDecl
,示例代码如下:
1 | Name x = ... |
因为 JCTree
类及其子类的大部分的内部定义字段都是 public
,可以直接读写,所以要想修改语法树,可以直接相关字段的值。比如,把 int y = x + 1
语句对应的 JCTree.JCVariableDecl
树节点改为 int y = 42
,可以直接修改 JCTree.JCVariableDecl
的 init
字段,示例代码如下:
1 | JCTree.JCVariableDecl decl = ... |
修改语法树的示例代码,参见 PlusProcessor 注解处理器。该示例注解处理器,修改 @PlusOne
注解标注的方法的内部实现,改造后的方法的逻辑为,返回请求参数值加 1 后的值。比如,修改语法树前,func
方法实现如下:
1 |
|
被 PlusProcessor
注解处理器修改语法树后,func
方法变成:
1 | public int func(int x) { |
修改方法内部实现的核心代码如下:
1 | private void modifyToPlusOneMethod(JCTree.JCMethodDecl methodDecl) { |
这个注解处理器仅仅用于示例,没有其他实际用途。实际开发中,Lombok 库被广泛使用,其底层实现就是利用注解处理器修改由 Lombok 注解(@Data
、@Getter
、@Setter
等)标注的代码的语法树,自动生成样板代码。针对 Lombok 库实现原理的解析,参见下文。
可插拔式注解处理 API,定义了 javax.annotation.processing.Filer
接口,这个接口提供了让注解处理器创建新文件的能力。createSourceFile
方法,用于创建新的源代码文件,createClassFile
,用于创建新的 class 文件。
来看下示例代码,GreetingProcessor 注解处理器。该注解处理器功能就是基于 Filer
自动生成 Greeting
类(打印 "hello world")。核心代码片段如下:
1 | private boolean generateGreeting(String className) throws Exception { |
模板文件 Greeting.tpl
的内容为:
1 | import javax.annotation.Generated; |
在实际开发中,MapStruct 是流行的用于 Bean 之间映射的工具库之一,其底层实现就是基于注解处理器 API。阅读源码,容易发现 MapStruct 库内部实现的注解处理器是 org.mapstruct.ap.MappingProcessor
(javadoc、github)。MappingProcessor
注解处理器生成的 Mapper 实现类,底层调用的就是 Filer
接口的 createSourceFile
方法,参见源代码 github。另外,MapStruct 库的注解处理器生成源代码文件利用了模板引擎 FreeMarker 库,可以参见 javadoc、github。
另外值得一提的是,除了模板引擎,生成源代码文件也可以使用 JavaPoet 工具库,JavaPoet 库提供 Java API 来生成 .java
源文件。笔者基于 JavaPoet 库,实现了能处理类似 Lombok 的 @Builder
注解的 BuilderProcessor 注解处理器,有兴趣的话可以查阅(附注:实际的 Lombok 的 @Builder
注解实现原理是修改语法树,并不是生成新的 Builder
类文件)。
依赖 JSR-269 实现的第三方工具库有很多[11],比如代码自动生成的 Lombok、MapStruct 和 Google Auto,代码检查的 Checker 和 Google Error Prone,编译阶段完成依赖注入的 Google Dagger 2 等。笔者在实际开发中就经常使用 Lombok 库和 MapStruct 库。MapStruct 库的实现原理,上文已经做了简单介绍。现在来看下 Lombok 的实现原理。
Lombok 提供 @NonNull
、@Getter
, @Setter
, @ToString
, @EqualsAndHashCode
, @Data
等注解,自动生成常见样板代码 boilerplate,解放开发效率。Lombok 支持 javac 和 ecj (Eclipse Compiler for Java)。对于 javac 编译器对应的注解处理器是 LombokProcessor,然后经过一些处理过程,每个注解都会有特定的 handler 来处理,@NonNull
对应 HandleNonNull
、@Getter
对应 HandleGetter
、@Setter
对应 HandleSetter
、@ToString
对应 HandleToString
、@EqualsAndHashCode
对应 HandleEqualsAndHashCode
、@Data
对应 HandleData
。如果想要改造 Lombok 项目,让 Lombok 支持新的注解,其实就是添加新的 handler。关于 Lombok 原理以及如何为 Lombok 贡献代码,文档 “Documentation for lombok developers”[12],也有简单介绍,可以阅读。
阅读这些 handler 的实现,可以看到样板代码的生成依赖的就是 com.sun.tools.javac.*
包。最新版的 Lombok 源码太繁杂了,可以从早期版本入手,比如 v0.8.1 版本。
现在来看下如何实现 @Getter
注解。@Getter
注解的功能,就是自动生成类字段的 getter 方法,如果注解加到 class 上,就生成类的全部字段的 getter 方法。假设字段名叫 foo
,那边生成的 getter 方法如下所示:
1 | public int getFoo() { |
参考 Lombok v0.8.1 和 v0.9.3 的 HandleGetter 实现源码(从 v0.9.3 版本开始,@Getter
注解支持加到 class 上,之前只能加到字段上),提取出其中的核心代码,实现 @Getter
的示例代码如下:
1 | private void handleGetter(JCTree.JCClassDecl classDecl) { |
容易发现,实现 @Getter
注解依赖的 JCTree
、TreeMaker
等相关类,这些类在上文都已经提及并介绍,不再复述。
为了加深对 javac 内部 API 的理解,笔者参考 Lombok 的源码,实现了支持类似 Lombok 的 @Data
、@Getter
、@Setter
、@Slf4j
注解的注解处理器,MyLombokProcessor,代码参见 GitHub。
附注:本文的示例代码的完整代码,都可以在 GitHub 的 annotation-processor-demo[13] 仓库上找到。
OpenJDK: The Java programming language Compiler Group http://openjdk.java.net/groups/compiler/ ↩
The Java Programming Language Compiler, javac https://docs.oracle.com/javase/8/docs/technotes/guides/javac/ ↩ ↩ ↩
2011-05 How does lombok work? http://stackoverflow.com/q/6107197 ↩
javac https://docs.oracle.com/javase/8/docs/technotes/tools/unix/javac.html https://www.mankier.com/1/javac ↩ ↩ ↩
OpenJDK: Compiler Package Overview https://openjdk.org/groups/compiler/doc/package-overview/index.html ↩ ↩
OpenJDK: The Hitchhiker's Guide to javac https://openjdk.org/groups/compiler/doc/hhgtjavac/index.html ↩
OpenJDK: Compilation Overview https://openjdk.org/groups/compiler/doc/compilation-overview/index.html ↩
莫枢 RednaxelaFX :JVM分享——Java程序的编译、加载与执行 http://www.valleytalk.org/2011/07/28/java-程序的编译,加载-和-执行/ ↩ ↩
深入解析Java编译器:源码剖析与实例详解,马智 2019 ↩
Awesome Java Annotation Processing https://github.com/gunnarmorling/awesome-annotation-processing ↩
Documentation for lombok developers https://projectlombok.org/contributing/ ↩
annotation-processor-demo https://github.com/yulewei/annotation-processor-demo ↩
先来看下 Executor 框架的 javadoc 描述 [ ref1 ref2 ]
接口:
实现:
涉及到的类与接口的层次结构,如下图所示:
Executor,此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。接口只定义唯一一个方法:
1 | void execute(Runnable command) |
实现 Executor 接口,就是定义某种运行任务的机制。最简单的运行任务的机制是,在调用者的线程中立即运行已提交的任务,如下:
1 | class DirectExecutor implements Executor { |
或者,以下实现将为每个任务生成一个新线程:
1 | class ThreadPerTaskExecutor implements Executor { |
更典型的执行任务的方式是,使用线程池(Thread pool)。
JDK 下的 Executors 类提供创建线程池的静态工厂方法:
这 4 个工厂方法返回的类型是 ExecutorService,该接口扩展自 Executor。ExecutorService 提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。
现在让我们来看看创建线程池的静态工厂方法,对应的实现源码 [ src ]:
1 | public static ExecutorService newFixedThreadPool(int nThreads) { |
可以看到 newFixedThreadPool
、newCachedThreadPool
和 newSingleThreadExecutor
内部都是通过类 ThreadPoolExecutor
实现。ThreadPoolExecutor 的构造方法的 javadoc 如下:
1 | public ThreadPoolExecutor(int corePoolSize, |
ThreadPoolExecutor 的处理流程如下图所示(参考自《Java并发编程的艺术》第9章 Java中的线程池):
基本上预定义的三个线程池已经满足常见的使用需求,若有特殊需求也可以,特殊构造 ThreadPoolExecutor 实例。此类提供 protected 的 beforeExecute 和 afterExecute 钩子 (hook) 方法,就是预留扩展用的。
ScheduledExecutorService 类提供的方法:
1 | ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) |
用法就如文档所示,不展开描述。
若直接使用线程运行任务,则典型的做法是创建 Runnable 接口实例,然后启动线程,如下:
1 | Runnable r = ... |
Runnable 实例是没有直接的办法获取运行结果的返回值的,若要获取,需要添加额外的代码,如示例中 getSavedValue
。
Executor 框架下,使用 ExecutorService 的 submit 方法提交任务。
1 | <T> Future<T> submit(Callable<T> task) |
Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Callable 会返回结果,并且可以抛出经过检查的异常。 submit 方法也可以接受 Runnable 参数,但阅读内部实现代码的话,就可以看到,最终还是会通过 Executors 类的 callable 方法,将 Runnable 转换成 Callable [ src src ]。Runnable 是为线程设计的,Callable 是为任务设计。任务和线程概念上分离,这样线程如何运用任务,即运行任务的机制,就可以按具体情况定义了。
上文提到 ExecutorService 扩展自 Executor 接口,那么现在就看下 submit 方法实现源码 [ src ]:
1 | public <T> Future<T> submit(Callable<T> task) { |
可以看到内部实现其实就是调用 execute,但传入参数和返回结果包裹了 Callable 和 Future。execute 方法跟具体实现有关,对于 ThreadPoolExecutor 的实现逻辑,代码会根据线程大小,以及任务队列 workQueue 和工作线程 worker 状况,做出相应选择:[ doc src ]:
ExecutorService
示例:
1 | package concurrent; |
输出结果:
1 | return 2 |
为什么输出结果是这样呢?查文档知道,Future 的 get 方法,若已经完成,则直接返回,否则会等待计算完成,然后获取其结果。在示例代码中,任务1 需要 2 秒完成,任务2 需要 1 秒完成,任务3 需要 3 秒完成。自然,在线程池中完成次序是,任务2 - 任务1 - 任务3。main 主线程,先是获取 get 任务1 的执行结果,需要等待 2 秒,在去获取 get 任务2 的执行结果,此时该任务已经完成,直接返回,接下来是获取 get 任务3 的执行结果,任务3 耗时 3 秒,此时时间线是第 2 秒,所以需等 1 秒,才能获取 get 执行结果。
使用 ExecutorService,若想按任务完成次序获取执行结果,可以将代码中 for 循环,修改为轮询方式,就不断地在调用 get 前,先用 isDone 判断是否任务已经完成。代码如下:
1 | while (true) { |
输出结果:
1 | return 2 |
这种实现方式,虽然可行,但相当繁琐。幸运的是,JDK 还提供一种更好的方法,CompletionService。
CompletionService 将 Executor 和 BlockingQueue 的功能融合在一起。你可以将 Callable 任务提交给它来执行,然后使用类似于队列操作的 take 和 poll 等方法来获得已完成的结果,而这些结果会在完成时将被封装为 Future。ExecutorCompletionService 实现了 CompletionService。ExecutorCompletionService 的实现非常简单。在构造函数中创建一个 BlockingQueue 来保存计算完成的结果。当计算完成时,调用 FutureTask 中的 done 方法。当提交某个任务时,该任务将首先包装为一个 QueueingFuture,这是 FutureTask 一个子类,然后再改写子类的 done 方法,并将结果放入 BlockingQueue 中,take 和 poll 方法委托给了 BlockingQueue,这些方法会在得出结果之前阻塞。[ Goetz 2006, 6.3.5 ]
可以看出整个实现逻辑核心在于重写 FutureTask 的 done 方法,正如这个方法的 javadoc 所述,定义该方法的目的就是如此:
1 | protected void done() |
再来看下,BlockingQueue 的实现源码 [ src ],以及 submit 任务是对应的实现源码 [ src ]:
1 | public Future<V> submit(Callable<V> task) { |
1 | private class QueueingFuture extends FutureTask<Void> { |
然后再看下 ExecutorCompletionService 的 take 和 poll 方法的实现源码 [ src ]:
1 | public Future<V> take() throws InterruptedException { |
整个实现逻辑正如上面文字所述。
上文示例的 ExecutorMain
,现在用 CompletionService
重现实现如下:
1 | package concurrent; |
运行结果,就是依次输出,任务2 - 任务1 - 任务3:
1 | return 2 |
CompletionService 的逻辑是,任务完成后,依次添加到完成队列中,然后让主线程主动去获取这些已经完成的任务。其实这种主线程主动模式,也可以修为被动模式,即任务完成后主动以事件回调的形式通知主线程,主动方从主线程换成了任务本身。这种方式就消除了等待任务完成的过程。可惜的是直到 Java 8 新添加的 CompletableFuture 才支持实现这种机制。在 Java 8 之前,需要借助第三方库,比如 Guava 提供的 ListenableFuture。使用 CompletableFuture 需要了解 lambda 以及函数式编程相关知识,本文暂不展开,本文只讨论 ListenableFuture。来看下 Guava 的 ListenableFuture 主要涉及到的类 [ doc ]:
这些类如何使用呢?还是先来看下示例代码吧。
1 | package concurrent; |
简单解释下。MoreExecutors 工具类的 listeningDecorator 方法将 JDK 创建的 ExecutorService 装饰为 ListeningExecutorService。接下来,创建的任务,并提交 submit 到这个 ExecutorService 里。submit 方法返回 ListenableFuture 接口实例(内部实现其实就是 ListenableFutureTask 对象)。Futures 工具类的 addCallback 方法用于添加在任务执行回调的监听器。
类似于 JDK 的 CompletionService,实现 Guava 的 ListeningExecutorService 也是通过重写 FutureTask 的 done 方法完成的。FutureTask 对应的子类就是 ListenableFutureTask(事实上,Guava 版本 19 开始 ListenableFutureTask 类替换成立了 TrustedListenableFutureTask 类 [ github ],这里暂不展开分析)。实现 ListenableFutureTask 的核心代码如下 [ src ]:
1 | private final ExecutionList executionList = new ExecutionList(); |
之前查看 submit 方法实现源码时,看到该方法内部会调用 newTaskFor 方法创建 FutureTask。相应的 JDK 的 AbstractExecutorService 的 newTaskFor 也 Guava 的 AbstractListeningExecutorService 被重写为 [ src ]:
1 | protected final <T> ListenableFutureTask<T> newTaskFor(Callable<T> callable) { |
这样在线程池中运行任务的不再是 FutureTask,而变成了 ListenableFutureTask。任务完成后调用 done 方法,而 done 方法就去执行该任务关联的监听器列表,即 executionList.execute()
。
再来看下 Futures 工具类的 addCallback 方法的实现 [ src ]:
1 | public static <V> void addCallback(final ListenableFuture<V> future, |
ListenableFuture API 功能的完整介绍参见 ListenableFutureExplained,本文不再展开。
一个大的任务可能会由多个子任务组成,比如整个任务A,由任务B 和任务C 组成,而任务B 又可以被分解为任务D 和任务E,如下图所示。任务分解组合问题,利用 ListenableFuture 的回调将子任务组合起来是一种解决办法,但还是不够优雅简洁。Java 7 引入的 Fork/Join 框架,就是以这种方式设计的。Fork/Join 框架编程的风格就是,将任务分解为多个子任务,并行执行,然后将结果组合起来,即分而治之。ExecutorService 适合解决相互独立的任务,而 Fork/Join 框架适合解决任务分解组合的情况 [ ref ]。
其实整个 java.util.concurrent 包是由 JSR-166 规范引入的,Fork/Join 框架就是其中的 jsr166y。JSR-166 由 Doug Lea 主导,是主要设计和代码实现者,而 jsr166y 最初源自他发表于 2000 年的论文“A Java Fork/Join Framework”(msa pdf)。
Fork/Join 框架采用工作窃取(work stealing)的任务调度机制 [ ref ]:
使用 LIFO 规则来处理每个工作线程的自己任务,窃取别的工作线程的任务却使用 FIFO 规则,这是一种被广泛使用的进行递归 fork/join 设计的一种调优手段。这种模式有以下两个优点:它通过窃取工作线程队列反方向的任务减少了竞争。同时,它利用了递归的分治算法越早的产生大任务这一特点。因此,更早期被窃取的任务有可能会提供一个更大的单元任务,从而使得窃取线程能够在将来进行递归分解。
这些规则的结果是,拥有相对细粒度的基本任务,比那些仅仅使用粗粒度划分或没有使用递归分解的任务运行更快。
ForkJoinPool 类是用于执行 ForkJoinTask 的 ExecutorService。在构造过程中,可以在构造函数中指定线程池的大小。如果使用的是默认的无参构造函数,那么会创建大小等同于可用处理器数量的线程池。ForkJoinPool 类的典型方法:
1 | public void execute(ForkJoinTask<?> task) |
提交到 ForkJoinPool 中的任务由 ForkJoinTask 抽象类表示。ForkJoinTask 的实现子类有 RecursiveAction 和 RecursiveTask,以及 Java 8 新增的 CountedCompleter。RecursiveAction 用于没有返回结果的任务,RecursiveTask 用于返回结果的任务。CountedCompleter 用于完成动作会触发另外一个动作的任务 [ javadoc ]。
计算斐波那契数(Fibonacci number)是一个经典的分而治之的问题。Doug Lea 的论文以及 RecursiveTask 的 javadoc 都以斐波那契数为例进行说明。斐波那契数定义公式为:
$$F_{n}=F_{n-1}+F_{n-2}$$
$$F_{0}=0, F_{1}=1$$
使用 Fork/Join 框架计算斐波那契数的示例代码如下:
1 | package concurrent; |
这个计算过程如下图所示:
新手在定义整数字段时,常常想当然通过,如int(3)
,来限制整数的有效长度,然而这样仅仅只是指定了显示宽度。选择有效长度不同的整数,需要使用tinyint
(1个字节)、smallint
(2个字节)、mediumint
(3个字节)、int
(4个字节)或bigint
(8个字节)。MySQL的相关文档如下[doc]:
MySQL supports an extension for optionally specifying the display width of integer data types in parentheses following the base keyword for the type. For example, INT(4) specifies an INT with a display width of four digits.
The display width does not constrain the range of values that can be stored in the column. Nor does it prevent values wider than the column display width from being displayed correctly. For example, a column specified as SMALLINT(3) has the usual SMALLINT range of
-32768
to32767
, and values outside the range permitted by three digits are displayed in full using more than three digits.
When used in conjunction with the optional (nonstandard) attribute ZEROFILL, the default padding of spaces is replaced with zeros. For example, for a column declared as INT(4) ZEROFILL, a value of
5
is retrieved as0005
.
如下示例,字段bar
类型是int(3)
,但依然能够正确保存数值12345
:
1 | mysql> create table test (foo int(3) zerofill, bar int(3) zerofill, baz int); |
MySQL同时支持bit
和bool
类型,但bool
仅仅是tinyint(1)
的同义词,创建的字段值的范围并不是0
和1
或true
和false
,而是-128
到127
。如下所示:
1 | mysql> create table test (foo bool, bar bit); |
从MySQL 4.1开始(2004年10月),用字符单位解释在字符列定义中的长度规范。(以前的一些MySQL版本以字节解释长度)。官方文档描述如下[doc]
In MySQL 4.1 and up , string data types include some features that you may not have encountered in working with versions of MySQL prior to 4.1:
MySQL interprets length specifications in character column definitions in character units. (Before MySQL 4.1, column lengths were interpreted in bytes.) This applies to
CHAR
,VARCHAR
, and theTEXT
types.
MySQL服务器默认的字符集是latin1
,使用的校对规则是latin1_swedish_ci
[doc](校对规则是在字符集内用于比较字符的一套规则)。若要保存中文,典型的做法是使用utf8
编码。但MySQL的utf8
编码最多只能保存使用utf8
编码后长度是3个字节的字符,即只支持基本多文种平面。使用MySQL的utf8保存常见的字符基本上没有问题,但对于生僻字或emoji字符就无能为力了。emoji的中的笑脸(grinning face)
的Unicode编码,如下 [ref1][ref2]:
表情 | Unicode | UTF-16 | UTF8 |
---|---|---|---|
😀 | U+1F604 | 0xD83D 0xDE04 | 0xF0 0x9F 0x98 0x84 |
emoji位于辅助多文种平面,utf8
需要4个字节保存。为了解决这个问题,MySQL 5.5.3开始支持utf8mb4
,支持辅助多文种平面,每个字符最大4个字节 [doc]。除了utf8
,MySQL还支持ucs2
、utf16
、utf32
等,完整列表如下 [ref]
字符集 | 支持的字符 | 每个字符需要的存储空间 |
---|---|---|
utf8 | 基本多文种平面 | 1, 2, 或 3 个字节 |
ucs2 | 基本多文种平面 | 2字节 |
utf8mb4 | 基本多文种平面和辅助多文种平面 | 1, 2, 3,或4字节 |
utf16 | 基本多文种平面和辅助多文种平面 | 2或4字节 |
utf16le | 基本多文种平面和辅助多文种平面 | 2或4字节 |
utf32 | 基本多文种平面和辅助多文种平面 | 4字节 |
##varchar和text
MySQL支持多种字符串类型, 如下表所示:
类型 | 最大字节长度 | 最大utf8 字符数 |
---|---|---|
char(M) | M,M为0~255之间的整数 | 85个utf8 字符 |
varchar(M) | M,M为0~65,535之间的整数 | 21,844个utf8 字符 |
tinytext | 255 (28−1) 字节 | 85个utf8 字符 |
text | 65,535 (216−1) 字节 = 64 KB | 21,844个utf8 字符 |
mediumtext | 16,777,215 (224−1) 字节 = 16 MB | 5,592,405个utf8 字符 |
longtext | 4,294,967,295 (232−1) 字节 = 4 GB | 1,431,655,765个utf8 字符 |
字符串类型实际支持的最大字符数与编码有关。比如,varchar
类型在utf8
编码下最大支持保存21,844 (65,535 / 3 = 21,844) 个字符,而utf8mb4
编码下最大支持保存16,383 (65,535 / 4 = 16,383) 个字符。
另外,MySQL表行最大总长度为65,535字节 [doc], 所以varchar
类型字段的最大字符数会被表中其他字段所占用的存储空间挤掉。blob
或text
类型的字段不会受表行总长度的限制,因为字段存储的实际内容和表行是分离的,只会占用表行的9到12个字节。
1 | mysql> create table test ( foo varchar(21845) character set utf8 ); |
MySQL支持的日期和时间类型如下表所示 [doc]:
日期和时间类型 | 字节 | 最小值 | 最大值 |
---|---|---|---|
date | 4 | 1000-01-01 | 9999-12-31 |
datetime | 8 | 1000-01-01 00:00:00.000000 | 9999-12-31 23:59:59.999999 |
timestamp | 4 | 1970-01-01 00:00:01.000000 | 2038-01-19 03:14:07.999999 |
time | 3 | -838:59:59.000000 | 838:59:59.000000 |
year | 1 | 1901 | 2155 |
1 | /** |
这些自动生成注释,没有实质性信息,其实真正有用的是,设计表时,该字段的用途,即表字段的注释。
庆幸的是,MyBatis生成器在版本1.3.3中添加了,addRemarkComments
选项 [github],可以在生成的实体类中附带表字段的注释。addRemarkComments
官方文档的介绍 [doc]:
This property is used to specify whether MBG will include table and column remarks from db table in the generated comments. The property supports these values:
- false: This is the default value When the property is false or unspecified, all generated comments will not include table and column remarks from db table when the element was generated.
- true: When the property is true, table and columns remarks from db table will be added to the generated comments.
Warning: If suppressAllComments option is true, this option will be ignored.
使用方法很简单,只需将MyBatis生成器的配置文件中的commentGenerator
节点修改为:
1 | <commentGenerator> |
通过addRemarkComments
选项生成的实体类的注释不够精简。于是笔者参考MyBatis的默认的注释生成器DefaultCommentGenerator
[github],对其进行改造成如下:
1 | package com.test.mbg; |
MyBatis的配置文件修改为:
1 | <commentGenerator type="com.test.mbg.RemarksCommentGenerator"> |
改用自定义的RemarksCommentGenerator
后,运行MyBatis插件可能会报错。需要使用Java来运行MyBatis生成器,代码如下 [doc]:
1 | package com.test.mbg; |
对集合的filter、map和reduce操作,示例代码如下:
1 | // 计算偶数个数 |
流(Stream)和集合(collection)有以下区别 [doc]:
当使用Stream时,会通过三个阶段来建立一个操作流水线 [ref]。
中间操作
。终止操作
来产生一个结果。该操作会强制它之前的延迟操作立即执行。在这之后,该Stream就不会再被使用了。整个流操作流水线,如图所示 [ref]:
Java 8的流创建、中间操作和终止操作的API汇总表,如下 [ref]
创建流 | 中间操作 | 终止操作 |
---|---|---|
Collection Stream, IntStream, LongStream, DoubleStream IntStream, LongStream Arrays BufferedReader Files JarFile ZipFile Pattern SplittableRandom Random ThreadLocalRandom BitSet CharSequence (String) StreamSupport (low level) | BaseStream sequential() parallel() unordered() onClose(..) Stream IntStream、LongStream和DoubleStream方法与Stream类似, 其中额外的方法如下: IntStream LongStream DoubleStream | BaseStream Stream IntStream、LongStream和DoubleStream方法与Stream类似, 其中额外的方法如下: IntStream, LongStream, DoubleStream |
筛选与映射操作,即上文所说的中间操作。相关的API如下:
1 | Stream<T> filter(Predicate<? super T> predicate) |
filter(..),筛选出元素符合某个条件的新流。map(..),用于对元素作某种形式的转换。flatMap(..),用于将多个子流合并为一个新流。
其他的中间操作有,提取子流,limit(..)(提取前n个)和skip(..)(丢弃前n个);有状态的转换,distinct()(过滤重复元素)和sorted(..)(排序),以及主要用于日志调试的peek(..)。
1 | Stream<T> limit(long maxSize) |
流有多种形式的通用的归约操作,reduce(..)和collect(..),同时也有多种特化的归约形式,比如 sum()、min(..)、max(..)或count()等。reduce(..)和collect(..)的方法签名如下:
1 | Optional<T> reduce(BinaryOperator<T> accumulator) |
sum()、min(..)、max(..)或count()等特化的归约操作,是reduce(..)归约操作的特殊化,内部实现依赖reduce(..),相应JDK的实现源码可以作为印证。
IntStream的sum(..)的JDK现实源码为 [github]:
1 |
|
IntStream的min(..)的JDK现实源码为 [github]:
1 |
|
Stream的count()的JDK现实源码为 [github]:
1 |
|
如果不想将流归约为单个值,而只要查看集合被流操作后的结果,需要使用收集器(collector)。上文已经见到,将流收集为List:
1 | List<Integer> evenList = list.stream() |
Collectors是收集器工具类,提供获取预定义收集器(实现接口Collector)的静态方法。Collectors类,除了将流收集为List,还可以是Map、Set、String,或者也能进行统计操作,如求和、计算平均值、最大值、最小值。Collectors的全部静态方法列表如下:
1 | PdfReader reader1 = new PdfReader("in1.pdf"); |
导出书签文件:
1 | PdfReader reader = new PdfReader("in.pdf"); |
将文档1中的标注(包括超链接)转存到文档2:
1 | PdfReader reader1 = new PdfReader("in1.pdf"); |
如果仅仅读取全部链接,只需将上述代码18行开始的for循环修改为:
1 | for (int j = 0; annotArray != null && j < annotArray.size(); ++j) { |
参考资料:
AOSA部分译文:
https://en.wikipedia.org/wiki/Template:Software_engineering
https://en.wikipedia.org/wiki/History_of_software_engineering
2009-05 敏捷书籍推荐 http://www.infoq.com/cn/news/2009/05/recommended-agile-books
2010-09 DZone's Top 10 Agile and Lean Development Books http://agile.dzone.com/articles/dzones-top-10-agile-and-lean
2010-08 最热门的敏捷书籍 http://www.infoq.com/cn/news/2010/08/top-agile-books
2011-08 2011年最热门的敏捷书籍排行榜 http://www.infoq.com/cn/news/2011/08/top-agile-books
著名软件项目
Steve McConnell,wiki
]]>http://en.wikipedia.org/wiki/Database
http://en.wikipedia.org/wiki/Template:Databases
http://en.wikipedia.org/wiki/Template:SQL
Ask HN: Help me find a good databases textbook https://news.ycombinator.com/item?id=377669
Database Internals - Where to Begin? http://stackoverflow.com/q/770273
Ask HN: good book or resources to get my SQL skills to the next level https://news.ycombinator.com/item?id=1370721
Ask HN: Is there a K&R for SQL? https://news.ycombinator.com/item?id=5087439
Tips for getting started with SQL? http://stackoverflow.com/q/110124
http://en.wikipedia.org/wiki/Template:MySQL
https://github.com/shlomi-noach/awesome-mysql
http://en.wikipedia.org/wiki/Database_engine
2014-01 Any recommended MySQL books for more advanced stuff? (self.mysql) http://redd.it/292u19
What resources exist for Database performance-tuning? http://stackoverflow.com/q/761204
2010-12 12 Best MySQL Database Books for Your Library http://www.thegeekstuff.com/2010/12/12-best-mysql-books/
https://zh.wikipedia.org/wiki/Template:程序设计语言
https://en.wikipedia.org/wiki/Template:IPC
https://en.wikipedia.org/wiki/Programming_language#Further_reading
Language Books/Tutorials for popular languages http://stackoverflow.com/q/22873
编程语言、程序分析相关书籍:
编译器相关书籍:
Java SE Technologies at a Glance http://www.oracle.com/technetwork/java/javase/tech/index.html
http://docs.oracle.com/javase/8/
http://docs.oracle.com/javase/8/docs/index.html Platform Overview平台概览图
http://docs.oracle.com/javase/8/javase-books.htm
Java Platform Overview (docs/technotes/guides)
Java SE API:
http://docs.oracle.com/javase/8/docs/technotes/guides/scripting/
中文API文档:
http://download.oracle.com/technetwork/java/javase/6/docs/zh/api/
http://www.cjsdn.net/Doc/JDK60/
https://en.wikipedia.org/wiki/Template:Java_Virtual_Machine
https://en.wikipedia.org/wiki/Java_performance
JDK源码:
https://github.com/dmlloyd/openjdk
https://github.com/openjdk-mirror/jdk7u-jdk
https://github.com/openjdk-mirror/jdk7u-hotspot
虚拟机与性能
http://en.wikipedia.org/wiki/Template:Java_%28software_platform%29
http://en.wikipedia.org/wiki/Template:Java_Virtual_Machine
http://en.wikipedia.org/wiki/Java_performance
http://openjdk.java.net/groups/hotspot/
Java SE HotSpot at a Glance,link
Troubleshooting Java SE 8,link 资料汇总
]]>http://docs.oracle.com/javase/8/
http://docs.oracle.com/javase/tutorial/
Java Language and Virtual Machine Specifications http://docs.oracle.com/javase/specs/
Sun官方Addison-Wesley出版的Java Series,link
https://github.com/iluwatar/java-design-patterns
2012-12 Review : Java 7 Concurrency Cookbook,原文:mkyong.com,译文:importnew.com