当前位置:   article > 正文

微服务学习系列14:分库分表ShardingSphere_微服务 数据分片

微服务 数据分片

系列文章目录


目录

系列文章目录

前言

一、什么是 ShardingSphere

二、ShardingSphere-JDBC独立部署

三、ShardingSphere-Proxy独立部署

四、混合部署架构

五、数据分片

垂直分片 

水平分片

六、ShardingSphere 基础知识

逻辑表

真实表

​编辑

        ​编辑

七、ShardingSphere-JDBC 入门案例

单库多表

多库多表

集成mybatis-plus

引入POM

application.yml 配置 

Order 定义 

OrderMapper 定义 

单元测试 

八、ShardingSphere-JDBC 经典案例

十亿用户的系统

表结构怎么设计 

十亿用户系统的登录流程

总结




前言

传统的将数据集中存储至单一节点的解决方案,在性能、可用性和运维成本这三方面已经难于满足海量数据的场景。

从性能方面来说,由于关系型数据库大多采用 B+ 树类型的索引,在数据量超过阈值的情况下,索引深度的增加也将使得磁盘访问的 IO 次数增加,进而导致查询性能的下降; 同时,高并发访问请求也使得集中式数据库成为系统的最大瓶颈。

数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。 数据分片的有效手段是对关系型数据库进行分库和分表。分库和分表均可以有效的避免由数据量超过可承受阈值而产生的查询瓶颈。 除此之外,分库还能够用于有效的分散对数据库单点的访问量;分表虽然无法缓解数据库压力,但却能够提供尽量将分布式事务转化为本地事务的可能,一旦涉及到跨库的更新操作,分布式事务往往会使问题变得复杂。 使用多主多从的分片方式,可以有效的避免数据单点,从而提升数据架构的可用性。


一、什么是 ShardingSphere

Apache ShardingSphere 是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。

Apache ShardingSphere 设计哲学为 Database Plus,旨在构建异构数据库上层的标准和生态。 它关注如何充分合理地利用数据库的计算和存储能力,而并非实现一个全新的数据库。 它站在数据库的上层视角,关注它们之间的协作多于数据库自身。

https://shardingsphere.apache.org/

二、ShardingSphere-JDBC独立部署

ShardingSphere-JDBC 定位为轻量级 Java 框架,在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库,以 jar 包形式提供服务,无需额外部署和依赖,可理解为增强版的 JDBC 驱动,完全兼容 JDBC 和各种 ORM 框架。

  • 适用于任何基于 JDBC 的 ORM 框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template 或直接使用 JDBC;
  • 支持任何第三方的数据库连接池,如:DBCP, C3P0, BoneCP, HikariCP 等;
  • 支持任意实现 JDBC 规范的数据库,目前支持 MySQL,PostgreSQL,Oracle,SQLServer 以及任何可使用 JDBC 访问的数据库。

   

三、ShardingSphere-Proxy独立部署

ShardingSphere-Proxy 定位为透明化的数据库代理端,通过实现数据库二进制协议,对异构语言提供支持。 目前提供 MySQL 和 PostgreSQL 协议,透明化数据库操作,对 DBA 更加友好。

  • 向应用程序完全透明,可直接当做 MySQL/PostgreSQL 使用;
  • 兼容 MariaDB 等基于 MySQL 协议的数据库,以及 openGauss 等基于 PostgreSQL 协议的数据库;
  • 适用于任何兼容 MySQL/PostgreSQL 协议的的客户端,如:MySQL Command Client, MySQL Workbench, Navicat 等。

   

四、混合部署架构

ShardingSphere-JDBC 采用无中心化架构,与应用程序共享资源,适用于 Java 开发的高性能的轻量级 OLTP 应用; ShardingSphere-Proxy 提供静态入口以及异构语言的支持,独立于应用程序部署,适用于 OLAP 应用以及对分片数据库进行管理和运维的场景。

Apache ShardingSphere 是多接入端共同组成的生态圈。 通过混合使用 ShardingSphere-JDBC 和 ShardingSphere-Proxy,并采用同一注册中心统一配置分片策略,能够灵活的搭建适用于各种场景的应用系统,使得架构师更加自由地调整适合于当前业务的最佳系统架构。

五、数据分片

数据分片指按照某个维度将存放在单一数据库中的数据分散地存放至多个数据库或表中以达到提升性能瓶颈以及可用性的效果。 数据分片的有效手段是对关系型数据库进行分库和分表。分库和分表均可以有效的避免由数据量超过可承受阈值而产生的查询瓶颈。 除此之外,分库还能够用于有效的分散对数据库单点的访问量;分表虽然无法缓解数据库压力,但却能够提供尽量将分布式事务转化为本地事务的可能,一旦涉及到跨库的更新操作,分布式事务往往会使问题变得复杂。 使用多主多从的分片方式,可以有效的避免数据单点,从而提升数据架构的可用性。

通过分库和分表进行数据的拆分来使得各个表的数据量保持在阈值以下,以及对流量进行疏导应对高访问量,是应对高并发和海量数据系统的有效手段。 数据分片的拆分方式又分为垂直分片和水平分片。

垂直分片 

按照业务拆分的方式称为垂直分片,又称为纵向拆分,它的核心理念是专库专用。 在拆分之前,一个数据库由多个数据表构成,每个表对应着不同的业务。而拆分之后,则是按照业务将表进行归类,分布到不同的数据库中,从而将压力分散至不同的数据库。 下图展示了根据业务需要,将用户表和订单表垂直分片到不同的数据库的方案。

垂直分片往往需要对架构和设计进行调整。通常来讲,是来不及应对互联网业务需求快速变化的;而且,它也并无法真正的解决单点瓶颈。 垂直拆分可以缓解数据量和访问量带来的问题,但无法根治。如果垂直拆分之后,表中的数据量依然超过单节点所能承载的阈值,则需要水平分片来进一步处理。

水平分片

水平分片又称为横向拆分。 相对于垂直分片,它不再将数据根据业务逻辑分类,而是通过某个字段(或某几个字段),根据某种规则将数据分散至多个库或表中,每个分片仅包含数据的一部分。 例如:根据主键分片,偶数主键的记录放入 0 库(或表),奇数主键的记录放入 1 库(或表),如下图所示。

水平分片从理论上突破了单机数据量处理的瓶颈,并且扩展相对自由,是数据分片的标准解决方案。 

六、ShardingSphere 基础知识

表是透明化数据分片的关键概念。 Apache ShardingSphere 通过提供多样化的表类型,适配不同场景下的数据分片需求。

逻辑表

相同结构的水平拆分数据库(表)的逻辑名称,是 SQL 中表的逻辑标识。 例:订单数据根据主键尾数拆分为 3 张表,分别是 t_order_0 ,t_order_2, t_order_3,他们的逻辑表名为 t_order。

真实表

在水平拆分的数据库中真实存在的物理表。 即上个示例中的 t_order_0 ,t_order_2, t_order_3

        

七、ShardingSphere-JDBC 入门案例

单库多表

数据库设计 

数据库:test

订单表:t_order_0,t_order_1,t_order_2

  1. CREATE TABLE `t_order_0` (
  2. `order_id` bigint(200) NOT NULL,
  3. `order_no` varchar(100) DEFAULT NULL,
  4. `create_name` varchar(50) DEFAULT NULL,
  5. `price` decimal(10,2) DEFAULT NULL,
  6. PRIMARY KEY (`order_id`)
  7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  8. CREATE TABLE `t_order_1` (
  9. `order_id` bigint(200) NOT NULL,
  10. `order_no` varchar(100) DEFAULT NULL,
  11. `create_name` varchar(50) DEFAULT NULL,
  12. `price` decimal(10,2) DEFAULT NULL,
  13. PRIMARY KEY (`order_id`)
  14. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  15. CREATE TABLE `t_order_2` (
  16. `order_id` bigint(200) NOT NULL,
  17. `order_no` varchar(100) DEFAULT NULL,
  18. `create_name` varchar(50) DEFAULT NULL,
  19. `price` decimal(10,2) DEFAULT NULL,
  20. PRIMARY KEY (`order_id`)
  21. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

application.yml配置

  1. spring:
  2. main:
  3. allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
  4. profiles:
  5. active: sharding-master-slave
  6. server:
  7. port: 8808
  8. mybatis:
  9. mapper-locations: classpath:mapper/*.xml
  10. typeAliasesPackage: org.example.test.entity
  11. configuration:
  12. map-underscore-to-camel-case: true

 application-sharding-master-slave.yml配置

  1. spring:
  2. shardingsphere:
  3. datasource:
  4. names: m1 #配置库的名字,随意
  5. m1: #配置目前m1库的数据源信息
  6. type: com.alibaba.druid.pool.DruidDataSource
  7. driverClassName: com.mysql.jdbc.Driver
  8. url: jdbc:mysql://localhost:3306/test?useUnicode=true
  9. username: root
  10. password: 123456
  11. sharding:
  12. tables:
  13. t_order: # 指定t_order表的数据分布情况,配置数据节点
  14. actualDataNodes: m1.t_order_$->{0..2}
  15. tableStrategy:
  16. inline: # 指定t_order表的分片策略,分片策略包括分片键和分片算法
  17. shardingColumn: order_id
  18. algorithmExpression: t_order_$->{order_id % 3}
  19. keyGenerator: # 指定t_order表的主键生成策略为SNOWFLAKE
  20. type: SNOWFLAKE #主键生成策略为SNOWFLAKE
  21. column: order_id #指定主键
  22. props:
  23. sql:
  24. show: true

Order实体类代码如下

  1. @Data
  2. public class Order {
  3. private Long orderId;
  4. private String orderNo;
  5. private String createName;
  6. private BigDecimal price;
  7. }

  OrderMapper

  1. @Mapper
  2. public interface OrderMapper {
  3. /**
  4. * 新增数据
  5. */
  6. int insert(Order order);
  7. Order queryById(Long orderId);
  8. }
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="org.example.test.mapper.OrderMapper">
  4. <resultMap type="org.example.test.entity.Order" id="orderMap">
  5. <result property="orderId" column="order_id" />
  6. <result property="orderNo" column="order_no"/>
  7. <result property="createName" column="create_name"/>
  8. <result property="price" column="price"/>
  9. </resultMap>
  10. <!--查询单个-->
  11. <select id="queryById" resultMap="orderMap">
  12. select
  13. order_id, orderNo, createName,price
  14. from t_order
  15. where id = #{orderId}
  16. </select>
  17. <!--新增所有列-->
  18. <insert id="insert" keyProperty="orderId" useGeneratedKeys="true">
  19. insert into t_order(order_no,create_name, price)
  20. values (#{orderNo},#{createName},#{price})
  21. </insert>
  22. </mapper>

OrderTest 单元测试 

  1. @Slf4j
  2. @RunWith(SpringRunner.class)
  3. @SpringBootTest(classes = ExampleWeb.class)
  4. public class OrderTest {
  5. @Resource
  6. private OrderMapper orderMapper;
  7. @Test
  8. public void insert() {
  9. for (int i = 1; i <= 30; i++) {
  10. Order order = new Order();
  11. order.setOrderNo(i + "");
  12. order.setCreateName("test" + i);
  13. order.setPrice(BigDecimal.valueOf(i));
  14. orderMapper.insert(order);
  15. System.out.println(JSON.toJSONString(order));
  16. }
  17. }
  18. }

多库多表

application-sharding.yml 配置信息

  1. spring:
  2. shardingsphere:
  3. datasource:
  4. names: ds0,ds1 #配置库的名字,随意
  5. ds0: #配置目前m1库的数据源信息
  6. type: com.alibaba.druid.pool.DruidDataSource
  7. driverClassName: com.mysql.jdbc.Driver
  8. url: jdbc:mysql://localhost:3306/ds_0?useUnicode=true
  9. username: root
  10. password: 123456
  11. ds1: #配置目前m1库的数据源信息
  12. type: com.alibaba.druid.pool.DruidDataSource
  13. driverClassName: com.mysql.jdbc.Driver
  14. url: jdbc:mysql://localhost:3306/ds_1?useUnicode=true
  15. username: root
  16. password: 123456
  17. sharding:
  18. tables:
  19. t_order: # 指定t_order表的数据分布情况,配置数据节点
  20. actualDataNodes: ds$->{0..1}.t_order_$->{0..3}
  21. database-strategy:
  22. inline: # 指定t_order表的分片策略,分片策略包括分片键和分片算法
  23. shardingColumn: order_no
  24. algorithmExpression: ds$->{order_no % 2}
  25. tableStrategy:
  26. inline: # 指定t_order表的分片策略,分片策略包括分片键和分片算法
  27. shardingColumn: order_no
  28. algorithmExpression: t_order_$->{order_no % 4}
  29. keyGenerator: # 指定t_order表的主键生成策略为SNOWFLAKE
  30. type: SNOWFLAKE #主键生成策略为SNOWFLAKE
  31. column: order_id #指定主键
  32. props:
  33. sql:
  34. show: true

集成mybatis-plus

引入POM

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <parent>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-parent</artifactId>
  9. <version>2.3.4.RELEASE</version>
  10. <relativePath/> <!-- lookup parent from repository -->
  11. </parent>
  12. <groupId>org.example</groupId>
  13. <artifactId>sharding</artifactId>
  14. <version>1.0-SNAPSHOT</version>
  15. <dependencies>
  16. <dependency>
  17. <groupId>org.springframework.boot</groupId>
  18. <artifactId>spring-boot-starter-web</artifactId>
  19. </dependency>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-configuration-processor</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>mysql</groupId>
  26. <artifactId>mysql-connector-java</artifactId>
  27. <scope>runtime</scope>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.projectlombok</groupId>
  31. <artifactId>lombok</artifactId>
  32. <optional>true</optional>
  33. </dependency>
  34. <dependency>
  35. <groupId>com.alibaba</groupId>
  36. <artifactId>druid</artifactId>
  37. <version>1.2.1</version>
  38. </dependency>
  39. <dependency>
  40. <groupId>mysql</groupId>
  41. <artifactId>mysql-connector-java</artifactId>
  42. <version>5.1.44</version>
  43. </dependency>
  44. <!--添加mybatis分页插件支持 根据需求可要可不要-->
  45. <dependency>
  46. <groupId>com.github.pagehelper</groupId>
  47. <artifactId>pagehelper</artifactId>
  48. <version>5.2.0</version>
  49. </dependency>
  50. <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
  51. <dependency>
  52. <groupId>org.apache.commons</groupId>
  53. <artifactId>commons-lang3</artifactId>
  54. <version>3.11</version>
  55. </dependency>
  56. <dependency>
  57. <groupId>com.baomidou</groupId>
  58. <artifactId>mybatis-plus-boot-starter</artifactId>
  59. <version>3.4.3.3</version>
  60. </dependency>
  61. <dependency>
  62. <groupId>org.apache.shardingsphere</groupId>
  63. <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
  64. <version>4.1.0</version>
  65. </dependency>
  66. <dependency>
  67. <groupId>org.springframework.boot</groupId>
  68. <artifactId>spring-boot-starter-test</artifactId>
  69. <version>2.7.9</version>
  70. </dependency>
  71. <dependency>
  72. <groupId>junit</groupId>
  73. <artifactId>junit</artifactId>
  74. <version>4.13.1</version>
  75. <scope>test</scope>
  76. </dependency>
  77. <dependency>
  78. <groupId>com.alibaba</groupId>
  79. <artifactId>fastjson</artifactId>
  80. <version>2.0.14</version>
  81. </dependency>
  82. </dependencies>
  83. </project>

application.yml 配置 

  1. spring:
  2. shardingsphere:
  3. datasource:
  4. names: ds0,ds1 #配置库的名字,随意
  5. ds0: #配置目前m1库的数据源信息
  6. type: com.alibaba.druid.pool.DruidDataSource
  7. driverClassName: com.mysql.jdbc.Driver
  8. url: jdbc:mysql://localhost:3306/ds_0?useUnicode=true
  9. username: root
  10. password: 123456
  11. ds1: #配置目前m1库的数据源信息
  12. type: com.alibaba.druid.pool.DruidDataSource
  13. driverClassName: com.mysql.jdbc.Driver
  14. url: jdbc:mysql://localhost:3306/ds_1?useUnicode=true
  15. username: root
  16. password: 123456
  17. sharding:
  18. tables:
  19. myOrder: # 指定t_order表的数据分布情况,配置数据节点
  20. actualDataNodes: ds$->{0..1}.t_order_$->{0..3}
  21. database-strategy:
  22. inline: # 指定t_order表的分片策略,分片策略包括分片键和分片算法
  23. shardingColumn: order_id
  24. algorithmExpression: ds$->{order_id % 2}
  25. tableStrategy:
  26. inline: # 指定t_order表的分片策略,分片策略包括分片键和分片算法
  27. shardingColumn: order_id
  28. algorithmExpression: t_order_$->{order_id % 4}
  29. keyGenerator: # 指定t_order表的主键生成策略为SNOWFLAKE
  30. type: SNOWFLAKE #主键生成策略为SNOWFLAKE
  31. column: order_id #指定主键
  32. props:
  33. sql:
  34. show: true
  35. server:
  36. port: 8808

Order 定义 

  1. @Data
  2. @TableName("myOrder")
  3. public class Order {
  4. @TableId(type = IdType.ASSIGN_ID)
  5. private Long orderId;
  6. private Integer orderNo;
  7. private String createName;
  8. private BigDecimal price;
  9. }

OrderMapper 定义 

  1. public interface OrderMapper extends BaseMapper<Order> {
  2. }

单元测试 

  1. @Slf4j
  2. @RunWith(SpringRunner.class)
  3. @SpringBootTest(classes = ExampleWeb.class)
  4. public class OrderTest {
  5. @Resource
  6. private OrderMapper orderMapper;
  7. @Test
  8. public void insert() throws Exception {
  9. ExecutorService executorService = Executors.newFixedThreadPool(30);
  10. for (int i = 1; i <= 30; i++) {
  11. executorService.execute(() -> {
  12. Order order = new Order();
  13. order.setOrderNo(111);
  14. order.setCreateName("test" );
  15. order.setPrice(BigDecimal.valueOf(1));
  16. orderMapper.insert(order);
  17. System.out.println(String.format(">>>>>>>>>>>>>>>>>>>>>>>>>>%s,%s", order.getOrderId(), order.getOrderId() % 4));
  18. });
  19. }
  20. Thread.sleep(10000);
  21. }
  22. }

八、ShardingSphere-JDBC 经典案例

十亿用户的系统

十亿用户的系统,用户有多种登录方式,可以使用手机号、账号、邮箱、昵称等登录,这样的表结构应该怎样设计?登录流程大致是怎样的?

表结构怎么设计 

当出现多种登录方式的时候,就意味着一个用户对应的账号可能会有若干个。现在可能用手机和昵称登录,以后就可能用邮箱登录,甚至将来还可能通过微信、QQ、微博等第三方渠道登录。

我们最先想到的是每增加一种登录方式就新增一个字端,

字段字段描述
idLong
name登录名
mobile手机号码
email电子邮箱

但这样会产生以下问题: 

  1. 当用户登录的时候,我们需要根据用户的登录类型,先要知道去查找用户表的哪个字段才可以进行登录逻辑判断。例如,用户登录用手机号了,我们就要知道去表里查找对应的 mobile 字段去校验登录;登录用邮箱了,我们就要知道去表里查找对应的 email 字段才可以。这样做,代码逻辑会很复杂。

  2. 再增加一种登录方式的时候,我们还得给数据库的表里再增加一个字段,同时还得修改登录的代码。这显然违反了我们设计模式中的开闭原则。而且这种修改很容易造成线上 bug。每增加一种登录方式,就新增一种流程,成本有点过高了。

我们设计的表,必须易扩展

加记录比加字段要更容易扩展

因此方案如下:

创建一张用户登录 user_login表,专门用来处理登录。当新增登录类型的时候,只需要考虑增加一条记录即可:记录登录类型、登录名称以及相关密码,同时有个 user_id 字段,去和用户表做关联。

字段名称字段描述
idLong
name用户名
type1:登录名 2:手机号码 3:邮箱
password密码(多个用户名可共用一个密码,也可以存储token)
user_id用户表ID

用户表user存储基础信息 

字段名称字段描述
id用户ID主键
nick_name用户昵称
user_logo用户头像
user_names存储和user_id关联的账号

这样设计后,很明显就做到了易扩展。

假如我自己有两种登录方式,user_login 表的数据: 

idnametypepassworduser_id
1xiyangyang11234561
2xiyangyang@qq.com31234561

用户表(User)的数据:

idnick_nameuser_logouser_names
1声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签