使用Memcached实现抽奖活动

作者: 虞大胆的叽叽喳喳 | 来源:发表于2018-11-15 05:43 被阅读36次

上个礼拜和同事讨论了一个活动抽奖的技术问题,觉得很有意思,特此记录一下。产品需求非常简单,每天有一个奖品,准时在12点抽取,从技术角度考虑,需要满足两点:

  • 公平原则,谁的抽奖请求先到达就会抽中。
  • 只能被一个人抽到,不能导致两个以上的人抽中,显然这是最核心的技术问题。

1:Mysql 解决方案

我首先想到的就是通过 Mysql 的特性来解决该问题,先看数据表(tb)结构:

奖品ID(id) 日期(dt) 奖品状态(st)
2 20181109 0
3 20181110 1

奖品状态为 1 表示该奖品已经被抽取了,0 表示奖品还没有被抽取。

接下去我们看伪代码:

$con = mysql_connect("","","");
$sqk = "select * from tb where st = 0 and dt=" . date("Ymd");
$rs = mysql_query($sql,$con);
$data = mysql_fetch_array($rs);
$id = $data["id"] ;
if (($id!="") && ($data["st"]===0)) {
    $sql = "update tb set st=1 where st = 0 and id={$id}";
    mysql_query($sql,$con);
    $num = mysql_affected_rows($con);
    if ($num == 1) {
        echo "恭喜您,获得一个奖品";
    } else {
        echo "很遗憾,没有抽中键盘";
    }
}

简单解释下,如果奖品还存在,则执行一条 sql 语句,由于 Mysql 本身 Update 操作是原子性的,所以永远只有一个人会将 status = 0 变更为 status =1。

也就是说通过 Mysql 的原子操作保证奖品抽取公平以及确保只有一个人抽中,这种解决方案很简单,但如果同时有很多人在线抽奖,并行执行大量的 Update 操作,对数据库有比较大的压力,接下去看另外一种解决方案。

2:Memcached add 操作

Memcached 以它的高性能、高并发著称,主要用于缓存,但现在很多开发者在很多应用场景使用它,比如抽奖活动,先看伪代码:

//代表奖品是否存在的key
$key = date("Ymd");
//代表奖品是否已经被抽取的key
$sign = "s-" . date("Ymd");
$m = new Memcached();
$m->addServer('localhost', 11211);
if ($m->get($key)!="") {
    $bool = $m->add($sign,0,24*3600);
    if ($bool) {
        $m->delete($key);
        echo "恭喜您,获得一个奖品";
    } else {
        echo "很遗憾,没有抽中键盘";
    }
}

主要技术解决点就在于 Memcached add 命令具有原子性,如果 $sign 对应的 key 已经被设置了,那么第二个命令序列执行 add 操作就会失败,这样保证只会有一个人抽中,是不是非常巧妙。

3:Memcached cas 特性

其实在 Memcached 中,原子操作或者称为版本控制有专门的一个命令,那就是 cas 子命令,先看看官方的解释:

Check And Set (or Compare And Swap). An operation that stores data,

but only if no one else has updated the data since you read it last.

Useful for resolving race conditions on updating cache data.

这是一个检查然后设置的操作,当从 Memcached 中读取 key 对应的数据后,在执行 cas 操作的时候,如果你的 local 数据(从memcached读取的副本)和 Memcached 服务器端数据相同则操作成功,如果不相同则 cas 操作失败。

从内部原理来说,就是每次读取的时候,会获取数据的一个版本号(通过 memcached gets 命令获得),在执行 cas 的时候,会使用版本号对应的数据和 Memcached 服务器端进行比较,从而决定执行结果,先看伪代码:

$m = new Memcached();
$m->addServer('localhost', 11211);
$key = "g" . date("Ymd");
$cas=null;
//第三个参数是引用传递,获取版本号 token
//底层相当于执行 gets memcached 命令
$m->add($key,1);
$data = $m->get($key, null, $cas);
echo $m->getResultCode();
echo $cas . "\n";
echo $data . "\n";
//$data == 1 表示至少在抽奖请求到来的时候奖品还在
if ($data == 1) {
    $m->cas($cas,$key,"-1",24*3600);
    $code = $m->getResultCode();
    if ($code == Memcached::RES_DATA_EXISTS) {
        echo "很遗憾,没有抽中键盘";
    } else if ($code == Memcached::RES_SUCCESS) {
        echo "恭喜您,获得一个奖品";
    }
}

简单解释下,首先获取到一个 cas 版本号,php mecached 将 gets 和 get命令合并为一个函数了,然后在 cas 设置 -1 的时候(表示认为自己获取到奖品了),memcached 发现cas 版本对应的值已经被修改了,就告知客户端 Memcached::RES_DATA_EXISTS,表示该 key 对应的值已经被修改了,即奖品被别人获取了。

关于 cas 命令,可参考 http://www.w3big.com/memcached/memcached-cas.html,里面有详细的原生命令操作演示,而 php 代码做了一层封装,我写的也是伪代码,可能会运行错误,此处简单提供一个思路。

个人觉得 cas 命令在应付抽奖活动技术需求的时候比 add 命令更合适,另外本文介绍的抽奖活动需求也比较简单,如果遇到复杂的需求以及大并发的抽奖,可能会出现一些新的情况。


【这篇文章于2018-11-10号发表于公众号,地址https://mp.weixin.qq.com/s/agUU5ZjcVep-vPIKVjB6Fg,也可以关注我的公众号(ID:yudadanwx,虞大胆的叽叽喳喳)】

相关文章

  • 使用Memcached实现抽奖活动

    上个礼拜和同事讨论了一个活动抽奖的技术问题,觉得很有意思,特此记录一下。产品需求非常简单,每天有一个奖品,准时在1...

  • 简单理解memcached的内存分配

    在写完《使用Memcached实现抽奖活动》这篇文章后,发现自己虽然很早就使用过 Memcached,但已经很久没...

  • 从运维的角度理解memcached

    《使用Memcached实现抽奖活动》从应用的角度讲了讲memcached,这篇文章从运维的角色说一说,换个角度思...

  • HTML九宫格抽奖

    在很多平台上可能会涉及到抽奖活动,下面就是提供了一个抽奖的demo,实现九宫格轮转抽奖。 效果如下: 使用时可以直...

  • python实现掘金定时签到抽奖

    python实现掘金定时签到抽奖 一. 概述 这里记录一下使用 python 实现掘金定时签到抽奖。首先需要登录掘...

  • PHP核心技术与技术实践---缓存详解--Memcached

    9.1 Memcached 9.2 Memcached的安装及使用 1、Memcached特点: 1、协议简...

  • 使用 Memcached 实现 Session 共享

    使用 Memcached 实现 Session 共享 应用场景 当有很多用户的时候,这些用户的登录位置在各个不同的...

  • Ehcache缓存框架入门级使用

    前言 JAVA缓存实现方案有很多,最基本的自己使用Map去构建缓存,或者使用memcached或Redis,但是上...

  • 数量控制器

    假设我们目前在做一个抽奖活动,我们使用redis进行计数: 以下是我们的代码实现: 并发场景: 客户端A,B同时访...

  • nginx+tomcat+memcached配置session共

    背景   两台tomcat,通过nginx来实现负载均衡,使用memcached来解决session共享问题。 t...

网友评论

    本文标题:使用Memcached实现抽奖活动

    本文链接:https://www.haomeiwen.com/subject/tikffqtx.html