Redis--大部分人不知道的缓存击穿与缓存设置顺序的操蛋事
发表于:2024-11-11 作者:千家信息网编辑
千家信息网最后更新 2024年11月11日,一、关于缓存设置顺序:cache,在web设计中经常用,大家也都会知道设置cache可以提高系统的响应速度,在做这个cache的时候,不知道为什么,很多人的流程都是:读取数据时,先从cache中读取数
千家信息网最后更新 2024年11月11日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安全错误
数据库的锁怎样保障安全
掌盟服务器
通信弱电网络技术员做什么
qt 显示数据库的数据
输入服务器名称
管网络安全的部门叫什么意思
云数据库挑战赛报名
受欢迎的眼镜行业软件开发
pram数据库
网络技术基础第一课
网络安全大赛小游戏
服务器之间数据传输安全
数据库实例启停
金蝶服务器换硬盘后重新验证
网络安全危害分析
国家网络安全审查平台
儿童网络安全教育很重要
防火墙显示网络安全警报
债券数据库python
linux服务器怎么分区
远程控制软件开发视频
孝昌警察公务员网络安全管理
软件开发如何成为技术大牛
如何实现网络技术安全
开博尔高清播放器的电脑服务器
新闻中心网络安全应急预案
快速的企业内网网络安全
数据库技术应用教程选择题
本地电脑怎么设置为网页服务器
莱茨狗市场的服务器多少钱一年
学软件开发以后干什么