现在做过几个web项目,都用到了api,有服务器发往服务器的api,也有微信上从前端发往后端的api。然后,我在使用 Pycharm 断点时发现,如果在断点暂停时一直发送请求,那么很多请求都能执行成功。然后在不断点的正常情况下,我又使用 Charles 重复发送请求,并发设置为10,一共发100个,也有10几个请求成功了。
前端向后端发送请求
我知道csrf_token可以防止跨站攻击,但如果现在是一个恶意用户怎么办?我有一个每日签到API,调用之后,在数据库中添加对应记录,并增加积分。我先在的校验方法是,如果数据库中存在一个今天签到的记录,就返回错误。现在如果用户进行请求重放,在数据库写入记录之前发了N条记录,那么增加积分的操作就会执行N次了。
现在的思路是,看看微信的做法,它的参数中含有 timestamp 和 nonce,并进行了签名加密。以及 如何防止别人抓包重放攻击 的一些思路。
一、微信的做法(微信文档地址)
- 验证消息的确来自微信服务器
通过 timestamp, nonce 以及双方约定好的 token 构成一个签名,能够验证信息是否来自微信。
在这里并不适用,因为api就是由用户调用的。
二、 如何防止别人抓包重放攻击
希望一个包就实现, 不是反复发包。
一个包实现并不仅仅指第一个包实现,可以是前n个失败,然后第n+1个成功了,然后就拒绝之后的包了。如果需要达成这样,我们要使能够实现的请求唯一,构建一个类似id这样,不能重复的东西。
三、 加锁
类似双十一抢购,当一个用户下单之后,就锁定5分钟等待其付款,期间拒绝其他用户的下单请求。我们能否做到,这个请求期间,针对一个 openid 的请求只进行一次,其他就在后面排队。当一个成功后,后面类似的请求就看做是失败的。
有一个同事做过类似的加锁,明天问问他(没问,现在在写新需求以及调优,还没做这个)。
问了一个同事,建议使用 redis 的 sadd 命令,如果订单号存在于 set 中,就返回失败。还有 redis 的分布式锁,他不建议使用,如果没有弄清楚分布式锁的原理。
现在回到我们最初的问题:
如何保证用户每天只能签到一次?
获取签到请求后,通过唯一值去 redis 中查找,如果存在,则说明签到过了,返回 "今日已签到"
不存在,则说明今天还没有签到,进行签到流程。
将上面的步骤拆开:
1. 根据用户的签到请求构建唯一值
2. 去 redis 中查询唯一值
3.1 已签到,拒绝
3.2 未签到,设置 redis,进行签到流程
问:为什么要储存在 redis 而不是 mysql 中?
- 因为储存到 mysql 中太慢了,
在储存到硬盘(mysql)的过程中,这个值是不存在的,若这时候用户再次发送签到请求,那么还是可以签到的。这就违反了"用户每天只能签到一次"
问:如何将数据保存到 mysql 中?
储存到 redis 后,使用异步任务将这个数据同步到 mysql。
问:如何确保存到 redis 就足够快?
不确保。我本地测试的时候没发现能够多次签到。
储存到 redis 的速度大于两次请求的速度就能够确保。
问:锁是什么?这个与锁有什么异同?
问:如何查找相关资料?
直接搜索后,找不到很多资料。那么我们就把问题夸大或缩小。
夸大:
搜索`秒杀`
问:能否写一个函数\装饰器\上下文管理器实现这个功能?
问:有没有相关的第三方库?
服务器向服务器发送api
一般的做法是使用AES加密待发送的内容,再使用RSA加密AES秘钥,然后将以上两个一起发送出去。令我感到疑惑的是:为什么需要使用RSA加密AES秘钥并传输,一开始两边约定好一个AES秘钥不就可以了吗?
所以我现在的做法就是,两边约定好AES KEY,然后使用AES加解密内容,并对解密后的内容进行校验。
在一个抽奖功能中又遇到了库存的问题
需求:存在 N 种不同的抽奖奖品,抽奖的概率与各奖品的实时数量成正比。希望把所有的奖品发放完毕,又不希望超卖。奖品库存总量变为 0 后,固定返回一种奖品。
比如有3中商品,数量为 A:10, B: 20 C: 70。 则开始时抽中 A 的概率为 (10)/(10+20+70) = 10%,
抽中 A 后, 数量变为 A: 9, B: 20 C: 70,则 A 的概率变为 9 / (9 + 20 + 70)
应用场景:现场抽奖。
综合我的需求与实现难度,决定使用 django transaction + select_for_update
发现的问题:
select_for_update 只锁了行,能读不能写,会造成超卖;
解决
使用MySQL表锁
LOCK TABLE business_bankcard READ;
LOCK TABLE business_bankcard WRITE;
# 其他代码
UNLOCK TABLES;
上面的写法有问题
LOCK TABLES table_name WRITE;
# 其他代码
UNLOCK TABLES
因为 WRITE 的含义就是不可读、不可写
Option | Description |
---|---|
READ | Read lock, no writes allowed |
READ LOCAL | Read lock, but allow concurrent inserts |
WRITE | Exclusive write lock. No other connections can read or write to this table |
LOW_PRIORITY WRITE | Exclusive write lock, but allow new read locks on the table until we get the write lock. |
WRITE CONCURRENT | Exclusive write lock, but allow READ LOCAL locks to the table. |
出处:LOCK TABLES and UNLOCK TABLES
锁表的注意事项
注意解锁
注意异常之后的解锁
比如捕捉异常后,解锁,再抛出异常
更好的方法
同事建议使用分布式锁
redis 单机锁
参考:
测试
如何测试。设置库存数量后,使用 timesleep 模拟并发的现象
参考:
(未看,待整合)Web大规模高并发请求和抢购的解决方案
(从上面这篇文章中可以了解到,要吸收强者的经验,秒杀与抢购还有比12306和淘宝更多的吗?看看他们是怎么实现的吧!)
(规模性的东西还是大公司牛叉)
如果你关心你做某件事后产生的实际效果,在大公司绝对是有更大的实际效果,归因于大公司的规模。如果我是在一家创业公司做我当前的工作,获得的收益大约是每月 1 万美元。我没什么好蔑视,但这都支付不起我的工资。但是同样的事情在大公司创造的收益会是1万美元的1000倍以上。在大公司有更大的实际效果因为它的规模很大。这里的推论是小公司很小以至于他们很容易对自身造成影响,尽管这个影响值本身很小。我感觉不到我做的事情对大公司会产生促进还是阻碍的作用。但是当我在小公司时,看起来我们所做的事情可以影响整个公司的命运。