转载

重构Sec-News之路

不知道什么时候突然发现我已经稳定运行了近半年的sec-news( http://wiki.ioin.in )突然变得特别慢,为跳转效率我也是尝试了很多方法,比如加缓存。我使用了一个叫flask-cache的缓存: https://pythonhosted.org/Flask-Cache/ ,很好用的cache。

特别喜欢python的一点就是,修饰器(@Decorator)的存在,让很多功能变得简单。flask-cache里有一种cache方式叫Memoization,它可以简单地用 Decorator的方式放在任意函数上。根据函数参数的值,来缓存函数的结果。

class Person(db.Model):     @cache.memoize(50)     def has_membership(self, role_id):             return Group.query.filter_by(user=self, role_id=role_id).count() >= 1
上面是文档里给出的一个example,其缓存了has_membership函数,当我们调用 has_membership(1)的时候,就缓存下50秒这个函数的返回值

。那么下次再调用

has_membership(1)的时候,就会直接返回缓存的结果,但如果你调用 has_membership(2),就是另一个缓存了。

我将flask-cache加到flask的view里,这样就可以缓存整个页面了。

但是,缓存永远不是 解决效率问题的根本方法,解决问题是找到根本原因。我仔细分析了我的sec-news,我认为以前使用的mongodb数据库,是导致整个网站运行慢的原因。

也的确,我设计mongodb的概念和以前设计mysql的概念完全不同,我设计了这样一个集合:

Rss

- id

- url

- title

- posts (array)

这个集合用来存储Rss数据,比如http://www.leavesongs.com/rss.php,这是一个订阅Rss。这个订阅的内容,其实就是它的文章(posts),我的订阅列表中有几个Rss,其中包含的文章已经超过1000篇,也就是posts数组大小已经超过1000,且数组中每篇文章我都保存了文章的标题和内容。

所以其实当我们没有设计好ORM的情况下,提取出这个Rss集合,将占用大量内存,导致Sec-news整体速度变慢。

这是我觉得影响网站效率的最大原因。备份数据后,我删掉了所有文章的内容,再次测试,结果也一样,速度并没有变快。

我开始怀疑架构问题,我开始怀疑是mongodb哪里有坑被我踩中了。这种问题对于半吊子开发我来说,实在是难以发现,难以解决。但在电脑维修界,有著名的『万金油定律』——重启、重装、换电脑。既然解决不了问题,不如用简单点的办法规避问题。

我现在的位置可能位于重启到重装这条路上,在替换一些数据(重启)的情况下并不能解决效率问题,那么我就需要思考『重装』的问题了。所谓的重装,也就是换掉mongodb。

sec-news在开发的时候就已经做到了MVR(Model - View - Route),代码耦合性也比较低,但实际上替换数据库的过程还是需要重构大量代码,主要原因就是mongodb->mysql是一场Nosql到sql的转变,基础架构需要调整。

不过总代码量也不大,整个view + model也只有700行代码左右,需要改动的部分不超过100行。重构过程还改进了很多功能、用户体验方面的问题(主要是后台)。

重构后的sec-news还是用ORM,我在peewee和sqlalchemy中选择了后者,因为flask-sqlalchemy是一个比较成熟的搭配,在实际开发中我比较看重稳定性,虽然个人感觉peewee更『酷』。

除了替换数据库。细节上还有一处改进:我将flask原生的client-session换成了一个叫"flask-session"的server-session的插件,以规避前段实际自己发现的『验证码绕过漏洞』。server-session储存在redis中,我喜欢redis胜过memcache,原因是memcache所拥有的功能redis都有,但redis所拥有的功能memcache并不一定有,所以我一般都不用memcache。

另外,我实现了后台多用户权限控制,其实说起来也比较简单:

def check_role(request_role):     def do_check(role_array):         def check(func):             @functools.wraps(func)             def do_function(*args, **kwargs):                 if flask.session.get("user_id") > 0:                     if flask.session.get("role") in role_array:                         return func(*args, **kwargs)                     else:                         return permission_deny(*args, **kwargs)                 else:                     return flask.redirect(flask.url_for("login"))             return do_function         return check      return do_check(request_role)  @app.route('/admin') @check_role(["admin", "user"]) def admin():     #show administrator index page  @app.route('/admin/add') @check_role(["admin"]) def add():     #add a new administrator

再次感谢python的 Decorator,我用一个简单的 check_role函数即可实现权限控制。比如admin函数,可以允许user、admin两个角色访问,而add函数就只允许admin角色访问,假设既不是user也不是admin,就直接跳到login页面。

Decorator也是我迟迟放不下python的原因,假设php里也加入这个语法糖,那我保准不会用python写网站了,很多方面还是php更方便。

在Route方面,我也做了一些改进。因为mongodb的默认索引_id是一个24位hash值,不容易被用户猜到,而mysql的主键通常是一个AUTO_INCREMENT的数字,好事者只需要编写一个脚本即可遍历我的所有文章,我不喜欢这样。

我用了hashids这个库,将int类型的id转换成了一个hashids,好事者猜不到这个字符串,也就无法遍历我的文章了。(当然可以写爬虫爬取,但这和遍历有本质区别)

重构用了大概一天半,传到原来的服务器上,发现…… 这TM还是一样慢啊……我真是错怪mongodb了,我给你赔罪!

那么现在,『重装』这条路也死了,并没有解决问题。

最后也就只剩『换电脑』了,我一咬牙一跺脚买了一台阿里云青岛的服务器(按流量计费,算下来还是不贵的,一个月50RMB左右)。这时候我基本上已经心力交瘁了,只想尽快把问题解决我好干别的。

我用最快的速度部署好服务器:

apt-get update apt-get install nginx mysql-server mysql-client redis-server libjpeg-dev git clone xxx pip install -r requirements.txt pip install gunicorn supervisor

直接安默认的,能用就行。因为服务器带的ubuntu14没有systemd,我就选择用supervisor管理我的gunicorn服务,nginx简单配了一下就了好了,mysql最开始也直接用root账号。

后面有空闲时间才慢慢优化了一下,找到几个小伙伴一起更新一些好文章,sec-news正式复活了。

希望我这次重构之路对大家的开发有启发,也欢迎大家订阅Sec-News的RSS,主页: http://wiki.ioin.in ,订阅:  http://wiki.ioin.in/atom

分享几张重构后后台的截图:

重构Sec-News之路

重构Sec-News之路

重构Sec-News之路

重构Sec-News之路

重构Sec-News之路

重构Sec-News之路

原文  https://www.leavesongs.com/PYTHON/rewrite-sec-news.html
正文到此结束
Loading...