樂觀鎖
為什么需要采用樂觀鎖?
由于activiti一個周期的transaction時間可能比較長,且同一流程實例中存在任務并發執行等場景。設計者將update、insert、delete事務性的操作推遲至command結束時完成,這樣盡量降低鎖沖突的概率,由此產生基于mybatis上封裝的session cache來管理這些中間狀態的實體對象。但在充分競爭情況下鎖是不可避免的,進一步利用樂觀鎖機制能保證執行模型的一致性。因為往往如果鎖定相同數據記錄說明多個相互影響的command并發執行,安全的策略就是讓第一個命令成功,其他皆失敗。
PS:
悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其他事務,以及來自 外部系統的事務處理)修改持保守態度,其它事務會一直阻塞,直到這個事務結束。因此,在整個數據處理過程中,將數據處于鎖定 狀態。悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能 真正保證數據訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系 統不會修改數據)。
對悲觀鎖而言,樂觀鎖機制采取了更加寬松的加鎖機制,不會鎖住任何東西。大多是基于數據版本 ( Version )記錄機制實現,更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大于數據庫表當前版本號,則予以更新,否則認為是過期數據予以駁回。
死鎖
在一個command執行過程中存在開始獨立的新事務PROPAGATION_REQUIRES_NEW,例如IdGenerator,DecrementJobRetriesCmd。
1、IdGenerator:為什么不用數據庫序列生成器?因為此前所說我們的數據庫操作全部延遲到最后執行,故而無法使用數據庫的自增序列,而是采用外部序列來聯系各實體。
2、DecrementJobRetriesCmd:asynchronous job執行失敗,在TransactionState.ROLLED_BACK時執行job異常登記鉤子
在command中嵌入開啟新subcommand(該subcommand包含需獲得數據庫連接資源),然而在有限資源的情況下自身已經持有資源再試圖去獲取同類資源是相當危險的方案,由此引發的競爭往往會將你推入死鎖的深淵。正如下圖所示便是activiti在高并發場景下產生死鎖的原因。
exclusive jobs
問題拋出
并行execution中servicetask async=true,當他們各自被不同worker thread執行,當一個execution跑到join節點會判斷是否除己之外所有的income flow都已到達,如果是則走過join否則join等待。但是當多個flow同時到達join,因為此時他們各自對于對方是不可見的,都假定對方沒有到達,導致都認為要等待對方,從而導致join永遠不會執行。
前面所述樂觀鎖在并發情況下同時只會保證第一個提交的job成功,其他拋出ActivitiOptimisticLockingException失敗,從而在一定的時間后重試,但重試的次數是有限的(默認為3次),并行線路越多沖突重試的可能性也越大。且如果task service不在bpm transaction控制之下(比如POST外部系統接口),則業務不能正確回滾,被執行多次。
exclusive job 保證隸屬同一process instance的job是被順序執行,即在org.activiti.engine.impl.jobexecutor.AcquireJobsRunnable中將同一流程的job壓人同一批次。