门户级UGC系统的技术进化路线——新浪新闻评论系统的架构演进和经验总结

评论系统,或者称为跟帖、留言板,是所有门户网站的核心标准服务组件之一。与论坛、博客等其他互联网UGC系统相比,评论系统虽然从产品功能角度衡量相对简单,但因为需要能够在突发热点新闻事件时,在没有任何预警和准备的前提下支撑住短短几分钟内上百倍甚至更高的访问量暴涨,而评论系统既无法像静态新闻内容业务那样通过CDN和反向代理等中间缓存手段化解冲击,也不可能在平时储备大量冗余设备应对突发新闻,所以如何在有限的设备资源条件下提升系统的抗压性和伸缩性,也是对一个貌似简单的UGC系统的不小考验。

新闻评论系统的起源

新浪网很早就在新闻中提供了评论功能,最开始是使用Perl语言开发的简单脚本,目前能找到的最早具备评论功能的新闻是2000年4月7日的,经过多次系统升级,2014年前的评论地址已经失效了,但数据仍保存在数据库中。直到今天,评论仍是国内所有新闻网站的标配功能。

评论系统3.0

2003年左右,我接手负责评论系统,系统版本为3.0。当时的评论系统运行在单机环境,一台x86版本Solaris系统的Dell 6300服务器提供了全部服务,包括MySQL和Apache,以及所有前后台CGI程序,使用C++开发。

图1  3.0系统流程和架构

3.0系统的缓存模块设计得比较巧妙,以显示页面为单位缓存数据,因为评论页面依照提交时间降序排列,每新增一条评论,所有帖子都需要向下移动一位,所以缓存格式设计为每两页数据一个文件,前后相邻的两个文件有一页数据重复,最新的缓存文件通常情况下不满两页数据。

图2  页面缓存算法示意图

图2是假设评论总数95条,每页显示20条时的页面缓存结构,此时用户看到的第一页数据读取自“缓存页4”的95~76,第二页数据读取自“缓存页3”的75~56,以此类推。

这样发帖动作对应的缓存更新可简化为一次文件追加写操作,效率最高。而且可保证任意评论总量和显示顺序下的翻页动作,都可在一个缓存文件中读到所需的全部数据,而不需要跨页读取再合并。缺点是更新评论状态时(如删除),需要清空自被删除帖子开始的所有后续缓存文件。缓存模块采取主动+被动更新模式,发帖为主动,每次发帖后触发一次页面缓存追加写操作。更新评论状态为被动,所涉及缓存页面文件会被清空,直到下一次用户读取页面缓存时再连接数据库完成查询,然后更新页面缓存,以备下次读取。这个针对发帖优化的页面缓存算法继续沿用到了后续版本的评论系统中。

此时的评论系统就已具备了将同一专题事件下所有新闻评论汇总显示的能力,在很长一段时间内这都是新浪评论系统的独有功能。

虽然3.0系统基本满足了当时的产品需求,但毕竟是单机系统,热点新闻时瞬间涌来的大量发帖和读取操作,经常会压垮这台当时已属高配的4U服务器,频繁显示资源耗尽的错误页面。我接手后的首要任务就是尽量在最短时间内最大限度降低系统的宕机频率,通过观察分析确定主要性能瓶颈在数据库层面。

3.0系统中,每个新闻频道的全部评论数据都保存在一张MyISAM表中,部分频道的数据量已经超过百万,在当时已属海量规模,而且只有一个数据库实例,读写竞争非常严重。一旦有评论状态更新,就会导致很多缓存页面失效,瞬间引发大量数据库查询,进一步加剧了读写竞争。当所有CGI进程都阻塞在数据库环节无法退出时,殃及Apache,进而导致系统Load值急剧上升无法响应任何操作,只有重启才能恢复。

解决方案是增加了一台FreeBSD系统的低配服务器用于数据库分流,当时MySQL的版本是3.23,Replication主从同步还未发布,采取的办法是每天给数据表减肥,把超过一周的评论数据搬到2号服务器上,保证主服务器的评论表数据量维持在合理范围,在这样的临时方案下,3.0系统又撑了几个月。

现在看来,在相当简陋的系统架构下,新浪评论系统3.0与中国互联网产业的门户时代一起经历了南海撞机、911劫机、非典、孙志刚等新闻事件。

评论系统4.0启动

2004年左右,运行了近三年的3.0系统已无法支撑新浪新闻流量的持续上涨,技术部门启动了4.0计划,核心需求就是三个字:不宕机。

因为当时我还负责了新浪聊天系统的工作,不得不分身应对新旧系统的开发维护和其他项目任务,所以在现有评论系统线上服务不能中断的前提下,制定了数据库结构不变,历史数据全部保留,双系统逐步无缝切换,升级期间新旧系统并存的大方针。

第一阶段:文件系统代替数据库,基于ICE的分布式系统

既然3.0系统数据库结构不可变,除了把数据库升级到MySQL 4.0启用Repliaction分解读写压力以外,最开始的设计重点是如何把数据库与用户行为隔离开。

解决方案是在MySQL数据库和页面缓存模块之间,新建一个带索引的数据文件层,每条新闻的所有评论都单独保存在一个索引文件和一个数据文件中,期望通过把对数据库单一表文件的读写操作,分解为文件系统上互不干涉可并发执行的读写操作,来提高系统并发处理能力。在新的索引数据模块中,查询评论总数、追加评论、更新评论状态都是针对性优化过的高效率操作。从这时起,MySQL数据库就降到了只提供归档备份和内部管理查询的角色,不再直接承载任何用户更新和查询请求了。

同时引入了数据库更新队列来缓解数据库并发写操作的压力,因为当时消息队列中间件远不如现在百花齐放,自行实现了一个简单的文件方式消息队列模块,逐步应用到4.0系统各个模块间异步通信场合中。

图3  4.0系统流程

选用了ICE作为RPC组件,用于所有的模块间调用和网络通信,这大概是刚设计4.0系统时唯一没做错的选择,在整个4.0系统项目生命周期,ICE的稳定性和性能表现从未成为过问题。

图4  4.0索引缓存结构

4.0系统开发语言仍为C++,因为同时选用了MySQL 4.0、ICE、Linux系统和新文件系统等多项应用经验不足的新技术,也为后来的系统表现动荡埋下了伏笔(新浪到2005年左右才逐步从FreeBSD和Solaris迁移到了CentOS系统)。

图5  4.0系统架构

此时的4.0评论系统已从双机互备扩容到五机集群,进入小范围试用阶段,虽然扛过了刘翔第一次夺金时创纪录的发帖高峰,但倒在了2004年亚洲杯中国队1 : 3败于日本队的那个夜晚。

当时系统在进入宕机之前的最高发帖速度大约是每分钟千帖量级,在十年前还算得上是业界同类系统的峰值,最终确认问题出在文件系统的I/O负载上。

设计索引缓存模块时的设想过于理想化,虽然把单一数据表的读写操作分解到了文件系统的多个文件上,但不可避免地带来了对机械磁盘的大量随机读写操作,在CentOS默认的Ext3文件系统上,每条新闻对应两个文件的设计(2004年新浪新闻总量为千万左右),虽然已采取了128×256的两层目录HASH来预防单目录下文件过多隐患,但刚上线时还表现良好的系统,稍过几个月后就把文件系统彻底拖垮了。

既然Ext3无法应对大数量文件的频繁随机读写,当时我们还可以选择使用B*树数据结构专为海量文件优化的ReiserFS文件系统,在与系统部同事配合反复对比测试,解决了ReiserFS与特定Linux Kernel版本搭配时的kswapd进程大量消耗CPU资源的问题后,终于选定了可以正常工作的Kernel和ReiserFS对应版本,当然这也埋下了ReiserFS作者杀妻入狱后新装的CentOS服务器找不到可用的ReiserFS安装包这个大隐患。

第二阶段:全系统异步化,索引分页算法优化

直到这个阶段,新浪评论系统的前端页面仍是传统的Apache+CGI模式,随着剩余频道的逐步切换,新浪评论系统升级为静态HTML页面使用XMLHTTP组件异步加载XML数据的AJAX模式,当时跨域限制更少的JSON还未流行。升级为当时刚刚开始流行的AJAX模式并不是盲目追新,而是为了实现一个非常重要的目标:缓存被动更新的异步化。

随着消息队列的普遍应用,4.0系统中所有的数据库写操作和缓存主动更新(即后台程序逻辑触发的更新)都异步化了,当时已在实践中证明,系统访问量大幅波动时,模块间异步化通信是解决系统伸缩性和保证系统响应性的唯一途径。但在CGI页面模式下,由用户动作触发的缓存被动更新,只能阻塞在等待状态,直到查询数据和更新缓存完成后才能返回,会导致前端服务器Apache CGI进程的堆积。

使用AJAX模式异步加载数据,可在几乎不影响用户体验的前提下完成等待和循环重试动作,接收缓存更新请求的支持优先级的消息队列还可合并对同一页面的重复请求,也隔离了用户行为对前端服务器的直接冲击,极大提高了前端服务器的伸缩性和适应能力,甚至连低硬件配置的客户端电脑在AJAX模式加载数据时都明显更顺畅了。前端页面静态化还可将全部数据组装和渲染逻辑,包括分页计算都转移到了客户端浏览器上,充分借用用户端资源,唯一的缺点是对SEO不友好。

通过以上各项措施,此时的4.0系统抗冲击能力已有明显改善,但是接下来出现了新的问题。在3.0系统时代,上万条评论的新闻已属少见,随着业务的增长,类似2005年超女专题或者体育频道NBA专题这样千万评论数级别的巨无霸留言板开始出现。

为了提高分页操作时定位和读取索引的效率,4.0系统的算法是先通过mmap操作把一个评论的索引文件加载到内存,然后按照评论状态(通过或者删除)和评论时间进行快速排序,筛选出通过状态的帖子并按时间降序排列,这样读取任意一页的索引数据,都是内存中一次常量时间成本的偏移量定位和读取操作。几百条或者几千条评论时,上述方案运作得很好,但在千万留言数量的索引文件上进行全量排序,占用大量内存和CPU资源,严重影响系统性能。我们曾尝试改用BerkeleyDB的Btree模式来存储评论索引,但性能不升反降。

为避免大数据量排序操作的成本,只能改为简单遍历方式,从头开始依次读取,直到获取所需的数据。虽可通过从索引文件的两端分别作为起点,来提升较新和较早页面的定位效率,但遍历读取本身就是一个随着请求页数增大越来越慢的线性算法,并且随着4.0系统滑动翻页功能的上线,原本用户无法轻易访问到的中间页面数据也开始被频繁请求,因此最终改为了两端精确分页,中间模糊分页的方式。模糊分页就是根据评论帖子的通过比例,假设可显示帖子均匀分布,一步跳到估算的索引偏移位置。毕竟在数十万甚至上百万页的评论里,精确计算分页偏移量没有太大实际意义。

图6  异步缓存更新流程

2005年非常受关注的日本申请加入联合国常任理事国事件,引发了各家网站的民意沸腾,新浪推出了征集反日入常签名活动并在短短几天内征集到2000多万签名。因为没有预计到会有如此多的网民参与,最开始简单实现的PHP+MySQL系统在很短时间内就无法响应了,然后基于4.0评论系统紧急加班开发了一个签名请愿功能,系统表现稳定。

评论系统4.0第三阶段:简化缓存策略,进一步降低文件系统I/O

到了这个阶段,硬件资源进一步扩容,评论系统的服务器数量终于达到了两位数,4.0系统已实现了当初的“不宕机”设计目标,随着网站的改版,所有新闻页面(包括网站首页)都开始实时加载和显示最新的评论数量和最新的帖子列表,此时4.0系统承受的Hits量级已接近新浪新闻静态池的水平。从这时起,新浪评论系统再没有因为流量压力宕机或者暂停服务过。

前面提到,新装的CentOS系统很难找到足够新版本的ReiserFS安装包,甚至不得不降级系统版本,一直困扰性能表现的文件系统也接近了优化的极限,这时候Memcached出现了。

图7  系统架构

2006年左右Memcached取代了4.0系统中索引缓存模块的实体数据部分(主要是评论正文),索引缓存模块在文件系统上只存储索引数据,评论文本都改用Memcached存储,极大降低了文件系统的I/O压力。因为系统流量与热点事件的时间相关性,仅保存最近几周的评论就足以保证系统性能,极少量过期数据访问即使穿透到MySQL也问题不大,当然服务器宕机重启和新装服务器上线时要非常留意数据的加载预热。

之后4.0系统进入稳定状态,小修小补,又坚持服役了若干年,并逐步拓展到股票社区、签名活动、三方辩论、专家答疑、观点投票等产品线,直到2010年之后5.0系统的上线。

2008年5月12日,我发现很多网友在地震新闻评论中询问亲友信息,就立即开发了基于评论系统的地震寻亲功能并于当晚上线。大约一周后为了配合Google发起的寻亲数据汇总项目,还专门为Google爬虫提供了非异步加载模式的数据页面以方便其抓取。

2004年上线的4.0系统,2010~2011年后被5.0系统取代逐步下线,从上线到下线期间系统处理的用户提交数据量变化趋势如图8所示。

图8  系统流量变化图

高访问量UGC系统设计总结

纵观整个4.0系统的设计和优化过程,在硬件资源有限的约束下,依靠过渡设计的多层缓冲,完成了流量剧烈波动时保障服务稳定的最基本目标,但也确实影响到了UGC系统最重要的数据更新实时性指标,数据更新的实时性也是之后5.0系统的重点改进方向。

总结下来,一般UGC系统的设计方针就是通过降低系统次要环节的实时一致性,在合理的成本范围内,尽量提高系统响应性能,而提高响应性能的手段归根结底就是三板斧:队列(Queue)、缓存(Cache)和分区(Sharding)。

  • 队列:可以缓解并发写操作的压力,提高系统伸缩性,同时也是异步化系统的最常见实现手段。
  • 缓存:从文件系统到数据库再到内存的各级缓存模块,解决了数据就近读取的需求。
  • 分区:保证了系统规模扩张和长期数据积累时,频繁操作的数据集规模在合理范围。

关于数据库,区分冷热数据,按照读写操作规律合理拆分存储,一般UGC系统近期数据才是热点,历史数据是冷数据。

  • 区分索引和实体数据,索引数据是Key,易变,一般用于筛选和定位,要保证充分的拆分存储,极端情况下要把关系数据库当NoSQL用;实体数据是Value,一般是正文文本,通常不变,一般业务下只按主键查询;两者要分开。
  • 区分核心业务和附加业务数据,每一项附加的新业务数据都单独存储,与核心业务数据表分开,既可降低核心业务数据库的变更成本,还可避免新业务频繁调整上下线时影响核心业务。

目前的互联网系统大都严重依赖MySQL的Replication主从同步来实现系统横向扩展,虽然MySQL在新版本中陆续加入RBR复制和半同步等机制,但从库的单线程写操作限制还是最大的制约因素,到现在还没有看到很理想的革新性解决方案。

关于缓存,从浏览器到文件系统很多环节都有涉及,这里主要说的是应用系统自己的部分。

  • 最好的缓存方案是不用缓存,缓存带来的问题往往多于它解决的问题。
  • 只有一次更新多次读取的数据才有必要缓存,个性化的冷数据没必要缓存。
  • 缓存分为主动(Server推)和被动(Client拉)两种更新方式,各自适用于不用场景。主动更新方式一般适用于更新频率较高的热数据,可保证缓存未命中时,失控的用户行为不会引发系统连锁反应,导致雪崩。被动更新方式一般适用于更新频率相对较低的数据,也可以通过上文提到的异步更新模式,避免连锁反应和雪崩。
  • 缓存的更新操作尽量设计为覆盖方式,避免偶发数据错误的累积效应。

一个UGC系统流量刚开始上涨时,初期的表面性能瓶颈一般会表现在Web Server层面,而实际上大多是数据库的原因,但经充分优化后,最终会落在文件系统或网络通信的I/O瓶颈上。直接承载用户访问冲击的前端服务器最好尽量设计为无状态模式,降低宕机重启后的修复工作量。

顺带提及,我在新浪聊天和评论系统的开发过程中,逐步积累了一个Web应用开发组件库,在新浪全面转向PHP之前,曾用于新浪的内容管理(CMS)、用户注册和通行证、日志分析和论坛等使用C++的系统,目前发布于github.com/pi1ot/webapplib。

评论系统5.0方案

2010年后针对4.0系统的缺陷,启动了5.0系统工作。因为工作的交接,5.0系统我只负责了方案设计,具体开发是交给其他同事负责的,线上的具体实现与原始设计方案可能会有区别。5.0系统极大简化了系统层次,在保证抵抗突发流量波动性能的前提下,数据更新的及时性有了明显提高。

图9  4.5系统流程

图10  5.0系统流程

设计方案上的主要变化有以下几点。

  • 评论帖子ID从数据库自增整数改为UUID,提交时即可确定,消除了必须等待主库写入后才能确定评论ID的瓶颈,对各个层面的缓存逻辑优化有极大帮助。
  • 重新设计数据库结构,通过充分的数据切分,保证了所有高频业务操作都可在一个有限数据量的数据表中的一次简单读取操作完成,索引和文本数据隔离存储,在数据库中实现了原4.0系统中索引模块的功能,取消了4.0系统的索引缓存层。
  • 改用内存NoSQL缓存用户频繁读取的最新10~20页数据,取消了原4.0系统文件方式的页面缓存层。
  • 系统运行环境迁移到新浪云的内部版本:新浪动态平台,设备资源富裕度有了极大改善。
  • 改为Python语言开发,不用再像4.0系统那样每次更新时都要等待半个小时的编译过程,也不用再打包几百兆的执行文件同步到几十台服务器上,而语言层面的性能损失可以忽略不计。

新闻评论产品总结

新闻评论作为微博之前最能反映舆情民意的UGC平台,长期承载了国内互联网用户对时事新闻的匿名表达欲望,曾经一度成为上到政府下到网民的关注焦点。虽然面临了相对其他社区系统更为严厉的管控力度,也错过了实施实名制改造时迈向社区化的最佳时机,但无论如何,在21世纪的前十年,国内门户网站的新闻评论服务,都是中国互联网产品和技术发展历史上绝对不能错过的一笔。

作者刘立,2000年毕业于哈尔滨工业大学计算机系,2000-2013年工作于新浪网研发中心和门户技术部门,目前在一家社交电商平台创业团队任技术负责人。

Bootcamp Mac 安装Win10 教程

安装win10之前,告诉你首先要准备的东西:一、苹果电脑(可以正常进入Mac OS系统的)二、U盘(大于等于8G,确定不是坏的)
这两样东西准备好,我们就可以开始了

1、分区–进入苹果电脑的Mac OS系统,找到“实用工具”里的“磁盘工具”,双击打开,先点本机总的那块儿磁盘,再选择“分区”,点“+”增加一个分区,右侧“大小”可以调整WINDOWS的大小,确定好分区大小之后,点击“应用”,就可以等待分区完毕了!(注意:1、WINDOWS分区的格式是Mac OS扩展(日志式)2、如果提示分区失败的话,可以进入recovery分区修复一下磁盘,如果修复后还是不行,建议整个磁盘格式化重装Mac吧)

1.png

2、准备–Win10镜像文件和激活工具

windows 10 下载(把下面链接复制到迅雷里即可下载)
cn_windows_10_multiple_editions_x64_dvd_6848463.iso (4.01 GB)

win10jihuotools_3987.rar (2.46 MB, 下载次数: 16337)
2.png

3、使用BootCamp助理制作WIN10启动U盘和获取WINDOWS对应的驱动
BootCamp助理的位置在–应用程序–实用工具下,找到后双击打开
3.png
点击继续
4.png
选择第一个选项(制作WIN10 U盘启动)和第二个选项(获取WINDOWS对应的驱动)
5.png
第一、选取正确的ISO镜像 第二、目的磁盘选择你确定是好的那个U盘(U盘记得先格式化一下,格式为Mac OS 扩展(日志式))
6.png
点继续
7.png
然后等着就可以了
8.png
等着…
9.png
等着…
10.png
快完成的时候会提示下面窗口,输入你系统密码点回车就可以
11.png
完成–U盘启动和win所需的驱动都在U盘里了
12.png

4、重启电脑,用做好的U盘启动安装WIN10
重启电脑长按OPTION不放(两边按哪个都行)
IMG_2009.jpg
然后会出现下图所示,选择第四个黄色U盘启动,点回车进入
IMG_2011.jpg
点击下一步
IMG_2012.jpg
点击跳过
IMG_2013.jpg
可以选择专业版和家庭版,果断选择专业版了,点击下一步
IMG_2014.jpg
点击下一步
IMG_2015.jpg
选择刚刚分了80G的那个区,点删除(注意:其他分区和未分配的空间不要动,删除后Mac OS就崩溃了)
IMG_2016.jpg
删除后会出现下面页面,然后点新建
IMG_2017.jpg
然后再点应用(大小默认的最大,不用自己调整)
IMG_2018.jpg
点击确定
IMG_2019.jpg
windows主分区已经完成,选择后点下一步
IMG_2020.jpg
等…
IMG_2021.jpg
等…
IMG_2022.jpg
等…
IMG_2023.jpg
完成–自动重启
IMG_2024.jpg
自动重启后就自动进入WIN10启动界面了,点“以后再说”
IMG_2025.jpg
点击“使用快速设置”
IMG_2026.jpg
新建一个账户,密码可以不填
IMG_2027.jpg

5、安装WINDOWS对应的BootCamp驱动(驱动已经在U盘里)
双击打开BootCamp文件夹
IMG_2029.jpg
双击Setup安装
IMG_2030.jpg
单机下一步
IMG_2031.jpg
等…
IMG_2032.jpg
等…
IMG_2033.jpg
完成–会提示重启电脑
IMG_2034.jpg
6、打开WIN10激活工具,点击激活就大功告成喽
IMG_2035.jpg

IMG_2036.jpg

PS:如果有看了此教程不会的,建议多看几遍;如果有看了此教程安装失败的,建议看着多安装几次

一号店迎战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:思维导图

未完待续……