redis性能優化(pipeline/lua)

redis優化

最近在作S線的業務中,須要計算用戶的排名以及不少雜項數據。因爲數據量過多,爲了保證系統響應速度和負載能力,因此在Redis中產生了緩存(基於天天)。php

pipe line

介紹

Redis的pipeline(管道)功能在命令行中沒有,但redis是支持pipeline的,並且在各個語言版的client中都有相應的實現。 因爲網絡開銷延遲,就算redis server端有很強的處理能力,也會因爲收到的client消息少,而形成吞吐量小。當client 使用pipelining 發送命令時,redis server必須將部分請求放到隊列中(使用內存),執行完畢後一次性發送結果;若是發送的命令不少的話,建議對返回的結果加標籤,固然這也會增長使用的內存。html

Pipeline在某些場景下很是有用,好比有多個command須要被「及時的」提交,並且他們對相應結果沒有互相依賴,並且對結果響應也無需當即得到,那麼pipeline就能夠充當這種「批處理」的工具;並且在必定程度上,能夠較大的提高性能,性能提高的緣由主要是TCP連接中較少了「交互往返」的時間。redis

語法(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.dom

由此能夠看出,官方仍是支持你們儘可能使用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;
相關文章
相關標籤/搜索