我在的部门新系统开发近一年,业务从开始的混沌也渐渐清晰。本着绝不过早、过度优化的原则,只是在一些数据结构上做了一些改进,在一些需要算的地方用空间换时间。
业务稳定后,逐渐把关注点放在性能上,比如说:缓存。
当然缓存有很多种方式:
- 把一些公用数据放在内存中
- 像刚提到的,把一些计算的值从读时计算改成写入时计算
- 当然还有最熟悉的 Redis,Memcached 这种内存型 key-value 缓存方案。
这里要重点推荐下耗子哥的好文缓存更新的套路,文中总结了四种常用模式:
- Cache Aside Pattern
- Read Through Pattern
- Write Through Pattern
- Write Behind Caching Pattern
这几种模式在系统中也有涉及。
缓存的粒度
Second Level Cache
对于对象来说,这个粒度会产生更多的对象,也会有更多的 DB call,从性能上来讲,肯定是不被接受的,但在 Cache 上分析,这却又变成了好处,更多的对象,意味着可以被更多的缓存,对于更新可以不用全局删除缓存。
会有一些通用的库,一般在 ORM 中。
Biz Level Cache
这是我们目前用到的,它的颗粒度更大,好处是不用维护太多 key,坏处是小的修改也会触发缓存失效。
全手工写。
Content Service Cache - Cache Aside Pattern
内容是最小可复用体,它可能会被多个 Gateway 引用。所以单独的 Cache 是有意义的。
我们把内容服务分为两部分,内容 & 记录(关于这一块,更多可了解。。。)。
内容一旦被写入,几乎不会再做修改。但记录这一块,不是很确定,所以没有直接上线缓存。
把接口数据抽像为了两部分:
1 | { |
第一部,我们只是对 Content 进行了缓存,上线几周后,在 ELK 里看关于 Record 的数据,看 POST 和 GET Method 的对比,来判断用户的读写习惯。
数据为 1.7:8.3,Get 请求比还是很可观的,说明用户经常打开一些题目,只是看看,并没有进行做题,这样我们对 Record 也做了缓存。
缓存的流程图大概就是这样了。
- 如何保证更新的事务。保证写入 Cache 成功。
Gateway Service Cache - Read/Write though Pattern
内容被多个 Gateway 引用,从业务场景上来看,一些只读的数据,无 Record 的。
理论上数据有一个短暂的不一致,在业务上是许可的,可接受的。
- Read Only Content
- Token Authentication
基于这个原则,我们来设计缓存规则。
不做更新。
Read Write Through Pattern 这种模式更像是被用作 ORM 中。
https://github.com/hooopo/second_level_cache
Write Through and Read Through caching library
Write Behind Caching Pattern
这种模式,有点像 Redis 本身,In memory,但也会做持久化。
当然,也可以用作一种业务上的缓存模式,只是见到的会比较少。
另外,Write Back实现逻辑比较复杂,因为他需要track有哪数据是被更新了的,需要刷到持久层上。操作系统的 write back会在仅当这个cache需要失效的时候,才会被真正持久起来,比如,内存不够了,或是进程退出了等情况,这又叫lazy write。
ORACLE: Read-Through, Write-Through, Write-Behind, and Refresh-Ahead Caching
MSDN: Cache-Aside Pattern 缓存模式
http://www.ehcache.org/documentation/2.8/apis/write-through-caching.html
http://www.ehcache.org/documentation/3.0/caching-patterns.html
http://www.computerweekly.com/feature/Write-through-write-around-write-back-Cache-explained
http://www.alachisoft.com/resources/articles/readthru-writethru-writebehind.html
4 Simple Memoization Patterns in Ruby (and One Gem) http://www.justinweiss.com/articles/4-simple-memoization-patterns-in-ruby-and-one-gem/