Rubin's Blog

  • 首页
  • 关于作者
  • 隐私政策
享受恬静与美好~~~
分享生活的点点滴滴~~~
  1. 首页
  2. MySQL
  3. 正文

MySQL之MyCat实战

2021年 11月 11日 1153点热度 0人点赞 0条评论

MyCat简介

MyCat 是一个实现了 MySQL 协议的 Server,前端用户可以把它看作是一个数据库代理,用 MySQL 客户端工具和命令行访问,而其后端可以用 MySQL 原生协议或JDBC 协议与多个 MySQL 服务器通信,其核心功能是分库分表和读写分离,即将一个大表水平分割为 N 个小表,存储在后端 MySQL 服务器里或者其他数据库里。

MyCat核心概念

逻辑库

对数据进行分片处理之后,从原有的一个库,被切分为多个分片数据库,所有的分片数据库集群构成了整个完整的数据库存储。MyCat在操作时,使用逻辑库来代表这个完整的数据库集群,便于对整个集群操作。

逻辑表

既然有逻辑库,那么就会有逻辑表,分布式数据库中,对应用来说,读写数据的表就是逻辑表。

分片表

分片表,是指那些原有的很大数据的表,需要切分到多个数据库的表,这样,每个分片都有一部分数据,所有分片构成了完整的数据。例如在MyCat 配置中的t_node就属于分片表,数据按照规则被分到dn1,dn2两个分片节点上。

<table name="t_node" primaryKey="vid" autoIncrement="true" dataNode="dn1,dn2"
 rule="rule1" />

ER表

MyCat提出了基于 E-R 关系的数据分片策略,子表的记录与所关联的父表记录存放在同一个数据分片上,即子表依赖于父表,通过表分组(Table Group)保证数据 join不会跨库操作。表分组(Table Group)是解决跨分片数据 join的一种很好的思路,也是数据切分规划的重要一条规则。

全局表

一个真实的业务系统中,往往存在大量的类似字典表的表,这些表基本上很少变动,字典表具有以下几个特性:

  • 变动不频繁
  • 数据量总体变化不大
  • 数据规模不大,很少有超过数十万条记录

对于这类的表,在分片的情况下,当业务表因为规模而进行分片以后,业务表与这些附属的字典表之间的关联,就成了比较棘手的问题,所以 MyCat 中通过数据冗余来解决这类表的 join,即所有的分片都有一份数据的拷贝,所有将字典表或者符合字典表特性的一些表定义为全局表。数据冗余是解决跨分片数据 join 的一种很好的思路,也是数据切分规划的另外一条重要规则。

分片节点

数据切分后,一个大表被分到不同的分片数据库上面,每个表分片所在的数据库就是分片节点dataNode。

节点主机

数据切分后,每个分片节点不一定都会独占一台机器,同一机器上面可以有多个分片数据库, 这样一个或多个分片节点所在的机器就是节点主机,为了规避单节点主机并发数限制, 尽量将读写压力高的分片节点均衡的放在不同的节点主机dataHost。

分片规则

前面讲了数据切分,一个大表被分成若干个分片表,就需要一定的规则rule,这样按照某种业务规则把数据分到某个分片的规则就是分片规则,数据切分选择合适的分片规则非常重要,将极大的避免后续数据处理的难度。

server.xml配置

server.xml几乎保存了所有MyCat需要的系统配置信息。

user标签

这个标签主要用于定义登录MyCat的用户和权限。例如下面的例子中,我们定义了一个用户,用户名为 root、密码为123456,可访问的schema为position。

<user name="root">
 <property name="password">123456</property>
 <property name="schemas">position</property>
 <property name="readOnly">true</property>
 <property name="defaultSchema">order</property>
</user>

firewall标签

<firewall>
 <!-- ip白名单 用户对应的可以访问的ip地址 -->
 <whitehost>
  <host host="127.0.0.*" user="root"/>
    <host host="127.0.*" user="root"/>
    <host host="127.*" user="root"/>
    <host host="1*7.*" user="root"/>
 </whitehost>
 <!-- 黑名单允许的权限 后面为默认 -->
 <blacklist check="true">
  <property name="selelctAllow">false</property>
  <property name="selelctIntoAllow">false</property>
  <property name="updateAllow">false</property>
  <property name="insertAllow">false</property>
  <property name="deletetAllow">false</property>
  <property name="dropAllow">false</property>
 </blacklist>
</firewall>

全局序列号

在实现分库分表的情况下,数据库自增主键已无法保证自增主键的全局唯一。为此,MyCat提供了全局sequence,并且提供了包含本地配置和数据库配置等多种实现方式。

<system>
    <property name="sequnceHandlerType">0</property>
</system>
  • 0:表示使用本地文件方式
  • 1:表示使用数据库方式生成
  • 2:表示使用本地时间戳方式
  • 3:表示基于ZK与本地配置的分布式ID生成器
  • 4:表示使用zookeeper递增方式生成

本地文件

此方式MyCat将sequence配置到文件中,当使用到sequence中的配置后,Mycat会更新classpath中的sequence_conf.properties文件中sequence当前的值。

#default global sequence
GLOBAL.HISIDS=
GLOBAL.MINID=10001
GLOBAL.MAXID=20000
GLOBAL.CURID=10000
# self define sequence
COMPANY.HISIDS=
COMPANY.MINID=1001
COMPANY.MAXID=2000
COMPANY.CURID=1000
ORDER.HISIDS=
ORDER.MINID=1001
ORDER.MAXID=2000
ORDER.CURID=1000

数据库方式

在数据库中建立一张表,存放sequence名称(name),sequence当前值(current_value),步长(increment)等信息。

CREATE TABLE MYCAT_SEQUENCE
(
 name     VARCHAR(64) NOT NULL,
 current_value BIGINT(20) NOT NULL,
 increment   INT     NOT NULL DEFAULT 1,
 PRIMARY KEY (name)
) ENGINE = InnoDB;

本地时间戳方式

ID为64 位二进制 ,42(毫秒)+5(机器 ID)+5(业务编码)+12(重复累加)。换算成十进制为18位数的long类型,每毫秒可以并发12位二进制的累加。

在MyCat下配置sequence_time_conf.properties文件

WORKID=0-31 任意整数
DATAACENTERID=0-31 任意整数

每个MyCat配置的WORKID、DATAACENTERID不同,组成唯一标识,总共支持32*32=1024 种组合。

分布式ZK ID生成器

ZK的连接信息统一在myid.properties的zkUR属性中配置。基于ZK与本地配置的分布式ID生成器,InstanceID可以通过ZK自动获取,也可以通过配置文件配置。在sequence_distributed_conf.properties,只要配置INSTANCEID=ZK就表示从ZK上获取InstanceID。

ID 最大为63位二进制,可以承受单机房单机器单线程 1000*(2^6)=640000的并发。结构如下:

  • currentTimeMillis(微秒时间戳 38 位,可以使用 17 年)
  • clusterId(机房或者 ZKid,通过配置文件配置,5 位)
  • instanceId(实例 ID,可以通过 ZK 或者配置文件获取,5 位)
  • threadId(线程 ID,9 位)
  • increment(自增,6 位)

ZK递增方式

ZK的连接信息统一在myid.properties的zkURL属性中配置。需要配置sequence_conf.properties文件:

  • TABLE.MINID: 某线程当前区间内最小值
  • TABLE.MAXID:某线程当前区间内最大值
  • TABLE.CURID:某线程当前区间内当前值

schema.xml配置

schema.xml作为MyCat中重要的配置文件之一,管理着MyCat的逻辑库、表、分片节点、主机等信息。

schema标签

schema标签用于定义MyCat实例中的逻辑库,MyCat可以有多个逻辑库,每个逻辑库都有自己的相关配置。可以使用schema标签来划分这些不同的逻辑库。

<!-- 逻辑库 -->
<schema name="rubin_shard" checkSQLschema="true" sqlMaxLimit="100"
 dataNode="dn1"></schema>
属性名值数量限制说明
dataNode任意String0-1分片节点
sqlMaxLimitInteger1查询返回的记录数限制limit
checkSQLschemaBoolean1是否去表库名

table标签

table标签定义了MyCat中的逻辑表,所有需要拆分的表都需要在这个标签中定义:

<table name="b_order" dataNode="dn1,dn2" rule="b_order_rule" primaryKey="ID"
 autoIncrement="true"/>
属性值数量限制说明
nameString1逻辑表名
dataNodeString1-*分片节点
ruleString0-1分片规则
ruleRequiredBoolean0-1是否强制绑定分片规则
primaryKeyString1主键
typeString0-1逻辑表类型,全局表、普通表
autoIncrementBoolean0-1自增长主键
subTablesString1分表
needAddLimit Boolean 0-1是否为查询SQL自动加limit限制

dataNode标签

dataNode标签定义了MyCat中的分片节点,也就是我们通常说所的数据分片。

<!-- 数据节点 -->
<dataNode name="dn1" dataHost="order_1" database="order_1" />
  • name: 定义数据节点的名字,这个名字需要是唯一的,我们需要在 table 标签上应用这个名字,来建立表与分片对应的关系
  • dataHost : 用于定义该分片属于哪个分片主机,属性值是引用 dataHost 标签上定义的 name 属性
  • database: 用于定义该分片节点属于哪个具体的库

dataHost标签

dataHost标签在MyCat逻辑库中也是作为最底层的标签存在,直接定义了具体的数据库实例、读写分离配置和心跳语句:

<dataHost name="order_1" maxCon="100" minCon="10" balance="0"
 writeType="0" dbType="mysql" dbDriver="native" switchType="1"
 slaveThreshold="100">
</dataHost>
属性值数量限制说明
nameString1节点主机名
maxConInteger1最大连接数
minConInteger1最小连接数
balanceInteger1读操作负载均衡类型
writeTypeInteger1写操作负载均衡类型
dbTypeString1数据库类型
dbDriverString1数据库驱动
switchTypeString1主从切换类型

heartbeat标签

heartbeat标签内指明用于和后端数据库进行心跳检查的语句。例如:MySQL 可以使用select user()、Oracle可以 使用select 1 from dual等。

<dataHost>
    <heartbeat>select user()</heartbeat>
</dataHost>

writeHost和readHost标签

writeHost和readHost标签都指定后端数据库的相关配置给MyCat,用于实例化后端连接池。唯一不同的是,writeHost指定写实例、readHost指定读实例。在一个dataHost 内可以定义多个writeHost和readHost。但是,如果 writeHost指定的后端数据库宕机,那么这个writeHost绑定的所有readHost都将不可用。另一方面,由于这个 writeHost宕机系统会自动的检测到,并切换到备用的writeHost上去。

<dataHost name="lg_edu_order_2" maxCon="100" minCon="10" balance="0"
 writeType="0" dbType="mysql" dbDriver="native" switchType="1"
 slaveThreshold="100">
    <heartbeat>select user()</heartbeat>
    <writeHost host="M1" url="192.168.95.133:3306" user="root" password="1234">
</writeHost>
</dataHost>
属性值数量限制说明
hostString1主机名
urlString1连接字符串
passwordString1密码
userString1用户名
weightString1权重
usingDecryptString1是否对密码加密,默认0

rule.xml配置

rule.xml用于定义MyCat的分片规则。

tableRule标签

<tableRule name="c_order_rule">
    <rule>
        <columns>user_id</columns>
        <algorithm>partitionByOrderFunc</algorithm>
    </rule>
</tableRule>
  • name:指定唯一的名字,用于标识不同的表规则
  • columns:指定要拆分的列名字
  • algorithm:使用function标签中的name属性,连接表规则和具体路由算法

function标签

<function name="partitionByOrderFunc"
 class="io.mycat.route.function.PartitionByMod">
    <property name="count">2</property>
</function>
  • name:指定算法的名字
  • class:制定路由算法具体的类名字
  • property: 为具体算法需要用到的一些属性

MyCat实战

MyCat安装

提示:需要先安装并配制好JDK!

在官网或者附件中下载对应的工具包并解压。

tar -zxvf Mycat-server-1.6.7.5-release-20200410174409-linux.tar.gz

进入mycat/bin,启动MyCat

启动命令:./mycat start
停止命令:./mycat stop
重启命令:./mycat restart
查看状态:./mycat status

访问MyCat

mysql -uroot -proot -h127.0.0.1 -P8066

分库分表

在rule.xml配置MyCat分库分表

<mycat:rule xmlns:mycat="http://io.mycat/">
 <tableRule name="b_order_rule">
   <rule>
     <columns>company_id</columns>
     <algorithm>partitionByOrderFunc</algorithm>
   </rule>
 </tableRule>
 <!-- 路由函数定义 -->
 <function name="partitionByOrderFunc"
class="io.mycat.route.function.PartitionByMod">
   <property name="count">2</property>
 </function>
</mycat:rule>

MyCat常用分片规则如下:

  • 时间类:按天分片、自然月分片、单月小时分片
  • 哈希类:Hash固定分片、日期范围Hash分片、截取数字Hash求模范围分片、截取数字Hash分片、一致性Hash分片
  • 取模类:取模分片、取模范围分片、范围求模分片
  • 其他类:枚举分片、范围约定分片、应用指定分片、冷热数据分片

MyCat常用分片配置示例:

  • 自动分片
<tableRule name="auto-sharding-long">
 <rule>
  <columns>id</columns>
  <algorithm>rang-long</algorithm>
 </rule>
</tableRule>
<function name="rang-long"
 class="io.mycat.route.function.AutoPartitionByLong">
 <property name="mapFile">autopartition-long.txt</property>
</function>

autopartition-long.txt文件内容如下:

# range start-end ,data node index
# K=1000,M=10000.
0-500M=0
500M-1000M=1
1000M-1500M=2
  • 枚举分片

把数据分类存储。

<tableRule name="sharding-by-intfile">
<rule>
  <columns>sharding_id</columns>
  <algorithm>hash-int</algorithm>
</rule>
</tableRule>
<function name="hash-int"
 class="io.mycat.route.function.PartitionByFileMap">
 <property name="mapFile">partition-hash-int.txt</property>
 <!-- 找不到分片时设置容错规则,把数据插入到默认分片0里面 -->
 <property name="defaultNode">0</property>
</function>

partition-hash-int.txt文件内容如下:

10000=0
10010=1
  • 取模分片

根据分片字段值 % 分片数 。

<tableRule name="mod-long">
 <rule>
   <columns>id</columns>
   <algorithm>mod-long</algorithm>
 </rule>
</tableRule>
<function name="mod-long" class="io.mycat.route.function.PartitionByMod">
 <!--分片数 -->
 <property name="count">3</property>
</function>
  • 冷热数据分片

根据日期查询日志数据冷热数据分布 ,最近n个月的到实时交易库查询,超过n个月的按照m天分片。

<tableRule name="sharding-by-date">
    <rule>
       <columns>create_time</columns>
       <algorithm>sharding-by-hotdate</algorithm>
    </rule>
</tableRule>
<function name="sharding-by-hotdate"
 class="org.opencloudb.route.function.PartitionByHotDate">
    <!-- 定义日期格式 -->
    <property name="dateFormat">yyyy-MM-dd</property>
    <!-- 热库存储多少天数据 -->
    <property name="sLastDay">30</property>
    <!-- 超过热库期限的数据按照多少天来分片 -->
    <property name="sPartionDay">30</property>
</function>
  • 一致性哈希分片
<tableRule name="sharding-by-murmur">
 <rule>
   <columns>id</columns>
   <algorithm>murmur</algorithm>
 </rule>
</tableRule>
<function name="murmur"
 class="io.mycat.route.function.PartitionByMurmurHash">
 <property name="seed">0</property><!-- 默认是0 -->
 <property name="count">2</property><!-- 要分片的数据库节点数量,必须指定,否
则没法分片 -->
  <!-- 一个实际的数据库节点
被映射为这么多虚拟节点,默认是160倍,也就是虚拟节点数是物理节点数的160倍 -->
 <property name="virtualBucketTimes">160</property>
 <!-- 节点的权重,
没有指定权重的节点默认是1。以properties文件的格式填写,以从0开始到count-1的整数值也就
是节点索引为key,以节点权重值为值。所有权重值必须是正整数,否则以1代替 -->
  <!-- <property name="weightMapFile">weightMapFile</property> -->
 <!-- 用于测试时观察各物理节点与虚拟节点的分布情况,如果指定了这个属性,会把虚拟节点的murmur hash值与物理节点的映射按行输出到这个文件,没有默认值,如果不指定,就不会输出任何东西 -->
  <!-- <property name="bucketMapPath">/etc/mycat/bucketMapPath</property> -->
</function>

读写分离

在schema.xml文件中配置MyCat读写分离。使用前需要搭建MySQL主从架构,并实现主从复制,MyCat不负责数据同步问题。

<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0"
 dbType="mysql" dbDriver="native">
 <heartbeat>select user()</heartbeat>
  <!-- can have multi write hosts -->
  <writeHost host="M1" url="localhost:3306" user="root" password="123456">
    <readHost host="S1" url="localhost:3307" user="root" password="123456"
 weight="1" />
  </writeHost>
</dataHost>

balance参数:

  • 0 :所有读操作都发送到当前可用的writeHost
  • 1 :所有读操作都随机发送到readHost和stand by writeHost
  • 2 :所有读操作都随机发送到writeHost和readHost
  • 3 :所有读操作都随机发送到writeHost对应的readHost上,但是writeHost不负担读压力

writeType参数:

  • 0 : 所有写操作都发送到可用的writeHost
  • 1 :所有写操作都随机发送到readHost
  • 2 :所有写操作都随机发送到writeHost,readHost

或者

<dataHost name="localhost1" maxCon="1000" minCon="10" balance="1" writeType="0"
 dbType="mysql" dbDriver="native">
 <heartbeat>select user()</heartbeat>
  <!-- can have multi write hosts -->
  <writeHost host="M1" url="localhost:3306" user="root" password="123456">
 </writeHost>
  <writeHost host="S1" url="localhost:3307" user="root" password="123456">
 </writeHost>
</dataHost>

以上两种取模第一种当写挂了读不可用,第二种可以继续使用。事务内部的一切操作都会走写节点,所以读操作不要加事务,如果读延时较大,使用根据主从延时切换的读写分离,或者强制走写节点。

强制路由

一个查询 SQL 语句以/* !mycat * /注解来确定其是走读节点还是写节点。

强制走从:
/*!mycat:db_type=slave*/ select * from travelrecord 
强制走写:
/*!mycat:db_type=master*/ select * from travelrecord 

1.6 以后MyCat除了支持db_type注解以外,还有其他注解,如下:

/*!mycat:sql=sql */    指定真正执行的SQL
/*!mycat:schema=schema1 */ 指定走那个schema
/*!mycat:datanode=dn1 */  指定sql要运行的节点
/*!mycat:catlet=io.mycat.catlets.ShareJoin */ 通过catlet支持跨分片复杂SQL实现以及存
储过程支持等

主从延时切换

switchType参数:

  • -1: 表示不自动切换
  • 1 :表示自动切换
  • 2 :基于MySQL主从同步状态决定是否切换
  • 3 :基于MySQL Cluster集群切换机制

1.4 开始支持 MySQL 主从复制状态绑定的读写分离机制,让读更加安全可靠,配置如下:

MyCat心跳检查语句配置为 show slave status。

dataHost上定义两个新属性: switchType="2" 与slaveThreshold="100",此时意味着开启MySQL主从复制状态绑定的读写分离与切换机制,MyCat心跳机制通过检测 show slave status 中的 "Seconds_Behind_Master"、"Slave_IO_Running"和
"Slave_SQL_Running"
三个字段来确定当前主从同步的状态以及Seconds_Behind_Master主从复制时延。 当 Seconds_Behind_Master > slaveThreshold 时,读写分离筛选器会过滤掉此slave机器,防止读到很久之前的旧数据。而当主节点宕机后,切换逻辑会检查slave上的 Seconds_Behind_Master 是否为 0,为 0 时则表示主从同步,可以安全切换,否则不会切换。

<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0"
 dbType="mysql" dbDriver="native" switchType="2" slaveThreshold="100">
    <heartbeat>show slave status </heartbeat>
    <!-- can have multi write hosts -->
    <writeHost host="M1" url="localhost:3306" user="root" password="123456">
    </writeHost>
    <writeHost host="S1" url="localhost:3316" user="root" password="123456">
    </writeHost>
</dataHost>

1.4.1 开始支持 MySQL 集群模式,让读更加安全可靠。配置如下:

MyCAT 心跳检查语句配置为 show status like ‘wsrep%’。

dataHost 上定义两个新属性: switchType="3",此时意味着开启 MySQL 集群复制状态状态绑定的读写分离与切换机制,MyCat心跳机制通过检测集群复制时延时,如果延时过大或者集群出现节点问题不会负载改节点。

<dataHost name="localhost1" maxCon="1000" minCon="10" balance="0" writeType="0"
 dbType="mysql" dbDriver="native" switchType="3" >
<heartbeat> show status like ‘wsrep%’</heartbeat>
  <writeHost host="M1" url="localhost:3306" user="root"password="123456">
    </writeHost>
    <writeHost host="S1"url="localhost:3316"user="root"password="123456" >
    </writeHost>
</dataHost>

MyCat事务

MyCat数据库事务

MyCat目前没有出来跨分片的事务强一致性支持,单库内部可以保证事务的完整性。如果跨库事务,在执行的时候任何分片出错,可以保证所有分片回滚,但是一旦应用发起 commit 指令,无法保证所有分片都成功,考虑到某个分片挂的可能性不大所以称为弱 XA。

XA事务使用

MyCat从1.6.5 版本开始支持标准XA分布式事务,考虑到MySQL 5.7之前版本XA有bug,所以推荐最佳搭配XA功能使用MySQL 5.7版本。

MyCat实现 XA 标准分布式事务,MyCat作为XA事务协调者角色,即使事务过程中MyCat宕机挂掉,由于MyCat会记录事务日志,所以MyCat恢复后会进行事务的恢复善后处理工作。考虑到分布式事务的性能开销比较大,所以只推荐在全局表的事务以及其他一些对一致性要求比较高的场景。

使用示例:

  • XA事务需要设置手动提交
set autocommit=0; 
  • 使用该命令开启XA事务
set xa=on;
  • 执行相应的SQL语句部分
insert into city(id,name,province) values(200,'chengdu','sichuan');
update position set salary='300000' where id<5;
  • 提交或回滚事务
commit;
rollback;

保证Repeatable Read

MyCat有一个特性,就是开事务之后,如果不运行 update/delete/select for update 等更新类语句SQL的话,不会将当前连接与当前session绑定。如下图所示:

这样做的好处是可以保证连接可以最大限度的复用,提升性能。

但是,这就会导致两次 select 中如果有其它的在提交的话,会出现两次同样的 select 不一 致的现象,即不能 Repeatable Read,这会让人直连 MySQL 的人很困惑。可能会在依赖 Repeatable Read 的场景出现问题。所以做了一个开关,当 server.xml 的 system 配置了 strictTxIsolation=true 的时候,会关掉这个特性,以保证 Repeatable Read ,加了开关后如下图所示:

以上就是本文的全部内容。欢迎小伙伴们积极留言交流~~~

附件

链接:https://pan.rubinchu.com/share/1458690427132575744

提取码:dvrg

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: MySQL
最后更新:2022年 6月 9日

RubinChu

一个快乐的小逗比~~~

打赏 点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复
文章目录
  • MyCat简介
  • MyCat核心概念
    • 逻辑库
    • 逻辑表
    • 分片表
    • ER表
    • 全局表
    • 分片节点
    • 节点主机
    • 分片规则
  • server.xml配置
    • user标签
    • firewall标签
    • 全局序列号
      • 本地文件
      • 数据库方式
      • 本地时间戳方式
      • 分布式ZK ID生成器
      • ZK递增方式
  • schema.xml配置
    • schema标签
    • table标签
    • dataNode标签
    • dataHost标签
    • heartbeat标签
    • writeHost和readHost标签
  • rule.xml配置
    • tableRule标签
    • function标签
  • MyCat实战
    • MyCat安装
    • 分库分表
    • 读写分离
    • 强制路由
    • 主从延时切换
  • MyCat事务
    • MyCat数据库事务
    • XA事务使用
    • 保证Repeatable Read
  • 附件
最新 热点 随机
最新 热点 随机
问题记录之Chrome设置屏蔽Https禁止调用Http行为 问题记录之Mac设置软链接 问题记录之JDK8连接MySQL数据库失败 面试系列之自我介绍 面试总结 算法思维
Kafka高级特性之主题 Elasticsearch之高级应用 Neo4j之数据管理和优化 SpringCloud Alibaba之Nacos java并发编程之多线程 Tomcat之手写简易服务器

COPYRIGHT © 2021 rubinchu.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

京ICP备19039146号-1