使用阿里云镜像服务器

最近本机访问 163.comCentOS 镜像比较不稳定,体现在 ping 很低,但是 HTTP 连接很慢,yumfastestmirror 也不太理想,所以一般都禁用之。

发现阿里云的镜像服务器,立马换了,便秘立刻就通了。

yum 镜像

官方源镜像

1
2
3
4
5
6
# 禁用 fastestmirror 插件
sed -i.backup 's/^enabled=1/enabled=0/' /etc/yum/pluginconf.d/fastestmirror.conf
# 备份
mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
# 使用阿里云镜像
wget -O /etc/yum.repos.d/CentOS-Base-aliyun.repo http://mirrors.aliyun.com/repo/Centos-6.repo

EPEL 镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 安装 EPEL 源
wget http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
wget http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
rpm -Uvh remi-release-6*.rpm epel-release-6*.rpm
# 使用阿里云镜像
if [[ ! -f /etc/yum.repos.d/epel.repo.backup ]]; then
mv /etc/yum.repos.d/epel.repo /etc/yum.repos.d/epel.repo.backup 2>/dev/null || :
fi

if [[ ! -f /etc/yum.repos.d/epel-testing.repo.backup ]]; then
mv /etc/yum.repos.d/epel-testing.repo /etc/yum.repos.d/epel-testing.repo.backup 2>/dev/null || :
fi

wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-6.repo

PyPi 镜像

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mkdir -p ~/.pip
touch ~/.pip/pip.conf

sed -i.backup -r \
's/^index-url\s*=\s*.+$/index-url = http:\/\/mirrors.aliyun.com\/pypi\/simple\//' \
~/.pip/pip.conf

# If file not changed, write contents back to pip.conf
diff "~/.pip/pip.conf" "~/.pip/pip.conf.backup" &> /dev/null

if [ $? -eq 0 ]; then
cat > ~/.pip/pip.conf <<EOF
[global]
index-url = http://mirrors.aliyun.com/pypi/simple/
EOF
fi

RubyGems 镜像

1
2
gem source -r https://rubygems.org/
gem source -a http://mirrors.aliyun.com/rubygems/

使用 FPM 创建 Python 的 RPM 包

生成 RPM 包太麻烦了,最近知道了一个名为 FPM 的神器,在此记录一下。

安装 FPM

NOTE: 测试系统为 RedHat 系的 CentOS 6.3,编译 Python 2.7.6 的 RPM 包。

安装 Ruby

由于 FPM 使用 Ruby 写成,因此系统中需要安装 Ruby 的运行环境(这里 gem 的源改为了 taobao 的镜像):

1
2
3
4
5
6
# Install ruby dependencies
yum -y install ruby rubygems ruby-devel
# Use taobao repo for ruby gems
gem sources -a http://ruby.taobao.org/
# Remove origin repo from ruby gems
gem sources --remove http://rubygems.org/

通过 Gem 安装 FPM

Ruby 安装完成后,就可以使用 gem 安装 FPM 了:

1
2
# Install fpm
gem install fpm

Good.

设置编译环境

在编译 Python 之前,需要安装开发工具和库:

1
2
3
4
5
6
7
8
# Install EPEL repository
wget http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
wget http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
rpm -Uvh remi-release-6*.rpm epel-release-6*.rpm

# Install build toolchain
yum -y groupinstall "Development tools"
yum -y install openssl-devel readline-devel bzip2-devel sqlite-devel zlib-devel ncurses-devel db4-devel expat-devel

编译并创建 Python 的 RPM 包

FPM 的使用比较简单,可以参考 FPM使用说明

首先,下载 Python-2.7.6 的源码包并解压:

1
2
curl --progress-bar -LO http://mirrors.sohu.com/python/2.7.6/Python-2.7.6.tgz
tar xf Python-2.7.6.tgz

第二步,编译 Python-2.7.6

1
2
3
4
5
6
7
8
9
10
11
12
cd Python-2.7.6.tgz

# Python2.7编译安装后会安装到这个目录,方便打包
export INTERMEDIATE_INSTALL_DIR=/tmp/installdir-Python-2.7.6
# RPM包安装后Python2.7的目录
export INSTALL_DIR=/usr/local

LDFLAGS="-Wl,-rpath=${INSTALL_DIR}/lib ${LDFLAGS}" \
./configure --prefix=${INSTALL_DIR} --enable-unicode=ucs4 \
--enable-shared --enable-ipv6
make
make install DESTDIR=${INTERMEDIATE_INSTALL_DIR}

第三步,使用 FPM 创建 RPM 包:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 注意之前导出 INTERMEDIATE_INSTALL_DIR 和 INSTALL_DIR 这两个环境变量,这里还要使用
fpm -s dir -t -f rpm -n python27 -v '2.7.6' \
-d 'openssl' \
-d 'bzip2' \
-d 'zlib' \
-d 'expat' \
-d 'db4' \
-d 'sqlite' \
-d 'ncurses' \
-d 'readline' \
--directories=${INSTALL_DIR}/lib/python2.7/ \
--directories=${INSTALL_DIR}/include/python2.7/ \
-C ${INTERMEDIATE_INSTALL_DIR} .

Bonus Time

包含以下内容:

  • 自动下载、编译、打包 Python RPM 包的 Makefile;
  • 自动下载、编译、打包 virtualenv、pip、supervisor 等 Python 库和工具的 RPM 包。

GitHub 项目地址:python27-rpm

RabbitMQ笔记(三): Pika客户端(Python)发送大尺寸消息的问题

问题描述

这个问题存在很久了,现象就是使用 Pika 库的客户端在发送大尺寸消息后,RabbitMQ 没有收到,Consumer 那里会认为消息已丢失。

NOTE: 即使在本文写时的最新版(v0.9.13)依然存在这个问题。

分析

因为网络状态很好,所以没有考虑网络的错误,直接考虑Pika库的问题。

那么第一步就是把 Pika 的代码拿来读一遍,主要看它是怎么发送消息的,经过一番探索,找到了 pika/adapters/base_connection.py 这个文件,来看看里面的内容:

pika/adapters/base_connection.pylink
1
2
3
4
5
6
7
8
9
10
11
12
def _handle_write(self):
"""Handle any outbound buffer writes that need to take place."""
total_written = 0
if self.outbound_buffer:
try:
bytes_written = self.socket.send(self.outbound_buffer.popleft())
except socket.timeout:
raise
except socket.error, error:
return self._handle_error(error)
total_written += bytes_written
return total_written

看出问题了吗?

来看看socket.send的文档吧:

Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data. For further information on this concept, consult the Socket Programming HOWTO.

问题就是,没有检查 socket.send 的返回值,由于 socket.send 返回实际发送的直接个数,可能会小于期望(在这里是 self.outbound_buffer.popleft())。

由于 socket.send 可能没有将数据发送完成就返回了,造成了消息丢失的情况。

本来想自己修的,看了一眼 Pikamaster 分支,已经修正了(一次保证将一个帧(Frame)发送完成):

pika/adapters/base_connection.pylink
1
2
3
4
5
6
7
8
9
10
11
12
13
def _handle_write(self):
"""Handle any outbound buffer writes that need to take place."""
bytes_written = 0
if self.outbound_buffer:
frame = self.outbound_buffer.popleft()
try:
self.socket.sendall(frame)
bytes_written = len(frame)
except socket.timeout:
raise
except socket.error as error:
return self._handle_error(error)
return bytes_written

呵呵……

解决

  1. 自行打补丁;
  2. 使用 git 上的 Pika: pip install git+https://github.com/pika/pika.git
  3. 不用 Pika, 换其它的,比如 Kombu

我现在不用 Pika 了,用 Kombu

PS: Pika 的发布也太不积极了,都怎么久了还不发新版本。

解决GitHub Pages的302转向问题

起因

NOTE: 如果给GitHub Pages使用的是子域名,按照GitHub Pages文档配置,不会出现该问题。

由于我在GitHub Pages上的搭的博客使用了theo.im这个根域名(Apex Domain),在按照 GitHub Pages文档上提供的信息,对theo.im设置了A记录,但是通过执行命令:

1
curl -I http://theo.im/sitemap.xml

发现得到的是302转向,不符合Sitemap协议的要求:

A successful request will return an HTTP 200 response code; if you receive a different response, you should resubmit your request. The HTTP 200 response code only indicates that the search engine has received your Sitemap, not that the Sitemap itself or the URLs contained in it were valid.

解决

通过搜索,我发现需要使用ALIAS记录解析根域名,但是由于我之前使用的是DNSPod 的服务,不支持ALIAS记录,因此决定换加DNS域名解析商。

经过一番搜寻,找到了两家支持ALIAS记录的DNS域名解析商:

  1. DNSimple: 全收费服务
  2. PointDNS: 有免费的开发者账户

本着能用收费不用免费的原则,因此我选择了 DNSimple 来解析我的域名 (゜o゜(☆○=(-_-)

OK,万文不如一图:

最后,运行 dig theo.im +nostats +nocomments +nocmd 检查DNS是否生效:

1
2
3
4
; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.23.rc1.el6_5.1 <<>> theo.im +nostats +nocomments +nocmd
;; global options: +cmd
;theo.im. IN A
theo.im. 3600 IN A 199.27.79.133

RabbitMQ笔记(二): 并发连接数

概要

对于服务器来说,并发连接数一直是一个需要考量的问题,因此在这里做一个简单的测试。

测试

在测试前,需要准备一个客户端环境,本文的环境是:

  • CentOS 6.3
  • Python 2.7.6

NOTE: 下文中的 IP 地址 10.10.0.70 为 RabbitMQ 服务器的 IP 地址

测试: 耗尽 rabbitmq 的 socket descriptors

首先, 编写一个脚本:

exhaust_socket_descriptors.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import logging

import config

logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
LOG = logging.getLogger(os.path.basename(__file__))


def main():
connections = []
while True:
LOG.info("Try to establish a new rabbit connection...")
connection = config.get_connection()
connection.connect()
connections.append(connection)
LOG.info("[%d] connections", len(connections))


if __name__ == '__main__':
main()

客户端执行exhaust_socket_descriptors.py, 会看到如下输出:

1
2
3
4
5
6
7
8
9
10
...前略...
2014-05-13 10:45:17,477 - exhaust_socket_descriptors.py - INFO - Try to establish a new rabbit connection...
2014-05-13 10:45:17,489 - exhaust_socket_descriptors.py - INFO - [826] connections
2014-05-13 10:45:17,489 - exhaust_socket_descriptors.py - INFO - Try to establish a new rabbit connection...
2014-05-13 10:45:17,502 - exhaust_socket_descriptors.py - INFO - [827] connections
2014-05-13 10:45:17,502 - exhaust_socket_descriptors.py - INFO - Try to establish a new rabbit connection...
2014-05-13 10:45:17,516 - exhaust_socket_descriptors.py - INFO - [828] connections
2014-05-13 10:45:17,516 - exhaust_socket_descriptors.py - INFO - Try to establish a new rabbit connection...
2014-05-13 10:45:17,528 - exhaust_socket_descriptors.py - INFO - [829] connections
2014-05-13 10:45:17,529 - exhaust_socket_descriptors.py - INFO - Try to establish a new rabbit connection...

看到似乎是连接耗尽了,在服务器端上确认一下:

1
2
3
[root@localhost vagrant]# rabbitmqctl status | grep sockets_
{sockets_limit,829},
{sockets_used,829}]},

sockets_usedsockets_limit 相同,可以确认 socket descriptors 耗尽了,因此客户端成功新连接,卡在 Try to establish a new rabbit connection... 处。但是问题在于,为什么不超时?

客户端中检查一下TCP连接个数:

1
2
[root@localhost ~]# netstat -atn | grep 10.10.0.70:5672 | wc -l
830

可以得出,socket连接数是830,大于服务器端的sockets_limit

继续,在客户端中,检查一下TCP连接状态:

1
2
[root@localhost ~]# netstat -atn | grep 10.10.0.70:5672  | awk '{print $6}' | uniq
ESTABLISHED

全部都是ESTABLISHED状态,因此服务器仅仅是阻塞了新连接,而不是拒绝新连接。这样,就产生了一个问题:

如果是使用 HAProxy 等工具搭建的集群,由于服务器依然会接受新连接,因此 HAProxy 不会认为节点已Down,最终会导致整个集群卡住

Bug

当 RabbitMQ 的 sockets_used 达到 sockets_limits 时候(连接数耗尽时),最终即使是 Consumer 也会全部阻塞,只有在 sockets_used < sockets_limit 时(释放部分连接后),才会恢复。

参见以下连接获取更多信息: http://markmail.org/message/r4yhvqc7vgfljpao

Workaround: 增加File/Socket Descriptors个数

通过官方(包括EPEL)的 deb, rpm 包安装的启动脚本,都会在rabbitmq-server 启动前 source 一次 /etc/default/rabbitmq-server 文件,因此我们可以在该文件中增加最大允许的 File/Socket Descriptors 个数。

1
echo 'ulimit -n 102400' > /etc/default/rabbitmq-server

最后,重启 RabbitMQ 服务器以生效该设置:

1
service rabbitmq-server restart

Updated

May 17, 2014

  • 更新 Bug 说明 (RabbitMQ bug26180)