微服务复杂查询之缓存策略
在上一篇 缓存设计的好,服务基本不会倒 介绍了db层缓存,回顾一下,db层缓存主要设计可以总结为:
- 缓存只删除不更新
- 行记录始终只存储一份,即主键对应行记录
- 唯一索引仅缓存主键值,不直接缓存行记录(参考mysql索引思想)
- 防缓存穿透设计,默认一分钟,防止缓存击穿和雪崩
- 不缓存多行记录
前言
在大型业务系统中,通过对持久层添加缓存,对于大多数单行记录查询,相信缓存能够帮持久层减轻很大的访问压力,但在实际业务中,数据读取不仅仅只是单行记录,面对大量多行记录的查询,这对持久层也会造成不小的访问压力,除此之外,像秒杀系统、选课系统这种高并发的场景,单纯靠持久层的缓存是不现实的,本文我们来介绍 go-zero 实践中的缓存设计之biz cache。
适用场景举例
- 选课系统
- 内容社交系统
- 秒杀
像这些系统,我们可以在业务层再增加一层缓存来存储系统中的关键信息,如选课系统中学生选课信息,课程剩余名额;内容社交系统中某一段时间之间的内容信息等。
接下来,我们以内容社交系统来进行举例说明。
在内容社交系统中,我们一般是先查询一批内容列表,然后点击某条内容查看详情,
在没有添加biz缓存前,内容信息的查询流程图应该为:
从上图以及上一篇文章 缓存设计的好,服务基本不会倒 中我们可以知道,内容列表的获取是没办法依赖缓存的, 如果我们在业务层添加一层缓存用来存储列表中的关键信息(甚至完整信息),那么多行记录的访问不再是一个问题,这就是biz redis要做的事情。 接下来我们来看一下设计方案,假设内容系统中单行记录包含以下字段
字段名称 | 字段类型 | 备注 |
---|---|---|
id | string | 内容id |
title | string | 标题 |
content | string | 详细内容 |
createTime | time.Time | 创建时间 |
我们的目标是获取一批内容列表,而尽量避免内容列表走db造成访问压力,首先我们采用redis的sort set数据结构来存储,根需要存储的字段信息量,有两种redis存储方案:
-
缓存局部信息 对其关键字段信息(如:id等)按照一定规则压缩,并存储,score我们用
createTime
毫秒值(时间值相等这里不讨论),这种存储方案的好处是节约redis存储空间, 那另一方面,缺点就是需要对列表详细内容进行二次回查(但这次回查是会利用到持久层的行记录缓存的) -
缓存完整信息 对发布的所有内容按照一定规则压缩后均进行存储,同样score我们还是用
createTime
毫秒值,这种存储方案的好处是业务的增、删、查、改均走reids,而db层这时候 就可以不用考虑行记录缓存了,持久层仅提供数据备份和恢复使用,从另一方面来看,其缺点也很明显,需要的存储空间、配置要求更高,费用也会随之增大。
示例代码:
type Content struct { Id string `json:"id"` Title string `json:"title"` Content string `json:"content"` CreateTime time.Time `json:"create_time"` } const bizContentCacheKey = `biz#content#cache` // AddContent 提供内容存储 func AddContent(r redis.Redis, c *Content) error { v := compress(c) _, err := r.Zadd(bizContentCacheKey, c.CreateTime.UnixNano()/1e6, v) return err } // DelContent 提供内容删除 func DelContent(r redis.Redis, c *Content) error { v := compress(c) _, err := r.Zrem(bizContentCacheKey, v) return err } // 内容压缩 func compress(c *Content) string { // todo: do it yourself var ret string return ret } // 内容解压 func uncompress(v string) *Content { // todo: do it yourself var ret Content return &ret } // ListByRangeTime提供根据时间段进行数据查询 func ListByRangeTime(r redis.Redis, start, end time.Time) ([]*Content, error) { kvs, err := r.ZrangebyscoreWithScores(bizContentCacheKey, start.UnixNano()/1e6, end.UnixNano()/1e6) if err != nil { return nil, err } var list []*Content for _, kv := range kvs { data := uncompress(kv.Key) list = append(list, data) } return list, nil }
在以上例子中,redis是没有设置过期时间的,我们将增、删、改、查操作均同步到redis,我们认为内容社交系统的列表访问请求是比较高的情况下才做这样的方案设计, 除此之外,还有一些数据访问,没有像内容设计系统这么频繁的访问, 可能是某一时间段内访问量突如其来的增加,之后可能很长一段时间才会再访问一次,以此间隔,或者说不会再访问了,面对这种场景,我们又该如何考虑缓存的设计呢?在go-zero内容实践中,有两种方案可以解决这种问题:
- 增加内存缓存:通过内存缓存来存储当前可能突发访问量比较大的数据,常用的存储方案采用map数据结构来存储,map数据存储实现比较简单,但缓存过期处理则需要增加定时器来处理,另一宗方案是通过go-zero库中的 Cache ,其是专门用于内存缓存管理。
- 采用biz redis,并设置合理的过期时间
总结
以上两个场景可以包含大部分的多行记录缓存,对于多行记录查询量不大的场景,暂时没必要直接把biz redis放进去,可以先尝试让db来承担,开发人员可以根据持久层监控及服务监控来衡量何时需要引入biz cache。
项目地址
https://github.com/tal-tech/go-zero
https://gitee.com/kevwan/go-zero
欢迎使用 go-zero 并 star 支持我们!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
微信方面已处罚超过8万个网络欺诈账号,网络安全形势依旧严峻
我国网民数量如今已超过10亿,这代表着几乎每一个人都和互联网挂着钩,享受着互联网带来的便利,但互联网在带给我们便利的同时,也存在着不小的隐患,比如网络诈骗! 就只从用户量达十亿的社交平台微信而言,在今年3月12日,微信安全团队发布了《微信个人帐号发布违禁品信息及欺诈行为打击公告》,其中有一条数据值得大家关注——微信方面今年以来已累计处罚8.4万余个网络欺诈个人账号。 8.4万个个人账号意味着什么?微信总用户量约十亿左右(微信账号数自然是超过十亿),差不多万分之一的比例,再加上网络的互联化,基本上代表着我们每个人的身边都有可能发生”网络诈骗“,说实话,初次看到这个数字时,我还是比较震惊的。 并且这还只是今年的数据,还只是最近3个月的数据,在去年疫情期间,“网络诈骗”现象更为肆虐,那个时候不知道发生过多少这类诈骗事件呢,细想起来让人觉得毛骨悚然。 由于目前不少人对于网络还不够熟练,尤其是涉世未深的未成年人和接触网络较少的老年人,往往会成为网络诈骗分子的首要目标,虽然有关部门已经制定相关措施,但是看起来收效甚微,还是需要相关亲人朋友多多告诫他们,让他们对于网络有一个更为清晰的认知。 除此之外...
- 下一篇
写了一个 gorm 乐观锁插件
前言 最近在用 Go 写业务的时碰到了并发更新数据的场景,由于该业务并发度不高,只是为了防止出现并发时数据异常。 所以自然就想到了乐观锁的解决方案。 实现 乐观锁的实现比较简单,相信大部分有数据库使用经验的都能想到。 UPDATE `table` SET `amount`=100,`version`=version+1 WHERE `version` = 1 AND `id` = 1 需要在表中新增一个类似于 version 的字段,本质上我们只是执行这段 SQL,在更新时比较当前版本与数据库版本是否一致。 如上图所示:版本一致则更新成功,并且将版本号+1;如果不一致则认为出现并发冲突,更新失败。 这时可以直接返回失败,让业务重试;当然也可以再次获取最新数据进行更新尝试。 我们使用的是 gorm 这个 orm 库,不过我查阅了官方文档却没有发现乐观锁相关的支持,看样子后续也不打算提供实现。 不过借助 gorm 实现也很简单: type Optimistic struct { Id int64 `gorm:"column:id;primary_key;AUTO_INCREMENT" js...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Hadoop3单机部署,实现最简伪集群
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS8编译安装MySQL8.0.19
- Docker安装Oracle12C,快速搭建Oracle学习环境