Celery (芹菜)是基于Python开发的分布式任务队列。它支持使用任务队列的方式在分布的机器/进程/线程上执行任务调度。

架构设计

Celery的架构由三部分组成,消息中间件(message broker),任务执行单元(worker)和任务执行结果存储(task result store)组成。

  • 消息中间件

    Celery本身不提供消息服务,但是可以方便的和第三方提供的消息中间件集成。包括,RabbitMQRedisMongoDB (experimental), Amazon SQS (experimental),CouchDB (experimental), SQLAlchemy (experimental),Django ORM (experimental), IronMQ

  • 任务执行单元

    Worker是Celery提供的任务执行的单元,worker并发的运行在分布式的系统节点中。

  • 任务结果存储

    Task result store用来存储Worker执行的任务的结果,Celery支持以不同方式存储任务的结果,包括AMQP, Redis,memcached, MongoDB,SQLAlchemy, Django ORM,Apache Cassandra, IronCache

另外, Celery还支持不同的并发和序列化的手段

  • 并发

    PreforkEventletgevent, threads/single threaded

  • 序列化

    picklejsonyamlmsgpackzlibbzip2 compression, Cryptographic message signing 等等

安装和运行

Celery的安装过程略为复杂,下面的安装过程是基于我的AWS EC2的Linux版本的安装过程,不同的系统安装过程可能会有差异。大家可以参考官方文档。

首先我选择RabbitMQ作为消息中间件,所以要先安装RabbitMQ。作为安装准备,先更新YUM。

sudo yum -y update

RabbitMQ是基于erlang的,所以先安装erlang

# Add and enable relevant application repositories:
# Note: We are also enabling third party remi package repositories.
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
sudo rpm -Uvh remi-release-6*.rpm epel-release-6*.rpm

# Finally, download and install Erlang:
yum install -y erlang

然后安装RabbitMQ

# Download the latest RabbitMQ package using wget:
wget  
# Add the necessary keys for verification:
rpm --import  
# Install the .RPM package using YUM:
yum install rabbitmq-server-3.2.2-1.noarch.rpm

启动RabbitMQ服务

rabbitmq-server start

RabbitMQ服务已经准备好了,然后安装Celery, 假定你使用pip来管理你的python安装包

pip install Celery

为了测试Celery是否工作,我们运行一个最简单的任务,编写tasks.py

from celery import Celery

app = Celery('tasks', backend='amqp', broker='amqp://guest@localhost//')
app.conf.CELERY_RESULT_BACKEND = 'db+sqlite:///results.sqlite'

@app.task
def add(x, y):
    return x + y

在当前目录运行一个worker,用来执行这个加法的task

celery -A tasks worker --loglevel=info

其中-A参数表示的是Celery App的名字。注意这里我使用的是SQLAlchemy作为结果存储。对应的python包要事先安装好。

worker日志中我们会看到这样的信息

- ** ---------- [config]
- ** ---------- .> app:         tasks:0x1e68d50
- ** ---------- .> transport:   amqp://guest:**@localhost:5672//
- ** ---------- .> results:     db+sqlite:///results.sqlite
- *** --- * --- .> concurrency: 8 (prefork)

其中,我们可以看到worker缺省使用prefork来执行并发,并设置并发数为8

下面的任务执行的客户端代码:

from tasks import add
import time
result = add.delay(4,4)

while not result.ready():
  print "not ready yet"
  time.sleep(5)

print result.get()

用python执行这段客户端代码,在客户端,结果如下

not ready   
8

Work日志显示

[2015-03-12 02:54:07,973: INFO/MainProcess] Received task: tasks.add[34c4210f-1bc5-420f-a421-1500361b914f]
[2015-03-12 02:54:08,006: INFO/MainProcess] Task tasks.add[34c4210f-1bc5-420f-a421-1500361b914f] succeeded in 0.0309705100954s: 8

这里我们可以发现,每一个task有一个唯一的ID,task异步执行在worker上。

这里要注意的是,如果你运行官方文档中的例子,你是无法在客户端得到结果的,这也是我为什么要使用SQLAlchemy来存储任务执行结果的原因。官方的例子使用AMPQ,有可能Worker在打印日志的时候取出了task的运行结果显示在worker日志中,然而AMPQ作为一个消息队列,当消息被取走后,队列中就没有了,于是客户端总是无法得到任务的执行结果。不知道为什么官方文档对这样的错误视而不见。

如果大家想要对Celery做更进一步的了解,请参考官方文档

不管是开源还是闭源,文档都是很重要的。当然理论上说,最好的文档就是代码本身,但是要让所有人都能读懂你的代码这太难了。所以我们要写文档。大部分情况,我们不希望维护一份代码再加上一份文档,这样做很容易造成文档和代码的不一致,程序员最讨厌更新文档了。所以最佳实践就是在程序员代码中加注释,然后通过构建脚本自通生成文档。

对应于Pyhon,有很多可供选择的工具:

  • sphinx 中文版介绍 Sphinx使用 reStructuredText作为标记语言(类似Markdown),可扩展,功能强大。要注意的是何有一个开源的搜索也叫Sphinx,斯芬克斯果然太受欢迎,开源的世界起个名字不容易呀。

  • pdoc 是一个简单易用的命令行工具,可以生成Python的API文档

  • Doxygen 是老牌的文档生成工具,可以针对各种语言生成文档,我们以前在C++的项目中曾经使用过

  • 其他还有诸如 pydoc , pydoctor 等等

下面我就介绍一下如果使用Sphinx为你的python项目快速的构建API 文档。

首先要安装Sphinx,不同的操作系统有不同的安装方式,Sphinx的源代码在这里 , 你也可以自己构建。我推荐使用pip install。(注,如果你安装了Anaconda,Sphinx已经包含在内了)

然后,假定你的python的源代码是在 src 目录下,我们在同一级并行建立一个文档目录 doc (你当然可以根据自己的项目需要,确定目录命名和结构)。

在doc目录下运行 

sphinx-quickstart

sphinx会提示你的项目的一些设置,生成项目的配置文件,这里给出一些推荐的配置:

> Root path for the documentation [.]:
<ENTER>
> Separate source and build directories (y/N) [n]:
y
> Name prefix for templates and static dir [_]:
<ENTER>
> Project name:
an_example_pypi_project
> Author name(s):
Andrew Carter
> Project version:
0.0.1
> Project release [0.0.1]:
<ENTER>
> Source file suffix [.rst]:
<ENTER>
> Name of your master document (without suffix) [index]:
<ENTER>
> autodoc: automatically insert docstrings from modules (y/N) [n]:
y
> doctest: automatically test code snippets in doctest blocks (y/N) [n]:
n
> intersphinx: link between Sphinx documentation of different projects (y/N) [n]:
y
> todo: write “todo” entries that can be shown or hidden on build (y/N) [n]:
n
> coverage: checks for documentation coverage (y/N) [n]:
n
> pngmath: include math, rendered as PNG images (y/N) [n]:
n
> jsmath: include math, rendered in the browser by JSMath (y/N) [n]:
n
> ifconfig: conditional inclusion of content based on config values (y/N) [n]:
y
> Create Makefile? (Y/n) [y]:
n
> Create Windows command file? (Y/n) [y]:
n

运行完毕,sphinx会生成项目的配置文档conf.py还有源文件(后缀为rst)

下一步要为捏python源文件生成sphinx的源文件,用来生成API文档,需要运行

sphinx-apidoc [options] -o outputdir packagedir [pathnames]

其中outputdir是doc目录,packagedir是src目录,也就是你的python代码包所在的目录

运行好后,会对每一个Python包生成一个rst文件,你可以编辑该文件来修改生成文档的细节,一般情况下不用改。

好了,准备工作做好了以后,就可以生成API文档了。在运行文档生成脚本之前,要确保你的Python源代码所在的包在系统路径中是可以找到的,需要修改conf.py。因为在生成文档是需要运行你的python代码,要保证code运行不出错。

sys.path.insert(0, os.path.abspath('../src'))

在doc目录下运行脚本

sphinx-build -b html . ./ouput

在output目录会生成HTML格式的API文档。(也可以选其他文档格式)

Sphinx还有一个automsummay的扩展,可能能简化以上的过程,等我试一试在更新结果。


在大数据时代,数据可视化是一个非常热门的话题。各个BI的厂商无不在数据可视化领域里投入大量的精力。Tableau凭借其强大的数据可视化的功能成为硅谷炙手可热的上市公司。Tableau的数据可视化的产品,其理论基础其实是《The Grammar of Graphic》,该书提出了对信息可视化的图表的语法抽象体系,数据的探索和分析可以由图像的语法来驱动,而非有固定的图表类型来驱动,使得数据的探索过程变得友好而有趣。

然而对于The Grammar of Graphic的理论的实践,并非Tableau独占,ggplot作为R语言上得一个图形库,其理论基础也是这本书。(注,笔者曾就职的BI巨头,主要职责也是数据可视化,我们曾经和加拿大团队研发过类似的产品,基于HTML5和D3,可惜未能推向市场)

现在越来越多的人开始使用python来做数据分析,IPython Notebook尤其令人喜爱,它的实时交互把脚本语言的优势发挥到极致。那么怎样才能在IPython Notebook中使用ggplot呢?我这里跟大家分享三种不同的方式供大家选择。

RPy2

第一种方式是使用rpy2, rpy2是对rpy的改写和重新设计,旨在提供Python用户在python中使用R的API。

rpy2提供了对R语言的对象和方法的基本封装,当然也包括可视化的图库这一块。

下面就是一段运行ggplot的R程序使用rpy2在python中运行的例子:

from rpy2 import robjects
from rpy2.robjects import Formula, Environment
from rpy2.robjects.vectors import IntVector, FloatVector
from rpy2.robjects.lib import grid
from rpy2.robjects.packages import importr, data
import rpy2.robjects.lib.ggplot2 as ggplot2

# The R 'print' function
rprint = robjects.globalenv.get("print")
stats = importr('stats')
grdevices = importr('grDevices')
base = importr('base')
datasets = importr('datasets')

mtcars = data(datasets).fetch('mtcars')['mtcars']

pp = ggplot2.ggplot(mtcars) + \
     ggplot2.aes_string(x='wt', y='mpg', col='factor(cyl)') + \
     ggplot2.geom_point() + \
     ggplot2.geom_smooth(ggplot2.aes_string(group = 'cyl'),
                         method = 'lm')
pp.plot()

以上程序在IPython Notebook中运行会有缺陷,会弹出一个新的窗口显示图,而且该python进程会阻塞在那里。我们希望图表能内嵌在IPython Notebook的页面中,为了解决该问题,我们引入如下代码:

%matplotlib inline

import uuid
from rpy2.robjects.packages import importr 
from IPython.core.display import Image

grdevices = importr('grDevices')
def ggplot_notebook(gg, width = 800, height = 600):
    fn = '{uuid}.png'.format(uuid = uuid.uuid4())
    grdevices.png(fn, width = width, height = height)
    gg.plot()
    grdevices.dev_off()
    return Image(filename=fn)

运行上述代码后,我们把ggplot的调用pp.plot()改为调用ggplot_notebook(pp, height=300)就能成功嵌入显示ggplot的结果。

RMagic

另一种方式是使用rmagic,rmagicy实际上依赖于rpy2。它的使用方式更像是直接在使用R

%load_ext rmagic
library(ggplot2)
dat <- data.frame(x = rnorm(10), y = rnorm(10), 
                  lab = sample(c('A', 'B'), 10, replace = TRUE))
x <- ggplot(dat, aes(x = x, y = y, color = lab)) + geom_point()
print(x)

运行结果如下

ggplot for python

ggplot是一个python的库,基本上是对R语言ggplot的功能移植到Python上。

运行安装脚本

pip install ggplot

安装成功后,可以试一下这个例子

%matplotlib inline
import pandas as pd
from ggplot import *
meat_lng = pd.melt(meat[['date', 'beef', 'pork', 'broilers']], id_vars='date')
ggplot(aes(x='date', y='value', colour='variable'), data=meat_lng) + \
    geom_point() + \
    stat_smooth(color='red')

结果如下:


总结

本文提供了三种不同的方式在Python(IPython Notebook)中调用ggplot。

rpy2和Rmagic都是一种对R的桥接,所以都需要安装R。不同之处在于rpy2提供Python接口而Rmagic更接近R。

ggplot Python库是ggplot的Python移植,所以无需安装R,部署起来更为简单,但功能上也许和R的ggplot还有差距。

大家可以根据自己的需要做出选择。

我们的目标是在Mac OS上获取一个静态服务器的内容,通常用wget是一个很好的选择。

wget是一个命令行工具用于从网络服务器来获取内容。但是在Mac OS X(Mountain Lion/ Mavericks / Snow Leopard)上没有提供该工具,但是有curl。

wget VS curl

curl

  • 基于跨平台的库libcurl

  • 支持unix管道

  • 返回错误代码来支持错误处理

  • 只返回单个url的内容,不支持自动取链接的内容

  • 大量协议支持诸如 :FTP, FTPS, HTTP, HTTPS, SCP, SFTP, TFTP, TELNET, DICT, LDAP, LDAPS, FILE, POP3, IMAP, SMTP, RTMP and RTSP

  • 可移植性好

  • 支持不同的SSL/TSL库

  • 支持HTTP认证(HTTP Authentication)

  • 支持双向和多部分提交数据

  • 支持压缩

  • MIT协议

wget

  • 只支持命令行

  • 支持递归的抓取数据,也就是说可以抓取返回内容中的url链接的内容。

  • 非常古老,开发不活跃。

  • 使用HTTP 1.0

  • GNU项目的一部分

  • GPL 协议

总体而言curl比wget要进步许多,可是要获取一个网站的镜像,迭代功能必不可少。只好自己动手,在Mac上构建一个wget。

构建wget

首先确定你已经安装了Xcode和GCC,如果不知道如何安装,可以参考这个链接

然后从gnu下载wget的源码

curl -O http://ftp.gnu.org/gnu/wget/wget-1.15.tar.gz

下载好后,解压缩

tar -xvf wget-1.15.tar.gz

解压缩好后,需要运行配置命令,为编译做准备

cd wget-1.15
./configure --with-ssl=openssl

这里我们选用openssl作为ssl的参数选项。大家一定不会忘记最近发生的openssl的heartbleed漏洞吧 :)

配置好了以后,运行make

make

这里不出意外会跳出一大堆的警告,不要担心,如果你看到如下的内容,你应该编译成功了

... ...
... ...
gcc  -O2 -Wall   -o wget cmpt.o connect.o convert.o cookies.o ftp.o css_.o css-url.o ftp-basic.o ftp-ls.o hash.o host.o html-parse.o html-url.o http.o init.o log.o main.o netrc.o progress.o ptimer.o recur.o res.o retr.o spider.o url.o warc.o utils.o exits.o build_info.o  version.o ftp-opie.o openssl.o http-ntlm.o ../lib/libgnu.a -liconv  -lssl -lcrypto -lz -ldl -lz -lz
Making all in doc
./texi2pod.pl -D VERSION="1.15" ./wget.texi wget.pod
/usr/bin/pod2man --center="GNU Wget" --release="GNU Wget 1.14" wget.pod > wget.1
Making all in po
Making all in tests
make[2]: Nothing to be done for `all'.
Making all in util
make[2]: Nothing to be done for `all'.
make[2]: Nothing to be done for `all-am'.

最后,安装

sudo make install

安装成功后,试一试wget是否成功安装

$ which wget
/usr/local/bin/wget

如果看到上述结果说明wget已经成功构建并部署到/usr/local/bin目录了

好了,万事具备,可以开始抓取你想要获得内容的网站了。

wget -mk http://website.com

其中-m参数表示迭代的抓取,-k参数表示用相对路径取代绝对路径。抓取的内容会被存放在本地的website.com的目录下。

举个例子,比如我要抓新浪新闻

$ wget -mk http://news.sina.com.cn
--2014-06-30 16:55:26--  http://news.sina.com.cn/
Resolving news.sina.com.cn... 58.63.236.31, 58.63.236.46, 58.63.236.48, ...
Connecting to news.sina.com.cn|58.63.236.31|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 636992 (622K) [text/html]
Saving to: ‘news.sina.com.cn/index.html’

100%[======================================>] 636,992      391KB/s   in 1.6s   

2014-06-30 16:55:29 (391 KB/s) - ‘news.sina.com.cn/index.html’ saved [636992/636992]

Loading robots.txt; please ignore errors.
--2014-06-30 16:55:29--  http://news.sina.com.cn/robots.txt
Reusing existing connection to news.sina.com.cn:80.
HTTP request sent, awaiting response... 200 OK
Length: 70 [text/plain]
Saving to: ‘news.sina.com.cn/robots.txt’

100%[======================================>] 70          --.-K/s   in 0.03s   

2014-06-30 16:55:29 (2.54 KB/s) - ‘news.sina.com.cn/robots.txt’ saved [70/70]

--2014-06-30 16:55:29--  http://news.sina.com.cn/js/792/2012-08-09/41/headnews.js
Reusing existing connection to news.sina.com.cn:80.
HTTP request sent, awaiting response... 200 OK
Length: 31699 (31K) [application/x-javascript]
Saving to: ‘news.sina.com.cn/js/792/2012-08-09/41/headnews.js’

100%[======================================>] 31,699      --.-K/s   in 0.04s   

2014-06-30 16:55:29 (731 KB/s) - ‘news.sina.com.cn/js/792/2012-08-09/41/headnews.js’ saved [31699/31699]

--2014-06-30 16:55:29--  http://news.sina.com.cn/pfpnews/js/libweb.js
Reusing existing connection to news.sina.com.cn:80.
HTTP request sent, awaiting response... 200 OK
Length: 6554 (6.4K) [application/x-javascript]
Saving to: ‘news.sina.com.cn/pfpnews/js/libweb.js’

100%[======================================>] 6,554       --.-K/s   in 0.03s

抓成功后的目录如下

注意:

  • 这样的方式仅适用于静态网站,对于使用动态代码生成的网站无能为力

  • 地址转换是发生在所有内容抓取完成之后,如果你中途退出,则所有的地址链接仍然指向原始地址。当你所要抓取的内容巨大时,需要非常小心。

  • 为了防止流量过大,对服务器造成太大的负担,可以使用-w参数设置两个请求中的间隔时间


这些年,我穿过的那些队服

2014年5月30日 05:13

世界杯将近,晒一下毕业后穿过球衣,缅怀一下青春。

1997~

首先映入眼帘的这件阿根廷队服,这是仍然保存的年代最久的一件球衣了,依稀是研究生时的队服,15,6年了,现在我仍然有机会就会穿上这件球衣。

2003~2007

这是在SAP B1时代的队服,英格兰。从那时起,我更多的使用14号球衣

这是队友同事在欧洲带的米兰队服,卡卡不错,我更喜欢巴斯滕。

2003~

这是我在小区俱乐部队的球衣,那时我是17号,那支球队很不错,大家都很有热情,很多队员还来我的婚礼帮忙,然而后来不知为什么,活动少了,我和球队也失去了联系。至今,我还能在qq群上看到球队的消息,队伍似乎壮大了不少,可是物是人非,当年的小伙伴们,已经没有剩下几个了。

2011~

2011年重归SAP,加入BOBJ足球队,我仍然是14号。那一年我们选择了曼城;那一年,我们勇夺公司联赛冠军;曼城也随后夺得英超的冠军。后来我们继续蝉联了一届冠军。可惜两次夺冠,我的贡献都不多,只有一些替补登场,只记得两次替补登场,都是在第一脚触球打入进球。后来由于关键球星的离去,球队未能继续辉煌。

大家注意,贴的字不能机洗,字毁掉!

这是夺冠后的奖励

2013~

去年,加入了一个新的球队-上海万里老男孩足球俱乐部,有组织真好,这是球队主要赞助商林特赞助的恒大队服。我们球队的气氛非常好,我很喜欢和大家一起踢球,赞!

球队另一主要赞助商味它赞助的意大利队服。

同时,还联系上了大学的校友会,在我们的世界杯抽签,我们签到了伊朗,不过太忙,还么来得及和队友合练。

23是我和我女儿的学号 :)

容颜易老,青春永恒~

神奇的阿基米德螺线

2014年5月23日 19:06

今天在读数学史,正巧读到阿基米德螺线,于是写了一段js代码,生成螺线。

See the Pen Spiral by gangtao (@gangtao) on CodePen.

更多的有趣内容请参考这篇文章

代码如下(需要jquery和d3):

HTML

<div id="chart"></div>

 

CSS

body {
background-color:#000000;
}

 

JS

function drawCircile(pc, r, container) {
    var circle = container.append("circle").attr("cx", pc.x).attr("cy", pc.y).attr("r", r)
        .attr("stroke", "#fa6900").attr("stroke-width", 1).attr("fill", "none");
    return circle;

}

function drawLine(p1, p2, c, isDash) {
    var line = c.append("line").attr("x1", p1.x).attr("y1", p1.y)
        .attr("x2", p2.x).attr("y2", p2.y)
        .style("stroke", "#ccc").style("stroke-width", 1);
    if (isDash) {
        line.style("stroke-dasharray", "5,5");
    }
    return line;
}

$(function () {
    var center = {x: 300, y :300};
    var l = 250;
    var a = 0, b = 0, p0, p1, line;
    var v = 0.3; //直线移动速度
    var f = 360; //转动速度 

    var root = d3.select("#chart").append("svg").append("g");
    drawCircile(center,3,root)

    var timer = setInterval(function(){
        a = a + Math.PI / f;
        b = b + v;
        p0 = {x:center.x+ l * Math.sin(a), y:center.y - l * Math.cos(a) };
        p1 = {x:center.x + b * Math.sin(a), y:center.y - b * Math.cos(a) };
        drawCircile(p1,1,root)

        if( b > l ) {
            clearInterval(timer);
        } else {
            if(!line) {
                line =  drawLine(center, p0, root);    
            } else {
                line.transition().duration(10).attr("x2",p0.x).attr("y2",p0.y);
            } 
        }
    },10);
});

改变v和f的值分别改变点在直线上的移动过速度和直线转动的角速度,会得到不同的结果。

我拿给我还在读一年级的女儿看,她很是入迷,不断要求我修改参数,结果得到许多有趣的图形,大家可以通过这个链接自己试试看。

f=2.5 海星

f=3 雪花

f=1.414

f = 0.618

f=8.8

f = 12.12

更多的结果,大家可以自己去探索。

感叹于数学的神奇和美丽,感叹于小孩子的探索精神, 感叹于计算机和编程使得数学的探索变得简单和有趣!