django开发总结Gitshell - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
gitshell

django开发总结Gitshell

  •  
  •   gitshell 2014 年 1 月 8 日 7598 次点击
    这是一个创建于 4490 天前的主题,其中的信息可能已经有所发展或是发生改变。
    django是流行的web开发框架,使用优雅的python语言。以下内容是使用django开发gitshell的经验总结,需要对django,python有一定的基础,对于入门,请看这里 The Django Book 中文版 。

    URL 设计
    django 认为 URL 是有语义的,URL 也要优雅,遵循人类的自然语言,可以实现一些类似 RESTful 接口。在 django 的官方文档上面:A clean, elegant URL scheme is an important detail in a high-quality Web application。事实上,优美的 url 设计对 seo 也是非常友好的。比如下面的登录注册,找回密码相关操作:

    from django.conf.urls.defaults import patterns, include, url

    urlpatterns = patterns('gitshell',
    url(r'^login/?$', 'gsuser.views.login'),
    url(r'^logout/?$', 'gsuser.views.logout'),
    url(r'^join/?(\w+)?/?$', 'gsuser.views.join'),
    url(r'^resetpassword/?(\w+)?/?$', 'gsuser.views.resetpassword'),
    )
    handler404 = 'gitshell.help.views.error'
    handler500 = 'gitshell.help.views.error'

    编码统一
    我的建议是在现代操作系统上,全部使用UTF-8编码,从操作系统到数据库到django,还有其他所有组件,这能减少很多编码的问题。
    确定 > locale 输出编码是 *.UTF-8
    django settings.py 里面:

    TIME_ZOnE= 'Asia/Shanghai' LANGUAGE_CODE = 'zh_CN' DEFAULT_CHARSET = 'UTF-8'

    这里讲和django相关的东西,说到mysql主要是考虑编码的问题,当然也建议mysql使新版本,innodb引擎:

    [client]
    default-character-set = utf8
    [mysqld]
    init_cOnnect= 'SET collation_cOnnection= utf8_general_ci'
    init_cOnnect= 'SET NAMES utf8'
    character-set-server = utf8
    collation-server = utf8_general_ci
    [mysql]
    default-character-set = utf8

    另外在python代码里面,添加coding,使用中文内容的时候添加unicode标识

    # -*- coding: utf-8 -*-
    var = u'中文内容'

    cache 机制
    gitshell 对内存用的非常重度,最大化的减少db的压力,关于使用的内存策略,这里简单说一下,以后可以单独成为一篇文章。
    大多数的系统都是读多于写,能否利用好内存是一个系统能不能面对多并发,多流量的关键部分。
    此外,一些系统具有“分片”的特征,最明显的就是crm系统,每一个更新操作都是在具体公司下面,下面提供一个思路:
    1)对于可以“分片”的数据库表结构,所有的请求都附带“分片ID”,比如具体公司ID
    2)使用版本号的概念,比如具体公司ID 1 的版本号就是 “company_id_1″ -> 1000
    3)所有的sql语句抽象为sql_id,包含参数,那么数据库请求就是先查看key为 “company_id_1_” + version + ‘_’ + sql_id 的缓存是否存在,比如 ‘company_id_1_100_sql_id’,如果cache存在,直接返回数据,否则取数据库然后放到cache。
    4)对于更新操作,cache key version自增,比如上面的 1000 自增为 1001,之前的所有缓存自动不再使用,等待废弃。
    5)监听 save() 接口,从中激发更新操作。
    6)对于直接 get_by_id,可以做一些针对化的cache,因为使用主键id来访问的情况非常频繁。
    监听 save() 接口,使用 django event 机制:

    def da_post_save(mobject):
    table = mobject._meta.db_table
    if not hasattr(mobject, 'id'):
    return False
    id_key = __get_idkey(table, mobject.id)
    cache.delete(id_key)
    if table in table_ptkey_field:
    ptkey_field = table_ptkey_field[table]
    ptkey_value = getattr(mobject, ptkey_field)
    version = __get_current_version()
    cache.set(__get_verkey(table, ptkey_value), version)
    return True
    def __cache_version_update(sender, **kwargs):
    da_post_save(kwargs['instance'])
    post_save.connect(__cache_version_update)

    注意,这种缓存方式不适合于非常频繁更新的操作,会导致memcache的item频繁不再使用。
    redis 配置
    gitshell 对 redis 的使用非常谨慎的,redis 虽然好,但是内存大户,所以需要使用redis的情况下才使用,比如排名,feed,前N最大最小列表,以下脚本测试redis占用量:

    #!/usr/bin/python
    import redis
    import random

    def main():
    feed_redis = redis.Redis('localhost', 6379, 3)
    for i in range(0, 1000):
    for ftype in ['r', 'u', 'wu', 'bwu', 'wr', 'c']:
    key = '%s:%s' % (ftype, i + 10000)
    for j in range(0, 100):
    value = random.randint(0, 1000000)
    feed_redis.zadd(key, value, value+1)

    if __name__ == '__main__':
    main()

    100000 的 sorted key 大概需要 150M 内存,假如你有 10 万个用户呢?把所有的 redis 相关操作封装成为方法,在一个 python class 里面,减少 redis 的滥用。
    redis 设计上全在内存使用,才能发挥最大优势,但是内存是易逝性存储,需要使用 M-S 做分发和复制。
    建议起两个实例,主从复制,主redis使用内存结构,从redis使用Append-only,appendfsync everysec。减少最大可能的丢失。
    decorator 做权限控制
    这个地方和 1 URL 设计 息息相关,decorator 可以拦截所有的请求,针对请求做指定事情。
    gitshell 所有仓库都是使用 /username/reponame/ 的方式,相对 URL 都是

    url(r'^(\w+)/(\w+)/issues/', 'repo.views.issues_show'),
    对应方法:
    @repo_permission_check
    def issues_show(request, user_name, repo_name):
    pass

    对于仓库是否可见的权限,repo_permission_check 就是 decorator 控制:使用这样的机制能使权限控制统一和优雅,减少离散粒度控制出现的失误

    from django.http import Http404
    from django.http import HttpResponseRedirect
    from gitshell.repo.models import RepoManager

    def repo_permission_check(function):

    def wrap(request, *args, **kwargs):
    if len(args) >= 2:
    user_name = args[0]
    repo_name = args[1]
    repo = RepoManager.get_repo_by_name(user_name, repo_name)
    if repo is None:
    return HttpResponseRedirect('/help/error/')
    # half private, code is keep
    if repo.auth_type == 2:
    if not RepoManager.is_repo_member(repo, request.user):
    return HttpResponseRedirect('/help/error/')
    return function(request, *args, **kwargs)
    wrap.__doc__=function.__doc__
    wrap.__name__=function.__name__

    return wrap

    异步事件
    异步事件使用 beanstalkd,beanstalkd 是一个非常小巧,依赖少的事件后台服务。默认是在内存中,如果需要持久状态,使用 -b 参数,这样就能持久的写在文件里,防止忽然的机器故障丢失数据。
    在 ubuntu 里,使用
    > sudo apt-get install beanstalkd
    python client 使用 beanstalkc
    多个 tube 的使用,如果有多个队列,为了能每个后台程序管理对应的队列,使用tube:

    from gitshell.settings import BEANSTALK_HOST, BEANSTALK_PORT
    class EventManager():

    @classmethod
    def sendevent(self, tube, event):
    beanstalk = beanstalkc.Connection(host=BEANSTALK_HOST, port=BEANSTALK_PORT)
    self.switch(beanstalk, tube)
    beanstalk.put(event)

    @classmethod
    def switch(self, beanstalk, tube):
    beanstalk.use(tube)
    beanstalk.watch(tube)
    beanstalk.ignore('default')

    @classmethod
    def send_stop_event(self, tube):
    stop_event = {'type': -1}
    self.sendevent(tube, json.dumps(stop_event))

    # ======== send event ========
    @classmethod
    def send_fork_event(self, from_repo_id, to_repo_id):
    fork_event = {'type': 0, 'from_repo_id': from_repo_id, 'to_repo_id': to_repo_id}
    self.sendevent(FORK_TUBE_NAME, json.dumps(fork_event))

    beanstalk 的事件建议使用 json 格式做序列化,简单,并且跨平台。
    logging 以及监控
    logging 是系统健壮的有效保证,呃,系统挂了,什么日志都没有??
    django 通过 logging 来记录所有的日志,settings.py 配置如下:

    LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
    'mail_admins': {
    'level': 'ERROR',
    'class': 'django.utils.log.AdminEmailHandler',
    },
    'file': {
    'level': 'INFO',
    'class': 'logging.FileHandler',
    'filename': '/opt/run/var/log/gitshell.8001.log',
    },
    },
    'loggers': {
    'gitshell': {
    'handlers': ['file'],
    'level': 'INFO',
    'propagate': True,
    },
    'django.request': {
    'handlers': ['mail_admins'],
    'level': 'ERROR',
    'propagate': True,
    },
    }
    }

    除此之外,写一个全局的middleware来捕获所有的exception,一个登录用户一定时间内(比如30分钟)最多访问请求(比如1000)限制控制:

    class ExceptionLoggingMiddleware(object):
    def process_exception(self, request, exception):
    logger = logging.getLogger('gitshell')
    logger.error(traceback.format_exc())
    return None
    class UserAccessLimitMiddleware(object):
    def process_request(self, request):
    path = request.path
    if path.startswith('/help/') or path.startswith('/captcha/'):
    return
    if request.user.is_authenticated():
    user_id = request.user.id
    key = '%s:%s' % (ACL_KEY, user_id)
    value = cache.get(key)
    if value is None:
    cache.add(key, 1, ACCESS_WITH_IN_TIME)
    return
    if value > MAX_ACCESS_TIME:
    return HttpResponseRedirect(OUT_OF_AccessLimit_URL)
    cache.incr(key)
    settings.py:
    MIDDLEWARE_CLASSES = (
    'gitshell.gsuser.middleware.UserAccessLimitMiddleware',
    'gitshell.gsuser.middleware.ExceptionLoggingMiddleware',
    )

    MIDDLEWARE 可以自由发挥,一个常见的例子就是每分钟超出一定数量的异常发生,那么就可以发送异常监控报警了,这对一个生产环境的系统很重要。
    安全相关
    django 对安全非常重视,假如你使用 POST 请求,你会发现 django 要求 csrfmiddlewaretoken 参数,在 html 代码如下:

    {csrfmiddlewaretoken: '{{ csrf_token }}'}

    为了减少 csrf 攻击,看起来简单粗暴,是吧?
    正是因为这样,我才推荐所有的ajax通过 POST 请求,你甚至可以通过 @require_http_methods(["POST"]) 来强制要求 POST 请求,这样 ajax 必须附带 csrfmiddlewaretoken 参数。
    另一个安全问题是 xss,随着 ajax 使用越来越多,这个问题越来越容易被忽视,gitshell 使用统一的 json 序列化方法来防止 xss 攻击:

    import json
    import functools
    from django.utils.html import escape
    from django.http import HttpResponse, HttpResponseRedirect, Http404

    def json_httpResponse(o):
    return HttpResponse(json_escape_dumps(o), mimetype='application/json')

    def json_escape_dumps(o):
    json.encoder.encode_basestring = encoder
    json.encoder.encode_basestring_ascii = encoder
    return json.dumps(o)

    def encoder(o, _encoder=json.encoder.encode_basestring):
    if isinstance(o, basestring):
    o = escape(o)
    return _encoder(o)

    iptables 也是必须的,简单的 iptables 策略就是只开放对外端口:

    *filter
    :INPUT ACCEPT [0:0]
    :FORWARD ACCEPT [0:0]
    :OUTPUT ACCEPT [0:0]
    -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
    -A INPUT -p icmp -j ACCEPT
    -A INPUT -i lo -j ACCEPT
    -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
    -A INPUT -j REJECT --reject-with icmp-host-prohibited
    -A FORWARD -j REJECT --reject-with icmp-host-prohibited
    COMMIT

    生产环境配置

    推荐生产环境使用 nginx + uwsgi,nginx 配置

    http {
    upstream uwsgicluster {
    server 127.0.0.1:8001;
    server 127.0.0.1:8002;
    }
    server {
    location / {
    include uwsgi_params;
    uwsgi_pass uwsgicluster;
    }
    }
    }

    uwsgi 配置文件:

    [uwsgi]
    socket = :8001
    protocol = uwsgi
    processes = 3
    harakiri = 30
    daemOnize= /opt/run/var/log/uwsgi.8001.daemonize.log
    listen = 4096
    master = true
    max-requests = 2500
    pidfile = /opt/run/var/uwsgi.8001.pid
    uid = git
    gid = git
    limit-as = 512
    limit-post = 3145728
    no-orphans = true
    post-buffering = 4096
    logto = /opt/run/var/log/uwsgi.8001.log
    log-slow = 800
    log-5xx = true
    log-big = 102400
    disable-logging = true
    chdir = /opt/app/8001
    pyhom = /opt/app/8001
    pythOnpath= /opt/app/8001
    env = DJANGO_SETTINGS_MODULE=gitshell.settings
    module = gitshell.wsgi:application

    使用 /opt/bin/uwsgi ini 的方式来启动。

    有其他问题,联系 admin AT gitshell.com

    http://gitshell.1kapp.com/?p=169
    第 1 条附言    2014 年 1 月 8 日
    这是开发Gitshell过程中的一个总结,本来想方便阅读贴了原文,不过这里的格式支持并不好,欢迎大家直接在博客阅读,谢谢 http://gitshell.1kapp.com/?p=169
    4 条回复    1970-01-01 08:00:00 +08:00
    zjwzszh
        1
    zjwzszh  
       2014 年 1 月 8 日   1
    从推广的角度看,这样做真是太糟糕了! 直接原文复制粘帖,没有一点没感,实在看不下去。 万幸下面有文章原文链接。。原文看着倒是赏心悦目

    阁下好歹给一个全文提炼,或者一个简述。这样才能吸引人吧-。-
    dorentus
        2
    dorentus  
       2014 年 1 月 8 日   1
    这里其实是鼓励直接贴链接的

    所以,摘要+链接,或者只有链接都可以
    gitshell
        3
    gitshell  
    OP
       2014 年 1 月 8 日   1
    @zjwzszh
    @dorentus 收到建议,感谢
    RIcter
        4
    RIcter  
       2014 年 1 月 8 日
    收藏之
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2694 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 123ms UTC 04:09 PVG 12:09 LAX 21:09 JFK 00:09
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86