现在有个发放激活码的系统,php+mysql。 code表: id(主键自增长),code(激活码内容,如123abc),status(1代表未被发放,0代表已被发放)。
现在有很多用户(注册用户,能拿到用户信息)去抽这些激活码,每人每天只能抽1次,这一次是肯定能抽到的。
每当用户抽一次激活码,就找到一行status为1的激活码记录,把这行记录的status置为0,同时在record表里添加一行记录(用户id和激活码id),然后返回激活码内容给用户。
逻辑挺简单,现在我要解决的问题是,就是高并发情况下可能会有问题:
比如很多用户同时抽激活码,我select一行status为1的激活码记录时,可能多个用户会select到同一个激活码。有没有什么办法当某个请求select一行记录时,就把这行记录锁住,包证其他请求不会拿到这个激活码。
希望各位大神帮我分析下我这种逻辑在高并发下可能会出现什么问题,有什么解决方案?因为我现在做的项目实际上会遇到高并发的情况,所以必须考虑进去。。。
既然id是自增长,而且激活码看起来也是先生成好的,说明激活码应该是会按照顺序一条一条被是用掉。那么, 有个想法可以尝试一下:
在内存中保留一个变量MAX_ID来记录当前可用的激活码的id(select min(id) + 1 from XXX where status=0,注意同步/锁)。 如果有用户过来请求激活码,具体流程如下:
但是要额外考虑几个问题:
######最简单的方法是加锁。如果对性能要求很高,就借助redis即可######php不知道,java我是缓存在线程安全的queue中,直接拿一个,然后update回数据库######这个有两个限制:1、激活码量不多 2、单机环境######缓存有了解吗?######嘿,简单点在事务里 select 出一条status为1的,然后update xxx set status=0 where id=id and status=1;如果更新成功就ok,失败【说明被别人先拿了】就回滚然后重新开始,select update 试个几次就行。这个我经常用,在多个人需要对同一行操作的情况下稍微比下面的优应为是到update才锁住行。注意控制重试次数。
另外一种也是事务里,select 出一条status为1的,然后select * from xxx where id=123 for update,然后判断返回的status是否为1,不是就回滚事务重新试。在这种场景下应该可以采用,和上面一样要控制好重试的次数,会锁住一行记录,这在产品库存扣减之类的场景就不合适。
最后一种,咳咳咳,是你这个场景的,recover表的激活码id设置成唯一索引,然后在事务里插入失败的话就回滚。会产生间隙锁和nextkey锁导致阻塞。
另外的解决方案,是。。。建一个自增表,表就字段id,自增的,code varchar(大小您看着办),uid。 每次抽就往这里插入一行记录code为空就好。 然后拿到自增id,然后 $code_num = $id^COVER_NUM;# COVER_NUM是一个大数常量,最好是类常量。 #如果有gmp拓展,并且php版本大于5.3,且gpm_strval支持到62位的话 $code = gmp_strval(gmp_init($code_num),62); #如果没有gmp或gmp只能到36位的话 $code = base_covert($code_num,10,36); 然后把code update 回去,没有锁竞争,但不好的地方是code不能事先生成,如果你要事先生成也可已,拿到id的时候不去生成code而是去一个表里取出事先生成的id值相等的code就可以。
版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。