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

原文: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

雪幕秋夕 简历

个人信息

  • 雪幕秋夕 / 男 / 1995
  • 山东大学 / 数学系 / 大三

联系方式

  • Email:i@lujq.me
  • QQ:549559373
  • 微信:ljq_0225
  • 手机:15165172012

技术笔记以及翻译文章

  1. 蚂蜂窝爬虫技术细节
  2. 关于全站Ajax的笔记
  3. py读写Excel模拟登陆代理爬取
  4. 翻译《MEAN-Web-Development》部分章节

代码经验

  1. 独立开发了一个管理Github Star的项目,以Web方式可视化管理Github Star过的项目,弥补Github原生Star功能的不足。后端采用Nodejs、数据库采用MongoDB、服务器采用Expressjs、前端采用Angularjs框架、UI使用Bootstrap库,项目已在Github开源 [ https://github.com/golmic/GithubStarManage ] 线上页面为 [http://gsm.lujq.me]
  2. 用Nodejs写了一个爬虫,爬取[[ 蚂蜂窝],并将爬取到的数据储存到Mongo数据库中,并展示。代码在 [ https://github.com/golmic/mafengwo-spider ] 技术细节在 [ http://code.lujq.me/2015/09/12/蚂蜂窝爬虫/] 上线页面为 [http://mafengwo.lujq.me]
  3. 独立开发了一个博客系统,同样是MEAN全栈实现,UI采用Google Material Design,实现Markdown编辑及实时预览,无刷新上传图片等功能。[ Github源码地址: https://github.com/golmic/G-blog ]
  4. 采用基于Nodejs的Hexo程序搭建了一个技术博客,托管在Github [ 技术博客地址 http://code.lujq.me ]
  5. 采用Wordpress搭建了个人博客,前端是基于Bootstrap框架自己写的主题,集成多个开源项目,如Animate.cssWOW等,大量使用HTML5以及CSS3特性。后端用php语言二次开发,实现了全站Ajax异步加载以及本地缓存。[ 个人博客地址 http://lujq.me ] [ 个人博客前端代码在Github开源 ]
  6. 开发微信公众号Golmic,后端采用PHP调用百度LBSAPI实现天气查询以及天气警报推送;调用API以及抓取方式实现关键词回复。
  7. 用Python写过爬虫,模拟登录爬取,处理Excel数据。
  8. 在自己的服务器上配置了ShadowSocks,供研究学习用。

技能清单

熟练使用 MEAN 栈堆

  • MongoDB
  • Express
  • AngularJS
  • Node.js

以下均为我熟练使用的技能

  • Web后端语言:Node.js/Python
  • 后端框架:Express
  • 前端:Bootstrap/AngularJS/HTML5/Material Design
  • 数据库:MongoDB
  • MVC框架
  • 版本管理:Git
  • 团队协作:Worktile/石墨
  • Linux各发行版部署及维护项目
  • 云:Microsoft Azure/DigitalOcean用户
  • 微信公众号开发
  • 《算法导论》

对以下知识均有一定了解

  • 后端语言:php
  • 后端框架:Django
  • 机器学习、数据挖掘
  • Go语言、C++
  • Linux系统运维、LNMP架构
  • Nginx、Apache服务器
  • 数据库相关:MySQL/PgSQL/SQLite
  • 《C++ Primer》《Effect C++》

其它补充

  • 接受技术岗,倾向于后端,钟情于算法;
  • 能接受的最低工作时薪为50,硬标准。
  • 数学院TOP10,主修数学。我的教材都已经是全英的了,英文水平高,无压力阅读或翻译英文文章及技术文档;
  • 重度Google用户,长期混迹Github、Stack Overflow等国内外的技术网站及社群;
  • 自学能力以及独立解决问题的能力极高;
  • 喜欢尝试各种新鲜技术和领域,Microsoft Azure用户。

致谢

感谢您花时间阅读我的简历,期待能有当面向您学习请教的机会。

前端性能优化方案索引

陆续整理和不断更新网络上给出的前端性能的优化方案。

这里只是做一个总概括式的索引,每一个方案都十分值得推敲和细说。

1 请求和响应

缓存控制

请求头里,可以发送 If-Modified-Since 以及 If-None-Match 等信息,来询问服务端请求内容是否有更新,如果没有更新,可返回304,告诉浏览器使用缓存,避免重新下载资源。Pragma 和 Cache-Control 等也能控制缓存。如告诉服务端不要缓存等。

响应头里,Expires 可以告诉浏览器过期时间,Last-Modified 最近更新时间,ETag 则可允许浏览器进行缓存验证(在 If-None-Match 请求信息中使用)。

复用TCP

请求头里,Connection 可控制 TCP 通道的使用,使用 keep-alive 可以复用上次打开的 TCP。

GZIP压缩

如果可以启用 gzip 压缩,将减少响应数据大小,加快响应。请求头里面可用 Accept-Encoding 告知浏览器支持的压缩方式,而服务端则用 Content-Encoding 作为回应。

Cookies

发送请求时,cookies 也在请求之中。因此,cookies 也可以作为减少请求的优化对象。如,根据同源限制策略,可以使用多个域名加载资源,如加载静态资源,就不会发送多余的 cookies;同时,合理设置 cookies 的路径和域名,如在子站避免不必要的来自父站的 cookies。

减少HTTP请求

有很多细节可以实现,比如CSS Sprites、Data URL等等,由于此部分内容和下述内容有所重复,故部分细节在下面会讲到。

多域名分发

同域下浏览器能并发的请求有限,而为了增加并发,尤其是一些静态资源上,可以使用多个域名。但由于域名DNS解析本身也是耗时的,所以实践原则是2-4个为宜。

需要额外提醒的是,加载图像资源的时候,并发没有问题;但在加载 JavaScript 脚本的时候,还是会暂停加载其他资源。

使用CDN

根据用户能访问的最快位置加速访问。

避免重定向和404

重定向和404将浪费加载请求。

favicon.ico

浏览器默认加载的资源,最好能够缓存之。

2 HTML

减少DOM

过多的DOM元素会影响渲染、加载、执行。除了精简页面结构外,还可以适时删除不必要的DOM元素(页面内已经不会再访问的元素),又或者可以懒加载(不一定会使用到的元素,如登录框)。

CSS 和 JavaScript 文件位置

CSS 放 head,JavaScript 放 body 闭标签前。乃是因为:

  • 样式表不参与 DOM 修改,所以不会为了解析样式停止文档解析
  • 浏览器要避免重绘,在没有拿到全部样式前不会开始渲染
  • 解析样式时,有的浏览器(FF)会停止脚本运行,而有的(Webkit)则会在脚本访问样式属性但可能受未加载样式影响时停止脚本运行
  • 脚本解析中可能请求样式,如果样式还未解析完毕就会出错
  • 脚本执行将暂停文档的解析和资源的下载

因此,将二者放在适当的位置,能够极大提高渲染效率。

脚本延迟加载

可将脚本添加 defer 和 async 属性。两个属性的共通点在于,脚本的加载和文档的解析是同步进行的,而区别在于:async 一旦加载完毕,立即停止文档解析并执行脚本;defer 等待文档解析完毕后再执行。

合理使用内联

脚本和样式,应按需选择内联或者外链。对于访问少、样式和脚本复用少的页面,可以考虑使用内联样式从而减少 HTTP 请求。但如果页面访问频繁,样式脚本在多个页面经常复用,使用外链则是最优选择。

无论如何,需要避免使用 @import 来导入样式。

而图像也是一样,高级浏览器支持将图像数据直接 base64 编码在 src 属性里,必要时可直接在 HTML 里输出图片数据。

减少iframe

iframe 本身有许多优点,比如可以并行下载脚本,适合加载慢内容(如广告),同时浏览器可以对其进行安全控制。

减少使用 iframe 的主要考虑是:iframe 会阻碍页面加载,同时也没有语义。

3 CSS

选择器

选择器效率排行如下:

  1. ID选择器
  2. 类选择器
  3. 标签选择器
  4. 相邻选择器
  5. 子选择器
  6. 后代选择器
  7. 通配符选择器
  8. 属性选择器
  9. 伪类选择器

效率与优先级并不是对等关系,优先级高的不一定效率高。如 #id.class 合用比 单个 #id 的优先级高,但效率却比值慢。

选择器书写建议是:

  1. 避免使用通配符
  2. 不使用标签名或类名修饰ID规则:如果规则使用ID选择器作为关键选择器,不要给规则添加标签名。因为ID本身就是唯一的,添加标签名会不必要地降低匹配效率
  3. 不使用标签名修饰类:相较于标签,类更具独特性
  4. 尽量选择最具体的方式:造成低效的最简单粗暴的原因就是在标签上使用太多规则。给元素添加类可以更快细分到类方式,可以减少规则去匹配标签的时间
  5. 关于后代选择器和子选择器:避免使用后代选择器,非要用的话建议用子选择器代替,但子选择器也要慎用,标签规则永远不要包含子选择器
  6. 利用可继承性:没必要在一般内容上声明样式

避免滤镜、表达式、Hack

效率低。

Sprites

合并图片可减少 HTTP 请求。其他建议有:

  1. Sprite 中水平排列图片,垂直排列会增加文件大小
  2. Sprite 中把颜色较近的组合在一起可以降低颜色数,理想状况是低于256色以便适用PNG8格式
  3. 不要在Spirite的图像中间留有较大空隙。这虽然不大会增加文件大小,但对于用户代理来说它需要更少的内存来把图片解压为像素地图。100×100的图片为1万像素,1000×1000就是100万像素

使用3D动画

使用 transform: translate3d 等可加速 GPU 渲染。

4 JavaScript

避免重排

渲染中可能存在的高成本操作:

  1. 修改、增加、删除DOM节点
  2. 移动DOM位置或者动画效果
  3. CSS样式修改(重绘比重排好些)
  4. 调整窗口大小,或者滚动时有绝对定位、fixed 背景以及动画
  5. 修改页面默认字体

浏览器一般会缓存Render Tree的更新渲染,但以下情况除外:

  1. 调整窗口大小和修改页面默认字体
  2. client/offset/scroll
  3. getComputedStyle() currentStyle

优化建议:

  1. 修改 className 而非 style
  2. 离线 DOM 后修改,如 documentFragment 或者 display:none 后再调整样式
  3. 缓存属性值
  4. 动画使用 absolute/fixed
  5. 不使用 table 布局(牵一发动全身)
  6. 修改层级比较低的 DOM

事件委托

将多个节点上的事件放到其父节点(经典案例:将 li 上的事件绑定到 ul 上)。

内存管理

合理释放和缓存内存。如缓存复用的属性,接触对象引用等。

5 资源

压缩大小

压缩样式、脚本、图像等资源的大小。

针对图像资源,可从预览小图、格式选择等多角度优化。

懒加载

如图像的懒加载(滚动到显示区域后才加载)等。

预加载

针对之后会用到的资源提前加载。

5 客户端

localStorage 缓存

相比 cookies,localStorage 存储容量更大。可以将一些静态资源(如 jQuery库)等缓存。

强迫症的 Mac 设置指南

如何配置一个高效的 Mac 工作环境

Table of Contents

  1. OS X
  2. 常用工具
  3. 开发工具

一直想写这么一篇文章,把我从同事那里学到的经验分享出来。市面上有很多类似的文章,写得都非常好,让我受益匪浅。不过我还是有一些自己总结出来的经验想要分享。

在工作中,我一般会在 1 到 10 人的团队中,经常会结对编程,即两个人共用一台 Mac 工作,因此也经常会把 Mac 外接一个大显示器、鼠标和键盘。我的常用开发平台有 Java、Ruby、Node.js、Web 等,使用 JetBrains 的开发工具,比如 IntelliJ IDEA、RubyMine、WebStorm 等。

我深知自己的知识有限,所以写下本文以便和大家切磋交流。同时更有效率的方法和更好的工具也在不断涌现,我也贪心的希望把更好的方法和工具都收集更到到这里,我会不断更新本文,让它尽量不过时。最新内容请访问:https://github.com/macdao/ocds-guide-to-setting-up-mac。欢迎通过 GitHub 的Issues或者直接Pull Requests方式来分享你的经验。期待你的反馈。

我认为“一个高效的 Mac 工作环境”有以下几个特点:

  • 自动化

    举个例子。手动安装一个应用,需要1)打开浏览器,2)搜索应用的名字,3)打开应用网站,4)寻找下载链接和安装方法,5)下载并等待下载完成,6)安装下载文件,7)可能还有后续的安装步骤。而自动化安装一个应用,只需要1)打开终端工具,2)敲入安装命令,3)等待完成这几个步骤。

    自动化可以大大简化操作,提高效率。

  • 统一

    我经常结对编程,偶尔会遇到快捷键不一样,命令不同等问题。我强烈建议,至少在一个团队中,大家尽量使用相同的快捷键、命令等环境。(我记得有个实践就是这个,可是我一直没找到该实践的名字和出处,求告诉)

  • 够用

    够用就好,如果系统本身已经满足了我的需求,我不会再使用第三方工具。

  • 效率

    效率,一切都是为了效率。

本文对于第三方应用如何安装和使用只有最简单的介绍,具体还请参考官方网站和相关文档。

有些章节标题标注了[OCD],意思是这些章节带有我强烈的个人色彩,如果你跟我臭味相投,欢迎借鉴,如果你并不认同,请忽略掉好了。

PS:虽然本文名为“强迫症”,但其实并不是真正意义上的强迫症,真正意义上的强迫症是一种会对患者的日常生活产生负面影响的疾病。

1. OS X

本节介绍操作系统本身的一些设置。

功能键

默认情况下,F1-F12 都是特殊功能,比如调节屏幕亮度。而当你需要键入 F1-F12 时(比如在使用 IntelliJ IDEA 的快捷键时),需要同时按住 Fn。这对于开发人员来说是非常不方便的。

把 F1-F12 改成标准功能键:选择System Preferences > Keyboard,在Keyboard标签页中选中Use all F1, F2, etc. keys as standard function keys

全键盘控制

当你在 Sublime Text 里关闭文件时,可能会遇到这样的对话框:

dialog-box-without-all-controls

注意这个Save按钮跟其他两个按钮不太一样,它的底色是蓝的。这种按钮被称为默认按钮,除了用鼠标点击触发外,还可以通过回车键触发。

那么问题来了,如果你不想保存,想点击Don't Save,是不是只能用鼠标点击了呢?

并不是这样:选择System Preferences > Keyboard,在Shortcuts标签页中选择All controls;或者使用快捷键⌃F7。之后这个对话框会变成这样:

dialog-box-with-all-controls

这个Don't Save按钮有了一圈蓝边,这个意味着你可以通过空格键触发。不仅如此,你还可以用Tab键把蓝边转移到其他按钮,来实现全键盘控制。

除了All controls这个方法,你还可以用⌘⌫来选择Don't Save⌘⌫的作用是在包含“删除”或“不存储”按钮的对话框中选择“删除”或“不存储”。

除了上述两个办法之外,居然还有个方法!就是按⌘D!据说是因为按⌘+按钮的大写首字母可以触发该按钮。可是!我按了⌘C⌘S想取消和保存都没用!但是⌘D真的有用!如果仅仅是这也就算了,可是我又手贱试了下 TextEdit,在关闭未保存的文件时弹出的对话框上有三个按钮DeleteCancelSave。然而⌘D⌘C都没用,但是!⌘S可以保存!我完全不能理解!我整个人几乎都是崩溃的,只好以咆哮体写下这段文字。如果谁能解释请务必告诉我,必有重谢!

⌘C不能用应该是因为它绑定到了复制功能;而⌘D不能用因为它的作用是从“打开”对话框或“存储”对话框中选择“桌面”文件夹。

在这个对话框上,你可以用Esc来执行Cancel操作。

Spotlight 快捷键

中文版 OS X 的 Spotlight 的快捷键是⌃Space。这个快捷键有一些问题:

  • JetBrains 的 IDE,比如 IntelliJ IDEA、WebStorm 等都使用⌃Space作为自动完成这个最常用功能的快捷键。我不建议更改 IDE 的快捷键,而建议更改 Spotlight 的快捷键。
  • 对于没有添加中文输入法的 Mac 来说,Spotlight 的快捷键是⌘Space。英语国家的人都是这样的。所以我建议把 Spotlight 的快捷键设置为⌘Space,跟他们一致。

输入法快捷键

一般来说切换输入法的快捷键是⌘Space。由于我建议把 Spotlight 的快捷键设置为⌘Space,所以我建议把切换输入法的快捷键设置为⌥Space

其他快捷键

让双手尽量多的键盘和快捷键,少使用鼠标和触摸板,可以大大提高效率。

设置 Trackpad 轻点来点按

默认情况下按下触摸板才是点按(click)。我喜欢设置成用轻点作为点按:

选择System Preferences > Trackpad,在Point & Click标签页中选中Tap to click

语音

OS X 自带了语音功能,可以用say命令让 Mac 开口说话:

say hello

可以和&&或者;配合使用来提示你某任务已经完成:

brew update && brew upgrade && brew cleanup ; say mission complete

通过命令行来听取发音还是有点麻烦。其实我们几乎可以在任何地方选中单词,然后使用快捷键⌥+ESC发音。仅仅需要这样设置一下:选择System Preferences > Dictation & Speech,在Text to Speech标签页中选中Speak selected text when the key is pressed

词典

OS X 自带了词典(Dictionary)。你几乎可以在任何应用中通过三指轻拍触摸板来现实对应单词的释义。

也可以打开 Dictionary 应用来查找单词。

可以在 Dictionary 应用中添加英汉汉英词典。

Dock Position

默认 Dock 在屏幕下方。我们的屏幕一般都是 16:10,Dock 在屏幕下方的话会占据本来就不大的垂直空间。建议把 Dock 放到左边或者右边。

更改 Caps Lock 键为 Control 键

我经常用到Control键,但这个键在键盘的左下角,很难按到。同时我发现我很少使用Caps Lock键,我一般会用Shift键加字母来输入大写字母,或者先输入小写再(通过快捷键)转换成大写。

基于以上原因,我把Caps Lock键的功能改成了Control键。很多同事也都这么做的,可能是受到HHKB 的影响。

设置方法:选择System Preferences > Keyboard,在Keyboard标签页中点击Modifier Keys...按钮,在弹出的窗口中,把Caps Lock (⇪) Key:对应的选项改成⌃ Control

Remove all Dock icons[OCD]

本条目对于强迫症适用。

默认情况下 Dock 被一堆系统自带的应用占据着,而其中大部分我都很少使用,当我打开几个常用应用后,Dock 上会有很多图标,每个图标都会被挤得很小。所以我会把所有 Dock 上固定的图标都删掉,这样一来 Dock 上只有我打开的应用。

PS:Finder 图标是删不掉的。

重置 Launchpad 上图标位置[OCD]

本条目对于强迫症适用。

新的应用被安装后,经常会跑到 Launchpad 的第一屏,所以它们的位置跟安装的顺序有关系,而我更希望它们可以按照某种更加稳定的顺序排列,比如按照系统默认的顺序:

defaults write com.apple.dock ResetLaunchPad -bool true; killall Dock

在默认顺序中,Launchpad 第一屏只有 Apple 自家应用。

2. 常用工具

本节介绍一些常用的,跟开发没有直接关系的第三方应用及其设置。

Homebrew

包管理工具,官方称之为The missing package manager for OS X

安装步骤:先打开 Terminal 应用,输入:

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

有了 brew 以后,要下载工具,比如 MySQL、Gradle、Maven、Node.js 等工具,就不需要去网上下载了,只要一行命令就能搞定:

brew install mysql gradle maven node

PS:安装 brew 的时候会自动下载和安装 Apple 的 Command Line Tools。

brew 的替代品有 MacPorts,现在基本没人用它。

Homebrew Cask

brew-cask 允许你使用命令行安装 OS X 应用。比如你可以这样安装 Chrome:brew cask install google-chrome。还有 Evernote、Skype、Sublime Text、VirtualBox 等都可以用 brew-cask 安装。

brew-cask 是社区驱动的,如果你发现 brew-cask 上的应用不是最新版本,或者缺少你某个应用,你可以自己提交 pull request。

安装:

brew install caskroom/cask/brew-cask

应用也可以通过 App Store 安装,而且有些应用只能通过 App Store 安装,比如 Xcode 等一些 Apple 的应用。App Store 没有对应的命令行工具,还需要 Apple ID。倒是更新起来很方便。

几乎所有常用的应用都可以通过 brew-cask 安装,而且是从应用的官网上下载,所以你要安装新的应用时,建议用 brew-cask 安装。如果你不知道应用在 brew-cask 中的 ID,可以先用brew cask search命令搜索。

iTerm2

iTerm2 是最常用的终端应用,是 Terminal 应用的替代品。提供了诸如Split Panes一群实用特性。它默认的黑色背景让我毫不犹豫的抛弃了 Terminal。

安装:

brew cask install iterm2

感谢 brew-cask,我们可以通过命令行自动安装 iTerm2 了。

在终端里,除了可以用⌃E等快捷键(详见其他快捷键)之外,还可以使用⌥B⌥F等快捷键(具体可以参考这里)。前提是这样设置一下:

选择Iterm菜单 > Preferences > Profiles,选择你在使用的 Profile(默认是Default),在Keys标签页中把Left option (⌥) key acts asRight option (⌥) key acts as都设置成+ESC

在打开新的窗口/标签页的时候,默认情况下新窗口总是 HOME 目录,还需要我每次敲命令才能进入工作目录。如果想要这个新窗口在打开的时候就自动进入工作目录,需要如下设置:

选择Iterm菜单 > Preferences > Profiles,选择你在使用的 Profile(默认是Default),在General标签页中的Working Directory部分中选择Reuse previous seesion's directory

至此,Terminal 应用已经出色的完成了其历史使命。后面命令行就交给 iTerm2 啦。

在 iTerm2 中双击会自动选中对应的词,三击会选中对应的整行。选中的内容会自动进入剪贴板,不需要再按⌘C复制。

Oh My Zsh

默认的 Bash 是黑白的,没有色彩。而 Oh My Zsh 可以带你进入彩色时代。Oh My Zsh 同时提供一套插件和工具,可以简化命令行操作。后面我们会看到很多介绍,你会看到我爱死这家伙了。

安装:

sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

目前我使用的插件有:git z sublime history rbenv bundler rake

Oh My Zsh 使用了 Z shell(zsh),一个和 Bash 相似的 Shell,而非 Bash。

在 Z shell 中,~/.zshrc是最重要的配置文件。Oh My Zsh 在安装的时候会把当前环境的$PATH写入~/.zshrc中。这并不是我期望的行为,因为使用了 brew,我们基本不再需要去定制$PATH,而 Oh My Zsh 提供的默认$PATH$HOME/bin:/usr/local/bin:$PATH是非常合适的一个值,它把$HOME/bin加入了$PATH,可以让我们把自己用的脚本放到$HOME/bin下。

所以建议把~/.zshrc重置:

cp ~/.oh-my-zsh/templates/zshrc.zsh-template ~/.zshrc

Oh My Zsh 还有很多有价值的插件

替代品有 Oh My Fish,使用了 Fishshell 作为基础。

Git 常用别名

几乎每个人都会使用一些方法比如 Git 别名来提高效率,几乎所有人都会把使用git st来代替git status。然而这需要手动设置,每个人也都不完全一样。

Oh My Zsh 提供了一套系统别名(alias),来达到相同的功能。比如gst作为git status的别名。而且 Git 插件是 Oh My Zsh 默认启用的,相当于你使用了 Oh My Zsh,你就拥有了一套高效率的别名,而且还是全球通用的。是不是棒棒哒?下面是一些我常用的别名:

Alias Command
gapa git add --patch
gc! git commit -v --amend
gcl git clone --recursive
gclean git reset --hard && git clean -dfx
gcm git checkout master
gcmsg git commit -m
gco git checkout
gd git diff
gdca git diff --cached
glola git log --graph --pretty = format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit --all
gp git push
grbc git rebase --continue
gst git status
gup git pull --rebase
gwip git add -A; git rm $(git ls-files --deleted) 2> /dev/null; git commit -m "--wip--"

完整列表请参考:https://github.com/robbyrussell/oh-my-zsh/wiki/Plugin:git

Scroll Reverser

当你在浏览一个很长的网页时,你看完了当前显示的内容,想要看后续的内容,你可以在 Trackpad 上双指上滑,或者鼠标滚轮向上滚动。这是被称作“自然”的滚动方向。

然而在 Windows 里鼠标滚动的行为是相反的:鼠标滚轮向下滚动才会让浏览器显示后续的内容,向上滚动会达到页面的顶部。你可以在 OS X 的系统偏好设置里修改(选择System Preferences > Trackpad,在Scroll & Zoom标签页中不选中Scroll direction: natural),但是这样会同时改变鼠标滚轮的方向和 Trackpad 的方向。

要想只改变鼠标滚轮的方向,而保持 Trackpad 依旧是“自然”的,我们需要 Scroll Reverser:

brew cask install scroll-reverser

PS:这货会让三指点击失效

ShiftIt

原生 OS X 下只能手动调整窗口大小,所以我们需要窗口管理工具。我用过很多窗口管理工具,可惜大部分工具都存在快捷键冲突的问题(对我来说主要是 IntelliJ IDEA)。ShiftIt 是少见的没有冲突的窗口管理工具:

brew cask install shiftit

PS:ShiftIt的旧版本需要安装 X11,最新版本已经修正了这个问题。

替代者有 SizeUp,主要快捷键和 ShiftIt 相同。

当然如果喜欢 hacking,Slate 是个不错的 hackable 的窗口管理工具。配置可以参照http://thume.ca/howto/2012/11/19/using-slate/

Sublime Text 2

安装:

brew cask install sublime-text

在命令行中指定使用 Sublime Text 打开某文件,是一个非常常用的功能,一般我们会按照 OS X Command Line 中所说执行 ln -s "/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl" ~/bin/subl 来增加subl链接。但是如果你用 brew-cask 安装的话,恭喜你,你不需要运行这个命令,因为 brew-cask 自动帮你做了这件事情。而且你卸载 Sublime Text 的时候 brew-cask 会自动删掉这个链接。

同时 Oh My Zsh 也提供了 Sublime Text 插件,叫做sublime。参考:https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins/sublime,这个插件和通过 brew-cask 安装的 Sublime Text 完美兼容。

替代品有 Atom、TextMate、Sublime Text 3 等,跟 Sublime Text 2 一样,用 brew-cask 安装的话命令行工具会被自动加入$PATH

MacDown

MacDown 是 Markdown 编辑器。由于 Mou 一直不支持代码高亮,我就转向了 MacDown。完美支持GFM

我特别喜欢 Markdown,我用 Makdown 来写文章(包括本文),写幻灯片(reveal.js)。Markdown 可以让我专注于内容本身,而无需花精力在排版和样式上。

安装:

brew cask install macdown

z

在打开终端后,你是怎么进入项目的工作目录?是cd xxx⌃R还是用别名?

z 工具可以帮你快速进入目录。比如在我的 Mac 上运行z cask就会进入/usr/local/Library/Taps/caskroom/homebrew-cask/Casks目录。

这货的安装非常方便,甚至都不需要下载任何东西,因为它已经整合在了 Oh My Zsh 中。编辑~/.zshrc文件,在plugins=(git)这行中加上z变成plugins=(git z),然后运行source ~/.zshrc重新加载配置文件,就可以使用 z 了。

替代品有 autojump。autojump 需要使用 brew 安装。

Vimium

Vimium 是一个 Google Chrome 扩展,让你可以纯键盘操作 Chrome,把你的 Chrome 变成“黑客的浏览器”。

安装方法请参考官方网站。

其他浏览器也有类似的工具,比如 FireFox 的 KeySnail

LastPass

LastPass 是管理密码的工具,支持二次验证,提供所有浏览器插件以及 Mac 桌面版本。

最重要的是,它提供 命令行 的版本,可以直接通过 brew 安装

brew install lastpass-cli --with-pinentry

之后,只需要登陆:

lpass login you@email.com

就可以拷贝密码或者集成到其他命令中了:

lpass show --password gmail.com -c

SourceTree

SourceTree 是 Atlassian 公司出品的一款优秀的 Git 图形化客户端。如果你发现命令行无法满足你的要求,可以试试 SourceTree。

安装:

brew cask install sourcetree

用 brew-cask 安装会自动增加命令行工具stree$PATH里。在命令行中输入stree可以快速用 SourceTree 打开当前 Git 仓库。详细用法请参见stree --help

CheatSheet

CheatSheet 能够显示当前程序的快捷键列表,默认的快捷键是长按

CheatSheet

安装:

brew cask install cheatsheet

3. 开发工具

Java

现在 OS X 都不会自带 JDK 了,所以进行 Java 开发的话,需要下载 JDK。在 brew-cask 之前,我们需要从 https://developer.apple.com/downloads/ 或者 Oracle 网站上下载。还有更麻烦的--卸载 JDK 和升级 JDK。

JDK 安装文件是 pkg 格式,卸载和.app不一样,且没有自动卸载方式。

而 brew-cask 提供了自动安装和卸载功能,能够自动从官网上下载并安装 JDK 8。

brew cask install java

如果你需要安装 JDK 7 或者 JDK 6,可以使用homebrew-cask-versions

brew tap caskroom/versions
brew cask install java6

在 OS X 上,你可以同时安装多个版本的 JDK。你可以通过命令/usr/libexec/java_home -V来查看安装了哪几个 JDK。

那问题来了,当你运行java或者 Java 程序时使用的是哪个 JDK 呢?在 OS X 下,java也就是/usr/bin/java在默认情况下指向的是已经安装的最新版本。但是你可以设置环境变量JAVA_HOME来更改其指向:

$ java -version
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
$ JAVA_HOME=/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home java -version
java version "1.6.0_65"
Java(TM) SE Runtime Environment (build 1.6.0_65-b14-466.1-11M4716)
Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-466.1, mixed mode)

其中JAVA_HOME=/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home可以用JAVA_HOME=`/usr/libexec/java_home -v 1.6`这种更加通用的方式代替。

jEnv

也可以使用 jEnv 来管理不同版本的 JDK,这个工具跟 rbenv 类似,通过当前目录下的.java-version来决定使用哪个 JDK。jEnv 也可以用 brew 安装。不过要使用 jEnv 要有几个问题:

  • 需要手动把eval "$(jenv init -)"加入 profile,没有 Oh My Zsh 插件。这点是我非常反感的。

    可以把eval "$(jenv init -)"加入~/.zlogin,这样可以避免修改~/.zshrc

  • 需要手动添加 JDK,不会自动采集系统 JDK。跟 Ruby 不同,OS X 已经提供/usr/libexec/java_home工具来管理安装的 JDK。
  • 需要 jenv rehash。这个是跟 rbenv 学的。

所以我建议不要使用 jEnv。

Java[OCD]

作为一个强迫症患者,每当我看到 Java 的错误写法就想纠正过来。

当指编程语言时,Java 的正确写法是首字母大写,其余小写。其他写法比如JAVAjava都是不对的。

在其他一些地方会使用小写的java

  • java命令
  • 原文件Main.java
  • 包名java.lang

只有在全大写的标题里使用JAVA或者环境变量JAVA_HOME

IntelliJ IDEA

Java 开发必备工具 IntelliJ IDEA。可以安装 Ultimate Edition:

brew cask install intellij-idea

也可以安装开源免费的 Community Edition:

brew cask install intellij-idea-ce

IntelliJ IDEA 有几套内建的快捷键方案(Keymap)。其中适用于 OS X 的有Mac OS XMac OS X 10.5+两种。区别是:

  • Mac OS X方案和其他平台上的快捷键类似,
  • Mac OS X 10.5+更加符合 OS X 常用的快捷键。

一个团队使用不同的快捷键会严重影响效率。可以用View | Quick Switch Scheme⌃ Back Quote)快速切换 Keymap。

如果可以选择的话,我建议使用Mac OS X方案。因为我经常遇到使用 Windows 的客户,而 Windows 平台上的快捷键和Mac OS X方案类似。

可以从 IDEA 的Help > Default Keymap Reference打开快捷键的参考手册。不过从这里打开的是Mac OS X 10.5+方案的,而Mac OS X方案的可以从这里找到:http://www.basrikahveci.com/static/ij_keymap_mac.pdf

rbenv

人人都需要一个 Ruby 版本管理工具。rbenv 就是这样一个轻量级工具,它可以通过 brew 安装。

安装:

brew install rbenv ruby-build

然后在~/.zshrc中加上rbenv插件。否则你需要手动添加eval "$(rbenv init -)"~/zshrc或者~/.zprofile文件里。

有时候项目会依赖一些奇怪的版本号,比如ruby-2.1.0,这个时候你需要 rbenv-aliases 帮忙:

brew install rbenv-aliases

替代品有 RVM、chruby。因为 RVM 不能通过 brew 安装,并且安装的时候会没有节操的修改一堆文件,所以被我早早的弃用了。chruby 也是一个轻量级工具,而且可以完美的和 Oh My Zsh 集成在一起,我看到有些生产环境在用它。

Ruby 常用别名

几乎所有 Ruby 开发人员都会把bi作为bundle install的别名。Oh My Zsh 提供builder插件,这个插件提供了一套别名,比如bibe。同时还能让你在运行一些常用 gem 的时候直接输入rspec,不需要be rspec这样了。具体包括哪些命令请参考这里

Z shell 对于[]符号有特殊的处理,所以在运行rake task[parameter]的时候会报错,你需要改成rake task\[parameter\]或者noglob rake task[parameter]。然而 Oh My Zsh 已经看穿这一切,自带的 rake 插件已经解决了这个问题:brake task[parameter]

添加插件的时候注意把rake放到bundler后面,例如这样:

plugins=(git z sublime history rbenv bundler rake)

参考资料

redis info命令详解

以一种易于解释(parse)且易于阅读的格式,返回关于 Redis 服务器的各种信息和统计数值。

通过给定可选的参数 section ,可以让命令只返回某一部分的信息:

  • server : 一般 Redis 服务器信息,包含以下域:

    • redis_version : Redis 服务器版本
    • redis_git_sha1 : Git SHA1
    • redis_git_dirty : Git dirty flag
    • os : Redis 服务器的宿主操作系统
    • arch_bits : 架构(32 或 64 位)
    • multiplexing_api : Redis 所使用的事件处理机制
    • gcc_version : 编译 Redis 时所使用的 GCC 版本
    • process_id : 服务器进程的 PID
    • run_id : Redis 服务器的随机标识符(用于 Sentinel 和集群)
    • tcp_port : TCP/IP 监听端口
    • uptime_in_seconds : 自 Redis 服务器启动以来,经过的秒数
    • uptime_in_days : 自 Redis 服务器启动以来,经过的天数
    • lru_clock : 以分钟为单位进行自增的时钟,用于 LRU 管理
  • clients : 已连接客户端信息,包含以下域:

    • connected_clients : 已连接客户端的数量(不包括通过从属服务器连接的客户端)
    • client_longest_output_list : 当前连接的客户端当中,最长的输出列表
    • client_longest_input_buf : 当前连接的客户端当中,最大输入缓存
    • blocked_clients : 正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量
  • memory : 内存信息,包含以下域:

    • used_memory : 由 Redis 分配器分配的内存总量,以字节(byte)为单位
    • used_memory_human : 以人类可读的格式返回 Redis 分配的内存总量
    • used_memory_rss : 从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 topps 等命令的输出一致。
    • used_memory_peak : Redis 的内存消耗峰值(以字节为单位)
    • used_memory_peak_human : 以人类可读的格式返回 Redis 的内存消耗峰值
    • used_memory_lua : Lua 引擎所使用的内存大小(以字节为单位)
    • mem_fragmentation_ratio : used_memory_rssused_memory 之间的比率
    • mem_allocator : 在编译时指定的, Redis 所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc 。
    在理想情况下, used_memory_rss 的值应该只比 used_memory 稍微高一点儿。
    rss > used ,且两者的值相差较大时,表示存在(内部或外部的)内存碎片。
    内存碎片的比率可以通过 mem_fragmentation_ratio 的值看出。
    used > rss 时,表示 Redis 的部分内存被操作系统换出到交换空间了,在这种情况下,操作可能会产生明显的延迟。

    Because Redis does not have control over how its allocations are mapped to memory pages, high used_memory_rss is often the result of a spike in memory usage.

    当 Redis 释放内存时,分配器可能会,也可能不会,将内存返还给操作系统。
    如果 Redis 释放了内存,却没有将内存返还给操作系统,那么 used_memory 的值可能和操作系统显示的 Redis 内存占用并不一致。
    查看 used_memory_peak 的值可以验证这种情况是否发生。
  • persistence : RDBAOF 的相关信息

  • stats : 一般统计信息

  • replication : 主/从复制信息

  • cpu : CPU 计算量统计信息

  • commandstats : Redis 命令统计信息

  • cluster : Redis 集群信息

  • keyspace : 数据库相关的统计信息

除上面给出的这些值以外,参数还可以是下面这两个:

  • all : 返回所有信息
  • default : 返回默认选择的信息

当不带参数直接调用 INFO 命令时,使用 default 作为默认参数。

不同版本的 Redis 可能对返回的一些域进行了增加或删减。

因此,一个健壮的客户端程序在对 INFO 命令的输出进行分析时,应该能够跳过不认识的域,并且妥善地处理丢失不见的域。

可用版本:
>= 1.0.0
时间复杂度:
O(1)
返回值:
具体请参见下面的测试代码。
redis> INFO
# Server
redis_version:2.5.9
redis_git_sha1:473f3090
redis_git_dirty:0
os:Linux 3.3.7-1-ARCH i686
arch_bits:32
multiplexing_api:epoll
gcc_version:4.7.0
process_id:8104
run_id:bc9e20c6f0aac67d0d396ab950940ae4d1479ad1
tcp_port:6379
uptime_in_seconds:7
uptime_in_days:0
lru_clock:1680564

# Clients
connected_clients:1
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0

# Memory
used_memory:439304
used_memory_human:429.01K
used_memory_rss:13897728
used_memory_peak:401776
used_memory_peak_human:392.36K
used_memory_lua:20480
mem_fragmentation_ratio:31.64
mem_allocator:jemalloc-3.0.0

# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1338011402
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:-1
rdb_current_bgsave_time_sec:-1
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1

# Stats
total_connections_received:1
total_commands_processed:0
instantaneous_ops_per_sec:0
rejected_connections:0
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:0

# Replication
role:master
connected_slaves:0

# CPU
used_cpu_sys:0.03
used_cpu_user:0.01
used_cpu_sys_children:0.00
used_cpu_user_children:0.00

Win10开发必备:Visual Studio 2015正式版下载

7月21日凌晨消息,面向大众用户的Visual Studio 2015集成开发工具正式版免费试用版已经推出。本文帮大家汇总一下简体中文社区版、专业版以及企业版在线安装版以及ISO离线安装镜像下载地址。

Visual Studio Community 2015简体中文版(社区版,针对个人免费):

在线安装 || ISO镜像

镜像SHA1:1044F9F4E0EA1304AFECF6780BF599F1DA248DF8

Visual Studio Professional 2015简体中文版(专业版):

在线安装 || ISO镜像

镜像SHA1:629E7154E2695F08A3C692C0B3F6CE19DF6D3A72

Visual Studio Enterprise 2015简体中文版(企业版):

在线安装 || ISO镜像

镜像SHA1:4FFA1EE3E2D3337D3EDAE550A3583ABE9C426BEF

更多版本点此下载

高效运维最佳实践(03):Redis集群技术及Codis实践

专栏介绍

“高效运维最佳实践”是InfoQ在2015年推出的精品专栏,由触控科技运维总监萧田国撰写,InfoQ总编辑崔康策划。

前言

诚如开篇文章所言,高效运维包括管理的专业化和技术的专业化。前两篇我们主要在说些管理相关的内容,本篇说一下技术专业化。希望读者朋友们能适应这个转换,谢谢。

互联网早在几年前就已进入Web 2.0时代,对后台支撑能力的要求,提高了几十倍甚至几百倍。在这个演化过程中,缓存系统扮演了举足轻重的角色。

运维进化到今天,已经不是重复造轮子的时代。所以,我们在架构优化和自动化运维中,可以尽可能地选用优秀的开源产品,而不是自己完全从头再来(各种技术geek除外)。

本文主要讨论Redis集群相关技术及新发展,关于Redis运维等内容,以后另开主题讨论。

本文重点推荐Codis——豌豆荚开源的Redis分布式中间件(该项目于4个月前在GitHub开源,目前star已超过2100)。其和Twemproxy相比,有诸多激动人心的新特性,并支持从Twemproxy无缝迁移至Codis。

本文主要目录如下,对Redis比较了解的朋友,可跳过前两部分,直接欣赏Codis相关内容。

1. Redis常见集群技术
1.1 客户端分片
1.2 代理分片
1.3 Redis Cluster
2. Twemproxy及不足之处
3. Codis实践
3.1 体系架构
3.2 性能对比测试
3.3 使用技巧、注意事项

好吧我们正式开始。

1. Redis常见集群技术

长期以来,Redis本身仅支持单实例,内存一般最多10~20GB。这无法支撑大型线上业务系统的需求。而且也造成资源的利用率过低——毕竟现在服务器内存动辄100~200GB。

为解决单机承载能力不足的问题,各大互联网企业纷纷出手,“自助式”地实现了集群机制。在这些非官方集群解决方案中,物理上把数据“分片”(sharding)存储在多个Redis实例,一般情况下,每一“片”是一个Redis实例。

包括官方近期推出的Redis Cluster,Redis集群有三种实现机制,分别介绍如下,希望对大家选型有所帮助。

1.1 客户端分片

这种方案将分片工作放在业务程序端,程序代码根据预先设置的路由规则,直接对多个Redis实例进行分布式访问。这样的好处是,不依赖于第三方分布式中间件,实现方法和代码都自己掌控,可随时调整,不用担心踩到坑。

这实际上是一种静态分片技术。Redis实例的增减,都得手工调整分片程序。基于此分片机制的开源产品,现在仍不多见。

这种分片机制的性能比代理式更好(少了一个中间分发环节)。但缺点是升级麻烦,对研发人员的个人依赖性强——需要有较强的程序开发能力做后盾。如果主力程序员离职,可能新的负责人,会选择重写一遍。

所以,这种方式下,可运维性较差。出现故障,定位和解决都得研发和运维配合着解决,故障时间变长。

这种方案,难以进行标准化运维,不太适合中小公司(除非有足够的DevOPS)。

1.2 代理分片

这种方案,将分片工作交给专门的代理程序来做。代理程序接收到来自业务程序的数据请求,根据路由规则,将这些请求分发给正确的Redis实例并返回给业务程序。

这种机制下,一般会选用第三方代理程序(而不是自己研发),因为后端有多个Redis实例,所以这类程序又称为分布式中间件。

这样的好处是,业务程序不用关心后端Redis实例,运维起来也方便。虽然会因此带来些性能损耗,但对于Redis这种内存读写型应用,相对而言是能容忍的。

这是我们推荐的集群实现方案。像基于该机制的开源产品Twemproxy,便是其中代表之一,应用非常广泛。

1.3 Redis Cluster

在这种机制下,没有中心节点(和代理模式的重要不同之处)。所以,一切开心和不开心的事情,都将基于此而展开。

Redis Cluster将所有Key映射到16384个Slot中,集群中每个Redis实例负责一部分,业务程序通过集成的Redis Cluster客户端进行操作。客户端可以向任一实例发出请求,如果所需数据不在该实例中,则该实例引导客户端自动去对应实例读写数据。

Redis Cluster的成员管理(节点名称、IP、端口、状态、角色)等,都通过节点之间两两通讯,定期交换并更新。

由此可见,这是一种非常“重”的方案。已经不是Redis单实例的“简单、可依赖”了。可能这也是延期多年之后,才近期发布的原因之一。

这令人想起一段历史。因为Memcache不支持持久化,所以有人写了一个Membase,后来改名叫Couchbase,说是支持Auto Rebalance,好几年了,至今都没多少家公司在使用。

这是个令人忧心忡忡的方案。为解决仲裁等集群管理的问题,Oracle RAC还会使用存储设备的一块空间。而Redis Cluster,是一种完全的去中心化……

本方案目前不推荐使用,从了解的情况来看,线上业务的实际应用也并不多见。

2. Twemproxy及不足之处

Twemproxy是一种代理分片机制,由Twitter开源。Twemproxy作为代理,可接受来自多个程序的访问,按照路由规则,转发给后台的各个Redis服务器,再原路返回。

这个方案顺理成章地解决了单个Redis实例承载能力的问题。当然,Twemproxy本身也是单点,需要用Keepalived做高可用方案。

我想很多人都应该感谢Twemproxy,这么些年来,应用范围最广、稳定性最高、最久经考验的分布式中间件,应该就是它了。只是,他还有诸多不方便之处。

Twemproxy最大的痛点在于,无法平滑地扩容/缩容。

这样导致运维同学非常痛苦:业务量突增,需增加Redis服务器;业务量萎缩,需要减少Redis服务器。但对Twemproxy而言,基本上都很难操作(那是一种锥心的、纠结的痛……)。

或者说,Twemproxy更加像服务器端静态sharding。有时为了规避业务量突增导致的扩容需求,甚至被迫新开一个基于Twemproxy的Redis集群。

Twemproxy另一个痛点是,运维不友好,甚至没有控制面板。

Codis刚好击中Twemproxy的这两大痛点,并且提供诸多其他令人激赏的特性。

3. Codis实践

Codis由豌豆荚于2014年11月开源,基于Go和C开发,是近期涌现的、国人开发的优秀开源软件之一。现已广泛用于豌豆荚的各种Redis业务场景(已得到豌豆荚@刘奇同学的确认,呵呵)。

从3个月的各种压力测试来看,稳定性符合高效运维的要求。性能更是改善很多,最初比Twemproxy慢20%;现在比Twemproxy快近100%(条件:多实例,一般Value长度)。

3.1 体系架构

Codis引入了Group的概念,每个Group包括1个Redis Master及至少1个Redis Slave,这是和Twemproxy的区别之一。这样做的好处是,如果当前Master有问题,则运维人员可通过Dashboard“自助式”切换到Slave,而不需要小心翼翼地修改程序配置文件。

为支持数据热迁移(Auto Rebalance),出品方修改了Redis Server源码,并称之为Codis Server。

Codis采用预先分片(Pre-Sharding)机制,事先规定好了,分成1024个slots(也就是说,最多能支持后端1024个Codis Server),这些路由信息保存在ZooKeeper中。

ZooKeeper还维护Codis Server Group信息,并提供分布式锁等服务。

3.2 性能对比测试

Codis目前仍被精益求精地改进中。其性能,从最初的比Twemproxy慢20%(虽然这对于内存型应用而言,并不明显),到现在远远超过Twemproxy性能(一定条件下)。

我们进行了长达3个月的测试。测试基于redis-benchmark,分别针对Codis和Twemproxy,测试Value长度从16B~10MB时的性能和稳定性,并进行多轮测试。

一共有4台物理服务器参与测试,其中一台分别部署codis和twemproxy,另外三台分别部署codis server和redis server,以形成两个集群。

从测试结果来看,就Set操作而言,在Value长度<888B时,Codis性能优越优于Twemproxy(这在一般业务的Value长度范围之内)。

就Get操作而言,Codis性能一直优于Twemproxy。

3.3 使用技巧、注意事项

Codis还有很多好玩的东东,从实际使用来看,有些地方也值得注意。

1)无缝迁移Twemproxy

出品方贴心地准备了Codis-port工具。通过它,可以实时地同步 Twemproxy 底下的 Redis 数据到你的 Codis 集群。同步完成后,只需修改一下程序配置文件,将 Twemproxy 的地址改成 Codis 的地址即可。是的,只需要做这么多。

2)支持Java程序的HA

Codis提供一个Java客户端,并称之为Jodis(名字很酷,是吧?)。这样,如果单个Codis Proxy宕掉,Jodis自动发现,并自动规避之,使得业务不受影响(真的很酷!)。

3)支持Pipeline

Pipeline使得客户端可以发出一批请求,并一次性获得这批请求的返回结果。这提升了Codis的想象空间。

从实际测试来看,在Value长度小于888B字节时,Set性能迅猛提升;

Get性能亦复如是。

4)Codis不负责主从同步

也就是说, Codis仅负责维护当前Redis Server列表,由运维人员自己去保证主从数据的一致性。

这是我最赞赏的地方之一。这样的好处是,没把Codis搞得那么重。也是我们敢于放手在线上环境中上线的原因之一。

5)对Codis的后续期待?

好吧,粗浅地说两个。希望Codis不要变得太重。另外,加pipeline参数后,Value长度如果较大,性能反而比Twemproxy要低一些,希望能有改善(我们多轮压测结果都如此)。

因篇幅有限,源码分析不在此展开。另外Codis源码、体系结构及FAQ,参见如下链接:https://github.com/wandoulabs/codis

PS:线上文档的可读性,也是相当值得称赞的地方。一句话:很走心,赞!

最后,Redis初学者请参考这个链接:http://www.gamecbg.com/bc/db/redis/13852.html,文字浅显易懂,而且比较全面。

本文得到Codis开发团队刘奇和黄东旭同学的大力协助,并得到Tim Yang老师等朋友们在内容把控方面的指导。本文共同作者为赵文华同学,他主要负责Codis及Twemproxy的对比测试。在此一并谢过。

关于作者

萧田国,男,硕士毕业于北京科技大学,ACMUG核心成员,目前为触控科技运维负责人。拥有十多年运维及团队管理经验。先后就职于联想集团(Oracle数据库主管)、搜狐畅游(数据库主管)、智明星通及世纪互联等。从1999年开始,折腾各种数据库如Oracle/MySQL/MS SQL Server/NoSQL等,兼任数据库培训讲师若干年。

曾经的云计算行业从业者,现在喜欢琢磨云计算及评测、云端数据库,及新技术在运维中的应用。主张管理学科和运维体系的融合、人性化运维管理,打造高效、专业运维团队。

近来有时参加一些大小技术会议,做做演讲嘉宾或主持人(有空找我来玩呀:)我的个人微信号:xiaotianguo。如需更多了解,请百度“萧田国”。

另外,我也有微信公众号叻,微信搜索“开心南瓜by萧田国”或扫描如下二维码,和我进行微信互动。公众号里将有我原创的各类技术和非技术文章,及我所喜欢的文章/帖子。一起来吧~


感谢崔康对本文的策划,丁晓昀对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们,并与我们的编辑和其他读者朋友交流。

谈谈REST与ASP.NET Web API

本节目录

 

Web API简介

REST

REST是“REpresentational State Transfer”的缩写,可以翻译成“表现状态转换”.

REST是一种软件架构风格,与技术无关,但是大部分基于REST风格的Web服务都是基于HTTP的

(虽然WCF在3.5以后支持REST,但是WCF太庞大了,Web API更适合做REST架构)

 

SOAP与REST

SOAP Web API采用RPC(面向方法Remote Procedure Call)风格,它采用面向功能的架构,所以在设计之初首先需要考虑的是提供怎样的功能。

RESTful Web API采用ROA(面向资源Resouce Oriented Architecture)架构,所以在设计之初首先需要考虑的是有哪些资源可供操作。

 

HTTP协议

HTTP采用简单的请求/响应模式的消息交换旨在实现针对某个Web资源的某种操作。

至于针对资源的操作类型,不外乎CRUD(Create、Retrieve、Update和Delete)而已。

一个HTTP请求除了利用URI标志目标资源之外,还需要通过HTTP方法指名针对资源的操作类型。

HTTP方法:包括GET(查)、POST(增)、PUT(改)、DELETE(删)、HEAD、OPTIONS、TRACE、CONNECTION和PATCH等

 

HTTP协议

1
2
3
4
5
6
7
8
9
GET http://neverc.cn/ HTTP/1.1
Host: neverc.cn
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.15 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Cookie:

第1行是HTTP的3个基本属性,method,uri,vesion

其他都是HTTP的请求报头header,http定义很多原生的header,也可以添加自定义header(实际就是键值对)

除了报头,一个HTTP请求还可以包括一个请求主体内容,可以是任意格式.

 

与HTTP请求一样,HTTP响应也是由报头和报文2部分组成.

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
Server: Microsoft-IIS/7.5
X-AspNetMvc-Version: 5.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
Date: Fri, 18 Sep 2015 05:39:50 GMT
Content-Length: 12003
<!DOCTYPE html>

第1行是vesion和statu(除了200 OK外,常见的有401 Not Authorized、404 Not Found)

第3行Content-Type表示媒体(或者叫资源/数据)类型.

  • text/html:HTML格式的文档。
  • text/xml(application/xml):XML格式的文本。
  • text/json(application/json): JSON格式的文本。
  • image/gif(image/jpeg、image/png):GIF(JPEG、PNG)格式的图片。
  • audio/mp4(audio/mpeg、audio/vnd.wave):MP4(MPEG、WAVE)格式的音频文件。
  • video/mp4(video/mpeg、video/quicktime):MP4(MPEG、QUICKTIME)格式的视频文件。

 

自我寄宿

建立项目

  • Model:一个类库项目,定义实体
  • WebApi:一个类库项目,定义API控制器(引用Model项目)
  • SelfHost:一个控制台项目,寄宿API服务(引用WebApi项目)

涉及到的引用的程序集

 

创建实体

在Model项目中,新建一个Contact类

1
2
3
4
5
public class Contact
{
    public string Id { get; set; }
    public string Name { get; set; }
}

 

创建控制器

在WebApi项目中,引用System.Web.Http类库并创建API控制器

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
public class ContactsController : ApiController
{
    #region Data
    static readonly List<Contact> contacts;
    static int counter = 2;
    static ContactsController()
    {
        contacts = new List<Contact>
        {
            new Contact{Id = "001",Name = "张三"},
            new Contact{Id = "002",Name = "李四"}
        };
    }
    #endregion
    public IEnumerable<Contact> Get(string id = null)
    {
        return from contact in contacts
               where contact.Id == id || string.IsNullOrEmpty(id)
               select contact;
    }
    public void Post(Contact contact)
    {
        //多线程并发处理
        Interlocked.Increment(ref counter);
        contact.Id = counter.ToString("D3");
        contacts.Add(contact);
    }
    public void Put(Contact contact)
    {
        contacts.Remove(contacts.First(c => c.Id == contact.Id));
        contacts.Add(contact);
    }
    public void Delete(string id)
    {
        contacts.Remove(contacts.First(c => c.Id == id));
    }
}

 

自我寄宿

在SelfHost中,引用System.Web.Http、System.Net.Http、System.Web.Http.SelfHost类库并实现寄宿

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static void Main(string[] args)
{
    //对于SelfHost来说,HttpController类型的解析在默认情况下只会针对加载到当前应用程序域中的程序集列表
    //通过手工加载,让该程序集加载到当前应用程序域中。
    Assembly.Load("WebApi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
    var configuration = new HttpSelfHostConfiguration("http://localhost/selfhost");
    using (var httpServer = new HttpSelfHostServer(configuration))
    {
        httpServer.Configuration.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional });
        httpServer.OpenAsync().Wait();
        Console.WriteLine("寄宿Web API服务成功");
        Console.Read();
    }
}

 

测试

运行SelfHost控制台,浏览器访问http://localhost/selfhost。

注意:(由于此处会注册http.sys,所以需要管理员身份运行VS)

 

 

IIS寄宿

使用IIS寄宿非常简单,只要注册好路由数据即可

 

建立项目

  • WebHost:一个空的Web项目(引用Model和WebApi项目)

 

注册路由

新建Global文件,注册HttpRoute

1
2
3
4
5
6
7
8
9
10
public class Global : HttpApplication
{
    protected void Application_Start(object sender, EventArgs e)
    {
        GlobalConfiguration.Configuration.Routes.MapHttpRoute(
          name: "DefaultApi",
          routeTemplate: "api/{controller}/{id}",
          defaults: new { id = RouteParameter.Optional });
    }
}

 

测试

运行WebHost项目,浏览器访问http://~/api/Contacts。

 

调用Web API

因为Web API是基于HTTP的,所以对于开发人员,就像普通请求网站数据一样

  • jQuery
  • MVVM/MVC框架的JS,AngularJS,Knockout.js
  • 后台可以使用HttpClient、WebClient、HttpWebRequest等

 

这里演示一个HttpClient完整的例子,对于异步有疑问,可阅读我的博客:[C#] 谈谈异步编程async await

 

新建一个控制台项目即可,实现Program类:

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
38
39
40
41
42
43
44
45
46
static HttpClient httpClient = new HttpClient();
        static void Main(string[] args)
        {
            //由于HttpClient类中的方法大部分为异步
            //Main方法不支持Async关键字
            //故新建一个方法,使其同步运行
            Process();
            Console.Read();
        }
        async static void Process()
        {
            //获取当前联系人列表
            ListContacts();
            //添加新的联系人
            var contact = new Contact { Name = "王五" };
            await httpClient.PostAsJsonAsync("http://localhost/selfhost/api/contacts", contact);
            Console.WriteLine("添加新联系人“王五”:");
            ListContacts();
            //修改现有的某个联系人
            var response = await httpClient.GetAsync("http://localhost/selfhost/api/contacts/001");
            contact = (await response.Content.ReadAsAsync<IEnumerable<Contact>>()).First();
            contact.Name = "赵六";
            await httpClient.PutAsJsonAsync("http://localhost/selfhost/api/contacts/001", contact);
            Console.WriteLine("修改联系人“001”信息:");
            ListContacts();
            //删除现有的某个联系人
            await httpClient.DeleteAsync("http://localhost/selfhost/api/contacts/002");
            Console.WriteLine("删除联系人“002”:");
            ListContacts();
        }
        async static void ListContacts()
        {
            var response = await httpClient.GetAsync("http://localhost/selfhost/api/contacts");
            IEnumerable<Contact> contacts = await response.Content.ReadAsAsync<IEnumerable<Contact>>();
            Console.WriteLine("当前联系人列表:");
            foreach (Contact contact in contacts)
            {
                Console.WriteLine("{0,-6}{1,-6}", contact.Id, contact.Name);
            }
            Console.WriteLine();
        }

 

Web API原理

Web API借用了MVC的设计,以Controller形式定义服务,Action代表具体的操作.

Web API借助于URL路由得到控制器,再根据路由对象,通过http方法找到对应的action.(实际上,如果根据url解析不到action的时候,才会通过http方法)

 

路由注册

1
2
3
4
5
config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

由于在模板中没有定义action,所以只能通过httpmethod来找action.(并且是根据方法前缀匹配即可)

 

通过浏览器api/Contacts查看的时候,会返回一个xml格式的数据.

实际上,webapi是先检查accept,从左到右,去匹配序列化器,如果没有匹配到则使用默认的json序列化器.

 

 

管道式设计

Web API也采用了管道式设计,这是一个不同于MVC的管道.虽然很多地方和MVC相似.

Route对象为HttpRoute

Handle对象为HttpControllerHandler(由于实现了IHttpAsyncHandler接口,所以默认走BeginProcessRequest异步方法)

 

本文地址:http://neverc.cnblogs.com/p/4603935.html

参考:http://www.cnblogs.com/artech/p/how-asp-net-web-api-works.html

Redis系列(五)-Opserver的监控

阅读目录:

  1. 基本介绍
  2. 使用配置
  3. 部署实例
  4. 面板属性

基本介绍

Opserver是Stack Exchange的一个开源监控系统,基于Net、MVC开发,所以Net程序员可以轻松基于它二次开发。它主要监控:

  • servers
  • SQL clusters/instances
  • redis
  • elastic search
  • exception logs
  • haproxy

Opserver提供详细的面板,用来快速展示被监控系统的总体情况。 下面Opserver的监控UI界面示例,非常详细:

使用配置

项目地址:https://github.com/opserver/Opserver

下载后用VS打开或IIS直接部署即可,下面是它的支持监控系统的view目录,结构比较清晰。

安全配置

Opserver系统本身后登陆验证,支持3种安全认证方式:

复制代码
<?xml version="1.0" encoding="utf-8"?>
<SecuritySettings provider="AD">
    <!-- 可选, 下面的网络可以不用验证直接访问 -->
    <InternalNetworks>
        <Network name="SE Internal" cidr="10.0.0.0/8" />
    </InternalNetworks>
</SecuritySettings>

<!-- 
每个人都是管理都可访问
<SecuritySettings provider="alladmin" />
-->
复制代码

如果使用活动目录验证,可以直接在web.config配置ViewGroups、AdminGroups,也可以单独在每个系统监控json配置文件里面添加ViewGroups、AdminGroups:

复制代码
"viewGroups": "*",
"adminGroups": "SysAdmins",
"user": "user",
"password": "pass",
"adminUser": "adminuser",
"adminPassword": "adminpass",
复制代码

监控配置

配置监控的地方在/Config/目录,Stack Exchange提供对应系统的配置示例,如图: 如果没有配置任何系统监控文件,浏览OpServer页面时,会报’No Configuration’的警告提示。 这里以Redis为例,监控配置如下:

复制代码
{
    "allServers": {
        "name": "All",
        "instances": [
              {
                "name": "本地master",
                "port": "6379"
            },
            {
                "name": "本地slave1",
                "port": "6380"
            },
            {
                "name": "本地master2",
                "port": "6382"
            }
        ]
          
    },
    "Servers": [
        { "name": "127.0.0.1" }
    ]
}
复制代码

部署实例

认证配置<SecuritySettings provider=”alladmin”>所有人都是管理员,打开浏览器访问,输入账号admin,密码admin:

可以看到有2组实例,其中6380是slave,6379是master,从图表上可以清晰看到层架关系。

实例列表:

点击单个Redis实例进去看到。

面板属性

面板展示的属性都是可以通过redis info命令获取到,opserver做了更清晰的展示。

Ops(/sec)  每秒处理量

memory(used)即used_memory_rss(used_memory)

used_memory_rss : 从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps等命令的输出一致。

used_memory_peak : Redis 的内存消耗峰值(以字节为单位)

used_memory : 由 Redis 分配器分配的内存总量,以字节(byte)为单位

 

Summary是总体概览部分。

Memory是内存使用情况,重要。

persistence 是RDB和AOF的状态。
keyspace key存储的情况,analyze进去可以查看详细分布。
stats  客户端命令的key命中率和处理量
clients 查看有哪个ip(或机器名)过来的连接数多,很方便的定位到那台应用端机器长时间没有释放连接,重要。

slow command log 服务端接受的命令日志。

 

 

Opserver 算是个比较轻量级的监控系统,部署修改都非常方便,比如增加连接数或者内存报警功能。