MyCAT——MyCAT(下载和安装、配置 Mycat 相关文件、出错时解决方法、mycat 分片规则【仅列举五种】)、分布式环境下的主键生成(Java 代码处理—— LEAF的两种模式))
一、MyCAT
当数据库是主从结构的时候,Java 怎么去操控呢?
两种方法,一种是配置多数据源,但是麻烦不好操控。另外一种是使用数据库中间件。
这个数据库中间件搭在 java 和数据库之间,以后要操作数据库,java 代码不需要同时操作这么多数据库,只需要操作数据库中间件即可。通过数据库中间件去操作多数据库。
虽然前面搭了主从架构的 mysql,但是工作中更多用到的是双 M(master) 结构。就是两个 mysql ,互为对方主机。为什么这么搭建?因为主从切换方便;像前面搭建的主从结构,主机写入,从机读取,当然从机也可以写数据,但是数据会乱套,且主机不能同步到从机的数据;现在这种双 M 结构的搭建,你往哪台机子上面写入数据,另外一台机子都可以同步数据;而且两台机子中有机子挂了,可以迅速的切换到另外一台机子上面去,非常的方便;且将来挂掉的机子重启了又可以重新同步数据。
1、引言和介绍
a、什么是 MyCAT
什么是MyCAT:
简单的说,MyCAT就是:
- 一个彻底开源的,面向企业应用开发的“大数据库集群”
- 支持事务、ACID、可以替代Mysql的加强版数据库中间件
- 一个可以视为“Mysql”集群的企业级数据库,用来替代昂贵的Oracle集群
- 一个融合内存缓存技术、Nosql技术、HDFS大数据的新型SQL Server
- 结合传统数据库和新型分布式数据仓库的新一代企业级数据库产品
- 一个新颖的数据库中间件产品
MyCAT的目标是:低成本的将现有的单机数据库和应用平滑迁移到“云”端,解决数据存储和业务规模迅速增长情况下的数据瓶颈问题。
MyCAT的关键特性:
- 支持 SQL 92标准
- 支持Mysql集群,可以作为Proxy使用
- 支持JDBC连接ORACLE、DB2、SQL Server,将其模拟为MySQL Server使用
- 支持galera for mysql集群,percona-cluster或者mariadb cluster,提供高可用性数据分片集群
- 自动故障切换,高可用性
- 支持读写分离,支持Mysql双主多从,以及一主多从的模式
- 支持全局表,数据自动分片到多个节点,用于高效表关联查询
- 支持独有的基于E-R 关系的分片策略,实现了高效的表关联查询
- 多平台支持,部署和实施简单
b、MyCAT 架构
c、Mycat解决的问题
d、Mycat对多数据库的支持
e、分片策略
MyCAT支持水平分片与垂直分片:
- 水平分片:一个表格的数据分割到多个节点上,按照行分隔。
- 垂直分片:一个数据库中多个表格A,B,C,A存储到节点1上,B存储到节点2上,C存储到节点3上。
MyCAT通过定义表的分片规则来实现分片,每个表格可以捆绑一个分片规则,每个分片规则指定一个分片字段并绑定一个函数,来实现动态分片算法。
- Schema:逻辑库,与MySQL中的Database(数据库)对应,一个逻辑库中定义了所包括的Table。
- Table:表,即物理数据库中存储的某一张表,与传统数据库不同,这里的表格需要声明其所存储的逻辑数据节点DataNode。在此可以指定表的分片规则。
- DataNode:MyCAT的逻辑数据节点,是存放table的具体物理节点,也称之为分片节点,通过DataSource来关联到后端某个具体数据库上
- DataSource:定义某个物理库的访问地址,用于捆绑到Datanode上
mycat 是不保存数据的。
2、Mycat的下载及安装
官方网站:
http://www.mycat.org.cn/
github地址
https://github.com/MyCATApache
因为 mycat 是用 java 开发的,所以需要 jdk,jdk 的下载和环境变量配置参考前面博客。
- 第一步:把MyCat的压缩包上传到linux服务器
- 第二步:解压缩,得到mycat目录
- 第三步:进入mycat/bin,启动MyCat
- 启动命令:./mycat start
- 停止命令:./mycat stop
- 重启命令:./mycat restart
- …
- 注意:可以使用mysql的客户端直接连接mycat服务。默认服务端口为8066
3、配置 Mycat 相关文件
a、配置 schema.xml
先介绍 Schema.xml
Schema.xml作为MyCat中重要的配置文件之一,管理着MyCat的逻辑库、表、分片规则、DataNode以及DataSource。弄懂这些配置,是正确使用MyCat的前提。这里就一层层对该文件进行解析:
- schema: 标签用于定义MyCat实例中的逻辑库。
- Table: 标签定义了MyCat中的逻辑表。
- dataNode: 标签定义了MyCat中的数据节点,也就是我们通常说所的数据分片。
- dataHost: 标签在mycat逻辑库中也是作为最底层的标签存在,直接定义了具体的数据库实例、读写分离配置和心跳语句。
注意:若是LINUX版本的MYSQL,则需要设置为Mysql大小写不敏感,否则可能会发生表找不到的问题。在MySQL的配置文件中my.cnf 位置在etc目录下[mysqld] 中增加一行:
lower_case_table_names = 1
先提醒一句:如果要配置主从结构,至少要 6 个 mysql 才能组成,因为这里指定最少需要 3 组 mysql 主从,也就是 6 个 mysql。
所以这里为了省事,就不搭主从了,就使用三个库来模拟效果,虽然不那么形象,但效果是一样的。
首先在创建三个数据库(模拟主从效果):
接着:
一个 datahost 可以代表主机和从机。
上图中有个地方写错了,配置 readHost 不是在 writeHost 的下面,而是里面:
到此就配置好了。
如果是三个 mysql 主从,估计 datanode 就要 6 个,datahost 就要三个。还要配置 readHost。
a、配置 server.xml
先介绍这个配置文件: server.xml几乎保存了所有mycat需要的系统配置信息。最常用的是在此配置用户名、密码及权限。
然后就使用链接 mysql 的工具就能够链接 mycat,用户名密码看上图设置。
4、出错时查看日志
如果有问题,一般主要是两种。
第一步是如果测试链接有问题,那就是配置 mycat 的文件没配置好。
第二种是如果能链接,但是在里面打开数据库有问题,那就是 mycat 连 mysql 有问题。
有问题这里只能去查看日志,没有别的办法。
在 mycat / logs / 目录下有个 wrapper.log 文件,里面记载了错误信息。查看即可。
5、简单使用 MyCAT
在 mycat 里面,执行创建数据表的语句,这时就会发现,三个数据库同时都有数据表:
然后看效果:
这时为三个库分别加上不同的数据,然后回到 mycat 查询数据,会把三个库的数据集合到一起:
诸如其他的 where 也是没有问题的。这一块的查询没啥好说的。
6、mycat 分片规则
mycat 的分片规则有 17 种,但是并不难,只要会用一个,其他的都会用,这里只列举五种。
分片规则讨论的就是,比如执行一条插入 sql,这个数据应该被插入到哪个库里面去。分片就是定义这些规则。
a、global
有一些表,数据量不大,也不怎么修改,主要是查询操作,例如系统配置表,这一类表我们可以使用 global 这种分片规则。且系统配置表没多少数据,一般也很少修改。
global 的特点是,该表会在所有的库中都创建,而且每一个库中都保存了该表的完整数据。
具体配置方式,就是在 schema.xml 的 table 节点中添加一个 type 属性,值为 global:
增删改都是同时对几个表一起操作,但是查询并不会结果合并,只会查出一个表的结果。好处是查询只需要查一个库即可,效率比较高;但是缺点是增删改效率低,比较慢,但是一开始也说了这个适用于数据量少且很少修改的表,所以对这些设置 global 没毛病。
在 schema.xml 的 table 节点中添加一个 type 属性,值为 global:
注意:这里因为 table 节点里面有个正常的分片规则,先要暂时去掉:
去掉绿色的,增加红色的即可。
保存退出后,再重启:
因为修改了定义,所以要删除且重新创建一张表:
然后随便执行一个插入语句,查看结果即可。只要所有表都有相同的数据,就是成功。
这时执行查询语句,会发现只会返回一张表的数据。
b、sharding-by-intfile
sharding-by-intfile 这个是枚举分片,就是在数据表中专门设计一个字段,以后根据这个字段的值来决定数据插入到哪个 dataNode 上。
这种分片就很像之前博客中的分区,男和女的例子。
注意,在配置 sharding-by-intfile 规则时,一定要删除 type=“global” ,否则配置不会生效。具体配置如下:
还需要改动这里:mycat 目录下的 conf / rule.xml 。 这里是各种分片的规则:
在 rule.xml 文件中,首先找到 tableRule 的名字为 sharding-by-intfile 的节点,这个节点中定义了两个属性,一个是 columns 表示一会在数据表中定义的枚举列的名字(数据表中一会需要创建一个名为 sharding_id 的列,这个列的值决定了该条数据保存在哪个数据库实例中),这个名字可以自定义;另外一个属性叫做 algorithm ,这是指 sharding-by-intfile 所对应的算法名称。根据这个名称,可以找到具体的算法:
可见上面的算法是 hash-int,根据这个,还是在当前文件下,去查找:
如果下载 mycat 的源码就能看到这个类,这里就不去看了。接着会看到会读取一个 partition-hash-int.txt 的配置文件,里面定义分片规则,和各种东西之间的配置关系。
接着退出去,继续改 partition-hash-int.txt 文件的内容:
可以看到:
这里的意思就是,如果 sharing_id 为 10000 的话,就保存到 0号库里面,后面的同理。当然这里是可以改的:
保存退出重启后,根据这个规则,需要在数据库里面加个字段:
同理,前面使用的表要删掉,不然不生效。
接着测试:
接着就可以在三个库里面中的一个找到一个是 zhangsan 的数据。
现在查询的话,各个数据库的数据并不会合并,而是都会展示出来。
当然查询的时候可以使用分片:
那么这种查询不会查另外两个库的节点,只会去查 sharing_id 为 2 的节点。
c、auto-sharding-long
auto-sharding-long 表示按照既定的范围去存储数据。就是提前规划好某个字段的值在某个范围时,相应的记录存到某个 dataNode 中。
更改方式是一样的。去配置文件里面把 rule 改为 auto-sharding-long 即可。
接着同样是根据上面的步骤去找 rule.xml 文件,然后查看算法,再去查找算法对应的文件,再去查看这个文件:
这里就是这个范围。
同样的,这里意思是 id 为 0-500万的,会进到 0 号库里面。后面同理。这里可以自行更改测试:
后续就是根据上图自行插入不同 id 去测试是否进到对应的库中。这里就不展示了。
提示:这里的范围是闭区间,0-5 都会进到 0 号库里面。
d、mod-long
取模:根据表中的某一个字段,做取模操作。根据取模的结果将记录存放在不同的 dataNode 上。这种方式不需要再添加额外字段。
就是取余数的意思。
默认是 id 去取模。
跟 3 取模,无非就是 0 1 2;如果给到其他数字,比如有 3,4 这些,那么有的数据会存不进去。如果是查询,查询出来的结果会为 null。
e、sharding-by-murmur
前面介绍的几种方式,都存在一个问题,如果数据库要扩容,之前配置会失效,可能会出现数据库查询紊乱。因此我们要引入一致性 hash 这样一种分片规则,可以解决这个问题。
意思是说如果一开始没设计好,后面想再扩容,就扩不了。所以这里就提供了这个,可以让我们去扩容。
具体配置和前面一样:
另外需要注意,在 rule.xml 中修改默认 dataNode 的数量:
修改完后,重启 MyCat ,进行测试。
二、分布式环境下的主键生成
两种思路:
整体上来说,这个问题有两种不同的思路:
- 让数据库自己搞定
- Java 代码来处理主键,然后直接插入数据库中即可。
这两种思路又对应了不同的方案,我们一个一个来看。
1、数据库自己搞定(了解即可)
数据库自己搞定,就是说我在数据插入的时候,依然不考虑主键的问题,希望继续使用数据库的主键自增,但是很明显,原本默认的主键自增现在没法用了,我们必须有新的方案。
数据库分库分表之后的结构如下图(假设数据库中间件用的 MyCat):
此时如果原本的 db1、db2、db3 继续各自主键自增,那么对于 MyCat 而言,主键就不是自增了,主键就会重复,用户从 MyCat 中查询到的数据主键就有问题。
我们可以直接修改 MySQL 数据库主键自增的起始值和步长。
首先我们可以通过如下 SQL 查看与此相关的两个变量的取值:
SHOW VARIABLES LIKE 'auto_increment%
可以看到,主键自增的起始值和步长都是 1。
我们可以修改起始值跟步长,比如起始值是10,步长是2,那么每次插入之后的主键就是 10、12、14 等等,以此类推。
起始值在定义表的时候就可以设置(在创建表语句的最后加):
这样,起始 id 就是从 100 开始。
起始值好改,在定义表的时候就可以设置,步长我们可以通过修改这个配置实现:
set @@auto_increment_increment=x;
通过设置上面这些,我们可以在创建不同的表的时候,分别设置起始值和步长,这样合并后的 id 就不会起冲突:
比如:
第一张表:1 4 7
第二张表:2 5 8
第三张表:3 6 9
但这么设置很多弊端,也很麻烦。
2、Java 代码处理
a、UUID(用的很少)
b、SNOWFLAKE(雪花算法)
下面给出一个雪花算法的工具类,仅供参考:
// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)
private final static long twepoch = 1288834974657L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 毫秒内自增位
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/* 上次生产id时间戳 */
private static long lastTimestamp = -1L;
// 0,并发控制
private long sequence = 0L;
private final long workerId;
// 数据标识id部分
private final long datacenterId;
public IdWorker(){
this.datacenterId = getDatacenterId(maxDatacenterId);
this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}
/**
* @param workerId
* 工作机器ID
* @param datacenterId
* 序列号
*/
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获取下一个ID
*
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
return nextId;
}
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
/**
* <p>
* 获取 maxWorkerId
* </p>
*/
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
StringBuffer mpid = new StringBuffer();
mpid.append(datacenterId);
String name = ManagementFactory.getRuntimeMXBean().getName();
if (!name.isEmpty()) {
/*
* GET jvmPid
*/
mpid.append(name.split("@")[0]);
}
/*
* MAC + PID 的 hashcode 获取16个低位
*/
return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}
/**
* <p>
* 数据标识id部分
* </p>
*/
protected static long getDatacenterId(long maxDatacenterId) {
long id = 0L;
try {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
if (network == null) {
id = 1L;
} else {
byte[] mac = network.getHardwareAddress();
id = ((0x000000FF & (long) mac[mac.length - 1])
| (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
id = id % (maxDatacenterId + 1);
}
} catch (Exception e) {
System.out.println(" getDatacenterId: " + e.getMessage());
}
return id;
}
用法如下:
这种方式其实用的也不多。
更为常见的是下面这种
c、LEAF(重点)
Leaf 是美团开源的分布式 ID 生成系统,最早期需求是各个业务线的订单 ID 生成需求。在美团早期,有的业务直接通过 DB 自增的方式生成 ID,有的业务通过 Redis 缓存来生成 ID,也有的业务直接用 UUID这种方式来生成 ID。以上的方式各自有各自的问题,因此美团决定实现一套分布式 ID 生成服务来满足需求目前 Leaf 覆盖了美团点评公司内部金融、餐饮、外卖、酒店旅游、猫眼电影等众多业务线。在4C8G VM 基础上,通过公司 RPC 方式调用,QPS 压测结果近 5w/s,TP999 1ms(TP=TopPercentile,Top 百分数,是一个统计学里的术语,与平均数、中位数都是一类。TP50、TP90 和 TP99等指标常用于系统性能监控场景,指高于 50%、90%、99% 等百分线的情况)。目前 LEAF 的使用有两种不同的思路,号段模式和 SNOWFLAKE 模式,你可以同时开启两种方式,也可
以指定开启某种方式(默认两种方式为关闭状态)。
我们从 GitHub 上 Clone LEAF 之后,它的配置文件在 leafserver/src/main/resources/leaf.properties 中,各项配置的含义如下:
可以看到,如果使用号段模式,需要数据库支持;如果使用 SNOWFLAKE 模式,需要 Zookeeper 支持。
这个项目是开源项目(也是个 SpringBoot 项目),可以直接去 github 上搜索 leaf 下载。
这里先使用第一种模式——号段模式
(1)号段模式
号段模式还是基于数据库,但是思路有些变化,如下:
- 利用 proxy server 从数据库中批量获取 id,每次获取一个 segment (step 决定其大小) 号段的
值,用完之后再去数据库获取新的号段,可以大大的减轻数据库的压力。 - 各个业务不同的发号需求用 biz_tag 字段来区分,每个 biz-tag 的 ID 获取相互隔离,互不影响。
- 如果有新的业务需要扩区 ID,只需要增加表记录即可。
如果使用号段模式,我们首先需要创建一张数据表,脚本如下:
CREATE DATABASE leaf
CREATE TABLE `leaf_alloc` (
`biz_tag` varchar(128) NOT NULL DEFAULT '',
`max_id` bigint(20) NOT NULL DEFAULT '1',
`step` int(11) NOT NULL,
`description` varchar(256) DEFAULT NULL,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB;
insert into leaf_alloc(biz_tag, max_id, step, description) values('leaf-segmenttest', 1, 2000, 'Test leaf Segment Mode Get Id')
这张表中各项字段的含义如下:
- biz_tag:业务标记(不同业务可以有不同的号段序列)
- max_id:当前号段下的最大 id
- step:每次取号段的步长
- description:描述信息
- update_time:更新时间
配置完成后,启动项目,访问 http://localhost:8080/api/segment/get/leaf-segment-test 路径(路径最后面的 leaf-segment-test 是业务标记),即可拿到 ID。
可以通过如下地址访问到号段模式的监控页面 http://localhost:8080/cache 。
这边先介绍一下用法:
上图中的 biz_tag 相当于业务标记。 前面也说了这套 id 自增的方案可以用到很多不同的业务中,你打开手机美团主页那么多不同的业功能,里面那么多不同的业务都可以使用这一套 id 自增方案。
上面的 max_id 意思是当前的 id 最大值到哪里。但并不是指目前的 id 。
step 就是步长。每到 id 数量不够用时,就会扩容,这个就是扩容的数量。
混合起来的意思是,
那么怎么获取自增长的 id 呢?
请求这个接口就能获得:http://localhost:8080/api/segment/get/leaf-segment-test
。每调用一次,就能获取一个自增长的 id 。
这个自增长的 id 就是从数据库里面获取的。但并不是每次要获取 id 都去请求数据库获取一次。是一口气就从数据库获取一批 id 过来放到缓存中去。 比如上面的 step 是10,那么每次都会获取 10 个 id 放到 SpringBoot 的内存中; 接下来如果要调用就直接从内存中获取就行。当发现这 10 个不太够用,会再一次获取 10 个 id 过来。注意,这里并不是全部用完了才去数据库中获取;而是消耗了缓存中的三分之一个 id 时,就会向数据库申请拿下一批的 id 了。以此类推。
这边用人话翻译一下优点:
使用简单、稳定、还支持分布式项目,且是真正的自增且连续的 id。
相信很多用过 mysql 的小伙伴都遇到过这么种情况,现在数据库有 1 - 10 的 id,如果删除 8 - 10,这时候再插入数据,id 就是从 11 开始了。mysql 的主键增长只能保证是自增的,但不能保证连续。但是这个 leaf 是可以实现真正的自增且连续的 id。
号段模式优缺点:
优点:
- Leaf 服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。
- ID 号码是趋势递增的 8byte 的 64 位数字,满足上述数据库存储的主键要求。
- 容灾性高:Leaf 服务内部有号段缓存,即使 DB 宕机,短时间内 Leaf 仍能正常对外提供服务。
- 可以自定义 max_id 的大小,非常方便业务从原有的 ID 方式上迁移过来。
缺点:
- ID 号码不够随机,能够泄露发号数量的信息,不太安全。
- DB 宕机会造成整个系统不可用。
使用截图:
然后修改一下依赖(在最外面那层的 pom.xml 中修改):
修改数据库版本(对应自己要连接的数据库版本即可):
修改 druid:
然后运行没问题就可测试:
这就是 自增长的 id 了。
多刷新几次 id 就会增长。
(2)雪花模式
雪花模式也需要有个公共的地方去统筹,id 增长的信息。这个统筹就是 Zookeeper 。
SNOWFLAKE 模式需要配合 Zookeeper 一起,不过 SNOWFLAKE 对 Zookeeper 的依赖是弱依赖,把Zookeeper 启动之后,我们可以在 SNOWFLAKE 中配置 Zookeeper 信息,如下:
Zookeeper 需要 在 docker 上面安装集群。可以用 docker-compose.yaml 安装:
要的自取:
version: '3.7'
# 给zk集群配置一个网络,网络名为zk-net
networks:
zk-net:
name: zk-net
# 配置zk集群的
# container services下的每一个子配置都对应一个zk节点的docker container
services:
zk1:
# docker container所使用的docker image
image: zookeeper
hostname: zk1
container_name: zk1
# 配置docker container和宿主机的端口映射
ports:
- 2181:2181
- 8081:8080
# 配置docker container的环境变量
environment:
# 当前zk实例的id
ZOO_MY_ID: 1
# 整个zk集群的机器、端口列表
ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181 server.2=zk2:2888:3888;2181 server.3=zk3:2888:3888;2181
# 将docker container上的路径挂载到宿主机上 实现宿主机和docker container的数据共享
volumes:
- ./zk1/data:/data
- ./zk1/datalog:/datalog
# 当前docker container加入名为zk-net的隔离网络
networks:
- zk-net
zk2:
image: zookeeper
hostname: zk2
container_name: zk2
ports:
- 2182:2181
- 8082:8080
environment:
ZOO_MY_ID: 2
ZOO_SERVERS: server.1=zk1:2888:3888;2181 server.2=0.0.0.0:2888:3888;2181 server.3=zk3:2888:3888;2181
volumes:
- ./zk2/data:/data
- ./zk2/datalog:/datalog
networks:
- zk-net
zk3:
image: zookeeper
hostname: zk3
container_name: zk3
ports:
- 2183:2181
- 8083:8080
environment:
ZOO_MY_ID: 3
ZOO_SERVERS: server.1=zk1:2888:3888;2181 server.2=zk2:2888:3888;2181 server.3=0.0.0.0:2888:3888;2181
volumes:
- ./zk3/data:/data
- ./zk3/datalog:/datalog
networks:
- zk-net
然后使用 docker-compose 安装的方式安装即可。
然后直接访问下面的地址,有出现画面就是成功:
接着在 SpringBoot 里面配置对应的信息即可:
这么配置就可以了。
然后重新启动项目,访问下面地址就可以获取 id:
http://localhost:8080/api/snowflake/get/test
效果: