环信即时通讯云急招前端工程师(薪资:20K-40K/月)
【前端工程师】
您的责任:
环信web IM SDK 等的开发设计,环信业务后台的开发设计
我们的要求:
1.3年web前端经验,熟悉流行的前端技术, 包括但不限于bootstrap, html5, css3, saas, less, jquery, bower, grunt
2.熟悉AJAX, REST等原理和使用方式
3.熟悉HTTP, WebSocket, Spdy等协议
4.熟悉Haml, Jade, Slim等模板语言优先考虑
5.有设计美感者优先考虑
6.深刻理解Web标准,对可用性. 可访问性等相关知识有实际的了解和实践经验;
7.熟悉ruby, python, bash, nodejs 等脚本语言优先考虑;
8.有开源社区经验者优先考虑
9.全栈工程师,DevOps直接录取
简历请发邮件:steven@easemob.com
关于环信:
环信是行业内领先的云服务提供商,国内最大的即时通讯云平台。环信上线半年内,已经完成了3轮融资。截止2015年上半年,环信已经帮助了23763个App加入社交和沟通能力,典型用户包括海豚浏览器、猎聘网、百合相亲、蜻蜓FM、汽车之家车友会、优听、节操精选、华图教育等。同时环信SDK覆盖用户高达2.51亿,日均消息量过亿,是经过真实亿级用户考验的即时通讯云平台。
收起阅读 »
您的责任:
环信web IM SDK 等的开发设计,环信业务后台的开发设计
我们的要求:
1.3年web前端经验,熟悉流行的前端技术, 包括但不限于bootstrap, html5, css3, saas, less, jquery, bower, grunt
2.熟悉AJAX, REST等原理和使用方式
3.熟悉HTTP, WebSocket, Spdy等协议
4.熟悉Haml, Jade, Slim等模板语言优先考虑
5.有设计美感者优先考虑
6.深刻理解Web标准,对可用性. 可访问性等相关知识有实际的了解和实践经验;
7.熟悉ruby, python, bash, nodejs 等脚本语言优先考虑;
8.有开源社区经验者优先考虑
9.全栈工程师,DevOps直接录取
简历请发邮件:steven@easemob.com
关于环信:
环信是行业内领先的云服务提供商,国内最大的即时通讯云平台。环信上线半年内,已经完成了3轮融资。截止2015年上半年,环信已经帮助了23763个App加入社交和沟通能力,典型用户包括海豚浏览器、猎聘网、百合相亲、蜻蜓FM、汽车之家车友会、优听、节操精选、华图教育等。同时环信SDK覆盖用户高达2.51亿,日均消息量过亿,是经过真实亿级用户考验的即时通讯云平台。
收起阅读 »
小窍门: @用户,被@方可以收到提示
直接@就可以了, 新编辑器里面, 回复暂时不能有下拉提示, 评论是有用户提示的.
不信你可以试试
P2P实时音视频之NAT穿越
在P2P实时音视频领域,NAT穿越是一个非常重要的技术。NAT穿越技术使得客户端和客户端直接进行通讯,从而减少了端到端的延迟,并大大减轻了服务器的压力,降低成本。
NAT是什么
NAT的全称Network Address Translation,通常指的是把内网地址转换成外网地址。一般家用的无线路由器就用到了NAT技术。NAT技术的出现是为了解决IPv4地址不够的问题,而且还能够避免来自网络外部的攻击,隐藏和保护网络内部的计算机。凡事有利必有弊,NAT同样带来了新的问题。
NAT工作原理
我们先看一下NAT的工作过程
NAT维护一个地址映射表,记录内容为内网主机地址iAddr、映射地址eAddr和外网主机地址hAddr,表初始为空
内网主机主机A发送数据包给服务器A,10.0.1.10:1111 -> 203.22.22.22:6000;
NAT在映射表里没找到源地址等于10.0.1.10:1111的记录,于是新建一条记录1,分配外网端口2000
NAT修改数据包的源地址再发到外网,202.11.11.11:2000 -> 203.22.22.22:6000;
后续所有源地址为10.0.1.10:1111,目标地址为203.22.22.22:6000都做同样的修改
服务器A发送数据包回给内网主机A,203.22.22.22:6000 -> 202.11.11.11:2000
NAT发现外网地址202.11.11.11:2000映射的内网地址为10.0.1.10:1111
NAT修改数据包的目的地址再发到内网,203.22.22.22:6000 -> 10.0.1.10:1111
内网主机B和服务器B通讯的过程也类似A,只是分配的外网端口是3000
从上面NAT的工作过程可以看出,NAT通过修改数据包的源地址或目的地址来实现地址映射的。NAT修改数据包对内网主机是透明的,不需要内网主机做任何配置,方便简单。
NAT工作原理可以总结为:
只有内网主机主动向外网发送数据,外网才有可能发送数据给内网主机
内网发送到外网的数据包会被修改源地址,外网发送给内网的数据包会被修改目的地址
很显然,第1条原理保护了内网主机免受外网的攻击,但却违背了网络端到端的设计原则。如果两台主机在不同的NAT后面,是没有办法穿越NAT直接端到端(P2P))通讯的。幸运的是,在大部分情况下,我们可以在服务器的协助下实现NAT穿越。
NAT类型
在讲NAT穿越之前,我们先来分析NAT的类型。由于没有强制性的NAT标准,在实际应用中NAT有多种类型。根据内网地址到外网地址的映射是1对1,还是1对多,NAT可以分成两大类:Cone NAT(锥型)和 Symmetric NAT(对称型)
从图中的淡紫色形状应该可以看出来它们名字的来历(呕心沥血独家原创图)。锥型NAT把一个内网地址固定的转换成一个外网地址,即1对1映射;对称型NAT的一个内网地址可以转换成多个外网地址,即1对多映射。从锥型NAT和对称型NAT的定义我们可以推测出他们的映射表内容。
锥型映射表应该是这样的:
对称型映射表应该是这样的:
Cone NAT子类型
锥型NAT还可以再继续细分类型。外网主机发送给内网主机的数据包在通过NAT时,NAT会根据映射表的外网主机地址限制条件来允许或限制数据包通过。根据这个限制条件,锥型NAT还可以分成三种子类型:
Full-cone NAT,全锥型
一旦某内网地址向外网发送过数据包,NAT允许任意外网地址发送数据给此内网地址。
(Address)-restricted-cone NAT,(地址)限制锥型
一旦某内网地址向某外网主机发送过数据包,NAT允许此外网主机发送数据给此内网地址。换句话说,只限制ip,不限制端口。
Port-restricted cone NAT,端口限制锥型
只有从内网地址发送过的外网地址,NAT才允许此外网地址发送数据给此内网地址。换句话说,同时限制ip和端口。
穿越NAT
通过上面对NAT的分析可以看出,在不同NAT后面的两个客户端A和B,如果知道对方的NAT映射后的外网地址,就有可能直接发送UDP包给对方外网地址进行通讯。但是这里有一个问题,客户端不能直接获取自身的NAT外网地址,解决的办法就是引入一个服务器S来协助客户端获取自身的外网地址。NAT的类型有多种,类型两两组合有很多种,不是每种组合都可以被穿越的,我们来分析两个典型的组合。
锥型 vs 锥型
端口限制锥型 vs 对称型
A发送数据包给eB1,因为eB1只接受来自S的数据,所以A的数据被NATB丢弃
B通过发送数据包给eA,因为eA是新的目标地址,NATB 创建新的映射地址eB2,而eA只接受来自S和eB1的数据,所以B的数据被NATA丢弃,无法建立P2P通道
这里就不一一分析其他组合,各位看官可以自行分析,这里直接给出结论:
现实中的NAT
在穿越NAT的结论里,只有两种组合不能穿越,即对称型vs对称型、端口限制锥型vs对称型,占比并不高,看起来结论还不错。但是,理论是美好的,现实是残酷的,生活中对称型NAT的数量并不少。只要是大型组织的网络,一般都采用对称型NAT,因为这类NAT安全性最好。我们团队曾经对常用的网络做过调查研究,以下是调研结果:
有公网IP的宽带:比如联通的ADSL,这类宽带会给每个用户分配一个公网IP,所以其NAT类型取决于用户所选用的路由器,大部分家用路由器都是端口限制锥型NAT;
无公网IP的宽带:比如宽带通,这类宽带给用户分配的是局域网IP,连接公网的NAT是运营商的,一般都是对称型NAT;
移动互联网:跟“无公网IP的宽带”类似,分配给手机的是局域网IP,出口基本都是对称型NAT;
大公司路由器:大部分都把路由器配置成对称型NAT;
比较可惜的是移动互联网也是对称型NAT,也就是说,如果通讯双方都走3G或4G的话,是很难直接P2P通讯的。我们的产品可以穿越部分对称型NAT,当碰到无法穿越的NAT时,为用户提供relay服务,保证接通率。
奇葩的NAT
我们现在知道NAT分为1种对称型和3种锥型,那还有没有其他类型的NAT呢?答案是YES。这个NAT各位看官应该并不陌生,它就是大名鼎鼎的netfilter/iptables。大家接触最多的iptables,是运行在ring3层用户态的配置程序,而运行在ring0内核态的netfilter才是真正实现NAT功能的程序。在大部分情况下,netfilter表现出来的是人见人爱的锥型NAT,但是在某种条件刺激下,它就华丽丽地变身成高贵冷漠的对称型NAT!
先上图:
在穿越时,假如右边B发给A的包比左边A发给B的包先到达netfilter,netfilter会用之前的映射地址eB把B的包发出去,这时候netfilter表现出来的是锥型NAT,穿越成功。反过来,假如A发给B的包先到达netfilter,那么B发给A的包就会被netfilter映射成新的地址eB’,这时候netfilter表现出来的是对称型NAT,导致穿越失败。见下图。
netfilter不分内网和外网,它会跟踪内网和外网所有协议的连接(conntrack),包括tcp和udp。当外网的数据先到达netfilter时,netfilter创建一条conntrack,内网的数据后到达netfilter,netfilter发现conntrack1已经占用了端口,就会选择另外一个外网端口作为映射端口。看官如果想了解详细情况,请阅读博大精深的netfilter源码,这里提示一下,看get_unique_tuple函数就可以了。虽然netfilter很奇葩,但我们的产品依然能够轻松的穿越它。作者介绍:
符宁,环信音视频team leader,在音视频客户端/服务器领域拥有多年设计、开发和管理经验。
联系方式:simon.fu@easemob.com 收起阅读 »
NAT是什么
NAT的全称Network Address Translation,通常指的是把内网地址转换成外网地址。一般家用的无线路由器就用到了NAT技术。NAT技术的出现是为了解决IPv4地址不够的问题,而且还能够避免来自网络外部的攻击,隐藏和保护网络内部的计算机。凡事有利必有弊,NAT同样带来了新的问题。
NAT工作原理
我们先看一下NAT的工作过程
NAT维护一个地址映射表,记录内容为内网主机地址iAddr、映射地址eAddr和外网主机地址hAddr,表初始为空
内网主机主机A发送数据包给服务器A,10.0.1.10:1111 -> 203.22.22.22:6000;
NAT在映射表里没找到源地址等于10.0.1.10:1111的记录,于是新建一条记录1,分配外网端口2000
NAT修改数据包的源地址再发到外网,202.11.11.11:2000 -> 203.22.22.22:6000;
后续所有源地址为10.0.1.10:1111,目标地址为203.22.22.22:6000都做同样的修改
服务器A发送数据包回给内网主机A,203.22.22.22:6000 -> 202.11.11.11:2000
NAT发现外网地址202.11.11.11:2000映射的内网地址为10.0.1.10:1111
NAT修改数据包的目的地址再发到内网,203.22.22.22:6000 -> 10.0.1.10:1111
内网主机B和服务器B通讯的过程也类似A,只是分配的外网端口是3000
从上面NAT的工作过程可以看出,NAT通过修改数据包的源地址或目的地址来实现地址映射的。NAT修改数据包对内网主机是透明的,不需要内网主机做任何配置,方便简单。
NAT工作原理可以总结为:
只有内网主机主动向外网发送数据,外网才有可能发送数据给内网主机
内网发送到外网的数据包会被修改源地址,外网发送给内网的数据包会被修改目的地址
很显然,第1条原理保护了内网主机免受外网的攻击,但却违背了网络端到端的设计原则。如果两台主机在不同的NAT后面,是没有办法穿越NAT直接端到端(P2P))通讯的。幸运的是,在大部分情况下,我们可以在服务器的协助下实现NAT穿越。
NAT类型
在讲NAT穿越之前,我们先来分析NAT的类型。由于没有强制性的NAT标准,在实际应用中NAT有多种类型。根据内网地址到外网地址的映射是1对1,还是1对多,NAT可以分成两大类:Cone NAT(锥型)和 Symmetric NAT(对称型)
从图中的淡紫色形状应该可以看出来它们名字的来历(呕心沥血独家原创图)。锥型NAT把一个内网地址固定的转换成一个外网地址,即1对1映射;对称型NAT的一个内网地址可以转换成多个外网地址,即1对多映射。从锥型NAT和对称型NAT的定义我们可以推测出他们的映射表内容。
锥型映射表应该是这样的:
对称型映射表应该是这样的:
Cone NAT子类型
锥型NAT还可以再继续细分类型。外网主机发送给内网主机的数据包在通过NAT时,NAT会根据映射表的外网主机地址限制条件来允许或限制数据包通过。根据这个限制条件,锥型NAT还可以分成三种子类型:
Full-cone NAT,全锥型
一旦某内网地址向外网发送过数据包,NAT允许任意外网地址发送数据给此内网地址。
(Address)-restricted-cone NAT,(地址)限制锥型
一旦某内网地址向某外网主机发送过数据包,NAT允许此外网主机发送数据给此内网地址。换句话说,只限制ip,不限制端口。
Port-restricted cone NAT,端口限制锥型
只有从内网地址发送过的外网地址,NAT才允许此外网地址发送数据给此内网地址。换句话说,同时限制ip和端口。
穿越NAT
通过上面对NAT的分析可以看出,在不同NAT后面的两个客户端A和B,如果知道对方的NAT映射后的外网地址,就有可能直接发送UDP包给对方外网地址进行通讯。但是这里有一个问题,客户端不能直接获取自身的NAT外网地址,解决的办法就是引入一个服务器S来协助客户端获取自身的外网地址。NAT的类型有多种,类型两两组合有很多种,不是每种组合都可以被穿越的,我们来分析两个典型的组合。
锥型 vs 锥型
- A发送数据包给S询问自身地址,S把A的外网地址eA返回给A
- B发送数据包给S询问自身地址,S把B的外网地址eB返回给B
- S把B的外网地址eB发送给A
- S把A的外网地址eA发送给B
- A发送数据包给eB,B发送数据包给eA,建立P2P通道
端口限制锥型 vs 对称型
- A发送数据包给S询问自身地址,S把A的外网地址eA返回给A
- B发送数据包给S询问自身地址,S把B的外网地址eB1返回给B
- S把B的外网地址eB1发送给A
- S把A的外网地址eA发送给B
A发送数据包给eB1,因为eB1只接受来自S的数据,所以A的数据被NATB丢弃
B通过发送数据包给eA,因为eA是新的目标地址,NATB 创建新的映射地址eB2,而eA只接受来自S和eB1的数据,所以B的数据被NATA丢弃,无法建立P2P通道
这里就不一一分析其他组合,各位看官可以自行分析,这里直接给出结论:
现实中的NAT
在穿越NAT的结论里,只有两种组合不能穿越,即对称型vs对称型、端口限制锥型vs对称型,占比并不高,看起来结论还不错。但是,理论是美好的,现实是残酷的,生活中对称型NAT的数量并不少。只要是大型组织的网络,一般都采用对称型NAT,因为这类NAT安全性最好。我们团队曾经对常用的网络做过调查研究,以下是调研结果:
有公网IP的宽带:比如联通的ADSL,这类宽带会给每个用户分配一个公网IP,所以其NAT类型取决于用户所选用的路由器,大部分家用路由器都是端口限制锥型NAT;
无公网IP的宽带:比如宽带通,这类宽带给用户分配的是局域网IP,连接公网的NAT是运营商的,一般都是对称型NAT;
移动互联网:跟“无公网IP的宽带”类似,分配给手机的是局域网IP,出口基本都是对称型NAT;
大公司路由器:大部分都把路由器配置成对称型NAT;
比较可惜的是移动互联网也是对称型NAT,也就是说,如果通讯双方都走3G或4G的话,是很难直接P2P通讯的。我们的产品可以穿越部分对称型NAT,当碰到无法穿越的NAT时,为用户提供relay服务,保证接通率。
奇葩的NAT
我们现在知道NAT分为1种对称型和3种锥型,那还有没有其他类型的NAT呢?答案是YES。这个NAT各位看官应该并不陌生,它就是大名鼎鼎的netfilter/iptables。大家接触最多的iptables,是运行在ring3层用户态的配置程序,而运行在ring0内核态的netfilter才是真正实现NAT功能的程序。在大部分情况下,netfilter表现出来的是人见人爱的锥型NAT,但是在某种条件刺激下,它就华丽丽地变身成高贵冷漠的对称型NAT!
先上图:
在穿越时,假如右边B发给A的包比左边A发给B的包先到达netfilter,netfilter会用之前的映射地址eB把B的包发出去,这时候netfilter表现出来的是锥型NAT,穿越成功。反过来,假如A发给B的包先到达netfilter,那么B发给A的包就会被netfilter映射成新的地址eB’,这时候netfilter表现出来的是对称型NAT,导致穿越失败。见下图。
netfilter不分内网和外网,它会跟踪内网和外网所有协议的连接(conntrack),包括tcp和udp。当外网的数据先到达netfilter时,netfilter创建一条conntrack,内网的数据后到达netfilter,netfilter发现conntrack1已经占用了端口,就会选择另外一个外网端口作为映射端口。看官如果想了解详细情况,请阅读博大精深的netfilter源码,这里提示一下,看get_unique_tuple函数就可以了。虽然netfilter很奇葩,但我们的产品依然能够轻松的穿越它。作者介绍:
符宁,环信音视频team leader,在音视频客户端/服务器领域拥有多年设计、开发和管理经验。
联系方式:simon.fu@easemob.com 收起阅读 »
实时网络音视频通讯qos的一种解决方案
一、前言
随着移动互联网的快速发展以及智能终端性能的逐步提高,智能终端间进行实时音视频通讯成为未来移动互联网
发展的一个重要方向。那么如何保证智能终端之间实时音视频通讯的服务质量成为一个必须加以重视的问题。实时音视频通讯包括采集、编码、网络传输、解码、播放等环节,其中采集、编解码和播放是不受网络条件影响的,只受限于编解码算法,播放策略等因素,网络传输的丢包、抖动和乱序对qos的影响最为重大,因此本文介绍的qos解决方案
要解决的是网络传输丢包、抖动和乱序因素对服务质量的不好影响。
二、发送端
对于实时音视频通讯,常采用UDP协议来传输多媒体数据,本文是采用基于udp的rtp协议来传输音视频数据。对
于不同格式的编码数据,会有不同的rtp打包协议,比如对于H.264视频数据,文档rfc3984对NAL U的rtp打包封装进行了规范,详情请参考该文档。对于视频数据的打包封装,因为一帧视频数据的数据长度可能大于MTU,所以相关的打包协议都会规定将长度大于MTU的帧进行切割,分块封装到多个rtp包进行传输。为了避免丢包、抖动和乱序对服务质量的影响,本方案在发送端和接收端各建立了节点数相等的一段循环buffer,用于缓存发送端数据和接收端数据。
发送端在发送数据的时候,某个rtp包的seq为send_seq,发送端把这个包通过udp socket发送出去的同时,把这
个rtp包的数据拷贝到send_seq对应节点的buffer中去,以便这个rtp包接收方没收到时,发送方还能重发这个rtp包。
这里要注意的一点是,发送端和接收端的循环buffer节点数要能被65536整除,这样rtp seq增加到最大值65535时对应最后一个节点,下一个rtp包的seq为0正好对应上第一个节点,避免rtp seq掉头时出现漏洞。
三、接收端
和发送端类似,接收端也开辟了一段节点数能被65536整除的循环buffer,用于缓存接收到的rtp包。接收端收到rtp包时,需要去解析rtp包头,取出接收到的rtp包的seq,对应下图中的received_seq。
当收到第一个包时,start_seq和end_seq都被设置为received_seq,并把收到的rtp包送到解码单元。后面收到
rtp包时,有两个工作要做,一个工作是接收的模块将接收到的rtp包拷贝到received_seq指向的节点的buffer,并将这个节点的数据flag(用于标记该节点是否填充了数据)设置为true,同时要根据start_seq、end_seq和received_seq的关系来决定要不要将end_seq更新为received_seq的值,如果received_seq对应的包本来应该end_seq对应的包之前到达,则不更新end_seq的值,否则就更新。另一个工作是要每过一段时间都要去扫描start_seq到end_seq对应的每个节点,首先,若当前时间和start_seq对应的数据到达时间的差值超过一定阈值(比如500ms),则将start_seq和end_seq之间的每个节点的数据全部丢弃,将每个节点的数据flag设置为false,更新start_seq为end_seq。其次,若start_seq对应的节点的下一个节点的数据falg为true,则将该节点的数据送到解码单元,同时将start_seq更新为该节点的seq,并将该节点的数据flag设置为false;若flag为false,且当前时间和start_seq对应的数据到达时间的差值超过一定阈值(比如50ms),则将该节点的seq(lost_seq)发送给发送端,请求发送端将seq对应的rtp数据再发一遍。这样,当有些包很久(大于500ms)都没收到,就认为它来不了,直接将它们丢弃;有些包短时间(小于50ms)没来,则向发送端发送重传请求,请求发送端再发一次该包,试图能补上这些包。
四、结果分析
没加qos模块时,两个手机视频通信在有丢包情况下回出现视频帧不完整,播放出现马赛克的现象,加上qos模块后,视频播放流畅,效果大为改善。同时我们为了测试该方案的作用,在发送端人为地分别丢弃10%和20%的视频rtp包,接收端解码播放效果良好,没有出现马赛克现象。
作者介绍:
彭祖元,环信资深音视频技术专家。拥有多年音视频编解码开发经验,在Android,iOS等平台音视频采集,编码,传输,解码,播放等方面有着丰富的经验,熟悉流媒体服务器开发。 收起阅读 »
随着移动互联网的快速发展以及智能终端性能的逐步提高,智能终端间进行实时音视频通讯成为未来移动互联网
发展的一个重要方向。那么如何保证智能终端之间实时音视频通讯的服务质量成为一个必须加以重视的问题。实时音视频通讯包括采集、编码、网络传输、解码、播放等环节,其中采集、编解码和播放是不受网络条件影响的,只受限于编解码算法,播放策略等因素,网络传输的丢包、抖动和乱序对qos的影响最为重大,因此本文介绍的qos解决方案
要解决的是网络传输丢包、抖动和乱序因素对服务质量的不好影响。
二、发送端
对于实时音视频通讯,常采用UDP协议来传输多媒体数据,本文是采用基于udp的rtp协议来传输音视频数据。对
于不同格式的编码数据,会有不同的rtp打包协议,比如对于H.264视频数据,文档rfc3984对NAL U的rtp打包封装进行了规范,详情请参考该文档。对于视频数据的打包封装,因为一帧视频数据的数据长度可能大于MTU,所以相关的打包协议都会规定将长度大于MTU的帧进行切割,分块封装到多个rtp包进行传输。为了避免丢包、抖动和乱序对服务质量的影响,本方案在发送端和接收端各建立了节点数相等的一段循环buffer,用于缓存发送端数据和接收端数据。
发送端在发送数据的时候,某个rtp包的seq为send_seq,发送端把这个包通过udp socket发送出去的同时,把这
个rtp包的数据拷贝到send_seq对应节点的buffer中去,以便这个rtp包接收方没收到时,发送方还能重发这个rtp包。
这里要注意的一点是,发送端和接收端的循环buffer节点数要能被65536整除,这样rtp seq增加到最大值65535时对应最后一个节点,下一个rtp包的seq为0正好对应上第一个节点,避免rtp seq掉头时出现漏洞。
三、接收端
和发送端类似,接收端也开辟了一段节点数能被65536整除的循环buffer,用于缓存接收到的rtp包。接收端收到rtp包时,需要去解析rtp包头,取出接收到的rtp包的seq,对应下图中的received_seq。
当收到第一个包时,start_seq和end_seq都被设置为received_seq,并把收到的rtp包送到解码单元。后面收到
rtp包时,有两个工作要做,一个工作是接收的模块将接收到的rtp包拷贝到received_seq指向的节点的buffer,并将这个节点的数据flag(用于标记该节点是否填充了数据)设置为true,同时要根据start_seq、end_seq和received_seq的关系来决定要不要将end_seq更新为received_seq的值,如果received_seq对应的包本来应该end_seq对应的包之前到达,则不更新end_seq的值,否则就更新。另一个工作是要每过一段时间都要去扫描start_seq到end_seq对应的每个节点,首先,若当前时间和start_seq对应的数据到达时间的差值超过一定阈值(比如500ms),则将start_seq和end_seq之间的每个节点的数据全部丢弃,将每个节点的数据flag设置为false,更新start_seq为end_seq。其次,若start_seq对应的节点的下一个节点的数据falg为true,则将该节点的数据送到解码单元,同时将start_seq更新为该节点的seq,并将该节点的数据flag设置为false;若flag为false,且当前时间和start_seq对应的数据到达时间的差值超过一定阈值(比如50ms),则将该节点的seq(lost_seq)发送给发送端,请求发送端将seq对应的rtp数据再发一遍。这样,当有些包很久(大于500ms)都没收到,就认为它来不了,直接将它们丢弃;有些包短时间(小于50ms)没来,则向发送端发送重传请求,请求发送端再发一次该包,试图能补上这些包。
四、结果分析
没加qos模块时,两个手机视频通信在有丢包情况下回出现视频帧不完整,播放出现马赛克的现象,加上qos模块后,视频播放流畅,效果大为改善。同时我们为了测试该方案的作用,在发送端人为地分别丢弃10%和20%的视频rtp包,接收端解码播放效果良好,没有出现马赛克现象。
作者介绍:
彭祖元,环信资深音视频技术专家。拥有多年音视频编解码开发经验,在Android,iOS等平台音视频采集,编码,传输,解码,播放等方面有着丰富的经验,熟悉流媒体服务器开发。 收起阅读 »
Big Data Processing at Easemob – Big Data Processing Platform
Easemob plug-in and its SDK on mobile text and voice messaging communication service become extremely popular in China recently and are used by over 13,000 mobile Apps with millions of registered users in total. One of the driving design principles of Easemob is that better quality of social activities means a better App with a more engaging user experience. With regards to this, Easemob decided to invest into Big Data technology for app developers in the form of an interactive web portal that presents business analytics related to their plug-ins.
Figure 1 Easemob Social Activity Big Data Analysis Application
Most data on mobile Apps with Easemob’s plug-in is consumed in real-time. Easemob recognized that providing results to app developers in closer to real time could help generate more engaging social activities by allowing more dynamic interaction with users. The finer grained details of a real-time system also provide much more information than a delayed multiple hours aggregate. Hence, the primary goal of Easemob's big data analysis system was to provide analytic results to app developers with minutes or even seconds of latency.
To achieve such target, we actually don’t have many options. The original idea is to use apache hadoop to complete the task. Hadoop MapReduce/Hive based solution certainly can handle such scale required by Easemob with high availability and accuracy. However, its batch processing logic based upon MapReduce framework means probably it won’t deliver results in real time. Hence Easemob will not be able to provide a specific SLA for its app developers if such idea is implemented.
With some careful research, we came up with a solution that relies on Spark integrated with Cassandra to deliver results. Easemob has been already using Cassandra for its messaging system and the nosql big table database has been proved to be able to scale and meet the requirement of Easemob app clients. In summary, we chose Spark and Cassandra for the new big data processing system based upon high performance Spark can provide and high availability and scalability from Cassandra. Some details about Spark and Cassandra are discussed in a later section of this article.
About Spark
Spark is an open source cluster computing system developed in the UC Berkeley AMP Lab. Some benchmark results done by Berkeley shows the results of comparison of performance between spark/shark and hadoop/hive combination.
Figure 2 Benchmark of Spark/Shark and Hadoop/Hive
It is not surprising to see Spark beats Hadoop easily. This is because spark provides primitives for in-memory cluster computing while MapReduce processing can only occur on data stored either in a file system (unstructured) or in a physical database (structured). It can not avoid I/O bottleneck between the individual jobs of an iterative MapReduce workflow. Spark does not have this issue.
Apart from its lighting speed, spark provides a few interesting features which will be needed by Easemob’s big data processing system sooner or later. Shark for Spark is like Hive for Hadoop. It actually builds directly on the Apache Hive codebase. However, it has a “Spark” heart. Underneath, it is using Spark execution engine for its query processing so it appears much faster than Hive. Spark also directly supports real time processing through its streaming framework on which Easemob’s social activity big data engine will be built. The apache machine learning library is tightly integrated into Spark. Interactive data mining is a supported feature by Spark. It is absolutely vital feature for Easemob ‘s future success of its social activity big data analysis system.
As for Cassandra, as mentioned previously in this article, it has been selected by Easemob for its messaging system. Today in Easemob, more than 100 millions messages a day are being handled by Cassandra database across multiple data centers. The big table database has been proven to be able to scale. The figure 3 as below shows what the big data processing platform for Easemob’s future social activity analysis engine looks like.
Figure 3 - Platform for Easemob Social Activity Big Data Analysis
There is one thing I particularly like about Spark. It makes use of the Scala language, which allows distributed datasets to be manipulated like local collections. It combines Object-oriented and functional programming perfectly. It has a very strong static type system. It overcomes many shortcomings of Java language but however, it is still compiled to java bytecode hence it runs on java virtual machine. The Interoperability of the two languages is maintained well. It is perfectly integrated into Spark.
In the big data world, Spark is relatively new technology, however, it is already used by many companies in production such as Yahoo, Sohu and Ebay etc. The long list can be viewed in Apache website.
About the author
Zhi Huang is the Head of Big Data Analytics Department in easemob.com. He can be reached at zhi.huang@easemob.com
Today, in China, our mobile text and voice messaging communication service serves more than 13,000 mobile apps and hundreds of millions of users. We are currently expanding our business globally. If you are app developer, you are more than welcome to use our free service at any time.
作者: 黄智 收起阅读 »
Figure 1 Easemob Social Activity Big Data Analysis Application
Most data on mobile Apps with Easemob’s plug-in is consumed in real-time. Easemob recognized that providing results to app developers in closer to real time could help generate more engaging social activities by allowing more dynamic interaction with users. The finer grained details of a real-time system also provide much more information than a delayed multiple hours aggregate. Hence, the primary goal of Easemob's big data analysis system was to provide analytic results to app developers with minutes or even seconds of latency.
To achieve such target, we actually don’t have many options. The original idea is to use apache hadoop to complete the task. Hadoop MapReduce/Hive based solution certainly can handle such scale required by Easemob with high availability and accuracy. However, its batch processing logic based upon MapReduce framework means probably it won’t deliver results in real time. Hence Easemob will not be able to provide a specific SLA for its app developers if such idea is implemented.
With some careful research, we came up with a solution that relies on Spark integrated with Cassandra to deliver results. Easemob has been already using Cassandra for its messaging system and the nosql big table database has been proved to be able to scale and meet the requirement of Easemob app clients. In summary, we chose Spark and Cassandra for the new big data processing system based upon high performance Spark can provide and high availability and scalability from Cassandra. Some details about Spark and Cassandra are discussed in a later section of this article.
About Spark
Spark is an open source cluster computing system developed in the UC Berkeley AMP Lab. Some benchmark results done by Berkeley shows the results of comparison of performance between spark/shark and hadoop/hive combination.
Figure 2 Benchmark of Spark/Shark and Hadoop/Hive
It is not surprising to see Spark beats Hadoop easily. This is because spark provides primitives for in-memory cluster computing while MapReduce processing can only occur on data stored either in a file system (unstructured) or in a physical database (structured). It can not avoid I/O bottleneck between the individual jobs of an iterative MapReduce workflow. Spark does not have this issue.
Apart from its lighting speed, spark provides a few interesting features which will be needed by Easemob’s big data processing system sooner or later. Shark for Spark is like Hive for Hadoop. It actually builds directly on the Apache Hive codebase. However, it has a “Spark” heart. Underneath, it is using Spark execution engine for its query processing so it appears much faster than Hive. Spark also directly supports real time processing through its streaming framework on which Easemob’s social activity big data engine will be built. The apache machine learning library is tightly integrated into Spark. Interactive data mining is a supported feature by Spark. It is absolutely vital feature for Easemob ‘s future success of its social activity big data analysis system.
As for Cassandra, as mentioned previously in this article, it has been selected by Easemob for its messaging system. Today in Easemob, more than 100 millions messages a day are being handled by Cassandra database across multiple data centers. The big table database has been proven to be able to scale. The figure 3 as below shows what the big data processing platform for Easemob’s future social activity analysis engine looks like.
Figure 3 - Platform for Easemob Social Activity Big Data Analysis
There is one thing I particularly like about Spark. It makes use of the Scala language, which allows distributed datasets to be manipulated like local collections. It combines Object-oriented and functional programming perfectly. It has a very strong static type system. It overcomes many shortcomings of Java language but however, it is still compiled to java bytecode hence it runs on java virtual machine. The Interoperability of the two languages is maintained well. It is perfectly integrated into Spark.
In the big data world, Spark is relatively new technology, however, it is already used by many companies in production such as Yahoo, Sohu and Ebay etc. The long list can be viewed in Apache website.
About the author
Zhi Huang is the Head of Big Data Analytics Department in easemob.com. He can be reached at zhi.huang@easemob.com
Today, in China, our mobile text and voice messaging communication service serves more than 13,000 mobile apps and hundreds of millions of users. We are currently expanding our business globally. If you are app developer, you are more than welcome to use our free service at any time.
作者: 黄智 收起阅读 »
IM客户端数据库加载过程优化
IM通讯里面有两个重要的数据概念,一个是会话,一个是会话中的消息。
在系统初始化时,这两部分都要从数据库中加载到内存中。
数据组织结构是ConversatonManager包含多个会话,每个会话含有消息列表。
每次系统启动的时候,首先查询会话列表,然后对每一个会话加载其中的消息。对应的伪码
conversationList = db.loadConverstaions()
FOR (conversation : conversationList) {
db.loadMessages(conversation);
}
因为每次查询都要涉及数据库操作,导致加载时间很长,而且大量的IO操作也会导致耗电量增加,
所以这部分加载过程,是我们优化的重点。
思路很简单:一条SQL语句做完所有事情,避免for循环,避免多次遍历数据库。
修改后的结构是:
conversationList = db.loadConverstaionsAndMessages();
这样大量的细节隐藏在SQL语句实现中。
这里面的实现有两种情况:
1. 一种是每个会话只加载一条消息记录。
2. 另一种是每个会话加载多条消息记录。
针对“1”中每个会话只加载一条消息记录(假设是最后一条消息),这种情况可以使用关键字group by 处理:
select *, max(msgTime) from xxx_table group by conversation
这种情况比较好理解,而且网上类似的问题很多,很容易找到答案。
对于“2”中每个会话要求加载多条消息的情况(消息按照时间排序),我的思路是在group by, order by, limit这些关键字中寻找答案。
先在网络上寻找答案,寻找一些类似的实现,可惜都不理想。
有的实现就是把for循环转移到sql语句中,利用游标的概念,但是计算的数量级并没有下降,使用我本地的较大的数据量进行试验,执行时间过长。
或者是看到oracle数据库中有解决方案,但是需要使用关键字partition,这个应该是oracle数据看到经常会有类似的问题而提出的专用关键字。
对于mysql, sqlite等常用数据库,没法移植该实现。
最终我使用的方法是,
select * from xxx_table order by conversation, msgTime desc.
这样整个表单进行排序,首先按照会话名称进行排序,然后按照消息时间排序。
还剩下一个条件没有满足,就是每个会话消息的限定个数。
把个数的遍历放在外面实现,通过一个while循环将会话中超出limit部分的消息剔除。
伪码:
cursor = db.EXEC('select * from xxx_table order by conversation, msgTime desc');
while (cursor.NEXT()) {
msg = msgFrom(cursor)
IF (! msg belong TO conversation) {
// 消息不属于当前的会话,所以
conversation = NEW Conversation();
conversation.ADD(msg);
continue;
}
IF (conversation.msgSize() < LIMIT && msg belong TO conversation) {
conversation.ADD(msg);
} ELSE {
// 消息个数已经超过会话消息限制
continue;
}
}
这种方法的缺点是cursor会把整个表单都返回到用户空间,然后把所有的数据在用户空间都遍历一遍,有多余的操作。
不属于最优实现。
优点是两次排序使用order by,可以由数据库实现,这部分执行效率比较高,然后一次遍历cursor就执行完剩余操作,执行效率在接受范围之内,和改动之前相比效率提升至少一个数量级。
测试结果:一万条消息记录,一千个会话,执行时间大概4秒
补充一下,对于非数据库专业人员来说,有一点需要注意:
group by, order by, limit这些关键字在sql语句中有强制的顺序要求,limit , order by,都不能写到group by前面。
下面是我在寻找这个问题过程中看到的一些帖子,第一行是文章标题,后面是我看后的感受。如有冒犯,敬请原谅。
[SQL中Group分组获取Top N方法实现]
游标方法可取,网上讨论说运行比较慢。
一条SQL语句搞定分组并且每组限定记录集的数量]
仅适用于oracle
mysql实现每组取前N条记录的sql,以及后续的组数据量限制]
好像是可以,没看明白
SQL--分组显示数据,显示每组的前几行数据]
http://blog.163.com/peng_peng1028/blog/static/107463820111019240379/
像是答案,效率好像很低
[取每组前几条记录的SQL写法]
http://blog.sina.com.cn/s/blog_412897e10100r2rq.html
该页面提供两种方法,都尝试过,效率太低,杀掉程序时还没执行完
作者: 李楠 收起阅读 »
在系统初始化时,这两部分都要从数据库中加载到内存中。
数据组织结构是ConversatonManager包含多个会话,每个会话含有消息列表。
每次系统启动的时候,首先查询会话列表,然后对每一个会话加载其中的消息。对应的伪码
conversationList = db.loadConverstaions()
FOR (conversation : conversationList) {
db.loadMessages(conversation);
}
因为每次查询都要涉及数据库操作,导致加载时间很长,而且大量的IO操作也会导致耗电量增加,
所以这部分加载过程,是我们优化的重点。
思路很简单:一条SQL语句做完所有事情,避免for循环,避免多次遍历数据库。
修改后的结构是:
conversationList = db.loadConverstaionsAndMessages();
这样大量的细节隐藏在SQL语句实现中。
这里面的实现有两种情况:
1. 一种是每个会话只加载一条消息记录。
2. 另一种是每个会话加载多条消息记录。
针对“1”中每个会话只加载一条消息记录(假设是最后一条消息),这种情况可以使用关键字group by 处理:
select *, max(msgTime) from xxx_table group by conversation
这种情况比较好理解,而且网上类似的问题很多,很容易找到答案。
对于“2”中每个会话要求加载多条消息的情况(消息按照时间排序),我的思路是在group by, order by, limit这些关键字中寻找答案。
先在网络上寻找答案,寻找一些类似的实现,可惜都不理想。
有的实现就是把for循环转移到sql语句中,利用游标的概念,但是计算的数量级并没有下降,使用我本地的较大的数据量进行试验,执行时间过长。
或者是看到oracle数据库中有解决方案,但是需要使用关键字partition,这个应该是oracle数据看到经常会有类似的问题而提出的专用关键字。
对于mysql, sqlite等常用数据库,没法移植该实现。
最终我使用的方法是,
select * from xxx_table order by conversation, msgTime desc.
这样整个表单进行排序,首先按照会话名称进行排序,然后按照消息时间排序。
还剩下一个条件没有满足,就是每个会话消息的限定个数。
把个数的遍历放在外面实现,通过一个while循环将会话中超出limit部分的消息剔除。
伪码:
cursor = db.EXEC('select * from xxx_table order by conversation, msgTime desc');
while (cursor.NEXT()) {
msg = msgFrom(cursor)
IF (! msg belong TO conversation) {
// 消息不属于当前的会话,所以
conversation = NEW Conversation();
conversation.ADD(msg);
continue;
}
IF (conversation.msgSize() < LIMIT && msg belong TO conversation) {
conversation.ADD(msg);
} ELSE {
// 消息个数已经超过会话消息限制
continue;
}
}
这种方法的缺点是cursor会把整个表单都返回到用户空间,然后把所有的数据在用户空间都遍历一遍,有多余的操作。
不属于最优实现。
优点是两次排序使用order by,可以由数据库实现,这部分执行效率比较高,然后一次遍历cursor就执行完剩余操作,执行效率在接受范围之内,和改动之前相比效率提升至少一个数量级。
测试结果:一万条消息记录,一千个会话,执行时间大概4秒
补充一下,对于非数据库专业人员来说,有一点需要注意:
group by, order by, limit这些关键字在sql语句中有强制的顺序要求,limit , order by,都不能写到group by前面。
下面是我在寻找这个问题过程中看到的一些帖子,第一行是文章标题,后面是我看后的感受。如有冒犯,敬请原谅。
[SQL中Group分组获取Top N方法实现]
游标方法可取,网上讨论说运行比较慢。
一条SQL语句搞定分组并且每组限定记录集的数量]
仅适用于oracle
mysql实现每组取前N条记录的sql,以及后续的组数据量限制]
好像是可以,没看明白
SQL--分组显示数据,显示每组的前几行数据]
http://blog.163.com/peng_peng1028/blog/static/107463820111019240379/
像是答案,效率好像很低
[取每组前几条记录的SQL写法]
http://blog.sina.com.cn/s/blog_412897e10100r2rq.html
该页面提供两种方法,都尝试过,效率太低,杀掉程序时还没执行完
作者: 李楠 收起阅读 »
移动开发之语言之美 – 类
现在最受开发者欢迎的两大平台 IOS, android现在主要还使用Objective-C 和 Java 来进行开发,不过苹果公司推出的编程语言 Swift,她吐故纳新,抛弃了Objective C繁琐的语法规则,引入了极为简洁,功能强大的语法。实际上除了平台本身的限制,包括如何调用平台提供的API,如何使用线程,如何启动定时器,如何设计UI界面等等之外,语言本身其实都是大同小异,所以为了方便开发者掌握不同平台的语言,对比学习Swift,Objective-C,Java等移动开发主流语言也是一种不错的选择。
在这个篇章中,我们首先从类定义开始,逐步探讨继承,接口,访问控制
类
如果你学过面向对象编程的语言,你就知道类是个什么概念,简而言之,我们把某种具有共同属性及功能单元的设计蓝图称之为类,它代表的设计定义,而对象就是根据类定义所产生的内存实体。
我现在用一个简单的需求实例来阐明不同的语言是如何实现这个需求的,以便读者可以了解不同的语言是对类如何定义和实现的。
需求:
实现一个类包含4个变量
包含5个函数
构造函数
Objective-C 定义
@interface YN_Example_Class : NSObject{
@private
int mExampleData;
@protected
NSString* mExampleStr;
@public
BOOL mExampleBool;
}
-(YN_Example_Class*) init;
@property(nonatomic) int exampleProperty;
-(void) exampleFunc;
-(void) exampleFuncWithParameters:(NSString*) str withBool:(BOOL) boolP;
-(NSString*) exampleFuncWithReturnValue;
+(void) classExampleFunc;
Objective-C 类实现
@implementation YN_Example_Class
-(YN_Example_Class*) init{
mExampleStr = @"YN_Example_Class";
mExampleData = 8;
mExampleBool = YES;
return self;
}
-(void) exampleFunc{
NSLog(@"exampleFunc");
}
-(void) exampleFuncWithParameters:(NSString *)str withBool:(BOOL) boolP{
mExampleStr = str;
mExampleBool = boolP;
}
-(NSString*) exampleFuncWithReturnValue{
return mExampleStr;
}
+(void) classExampleFunc{
NSLog(@"classExampleFunc");
}
-(void) setExampleProperty: (int) data{
if(_exampleProperty < 100){
_exampleProperty = data;
}
}
-(int) getExampleProperty{
_exampleProperty++;
return _exampleProperty;
}
@end
Swift
class YN_Example_class{
var mExampleStr:String
var mExampleData:UInt
var mExampleBool:Bool
var exampleProperty:UInt{
get{
mExampleData++
return mExampleData;
}
set(data){
if(mExampleData < 100){ mExampleData = data } } } init(){ mExampleStr = "YN_Example_class" mExampleData = 8 mExampleBool = true } func exampleFunc(){ println("exampleFunc") } func exampleFuncWithParameters(str:String, boolP:Bool){ mExampleStr = str; mExampleBool = boolP } func exampleFuncWithReturnValue() -> String{
return mExampleStr
}
class func exampleClassFunc(){
println("exampleClassFunc")
}
}
Java
class YN_Example_Class{
private int mExampleData;
protected String mExampleStr;
public boolean mExampleBool;
private int exampleProperty;
static private String gStr;
YN_Example_Class(){
mExampleStr = "YN_Example_Class";
mExampleData = 8;
mExampleBool = true;
setExampleProperty(0);
}
void exampleFunc(){
System.out.println("exampleFunc");
}
void exampleFuncWithParameters(String str, boolean boolP){
mExampleStr = str;
mExampleBool = boolP;
}
String exampleFuncWithReturnValue(){
return mExampleStr;
}
static void classExampleFunc(){
System.out.println("classExampleFunc");
}
public int getExampleProperty() {
exampleProperty++;
return exampleProperty;
}
public void setExampleProperty(int exampleProperty) {
if(this.exampleProperty < 100){
this.exampleProperty = exampleProperty;
}
}
}
属性property/成员变量
Objective-C既提供变量也提供属性的概念,实际上属性就是一种语法规则,属性本身并不存在内存中,但编译器为之合成一个内存实体叫_PropertyName,我们可以简单的理解为是用来替代麻烦的setter和getter操作,编译器会自动合成相应的setter和getter函数,不过属性定义的不同会导致setter和getter实现的不同。
为了简洁,去除混淆,Swift已经不再区分成员变量和property,她应经统一了二者,直接用关键字var来定义。
Swift 定义了两种propertyStored Property -- 可以理解为property直接存储数据
Computed Property -- 可以理解为property不能直接存储数据,而是通过getter,setter来间接存取exampleProperty就是个Computed Property
var example:YN_Example_class = YN_Example_class()
example.exampleProperty = 9;
println(example.exampleProperty)
example.exampleProperty 值为10
Java 和 Objective-c 还有 Swift不同之处就是,语言层面没有property的支持,不过无非就是多写两行代码,用来setter和getter成员变量
类函数/静态函数
直接了当的说,就是直接可以通过类名访问
YN_Example_Class.classExampleFunc
类变量/静态变量
用来做所有实例共享的变量,单例模式
swift,和Objective-C不能声明类变量在类里,Java一定要声明在类里。
Objective-C
#import
#import "objc_language_class.h"
static NSString* gStr;
@implementation YN_Example_Delegate
Swift
import Foundation
private var gStr:String?
类属性,所有的实例都共享同一个类属性,类属性一定是个Computed Property(看上述property属性),类属性的实现需要全局属性的支持,上述就是如何定义一个全局属性来存储数据
类属性例子如下:
class var gProperty:String {
set(g){
gStr = g
}
get{
return gStr!
}
}
Java
class YN_Example_Class{
static private String gStr;
接口,继承,重载
Swift
import Foundation
protocol YNExample_Protocol{
func getMe() -> String
}
class YN_Example_Delegte : YNExample_Protocol{
var myproto:UInt
var myStr:String
init(){
myproto = 3;
myStr = String()
}
func getMe() -> String{
return "YN_Example_Delegte"
}
}
class YN_Example_Delegte_derived : YN_Example_Delegte{
// without override, complier will give the error
override func getMe() -> String{
return "YN_Example_Delegte_derived"
}
}
Objective-C
#ifndef objc_language_class_objc_language_class_h
#define objc_language_class_objc_language_class_h
@protocol YN_Example_Protocol
-(NSString*) getMe;
@end
@interface YN_Example_Delegate : NSObject{
NSString* myStr;
}
@property int myprop;
-(YN_Example_Delegate*) init;
@end
@interface YN_Example_DelegateDerived : YN_Example_Delegate;
@end
#endif
@implementation YN_Example_Delegate
- (NSString*) getMe{
return @"YN_Example_Delegate";
}
-(YN_Example_Delegate*) init{
myStr = [[NSString alloc] initWithUTF8String:""];
return self;
}
@end
@implementation YN_Example_DelegateDerived
- (NSString*) getMe{
myStr = @"";
return @"YN_Example_DelegateDerived";
}
@end
Java
interface YNExample_Protocol{
String getMe();
}
class YN_Example_Delegte implements YNExample_Protocol{
@Override
public String getMe() {
// TODO Auto-generated method stub
return "YN_Example_Delegte";
}
}
class YN_Example_Delegte_derived extends YN_Example_Delegte{
@Override
public String getMe() {
// TODO Auto-generated method stub
return "YN_Example_Delegte_derived";
}
}
三种语言都不支持多继承,但是都支持多接口实现。
关于重载只有Swift硬性要求在重载的函数前加override,否者complier会报错
构造函数
Swift和Objective-C的默认构造函数都是init(),并且可以定制的构造函,只不过名称都必须命名为init只是参数不同,Java和C++一样都是类名开始的函数,但init有返回值,Java构造函数没有。可以参考上面代码。
Java 和 Objective-C默认都会调用父类的构造函数,但swift则不同,默认不调用除非以下情况:子类没有实现任何init构造函数
子类重载或新实现某些构造函数,但子类所有的property都必须要有默认的初始值,也就是在声明时赋的值
Swift 可以重载构造函数,不过必须要加override前缀
类探测API
Java:
用 instanceof 来探测类type
if(example instanceof YN_Example_Class){
System.out.println("is YN_Example_Class");
}
Swift:
用as来探测类type
delegate as YN_Example_Delegte
Objective-C
用isKindOfClass来探测类type
BOOL isClass = [delegate isKindOfClass:[YN_Example_DelegateDerived class]];
访问控制
Objective-C
其访问控制基本和C++一致
@private: 只有这个类可以访问
@protected: 此类和子类可访问
@public: 任何类都可以访问
默认权限是protected
Swift:
private: 只有在本源文件里的代码可以访问
internal: 只有在本module(例如,app,或framework)里可以访问
public: 没有限制,可以作为对外暴漏的接口,例如创建个library
默认权限是internal
Java:
private: 只有这个类可以访问,不可跨包访问
default: 只有本包得类可以访问,默认的类都是default
protected:本类和此类的子类可以访问,跨包可访问
public: 没有限制,所有类可以访问,跨包可以访问
默认权限是default
此文主要讲解不同的语言对类不同的定义和实现,哪些地方不同并且应该注意什么,不过从结构来看,大体语言都很类似。此文并没有讲解的面面俱到,例如教大家如何熟悉语言的基本语法,如何声明变量,如何写控制流,这些都需要读者自己写程序加以熟悉。
由于时间有限,如果有纰漏之处,还望提醒,我会及时改正,谢谢。
PS:如果需要源码,请发信给mailto : syyorient@outlook.com
作者: 隋云怡
[Reference]
https://itunes.apple.com/cn/book/swift-programming-language/id881256329?mt=11
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/I
收起阅读 »
在这个篇章中,我们首先从类定义开始,逐步探讨继承,接口,访问控制
类
如果你学过面向对象编程的语言,你就知道类是个什么概念,简而言之,我们把某种具有共同属性及功能单元的设计蓝图称之为类,它代表的设计定义,而对象就是根据类定义所产生的内存实体。
我现在用一个简单的需求实例来阐明不同的语言是如何实现这个需求的,以便读者可以了解不同的语言是对类如何定义和实现的。
需求:
实现一个类包含4个变量
- mExampleData 是integer类型
- mExampleStr是字符串类型
- mExampleBool 布尔类型
- exampleProperty 是int类型
- 但是exampleProperty是可以通过api进行改变的
包含5个函数
构造函数
- exampleFunc函数没有任何返回值和参数
- exampleFuncWithParameters 没有返回值但是有一个字符串参数,和 布尔型参数
- exampleFuncWithReturnValue 返回值为字符,但没有参数的函数
- classExampleFunc 静态或者类函数
Objective-C 定义
@interface YN_Example_Class : NSObject{
@private
int mExampleData;
@protected
NSString* mExampleStr;
@public
BOOL mExampleBool;
}
-(YN_Example_Class*) init;
@property(nonatomic) int exampleProperty;
-(void) exampleFunc;
-(void) exampleFuncWithParameters:(NSString*) str withBool:(BOOL) boolP;
-(NSString*) exampleFuncWithReturnValue;
+(void) classExampleFunc;
Objective-C 类实现
@implementation YN_Example_Class
-(YN_Example_Class*) init{
mExampleStr = @"YN_Example_Class";
mExampleData = 8;
mExampleBool = YES;
return self;
}
-(void) exampleFunc{
NSLog(@"exampleFunc");
}
-(void) exampleFuncWithParameters:(NSString *)str withBool:(BOOL) boolP{
mExampleStr = str;
mExampleBool = boolP;
}
-(NSString*) exampleFuncWithReturnValue{
return mExampleStr;
}
+(void) classExampleFunc{
NSLog(@"classExampleFunc");
}
-(void) setExampleProperty: (int) data{
if(_exampleProperty < 100){
_exampleProperty = data;
}
}
-(int) getExampleProperty{
_exampleProperty++;
return _exampleProperty;
}
@end
Swift
class YN_Example_class{
var mExampleStr:String
var mExampleData:UInt
var mExampleBool:Bool
var exampleProperty:UInt{
get{
mExampleData++
return mExampleData;
}
set(data){
if(mExampleData < 100){ mExampleData = data } } } init(){ mExampleStr = "YN_Example_class" mExampleData = 8 mExampleBool = true } func exampleFunc(){ println("exampleFunc") } func exampleFuncWithParameters(str:String, boolP:Bool){ mExampleStr = str; mExampleBool = boolP } func exampleFuncWithReturnValue() -> String{
return mExampleStr
}
class func exampleClassFunc(){
println("exampleClassFunc")
}
}
Java
class YN_Example_Class{
private int mExampleData;
protected String mExampleStr;
public boolean mExampleBool;
private int exampleProperty;
static private String gStr;
YN_Example_Class(){
mExampleStr = "YN_Example_Class";
mExampleData = 8;
mExampleBool = true;
setExampleProperty(0);
}
void exampleFunc(){
System.out.println("exampleFunc");
}
void exampleFuncWithParameters(String str, boolean boolP){
mExampleStr = str;
mExampleBool = boolP;
}
String exampleFuncWithReturnValue(){
return mExampleStr;
}
static void classExampleFunc(){
System.out.println("classExampleFunc");
}
public int getExampleProperty() {
exampleProperty++;
return exampleProperty;
}
public void setExampleProperty(int exampleProperty) {
if(this.exampleProperty < 100){
this.exampleProperty = exampleProperty;
}
}
}
属性property/成员变量
Objective-C既提供变量也提供属性的概念,实际上属性就是一种语法规则,属性本身并不存在内存中,但编译器为之合成一个内存实体叫_PropertyName,我们可以简单的理解为是用来替代麻烦的setter和getter操作,编译器会自动合成相应的setter和getter函数,不过属性定义的不同会导致setter和getter实现的不同。
为了简洁,去除混淆,Swift已经不再区分成员变量和property,她应经统一了二者,直接用关键字var来定义。
Swift 定义了两种propertyStored Property -- 可以理解为property直接存储数据
Computed Property -- 可以理解为property不能直接存储数据,而是通过getter,setter来间接存取exampleProperty就是个Computed Property
var example:YN_Example_class = YN_Example_class()
example.exampleProperty = 9;
println(example.exampleProperty)
example.exampleProperty 值为10
Java 和 Objective-c 还有 Swift不同之处就是,语言层面没有property的支持,不过无非就是多写两行代码,用来setter和getter成员变量
类函数/静态函数
直接了当的说,就是直接可以通过类名访问
YN_Example_Class.classExampleFunc
类变量/静态变量
用来做所有实例共享的变量,单例模式
swift,和Objective-C不能声明类变量在类里,Java一定要声明在类里。
Objective-C
#import
#import "objc_language_class.h"
static NSString* gStr;
@implementation YN_Example_Delegate
Swift
import Foundation
private var gStr:String?
类属性,所有的实例都共享同一个类属性,类属性一定是个Computed Property(看上述property属性),类属性的实现需要全局属性的支持,上述就是如何定义一个全局属性来存储数据
类属性例子如下:
class var gProperty:String {
set(g){
gStr = g
}
get{
return gStr!
}
}
Java
class YN_Example_Class{
static private String gStr;
接口,继承,重载
Swift
import Foundation
protocol YNExample_Protocol{
func getMe() -> String
}
class YN_Example_Delegte : YNExample_Protocol{
var myproto:UInt
var myStr:String
init(){
myproto = 3;
myStr = String()
}
func getMe() -> String{
return "YN_Example_Delegte"
}
}
class YN_Example_Delegte_derived : YN_Example_Delegte{
// without override, complier will give the error
override func getMe() -> String{
return "YN_Example_Delegte_derived"
}
}
Objective-C
#ifndef objc_language_class_objc_language_class_h
#define objc_language_class_objc_language_class_h
@protocol YN_Example_Protocol
-(NSString*) getMe;
@end
@interface YN_Example_Delegate : NSObject
NSString* myStr;
}
@property int myprop;
-(YN_Example_Delegate*) init;
@end
@interface YN_Example_DelegateDerived : YN_Example_Delegate;
@end
#endif
static NSString* gStr;
@implementation YN_Example_Delegate
- (NSString*) getMe{
return @"YN_Example_Delegate";
}
-(YN_Example_Delegate*) init{
myStr = [[NSString alloc] initWithUTF8String:""];
return self;
}
@end
@implementation YN_Example_DelegateDerived
- (NSString*) getMe{
myStr = @"";
return @"YN_Example_DelegateDerived";
}
@end
Java
interface YNExample_Protocol{
String getMe();
}
class YN_Example_Delegte implements YNExample_Protocol{
@Override
public String getMe() {
// TODO Auto-generated method stub
return "YN_Example_Delegte";
}
}
class YN_Example_Delegte_derived extends YN_Example_Delegte{
@Override
public String getMe() {
// TODO Auto-generated method stub
return "YN_Example_Delegte_derived";
}
}
三种语言都不支持多继承,但是都支持多接口实现。
关于重载只有Swift硬性要求在重载的函数前加override,否者complier会报错
构造函数
Swift和Objective-C的默认构造函数都是init(),并且可以定制的构造函,只不过名称都必须命名为init只是参数不同,Java和C++一样都是类名开始的函数,但init有返回值,Java构造函数没有。可以参考上面代码。
Java 和 Objective-C默认都会调用父类的构造函数,但swift则不同,默认不调用除非以下情况:子类没有实现任何init构造函数
子类重载或新实现某些构造函数,但子类所有的property都必须要有默认的初始值,也就是在声明时赋的值
Swift 可以重载构造函数,不过必须要加override前缀
类探测API
Java:
用 instanceof 来探测类type
if(example instanceof YN_Example_Class){
System.out.println("is YN_Example_Class");
}
Swift:
用as来探测类type
delegate as YN_Example_Delegte
Objective-C
用isKindOfClass来探测类type
BOOL isClass = [delegate isKindOfClass:[YN_Example_DelegateDerived class]];
访问控制
Objective-C
其访问控制基本和C++一致
@private: 只有这个类可以访问
@protected: 此类和子类可访问
@public: 任何类都可以访问
默认权限是protected
Swift:
private: 只有在本源文件里的代码可以访问
internal: 只有在本module(例如,app,或framework)里可以访问
public: 没有限制,可以作为对外暴漏的接口,例如创建个library
默认权限是internal
Java:
private: 只有这个类可以访问,不可跨包访问
default: 只有本包得类可以访问,默认的类都是default
protected:本类和此类的子类可以访问,跨包可访问
public: 没有限制,所有类可以访问,跨包可以访问
默认权限是default
此文主要讲解不同的语言对类不同的定义和实现,哪些地方不同并且应该注意什么,不过从结构来看,大体语言都很类似。此文并没有讲解的面面俱到,例如教大家如何熟悉语言的基本语法,如何声明变量,如何写控制流,这些都需要读者自己写程序加以熟悉。
由于时间有限,如果有纰漏之处,还望提醒,我会及时改正,谢谢。
PS:如果需要源码,请发信给mailto : syyorient@outlook.com
作者: 隋云怡
[Reference]
https://itunes.apple.com/cn/book/swift-programming-language/id881256329?mt=11
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Introduction/I
收起阅读 »
自组织是不是团队管理的乌托邦?
对于很多管理者,最幸福的事,莫过于做到名义上管理一个团队,但实际上什么都不需要做。他所带的逆天团队还可以成果迭出。对于团队中的成员来讲,如果他可以做什么都不被管,做什么都有人帮,那真是可以做梦也会笑醒的。这样的团队存在么?自组织的团队据说就可以,不管你信不信,所以我看了这本书。
我想要谈论它,还有个人变动的原因。我曾经待过养老的公司,也从热衷微管理的公司走过。几个月前又刚从一个大公司出来,到了一个小小的初创企业。环境的变化会让人变得更加敏感,上面那些变化让我得以对比思考管理的各个方面。其实那个大公司也不大,不过大公司很多问题它都有而已。(大公司病嘛,等有时间再讨论)
很多时候,加入一个有问题的团队,就像旅行路上经过的沼泽地。为了生存你只能滚着前进,与沼泽保持尽可能大的接触面,然后弄一身脏泥巴。等到滚多了,再加上勤奋努力,很容易的,你会成为一个滚泥巴专家。
你很可能忘记,本来只是为了经过。你想要去的,是美好的远方。
这只是个提醒。也许你只想研究技术,但你值得知道,哪些是该受的,哪些是该得的。如果你还想管理,不想掉进泥沼里,你更不应该停止对好团队的追求。这本书也就更值得一看。
这是一篇读书笔记,书是《管理3.0》。这本书都很多理论和观点有意思,我会谈谈我喜欢的一些,包括自组织相关的思维方式、创新问题、开发模式和团队管理。也会补充我认为缺失的一环。最后是一个问题,也是对个人最重要的问题,你适合什么样的团队?
思维方式
值得一提的是系统思维,来源于彼得·桑吉的《第五项修炼》,“将问题视为整个系统的一个组成部分,侧重于组织内的循环关系和非线性因果关系”(P47)。其实就是不要将一个运动的整体割裂对待。当一个人作为个体存在于团队内部时,其他人都是环境变量。你做的所有动作,都会对其他人产生影响。要认识到自己能力可以扩展的边界。当然重要的是,不要跟猪做队友。
这本书我也是几年前看过,关于系统内部正向反向反馈的说法印象深刻。只要反馈环建立起来,就会有习惯的力量帮助你或者阻碍你。所以要关心当前团队的运转情况,强化正向反馈。
其次是关于简化论的说明,“找到了心脏病的诱因(简化论),并不意味着可以制造一个不生病的心脏(构建论)”(P9)。这也是很多人知道自己团队的问题,却不知道如何构建一个好团队的原因。简化论是很多人思考停止的地方,因为对于吐槽来说足够了,殊不知后面才是更有价值的。
关于创新
1. 创新需要土壤
在这个人人视抄袭为平常的国家里,创新已经稀有到要比鬼还少了。为什么中世纪后有文艺复兴,为什么有春秋战国可以百家争鸣?我与作者一样,非常赞同土壤的说法,“复杂性系统方法认为创新是可遇而不可求的,它是水到渠成而自然涌现的结果。得先有涌现所需的土壤,才能期待它的发生”(P52)。就在去年提这说法的时候(内部在创新方面一直有讨论),我是希望说明创新是需要多数人参与,发生在少数人身上的,要给工程师们空间,不要太多管理操作。
前文有个关键词是“涌现”,它在做属性时,按照定义是“当一个系统的属性不能追溯到系统的任何独立部分时,我们就称之为涌现属性”,就像“流动性是水的涌现属性,文化是群体的涌现属性”;做动词时,强调的是自然发生,整体性质的体现,比如在说涌现式设计则强调 “最好的架构不是预先定义好的(可能有一个基本的形式),它可以在产品开发过程中逐步浮现出来”(P21)。这里隐含的意思,我理解,创新是一种整体表现,也是需要用结果后验的,我们要做和能做的只是增大其可能性而已。
2. 包容式多样性
作者更进一步,强调了土壤的肥沃问题,“团队的多样性能够显著增加其创造力。但必须有足够的共同点加一些制衡,即包容式多样性”(P60)。做事要找志趣相投的人只是其一,只有能力互补,才容易有火花碰撞出来。这个在我组织过的黑客马拉松、参加过的内部创新大赛表现得十分明显,如果只是技术人组队,很容易陷在 Geek 圈里,玩玩原型做技术储备可以,离产品化还是甚远。
说起来,此次微博的升级视频,我自认为是个小的正向例子。那是有一天,设计师跑过来问说如果想利用每个人的信息来生成个性化的视频,能不能搞得定。如果没有服务的话,他只能每个人手动生成了,可能几千个。我说没有经验,但可以试试看。于是有了最后的 AppleScript + Shell + JavascriptX 的奇怪组合。我想说的是,由于有设计师的参与,由于不同领域的人共同对技术应用边界的摸索,才有了产品质量的成果出来。事实上效果还是比较令人满意的,没看过的可以看看这里:http://www.weibo.com/1819367693/Bp6II3a1V
我说这个例子是偶然,因为从想法到实现还有相当长的路要走。大多数想法都像书中所说,"一个很好地想法可能在公司内被踢来踢去好几年而得不到利用,这不是因为大家没有看到它的好处,而是因为不知道应该由谁来负责将它踏实地落到行动上”(P63)。我是负责后端团队,通常不会与设计师打交道,与这位设计师认识纯粹是由于组织黑客马拉松的时候他帮忙设计制作了宣传视频。
3. 勒曼第六法则
对于一个业务基本稳定的公司,创新难还有一个重要原因,就是勒曼第六法则,"许多软件产品并没有演化使自身变得更好,他们演化之目的只是为了尽量不被用户抛弃(不可避免)”(P310),大多时候,它们 “为了将客户满意度维持在相同的水平,要不断增加产品的功能性”。制定 KPI 当然要 SMART 不是么?你见过 KPI 说要将创新水平提高几个百分点么?。。。
说回创造力,还有个有趣的三阶段理论。先是前习俗创造力,属于儿童的,是自发性的;然后是习俗创造力,一般在7-11岁左右,开始是真正的思考,了解世界的边界;最后是后习俗创造力,也是创新开始的地方(P67)。最后一阶段的时候,便是讲初心的地方,“即使知道有实实在在的约束,也可以像小孩子一样充满想象力”(P70)。这个理论很类似的一个学习理论,会在后面人员培养处再讲。这里主要说明边界学习对创新的影响。边界客观存在,不要让它制约你的想法。
4. 罗杰斯创新曲线
罗杰斯有个创新曲线,说的是团队人群分布。“创新者2.5%、早期采用者13.5%、早期采用人群34%、后期采用者34%、迟缓者16%。改变要从渴望尝试新事物的创新者入手,一次努力推动后面人群。忽略行动迟缓者,他们会一直抵制改变,直到其他所有人都采用”(P335)。这个比例在不同团队肯定会有所差异,你只要记住,不要想当然所有人都愿意改变就行。然后在尝试新事物的时候,不要怕反对声音。
管理者要做的就是发现这种萌芽,帮助其成长而不被恶劣环境影响。对那些不愿意改变的力量进行驱赶,甚至可以让他们适当痛苦。“让停滞痛苦不堪”(P336)说的就是这种迂回方式,有些人吃一堑才能长一智。
手中的鞭子和驴头前面的胡萝卜同样有用。
开发模式
书中提了两家的宣言:
敏捷软件开发(P19)
“个人和交互 胜于 流程和工具;可以工作的软件 胜于 复杂的文档”;
“虽然右项也具有价值,我们认为左项具有更大的价值”
精益软件开发(P24)
“可以工作的软件犹不足,尚需精益求精;响应变化犹不足,尚需稳步增加价值”
“在追求左项的过程中,我们发现右项亦不可或缺”
说实话我对这些模式都不感冒,这方面我更功利,我觉得不同发展阶段需要不同的开发方式。特别在互联网行业,一个团队的开发活动需要在高速度和高质量方面反复切换。
在团队人员紧缺的时候,你要有速度意识,这是质量下降几乎是必然的;如果人员稍微充沛,就要重新思考质量问题,承认低质量软件的存在与不合理之处,持续改进。因为当用户请求量上来,几乎所有的问题都会显现出来(墨菲定律),你要做的是在Bug 酿成灾难之前消灭掉。
我在心里是渴望精益的。
关于不同开发方式其实网上有很多争吵,极端言论也比比皆是,但看两者的宣言,你很难产生针锋相对的感觉。为什么?因为很多人在交流时急于表达自己的观点,(或有意或无意地)不会强调与对方观点的相同之处,这在沟通中造成了很多无谓的争执。相互熟悉的人之间也无法避免这种情况,更别说背景差异的团队成员之间了。
因此我也更加赞同敏捷宣言里对个人和交互的强调。有效沟通才可能让改进发生。
团队管理
1. 动机管理,以人为本
“人是任何软件项目中最复杂的元素”(P62),确实所有对时间和项目的管理,都是对人的管理。而其中最难的部分,是人员的动机管理,“所有管理者首先关注的应该是激励员工以确保他们愿意全心全意做事,而这一切,都需要动力”(P56)。如果一个人已经与团队贰心,那你实质上已经失去他了。所有的CEO(大忽悠)都会谈价值观,谈企业文化,也是这个目的。
我们把只画饼的宣传叫做忽悠,是在强调奖励的必要性。但奖励最重要的目的是要变成激励,书里提醒 “不要把为团队聚餐买单当成庆祝里程碑式胜利表达谢意的方式。为团队买单是为了解决人们社交和相互联系的需求”(P85)。而且“不要应某些员工的要求而取悦他们,从而引入规则、实践和政策。真正的目的必须是引入秩序和稳定性”。
后面这句话我不知道是否理解得准确,我只能说部分同意。我同意规则和秩序,只要他们是公平的,而不在意是否为了取悦员工。事实上我认为,除了真正的团队目标,其他所有事情都应该为了成员愉悦而做,这样才能最大发挥成员的价值。
在某种程度上,管理应该是服务性的。
2. 适当授权,解除电网
如果说动机管理搞定了人员能动性的问题,那么授权管理就是为解决工作效率的问题。“智慧的控制要做到无形中的影响。决策的制定也应该来自人员之间的互动,而非来自我的权威”(P109),这方面英文Empowerment更容易理解一些。把权力下放给员工,但不要忘记目的是“不是增强激励,而是改善管理”。当然,授权也是分级别的:告知、贩卖、咨询、商定、建议、征询、委托(P125)。当然,“这并不意味着需要分级的架构”。
授权不当就会造成隐形电网的存在。“当管理者对下属赋能时,他们并不会清楚地点明其职权界限,这意味着人们往往只能摸着石头过河,磕磕绊绊,中间还伴随着情感伤害”,“它是时间和资源的双重浪费。屡屡触及隐形电网的经历会扼杀人们的积极性”(P173)。这方面我倒是体会颇深,因为不同等级的信息不对称和相互之间的沟通问题,会造成很多理解上的偏差,碰上电网是非常可能发生的。这个事故的预防(降低频次)以及处理好坏(减轻伤害),不同的管理者之间差距非常明显。坏的结果多了,就很容易造成多一事不如少一事的氛围,离自组织也必然是越来越远。
3. 运用同侪压力
适当授权之后,就可以放手团队去解决问题了,也是时候做好准备接受成员犯错的考验了。“若要有所作为,必先磨练耐性”(P130),而且“当已授权的团队走进办公室请求你决定某件事情时,要想办法让他们自己解决问题”(P135)。不要做保姆,不止是减轻当前的负担,重要的是要防止成员思维惰性,养成依赖别人思考的习惯。
团队可以自行运转,来源于成员想一起共事的意愿,但意愿能促进协作么?书里提到的“同侪压力”,很好的说明了这一点。当你做事的时候,大家都在看着你。你的能力展现无遗,好与不好都在每个人心里。这种社会压力让成员之间可以互相驱动,也是规则发挥作用的地方。
要让同侪压力转化成动力,要注意“与那些试图破坏这些团队的人做斗争”,因为“只有在人们想要加入某个团体时,该团体的社会压力才会起作用”(P224)。最好的方式是,“建立团队、设定目标、再退后一步”。如果有人要破坏团队的规则,那么“找他谈一谈”,不奏效就“再谈一谈”,还不行就“请他离开”。团队是作为团队而存在,而不是为某个人。我的经验是,在这方面越果断越好。
如果怕请人离开,那就把好入门的关。“在允许某人加入(有挑战性的)项目之前,必须对他进行适当的测试”(P185),这是书中根据荷兰的低交通事故率总结的经验。我在微博也一直坚持,没有经过内部新兵训练以及高性能服务设计演练的人,再忙也不会让其进入正式的开发序列。这是对当事人负责,如果他因为经验缺乏酿成事故,以后做事也会畏手畏脚;更是对团队其他人负责,团队不应该因为某个人的问题而受拖累。互联网应用,完成功能只是基本的,还要保证性能、可用性等很多方面。问题在上线前解决要远比上线后简单。而一旦上线,就是7x24小时的服务,出了问题基本是不搞定不眠不休的(也是苦逼之处)。
一句话,宁肯一堆人累,也不要被一个人坑。
4. 保护团队成员
“管理者既要促进自组织,还要保护大家不受伤害”(P175)。我本来觉得这是对管理者基本的要求,但是在一次内部讨论中,出现了截然不同的相反观点。有人反问说,如果一个人不能很好地保护自己,总需要人呵护,如何期望他在对外合作中,甚至在公司外部的行业洗礼中生存下来?
如果说从个人角度来看待这个问题,尚可以理解的话,从团队角度看来我是完全不赞同。组成团队的主要目的是为了完成共同的目标,而不是为了让个人接受锻炼。而在完成目标过程中成员之间互相配合互相帮助,也是团队存在的价值所在。让个人成长,在团队中发挥更大价值,跟让个人接受磨练以成长到更高层次,是两件事。
特别是当管理者不是领导整个公司的时候(个人还没体验过),如果不喜欢随波逐流,期望自己的团队在公司内可以变得更好,举例来讲先变成自组织模式,更要注意这点。如果让团队中的普通成员去接受不必要的压力,要么他觉得太困难无法做事,要么他适应下来,结果把外界的习惯带回当前小团队,所有这些都会对团队向自组织转变起到负面作用。这就是环境的力量。用系统思维方式来看,如之前所述,就是不要让环境对团队改进产生负向反馈。
5. 开始,不要停止改进
开始改变并不意味着就从此一路向前了。一切都在变化之中,环境、人员甚至是目标都有可能随时改变。而整个团队也很可能进入局部优化的状态,事情做得马马虎虎,也总是问题不断。这时候就要考虑模拟退火了。“先加热再冷却会改变金属的性质,比如强度和硬度,这种技术叫退火。复杂性系统中,错误和噪声-通常由环境引发,会扰乱系统并使之拜托局部优化,在这之后,系统更容易找到更好地位置安定下来。”(P339)
用错误和噪声来改善团队,说起来有些奇怪,再看一下模因论可能就好理解了:“不要求模因组内所有思想、概念和实践都必须是有益的。其中的一些有害处,但由于同属于一个模因组,负面思想也会帮助正面思想互相复制,这样便可以中和负面影响。危险范例:没有确凿证据能够证明代码集体所有制的价值,但是此实践似乎能增强其他敏捷实践,所以复制它也不会有什么坏处。”(P204)
如果发生了局部优化,我理解很可能是某些方法措施有了什么副作用。这时就要重新审视团队,是否有规则没有实施或者被错误的实施了;如果发现新规则不适应当前的团队,要继续尝试其他可能。
不恰当的错误补救例子很多,比如弹性制度下一个人钻空子不好好工作变为全部人强制坐班,或者一个人在项目过程中出现沟通问题,全部人天天一起开会。这种情况还有很多,说个亲身经历的创新相关的例子。
曾经有一次大家想搞创新,但是在如何创新上分化成了两派。一派就是我刚才提的土壤论,希望解放大多数同学的创造力;另一派则是规划论,认为创新是可以规划出来,用项目来管理的。讨论结果是决定先用后者。不出意料的是,执行规划论的结果就是,大家只是将其看做是另一项工作任务而已。问题是本来都忙不过来了,还要再进行一项自己毫不在意的事情,即使它的名字叫创新,会有什么动力呢。因此这个行动基本上还未开始就结束了。然后,整个事情最奇怪的地方出现了,再没有人谈论创新问题,好像不存在这件事情一样。
举这个例子,不是说理念分歧的问题,而是整个团队在失败尝试之后的总结和使用方式,也就是如何获取经验的问题。当一件事情失败之后,不要怕把事情放在台面上讨论,特别是尝试,大部分都是要失败的。如果失败了而不总结,才是对自己最大的不负责任。总结可以得到经验,但要时刻对经验保持警惕。好的经验固然让人成熟进步,错误经验却让人裹足不前。
攻打威虎山的时候,正面土匪多有坦克,小分队又从背面上山了不是?
这就够了么
思想是好思想,行动也有方法,就能实现么?在我看来,至少还有两个问题需要解决才行,也是书上没有讲的。一是目标管理,二是人员培养。
1. 目标管理
这个目标管理不同于前面说的动机管理。动机管理是解决愿不愿意做的问题,目标是解决要做什么的问题。一个团队是自组织的,意味着每个人都是自主的而不是被管理的,好的时候会像大雁一样跟随头雁,不好的时候就各自为政,一盘散沙。
因此目标管理是不可忽略的。咦,那还怎么自组织?我个人理解,这不是像KPI 一样的设定与分解,而是一起来讨论团队目标,然后自我选择与设定的过程。事情一样,但规则不同。
2. 人员培养
并不是每个人都适合自组织团队。有的人能力不足,无法自主完成任务;有的人驱动不足,需要监督和指导;有的人悟性不够,无法自己发现问题。你也许会说,那就不是我们需要的人啊。
那只是理想。什么都是变化的,市场是变的,目标是变的,人也是如此。好的方面是,他的能力会提高,你有可能培养出一个合格的人;不好的方面是,他可能离开,你会流失已有的成员。
所以现实是,在你集合一群合格的人之前,你有漫长的路要走,而你的船不能停或掉头。因此,人员培养是必须要重视的。但其实团队能给的帮助有限,只有足够的压力和适当的指导。适当的指导让其找到方向,适当的压力让其不要懈怠,如此而已。
人必先自救,而后人救之。这句话放在这里依然是适用的。
自组织是不是团队管理的乌托邦
你可能会想,有个先进社会还说达到之后人人富足,衣食无忧,我不也天天在初级阶段生活么?自组织会不会成为团队管理的乌托邦?坦率来讲,我也不知道。我曾经看过(自以为)自组织的影子,但是没有完全实现。
每个人有特定适应的团队,如果你怀疑自组织,你可以不去实践,但你肯定需要寻找自己心仪的团队,不管是加入还是组建。我只是知道我喜欢这样的团队,我也愿意尝试。
你要考虑的问题其实是,你适合什么样的团队。想一想,
你是否能够把握自己的方向?
你是否希望不再被指手画脚?
你是否能够自学愿意帮助他人?
你希望找到一群跟你一样的人?
...
如果是的话,那就值得一试。
我们正在试。
原文: http://ericliang.info/is-self-organization-utopia-of-group-management/
作者: 梁宇鹏|
收起阅读 »
环信SDK与Apple Watch的结合系列讲解(3)
第3章主要介绍怎样在Watch App的页面上显示iPhone程序里的数据。主要操作的是“EMWatchOCDemo WatchKit Extension”这个文件夹,附源码EMWatchOCDemo。
如果你已经看过我在第1章推荐的blog,应该明白这个target主要是负责逻辑的,从iPhone App中获取数据,调动Watch App显示数据。
默认是这个样子的
一、WathKit定义了一些专门用于Watch App的类,与UIKit的对比如下图
二、整合Watch App和iPhone App
1、新建Controller
根据Interface.storyboard,我需要新建5个Controller。右键---New File---Cocoa Touch Class
新建的类默认有三个方法,[-awakeWithContext:]相当于[-viewDidLoad],[-willActivate]相当于[-viewWillAppear],[-didDeactivate]相当于[-viewDidDisappear],“相当于”一下是不是就很容易理解每个方法中能进行什么操作了?
建好这5个Controller之后,再次打开Interface.storyboard,在每个storyboard Controller的Class属性中填写对应的类名,这种操作对于熟悉storyboard的开发者来说,应该都不陌生。
附图一张
2、将自定义的类与storyboard关联起来之后,继续关联其他的控件。
声明插件变量Table,并在storyboard中进行关联。
@property (weak, nonatomic) IBOutlet WKInterfaceTable *table;
创建自定义的Table Row Controller,右键---New File---Cocoa Touch Class---Subclass of “NSObject”,声明插件变量Label,在storyboard中将Table Row Controller和Label进行关联。要记得填写Table Row Controller的Identifier,在加载数据时会用到这个属性。
3、接下来要进行每个页面的数据获取了,我是在[-awakeWithContext:]中进行的数据获取。
WKInterfaceController有个类方法[+ openParentApplication: reply:],用于向对应的iPhone App发起申请。
而对应的iPhone App要想检测到这个请求,需要在AppDelegate中监听 [- application: handleWatchKitExtensionRequest: reply:].
以菜单页面MenuController为例,当页面加载时要先向iPhone App发起获取是否登录的申请,iPhone App收到申请,将是否登录的值返给WatchKit Extension;如果没有登录,页面上显示“登录”选项,如果登录了,显示“会话”“好友”“群组”三个选项。
MenuController:
[WKInterfaceController openParentApplication:@{@"action":@"isLogined"} reply:^(NSDictionary *replyInfo, NSError *error) {
BOOL isLogined = NO;
if ([replyInfo count] > 0) {
isLogined = [[replyInfo objectForKey:@"isLogined"] boolValue];
}
if (isLogined) {
NSDictionary *conversationInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"会话", @"title", nil];
NSDictionary *friendInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"好友", @"title", nil];
NSDictionary *groupInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"群组", @"title", nil];
[self.dataSoure addObject:conversationInfo];
[self.dataSoure addObject:friendInfo];
[self.dataSoure addObject:groupInfo];
NSInteger count = [self.dataSoure count];
//@"RowType2Controller"就是上边提到的Table Row Controller 的Identifier属性
[self.table setNumberOfRows:[self.dataSoure count] withRowType:@"RowType2Controller"];
for (int i = 0; i < count; i++) {
RowType2Controller *rowController = [self.table rowControllerAtIndex:i];
NSDictionary *dic = self.dataSoure[i];
NSString *title = dic[@"title"];
[rowController.titleLabel setText:title];
}
}
else{
//@"RowType2Controller"就是上边提到的Table Row Controller 的Identifier属性
[self.table setNumberOfRows:1 withRowType:@"RowType2Controller"];
RowType2Controller *rowController = [self.table rowControllerAtIndex:0];
[rowController.titleLabel setText:@"登录"];
}
}];
AppDelegate
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
if ([userInfo count] > 0) {
NSString *actionString = [userInfo objectForKey:@"action"];
EaseMob *easemob = [EaseMob sharedInstance];
if ([actionString isEqualToString:@"isLogined"]) {
reply(@{@"isLogined":[NSNumber numberWithBool:[easemob.chatManager isLoggedIn]]});
}
}
4、获取到了数据,接下来要调用Watch App显示数据了。
显示数据主要用到了WKInterfaceTable。WKInterfaceTable相对于UITableView而言,能调用的接口少的可怜
WKInterfaceTable.h
WK_CLASS_AVAILABLE_IOS(8_2)
@interface WKInterfaceTable : WKInterfaceObject
- (void)setRowTypes:(NSArray *)rowTypes; // row names. size of array is number of rows
- (void)setNumberOfRows:(NSInteger)numberOfRows withRowType:(NSString *)rowType; // repeating row name
@property(nonatomic,readonly) NSInteger numberOfRows;
- (id)rowControllerAtIndex:(NSInteger)index;
- (void)insertRowsAtIndexes:(NSIndexSet *)rows withRowType:(NSString *)rowType;
- (void)removeRowsAtIndexes:(NSIndexSet *)rows;
- (void)scrollToRowAtIndex:(NSInteger)index;
@end
WKInterfaceController中
上一步中的代码示例已经给出了WKInterfaceTable使用方式,具体代码请看demo。
5、每个单独的页面都写好了,现在要让他们动起来。
WatchKit提供了三类页面导航方式。
第一种UINavigationController 控制的类似栈的导航方式,相应接口
- (void)pushControllerWithName:(NSString *)name context:(id)context; // context passed to child controller via initWithContext:
- (void)popController;
- (void)popToRootController;
第二种 modal 形式,相应接口
- (void)presentControllerWithName:(NSString *)name context:(id)context; // modal presentation - (void)dismissController;
第三种 类似 UIPageController 的分页式导航,相应接口
- (void)presentControllerWithNames:(NSArray *)names contexts:(NSArray *)contexts; // modal presentation of paged controllers. contexts matched to controllers - (void)becomeCurrentPage;
其中的“WithName(s):”参数就是每个控件在storyboard中设置的Identifier属性。
好了,就先写这么多吧,后期有时间会继续补充。
作者: 谢雅杰
环信SDK与Apple Watch的结合系列讲解(1)
环信SDK与Apple Watch的结合系列讲解(2)
收起阅读 »
如果你已经看过我在第1章推荐的blog,应该明白这个target主要是负责逻辑的,从iPhone App中获取数据,调动Watch App显示数据。
默认是这个样子的
一、WathKit定义了一些专门用于Watch App的类,与UIKit的对比如下图
二、整合Watch App和iPhone App
1、新建Controller
根据Interface.storyboard,我需要新建5个Controller。右键---New File---Cocoa Touch Class
新建的类默认有三个方法,[-awakeWithContext:]相当于[-viewDidLoad],[-willActivate]相当于[-viewWillAppear],[-didDeactivate]相当于[-viewDidDisappear],“相当于”一下是不是就很容易理解每个方法中能进行什么操作了?
建好这5个Controller之后,再次打开Interface.storyboard,在每个storyboard Controller的Class属性中填写对应的类名,这种操作对于熟悉storyboard的开发者来说,应该都不陌生。
附图一张
2、将自定义的类与storyboard关联起来之后,继续关联其他的控件。
声明插件变量Table,并在storyboard中进行关联。
@property (weak, nonatomic) IBOutlet WKInterfaceTable *table;
创建自定义的Table Row Controller,右键---New File---Cocoa Touch Class---Subclass of “NSObject”,声明插件变量Label,在storyboard中将Table Row Controller和Label进行关联。要记得填写Table Row Controller的Identifier,在加载数据时会用到这个属性。
3、接下来要进行每个页面的数据获取了,我是在[-awakeWithContext:]中进行的数据获取。
WKInterfaceController有个类方法[+ openParentApplication: reply:],用于向对应的iPhone App发起申请。
而对应的iPhone App要想检测到这个请求,需要在AppDelegate中监听 [- application: handleWatchKitExtensionRequest: reply:].
以菜单页面MenuController为例,当页面加载时要先向iPhone App发起获取是否登录的申请,iPhone App收到申请,将是否登录的值返给WatchKit Extension;如果没有登录,页面上显示“登录”选项,如果登录了,显示“会话”“好友”“群组”三个选项。
MenuController:
[WKInterfaceController openParentApplication:@{@"action":@"isLogined"} reply:^(NSDictionary *replyInfo, NSError *error) {
BOOL isLogined = NO;
if ([replyInfo count] > 0) {
isLogined = [[replyInfo objectForKey:@"isLogined"] boolValue];
}
if (isLogined) {
NSDictionary *conversationInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"会话", @"title", nil];
NSDictionary *friendInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"好友", @"title", nil];
NSDictionary *groupInfo = [NSDictionary dictionaryWithObjectsAndKeys:@"群组", @"title", nil];
[self.dataSoure addObject:conversationInfo];
[self.dataSoure addObject:friendInfo];
[self.dataSoure addObject:groupInfo];
NSInteger count = [self.dataSoure count];
//@"RowType2Controller"就是上边提到的Table Row Controller 的Identifier属性
[self.table setNumberOfRows:[self.dataSoure count] withRowType:@"RowType2Controller"];
for (int i = 0; i < count; i++) {
RowType2Controller *rowController = [self.table rowControllerAtIndex:i];
NSDictionary *dic = self.dataSoure[i];
NSString *title = dic[@"title"];
[rowController.titleLabel setText:title];
}
}
else{
//@"RowType2Controller"就是上边提到的Table Row Controller 的Identifier属性
[self.table setNumberOfRows:1 withRowType:@"RowType2Controller"];
RowType2Controller *rowController = [self.table rowControllerAtIndex:0];
[rowController.titleLabel setText:@"登录"];
}
}];
AppDelegate
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply
{
if ([userInfo count] > 0) {
NSString *actionString = [userInfo objectForKey:@"action"];
EaseMob *easemob = [EaseMob sharedInstance];
if ([actionString isEqualToString:@"isLogined"]) {
reply(@{@"isLogined":[NSNumber numberWithBool:[easemob.chatManager isLoggedIn]]});
}
}
4、获取到了数据,接下来要调用Watch App显示数据了。
显示数据主要用到了WKInterfaceTable。WKInterfaceTable相对于UITableView而言,能调用的接口少的可怜
WKInterfaceTable.h
WK_CLASS_AVAILABLE_IOS(8_2)
@interface WKInterfaceTable : WKInterfaceObject
- (void)setRowTypes:(NSArray *)rowTypes; // row names. size of array is number of rows
- (void)setNumberOfRows:(NSInteger)numberOfRows withRowType:(NSString *)rowType; // repeating row name
@property(nonatomic,readonly) NSInteger numberOfRows;
- (id)rowControllerAtIndex:(NSInteger)index;
- (void)insertRowsAtIndexes:(NSIndexSet *)rows withRowType:(NSString *)rowType;
- (void)removeRowsAtIndexes:(NSIndexSet *)rows;
- (void)scrollToRowAtIndex:(NSInteger)index;
@end
WKInterfaceController中
上一步中的代码示例已经给出了WKInterfaceTable使用方式,具体代码请看demo。
5、每个单独的页面都写好了,现在要让他们动起来。
WatchKit提供了三类页面导航方式。
第一种UINavigationController 控制的类似栈的导航方式,相应接口
- (void)pushControllerWithName:(NSString *)name context:(id)context; // context passed to child controller via initWithContext:
- (void)popController;
- (void)popToRootController;
第二种 modal 形式,相应接口
- (void)presentControllerWithName:(NSString *)name context:(id)context; // modal presentation - (void)dismissController;
第三种 类似 UIPageController 的分页式导航,相应接口
- (void)presentControllerWithNames:(NSArray *)names contexts:(NSArray *)contexts; // modal presentation of paged controllers. contexts matched to controllers - (void)becomeCurrentPage;
其中的“WithName(s):”参数就是每个控件在storyboard中设置的Identifier属性。
好了,就先写这么多吧,后期有时间会继续补充。
作者: 谢雅杰
环信SDK与Apple Watch的结合系列讲解(1)
环信SDK与Apple Watch的结合系列讲解(2)
收起阅读 »
环信SDK与Apple Watch的结合系列讲解(2)
这一篇主要是介绍怎么拖apple watch上的相关页面,附源码EMWatchOCDemo。
需要在工程中的“EMWatchOCDemo WatchKit App”中进行操作,该文件夹的结构如图
WatchKit几乎不允许直接coding页面,只能在storyboard上拖来拖去,对于我这种习惯直接coding页面的人来说,真真是极痛苦的。
一、确定apple watch上的操作流程
首先,我想要一个菜单页面,跟iPhone程序对应,在环信SDK未登录情况下,显示登录选项;在环信SDK登录情况下,有三项:会话,好友,群组。
然后,登录选项点击之后,能启动iPhone进行登录操作,因为watch的页面实在是太小了,没有键盘,环信SDK也不支持指纹或者声音登录。
再然后,登录操作成功之后菜单页面显示三项选项,每项点击都可以进入对应页面,显示相应的数据。
最后,进入聊天页面,可以显示已有的聊天记录,可以发送表情。本来想加入发送心跳的功能,但是发现WatchKit不支持watch app获取硬件传感器参数,而且找到了一篇不错的自问自答文章http://www.cocoachina.com/ios/20150323/11396.html
二、在storyboard上拖来拖去
1、点击文件夹下的interface.storyboard文件,右边会出现相应的视图显示。根据第一步的思路,直接拖4个Interface Controller, duang~,duang~,duang~, duang~,就成这个样子了
每个Interface Controller的Identifier属性强烈建议写上,为什么请见第3章。为了便于区分每个controller,个人习惯给每个Interface Controller都填写Title属性。
2、想了一下菜单,会话,好友,群组的功能,显示用table是再好不过的了,直接再拖table到每个controller,然后你就会发现一些奇怪的东西,比如这样的
Table Row Controller是类似于自定义UITableViewCell的东西,默认是继承于NSObject。
Group应该算是个新概念,它的作用是将页面上的控件分块。WatchKit不支持设置frame,目前我知道的,只是支持在横向上“左中右”,在纵向上“上中下”,支持设置大小。如果你想实现一个九宫格样式的页面,比较讨巧的方式是直接用三行Table Row Controller,每行三个button。不讨巧的方法,抱歉,我发现不管怎样排列组合这些属性,都出不来九宫格。
3、在会话页面显示和谁聊天的username,在好友页面显示好友的username,在群组页面显示群组的名字,用Label通通能搞定。
我在group里拖一个label,默认就是这个样子
自己选下不同的选项,看下效果,应该就能理解了。
4、聊天页面比较特殊,需要自己发送的在左边显示,对方发的在右边显示。不过这个很容易实现,只需要发送的和接收的放在两个Table Row Controller里,然后设置空间的Position即可,示意图:
拖完上边这些,页面就差不多了 。在Scheme中选择EMWatchOCDemo WatchKit App,编译 OK,可以进行第3章了。
环信SDK与Apple Watch的结合系列讲解(1)
环信SDK与Apple Watch的结合系列讲解(3) 收起阅读 »
需要在工程中的“EMWatchOCDemo WatchKit App”中进行操作,该文件夹的结构如图
WatchKit几乎不允许直接coding页面,只能在storyboard上拖来拖去,对于我这种习惯直接coding页面的人来说,真真是极痛苦的。
一、确定apple watch上的操作流程
首先,我想要一个菜单页面,跟iPhone程序对应,在环信SDK未登录情况下,显示登录选项;在环信SDK登录情况下,有三项:会话,好友,群组。
然后,登录选项点击之后,能启动iPhone进行登录操作,因为watch的页面实在是太小了,没有键盘,环信SDK也不支持指纹或者声音登录。
再然后,登录操作成功之后菜单页面显示三项选项,每项点击都可以进入对应页面,显示相应的数据。
最后,进入聊天页面,可以显示已有的聊天记录,可以发送表情。本来想加入发送心跳的功能,但是发现WatchKit不支持watch app获取硬件传感器参数,而且找到了一篇不错的自问自答文章http://www.cocoachina.com/ios/20150323/11396.html
二、在storyboard上拖来拖去
1、点击文件夹下的interface.storyboard文件,右边会出现相应的视图显示。根据第一步的思路,直接拖4个Interface Controller, duang~,duang~,duang~, duang~,就成这个样子了
每个Interface Controller的Identifier属性强烈建议写上,为什么请见第3章。为了便于区分每个controller,个人习惯给每个Interface Controller都填写Title属性。
2、想了一下菜单,会话,好友,群组的功能,显示用table是再好不过的了,直接再拖table到每个controller,然后你就会发现一些奇怪的东西,比如这样的
Table Row Controller是类似于自定义UITableViewCell的东西,默认是继承于NSObject。
Group应该算是个新概念,它的作用是将页面上的控件分块。WatchKit不支持设置frame,目前我知道的,只是支持在横向上“左中右”,在纵向上“上中下”,支持设置大小。如果你想实现一个九宫格样式的页面,比较讨巧的方式是直接用三行Table Row Controller,每行三个button。不讨巧的方法,抱歉,我发现不管怎样排列组合这些属性,都出不来九宫格。
3、在会话页面显示和谁聊天的username,在好友页面显示好友的username,在群组页面显示群组的名字,用Label通通能搞定。
我在group里拖一个label,默认就是这个样子
自己选下不同的选项,看下效果,应该就能理解了。
4、聊天页面比较特殊,需要自己发送的在左边显示,对方发的在右边显示。不过这个很容易实现,只需要发送的和接收的放在两个Table Row Controller里,然后设置空间的Position即可,示意图:
拖完上边这些,页面就差不多了 。在Scheme中选择EMWatchOCDemo WatchKit App,编译 OK,可以进行第3章了。
环信SDK与Apple Watch的结合系列讲解(1)
环信SDK与Apple Watch的结合系列讲解(3) 收起阅读 »
环信SDK与Apple Watch的结合系列讲解(1)
该系列是记录在apple watch上开发IM,用到了最近挺流行的环信IM SDK。
一、先来一段网上随处可查到的信息:
1、两种分辨率
1.65寸 312*390
1.5寸 272*340
2、开发环境
Xcode 6.2 or later
OS X 10.9.4 or later
Watchkit
3、三种展现类型
a>标准的watch app,拥有自身的界面和功能
b>Glances,纯信息浏览,样式也很固定。这一方式适合新闻、天气、股票、运动数据等信息。
c>Notification,用于显示用户的本地通知和远程通知,它包括 Short-Look 和 Long-Look 两种形式。
4、官方文档
https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/WatchKitProgrammingGuide/index.html
5、怎么在工程中加入apple watch
关于这部分,网上的资料很多,在这外链一些不错的blog:
http://www.tuicool.com/articles/MFJFNny
http://blog.jobbole.com/79984/
二、WatchKit和环信SDK的结合
1、在工程中添加WatchKit扩展
你可以新建一个工程,在target上添加,也可以在已有的工程的target上添加,操作步骤是一样一样的。
为了记录的完整性,我新建了一个工程EMWatchOCDemo,看名字可以知道,这是一个ObjC写的工程。
2、EMWatchOCDemo是iphone的程序,先在这个target上接入环信SDK。
按照环信官网上的ios集成文档下载环信sdk,加入依赖库,配置好属性。我不准备实现实时语音,所以只用了lite.a。导入环信sdk之后的工程变成了这个样子
我比较习惯每个步骤都编译一下,如果编译通过,继续进行下边的步骤。
3、初始化环信SDK
环信SDK的初始化几乎都在AppDelegate.m中实现,如注册app,配置apns证书和昵称,进入前台和进入后台的操作,这些在环信官网的ios初始化文档中都有描述,不再赘述。
4、实现一些简单的功能
ConversationViewController 会话获取,展示。
FriendsViewController 好友获取,展示。
GroupsViewController 群组获取,展示。
HomeViewController是首页,Tab容器,实现了登录等操作。
DXEMIMHelper是IM的管理类,定义了全局的宏定义,包括默认登录账号KDEFAULT_USERNAME。
作者: 谢雅杰
环信SDK与Apple Watch的结合系列讲解(2)
环信SDK与Apple Watch的结合系列讲解(3) 收起阅读 »
一、先来一段网上随处可查到的信息:
1、两种分辨率
1.65寸 312*390
1.5寸 272*340
2、开发环境
Xcode 6.2 or later
OS X 10.9.4 or later
Watchkit
3、三种展现类型
a>标准的watch app,拥有自身的界面和功能
b>Glances,纯信息浏览,样式也很固定。这一方式适合新闻、天气、股票、运动数据等信息。
c>Notification,用于显示用户的本地通知和远程通知,它包括 Short-Look 和 Long-Look 两种形式。
4、官方文档
https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/WatchKitProgrammingGuide/index.html
5、怎么在工程中加入apple watch
关于这部分,网上的资料很多,在这外链一些不错的blog:
http://www.tuicool.com/articles/MFJFNny
http://blog.jobbole.com/79984/
二、WatchKit和环信SDK的结合
1、在工程中添加WatchKit扩展
你可以新建一个工程,在target上添加,也可以在已有的工程的target上添加,操作步骤是一样一样的。
为了记录的完整性,我新建了一个工程EMWatchOCDemo,看名字可以知道,这是一个ObjC写的工程。
2、EMWatchOCDemo是iphone的程序,先在这个target上接入环信SDK。
按照环信官网上的ios集成文档下载环信sdk,加入依赖库,配置好属性。我不准备实现实时语音,所以只用了lite.a。导入环信sdk之后的工程变成了这个样子
我比较习惯每个步骤都编译一下,如果编译通过,继续进行下边的步骤。
3、初始化环信SDK
环信SDK的初始化几乎都在AppDelegate.m中实现,如注册app,配置apns证书和昵称,进入前台和进入后台的操作,这些在环信官网的ios初始化文档中都有描述,不再赘述。
4、实现一些简单的功能
ConversationViewController 会话获取,展示。
FriendsViewController 好友获取,展示。
GroupsViewController 群组获取,展示。
HomeViewController是首页,Tab容器,实现了登录等操作。
DXEMIMHelper是IM的管理类,定义了全局的宏定义,包括默认登录账号KDEFAULT_USERNAME。
作者: 谢雅杰
环信SDK与Apple Watch的结合系列讲解(2)
环信SDK与Apple Watch的结合系列讲解(3) 收起阅读 »
怎样在Apple Watch上集成环信SDK
本文简单的讲述下如何用Apple Watch Kit集成环信SDK.
首先:需要升级xcode到version 6.2,和 IOS SDK8.2
然后下载环信SDK从官网
打开XCode->new project->new target->选择WatchKit App
xcode 会自动给你创建几个targets,例如下图:
把EaseMobSDK文件夹拖拽到HxAppleWatchDemo Target里
选择target HXAppleWatchDemo,加入下图所有的Linked Frameworks and Libraries里的库文件
在HXAppleWatchDemo target 创建bridging header文件
设置bridging header文件
设置other linker flags 以保证SDK Lib category的扩展方法可用
所有环境设置都已完成,试着build下看有啥问题么
接着开始写代码:
1. 打开HXAppleWatchDemo WatchKit App 里的interface.storyboard然后加个button 叫load contacts
2. 找到HXAppleWatchDemo WatchKit Extension里的文件InterfaceController.swift,然后把上述的button关联到 @IBOutlet weakvar open:WKInterfaceButton!
InterfaceController.swift代码如下
//
// InterfaceController.swift
// HXAppleWatchDemo WatchKit Extension
//
// Created by youni on 15/4/1.
// Copyright (c) 2015年 youni. All rights reserved.
//
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
@IBOutlet weak var open: WKInterfaceButton!
@IBAction func openApp() {
InterfaceController.openParentApplication(["action":"getcontact"], reply: {(info:[NSObject : AnyObject]!, error:NSError!) -> Void in
if info != nil{
if info.count > 0{
self.getContacts(info!["contacts"] as [String])
}
}
})
}
func getContacts(contacts:[String]){
presentTextInputControllerWithSuggestions(contacts, allowedInputMode: WKTextInputMode.Plain, completion: {(result:[AnyObject]!)-> Void in
if result != nil{
var id:String = result[0] as String
var date:NSDate = NSDate()
var now:NSTimeInterval = date.timeIntervalSinceNow
self.sendMessage(id,text: now.description)
}
}
)
}
func sendMessage(id:String,text:String){
InterfaceController.openParentApplication(["action":"send","name":id,"message":text], reply: {(info:[NSObject : AnyObject]!, error:NSError!) -> Void in
})
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
InterfaceController.openParentApplication是用来和IOS的程序通讯的接口,大部分的业务逻辑需要在parent application实现也就是上述说的HXAppleWatchDemo Target
我们看下HXAppleWatchDemo是如何实现和Apple Watch App通讯的
//
// AppDelegate.swift
// HXAppleWatchDemo
//
// Created by youni on 15/4/1.
// Copyright (c) 2015年 youni. All rights reserved.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,IChatManagerDelegate{
var window: UIWindow?
var callback:(([NSObject : AnyObject]!) -> Void)!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let apnsCertName:String = "chatdemoui";
EaseMob.sharedInstance().registerSDKWithAppKey("easemob-demo#chatdemoui", apnsCertName: apnsCertName)
EaseMob.sharedInstance().chatManager.addDelegate(self, delegateQueue: nil)
EaseMob.sharedInstance().chatManager.asyncLoginWithUsername("tt1", password: "1", completion: { (loginInfo,error) -> Void in
NSLog("login callback : ")
HXSDKHelper.instance.sendTextMessage("uni3", textMessge:"test from")
}, onQueue: nil)
return true
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {
if(userInfo != nil){
if userInfo!["action"] != nil{
var action:String = userInfo!["action"] as String
if action == "getcontact"{
reply!(["contacts":["uni3","uni5","tt2"]])
}else if action == "send"{
var name:String = userInfo!["name"] as String
var message:String = userInfo!["message"] as String
NSLog("name : " + name + "message : " + message)
HXSDKHelper.instance.sendTextMessage(name, textMessge:message)
callback = reply
}
}
}
}
func didSendMessage(message: EMMessage!, error: EMError!) {
if(error != nil){
callback!(["send":error!.errorCode.value])
}else{
callback!(["send":"ok"])
}
}
func didReceiveMessage(message: EMMessage!) {
}
}
这个就是和Apple WatchKit App实现通讯的接口:
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {
if(userInfo != nil){
if userInfo!["action"] != nil{
var action:String = userInfo!["action"] as String
if action == "getcontact"{
reply!(["contacts":["uni3","uni5","tt2"]])
}else if action == "send"{
var name:String = userInfo!["name"] as String
var message:String = userInfo!["message"] as String
NSLog("name : " + name + "message : " + message)
HXSDKHelper.instance.sendTextMessage(name, textMessge:message)
callback = reply
}
}
}
}
HXSDKHelper就是对环信一个简单的封装,现在里面只实现了一个函数
//
// HXSDKHelper.swift
// swittest
//
// Created by youni on 15/3/15.
// Copyright (c) 2015年 youni. All rights reserved.
//
import Foundation
private var gInstance = HXSDKHelper()
class HXSDKHelper : NSObject{
class var instance:HXSDKHelper{
return gInstance
}
func sendTextMessage(to : String, textMessge : String){
var latestMessage:EMMessage = EMMessage()
var chatText:EMChatText = EMChatText(text: textMessge)
var txtBody:EMTextMessageBody = EMTextMessageBody(chatObject: chatText)
latestMessage.addMessageBody(txtBody);
latestMessage.to = to;
EaseMob.sharedInstance().chatManager.asyncSendMessage(latestMessage, progress: nil);
}
}
大功告成,完成以上步骤,你就能够做个简单的Watch App 可以用环信的SDK发消息了。
由于没有真机,以上都是在模拟器上测试通过。
如果需要工程代码请联系我:syyorient@outlook.com
或者从github上获取
https://github.com/youniworld/AppleWatchDemo-HuanXin
如果其他问题可以登陆http://www.easemob.com/docs/gettingstart/
作者:隋云怡 收起阅读 »
首先:需要升级xcode到version 6.2,和 IOS SDK8.2
然后下载环信SDK从官网
打开XCode->new project->new target->选择WatchKit App
xcode 会自动给你创建几个targets,例如下图:
把EaseMobSDK文件夹拖拽到HxAppleWatchDemo Target里
选择target HXAppleWatchDemo,加入下图所有的Linked Frameworks and Libraries里的库文件
在HXAppleWatchDemo target 创建bridging header文件
设置bridging header文件
设置other linker flags 以保证SDK Lib category的扩展方法可用
所有环境设置都已完成,试着build下看有啥问题么
接着开始写代码:
1. 打开HXAppleWatchDemo WatchKit App 里的interface.storyboard然后加个button 叫load contacts
2. 找到HXAppleWatchDemo WatchKit Extension里的文件InterfaceController.swift,然后把上述的button关联到 @IBOutlet weakvar open:WKInterfaceButton!
InterfaceController.swift代码如下
//
// InterfaceController.swift
// HXAppleWatchDemo WatchKit Extension
//
// Created by youni on 15/4/1.
// Copyright (c) 2015年 youni. All rights reserved.
//
import WatchKit
import Foundation
class InterfaceController: WKInterfaceController {
@IBOutlet weak var open: WKInterfaceButton!
@IBAction func openApp() {
InterfaceController.openParentApplication(["action":"getcontact"], reply: {(info:[NSObject : AnyObject]!, error:NSError!) -> Void in
if info != nil{
if info.count > 0{
self.getContacts(info!["contacts"] as [String])
}
}
})
}
func getContacts(contacts:[String]){
presentTextInputControllerWithSuggestions(contacts, allowedInputMode: WKTextInputMode.Plain, completion: {(result:[AnyObject]!)-> Void in
if result != nil{
var id:String = result[0] as String
var date:NSDate = NSDate()
var now:NSTimeInterval = date.timeIntervalSinceNow
self.sendMessage(id,text: now.description)
}
}
)
}
func sendMessage(id:String,text:String){
InterfaceController.openParentApplication(["action":"send","name":id,"message":text], reply: {(info:[NSObject : AnyObject]!, error:NSError!) -> Void in
})
}
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
}
InterfaceController.openParentApplication是用来和IOS的程序通讯的接口,大部分的业务逻辑需要在parent application实现也就是上述说的HXAppleWatchDemo Target
我们看下HXAppleWatchDemo是如何实现和Apple Watch App通讯的
//
// AppDelegate.swift
// HXAppleWatchDemo
//
// Created by youni on 15/4/1.
// Copyright (c) 2015年 youni. All rights reserved.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate,IChatManagerDelegate{
var window: UIWindow?
var callback:(([NSObject : AnyObject]!) -> Void)!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let apnsCertName:String = "chatdemoui";
EaseMob.sharedInstance().registerSDKWithAppKey("easemob-demo#chatdemoui", apnsCertName: apnsCertName)
EaseMob.sharedInstance().chatManager.addDelegate(self, delegateQueue: nil)
EaseMob.sharedInstance().chatManager.asyncLoginWithUsername("tt1", password: "1", completion: { (loginInfo,error) -> Void in
NSLog("login callback : ")
HXSDKHelper.instance.sendTextMessage("uni3", textMessge:"test from")
}, onQueue: nil)
return true
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {
if(userInfo != nil){
if userInfo!["action"] != nil{
var action:String = userInfo!["action"] as String
if action == "getcontact"{
reply!(["contacts":["uni3","uni5","tt2"]])
}else if action == "send"{
var name:String = userInfo!["name"] as String
var message:String = userInfo!["message"] as String
NSLog("name : " + name + "message : " + message)
HXSDKHelper.instance.sendTextMessage(name, textMessge:message)
callback = reply
}
}
}
}
func didSendMessage(message: EMMessage!, error: EMError!) {
if(error != nil){
callback!(["send":error!.errorCode.value])
}else{
callback!(["send":"ok"])
}
}
func didReceiveMessage(message: EMMessage!) {
}
}
这个就是和Apple WatchKit App实现通讯的接口:
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {
if(userInfo != nil){
if userInfo!["action"] != nil{
var action:String = userInfo!["action"] as String
if action == "getcontact"{
reply!(["contacts":["uni3","uni5","tt2"]])
}else if action == "send"{
var name:String = userInfo!["name"] as String
var message:String = userInfo!["message"] as String
NSLog("name : " + name + "message : " + message)
HXSDKHelper.instance.sendTextMessage(name, textMessge:message)
callback = reply
}
}
}
}
HXSDKHelper就是对环信一个简单的封装,现在里面只实现了一个函数
//
// HXSDKHelper.swift
// swittest
//
// Created by youni on 15/3/15.
// Copyright (c) 2015年 youni. All rights reserved.
//
import Foundation
private var gInstance = HXSDKHelper()
class HXSDKHelper : NSObject{
class var instance:HXSDKHelper{
return gInstance
}
func sendTextMessage(to : String, textMessge : String){
var latestMessage:EMMessage = EMMessage()
var chatText:EMChatText = EMChatText(text: textMessge)
var txtBody:EMTextMessageBody = EMTextMessageBody(chatObject: chatText)
latestMessage.addMessageBody(txtBody);
latestMessage.to = to;
EaseMob.sharedInstance().chatManager.asyncSendMessage(latestMessage, progress: nil);
}
}
大功告成,完成以上步骤,你就能够做个简单的Watch App 可以用环信的SDK发消息了。
由于没有真机,以上都是在模拟器上测试通过。
如果需要工程代码请联系我:syyorient@outlook.com
或者从github上获取
https://github.com/youniworld/AppleWatchDemo-HuanXin
如果其他问题可以登陆http://www.easemob.com/docs/gettingstart/
作者:隋云怡 收起阅读 »
IM客户端数据库加载过程优化
IM通讯里面有两个重要的数据概念,一个是会话,一个是会话中的消息。
在系统初始化时,这两部分都要从数据库中加载到内存中。
数据组织结构是ConversatonManager包含多个会话,每个会话含有消息列表。
[datalist]
每次系统启动的时候,首先查询会话列表,然后对每一个会话加载其中的消息。对应的伪码
conversationList = db.loadConverstaions()
FOR (conversation : conversationList) {
db.loadMessages(conversation);
}
因为每次查询都要涉及数据库操作,导致加载时间很长,而且大量的IO操作也会导致耗电量增加,
所以这部分加载过程,是我们优化的重点。
思路很简单:一条SQL语句做完所有事情,避免for循环,避免多次遍历数据库。
修改后的结构是:
conversationList = db.loadConverstaionsAndMessages()
这样大量的细节隐藏在SQL语句实现中。
这里面的实现有两种情况:
1. 一种是每个会话只加载一条消息记录。
2. 另一种是每个会话加载多条消息记录。
1. 每个会话只加载一条消息记录(假设是最后一条消息),这种情况可以使用关键字group by 处理:
select *, max(msgTime) from xxx_table group by conversation
这种情况比较好理解,而且网上类似的问题很多,很容易找到答案。
2. 对于每个会话要求加载多条消息的情况(消息按照时间排序),我的思路是在group by, order by, limit这些关键字中寻找答案。
先在网络上寻找答案,寻找一些类似的实现,可惜都不理想。
有的实现就是把for循环转移到sql语句中,利用游标的概念,但是计算的数量级并没有下降,使用我本地的较大的数据量进行试验,执行时间过长。
或者是看到oracle数据库中有解决方案,但是需要使用关键字partition,这个应该是oracle数据看到经常会有类似的问题而提出的专用关键字。
对于mysql, sqlite等常用数据库,没法移植该实现。
最终我使用的方法是,
select * from xxx_table order by conversation, msgTime desc.
这样整个表单进行排序,首先按照会话名称进行排序,然后按照消息时间排序。
还剩下一个条件没有满足,就是每个会话消息的限定个数。
把个数的遍历放在外面实现,通过一个while循环将会话中超出limit部分的消息剔除。
伪码:
cursor = db.EXEC('select * from xxx_table order by conversation, msgTime desc');
while (cursor.NEXT()) {
msg = msgFrom(cursor)
IF (! msg belong TO conversation) {
// 消息不属于当前的会话,所以
conversation = NEW Conversation();
conversation.ADD(msg);
continue;
}
IF (conversation.msgSize() < LIMIT && msg belong TO conversation) {
conversation.ADD(msg);
} ELSE {
// 消息个数已经超过会话消息限制
continue;
}
}
这种方法的缺点是cursor会把整个表单都返回到用户空间,然后把所有的数据在用户空间都遍历一遍,有多余的操作。
不属于最优实现。
优点是两次排序使用order by,可以由数据库实现,这部分执行效率比较高,然后一次遍历cursor就执行完剩余操作,执行效率在接受范围之内,和改动之前相比效率提升至少一个数量级。
测试结果:一万条消息记录,一千个会话,执行时间大概4秒
补充一下,对于非数据库专业人员来说,有一点需要注意:
group by, order by, limit这些关键字在sql语句中有强制的顺序要求,limit , order by,都不能写到group by前面。
下面是我在寻找这个问题过程中看到的一些帖子,第一行是文章标题,后面是我看后的感受。如有冒犯,敬请原谅。
[SQL中Group分组获取Top N方法实现]
游标方法可取,网上讨论说运行比较慢。
[]一条SQL语句搞定分组并且每组限定记录集的数量]
仅适用于oracle
[]mysql实现每组取前N条记录的sql,以及后续的组数据量限制]
好像是可以,没看明白
[]SQL--分组显示数据,显示每组的前几行数据]
http://blog.163.com/peng_peng1028/blog/static/107463820111019240379/
像是答案,效率好像很低
[取每组前几条记录的SQL写法]
http://blog.sina.com.cn/s/blog_412897e10100r2rq.html
该页面提供两种方法,都尝试过,效率太低,杀掉程序时还没执行完
作者:李楠 收起阅读 »
在系统初始化时,这两部分都要从数据库中加载到内存中。
数据组织结构是ConversatonManager包含多个会话,每个会话含有消息列表。
[datalist]
每次系统启动的时候,首先查询会话列表,然后对每一个会话加载其中的消息。对应的伪码
conversationList = db.loadConverstaions()
FOR (conversation : conversationList) {
db.loadMessages(conversation);
}
因为每次查询都要涉及数据库操作,导致加载时间很长,而且大量的IO操作也会导致耗电量增加,
所以这部分加载过程,是我们优化的重点。
思路很简单:一条SQL语句做完所有事情,避免for循环,避免多次遍历数据库。
修改后的结构是:
conversationList = db.loadConverstaionsAndMessages()
这样大量的细节隐藏在SQL语句实现中。
这里面的实现有两种情况:
1. 一种是每个会话只加载一条消息记录。
2. 另一种是每个会话加载多条消息记录。
1. 每个会话只加载一条消息记录(假设是最后一条消息),这种情况可以使用关键字group by 处理:
select *, max(msgTime) from xxx_table group by conversation
这种情况比较好理解,而且网上类似的问题很多,很容易找到答案。
2. 对于每个会话要求加载多条消息的情况(消息按照时间排序),我的思路是在group by, order by, limit这些关键字中寻找答案。
先在网络上寻找答案,寻找一些类似的实现,可惜都不理想。
有的实现就是把for循环转移到sql语句中,利用游标的概念,但是计算的数量级并没有下降,使用我本地的较大的数据量进行试验,执行时间过长。
或者是看到oracle数据库中有解决方案,但是需要使用关键字partition,这个应该是oracle数据看到经常会有类似的问题而提出的专用关键字。
对于mysql, sqlite等常用数据库,没法移植该实现。
最终我使用的方法是,
select * from xxx_table order by conversation, msgTime desc.
这样整个表单进行排序,首先按照会话名称进行排序,然后按照消息时间排序。
还剩下一个条件没有满足,就是每个会话消息的限定个数。
把个数的遍历放在外面实现,通过一个while循环将会话中超出limit部分的消息剔除。
伪码:
cursor = db.EXEC('select * from xxx_table order by conversation, msgTime desc');
while (cursor.NEXT()) {
msg = msgFrom(cursor)
IF (! msg belong TO conversation) {
// 消息不属于当前的会话,所以
conversation = NEW Conversation();
conversation.ADD(msg);
continue;
}
IF (conversation.msgSize() < LIMIT && msg belong TO conversation) {
conversation.ADD(msg);
} ELSE {
// 消息个数已经超过会话消息限制
continue;
}
}
这种方法的缺点是cursor会把整个表单都返回到用户空间,然后把所有的数据在用户空间都遍历一遍,有多余的操作。
不属于最优实现。
优点是两次排序使用order by,可以由数据库实现,这部分执行效率比较高,然后一次遍历cursor就执行完剩余操作,执行效率在接受范围之内,和改动之前相比效率提升至少一个数量级。
测试结果:一万条消息记录,一千个会话,执行时间大概4秒
补充一下,对于非数据库专业人员来说,有一点需要注意:
group by, order by, limit这些关键字在sql语句中有强制的顺序要求,limit , order by,都不能写到group by前面。
下面是我在寻找这个问题过程中看到的一些帖子,第一行是文章标题,后面是我看后的感受。如有冒犯,敬请原谅。
[SQL中Group分组获取Top N方法实现]
游标方法可取,网上讨论说运行比较慢。
[]一条SQL语句搞定分组并且每组限定记录集的数量]
仅适用于oracle
[]mysql实现每组取前N条记录的sql,以及后续的组数据量限制]
好像是可以,没看明白
[]SQL--分组显示数据,显示每组的前几行数据]
http://blog.163.com/peng_peng1028/blog/static/107463820111019240379/
像是答案,效率好像很低
[取每组前几条记录的SQL写法]
http://blog.sina.com.cn/s/blog_412897e10100r2rq.html
该页面提供两种方法,都尝试过,效率太低,杀掉程序时还没执行完
作者:李楠 收起阅读 »
io操作中常见的几个概念
1. 概述
web应用中,io这块是一个重点,不同的策略对系统的性能影响很大.
对于io操作来说,用户线程发起io请求,内核负责完成此请求,并反馈结果,这两个角色之间需要进行协调:
1. 系统挂起用户线程,操作完成之后,系统返回结果,唤醒用户线程
2. 系统返回状态码,用户线程轮询结果,直到操作完成.
3. 用户线程发起请求时附带回调信息,内核接受请求后返回,用户线程执行别的逻辑;系统会在操作完成之后回调,有的系统自动创建新用户线程完成io处理,有的系统会利用已有的用户线程执行处理逻辑
4. 用户系统注册一个大的信号处理线程,用户线程发起请求后直接返回,系统在操作完成之后发送信号,信号处理线程接手处理.
可以看出,有的方式会造成线程挂起等待,有的会造成线程空转;一个线程好不容易等到系统分配了时间片,却又无奈的交出自己的时间片,浪费系统资源,我们的目标就是尽量少的线程,尽量少浪费线程的时间片,尽量少的线程切换,要做到这点,还是先来说说io中较常见的同步、异步、阻塞和非阻塞这几个概念.
2. 同步
我理解的io同步是指线程做io请求时需要等待内核将数据准备好,浪费了自己的cpu时间片.比如read,如果数据没准备好则线程直接挂起,浪费了一次线程调度;如果数据准备好了,需要将数据从内核空间拷贝到用户空间,可能分给自己1ms,结果等待拷贝数据花了了0.5ms,,实际只花费了0.5ms在业务上.
也有人是从当前线程是否需要等待处理结果来考量,当前线程需要等待,那就是同步,这个和我的观点实际没差别,到了异步这儿就不太一样,我们下面说.
3. 异步
与同步模式相反,异步模式下用户线程只需要注册相关信息,操作系统在操作完成后,将数据从内核空间拷贝到用户空间,然后根据注册信息通知用户进程处理io结果.整个流程中无需用户线程干预,用户线程可以充分利用分配给自己的时间.
有的io调用返回的是状态码,用户线程再根据状态码做相应的处理,但实际去做io操作时还是要主动的发起系统调用去获取数据,这是同步模型;还有的io操作是通过信号驱动,内核在操作完成后发送信号通知用户进程处理,用户进程捕获到信号后再发起系统调用去读取数据,实际上还是同步模式.
回到前面的问题,当前线程不等待io的操作结果这是否可以认为是异步?
我是这样想的,io数据从内核空间拷贝到用户空间这一步所花费的时间,不花在这个用户线程就花在别的用户线程,,总是消耗了用户线程的cpu时间片,除非由内核来驱动用户线程.
4. 阻塞
阻塞是指进行io操作时是否引起线程挂起,挂起了就是阻塞模式.很多时候会把同步和阻塞混淆,主要是因为同步一般都是由阻塞实现的.仔细想想,非阻塞也可以是同步,创建socket时如果指定BLOCK为false,那所有的操作都变成非阻塞,此时可能还是同步模式.
阻塞模式既有有点又有缺点,因为会阻塞,在请求不活跃时会节约cpu;因为会阻塞,也就造成了线程切换,也就浪费了cpu.
5. 非阻塞
没啥好说的,线程一路畅通无阻,看起来挺好,可如果忙着做状态检测,那就极大的浪费了cpu资源.
5 四种理论模型
同步异步,阻塞非阻塞,交叉组合,共有四种模型
5.1 同步阻塞
最经典的使用方式,最简单的,最喜欢的....
io操作会引起线程阻塞,只有系统准备好了时才会有返回,可以说不会浪费任何cpu资源.一般会有少量的线程接入请求,再来一个线程池处理接入的请求,简单有效.如果你的系统处理的连接不多,或者大部分不活跃,不用犹豫就它了.
在请求频繁时,同步阻塞会放大线程调度的成本,如果总得连接数超过线程池大小还会造成请求排队,此时还是尽早调整策略.
5.2 同步非阻塞
建立socket时,可以指定no block,此时所有的操作都会立刻返回,线程再根据返回值做相应的处理,这是一种轮询的方案,相比于同步阻塞,会多出若干次的系统调用,很不合算.
另外还有一种多路复用的io模式,线程先向内核注册若干个感兴趣的事件,然后一直等待,在某个或若干个事件符合条件后,内核将其打包返回,线程接到返回值,再去处理事件.
相比于直接在read/write上block住,同步阻塞多了一个获取事件的调用,因此相比于同步阻塞会有额外的系统开销;不过,因为一个线程可以同时监听多个连接,也就能一次处理若干个连接,在连接数较多时可以节约线程调度的成本,优势明显.
5.3 异步阻塞
这个没啥说的,略
5.4 异步非阻塞
用户线程先发起io请求,内核立刻返回,于是用户线程就可以做其它的事.在数据准备好之后,内核会将数据拷贝到用户空间,再给用户线程发送操作完成的通知.这个模型下,可以每个事件一个线程,也可以每个连接一个线程,相比于其他模式,能够最大化的节省线程数;另外,由于用户线程不需要去主动检查,每个用户线程都能用满自己的时间片,整个系统的性能值得期待.
理论说得这么好可别轻易动心,能用够用省力气才是王道,一般的用个同步阻塞就够了.真要用异步io,最好测测,以前的linux的异步io实现的不是太好,不知道现在啥状况了.
6 java中的模型
jdk一开始仅支持bio模式,也就是同步阻塞模式,在1.4中提供了nio,支持多路复用,1.7中又引入了aio,支持异步io.
作者:李春进 收起阅读 »
web应用中,io这块是一个重点,不同的策略对系统的性能影响很大.
对于io操作来说,用户线程发起io请求,内核负责完成此请求,并反馈结果,这两个角色之间需要进行协调:
1. 系统挂起用户线程,操作完成之后,系统返回结果,唤醒用户线程
2. 系统返回状态码,用户线程轮询结果,直到操作完成.
3. 用户线程发起请求时附带回调信息,内核接受请求后返回,用户线程执行别的逻辑;系统会在操作完成之后回调,有的系统自动创建新用户线程完成io处理,有的系统会利用已有的用户线程执行处理逻辑
4. 用户系统注册一个大的信号处理线程,用户线程发起请求后直接返回,系统在操作完成之后发送信号,信号处理线程接手处理.
可以看出,有的方式会造成线程挂起等待,有的会造成线程空转;一个线程好不容易等到系统分配了时间片,却又无奈的交出自己的时间片,浪费系统资源,我们的目标就是尽量少的线程,尽量少浪费线程的时间片,尽量少的线程切换,要做到这点,还是先来说说io中较常见的同步、异步、阻塞和非阻塞这几个概念.
2. 同步
我理解的io同步是指线程做io请求时需要等待内核将数据准备好,浪费了自己的cpu时间片.比如read,如果数据没准备好则线程直接挂起,浪费了一次线程调度;如果数据准备好了,需要将数据从内核空间拷贝到用户空间,可能分给自己1ms,结果等待拷贝数据花了了0.5ms,,实际只花费了0.5ms在业务上.
也有人是从当前线程是否需要等待处理结果来考量,当前线程需要等待,那就是同步,这个和我的观点实际没差别,到了异步这儿就不太一样,我们下面说.
3. 异步
与同步模式相反,异步模式下用户线程只需要注册相关信息,操作系统在操作完成后,将数据从内核空间拷贝到用户空间,然后根据注册信息通知用户进程处理io结果.整个流程中无需用户线程干预,用户线程可以充分利用分配给自己的时间.
有的io调用返回的是状态码,用户线程再根据状态码做相应的处理,但实际去做io操作时还是要主动的发起系统调用去获取数据,这是同步模型;还有的io操作是通过信号驱动,内核在操作完成后发送信号通知用户进程处理,用户进程捕获到信号后再发起系统调用去读取数据,实际上还是同步模式.
回到前面的问题,当前线程不等待io的操作结果这是否可以认为是异步?
我是这样想的,io数据从内核空间拷贝到用户空间这一步所花费的时间,不花在这个用户线程就花在别的用户线程,,总是消耗了用户线程的cpu时间片,除非由内核来驱动用户线程.
4. 阻塞
阻塞是指进行io操作时是否引起线程挂起,挂起了就是阻塞模式.很多时候会把同步和阻塞混淆,主要是因为同步一般都是由阻塞实现的.仔细想想,非阻塞也可以是同步,创建socket时如果指定BLOCK为false,那所有的操作都变成非阻塞,此时可能还是同步模式.
阻塞模式既有有点又有缺点,因为会阻塞,在请求不活跃时会节约cpu;因为会阻塞,也就造成了线程切换,也就浪费了cpu.
5. 非阻塞
没啥好说的,线程一路畅通无阻,看起来挺好,可如果忙着做状态检测,那就极大的浪费了cpu资源.
5 四种理论模型
同步异步,阻塞非阻塞,交叉组合,共有四种模型
5.1 同步阻塞
最经典的使用方式,最简单的,最喜欢的....
io操作会引起线程阻塞,只有系统准备好了时才会有返回,可以说不会浪费任何cpu资源.一般会有少量的线程接入请求,再来一个线程池处理接入的请求,简单有效.如果你的系统处理的连接不多,或者大部分不活跃,不用犹豫就它了.
在请求频繁时,同步阻塞会放大线程调度的成本,如果总得连接数超过线程池大小还会造成请求排队,此时还是尽早调整策略.
5.2 同步非阻塞
建立socket时,可以指定no block,此时所有的操作都会立刻返回,线程再根据返回值做相应的处理,这是一种轮询的方案,相比于同步阻塞,会多出若干次的系统调用,很不合算.
另外还有一种多路复用的io模式,线程先向内核注册若干个感兴趣的事件,然后一直等待,在某个或若干个事件符合条件后,内核将其打包返回,线程接到返回值,再去处理事件.
相比于直接在read/write上block住,同步阻塞多了一个获取事件的调用,因此相比于同步阻塞会有额外的系统开销;不过,因为一个线程可以同时监听多个连接,也就能一次处理若干个连接,在连接数较多时可以节约线程调度的成本,优势明显.
5.3 异步阻塞
这个没啥说的,略
5.4 异步非阻塞
用户线程先发起io请求,内核立刻返回,于是用户线程就可以做其它的事.在数据准备好之后,内核会将数据拷贝到用户空间,再给用户线程发送操作完成的通知.这个模型下,可以每个事件一个线程,也可以每个连接一个线程,相比于其他模式,能够最大化的节省线程数;另外,由于用户线程不需要去主动检查,每个用户线程都能用满自己的时间片,整个系统的性能值得期待.
理论说得这么好可别轻易动心,能用够用省力气才是王道,一般的用个同步阻塞就够了.真要用异步io,最好测测,以前的linux的异步io实现的不是太好,不知道现在啥状况了.
6 java中的模型
jdk一开始仅支持bio模式,也就是同步阻塞模式,在1.4中提供了nio,支持多路复用,1.7中又引入了aio,支持异步io.
作者:李春进 收起阅读 »
崩溃引发的思考:怎样才算真正的互联网技术公司?
阿里、携程接力两天的服务故障,沸腾了整个码农界。
看热闹都不怕事儿大。光纤被挖,引得异地双活被嘲讽,逼着支付宝最后有人出来打保票,年底我们一定搞完异地双活;线上服务被删更是报复论、Bug论频频,还出现了异司双活的崇拜,携程只是道了个歉,但是各种开人、睡人的小道消息也没停下。
热闹之余,人民群众也没闲着,纷纷忆苦思甜,言必称曾经。
这个说,“曾经我把数据库truncate了,老大当时不给解决方案只给目标,修了一晚上,期间各种惊心动魄,第二天恢复了竟然没人发现”。
那个说,“曾经有个新员工,没分清线上环境和测试环境,把线上数据库Rebuild了,导致某网站下线几小时”。(注:此次携程下线8个小时,据公开资料显示,其每宕机一秒会损失人民币5000元)
。。。
就像说起苦日子,都是自己小时候家里穷,你说你吃过糠我就说挖过树皮。事实上,大多数互联网服务都是7x24的,线上问题肯定是天天有。@TimYang 更是写过一篇黑天鹅来表达这种见怪不怪,见 http://timyang.net/service/black-swan-of-service/
这些苦又有什么可说呢?用来在自己遇到问题的某一天,聊以自慰么?
我们经常说,“吃一堑,长一智”,而我在每个团队里都在说的是,“真正聪明的人,可以从别人的问题里学到东西”。在我看来,更重要的其实在后面,后来又发生了什么?
依携程现在流言四出的状况,公关已然疯掉,明确的事故报告是很难了。但这不妨碍我们思考,要是发生在自己身上会发生什么?如果分析出来原因是程序Bug,如果原因是人为操作失误,如果是故意操作,结果会有区别么?我会在文后回答这个问题。
@左耳朵耗子 说得很直接
“技术上出故障是必然的。能否体现一个公司是技术公司,重要看这几点:1)故障的恢复是否有技术含量,2)公司对故障的处理方式,如果是通过加更多的流程,或是通过加更多的权限管控,或是通过处罚犯错误的人,或是上升到员工意识形态上,而不是去用更好的技术来解决,那么这个公司不会是个技术公司。”
但是遗憾的是,增加流程基本上算是大多数国产公司的首选,来个层层审批,更有甚者,来个线下审批什么的,让你一夜回到有纸办公的原始社会。
说一个前几天的故事。
我们服务升级,灰度部署一台机器后,把一个存储集群搞挂了。这件事的直接原因,是改了一行代码,没有压测就上了线。
服务最后通过降级扶住了,但是大家还是各出了一身汗,于是开会总结。总结的目的肯定是,如何避免同类事情的再次发生?有人提建议说,增加产品经理来做最后一级上线审批吧?
我当然是拒绝的。
为什么?我当然也知道增加审批的好处。首先,后续措施如果是加流程,那其实在暗示整个事情的发生受累于流程缺失,其他原因可以不再深究。
其次,再加一道审批,说明以后也不怕出问题了,因为最大的责任总会落在最后一个人身上,即使后面还是问题不断。
最后一点,对领导更有交待,如果你只是说Fix了一个服务的Bug,总有可能显得对问题不太上心,听起来也不太可靠。比较而言,增加流程就让人放心多了。
但是,我们聚在一起是为了做事,而不是为逃避责任不是吗?但是但是,人又是懒惰的。Larry Wall说,“好的程序员有三个宝贵品德,懒惰、没耐心和骄傲”。这话经过了一个又一个优秀的程序员的验证。
你要做事,又很懒惰。那除了让事情更快更好地完成,似乎别无他法。
加流程让事情更快了么?明显没有。每次服务上线多一层沟通,沟通意味着要协商另一个人的时间,然后你向他解释当前的情况。如果对方再不了解背景,那就足以让你崩溃死机了。
加流程让事情更好了么?没有。它对真正问题毫无益处,不幸的是,它还让整件事情变得更坏了。问题的根本原因在加流程的掩盖下蒙混过关,然后蠢蠢欲动在未来的某一天重新爆发。到了那时候,你依然需要分析整个事情,不同的是已经有了惯例,而这个惯例又会带来新的流程。
这就像纸尿裤。一个小孩尿了裤子,可以给他穿上纸尿裤。但是,健康成年人有穿的么?更危险的还在于,你不知道什么时候可以脱下来,因为是别人给你穿的,别人才不管你什么时候可以自禁了。
这也是很多公司为什么流程越来越重的原因,取消一个流程比增加一个流程要难得多。如果你能证明不需要一个流程也许在最开始就可以通过证明不增加它了。
回到总结会,我们又继续讨论了下去。经过复盘确认,当时的情况是,压测环境创建耗时费力,开发进度一再推迟下,经过团队讨论觉得没有问题,于是就跳过了压测步骤。
为什么可以跳过?因为整个技术团队都讨论了,评估这个压测可以不做(为什么我后文会讲)。这也是我拒绝产品经理审核的一个原因,因为他肯定没有技术了解整个服务,而风险管控最大的依据在于技术团队的评估。如果技术评估没有风险,他再来也是不会对结果有所改变的。
为什么决定这个压测不做了呢?因为我们两个负责人(本来也只有两个人),其中看了一眼文档(你没有看错),然后跟对方说似乎有个性能改进。另一个听到对方这么说,于是也看了一眼文档,下意识觉得那肯定是妥妥的了嘛。
这是什么鬼?思维惰性导致的循环依赖啊。实际上,只要再看一眼源代码,就知道不应该做这个改动了。但是两个人都没有去看,真是群愚的又一个例子。
选择不做压测,还有个原因,压测过程迟迟不能开展,而服务又着急上线。压测进展慢,因为压测没有计划,且在功能回归之后才进行。压测环境部署没有自动化,全部服务部署一次需要近十个小时,再跑完压测基本需要额外一天。而服务着急上线,是因为服务交付日期延迟了一周又一周。归根到底,整个团队研发效率的低下,已经导致完整流程跑不完了。
所以最后会议的决定变成了:
1. 确定压测步骤的必要性,再少的代码改动都不可以绕过;
2. 明确上线责任,而不是让责任继续分散,开发人员自己需要对新版本性能负责;
3. 提前制定压测计划,在服务开发完后立即进行压测,与功能测试同时进行;
4. 自动化压测环境部署,我们在云上构建服务,主机初始化和服务部署都使用脚本或管理工具自动化;
就在本文截稿之时,经过运维同事的努力,我们从零完整部署基准压测环境(支持100w同时在线)的时间,已经缩短到两小时以内。整个压测回归可以在三个小时内搞定。压测再也不是一个负担了。
当然研发效率的提高也在改进中,这是一个更大的话题稍后有机会再展开。
从这方面看,我们算一个技术公司么?我觉得是的,因为这样的公司里,不管遇到什么问题,技术的总会归技术。因为我们都相信,技术问题都可以通过技术解决。这句话也是对开始问题的回复。
所以亲爱的兄弟姐妹们,那个故障之后,你们得到的是流程还是技术改进呢?你们还好么?
如果有一天,你忽然发现,增加了太多流程,却依然做不好服务。请不要忘记,我们欢迎你。
作者: 梁宇鹏 收起阅读 »
看热闹都不怕事儿大。光纤被挖,引得异地双活被嘲讽,逼着支付宝最后有人出来打保票,年底我们一定搞完异地双活;线上服务被删更是报复论、Bug论频频,还出现了异司双活的崇拜,携程只是道了个歉,但是各种开人、睡人的小道消息也没停下。
热闹之余,人民群众也没闲着,纷纷忆苦思甜,言必称曾经。
这个说,“曾经我把数据库truncate了,老大当时不给解决方案只给目标,修了一晚上,期间各种惊心动魄,第二天恢复了竟然没人发现”。
那个说,“曾经有个新员工,没分清线上环境和测试环境,把线上数据库Rebuild了,导致某网站下线几小时”。(注:此次携程下线8个小时,据公开资料显示,其每宕机一秒会损失人民币5000元)
。。。
就像说起苦日子,都是自己小时候家里穷,你说你吃过糠我就说挖过树皮。事实上,大多数互联网服务都是7x24的,线上问题肯定是天天有。@TimYang 更是写过一篇黑天鹅来表达这种见怪不怪,见 http://timyang.net/service/black-swan-of-service/
这些苦又有什么可说呢?用来在自己遇到问题的某一天,聊以自慰么?
我们经常说,“吃一堑,长一智”,而我在每个团队里都在说的是,“真正聪明的人,可以从别人的问题里学到东西”。在我看来,更重要的其实在后面,后来又发生了什么?
依携程现在流言四出的状况,公关已然疯掉,明确的事故报告是很难了。但这不妨碍我们思考,要是发生在自己身上会发生什么?如果分析出来原因是程序Bug,如果原因是人为操作失误,如果是故意操作,结果会有区别么?我会在文后回答这个问题。
@左耳朵耗子 说得很直接
“技术上出故障是必然的。能否体现一个公司是技术公司,重要看这几点:1)故障的恢复是否有技术含量,2)公司对故障的处理方式,如果是通过加更多的流程,或是通过加更多的权限管控,或是通过处罚犯错误的人,或是上升到员工意识形态上,而不是去用更好的技术来解决,那么这个公司不会是个技术公司。”
但是遗憾的是,增加流程基本上算是大多数国产公司的首选,来个层层审批,更有甚者,来个线下审批什么的,让你一夜回到有纸办公的原始社会。
说一个前几天的故事。
我们服务升级,灰度部署一台机器后,把一个存储集群搞挂了。这件事的直接原因,是改了一行代码,没有压测就上了线。
服务最后通过降级扶住了,但是大家还是各出了一身汗,于是开会总结。总结的目的肯定是,如何避免同类事情的再次发生?有人提建议说,增加产品经理来做最后一级上线审批吧?
我当然是拒绝的。
为什么?我当然也知道增加审批的好处。首先,后续措施如果是加流程,那其实在暗示整个事情的发生受累于流程缺失,其他原因可以不再深究。
其次,再加一道审批,说明以后也不怕出问题了,因为最大的责任总会落在最后一个人身上,即使后面还是问题不断。
最后一点,对领导更有交待,如果你只是说Fix了一个服务的Bug,总有可能显得对问题不太上心,听起来也不太可靠。比较而言,增加流程就让人放心多了。
但是,我们聚在一起是为了做事,而不是为逃避责任不是吗?但是但是,人又是懒惰的。Larry Wall说,“好的程序员有三个宝贵品德,懒惰、没耐心和骄傲”。这话经过了一个又一个优秀的程序员的验证。
你要做事,又很懒惰。那除了让事情更快更好地完成,似乎别无他法。
加流程让事情更快了么?明显没有。每次服务上线多一层沟通,沟通意味着要协商另一个人的时间,然后你向他解释当前的情况。如果对方再不了解背景,那就足以让你崩溃死机了。
加流程让事情更好了么?没有。它对真正问题毫无益处,不幸的是,它还让整件事情变得更坏了。问题的根本原因在加流程的掩盖下蒙混过关,然后蠢蠢欲动在未来的某一天重新爆发。到了那时候,你依然需要分析整个事情,不同的是已经有了惯例,而这个惯例又会带来新的流程。
这就像纸尿裤。一个小孩尿了裤子,可以给他穿上纸尿裤。但是,健康成年人有穿的么?更危险的还在于,你不知道什么时候可以脱下来,因为是别人给你穿的,别人才不管你什么时候可以自禁了。
这也是很多公司为什么流程越来越重的原因,取消一个流程比增加一个流程要难得多。如果你能证明不需要一个流程也许在最开始就可以通过证明不增加它了。
回到总结会,我们又继续讨论了下去。经过复盘确认,当时的情况是,压测环境创建耗时费力,开发进度一再推迟下,经过团队讨论觉得没有问题,于是就跳过了压测步骤。
为什么可以跳过?因为整个技术团队都讨论了,评估这个压测可以不做(为什么我后文会讲)。这也是我拒绝产品经理审核的一个原因,因为他肯定没有技术了解整个服务,而风险管控最大的依据在于技术团队的评估。如果技术评估没有风险,他再来也是不会对结果有所改变的。
为什么决定这个压测不做了呢?因为我们两个负责人(本来也只有两个人),其中看了一眼文档(你没有看错),然后跟对方说似乎有个性能改进。另一个听到对方这么说,于是也看了一眼文档,下意识觉得那肯定是妥妥的了嘛。
这是什么鬼?思维惰性导致的循环依赖啊。实际上,只要再看一眼源代码,就知道不应该做这个改动了。但是两个人都没有去看,真是群愚的又一个例子。
选择不做压测,还有个原因,压测过程迟迟不能开展,而服务又着急上线。压测进展慢,因为压测没有计划,且在功能回归之后才进行。压测环境部署没有自动化,全部服务部署一次需要近十个小时,再跑完压测基本需要额外一天。而服务着急上线,是因为服务交付日期延迟了一周又一周。归根到底,整个团队研发效率的低下,已经导致完整流程跑不完了。
所以最后会议的决定变成了:
1. 确定压测步骤的必要性,再少的代码改动都不可以绕过;
2. 明确上线责任,而不是让责任继续分散,开发人员自己需要对新版本性能负责;
3. 提前制定压测计划,在服务开发完后立即进行压测,与功能测试同时进行;
4. 自动化压测环境部署,我们在云上构建服务,主机初始化和服务部署都使用脚本或管理工具自动化;
就在本文截稿之时,经过运维同事的努力,我们从零完整部署基准压测环境(支持100w同时在线)的时间,已经缩短到两小时以内。整个压测回归可以在三个小时内搞定。压测再也不是一个负担了。
当然研发效率的提高也在改进中,这是一个更大的话题稍后有机会再展开。
从这方面看,我们算一个技术公司么?我觉得是的,因为这样的公司里,不管遇到什么问题,技术的总会归技术。因为我们都相信,技术问题都可以通过技术解决。这句话也是对开始问题的回复。
所以亲爱的兄弟姐妹们,那个故障之后,你们得到的是流程还是技术改进呢?你们还好么?
如果有一天,你忽然发现,增加了太多流程,却依然做不好服务。请不要忘记,我们欢迎你。
作者: 梁宇鹏 收起阅读 »
回顾:电商抢滩618,Docker帮大忙!7个你不可错过的Docker分享!
618电商大战由来已久,巨大的数据和流量对于各电商平台来说,无疑是一项巨大挑战。如何在挑战中保证服务器的流畅和稳定,是这场大战胜负的关键性因素。Docker的出现正好能缓解这些因素,下边就盘点下几位全国顶级专家的Docker演讲,帮助你更全面系统的了解和学习Docker在云计算时代,开发者将应用转移到云上已经解决了硬件管理的问题,然而软件配置和管理相关的问题依然存在。Docker的出现正好能帮助软件开发者开阔思路,尝试新的软件管理方法来解决这个问题。通过掌握Docker,开发人员便可享受先进的自动化运维理念和工具,无需运维人员介入即可顺利运行于各种运行环境。
基于 Docker 的开发与运维一体化
平安健康互联网技术平台资深架构师 王延炯
Docker经过一段时间的发展,已快速进入以互联网公司为主的技术界。但对非互联网企业而言,传统业务复杂、IT设施薄弱、软件研发过程冗长,开发、测试、运维组织关系松散。应如何借鉴互联网企业经验,使得IT系统更好、更快速帮助企业开展业务、提升整体运营效率?本演讲将对比Docker与传统企业的IT过程,并以具体实践为参考,帮助传统企业向互联网化迈出探索性的一步。
基于Docker的云计算平台搭建实战
北京数人科技有限公司CTO 肖德时
CTO 肖德时在以往的云计算平台搭建过程,我们首选的解决方案是OpenStack解决方案,它非常成熟,让业界很放心。 随着新一代容器技术Docker生态圈的兴起,基于Docker的混合云计算平台搭建成为可能。我们为什么现在 这么需要容器技术,其原因是虚拟化计算已经把我们的网络环境从现实的物理机时代拉到虚拟机时代,部署在虚拟机上 的应用开始走向分布式架构,MicroService模式的应用越来越受到用户的喜爱。复杂的网络环境、软件环境让 开发者越来越无法承受。通过类似Docker技术,把内部构建、分发、部署都定制为统一的标准,正好是容器天然的优势。
Gaia—万台规模的Docker应用实战
腾讯数据平台部高级工程师 罗韩梅
作为底层的资源调度平台,Gaia(盖娅)能够让应用开发者像使用一台超级计算机一样使用整个集群,极大地简化了资源管理逻辑。Gaia提供高并发任务调度和资源管理,实现集群共享,具有高度可伸缩性和可靠性,能够支持MR等离线业务,甚至是实时计算、在线service业务。通过一系列的优化,Gaia可以支持到单cluster万台规模,毫秒级的作业下发效率以及更加完善的资源管理,同时,我们引入了弹性内存管理,增加了网络和磁盘带宽管理。Docker轻量、可移植、跨平台的特性将彻底改变程序的交付方式,并充分释放了虚拟化的威力,大有掀起一场容器革命之势。云计算、大数据经常意味着需要调动数据中心大量的资源,如何能够快速的匹配合适资源,需要一个聪明的“大脑”——Gaia。通过Docker on Gaia实现的Docker 云,将会让Docker的能量发挥到极致。
面向混合云的容器服务实践
IBM中国开发中心云平台服务部架构总监 杨博
演讲主要以企业应用与混合云的视角来审视容器技术,并分享IBM使用Docker容器技术来构建针对企业的混合云平台的实践与思考。通过本次分享将了解到企业应用在向云尤其是混合云迁移时会遇到哪些困难与挑战,而容器技术又是如何解决这些问题的,以及IBM云平台Bluemix里的容器服务是如何为用户带来提升的。
Docker在雪球的技术实践
球SRE团队高级工程师 高磊
基于Mesos和Docker的分布式计算
数人科技CEO 王璞
本次演讲主要介绍了如何基于Mesos和Docker搭建企业级分布式计算平台。Mesos和Docker都是最近非常热门的技术。国内企业使用Mesos的并不多,而国外主要是Twitter和Airbnb之类的新兴互联网公司在使用Mesos。Docker在2014年蓬勃发展,但是基于Docker真正开发企业应用的客户也并不多。基于Mesos和Docker打造的企业级分布式计算平台比Hadoop和Spark等常见分布式数据处理平台更具灵活性,能够处理实时请求、离线批处理请求、同构异构数据等等。
使用Docker构建项目
七牛任职七牛公司全栈架构师 李兆海
Docker的火热众所周知,那么到底该如何使用Docker来构建项目呢?本次演讲将分析利用Docker来构建项目可以解决哪些问题,以及分享七牛在使用Docker构建项目时的一些实践经验。
内容来源:StuQ 收起阅读 »
基于 Docker 的开发与运维一体化
平安健康互联网技术平台资深架构师 王延炯
Docker经过一段时间的发展,已快速进入以互联网公司为主的技术界。但对非互联网企业而言,传统业务复杂、IT设施薄弱、软件研发过程冗长,开发、测试、运维组织关系松散。应如何借鉴互联网企业经验,使得IT系统更好、更快速帮助企业开展业务、提升整体运营效率?本演讲将对比Docker与传统企业的IT过程,并以具体实践为参考,帮助传统企业向互联网化迈出探索性的一步。
基于Docker的云计算平台搭建实战
北京数人科技有限公司CTO 肖德时
CTO 肖德时在以往的云计算平台搭建过程,我们首选的解决方案是OpenStack解决方案,它非常成熟,让业界很放心。 随着新一代容器技术Docker生态圈的兴起,基于Docker的混合云计算平台搭建成为可能。我们为什么现在 这么需要容器技术,其原因是虚拟化计算已经把我们的网络环境从现实的物理机时代拉到虚拟机时代,部署在虚拟机上 的应用开始走向分布式架构,MicroService模式的应用越来越受到用户的喜爱。复杂的网络环境、软件环境让 开发者越来越无法承受。通过类似Docker技术,把内部构建、分发、部署都定制为统一的标准,正好是容器天然的优势。
Gaia—万台规模的Docker应用实战
腾讯数据平台部高级工程师 罗韩梅
作为底层的资源调度平台,Gaia(盖娅)能够让应用开发者像使用一台超级计算机一样使用整个集群,极大地简化了资源管理逻辑。Gaia提供高并发任务调度和资源管理,实现集群共享,具有高度可伸缩性和可靠性,能够支持MR等离线业务,甚至是实时计算、在线service业务。通过一系列的优化,Gaia可以支持到单cluster万台规模,毫秒级的作业下发效率以及更加完善的资源管理,同时,我们引入了弹性内存管理,增加了网络和磁盘带宽管理。Docker轻量、可移植、跨平台的特性将彻底改变程序的交付方式,并充分释放了虚拟化的威力,大有掀起一场容器革命之势。云计算、大数据经常意味着需要调动数据中心大量的资源,如何能够快速的匹配合适资源,需要一个聪明的“大脑”——Gaia。通过Docker on Gaia实现的Docker 云,将会让Docker的能量发挥到极致。
面向混合云的容器服务实践
IBM中国开发中心云平台服务部架构总监 杨博
演讲主要以企业应用与混合云的视角来审视容器技术,并分享IBM使用Docker容器技术来构建针对企业的混合云平台的实践与思考。通过本次分享将了解到企业应用在向云尤其是混合云迁移时会遇到哪些困难与挑战,而容器技术又是如何解决这些问题的,以及IBM云平台Bluemix里的容器服务是如何为用户带来提升的。
Docker在雪球的技术实践
球SRE团队高级工程师 高磊
基于Mesos和Docker的分布式计算
数人科技CEO 王璞
本次演讲主要介绍了如何基于Mesos和Docker搭建企业级分布式计算平台。Mesos和Docker都是最近非常热门的技术。国内企业使用Mesos的并不多,而国外主要是Twitter和Airbnb之类的新兴互联网公司在使用Mesos。Docker在2014年蓬勃发展,但是基于Docker真正开发企业应用的客户也并不多。基于Mesos和Docker打造的企业级分布式计算平台比Hadoop和Spark等常见分布式数据处理平台更具灵活性,能够处理实时请求、离线批处理请求、同构异构数据等等。
使用Docker构建项目
七牛任职七牛公司全栈架构师 李兆海
Docker的火热众所周知,那么到底该如何使用Docker来构建项目呢?本次演讲将分析利用Docker来构建项目可以解决哪些问题,以及分享七牛在使用Docker构建项目时的一些实践经验。
内容来源:StuQ 收起阅读 »
传微软要收购Docker了,真的假的?
传微软要收购Docker!以目前Docker的市值来看,微软收购Docker绝对是绰绰有余。但微软收购Docker之后的意义是什么了?对于Docker而言又意味着什么了?
如果微软能成功收购Docker对于微软的Azure服务来说会是一个很好的举措,可以同时提高其在开源领域与容器领域的声望。
当然,如果微软还没有着手收购Docker的话,可能它马上要行动了。
这个迷人而大胆的举措是上周末微软CEO Satya Nadella爆出来的,他正在努力让Azure云成为商业云的一个不错选择;同时,使Azure成为容器运行与开源项目托管的“头等选择”也是其努力实现的目标。
Docker是一个开源的容器技术,它可以让用户将其应用程序打包,并以轻量级的方式将其运行在不同的基础设施(infrastructure)之上,而且可以保持部署上的最小配置变化。同时也不要混淆了,Docker还是一个有融资背景的初创公司,它曾经叫DotCloud,并将Docker容器技术进行商业化运作,而如今它已经价值10亿美金。
商业界对容器技术——一种新的虚拟化技术十分热衷,因为容器技术让部署应用程序变得更为简单,同时也使数据中心的运维更为顺畅。
Docker与微软这两家公司都没有对这个收购传闻做出回应,但是“容器圈子”对此事的评价是:微软会从这个收购案中抢占到容器领域的制高点;同时也可以使微软在容器领域不再“落后”于Google;而后者在容器领域已经发布了自己的容器管理服务——Kubernetes。
“Google虽然在容器方面与其它公司进行了广泛的合作,但是它还是在努力独吞这个市场。如果微软成功收购Docker,保持Docker开源并为其打造一个高质量的编排服务,微软就能既在舆论上也能在技术上获取成功,”一位行业内人士透露,“微软也在用Kubernetes,但是其肯定不会将自己的未来赌在Google的开源技术上,毕竟这些都不是自己的。”
微软与Google在公有云市场上是竞争对手,但是他们又都想挑战这个行业的龙头Amazon Web Service;当然他们互相竞争的领域还包括移动操作系统与搜索。
虽然Docker与微软达成协议确保Docker能在Azure上运行,但是,Docker这个春季刚刚加入10亿美元估值俱乐部的公司,同样跟其他平台提供商有相似的协议。
据投资领域专家称,Docker愿意出售,但是投资商不会将其以低于35亿美金的价格出售。尽管如此,微软还是能轻松在这个价位上买下 Docker,毕竟这家科技巨擎的现金储备达到了953亿美金。相比四月传出的流言——微软将以500亿美金收购Saleforce来说,这个数目还是相当便宜的。
对于Docker来说,这笔交易未尝不可。Docker尽管在开发圈子里已经十分流行——它的源代码已经累计下载了3亿次;但是它的核心产品也是开源的,也就是免费的,从一个侧面来说,Docker存在商业模式盈利的潜在问题。
但是Docker也在研发付费版本,这个版本将在容器安全方面有增强功能,同时也会加入很多企业级用户关心的功能。这个版本还处于预览版阶段,但是将会在下周的Docker开发者大会上作大力度宣传。同时,Docker还将发布能使Docker容器更容易的在大规模集群下安装部署与管理的特性;这些容器编排服务将使用户能同时管理调度多个容器,并最终为客户节约运维成本。
在容器编排服务领域,Docker的竞争对手像CoreOS与Mesosphere,还有前面介绍的Google Kubernetes,都是开源且免费的产品,他们也提供相似的功能与产品。一些分析师认为他们的产品在某种程度上要优于Docker最开始的水准。
一个投资者在四月Docker完成D轮9500万美金融资后说,相对于其他免费的容器服务,Docker跟他们竞争会很难,Docker的这个风险也是广泛认同的。
“显然Docker在商业模式上的风险会更大些,”Insight Venture Partners公司的Jeff Horing在对Docker完成D轮融资时也说。
同样也有人说微软与Docker什么都不会发生;对收购持消极态度者称:微软并不需要买下Docker,只需要跟Docker合作就能得到其想要的;其他反对者也说微软在管理开源项目缺乏经验,同时微软也在为Windows服务器引入容器,并兼容Docker。
Janakiram & Associates的创始人Janakiram说:“Windows是商业领域Linux唯一的替代品,微软会推自己的容器服务来支持Docker而不会卖Linux操作系统。”
但是,微软在Docker估值不断增加的时候买下Docker真的好吗?而且IBM与Red Hat会随时跳出来横刀夺爱,毕竟Red Hat也表示过“会尽一切可能将Docker揽入怀里。”
这个传闻会有另一个结果。就像拍卖一样,大家都会对Docker进行轮番报价,Docker的价值水涨船高,对Docker来说何乐而不为呢? 收起阅读 »
如果微软能成功收购Docker对于微软的Azure服务来说会是一个很好的举措,可以同时提高其在开源领域与容器领域的声望。
当然,如果微软还没有着手收购Docker的话,可能它马上要行动了。
这个迷人而大胆的举措是上周末微软CEO Satya Nadella爆出来的,他正在努力让Azure云成为商业云的一个不错选择;同时,使Azure成为容器运行与开源项目托管的“头等选择”也是其努力实现的目标。
Docker是一个开源的容器技术,它可以让用户将其应用程序打包,并以轻量级的方式将其运行在不同的基础设施(infrastructure)之上,而且可以保持部署上的最小配置变化。同时也不要混淆了,Docker还是一个有融资背景的初创公司,它曾经叫DotCloud,并将Docker容器技术进行商业化运作,而如今它已经价值10亿美金。
商业界对容器技术——一种新的虚拟化技术十分热衷,因为容器技术让部署应用程序变得更为简单,同时也使数据中心的运维更为顺畅。
Docker与微软这两家公司都没有对这个收购传闻做出回应,但是“容器圈子”对此事的评价是:微软会从这个收购案中抢占到容器领域的制高点;同时也可以使微软在容器领域不再“落后”于Google;而后者在容器领域已经发布了自己的容器管理服务——Kubernetes。
“Google虽然在容器方面与其它公司进行了广泛的合作,但是它还是在努力独吞这个市场。如果微软成功收购Docker,保持Docker开源并为其打造一个高质量的编排服务,微软就能既在舆论上也能在技术上获取成功,”一位行业内人士透露,“微软也在用Kubernetes,但是其肯定不会将自己的未来赌在Google的开源技术上,毕竟这些都不是自己的。”
微软与Google在公有云市场上是竞争对手,但是他们又都想挑战这个行业的龙头Amazon Web Service;当然他们互相竞争的领域还包括移动操作系统与搜索。
虽然Docker与微软达成协议确保Docker能在Azure上运行,但是,Docker这个春季刚刚加入10亿美元估值俱乐部的公司,同样跟其他平台提供商有相似的协议。
据投资领域专家称,Docker愿意出售,但是投资商不会将其以低于35亿美金的价格出售。尽管如此,微软还是能轻松在这个价位上买下 Docker,毕竟这家科技巨擎的现金储备达到了953亿美金。相比四月传出的流言——微软将以500亿美金收购Saleforce来说,这个数目还是相当便宜的。
对于Docker来说,这笔交易未尝不可。Docker尽管在开发圈子里已经十分流行——它的源代码已经累计下载了3亿次;但是它的核心产品也是开源的,也就是免费的,从一个侧面来说,Docker存在商业模式盈利的潜在问题。
但是Docker也在研发付费版本,这个版本将在容器安全方面有增强功能,同时也会加入很多企业级用户关心的功能。这个版本还处于预览版阶段,但是将会在下周的Docker开发者大会上作大力度宣传。同时,Docker还将发布能使Docker容器更容易的在大规模集群下安装部署与管理的特性;这些容器编排服务将使用户能同时管理调度多个容器,并最终为客户节约运维成本。
在容器编排服务领域,Docker的竞争对手像CoreOS与Mesosphere,还有前面介绍的Google Kubernetes,都是开源且免费的产品,他们也提供相似的功能与产品。一些分析师认为他们的产品在某种程度上要优于Docker最开始的水准。
一个投资者在四月Docker完成D轮9500万美金融资后说,相对于其他免费的容器服务,Docker跟他们竞争会很难,Docker的这个风险也是广泛认同的。
“显然Docker在商业模式上的风险会更大些,”Insight Venture Partners公司的Jeff Horing在对Docker完成D轮融资时也说。
同样也有人说微软与Docker什么都不会发生;对收购持消极态度者称:微软并不需要买下Docker,只需要跟Docker合作就能得到其想要的;其他反对者也说微软在管理开源项目缺乏经验,同时微软也在为Windows服务器引入容器,并兼容Docker。
Janakiram & Associates的创始人Janakiram说:“Windows是商业领域Linux唯一的替代品,微软会推自己的容器服务来支持Docker而不会卖Linux操作系统。”
但是,微软在Docker估值不断增加的时候买下Docker真的好吗?而且IBM与Red Hat会随时跳出来横刀夺爱,毕竟Red Hat也表示过“会尽一切可能将Docker揽入怀里。”
这个传闻会有另一个结果。就像拍卖一样,大家都会对Docker进行轮番报价,Docker的价值水涨船高,对Docker来说何乐而不为呢? 收起阅读 »
解读:开发者可以使用Docker做什么?
有些开发者可能还是不明白 Docker 对自己到底有多大的用处,因此翻译 Docker 个人用例 这篇文章中来介绍 Docker 在普通开发者开发过程中的用例。
Docker 如今赢得了许多关注,很多人觉得盛名之下其实难副,因为他们仍然搞不清 Docker 和普通开发者到底有什么关系。许多开发者觉得 Docker 离自己很远,Docker 是生产环境中的工具,和自己无关。我也是花了很长时间才想清楚作为普通开发人员如何在自己的开发中使用 Docker。坦率地说,我仍处在学习的过程中。
这篇文章提供了一个 Docker 用例列表,我希望它能更好地帮助你理解 Docker 并引发你的思考。本文只是描述 Docker 在普通开发者日常的应用,并不提供完整的解决方案。
在介绍用例之前,我希望你能先记住这句话:“Docker 是一个便携的应用容器”。你可以不知道 Docker 所说的的“便携式容器”到底是什么意思,但是你必须清楚 Docker 在日常中能带来非常大的效率提升。
当你需要在容器内运行自己的应用(当然可以是任何应用),Docker 都提供了一个基础系统镜像作为运行应用时的基础系统。也就是说,只要是 Linux 系统上的应用都可以运行在 Docker 中。
可以在 Docker 里面运行数据库吗?当然可以。
可以在 Docker 里面运行 Node.js 网站服务器吗?当然可以。
可以在 Docker 里面运行 API 服务器吗?当然可以。
Docker 并不在乎你的应用程序是什么、做什么,Docker 提供了一组应用打包、传输和部署的方法,以便你能更好地在容器内运行任何应用。
下面的例子我自己经常使用,当然你有更好的案例也可以分享给我。
尝试新软件
对开发者而言,每天会催生出的各式各样的新技术都需要尝试,然而开发者却不太可能为他们一一搭建好环境并进行测试。时间非常宝贵,正是得益于 Docker,让我们有可能在一条或者几条命令内就搭建完环境。Docker 有一个傻瓜化的获取软件的方法,Docker 后台会自动获得环境镜像并且运行环境。
并不仅仅是新技术环境搭建用得到 Docker。如果你想快速在你的笔记本上运行一个 MySQL 数据库,或者一个 Redis 消息队列,那么使用 Docker 便可以非常容易地做到。例如 Docker 只需要一条命令便可以运行 MySQL 数据库:docker run -d -p 3306:3306 tutum/mysql。
译者注:虽然使用命令也能非常快地安装 MySQL 数据库,但是当用到最新的技术或者非常复杂的技术时,使用 Docker 便会是个非常好的选择,例如 Gitlab,普通用户大概需要一天的时间去搭建 Gitlab 平台,而 Docker 则只需要一条命令。
进行演示
现在我经常需要在周末用自己开发的成果对客户活着别人做一两个演示。搭建演示环境的过程非常麻烦。现在我发现 Docker 已经成为我演示这些工具的最合理的方式。同时,对于客户来说,我可以直接将 Docker 镜像提供给他们,而不必去做任何环境配置的工作,工作的效果也会和在他们演示中所看到的一模一样,同时不必担心他们的环境配置会导致我们的产品无法运行。
避免“我机器上可以运行”
无论是上一篇介绍的企业部署 Docker 还是本文的个人 Docker 用例,都提到了这个情况。因为环境配置不同,很多人在开发中也会遇到这个情况,甚至开发的软件到了测试人员的机器上便不能运行。但这都不是重点。重点是,如果我们有一个可靠的、可分发的标准开发环境,那么我们的开发将不会像现在这么痛苦。Docker 便可以解决这个问题。Docker 镜像并不会因为环境的变化而不能运行,也不会在不同的电脑上有不同的运行结果。可以给测试人员提交含有应用的 Docker 镜像,这样便不再会发生“在我机器上是可以运行的”这种事情,很大程度上减轻了开发人员测试人员互相检查机器环境设置带来的时间成本。
另一个 Docker 可以发挥用处的地方是培训班。除了 Docker 容器的隔离性之外,更能体会到 Docker 优势的地方在于环境搭建。培训班的新手每个人都要在环境搭建上花费很多时间,但是如果在这里应用到 Docker 的话,那么我们只需要把标准的运行环境镜像分发下去,然后就可以开始上课了。使用 Docker 和使用虚拟机一样简单,但是 Docker 要更方便、更轻量级。同时,我们也可以告诉学员:“在培训的同时,我们还将学到当下最流行的技术——Docker”,这种双赢的结局,何乐而不为呢。
学习 Linux 脚本
当然这个原因看起来可能很奇怪,但是对不不熟悉 Linux 操作系统和 Shell 脚本的人来说,确实是一个好机会。即便本文并不是在讲 Linux,Linux 的重要度仍然不言而喻。如果你用的是 Windows,那么我给你一个建议:从云主机提供商那儿租用一台云主机:我推荐使用 CoreOS 系统的云主机。虽然这样并不会让你成为专业的 Linux 运维,但是可以让你快速地学到 Linux 基础知识,爱上命令行操作,并且慢慢开始熟悉和欣赏 Linux。
更好地利用资源
虚拟机的粒度是“虚拟出的机器”,而 Docker 的粒度则是“被限制的应用”,相比较而言 Docker 的内存占用更少,更加轻量级。
对我来说这是 Docker 的一个优势:因为我经常在自己电脑中运行多个 Docker 应用,使用 Docker 比使用虚拟机更加简单,方便,粒度更细,也能持续地跟踪容器状态。
为微服务定制
如果你一直在关注科技新闻的话,那么你应该听说过“微服务(Microservices)”的概念。Docker 可以很好地和微服务结合起来。从概念上来说,一个微服务便是一个提供一整套应用程序的部分功能,Docker 便可以在开发、测试和部署过程中一直充当微服务的容器。甚至生产环境也可以在 Docker 中部署微服务。
在云服务提供商之间移植
大多数的云主机提供商已经全面支持 Docker。对于开发人员来说,这表示你可以很方便地切换云服务提供商,当然也可以很方便地将你本地的开发环境移动到云主机上,不需要本地上配置一次运行环境、在云主机上还配置一次运行环境。全面部署 Docker (Docker here and Docker there) 作为标准运行环境可以极大地减轻应用上线时的工作量和产生 BUG。
API 端
API 是应用之间的粘合剂,一个合格开发者肯定使用过别人提供的 REST API,或者自己开发过 REST API。需要指出的是,无论是客户端还是 API 提供端,在开发之前都需要先定义一组公共的 API 接口,写成文档,然后才能进行编码。如果服务端和客户端是共同开发的话,那么服务端通常会先实现能返回固定字符串的 API 接口,在以后的开发中再慢慢去实现 API 的功能。
虽然有人会认为在这里 Docker 被滥用了,完全可以用 sample.json 这种文件去实现虚拟 API,但是下面有个实例可以更好地解决前后端分离开发时的 API 问题。
为了更好地解释我的意思,给大家提供一个实例:JSON Server,一个用于提供 JSON 数据的 REST API。使用过这个容器的人就会知道,既然有这么好用的 Docker JSON Server,我们没有理由不用 Docker。
译者注:
运行示例的 JSON Server,同时使用示例中提供的 JSON 文件,只需执行一条命令便可以创建一个服务端的 API 应用。
使用 curl http://127.0.0.1:80/posts 即可获取示例文件中的 posts 段,这样在后端没有开发完 API 的时候,前端一样可以进行协同开发。
技术的创新
这点应该算不上是用例,但是我还是来写一下。Docker 正在快速发展,工具也在不断更新,没有人能预见到未来 Docker 会是什么样子的。你在复杂的系统中 Docker 使用的越多,越是可能会发现技术上的空白和未来技术发展的方向。现在还处在 Docker 的发展期,任何你使用 Docker 创建的工具都有可能成为社区关注的热点。这是 Docker 的机会,也是成就你自己的机会。
你的用例
最后一条便不再是我的用例了,而是 Docker 在你手中能发挥多大的作用。我也很希望看到你能提供更多使用 Docker 的方式,欢迎留言。
其他
还有两个技巧可以分享给你们。在学习 Docker 的过程中因为有了这两个的帮助,我才得意不断地提升自己。
一:Docker Hub Registry。这是 Docker 的官方镜像仓库,除了托管着 Docker 官方的镜像外,和 Github 一样,你可以在上面上传自己的镜像,也可以在上面搜寻其他有用的镜像,极大地节省自己的时间。例如 Oracle-XE-11g 镜像,所有的一切都是现成的,完全不需要自己去下载 Oracle XE 11g 安装。这样为你和团队节约了大量的时间成本。
如果你不太确定的话,可以去 Docker Hub 上搜有一下有没有自己用得到的镜像。大部分情况下你所需要的镜像在 Docker Hub 上都已经有人构建了。
二:多参考 IaaS 供应商的新闻,虽然我们不能像在他们会议室里那样完全了解他们的公司动态,但是仍然可以从新闻中可以了解到 Docker 最新的发展方向和技术趋势。可以肯定的是,容器化技术是未来的热点,我们不仅可以在本机运行 Docker,不仅仅在一家云服务提供商的主机上运行 Docker,未来所有的云服务提供商都会支持 Docker。
Docker 前景很明确,采用 Docker 只会让开发变得更方便。
内容来源:OPEN资讯 收起阅读 »
Docker 如今赢得了许多关注,很多人觉得盛名之下其实难副,因为他们仍然搞不清 Docker 和普通开发者到底有什么关系。许多开发者觉得 Docker 离自己很远,Docker 是生产环境中的工具,和自己无关。我也是花了很长时间才想清楚作为普通开发人员如何在自己的开发中使用 Docker。坦率地说,我仍处在学习的过程中。
这篇文章提供了一个 Docker 用例列表,我希望它能更好地帮助你理解 Docker 并引发你的思考。本文只是描述 Docker 在普通开发者日常的应用,并不提供完整的解决方案。
在介绍用例之前,我希望你能先记住这句话:“Docker 是一个便携的应用容器”。你可以不知道 Docker 所说的的“便携式容器”到底是什么意思,但是你必须清楚 Docker 在日常中能带来非常大的效率提升。
当你需要在容器内运行自己的应用(当然可以是任何应用),Docker 都提供了一个基础系统镜像作为运行应用时的基础系统。也就是说,只要是 Linux 系统上的应用都可以运行在 Docker 中。
可以在 Docker 里面运行数据库吗?当然可以。
可以在 Docker 里面运行 Node.js 网站服务器吗?当然可以。
可以在 Docker 里面运行 API 服务器吗?当然可以。
Docker 并不在乎你的应用程序是什么、做什么,Docker 提供了一组应用打包、传输和部署的方法,以便你能更好地在容器内运行任何应用。
下面的例子我自己经常使用,当然你有更好的案例也可以分享给我。
尝试新软件
对开发者而言,每天会催生出的各式各样的新技术都需要尝试,然而开发者却不太可能为他们一一搭建好环境并进行测试。时间非常宝贵,正是得益于 Docker,让我们有可能在一条或者几条命令内就搭建完环境。Docker 有一个傻瓜化的获取软件的方法,Docker 后台会自动获得环境镜像并且运行环境。
并不仅仅是新技术环境搭建用得到 Docker。如果你想快速在你的笔记本上运行一个 MySQL 数据库,或者一个 Redis 消息队列,那么使用 Docker 便可以非常容易地做到。例如 Docker 只需要一条命令便可以运行 MySQL 数据库:docker run -d -p 3306:3306 tutum/mysql。
译者注:虽然使用命令也能非常快地安装 MySQL 数据库,但是当用到最新的技术或者非常复杂的技术时,使用 Docker 便会是个非常好的选择,例如 Gitlab,普通用户大概需要一天的时间去搭建 Gitlab 平台,而 Docker 则只需要一条命令。
进行演示
现在我经常需要在周末用自己开发的成果对客户活着别人做一两个演示。搭建演示环境的过程非常麻烦。现在我发现 Docker 已经成为我演示这些工具的最合理的方式。同时,对于客户来说,我可以直接将 Docker 镜像提供给他们,而不必去做任何环境配置的工作,工作的效果也会和在他们演示中所看到的一模一样,同时不必担心他们的环境配置会导致我们的产品无法运行。
避免“我机器上可以运行”
无论是上一篇介绍的企业部署 Docker 还是本文的个人 Docker 用例,都提到了这个情况。因为环境配置不同,很多人在开发中也会遇到这个情况,甚至开发的软件到了测试人员的机器上便不能运行。但这都不是重点。重点是,如果我们有一个可靠的、可分发的标准开发环境,那么我们的开发将不会像现在这么痛苦。Docker 便可以解决这个问题。Docker 镜像并不会因为环境的变化而不能运行,也不会在不同的电脑上有不同的运行结果。可以给测试人员提交含有应用的 Docker 镜像,这样便不再会发生“在我机器上是可以运行的”这种事情,很大程度上减轻了开发人员测试人员互相检查机器环境设置带来的时间成本。
另一个 Docker 可以发挥用处的地方是培训班。除了 Docker 容器的隔离性之外,更能体会到 Docker 优势的地方在于环境搭建。培训班的新手每个人都要在环境搭建上花费很多时间,但是如果在这里应用到 Docker 的话,那么我们只需要把标准的运行环境镜像分发下去,然后就可以开始上课了。使用 Docker 和使用虚拟机一样简单,但是 Docker 要更方便、更轻量级。同时,我们也可以告诉学员:“在培训的同时,我们还将学到当下最流行的技术——Docker”,这种双赢的结局,何乐而不为呢。
学习 Linux 脚本
当然这个原因看起来可能很奇怪,但是对不不熟悉 Linux 操作系统和 Shell 脚本的人来说,确实是一个好机会。即便本文并不是在讲 Linux,Linux 的重要度仍然不言而喻。如果你用的是 Windows,那么我给你一个建议:从云主机提供商那儿租用一台云主机:我推荐使用 CoreOS 系统的云主机。虽然这样并不会让你成为专业的 Linux 运维,但是可以让你快速地学到 Linux 基础知识,爱上命令行操作,并且慢慢开始熟悉和欣赏 Linux。
更好地利用资源
虚拟机的粒度是“虚拟出的机器”,而 Docker 的粒度则是“被限制的应用”,相比较而言 Docker 的内存占用更少,更加轻量级。
对我来说这是 Docker 的一个优势:因为我经常在自己电脑中运行多个 Docker 应用,使用 Docker 比使用虚拟机更加简单,方便,粒度更细,也能持续地跟踪容器状态。
为微服务定制
如果你一直在关注科技新闻的话,那么你应该听说过“微服务(Microservices)”的概念。Docker 可以很好地和微服务结合起来。从概念上来说,一个微服务便是一个提供一整套应用程序的部分功能,Docker 便可以在开发、测试和部署过程中一直充当微服务的容器。甚至生产环境也可以在 Docker 中部署微服务。
在云服务提供商之间移植
大多数的云主机提供商已经全面支持 Docker。对于开发人员来说,这表示你可以很方便地切换云服务提供商,当然也可以很方便地将你本地的开发环境移动到云主机上,不需要本地上配置一次运行环境、在云主机上还配置一次运行环境。全面部署 Docker (Docker here and Docker there) 作为标准运行环境可以极大地减轻应用上线时的工作量和产生 BUG。
API 端
API 是应用之间的粘合剂,一个合格开发者肯定使用过别人提供的 REST API,或者自己开发过 REST API。需要指出的是,无论是客户端还是 API 提供端,在开发之前都需要先定义一组公共的 API 接口,写成文档,然后才能进行编码。如果服务端和客户端是共同开发的话,那么服务端通常会先实现能返回固定字符串的 API 接口,在以后的开发中再慢慢去实现 API 的功能。
虽然有人会认为在这里 Docker 被滥用了,完全可以用 sample.json 这种文件去实现虚拟 API,但是下面有个实例可以更好地解决前后端分离开发时的 API 问题。
为了更好地解释我的意思,给大家提供一个实例:JSON Server,一个用于提供 JSON 数据的 REST API。使用过这个容器的人就会知道,既然有这么好用的 Docker JSON Server,我们没有理由不用 Docker。
译者注:
运行示例的 JSON Server,同时使用示例中提供的 JSON 文件,只需执行一条命令便可以创建一个服务端的 API 应用。
使用 curl http://127.0.0.1:80/posts 即可获取示例文件中的 posts 段,这样在后端没有开发完 API 的时候,前端一样可以进行协同开发。
技术的创新
这点应该算不上是用例,但是我还是来写一下。Docker 正在快速发展,工具也在不断更新,没有人能预见到未来 Docker 会是什么样子的。你在复杂的系统中 Docker 使用的越多,越是可能会发现技术上的空白和未来技术发展的方向。现在还处在 Docker 的发展期,任何你使用 Docker 创建的工具都有可能成为社区关注的热点。这是 Docker 的机会,也是成就你自己的机会。
你的用例
最后一条便不再是我的用例了,而是 Docker 在你手中能发挥多大的作用。我也很希望看到你能提供更多使用 Docker 的方式,欢迎留言。
其他
还有两个技巧可以分享给你们。在学习 Docker 的过程中因为有了这两个的帮助,我才得意不断地提升自己。
一:Docker Hub Registry。这是 Docker 的官方镜像仓库,除了托管着 Docker 官方的镜像外,和 Github 一样,你可以在上面上传自己的镜像,也可以在上面搜寻其他有用的镜像,极大地节省自己的时间。例如 Oracle-XE-11g 镜像,所有的一切都是现成的,完全不需要自己去下载 Oracle XE 11g 安装。这样为你和团队节约了大量的时间成本。
如果你不太确定的话,可以去 Docker Hub 上搜有一下有没有自己用得到的镜像。大部分情况下你所需要的镜像在 Docker Hub 上都已经有人构建了。
二:多参考 IaaS 供应商的新闻,虽然我们不能像在他们会议室里那样完全了解他们的公司动态,但是仍然可以从新闻中可以了解到 Docker 最新的发展方向和技术趋势。可以肯定的是,容器化技术是未来的热点,我们不仅可以在本机运行 Docker,不仅仅在一家云服务提供商的主机上运行 Docker,未来所有的云服务提供商都会支持 Docker。
Docker 前景很明确,采用 Docker 只会让开发变得更方便。
内容来源:OPEN资讯 收起阅读 »
基于Mesos和Docker的分布式计算平台
Docker及其相关技术的出现和发展,给大规模集群管理带来了新的想象空间。如何将二者进行有效地结合?本文将介绍数人科技基于Mesos和Docker的分布式计算平台的实践。
针对“互联网+”时代的业务增长、变化速度及大规模计算的需求,廉价的、高可扩展的分布式x86集群已成为标准解决方案,如Google已经在几千万台服务器上部署分布式系统。Docker及其相关技术的出现和发展,又给大规模集群管理带来了新的想象空间。如何将二者进行有效地结合?本文将介绍数人科技基于Mesos和Docker的分布式计算平台的实践。
分布式系统设计准则可伸缩性
首先分布式系统一定是大规模的系统,有很好的Scalability。出于成本的考虑,很多大规模的分布式系统一般采用廉价的PC服务器,而不是大型的高性能服务器。没有单点失效
廉价的PC服务器在大规模使用中经常会遇到各种各样的问题,PC服务器的硬件不可能是高可靠的,比如Google的数据中心每天都会有大量的硬盘失效,所以分布式系统一定要对硬件容错,保证没有任何的单点失效。在这种很不稳定、很不可靠的硬件计算环境下,搭建一个分布式系统提供高可靠服务,必须要通过软件来容错。分布式系统针对不允许有单点失效的要求有两方面的设计考虑,一种是服务类的企业级应用,每个服务后台实例都要有多个副本,一两台硬件故障不至于影响所有服务实例;另外一种数据存储的应用,每份数据也必须要有多个备份,保证即使某几个硬件坏掉了数据也不会丢失。
高可靠性
除了单点失效,还要保证高可靠性。在分布式环境下,针对企业级服务应用,要做负载均衡和服务发现来保证高可靠性;针对数据服务,为了做到高可靠性,首先要按照某种算法来把整体数据分片(因为一台服务器装不下),然后按照同样的算法来进行分片查找。
数据本地性
再一个分布式设计理念是数据本地性,因为网络通信开销是分布式系统的瓶颈,要减少网络开销,应当让计算任务去找数据,而不是让数据去找计算。
分布式系统与Linux操作系统的比较
由于纵向拓展可优化空间太小(单台服务器的性能上限很明显),分布式系统强调横向扩展、横向优化,当分布式集群计算资源不足时,就要往集群里面添加服务器,来不停地提升分布式集群的计算能力。分布式系统要做到统一管理集群的所有服务器,屏蔽底层管理细节,诸如容错、调度、通信等,让开发人员觉得分布式集群在逻辑上是一台服务器。
和单机Linux操作系统相比,虽然分布式系统还没有成熟到成为“分布式操作系统”,但它和单机Linux一样要解决五大类操作系统必需的功能,即资源分配、进程管理、任务调度、进程间通信(IPC)和文件系统,可分别由Mesos、Docker、Marathon/Chronos、RabbitMQ和HDFS/Ceph来解决,对应于Linux下的Linux Kernel、Linux Kernel、init.d/cron、Pipe/Socket和ext4,如图1所示。
图1 分布式系统与Linux操作系统的比较
基于Mesos的分布式计算平台
Mesos资源分配原理
目前我们的Mesos集群部署在公有云服务上,用100多台虚拟机组成Mesos集群。Mesos不要求计算节点是物理服务器还是虚拟服务器,只要是Linux操作系统就可以。Mesos可以理解成一个分布式的Kernel,只分配集群计算资源,不负责任务调度。基于Mesos之上可以运行不同的分布式计算平台,如Spark、Storm、Hadoop、Marathon和Chronos等。Spark、Storm和Hadoop这样的计算平台有任务调度功能,可以直接使用Mesos SDK跟Mesos请求资源,然后自行调度计算任务,并对硬件容错。Marathon针对服务型分布式应用提供任务调度,比如企业网站等这类需要长时间运行的服务。通常网站应用程序没有任务调度和容错能力,因为网站程序不太会处理某个后台实例挂掉以后要在哪台机器上重新恢复等这类复杂问题。这类没有任务调度能力的服务型分布式应用,可以由Marathon来负责调度。比如,Marathon调度执行了网站服务的一百个后台实例,如果某个实例挂掉了,Marathon会在其他服务器上把这个实例恢复起来。Chronos是针对分布式批处理应用提供任务调度,比如定期处理日志或者定期调Hadoop等离线任务。
Mesos最大的好处是能够对分布式集群做细粒度资源分配。如图2所示,左边是粗粒的资源分配,右边是细粒的资源分配。
图2 Mesos资源调度的两种方式
图2左边有三个集群,每个集群三台服务器,分别装三种分布式计算平台,比如上面装三台Hadoop,中间三台是Spark,下面三台是Storm,三个不同的框架分别进行管理。右边是Mesos集群统一管理9台服务器,所有来自Spark、Hadoop或Storm的任务都在9台服务器上混合运行。Mesos首先提高了资源冗余率。粗粒资源管理肯定带来一定的浪费,细粒的资源提高资源管理能力。Hadoop机器很清闲,Spark没有安装,但Mesos可以只要任何一个调度马上响应。最后一个还有数据稳定性,因为所有9台都被Mesos统一管理,假如说装的Hadoop,Mesos会集群调度。这个计算资源都不共享,存储之间也不好共享。如果这上面跑了Spark做网络数据迁移,显然很影响速度。然后资源分配的方法就是resource offers,是在窗口的可调度的资源自己去选,Mesos是Spark或者是Hadoop等等。这种方法,Mesos的分配逻辑就很简单,只要不停地报告哪些是可用资源就可以了。Mesos资源分配方法也有一个潜在的缺点,就是无中心化的分配方式,所以有可能不会带来全局最优的方式。但这个数据资源缺点对目前来讲并不是很严重。现在一个计算中心资源贡献率很难达到50%,绝大部分计算中心都是很闲的状态。
Mesos资源分配示例
下面具体举例说明怎么用Mesos资源分配。如图3所示,中间是Mesos Master,下面是Mesos Slave,上面是Spark和Hadoop运行在Mesos之上。Mesos Master把可用资源报告给Spark或Hadoop。假定Hadoop有一个任务想运行,Hadoop从Mesos Master上报的可用资源中选择某个Mesos Slave节点,然后这个任务就会在这个Mesos Slave节点上执行,这是任务完成一次资源分配,接下来Mesos Master继续进行资源分配。
任务调度
Mesos只做一件事情,就是分布式集群资源分配,不管任务调度。Marathon和Chonos是基于Mesos来做任务调度。如图4所示,Mesos集群混合运行来自Marathon和Chronos的不同类型的任务。Marathon和Chonos基于Mesos做任务调度时,一定是动态调度,也就是每个任务在执行之前是不知道它将来在哪一台服务器上执行和绑定哪一个端口。如图5所示,9台服务器组成的Mesos集群上混合运行各种Marathon调度的任务,中间一台服务器坏掉以后,这台服务器上的两个任务就受影响,然后Marathon把这两个任务迁移到其他服务器上,这就是动态任务调度带来的好处,非常容易实现容错。
图4 Mesos集群运行不同类型的任务
图5 Marathon动态任务调度
为了减少硬件故障对应用服务的影响,应用程序要尽量做到无状态。无状态的好处是在程序受到影响时不需要进行任何恢复,这样这个程序只要重新调度起来就可以。无状态要求把状态数据放到存储服务器或者是消息队列里面,这样的好处是容错时恢复起来会变得很方便。
服务类的高可靠性
对于服务类型的任务,分布式环境保证服务的高可靠性,这需要负载均衡和服务发现。在分布式环境下做负载均衡有一个难点就是后台这些实例有可能发生动态变化,比如说某一个节点坏掉了,这个节点上的实例会受到影响,然后迁移到其他节点上。然而传统负载均衡器的后台实例地址端口都是静态的。所以在分布式环境下,为了做负载均衡一定要做服务发现。比如,某个服务之前有四个事例,现在新添加了两个实例,需要告诉负载均衡器新增加的实例的地址和端口。服务发现的过程是由几个模块配合完成,比如说Marathon给某个服务增加了新的实例,把新调度的实例地址端口写到Zookeeper,然后Bamboo把Zookeeper里存放的该服务新的实例的地址端口信息告诉负载均衡器,这样负载均衡器就知道新的实例地址端口,完成了服务发现。
数据类的高可靠性
对于服务类型的应用,分布式系统用负载均衡器和服务发现来保证高可靠性的服务。对于数据类型的应用,分布式系统同样要保证高可靠的数据服务。首先要做数据分片,一台服务器存不下所有数据就分成多份来存,但对数据进行分片必须按照某个规则来进行分片,后面查找时要按照同样的规则来进行分片查找,就是一致性。假定最原始的方案我们用Hash计算做成方法,在线性空间上分了三份以后,我要在数据分成三块机器来存,三台机器都存满了时,再把数据进行分配的时候不再把它分配到直线线性空间上,而是把它分配到环状空间上,把起点和终点连接起来,连成一个数据环,如图6所示,这样相应的数据点就放在这一块。如果要添加一个新的数据中心就在环上新切出来这块,这样很方便,切出来这一部分代表这一部分数据都应该放到新的芯片上,所以把原来子数据分片挪到嵌入式的分片上。
图6 数据分片
还有可能删除数据,我们把黄色的数据放到红色的数据上,这是环的好处。实际为了做到高可靠性,任何一个数据可能假定映射到黄色部分以后,这些黄色的部分只要映射到任何一个黄色的区域都会存在同一片机器上,同一片机器底层会有多个副本和做数据的备份,这是实际数据分片的一个实例。这是怎么做数据的高可靠性。这些数据分片,还有负载均衡,都是为了对应分布式分片硬件带来的不可靠和失效,这是我们用分布式系统最大的特点。
基于Docker的分布式计算平台
Docker工作流
我们主要用Docker来做分布式环境下的进程管理。Docker工作流如图7所示,我们不仅把Docker应用到生产阶段,也应用到开发阶段,所以我们每天编辑Dockerfile,提升Docker Images,测试上线,发Docker镜像,在我们内部私有Docker regis里面,再调到我们Docker集群生产环境里面,这和其他的Docker工作流没有什么区别。
在Mesos提交Docker任务
因为Mesos和Docker已经是无缝结合起来。通过Marathon和Chronos提交服务型应用和批处理型应用。Marathon和Chronos通过RESTful的方式提交任务,用JSON脚本设定应用的后台实例个数、应用的参数、以及Docker Images的路径等等。
分布式环境下的进程通信
在分布式环境下应用服务之间通信,是用分布式消息队列来做,我们用的是RabbitMQ。RabbitMQ也是一个分布式系统,它也要保证高可靠性、解决容错的问题。首先RabbitMQ也有集群,如图8所示,六个节点组成了一个RabbitMQ的集群,每个节点之间是互为备份的关系,任何一个坏掉,其他五个还可以提供服务,通过冗余来保证RabbitMQ的高可靠性。
图8 RabbitMQ集群
其次,RabbitMQ也有数据分片机制。因为消息队列有可能很长,长到所有的消息不可能都放到一个节点上,这时就要用分片,把很长的消息队列分为几段,分别放到不同的节点上。如图9所示是RabbitMQ的联盟机制,把一个消息队列打成两段,一段放在上游一段放在下游,假定下游消息队列的消息被消费完了就自动把上游消息队列里的消息移到下游,这样一个消息队列变成非常长的时候也不怕,分片到多个节点上即可。
图9 消息队列分片
分布式文件系统
最后讲一下分布式文件系统HDFS和Ceph。Hadoop文件系统HDFS,如图10所示,每个数据块有三个备份,必须放在不同的服务器上,而且三个备份里面每个机架最多放两份,这么做也是为了容错。Ceph是另一种流行的开源分布式文件系统。Ceph把网络存储设备抽象成一张逻辑硬盘,然后“挂载”到分布式集群的每台服务器上,原理上非常像是Linux操作系统Mount一块物理硬盘。这样一来,用户程序访问Ceph的文件系统就跟访问Linux本地路径一样,非常方便。
分布式环境下的监控
分布式环境下,程序不是运行在本地,而是在集群上面,没有监控就等于程序运行在黑盒子下,无法调优,必须要有监控。分布式环境下的监控分为两个部分,一是性能监控,另一个是报警。性能监控要知道每个应用程序运行状态是什么样,即每一个应用程序占了多少CPU内存、服务的请求处理延迟等。我们是用Graphite来做应用程序性能监控;还有其他系统,比如MongoDB、Hadoop等开源系统,我们用Ganglia来做性能监控,比如CPU内存硬盘的使用情况等。报警是要在关键服务出现故障时,通知开发运维人员及时排解故障,我们用Zabbix来做报警。(责编/周建丁)
内容来源:《程序员》 收起阅读 »
针对“互联网+”时代的业务增长、变化速度及大规模计算的需求,廉价的、高可扩展的分布式x86集群已成为标准解决方案,如Google已经在几千万台服务器上部署分布式系统。Docker及其相关技术的出现和发展,又给大规模集群管理带来了新的想象空间。如何将二者进行有效地结合?本文将介绍数人科技基于Mesos和Docker的分布式计算平台的实践。
分布式系统设计准则可伸缩性
首先分布式系统一定是大规模的系统,有很好的Scalability。出于成本的考虑,很多大规模的分布式系统一般采用廉价的PC服务器,而不是大型的高性能服务器。没有单点失效
廉价的PC服务器在大规模使用中经常会遇到各种各样的问题,PC服务器的硬件不可能是高可靠的,比如Google的数据中心每天都会有大量的硬盘失效,所以分布式系统一定要对硬件容错,保证没有任何的单点失效。在这种很不稳定、很不可靠的硬件计算环境下,搭建一个分布式系统提供高可靠服务,必须要通过软件来容错。分布式系统针对不允许有单点失效的要求有两方面的设计考虑,一种是服务类的企业级应用,每个服务后台实例都要有多个副本,一两台硬件故障不至于影响所有服务实例;另外一种数据存储的应用,每份数据也必须要有多个备份,保证即使某几个硬件坏掉了数据也不会丢失。
高可靠性
除了单点失效,还要保证高可靠性。在分布式环境下,针对企业级服务应用,要做负载均衡和服务发现来保证高可靠性;针对数据服务,为了做到高可靠性,首先要按照某种算法来把整体数据分片(因为一台服务器装不下),然后按照同样的算法来进行分片查找。
数据本地性
再一个分布式设计理念是数据本地性,因为网络通信开销是分布式系统的瓶颈,要减少网络开销,应当让计算任务去找数据,而不是让数据去找计算。
分布式系统与Linux操作系统的比较
由于纵向拓展可优化空间太小(单台服务器的性能上限很明显),分布式系统强调横向扩展、横向优化,当分布式集群计算资源不足时,就要往集群里面添加服务器,来不停地提升分布式集群的计算能力。分布式系统要做到统一管理集群的所有服务器,屏蔽底层管理细节,诸如容错、调度、通信等,让开发人员觉得分布式集群在逻辑上是一台服务器。
和单机Linux操作系统相比,虽然分布式系统还没有成熟到成为“分布式操作系统”,但它和单机Linux一样要解决五大类操作系统必需的功能,即资源分配、进程管理、任务调度、进程间通信(IPC)和文件系统,可分别由Mesos、Docker、Marathon/Chronos、RabbitMQ和HDFS/Ceph来解决,对应于Linux下的Linux Kernel、Linux Kernel、init.d/cron、Pipe/Socket和ext4,如图1所示。
图1 分布式系统与Linux操作系统的比较
基于Mesos的分布式计算平台
Mesos资源分配原理
目前我们的Mesos集群部署在公有云服务上,用100多台虚拟机组成Mesos集群。Mesos不要求计算节点是物理服务器还是虚拟服务器,只要是Linux操作系统就可以。Mesos可以理解成一个分布式的Kernel,只分配集群计算资源,不负责任务调度。基于Mesos之上可以运行不同的分布式计算平台,如Spark、Storm、Hadoop、Marathon和Chronos等。Spark、Storm和Hadoop这样的计算平台有任务调度功能,可以直接使用Mesos SDK跟Mesos请求资源,然后自行调度计算任务,并对硬件容错。Marathon针对服务型分布式应用提供任务调度,比如企业网站等这类需要长时间运行的服务。通常网站应用程序没有任务调度和容错能力,因为网站程序不太会处理某个后台实例挂掉以后要在哪台机器上重新恢复等这类复杂问题。这类没有任务调度能力的服务型分布式应用,可以由Marathon来负责调度。比如,Marathon调度执行了网站服务的一百个后台实例,如果某个实例挂掉了,Marathon会在其他服务器上把这个实例恢复起来。Chronos是针对分布式批处理应用提供任务调度,比如定期处理日志或者定期调Hadoop等离线任务。
Mesos最大的好处是能够对分布式集群做细粒度资源分配。如图2所示,左边是粗粒的资源分配,右边是细粒的资源分配。
图2 Mesos资源调度的两种方式
图2左边有三个集群,每个集群三台服务器,分别装三种分布式计算平台,比如上面装三台Hadoop,中间三台是Spark,下面三台是Storm,三个不同的框架分别进行管理。右边是Mesos集群统一管理9台服务器,所有来自Spark、Hadoop或Storm的任务都在9台服务器上混合运行。Mesos首先提高了资源冗余率。粗粒资源管理肯定带来一定的浪费,细粒的资源提高资源管理能力。Hadoop机器很清闲,Spark没有安装,但Mesos可以只要任何一个调度马上响应。最后一个还有数据稳定性,因为所有9台都被Mesos统一管理,假如说装的Hadoop,Mesos会集群调度。这个计算资源都不共享,存储之间也不好共享。如果这上面跑了Spark做网络数据迁移,显然很影响速度。然后资源分配的方法就是resource offers,是在窗口的可调度的资源自己去选,Mesos是Spark或者是Hadoop等等。这种方法,Mesos的分配逻辑就很简单,只要不停地报告哪些是可用资源就可以了。Mesos资源分配方法也有一个潜在的缺点,就是无中心化的分配方式,所以有可能不会带来全局最优的方式。但这个数据资源缺点对目前来讲并不是很严重。现在一个计算中心资源贡献率很难达到50%,绝大部分计算中心都是很闲的状态。
Mesos资源分配示例
下面具体举例说明怎么用Mesos资源分配。如图3所示,中间是Mesos Master,下面是Mesos Slave,上面是Spark和Hadoop运行在Mesos之上。Mesos Master把可用资源报告给Spark或Hadoop。假定Hadoop有一个任务想运行,Hadoop从Mesos Master上报的可用资源中选择某个Mesos Slave节点,然后这个任务就会在这个Mesos Slave节点上执行,这是任务完成一次资源分配,接下来Mesos Master继续进行资源分配。
任务调度
Mesos只做一件事情,就是分布式集群资源分配,不管任务调度。Marathon和Chonos是基于Mesos来做任务调度。如图4所示,Mesos集群混合运行来自Marathon和Chronos的不同类型的任务。Marathon和Chonos基于Mesos做任务调度时,一定是动态调度,也就是每个任务在执行之前是不知道它将来在哪一台服务器上执行和绑定哪一个端口。如图5所示,9台服务器组成的Mesos集群上混合运行各种Marathon调度的任务,中间一台服务器坏掉以后,这台服务器上的两个任务就受影响,然后Marathon把这两个任务迁移到其他服务器上,这就是动态任务调度带来的好处,非常容易实现容错。
图4 Mesos集群运行不同类型的任务
图5 Marathon动态任务调度
为了减少硬件故障对应用服务的影响,应用程序要尽量做到无状态。无状态的好处是在程序受到影响时不需要进行任何恢复,这样这个程序只要重新调度起来就可以。无状态要求把状态数据放到存储服务器或者是消息队列里面,这样的好处是容错时恢复起来会变得很方便。
服务类的高可靠性
对于服务类型的任务,分布式环境保证服务的高可靠性,这需要负载均衡和服务发现。在分布式环境下做负载均衡有一个难点就是后台这些实例有可能发生动态变化,比如说某一个节点坏掉了,这个节点上的实例会受到影响,然后迁移到其他节点上。然而传统负载均衡器的后台实例地址端口都是静态的。所以在分布式环境下,为了做负载均衡一定要做服务发现。比如,某个服务之前有四个事例,现在新添加了两个实例,需要告诉负载均衡器新增加的实例的地址和端口。服务发现的过程是由几个模块配合完成,比如说Marathon给某个服务增加了新的实例,把新调度的实例地址端口写到Zookeeper,然后Bamboo把Zookeeper里存放的该服务新的实例的地址端口信息告诉负载均衡器,这样负载均衡器就知道新的实例地址端口,完成了服务发现。
数据类的高可靠性
对于服务类型的应用,分布式系统用负载均衡器和服务发现来保证高可靠性的服务。对于数据类型的应用,分布式系统同样要保证高可靠的数据服务。首先要做数据分片,一台服务器存不下所有数据就分成多份来存,但对数据进行分片必须按照某个规则来进行分片,后面查找时要按照同样的规则来进行分片查找,就是一致性。假定最原始的方案我们用Hash计算做成方法,在线性空间上分了三份以后,我要在数据分成三块机器来存,三台机器都存满了时,再把数据进行分配的时候不再把它分配到直线线性空间上,而是把它分配到环状空间上,把起点和终点连接起来,连成一个数据环,如图6所示,这样相应的数据点就放在这一块。如果要添加一个新的数据中心就在环上新切出来这块,这样很方便,切出来这一部分代表这一部分数据都应该放到新的芯片上,所以把原来子数据分片挪到嵌入式的分片上。
图6 数据分片
还有可能删除数据,我们把黄色的数据放到红色的数据上,这是环的好处。实际为了做到高可靠性,任何一个数据可能假定映射到黄色部分以后,这些黄色的部分只要映射到任何一个黄色的区域都会存在同一片机器上,同一片机器底层会有多个副本和做数据的备份,这是实际数据分片的一个实例。这是怎么做数据的高可靠性。这些数据分片,还有负载均衡,都是为了对应分布式分片硬件带来的不可靠和失效,这是我们用分布式系统最大的特点。
基于Docker的分布式计算平台
Docker工作流
我们主要用Docker来做分布式环境下的进程管理。Docker工作流如图7所示,我们不仅把Docker应用到生产阶段,也应用到开发阶段,所以我们每天编辑Dockerfile,提升Docker Images,测试上线,发Docker镜像,在我们内部私有Docker regis里面,再调到我们Docker集群生产环境里面,这和其他的Docker工作流没有什么区别。
在Mesos提交Docker任务
因为Mesos和Docker已经是无缝结合起来。通过Marathon和Chronos提交服务型应用和批处理型应用。Marathon和Chronos通过RESTful的方式提交任务,用JSON脚本设定应用的后台实例个数、应用的参数、以及Docker Images的路径等等。
分布式环境下的进程通信
在分布式环境下应用服务之间通信,是用分布式消息队列来做,我们用的是RabbitMQ。RabbitMQ也是一个分布式系统,它也要保证高可靠性、解决容错的问题。首先RabbitMQ也有集群,如图8所示,六个节点组成了一个RabbitMQ的集群,每个节点之间是互为备份的关系,任何一个坏掉,其他五个还可以提供服务,通过冗余来保证RabbitMQ的高可靠性。
图8 RabbitMQ集群
其次,RabbitMQ也有数据分片机制。因为消息队列有可能很长,长到所有的消息不可能都放到一个节点上,这时就要用分片,把很长的消息队列分为几段,分别放到不同的节点上。如图9所示是RabbitMQ的联盟机制,把一个消息队列打成两段,一段放在上游一段放在下游,假定下游消息队列的消息被消费完了就自动把上游消息队列里的消息移到下游,这样一个消息队列变成非常长的时候也不怕,分片到多个节点上即可。
图9 消息队列分片
分布式文件系统
最后讲一下分布式文件系统HDFS和Ceph。Hadoop文件系统HDFS,如图10所示,每个数据块有三个备份,必须放在不同的服务器上,而且三个备份里面每个机架最多放两份,这么做也是为了容错。Ceph是另一种流行的开源分布式文件系统。Ceph把网络存储设备抽象成一张逻辑硬盘,然后“挂载”到分布式集群的每台服务器上,原理上非常像是Linux操作系统Mount一块物理硬盘。这样一来,用户程序访问Ceph的文件系统就跟访问Linux本地路径一样,非常方便。
分布式环境下的监控
分布式环境下,程序不是运行在本地,而是在集群上面,没有监控就等于程序运行在黑盒子下,无法调优,必须要有监控。分布式环境下的监控分为两个部分,一是性能监控,另一个是报警。性能监控要知道每个应用程序运行状态是什么样,即每一个应用程序占了多少CPU内存、服务的请求处理延迟等。我们是用Graphite来做应用程序性能监控;还有其他系统,比如MongoDB、Hadoop等开源系统,我们用Ganglia来做性能监控,比如CPU内存硬盘的使用情况等。报警是要在关键服务出现故障时,通知开发运维人员及时排解故障,我们用Zabbix来做报警。(责编/周建丁)
内容来源:《程序员》 收起阅读 »
【经验分享】教你写Android网络框架之基本架构
前言
在开发过程中,网络是我们很重要的一部分,因此我们就以网络框架或者说网络模块开始。在这个框架开发过程中,我会整理开发思路、以及遇到一些设计问题时会有怎么样的考虑、解决方案,当然这只是我个人的观点,大家也可以有自己的实现。除了网络框架,后续的系列还想更新ImageLoader框架、ORM框架,如果有时间也会增加动画框架和微博开发的系列文章。当然这些框架只是一些简单的框架基础,本人水平、时间有限,而且已经有现成、成熟的很多框架,我们在这里只是以重复造轮子的态度去学习轮子构建过程,从而达到能够造轮子的地步。至于很多细节的问题,我们这里就不过多讨论了,如果有兴趣,各位可以自行研究。
最后,我们暂且把这个框架命名为SimpleNet,下面我们一起进入主题吧。
基本结构
图1 ( SimpleNet的基本结构 )
SimpleNet框架的基本结构类似于Volley,包括一些命名上也有跟Volley一致。它主要分为四个部分,最上面的部分为Request,即各种请求类型。例如返回的数据类型为json的对应为JsonRequest,返回数据字符串的为StringRequest,如果需要上传文件,那么你需要使用MultipartRequest,该请求只支持小文件的上传,如果上传的文件过大则会产生OOM。
第二部分为消息队列,消息队列维护了提交给网络框架的请求列表,并且根据相应的规则进行排序。默认情况下更具优先级和进入队列的顺序来执行,该队列使用的是线程安全的PriorityBlockingQueue,因为我们的队列会被并发的访问,因此需要保证访问的原子性。
第三部分是Executor,也就是网络的执行者。该Executor继承自Thread,在run方法中循环访问第二部分的请求队列,请求完成之后将结果投递给UI线程。为了更好的控制请求队列,例如请求排序、取消等操作,这里我们并没有使用线程池来操作,而是自行管理队列和Thread的形式,这样整个结构也变得更为灵活。
第四部分则是Response投递类,在第三部分的Executor中执行网络请求,Executor是Thread,但是我们并不能在主线程中更新UI,因此我们使用
ResponseDelivery来封装Response的投递,保证Response执行在UI线程。
每个部分职责都相对单一,这样便于日后的升级和维护。
框架分析
图1中看起来有点像是分层架构,其实不是,这个图更多的是表达了它的逻辑顺序,而不是结构。而在我们的应用开发中,分层架构是一个重要的手段,如图2所示。
但在开发过程中,我们往往会把UI和业务层耦合起来,因为它们的关系太密切了,分解起来并不是那么容易。高手能够把复杂的事情简单化,而分解就是简单化的重要手段,分解这个过程在开发过程中我们成为重构。
那么我们就引入了一个分层概念,为了便于理解你也可以按照如图1的结构来加深理解。那么分层有什么优缺点呢?
优点:
复杂问题分解简单化,每一层负责自己的实现,并向外提供服务;
职责分离,复杂的系统都有很多人员进行开发,这些功能开发的管理和集成是个很严重的问题,分层设计实现之后,每层只需定义好自己的对外接口,其他依赖层服务的就可以进行开发;
每一层对其他层都是独立的,对外隐藏实现细节,上层无需知道下层的细节,只需调用接口即可;
有利于标准化。
缺点:
分层之后对于领域业务的修改有可能需要修改很多层;
过多的层次影响性能。
如上所说,我们的SimpleNet并不是分层的,而是简单的模块化,但是理论基础都是类似的,依赖于抽象而不依赖于实现、单一职责……这里引入分层的概念,这是便于理解,同时也是希望大家在开发过程中能够尽量保证模块的内聚性、耦合性。
再看SimpleNet,Request是一个抽象的泛型类,泛型类型就是返回的Response类型,例如StringRequest就是继承自Request。第二部分的RequestQueue依赖于Request,Request是抽象的,因此任何Request的子类都可以传递到请求队列中来,它依赖的是抽象Request,而不是具体的某个实现,因此保证了可扩展性。你可以自己实现自己所需的Request,例如大文件的上传Request。同理,第三部分的NetworkExecutor也只是依赖于Request抽象,但这里又引入了一个类型HttpStack,这个网络请求的真正执行者,有HttpClientStack和HttpUrlConnStack,两者分别为Apache的HttpClient和java的HttpURLConnection,关于这两者的区别请参考:Android访问网络,使用HttpURLConnection还是HttpClient?。HttpStack也是一个抽象,具体使用HttpClient还是HttpURLConnection则由运行系统版本来定,HttpStackFactory会根据系统版本给框架返回对应的HttpStack。最后的ResponseDelivery比较简单了,只是通过Handler将结果投递给UI线程执行,也就是执行RequestListener的onComplete方法,此时网络执行完成,用户即可在该方法中更新UI或者相关的其他的操作。
下面我们再看看SimpleNet的工程结构,如图3所示。
图3
这就是SimpleNet框架的基本结构了~
收起阅读 »
在开发过程中,网络是我们很重要的一部分,因此我们就以网络框架或者说网络模块开始。在这个框架开发过程中,我会整理开发思路、以及遇到一些设计问题时会有怎么样的考虑、解决方案,当然这只是我个人的观点,大家也可以有自己的实现。除了网络框架,后续的系列还想更新ImageLoader框架、ORM框架,如果有时间也会增加动画框架和微博开发的系列文章。当然这些框架只是一些简单的框架基础,本人水平、时间有限,而且已经有现成、成熟的很多框架,我们在这里只是以重复造轮子的态度去学习轮子构建过程,从而达到能够造轮子的地步。至于很多细节的问题,我们这里就不过多讨论了,如果有兴趣,各位可以自行研究。
最后,我们暂且把这个框架命名为SimpleNet,下面我们一起进入主题吧。
基本结构
图1 ( SimpleNet的基本结构 )
SimpleNet框架的基本结构类似于Volley,包括一些命名上也有跟Volley一致。它主要分为四个部分,最上面的部分为Request,即各种请求类型。例如返回的数据类型为json的对应为JsonRequest,返回数据字符串的为StringRequest,如果需要上传文件,那么你需要使用MultipartRequest,该请求只支持小文件的上传,如果上传的文件过大则会产生OOM。
第二部分为消息队列,消息队列维护了提交给网络框架的请求列表,并且根据相应的规则进行排序。默认情况下更具优先级和进入队列的顺序来执行,该队列使用的是线程安全的PriorityBlockingQueue,因为我们的队列会被并发的访问,因此需要保证访问的原子性。
第三部分是Executor,也就是网络的执行者。该Executor继承自Thread,在run方法中循环访问第二部分的请求队列,请求完成之后将结果投递给UI线程。为了更好的控制请求队列,例如请求排序、取消等操作,这里我们并没有使用线程池来操作,而是自行管理队列和Thread的形式,这样整个结构也变得更为灵活。
第四部分则是Response投递类,在第三部分的Executor中执行网络请求,Executor是Thread,但是我们并不能在主线程中更新UI,因此我们使用
ResponseDelivery来封装Response的投递,保证Response执行在UI线程。
每个部分职责都相对单一,这样便于日后的升级和维护。
框架分析
图1中看起来有点像是分层架构,其实不是,这个图更多的是表达了它的逻辑顺序,而不是结构。而在我们的应用开发中,分层架构是一个重要的手段,如图2所示。
但在开发过程中,我们往往会把UI和业务层耦合起来,因为它们的关系太密切了,分解起来并不是那么容易。高手能够把复杂的事情简单化,而分解就是简单化的重要手段,分解这个过程在开发过程中我们成为重构。
那么我们就引入了一个分层概念,为了便于理解你也可以按照如图1的结构来加深理解。那么分层有什么优缺点呢?
优点:
复杂问题分解简单化,每一层负责自己的实现,并向外提供服务;
职责分离,复杂的系统都有很多人员进行开发,这些功能开发的管理和集成是个很严重的问题,分层设计实现之后,每层只需定义好自己的对外接口,其他依赖层服务的就可以进行开发;
每一层对其他层都是独立的,对外隐藏实现细节,上层无需知道下层的细节,只需调用接口即可;
有利于标准化。
缺点:
分层之后对于领域业务的修改有可能需要修改很多层;
过多的层次影响性能。
如上所说,我们的SimpleNet并不是分层的,而是简单的模块化,但是理论基础都是类似的,依赖于抽象而不依赖于实现、单一职责……这里引入分层的概念,这是便于理解,同时也是希望大家在开发过程中能够尽量保证模块的内聚性、耦合性。
再看SimpleNet,Request是一个抽象的泛型类,泛型类型就是返回的Response类型,例如StringRequest就是继承自Request。第二部分的RequestQueue依赖于Request,Request是抽象的,因此任何Request的子类都可以传递到请求队列中来,它依赖的是抽象Request,而不是具体的某个实现,因此保证了可扩展性。你可以自己实现自己所需的Request,例如大文件的上传Request。同理,第三部分的NetworkExecutor也只是依赖于Request抽象,但这里又引入了一个类型HttpStack,这个网络请求的真正执行者,有HttpClientStack和HttpUrlConnStack,两者分别为Apache的HttpClient和java的HttpURLConnection,关于这两者的区别请参考:Android访问网络,使用HttpURLConnection还是HttpClient?。HttpStack也是一个抽象,具体使用HttpClient还是HttpURLConnection则由运行系统版本来定,HttpStackFactory会根据系统版本给框架返回对应的HttpStack。最后的ResponseDelivery比较简单了,只是通过Handler将结果投递给UI线程执行,也就是执行RequestListener的onComplete方法,此时网络执行完成,用户即可在该方法中更新UI或者相关的其他的操作。
下面我们再看看SimpleNet的工程结构,如图3所示。
图3
这就是SimpleNet框架的基本结构了~
收起阅读 »
新版imgeek.org社区公测
各位geekers,
我们重新输理和构建了了imgeek.org开发者社区,邀请各位测试,现在的样子看起来有点像stackoverflow.
对于新用户,直接注册即可进入
对于已经注册的用户,由于你的密码是加密存放的,我们无法迁移你的密码, 你需要通过"找回密码"进入:http://www.imgeek.org/?/account/find_password/
fat1
---------------------------------------------
2015.6.16 imgeek.org release note
. 调整问题及回复的展现形式
. 增加发布“文章”功能
. 新增“活动”功能
. 新增“工单功能
. 增强提醒功能
. 新增“知识库”/“帮助”功能
. 新增提问前“问题提示”功能
. 新增“简历”功能及实名认证功能
. 增加“个人空间”及用户信誉体系
. 增加与客服对接的交换格式
. 增强搜索功能
. 更好的手机端访问体验
. .......(更多好功能等你体验)
to do list :
1. 社交账号(微博、微信、QQ、facebook ,twitter,google)登录
2. "活动"板块优化
3. "话题"/标签优化
4. 提醒功能优化
5. 推出英文版本
6. 开源项目托管协作托管平台
原来的社区站仍然可以通过:http://www.imgeek.org/bbs 访问
收起阅读 »
我们重新输理和构建了了imgeek.org开发者社区,邀请各位测试,现在的样子看起来有点像stackoverflow.
对于新用户,直接注册即可进入
对于已经注册的用户,由于你的密码是加密存放的,我们无法迁移你的密码, 你需要通过"找回密码"进入:http://www.imgeek.org/?/account/find_password/
fat1
---------------------------------------------
2015.6.16 imgeek.org release note
. 调整问题及回复的展现形式
. 增加发布“文章”功能
. 新增“活动”功能
. 新增“工单功能
. 增强提醒功能
. 新增“知识库”/“帮助”功能
. 新增提问前“问题提示”功能
. 新增“简历”功能及实名认证功能
. 增加“个人空间”及用户信誉体系
. 增加与客服对接的交换格式
. 增强搜索功能
. 更好的手机端访问体验
. .......(更多好功能等你体验)
to do list :
1. 社交账号(微博、微信、QQ、facebook ,twitter,google)登录
2. "活动"板块优化
3. "话题"/标签优化
4. 提醒功能优化
5. 推出英文版本
6. 开源项目托管协作托管平台
原来的社区站仍然可以通过:http://www.imgeek.org/bbs 访问
收起阅读 »
Meteor 获 2000 万美元融资,Galaxy 是新重点
JavaScript 开发框架 Meteor 的开发团队 Meteor Development Group (简称 MDG)为它的框架堆栈、Web 开发工具和 JavaScript 移动应用融资 2000 万美元。此轮融资由经纬创投、安德森·霍洛维茨基金和 Trinity 风险投资领导,并提供了 MDG 开发开源框架所需要的资源。
MDG 的 CEO Geoff Schmidt 表示,下一步的重点是 Galaxy,它是基于谷歌开源项目 Kubernetes 的 Meteor 运行应用程序的系统。另外,Meteor 也增加处理 UI 的库,比如对 Angular 和 React 的支持。 收起阅读 »
MDG 的 CEO Geoff Schmidt 表示,下一步的重点是 Galaxy,它是基于谷歌开源项目 Kubernetes 的 Meteor 运行应用程序的系统。另外,Meteor 也增加处理 UI 的库,比如对 Angular 和 React 的支持。 收起阅读 »