全数据库全SQL兼容、完美RR级别读写分离、与原生一致的ACID特性、轻量简单易扩展
很多人会质疑 市面上较为流行的Sharding中间件/应用层Sharing框架已经有很多,他们都已经发展了很久了,功能也很强大,为什么要一个再重复制造这么个轮子呢?
之所以这里有一个新的轮子并不是因为我懒得无事,而是我对目前基于传统RDB上Sharding框架的设计的理念不太赞同,虽然他们或许都很圆,跑的很快,但是使用不当的话,容易翻车。我期望的轮子是既能跑的快,但也能跑的稳。
我们目前国内主流的Sharding框架都是基于SQL来完成,其主要流程:
这种实现希望提供一个屏蔽底层Sharding逻辑的解决方案,对上层应用来说,只有一个RDB,这样应用可以透明访问多个数据库。
然而,这仅仅只是一个美丽的目标。因种种原因,这些层次的Sharding方案都无法提供跟原生数据库一样的功能:
正因为存在这些差异,本质上,上层应用必须明确的知道经过此类Sharding方案后得到的查询结果、事务结果与原生的有啥不一致才能写出正确可靠的程序。
因此,基于SQL的Sharding方案对应用层并不透明。
如果要基于SQL层的框架写出正确可靠的代码的话,我们需要遵循一些范式:
这些范式对于实际上就是使用Sharding数据库框架时,不透明的表现。而这些表现都是隐式的隐藏于SQL中,难以REVIEW。
而且这些范式对于很多人来说不一定能够充分理解执行的含义,以至于忽略了。
由上面最重要的一点“所有事务(包括读、写)都不能跨库”决定,一个合理设计的代码里绝大多数的业务代码中数据库访问都不会跨分区,核心业务代码都在同一分区内进行。 因此,我们大多数情况下,需要的只是一个协助我们便捷选择对应分片的一个框架。
因此我的想法很简单,提供一个方便透明选择分片、并辅以自动生成ID的框架。对于需要访问多个分片的少数业务,框架提供手段,便捷地获取所有分片数据库的数据,并由用户自行归并得出所需结果(简单的归并框架可以自动进行)。
先简略展示以下框架的基本用法(以下代码在UT案例中,但为突出重点,有所裁剪)
Service层
@Service
@ShardingContext(dataSourceSet="orderSet",shardingKeyEls="[user].userId",shardingStrategy="@modUserId",generateIdStrategy="@snowflaker",generateIdEls="[user].userId")
public class UserServceImpl {
@Autowired
private UserDaoImpl userDao;
@Transactional
@SelectDataSource
public void updateUser(User user){
userDao.updateUser(user);
}
@Transactional
@SelectDataSource
@GenerateId
public void saveUser(User user){
userDao.saveUser(user);
}
@Transactional(readOnly=true)
@SelectDataSource(keyNameEls="[userId]")
public User findUser(int userId){
return userDao.findUser(userId);
}
public List findAllUsers(){
return userDao.findAllUsers();
}
public double calcUserAvgAge(){
List allUsers = userDao.findAllUsers();
return allUsers.stream().mapToInt(u->u.getAge()).average().getAsDouble();
}
}
@ShardingContext表示当前的Service的Sharding上下文,就是说,如果有 选择数据源、Map到各数据库Reduce出结果、生成ID等操作时,如果某些参数没有指定,都从这个ShardingContext里面的配置取
@SelectDataSource表示为该方法内执行的SQL根据Sharding策略选择一个Sharding数据源,在方法结束返回前,不能更改Sharding数据源
@GenerateId表示生成ID,并将其赋值到参数的指定位置
@GenerateId对应的逻辑会先执行,然后到@SelectDataSource然后到@Transaction
@Transactional(readOnly=true)标签指定了事务时只读的,因此框架会根据readOnly标志自动选择读库(如果有的话)
从方法calcUserAvgAge可以看到在JDK8的LAMBADA表达式及Stream功能下,JAVA分析处理集合数据变得极为简单,这会大大减少我们自行加工Sharding分片数据的复杂度。
接下来看DAO层
@Component
public class UserDaoImpl {
@Autowired
private JdbcTemplate jdbcTemplate;
public void updateUser(User user){
int update = jdbcTemplate.update("UPDATE `user` SET `name`=? WHERE `user_id`=?;",user.getName(),user.getUserId());
Assert.isTrue(update == 1,"it should be updated!");
}
public User findUser(int userId){
return jdbcTemplate.queryForObject("SELECT * FROM user WHERE user_id = ?", new Object[]{userId}, rowMapper);
}
@Transactional
@MapReduce
public List findAllUsers(){
return jdbcTemplate.query("SELECT * FROM user", rowMapper);
}
@Transactional(readOnly=true)
@MapReduce
public void findAllUsers(ReduceResultHolder resultHolder){
List shardingUsers = jdbcTemplate.query("SELECT * FROM user", rowMapper);
resultHolder.setShardingResult(shardingUsers);
}
}
@MapReduce表示该方法将会在每个数据分片都执行一遍,然后进行数据聚合后返回。 对于聚合前后返回的数据类型一致的方法,调用时可以直接从返回值取得聚合结果。 对于聚合前后返回的数据类型不一致的方法,需要传入一个对象ReduceResultHolder,调用完成后,通过该对象获得聚合结果
默认情况下,框架会提供一个通用Reduce策略,如果是数字则累加返回,如果是Collection及其子类则合并后返回,如果是MAP则也是合并后返回。 如果该策略不适合,那么用户可自行设计指定Reduce策略。
@Transaction表示每一个Sharding执行的SQL都处于一个事务中,并不是表示整个聚合操作是一个整体的事务。所以,MapReduce最好不要进行更新操作(考虑框架层次限制MapReduce只允许ReadOnly事务)。
@MapReduce执行的操作会在@Transaction之前。
以上是框架的主要使用形式,我们可以从这种实现中发现服务层的Sharding有以下好处
当然也存在缺点
劣势:
更具体使用案例请参考 测试Package:org.easydevelop.business里的案例
sharding-method分表分库的新思路——服务层Sharding框架,全SQL、全数据库兼容,ACID特性与原生数据库一致,能实现RR级别读写分离,无SQL解析性能更高 目前国内主流的Sharding框架都是基于SQL来完成,其主要流程: 1)是解析上层传入的SQL 2)结合对应的分表分库配置,对传入的SQL进行改写并分发到对应的单机数据库上 3)获得各个单机数据库的返回结果后,根据原
Sharding Sphere,Sharding JDBC学习笔记 1、什么是 ShardingSphere? 一套开源的分布式数据库中间件解决方案 有三个产品:Sharding-JDBC 和 Sharding-Proxy 定位为关系型数据库中间件,合理在分布式环境下使用关系型数据库操作 2、什么是分库分表? 开发中遇到的问题: 数据库数据量不可控的,随着时间和业务发展,造成表里面数据越来越多
被Sharding的数据应该Share Nothing的。所谓“Share Nothing”是指
获取所有表分布 TableRule tableRule = ShardingDataSource…getRuntimeContext().getRule()…getTableRule(logicTableName) public Map<String, Set<String>> getDistTopology(final String logicTableName) if (dataSource
关于sharding mode,pika底层提供slot 的概念。Pika将key进行哈希取模之后散列到各个slot当中处理。sharding mode 根据线上的具体情况可以应用于单个pika,也可以应用到多个pika组成的pika cluster。这个tutorial主要介绍开启sharding mode 需要了解的一些概念,以及需要调整的一些配置参数。 0. 模式的选择 目前pika 分为两
我对Cassandra来说是个新手,这篇文章解释了分片和复制,我被困在了一个点上- 我在本地计算机上配置了一个包含6个Cassandra节点的集群。我创建了一个新的关键字空间“TestKeyspace”,复制因子为6,并在关键字空间“Employee”中创建了一个表,主键是名为rid的自动增量数字。我无法理解这些数据将如何分区和复制。我想知道的是,既然我将复制因子保持为6,并且数据将分布在多个节点
DM (Data Migration) 使用 sharding DDL lock 来确保分库分表的 DDL 操作可以正确执行。绝大多数情况下,该锁定机制可自动完成;但在部分异常情况发生时,需要使用 unlock-ddl-lock 手动处理异常的 DDL lock。 注意: 本文档只适用于悲观协调模式下 sharding DDL lock 的处理。 不要轻易使用 unlock-ddl-lock 命令
我在Docker上有分片系统。我有一个带有副本集的6个碎片(P-S-A)、带有副本集的配置服务器和2个mongo服务器。
我的用例很简单:从pub/sub订阅中读取事件日志,解析它们并保存到BigQuery中。由于预计事件的数量将显著增加,而且我使用的是无界数据源,所以我决定在BigQuery中配置sharding:根据事件数据的时间戳(在Beam文档中称为“事件时间”)将事件存储到每日表中。我的问题是,我是否需要在我的情况下配置窗口,或者我可以只保留默认配置,隐式地使用全局窗口?我之所以问这个问题,是因为我发现的大
MySQL插件式存储引擎是MySQL数据库服务器中的组件,负责为数据库执行实际的数据I/O操作,并能允许和强制执行面向特殊应用需求的特定特性集合。使用特殊存储引擎的主要优点之一在于,仅需提供特殊应用所需的特性,因此,数据库中的系统开销较小,最终结果具有更有效和更高的数据库性能。这也是MySQL被始终视为具有高性能的原因之一,在行业标准基准方面,它能匹敌或击败专有的整体式数据库。 从技术角度上看,在