生成唯一字符串

场景

生成唯一的准考证号,为日期+六位字符(大写字母+数字),如170808ABC123,并希望生成的准考证号是不连续的

问题

这里简化为返回一个唯一的数字,之前的解决方案是:

  1. 第一次请求,返回一个固定的开始数字 + rand(1, 30),并将数字存入memcache
  2. 之后的请求,每次先用memcached->get()读取memcache中上一次的数字,然后加上rand(1, 30),作为本次生成的数字返回,并更新memcache
#伪代码#
$conn = new Memcached();
$conn->addServer("localhost", 11211);
$key = '_lock_test_long_';
$num = $conn->get($key);
if($conn->getResultCode() == Memcached::RES_NOTFOUND){
    $num = NUM_START;
} 
$num += rand(1, 30);
$conn->set($key, $num);
return $num;

这个做法的问题是,并发场景下,两个并发请求从 memcached->get() 中获取了同一个数,然后rand(1,30)也碰巧相同了,则此时两个请求返回了相同的数字,造成生成准考证号重复。

解决

至少有三种解决方法:
A. 使用Memcached::cas方法,get()的同时获取key的cas_token,然后累加随机数,最后更新memcache时使用cas(),如果更新失败,说明这个key已经在当前请求的get()后发生了更改,生成失败。

#伪代码#
$conn = new Memcached();
$conn->addServer('localhost', 11211);

$key = '_lock_test_long_';
do {
    $num =  $conn->get($key, null, $cas);
    /* 如果key不存在, 创建并进行一个原子添加*/
    if ($conn->getResultCode() == Memcached::RES_NOTFOUND) {
        $num = NUM_START;
        $conn->add($key, $num);
    } else { 
        $num += rand(1, 30);
        $conn->cas($cas, $key, $num);
    }   
} while ($conn->getResultCode() != Memcached::RES_SUCCESS);
return $num;

B. 使用Memcached::increment方法,return increment($key, rand(1, 30)),用increment函数的原子性保证并发请求得到顺序处理

#伪代码#
$conn = new Memcached();
$conn->addServer("localhost", 11211);
$key = '_lock_test_long_';
$num = $conn->increment($key,  rand(1, 30), NUM_START);
return $num;

C. 使用Redis::incr,原理同 2,并且没有key存在时,值从0开始。

最后,方法1的缺点是需要额外处理失败的情况,方法2的缺点是CI对memcached的组件封装没有increment方法(汗。。。),需要手工改框架。最后选了方法3。

    原文作者:乔四儿丶
    原文地址: https://www.jianshu.com/p/37e0f385f577
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞