ShardingSphere 分库分表入门实战

news2024/11/26 11:05:11

在这里插入图片描述

分库分表

需求分析

如果我们的平台发展迅速,用户量激增,从数据库层面去思考,哪个表的数据会最大呢?

回顾一下我们的数据库设计:

1)app 应用表

显然不会,成百上千的应用已经多,但对数据库而已,这还是小量级

2)question 题目表

不太可能,一个应用一般最多也就几十个题目

3)scoring_result 评分结果表

不太可能,一个应用对应不会有多少结果,比如 MBTI 也就 16 个。

4)user 表

有可能,如果用户达到几千万级,那么确实挺多了

5)user_answer 用户答题记录表

一个用户可以对同个应用多次答题,也可以在多个应用多次答题,理论上如果用户量足够大,那么这个表肯定是最先遇到瓶颈的。

除了清理数据外,常见的一种优化方案是分库分表。

什么是分库分表

这里我们先简单了解下分库分表的场景。

随着用户量的激增和时间的堆砌,存在数据库里面的数据越来越多,此时的数据库就会产生瓶颈,出现资源报警、查询慢等场景。

首先单机数据库所能承载的连接数、I/O 及网络的吞吐等都是有限的,所以当并发量上来了之后,数据库就渐渐顶不住了。

而且如果单表的数据量过大,查询的性能也会下降。因为数据越多底层存储的 B+ 树就越高,树越高则查询 I/O 的次数就越多,那么性能也就越差。

分库和分表怎么区分呢?

把以前存在 一个数据库 实例里的数据拆分成多个数据库实例,部署在不同的服务器中,这是分库。

把以前存在 一张表 里面的数据拆分成多张表,这是分表。

一般而言:

  • 分表:是为了解决由于单张表数据量多大,而导致查询慢的问题。大致三、四千万行数据就得拆分,不过具体还是得看每一行的数据量大小,有些字段都很小的可能支持更多行数,有些字段大的可能一千万就顶不住了。
  • 分库:是为了解决服务器资源受单机限制,顶不住高并发访问的问题,把请求分配到多台服务器上,降低服务器压力。

比如电商网站的使用人数不断增加, 用户数不断增加,订单数也日益增长,此时就应该把用户库和订单库拆开来,这样就能降低数据库的压力,且对业务而言数据分的也更清晰,并且理论上订单数会远大于用户数,还可以针对订单库单一升配。

由于电商网站品类不断增加,在促销活动的作用下订单取得爆炸式增长,如果所有订单仅存储在一张表中,这张表得有多大?

因此此时就需要根据订单表进行分表,可以按时间维度,比如 order_202401、order_202402 来拆分,如果每天的订单量很大,则可以通过 order_20240101、order_20240102 这样拆分。

技术选型

1、通用选型思路

在公司内如果进行技术选型,一般有以下几个考察点:

1)场景适配,考察选择的框架或组件所提供的功能是否符合当前的需求。

2)团队能力,考察当前团队是否有能力使用和运维选择的三方框架和组件。比如团队没人会 c++ ,你选个 c++ 开发的组件,这可能不太合适,后续遇到问题一脸懵逼,学习成本大。

3)技术栈匹配度,考察引入的组件是否有很多附带的依赖,比如引入 rpc 框架,可能需要配套引入注册中心、配置中心等等,需要确认目前项目是否已经拥有这部分能力,评估成本。

4)社区与生态,选择的开源组件社区是否活跃,资料是否丰富,不要如果遇到个小众的不活跃的社区,出了 bug 可能都没人修,且需要观察生态,比如我们用spring 生态就很好,基本上 java 需要的能力例如 orm 支持、大数据支持等等都有,如果选择生态不好的,后续要进行一些扩展这部分的成本也很大。

主要是以上四点,最终就是考虑成本和收益再做决定。

2、分库分表开源组件选型

常见的分库分表开源组件有:ShardingSphere、MyCat、Cobar 等。

官方文档:https://github.com/apache/shardingsphere

官方文档:https://github.com/MyCATApache/Mycat2

官方文档:https://github.com/alibaba/cobar

看下 star 数其实就有个选择预期了,Sharding-JDBC 相比而言功能更丰富,还支持读写分离、数据脱敏、分布式事务等等。

并且 ShardingSphere 不仅支持嵌入式的 Sharding-JDBC,还支持 Sharding-Proxy(独立代理服务)和Sharding-Sidecar(服务网格模式)。

再者 ShardingSphere 非常活跃,社区庞大且资料丰富,项目迭代也非常快,毕竟是 apache 项目。

因此本项目选择 ShardingSphere 内的 Sharding-JDBC。

Sharding-JDBC 原理

Sharding-JDBC 核心原理其实很简单,可以用几个字总结:

改写SQL

比如我们想根据 appId 来将对应的用户答题记录表进行分表。

将 appId % 2 等于 0 的应用的所有用户的答题记录都划分到 user_answer_0,等于 1 的应用的所有用户的答题记录都划分到 user_answer_1。

按照我们正常的想法处理逻辑就是:

if(appId % 2 == 0{
    userAnswer0Service.save(userAnswer);
} else {
    userAnswer1Service.save(userAnswer);
}

而用了 Sharding-JDBC 后,我们只要写好配置,Sharding-JDBC 就会根据配置,执行我们上面的逻辑,在业务代码上我们就可以透明化分库分表的存在,减少了很多重复逻辑!

它会解析 SQL ,根据我们指定的 分片键,按照我们设置的逻辑来计算得到对应的路由分片(数据库或者表),最终改写 SQL 后进行 SQL 的执行。

方案设计

分库分表的核心是确定按照什么维度(或者字段)进行拆分,一般会选择唯一的、业务合理的、能够均匀分配的字段。

你在哪个字段加索引,就用哪个字段分表,核心在于用户的查询,一定要根据业务的实际情况来。尽量避免出现跨表和跨库查询。

对于本项目,user_answer 有个天然的拆分字段即 appId,不同应用的用户答题记录没有关联,因此我们可以根据 appId 拆解 user_answer 表。

实现流程比较简单:

  1. 新建 user_answer_0 和 user_answer_1,作为 user_answer 表的分表
  2. 引入 Sharding-JDBC
  3. 配置文件中设置分表逻辑

后端开发

新建表

直接复制 user_answer 的 DDL 表结构,改个名称即可

-- 用户答题记录表
create table if not exists user_answer_0
(
    id              bigint auto_increment primary key,
    appId           bigint                             not null comment '应用 id',
    appType         tinyint  default 0                 not null comment '应用类型(0-得分类,1-角色测评类)',
    ......
    index idx_appId (appId),
    index idx_userId (userId)
) comment '用户答题记录' collate = utf8mb4_unicode_ci;

-- 用户答题记录表
create table if not exists user_answer_1
(
    id              bigint auto_increment primary key,
    appId           bigint                             not null comment '应用 id',
    appType         tinyint  default 0                 not null comment '应用类型(0-得分类,1-角色测评类)',
    ......
    index idx_appId (appId),
    index idx_userId (userId)
) comment '用户答题记录' collate = utf8mb4_unicode_ci;
引入依赖
        <!-- https://github.com/apache/shardingsphere -->
        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
            <version>5.2.0</version>
        </dependency>
配置
spring:
  shardingsphere:
    #数据源配置
    datasource:
      # 多数据源以逗号隔开即可
      names: question_craft
      question_craft:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        jdbc-url: jdbc:mysql://localhost:3306/question_craft?allowPublicKeyRetrieval=true&useSSL=false&autoReconnect=true&characterEncoding=utf8
        username: root
        password: 123456
    # 规则配置
    rules:
      sharding:
        # 分片算法配置
        sharding-algorithms:
          # 自定义分片规则名
          answer-table-inline:
            ## inline 类型是简单的配置文件里面就能写的类型,其他还有自定义类等等
            type: INLINE
            props:
              algorithm-expression: user_answer_$->{appId % 2}
        tables:
          user_answer:
            actual-data-nodes: question_craft.user_answer_$->{0..1}
            # 分表策略
            table-strategy:
              standard:
                sharding-column: appId
                sharding-algorithm-name: answer-table-inline

配置解析:

1)需要将数据源挪至 shardingsphere 下

2)指定数据源的名字和 url 等配置

3)自定义分片规则,即 answer-table-inline,分片算法为 user_answer_$->{appId % 2} ,这个含义就是根据 appId % 2 的结果拼接表名,改写 SQL

4)设置对应的表使用分片规则,即 tables:user_answer:table-strategy,指定分片键为 appId,分片的规则是 answer-table-inline

测试
@SpringBootTest
public class UserAnswerShardingTest {

    @Resource
    private UserAnswerService userAnswerService;

    @Test
    void test() {

        UserAnswer userAnswer1 = new UserAnswer();

        userAnswer1.setAppId(1L);
        userAnswer1.setUserId(1L);
        userAnswer1.setChoices("1");
        userAnswerService.save(userAnswer1);

        UserAnswer userAnswer2 = new UserAnswer();
        userAnswer2.setAppId(2L);
        userAnswer2.setUserId(1L);
        userAnswer2.setChoices("2");
        userAnswerService.save(userAnswer2);

        UserAnswer userAnswerOne = userAnswerService.getOne(Wrappers.lambdaQuery(UserAnswer.class).eq(UserAnswer::getAppId, 1L));
        System.out.println(JSONUtil.toJsonStr(userAnswerOne));

        UserAnswer userAnswerTwo = userAnswerService.getOne(Wrappers.lambdaQuery(UserAnswer.class).eq(UserAnswer::getAppId, 2L));
        System.out.println(JSONUtil.toJsonStr(userAnswerTwo));
    }
}

注意,分表后,一定不能更新分表字段!

在这里插入图片描述
可以看到测试结果,appId % 2,余数为0,则用的是表user_answer_0,余数等于1,则使用表user_answer_1。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2222914.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESP32移植Openharmony设备开发---(6)Mutex互斥锁

Mutex互斥锁 官方文档&#xff1a;OpenAtom OpenHarmony 基本概念 互斥锁又称互斥型信号量&#xff0c;用于实现对共享资源的独占式处理。当有任务持有时&#xff0c;这个任务获得该互斥锁的所有权。当该任务释放它时&#xff0c;任务失去该互斥锁的所有权。当一个任务持有互…

2024年最新苹果iOS证书申请创建App详细图文流程

iOS 证书设置指南&#xff1a; 对于开发者来说&#xff0c;在没有Mac电脑或对Xcode等开发工具不熟悉的情况下&#xff0c;如何快速完成IOS证书制作和IPA文件提交至开发者中心一直是一个难题。但是现在&#xff0c;有了初雪云提供的极简工具&#xff0c;您可以轻松实现这两个任…

Appium中的api(一)

目录 1.基础python代码准备 1--参数的一些说明 2--python内所要编写的代码 解释 2.如何获取包名和界面名 1-api 2-完整代码 代码解释 3.如何关闭驱动连接 4.安装卸载app 1--卸载 2--安装 5.判断app是否安装 6.将应用放到后台在切换为前台的时间 7.UIAutomatorViewer的使用 1--找…

git rebase的常用场景: 交互式变基, 变基和本地分支基于远端分支的变基

文章目录 作用应用场景场景一&#xff1a;交互式变基(合并同一条线上的提交记录) —— git rebase -i HEAD~2场景二&#xff1a;变基(合并分支) —— git rebase [其他分支名称]场景三&#xff1a;本地分支与远端分支的变基 作用 使git的提交记录变得更加简洁 应用场景 场景…

【华为HCIP实战课程十六】OSPF虚链路Vlink,网络工程师

一、vlink续 区域内部的路由优于区域之间的路由,区域之间优于外部路由,外部路由类型1优于外部类型2 只有同一级别的路由才会对比cost <R3>tracert 11.1.1.1 traceroute to 11.1.1.1(11.1.1.1), max hops: 30 ,packet length: 40,press CTRL_C to break 1 10.1.35.5 …

Wave-Mamba 论文总结

题目&#xff1a;Exchange&#xff08;交换&#xff09; Wave-Mamba: Wavelet State Space Model&#xff08;小波状态空间模型&#xff09;for Ultra-High-Definition&#xff08;超高清&#xff09;Low-Light Image Enhancement&#xff08;弱光图像增强&#xff09; 论文&am…

stm32单片机基于rt-thread 的 串行 Flash 通用驱动库 SFUD 的使用

1024程序员节&#xff5c;征文 一、sfud 通用驱动库介绍 SFUD 是一款开源的串行 SPI Flash 通用驱动库。由于现有市面的串行 Flash 种类居多&#xff0c;各个 Flash 的规格及命令存在差异&#xff0c; SFUD 就是为了解决这些 Flash 的差异现状而设计&#xff0c;能够支持不同品…

二叉树习题其一Java【力扣】【算法学习day.8】

前言 书接上篇文章介绍的链表基础知识—>二叉树理论&#xff0c;这篇文章我们将通过习题来掌握哈希表的使用。 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向&#xff08;例如想要掌握基础用法&#xff0c;该刷哪些题&#xff1f;&#xff09;我的解析也不会…

PHP多功能图片编辑器

PHP多功能图片编辑器 前言效果图功能说明平台支持情况部分源码领取源码下期更新 前言 PHP多功能图片编辑器 工具箱网站源码无需数据库上传即用&#xff0c;测试了一下还可以&#xff0c;免费分享自行研究。 效果图 功能说明 ✓ 无需上传&#xff0c;使用浏览器自身进行转换 …

049_python基于Python的热门微博数据可视化分析

目录 系统展示 开发背景 代码实现 项目案例 获取源码 博主介绍&#xff1a;CodeMentor毕业设计领航者、全网关注者30W群落&#xff0c;InfoQ特邀专栏作家、技术博客领航者、InfoQ新星培育计划导师、Web开发领域杰出贡献者&#xff0c;博客领航之星、开发者头条/腾讯云/AW…

多模态大语言模型(MLLM)-Deepseek Janus

论文链接&#xff1a;https://arxiv.org/abs/2410.13848 代码链接&#xff1a;https://github.com/deepseek-ai/Janus 本次解读Janus: Decoupling Visual Encoding for Unified Multimodal Understanding and Generation 前言 Deepseek出品&#xff0c;必属精品。 创新点 传…

如何在Java应用中发送短信

很多业务场景里&#xff0c;我们都需要发送短信&#xff0c;比如登陆验证码、告警、营销通知、节日祝福等等。 这篇文章&#xff0c;我们聊聊 Java 应用中如何优雅的发送短信。 1 客户端/服务端两种模式 Java 应用中发送短信通常需要使用短信服务提供商提供的短信 API 。 我…

多ip访问多网站

多IP访问多网站 1.预配操作 [rootlocalhost ~]# mount /dev/sr0 /mnt mount: /mnt: WARNING: source write-protected, mounted read-only. [rootlocalhost ~]# systemctl stop firewalld ----------关闭防火墙 [rootlocalhost ~]# setenforce 0 -------关闭selinux2.安装n…

技术人员的自我修炼:在变化中成长

引言 在技术的海洋中&#xff0c;我们每个人都是一名探索者&#xff0c;不断学习、适应、成长。作为一名技术人员&#xff0c;我们不仅要面对自身技能的提升和心态的调整&#xff0c;还要应对外部环境的不断变化。本文将探讨技术人员如何在内部修炼和外部适应中找到平衡&#…

UE5 喷射背包

首选创建一个输入操作 然后在输入映射中添加&#xff0c;shift是向上飞&#xff0c;ctrl是向下飞 进入人物蓝图中编写逻辑&#xff0c;变量HaveJatpack默认true&#xff0c;Thrust为0 最后

【C语言】编译和链接(编译环境和运行环境)

文章目录 一、翻译环境和运行环境二、翻译环境1.编译预处理编译汇编 2.链接 四、运行环境 一、翻译环境和运行环境 在 ANSI C 的任何⼀种实现中&#xff0c;存在两个不同的环境&#xff0c;如下&#xff1a; 翻译环境&#xff1a;在翻译环境中&#xff0c;会通过编译和链接两个…

鸿蒙软件开发中常见的如何快速自动生成二维码?QRCode组件

QRCode 用于显示单个二维码的组件。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 二维码组件的像素点数量与内容有关&#xff0c;当组件尺寸过小时&#xff0c;可能出现无法展示内容的情况&…

在 Controller 层对系统作防御性编程

简介 Web 开发中无论是 MVC 还是 DDD 架构 Controller 层都是系统的门面&#xff0c;既对外的接口&#xff0c;对内的接口&#xff0c;一般情况下任何错误必须组织在 Controller 层 如何作 在 Controller 层中的接口使用 try-catch Slf4j RestController("/") Re…

MobileNetV2实现实时口罩检测tensorflow

项目源码获取方式见文章末尾&#xff01; 回复暗号&#xff1a;13&#xff0c;免费获取600多个深度学习项目资料&#xff0c;快来加入社群一起学习吧。 **《------往期经典推荐------》**项目名称 1.【Informer模型复现项目实战】 2.【卫星图像道路检测DeepLabV3Plus模型】 3.【…

[LeetCode] 230. 二叉搜索树中第K小的元素

题目描述&#xff1a; 给定一个二叉搜索树的根节点 root &#xff0c;和一个整数 k &#xff0c;请你设计一个算法查找其中第 k 小的元素&#xff08;从 1 开始计数&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3,1,4,null,2], k 1 输出&#xff1a;1示例 2&am…