Redis--大部分人不知道的缓存击穿与缓存设置顺序的操蛋事
发表于:2024-11-26 作者:千家信息网编辑
千家信息网最后更新 2024年11月26日,一、关于缓存设置顺序:cache,在web设计中经常用,大家也都会知道设置cache可以提高系统的响应速度,在做这个cache的时候,不知道为什么,很多人的流程都是:读取数据时,先从cache中读取数
千家信息网最后更新 2024年11月26日Redis--大部分人不知道的缓存击穿与缓存设置顺序的操蛋事
一、关于缓存设置顺序:
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
数据
更新
进程
一致
问题
缓存
数据库
概率
成功
同时
错误
代码
再次
情况
时候
系统
要么
测试
运行
顺序
数据库的安全要保护哪些东西
数据库安全各自的含义是什么
生产安全数据库录入
数据库的安全性及管理
数据库安全策略包含哪些
海淀数据库安全审计系统
建立农村房屋安全信息数据库
易用的数据库客户端支持安全管理
连接数据库失败ssl安全错误
数据库的锁怎样保障安全
胸科医师学会的全国数据库
jdbc查询 不到数据库
2021网络安全知识考试
黑马学网络安全好
河南道达网络技术有限公司
钱袋网络技术怎么样
正规网络安全供应商
安卓调用sql数据库
网络安全法规不健全
吉林时代网络技术代理商
因为网络安全领导干部被追责
计算机设置为服务器
网络安全防护排行榜
数据库漏洞扫描软件
客户关系管理与数据库营销
温州管理软件开发专业团队
怎样破坏服务器里面的数据
网络技术基础教案ppt
制衣厂老板软件开发
图片数据库安装
软件开发外派 坑人
安滨鑫互联网科技
美图手机怎么删除所有数据库
美团外卖服务器如何关闭
投诉网络安全
网络安全法多少条
联想服务器中bmc设置找不到
南邮网络技术与应用mooc答案
高度重视大学生网络安全工作
武汉学软件开发哪个学校好