左耳听风_061_60_性能设计篇之数据库扩展
你好,我是陈浩网名作耳读house.这节课呢我们来谈一谈数据库扩展。
首先呢我们来说读写分离,读写分离呢是数据库扩展最简单实用的玩法了。
那这种方法针对读多写少的业务场景啊还是很管用的,而且呢还可以有效的把业务做相应的隔离。
我们先来看文中这张示意图数据库呢只有一个写库,有两个读库所有的服务呢都写一个数据库。
那对于读操作来说呢,负a和服务b走重库a服d和服e走重库b服务c在重库a和重库b之间做轮选。
那这样的方法有这么几个好处。
那第一呢就是比较容易实现数据库的master slave的配置和服务框架里的读写分离啊都比较成熟,应用起来呢也很快。
那第二呢就是可以很好的把各个业务隔离开,不会因为一个业务把数据库拖死,而导致所有的业务都死掉。
那第三呢就是可以很好的分担数据库的读负载啊,毕竟读操作呢是最好数据库CPU的操作,但这样的方法呢也有一些不好的地方。
那第一呢就是写库有单点故障的问题,呃,如果写库出了性能问题,那所有的业务一样不可用。
那对于交易型的业务要得到高的写操作速度,这样的方式啊肯定是不行的。
第二呢就是数据库同步不实时,需要强一致性的读写操作呢,还是需要落在写库上。
综上所述啊,一般来说呢这样的玩法主要是为了减少读操作的压力。
那当然啊这样的读写分离看上去有点不足以让人满意。
那么我们呢还是为它找一个更靠谱的设计啊,就是CQRS.那关于CQRS呢,我在这里啊只做一个简单的介绍,更多的细节呢你可以上网自行google. Cqs的全称呢是common on and query responsibility segregation.也就是命令与查询职责分离。
它的原理呢是用户对于一个应用的操作可以分成两种。
一种是common啊,也就是我们的写操作,包括增删改。
那另一种呢是query操作啊,也就是读操作。
Query操作基本上来说啊就是在做数据的整合和显现。
而帽子操作这边呢会有更重的业务逻辑,那分离开这两种操作呢,可以在语义上做好区分命令,可帽呢不会返回结果,数据只会返回执行状态,但是会改变数据。
而查询corry呢会返回结果数据,但是不会改变数据对系统啊,没有副作用。
那这样一来呢可以带来一些好处。
那第一呢就是分工明确,可以负责不同的部分。
那第二呢就是业务上的命令和查询的职责。
分离能够提高系统的性能,可扩展性还有安全性。
并且在系统的演化中啊,能够保持高度的灵活性,能够防止出现CRUD模式中对查询或者修改中的某一方进行改动,而导致另一方出现问题的情况。
那第三呢就是逻辑清晰,能够看到系统中的哪些行为或者操作,导致了系统的状态变化。
那第四个好处呢就是可以从数据驱动转到任务驱动和事件驱动。
那如果把common操作变成event sourcing,那么就只需要记录不可修改的事件,并通过回溯事件得到数据的状态。
于是呢我们就可以把写操作给完全简化掉啊,也变成无状态的那这样呢就可以大幅度降低整个系统的副作用啊,并可以得到更大的并发和性能。
文中有一般的sourcing和CQRS的架构示意图啊,你可以了解一下。
然后呢,我们再来说分库分表十二点。
那一般来说呢影响数据库最大的性能问题有两个,一个呢是对数据库的操作,一个呢是数据库中数据的大小。
那对于前者,我们需要从业务上来优化,一方面呢要简化业务,不要在数据库上做太多的关联查询。
而对于一些更为复杂的啊用于做报表或者搜索的数据库操作呢,应该把它移到更适合的地方。
比如说用elastic search来做查询,用hadle或者别的数据分析软件来做报表分析。
那对于后者呢,如果数据库里的数据越来越多,那么也会影响我们的数据操作。
而且对于我们的分布式系统来说啊,后端服务都可以做成分布式的。
而数据库呢最好也可以拆开成分布式的读写分离。
也因为数据库里的数据太多而变慢,于是呢分库分表就成了我们必须用的手段。
我们来看文中这个分库的事例。
那其中有两个事儿,在这里需要提一下,一个是关于分库的策略,一个呢是关于数据访问层的中间线。
首先是关于分库的策略。
我们把数据库按照某种规则分成了三个库,比如按地理位置或者按日期啊,或者按某个范类分,或者按一种哈希撒列算法。
总之呢我们把数据库分到了三个库当中。
那其次呢是关于数据访问层。
为了不让我们前面的服务感知到数据库的变化,我们需要引入一个叫数据访问层的中间件啊,用来做数据路由。
但是呢老实说啊,这个数据访问层的中间件很不好写,其中要有解析circle语句的能力,还要根据解析好的circle语句来做路由。
但即使是这样,还有很多其他的麻烦事儿。
比如说啊我要做一个分页功能啊,需要读一组顺序的数据,或者呢是需要做max min或者count这样的操作。
于是呢你就要去三个库中分别求值啊,然后在数据访问层这里再合计处理返回。
但即使是这样,你也会遇到各种令人烦恼的事儿啊,比如说一个跨库的事物,你就要走叉a这样的啊两阶段提交的操作。
那这样呢就会把数据库的性能降到最低的那为了避免数据访问层的麻烦,分辨策略呢一般有这几种。
第一呢是按多租户的方式用租户ID来分。
那这样呢可以把租户给隔离开来。
比如说一个电商平台的商家中心啊,就可以按照商家的ID来分。
那第二呢就是按数据的种类来分。
比如说一个电商平台的商品库,可以按类目来分啊,或者按商家地域来分。
那第三呢就是通过范围来分。
那这样分片呢就可以保证在同一个分片中的数据是连续的。
于是呢我们的数据库操作,比如分页查询会更高效一些。
那一般来说呢,大多数情况是用时间来分片的。
比如说一个电商平台的订单中心啊,是按月份来分表了。
那这样呢就可以快速的检索和统计一段连续的数据。
那第四呢就是通过哈希散列算法来分。
那这个策略的目的呢是降低形成热点的可能性。
但是这样呢会带来两个问题,一个就是前面所说的跨库跨表的查询和事务的问题。
另一个问题呢就是如果要扩容啊,需要重新哈希部分或者全部的数据,那这些啊都是最常见的分屏模式。
那除此之外呢,你还应该考虑应用程序的业务要求和它的数据使用模式。
这里呢请注意几个非常关键的事宜。
那第一呢就是数据库分片必须考虑业务,从业务的角度入手,而不是从技术的角度入手。
那如果你不清楚业务啊,就无法做出好的分辨策略。
那第二呢就是请只考虑业务分片,请不要走哈希散列的分片方式啊,除非啊有个人拿着刀把你逼到墙角,你马上就有生命危险,你才能走哈希散列的分片方式。
那最后呢我们再来聊一聊数据库扩展的设计重点。
先说明一下,那这里呢我不会去讲真正数据库引擎的水平扩展的方法啊,我们只是在业务层上谈了一下数据扩展的两种方法。
那关于数据库引擎的水平扩展呢,你可以看一下我之前发过的分布式数据调度的相关论文,里面提到了AWS orura和google spanner的相关论文啊,提到了一些方法。
那接下来呢我们说一下从业务层上把单体的数据库给拆解掉的相关重点。
首先呢你需要把数据库和应用服务一同拆开,也就是说一个服务一个谱,这就是微服务的玩法啊,也是的服务好的玩法。
就是服务之间呢只能通过服务接口来通讯,不能通过访问对方的数据库。
在阿里斯内部呢每个服务啊都会有一个自己的数据库,比如说地址库啊、银行卡库啊等等。
那这样一来呢,你的数据库啊就会被天生的给拆成服务化的,而不是一个单体的库。
我们要知道在一个单体的库上做读写分离或者分片都是一件治标不治本的事儿。
但真正治本的方法呢就是要和服务一起拆解。
那当数据库也服务化之后呢啊我们才会在这个小的服务数据库上进行读写分离,或者分片的方式来获得更多的性能和吞吐量。
那这个呢就是整个设计模式的原则啊,先做孵化拆分,再做分片。
那对于分片来说呢,有两种分片模式,一种呢是水平分片,一种是垂直分片。
那水平分片呢就是我们之前说的那种分片,而垂直分片呢是把一张表中的一些字段啊放到一张表中。
那另一些字段呢放到另一张表中。
那垂直分片啊主要是把一些经常修改的数据和不经常修改的数据啊给分离开。
那这样在修改某个字段的数据的时候呢,就不会导致其他的字段数据被锁而影响性能。
比如说对于电商系统来说呢,商品的描述信息也不常改。
但是商品的库存和价格经常改,所以呢就可以把描述信息和库存价格分成两张表。
那这样呢就可以让商品的描述信息的查询啊更快。
我们所说的十二点,其实更多的是在说水平分片,那水平分片呢需要有一些注意事项。
那第一呢就是随着数据库中数据的变化,我们有可能啊需要定期的重新平衡,分片,以保证均匀分布并降低形成热点的可能性。
但是呢重新平衡是一项昂贵的操作。
如果要减少重新平衡的频率,你就需要确保每个分片不含足够的可用空间来处理未来一段时间的变化。
那另外呢,我们还需要开发用于快速重新平衡分片的工具和脚本。
那第二呢就是分片是静态的,而数据库的访问呢则是不可预期的,可能需要经常性的调整我们的分片。
那这样一来呢,成本太高了。
所以呢我们最好使用一个缩影表的方式来进行分片。
也就是说呢,我们把数据库的索引动态的记录在一个索引表当中。
那这样一来呢,我们就可以非常灵活的调度我们的数据了。
那当数据调度到另一台节点上的时候呢,我们只需要去索引表里改一下这个数据的位置就好了。
那第三呢就是如果程序必须要从多个分片检索数据的查询啊,可以使用并行任务从各个分片上提取这些数据,然后聚合到单个结果中。
但这个方法呢不可避免的会在一定程度上增加数据访问逻辑的复杂性。
那第四呢就是数据分片之后啊,我们很难在分片之间保持引用完整性和一致性,啊,也就是所谓的跨分片的事物。
因此呢,我们应当尽量减少会影响多个分片中的数据的操作。
那如果应用程序必须要跨分片修改数据,那么我们需要评估一致性啊,评估是否要采用两阶段提交的方式。
那还有一个要注意的地方,就是配置和管理大量分批啊,可能是一个挑战。
在做相应的变更的时候呢,一定要从生产线上拉出数据,然后根据数据啊计划好新的分片方式,并且要做好相当的测试工作。
否则这个事儿出了问题啊会是一个灾难性的问题。
好了,我们来总结一下今天分享的主要内容。
首先呢我介绍了单主库多重库的读写分离,并进一步用CQRS把语义区啊分成命令和查询。
那命令的执行呢可以变成事件溯源的方式,从而得到更大的并发和性能。
那随后呢我讲了分库分表的策略啊和它的数据访问层所做的抽象。
在最后呢我指出了数据库扩展的设计重点。
在下一节课呢我们将会聊一聊秒杀这个特定的场景啊,希望能对你有帮助。
也欢迎你在留言区分享一下你的数据库做过哪些形式的扩展呢?设计中有哪些方面的考量呢?文末呢我给出了分布式系统设计模式系列文章的目录,希望你能在这个列表里啊找到自己感兴趣的内容。