2013年6月14日星期五

在CentOS上一键安装OpenStack

(转载请注明出处:http://traits.blog.163.com/blog/static/1951873902013330103249417/ )
网上有很多Ubuntu平台安装OpenStack的资料,也有一些一键安装的脚本,但是RHEL以及CentOS等系统的资料就少很多。在这里要感谢Redhat做的安装工具packstack (https://github.com/redhat-openstack/packstack),基本上可以达到在CentOS 6.4平台上一键安装最新的OpenStack Grizzly版本。

具体流程可以参加Redhat提供的手册,http://openstack.redhat.com/Quickstart

稍微简述一下:

1.单节点安装

安装软件源
sudo yum install -y http://rdo.fedorapeople.org/openstack/openstack-grizzly/rdo-release-grizzly-1.noarch.rpm
安装packstack
sudo yum install -y openstack-packstack
一键安装
packstack --allinone

收工,keystonerc_admin的文件在/root目录下

2.多节点安装
准备双网卡的机器若干,1个做控制节点,其他做计算节点。假设em1是第一个网卡,em2是第二个网卡(将来做vm的内网)。
有以下两种部署方案:

2.1 增量部署计算节点
首先,按照单节点安装的方法,部署控制节点。之后,再增量部署计算节点。
增量部署的手册可参考 http://openstack.redhat.com/Adding_a_compute_node

在控制节点上,编辑单节点安装生成的配置文件 packstack-answer-$date-$time.txt

修改 CONFIG_NOVA_COMPUTE_PRIVIF 和 CONFIG_NOVA_NETWORK_PRIVIF 为 em2(第二个网卡)

修改CONFIG_NOVA_COMPUTE_HOSTS为计算节点的em1的ip地址。可以逗号分隔,列出多个计算节点

运行
sudo packstack --answer-file=$youranswerfile

2.2 直接安装多节点
在控制节点,参照单节点安装软件源和packstack后,直接运行
 sudo packstack
慢慢的回答问题就可以了,大部分可以用默认选项。只需要在私有网卡的名字替换为em2,以及提供需要安装nova compute服务的机器ip

3.错误处理

3.1 多节点时nagios安装错误
可以暂时不安装,只是个额外的监控软件,将packstack-answer文件的对应项设置为n。packstack似乎在github上的版本已经修了这个bug

3.2 偶尔安装过程中网络异常
有时候,因为网络问题,会链接不上远程机器,造成安装失败。只需要重新运行packstack就可以。

3.3 cinder中创建volume失败等
删除/var/lib/cinder文件夹,重新运行packstack


希望各位OpenStack玩得愉快

如果有问题,我会尽量恢复,我也是小白啊
Email: traits.zhang at gmail.com

Xianyi

周筱赟揭露红会后遭恐吓杀全家,红会难逃嫌疑!已经报警

【周筱赟语录】文学即垃圾,文人即流氓。粪青则是更低级的流氓。我写的不是文学,我只陈述事实。陈述事实、揭露真相就是正能量!
努力了不一定能改变,但不努力就永远不会改变!
周筱赟曾揭露中石化天价酒事件、卢美美父女中非希望工程事件、重庆国际小姐选美黑幕、中华儿慈会48亿巨款神秘消失、江苏宿迁外籍人士当县长等,起诉铁道部拒绝公开12306订票网站招标详情获立案。
凡被周筱赟揭露者,要么认错、要么停工、要么巨亏、要么撤职、要么双规、要么判刑、要么解散(铁道部),一旦出手,从不失手,树立起了“周筱赟爆料绝对靠谱”的品牌。
本文仅代表我个人立场,与发布网站无关,与本人供职单位无关。

文/周筱赟

周筱赟揭露红会后遭恐吓杀全家,红会难逃嫌疑!已经报警 - 周筱赟 - 落魄书生周筱赟的博客
本人周筱赟,自5月13日率先以确凿证据@中国红十字会社会监督委员会 其实是红会养着的公关部,其经费、场地、人员均由红会提供,却无耻的宣称是独立第三方监督,和红会没有隶属关系。此后,我和媒体陆续揭露16名委员中,有9名委员和红会有直接利益关系,超过一半。

红会社监委就是红会自己邀请了一帮熟人来装门面假装监督的,都是七大姑八大姨,和红会曾经有过合作,当红会法律顾问的、拿红会课题费的,在红会直属单位做理事的、帮红会筹款的,从红会领工资的,红会合作经营单位的……

我揭露红会社监委的黑幕后,有人在天涯私信恐吓我,扬言要杀光我的全家、剥我的皮、抽我的筋、剁我的肉。我根本不视为威胁。如果我被人砍,红会难逃嫌疑!我已到派出所报案,警察蜀黍给我做了笔录。谁在幕后指使恐吓者呢?

有位网友提醒我说“俗话说,断人财路如杀人父母啊,看样子有些人按捺不住了。”说的好,“断人财路如杀人父母”,怪不得这人会这么恨我。

@中国红十字会社会监督委员会 能出来辟谣和你们无关吗?

周筱赟揭露红会后遭恐吓杀全家,红会难逃嫌疑!已经报警 - 周筱赟 - 落魄书生周筱赟的博客

6月9日社监委全体会议,拒绝记者旁听,先说上午开会后说改下午,先说在红会总部开后说改了地点但有纪律不能告诉记者,先说晚7点开发布会后说取消了。把采访的记者忽悠的团团转,有记者因此在红会门口的雨中白白等了三个小时。社监委,你们是在搞地下活动吗?社监委开个会都要偷偷摸摸,还能代表社会监督红会?拒绝媒体,拒绝公开,成天帮红会辟谣,这就是社监委号称的独立第三监督?一个不愿接受社会舆论监督的机构,有能力去监督红会吗?

据央广经济之声《天下财经》报道,“中国红十字会社会监督委员会昨天召开2013年中期会议,决定今后所有社监委委员的监督都采用自愿义务服务形式参与,不再与红会有任何利益关系;并决定本月14日召开媒体见面会。”

“红会社监会决定不再与红会有任何利益关系”,这个“再”字用的真是妙!原来红会社监委此前的监督,都是和红会有利益关系的啊!一不小心说了实话了。

既然承认此前红会社监会与红会有利益关系,那就该回避的回避、该辞职的辞职,还有什么废话呢?可是,这次红会社监委的会议明确表示,“这些委员是敬业的,认真地履行了自己的责任,不会因为质疑而离开社监委。”这就是不要脸!这是在侮辱公众的智商!

中国孕妇另辟蹊径:去塞班岛生"美国娃"渐流行

中新网6月14日电 据美国侨报网编译报道,月子中心在加州遍地开花,中国孕妇赴美生子早已不是新鲜事。但随着美国当局对月子中心严加管控,中国孕妇另辟蹊径,越来越多的人选择去塞班岛生子。
报道援引Islands Business International网站消息指出,这些挺着大肚子的亚洲“游客”多来自中国和韩国,他们不辞辛苦来到塞班岛只有一个目的:为即将出生的孩子赢得美国国籍。
根据美国现行法律,在北马里亚纳群岛出生的婴儿,将自动获得美国国籍。跟美国本土相比,北马里亚纳离亚洲更近,从中国飞往塞班岛的机票更低廉。加之与赴香港或美国本土生子相比,塞班岛生子的收费更低,塞班岛的“生育之旅”吸引着越来越多的中国孕妇。
例如,一个亚洲孕妇只需要花费1万美元,就可以买到赴塞班的机票、租住一间公寓、为孩子支付办理美国护照的费用,并且还可以雇佣一个司机。还有中间人专门为中国孕妇提供翻译服务,保这些特殊“游客”无后顾之忧。
自2009年国土安全局批准中国游客赴塞班免签政策后,中国孕妇到塞班生子的情况日益增多。北马里亚纳群岛政府多次要求联邦政府实行更严格的边境管制以减少“生育之旅”,但尚未见到成效。
赴北马里亚纳群岛旅游的中国游客人数有望增长29%,其中专程赴美生子的人数不得而知。北马里亚纳群岛总督伊诺斯(Eloy S. Inos)称,在北马里亚纳非法滞留的人数显著增加,其中很多人是待产的亚洲孕妇,他们在等孩子的出生证明。
一名在塞班做生意的中国女商人称,只有中国有钱人才能付得起到北马里亚纳群岛生子的费用,他们一般都高价租住公寓,不吝花钱,带来了巨大商机。他们在此住院生产、租房租车、雇用司机和翻译,为当地经济注入活力。(马柯斯)

[翻译] [RabbitMQ+Python入门经典] 兔子和兔子窝

 RabbitMQ作为一个工业级的消息队列服务器,在其客户端手册列表的Python段当中推荐了一篇blog ,作为RabbitMQ+Python的入门手册再合适不过了。不过,正如其标题Rabbit and Warrens (兔子和养兔场)一样,,这篇英文写的相当俏皮,以至于对于我等非英文读者来说不像一般的技术文档那么好懂,所以,翻译一下吧。翻译过了,希望其他人可以少用一些时间。翻译水平有限,不可能像原文一样俏皮,部分地方可能就意译了,希望以容易懂为准。想看看老外的幽默的,推荐去看原文,其实,也不是那么难理解……
  兔子和兔子窝
  当时我们的动机很简单:从生产环境的电子邮件处理流程当中分支出一个特定的离线分析流程。我们开始用的MySQL,将要处理的东西放在表里面,另一个程序从中取。不过很快,这种设计的丑陋之处就显现出来了…… 你想要多个程序从一个队列当中取数据来处理?没问题,我们硬编码程序的个数好了……什么?还要能够允许程序动态地增加和减少的时候动态进行压力分配?
  是的,当年我们想的简单的东西(做一个分支处理)逐渐变成了一个棘手的问题。以前拿着锤子(MySQL)看所有东西都是钉子(表)的年代是多么美好……
  在搜索了一下之后,我们走进了消息队列(message queue)的大门。不不,我们当然知道消息队列是什么,我们可是以做电子邮件程序谋生的。我们实现过各种各样的专业的,高速的内存队列用来做电子邮件处理。我们不知道的是那一大类现成的、通用的消息队列(MQ)服务器——无论是用什么语言写出的,不需要复杂的装配的,可以自然的在网络上的应用程序之间传送数据的一类程序。不用我们自己写?看看再说。
  让大家看看你们的Queue吧……
  过去的4年里,人们写了有好多好多的开源的MQ服务器啊。其中大多数都是某公司例如LiveJournal写出来用来解决特定问题的。它们的确不关心上面跑的是什么类型的消息,不过他们的设计思想通常是和创建者息息相关的(消息的持久化,崩溃恢复等通常不在他们考虑范围内)。不过,有三个专门设计用来做及其灵活的消息队列的程序值得关注:
  Apache ActiveMQ
  ZeroMQ
  RabbitMQ
  Apache ActiveMQ曝光率最高,不过看起来它有些问题,可能会造成丢消息。不可接受,下一个。
  ZeroMQ和RabbitMQ都支持一个开源的消息协议,成为AMQP。AMQP的一个优点是它是一个灵活和开放的协议,以便和另外两个商业化的Message Queue (IBM和Tibco)竞争,很好。不过ZeroMQ不支持消息持久化和崩溃恢复,不太好。剩下的只有RabbitMQ了。如果你不在意消息持久化和崩溃恢复,试试ZeroMQ吧,延迟很低,而且支持灵活的拓扑。
  剩下的只有这个吃胡萝卜的家伙了……

 
  当我读到它是用Erlang写的时候,RabbitMQ震了我一下。Erlang 是爱立信开发的高度并行的语言,用来跑在电话交换机上。是的,那些要求6个9的在线时间的东西。在Erlang当中,充斥着大量轻量进程,它们之间用消息传递来通信。听起来思路和我们用消息队列的思路是一样的,不是么?
  而且,RabbitMQ支持持久化。是的,如果RabbitMQ死掉了,消息并不会丢失,当队列重启,一切都会回来。而且,正如在 DigiTar(注:原文作者的公司)做事情期望的那样,它可以和Python无缝结合 。除此之外,RabbitMQ的文档相当的……恐怖。如果你懂AMQP,这些文档还好,但是有多少人懂AMQP?这些文档就像MySQL的文档假设你已经懂了SQL一样……不过没关系啦。
  好了,废话少说。这里是花了一周时间阅读关于AMQP和关于它如何在RabbitMQ上工作的文档之后的一个总结,还有,怎么在Python当中使用。
  开始吧
  AMQP当中有四个概念非常重要:虚拟主机(virtual host),交换机(exchange),队列(queue)和绑定(binding)。一个虚拟主机持有一组交换机、队列和绑定。为什么需要多个虚拟主机呢?很简单,RabbitMQ当中,用户只能在虚拟主机的粒度进行权限控制。因此,如果需要禁止A组访问B组的交换机/队列/绑定,必须为A和B分别创建一个虚拟主机。每一个RabbitMQ服务器都有一个默认的虚拟主机“/”。如果这就够了,那现在就可以开始了。
  交换机,队列,还有绑定……天哪!
  刚开始我思维的列车就是在这里脱轨的…… 这些鬼东西怎么结合起来的?
  队列(Queues)是你的消息(messages)的终点,可以理解成装消息的容器。消息就一直在里面,直到有客户端(也就是消费者,Consumer)连接到这个队列并且将其取走为止。不过。你可以将一个队列配置成这样的:一旦消息进入这个队列,biu~,它就烟消云散了。这个有点跑题了……
  需要记住的是,队列是由消费者(Consumer)通过程序建立的,不是通过配置文件或者命令行工具。这没什么问题,如果一个消费者试图创建一个已经存在的队列,RabbitMQ就会起来拍拍他的脑袋,笑一笑,然后忽略这个请求。因此你可以将消息队列的配置写在应用程序的代码里面。这个概念不错。
  OK,你已经创建并且连接到了你的队列,你的消费者程序正在百无聊赖的敲着手指等待消息的到来,敲啊,敲啊…… 没有消息。发生了什么?你当然需要先把一个消息放进队列才行。不过要做这个,你需要一个交换机(Exchange)……
  交换机可以理解成具有路由表的路由程序,仅此而已。每个消息都有一个称为路由键(routing key)的属性,就是一个简单的字符串。交换机当中有一系列的绑定(binding),即路由规则(routes),例如,指明具有路由键 “X” 的消息要到名为timbuku的队列当中去。先不讨论这个,我们有点超前了。
  你的消费者程序要负责创建你的交换机们 (复数)。啥?你是说你可以有多个交换机?是的,这个可以有,不过为啥?很简单,每个交换机在自己独立的进程当中执行,因此增加多个交换机就是增加多个进程,可以充分利用服务器上的CPU核以便达到更高的效率。例如,在一个8 核的服务器上,可以创建5个交换机来用5个核,另外3个核留下来做消息处理。类似的,在RabbitMQ的集群当中,你可以用类似的思路来扩展交换机一边获取更高的吞吐量。
  OK,你已经创建了一个交换机。但是他并不知道要把消息送到哪个队列。你需要路由规则,即绑定(binding)。一个绑定就是一个类似这样的规则:将交换机“desert(沙漠)”当中具有路由键“阿里巴巴”的消息送到队列“hideout(山洞)”里面去。换句话说,一个绑定就是一个基于路由键将交换机和队列连接起来的路由规则。例如,具有路由键“audit”的消息需要被送到两个队列,“log-forever”和“alert-the- big-dude”。要做到这个,就需要创建两个绑定,每个都连接一个交换机和一个队列,两者都是由“audit”路由键触发。在这种情况下,交换机会复制一份消息并且把它们分别发送到两个队列当中。交换机不过就是一个由绑定构成的路由表。
  现在复杂的东西来了:交换机有多种类型。他们都是做路由的,不过接受不同类型的绑定。为什么不创建一种交换机来处理所有类型的路由规则呢?因为每种规则用来做匹配分子的CPU开销是不同的。例如,一个“topic”类型的交换机试图将消息的路由键与类似“dogs.* ” 的模式进行匹配。匹配这种末端的通配符比直接将路由键与“dogs ”比较(“direct”类型的交换机)要消耗更多的CPU。如果你不需要“topic”类型的交换机带来的灵活性,你可以通过使用“direct”类型的交换机获取更高的处理效率。那么有哪些类型,他们又是怎么处理的呢?
  Fanout Exchange——不处理路由键。你只需要简单的将队列绑定到交换机上。一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。
  Direct Exchange——处理路由键。需要将一个队列绑定到交换机上,要求该消息与一个特定的路由键完全匹配。这是一个完整的匹配。如果一个队列绑定到该交换机上要求路由键 “dog”,则只有被标记为“dog ”的消息才被转发,不会转发dog.puppy ,也不会转发dog.guard ,只会转发dog 。
  Topic Exchange——将路由键和某模式进行匹配。此时队列需要绑定要一个模式上。符号“#”匹配一个或多个词,符号“*”匹配不多不少一个词。因此“audit.#”能够匹配到“audit.irs.corporate ”,但是“audit.* ” 只会匹配到“audit.irs ”。
 持久化这些小东西们
  你花了大量的时间来创建队列、交换机和绑定,然后,砰~服务器程序挂了。你的队列、交换机和绑定怎么样了?还有,放在队列里面但是尚未处理的消息们呢?
  放松~如果你是用默认参数构造的这一切的话,那么,他们,都,biu~,灰飞烟灭了。是的,RabbitMQ重启之后会干净的像个新生儿。你必须重做所有的一切,亡羊补牢,如何避免将来再度发生此类杯具?
  队列和交换机有一个创建时候指定的标志durable,直译叫做坚固的。durable的唯一含义就是具有这个标志的队列和交换机会在重启之后重新建立,它不表示说在队列当中的消息会在重启后恢复。那么如何才能做到不只是队列和交换机,还有消息都是持久的呢?
  但是首先一个问题是,你真的需要消息是持久的吗?对于一个需要在重启之后回复的消息来说,它需要被写入到磁盘上,而即使是最简单的磁盘操作也是要消耗时间的。如果和消息的内容相比,你更看重的是消息处理的速度,那么不要使用持久化的消息。不过对于我们@DigiTar来说,持久化很重要。
  当你将消息发布到交换机的时候,可以指定一个标志“Delivery Mode”(投递模式)。根据你使用的AMQP的库不同,指定这个标志的方法可能不太一样(我们后面会讨论如何用Python搞定)。简单的说,就是将 Delivery Mode设置成2,也就是持久的(persistent)即可。一般的AMQP库都是将Delivery Mode设置成1,也就是非持久的。所以要持久化消息的步骤如下:
  •   将交换机设成 durable。
  •   将队列设成 durable。
  •   将消息的 Delivery Mode 设置成2 。
  就这样,不是很复杂,起码没有造火箭复杂,不过也有可能犯点小错误。
  下面还要罗嗦一个东西……绑定(Bindings)怎么办?我们无法在创建绑定的时候设置成durable。没问题,如果你绑定了一个 durable的队列和一个durable的交换机,RabbitMQ会自动保留这个绑定。类似的,如果删除了某个队列或交换机(无论是不是 durable),依赖它的绑定都会自动删除。
  注意两点:
  RabbitMQ不允许你绑定一个非坚固(non-durable)的交换机和一个durable的队列。反之亦然。要想成功必须队列和交换机都是durable的。 一旦创建了队列和交换机,就不能修改其标志了。例如,如果创建了一个non-durable的队列,然后想把它改变成durable的,唯一的办法就是删除这个队列然后重现创建。因此,最好仔细检查创建的标志。
  开始喂蛇了~【译注】说喂蛇是因为Python的图标是条蛇。
  AMQP的一个空白地带是如何在Python当中使用。对于其他语言有一大坨材料。
以下是引用片段:
Java – http://www.rabbitmq.com/java-client.html  
.NET – http://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.5.0/rabbitmq-dotnet-client-1.5.0-user-guide.pdf  
Ruby – http://somic.org/2008/06/24/ruby-amqp-rabbitmq-example/ 
  但是对Python老兄来说,你需要花点时间来挖掘一下。所以我写了这个,这样别的家伙们就不需要经历我这种抓狂的过程了。
  首先,我们需要一个Python的AMQP库。有两个可选:
  •   py-amqplib——通用的AMQP
  •   txAMQP——使用 Twisted 框架的AMQP库,因此允许异步I/O。
  根据你的需求,py-amqplib或者txAMQP都是可以的。因为是基于Twisted的,txAMQP可以保证用异步IO构建超高性能的 AMQP程序。但是Twisted编程本身就是一个很大的主题……因此清晰起见,我们打算用 py-amqplib。更新:请参见 Esteve Fernandez关于txAMQP的使用和代码样例的回复 。
  AMQP支持在一个TCP连接上启用多个MQ通信channel,每个channel都可以被应用作为通信流。每个AMQP程序至少要有一个连接和一个channel。
以下是引用片段:
view plaincopy to clipboardprint? 
<SPAN style="FONT-WEIGHT: bold; COLOR: #ff7700">from</SPAN>    
 amqplib <SPAN style="FONT-WEIGHT: bold; COLOR: #ff7700">import</SPAN>   
 client_0_8 <SPAN style="FONT-WEIGHT: bold; COLOR: #ff7700">as</SPAN>    
 amqp<BR>    
conn = amqp.<SPAN style="COLOR: black">Connection</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
host=<SPAN style="COLOR: #483d8b">"localhost:5672 "</SPAN>    
, userid=<SPAN style="COLOR: #483d8b">"guest"</SPAN>    
,<BR>    
password=<SPAN style="COLOR: #483d8b">"guest"</SPAN>    
, virtual_host=<SPAN style="COLOR: #483d8b">"/"</SPAN>    
, insist=<SPAN style="COLOR: #008000">False</SPAN>    
<SPAN style="COLOR: black">)</SPAN>    
<BR>    
chan = conn.<SPAN style="COLOR: black">channel</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
<SPAN style="COLOR: black">)</SPAN>   
from 
 amqplib import 
 client_0_8 as 
 amqp 
conn = amqp.Connection 

host="localhost:5672 " 
, userid="guest" 

password="guest" 
, virtual_host="/" 
, insist=False 

chan = conn.channel 

)
  每个channel都被分配了一个整数标识,自动由Connection()类的.channel()方法维护。或者,你可以使用.channel(x)来指定channel标识,其中x是你想要使用的channel标识。通常情况下,推荐使用.channel()方法来自动分配 channel标识,以便防止冲突。
  现在我们已经有了一个可以用的连接和channel。现在,我们的代码将分成两个应用,生产者(producer)和消费者(consumer)。我们先创建一个消费者程序,他会创建一个叫做“po_box”的队列和一个叫“sorting_room”的交换机:
以下是引用片段:
view plaincopy to clipboardprint? 
chan.<SPAN style="COLOR: black">queue_declare</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
queue=<SPAN style="COLOR: #483d8b">"po_box"</SPAN>    
, durable=<SPAN style="COLOR: #008000">True</SPAN>    
, exclusive=<SPAN style="COLOR: #008000">False</SPAN>    
, auto_delete=<SPAN style="COLOR: #008000">False</SPAN>    
<SPAN style="COLOR: black">)</SPAN>    
<BR>    
chan.<SPAN style="COLOR: black">exchange_declare</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
exchange=<SPAN style="COLOR: #483d8b">"sorting_room"</SPAN>    
, <SPAN style="COLOR: #008000">type</SPAN>    
=<SPAN style="COLOR: #483d8b">"direct"</SPAN>    
, durable=<SPAN style="COLOR: #008000">True</SPAN>    
, auto_delete=<SPAN style="COLOR: #008000">False</SPAN>    
,<SPAN style="COLOR: black">)</SPAN>   
chan.queue_declare 

queue="po_box" 
, durable=True 
, exclusive=False 
, auto_delete=False 

chan.exchange_declare 

exchange="sorting_room" 
, type 
="direct" 
, durable=True 
, auto_delete=False 
,)
  这段代码干了啥?首先,它创建了一个名叫“po_box ”的队列,它是durable的(重启之后会重新建立),并且最后一个消费者断开的时候不会自动删除(auto_delete=False )。在创建durable的队列(或者交换机)的时候,将auto_delete设置成false是很重要的,否则队列将会在最后一个消费者断开的时候消失,与durable与否无关。如果将durable和auto_delete都设置成True,只有尚有消费者活动的队列可以在RabbitMQ意外崩溃的时候自动恢复。
  (你可以注意到了另一个标志,称为“exclusive”。如果设置成True,只有创建这个队列的消费者程序才允许连接到该队列。这种队列对于这个消费者程序是私有的)。
  还有另一个交换机声明,创建了一个名字叫“sorting_room”的交换机。auto_delete和durable的含义和队列是一样的。但是,.excange_declare() 还有另外一个参数叫做type,用来指定要创建的交换机的类型(如前面列出的): fanout , direct 和 topic .
  到此为止,你已经有了一个可以接收消息的队列和一个可以发送消息的交换机。不过我们需要创建一个绑定,把它们连接起来。
  chan.queue_bind(queue=”po_box”, exchange=”sorting_room”, routing_key=”jason”)这个绑定的过程非常直接。任何送到交换机“sorting_room ”的具有路由键“jason ” 的消息都被路由到名为“po_box ” 的队列。
  现在,你有两种方法从队列当中取出消息。第一个是调用chan.basic_get() ,主动从队列当中拉出下一个消息(如果队列当中没有消息,chan.basic_get()会返回None, 因此下面代码当中print msg.body 会在没有消息的时候崩掉):
以下是引用片段:
view plaincopy to clipboardprint? 
msg = chan.<SPAN style="COLOR: black">basic_get</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
<SPAN style="COLOR: #483d8b">"po_box"</SPAN>    
<SPAN style="COLOR: black">)</SPAN>    
<BR>    
<SPAN style="FONT-WEIGHT: bold; COLOR: #ff7700">print</SPAN>    
 msg.<SPAN style="COLOR: black">body</SPAN>    
<BR>    
chan.<SPAN style="COLOR: black">basic_ack</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
msg.<SPAN style="COLOR: black">delivery_tag</SPAN>    
<SPAN style="COLOR: black">)</SPAN>   
msg = chan.basic_get 

"po_box" 

print 
 msg.body 
chan.basic_ack 

msg.delivery_tag 
)
  但是如果你想要应用程序在消息到达的时候立即得到通知怎么办?这种情况下不能使用chan.basic_get() ,你需要用chan.basic_consume() 注册一个新消息到达的回调。
以下是引用片段:
view plaincopy to clipboardprint? 
<SPAN style="FONT-WEIGHT: bold; COLOR: #ff7700">def</SPAN>    
 recv_callback<SPAN style="COLOR: black">(</SPAN>    
msg<SPAN style="COLOR: black">)</SPAN>    
:<BR>    
    <SPAN style="FONT-WEIGHT: bold; COLOR: #ff7700">print</SPAN>    
 <SPAN style="COLOR: #483d8b">'Received: '</SPAN>    
 + msg.<SPAN style="COLOR: black">body</SPAN>    
<BR>    
chan.<SPAN style="COLOR: black">basic_consume</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
queue=<SPAN style="COLOR: #483d8b">'po_box'</SPAN>    
, no_ack=<SPAN style="COLOR: #008000">True</SPAN>    
,<BR>    
callback=recv_callback, consumer_tag=<SPAN style="COLOR: #483d8b">"testtag"</SPAN>    
<SPAN style="COLOR: black">)</SPAN>    
<BR>    
<SPAN style="FONT-WEIGHT: bold; COLOR: #ff7700">while</SPAN>    
 <SPAN style="COLOR: #008000">True</SPAN>    
:<BR>    
    chan.<SPAN style="COLOR: black">wait</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
<SPAN style="COLOR: black">)</SPAN>    
<BR>    
chan.<SPAN style="COLOR: black">basic_cancel</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
<SPAN style="COLOR: #483d8b">"testtag"</SPAN>    
<SPAN style="COLOR: black">)</SPAN>   
def 
 recv_callback( 
msg) 

    print 
 'Received: ' 
 + msg.body 
chan.basic_consume 

queue='po_box' 
, no_ack=True 

callback=recv_callback, consumer_tag="testtag" 

while 
 True 

    chan.wait 


chan.basic_cancel 

"testtag" 
)
  chan.wait()放在一个无限循环里面,这个函数会等待在队列上,直到下一个消息到达队列。chan.basic_cancel()用来注销该回调函数。参数consumer_tag 当中指定的字符串和chan.basic_consume()注册的一直。在这个例子当中chan.basic_cancel()不会被调用到,因为上面是个无限循环…… 不过你需要知道这个调用,所以我把它放在了代码里。
  需要注意的另一个东西是no_ack参数。这个参数可以传给chan.basic_get() 和chan.basic_consume(),默认是false。当从队列当中取出一个消息的时候,RabbitMQ需要应用显式地回馈说已经获取到了该消息。如果一段时间内不回馈,RabbitMQ 会将该消息重新分配给另外一个绑定在该队列上的消费者。另一种情况是消费者断开连接,但是获取到的消息没有回馈,则RabbitMQ同样重新分配。如果将no_ack 参数设置为true,则py-amqplib会为下一个AMQP请求添加一个no_ack属性,告诉AMQP服务器不需要等待回馈。但是,大多数时候,你也许想要自己手工发送回馈,例如,需要在回馈之前将消息存入数据库。回馈通常是通过调用chan.basic_ack() 方法,使用消息的delivery_tag 属性作为参数。参见chan.basic_get() 的实例代码。
  好了,这就是消费者的全部代码。(下载:amqp_consumer.py )
  不过没有人发送消息的话,要消费者何用?所以需要一个生产者。下面的代码示例表明如何将一个简单消息发送到交换区“sorting_room ”,并且标记为路由键“jason ” :
以下是引用片段:
view plaincopy to clipboardprint? 
msg = amqp.<SPAN style="COLOR: black">Message</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
<SPAN style="COLOR: #483d8b">"Test message!"</SPAN>    
<SPAN style="COLOR: black">)</SPAN>    
<BR>    
msg.<SPAN style="COLOR: black">properties</SPAN>    
<SPAN style="COLOR: black">[</SPAN>    
<SPAN style="COLOR: #483d8b">"delivery_mode"</SPAN>    
<SPAN style="COLOR: black">]</SPAN>    
 = <SPAN style="COLOR: #ff4500">2</SPAN>    
<BR>    
chan.<SPAN style="COLOR: black">basic_publish</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
msg,exchange=<SPAN style="COLOR: #483d8b">"sorting_room"</SPAN>    
,routing_key=<SPAN style="COLOR: #483d8b">"jason"</SPAN>    
<SPAN style="COLOR: black">)</SPAN>   
msg = amqp.Message 

"Test message!" 

msg.properties 

"delivery_mode" 

 = 2 
chan.basic_publish 

msg,exchange="sorting_room" 
,routing_key="jason" 
)
  你也许注意到我们设置消息的delivery_mode 属性为2,因为队列和交换机都设置为durable的,这个设置将保证消息能够持久化,也就是说,当它还没有送达消费者之前如果RabbitMQ重启则它能够被恢复。
  剩下的最后一件事情(生产者和消费者都需要调用的)是关闭channel和连接:
以下是引用片段:
view plaincopy to clipboardprint? 
chan.<SPAN style="COLOR: black">close</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
<SPAN style="COLOR: black">)</SPAN>    
<BR>    
conn.<SPAN style="COLOR: black">close</SPAN>    
<SPAN style="COLOR: black">(</SPAN>    
<SPAN style="COLOR: black">)</SPAN>   
chan.close 


conn.close 

)
  很简单吧。(下载:amqp_publisher.py )
  来真实地跑一下吧……
  现在我们已经写好了生产者和消费者,让他们跑起来吧。假设你的RabbitMQ在localhost上安装并且运行。
  打开一个终端,执行python ./amqp_consumer.py 让消费者运行,并且创建队列、交换机和绑定。
  然后在另一个终端运行python ./amqp_publisher.py “AMQP rocks.” 。如果一切良好,你应该能够在第一个终端看到输出的消息。
  付诸使用吧
  我知道这个教程是非常粗浅的关于AMQP/RabbitMQ和如何使用Python访问的教程。希望这个可以说明所有的概念如何在Python当中被组合起来。如果你发现任何错误,请联系原作者(williamsjj@digitar.com ) 【译注:如果是翻译问题请联系译者】。同时,我很高兴回答我知道的问题。【译注:译者也是一样的】。接下来是,集群化(clustering)!不过我需要先把它弄懂再说。

RabbitMQ持久化的PHP使用示例

      在使用RabbitMQ时候想对消息进行持久化,一开始exchange和queue做了持久化设置setFlags(AMQP_DURABLE),重启rabbitmq-server后发现数据丢了。后来看官方手册里的publish方法里有delivery_mode、priority两个属性,delivery_mode:1非持久化,delivery_mode:2持久化,其中priority是设置消息优先级的可设置0-9级别,但是官方解释好像并未实现,测试中发现并未起作用。即使设置了持久化,也不能完全保证消息不丢失,比如RabbitMQ接受到消息后还没来得及写到磁盘就宕机了,那么就会有小概率的信息丢失。

具体使用示例如下:
<?php
//连接RabbitMQ
$conn_args = array('host' =>'localhost', 'port'=> '5672', 'login' =>'guest', 'password'=> 'guest', 'vhost' => '/');
$conn = new AMQPConnection($conn_args);
$conn->connect();

$channel = new AMQPChannel($conn);
$exchangeName = 'reported';
$queueName = 'request';
$queueKey = 'request_item';

if( isset($_GET['read']) ){ //读队列

$q = new AMQPQueue($channel);
$q->setName($queueName);
$q->setFlags(AMQP_DURABLE);
$messages = '';
try {
$q->bind($exchangeName, $queueKey);
$messages = $q->get(AMQP_AUTOACK); //消息获取
!empty($messages) && $messages = json_decode($messages->getBody(), true);
} catch (Exception $e) {
var_dump($e);
}
var_dump($messages);
$conn->disconnect();

}else{ //写队列

$ex = new AMQPExchange($channel);
$ex->setName($exchangeName);
$ex->setType(AMQP_EX_TYPE_TOPIC);
$ex->setFlags(AMQP_DURABLE); //exchange持久化
$ex->declare();

$q = new AMQPQueue($channel);
$q->setName($queueName);
$q->setFlags(AMQP_DURABLE); //queue持久化
$q->declare();
$q->bind($exchangeName, $queueKey);
$channel->startTransaction();
$message = array('content' => 'test','time' => time());

/**
* 消息持久化,delivery_mode:2持久化、delivery_mode:1非持久化,其中priority是设置消息的优先级,测试中发现并未起作用。
* 消息还有其他属性,请参考http://www.php.net/manual/zh/amqpexchange.publish.php
*/
$result = $ex->publish(json_encode($message), $queueKey, AMQP_NOPARAM, array('delivery_mode'=>2, 'priority'=> 9));
var_dump($result);
$channel->commitTransaction();
$conn->disconnect();
}

?>

【架构】关于RabbitMQ

1      什么是RabbitMQ

RabbitMQ是实现AMQP(高级消息队列协议)的消息中间件的一种,最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然:

 
单向解耦

 
双向解耦(如:RPC
    例如一个日志系统,很容易使用RabbitMQ简化工作量,一个Consumer可以进行消息的正常处理,另一个Consumer负责对消息进行日志记录,只要在程序中指定两个Consumer所监听的queue以相同的方式绑定到同一个exchange即可,剩下的消息分发工作由RabbitMQ完成。
 

使用RabbitMQ server需要:
1. ErLang语言包;
2. RabbitMQ安装包;
RabbitMQ同时提供了java的客户端(一个jar包)。

2      概念和特性

2.1      交换机(exchange):

1. 接收消息,转发消息到绑定的队列。四种类型:direct, topic, headers and fanout
direct:转发消息到routigKey指定的队列
topic:按规则转发消息(最灵活)
headers:(这个还没有接触到)
fanout:转发消息到所有绑定队列
2. 如果没有队列绑定在交换机上,则发送到该交换机上的消息会丢失。
3. 一个交换机可以绑定多个队列,一个队列可以被多个交换机绑定。
4. topic类型交换器通过模式匹配分析消息的routing-key属性。它将routing-keybinding-key的字符串切分成单词。这些单词之间用点隔开。它同样也会识别两个通配符:#匹配0个或者多个单词,*匹配一个单词。例如,binding key*.stock.#匹配routing keyusd.stcokeur.stock.db,但是不匹配stock.nana
还有一些其他的交换器类型,如headerfailoversystem等,现在在当前的RabbitMQ版本中均未实现。
5. 因为交换器是命名实体,声明一个已经存在的交换器,但是试图赋予不同类型是会导致错误。客户端需要删除这个已经存在的交换器,然后重新声明并且赋予新的类型。
6. 交换器的属性:
持久性:如果启用,交换器将会在server重启前都有效。
自动删除:如果启用,那么交换器将会在其绑定的队列都被删除掉之后自动删除掉自身。
惰性:如果没有声明交换器,那么在执行到使用的时候会导致异常,并不会主动声明。

2.2      队列(queue):

1. 队列是RabbitMQ内部对象,存储消息。相同属性的queue可以重复定义。
2. 临时队列。channel.queueDeclare(),有时不需要指定队列的名字,并希望断开连接时删除队列。
3. 队列的属性:
持久性:如果启用,队列将会在server重启前都有效。
自动删除:如果启用,那么队列将会在所有的消费者停止使用之后自动删除掉自身。
惰性:如果没有声明队列,那么在执行到使用的时候会导致异常,并不会主动声明。
排他性:如果启用,队列只能被声明它的消费者使用。
这些性质可以用来创建例如排他和自删除的transient或者私有队列。这种队列将会在所有链接到它的客户端断开连接之后被自动删除掉。它们只是短暂地连接到server,但是可以用于实现例如RPC或者在AMQ上的对等通信。4. RPC的使用是这样的:RPC客户端声明一个回复队列,唯一命名(例如用UUID),并且是自删除和排他的。然后它发送请求给一些交换器,在消息的reply-to字段中包含了之前声明的回复队列的名字。RPC服务器将会回答这些请求,使用消息的reply-to作为routing key(默认绑定器会绑定所有的队列到默认交换器,名称为“amp.交换器类型名”)发送到默认交换器。注意这仅仅是惯例而已,可以根据和RPC服务器的约定,它可以解释消息的任何属性(甚至数据体)来决定回复给谁。

2.3      消息传递:

1. 消息在队列中保存,以轮询的方式将消息发送给监听消息队列的消费者,可以动态的增加消费者以提高消息的处理能力。
2. 为了实现负载均衡,可以在消费者端通知RabbitMQ,一个消息处理完之后才会接受下一个消息。
channel.basic_qos(prefetch_count=1)
注意:要防止如果所有的消费者都在处理中,则队列中的消息会累积的情况。
3. 消息有14个属性,最常用的几种:
deliveryMode:持久化属性
contentType:编码
replyTo:指定一个回调队列
correlationId:消息id
实例代码:
4. 消息生产者可以选择是否在消息被发送到交换器并且还未投递到队列(没有绑定器存在)和/或没有消费者能够立即处理的时候得到通知。通过设置消息的mandatory/immediate属性为真,这些投递保障机制的能力得到了强化。
5. 此外,一个生产者可以设置消息的persistent属性为真。这样一来,server将会尝试将这些消息存储在一个稳定的位置,直到server崩溃。当然,这些消息肯定不会被投递到非持久的队列中。

2.4      高可用性(HA):

1. 消息ACK,通知RabbitMQ消息已被处理,可以从内存删除。如果消费者因宕机或链接失败等原因没有发送ACK(不同于ActiveMQ,在RabbitMQ里,消息没有过期的概念),则RabbitMQ会将消息重新发送给其他监听在队列的下一个消费者。
channel.basicConsume(queuename, noAck=false, consumer);
2. 消息和队列的持久化。定义队列时可以指定队列的持久化属性(问:持久化队列如何删除?)
channel.queueDeclare(queuename, durable=true, false, false, null);
发送消息时可以指定消息持久化属性:
channel.basicPublish(exchangeName, routingKey,
            MessageProperties.PERSISTENT_TEXT_PLAIN,
            message.getBytes());
这样,即使RabbitMQ服务器重启,也不会丢失队列和消息。
3. publisher confirms
4. master/slave机制,配合Mirrored Queue,这种情况下,publisher会正常发送消息和接收消息的confirm,但对于subscriber来说,需要接收Consumer Cancellation Notifications来得到主节点失败的通知,然后re-consume from the queue,此时要求client有处理重复消息的能力。注意:如果queue在一个新加入的节点上增加了一个slave,此时slave上没有此前queue的信息(目前还没有同步机制)。
(通过命令行或管理插件可以查看哪个slave是同步的:
rabbitmqctl list_queues name slave_pids synchronised_slave_pids
    当一个slave重新加入mirrored-queue时,如果queuedurable的,则会被清空。

2.5      集群(cluster):

1. 不支持跨网段(如需支持,需要shovelfederation插件)
2. 可以随意的动态增加或减少、启动或停止节点,允许节点故障
3. 集群分为RAM节点和DISK节点,一个集群最好至少有一个DISK节点保存集群的状态。
4. 集群的配置可以通过命令行,也可以通过配置文件,命令行优先。

3      使用

3.1      简易使用流程
 

3.2      RabbitMQOpenStack中的使用

 


    Openstack中,组件之间对RabbitMQ使用基本都是“Remote Procedure Calls”的方式。每一个Nova服务(比如计算服务、存储服务等)初始化时会创建两个队列,一个名为“NODE-TYPE.NODE-ID”,另一个名为“NODE-TYPE”,NODE-TYPE是指服务的类型,NODE-ID指节点名称。
    从抽象层面上讲,RabbitMQ的组件的使用类似于下图所示:
每个服务会绑定两个队列到同一个topic类型的exchange,从不同的队列中接收不同类型的消息。消息的发送者如果关心消息的返回值,则会监听另一个队列,该队列绑定在一个direct类型的exchange。接受者收到消息并处理后,会将消息的返回发送到此exchange
Openstack中,如果不关心消息返回,消息的流程图如下:

 
    如果关心消息返回值,流程图如下:


 

3.3      为什么要使用RabbitMQ

曾经有过一个人做过一个测试(http://www.cnblogs.com/amityat/archive/2011/08/31/2160293.html),发送1百万个并发消息,对性能有很高的需求,于是作者对比了RabbitMQMSMQActiveMQZeroMQueue,整个过程共产生1百万条1K的消息。测试的执行是在一个Windows Vista上进行的,测试结果如下:

    虽然ZeroMQ性能较高,但这个产品不提供消息持久化,需要自己实现审计和数据恢复,因此在易用性和HA上不是令人满意,通过测试结果可以看到,RabbitMQ的性能确实不错。
    我在本机也做了一些测试,但我的测试是基于组件的原生配置,没有做任何的配置优化,因此总觉的不靠谱。我只测试了RabbitMQActiveMQ两款产品,虽然网上都说ActiveMQ性能不如前者,但平心而论,ActiveMQ提供了很多配置,存在很大的调优空间,也许修改一个配置参数就会使组件的性能有一个质的飞跃。

我的浏览器兼容实践

目标:兼容IE6 IE7 Firefox;
        当我和我们的美工开始着手进行网站的浏览器兼容工作的时候,有一个明显的感觉:以前我们被IE惯坏了,由奢入俭难,改变坏毛病是不容易啊;如何现存系统进行浏览器兼容呢?以后的工作中怎么进行浏览器兼容呢?
       开始我用事先想好的一套:用搜索引擎搜索出来各种浏览器兼容的方案,比如:两个层重叠了怎么处理,表格撑开了怎么处理...然后整理出来一份文档放在团队共享。我的确就是这样做的,从早晨开始做到晚上8点,也有“成就”:40多页的文档;可就在临睡觉之前我意识到这是一件“勤劳而愚蠢的事情”。
        原因:页面对了,不考虑浏览器对HTML元素渲染的差别,那么在所有浏览器中显示基本一样;但是页面错了那就花样百出了。比如同样是一个DIV的闭合标签没有写,在火狐里面,有可能一个页面是表现成层重叠,另一个页面表现为这个层跑到页面最下面了。而在IE中这有可能是完全正常的。我做的工作是什么呢?是在穷举出所有的错误情况并找出解决方案,岂不荒唐
       无论整体思路有没有,手头的问题还是要解决的,在解决问题的过程中,一切豁然开朗;
       那是一个系统中最重要的页面,也是在火狐中最混乱的页面:有一部分功能被覆盖,左右两个DIV一上一下,随着页面内容展开footer不自动往下延伸...经过两天的奋战页面正常了,回头看看我们到底做了什么呢?我们实质上是在无限的靠近W3C标准。
        于是,一个个问题迎刃而解:对于现存的页面,用W3C验证工具,从Fatal级别的错误开始解决;以后的开发中页面的浏览器兼容怎么保证----只要遵循W3C标准做就可以了。这里有一个技巧,不是所有浏览器都完美的支持W3C的所有标准,同时W3C标准在有些地方的确吹毛求疵,所以我们大体上遵循W3C的标准,但是页面上并不添加W3C的声明。
        做浏览器兼容开始做的比较累,后来找到一个工具,一开始我只告诉她,没想到一传十十传百,竟成了众人皆知的秘密(玩笑了)----FireBug+Yslow;http://com3.devnet.re3.yahoo.com/yslow/  前者是调试脚本和页面样式的绝佳工具,可以在脚本中设置断点。后者则是根据High performance Web sites提到的14条原则做成了一个工具:
YSlow analyzes web pages and tells you why they're slow based on the rules for high performance web sites. YSlow is a Firefox add-on integrated with the popular Firebug web development tool.
做浏览器兼容时遇到的一些脚本兼容问题:
1.Firefox里面不能操作剪贴板(有曲线救国之策么?)
2.IE里面的event.srcElement在FireFox里面是event.target注意兼顾
3.IE里面的 div.innerText;在FireFox里面是div.textContent;
4.文本超长自动省略: style="overflow: hidden; text-overflow: ellipsis;  width: 260px; white-space: nowrap; cursor: pointer; "注意要删掉页面上的W3c标准!
5.如果真的需要为不同的浏览器做区分那么可以
if (window.navigator.userAgent.indexOf("MSIE")>=1)  //IE6 7
if (window.navigator.userAgent.indexOf("Firefox")>=1)  //FF
6.CSS方面也有一些做浏览器兼容的策略 就不越俎代庖了 可以搜索一下

总结一下:
1.  遵循标准但是不把W3C标准声明添加到页面上,这种做法在时间和页面质量之间做了一个折衷
2.对现存的页面做美化 可以从验证W3C开始 这是一个好的切入点
3.浏览器兼容在新页面的开发时就要考虑,完成之后再做成本是完全不一样的 类似于代码重构
4.有些javascript编写也要关注一些敏感的浏览器兼容问题
5.多说一句:页面开发人员学一点CSS的知识对于解决浏览器兼容的问题是很有帮助的

DNS服务器如何被利用来进行攻击

目前对DNS服务器的攻击基本都是假冒IP的潮水攻击。普通情况下,DNS查询是UDP协议,无需像TCP那样的三次握手过程,因此DNS服务器无法识别请求包里的源IP是否真实有效。如果大量的假冒IP进行攻击,就造成名字服务器要么计算资源耗尽,要么带宽耗尽,从而拒绝服务。
由于客户端是假冒IP,因此DNS服务器端的安全策略,例如DNS RRL、或者iptables,都作用不大。唯一有效的方式是从源头制止这种攻击,即各ISP、IDC都要严格执行BCP 38,拒绝非本网络的源IP对外发起请求。
除了直接攻击DNS服务器外,还有一种情况是DNS服务器被利用来攻击别人。如果攻击者想攻击某个站点,他假冒这个站点的IP,对互联网上开放的DNS服务器发起查询,DNS服务器会将查询应答包返回给站点IP。由于开放的DNS服务器数量众多(比如运营商的递归服务器、各个公司自己的权威服务器),假如攻击者同时往1000台DNS服务器发起查询,那么1000个服务器的返回包到达后,巨大的流量直接把目标站点干掉。示意图请见:
这种情况下,可以利用RRL、iptables来保护自己的名字服务器免被攻击者利用来攻击别人。
一个简单的iptables规则可以如下:
iptables -I INPUT -p udp –dport 53 -m state –state NEW  -m recent –set
iptables -I INPUT -p udp –dport 53 -m state –state NEW  -m recent –update –seconds 60 –hitcount 1000 -j DROP
上述规则的作用是,如果在1分钟内对DNS的查询频率超过1000次,就拦截掉该源IP。