Hibernate 分片提供了非常大的灵活性,您可以配置数据在分片中的分布方式以及在分片中查询数据的方式。此配置的入口点是org.hibernate.shards.strategy.ShardStrategy界面
public interface ShardStrategy { ShardSelectionStrategy getShardSelectionStrategy(); ShardResolutionStrategy getShardResolutionStrategy(); ShardAccessStrategy getShardAccessStrategy(); }
如您所见,ShardStrategy包括三个子策略。我们依次讨论每个策略。
我们从最简单的策略开始ShardAccessStrategy。Hibernate 分片使用ShardAccessStrategy为了确定如何在多个分片中应用数据库操作。ShardAccessStrategy无论何时针对分片执行查询都会查询到。我们已经提供了此界面的两个实现,我们希望这些实现足以满足大多数应用程序的需求。
SequentialShardAccessStrategy的行为与它的名字所暗示的一模一样:按顺序针对各个分片执行查询。根据您执行的查询类型,您可能希望避免使用此实现,因为它每次按相同顺序针对分片执行查询。如果您执行大量按行限制且无序的查询,这可以导致分片利用率较差(列表中靠前的分片将不断被重击,而列表中靠后的分片将闲置,动动它的分片拇指)。如果您对此担心,则应考虑使用LoadBalancedSequentialShardAccessStrategy。此实现会在每次调用时接收您分片的轮换视图,从而平均分配查询负载。
ParallelShardAccessStrategy的行为也与它的名字所暗示的一模一样:并行针对各个分片执行查询。当您使用此实现时,您需要提供一个java.util.concurrent.ThreadPoolExecutor,它适用于您应用程序的性能和吞吐量需求。这是一个简单的示例
ThreadFactory factory = new ThreadFactory() { public Thread newThread(Runnable r) { Thread t = Executors.defaultThreadFactory().newThread(r); t.setDaemon(true); return t; } }; ThreadPoolExecutor exec = new ThreadPoolExecutor( 10, 50, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), factory); return new ParallelShardAccessStrategy(exec);
请注意,这些仅仅是示例值 - 适当的线程池配置超出了本文档的范围。
Hibernate 分片使用ShardSelectionStrategy来确定在新对象上创建哪个分片。完全由您决定此界面的实现看起来是什么样,但我们提供了一个循环实现,以便您入门 (RoundRobinShardSelectionStrategy)。我们希望许多应用程序将要实现基于属性的分片,因此对于存储天气报告的示例应用程序,我们按报告的来源大陆对报告进行分片
public class WeatherReportShardSelectionStrategy implements ShardSelectionStrategy { public ShardId selectShardIdForNewObject(Object obj) { if(obj instanceof WeatherReport) { return ((WeatherReport)obj).getContinent().getShardId(); } throw new IllegalArgumentException(); } }
重要的是要注明,如果通过 Hibernate 的级联功能保存多级对象图,ShardSelectionStrategy仅在保存顶级对象时咨询。所有子对象都会自动保存在同一分片中,因此父对象。
如果阻止开发人员在对象层次结构中一个以上的级别创建新对象,则ShardSelectionStrategy更容易实施。通过使ShardSelectionStrategy实施识别模型中的顶级对象,并在遇到不在此集合中的对象时抛出异常,可以实现此目的。如果您不希望施加此限制,那就这样做,但请记住,如果您正在执行基于属性的分片选择,则用于做出决策的属性需要在传递到 session.save() 的每个对象中可用。
Hibernate 分片使用ShardResolutionStrategy确定具有给定 ID 的对象可能驻留在其上的分片组。让我们回到我们的天气报告应用程序,假设每个大陆都有一个与之关联的 ID 范围。每当我们向 WeatherReport 分配 ID 时,我们选择一个属于 WeatherReport 所属大陆的合法范围的 ID。
我们的ShardResolutionStrategy可以通过查看 ID,轻松使用此信息来识别 WeatherReport 驻留在哪个分片
public class WeatherReportShardResolutionStrategy extends AllShardsShardResolutionStrategy { public WeatherReportShardResolutionStrategy(List<ShardId> shardIds) { super(shardIds); } public List<ShardId> selectShardIdsFromShardResolutionStrategyData( ShardResolutionStrategyData srsd) { if(srsd.getEntityName().equals(WeatherReport.class.getName())) { return Continent.getContinentByReportId(srsd.getId()).getShardId(); } return super.selectShardIdsFromShardResolutionStrategyData(srsd); } }
值得指出的是,虽然我们 (还没有) 实现将实体名称/ID 映射到分片的缓存,ShardResolutionStrategy是插入此类缓存的绝佳位置。
分片解析与 ID 生成紧密相关。如果您为您的类选择了一个在对象的 ID 中对分片 ID 进行编码的 ID 生成器,则您的ShardResolutionStrategy甚至永远不会被调用。如果您计划仅使用在对象的 ID 中对分片 ID 进行编码的 ID 生成器,则应该使用AllShardsShardResolutionStrategy作为您的ShardResolutionStrategy.
Hibernate Sharding 支持任何 ID 生成策略;唯一的要求是在所有分片上,对象 ID 必须是唯一的。有一些简单的 ID 生成策略支持此要求
Native ID generation - 使用 Hibernate 的nativeID 生成策略,并配置您的数据库,以便 ID 绝不会冲突。例如,如果您使用identityID 生成,有 5 个数据库,将均匀地分布数据,而且预计永远不会拥有超过 1 百万的记录,可以将数据库 0 配置为从 0 开始返回 ID,将数据库 1 配置为从 200000 开始返回 ID,将数据库 2 配置为从 400000 开始返回 ID,依此类推。只要对数据做出正确的假设,对象的 ID 就永远不会发生冲突。
应用程序级 UUID 生成 - 根据定义,不必担心 ID 冲突,但确实需要愿意处理对象不太好用的主键。
Hibernate Shards 提供了一个简单分片感知 UUID 生成器的实现 -ShardedUUIDGenerator.
分布式 hilo 生成 - 想法是在仅一个分片上拥有一个 hilo 表,确保由 hi/lo 算法生成的标识符在所有分片中都是唯一的。这种方法的两个主要缺点是,对 hilo 表的访问可能会成为 ID 生成中的瓶颈,并且在单个数据库上存储 hilo 表会创建系统中的单一故障点。
Hibernate Shards 提供了一个分布式 hilo 生成算法的实现 -ShardedTableHiLoGenerator. 这种实现基于org.hibernate.id.TableHiLoGenerator,因此,有关实现依赖的数据库表的预期结构的信息,请参阅此类的文档。
ID 生成也与分片解析紧密相关。分片解析的目标是找到对象驻留的分片,并且已知对象的 ID。有两种方法可实现此目标
使用ShardResolutionStrategy,如上所述
在 ID 生成期间将分片 ID 编码到对象 ID 中,并在分片解析期间检索分片 ID
将分片 ID 编码到对象 ID 中的主要优点是,它使 Hibernate Shards 能够从对象 ID 中更快速地解析分片,而无需数据库查找、缓存查找等。Hibernate Shards 不要求使用任何特定算法对分片 ID 进行编码/解码 - 所要做的就是使用实现ShardEncodingIdentifierGenerator接口的 ID 生成器。在 Hibernate Shards 附带的两个 ID 生成器中,ShardedUUIDGenerator实现了此接口。