一个基于Django的API开发记录

2018-03-14  本文已影响0人  一斤蔬菜

需求

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


示意图

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


架构设计

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

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

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

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


选用的工具及理由


遇到的问题

网络请求效率低下

前期我采用了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

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

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


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项目,让我思考了很多,有不小的进步.写下这个权当警醒自己不要停止学习吧.

上一篇 下一篇

猜你喜欢

热点阅读