This commit is contained in:
louzefeng
2024-07-11 05:50:32 +00:00
parent bf99793fd0
commit d3828a7aee
6071 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
<audio id="audio" title="58 | 性能设计篇之“缓存”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/dd/b2/ddf9fc19286af0f1bc2b0f3ab19795b2.mp3"></audio>
前面分享了《分布式系统设计模式》系列文章的前两部分——弹力设计篇和管理设计篇。今天开始这一系列的最后一部分内容——性能设计篇,主题为《性能设计篇之“缓存”》。
基本上来说在分布式系统中最耗性能的地方就是最后端的数据库了。一般来说只要小心维护好数据库四种操作select、update、insert和delete中的三个写操作insert、update和delete不太会出现性能问题insert一般不会有性能问题update和delete一般会有主键所以也不会太慢。除非索引建得太多而数据库里的数据又太多这三个操作才会变慢。
绝大多数情况下select是出现性能问题最大的地方。一方面select会有很多像join、group、order、like等这样丰富的语义而这些语义是非常耗性能的另一方面大多数应用都是读多写少所以加剧了慢查询的问题。
分布式系统中远程调用也会消耗很多资源,因为网络开销会导致整体的响应时间下降。为了挽救这样的性能开销,在业务允许的情况(不需要太实时的数据)下,使用缓存是非常必要的事情。
从另一个方面说缓存在今天的移动互联网中是必不可少的一部分因为网络质量不一定永远是最好的所以前端也会为所有的API加上缓存。不然网络不通畅的时候没有数据前端都不知道怎么展示UI了。既然因为移动互联网的网络质量而导致我们必须容忍数据的不实时性那么从业务上来说在大多数情况下是可以使用缓存的。
缓存是提高性能最好的方式,一般来说,缓存有以下三种模式。
# Cache Aside 更新模式
这是最常用的设计模式了,其具体逻辑如下。
- **失效**应用程序先从Cache取数据如果没有得到则从数据库中取数据成功后放到缓存中。
- **命中**应用程序从Cache中取数据取到后返回。
- **更新**:先把数据存到数据库中,成功后,再让缓存失效。
<img src="https://static001.geekbang.org/resource/image/0a/d4/0a39fbce98c0d43e15b56b0ed09099d4.png" alt="" />
<img src="https://static001.geekbang.org/resource/image/e0/94/e0ecbc94d474f7bd0c8eb53dfd8bde94.png" alt="" />
这是标准的设计模式包括Facebook的论文《[Scaling Memcache at Facebook](https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf)》中也使用了这个策略。为什么不是写完数据库后更新缓存你可以看一下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?](https://www.quora.com/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的Cache词条。其中的Memory你可以理解为就是我们例子里的数据库。
<img src="https://static001.geekbang.org/resource/image/93/9a/933ed3ddb7d56735a0db5101f86d9a9a.png" alt="" />
# 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 src="https://static001.geekbang.org/resource/image/6f/fd/6f7a59b05ddcb343f75ae2069b2a7efd.png" alt="" />
# 缓存设计的重点
缓存更新的模式基本如前面所说不过这还没完缓存已经成为高并发高性能架构的一个关键组件了。现在很多公司都在用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的锁竞争、爬虫等多方面都需要考虑。下篇文章中我们讲述异步处理。希望对你有帮助。
也欢迎你分享一下你接触到的缓存方式有哪些?怎样权衡一致性和缓存的效率?
文末给出了《分布式系统设计模式》系列文章的目录,希望你能在这个列表里找到自己感兴趣的内容。
<li>弹力设计篇
<ul>
- [认识故障和弹力设计](https://time.geekbang.org/column/article/3912)
- [隔离设计Bulkheads](https://time.geekbang.org/column/article/3917)
- [异步通讯设计Asynchronous](https://time.geekbang.org/column/article/3926)
- [幂等性设计Idempotency](https://time.geekbang.org/column/article/4050)
- [服务的状态State](https://time.geekbang.org/column/article/4086)
- [补偿事务Compensating Transaction](https://time.geekbang.org/column/article/4087)
- [重试设计Retry](https://time.geekbang.org/column/article/4121)
- [熔断设计Circuit Breaker](https://time.geekbang.org/column/article/4241)
- [限流设计Throttle](https://time.geekbang.org/column/article/4245)
- [降级设计degradation](https://time.geekbang.org/column/article/4252)
- [弹力设计总结](https://time.geekbang.org/column/article/4253)
- [分布式锁Distributed Lock](https://time.geekbang.org/column/article/5175)
- [配置中心Configuration Management](https://time.geekbang.org/column/article/5819)
- [边车模式Sidecar](https://time.geekbang.org/column/article/5909)
- [服务网格Service Mesh](https://time.geekbang.org/column/article/5920)
- [网关模式Gateway](https://time.geekbang.org/column/article/6086)
- [部署升级策略](https://time.geekbang.org/column/article/6283)
- [缓存Cache](https://time.geekbang.org/column/article/6282)
- [异步处理Asynchronous](https://time.geekbang.org/column/article/7036)
- [数据库扩展](https://time.geekbang.org/column/article/7045)
- [秒杀Flash Sales](https://time.geekbang.org/column/article/7047)
- [边缘计算Edge Computing](https://time.geekbang.org/column/article/7086)

View File

@@ -0,0 +1,126 @@
<audio id="audio" title="59 | 性能设计篇之“异步处理”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/71/72/71be23b9db1bd127dc93c3e02436f272.mp3"></audio>
在弹力设计篇中我们讲过,异步通讯的设计模式有助于提高系统的稳定性和容错能力。其实,异步通讯在分布式系统中还可以增加整个系统的吞吐量,从而可以面对更高的并发,并可以从容地利用好现有的系统资源。为什么这么说呢?
我们试想一下,在你的工作中,有很多人会来找你,让你帮着做事。如果你是这种请求响应式的工作方式,那么本质上来说,你是在被动工作,也就是被别人驱动的工作方式。
当你在做一件事的时候如果有别人来找你做其它事你就会被打断而要去干别的事。而如果你可以统筹安排这些事情本来五件事只需要2个小时如果不能或者老被别人打乱思路那你可能就要花5个小时。异步处理任务可以让你更好地利用好时间和资源。利用好了时间和资源性能自然就会提升上来。
这就好像邮递业务一样,你寄东西的时候,邮递公司会把大量的去往同一个方向的订单合并处理,并统一地调配物流交通工具,从而在整体上更为节省资源和时间。
在分布式架构中,我们的系统被拆成了很多的子系统。如果想把这堆系统合理地用好,并更快地处理大量的任务,我们就需要统一地规划和统筹整体,这样可以达到整体的最优。本质上,这和邮递公司处理邮件一样,是相同的道理。
在计算机的世界里到处都是异步处理。比如当程序读写文件时我们的操作系统并不会真正同步地去操作硬盘而是把硬盘读写请求先在内存中hold上一小会儿几十毫秒然后对这些读写请求做merge和sort。
也就是说merge是把相同的操作合并相同的读操作只读一次相同的写操作只写最后一次而sort是把不同的操作排个序这样可以让硬盘向一个方向转一次就可以把所有的数据读出来而不是来来回回地转。这样可以极大地提高硬盘的吞吐率。
再如我们的TCP协议向网络发包的时候会把我们要发的数据先在缓冲区中进行囤积当囤积到一定尺寸时MTU才向网络发送这样可以最大化利用我们的网络带宽。而传输速度和性能也会变得很快。
**这就是异步系统所带来的好处——让我们的系统可以统一调度。**
另外,我举上面这两个例子是想告诉你,我们可能会觉得异步通讯慢,其实并不然,我们同样也可以把异步做得比较实时。
多说一句,就算是有延时,异步处理在用户体验上也可以给用户带来一个不错的用户体验,那就是用户可以有机会反悔之前的操作。
# 异步处理的设计
之前,我们在弹力设计中讲的是异步通讯,这里,我们想讲的是异步任务处理。当然,这里面没有什么冲突的,只不过是,异步通讯讲的是怎么把系统连接起来,而我们这里想讲的是怎么处理任务。
首先,我们需要一个前台系统,把用户发来的请求一一记录下来,有点像请求日志。这样,我们的操作在数据库或是存储上只会有追加的操作,性能会很高。我们收到请求后,给客户端返回“收到请求,正在处理中”。
然后我们有个任务处理系统来真正地处理收到的这些请求。为了解耦我们需要一个任务派发器这里就会出来两个事一个是推模型Push一个是拉模型Pull。
所谓Push推模型就是把任务派发给相应的人去处理有点像是一个工头的调度者的角色。而Pull拉模型则是由处理的人来拉取任务处理。这两种模型各有各的好坏。一般来说Push模型可以做调度但是它需要知道下游工作结点的情况。
除了要知道哪些是活着的还要知道它们的忙闲程度。这样一来当下游工作结点扩容缩容或是有故障需要维护等一些情况发生时Push结点都需要知道这会增加一定的系统复杂度。而Pull的好处则是可以让上游结点不用关心下游结点的状态只要自己忙得过来就会来拿任务处理这样可以减少一定的复杂度但是少了整体任务调度。
一般来说我们构建的都是推拉结合的系统Push端会做一定的任务调度比如它可以像物流那样把相同商品的订单都合并起来打成一个包交给下游系统让其一次处理掉也可以把同一个用户的订单中的不同商品给拆成多个订单。然后Pull端来订阅Push端发出来的异步消息处理相应的任务。
# 事件溯源
在这里我们需要提一下Event Sourcing事件溯源这个设计模式。
所谓Event Sourcing其主要想解决的问题是我们可以看到数据库中的一个数据的值状态但我们完全不知道这个值是怎么得出来的。就像银行的存折一样我们可以在银行的存折看到我们收支的所有记录也能看得到每一笔记录后的余额。
当然,如果我们有了所有的收支流水账的记录,我们完全不需要保存余额,因为我们只需要回放一下所有的收支事件,就可以得到最终的数据状态。这样一来,我们的系统就会变得非常简单,只需要追加不可修改的数据操作事件,而不是保存最终状态。除了可以提高性能和响应时间之外,还可以提供事务数据一致性,并保留了可以启用补偿操作的完整记录和历史记录。
还有一个好处就是如果我们的代码里有了bug在记录状态的系统里我们修改bug后还需要做数据修正。然而在Event Sourcing的系统里我们只需要把所有事件重新播放一遍就好了因为整个系统没有状态了。
事件不可变,并且可使用只追加操作进行存储。 用户界面、工作流或启动事件的进程可继续,处理事件的任务可在后台异步运行。 此外,处理事务期间不存在争用,这两点可极大提高应用程序的性能和可伸缩性。
事件是描述已发生操作的简单对象以及描述事件代表的操作所需的相关数据。 事件不会直接更新数据存储,只会对事件进行记录,以便在合适的时间进行处理。 这可简化实施和管理。
事件溯源不需要直接更新数据存储中的对象,因而有助于防止并发更新造成冲突。
最重要的是,异步处理 + 事件溯源的方式,可以很好地让我们的整个系统进行任务的统筹安排、批量处理,可以让整体处理过程达到性能和资源的最大化利用。
关于Event Sourcing一般会和CQRS一起提。另外你可以去GitHub上看看[这个项目的示例](https://github.com/cer/event-sourcing-examples)以得到更多的信息。
# 异步处理的分布式事务
在前面的《分布式系统的本质》一文中我们说过对于分布式事务在强一致性下在业务层上只能做两阶段提交而在数据层面上需要使用Raft/Paxos的算法。但是我想说在现实生活中需要用到强一致性的场景实在不多不是所有的场景都必须要强一致性的事务的。
我们仔细想想现实生活当中的很多例子。比如,我们去餐馆吃饭,先付钱,然后拿个小票去领餐。这种情况下,把交钱和取货这两个动作分开,可以让我们的餐馆有更高的并发和接客能力。如果要做成两阶段提交,顾客锁定好钱,餐馆锁定好食材,最后一手交钱一手交餐,那么这是一件非常恐怖的事。
是的,你可以看到,我们的现实世界中有很多这样先付钱,拿小票去领货的场景,也有先消费,然后拿一个账单去付钱的场景。总之,完全不需要两阶段提交这种方式。我们完全可以使用异步的方式来达到一致性,当然,是最终一致性。
要达到最终一致性我们需要有个交易凭证。也就是说如果一个事务需要做A和B两件事比如把我的钱转给我的朋友首先先做扣钱交易然后记录下扣钱的凭证拿这个凭证去给我朋友的账号上加钱。
在达成这个事务的过程中,有几点需要注意。
- 凭证需要非常好地保存起来,不然会导致事务做不下去。
- 凭证处理的幂等性问题,不然在重试时就会出现多次交易的情况。
- 如果事务完成不了,需要做补偿事务处理。
# 异步处理的设计要点
异步处理中的事件驱动和事件溯源是两个比较关键的技术。
异步处理可能会因为一些故障导致我们的一些任务没有被处理,比如消息丢失,没有通知到,或通知到了,没有处理。有这一系列的问题,异步通知的方式需要任务处理方处理完成后,给任务发起方回传状态,这样确保不会有漏掉的。
另外,发起方也需要有个定时任务,把一些超时没有回传状态的任务再重新做一遍,你可以认为这是异步系统中的&quot;对账&quot;功能。当然,如果要重做的话,就需要处理方支持幂等性处理。
异步处理的整体业务事务问题,也就是说,异步处理在处理任务的时候,并不知道能否处理成功,于是就会一步一步地处理,如果到最后一步不能成功,那么你就需要回滚。这个时候,需要走我们在弹力设计中说的补偿事务的流程。
并不是所有的业务都可以用异步的方式,比如一些需要强一致性的业务,使用异步的方式可能就不适合,这里需要我们小心地分析业务。我相信绝大多数的业务场景都用不到强一致性,包括银行业务。另外,在需要性能的时候,需要牺牲强一致性,变为最终一致性。
在运维时,我们要监控任务队列里的任务积压情况。如果有任务积压了,要能做到快速地扩容。如果不能扩容,而且任务积压太多,可能会导致整个系统挂掉,那么就要开始对前端流量进行限流。
最后,还想强调一下,异步处理系统的本质是把被动的任务处理变成主动的任务处理,其本质是在对任务进行调度和统筹管理。
# 小结
好了我们来总结一下今天分享的主要内容。首先我介绍了异步通讯它在弹力设计中的作用是提高系统的稳定性和容错能力而其实我们还可以在异步通讯的基础上统筹任务来提高系统的吞吐量。接着我讲了异步通讯的设计包括推拉结合的模型。异步处理配合事件溯源一起使用将大大简化bug修复后的数据恢复也能用于实现存储的事务一致性。
我将餐馆吃饭作为比喻,介绍了异步处理的事务一致性一般不是强一致性,而是最终一致性,这样才能取得高的吞吐量。最后,我指出了异步处理的设计要点。下篇文章中,我们讲述数据库扩展。希望对你有帮助。
也欢迎你分享一下你的异步处理过程是怎样统筹安排来提高执行效率的?异步事务又是怎样实现的?
文末给出了《分布式系统设计模式》系列文章的目录,希望你能在这个列表里找到自己感兴趣的内容。
<li>弹力设计篇
<ul>
- [认识故障和弹力设计](https://time.geekbang.org/column/article/3912)
- [隔离设计Bulkheads](https://time.geekbang.org/column/article/3917)
- [异步通讯设计Asynchronous](https://time.geekbang.org/column/article/3926)
- [幂等性设计Idempotency](https://time.geekbang.org/column/article/4050)
- [服务的状态State](https://time.geekbang.org/column/article/4086)
- [补偿事务Compensating Transaction](https://time.geekbang.org/column/article/4087)
- [重试设计Retry](https://time.geekbang.org/column/article/4121)
- [熔断设计Circuit Breaker](https://time.geekbang.org/column/article/4241)
- [限流设计Throttle](https://time.geekbang.org/column/article/4245)
- [降级设计degradation](https://time.geekbang.org/column/article/4252)
- [弹力设计总结](https://time.geekbang.org/column/article/4253)
- [分布式锁Distributed Lock](https://time.geekbang.org/column/article/5175)
- [配置中心Configuration Management](https://time.geekbang.org/column/article/5819)
- [边车模式Sidecar](https://time.geekbang.org/column/article/5909)
- [服务网格Service Mesh](https://time.geekbang.org/column/article/5920)
- [网关模式Gateway](https://time.geekbang.org/column/article/6086)
- [部署升级策略](https://time.geekbang.org/column/article/6283)
- [缓存Cache](https://time.geekbang.org/column/article/6282)
- [异步处理Asynchronous](https://time.geekbang.org/column/article/7036)
- [数据库扩展](https://time.geekbang.org/column/article/7045)
- [秒杀Flash Sales](https://time.geekbang.org/column/article/7047)
- [边缘计算Edge Computing](https://time.geekbang.org/column/article/7086)

View File

@@ -0,0 +1,163 @@
<audio id="audio" title="60 | 性能设计篇之“数据库扩展”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/20/03/2006120dafccff310169fc406c6f1e03.mp3"></audio>
# 读写分离 CQRS
读写分离是数据库扩展最简单实用的玩法了,这种方法针对读多写少的业务场景还是很管用的,而且还可以有效地把业务做相应的隔离。
如下图所示数据库只有一个写库有两个读库所有的服务都写一个数据库。对于读操作来说服务A和服务B走从库A服务D和服务E走从库B服务C在从库A和从库B间做轮询。
<img src="https://static001.geekbang.org/resource/image/77/71/77892f8385a4fa21e97ca41075780b71.png" alt="" />
这样的方法好处是:
- 比较容易实现。数据库的master-slave的配置和服务框架里的读写分离都比较成熟应用起来也很快。
- 可以很好地把各个业务隔离开来。不会因为一个业务把数据库拖死而导致所有的业务都死掉。
- 可以很好地分担数据库的读负载毕竟读操作是最耗数据库CPU的操作。
这样的方法不好的地方是:
- 写库有单点故障问题。如果是写库出了性能问题,那么所有的业务一样不可用。对于交易型的业务,要得到高的写操作速度,这样的方式不行。
- 数据库同步不实时,需要强一致性的读写操作还是需要落在写库上。
综上所述,一般来说,这样的玩法主要是为了减少读操作的压力。
当然这样的读写分离看上去有点差强人意那么我们还是为之找一个更靠谱的设计——CQRS。关于CQRS我在这里只做一个简单的介绍更多的细节你可以上网自行Google。
CQRS全称Command and Query Responsibility Segregation也就是命令与查询职责分离。其原理是用户对于一个应用的操作可以分成两种一种是Command也就是我们的写操作另一种是Query操作也就是读操作。Query操作基本上是在做数据整合显现而Command操作这边会有更重的业务逻辑。分离开这两种操作可以在语义上做好区分。
- 命令Command不会返回结果数据只会返回执行状态但会改变数据。
- 查询Query会返回结果数据但是不会改变数据对系统没有副作用。
这样一来,可以带来一些好处。
<li>
分工明确,可以负责不同的部分。
</li>
<li>
将业务上的命令和查询的职责分离能够提高系统的性能、可扩展性和安全性。并且在系统的演化中能够保持高度的灵活性能够防止出现CRUD模式中对查询或者修改中的某一方进行改动导致另一方出现问题的情况。
</li>
<li>
逻辑清晰,能够看到系统中的哪些行为或者操作导致了系统的状态变化。
</li>
<li>
可以从数据驱动Data-Driven转到任务驱动Task-Driven以及事件驱动。
</li>
如果把Command操作变成Event Sourcing那么只需要记录不可修改的事件并通过回溯事件得到数据的状态。于是我们可以把写操作给完全简化掉也变成无状态的这样可以大幅度降低整个系统的副作用并可以得到更大的并发和性能。
文本中有Event Sourcing和CQRS的架构示意图。
<img src="https://static001.geekbang.org/resource/image/ce/87/ceeb536d0fa15afa4f5fde0b2cbe7787.png" alt="" />
图片来源 - [CQRS and Event Sourcing Application with Cassandra](https://www.slideshare.net/planetcassandra/codecentric-ag-cqrs-and-event-sourcing-applications-with-cassandra)
# 分库分表Sharding
一般来说,影响数据库最大的性能问题有两个,一个是对数据库的操作,一个是数据库中数据的大小。
对于前者我们需要从业务上来优化。一方面简化业务不要在数据库上做太多的关联查询而对于一些更为复杂的用于做报表或是搜索的数据库操作应该把其移到更适合的地方。比如用ElasticSearch来做查询用Hadoop或别的数据分析软件来做报表分析。
对于后者,如果数据库里的数据越来越多,那么也会影响我们的数据操作。而且,对于我们的分布式系统来说,后端服务都可以做成分布式的,而数据库最好也是可以拆开成分布式的。读写分离也因为数据库里的数据太多而变慢,于是,分库分表就成了我们必须用的手段。
<img src="https://static001.geekbang.org/resource/image/8a/64/8ab721d3d6512a7f607fe393f6550f64.png" alt="" />
上面的图片是一个分库的示例。其中有两个事,这里需要提一下,一个是关于分库的策略,一个是关于数据访问层的中间件。
**关于分库的策略**。我们把数据库按某种规则分成了三个库。比如,或是按地理位置,或是按日期,或是按某个范围分,或是按一种哈希散列算法。总之,我们把数据分到了三个库中。
**关于数据访问层**。为了不让我们前面的服务感知到数据库的变化,我们需要引入一个叫&quot;数据访问层&quot;的中间件用来做数据路由。但是老实说这个数据访问层的中间件很不好写其中要有解析SQL语句的能力还要根据解析好的SQL语句来做路由。但即便是这样也有很多麻烦事。
比如我要做一个分页功能需要读一组顺序的数据或是需要做Max/Min/Count这样的操作。于是你要到三个库中分别求值然后在数据访问层这里再合计处理返回。但即使是这样你也会遇到各种令人烦恼的事比如一个跨库的事务你需要走XA这样的两阶段提交的操作这样会把数据库的性能降到最低的。
为了避免数据访问层的麻烦,分片策略一般如下。
<li>
按多租户的方式。用租户ID来分这样可以把租户隔离开来。比如一个电商平台的商家中心可以按商家的ID来分。
</li>
<li>
按数据的种类来分。比如,一个电商平台的商品库可以按类目来分,或是商家按地域来分。
</li>
<li>
通过范围来分。这样分片,可以保证在同一分片中的数据是连续的,于是我们数据库操作,比如分页查询会更高效一些。一般来说,大多数情况是用时间来分片的,比如,一个电商平台的订单中心是按月份来分表的,这样可以快速检索和统计一段连续的数据。
</li>
<li>
通过哈希散列算法来分比如主键id % 3之类的算法。此策略的目的是降低形成热点的可能性接收不成比例的负载的分片。但是这会带来两个问题一个就是前面所说的跨库跨表的查询和事务问题另一个就是如果要扩容需要重新哈希部分或全部数据。
</li>
上面是最常见的分片模式,但是你还应考虑应用程序的业务要求及其数据使用模式。这里请注意几个非常关键的事宜。
<li>
数据库分片必须考虑业务,从业务的角度入手,而不是从技术的角度入手,如果你不清楚业务,那么无法做出好的分片策略。
</li>
<li>
请只考虑业务分片。请不要走哈希散列的分片方式,除非有个人拿着刀把你逼到墙角,你马上就有生命危险,你才能走哈希散列的分片方式。
</li>
# 数据库扩展的设计重点
先说明一下这里没有讲真正数据库引擎的水平扩展的方法我们只是在业务层上谈了一下数据扩展的两种方法。关于数据库引擎的水平扩展你可能看一下我之前发过的《分布式数据调度的相关论文》一文中的AWS Aurora和Google Spanner的相关论文中提到的那些方法。
接下来,我们说一下从业务层上把单体的数据库给拆解掉的相关重点。
首先你需要把数据库和应用服务一同拆开。也就是说一个服务一个库这就是微服务的玩法也是Amazon的服务化的玩法——服务之间只能通过服务接口通讯不能通过访问对方的数据库。在Amazon内每个服务都会有一个自己的数据库比如地址库、银行卡库等。这样一来你的数据库就会被&quot;天生地&quot;给拆成服务化的,而不是一个单体的库。
我们要知道,在一个单体的库上做读写分离或是做分片都是一件治标不治本的事,真正治本的方法就是要和服务一起拆解。
当数据库也服务化后,我们才会在这个小的服务数据库上进行读写分离或分片的方式来获得更多的性能和吞吐量。这是整个设计模式的原则——先做服务化拆分,再做分片。
对于分片来说,有两种分片模式,一种是水平分片,一种是垂直分片。水平分片就是我们之前说的那种分片。而垂直分片是把一张表中的一些字段放到一张表中,另一些字段放到另一张表中。垂直分片主要是把一些经常修改的数据和不经常修改的数据给分离开来,这样在修改某个字段的数据时,不会导致其它字段的数据被锁而影响性能。比如,对于电商系统来说,商品的描述信息不常改,但是商品的库存和价格经常改,所以,可以把描述信息和库存价格分成两张表,这样可以让商品的描述信息的查询更快。
我们所说的sharding更多的是说水平分片。水平分片需要有以下一些注意事项。
<li>
随着数据库中数据的变化,我们有可能需要定期重新平衡分片,以保证均匀分布并降低形成热点的可能性。 但是,重新平衡是一项昂贵的操作。 若要减少重新平衡的频率,我们需要通过确保每个分片包含足够的可用空间来处理未来一段时间的变化。 另外,我们还需要开发用于快速重新平衡分片的工具和脚本。
</li>
<li>
分片是静态的,而数据的访问则是不可预期的,可能需要经常性地调整我们的分片,这样一来成本太高。所以,我们最好使用一个索引表的方式来进行分片。也就是说,把我们数据的索引动态地记录在一个索引表中。这样一来,我们就可以非常灵活地调度我们的数据了。当数据调度到另一台结点上时,我们只需要去索引表里改一下这个数据的位置就好了。
</li>
<li>
如果程序必须要从多个分片检索数据的查询,则可以使用并行任务从各个分片上提取此数据,然后聚合到单个结果中。 但是,此方法不可避免地会在一定程度上增加解决方案数据访问逻辑的复杂性。
</li>
<li>
数据分片后,我们很难在分片之间保持引用完整性和一致性,也就是所谓的跨分片的事务,因此应尽量减少会影响多个分片中的数据的操作。如果应用程序必须跨分片修改数据,那么我们需要评估一致性以及评估是否采用两阶段提交的方式。
</li>
<li>
配置和管理大量分片可能是一个挑战。在做相应的变更时,一定要先从生产线上拉出数据,然后根据数据计划好新的分片方式,并做好相当的测试工作。否则,这个事出了问题会是一个灾难性的问题。
</li>
# 小结
好了我们来总结一下今天分享的主要内容。首先我介绍了单主库多从库的读写分离并进一步用CQRS把语义区分成命令和查询。命令的执行可以变成事件溯源方式从而得到更大的并发和性能。随后我讲了分库分表的策略及其数据访问层所做的抽象。最后我指出了数据库扩展的设计重点。下篇文章中我们将会聊聊秒杀这个特定的场景希望对你有帮助。
也欢迎你在留言区分享一下你的数据库做过哪些形式的扩展?设计中有哪些方面的考量?
文末给出了《分布式系统设计模式》系列文章的目录,希望你能在这个列表里找到自己感兴趣的内容。
<li>弹力设计篇
<ul>
- [认识故障和弹力设计](https://time.geekbang.org/column/article/3912)
- [隔离设计Bulkheads](https://time.geekbang.org/column/article/3917)
- [异步通讯设计Asynchronous](https://time.geekbang.org/column/article/3926)
- [幂等性设计Idempotency](https://time.geekbang.org/column/article/4050)
- [服务的状态State](https://time.geekbang.org/column/article/4086)
- [补偿事务Compensating Transaction](https://time.geekbang.org/column/article/4087)
- [重试设计Retry](https://time.geekbang.org/column/article/4121)
- [熔断设计Circuit Breaker](https://time.geekbang.org/column/article/4241)
- [限流设计Throttle](https://time.geekbang.org/column/article/4245)
- [降级设计degradation](https://time.geekbang.org/column/article/4252)
- [弹力设计总结](https://time.geekbang.org/column/article/4253)
- [分布式锁Distributed Lock](https://time.geekbang.org/column/article/5175)
- [配置中心Configuration Management](https://time.geekbang.org/column/article/5819)
- [边车模式Sidecar](https://time.geekbang.org/column/article/5909)
- [服务网格Service Mesh](https://time.geekbang.org/column/article/5920)
- [网关模式Gateway](https://time.geekbang.org/column/article/6086)
- [部署升级策略](https://time.geekbang.org/column/article/6283)
- [缓存Cache](https://time.geekbang.org/column/article/6282)
- [异步处理Asynchronous](https://time.geekbang.org/column/article/7036)
- [数据库扩展](https://time.geekbang.org/column/article/7045)
- [秒杀Flash Sales](https://time.geekbang.org/column/article/7047)
- [边缘计算Edge Computing](https://time.geekbang.org/column/article/7086)

View File

@@ -0,0 +1,108 @@
<audio id="audio" title="61 | 性能设计篇之“秒杀”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/8c/16/8c14383d12de00b76a2848912367ed16.mp3"></audio>
一说起秒杀,大家都觉得这事很有技术含量。实际上,并不是这个样子的,秒杀这种互联网的交易方式其实并没有我们想象中的那么复杂。下面先让我们来系统地看一下,秒杀是怎么做的。
# 秒杀的流程
“秒杀”其实是商家为了促销使用非常低的价格销售商品比如1元卖iPhone100台于是来了一百万人抢购。
我们把技术挑战放在一边,先从用户或是产品的角度来看一下,秒杀的流程是什么样的。
- 首先你需要一个秒杀的landing page在这个秒杀页上有一个倒计时的按钮。
- 一旦这个倒计时的时间到了,按钮就被点亮,让你可以点击按钮下单。
- 一般来说下单时需要你填写一个校验码,以防止是机器来抢。
从技术上来说,这个倒计时按钮上的时间和按钮可以被点击的时间是需要后台服务器来校准的,这意味着:
- 前端页面要不断地向后端来请求,开没开始,开没开始……
- 每次询问的时候,后端都会给前端一个时间,以校准前端的时间。
- 一旦后端服务器表示OK可以开始后端服务会返回一个URL。
- 这个URL会被安置在那个按钮上就可以点击了。
- 点击后,如果抢到了库存,就进入支付页面,如果没有则返回秒杀已结束。
这个不断轮询的过程就好像大家等着抢。你想想有100万人来不停地询问有没有开始了这个事估计后端也扛不住。
# 秒杀的技术挑战
接下来,我们需要来看一下“秒杀”的技术挑战。
面对上面我们要解决的技术问题我们的技术上的挑战就是怎么应对这100万人同时下单请求100万的同时并发会导致我们的网站瞬间就崩溃了一方面是100万人同时请求我们的网络带宽不够另一方面是理论上来说要扛100万的TPS需要非常多的机器。
但是最恐怖的是,所有的请求都会集中在同一条数据库记录上,无论是怎么分库分表,还是使用了分布式数据库都无济于事,因为你面对的是单条的热点数据。
这几乎是一件无法解决的技术问题。
# 秒杀的解决方案
很明显要让100万用户能够在同一时间打开一个页面这个时候我们就需要用到CDN了。数据中心肯定是扛不住的所以我们要引入CDN。
在CDN上这100万个用户就会被几十个甚至上百个CDN的边缘结点给分担了于是就能够扛得住。然后我们还需要在这些CDN结点上做点小文章。
一方面我们需要把小服务部署到CDN结点上去这样当前端页面来问开没开始时这个小服务除了告诉前端开没开始外它还可以统计下有多少人在线。每个小服务会把当前在线等待秒杀的人数每隔一段时间就回传给我们的数据中心于是我们就知道全网总共在线的人数有多少。
假设我们知道有大约100万的人在线等着抢那么在我们快要开始的时候由数据中心向各个部署在CDN结点上的小服务上传递一个概率值比如说是0.02%。
于是当秒杀开始的时候这100万用户都在点下单按钮首先他们请求到的是CDN上的这些服务这些小服务按照0.02%的量把用户放到后面的数据中心也就是1万个人放过去两个剩下的9998个都直接返回秒杀已结束。
于是100万用户被放过了0.02%的用户也就是200个左右而这200个人在数据中心抢那100个iPhone也就是200 TPS这个并发量怎么都应该能扛住了。
这就是整个“秒杀”的技术细节,是不是有点不敢相信?
说到这里我相信你一定会问我12306和奥运会抢票的问题。我觉得2008年奥运会抢票把服务器抢挂了是可以使用秒杀这个解决方案的。而12306则不行因为他们完全不知道用户来是要买哪张火车票的。不知道这个信息很不好过滤用户而且用户在买票前需要有很多查询操作然后在查询中选择自己的车票。
对此12306最好的应对方式除了不要一次把所有的票放出来而是分批在不同的时间段把票放出来这样可以让人们不要集中在一个时间点来抢票做到人肉分流可以降低一些并发度。
另外我一直觉得12306最好是用预售的方式让大家把自己的购票先输入到系统中。系统并不真正放票而是把大家的需求都收集好然后做整体统筹安排该增加车次的增加车次该加车厢的加车厢这样可以确保大家都能走。实在不行那就抽签了。
# 更多的思考
我们可以看到解决秒杀这种特定业务场景可以使用CDN的边缘结点来扛流量然后过滤用户请求限流用户请求来保护数据中心的系统这样才让整个秒杀得以顺利进行。
那么如果我们像双11那样想尽可能多地卖出商品那么就不像秒杀了。这是要尽可能多地收订单但又不能超过库存其中还有大量的银行支付各大仓库的库存查询和分配这些都是非常慢的操作。为了保证一致性还要能够扛得住像双11这样的大规模并发访问那么应该怎么做呢
使用秒杀这样的解决方案基本上不太科学了。这个时候就需要认认真真地做高并发的架构和测试了,需要各个系统把自己的性能调整上去,还要小心地做性能规划,更要把分布式的弹力设计做好,最后是要不停地做性能测试,找到整个架构的系统瓶颈,然后不断地做水平扩展,以解决大规模的并发。
但是,从另一方面来说,像我们用边缘结点来解决秒杀这样的场景的玩法,是否也有一定的普适性?这里,我想说,一定是有的。
有些时候,我们总是在想数据中心的解决方案。其实,我们有时候也需要换一换思路,也许,在数据中心解决并不一定是最好的方式,放在边缘来解决可能会更好一些。尤其是针对一些有地域特征的业务,比如像外卖、共享单车、打车这样的业务。其实,把一些简单的业务逻辑放在边缘,比放在数据中心不但能够有更好的性能,还有更便宜的成本。
我觉得,随着请求量越来越大,数据也越来越多,数据中心是有点到瓶颈了,而需要边缘结点来帮忙了。而且,这个边缘化解决方案的趋势也会越来越有优势。
在这里,我先按住不表,因为这是我的创业方向,我会在下一篇文章,也是本系列的最后一篇文章,向你介绍边缘计算以及我想用边缘计算干些什么事。
# 小结
好了我们来总结一下今天分享的主要内容。首先我介绍了秒杀。先是分析了其业务流程并列举了其所面临的技术挑战随后介绍了其解决方案。接着分析了相关的奥运会和12306抢票问题以及双十一购物节问题。
它们各自有不同的解决思路其中双十一则要求我们必须认认真真地用高并发架构来应对。最后从秒杀解决方案中的CDN边缘节点计算我引出了普适的边缘节点计算。下篇文章中我们详细讲述边缘计算。希望对你有帮助。
也欢迎你分享一下你参与过秒杀系统的构建吗?双十一呢?解决方案是怎样的呢?
文末给出了《分布式系统设计模式》系列文章的目录,希望你能在这个列表里找到自己感兴趣的内容。
<li>弹力设计篇
<ul>
- [认识故障和弹力设计](https://time.geekbang.org/column/article/3912)
- [隔离设计Bulkheads](https://time.geekbang.org/column/article/3917)
- [异步通讯设计Asynchronous](https://time.geekbang.org/column/article/3926)
- [幂等性设计Idempotency](https://time.geekbang.org/column/article/4050)
- [服务的状态State](https://time.geekbang.org/column/article/4086)
- [补偿事务Compensating Transaction](https://time.geekbang.org/column/article/4087)
- [重试设计Retry](https://time.geekbang.org/column/article/4121)
- [熔断设计Circuit Breaker](https://time.geekbang.org/column/article/4241)
- [限流设计Throttle](https://time.geekbang.org/column/article/4245)
- [降级设计degradation](https://time.geekbang.org/column/article/4252)
- [弹力设计总结](https://time.geekbang.org/column/article/4253)
- [分布式锁Distributed Lock](https://time.geekbang.org/column/article/5175)
- [配置中心Configuration Management](https://time.geekbang.org/column/article/5819)
- [边车模式Sidecar](https://time.geekbang.org/column/article/5909)
- [服务网格Service Mesh](https://time.geekbang.org/column/article/5920)
- [网关模式Gateway](https://time.geekbang.org/column/article/6086)
- [部署升级策略](https://time.geekbang.org/column/article/6283)
- [缓存Cache](https://time.geekbang.org/column/article/6282)
- [异步处理Asynchronous](https://time.geekbang.org/column/article/7036)
- [数据库扩展](https://time.geekbang.org/column/article/7045)
- [秒杀Flash Sales](https://time.geekbang.org/column/article/7047)
- [边缘计算Edge Computing](https://time.geekbang.org/column/article/7086)

View File

@@ -0,0 +1,177 @@
<audio id="audio" title="62 | 性能设计篇之“边缘计算”" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f3/7e/f333ab6587e327ef79ed1bb3ff9eb97e.mp3"></audio>
前面我们通过一个秒杀的示例展示了如何在CDN结点上简单地部署小服务然后就可以完成在数据中心很难完成的事我想你应该能看到边缘结点的一些威力。今天我会和你聊聊我所认识的边缘计算这也是我创业的方向。
首先一说起边缘计算网上大多数文章都会说这是和IoT相关的一个技术。其实我觉得这个说法只说对了边缘计算的一部分边缘计算可以做的事情还有很多很多。
所谓边缘计算它是相对于数据中心而言。数据中心喜欢把所有的服务放在一个机房里集中处理用户的数据和请求集中式部署一方面便于管理和运维另一方面也便于服务间的通讯有一个比较好的网络保障。的确没错。不过我们依然需要像CDN这样的边缘式的内容发布网络把我们的静态内容推到离用户最近的地方然后获得更好的性能。
如果我们让CDN的这些边缘结点拥有可定制的计算能力那么就可以像秒杀那样可以在边缘结点上处理很多事情从而为我们的数据中心带来更好的性能更好的扩展性还有更好的稳定性。而我们的用户也会觉得响应飞快从而有了更好的用户体验。
下面,让我们来看看为什么边缘计算会变成一个必然的产物。这里,我有两个例子。
# 为什么要有边缘计算
## 从趋势上来说
首先,我们得看一下整个时代是怎么发展的。我们处在信息化革命时代,也有人叫数字化革命,总之就是电脑时代。这个时代,把各式各样的信息都给数字化掉,然后交给计算机来处理。所以,我们要清楚地知道,**整个计算机发展的本质就是我们人类生活信息化建设的过程**。
这个过程中计算机硬件的发展也是非常迅猛的。CPU的处理速度硬盘的大小和速度网络的带宽和速度都在拼命地升级和降价。我们用越来越低的成本获得越来越快的速度、越来越大的带宽、越来越快的存储……
所有的这一切其实都是和信息还有数据有关。我们的信息和数据越来越多越来越大所以我们需要更好、更快、更便宜的硬件和基础设施。这个演化过程中在我参加工作这20年来就没有停止过而且我也不认为未来会停下来这个过程只会越来越快。
下面是我画的一个时代的变更图(不用太纠结其中的时间点,我只是想表示信息演进的过程)
<img src="https://static001.geekbang.org/resource/image/32/db/3257b6428ef87f7a663bd3677bb49ddb.png" alt="" />
从一开始我们处在MB时代那个时候电脑也是几百兆的硬盘就够了。因为那个时候的信息量不够大只有内容服务提供商在提供内容他们主要以新闻资讯为主所以数据还不多。
然后开始进入UGC时代用户开始产生数据他们写博客发贴子拍照片拍视频……于是信息越来越多于是我们的数据进入了GB时代于是计算机的硬件网络的基础设施都在升级。
再然后我们进入了大数据时代这个时代也是移动互联网的时代。以前你要打开电脑才能上网现在你只要手机有电你就是在线的而且这个时代大量的线下服务走到线上比如外卖、叫车……于是有各种各样的App在收集你的行为和数据。这个时候是计算机在记录每个人的上网行为的时候所以数据量也不是一般的大。
然后,这个趋势只会越来越大,下一个时间,我们的数据和信息只会越来越大,因为计算机正在吞噬可以被数字化的一切事情。除了继续吞噬线上的业务,一定会开始吞噬线下的信息和数据。比如,通过摄像头识别线下的各种活动,如车牌;通过一些传感器来收集线下的各种数据,如农业、水利……于是,数据只会变得越来越大。
这个时候,我们想一想,如果把这么大量的数据都拿到数据中心来做分析和计算,一个数据中心顶得住吗?我现在已经接到好几个用户和我说,数据量太大了,不知道怎么架构数据中心了,各种慢,各种贵,各种痛苦……
而且,还有另外一个需求就是要实时,对于大数据处理的实时需求越来越成为刚需了,因为,如果不能实时处理、实时响应,那么怎么能跟得上这个快速的时代呢。这就好像一个人脸识别的功能。如果苹果手机的人脸识别需要到服务器上算,然后把结果返回,那么用户的体验就很糟糕了。这就是为什么苹果在手机里直接植入了神经网络的芯片。
**我们可以看到,数量越来越大,分析结果的速度需要越来越快,这两个需求,只会把我们逼到边缘计算上去。** 如果你还是在数据中心处理,你会发现你的成本只会越来越高,到一定时候就完全玩不下去了。
## 从成本上来说
上面这个是第一个示例,我们再来看看数据中心的成本,当一个公司需要支持几十万用户的时候,并没有什么感觉。当他们要支撑上千万乃至上亿用户的时候,我们就会发现,一个几十万用户的系统架构和一个支撑上千万用户的架构,在成本上来说,完全不是一个数量级的。就像文本中的图片所描述的那样(只是一个草图,用于说明问题)。
<img src="https://static001.geekbang.org/resource/image/f5/e7/f5337ffab2d65ae5fc114ba21da82de7.png" alt="" />
在这个图中,我们可以看到,当需要处理的数据或是用户请求的规模越来越大时,我们的成本是呈现快速上升的曲线,而不是一个线性上升的成本关系。
我们可以来算一下根据我过去服务过的40多家公司的经验可以看到如下的投入
<li>
几十万用户的公司只需要处理百级QPS的量只需要10台左右的服务器
</li>
<li>
上百万用户的公司只需要处理千级QPS的量需要有50台左右的服务器
</li>
<li>
上千万用户的公司需要处理万级到十万级QPS的量需要700台左右的服务器
</li>
<li>
上亿用户的公司其需要处理百万级QPS的量需要上万台的服务器。
</li>
可以看到十万用户到上亿用户也就多了100倍为什么服务器需要1000倍完全不是呈线性的关系。
这时因为,当架构变复杂了后,你就要做很多非功能的东西了,比如,缓存、队列、服务发现、网关、自动化运维、监控等。
那么我们不妨开个脑洞。如果我们能够把那上亿的用户拆成100个百万级的用户那么只需要5000多台机器100个50台服务器的数据中心
我们还是同样服务了这么多的用户但我们的成本下降得很快。只不过我们需要运维100个小数据中心。不过相信我运维100个50台服务器的小数据中心的难度应该远远低于运维一个10000台服务器的数据中心。
好了,问题来了,什么样的业务可以这么做?我觉得有地域性的业务是可以这么做的,比如:外卖、叫车、共享单车之类的。
然而100个50台服务器的小数据中心也会带来一些复杂的问题因为当你的公司有100万用户的时候的业务形态和有1亿用户的业务形态是完全不一样的1亿用户的业务形态可能会复杂得多得多。也就是说我们不可能在一个小数据中心只有50台服务器因为那是百万用户的业务形态只有几十个服务。当公司成长到上亿用户的规模时可能会有上百个服务50台服务器是不够部署的。
所以,我上面那种多个数据中心的理想只存在于理论上,而实际上不会发生。
但是,我们依然可以沿着这条路思考下去。我们不难发现,我们完全可以用边缘结点处理高峰流量,这样,我们的数据中心就不需要花那么大的成本来建设了。
于是,还是到了边缘计算。
# 边缘计算的业务场景
通过上面的两个案例分析,我觉得边缘计算一定会成为一个必然产物,其会作为以数据中心为主的云计算的一个非常好的补充。这个补充在我看来,其主要是做下面一些事情。
<li>
处理一些实时响应的业务。它和用户靠得很近,所以可以实时响应用户的一些本地请求,比如,某公司的人脸门禁系统、共享单车的开锁。
</li>
<li>
处理一些简单的业务逻辑。比如像秒杀、抢红包这样的业务场景。
</li>
<li>
收集并结构化数据。比如,把视频中的车牌信息抠出来,转成文字,传回数据中心。
</li>
<li>
实时设备监控。主要是线下设备的数据采集和监控。
</li>
<li>
P2P的一些去中心化的应用。比如边缘结点作为一个服务发现的服务器可以让本地设备之间进行P2P通讯。
</li>
<li>
云资源调度。边缘结点非常适合用来做云端服务的调度。比如允许用户使用不同生产商的云存储服务使用不同生产商但是功能相同的API服务比如支付API相关。因为是流量接入方所以可以调度流量。
</li>
<li>
云资源聚合。比如我们可以把语音转文字的API和语义识别的API相结合聚合出来一个识别语音语义的API从而简化开发人员的开发成本。
</li>
<li>
……
</li>
其实还有很多,我觉得边缘计算带来的想象力还是很令人激动的。
关于现实当中的一些案例,你可以看看 [Netflix的全球边缘架构的PPT](https://www.slideshare.net/MikeyCohen1/edge-architecture-ieee-international-conference-on-cloud-engineering-32240146)。
# 边缘计算的关键技术
在我看来,边缘计算的关键技术如下。
<li>
API Gateway。关于网关这个就不说了我们在管理设计篇中有一篇就是专门讨论这个东西的。
</li>
<li>
Serverless/FaaS。就是服务函数化这个技术就像是AWS Lambda服务一样你写好一个函数然后不用关心这个函数运行在哪里直接发布就好了。然后就可以用了。
</li>
Serverless这个词第一次被使用大约是2012年由Ken Form所写的一篇名为《Why The Future of Software and Apps is Serverless》的文章。这篇文章谈到的内容是关于持续集成及源代码控制等并不是我们今天所特指的这一种架构模式。
但Amazon在2014年发布的AWS Lambda让&quot;Serverless&quot;这一范式提高到一个全新的层面为云中运行的应用程序提供了一种全新的系统体系结构。至此再也不需要在服务器上持续运行进程以等待HTTP请求或API调用而是可以通过某种事件机制触发代码的执行。
通常这只需要在AWS的某台服务器上配置一个简单的功能。此后Ant Stanley 在2015年7月的名为《Server are Dead…》的文章中更是围绕着AWS Lambda及刚刚发布的AWS API Gateway这两个服务解释了他心目中的Serverless“Server are dead…they just dont know it yet”。
如果说微服务是以专注于单一责任与功能的小型功能块为基础利用模块化的方式组合出复杂的大型应用程序那么我们还可以进一步认为Serverless架构可以提供一种更加&quot;代码碎片化&quot;的软件架构范式我们称之为Function as a ServicesFaaS。所谓的“函数”Function提供的是相比微服务更加细小的程序单元。
目前比较流行的几个开源项目是:
- [Serverless Framework](https://github.com/serverless/serverless)
- [Fission: Serverless Functions for Kubernetes](https://github.com/fission/fission)
- [Open Lambda](https://github.com/open-lambda/open-lambda)
- [Open FaaS](https://github.com/openfaas/faas)
- [IronFunction](https://github.com/iron-io/functions)
# 小结
好了,我们来总结一下今天分享的主要内容。首先,我描绘了边缘计算的初始模样。接着,我讲了从计算的发展趋势上来看,数据量的不断增大迫使边缘计算成为一个必然。大数据中心的成本问题,也需要通过边缘计算来降低。然后,我列举了边缘计算的业务场景。最后,我介绍了实现边缘计算所需的关键技术。希望对你有帮助。
也欢迎你分享一下你对边缘计算的看法如何?有没有什么好的想法?
文末给出了《分布式系统设计模式》系列文章的目录,希望你能在这个列表里找到自己感兴趣的内容。
<li>弹力设计篇
<ul>
- [认识故障和弹力设计](https://time.geekbang.org/column/article/3912)
- [隔离设计Bulkheads](https://time.geekbang.org/column/article/3917)
- [异步通讯设计Asynchronous](https://time.geekbang.org/column/article/3926)
- [幂等性设计Idempotency](https://time.geekbang.org/column/article/4050)
- [服务的状态State](https://time.geekbang.org/column/article/4086)
- [补偿事务Compensating Transaction](https://time.geekbang.org/column/article/4087)
- [重试设计Retry](https://time.geekbang.org/column/article/4121)
- [熔断设计Circuit Breaker](https://time.geekbang.org/column/article/4241)
- [限流设计Throttle](https://time.geekbang.org/column/article/4245)
- [降级设计degradation](https://time.geekbang.org/column/article/4252)
- [弹力设计总结](https://time.geekbang.org/column/article/4253)
- [分布式锁Distributed Lock](https://time.geekbang.org/column/article/5175)
- [配置中心Configuration Management](https://time.geekbang.org/column/article/5819)
- [边车模式Sidecar](https://time.geekbang.org/column/article/5909)
- [服务网格Service Mesh](https://time.geekbang.org/column/article/5920)
- [网关模式Gateway](https://time.geekbang.org/column/article/6086)
- [部署升级策略](https://time.geekbang.org/column/article/6283)
- [缓存Cache](https://time.geekbang.org/column/article/6282)
- [异步处理Asynchronous](https://time.geekbang.org/column/article/7036)
- [数据库扩展](https://time.geekbang.org/column/article/7045)
- [秒杀Flash Sales](https://time.geekbang.org/column/article/7047)
- [边缘计算Edge Computing](https://time.geekbang.org/column/article/7086)