1。大家一定听说过微服务架构的介绍。简单来说,微服务架构就是把一个传统的单体应用开发成一组“小服务”。这些“小服务”可以运行在不同的机器上,它们运行在各自的进程中。“小服务”可以通过HTTPAPI等轻量级机制相互通信,这些“小服务”紧密围绕项目的业务需求进行开发,同时根据业务边界划分为独立的微服务.这些微服务看似独立又像是一个整体,形成了一个业务集群2.为什么分库微服务架构从业务逻辑实现的角度优化了系统的性能,但对数据库的负担是增加。假设一个分布式电子商务系统,那么这个系统会包含会员信息,订单信息,商品信息,商品库存信息等,数据存储在数据库中。访问数据,需要建立一个和数据库的连接,而数据库的连接是有限制的,在这样的业务环境下,会出现更多的高并发场景.如果所有的数据同时访问商城数据库,数据库显然经不起这样的折腾。如图:上文提到,微服务是按业务边界划分的,所以这些服务可以用不同的编程语言和不同的数据存储技术来编写,前提是要保持最低限度的集中管理。也就是说,每个微服务处理的数据都可以实现自治。因此,为了应对高并发,设计数据库可以分库进行,让每个微服务都有自己独立的数据库,就像订单微服务自治订单信息、支付流水信息、退款信息等.当订单微服务需要会员微服务的会员数据时,可以使用服务的通信机制,比如feign,达到分担传统模型压力的效果,如图图:同时对于每个分库,也可以分库部署。分库有相同的表,唯一不同的是存储的数据集。有效缓解单机单库的性能瓶颈和压力。随着需求的细化,项目的业务量巨大,这也导致项目的数据量巨大。数据库分库部署,有效降低磁盘负担。如图:3、为什么我们在分表微服务开发中经常遇到大表?所谓大表是指存储了数百万条甚至千万条记录的表。此类表中的数据太大,导致查询和插入数据库的时间太长。即使使用了索引,面对大量的数据也会降低查询效率,更不用说无法使用索引的情况了。下面是一些无法使用索引的情况:#在字符串前使用LIKE通配符mysql>SELECT*FROMtestWHEREnameLIKE'%Xiaowang';#函数操作mysql>SELECT*FROMtestWHEREUPPER(name)='ZS';分表是对表进行分区的主要目的是减轻数据库的负担,提高数据库的效率。表分区就是将数据库中的一个表按照一定的规则分解成多个更小的表。使用分区的表在逻辑上仍然是一张表,但物理存储被分成了多个部分。分区后的表的各个部分,可以独立处理数据。分区有以下优点:更大的存储空间和更快的查询速度,只需要扫描需要的分区表,然后合并结果。不会因为全表扫描而浪费不必要的资源。删除数据更方便。只需要删除对应分区的数据就可以跨磁盘存储,充分利用磁盘读取,提高吞吐量。分区是为了将数据段存储在多个位置,或者在同一个磁盘上,或者在不同的机器上。表分区的策略有很多种,根据不同的策略可以适配多种业务场景。比如可以按照表中属性值的范围来划分表,如下图,就是按照流时间来划分商城支付流量计:表划分之后,表面上看还是一个表,但数据散列到多个位置。应用程序读写时,仍然操作大表的表名,数据库系统自动整理分区数据。使用分区时需要注意:在MySQL8.0之前,支持创建表分区的存储引擎有InnoDB、Memory、MyISAM、MERGE。MySQL8.0之后,只支持InnoDB存储引擎。分区表必须一致,即同一张表分区后,每个分区表必须使用一致的存储引擎3.1。表分区表分区可以在创建数据库表时指定,格式如下:CREATETABLETABLE_NAME(…………)PARTITIONBYRANGE|LIST|HASH(TABLE_COLUMN)(PARTITIONP0……)文中提到有不同表分区策略,也可以称为不同类型:RANGE分区LIST分区COLUMNS分区HASH分区KEY分区子分区3.1.1。RANGE分区RANGE分区是根据给定的连续区间,它们相互重叠,数据会根据范围分配到不同的分区中。RANGE的partitionkey必须是单列int类型,每个partitionrange必须有序(后一个partitionrange的值大于前一个值)。假设一张表指定为RANGE分区,分为4个区。为了防止数据定义问题,最后一个区域设置为最大值“MAXVALUE”:mysql>CREATETABLEtestrange(->idINTPRIMARYKEYAUTO_INCREMENT,->nameVARCHAR(10))->PARTITIONBYRANGE(id)(->分区p0值小于(50),->分区p1值小于(100),->分区p2值小于(150),->分区p3值小于(MAXVALUE));3.1.2.LIST分区LIST分区的分区键的类型只能是int类型,LIST分区是根据枚举值列表分区的,枚举范围不能有重复值,如果插入的数据不在枚举范围内,会报错。#指定为LIST分区,分为2个区mysql>CREATETABLEtestlist(->idINTPRIMARYKEYAUTO_INCREMENT,->nameVARCHAR(10))->PARTITIONBYLIST(id)(->PARTITIONp0VALUESIN(1,2,3),->分区p1值在(4,6,9));mysql>INSERTINTOtestlistVALUES(null,'张三');mysql>INSERTINTOtestlistVALUES(2,'李四');#插入一个值不存在的列,会提示此表没有改值分区mysql>INSERTINTOtestlistVALUES(5,'李三');ERROR1526(HY000):Tablehasnopartition价值53.1。3、COLUMNS分区COLUMNS分区区别于RANGE分区和LIST分区的最大特点是支持多列分区。COLUMNS分区有两种形式,RANGECOLUMNS和LISTCOLUMNS分区。两种分区类型都支持整数类型、日期类型和字符类型。不同的是,如果COLUMNS分区中有多个分区键,当数据库要插入数据时,会先考虑是否满足第一个键。满足条件则写入Data,不满足条件则判断第二个key的条件,以此类推。mysql>CREATETABLEtestrancol(->sidINT,cidINT,PRIMARYKEY(sid,cid))->PARTITIONBYRANGECOLUMNS(sid,cid)(->PARTITIONp0VALUESLESSTHAN(1,10),->分区p1值小于(10,20));3.1.4.HASH分区HASH分区主要用于将一个整体的数据分散成若干个数量相等的分区。HASH分区有两种:(1)常规HASH分区,采用取模运算。假设分区数为4,则有0、1、2、3四个值,对应四个分区。由于使用了模运算,分区键必须是整数类型列或返回整数类型的表达式:mysql>CREATETABLEtesthash1(->idINTPRIMARYKEY,numVARCHAR(10))->PARTITIONBYHASH(id)PARTITIONS4;#如果此时插入的数据id为83,则模4取余得到3,将数据放在第三个partitionLINEAR关键字中。分配数据时,使用2的幂运算来分配数据。分配数据分两步:mysql>CREATETABLEtesthash2(->idINTPRIMARYKEY,numVARCHAR(10))->PARTITIONBYLINEARHASH(id)PARTITIONS4;#1.第一步计算值V#的计算方法是:V=POWER(2,CEILING(LOG(2,分区数)))#假设分区数为4,则log()的值为2#Ceiling()取最小的整数,仍然是2#次方返回2的2次方,最终V=4#2。第二步,对partitionkey和V-1#进行按位AND运算,假设partitionkey值is8And10:#然后插入8、10和4-1的3并进行按位与运算得到插入分区分别为0和2的分区3.1.5。KEY分区KEY分区类似于HASH分区,区别如下Point:KEY分区不允许使用自定义表达式作为分区键。如果KEY分区没有指定分区键,则默认使用表中的主键。如果没有主键,则使用非空唯一键。如果没有,您必须手动指定分区键。您可以使用非数字列作为分区键。KEY分区也分为常规KEY分区和线性KEY分区。mysql>CREATETABLEtesthash1(->idINTPRIMARYKEY,numVARCHAR(10))->PARTITIONBY[LINEAR]KEY(id)PARTITIONS4;3.1.6。Sub-partition子分区就是分区表的每一个分区,对于二级分区,使用RANGE和LIST对表进行分区,然后可以使用HASH或者KEY进行子分区。假设表有2个分区,而这2个分区再分为2个子分区,一共4个Partition,有两种写法:隐式创建子分区,子分区的名字自动创建并且相同名称,但不会冲突,输入小于1900的年份,按照HASH分区的规则(模2运算),分别存放在两个P0中:mysql>CREATETABLEtestfh(->idINT,purDATE)->按范围分区(年(pur))->按哈希分区(TO_DAYS(pur))->子分区2(->分区p0值小于(1900),->分区p1值小于最大值);显示子分区的创建,要求每个分区的子分区数量必须一致,分区的创建必须一致,即所有的子分区都是使用隐式或显式创建的,不能混用,并且子分区的名称必须是唯一的>分区p0值小于(1900)(->子分区s0,子分区s1),->分区p1值小于最大值(->子分区s2,子分区条款s3));3.2.表分区注意事项3.2.1.RANGE表分区注意事项使用RANGE策略进行表分区的好处是分区后的数据具有很好的扩展性,不需要进行数据迁移。如果插入的数据超出了原来建立的分区范围,可以根据实际情况考虑。在原有基础上增加表分区或水平部署同一张表来存储数据。添加表分区请参考如下格式:#RANGE分区在末尾添加分区,所以如果RANGE已经有包含最大值的分区,那么新添加的分区会报错ALTERTABLE表名ADDPARTITION(PARTITION分区名称VALESLESSTHAN(range));需要注意的是,对表进行RANGE分区时指定的partitionkey需要根据实际情况考虑,如果使用不当,会造成个别分区数据过多。假设电子商务系统需要存储有关订单的信息。当用户购买产品时,就会产生订单。订单通常包含多种不同的产品。可以设置订单商品表,用于存储订单的每个商品id、商品名称、价格、数量等数据。如果使用商品id作为partitionkey,就会出现“数据热点”问题。有的商品卖得好,分区数据就会多,有的商品卖得不好。好吧,那么数据就会很少。3.2.2.HASH表分区注意事项使用HASH策略的好处是可以解决数据热点问题。但是HASH表分区的分区数是固定的。如果数据过多达到瓶颈,则必须修改分区数。因此需要通过取模重新存储之前存储的数据,需要进行数据迁移。语法如下:#Add2partitionsALTERTABLEtablenamePARTITION2;#Reduce2partitionsALTERTABLEtablenameCOALESCEPARTITION2;3.2。3.MySQL表分区遇到NULL值当MySQL表分区遇到NULL值时,MySQL不会禁止分区键有NULL值,但不同的分区类型会将NULL值当成不同的数据,比如:在RANGE分区中,NULL值被视为最小的数字。在LIST分区中,NULL被视为字符串。如果NULL不在LIST的枚举范围内,就会报错。在HASH和KEY中,NULL值被视为0。因此,在使用分区时,要注意处理NULL值输入。为避免MySQL误判,将partitionkey设置为NOTNULL或默认值可以很好的应对这种情况。4.小结本文介绍为什么大多数微服务架构都采用分库分表设计的方式来设计数据库。当然,在分布式系统的设计过程中也需要注意一些问题。比如我们在创建数据库表的时候,是否可以先考虑表中数据的特点,提前提取一些不需要经常变化的内容,组成一个新的表?从某种程度上说,这种方法也是一种“分表”操作。同时,当分仓涉及到交易安全时,也必须引起注意。例如,用户在商城提交订单,系统需要锁定所购商品的库存。锁定的库存已回滚,但订单和库存有不同的数据。如何保证事务的原子性?如果都部署在本地,可以使用AOP来代理事务。在异机部署的情况下,undo_log表也可以通过阿里的Seata进行设置和代理。但是,在高并发的情况下,这些方法很可能造成“雪崩”。这时候也可以考虑消息队列,通过延迟队列完成库存解锁。
