高并发的理解

发现问题

这几天登录服务器上线,发现注册新用户数据库有多条记录!!

跟踪分析问题

  • 查询nginx的访问日志发现有相同的请求同时请求。
  • 查看代码逻辑发现创建新用户时,先查询mysql是否有相同的用户udid。如果有,那么直接返回用户主键id。没有就插入一条数据。
  • 逻辑非常简单,也没有用上缓存。

本地重现

  • ab创建注册接口。10个用户并发,100个人次。
  • ab发现数据库出现了重复数据。概率90%以上。

上网查询解决方案

redis缓存

  • 用户访问注册接口。
  • 先访问缓存如果有用户id就直接返回用户id
  • 没有就插入redis缓存一条,然后再访问数据库查询是否存在,不存在就插入mysql,再更新缓存。

本地测试


ab创建注册接口。10个用户并发,100个人次。没有发现重复数据
直到ab 60个用户并发,200人次再次出现重复数据,复现80%左右。
没有完美解决

还是redis缓存

直接用redis做防护层,控制相同udid一秒内只能一次,其他返回失败。 本地测试


ab创建注册接口。10个用户并发,100个人次。没有发现重复数据
直到ab 60个用户并发,200人次再次出现重复数据,复现80%左右。 没有完美解决

使用文件锁或者redis锁

这个没有实现测试,思考时,我不打算用代码层做阻塞用户的操作!!!

使用队列

让注册用户的并发串行化。使用延迟插入。可以解决。
队列带来异步的问题。需要客户端配合。

mysql的唯一索引

这个可以解决但是会有报错返回对客户端不友好,不合适。

使用mysql InnoDB的悲观锁

查询时候进行行锁,然后再插入。最后提交。
ab创建注册接口。10个用户并发,100个人次。没有发现重复数据
直到ab 60个用户并发,200人次没有出现重复数据。
本地完美解决。

网上解决方案思考。

利用redis做缓存还是不能完美解决并发的问题,只能解决一部分。如果使用redis集群提高处理速度和延迟,买更好的机器。可以降低并发出问题的几率,但不完美,不可扩展。 利用mysql悲观锁可以解决但是会让mysql性能下降,并且代码逻辑不严谨有产生死锁的可能。所以mysql的悲观锁也不是一个好的解决方案。

询问其他人

游戏服务器他们的处理是用单进程单线程来解决。

根据其他人思路实现

我之前一直再测试workerman框架,打算使用它的单进程单线程来处理注册用户的逻辑。

编写代码实现了一个单进程单线程的http服务来处理注册。先查询数据库再插入的简单逻辑。
接口使用curl来请求workerman来注册。
ab后发现可以完美避免重复数据问题。带来的新问题是接口的qps从400降到70.性能下降巨大。

再次检查测试发现curl的性能消耗很大导致qps下降。要解决就不要用curl。那么,决定不使用http,使用tcp直接请求workerman。

改变workerman的协议为tcp,服务器代码使用socket来访问。 ab之后发现qps重新回到400多。压力全部在mysql。

未完成的测试,workerman的tcp服务并发的极限。

现在正在压测使用workerman的tcp服务注册接口。要几天后再看是否使用这套解决方案。

高并发的思考

高并发我的理解可以分成四类:

  • 先读后写
  • 先写后读

解决使用缓存即可。缓存可以多点部署。或者直接使用静态文件让后使用CDN来做。主要这种读不需要考虑更新可以接受比较大的延迟。

可以先写缓存,或者队列,异步存入mysql数据库。

先读后写

这次事件就是这种模式

先写后读

例子:mysql读写分离之后,写入写库后,从读库读取,获取不到数据问题,这个有一定延迟。以前解决办法就是直接读取写库解决。

解决方案思考

单纯的读或写都可以用缓存或队列解决。但是先写后读和先读后写这两种,我暂时考虑单进程单线程的模式解决。

待解决问题

单进程单线程的扩展性。还需要更深入去理解redis、mysql的高并发的性能受什么影响,如何更好的解决。





上一页  C Primer Plus阅读学习(七)

下一页  编程习惯