一、關于緩存設定順序:
錯誤操作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%左右,是以這個比較好
二、緩存“擊穿”處理:
解決辦法:
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