微服务化的数据库设计与读写分离

本文由  网易云 发布。html

 

做者:刘超,网易云解决方案架构师git

上文:github

微服务化的基石——持续集成数据库

微服务的接入层设计与动静资源隔离缓存

数据库永远是应用最关键的一环,同时越到高并发阶段,数据库每每成为瓶颈,若是数据库表和索引不在一开始就进行良好的设计,则后期数据库横向扩展,分库分表都会遇到困难。性能优化

对于互联网公司来说,通常都会使用MySQL数据库。数据结构

1、数据库的整体架构

咱们首先来看MySQL数据的整体架构以下:架构

这是一张很是经典的MySQL的系统架构图,经过这个图能够看出MySQL各个部分的功能。并发

当客户端链接数据库的时候,首先面对的是链接池,用于管理用户的链接,并会作必定的认证和鉴权。异步

链接了数据库以后,客户端会发送SQL语句,而SQL接口这个模块就是来接受用户的SQL语句的。

SQL语句每每须要符合严格的语法规则,于是要有语法解析器对语句进行语法解析,解析语法的原理如同编译原理中的学到的那样,从语句变成语法树。

对于用户属于的查询能够进行优化,从而能够选择最快的查询路径,这就是优化器的做用。

为了加快查询速度,会有查询缓存模块,若是查询缓存有命中的查询结果,查询语句就能够直接去查询缓存中取数据。

上面的全部的组件都是数据库服务层,接下来是数据库引擎层,当前主流的数据库引擎就是InnoDB。

对于数据库有任何的修改,数据库服务层会有binary log记录下来,这是主备复制的基础。

对于数据库引擎层,一个著名的图以下:

在存储引擎层,也有缓存,也有日志,最终数据是落到盘上的。

存储引擎层的缓存也是用于提升性能的,可是同数据库服务层的缓存不一样,数据库服务层的缓存是查询缓存,而数据库引擎层的缓存读写都缓存。数据库服务层的缓存是基于查询逻辑的,而数据库引擎引擎的缓存是基于数据页的,能够说是物理的。

哪怕是数据的写入仅仅写入到了数据库引擎层中的缓存,对于数据库服务层来说,就算是已经持久化了,固然这个时候会形成缓存页和硬盘上的页的数据的不一致,这种不一致由数据库引擎层的日志来保证完整性。

因此数据库引擎层的日志和数据库服务层的也不一样,服务层的日志记录的是一个个的修改逻辑,而引擎层的日志记录的是缓存页和数据页的物理差别。

2、数据库的工做流程

在收到一个查询的时候,MySQL的架构中的各个组件是如此工做的:

客户端同数据库服务层创建TCP链接,链接管理模块会创建链接,并请求一个链接线程。若是链接池中有空闲的链接线程,则分配给这个链接,若是没有,在没有超过最大链接数的状况下,建立新的链接线程负责这个客户端。

在真正的操做以前,还须要调用用户模块进行受权检查,来验证用户是否有权限。经过后,方才提供服务,链接线程开始接收并处理来自客户端的SQL语句。

链接线程接收到SQL语句以后,将语句交给SQL语句解析模块进行语法分析和语义分析。

若是是一个查询语句,则能够先看查询缓存中是否有结果,若是有结果能够直接返回给客户端。

若是查询缓存中没有结果,就须要真的查询数据库引擎层了,因而发给SQL优化器,进行查询的优化。若是是表变动,则分别交给insert, update, delete, create,alter处理模块进行处理。

接下来就是请求数据库引擎层,打开表,若是须要的话获取相应的锁。

接下来的处理过程就到了数据库引擎层,例如InnoDB。

在数据库引擎层,要先查询缓存页中有没有相应的数据,若是有则能够直接返回,若是没有就要从磁盘上去读取。

当在磁盘中找到相应的数据以后,则会加载到缓存中来,从而使得后面的查询更加高效,因为内存有限,多采用变通的LRU表来管理缓存页,保证缓存的都是常常访问的数据。

获取数据后返回给客户端,关闭链接,释放链接线程,过程结束。

3、数据库索引的原理

在整个过程当中,最容易称为瓶颈点的是数据的读写,每每意味着要顺序或者随机读写磁盘,而读写磁盘的速度每每是比较慢的。

若是加快这个过程呢?相信你们都猜到了就是创建索引。

为何索引可以加快这个过程呢?

相信你们都逛过美食城,里面众多家餐馆琳琅满目,若是你不着急呢,肚子不饿,对搜索的性能没有要求,就能够在商场里面慢慢逛,逛一家看一家,知道找到本身想吃的餐馆。可是当你饿了,或者大家约好了餐馆,你必定想直奔那个餐馆,这个时候,你每每会去看楼层的索引图,快速的查找你目标餐馆的位置,找到后,直奔主题,就会大大节约时间,这就是索引的做用。

因此索引就是经过值,快速的找到它的位置,从而能够快速的访问。

索引的另一个做用就是不用真正的查看数据,就可以作一些判断,例如商场里面有没有某个餐馆,你看一下索引就知道了,没必要真的到商场里面逛一圈,再如找出全部的川菜馆,也是只要看索引就能够了,不用一家一家川菜馆跑。

那么在MySQL中,索引是如何工做的呢?

MySQL的索引结构,每每是一棵B+树。

一棵m阶B+树具备以下的性质:

  1. 节点分索引节点和数据节点。索引节点至关于B树的内部节点,全部的索引节点组成一棵B树,具备B树的全部的特性。在索引节点中,存放着Key和指针,并不存放具体的元素。数据节点至关与B树的外部节点,B树的外部节点为空,在B+树中被利用了起来,用于存放真正的数据元素,里面包含了Key和元素的其余信息,可是没有指针。
  2. 整棵索引节点组成的B树仅仅用来查找具备某个Key的数据元素位于哪一个外部节点。在索引节点中找到了Key,事情没有结束,要继续找到数据节点,而后将数据节点中的元素读出来,或者二分查找,或者顺序扫描来寻找真正的数据元素。
  3. M这个阶数仅仅用来控制索引节点部分的度,至于每一个数据节点包含多少元素,与m无关。
  4. 另外有一个链表,将全部的数据节点串起来,能够顺序访问。

这个定义的比较抽象,咱们来看一个具体的例子。

从图中咱们能够看出,这是一个3阶B+树,而一个外部数据节点最多包含5项。若是插入的数据在数据节点,若是不引发分裂和合并,则索引节点组成的B树就不会变。

若是在71到75的外部节点插入一项76,则引发分裂,71,72,73成为一个数据节点,74,75,76成为一个数据节点,而对于索引节点来说至关于插入一个Key为74的过程。

若是在41到43的外部节点中删除43,则引发合并,41,42,61,62,63合并成一个节点,对于索引节点来说,至关于删除Key为60的过程。

查找的时候,因为B+树层高很小,因此可以比较快速的定位,例如咱们要查找值62,在根节点发现大于40则访问右面,小于70则访问左面,大于60则访问右面,在叶子节点的第二个,就找到了62,成功定位。

在MySQL的InnoDB中,有两种类型的B+树索引,一种称为聚簇索引,一种称为二级索引。

聚簇索引的叶子节点就是数据节点,每每是主键做为聚簇索引,二级索引的叶子节点存放的是KEY字段加主键值。于是经过二级索引访问数据,要访问两次索引。

还有一种索引的形式称为组合索引,或者复合索引,能够在多个列上创建索引。

这种索引的排序规则为,先比较第一列,在第一列相等的状况下,比较第二列,以此类推。

4、数据库索引的优缺点

数据库索引的优点最明显的就是减小I/O,下面分析几种场景。

对于=条件的字段,能够直接经过查找B+树的方式,经过不多的硬盘读取次数(至关于B+树层高),就可以到达叶子节点,而后直接定位到数据的位置。

对于范围的字段,因为B+树里面都是排好序的,范围能够很快的经过树进行定位。

同理对于orderby/group by/distinct/max/min,因为B+树是排好序的,也是可以很快的获得结果的。

还有一个常见的场景称为索引覆盖数据。例如A, B两个字段做为条件字段,常出现A=a AND B=b,同时select C, D时候,每每会建联合索引(A, B),是一个二级索引,因此搜索的时候,经过二级索引的B+树可以很快的找到相应的叶子节点和记录,可是记录中有的是聚簇索引的ID,因此还须要查找一次聚簇索引的B+树,找到真正的表中的记录,而后在记录中,将C,D读取出来。若是创建联合索引的时候为(A, B, C, D),则在二级索引的B+树中就有了全部的数据,能够直接返回了,减小了一次搜索树的过程。

固然索引确定是有代价的,天下没有免费的午饭。

索引带来的好处可能是读的效率的提升,而索引带来的代价就是写的效率的下降。

插入和修改数据,都有可能意味着索引的改变。

插入的时候,每每会在主键上建设聚簇索引,于是主键最好使用自增加,这样插入的数据就老是在最后,并且是顺序的,效率比较高。主键不要使用UUID,这样顺序比较随机,会带来随机的写入,效率比较差。主键不要使用和业务有关,由于与业务相关意味着会被更新,将面临着一次删除和从新插入,效率会比较差。

经过上面对于B+树的原理的介绍,咱们能够看出B+树的分裂代价仍是比较大的,而分裂每每就产生于插入的过程当中。

而对于数据的修改,则基本至关于删除再插入,代价也比较大。

对于一些字符串的列的二级索引,每每会形成随机的写入和读取,对I/O的压力也比较大。

5、解读数据库军规背后的原理

了解了这两种索引的原理,咱们就可以解释为何不少所谓的数据库的军规长这个样子了。下面咱们来一一解释。

  • 什么状况下应该使用组合索引而非单独索引呢?

假设有条件语句A=a AND B=b,若是A和B是两个单独的索引,在AND条件下只有一个索引发做用,对于B则要逐个判断,而若是使用组合索引(A, B),只要遍历一棵树就能够了,大大增长了效率。可是对于A=a OR B=b,因为是或的关系,于是组合索引是不起做用的,于是可使用单独索引,这个时候,两个索引能够同时起做用。

  • 为何索引要有区分度,组合索引中应该讲有区分度的放在前面?

若是没有区分度,例如用性别,至关于把整个大表分红两部分,查找数据仍是须要遍历半个表才能找到,使得索引失去了意义。

  • 若是有组合索引,还须要单列索引吗?

若是组合索引是(A, B),则对于条件A=a,是能够用上这个组合索引的,由于组合索引是先按照第一列进行排序的,因此不必对于A单独创建一个索引,可是对于B=b就用不上了,由于只有在第一列相同的状况下,才比较第二列,于是第二列相同的,能够分布在不一样的节点上,没办法快速定位。

  • 索引是越多越好吗?

固然不是,只有在必要的地方添加索引,索引不但会使得插入和修改的效率下降,并且在查询的时候,有一个查询优化器,太多的索引会让优化器困惑,可能没有办法找到正确的查询路径,从而选择了慢的索引。

  • 为何要使用自增主键

由于字符串主键和随机主键会使得数据随机插入,效率比较差,主键应该少更新,避免B+树和频繁合并和分裂。

  • 为何尽可能不使用NULL

NULL在B+树里面比较难以处理,每每须要特殊的逻辑进行处理,反而下降了效率。

  • 为何不要在更新频繁的字段上创建索引

更新一个字段意味着相应的索引也要更新,更新每每意味着删除而后再插入,索引原本是一种事先在写的阶段造成必定的数据结构,从而使得在读的阶段效率较高的方式,可是若是一个字段是写多读少,则不建议使用索引。

  • 为何在查询条件里面不要使用函数

例如ID+1=10这种条件,索引是事先写入的时候生成好的,ID+1这种操做在查询阶段,索引无能为例,没办法把全部的索引都先作一个计算,而后再比较吧,代价太大了,于是应该使用ID=10-1。

  • 为何不要使用NOT等负向查询条件

你能够想象一下,对于一棵B+树,跟节点是40,若是你的条件是等于20,就去左面查,你的条件等于50,就去右面查,可是你的条件是不等于66,索引应该咋办?还不是遍历一遍才知道。

  • 为何模糊查询不要以通配符开头

对于一棵B+树来说,若是根是字符def,若是通配符在后面,例如abc%,则应该搜索左面,例如efg%,则应该搜索右面,若是通配符在前面%abc,则不知道应该走哪一面,仍是都扫描一遍吧。

  • 为何OR要改为IN,或者使用Union

OR查询条件的优化每每比较难找到最佳的路径,尤为是OR的条件比较多的时候,尤为如此,对于同一个字段,使用IN就好一些,数据库会对IN里面的条件进行排序,并统一经过二分搜索的方法处理。对于不一样的字段,使用Union,则可让每个子查询都使用索引。

  • 为何数据类型应该尽可能小,经常使用整型来代替字符型,长字符类型能够考虑使用前缀索引?

由于数据库是按照页存放的,每一页的大小是同样的,若是数据类型比较大,则页数会比较多,每一页放的数据会比较少,树的高度会比较高,于是搜索数据要读取的I/O数目会比较多,插入的时候节点也容易分裂,效率会下降。使用整型来代替字符型可能是这个考虑,整型对于索引有更高的效率,例如IP地址等。若是有长字符类型须要使用索引进行查询,为了避免要使得索引太大,能够考虑将字段的前缀进行索引,而非整个字段。

6、查询优化的方法论

要找到须要优化的SQL语句,首先要收集有问题的SQL语句。

MySQL 数据库提供了慢SQL日志功能,经过参数slow_query_log,获取执行时间超过必定阈值的SQL语录列表。

没有使用索引的SQL语句,能够经过long_queries_not_using_indexes参数开启。

min_examined_row_limit,扫描记录数大于该值的SQL语句才会被记入慢SQL日志。

找到有问题的语句,接下来就是经过explainSQL,获取SQL的执行计划,是否经过索引扫描记录,能够经过建立索引来优化执行效率。是否扫描记录数过多。是否持锁时间过长,是否存在锁冲突。返回的记录数是否较多。

接下来能够定制化的优化。没有被索引覆盖的过滤条件涉及的字段,在区分度较大的字段上建立索引,若是涉及多个字段,尽可能建立联合索引。

扫描记录数很是多,返回记录数很少,区分度较差,从新评估SQL语句涉及的字段,选择区分度高的多个字段建立索引。

扫描记录数很是多,返回记录数也很是多,过滤条件不强,增长SQL过滤条件schema_redundant_indexes查看有哪些冗余索引。

若是多个索引涉及字段顺序一致,则能够组成一个联合索引schema_unused_indexes查看哪些索引从没有被使用。

7、读写分离的原理

数据库每每写少读多,因此性能优化的第一步就是读写分离。

主从复制基于主节点上的服务层的日志实现的,而从节点上有一个IO线程读取这个日志,而后写入本地。另有一个线程从本地日志读取后在从节点从新执行。

如图是主从异步复制的流程图。在主实例写入引擎后就返回成功,而后将事件发给从实例,在从实例上执行。这种同步方式速度较快,可是在主挂了的时候,若是尚未复制,则可能存在数据丢失问题。

数据库同步复制也不一样,是当从节点落盘后再返回客户端,固然这样会使得性能有所下降,网易数据库团队是经过组提交,并行复制等技术将性能提上来。

有了主从复制,在数据库DAO层能够设置读写分离策略,也有经过数据库中间件作这个事情的。

其实数据库日志还有不少其余用处,如使用canal(阿里巴巴开源项目: 基于MySQL数据库binlog的增量订阅&消费)订阅数据库的binlog,能够用于更新缓存等。

 

了解 网易云 :
网易云官网:https://www.163yun.com/
新用户大礼包:https://www.163yun.com/gift
网易云社区:https://sq.163yun.com/