方案一:
租户共享MyCat中的schema,schema中的表会跨越多个datanode,因此每个表应该指定primary key, sharding rule可以解析primary key中包含的租户code,从而进一步确定每个租户对应的datanode.这就要求每个表的主键生成必须要主键生成器来生成(key generator),主键生成器要满足以下要求:
主键生成效率高
生成的主键全局无冲突
生成的主键要包含租户code信息,并可被反向解析出来

方案二:
每个租户独占MyCat中的一个schema,schema的表不会跨datanode,类似的拓扑如下:

MyCat核心配置:
server.xml
<user name="root">
<property name="password">password</property>
<property name="schemas">GLOBALDB,JG1DB,JG2DB,JG3DB,JG4DB,JG5DB</property>
</user>

2. schema.xml

<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="GLOBALDB" checkSQLschema="false" sqlMaxLimit="100">
<!-- global table is auto cloned to all defined data nodes ,so can join
with any table whose sharding node is in the same data node -->
<table name="orgmapping" primaryKey="id" type="global" dataNode="gdn" />
</schema>
<schema name="JG1DB" checkSQLschema="false" sqlMaxLimit="100">
<table name="user" primaryKey="id" autoIncrement="true" dataNode="jg1dn" />
<table name="user_order" primaryKey="id" autoIncrement="true" dataNode="jg1dn" />
</schema>
<schema name="JG2DB" checkSQLschema="false" sqlMaxLimit="100">
<table name="user" primaryKey="id" autoIncrement="true" dataNode="jg2dn" />
<table name="user_order" primaryKey="id" autoIncrement="true" dataNode="jg2dn" />
</schema>
<schema name="JG3DB" checkSQLschema="false" sqlMaxLimit="100">
<table name="user" primaryKey="id" autoIncrement="true" dataNode="jg3dn" />
<table name="user_order" primaryKey="id" autoIncrement="true" dataNode="jg3dn" />
</schema>
<schema name="JG4DB" checkSQLschema="false" sqlMaxLimit="100">
<table name="user" primaryKey="id" autoIncrement="true" dataNode="jg4dn" />
<table name="user_order" primaryKey="id" autoIncrement="true" dataNode="jg4dn" />
</schema>
<schema name="JG5DB" checkSQLschema="false" sqlMaxLimit="100">
<table name="user" primaryKey="id" autoIncrement="true" dataNode="jg5dn" />
<table name="user_order" primaryKey="id" autoIncrement="true" dataNode="jg5dn" />
</schema>
<dataNode name="gdn" dataHost="globalhost" database="wymglobal" />
<dataNode name="jg1dn" dataHost="g1host" database="jg1" />
<dataNode name="jg2dn" dataHost="g1host" database="jg2" />
<dataNode name="jg3dn" dataHost="g2host" database="jg3" />
<dataNode name="jg4dn" dataHost="g2host" database="jg4" />
<dataNode name="jg5dn" dataHost="g2host" database="jg5" />
<dataHost name="globalhost" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="192.168.0.199:3306" user="root"
password="password">
</writeHost>
</dataHost>
<dataHost name="g1host" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="192.168.1.13:3306" user="root"
password="password">
</writeHost>
</dataHost>
<dataHost name="g2host" maxCon="1000" minCon="10" balance="0"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<!-- can have multi write hosts -->
<writeHost host="hostM1" url="192.168.1.142:3306" user="root"
password="password">
</writeHost>
</dataHost>
</mycat:schema>

验证方案:
利用Spring boot jdbc 写测试程序:

在src/main/resources/application.yml中定义数据源
logging:
level:
org.springframework: INFO
com.wym: DEBUG
################### DataSource Configuration ##########################
spring:
application:
name: gs-relational-data-access
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:8066
username: root
password: password
initialize: false
init-db: false
2. 在DatasourceConfig.java中定义Datasource Bean和JdbcTemplate Bean

package com.wym.mycatdemo;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;

import com.alibaba.druid.pool.DruidDataSource;

@Configuration
public class DatasourceConfig {

@Autowired
private Environment env;

@Bean(name = "dataSource")
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(env.getProperty("spring.datasource.url"));
dataSource.setUsername(env.getProperty("spring.datasource.username"));// 用户名
dataSource.setPassword(env.getProperty("spring.datasource.password"));// 密码
dataSource.setInitialSize(2);
dataSource.setMaxActive(20);
dataSource.setMinIdle(0);
dataSource.setMaxWait(60000);
dataSource.setValidationQuery("SELECT 1");
dataSource.setTestOnBorrow(false);
dataSource.setTestWhileIdle(true);
dataSource.setPoolPreparedStatements(false);
return dataSource;
}

@Bean(name = "businessJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(@Qualifier("dataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}

}
3. 4个机构数据库中user表的建表语句如下:
CREATE TABLE `user` (
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL COMMENT '用户名',
`password` varchar(20) DEFAULT NULL COMMENT '密码',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

4. 定义User.java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
5. 定义User的数据库访问对象UserDao.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.wym.mycatdemo.model.User;

@Repository
public class UserDao {
public static final String TENANT_SQL_TEMPLATE = "/*!mycat:schema= {0}*/{1}";
@Autowired
@Qualifier("businessJdbcTemplate")
private JdbcTemplate jdbcTemplate;

@Transactional(readOnly = true)
public List<User> findAll(String tenantSchema) {
return jdbcTemplate.query(MessageFormat.format(TENANT_SQL_TEMPLATE, tenantSchema, "select * from user"), new UserRowMapper());
}

@Transactional(readOnly = true)
public User findUserById(String tenantSchema, int id) {
return jdbcTemplate.queryForObject(MessageFormat.format(TENANT_SQL_TEMPLATE, tenantSchema, "select * from user where id=?"), new Object[] { id }, new UserRowMapper());
}

@Transactional
public User create(String tenantSchema, final User user) {
final String sql = MessageFormat.format(TENANT_SQL_TEMPLATE, tenantSchema, "insert into user(name,password) values(?,?)");

KeyHolder holder = new GeneratedKeyHolder();

jdbcTemplate.update(new PreparedStatementCreator() {

@Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, user.getName());
ps.setString(2, user.getPassword());
return ps;
}
}, holder);

int newUserId = holder.getKey().intValue();
user.setId(newUserId);
return user;
}

@Transactional
public void delete(String tenantSchema, final Integer id) {
final String sql = MessageFormat.format(TENANT_SQL_TEMPLATE, tenantSchema, "delete from user where id=?");
jdbcTemplate.update(sql, new Object[] { id }, new int[] { java.sql.Types.INTEGER });
}

@Transactional
public void update(String tenantSchema, final User user) {
jdbcTemplate.update(MessageFormat.format(TENANT_SQL_TEMPLATE, tenantSchema, "update user set name=?,password=? where id=?"),
new Object[] { user.getName(), user.getPassword(), user.getId() });
}

class UserRowMapper implements RowMapper<User> {

@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setPassword(rs.getString("password"));
return user;
}

}
}
6. 测试内编写
import java.util.List;

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.SpringJUnit4ClassRunner;

import com.wym.mycatdemo.dao.UserDao;
import com.wym.mycatdemo.model.User;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = SpringBootJdbcDemoApplication.class) // 指定spring-boot的启动类

public class SpringBootJdbcDemoApplicationTests {

@Autowired
private UserDao userDao;

@Test
public void findAllUsers() {
List<User> users = userDao.findAll("JG1DB");
System.out.println(users);

}

@Test
public void findUserById() {
User user = userDao.findUserById("JG2DB",1);
System.out.println(user);
}

@Test
public void updateById() {
User user = userDao.findUserById("JG3DB",2);
System.out.println(user);
User newUser = new User(2, "JackChen", "JackChen@qq.com");
userDao.update("JG3DB", newUser);
User newUser2 = userDao.findUserById("JG3DB",newUser.getId());
System.out.println(newUser2);
}

@Test
public void createUser() {
User user = new User("rose", "rose@gmail.com");
User savedUser = userDao.create("JG4DB",user);
user = userDao.findUserById("JG4DB",savedUser.getId());
System.out.println(user);
}
@Test
public void findAllUsers1() {
List<User> users = userDao.findAll("JG5DB");
System.out.println("----------------------------------"+users);

}

}
7. 由运行结果即可得知,数据访问被MyCat正确的路由到各个机构的数据库中。

当然这个只是演示例子,正式项目里一个比较好的思路是:

前端登录验证成功后,租户编码存入cookie中或本地缓存里
前端通过HTTP方式访问后端api时,都需要在header中附带租户编码信息
后端的 Restful API 的 定义2个filter, 比较一个叫prefilter, 一个叫postfilter, prefilter中读取http request中的header里的租户编码信息,并把它写入一个public static 的ThreadLocal对象, postfilter负责ThreadLocal对象的清除
DAO层从ThreadLocal对象中抓取租户编码,并把租户编码附加到sql语句头部

踩坑记录:

1. 为每个机构都定义个一个表名叫order的表,但是mycat不认,查了半天,结果发现是由于order是SQL查询关键字造    成的

2.MyCat中的表ID定义成自增长型,而且id自增长配置为
<property name="sequnceHandlerType">2</property>, 文档上说明为本地时间戳算法:
ID= 64位二进制 (42(毫秒)+5(机器ID)+5(业务编码)+12(重复累加)
换算成十进制为18位数的long类型,每毫秒可以并发12位二进制的累加
但是MyCAT老是报id冲突,但从日志看生成的id与数据库中已有的并不冲突,查了半天是因为ID的类型是int 32位,生成的id为64的,插入时被截断(可能是保留高位),因此产生冲突。

完整的代码,请访问:
https://github.com/tangaiyun/multitenancybymycat
--------------------- 
作者:suncold 
来源:CSDN 
原文:https://blog.csdn.net/suncold/article/details/79814926 
版权声明:本文为博主原创文章,转载请附上博文链接!

基于Mycat的多租户分库方案相关推荐

  1. 开源分布式数据库中间件MyCat架构简介(二)——基于MyCat的分库分表,读写分离,水平切分和垂直切分实现原理

    目录 前言 基于MyCat的分库分表,读写分离,水平切分和垂直切分实现原理 一.关于Mycat 二.Mycat 实现原理 三.MyCat 应用场景 四.MyCat 未来展望 五.Mycat 中相关概念 ...

  2. 基于mycat高可用方案——数据库负载

    引言 传统企业级应用一般采取单台数据库,吞吐所有应用的读写,随着互联网的高速发展,以及微服务架构越来越普及,往往采用分库分表来支撑高速增长的大量业务数据吞吐.分库分表主要有两种方式:水平分表和垂直分库 ...

  3. 基于mycat高可用方案——数据库负载(基于阿里云)

    引言 传统企业级应用一般采取单台数据库,吞吐所有应用的读写,随着互联网的高速发展,以及微服务架构越来越普及,往往采用分库分表来支撑高速增长的大量业务数据吞吐.分库分表主要有两种方式:水平分表和垂直分库 ...

  4. 基于Mycat的读写分离

    文章目录 1 读写分离原理 2 实现方式 3. mycat介绍 3.1 概述 3.2 功能 3.3 mycat原理 3.4 mycat应用场景 4. mycat安装配置 4.1 安装JDK 4.2 创 ...

  5. 分享 : 警惕MySQL运维陷阱:基于MyCat的伪分布式架构

    分布式数据库已经进入了全面快速发展阶段.这种发展是与时俱进的,与人的需求分不开,因为现在信息时代的高速发展,导致数据量和交易量越来越大.这种现象首先导致的就是存储瓶颈,因为MySQL数据库实质上还是一 ...

  6. Mysql数据库SaaS多租户实现方案

    一.前言 在上一篇文章中描述了数据库的多租户实现方案主要有3种: 1.独立数据库模式 2.共享数据库独立schema模式 3.共享schema模式 那么我们选择哪一种呢?从业界来讲,使用第三种共享sc ...

  7. JAVA二开工具开源 (三)--多租户实现方案

    JAVA二开工具开源 (三)–多租户实现方案 最近想了解如何Java对接微信平台,快速搭建完整项目开发,发现网上有很对开源的这类二开源码.https://gitee.com/luozijing123/ ...

  8. Linux+MySQL+MyCat实现分库分表,通过MyCat数据库中间件实现分库分表配置实战

    目录 前言 Linux+MySQL+MyCat实现读写分离,主从同步的解决方案 一.Linux下MySQL数据库服务的安装与部署 二.下载Linux MyCat 三.上传Linux服务器,并解压 四. ...

  9. MySQL 基于MyCAT配置数据分片

    目录 MySQL 基于MyCAT配置数据分片 相关概念 分库/分表 垂直分割 水平分割 MyCAT介绍 软件介绍 分片规则 工作过程 部署MyCAT服务 环境部署 拓部结构 IP规划 部署MyCAT服 ...

最新文章

  1. 程序员又背锅?美团外卖声明“杀熟会员”是技术原因,软件定位缓存导致配送费不准!网友:程序员太惨!...
  2. 申请图吧地图Android API密钥详解
  3. flash与IPhone
  4. 软考-信息系统项目管理师-项目整体管理
  5. POJ - 2299 Ultra-QuickSort(线段树+离散化/归并排序)
  6. @bzoj - 3238@ [Ahoi2013]差异
  7. sql安装联机丛书提示发生网络错误_速达软件:安装问题解答
  8. SQL入门教程(一):基础知识
  9. Linux安装winetim简单教程,ubuntu wine安装TIM
  10. Eclipse的使用教程
  11. example包下载
  12. latex如何插入图片格式
  13. CampusBulider(模模搭)学习笔记6:室内搭建
  14. 网络 4.0 防火墙概述
  15. 15、react 的 非受控组件 和 受控组件
  16. 23王道考研数据操作目录一览
  17. LeetCode 1310 子数组异或查询
  18. 60.【Java 进阶】
  19. bootstrap之data-toggle,data-dismiss,data-target用法
  20. 计算机保护分区,磁盘显示为GPT(保护分区)

热门文章

  1. 发布Acro Multi-Language Suite for Delphi.Net
  2. Acro Multi-Language Class-Method for Devexpress VCL
  3. 显卡驱动怎么更新下载,驱动人生一键解决
  4. echart 折线图设置y轴单位_echartsY轴双坐标单位切换
  5. IOS设计结构-姬云鹏
  6. 《梦幻奇游》新手指引
  7. MMoE ESSM PLE对比
  8. hdu4558 仙剑奇缘
  9. 无法创建html包装器,如何防止Markdown将生成的HTML包装在p元素中?/p
  10. Android 12 “Bug”!除了一加、三星,谷歌自家手机都被“坑”了