最近项目有个类似于签到的功能,功能上线后出现一位用户疯狂刷签到的问题;我们功能正常没问题,由于用户模拟了高并发的场景,所以数据库中出现了很多同时插入的数据;在来回检查代码,和数据库之后才终于解决了问题;
1.出现相同数据的原因
因为同一时间内过来的请求很多,并且这些请求间隔都是毫秒级,它们同时通过了对数据重复判断的条件,所以同时插入了很多数据
2.我的解决办法
我用了文件锁的思路:使用非阻塞的文件排他锁,用了之后继续观察了几天也没在有重复数据了,并且我升级我们接口的验证规则,防止再有利用接口重复请求的情况
$fp = fopen(PUBLIC_PATH.date('Y-m-d').'lock.txt', "w+");//使用非阻塞的文件排他锁,防止高并发插入多条数据
if(!flock($fp,LOCK_EX | LOCK_NB)){ //flock() 函数锁定或释放文件。
$this->jsonError('','系统繁忙,签到失败,请刷新后重试!');
}
//用户当天签到的数据
if($todayData['isign'] == 1){
//已签到
}else{
/**此区域有bug,高并发会同时插入很多数据**/
// 无今天数据
if($todayData == NULL){
}else{
//签到成功
flock($fp,LOCK_UN);//释放锁
}else{
$this->jsonError('','签到失败,请刷新后重试!');
}
以上是项目遇到的问题,进行的记录,接下来总结一下再遇到这样的问题解决的方法.
一般遇到类似秒杀,批量入库的操作解决方法,就是加锁和队列,锁可以学习一下悲观和乐观锁,文件锁,队列就redis的就ok;
1.悲观锁,也就是在修改数据的时候,采用锁定状态,排斥外部请求的修改。遇到加锁的状态,就必须等待。
2. 使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据
3. 对于日IP不高或者说并发数不是很大的应用,一般不用考虑这些!用一般的文件操作方法完全没有问题。但如果并发高,在我们对文件进行读写操作时,很有可能多个进程对进一文件进行操作,如果这时不对文件的访问进行相应的独占,就容易造成数据丢失
4. 直接将请求放入队列中的,采用FIFO(First Input First Output,先进先出),这样的话,我们就不会导致某些请求永远获取不到锁。