Redis最佳實踐 - 秒殺

目錄

入門

啓動Redis服務: docker run --name redis-in-action -p 6379:6379 -d redis

redis-cli

EVAL "return {KEYS[1], KEYS[2], ARGV[1], ARGV[2]}" 2 1 2 3 4
1) "1"
2) "2"
3) "3"
4) "4"
echo "return { KEYS[1], KEYS[2], ARGV[1], ARGV[2] }" >> hello.lua
redis-cli --eval hello.lua 1 2 , 3 4
1) "1"
2) "2"
3) "3"
4) "4"

關於Lua更多介紹 參考Lua簡明教程

秒殺

  • 高併發 - Redis內存

  • 原子性 - Redis單線程

vim seckilling.lua
local n = tonumber(ARGV[1])
if not n or n == 0 then
    return 0
end
local vals = redis.call("HMGET", KEYS[1], "Total", "Booked")
local total = tonumber(vals[1])
local blocked = tonumber(vals[2])
if not total or not blocked then
    return 0
end
if blocked + n <= total then
    redis.call("HINCRBY", KEYS[1], "Booked", n)
    return n;
end
return 0
redis-cli script load "$(cat seckilling.lua)" # 58652c43df5e7752b7bb1f8e65e382299b5bd8b6
redis-cli

HMSET goodsId Total 100 Booked 0

HMGET goodsId Total Booked
1) "100"
2) "1"
SCRIPT EXISTS 58652c43df5e7752b7bb1f8e65e382299b5bd8b6

EVALSHA 58652c43df5e7752b7bb1f8e65e382299b5bd8b6 1 goodsId 1 

HMGET goodsId Total Booked
1) "100"
2) "1"

關於Redis單線程模型的更多討論 可以參考爲什麼redis 是單線程的? & Redis單線程優勢 & Redis is single threaded. How can I exploit multiple CPU / cores?

隊列

  • 發佈-訂閱 - Redis隊列

  • 緩存 - 高速Redis + MySQL

vim seckilling.lua
local n = tonumber(ARGV[1])
if not n or n == 0 then
    return 0
end
local vals = redis.call("HMGET", KEYS[1], "Total", "Booked")
local total = tonumber(vals[1])
local blocked = tonumber(vals[2])
if not total or not blocked then
    return 0
end
if blocked + n <= total then
    redis.call("HINCRBY", KEYS[1], "Booked", n)
    redis.call("LPUSH", KEYS[2], "Booked "..n)
    return n;
end
return 0
redis-cli script load "$(cat seckilling.lua)" # b191ffa49361dac8f9142f0eac552cecd0df2489
redis-cli

SCRIPT EXISTS b191ffa49361dac8f9142f0eac552cecd0df2489

EVALSHA b191ffa49361dac8f9142f0eac552cecd0df2489 2 goodsId orderList 1 

HMGET goodsId Total Booked
1) "100"
2) "2"
BRPOP orderList 60
1) "orderList"
2) "Booked 1"

這裏只是將orderList的數據取出 實際項目中可以結合關係數據庫處理該訂單

服務

egg-init --type=simple server

cd server && cnpm i

cnpm i --save egg-redis
vim config/plugin.js
'use strict';

exports.redis = {
    enable: true,
    package: 'egg-redis',
};
vim config/config.default.js
'use strict';

module.exports = appInfo => {
    const config = exports = {};

    // use for cookie sign key, should change to your own and keep security
    config.keys = appInfo.name + '_1537341461009_7706';

    // add your config here
    config.middleware = [];

    config.redis = {
        client: {
            host: '127.0.0.1',
            port: 6379,
            password: '',
            db: '0',
        }
    };

    return config;
};
vim app/controller/home.js
'use strict';

const Controller = require('egg').Controller;

class HomeController extends Controller {
    async index() {
        const { ctx, app } = this;
        const code =
            `
local n = tonumber(ARGV[1])
if not n or n == 0 then
    return 0
end
local vals = redis.call("HMGET", KEYS[1], "Total", "Booked")
local total = tonumber(vals[1])
local blocked = tonumber(vals[2])
if not total or not blocked then
    return 0
end
if blocked + n <= total then
    redis.call("HINCRBY", KEYS[1], "Booked", n)
    redis.call("LPUSH", KEYS[2], "Booked "..n)
    return n;
end
return 0
        `;
        app.redis.defineCommand('seckilling', {
            lua: code
        });
        ctx.body = await app.redis.seckilling(2, 'goodsId', 'orderList', '1');
    }
}

module.exports = HomeController;

關於egg-redis的更多介紹 可以參考egg-redis & ioredis

cnpm run dev # localhost:7001

測試

redis-cli

HMSET goodsId Total 100 Booked 0

DEL orderList
sudo apt update

sudo apt install -y apache2-utils

ulimit -n 10000

ab -n 50000 -c 5000 http://192.168.56.1:7001/

關於ab的更多介紹 可以參考超實用壓力測試工具-ab工具

redis-cli

HMGET goodsId Total Booked
1) "100"
2) "100"

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章