http协议Mysql的高并发抢红包处理

1、Mysql 事物处理的问题


假设字段S-RedNum是红包数量,有三个客户端S-A,S-B,S-C。下面是正常的流程。


微信图片_20181230172859.png


上图是三个客户端请求不在一个时间线上,所以服务端可以分别处理,不会造成并发现象。


假设:三个客户的同时请求,这种情况是很多的。然后会造成什么情况?即使加了事物,当S-A读取完红包数走处理加钱逻辑的时候,注意这个时候事物并没有提交给数据库,所以红包数还是3,这时S-B请求过来了,由于事物的特性,S-B获取的红包数还是3。这样就造成了“脏读”,即S-B读取的数据是S-A事物还没有提交的数据。所以事物并不是解决高并发的,而是确保你的执行代码块是有效的,即要么全部成功,要么回滚到初始状态。


2、Mysql 加锁问题

这种情况应该是加一个X锁,即排它锁。它是一个有效的方法,但是并不是一个合理的方法。排它锁是在写入的时候不可读。这会造成一个问题。比方说S-A正在写入,这个时候由于是排它锁,所以S-B,S-C过来后无法读取,那么可能会返给前端说已经没有红包,其实红包还有,只是S-A在写数据,其余的请求无法获取对应的红包数而已。如果说S-B在请求的时候如果判断为没有红包而进行死循环等待,其实也是不行的。在并发数大的情况下,S-B,S-C...S-N都会进行等待,这个等待时间如何控制?要知道http是需要设置超时时间的。超过了时间还是要返回给前端。


加锁会造成的情况:S-A在写入,S-B返回无红包,S-C却是正好在S-A写完后进来的,所以S-C又可以拆的红包。S-B就会漏掉红包,但是它确实是抢红包较早的用户。


  • InnoDB的锁配合事务使用

  • MySQL有共享锁和排它锁

  • 使用共享锁时,其他线程(连接)可以查询数据,但是不能更新和删除数据,使用排它锁时,不能查询数据不能更新数据,不能删除数据

  • MySQL的InnoDB引擎支持行级锁和表级锁,行级锁

  • InnoDB的行级锁是基于索引的,加锁是对索引加锁,加锁时没有索引时会锁住整张表

  • 修改锁等待的时间:innodb_lock_wait_timeout=500 单位秒


死锁情况:

1、A用户锁住了x行,B用户锁住了n行。这个时候A、B均为提交事物,所以锁还都保持着。然后A要访问n行,由于B用户锁住了n行,所以A用户等待。这时B用户访问x行,由于A用户锁住了x行,没有提交事物,所以锁还在。B只能等待A释放x行的锁,而A又等待B释放n行的锁。所以等待。


2、间隙锁造成的死锁:

number1222566611
id135
7910111223

A:select * from t where number=6;那么间隙锁锁定的间隙为:(5,11),也就是id 10 11 12 都会被加上锁

B:select * from t where number=2;那么间隙锁锁定的间隙为:(1,5),也就是id 3 5 7 都会被加上锁

注意这个时候A、B都没有提交事务。

A:更新id为3的数据,由于B锁住了3-7所以A等待

B:更新id为10的数据,由于A锁住了10-12所以B等待


死锁造成的情况还有很多,所以写的时候又要特别注意。


查询存在死锁的事务:SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;

结束存在死锁的事务:kill trx_mysql_thread_id(线程id)




3、临界

临界可以理解为一种加锁,只有你申请到了临界牌才可以进入临界进行操作,但是这也会造成标题2的问题。


4、redis对高并发

redis提供的指令都是具有原子性的,即不会被线程调度机制所打断,也就是不会被时间切片。lpush llen 这些操作同样具备原子性,但是在业务处理中,需要的是一系列的指令集合,这些指令集合可以理解为代码块,如果将具备原子性的指令组成代码块,那么这个代码块是不具备原子性的,有被打断的可能,如果被打断,其他线程的指令又过来操作数据,那么会造成标题2中提到的加锁问题。造成的结果是一样样的。即使你用setnx这类加锁指令也是一样的。


大多数的处理:使用pop命令,即预先把红包数存入栈中,然后每次pop出来,因为这个pop是具备原子性的,所以可以有效的解决高并发抢红包问题,并且它也是合理的。




5、高并发的解决思路:队列方式

注意这里说的一种思路是队列的方式,即把用户请求排队,尝试利用php来实现队列的操作,但是发现这种操作无法保证原子性,因为php的指令绝大部分是不支持原子性的,后来想自己实现原子性函数,但是发现原子性函数的实现php是无法完成的,需要用汇编语言直接编写。汇编我只学习了8086处理器的编写方法,Linux还并没有技术积累,而且即使写了具备原子性的函数,如何和php进行沟通,这又是一个问题。所以用php或者汇编来编写具备原子性的函数的方法搁浅~~~


6、高并发的解决思路:Socket

在看了redis和memcached的使用方法,看到它们都是做了一个tcp的监听,于是发现是不是可以利用socket的特性来做高并发处理,但是php要实现常驻socket需要cli模式运行,所以大多数情况下,php不作为socket服务端,而是只作为客户端。


变通思路:利用c++语言编写php扩展,实现socket服务端监听,而php作为客户端与服务端进行通讯,好处是可以实现分布式部署。


实现:


这里我用c++语言做了一个服务端的监听,并且利用php来做请求,实际php代码和监听服务端是在同一服务器内。注意,我这里的模式是阻塞模式,最好是像redis那样利用epoll来做一个非阻塞模式的socket。


SOCKET connSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &len);
char recvBuf[14]={}; //注意对你申请的栈进行置空,不然会有乱码
recv(connSocket, recvBuf, 14, 0);
if(redPack>0)
{
   sprintf_s(sendBuf, "成功:%s", inet_ntoa(clientAddr.sin_addr));
   redPack--;
   printf("当前红包数:%d | 请求时间戳:",redPack);
}
else
{
    sprintf_s(sendBuf, "失败:%s", inet_ntoa(clientAddr.sin_addr));
    printf("红包已拆完 | 请求时间戳:");
}

然后php端

if(!socket_write($socket, $in, strlen($in))) {
    return json(['r'=>0,'m'=>'抢红包成功']);
}


执行结果,这里我并发了200个请求,对于数量较多的请求需要看你服务器的吞吐量。


微信截图_20181230194557.png


可以看到相同的时间戳下,抢红包还是正确的。并且现在已经精确到了毫秒。



socket 阻塞模式下,socket并不会同时处理多个请求,而会一个一个请求,其余多的请求会放在缓存中等待,这是socket的内部实现机制。



对待真实的请求,我觉得还是要具体问题具体分析,并不是一种方式能够解决的,socket还有很多问题,比如多个红包,你要怎么做处理,怎么和php进行协作,这就涉及到了内存操作,而内存操作实际更为复杂,因为内存里是没有变量类型这一说法的,所以你要自己实现内存的操作模块,这里就涉及到了你的存储方法,如果是链表的话,那你要完全实现一套链表的增删改查,而且是否还需要处理“原子性”问题,这些都是要考虑进去的,并且阻塞模式是非常不推荐的,所以你要实现非阻塞模式,而这种方式你试后会发现......... 。



写这篇文章和demo主要是记录查阅资料过程中学习到的知识点。不保证正确性,因为好多概念还比较懵懂。如有错误还望指出。


http协议Mysql的高并发抢红包处理


关注小程序 [上下博客] 扫码手机完整阅读

标签: 高并发, 高并发抢红包, 高并发处理

添加新评论