redis优化
最近在做S线的业务中,需要计算用户的排名以及很多杂项数据。由于数据量过多,为了保证系统响应速度和负载能力,所以在Redis中产生了缓存(基于每天)。
pipe line
介绍
Redis的pipeline(管道)功能在命令行中没有,但redis是支持pipeline的,而且在各个语言版的client中都有相应的实现。 由于网络开销延迟,就算redis server端有很强的处理能力,也会由于收到的client消息少,而造成吞吐量小。当client 使用pipelining 发送命令时,redis server必须将部分请求放到队列中(使用内存),执行完毕后一次性发送结果;如果发送的命令很多的话,建议对返回的结果加标签,当然这也会增加使用的内存。
Pipeline在某些场景下非常有用,比如有多个command需要被“及时的”提交,而且他们对相应结果没有互相依赖,而且对结果响应也无需立即获得,那么pipeline就可以充当这种“批处理”的工具;而且在一定程度上,可以较大的提升性能,性能提升的原因主要是TCP链接中较少了“交互往返”的时间。
语法(php)
$redis = new Redis();
$redis->connect('host','port');
$redis->pipeline();
//do any more
$redis->exec();
$redis-close();
redis lua
介绍
Redis内置了对LUA脚本的支持,并且在计算过程中保证了脚本中执行的原子性。因此在开发过程中对Redis对Lua的支持进行了学习。从 Redis 2.6.0 版本开始,通过内置的 Lua 解释器,可以使用EVAL命令对 Lua 脚本进行求值。以下将Redis对LUA的支持进行总结。
官网文档上有这样一段话(官方文档):
A Redis script is transactional by definition, so everything you can do with a Redis transaction, you can also do with a script, and usually the script will be both simpler and faster.
由此可以看出,官方还是支持大家尽量使用lua script来代替transaction的。
关于更多设计细节,可以参考Redis 设计与实现。
lua语法(php)
$redis = new Redis();
$redis->connect('host','port');
$script = "
local result ={}
for i = 1,#(KEYS) do
result[i]= redis.call('get',KEYS[i]) //do any more
end
return result
";
$result = $redis->eval($script, $keys, count($keys));
性能测试代码
基础测试(get string key)
$host = '';
$port = '';
$keys = ['test1','test2','test3','test4','test5','tese6','test7'];
$redis = new Redis();
$redis->connect($host, $port);
foreach($keys as $key) {
$random = rand(1,100);
$value = '';
for ($i=0; $i < $random; $i++) {
$value .= $key;
}
$redis->set($key, $value);
}
$redis->close();
//定义的lua脚本
$script = "local result ={}
for i = 1,#(KEYS) do
result[i]= redis.call('get',KEYS[i])
end
return result";
for($i =0 ; $i < 100; $i ++ ) {
$start = microtime(true);
$redis = new Redis();
$redis->connect($host, $port);
foreach($keys as $key) {
$redis->get($key);
}
$redis->close();
$foreachTime[$i] = ( microtime(true) - $start)*1000;
file_put_contents('redis_time.txt','foreach:'.$foreachTime[$i], 8);
file_put_contents('redis_time.txt', "\n", 8);
$start = microtime(true);
$redis = new Redis();
$redis->connect($host, $port);
$redis->pipeline();
foreach($keys as $key) {
$redis->get($key);
}
$redis->exec();
$redis->close();
$pipeTime[$i] = ( microtime(true) - $start)*1000;
file_put_contents('redis_time.txt','pipeline:'.$pipeTime[$i], 8);
file_put_contents('redis_time.txt', "\n", 8);
$start = microtime(true);
$redis = new Redis();
$redis->connect($host, $port);
$redis->eval($script, $keys, count($keys));
$redis->close();
$evalTime[$i] = ( microtime(true) - $start)*1000;
file_put_contents('redis_time.txt','eval:'.$evalTime[$i], 8);
file_put_contents('redis_time.txt', "\n", 8);
echo $foreachTime[$i];
echo PHP_EOL;
echo $pipeTime[$i];
echo PHP_EOL;
echo $evalTime[$i];
echo PHP_EOL;
}
file_put_contents('redis_time.txt', "\n", 8);
file_put_contents('redis_time.txt', "\n", 8);
file_put_contents('redis_time.txt', "\n", 8);
file_put_contents('redis_time.txt','foreach avg: '.( array_sum($foreachTime)/100), 8);
file_put_contents('redis_time.txt', "\n", 8);
file_put_contents('redis_time.txt','pipe avg: '.( array_sum($pipeTime)/100), 8);
file_put_contents('redis_time.txt', "\n", 8);
file_put_contents('redis_time.txt','eval avg: '.( array_sum($evalTime)/100), 8);
file_put_contents('redis_time.txt', "\n", 8);
测试结果(ms)
foreach avg: 191.03681325912
pipe avg: 53.837163448334
eval avg: 54.453134536743
从结果来看,lua和pipeline的性能提升差不多,但是相对而言,lua更加灵活(可写简单业务)。 同样,由于redis lua和 redis pipeline的耗时性,由于redis原子性的要求,导致同一时间只能执行一个命令,因此,单个pipeline/lua不建议太大,导致系统被占用,从而引起其他服务无法正常进行。
包含逻辑测试(假定需求,多个用户排行榜)
构建数据
$host = '';
$port = '';
$redis = new Redis();
$redis->connect($host, $port);
$rankTypes = [1,3,4,5,6,9,14];
for($i = 0; $i< 1000; $i++){
$rand = array_rand($rankTypes);
$key = 'user:test:'.$i;
$redis->hset($key,'rank_type',$rankTypes[$rand]);
$redis->hset($key,'rank',$i);
$redis->hset($key,'score', $i);
$redis->hset($key,'blog_read_score', $i);
$redis->hset($key,'blog_read_rank', $i);
$redis->hset($key,'interact_score', $i);
$redis->hset($key,'interact_rank', $i);
$redis->hset($key,'close_score',$i);
$redis->hset($key,'close_rank',$i);
$redis->hset($key,'mention_score',$i);
$redis->hset($key,'mention_rank',$i);
$key = 'rank_key:'.$rankTypes[$rand];
$redis->lpush($key, $i);
}
普通查询
$key_format = 'rank_key:%d';
$redis = new Redis();
$redis->connect($host,$port);
$user = [];
$start = microtime(true);
foreach ($rankTypes as $type) {
$key = sprintf($key_format, $type);
$top50 = $redis->lrange($key, 0, 50);
foreach ($top50 as $uid) {
$key = 'user:test:'.$uid;
$user[$type][$uid] = $redis->hGetAll($key);
}
}
$redis->close();
echo ( microtime(true) - $start)*1000; //耗时2S~3S+
echo PHP_EOL;
pipeline查询(需要两条,因为需求限定需要上次的查询结果)
$key_format = 'rank_key:%d';
$start = microtime(true);
$redis = new Redis();
$redis->connect($host,$port);
$redis->pipeline();
foreach ($rankTypes as $type) {
$key = sprintf($key_format, $type);
$redis->lrange($key, 0, 49);
}
$top50 = $redis->exec();
$redis->pipeline();
foreach ($rankTypes as $key=>$type) {
foreach ($top50[$key] as $uid) {
$key = 'user:test:'.$uid;
$redis->hGetAll($key);
}
}
$users = $redis->exec();
$redis->close();
$result = [];
$i = 0;
foreach ($users as $key=>$user) {
if($key != 0 && $key%50 == 0) {
$i++;
continue;
}
$result[$rankTypes[$i]][] = $user;
# code...
}
echo ( microtime(true) - $start)*1000; //耗时100ms~400ms
echo PHP_EOL;
lua脚本查询
$script = "local users = {}
for i = 1,#(KEYS) do
local key = 'rank_key:'..KEYS[i]
users[i] = redis.call('lrange',key,0,49)
end
local result ={}
for j = 1,#(KEYS) do
local user_table = {}
for k=1,50,1 do
local user_key = 'user:test:'..users[j][k]
user_table[k] = redis.call('hgetall', user_key)
end
result[j] = user_table
end
return result";
$start = microtime(true);
$redis = new Redis();
$redis->connect($host,$port);
$result = $redis->eval($script, $rankTypes, count($rankTypes));
$redis->close();
echo ( microtime(true) - $start)*1000; //耗时100ms~200ms
echo PHP_EOL;