方案概述
應用場景
電商秒殺是一種網(wǎng)上競拍活動,通常商家會在平臺釋放少量稀缺商品,吸引大量客戶,平臺會收到平時數(shù)十倍甚至上百倍的下單請求,但是只有少數(shù)客戶可以下單成功。電商秒殺系統(tǒng)的分流過程可以分為以下幾個步驟:
- 用戶請求進入系統(tǒng):當用戶發(fā)起秒殺請求時,請求會首先進入 負載均衡 服務器。
- 負載均衡:負載均衡服務器會根據(jù)一定的算法將請求分發(fā)給后端多臺服務器,以達到負載均衡的目的。負載均衡算法可以采用輪詢、隨機、最少連接數(shù)等方式。
- 業(yè)務邏輯處理:后端服務器接收到請求后,進行業(yè)務邏輯處理,并根據(jù)請求的商品數(shù)量、用戶身份等信息進行校驗。
- 庫存扣減:如果庫存充足,后端服務器會進行庫存扣減操作,并生成訂單信息,返回給用戶秒殺成功的信息;如果庫存不足,則返回給用戶秒殺失敗的信息。
- 訂單處理:后端服務器會將訂單信息保存到 數(shù)據(jù)庫 中,并進行異步處理,例如發(fā)送 消息通知 用戶訂單狀態(tài)。
- 緩存更新:后端服務器會更新緩存中的商品庫存信息,以便處理下一次秒殺請求。
秒殺過程中多次訪問數(shù)據(jù)庫,下單通常是利用行級鎖進行訪問限制,搶到鎖才能查詢數(shù)據(jù)庫和下單。但是秒殺時的大量訂單請求,會導致數(shù)據(jù)庫訪問阻塞。
解決方案
利用分布式緩存服務(DCS)的Redis作為數(shù)據(jù)庫的緩存,客戶端訪問Redis進行庫存查詢和下單操作,具有以下優(yōu)勢:
- Redis提供很高的讀寫速度和并發(fā)性能,可以滿足電商秒殺系統(tǒng)高并發(fā)的需求。
- Redis支持主備、集群等高可用架構, 支持數(shù)據(jù)持久化,即使服務器宕機也可以恢復數(shù)據(jù)。
- Redis支持事務和原子性操作,可以保證秒殺操作的一致性和正確性。
- 利用Redis緩存商品和用戶信息,減輕數(shù)據(jù)庫的壓力,提高系統(tǒng)的性能。
本篇文檔示例中,用Redis中的hash結構表示商品信息。total表示總數(shù),booked表示下單數(shù),remain表示剩余商品數(shù)量。
“product”: {
“total”: 200
“booked”:0
“remain”:200
}
扣量時,服務器通過請求Redis獲取下單資格。Redis為單線程模型,lua可以保證多個命令的原子性。通過如下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\", \"remain\"); local booked = tonumber(vals[2]) local remain = tonumber(vals[3]) if booked <= remain then redis.call(\"HINCRBY\", KEYS[1], \"booked\", n) redis.call(\"HINCRBY\", KEYS[1], \"remain\", -n) return n; end return 0
前提條件
- 已創(chuàng)建DCS緩存實例,且狀態(tài)為“運行中”。
- 客戶端所在服務器與DCS緩存實例網(wǎng)絡互通:
- 客戶端與Redis實例所在VPC為同一VPC
- 客戶端與Redis實例所在VPC為相同region下的不同VPC
如果客戶端與Redis實例不在相同VPC中,可以通過建立VPC對等連接方式連通網(wǎng)絡,具體請參考:緩存實例是否支持跨VPC訪問?。
- 客戶端與Redis實例所在VPC不在相同region
- 公網(wǎng)訪問
客戶端公網(wǎng)訪問Redis 4.0/5.0/6.0實例時,需要開啟實例公網(wǎng)訪問開關,具體請參考開啟Redis 4.0/5.0/6.0公網(wǎng)訪問并獲取公網(wǎng)訪問地址。
- 客戶端所在服務器已安裝JDK1.8以上版本和Intellij IDEA開發(fā)工具,下載jedis客戶端(點此處下載jar包)。
本文檔下載的開發(fā)工具和客戶端僅為示例,您可以選擇其它類型的工具和客戶端。
實施步驟
- 在服務器上運行Intellij IDEA,創(chuàng)建一個MAVEN工程,為示例代碼創(chuàng)建一個SecondsKill.java文件,pom.xml文件中引用Jedis:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.2.0</version> </dependency> - 編譯并運行以下demo,該示例以Java語言實現(xiàn)。
示例中的Redis連接地址和端口需要根據(jù)實際獲取的值進行修改。
package com.huawei.demo; import java.util.ArrayList; import java.util.*; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; public class SecondsKill { private static void InitProduct(Jedis jedis) { jedis.hset("product", "total", "200"); jedis.hset("product", "booked", "0"); jedis.hset("product","remain", "200"); } private static String LoadLuaScript(Jedis jedis) { String lua = "local n = tonumber(ARGV[1])\n" + "if not n or n == 0 then\n" + "return 0\n" + "end\n" + "local vals = redis.call(\"HMGET\", KEYS[1], \"total\", \"booked\", \"remain\");\n" + "local booked = tonumber(vals[2])\n" + "local remain = tonumber(vals[3])\n" + "if booked <= remain then\n" + "redis.call(\"HINCRBY\", KEYS[1], \"booked\", n)\n" + "redis.call(\"HINCRBY\", KEYS[1], \"remain\", -n)\n" + "return n;\n" + "end\n" + "return 0"; String scriptLoad = jedis.scriptLoad(lua); return scriptLoad; } public static void main(String[] args) { JedisPoolConfig config = new JedisPoolConfig(); // 最大連接數(shù) config.setMaxTotal(30); // 最大連接空閑數(shù) config.setMaxIdle(2); // 連接Redis,Redis實例連接地址和端口需替換為實際獲取的值 JedisPool pool = new JedisPool(config, "127.0.0.1", 6379); Jedis jedis = null; try { jedis = pool.getResource(); jedis.auth("password"); //配置實例的連接密碼,免密訪問的實例無需填寫 System.out.println(jedis); // 初始化產(chǎn)品信息 InitProduct(jedis); // 存入lua腳本 String scriptLoad = LoadLuaScript(jedis); List<String> keys = new ArrayList<>(); List<String> vals = new ArrayList<>(); keys.add("product"); //下單15個 int num = 15; vals.add(String.valueOf(num)); //執(zhí)行l(wèi)ua腳本 jedis.evalsha(scriptLoad, keys, vals); System.out.println("total:"+jedis.hget("product", "total")+"\n"+"booked:"+jedis.hget("product", "booked")+"\n"+"remain:"+jedis.hget("product","remain")); } catch (Exception ex) { ex.printStackTrace(); } finally { if (jedis != null) { jedis.close(); } } } }執(zhí)行結果:
total:200 booked:15 remain:185