- 浏览: 103810 次
- 性别:
- 来自: 上海
博客专栏
-
Hyperic插件开发不完...
浏览量:11273
文章分类
- 全部博客 (53)
- Agile: RUP Scrum and XP (0)
- Design and Analysis Pattern (0)
- J2SE and JVM (6)
- Java Persistence, JPA, JDO; Transaction, JTS, JTA and ORM (4)
- JDBC JNDI JMS RMI EJB JavaMail and Other J2EE Tech (5)
- JSF Facelets and Ajax (0)
- JSP, Servlet and JSTL (0)
- Life (3)
- Oracle, Mysql, RDBMS (2)
- Other Java and J2EE frameworks (4)
- Ruby On Rails (0)
- Security (0)
- SOA, SCA, JBI, BPEL and OSGI (0)
- Spring (2)
- Test (1)
- UML, OOAD (0)
- Lucene, Infomation Retriever (1)
- NOSQL (5)
- crawl, Index and Search (0)
- Mysql (2)
- Architecture (2)
- nginx,squid,fastcgi and so on (1)
- JS, HTML, CSS, Web前端 (1)
- Flex (0)
- web (7)
- monitor (5)
- statsitic (0)
- recommendation (0)
- crawl (3)
- Index and Search (4)
- Oracle (1)
- RDBMS (1)
- Hadoop/HBase/Hive/MR (2)
- android (1)
- ios (0)
- 防辐射的 (0)
最新评论
-
u011506498:
楼主,能否求源码,924393541@qq.com,多谢!
Java爬虫框架(三)--模块设计之二 -
yutiannanjingjiangsu:
leftpop的返回值问题,在jedis2.6中似乎已解决(l ...
Spring-data-redis使用心得 -
xpf123fly:
楼主,能否求源码,593829792@qq.com,多谢!
Java爬虫框架(三)--模块设计之二 -
wenlinguo:
写得不是很好理解
Hyperic插件开发不完全指南(二)--插件进阶 -
406657836:
java 在server模式下对while进行了优化。把判断提 ...
Java进程CPU100%的问题
在分布式事务中实现基于Oracle PLSQL UL LOCK的悲观离线锁
背景
应用项目组每个小时会定时的run一个存储过程进行结算,每次执行的时间也许会超过一个小时,而且需要绝对保证存储过程的串行执行。因为使用内存锁不能绝对保证两个存储过程的串行执行,因为应用服务器down掉重启后可能会出现并发执行的情况,因为先前的存储过程还在db中运行。我们是使用LTS,对quartz进行了封装来做任务调度的。我们决定锁的管理操作由framework来实现。原因是:
l 锁管理器可以做成通用的模块
l 申请锁,释放锁是比较危险的操作,担心业务开发人员由于遗忘导致死锁或者并发问题
l 可以很好的集成到我们现有的framework中,方便地开放给业务开发人员使用
注意:我们极其不推荐使用悲观离线锁,如果冲突出现的概率比较少,可以用其他方法比如乐观离线锁,DB Constraint再通过补偿操作能解决的问题,请不要使用悲观离线锁。
原理
PLSQL UL LOCK是oracle提供出来给开发人员使用的锁资源,功能和DML锁是类似的,当然我们可以通过DML锁来完成并发控制,select…for update或者自己维护一张锁表,考虑到实现代价,我们打算使用PLSQL UL LOCK。而且Oracle保证了session释放时,UL lock都会被释放。
但是用它时,需要注意到它的DBMS_LOCK.Unique函数它每次都会commit数据。如果是在分布式事务当中,会抛出事务已提交的异常。因为我们使用的是XA resource并且transaction level是global的,也就是JTA。为了使得锁的申请和释放不影响分布式业务事务,或者我们是使用非xa的resource和local的transaction来完成锁操作,或者也可以暂停已有事务,等锁操作完成后resume暂停的分布式事务。考虑到重用已有的xa resource我们打算使用后一种方法,其实这种方法我们也会经常使用,暂停分布式事务做DDL操作,再释放事务。
实现方法:
l 封装DBMS_LOCK包中的几个存储过程为我们所用
l Java端提供一个基于PLSQL UL锁的管理器
l Java端定义好申请锁,业务操作,释放锁的使用流程,作为一个模板
DB存储过程:
对DBMS_LOCK做了简单的封装,避免直接调用DBMS_LOCK。这样做的好处是:
l 和Oracle解耦,如果其他数据库可以提供类似的功能,我们也可以用同名的存储过程实现
l 方便以后对存储过程重构,升级
l 我们需要对DBMS_LOCK进行简单的封装,因为DBMS_LOCK.Unique获取lockhandle oracle中锁的唯一标识,输入是lockname,逻辑名,输出是锁的唯一标识,对java端应该是透明的,java端应该只关心锁的逻辑名。
create or replace package body frm_lts_processor_lock_pkg is |
锁管理器:
其实应用项目组有多个这样的存储过程,而这些存储过程之间的串行执行可以有多个business key来决定的,比如job order number,delivery order等。所以我们需要给他们提供多锁管理机制。我们会对这多个锁进行排序,以避免死锁,并强烈推荐应用项目设置超时时间。这些business key是由String对象构成的,为了防止大量的业务操作被锁在null或者空string这样没有意义的business key上面,我们对application提供的锁集合还需要进行过滤。
原理还是很简单的,就是在本地事务中调用db端的申请锁,释放锁的存储过程,然后对返回的结果进行一系列处理。
在使用多锁机制的时候要保证,如果只申请到了部分锁,在申请其中另外一个锁时发生了错误或者超时,要能够安全地将已申请的锁释放掉,所以多锁申请需要记录已申请到的锁,并且记录发生的错误,区分timeout和异常。Timeout返回false,如果出现异常记录下来,最后抛出。释放多锁时,不能被中断,记录释放每个锁后的结果,最后判定如果其中一些锁释放时发生了错误,抛出。
handleLock定义暂停jta事务,执行锁操作,释放jta事务流程
private Object handleLock(Connection connection, LocalTransactionCallback localTransactionCallback) throws LockException { TransactionManager tm = null; Transaction currentTx = null; Object result = null; try { Context initialContext = new InitialContext(); UserTransaction userTrx = (javax.transaction.UserTransaction) initialContext .lookup("java:comp/UserTransaction"); if (!(userTrx.getStatus() == Status.STATUS_NO_TRANSACTION)) { tm = TransactionUtils.getTransactionManager(userTrx); if (tm != null) { currentTx = tm.suspend(); } } result = localTransactionCallback .executeInLocalTransaction(connection); if (null != currentTx) { tm.resume(currentTx); } } catch (NamingException e) { } catch (SystemException e) { } catch (InvalidTransactionException e) { } catch (IllegalStateException e) { } return result; }
多锁申请操作是上面流程的一个回调
private class ObtainMutipleLocksLocalTransactionCallback implements LocalTransactionCallback { private Set<String> lockNames; private int waitTime; ObtainMutipleLocksLocalTransactionCallback(Set<String> lockNames, int waitTime) { this.lockNames = lockNames; this.waitTime = waitTime; } public Object executeInLocalTransaction(Connection conn) { CallableStatement lockAcquireStmt = null; Set<String> obtainedLockNames = new HashSet<String>(); boolean timeOut = false; String timeOutLockName = null; Exception mifLockException = null; try { lockAcquireStmt = conn.prepareCall(OBTAIN_LOCK_PROC_CALL); for (String lockName : lockNames) { lockAcquireStmt.setString(1, lockName); lockAcquireStmt.setInt(2, LCOK_EXPIRE_TIME); lockAcquireStmt.setInt(3, waitTime); lockAcquireStmt.registerOutParameter(4, java.sql.Types.INTEGER); lockAcquireStmt.registerOutParameter(5, java.sql.Types.VARCHAR); lockAcquireStmt.executeUpdate(); int lockacquireResult = lockAcquireStmt.getInt(4); if (lockacquireResult == ULLockResultType.SUCCESSFUL) obtainedLockNames.add(lockName); } else if (lockacquireResult == ULLockResultType.TIMEOUT) { timeOut = true; timeOutLockName = lockName; break; } else if (lockacquireResult != ULLockResultType.ALREADY_OWNED) { String lockResultDesc = ULLockResultType .getAcquireTypeDesc(lockacquireResult); LockException lockException = new LockException( "Obtain lock " + lockName + " fails, the reason is " + lockResultDesc + " ."); lockException.setLockName(lockName); lockException.setLockHandlingResult(lockResultDesc); throw lockException; } else { } } } catch (Exception ex) { mifLockException = ex; } finally { if (null != lockAcquireStmt) { try { lockAcquireStmt.close(); } catch (SQLException e) { // swallow } } } boolean success = true; if (timeOut || mifLockException != null) { success = false; } return new ObtainMultipleLocksResult(success, obtainedLockNames, timeOut, timeOutLockName, mifLockException); } }
多锁释放操作也是事务暂停流程的一个回调
private class ReleaseMultipleLocksLocalTransactionCallback implements LocalTransactionCallback { private Set<String> lockNames; ReleaseMultipleLocksLocalTransactionCallback(Set<String> lockNames) { this.lockNames = lockNames; } public Object executeInLocalTransaction(Connection conn) { CallableStatement lockReleaseStmt = null; Map<String, Exception> mifLockErrors = new HashMap<String, Exception>(); Set<String> releasedLocks = new HashSet<String>(); try { try { lockReleaseStmt = conn.prepareCall(RELEASE_LOCK_PROC_CALL); } catch (Exception ex) { for (String lockName : lockNames) { mifLockErrors.put(lockName, ex); } return new ReleaseMutipleLocksResult(false, releasedLocks, mifLockErrors); } for (String lockName : lockNames) { try { lockReleaseStmt.registerOutParameter(1, java.sql.Types.INTEGER); lockReleaseStmt.setString(2, lockName); lockReleaseStmt.executeUpdate(); int lockReleaseResult = lockReleaseStmt.getInt(1); if (lockReleaseResult == ULLockResultType.SUCCESSFUL) { releasedLocks.add(lockName); } else { String lockResultDesc = ULLockResultType .getReleaseTypeDesc(lockReleaseResult); LockException lockException = new LockException( "Release lock " + lockName + " fails, the reason is " + lockResultDesc + " ."); lockException.setLockName(lockName); lockException.setLockHandlingResult(lockResultDesc); mifLockErrors.put(lockName, lockException); } } catch (Exception ex) { mifLockErrors.put(lockName, ex); } } } finally { if (null != lockReleaseStmt) { try { lockReleaseStmt.close(); } catch (SQLException e) { } } } boolean success = releasedLocks.size() == this.lockNames.size(); return new ReleaseMutipleLocksResult(success, releasedLocks, mifLockErrors); } }
使用模板:注意锁的释放要写在finally语句块里面,保证锁的释放。
定义好模板,防止Application用户直接调用锁管理器或者滥用锁,忘记释放锁。我们决定定义一个模板,做到锁的申请和释放对application用户来说是透明的,把它做成了隐含锁。
public void execute(JobExecutionContext context) throws JobExecutionException { Map jobDataMap = context .getJobDetail().getJobDataMap(); Collection<String> lockKeys = (Collection<String>) jobDataMap.get(LOCK_NAME_KEY); Integer waitTimeInteger = (Integer) jobDataMap .get(LOCK_WAIT_TIME_SECONDS_KEY); int waitTime = MAX_WAITTIME; if (waitTimeInteger != null) { waitTime = waitTimeInteger.intValue(); } Set<String> uniqueLockKeys = new HashSet<String>(lockKeys); // filter empty keys Iterator<String> keyIterator = uniqueLockKeys.iterator(); while (keyIterator.hasNext()) { String key = keyIterator.next(); if (StringUtils.isEmptyNoOffset(key)) { keyIterator.remove(); } } if (CollectionUtils.isNotEmptyCollection(uniqueLockKeys)) { Set<String> obtainedLockNames = null; Connection connection = null; try { connection = DataSource.getConnection(); ObtainMultipleLocksResult result = LOCK_MANAGER.obtainLock( connection, uniqueLockKeys, waitTime); obtainedLockNames = result.getObtainedLockNames(); if (!result.isSuccess()) { if (result.isTimeout()) { //do log return; } else { JobExecutionException jobException = new JobExecutionException( "Obtain locks failed! " + result.getMifLockException() .getMessage(), result .getMifLockException()); throw jobException; } } this. executeInLock (context); } catch (Throwable e) { throw new JobExecutionException( "Get db connection failed!" + e.getMessage(), e); } finally { if (null != connection) { this.releaseLocks(connection, obtainedLockNames); try { connection.close(); } catch (SQLException e) { throw new JobExecutionException( "close db connection failed!" + e.getMessage(), e); } } } } else { this.executeInLock(context); } }
executeInLock由application的子类继承实现
缓存
l 缓存悲观离线锁
l 缓存lockhandle
因为使用的是悲观离线锁,每次申请锁都要跑一趟db,但如果当前线程已经是lock的所有者就不需要白跑一趟了。可以用ThreadLocal把当前线程已经拥有的锁缓存起来,释放锁时对应的需要清除缓存。
在申请锁时,需要获得UL Lock时的lockhandle,释放锁时也需要提供锁的lockhandle,我们需要将它缓存起来,主要是因为DBMS_LOCK.Unique每次都会commit,会影响性能,这样每次释放锁时就可以直接使用lockhandle了。有两种方法对lockhandle进行缓存,缓存在java端作为实例变量,缓存在plsql包的全局变量中。缓存在java端需要注意的是,lock manager不能作为单例或者享元来使用,否则lock handle的缓存在多jvm之间也存在着并发控制和同步的问题。
源代码: 见附件
参考:
- ULLock-sources.rar (9.6 KB)
- 下载次数: 9
- lock.rar (785 Bytes)
- 下载次数: 8
相关推荐
PLSQL Developer官方下载离线文件,可直接使用 载使用教程 https://blog.csdn.net/liu_yulong/article/details/109624904,windows下 PlSQL developer连接远程oracle步骤
PLSQL Developer官方下载离线文件,可直接使用, Instant Client(轻量级的客户端),作为本地Oracle环境 。已经配置好了,可以直接下载使用教程 https://blog.csdn.net/liu_yulong/article/details/109624904,...
在实际应用中,PLSQL通常用于企业级数据库管理系统的后端开发,与前端应用程序交互,处理大量数据操作和事务处理。通过《Oracle PLSQL 从入门到精通》这本书,你将能够系统地学习和实践这些知识,逐步成为一名熟练的...
PLSql 很好的一个控制数据库工具(ORACLE)
PLSQL Developer是一款强大的Oracle数据库开发工具,主要用于编写、调试、执行和管理PL/SQL程序。在Windows 64位系统上使用此离线安装包,用户可以享受到一系列便捷的功能,以便于更高效地进行数据库管理和开发工作...
《Oracle PLSQL详解 宝典》是一本专为IT专业人士准备的深度学习Oracle数据库中的PL/SQL编程语言的指南。本书旨在帮助读者深入理解PL/SQL的各个方面,从而能够熟练地利用这一强大的工具来解决实际工作中遇到的问题。...
Oracle PL/SQL是一种在Oracle数据库环境中进行数据库编程的语言,它结合了SQL的查询能力与过程化编程语言的特点,使得开发者可以编写复杂的业务逻辑和数据库操作。在这个“Oracle PL/SQL”主题中,我们将深入探讨这...
Oracle PLSQL,全称为Oracle Procedural Language/Structured Query Language,是Oracle数据库系统中的一个编程组件,用于扩展SQL的功能,实现更复杂的业务逻辑。它结合了SQL的查询能力与过程式编程语言的特点,使得...
Oracle PLSQL编程是数据库开发领域中的重要组成部分,尤其在企业级应用系统中广泛使用。第四版的《Oracle PLSQL编程》旨在深入解析Oracle数据库的PL/SQL编程语言,帮助开发者提升技能,掌握高效、稳定和安全的数据库...
本书的方法完全反映了作者在哥伦比亚大学给专业人员讲授PL/SQL的广受好评的经验。数据库开发的新手和DBA可以通过学习本书快速获得成效。有经验的PL/SQL程序员会发现本书是很好的Oracle12c的解决方案参考。
标题中的“不安装oracle使用plsql”意味着要在没有Oracle数据库客户端的情况下使用PL/SQL Developer这一工具。这通常通过Oracle的Instant Client实现,该组件提供了一种轻量级的解决方案,允许用户连接到Oracle...
oracle plsql 代码入门到精通实例演示讲解,plsql编程入门教程
ORACLE PLSQL实例精解(第4版).part2
在Linux环境下离线安装Oracle数据库19c是一个涉及多个步骤的过程,需要准备相应的安装文件,并且对系统进行预安装配置。以下是对整个流程的详细解释: 1. **文件准备** - `compat-libstdc++-33-3.2.3-72.el7.x86_...
在Oracle PL/SQL中,你可以编写存储过程、函数、触发器、游标等,以实现复杂的业务逻辑和数据处理。以下是对Oracle PL/SQL及其相关知识点的详细解释: 1. **PL/SQL块**:PL/SQL程序由一个或多个块组成,每个块都有...
《Oracle PLSQL Programming 6th Edition》是一本深入讲解Oracle数据库中的PL/SQL编程语言的权威著作。PL/SQL是Oracle数据库系统中的过程式语言,它结合了SQL的查询功能和传统的编程语言特性,用于创建复杂的数据库...
### ORACLE PL/SQL 实例精解(第4版)知识点概述 #### 一、PL/SQL基础 **1.1 PL/SQL简介** - **定义**:PL/SQL(Procedural Language for SQL)是一种过程化语言,专门用于增强Oracle数据库的功能。它将SQL命令与...
Oracle连接工具plsql11x64位Oracle连接工具plsql11x64位Oracle连接工具plsql11x64位Oracle连接工具plsql11x64位Oracle连接工具plsql11x64位Oracle连接工具plsql11x64位Oracle连接工具plsql11x64位Oracle连接工具...