0%

基本上来说,在分布式系统中最耗性能的地方就是最后端的数据库了。一般来说,只要小心维护好,数据库四种操作(select、update、insert 和 delete)中的三个写操作 insert、update 和 delete 不太会出现性能问题(insert 一般不会有性能问题,update 和 delete 一般会有主键,所以也不会太慢)。除非索引建得太多,而数据库里的数据又太多,这三个操作才会变慢。

绝大多数情况下,select 是出现性能问题最大的地方。一方面,select 会有很多像 join、group、order、like 等这样丰富的语义,而这些语义是非常耗性能的;另一方面,大多数应用都是读多写少,所以加剧了慢查询的问题。

分布式系统中远程调用也会耗很多性能,因为有网络开销,会导致整体的响应时间下降。为了挽救这样的性能开销,在业务允许的情况(不需要太实时的数据)下,使用缓存是非常必要的事情。

从另一个方面说,缓存在今天的移动互联网中是必不可少的一部分,因为网络质量不总是最好的,所以前端也会为所有的 API 加上缓存。不然,网络不通畅的时候,没有数据,前端都不知道怎么展示 UI 了。既然因为移动互联网的网络质量而导致我们必需容忍数据的不实时性,那么,从业务上来说,在大多数情况下是可以使用缓存的。

缓存是提高性能最好的方式,一般来说,缓存有以下三种模式。

Cache Aside 更新模式

这是最常用的设计模式了,其具体逻辑如下。

  • 失效:应用程序先从 cache 取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从 cache 中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

img

img

这是标准的设计模式,包括 Facebook 的论文《Scaling Memcache at Facebook》中也使用了这个策略。为什么不是写完数据库后更新缓存?可以看一下 Quora 上的这个问答《Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?》,主要是怕两个并发的写操作导致脏数据。

那么,是不是这个 Cache Aside 就不会有并发问题了?不是的。比如,一个是读操作,但是没有命中缓存,就会到数据库中取数据。而此时来了一个写操作,写完数据库后,让缓存失效,然后之前的那个读操作再把老的数据放进去,所以会造成脏数据。

这个案例理论上会出现,但实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且有一个并发的写操作。实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,又要晚于写操作更新缓存,所有这些条件都具备的概率并不大。

所以,这也就是 Quora 上的那个答案里说的,要么通过 2PC 或是 Paxos 协议保证一致性,要么就是拼命地降低并发时脏数据的概率。而 Facebook 使用了这个降低概率的玩法,因为 2PC 太慢,而 Paxos 太复杂。当然,最好还是为缓存设置好过期时间。

Read/Write Through 更新模式

我们可以看到,在上面的 Cache Aside 套路中,应用代码需要维护两个数据存储,一个是缓存(cache),一个是数据库(repository)。所以,应用程序比较啰嗦。而 Read/Write Through 套路是把更新数据库(repository)的操作由缓存自己代理了,所以,对于应用层来说,就简单很多了。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的 Cache。

Read Through

Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或 LRU 换出),Cache Aside 是由调用方负责把数据加载入缓存,而 Read Through 则用缓存服务自己来加载,从而对应用方是透明的。

Write Through

Write Through 套路和 Read Through 相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后由 Cache 自己更新数据库(这是一个同步操作)。

下图自来 Wikipedia 的 https://en.wikipedia.org/wiki/Cache_(computing">Cache 词条 。其中的 Memory,可以理解为就是我们例子里的数据库。

img

Write Behind Caching 更新模式

Write Behind 又叫 Write Back。一些了解 Linux 操作系统内核的同学对 write back 应该非常熟悉,这不就是 Linux 文件系统的 page cache 算法吗?基础知识全都是相通的。所以,基础很重要。

Write Back 套路就是,在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的 I/O 操作飞快无比(因为直接操作内存嘛)。因为异步,Write Back 还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。

但其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道 Unix/Linux 非正常关机会导致数据丢失,就是因为这个事)。在软件设计上,我们基本上不可能做出一个没有缺陷的设计,就像算法设计中的时间换空间、空间换时间一个道理。有时候,强一致性和高性能,高可用和高性能是有冲突的。软件设计从来都是 trade-off(取舍)。

另外,Write Back 实现逻辑比较复杂,因为它需要 track 有哪些数据是被更新了的,需要刷到持久层上。操作系统的 Write Back 会在仅当这个 cache 需要失效的时候,才会把它真正持久起来。比如,内存不够了,或是进程退出了等情况,这又叫 lazy write。

在 Wikipedia 上有一张 Write Back 的流程图,基本逻辑可以在下图中看到。

img

缓存设计的重点

缓存更新的模式基本如前面所说,不过这还没完,缓存已经成为高并发高性能架构的一个关键组件了。现在,很多公司都在用 Redis 来搭建他们的缓存系统。一方面是因为 Redis 的数据结构比较丰富。另一方面,我们不能在 Service 内放 local cache,一是每台机器的内存不够大,二是我们的 Service 有多个实例,负载均衡器会把请求随机分布到不同的实例。缓存需要在所有的 Service 实例上都建好,这让我们的 Service 有了状态,更难管理了。

所以,在分布式架构下,一般都需要一个外部的缓存集群。关于这个缓存集群,需要保证的是内存要足够大,网络带宽也要好,因为缓存本质上是个内存和 IO 密集型的应用。

另外,如果需要内存很大,那么还要动用数据分片技术来把不同的缓存分布到不同的机器上。这样,可以保证我们的缓存集群可以不断地 scale 下去。

缓存的好坏要看命中率。缓存的命中率高说明缓存有效,一般来说命中率到 80% 以上就算很高了。当然,有的网络为了追求更高的性能,要做到 95% 以上,甚至可能会把数据库里的数据几乎全部装进缓存中。这当然是不必要的,也是没有效率的,因为通常来说,热点数据只会是少数。

另外,缓存是通过牺牲强一致性来提高性能的,这世上任何事情都不是免费的,所以并不是所有的业务都适合用缓存,这需要在设计的时候仔细调研好需求。使用缓存提高性能,就是会有数据更新的延迟。

缓存数据的时间周期也需要好好设计,太长太短都不好,过期期限不宜太短,因为可能导致应用程序不断从数据存储检索数据并将其添加到缓存。同样,过期期限不宜太长,因为这会导致一些没人访问的数据还在内存中不过期,而浪费内存。

使用缓存的时候,一般会使用 LRU 策略。也就是说,当内存不够需要有数据被清出内存时,会找最不活跃的数据清除。所谓最不活跃的意思是最长时间没有被访问过了。所以,开启 LRU 策略会让缓存在每个数据访问的时候把其调到前面,而要淘汰数据时,就从最后面开始淘汰。

于是,对于 LRU 的缓存系统来说,其需要在 key-value 这样的非顺序的数据结构中维护一个顺序的数据结构,并在读缓存时,需要改变被访问数据在顺序结构中的排位。于是,我们的 LRU 在读写时都需要加锁(除非是单线程无并发),因此 LRU 可能会导致更慢的缓存存取的时间。这点要小心。

最后,我们的世界是比较复杂的,很多网站都会被爬虫爬,要小心这些爬虫。因为这些爬虫可能会爬到一些很古老的数据,而程序会把这些数据加入到缓存中去,而导致缓存中那些真实的热点数据被挤出去(因为机器的速度足够快)。对此,一般来说,我们需要有一个爬虫保护机制,或是我们引导这些人去使用我们提供的外部 API。在那边,我们可以有针对性地做多租户的缓存系统(也就是说,把用户和第三方开发者的缓存系统分离开来)。

小结

好了,我们来总结一下今天分享的主要内容。首先,缓存是为了加速数据访问,在数据库之上添加的一层机制。讲了几种典型的缓存模式,包括 Cache Aside、Read/Write Through 和 Write Behind Caching 以及它们各自的优缺点。

最后,介绍了缓存设计的重点,除了性能之外,在分布式架构下和公网环境下,对缓存集群、一致性、LRU 的锁竞争、爬虫等多方面都需要考虑。

或多或少我们都会经历线上的故障。在我的职业生涯中,就经历过很多的线上故障。老实说,线上故障是技术人员成长中必须要经历的事。从故障中我们可以吸取到很多教训,能让我们学到很多书本上学不到的知识。坑踩多了,会变得越来越有经验,也就成为老司机了。

不过,很多公司处理线上故障的方式并不科学,而且存在很多问题,所以,分享一些我的经验。这些经验主要来自亚马逊和阿里这两家互联网公司,以及个人的经验总结。希望这套方法能够有帮助。

故障发生时

在故障发生时,最重要的是快速恢复故障。而快速恢复故障的前提是快速定位故障源。因为在很多分布式系统中,一旦发生故障就会出现“多米诺骨牌效应”。也就是说,系统会随着一个故障开始一点一点地波及到其它系统,而且这个过程可能会很快。一旦很多系统都在报警,要想快速定位到故障源就不是一件简单的事了。

在亚马逊内部,每个开发团队至少都会有一位 oncall 的工程师。在 oncall 的时候,工程师要专心处理线上故障,轮换周期为每人一周。一旦发生比较大的故障,比如,S1 全部不可用,或 S2 某功能不可用,而且找不到替代方案,那么这个故障就会被提交到一个工单系统里。几乎所有相关团队 oncall 的工程师都会被叫到线上处理问题。

工作流是,先线上签到,然后自查自己的服务,如果自己的服务没有问题,那么就可以在旁边待命(standby),以备在需要时进行配合。如果问题没有被及时解决,就会自动升级到高层,直到 SVP 级别。

大家都知道,在亚马逊,不是按技能分工,而是按职责分工,也就是一个团队不是按前端、后端、运维等来分工,而是按所负责的 Service 来分工。所以,亚马逊的开发人员都是前端、后端、测试、运维全部都要干的。而亚马逊内部有很多的服务,一旦出现问题,为了避免一个工单在各个团队流转,需要所有团队上线处理,这样是最快的。

如果我们的系统架构是分布式服务化的,那么一个用户的请求可能会经过很多的服务,开发和运维起来是非常麻烦的。此时,跨团队跨部门的开发和运维就变得非常的重要了。就我的经历而言,在故障发生时,亚马逊的处理过程是比较有效和快速的,尤其是能够快速地定位故障源。对于被影响的其他团队也可以做一定的处理,比如做降级处理,这样可以控制故障的范围不被扩散。

故障源团队通常会有以下几种手段来恢复系统。

  • 重启和限流。重启和限流主要解决的是可用性的问题,不是功能性的问题。重启还好说,但是限流这个事就需要相关的流控中间件了。
  • 回滚操作。回滚操作一般来说是解决新代码的 bug,把代码回滚到之前的版本是快速的方式。
  • 降级操作。并不是所有的代码变更都是能够被回滚的,如果无法回滚,就需要降级功能了。也就是说,需要挂一个停止服务的故障公告,主要是不要把事态扩大。
  • 紧急更新。紧急更新是常用的手段,这个需要强大的自动化系统,尤其是自动化测试和自动化发布系统。假如你要紧急更新 1000 多台服务器,没有一个强大的自动化发布系统是很难做到的。

也就是说,出现故障时,最重要的不是 debug 故障,而是尽可能地减少故障的影响范围,并尽可能快地修复问题

国内的很多公司,都是由专职的运维团队来处理线上问题的。然而,运维团队通常只能处理一些基础设施方面的问题,或是非功能性的问题。对于一些功能性的问题,运维团队是完全没有能力处理的,只能通过相应的联系人,把相关的开发人员叫到线上来看。而可能这个开发人员看到的是别的系统有问题,又会叫上其它团队的人来。所以,一级一级地传递下去,会浪费很多时间。

故障前的准备工作

为了能够在面临故障时做得有条不紊,我们需要做一些前期的准备工作。这些准备工作做得越细,那么故障处理起来也就越有条理。我们知道,故障来临时,一切都会变得混乱。此时,对于需要处理故障的我们来说,事可以乱,但人不能乱。如果人跟着事一起乱,那就是真正的混乱了。

所以,我们需要做一些故障前的准备工作。在这里,我给出一些我的经验。

  • 以用户功能为索引的服务和资源的全视图。首先,我们需要一个系统来记录前端用户操作界面和后端服务,以及服务所使用到的硬件资源之间的关联关系。这个系统有点像 CMDB(配置管理数据库),但是比 CMDB 要大得多,是以用户端的功能来做索引的。然后,把后端的服务、服务的调用关系,以及服务使用到的资源都关联起来做成一个视图。

这个视图最好是由相应的自动化监控系统生成。有了这个资源图后,我们就可以很容易地找到处理故障的路径了。这就好像一张地图,如果没有地图,我们只能像个无头苍蝇一样乱试了

  • 为地图中的各个服务制订关键指标,以及一套运维流程和工具,包括应急方案。以用户功能为索引,为每个用户功能的服务都制订一个服务故障的检测、处理和恢复手册,以及相关的检测、查错或是恢复的运维工具。对于基础层和一些通用的中间件,也需要有相应的最佳实践的方法。

比如 Redis,怎样检查其是否存在问题,怎样查看其健康和运行状态?哪些是关键指标,面对常见的故障应该怎么应对,服务不可用的服务方案是什么,服务需要回滚了应该怎么操作,等等。这就好像一个导航仪,能够告诉你怎么做。而没有导航仪,就没有章法,会导致混乱

  • 设定故障的等级。还要设定不同故障等级的处理方式。比如,亚马逊一般将故障分为 4 级:1 级是全站不可用;2 级是某功能不可用,且无替代方案;3 级是某功能不可用,但有替代方案;4 级是非功能性故障,或是用户不关心的故障。阿里内的分类更多样一些,有时会根据影响多少用户来定故障等级。

制定故障等级,主要是为了确定该故障要牵扯进多大规模的人员来处理。故障级别越高,牵扯进来的人就越多,参与进来的管理层级别也就越高。就像亚马逊内的全员上线 oncall 一样。这就好像是我们社会中常用的“红色警报”、“橙色警报”、“黄色警报”之类的,会触发不同的处理流程

  • 故障演练。故障是需要演练的。因为故障并不会时常发生,但我们又需要不断提升处理故障的能力,所以需要经常演练。一些大公司,如 Netflix,会有一个叫 Chaos Monkey 的东西,随机地在生产线上乱来。Facebook 也会有一些故障演习,比如,随机关掉线上的一些服务器。总之,要提升故障处理水平,最好的方式就是实践。见得多了,处理得多了,才能驾轻就熟。故障演练是一个非常好的实践
  • 灰度发布系统。要减少线上故障的影响范围,通过灰度发布系统来发布是一个很不错的方式。毕竟,我们在测试环境中很难模拟出线上环境的所有情况,所以,在生产线上进行灰度发布或是 A/B 测试是一件很好的事。

在亚马逊,发布系统中有一个叫 Weblab 的系统,就是用来做灰度发布的。另外,亚马逊全球会有多个站点。一般来说,会先发中国区。如果中国区没什么问题了,就发日本区,然后发欧洲区,最后是美国区。而如果没有很多站点的话,那么你就需要一个流量分配系统来做这个事了。

评分

0 - you are unfamiliar with the subject area.(0 -你不熟悉主题领域。)

1 - you can read / understand the most fundamental aspects of the subject area.(1 -你可以阅读/了解主题领域最基本的方面。)

2 - ability to implement small changes, understand basic principles and able to figure out additional details with minimal help.(2 -能够实现小的变化,理解基本原理,并能在最小的帮助下找出更多的细节。)

3 - basic proficiency in a subject area without relying on help.(3 -在不依赖帮助的情况下,熟练掌握某一科目。)

4 - you are comfortable with the subject area and all routine work on it: (4 -你对主题领域和所有日常工作都很熟悉:)

For software areas - ability to develop medium programs using all basic language features w/o book, awareness of more esoteric features (with book).(对于软件领域来说,能够使用所有基本的语言来开发中等的程序,使用w/o book,了解更深奥的特性(带书)。)

For systems areas - understanding of many fundamentals of networking and systems administration, ability to run a small network of systems including recovery, debugging and nontrivial troubleshooting that relies on the knowledge of internals.(对于系统领域——了解网络和系统管理的许多基础知识,能够运行一个小型的系统网络,包括恢复、调试和依赖于内部知识的重要故障排除。)

5 - an even lower degree of reliance on reference materials. Deeper skills in a field or specific technology in the subject area.(5 -对参考资料的依赖程度更低。在某一领域或某一特定技术领域有较深的技能。)

6 - ability to develop large programs and systems from scratch. Understanding of low level details and internals. Ability to design / deploy most large, distributed systems from scratch.(6 -能够从头开始开发大型程序和系统。了解低层次的细节和内部信息。能够设计/部署大多数大型的分布式系统。)

7 - you understand and make use of most lesser known language features, technologies, and associated internals. Ability to automate significant amounts of systems administration.(7 -你理解并利用最不知名的语言特征、技术和相关的内部信息。能够自动化大量的系统管理。)

8 - deep understanding of corner cases, esoteric features, protocols and systems including “theory of operation”. Demonstrated ability to design, deploy and own very critical or large infrastructure, build accompanying automation.(8 -深刻理解角落案例,深奥的特点,协议和系统,包括“操作理论”。演示了设计、部署和拥有非常关键或大型基础设施的能力,并建立了相应的自动化。)

9 - could have written the book about the subject area but didn’t; works with standards committees on defining new standards and methodologies.(9 -本可以写关于主题领域的书,但没有;与标准委员会一起制定新的标准和方法。)

10 - wrote the book on the subject area (there actually has to be a book). Recognized industry expert in the field, might have invented it.(10 -写在主题领域的书(实际上必须有一本书)。业内公认的业内专家,可能已经发明了它。)

Subject Areas

TCP/IP Networking (OSI stack, DNS etc)

Unix/Linux internals

Unix/Linux Systems administration

Algorithms and Data Structures

C

C++

Python

Java

Perl

Go

Shell Scripting (sh, Bash, ksh, csh)

SQL and/or Database Admin

Scripting language of your choice (not already mentioned) _

People Management

Project Management

作者:carolsanders
链接:https://www.jianshu.com/p/b1f57417320d
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

软件还有一些配置信息,比如数据库的用户名和密码,还有一些我们不想写死在代码里的东西,像线程池大小、队列长度等运行参数,以及日志级别、算法策略等,还有一些是软件运行环境的参数,如 Java 的内存大小,应用启动的参数,包括操作系统的一些参数配置……

所有这些东西,我们都叫做软件配置。以前,我们把软件配置写在一个配置文件中,就像 Windows 下的 ini 文件,或是 Linux 下的 conf 文件。然而,在分布式系统下,这样的方式就变得非常不好管理,并容易出错。于是,为了便于管理,我们引入了一个集中式的配置管理系统,这就是配置中心的由来。

现在,软件的配置中心是分布式系统的一个必要组件。这个系统听起来很简单,但其实并不是。

配置中心的设计

区分软件的配置

首先,我们要区分软件的配置,软件配置的区分有多种方式。

有一种方式是把软件的配置分成静态配置和动态配置。所谓静态配置其实就是在软件启动时的一些配置,运行时基本不会进行修改,也可以理解为是环境或软件初始化时需要用到的配置。

例如,操作系统的网络配置,软件运行时 Docker 进程的配置,这些配置在软件环境初始化时就确定了,未来基本不会修改了。而所谓动态配置其实就是软件运行时的一些配置,在运行时会被修改。比如,日志级别、降级开关、活动开关。

当然,我们这里的内容主要针对动态配置的管理。

对于动态配置的管理,我们还要做好区分。一般来说,会有三个区分的维度。

  • 按运行环境分。一般来说,会有开发环境、测试环境、预发环境、生产环境。这些环境上的运行配置都不完全一样,但是理论来说,应该是大同小异的。
  • 按依赖区分。一种是依赖配置,一种是不依赖的内部配置。比如,外部依赖的 MySQL 或 Redis 的连接配置。还有一种完全是自己内部的配置。
  • 按层次分。就像云计算一样,配置也可以分成 IaaS、PaaS、SaaS 三层。基础层的配置是操作系统的配置,中间平台层的配置是中间件的配置,如 Tomcat 的配置,上层软件层的配置是应用自己的配置。

这些分类方式其实是为了更好地管理我们的配置项。小公司无所谓,而当一个公司变大了以后了,如果这些东西没有被很好地管理起来,那么会增加太多系统维护的复杂度。

配置中心的模型

有了上面为配置项的分类,我们就可以设计软件配置模型了。

首先,软件配置基本上来说,每个配置项就是 key/value 的模型。

然后,我们把软件的配置分成三层。操作系统层和平台层的配置项得由专门的运维人员或架构师来配置。其中的 value 应该是选项,而不是让用户可以自由输入的,最好是有相关的模板来初始化全套的配置参数。而应用层的配置项,需要有相应的命名规范,最好有像 C++ 那样的名字空间的管理,确保不同应用的配置项不会冲突。

另外,配置参数中,如果有外部服务依赖的配置,强烈建议不要放在配置中心里,而要放在服务发现系统中。因为一方面这在语义上更清楚一些,另外,这样会减少因为运行不同环境而导致配置不同的差异性(如测试环境和生产环境的不同)。

对于不同运行环境中配置的差异来说,比如在开发环境和测试环境下,日志级别是 Debug 级,对于生产环境则是 Warning 或 Error 级,因为环境的不一样,会导致我们需要不同的配置项的值。这点需要考虑到。

还有,配置需要有一个整体的版本管理,每次变动都能将版本差异记录下来。当然,如果可能,最好能和软件的版本号做关联。

可以看到,其中有些配置是通过模板来选择的,有的配置需要在不同环境下配置不同值。所以,还需要一个配置管理的工具,可能是命令行的,也可以是 Web 的。这个工具的界面在文本中(下面这个 UI 的 mockup 只是想表明一个模型)。

img

用户可以根据不同的机器型号还有不同的环境直接调出后台配置好的相关标准配置的模板。对于一些用户需要自己调整的参数也可以在这个模板上进行调整和配置(当然,为了方便运维和管理最好不要进行调整)。然后,用户可以在下面的那个表格中填写好自己的应用要用的参数和各个环境中的值。

这样一来,这个工具就可以非常方便地让开发人员来配置他们自己的软件配置。而我们的配置中心还需要提 API 来让应用获取配置。这个 API 上至少需要有如下参数:服务名,配置的版本号,配置的环境。

配置中心的架构

接下来,要来解决配置落地的问题。我们可以看到,和一个软件运行有关系的各种配置隶属于不同的地方,所以,要让它们落地还需要些不一样的细节要处理。文本中,我们给了一个大概的架构图。

img

在这个图中可以看到,我们把配置录入后,配置中心发出变更通知,配置变更控制器会来读取最新的配置,然后应用配置。这看上去很简单,但是有很多细节问题,下面我来一一说明。

  • 为什么需要一个变更通知的组件,而不是让配置中心直接推送?原因是,分布式环境下,服务器太多,推送不太现实,而采用一个 Pub/Sub 的通知服务可以让数据交换经济一些。
  • 为什么不直接 Pub 数据过去,还要订阅方反向拉数据?直接推数据当然可以,但让程序反过来用 API 读配置的好处是,一方面,API 可以校验请求者的权限,另一方面,有时候还是需要调用配置中心的基本 API,比如下载最新的证书之类的。还有就是,服务启动时需要从服务中心拉一份配置下来。
  • 配置变更控制器部署在哪里?是在每个服务器上呢,还是在一个中心的地方?我觉得因为这个事是要变更配置,变更配置又是有很多步骤的,所以这些步骤算是一个事务。为了执行效率更好,事务成功率更大,建议把这个配置变更的控制放在每一台主机上。
  • 平台层的配置变更,有的参数是在服务启动的命令行上,这个怎么变更呢?一般来说,命令行上的参数需要通过 Shell 环境变量做成配置项,然后通过更改系统环境变量,并重启服务达到配置变更。
  • 操作系统的配置变更和平台层的配置变更最好模块化掉,就像云服务中的不同尺寸的主机型号一样。这样有利于维护和减少配置的复杂性。
  • 应用服务配置更新的标准化。因为一个公司的应用由不同的团队完成,所以,可能其配置会因为应用的属性不同而不一样。为了便于管理,最好有统一的配置更新。一般来说,有的应用服务的配置是在配置文件中,有的应用服务的配置是通过调用 Admin API 的方式变更,不同的应用系统完全不一样,你似乎完全没有方法做成统一的。这里给几个方案。
  • 可以通过一个开发框架或 SDK 的方式来解决,也就是应用代码找你这个 SDK 来要配置,并通过 observer 模式订阅配置修改的事件,或是直接提供配置变更的 Admin 的 API。这种方式的好处在于在开发期标准化,并可以规范开发;不好的是,耦合语言。
  • 通过一个标准应用运维脚本,让应用方自己来提供应用变更时的脚本动作。这种方式虽然通过运维的方式标准化掉配置变更的接口,就可以通过一个配置控制器来统一操作各个应用变更,但是在这个脚本中各个应用方依然使用着各种不同的方式来变更配置。这种方式的好处是不耦合语言,灵活,但对于标准化的建设可能不利,而且使用或者调用脚本是 Bug 很多的东西,容易出问题。
  • 或是结合上述两种方案,不使用开发阶段的 SDK 方式嵌入到应用服务中,而是为每个应用服务单独做一个 Agent。这个 Agent 对外以 Admin API 的方式服务,后面则适配应用的配置变更手段,如更新配置文件,或者调用应用的 API 等。这种方式在落地方面是很不错的(这其中是另一种设计模式,后面会讲到)。

配置中心的设计重点

配置中心主要的用处是统一和规范化管理所有的服务配置,也算是一种配置上的治理活动。所以,配置中心的设计重点应该放在如何统一和标准化软件的配置项,其还会涉及到软件版本、运行环境、平台、中间件等一系列的配置参数。如果你觉得软件配置非常复杂,那么,你应该静下心来仔细梳理或治理一下现有的配置参数,并简化相应的配置,使用模块会是一种比较好的简化手段。

根据我们前面《编程范式游记》中所说的,编程的本质是对 logic 和 control 的分离,所以,对于配置也一样,其也有控制面上的配置和业务逻辑面上的配置,控制面上的配置最好能标准统一。

配置更新的时候是一个事务处理,需要考虑事务的问题,如果变更不能继续,需要回滚到上个版本的配置。配置版本最好和软件版本对应上。

配置更新控制器,需要应用服务的配合,比如,配置的 reload,服务的优雅重启,服务的 Admin API,或是通过环境变量……这些最好是由一个统一的开发框架搞定。

配置更新控制器还担任服务启动的责任,由配置更新控制器来启动服务。这样,配置控制器会从配置中心拉取所有的配置,更新操作系统,设置好启动时用的环境变量,并更新好服务需要的配置文件 ,然后启动服务。(当然,你也可以在服务启动的脚本中真正启动服务前放上一段让配置更新控制器更新配置的脚本。无论怎么样,这些都可以在运维层面实现,不需要业务开发人员知道。)

小结

好了,我们来总结一下今天分享的主要内容。首先,传统单机软件的配置通常保存在文件中,但在分布式系统下,为了管理方便,必须有一个配置中心。然后我讲了配置的区分:按静态和动态、运行环境、依赖和层次来区分。进一步,从区分出的情况出发,层次方面,平台、中间件和应用三个层次由不同职责的运维人员来配置。

外部依赖的配置并不适合放在配置中心里,而最好是由服务发现系统来提供。开发环境和生产环境的日志级别配置也会不同。出于这些特点,可以用一个配置管理工具来管理这些配置。接着,介绍了配置管理架构中几个关键问题的解决思路。最后,介绍了配置中心的几个设计重点。

  • 系统底层相关。 主要是以 Linux 系统为主,其中有大量的文章可以让你学习到 Linux 内核,以及内存、网络、异步 I/O 模型、Lock-free 的无锁编程,还有其它和系统底层相关的东西。注意,系统底层要是深下去是可以完全不见底的。而且内存方面的知识也是比较多的,所以,这里还是主要给出一些非常有价值的基础性的知识和技术。学好这些东西,你会对系统有很深的理解,而且可以把这些知识反哺到架构设计上来。
  • 数据库相关。数据库方面主要是 MySQL 和各种开源 NoSQL 的一些相关的有价值的文章和导读,主要是让你对这些数据库的内在有一定的了解,但又不会太深。真正的深入是需要扎入到源代码中的。需要说明的是,这块技术不是长项,但又是每个架构师需要知道的,所以,在这里给的学习资源可能会比较浅,这点还希望你来补充和指正。
  • 分布式架构。这一部分是最长最多的。其中有架构入门、分布式理论中各种非常有价值的经典论文,然后是一些分布式工程设计方面的文章,其中包括设计模式和工程应用,最后还有各大公司的架构供参考。
  • 微服务。有了分布式架构理论和工程的基础,接下来是对微服务的学习。在这部分内容中,罗列几个介绍微服务架构非常系统的文章,然后比较一下微服务和 SOA 的差别,最后则是一些工程实践和最佳实践。
  • 容器化和自动化运维。在容器化和自动化运维中,主要是学习 Docker 和 Kubernetes 这两个自动化运维的杀手型技术。而不是 Salt、Puppet、Chef 和 Ansible 这样比较传统的工具。原因很简单,因为自动化部署根本不够,还需要对环境和运行时的管理和运维才够,而只有 Docker 和 Kubernetes 才是未来。所以,这里重点让你学习这两个技术,其中有很多文章需要一些系统底层的知识。
  • 机器学习和人工智能。主要给了一些基础性的知识,其中包括基本原理、图书、课程、文章和相关的算法。顺着这路走,不能说能成为一个人工智能专家,但成为一个机器学习的高级工程师甚至准专家还是可能的。
  • 前端开发。这里的前端主要是 H5 的前端了,这一节会带你学习一下前端开发所需要知道的基础知识,尤其是对前端开发语言 JavaScript 的学习,花费了相当的篇幅列出了很多很经典的学习资料,必定会让你成为一个 JavaScript 高手。然后你还需要了解浏览器是怎样工作的,还有相关的网络协议和一些性能优化的技巧。最后则是 JavaScript 框架的学习,这里只给了 React.js 和 Vue.js,并通过 React.js 带出来函数式编程的学习。后端程序员给出来的这组前端开发的学习资料和路径会比前端程序员更靠谱一些。
  • 信息源。最后,则是一些信息源,其中包括各大公司的技术 Blog,还有相关的论文集散地。

另外,这里需要说明几点。

  • 假设你在前面已经打下了非常扎实的基础,但是要成为一个高手,基础知识只是一个地基,你还需要很多更为具体的技术。对很多人来说,就是看各种各样的文章、手册、论文、分享…… 其实,学习到一定程度,就是要从书本中走出去,到社区里和大家一起学习,而且还需要自己找食吃了。所以,对于这里面的文章,有很多都是在罗列各种文章和资源,只是为你梳理信息源,而不是喂你吃饭。
  • 老实说,已经为你梳理并过滤掉了很多的信息,这里只留下了 30% 觉得最经济也最有价值的信息。虽然对于不同定位和不同需求的人还可以再对这些信息进行删减,但是这么一做就会对其它人不公平了。所以,这也是最小数量集的信息和资源吧。你也可以把这里的东西当成一个索引来对待
  • 这些内容,不能说是隔离开来的,应该说是相辅相成的。也没什么顺序,可以各取所需。虽然看上去内容很多,但你也别害怕,真的不用害怕,你会越学越快,越实践越有感觉,也越有效率。在一开始可能会很慢,但是坚持住,积累一段时间后就会越来越快的。 而且,绝大多数人是坚持不下来的。只要你能坚持下来,你一定会成为各个大公司的抢手货。你不需要特别努力,只需要日进一步,3-5 年后,你就会发现,绝大多数人都在你身后很远的地方了。

今天分享的内容为系统底层知识中的 Linux 系统、内存和网络等方面的相关知识及推荐的学习资料。

Linux 系统相关

学习 Linux 操作系统的原理是通向系统工程师的必经之路。Unix/Linux 操作系统里的东西并不难学。你千万不要一下子扎到源代码里去,那样没用——你还是要在上层先通过读一些不错的文档来学习。下面罗列了一些很不错的站点,其中有很多内容供你去钻研和探索。

在这里默认你前面已经读过并读懂了推荐的那些和 Unix/Linux 相关的图书了。所以,相信你对 Unix/Linux 下的编程已经是有一些基础了,因此,你继续深挖 Linux 下的这些知识应该也不是很难的事了。

  • Red Hat Enterprise Linux 文档 。Red Hat Enterprise Linux(RHEL)是老牌 Linux 厂商 Red Hat 出品的面向商业的 Linux 发行版。Red Hat 网站上的这个文档中有很多很有价值的内容,值得一看。
  • Linux Insides ,GitHub 上的一个开源电子书,其中讲述了 Linux 内核是怎样启动、初始化以及进行管理的。
  • LWN’s kernel page ,上面有很多非常不错的文章来解释 Linux 内核的一些东西。
  • Learn Linux Kernel from Android Perspective ,从 Android 的角度来学习 Linux 内核,这个站点上的 Blog 相对于前面的比较简单易读一些。
  • Linux Kernel Doc, Linux 的内核文档也可以浏览一下。
  • Kernel Planet ,Linux 内核开发者的 Blog,有很多很不错的文章和想法。
  • Linux Performance and Tuning Guidelines ,这是 IBM 出的红皮书,虽然有点老了,但还是非常值得一读的。
  • TLK: The Linux Kernel ,这是一本相对比较老的书了,Linux 内核版本为 2.0.33,但了解一下前人的思路,也是很有帮助的。
  • Linux Performance ,这个网站上提供了和 Linux 系统性能相关的各种工具和文章收集,非常不错。
  • Optimizing web servers for high throughput and low latency ,这是一篇非常底层的系统调优的文章,来自 DropBox,从中你可以学到很多底层的性能调优的经验和知识。

内存相关

计算机内存管理是每一个底层程序员需要了解的非常重要的事儿。当然,这里重点还是 Linux 操作系统相关的内存管理上的知识。

首先,LWN.net 上有一系列的 “What every programmer should know about memory” 文章你需要读一下。当然,你可以直接访问一个完整的 PDF 文档。下面是这个系列文章的网页版列表。读完这个列表的内容,你基本上就对内存有了一个比较好的知识体系了。

然后是几篇和内存相关的论文。下面这三篇论文是个人觉得能对你非常有帮助的文章,尤其是你要做一些程序的性能优化方面。

  • Memory Barriers: a Hardware View for Software Hackers。内存的读写屏障是线程并发访问共享的内存数据时,从程序本身、编译器到 CPU 都必须遵循的一个规范。有了这个规范,才能保证访问共享的内存数据时,一个线程对该数据的更新能被另一个线程以正确的顺序感知到。在 SMP(对称多处理)这种类型的多处理器系统(包括多核系统)上,这种读写屏障还包含了复杂的缓存一致性策略。这篇文章做了详细解释。
  • A Tutorial Introduction to the ARM and POWER Relaxed Memory Models,对 ARM 和 POWER 的宽松内存模型的一个教程式的简介。本篇文章的焦点是 ARM 和 POWER 体系结构下多处理器系统内存并发访问一致性的设计思路和使用方法。与支持较强的 TSO 模型的 x86 体系结构不同,ARM 和 POWER 这两种体系结构出于对功耗和性能的考虑,使用了一种更为宽松的内存模型。本文详细讨论了 ARM 和 POWER 的模型。
  • x86-TSO: A Rigorous and Usable Programmer’s Model for x86 Multiprocessors,介绍 x86 的多处理器内存并发访问的一致性模型 TSO。

接下来是开发者最关心的内存管理方面的 lib 库。通常来说,有三种内存分配管理模块。就目前而言,BSD 的 jemalloc 有很大的影响力。后面可以看到不同公司的实践性文章。

关于 C 的这些内存分配器,你可以参看 Wikipedia 的 “C Dynamic Memory Allocation”这个词条。

下面是几篇不错的文章,让你感觉一下上面那三种内存分配器的一些比较和工程实践。

计算机网络

网络学习

首先,推荐一本书——《计算机网络(第五版)》,这本“计算机网络”和前面推荐的那本计算机网络不一样,前面那本偏扫盲,这本中有很多细节。这本书是国内外使用最广泛、最权威的计算机网络经典教材。全书按照网络协议模型自下而上(物理层、数据链路层、介质访问控制层、网络层、传输层和应用层)有系统地介绍了计算机网络的基本原理,并结合 Internet 给出了大量的协议实例。

这本书还与时俱进地引入了最新的网络技术,包括无线网络、3G 蜂窝网络、RFID 与传感器网络、内容分发与 P2P 网络、流媒体传输与 IP 语音,以及延迟容忍网络等。另外,本书针对当前网络应用中日益突出的安全问题,用了一整章的篇幅对计算机网络的安全性进行了深入讨论,而且把相关内容与最新网络技术结合起来阐述。这本书读起来并不枯燥,因为其占有很多小故事和小段子。

然后,有两个网上的教程和讲义也可以让人入门。

网络调优

接下来,你可能需要一些非常实用的可以操作的技术,下面的几篇文章相信可以帮助到你。

网络协议

接下来,想要学习网络协议最好的方式就是学习通讯相关的 RFC。所以,在这里会推荐一系列值得读的 RFC 给你。读 RFC 有几个好处,一方面可以学习技术,另一方面,你可以通过 RFC 学习到一个好的技术文档是怎么写的,还能看到各种解决问题的方案和思路。

对于第 2 层链路层,你可能需要了解一下 ARP:

以及 Tunnel 相关的协议:

对于第 4 层,你最需要了解的是 TCP/IP 了。和 TCP 相关的 RFC 相当多,这里给一系列经典的 RFC。这些 RFC 都引用在了在 CoolShell 上的《TCP 的那些事儿(上)》和《TCP 的那些事儿(下)》两篇文章中。如果你看不懂 RFC,你也可以去看上述的文章。

个人觉得 TCP 最牛的不是不丢包,而是拥塞控制。对此,如果你感兴趣,可以读一下经典论文《Congestion Avoidance and Control》。

关于 Linux 下的 TCP 参数,你需要仔仔细细地读一下TCP 的 man page

对于第 7 层协议,HTTP 协议是重点要学习的。

首先推荐的是《HTTP 权威指南 》,这本书有点厚,可以当参考书来看。这本书中没有提到 HTTP/2 的事,但是可以让你了解到 HTTP 协议的绝大多数特性。

HTTP 1.1 的原始 RFC 是 1999 年 6 月的 RFC 2616,但其在 2014 后很快被下面这些 RFC 给取代了。

关于HTTP/2,这是 HTTP 的一个比较新的协议,它于 2015 年被批准通过,现在基本上所有的主流浏览器都默认启用这个协议。所以,你有必要学习一下这个协议。下面是相关的学习资源。

最后,你可以上 Wikipedia 的 Internet Protocol Suite 上看看,这是一个很不错的网络协议的词条汇集地。顺着这些协议,你可以找到很多有用的东西。

EC2简介

1. EC2的特性

  • EC2是AWS提供的一种计算服务,它以EC2实例(EC2 Instance)的形式存在,因此一个EC2实例可以被认为是一个虚拟机
  • 预配置的EC2镜像被称之为Amazon Machine Images (AMI),一个AMI包含了你打包的好操作系统,以及相应的应用程序和配置
  • 不同的EC2实例类型包含了不同的CPU,内存,存储和网络性能
  • AWS默认以及建议使用密钥对(Key Pair)的形式访问EC2实例,AWS将保存公钥,您将负责保存私钥
  • EC2实例存储(Instance store volumes)是一种短暂性的存储,一旦您停止或者终止您的EC2实例,这个存储内的数据将永久消失
  • EBS存储(Amazon EBS volumes)是一种持续性的存储,不管EC2实例是什么状态,你都可以保留EBS存储内的数据。这种类型的存储对于进行数据盘的迁移非常方便,使用场景也比较多。
  • 安全组(Security Group)会通过检测数据包的端口、协议、源IP地址从而从当防火墙的作用
  • 弹性IP(Elastic IP address)可以方便您为您的EC2实例分配一个固定的公网IP地址,并且保证每次关机重启该地址依旧有效。
  • 虚拟私有云(Virtual Private Cloud, VPC)是AWS的网络组件,可以让你的AWS资源与其他用户的资源在逻辑上进行隔离。您也可以使用VPC与您的物理数据中心进行连接。

2. 如何访问EC2

AWS提供了非常多的方法方便您来访问EC2实例,下面将一一进行列举。并且会在后续课程通过视频的方式为大家进行演示。

  • AWS 命令行(CLI)- 您可以通过一些终端软件并利用命令行的方式访问您的Windows,Mac和Linux实例
  • AWS EC2管理平台 – 您可以通过基于web的用户界面来访问您的EC2实例(通过java等插件)
  • AWS CLI工具 – 您可以通过AWS CLI工具来访问AWS的多个组件
  • Windows Powershell
  • AWS API
  • AWS SDK

3. EC2实例的计费类型

EC2的实例计费类型有很多种,每一种都有自己的使用场景,不同的客户可能对计费类型的需求也不一样。

官网:

https://aws.amazon.com/cn/ec2/instance-types/

  • On-Demand Instances (按需实例)
  • Reserved Instances (预留实例)
  • Spot Instances (竞价实例)
  • Scheduled Reserved Instances (计划的预留实例)
  • Dedicated Instances (专用的实例)
  • Dedicated Hosts(专用的主机)

针对考试的朋友只着重看前三种实例的计费类型即可。

3.1 On-Demand Instances (按需实例)

这是最基本的实例计费类型,也应该是使用率最高的类型了。这种实例是用多少收费多少,精确到秒。在实例处于停止状态的时候,AWS不会收取你关于EC2的任何费用。只有在实例处于运行状态的时候,才会产生花销。

这种EC2的实例类型非常灵活,你可以在任何时间和地点开启并使用你的实例,然后在不需要的时候将其终止(Terminate)。你的消费只取决于你使用的时间,而没有其他的额外成本或者初始投资成本。

在传统的数据中心内,如果你要部署一个新的应用程序,那么你可能需要先去申请购买硬件服务器,然后安装操作系统,物理接上网络甚至存储,安装和调试应用程序以及代码。这一系列的过程会消耗你几个月甚至大半年的时间。

而是用AWS EC2实例,你只需要一个浏览器,以及几分钟的操作就可以创建你所需要的应用程序了!

如果你不需要这个应用程序,直接将其删除即可。这样的模式特别适合初创企业开发新的应用程序并进行试错。

这也是云计算带来的优势。

On-Demand Instances (按需实例)总结特点如下:

  • 按秒收费(以前是按小时收费,现在AWS更改了),用多少收费多少
  • EC2实例可以根据业务需求实时增加或减少规模
  • 不会有昂贵的初始投资成本
  • 适合用来部署有突发性,爆发性流量的应用程序,比如双11
  • 适合用来测试和开发新的应用程序

3.2 Reserved Instances (预留实例)

这种预留实例你需要和AWS签订1年或3年的合同,并且会以单价低于同等规格的按需实例的价格买到预留的实例,需要一次性付费。在合同有效的期限内,实例不管处于任何状态,费用都不会有变化。

如果你需要使用某种需要不间断运行(24x7x365)的应用程序,那么这种预留实例则会非常适合你,因为你会获得单位时间内最便宜的EC2实例。

1年的合同可以获得40%左右的折扣,3年的合同可以获得60%的折扣。

Reserved Instances (预留实例)总结特点如下:

  • 更低的每小时运行成本
  • 买断了一定的计算资源,不会出现AWS计算资源不足而无法创建EC2的情况
  • 费用在合同期内是固定的,因此费用可预期
  • 适合需要长期运行、稳定的、可预估的应用程序

3.3 Spot Instances (竞价实例)

AWS的计算资源会提供给全球成千上万的客户,因为客户使用计算资源的程度不同,也会影响AWS的总体计算资源消耗的程度。AWS向客户提供的这种竞价实例的每小时价格是浮动的,在计算资源紧张的时候价格较高,在计算资源较为空闲的时候价格较低。

因此客户可以设定规则,在实例价格低于某个阀值的情况下申请并使用该实例,而达到最大的性价比。

这种实例一般比较适合仅进行计算和分析的任务,并且可以随时中断。

(扩展阅读)可以查看客户评价和案例研究,目前已经非常多公司通过使用竞价实例有效地降低非常大的成本。

Spot Instances (竞价实例)总结特点如下:

  • 每个小时都会变化,依据是竞价实力的供需关系
  • 可以非常有效地降低运行EC2实例的成本(特别对于有大数量实例需求的情况下)
  • 在其上安装的应用程序随时可以中断,也就是数据和任务处理结果都需要保存在外部存储上
  • 对实例运行开始的时间没有太多要求

3.4 Scheduled Reserved Instances (计划的预留实例)

这个类型的实例和预留实例很相似,但不一样的地方是你可以定义该类型的实例在每天,每周,每个月的某个时间段运行,其他时间段处于关闭的状态。

这种实例比较适合于例如只在上班时间运行的应用程序,或者每周末进行的数据批处理等。

3.5 Dedicated Instances (专用的实例)

和按需实例差不多,但是实例会运行在专用的AWS硬件上。您的专用实例与属于其他 AWS 账户的实例在主机硬件级别是实体隔离的。

3.6 Dedicated Hosts(专用的主机)

相当于购买了一整台AWS的物理服务器,针对许可证会绑定CPU或者Socket的软件特别有用处。

4. EC2实例的运行平台

目前,EC2上可以运行基于Windows和Linux操作系统的实例,不支持任何非X86架构的实例。

另外,在2017 Re:Invent会议上AWS发布了一种新型的实例,可以让你操作AWS的裸机(不含任何操作系统),因此方便你在上面直接安装类似VMware Esx的虚拟操作系统。

5. EC2的实例类型

目前,AWS提供了70种以上的不同类型的EC2实例,解决客户的不同需求,并且这个类型的数量在逐年增加。

总体来说,EC2的实例类型可以分为通用型、计算优化型、内存优化型、存储优化型、加速计算型。

更高级别的实例会给你带来更多的CPU,内存,I/O,存储等等,当然价格也是越来越高。

举个例子,AWS提供的1年免费套餐中所包含的实例类型是t2.micro,这种实例类型提供了1个vCPU和1G的内存容量,而其他的参数例如网络,I/O则是所有在同一个物理主机中的实例一起共享的。

更多实例相关的内容,可以查看官网的Amazon EC2 实例类型介绍。

6. EC2实例信息综述

6.1 EC2 (Elastic Cloud Compute)

  • 了解不同EC2实例类型的区别,要知道在不同的使用场景需要使用哪一种类型的EC2
    • 按需实例(On Demand Instance)- 用多少时间付费多少,费用精确到秒,不用则可以随时关闭/终止并停止费用的产生
    • 竞价实例(Spot Instance)- 以低于按需实例的价格竞得实例,但价格高于设定得价格后会自动被终止
    • 保留实例(Reserved Instance) – 相当于买断一个实例1年/3年,期间不管实例开不开都需要付总得费用,但平均下来费用会比按需实例便宜
    • 专用主机实例(Dedicated Hosts)- 涉及到软件许可证的时候,会考虑使用专用主机实例
  • 终止一个竞价实例
    • 如果主动终止一个竞价实例,需要为当前这个完整小时付费
    • 如果因为价格上涨,AWS终止了你的竞价实例,那么这个小时的费用会被免费
  • 实例的终止保护(Termination Protection)功能是默认关闭的,有需要必须手动开启。开启后实例将无法被终止,除非先将终止保护关闭
  • 使用EBS为存储的实例,默认情况下如果该实例被终止,这个根EBS卷也会被随之删除
    • 但也可以设置为实例被终止的时候保留根EBS卷

EBS (Elastic Block Storage)

  • EBS的不同类型,需要了解不同类型的EBS主要的使用场景
    • 通用型SSD – GP2 (高达10,000 IOPS),适用于启动盘,低延迟的应用程序等
    • 预配置型SSD – IO1 (超过10,000 IOPS),适用于IO密集型的数据库
    • 吞吐量优化型HDD -ST1,适用于数据仓库,日志处理
    • HDD Cold – SC1 – 适合较少使用的冷数据
    • HDD, Magnetic
  • 不能将EBS挂载到多个EC2实例上,一个EBS只能挂载到1个EC2实例上。
    • 如果有共享数据盘的需求,请使用EFS (Elastic File System)
  • 根EBS卷默认是不能进行加密的,但可以使用第三方的加密工具(例如BitLocker)对其进行加密
    • 除了根磁盘外的其他卷是可以加密的

EBS快照(Snapshot)

  • EBS的快照会被保存到S3(Simple Storage System)上
  • 你可以对一个EBS卷创建一个快照,这个快照会被保存到S3上
  • 快照实际上是增量备份,只有在上次进行快照之后更改的数据才会被添加的S3上
    • 因此第一次快照所花费的时间比较长
    • 而第二次以后的快照所花费的时间相对短很多
  • 对加密的EBS卷创建快照,创建后的快照也会是加密的
  • 从加密的快照恢复的EBS卷也会是加密的
  • 你可以分享快照给其他账户或AWS市场,但仅限于这个快照是没有进行过加密的
  • 要为一个作为根设备的EBS卷创建快照的话,建议停止这个实例再做快照

实例存储(Instance Store)

  • 实例存储也叫做短暂性存储(Ephemeral Storage)
  • 实例存储的实例不能被停止(只能重启或终止),如果这个实例出现故障,那么在上面的所有数据将会丢失
  • 使用EBS的实例可以被停止,停止后EBS上的数据不会丢失
  • 重启使用实例存储的实例或者重启使用EBS的实例都不会导致数据丢失

亚马逊系统镜像(AMI)

  • AMI是区域化的,只能使用本区域的AMI来创建实例;但你可以将AMI从一个区域复制到另一个区域

角色(Role)

  • 使用角色(Role)比使用Access Key和Secret Access Key要安全很多
  • 角色更容易管理和变更
  • 角色可以在EC2实例启动之后再分配,并且可以随时更改角色以及角色关联的策略
    • 在旧版本考试中,角色只能在EC2创建的时候分配,并且实例启动之后不能对角色进行任何更改
  • 角色是跨区域的,创建的角色可以在任何区域中使用

实例Meta-data

EC2置放群组(Placement Group)

EC2 置放群组(Placement Group)逻辑性地把一些实例放置在一个组里面,在这个组里面的实例能享受低延迟、高网络吞吐的网络。

Placement Group的特点

  • EC2 Placement Group分为集群模式(Cluster)和分布模式(Spread)
    • 集群模式(Cluster)即传统的置放群组,所有的实例需要在同一个可用区内
    • 分布模式(Spread)是将实例分布到不同的底层硬件,可以在不同的可用区内。你最多可以在每一个置放群组的每一个可用区内创建7个实例
  • Placement Group提供了低延迟,高速率的网络,可提供高达10 Gbps的速度
  • EC2 Placement Group的命名需要在你的AWS账户内唯一,不能有命名重复
  • 只有特定的EC2实例类型可以放在配置Placement Group内(某些计算优化型、GPU、内存优化型和存储优化型的实例)
  • AWS建议在一个Placement Group内的所有EC2实例是一模一样的,否则会有短板效应
  • 不可以合并多个EC2 Placement Group
  • 不可以将一个正在运行的EC2实例放到一个EC2 Placement Group中;只能为这个EC2实例创建一个AMI,然后基于AMI创建一个新的实例并且加入到Placement Group内
  • Placement Group可以跨越peerd VPC,但要保证在同一个可用区内
  • 如果在Placement Group中创建实例的时候出现“capacity error”的错误,可以停止再启动组中的所有实例,再重新创建刚才的实例
    • 停止再启动组中的所有实例可以改变这些实例所在的底层物理设备,从而带来更多的性能和空间启动新的实例
  • Placement Group的创建会告诉AWS将组里的实例安置在物理上接近的AWS设备内

使用Placement Group的最佳实践

  • 组内使用一样类型的EC2实例
  • 在同一时间启动组内所有EC2实例,这样可以减少出现“capacity error”错误的概率
  • 可以通过更改最大传输单元(MTU),从默认的1500改成9001来进一步增加Placement Group内实例之间的传输速度

在多线程情况下访问一些共享资源需要加锁,不然就会出现数据被写乱的问题。在分布式系统下,这样的问题也是一样的。只不过,我们需要一个分布式的锁服务。对于分布式的锁服务,一般可以用数据库 DB、Redis 和 ZooKeeper 等实现。不管怎么样,分布式的锁服务需要有以下几个特点。

  • 安全性(Safety):在任意时刻,只有一个客户端可以获得锁(排他性)。
  • 避免死锁:客户端最终一定可以获得锁,即使锁住某个资源的客户端在释放锁之前崩溃或者网络不可达。
  • 容错性:只要锁服务集群中的大部分节点存活,Client 就可以进行加锁解锁操作。

Redis 的分布式锁服务

这里提一下,避免死锁的问题。下面以 Redis 的锁服务为例(参考 Redis 的官方文档 )。

通过以下命令对资源加锁。

1
SET resource_name my_random_value NX PX 30000

解释一下:

  • SET NX 命令只会在 key 不存在的时候给 key 赋值,PX 命令通知 Redis 保存这个 key 30000ms。
  • my_random_value 必须是全局唯一的值。这个随机数在释放锁时保证释放锁操作的安全性。
  • PX 操作后面的参数代表的是这个 key 的存活时间,称作锁过期时间。
  • 当资源被锁定超过这个时间时,锁将自动释放。
  • 获得锁的客户端如果没有在这个时间窗口内完成操作,就可能会有其他客户端获得锁,引起争用问题。

这里的原理是,只有在某个 key 不存在的情况下才能设置(set)成功该 key。于是,这就可以让多个进程并发去设置同一个 key,只有一个进程能设置成功。而其它的进程因为之前有人把 key 设置成功了,而导致失败(也就是获得锁失败)。

通过下面的脚本为申请成功的锁解锁:

1
2
3
4
5
if redis.call("get",KEYS[1]) == ARGV[1] then 
return redis.call("del",KEYS[1])
else
return 0
end

如果 key 对应的 value 一致,则删除这个 key。

通过这个方式释放锁是为了避免 Client 释放了其他 Client 申请的锁。

例如,下面的例子演示了不区分 Client 会出现的一种问题。

  1. Client A 获得了一个锁。
  2. 当尝试释放锁的请求发送给 Redis 时被阻塞,没有及时到达 Redis。
  3. 锁定时间超时,Redis 认为锁的租约到期,释放了这个锁。
  4. Client B 重新申请到了这个锁。
  5. Client A 的解锁请求到达,将 Client B 锁定的 key 解锁。
  6. Client C 也获得了锁。
  7. Client B 和 Client C 同时持有锁。

通过执行上面脚本的方式释放锁,Client 的解锁操作只会解锁自己曾经加锁的资源,所以是安全的。

关于 value 的生成,官方推荐从 /dev/urandom 中取 20 个 byte 作为随机数。或者采用更加简单的方式,例如使用 RC4 加密算法在 /dev/urandom 中得到一个种子(Seed),然后生成一个伪随机流。

也可以采用更简单的方法,使用时间戳 + 客户端编号的方式生成随机数。Redis 的官方文档说:“这种方式的安全性较差一些,但对于绝大多数的场景来说已经足够安全了”。

分布式锁服务的一个问题

注意,虽然 Redis 文档里说他们的分布式锁是没有问题的,但其实还是很有问题的。尤其是上面那个为了避免 Client 端把锁占住不释放,然后,Redis 在超时后把其释放掉。不知道你怎么样想,但我觉得这事儿听起来就有点不靠谱。

我们来脑补一下,不难发现下面这个案例。

  • 如果 Client A 先取得了锁。
  • 其它 Client(比如说 Client B)在等待 Client A 的工作完成。
  • 这个时候,如果 Client A 被挂在了某些事上,比如一个外部的阻塞调用,或是 CPU 被别的进程吃满,或是不巧碰上了 Full GC,导致 Client A 花了超过平时几倍的时间。
  • 然后,我们的锁服务因为怕死锁,就在一定时间后,把锁给释放掉了。
  • 此时,Client B 获得了锁并更新了资源。
  • 这个时候,Client A 服务缓过来了,然后也去更新了资源。于是乎,把 Client B 的更新给冲掉了。
  • 这就造成了数据出错。

这听起来挺严重的吧。示例一下。

img

千万不要以为这是脑补出来的案例。其实,这个是真实案例。HBase 就曾经遇到过这样的问题,你可以在他们的 PPT(HBase and HDFS: Understanding FileSystem Usage in HBase)中看到相关的描述。

要解决这个问题,你需要引入 fence(栅栏)技术。一般来说,这就是乐观锁机制,需要一个版本号排它。我们的流程就变成了下图中的这个样子。

img

我们从图中可以看到:

  • 锁服务需要有一个单调递增的版本号。
  • 写数据的时候,也需要带上自己的版本号。
  • 数据库服务需要保存数据的版本号,然后对请求做检查。

如果使用 ZooKeeper 做锁服务的话,那么可以使用 zxid 或 znode 的版本号来做这个 fence 版本号。

从乐观锁到 CAS

但是,我们想想,如果数据库中也保留着版本号,那么完全可以用数据库来做这个锁服务,不就更方便了吗?下面的图展示了这个过程。

img

使用数据版本(Version)记录机制,即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现的。当读取数据时,将 version 字段的值一同读出,数据每更新一次,对此 version 值加一。

当我们提交更新的时候,数据库表对应记录的当前版本信息与第一次取出来的 version 值进行比对。如果数据库表当前版本号与第一次取出来的 version 值相等,则予以更新,否则认为是过期数据。更新语句写成 SQL 大概是下面这个样子:

1
UPDATE table_name SET xxx = #{xxx}, version=version+1 where version =#{version};

这不就是乐观锁吗?是的,这是乐观锁最常用的一种实现方式。是的,如果我们使用版本号,或是 fence token 这种方式,就不需要使用分布式锁服务了。

另外,多说一下。这种 fence token 的玩法,在数据库那边一般会用 timestamp 时间截来玩。也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则 OK,否则就是版本冲突。

还有,我们有时候都不需要增加额外的版本字段或是 fence token。比如,如果想更新库存,我们可以这样操作:

1
2
SELECT stock FROM tb_product where product_id=#{product_id};
UPDATE tb_product SET stock=stock-#{num} WHERE product_id=#{product_id} AND stock=#{stock};

先把库存数量(stock)查出来,然后在更新的时候,检查一下是否是上次读出来的库存。如果不是,说明有别人更新过了,我的 UPDATE 操作就会失败,得重新再来。

细心的你一定发现了,这不就是计算机汇编指令中的原子操作 CAS(Compare And Swap)嘛,大量无锁的数据结构都需要用到这个。(关于 CAS 的话题,你可以看一下我在 CoolShell 上写的无锁队列的实现 )。

我们一步一步地从分布式锁服务到乐观锁,再到 CAS,你看到了什么?你是否得思考一个有趣的问题——我们还需要分布式锁服务吗?

分布式锁设计的重点

最后,我们来谈谈分布式锁设计的重点。

一般情况下,我们可以使用数据库、Redis 或 ZooKeeper 来做分布式锁服务,这几种方式都可以用于实现分布式锁。

分布式锁的特点是,保证在一个集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。这就是所谓的分布式互斥。所以,大家在做某个事的时候,要去一个服务上去请求一个标识。如果请求到了,我们就可以操作,操作完后,把这个标识还回去,这样别的进程就可以请求到了。

首先,需要明确一下分布式锁服务的初衷和几个概念性的问题。

  • 如果获得锁的进程挂掉了怎么办?锁还不回来了,会导致死锁。一般的处理方法是在锁服务那边加上一个过期时间,如果在这个时间内锁没有被还回来,那么锁服务要自动解锁,以避免全部锁住。
  • 如果锁服务自动解锁了,新的进程就拿到锁了,但之前的进程以为自己还有锁,那么就出现了两个进程拿到了同一个锁的问题,它们在更新数据的时候就会产生问题。对于这个问题,我想说:
  • 像 Redis 那样也可以使用 Check and Set 的方式来保证数据的一致性。这就有点像计算机原子指令 CAS(Compare And Swap)一样。就是说,我在改变一个值的时候先检查一下是不是我之前读出来的值,这样来保证其间没有人改过。
  • 如果通过像 CAS 这样的操作的话,我们还需要分布式锁服务吗?的确是不需要了,不是吗?
  • 但现实生活中也有不需要更新某个数据的场景,只是为了同步或是互斥一下不同机器上的线程,这时候像 Redis 这样的分布式锁服务就有意义了。

所以,需要分清楚:我是用来做修改某个共享源的,还是用来做不同进程间的同步或是互斥的。如果使用 CAS 这样的方式(无锁方式)来更新数据,那么我们是不需要使用分布式锁服务的,而后者可能是需要的。所以,这是我们在决定使用分布式锁服务前需要考虑的第一个问题——我们是否需要?

如果确定要分布式锁服务,你需要考虑下面几个设计。

  • 需要给一个锁被释放的方式,以避免请求者不把锁还回来,导致死锁的问题。Redis 使用超时时间,ZooKeeper 可以依靠自身的 sessionTimeout 来删除节点。
  • 分布式锁服务应该是高可用的,而且是需要持久化的。对此,你可以看一下 Redis 的文档 RedLock 看看它是怎么做到高可用的。
  • 要提供非阻塞方式的锁服务。
  • 还要考虑锁的可重入性。

我认为,Redis 也是不错的,ZooKeeper 在使用起来需要有一些变通的方式,好在 Apache 有 Curator 帮我们封装了各种分布式锁的玩法。

小结

首先,介绍了为什么需要分布式锁。就像单机系统上的多线程程序需要用操作系统锁或数据库锁来互斥对共享资源的访问一样,分布式程序也需要通过分布式锁来互斥对共享资源的访问。

分布式锁服务一般可以通过 Redis 和 ZooKeeper 等实现。接着,以 Redis 为例,介绍了怎样用它来加锁和解锁,由此引出了锁超时后的潜在风险。类似于数据库的乐观并发控制,这种风险可以通过版本号的方式来解决。

进一步,数据库如果本身利用 CAS 等手段支持这种版本控制方式,其实也就没必要用一个独立的分布式锁服务了。最后,我们发现,分布式锁服务还能用来做同步,这是数据库锁做不了的事情。

上个月,作为 Go 语言的三位创始人之一,Unix 老牌黑客罗勃·派克(Rob Pike)在新文章“Go: Ten years and climbing”中,回顾了一下 Go 语言的发展过程。其中提到,Go 语言这十年的迅猛发展大到连他们自己都没有想到,并且还成为了云计算领域中新一代的开发语言。还提到了,中国程序员对 Go 语言的热爱完全超出了他们的想象,甚至他们都不敢相信是真的。

Docker 负责人也感叹:他们完全没有想到居然中国有那么多人喜欢 Docker,而且还有这么多人在为 Docker 做贡献,这让他们感到非常意外,中国是除了美国本土之外的另外一个如此喜欢 Docker 技术的国家,在其它国家都没有看到。

的确如他们所说,Go 语言和 Docker 这两种技术已经成为新一代的云计算技术,而且可以看到其发展态势非常迅猛。而中国也成为了像美国一样在强力推动这两种技术的国家。这的确是一件让人感到非常高兴的事,因为中国在跟随时代潮流这件事上已经做得非常不错了。

然而,从 2014-2015 年我在阿里推动 Docker 和 Go 语言的痛苦和失败过程中,以及这许多年来,有很多很多人问我是否要学 Go 语言,是否要学 Docker,Go 和 Docker 是否能用在生产线上,这些问题看来,对于 Go 语言和 Docker 这两种技术,在国内的技术圈中有相当大的一部分人和群体还在执观望或是不信任的态度。

这篇文章从两个方面来论述一下我的观点和看法。

  • 一个方面,为什么 Go 语言和 Docker 会是新一代的云计算技术。
  • 另一个方面,作为技术人员,如何识别什么样的新技术会是未来的趋势。

Go 语言是在 2009 年底开源的。go语言有以下特点。

第一,语言简单,上手快。Go 语言的语法特性简直是太简单了,简单到你几乎玩不出什么花招,直来直去的,学习曲线很低,上手非常快。

第二,并行和异步编程几乎无痛点。Go 语言的 Goroutine 和 Channel 这两个神器简直就是并发和异步编程的巨大福音。像 C、C++、Java、Python 和 JavaScript 这些语言的并发和异步方式太控制就比较复杂了,而且容易出错,而 Go 解决这个问题非常地优雅和流畅。这对于编程多年受尽并发和异步折磨的我来说,完全就是让我眼前一亮的感觉。

img
(图片来自 Medium:Why should you learn Go?)

第三,Go 语言的 lib 库麻雀虽小五脏俱全。Go 语言的 lib 库中基本上有绝大多数常用的库,虽然有些库还不是很好,但我觉得不是问题,因为我相信在未来的发展中会把这些问题解决掉。

第四,C 语言的理念和 Python 的姿态。C 语言的理念是信任程序员,保持语言的小巧,不屏蔽底层且底层友好,关注语言的执行效率和性能。而 Python 的姿态是用尽量少的代码完成尽量多的事。于是我能够感觉到,Go 语言想要把 C 和 Python 统一起来,这是多棒的一件事啊。

img
(图片来自 Medium:Why should you learn Go?)

所以,即便 Go 语言存在诸多的问题,比如垃圾回收、异常处理、泛型编程等,但相较于上面这几个优势,我认为这些问题都是些小问题。于是就毫不犹豫地入坑了。

当然,一个技术能不能发展起来,关键还要看三点。

  • 有没有一个比较好的社区。像 C、C++、Java、Python 和 JavaScript 的生态圈都是非常丰富和火爆的。尤其是有很多商业机构参与的社区那就更为人气爆棚了,比如 Linux 的社区。
  • 有没有一个工业化的标准。像 C、C++、Java 都是有标准化组织的。尤其是 Java,其在架构上还搞出了像 J2EE 这样的企业级标准。
  • 有没有一个或多个杀手级应用。C、C++ 和 Java 的杀手级应用不用多说了,就算是对于 PHP 这样还不能算是一个好的编程语言来说,因为是 Linux 时代的第一个杀手级解决方案 LAMP 中的关键技术,所以,也发展起来了。

上述的这三点是非常关键的,新的技术只需要占到其中一到两点就已经很不错了,何况有的技术,比如 Java,是三点全占到了,所以,Java 的发展是如此好。当然,除了上面这三点重要的,还有一些其它的影响因素,比如:

  • 学习曲线是否低,上手是否快。这点非常重要,C++ 在这点上越做越不好了。
  • 有没有一个不错的提高开发效率的开发框架。如:Java 的 Spring 框架,C++ 的 STL 等。
  • 是否有一个或多个巨型的技术公司作为后盾。如:Java 和 Linux 后面的 IBM、Sun……
  • 有没有解决软件开发中的痛点。如:Java 解决了 C 和 C++ 的内存管理问题。

用这些标尺来量一下 Go 语言,可以清楚地看到:

  • Go 语言容易上手;
  • Go 语言解决了并发编程和写底层应用开发效率的痛点;
  • Go 语言有 Google 这个世界一流的技术公司在后面;
  • Go 语言的杀手级应用是 Docker,而 Docker 的生态圈在这几年完全爆棚了。

所以,Go 语言的未来是不可限量的。当然,我个人觉得,Go 可能会吞食很多 C、C++、Java 的项目。不过,Go 语言所吞食主要的项目应该是中间层的项目,既不是非常底层也不会是业务层。

也就是说,Go 语言不会吞食底层到 C 和 C++ 那个级别的,也不会吞食到高层如 Java 业务层的项目。Go 语言能吞食的一定是 PaaS 上的项目,比如一些消息缓存中间件、服务发现、服务代理、控制系统、Agent、日志收集等等,没有复杂的业务场景,也到不了特别底层(如操作系统)的中间平台层的软件项目或工具。而 C 和 C++ 会被打到更底层,Java 会被打到更上层的业务层。这是我的一个判断。

再用上面的标尺来量一下 Go 语言的杀手级应用 Docker,会发现基本是一样的。

  • Docker 上手很容易。
  • Docker 解决了运维中的环境问题以及服务调度的痛点。
  • Docker 的生态圈中有大公司在后面助力。比如 Google。
  • Docker 产出了工业界标准 OCI。
  • Docker 的社区和生态圈已经出现像 Java 和 Linux 那样的态势。
  • ……

所以,Docker 一定会是未来的技术。虽然坑儿还很多,但是,相对于这些大的因素来说,那些小坑儿都不是问题。只是需要一些时间,这些小坑儿在未来 5-10 年就可以完全被填平了。

同样,可以看到 Kubernetes 作为服务和容器调度的关键技术一定会是最后的赢家。

Docker 是云计算中 PaaS 的关键技术,虽然,这世上在出现 Docker 之前,几乎所有的要玩公有 PaaS 的公司和产品都玩不起来,比如:Google 的 GAE,国内的各种 XAE,如淘宝的 TAE,新浪的 SAE 等。PaaS 是一个被世界或是被产业界严重低估的平台

PaaS 层是承上启下的关键技术,任何一个不重视 PaaS 的公司,其技术架构都不可能让这家公司成长为一个大型的公司。因为 PaaS 层的技术主要能解决下面这些问题。

  • 软件生产线的问题。持续集成和持续发布,以及 DevOps 中的技术必需通过 PaaS。
  • 分布式服务化的问题。分布式服务化的服务高可用、服务编排、服务调度、服务发现、服务路由,以及分布式服务化的支撑技术完全是 PaaS 的菜。
  • 提高服务的可用性 SLA。提高服务可用性 SLA 所需要的分布式、高可用的技术架构和运维工具,也是 PaaS 层提供的。
  • 软件能力的复用。软件工程中的核心就是软件能力的复用,这一点也完美地体现在 PaaS 平台的技术上。

老实说,这些问题的关键程度已经到了能判断一家依托技术的公司的研发能力是否靠谱的程度。没有这些技术,依托技术拓展业务的公司几乎没有可能发展得规模很大。

最后,为什么要早一点地进入这些新技术,而不是等待这些技术成熟了后再进入。原因有这么几个。

  • 技术的发展过程非常重要

  • 这些关键新技术,可以让你拿到技术的先机。这些对一个需要技术领导力的个人或公司来说都是非常重要的。

一个公司或是个人能够占有技术先机,就会比其它公司或个人有更大的影响力。一旦未来行业需求引爆,那么这个公司或是个人的影响力就会形成一个比较大的护城河,并可以快速地产生经济利益。

通讯行业、金融行业对于 PaaS 平台的理解已经超过了互联网公司。

所以,Go 语和 Docker 作为 PaaS 平台的关键技术前途是无限的,

微服务是分布式系统中最近比较流行的架构模型,也是 SOA 架构的一个进化。微服务架构并不是银弹,所以,也不要寄希望于微服务构架能够解决所有的问题。微服务架构主要解决的是如何快速地开发和部署我们的服务,这对于一个能够适应快速开发和成长的公司是非常必要的。同时我也觉得,微服务中有很多很不错的想法和理念,所以学习微服务是每一个技术人员迈向卓越的架构师的必经之路。

首先,需要看一下,Martin Fowler 的这篇关于微服务架构的文档 - Microservice Architecture中译版),这篇文章说明了微服务的架构与传统架构的不同之处在于,微服务的每个服务与其数据库都是独立的,可以无依赖地进行部署。你也可以看看 Martin Fowler 老人家现身说法的视频

另外,你还可以简单地浏览一下,各家对微服务的理解。

微服务架构

接下来,可以看一下 IBM 红皮书:Microservices Best Practices for Java ,这本书非常好,不但有通过把 Spring Boot 和 Dropwizard 来架建 Java 的微服务,而且还谈到了一些标准的架构模型,如服务注册、服务发现、API 网关、服务通讯、数据处理、应用安全、测试、部署、运维等,是相当不错的一本书。

当然,有一本书也可以读一下—— 微服务设计。这本书全面介绍了微服务的建模、集成、测试、部署和监控,通过一个虚构的公司讲解了如何建立微服务架构。主要内容包括认识微服务在保证系统设计与组织目标统一上的重要性,学会把服务集成到已有系统中,采用递增手段拆分单块大型应用,通过持续集成部署微服务,等等。

与此相似的,也有其它的一系列文章,值得一读。

下面是 Nginx 上的一组微服务架构的系列文章。

下面这是 Auto0 Blog 上一系列的微服务的介绍,有代码演示。

还有 Dzone 的这个 Spring boot 的教程。

当然,如果你要玩得时髦一些的话,我推荐你使用下面的这套架构。

  • 前端React.jsVue.js
  • 后端Go 语言 + 微服务工具集 Go kit ,因为是微服务了,所以,每个服务的代码就简单了。既然简单了,也就可以用任何语言了,所以,我推荐 Go 语言。
  • 通讯gRPC,这是 Google 远程调用的一个框架,它比 Restful 的调用要快 20 倍到 50 倍的样子。
  • APISwagger ,Swagger 是一种 Restful API 的简单但强大的表示方式,标准的,语言无关,这种表示方式不但人可读,而且机器可读。可以作为 Restful API 的交互式文档,也可以作为 Restful API 形式化的接口描述,生成客户端和服务端的代码。今天,所有的 API 应该都通过 Swagger 来完成。
  • 网关Envoy 其包含了服务发现、负载均衡和熔断等这些特性,也是一个很有潜力的网关。当然,Kubernetes 也是很好的,而且它也是高扩展的,所以,完全可以把 Envoy 通过 Ingress 集成进 Kubernetes。这里有一个开源项目就是干这个事的 - contour
  • 日志监控fluentd + ELK
  • 指标监控Prometheus
  • 调用跟踪Jaeger 或是 Zipkin,当然,后者比较传统一些,前者比较时髦,最重要的是,其可以和 Prometheus 和 Envory 集成。
  • 自动化运维Docker + Kubernetes

微服务和 SOA

在对微服务有了一定的认识以后,一定有很多同学分不清楚微服务和 SOA 架构,对此,可以看一下这本电子书 - 《Microservices vs. Service-Oriented Architecture》。通过这本书,可以学到,服务化架构的一些事实,还有基础的 SOA 和微服务的架构知识,以及两种架构的不同。这本书的作者马克·理查兹(Mark Richards)同学拥有十年以上的 SOA 和微服务架构的设计和实现的经验。

另外,还有几篇其它对比 SOA 和微服务的文章你也可以看看。

除此之外,我们还需要知道微服务和其它架构的一些不同和比较,这样我们就可以了解微服务架构的优缺点。下面几篇文章将帮助获得这些知识。

设计模式和最佳实践

然后,可以看一下微服务的一些设计模式。

相关资源

小结

微服务中有很多很不错的想法和理念,所以学习微服务是每一个技术人员迈向卓越的架构师的必经之路。在这篇文章中,先给出了 AWS、Microsoft 和 Pivotal 对微服务的理解;然后给出了好几个系列的教程,帮你全面学习和理解微服务架构;然后通过一系列文章帮你来区分何为微服务,何为 SOA;最后给出了微服务架构的设计模式和最佳实践,以及相关资源。

基本原理简介

我们先来介绍一下机器学习的基本原理。

机器学习主要有两种方式,一种是监督式学习(Supervised Learning),另一种是非监督式学习(Unsupervised Learning)。下面简单地说一下这两者的不同。

  • 监督式学习(Supervised Learning)。所谓监督式学习,也就是说,我们需要提供一组学习样本,包括相关的特征数据和相应的标签。我们的程序可以通过这组样本来学习相关的规律或是模式,然后通过得到的规律或模式来判断没有被打过标签的数据是什么样的数据。

    举个例子,假设需要识别一些手写的数字,我们要找到尽可能多的手写体的数字的图像样本,然后人工或是通过某种算法来明确地标注上什么是这些手写体的图片,谁是 1,谁是 2,谁是 3…… 这组数据叫样本数据,又叫训练数据(training data)。然后通过机器学习的算法,找到每个数字在不同手写体下的特征,找到规律和模式。通过得到的规律或模式来识别那些没有被打过标签的手写数据,以此完成识别手写体数字的目的。

  • 非监督式学习(Unsupervised Learning)。对于非监督式学习,也就是说,数据是没有被标注过的,所以相关的机器学习算法需要找到这些数据中的共性。因为大量的数据是没被被标识过的,所以这种学习方式可以让大量的未标识的数据能够更有价值。而且,非监督式学习,可以为我们找到人类很难发现的数据里的规律或模型,所以也有人称这种学习为 “ 特征点学习 “,其可以让我们自动地为数据进行分类,并找到分类的模型。

    一般来说,非监督式学习会应用在一些交易型的数据中。比如,你有一堆堆的用户购买数据,但是对于人类来说,我们很难找到用户属性和购买商品类型之间的关系。所以,非监督式学习算法可以帮助我们找到它们之间的关系。比如,一个在某个年龄段的女性购买了某种肥皂,有可能说明这个女性在怀孕期,或是某人购买儿童用品,有可能说明这个人的关系链中有孩子,等等。于是,这些信息会被用作一些所谓的精准市场营销活动,从而可以增加商品销量。

监督式学习是在被告诉过了正确的答案后的学习,而非监督式学习是在没有被告诉正确答案时的学习。所以,非监督式学习是在大量的非常乱的数据中找寻一些潜在的关系,这个成本也比较高。非监督式学习经常被用来检测一些不正常的事情发生,比如信用卡的诈骗或是盗刷。也被用在推荐系统,比如买了这个商品的人又买了别的什么商品,或是如果某个人喜欢某篇文章、某个音乐、某个餐馆,那么他可能会喜欢某个车、某个明星或某个地方。

在监督式学习算法下,可以用一组 “ 狗 “ 的照片来确定某个照片中的物体是不是狗。而在非监督式学习算法下,我们可以通过一个照片来找到其中有与其相似的事物的照片。这两种学习方式都有些有用的场景。

关于机器学习,你可以读一读 Machine Learning is Fun! ,这篇文章(中文翻译版)恐怕是全世界最简单的入门资料了。

相关课程

在学习机器学习之前,我们需要学习数据分析,所以,我们得先学一些大数据相关的东西,也就是 Data Science 相关的内容。下面是两个不错的和数据科学相关的教程以及一个资源列表。

之后,有下面几门不错的在线机器学习的课程供你入门,也是非常不错。

  • 吴恩达教授(Andrew Ng)在 Coursera 上的免费机器学习课程 非常棒。我强烈建议从此入手。对于任何拥有计算机或科学学位的人,或是还能记住一点点数学知识的人来说,都应该非常容易入门。这个斯坦福大学的课程请尽量拿满分。可以在 网易公开课 中找到这一课程。除此之外,吴恩达教授还有一组新的和深度学习相关的课程,现在可以在网易公开课上免费学习——Deep Learning Specialization
  • Deep Learning by Google ,Google 的一个关于深度学习的在线免费课程,其支持中英文。这门课会教授你如何训练和优化基本神经网络、卷积神经网络和长短期记忆网络。你将通过项目和任务接触完整的机器学习系统 TensorFlow。
  • 卡内基梅隆大学汤姆·米切尔(Tom Mitchell)的机器学习 英文原版视频与课件 PDF
  • 2013 年加利福尼亚理工学院亚瑟·阿布 - 穆斯塔法(Yaser Abu-Mostafa)的 Learning from Data 课程视频及课件 PDF,内容更适合进阶。
  • 关于神经网络方面,YouTube 上有一个非常火的课程视频,由宾夕法尼亚大学的雨果·拉罗歇尔(Hugo Larochelle)的教学课程 - Neural networks class - Université de Sherbrooke

除此之外,还有很多的在线大学课程可以学习。比如:

更多的列表,请参看——Awesome Machine Learning Courses

相关图书

相关文章

除了上述的那些课程和图书外,下面这些文章也很不错。

下面是一些和神经网络相关的不错的文章。

相关算法

下面是 10 个非常经典的机器学习的算法。

如果你想了解更全的机器学习的算法列表,你可以看一下 Wikipedia 上的 List of Machine Learning Algorithms

A Tour of Machine Learning Algorithms ,这篇文章带你概览了一些机器学习算法,其中还有一个 “ 脑图 “ 可以下载,并还有一些 How-To 的文章供你参考。

对于这些算法,SciKit-Learn有一些文档供你学习。

相关资源

小结

总结一下今天的内容。首先介绍了机器学习的基本原理:监督式学习和非监督式学习,然后给出了全世界最简单的入门资料 Machine Learning is Fun!。随后给出了与机器学习密切相关的数据分析方面的内容和资料,然后推荐了深入学习机器学习知识的在线课程、图书和文章等,尤其列举了神经网络方面的学习资料。最后描述了机器学习的十大经典算法及相关的学习资料。