一号店迎战11.11 三大方案详解

传统秒杀系统痛点

首先,秒杀的场景决定了秒杀是一场速度的比拼,也就是俗话说的“手快有、手慢无”。大家都争着在活动开始后,第一时间将商品抢到,完成下单。因此秒杀活动开始的一瞬间会有大量的流量涌入,几倍、甚至于十几倍的流量对系统的冲击不可谓不大。如果系统没有足够的capacity或应对措施,很可能就被瞬时高流量给压垮了。

其次,突如其来的高流量,给系统各个模块都来了一连串的压力,系统可能会因此变慢,而且可能会彼此影响,影响可用性。比如:数据库更新同一个商品库存,需对同一行记录加锁,随着并发的压力逐渐增大,数据库更新的性能是逐渐下降的。从而引起提供库存service的应用服务性能下降,连锁的影响到下单service的性能,最终反馈到消费者的可能就是整个网站购物流程性能差、响应慢。而面对响应慢的系统,很多消费者可能采取反复刷新,多次尝试,这无疑又增大了对系统的压力。

上述种种给消费者带来的往往是体验上的痛苦。如:网站响应慢,点击抢购按钮没反应。好不容易可以操作了,却发现秒杀活动已经结束,消费者的参与感比较差。久而久之,可能就对此类活动失去了兴趣。

1号店秒杀系统设计理念

『 限流 』当秒杀活动开始后,只有少部分消费者能抢购到秒杀商品,意味着其实大部分用户的流量传达到后台服务后都是无效。如果能引导这大部分的流量,不让这大部分的流量传达到后台服务,其实对我们系统的压力就很小了。因此设计思路之一就是,仅让能成功抢购到商品的流量(可以有一定余量)进入我们的系统。

『削峰 』进入系统的有效流量虽然总量不一定是很大的,但却是在很短的时间内涌入的,因此会存在很高的瞬时流量峰值。总量相同的流量在1秒钟进入系统,和在10分钟均匀地进入系统,对系统的冲击是相差很大的。高峰值的流量往往能将系统压垮。因此另一个设计思路是,如何将进入系统的瞬时高流量拉平,使得系统可以在自己处理能力范围内,将所有抢购的请求处理完毕。

『 异步处理 』传统的系统对于请求是同步处理的,即收到请求后立即处理并把结果返回给用户。我们的系统有了削峰的设计后,请求不是被立刻处理的,因此就要求我们能将同步的服务改造成异步的。

『 可用性 』我们设计时始终把系统的可用性放在重要的位置,针对系统可能出现的各种状况,都尽最大程度地保证高可用。

『 用户体验 』系统设计一定要充分考虑用户体验。消费者点击抢购按钮后,无论是否能抢到商品,期望是能得到及时的反馈。系统上发生任何故障也要尽可能的保证用户体验的损害减到最小。

秒杀排队系统架构三个主要模块

『排队模块 』负责接收用户的抢购请求,将请求以先入先出的方式保存下来。每一个参加秒杀活动的商品保存一个队列,队列的大小可以根据参与秒杀的商品数量(或加点余量)自行定义。排队模块还负责提供一系列接口,如:给已进入队列的用户查询下单状态的接口,给调度模块拉取请求的接口,服务模块回写业务处理状态的接口等。

『 调度模块 』负责排队模块到服务模块的动态调度,不断检查服务模块,一旦处理能力有空闲,就从排队队列头上把用户访问请求调入服务模块。并负责向服务模块分发请求。这里调度模块扮演一个中介的角色,但不只是传递请求而已。它还担负着调节系统处理能力的重任。我们可以根据服务模块的实际处理能力,动态调节向排队系统拉取请求的速度。作用有点类似水坝的闸门,当下游干旱时就打开闸门多放些水,当下游洪涝时,就放下闸门少放些水。

『 服务模块 』是负责调用真正业务处理服务,并返回处理结果,并调用排队模块的接口回写业务处理结果。我们设计这个模块,是为了和后面真正的业务处理服务解耦。目前我们的系统不只支持秒杀抢购这种业务场景,后续有其他适用于排队系统的业务都可以接入,如:领取抵用券等等。同时我们也可以针对后面业务系统的处理能力,动态调节服务模块调用后面业务处理服务的速度。

容错处理

任何系统都不可能一帆风顺,但我们要能在出现错误时仍旧保证系统的高用性和良好的客户体验。举几个简单的例子,比如服务模块调用后面服务时,出现调用服务超时怎么办?对此我们设计了对于超时的请求,可以重试的机制。再比如,如果后面真正的业务处理系统宕机怎么办?如果是传统系统的话,可能就面临系统无法使用的尴尬。系统已异步,因此加入排队队列的用户请求,在业务处理系统恢复后都可以得到处理。只要我们在前端给用户以友好的交互、提示,系统还是能提供一定质量服务的。

用户交互

因为我们的系统设计成异步的,因此消费者不再是像以前一样同步地去等待反馈。消费者需要一个途径来获取抢购的状态和进度。我们的主体流程大体上分为几个阶段:

  • 当等待人数大于500人,页面提示:排在您前面的人超过500位;
  • 当等待人数小于等于500人,页面提示:您已挤进第***位;
  • 当等待时间大于等于1分钟,页面提示:剩余时间约*分钟。每次以分钟倒计时。
  • 当等待时间小于1分钟,页面提示:预计剩余*秒。
  • 抢购成功,后续跳转到订单支付页面

【以下为2个PC端页面交互设计示例】


TWO
布式搜索引擎架构实践
应对大流量与高突发性的两个核心需求

『 可扩展 』如何抗住这样的流量,针对这个需求,1号店搜索团队构建了分布式搜索引擎,支持横向扩展;并且针对业务特点做了Routing优化,让搜索的效率更高。

『 快速响应 』流量越大,单位时间内的流量价值就越大,出现问题的损失也就越大,如何做到快速响应变得非常关键。针对这个需求,搜索系统支持自动部署和快速扩容以应对突发流量,索引数据从导入、处理到上线服务会经过层层验证,同时还有监控体系及时发现线上的问题。

分布式搜索引擎

1号店分布式搜索引擎是Lucene/Solr核心的,结合SOA框架Hedwig构建了一层分布式框架,支持搜索请求的分发和合并,并且构建了搜索管理后台,支持多索引管理、集群管理、全量索引切换和实时索引更新。

选择自己构建分布式方案,而不是采用开源的SolrCloud或ElasticSearch,主要是基于以下几点考虑:

  • ElasticSearch/SolrCloud都适合于把搜索引擎作为一个黑盒系统来使用,而1号店搜索业务的展现形式多样性很高,搜索条件有的会很复杂,有的需要通过自定义插件来实现,性能调优时也需要对引擎内部的执行细节进行监控。
  • 将ElasticSearch/SolrCloud与公司内部的发布系统、监控系统和SOA体系结合起来,也是一项比较耗时的工作。
  • 相对于整体使用,我们更倾向于把Lucene/Solr开源家族中的各个组件按需引入,一方面降低引入复杂工程的可维护性风险,另一方面逐渐深入理解这些组件,可以在必要时替换为定制化的组件。

分布式搜索是为了解决数据增长过程中索引变大和操作时间变长的问题,它将原来的单个索引文件划分成n个切片(shards),让每个shard都足够小,从而保证索引可以在多台服务器上部署,而且搜索操作可以在较短时间内返回。

如上图所示,分布式搜索中有两个主要组件:Shard Searcher和Broker,其中Shard Searcher与单机搜索引擎类似,基于Lucene/Solr完成基本的搜索任务。Broker负责把针对整个索引的搜索请求转化为针对单个Shard的搜索请求,它把Shard搜索请求分发给各个ShardSearcher,并且把各个Shard的结果进行合并,生成最终的结果。

分布式搜索中,一次搜索所需的资源与它要访问的Shard数和每个Shard要返回的结果数有非常强的关联关系,在Shard数特别多或结果数特别多时可能会碰到一些的内存、CPU资源使用的问题。针对结果数特别多的情况,可以按照业务场景优化,比如如果对排序无要求,就可以每次指定一个Shard进行搜索,搜完这个Shard再换下一个,这样就限制了每次搜索的Shard数,另一方面也可以考虑使用DeepPaging等技术,减少每次Shard搜索的成本。我们下一小节也会介绍1号店主站搜索是如何减少每次搜索Shard数的。

上图中的Broker和Shard Searcher仅仅是概念上的划分,实际部署时有几种选择:

  • 每个节点上都有Broker和部分Shard的Shard Searcher。
  • Broker单独部署成一个集群,与Shard Searcher隔离。
  • Broker作为客户端的一部分,和搜索应用一起部署。

我们开始使用的是A方式,后来主站搜索转为C方式,主要是考虑到可以节省一次网络调用(以及请求和结果的序列化开销),另外Broker在客户端也可以更多地使用应用逻辑相关的数据进行更高效的Routing。

高效Routing

通过前面的讲述,我们不难看出,使用分布式搜索引擎,面临的核心问题就是如何选择高效的Sharding策略和Routing方案。为了设计Routing方案,我们需要深入理解业务场景。

1号店有很多的类目,每个类目的业务模式也不尽相同。以图书和快消品为例,图书是一种典型的长尾商品,它需要索引大量的SKU(Stock Keeping Unit,可以理解为一个独立的商品),但每个SKU的访问量和销量都不高;快消品是另外一个极端,总体SKU数量不高,但是访问量和效率都很高。这就造成了一个不平衡的局面,图书的SKU数目占比达到了50%以上,流量却小于10%,我们就首先排除了按Shard数目取模(id mod N)这种平衡划分的策略。

1号店搜索有两个主要入口,一个是搜索框的搜词,另外是类目导航。在这两个入口中,类目的点击肯定是访问到一个特定的一级类目下,搜词时用户其实也只会关注相关的几个类目。基于这种访问模式,我们就采用了按照类目来切分Shard的策略。基本操作为:

  • 按照一级类目切分Shard。
  • 如果该Shard过大,则按照二级类目继续切分。
  • 经过前两步之后,如果切分后的Shard过小,则按照相关性进行Shard合并。

经过这样一番尝试,Sharding策略就确定下来,切分之后的Shard索引大小一般为200~500MB,Shard上单次搜索可以控制在10ms以下。

接下来说到Routing,我们还是分搜词和类目导航两种场景,对于类目导航,原理上非常简单,按照类目ID来查找Sharding策略,就可以确定需要访问的Shard,虽然现实中还需要考虑扩展类目等特殊场景,但是也不难做出一个简单的Routing策略。再加上类目数是有限的,Routing规则在Broker本地内存就可以缓存起来。

搜词场景就复杂很多,仅凭词本身很难判断它属于哪个Shard。我们首先按照词的热度分为两类,采取不同的Routing策略。对于热词,搜词流量同样符合80-20规则,20%的热词占比80%的搜索流量,对于热词,我们可以在建完索引之后,就跑一遍热词搜索,记录那些Shard有结果,离线构建出热词Routing表。切换索引时,Routing表也一起加载进去。对于非热词,则采用首次搜索去访问所有Shard,根据结果记录Routing表,这个词在下次搜索时,就有了缓存可用。

基本的Routing策略上线之后,通过监控每个Shard的访问量,我们又发现了新的问题,图书类目的访问量比它应有的流量要高出不少。仔细分析之后发现,由于图书类目的特殊性,很多词都可以在图书中找到结果,然而这些结果一般都不是用户想要的,实际上也不会排到前几页,并没有展示的机会。于是我们又增加了一种360-Routing策略,跟进搜索前五页的结果(每页72个商品,共360个商品)计算Routing,下次搜索时优先是用这份Routing规则。由于前五页的流量占比在80%以上,这就进一步减少了单次搜索需要访问的Shard数。

使用了以上这些Routing规则,1号店主站搜索每次搜索平均只需要访问1/3的索引数据,这就节约了2/3的资源,提高了搜索效率。

自动部署与快速扩容

按照类目进行Sharding和Routing的方式,在带来高效的同时,也带来了管理上的成本。按照类目切分,必然会导致各个Shard的大小不平均,而对应的Routing方案,必然会带来各个Shard的访问量不平均。这两个维度不平均就要求更加复杂的索引部署方案,主要的原则为:

  • 首先根据流量比例和高可用的需求,确定每个Shard的副本数。
  • 然后按照单个节点不能放置同一Shard的多个副本,节点上的承担的流量总和与节点的服务能力成正比。
  • 每个节点上的索引总大小尽量也保持差异最小。

按照流量比例,副本数计算如下:

Shard 副本数 备注
S0 4
S1 2 高可用约束
S2 4
S3 3

【部署之后的效果如下图所示】

Shard数增多之后,人工计算部署方案就显得较为复杂,于是我们就把部署方案生成做成了自动化,一个基本的Packing算法就可以完成这个工作。除了初始部署之外,自动部署工具还可以支持节点增加、减少和更换。

在11.11的场景下,这个自动化部署工具也可以支持快速扩容,基本过程为:

  • Index Server(部署工具界面)计算出扩容之后的部署方案,写入到ZooKeeper。这里的算法与初始部署有些不同,它需要保证现有服务器的Shard尽量不变。
  • 每个Shard Searcher服务器都会监听ZooKeeper上的部署方案,方案改变之后,Shard Searcher会获取新的方案与本地方案对比,进行增减操作。
  • Shard Searcher从HDFS上获取索引数据,与最近的实时更新数据合并之后,启动索引提供服务。

有了自动扩容工具,11.11容量规划确定之后,新的服务器就可以很快部署上线,也减少了人工操作可能引入的失误等问题。大促期间如果需要紧急扩容,也可以在几分钟内提高整个系统的服务能力。

实时监控体系

在索引数据方面,从源头开始对索引数据准备的各个环节进行验证,包括数据的条数、处理过程中的异常、最后生成的索引在常见搜索中的执行结果差异等,层层预防,防止有问题的索引数据被用到线上。

在搜索服务方面,监控系统会定时执行常见的搜索,对比排序结果,结果差异较大时及时告警。同时大促期间会有一些商品很快卖完,这些商品继续显示在搜索结果中也没有价值,搜索监控也会及时发现这些商品,触发索引更新,把商品排到后面。

THREE
从应用架构落地点谈高可用高并发高性能
一号店三高策略——高可用高并发高性能

1号店技术部从1个人做起到今天千人级别的规模,系统支持每天亿级的访问量、单Service支持每天亿级的请求、订单支持每分钟几万单级别、Service服务可用性达到99.9999%,架构上也经历了历次演进,今天我们就从应用架构历次演进的落地点谈起。

1号店应用架构的演进大致经历了以下历程:强依赖-> Service化->业务解耦->读写分离->异步->水平/垂直拆分->服务逻辑分组等。

当然这个过程从不是串行的,永远是并行的,并且这个过程永远是在1号店这辆系统大巴行进过程中进行的,因为我们不能停车也不能刹车,而且还必须不断提速。

应用架构的最大演进-SOA治理

早期的1号店系统,遵循简单的MVC架构,Controller层处理了所有的业务逻辑包括与DB的交互,在系统初期这种Simple的架构方便快捷,成本低业务响应快。但当业务开始变得复杂、人员规模爆发式增长,这种强耦合强依赖带来的弊端就成了巨大的瓶颈,代码耦合度高互相冲突、出错概率和事故概率明显提升,业务需求不能快速响应,SOA治理迫在眉睫,解耦和去依赖成为第一需求,于是Service化成为第一前提。

SOA治理-Service日志即保障

『 Tips 』

做Service规划首要考虑要素:

  • 很多人想的是采用什么RPC框架、采用什么技术,怎么让性能更高;也有人首先想的是业务怎么拆分,怎么才能更合理。
  • 我们首先想到的是如何做监控和问题定位。
  • 高可用不是一步做到的,我们的Service可用性不是一步达到99.9999%的,在这过程中一定会有很多的问题出现,怎么提前发现这些问题、出现问题后如何快速定位,这才是最重要的。这只能依赖日志,这是监控和问题定位的基础。

下单接口业务性强,其对可用、并发、性能的要求作为技术人你懂的。我们就从这个接口开始下手,但我们没有先去分析业务,首先想的是如何定义日志系统,让以后的监控和问题定位更简单更快捷。事实证明这个决定是对的,直到现在1号店的大部分订单相关的监控、预警、问题排查定位等完全依赖这个日志。

日志系统的设计基于以下:一是进数据库、持久化有序化,二是分类化、层次化、错误code唯一。

  • 进数据库、持久化有序化这个大家都理解,我曾经接手过的一个应用系统,一天下来Tomcat的log文件大小超过1G,会让你崩溃的。
  • 分类化、层次化、特别是错误code唯一这个是关键,它是大海航行中的那盏灯塔,让你可以瞬间定位问题位置,它给监控预警带来的好处同样伟大,可以从各个维度去做监控预警。

1号店现在有了很好的SOA中间件 – Hedwig,可支持百亿级的访问请求,它有SOA级别的日志,也很完善。但应用级别的日志我们还是建议各应用系统自己做,它的业务性、个性化是公共架构永远代替不了的。

应用架构演进之落地

『业务垂直拆分 』

从业务角度规划Service,从产品、用户、订单三个维度上对Service进行了规划,构成1号店应用架构上的三架马车,确立了SOA治理的框架基础。

在此基础上,又陆续衍生出促销、积分、支付等众多Service业务,在三架马车中同样会细分至如文描、价格、库存、下单、订单查询等垂直服务。

Service化是前提,Service化完成后,后面可以大刀阔斧地干了,因为业务独立了、DB读写权限收回了,哈,好像整个天下都是我的了。但,得天下易治天下难,真正的大戏在后面。

『读写分流 』

读写分离的重要性不需多讲,除了最简单的读写分离,写可以从应用层面继续细分,读也可以从应用和DB层面再细分,如订单的读写分离:

『 水平拆分 』

早在2013年,1号店实现库存的水平拆分,2014年又一举完成订单水平拆库&去Oracle迁Mysql项目。

订单水平拆库&去Oracle迁Mysql两个重量级的大项目合并为一个项目并完美无缝上线,在经济上时间上节省的是成本,在技术上体现的1号店的整体技术实力和水平。

『 服务逻辑分组 』

物理分离好处明显,但其硬件成本、维护成本高的弊端也显而易见。这时候,我们的大杀器-Hedwig又上场了,有兴趣多来了解下我们SOA中间件-Hedwig,这可是获总裁奖的项目。

Hedwig提供了服务自动注册发现、软负载均衡、节点踢出与复活、服务动态逻辑分组、请求自动重试等众多SOA框架所需的强大功能,支持并行请求、灰度发布,其背后提供的调用链路及层次关系、日志分析、监控预警等更是为SOA治理提供了强大的后勤保障。

『 业务解耦|异步 』

上面提到的读写分离、水平拆分、逻辑分组等主要是从技术层面保障,也是技术人员最喜欢的话题。业务流程的梳理、优化、改造往往不被重视,但作为应用架构,我们最不能忽视的是业务。

  • 我们的一个核心Service服务,性能从2年前的近200ms到现在的几十ms,从代码上、sql上的优化提升顶多占到10%,更多地优化都在业务流程的梳理改造上。
  • 我们曾经花费两周多的时间将一个系统的整体性能优化提升了近10倍,最后总结下来,纯技术的优化(代码或sql质量导致的性能差)几乎没有。
  • 优化主要在两方面,一是架构上,如使用缓存、单SKU的循环查询改成批量查询等,这个能优化的也并不太多,因为我们的架构整体还是比较合理的;最大的优化还是在业务梳理上,很多地方我们使用了重接口,接口里很多逻辑计算和DB查询,这些逻辑并不是所有的业务场景都需要的,但开发人员为了简单没有将业务场景细分,导致大量不合理的调用,既浪费了服务器资源又严重影响用户体验;同样,很多地方为了一个不重要的展示或逻辑也产生大量不合理的调用,反而影响了核心业务,这些都是最需要优化的,也是优化效果最明显的。
  • 异步本身不是什么高深的技术,关键是哪些业务可以走异步,这更体现架构师的业务理解能力和综合能力。
  • 如下单成功后给用户的消息通知、发票详细信息的生成等都可以异步,我们在这方面做了很多工作,包括和各业务方的大量沟通制定方案等,在不牺牲用户体验又保证业务流程完整的情况下,尽量走异步解耦,这对可用、性能都是极大的提升。

实例:一个下单接口定义135个错误编码

前面提到过日志和错误编码的定义,仅一个下单接口就定义了135个错误编码。接口上线后至今出现的错误编码在50-60个,也就是说有50-60处不合理或错误的地方,这个不合理或错误既有业务的又有程序的也有我们对编码定义的不合理。

目前日常每天下单出现3-5个错误编码,主要为业务性如特价商品已售完无库存等。在下单接口上线近2年后,一个之前从未出现过的错误编码跳出来了,是一个很难出现的业务场景,但通过编码马上定位问题,就不用去看代码。

实例:Service服务可用性99.9999%

实例:销售库存准确率99.9999%,超卖率为0

做过电商核心系统的人都明白库存控制的难度,库存不准甚至超卖的问题至今还有很多电商公司没有完全解决。

这个问题也曾经困扰我们,为此还专门写了一个库存刷子的程序来刷数据,现在这个程序已基本宣告废弃了,因为我们的库存准确率达到了6个9,超卖率为0。

怎么做到的?业务、业务、业务,重要的事说三遍。3个多月对所有库存应用场景逐一排查,最终梳理出16个有问题的库存场景,并逐一协调解决,让库存形成严格的闭环线路,保证了库存的准确性。在这过程中,对库存闭环方案和对业务的理解成为关键,纯靠技术,我们能做的并不多。

业务监控应用场景

业务监控首提订单监控,对订单我们从实际订单数据和Service接口调用量两个维度去做监控,保证了监控系统的稳定和准确(监控系统也会出错的),其中下单接口调用量使用的就是Service日志数据。

服务监控应用场景

『 依赖查询 』

『服务监控 』

七牛CEO许式伟:服务端开发那些事儿

服务端开发对于任何互联网公司来讲,都并非易事,它所涉及的技术知识面非常广泛,如果开发人员的经验不足,将直接影响产品用户的体验。作为七牛云存储创始人,许式伟有着超过15年的编程经验,对于服务端开发那些事甚是了解。因此,在本文中,他将对服务端开发所涉及的各方面原理知识进行详细阐述,内容涵盖网络协议、操作系统原理、存储系统原理、模块设计、服务器设计等多方面。

以下为演讲实录,略有删减。

大家好,我今天的演讲题目是《服务端开发那些事儿》,主要涵盖的内容为网络协议、操作系统原理、存储系统原理、模块设计和服务器设计。这些是我觉得服务端开发人员比较直接相关的东西。第一个是网络协议,因为毕竟服务端是基于C/S模型,一上来涉及的就是协议。第二个是操作系统原理,因为服务端开发和客户端不太一样,服务端涉及到大量的锁、通讯相关的东西,所以操作系统原理对服务端程序员比对客户端程序员来说是要重要很多的。我最早的时候在做office软件,那时候基本上不涉及到太多多线程的东西,只是应用的逻辑很复杂,这是桌面端开发的特点。第三个东西,我觉得是存储系统原理。存储系统会有哪一些基本的道理,我觉得也是做服务端开发非常重视的。第四个是模块设计本身,这个是和服务端开发没有关系,是所有的开发人员应该掌握的一些基础的东西。再后一点是服务器开发本身设计的相关的要点。

网络协议

首先从网络协议开始。在七牛有一个特点,就是我们所有的服务端都是直接基于HTTP协议的,很少有定义私有网络协议的行为。我们认为HTTP协议的周边支撑是非常完善的,而且因为它是文本协议,所以大家去调试的时候非常容易理解。如果是私有二进制协议的话,还需要要专门为它写一个包的解析和查看的工具。而HTTP有很多天然的好处在里面,所以我们会基于HTTP协议。HTTP协议最直接的就是GET、POST等,我就不详细讲了,更复杂一点就是这是带授权的。以下4张图将涵盖最基本的HTTP协议。



操作系统原理

第二个层面我们谈谈操作系统原理。这块最核心的就是线程和进程之间的通讯,这个通讯包括互斥、同步、消息。大家经常会接触到互斥。只要有共享变量就一定会有锁。在Go语言的服务器开发,很难避开锁。为什么呢?因为服务器本身是其实是有很多请求同时在响应的,服务器本身就是共享资源,既然是共享资源,那么必然是有锁的。

这里我话外要提一提的是 Erlang。Erlang里面很多人会说它没有锁。我一直有个看法,不是因为Erlang是函数式程序设计语言,它没有变量,所以没有锁。只要是服务器,有很多并发的请求,那么服务器就一定是共享资源,这个是物理事实,是不可改变的。为什么Erlang可以没有锁,原因是因为Erlang强制让所有的请求排队了。排队其实就是单线程化,那当然没有锁的,在C里面,在Go里面都可以这么做,所以这并不奇怪。因此,本质上来讲,并不是因为它是函数式程序设计语言,而是因为它把请求串行化,也就是说不并发。那怎么并发呢?Erlang里面想要并发,其实是用异步消息,也就是将消息发出去,让别人做,自己继续往下执行。这样就涉及到的异步编程,这些我今天不展开讲。但是我认为,本质上来讲,服务器编程其实互斥是难以避免的,因此,Golang服务器runtim.GOMAXPROCS(1)将程序设为单线程后,仍然需要锁,单线程!=所有请求串行化处理。而锁主要存在以下几个问题。

锁最大的问题:不易控制

很多人会因为慢而避开锁,其实这样做是错误的。大部分框架想避开锁并不是因为锁慢,而是不易控制,主要表现为如果锁忘记了Unlock,结果将是灾难性的,因为不止是该请求挂掉,而是整个服务器挂掉了,所有的请求都会被这个锁挡在外面。如果Lock和Unlock不匹配,将因为一个请求而导致所有人均受影响。

锁的次要问题:性能杀手

锁虽然会导致代码串行化执行,但锁并不是特别慢。因为线程之间的通讯,它有其他原语,如同步、收发消息,这些都是比锁慢很多的原语。网络上有部分人用Golang的Channel实现锁,这很不正确。因为Channel就是线程之间发消息,成本比锁高很多。比锁快的东西,一是没有锁,二是原子操作。其中,原子操作并未比锁快很多,因为如果在冲突不多的情况下,一个锁基本上就是一个原子操作,发现没有冲突,直接继续执行。所以锁的成本并没有像大家想象的那么高,尤其是在服务端,因为服务端绝大部分应用的程序其实是IO比较多,更多的时间是花在IO上面的。

在锁的最佳实践里面,核心是控制锁的粒度。如果锁的粒度太大,例如把某一个IO操作给包进去了,那这个锁就比较灾难了。比如这个IO操作是操作数据库,那么这个锁把数据库的操作,请求和返回结果这样一个操作包进去了,那这个锁的粒度就很大,就会导致很多人都会被挡在外面。这个是锁粒度的问题。这也是锁里面比较难控制的一个点。

在锁的最佳实践里面,第一点是要懂得善用defer。在Go里面有一点是比较好的,Go语言里面有defer,容易让你避免锁的Lock和Unlock不匹配的问题,可以大大降低用锁的心智负担。但滥用defer可能会导致锁的粒度变得很大,因为你可能在函数的开始就Lock,然后defer Unlock,这样整个函数的执行全都被锁,函数里面只要有时间较长的IO操作,服务器的性能就会下降。这是锁需要注意的地方。

另外,锁的最佳实践中,第二点是要善用读写锁。绝大部分服务器里面,尤其是一些请求量比较大的请求,大部分请求的读操作居多而写操作较少,这种情况下用读写锁是非常好的方法,可以大大降低锁的成本。另外一个降低锁粒度的方法是锁数组。锁数组是用于什么场景呢?如果服务器共享资源本身有很强的分区特征,那么用锁数组比较好。例如你要做一个网盘服务,不同用户之间的数据没有关系,网盘就是一个文件系统,它是树型结构,这个树型结构的操作往往需要较高的一致性的要求,不能出现操作到一半被另外一个操作给中断,导致文件系统的树结构被破坏。所以在网盘里面更有可能出现包含了IO操作的大锁,这种情况下,如果某个用户的一次网盘同步操作会影响其他用户就会很难受。因此,在网盘服务的一个系统里,用锁数组会比较自然,你可以直接用用户的ID除以锁数组的数组大小然后取模,数组的大小决定于服务的并发量有多大,选一个合适的值就好。这样可以让不同的用户相互不干扰,同一个用户只影响他自己。

我认为,掌握好与锁相关的技术,基本上是将服务器里面很可能最大的一个坑给解决了。线程间其他的通讯,比如说同步、消息相关的坑相对少。例如,Go语言的channel实际上非常好用,既可以作为同步原语,也可以作为收发消息的原语。channel唯一一个需要注意的,channel是有缓冲区大小的,所以如果不设缓冲区的话,有一个goroutine发消息,另一个goroutine如果没有及时接收的话,发消息的那个goroutine就阻塞了。但是这个其实也很容易就能找到问题,所以这个问题不是很大。但是要注意,channel不是唯一的同步原语。Go语言里面其实同步原语还是蛮多的。比如说Group,这是一个很好用的同步原语,它是用来干吗的呢?它是让很多人一起干做某件事情,然后最后在某一个总控的地方等所有的人干完,然后继续往下走的一个原语。另外一个就是Cond原语,Cond其实用得不多,原因是channel把大部分Cond适用的场景给满足了。但是作为操作系统原理中经常提的生产者消费者模型里面最重要的一个原语,了解它是很重要的。因为channel这样一个通讯设施,它背后其实是可以认为就是用Cond实现的。而Cond它要比channel原始很多,应用范畴也要广得多。我今天不展开讲Cond了,大家要感兴趣,可以翻一翻操作系统原理相关的书。

存储系统原理

七牛就是做存储的。我觉得存储这个东西对服务端开发来说很重要。为什么呢?因为实际上服务器端开发的难度原理上比大家想象得要大,之所以今天大家不会觉得特别特别累,就是因为有存储中间件。存储是什么东西呢?存储其实是状态的维持者,存储它本身不是问题,但是有了服务器之后,它就是问题。因为大家在桌面端,大家知道存储的要求不高的,文件系统就是一个存储,那它放图片或者放什么,丢了就丢了,也没有多少操作系统关心它丢了会怎么样。但是在服务器端大家都知道,服务必须逻辑上是不宕机的。也就意味着状态维持的人是不能挂掉的。物理的服务器肯定是会挂掉的,但是哪怕物理服务器挂掉了,你的逻辑的服务或者说服务器本身不应该被挂掉的。因此,它的状态继续要维持,那谁维持呢?就是存储。如果这个世界上没有存储中间件的话,大家可以想象,写服务器是非常非常累的,你每做一件事情,做这件事情的每一步,都要想一想,中间需要把状态存下来,以便万一挂掉之后我该怎么办这样一个问题。

因此,存储中间件是大家最重要的生存基础。对于服务器程序员来讲,它是真正革命性的,它是让你能够今天这么轻松的写代码的基础。这也是我们需要理解存储系统为什么重要,它是大家赖以生存的最重要的一个外部条件。存储我蛮早的时候提过一个观点,存储就是数据结构。这个世界上存储中间件是写不完的,很多很多,消息队列这些是存储,文件系统、数据库、搜索引擎的倒排档等等,这些其实都是存储。为什么说存储就是数据结构呢?因为在桌面端开发的时候,大家都知道数据结构通常都是自己写的,或者说某个语言的标准库写的。但是在服务端里面,因为状态通常是持久化的,所以数据结构很难写。而存储其实就是一个中间件服务,是让你把状态维持这样一件事情,从业务里面剥离出来。可以想象,存储是非常多样化的,并且会和大家熟知的各种各样的数据结构对应起来(参考:http://open.qiniudn.com/golang-and-cloud-storage.pdf)。

靠谱的服务器是怎么构建的呢?很核心的一个原理,叫Fail Fast,也就是速错。我认为,速错思想对于服务端开发来说非常非常重要。但是速错理念的基础是靠谱的存储。因为速错的意思是说,系统万一有问题,就挂掉了,挂要之后要重启重新做。但是重新做,你得知道它刚才在干什么,它的基础就是要有人维持状态,也就是存储。速错的思想最早是在硬件领域,后来Erlang语言中首先提出将速错这样一个思想运用在软件开发里面,以构建高可靠的软件系统。这是一篇Erlang作者的博士论文(http://open.qiniudn.com/[Joe-Armstrong][CN]Making-reliable-distributed-systems-in-the-presence-of-software-errors.pdf)。这篇文章对于我的影响是非常大的,是我个人在服务端开发里面的启蒙的一个著作。大家知道软件是偏实践的科学,比较少有体系化的理念出现,这个是我见过的很棒的一个服务端开发或者分布式系统相关的理论,个人受益匪浅。

然而存储为什么难呢?是因为别人都可以Fail Fast,但是存储系统不行。存储系统必须遵守顶层设计理念,其实是和FailFast相反的,它需要达到的结果是,无论怎么错都应该有正确的结果。当然如果说存储系统完全和Fail Fast相反倒也不至于,因为存储系统的内部实现细节本身,还是会用到很多速错相关的原理。但是存储系统对外表现出来的、所呈现的使用界面,和速错原理会有反过来的感觉。因为无论发生什么样的错误,包括软件、网络、磁盘、服务器断电、内存,甚至是IDC故障等等,对于一个存储系统来讲,它都认为,这必须是能承受的,必须有合理的结果。当然这个能承受的范围,不同的存储系统是不一样的,代价也不一样。比如说MemCache这样的存储系统,它就不考虑断电这样的问题。对于MySQL这样的东西,如果说在最早的时候,它是不考虑宕机这样的故障的,后来引入了主从之后,你就可以想象,它就能够解决服务器挂掉、硬盘挂掉等问题。不同的存储系统,因为对可靠性要求不一样,它的实现难度也有非常大的差别(参考:http://open.qiniudn.com/cloud-storage-tech.pdf)。

那么现实中的存储,好吧,第一个我提了七牛云存储,我这是打广告了。第二像MongoDB、MySQL等这些都是存储。大家经常接触的也主要是这一些。

模块的设计

我一般讲模块设计的时候,都会先讲架构相关的一些东西。当然架构这个话题,要完整的讲,可以讲很长很长时间。因为架构的话题真的很复杂。如果只是用一两页描述架构的话,我会谈这么一些点。首先架构师必须重视的第一件事情是需求,因为架构的目的是为了满足需求,这一点千万不能搞错。谈到架构,很多人都会喜欢说,我设计了一个牛逼的框架。但是我长期以来在强调的一个观点是说,框架这种事情其实在架构哲学里面一点都不重要,框架其实是实践层面的事情,架构真正需要关心的其实是需求的正交分解,怎么样把需求分解得足够的正交。所谓的正交就是两个模块之间没有什么太复杂的关系。当然正交是数学里面的词,我不知道其他人有没有会把它用到这个领域。但是我觉得正交这个词很符合需求分解的这个概念。

随着大需求(比如说一个应用程序,或者一个服务器)逐渐被切成很多个小需求,小的需求继续分解变成一个个类和函数。这一层层的需求分解的单元,本质上来讲,都是同样的东西,都是模块,只是粒度问题。所有这些app、service、package、class、func等,统一都可以称之为模块。那所有的模块,第一重要的是什么呢?是它的规格。模块里面最核心的,任何一个模块的规格是要体现需求。为什么我会说我是反框架的,因为框架其实就是模块的连接方式,不同的模块如何连接这个框架。那这种连接方式通常是易变的、不稳定的,因为框架是需要演进的。随着需求的增加、修改,它会不断演进,肯定后面会发现,之前搭的框架不太好了,需要重构。框架需要变的时候,通常很痛苦,所以也是很多人为什么重视框架的原因。但是不应该因为这一点儿把框架看得太重。因为不稳定的东西,通常是最不重要的东西。你要抓住的是稳定的东西。因此,框架只是实践程度可依赖的东西,但是从架构来讲不要太强调。

模块,刚才我讲了,模块其实最重要的是规格,也就是使用界面,或者叫interface(接口)。对于一个应用程序来说,interface就是用户交互。对于一个service来说,interface就是api。对于一个package来说,就是包的导出的函数或者类。对于一个class来说,就是公开的方法。对于函数来说就是函数的原型。这些都是interface。模块的interface必须体现需求,否则这个interface就不是一个好interface。

总结一下,如果要提炼模块的最佳实践的话,我会提炼这样三点。

  • 第一,模块的需求一定要是单一职责。就是这个模块不能做太多的事情,如果有太多的事情,它就要进一步的分解。
  • 第二,模块的使用界面要体现需求。大家一看这个模块的界面,就知道这个模块是干什么的。比如一个软件,你下载下来玩的时候,一看就应该知道这个软件目的是什么,而不是看了好几眼都分不清楚这个软件到底是财务软件还是什么软件,那这个interface就太糟糕了。所以其实所有的interface都是一样的,都要体现需求。
  • 第三是模块的可测试性。任何一个模块,如果提炼得好的话,它应该很容易测试。为什么这一点很重要呢?因为测试在软件系统里面其实非常重要,尤其是在服务端开发,尤其是像七牛这样一个做基础服务的,一个bug或者一个故障会导致成千上万甚至上百万的公司受影响,那么这个测试非常非常重要。可测试性包括什么呢?它包括把模块跑起来的最小的环境。如果一个模块耦合小,意味着外部环境依赖少,这个模块就很容易测试。反过来,很容易测试意味着这个模块的耦合很低。因此,模块的可测试性,其实能够反向来推导这个模块设计得好与不好。

展开来讲,第一,模块的使用界面,它应该符合需求,而不应该符合某种框架的需要。这一点,我为什么强调呢?而且是反复强调呢?是因为我认为很多刚刚踏入这个行业的人会违背这一点,包括我自己。最早做office软件的时候,我很清楚自己犯了无数次这样的错误,所以我后来把这一条作为非常重要的告诫自己的点。因为不体现需求的话,意味着这个模块的使用界面是不稳定的。最自然体现需求的使用界面是最稳定的。第二,我认为模块应该是可完成的。也就是说它的需求是稳定的可预期的,或者是说模块的目标是单一的,只做一件事情。只有这样才能做到模块可完成。但是反例很多很多。比如C++里面有Boost、MFC、QT这些库。其实你知道,它们都是大而统,包含很多的东西,你不知道这个库是干嘛的。这种我个人是非常反对。我早期也是这样的,早期自己写了一些通用库,都是很含糊,想到一个很好的东西,就把它扔到通用库里面,最后这个通用库就变成垃圾筒,什么东西都有。任何一个模块,都有一个你对它的边界的界定,边界界定好之后,这个模块总归有一天,它逐步趋于稳定,最终几乎不必去修改(就算修改也只是实现上的优化)。

刚才我也讲了模块应该是可测试的。可测试性可以表征一个模块的耦合度。耦合越低越容易测试。所谓的耦合就是环境依赖,我依赖外部的东西越少越容易测试。一个模块要测试的话,必须要模拟整个环境,让它跑起来。

服务器的设计

服务器的设计首先要遵循模块的设计,其次是服务器有服务器特有的一些东西。第一是服务器的测试。七牛对于测试非常看重,参加过上次Gopher China大会的都知道我讲的内容,就是HTTP服务器如何测试。七牛为此自己发明了一个DSL语言,就是领域专用语言,专门用于测试。现在这个DSL在我们团队用得非常广泛,基本上所有新增的模块都会用这个方法进行单元测试。第二个是服务器的可维护性。我没有讲服务器本身应该怎么设计,因为这个其实跟领域是有关系的,也就是你做什么事情,本身是很具化的,我没办法告诉你应该怎么样设计。服务器的设计,无非遵循我刚刚讲的模块设计的一些准则,但是服务器有它自己的特征,因为它作为一个互联网,或者作为一个C/S结构的东西,它有一些通用的需求。刚才我们讲模块需要做需求的正交分解,那作为一个Web服务器的话,除了业务相关的东西,会不会有一些通用的需求?其实通用的需求是非常多的,我这里列了很多,但是肯定不完整,当然列这一些,已经有更多细节的话题可以展开来讲。

第一个比如路由,这个就不用说了。大家看到大部分的Web框架都会解决路由相关的问题。第二个是协议,通常大家看到比较多的协议,如果用HTTP的话,会比较多的见到form、json或者xml。第三个是授权,授权我就不展开了。会话,其实跟授权类似。第四个是问题的跟踪和定位。这个我等一下再讲。第五个是审计。审计有两个用处,一个是计费,像七牛这样的服务需要审计,因为每一次API请求,会有计费相关的东西;另一个做对账,你说有我说没有,那最终是有还是没有,看服务器的日志来说了算。第六个是性能的调优,然后是测试和监控。为什么会有这么多的需求呢?原因是因为服务器开发,我觉得大家可能关注了开发两个字,但是可能忘了服务器开发完了是干什么的。服务器开发完了,它后面还要在线上跑很长时间,而且绝大部分的时间是在线上。所以服务器的开发其实和在线上运维的过程是不能脱节的。正因为不能脱节,所以我们才会关注像监控、性能调优、问题跟踪定位、审计相关的一些需求。

这一些其实都是和具体的业务无关的,所有的服务器都需要。那么其实这些需求,可以被分解出来,由一些基础组件去实现。当然因为这些东西,很多都是和七牛内部的组件相关,所以我没有展开。

服务器的测试

我大概的讲一下服务器的测试方法,在七牛这边怎么用的,还有服务器可维护性相关的东西。第一个是七牛用的两个东西,一个是叫mockhttp,当然这个不一定要用,因为我知道Go其实有标准的httptest模块,它能够监听随机端口的服务。七牛也用这种方式起测试服务器,但是我自己个人更喜欢用mockhttp。因为它不监听物理的端口,所以它没有端口冲突问题,心智相对负担比较低,而且相比那种监听真正物理端口的程序跑得会更快一些。这个mockhttp已经开源了,在github.com/qiniu上可以找到。

第二个是基于七牛的httptest,暂时还没有开源。今天我没有办法完整地讲,因为我之前有个完整的讲座,在网上可以搜索得到。它最核心的思想是什么呢?你不用写client端sdk,直接就可以基于http协议写测试案例。如果没有这样的工具,写一个服务器的测试,显然第一件事情就写一个sdk,把你的服务器的interface,也就是网络api包装一下,包装成一个类,里面有很多函数。然后通过这个类去测你的服务。这种模式有什么不好的地方呢?最大的问题是这个sdk,其实很多时候是不稳定的。不稳定会带来一个问题,就是这个sdk你改改改,有可能改sdk的人忘了服务器的测试案例在用它,改了后会导致编不过,从而导致测试案例失败。当然也有另外一种做法,就是我为服务器的测试专门写一个sdk,但是我觉得这个成本是比较高昂的,因为你相当于只为某一个具体的场景专门做一个事情,而这个事情,可能工作量不一定非常巨大,但是很繁杂。因此,七牛的httptest最本质的点是,可以直接写一个看起来像直接发网络包的方式去做测试。然后尽可能把网络协议的文本描述让它看起来更人性化一些,让人一看就知道发过去的是什么。这样也可以认为是写了一个sdk,但是这个sdk非常通用,所有的HTTP服务器都能去用它。这样所有的HTTP服务测试,包括单元测试和集成测试,都可以用这种方式测。

服务器的可维护性

我刚才在讲服务器的需求时提过这一点,也是我觉得非常非常需要去强调的一点,就是服务器的可维护性。这一点是极其极其重要的,因为服务器的开发和运维并不能分割的,服务器本身的设计需要为运维做好准备。这个系统跑到线上会发生很多问题,发生这些问题之后,如何快速地解决,需要在开发阶段就去思考。正因为如此,所以才会有很多出于可维护性上的一些基础的需求,包括日志。日志其实是最基础的。没有日志怎么排除这种故障呢?但是对于经常要发生的情况,服务器设计本身就需要避免,最最基本的不能有单点,因为有单点,一个服务器挂掉了,线上就完蛋了,运维就要立刻跟上。但是这种事情必然会发生。对于必然会发生的事情,必然是需要在开发阶段就去避免。所以某种意义上来说,高可用是为了可维护性,如果不是为了可维护性,就不用考虑高可用的问题。

服务器的可维护性,我觉得大概分为这样几个类别的需求。

  • 第一个是性能瓶颈。性能瓶颈,比如说你发现业务支撑的并发量不够,经常需要加机器,这时候要发现慢到底慢在哪里。也许你认为网站刚刚上线的时候,不用考虑这个问题,但是如果这个网站能做大的话,总有一天会碰到瓶颈问题。因此,最早的时候,就要为瓶颈问题考虑好,如果万一发生瓶颈,如何能尽快发现瓶颈在哪里。此外,我认为非常非常关键的是异常情况的预警。很多时候如果存在瓶颈,那么等它发生的时候就已经是灾难了。最好的情况下,在达到灾难的临界点之前,最好有个预警线,在那个预警线上开放排除问题就比较好一点。
  • 第二个是故障发现和处理。当线上真的发现故障了,虽然我们极力去避免,但是肯定避免不了了,一定会发生故障,没有一个公司不会发生故障。发生故障的时候,如何去快速地响应,这个就是快速地定位故障源。这个其实也是服务器开发里面,我觉得需要深度去考虑的一个问题。对于经常发生的故障,必须要实现自我恢复。也就是我刚刚第一个讲的。一旦发生这个事情,不是偶然,是经常的。那么你必须要在开发阶段解决,而不是到线上运维阶段解决这个问题。
  • 第三个是用户问题排查,一个用户,提供了一个非常个例化的问题,不是服务总体的一个问题,可能就是一个客服的个例问题,那么就需要有所谓的reqid。每一个用户请求都有一个唯一的reqid,一旦是个例的问题需要跟进,客户要告诉我你的reqid是多少,输入这个ID,就能把所有和该请求相关的东西都能找到。整个服务端的请求链都能找到之后,这个问题的排查就更容易。

以上为本次分享的全部内容,希望能给开发者们带来帮助。点击“阅读原文”查看本次分享的PPT。

Reddit月浏览量从百万扩容到十亿的陷阱和教训

伯乐在线导读:Reddit[1]是一个社交新闻网站。用户可以将互联网上搜集或原创的图片或材料以帖子形式发布于网站上。而后其他用户可以投票,投票的结果将作为帖子排名的依据。

Reddit 网站的第一位领薪雇员杰里米·埃德伯格在RAMP[2]讨论会上做了一次出色的演讲 ,他教给我们很多关于如何创建一个成功的社交网站。这儿可以看到视频。

杰里米借用了(宗教里对比)罪恶和美德的方式来总结经验教训,他分享了在扩展Reddit过程中所犯错误的例子,也可以看到他们同样做了很多正确的事情。不过有点令人吃惊的是,杰里米现在是Netflix[3]的可靠性架构师。所以这篇文章中也有Netflix的一些经验。(感谢 dryrun 的热心翻译和详细的译注,感谢@战斗的那美克星人 的重审校对。如果其他朋友也有不错的原创或译文,可以尝试提交到伯乐在线。)

我完全认可的一些经验教训:

  • SSD[4]看做是便宜的内存[5],而不是昂贵的硬盘。当我们为了数据库的性能将reddit从普通硬盘转移到SSD后,我们的服务器数量从12台降低到1台(这个很可能是个比例数据,译者),并且还有很大的富余。SSD虽然贵了四倍,但是你会得到16倍的性能提升,这么做是很值得的。
  • 给用户一些权力,看看他们怎么做,然后考虑把好的点子加入到网站功能中来。用用户让reddit学到了很多东西,并且,用户很大程度上提升了网站的流畅度(文后会有描述用户如何协助网站的顺利运转,译者),这个是我众多收获中很重要的一点。用户会告诉你很多你不知道的事情。关于这点,reddit gold是个很好的案例,这个项目开始只是社区里的一个玩笑,后来它被做成了产品,并深得所有用户喜爱。
  • 项目之初关于可扩展性架构的考虑其实是没有太多必要的。一开始时你不知道什么会是你的功能集,所以你也不会知道你有哪些扩展问题。随着你的网站增长,你可以了解哪里将存在扩展问题。
  • 把未登录用户当作二等公民。给未登录用户返回缓存内容,Akamai(美国著名CDN服务提供商)[6]首当其冲,reddit压力大减,这种做法带来巨大的性能提升。

 

还有很多。我们从早期扩展reddit的错误中学到许多经验教训,下面是我的观点的一些解释和展开:

统计

  • 流量大约每15个月增加1倍。
  • 上个月(8月份),来自177个不同国家的 73,293,644 名独立访客查看了reddit的 4,885,611,148 个页面。“10亿的PV承载能力”来自这个数据。不确认现在的架构是否会有很大不同。
  • 28名雇员。
  • 每名雇员应对大约240万独立访客。(链接
  • 数以千计的版主。
  • 在2012年时,他们用240台服务器,支撑每月20亿页面浏览量和Postgres[7]里的2TB数据。所有高流量压力数据从EBS[8]转移到本地临时磁盘。

 

起源故事

  • Reddit始于2005年。在带着“通过文字来点餐”的想法去Y Combinator[9]被拒绝后,他们和Paul Grahm讨论并着手开始建设互联网网页。这就是reddit。那时他们并不知道Digg[10]
  • 从数据中心开始,随后将功能逐步转移至EC2[11]

1 在2006年在EC2上开始使用S3[12]提供标识的存储和服务。

2 2007年开始使用S3支持缩略图。

3 2008年使用EC2提供批处理服务,并通过vpn通道连接数据中心。

4 2009年使用EC2服务整个网站。网站停运一天,将数据全部迁移到EC2。稍后会谈到著名的数据重力例子。

 

EC2

  • 迁移到EC2上的动机

5 堆叠机架不好玩。不想租用更多的机柜和购买更多的服务器。

6 数据中心增长过快,增速在早期无法预测。

7 4人一组的开发团队的使用价格,EC2比在旧金山的数据中心成本节约29%。

  • EC2不是灵丹妙药。你要经受更高的网络延迟和嘈杂的邻居,所以计划好应对方案。好处是你可以随时按需扩容。
  • 持续跟踪EC2的资源限制

8 所有资源都有账户配额。

9 甚至亚马逊自己都不清楚什么地方可能有配额。

10 跟踪配额,并在需要的时候提升。

11 捕获异常以确认什么时候又达到了配额上限。

 

架构

  • Reddit的架构很简单。用户连接网络层,网络层对话应用层。应用层跟memcache[14],Cassandra[15],以及Postgres对话。Postgres使用主从配置。批处理系统使用Cassandra和Postgres。
  • 对比netflix,使用面向服务架构,所有组件之间通过REST API进行交互。

12 优点:更容易自动调节,因为只是有问题的服务需要调节;更容易规划容量;问题也更容易定位,因为它们在REST调用背后都是孤立的;改动和升级产生的影响范围更小;更高效的本地缓存。

13 缺点:需要多个开发团队或开发者来跟进多个服务,因此你需要更多的人;需要一个共同的平台以防重复工作;对刚起步的小团队来说太多的间接支出。

  • PostgreSQL是一个出色数据库。它提供很棒的、很快的键值存储。
  • 电子邮件是一个很难的问题。很难正确被送达。开始是使用自行开发的邮件系统,但是今天可能会去选择一个电子邮件服务提供商。
  • 队列是救世主。组件之间传递任务时,把它放到一个队列。你会得到一个不错的小缓冲区。(Reddit使用RabbitMQ[17]作为消息队列。)
  • 混合HAProxy[18]Nginx[19]。一些流量被引导到它们中的一个。在尝试Niginx(遇到故障)后,负载均衡会选择HAProxy。它使用L7[20]做负载均衡。Nginx仍被用来完成SSL[21]请求和服务静态内容。

 

代码

  • 框架。最开始,使用了一个基于Python的架构(Django[23]太慢了)Pylons[22]。对Pylons进行定制以使上手变得容易,不过最终因为使用场景差异较大而放弃。对Pylon做了很大改动,结果最后使得它难以升级到新版本(现在修复了)。会将再次使用金字塔的(Pylons的新名字)。
  • 基于线程还是事件?基于线程可以提前以排列大小,但大小可能是错误的。基于事件可以处理更多连接。但是当你碰壁时就只能碰壁。你想花更多时间来规划你得线程池大小,还是就突然碰壁?
  • 开源很不错。Reddit建立在开源基础上。不用为软件付钱很不错,特别是在起步阶段。

 

数据

  • 数据是公司最重要的资产。Facebook,Google和Flickr[24]等公司都是建立在数据之上。
  • 数据重力。数据放在哪儿,应用就部署到哪儿。思路是应用随数据调整。数据创建了重力井,其他东西需要围绕它展开,因为数据是最难移动的。数据越多就越难移动。目前,从EC2导出数据的成本太大了。这就是为什么EC2允许你免费导入数据,当你导出时向你收费。他们希望你把你所有的数据放在云中。
  • 关系与非关系。Reddit里的大部分数据是以键值方式存储在Postgres。一切涉及金钱交易的数据都被保存在关系数据库,这样可以保障更好的事务性和更容易进行分析。
  • Postgres很稳定。它坚如磐石,他们从未有过自身问题。如果他们有问题,那也是使用上的问题,例如用Python编写的调用系统。因为,很难找到Postgres专家。
  • Postgres被选为键值存储,因为当时还木有Cassandra。另外,Postgres的速度非常快,而且现在原生地支持键值(KV)。
  • 分区。写入被分割的四个主数据库:链接,帐户,subreddits[25],评论,投票,以及杂项。

14 每个数据库都有主从设计。投票用数据库是一主一从。而注释用数据库是一主12从。

15 尽可能避免从主数据库读取,而是直接从从属数据库读取,保持主数据库只进行写操作。

16 客户端库将在从属数据库之间进行负载平衡,如果一个从属数据库处于忙状态,则尝试使用另外一个从数据库。

17 编写一个叫“thing”的数据库访问层。

18 这种方式支撑了很长时间,包括数据库切分、从数据库读取、跟踪从数据库的性能用于更好的负载均衡。

  • Cassandra

19 快速写入,快速反查,简单的增量可扩展性,无单点故障。

20 在Netflix数据被分布式部署在三个不同的区域。完整的数据副本同时存在三个区域中。即使一个区域丢失,依然可以正确运行。

21 将“投票”相关数据切换到Cassandra是reddit的一个巨大成功。Cassandra的布隆过滤器(Bloom Filters[26])使快速反查成为可能。它能很快识别哪个评论你没有投票,所以反查结果会很快返回。(更多关于这个话题

 

社交

  • 2008reddit是开源的

22 用户可以读取代码从而得知篡改投票是不存在的。

23 用户可以添加他们想要的功能,reddit会接受它。这个其实行不通,因为人们并不真的想写代码。

24 招聘。其他人知道这代码,所以更容易雇人。

  • 蠕虫事件。有人指出在页面中注入额外的JavaScript的方式可以制造蠕虫病毒。如何出现已无关紧要,但是蠕虫事件失控了。在一个创始人的婚礼当天,整个团队乘一架飞机从婚礼现场赶回。一个用户已经预先提交了一个可以组织蠕虫蔓延的补丁。代码开源使社区在危急时刻提供了很大帮助。

 

Reddit如何挣钱?

  • 侧栏广告,自助式广告,商品,reddit gold,市场。
  • 需要注意的是reddit是尚未盈利(链接)。这带来了一个问题,像reddit这样的网站,什么时候能在云端盈利?
  • 还要注意的是,reddit已不属于Condé Nast[27]了,所以它是独立的。(链接

 

错误

  • 没有考虑到迁移到EC2后增加的延迟。在数据中心,机器之间的响应是亚毫秒级的,一个页面的加载时间内可以进行1000次memcache访问。在EC2上并非如此。 Memcache访问时间增加了10倍,到毫秒级,使得之前的逻辑基本不可用。修复方法是在一个memcache请求内进行批量操作。
  • 只是承诺。亚马逊并不总兑现承诺和解决问题。所以设计的时候需要考虑绕过而不是解决。 (这里没有提到,也许EBS?)
  • 在生产中使用更稳定的产品。我们在Cassandra的开发阶段就开始使用它。现在真的很棒,但那时它是个问题专业户。
  • 本应该更早将负载转移到客户端。服务器做了很多的页面渲染,其中一些本可以交给客户端去做。 Facebook是这方面的大师。返回一个提前规划好的空间和很多的div,以及通过API调用来填充他们。这就是他们起初所希望的reddit。它本可以扩展得更好。它还有助于调试,因为很容易确定哪个API调用出了问题。
  • 不具备足够的监控,使用的监控系统并不能很好支持虚拟化。一开始用的是Ganglia[28],倒是有很好的图形界面,但很难使用,而且变化太快,特别是在一个实例会不断创建和消亡的场景下。
  • 数据没有过期时间。 你可以在reddit看到最初的那些评论被挖坟。他们已经开始着手做一些限制工作,你不能再给旧评论投票,或给旧帖添加评论。挖坟的问题会导致数据随着时间的推移不断增长,使得在数据库保存热点数据的难度越来越大。
  • 没有使用一致性哈希算法。当hash一个缓存时问题是,如果你需要添加更多的缓存,你会被卡住,因为所有的数据会命中一个hash或者许多你正在哈希的缓存。当增加缓存时你无法在此平衡。一致性哈希算法是解决这个问题的一个方法。迁移到Cassandra使这个问题被解决。

 

经验教训

  • 扩展的关键是现于用户发现瓶颈。
  • 使用代理是扩展的巨大福音。根据用户命中的URL进行路由。 Reddit有一个系统,监控每个URL抵达服务的时长(从请求发起到到达服务的时间,译者)。人们被放进不同的线路。慢流量去一个地方,快的去另一个。基于响应速度的流量分割是一个巨大的改进。
  • 自动化所有事情。像对待你的代码一样对待你的基础架构,你的生活会容易得多。一切都应该自动上线,自动配置。
  • 没有必要从一开始就建立一个可扩展的架构。一开始时你不知道什么会是你的功能集,所以你也不知道你有会有哪些扩展问题。随着你的网站增长,你可以了解到哪里将是扩展问题。
  • 刚起步时不要使用面向服务的架构。记住,当发展到中等规模时你可以去实现它;前期只会带来很多额外的开销。
  • 不要跟随潮流。有时候潮流是对的,例如node.js。
  • 给所有东西加个限制。给反复发生的事情设置一个上限,并根据需要提高或降低限制。如果超过限制,阻止用户以。这样可以保护正常服务。例如为subreddits上传文件标识。有用户指出他们可以上传很大的文件并损害系统。也不要接受巨大的文本。会有人指出如何给你发送一个5GB的文本。
  • 长远计划。在设计时总是假设将有一大堆事情需要你做。应用服务器,数据库,缓存。总是在开始时就假定你有一个以上的事情。这样,未来的横向扩展会更容易。
  • 用C重写Python函数。在reddit扩展中,为了获取速度,他们找出Python代码里重复次数最多的函数,并用C重写。比如过滤器,markdown渲染,memcache调用。Python的一个好处是可以很容易并高效地调用C。
  • 尽可能保持无结构模式。这样的话,可以很容易地增加新的功能。你所要做的就是添加新的属性,而无需改变数据表结构。
  • 数据过期。锁定旧帖和创建一个完整渲染的页面并缓存它。这个可以用来应对大规模旧数据冲击数据库的场景。同样的,不要允许在旧的评论上投票,或给旧帖添加评论。用户很少会关心这个。
  • 把SSD[4]看做是便宜的内存[5],而不是昂贵的硬盘。当我们为了数据库的性能将reddit从普通硬盘转移到SSD后,我们的服务器数量从12台降低到1台(这个很可能是个比例数据,译者),并且还有很大的富余。SSD虽然贵了四倍,但是你会得到16倍的性能提升,这么做是很值得的。
  • 每个工具都有不同的使用场景。Memcache不提供持久性保障,但是会非常快,所以投票数据存储在那里,以使渲染页面尽可能的快。Cassandra持久,快速,并能提供快速的反查询,因为其布隆过滤器,所以它适合提供memchche里投票数据副本的持久化。 Postgres是坚如磐石而且是关系型的,所以用作备份Cassandra投票(如果需要,Cassandra中的所有数据可以从Postgres恢复),也可以做批量处理,有时这需要关系的能力。
  • 将未登录用户当作二等公民。未登录用户之前曾占据约80%的流量,现在是接近50%。总是给未登录用户提供缓存内容,Akamai承载了大部分Reddit的流量,带来巨大的性能改进。附带的好处是如果reddit宕机,你不登录的话,你可能永远不会知道。
  • 把一切都放入一个队列。投票,评论,创建缩略图,预计算查询,邮件处理和更正。通过监控队列长度,你可以知道什么时候队列可能存在问题了。附带的好处是队列隐藏了用户的问题,因为诸如投票请求的事情都在排队,投票不做提交时用户并不会马上发现。
  • 把数据分散到多个可用区域进行保存。
  • 避免在单一实例上保存状态。
  • EBS磁盘频繁创建快照。
  • 不要在实例上保存密钥。亚马逊现提供了一个服务。
  • 基于安全分组分解功能
  • 提供API。程序员将会在你的平台上做东西。例如,reddit的iPhone应用程序,就是其他人用公开的API制作的。
  • 在自己的社区保持活跃。Reddit的用户喜欢reddit的管理员在自己网站上活跃,并与他们互动。
  • 让用户为你工作。有用户输入的网站,总是有作弊、垃圾邮件和欺诈这样一些问题。Reddit大部分管理的工作是由成千上万的志愿者来完成的,比如他们处理大多数的垃圾邮件问题。这种方式运转得非常好,是reddit的可以保持小团队的原因之一。
  • 给用户一点权利,看他们用它做什么,并好东西变成功能。例如,当添加了给subreddits增加CSS得能力时,他们看到人们在做什么,增加了许多通用的东西作为大家的功能。这也使得用户能在Reddit上做东西而兴奋,因为他们喜欢那种操控感。有很多其他的例子。
  • 倾听你的用户。用户会告诉你很多你不知道但是你可能想知道的东西。例如,reddit gold在社区里以一个笑话开始。后来他们把它做成了产品,用户也很喜欢。

 

 

译注:

1. Reddit是一家美国社交新闻网站Reddit.com。
2. RAMP是一个所有创业者在扩大经营规模前想要参加的讨论会。
3. Netflix是一家美国公司,提供互联网随选流媒体播放、在线出租业务。
4. SSD即Solid State Disk固态硬盘。
5. RAM即Random Access Memory随机访问存储器。
6. Akamai是一家美国内容分发网络(CDN)服务商。
7. Postgres即PostgreSQL, 是一个自由的对象-关系数据库服务器(数据库管理系统)。
8. EBS即exclusion basis system,动态密钥管理方法。
9. Y Combinator是一家以投资种子阶段初创公司为业务的创投公司。
10. Digg即“掘客”,或者“顶格”,美国公司,2012年被纽约科技开发公司Betaworks收购。
11. EC2即亚马逊弹性计算云(Elastic Compute Cloud),是一个让使用者可以租用云端电脑运行所需应用的系统。
12. S3即亚马逊简易存储服务(Simple Storage Service),由亚马逊网络服务系统提供的在线存储服务。
13. VPN即虚拟专用网络(Virtual Private Network),是在公用网络上建立专用网络的技术。
14. Memcache是一个高性能的分布式的内存对象缓存系统。
15. Cassandra是一套开源分布式NoSQL数据库系统。
16. REST即表征状态转移(Representational State Transfer)是Roy Fielding博士在2000年博士论文中提出来的一种软件架构风格。
17. RabbitMQ是流行的开源消息队列系统。
18. HAProxy提供高可用性、负载均衡以及基于TCP和HTTP应用的代理。
19. Nginx即engine x,是一个高性能的 HTTP和反向代理服务器。
20. L7即Layer7,是网络层协议的脚本文件。
21. SSL即Secure Sockets Layer安全套接层,是为网络通信提供安全及数据完整性的一种安全协议。
22. Pylons是一个开放源代码的Web应用框架,使用python语言编写。
23. Django是一个开放源代码的Web应用框架,使用python语言编写。
24. Flickr是雅虎旗下图片分享网站。
25. Subreddit 是reddit上一个定制的子论坛。
26. Bloom Filter即布隆过滤器,是一个很长的二进制向量和一系列随机映射函数。
27. Condé Nast即康泰纳仕,是一个总部位于美国纽约市的国际期刊出版集团。
Ganglia是UC Berkeley发起的一个开源集群监视项目。

Windows 10官方批量激活密钥来了

当微软最初宣布Windows10免费升级之时,好多小伙伴都以为要跟密钥说再见了。然而随着微软逐渐公布Win10免费升级策略,大家也逐渐的明白,Win10系统已然离不开密钥。今天微软在TechNet社区公布了Win10预览版的KMS客户端安装密钥,小编在此跟大家分享一下。

     【更新】最新Windows 10免费激活教程(附汇总秘钥)

http://www.iwin10.com/jiaocheng/835.html

\

这些密钥主要用于新系统的批量激活,对于拥有多台设备的企业组织来说是非常有用的。各版本密钥如下:

Windows 10专业版:W269N-WFGWX-YVC9B-4J6C9-T83GX

Windows 10专业版N:MH37W-N47XK-V7XM9-C7227-GCQG9

Windows 10企业版:NPPR9-FWDCX-D2C8J-H872K-2YT43

Windows 10企业版N:DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4

 Windows 10教育版:NW6C2-QMPVW-D7KKK-3GKT6-VCFB2

 Windows 10教育版N:2WH4N-8QGBV-H22JP-CT43Q-MDWWJ

 Windows 10企业版2015 LTSB:WNMTR-4C88C-JK8YV-HQ7T2-76DF9

Windows 10企业版2015 LTSB N:2F77B-TNFGY-69QQF-B8YKP-D69TJ

其中带“N”的版本中不包含Windows Media Player播放器,另外家庭版不需要KSM,因此这里并没有提供家庭版的密钥。

\

说到KMS,对于新用户来说或许并不熟悉,小编在这里就给大家科普一下吧。KMS是微软在Windows Vista、Windows Server 2008系统中首次提出的全新产品激活机制,当然这一举措是为了更好地反盗版。有用多台设备的企业或组织可以通过局域网中的一台KMS主机服务器来激活本地网络内的所有机器,有效期为180天,到期后则需要重新连接激活。

有关KMS部署和激活方法可以参考以下官方教程

https://technet.microsoft.com/en-us/library/ff793419.aspx

  玩转Windows10操作系统,请锁定专注Win10内容阅读体验的Windows10之家!

Redis之KEY操作命令 【学习笔记】

本文记录redis key操作的大部分命令和自己测试的例子,希望能帮助到别人

 

开启服务

$sudo ./src/redis-server &

键操作

*定义并赋值变量苹果的数量
$ redis-cli set dangcheng::apple::count 152
OK

*获取刚才的苹果数量
$ redis-cli get dangcheng::apple::count
“152”

*当苹果数量增加1 incr关键字实际上是increment的简写
$ redis-cli incr dangcheng::apple::count
(integer) 153

*当苹果来了一盒,而这盒苹果共7个则我们需要加上指定数量关键字是incrby
$ redis-cli incrby dangcheng::apple::count 7
(integer) 160

*如果set一个php数组 我们可以使用php函数serialize将对象序列化后写入
$age=array(“Bill”=>“35”,“Steve”=>“37”,“Peter”=>“43”);
$age_str = seriallze($age);//将这个结果写入redias中
$age = unseriallze($age_str);//get获取后我们可以使用unseriallze反序列化把其转换为PHP对象

EXISTS key 是否存在

存在返回 1
$ redis-cli exists dangcheng::apple::count
(integer) 1
不存在返回 0
$ redis-cli exists dangcheng::apple::color
(integer) 0

DUMP序列化值 不包含任何生命周期信息

不存在返回nil
$ redis-cli dump dangcheng::apple::color
(nil)
返回序列化结果
$redis-cli dump dangcheng::apple::count
“\x00\xc1\xa0\x00\x06\x00\xb0t6\xdaT\x99#\xa2”

expire 和 ttl 设置生命期和查看剩余时间

TTL命令是查询生存时间剩余,不设置生存时间返回-1代表永久
$ redis-cli ttl dangcheng::apple::count
(integer) -1
用expire设置生存时间剩余366秒
$ redis-cli expire dangcheng::apple::count 366
(integer) 1
写这段的过程中消耗了127秒
$ redis-cli ttl dangcheng::apple::count
(integer) 239
PS1:此外还有PEXPIRE命令与expire功能是一样的,只是设置是以毫秒为单位
*PS2:此外还有PTTL命令与TTL功能是一样的,只是显示是以毫秒为单位

expireat

此命令不同于expire去设定剩余,而是直接设置当前key什么时候死亡 而参数是linux时间戳表示
PS:pexpireat 命令与expireat命令等效,区别是单位精确到毫秒数的unix时间戳

keys 查找所有符合给定模式 pattern 的 key

KEYS * 匹配数据库中所有 key 。
KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
KEYS hllo 匹配 hllo 和 heeeeello 等。
KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo 。
特殊符号用 \ 隔开
$ redis-cli keys dangcheng::*
1) “dangcheng::apple::color”
2) “dangcheng::apple::count”

migrate 将当前redis实例中的对象迁移到另一个host下的对应端口的实例【数据的迁移】

在本机8888端口创建另一个redis服务
$ redis-server –port 8888 &
[1] 13086
原有实例中存在
$ redis-cli
127.0.0.1:6379> keys dangcheng::*
1) “dangcheng::apple::color”
2) “dangcheng::apple::count”

8888端口下的实例中找不到数据
$ redis-cli -p 8888
127.0.0.1:8888> keys dangcheng

(empty list or set)
将源实例中的数据count传入8888端口的实例
127.0.0.1:6379> migrate 127.0.0.1 8888 dangcheng::apple::count 0 -1
OK
源实例中的count不见了,证明传输源会被删掉
127.0.0.1:6379> keys dangcheng
1) “dangcheng::apple::color”
在8888端口下的实例中查到了
127.0.0.1:8888> get dangcheng::apple::count
“25”

move 将数据从一个db空间中迁移到另一个db空间中

选择index0的dbspace
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> keys *
1) “dangcheng::apple::color”
2) “test::first”
移动到index1的dbspace
127.0.0.1:6379> move test::first 1
(integer) 1
127.0.0.1:6379> keys *
1) “dangcheng::apple::color”
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) “test::first”
我们看到了的确在dbsopace1中查询到了
127.0.0.1:6379[1]> get test::first
“556779”
PS:要注意的是如果target db 有相同的key 则移动不会成功

OBJECT 对象操作

*返回给定 key 引用所储存的值的次数。此命令主要用于除错
127.0.0.1:6379[1]> object refcount test::first
(integer) 1

  • 返回给定 key 锁储存的值所使用的内部表示(representation)。
    127.0.0.1:6379[1]> object encoding test::first
    “int”
    127.0.0.1:6379[1]> set str ‘hello redis!’
    OK
    127.0.0.1:6379[1]> object encoding str
    “raw”
    返回给定 key 自储存以来的空闲时间(idle, 没有被读取也没有被写入),以秒为单位
    127.0.0.1:6379[1]> object idletime str
    (integer) 236
    对象可以以多种方式编码:
    字符串可以被编码为 raw (一般字符串)或 int (为了节约内存,Redis 会将字符串表示的 64 位有符号整数编码为整数来进行储存)。
    列表可以被编码为 ziplist 或 linkedlist 。 ziplist 是为节约大小较小的列表空间而作的特殊表示。
    集合可以被编码为 intset 或者 hashtable 。 intset 是只储存数字的小集合的特殊表示。
    哈希表可以编码为 zipmap 或者 hashtable 。 zipmap 是小哈希表的特殊表示。
    *有序集合可以被编码为 ziplist 或者 skiplist 格式。 ziplist 用于表示小的有序集合,而 skiplist 则用于表示任何大小的有序集合。

    persist 将一个key的超时时间变为永久

    使用expire 给对象设置生存时间
    127.0.0.1:6379[1]> expire test::first 3600
    (integer) 1
    查询剩余时间
    127.0.0.1:6379[1]> ttl test::first
    (integer) 3597
    127.0.0.1:6379[1]> persist test::first
    (integer) 1
    *的确变为了持久化key
    127.0.0.1:6379[1]> ttl test::first
    (integer) -1

*randomkey 随机抽取一个key
127.0.0.1:6379> randomkey
“dangcheng::apple::color2”
127.0.0.1:6379> randomkey
“dangcheng::apple::color”
127.0.0.1:6379> randomkey
“dangcheng::apple::color1”

mset[multi set] 批量设置

127.0.0.1:8888> mset a ‘aaa’ b ‘bbb’ c ‘cccc’
OK
查询所有key
127.0.0.1:8888> keys *
1) “dangcheng::apple::count”
2) “o”
3) “a”
4) “b”
5) “c”
清空dbspace
127.0.0.1:8888> flushdb
OK
*查看所有,很显然没有了
127.0.0.1:8888> keys *
(empty list or set)

rename 修改key名字

127.0.0.1:6379> set a ‘aaa’
OK
127.0.0.1:6379> rename a b
OK
127.0.0.1:6379> get a
(nil)
127.0.0.1:6379> get b
“aaa”

renamenx 目标key一旦已存在不会被覆盖 只有空的key才能成为target

127.0.0.1:6379> renamenx b c
(integer) 0
127.0.0.1:6379> get c
“66”
127.0.0.1:6379> get b
“bb”
127.0.0.1:6379> renamenx b d
(integer) 1
127.0.0.1:6379> get d
“bb”
127.0.0.1:6379> getb
(error) ERR unknown command ‘getb’
127.0.0.1:6379> get b
(nil)

dump&restore 序列化和反序列化

127.0.0.1:6379> dump c
“\x00\xc0B\x06\x00\xac\x15Y\x1aO127.0.0.1:6379> get c
“66”
序列化c中的值
127.0.0.1:6379> dump c
“\x00\xc0B\x06\x00\xac\x15Y\x1aO
通过反序列化一个值创建新的键 newc
127.0.0.1:6379> restore newc 0 “\x00\xc0B\x06\x00\xac\x15Y\x1aOOK
127.0.0.1:6379> get newc
“66”

sort [asc|desc] [alpha][limit]给一个列表进行排序 默认为asc方式,使用alpha方式可以给字符串进行排序,此外还可以使用limit进行范围限制

*lpush 是用于创建一个列表的命令
127.0.0.1:6379> lpush sort_list 15 2 45 20 16 3 7 9
(integer) 8
127.0.0.1:6379> sort sort_list
1) “2”
2) “3”
3) “7”
4) “9”
5) “15”
6) “16”
7) “20”
8) “45”
127.0.0.1:6379> sort sort_list asc
1) “2”
2) “3”
3) “7”
4) “9”
5) “15”
6) “16”
7) “20”
8) “45”
127.0.0.1:6379> sort sort_list desc
1) “45”
2) “20”
3) “16”
4) “15”
5) “9”
6) “7”
7) “3”
8) “2”

type 查看key的数据类型

*@return none (key不存在) |string (字符串)|list (列表)|set (集合)|zset (有序集)|hash (哈希表)
127.0.0.1:6379> type sort_list
list
127.0.0.1:6379> type c
string

Jsoup获取全国地区数据(省市县镇村)

最近手头在做一些东西,需要一个全国各地的地域数据,从省市区到县镇乡街道的。各种度娘,各种谷歌,都没找到一个完整的数据。最后功夫不负有心人,总算找到一份相对来说比较完整的数据,但是这里的数据也只是精确到镇级别,没有村一级的数据(后来通过分析数据源我知道了为什么,呵呵),在加上博主提供的有些数据存在冗余,对于有强迫症和追求完美的我,心想着我一定要自己动手去把这部分数据给爬取出来。

上述博文中的内容还算丰富,博主是用的是php来实现的,作为2015年度编程语言排行榜的第一位,我们也不能示弱啊,下面我就带着大家一起来看看用java怎么从网页当中爬取我们想要的数据…

第一步、准备工作(数据源+工具):

数据源(截止目前最全面权威的官方数据):http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2013/

爬取数据的工具(爬虫工具):http://jsoup.org/

第二、数据源分析:

首先jsoup工具的使用我在这里就不做讲解了,感兴趣的可以自己动手去查阅。

做开发就应该多去了解一些软件工具的使用,在平常开发过程中遇到了才知道从何下手,鼓励大家多平时留意一些身边的软件工具,以备不时之需。在做这个东西以前,我也不知道jsoup要怎么用,但我知道jsoup可以用来干嘛,在我需要的用到的时候,再去查阅资料,自己学习。

 

上述的数据源是2013年中华人民共和国国家统计局发布的,其准确性和权威性不言而喻。

接下来我们分析一下数据源的结构,先从首页说起:   

通过分析首页源码我们可以得到如下3点:

  1. 页面的整个布局是用的table标签来控制的,也就是说我们如果要通过jsoup来选择超链接,那么一定要注意,上图中不是只要标注了省市地区的地方采用的才是表格,整个页面中存在多个表格,因此是不可以直接通过表格
    Document connect = connect("http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2013/");
    Elements rowProvince = connect.select("table");

    来解析数据的。

  2. 页面中有超链接的部分有多少地方。可能是官方考虑到了你们这种程序员需要获取这样的数据的原因吧,页面很干净,除开下方的备案号是多余的超链接,其他的链接可以直接爬取。
  3. 省份城市的数据规律。包含有效信息的表格的每一行都有一个class属性provincetr,这个属性很重要,至于为什么重要,请接着往下看;每一行数据中存在多个td标签,每一个td标签中包含一个a超链接,而这个超链接正是我们想要的超链接,超链接的文本即使省份(直辖市等)的名称。

 

再次我们再看一下一般的数据页面(一般的数据页面包括市级、县级、镇级这三级数据展示页面):

之所以要把上述三个页面放在一起,是因为通过分析我们可以发现,这三级数据的数据页面完全一致,唯一不同的就是在html源码数据表格中的数据行tr的class属性不一致,分别对应为:citytr,countrytrhe towntr。其他均一致。这样我们就可以用一个通用的方法解决这三个页面的数据爬取。  

 

最后看看村一级的数据页面:   

在村一级的数据中,和上述市县镇的数据格式不一致,这一级所表示的数据是最低一级的,所以不存在a链接,因此不能采用上面市县镇数据的爬取方式去爬取;这里展示数据的表格行的class为villagetr,除开这两点以外,在每一行数据中包含三列数据,第一列是citycode,第二列是城乡分类(市县镇的数据格式不存在这一项),第三列是城市名称。

把握了以上各个要点之外,我们就可以开始编码了。

第三步、编码实现:

复制代码
  1 import java.io.BufferedWriter;
  2 import java.io.File;
  3 import java.io.FileWriter;
  4 import java.io.IOException;
  5 import java.util.HashMap;
  6 import java.util.Map;
  7 
  8 import org.jsoup.Jsoup;
  9 import org.jsoup.nodes.Document;
 10 import org.jsoup.nodes.Element;
 11 import org.jsoup.select.Elements;
 12 
 13 /**
 14  * 全国省市县镇村数据爬取
 15  * @author liushaofeng
 16  * @date 2015-10-11 上午12:19:39
 17  * @version 1.0.0
 18  */
 19 public class JsoupTest
 20 {
 21     private static Map<Integer, String> cssMap = new HashMap<Integer, String>();
 22     private static BufferedWriter bufferedWriter = null;
 23 
 24     static
 25     {
 26         cssMap.put(1, "provincetr");// 省
 27         cssMap.put(2, "citytr");// 市
 28         cssMap.put(3, "countytr");// 县
 29         cssMap.put(4, "towntr");// 镇
 30         cssMap.put(5, "villagetr");// 村
 31     }
 32 
 33     public static void main(String[] args) throws IOException
 34     {
 35         int level = 1;
 36 
 37         initFile();
 38 
 39         // 获取全国各个省级信息
 40         Document connect = connect("http://www.stats.gov.cn/tjsj/tjbz/tjyqhdmhcxhfdm/2013/");
 41         Elements rowProvince = connect.select("tr." + cssMap.get(level));
 42         for (Element provinceElement : rowProvince)// 遍历每一行的省份城市
 43         {
 44             Elements select = provinceElement.select("a");
 45             for (Element province : select)// 每一个省份(四川省)
 46             {
 47                 parseNextLevel(province, level + 1);
 48             }
 49         }
 50 
 51         closeStream();
 52     }
 53 
 54     private static void initFile()
 55     {
 56         try
 57         {
 58             bufferedWriter = new BufferedWriter(new FileWriter(new File("d:\\CityInfo.txt"), true));
 59         } catch (IOException e)
 60         {
 61             e.printStackTrace();
 62         }
 63     }
 64 
 65     private static void closeStream()
 66     {
 67         if (bufferedWriter != null)
 68         {
 69             try
 70             {
 71                 bufferedWriter.close();
 72             } catch (IOException e)
 73             {
 74                 e.printStackTrace();
 75             }
 76             bufferedWriter = null;
 77         }
 78     }
 79 
 80     private static void parseNextLevel(Element parentElement, int level) throws IOException
 81     {
 82         try
 83         {
 84             Thread.sleep(500);//睡眠一下,否则可能出现各种错误状态码
 85         } catch (InterruptedException e)
 86         {
 87             e.printStackTrace();
 88         }
 89 
 90         Document doc = connect(parentElement.attr("abs:href"));
 91         if (doc != null)
 92         {
 93             Elements newsHeadlines = doc.select("tr." + cssMap.get(level));//
 94             // 获取表格的一行数据
 95             for (Element element : newsHeadlines)
 96             {
 97                 printInfo(element, level + 1);
 98                 Elements select = element.select("a");// 在递归调用的时候,这里是判断是否是村一级的数据,村一级的数据没有a标签
 99                 if (select.size() != 0)
100                 {
101                     parseNextLevel(select.last(), level + 1);
102                 }
103             }
104         }
105     }
106 
107     /**
108      * 写一行数据到数据文件中去
109      * @param element 爬取到的数据元素
110      * @param level 城市级别
111      */
112     private static void printInfo(Element element, int level)
113     {
114         try
115         {
116             bufferedWriter.write(element.select("td").last().text() + "{" + level + "}["
117                 + element.select("td").first().text() + "]");
118             bufferedWriter.newLine();
119             bufferedWriter.flush();
120         } catch (IOException e)
121         {
122             e.printStackTrace();
123         }
124     }
125 
126     private static Document connect(String url)
127     {
128         if (url == null || url.isEmpty())
129         {
130             throw new IllegalArgumentException("The input url('" + url + "') is invalid!");
131         }
132         try
133         {
134             return Jsoup.connect(url).timeout(100 * 1000).get();
135         } catch (IOException e)
136         {
137             e.printStackTrace();
138             return null;
139         }
140     }
141 }
复制代码
数据爬取过程便是一个漫长的过程,只需要慢慢等待吧,呵呵,由于程序运行时间较长,请不要在控制台打印输出,否则可能会影响程序运行....

最终获取到数据的格式如下(”{}”中表示城市级别,”[]”中内容表示城市编码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
市辖区{3}[110100000000]
东城区{4}[110101000000]
东华门街道办事处{5}[110101001000]
多福巷社区居委会{6}[110101001001]
银闸社区居委会{6}[110101001002]
东厂社区居委会{6}[110101001005]
智德社区居委会{6}[110101001006]
南池子社区居委会{6}[110101001007]
黄图岗社区居委会{6}[110101001008]
灯市口社区居委会{6}[110101001009]
正义路社区居委会{6}[110101001010]
甘雨社区居委会{6}[110101001011]
台基厂社区居委会{6}[110101001013]
韶九社区居委会{6}[110101001014]
王府井社区居委会{6}[110101001015]
景山街道办事处{5}[110101002000]
隆福寺社区居委会{6}[110101002001]
吉祥社区居委会{6}[110101002002]
黄化门社区居委会{6}[110101002003]
钟鼓社区居委会{6}[110101002004]
魏家社区居委会{6}[110101002005]
汪芝麻社区居委会{6}[110101002006]
景山东街社区居委会{6}[110101002008]
皇城根北街社区居委会{6}[110101002009]
交道口街道办事处{5}[110101003000]
交东社区居委会{6}[110101003001]
福祥社区居委会{6}[110101003002]
大兴社区居委会{6}[110101003003]
府学社区居委会{6}[110101003005]
鼓楼苑社区居委会{6}[110101003007]
菊儿社区居委会{6}[110101003008]
南锣鼓巷社区居委会{6}[110101003009]
安定门街道办事处{5}[110101004000]
交北头条社区居委会{6}[110101004001]
北锣鼓巷社区居委会{6}[110101004002]
国子监社区居委会{6}[110101004003]
......

拿到以上数据以后,自己想干什么都可以自我去实现了,以上的代码可以直接运行,从数据源爬取后,可以直接转换成自己所要的格式。

后续处理的最终结果,请参见博文:http://www.cnblogs.com/liushaofeng89/p/4937714.html

 

如果你觉得本博文对你有所帮助,请记得点击右下方的”推荐”哦,么么哒…

转载请注明出处:http://www.cnblogs.com/liushaofeng89/p/4873086.html

Redis系列-存储篇set主要操作函数小结

最近,总是以“太忙“为借口,很久没有blog了,凡事贵在恒,希望我能够坚持不懈,毕竟在blog的时候,也能提升自己。废话不说了,直奔主题”set“

redis set 是string类型对象的无序集合,set不管存储多少对象,对存储对象的add,remove和test操作的时间复杂度是O(1)。set最多能包含 232 – 1 个member。

1、增加

语法:sadd key member[member…]

解释:对特定key的set增加一个或多个值,返回是增加元素的个数。注意:对同一个member多次add,set中只会保留一份。

  1. [root@xsf001 ~]# redis-cli
  2. redis 127.0.0.1:6379> sadd stu zhangsan lisi wangwu #新增
  3. (integer) 3
  4. redis 127.0.0.1:6379> smembers stu    #得到set的所有member
  5. 1) “wangwu”
  6. 2) “lisi”
  7. 3) “zhangsan”
  8. redis 127.0.0.1:6379> sadd stu zhangsan #增加存在的member
  9. (integer) 0
  10. redis 127.0.0.1:6379> smembers stu
  11. 1) “wangwu”
  12. 2) “lisi”
  13. 3) “zhangsan”
  14. redis 127.0.0.1:6379> sadd tech wangwu liming joe
  15. (integer) 3
  16. redis 127.0.0.1:6379> sadd tech jim
  17. (integer) 1
  18. redis 127.0.0.1:6379> smembers tech
  19. 1) “jim”
  20. 2) “liming”
  21. 3) “wangwu”
  22. 4) “joe”

2、查询

a)smembers

语法:smembers key

解释:获取set中的所有member

  1. redis 127.0.0.1:6379> smembers stu
  2. 1) “wangwu”
  3. 2) “lisi”
  4. 3) “zhangsan”
  5. redis 127.0.0.1:6379> smembers tech
  6. 1) “jim”
  7. 2) “liming”
  8. 3) “wangwu”
  9. 4) “joe”

b)sismember

语法:sismember key member

解释:判断值是否是set的member。如果值是set的member返回1,否则,返回0

  1. redis 127.0.0.1:6379> sismember tech jim #jim 是set的member
  2. (integer) 1
  3. redis 127.0.0.1:6379> sismember tech jim001 #jim001 不是set的member
  4. (integer) 0

c)scard

语法:scard key

解释:返回set的member个数,如果set不存在,返回0

  1. redis 127.0.0.1:6379> scard tech  # tech 存在
  2. (integer) 4
  3. redis 127.0.0.1:6379> scard stud #stud 不存在
  4. (integer) 0
  5. redis 127.0.0.1:6379> scard stu
  6. (integer) 4

d)srandmember

语法:srandmember key

解释:从set中返回一个随机member

  1. redis 127.0.0.1:6379> srandmember stu
  2. “zhangsan”
  3. redis 127.0.0.1:6379> srandmember stu
  4. “zhangsan”
  5. redis 127.0.0.1:6379> srandmember stu
  6. “wangwu”
  7. redis 127.0.0.1:6379> srandmember stu
  8. “zhangsan01”

3、删除

a)spop

语法:spop key

解释:移除并返回一个随机member

  1. redis 127.0.0.1:6379> smembers stu #pop前
  2. 1) “zhangsan01”
  3. 2) “wangwu”
  4. 3) “lisi”
  5. 4) “zhangsan”
  6. redis 127.0.0.1:6379> spop stu  #移除一个随机member
  7. “lisi”
  8. redis 127.0.0.1:6379> smembers stu #pop后
  9. 1) “zhangsan01″<span style=”white-space:pre”>   </span>
  10. 2) “wangwu”
  11. 3) “zhangsan”

b)srem

语法:srem key member [member …]

解释:移除一个或多个member

  1. redis 127.0.0.1:6379> smembers tech
  2. 1) “jim”
  3. 2) “liming”
  4. 3) “wangwu”
  5. 4) “joe”
  6. redis 127.0.0.1:6379> srem tech jim   #移除jim
  7. (integer) 1
  8. redis 127.0.0.1:6379> smembers tech
  9. 1) “liming”
  10. 2) “wangwu”
  11. 3) “joe”
  12. redis 127.0.0.1:6379> srem tech liming joe #移除多个member
  13. (integer) 2
  14. redis 127.0.0.1:6379> smembers tech
  15. 1) “wangwu”

c)smove

语法:smove source destination member

解释:将source中的member移动到destination

  1. redis 127.0.0.1:6379> smembers tech   #smove前
  2. 1) “wangwu”
  3. redis 127.0.0.1:6379> smembers stu
  4. 1) “zhangsan01”
  5. 2) “wangwu”
  6. 3) “zhangsan”
  7. redis 127.0.0.1:6379> smove stu tech zhangsan  #将zhangsan 从stu移动到tech
  8. (integer) 1
  9. redis 127.0.0.1:6379> smembers stu #smove后
  10. 1) “zhangsan01”
  11. 2) “wangwu”
  12. redis 127.0.0.1:6379> smembers tech
  13. 1) “wangwu”
  14. 2) “zhangsan”

4、其他

a)并集

语法:sunion key[key…]

解释:多个set的并集

  1. redis 127.0.0.1:6379> smembers stu
  2. 1) “zhangsan01”
  3. 2) “wangwu”
  4. redis 127.0.0.1:6379> sunion stu
  5. 1) “zhangsan01”
  6. 2) “wangwu”
  7. redis 127.0.0.1:6379> smembers tech
  8. 1) “wangwu”
  9. 2) “zhangsan”
  10. redis 127.0.0.1:6379> sunion stu tech
  11. 1) “zhangsan01”
  12. 2) “wangwu”
  13. 3) “zhangsan”

b)把并集结果存储到set

语法:sunionstore destination key [key …]

解释:求多个set并集,并把结果存储到destination

  1. redis 127.0.0.1:6379> sunionstore same stu tech #把stu tech并集结果存储在union
  2. (integer) 3
  3. redis 127.0.0.1:6379> smembers union
  4. 1) “zhangsan01”
  5. 2) “wangwu”
  6. 3) “zhangsan”

c)交集

语法:sinter key[key…]

解释:多个set的交集

  1. redis 127.0.0.1:6379> smembers stu
  2. 1) “zhangsan01”
  3. 2) “wangwu”
  4. redis 127.0.0.1:6379> smembers tech
  5. 1) “wangwu”
  6. 2) “zhangsan”
  7. redis 127.0.0.1:6379> sinter stu tech
  8. 1) “wangwu”

d)把交集结果存储到指定set

语法:sinterstore destination key [key …]

解释:把多个set的交集结果存储到destination

  1. redis 127.0.0.1:6379> sinterstore inter stu tech
  2. (integer) 1
  3. redis 127.0.0.1:6379> smembers inter
  4. 1) “wangwu”

e) set中在其他set中不存在member

语法:sdiff key[key …]

  1. redis 127.0.0.1:6379>
  2. redis 127.0.0.1:6379> smembers stu
  3. 1) “zhangsan01”
  4. 2) “wangwu”
  5. redis 127.0.0.1:6379> smembers tech
  6. 1) “wangwu”
  7. 2) “zhangsan”
  8. redis 127.0.0.1:6379> sdiff stu tech
  9. 1) “zhangsan01”
  10. redis 127.0.0.1:6379> sdiff tech stu
  11. 1) “zhangsan”

f)把set中在其他set中不存在的member存储到新的set

语法:sdiffstore key[key…]

  1. redis 127.0.0.1:6379> sdiffstore diff stu tech
  2. (integer) 1
  3. redis 127.0.0.1:6379> smembers diff
  4. 1) “zhangsan01”

主要参考:

http://redis.io/commands#set

http://redis.io/topics/data-types

工欲善其事,必先利其器

本文版权归翟士丹(Stan Zhai)博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。

原文地址:http://www.cnblogs.com/jasondan/p/it-memo.html

技术站点

Hacker News:非常棒的针对编程的链接聚合网站

Programming reddit:同上

MSDN:微软相关的官方技术集中地,主要是文档类

infoq:企业级应用,关注软件开发领域

OSChina:开源技术社区,开源方面做的不错哦

cnblogs,51cto,csdn:常见的技术社区,各有专长

stackoverflow:IT技术问答网站

GitHub:全球最大的源代码管理平台,很多知名开源项目都在上面,如Linux内核,

OpenStack等免费的it电子书:http://it-ebooks.info/

DevStore:开发者服务商店

不错的书籍

人件

人月神话

代码大全2

计算机程序设计艺术

程序员的自我修养

程序员修炼之道

高效能程序员的修炼(成为一名杰出的程序员其实跟写代码没有太大关系)

深入理解计算机系统

软件随想录

算法导论(麻省理工学院出版社)

离线数学及其应用

设计模式

编程之美

黑客与画家

编程珠玑

C++ Prime

Effective C++

TCP/IP详解

Unix 编程艺术

《精神分析引论》弗洛伊德

搞定:无压力工作的艺术

平台工具(都是开源的好东东哦)

Redmine/Trac:项目管理平台

Jenkins/Jira(非开源):持续集成系统(Apache Continuum,这个是Apache下的CI系统,还没来得及研究)

Sonar:代码质量管理平台

git,svn:源代码版本控制系统

GitLib/Gitorious:构建自己的GitHub服务器

gitbook:https://www.gitbook.io/写书的好东西,当然用来写文档也很不错的

Travis-ci:开源项目持续集成必备,和GitHub相结合,https://travis-ci.org/

开源测试工具、社区(Selenium、OpenQA.org)

Puppet:一个自动管理引擎,可以适用于Linux、Unix以及Windows平台。所谓配置管理系统,就是管理机器里面诸如文件、用户、进程、软件包这些资源。无论是管理1台,还是上万台机器Puppet都能轻松搞定。

Nagios:系统状态监控报警,还有个Icinga(完全兼容nagios所有的插件,工作原理,配置文件以及方法,几乎一模一样。配置简单,功能强大)

Ganglia:分布式监控系统

fleet:分布式init系统

爬虫相关(好玩的工具)

Phantomjs

berserkJS(基于Phantomjs的改进版本)

SlimerJS

CasperJS

selenium

Web服务器性能/压力测试工具/负载均衡器

http_load: 程序非常小,解压后也不到100K

webbench: 是Linux下的一个网站压力测试工具,最多可以模拟3万个并发连接去测试网站的负载能力

ab: ab是apache自带的一款功能强大的测试工具

Siege: 一款开源的压力测试工具,可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行。

squid(前端缓存),nginx(负载),nodejs(没错它也可以,自己写点代码就能实现高性能的负载均衡器):常用的负载均衡器

Piwik:开源网站访问量统计系统

ClickHeat:开源的网站点击情况热力图

HAProxy:高性能TCP /HTTP负载均衡器

ElasticSearch:搜索引擎基于Lucene

Page Speed SDK和YSLOW

HAR Viewer: HAR分析工具

protractor:E2E(end to end)自动化测试工具

Web前端相关

GRUNT: js task runner

Sea.js: js模块化

knockout.js:MVVM开发前台,绑定技术

Angular.js: 使用超动感HTML & JS开发WEB应用!

Highcharts.js,Flot:常用的Web图表插件

Raw:非常不错的一款高级数据可视化工具

Rickshaw:时序图标库,可用于构建实时图表

JavaScript InfoVis Toolkit:另一款Web数据可视化插件

Pdf.js,在html中展现pdf

ACE,CodeMirror:Html代码编辑器(ACE甚好啊)

NProcess:绚丽的加载进度条

impress.js:让你制作出令人眩目的内容展示效果(类似的还有reveal)

Threejs:3DWeb库

Hightopo:基于Html5的2D、3D可视化UI库

jQuery.dataTables.js:高度灵活的表格插件

Raphaël:js,canvas绘图库,后来发现百度指数的图形就是用它绘出来的

director.js:js路由模块,前端路由,Nodejs后端路由等,适合构造单页应用

pace.js:页面加载进度条

bower:Web包管理器

jsnice:有趣的js反编译工具,猜压缩后的变量名 http://www.jsnice.org/

D3.js: 是一个基于JavaScript数据展示库(类似的还有P5.js)

Zepto.js:移动端替代jQuery的东东,当然也可以使用jquery-mobile.

UI框架:Foundation,Boostrap,Pure,EasyUI,Polymer

前端UI设计师必去的几个网站:DribbbleawwwardsunmatchedstyleUIMaker

Mozilla 开发者中心:https://developer.mozilla.org/en-US/

图标资源:IcoMoon(我的最爱),Themify IconsFreePikGlyphiconsart

Dialog:非常漂亮的对话框

AdminLTE:github上的一个开源项目,基于Boostrap3的后台管理页面框架

Respond.js:让不懂爱的IE6-8支持响应式设计

require.js: js模块加载库

select2:比chosen具有更多特性的选择框替代库

AngularUI:集成angular.js的UI库

normalize.css: 采用了现代化标准让各浏览器渲染出的html保持一致的库

CreateJS:Html5游戏引擎Less,Compass:简化CSS开发

emojify.js:用于自动识别网页上的Emoji文字并将其显示为图像

simditor:一个不错的开源的html编辑器,简洁高效

Sencha:  基于html5的移动端开发框架

SuperScrollorama+TweenMax+skrollr:打造超酷的视差滚动效果网页动画

jquery-smooth-scroll:同上,平滑滚动插件

Animate.css:实现了各种动画效果的css库

Emmet:前端工程师必备,ZenCode的前身

MagicDraw:Uml图工具

大数据处理/数据分析/分布式工具

Hadoop:分布式的文件系统,结合其MapReduce编程模型可以用来做海量数据的批处理(Hive,Pig,HBase啥的就不说了),值得介绍的是Cloudera的Hadoop分支CDH5,基于YARN MRv2集成了Spark可直接用于生产环境的Hadoop,对于企业快速构建数据仓库非常有用。

Ceph:Linux分布式文件系统(特点:无中心)

Storm:实时流数据处理,可以看下IBM的一篇介绍 (还有个Yahoo的S4,也是做流数据处理的)

Spark:大规模流式数据处理(可以应付企业中常见的三种数据处理场景:复杂的批量数据处理(batch data processing);基于历史数据的交互式查询(interactive query);基于实时数据流的数据处理(streaming data processing)),CSND有篇文章介绍的不错

Spark Streaming:基于Spark的实时计算框架

Tachyon:分布式内存文件系统

Mesos:计算框架一个集群管理器,提供了有效的、跨分布式应用或框架的资源隔离和共享Impala:新一代开源大数据分析引擎,提供Sql语义,比Hive强在速度上

SNAPPY:快速的数据压缩系统,适用于Hadoop生态系统中

Kafka:高吞吐量的分布式消息队列系统

ActiveMQ:是Apache出品,最流行的,能力强劲的开源消息总线

MQTT:Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分

RabbitMQ:记得OpenStack就是用的这个东西吧

ZeroMQ:宣称是将分布式计算变得更简单,是个分布式消息队列,可以看下云风的一篇文章的介绍开源的日志收集系统:scribe、chukwa、kafka、flume。这有一篇对比文章

Zookeeper:可靠的分布式协调的开源项目

Databus:LinkedIn 实时低延迟数据抓取系统

数据源获取:Flume、Google Refine、Needlebase、ScraperWiki、BloomReach

序列化技术:JSON、BSON、Thrift、Avro、Google Protocol Buffers

NoSql:Apache Hadoop、Apache Casandra、MongoDB、Apache CouchDB、Redis、BigTable、HBase、Hypertable、Voldemort、Neo4j

MapReduce相关:Hive、Pig、Cascading、Cascalog、mrjob、Caffeine、S4、MapR、Acunu、Flume、Kafka、Azkaban、Oozie、Greenplum

数据处理:R、Yahoo! Pipes、Mechanical Turk、Solr/ Lucene、ElasticSearch、Datameer、Bigsheets、TinkerpopNLP自然语言处理:Natural Language Toolkit、Apache OpenNLP、Boilerpipe、OpenCalais

机器学习:WEKA、Mahout、scikits.learn、SkyTree

可视化技术:GraphViz、Processing、Protovis、Google Fusion Tables、Tableau、Highcharts、EChats(百度的还不错)、Raphaël.js

Kettle:开源的ETL工具

Pentaho:以工作流为核心的开源BI系统

Mondrian:开源的Rolap服务器

Oozie:开源hadoop的工作流调度引擎

开源的数据分析可视化工具:Weka、Orange、KNIME

Cobar:阿里巴巴的MySql分布式中间件

C & C++

Thrift:用来进行可扩展且跨语言的服务的开发(类似的还有个Avro,Google protobuf)。

libevent:是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机制。(对了还有个libev呢)

Boost:不多说了,准C++标准库

Ptmalloc\Valgrind\Purify

NetworkServer架构:acceptor->dispatcher->worker(这个不算工具哦)

breakpad:崩溃转储和分析模块,很多crashreport会用到

UI界面相关:MFC、BCG和QT这类的就不说了,高端一点的还有Html和DirectUI技术:libcef(基于chrome内核的,想想使用html5开发页面,还真有点小激动呢)、HtmlLayout、Duilib、Bolt,非C++的,还有node-webkit也不错,集成了node和webkit内核。

游戏开发相关

MINA:使用Java开发手游和页游服务器(对了还有Netty,也很猛的,都是基于NIO的)

HP-Socket:见有有些页游服务器使用这个构建的

云风的技术博客:http://blog.codingnow.com/

OGRE:大名鼎鼎的3D图形渲染引擎

OpenVDB:梦工厂C++的特效库,开源的

cocos2d:跨平台2D游戏引擎

unity3d:跨平台3D游戏引擎,很火的哦

Nodejs:也有不少使用它来开发手游和也有服务器(网易的Pomelo就是哦)

日志聚合,分布式日志收集

Scribe:Facebook的(nodejs + scribe + inotify 同步日志)

logstash:强大的日志收集系统,可以基于logstash+kibana+elasticsearch+redis开发强大的日志分析平台

log.io: nodejs开发的实时日志收集系统

RTP,实时传输协议与音视频

RTP,RTCP,RTSP-> librtp,JRTPLIB(遵循了RFC1889标准)

环形缓冲区,实时数据传输用

SDL,ffmpeg,live555,Speex

Red5:用Java开发开源的Flash流媒体服务器。它支持:把音频(MP3)和视频(FLV)转换成播放流; 录制客户端播放流(只支持FLV);共享对象;现场直播流发布;远程调用。

Python

Eric,Eclipse+pydev,比较不错的Python IDE

PyWin:Win32 api编程包

numpy:科学计算包,主要用来处理大型矩阵计算等,此外还有SciPy,Matplotlib

GUI相关:PyQt,PyQwt

supervisor:进程监控工具

Java相关

常用的IDE:IntelliJ IDEA,Eclipse,Netbeans

Web开发相关:Tomcat、Resin、Jetty、WebLogic等,常用的组件Struts,Spring

HibernateNetty: 异步事件驱动网络应用编程框架,用于高并发网络编程比较好(NIO框架)

MINA:简单地开发高性能和高可靠性的网络应用程序(也是个NIO框架),不少手游服务端是用它开发的

jOOQ:java Orm框架Activiti:工作流引擎,类似的还有jBPM、Snaker

Perfuse:是一个用户界面包用来把有结构与无结构数据以具有交互性的可视化图形展示出来.

Gephi:复杂网络分析软件, 其主要用于各种网络和复杂系统,动态和分层图的交互可视化与探测开源工具

Nutch:知名的爬虫项目,hadoop就是从这个项目中发展出来的

web-harvest:Web数据提取工具

POM工具:Maven+ArtifactoryNetflix

Curator:Netflix公司开源的一个Zookeeper client library,用于简化Zookeeper客户端编程

Akka:一款基于actor模型实现的 并发处理框架

EclEmma:覆盖测试工具

.net相关

Xilium.CefGlue:基于CEF框架的.NET封装,基于.NET开发Chrome内核浏览器

CefSharp:同上,有一款WebKit的封装,C#和Js交互会更简单

netz:免费的 .NET 可执行文件压缩工具

SmartAssembly:变态的.net代码优化混淆工具

NETDeob0:.net反混淆工具,真是魔高一尺道高一丈啊(还有个de4dot,在GitHub上,都是开源的)

ILMerge:将所有引用的DLL和exe文件打成一个exe文件

ILSpy:开源.net程序反编译工具

Javascript.NET:很不错的js执行引擎,对v8做了封装

NPOI: Excel操作

DotRAS:远程访问服务的模块

WinHtmlEditor: Winform下的html编辑器

SmartThreadPool:使用C#实现的,带高级特性的线程池

Snoop: WPF Spy Utility

Autofac: 轻量级IoC框架

HtmlAgilityPack:Html解析利器

Quartz.NET:Job调度

HttpLib:@CodePlex,简化http请求

SuperSocket:简化Socket操作,基于他的还有个SuperWebSocket,可以开发独立的WebSocket服务器了

DocX:未安装Office的情况下操作Word文件

Dapper:轻量级的ORM类,性能不错

HubbleDotNet:支持接入数据库的全文搜索系统

fastJSON:@CodeProject,高性能的json序列化类

ZXing.NET:@CodePlex,QR,条形码相关

Nancy:轻量级Http服务器,做个小型的Web应用可以摆脱IIS喽(Nancy.Viewengines.Razor,可以加入Razor引擎)

AntiXSS:微软的XSS防御库Microsoft Web Protection

LibraryJint:JavaScript解释器

CS-Script:将C#代码文件作为脚本执行

Jexus:Linux下 高性能、易用、免费的ASP.NET服务器

Clay:将dynamic发挥的更加灵活,像写js一样写C#

DynamicJSON:不必定义数据模型获取json数据

Antlr:开源的语法分析器(归到C#不太合适,其他语言也可以去用)

SharpPcap:C#版的WinPcap调用端,牛逼的网络包分析库(自带PacketNotNet用于包协议分析)

Roslyn:C#,VB编译器

ImageResizer: 服务端自由控制图片大小,真乃神器也,对手机端传小图,PC端传大图,CMS用它很方便

UI相关:DevExpress, Fluent(Office 07风格), mui(Modern UI for WPF)

NetSparkle:应用自动更新组件

ConfuserEx: 开源.net混淆工具

ServiceStack: 开源高性能Web服务框架,可用于构建高性能的REST服务Expression

Evaluator:Eval for C#,处理字符串表达式

http://nugetmusthaves.com/

常用工具

Fiddler:非常好用的Web前端调试工具,当然是针对底层http协议的,一般情况使用Chrome等自带的调试工具也足够了,特殊情况还得用它去处理

wireshark:知名的网络数据包分析工具

PowerCmd:替代Windows Cmd的利器

RegexBuddy:强大的正则表达式测试工具

Soure Insight:源代码阅读神器

SublimeText:程序员最爱的编辑器

Database.NET:一个通用的关系型数据库客户端,基于.NET 4.0开发的,做简单的处理还是蛮方便的

Navicat Premium:支持MySql、PostgreSQL、Oracle、Sqlite和SQL Server的客户端,通用性上不如Database.NET,但性能方面比Database.NET好很多,自带备份功能也用于数据库定时备份。

Synergy : 局域网内一套键盘鼠标控制多台电脑

DameWare:远程协助工具集(我在公司主要控制大屏幕用)

Radmin: 远程控制工具,用了一段时间的

DameWare,还要破解,对Win7支持的不好,还是发现这个好用

Listary:能极大幅度提高你 Windows 文件浏览与搜索速度效率的「超级神器」

Clover:给资源管理器加上多标签

WinLaunch:模拟Mac OS的Launch工具

Fritzing:绘制电路图

LICEcap:gif教程制作git,

svn:版本控制系统Enigma Virtual Box(将exe,dll等封装成一个可执行程序)

Open DBDiff(针对SqlServer)数据库同步

SymmetricDS:数据库同步

BIEE,Infomatica,SPSS,weka,R语言:数据分析

CodeSmith,LightSwitch:代码生成

Pandoc:Markdown转换工具,出书用的。以前玩过docbook,不过现在还是Markdown盛行啊。

Window Magnet[Mac]:增强Mac窗口管理功能,想Win7一样具有窗口拖放到屏幕边缘自动调整的功能

log explorer:查看SqlServer日志dependency

walker:查询Windows应用程序dll依赖项

Shairport4w:将iPhone,iPad,iPod上的音频通过AirPlay协议传输到PC上

ngrok:内网穿透工具Axure:快速原型制作工具,还有个在线作图的工具国内的一个创业团队做的,用着很不错 http://www.processon.com

tinyproxy:(Linux)小型的代理服务器支持http和https协议EaseUS Partition

Master:超级简单的分区调整工具,速度还是蛮快的,C盘不够用了就用它从D盘划点空间吧,不用重装系统这么折腾哦。

CheatEngine:玩游戏修改内存值必备神器(记得我在玩轩辕剑6的时候就用的它,超级方便呢)

ApkIDE:Android反编译神器翻、墙工具(自|由|门、天行浏览器)

设计工具:Sketch、OmniGraffle

MindManger:思维导图

未完待续……

[译]拳头公司聊天服务架构:服务器篇

原文:Chat Service Architecture Servers

译者:杰微刊-张帆

服务器,架构,开发

英雄联盟玩家每天总计发送数百万的信息。他们邀请朋友双排,在英雄选择界面和队伍沟通英雄选择,在战局结束时向所有人敲出”GG”(Good Game)以表感谢。今年的7月21号(我随机挑选了一天),玩家在游戏中建立了170万新的好友关系——满满都是爱!每次玩家发送消息,都会触发一系列后台的操作,而正是这些后台技术驱动了Riot的聊天系统。

在之前的文章《聊天服务架构:协议篇》中,我论述了我们所选用的客户端服务器间通信协议:XMPP(Extensible Messaging and Presence Protocol, 可扩展通讯和表示协议)。今天我会适当地就服务端和基础设施架构的相关机制进行深入探讨,同时,我也会论述到目前为了保证服务器的可扩展性和健壮性,我们所做的努力和已经完成的工作。像上一篇文章一样,我希望任何需要为分布式客户端库添加聊天特性的开发者会对本篇文章感兴趣。

服务器硬件

聊天系统的物理服务器应当具备这样的能力——确保服务对玩家来讲持续可用。这些服务器管理每个用户的聊天会话,同时能够保证必要的稳定性和应用安全验证隐私设置,如访问量流量限制、指标收集和日志记录等。
我们的聊天服务是按区域部署的(我们称每个区域为一个“分片”),这意味着每个英雄联盟区服拥有其自己的聊天集群,用于提供并且仅为该分片的玩家提供聊天功能。结果导致跨区服的玩家不能进行沟通交流,同时聊天服务器不能使用其他区域的数据。比如,北美服务器(NA)不能直接与西欧服务器(EU West)进行交流。
每个分片维护了一个由大量各种各样的物理服务器组成的聊天集群,这些服务器上运行着相同的服务器软件。每个区域的硬件规格参数都不尽相同,因为要考虑一系列的原因,如承载量需求、设备年限和硬件可用性——我们最新的服务器具备现代24核CPU、196GB的内存和固态硬盘(SSD),而较陈旧的服务器则使用24G内存和传统磁碟硬盘。
在聊天集群内,每个节点是完全独立和可替换的,这使得系统可以轻松的进行维护,从而提高了系统整体的容错性。一个集群中的节点数约为6~12台机器。尽管我们可以在每个区域运行更少的机器数量,但是为了给容错提供足够的空间和适应将来的增长需求,还是保证了足够的数量。如果月到升级需要关闭服务器,举例来讲,我们可以关闭单个集群中一半的节点数来确保不会中断为玩家提供的服务。
下面是一个简单的图表,展示了我们聊天服务的集群架构

服务器,架构,开发

在过去一段时间,我们遇到过大量由于硬件或者网络故障引起的意外事件,为了解决这类问题,我们不得不将个人服务器关闭几天,然而正是因为系统具备较好的容错机制,对玩家来讲,服务持续进行并没有中断。此外,我们还在客户端实现了这样一个逻辑——帮助已连接到关闭服务器的玩家自动重新获取他们的链接。
具体实现
拳头的聊天服务器主要是用Erlang(如果你对该语言感兴趣,可以点击查看这个视频https://www.youtube.com/watch?v=xrIjfIjssLE)写的,此外我们还用C来实现一些下层操作,如XML解析、SSL握手、字符串操作等。我们聊天服务的代码库,大约10%是C语言实现,90%是纯Erlang(忽略外部库和Erlang VM本身)。
在用C语言开发服务器组件之前,我们花了大量的时间分析和优化现有的Erlang代码库。我们尝试在使用全部现有工具的前提下,找出不同抽象等级下潜在的并发和效率瓶颈:
① 对于简单的调用计数,我们使用cprof,cprof框架极其简单,却能够具有验证我们是否在正确的时间进行了正确调用的能力。
② 为了进行更详细的分析,我们运行了fprof。不幸的是,fprof对测试服务器会产生较大的影响,所以不能在一个完整的负载测试期间使用——然而,它却能够使我们更详细的了解到如何和何时进行调用。其中包括单个函数在它执行期间花费的时间(own time,独占时间),包含调用函数所花费时间在内的全部时间(accumulated time,累计时间)和过程结果的分组。所以这些帮助我们找出系统执行过程中的CPU密集区域。
③ 当检测并发瓶颈时,perceptlcnt十分友好。这些工具帮我们尽可能的识别所有并行化数据处理的时机,以便是我们可以利用所有的可用核心。
④ 在操作系统层面,我们采用了传统分析工具,比如mpstat,vmstat,iostat,perf,还有一些/proc的文件系统文件。
我们发现,大部分珍贵的CPU执行周期和内存分配发生在处理文本和数据流时。由于Erlang的terms被定义为不可修改,不管我们如何优化代码,执行任何字符串密集的排序操作将十分“昂贵”。所以,我们使用C来重写最笨重的字符串操作的系统部件,同时,使用HiPE来编译部分标准库——这使得CPU占有率性能提高了60%以上。此外,我们发现,对每个玩家的会话处理,内存分配由150kb降低至25kb。
我们尝试遵从“Erlang/OTP设计原则和最佳实践”(best practices and Erlang/OTP design principles),所以拳头的聊天服务器形成了一个全关联的集群,即集群中的每两个聊天服务器之间由Erlang VM维护单独的一个持久TCP连接。服务器使用这些连接来传达Erlang分布式协议,进行内部交流。至此,无论如何详尽的测试,我们再次交流模型上再也找不到任何瓶颈。

服务器,架构,开发

前面我提到过,集群中的每个服务器都是一个完全独立的单元——这使得每个服务器在任何时间都可以独立操作,独立运行,独立提供服务。为了提供充分的可扩展性/伸缩性,我们将系统设计成节点间共享尽可能少的数据。因此,聊天集群中的服务器仅共享一小部分内部键值表,这部分表完全复制在内存中,通过一个高度优化的Erlang分布式数据库管理系统进行通讯,这个系统被称作Mnesia(如果你想学习更多有关Mnesia的内容,我强烈建议你去看一下erlang.org上的材料和这个网站learnyousomeerlang.com)。这些表将玩家或者组聊天识别符(在XMPP中被称作JIDs)与Erlang处理器进程进行映射,后者维护了一些上下文特定的数据。
对单独的玩家聊天,这些数据中可能会包括链接套接字(connection socket)、好友列表和限速器;对于群组对话、这些数据则包括房间名册和聊天记录。每次在玩家或者群组对话间有需要路由任何类型的信息时,服务器会问询这些键值表以通过JIDs来处理他们的会话句柄。因为每个服务器持有一份session表的完全拷贝,所以所有的都操作都是在本地发起,减少了整体的路由延迟。

服务器,架构,开发

上述做法的负面影响就是每个应用于这些表的更新必须被复制到其他所有已连接的聊天服务器中。据我们所知,这些表是唯一可能会阻止我们顺利线性扩展聊天集群的因素——然而,负载测试显示我们可以将聊天集群增加至30台服务器而没有任何性能损耗。
为了保证玩家的会话数据或者群组聊天数据在网络切割或者服务器故障时保持同步,每个聊天服务器运行订阅了关于集群拓扑变化的VM内部通知Erlang进程。当任何事件产生,比如聊天服务器宕机,其他节点将从它们的本地表中移除脱机节点上正在运行的所有会话条目。一旦节点恢复上线(通常是在重启或等待网络连接恢复之后),该节点会向其他集群成员推送自己的本地状态,并且下载其他节点的状态。通过这种模式,聊天服务器对其自己的数据而言扮演了绝对权威的角色,对其他聊天服务器的数据,完全信任。
为了解释这个设计,让我们一起考虑下面的示例:
① 我们有一个三台聊天服务器组成的集群:节点A(玩家Alice和Bobby连接到该节点)、节点B(玩家Charlie连接到该节点)和节点C(玩家Dan和Eve连接到该节点)。所有的玩家可以立刻进行交流:

服务器,架构,开发

② 网络故障导致节点A与集群中的其他节点脱离,下次其他机器尝试与A进行通讯时,会遇到一个关闭的TCP套接字并产生一个由信号群集拓扑改变引发的事件。这是网络断裂的一个十分典型的案例。在这个案例中,Alice和Bob就像被隔离到单独的岛屿上,不能与Charlie、Dan和Eve进行通讯,然而Charlie、Dan和Eve之间仍可正常通讯:

服务器,架构,开发

③ 现在,节点B和C不能访问单独一侧的节点A,当Erlang VM将集群拓扑变更事件传递给订阅处理程序,节点B和C会舍弃节点A上的玩家会话引用。尽管Alice和Bob仍可访问服务并相互之间可以聊天,但是他们不能与Charlie、Dan和Eve进行沟通。【译者注:原文中的下图中所示,节点A其实也把Charlie,Dan和Eve的会话引用舍弃掉了,文中并没有指明】

服务器,架构,开发

④ 网络连接恢复后,节点A重新与节点B和C之间建立TCP链接。不幸的是,连接建立后,节点A还不知道节点B和C,所以Alice和Bob仍然不能与Charlie、Dan和Eve沟通:

服务器,架构,开发

⑤ 最后,Erlang VM再次发起了集群拓扑变更事件,这次发出的通知是服务器加入。节点A下载节点B和节点C的会话数据到A本地Mnesia表,节点B和C同理将节点A的会话数据下载到本地。现在集群再一次复制完毕,可以允许Aclice、Bob、Charlie、Dan和Eve之间所有的通讯:

服务器,架构,开发

这个设计可以使我们构建健壮可自修复的基础架构,不再需要在连接失败的情况下立即手动干预。所以,服务可以自动从网络异常中恢复,并对玩家恢复其功能。
数据流
基于上面谈到的服务器实现,让我们来审查下提供给玩家聊天的软件过程。无论任何时候,当玩家客户端链接到聊天服务器时,客户端便向其公共的XMPP终端开放了一个持久的、加密的(采用AES256-SHA加密算法)TCP链接。集群负载均衡器会在后端选择聊天服务器中的一个,并且将该玩家的会话指定分配到这个服务器。目前,我们使用一个正则循环负载平衡策略以在所有可用的服务器之间分发负载均匀。
一旦链接建立,聊天服务器会创建一个新的专用的Erlang进程来处理该玩家的会话。这个进程(又被称作c2s,即client to server, 客户端到服务端)维护了玩家的TCP套接字、XML解析器实例、好友列表和黑名单、最后状态数据、召唤师名称、速率限制配置和其他重要的服务器所使用的用于提供玩家体验的详细信息。在链接中,我们的系统会立即要求身份验证,校验该玩家身份。在这里,我们试用了兼容第三方客户端的XMPP标准身份验证机制
为了进一步说明,我们还是举Alice和Bob的例子。二位都是青铜2的玩家,期待有一天可以打上王者段位。为此,Alice和Bob天天一起训练,在召唤师峡谷磨练他们的技术。
Alice想给Bob发信息,邀请Bob一起双排去打晋级青铜1的晋级赛。下面是一旦Alice通过验证链接到聊天服务器后台的一系列操作:
① Alice的游戏客户端通过其c2s进程维护的加密的TCP链接发送了一条XMPP信息(包含这样一条信息“要来一起打晋级赛生死局吗”)到聊天服务器
② Alice的c2s进程收到信息后进行解密喝转换成XML
③ 转换之后,该进程在这条消息上进行了几个验证,包括速率限制遵从性、防欺骗验证、黑名单和好友列表成员测试
④ 通过验证之后,Alice的c2s在其内部的映射表中查找Bob的会话句柄,并验证是否可达
⑤ 如果Bob当前不在线,则信息会被持久化到数据存储中,下次Bob登陆游戏之后再次发送他。根据分片的不同,数据库选用MySQL(对于老式环境)或者Riak(对于新的分片)。数据存储将是我下一片讨论聊天服务架构文章的主题
⑥ 如果Bob当前在线,则服务器将使用标准Erlang信息传递机制将信息发送至他的c2s进程
⑦ 当Bob的c2s进程接收到信息,将会像前面那样进行一系列校验
⑧ 校验结束,进程将将消息序列化为XML,然后发送至Bob的TCP套接字
⑨ 最终,Bob的游戏客户端可以接收到信息,并展示在合适的信息窗口
显而易见,一旦收到信息,Bob会很乐意加入Alice,两人将carry他们的团队,很快就可以晋级青铜1了。
内部接口
除了直接提供玩家聊天特性,Riot的聊天服务器还公开了几个私有的内部消费相关的REST接口。一贯地,服务使用这些REST端点来访问社交图谱(由玩家之间的好友关系确定)
例如:
1) 对于内容馈赠,如英雄皮肤,在游戏内部的商店背后,由服务器验证他们的好友关系是否足够“成熟”——这避免了威胁账号的恶意赠送
2) 建立战队的功能使用了LoL(League of Legends简写)的社交图谱,用来构建一个建议好友列表供玩家在建立战队时选择邀请的好友
3)  对于联盟相关的服务,系统会在后端查询好友列表用来决定哪些是新手玩家,偏向于将这些玩家与其好友放置在一方——即匹配时,更有可能与自己的好友在一个联盟中。
这里拿一个真实的例子,考虑一下请求ID13131召唤师的好友列表:

“`
GET /friends/summoner/13131 HTTP/1.1 … < HTTP/1.1 200 OK < server: Cowboy < connection: keep-alive [ { “ask”: “none”, “askmessage”: “”, “createdat”: “2015-06-30 10:52:26”, “group”: “Work”, “nick”: “Riot Teemo”, “note”: “top laner!”, “subscription”: “both”, “summonerid”: 112233 }, { “ask”: “none”, “askmessage”: “”, “createdat”: “2015-06-25 11:25:07”, “group”: “Family”, “nick”: “Pentakill Morg”, “note”: “Mom”, “subscription”: “both”, “summonerid”: 223344 }, { “ask”: “none”, “askmessage”: “”, “createdat”: “2015-06-17 17:57:17”, “group”: “Work”, “nick”: “Jax Jax Jax”, “note”: “plays only Jax?”, “subscription”: “both”, “summoner_id”: 334455 }, … ] “`

为了支持这些请求,聊天服务器运行了Cowboy,一个用于处理其他内部服务传来的HTTP请求的嵌入式Web服务器。为了方便集成,每个后端通过Swagger返回JSON对象来填充请求。
尽管当前,我们使用了一个集中的内部均衡器来分发这些请求,在将来,我们更倾向于使用自动发现模型和客户端的负载均衡机制。服务可进行自配置可以使我们在不用进行网络的重新配置的前提下便可以动态的重新部署集群大小,同时还可以十分简单的建立新的分区(对内提供测试或者对外提供玩家使用)。
结语
在单独的一天之中,Riot的聊天服务器通常会路由转发大约十亿的事件(如在线状态、信息等),并且处理上百万的REST查询。尽管现在系统并不完美,基础设施的健壮性和可扩展性还是能够保持聊天服务对玩家持续可用——今年,系统由于时不时出现的硬件问题常需要关闭某一个独立的服务,但是自修复的特性为Riot的所有区服提供了99.999%的运行时间。尽管我在这里仅仅探讨了很表层的内容,但是我还是希望能对于如何在服务器端增加了聊天功能进行一个抛砖引玉。
如有任何问题或者评论,欢迎留言,我很期待大家的留言。这个系列的下一篇文章,也是最后一篇文章,将会着重关注Riot聊天服务的数据库部分,届时见~

——————-好久不见的分割线——————-

如果您发现这篇译文的任何问题,可随时与杰微刊联系。

我们水平有限,但理想高远。杰微刊旨在分享优质的内容。

杰微刊也同样期待理想的您对这个世界的贡献。欢迎任何目的的联系。

杰微刊的有偿投稿邮箱是:weikan@jointforce.com

我们的QQ是:3272840549。

[转载请保留原文出处、译者和审校者。 可以不保留我们的链接]

iOS开发中@property的属性weak nonatomic strong readonly等介绍

@property与@synthesize是成对出现的,可以自动生成某个类成员变量的存取方法。在Xcode4.5以及以后的版本,@synthesize可以省略。

1.atomic与nonatomic
atomic:默认是有该属性的,这个属性是为了保证程序在多线程情况,编译器会自动生成一些互斥加锁代码,避免该变量的读写不同步问题。
nonatomic:如果该对象无需考虑多线程的情况,请加入这个属性,这样会让编译器少生成一些互斥加锁代码,可以提高效率。

2.readwrite与readonly
readwrite:这个属性是默认的情况,会自动为你生成存取器。
readonly:只生成getter不会有setter方法。
readwritereadonly这两个属性的真正价值,不是提供成员变量访问接口,而是控制成员变量的访问权限。

3.strong与weak
strong:强引用,也是我们通常说的引用,其存亡直接决定了所指向对象的存亡。如果不存在指向一个对象的引用,并且此对象不再显示在列表中,则此对象会被从内存中释放。
weak:弱引用,不决定对象的存亡。即使一个对象被持有无数个弱引用,只要没有强引用指向它,那么还是会被清除。
strongretain功能相似;weakassign相似,只是当对象消失后weak会自动把指针变为nil;

4.assign、copy、retain
assign:默认类型,setter方法直接赋值,不进行任何retain操作,不改变引用计数。一般用来处理基本数据类型。
retain:释放旧的对象(release),将旧对象的值赋给新对象,再令新对象引用计数为1。我理解为指针的拷贝,拷贝一份原来的指针,释放原来指针指向的对象的内容,再令指针指向新的对象内容。
copy:与retain处理流程一样,先对旧值release,再copy出新的对象,retainCount为1.为了减少对上下文的依赖而引入的机 制。我理解为内容的拷贝,向内存申请一块空间,把原来的对象内容赋给它,令其引用计数为1。对copy属性要特别注意:被定义有copy属性的对象必须要 符合NSCopying协议,必须实现- (id)copyWithZone:(NSZone *)zone方法。
也可以直接使用:
使用assign: 对基础数据类型 (NSInteger,CGFloat)和C数据类型(int, float, double, char, 等等)
使用copy: 对NSString
使用retain: 对其他NSObject和其子类

5.getter setter
getter:是用来指定get方法的方法名
setter:是用来指定set访求的方法名
在@property的属性中,如果这个属性是一个BOOL值,通常我们可以用getter来定义一个自己喜欢的名字,例如:
@property (nonatomic, assign, getter=isValue) boolean value;
@property (nonatomic, assign, setter=setIsValue) boolean value;


一,retain, copy, assign区别

1.
假设你用malloc分配了一块内存,并且把它的地址赋值给了指针a,后来你希望指针b也共享这块内存,于是你又把a赋值给(assign)了b。此时a
和b指向同一块内存,请问当a不再需要这块内存,能否直接释放它?答案是否定的,因为a并不知道b是否还在使用这块内存,如果a释放了,那么b在使用这块
内存的时候会引起程序crash掉。

2.
了解到1中assign的问题,那么如何解决?最简单的一个方法就是使用引用计数(reference
counting),还是上面的那个例子,我们给那块内存设一个引用计数,当内存被分配并且赋值给a时,引用计数是1。当把a赋值给b时引用计数增加到
2。这时如果a不再使用这块内存,它只需要把引用计数减1,表明自己不再拥有这块内存。b不再使用这块内存时也把引用计数减1。当引用计数变为0的时候,
代表该内存不再被任何指针所引用,系统可以把它直接释放掉。

3. 上面两点其实就是assign和retain的区别,assign就是直接赋值,从而可能引起1中的问题,当数据为int, float等原生类型时,可以使用assign。retain就如2中所述,使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收。

4. copy是在你不希望a和b共享一块内存时会使用到。a和b各自有自己的内存。

5. atomic和nonatomic用来决定编译器生成的getter和setter是否为原子操作。在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数会变成下面这样:

if (property != newValue) {
[property release];
property = [newValue retain];
}

二,深入理解一下(包括autorelease)1. retain之后count加一。alloc之后count就是1,release就会调用dealloc销毁这个对象。
如果 retain,需要release两次。通常在method中把参数赋给成员变量时需要retain。
例如:
ClassA有 setName这个方法:
-(void)setName:(ClassName *) inputName
{
name = inputName;
[name retain]; //此处retian,等同于[inputName retain],count等于2
}
调用时:
ClassName *myName = [[ClassName alloc] init];
[classA setName:myName]; //retain count == 2
[myName release]; //retain count==1,在ClassA的dealloc中release name才能真正释放内存。

2. autorelease 更加tricky,而且很容易被它的名字迷惑。我在这里要强调一下:autorelease不是garbage collection,完全不同于Java或者.Net中的GC。
autorelease和作用域没有任何关系!
autorelease 原理:
a.先建立一个autorelease pool
b.对象从这个autorelease pool里面生成。
c.对象生成 之后调用autorelease函数,这个函数的作用仅仅是在autorelease pool中做个标记,让pool记得将来release一下这个对象。
d.程序结束时,pool本身也需要rerlease, 此时pool会把每一个标记为autorelease的对象release一次。如果某个对象此时retain count大于1,这个对象还是没有被销毁。
上面这个例子应该这样写:
ClassName *myName = [[[ClassName alloc] init] autorelease];//标记为autorelease
[classA setName:myName]; //retain count == 2
[myName release]; //retain count==1,注意,在ClassA的dealloc中不能release name,否则release pool时会release这个retain count为0的对象,这是不对的。

记住一点:如果这个对象是你alloc或者new出来的,你就需要调用release。如果使用autorelease,那么仅在发生过retain的时候release一次(让retain count始终为1)。

3 xcode 中的新标记 strong weak
strong 用来修饰强引用的属性;对应以前retain
weak 用来修饰弱引用的属性;对应以前的assign