一个基于Django的API开发记录

需求

这个项目是刚开始做Web开发的时候,做的第一个项目,之前都在写爬虫.
项目大概是这个样子.


示意图

项目本身就是当收到客户查询请求之后,去供应商接口查询数据,解析结果并返回给客户,哪怕在我当时没什么经验,也知道这个蛮好做的.
当时定下的是使用Django+MySQL来做.


架构设计

结构示意图

这是第一版设计的架构图,然后一直用到了现在.(不过并不是因为设计的多么合理,而是重构不如重写,后来我用Golang重新开发了一套基于同一个数据库的系统,来接入新产品.)

设计架构的时候,主要是考虑了一下几点.

  • 可分布式部署,来应对调用量的增加
  • 保存业务日志,整理二手数据降低成本
  • 业务日志与业务逻辑分离,尽量减少数据库操作来提高响应速度
  • 预充值,实时计费

对应这些需求,我首先确定了使用Celery+Redis作为消息队列,处理保存日志的任务,同时将Redis作为Django的缓存,提高用户鉴权速度.
鉴权主要包含两个过程:

  • 验证身份
  • 验证余额

为此,我在缓存中缓存了用户密码相关内容,余额,以及对应调用API的价格,保证每个用户24小时内只从数据库读取一次信息.
当用户一次查询完成之后,将需要记录的内容通过celery发送到worker进行数据库写入操作.降低业务阻塞时间.
(事实证明,这一顿花式操作,没太大用,后期业务不是特别多,而且我们作为中转方,时间开销99%都在等待远程服务器返回结果,做这些只是保证没有出现低级错误而已)


选用的工具及理由

  • Django:相比来说使用Django更多是因为认为开发会更快一些,一是自己了解一些,学习时间更短,二是ORM和ADMIN都是现成的,我一个人做这个很重要.
  • Celery:..应该没什么其他选择了.
  • Redis:同时作为Celery的Broker和Django缓存,肯定比RabbitMQ+Memcached强,尽量降低系统的复杂度了.
  • MySQL:这个位置上,其实有更好的选项Postgresql,因为不熟悉,我选择了自己更熟悉的Mysql,而且一开始就有避免频繁数据库操作,选用哪个完全不会构成瓶颈,SQLite都行,就是有点low.
  • 阿里云SLB:设计之初并没有决定使用哪个云平台,不过确立了Nginx+Gunicorn的结构,后来迁移到阿里云之后,阿里云的SLB的确很方便,因为他可以通过调整权重来停用\启用服务器,实现热更新重启
  • Gunicorn:打败uWsgi的原因是这个部署更简单,方便.
  • Supervisor:可以说很好用,无脑,用了它,夜里也很安心.不过有一个问题是,这玩意不能关掉Celery的进程,每次重启Celery都需要ps + xargs + kill来关,具体是什么原因,一直没有深究.

遇到的问题

网络请求效率低下

前期我采用了requests来做网络请求,可以说我快哭了,慢的令人发指...主要体现在无法和Gunicorn+Gevent配合,请求数量稍微提高一些,效率蹭蹭往下掉.定位这个问题我花了好几个小时.也没有什么好的解决思路,尝试把requests替换成urllib3,居然就解决了.
不过时至今日,我也没有解决不同worker之间共享一个连接的问题,urllib3的连接池只能做到在一个worker里复用,好在这也算不得瓶颈,就搁置了.

业务层设计不合理

这一段实际上是我最想说的,因为这个是我设计的第一套系统,让我吃了挺多堑,涨了不少智.


数据表设计的问题
我拿到项目需求的时候,实际上只有一个数据供应商,三个数据接口,我所需要做的就是把这三个接口加一层壳而已,我开开心心的在数据库建了两张表,日志表就不说了:

class User(models.Model):

    class Meta:
        db_table = u"用户信息"

    account = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=32)
    price1 = models.FloatField(default=0.5)
    price2 = models.FloatField(default=0.5)
    price3 = models.FloatField(default=0.5)
    total = models.FloatField(default=0)
    cost = models.FloatField(default=0)
    def __unicode__(self):
        return self.account

美滋滋,开搞!感觉自己萌萌哒,稚嫩的我甚至都没有考虑api的访问权限,就这么把系统给开发完了.
后来他变成了这个样子:

class User(models.Model):
    # todo
    # 此处由于初期端口较少,在定义价格时采用
    # 了一个用户的价格和账户信息写在一条里的方式.
    # 后期端口增加,拓展性较差,较难维护.
    # 合理的设计结构为[用户+接口+价格+是否开放...]来储存价格
    # 此处应择期重构.

    class Meta:
        db_table = u"用户信息"

    account = models.CharField(max_length=32, unique=True)
    password = models.CharField(max_length=32)
    price1 = models.FloatField(default=0.5)
    price2 = models.FloatField(default=0.5)
    price3 = models.FloatField(default=0.5)
    price4 = models.FloatField(default=0.5)
    price5 = models.FloatField(default=0.5)
    price6 = models.FloatField(default=0.5)
    price11 = models.FloatField(default=0.5)
    price12 = models.FloatField(default=0.5)
    price13 = models.FloatField(default=0.5)
    price14 = models.FloatField(default=0.5)
    total = models.FloatField(default=0)
    cost = models.FloatField(default=0)

    def __unicode__(self):
        return self.account

事情搞成这样,大家都不想的,谁能想到一开始的三个接口,搞到后来十几个呢(可能只有我没想到...)?
商务每次有新的接口谈进来,我就不想干活.

  • 写逻辑
  • 数据库里添加字段设置价格(这个表居然是横着长的,卧槽太蠢了)
  • 同步数据库(数据库加了字段要保证线上系统不停机,每次小心翼翼)
  • 数据库写入Worker里边的原生语句修改,能够读取和修改新字段(这个看上边的model看不出来,事情其实更复杂一些)
  • 重启worker(celery不好关,...ps -ef|..xargs...|kill)
  • 负载均衡调权重为0,重启服务,恢复权重.

这一套想想我就觉得麻烦,每次都觉得当初脑子一定是进水了,为什么设计成这个样子?
这也让我认识到, 初期的架构设计真的非常重要,不管是什么样的精英团队,将来都会遇到无法预料的需求,微服务,低耦合这些,只有真的体会过切肤之痛之后,才会甘之如饴.如果一开始就瞎搞,后边心态比系统先崩.
终于有一天我忍不了了.重写了一套系统,下文再谈.


RSA加密
本来这个不值得拿出来一谈,但是这一点我折腾了挺久,权当凑字数吧.
加密这块之前接触的不多,供应商那里的系统是Java,我这边是Python,RSA搞起来比较麻烦.
对方提供的公钥是pkcs8的,而python支持的是pkcs1,我这边转来转去的挺痛苦的,具体当时是怎么折腾的都忘记了.
提这个主要是想说,Rsa这个东西,python做起来还是挺慢的,在web开发过程中,这种情况还是比较少见的.如果条件允许的话,还是用C写个.so来调用更好一些.


Restful

给出去的接口如果明显不符合restful规范,挺掉价的,大家都是搞技术的,你自己写的代码别人是看不到的,但是你设计的Url和Response别人一眼就能看到,无论是uri还是参数命名,都是一个公司在技术上的门面.
有些公司管理比较到位,丢人在公司内部丢丢就完了,这个业务我自己就做主了,导致后来看到之前设计的uri都有点脸红,不提了.

重写

后来这个项目由于写的太烂,我选择了重构.没有继续使用Python,而选择了Golang来写,就当练手了,避免自己怕麻烦推进不下去.

新框架示意图

自己做了一段时间开发,虽说没人教没人带,但是也算摸索除了点方法.
这次采用了链式的请求处理,把常规操作全部交给中间件来负责,只关心业务逻辑.
有Golang的天生异步,连消息队列都省了,请求返回之后异步处理日志问题.

现在新增一个接口,数据库不需要改动,只需要新增记录即可.

INSERT INTO `USERINFO` (`用户名`,`接口ID`,`是否可用`,`价格`) 
VALUES ("user",16,1,0.5);

在经过权衡之后我并没有把更多的配置放在数据库中,比如供应商接口地址,己方定义的url等,而是写在了代码中的setting文件中.由于golang的性能优势,我也暂时没有引入缓存,鉴权操作需要操作2次数据库的Select操作读取密码和连接权限.我认为目前这不是瓶颈,没有必要增加复杂度.后期如果需要,也只需要在鉴权中间件上进行修改就好,仅仅是1个小时不到的工作量.


后记

这个项目从开发到现在大概有半年时间了,断断续续的修改,到最后的重写,其实并没有什么特别的困难,也没有什么高端的操作,但是作为第一个web项目,让我思考了很多,有不小的进步.写下这个权当警醒自己不要停止学习吧.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 版权: https://github.com/haiiiiiyun/awesome-django-cn Aweso...
    若与阅读 23,010评论 3 241
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    aimaile阅读 26,440评论 6 428
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    小迈克阅读 2,961评论 1 3
  • 2017年的第一天我们还在高中的课室里为高考奋斗,转眼间就到了2017年的最后一天。2017年算是人生的一...
    池夕阅读 200评论 0 0
  • 这是我命吗?我该信吗?为什么?为什么没有一次是幸运的?我很无奈,也很困惑、更是无力!我没有在抱怨,我只能说...
    慧_cbb6阅读 181评论 0 0