又要搬家咯

7.2号就得搬家了,东西还没收拾,等到了周末再说吧,反正也就只剩下一天了。

今天晚上不知道咋了,很兴奋。一个箭步蹿到沙发上,爪子抓了大姐的头,结果被逮住一顿“毒打”,打完之后就这样了。思考猫生,谁都不让摸。

排面

lua是个好东西,今天看老钱关于lua脚本在Redis中可以被原子性执行的介绍后,特意去看了看文档。

Redis uses the same Lua interpreter to run all the commands. Also Redis guarantees that a script is executed in an atomic way: no other script or Redis command will be executed while a script is being executed. This semantic is similar to the one of MULTI / EXEC. From the point of view of all the other clients the effects of a script are either still not visible or already completed.

所以lua脚本被Redis内嵌的lua引擎执行的时候,不会出现并发问题,也就保证了lua脚本本身作为原子性执行的环境。联想到之前我设计的那个红包Timeline清理,突然想到了不错的拓展方法。可以避免现在为了达到一个事件有且仅能被执行一次而设计的单个消费者模型。如果借助lua脚本,内部封装zrevrange+zrem。搞成类似zpop这种的,就可以同时运行多个消费者,这样即便是今后用户多了,红包清理也不会受到影响。

<?php
$redis = new Redis();
$redis->pconnect("localhost", 44444);

$luascript = <<<SCRIPT
    local item = '';
    item = redis.call("zrevrange", KEYS[1], ARGV[1], ARGV[2], ARGV[3])
    redis.call("zrem", KEYS[1], item[1])
    return item[2]
SCRIPT;
$ret = $redis->eval($luascript, ["zset", "1", "1", "withscores"], 1);
var_dump($ret);

哈哈,还真的蛮好使。

从这个角度看LUA在这块潜力很大诶,有个念头,找时间好好学学这个脚本语言。回头可以的话,对公司现有的phpredis包装一下,lua脚本,就可以应对更多的场景了。。

此属于更上层的行为,交给底层redis-server去执行,所以移植性肯定也还行。


这个念头绕着我好几天了,趁热打铁来verify下这样是不是可行的。

root@Server218 /h/d/g/lua# cat consume.php
<?php
$redis = new Redis();
$redis->pconnect("localhost", 44444);
$ret = $redis->zrevrangebyscore("zset", 1, 0, ["withscores"=>true]);
var_dump($ret);
foreach($ret as $key=>$score) {
    usleep(1000);
    $tmp = $redis->zrem("zset", $key);
    echo "deleted key:{$key}, ret: {$tmp}.\n";
}
root@Server218 /h/d/g/lua# cat for-consume.sh
#!/usr/bin bash
sleep 2
echo "---------------consume.php 1:----------------"
php consume.php
echo "---------------consume.php 2:----------------"
php consume.php
root@Server218 /h/d/g/lua# cat zpop.php
<?php
$redis = new Redis();
$redis->pconnect("localhost", 44444);

$luascript = <<<SCRIPT
    local item = '';
    item = redis.call("zrevrangebyscore", KEYS[1], ARGV[1], ARGV[2], ARGV[3])
    redis.call("zrem", KEYS[1], item[1])
    return item
SCRIPT;
$ret = $redis->eval($luascript, ["zset", "1", "0", "withscores"], 1);
sleep(1);
var_dump($ret);
root@Server218 /h/d/g/lua# cat for-zpop.sh
#!/usr/bin bash
sleep 2
echo "---------------zpop.php 1:-------------------"
php zpop.php
echo "---------------zpop.php 2:-------------------"
php zpop.php
root@Server218 /h/d/g/lua# cat paddingredis.sh
#!/usr/bin bash
redis-cli -p 44444 del zset
redis-cli -p 44444 zadd zset 0 0 1 1 2 2 3 3

下面就是模拟下并发,看看多个消费者的运行状况。对于for-consume.phpfor-zpop.php 跑起来的时间越接近越好。

首先先把Redis中内容padding好,作为生产者角色。

root@Server218 /h/d/g/lua# bash paddingredis.sh
(integer) 1
(integer) 4

接下来是消费者。

root@Server218 /h/d/g/lua# bash for-zpop.sh
---------------zpop.php 1:-------------------
array(4) {
  [0]=>
  string(1) "1"
  [1]=>
  string(1) "1"
  [2]=>
  string(1) "0"
  [3]=>
  string(1) "0"
}
---------------zpop.php 2:-------------------
array(2) {
  [0]=>
  string(1) "0"
  [1]=>
  string(1) "0"
}

“同一时间”,启动另一个消费者。

root@Server218 /h/d/g/lua# bash for-consume.sh
---------------consume.php 1:----------------
array(2) {
  [1]=>
  float(1)
  [0]=>
  float(0)
}
deleted key:1, ret: 0.
deleted key:0, ret: 0.
---------------consume.php 2:----------------
array(0) {
}

从运行状态来看,这里的sleep 设置的稍微有点大,实际生产环境中也不会有这么大的延迟,如果把sleep时间调小一点,可以发现,这俩消费者不定哪个更快。但是为了安全起见,原子性方式在这种场景下是更为合适的,即便是业务拓展开,也不会导致重复消费问题。

当然,这里还只是个模型,要想好好利用这个特性,还得做好封装。

展开阅读全文

没有更多推荐了,返回首页