基于SpringBoot2 + Redis + MySQL实现一个抢红包系统(至尊典藏版)
?
一、需求分析
SpringBoot2 + Redis 實現(xiàn)一個搶紅包系統(tǒng)。本文分析一個具體的實現(xiàn)方案,不喜輕噴!
常見的紅包系統(tǒng),由用戶指定金額、紅包總數(shù)來完成紅包的創(chuàng)建,然后通過某個入口將紅包下發(fā)至目標用戶,用戶看到紅包后,點擊紅包,隨機獲取紅包,最后,用戶可以查看自己搶到的紅包。整個業(yè)務(wù)流程不復雜,難點在于搶紅包這個行為可能有很高的并發(fā)。所以,系統(tǒng)設(shè)計的優(yōu)化點主要關(guān)注在搶紅包這個行為上。
由于查看紅包過于簡單,所以本文不討論。那么系統(tǒng)用例就只剩下發(fā)、搶兩種。
-
發(fā)紅包:用戶設(shè)置紅包總金額、總數(shù)量
-
搶紅包:用戶從總紅包中隨機獲得一定金額
沒什么好說的,相信大家的微信紅包沒少搶,一想都明白。看起來業(yè)務(wù)很簡單,卻其實還有點小麻煩。首先,搶紅包必須保證高可用,不然用戶會很憤怒。其次,必須保證系統(tǒng)數(shù)據(jù)一致性不能超發(fā),不然搶到紅包的用戶收不到錢,用戶會很憤怒。最后一點,系統(tǒng)可能會有很高的并發(fā)。
給各位推薦一個項目分享網(wǎng)站:?
各類Java項目分享網(wǎng)站鏈接
OK,分析完直接進行詳細設(shè)計。所以簡簡單單只有兩個接口:發(fā)紅包、搶紅包。
二、表結(jié)構(gòu)設(shè)計
這里直接給出建表語句:
紅包活動表
CREATE TABLE `t_redpack_activity` (`id` bigint(20) NOT NULL COMMENT '主鍵',`total_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '總金額',`surplus_amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '剩余金額',`total` bigint(20) NOT NULL DEFAULT '0' COMMENT '紅包總數(shù)',`surplus_total` bigint(20) NOT NULL DEFAULT '0' COMMENT '紅包剩余總數(shù)',`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '用戶編號',`version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本號',PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;紅包表
CREATE TABLE `t_redpack` (`id` bigint(20) NOT NULL COMMENT '主鍵',`activity_id` bigint(20) NOT NULL DEFAULT 0 COMMENT '紅包活動ID',`amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '金額',`status` TINYINT(4) NOT NULL DEFAULT 0 COMMENT '紅包狀態(tài) 1可用 2不可用',`version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本號',PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;明細表
CREATE TABLE `t_redpack_detail` (`id` bigint(20) NOT NULL COMMENT '主鍵',`amount` decimal(10, 2) NOT NULL DEFAULT '0.00' COMMENT '金額',`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '用戶編號',`redpack_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '紅包編號',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8;活動表,就是你發(fā)了多少個紅包,并且需要維護剩余金額。明細表是用戶搶到的紅包明細。紅包表是每一個具體的紅包信息。為什么需要三個表呢?事實上如果沒有紅包表也是可以的。但我們的方案預(yù)先分配紅包需要使用一張表來記錄紅包的信息,所以設(shè)計的時候才有此表。Java項目分享
OK,分析完表結(jié)構(gòu)其實方案已經(jīng)七七八八差不多了。請接著看下面的方案,從簡單到復雜的過度。
三、流程說明
本項目提供了兩個接口:
發(fā)紅包
搶紅包
四、方案說明
4.1基于分布式鎖的實現(xiàn)
?分布式鎖搶紅包基于分布式鎖的實現(xiàn)最為簡單粗暴,整個搶紅包接口以activityId作為key進行加鎖,保證同一批紅包搶行為都是串行執(zhí)行。分布式鎖的實現(xiàn)是由spring-integration-redis工程提供,核心類是RedisLockRegistry。鎖通過Redis的lua腳本實現(xiàn),且實現(xiàn)了阻塞式本地可重入。
4.2.基于樂觀鎖的實現(xiàn)
?基于樂觀鎖的實現(xiàn) 第二種方式,為紅包活動表增加樂觀鎖版本控制,當多個線程同時更新同一活動表時,只有一個clien會成功。其它失敗的client進行循環(huán)重試,設(shè)置一個最大循環(huán)次數(shù)即可。此種方案可以實現(xiàn)并發(fā)情況下的處理,但是沖突很大。因為每次只有一個人會成功,其他client需要進行重試,即使重試也只能保證一次只有一個人成功,因此TPS很低。當設(shè)置的失敗重試次數(shù)小于發(fā)放的紅包數(shù)時,可能導致最后有人沒搶到紅包,實際上還有剩余紅包。
4.3.基于悲觀鎖的實現(xiàn)
基于悲觀鎖的實現(xiàn) 由于紅包活動表增加樂觀鎖沖突很大,所以可以考慮使用使用悲觀鎖:select * from t_redpack_activity where id = #{id} for update,注意悲觀鎖必須在事務(wù)中才能使用。此時,所有的搶紅包行為變成了串行。此種情況下,悲觀鎖的效率遠大于樂觀鎖。
4.4.基于樂觀鎖的實現(xiàn)?
?預(yù)先分配紅包,基于樂觀鎖的實現(xiàn) ,可以看到,如果我們將樂觀鎖的維度加在紅包明細上,那么沖突又會降低。因為之前紅包明細是用戶搶到后才創(chuàng)建的,那么現(xiàn)在需要預(yù)先分配紅包,即創(chuàng)建紅包活動時即生成N個紅包,通過狀態(tài)來控制可用/不可用。這樣,當多個client搶紅包時,獲取該活動下所有可用的紅包明細,隨機返回其中一條然后再去更新,更新成功則代表用戶搶到了該紅包,失敗則代表出現(xiàn)了沖突,可以循環(huán)進行重試。如此,沖突便被降低了。
4.5.基于Redis隊列的實現(xiàn)
?基于Redis隊列的實現(xiàn) ,和上一個方案類似,不過,用戶發(fā)放紅包時會創(chuàng)建相應(yīng)數(shù)量的紅包,并且加入到Redis隊列中。搶紅包時會將其彈出。Redis隊列很好的契合了我們的需求,每次彈出都不會出現(xiàn)重復的元素,用完即銷毀。缺陷:搶紅包時一旦從隊列彈出,此時系統(tǒng)崩潰,恢復后此隊列中的紅包明細信息已丟失,需要人工補償。
4.6.基于Redis隊列
?
五、QA
1、Redis掛了怎么辦?
?Redis做高可用。
2、紅包算法使用的什么?
此工程主要展示搶紅包系統(tǒng)的設(shè)計,紅包算法不是重點,所以沒有二倍均值法之類的實現(xiàn)。
當然,一個健壯的系統(tǒng)可能還要考慮到方方面面。發(fā)紅包本身如果是數(shù)據(jù)量特別大的情況要還需要做多副本方案。本文只是演示各種方案的優(yōu)缺點,僅供參考。另外,如果采用Redis則需要做高可用。
?
?
總結(jié)
以上是生活随笔為你收集整理的基于SpringBoot2 + Redis + MySQL实现一个抢红包系统(至尊典藏版)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网页轮播图制作
- 下一篇: 近似查询mysql_近似查询处理简介