清缓存的姿势不对,真的会出生产bug哦

因缓存清理逻辑错误,导致生产环境出现数据库锁表及错误提示问题,通过重启服务、修改代码及全面审查解决,强调finally块内清理缓存的重要性。

 

最近解决了一个生产bug,bug的原因很简单,就是清理缓存的方式不对。本来没啥好说的,但是考虑到我们有时候确实会在一些小问题上栽跟头,最终决定把这个小故事拿出来跟大家分享下。

 

风起
有一天在撸代码,突然有个人加我微信,看头像是个妹子。我第一反应:对方是微商或者卖茶叶的(忍住,别笑)。因为已经有很多次这种加我好友的情况了,问对方是谁就从来没有下文。所以这次我也没有通过,而是像以前一样追问了一句“我们认识吗”,就没再管它,心想对方肯定不回的。然后继续然后继续撸串,哦不,是撸代码。
还没1分钟,对方竟然回复了,大意是“我是xxx公司的某某某,有个问题想咨询下”,哇哦,原来是客户。。。赶紧通过验证并问具体啥问题。原来是对方在点某个页面按钮的时候一直提示错误,不能正常进行业务了。

 

常规操作
和以往一样,我查起了生产log,发现是数据库锁表了。客户是业务型公司,一般不会出现多人同时操作的情况,数据量也不大,生产上从来没有出现过,倒是我本地调试的时候经常因为性子急多点几次导致锁表,因此感觉这个问题很好解决,让管理员把锁解了就行了。

 

云涌
没有锁了,让客户再试下,客户的反馈“还是报错”,没道理啊。再查日志,发现已经没有了堆栈信息,为啥还不行呢。
没办法只能看那个时间段所有的log,发现有一行“map缓存中有数据,可能多人同时操作”。
查下代码,发现代码中为了防止同一条数据多人同时操作,加了map作为缓存,数据记录的PK作为key和value。

(记住这个图,后面会考)
每次操作的时候将数据put进map,处理完后remove掉。如果map中有相关KV就表示这条记录有人正在操作,则其他人不能操作,抛提示信息。
于是乎问客户“多人同操乎?”,得到的答复是“否”,纳尼?怎可能。日志是不会骗人的啊

 

再查代码
针对map再把代码看一遍,看它put和remove的地方,还有日志打印的地方。终于发现一个问题,
map的清理动作是在try里面正常处理后做的,如果出现异常就不能正常清理了,而map定义的时候为了对象间共享定义成了静态成员变量,
刚才的锁表抛了异常,当时已经put进入到map里面的KV就一直没有机会清掉了,也就是说只要服务不重启,问题会一直坚定的陪着你。

 

对症下药
1.跟领导申请紧急重启服务,保证业务正常进行。
2.修改问题代码,将报错放到finally块。
3.横展开调查其他类似代码是不是也存在这个问题,一并修改。

 

风平浪静
问题解决,对方表示感谢,我也回复不客气,一切回归平静。

 

总结
其实这个缓存清理的问题本身很简单,大家都懂,就和释放数据库连接等情况一样,需要放到finally块里面,
这个即使代码抛异常了也能正常释放或清理。但是就是撸这段代码的时候,因为这样那样的原因一时没有想到。
如果公司有代码review的环节的话会好很多,如果没有review,那么在写完代码后最好自己review一遍。
否则,万一出现类似的问题真的有点尴尬,正如题目所说“清缓存的姿势不对,真的会出生产bug哦”。
希望你我今后都能避免这种情况的发生。

<think>我们主要需要解决四个问题: 1. 抽卡次数重置问题:需要将每日抽卡次数和最后一次抽卡日期存储到本地,并在每次启动时检查是否需要重置抽卡次数。 2. 数据存储位置和除方法:需要明确数据存储的位置,并提供除数据的方法(测试用)。 3. 卡牌池和卡牌库的数据分隔存储:将卡牌池(静态数据)和卡牌库(用户收集的数据)分开存储。 4. 存储上限问题:讨论本地存储是否有上限以及如何应对。 解决方案如下: 问题1:每日抽卡次数的持久化存储和重置逻辑 - 我们将使用uni-app的本地存储(uni.setStorageSync/uni.getStorageSync)来存储两个数据: lastDrawDate: 最后一次抽卡的日期(字符串,格式为'YYYY-MM-DD') dailyDrawCount: 当天的剩余抽卡次数 - 在应用启动时(例如在onLoad或onShow生命周期中),检查当前日期与lastDrawDate是否相同: - 如果同且是新的一天,则重置抽卡次数 - 如果相同,则使用存储的剩余抽卡次数 - 注意:需要处理第一次使用的情况 问题2:数据存储位置和除方法 - 在uni-app中,使用uni.setStorageSync存储的数据位于小程序或APP的本地存储中。 - 对于H5,存储于LocalStorage;对于小程序,存储于小程序的Storage;对于APP,存储于原生应用的本地存储(如iOS的NSUserDefaults,Android的SharedPreferences)。 - 除方法: - 开发阶段:可以在代码中调用uni.clearStorageSync()除所有存储,或者调用uni.removeStorageSync(key)除指定数据。 - 运行阶段:用户可以通过缓存除,但作为开发测试,我们可以提供一个隐藏的“除数据”按钮(只在开发环境下显示)来除。 问题3:卡牌池和卡牌库的数据分隔存储 - 卡牌池(cardPool)是静态数据,我们将其放在单独的js文件中(如cardPool.js),然后通过import引入。 - 卡牌库(collection)是用户收集的数据,存储在本地存储中(使用uni.setStorageSync),每次启动时从本地存储读取。 问题4:存储上限 - 同平台的上限同: - H5的LocalStorage一般为5MB-10MB - 微信小程序:单个key允许存储的最大数据为1MB,总上限为10MB - APP:一般较大,但具体取决于设备,通常至少5MB以上 - 对于数百张卡牌(假设每张卡牌的数据量为1KB,1000张才1MB),通常会超过上限。 修改步骤: 1. 将卡牌池数据独立到单独文件 创建`/common/cardPool.js`: ```javascript export default [ { id: 1, category: "动作类", name: "轻抚发丝", rarity: "暧昧", description: "轻柔地抚摸对方的头发", image: "/static/cards/action1.jpg" }, // ...其他卡牌 ]; ``` 2. 修改主页面代码,引入卡牌池 ```javascript import cardPool from '@/common/cardPool.js'; ``` 3. 实现每日抽卡次数的重置逻辑 4. 提供测试用的除数据按钮(只在开发环境显示) 下面是修改后的代码框架(只展示关键部分): </think># 每日抽卡游戏框架优化方案 我将针对你提的四个问题进行优化,解决抽卡次数重置、数据存储管理、数据分隔和上限问题。以下是详细的解决方案: ## 1. 解决抽卡次数重置问题(每日固定抽卡机制) ```javascript // 在页面逻辑中添加以下方法 methods: { // 初始化每日抽卡状态 initDailyDraw() { const today = new Date().toDateString(); const lastDrawDate = uni.getStorageSync('lastDrawDate'); const savedDrawCount = uni.getStorageSync('dailyDrawCount'); if (lastDrawDate !== today) { // 新的一天,重置抽卡次数 this.drawCount = this.dailyDrawLimit; uni.setStorageSync('dailyDrawCount', this.dailyDrawLimit); uni.setStorageSync('lastDrawDate', today); } else { // 同一天,使用保存的次数 this.drawCount = savedDrawCount || this.dailyDrawLimit; } }, // 保存抽卡状态 saveDrawState() { uni.setStorageSync('dailyDrawCount', this.drawCount); uni.setStorageSync('lastDrawDate', new Date().toDateString()); } }, onLoad() { // 设置每日抽卡限制(可根据需要修改) this.dailyDrawLimit = 1; // 初始化抽卡状态 this.initDailyDraw(); // 加载其他数据... } ``` ## 2. 数据存储位置与管理 ### 存储位置说明 - **卡牌库**:存储在 `cardCollection` 键下 - **抽卡次数**:存储在 `dailyDrawCount` 键下 - **最后抽卡日期**:存储在 `lastDrawDate` 键下 ### 添加测试重置功能 ```vue <template> <!-- 在首页添加测试按钮 --> <view v-if="isDevMode" class="dev-tools"> <button class="reset-btn" @click="resetTestData">重置测试数据</button> </view> </template> <script> export default { data() { return { isDevMode: true, // 开发模式开关,发布时设为false } }, methods: { // 重置所有测试数据 resetTestData() { // 除卡牌库 uni.removeStorageSync('cardCollection'); this.collection = []; // 重置抽卡状态 uni.removeStorageSync('dailyDrawCount'); uni.removeStorageSync('lastDrawDate'); // 重新初始化 this.initDailyDraw(); uni.showToast({ title: '测试数据已重置', icon: 'success' }); } } } </script> ``` ## 3. 数据分隔存储方案 ### 项目结构调整 ``` /src /common cardPool.js ← 卡牌池数据 cardLibrary.js ← 卡牌库管理 dailyDraw.js ← 每日抽卡管理 /pages index.vue ← 主页面 ``` ### /common/cardPool.js ```javascript // 卡牌池数据 - 独立文件 export default [ { id: 1, category: "动作类", name: "轻抚发丝", rarity: "暧昧", description: "轻柔地抚摸对方的头发", image: "/static/cards/action1.jpg" }, { id: 2, category: "姿势类", name: "依偎而坐", rarity: "边缘", description: "两人靠在一起坐着", image: "/static/cards/pose1.jpg" }, // 其他卡牌... ]; ``` ### /common/cardLibrary.js ```javascript // 卡牌库管理 export default { // 获取卡牌库 getCollection() { return uni.getStorageSync('cardCollection') || []; }, // 保存卡牌库 saveCollection(collection) { uni.setStorageSync('cardCollection', collection); }, // 添加卡牌到卡牌库 addCardToCollection(card) { const collection = this.getCollection(); const existingCard = collection.find(c => c.id === card.id); if (existingCard) { existingCard.count++; } else { collection.push({ ...card, count: 1 }); } // 按稀有度排序 const rarityOrder = ["放浪", "畅意", "边缘", "暧昧"]; collection.sort((a, b) => { return rarityOrder.indexOf(a.rarity) - rarityOrder.indexOf(b.rarity); }); this.saveCollection(collection); return collection; }, // 空卡牌库(测试用) clearCollection() { uni.removeStorageSync('cardCollection'); } } ``` ### /common/dailyDraw.js ```javascript // 每日抽卡管理 export default { // 初始化每日抽卡状态 init(dailyDrawLimit = 1) { const today = new Date().toDateString(); const lastDrawDate = uni.getStorageSync('lastDrawDate'); const savedDrawCount = uni.getStorageSync('dailyDrawCount'); if (lastDrawDate !== today) { // 新的一天 this.saveDrawState(dailyDrawLimit, today); return dailyDrawLimit; } else { // 同一天 return savedDrawCount || dailyDrawLimit; } }, // 保存抽卡状态 saveDrawState(count, date = new Date().toDateString()) { uni.setStorageSync('dailyDrawCount', count); uni.setStorageSync('lastDrawDate', date); // 返回当前状态 return { count, date }; }, // 减少抽卡次数 decreaseDrawCount() { const currentCount = uni.getStorageSync('dailyDrawCount'); const newCount = Math.max(0, currentCount - 1); this.saveDrawState(newCount); return newCount; }, // 重置抽卡状态(测试用) reset() { uni.removeStorageSync('dailyDrawCount'); uni.removeStorageSync('lastDrawDate'); } } ``` ### /pages/index.vue 整合 ```javascript <script> // 导入模块 import cardPool from '@/common/cardPool.js'; import cardLibrary from '@/common/cardLibrary.js'; import dailyDraw from '@/common/dailyDraw.js'; export default { data() { return { dailyDrawLimit: 1, // 每日抽卡限制 drawCount: 0, // 当前剩余抽卡次数 // 其他数据... } }, onLoad() { // 初始化抽卡状态 this.drawCount = dailyDraw.init(this.dailyDrawLimit); // 初始化卡牌库 this.collection = cardLibrary.getCollection(); // 其他初始化... }, methods: { // 抽卡方法 drawCards() { if (this.drawCount <= 0) return; // 减少抽卡次数 this.drawCount = dailyDraw.decreaseDrawCount(); // 抽卡逻辑... }, // 选择卡牌 selectCard(card) { // 添加到卡牌库 this.collection = cardLibrary.addCardToCollection(card); // 其他逻辑... }, // 重置测试数据 resetTestData() { cardLibrary.clearCollection(); dailyDraw.reset(); // 重新初始化 this.drawCount = dailyDraw.init(this.dailyDrawLimit); this.collection = cardLibrary.getCollection(); uni.showToast({ title: '测试数据已重置', icon: 'success' }); } } } </script> ``` ## 4. 数据存储上限解决方案 uni-app 的本地存储上限: - **H5平台**:一般5-10MB (取决于浏览器) - **小程序平台**: - 微信小程序:单个key上限1MB,总上限10MB - 其他小程序类似 - **App平台**:一般较大,iOS/Android通常可达5-50MB ### 优化策略(应对数百张卡牌) ```javascript // 在 cardLibrary.js 中添加优化方法 export default { // ...其他方法 // 优化存储结构 optimizeStorage(collection) { // 只存储必要数据 return collection.map(card => ({ id: card.id, count: card.count })); }, // 获取完整卡牌库(结合卡牌池) getFullCollection() { const collection = this.getCollection(); const cardPool = getApp().globalData.cardPool; return collection.map(item => { const cardData = cardPool.find(c => c.id === item.id) || {}; return { ...cardData, count: item.count }; }); }, // 保存优化后的数据 saveOptimizedCollection() { const fullCollection = this.getCollection(); const optimized = this.optimizeStorage(fullCollection); uni.setStorageSync('cardCollection', optimized); } } ``` ## 完整实现优势 1. **每日抽卡机制**: - 基于日期判断是否重置抽卡次数 - 抽卡状态持久化存储 - 可通过重置功能除测试数据 2. **数据管理晰**: - 卡牌池(静态数据)独立存储在单独文件中 - 卡牌库(用户数据)使用专用管理模块 - 抽卡状态单独管理 3. **存储优化**: - 卡牌库只存储ID和数量,减少数据体积 - 使用时动态组合卡牌池数据 - 应对数百张卡牌毫无压力 4. **测试友好**: - 添加开发模式开关 - 提供一键重置功能 - 所有数据可除 ## 存储容量估算 假设你有500张卡牌: - 原始存储:每张卡牌约500字节,500张 = 250KB - 优化后存储:每个收藏项约20字节,500张 = 10KB - 加上其他数据,总存储量远低于1MB 即使有数千张卡牌,这套方案也能轻松应对各种平台的存储限制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值