本文目录一览:
【Minio】基于AWS S3协议搭建个人云存储服务
在2007年,GlusterFS演变为大型分布式存储方案后,任何配备合适硬件的公司,单位都可以利用个做分布式的流媒体,数据分析。在2011年,Red Hat收购了GlusterFS.
Minio是GlusterFS创始人之一Anand Babu Periasamy发布新的开源项目。Minio兼容Amason的S3分布式对象存储项目,采用Golang实现,客户端支持Java,Python,Javacript, Golang语言。
Minio 提供对象存储服务,兼容了 AWS S3 存储协议,用于非结构化的数据存。非结构化对象,比如图像,音、视频,日志文件,备份镜像…等等管理不方便,不定长,大小变化大、类型多,云端的访问复杂,minio就是来解决这种场景的。非结构化的文件从数KB到5TB都能很好的支持。开源并且用 Go 语言开发,有web操作界面,我们可以用它来搭建兼容S3协议的存储云服务。
Minio可以做为云存储的解决方案用来保存海量的图片,视频,文档。由于采用Golang实现,服务端可以工作在Windows,Linux, OS X和FreeBSD上。配置简单,基本是复制可执行程序,单行命令可以运行起来。
官网:
那么,如何自己搭建一个私有的S3存储云服务呢?
官方的话是推荐用Docker来搞,我们先用普通的二进制文件来直接解决了!
######################################################################################
# mkdir /data/aws_s3
# wget
# mv minio /usr/local/bin/
# chmod 755 /usr/local/bin/minio
# minio server /data/aws_s3
#############################################################
Created minio configuration file successfully at /root/.minio
Endpoint:
AccessKey: U3XLU4IMXY3IDKHU268F
SecretKey: /6NCL6HGacviaCgRqr2qLbVOjhkkJdRpV7wz0JJD
Region: us-east-1
SQS ARNs:
Browser Access:
Command-line Access:
################################################################
$ mc config host add myminio U3XLU4IMXY3IDKHU268F /6NCL6HGacviaCgRqr2qLbVOjhkkJdRpV7wz0JJD
Object API (Amazon S3 compatible):
Go:
Java:
Python:
JavaScript:
Drive Capacity: 8.3 GiB Free, 9.1 GiB Total
##############################################################
我们就成功启动了minio的s3服务,默认端口9000,可以通过网页访问:
注意 :第一次打开时候需要填写AccessKey和SecretKey才能进入,我们上面启动服务的时候,已经看到屏幕有输出:
AccessKey: U3XLU4IMXY3IDKHU268F
SecretKey:6NCL6HGacviaCgRqr2qLbVOjhkkJdRpV7wz0JJD
把这两个Key填入,就能顺利进入,进入后展开页面如下:
这就是我们的S3云存储的管理页面了,看着是不是和七牛什么的提供云存储的产品页面挺像的,大家都是基于S3协议开发的!
上传个文件试试:
点击右下角的红色小加号按钮,弹出的菜单选择”create bucket”则会创建一个桶,输入名字”test”
点击刚才那个红色小加号按钮,这次选择”Upload file”上传文件,给这个桶上传了一个叫login.txt的文本文档
此时页面如下:
至此我们可以看到文件已经上传,要访问这个文件,可以点击文件右侧的三个点的按钮,选择分享就可以得到一个外链,在浏览器中访问这个外链就可以直接访问文件。
那么文件到底被存到哪里去了呢,我们启动命令中其实指定了工作路径/data/aws_s3/,所以到服务器这个目录下看看:
# ls /data/aws_s3/
test
# ls /data/aws_s3/test/
login.txt
桶名称test是一个目录,其下就有上传的login.txt文件。
如果想指定ip和端口,可以这样写:
# minio server /data/aws_s3 --address=0.0.0.0:9000
如果想让服务在后台运行:
# nohup minio server /data/aws_s3 --address=0.0.0.0:443
[1] 19882
// nohup: 忽略输入并把输出追加到启动命令的当前目录下的 "nohup.out"文件
minio可以用来搭建分布式存储系统 GlusterFS,这样就成了真正的云存储了,有时间再研究下把它从现在的单机测试,变成一朵存储云!
minio官网:
minio官方文档:
minio github主页:
如何实现支持数亿用户的长连消息系统
此文是根据周洋在【高可用架构群】中的分享内容整理而成,转发请注明出处。 周洋,360手机助手技术经理及架构师,负责360长连接消息系统,360手机助手架构的开发与维护。 不知道咱们群名什么时候改为“Python高可用架构群”了,所以不得不说,很荣幸能在接下来的一个小时里在Python群里讨论golang....360消息系统介绍 360消息系统更确切的说是长连接push系统,目前服务于360内部多个产品,开发平台数千款app,也支持部分聊天业务场景,单通道多app复用,支持上行数据,提供接入方不同粒度的上行数据和用户状态回调服务。 目前整个系统按不同业务分成9个功能完整的集群,部署在多个idc上(每个集群覆盖不同的idc),实时在线数亿量级。通常情况下,pc,手机,甚至是智能硬件上的360产品的push消息,基本上是从我们系统发出的。 关于push系统对比与性能指标的讨论 很多同行比较关心go语言在实现push系统上的性能问题,单机性能究竟如何,能否和其他语言实现的类似系统做对比么?甚至问如果是创业,第三方云推送平台,推荐哪个? 其实各大厂都有类似的push系统,市场上也有类似功能的云服务。包括我们公司早期也有erlang,nodejs实现的类似系统,也一度被公司要求做类似的对比测试。我感觉在讨论对比数据的时候,很难保证大家环境和需求的统一,我只能说下我这里的体会,数据是有的,但这个数据前面估计会有很多定语~ 第一个重要指标:单机的连接数指标 做过长连接的同行,应该有体会,如果在稳定连接情况下,连接数这个指标,在没有网络吞吐情况下对比,其实意义往往不大,维持连接消耗cpu资源很小,每条连接tcp协议栈会占约4k的内存开销,系统参数调整后,我们单机测试数据,最高也是可以达到单实例300w长连接。但做更高的测试,我个人感觉意义不大。 因为实际网络环境下,单实例300w长连接,从理论上算压力就很大:实际弱网络环境下,移动客户端的断线率很高,假设每秒有1000分之一的用户断线重连。300w长连接,每秒新建连接达到3w,这同时连入的3w用户,要进行注册,加载离线存储等对内rpc调用,另外300w长连接的用户心跳需要维持,假设心跳300s一次,心跳包每秒需要1w tps。单播和多播数据的转发,广播数据的转发,本身也要响应内部的rpc调用,300w长连接情况下,gc带来的压力,内部接口的响应延迟能否稳定保障。这些集中在一个实例中,可用性是一个挑战。所以线上单实例不会hold很高的长连接,实际情况也要根据接入客户端网络状况来决定。 第二个重要指标:消息系统的内存使用量指标 这一点上,使用go语言情况下,由于协程的原因,会有一部分额外开销。但是要做两个推送系统的对比,也有些需要确定问题。比如系统从设计上是否需要全双工(即读写是否需要同时进行)如果半双工,理论上对一个用户的连接只需要使用一个协程即可(这种情况下,对用户的断线检测可能会有延时),如果是全双工,那读/写各一个协程。两种场景内存开销是有区别的。 另外测试数据的大小往往决定我们对连接上设置的读写buffer是多大,是全局复用的,还是每个连接上独享的,还是动态申请的。另外是否全双工也决定buffer怎么开。不同的策略,可能在不同情况的测试中表现不一样。 第三个重要指标:每秒消息下发量 这一点上,也要看我们对消息到达的QoS级别(回复ack策略区别),另外看架构策略,每种策略有其更适用的场景,是纯粹推?还是推拉结合?甚至是否开启了消息日志?日志库的实现机制、以及缓冲开多大?flush策略……这些都影响整个系统的吞吐量。 另外为了HA,增加了内部通信成本,为了避免一些小概率事件,提供闪断补偿策略,这些都要考虑进去。如果所有的都去掉,那就是比较基础库的性能了。 所以我只能给出大概数据,24核,64G的服务器上,在QoS为message at least,纯粹推,消息体256B~1kB情况下,单个实例100w实际用户(200w+)协程,峰值可以达到2~5w的QPS...内存可以稳定在25G左右,gc时间在200~800ms左右(还有优化空间)。 我们正常线上单实例用户控制在80w以内,单机最多两个实例。事实上,整个系统在推送的需求上,对高峰的输出不是提速,往往是进行限速,以防push系统瞬时的高吞吐量,转化成对接入方业务服务器的ddos攻击所以对于性能上,我感觉大家可以放心使用,至少在我们这个量级上,经受过考验,go1.5到来后,确实有之前投资又增值了的感觉。 消息系统架构介绍 下面是对消息系统的大概介绍,之前一些同学可能在gopher china上可以看到分享,这里简单讲解下架构和各个组件功能,额外补充一些当时遗漏的信息: 架构图如下,所有的service都 written by golang.几个大概重要组件介绍如下: dispatcher service根据客户端请求信息,将应网络和区域的长连接服务器的,一组IP传送给客户端。客户端根据返回的IP,建立长连接,连接Room service. room Service,长连接网关,hold用户连接,并将用户注册进register service,本身也做一些接入安全策略、白名单、IP限制等。 register service是我们全局session存储组件,存储和索引用户的相关信息,以供获取和查询。 coordinator service用来转发用户的上行数据,包括接入方订阅的用户状态信息的回调,另外做需要协调各个组件的异步操作,比如kick用户操作,需要从register拿出其他用户做异步操作. saver service是存储访问层,承担了对redis和mysql的操作,另外也提供部分业务逻辑相关的内存缓存,比如广播信息的加载可以在saver中进行缓存。另外一些策略,比如客户端sdk由于被恶意或者意外修改,每次加载了消息,不回复ack,那服务端就不会删除消息,消息就会被反复加载,形成死循环,可以通过在saver中做策略和判断。(客户端总是不可信的)。 center service提供给接入方的内部api服务器,比如单播或者广播接口,状态查询接口等一系列api,包括运维和管理的api。 举两个常见例子,了解工作机制:比如发一条单播给一个用户,center先请求Register获取这个用户之前注册的连接通道标识、room实例地址,通过room service下发给长连接 Center Service比较重的工作如全网广播,需要把所有的任务分解成一系列的子任务,分发给所有center,然后在所有的子任务里,分别获取在线和离线的所有用户,再批量推到Room Service。通常整个集群在那一瞬间压力很大。 deployd/agent service用于部署管理各个进程,收集各组件的状态和信息,zookeeper和keeper用于整个系统的配置文件管理和简单调度 关于推送的服务端架构 常见的推送模型有长轮训拉取,服务端直接推送(360消息系统目前主要是这种),推拉结合(推送只发通知,推送后根据通知去拉取消息). 拉取的方式不说了,现在并不常用了,早期很多是nginx+lua+redis,长轮训,主要问题是开销比较大,时效性也不好,能做的优化策略不多。 直接推送的系统,目前就是360消息系统这种,消息类型是消耗型的,并且对于同一个用户并不允许重复消耗,如果需要多终端重复消耗,需要抽象成不同用户。 推的好处是实时性好,开销小,直接将消息下发给客户端,不需要客户端走从接入层到存储层主动拉取. 但纯推送模型,有个很大问题,由于系统是异步的,他的时序性无法精确保证。这对于push需求来说是够用的,但如果复用推送系统做im类型通信,可能并不合适。 对于严格要求时序性,消息可以重复消耗的系统,目前也都是走推拉结合的模型,就是只使用我们的推送系统发通知,并附带id等给客户端做拉取的判断策略,客户端根据推送的key,主动从业务服务器拉取消息。并且当主从同步延迟的时候,跟进推送的key做延迟拉取策略。同时也可以通过消息本身的QoS,做纯粹的推送策略,比如一些“正在打字的”低优先级消息,不需要主动拉取了,通过推送直接消耗掉。 哪些因素决定推送系统的效果? 首先是sdk的完善程度,sdk策略和细节完善度,往往决定了弱网络环境下最终推送质量. SDK选路策略,最基本的一些策略如下:有些开源服务可能会针对用户hash一个该接入区域的固定ip,实际上在国内环境下不可行,最好分配器(dispatcher)是返回散列的一组,而且端口也要参开,必要时候,客户端告知是retry多组都连不上,返回不同idc的服务器。因为我们会经常检测到一些case,同一地区的不同用户,可能对同一idc内的不同ip连通性都不一样,也出现过同一ip不同端口连通性不同,所以用户的选路策略一定要灵活,策略要足够完善.另外在选路过程中,客户端要对不同网络情况下的长连接ip做缓存,当网络环境切换时候(wifi、2G、3G),重新请求分配器,缓存不同网络环境的长连接ip。 客户端对于数据心跳和读写超时设置,完善断线检测重连机制 针对不同网络环境,或者客户端本身消息的活跃程度,心跳要自适应的进行调整并与服务端协商,来保证链路的连通性。并且在弱网络环境下,除了网络切换(wifi切3G)或者读写出错情况,什么时候重新建立链路也是一个问题。客户端发出的ping包,不同网络下,多久没有得到响应,认为网络出现问题,重新建立链路需要有个权衡。另外对于不同网络环境下,读取不同的消息长度,也要有不同的容忍时间,不能一刀切。好的心跳和读写超时设置,可以让客户端最快的检测到网络问题,重新建立链路,同时在网络抖动情况下也能完成大数据传输。 结合服务端做策略 另外系统可能结合服务端做一些特殊的策略,比如我们在选路时候,我们会将同一个用户尽量映射到同一个room service实例上。断线时,客户端尽量对上次连接成功的地址进行重试。主要是方便服务端做闪断情况下策略,会暂存用户闪断时实例上的信息,重新连入的 时候,做单实例内的迁移,减少延时与加载开销. 客户端保活策略 很多创业公司愿意重新搭建一套push系统,确实不难实现,其实在协议完备情况下(最简单就是客户端不回ack不清数据),服务端会保证消息是不丢的。但问题是为什么在消息有效期内,到达率上不去?往往因为自己app的push service存活能力不高。选用云平台或者大厂的,往往sdk会做一些保活策略,比如和其他app共生,互相唤醒,这也是云平台的push service更有保障原因。我相信很多云平台旗下的sdk,多个使用同样sdk的app,为了实现服务存活,是可以互相唤醒和保证活跃的。另外现在push sdk本身是单连接,多app复用的,这为sdk实现,增加了新的挑战。 综上,对我来说,选择推送平台,优先会考虑客户端sdk的完善程度。对于服务端,选择条件稍微简单,要求部署接入点(IDC)越要多,配合精细的选路策略,效果越有保证,至于想知道哪些云服务有多少点,这个群里来自各地的小伙伴们,可以合伙测测。 go语言开发问题与解决方案 下面讲下,go开发过程中遇到挑战和优化策略,给大家看下当年的一张图,在第一版优化方案上线前一天截图~可以看到,内存最高占用69G,GC时间单实例最高时候高达3~6s.这种情况下,试想一次悲剧的请求,经过了几个正在执行gc的组件,后果必然是超时... gc照成的接入方重试,又加重了系统的负担。遇到这种情况当时整个系统最差情况每隔2,3天就需要重启一次~ 当时出现问题,现在总结起来,大概以下几点 1.散落在协程里的I/O,Buffer和对象不复用。 当时(12年)由于对go的gc效率理解有限,比较奔放,程序里大量short live的协程,对内通信的很多io操作,由于不想阻塞主循环逻辑或者需要及时响应的逻辑,通过单独go协程来实现异步。这回会gc带来很多负担。 针对这个问题,应尽量控制协程创建,对于长连接这种应用,本身已经有几百万并发协程情况下,很多情况没必要在各个并发协程内部做异步io,因为程序的并行度是有限,理论上做协程内做阻塞操作是没问题。 如果有些需要异步执行,比如如果不异步执行,影响对用户心跳或者等待response无法响应,最好通过一个任务池,和一组常驻协程,来消耗,处理结果,通过channel再传回调用方。使用任务池还有额外的好处,可以对请求进行打包处理,提高吞吐量,并且可以加入控量策略. 2.网络环境不好引起激增 go协程相比较以往高并发程序,如果做不好流控,会引起协程数量激增。早期的时候也会发现,时不时有部分主机内存会远远大于其他服务器,但发现时候,所有主要profiling参数都正常了。 后来发现,通信较多系统中,网络抖动阻塞是不可免的(即使是内网),对外不停accept接受新请求,但执行过程中,由于对内通信阻塞,大量协程被 创建,业务协程等待通信结果没有释放,往往瞬时会迎来协程暴涨。但这些内存在系统稳定后,virt和res都并没能彻底释放,下降后,维持高位。 处理这种情况,需要增加一些流控策略,流控策略可以选择在rpc库来做,或者上面说的任务池来做,其实我感觉放在任务池里做更合理些,毕竟rpc通信库可以做读写数据的限流,但它并不清楚具体的限流策略,到底是重试还是日志还是缓存到指定队列。任务池本身就是业务逻辑相关的,它清楚针对不同的接口需要的流控限制策略。 3.低效和开销大的rpc框架 早期rpc通信框架比较简单,对内通信时候使用的也是短连接。这本来短连接开销和性能瓶颈超出我们预期,短连接io效率是低一些,但端口资源够,本身吞吐可以满足需要,用是没问题的,很多分层的系统,也有http短连接对内进行请求的 但早期go版本,这样写程序,在一定量级情况,是支撑不住的。短连接大量临时对象和临时buffer创建,在本已经百万协程的程序中,是无法承受的。所以后续我们对我们的rpc框架作了两次调整。 第二版的rpc框架,使用了连接池,通过长连接对内进行通信(复用的资源包括client和server的:编解码Buffer、Request/response),大大改善了性能。 但这种在一次request和response还是占用连接的,如果网络状况ok情况下,这不是问题,足够满足需要了,但试想一个room实例要与后面的数百个的register,coordinator,saver,center,keeper实例进行通信,需要建立大量的常驻连接,每个目标机几十个连接,也有数千个连接被占用。 非持续抖动时候(持续逗开多少无解),或者有延迟较高的请求时候,如果针对目标ip连接开少了,会有瞬时大量请求阻塞,连接无法得到充分利用。第三版增加了Pipeline操作,Pipeline会带来一些额外的开销,利用tcp的全双特性,以尽量少的连接完成对各个服务集群的rpc调用。 4.Gc时间过长 Go的Gc仍旧在持续改善中,大量对象和buffer创建,仍旧会给gc带来很大负担,尤其一个占用了25G左右的程序。之前go team的大咖邮件也告知我们,未来会让使用协程的成本更低,理论上不需要在应用层做更多的策略来缓解gc. 改善方式,一种是多实例的拆分,如果公司没有端口限制,可以很快部署大量实例,减少gc时长,最直接方法。不过对于360来说,外网通常只能使用80和433。因此常规上只能开启两个实例。当然很多人给我建议能否使用SO_REUSEPORT,不过我们内核版本确实比较低,并没有实践过。 另外能否模仿nginx,fork多个进程监控同样端口,至少我们目前没有这样做,主要对于我们目前进程管理上,还是独立的运行的,对外监听不同端口程序,还有配套的内部通信和管理端口,实例管理和升级上要做调整。 解决gc的另两个手段,是内存池和对象池,不过最好做仔细评估和测试,内存池、对象池使用,也需要对于代码可读性与整体效率进行权衡。 这种程序一定情况下会降低并行度,因为用池内资源一定要加互斥锁或者原子操作做CAS,通常原子操作实测要更快一些。CAS可以理解为可操作的更细行为粒度的锁(可以做更多CAS策略,放弃运行,防止忙等)。这种方式带来的问题是,程序的可读性会越来越像C语言,每次要malloc,各地方用完后要free,对于对象池free之前要reset,我曾经在应用层尝试做了一个分层次结构的“无锁队列”上图左边的数组实际上是一个列表,这个列表按大小将内存分块,然后使用atomic操作进行CAS。但实际要看测试数据了,池技术可以明显减少临时对象和内存的申请和释放,gc时间会减少,但加锁带来的并行度的降低,是否能给一段时间内的整体吞吐量带来提升,要做测试和权衡… 在我们消息系统,实际上后续去除了部分这种黑科技,试想在百万个协程里面做自旋操作申请复用的buffer和对象,开销会很大,尤其在协程对线程多对多模型情况下,更依赖于golang本身调度策略,除非我对池增加更多的策略处理,减少忙等,感觉是在把runtime做的事情,在应用层非常不优雅的实现。普遍使用开销理论就大于收益。 但对于rpc库或者codec库,任务池内部,这些开定量协程,集中处理数据的区域,可以尝试改造~ 对于有些固定对象复用,比如固定的心跳包什么的,可以考虑使用全局一些对象,进行复用,针对应用层数据,具体设计对象池,在部分环节去复用,可能比这种无差别的设计一个通用池更能进行效果评估. 消息系统的运维及测试 下面介绍消息系统的架构迭代和一些迭代经验,由于之前在其他地方有过分享,后面的会给出相关链接,下面实际做个简单介绍,感兴趣可以去链接里面看 架构迭代~根据业务和集群的拆分,能解决部分灰度部署上线测试,减少点对点通信和广播通信不同产品的相互影响,针对特定的功能做独立的优化. 消息系统架构和集群拆分,最基本的是拆分多实例,其次是按照业务类型对资源占用情况分类,按用户接入网络和对idc布点要求分类(目前没有条件,所有的产品都部署到全部idc)系统的测试go语言在并发测试上有独特优势。对于压力测试,目前主要针对指定的服务器,选定线上空闲的服务器做长连接压测。然后结合可视化,分析压测过程中的系统状态。但压测早期用的比较多,但实现的统计报表功能和我理想有一定差距。我觉得最近出的golang开源产品都符合这种场景,go写网络并发程序给大家带来的便利,让大家把以往为了降低复杂度,拆解或者分层协作的组件,又组合在了一起。 QA Q1:协议栈大小,超时时间定制原则? 移动网络下超时时间按产品需求通常2g,3G情况下是5分钟,wifi情况下5~8分钟。但对于个别场景,要求响应非常迅速的场景,如果连接idle超过1分钟,都会有ping,pong,来校验是否断线检测,尽快做到重新连接。 Q2:消息是否持久化? 消息持久化,通常是先存后发,存储用的redis,但落地用的mysql。mysql只做故障恢复使用。 Q3:消息风暴怎么解决的? 如果是发送情况下,普通产品是不需要限速的,对于较大产品是有发送队列做控速度,按人数,按秒进行控速度发放,发送成功再发送下一条。 Q4:golang的工具链支持怎么样?我自己写过一些小程序千把行之内,确实很不错,但不知道代码量上去之后,配套的debug工具和profiling工具如何,我看上边有分享说golang自带的profiling工具还不错,那debug呢怎么样呢,官方一直没有出debug工具,gdb支持也不完善,不知你们用的什么? 是这样的,我们正常就是println,我感觉基本上可以定位我所有问题,但也不排除由于并行性通过println无法复现的问题,目前来看只能靠经验了。只要常见并发尝试,经过分析是可以找到的。go很快会推出调试工具的~ Q5:协议栈是基于tcp吗? 是否有协议拓展功能?协议栈是tcp,整个系统tcp长连接,没有考虑扩展其功能~如果有好的经验,可以分享~ Q6:问个问题,这个系统是接收上行数据的吧,系统接收上行数据后是转发给相应系统做处理么,是怎么转发呢,如果需要给客户端返回调用结果又是怎么处理呢? 系统上行数据是根据协议头进行转发,协议头里面标记了产品和转发类型,在coordinator里面跟进产品和转发类型,回调用户,如果用户需要阻塞等待回复才能后续操作,那通过再发送消息,路由回用户。因为整个系统是全异步的。 Q7:问个pushsdk的问题。pushsdk的单连接,多app复用方式,这样的情况下以下几个问题是如何解决的:1)系统流量统计会把所有流量都算到启动连接的应用吧?而启动应用的连接是不固定的吧?2)同一个pushsdk在不同的应用中的版本号可能不一样,这样暴露出来的接口可能有版本问题,如果用单连接模式怎么解决? 流量只能算在启动的app上了,但一般这种安装率很高的app承担可能性大,常用app本身被检测和杀死可能性较少,另外消息下发量是有严格控制 的。整体上用户还是省电和省流量的。我们pushsdk尽量向上兼容,出于这个目的,push sdk本身做的工作非常有限,抽象出来一些常见的功能,纯推的系统,客户端策略目前做的很少,也有这个原因。 Q8:生产系统的profiling是一直打开的么? 不是一直打开,每个集群都有采样,但需要开启哪个可以后台控制。这个profling是通过接口调用。 Q9:面前系统中的消息消费者可不可以分组?类似于Kafka。 客户端可以订阅不同产品的消息,接受不同的分组。接入的时候进行bind或者unbind操作 Q10:为什么放弃erlang,而选择go,有什么特别原因吗?我们现在用的erlang? erlang没有问题,原因是我们上线后,其他团队才做出来,经过qa一个部门对比测试,在没有显著性能提升下,选择继续使用go版本的push,作为公司基础服务。 Q11:流控问题有排查过网卡配置导致的idle问题吗? 流控是业务级别的流控,我们上线前对于内网的极限通信量做了测试,后续将请求在rpc库内,控制在小于内部通信开销的上限以下.在到达上限前作流控。 Q12:服务的协调调度为什么选择zk有考虑过raft实现吗?golang的raft实现很多啊,比如Consul和ectd之类的。 3年前,还没有后两者或者后两者没听过应该。zk当时公司内部成熟方案,不过目前来看,我们不准备用zk作结合系统的定制开发,准备用自己写的keeper代替zk,完成配置文件自动转数据结构,数据结构自动同步指定进程,同时里面可以完成很多自定义的发现和控制策略,客户端包含keeper的sdk就可以实现以上的所有监控数据,profling数据收集,配置文件更新,启动关闭等回调。完全抽象成语keeper通信sdk,keeper之间考虑用raft。 Q13:负载策略是否同时在服务侧与CLIENT侧同时做的 (DISPATCHER 会返回一组IP)?另外,ROOM SERVER/REGISTER SERVER连接状态的一致性可用性如何保证? 服务侧保活有无特别关注的地方? 安全性方面是基于TLS再加上应用层加密? 会在server端做,比如重启操作前,会下发指令类型消息,让客户端进行主动行为。部分消息使用了加密策略,自定义的rsa+des,另外满足我们安全公司的需要,也定制开发很多安全加密策略。一致性是通过冷备解决的,早期考虑双写,但实时状态双写同步代价太高而且容易有脏数据,比如register挂了,调用所有room,通过重新刷入指定register来解决。 Q14:这个keeper有开源打算吗? 还在写,如果没耦合我们系统太多功能,一定会开源的,主要这意味着,我们所有的bind在sdk的库也需要开源~ Q15:比较好奇lisence是哪个如果开源?
nsq介绍和使用
最近一直在寻找一个高性能,高可用的消息队列做内部服务之间的通讯。一开始想到用zeromq,但在查找资料的过程中,意外的发现了Nsq这个由golang开发的消息队列,毕竟是golang原汁原味的东西,功能齐全,关键是性能还不错。其中支持动态拓展,消除单点故障等特性, 都可以很好的满足我的需求
下面上一张Nsq与其他mq的对比图,看上去的确强大。下面简单记录一下Nsq的使用方法
图片来自golang2017开发者大会
在使用Nsq服务之前,还是有必要了解一下Nsq的几个核心组件
整个Nsq服务包含三个主要部分
先看看官方的原话是怎么说:
nsqlookupd是守护进程负责管理拓扑信息。客户端通过查询 nsqlookupd 来发现指定话题(topic)的生产者,并且 nsqd 节点广播话题(topic)和通道(channel)信息
简单的说nsqlookupd就是中心管理服务,它使用tcp(默认端口4160)管理nsqd服务,使用http(默认端口4161)管理nsqadmin服务。同时为客户端提供查询功能
总的来说,nsqlookupd具有以下功能或特性
官方原话:是一套 WEB UI,用来汇集集群的实时统计,并执行不同的管理任务
总的来说,nsqadmin具有以下功能或特性
nsqadmin默认的访问地址是
官方原话:nsqd 是一个守护进程,负责接收,排队,投递消息给客户端
简单的说,真正干活的就是这个服务,它主要负责message的收发,队列的维护。nsqd会默认监听一个tcp端口(4150)和一个http端口(4151)以及一个可选的https端口
总的来说,nsqd 具有以下功能或特性
这是官方的图,第一个channel(meteics)因为有多个消费者,所以触发了负载均衡机制。后面两个channel由于没有消费者,所有的message均会被缓存在相应的队列里,直到消费者出现
这里想到一个问题是,如果一个channel只有生产者不停的在投递message,会不会导致服务器资源被耗尽?也许nsqd内部做了相应处理,但还是要避免这种情况的出现
了解nsqlookupd,nsqd与客户端中消费者和生产者的关系
消费者有两种方式与nsqd建立连接
生产者必须直连nsqd去投递message(网上说,可以连接到nsqlookupd,让nsqlookupd自动选择一个nsqd去完成投递,但是我用Producer的tcp是连不上nsqlookupd的,不知道http可不可以…),
这里有一个问题就是如果生产者所连接的nsqd炸了,那么message就会投递失败,所以在客户端必须自己实现相应的备用方案
执行完后检查godep是否已经安装在bin目录下,一般都会自动安装,如果没有,用go install手动安装下
如果安装成功,bin目录里就会出现一大堆nsq_…开头的可执行文件
nsqd是一个独立的服务,启动一个nsqd就可以完成message的收发,启动一个单机的nsqd,很简单
客户端可以使用http,也可以使用tcp,这里我使用是官方的go-nsq包做客户端,使用tcp进行message的收发
//Nsq发送测试
//Nsq接收测试
golang 协程什么时候切换
应puppet大拿刘宇的邀请,我去西山居运维团队做了一个简短分享,谈谈为什么我要将我们的项目从python转向go。
坦白的讲,在一帮python用户面前讲为什么放弃python转而用go其实是一件压力蛮大的事情,语言之争就跟vim和emacs之争一样,是一个永恒的无解话题,稍微不注意就可能导致粉丝强烈地反击。所以我只会从我们项目实际情况出发,来讲讲为什么我最终选择了go。
为什么放弃python
首先,我其实得说说为什么我们会选择python。在我加入企业快盘团队之前,整个项目包括更早的金山快盘都是采用python进行开发的。至于为什么这么选择,当时的架构师葱头告诉我,主要是因为python上手简单,开发迅速。对于团队里面大部分完全没服务端开发经验的同学来说,python真的是一个很好的选择。
python的简单高效,我是深有体会的。当时私有云项目也就几个程序员,但是我们要服务多家大型企业,进行定制化的开发,多亏了python,我们才能快速出活。后来企业快盘挂掉之后,我们启动轻办公项目,自然也使用python进行了原始版本的构建。
python虽然很强大,但我们在使用的时候也碰到了一些问题,主要由如下几个方面:
动态语言
python是一门动态强类型语言。但是,仍然可能出现int + string这样的运行时错误,因为对于一个变量,在写代码的时候,我们有时候很容易就忘记这个变量到底是啥类型的了。
在python里面,可以允许同名函数的出现,后一个函数会覆盖前一个函数,有一次我们系统一个很严重的错误就是因为这个导致的。
上面说到的这些,静态语言在编译的时候就能帮我们检测出来,而不需要等到运行时出问题才知道。虽然我们有很完善的测试用例,但总有case遗漏的情况。所以每次出现运行时错误,我心里都想着如果能在编译的时候就发现该多好。
性能
其实这个一直是很多人吐槽python的地方,但python有它适合干的事情,硬是要用python进行一些高性能模块的开发,那也有点难为它了。
python的GIL导致无法真正的多线程,大家可能会说我用多进程不就完了。但如果一些计算需要涉及到多进程交互,进程之间的通讯开销也是不得不考虑的。
无状态的分布式处理使用多进程很方便,譬如处理http请求,我们就是在nginx后面挂载了200多个django server来处理http的,但这么多个进程自然导致整体机器负载偏高。
但即使我们使用了多个django进程来处理http请求,对于一些超大量请求,python仍然处理不过来。所以我们使用openresty,将高频次的http请求使用lua来实现。可这样又导致使用两种开发语言,而且一些逻辑还得写两份不同的代码。
同步网络模型
django的网络是同步阻塞的,也就是说,如果我们需要访问外部的一个服务,在等待结果返回这段时间,django不能处理任何其他的逻辑(当然,多线程的除外)。如果访问外部服务需要很长时间,那就意味着我们的整个服务几乎在很长一段时间完全不可用。
为了解决这个问题,我们只能不断的多开django进程,同时需要保证所有服务都能快速的处理响应,但想想这其实是一件很不靠谱的事情。
异步网络模型
tornado的网络模型是异步的,这意味着它不会出现django那样因为外部服务不可用导致这个服务无法响应的问题。话说,比起django,我可是非常喜欢tornado的,小巧简单,以前还写过几篇深入剖析tornado的文章了。
虽然tornado是异步的,但是python的mysql库都不支持异步,这也就意味着如果我们在tornado里面访问数据库,我们仍然可能面临因为数据库问题造成的整个服务不可用。
其实异步模型最大的问题在于代码逻辑的割裂,因为是事件触发的,所以我们都是通过callback进行相关处理,于是代码里面就经常出现干一件事情,传一个callback,然后callback里面又传callback的情况,这样的结果就是整个代码逻辑非常混乱。
python没有原生的协程支持,虽然可以通过gevent,greenlet这种的上patch方式来支持协程,但毕竟更改了python源码。另外,python的yield也可以进行简单的协程模拟,但毕竟不能跨堆栈,局限性很大,不知道3.x的版本有没有改进。
开发运维部署
当我第一次使用python开发项目,我是没成功安装上项目需要的包的,光安装成功mysql库就弄了很久。后来,是一位同事将他整个python目录打包给我用,我才能正常的将项目跑起来。话说,现在有了docker,是多么让人幸福的一件事情。
而部署python服务的时候,我们需要在服务器上面安装一堆的包,光是这一点就让人很麻烦,虽然可以通过puppet,salt这些自动化工具解决部署问题,但相比而言,静态编译语言只用扔一个二进制文件,可就方便太多了。
代码失控
python非常灵活简单,写c几十行代码才能搞定的功能,python一行代码没准就能解决。但是太简单,反而导致很多同学无法对代码进行深层次的思考,对整个架构进行细致的考量。来了一个需求,啪啪啪,键盘敲完开速实现,结果就是代码越来越混乱,最终导致了整个项目代码失控。
虽然这也有我们自身的原因,譬如没好的代码review机制,没有好的项目规范,但个人感觉,如果一个程序员没经过良好的编码训练,用python很容易就写出烂的代码,因为太自由了。
当然,我这里并不是说用python无法进行大型项目的开发,豆瓣,dropbox都是很好的例子,只是在我们项目中,我们的python代码失控了。
上面提到的都是我们在实际项目中使用python遇到的问题,虽然最终都解决了,但是让我愈发的觉得,随着项目复杂度的增大,流量性能压力的增大,python并不是一个很好的选择。
为什么选择go
说完了python,现在来说说为什么我们选择go。其实除了python,我们也有其他的选择,java,php,lua(openresty),但最终我们选择了go。
虽然java和php都是最好的编程语言(大家都这么争的),但我更倾向一门更简单的语言。而openresty,虽然性能强悍,但lua仍然是动态语言,也会碰到前面说的动态语言一些问题。最后,前金山许式伟用的go,前快盘架构师葱头也用的go,所以我们很自然地选择了go。
go并不是完美,一堆值得我们吐槽的地方。
error,好吧,如果有语言洁癖的同学可能真的受不了go的语法,尤其是约定的最后一个返回值是error。项目里面经常会充斥这样的代码:
if _, err := w.Write(data1); err != nil {
returun err
}
if _, err := w.Write(data2); err != nil {
returun err
}
难怪有个梗是对于一个需求,java的程序员在写配置的时候,go程序员已经写了大部分代码,但是当java的程序员写完的时候,go程序员还在写err != nil。
这方面,errors-are-values倒是推荐了一个不错的解决方案。
包管理,go的包管理太弱了,只有一个go get,也就是如果不小心更新了一个外部库,很有可能就导致现有的代码编译不过了。虽然已经有很多开源方案,譬如godep以及现在才出来的gb等,但毕竟不是官方的。貌似google也是通过vendor机制来管理第三方库的。希望go 1.5或者之后的版本能好好处理下这个问题。
GC,java的GC发展20年了,go才这么点时间,gc铁定不完善。所以我们仍然不能随心所欲的写代码,不然在大请求量下面gc可能会卡顿整个服务。所以有时候,该用对象池,内存池的一定要用,虽然代码丑了点,但好歹性能上去了。
泛型,虽然go有inteface,但泛型的缺失会让我们在实现一个功能的时候写大量的重复代码,譬如int32和int64类型的sort,我们得为分别写两套代码,好冗余。go 1.4之后有了go generate的支持,但这种的仍然需要自己根据go的AST库来手动写相关的parser,难度也挺大的。虽然也有很多开源的generate实现,但毕竟不是官方的。
当然还有很多值得吐槽的地方,就不一一列举了,但是go仍旧有它的优势。
静态语言,强类型。静态编译能帮我们检查出来大量的错误,go的强类型甚至变态到不支持隐式的类型转换。虽然写代码感觉很别扭,但减少了犯错的可能。
gofmt,应该这是我知道的第一个官方提供统一格式化代码工具的语言了。有了gofmt,大家的代码长一个样了,也就没有花括号到底放到结尾还是新开一行这种蛋疼的代码风格讨论了。因为大家的代码风格一样,所以看go的代码很容易。
天生的并行支持,因为goroutine以及channel,用go写分布式应用,写并发程序异常的容易。没有了蛋疼的callback导致的代码逻辑割裂,代码逻辑都是顺序的。
性能,go的性能可能赶不上c,c++以及openresty,但真的也挺强悍的。在我们的项目中,现在单机就部署了一个go的进程,就完全能够胜任以前200个python进程干的事情,而且CPU和MEM占用更低。
运维部署,直接编译成二进制,扔到服务器上面就成,比python需要安装一堆的环境那是简单的太多了。当然,如果有cgo,我们也需要将对应的动态库给扔过去。
开发效率,虽然go是静态语言,但我个人感觉开发效率真的挺高,直觉上面跟python不相上下。对于我个人来说,最好的例子就是我用go快速开发了非常多的开源组件,譬如ledisdb,go-mysql等,而这些最开始的版本都是在很短的时间里面完成的。对于我们项目来说,我们也是用go在一个月就重构完成了第一个版本,并发布。
实际项目中一些Go Tips
到现在为止,我们几乎所有的服务端项目都已经转向go,当然在使用的时候也遇到了一些问题,列出来算是经验分享吧。
godep,我们使用godep进行第三方库管理,但是godep我碰到的最大的坑就是build tag问题,如果一个文件有build tag,godep很有可能就会忽略这个文件。
IO deadline,如果能自己在应用层处理的都自己处理,go的deadline内部是timer来控制,但timer内部采用一个array来实现的heap,全局共用一个锁,如果大并发量,并且timer数量过多,timeout变动太频繁,很容易就引起性能问题。
GC,这个前面也说了,多用内存池,对象池,另外,我还发现,如果对象的生命周期跟goroutine一致,对性能的提升也不错,也在go的group问过相关问题,大家猜测可能是因为一些对象其实是在goroutine的8k栈上面分配的,所以一起回收没有额外GC了。
Go gob,如果要做RPC服务,gob并不是一个很好的选择,首先就跟python的pickle不通用,然后为了做不同系统的数据传入,任何包都必须带上类型的详细信息,size太大。go里面现在还没一套官方的RPC方案,gRPC貌似有上位的可能。