分享

django开发总结

 java_laq小馆 2014-01-15

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 = utf8collation-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_fieldptkey_value = getattr(mobject, ptkey_field)        version = __get_current_version()        cache.set(__get_verkey(table, ptkey_value), version)    return Truedef __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/pythonimport redisimport 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_checkdef issues_show(request, user_name, repo_name):    pass对于仓库是否可见的权限,repo_permission_check 就是 decorator 控制:
    使用这样的机制能使权限控制统一和优雅,减少离散粒度控制出现的失误
    from django.http import Http404from django.http import HttpResponseRedirectfrom 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_PORTclass 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 Noneclass 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 代码如下:[table]
    {csrfmiddlewaretoken: '{{ csrf_token }}'}
    为了减少 csrf 攻击,看起来简单粗暴,是吧?
    正是因为这样,我才推荐所有的ajax通过 POST 请求,你甚至可以通过 @require_http_methods(["POST"]) 来强制要求 POST 请求,这样 ajax 必须附带 csrfmiddlewaretoken 参数。
    另一个安全问题是 xss,随着 ajax 使用越来越多,这个问题越来越容易被忽视,gitshell 使用统一的 json 序列化方法来防止 xss 攻击:
    import jsonimport functoolsfrom django.utils.html import escapefrom 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-prohibitedCOMMIT
  • 生产环境配置
    推荐生产环境使用 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 = :8001protocol = uwsgiprocesses = 3harakiri = 30daemonize = /opt/run/var/log/uwsgi.8001.daemonize.loglisten = 4096master = truemax-requests = 2500pidfile = /opt/run/var/uwsgi.8001.piduid = gitgid = gitlimit-as = 512limit-post = 3145728no-orphans = truepost-buffering = 4096logto = /opt/run/var/log/uwsgi.8001.loglog-slow = 800log-5xx = truelog-big = 102400disable-logging = truechdir = /opt/app/8001pyhom = /opt/app/8001pythonpath = /opt/app/8001env = DJANGO_SETTINGS_MODULE=gitshell.settingsmodule = gitshell.wsgi:application
    使用 /opt/bin/uwsgi –ini 的方式来启动。

    来自:http://www./forum.php?mod=viewthread&tid=9513

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多