最近2018中文字幕在日韩欧美国产成人片_国产日韩精品一区二区在线_在线观看成年美女黄网色视频_国产精品一区三区五区_国产精彩刺激乱对白_看黄色黄大色黄片免费_人人超碰自拍cao_国产高清av在线_亚洲精品电影av_日韩美女尤物视频网站

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線(xiàn)溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
Redis--大部分人不知道的緩存擊穿與緩存設(shè)置順序的操蛋事

一、關(guān)于緩存設(shè)置順序:

cache,在web設(shè)計(jì)中經(jīng)常用,大家也都會(huì)知道設(shè)置cache可以提高系統(tǒng)的響應(yīng)速度,在做這個(gè)cache的時(shí)候,不知道為什么,很多人的流程都是:讀取數(shù)據(jù)時(shí),先從cache中讀取數(shù)據(jù),獲取不到再去DB中獲取,再將數(shù)據(jù)設(shè)置到cache中;更新數(shù)據(jù)時(shí),先更新DB,成功之后再更新cache。在這里看似簡(jiǎn)單并沒(méi)有什么問(wèn)題,但是實(shí)際上,DB和cache理應(yīng)是一個(gè)事務(wù)操作,要么同時(shí)失敗,要么同時(shí)成功。
所以,往往會(huì)有以下的錯(cuò)誤:

在花溪等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專(zhuān)注、極致的服務(wù)理念,為客戶(hù)提供成都做網(wǎng)站、網(wǎng)站建設(shè) 網(wǎng)站設(shè)計(jì)制作定制制作,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),高端網(wǎng)站設(shè)計(jì),全網(wǎng)整合營(yíng)銷(xiāo)推廣,成都外貿(mào)網(wǎng)站建設(shè)公司,花溪網(wǎng)站建設(shè)費(fèi)用合理。

錯(cuò)誤操作1:更新DB,同時(shí)寫(xiě)入cache 
eg:進(jìn)程A寫(xiě)了cache,此時(shí)進(jìn)程B打斷了A,又寫(xiě)cache,并寫(xiě)了DB,再次輪到進(jìn)程A繼續(xù)寫(xiě)DB,
此時(shí)會(huì)導(dǎo)致,cache中保存的是B寫(xiě)入的數(shù)據(jù),而DB中保存了A寫(xiě)入的數(shù)據(jù),最終數(shù)據(jù)不一致,而且
這個(gè)cache一直都是臟數(shù)據(jù),如果此時(shí)不斷有進(jìn)程來(lái)讀取,都是存在的cache臟數(shù)據(jù);同理,如果先
寫(xiě)DB,在寫(xiě)cache,一樣存在可能被打斷,最終導(dǎo)致cache是臟數(shù)據(jù)的問(wèn)題

錯(cuò)誤操作2,先刪除cache,再更新DB,高并發(fā)時(shí)可能出現(xiàn)的問(wèn)題:
eg:進(jìn)程A先刪除了cache,此時(shí)進(jìn)程B打斷A,則從DB中讀取舊數(shù)據(jù),并設(shè)置到了cache,再回來(lái)
進(jìn)程A更新DB,那么從這里開(kāi)始,接下去所有的讀請(qǐng)求,都是舊cache,而且一直都是臟數(shù)據(jù)

正確的做法應(yīng)該是:
1、讀:先從DB讀取之后,再寫(xiě)到cache中
2、更新:先更新 DB 中的數(shù)據(jù),再刪除 cache (必須是刪除,而不是更新cache)
但是,這樣一樣不能保證不出錯(cuò)
eg:A進(jìn)程讀DB,B進(jìn)程打斷A,進(jìn)行DB的更新,刪除cache,再回來(lái)A進(jìn)程寫(xiě)入到cache,一樣
cache中是舊數(shù)據(jù),而且一直是臟數(shù)據(jù),但是,讀數(shù)據(jù)庫(kù)操作很快,寫(xiě)數(shù)據(jù)庫(kù)操作比較慢,讓一個(gè)慢
的操作打斷快的相對(duì)概率比較低,所以采用這種方式,至于這里為什么是刪除cache,而不是更新
cache,那是因?yàn)?,如果A進(jìn)程更新DB,此時(shí)B進(jìn)程更新DB,同時(shí)更新cache,A進(jìn)程再回來(lái)更新
cache,將會(huì)導(dǎo)致cache中的是臟數(shù)據(jù)

下面用代碼來(lái)實(shí)現(xiàn)看看

def worker_read_type1(write_flag, user_workid):
        ''先從cache中讀,獲取不到,再?gòu)腄B讀取之后,再寫(xiě)到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 '出現(xiàn)不一致--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  #寫(xiě)入數(shù)據(jù)庫(kù)后的值
                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  #寫(xiě)入數(shù)據(jù)庫(kù)后的值
                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  #寫(xiě)入數(shù)據(jù)庫(kù)后的值
                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  #寫(xiě)入數(shù)據(jù)庫(kù)后的值
                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):
        "運(yùn)行測(cè)試"
        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            

#下面運(yùn)行測(cè)試看看,一般來(lái)說(shuō),系統(tǒng)的讀請(qǐng)求遠(yuǎn)遠(yuǎn)大于寫(xiě)請(qǐng)求,這里100個(gè)進(jìn)程讀,2個(gè)進(jìn)程寫(xiě)
test_check_run(read_nump=100, wri_nump=2, readfunc=worker_read_type1, wrifunc=worker_update_type1)  #先更新DB,再更新cache,多進(jìn)程寫(xiě)有問(wèn)題
出現(xiàn)不一致--user_name:RobotZhu2038562---write_flag:RobotZhu669457, errpercent: err_num/num=11.4285714286%  出現(xiàn)數(shù)據(jù)不一致的情況概率是11.5%左右
test_check_run(read_nump=100, wri_nump=2, readfunc=worker_read_type1, wrifunc=worker_update_type2)  #先更新cache,再更新DB,多進(jìn)程寫(xiě)有問(wèn)題
出現(xiàn)不一致--user_name:RobotZhu4607997---write_flag:RobotZhu8633737, errpercent: err_num/num=53.8461538462% 出現(xiàn)數(shù)據(jù)不一致的情況概率是50%左右
test_check_run(read_nump=100, wri_nump=2, readfunc=worker_read_type1, wrifunc=worker_update_type3)  #先刪除cache,再更新DB,讀進(jìn)程打斷寫(xiě)進(jìn)程時(shí)有嚴(yán)重問(wèn)題
出現(xiàn)不一致--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,寫(xiě)進(jìn)程打斷讀進(jìn)程是有問(wèn)題
出現(xiàn)不一致--user_name:RobotZhu1536990---write_flag:RobotZhu1536990, errpercent: err_num/num=7.69230769231%  數(shù)據(jù)不一致概率7%左右,所以這個(gè)比較好

二、緩存“擊穿”處理:

一個(gè)設(shè)置了過(guò)期時(shí)間的cache,在它過(guò)期那一刻,大量的并發(fā)請(qǐng)求會(huì)直連DB,DB負(fù)載過(guò)重問(wèn)題。

解決辦法:
1、當(dāng)獲取數(shù)據(jù)發(fā)現(xiàn)為空時(shí),說(shuō)明cache過(guò)期了,此時(shí)不馬上連接DB,而是類(lèi)似redis中的SETNX語(yǔ)
法,設(shè)置一個(gè)tempkey=1,如果這個(gè)tempkey存在,則設(shè)置失敗,不存在則設(shè)置成功, 設(shè)置成功,則
進(jìn)行DB讀取數(shù)據(jù),寫(xiě)入cache,否則延時(shí)30s,再次重試讀cache,可能就有數(shù)據(jù)了。為什么這么
做?因?yàn)槎噙M(jìn)程并發(fā)的時(shí)候,第一個(gè)發(fā)現(xiàn)cache失效了,設(shè)置了tempkey,進(jìn)行DB讀數(shù)據(jù),其他進(jìn)程
則因?yàn)闊o(wú)法設(shè)置tempkey而等待一會(huì),再讀數(shù)據(jù)。
代碼示例:
def get_data(key=None):
  value = redis.get(key)
  if not value:
      #緩存失效
      if 1==redis.setnx(key+'tempkey', 1, 60):   #設(shè)置一個(gè)臨時(shí)key,如果被其他進(jìn)程設(shè)置過(guò)了,則設(shè)置失敗,也就不會(huì)連接db
          value = db.query('select name from test')
          redis.set(key, value)
          redis.delete(key+'tempkey')
     else:
         time.sleep(10)
         get_data(key)  #遞歸重試,或許已經(jīng)可以直接從cache中獲取了
 else:
     return value

分享名稱(chēng):Redis--大部分人不知道的緩存擊穿與緩存設(shè)置順序的操蛋事
分享URL:http://fisionsoft.com.cn/article/gepdps.html