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