高并发下数据库方案演进

用户头像
superman
关注
发布于: 2020 年 07 月 15 日

1:问题与方案演进



随着系统规模扩大,系统数据库的压力在两个方面扩展,数据量与并发请求量。高并发请求包括高并发读与高并发写。根据压力生长过程,数据库方案有以下演进历程(不是严格的先后关系)

  • 单数据库

  • 读写分离

  • 业务分库

  • 数据归档

  • 分库分表

  • 分布式数据库

下面分别细说

2:方案

业务起始阶段都从单个数据库开始,不细说。

2.1 读写分离方案

数据库读的压力增加,单个实例不能支持时,进行读写分离。

读多写少的业务,通过部署多个数据库实例,数据库间进行主从同步,通过一写多读方式解决高并发的读问题。

适用场景

    读多写少。

    有复杂的统计分析等后台功能,只读不写数据库,但对对数据库压力大。

特点

   从库起到备份作用,主库服务器故障时可切换为主库

   高耗时的读操作放到从库减轻主库压力

   可根据需要挂载多份从库。

   分担主库的读请求压力

方案实现

    应用程序读写分离实现方式(分配)

             手动修改DAO层代码:读写分离,针对不同的请求使用不同的数据源。

              客户端中间件:应用程序少改动。sharding-jdbc,TDDL

              数据库代理层:应用程序访问代理程序,代理程序访问数据库。atlas,MaxScale.,代理加长了调用链路。

 数据库主从配置

  mysql主从复制可以支持同步复制,异步复制,半同步复制。

  •              异步复制:主库提交事务,从库异步复制,性能好。存在丢失数据及从库查不到数据的问题

  •              同步复制:等待从库复制后在反馈用户。性能差,一般不采用

  •              半同步复制:多个从库,部分复制成功即可。折中的方案。不丢失数据。



 写如果多一般都采用异步复制。

 

 同步延迟问题

              主库与从库间就存在短期数据不一致情况-主从同步延迟。刚更新的数据可能不到。

              解决思路:

                      写后延迟读:不在更新后立马查,业务上给用户个延迟。

                      读写放到同一个事务

                      二次读取:读取失败后

                      按业务分流:部分及时性要求高的读主库,要求不特别高的读从库。



2.2 业务分库

      如果系统业务越来越多,可按业务拆分到多个数据库实例,减小业务间的影响。

      拆分过程可先将压力大的业务拆分到独立的物理库,后续逐步按需要拆分压力大的业务。



2.3 数据归档



历史数据归档:

       很多表的数据只近期的会频繁查询,历史数据要么很少查或就不在需要了。可以对这部分数据迁移到历史表中,或定期删除。定期处理后使当前表保持数据量不太大,查询快速。

       比如:消息表,订单表,超过几个月基本不查。可以迁移到历史表(单个历史表或按时间分为多个历史表)。查询历史时业务上区分开来,历史数据查历史表。

        短信验证码表短期可能记录在数据库,但超过几个小时就没用了,这种可定期清理。将统计数据记录下来用于对账即可。或迁移到历史表,超过有效期(对账期)后删除即可。

             

 结果归档:

        某些历史数据后续查询主要用于统计计算,数据量如果比较多,统计会越来越慢,可以将数据按时间生成分阶段的统计结果,然后历史数据迁移。计算统计时用已有的历史统计结果再加上近期数据的统计值。

        比如账单:消费总计,可以按月,年统计,计算时,只计算当月的,然后跟历史统计结果再计算。减少查询数据。同时历史数据也可以迁移走,提高表的检索速度。



2.4 分库分表



2.4.1 拆分时机

读写分离只能解决读压力,写压力大时数据要分库。

如果单表数据量过大检索会非常慢,同时调整表结构也会非常慢,就需要分表。

         分库:解决高并发写,将写压力分担到多个数据库实例

         分表:解决单表过大。

 即:

如果表数据量过大,必须拆分为多个表,如果同时并发写压力比较多就把拆分的表同时放入不同的库。

      如果表数据量不特别大,但并发写的压力很大,也要将表拆为多份放入多个库

 

2.4.2 表拆分方法

        

  三种拆分方法     

垂直拆分:将单表不同列拆分到不用的表,将不同表分到不同的数据库或相同库。

     水平拆分:将相同表结构拆分到多个表或多个库。

     同时进行垂直拆分与水平拆分。

 

2.4.3 垂直拆分

        将经常访问的简略信息与不经常访问的详细信息拆分

        用户信息:id,loging_name,等是常用字段并且很少变更,而昵称,简介等属于用户信息的详细字段。

        很多访问尤其是业务关系检查时都只需要只用的身份字段,不需要详细字段。

        将用户表拆分为用户表与用户信息表。数据量不大的时候就用两个表。用户信息表大时只对用户信息表按用户id分片为多个表,而用户表不拆分。

     将索引关联字段与内容大字段拆分

            查询关联的索引字段固定,并且较小,内容字段不固定或较大。索引字段不拆分,内容字段拆分为多个表。

           比如: im系统的消息表,发送者,接收者,时间,用于检索的固定字段,而消息的内容类型,具体内容可能很大。查询时通过检索字段查询最新消息,内容表通过消息id 关联,内容表可按消息id拆分到多个库,多个表。

                    

      引入的问题:垂直拆分后查询详细信息要进行两次检索。   

2.4.4 水平拆分

         不符合垂直拆分条件的或垂直拆分后数据量仍然过大的再进行水平拆分

               

水平拆分如何拆-选择合适分片算法(key选择与路由算法)

        数据拆分后要满足数据分布均衡,访问负载均衡,查询时可以方便确认分片,而不是每次都在所有分片查找(查询时有分配的key)。

 

2.4.3.1分片key的选择

key选择要跟后续查询相关,查询时要尽量有key,才可以尽量的可以只在数据所在分片上查找。

             根据实际情况,可以是单一字段做key,也可以是多个字段联合做key,还可以根据业务特点不同数据用不同的字段做key.  注意key必须是不可变更的字段。

       1:单字段key:

查询比较简单主要用某个字段查询就选择该字段做key .

             2:联合key:

                         查询条件动态计算key

                         查询时会用几个字段,可用多个字段运算后获取需要的key

                          比如im系统消息表,私信与群消息都包括,但查询时查询条件包括查询用户及查询的会话类型,标记(查私信,群),时间。 根据类型,私信就用用户id做key,群消息就用群标记做key.

            双字段检索key-两个字段都可以有key

                         查询是可能用两个字段中的某一个,字段间设置关联,都可以计算出key

                          用户表:有时用用户id查,有时用登录名查。 可在用户id的生成规则中做手脚,用户id包括登录名的标记或登录名中取一个字节放入uid后面。 分片时key就选uid后面的这个字节。   

                3:key关联关系表或key关系缓存

                                  key使用一个字段,查询使用另一个字段,查询字段与key的关系用关联表或缓存存储。 

                                    还以用户表为例,用户登录名跟用户id的关系可以放在缓存中或索引表中。

                                    更多的是用在数据垂直拆分上:以消息表为例,消息内容大专门拆分出来,查询消息是,先根据条件查到消息id,在根据消息id获取消息内容,消息内容表根据消息id做分片。

                   4:多写

                    有些表检索条件可能是多种多样的,但对及时性要求不高。可以将数据按场景要求异步写入不同的表中,各个表按照其业务检索规则选择分片key。

2.4.3.2 路由算法

常见的有范围路由,哈希路由

范围算法简单,但可能出现不均匀。不同算法要考虑数据节点扩容时迁移的数据量。  

2.4.5 数据分片规划与迁移

        数据库如果添加新的实例来分担压力,就需要迁移数据,迁移时尽量只将新节点承担的数据迁移到新节点,而老节点间不进行数据迁移。

        如果是根据指定的路由表确定分片的,可以迁移指定数据库更改路由表。

        规划时根据数据的容量确定分表数,根据并发压力规划数据库数,并在基础上做扩展。

         比如预计将来压力最多增加多少,需要几个库支持,可将分片数多设置些。只是一个数据库实例承担了多个分片。需要扩展节点是,将新节点承担的分片迁移过来,然后调整分片指向的数据库即可。



2.4.6 拆分方案实现:

          分库分表实现比较复杂,除非业务简单,一般都采用中间件实现。

    MYCAT,sharding-jdbc,TDDL 等



2.4.7 拆分后的引入问题及处理

          拆分后没有join了,当然如果需要join的表可以用相同的规则拆到同一个分片也可以。

          字典表:可以存储多份方便做关联查询。

           拆分后不能使用数据库的自增id,需要采用分布式的id生成器,生成id.

拆分后事务问题:不能用事务,要自己解决失败补偿等



 2.5分布式数据库

                  从根上解决数据压力问题

                 后续再专题分析分布式数据库。

 

参考

     极客时间:从0学微服务-数据库架构,后端存储实战12-15,架构师训练营第五-六周等



用户头像

superman

关注

还未添加个人签名 2018.07.20 加入

还未添加个人简介

评论

发布
暂无评论
高并发下数据库方案演进