業務場景:
任務體系越來越受到活動的青睞,例如最近的淘寶和京東雙十一活動,都是採用任務類型的活動,我司近幾個月的活動也多采用類似的任務體系,分享、關注、點贊、投幣、投稿等,既然是活動,就限制了活動的範圍是活動稿件或指定稿件上的點贊/投幣行爲,因此Job監聽用戶點贊、投幣時,需要查詢點贊或者投幣的是否是活動稿件。
在數據庫中,稿件業務方有單獨的表,但是如果是活動稿件,也會進入活動單獨的活動稿件表,因此,每當有一條點贊/投幣的消息到來時,需要判斷該條信息的稿件id是否屬於活動稿件,就需要查詢活動稿件表。當數據量不多時,直接查db是沒有問題的,但是數據量大的時候,很容易把數據庫打爆,然後限流。。。
原始查詢:
"SELECT `id` From likes WHERE sid = ? and wid = ? and `state` != -1"
優化一:既然直接查數據庫不可以,可以改走內存哇。先把該活動下的稿件拉出來,每幾分鐘拉一次,放在一個協程裏,然後設置一個內存變量map,稿件id爲map的鍵,值爲空結構體就好struct{}{}。每次來消息時候,就判斷是否存在即可:
if _, ok := s.XXmap[m.ID]; ok {
xxx
}
sql查詢如下:
SELECT wid FROM xxx WHERE state=1 AND sid=? ORDER BY type
思考:
走內存是沒問題的,但是這麼查,如果稿件量特別大,還是容易出問題,因此,考慮分段查詢,使用limit
優化二:
sql查詢如下:
SELECT wid FROM xxx WHERE state= 1 AND sid = ? ORDER BY id LIMIT ?,?
這樣可以先查詢一個總量,然後使用for循環,分批次100個100個的查,上面的sql填充limit offset,limit
for i := 0; i < count; i += 100 {
//查詢
}
思考:
limit ?,?會有深度分頁問題,因儘量少這麼使用,因此考慮更好的分批次查詢方式。
優化三:
SELECT id,wid FROM xxx WHERE sid=? and `state`!=-1 and id > ? ORDER BY id ASC LIMIT ?
如上,同樣是使用limit,但是在前面,加了一個id > ?的判斷,不加這個判斷依舊是查所有的數據,加了之後,第一次傳0,第二次傳第一次查出來的最後一個id,這樣就能比較好的分批次查詢了。整個協程代碼如下:
func (s *Service) loadXXXArc(sid int64) (data map[int64]struct{}, err error) {
var (
i int64
tmp = make(map[int64]struct{})
res []*l.XXX
)
for {
time.Sleep(100 * time.Millisecond)
res, err = s.loadList(context.Background(), sid, i, _objectPieceSize, _retryTimes) //封裝的查詢方法,i對應sql中的id>?,_objectPieceSize常量對應limit?
if err != nil {
log.Error("loadXXXArc Err %v", err)
break
}
if len(res) == 0 {
log.Warn("loadXXXArc load data finish")
break
}
for key, val := range res {
tmp[val.Wid] = struct{}{}
if key == len(res)-1 {
i = val.ID //取上一次查詢的最後一個id賦值
}
}
}
data = tmp
return
}
func (s *Service) loadArc() {
data, err := s.loadXXXArc(s.c.Eleven.ElevenSid)
if err != nil {
return
}
s.Arc = data
}
func (s *Service) loadArcproc() {
for {
time.Sleep(5 * time.Minute)
s.loadArc()
}
}
//最後,在service層的New()中調用即可
s.loadArc()
go s.loadArcproc()