Cache Aside

简介: Cache Aside

分库分表
  
分库分表作为数据库优化的一种非常经典的优化方案,特别是在以前NoSQL还不是很成熟的年代,这个方案就如救命草一般的存在。
  
如今也有不少同行也会选择这种优化方式,但是从我角度来看,分库分表是一种优化成本很大的方案。这里我有几个建议:

  1. 分库分表是实在没有办法的办法,应放到最后选择。
  2. 优先选择NoSQL代替,因为NoSQL诞生基本上为了扩展性与高性能。
  3. 究竟分库还是分表?量大则分表,并发高则分库
  4. 不考虑扩容,一部做到位。因为技术更新太快了,每3-5年一大变。

拆分方式

                                        分库分表-拆分方式

拆分方式
角度
优点
垂直拆分
按照业务拆分
降低业务耦合度
减少字段,物理页所拥有的行数则变多
水平拆分
从物理层面分片
从根本上减少数据量
  
只要涉及到这个拆,那么无论是微服务也好,分库分表也好,拆分的方式主要分两种:垂直拆分、水平拆分。
  
垂直拆分更多是从业务角度进行拆分,主要是为了降低业务耦合度;此外以SQL Server为例,一页是8KB存储,如果在一张表里字段越多,一行数据自然占的空间就越大,那么一页数据所存储的行数就自然越少,那么每次查询所需要IO则越高因此性能自然也越慢;因此反之,减少字段也能很好提高性能。之前我听说某些同行的表有80个字段,几百万的数据就开始慢了。
  
水平拆分更多是从技术角度进行拆分,拆分后每张表的结构是一模一样的,简而言之就是把原有一张表的数据,通过技术手段进行分片到多张表存储,从根本上解决了数据量的问题。
路由方式
路由方式
算法
优点
缺点
区间范围
查询定位比较容易
容易造成数据不平均(热点数据)
容易忘记创建新表
Hash
分片均匀
必须带分区键,不带分区键则会所有表都扫描一遍
分库情况下无法使用关系型数据库的特性(Join、聚合计算、分页)
分片映射表
补充方案
二次查询
  
进行水平拆分后,根据分区键(sharding key)原来应该在同一张表的数据拆解写到不同的物理表里,那么查询也得根据分区键进行定位到对应的物理表从而把数据给查询出来。
  
路由方式一般有三种区间范围、Hash、分片映射表,每种路由方式都有自己的优点和缺点,可以根据对应的业务场景进行选择。
  
区间范围根据某个元素的区间的进行拆分,以时间为例子,假如有个业务我们希望以月为单位拆分那么表就会拆分像 table_2022-04,这种对于文档型、ElasticSearch这类型的NoSQL也适用,无论是定位查询,还是日后清理维护都是非常的方便的。那么缺点也明显,会因为业务独特性导致数据不平均,甚至不同区间范围之间的数据量差异很大。
  
Hash也是一种常用的路由方式,根据Hash算法取模以数据量均匀分别存储在物理表里,缺点是对于带分区键的查询依赖特别强,如果不带分区键就无法定位到具体的物理表导致相关所有表都查询一次,而且在分库的情况下对于Join、聚合计算、分页等一些RDBMS的特性功能还无法使用。
  
一般分区键就一个,假如有时候业务场景得用不是分区键的字段进行查询,那么难道就必须得全部扫描一遍?其实可以使用分片映射表的方式,简单来说就是额外有一张表记录额外字段与分区键的映射关系。举个例子,有张订单表,原本是以UserID作为分区键拆分的,现在希望用OrderID进行查询,那么得有额外得一张物理表记录了OrderID与UserID的映射关系。因此得先查询一次映射表拿到分区键,再根据分区键的值路由到对应的物理表查询出来。可能有些朋友会问,那这映射表是否多一个映射关系就多一张表,还是多个映射关系在同一张表。我优先建议单独处理,如果说映射表字段过多,那跟不进行水平拆分时的状态其实就是一致的,这又跑回去的老问题。
用空间换性能
该类型的两个方案都是用来应对高负载的场景,方案有以下两种:分布式缓存、一主多从。
  
与其说这个方案叫用空间换性能,我认为用空间换资源更加贴切一些。因此两个方案的本质主要通数据冗余、集群等方式分担负载压力。
  
对于关系型数据库而言,因为他的ACID特性让它天生不支持写的分布式存储,但是它依然天然的支持分布式读。
分布式缓存
分布式缓存
做法
场景
缺点
Cache Aside
应对高并发读
动态条件比较多的业务场景,缓存命中低
伪静态数据(业务配置、低时效的数据)
实时性要求高的数据场景,处理起来比较花功夫
  
缓存层级可以分好几种:客户端缓存、API服务本地缓存和分布式缓存,咱们这次只聊分布式缓存。一般我们选择分布式缓存系统都会优先选择NoSQL的键值型数据库,例如Memcached、Redis,如今Redis的数据结构多样性,高性能,易扩展性也逐渐占据了分布式缓存的主导地位。
  
缓存策略也主要有很多种:Cache-Aside、Read/Wirte-Through、Write-Back,咱们用得比较多的方式主要Cache-Aside,具体流程可看下图:

我相信大家对分布式缓存相对都比较熟悉了,但是我在这里还是有几个注意点希望提醒一下大家:
避免滥用缓存
  
缓存应该是按需使用,从28法则来看,80%的性能问题由主要的20%的功能引起。滥用缓存的后果会导致维护成本增大,而且有一些数据一致性的问题也不好定位。特别像一些动态条件的查询或者分页,key的组装是多样化的,量大又不好用keys指令去处理,当然我们可以用额外的一个key把记录数据的key以集合方式存储,删除时候做两次查询,先查Key的集合,然后再遍历Key集合把对应的内容删除。这一顿操作下来无疑是非常废功夫的,谁弄谁知道。
避免缓存击穿
  
当缓存没有数据,就得跑去数据库查询出来,这就是缓存穿透。假如某个时间临界点数据是空的例如周排行榜,穿透过去的无论查找多少次数据库仍然是空,而且该查询消耗CPU相对比较高,并发一进来因为缺少了缓存层的对高并发的应对,这个时候就会因为并发导致数据库资源消耗过高,这就是缓存击穿。数据库资源消耗过高就会导致其他查询超时等问题。
  
该问题的解决方案也简单,对于查询到数据库的空结果也缓存起来,但是给一个相对快过期的时间。有些同行可能又会问,这样不就会造成了数据不一致了么?一般有数据同步的方案像分布式缓存、后续会说的一主多从、CQRS,只要存在数据同步这几个字,那就意味着会存在数据一致性的问题,因此如果使用上述方案,对应的业务场景应允许容忍一定的数据不一致。
不是所有慢查询都适用
  
一般来说,慢的查询都意味着比较吃资源的(CPU、磁盘I/O)。举个例子,假如某个查询功能需要3秒时间,串行查询的时候并没什么问题,我们继续假设这功能每秒大概QPS为100,那么在第一次查询结果返回之前,接下来的所有查询都应该穿透到数据库,也就意味着这几秒时间有300个请求到数据库,如果这个时候数据库CPU达到了100%,那么接下来的所有查询都会超时,也就是无法有第一个查询结果缓存起来,从而还是形成了缓存击穿。
一主多从
一主多从
场景
优点
缺点
分担数据库读压力
应急调整方便,单以运维直接解决。
高硬件成本
还没找到更好的降低数据库负载的临时方案
扩展性有限
  
常用的分担数据库压力还有一种常用做法,就是读写分离、一主多从。咱们都是知道关系型数据库天生是不具备分布式分片存储的,也就是不支持分布式写,但是它天然的支持分布式读。一主多从是部署多台从库只读实例,通过冗余主库的数据来分担读请求的压力,路由算法可有代码实现或者中间件解决,具体可以根据团队的运维能力与代码组件支持视情况选择。
  
一主多从在还没找到根治方案前是一个非常好的应急解决方案,特别是在现在云服务的年代,扩展从库是一件非常方便的事情,而且一般情况只需要运维或者DBA解决就行,无需开发人员接入。当然这方案也有缺点,因为数据无法分片,所以主从的数据量完全冗余过去,也会导致高的硬件成本。从库也有其上限,从库过多了会主库的多线程同步数据的压力。

相关文章
|
缓存
cache
cache
114 0
|
内存技术
cache基础
cache
135 0
|
缓存
艾伟:Cache总义
Cache用法之页面声明 Cache用法之代码控制HttpCachePolicy类是专门用来控件Cache的,可以用Response.Cahce来访问这个类的实例 Response.Cache.
840 0
|
缓存 NoSQL Redis
|
PHP 缓存
|
Java API Memcache
|
缓存 数据库
|
缓存 JavaScript 前端开发