<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Design | 酷 壳 - CoolShell</title>
	<atom:link href="https://coolshell.cn/tag/design/feed" rel="self" type="application/rss+xml" />
	<link>https://coolshell.cn</link>
	<description>享受编程和技术所带来的快乐 - Coding Your Ambition</description>
	<lastBuildDate>Sat, 12 Feb 2022 05:23:39 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2</generator>
	<item>
		<title>我做系统架构的一些原则</title>
		<link>https://coolshell.cn/articles/21672.html</link>
					<comments>https://coolshell.cn/articles/21672.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Tue, 21 Dec 2021 07:46:41 +0000</pubDate>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[Architecture]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[程序员]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=21672</guid>

					<description><![CDATA[<p>工作 20 多年了，这 20 来年看到了很多公司系统架构，也看到了很多问题，在跟这些公司进行交流和讨论的时候，包括进行实施和方案比较的时候，都有很多各种方案的比...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/21672.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/21672.html">我做系统架构的一些原则</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-full wp-image-21682" src="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x.png" alt="" width="250" height="250" srcset="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x.png 250w, https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x-150x150.png 150w" sizes="(max-width: 250px) 100vw, 250px" />工作 20 多年了，这 20 来年看到了很多公司系统架构，也看到了很多问题，在跟这些公司进行交流和讨论的时候，包括进行实施和方案比较的时候，都有很多各种方案的比较和妥协，因为相关的经历越来越多，所以，逐渐形成了自己的逻辑和方法论。今天，想写下这篇文章，把我的这些个人的经验和想法总结下来，希望能够让更多的人可以参考和借鉴，并能够做出更好的架构来。另外，我的这些思维方式和原则都针对于现有市面上众多不合理的架构和方案，所以，也算是一种“纠正”……（注意，这篇文章所说的这些架构上的原则，一般适用于相对比较复杂的业务，如果只是一些简单和访问量不大的应用，那么你可能会得出相反的结论）</p>
<h4>原则一：关注于真正的收益而不是技术本身</h4>
<p>对于软件架构来说，我觉得第一重要的是架构的收益，如果不说收益，只是为了技术而技术，而没有任何意义。对于技术收益来说，我觉得下面这几个收益是非常重要的：</p>
<ul>
<li><strong>是否可以降低技术门槛加快整个团队的开发流程</strong>。能够加快整个团队的工程流程，快速发布，是软件工程一直在解决的问题，所以，系统架构需要能够进行并行开发，并行上线和并行运维，而不会让某个团队成为瓶颈点。（注：就算拖累团队的原因是组织构架，也不妨碍我们做出并行的系统架构设计）</li>
<li><strong>是否可以让整个系统可以运行的更稳定</strong>。要让整个系统可以运行的更为的稳定，提升整个系统的 SLA，就需要对有计划和无计划的停机做相应的解决方案（参看《<a title="关于高可用的系统" href="https://coolshell.cn/articles/17459.html" target="_blank" rel="noopener">关于高可用的架构</a>》）</li>
<li><strong>是否可以通过简化和自动化降低成本</strong>。最高优化的成本是人力成本，人的成本除了慢和贵，还有经常不断的 human error。如果不能降低人力成本，反而需要更多的人，那么这个架构设计一定是失败的。除此之外，是时间成本，资金成本。</li>
</ul>
<p>如果一个系统架构不能在上面三个事上起到作用，那就没有意义了。</p>
<p><span id="more-21672"></span></p>
<h4>原则二：以应用服务和 API 为视角，而不是以资源和技术为视角</h4>
<p>国内很多公司都会有很多分工，基本上都会分成运维和开发，运维又会分成基础运维和应用运维，开发则会分成基础核心开发和业务开发。不同的分工会导致完全不同的视角和出发点。比如，基础运维和开发的同学更多的只是关注资源的利用率和性能，而应用运维和业务开发则更多关注的是应用和服务上的东西。这两者本来相关无事，但是因为分布式架构的演进，导致有一些系统已经说不清楚是基础层的还是应用层的了，比如像服务治理上的东西，里面即有底层基础技术，也需要业务的同学来配合，包括 k8s 也样，里面即有底层的如网络这样的技术，也有需要业务配合的 readniess和 liveness 这样的健康检查，以及业务应用需要 configMap 等等 ……</p>
<p><strong>这些东西都让我感觉到所谓 DevOps，其实就是因为很多技术和组件已经分不清是 Dev 还是 Ops 的了，所以，需要合并 Dev和 Ops</strong>。而且，整个组织和架构的优化，已经不能通过调优单一分工或是单一组件能够有很大提升的了。其需要有一种自顶向下的，整体规划，统一设计的方式，才能做到整体的提升（可以试想一下城市交通的优化，当城市规模到一定程度的时候，整体的性能你是无法通过优化几条路或是几条街区来完成的，你需要对整个城市做整体的功能体的规划才可能达到整体效率的提升）。而为了做到整体的提升，需要所有的人都要有一个统一的视角和目标，这几年来，我觉得这个目标就是——<strong>要站在服务和 对外API的视角来看问题，而不是技术和底层的角度。</strong></p>
<h4>原则三：选择最主流和成熟的技术</h4>
<p>技术选型是一件很重要的事，技术一旦选错，那会导致整个架构需要做调整，而对架构的调整重来都不是一件简单的事，我在过去几年内，当系统越来越复杂的时候，用户把他们的  PHP，Python, .NET，或 Node.js 的架构完全都迁移到 Java + Go 的架构上来的案例不断的发生。这个过程还是非常痛苦的，但是你没有办法，当你的系统越来越复杂，越来越大时，你就再也不能在一些玩具技术上玩了，你需要的更为工业化的技术。</p>
<ul>
<li><strong>尽可能的使用更为成熟更为工业化的技术栈，而不是自己熟悉的技术栈</strong>。 所谓工业化的技术栈，你可以看看大多数公司使用的技术栈，比如：互联网，金融，电信……等等 ，大公司会有更多的技术投入，也需要更大规模的生产，所以，他们使用的技术通常来说都是比较工业化的。在技术选型上，千万不要被——“你看某个视频公司也在用这个技术”，或是一些在论坛上看到的一些程序员吐槽技术的观点（没有任何的数据，只有自己的喜好）来决定自己的技术，还是看看主流大多数公司实际在用的技术栈，会更靠谱一些。</li>
<li><strong>选择全球流行的技术，而不是中国流行的技术</strong>。技术这个东西一定是一个全球化的东西，不是一个局域化的事。所以，一定要选国际化的会更好。另外，千万不要被某些公司的“特别案例”骗过去了，那怕这个案例很性感，关键还是要看解决问题的思路和采用的技术是否具有普世性。只有普世性的技术有更强的生命力。</li>
<li><strong>尽可能的使用红利大的主流技术，而不要自己发明轮子，更不要魔改</strong>。我见过好些个公司魔改开源软件，比如有个公司同魔改mesos，最后改着改着发现自己发明另一个 kubernetes。我还见过很多公司或技术团队喜欢自己发明自己的专用轮子，最后都会被主流开源软件所取代。完全没有必要。不重新发明轮子，不魔改，不是因为自己技术不能，而是因为，这个世界早已不是自己干所有事的年代了，这个时代是要想尽方法跟整个产业，整个技术社区融合和合作，这样才会有最大的收益。那些试图因为某个特例需要自成一套的玩法，短期没问题，但长期来说，我都不看好。</li>
<li><strong>绝大多数情况下，如无非常特殊要求，选 Java基本是不会错的</strong>。一方面，这是因为 Java 的业务开发的生产力是非常好的，而且有 Spring 框架保障，代码很难写烂，另外，Java 的社区太成熟了，你需要的各种架构和技术都可以很容易获得，技术红利实在是太大。这种运行在JVM上的语言有太多太多的好处了。在 Java 的技术栈上，你的架构风险和架构的成本（无论是人力成本，时间成本和资金成本）从长期来说都是最优的</li>
</ul>
<p>在我见过的公司中，好些公司的架构都被技术负责人个人的喜好、擅长和个人经验给绑架了，完全不是从一个客观的角度来进行技术选型。其实，从 0 到 1 的阶段，你用什么样的技术都行，如果你做一个简单的应用，没有事务处理没有复杂的交易流程，比如一些论坛、社交之类的应用，你用任何语言都行。但是如果有一天你的系统变复杂了，需要处理交易了，量也上来了，从 1 到 10，甚至从 10 到 100，你的开发团队也变大了，需要构建的系统越来越大，你可能会发现你只有一个选择，就是 Java。想想京东从.NET 到 Java，淘宝从 PHP 到 Java……</p>
<p>注，一些有主观喜好的人一定会对我上述对 Java 的描述感到不适，我还用一些证据说明一下——全中国所有的电商平台，几百家银行，三大电信运营商，所有的保险公司，劵商的系统，医院里的系统，电子政府系统，等等，基本都是用 Java 开发的，包括 AWS 的主流语言也是 Java，阿里云一开始用 C++/Python 写控制系统，后面也开始用 Java ……你可能会说 B站是用 go语言，但是你可能不知道 B 站的电商和大数据是用 Java……懂着数据分析的同学，建议上各大招聘网站上搜一下 Java 的职位数量，你就知道某个技术是否主流和热门……</p>
<h4>原则四：完备性会比性能更重要</h4>
<p>我发现好些公司的架构师做架构的时候，首要考虑的是架构的性能是否能够撑得住多大多大的流量，而不是考虑系统的完备性和扩展性。所以，我已经多次见过这样的案例了，一开始直接使用 MongoDB 这样的非关系型数据库，或是把数据直接放在 Redis 里，而直接放弃关系型数据库的数据完备性的模型，而在后来需要在数据上进行关系查询的时候，发现 NoSQL 的数据库在 Join 上都表现的太差，然后就开始各种飞线，为了不做 Join 就开始冗余数据，然而自己又维护不好冗余数据后带来的数据一致性的问题，导致数据上的各种错乱丢失。</p>
<p>所以，我给如下的一些如下的架构原则：</p>
<ul>
<li><strong>使用最科学严谨的技术模型为主，并以不严谨的模型作为补充</strong>。对于上面那个案例来说，就是——永远使用完备支持 ACID 的关系型数据库，然后用 NoSQL 作补充，而不是完全放弃关系型数据库。这里的原则就是所谓的“先紧后松”，一开始紧了，你可以慢慢松，但是开始松了，以后你想紧再也紧不过来了。</li>
<li><strong>性能上的东西，总是有很多解的</strong>。我这么多年的经历告诉我，性能上的事，总是有解的，手段也是最多的，这个比起架构的完备性和扩展性来说真的不必太过担心。</li>
</ul>
<p>为了追求所谓的性能，把整个系统的完备性丢失掉，相当地得不偿失。</p>
<h4>原则五：制定并遵循服从标准、规范和最佳实践</h4>
<p>这个原则是非常重要的，因为只有服从了标准，你的架构才能够有更好的扩展性。比如：我经常性的见到很多公司的系统既没有服从业界标准，也没有形成自己公司的标准，感觉就像一群乌合之众一样。最典型的例子就是 HTTP 调用的状态返回码。业内给你的标准是 200表示成功，3xx 跳转，4xx 表示调用端出错，5xx 表示服务端出错，我实在是不明白为什么无论成功和失败大家都喜欢返回 200，然后在 body 里指出是否error（前两年我在微信公众号里看到一个有一定名气的互联网老兵推荐使用无论正确还是出错都返回 200 的做法，我在后台再三确认后，我发现这样的架构师真是害人不浅）。这样做最大的问题是——监控系统将在一种低效的状态下工作。监控系统需要把所有的网络请求包打开后才知道是否是错误，而且完全不知道是调用端出错还是服务端出错，于是一些像重试或熔断这样的控制系统完全不知道怎么搞（如果是 4xx错，那么重试或熔断是没有意义的，只有 5xx 才有意义）。<strong>有时候，我会有种越活越退步的感觉，错误码设计这种最基本最基础的东西为什么会没有？并且一个公司会任由着大家乱来？这些基础技能怎么就这样丢掉了？</strong></p>
<p>还有，我还见过一些公司，他们整个组织没有一个统一的用户 ID 的设计，各个系统之间同步用户的数据是通过用户的身份证 ID，是的，就是现实世界的身份证 ID，包括在网关上设置的用户白名单居然也是用身份证 ID。我对这个公司的内的用户隐私管理有很大的担忧。一个企业，一个组织，如果没有标准和规范，也就会有抽象，这一定是要出各种乱子的。</p>
<p>下面，我罗列一些你需要注意的标准和规范（包括但不限于）：</p>
<ul>
<li><strong>服务间调用的协议标准和规范</strong>。这其中包括 Restful API路径, HTTP 方法、状态码、标准头、自定义头等，返回数据 JSon Scheme……等。</li>
<li><strong>一些命名的标准和规范</strong>。这其中包括如：用户 ID，服务名、标签名、状态名、错误码、消息、数据库……等等</li>
<li><strong>日志和监控的规范</strong>。这其中包括：日志格式，监控数据，采样要求，报警……等等</li>
<li><strong>配置上的规范</strong>。这其中包括：操作系统配置、中间件配置，软件包……等等</li>
<li><strong>中间件使用的规范</strong>。数据库，缓存、消息队列……等等</li>
<li><strong>软件和开发库版本统一</strong>。整个组织架构内，软件或开发库的版本最好每年都升一次级，然后在各团队内统一。</li>
</ul>
<p>这里重要说一下两个事：</p>
<ul>
<li><strong>Restful API 的规范</strong>。我觉得是非常重要的，这里给两个我觉得写得最好的参考：<a href="https://github.com/paypal/api-standards/blob/master/api-style-guide.md" target="_blank" rel="noopener">Paypal</a> 和 <a href="https://github.com/microsoft/api-guidelines" target="_blank" rel="noopener">Microsoft</a> 。Restful API 有一个标准和规范最大的好处就是监视可以很容易地做各种统计分析，控制系统可以很容易的做流量编排和调度。</li>
<li><strong>另一个是服务调用链追踪</strong>。对于服务调用链追踪来说，基本上都是参考于 <a href="https://research.google/pubs/pub36356/" target="_blank" rel="noopener">Google Dapper</a> 这篇论文，目前有很多的实现，最严格的实现是 <a href="https://zipkin.io/" target="_blank" rel="noopener">Zipkin</a>，这也是 Spring Cloud Sleuth 的底层实现。Zipkin 贴近 Google Dapper 论文的好处在于——无状态，快速地把 Span 发出来，不消耗服务应用侧的内存和 CPU。这意味着，监控系统宁可自己死了也不能干扰实际应用。</li>
<li><strong>软件升级</strong>。我发现很多公司包括 BAT，他们完全没有软件升级的活动，全靠开发人员自发。然而，这种成体系的活动，是永远不可能靠大众的自发形成的。一个公司至少一年要有一次软件版本升级的review，然后形成软件版本的统一和一致，这样会极太简化系统架构的复杂度。</li>
</ul>
<h4>原则六：重视架构扩展性和可运维性</h4>
<p>在我见过很多架构里，技术人员只考虑当下，但从来不考虑系统的未来扩展性和可运维性。所谓的管生不管养。如果你生下来的孩子胳膊少腿，严重畸形，那么未来是很难玩的。因为架构和软件不是写好就完的，是需要不断修改不断维护的，80%的软件成本都是在维护上。所以，如何让你的架构有更好的扩展性，可以更容易地运维，这个是比较重要的。所谓的扩展性，意味着，我可以很容易地加更多的功能，或是加入更多的系统，而所谓可运维，就是说我可以对线上的系统做任意的变更。扩展性要求的是有标准规范且不耦合的业务架构，可运维性要求的则是可控的能力，也就是一组各式各样的控制系统。</p>
<ul>
<li><strong>通过服务编排架构来降低服务间的耦合</strong>。比如：通过一个业务流程的专用服务，或是像 Workflow，Event Driven Architecture ， Broker，Gateway，Service Discovery 等这类的的中间件来降低服务间的依赖关系。</li>
<li><strong>通过服务发现或服务网关来降低服务依赖所带来的运维复杂度</strong>。服务发现可以很好的降低相关依赖服务的运维复杂度，让你可以很轻松的上线或下线服务，或是进行服务伸缩。</li>
<li><strong>一定要使用各种软件设计的原则</strong>。比如：像SOLID这样的原则（参看《<a title="一些软件设计的原则" href="https://coolshell.cn/articles/4535.html">一些软件设计的原则</a>》），IoC/DIP，SOA 或 Spring Cloud 等 架构的最佳实践（参看《<a title="SteveY对Amazon和Google平台的吐槽 - 67,710 人阅读" href="https://coolshell.cn/articles/5701.html" target="_blank" rel="noopener">SteveY对Amazon和Google平台的吐槽</a>》中的 Service Interface 的那几条军规），分布式系统架构的相关实践（参看：《<a title="分布式系统的事务处理" href="https://coolshell.cn/articles/10910.html" target="_blank" rel="noopener">分布式系统的事务处理</a>》，或微软件的 《<a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/" target="_blank" rel="noopener">Cloud Design Patterns</a>》）……等等</li>
</ul>
<h4>原则七：对控制逻辑进行全面收口</h4>
<p>所有的程序都会有两种逻辑，一种是业务逻辑，一种是控制逻辑，业务逻辑就是完成业务的逻辑，控制逻辑是辅助，比如你用多线程，还是用分布式，是用数据库还是用文件，如何配置、部署，运维、监控，事务控制，服务发现，弹性伸缩，灰度发布，高并发，等等，等等 ……这些都是控制逻辑，跟业务逻辑没有一毛钱关系。控制逻辑的技术深度会通常会比业务逻辑要深一些，门槛也会要高一些，所以，最好要专业的程序员来负责控制逻辑的开发，统一规划统一管理，进行收口。这其中包括：</p>
<ul>
<li><strong>流量收口</strong>。包括南北向和东西向的流量的调度，主要通过流量网关，开发框架 SDK或 Service Mesh 这样的技术。</li>
<li><strong>服务治理收口</strong>。包括：服务发现、健康检查，配置管理、事务、事件、重试、熔断、限流……主要通过开发框架 SDK &#8211; 如：Spring Cloud，或服务网格Service Mesh等技术。</li>
<li><strong>监控数据收口</strong>。包括：日志、指标、调用链……主要通过一些标准主流的探针，再加上后台的数据清洗和数据存储来完成，最好是使用无侵入式的技术。监控的数据必须统一在一个地方进行关联，这样才会产生信息。</li>
<li><strong>资源调度有应用部署的收口</strong>。包括：计算、网络和存储的收口，主要是通过容器化的方案，如k8s来完成。</li>
<li><strong>中间件的收口</strong>。包括：数据库，消息，缓存，服务发现，网关……等等。这类的收口方式一般要在企业内部统一建立一个共享的云化的中间件资源池。</li>
</ul>
<p>对此，这里的原则是：</p>
<ul>
<li><strong>你要选择容易进行业务逻辑和控制逻辑分离的技术</strong>。这里，Java 的 JVM+字节码注入+AOP 式的Spring 开发框架，会带给你太多的优势。</li>
<li><strong>你要选择可以享受“前人种树，后人乘凉”的有技术红利的技术</strong>。如：有庞大社区而且相互兼容的技术，如：Java, Docker,  Ansible，HTTP，Telegraf/Collectd……</li>
<li><strong>中间件你要使用可以 支持HA集群和多租户的技术</strong>。这里基本上所有的主流中间件都会支持 HA 集群方式的。</li>
</ul>
<h4>原则八：不要迁就老旧系统的技术债务</h4>
<p>我发现很多公司都很非常大的技术债务，这些债务具体表现如下：</p>
<ul>
<li><strong>使用老旧的技术</strong>。比如，使用HTTP1.0， Java 1.6，Websphere，ESB，基于 socket的通讯协议，过时的模型……等等</li>
<li><strong>不合理的设计</strong>。比如，在 gateway 中写大量的业务逻辑，单体架构，数据和业务逻辑深度耦合，错误的系统架构（把缓存当数据库，用消息队列同步数据）……等等</li>
<li> <strong>缺少配套设施</strong>。比如，没有自动化测试，没有好的软件文档，没有质量好的代码，没有标准和规范……等等</li>
</ul>
<p>来找我寻求技术帮助的人都有各种各样的问题。我都会对他们苦口婆心地说同样的一句话——“<strong>如果你是来找我 case-by-case 解决问题，我兴趣不大，因为，你们千万不要寄希望能够很简单的把一辆夏利车改成一辆法拉利跑车，或是把一栋地基没打好的歪楼搞正。以前欠下的技术债，都得要还，没打好的地基要重新打，没建配套设施都要建。这些基础设施如果不按照正确科学的方式建立的话，你是不可能有一个好的的系统，我也没办法帮你 case-by-case 的解决问题……</strong>”，一开始，他们都会对我说，没问题，我们就是要还债，但是，最后发现要还的债真多，有点承受不了，就开始现原形了。</p>
<p>他们开始为自己的“欠的技术债”找各种合理化的理由——给你解释各种各样的历史原因和不得以而为之的理由。谈着谈着，让我有一种感觉——他们希望得到一种什么都不改什么都不付出的方式就可以进步的心态，他们宁可让新的技术 low 下来迁就于这些技术债，把新的技术滥用地乱七八糟的。有一个公司，他们的系统架构和技术选型基本都搞错了，使用错误的模型构建系统，导致整个系统的性能非常之差，也才几千万条数据，但他们想的不是还债，不是把地基和配套设施建好，而且要把楼修的更高，上更多的系统——他们觉得现有的系统挺好，性能问题的原因是他们没一个大数据平台，所以要建大数据平台……</p>
<p>我见过很多很多公司，包括大如 BAT 这样的公司，都会在原来的技术债上进行更多的建设，然后，技术债越来越大，利息越来越大，最终成为一个高利贷，再也还不了（我在《<a href="https://coolshell.cn/articles/11656.html" target="_blank" rel="noopener">开发团队的效率</a>》一文中讲过一个 WatchDog 的架构模式，一个系统烂了，不是去改这个系统，而是在旁边建一个系统来看着它，我很难理解为什么会有这样的逻辑，也许是为了要解决更多的就业……）</p>
<p>这里有几个原则和方法我是非常坚持的，分享给大家：</p>
<ul>
<li><strong>与其花大力气迁就技术债务，不如直接还技术债。是所谓的长痛不如短痛。</strong></li>
<li><strong>建设没有技术债的“新城区”，并通过“<a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer" target="_blank" rel="noopener">防腐层</a> ”的架构模型，不要让技术债侵入“新城区”</strong>。</li>
</ul>
<h4>原则九：不要依赖自己的经验，要依赖于数据和学习</h4>
<p>有好些人来找我跟我说他们的技术问题，然后希望我能够给他们一个答案。我说，我需要了解一下你现有系统的情况，也就是需要先做个诊断，我只有得到这些数据后，我才可能明白真正的原因是什么 ，我才可能给你做出一个比较好的技术方案。我个人觉得这是一种对对方负责的方法，因为技术手段太多了，所有的技术手段都有适应的场景，并且有各种 trade-off，所以，只有调研完后才能做出决定。这跟医生看病是一样的，确诊病因不能靠经验，还是要靠诊断数据。在科学面前，所有的经验都是靠不住的……</p>
<p>另外，如果有一天你在做技术决定的时候，开始凭自己以往的经验，那么你就已经不可能再成长了。人都是不可能通过不断重复过去而进步的，人的进步从来都是通过学习自己不知道的东西。所以，千万不要依赖于自己的经验做决定。做任何决定之前，最好花上一点时间，上网查一下相关的资料，技术博客，文章，论文等 ，同时，也看看各个公司，或是各个开源软件他们是怎么做的？然后，比较多种方案的 Pros/Cons，最终形成自己的决定，这样，才可能做出一个更好的决定。</p>
<h4>原则十：千万要小心 X &#8211; Y 问题，要追问原始需求</h4>
<p>对于 <a title="X-Y Problem" href="https://coolshell.cn/articles/10804.html">X-Y 问题</a>，也就是说，用户为了解决 X问题，他觉得用 Y 可以解，于是问我 Y 怎么搞，结果搞到最后，发现原来要解决的 X 问题，这个时候最好的解决方案不是 Y，而是 Z。 这种 X-Y 问题真是相当之多，见的太多太多了。所以，每次用户来找我的时候，我都要不断地追问什么是 X 问题。</p>
<p>比如，好些用户都会来问我他们要一个大数据流式处理，结果追问具体要解决什么样的问题时，才发现他们的问题是因为服务中有大量的状态，需要把相同用户的数据请求放在同一个服务上处理，而且设计上导致一个慢函数拖慢整个应用服务。最终就是做一下性能调优就好了，根本没有必要上什么大数据的流式处理。</p>
<p>我很喜欢追问为什么 ，这种追问，会让客户也跟着来一起重新思考。比如，有个客户来找我评估的一个技术架构的决定，从理论上来说，好像这个架构在用户的这个场景下非常不错。但是，这个场景和这个架构是我职业生涯从来没有见过的。于是，我开始追问这个为什么会是这么一个场景？当我追问的时候，我发现用户都感到这个场景的各种不合理。最后引起了大家非常深刻的研讨，最终用户把那个场景修正后，而架构就突然就变成了一个常见且成熟的的模型……</p>
<h4>原则十一：激进胜于保守，创新与实用并不冲突</h4>
<p>我对技术的态度是比较激进的，但是，所谓的激进并不是瞎搞，也不是见新技术就上，而是积极拥抱会改变未来的新技术，如：Docker/Go，我就非常快地跟进，但是像区块链或是 Rust 这样的，我就不是很积极。因为，其并没有命中我认为的技术趋势的几个特征（参看《<a title="Go语言、Docker 和新技术" href="https://coolshell.cn/articles/18190.html" target="_blank" rel="noopener">Go,Docker 和新技术</a> 》）。当然，我也不是不喜欢的就不学了，我对区块链和 Rust 我一样学习，我也知道这些技术的优势，但我不会大规模使用它们。另外，我也尊重保守的决定，这里面没有对和错。但是，我个人觉得对技术激进的态度比起保守来说有太多的好处了。一方面来说，对于用户来说，很大程度上来说，新技术通常都表面有很好的竞争力，而且我见太多这样成功的公司都在积极拥抱新的技术的，而保守的通常来说都越来越不好。</p>
<p>有一些人会跟我说，我们是实用主义，我们不需要创新，能解决当下的问题就好，所以，我们不需要新技术，现有的技术用好就行了。这类的公司，他们的技术设计第一天就在负债，虽然可以解决当下问题，但是马上就会出现新的问题，然后他们会疲于解决各种问题。最后呢，最后还是会走到新的技术上。</p>
<p>这里的逻辑很简单 —— <strong>进步永远来自于探索，探索是要付出代价的，但是收益更大</strong>。对我而言，不敢冒险才是最大的冒险，不敢犯错才是最大的错误，害怕失去会让你失去的更多……</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" id="wp_rp_first"><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-150x150.jpg" alt="从Gitlab误删除数据库想到的" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_title">从Gitlab误删除数据库想到的</a></li><li ><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-150x150.png" alt="关于高可用的系统" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_title">关于高可用的系统</a></li><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/6775.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="Bret Victor &#8211; Inventing on Principle" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6775.html" class="wp_rp_title">Bret Victor &#8211; Inventing on Principle</a></li><li ><a href="https://coolshell.cn/articles/5686.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/15.jpg" alt="多些时间能少写些代码" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5686.html" class="wp_rp_title">多些时间能少写些代码</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/21672.html">我做系统架构的一些原则</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/21672.html/feed</wfw:commentRss>
			<slash:comments>163</slash:comments>
		
		
			</item>
		<item>
		<title>API设计原则 &#8211; Qt官网的设计实践总结</title>
		<link>https://coolshell.cn/articles/18024.html</link>
					<comments>https://coolshell.cn/articles/18024.html#comments</comments>
		
		<dc:creator><![CDATA[李 鼎]]></dc:creator>
		<pubDate>Tue, 25 Jul 2017 06:16:30 +0000</pubDate>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[技术读物]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[API]]></category>
		<category><![CDATA[api-design]]></category>
		<category><![CDATA[API设计]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Coding]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[qt]]></category>
		<category><![CDATA[程序员]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=18024</guid>

					<description><![CDATA[<p>（感谢好友 @李鼎 翻译此文） 原文链接：API Design Principles &#8211; Qt Wiki 基于Gary的影响力上 Gary Gao ...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/18024.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/18024.html">API设计原则 – Qt官网的设计实践总结</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><span style="color: #993300;"><strong>（感谢好友 <a href="http://www.weibo.com/oldratlee" target="_blank"  rel="noopener noreferrer">@李鼎</a> 翻译此文）</strong></span></p>
<p>原文链接：<a href="http://qt-project.org/wiki/API-Design-Principles">API Design Principles</a> &#8211; <a href="http://wiki.qt.io/">Qt Wiki</a><br />
基于<a href="http://blog.csdn.net/gaoyingju">Gary的影响力</a>上 <em>Gary Gao</em> 的译文稿：<a href="http://blog.csdn.net/gaoyingju/article/details/8245108">C++的API设计指导</a></p>
<h2>译序</h2>
<p><img decoding="async" loading="lazy" class="alignright size-medium" src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278.jpg" alt="" width="300" height="278" /></p>
<p>Qt的设计水准在业界很有口碑，一致、易于掌握和强大的API是Qt最著名的优点之一。此文既是Qt官网上的API设计指导准则，也是Qt在API设计上的实践总结。虽然Qt用的是C++，但其中设计原则和思考是具有普适性的（如果你对C++还不精通，可以忽略与C++强相关或是过于细节的部分，仍然可以学习或梳理关于API设计最有价值的内容）。整个篇幅中有很多示例，是关于API设计一篇难得的好文章。</p>
<p>需要注意的是，这篇Wiki有一些内容并不完整，所以，可能会有一些阅读上的问题，我们对此做了一些相关的注释。</p>
<p>PS：翻译中肯定会有不足和不对之处，欢迎评论&amp;交流；另译文源码在<a href="https://github.com/oldratlee/translations/tree/master/api-design-principles-from-qt">GitHub的这个仓库</a>中，可以<a href="https://github.com/oldratlee/translations/issues">提交Issue</a>/<a href="https://github.com/oldratlee/translations/fork">Fork后提交代码</a>来建议/指正。</p>
<h1>API设计原则</h1>
<p>一致、易于掌握和强大的API是Qt最著名的优点之一。此文总结了我们在设计Qt风格API的过程中所积累的诀窍（know-how）。其中许多是通用准则；而其他的则更偏向于约定，遵循这些约定主要是为了与已有的API保持一致。</p>
<p>虽然这些准则主要用于对外的API（public API），但在设计对内的API（private API）时也推荐遵循相同的技巧（techniques），作为开发者之间协作的礼仪（courtesy）。</p>
<p><span id="more-18024"></span></p>
<p>如有兴趣也可以读一下 <em>Jasmin Blanchette</em> 的<a href="http://www4.in.tum.de/~blanchet/api-design.pdf">Little Manual of API Design (PDF)</a> 或是本文的前身 <em>Matthias Ettrich</em> 的<a href="https://doc.qt.io/archives/qq/qq13-apis.html">Designing Qt-Style C++ APIs</a>。</p>
<h1>1. 好API的6个特质</h1>
<p>API之于程序员就如同图形界面之于普通用户（end-user）。API中的『P』实际上指的是『程序员』（Programmer），而不是『程序』（Program），强调的是API是给程序员使用的这一事实。</p>
<p>在第13期<a href="http://doc.qt.io/archives/qq/">Qt季刊</a>，<em>Matthias</em> 的<a href="https://doc.qt.io/archives/qq/qq13-apis.html">关于API设计的文章</a>中提出了观点：API应该极简（minimal）且完备（complete）、语义清晰简单（have clear and simple semantics）、符合直觉（be intuitive）、易于记忆（be easy to memorize）和引导API使用者写出可读代码（lead to readable code）。</p>
<h2>1.1 极简</h2>
<p>极简的API是指每个class的public成员尽可能少，public的class也尽可能少。这样的API更易理解、记忆、调试和变更。</p>
<h2>1.2 完备</h2>
<p>完备的API是指期望有的功能都包含了。这点会和保持API极简有些冲突。如果一个成员函数放在错误的类中，那么这个函数的潜在用户就会找不到，这也是违反完备性的。</p>
<h2>1.3 语义清晰简单</h2>
<p>就像其他的设计一样，我们应该遵守最少意外原则（the principle of least surprise）。好的API应该可以让常见的事完成的更简单，并有可以完成不常见的事的可能性，但是却不会关注于那些不常见的事。解决的是具体问题；当没有需求时不要过度通用化解决方案。（举个例子，在Qt 3中，<code>QMimeSourceFactory</code>不应命名成<code>QImageLoader</code>并有不一样的API。）</p>
<h2>1.4 符合直觉</h2>
<p>就像计算机里的其他事物一样，API应该符合直觉。对于什么是符合直觉的什么不符合，不同经验和背景的人会有不同的看法。API符合直觉的测试方法：经验不很丰富的用户不用阅读API文档就能搞懂API，而且程序员不用了解API就能看明白使用API的代码。</p>
<h2>1.5 易于记忆</h2>
<p>为使API易于记忆，API的命名约定应该具有一致性和精确性。使用易于识别的模式和概念，并且避免用缩写。</p>
<h2>1.6 引导API使用者写出可读代码</h2>
<p>代码只写一次，却要多次的阅读（还有调试和修改）。写出可读性好的代码有时候要花费更多的时间，但对于产品的整个生命周期来说是节省了时间的。</p>
<p>最后，要记住的是，不同的用户会使用API的不同部分。尽管简单使用单个Qt类的实例应该符合直觉，但如果是要继承一个类，让用户事先看好文档是个合理的要求。</p>
<h1>2. 静态多态</h1>
<p>相似的类应该有相似的API。在继承（inheritance）合适时可以用继承达到这个效果，即运行时多态。然而多态也发生在设计阶段。例如，如果你用<code>QProgressBar</code>替换<code>QSlider</code>，或是用<code>QString</code>替换<code>QByteArray</code>，你会发现API的相似性使的替换很容易。这即是所谓的『静态多态』（static polymorphism）。</p>
<p>静态多态也使记忆API和编程模式更加容易。因此，一组相关的类有相似的API有时候比每个类都有各自的一套API更好。</p>
<p>一般来说，在Qt中，如果没有足够的理由要使用继承，我们更倾向于用静态多态。这样可以减少Qt public类的个数，也使刚学习Qt的用户在翻看文档时更有方向感。</p>
<h2>2.1 好的案例</h2>
<p><code>QDialogButtonBox</code>与<code>QMessageBox</code>，在处理按钮（<code>addButton()</code>、<code>setStandardButtons()</code>等等）上有相似的API，不需要继承某个<code>QAbstractButtonBox</code>类。</p>
<h2>2.2 差的案例</h2>
<p><code>QTcpSocket</code>与<code>QUdpSocket</code>都继承了<code>QAbstractSocket</code>，这两个类的交互行为的模式（mode of interaction）非常不同。似乎没有什么人以通用和有意义的方式用过<code>QAbstractSocket</code>指针（或者 <strong><em>能</em></strong> 以通用和有意义的方式使用<code>QAbstractSocket</code>指针）。</p>
<h2>2.3 值得斟酌的案例</h2>
<p><code>QBoxLayout</code>是<code>QHBoxLayout</code>与<code>QVBoxLayout</code>的父类。好处：可以在工具栏上使用<code>QBoxLayout</code>，调用<code>setOrientation()</code>使其变为水平/垂直。坏处：要多一个类，并且有可能导致用户写出这样没什么意义的代码，<code>((QBoxLayout *)hbox)-&gt;setOrientation(Qt::Vertical)</code>。</p>
<h1>3. 基于属性的API</h1>
<p>新的Qt类倾向于用『基于属性（property）的API』，例如：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QTimer timer;<br />
timer.setInterval(1000);<br />
timer.setSingleShot(true);<br />
timer.start();<br />
[/code]</p>
<p>这里的 <strong><em>属性</em></strong> 是指任何的概念特征（conceptual attribute），是对象状态的一部分 —— 无论它是不是<code>Q_PROPERTY</code>。在说得通的情况下，用户应该可以以任何顺序设置属性，也就是说，属性之间应该是正交的（orthogonal）。例如，上面的代码可以写成：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QTimer timer;<br />
timer.setSingleShot(true);<br />
timer.setInterval(1000);<br />
timer.start();<br />
[/code]</p>
<blockquote><p>【译注】：正交性是指改变某个特性而不会影响到其他的特性。<a href="https://book.douban.com/subject/5387402/">《程序员修炼之道》</a>中讲了关于正交性的一个直升飞机坠毁的例子，讲得深入浅出很有画面感。</p></blockquote>
<p>为了方便，也写成：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
timer.start(1000)；<br />
[/code]</p>
<p>类似地，对于<code>QRegExp</code>会是这样的代码：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QRegExp regExp;<br />
regExp.setCaseSensitive(Qt::CaseInsensitive);<br />
regExp.setPattern(&quot;.&quot;);<br />
regExp.setPatternSyntax(Qt::WildcardSyntax);<br />
[/code]</p>
<p>为实现这种类型的API，需要借助底层对象的懒创建。例如，对于<code>QRegExp</code>的例子，在不知道模式语法（pattern syntax）的情况下，在<code>setPattern()</code>中去解释<code>"."</code>就为时过早了。</p>
<p>属性之间常常有关联的；在这种情况下，我们必须小心处理。思考下面的问题：当前的风格（style）提供了『默认的图标尺寸』属性 vs. <code>QToolButton</code>的『<code>iconSize</code>』属性：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
toolButton-&gt;setStyle(otherStyle);<br />
toolButton-&gt;iconSize();    // returns the default for otherStyle<br />
toolButton-&gt;setIconSize(QSize(52, 52));<br />
toolButton-&gt;iconSize();    // returns (52, 52)<br />
toolButton-&gt;setStyle(yetAnotherStyle);<br />
toolButton-&gt;iconSize();    // returns (52, 52)<br />
[/code]</p>
<p>提醒一下，一旦设置了<code>iconSize</code>，设置就会一直保持，即使改变当前的风格。这 <strong><em>很好</em></strong>。但有的时候需要能重置属性。有两种方法：</p>
<ol>
<li>传入一个特殊值（如<code>QSize()</code>、<code>-1</code>或者<code>Qt::Alignment(0)</code>）来表示『重置』</li>
<li>提供一个明确的重置方法，如<code>resetFoo()</code>和<code>unsetFoo()</code></li>
</ol>
<p>对于<code>iconSize</code>，使用<code>QSize()</code>（比如 <code>QSize(–1, -1)</code>）来表示『重置』就够用了。</p>
<p>在某些情况下，getter方法返回的结果与所设置的值不同。例如，虽然调用了<code>widget-&gt;setEnabled(true)</code>，但如果它的父widget处于disabled状态，那么<code>widget-&gt;isEnabled()</code>仍然返回的是<code>false</code>。这样是OK的，因为一般来说就是我们想要的检查结果（父widget处于disabled状态，里面的子widget也应该变为灰的不响应用户操作，就好像子widget自身处于disabled状态一样；与此同时，因为子widget记得在自己的内心深处是enabled状态的，只是一直等待着它的父widget变为enabled）。当然诸如这些都必须在文档中妥善地说明清楚。</p>
<h1>4. C++相关</h1>
<h2>4.1 值 vs. 对象</h2>
<h3>4.1.1 指针 vs. 引用</h3>
<p>指针（pointer）还是引用（reference）哪个是最好的输出参数（out-parameters）？</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void getHsv(int *h, int *s, int *v) const;<br />
void getHsv(int &amp;h, int &amp;s, int &amp;v) const;<br />
[/code]</p>
<p>大多数C++书籍推荐尽可能使用引用，基于一个普遍的观点：引用比指针『更加安全和优雅』。与此相反，我们在开发Qt时倾向于指针，因为指针让用户代码可读性更好。比较下面例子：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
color.getHsv(&amp;h, &amp;s, &amp;v);<br />
color.getHsv(h, s, v);<br />
[/code]</p>
<p>只有第一行代码清楚表达出<code>h</code>、<code>s</code>、<code>v</code>参数在函数调用中非常有可能会被修改。</p>
<p>这也就是说，编译器并不喜欢『出参』，所你应该在新的API中避免使用『出参』，而是返回一个结构体，如下所示：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
struct Hsv { int hue, saturation, value };<br />
Hsv getHsv() const;<br />
[/code]</p>
<blockquote><p>【译注】：函数的『入参』和『出参』的混用会导致 API 接口语义的混乱，所以，使用指针，在调用的时候，实参需要加上“&amp;”，这样在代码阅读的时候，可以看到是一个『出参』，有利于代码阅读。（但是这样做，在函数内就需要判断指针是否为空的情况，因为引用是不需要判断的，所以，这是一种 trade-off）</p>
<p>另外，如果这样的参数过多的话，最好使用一个结构体来把数据打包，一方面，为一组返回值取个名字，另一方面，这样有利用接口的简单。</p></blockquote>
<h3>4.1.2 按常量引用传参 vs. 按值传参</h3>
<p>如果类型大于16字节，按常量引用传参。</p>
<p>如果类型有重型的（non-trivial）拷贝构造函数（copy-constructor）或是重型的析构函数（destructor），按常量引用传参以避免执行这些函数。</p>
<p>对于其它的类型通常应该按值传参。</p>
<p>示例：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void setAge(int age);<br />
void setCategory(QChar cat);<br />
void setName(QLatin1String name);</p>
<p>// const-ref is much faster than running copy-constructor and destructor<br />
void setAlarm(const QSharedPointer&lt;Alarm&gt; &amp;alarm);</p>
<p>// QDate, QTime, QPoint, QPointF, QSize, QSizeF, QRect<br />
// are good examples of other classes you should pass by value.<br />
[/code]</p>
<blockquote><p>【译注】：这是传引用和传值的差别了，因为传值会有对像拷贝，传引用则不会。所以，如果对像的构造比较重的话（换句话说，就是对像里的成员变量需要的内存比较大），这就会影响很多性能。所以，为了提高性能，最好是传引用。但是如果传入引用的话，会导致这个对象可能会被改变。所以传入const reference。</p></blockquote>
<h2>4.2 虚函数</h2>
<p>在C++中，当类的成员函数声明为virtual，主要是为了通过在子类重载此函数能够定制函数的行为。将函数声明为virtual的目的是为了让对这个函数已有的调用变成执行实际实例的代码路径。对于没有在类外部调用的函数声明成virtual，你应该事先非常慎重地思考过。</p>
<p>[code language=&#8221;cpp&#8221;]<br />
// QTextEdit in Qt 3: member functions that have no reason for being virtual<br />
virtual void resetFormat();<br />
virtual void setUndoDepth( int d );<br />
virtual void setFormat( QTextFormat *f, int flags );<br />
virtual void ensureCursorVisible();<br />
virtual void placeCursor( const QPoint &amp;pos;, QTextCursor **c = 0 );<br />
virtual void moveCursor( CursorAction action, bool select );<br />
virtual void doKeyboardAction( KeyboardAction action );<br />
virtual void removeSelectedText( int selNum = 0 );<br />
virtual void removeSelection( int selNum = 0 );<br />
virtual void setCurrentFont( const QFont &amp;f );<br />
virtual void setOverwriteMode( bool b ) { overWrite = b; }<br />
[/code]</p>
<p><code>QTextEdit</code>从Qt 3移植到Qt 4的时候，几乎所有的虚函数都被移除了。有趣的是（但在预料之中），并没有人对此有大的抱怨，为什么？因为Qt 3没用到<code>QTextEdit</code>的多态行为 —— 只有你会；简单地说，没有理由去继承<code>QTextEdit</code>并重写这些函数，除非你自己调用了这些方法。如果在Qt在外部你的应用程序你需要多态，你可以自己添加多态。</p>
<blockquote><p>【译注】：『多态』的目的只不过是为了实践 —— 『依赖于接口而不是实现』，也就是说，接口是代码抽像的一个非常重要的方式（在Java/Go中都有专门的接口声明语法）。所以，如果没有接口抽像，使用『多态』的意义也就不大了，因为也就没有必要使用『虚函数』了。</p></blockquote>
<h3>4.2.1 避免虚函数</h3>
<p>在Qt中，我们有很多理由尽量减少虚函数的数量。每一次对虚函数的调用会在函数调用链路中插入一个未掌控的节点（某种程度上使结果更无法预测），使得bug修复变得更复杂。用户在重写的虚函数中可以做很多疯狂的事：</p>
<ul>
<li>发送事件</li>
<li>发送信号</li>
<li>重新进入事件循环（例如，通过打开一个模态文件对话框）</li>
<li>删除对象（即触发『<code>delete this</code>』）</li>
</ul>
<p>还有其他很多原因要避免过度使用虚函数：</p>
<ul>
<li>添加、移动或是删除虚函数都带来二进制兼容问题（binary compatibility/BC）</li>
<li>重载虚函数并不容易</li>
<li>编译器几乎不能优化或内联（inline）对虚函数的调用</li>
<li>虚函数调用需要查找虚函数表（v-table），这比普通函数调用慢了2到3倍</li>
<li>虚函数使得类很难按值拷贝（尽管也可以按值拷贝，但是非常混乱并且不建议这样做）</li>
</ul>
<p>经验告诉我们，没有虚函数的类一般bug更少、维护成本也更低。</p>
<p>一般的经验法则是，除非我们以这个类作为工具集提供而且有很多用户来调用某个类的虚函数，否则这个函数九成不应该设计成虚函数。</p>
<blockquote><p>【译注】：</p>
<ol>
<li>使用虚函数时，你需要对编译器的内部行为非常清楚，否则，你会在使用虚函数时，觉得有好些『古怪』的问题发生。比如在创建数组对象的时候。</li>
<li>在C++中，会有一个基础类，这个基础类中已经实现好了很多功能，然后把其中的一些函数放给子类去修改和实现。这种方法在父类和子类都是一组开发人员维护时没有什么问题，但是如果这是两组开发人员，这就会带来很多问题了，就像Qt这样，子类完全无法控制，全世界的开发人员想干什么就干什么。所以，子类的代码和父类的代码在兼容上就会出现很多很多问题。所以，还是上面所说，其实，虚函数应该声明在接口的语义里（这就是设计模式的两个宗旨——依赖于接口，而不是实现；钟爱于组合，而不是继承。也是为什么Java和Go语言使用interface关键字的原因，C++在多态的语义上非常容易滥用）</li>
</ol>
</blockquote>
<h3>4.2.2 虚函数 vs. 拷贝</h3>
<p>多态对象（polymorphic objects）和值类型的类（value-type classes）两者很难协作好。</p>
<p>包含虚函数的类必须把析构函数声明为虚函数，以防止父类析构时没有清理子类的数据，导致内存泄漏。</p>
<p>如果要使一个类能够拷贝、赋值或按值比较，往往需要拷贝构造函数、赋值操作符（<code>operator =</code>）和相等操作符（<code>operator ==</code>）。</p>
<p>[code language=&#8221;cpp&#8221;]<br />
class CopyClass {<br />
public:<br />
    CopyClass();<br />
    CopyClass(const CopyClass &amp;other);<br />
    ~CopyClass();<br />
    CopyClass &amp;operator =(const CopyClass &amp;other);<br />
    bool operator ==(const CopyClass &amp;other) const;<br />
    bool operator !=(const CopyClass &amp;other) const;</p>
<p>    virtual void setValue(int v);<br />
};<br />
[/code]</p>
<p>如果继承<code>CopyClass</code>这个类，预料之外的事就已经在代码时酝酿了。一般情况下，如果没有虚成员函数和虚析构函数，就不能创建出可以多态的子类。然而，如果存在虚成员函数和虚析构函数，这突然变成了要有子类去继承的理由，而且开始变得复杂了。<strong><em>起初认为只要简单声明上虚操作符重载函数（virtual operators）。</em></strong> 但其实是走上了一条混乱和毁灭之路（破坏了代码的可读性）。看看下面的这个例子：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
class OtherClass {<br />
public:<br />
    const CopyClass &amp;instance() const; // 这个方法返回的是什么？可以赋值什么？<br />
};<br />
[/code]</p>
<p>（这部份还未完成）</p>
<blockquote><p>【译注】：因为原文上说，这部份并没有完成，所以，我也没有搞懂原文具体也是想表达什么。不过，就标题而言，原文是想说，在多态的情况下拷贝对象所带来的问题？？</p></blockquote>
<h2>4.3 关于const</h2>
<p><strong><em>C++的关键词const表明了内容不会改变或是没有副作用。可以应用于简单的值、指针及指针所指的内容，也可以作为一个特别的属性应用于类的成员函数上，表示成员函数不能修改对象的状态。</em></strong></p>
<p>然而，const本身并没有提供太大的价值 —— 很多编程语言甚至没有类似const的关键词，但是却并没有因此产生问题。实际上，如果你不用函数重载，并在C++源代码用搜索并删除所有的const，几乎总能编译通过并且正常运行。尽量让使用的const保持实用有效，这点很重要。</p>
<p>让我们看一下在Qt的API设计中与const相关的场景。</p>
<h3>4.3.1 输入参数：const指针</h3>
<p>有输入指针参数的const成员函数，几乎总是const指针参数。</p>
<p>如果函数声明为const，意味着既没有副作用，也不会改变对象的可见状态。那为什么它需要一个没有const限定的输入参数呢？记住const类型的函数通常被其他const类型的函数调用，接收到的一般都是const指针（只要不主动const_cast，我们推荐尽量避免使用const_cast）</p>
<p>以前：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
bool QWidget::isVisibleTo(QWidget *ancestor) const;<br />
bool QWidget::isEnabledTo(QWidget *ancestor) const;<br />
QPoint QWidget::mapFrom(QWidget *ancestor, const QPoint &amp;pos) const;<br />
[/code]</p>
<p><code>QWidget</code>声明了许多非const指针输入参数的const成员函数。注意，这些函数可以修改传入的参数，不能修改对象自己。使用这样的函数常常要借助const_cast转换。如果是const指针输入参数，就可以避免这样的转换了。</p>
<p>之后：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
bool QWidget::isVisibleTo(const QWidget *ancestor) const;<br />
bool QWidget::isEnabledTo(const QWidget *ancestor) const;<br />
QPoint QWidget::mapFrom(const QWidget *ancestor, const QPoint &amp;pos) const;<br />
[/code]</p>
<p>注意，我们在<code>QGraphicsItem</code>中对此做了修正，但是<code>QWidget</code>要等到Qt 5:</p>
<p>[code language=&#8221;cpp&#8221;]<br />
bool isVisibleTo(const QGraphicsItem *parent) const;<br />
QPointF mapFromItem (const QGraphicsItem *item, const QPointF &amp;point) const;<br />
[/code]</p>
<h3>4.3.2 返回值：const值</h3>
<p>调用函数返回的非引用类型的结果，称之为右值（R-value）。</p>
<p>非类（non-class）的右值总是无cv限定类型（cv-unqualified type）。虽然从语法上讲，加上const也可以，但是没什么意义，因为鉴于访问权限这些值是不能改变的。多数现代编译器在编译这样的代码时会提示警告信息。</p>
<blockquote><p>【译注】：cv-qualified的类型（与cv-unqualified相反）是由const或者volatile或者volatile const限定的类型。详见<a href="http://en.cppreference.com/w/cpp/language/cv">cv (const and volatile) type qualifiers &#8211; C++语言参考</a></p></blockquote>
<p>当在类类型（class type）右值上添加const关键字，则禁止访问非const成员函数以及对成员的直接操作。</p>
<p>不加const则没有以上的限制，但几乎没有必要加上const，因为右值对象生存时间（life time）的结束一般在C++清理的时候（通俗的说，下一个分号地方），而对右值对象的修改随着右值对象的生存时间也一起结束了（也就是本条语句的执行完成的时候）。</p>
<p>示例：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
struct Foo {<br />
    void setValue(int v) { value = v; }<br />
    int value;<br />
};</p>
<p>Foo foo() {<br />
    return Foo();<br />
}</p>
<p>const Foo cfoo() {<br />
    return Foo();<br />
}</p>
<p>int main() {<br />
    // The following does compile, foo() is non-const R-value which<br />
    // can&#8217;t be assigned to (this generally requires an L-value)<br />
    // but member access leads to a L-value:<br />
    foo().value = 1; // Ok, but temporary will be thrown away at the end of the full-expression.</p>
<p>    // The following does compile, foo() is non-const R-value which<br />
    // can&#8217;t be assigned to, but calling (even non-const) member<br />
    // function is fine:<br />
    foo().setValue(1); // Ok, but temporary will be thrown away at the end of the full-expression.</p>
<p>    // The following does _not_compile, foo() is &#8221;const&#8221; R-value<br />
    // with const member which member access can&#8217;t be assigned to:<br />
    cfoo().value = 1; // Not ok.</p>
<p>    // The following does _not_compile, foo() is &#8221;const&#8221; R-value,<br />
    // one cannot call non-const member functions:<br />
    cfoo().setValue(1); // Not ok<br />
}<br />
[/code]</p>
<blockquote><p>【译注】：上述的代码说明，如果返回值不是const的，代码可以顺利编译通过，然而并没有什么卵用，因为那个临时对像马上就被抛弃了。所以，这样的无用的代码最好还是在编译时报个错，以免当时头脑发热想错了，写了一段没用但还以为有用的代码。</p></blockquote>
<h3>4.3.3 返回值：非const的指针还是有const的指针</h3>
<p>谈到const函数应该返回非const的指针还是const指针这个话题时，多数人发现在C++中关于『const正确性』（const correctness）在概念上产生了分歧。 <em>问题起源是：<strong>const函数本身不能修改对象自身的状态，却可以返回成员的非const指针</strong>。返回指针这个简单动作本身既不会影响整个对象的可见状态，当然也不会改变这个函数职责范围内涉及的状态。但是，这却使得程序员可以间接访问并修改对象的状态。</em></p>
<p>下面的例子演示了通过返回非const指针的const函数绕开const约定（constness）的诸多方式中的一种：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QVariant CustomWidget::inputMethodQuery(Qt::InputMethodQuery query) const {<br />
    moveBy(10, 10); // doesn&#8217;t compile!<br />
    window()-&gt;childAt(mapTo(window(), rect().center()))-&gt;moveBy(10, 10); // compiles!<br />
}<br />
[/code]</p>
<p>返回const指针的函数正是保护以避免这些（可能是不期望的/没有预料到的）副作用，至少是在一定程度上。但哪个函数你会觉得更想返回const指针，或是不止一个函数？</p>
<p>若采用const正确（const-correct）的方法，每个返回某个成员的指针（或多个指向成员的指针）的const函数必须返回const指针。在实践中，很不幸这样的做法将导致无法使用的API：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QGraphicsScene scene;<br />
// … populate scene</p>
<p>foreach (const QGraphicsItem *item, scene.items()) {<br />
    item-&gt;setPos(qrand() % 500, qrand() % 500); // doesn&#8217;t compile! item is a const pointer<br />
}<br />
[/code]</p>
<p><code>QGraphicsScene::items()</code>是一个const函数，顺着思考看起来这个函数只应该返回const指针。</p>
<p>在Qt中，我们几乎只有非const的使用模式。我们选择的是实用路子： 相比滥用非const指针返回类型带来的问题，返回const指针更可能招致过分使用const_cast的问题。</p>
<h3>4.3.4 返回值：按值返回 还是 按const引用返回？</h3>
<p>若返回的是对象的拷贝，那么返回const引用是更直接的方案； 然而，这样的做法限制了后面想要对这个类的重构（refactor）。 （以<code>d-point</code>的典型做法（idiom）为例，我们可以在任何时候改变Qt类在内存表示（memory representation）；但却不能在不破坏二进制兼容性的情况下把改变函数的签名，返回值从<code>const QFoo &amp;</code>变为<code>QFoo</code>。） 基于这个原因，除去对运行速度敏感（speed is critical）而重构不是问题的个别情形（例如，<code>QList::at()</code>），我们一般返回<code>QFoo</code>而不是<code>const QFoo &amp;</code>。</p>
<blockquote><p>【译注】：参看《Effective C++》中条款23：Don&#8217;t try to return a reference when you must return an object</p></blockquote>
<h3>4.4.5 const vs. 对象的状态</h3>
<p>const正确性（Const correctness）的问题就像C圈子中vi与emacs的讨论，因为这个话题在很多地方都存在分歧（比如基于指针的函数）。</p>
<p>但通用准则是const函数不能改变类的可见状态。『状态』的意思是『自身以及涉及的职责』。这并不是指非const函数能够改变自身的私有成员，也不是指const函数改变不了。而是指函数是活跃的并存在可见的副作用（visible side effects）。const函数一般没有任何可见的副作用，比如：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QSize size = widget-&gt;sizeHint(); // const<br />
widget-&gt;move(10, 10); // not const<br />
[/code]</p>
<p>代理（delegate）负责在其它对象上绘制内容。 它的状态包括它的职责，因此包括在哪个对象做绘制这样的状态。 调用它的绘画行为必然会有副作用； 它改变了它绘制所在设备的外观（及其所关联的状态）。鉴于这些，<code>paint()</code>作为const函数并不合理。 进一步说，任何<code>paint()</code>或<code>QIcon</code>的<code>paint()</code>的视图函数是const函数也不合理。 没有人会从内部的const函数去调用<code>QIcon::paint()</code>，除非他想显式地绕开const这个特性。 如果是这种情况，使用const_cast会更好。</p>
<p>[code language=&#8221;cpp&#8221;]<br />
// QAbstractItemDelegate::paint is const<br />
void QAbstractItemDelegate::paint(QPainter **painter, const QStyleOptionViewItem &amp;option, const QModelIndex &amp;index) const</p>
<p>// QGraphicsItem::paint is not const<br />
void QGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem option, QWidget *widget)<br />
[/code]</p>
<p>const关键字并不能按你期望的样子起作用。应该考虑将其移除而不是去重载const/非const函数。</p>
<h1>5. API的语义和文档</h1>
<p>当传值为<code>-1</code>的参数给函数，函数会是什么行为？有很多类似的问题……</p>
<p>是警告、致命错误还是其它？</p>
<p>API需要的是质量保证。API第一个版本一定是不对的；必须对其进行测试。 以阅读使用API的代码的方式编写用例，且验证这样代码是可读的。</p>
<p>还有其他的验证方法，比如</p>
<ul>
<li>让别人使用API（看了文档或是先不看文档都可以）</li>
<li>给类写文档（包含类的概述和每个函数）</li>
</ul>
<h1>6. 命名的艺术</h1>
<p>命名很可能是API设计中最重要的一个问题。类应该叫什么名字？成员函数应该叫什么名字？</p>
<h2>6.1 通用的命名规则</h2>
<p>有几个规则对于所有类型的命名都等同适用。第一个，之前已经提到过，不要使用缩写。即使是明显的缩写，比如把<code>previous</code>缩写成<code>prev</code>，从长远来看是回报是负的，因为用户必须要记住缩写词的含义。</p>
<p>如果API本身没有一致性，之后事情自然就会越来越糟；例如，Qt 3 中同时存在<code>activatePreviousWindow()</code>与<code>fetchPrev()</code>。恪守『不缩写』规则更容易地创建一致性的API。</p>
<p>另一个时重要但更微妙的准则是在设计类时应该保持子类名称空间的干净。在Qt 3中，此项准则并没有一直遵循。以<code>QToolButton</code>为例对此进行说明。如果调用<code>QToolButton</code>的 <code>name()</code>、<code>caption()</code>、<code>text()</code>或者<code>textLabel()</code>，你觉得会返回什么？用Qt设计器在<code>QToolButton</code>上自己先试试吧：</p>
<ul>
<li><code>name</code>属性是继承自<code>QObject</code>，返回内部的对象名称，用于调试和测试。</li>
<li><code>caption</code>属性继承自<code>QWidget</code>，返回窗口标题，对<code>QToolButton</code>来说毫无意义，因为它在创建的时候parent就存在了。</li>
<li><code>text</code>函数继承自<code>QButton</code>，一般用于按钮。当<code>useTextLabel</code>不为<code>true</code>，才用这个属性。</li>
<li><code>textLabel</code>属性在<code>QToolButton</code>内声明，当<code>useTextLabel</code>为<code>true</code>时显示在按钮上。</li>
</ul>
<p>为了可读性，在Qt 4中<code>QToolButton</code>的<code>name</code>属性改成了<code>objectName</code>，<code>caption</code>改成了<code>windowTitle</code>，删除了<code>textLabel</code>属性因为和<code>text</code>属性相同。</p>
<p>当你找不到好的命名时，写文档也是个很好方法：要做的就是尝试为各个条目（item）（如类、方法、枚举值等等）写文档，并用写下的第一句话作为启发。如果找不到一个确切的命名，往往说明这个条目是不该有的。如果所有尝试都失败了，并且你坚信这个概念是合理的，那么就发明一个新名字。像widget、event、focus和buddy这些命名就是在这一步诞生的。</p>
<blockquote><p>【译注】：写文档是一个非常好的习惯。写文档的过程其实就是在帮你梳理你的编程思路。很多时候，文档写着写着你就会发现要去改代码去了。除了上述的好处多，写文档还有更多的好处。比如，在写文档的过程中，你发现文字描述过于复杂了，这表明着你的代码或逻辑是复杂的，这就倒逼你去重构你的代码。所以 —— <strong>写文档其实就是写代码</strong>。</p></blockquote>
<h2>6.2 类的命名</h2>
<p>识别出类所在的分组，而不是为每个类都去找个完美的命名。例如，所有Qt 4的能感知模型（model-aware）的item view，类后缀都是<code>View</code>（<code>QListView</code>、<code>QTableView</code>、<code>QTreeView</code>），而相应的基于item（item-based）的类后缀是<code>Widget</code>（<code>QListWidget</code>、<code>QTableWidget</code>、<code>QTreeWidget</code>）。</p>
<h2>6.3 枚举类型及其值的命名</h2>
<p>声明枚举类型时，需要记住在C++中枚举值在使用时不会带上类型（与Java、C#不同）。下面的例子演示了枚举值命名得过于通用的危害：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
namespace Qt<br />
{<br />
    enum Corner { TopLeft, BottomRight, &#8230; };<br />
    enum CaseSensitivity { Insensitive, Sensitive };<br />
    &#8230;<br />
};</p>
<p>tabWidget-&gt;setCornerWidget(widget, Qt::TopLeft);<br />
str.indexOf(&quot;$(QTDIR)&quot;, Qt::Insensitive);<br />
[/code]</p>
<p>在最后一行，<code>Insensitive</code>是什么意思？命名枚举类型的一个准则是在枚举值中至少重复此枚举类型名中的一个元素：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
namespace Qt<br />
{<br />
    enum Corner { TopLeftCorner, BottomRightCorner, &#8230; };<br />
    enum CaseSensitivity { CaseInsensitive, CaseSensitive };<br />
    &#8230;<br />
};</p>
<p>tabWidget-&gt;setCornerWidget(widget, Qt::TopLeftCorner);<br />
str.indexOf(&quot;$(QTDIR)&quot;, Qt::CaseInsensitive);<br />
[/code]</p>
<p>当对枚举值进行或运算并作为某种标志（flag）时，传统的做法是把或运算的结果保存在int型的值中，但这不是类型安全的。Qt 4提供了一个模板类<code>QFlags</code>，其中的<code>T</code>是枚举类型。为了方便使用，Qt用<code>typedef</code>重新定义了<code>QFlag</code>类型，所以可以用<code>Qt::Alignment</code>代替<code>QFlags</code>。</p>
<p>习惯上，枚举类型命名用单数形式（因为它一次只能『持有』一个flag），而持有多个『flag』的类型用复数形式，例如：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
enum RectangleEdge { LeftEdge, RightEdge, &#8230; };<br />
typedef QFlags&lt;RectangleEdge&gt; RectangleEdges;<br />
[/code]</p>
<p>在某些情形下，持有多个『flag』的类型命名用单数形式。对于这种情况，持有的枚举类型名称要求是以<code>Flag</code>为后缀：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
enum AlignmentFlag { AlignLeft, AlignTop, &#8230; };<br />
typedef QFlags&lt;AlignmentFlag&gt; Alignment;<br />
[/code]</p>
<h2>6.4 函数和参数的命名</h2>
<p>函数命名的第一准则是可以从函数名看出来此函数是否有副作用。在Qt 3中，const函数<code>QString::simplifyWhiteSpace()</code>违反了此准则，因为它返回了一个<code>QString</code>而不是按名称暗示的那样，改变调用它的<code>QString</code>对象。在Qt 4中，此函数重命名为<code>QString::simplified()</code>。</p>
<p>虽然参数名不会出现在使用API的代码中，但是它们给程序员提供了重要信息。因为现代的IDE都会在写代码时显示参数名称，所以值得在头文件中给参数起一个恰当的名字并在文档中使用相同的名字。</p>
<h2>6.5 布尔类型的getter与setter方法的命名</h2>
<p>为<code>bool</code>属性的getter和setter方法命名总是很痛苦。getter应该叫做<code>checked()</code>还是<code>isChecked()</code>？<code>scrollBarsEnabled()</code>还是<code>areScrollBarEnabled()</code>？</p>
<p>Qt 4中，我们套用以下准则为getter命名：</p>
<ul>
<li>形容词以<code>is</code>为前缀，例子：
<ul>
<li><code>isChecked()</code></li>
<li><code>isDown()</code></li>
<li><code>isEmpty()</code></li>
<li><code>isMovingEnabled()</code></li>
</ul>
</li>
<li>然而，修饰名词的形容词没有前缀：
<ul>
<li><code>scrollBarsEnabled()</code>，而不是<code>areScrollBarsEnabled()</code></li>
</ul>
</li>
<li>动词没有前缀，也不使用第三人称(<code>-s</code>)：
<ul>
<li><code>acceptDrops()</code>，而不是<code>acceptsDrops()</code></li>
<li><code>allColumnsShowFocus()</code></li>
</ul>
</li>
<li>名词一般没有前缀：
<ul>
<li><code>autoCompletion()</code>，而不是<code>isAutoCompletion()</code></li>
<li><code>boundaryChecking()</code></li>
</ul>
</li>
<li>有的时候，没有前缀容易产生误导，这种情况下会加上<code>is</code>前缀：
<ul>
<li><code>isOpenGLAvailable()</code>，而不是<code>openGL()</code></li>
<li><code>isDialog()</code>，而不是<code>dialog()</code><br />
（一个叫做<code>dialog()</code>的函数，一般会被认为是返回<code>QDialog</code>。）</li>
</ul>
</li>
</ul>
<p>setter的名字由getter衍生，去掉了前缀后在前面加上了<code>set</code>；例如，<code>setDown()</code>与<code>setScrollBarsEnabled()</code>。</p>
<h1>7. 避免常见陷阱</h1>
<h2>7.1 简化的陷阱</h2>
<p>一个常见的误解是：实现需要写的代码越少，API就设计得越好。应该记住：代码只会写上几次，却要被反复阅读并理解。例如：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QSlider *slider = new QSlider(12, 18, 3, 13, Qt::Vertical, 0, &quot;volume&quot;);<br />
[/code]</p>
<p>这段代码比下面的读起来要难得多（甚至写起来也更难）：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QSlider *slider = new QSlider(Qt::Vertical);<br />
slider-&gt;setRange(12, 18);<br />
slider-&gt;setPageStep(3);<br />
slider-&gt;setValue(13);<br />
slider-&gt;setObjectName(&quot;volume&quot;);<br />
[/code]</p>
<blockquote><p>【译注】：在有IDE的自动提示的支持下，后者写起来非常方便，而前者还需要看相应的文档。</p></blockquote>
<h2>7.2 布尔参数的陷阱</h2>
<p>布尔类型的参数总是带来无法阅读的代码。给现有的函数增加一个<code>bool</code>型的参数几乎永远是一种错误的行为。仍以Qt为例，<code>repaint()</code>有一个<code>bool</code>类型的可选参数用于指定背景是否被擦除。可以写出这样的代码：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
widget-&gt;repaint(false);<br />
[/code]</p>
<p>初学者很可能是这样理解的，『不要重新绘制！』，能有多少Qt用户真心知道下面3行是什么意思：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
widget-&gt;repaint();<br />
widget-&gt;repaint(true);<br />
widget-&gt;repaint(false);<br />
[/code]</p>
<p>更好的API设计应该是这样的：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
widget-&gt;repaint();<br />
widget-&gt;repaintWithoutErasing();<br />
[/code]</p>
<p>在Qt 4中，我们通过移除了重新绘制（repaint）而不擦除widget的能力来解决了此问题。Qt 4的双缓冲使这种特性被废弃。</p>
<p>还有更多的例子：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
widget-&gt;setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding, true);<br />
textEdit-&gt;insert(&quot;Where&#8217;s Waldo?&quot;, true, true, false);<br />
QRegExp rx(&quot;moc_***.c??&quot;, false, true);<br />
[/code]</p>
<p>一个明显的解决方案是<code>bool</code>类型改成枚举类型。我们在Qt 4的<code>QString</code>中就是这么做的。对比效果如下：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
str.replace(&quot;%USER%&quot;, user, false);               // Qt 3<br />
str.replace(&quot;%USER%&quot;, user, Qt::CaseInsensitive); // Qt 4<br />
[/code]</p>
<blockquote><p>【译注】：关于这个条目可以看看 CoolShell 这篇文章一些展开的讨论： <a href="https://coolshell.cn/articles/5444.html" rel="nofollow">千万不要把 BOOL 设计成函数参数</a>。</p></blockquote>
<h1>8. 案例研究</h1>
<h2>8.1 <code>QProgressBar</code></h2>
<p>为了展示上文各种准则的实际应用。我们来研究一下Qt 3中<code>QProgressBar</code>的API，并与Qt 4中对应的API作比较。在Qt 3中：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
class QProgressBar : public QWidget<br />
{<br />
    &#8230;<br />
public:<br />
    int totalSteps() const;<br />
    int progress() const;</p>
<p>    const QString &amp;progressString() const;<br />
    bool percentageVisible() const;<br />
    void setPercentageVisible(bool);</p>
<p>    void setCenterIndicator(bool on);<br />
    bool centerIndicator() const;</p>
<p>    void setIndicatorFollowsStyle(bool);<br />
    bool indicatorFollowsStyle() const;</p>
<p>public slots:<br />
    void reset();<br />
    virtual void setTotalSteps(int totalSteps);<br />
    virtual void setProgress(int progress);<br />
    void setProgress(int progress, int totalSteps);</p>
<p>protected:<br />
    virtual bool setIndicator(QString &amp;progressStr,<br />
                              int progress,<br />
                              int totalSteps);<br />
    &#8230;<br />
};<br />
[/code]</p>
<p>该API相当的复杂和不一致；例如，<code>reset()</code>、<code>setTotalSteps()</code>、<code>setProgress()</code>是紧密联系的，但方法的命名并没明确地表达出来。</p>
<p>改善此API的关键是抓住<code>QProgressBar</code>与Qt 4的<code>QAbstractSpinBox</code>及其子类<code>QSpinBox</code>、<code>QSlider</code>、<code>QDail</code>之间的相似性。怎么做？把<code>progress</code>、<code>totalSteps</code>替换为<code>minimum</code>、<code>maximum</code>和<code>value</code>。增加一个<code>valueChanged()</code>消息，再增加一个<code>setRange()</code>函数。</p>
<p>进一步可以观察到<code>progressString</code>、<code>percentage</code>与<code>indicator</code>其实是一回事，即是显示在进度条上的文本。通常这个文本是个百分比，但是可通过<code>setIndicator()</code>设置为任何内容。以下是新的API：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
virtual QString text() const;<br />
void setTextVisible(bool visible);<br />
bool isTextVisible() const;<br />
[/code]</p>
<p>默认情况下，显示文本是百分比指示器（percentage indicator），通过重写<code>text()</code>方法来定制行为。</p>
<p>Qt 3的<code>setCenterIndicator()</code>与<code>setIndicatorFollowsStyle()</code>是两个影响对齐方式的函数。他们可被一个<code>setAlignment()</code>函数代替：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
void setAlignment(Qt::Alignment alignment);<br />
[/code]</p>
<p>如果开发者未调用<code>setAlignment()</code>，那么对齐方式由风格决定。对于基于<code>Motif</code>的风格，文字内容在中间显示；对于其他风格，在右侧显示。</p>
<p>下面是改善后的<code>QProgressBar API</code>:</p>
<p>[code language=&#8221;cpp&#8221;]<br />
class QProgressBar : public QWidget<br />
{<br />
    &#8230;<br />
public:<br />
    void setMinimum(int minimum);<br />
    int minimum() const;<br />
    void setMaximum(int maximum);<br />
    int maximum() const;<br />
    void setRange(int minimum, int maximum);<br />
    int value() const;</p>
<p>    virtual QString text() const;<br />
    void setTextVisible(bool visible);<br />
    bool isTextVisible() const;<br />
    Qt::Alignment alignment() const;<br />
    void setAlignment(Qt::Alignment alignment);</p>
<p>public slots:<br />
    void reset();<br />
    void setValue(int value);</p>
<p>signals:<br />
    void valueChanged(int value);<br />
    &#8230;<br />
};<br />
[/code]</p>
<h2>8.2 <code>QAbstractPrintDialog</code> &amp; <code>QAbstractPageSizeDialog</code></h2>
<p>Qt 4.0有2个幽灵类<code>QAbstractPrintDialog</code>和<code>QAbstractPageSizeDialog</code>，作为 <code>QPrintDialog</code>和<code>QPageSizeDialog</code>类的父类。这2个类完全没有用，因为Qt的API没有是<code>QAbstractPrint-</code>或是<code>-PageSizeDialog</code>指针作为参数并执行操作。通过篡改qdoc（Qt文档），我们虽然把这2个类隐藏起来了，却成了无用抽象类的典型案例。</p>
<p>这不是说，<strong><em>好</em></strong> 的抽象是错的，<code>QPrintDialog</code>应该是需要有个工厂或是其它改变的机制 —— 证据就是它声明中的<code>#ifdef QTOPIA_PRINTDIALOG</code>。</p>
<h2>8.3 <code>QAbstractItemModel</code></h2>
<p>关于模型/视图（model/view）问题的细节在相应的文档中已经说明得很好了，但作为一个重要的总结这里还需要强调一下：抽象类不应该仅是所有可能子类的并集（union）。这样『合并所有』的父类几乎不可能是一个好的方案。<code>QAbstractItemModel</code>就犯了这个错误 —— 它实际上就是个<code>QTreeOfTablesModel</code>，结果导致了错综复杂（complicated）的API，而这样的API要让 <strong><em>所有本来设计还不错的子类</em></strong> 去继承。</p>
<p>仅仅增加抽象是不会自动就把API变得更好的。</p>
<h2>8.4 <code>QLayoutIterator</code> &amp; <code>QGLayoutIterator</code></h2>
<p>在Qt 3，创建自定义的布局类需要同时继承<code>QLayout</code>和<code>QGLayoutIterator</code>（命名中的<code>G</code>是指Generic（通用））。<code>QGLayoutIterator</code>子类的实例指针会包装成<code>QLayoutIterator</code>，这样用户可以像和其它的迭代器（iterator）类一样的方式来使用。通过<code>QLayoutIterator</code>可以写出下面这样的代码：</p>
<p>[code language=&#8221;cpp&#8221;]<br />
QLayoutIterator it = layout()-&gt;iterator();<br />
QLayoutItem **child;<br />
while ((child = it.current()) != 0) {<br />
    if (child-&gt;widget() == myWidget) {<br />
        it.takeCurrent();<br />
        return;<br />
    }<br />
    ++it;<br />
}<br />
[/code]</p>
<p>在Qt 4，我们干掉了<code>QGLayoutIterator</code>类（以及用于盒子布局和格子布局的内部子类），转而是让<code>QLayout</code>的子类重写<code>itemAt()</code>、<code>takeAt()</code>和<code>count()</code>。</p>
<h2>8.5 <code>QImageSink</code></h2>
<p>Qt 3有一整套类用来把完成增量加载的图片传递给一个动画 —— <code>QImageSource</code>/<code>Sink</code>/<code>QASyncIO</code>/<code>QASyncImageIO</code>。由于这些类之前只是用于启用动画的<code>QLabel</code>，完全过度设计了（overkill）。</p>
<p>从中得到的教训就是：对于那些未来可能的还不明朗的需求，不要过早地增加抽象设计。当需求真的出现时，比起一个复杂的系统，在简单的系统新增需求要容易得多。<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/5444.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/14.jpg" alt="千万不要把 bool 设计成函数参数" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5444.html" class="wp_rp_title">千万不要把 bool 设计成函数参数</a></li><li ><a href="https://coolshell.cn/articles/4758.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/30.jpg" alt="如何写出无法维护的代码" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4758.html" class="wp_rp_title">如何写出无法维护的代码</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-150x150.jpg" alt="从Gitlab误删除数据库想到的" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_title">从Gitlab误删除数据库想到的</a></li><li ><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-150x150.png" alt="关于高可用的系统" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_title">关于高可用的系统</a></li><li ><a href="https://coolshell.cn/articles/12052.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/29.jpg" alt="Leetcode 编程训练" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12052.html" class="wp_rp_title">Leetcode 编程训练</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/18024.html">API设计原则 – Qt官网的设计实践总结</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/18024.html/feed</wfw:commentRss>
			<slash:comments>26</slash:comments>
		
		
			</item>
		<item>
		<title>AWS 的 S3 故障回顾和思考</title>
		<link>https://coolshell.cn/articles/17737.html</link>
					<comments>https://coolshell.cn/articles/17737.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Fri, 03 Mar 2017 06:20:03 +0000</pubDate>
				<category><![CDATA[业界新闻]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Amazon S3]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[High Availability]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17737</guid>

					<description><![CDATA[<p>继Gitlab的误删除数据事件没几天，“不沉航母” AWS S3 （Simple Storage Service）几天前也“沉”了4个小时，墙外的半个互联网也跟...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17737.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17737.html">AWS 的 S3 故障回顾和思考</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright wp-image-17738" src="https://coolshell.cn/wp-content/uploads/2017/03/Amazon-Web-Services-Down.png" width="360" height="197" srcset="https://coolshell.cn/wp-content/uploads/2017/03/Amazon-Web-Services-Down.png 553w, https://coolshell.cn/wp-content/uploads/2017/03/Amazon-Web-Services-Down-300x164.png 300w, https://coolshell.cn/wp-content/uploads/2017/03/Amazon-Web-Services-Down-494x270.png 494w" sizes="(max-width: 360px) 100vw, 360px" />继<a href="https://coolshell.cn/articles/17680.html" target="_blank">Gitlab的误删除数据事件</a>没几天，“不沉航母” AWS S3 （Simple Storage Service）几天前也“沉”了4个小时，墙外的半个互联网也跟着挂了。如约，按 AWS 惯例，AWS今天给出了一个简单的故障报告《<span class="s1"><a href="https://aws.amazon.com/cn/message/41926/" target="_blank">Summary of the Amazon S3 Service Disruption in the Northern Virginia (US-EAST-1) Region</a>》。这个故障和简单来说和Gitlab一样，也是人员误操作。先简单的说一下这份报中说了什么。</span></p>
<h4>故障原因</h4>
<p>简单来说，这天，有一个 AWS 工程师在调查 <span class="s1">Northern Virginia (US-EAST-1) Region 上 S3 的一个和账务系统相关的问题，这个问题是S3的账务系统变慢了（我估计这个故障在Amazon里可能是Sev2级，Sev2级的故障在Amazon算是比较大的故障，需要很快解决），Oncall的开发工程师（注：Amazon的运维都是由开发工程师来干的，所以Amazon内部嬉称SDE-Software Developer Engineer 为 Someone Do Everything）想移除一个账务系统里的一个子系统下的一些少量的服务器（估计这些服务器上有问题，所以想移掉后重新部署），结果呢，有一条命令搞错了，导致了移除了大量的S3的控制系统。包括两个很重要的子系统：</span></p>
<p><span id="more-17737"></span></p>
<p style="padding-left: 30px;">1）<strong>一个是S3的对象索引服务（Index）</strong>，其中存储了S3对象的metadata和位置信息。这个服务也提供了所有的 GET，LIST，PUT 和DELETE请求。</p>
<p style="padding-left: 30px;">2）<strong>一个是S3的位置服务系统（Placement）</strong>，这个服务提供对象的存储位置和索引服务的系统。这个系统主要是用于处理PUT新对象请求。</p>
<p>这就是为什么S3不可访问的原因。</p>
<p>在后面，AWS也说明了一下故障恢复的过程，其中重点提到了这点——</p>
<p style="padding-left: 30px;">虽然整个S3的是做过充分的故障设计的（注：AWS的七大Design Principle 之一 Design for Failure）—— 就算是最核心的组件或服务出问题了，系统也能恢复。但是，可能是在过去的日子里 S3 太稳定了，所以，AWS 在很长很长一段时间内都没有重启过 S3 的核心服务，而过去这几年，S3 的数据对象存储级数级的成长（S3存了什么样数量级的对象，因为在Amazon工作过，所以多大概知道是个什么数量级，这里不能说，不过，老实说，很惊人的），所以，这两个核心服务在启动时要重建并校验对象索引元数据的完整性，这个过程没想到花了这么长的时候。而Placement服务系统依赖于Index 服务，所以花了更长的时间。</p>
<p>了解过系统底层的技术人员应该都知道这两个服务有多重要，简而言之，这两个系统就像是Unix/Linux文件系统中的inode，或是像HDFS里的node name，如果这些元数据丢失，那么，用户的所有数据基本上来说就等于全丢了。</p>
<p>而要恢复索引系统，就像你的操作系统从异常关机后启动，文件系统要做系统自检那样，硬盘越大，文件越多，这个过程就越慢。</p>
<p>另外，这次，AWS没有使用像以前那样 Outage 的故障名称，用的是 “Increased Error Rate” 这样的东西。我估计是没有把所有这两个服务删除完，估计有些用户是可以用的，有的用户是则不行了。</p>
<h4>后续改进</h4>
<p>在这篇故障简报中，AWS 也提到了下面的这些改进措施——</p>
<p>1）<strong>改进运维操作工具</strong>。对于此次故障的运维工具，有下面改进：</p>
<ul>
<li><strong>让删除服务这个操作变慢一些</strong>（陈皓注：这样错了也可以有时间反悔，相对于一个大规模的分布式系统，这招还是很不错的，至少在系统报警时有也可以挽救）</li>
</ul>
<ul>
<li><strong>加上一个最小资源数限制的SafeGuard</strong>（陈皓注：就是说，任何服务在运行时都应该有一个最小资源数，分布式集群控制系统会强行维护服务正常运行的最小的一个资源数）</li>
</ul>
<ul>
<li>举一反三，Review所有和其它的运维工具，保证他们也相关的检查。</li>
</ul>
<p>2）<strong>改进恢复过程。</strong>对于恢复时间过长的问题，有如下改进：</p>
<ul>
<li><strong>分解现有厚重的重要服务成更小的单元</strong>（在 AWS，Service是大服务，小服务被称之为 Cell），AWS 会把这几个重要的服务重构成 Cell服务。（陈皓注：这应该就是所谓的“微服务”了吧）。这样，服务粒度变小，重启也会快一些，而且还可以减少故障面（原文：blast radius &#8211; 爆炸半径）</li>
</ul>
<ul>
<li><strong>今年内完成对 Index 索引服务的分区计划</strong>。</li>
</ul>
<p>&nbsp;</p>
<h4>相关思考</h4>
<p>下面是我对这一故障的相关思考——</p>
<p>0）<strong>太喜欢像Gitlab和AWS这样的故障公开了</strong>，那怕是一个自己人为的低级错误。不掩盖，不文过饰非，透明且诚恳。Cool!</p>
<p>1）这次事件，还好没有丢失这么重要的数据，不然的话，将是灾难性的。</p>
<p>2）另外，面对在 US-EASE-1 这个老牌 Region 上的海量的对象，而且能在几个小时内恢复，很不容易了。</p>
<p>3）这个事件，再次映证了我在《<a href="https://coolshell.cn/articles/17459.html">关于高可用的系统</a>》中提到的观点：<strong>一个系统的高可用的因素很多，不仅仅只是系统架构，更重要的是——高可用运维</strong>。</p>
<p>4）<strong>对于高可用的运维，平时的故障演习是很重要的。</strong>AWS 平时应该没有相应的故障演习，所以导致要么长期不出故障，一出就出个大的让你措手不及。这点，Facebook就好一些，他们每个季度扔个骰子，随机关掉一个IDC一天。Netflix 也有相关的 Chaos Monkey，我以前在的路透每年也会做一次大规模的故障演练——灾难演习。</p>
<p>5）AWS对于后续的改进可以看出他的技术范儿。可以看到其改进方案是用技术让自己的系统更为的高可用。然后，对比国内的公司对于这样的故障，基本上会是下面这样的画风：</p>
<p style="padding-left: 30px;">a）加上更多更为严格的变更和审批流程，</p>
<p style="padding-left: 30px;">b）使用限制更多的权限系统和审批系统</p>
<p style="padding-left: 30px;">c）使用更多的人来干活（一个人干事，另一个人在旁边看）</p>
<p style="padding-left: 30px;">d）使用更为厚重的测试和发布过程</p>
<p style="padding-left: 30px;">e）惩罚故障人，用价值观教育工程师。</p>
<p>这还是我老生长谈的那句话——<strong>如果你是一个技术公司，你就会更多的相信技术而不是管理。相信技术会用技术来解决问题，相信管理，那就只会有制度、流程和价值观来解决问题</strong>。（注意：这里我并没有隔离技术和管理，只是更为倾向于用技术解决问题）</p>
<p><strong>最后，你是要建一个 “高可用的技术系统” ，还是一个 “高用的管理系统”？ ;-)</strong></p>
<p>（全文完）</p>
<p>&nbsp;<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-150x150.jpg" alt="从Gitlab误删除数据库想到的" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_title">从Gitlab误删除数据库想到的</a></li><li ><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-150x150.png" alt="关于高可用的系统" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_title">关于高可用的系统</a></li><li ><a href="https://coolshell.cn/articles/22422.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2023/05/monolith.microservices-150x150.png" alt="是微服务架构不香还是云不香？" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22422.html" class="wp_rp_title">是微服务架构不香还是云不香？</a></li><li ><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x-150x150.png" alt="我做系统架构的一些原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_title">我做系统架构的一些原则</a></li><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/07/cache-150x150.png" alt="缓存更新的套路" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_title">缓存更新的套路</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17737.html">AWS 的 S3 故障回顾和思考</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17737.html/feed</wfw:commentRss>
			<slash:comments>56</slash:comments>
		
		
			</item>
		<item>
		<title>从Gitlab误删除数据库想到的</title>
		<link>https://coolshell.cn/articles/17680.html</link>
					<comments>https://coolshell.cn/articles/17680.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Thu, 02 Feb 2017 08:11:28 +0000</pubDate>
				<category><![CDATA[技术新闻]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Gitlab]]></category>
		<category><![CDATA[High Availability]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[分布式]]></category>
		<category><![CDATA[程序员]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17680</guid>

					<description><![CDATA[<p>昨天，Gitlab.com发生了一个大事，某同学误删了数据库，这个事看似是个低级错误，不过，因为Gitlab把整个过程的细节都全部暴露出来了，所以，可以看到很多...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17680.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17680.html">从Gitlab误删除数据库想到的</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright wp-image-17685" src="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600.jpg" width="300" height="215" srcset="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600.jpg 439w, https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-300x215.jpg 300w, https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-377x270.jpg 377w" sizes="(max-width: 300px) 100vw, 300px" />昨天，Gitlab.com发生了一个大事，某同学误删了数据库，这个事看似是个低级错误，不过，因为Gitlab把整个过程的细节都全部暴露出来了，所以，可以看到很多东西，而对于类似这样的事情，我自己以前也干过，而在最近的两公司中我也见过（Amazon中见过一次，阿里中见过至少四次），正好通过这个事来说说一下自己的一些感想和观点吧。<strong>我先放个观点：你觉得有备份系统就不会丢数据了吗？</strong></p>
<h4>事件回顾</h4>
<p>整个事件的回顾Gitlab.com在第一时间就放到了<a href="https://docs.google.com/document/d/1GCK53YDcBWQveod9kfzW-VCxIABGiryG7_z_6jHdVik/pub" target="_blank">Google Doc上</a>，事后，又发了<a href="https://about.gitlab.com/2017/02/01/gitlab-dot-com-database-incident/" target="_blank">一篇Blog</a>来说明这个事，在这里，我简单的回顾一下这个事件的过程。</p>
<p>首先，一个叫YP的同学在给gitlab的线上数据库做一些负载均衡的工作，在做这个工作时的时候突发了一个情况，Gitlab被DDoS攻击，数据库的使用飙高，在block完攻击者的IP后，发现有个staging的数据库(db2.staging)已经落后生产库4GB的数据，于是YP同学在Fix这个staging库的同步问题的时候，发现db2.staging有各种问题都和主库无法同步，在这个时候，YP同学已经工作的很晚了，在尝试过多个方法后，发现db2.staging都hang在那里，无法同步，于是他想把db2.staging的数据库删除了，这样全新启动一个新的复制，结果呢，删除数据库的命令错误的敲在了生产环境上（db1.cluster），结果导致整个生产数据库被误删除。（<strong>陈皓注：这个失败基本上就是 “工作时间过长” + “在多数终端窗口中切换中迷失掉了”</strong>）</p>
<p><span id="more-17680"></span></p>
<p>在恢复的过程中，他们发现只有db1.staging的数据库可以用于恢复，而其它的5种备份机制都不可用，第一个是数据库的同步，没有同步webhook，第二个是对硬盘的快照，没有对数据库做，第三个是用pg_dump的备份，发现版本不对（用9.2的版本去dump 9.6的数据）导致没有dump出数据，第四个S3的备份，完全没有备份上，第五个是相关的备份流程是问题百出的，只有几个粗糙的人肉的脚本和糟糕的文档，也就是说，不但是是人肉的，而且还是完全不可执行的。（<strong>陈皓注：就算是这些备份机制都work，其实也有问题，因为这些备份大多数基本上都是24小时干一次，所以，要从这些备份恢复也一定是是要丢数据的了，只有第一个数据库同步才会实时一些</strong>）</p>
<p>最终，gitlab从db1.staging上把6个小时前的数据copy回来，结果发现速度非常的慢，备份结点只有60Mbits/S，拷了很长时间（<strong>陈皓注：为什么不把db1.staging给直接变成生产机？因为那台机器的性能很差</strong>）。数据现在的恢复了，不过，因为恢复的数据是6小时前的，所以，有如下的数据丢失掉了：</p>
<ul class="ul1">
<li class="li1"><span class="s2">粗略估计，有4613 的项目， 74 forks,  和 350 imports 丢失了；但是，因为Git仓库还在，所以，可以从Git仓库反向推导数据库中的数据，但是，项目中的issues等就完全丢失了。</span></li>
<li class="li1"><span class="s2">大约有±4979 提交记录丢失了（陈皓注：估计也可以用git仓库中反向恢复）。</span></li>
<li class="li1"><span class="s2">可能有 707  用户丢失了，这个数据来自Kibana的日志。</span></li>
<li class="li2"><span class="s4">在1月31日17:20 后的Webhooks 丢失了。</span></li>
</ul>
<p>因为Gitlab把整个事件的细节公开了出来，所以，也得到了很多外部的帮助，2nd Quadrant的CTO &#8211; <span class="s1"><a href="https://www.linkedin.com/in/simonat2ndquadrantdotcom" target="_blank">Simon Riggs</a> 在他的blog上也发布文章 <a href="http://blog.2ndquadrant.com/dataloss-at-gitlab/" target="_blank">Dataloss at Gitlab </a>给了一些非常不错的建议：</span></p>
<ul>
<li>关于PostgreSQL 9.6的数据同步hang住的问题，可能有一些Bug，正在fix中。</li>
<li>PostgreSQL有4GB的同步滞后是正常的，这不是什么问题。</li>
<li>正常的停止从结点，会让主结点自动释放WALSender的链接数，所以，不应该重新配置主结点的 max_wal_senders 参数。但是，停止从结点时，主结点的复数连接数不会很快的被释放，而新启动的从结点又会消耗更多的链接数。他认为，Gitlab配置的32个链接数太高了，通常来说，2到4个就足够了。</li>
<li>另外，之前gitlab配置的max_connections=8000太高了，现在降到2000个是合理的。</li>
<li>pg_basebackup 会先在主结点上建一个checkpoint，然后再开始同步，这个过程大约需要4分钟。</li>
<li>手动的删除数据库目录是非常危险的操作，这个事应该交给程序来做。推荐使用刚release 的 <a href="https://www.2ndquadrant.com/en/resources/repmgr/" target="_blank">repmgr</a></li>
<li>恢复备份也是非常重要的，所以，也应该用相应的程序来做。推荐使用 <a href="https://www.2ndquadrant.com/en/resources/barman/" target="_blank">barman</a> （其支持S3）</li>
<li>测试备份和恢复是一个很重要的过程。</li>
</ul>
<p>看这个样子，估计也有一定的原因是——Gitlab的同学对PostgreSQL不是很熟悉。</p>
<p>随后，Gitlab在其网站上也开了一系列的issues，其issues列表在这里 <a href="https://gitlab.com/gitlab-com/www-gitlab-com/issues/1108" target="_blank">Write post-mortem</a> (这个列表可能还会在不断更新中)</p>
<ul class="ul1">
<li class="li1"><span class="s1"><span class="s2"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1094">infrastructure#1094</a> &#8211; Update PS1 across all hosts to more clearly differentiate between hosts and environments</span></span></li>
<li class="li1"><span class="s3"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1095">infrastructure#1095</a> &#8211; Prometheus monitoring for backups</span></span></li>
<li class="li1"><span class="s3"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1096">infrastructure#1096</a> &#8211; Set PostgreSQL&#8217;s max_connections to a sane value</span></span></li>
<li class="li1"><span class="s3"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1097">infrastructure#1097</a> &#8211; Investigate Point in time recovery &amp; continuous archiving for PostgreSQL</span></span></li>
<li class="li1"><span class="s3"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1098">infrastructure#1098</a> &#8211; Hourly LVM snapshots of the production databases</span></span></li>
<li class="li1"><span class="s3"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1099">infrastructure#1099</a> &#8211; Azure disk snapshots of production databases</span></span></li>
<li class="li1"><span class="s3"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1100">infrastructure#1100</a> &#8211; Move staging to the ARM environment</span></span></li>
<li class="li1"><span class="s3"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1101">infrastructure#1101</a> &#8211; Recover production replica(s)</span></span></li>
<li class="li1"><span class="s3"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1102">infrastructure#1102</a> &#8211; Automated testing of recovering PostgreSQL database backups</span></span></li>
<li class="li1"><span class="s3"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1103">infrastructure#1103</a> &#8211; Improve PostgreSQL replication documentation/runbooks</span></span></li>
<li class="li1"><span class="s3"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1104">infrastructure#1104</a> &#8211; Kick out SSH users inactive for N minutes</span></span></li>
<li class="li2"><span class="s5"><span class="s4"><a href="https://gitlab.com/gitlab-com/infrastructure/issues/1105">infrastructure#1105</a> &#8211; Investigate pgbarman for creating PostgreSQL backups</span></span></li>
</ul>
<p>从上面的这个列表中，我们可以看到一些改进措施了。挺好的，不过我觉得还不是很够。</p>
<h4>相关的思考</h4>
<p>因为类似这样的事，我以前也干过（误删除过数据库，在多个终端窗口中迷失掉了自己所操作的机器……），而且我在amazon里也见过一次，在阿里内至少见过四次以上（在阿里人肉运维的误操作的事故是我见过最多的），但是我无法在这里公开分享，私下可以分享。在这里，我只想从非技术和技术两个方面分享一下我的经验和认识。</p>
<h5>技术方面</h5>
<p><strong>人肉运维</strong></p>
<p>一直以来，我都觉得直接到生产线上敲命令是一种非常不好的习惯。我认为，<strong>一个公司的运维能力的强弱和你上线上环境敲命令是有关的，你越是喜欢上线敲命令你的运维能力就越弱，越是通过自动化来处理问题，你的运维能力就越强</strong>。理由如下：</p>
<p style="padding-left: 30px;">其一，如果说对代码的改动都是一次发布的话，那么，对生产环境的任何改动（包括硬件、操作系统、网络、软件配置……），也都算是一次发布。那么这样的发布就应该走发布系统和发布流程，要被很好的测试、上线和回滚计划。关键是，走发布过程是可以被记录、追踪和回溯的，而在线上敲命令是完全无法追踪的。没人知道你敲了什么命令。</p>
<p style="padding-left: 30px;">其二，真正良性的运维能力是——人管代码，代码管机器，而不是人管机器。你敲了什么命令没人知道，但是你写个工具做变更线上系统，这个工具干了什么事，看看工具的源码就知道了。</p>
<p>另外、有人说，以后不要用rm了，要用mv，还有人说，以后干这样的事时，一个人干，另一个人在旁边看，还有人说，要有一个checklist的强制流程做线上的变更，还有人说要增加一个权限系统。我觉得，这些虽然可以work，但是依然不好，再由如下：</p>
<p style="padding-left: 30px;">其一、如果要解决一个事情需要加更多的人来做的事，那这事就做成劳动密集型了。今天我们的科技就是在努力消除人力成本，而不是在增加人力成本。而做为一个技术人员，解决问题的最好方式是努力使用技术手段，而不是使用更多的人肉手段。<strong>人类区别于动物的差别就是会发明和使用现代化的工具，而不是使用更多的人力</strong>。另外，<strong>这不仅仅因为是，人都是会有这样或那样的问题（疲惫、情绪化、急燥、冲动……），而机器是单一无脑不知疲惫的，更是因为，机器干活的效率和速度是比人肉高出N多倍的</strong>。</p>
<p style="padding-left: 30px;">其二、增加一个权限系统或是别的一个watch dog的系统完全是在开倒车，权限系统中的权限谁来维护和审批？不仅仅是因为多出来的系统需要多出来的维护，关键是这个事就没有把问题解决在root上。除了为社会解决就业问题，别无好处，故障依然会发生，有权限的人一样会误操作。对于Gitlab这个问题，正如2nd Quadrant的CTO建议的那样，你需要的是一个自动化的备份和恢复的工具，而不是一个权限系统。</p>
<p style="padding-left: 30px;">其三、像使用mv而不rm，搞一个checklist和一个更重的流程，更糟糕。这里的逻辑很简单，因为，1）这些规则需要人去学习和记忆，本质上来说，你本来就不相信人，所以你搞出了一些规则和流程，而这些规则和流程的执行，又依赖于人，换汤不换药，2）另外，<strong>写在纸面上的东西都是不可执行的，可以执行的就是只有程序，所以，为什么不把checklist和流程写成代码呢</strong>？（你可能会说程序也会犯错，是的，程序的错误是consistent，而人的错误是inconsistent）</p>
<p>最关键的是，<strong>数据丢失有各种各样的情况，不单单只是人员的误操作，比如，掉电、磁盘损坏、中病毒等等，在这些情况下，你设计的那些想流程、规则、人肉检查、权限系统、checklist等等统统都不管用了，这个时候，你觉得应该怎么做呢？是的，你会发现，你不得不用更好的技术去设计出一个高可用的系统！别无它法。</strong></p>
<h4>关于备份</h4>
<p>一个系统是需要做数据备份的，但是，你会发现，<strong>Gitlab这个事中，就算所有的备份都可用，也不可避免地会有数据的丢失，或是也会有很多问题</strong>。理由如下：</p>
<p style="padding-left: 30px;">1）备份通常来说都是周期性的，所以，如果你的数据丢失了，从你最近的备份恢复数据里，从备份时间到故障时间的数据都丢失了。</p>
<p style="padding-left: 30px;">2）备份的数据会有版本不兼容的问题。比如，在你上次备份数据到故障期间，你对数据的scheme做了一次改动，或是你对数据做了一些调整，那么，你备份的数据就会和你线上的程序出现不兼容的情况。</p>
<p style="padding-left: 30px;">3）有一些公司或是银行有灾备的数据中心，但是灾备的数据中心没有一天live过。等真正灾难来临需要live的时候，你就会发现，各种问题让你live不起来。你可以读一读几年前的这篇报道好好感受一下《<a href="http://finance.sina.com.cn/money/bank/20140804/091219903553.shtml" target="_blank">以史为鉴 宁夏银行7月系统瘫痪最新解析</a>》</p>
<p>所以，在灾难来临的时候，你会发现你所设计精良的“备份系统”或是“灾备系统”就算是平时可以工作，但也会导致数据丢失，而且可能长期不用的备份系统很难恢复（比如应用、工具、数据的版本不兼容等问题）。</p>
<p>我之前写过一篇《<a href="https://coolshell.cn/articles/10910.html" target="_blank">分布式系统的事务处理</a>》，你还记得下面这张图吗？看看 Data Loss 那一行的，在Backups, Master/Slave 和 Master/Master的架构下，都是会丢的。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-10942" src="https://coolshell.cn/wp-content/uploads/2014/01/Transaction-Across-DataCenter.jpg" alt="" width="566" height="255" srcset="https://coolshell.cn/wp-content/uploads/2014/01/Transaction-Across-DataCenter.jpg 566w, https://coolshell.cn/wp-content/uploads/2014/01/Transaction-Across-DataCenter-300x135.jpg 300w" sizes="(max-width: 566px) 100vw, 566px" /></p>
<p>所以说，<strong>如果你要让你的备份系统随时都可以用，那么你就要让它随时都Live着</strong>，而随时都Live着的多结点系统，基本上就是一个分布式的高可用的系统。因为<strong>，数据丢失的原因有很多种，比如掉电、磁盘损坏、中病毒等等，而那些流程、规则、人肉检查、权限系统、checklist等等都只是让人不要误操作，都不管用，这个时候，你不得不用更好的技术去设计出一个高可用的系统！别无它法。（重要的事，得再说一篇）</strong></p>
<p>另外，你可以参看我的另一篇《<a href="https://coolshell.cn/articles/17459.html" target="_blank">关于高可用系统</a>》，这篇文章中以MySQL为例，数据库的replication也只能达到 两个9。</p>
<p><strong>AWS 的 S3 的的高可用是4个加11个9的持久性（</strong>所谓11个9的持久性durability，AWS是这样定义的，如果你存了1万个对象，那么丢一个的时间是1000万年<strong>），这意味着，不仅仅只是硬盘坏，机器掉电，整个机房挂了，其保证可以承受有两个设施的数据丢失，数据还是可用的。试想，如果你把数据的可用性通过技术做到了这个份上，那么，你还怕被人误删一个结点上的数据吗？</strong></p>
<h5>非技术方面</h5>
<p><strong>故障反思</strong></p>
<p>一般说来，故障都需要反思，在Amazon，S2以上的故障都需要写COE（Correction of Errors），其中一节就是需要Ask 5 Whys，我发现在Gitlab的故障回顾的blog中第一段中也有说要在今天写个Ask 5 Whys。关于Ask 5 Whys，其实并不是亚马逊的玩法，这还是算一个业内常用的玩法，也就是说不断的为自己为为什么，直到找到问题的概本原因，这会逼着所有的当事人去学习和深究很多东西。在Wikipedia上有相关的词条 <a href="https://en.wikipedia.org/wiki/5_Whys" target="_blank">5 Whys</a>，其中罗列了14条规则：</p>
<ol>
<li>你需要找到正确的团队来完成这个故障反思。</li>
<li>使用纸或白板而不是电脑。</li>
<li>写下整个问题的过程，确保每个人都能看懂。</li>
<li>区别原因和症状。</li>
<li>特别注意因果关系。</li>
<li>说明Root Cause以及相关的证据。</li>
<li>5个为什么的答案需要是精确的。</li>
<li>寻找问题根源的步骤，而不是直接跳到结论。</li>
<li>要基础客观的事实、数据和知识。</li>
<li>评估过程而不是人。</li>
<li>千万不要把“人为失误”或是“工作不注意”当成问题的根源。</li>
<li>培养信任和真诚的气氛和文化。</li>
<li>不断的问“为什么”直到问题的根源被找到。这样可以保证同一个坑不会掉进去两次。<sup id="cite_ref-7" class="reference"></sup></li>
<li>当你给出“为什么”的答案时，你应该从用户的角度来回答。</li>
</ol>
<p><strong>工程师文化</strong></p>
<p>上述的这些观点，其实，我在我的以住的博客中都讲过很多遍了，你可以参看《<a href="https://coolshell.cn/articles/17497.html" target="_blank">什么是工程师文化？</a>》以及《<a href="https://coolshell.cn/articles/11656.html" target="_blank">开发团队的效率</a>》。其实，说白了就是这么一个事——<strong>如果你是一个技术公司，你就会更多的相信技术而不是管理。相信技术会用技术来解决问题，相信管理，那就只会有制度、流程和价值观来解决问题</strong>。</p>
<p>这个道理很简单，<strong>数据丢失有各种各样的情况，不单单只是人员的误操作，比如，掉电、磁盘损坏、中病毒等等，在这些情况下，你设计的那些流程、规则、人肉检查、权限系统、checklist等等统统都不管用，这个时候，你觉得应该怎么做呢？是的，你会发现，你不得不用更好的技术去设计出一个高可用的系统！别无它法。（重要的事得说三遍）</strong></p>
<p><strong>事件公开</strong></p>
<p>很多公司基本上都是这样的套路，首先是极力掩盖，如果掩盖不了了就开始撒谎，撒不了谎了，就“文过饰非”、“避重就轻”、“转移视线”。然而，面对危机的最佳方法就是——“多一些真诚，少一些套路”，<strong>所谓的“多一些真诚”的最佳实践就是——“透明公开所有的信息”</strong>，Gitlab此次的这个事给大家树立了非常好的榜样。AWS也会把自己所有的故障和细节都批露出来。</p>
<p><strong>事情本来就做错了，而公开所有的细节，会让大众少很多猜测的空间，有利于抵制流言和黑公关，同时，还会赢得大众的理解和支持</strong>。看看Gitlab这次还去YouTube上直播整个修复过程，是件很了不起的事，大家可以到他们的blog上看看，对于这样的透明和公开，一片好评。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-150x150.png" alt="关于高可用的系统" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_title">关于高可用的系统</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/6775.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="Bret Victor &#8211; Inventing on Principle" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6775.html" class="wp_rp_title">Bret Victor &#8211; Inventing on Principle</a></li><li ><a href="https://coolshell.cn/articles/5686.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/15.jpg" alt="多些时间能少写些代码" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5686.html" class="wp_rp_title">多些时间能少写些代码</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17680.html">从Gitlab误删除数据库想到的</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17680.html/feed</wfw:commentRss>
			<slash:comments>67</slash:comments>
		
		
			</item>
		<item>
		<title>关于高可用的系统</title>
		<link>https://coolshell.cn/articles/17459.html</link>
					<comments>https://coolshell.cn/articles/17459.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sun, 21 Aug 2016 04:34:53 +0000</pubDate>
				<category><![CDATA[技术管理]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[High Availability]]></category>
		<category><![CDATA[Paxos]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[分布式]]></category>
		<category><![CDATA[程序员]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17459</guid>

					<description><![CDATA[<p>在《这多年来我一直在钻研的技术》这篇文章中，我讲述了一下，我这么多年来一直在关注的技术领域，其中我多次提到了工业级的软件，我还以为有很多人会问我怎么定义工业级？...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17459.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17459.html">关于高可用的系统</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium wp-image-17475" src="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-300x300.png" alt="HighAvailability-BK" width="300" height="300" srcset="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-300x300.png 300w, https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-150x150.png 150w, https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-768x768.png 768w, https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-200x200.png 200w, https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-270x270.png 270w, https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK.png 1000w" sizes="(max-width: 300px) 100vw, 300px" />在《<a href="https://coolshell.cn/articles/17446.html" target="_blank">这多年来我一直在钻研的技术</a>》这篇文章中，我讲述了一下，我这么多年来一直在关注的技术领域，其中我多次提到了工业级的软件，我还以为有很多人会问我怎么定义工业级？以及一个高可用性的软件系统应该要怎么干出来？这样我也可以顺理成章的写下这篇文章，但是没有人问，那么，我只好厚颜无耻的自己写下这篇文章了。哈哈。</p>
<p>另外，我在一些讨论高可用系统的地方看到大家只讨论各个公司的技术方案，<strong>其实，高可用的系统并不简单的是技术方案，一个高可用的系统其实还包括很多别的东西，所以，我觉得大家对高可用的系统了解的还不全面，为了让大家的认识更全面，所以，我写下这篇文章</strong>。</p>
<h4>理解高可用系统</h4>
<p>首先，我们需要理解什么是高可用，英文叫High Availability（<a href="https://en.wikipedia.org/wiki/High_availability">Wikipedia词条</a>），基本上来说，就是要让我们的计算环境（包括软硬件）做到full-time的可用性。在设计上一般来说，需要做好如下的设计：</p>
<p><span id="more-17459"></span></p>
<ol>
<li>对软硬件的冗余，以消除单点故障。任何系统都会有一个或多个冗余系统做standby</li>
<li>对故障的检测和恢复。检测故障以及用备份的结点接管故障点。这也就是failover</li>
<li>需要很可靠的交汇点（CrossOver）。这是一些不容易冗余的结点，比如域名解析，负载均衡器等。</li>
</ol>
<p>听起似乎很简单吧，然而不是，细节之处全是魔鬼，冗余结点最大的难题就是对于有状态的结点的数据复制和数据一致性的保证（无状态结点的冗余相对比较简单）。冗余数据所带来的一致性问题是魔鬼中的魔鬼：</p>
<ul>
<li>如果系统的数据镜像到冗余结点是异步的，那么在failover的时候就会出现数据差异的情况。</li>
</ul>
<ul>
<li>如果系统在数据镜像到冗余结点是同步的，那么就会导致冗余结点越多性能越慢。</li>
</ul>
<p>所以，很多高可用系统都是在做各种取舍，这需要比对着业务的特点来的，比如银行账号的余额是一个状态型的数据，那么，冗余时就必需做到强一致性，再比如说，订单记录属于追加性的数据，那么在failover的时候，就可以到备机上进行追加，这样就比较简单了（阿里目前所谓的异地双活其实根本做不到状态型数据的双活）。</p>
<p>下面，总结一下高可用的设计原理：</p>
<ul>
<li>要做到数据不丢，就必需要持久化</li>
<li>要做到服务高可用，就必需要有备用（复本），无论是应用结点还是数据结点</li>
<li>要做到复制，就会有数据一致性的问题。</li>
<li>我们不可能做到100%的高可用，也就是说，我们能做到几个9个的SLA。</li>
</ul>
<h4>高可用系统的技术解决方案</h4>
<p>我在《<a href="https://coolshell.cn/articles/10910.html" target="_blank">分布式系统的事务处理</a>》中引用过下面这个图：这个图来自来自：Google App Engine的co-founder Ryan Barrett在2009年的Google I/O上的演讲《<a href="http://snarfed.org/transactions_across_datacenters_io.html" target="_blank">Transaction Across DataCenter</a>》（视频： <a title="阿里旺旺无法确定该链接的安全性" href="http://www.youtube.com/watch?v=srOgpXECblk" target="_blank">http://www.youtube.com/watch?v=srOgpXECblk</a>）</p>
<p><img decoding="async" loading="lazy" class="size-full wp-image-10942 aligncenter" src="https://coolshell.cn/wp-content/uploads/2014/01/Transaction-Across-DataCenter.jpg" alt="Transaction Across DataCenter" width="566" height="255" srcset="https://coolshell.cn/wp-content/uploads/2014/01/Transaction-Across-DataCenter.jpg 566w, https://coolshell.cn/wp-content/uploads/2014/01/Transaction-Across-DataCenter-300x135.jpg 300w" sizes="(max-width: 566px) 100vw, 566px" /></p>
<p>这个图基本上来说是目前高可用系统中能看得到的所有的解决方案的基础了。M/S、MM实现起来不难，但是会有很多问题，2PC的问题就是性能不行，而Paxos的问题就是太复杂，实现难度太大。</p>
<p>总结一下各个高可用方案的的问题：</p>
<ul>
<li>对于最终一致性来说，在宕机的情况下，会出现数据没有完全同步完成，会出现数据差异性。</li>
<li>对于强一致性来说，要么使用性能比较慢的<a href="https://en.wikipedia.org/wiki/X/Open_XA">XA系</a>的两阶段提交的方案，要么使用性能比较好，但是实现比较复杂的Paxos协议。</li>
</ul>
<p>注：这是软件方面的方案。当然，也可以使用造价比较高的硬件解决方案，不过本文不涉及硬件解决方案。</p>
<p>另外，现今开源软件中，很多缓存，消息中间件或数据库都有持久化和Replication的设计，从而也都有高可用解决方案，但是开源软件一般都没有比较高的SLA，所以，如果我们使用开源软件的话，需要注意这一点。</p>
<h4>高可用技术方案的示例</h4>
<p>下面，我们来看一下MySQL的高可用的方案的SLA（下图下面红色的标识表示了这个方案有几个9）：</p>
<p><a href="http://www.slideshare.net/andrewjamesmorgan/mysql-high-availability-solutions-feb-2015-webinar"><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17461" src="https://coolshell.cn/wp-content/uploads/2016/08/mysql-high-availability-solutions-feb-2015-webinar-9-638.jpg" alt="mysql-high-availability-solutions-feb-2015-webinar-9-638" width="638" height="359" srcset="https://coolshell.cn/wp-content/uploads/2016/08/mysql-high-availability-solutions-feb-2015-webinar-9-638.jpg 638w, https://coolshell.cn/wp-content/uploads/2016/08/mysql-high-availability-solutions-feb-2015-webinar-9-638-300x169.jpg 300w" sizes="(max-width: 638px) 100vw, 638px" /></a></p>
<p style="text-align: center;">图片来源：<a href="http://www.slideshare.net/andrewjamesmorgan/mysql-high-availability-solutions-feb-2015-webinar">MySQL High Availability Solutions</a></p>
<p>简单解释一下MySQL的这几个方案（主要是想表达一个越多的9就越复杂）</p>
<ul>
<li>MySQL Repleaction就是传统的异步数据同步或是半同步Semi-Sync（只要有一个slave收到更新就返回成功）这个方式本质上不到2个9。</li>
<li>MySQL Fabric简单来说就是数据分片下的M/S的读写分离模式。这个方案的的可用性可以达到99%</li>
<li>DRBD通过底层的磁盘同步技术来解决数据同步的问题，就是RAID 1——把两台以上的主机的硬盘镜像成一个。这个方案不到3个9</li>
<li>Solaris Clustering/Oracle VM ，这个机制监控了包括硬件、操作系统、网络和数据库。这个方案一般会伴随着节点间的“心跳机制”，而且还会动用到SAN（Storage Area Network）或是本地的分布式存储系统，还会动用虚拟化技术来做虚拟机的迁移以降低宕机时间的概率。这个解决方案完全就是一个“全栈式的解决方案”。这个方案接近4个9。</li>
<li>MySQL Cluster是官方的一个开源方案，其把MySQL的集群分成SQL Node 和Data Node，Data Node是一个自动化sharing和复制的集群NDB，为了更高的可用性，MySQL Cluster采用了“完全同步”的数据复制的机制来冗余数据结点。这个方案接近5个9。</li>
</ul>
<p>那么，这些2个9，3个9，4个9，5个9是什么意思呢？又是怎么来的呢？请往下看。</p>
<h4>高可用性的SLA的定义</h4>
<p><strong>上面那些都不是本文的重点，本文的重点现在开始，如何测量系统的高可用性</strong>。当然是SLA，全称<a href="https://en.wikipedia.org/wiki/Service-level_agreement" target="_blank">Service Level Agrement</a>，也就是有几个9的高可用性。</p>
<p>工业界有两种方法来测量SLA，</p>
<ul>
<li>一个是故障发生到恢复的时间</li>
<li>另一个是两次故障间的时间</li>
</ul>
<p>但大多数都采用第一种方法，也就是故障发生到恢复的时间，也就是服务不可用的时间，如下表所示：</p>
<table class="wikitable" align="center">
<tbody>
<tr>
<th>系统可用性%</th>
<th>宕机时间/年</th>
<th>宕机时间/月</th>
<th>宕机时间/周</th>
<th>宕机时间/天</th>
</tr>
<tr>
<td align="left">90% (1个9)</td>
<td>36.5 天</td>
<td>72 小时</td>
<td>16.8 小时</td>
<td>2.4 小时</td>
</tr>
<tr>
<td align="left">99% (2个9)</td>
<td>3.65 天</td>
<td>7.20 小时</td>
<td>1.68 小时</td>
<td>14.4 分</td>
</tr>
<tr>
<td align="left">99.9% (3个9)</td>
<td>8.76 小时</td>
<td>43.8 分</td>
<td>10.1 分钟</td>
<td>1.44 分</td>
</tr>
<tr>
<td align="left">99.99% (4个9)</td>
<td>52.56 分</td>
<td>4.38 分</td>
<td>1.01 分钟</td>
<td>8.66 秒</td>
</tr>
<tr>
<td align="left">99.999% (5个9)</td>
<td>5.26 分</td>
<td>25.9 秒</td>
<td>6.05 秒</td>
<td>0.87 秒</td>
</tr>
</tbody>
</table>
<p>比如，99.999%的可用性，一年只能有5分半钟的服务不可用。感觉很难做到吧。</p>
<p><strong>就算是3个9的可用性，一个月的宕机时间也只有40多分钟，看看那些设计和编码不认真的团队，把所有的期望寄托在人肉处理故障的运维团队， 一个故障就能处理1个多小时甚至2-3个小时，连个自动化的工具都没有，还好意思在官网上声明自己的SLA是3个9或是5个9，这不是欺骗大众吗？</strong>。</p>
<h4>影响高可用的因素</h4>
<p>老实说，我们很难计算我们设计的系统有多少的可用性，因为影响一个系统的因素实在是太多了，除了软件设计，还有硬件，还有每三方的服务（如电信联通的宽带SLA），当然包括“建筑施工队的挖掘机”。所以，正如SLA的定义，<strong>这不仅仅只是一个技术指标，而是一种服务提供商和用户之间的contract或契约</strong>。<strong>这种工业级的玩法，就像飞机一样，并不是把飞机造出来就好了，还有大量的无比专业的配套设施、工具、流程、管理和运营</strong>。</p>
<p>简而言之，SLA的几个9就是能持续提供可用服务的级别，不过，工业界中，会把服务不可用的因素分成两种：一种是有计划的，一种是无计划的。</p>
<h5>无计划的宕机原因</h5>
<p>下图来自Oracle的 《<a href="https://docs.oracle.com/cd/A91202_01/901_doc/rac.901/a89867/pshavdtl.htm">High Availability Concepts and Best Practices</a>》</p>
<p>&nbsp;</p>
<h5><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17467" src="https://coolshell.cn/wp-content/uploads/2016/08/unplaned_downtime.gif" alt="unplaned_downtime" width="600" height="602" />有计划的宕机原因</h5>
<p>下图来自Oracle的 《<a href="https://docs.oracle.com/cd/A91202_01/901_doc/rac.901/a89867/pshavdtl.htm">High Availability Concepts and Best Practices</a>》</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17466" src="https://coolshell.cn/wp-content/uploads/2016/08/planned_downtime.gif" alt="planned_downtime" width="600" height="356" /></p>
<p>&nbsp;</p>
<p>我们可以看到，上面的宕机原因包括如下：</p>
<p>无计划的</p>
<ul>
<li>系统级的故障 &#8211;  包括主机、操作系统、中间件、数据库、网络、电源以及外围设备</li>
<li>数据和中介的故障 &#8211; 包括人员误操作、硬盘故障、数据乱了</li>
<li>还有：自然灾害、人为破坏、以及供电问题。</li>
</ul>
<p>有计划的</p>
<ul>
<li>日常任务：备份，容量规划，用户和安全管理，后台批处理应用</li>
<li>运维相关：数据库维护、应用维护、中间件维护、操作系统维护、网络维护</li>
<li>升级相关：数据库、应用、中间件、操作系统、网络、包括硬件升级</li>
</ul>
<h4>真正决定高可用系统的本质原因</h4>
<p>从上面这些会影响高可用的SLA的因素，你看到了什么？如果你还是只看到了技术方面或是软件设计的东西，那么你只看到了冰山一角。我们再仔细想一想，<strong>那个5个9的SLA在一年内只能是5分钟的不可用时间，5分钟啊，如果按一年只出1次故障，你也得在五分钟内恢复故障，让我们想想，这意味着什么？</strong></p>
<p><strong>如果你没有一套科学的牛逼的软件工程的管理，没有牛逼先进的自动化的运维工具，没有技术能力很牛逼的工程师团队，怎么可能出现高可用的系统啊</strong>。</p>
<p>是的，<strong>要干出高可用的系统，这TMD就是一套严谨科学的工程管理</strong>，其中包括但不限于了：</p>
<ul>
<li>软件的设计、编码、测试、上线和软件配置管理的水平</li>
<li>工程师的人员技能水平</li>
<li>运维的管理和技术水平</li>
<li>数据中心的运营管理水平</li>
<li>依赖于第三方服务的管理水平</li>
</ul>
<p>深层交的东西则是——对工程这门科学的尊重：</p>
<ul>
<li>对待技术的态度</li>
<li>一个公司的工程文化</li>
<li>领导者对工程的尊重</li>
</ul>
<p><strong>所以，以后有人在你面前提高可用，你要看的不是他的技术设计，而还要看看他们的工程能力，看看他们公司是否真正的尊重工程这门科学</strong>。</p>
<h4>其它</h4>
<p>有好些非技术甚至技术人员和我说过，做个APP做个网站，不就是找几个码农过来写写代码嘛。等系统不可用的时候，他们才会明白，要找技术能力比较强的人，但是，<strong>就算你和他们讲一万遍道理，他们也很难会明白写代码怎么就是一种工程了，而工程怎么就成了一门科学了。其实，很多做技术的人都不明白这个道理</strong>。</p>
<p>包括很多技术人员也永远不会理解，为什么要做好多像Code Review、自动化运维、自动化测试、持续集成之类这样很无聊的东西。就像我在《<a href="https://coolshell.cn/articles/11432.html" target="_blank">从Code Review 谈如何做技术</a>》中提到的，阿里很多的工程师，架构师/专家，甚至资深架构师都没有这个意识，当然，这不怪他们，因为经历决定思维方式，他们的经历的是民用级的系统，做的都是堆功能的工作，的确不需要。</p>
<p>看完这些，最后让我们都扪心自问一下，你还敢说你的系统是高可用的了么？ ;-)</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-150x150.jpg" alt="从Gitlab误删除数据库想到的" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_title">从Gitlab误删除数据库想到的</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/10910.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/01/trade-off-150x150.jpg" alt="分布式系统的事务处理" width="150" height="150" /></a><a href="https://coolshell.cn/articles/10910.html" class="wp_rp_title">分布式系统的事务处理</a></li><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/6775.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="Bret Victor &#8211; Inventing on Principle" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6775.html" class="wp_rp_title">Bret Victor &#8211; Inventing on Principle</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17459.html">关于高可用的系统</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17459.html/feed</wfw:commentRss>
			<slash:comments>87</slash:comments>
		
		
			</item>
		<item>
		<title>缓存更新的套路</title>
		<link>https://coolshell.cn/articles/17416.html</link>
					<comments>https://coolshell.cn/articles/17416.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Wed, 27 Jul 2016 08:25:28 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[design pattern]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17416</guid>

					<description><![CDATA[<p>看到好些人在写更新缓存数据代码时，先删除缓存，然后再更新数据库，而后续的操作会把数据再装载的缓存中。然而，这个是逻辑是错误的。试想，两个并发操作，一个是更新操作...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17416.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17416.html">缓存更新的套路</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium wp-image-17422" src="https://coolshell.cn/wp-content/uploads/2016/07/cache-300x158.png" alt="cache" width="300" height="158" srcset="https://coolshell.cn/wp-content/uploads/2016/07/cache-300x158.png 300w, https://coolshell.cn/wp-content/uploads/2016/07/cache-514x270.png 514w, https://coolshell.cn/wp-content/uploads/2016/07/cache.png 600w" sizes="(max-width: 300px) 100vw, 300px" />看到好些人在写更新缓存数据代码时，<strong>先删除缓存，然后再更新数据库</strong>，而后续的操作会把数据再装载的缓存中。<strong>然而，这个是逻辑是错误的</strong>。试想，两个并发操作，一个是更新操作，另一个是查询操作，更新操作删除缓存后，查询操作没有命中缓存，先把老数据读出来后放到缓存中，然后更新操作更新了数据库。于是，在缓存中的数据还是老的数据，导致缓存中的数据是脏的，而且还一直这样脏下去了。</p>
<p>我不知道为什么这么多人用的都是这个逻辑，当我在微博上发了这个贴以后，我发现好些人给了好多非常复杂和诡异的方案，所以，我想写这篇文章说一下几个缓存更新的Design Pattern（让我们多一些套路吧）。</p>
<p>这里，我们先不讨论更新缓存和更新数据这两个事是一个事务的事，或是会有失败的可能，我们先假设更新数据库和更新缓存都可以成功的情况（我们先把成功的代码逻辑先写对）。</p>
<p>更新缓存的的Design Pattern有四种：Cache aside, Read through, Write through, Write behind caching，我们下面一一来看一下这四种Pattern。</p>
<p><span id="more-17416"></span></p>
<h4>Cache Aside Pattern</h4>
<p>这是最常用最常用的pattern了。其具体逻辑如下：</p>
<ul>
<li><strong>失效</strong>：应用程序先从cache取数据，没有得到，则从数据库中取数据，成功后，放到缓存中。</li>
</ul>
<ul>
<li><strong>命中</strong>：应用程序从cache中取数据，取到后返回。</li>
</ul>
<ul>
<li><strong>更新</strong>：先把数据存到数据库中，成功后，再让缓存失效。</li>
</ul>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-17438 size-full" src="https://coolshell.cn/wp-content/uploads/2016/07/Cache-Aside-Design-Pattern-Flow-Diagram-e1470471723210.png" alt="Cache-Aside-Design-Pattern-Flow-Diagram" width="600" height="188" /></p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-17437 size-full" src="https://coolshell.cn/wp-content/uploads/2016/07/Updating-Data-using-the-Cache-Aside-Pattern-Flow-Diagram-1-e1470471761402.png" alt="Updating-Data-using-the-Cache-Aside-Pattern-Flow-Diagram-1" width="600" height="186" /></p>
<p>注意，我们的更新是先更新数据库，成功后，让缓存失效。那么，这种方式是否可以没有文章前面提到过的那个问题呢？我们可以脑补一下。</p>
<p>一个是查询操作，一个是更新操作的并发，首先，没有了删除cache数据的操作了，而是先更新了数据库中的数据，此时，缓存依然有效，所以，并发的查询操作拿的是没有更新的数据，但是，更新操作马上让缓存的失效了，后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题，后续的查询操作一直都在取老的数据。</p>
<p>这是标准的design pattern，包括Facebook的论文《<a href="https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf" target="_blank">Scaling Memcache at Facebook</a>》也使用了这个策略。为什么不是写完数据库后更新缓存？你可以看一下Quora上的这个问答《<a href="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">Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?</a>》，主要是怕两个并发的写操作导致脏数据。</p>
<p>那么，是不是Cache Aside这个就不会有并发问题了？不是的，比如，一个是读操作，但是没有命中缓存，然后就到数据库中取数据，此时来了一个写操作，写完数据库后，让缓存失效，然后，之前的那个读操作再把老的数据放进去，所以，会造成脏数据。</p>
<p>但，这个case理论上会出现，不过，实际上出现的概率可能非常低，因为这个条件需要发生在读缓存时缓存失效，而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多，而且还要锁表，而读操作必需在写操作前进入数据库操作，而又要晚于写操作更新缓存，所有的这些条件都具备的概率基本并不大。</p>
<p><strong>所以，这也就是Quora上的那个答案里说的，要么通过2PC或是Paxos协议保证一致性，要么就是拼命的降低并发时脏数据的概率，而Facebook使用了这个降低概率的玩法，因为2PC太慢，而Paxos太复杂。当然，最好还是为缓存设置上过期时间。</strong></p>
<h4>Read/Write Through Pattern</h4>
<p>我们可以看到，在上面的Cache Aside套路中，我们的应用代码需要维护两个数据存储，一个是缓存（Cache），一个是数据库（Repository）。所以，应用程序比较啰嗦。而Read/Write Through套路是把更新数据库（Repository）的操作由缓存自己代理了，所以，对于应用层来说，就简单很多了。<strong>可以理解为，应用认为后端就是一个单一的存储，而存储自己维护自己的Cache。</strong></p>
<h5>Read Through</h5>
<p>Read Through 套路就是在查询操作中更新缓存，也就是说，当缓存失效的时候（过期或LRU换出），Cache Aside是由调用方负责把数据加载入缓存，而Read Through则用缓存服务自己来加载，从而对应用方是透明的。</p>
<h5>Write Through</h5>
<p>Write Through 套路和Read Through相仿，不过是在更新数据时发生。当有数据更新的时候，如果没有命中缓存，直接更新数据库，然后返回。如果命中了缓存，则更新缓存，然后再由Cache自己更新数据库（这是一个同步操作）</p>
<p>下图自来Wikipedia的<a href="https://en.wikipedia.org/wiki/Cache_(computing)">Cache词条</a>。其中的Memory你可以理解为就是我们例子里的数据库。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17417" src="https://coolshell.cn/wp-content/uploads/2016/07/460px-Write-through_with_no-write-allocation.svg_.png" alt="Write-through_with_no-write-allocation" width="460" height="620" srcset="https://coolshell.cn/wp-content/uploads/2016/07/460px-Write-through_with_no-write-allocation.svg_.png 460w, https://coolshell.cn/wp-content/uploads/2016/07/460px-Write-through_with_no-write-allocation.svg_-223x300.png 223w" sizes="(max-width: 460px) 100vw, 460px" /></p>
<h4>Write Behind Caching Pattern</h4>
<p>Write Behind 又叫 Write Back。<strong>一些了解Linux操作系统内核的同学对write back应该非常熟悉，这不就是Linux文件系统的Page Cache的算法吗？是的，你看基础这玩意全都是相通的。</strong>所以，基础很重要，我已经不是一次说过基础很重要这事了。</p>
<p>Write Back套路，一句说就是，在更新数据的时候，只更新缓存，不更新数据库，而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比（因为直接操作内存嘛 ），因为异步，write backg还可以合并对同一个数据的多次操作，所以性能的提高是相当可观的。</p>
<p>但是，其带来的问题是，数据不是强一致性的，而且可能会丢失（我们知道Unix/Linux非正常关机会导致数据丢失，就是因为这个事）。在软件设计上，我们基本上不可能做出一个没有缺陷的设计，就像算法设计中的时间换空间，空间换时间一个道理，有时候，强一致性和高性能，高可用和高性性是有冲突的。软件设计从来都是取舍Trade-Off。</p>
<p>另外，Write Back实现逻辑比较复杂，因为他需要track有哪数据是被更新了的，需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候，才会被真正持久起来，比如，内存不够了，或是进程退出了等情况，这又叫lazy write。</p>
<p>在wikipedia上有一张write back的流程图，基本逻辑如下：</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17428" src="https://coolshell.cn/wp-content/uploads/2016/07/Write-back_with_write-allocation.png" alt="Write-back_with_write-allocation" width="640" height="820" srcset="https://coolshell.cn/wp-content/uploads/2016/07/Write-back_with_write-allocation.png 640w, https://coolshell.cn/wp-content/uploads/2016/07/Write-back_with_write-allocation-234x300.png 234w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>&nbsp;</p>
<h4>再多唠叨一些</h4>
<p>1）上面讲的这些Design Pattern，其实并不是软件架构里的mysql数据库和memcache/redis的更新策略，这些东西都是计算机体系结构里的设计，比如CPU的缓存，硬盘文件系统中的缓存，硬盘上的缓存，数据库中的缓存。<strong>基本上来说，这些缓存更新的设计模式都是非常老古董的，而且历经长时间考验的策略</strong>，所以这也就是，工程学上所谓的Best Practice，遵从就好了。</p>
<p>2）有时候，我们觉得能做宏观的系统架构的人一定是很有经验的，其实，宏观系统架构中的很多设计都来源于这些微观的东西。比如，云计算中的很多虚拟化技术的原理，和传统的虚拟内存不是很像么？Unix下的那些I/O模型，也放大到了架构里的同步异步的模型，还有Unix发明的管道不就是数据流式计算架构吗？TCP的好些设计也用在不同系统间的通讯中，仔细看看这些微观层面，你会发现有很多设计都非常精妙……所以，<strong>请允许我在这里放句观点鲜明的话——如果你要做好架构，首先你得把计算机体系结构以及很多老古董的基础技术吃透了</strong>。</p>
<p>3）在软件开发或设计中，我非常建议在之前先去参考一下已有的设计和思路，<strong>看看相应的guideline，best practice或design pattern，吃透了已有的这些东西，再决定是否要重新发明轮子</strong>。千万不要似是而非地，想当然的做软件设计。</p>
<p>4）上面，我们没有考虑缓存（Cache）和持久层（Repository）的整体事务的问题。比如，更新Cache成功，更新数据库失败了怎么吗？或是反过来。关于这个事，如果你需要强一致性，你需要使用“两阶段提交协议”——prepare, commit/rollback，比如Java 7 的<a href="http://docs.oracle.com/javaee/7/api/javax/transaction/xa/XAResource.html" target="_blank">XAResource</a>，还有MySQL 5.7的 <a href="http://dev.mysql.com/doc/refman/5.7/en/xa.html" target="_blank">XA Transaction</a>，有些cache也支持XA，比如<a href="http://www.ehcache.org/documentation/3.0/xa.html" target="_blank">EhCache</a>。当然，XA这样的强一致性的玩法会导致性能下降，关于分布式的事务的相关话题，你可以看看《<a href="https://coolshell.cn/articles/10910.html" target="_blank">分布式系统的事务处理</a>》一文。</p>
<p>（全文完）</p>
<p>&nbsp;</p>
<p>&nbsp;<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/01/kiss-150x150.png" alt="从面向对象的设计模式看软件设计" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_title">从面向对象的设计模式看软件设计</a></li><li ><a href="https://coolshell.cn/articles/7236.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/05/Bannière-Unix-linux-150x150.jpg" alt="用Unix的设计思想来应对多变的需求" width="150" height="150" /></a><a href="https://coolshell.cn/articles/7236.html" class="wp_rp_title">用Unix的设计思想来应对多变的需求</a></li><li ><a href="https://coolshell.cn/articles/6950.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/21.jpg" alt="需求变化与IoC" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6950.html" class="wp_rp_title">需求变化与IoC</a></li><li ><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF-150x150.jpeg" alt="eBPF 介绍" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_title">eBPF 介绍</a></li><li ><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x-150x150.png" alt="我做系统架构的一些原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_title">我做系统架构的一些原则</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17416.html">缓存更新的套路</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17416.html/feed</wfw:commentRss>
			<slash:comments>186</slash:comments>
		
		
			</item>
		<item>
		<title>分布式系统的事务处理</title>
		<link>https://coolshell.cn/articles/10910.html</link>
					<comments>https://coolshell.cn/articles/10910.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Mon, 20 Jan 2014 03:08:16 +0000</pubDate>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[2PC]]></category>
		<category><![CDATA[3PC]]></category>
		<category><![CDATA[Consistency]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[NWR]]></category>
		<category><![CDATA[Paxos]]></category>
		<category><![CDATA[Performance]]></category>
		<category><![CDATA[Vector Clock]]></category>
		<category><![CDATA[分布式]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=10910</guid>

					<description><![CDATA[<p>当我们在生产线上用一台服务器来提供数据服务的时候，我会遇到如下的两个问题： 1）一台服务器的性能不足以提供足够的能力服务于所有的网络请求。 2）我们总是害怕我们...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/10910.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/10910.html">分布式系统的事务处理</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-full wp-image-10946" alt="" src="https://coolshell.cn/wp-content/uploads/2014/01/trade-off.jpg" width="251" height="200" />当我们在生产线上用一台服务器来提供数据服务的时候，我会遇到如下的两个问题：</p>
<p style="padding-left: 30px;">1）一台服务器的性能不足以提供足够的能力服务于所有的网络请求。</p>
<p style="padding-left: 30px;">2）我们总是害怕我们的这台服务器停机，造成服务不可用或是数据丢失。</p>
<p>于是我们不得不对我们的服务器进行扩展，加入更多的机器来分担性能上的问题，以及来解决单点故障问题。 通常，我们会通过两种手段来扩展我们的数据服务：</p>
<p style="padding-left: 30px;">1）<strong>数据分区</strong>：就是把数据分块放在不同的服务器上（如：uid % 16，一致性哈希等）。</p>
<p style="padding-left: 30px;">2）<strong>数据镜像</strong>：让所有的服务器都有相同的数据，提供相当的服务。</p>
<p>对于第一种情况，我们无法解决数据丢失的问题，单台服务器出问题时，会有部分数据丢失。所以，<strong>数据服务的高可用性只能通过第二种方法来完成——数据的冗余存储</strong>（一般工业界认为比较安全的备份数应该是3份，如：Hadoop和Dynamo）<strong>。 但是，加入更多的机器，会让我们的数据服务变得很复杂，尤其是跨服务器的事务处理，也就是跨服务器的数据一致性</strong>。这个是一个很难的问题。 让我们用最经典的Use Case：“A帐号向B帐号汇钱”来说明一下，熟悉RDBMS事务的都知道从帐号A到帐号B需要6个操作：</p>
<ol>
<li>从A帐号中把余额读出来。</li>
<li>对A帐号做减法操作。</li>
<li>把结果写回A帐号中。</li>
<li>从B帐号中把余额读出来。</li>
<li>对B帐号做加法操作。</li>
<li>把结果写回B帐号中。</li>
</ol>
<p>为了数据的一致性，这6件事，要么都成功做完，要么都不成功，而且这个操作的过程中，对A、B帐号的其它访问必需锁死，所谓锁死就是要排除其它的读写操作，不然会有脏数据的问题，这就是事务。那么，我们在加入了更多的机器后，这个事情会变得复杂起来：</p>
<p><span id="more-10910"></span></p>
<p style="padding-left: 30px;">1）<strong>在数据分区的方案中</strong>：如果A帐号和B帐号的数据不在同一台服务器上怎么办？我们需要一个跨机器的事务处理。也就是说，如果A的扣钱成功了，但B的加钱不成功，我们还要把A的操作给回滚回去。这在跨机器的情况下，就变得比较复杂了。</p>
<p style="padding-left: 30px;">2）<strong>在数据镜像的方案中</strong>：A帐号和B帐号间的汇款是可以在一台机器上完成的，但是别忘了我们有多台机器存在A帐号和B帐号的副本。如果对A帐号的汇钱有两个并发操作（要汇给B和C），这两个操作发生在不同的两台服务器上怎么办？也就是说，在数据镜像中，在不同的服务器上对同一个数据的写操作怎么保证其一致性，保证数据不冲突？</p>
<p>同时，我们还要考虑性能的因素，如果不考虑性能的话，事务得到保证并不困难，系统慢一点就行了。除了考虑性能外，我们还要考虑可用性，也就是说，一台机器没了，数据不丢失，服务可由别的机器继续提供。 于是，我们需要重点考虑下面的这么几个情况：</p>
<p style="padding-left: 30px;">1）<strong>容灾</strong>：数据不丢、结点的Failover</p>
<p style="padding-left: 30px;">2）<strong>数据的一致性</strong>：事务处理</p>
<p style="padding-left: 30px;">3）<strong>性能：吞吐量 、 响应时间</strong></p>
<p>前面说过，要解决数据不丢，只能通过数据冗余的方法，就算是数据分区，每个区也需要进行数据冗余处理。这就是数据副本：当出现某个节点的数据丢失时可以从副本读到，数据副本是分布式系统解决数据丢失异常的唯一手段。所以，在这篇文章中，简单起见，我们只讨论在数据冗余情况下考虑数据的一致性和性能的问题。简单说来：</p>
<p style="padding-left: 30px;"><strong>1）要想让数据有高可用性，就得写多份数据。</strong></p>
<p style="padding-left: 30px;"><strong>2）写多份的问题会导致数据一致性的问题。</strong></p>
<p style="padding-left: 30px;"><strong>3）数据一致性的问题又会引发性能问题</strong></p>
<p>这就是软件开发，按下了葫芦起了瓢。</p>
<h4>一致性模型</h4>
<p>说起数据一致性来说，简单说有三种类型（当然，如果细分的话，还有很多一致性模型，如：顺序一致性，FIFO一致性，会话一致性，单读一致性，单写一致性，但为了本文的简单易读，我只说下面三种）：</p>
<p style="padding-left: 30px;">1）<strong>Weak 弱一致性</strong>：当你写入一个新值后，读操作在数据副本上可能读出来，也可能读不出来。比如：某些cache系统，网络游戏其它玩家的数据和你没什么关系，VOIP这样的系统，或是百度搜索引擎（呵呵）。</p>
<p style="padding-left: 30px;">2）<strong>Eventually 最终一致性</strong>：当你写入一个新值后，有可能读不出来，但在某个时间窗口之后保证最终能读出来。比如：DNS，电子邮件、Amazon S3，Google搜索引擎这样的系统。</p>
<p style="padding-left: 30px;">3）<strong>Strong 强一致性</strong>：新的数据一旦写入，在任意副本任意时刻都能读到新值。比如：文件系统，RDBMS，Azure Table都是强一致性的。</p>
<p>从这三种一致型的模型上来说，我们可以看到，Weak和Eventually一般来说是异步冗余的，而Strong一般来说是同步冗余的，异步的通常意味着更好的性能，但也意味着更复杂的状态控制。同步意味着简单，但也意味着性能下降。 好，让我们由浅入深，一步一步地来看有哪些技术：</p>
<h4>Master-Slave</h4>
<p>首先是Master-Slave结构，对于这种加构，Slave一般是Master的备份。在这样的系统中，一般是如下设计的：</p>
<p style="padding-left: 30px;">1）读写请求都由Master负责。</p>
<p style="padding-left: 30px;">2）写请求写到Master上后，由Master同步到Slave上。</p>
<p>从Master同步到Slave上，你可以使用异步，也可以使用同步，可以使用Master来push，也可以使用Slave来pull。 通常来说是Slave来周期性的pull，所以，是最终一致性。这个设计的问题是，如果Master在pull周期内垮掉了，那么会导致这个时间片内的数据丢失。如果你不想让数据丢掉，Slave只能成为Read-Only的方式等Master恢复。</p>
<p>当然，如果你可以容忍数据丢掉的话，你可以马上让Slave代替Master工作（对于只负责计算的结点来说，没有数据一致性和数据丢失的问题，Master-Slave的方式就可以解决单点问题了） 当然，Master Slave也可以是强一致性的， 比如：当我们写Master的时候，Master负责先写自己，等成功后，再写Slave，两者都成功后返回成功，整个过程是同步的，如果写Slave失败了，那么两种方法，一种是标记Slave不可用报错并继续服务（等Slave恢复后同步Master的数据，可以有多个Slave，这样少一个，还有备份，就像前面说的写三份那样），另一种是回滚自己并返回写失败。（注：一般不先写Slave，因为如果写Master自己失败后，还要回滚Slave，此时如果回滚Slave失败，就得手工订正数据了）你可以看到，如果Master-Slave需要做成强一致性有多复杂。</p>
<h4>Master-Master</h4>
<p>Master-Master，又叫<a href="http://en.wikipedia.org/wiki/Multi-master_replication" target="_blank">Multi-master</a>，是指一个系统存在两个或多个Master，每个Master都提供read-write服务。这个模型是Master-Slave的加强版，数据间同步一般是通过Master间的异步完成，所以是最终一致性。 Master-Master的好处是，一台Master挂了，别的Master可以正常做读写服务，他和Master-Slave一样，当数据没有被复制到别的Master上时，数据会丢失。很多数据库都支持Master-Master的Replication的机制。</p>
<p>另外，如果多个Master对同一个数据进行修改的时候，这个模型的恶梦就出现了——对数据间的冲突合并，这并不是一件容易的事情。看看Dynamo的Vector Clock的设计（记录数据的版本号和修改者）就知道这个事并不那么简单，而且Dynamo对数据冲突这个事是交给用户自己搞的。就像我们的SVN源码冲突一样，对于同一行代码的冲突，只能交给开发者自己来处理。（在本文后后面会讨论一下Dynamo的Vector Clock）</p>
<h4>Two/Three Phase Commit</h4>
<p>这个协议的缩写又叫2PC，中文叫两阶段提交。在分布式系统中，每个节点虽然可以知晓自己的操作时成功或者失败，却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时，为了保持事务的ACID特性，需要引入一个作为<strong>协调者</strong>的组件来统一掌控所有节点(称作<b>参与者</b>)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。 两阶段提交的算法如下：</p>
<p><strong> 第一阶段</strong>：</p>
<ol>
<li>协调者会问所有的参与者结点，是否可以执行提交操作。</li>
<li>各个参与者开始事务执行的准备工作：如：为资源上锁，预留资源，写undo/redo log……</li>
<li>参与者响应协调者，如果事务的准备工作成功，则回应“可以提交”，否则回应“拒绝提交”。</li>
</ol>
<p><strong>第二阶段</strong>：</p>
<ul>
<li>如果所有的参与者都回应“可以提交”，那么，协调者向所有的参与者发送“正式提交”的命令。参与者完成正式提交，并释放所有资源，然后回应“完成”，协调者收集各结点的“完成”回应后结束这个Global Transaction。</li>
</ul>
<ul>
<li>如果有一个参与者回应“拒绝提交”，那么，协调者向所有的参与者发送“回滚操作”，并释放所有资源，然后回应“回滚完成”，协调者收集各结点的“回滚”回应后，取消这个Global Transaction。</li>
</ul>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter  wp-image-10929" alt="" src="https://coolshell.cn/wp-content/uploads/2014/01/Two-phase_commit.png" width="518" height="195" srcset="https://coolshell.cn/wp-content/uploads/2014/01/Two-phase_commit.png 647w, https://coolshell.cn/wp-content/uploads/2014/01/Two-phase_commit-300x113.png 300w" sizes="(max-width: 518px) 100vw, 518px" /></p>
<p>我们可以看到，2PC说白了就是第一阶段做Vote，第二阶段做决定的一个算法，也可以看到2PC这个事是强一致性的算法。在前面我们讨论过Master-Slave的强一致性策略，和2PC有点相似，只不过2PC更为保守一些——先尝试再提交。 2PC用的是比较多的，在一些系统设计中，会串联一系列的调用，比如：A -&gt; B -&gt; C -&gt; D，每一步都会分配一些资源或改写一些数据。比如我们B2C网上购物的下单操作在后台会有一系列的流程需要做。如果我们一步一步地做，就会出现这样的问题，如果某一步做不下去了，那么前面每一次所分配的资源需要做反向操作把他们都回收掉，所以，操作起来比较复杂。现在很多处理流程（Workflow）都会借鉴2PC这个算法，使用 try -&gt; confirm的流程来确保整个流程的能够成功完成。 举个通俗的例子，西方教堂结婚的时候，都有这样的桥段：</p>
<p style="padding-left: 30px;">1）牧师分别问新郎和新娘：你是否愿意……不管生老病死……（询问阶段）</p>
<p style="padding-left: 30px;">2）当新郎和新娘都回答愿意后（锁定一生的资源），牧师就会说：我宣布你们……（事务提交）</p>
<p>这是多么经典的一个两阶段提交的事务处理。 另外，我们也可以看到其中的一些问题， A）其中一个是同步阻塞操作，这个事情必然会非常大地影响性能。 B）另一个主要的问题是在TimeOut上，比如，</p>
<p style="padding-left: 30px;">1）如果第一阶段中，参与者没有收到询问请求，或是参与者的回应没有到达协调者。那么，需要协调者做超时处理，一旦超时，可以当作失败，也可以重试。</p>
<p style="padding-left: 30px;">2）如果第二阶段中，正式提交发出后，如果有的参与者没有收到，或是参与者提交/回滚后的确认信息没有返回，一旦参与者的回应超时，要么重试，要么把那个参与者标记为问题结点剔除整个集群，这样可以保证服务结点都是数据一致性的。</p>
<p style="padding-left: 30px;">3）糟糕的情况是，第二阶段中，如果参与者收不到协调者的commit/fallback指令，参与者将处于“状态未知”阶段，参与者完全不知道要怎么办，比如：如果所有的参与者完成第一阶段的回复后（可能全部yes，可能全部no，可能部分yes部分no），如果协调者在这个时候挂掉了。那么所有的结点完全不知道怎么办（问别的参与者都不行）。为了一致性，要么死等协调者，要么重发第一阶段的yes/no命令。</p>
<p>两段提交最大的问题就是第3）项，<strong>如果第一阶段完成后，参与者在第二阶没有收到决策，那么数据结点会进入“不知所措”的状态，这个状态会block住整个事务</strong>。也就是说，协调者Coordinator对于事务的完成非常重要，Coordinator的可用性是个关键。 因些，我们引入三段提交，三段提交在<a href="http://en.wikipedia.org/wiki/Three-phase_commit_protocol" target="_blank">Wikipedia</a>上的描述如下，他把二段提交的第一个段break成了两段：询问，然后再锁资源。最后真正提交。三段提交的示意图如下：</p>
<p style="padding-left: 30px;"><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2014/01/Three-phase_commit_diagram.png" width="611" height="321" /></p>
<p>三段提交的核心理念是：<strong>在询问的时候并不锁定资源，除非所有人都同意了，才开始锁资源</strong>。</p>
<p style="text-align: left;">理论上来说，如果第一阶段所有的结点返回成功，那么有理由相信成功提交的概率很大。这样一来，可以降低参与者Cohorts的状态未知的概率。也就是说，一旦参与者收到了PreCommit，意味他知道大家其实都同意修改了。这一点很重要。下面我们来看一下3PC的状态迁移图：（<strong>注意图中的虚线，那些F,T是Failuer或Timeout</strong>，其中的：状态含义是 q &#8211; Query，a &#8211; Abort，w &#8211; Wait，p &#8211; PreCommit，c &#8211; Commit）</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter  wp-image-10931" alt="" src="https://coolshell.cn/wp-content/uploads/2014/01/Three-phase_commit_status.png" width="614" height="374" srcset="https://coolshell.cn/wp-content/uploads/2014/01/Three-phase_commit_status.png 767w, https://coolshell.cn/wp-content/uploads/2014/01/Three-phase_commit_status-300x183.png 300w" sizes="(max-width: 614px) 100vw, 614px" /></p>
<p style="text-align: left;">从上图的状态变化图我们可以从虚线（那些F,T是Failuer或Timeout）看到——<strong>如果结点处在P状态（PreCommit）的时候发生了F/T的问题，三段提交比两段提交的好处是，三段提交可以继续直接把状态变成C状态（Commit），而两段提交则不知所措</strong>。</p>
<p style="text-align: left;">其实，三段提交是一个很复杂的事情，实现起来相当难，而且也有一些问题。</p>
<p style="text-align: left;">看到这里，我相信你有很多很多的问题，你一定在思考2PC/3PC中各种各样的失败场景，<strong>你会发现Timeout是个非常难处理的事情，因为网络上的Timeout在很多时候让你无所事从，你也不知道对方是做了还是没有做。于是你好好的一个状态机就因为Timeout成了个摆设</strong>。</p>
<p style="text-align: left;"><strong>一个网络服务会有三种状态：1）Success，2）Failure，3）Timeout，第三个绝对是恶梦，尤其在你需要维护状态的时候</strong>。</p>
<h4>Two Generals Problem（两将军问题）</h4>
<p><a href="http://en.wikipedia.org/wiki/Two_Generals'_Problem" target="_blank">Two Generals Problem</a> 两将军问题是这么一个思维性实验问题： 有两支军队，它们分别有一位将军领导，现在准备攻击一座修筑了防御工事的城市。这两支军队都驻扎在那座城市的附近，分占一座山头。一道山谷把两座山分隔开来，并且两位将军唯一的通信方式就是派各自的信使来往于山谷两边。不幸的是，这个山谷已经被那座城市的保卫者占领，并且存在一种可能，那就是任何被派出的信使通过山谷是会被捕。 请注意，虽然两位将军已经就攻击那座城市达成共识，但在他们各自占领山头阵地之前，并没有就进攻时间达成共识。两位将军必须让自己的军队同时进攻城市才能取得成功。因此，他们必须互相沟通，以确定一个时间来攻击，并同意就在那时攻击。如果只有一个将军进行攻击，那么这将是一个灾难性的失败。 这个思维实验就包括考虑他们如何去做这件事情。下面是我们的思考：</p>
<p style="padding-left: 30px;">1）第一位将军先发送一段消息“让我们在上午9点开始进攻”。然而，一旦信使被派遣，他是否通过了山谷，第一位将军就不得而知了。任何一点的不确定性都会使得第一位将军攻击犹豫，因为如果第二位将军不能在同一时刻发动攻击，那座城市的驻军就会击退他的军队的进攻，导致他的军对被摧毁。</p>
<p style="padding-left: 30px;">2）知道了这一点，第二位将军就需要发送一个确认回条：“我收到您的邮件，并会在9点的攻击。”但是，如果带着确认消息的信使被抓怎么办？所以第二位将军会犹豫自己的确认消息是否能到达。</p>
<p style="padding-left: 30px;">3）于是，似乎我们还要让第一位将军再发送一条确认消息——“我收到了你的确认”。然而，如果这位信使被抓怎么办呢？</p>
<p style="padding-left: 30px;">4）这样一来，是不是我们还要第二位将军发送一个“确认收到你的确认”的信息。</p>
<p>靠，于是你会发现，这事情很快就发展成为不管发送多少个确认消息，都没有办法来保证两位将军有足够的自信自己的信使没有被敌军捕获。</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter  wp-image-10933" alt="" src="https://coolshell.cn/wp-content/uploads/2014/01/two-generals-problems.jpg" width="538" height="251" srcset="https://coolshell.cn/wp-content/uploads/2014/01/two-generals-problems.jpg 598w, https://coolshell.cn/wp-content/uploads/2014/01/two-generals-problems-300x139.jpg 300w" sizes="(max-width: 538px) 100vw, 538px" /></p>
<p style="text-align: left;"><strong style="line-height: 1.5em;">这个问题是无解的</strong><span style="line-height: 1.5em;">。</span><span style="line-height: 1.5em;">两个将军问题和它的无解证明首先由E.A.Akkoyunlu,K.Ekanadham和R.V.Huber于1975年在《一些限制与折衷的网络通信设计》一文中发表，就在这篇文章的第73页中一段描述两个黑帮之间的通信中被阐明。 1978年，在Jim Gray的《数据库操作系统注意事项》一书中（从第465页开始）被命名为两个将军悖论。作为两个将军问题的定义和无解性的证明的来源，这一参考被广泛提及。</span></p>
<p style="text-align: left;">这个实验意在阐明：试图通过建立在一个不可靠的连接上的交流来协调一项行动的隐患和设计上的巨大挑战。</p>
<p style="text-align: left;">从工程上来说，一个解决两个将军问题的实际方法是使用一个能够承受通信信道不可靠性的方案，并不试图去消除这个不可靠性，但要将不可靠性削减到一个可以接受的程度。比如，第一位将军排出了100位信使并预计他们都被捕的可能性很小。在这种情况下，不管第二位将军是否会攻击或者受到任何消息，第一位将军都会进行攻击。另外，第一位将军可以发送一个消息流，而第二位将军可以对其中的每一条消息发送一个确认消息，这样如果每条消息都被接收到，两位将军会感觉更好。然而我们可以从证明中看出，他们俩都不能肯定这个攻击是可以协调的。他们没有算法可用（比如，收到4条以上的消息就攻击）能够确保防止仅有一方攻击。再者，第一位将军还可以为每条消息编号，说这是1号，2号……直到n号。这种方法能让第二位将军知道通信信道到底有多可靠，并且返回合适的数量的消息来确保最后一条消息被接收到。如果信道是可靠的话，只要一条消息就行了，其余的就帮不上什么忙了。最后一条和第一条消息丢失的概率是相等的。</p>
<p> 两将军问题可以扩展成更变态的<strong>拜占庭将军问题 (Byzantine Generals Problem)</strong>，其故事背景是这样的：拜占庭位于现在土耳其的伊斯坦布尔，是东罗马帝国的首都。由于当时拜占庭罗马帝国国土辽阔，为了防御目的，因此每个军队都分隔很远，将军与将军之间只能靠信差传消息。 在战争的时候，拜占庭军队内所有将军必需达成一致的共识，决定是否有赢的机会才去攻打敌人的阵营。但是，军队可能有叛徒和敌军间谍，这些叛徒将军们会扰乱或左右决策的过程。这时候，在已知有成员谋反的情况下，其余忠诚的将军在不受叛徒的影响下如何达成一致的协议，这就是拜占庭将军问题。</p>
<h4>Paxos算法</h4>
<p><a href="http://en.wikipedia.org/wiki/Paxos_(computer_science)" target="_blank">Wikipedia上的各种Paxos算法</a>的描述非常详细，大家可以去围观一下。</p>
<p>Paxos 算法解决的问题是在一个可能发生上述异常的分布式系统中如何就某个值达成一致，保证不论发生以上任何异常，都不会破坏决议的一致性。一个典型的场景是，在一个分布式数据库系统中，如果各节点的初始状态一致，每个节点都执行相同的操作序列，那么他们最后能得到一个一致的状态。为保证每个节点执行相同的命令序列，需要在每一条指令上执行一个「一致性算法」以保证每个节点看到的指令一致。一个通用的一致性算法可以应用在许多场景中，是分布式计算中的重要问题。从20世纪80年代起对于一致性算法的研究就没有停止过。</p>
<p><strong>Notes</strong>：Paxos算法是莱斯利·兰伯特（Leslie Lamport，就是 LaTeX 中的&#8221;La&#8221;，此人现在在微软研究院）于1990年提出的一种基于消息传递的一致性算法。由于算法难以理解起初并没有引起人们的重视，使Lamport在八年后1998年重新发表到ACM Transactions on Computer Systems上（<a href="http://research.microsoft.com/users/lamport/pubs/lamport-paxos.pdf" rel="nofollow">The Part-Time Parliament</a>）。即便如此paxos算法还是没有得到重视，2001年Lamport 觉得同行无法接受他的幽默感，于是用容易接受的方法重新表述了一遍（<a href="http://research.microsoft.com/users/lamport/pubs/paxos-simple.pdf" rel="nofollow">Paxos Made Simple</a>）。可见Lamport对Paxos算法情有独钟。近几年Paxos算法的普遍使用也证明它在分布式一致性算法中的重要地位。2006年Google的三篇论文初现“云”的端倪，其中的Chubby Lock服务使用Paxos作为Chubby Cell中的一致性算法，Paxos的人气从此一路狂飙。（Lamport 本人在 <a href="http://research.microsoft.com/users/lamport/pubs/pubs.html#lamport-paxos" target="_blank">他的blog 中</a>描写了他用9年时间发表这个算法的前前后后）</p>
<p>注：Amazon的AWS中，所有的云服务都基于一个ALF（Async Lock Framework）的框架实现的，这个ALF用的就是Paxos算法。我在Amazon的时候，看内部的分享视频时，设计者在内部的Principle Talk里说他参考了ZooKeeper的方法，但他用了另一种比ZooKeeper更易读的方式实现了这个算法。</p>
<p>简单说来，Paxos的目的是让整个集群的结点对某个值的变更达成一致。Paxos算法基本上来说是个民主选举的算法——大多数的决定会成个整个集群的统一决定。任何一个点都可以提出要修改某个数据的提案，是否通过这个提案取决于这个集群中是否有超过半数的结点同意（所以Paxos算法需要集群中的结点是单数）。</p>
<p><span style="line-height: 1.5em;">这个算法有两个阶段（假设这个有三个结点：A，B，C）：</span></p>
<p style="padding-left: 30px;"><strong>第一阶段：Prepare阶段</strong></p>
<p style="padding-left: 30px;">A把申请修改的请求Prepare Request发给所有的结点A，B，C。注意，Paxos算法会有一个Sequence Number（你可以认为是一个提案号，这个数不断递增，而且是唯一的，也就是说A和B不可能有相同的提案号），这个提案号会和修改请求一同发出，任何结点在“Prepare阶段”时都会拒绝其值小于当前提案号的请求。所以，结点A在向所有结点申请修改请求的时候，需要带一个提案号，越新的提案，这个提案号就越是是最大的。</p>
<p style="padding-left: 30px;">如果接收结点收到的提案号n大于其它结点发过来的提案号，这个结点会回应Yes（本结点上最新的被批准提案号），并保证不接收其它&lt;n的提案。这样一来，结点上在Prepare阶段里总是会对最新的提案做承诺。</p>
<p style="padding-left: 30px;">优化：在上述 prepare 过程中，如果任何一个结点发现存在一个更高编号的提案，则需要通知 提案人，提醒其中断这次提案。</p>
<p style="padding-left: 30px;"><strong>第二阶段：Accept阶段</strong></p>
<p style="padding-left: 30px;">如果提案者A收到了超过半数的结点返回的Yes，然后他就会向所有的结点发布Accept Request（同样，需要带上提案号n），如果没有超过半数的话，那就返回失败。</p>
<p style="padding-left: 30px;">当结点们收到了Accept Request后，如果对于接收的结点来说，n是最大的了，那么，它就会修改这个值，如果发现自己有一个更大的提案号，那么，结点就会拒绝修改。</p>
<p>我们可以看以，这似乎就是一个“两段提交”的优化。其实，<strong>2PC/3PC都是分布式一致性算法的残次版本，Google Chubby的作者Mike Burrows说过这个世界上只有一种一致性算法，那就是Paxos，其它的算法都是残次品。</strong></p>
<p><strong></strong>我们还可以看到：对于同一个值的在不同结点的修改提案就算是在接收方被乱序收到也是没有问题的。</p>
<p>关于一些实例，你可以看一下Wikipedia中文中的“<a href="http://zh.wikipedia.org/zh/Paxos%E7%AE%97%E6%B3%95#.E5.AE.9E.E4.BE.8B" target="_blank">Paxos样例</a>”一节，我在这里就不再多说了。对于Paxos算法中的一些异常示例，大家可以自己推导一下。你会发现基本上来说只要保证有半数以上的结点存活，就没有什么问题。</p>
<p>多说一下，自从Lamport在1998年发表Paxos算法后，对Paxos的各种改进工作就从未停止，其中动作最大的莫过于2005年发表的<a href="http://research.microsoft.com/apps/pubs/default.aspx?id=64624" target="_blank">Fast Paxos</a>。无论何种改进，其重点依然是在消息延迟与性能、吞吐量之间作出各种权衡。为了容易地从概念上区分二者，称前者Classic Paxos，改进后的后者为Fast Paxos。</p>
<h4>总结</h4>
<div id="Msg_18730">下图来自：Google App Engine的co-founder Ryan Barrett在2009年的google i/o上的演讲《<a href="http://snarfed.org/transactions_across_datacenters_io.html" target="_blank">Transaction Across DataCenter</a>》（视频： <a title="阿里旺旺无法确定该链接的安全性" href="http://www.youtube.com/watch?v=srOgpXECblk" target="_blank">http://www.youtube.com/watch?v=srOgpXECblk</a>）</div>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-10942" alt="" src="https://coolshell.cn/wp-content/uploads/2014/01/Transaction-Across-DataCenter.jpg" width="566" height="255" srcset="https://coolshell.cn/wp-content/uploads/2014/01/Transaction-Across-DataCenter.jpg 566w, https://coolshell.cn/wp-content/uploads/2014/01/Transaction-Across-DataCenter-300x135.jpg 300w" sizes="(max-width: 566px) 100vw, 566px" /></p>
<p style="text-align: left;">前面，我们说过，要想让数据有高可用性，就需要冗余数据写多份。写多份的问题会带来一致性的问题，而一致性的问题又会带来性能问题。从上图我们可以看到，我们基本上来说不可以让所有的项都绿起来，这就是著名的CAP理论：一致性，可用性，分区容忍性，你只可能要其中的两个。</p>
<h4>NWR模型</h4>
<p><strong>最后我还想提一下Amazon Dynamo的NWR模型。这个NWR模型把CAP的选择权交给了用户，让用户自己的选择你的CAP中的哪两个</strong>。</p>
<p>所谓NWR模型。N代表N个备份，W代表要写入至少W份才认为成功，R表示至少读取R个备份。<strong>配置的时候要求W+R &gt; N</strong>。 因为W+R &gt; N， 所以 R &gt; N-W 这个是什么意思呢？就是读取的份数一定要比总备份数减去确保写成功的倍数的差值要大。</p>
<p>也就是说，每次读取，都至少读取到一个最新的版本。从而不会读到一份旧数据。当我们需要高可写的环境的时候，我们可以配置W = 1 如果N=3 那么R = 3。 这个时候只要写任何节点成功就认为成功，但是读的时候必须从所有的节点都读出数据。如果我们要求读的高效率，我们可以配置 W=N R=1。这个时候任何一个节点读成功就认为成功，但是写的时候必须写所有三个节点成功才认为成功。</p>
<p>NWR模型的一些设置会造成脏数据的问题，因为这很明显不是像Paxos一样是一个强一致的东西，所以，可能每次的读写操作都不在同一个结点上，于是会出现一些结点上的数据并不是最新版本，但却进行了最新的操作。</p>
<p>所以，Amazon Dynamo引了数据版本的设计。也就是说，如果你读出来数据的版本是v1，当你计算完成后要回填数据后，却发现数据的版本号已经被人更新成了v2，那么服务器就会拒绝你。版本这个事就像“乐观锁”一样。</p>
<p>但是，对于分布式和NWR模型来说，版本也会有恶梦的时候——就是版本冲的问题，比如：我们设置了N=3 W=1，如果A结点上接受了一个值，版本由v1 -&gt; v2，但还没有来得及同步到结点B上（异步的，应该W=1，写一份就算成功），B结点上还是v1版本，此时，B结点接到写请求，按道理来说，他需要拒绝掉，但是他一方面并不知道别的结点已经被更新到v2，另一方面他也无法拒绝，因为W=1，所以写一分就成功了。于是，出现了严重的版本冲突。</p>
<p>Amazon的Dynamo把版本冲突这个问题巧妙地回避掉了——版本冲这个事交给用户自己来处理。</p>
<p>于是，Dynamo引入了Vector Clock（矢量钟？!）这个设计。这个设计让每个结点各自记录自己的版本信息，也就是说，对于同一个数据，需要记录两个事：1）谁更新的我，2）我的版本号是什么。</p>
<p>下面，我们来看一个操作序列：</p>
<p style="padding-left: 30px;">1）一个写请求，第一次被节点A处理了。节点A会增加一个版本信息(A，1)。我们把这个时候的数据记做D1(A，1)。 然后另外一个对同样key的请求还是被A处理了于是有D2(A，2)。这个时候，D2是可以覆盖D1的，不会有冲突产生。</p>
<p style="padding-left: 30px;">2）现在我们假设D2传播到了所有节点(B和C)，B和C收到的数据不是从客户产生的，而是别人复制给他们的，所以他们不产生新的版本信息，所以现在B和C所持有的数据还是D2(A，2)。于是A，B，C上的数据及其版本号都是一样的。</p>
<p style="padding-left: 30px;">3）如果我们有一个新的写请求到了B结点上，于是B结点生成数据D3(A,2; B,1)，意思是：数据D全局版本号为3，A升了两新，B升了一次。这不就是所谓的代码版本的log么？</p>
<p style="padding-left: 30px;">4）如果D3没有传播到C的时候又一个请求被C处理了，于是，以C结点上的数据是D4(A,2; C,1)。</p>
<p style="padding-left: 30px;">5）好，最精彩的事情来了：如果这个时候来了一个读请求，我们要记得，我们的W=1 那么R=N=3，所以R会从所有三个节点上读，此时，他会读到三个版本：</p>
<ul>
<ul>
<li>A结点：D2(A,2)</li>
<li>B结点：D3(A,2;  B,1);</li>
<li>C结点：D4(A,2;  C,1)</li>
</ul>
</ul>
<p style="padding-left: 30px;">6）这个时候可以判断出，D2已经是旧版本（已经包含在D3/D4中），可以舍弃。</p>
<p style="padding-left: 30px;">7）但是D3和D4是明显的版本冲突。于是，交给调用方自己去做版本冲突处理。就像源代码版本管理一样。</p>
<p>很明显，上述的Dynamo的配置用的是CAP里的A和P。</p>
<p>我非常推大家都去看看这篇论文：《<a href="http://www.read.seas.harvard.edu/~kohler/class/cs239-w08/decandia07dynamo.pdf" target="_blank">Dynamo：Amazon&#8217;s Highly Available Key-Value Store</a>》，如果英文痛苦，你可以<a href="http://vdisk.weibo.com/s/AKRQZMLLc1ol  " target="_blank">看看译文</a>（译者不详）。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="http://coolshell.cn/articles/7490.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/06/f1-150x150.jpg" alt="性能调优攻略" width="150" height="150" /></a><a href="http://coolshell.cn/articles/7490.html" class="wp_rp_title">性能调优攻略</a></li><li ><a href="http://coolshell.cn/articles/6470.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/01/12306-150x150.png" alt="由12306.cn谈谈网站性能技术 " width="150" height="150" /></a><a href="http://coolshell.cn/articles/6470.html" class="wp_rp_title">由12306.cn谈谈网站性能技术 </a></li><li ><a href="http://coolshell.cn/articles/9169.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/02/Disruptor-150x150.png" alt="并发框架Disruptor译文" width="150" height="150" /></a><a href="http://coolshell.cn/articles/9169.html" class="wp_rp_title">并发框架Disruptor译文</a></li><li ><a href="http://coolshell.cn/articles/6790.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/03/o_conditional_update_1-150x150.png" alt="多版本并发控制(MVCC)在分布式系统中的应用" width="150" height="150" /></a><a href="http://coolshell.cn/articles/6790.html" class="wp_rp_title">多版本并发控制(MVCC)在分布式系统中的应用</a></li><li ><a href="http://coolshell.cn/articles/8239.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/09/lock_free_bicycle-150x150.jpg" alt="无锁队列的实现" width="150" height="150" /></a><a href="http://coolshell.cn/articles/8239.html" class="wp_rp_title">无锁队列的实现</a></li><li ><a href="http://coolshell.cn/articles/7236.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/05/Bannière-Unix-linux-150x150.jpg" alt="用Unix的设计思想来应对多变的需求" width="150" height="150" /></a><a href="http://coolshell.cn/articles/7236.html" class="wp_rp_title">用Unix的设计思想来应对多变的需求</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/10910.html">分布式系统的事务处理</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/10910.html/feed</wfw:commentRss>
			<slash:comments>181</slash:comments>
		
		
			</item>
		<item>
		<title>类型的本质和函数式实现</title>
		<link>https://coolshell.cn/articles/10169.html</link>
					<comments>https://coolshell.cn/articles/10169.html#comments</comments>
		
		<dc:creator><![CDATA[Todd]]></dc:creator>
		<pubDate>Mon, 22 Jul 2013 11:46:00 +0000</pubDate>
				<category><![CDATA[编程语言]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=10169</guid>

					<description><![CDATA[<p>（感谢 @文艺复兴记（todd） 投递此文） 在上一篇文章《二叉树迭代器算法》中，我介绍了一种基于栈的二叉树迭代器实现。程序设计语言和Haskell大牛@九瓜 ...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/10169.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/10169.html">类型的本质和函数式实现</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong>（感谢 </strong><a href="http://weibo.com/weidagang" target="_blank">@文艺复兴记</a><strong>（todd） 投递此文）</strong></p>
<p>在上一篇文章<a href="https://coolshell.cn/articles/9886.html">《二叉树迭代器算法》</a>中，我介绍了一种基于栈的二叉树迭代器实现。程序设计语言和Haskell大牛<a href="http://weibo.com/u/1684815495">@九瓜</a> 在看过之后评论到：</p>
<blockquote><p>这里用了 stack 来做，有点偷懒，所以错失了一个抽象思考机会。如果我们能够理解二叉树到线性表的转换过程，完全可以把 Iterator 当作抽象的线性表来看，只要定义了关于 Iterator 的 empty, singleton, 还有 append 操作，实现二叉树的 Iterator 就变得非常直观。</p></blockquote>
<p>“错失了一个抽象思考机会”是什么意思呢？我理解九瓜的意思是基于栈的实现虽然是正确的，但它缺乏对于迭代器类型本质的理解，不具有通用性。如果能对迭代器进行合适地抽象就可以像二叉树递归遍历一样自然地得出二叉树迭代器，甚至其他更复杂的数据结构，只要我们能写出它的遍历算法，迭代器算法都可以自然推出。</p>
<h4>类型的本质</h4>
<p>九瓜提到了通过empty, singleton和append操作对Iterator进行抽象，我本来打算直接根据这个思路介绍函数式的二叉树迭代器实现，但是考虑到其实首要的问题在于理解类型的本质，而并不是所有人都具备这个基础，不如先普及一下类型基础再进入具体实现。那么下面我们就先来认识一下类型到底是什么？我们先以来看看表示元素对的Pair类型，可能有人一提到Pair类型马上就会在脑海中浮现出下面的结构：</p>
<p><span id="more-10169"></span></p>
<pre data-enlighter-language="cpp" class="EnlighterJSRAW">
struct Pair {
    int left;
    int right;
}
</pre>
<p>其实，这种理解是非本质的，Pair完全可以用2个元素的数组来表示，第一个元素表示left，第二个元素表示right：</p>
<pre data-enlighter-language="cpp" class="EnlighterJSRAW">
struct Pair {
    int elements[2];
}
</pre>
<p>上面的两种不同表示是类型的不同实现，而<strong>类型的本质是由操作(Operation)和操作间的关系或不变式(Invariant)所定义的</strong>，我们称之为类型规范(Type Specification)。比如，Pair类型是这样定义的：</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
Type Pair:
    Operations:
        Pair make_pair(int x, int y)
        int get_left(Pair pair)
        int get_right(Pair pair)
    Invariants:
        get_left(make_pair(x, y)) == x  //对x, y构造的Pair取左元素等于x
        get_right(make_pair(x, y)) == y  //对x, y构造的Pair取右元素等于y
</pre>
<p>也就是说只要是满足Pair类型规范，即定义了make_pair，get_left, get_right这3种操作，并且这些操作满足上面两个不变式，那么它这就是Pair类型。我们再来看看稍微复杂一点的Stack类型：</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
Type Stack:
    Operations:
        Stack make_stack()  //构造空栈
        Stack push(Stack stack, int x)  //将元素x压栈，返回新栈
        int top(stack)  //获取栈顶元素
        Stack pop(Stack stack)  //将栈顶元素弹栈，返回新栈
    Invariants:
        top(push(stack, x)) == x  //栈顶元素为最后一次压栈值
        pop(push(stack, x)) == stack  //对stack压栈后再弹栈等于原来的stack
</pre>
<p>Stack类型规范简言之就是FILO（先入后出），如果要形式化就是上面的不变式。为了加深理解，我们现在切换到测试视角来看一看，如果请你来编写一个Stack类的单元测试用例，你应该怎么写呢？许多朋友都不清楚单元测试到底测什么？怎么测？我见过不少人用一个测试用例单独对push做测试，用另一个测试用例对pop单独做测试，其主要原因就在于缺乏对类型本质的理解。其实，只要理解了类型的本质我们就知道孤立地看push或pop是没有任何意义的，它们的意义是在FILO关系下相互解释的，所以测试当然是基于类型规范来测试FILO不变式！这种基于类型规范的测试是一种黑盒测试，与类型的内部实现细节无关，只要单元测试覆盖了类型所定义的所有操作和不变式，那么不管内部怎么实现或优化，测试用例都不需要调整。反之，如果深入到了类型的内部实现做白盒测试，那这样的测试用例实际上就不再是反映其类型规范了，它会随着实现细节的调整而失效。</p>
<p>更深一层来看，不仅是在Pair，Stack这样的微观层面，在一个系统的宏观层面同样可以采用类型视角，即考察系统定义了哪些操作？这些操作之间有什么样的关系或不变式？比如，你如何从类型的角度来看待MySQL这样一套数据库系统？MySQL系统定义了哪些操作？这些操作之间必须满足怎样的关系和不变式？不仅如此，类型视角除了可以应用于计算机系统，甚至还可以应用于生活中的事物，比如，你到超市购物可以寄存自己的包，在寄包的时候会获得一张密码条，取包时可以通过密码条打开箱子。你能从超市寄包这个例子中看出类型来吗？如果你看出来了，说明你对类型的理解真正融会贯通了！</p>
<h4>类型的函数式实现</h4>
<p>上面我们介绍了类型的本质在于操作和操作间的关系，下面我们要关注的是类型的实现。在上面的例子中，Pair的内部结构到底是什么，是一个left和一个right成员？还是一个两元素的数组？没有讲，也没关系，就好像Windows的Handle和Linux的FileDescriptor一样，它们都是一个标识，你并不需要关心它的值本身，你只需要用几个相关的函数创建和操作它就行了（上面超市寄包例子中的密码条和Windows中的Handle是什么关系，你看出来了吗？你需要理解密码条的内容吗？）。换句话说，只要满足类型规范，具体实现是可变的，使用者<strong>只依赖于类型规范而不依赖于其具体实现</strong>。这在面向对象语言中意味着接口保持不变而具体实现可以变化（这里把public方法视为一种广义的接口）。</p>
<p>下面，我们还会看到的是不仅类型的内部实现可以变化，而且可以根本没有什么所谓的内部实现。这是什么意思呢？让我们来思考一下，是不是Pair内部一定要有什么东西来保存构造函数传入的left和right？我们能跳出这个定势吗？在函数式编程中，我们能做到：</p>
<p>[javascript]<br />
//Javascript<br />
function make_pair(x, y) {<br />
    // 返回一个支持get_left和get_right操作的闭包(Closure)<br />
    return {<br />
        get_left : function() { return x },<br />
        get_right : function() { return y }<br />
    }<br />
}<br />
function get_left(pair) {<br />
    return pair.get_left();<br />
}<br />
function get_right(pair) {<br />
    return pair.get_right();<br />
}<br />
// Test case<br />
console.log(get_left(make_pair(1, 2))) //1<br />
console.log(get_right(make_pair(1, 2))) //2<br />
[/javascript]</p>
<p>上面的关键代码在于make_pair的内部返回的不是一种具体的数据结构，而是一个支持get_left和get_right操作的闭包(Closure)，将来可以通过get_left和get_right来提取x, y。这种基于闭包的实现和我们通常采用的基于数据结构的实现的本质区别在哪里呢？不难发现，<strong>基于闭包的实现和类型规范是直接对应的</strong>，它并没有引入类型规范之外的东西，而基于数据结构的实现则隐藏了实现的细节。换句话说，如果要验证实现代码的正确性，对于前者只需要比对着类型规范，对于后者我们可能需要去仔细理解推敲其所采用的数据结构。对于Pair这样简单的结构二者差别不大，甚至基于数据结构的实现更简单，但是对于复杂的类型就容易体现出闭包实现的优势了。为了加深理解，我们再来看一个Stack的函数式实现：</p>
<p>[javascript]<br />
//Javascript<br />
function make_stack() {<br />
    return null<br />
}<br />
function push(stack, x) {<br />
    return {<br />
        top : function() { return x },<br />
        pop : function() { return stack }<br />
    }<br />
}<br />
function top(stack) {<br />
    return stack.top()<br />
}<br />
function pop(stack) {<br />
    return stack.pop()<br />
}<br />
// Test case<br />
var stack = make_stack()<br />
stack = push(stack, 1)<br />
stack = push(stack, 2)<br />
stack = push(stack, 3)<br />
console.log(top(stack)) //3<br />
stack = pop(stack)<br />
console.log(top(stack)) //2<br />
stack = push(stack, 4)<br />
console.log(top(stack)) //4<br />
[/javascript]</p>
<p>上面的所有函数都是采用了无副作用的纯函数式设计，可能习惯面向对象编程的朋友不是很习惯，不过这不影响我们对类型的讨论，而且它也很容易改造成面向对象的风格，感兴趣的朋友可以自己尝试对上面的代码进行简单的包装让它看起来像面向对象的样子。</p>
<h4>函数式二叉树迭代器</h4>
<p>上面我们介绍了类型的本质和函数式实现，下面我们再来看看Iterator类型又该如何定义和实现呢？ 思路当然还是从操作入手，考虑Iterator类型对应了哪些操作，它们的关系是什么？上面九瓜提示了Iterator类型可以抽象为线性表List类型，或者说Iterator本质上是一个List。为什么呢？其实，只要跳出“如何表示数据结构”的思维，从类型角度思考就很容易理解，因为Iterator和List都定义了相同的操作，Iterator的使用者完全不关心也不知道它背后到底是链表还是二叉树，你对Iterator的操作和一个List的操作完全一样。正是这个原因，STL等范型库才能通过Iterator将算法和数据结构解耦。</p>
<p>怎么定义一个List类型呢？九瓜提到的empty(), singleton()和append()实际上就是和List打交道最多的Lisp语言的经典定义方式。Lisp是基于s-expression的，s-expression既可以视为线性表又可以视为树，本质上Lisp为List类型了构造、取首元素和取剩余元素等几种操作：</p>
<pre data-enlighter-language="python" class="EnlighterJSRAW">
Type List:
    Operations:
        List empty()  //构造空表，通常由()这个文字量表示
        List singleton(Element e)  //构造包含一个元素的表，通常由(e)这个文字量表示
        Element first(List list)   //取list的第一个元素，通常又叫car操作
        List rest(List list)  //取list除第一个元素外的剩余部分，通常又叫cdr操作
        List append(List list1, List list2) //连接两个表
    Invariants:
        append(empty(), list) == list  //空表和表list连接后等于表list
        append(list, empty()) == list  //空表和表list连接后等于表list
        first(singleton(e)) == e  //对singleton(e)取首元素等于e
        rest(singleton(e)) == empty()  //对singleton(e)取首元素外的剩余部分的结果为空表
        append(first(list), rest(list)) == list  //对list的首尾两部分进行连接等于list本身
        if list1 is not empty then
            first(append(list1, list2)) == first(list1)  //对非空表list1于表list2的连接取首元素等于对非空表list1取首元素
        if list1 is not empty then
            rest(append(list1, list2)) == append(rest(list1), list2)  //对非空表list1于表list2的连接取首元素等于对非空表list1取首元素
</pre>
<p>有了上面的分析，我们相应地写出下面的List实现：</p>
<p>[javascript]<br />
//Javascript<br />
function empty() {<br />
    return null<br />
}<br />
function singleton(e) {<br />
    return {<br />
        first: function() { return e },<br />
        rest: function() { return null }<br />
    }<br />
}<br />
function first(list) {<br />
    return list.first()<br />
}<br />
function rest(list) {<br />
    return list.rest()<br />
}<br />
function append(list1, list2) {<br />
    if (null == list1) return list2<br />
    if (null == list2) return list1</p>
<p>    return {<br />
        first : function() { return first(list1) },<br />
        rest : function() { return append(rest(list1), list2) }<br />
    }<br />
}<br />
[/javascript]</p>
<p>在此基础上可以进一步实现二叉树迭代器：</p>
<p>[javascript]<br />
function make_binary_tree_iterator(node) {<br />
    return {<br />
        first : function() {<br />
            return null != node.left ? first(make_binary_tree_iterator(node.left)) : node<br />
        },<br />
        rest : function() {<br />
            var left_it = (null == node.left ? null : make_binary_tree_iterator(node.left))<br />
            var root_it = singleton(node)<br />
            var right_it = (null == node.right ? null : make_binary_tree_iterator(node.right))<br />
            var it = append(append(left_it, root_it), right_it)<br />
            return rest(it)<br />
        }<br />
    }<br />
}<br />
//======== Test case ========<br />
var tree = {<br />
    value : 1,<br />
        left : {<br />
            value : 2,<br />
            left : { value : 4, left : null, right : null },<br />
            right : null<br />
        },<br />
        right : {<br />
            value : 3,<br />
            left : null,<br />
            right : { value : 7, left : null, right : null }<br />
    }<br />
}<br />
for (var it = make_binary_tree_iterator(tree); null != it; it = rest(it)) {<br />
    console.log(first(it).value)<br />
}<br />
[/javascript]</p>
<p>上面的make_binary_tree_iterator在List类型的基础上按照二叉树遍历过程构造了一个List。不知道你是否注意到了，为什么它不像下面这个例子一样直接返回一个List，而要构造一个闭包呢？</p>
<p>[javascript]<br />
function make_binary_tree_iterator(node) {<br />
    var left_it = (null == node.left ? null : make_binary_tree_iterator(node.left))<br />
    var root_it = singleton(node)<br />
    var right_it = (null == node.right ? null : make_binary_tree_iterator(node.right))<br />
    return append(append(left_it, root_it), right_it)<br />
}<br />
[/javascript]</p>
<p>这里关键的区别在于闭包是惰性求值的，也就是说只有当真正开始迭代遍历的时候才会逐渐对各个函数进行求值，而上面的函数递归调用是非惰性的，会从一开始就把所有结点展开成线性表。如果你对这一点还不能很好地理解，可以尝试在各个函数中加log跟踪函数的调用过程。</p>
<h4>总结</h4>
<p>本文介绍了类型的本质在于它所定义的操作以及操作之间的关系和不变式。类型的实现关键在于满足类型规范的要求，而具体实现是可以变化的，使用者和测试用例都应该只依赖于类型规范而不依赖于具体实现。函数式的类型实现往往和类型规范是直接对应的，简单通用，但可能有性能问题，而命令式的类型实现往往会引入复杂的内部数据结构，但是其优点是高效。这两种实现并不是完全互斥的，有时候可以将二者相结合达到简单与高效的结合。<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/10822.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/12/yoda-lambda-150x150.png" alt="函数式编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/10822.html" class="wp_rp_title">函数式编程</a></li><li ><a href="https://coolshell.cn/articles/10739.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/12/lua-150x150.gif" alt="Lua简明教程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/10739.html" class="wp_rp_title">Lua简明教程</a></li><li ><a href="https://coolshell.cn/articles/10337.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="数据即代码：元驱动编程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/10337.html" class="wp_rp_title">数据即代码：元驱动编程</a></li><li ><a href="https://coolshell.cn/articles/7886.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/07/muxnt-150x150.jpg" alt="代码执行的效率" width="150" height="150" /></a><a href="https://coolshell.cn/articles/7886.html" class="wp_rp_title">代码执行的效率</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/10169.html">类型的本质和函数式实现</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/10169.html/feed</wfw:commentRss>
			<slash:comments>32</slash:comments>
		
		
			</item>
		<item>
		<title>IoC/DIP其实是一种管理思想</title>
		<link>https://coolshell.cn/articles/9949.html</link>
					<comments>https://coolshell.cn/articles/9949.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Fri, 05 Jul 2013 00:44:03 +0000</pubDate>
				<category><![CDATA[技术管理]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[design pattern]]></category>
		<category><![CDATA[DIP]]></category>
		<category><![CDATA[IoC]]></category>
		<category><![CDATA[Object-Oriented]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[程序员]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=9949</guid>

					<description><![CDATA[<p>关于IoC的的概念提出来已经很多年了，其被用于一种面象对像的设计。我在这里再简单的回顾一下这个概念。我先谈技术，再说管理。 话说，我们有一个开关要控制一个灯的开...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/9949.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/9949.html">IoC/DIP其实是一种管理思想</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium wp-image-9957" alt="" src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-300x200.jpg" width="300" height="200" /> 关于IoC的的概念提出来已经很多年了，其被用于一种面象对像的设计。我在这里再简单的回顾一下这个概念。我先谈技术，再说管理。</p>
<p>话说，我们有一个开关要控制一个灯的开和关这两个动作，最常见也是最没有技术含量的实现会是这个样子：</p>
<p><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/07/IoC1.jpg" width="240" height="82" /></p>
<p>然后，有一天，我们发现需要对灯泡扩展一下，于是我们做了个抽象类：</p>
<p><img decoding="async" loading="lazy" class="aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/07/IoC2.jpg" width="290" height="183" /></p>
<p>但是，如果有一天，我们发现这个开关可能还要控制别的不单单是灯泡的东西，我们就发现这个开关耦合了灯泡这种类别，非常不利于我们的扩展，于是反转控制出现了。</p>
<p>就像现实世界一样，造开关的工厂根本不关心要控制的东西是什么，它只做一个开关应该做好的事，就是把电接通，把电断开（不管是手动的，还是声控的，还是光控，还是遥控的），而我们的造各种各样的灯泡（不管是日关灯，白炽灯）的工厂也不关心你用什么样的开关，反正我只管把灯的电源接口给做出来，然后，开关厂和电灯厂依赖于一个标准的通电和断电的接口。于是产生了IoC控制反转，如下图：</p>
<p><span id="more-9949"></span></p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="size-full wp-image-9952 aligncenter" alt="" src="https://coolshell.cn/wp-content/uploads/2013/07/IoC3.jpg" width="504" height="309" srcset="https://coolshell.cn/wp-content/uploads/2013/07/IoC3.jpg 504w, https://coolshell.cn/wp-content/uploads/2013/07/IoC3-300x183.jpg 300w" sizes="(max-width: 504px) 100vw, 504px" /></p>
<p style="text-align: left;"><strong>所谓控制反转的意思是，开关从以前的设备的专用开关，转变到了控制电源的开关，而以前的设备要反过来依赖于开关厂声明的电源连接接口。只要符合开关厂定义的电源连接的接口，这个开关可以控制所有符合这个电源连接接口的设备</strong>。<span style="color: #ff0000;"><strong>也就是说，开关从依赖设备这种情况，变成了，设备反过来依赖于开关所定义的接口</strong></span>。</p>
<p>只要你看过我的那篇《<a title="从面向对象的设计模式看软件设计" href="https://coolshell.cn/articles/8961.html" target="_blank">面向对象设计其实和面象对象一点关系也没有</a>》，你就知道这样的例子在生活中太多见了。比如说：</p>
<p style="padding-left: 30px;">1）在交易的过程中，卖家向买家卖东西，一手交钱一手交货，所以，基本上来说卖家和买家必需强耦合（必需见面）。这个时候，银行出来做担保，买家把钱先垫到银行，银行让卖家发货，买家验货后，银行再把钱打给卖家。这就是反转控制。买卖双方把对对方的直接控制，反转到了让对方来依赖一个标准的交易模型的接口。股票交易也是一样的，证交所就是买卖双方的标准交易模型接口。</p>
<p style="padding-left: 30px;">2）上面这个例子，可能还不明显，再举一个例子。海尔公司作为一个电器制商需要把自己的商品分销到全国各地，但是发现，不同的分销渠道有不同的玩法，于是派出了各种销售代表玩不同的玩法，随着渠道越来越多，发现，每增加一个渠道就要新增一批人和一个新的流程，严重耦合并依赖各渠道商的玩法。实在受不了了，于是制定业务标准，开发分销信息化系统，只有符合这个标准的渠道商才能成为海尔的分销商。让各个渠道商反过来依赖自己标准。反转了控制，倒置了依赖。</p>
<p><strong>可见，控制反转和依赖倒置不单单的一种设计模式，反而更是一种管理模式。</strong></p>
<p>在大公司中，有很多很多的团队，这些团队开发的软件有很多依赖，跨团队合作是一件挺麻烦的事情，下面是一些比较真实的示例：</p>
<p style="padding-left: 30px;">1）一个网页会有很多频道，于是，我们的前端工程师进入到各个页面为各种频道开发他们的页面，随着频道越来越多，前端开发工程师的人数也越来越多，每增加一个频道，就要增加一个为这个频道服务的前端团队，于是，人数越来越多，干成了劳动密集型。为什么不反转控制，倒置依赖呢？前端的同学完全可以开发出各种页面的标准组件，布局，模板，以前与后端交互框架，然后，让后端的同学反过来依赖于前端的标准，使用前端的框架，前端的布局，模板，和组件，以向前端接入后端的模块。</p>
<p style="padding-left: 30px;">2）一个平台需要接入各种各样的业务系统，这些垂直业务系统都有自己的账号体系，于是这个平台为了要兼这些垂直系统的账号体系以做到权限控制，需要做各个系统和自己系统中的账号映射，并为账号和分配出来的资源设置各垂直系统的标识，还要在自己的代码中要写很多很多的依赖于各种账号体系的代码。其实，一个依赖倒置和反转控制就很简单。开发一个权限体系标准，让接入方的账号系统反过来依赖并控制这个标准的权限系统，从而做出一个干净的系统。</p>
<p style="padding-left: 30px;">3）还有一个云平台中的管理模式，一些底层服务的开发团队只管开发底层的技术，然后什么也不管了，就交给上层的开发人员，在底层团队的开发出来的产品上面开发各种管理这个底层资源的东西，比如：生产底层资源的业务，底层资源的控制台，底层资源的监控系统。这个让底层团队只干纯技术，不干与底层技术无关的东西，看似很科学，其实是做错了。因为，上层为各个云资源控制生产，开发控制台和监控的团队，随着接入的资源的越来越多，完全干不过来了，苦逼得一塌糊涂，因为底层的资源千差百怪，每接一个就要开发一堆这个产品的代码。这个时候依赖倒置和反转控制又可以解决问题了。很简单，上层为各个云资源控制生产，开发控制台，和监控的团队应该制定一个标准，让底层的IaaS云资源开发团队反过来依赖这个标准，统一接入方式，如果开发的云资源不符我的生产控制模型，没有控制台，不把监控数据喂入我的监控系统，对不起，请不要接入我这个PaaS平台。</p>
<p style="padding-left: 30px;">4）一个集中式的处理电子商务中的订单的流程。各个垂直业务线都需要通过这个平台来处理自己的交易业务，但是垂直业务线上的个性化需求太多。于是，这个技术平台开始出现了黑魔法——“为了害怕改变数据库表结构，不得不在数据库中预留一些字段，里面存把业务方的个性化字段存成如JSON这样的东西”，并为之自豪认为可以快速解决业务问题（WTF）。然而，恶梦并没就此结束，管理这个技术平台的小组开始发现，对来自各个业务方的需求应接不暇，各种变态需求严重干扰系统，各种技术决定越来越不好做，导致需求排期排不过来。于是，不单单得到了各个业务方的各种抱怨，最可怕的是还有高层老大们压过来的Deadline，加班加点，苦逼之极，最后业务方自己要去一个自己的平台。为什么不用依赖倒置和反转控制的思想呢？开发一个插件模型、工作流引擎和Pub/Sub系统，让业务方的个性化需求可以以插件的方式插入我的订单流程中，业务方自的数据存在自己的库中，业务逻辑也不要侵入我的系统，并可以使用工作流引擎或Pub/Sub的协义标准来自己定义工作流的各个步骤（甚至把工作流引擎的各个步骤的Decider交给各个业务方自行处理）。让各个业务方来依赖于我的标准插件和工作流接口，反转迭控制，让他们来控制我的系统，依赖倒置，让他们来依赖我的标准。（这个团队想过把自己的系统内部开源出去让别的团队也进来参与，可以是可以，但一定要用Linux/Git这种方式，允许出现多个分支，多个发行版。但多个版本又造成了多个业务平台，这会上上层垂直业务不知所措）</p>
<p style="padding-left: 30px;">5）看过《<a title="SteveY对Amazon和Google平台的吐槽 - 67,710 人阅读" href="https://coolshell.cn/articles/5701.html" target="_blank">SteveY对Amazon和Google平台的吐槽</a>》的人都知道，Amazon内部系统的SOA架构（这个SOA架构离IBM定义的那个非常变态的SOA还有一定距离），但是这基本上都是依赖倒置和控制反转的思路了—— <strong>与其让我来帮你实现你的业务逻辑，不如把我的业务逻辑开放成服务的方式让你来控制</strong>。</p>
<p style="padding-left: 30px;">6）再说一个我在Amazon经历的例子。有一个项目是在给Amazon的各个商区（Marketplace）做国际出口的业务，我们先把Media类的产品（书，DVD之类的）做国际出口开放，项目不难，就是让商家同意一个法律协议（上传自己的签名），然后后台小改一下。美国的，欧洲的做的都没有问题，物流团队在出口报关单上打的都是Amazon仓库的地址和商家的签名（本来这就是错的，打的应该是商家的地址和商家的签名），但是到了日本，就出了问题，因为日本海关即要日文信息，也要商家的英文名和英文地址，而我们的系统里面只有商家的日文信息。本来，这是一个挺简单的事——数据库里加两个字段，在那个同意条款的网页上收集一下商家的英文名和地址，然后把这些信息传给后面的物流团队。物流团队一看这个，发现搞不了，因为他还要传给仓库，N多的地方都要加这两个字段，还要写下各种if (site == JP)这样的判断。物流团队不蛮干，重新设计自己的系统。做一个Document Template的东西，这个就是那个那个要贴在物流盒子上的单子。再也不让各个业务团队把那些信息传过来，而是把这个Document Template的东西传给上面的业务方，他们想怎么写就怎么写， 写完后，把这个东西传回来。于是，大家依赖了一个标准的协议，而不是一其字段。（当然，这个改动过多，为此改了半年多，不过非常值）</p>
<p>所以说啊，在跨团队的工作中，</p>
<ul>
<li>如果依赖和控制的东西过多了，就需要制定标准，倒置依赖，反转控制。</li>
</ul>
<ul>
<li>控制欲望最好不要太强，不要想着能干所有的事情，要学会控制反转和依赖倒置原则。否则只会引火烧身。</li>
</ul>
<ul>
<li>反转控制和依赖倒置是一种智慧。</li>
</ul>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-150x150.jpg" alt="从Gitlab误删除数据库想到的" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_title">从Gitlab误删除数据库想到的</a></li><li ><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-150x150.png" alt="关于高可用的系统" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_title">关于高可用的系统</a></li><li ><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/01/kiss-150x150.png" alt="从面向对象的设计模式看软件设计" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_title">从面向对象的设计模式看软件设计</a></li><li ><a href="https://coolshell.cn/articles/6950.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/21.jpg" alt="需求变化与IoC" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6950.html" class="wp_rp_title">需求变化与IoC</a></li><li ><a href="https://coolshell.cn/articles/6775.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/24.jpg" alt="Bret Victor &#8211; Inventing on Principle" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6775.html" class="wp_rp_title">Bret Victor &#8211; Inventing on Principle</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/9949.html">IoC/DIP其实是一种管理思想</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/9949.html/feed</wfw:commentRss>
			<slash:comments>66</slash:comments>
		
		
			</item>
		<item>
		<title>从面向对象的设计模式看软件设计</title>
		<link>https://coolshell.cn/articles/8961.html</link>
					<comments>https://coolshell.cn/articles/8961.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Fri, 01 Feb 2013 00:15:59 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[design pattern]]></category>
		<category><![CDATA[Object-Oriented]]></category>
		<category><![CDATA[Unix]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=8961</guid>

					<description><![CDATA[<p>前些天发了一篇《如此理解面向对象编程》的文章，然后引起了大家的热议。然后我在微博上说了一句——“那23个经典的设计模式和OO半毛钱关系没有，只不过人家用OO来实...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/8961.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/8961.html">从面向对象的设计模式看软件设计</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>前些天发了一篇《<a title="如此理解面向对象编程" href="https://coolshell.cn/articles/8745.html" target="_blank" rel="bookmark">如此理解面向对象编程</a>》的文章，然后引起了大家的热议。然后我在<a href="http://weibo.com/1401880315/z9wWHrrVR" target="_blank">微博上说</a>了一句——“<strong>那23个经典的设计模式和OO半毛钱关系没有，只不过人家用OO来实现罢了……OO的设计模式思想和Unix的设计思想基本没什么差别</strong>”，结果引来了一点点争议。所以，我写下这篇文章把我的观点说明一下。我希望这样可以让大家更容易地理解什么是设计模式。<strong>我顺便帮OO和 Unix/Linux搞搞基</strong>。</p>
<h4>什么是模式</h4>
<p>在正式说明GoF的那23个经典的设计模式其实和OO关系不大并和Unix的设计思想很相似的这个观点之前，让我先来说说什么是模式？设计模式的英文是Design Pattern，模式是Pattern的汉译。所谓Pattern就是一种规则，或是一种模型，或是一种习惯。Pattern这个东西到处都是，并不只有技术圏子里才有。比如：</p>
<ul>
<li>文章有文章的Pattern。如新闻有新闻的Pattern（第一段话简述了整个新闻），诗歌总是抒情的，论文总是死板的，讲稿总是高谈的，漫画总是幽默的，……</li>
<li><span style="line-height: 13px;">小说有小说的Pattern。比如，</span>
<ul>
<li><span style="line-height: 13px;">武侠小说必然要整个武林大会，整几个NB的武功和大师，分个正派和反派，还有一个或数个惊天阴谋，坏人总是要在一开始占尽优势，好人总是要力挽狂澜……</span></li>
<li><span style="line-height: 13px;">言情小说总是要有第三者，总是要有负心人，里面的女子总是要哭得死去活来，但又痴心不改，……</span></li>
</ul>
</li>
<li> 新闻联播的模式是：头10分钟领导很忙，中间10分钟人民很幸福，后10分钟国外很乱。中国政府官方宣传稿也模式也很明显，各种赞美，口号，胜利，总是要坚持个什么，团结个什么，迈向个什么，某某精神，某某思想，群众情绪稳定，不明真相，等等……</li>
<li>春节的模式是，回家，吃饺子，放个鞭炮，给压岁钱，同学聚会…… 同学聚会的模式基本上都是在饭桌上回忆一下校园时光，比较一下各自的当前处境，调戏一下女同学……</li>
<li>…… ……</li>
</ul>
<p>这就是Pattern，只要你细心观察，你会发现这世间有很多很多的Pattern。</p>
<p><span id="more-8961"></span></p>
<h4>GoF的23个设计模式</h4>
<p>《<a href="http://product.china-pub.com/25961" target="_blank">设计模式</a>》这本书中，GoF这四个人总结了23个经典的面向对象的设计模式，某中有5个创建模式，7个结构模式，11个行为模式。<strong>很多人都会觉得这是面向对象的设计模式，很多人也觉得非面向对象不能用这些模式。我觉得这是一种教条主义。</strong>就像《<a title="各种流行的编程风格" href="https://coolshell.cn/articles/2058.html" target="_blank">那些流行的编程方法</a>》中的“设计模式驱动型编程”一样，就像《<a title="如此理解面向对象编程" href="https://coolshell.cn/articles/8745.html" target="_blank">如此理解面向对象</a>》一样的那么的滑稽。</p>
<p>好了，回到我的论点——“<strong>GoF的这23个设计模式和OO关系不大，并且和Unix的设计思想基本一致，只不过GoF用OO实现了它们</strong>”，就像我上面说过的那些生活中的Pattern一样，只要你仔细思考，你会发现这23个设计模式在我们的生活和社会中也能有他们的身影。而且也一样可以用OO的方式实现之。</p>
<p>让我们来看看这23个经典的设计模式中的几个常用的模式：</p>
<p><strong>Factory 模式</strong>，这个模式可能是是个人都知道的模式。这个模式在现实社会中就像各种工厂一样，工厂跨界的不多，基本上都是在生产同一类的产品，有的生产汽车，有的生产电视，有的生产衣服，有的生产卫生纸……基本上来说，一个生产线上只有做同一类的东西。这和Factory模式很相似。编程中，像内存池，线程池，连接池等池化技术都是这个模式，当然，Factory给你的一个对象，而不单单只是资源，factory创建出来的对象都有同样的接口可以被多态调用。<strong>这其实和Unix把所有的硬件都factory成文件一样，并提供了read/write等文件操作来让你操作任意设备的I/O</strong>。</p>
<p><strong>Abstract Factor</strong>y：抽象工厂这个模式是创建一组有同一主题的不同的类。这个模式在现实社会当中也有很多例子，比如：</p>
<ul>
<li>移动公司的合约机计划，88套餐（通话100分钟，短信100条，彩信，20条，上网200M），128套餐（通话200分钟，短信150条，彩信50条，上网500M）……</li>
</ul>
<ul>
<li>家里的装修，总是要有厨卫，有门，有灯，有沙发，有茶几，有床，有衣柜，有电视，有冰箱，有洗衣机……，这些都是必需的，只是每个家庭里的具体装修不一样。</li>
</ul>
<ul>
<li>Diablo游戏中的Normal，Hard，Nightmare，Hell模式，这些模式的怪和场景和故事情况都差不多，就是每个场景的怪物和装备的属性不一样。或是WarCraft中的地图就是一个Abstract Factory模式(注：Warcraft的地图什么都能干)。这和学校中的小学，初中，高中，大学差不多，都是一样的学习环境，一样的教学方式，一样的教室，都要期中考和期末考，都有班长和科代表，就是学的东西的难度不一样，但基本上都是语文，英语，数，理，化，还有永远都有的政治课。学校就是一个抽象工厂。</li>
</ul>
<p>这就是抽象工厂的业务模型（或是：Business Pattern），你觉得是不是不一定非要用OO来实现这样的模式？（我们思考一下，我们会不会被先入为主了，觉得不会OO都不知道怎么实现了），不用OO，用相同格式但内容不同的配置文件是不是也能实现？在Unix下<strong>，抽象工厂这个模式在Unix下就像是/etc/rcX.d下的那些东西，1代表命令行单用户，2，代表命令行多用户，3代表命令行多用户完整模式启动，5代表图形界面启动，0代表关机，6代表重启，你要切换的话，init &lt;X&gt;就行了</strong>。</p>
<p><strong>Prototype模式</strong>，原型模式，复制一个类的实现。这个模式在现实中的例子也有很多：传真，复印，都是这个模式。<strong>Unix进程和Github项目的Fork就是一种。进程fork明显不是OO的模型</strong>（参看：<a title="一个fork的面试题" href="https://coolshell.cn/articles/7965.html" target="_blank">关于Fork的一道面试题</a>）。用非OO的方法同样可以实现这个模式。</p>
<p><strong>Singleton模式</strong>，单例模式。生活中，公司只有一个CEO，法律限制你只能有一个老婆，你只能有一个身份证号，一个TCP端口只能被一个进程使用，等等。软件开发方面，并不一定只有OO才能做到，你可以用一个全局变量，一个中心服务器，甚至可以使用行政手段来约束开发中不会出现多个实例。<strong>Unix下实现单例进程的一个最常用的实践是在进程启动的时候用“(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)”模式打开一个“锁文件”</strong>。</p>
<p><strong>Adapter模式</strong>，适配器模式。可以兼容欧洲美国中国的插头或插座，万能读卡器，可以播放各种格式多媒体文件的插放器，可以解析FTP/HTTP/HTTPS/等网络协议的浏览器，可以兼容各大银行的银联接口、支付宝、Paypal、VISA等银行接口，可以适配各种后端的解释器的Nginx或Apache，等等。用非OO的编程方式就是重新包装成一个标准接口。<strong>这个模式很像Unix下的/dev下的那些文件，操作系统把系统设备适配成文件，于是你就可以使用read/write来进行读写了</strong>。</p>
<p><strong>Bridge模式</strong>，桥接模式。这个模式用的更多，比如一个灯具可以接各种灯泡或灯管，一个电钻可以换上不同的钻头来适应不同的材料，一辆汽车可以随时更换不同的轮胎来适应不同的路面，你的桌面可以随时更换一个图片来适应你的心情，你的单反相机可以更换不同的镜头来拍不同的照片…… 桥接模式说白了就是组件化，模块化，可以自由拼装。在OO中，其主要是通过让业务类组合一个标准接口来完成，这在非OO的程序设计中用得实在是太多了，主要是通过回调函数或是标准接口来实现。这个也是Unix设计哲学中的主要思想。<strong>在Unix中，文件的权限使用的就是Bridge模式，标准接口是用户，用户组和其它，rwx三个模式，然后用 chmod/chown改一改，这文件就有不同的属主和属性了</strong>。</p>
<p><strong>Decorator模式</strong>，装饰模式。这个模式在生活中太多了，你给你的手机或电脑贴个什么，挂个什么，吃东西的时候加点什么佐料，多点肉还是多个蛋，一个Unix/Linux命令的各种参数是对这个命令的修饰，等等。<strong>我觉得这个模式在Unix中最经常的体现就是通过管道把命令连接起来来完成一个功能</strong>，比如：ps -elf  是列进程的，用管道 grep hchen就可以达到过滤的目的，grep的逻辑没有侵入ps中，grep 修饰了 ps，但是其组合起来完成了一个特定的功能。可见，这和OO没有什么关系。</p>
<p><strong>Facade模式，</strong>这个模式我们每个人从会编程的时候就在无意识地用这个模式了。这个模式就是把一大堆类拼装起来，并统一往外提供提口。在现实生活中这样的例子太多了，比如：旅行社把机票，酒店，景点，导游，司机，进店打了一个包叫旅行；IBM把主机，存储，OS，J2EE，DB，网络，流程打了个包叫企业级解决方案。Unix中最典型的一个例子就是用Shell脚本组合各种命令来创造一个新的功能，这是的Shell中的各种命令通过标准I/O这个接口进行组合交互。</p>
<p><strong>Proxy模式</strong>，代理模式。我们租个房，买个机票，打个官司，都少不了代理，人大代表代理了老百姓去行使政治权力。我们去饭馆里吃饭也是一种代理模式，因为我们只管吃就好了，洗菜做饭洗碗的工作都被Proxy帮你干了，于是你就省事多了。操作系统就是硬件的代理，CDN就是网站的代理，……使用代理你可以让事情变理更简单，也可以在代理层加入一些权限检查，这样可以让业务模块更关注业务，而把一些非业务的事情剥离出来交给代理以完成解耦。可见这个模式和OO没啥关系。<strong>Unix下这个模式最佳体现就是Shell，它代理了系统调用并提供UI</strong>。还有很多命令会帮你把/proc目录下的那些文件内容整理和显示出来。</p>
<p><strong>Chain of Responsibility模式</strong>，劫匪来抢银行，保安搞不定，就交给110，110搞不定就交给武警。有什么事件发生时的响应的Escalation Path，办公中的逐级审批。这个模式用一个函数指针数组或是栈结构就可以实现了。这个思想很像编程中的异常处理机制，一层一层地往上传递异常直到异常被捕捉。<strong>在Unix下，一个最简单的例子就是用 &amp;&amp; 或 || 来把命令拼起来，如：cmd1 &amp;&amp; cmd2  或 cmd3 || cmd4 ， 如果cmd1失败了，cmd2就不会执行，如果cmd3失败了，cmd4才会执行。</strong>如： cd lib &amp;&amp; rm -rf .o 或 ping -c1 coolshell.cn &amp;&amp; ssh haoel@coolshell.cn</p>
<p><strong>Command模式</strong>，这恐怕是软件里最多的模式了，比如：编译器里的Undo/Redo，宏录制。还有数据库的事务处理，线程池，设置向导，包括程序并行执行的指令集等等。这个模式主要是把一个对象的行为封装成一个一个的有相同接口的command，然后交给一个统一的命令执行器执行或管理这些命令。<strong>这个模式和我们的Unix/Linux机器启动时在/etc/init.d下的那些S和K开头的脚本很像，把各种daemon的启动和退出行为封装成一个脚本其支持reload/start/stop/status这样的命令，然后把他们按一定的规范做符号链接到/etc/init.d目录下，这样操作系统就会接管这些daemon的启动和退出</strong>。</p>
<p><strong>Observer模式</strong>，观察者模式，这个模式也叫pub-sub模式，很像我们用手机订阅手机报，微博的follow的信息流也是这样的一个模式。MVC中的C会sub V中的事件，用非OO的方式其实也是一个回调函数的事。在很多异步系统中，你需要知道最终的调用有没有成功，比如说调用支付宝的支付接口，你需要向支付宝注册一个回调的接口，以便支付宝回调你。<strong>Linux下的一些系统调用如epoll/aio/inotify/signal都是这种思路</strong>。</p>
<p><strong>Strategy 模式</strong>，策略模式，这个模式和Bridge模式很像，只不过Bridge是结构模式，其主要是用于对象的构造；而Strategy是行为模式，主要是用于对象的行为。策略模式很像浏览器里的各种插件，只要你装了某个插件，你就有某个功能。你可以安装多个插件来让你的浏览器有更多的功能（书本上的这个模式是你只能选用一个算法，当然，我们不用那么教条）。<strong>就像《<a title="你可能不知道的Shell" href="https://coolshell.cn/articles/8619.html" target="_blank">你可能不知道的Shell</a>》中的那个设置设置$EDITOR变量后可以按ctrl+x e启动编译器，或是用set -o vi或set -o emacs 来让自己的shell像vi或 emacs 一样，或是像find -exec或xargs一样的拼装命令</strong>。</p>
<p><strong>Bridge 和 Strategy是OO设计模式里的“Favor Composition Over Inheritance” 的典范，其实现了接口与实现分离的</strong>。Unix中的Shell就是一种，你可随意地更换不同的Shell。还有Emacs中的LISP驱动C，C实现了引擎，交给LISP实现逻辑。把程序分为前端和后端，通过socket专用应用协议进行通讯，前端实现策略，后端实现机制。再看看makefile把编译器和源代码的解耦，命令行输出这个接口可以把一个复杂的功能解耦并抽像成各种各样小而美的小功能命令，等等这样的例子，你会发现，还有大量的编程框架都会多少采用这样的思想，可以让你的软件像更换汽车零件一样方便。我在用<a title="用Unix的设计思想来应对多变的需求" href="https://coolshell.cn/articles/7236.html" target="_blank">Unix的设计思想来应对变更的需求</a>中说过灯具厂，灯泡厂，和开关厂的例子。</p>
<h4>后记</h4>
<p>因为写作仓促，上面的那些东西，可能会你让你觉得有些牵强，那么抱歉了，你可以帮我看看在生活中和 Unix里有没有更帅的例子。</p>
<p>不过，我们会发现上面OO搞出来的那么多模式在Unix下看来好像没有那么复杂，而且Unix下看起来并没有那么多模式，而且Unix中的设计模式无非就是这么几个关键词：<strong>单一，简洁，模块，拼装</strong>。我们再来看看OO设计的两大准则：<strong>1）钟情于组合而不是继承，2）依赖于接口而不是实现</strong>。还有S.O.L.I.D原则也一样（如果你仔细观察，你会发现SOLID原则在Unix下也是完美地体现）。你看，Unix和OO设计模式是不是完美的统一吗？</p>
<p>我有种强烈的感觉——<strong>Unix对这些所谓的OO的设计模式实现得更好</strong>。因为Unix就一条设计模式！再次推荐《<em><a href="http://book.douban.com/subject/5387401/" target="_blank">The Art of Unix Programming</a></em>》</p>
<p><img decoding="async" loading="lazy" class="size-full wp-image-8967 aligncenter" alt="Unix Kiss" src="https://coolshell.cn/wp-content/uploads/2013/01/kiss.png" width="468" height="219" srcset="https://coolshell.cn/wp-content/uploads/2013/01/kiss.png 468w, https://coolshell.cn/wp-content/uploads/2013/01/kiss-300x140.png 300w" sizes="(max-width: 468px) 100vw, 468px" /></p>
<h4>餐后甜点</h4>
<p>我上面提到了《<em><a href="http://book.douban.com/subject/5387401/" target="_blank">The Art of Unix Programming</a></em>》，所以我有必要再谈谈这本书中我中毒最深的一章《模块性：保持清晰和简洁》中所谈到的胶合层。</p>
<p>胶合层这一节中说了，我们开发软件一般要么Top-Down，要么Bottom-Up，这两种方法都有好有不好。顶层一般是应用逻辑层，底层一般是原语层（我理解为技术沉淀层，或是技术基础层）。自顶向下的开发，你可能会因为开发到底层后发现底层可沉淀的东西越来越不爽（因为被可能被很多业务逻辑所侵入），如果自底向上的开发，你可能越到上层你越发现很多你下面干的基础上工作有很多用不上（比如干多了）。所以，最好的方式是同时进行，一会顶层，一会底层，来来回回的开发——说白了就是在开发中不断的重构，边开发边理解边沉淀。</p>
<p>无论怎么样，你会发现需要一层胶合层来胶合业务逻辑层和底层原语层（软件开发中的业务层和技术层的胶合），Unix的设计哲学认为，这层胶合层应该尽量地薄，胶合层越多，我们就只能在其中苦苦挣扎。</p>
<p>其实，<strong>胶合层原则就是分离原则上更为上层地体现，策略（业务逻辑）和机制（基础技术或原语）的清楚的分离。你可以看到，OO和Unix都是在做这样的分离。但是需要注意到的时，OO用抽象接口来做这个分离——很多OO的模式中，抽象层太多了，导致胶合层太过于复杂了，也就是说，OO鼓励了——“厚重地胶合和复杂层次”，反而增加了程序的复杂度（这种情况在恶化中）。而Unix采用的是薄的胶合层，薄地相当的优雅</strong>。（通过这段话的描述，我相信你会明白了《<a title="如此理解面向对象编程" href="https://coolshell.cn/articles/8745.html" target="_blank" rel="bookmark">如此理解面向对象编程</a>》中的个例子——为什么用OO来实现会比用非OO来实现更为地恶心——那就是因为OO胶合层太复杂了）</p>
<p><strong>OO的最大的问题就——接口复杂度太高，胶合层太多！</strong>（注：Unix编程艺术这本书里说了软件有三个复杂度：代码量、接口、实现，这三个东西构成了我们的软件复杂度）</p>
<h4>再送一个果盘</h4>
<p>大家一定记得《<a title="SteveY对Amazon和Google平台的长篇大论 - 60,581 人阅读" href="https://coolshell.cn/articles/5701.html">SteveY对Amazon和Google平台的长篇大论</a>》中Amazon中那个令人非常向往的SOA式的架构。因为以前在Amazon，有些话不好说。现在可以说了，我在Amazon里，我个人对这个服务化的架构相当的不待见，太复杂，复杂以乱七八糟，方向是好的，想法也是好的，但是这东西和OO一样，造成大量的接口复杂度，今天的Amazon，完全没人知道各个服务是怎么个调用的，一团乱麻（其内部并不像你看到的AWS那么的美妙。注：AWS是非常不错的，是相当好的设计）。</p>
<p><strong>那么我们怎么来解决SOA的接口复杂度问题？其实，Unix早就给出了答案——数据驱动编程</strong>（详见：《Unix编程艺术》的第9.1章），在我离开Amazon的时候，美国总部的Principle SDE们在吐槽今天Amazon的SOA架构，更好的架构应该是数据驱动式的。（今天还在Amazon的同学可以上内网boardcast上看看相关的Principle Talk视频）</p>
<p>（瞎扯一句：这本来是我想在2012年杭州QCon上的分享的一个主题，无奈当时被大会组织者给拒了，所以只好讲了一个《建一支小团队》，今天有多人还是不能明白甚至反感我的那个《小团队》的演讲，但是我相信那是必然的趋势，就像十年前大家在说“程序员只能干到30岁”时，当时的我我却毫不犹豫地相信十年后，30岁以上的有经验的老程序员一定会成为各个公司角逐和竟争的红人）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/7236.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/05/Bannière-Unix-linux-150x150.jpg" alt="用Unix的设计思想来应对多变的需求" width="150" height="150" /></a><a href="https://coolshell.cn/articles/7236.html" class="wp_rp_title">用Unix的设计思想来应对多变的需求</a></li><li ><a href="https://coolshell.cn/articles/4535.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/1.jpg" alt="一些软件设计的原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4535.html" class="wp_rp_title">一些软件设计的原则</a></li><li ><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/07/cache-150x150.png" alt="缓存更新的套路" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_title">缓存更新的套路</a></li><li ><a href="https://coolshell.cn/articles/6950.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/21.jpg" alt="需求变化与IoC" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6950.html" class="wp_rp_title">需求变化与IoC</a></li><li ><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x-150x150.png" alt="我做系统架构的一些原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_title">我做系统架构的一些原则</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/8961.html">从面向对象的设计模式看软件设计</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/8961.html/feed</wfw:commentRss>
			<slash:comments>92</slash:comments>
		
		
			</item>
		<item>
		<title>用Unix的设计思想来应对多变的需求</title>
		<link>https://coolshell.cn/articles/7236.html</link>
					<comments>https://coolshell.cn/articles/7236.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Thu, 03 May 2012 00:14:20 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[design pattern]]></category>
		<category><![CDATA[Unix]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=7236</guid>

					<description><![CDATA[<p>之前，@风枫峰 在“这是谁的错？”中说过开发团队对需求来者不拒，而@weidagang 也在“需求变更和IoC”中说过用IoC来最大程度地解决需求变更。今天我也...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/7236.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/7236.html">用Unix的设计思想来应对多变的需求</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>之前，@<a title="风枫峰" href="http://weibo.com/hfcc?source=webim" target="_blank">风枫峰</a> 在“<a title="这到底是谁之错？" href="https://coolshell.cn/articles/7126.html" target="_blank">这是谁的错？</a>”中说过开发团队对需求来者不拒，而@<a title="weidagang" href="http://weibo.com/weidagang" target="_blank">weidagang</a> 也在“<a title="需求变化与IoC" href="https://coolshell.cn/articles/6950.html" target="_blank">需求变更和IoC</a>”中说过用IoC来最大程度地解决需求变更。今天我也想从Unix设计思想的角度来说说什么是好的软件设计，什么样的设计可以把需求变更对开发的影响降低。（<strong>注意</strong>：这并不能解决用户或是PM的无理需求，面对无理需求，需要仔细分析需求，而用技术的手段无法搞定这个事，但是可以减轻需求变更带来的痛苦） 我曾经在<a href="https://coolshell.cn/articles/2324.html" target="_blank">《Unix传奇》的下篇</a>中写过一些Unix的设计哲学和思想（这里重点推荐大家看一下《<em><a href="http://product.china-pub.com/197413" target="_blank">The Art of Unix Programming</a></em>》，我推荐过多次了），以前也发过一篇《<a title="一些软件设计的原则" href="https://coolshell.cn/articles/4535.html" target="_blank">一些软件设计的原则</a>》，不过，这些东西都太多了，记不住。其实，这么多年来，我的经验告诉我，<strong>无论是Unix设计，还是面向对象设计，还是别的什么如SOA，ECB，消息，事件，MVC，网络七层模型，数据库设计，等等，他们都在干三件事——<span style="color: #cc0000;">解耦，解耦，还是解耦</span>！</strong>所谓解耦，就是让软件的模块和模块间尽量少地依赖起来。</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter" title="Unix" src="https://coolshell.cn/wp-content/uploads/2012/05/Bannière-Unix-linux.jpg" alt="" width="448" height="173" /></p>
<h4>现实当中的例子</h4>
<p>让我先举几个现实生活中的例子：</p>
<p style="padding-left: 30px;">1、现实社会中，制造灯具的工厂完全不关心制造灯泡的工厂，制造灯泡的工厂完全不关心制造灯具的工厂，但是，灯泡和灯饰可以很完美地组合成用户所喜欢的样子（这和@<a title="weidagang" href="http://weibo.com/weidagang" target="_blank">weidagang</a> 在“<a title="需求变化与IoC" href="https://coolshell.cn/articles/6950.html" target="_blank">需求变更和IoC</a>”说到的那个PC的例子相仿）。他们是怎么做到的？</p>
<p style="padding-left: 30px;">2、互联网上，做网站的人完全不用关心用户在用什么样的操作系统，什么样的客户端浏览器（当然事实上，浏览器的不标准让网站那边很头痛，这里只是举个例），反过来，上网的人也不关心做网站的人在用什么的技术开发网站。但是大家在完全不关心对方的情况下，可以很正常地协同工作在一起。为什么？</p>
<p><span id="more-7236"></span> 这样的例子太多了。为什么可以做成这样呢？因为大家依赖的是一个接口，灯具和灯泡并不互相依赖，他们依赖的是一个接口，做网站的人和浏览网站的人依赖的还是接口——HTTP协议。这就是面向对象的核心思想——依赖于接口而不是实现，这就是解耦。<strong>当你看过这两个例子以后，我希望你以后设计的软件至少不能比我们现实社会中的这些方法要差</strong>。不然，你就是在让社会倒退了，呵呵。 你会说，这和Unix，和应对需求变化有什么关系？好让我们再来看一下Unix的设计。</p>
<h4>Unix设计的例子</h4>
<p>下面是几个Unix下的例子：</p>
<p style="padding-left: 30px;">1、Unix下，所有的硬件都可以通过文件的方式存取。其统统在/dev下。于是，软件和硬件的耦合被解开了，操作系统只需要把硬件统统变成文件，而程序只需要使用三个东西，一个是fd，一个是read()，一个是write()，就可以来操作任意的硬件了，这就是抽象，简单到不行。</p>
<p style="padding-left: 30px;">2、Unix下，所有的命令都可以用管道串起来（管道绝对是个伟大的发明），这样，所有的命令间的交互全部解耦到只依赖于STD_IN, STD_OUT设备上。最酷的是，用户可以使用管道任意地拼装那些命令，以完成各式各样的功能。管道这个设计思想可以映射为今天的Web Service，你可以任意地拼装各种Web Service。</p>
<p>看到这里，你会发现，这还是解耦，本质上来说，也是一种依赖倒置——OOD的精髓。但是，Unix还不仅仅是这些。我们再来看几个例子：</p>
<p style="padding-left: 30px;">1、Unix下，软件都是绿色地安装。在iOS上更明显——各个程序间基本上互不干扰，这个程序产生的垃圾文件不会影响到另一个程序。你删掉一个程序不会让另一个程序不举，各是各的空间。你可以删除这些程序，只要把内核心留着，系统照样可以启动。</p>
<p style="padding-left: 30px;">2、Unix下，你可以通过设置一些环境变量，让多种环境同时存在，比如：某个LAMP用的是Apache 2.0, Mysql 4.0, PHP 4.0，某个LAMP用的是Apache 2.2, Mysql 5.0，PHP5.3，你不但可以方便地在系统中切换这两个环境，你甚至还可以同时启动他们。</p>
<p style="padding-left: 30px;">3、Unix下，你可以随意地替换你想要的程序。比如，你不喜欢bash，你可以替换成ksh/csh等，你不喜欢awk ，你可以替换成 gawk ，所有的东西都像零件一样，你不喜欢什么，你就可以替换什么。</p>
<p>这三个例子告诉了我们——<strong>当你把你的软件设计地耦合度非常地低时，你可以随意地组合，随意地安排你的系统</strong>。相当的灵活，灵活到Windows到今天都学不会。</p>
<h4>应对需求变化</h4>
<p>看到这里，你可能明白我想说的是什么了，你可能开始觉得怎么样的系统设计会更有效了。如果你还记得《<a title="SteveY对Amazon和Google平台的长篇大论" href="https://coolshell.cn/articles/5701.html" target="_blank">Steve Y 对平台的长篇大论</a>》，你就会知道我想说什么了。是的，我想说的就是，<strong>当你真正了解了Unix的设计思想后，你会觉得今天的很多东西都是对Unix设计思想的一种传承或是变种</strong>。这种东西就是：</p>
<p style="padding-left: 30px;">1）<strong>解耦，解耦，解耦</strong>。尽量地让你的模块不要在实现上耦合，而是耦合某个规范，某个标准。</p>
<p style="padding-left: 30px;">2）<strong>KISS，KISS，KISS</strong>。要做到高度解耦，你的模块就一定要很简单，当然不是说简单到只有几行代码，而是简单到只干一件事，并把这件事干到极致。然后通过某个标准拼装起来。</p>
<p style="padding-left: 30px;">3）<strong>拼装，拼装，拼装。</strong>我想不起来是谁说的了，这句话是这样的，当我想用一个模块的时候，我直接调用就好了，没有必要像C或Java一样，还要编译。是的，拼装需要一个框架，需要一种标准协议，然后让所有的系统都耦合在这种规范上，各自独立运行，就像一个机器上的各个部件一样，当我觉得这个部件不爽，换了就是了。（例如，当我们在尝试不同的算法的时候）</p>
<p>想想建材和家俱市场，无论用户过来想装修什么，我都可以满足用户的不同需求，只要你是和家装相关，我基本上都能满足你，不是吗？无论你怎么变，只要不变态，我基本上都可以满足你。这就是解耦，拼装带来的好处。 你可能会说我说得太简单了，另一方面，你可能觉得有一些系统这样做没必要，我承认，不过，你可以有选择的或多或少地试试。（其实，我相信你已经在不自觉得或多或少地使用这种方式开发软件了） （全文完）</p>
<div></div>
<p><!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/01/kiss-150x150.png" alt="从面向对象的设计模式看软件设计" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_title">从面向对象的设计模式看软件设计</a></li><li ><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/07/cache-150x150.png" alt="缓存更新的套路" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_title">缓存更新的套路</a></li><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/6950.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/21.jpg" alt="需求变化与IoC" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6950.html" class="wp_rp_title">需求变化与IoC</a></li><li ><a href="https://coolshell.cn/articles/4535.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/1.jpg" alt="一些软件设计的原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4535.html" class="wp_rp_title">一些软件设计的原则</a></li><li ><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x-150x150.png" alt="我做系统架构的一些原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_title">我做系统架构的一些原则</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/7236.html">用Unix的设计思想来应对多变的需求</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/7236.html/feed</wfw:commentRss>
			<slash:comments>90</slash:comments>
		
		
			</item>
		<item>
		<title>需求变化与IoC</title>
		<link>https://coolshell.cn/articles/6950.html</link>
					<comments>https://coolshell.cn/articles/6950.html#comments</comments>
		
		<dc:creator><![CDATA[Todd]]></dc:creator>
		<pubDate>Mon, 26 Mar 2012 03:01:07 +0000</pubDate>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[design pattern]]></category>
		<category><![CDATA[IoC]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=6950</guid>

					<description><![CDATA[<p>【感谢 Todd投递本文 – 微博帐号：@weidagang 】 需求又变了，怎么办？ 先上一个轻松的段子： 程序员XX遭遇车祸成植物人，医生说活下来的希望只有...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/6950.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/6950.html">需求变化与IoC</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>【<span style="color: #cc0000;">感谢 Todd投递本文 – 微博帐号</span>：@<a title="weidagang" onclick="pageTracker._trackPageview('/outgoing/weibo.com/weidagang?referer=http%3A%2F%2Fcoolshell.cn%2F');" href="http://weibo.com/weidagang" target="_blank">weidagang</a> 】</p>
<h4>需求又变了，怎么办？</h4>
<p>先上一个轻松的段子：</p>
<blockquote><p>程序员XX遭遇车祸成植物人，医生说活下来的希望只有万分之一，唤醒更为渺茫。可他的Lead和亲人没有放弃，他们根据XX工作如命的作风，每天都在他身边念：“XX，需求又改了，该干活了，你快来呀！”，奇迹终于发生了，XX醒来了，第一句话：“需求又改了？”。</p></blockquote>
<p>这个段子用幽默的方式反映了需求变化是每一个程序员、架构师或项目经理都会经常遇到的问题。面对这个问题，不同的人有不同的应对之道，最近微博上有一段关于需求变化的讨论：</p>
<blockquote><p>@假装刺猬的猪：我们在软件开发过程中，会持续碰到客户需求变更的情况。如果没有领域建模，我们单纯将问题使用直觉将问题解决，那么等到客户需求变更或者有新的需求时，就会面临一个僵硬的前设计！无法在以前的设计上持续深入的优化模型，导致需求变更无法及时深化。设计实现均滞后与变更！</p>
<p>@高煥堂: &lt;碰到客户需求变更的情况&gt;是合理的；但&lt;领域建模&gt;不是美好的手段!!!</p>
<p>@weidagang: 要不被客户牵着鼻子走，需要自己有很强的设计能力，<strong>反过来</strong>让客户跟着你的设计来满足你的要求。能做到这点的公司很少，但这是软件行业唯一有希望的出路。</p>
<p>@高煥堂: &lt;这是软件行业唯一有希望的出路&gt;。 Great!!</p></blockquote>
<p>如何应对需求变化？ @假装刺猬的猪 的答案是领域建模，并持续优化模型，适应需求的变化。@高煥堂 则认为领域建模不是美好的手段。我进一步补充，应该<strong>“反过来”</strong>让自己在需求变化中处于主导地位，而不是被动地适应。</p>
<p><span id="more-6950"></span></p>
<h4>控制反转 (IoC)</h4>
<p>什么样就算是“反过来”了呢？举个例子：</p>
<blockquote><p>用户想购买一台普通PC，他只想电脑能流畅运行魔兽世界，他根本不想知道什么叫主板，什么叫内存，什么叫CPU；但他不得不接受必须购买主板、CPU、内存的事实，因为PC架构是产业标准，而不是由用户定的。客户有选择的权利，但没有设计的权利，客户的需求必须在设计框架下得到满足。</p></blockquote>
<p>这里我们要问PC架构是保护了谁的利益？显然，直接的受益者是厂商。如果没有PC架构的保护，厂商就会直接面对客户，客户说我需要功能A，我马上分析设计实现功能A；客户说我要功能B，我马上分析设计实现功能B &#8230; 有了PC架构的保护，厂商就变得更加强势，用户的一切需求都必须在PC架构下来谈。厂商可以倾听用户的声音，不断改进产品，但设计主导权永远在自己手中。我们IT行业常常用“做产品”和“做项目”的视角来区分不同的公司，但很少有人用“做设计”的视角来看。实际上，关键的问题在于设计主导权是厂商还是在客户。如果设计主导权在客户，不管是做产品、做服务还是做项目，其命运必然是疲于奔命应付客户，最后获得微薄的利润；如果设计主导权在厂商，不管做产品、做服务还是做项目都能有更多的话语权和更高的利润。</p>
<p>当然，光有设计还不够，必须客户接受才能起到通过设计掌握主导权的作用。这一方面需要自己具有很强的设计能力，如苹果就是以设计能力著称的公司；另一方面，和其他厂商结盟壮大阵营也是一种方法，如最著名的Wintel联盟(Windows+Intel)，以及现在的日益壮大的Android阵营都属于此类。假如有厂商不遵守PC产业标准，说我的PC就没有主板，没有显卡，因为用户更不不需要这些东西；那么，它要么像苹果一样独树一帜成为一种新的标准，要么无人问津。</p>
<p>我所谈到的“反过来”本质上就是软件设计中的控制反转 (Inversion of Control, IoC)思想。IoC是每一个初级程序员向高级进阶所需要了解的<strong>最重要</strong>的设计思想。由于Spring等开发框架的流行，知道IoC概念的程序员不在少数，但不少人对于IoC的理解仅仅停留在通过依赖注入 (Dependency Injection)实现解耦这个层面。实际上，IoC的应用不仅包括解耦，它还是框架的基本原理，在非计算机领域，IoC也是无处不在，如果你能从上面的例子中体会到IoC，这才算是融会贯通了。</p>
<p>软件开发中一种最常见的模式是“以用户为出发点，以需求分析为核心”。该模式提倡从用户需求中分析推导出设计和实现，比如，TDD式的设计正是这类典型。而IoC式的软件设计与此截然相反，IoC的设计是一种“以愿景（自身利益是愿景的重要方面）为出发点，以架构为核心”的模式。如果用户的需求是一台电脑，我们如何能通过第一种模式分析需求推导出“主板-CPU-内存-外设”的PC架构呢？恐怕很难。IoC式的设计是以用户看不见摸不着的架构为核心，自己主导设计，用户需求是设计的约束条件和验证手段，而不是出发点和目标。我们想要掌握主动，不被需求变化搞得疲于奔命，就必须熟练使用第二种模式。</p>
<p>我们的人生都被环境和各种客观条件所束缚，多数人只能随波逐流，听从命运的安排。你有没有想过要拥有人生的主导权呢？既然你是程序员，你懂IoC，你能否设计自己的人生框架呢？Yes，you can!<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/07/cache-150x150.png" alt="缓存更新的套路" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_title">缓存更新的套路</a></li><li ><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/01/kiss-150x150.png" alt="从面向对象的设计模式看软件设计" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_title">从面向对象的设计模式看软件设计</a></li><li ><a href="https://coolshell.cn/articles/7236.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/05/Bannière-Unix-linux-150x150.jpg" alt="用Unix的设计思想来应对多变的需求" width="150" height="150" /></a><a href="https://coolshell.cn/articles/7236.html" class="wp_rp_title">用Unix的设计思想来应对多变的需求</a></li><li ><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x-150x150.png" alt="我做系统架构的一些原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_title">我做系统架构的一些原则</a></li><li ><a href="https://coolshell.cn/articles/21263.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2020/12/go.k8s-150x150.png" alt="Go 编程模式：k8s Visitor 模式" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21263.html" class="wp_rp_title">Go 编程模式：k8s Visitor 模式</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/6950.html">需求变化与IoC</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/6950.html/feed</wfw:commentRss>
			<slash:comments>73</slash:comments>
		
		
			</item>
		<item>
		<title>多版本并发控制(MVCC)在分布式系统中的应用</title>
		<link>https://coolshell.cn/articles/6790.html</link>
					<comments>https://coolshell.cn/articles/6790.html#comments</comments>
		
		<dc:creator><![CDATA[Todd]]></dc:creator>
		<pubDate>Tue, 13 Mar 2012 00:36:53 +0000</pubDate>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[cas]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[lock-free]]></category>
		<category><![CDATA[mvcc]]></category>
		<category><![CDATA[Performance]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=6790</guid>

					<description><![CDATA[<p>【感谢 Todd投递本文 – 微博帐号：weidagang 】 问题 最近项目中遇到了一个分布式系统的并发控制问题。该问题可以抽象为：某分布式系统由一个数据中心...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/6790.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/6790.html">多版本并发控制(MVCC)在分布式系统中的应用</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>【<span style="color: #cc0000;">感谢 Todd投递本文 – 微博帐号：</span><a title="weidagang" href="http://weibo.com/weidagang" target="_blank">weidagang</a> 】</p>
<h4>问题</h4>
<p>最近项目中遇到了一个分布式系统的并发控制问题。该问题可以抽象为：某分布式系统由一个数据中心D和若干业务处理中心L1，L2 &#8230; Ln组成；D本质上是一个key-value存储，它对外提供基于HTTP协议的CRUD操作接口。L的业务逻辑可以抽象为下面3个步骤：</p>
<ol>
<li>read: 根据keySet {k1, &#8230; kn}从D获取keyValueSet {k1:v1, &#8230; kn:vn}</li>
<li>do: 根据keyValueSet进行业务处理，得到需要更新的数据集keyValueSet&#8217; {k1&#8242;:v1&#8242;, &#8230; km&#8217;:vm&#8217;} (<strong>注</strong>：读取的keySet和更新的keySet&#8217;可能不同)</li>
<li>update: 把keyValueSet&#8217;更新到D （<strong>注</strong>：D保证在一次调用更新多个key的原子性）</li>
</ol>
<p>在没有事务支持的情况下，多个L进行并发处理可能会导致数据一致性问题。比如，考虑L1和L2的如下执行顺序：</p>
<ol>
<li>L1从D读取key:123对应的值100</li>
<li>L2从D读取key:123对应的100</li>
<li>L1将key:123更新为100 + 1</li>
<li>L2将key:123更新为100 + 2</li>
</ol>
<p>如果L1和L2串行执行，key:123对应的值将为103，但上面并发执行中L1的执行效果完全被L2所覆盖，实际key:123所对应的值变成了102。</p>
<p><span id="more-6790"></span></p>
<h4>解决方案1：基于锁的事务</h4>
<p>为了让L的处理具有可串行化特性(Serializability)，一种最直接的解决方案就是考虑为D加上基于锁的简单事务。让L在进行业务处理前先锁定D，完成以后释放锁。另外，为了防止持有锁的L由于某种原因长时间未提交事务，D还需要具有超时机制，当L尝试提交一个已超时的事务时会得到一个错误响应。</p>
<p><img decoding="async" src="http://images.cnblogs.com/cnblogs_com/weidagang2046/362318/o_conditional_update_1.PNG" alt="" /><img decoding="async" loading="lazy" class="size-full wp-image-16991 aligncenter" src="https://coolshell.cn/wp-content/uploads/2012/03/0915536496-0.png" alt="0915536496-0" width="769" height="749" srcset="https://coolshell.cn/wp-content/uploads/2012/03/0915536496-0.png 769w, https://coolshell.cn/wp-content/uploads/2012/03/0915536496-0-300x292.png 300w" sizes="(max-width: 769px) 100vw, 769px" /></p>
<p>本方案的优点是实现简单，缺点是锁定了整个数据集，粒度太大；时间上包含了L的整个处理时间，跨度太长。虽然我们可以考虑把锁定粒度降低到数据项级别，按key进行锁定，但这又会带来其他的问题。由于更新的keySet&#8217;可能是事先不确定的，所以可能无法在开始事务时锁定所有的key；如果分阶段来锁定需要的key，又可能出现死锁(Deadlock)问题。另外，按key锁定在有锁争用的情况下并不能解决锁定时间太长的问题。所以，按key锁定仍然存在重要的不足之处。</p>
<h4>解决方案2：多版本并发控制</h4>
<p>为了实现可串行化，同时避免锁机制存在的各种问题，我们可以采用基于多版本并发控制（Multiversion concurrency control，MVCC）思想的无锁事务机制。人们一般把基于锁的并发控制机制称成为悲观机制，而把MVCC机制称为乐观机制。这是因为锁机制是一种预防性的，读会阻塞写，写也会阻塞读，当锁定粒度较大，时间较长时并发性能就不会太好；而MVCC是一种后验性的，读不阻塞写，写也不阻塞读，等到提交的时候才检验是否有冲突，由于没有锁，所以读写不会相互阻塞，从而大大提升了并发性能。我们可以借用源代码版本控制来理解MVCC，每个人都可以自由地阅读和修改本地的代码，相互之间不会阻塞，只在提交的时候版本控制器会检查冲突，并提示merge。目前，Oracle、PostgreSQL和MySQL都已支持基于MVCC的并发机制，但具体实现各有不同。</p>
<p>MVCC的一种简单实现是基于CAS（Compare-and-swap）思想的有条件更新（Conditional Update）。普通的update参数只包含了一个keyValueSet&#8217;，Conditional Update在此基础上加上了一组更新条件conditionSet { &#8230; data[keyx]=valuex, &#8230; }，即只有在D满足更新条件的情况下才将数据更新为keyValueSet&#8217;；否则，返回错误信息。这样，L就形成了如下图所示的Try/Conditional Update/(Try again)的处理模式：</p>
<p><img decoding="async" src="http://images.cnblogs.com/cnblogs_com/weidagang2046/362318/o_mvcc_2.png" alt="" /><img decoding="async" loading="lazy" class="aligncenter  wp-image-16989" src="https://coolshell.cn/wp-content/uploads/2012/03/0915535U3-1.png" alt="0915535U3-1" width="746" height="483" srcset="https://coolshell.cn/wp-content/uploads/2012/03/0915535U3-1.png 826w, https://coolshell.cn/wp-content/uploads/2012/03/0915535U3-1-300x194.png 300w" sizes="(max-width: 746px) 100vw, 746px" /></p>
<p>虽然对单个L来讲不能保证每次都成功更新，但从整个系统来看，总是有任务能够顺利进行。这种方案利用Conditional Update避免了大粒度和长时间的锁定，当各个业务之间资源争用不大的情况下，并发性能很好。不过，由于Conditional Update需要更多的参数，如果condition中value的长度很长，那么每次网络传送的数据量就会比较大，从而导致性能下降。特别是当需要更新的keyValueSet&#8217;很小，而condition很大时，就显得非常不经济。</p>
<p>为了避免condition太大所带来的性能问题，可以为每条数据项增加一个int型的版本号字段，由D维护该版本号，每次数据有更新就增加版本号；L在进行Conditional Update时，通过版本号取代具体的值。</p>
<p><img decoding="async" src="http://images.cnblogs.com/cnblogs_com/weidagang2046/362318/o_mvcc_3.png" alt="" /><img decoding="async" loading="lazy" class="aligncenter  wp-image-16990" src="https://coolshell.cn/wp-content/uploads/2012/03/0915533324-2.png" alt="0915533324-2" width="706" height="265" srcset="https://coolshell.cn/wp-content/uploads/2012/03/0915533324-2.png 912w, https://coolshell.cn/wp-content/uploads/2012/03/0915533324-2-300x113.png 300w" sizes="(max-width: 706px) 100vw, 706px" /></p>
<p>另一个问题是上面的解决方案假设了D是可以支持Conditional Update的；那么，如果D是一个不支持Conditional Update的第三方的key-value存储怎么办呢？这时，我们可以在L和D之间增加一个P作为代理，所有的CRUD操作都必须经过P，让P来进行条件检查，而实际的数据操作放在D。这种方式实现了条件检查和数据操作的分离，但同时降低了性能，需要在P中增加cache，提升性能。由于P是D的唯一客户端；所以，P的cache管理是非常简单的，不必像多客户端情形担心缓存的失效。不过，实际上，据我所知redis和Amazon SimpleDB都已经有了Conditional Update的支持。</p>
<h4>悲观锁和MVCC对比</h4>
<p>上面介绍了悲观锁和MVCC的基本原理，但是对于它们分别适用于什么场合，不同的场合下两种机制优劣具体表现在什么地方还不是很清楚。这里我就对一些典型的应用场景进行简单的分析。需要注意的是下面的分析不针对分布式，悲观锁和MVCC两种机制在分布式系统、单数据库系统、甚至到内存变量各个层次都存在。</p>
<p>### 场景1：对读的响应速度要求高</p>
<p>有一类系统更新特别频繁，并且对读的响应速度要求很高，如股票交易系统。在悲观锁机制下，写会阻塞读，那么当有写操作时，读操作的响应速度就会受到影响；而MVCC不存在读写锁，读操作是不受任何阻塞的，所以读的响应速度会更快更稳定。</p>
<p>### 场景2：读远多于写</p>
<p>对于许多系统来讲，读操作的比例往往远大于写操作，特别是某些海量并发读的系统。在悲观锁机制下，当有写操作占用锁，就会有大量的读操作被阻塞，影响并发性能；而MVCC可以保持比较高且稳定的读并发能力。</p>
<p>### 场景3：写操作冲突频繁</p>
<p>如果系统中写操作的比例很高，且冲突频繁，这时就需要仔细评估。假设两个有冲突的业务L1和L2，它们在单独执行是分别耗时t1，t2。在悲观锁机制下，它们的总时间大约等于串行执行的时间：</p>
<p>T = t1 + t2</p>
<p>而在MVCC下，假设L1在L2之前更新，L2需要retry一次，它们的总时间大约等于L2执行两次的时间（这里假设L2的两次执行耗时相等，更好的情况是，如果第1次能缓存下部分有效结果，第二次执行L2耗时是可能减小的）：</p>
<p>T&#8217; = 2 * t2</p>
<p>这时关键是要评估retry的代价，如果retry的代价很低，比如，对某个计数器递增，又或者第二次执行可以比第一次快很多，这时采用MVCC机制就比较适合。反之，如果retry的代价很大，比如，报表统计运算需要算几小时甚至一天那就应该采用锁机制避免retry。</p>
<p>从上面的分析，我们可以简单的得出这样的结论：对读的响应速度和并发性要求比较高的场景适合MVCC；而retry代价越大的场景越适合悲观锁机制。</p>
<h4>总结</h4>
<p>本文介绍了一种基于多版本并发控制（MVCC）思想的Conditional Update解决分布式系统并发控制问题的方法。和基于悲观锁的方法相比，该方法避免了大粒度和长时间的锁定，能更好地适应对读的响应速度和并发性要求高的场景。</p>
<h4>参考</h4>
<ul>
<li><a href="http://en.wikipedia.org/wiki/Serializability">Wikipedia &#8211; Serializability</a></li>
<li><a href="http://en.wikipedia.org/wiki/Compare-and-swap">Wikipedia &#8211; Compare-and-swap</a></li>
<li><a href="http://en.wikipedia.org/wiki/Multiversion_concurrency_control">Wikipedia &#8211; Multiversion concurrency control</a></li>
<li><a href="http://blogs.msdn.com/b/oldnewthing/archive/2011/04/12/10152296.aspx">Lock-free algorithms: The try/commit/(try again) pattern</a></li>
<li><a href="http://aws.amazon.com/simpledb/faqs/#Does_Amazon_SimpleDB_support_transactions">Amazon SimpleDB FAQs &#8211; Does Amazon SimpleDB support transactions?</a></li>
<li><a href="http://redis.io/topics/transactions">redis &#8211; Transactions</a></li>
<li><a href="http://simpledbm.googlecode.com/files/mvcc-survey-1.0.pdf">A Quick Survey of MultiVersion Concurrency Algorithms</a></li>
<li><a href="http://www.cnblogs.com/jobs/archive/2007/11/13/957446.html">非阻塞算法思想在关系数据库应用程序开发中的使用</a></li>
</ul>
<h4>友情推荐</h4>
<p>本文的图是用我自己开发的<a href="http://textdiagram.sinaapp.com">TextDiagram</a>工具画的，欢迎试用！如果您喜欢，请推荐给朋友，谢谢！<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/17.jpg" alt="从LongAdder看更高效的无锁实现" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11454.html" class="wp_rp_title">从LongAdder看更高效的无锁实现</a></li><li ><a href="https://coolshell.cn/articles/10910.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/01/trade-off-150x150.jpg" alt="分布式系统的事务处理" width="150" height="150" /></a><a href="https://coolshell.cn/articles/10910.html" class="wp_rp_title">分布式系统的事务处理</a></li><li ><a href="https://coolshell.cn/articles/6470.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/01/12306-150x150.png" alt="由12306.cn谈谈网站性能技术 " width="150" height="150" /></a><a href="https://coolshell.cn/articles/6470.html" class="wp_rp_title">由12306.cn谈谈网站性能技术 </a></li><li ><a href="https://coolshell.cn/articles/22242.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/05/etcd-150x150.png" alt="ETCD的内存问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22242.html" class="wp_rp_title">ETCD的内存问题</a></li><li ><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x-150x150.png" alt="我做系统架构的一些原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_title">我做系统架构的一些原则</a></li><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/6790.html">多版本并发控制(MVCC)在分布式系统中的应用</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/6790.html/feed</wfw:commentRss>
			<slash:comments>98</slash:comments>
		
		
			</item>
		<item>
		<title>Bret Victor &#8211; Inventing on Principle</title>
		<link>https://coolshell.cn/articles/6775.html</link>
					<comments>https://coolshell.cn/articles/6775.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Fri, 09 Mar 2012 00:38:48 +0000</pubDate>
				<category><![CDATA[技术新闻]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[Bret Victor]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[程序员]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=6775</guid>

					<description><![CDATA[<p>Bret Victor（简历） &#8211; 苹果公司的UI交互设计师（大神级的人），在 CUSEC（Canadian University Software...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/6775.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/6775.html">Bret Victor – Inventing on Principle</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><a href="http://worrydream.com" target="_blank">Bret Victor</a>（<a href="http://worrydream.com/#!/cv/bret_victor_resume.pdf" target="_blank">简历</a>） &#8211; 苹果公司的UI交互设计师（大神级的人），在 <a href="http://cusec.net" target="_blank">CUSEC</a>（<em>Canadian University Software Engineering Conference</em>） 上做了一个题为 “Inventing on Principle” 的演讲（<a href="http://vimeo.com/36579366" target="_blank">vimeo视频链接</a>），这个演讲中展示了五个示例：</p>
<ul>
<li>用程序画树。如何把程序绘图变成实时的，如何把程序和图映射起来。</li>
<li>游戏调试。在实时编程的基础上，可以更容易的让你看到程序参数对游戏的调整，甚至对游戏过程的可视化调试。</li>
<li>算法调试。在写二分查找算法时可以实时看到程序的执行过程。边写边看到。</li>
<li>电路图。可以实时地看到电路图中各个部件的对1/0信号的处理。</li>
<li>动画。一种比flash制作动画更NB 的方法。</li>
</ul>
<p>下面是优酷上的视频——你一定会被示例中的那些编程工具所震撼！</p>
<p><center><embed src="http://player.youku.com/player.php/sid/XMzUyOTIyNzg0/v.swf" allowFullScreen="true" quality="high" width="480" height="400" align="middle" allowScriptAccess="always" type="application/x-shockwave-flash"></embed></center></p>
<p>不过，Bret并不是在说什么编程，也不是在说什么技术，他是在说 How to live your life。</p>
<p><span id="more-6775"></span></p>
<p>他认为，在我们的生活当中，我们听到太多的诸如：“跟随你的喜好”、“跟随你的兴趣”，“跟随你的热情”之类的东西，但他更认为，更应该是“跟随一个原则 follow you principle”，他认为真正能让你把事做正确的不是你的喜好，不是你的兴趣，也不是热情，而一个做事的原则。在这个演讲中Bret介绍了他自己的原则和他人的原则，供你参考和并找到你的原则从而live in your life。</p>
<p>Bret的原则是，他觉得人总是会有很多想法，而把这些想法变成现实是一件非常重要的事，也是最难的事。他觉得当我们在写代码实现一个东西的时候，在实现一个游戏，算法，电路，动画的时候，人很难把自己脑海里的东西映射成现实的东西，因为一个想法变成现实，需要反反复复的调整，如果看不见，就没办法调整。而我们在写好程序，需要编译程序，运行程序，才能看到结果，之后，有些东西发现并不满意，需要做调整，于是需要猜着去改一下程序，再编译运行，再看结果，于是，这个调整的过程相当令人痛苦，因为代码里的那些数字，我需要一点一点地去试，调大调小，总是不能调到我们想要的结果，从而让人无法正常思考。所以，他的原则是——<strong>创造者需要对自己的创造的东西有实时的反馈</strong>。于是出现了视频中的实时编程的那些示例。（其实，这个东西和Firebug很相似，我还记得以前和朋友说过，如果写C/C++程序也能有像Firebug的这种工具就好了，现在果然离实现不远了）</p>
<p>Bret说起他的动机的时候，他说，他把这个事当成了一种责任而不是一种机会。他说，这就好像我们听到的：审查机制，性别歧视，环境破坏，违反人权等这些问题，绝大多数人是不会把这些事当成一个机会的，而那些有责任感的人会把解决这些问题当作一种责任。同样，当他看到我们被工具或环境限制住了我们创造东西的过程时，他并不觉得这是一个可以发明更好的产品的机会，甚至这是开创自己事业的机会，或是对社会做贡献的机会。他认为想法相当的宝贵，如果一个好的想法推动不了的时候，他会很难受，就像看到一场灾难一样，他觉得，让大家的想法能够顺的进行，这是他的一个责任。</p>
<p>后面他，举到了很多人的例子，</p>
<p style="padding-left: 30px;"><a href="http://en.wikipedia.org/wiki/Larry_Tesler" target="_blank">Larry Tesler</a> &#8211; 著名的计算机科学家，前苹果的首席科学家，图形界面的创造者（在“<a title="SteveY对Amazon和Google平台的长篇大论" href="https://coolshell.cn/articles/5701.html" target="_blank">SteveY对Amazon和Google平台的长篇大论</a>”中提到过他）。他在70年代看到人们在使用电脑文本编辑器时，需要按某个键进入某种模式（Mode），然后才能输入（VI）。他觉得这样操作起来很复杂，也很不舒服，所以，他为自己设定了一个原则——“Don&#8217;t Mode Me In”，他做了很多尝试，做了一个叫Gypsy的文档编辑器，可以通过拖拽移动字符，而且他还发明了复制粘贴，对于一个没有使用过电脑的人来说，只需半个小时的训练就可以输入文字了。Larry把消除模式设置成了自己的原则或责任。他的个人主页是：http://www.nomodes.com，他的Twitter是 @nomodes，甚至他的车牌也是nomodes.</p>
<p style="padding-left: 30px;"><a href="http://en.wikipedia.org/wiki/Elizabeth_Cady_Stanton" target="_blank">Elizabeth Cady Stanton</a>，100年前的一个美国的女权主义者，当时，她主张妇女的各项权益，比如参与投票，所有人都以为他疯了，今天看来，她是对的，她消除了性别歧视。这和Larry 很相似，他们都看到了一种文化上的错误，并要预见到了未来的样子，他们都为自己设定了一个原则或是信仰，而去为之奋斗。</p>
<p style="padding-left: 30px;"><a href="http://en.wikipedia.org/wiki/Doug_Engelbart" target="_blank">Doug Engelbart</a>，美国发明家，瑞典人和挪威人后裔。最广为人知的是他发明了鼠标，另外他的小组是人机交互的先锋，开发了超文本系统、网络计算机，以及图形用户界面的先驱；并致力于倡导运用计算机和网络，来协同解决世界上日益增长的紧急而又复杂的问题。</p>
<p style="padding-left: 30px;"><a href="http://en.wikipedia.org/wiki/Alan_Kay" target="_blank">Alan Kay</a>，美国计算机科学家，在面向对象编程和窗口式图形用户界面方面作出了先驱性贡献。2003年获得图灵奖。目前担任Viewpoints研究院院长，加州大学伯克利分校兼职教授。曾任Apple公司院士，惠普公司资深院士。他有一句尽人皆知的名言——预测未来的最好办法就是创造未来。他相信如果小孩能够熟练掌握电脑，如果写程序是和读书写字一样成为基础知识，那么人们就掌握了一种新的方式去思考，新的方式去了解世界。他所有发明的东西都基于他自己的原则或信条。</p>
<p style="padding-left: 30px;"><a href="http://en.wikipedia.org/wiki/Richard_Stallman" target="_blank">Richard Stallman</a>，是美国自由软件运动的精神领袖、GNU计划以及自由软件基金会（Free Software Foundation）的创立者。作为一个著名的黑客，他的主要成就包括Emacs及后来的GNU Emacs，GNU C 编译器及GDB 调试器。他所写作的GNU通用公共许可证（GNU GPL）是世上最广为采用的自由软件许可证，为copyleft观念开拓出一条崭新的道路。他的原则，是软件必须是自由的，他认为软件的自由是关乎道义上的对错的，关系到人类的自由。他没车没房没结婚没孩子，也不用手机，但他有非常忠实自由的信条和责任感。</p>
<p>Bret通过这些例子继续强调——他并不是要大家样做，他只是给大家一个选择。你可以成为一个非常优秀的工程师，非常熟练地掌握了一些技术，你也可以为这个社会做很多贡献，这是成为一个工匠的路，也是大多数人走的路。不过旁边还有一条路，值得去走，那就是解决问题的路，这条路往一头走是创业者，往另一头走是学者，但你需要找到一个你自己的原则，你可能需要很长时间才能找到你的原则，Bret说他花了10年才搞清楚他的原则是什么。</p>
<p>个人以为，Bret所说这个原则也好，信条也好，是一种对自己创造力有引导性质的原则和信条，并不是那些已有的原则或信条，否则那只不过是在跟从了，所以，这些原则和信条应该是新的东西，是自己悟出来的东西，这样的原则和信条会导致你有一种责任感向正确的方向去创造。当然，这些原则也不是那些非常笼统和模糊的东西，比如，要创业开公司，要设计出有更好的用户体验的东西，要创造有很多用户使用的产品，或是有更好的收入什么的。其应该是明确的，有指导性的，就像Bret他自己的信条一样——“创造者需要即时的反馈”，就像他演示的那样，当你在一行一行修改你的代码的时候，你可以立即看到代码运行的过程和效果。这个原则可以指导着他要对一切达不到这个原则的东西负责，并引导着他知道应该做什么，不应该做什么，从而去创造新的东西，解决问题。</p>
<p>当然，世界是多元的，每个人都有每个人自己的原则。不同的原则必然会把你导到不同的路上。不管你是否同意，视频中的那些演示是相当令人震撼的。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/02/gitlab-600-150x150.jpg" alt="从Gitlab误删除数据库想到的" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17680.html" class="wp_rp_title">从Gitlab误删除数据库想到的</a></li><li ><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/08/HighAvailability-BK-150x150.png" alt="关于高可用的系统" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17459.html" class="wp_rp_title">关于高可用的系统</a></li><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/8387.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/10/Learnable_Programming-150x150.jpg" alt="Bret Victor &#8211; Learnable Programming" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8387.html" class="wp_rp_title">Bret Victor &#8211; Learnable Programming</a></li><li ><a href="https://coolshell.cn/articles/5686.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/15.jpg" alt="多些时间能少写些代码" width="150" height="150" /></a><a href="https://coolshell.cn/articles/5686.html" class="wp_rp_title">多些时间能少写些代码</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/6775.html">Bret Victor – Inventing on Principle</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/6775.html/feed</wfw:commentRss>
			<slash:comments>56</slash:comments>
		
		
			</item>
		<item>
		<title>由12306.cn谈谈网站性能技术</title>
		<link>https://coolshell.cn/articles/6470.html</link>
					<comments>https://coolshell.cn/articles/6470.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Mon, 16 Jan 2012 00:20:22 +0000</pubDate>
				<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[系统架构]]></category>
		<category><![CDATA[12306.cn]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[eComm]]></category>
		<category><![CDATA[Performance]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=6470</guid>

					<description><![CDATA[<p>12306.cn网站挂了，被全国人民骂了。我这两天也在思考这个事，我想以这个事来粗略地和大家讨论一下网站性能的问题。因为仓促，而且完全基于本人有限的经验和了解，...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/6470.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/6470.html">由12306.cn谈谈网站性能技术</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>12306.cn网站挂了，被全国人民骂了。我这两天也在思考这个事，我想以这个事来粗略地和大家讨论一下网站性能的问题。因为仓促，而且完全基于本人有限的经验和了解，所以，如果有什么问题还请大家一起讨论和指正。（这又是一篇长文，只讨论性能问题，不讨论那些UI，用户体验，或是是否把支付和购票下单环节分开的功能性的东西）</p>
<h4>业务</h4>
<p>任何技术都离不开业务需求，所以，要说明性能问题，首先还是想先说说业务问题。</p>
<ul>
<li><strong>其一</strong>，<strong>有人可能把这个东西和QQ或是网游相比</strong>。但我觉得这两者是不一样的，网游和QQ在线或是登录时访问的更多的是用户自己的数据，而订票系统访问的是中心的票量数据，这是不一样的。不要觉得网游或是QQ能行你就以为这是一样的。网游和QQ 的后端负载相对于电子商务的系统还是简单。</li>
</ul>
<ul>
<li><strong>其二</strong>，<strong>有人说春节期间订火车的这个事好像网站的秒杀活动</strong>。的确很相似，但是如果你的思考不在表面的话，你会发现这也有些不一样。火车票这个事，一方面会伴随着大量的查询操作，更BT的是下单的时候需要对数据库很多的一致性的操作，一方面是从起点到终点各个分段票的一致性，另一方面，买的人路线、车次、时间选择有很多，会不停地改变下单方式。而秒杀，直接杀就好了，没有那么多查询和一致性的问题。另外，关于秒杀，完全可以做成只接受前N个用户的请求（完全不操作后端的任何数据， 仅仅只是对用户的下单操作log），这种业务，只需要在内存cache中放好可秒杀的数量，还可以把数据分布开来放，100商品，10台服务器一台放10个，无需在当时操作任何数据库。可以订单数够后，停止秒杀，然后批量写数据库。而且秒杀的商品不多。火车票这个不是像秒杀那么简单的，春运时间，几乎所有的票都是热门票，而且几乎是全国人民都来了，而且还有转车业务，多条线的库存都要做事务操作，你想想吧，这有多难。（淘宝的双十一也就3百万用户，而火车票瞬时有千万级别甚至是亿级别的）（<strong>更新：2014年1月11日</strong>：来了淘宝后，对淘宝的系统有了解，淘宝的秒杀活动，本质上是用输验证码并在CDN上把用户直接过滤掉了，比如：1千万个用户过滤了只剩2万个用户，这样数据库就顶得住了）</li>
</ul>
<ul>
<li><strong>其三</strong>，<strong>有人拿这个系统和奥运会的票务系统比较</strong>。我觉得还是不一样。虽然奥运会的票务系统当年也一上线就废了。但是奥运会用的是抽奖的方式，也就是说不存在先来先得的抢的方式，而且，是事后抽奖，事前只需要收信息，事前不需要保证数据一致性，没有锁，很容易水平扩展。</li>
</ul>
<ul>
<li><strong>其四</strong>，<strong>订票系统应该和电子商务的订单系统很相似</strong>，都是需要对库存进行：1）占住库存，2）支付（可选），3）扣除库存的操作。这个是需要有一致性的检查的，也就是在并发时需要对数据加锁的。B2C的电商基本上都会把这个事干成异步的，也就是说，你下的订单并不是马上处理的，而是延时处理的，只有成功处理了，系统才会给你一封确认邮件说是订单成功。我相信有很多朋友都收到认单不成功的邮件。<strong>这就是说，数据一致性在并发下是一个瓶颈</strong>。</li>
</ul>
<p><span id="more-6470"></span></p>
<ul>
<li><strong>其五</strong>，<strong>铁路的票务业务很变态</strong>，其采用的是突然放票，而有的票又远远不够大家分，所以，大家才会有抢票这种有中国特色的业务的做法。于是当票放出来的时候，就会有几百万人甚至上千万人杀上去，查询，下单。几十分钟内，一个网站能接受几千万的访问量，这个是很恐怖的事情。<a href="http://www.linuxso.com/architecture/17006.html" target="_blank">据说12306的高峰访问是10亿PV</a>，集中在早8点到10点，每秒PV在高峰时上千万。</li>
</ul>
<p>多说几句：</p>
<ul>
<li><strong>库存是B2C的恶梦，库存管理相当的复杂</strong>。不信，你可以问问所有传统和电务零售业的企业，看看他们管理库存是多么难的一件事。不然，就不会有那么多人在问凡客的库存问题了。（你还可以看看《乔布斯传》，你就知道为什么Tim会接任Apple的CEO了，最主要的原因是他搞定了苹果的库存周期问题）</li>
</ul>
<ul>
<li><strong>对于一个网站来说，浏览网页的高负载很容易搞定，查询的负载有一定的难度去处理，不过还是可以通过缓存查询结果来搞定，最难的就是下单的负载</strong>。因为要访问库存啊，对于下单，基本上是用异步来搞定的。去年双11节，淘宝的每小时的订单数大约在60万左右，京东一天也才能支持40万（居然比12306还差），亚马逊5年前一小时可支持70万订单量。可见，下订单的操作并没有我们相像的那么性能高。</li>
</ul>
<ul>
<li><strong>淘宝要比B2C的网站要简单得多，因为没有仓库</strong>，所以，不存在像B2C这样有N个仓库对同一商品库存更新和查询的操作。下单的时候，B2C的 网站要去找一个仓库，又要离用户近，又要有库存，这需要很多计算。试想，你在北京买了一本书，北京的仓库没货了，就要从周边的仓库调，那就要去看看沈阳或 是西安的仓库有没有货，如果没有，又得看看江苏的仓库，等等。淘宝的就没有那么多事了，每个商户有自己的库存，库存就是一个数字，并且库存分到商户头上了，反而有利于性能扩展。</li>
</ul>
<ul>
<li><strong>数据一致性才是真正的性能瓶颈</strong>。有 人说nginx可以搞定每秒10万的静态请求，我不怀疑。但这只是静态请求，理论值，只要带宽、I/O够强，服务器计算能力够，并支持的并发连接数顶得住10万TCP链接的建立 的话，那没有问题。但在数据一致性面前，这10万就完完全全成了一个可望不可及的理论值了。</li>
</ul>
<p>我说那么多，我只是想从业务上告诉大家，我们需要从业务上真正了解春运铁路订票这样业务的变态之处。</p>
<h4>前端性能优化技术</h4>
<p>要解决性能的问题，有很多种常用的方法，我在下面列举一下，我相信12306这个网站使用下面的这些技术会让其性能有质的飞跃。</p>
<h5>一、前端负载均衡</h5>
<p>通过DNS的负载均衡器（一般在路由器上根据路由的负载重定向）可以把用户的访问均匀地分散在多个Web服务器上。这样可以减少Web服务器的请求负载。因为http的请求都是短作业，所以，可以通过很简单的负载均衡器来完成这一功能。最好是有CDN网络让用户连接与其最近的服务器（CDN通常伴随着分布式存储）。（关于负载均衡更为详细的说明见“后端的负载均衡”）</p>
<h5>二、减少前端链接数</h5>
<p>我看了一下12306.cn，打开主页需要建60多个HTTP连接，车票预订页面则有70多个HTTP请求，现在的浏览器都是并发请求的（当然，浏览器的一个页面的并发数是有限的，但是你挡不住用户开多个页面，而且，后端服务器TCP链接在前端断开始，还不会马上释放或重要）。所以，只要有100万个用户，就有可能会有6000万个链接（访问第一次后有了浏览器端的cache，这个数会下来，就算只有20%也是百万级的链接数），太多了。一个登录查询页面就好了。把js打成一个文件，把css也打成一个文件，把图标也打成一个文件，用css分块展示。把链接数减到最低。</p>
<h5>三、减少网页大小增加带宽</h5>
<p>这个世界不是哪个公司都敢做图片服务的，因为图片太耗带宽了。现在宽带时代很难有人能体会到当拨号时代做个图页都不敢用图片的情形（现在在手机端浏览也是这个情形）。我查看了一下12306首页的需要下载的总文件大小大约在900KB左右，如果你访问过了，浏览器会帮你缓存很多，只需下载10K左右的文件。但是我们可以想像一个极端一点的案例，1百万用户同时访问，且都是第一次访问，每人下载量需要1M，如果需要在120秒内返回，那么就需要，1M * 1M /120 * 8 = 66Gbps的带宽。很惊人吧。所以，我估计在当天，12306的阻塞基本上应该是网络带宽，所以，你可能看到的是没有响应。后面随着浏览器的缓存帮助12306减少很多带宽占用，于是负载一下就到了后端，后端的数据处理瓶颈一下就出来。于是你会看到很多http 500之类的错误。这说明后端服务器垮了。</p>
<h5>四、前端页面静态化</h5>
<p>静态化一些不常变的页面和数据，并gzip一下。<del>还有一个变态的方法是把这些静态页面放在/dev/shm下，这个目录就是内存，直接从内存中把文件读出来返回，这样可以减少昂贵的磁盘I/O</del>。使用nginx的sendfile功能可以让这些静态文件直接在内核心态交换，可以极大增加性能。</p>
<h5>五、优化查询</h5>
<p>很多人查询都是在查一样的，完全可以用反向代理合并这些并发的相同的查询。这样的技术主要用查询结果缓存来实现，第一次查询走数据库获得数据，并把数据放到缓存，后面的查询统统直接访问高速缓存。为每个查询做Hash，使用NoSQL的技术可以完成这个优化。（这个技术也可以用做静态页面）</p>
<p>对于火车票量的查询，个人觉得不要显示数字，就显示一个“有”或“无”就好了，这样可以大大简化系统复杂度，并提升性能。把查询对数据库的负载分出去，从而让数据库可以更好地为下单的人服务。</p>
<h5>六、缓存的问题</h5>
<p>缓存可以用来缓存动态页面，也可以用来缓存查询的数据。缓存通常有那么几个问题：</p>
<p>1）缓存的更新。也叫缓存和数据库的同步。有这么几种方法，一是缓存time out，让缓存失效，重查，二是，由后端通知更新，一量后端发生变化，通知前端更新。前者实现起来比较简单，但实时性不高，后者实现起来比较复杂 ，但实时性高。</p>
<p>2）缓存的换页。内存可能不够，所以，需要把一些不活跃的数据换出内存，这个和操作系统的内存换页和交换内存很相似。FIFO、LRU、LFU都是比较经典的换页算法。相关内容参看<a href="http://en.wikipedia.org/wiki/Cache_algorithms" target="_blank">Wikipeida的缓存算法</a>。</p>
<p>3）缓存的重建和持久化。缓存在内存，系统总要维护，所以，缓存就会丢失，如果缓存没了，就需要重建，如果数据量很大，缓存重建的过程会很慢，这会影响生产环境，所以，缓存的持久化也是需要考虑的。</p>
<p>诸多强大的NoSQL都很好支持了上述三大缓存的问题。</p>
<h4>后端性能优化技术</h4>
<p>前面讨论了前端性能的优化技术，于是前端可能就不是瓶颈问题了。那么性能问题就会到后端数据上来了。下面说几个后端常见的性能优化技术。</p>
<h5>一、数据冗余</h5>
<p>关于数据冗余，也就是说，把我们的数据库的数据冗余处理，也就是减少表连接这样的开销比较大的操作，但这样会牺牲数据的一致性。风险比较大。很多人把NoSQL用做数据，快是快了，因为数据冗余了，但这对数据一致性有大的风险。这需要根据不同的业务进行分析和处理。（注意：用关系型数据库很容易移植到NoSQL上，但是反过来从NoSQL到关系型就难了）</p>
<h5>二、数据镜像</h5>
<p>几乎所有主流的数据库都支持镜像，也就是replication。数据库的镜像带来的好处就是可以做负载均衡。把一台数据库的负载均分到多台上，同时又保证了数据一致性（Oracle的SCN）。最重要的是，这样还可以有高可用性，一台废了，还有另一台在服务。</p>
<p>数据镜像的数据一致性可能是个复杂的问题，所以我们要在单条数据上进行数据分区，也就是说，把一个畅销商品的库存均分到不同的服务器上，如，一个畅销商品有1万的库存，我们可以设置10台服务器，每台服务器上有1000个库存，这就好像B2C的仓库一样。</p>
<h5>三、数据分区</h5>
<p>数据镜像不能解决的一个问题就是数据表里的记录太多，导致数据库操作太慢。所以，把数据分区。数据分区有很多种做法，一般来说有下面这几种：</p>
<p>1）把数据把某种逻辑来分类。比如火车票的订票系统可以按各铁路局来分，可按各种车型分，可以按始发站分，可以按目的地分……，反正就是把一张表拆成多张有一样的字段但是不同种类的表，这样，这些表就可以存在不同的机器上以达到分担负载的目的。</p>
<p>2）把数据按字段分，也就是竖着分表。比如把一些不经常改的数据放在一个表里，经常改的数据放在另外多个表里。把一张表变成1对1的关系，这样，你可以减少表的字段个数，同样可以提升一定的性能。另外，字段多会造成一条记录的存储会被放到不同的页表里，这对于读写性能都有问题。但这样一来会有很多复杂的控制。</p>
<p>3）平均分表。因为第一种方法是并不一定平均分均，可能某个种类的数据还是很多。所以，也有采用平均分配的方式，通过主键ID的范围来分表。</p>
<p>4）同一数据分区。这个在上面数据镜像提过。也就是把同一商品的库存值分到不同的服务器上，比如有10000个库存，可以分到10台服务器上，一台上有1000个库存。然后负载均衡。</p>
<p>这三种分区都有好有坏。最常用的还是第一种。数据一旦分区，你就需要有一个或是多个调度来让你的前端程序知道去哪里找数据。<strong>把火车票的数据分区，并放在各个省市，会对12306这个系统有非常有意义的质的性能的提高</strong>。</p>
<h5>四、后端系统负载均衡</h5>
<p>前面说了数据分区，数据分区可以在一定程度上减轻负载，但是无法减轻热销商品的负载，对于火车票来说，可以认为是大城市的某些主干线上的车票。这就需要使用数据镜像来减轻负载。使用数据镜像，你必然要使用负载均衡，在后端，我们可能很难使用像路由器上的负载均衡器，因为那是均衡流量的，因为流量并不代表服务器的繁忙程度。因此，我们需要一个任务分配系统，其还能监控各个服务器的负载情况。</p>
<p>任务分配服务器有一些难点：</p>
<ul>
<li>负载情况比较复杂。什么叫忙？是CPU高？还是磁盘I/O高？还是内存使用高？还是并发高？还是内存换页率高？你可能需要全部都要考虑。这些信息要发送给那个任务分配器上，由任务分配器挑选一台负载最轻的服务器来处理。</li>
</ul>
<ul>
<li>任务分配服务器上需要对任务队列，不能丢任务啊，所以还需要持久化。并且可以以批量的方式把任务分配给计算服务器。</li>
</ul>
<ul>
<li>任务分配服务器死了怎么办？这里需要一些如Live-Standby或是failover等高可用性的技术。我们还需要注意那些持久化了的任务的队列如何转移到别的服务器上的问题。</li>
</ul>
<p>我看到有很多系统都用静态的方式来分配，有的用hash，有的就简单地轮流分析。这些都不够好，一个是不能完美地负载均衡，另一个静态的方法的致命缺陷是，如果有一台计算服务器死机了，或是我们需要加入新的服务器，对于我们的分配器来说，都需要知道的。另外，还要重算哈希（一致性hash可以部分解决这个问题）。</p>
<p>还有一种方法是使用抢占式的方式进行负载均衡，由下游的计算服务器去任务服务器上拿任务。让这些计算服务器自己决定自己是否要任务。这样的好处是可以简化系统的复杂度，而且还可以任意实时地减少或增加计算服务器。但是唯一不好的就是，如果有一些任务只能在某种服务器上处理，这可能会引入一些复杂度。不过总体来说，这种方法可能是比较好的负载均衡。</p>
<h5>五、异步、 throttle 和 批量处理</h5>
<p>异步、throttle（节流阀） 和批量处理都需要对并发请求数做队列处理的。</p>
<ul>
<li>异步在业务上一般来说就是收集请求，然后延时处理。在技术上就是可以把各个处理程序做成并行的，也就可以水平扩展了。但是异步的技术问题大概有这些，a）被调用方的结果返回，会涉及进程线程间通信的问题。b）如果程序需要回滚，回滚会有点复杂。c）异步通常都会伴随多线程多进程，并发的控制也相对麻烦一些。d）很多异步系统都用消息机制，消息的丢失和乱序也会是比较复杂的问题。</li>
</ul>
<ul>
<li>throttle 技术其实并不提升性能，这个技术主要是防止系统被超过自己不能处理的流量给搞垮了，这其实是个保护机制。使用throttle技术一般来说是对于一些自己无法控制的系统，比如，和你网站对接的银行系统。</li>
</ul>
<ul>
<li>批量处理的技术，是把一堆基本相同的请求批量处理。比如，大家同时购买同一个商品，没有必要你买一个我就写一次数据库，完全可以收集到一定数量的请求，一次操作。这个技术可以用作很多方面。比如节省网络带宽，我们都知道网络上的MTU（最大传输单元），以态网是1500字节，光纤可以达到4000多个字节，如果你的一个网络包没有放满这个MTU，那就是在浪费网络带宽，因为网卡的驱动程序只有一块一块地读效率才会高。因此，网络发包时，我们需要收集到足够多的信息后再做网络I/O，这也是一种批量处理的方式。批量处理的敌人是流量低，所以，批量处理的系统一般都会设置上两个阀值，一个是作业量，另一个是timeout，只要有一个条件满足，就会开始提交处理。</li>
</ul>
<p>所以，<strong>只要是异步，一般都会有throttle机制，一般都会有队列来排队，有队列，就会有持久化，而系统一般都会使用批量的方式来处理</strong>。</p>
<p><a href="http://blog.codingnow.com/2012/01/ticket_queue.html" target="_blank">云风同学设计的“排队系统”</a> 就是这个技术。这和电子商务的订单系统很相似，就是说，我的系统收到了你的购票下单请求，但是我还没有真正处理，我的系统会跟据我自己的处理能力来throttle住这些大量的请求，并一点一点地处理。一旦处理完成，我就可以发邮件或短信告诉用户你来可以真正购票了。</p>
<p>在这里，我想通过业务和用户需求方面讨论一下云风同学的这个排队系统，因为其从技术上看似解决了这个问题，但是从业务和用户需求上来说可能还是有一些值得我们去深入思考的地方：</p>
<p style="padding-left: 30px;">1）<strong>队列的DoS攻击</strong>。首先，我们思考一下，这个队是个单纯地排队的吗？这样做还不够好，因为这样我们不能杜绝黄牛，而且单纯的ticket_id很容易发生DoS攻击，比如，我发起N个 ticket_id，进入购票流程后，我不买，我就耗你半个小时，很容易我就可以让想买票的人几天都买不到票。有人说，用户应该要用身份证来排队， 这样在购买里就必需要用这个身份证来买，但这也还不能杜绝黄牛排队或是号贩子。因为他们可以注册N个帐号来排队，但就是不买。黄牛这些人这个时候只需要干一个事，把网站搞得正常人不能访问，让用户只能通过他们来买。</p>
<p style="padding-left: 30px;">2）<strong>对列的一致性</strong>？对这个队列的操作是不是需要锁？只要有锁，性能一定上不去。试想，100万个人同时要求你来分配位置号，这个队列将会成为性能瓶颈。你一定没有数据库实现得性能好，所以，可能比现在还差。<strong>抢数据库和抢队列本质上是一样的</strong>。</p>
<p style="padding-left: 30px;">3）<strong>队列的等待时间</strong>。购票时间半小时够不够？多不多？要是那时用户正好不能上网呢？如果时间短了，用户不够时间操作也会抱怨，如果时间长了，后面在排队的那些人也会抱怨。这个方法可能在实际操作上会有很多问题。另外，半个小时太长了，这完全不现实，我们用15分钟来举例：有1千万用户，每一个时刻只能放进去1万个，这1万个用户需要15分钟完成所有操作，那么，这1千万用户全部处理完，需要1000*15m = 250小时，10天半，火车早开了。（我并非信口开河，<a href="http://t.cn/z0g7dGJ" target="_blank">根据铁道部专家的说明</a>：这几天，平均一天下单100万，所以，处理1000万的用户需要十天。这个计算可能有点简单了，我只是想说，<strong>在这样低负载的系统下用排队可能都不能解决业务问题</strong>）</p>
<p style="padding-left: 30px;">4）<strong>队列的分布式</strong>。这个排队系统只有一个队列好吗？还不足够好。因为，如果你放进去的可以购票的人如果在买同一个车次的同样的类型的票（比如某动车卧铺），还是等于在抢票，也就是说系统的负载还是会有可能集中到其中某台服务器上。因此，最好的方法是根据用户的需求——提供出发地和目的地，来对用户进行排队。而这样一来，队列也就可以是多个，只要是多个队列，就可以水平扩展了。这样可以解决性能问题，但是没有解决用户长时间排队的问题。</p>
<p>我觉得完全可以向网上购物学习。<strong>在排队（下单）的时候，收集好用户的信息和想要买的票，并允许用户设置购票的优先级，比如，A车次卧铺买 不到就买 B车次的卧铺，如果还买不到就买硬座等等，然后用户把所需的钱先充值好，接下来就是系统完全自动地异步处理订单</strong>。成功不成功都发短信或邮件通知用户。这样，系统不仅可以省去那半个小时的用户交互时间，自动化加快处理，还可以合并相同购票请求的人，进行批处理（减少数据库的操作次数）。<strong>这种方法最妙的事是可以知道这些排队用户的需求，不但可以优化用户的队列，把用户分布到不同的队列，还可以像亚马逊的心愿单一样，通过一些计算就可以让铁道部做车次统筹安排和调整</strong>（最后，排队系统（下单系统）还是要保存在数据库里的或做持久化，不能只放在内存中，不然机器一down，就等着被骂吧）。</p>
<h4>小结</h4>
<p>写了那么多，我小结一下：</p>
<p>0）<strong>无论你怎么设计，你的系统一定要能容易地水平扩展</strong>。也就是说，你的整个数据流中，所有的环节都要能够水平扩展。这样，当你的系统有性能问题时，“加30倍的服务器”才不会被人讥笑。</p>
<p>1）<strong>上述的技术不是一朝一夕能搞定的，没有长期的积累，基本无望</strong>。我们可以看到，无论你用哪种都会引发一些复杂性，设计总是在做一种权衡。</p>
<p>2）集中式的卖票很难搞定，使用上述的技术可以让订票系统能有几佰倍的性能提升。而在<strong>各个省市建分站，分开卖票，是能让现有系统性能有质的提升的最好方法</strong>。</p>
<p>3）<strong>春运前夕抢票且票量供远小于求这种业务模式是相当变态的</strong>，让几千万甚至上亿的人在某个早晨的8点钟同时登录同时抢票的这种业务模式是变态中的变态。业务形态的变态决定了无论他们怎么办干一定会被骂。</p>
<p>4）<strong>为了那么一两个星期而搞那么大的系统</strong>，而其它时间都在闲着，有些可惜了，这也就是铁路才干得出来这样的事了。</p>
<p><em><strong>更新2012年9月27日 </strong></em></p>
<p style="text-align: center;"><strong> Alexa 统计的12306的PV</strong> （注：Alexa的PV定义是：一个用户在一天内对一个页面的多次点击只算一次）</p>
<p style="text-align: center;"><img decoding="async" loading="lazy" class="aligncenter  wp-image-8367" title="12306" src="https://coolshell.cn/wp-content/uploads/2012/01/12306.png" alt="" width="640" height="352" srcset="https://coolshell.cn/wp-content/uploads/2012/01/12306.png 800w, https://coolshell.cn/wp-content/uploads/2012/01/12306-300x165.png 300w, https://coolshell.cn/wp-content/uploads/2012/01/12306-768x422.png 768w, https://coolshell.cn/wp-content/uploads/2012/01/12306-491x270.png 491w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>（<span style="color: #cc0000;"><strong>本文转载时请注明作者和出处，请勿于记商业目的</strong></span>）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/10910.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/01/trade-off-150x150.jpg" alt="分布式系统的事务处理" width="150" height="150" /></a><a href="https://coolshell.cn/articles/10910.html" class="wp_rp_title">分布式系统的事务处理</a></li><li ><a href="https://coolshell.cn/articles/6790.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/03/o_conditional_update_1-150x150.png" alt="多版本并发控制(MVCC)在分布式系统中的应用" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6790.html" class="wp_rp_title">多版本并发控制(MVCC)在分布式系统中的应用</a></li><li ><a href="https://coolshell.cn/articles/22242.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/05/etcd-150x150.png" alt="ETCD的内存问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22242.html" class="wp_rp_title">ETCD的内存问题</a></li><li ><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x-150x150.png" alt="我做系统架构的一些原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_title">我做系统架构的一些原则</a></li><li ><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/api-design-300x278-2-150x150.jpg" alt="API设计原则 &#8211; Qt官网的设计实践总结" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18024.html" class="wp_rp_title">API设计原则 &#8211; Qt官网的设计实践总结</a></li><li ><a href="https://coolshell.cn/articles/17737.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/03/Amazon-Web-Services-Down-150x150.png" alt="AWS 的 S3 故障回顾和思考" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17737.html" class="wp_rp_title">AWS 的 S3 故障回顾和思考</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/6470.html">由12306.cn谈谈网站性能技术</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/6470.html/feed</wfw:commentRss>
			<slash:comments>369</slash:comments>
		
		
			</item>
	</channel>
</rss>
