距离上次写博客有两三个月了,这段时间去了新公司上班,忙了很多。接手了一个项目,刚好用到 redis,先总结下遇到的问题(跟 redis 相关的问题):
1、列表问题
举例:展示商品列表,但是要先展示运营置顶的数据,如果排序的序号一样,则按照 id 降序排序,就是需要按照 sort asc, id desc 来排序;用 redis 怎么处理?
【分析】
首先这个问题可能本身有点问题,因为如果限定了 redis,那么处理的方法就给限定死了,当时由于一股劲想着用 redis 处理,而忘了去想下 redis 是否适合处理这种问题;
第二,讲下一开始是怎么用 redis 处理的:
1)商品列表存储起来在有序集合,按照 sort 字段排序比如有序集合 goods-by-sort 中数据:
商品 id (member) , value(score)
11
21
33
42
2)每当创建新商品,加入此队列,sort 值是默认值;
3)每当运营在管理后台修改 sort 值,则修改此有序集合中对应商品的 score 值;
4)删除商品,或者设置不可见,则从这个有序集合中删掉该商品 id 的数据;
5)用户获取商品列表时,因为需要按照 sort 和 id 排序,所以我当时再新增一个有序集合: goods_list_data,首先从 goods_by_sort 取出数据,在程序里面重新排序,然后写到 goods_temp 有序集合,然后 rename 为 goods_list_data 有序集合,然后给这个集合设置一个过期时间,比如 2 分钟。
6)针对上面第 2)到第 4)步可能会对 goods_by_sort 有序集合的数据进行调整,比如修改、新增和删除,那么 goods_list_data 数据也需要更新,否则用户会一直看到被删除的商品。所以上面这三种情况,我会去更新一个 string 类型的 refresh_goods_data 的 key,每次去 incr。用一个定时任务,每分钟一次去检查这个值,如果不为 0,那么就去更新 goods_list_data,然后设置 refresh_goods_data 的值为 0,。否则则不处理,因为 goods_list_data 有序集合没改动。
【上面这么做的问题】
1)耦合很深,不好维护;搞了 2 个有序集合,还有定时任务;
2)会有无底洞问题:有序集合存储的数据会越来越多,当然这个可以根据业务处理,比如裁剪,但是维护这个有序集合也是个问题,增删改都要做相应维护。
【比较好的处理方法】
1)还是回归到查询数据库,根据 sort asc,id desc 排序来分页获取,但是基础数据就从 redis 中获取;这个要根据数据量,还有 sql 语句复杂度来评估,如果联表,或者是已经被告知数据库出现这个慢查询 sql,那就肯定不能用这个方法。
2)同事建议这种用 sphinx 来处理,有道理,不过还没尝试。2、redis 防雪崩、防穿透、无底洞问题
【分析和解决方法】
防止雪崩问题的有效方法:
1)不设置过期时间,只要数据实时更新到 redis,那么给用户的数据就是实时的,不影响后端数据库;
2)热数据和冷数据的区分,每天定时刷新热数据;
防穿透问题:
1)由于在 redis 中找不到数据,所以会去数据库读取,但是数据库也没有,所以不会写回缓存,导致并发访问时每次兜圈数据库读取;
2)可以给这类数据写一个 null 或 false 到 redis,设置一个过期时间,比如 2 分钟;
3)要注意的是,这种 key 不能存储太长时间,key 的量多起来,内存占用也会多的。
无底洞问题:
1)key 如果不过期,那么会一直保存在内存,内存会越来越不够用;
2)key 如果过期,那么过期后,怎么处理,高并发访问数据,数据库会不会挂掉?网上有不少代码用 setnx 加锁方式,获取到锁的就去 db 查询然后写回 redis,而其他的请求没有获取到锁,则等待一段时间(比如 10 毫秒)然后再去 redis 读取,取到就返回,取不到数据的话可以根据业务看要不要直接返回空结果,还是再去获取锁,直到读取到数据或者尝试的次数到达指定次数。3、刷缓存问题
举例:由于是在一个老项目上做优化,之前是没有做 redis 缓存(严格来说还是有缓存,但是仅仅是把 api 接口的结果缓存起来设置个过期时间),所以新的优化上线后,如果访问旧的数据,缓存中没有,那么如果不刷数据,就会所有请求到缓存都是空命中,此时要么直接返回说没数据(用户体验非常差),或者去数据库查询,此时是类似雪崩的情况,并发访问数据库,数据库可能会挂掉,那么比较好的处理方式就是先把数据刷到缓存。这里该如何处理?
【分析】
1)根据业务,分析哪些数据需要提前刷新到缓存;
2)增加锁机制,如果获取不到数据,则先去获取锁,获取到锁的则去 db 查询然后写回 redis,db 无数据则写 null 或 false 到 redis 并设置过期时间;
3)根据数据库表中数据量和业务,分析是否可以先刷一部分数据。比如商品有 1000 万条数据,用户在网站首页可以分页慢慢地看 1000 万条数据,但是大部分用户可能只会看前面的几十页数据,比如一页 20 条数据,那么准备 2000 条最新数据或者热门数据即可。其他的商品,等待用户访问的时候,从 redis 获取,读取不到从 db 获取写回 redis 即可。 4、商品之间根据标签进行关联,比如:
商品 A: tag1, tag2 ,tag3
商品 B: tag2, tag4
商品 C: tag1, tag5
商品 D: tag4
所以,商品 A 和商品 B、商品 C 关联;商品 B 和商品 D 关联;商品 C 和商品 A 关联;商品 D 和商品 B 关联。
每当要获取商品 A 关联的商品时,当然可以从 db 去获取 A 的标签,然后计算出管理的商品,那么如何用 redis 处理?
【分析】
如果非要用 redis 处理,那么就是需要提前把关系计算好,存放到集合 / 有序集合,那么一旦需要获取数据的时候,不需要再去计算,而是直接从缓存读出这些关联的 id。
1)新增商品时,会附带标签。计算关联的商品,是比较耗时的操作,可以放在队列,由后台脚本定时处理;
2)删除商品时,也是需要放入队列,由后台脚本去处理;
3)修改标签(给商品新增一个或多个标签、删除一个或多个标签和修改某些标签),也是需要放入队列,由后台脚本去处理;
【上面这么做的问题】
1)不好维护;增删改,都需要去维护这个有序集合。好处就是需要获取关联数据时直接从集合 / 有序集合获取 id,基础信息也从 redis 获取;
2)请教了同事,说用 sphinx 也可以处理,还没尝试。
5、用 redis 能否解决 mysql like 的问题
答案自然是解决不了,只能用 sphinx 或 es 这些全文搜索系统。6、商品列表有个逻辑是,允许展示用户自己发布的商品(不论审核状态)+ 其他用户发布的商品(只能审核通过状态)的这些数据。当时用 redis 处理,搞了两个有序集合,一个是存储网站上所有审核通过的商品;第二个有序集合是存储用户自己发布的商品(不论审核状态)。当需要获取商品列表时,合并两个集合,然后分页取数据。
【问题】
并发情况下,合并有序集合的代价是很高的,可能造成阻塞;
【解决方法】
1)可以的话直接 mysql 查询。
2)使用 sphinx:又是同事的建议。总结:
1、redis 不是万能的,也有自己的优势和劣势;redis 不适合处理 sql 这种关系型的业务;
2、redis 的性能是很好的,但是人为的操作,使用不当,可能造成阻塞;
3、除了 redis,还有其他的方法可以处理,不能限定死了。处理问题的时候,不仅要考虑能不能处理,还要考虑是否合理。
来源: https://www.cnblogs.com/guangye/p/8120683.html