背景:最近看自己以前寫(xiě)的博客。有一個(gè)有關(guān)秒殺的博客:秒殺系統(tǒng)設(shè)計(jì)思路。覺(jué)得不夠細(xì)節(jié),或者曾經(jīng)有些理想化,不夠落地、比較稚嫩。其實(shí)實(shí)際業(yè)務(wù),一定要注重:簡(jiǎn)單、實(shí)用。能不用某個(gè)中間件盡量不用,能不做的多余設(shè)計(jì)盡量不做。
真正設(shè)計(jì)一個(gè)類似的業(yè)務(wù)后端核心會(huì)如何設(shè)計(jì)。
例子:簡(jiǎn)單點(diǎn),以學(xué)校的選修課選課系統(tǒng)為例。
工具:使用redis縮短一組原子操作的時(shí)長(zhǎng)。使用mq異步解決任務(wù)多防止數(shù)據(jù)丟失。
業(yè)務(wù)場(chǎng)景:每次開(kāi)學(xué)初,整個(gè)學(xué)校的學(xué)生,會(huì)在同一時(shí)間,搶選修課。
表設(shè)計(jì):
1.課程目錄表:course
字段:課程id、學(xué)生總數(shù)、已有學(xué)生數(shù)、老師id、老師名 ...
2.課程學(xué)生關(guān)聯(lián)表:course_student
字段: 課程id(course_id)、teacher_id、學(xué)生id(student_Id)、...
需要的操作
1.查詢:
查詢課程信息:課程名、老師名、學(xué)生總數(shù)、已有學(xué)生數(shù)
select * from course
學(xué)生查詢已選課程:
select * from course_student where student_id = ?
教師查詢課程學(xué)生:
select * from course_student where teacher_id = ? and course_id= ?
2.更新:
搶課:
加鎖:鎖住courseId、再鎖住studentId
查詢課程剩余人數(shù):
select * from course where course_id = ?
if num > 0
則:insert into course_student;
update course set choose_num = choose_num + 1 where course_id = ?
釋放鎖。
問(wèn)題
主要等待時(shí)間在:鎖courseId之后更新數(shù)據(jù),這個(gè)系列操作上。
假設(shè):這一組操作,時(shí)長(zhǎng),100ms,則100個(gè)學(xué)生同時(shí)搶1個(gè)course,則總時(shí)長(zhǎng):100 * 100 = 10000ms = 10s。
可優(yōu)化的地方
鎖courseId,是肯定要鎖的,計(jì)劃從縮短更新數(shù)據(jù)操作的時(shí)長(zhǎng),從100ms,優(yōu)化到10ms,則 總時(shí)長(zhǎng):100 * 10 = 1000ms。
方案:使用redis。
課程剩余人數(shù)第一次查詢db后存入redis。
1、加鎖:分布式鎖:鎖住courseId、再鎖住studentId
2、key courseseId:leave_num
3、if leave_num is null
select * from course where course_id = ?
set courseId:leave_num ?
else
return leave_num
4、insert into course_student。(操作基本不會(huì)失敗??梢援惒讲僮鳎?br>
5、更新redis剩余人數(shù):set courseId:leave_num = leave_num - 1
6、更新db剩余人數(shù):update course set choose_num = choose_num + 1 where course_id = ?(異步操作).
7、釋放鎖。
異步操作假設(shè)用線程池做,則:1.萬(wàn)一服務(wù)中途掛了,則數(shù)據(jù)丟失就不一致了。2.線程池會(huì)排隊(duì)很多任務(wù)。
所以可以使用mq解決這個(gè)問(wèn)題。即使服務(wù)掛了,也不丟數(shù)據(jù)。排隊(duì)很多任務(wù)也不怕。