欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

提升数据库性能的重要手段--冗余 博客分类: 架构mysql 读写分离镜像同步非对称同步 

程序员文章站 2024-02-14 09:22:34
...

前言

 

在程序设计中有一种常用的提升数据查询性能的手段以--空间换时间。典型的场景就是使用缓存,在查询数据库之前加一层全局共享缓存(如:redis),更有甚者在应用实例内部在加一层本地缓存。以java应用+mysql数据库为例,该架构设计方式如下:


提升数据库性能的重要手段--冗余
            
    
    博客分类: 架构mysql 读写分离镜像同步非对称同步 
 

 

数据查询逻辑为:

 


提升数据库性能的重要手段--冗余
            
    
    博客分类: 架构mysql 读写分离镜像同步非对称同步 
 

 

本地缓存的查询速度是纳秒级

Redis缓存的查询速度是毫秒级

Msyql数据的查询速度是毫秒级-秒级

 

有人会说既然本地缓存是最快的为什么不直接用本地缓存,还要使用redis缓存呢?本地缓存说白就是jvm内存,空间毕竟是有限的,再者分布式多实例部署的应用有多个jvm实例,本地缓存分散在各个实例内部不便于同步更新,使用上存在局限。具体怎么去权衡“本地缓存”和全局共享缓存不是本次讨论的重点,不再过多介绍。

 

所以为了提高性能需要把同一份数据存放三份,这就是数据冗余,典型的以空间换时间的使用场景。

 

可以发现在该场景中,最终瓶颈还是msyql数据库(在缓存失效时都会落到数据库),如果想要进一步的优化性能,一个重要的优化点还是在msyql数据库查询性能优化上(当然还有一个点就是优化程序本身)。

 

以空间换时间的方式同样适用运用到mysql数据库的性能优化中,主要体现在三个地方:表字段冗余、读写分离之镜像复制、读写分离之非对称复制。下面根据不同的场景,分别进行讲解。

 

表字段冗余

 

读书期间,在数据库表设计章节,相信大家都学习过“范式”。如果完全遵循“三范式”设计的数据库,对于数据存储来说可以极大的降低数据的存储量。但对于数据查询来说,会有很多联表查询,这会非常影响性能。对于高频查询的sql语句来说 “联表查询”绝对是个灾难。

 

这时的常用手段就是“表字段冗余”,举个真实的案例:在一个项目初期,使用一张表名为“sale_info”的表存放活动信息。随着业务的发展,该表的字段越来越多,为了防止一张表的字段太多,最简单的做法就是新增一个扩展表“sale_info_ext”,后续新增的字段都放到这张扩展表里。

 

这种简单的处理方式,解决了宽表问题。但同时又引入了新的问题,在查询活动信息时,由于业务数据分散在两张表里,经常需要做联表查询select a.xx,b.xx from sale_info a left join sale_info_ext b on a.id=b.sale_id where “省略其他条件。刚开始没有发现问题,但随着业务的增长,两张表的数据越来越多,应用程序经常出现“timeout”现象,通过分析慢查询日志有一条高频联表查询语句在中暴露出来。发现该问题后,初步做法是优化索引,也就是对省略其他条件字段加索引,但效果并不明显。

 

最后的做法是:分析者两张表的所有联表查询”sql语句,把主业务相关的字段迁移到sale_info表,把副业务相关的字段迁移到sale_info_ext表,对于主副业务都需要的常用字段 在两张表中做冗余存储。从而保证高频查询的sql语句,都是单表查询,最终解决该问题。对于一些低频查询的sql语句,仍然联表查询已经无所谓了(有时还应防止这些低频查询的sql语句转为高频)。

 

“表字段冗余”说起来原理简单,但实际操作有时会比较复杂。最重要的原则还是要根据业务划分,找准冗余点才能做到以空间换时间,否则空间消耗了时间减少--这就不是我们想要的效果了。

 

读写分离之镜像复制

 

对于“读多写少”的业务(其实大部分业务都是这种场景),最常见的以空间换时间的做法就是“读写分离”,要做读写分离 首先要做主备。对应“写”业务直接写主库,对于延迟要求不高的业务读从库。现在的问题就变为 主从同步问题,数据同步始终会有延迟(即便是采用数据库自带的,如mysqlReplication)。所以,对于不允许延迟的业务,只能读主库

 

所谓“镜像复制”就是所有备库的内容,跟主库内容完全一致。“镜像复制”,又存在两种情况一主多备多主多备。对于,延迟要求不高的业务,可以采用一主多备;对于,延迟要求高的业务,可以采用多主多备,具体做法是:写入数据时,写入多个主库(或者说写库),对于不允许延迟的业务直接从主库中读取数据,对于延迟要求不高的业务到从库读取数据。多主多备的架构设计如下:


提升数据库性能的重要手段--冗余
            
    
    博客分类: 架构mysql 读写分离镜像同步非对称同步 
 

 

这时会出现在同一个应用中有多个数据源的情况,一般做法是:在spring配置文件中配置多个数据源,获取数据源时通过一个工具类获取:

 

public class DataSourceUtil {
    private List<DataSource> readDatasources;//通过spring配置注入
    private List<DataSource> writeDatasources;//通过spring配置注入
    private Random random = new Random();
 
    public List<DataSource> getALLWrite(){//获取所有的写数据源
        return readDatasources;
    }
 
    public DataSource getOneWrite(){//随机获取一个写数据源 用于“非延时”读
        return readDatasources.get(random.nextInt(readDatasources.size()));
    }
 
    public DataSource getOneRead(){//随机获取一个写数据源 用于“可延时”读
        return readDatasources.get(random.nextInt(readDatasources.size()));
    }
}

 

最后这些数据源配置可以放到配置管理系统,可以实现在线切换数据库

 

读写分离之非对称复制

 

前一种镜像同步方式,是主库和备库的内容是完全相同的。在分库分表的系统中,做数据冗余还有另外一种数据冗余方式:非对称复制,主要作用就是减少查询时多张表的join操作。下面看一个真实的场景:

 

在笔者所在的一个活动页cms系统中,需要记录每个页面上的sku(商品编码),每天上线的活动页数量成千上万个,每个页面上对应的sku从几十个到几百个不等。可见数据量,是比较大,一般会进行分表存储。

 

应用中经常会 根据“活动页”id查询页面上的sku列表,为了减少表的join次数,我们用“活动页”idhash(可以直接取模,或者使用一致性hash) 进行分表,保证每个“活动页”id对应的sku都存在同一张表中。假设分8张表存储,分表方式如下:


提升数据库性能的重要手段--冗余
            
    
    博客分类: 架构mysql 读写分离镜像同步非对称同步 
 

 

现在要查询某个“活动页id”下的所有sku,首先通过“活动页idmod 8,计算出数据所在的表,然后通过一条简单的select语句查询该表就搞定。

 

但现在问题来了,“业务方”想要知道某个sku 今天在哪些页面上出现过,用来对比各个不同的页面推广效果。怎么办呢?如果按照上述分表,包含某个sku的的活动页id”分散在8张表里,需要进行7join操作或者查询8次进行合并 采用获取到所有的活动页id”

 

这种场景就可以使用“非对称复制”,在写入数据时,我们可以用另外的8张表存储上述相同的数据,唯一不同的地方就是分表规则改为对 sku编号进行hash(取模或者一致性hash都可以),分表方式如下:


提升数据库性能的重要手段--冗余
            
    
    博客分类: 架构mysql 读写分离镜像同步非对称同步 
 

 

好了,现在要查询某天某个sku出现过的活动页面有哪些,也就同样简单了,首先通过sku编号 mod 8获取到所在表,再通过一个简单的selec语句查询该表就搞定。

 

也就是说:如果查询条件是“活动页id”就使用第一种分表规则;反正就是使用第二种分表规则。都是单表唯一索引查询,查询速度也非常快。只是存储空间翻倍,这也是典型的空间换时间的场景。

 

这里讲的是单库操作,按照第二冗余方式所讲,如果需要做读写分离,这时有两种方案。方案一:两种分表方式的写入操作,都在同一个主库中进行,再通过数据库自带的同步工具同步到读库,查询时直接读读库。方案二:在写入数据时,两种分表方式分别写入不同的数据库,这时直接借助程序自己实现,也可以借助一些数据库中间件来完成。第二种方式虽然麻烦些,但如果数据量大 同时查询又很频繁,采用这种方式可以进一步实现不同的查询业务分库,单个数据库可以存储更多数据 并且降低单个数据库并发查询压力。

 

总结

 

仅对sql语句层面进行优化始终有局限,作为项目里的架构师一定要从更高层次出发,根据不同的业务场景采用不同架构设计手段,以减轻数据库的压力。这里不是说优化sql语句不重要,优化sql是需要首先做。

 

 

另外以空间换时间是架构设计中的常用手段,可以在各种不同的场景下使用,达到以多个廉价的pc机(空间),换取需要使用昂贵的大中型机采用达到的性能(时间)。

 

出处:

http://moon-walker.iteye.com/blog/2405545

 

  • 提升数据库性能的重要手段--冗余
            
    
    博客分类: 架构mysql 读写分离镜像同步非对称同步 
  • 大小: 33.2 KB
  • 提升数据库性能的重要手段--冗余
            
    
    博客分类: 架构mysql 读写分离镜像同步非对称同步 
  • 大小: 38 KB
  • 提升数据库性能的重要手段--冗余
            
    
    博客分类: 架构mysql 读写分离镜像同步非对称同步 
  • 大小: 35.6 KB
  • 提升数据库性能的重要手段--冗余
            
    
    博客分类: 架构mysql 读写分离镜像同步非对称同步 
  • 大小: 19.8 KB
  • 提升数据库性能的重要手段--冗余
            
    
    博客分类: 架构mysql 读写分离镜像同步非对称同步 
  • 大小: 20.9 KB