依赖
点击查看完整内容
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>net.xdclass</groupId> <artifactId>sharding-jdbc</artifactId> <version>1.0-SNAPSHOT</version> <properties> <!--JDK版本,如果是jdk8则这里是 1.8--> <java.version>1.8</java.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.boot.version>2.5.5</spring.boot.version> <mybatisplus.boot.starter.version>3.4.0</mybatisplus.boot.starter.version> <lombok.version>1.18.16</lombok.version> <sharding-jdbc.version>4.1.1</sharding-jdbc.version> <junit.version>4.12</junit.version> <druid.version>1.1.16</druid.version> <!--跳过单元测试--> <skipTests>true</skipTests> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>${spring.boot.version}</version> </dependency> <!--<dependency>--> <!--<groupId>org.springframework.boot</groupId>--> <!--<artifactId>spring-boot-test</artifactId>--> <!--</dependency>--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <version>${spring.boot.version}</version> <scope>test</scope> </dependency> <!--mybatis plus和springboot整合--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>${mybatisplus.boot.starter.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.27</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <!--<scope>provided</scope>--> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>sharding-jdbc-spring-boot-starter</artifactId> <version>${sharding-jdbc.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>${spring.boot.version}</version> <configuration> <fork>true</fork> <addResources>true</addResources> </configuration> </plugin> </plugins> </build> </project>
ProductOrderMapper
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import net.xdclass.model.ProductOrderDO; public interface ProductOrderMapper extends BaseMapper<ProductOrderDO> { }
ProductOrderDO
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; import java.util.Date; @Data @TableName("product_order") @EqualsAndHashCode(callSuper = false) public class ProductOrderDO { //@TableId(value = "id",type = IdType.ASSIGN_ID) private Long id; private String outTradeNo; private String state; private Date createTime; private Double payAmount; private String nickname; private Long userId; }
配置文件
spring.application.name=xdclass-sharding-jdbc server.port=8080 # 打印执行的数据库以及语句 spring.shardingsphere.props.sql.show=true # 数据源 db0 spring.shardingsphere.datasource.names=ds0,ds1 # 第一个数据库 spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://8.142.19.202:3306/xdclass_shop_order_0?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.ds0.username=root spring.shardingsphere.datasource.ds0.password=你的密码 # 第二个数据库 spring.shardingsphere.datasource.ds1.type=com.zaxxer.hikari.HikariDataSource spring.shardingsphere.datasource.ds1.driver-class-name=com.mysql.cj.jdbc.Driver spring.shardingsphere.datasource.ds1.jdbc-url=jdbc:mysql://8.142.19.202:3306/xdclass_shop_order_1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true spring.shardingsphere.datasource.ds1.username=root spring.shardingsphere.datasource.ds1.password=你的密码 #配置workId spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1 #id生成策略 spring.shardingsphere.sharding.tables.product_order.key-generator.column=id spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE # 指定product_order表的数据分布情况,配置数据节点,行表达式标识符使用 ${...} 或 $->{...}, # 但前者与 Spring 本身的文件占位符冲突,所以在 Spring 环境中建议使用 $->{...} spring.shardingsphere.sharding.tables.product_order.actual-data-nodes=ds0.product_order_$->{0..1} # 指定product_order表的分片策略,分片策略包括【分片键和分片算法】 spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.sharding-column=user_id spring.shardingsphere.sharding.tables.product_order.table-strategy.inline.algorithm-expression=product_order_$->{user_id % 2}
这里我们看看我们的简单数据库,其实就是两个库,然后每一个库有两个表。(其他复杂情况同理可得,全是注释了)
单元测试一下
import lombok.extern.slf4j.Slf4j; import net.xdclass.DemoApplication; import net.xdclass.mapper.ProductOrderMapper; import net.xdclass.model.ProductOrderDO; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Date; import java.util.UUID; @RunWith(SpringRunner.class) @SpringBootTest(classes = DemoApplication.class) @Slf4j public class DbTest { @Autowired private ProductOrderMapper productOrderMapper; @Test public void testSaveProductOrder(){ for(int i=0; i<10;i++){ ProductOrderDO productOrderDO = new ProductOrderDO(); productOrderDO.setCreateTime(new Date()); productOrderDO.setNickname("小滴课堂i="+1); productOrderDO.setOutTradeNo(UUID.randomUUID().toString().substring(0,32)); productOrderDO.setPayAmount(100.00); productOrderDO.setState("PAY"); productOrderDO.setUserId(Long.valueOf(i+"")); productOrderMapper.insert(productOrderDO); } } }
- 单库下一般使用Mysql自增ID, 但是分库分表后,会造成不同分片上的数据表主键会重复。
- 需求
- 性能强劲
- 全局唯一
- 防止恶意用户规矩id的规则来获取数据
- 业界常用ID解决方案
- 数据库自增ID
- 利用自增id, 设置不同的自增步长,auto_increment_offset、auto-increment-increment
- DB1: 单数
- 从1开始、每次加2
- DB2: 偶数
- 从2开始,每次加2
- 缺点
- 依靠数据库系统的功能实现,但是未来扩容麻烦
- 主从切换时的不一致可能会导致重复发号
- 性能瓶颈存在单台sql上
- 数据库自增ID
- UUID
- 性能非常高,没有网络消耗
- 缺点
- 无序的字符串,不具备趋势自增特性
- UUID太长,不易于存储,浪费存储空间,很多场景不适用
- Redis发号器
- 利用Redis的INCR和INCRBY来实现,原子操作,线程安全,性能比Mysql强劲
- 缺点
- 需要占用网络资源,增加系统复杂度
- Snowflake雪花算法
- twitter 开源的分布式 ID 生成算法,代码实现简单、不占用宽带、数据迁移不受影响
- 生成的 id 中包含有时间戳,所以生成的 id 按照时间递增
- 部署了多台服务器,需要保证系统时间一样,机器编号不一样
- 缺点
- 依赖系统时钟(多台服务器时间一定要一样)
- 什么是雪花算法Snowflake
- twitter用scala语言编写的高效生成唯一ID的算法
- 优点
- 生成的ID不重复
- 算法性能高
- 基于时间戳,基本保证有序递增
- 计算机的基础知识回顾
- bit与byte
- bit(位):电脑中存储的最小单位,可以存储二进制中的0或1
- byte(字节):一个byte由8个bit组成
- 常规64位系统里面java数据类型存储字节大小
- int:4 个字节
- short:2 个字节
- long:8 个字节
- byte:1 个字节
- float:4 个字节
- double:8 个字节
- char:2 个字节
- 科普:数据类型在不同位数机器的平台下长度不同(怼面试官的严谨性)
- 16位平台 int 2个字节16位
- 32位平台 int 4个字节32位
- 64位平台 int 4个字节32位
- bit与byte
- 雪花算法生成的数字,long类,所以就是8个byte,64bit
- 表示的值 -9223372036854775808(-2的63次方) ~ 9223372036854775807(2的63次方-1)
- 生成的唯一值用于数据库主键,不能是负数,所以值为0~9223372036854775807(2的63次方-1)
分布式ID生成器Snowflake里面的坑你是否知道
- 布式ID生成器需求
- 性能强劲
- 全局唯一不能重复
- 防止恶意用户规矩id的规则来获取数据
- 全局唯一不能重复-坑
- 坑一
- 分布式部署就需要分配不同的workId, 如果workId相同,可能会导致生成的id相同
- 坑二:
- 分布式情况下,需要保证各个系统时间一致,如果服务器的时钟回拨,就会导致生成的 id 重复
- 啥时候会有系统回拨????
- 小滴课堂-老王闲着,人工去生产环境做了系统时间调整,应该不会这么傻吧
- 业务需求,代码里面做了系统时间同步
- 啥时候会有系统回拨????
- 分布式情况下,需要保证各个系统时间一致,如果服务器的时钟回拨,就会导致生成的 id 重复
- 坑一
- 配置实操
-
spring.shardingsphere.sharding.tables.product_order.key-generator.props.worker.id=1
方式一
订单id使用MybatisPlus的配置,ProductOrder类配置
@TableId(value = "id", type = IdType.ASSIGN_ID) 默认实现类为DefaultIdentifierGenerator雪花算法
方式二
使用Sharding-Jdbc配置文件,注释DO类里面的id分配策略
#id生成策略 spring.shardingsphere.sharding.tables.product_order.key-generator.column=id spring.shardingsphere.sharding.tables.product_order.key-generator.type=SNOWFLAKE
- 进阶:动态指定sharding jdbc 的雪花算法中的属性work.id属性
- 使用sharding-jdbc中的使用IP后几位来做workId, 但在某些情况下会出现生成重复ID的情况
- 解决办法时:在启动时给每个服务分配不同的workId, 引入redis/zk都行,缺点就是多了依赖
- 使用sharding-jdbc中的使用IP后几位来做workId, 但在某些情况下会出现生成重复ID的情况
本文作者为DBC,转载请注明。