这里做了一个简单的, 对主要部分的翻译, 算是一个笔记
原文:http://www.jroller.com/kenwdelong/entry/horizontal_database_partitioning_with_spring
这里以user表为例来做水平切分, 将与user相关的信息(如user的profile, user创建的内容等)放在同一个分区中, 同时使用GLUD(user的主键和分区id的组合)来作为定位一条user记录的key值
这里是一种解决方案(类似Hibernate Shards):
一个hibernate session factory对应一个数据库, 比如有user1, user2两个数据库, 则有两个session factory, 以此类推. 最后会创建两个ProfileService实例来对应不同的数据源, 在调用ProfileService前, 将通过一个Spring拦截器获取user的主键, 然后通过GLUD来确定该user存储在哪个数据库中, 最后决定调用哪个ProfileService实例
其实Spring也有一种解决方案, 那就是使用AbstractRoutingDataSource, 该方案只需要一个SessionFatory和一个ProfileService, 它通过借助Spring的AbstractRoutingDataSource和一些规则自动定位到指定的DataSource上.
下面来说说Hibernate Shards的一些缺点, 首先这个项目还比较新, 也不是很活跃, 稳定性有待考量, 此外一个datasource对应一个session factory灵活性很差, 比如数据库服务器达到myspace这种规模, 创建session factory消耗的资源几乎是不可想象的.还有一点,就是在与Spring集成方面, 它的文档也是含糊不清, 因为它没法和LocalSessionFactoryBean一起工作, 因此要利用Spring的事务管理,还得自己去实现一个类似ShardsSessionFactoryBean的bean, 这个也是我非所愿的
经过比较之后, 下面是我们的实现
首先创建一个GLUD数据库, 它包含有主库(master)的user表(master_user), 该表保存了用户的主键和email信息(也可以是唯一标识一条user记录的其他信息), 另一个是partition_map表, 也就是分区表id, 通过对user的主键hash之后能映射到某个分区id, 这样当你拿到一个user的pk值或者任意的唯一标识值之后, 就可以确定该记录存放在那个分区中.
通过写了一个Spring AOP的拦截器来获取记录所在的分区, 所有要使用分区数据库的service都会被该拦截器路由到指定的分区中, 同时规定, 使用分区数据的Service的所有方法的第一个参数必须为: 一个user 对象, 一个user的pk, 或者其他能唯一标识一条user记录的值, 拦截器代码如下:
其中datasourceNumberCache是一个用来保存分区id的ThreadLocal<Integer>变量
为了处理各种情况, 这里还用到了annocations,
在所有需要使用拦截的方法加上如下声明:
LocatePreexistingUser的定义如下:
UserIdentifier是一个枚举类型, 其值为:USER_OBJECT, EMAIL, and USER_PK.当对唯一标识字段值修改时(比如, 修改email), 需要做如下设置:
而在拦截器中需要做如下设置:
当hibernate在访问数据库的时候, 将通过datasource取得一个connection, PartitionRoutingDataSource(AbstractRoutingDataSource实现类) 将通过ThreadLocal取得分区id,然后取得记录所在的DataSource:
datasource bean xml配置如下:
hibernate session factory xml配置:
从上面的配置可以看出, 对分区进行扩展将会非常简单, 但是还有一个问题, 在spring中开启一个事务之前必须获取分区的id, 因此分区服务拦截器必须在事务拦截器之前被执行, 因此还需要做如下设置:
在使用spring, hibernate做分区处理的时候还有一些地方需要注意, 比如, 二级缓存, jta事务, 数据共享, 测试等, 这里不做一一展开
原文:http://www.jroller.com/kenwdelong/entry/horizontal_database_partitioning_with_spring
这里以user表为例来做水平切分, 将与user相关的信息(如user的profile, user创建的内容等)放在同一个分区中, 同时使用GLUD(user的主键和分区id的组合)来作为定位一条user记录的key值
这里是一种解决方案(类似Hibernate Shards):
一个hibernate session factory对应一个数据库, 比如有user1, user2两个数据库, 则有两个session factory, 以此类推. 最后会创建两个ProfileService实例来对应不同的数据源, 在调用ProfileService前, 将通过一个Spring拦截器获取user的主键, 然后通过GLUD来确定该user存储在哪个数据库中, 最后决定调用哪个ProfileService实例
其实Spring也有一种解决方案, 那就是使用AbstractRoutingDataSource, 该方案只需要一个SessionFatory和一个ProfileService, 它通过借助Spring的AbstractRoutingDataSource和一些规则自动定位到指定的DataSource上.
下面来说说Hibernate Shards的一些缺点, 首先这个项目还比较新, 也不是很活跃, 稳定性有待考量, 此外一个datasource对应一个session factory灵活性很差, 比如数据库服务器达到myspace这种规模, 创建session factory消耗的资源几乎是不可想象的.还有一点,就是在与Spring集成方面, 它的文档也是含糊不清, 因为它没法和LocalSessionFactoryBean一起工作, 因此要利用Spring的事务管理,还得自己去实现一个类似ShardsSessionFactoryBean的bean, 这个也是我非所愿的
经过比较之后, 下面是我们的实现
首先创建一个GLUD数据库, 它包含有主库(master)的user表(master_user), 该表保存了用户的主键和email信息(也可以是唯一标识一条user记录的其他信息), 另一个是partition_map表, 也就是分区表id, 通过对user的主键hash之后能映射到某个分区id, 这样当你拿到一个user的pk值或者任意的唯一标识值之后, 就可以确定该记录存放在那个分区中.
通过写了一个Spring AOP的拦截器来获取记录所在的分区, 所有要使用分区数据库的service都会被该拦截器路由到指定的分区中, 同时规定, 使用分区数据的Service的所有方法的第一个参数必须为: 一个user 对象, 一个user的pk, 或者其他能唯一标识一条user记录的值, 拦截器代码如下:
public Object selectExistingPartitionWithUser(ProceedingJoinPoint jp, LocatePreexistingUser annotation, User user) throws Throwable
{
GludEntry gludEntry = getGludService().getGludEntryForExistingUser(user);
int partitionNumber = gludEntry.getDatabasePartition();
datasourceNumberCache.set(partitionNumber);
Object returnValue = null;
try
{
returnValue = jp.proceed();
}
finally
{
datasourceNumberCache.remove();
}
return returnValue;
}
其中datasourceNumberCache是一个用来保存分区id的ThreadLocal<Integer>变量
为了处理各种情况, 这里还用到了annocations,
在所有需要使用拦截的方法加上如下声明:
@Around(value="@annotation(annotation) && args(user, ..)", argNames="annotation,user")
LocatePreexistingUser的定义如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LocatePreexistingUser
{
public UserIdentifier userIdentifier() default USER_OBJECT;
public boolean userUpdate() default false;
}
UserIdentifier是一个枚举类型, 其值为:USER_OBJECT, EMAIL, and USER_PK.当对唯一标识字段值修改时(比如, 修改email), 需要做如下设置:
@LocatePreexistingUser(userUpdate=true)
public void updateEmail(User user, String newEmail)
{ ... }
而在拦截器中需要做如下设置:
if(annotation.userUpdate)
{
// tell GLUD service to update its master_user record
}
当hibernate在访问数据库的时候, 将通过datasource取得一个connection, PartitionRoutingDataSource(AbstractRoutingDataSource实现类) 将通过ThreadLocal取得分区id,然后取得记录所在的DataSource:
protected Object determineCurrentLookupKey()
{
Integer datasourceNumber = DatasourceSwitchingAspect.datasourceNumberCache.get();
return datasourceNumber;
}
datasource bean xml配置如下:
<bean id="userDataSource" class="PartitionRoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.Integer">
<entry key="1" value-ref="user1DataSource"/>
<entry key="2" value-ref="user2DataSource"/>
</map>
</property>
</bean>
hibernate session factory xml配置:
<bean id="userSessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="userDataSource"/>
etc etc
</bean>
从上面的配置可以看出, 对分区进行扩展将会非常简单, 但是还有一个问题, 在spring中开启一个事务之前必须获取分区的id, 因此分区服务拦截器必须在事务拦截器之前被执行, 因此还需要做如下设置:
<aop:config>
<aop:pointcut id="profileServicePointcut" expression="execution(* *..ProfileService.*(..))"/>
<aop:advisor advice-ref="userTxAdvice" pointcut-ref="profileServicePointcut" order="2"/>
</aop:config>
在使用spring, hibernate做分区处理的时候还有一些地方需要注意, 比如, 二级缓存, jta事务, 数据共享, 测试等, 这里不做一一展开