Category Archives: magnificent

magnificent

最低成本搭建一个Rocket.chat

搭建私有聊天群组的必要性

大厂的封杀

当前,企业级别或者咨询类的自媒体个人除了考虑使用发光发热的微博、微信等大厂提供的社交服务的工具外,应该还是要考虑搭建一个属于自己的、免受厂商政策影响的私密聊天工具。说到这里有一个极端的例子,比如媒体虎嗅,之前就和微博的关系搞得不是很好,当然微博在自己的注册协议(http://weibo.com/signup/v5/protocol)中就明确的说清楚了:

当然你要说这是“霸王条款”,那就是你的理解了,但是既然人家明确的写出来了,而且你在注册的时候同意了这个协议,别人封杀你的账号就是他按照“规章制度”办事了。

信息的审查

当然我们不能在无凭无据的情况下职责有人在背后审查互联网言论,但是至少在不涉及国家敏感信息下,大厂可能还是会在不经意或者经意的情况下对个人的通信言论进行审查,有一个典型的例子,就是大家在用微信聊天的时候发送图片、特别是截图的时候会感觉明显的非常慢,可能小图标转了半天还是发不出去,我的理解在这个过程中就有“审查”出现了。而且某个管家还会依据“网友”的举报对链接进行分级,加上不同的提示,比如危险、拦截、健康等等。所谓信息的不对称,就是不对称在这里,用别人的手段啊。当然作为一个企业,应该没有一个老板希望自己公司的聊天记录经过了其他人的服务器吧?

为啥要选择Rocket.chat

Rocket.chat是什么

这个东西大家都一致认为是slack的开源代替品。反正项目组也是这么自己介绍自己的。“Have your own Slack like online chat, built with Meteor.”,除了这个,从Google搜到的自己的介绍是Rocket.Chat 是特性最丰富的 Slack 开源替代品之一。 主要功能:群组聊天,直接通信,私聊群,桌面通知,媒体嵌入,链接预览,文件上传,语音/视频 聊天,截图等等。这个项目暂时是托管在github的,项目地址 https://github.com/RocketChat/Rocket.Chat 。

部署到哪

其实项目主页上各大托管厂商也都在互相争抢生意,看排名颇有竞价的意思,大概有这么一些都能快速部署:

方便是方便,但是毕竟咱们在中国,弄到这些环境企业访问起来还是比较麻烦,那国内有没有就自动集成这个的呢?讲了半天终于讲到本文的重点了,除了部署到自己的服务器上,还可以从SAE上快速安装

部署流程

从应用商店安装Rocket.chat

注册一个SAE账号后,进入:http://sae.sina.com.cn/?m=appmarket&a=index&id=5&type=community

点击安装后,选择一个二级域名的名字,然后选下运行环境的配置点下一步即可,这里将会使用到SAE的两个服务,MongoDB和容器运行环境,这两个都是按照运行时间计费的。稍微等待一下就可以等到安装完成界面了。但是由于需要MongoDB服务,这个东西SAE给分配了两个二级域名,但是解析需要一点时间,导致容器第一次启动的时候会报连不上mongodb的错误,稍等两分钟后就会自动启动成功的

创建管理员账户

这个软件的第一个注册用户就是默认的管理员,所以在安装成功后,看到容器启动就可以去创建账号了。

开始使用

到此应该就完成了搭建,看看,整个过程不用写一行代码,点点鼠标就行了。

股票实时接口

6/20号,我们联合新浪财经发布了实时的股票查询接口。这个接口在国内确实算是第一份正规、有保障的数据源了,很值得广大的股票从业人员、开发人员使用。该数据的最大的特点就是百分百保证实时,百分百保证怎么调用都不会封禁。

昨天也拿这个事情发了一个微信的动态,很多朋友对此表示很关心。好久也没有更新博客了,正好借着这个事情说一下我个人的简介以及介绍一下如何使用这个数据源。

接口文档

先给出官方的文档地址:http://www.sinacloud.com/doc/api.html#xin-lang-cai-jing-shi-shi-bao-jia

数据源能做什么

用一个开放的数据能做什么,这个让我想起了11年微博开放平台很火热的时候,那时候我也是一个普通的应用开发者,那时候仿佛大家的想法层出不穷,有用粉丝的头像做图片墙的、有用粉丝做游戏的、有做各种测试的、还有用接口做树洞的等等等。所以在开放一个数据源的情况下,每个人都会有自己的见解,都可以用程序实现自己心中的想法。我能想到的几个点在以下几点:

展示自家公司的K线图 这个是最典型的应用场景了,如果开发者所在的公司是一个上市公司,并且需要在自己的官网上展示自家公司的股票信息,这个时候没什么比找到一个不会出问题的接口最让人省心的的。

给炒股的人开发提示应用 比如说我接下来就会开发一个这样的应用,可以支持让炒股的人在系统中配置自己的持仓列表,并输入买入价,这样就不用一直盯着大盘了,让股民们自己设置一个赔钱的低点和赚钱可以抛出的高点,系统借助这个接口去帮助股民们实时监控这些数据,达到后就发送短信等。

投资分析 当然这个需要一定的数据累计,需要实时的去抓取股票的信息并保存下来,累计到一定量级的时候就可以拿这些数据做分析了。

模拟操盘系统 借助实时的股票报价接口就可以自己开发一个模拟操盘系统了,还可以实时的得到模拟操盘的盈亏值。

如何使用这个接口

注册SAE并创建应用 按设计,用于接口的签名只能用应用的accesskey和secretkey,因此不论你是不是SAE的应用,想要使用这个接口都必须注册SAE(http://sae.sina.com.cn)并创建一个应用。

接口SDK 我写了一个PHP的sdk,需要的可以从这里下载:https://github.com/xiaosier/gofun/tree/master/sae-stock-api-sdk

你看,全世界都在奔跑

一个本是慵懒的周末午后,睡到四点起来。似乎看完了所有能看到的东西,然后无聊之下静下心来拜读了一下最近还在各种风声水起的国内云计算行业。总结出了这么一个标题,“你看,全世界都在奔跑”。

总体看起来,大家的思路几乎都如出一辙,说白了都想走差异化竞争,都想着自己的产品能改变世界。当然这对每一个创业的或者巨头的产品来说都是好的,不看好自己产品的生产者怎么会做出优秀的产品。国内的互联网确实面临着恰似大家在微博上的意向,总是觉得腾讯是各种创业团队最恨的敌人。但是这也不能怪人家,这个世界总是公平的,人家厂内的人也是人才,也要在KPI的考试卷上交出好的成绩。你做我也做,这不是现在各大公司的一贯思路,也是上次和一个微博运营同事交流得出的一个专业点的词叫“防守”。

那到底什么是云计算,个人的见解总是不同,现在的PAAS厂商自称的云总是不被大家接受,原因在于云计算这个词汇中总是包含着计算这么个词汇。大家印象中的计算就是计算数据,总会有人误解提供web服务就不是计算了。然后大家就认为IAAS这种东西应该叫云计算,因为我可以ssh远程登陆到机器呀,装上各种开源的软件,然后就能分析日志了等等。似乎这个理听起来也没有错,我之前去深圳讲我们的计算能力,似乎现场理解的人不多。那到底啥是云计算呢,如果你真理解了我觉得你会选一个混合云,因为兼顾了两种,一种可以称为真正的分布式,还可以包含一部分称为“灵活”的东西。我平时交流中听到很多的声音都是用PAAS不灵活,限制这限制那,我也觉得这种观点很有道理。我个人认为云计算就是一种高可靠的服务,怎么实现大家随意。

开发者需要什么?我有时总是在问自己这个问题,但由于自己是从一个开发者转变成云服务的开发者,而且我可能局限于PaaS模式了,认识可能有浅薄的地方。其实所有的互联网类的服务无外乎几种,类似微博的web服务,客户端服务,类似QQ的及时通讯服务,类似大话西游等在线游戏类服务。然后各种企业都是在想实现这些东西,只是产品的思路不一样,例如一个买鞋的只要一个在线商城和一个手机客户端就行了。web服务无外乎就是用各种常用的语言,PHP、JAVA、PYTHON等构建的站点,借助各种数据库服务,存储服务完成附件存储;客户端就更简单了,一个REST接口就行了。及时通讯和游戏可能复杂一点,需要在机器上运行各种自己编写的复杂大型编译型程序。那么思路就很明显了,PAAS服务专注于提供前两种企业或者个人,IAAS专注于提供后两种服务企业。

开发者会这么选择么?从我接触这个行业三年以来的总结是,不总是大家都能看清这个问题,大家总是想着还是要创建一个自己的虚拟机登上去看到apache在运行才感觉自己是一个做技术的,总要敲点命令才显得专业,其实我有时不禁在想,真的每一个人都能做到那么专业吗?跑在虚拟机上的web服务真的能自己做到负载均衡吗,我看不见得。大道理总是人人都知道,大家看看各大厂商的各种演讲都能看到一些端倪,大家都是差不多那么做的,然后各种想展示自己能力的开发者们就会这么觉得了,我也可以这么做。公司的老板们就会这么想,没有解决不了的问题,只有不努力的员工还有不够的钱。有时看到这种夸夸奇谈真想转发一下呵呵一下,如果真是什么事情都是能靠钱解决的那你们早就发达了。

开发者该如何选择?当然跟上上一个段落说的一样,看看你的业务。但有一条切忌,千万不要自己去买个服务器托管,这种做法现在看起来是非常土的做法,还好我身边看到的创业公司都没有这么做的了。只有一些传统的国有企业等还在采取这么土的方式,为啥土,因为你们没有能力能管好。牛逼的人儿肯定不会呆在你们那给你们好好做技术,哪个技术儿没有一个技术梦。

现在云计算厂商的模式。上面说了那么多好像有点离题了,我觉得我们之前商务的小姑娘在PPT上写的那么一句还是很有意思的,叫“不提供实用功能的云计算都是不靠谱的”,这个也大体能说明从一个用户角度看到的希望云计算提供啥,你吹得天花乱坠的,人家用起来看文档花了半天还不知道啥是啥的怎么用嘛,你说对不对。产品的友好是所有产品人追求的目标,好在现在所有的云计算厂商都提供了一个很友好的管理界面,虽然我一直不太认可GAE的界面,但是人家是鼻祖也不好说什么了。现在出来一个新的模式Docker,催化了另外一种云计算创业公司的思路,就是看中了IAAS的灵活性和PAAS的约束性。这么想,大家都想登到机器上去,业务挂个QQ也是好的,好吧我瞎说的,然后呢,这部分用户实在对那些配置什么ngnix啊,装mysql啊不懂,那怎么办呢?好不容易用上了高大上的云但是不知道怎么整,这该如何是好,这时候不要急,可以找这批厂商,花点钱买下人家做好的docker镜像,一条命令就把一个wordpress装好了,还是挺靠谱的。

现在开发者的心。我个人觉得现在每个国内的技术都在藐视别人的技术,不就是做了个啥啥啥吗,只要我们做肯定做的比你们好,然后对facebook啊,google的技术崇拜的一塌糊涂,其实吧我也挺崇拜他们技术的,但是我不太认同前面的部分,对他人的尊重是基本准则,这不是每个妈妈小时候都会教的吗。我可以坦白的说,你做肯定做不到别人那样,现在叫你去重新做个QQ,撇开运营不说吧,技术你也实现不了。在面对一个事情之前,先好好思索下,然后再说no比较靠谱。

那么以上基于IAAS做的一些PaaS的工作其实有意义吗?现在看还是很有意义的,跟上文中我阐述了个人开发者的思想有关,想不被约束但是又没有能力自己基于这些个创建的好虚拟机做好工作,其实本质上还是相当于花了两份钱体验了不同的服务,最终体验了一下IaaS,并把业务跑在了一个别人基于IaaS上的PaaS中,不能没有意义,因为这个PaaS会表现出直接的PaaS平台表现不出的灵活性,但这种灵活性不是每一个企业都需要,而且使用后的运维工作还是需要自己承担。这个也有点脱离了PaaS的样子,因为PaaS其实最大的亮点在于运维托管,所以运维工程师的工资其实是很高的,比我高多了!好吧,瞎吐槽。

正与标题的意思,这个世界总是在快速的变化。我们看到的八分钟之后的世界比八分钟之前的太阳变化可要大多了,PaaS平台其实也不会坐以待毙的,我想表现出更好的灵活性是将来PaaS平台奋斗的目标吧,PaaS借助于IAAS提供混合云的优势比IAAS厂商要方便的多,例如我可以在你创建的应用之外让你绑定一个或者多个虚拟机运行你自定义的服务,但是PAAS本身的服务却不会打任何折扣,这时候就可以提供更灵魂的混合云模型。

更快,更稳定。这个应该是所有的云计算厂商追求的目标吧,当然自己做了这么多年的公有云有很多的槽可以吐,国内的网络环境还有政策原因让公有云受到了很多限制,高速的接入机房和频繁的网络攻击应对就会让很多的公有云创业公司走向没落,因为会发现根本没有那么多的钱可以烧。而且面对强大的竞争对手例如阿里云啊几乎会让人感到绝望,但是我还是很庆幸看到现在成长起来了很多很靠谱的公司例如Ucloud等。

说话留一线吧,总觉得现在有很多公司的人或者微博人士大大没有这个准则。你在奔跑,别人就在往回走么,别异想天开了,何况你也不一定在跑。

Ratelimit callbacks suppressed

最近在查一个问题的时候看了看系统的messages,然后无意中注意到好多这样的错误,贴一点日志片段:

Jan  6 10:07:06 yq155 kernel: __ratelimit: 1957 callbacks suppressed
Jan  6 10:07:06 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:06 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:06 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:06 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:06 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:06 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:06 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:06 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:06 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:06 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:11 yq155 kernel: __ratelimit: 2023 callbacks suppressed
Jan  6 10:07:11 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:11 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:11 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:11 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:11 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:11 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:11 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:11 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:11 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:11 yq155 kernel: TCP: time wait bucket table overflow
Jan  6 10:07:17 yq155 kernel: __ratelimit: 194 callbacks suppressed

google了一把,搜到了 http://zszsit.blogspot.com.br/2012/10/ratelimit-callbacks-suppressed.html 这篇牛逼的文章,我看了一下服务器的配置,还真是这样的设置:

[root@yq155.webinternal /var/log]#  cat /proc/sys/kernel/printk_ratelimit
5
[root@yq155.webinternal /var/log]#  cat /proc/sys/kernel/printk_ratelimit_burst
10

这也正好迎合了上面的日志为什么间隔都是5S了,原因是因为这个10条/5秒的配额干掉了其他的系统日志。怎么解决这个问题呢?那首先肯定还是要优化产生这么多问题的原因了,例如TCP的time wait,这么多的wait肯定是有问题的;其次还可以稍微调大这个坑爹的10条/5S的配额值。设置/etc/sysctl.conf:

kernel.printk_ratelimit = 30
kernel.printk_ratelimit_burst = 200

vim 自动添加文件注解(针对php和js文件)

我们在开始一个新的项目的时候总想对所有的文件加一点注释信息,例如版权呀,作者呀,文件创建的时间呀等等。那么用VIM 是怎么做到的呢?

修改.vimrc文件

首先需要修改下vimrc的配置文件,让vim 在创建新文件的时候自动补齐那些信息。添加的部分如下:

autocmd BufNewFile *.php,*.js exec ":call SetComment()"

func SetComment()
        if expand("%:e") == 'php'
                call setline(1,"<?php")
        elseif expand("%:e") == 'js'
                call setline(1,"// JavaScript file")
        endif
        call append(line("."),   "/*")
        call append(line(".")+1,   "  +----------------------------------------------------------------------+")
        call append(line(".")+2, "  | LazyApiDoc                                                           |")
        call append(line(".")+3, "  +----------------------------------------------------------------------+")
        call append(line(".")+4, "  | LazyApiDoc is a tool for restful interface document manager.         |")
        call append(line(".")+5, "  | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )              |")
        call append(line(".")+6, "  +----------------------------------------------------------------------+")  
        call append(line(".")+7, "  | Author:lazypeople <lazy@changes.com.cn>                              |")
        call append(line(".")+8, "  +----------------------------------------------------------------------+")
        call append(line(".")+9, "*/")
        call append(line(".")+10, " ")
        call append(line(".")+11, "/*".expand("%:t")." ".strftime("%Y/%m/%d %H:%M:%S %Z")." lazypeople*/")  
endfunc

主要是autocmd BufNewFile *.php,*.js exec “:call SetComment()”让vim在创建文件的时候可以知道到底是不是php或者js文件,这个是依赖创建时的后缀名决定的。当然如果你不是php程序员,当然可以按照上述的例子修改成为你的语言,例如.py .c .cpp .pl等等.如果相对所有的文件都加上注解,这个可能不是很好办,因为不同文件的注解形式是不一样的,例如你对c语言的注解就无法用在html文件中。

测试

lazy@lazy-Latitude-E5420:~/workspace/lazyapidoc$ vim testAutoCommit.php

可以看到生成的文件:

  1 <?php                                                                          
  2 /*                                                                              
  3   +----------------------------------------------------------------------+      
  4   | LazyApiDoc                                                           |      
  5   +----------------------------------------------------------------------+      
  6   | LazyApiDoc is a tool for restful interface document manager.         |      
  7   | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )              |      
  8   +----------------------------------------------------------------------+      
  9   | Author:lazypeople <lazy@changes.com.cn>                              |      
 10   +----------------------------------------------------------------------+      
 11 */
                                                                             
 12                                                                                
 13 /*testAutoCommit.php 2013/10/11 10:21:11 CST lazypeople*/

关于租约机制Lease

最近在工作中遇到了这类的问题,就是在多个后端的环境下需要在单一的后端锁定某个任务独立执行。这个需求其实是在需要强制取消某个正在进行的任务时才需要的,例如我们需要在不同的后端A、B、C等等机器上强制终止某些正在执行的脚本,但是调用强制终止的接口还是需要通过API调用,那么由于HA的机制,还是会随机的落在A、B、C上,假设处理这个接口的机器落在A上,那执行任务的脚本可能不在A上,那么此时想终止其他机器上正在执行的任务,可能就需要SSH连接到其他的机器去ps -ef|grep 找出pid,然后再kill掉进程。这种思路其实是不合理的,它主要会产生两个问题,第一:靠找pid可能存在安全隐患,万一找错了呢?杀掉了核心进程例如apache的就悲剧了;第二:这样会增加系统对其他环境的依赖,例如需要ssh,配置后端机器之间的ssh key等。而且这个非常不利于系统的调整,例如新加一台后端机器,还需要添加很多东西。那么在此时使用租约机制就会有一种柳暗花明的感觉。

我们可以利用最土的mysql数据库作为锁,将作业任务以一条条的记录推到表中,其中设置两个字段,第一个可以称之为status字段,这个字段用于标识当前任务有没有被认领。那么有人可能会说,这样就可以了呀,认领后状态从waiting变成doing不就能标识这个任务的状态了。但其实不行,因为在这种情况下会发生一种叫“死锁”的状态。一旦出现死锁,其实是很麻烦的事情,首先它会影响你的业务逻辑,让作业任务变得混乱不堪。其次死锁问题手工解决很麻烦,你得慢慢的顺着你的程序逻辑去一步一步的追踪导致应该杀死哪个机器的哪个进程或者修改什么字段才能解决这个死锁问题。那么在这种情况下,我们可以在表中再加上一个timeline字段,这个字段可以简单的用time()【php】得到的时间戳填进去,我们可以假定它是有时效性的。例如100s,那么作业任务在执行的时候,我们强制规定脚本在执行操作的时候每一百秒之内必须要更新这个时间戳,你没有更新我们就认为你已经阵亡。就算你没有阵亡在你下一次判断的时候也必须要推出脚本。

以上这个实现的原理其实在我们的实际生活中也是常常应用的,例如北京的地铁司机,虽然地铁和飞机等等完全可以做到无人驾驶了,而且可以做到很好,但是每列地铁还是有几个司机在那盯着。而且规定30s之内必须要踩一次特定的装置,这样调度中心才会确认,司机没有睡着,如果没有触发不论地铁是否运行正常,都会强制停车。那么以上的租约锁也同样是这样,客户端需要时刻提醒调度中心,就是我们存放作业任务的mysql,告诉它你还有心跳。

下面引一段比较经典的关于租约的介绍。

1989年斯坦福大学的Cary G. Gray和David R. Cheriton提出了利用租约来维护缓存一致性的方法。所谓租约,其实就是一个合同,即服务器给予客户端在一定期限内可以控制修改操作的权力。如果服务器要修改数据,首先要征求拥有这块数据的租约的客户端的同意,之后才可以修改。客户端从服务器读取数据时往往就同时获取租约,在租约期限内,如果没有收到服务器的修改请求,就可以保证当前缓存中的内容就是最新的。如果在租约期限内收到了修改数据的请求并且同意了,就需要清空缓存。在租约过 期以后,客户端如果还要从缓存读取数据,就必须重新获取租约,我们称这个操作为“续约”。

在租约期限内,客户端可以保证其缓存中的数据是最新的。同时,租约可以容忍各种非拜占庭式失效(机器崩溃、网络分割等)。如果客户端崩溃或者网络中断,服务器只需要等待其租约过期就可以进行修改操作。如果服务器出错丢失了所有客户端的信息,它只需要知道租约的最长期限,就可以在这个期限之后安全的修改数据。与回调方式相比,服务器只需记住还拥有租约的客户端即可。

租约与带期限的锁非常相似,但更加灵活,因为租约还提供了“寻求同意”的机制(我觉得可以称为“带期限可妥协的锁”)。服务器还可以实现多种租约,比如“写租约”和“读租约”,并保证一个时间段内只有一个写租约或者多个读租约,这就相当于是单写者多读者的锁协议。

因为租约是基于时间的,因此其有效性需要系统时间来保证。如果服务器的时钟快而客户端时钟慢,那么有可能服务器认为一个租约已经过期而客户端仍然认为其有 效,就可能导致错误。对这种情况就必须通过时钟同步协议来解决了,不过这种情况很少见。一般情况下,我们可以认为一个分布式系统的时间是同步在一个很小的时间差e之内,只需把这个e考虑到租约期限内即可。

租约的属性和管理有多种选择,首先要考虑的就是租约期限的长短。

一般情况下,应当选择较短的租约期限。与长租约相比,短租约有三个优点。首先,在失效情况下修改操作往往需要等待租约过期,因此短租约就意味着更短的失效延迟。其次,就算一个客户端已经不再需要读取数据,但在其租约过期前,任何的修改操作仍然需要征求它的同意,这种情况叫做“假共享”,显然租约期限越长,这个问题就越严重。最后,短租约也使得服务器要维护的客户端信息更少。然而短租约也意味着更大的续约开销, 因此对于要反复读取却很少修改的数据,长租约会更有效。因此,对租约期的选择要权衡失效延迟、假共享开销和续约开销等多个因素,服务器可以根据数据访问特 性和客户端的性质灵活设置期限。事实上,如果我们把租约期限设为零,就相当与轮询,此时修改操作随时可以进行,而读取数据总是要联系服务器。如果把租约期 限设为无限长,就相当于回调。

除了期限的选择,还有很多管理选项。对客户端来说,可以选择 是否续约、何时续约以及是否同意修改等。比如为了减少读取延迟,客户端可以在租约过期前就续约,不过这样可能加重服务器的负担。对服务器来说,可以选择是 否发放租约、租约覆盖粒度以及对如何进行修改操作。比如在收到修改请求后,服务器可以不征求客户端同意,而是简单的等待所有租约过期(等待时不再发放新租 约以避免无限期的延迟)。对于“安装文件”,也就是修改极少的文件(比如头文件、库文件),服务器可以用一个租约来覆盖一批文件,同时定期广播续约通知来节省开销,如果需要修改数据,就停止广播并等待租约过期即可。

关于程序向下兼容的教训

其实好久都没有动手写点什么的习惯了。今天的工作给了自己一次教训,所以为了避免再次走向一个同样的坑,还是写下来给自己提个醒。

这种问题就好像是前人给你留下的坑,但是你想填上这个坑,但是却发现有些人已经习惯了那个地方有个坑。而且把这种错误当做是正确的情况在处理。那么你填上了有些人就觉得你错了。其实从我们的角度上来看,不是我们的问题,因为我们认为这个bug已经修复,但是站在用户的角度,我们确实都是错了,因为用户没有任何的必要为我们以前的过失买单。

Continue reading

curl不能发送value为空的header问题追踪以及解决方案

下午遇到的奇葩的问题,先举个curl的例子吧。分别执行这个命令:

curl "www.baidu.com" -H"XXXX:" -v >/dev/null
curl "www.baidu.com" -H"XXXX:1" -v >/dev/null

看到结果:

[root@vm12080024 ~]# curl "www.baidu.com" -H"XXXX:" -v >/dev/null
* About to connect() to www.baidu.com port 80
*   Trying 61.135.169.125... connected
* Connected to www.baidu.com (61.135.169.125) port 80
> GET / HTTP/1.1
> User-Agent: curl/7.15.5 (x86_64-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5
> Host: www.baidu.com
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Thu, 25 Apr 2013 09:10:19 GMT
< Server: BWS/1.0
< Content-Length: 10502
< Content-Type: text/html;charset=utf-8
< Cache-Control: private
< Set-Cookie: BDSVRTM=17; path=/
< Set-Cookie: H_PS_PSSID=2240_2198_1463_1945_2201_1788_2250_2260_2287; path=/; domain=.baidu.com
< Set-Cookie: BAIDUID=D7136CF3FDF7D815ED0F017710590E45:FG=1; expires=Thu, 25-Apr-43 09:10:19 GMT; path=/; domain=.baidu.com
< Expires: Thu, 25 Apr 2013 09:10:19 GMT
< P3P: CP=" OTI DSP COR IVA OUR IND COM "
< Connection: Keep-Alive
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10502  100 10502    0     0   237k      0 --:--:-- --:--:-- --:--:-- 4424kConnection #0 to host www.baidu.com left intact

* Closing connection #0

再对比一下:

[root@vm12080024 ~]# curl "www.baidu.com" -H"XXXX:1" -v >/dev/null
* About to connect() to www.baidu.com port 80
*   Trying 61.135.169.125... connected
* Connected to www.baidu.com (61.135.169.125) port 80
> GET / HTTP/1.1
> User-Agent: curl/7.15.5 (x86_64-redhat-linux-gnu) libcurl/7.15.5 OpenSSL/0.9.8b zlib/1.2.3 libidn/0.6.5
> Host: www.baidu.com
> Accept: */*
> XXXX:1
>
< HTTP/1.1 200 OK
< Date: Thu, 25 Apr 2013 09:10:22 GMT
< Server: BWS/1.0
< Content-Length: 10492
< Content-Type: text/html;charset=utf-8
< Cache-Control: private
< Set-Cookie: BDSVRTM=19; path=/
< Set-Cookie: H_PS_PSSID=2240_2299_1444_2132_1945_1788_2250_2254; path=/; domain=.baidu.com
< Set-Cookie: BAIDUID=C4F1B495285429AD74DBDBB39F76E704:FG=1; expires=Thu, 25-Apr-43 09:10:22 GMT; path=/; domain=.baidu.com
< Expires: Thu, 25 Apr 2013 09:10:22 GMT
< P3P: CP=" OTI DSP COR IVA OUR IND COM "
< Connection: Keep-Alive
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 10492  100 10492    0     0   242k      0 --:--:-- --:--:-- --:--:-- 4419kConnection #0 to host www.baidu.com left intact

* Closing connection #0

很显然的我们在HTTP 交互时当header的值为空时会被舍弃,那么这个舍弃究竟是在客户端还是服务器端?
先看RFC的协议:http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2

> Empty HTTP headers are imho legal according to RFC. They are used in certain
> environments to indicate special things to back-end services.

The field-content does not include any leading or trailing LWS: linear white space occurring before the first non-whitespace character of the field-value or after the last non-whitespace character of the field-value. Such leading or trailing LWS MAY be removed without changing the semantics of the field value. Any LWS that occurs between field-content MAY be replaced with a single SP before interpreting the field value or forwarding the message downstream.

IMHO, sending an empty header is completely pointless. It shouldn’t be done, and parsers may not correctly parse these headers. Traditionally, people who want to circumvent such limitations when dealing with non-compliant components have specified “pseudo-empty” values like this:

XXX: ""

If you simply want to validate that a header field was sent as some form of boolean switch, consider sending a placeholder value like the above instead of an empty value.
从以上的文档看起来,[ field-value ]是可选的选项,那么从RFC的协议来看是允许的,因此这个drop应该是在客户端的libcurl把空的header头干掉了,我们深度剖析一下libcurl的处理header部分的代码:

CURLcode Curl_add_custom_headers(struct connectdata *conn,Curl_send_buffer *req_buffer)
{
  char *ptr;
  struct curl_slist *headers=conn->data->set.headers;

  while(headers) {
    ptr = strchr(headers->data, ':');
    if(ptr) {
      /* we require a colon for this to be a true header */

      ptr++; /* pass the colon */
      while(*ptr && ISSPACE(*ptr))
        ptr++;

      if(*ptr) {
        /* only send this if the contents was non-blank */

        if(conn->allocptr.host &&
           /* a Host: header was sent already, don't pass on any custom Host:
              header as that will produce *two* in the same request! */

           checkprefix("Host:", headers->data))
          ;
        else if(conn->data->set.httpreq == HTTPREQ_POST_FORM &&
                /* this header (extended by formdata.c) is sent later */
                checkprefix("Content-Type:", headers->data))
          ;
        else if(conn->bits.authneg &&
                /* while doing auth neg, don't allow the custom length since
                   we will force length zero then */

                checkprefix("Content-Length", headers->data))
          ;
        else if(conn->allocptr.te &&
                /* when asking for Transfer-Encoding, don't pass on a custom
                   Connection: */

                checkprefix("Connection", headers->data))
          ;
        else {
          CURLcode result = Curl_add_bufferf(req_buffer, "%s\r\n",
                                             headers->data);
          if(result)
            return result;
        }
      }
    }
    else {
      ptr = strchr(headers->data, ';');
      if(ptr) {

        ptr++; /* pass the semicolon */
        while(*ptr && ISSPACE(*ptr))
          ptr++;

        if(*ptr) {
          /* this may be used for something else in the future */
        }
        else {
          if(*(--ptr) == ';') {
            CURLcode result;

            /* send no-value custom header if terminated by semicolon */
            *ptr = ':';
            result = Curl_add_bufferf(req_buffer, "%s\r\n",
                                             headers->data);
            if(result)
              return result;
          }
        }
      }
    }
    headers = headers->next;
  }
  return CURLE_OK;
}

显然通过

while(*ptr && ISSPACE(*ptr))
        ptr++;

可以看到类似xxxx: 这样的头部全部被忽略了。但从代码可以看到在分号下,我们可以进入到下一个循环,可以设置空的http header了,在版本Fixed in 7.23.0 – November 15 2011。

Empty headers can be sent in HTTP requests by terminating with a semicolon

现在测试一下手工升级curl:

wget "http://curl.haxx.se/download/curl-7.30.0.tar.gz"
tar -zxvf curl-7.30.0.tar.gz
cd curl-7.30.0
./configure -prefix=/usr/local/curl; make; make install
cp  /usr/local/curl/bin/curl  /usr/bin/

此时再检查一下curl的版本

curl -V

可以看到:

[root@mingming-dev curl-7.30.0]# curl -V
curl 7.30.0 (x86_64-unknown-linux-gnu) libcurl/7.30.0 OpenSSL/1.0.0 zlib/1.2.3 libidn/1.18 libssh2/1.2.2
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp scp sftp smtp smtps telnet tftp
Features: IDN IPv6 Largefile NTLM NTLM_WB SSL libz

此时用分号发送看一下:

curl "www.baidu.com" -H"xxxxx;" -v >/dev/null
root@vm12080024 logs]# curl "www.baidu.com" -H"xxxxx;" -v >/dev/null
* About to connect() to www.baidu.com port 80 (#0)
*   Trying 61.135.169.105...
* Adding handle: conn: 0x25a9cb0
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x25a9cb0) send_pipe: 1, recv_pipe: 0
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to www.baidu.com (61.135.169.105) port 80 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.30.0
> Host: www.baidu.com
> Accept: */*
> xxxxx:
>
< HTTP/1.1 200 OK
< Date: Fri, 26 Apr 2013 01:47:37 GMT
...下面的省略了

可以升级以后可以直接使用分号发送empty header了 :)

再细致一点,应用到php之中

注意:此时必须重新打包php的curl模块,前提是先升级libcurl~

重新打包curl模块的部分不再赘述了..找源码重新编译一下覆盖掉以前的就行。此时进行测试test.php:

<?php
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);
$url = 'http://skirt.sinaapp.com';
$method = 'POST';
$header = array('X-SWS-Container-Meta-Expires-Rule;','X-SWS-Container-Meta-Expires-Rulesssss:222222222222222222222222');
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, 120);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
$resp = curl_exec($ch);
$resp_info = curl_getinfo($ch);
var_dump($header);
echo "=========================================================================\n";
var_dump($resp_info);

理论上应该包含了一个X-SWS-Container-Meta-Expires-Rule的没有value的header,事实本如此,直接看测试的结果:

[root@mingming-dev ~]# php test.php
array(2) {
  [0]=>
  string(34) "X-SWS-Container-Meta-Expires-Rule;"
  [1]=>
  string(63) "X-SWS-Container-Meta-Expires-Rulesssss:222222222222222222222222"
}
=========================================================================
array(22) {
  ["url"]=>
  string(25) "http://skirt.sinaapp.com/"
  ["content_type"]=>
  string(24) "text/html; charset=UTF-8"
  ["http_code"]=>
  int(200)
  ...........
  为缩短篇幅,中间的部分就省略了
  ...........
  ["request_header"]=>
  string(158) "POST / HTTP/1.1
Host: skirt.sinaapp.com
Accept: */*
X-SWS-Container-Meta-Expires-Rule:
X-SWS-Container-Meta-Expires-Rulesssss:222222222222222222222222

"
}

此时惊喜的看到包含了X-SWS-Container-Meta-Expires-Rule:这个没有value的header~此文献给这个问题,希望对将来碰到这个问题的人有些启示。

利用apache搭建反向代理服务器

之前也只是用别人搭好的环境,例如当你的域名没有并备案时,你在使用SAE绑定独立域名的时候可以使用SAE提供的反向代理服务。
反向代理的工作原理就是用代理的省份接收客户端的请求,然后从实际的机器上获取数据返回给客户端。在用户看来,是透明的,他们并不知道所访问的域名在底层是怎么工作的,那么在实际的工作中,代理服务器的速度和代理服务器到真实工作的server的速度就决定了网站响应的速度,这有点类似于“木桶效应”。
那么简单的介绍下怎么在一台apache服务器上配置完成代理服务器。
Continue reading

CSMA网络的仿真

其实CSMA网络的仿真原理很简单,就是产生一个随机的退避时间加上一个网络是否空间的侦听,我看别人写的程序中是给$bus给定一个0或者1,当bus为0时表示信道是空闲的,此时退避到0的网络节点就可以发送数据,反之,如果检测到信道是忙的情况下,那么就继续原地“数羊”。那么究竟能不能用程序语言去仿真实现这个过程呢,that is a problem…让我头疼的问题,这个随机的退避时间如何产生,如果采用单一的总线 ,那么怎么将模型和我建设的四个节点的模型连接起来呢?难道需要四条总线?

Continue reading