- 如果上司給一個任務,讓我們在實現微信搶紅包這個功能,我們該怎么做?
* 業務思考,實現方式千百種,不追求方法復制,只追求推導過程的思考總結
* 功能點探索
* 新建紅包:在DB、cache各新增一條記錄
* 搶紅包:請求訪問cache,剩余紅包個數大於0則可拆開紅包
* key:1,value:20 string decr原子減,每次減1 , 而decreby減指定數量N
* 拆紅包: 20個紅包里面有500塊,key:1,value:50000(以分為單位) decreby 548,decreby 1055 ,decreby 2329
* 請求訪問cache,剩余紅包個數大於0則繼續,同時獲取可搶紅包數與金額
* 計算金額(從1分到剩余平均值2倍之間隨機數,如果不是最后一個紅包,剩余金額預留最少1分給cas更新失敗,最后一位拿紅包的人)
* cas更新數據庫(更新紅包計數表記錄【剩余紅包個數、剩余紅包金額】、插入領取記錄)
* 查看紅包記錄:用戶進來直接查DB即可
1微信紅包數據庫表設計
- 紅包流水表
CREATE TABLE `red_packet_info` ( `id` int(11) NOT NULL AUTO_INCREMENT, `red_packet_id` bigint(11) NOT NULL DEFAULT 0 COMMENT '紅包id,采用timestamp+5位隨機數', `total_amount` int(11) NOT NULL DEFAULT 0 COMMENT '紅包總金額,單位分', `total_packet` int(11) NOT NULL DEFAULT 0 COMMENT '紅包總個數', `remaining_amount` int(11) NOT NULL DEFAULT 0 COMMENT '剩余紅包金額,單位分', `remaining_packet` int(11) NOT NULL DEFAULT 0 COMMENT '剩余紅包個數', `uid` int(20) NOT NULL DEFAULT 0 COMMENT '新建紅包用戶的用戶標識', `create_time` timestamp COMMENT '創建時間', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
PRIMARY KEY (`id`) )
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='紅包信息表,新建一個紅包插入一條記錄';
- 紅包記錄表
CREATE TABLE `red_packet_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `amount` int(11) NOT NULL DEFAULT '0' COMMENT '搶到紅包的金額', `nick_name` varchar(32) NOT NULL DEFAULT '0' COMMENT '搶到紅包的用戶的用戶名', `img_url` varchar(255) NOT NULL DEFAULT '0' COMMENT '搶到紅包的用戶的頭像', `uid` int(20) NOT NULL DEFAULT '0' COMMENT '搶到紅包用戶的用戶標識', `red_packet_id` bigint(11) NOT NULL DEFAULT '0' COMMENT '紅包id,采用timestamp+5位隨機數', `create_time` timestamp COMMENT '創建時間', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
PRIMARY KEY (`id`) )
ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='搶紅包記錄表,搶一個紅包插入一條記錄';
- 將庫表導入數據庫
- 通過mybatis generator生成代碼
2發紅包接口實現
- 發紅包功能接口開發
* 新增一條紅包記錄
* 往mysql里面添加一條紅包記錄
* 往redis里面添加一條紅包數量記錄 decr
* 往redis里面添加一條紅包金額記錄 decreby
- 搶紅包功能接口開發
* 搶紅包功能屬於原子減操作
* 當大小小於0時原子減失敗
- 當紅包個數為0時后面進來的用戶全部搶紅包失敗,並不會進入拆紅包環節
- 搶紅包功能擴展設計
* 將紅包ID的請求放入請求隊列中,如果發現超過紅包的個數,直接返回
* 類推出token令牌和秒殺設計原理
- 注意點
* 搶到紅包不到能拆成功
* 2014年的紅包一點開就知道金額,分兩次操作,先搶到金額,然后再轉賬。
2015年后的紅包的拆和搶是分離的,需要點兩次,因此會出現搶到紅包了,但點開后告知紅包已經被領完的狀況。進入到第一個頁面不代表搶到,只表示當時紅包還有。
3 搶紅包接口實現
- 搶紅包功能接口開發
* 在搶紅包這里並不能保證用戶已經能領到這個紅包
- 搶紅包只是做了一個判斷,判斷當前是否還有紅包
- 有紅包則返回可以領
- 沒紅包則返回不可以領
- 拆紅包功能接口開發
* 拆紅包才是用戶能領到紅包
* 這時候要先減redis里面的金額和紅包數量
* 減完金額再入庫
4微信紅包設計算法分析
- 玩法:微信金額是拆的時候實時算出來,不是預先分配的,采用的是純內存計算,不需要預算空間存儲
- 分配:
* *發100塊錢,總共10個紅包,那么平均值是10塊錢一個,那么發出來的紅包的額度在0.01元~20元之間波動*
* 當前面4個紅包總共被領了30塊錢時,剩下70塊錢,總共6個紅包,那么這7個紅包的額度在:0.01~(70➗6✖️2)=23.33之間波動(紅包金額/個數*2)
* 這樣算下去,可能會超過最開始的全部金額,因此到了最后面如果不夠這么算,那么會采取如下算法:保證剩余用戶能拿到最低1分錢即可
- 存儲:數據庫會累加已經領取的個數與金額,插入一條領取記錄。入賬則是后台異步操作
- 轉賬:通過財付通往紅包所得者賬戶轉賬,過程通過是異步操作
搶紅包項目總結
- take all操作
- 入庫轉賬時需要保證紅包個數和紅包剩余金額正確
- 高並發處理:紅包如何計算被搶完?
- cache會抵抗無效請求,將無效的請求過濾掉,實際進入到后台的量不大。cache記錄紅包個數,原子操作進行個數遞減,到0表示被搶光
- 性能擴展
* 多主sharding,水平擴展機器
* 數據庫層面sharding分片
* redis層面sharding分片技術
- 業務能動性,從發展的角度來看待業務
- 觀察總結,技術賦能業務