內(nèi)網(wǎng)單點登錄系統(tǒng)慢接口(/user/varify)優(yōu)化總結(jié)

背景

最近接到平臺研發(fā)同事反饋說inpass的/user/varify接口慢,經(jīng)過溝通后開始排查了,對方給了cat監(jiān)控的歷史,每天大概有個100~200個樣子,說是驗證超過1s,我這邊監(jiān)控平臺除了這個接口其他接口都監(jiān)控到了,并無異常,因為沒有接調(diào)用鏈,架構(gòu)部也無法給出準(zhǔn)確答復(fù),由于歷史原因接入調(diào)用鏈的時機(jī)并不成熟所以也不好說什么,自己通過access日志排查,發(fā)現(xiàn)也沒有多慢,因為這個接口是高頻接口,本能上告訴對方不會有多慢,但是對方說人家倆接口也是高頻接口,,所以我這邊也只能說繼續(xù)跟進(jìn)了。

說明

1.inpass:內(nèi)部passport的簡單實現(xiàn),登錄一個系統(tǒng)后,登錄其他系統(tǒng)不需要再次登錄;
2./user/varify:該接口的作用是驗證cookie和ticket的有效性,也就是說相當(dāng)于一個攔截器,在業(yè)務(wù)系統(tǒng)的每個http請求之后,后端的攔截器會調(diào)用這個接口進(jìn)行登錄狀態(tài)的驗證。所以調(diào)用量會非常多。

正好,最近有個inpass單設(shè)備登錄的需求,因此進(jìn)行了排查
1.首先在接口開始的地方獲取當(dāng)前時間戳,然后在返回的地方獲取當(dāng)前時間戳,然后取時間差,就是毫秒數(shù)了。有4個地方進(jìn)行了返回,因此在4個地方都打印了執(zhí)行的時間差。
2.經(jīng)過測試環(huán)境的日志打印發(fā)現(xiàn),確實有些有問題,時不時的有100 ~ 600ms之間的調(diào)用量,但是90%的情況下是10 ~ 30ms之內(nèi)返回驗證成功。

查代碼

驗證成功返回后獲取用戶信息的邏輯_20190802173819.jpg

上圖代碼中的邏輯是登錄成功之后,調(diào)用服務(wù)填充信息,并返回,然后打印執(zhí)行時間。
重點就在畫紅框的地方。
下面跟蹤分析一下代碼邏輯


開始調(diào)用遠(yuǎn)程接口獲取用戶信息_20190802174025.jpg

再進(jìn)上圖中的RPC方法里(在另外一個工程里),由于邏輯有點多,不貼代碼了,下面使用偽代碼說明一下調(diào)用情況

User user = userDB.getByid(id);

if(user != null){
    userB = user.copy();//屬性復(fù)制
    staffRedis =  staffservice.getredis();
    if(staffRedis == null){
         Staff staff = staffDB.getById(id)
         if(staff != null){
            staffB = staff.copy();
            if(staffB.deptid !=null){
                Depart depart = departservice.getredis(staffB.deptid);
                if(depart == null){
                    depart = departDB.getById(id)
                    while(depart.superid != 0){
                        depart = departDB.getById(depart.superid)
                    }
                    departB = depart.copy();
                    departB.putredis();
                }
            }
            
            User userC = userDB.getByStaff(id);
            staffB.setuser(userC.copy());
            staffB.putredis();
         }
    }else{
        Depart depart = departservice.getredis(staffB.deptid);
        if(depart == null){
            depart = departDB.getById(id)
            while(depart.superid != 0){
                depart = departDB.getById(depart.superid)
            }
            departB = depart.copy();
            departB.putredis();
        }
    }
}

當(dāng)然,通過上面代碼我們可以知道,為啥很少的情況下會出現(xiàn)100~600之間的調(diào)用返回了,同時,由于有多次屬性copy,多次redis調(diào)用,多次db調(diào)用從而導(dǎo)致接口抖動,后面再看,登錄成功或者驗證登錄狀態(tài)成功之后我們需要返回哪些信息
userId,userName,staffId,nickName,handphone,jobNumber,email,departmentId,departmentName

那么分析到這里該怎么優(yōu)化呢

  1. 重寫后端getById邏輯
  2. 提供新接口獲取以上字段數(shù)據(jù)
  3. 提供新接口獲取以上字段數(shù)據(jù)并實時緩存,保證緩存永久有效

三種思路

  1. 接口可能被別的地方調(diào)用,重寫影響穩(wěn)定性
  2. 按字段邏輯需要取三張表的數(shù)據(jù)(user,staff,depart),由于是高頻訪問接口(DB+Redis)的普通訪問邏輯同樣存在多次調(diào)用的情況,連表查+Redis的方案也不行,高頻訪問的話,總會有redis key失效的時候,由于底層DB被多個上層應(yīng)用共享,導(dǎo)致Redis key失效的情況很多,不可能讓其他服務(wù)專門清這些redis,所以也不完美
  3. 提供新接口的時候做如下操作
  • 調(diào)用RCP服務(wù)直接根據(jù)userID取Redis
  • 使用定時任務(wù)線程從User表中獲取所有在職的員工的賬號,然后取所有關(guān)聯(lián)表的staff,department表中的相關(guān)字段的數(shù)據(jù)
  • 然后根據(jù)userId遍歷Redis中的相關(guān)key,進(jìn)行更新
  1. 最后可以設(shè)置userID_redis的失效時間是2小時,定時任務(wù)的執(zhí)行頻率是30分鐘一次,即可滿足Redis數(shù)據(jù)永不失效,同時提供近實時的數(shù)據(jù)

看數(shù)據(jù)量:user:33370,staff:26437,department:2456,雖然不是很多,由于是高頻訪問(qps200左右),且其他有很多應(yīng)用系統(tǒng)也是直連這個庫,所以有必要采用第三種方案優(yōu)化

總結(jié)

由于很長時間沒有反饋說這個系統(tǒng)有問題了,畢竟這個系統(tǒng)是個只負(fù)責(zé)登錄的系統(tǒng),同時肩負(fù)著全公司近200多個業(yè)務(wù)系統(tǒng)的使用和驗證,因此非常有必要進(jìn)行排查以及代碼優(yōu)化。幸好有同事反饋,不然還不知道背后的調(diào)用過程如此繁瑣,影響接入方。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容