以前就用过Supervisor,但没有细看,这次认真的使用了之后,还是发现了一些问题。

  • 如何让Supervisor成为服务

如果是通过pip install supervisor这种方式安装的Supervisor, 如何让Supervisor成为系统的一种服务,让系统开机时自动启动,在这里给出了一些例子,然而,我的系统是Centos 6.5, 这些例子没有包括。于是通过yum install supervisor的方式安装,得到了/etc/init.d/supervisord下的服务脚本. 之后把通过yum安装得到的/usr/bin/supervisord和/usr/bin/supervisorctl替换成pip安装得到的/usr/local/bin/supervisord和/usr/local/bin/supervisorctl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/bin/bash
#
# supervisord This scripts turns supervisord on
#
# Author: Mike McGrath <mmcgrath@redhat.com> (based off yumupdatesd)
#
# chkconfig: - 95 04
#
# description: supervisor is a process control utility. It has a web based
# xmlrpc interface as well as a few other nifty features.
# processname: supervisord
# config: /etc/supervisord.conf
# pidfile: /var/run/supervisord.pid
#

# source function library
. /etc/rc.d/init.d/functions

RETVAL=0

start() {
echo -n $"Starting supervisord: "
daemon supervisord
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/supervisord
}

stop() {
echo -n $"Stopping supervisord: "
killproc supervisord
echo
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/supervisord
}

restart() {
stop
start
}

case "$1" in
start)
start
;;
stop)
stop
;;
restart|force-reload|reload)
restart
;;
condrestart)
[ -f /var/lock/subsys/supervisord ] && restart
;;
status)
status supervisord
RETVAL=$?
;;
*)
echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart}"
exit 1
esac

exit $RETVAL
  • Supervisor无法管理daemon进程

在启动Shadowsock的时候,加了-d参数
/usr/local/bin/ssserver -p 443 -k password --user nobody -d start, 也就是让它成为daemon进程,于是Supervisor就无法管理了。所以之后把这个参数去掉。

  • 当supervisor挂了之后,它管理的进程就给了init进程,之后supervisor再次启动,端口已经被绑定了,怎么破?目前还没有找到解决的办法。

参考资料

联系作者

在Django中,使用select_relatedprefetch_related是两个很常见的优化手段。

举个例子最能说明问题。

准备工作

首先建立如下model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Category(models.Model):
name = models.CharField(max_length=30)
creat_time = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return u'%s' % self.name

class Tag(models.Model):
name = models.CharField(max_length=30)
creat_time = models.DateTimeField(auto_now_add=True)
def __unicode__(self):
return u'%s' % self.name

class Post(models.Model):
title = models.CharField(max_length=255)
slug = models.SlugField(max_length=300, allow_unicode=True, unique=True)
content = models.TextField()
publish_time = models.DateTimeField(auto_now_add=True)
category = models.ForeignKey(Category)
tag = models.ManyToManyField(Tag, blank=True)

def __unicode__(self):
return u'%s' % self.title

之后编写序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category


class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag


class PostSerializer(serializers.ModelSerializer):
category = CategorySerializer()
tag = TagSerializer(many=True)

class Meta:
model = Post
fields = ('id', 'title', 'slug', 'content', 'publish_time', 'category', 'tag', )

之后编写api

1
2
3
4
5
6
7
8
9

class PostListAPI(generics.ListAPIView):
serializer_class = PostSerializer
model = Post
paginate_by = 10


def get_queryset(self):
return Post.objects.all().order_by('-publish_time')

编写url

1
url(r'^api/posts/?$', PostListAPI.as_view(), name='post_list'),

之后在后台新建两篇文章,访问api, 可以看到访问的sql

1
2
3
4
5
DEBUG [24/Jul/2016 13:57:13] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_post"."id", "blog_post"."title", "blog_post"."slug", "blog_post"."content", "blog_post"."publish_time", "blog_post"."category_id" FROM "blog_post" ORDER BY "blog_post"."publish_time" DESC; args=()
DEBUG [24/Jul/2016 13:57:13] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_category"."id", "blog_category"."name", "blog_category"."creat_time" FROM "blog_category" WHERE "blog_category"."id" = 1; args=(1,)
DEBUG [24/Jul/2016 13:57:13] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_tag"."id", "blog_tag"."name", "blog_tag"."creat_time" FROM "blog_tag" INNER JOIN "blog_post_tag" ON ("blog_tag"."id" = "blog_post_tag"."tag_id") WHERE "blog_post_tag"."post_id" = 2; args=(2,)
DEBUG [24/Jul/2016 13:57:13] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_category"."id", "blog_category"."name", "blog_category"."creat_time" FROM "blog_category" WHERE "blog_category"."id" = 1; args=(1,)
DEBUG [24/Jul/2016 13:57:13] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_tag"."id", "blog_tag"."name", "blog_tag"."creat_time" FROM "blog_tag" INNER JOIN "blog_post_tag" ON ("blog_tag"."id" = "blog_post_tag"."tag_id") WHERE "blog_post_tag"."post_id" = 1; args=(1,)

这里有两篇文章,访问tag和category表都分别访问了两次,如果是10篇文章,那访问tag和category则分别要10次。此时select_related和prefetch_related派上了用场。

查看select_related的文档,在返回QuerySet时,对于ForeignKey和OneToOneField等字段,通过添加select_related,可以把相关的对象在一次查询中查出,之后使用时就不需要再次查数据库。

还是看例子容易明白。对于上面的Post中category字段,因为是ForeignKey, 所以可以通过select_related查出,修改api如下

1
2
3
4
5
6
7
8
9
10
11

class PostListAPI(generics.ListAPIView):
serializer_class = PostSerializer
model = Post
paginate_by = 10


def get_queryset(self):
queryset = Post.objects.all().order_by('-publish_time')
queryset = queryset.select_related('category')
return queryset

访问url, 查看sql

1
2
3
DEBUG [24/Jul/2016 13:58:29] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_post"."id", "blog_post"."title", "blog_post"."slug", "blog_post"."content", "blog_post"."publish_time", "blog_post"."category_id", "blog_category"."id", "blog_category"."name", "blog_category"."creat_time" FROM "blog_post" INNER JOIN "blog_category" ON ("blog_post"."category_id" = "blog_category"."id") ORDER BY "blog_post"."publish_time" DESC; args=()
DEBUG [24/Jul/2016 13:58:29] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_tag"."id", "blog_tag"."name", "blog_tag"."creat_time" FROM "blog_tag" INNER JOIN "blog_post_tag" ON ("blog_tag"."id" = "blog_post_tag"."tag_id") WHERE "blog_post_tag"."post_id" = 2; args=(2,)
DEBUG [24/Jul/2016 13:58:29] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_tag"."id", "blog_tag"."name", "blog_tag"."creat_time" FROM "blog_tag" INNER JOIN "blog_post_tag" ON ("blog_tag"."id" = "blog_post_tag"."tag_id") WHERE "blog_post_tag"."post_id" = 1; args=(1,)

可以看到,对于category信息,在查询post的同时,也一起查出,减少了查询category表的操作。

查看Django的prefetch_related文档,了解到prefetch_related对于相关对象会进行一次独立的查询,然后在Python中把对象关联起来。所以prefetch_related可以用于many-to-many and many-to-one关系。

还是举例子,对于上面的tag, 可以使用prefetch_related来处理。修改api如下:

1
2
3
4
5
6
7
8
9
10
11
class PostListAPI(generics.ListAPIView):
serializer_class = PostSerializer
model = Post
paginate_by = 10


def get_queryset(self):
queryset = Post.objects.all().order_by('-publish_time')
queryset = queryset.select_related('category')
queryset = queryset.prefetch_related('tag')
return queryset

之后访问api,查看sql

1
2
DEBUG [24/Jul/2016 14:02:35] [django.db.backends:utils:execute:89] [None] (0.000) SELECT "blog_post"."id", "blog_post"."title", "blog_post"."slug", "blog_post"."content", "blog_post"."publish_time", "blog_post"."category_id", "blog_category"."id", "blog_category"."name", "blog_category"."creat_time" FROM "blog_post" INNER JOIN "blog_category" ON ("blog_post"."category_id" = "blog_category"."id") ORDER BY "blog_post"."publish_time" DESC; args=()
DEBUG [24/Jul/2016 14:02:35] [django.db.backends:utils:execute:89] [None] (0.000) SELECT ("blog_post_tag"."post_id") AS "_prefetch_related_val_post_id", "blog_tag"."id", "blog_tag"."name", "blog_tag"."creat_time" FROM "blog_tag" INNER JOIN "blog_post_tag" ON ("blog_tag"."id" = "blog_post_tag"."tag_id") WHERE "blog_post_tag"."post_id" IN (2, 1); args=(2, 1)

可以看到对于tag的查询也只有一次。

从上面的例子可以看到,掌握select_related和prefetch_related的用法非常重要。本文的代码见test-django的blog

参看资料:

联系作者

对于一个应用来说,日志是很重要的一部分。Django提供的logging功能,使用Python自带的logging模块来处理系统日志很方便。

下面是一份简单的配置。配置最主要的需求是能够看到Django查询数据库的sql语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format' : '%(levelname)s [%(asctime)s] [%(name)s:%(module)s:%(funcName)s:%(lineno)s] [%(exc_info)s] %(message)s',
'datefmt' : "%d/%b/%Y %H:%M:%S"
},
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'logs/debug.log',
'formatter': 'standard'
},
'console': {
'class': 'logging.StreamHandler',
'formatter': 'standard'
},
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'DEBUG',
'propagate': True,
},
},
}

联系作者

Django提供了Q和F算子来方便查询。

Q算子

在对queryset进行filter时,filter语句的关系是AND关系,要实现OR关系,则需要使用Q算子

例如

1
Q(question__startswith='Who') | Q(question__startswith='What')

相当于

1
WHERE question LIKE 'Who%' OR question LIKE 'What%'

F算子

在实际开发中,常常需要对数据库中的某一个字段进行加减操作,如评论数,访问量等等。一种做法是把这个字段读出来,然后进行操作,之后再保存到数据库中,而F算子提供了简便的方法,它使这些操作直接在数据库中执行。
例如,在下面的例子中需要对stories_filed字段进行加1操作。

1
2
3
4
# Tintin filed a news story!
reporter = Reporters.objects.get(name='Tintin')
reporter.stories_filed += 1
reporter.save()

而使用F算子,则是

1
2
3
4
from django.db.models import F

reporter = Reporters.objects.filter(name='Tintin')
reporter.update(stories_filed=F('stories_filed') + 1)

F算子的优势在于不需要使用Python, 而只是使用数据库来完成操作。

而F算子的另外一个好处是减少多线程下的变量问题。

联系作者

Wordpress迁移到Hexo遇到的问题中说过Wordpress迁移到Hexo时会遇到文件名问题,当时写了个Python脚本解决,现在有了更好的解决办法,于是记下。

在迁移的时候会生成

1
2
3
e8-af-ad-e8-a8-80-e7-89-b9-e6-80-a7-e8-bf-98-e6-98-af-e6-9c-89-e5-bf-85-e8-a6-81-e5-ad-a6-e4-b9-a0-e7-9a-84.md
e8-bd-af-e8-bf-9e-e6-8e-a5-e5-92-8c-e7-a1-ac-e8-bf-9e-e6-8e-a5.md
e8-bf-bd-e8-b8-aaquery-too-complex-not-enough-stack-e9-94-99-e8-af-af.md

这样的文件名, 原因是文件名是从URL转化而来。后来发现将URL进行encode之后就没有这个问题。在fork出的hexo-migrator-wordpress里解决了这个问题,在github上提交了一个pull request, 但一直没有被合并,匪夷所思。

不过还是可以直接安装我的仓库里的代码来解决这个问题。npm install https://github.com/dengshilong/hexo-migrator-wordpress.git --save

联系作者

如果需要对提交的代码进行检查,可以使用hooks, 也就是钩子,如果需要在代码提交到参考后进行发布,也可以使用hooks.

在初始化git仓库时,在.git目录的hooks目录里提供了一些例子。

在我的学习笔记的仓库里,我定义了post-receive这个hooks, 当deploy出现在commit的说明信息中,就更新学习笔记。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#file info
GIT_REPO=/home/dengsl/program/nodejs/blog
DEPLOY_DIR=/home/dengsl/program/html/blog/note

# Get the latest commit subject
SUBJECT=$(git log -1 --pretty=format:"%s")

cd $GIT_REPO
env -i git reset --hard

#update or deploy
IF_DEPLOY=$( echo $SUBJECT | grep 'deploy')
if [ -z "$IF_DEPLOY" ]; then
echo >&2 "Success. Repo update only"
exit 0;
fi

# Check the deploy dir whether it exists
if [ ! -d $DEPLOY_DIR ] ; then
echo >&2 "fatal: post-receive: DEPLOY_DIR_NOT_EXIST: \"$DEPLOY_DIR\""
exit 1
fi

#deploy static site
hexo g
cp -r public/* $DEPLOY_DIR

参考资料:

联系作者

Django提供的migrations功能将项目中的model与数据库表同步,方便开发。

  • makemigrations app_name 生成某个app的migrations
  • migrate [app_label] [migration_name]使某个app的migrations生效,当加上–fake时,假装执行migrations,实际并不执行
  • sqlmigrate app_label migration_name 列出某个migrations的sql语句

联系作者

在天涯的时候,有个同事建议我分享一下正则,只是那时候使用正则的开发经验还少,没有能力分享。

后来到杭州后,进入同花顺,使用正则解决了一些问题。也是在这里,我才知道,许多开发人员对于正则还不是很理解。

离开同花顺,来到天猪,做Web开发,使用正则的场景比之前多。在这里,同样发现同事的正则水平需要提高。前端组同学组织了两周一次的技术分享,后来发展到技术组的技术分享,于是我也给大家进行了一次正则分享

联系作者