素材牛VIP会员
php redis做mysql的缓存,怎么异步redis同步到mysql数据库?
 Al***ay  分类:PHP代码  人气:3196  回帖:25  发布于6年前 收藏

公司做抽奖或者红包活动,总有人恶意大访问量请求,查询mysql去做重复验证在大并发上限制不住,总会有重复插入,会造成多发奖品。
想用redis做mysql的缓存,但是现在遇到的问题是如何把redis的数据写回mysql,不可能每次校验的时候就写回mysql,那样的话根本没有解决问题。
现在的想法是能否利用php,或者其他什么技术,定时将redis中的数据写回mysql。程序只与redis交互。
希望能给出具体的逻辑或者解决方案,网上的回答都太笼统了,根本解决不了问题。

谢谢。

1月16日补充:
我问题描述的不太清晰。
我把遇到的问题详细的说一下吧,并不是发奖时候脏读的问题,而是判断这个人是否有机会抽奖。
有一个抽奖的表,如果这个人抽过奖了,就在表中插入一条记录。
目前的实现方式是:

1.读取抽奖表判断这个人是否抽过奖
2.如果表中有记录,那就是抽过了,直接告诉没机会
3.如果表中没有记录,走抽奖逻辑,然后插入抽奖表

正常情况下是没有问题的,
但是有人用恶意脚本进行刷奖,也就是同一个人发起大量请求,1秒可能一两百的请求甚至更多,而且不只一个人刷奖。
问题出在1这一步
举个例子,假设每人只能抽一次奖,因为请求太快,同一人的a,b两个请求几乎同时来,a走完抽奖逻辑了,并且在抽奖表中插入记录的过程时,因为mysql的性能的问题,b去走1这一步是读不到表中的记录的,因为a的插入根本没有完成。所以b请求会再走一次抽奖逻辑。造成同一人抽奖两次,然后再插入抽奖表。
我关心的是能否a插入抽奖表的瞬间,b就能判断出抽奖表有数据。
所以我觉得问题是mysql写入的不够快,读取的不够快,所以我要采用redis做一层快速缓存。
我们做的抽奖是单一奖品百分之百中奖,只限制奖品数量,所以必须保证每人只能抽一次,而且尽量在程序层面去解决。


1月20日补充
看了大家的回答很受启发,非常感谢。
至于有人提到并发没想象的那么大这件事,是这样的,我们的服务器是阿里云单台的ECS,配置4核8G 独享50m宽带,centos,只做对外活动。

这是12月24号上线的一个不到4小时活动的抽奖活动的浏览量。


这是当时TCP连接数的监控

抽奖不同于网站访问,这些参与者基本绝大多数是同一时间段来访问服务器。我个人觉得在服务器配置不升级的情况下,软件层面的优化完全能扛住这些访问。我目前觉得瓶颈在mysql。

讨论这个帖子(25)垃圾回帖将一律封号处理……

Lv3 码奴
懒***材 职业无 6年前#1

就用redis就行,在redis中存

KEY(用户唯一标识) ----> VALUE(0/1)
0: 未抽奖,1:以抽奖

只要

  1. 判断KEY(用户唯一标识)的VALUE是0还是1

  2. 是0返回,未抽奖,并赋值VALUE为1

  3. 其他返回,以抽奖

以上3步是事务操作/原子操作,管你并发量破亿还是百亿,我们都吼得住。

假使有一个人同时发起了1000个请求1,2,3,...m...,999,1000。其中请求m略快一些被服务器先处理,m在执行上述3步时,其他999个只能眼巴巴的看着,因为是原子操作 等m被处理完了,其他999各只能看到1,即已抽过奖了

那么问题来了,在redis中如何实现上述3步的原子操作那?

答案是:redis的lua脚步,redis规定:redis的lua脚本的执行是原子性的

假使初始化时,所有用户的唯一标示对应的value都是0,下面给一个lua脚本实例

local uid = KEYS[1]
local res = 1
local isDraw = redis.call('get', uid)
if isDraw == 0 then
    res = 0
    redis.call('set', uid, 1)
end
return res

这里只是一个不成熟的简单例子,可以参考 http://www.redis.cn/commands/eval.html 这里看看具体该如何写,思路就是这样

PS:一定记得做好redis的备份工作,要不断电了,就悲哀了呀!!

Lv7 码师
亡***师 JS工程师 6年前#2

可以考虑crontab跑定时任务 执行php脚本去写入 或者跑守护进程去写

Lv1 新人
in***ex 职业无 6年前#3

我觉得读写都可以在 Redis 中进行,使用 Redis 来维护这个『抽奖记录』。后台使用定时程序(最好不要用 crontab,自己使用定时器更好,有异常也可以有记录、报警),定期将 Redis 的数据备份、存储。此时,要不要入 MySQL,看具体业务上的需要了。

如果对数据可靠性要求很高,或者单表数量非常高,也可以考虑直接使用 MongoDB 来存储这个『抽奖记录』。使用 MongoDB Sharding 的话,10亿条记录以下基本问题不大。而且数据是存储于磁盘的,相比 Redis,健壮性更好。不过,如果使用 sharding 环境(其实千万条记录左右,我以为 Repl 就足够了),最好注意一下唯一性索引的使用。详情可以参考 MongoDB 的官方文档。

Lv3 码奴
xi***xu 职业无 6年前#4

调用完redis后,用fastcgi_finish_request()这个函数,后面该怎么写mysql的处理就怎么写,执行完fastcgi_finish_request()后会返回响应的,进程不会结束,会把后面的程序处理完。

Lv4 码徒
小***学 软件测试工程师 6年前#5

每秒几百个请求mysql就受不了了,我只能说要么机子配置实在是太差,要么代码写得太差

Lv2 入门
蒙***生 职业无 6年前#6

LZ的需求跟秒杀系统类似, 给个文章, 看看有没有参考价值: http://blog.jobbole.com/99463/

Lv5 码农
夏***t 移动开发工程师 6年前#7

楼主应该充分利用redis的优势哦。
阅读完,我觉得楼主的第一步就错了,把是否抽奖放在mysql来读取,这个mysql是扛不住的。用redis的原子操作就轻松搞定了。因为第一步错了,所以后面不得不用很多其他措施来弥补。

Lv3 码奴
上***水 职业无 6年前#8

我有过类似的问题。我的解决办法是:

  1. 用redis存储当前用户的session,包括ip,账号,电邮地址

  2. 在表单里面, 根据当前用户的session,生成一个form的验证码(有时间戳),以type='hidden'的形式存在。

  3. 在用户提交表单后:

    a) 先验证提交的隐藏验证码是否属于当前用户(不需要连接后台,根据已知的session来判定)
    b) 然后先搜索当前用户的session是否存在抽奖动作(不需要连接后台,根据已知的session来判定)
    c) 如果当前用户通过 a) 的验证后, 在 b)里面没有抽奖动作就证明该用户之前没有抽奖过,那么就添加一个抽奖动作为true. 如果当前用户通过 a) 的验证后,在 b) 里面有发现抽奖动作为true。就返回以抽奖页面。 也就是说c)跟b)是互相定义, 互相监督的。
    
  4. 在通过3的验证后,再开始抽奖程序。

  5. 在抽奖之后可以验证奖品存货量。(这一步可以在开始做,或者不做,看具体情况)

注意:

  1. 个人来说不赞成锁定数据库,这样会让程序变慢,对大多数用户来说不友好。毕竟会有情况真的是大量真实的抽奖请求出现,锁定数据库会让效能大大降低。

  2. 通过1,2,3的设置,可以屏蔽单IP短时间重复抽奖的问题。当然如果对方牛逼到能短时间内换IP+重复更换登陆用户+刷新页面生成不同的session,那你的程序绝对有重大逻辑错误,只能灭了重写了。

Lv2 入门
蒙***生 职业无 6年前#9

要是我 我直接在 NGINX 成面上干掉大部分流量

Lv7 码师
un***oo 职业无 6年前#10

ls的应该都说的很清楚了。
1.防攻击:redis枷锁类似防重复提交。
2.加版本号和用户id建唯一索引。不过数据量大数据库应该压力大,频发抛异常
3.将控制用户只抽一次 交给redis,这样还可以通过redis有效期控制抽奖周期。

 文明上网,理性发言!   😉 阿里云幸运券,戳我领取