前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分布式锁在JPA ID生成器中的应用

分布式锁在JPA ID生成器中的应用

作者头像
Bruce Li
发布2019-08-28 15:14:29
9000
发布2019-08-28 15:14:29
举报

在现实生活中,很多场景都需要ID生成器,比如说电商平台的订单号生成、银行的叫号系统等。针对不用的业务需求,ID生成策略也不一样,比如电商平台的订单号可以由时间序列组成,银行的叫号系统则是自然数自增序列。对于自增序列的ID生成器,在多并发环境下,为保证严格的自增,常常可以通过锁来保证。

设想一下,如果我们想在应用层面自己实现一个自增序列的ID生成器(其实本质上我们需要实现的是一个getNextValue方法),怎么做?对很多人来说,可能首先想到的是直接用i++这样语法层面的语句,但是必须要对方法加锁才行,因为i++不是一个原子操作。还有另外一个办法,就是利用java的AtomicInteger类,AtomicInteger的实现不是基于锁,而是基于CAS(Compare and Swap),在某些场景下,效率要比加锁的方式高,参考(漫画:什么是 CAS 机制?)。

上面介绍的语言层面的支持更多的是一些理论层面的东西,常常适用于单机系统,如果要应用到实际的软件系统中,还需要考虑很多其他方面,比如说自增序列的持久化、分布式系统中如何生成自增序列。

在分布式系统中,如何实现ID生成器,有很多办法,有兴趣的童鞋可以自行网上搜索。下面主要分析JPA的ID生成器是如何依赖于数据库的锁实现的。

其实很多分布式场景下的需求和功能,都还是依赖于数据库的基本功能来实现,之前写的一篇文章(liquibase和flyway中分布式锁实现的区别?)就介绍了在flyway中如何利用数据库的排他锁实现分布式锁。然而,大量依赖数据库也可能导致数据库成为一个单点性能瓶颈,这时候往往就需要考虑一些方案来减轻这个瓶颈,比如说分库分表(现在流行的微服务架构就是一个High-level的分库分表的实践)。

JPA的@GeneratedValue@TableGenerator两个Annotation可以直接用来生成自增序列,并且会把当前的序列存在数据库中,JPA现在流行的两个provider(eclipselink和hibernate)在实现上,有异曲同工之处,都是依赖的数据库的排他锁。

那么eclipselink是如何实现的呢?就像上面提到的,本质上就是实现了一个getNextValue方法,只是这里加的锁是数据的排他锁,而不是语言层面的锁,如下图所示。

这里数据库排他锁工作的基本原理是:在一个事务中,当update一条记录时,会在当前记录上加一个排他锁(或者整个表上),只有事务结束(commit或者rollback)之后,才会释放这个锁;这时其他阻塞的事务就继续执行。参考如下代码:

代码语言:javascript
复制
Connection c = null;
try {
    Class.forName("org.postgresql.Driver");
    c = DriverManager.getConnection("jdbc:postgresql://localhost:5432/postgres","postgres", "postgres");
} catch (Exception e) {
    e.printStackTrace();
    System.err.println(e.getClass().getName()+": "+e.getMessage());
    System.exit(0);
}
c.setAutoCommit(false);
String sql1 = "update sequence set seq_count = 35 where id_generation_category='t1'";
PreparedStatement preparedStatement1 = c.prepareStatement(sql1);
preparedStatement1.executeUpdate();
String sql2 = "select * from sequence where id_generation_category='t1'";
ResultSet rs = preparedStatement2.executeQuery();
while(rs.next()) {
    int seq_count = rs.getInt("seq_count");
    System.out.println("seq_count: " + seq_count);
}
try{
    c.commit();
}catch (SQLException e){
    c.rollback();
} finally {
    preparedStatement1.close();
    preparedStatement2.close();
    c.close();
}

Hibernate的实现类似,具体可以参考文章(https://dzone.com/articles/hibernate-identity-sequence),可以看到Hibernate采用的是select for update语句显示加排他锁的方式,和前面写的一篇文章(liquibase和flyway中分布式锁实现的区别?)flyway加锁的方式一样。

上面提到,实现自增序列也可以不用加锁,java语言层面提供的AtomicInteger类就是采用不加锁的方法,而是采用的CAS(Compare and Swap)。那么在分布式环境下,ID生成器是不是也可以采用CAS呢?这篇文章(浅谈CAS在分布式ID生成方案上的应用 | 架构师之路)就简单介绍了如何采用CAS实现分布式ID生成器。

延伸

关于各种锁概念的解释,推荐两篇文章:(Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等),(Java中的锁原理、锁优化、CAS、AQS详解!

References

  • https://vladmihalcea.com/why-you-should-never-use-the-table-identifier-generator-with-jpa-and-hibernate/
  • https://javarevisited.blogspot.com/2013/03/reentrantlock-example-in-java-synchronized-difference-vs-lock.html
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 天马行空布鲁斯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档