中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Redis--大部分人不知道的緩存擊穿與緩存設置順序的操蛋事

發布時間:2020-08-08 21:39:22 來源:網絡 閱讀:21725 作者:朱飛東 欄目:關系型數據庫

一、關于緩存設置順序:

cache,在web設計中經常用,大家也都會知道設置cache可以提高系統的響應速度,在做這個cache的時候,不知道為什么,很多人的流程都是:讀取數據時,先從cache中讀取數據,獲取不到再去DB中獲取,再將數據設置到cache中;更新數據時,先更新DB,成功之后再更新cache。在這里看似簡單并沒有什么問題,但是實際上,DB和cache理應是一個事務操作,要么同時失敗,要么同時成功。
所以,往往會有以下的錯誤:

錯誤操作1:更新DB,同時寫入cache 
eg:進程A寫了cache,此時進程B打斷了A,又寫cache,并寫了DB,再次輪到進程A繼續寫DB,
此時會導致,cache中保存的是B寫入的數據,而DB中保存了A寫入的數據,最終數據不一致,而且
這個cache一直都是臟數據,如果此時不斷有進程來讀取,都是存在的cache臟數據;同理,如果先
寫DB,在寫cache,一樣存在可能被打斷,最終導致cache是臟數據的問題

錯誤操作2,先刪除cache,再更新DB,高并發時可能出現的問題:
eg:進程A先刪除了cache,此時進程B打斷A,則從DB中讀取舊數據,并設置到了cache,再回來
進程A更新DB,那么從這里開始,接下去所有的讀請求,都是舊cache,而且一直都是臟數據

正確的做法應該是:
1、讀:先從DB讀取之后,再寫到cache中
2、更新:先更新 DB 中的數據,再刪除 cache (必須是刪除,而不是更新cache)
但是,這樣一樣不能保證不出錯
eg:A進程讀DB,B進程打斷A,進行DB的更新,刪除cache,再回來A進程寫入到cache,一樣
cache中是舊數據,而且一直是臟數據,但是,讀數據庫操作很快,寫數據庫操作比較慢,讓一個慢
的操作打斷快的相對概率比較低,所以采用這種方式,至于這里為什么是刪除cache,而不是更新
cache,那是因為,如果A進程更新DB,此時B進程更新DB,同時更新cache,A進程再回來更新
cache,將會導致cache中的是臟數據

下面用代碼來實現看看

def worker_read_type1(write_flag, user_workid):
        ''先從cache中讀,獲取不到,再從DB讀取之后,再寫到cache中''
        num = 0
        err_num = 0
        while True:
                redis_key = 't_users:'+str(user_workid)
                user_name = redis_db.get(redis_key)
                if not user_name:
                        sql = "select user_workid, user_name from t_users where user_workid={user_workid} limit 1".format(user_workid=user_workid)
                        data = mysql_extract_db.query_one_dict(sql=sql)
                        user_name = data.get('user_name', '')
                        redis_db.set(redis_key, user_name)
                num += 1
                if len(write_flag):
                        if user_name != write_flag[0]:
                                err_num += 1
                                print '出現不一致--user_name:{}---write_flag:{}, errpercent: err_num/num={}'.format(user_name, write_flag[0], str(float(err_num*100)/num)+'%')
                time.sleep(0.01)

    def worker_update_type1(write_flag, user_workid, user_name):
        "'先更新DB,然后更新cache'''
        while True:
                sql = "update t_users set user_name='{user_name}' where user_workid={user_workid} ".format(user_name=user_name, user_workid=user_workid)
                res = mysql_extract_db.execute_commit(sql=sql)
                write_flag[0] = user_name  #寫入數據庫后的值
                if res:
                        redis_key = 't_users:'+str(user_workid)
                        redis_db.set(redis_key, user_name)  #再更新cache
                time.sleep(0.01)

def worker_update_type2(write_flag, user_workid, user_name):
        ''
        先更新cache,再更新DB
        ''
        while True:
                redis_key = 't_users:'+str(user_workid)
                redis_db.set(redis_key, user_name)  #更新緩存
                sql = "update t_users set user_name='{user_name}' where user_workid={user_workid} ".format(user_name=user_name, user_workid=user_workid)
                res = mysql_extract_db.execute_commit(sql=sql)
                write_flag[0] = user_name  #寫入數據庫后的值
                time.sleep(0.01)

def worker_update_type3(write_flag, user_workid, user_name):
        ''
        先刪除cache,再更新DB
        ''
        while True:
                redis_key = 't_users:'+str(user_workid)
                redis_db.delete(redis_key)  #刪除緩存
                sql = "update t_users set user_name='{user_name}' where user_workid={user_workid} ".format(user_name=user_name, user_workid=user_workid)
                res = mysql_extract_db.execute_commit(sql=sql)
                write_flag[0] = user_name  #寫入數據庫后的值
                time.sleep(0.01)

def worker_update_type4(write_flag, user_workid, user_name):
        ''
        先更新DB,再刪除cache
        ''
        while True:
                begin = time.time()
                sql = "update t_users set user_name='{user_name}' where user_workid={user_workid} ".format(user_name=user_name, user_workid=user_workid)
                res = mysql_extract_db.execute_commit(sql=sql)
                write_flag[0] = user_name  #寫入數據庫后的值
                if res:
                        redis_key = 't_users:'+str(user_workid)
                        redis_db.delete(redis_key)  #刪除緩存
                time.sleep(0.01)

def test_check_run(read_nump=1, wri_nump=2, readfunc=None, wrifunc=None):
        "運行測試"
        write_flag = Manager().list()
        write_flag.append('1')
        for i in range(0, wri_nump):
                    p_write = Process(target=wrifunc, args=(write_flag, 2633,'RobotZhu'+str(random.randrange(1, 10000000))))
                p_write.start()
        for i in range(0, read_nump):
                p_read = Process(target=readfunc, args=(write_flag, 2633, ))
                p_read.start()
        print 'p is running'
        while True:
                pass            

#下面運行測試看看,一般來說,系統的讀請求遠遠大于寫請求,這里100個進程讀,2個進程寫
test_check_run(read_nump=100, wri_nump=2, readfunc=worker_read_type1, wrifunc=worker_update_type1)  #先更新DB,再更新cache,多進程寫有問題
出現不一致--user_name:RobotZhu2038562---write_flag:RobotZhu669457, errpercent: err_num/num=11.4285714286%  出現數據不一致的情況概率是11.5%左右
test_check_run(read_nump=100, wri_nump=2, readfunc=worker_read_type1, wrifunc=worker_update_type2)  #先更新cache,再更新DB,多進程寫有問題
出現不一致--user_name:RobotZhu4607997---write_flag:RobotZhu8633737, errpercent: err_num/num=53.8461538462% 出現數據不一致的情況概率是50%左右
test_check_run(read_nump=100, wri_nump=2, readfunc=worker_read_type1, wrifunc=worker_update_type3)  #先刪除cache,再更新DB,讀進程打斷寫進程時有嚴重問題
出現不一致--user_name:RobotZhu2034159---write_flag:RobotZhu4882794, errpercent: err_num/num=23.9436619718%  不一致概率20%多
test_check_run(read_nump=100, wri_nump=2, readfunc=worker_read_type1, wrifunc=worker_update_type4)  #先更新DB,再刪除cache,寫進程打斷讀進程是有問題
出現不一致--user_name:RobotZhu1536990---write_flag:RobotZhu1536990, errpercent: err_num/num=7.69230769231%  數據不一致概率7%左右,所以這個比較好

二、緩存“擊穿”處理:

一個設置了過期時間的cache,在它過期那一刻,大量的并發請求會直連DB,DB負載過重問題。

解決辦法:
1、當獲取數據發現為空時,說明cache過期了,此時不馬上連接DB,而是類似redis中的SETNX語
法,設置一個tempkey=1,如果這個tempkey存在,則設置失敗,不存在則設置成功, 設置成功,則
進行DB讀取數據,寫入cache,否則延時30s,再次重試讀cache,可能就有數據了。為什么這么
做?因為多進程并發的時候,第一個發現cache失效了,設置了tempkey,進行DB讀數據,其他進程
則因為無法設置tempkey而等待一會,再讀數據。
代碼示例:
def get_data(key=None):
  value = redis.get(key)
  if not value:
      #緩存失效
      if 1==redis.setnx(key+'tempkey', 1, 60):   #設置一個臨時key,如果被其他進程設置過了,則設置失敗,也就不會連接db
          value = db.query('select name from test')
          redis.set(key, value)
          redis.delete(key+'tempkey')
     else:
         time.sleep(10)
         get_data(key)  #遞歸重試,或許已經可以直接從cache中獲取了
 else:
     return value
向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

喀什市| 汤原县| 且末县| 荥阳市| 合水县| 壤塘县| 眉山市| 宁陵县| 金湖县| 湘西| 威远县| 广汉市| 揭阳市| 闽侯县| 曲阳县| 潞西市| 农安县| 台江县| 读书| 西平县| 达州市| 滦平县| 巢湖市| 六枝特区| 宁河县| 溧水县| 舒城县| 绍兴县| 禹州市| 平邑县| 双牌县| 新郑市| 义乌市| 沙田区| 德格县| 辰溪县| 阿荣旗| 叶城县| 大英县| 正蓝旗| 信宜市|