高并发的理解
发现问题
这几天登录服务器上线,发现注册新用户数据库有多条记录!!
跟踪分析问题
- 查询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阅读学习(七)
下一页 编程习惯