Ik betwijfel of het maximaliseren van het CPU-gebruik van Redis je backend-ontwerp ten goede zal komen. De juiste vraag is eerder of Redis efficiënt genoeg is om uw doorvoer bij een bepaalde latentie te ondersteunen. Redis is een single-threaded server:bij 80% CPU-verbruik zal de latentie waarschijnlijk erg slecht zijn.
Ik stel voor dat u de latentie meet terwijl redis-benchmark werkt om te zien of dit acceptabel is voor uw behoeften voordat u probeert het CPU-verbruik van Redis te verhogen. De --latency optie van redis-cli kan hiervoor gebruikt worden:
- start redis-server
- probeer redis-cli --latency, noteer de gemiddelde waarde, stop ermee
- start in een ander venster de benchmark en zorg ervoor dat deze een tijdje draait
- probeer redis-cli --latency, noteer de gemiddelde waarde, stop ermee
- stop de benchmark
- vergelijk de twee gemiddelde waarden
Als u het CPU-verbruik van Redis nu echt wilt verhogen, heeft u ofwel een efficiënt clientprogramma nodig (zoals redis-benchmark), dat meerdere verbindingen tegelijk kan verwerken, ofwel meerdere instanties van uw clientprogramma.
Lua is een snel geïnterpreteerde taal, maar het is nog steeds een geïnterpreteerde taal. Het zal een of twee ordes van grootte langzamer zijn dan de C-code. Redis is veel sneller in het ontleden/genereren van zijn protocol dan lua-redis, dus je zult Redis niet kunnen verzadigen met een unieke Lua-client (behalve als je O(n) Redis-commando's gebruikt - zie later).
webdis is geïmplementeerd in C, met een efficiënte clientbibliotheek, maar moet de http/json-protocollen ontleden die uitgebreider en complexer zijn dan het Redis-protocol. Het verbruikt waarschijnlijk meer CPU dan Redis zelf voor de meeste bewerkingen. Dus nogmaals, u zult Redis niet verzadigen met een enkele webdis-instantie.
Hier zijn enkele voorbeelden om Redis te verzadigen met meerdere Lua-clients.
Als dit nog niet is gebeurd, raad ik u aan eerst de Redis-benchmarkpagina te bekijken.
Als u uw benchmark uitvoert op dezelfde box als Redis:
Het belangrijkste punt is om een kern toe te wijzen aan Redis en de clientprogramma's op de andere kernen uit te voeren. Op Linux kun je hiervoor het taskset-commando gebruiken.
# Start Redis on core 0
taskset -c 0 redis-server redis.conf
# Start Lua programs on the other cores
for x in `seq 1 10` ; do taskset -c 1,2,3 luajit example.lua & done
Het Lua-programma zou pipelining moeten gebruiken om de doorvoer te maximaliseren en de systeemactiviteit te verminderen.
local redis = require 'redis'
local client = redis.connect('127.0.0.1', 6379)
for i=1,1000000 do
local replies = client:pipeline(function(p)
for j=1,1000 do
local key = 'counter:'..tostring(j)
p:incrby(key,1)
end
end)
end
Op mijn systeem neemt het Lua-programma meer dan 4 keer de CPU van Redis in beslag, dus je hebt meer dan 4 cores nodig om Redis te verzadigen met deze methode (een doos met 6 cores zou goed moeten zijn).
Als u uw benchmark uitvoert op een andere box dan Redis:
Behalve als u op CPU-uitgehongerde virtuele machines draait, zal in dat geval waarschijnlijk het netwerk het knelpunt zijn. Ik denk niet dat je Redis kunt verzadigen met iets minder dan een 1 GbE-link.
Zorg ervoor dat u uw vragen zo ver mogelijk doorstuurt (zie het vorige Lua-programma) om het knelpunt van de netwerklatentie te vermijden en de kosten van netwerkonderbrekingen op de CPU (het vullen van ethernetpakketten) te verlagen. Probeer Redis uit te voeren op een kern die niet aan de netwerkkaart is gebonden (en netwerkonderbrekingen verwerkt). Je kunt tools zoals htop gebruiken om dit laatste punt te controleren.
Probeer indien mogelijk uw Lua-clients op verschillende andere machines van het netwerk uit te voeren. Nogmaals, je hebt een flink aantal Lua-clients nodig om Redis te verzadigen (6-10 zou goed moeten zijn).
In sommige gevallen is een uniek Lua-proces voldoende:
Nu is het mogelijk om Redis te verzadigen met een enkele Lua-client als elke zoekopdracht duur genoeg is. Hier is een voorbeeld:
local redis = require 'redis'
local client = redis.connect('127.0.0.1', 6379)
for i=1,1000 do
local replies = client:pipeline(function(p)
for j=1,1000 do
p:rpush("toto",i*1000+j)
end
end)
end
N = 500000
for i=1,100000 do
local replies = client:pipeline(function(p)
for j=1,10 do
p:lrange("toto",N, N+10)
end
end)
end
Dit programma vult een lijst met 1 miljoen items en gebruikt vervolgens lrange-commando's om 10 items uit het midden van de lijst te halen (in het slechtste geval voor Redis). Dus elke keer dat een query wordt uitgevoerd, worden 500K items gescand door de server. Omdat er slechts 10 items worden geretourneerd, kunnen ze snel worden geparseerd door lua-redis die geen CPU verbruikt. In deze situatie is al het CPU-verbruik aan de serverzijde.
Laatste woorden
Er zijn waarschijnlijk snellere Redis-clients dan redis-lua:
- https://github.com/agladysh/lua-hiredis (gebaseerd op hires)
- https://github.com/agladysh/ljffi-hiredis (gebaseerd op hireis, met luajit FFI)
Misschien wil je ze proberen.