追求极致的数据库分区分表方案

序言

一直在做企业应用,目前要做一些互联网应用,当然只是应用是放在互联网的,数据量距离真正的互联网应用还是有相当大的差距的。但是不可避免的,在数据库出现瓶颈的情况还是有的,现在做互联网上的应用,当然也要未雨绸缪,要考虑数据量大的时候的解决方案。

这个目前开源的商用的也都有不少解决方案,一来,做技术的都有这么个臭毛病,即使是使用别人的方案,自己也要搞清楚内部的一些实现机制,这样才会有真正的体会,否则去评估一个方案的时候,就只能盲人摸象了。

为此,构建一个验证型的分布式数据库框架,来解决数据库的垂直与水平扩展方面的问题,由于是验证性开发,所以,思考不完善的地方肯定存在,欢迎批评指正。

提升数据库处理能力方案

读写分离方案

海量数据的存储及访问,通过对数据库进行读写分离,来提升数据的处理能力。读写分离它的方案特点是数据库产生多个副本,数据库的写操作都集中到一个数据库上,而一些读的操作呢,可以分解到其它数据库上。这样,只要付出数据复制的成本,就可以使得数据库的处理压力分解到多个数据库上,从而大大提升数据处理能力。

  • 优点:由于所有的数据库副本,都有数据的全拷贝,因此所有的数据库特性都可以实现,部分机器当机不影响系统的使用。
  • 缺点:数据的复制同步是一个问题,要么采用数据库自身的复制方案,要么自行实现数据复制方案。需要考虑数据的迟滞性,一致性方面的问题。

数据分区方案

原来所有的数据都是在一个数据库上的,网络IO及文件IO都集中在一个数据库上的,因此CPU、内存、文件IO、网络IO都可能会成为系统瓶颈。而分区的方案就是把某一个或某几张相关的表的数据放在一个独立的数据库上,这样就可以把CPU、内存、文件IO、网络IO分解到多个机器中,从而提升系统处理能力。

  • 优点:不存在数据库副本复制,性能更高。
  • 缺点:分区策略必须经过充分考虑,避免多个分区之间的数据存在关联关系,每个分区都是单点,如果某个分区宕机,就会影响到系统的使用。

数据分表方案

不管是上面的读写分离方案还是数据分区方案,当数据量大到一定程度的时候,都会导致处理性能的不足,这个时候就没有办法了,只能进行分表处理。也就是把数据库当中数据根据按照分库原则分到多个数据表当中,这样,就可以把大表变成多个小表,不同的分表中数据不重复,从而提高处理效率。

  • 优点:数据不存在多个副本,不必进行数据复制,性能更高。
  • 缺点:分表之间的数据很少进行集合运算;分表都是单点,如果某个分表宕机,如果使用的数据不在此分表,不影响使用。

分表也有两种方案:

1. 同库分表:所有的分表都在一个数据库中,由于数据库中表名不能重复,因此需要把数据表名起成不同的名字。

  • 优点:由于都在一个数据库中,公共表,不必进行复制,处理更简单
  • 缺点:由于还在一个数据库中,CPU、内存、文件IO、网络IO等瓶颈还是无法解决,只能降低单表中的数据记录数。表名不一致,会导后续的处理复杂。

2. 不同库分表:由于分表在不同的数据库中,这个时候就可以使用同样的表名。

  • 优点:CPU、内存、文件IO、网络IO等瓶颈可以得到有效解决,表名相同,处理起来相对简单
  • 缺点:公共表由于在所有的分表都要使用,因此要进行复制、同步。

混合方案

通过上面的描述,我们理解了读写分离,数据分区,数据分表三个解决方案,实际上都各有优点,也各有缺 ,因此,实践当中,会把三种方案混合使用。由于数据不是一天长大的,实际上,在刚开始的时候,可能只采用其中一种方案,随着应用的复杂,数据量的增长,会逐步采用多个方案混合的方案。以提升处理能力,避免单点。

实现路线分析

正所谓条条大路通罗马,解决这个问题的方案也有多种,但究其深源,都可以归到两种方案之上,一种是对用户透明的方案,即用户只用像普通的JDBC数据源一样访问即可,由框架解决所有的数据访问问题。另外一种是应用层解决,具体一般是在Dao层进行封装。

JDBC层方案

  • 优点:开发人员使用非常方便,开发工作量比较小;可以实现数据库无关。
  • 缺点:框架实现难度比较大,性能不一定能做到最优。

同样是JDBC方案,也有两种解决方案,一种是有代理模式,一种是无代理模式。

有代理模式,有一台专门的代理服务器,来接收用户请求,然后发送请求给数据库集群中的数据,并对数据进行汇集后再提交给请求方。

无代理模式,就是说没有代理服务器,集群框架直接部署在应用访问端。

有代理模式,能够提供的功能更强大,甚至可买提供中间库进行数据处理,无代理模式处理性能较强有代理模式少一次网络访问,相对来说性能更好,但是功能性不如有代理模式。

DAO层方案

  • 优点:开发人员自由度非常大,性能调优更精准。
  • 缺点:开发人员在一定程度上受影响,与具体的Dao技术实现相关,较难做到数据库无关。

由于需要对SQL脚本进行判断,然后进行路由,因此DAO层优化方案一般都是选用iBatis或Spring Jdbc Template等方案进行封装,而对于Hibernate等高度封装的OR映射方案,实现起来就非常困难了。

需求

需求决定了后续的解决方案及问题领域:

  • 采用JDBC层解决方案:对于最终用户来说,要完全透明
  • 采用无代理解决方案:数据库集群框架代码直接放在应用层
  • 支持读写分离、分区、分表三种方式及其混合使用方式:三种方式可以混用可以提供极大的灵活性及对未来的扩展性
  • 需要提供灵活的分区及分表规则支持
  • 对于读写分离的方案,需要提供灵活的路由规则,比如:平均路由规则、加权路由规则,可以提供写库的备用服务器,即主写入服务器当机之后,即可写入备用服务器当中。
  • 支持高性能分布式主键生成器
  • 有良好的集群事务功能
  • 可以通过扩展点来对框架进行扩展,以便于处理分区、分表相关的操作。
  • 支持各种类型支持JDBC驱动的数据库
  • 支持异构数据库集群
  • 支持count、sum、avg、min、max等统计函数
  • 支持排序
  • 支持光标移动
  • 支持结果集合并
  • 支持数据库自增长主键
  • 支持数据库分页指令
  • 支持DDL语句处理
  • 支持Grovy脚本定义规则
  • 支持JDK1.5~1.8
  • 支持连接延迟获取--只到使用时才申请数据库连接,提升性能降低资源战胜

明确不支持的内容或限定条件:

  • 不支持分区之间的联合查询

特性说明

上面的红色部分特性是最新添加的功能特性,这里简单解释一下:

支持数据库自增长主键:比如:Mysql,SqlServer等数据库支持 auto increase主键,原来不支持,现在可以完美支持了

支持数据库分页指令:比如,有些数据库支持start limit或类似的分页指令,原来不支持,现在可以完美支持了

支持DDL语句:原来对数据库结构方面的操纵,原来不支持,现在可以完美的支持了,比如:可以一次修改所有的分片的表结构

支持Grovy脚本定义分区分片规则:原来是只能采用实现接口的方式进行分区和分片规则的实现,现在可以用Grovyy脚本来定义了

支持JDK1.5~1.8:原来是是分成两个版本,一个jdbc3,一个jdbc4的,框架开发和维护非常麻烦,现在合并成一个工程,两个可以同步支持了

支持连接延迟获取:原来是整个分区分片中需要的连接一次申请到,现在优化为需要时才申请,这样会大大降低对数据库连接资源的需要,同时由于事务变小,也可以显著提升处理效率。

结构设计

框架采用三层设计:最上层是Cluster,一个Cluster相当于我们常规的一个数据库;一个Cluster当中可以包含一到多个Partition,也就是分区;而一个Partition中可以包含一到多个Shard,也就是分片。

所以一个就形成了一个树状结构,通过Cluster->Partion->Shard就构成了整个数据库集群。但是对于开发人员来说,实际上并不知道这个内部结构,他只是连接上了一个JDBC数据源,然后做它应该做的事情就可以了。

Cluster

以完整的形态对外提供服务,它封装了Cluster当中所有Partition及其Shard的访问。把它打开是一个数据库集群,对于使用者来说是一个完整的数据库。


属性名


类型


说明


id


String


集群标识


userName


String


连接集群时的用户名


Password


String


连接集群时的密码


dataSources


List<DataSourceConfig>


集群中需要访问的数据源列表


partitions


List<Partition>;


集群中包含的分区列表

Partition

分区,分区有两种模式,一种是主从模式,用于做读写分离;另外一种模式是分片模式,也就是说把一个表中的数据分解到多个表中。一个分区只能是其中的一种模式。但是一个Cluster可以包含多个分区,不同的分区可以是不同的模式。


属性名


类型


说明


id


String


分区标识


mode


int


分区类型,可以是主从,也可以是分表


Password


String


连接集群时的密码


shards


List<Shard>


分区中包含的分片列表


partitionRules


List<PartitionRule>


分区规则,当进行处理的时候,路由到哪个分区执行

Shard

Shard与一个物理的数据源相关联。


属性名


类型


说明


id


String


分区标识


dataSourceId


String


实际访问的数据库配置ID


readWeight


int


读权重,仅用于主从读写分离模式


writeWeight


int


写权重,仅用于主从读写分离模式


shardRules


List<ShardRule>


分片规则,当进行处理的时候,路由到哪个分片执行,仅用于分模式


tableMappings


List<TableMapping>;


表名映射列表,仅用于同库不同表名分表模式

分布式主键接口

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

/**

 

* 分布式Key获取器

 

*

 

* @param <T>

 

*/

 

public interface ClusterKeyGenerator<T> {

 

T getKey(String tableName);

 

}

主键接口可以用来生成各种主键类型,如:字符串、整型、长整型,入口参数必须是表名,框架已经实现了字符串、整型、长整型的分布式高效主键生成器,当然,也可以自行实现。

集群管理器

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

public interface ClusterManager {

 

/**

 

* 返回是否是分片语句

 

*

 

* @param partition

 

* @param sql

 

* @return

 

*/

 

boolean isShardSql(Partition partition, String sql);

 

/**

 

* 添加语句处理器

 

*

 

* @param statementProcessor

 

*/

 

void addStatementProcessor(StatementProcessor statementProcessor);

 

/**

 

* 返回语句处理器列表

 

*

 

* @return

 

*/

 

List<StatementProcessor> getStatementProcessorList();

 

/**

 

* 给某个集群的数据表产生主键

 

*

 

* @param cluster

 

* @param tableName

 

* @param <T>

 

* @return

 

*/

 

<T> T getPrimaryKey(Cluster cluster, String tableName);

 

/**

 

* 返回SQL对应的Statement

 

*

 

* @param sql

 

* @return

 

*/

 

Statement getSqlStatement(String sql);

 

/**

 

* 添加集群

 

*

 

* @param cluster

 

*/

 

void addCluster(Cluster cluster);

 

/**

 

* 获取集群

 

*

 

* @param clusterId

 

* @return

 

*/

 

Cluster getCluster(String clusterId);

 

/**

 

* 返回某个分区与sql是否匹配

 

*

 

* @param partition

 

* @param sql

 

* @return

 

*/

 

boolean isMatch(Partition partition, String sql);

 

/**

 

* 返回某个分片是否匹配

 

*

 

* @param shard

 

* @param sql

 

* @return

 

*/

 

boolean isMatch(Partition partition, Shard shard, String sql);

 

/**

 

* 返回分片执行语句

 

*

 

* @param partition

 

* @param shard

 

* @param sql

 

* @return

 

*/

 

String getSql(Partition partition, Shard shard, String sql);

 

/**

 

* 获取匹配的分区<br>

 

*

 

* @param clusterId

 

* @param sql

 

* @return

 

*/

 

Collection<Partition> getPartitions(String clusterId, String sql);

 

/**

 

* 获取匹配的首个分区

 

*

 

* @param clusterId

 

* @param sql

 

* @return

 

*/

 

Partition getPartition(String clusterId, String sql);

 

/**

 

* 获取匹配的首个分区

 

*

 

* @param cluster

 

* @param sql

 

* @return

 

*/

 

Partition getPartition(Cluster cluster, String sql);

 

/**

 

* 获取匹配的分区

 

*

 

* @param cluster

 

* @param sql

 

* @return

 

*/

 

List<Partition> getPartitions(Cluster cluster, String sql);

 

/**

 

* 获取匹配的分片

 

*

 

* @param partition

 

* @param sql

 

* @return

 

*/

 

List<Shard> getShards(Partition partition, String sql);

 

/**

 

* 返回分片均衡器

 

*

 

* @return

 

*/

 

ShardBalance getShardBalance();

 

/**

 

* 设置分片均衡器

 

*

 

* @param balance

 

*/

 

void setShardBalance(ShardBalance balance);

 

}

分区规则

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

/**

 

* 分区规则接口<br>

 

* 规则参数在实现类中定义

 

*

 

*/

 

public interface PartitionRule {

 

/**

 

* 返回是否命中,如果有多个命中,则只用第一个进行处理

 

*

 

* @param sql

 

* @return

 

*/

 

boolean isMatch(String sql);

 

}

框架自带了常用分区规则,但是也可以根据情况自已扩展。

分片规则

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

/**

 

* 分片规则

 

*

 

*/

 

public interface ShardRule {

 

/**

 

* 返回是否属于当前分片处理

 

*

 

* @param sql

 

* @return

 

*/

 

boolean isMatch(Partition partition, String sql);

 

}

框架自带了常用的分片规则,但是也可以根据情况自已扩展

语句处理器

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

/**

 

* 用于对SQL进行特殊处理并进行结果合并等<br>

 

* <p/>

 

* 比如sql语句是select count(*) from abc<br>

 

* 则会到所有的shard执行,并对结果相加后返回

 

*

 

*/

 

public interface StatementProcessor {

 

/**

 

* 返回是否由此SQL处理器进行处理

 

*

 

* @param sql

 

* @return

 

*/

 

boolean isMatch(String sql);

 

/**

 

* 返回处理器转换过之后的SQL

 

*

 

* @param sql

 

* @return

 

*/

 

String getSql(String sql);

 

/**

 

* 对结果进行合并

 

*

 

* @param results

 

* @return

 

* @throws SQLException

 

*/

 

ResultSet combineResult(List<ResultSet> results) throws SQLException;

 

}

在分片之后,许多时候单纯查找回的数据已经是不正确的了,这个时候就需要进行二次处理才能保证返回的结果是正确的。

比如:用户输入的SQL语句是:Select count(*) from aaa

这个时候就会用分片指令到各个分片去查找并返回结果,默认的处理结果是简单合并结果集的方式。这个时候,如果有5个分片,会返回5条记录给最终用户,这当然不是他想要的结果。这个时候就是语句处理器大显身手的时候了,他可以偷梁换柱,也可以改头换面,通过它的处理,就可以返回正确的结果了。

JDBC层封装

当然,要想外部程序用JDBC的方式进行访问,就得做JDBC层的实现。这个部分做了大量的处理,使得即高效又与用户期望的方式相匹配。

可以说上面所有的准备都是为了这一层做准备的,毕竟最终要落到真正的数据库访问上。由于接口就是标准的JDBC接口,因此就不再详述。

事务问题

在分区或分表模式中,由于写操作会被分解到不同的物理数据库上去,这就会导致出现事务问题。因此框架内部集成了JTA,使得事务保持一致。

代码实现

没什么好说的,噼里啪啦,噼里啪啦,一阵乱响,代码就绪了,下面看看测试场景。

测试用例

JDBC方式访问集群

?


1

2

3

4

5

6

7

Class.forName("org.tinygroup.dbcluster.jdbc.TinyDriver");

 

Connection conn = DriverManager.getConnection("jdbc:dbcluster://cluster1", "username", "password");

 

Statement stmt = conn.createStatement();

 

stmt.execute(“select * from aaa”);

代码解释:

上面完全是按照JDBC的方式访问数据库的,url必须以“jdbc:dbcluster://”开始,后面跟着的是集群的ID名称,上面示例中就是“cluster1”;用户名、密码必须与集群中配置的相一致。接下来就与普通的jdbc数据源没有任何区别了。

同库分表

创建测试表

在同一个数据库中创建同样结构的表,比如:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

CREATE TABLE `aaa0` (

 

`id` int(11) NOT NULL,

 

`aaa` varchar(45) DEFAULT NULL,

 

PRIMARY KEY (`id`)

 

);

 

CREATE TABLE `aaa1` (

 

`id` int(11) NOT NULL,

 

`aaa` varchar(45) DEFAULT NULL,

 

PRIMARY KEY (`id`)

 

);

 

CREATE TABLE `aaa2` (

 

`id` int(11) NOT NULL,

 

`aaa` varchar(45) DEFAULT NULL,

 

PRIMARY KEY (`id`)

 

);

测试代码:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

public static void main(String[] args) throws Throwable {

 

Class.forName("org.tinygroup.dbcluster.jdbc.TinyDriver");

 

Connection conn = DriverManager.getConnection("jdbc:dbcluster://cluster1", "username", "password");

 

Statement stmt = conn.createStatement();

 

String sql;

 

//插入100条数据

 

for (int i = 0; i < 100; i++) {

 

sql = "insert into aaa(id,aaa) values (" + clusterManager.getPrimaryKey(cluster, "aaa") + ",'ppp')";

 

boolean result = stmt.execute(sql);

 

}

 

}

运行结果:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

Using shard:shard1 to execute sql:insert into aaa(id,aaa) values (1,'ppp')

 

Using shard:shard2 to execute sql:insert into aaa(id,aaa) values (2,'ppp')

 

Using shard:shard0 to execute sql:insert into aaa(id,aaa) values (3,'ppp')

 

Using shard:shard1 to execute sql:insert into aaa(id,aaa) values (4,'ppp')

 

Using shard:shard2 to execute sql:insert into aaa(id,aaa) values (5,'ppp')

 

Using shard:shard0 to execute sql:insert into aaa(id,aaa) values (6,'ppp')

 

…….

可以看出,插入的数据确实分到了三个分片中。

再用Select语句查找插入的数据:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

public static void main(String[] args) throws Throwable {

 

Class.forName("org.tinygroup.dbcluster.jdbc.TinyDriver");

 

Connection conn =

 

DriverManager.getConnection("jdbc:dbcluster://cluster1", "username", "password");

 

Statement stmt = conn.createStatement();

 

String sql = "select * from aaa order by id";

 

ResultSet resultSet = stmt.executeQuery(sql);

 

while (resultSet.next()) {

 

System.out.printf(" id: %d, aaa: %s \n", resultSet.getInt(1), resultSet.getString(2));

 

}

 

}

运行结果如下:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

Using shard:shard0 to execute sql:select * from aaa order by id

 

Using shard:shard1 to execute sql:select * from aaa order by id

 

Using shard:shard2 to execute sql:select * from aaa order by id

 

id: 1, aaa: ppp

 

id: 2, aaa: ppp

 

id: 3, aaa: ppp

 

id: 4, aaa: ppp

 

id: 5, aaa: ppp

 

id: 6, aaa: ppp

 

……

从上面的结果可以看到,明显已经合并了结果并且是按顺序显示的

接下来,把测试的数据删除掉:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

public static void main(String[] args) throws Throwable {

 

Class.forName("org.tinygroup.dbcluster.jdbc.TinyDriver");

 

Connection conn = DriverManager.getConnection("jdbc:dbcluster://cluster1", " username ", "password");

 

Statement stmt = conn.createStatement();

 

String sql = "delete from aaa";

 

stmt.execute(sql);

 

}

运行结果如下:

?


1

2

3

4

5

Using shard:shard0 to execute sql:delete from aaa

 

Using shard:shard1 to execute sql:delete from aaa

 

Using shard:shard2 to execute sql:delete from aaa

再去数据库中查看,数据确实已经被删除。

不同库分表

在不同的数据库中创建同样结构的表,比如:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

CREATE TABLE test0. aaa (

 

`id` int(11) NOT NULL,

 

`aaa` varchar(45) DEFAULT NULL,

 

PRIMARY KEY (`id`)

 

);

 

CREATE TABLE test1. aaa(

 

`id` int(11) NOT NULL,

 

`aaa` varchar(45) DEFAULT NULL,

 

PRIMARY KEY (`id`)

 

);

 

CREATE TABLE test2. aaa(

 

`id` int(11) NOT NULL,

 

`aaa` varchar(45) DEFAULT NULL,

 

PRIMARY KEY (`id`)

 

);

测试用例同同库分表,结果测试同样OK。

读写分离

插入与删除等比较简单,就不再展示了,下面看看读指令的执行过程。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

public static void main(String[] args) throws Throwable {

 

Class.forName("org.tinygroup.dbcluster.jdbc.TinyDriver");

 

Connection conn =

 

DriverManager.getConnection("jdbc:dbcluster://cluster1", "username", "password");

 

Statement stmt = conn.createStatement();

 

for (int i = 1; i <= 100; i++) {

 

boolean result = stmt.execute(“select * from aaa”);

 

}

 

}

运行结果:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

Using shard:shard3 to execute sql:select * from aaa

 

Using shard:shard2 to execute sql:select * from aaa

 

Using shard:shard1 to execute sql:select * from aaa

 

Using shard:shard1 to execute sql:select * from aaa

 

Using shard:shard1 to execute sql:select * from aaa

 

Using shard:shard1 to execute sql:select * from aaa

 

Using shard:shard2 to execute sql:select * from aaa

 

Using shard:shard1 to execute sql:select * from aaa

 

Using shard:shard1 to execute sql:select * from aaa

 

Using shard:shard1 to execute sql:select * from aaa

 

Using shard:shard1 to execute sql:select * from aaa

 

Using shard:shard1 to execute sql:select * from aaa

 

Using shard:shard3 to execute sql:select * from aaa

可以看到,读的SQL已经由三个分片进行了均衡执行。

记录集遍历

对于ResultSet的遍历,也有良好的支持,对于各种移动光标的方法都有支持,而且支持排序的移动,同时对于性能也有良好支持,性能接近于单表操作。

下面展示一下绝对定位:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

Class.forName("org.tinygroup.dbcluster.jdbc.TinyDriver");

 

Connection conn = DriverManager.getConnection(

 

"jdbc:dbcluster://cluster1", "luog", "123456");

 

Statement stmt = conn.createStatement();

 

String sql = "select * from aaa order by id";

 

ResultSet resultSet = stmt.executeQuery(sql);

 

resultSet.absolute(10);

 

System.out.printf(" id: %d, aaa: %s \n", resultSet.getInt(1),

 

resultSet.getString(2));

 

while (resultSet.next()) {

 

System.out.printf(" id: %d, aaa: %s \n", resultSet.getInt(1),

 

resultSet.getString(2));

 

}

运行结果:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

Using shard:shard0 to execute sql:select * from aaa order by id

 

Using shard:shard1 to execute sql:select * from aaa order by id

 

Using shard:shard2 to execute sql:select * from aaa order by id

 

id: 10, aaa: ppp

 

id: 11, aaa: ppp

 

id: 12, aaa: ppp

 

id: 13, aaa: ppp

 

id: 14, aaa: ppp

 

id: 15, aaa: ppp

 

id: 16, aaa: ppp

 

id: 17, aaa: ppp

 

id: 18, aaa: ppp

 

id: 19, aaa: ppp

 

…….

可以看到确实是从第10条开始显示。

总结

分区分片通用解决方案,确实有相当的通用性,支持各种数据库,提供了非常大的灵活性,支持多种集群单独或混合使用的场景,同时还可以保持数据访问的事务一致性,为用户的访问提供与JDBC一样的用户接口,这也会大大降低开发人员的开发难度。基本上(违反需求中指定的限制条件的除外)可以做到原有业务代码透明访问,降低了系统的迁移成本。同时它在性能方面也是非常杰出的,与原生的JDBC驱动程序相比,性能没有显著降低。当然它的配置也是非常简单的,学习成本非常低。由于做在JDBC层,因此可以对Hibernate,iBatis等各种框架有良好支持。

时间: 2024-09-16 22:35:14

追求极致的数据库分区分表方案的相关文章

分区分表支持

最近,正在做一个互联网项目,数据量比较大,因此,拟采用分区分表支持. 目前分区分表这种东东也比较多,在文章http://www.itokit.com/2012/0404/73496.html中有详细的介绍,写得非常好,建议对这个点比较感兴趣的同学前往观看. 文中也总结了觉的分区分表实现方式有两种:JDBC层的封装,ORM框架层的实现 Tiny框架中拟采用JDBC层的封装,采用JDBC层的封装有诸多好处,第一,对应用的影响最小,第二,对各种数据库有更好的更通用的支持:当然,同步带来的问题就是难度比

透明的分库分表方案

问题提出 随着应用规模的不断扩大,单机数据库就慢慢无法满足应用的需要了,这主要表现在如下方面: 存量数据越来越大,查询速度越来越慢 访问并发越来越大,磁盘IO.网络IO.CPU都慢慢成为瓶颈 事务数越来越多,事务冲突越来越严重,导致TPS越来越少 这个时候,有的人采用了换商用数据库的方案比如Oracle,然后用Oracle的RAC方式进行水平扩展.但是带来的缺点也比较明显,第一是成本太高,一般人吃不消:第二,管理复杂度较单节点有非常大的提升,风险及管理成本也相应增加:第三,对人员的水平要求更高,

数据库分表后,并发环境下,生成全局id生成的几种方式

最近一个项目由于数据量变大,需要进行数据分表.数据存储在淘宝的tddl上.分表后,原先的自增id就不能使用了.tddl对java支持很好,分表后无需考虑全局id的问题.但是这个项目使用的是php进行开发,必须自己生成全局id.以下列出几种分表方案,仅当抛砖引玉. 方法1:使用CAS(compare and swap) 其实这里并不是严格的CAS,而是使用了比较交换原子操作的思想. 生成思路如下: 每次生成全局id时,先从sequence表中获取当前的全局最大id.然后在获取的全局id上做加1操作

新闻数据库分表案例

Netkiller MySQL 手札 MySQL MariaDB... Mr. Neo Chan, 陈景峰(BG7NYT) 中国广东省深圳市龙华新区民治街道溪山美地518131+86 13113668890+86 755 29812080<netkiller@msn.com> 文档始创于2010-11-18 版权 2011, 2012, 2013 Netkiller(Neo Chan). All rights reserved. 版权声明 转载请与作者联系,转载时请务必标明文章原始出处和作者信

Mycat(4):消息表mysql数据库分表实践

本文的原文连接是: http://blog.csdn.net/freewebsys/article/details/46882777 未经博主允许不得转载. 1,业务需求 比如一个社交软件,比如像腾讯的qq.可以进行群聊天(gid),也可以单人聊天. 这里面使用到了数据库中间件mycat,和mysql数据表分区. 关于mycat分区参考: [ 数据库垂直拆分,水平拆分利器,cobar升级版mycat] http://blog.csdn.net/freewebsys/article/details

mysql数据库分表后生成全局id的几种方式

 最近一个项目由于数据量变大,需要进行数据分表.数据存储在淘宝的tddl上.分表后,原先的自增id就不能使用了.tddl对java支持很好,分表后无需考虑全局id的问题.但是这个项目使用的是php进行开发,必须自己生成全局id.以下列出几种分表方案,仅当抛砖引玉.    方法1:使用CAS(compare and swap)    其实这里并不是严格的CAS,而是使用了比较交换原子操作的思想.    生成思路如下:    每次生成全局id时,先从sequence表中获取当前的全局最大id.然后在

mongodb 分片代替数据库分表

MongoDB 的Sharding机制解决了海量存储和动态扩容的问题,但离生产环境的高可靠,高可用还有距离,Sharding在单点出现故障时就无能为力了.但是 MongoDB的副本集却可以很轻松的处理单点故障,所以就有了Replica Sets + Sharding的高可用,高安全的架构.Mongodb支持自动分片和划分架构,可以利用它构建一个水平扩展的数据库集群系统,将数据库分表存储在各个sharding节点上. 架构如下:       1,shard服务器:使用Replica Sets确保每

大数据-关于数据库分表后的 业务逻辑应该是怎么样,求解答!

问题描述 关于数据库分表后的 业务逻辑应该是怎么样,求解答! 数据库有一张表 数据太多 导致查询非常慢,分表后 业务逻辑是怎样的: 假如把一张表分成三张表,那么在项目里面写查询的时候是要连续查三张表么? 解决方案 如果是你的表太宽也就是字段太多可以考虑分表,按业务逻辑拆分如果是数据量太大导致查询缓慢,建议不分表.因为只是查询的话必然会对全表做一次扫描,起不到提高查询效率的作用.可以考虑以下几种方式:1. 通过索引的方式,使用索引字段查询2. 在表上建立分区,查询时指定分区条件3. 如果是关联查询

phalapi-进阶篇6(解决大量数据存储数据库分表分库拓展)

phalapi-进阶篇6(解决大量数据存储数据库分表分库拓展) 前言 时隔半个月随着PHP7的推出为PHP打了一瓶兴奋剂,在性能提升了一倍的情况下我们会逐渐发现,瓶颈会集中在数据库操作,那我们的内容就接着数据库读写分离,来聊聊分表分库应该怎么玩,应为PhalApi的分表分库并不是非常方便,笔者在这里提供了一个分表分库数据库集群的拓展,详细文档请见博客基于PhalApi的DB集群拓展 V0.1bate 大家可以自行在开源中国扩展Git地址中找到Cluster进行下载使用. 先在这里感谢phalap