当前位置:首页 > 技术分析 > 正文内容

信息安全聚合 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

这个集合用来存储 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 行代码左右,需要改动的部分不超过 200 行。重构过程还改进了很多功能、用户体验方面的问题(主要是后台)。

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

除了替换数据库。细节上还有一处改进:我将 flask 原生的 client-side-session 换成了一个叫"flask-session"的 server-side-session 的插件,以规避前段时间自己发现的『验证码绕过漏洞』。 flask-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 账号。

服务器移到国内,还有一个问题就是域名,我的 leavesongs.com 是没有备案的,所以新的 sec-news 域名不能再用这个子域名了。还好自己手上刚备案了一个新域名,我就直接用新域名下的子域名作为 sec-news 的域名。

那么老域名的"遗产"怎么办?

如上图,有些网站还保留着我的老域名下的链接,我想尽量保持一切不变。于是我从老数据库导出了一个 json 格式的对象:_id : url ,在老 vps 上做了个简单的转发:

location ^~ /url/ {
        rewrite ^/url/(.*)$ /old.php?hash=$1 last;
}

location = /old.php {
        fastcgi_pass  unix:/var/run/php5-fpm.sock;
        fastcgi_index index.php;
        include fastcgi.conf;
}

location / {
        rewrite ^/(.*) http://wiki.ioin.in/$1 permanent;
}

将所有 /url/开头的链接转发到 old.php 里处理,其他链接就直接 301 到新域名下。那么 old.php 就专门处理以前_id 是 24 位 hash 的链接:

<?php
$old_data = json_decode(file_get_contents('olddata.txt'), TRUE);
$hash = isset($_GET['hash']) ? $_GET['hash'] : "";
if($hash && array_key_exists($hash, $old_data)) {
        header('Location: ' . $old_data[$hash]);
} else {
        header('Location: http://wiki.ioin.in/url/' . $hash);
}

这样就能保证以前的链接全部能够访问,新链接直接跳转到新域名。

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

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

扫描二维码推送至手机访问。

版权声明:本文由ruisui88发布,如需转载请注明出处。

本文链接:http://www.ruisui88.com/post/4714.html

标签: kwargs.get
分享给朋友:

“信息安全聚合 Sec-News 的重构之路” 的相关文章

K8s里我的容器到底用了多少内存?

作者:frostchen导语 Linux下开发者习惯在物理机或者虚拟机环境下使用top和free等命令查看机器和进程的内存使用量,近年来越来越多的应用服务完成了微服务容器化改造,过去查看、监控和定位内存使用量的方法似乎时常不太奏效。如果你的应用程序刚刚迁移到K8s中,经常被诸如以下问题所困扰:容器的...

VIM配置整理

一、基本配色set number set showcmd set incsearch set expandtab set showcmd set history=400 set autoread set ffs=unix,mac,dos set hlsearch set shiftwidth=2 s...

USB电池充电基础:应急指南

USB为便携设备供电与其串行通信功能一样,已经成为一种标准应用。如今,USB 供电已经扩展到电池充电、交流适配器及其它供电形式的应用。应用的普及带来的一个显著效果是便携设备的充电和供电可以互换插头和适配器。因此,相对于过去每种装置都采用专用适配器的架构相比,目前的解决方案允许采用多种电源进行充电。毋...

史上最全 vue-router 讲解 !!!

前端路由 前端路由是后来发展到SPA(单页应用)时才出现的概念。 SPA 就是一个WEB项目只有一个 HTML 页面,一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转。 前端路由在SPA项目中是必不可少的,页面的跳转、刷新都与路由有关,通过不同的url显示相应的页面。 优点:前...

电脑提速教程:用NVMe固态硬盘帮扶加速SATA硬盘

不知不觉当中,固态硬盘已经取代机械硬盘成为主流。越来越多的玩家已经淘汰机械盘,使用NVMe+SATA的固态硬盘高低搭配。既然是高低搭配,就一定会有性能差距,是否能从NVMe固态硬盘中划分出一小部分空间来给SATA固态硬盘加速,实现更好地整机性能呢?答案是肯定的,而且这一功能早已隐藏在英特尔Z170、...

明日9时,成绩公布!

甘肃省2023年普通高校毕业生基层服务项目(三支一扶、特岗计划、西部计划)考试成绩将于7月12日公布甘肃省2023年普通高校毕业生基层服务项目(三支一扶、特岗计划、西部计划)考试成绩将于2023年7月12日9:00开通查询,考生可登录“甘肃人事考试网”,点击“成绩查询”栏目查询本人成绩。网址:htt...