Activiti中具有单独数据库模式的多租户
我們過去聽到的一項功能請求是以多租戶方式運行Activiti引擎,使租戶的數(shù)據(jù)與其他租戶的數(shù)據(jù)隔離。 當(dāng)然,在某些云/ SaaS環(huán)境中,這是必須的。
幾個月前,波恩大學(xué)的學(xué)生拉斐爾·吉倫(Raphael Gielen)與我接觸,他正在撰寫有關(guān)Activiti中多租戶的碩士論文。 幾周前,我們在一個共同工作的咖啡館聚會,反彈想法,并為租戶一起破解了具有數(shù)據(jù)庫模式隔離的第一個原型。 很有趣 :-)。
無論如何,我們一直在完善和完善該代碼,并將其提交給Activiti代碼庫。 讓我們在下面的前兩個部分中了解使用Activiti進行多租戶的現(xiàn)有方法。 在第三部分中,我們將深入研究新的多租戶多模式功能,其中包含一些實際工作的代碼示例!
共享數(shù)據(jù)庫多租戶
Activiti已經(jīng)有多租戶功能了一段時間(自版本5.15起)。 所采用的方法是共享數(shù)據(jù)庫 :擁有一個(或多個)Activiti引擎,并且它們都進入同一個數(shù)據(jù)庫。 數(shù)據(jù)庫表中的每個條目都有一個租戶標識符 ,最好將其理解為該數(shù)據(jù)的一種標記。 然后,Activiti引擎和API會讀取并使用該租戶標識符在租戶的上下文中執(zhí)行其各種操作。
例如,如下圖所示,兩個不同的租戶可以具有使用相同密鑰的流程定義。 引擎和API確保沒有數(shù)據(jù)混合。
這種方法的好處是部署簡單,因為與設(shè)置“常規(guī)” Activiti引擎沒有區(qū)別。 缺點是您必須記住使用正確的API調(diào)用(即那些考慮了租戶標識符的調(diào)用)。 而且,它與任何具有共享資源的系統(tǒng)都存在相同的問題:租戶之間總是存在對資源的競爭。 在大多數(shù)用例中,這很好,但是有些用例不能以這種方式完成,例如為某些租戶提供更多或更少的系統(tǒng)資源。
多引擎多租戶
自Activiti的第一個版本以來,另一種可能的方法是為每個租戶簡單地擁有一個引擎實例:
在此設(shè)置中,每個租戶可以具有不同的資源配置,甚至可以在不同的物理服務(wù)器上運行。 當(dāng)然,此圖中的每個引擎可以是多個引擎,以提高性能/故障轉(zhuǎn)移/等等。 現(xiàn)在的好處是,資源是為租戶量身定制的。 缺點是設(shè)置比較復(fù)雜(多個數(shù)據(jù)庫架構(gòu),每個租戶都有不同的配置文件,等等)。 每個引擎實例都將占用內(nèi)存(但是Activiti會占用很少的內(nèi)存)。 另外,您無需編寫一些路由組件 ,就可以以某種方式知道當(dāng)前租戶上下文并路由到正確的引擎。
多架構(gòu)多租戶
Activiti多租戶故事的最新添加是在兩周前添加的(這是commit ),同時是版本5和6的添加。這里,每個租戶都有一個數(shù)據(jù)庫(模式),但只有一個引擎實例。 再次,在實踐中,可能有多個用于性能/故障轉(zhuǎn)移/實例的實例,但是概念是相同的:
好處顯而易見:只有一個引擎實例可以管理和配置,而且API與非多租戶引擎完全相同。 但最重要的是,租戶的數(shù)據(jù)與其他租戶的數(shù)據(jù)完全分開。 缺點(類似于多引擎多租戶方法)是有人需要管理和配置不同的數(shù)據(jù)庫。 但是復(fù)雜的引擎管理已不復(fù)存在。
我上面鏈接到的提交還包含一個單元測試 ,該測試顯示了多模式多租戶引擎的工作方式。
構(gòu)建流程引擎很容易,因為有一個MultiSchemaMultiTenantProcessEngineConfiguration可抽象出大多數(shù)細節(jié):
config = new MultiSchemaMultiTenantProcessEngineConfiguration(tenantInfoHolder);config.setDatabaseType(MultiSchemaMultiTenantProcessEngineConfiguration.DATABASE_TYPE_H2); config.setDatabaseSchemaUpdate(MultiSchemaMultiTenantProcessEngineConfiguration.DB_SCHEMA_UPDATE_DROP_CREATE);config.registerTenant("alfresco", createDataSource("jdbc:h2:mem:activiti-mt-alfresco;DB_CLOSE_DELAY=1000", "sa", "")); config.registerTenant("acme", createDataSource("jdbc:h2:mem:activiti-mt-acme;DB_CLOSE_DELAY=1000", "sa", "")); config.registerTenant("starkindustries", createDataSource("jdbc:h2:mem:activiti-mt-stark;DB_CLOSE_DELAY=1000", "sa", ""));processEngine = config.buildProcessEngine();這看起來與啟動常規(guī)Activiti流程引擎實例非常相似。 主要區(qū)別在于我們是使用引擎注冊租戶。 每個租戶都需要添加其唯一的租戶標識符和數(shù)據(jù)源實現(xiàn)。 當(dāng)然,數(shù)據(jù)源實現(xiàn)需要有自己的連接池。 這意味著您可以根據(jù)使用情況有效地為某些租戶提供不同的連接池配置。 Activiti引擎將確保已創(chuàng)建或驗證每個數(shù)據(jù)庫架構(gòu)都是正確的。
魔術(shù)以使這一切工作是TenantAwareDataSource 。 這是一個javax.sql.DataSource實現(xiàn),它根據(jù)當(dāng)前租戶標識符委托給正確的數(shù)據(jù)源。 此類的想法在很大程度上受到Spring的AbstractRoutingDataSource (站在其他開源項目的肩膀上!)的影響。
通過從TenantInfoHolder實例獲取當(dāng)前的租戶標識符 ,可以路由到正確的數(shù)據(jù)源。 正如您在上面的代碼片段中看到的那樣,在構(gòu)造MultiSchemaMultiTenantProcessEngineConfiguration時,這也是必需的參數(shù)。 TenantInfoHolder是您需要實現(xiàn)的接口,具體取決于您環(huán)境中用戶和租戶的管理方式。 通常,您將使用ThreadLocal來存儲由某些安全過濾器填充的當(dāng)前用戶/租戶信息(類似于Spring Security)。 該類有效地充當(dāng)下圖中的“路由組件”:
在單元測試示例中,我們確實使用ThreadLocal來存儲當(dāng)前的租戶標識符 ,并用一些演示數(shù)據(jù)填充它:
private void setupTenantInfoHolder() {DummyTenantInfoHolder tenantInfoHolder = new DummyTenantInfoHolder();tenantInfoHolder.addTenant("alfresco");tenantInfoHolder.addUser("alfresco", "joram");tenantInfoHolder.addUser("alfresco", "tijs");tenantInfoHolder.addUser("alfresco", "paul");tenantInfoHolder.addUser("alfresco", "yvo");tenantInfoHolder.addTenant("acme");tenantInfoHolder.addUser("acme", "raphael");tenantInfoHolder.addUser("acme", "john");tenantInfoHolder.addTenant("starkindustries");tenantInfoHolder.addUser("starkindustries", "tony");this.tenantInfoHolder = tenantInfoHolder;}現(xiàn)在,我們開始一些流程實例,同時還要切換當(dāng)前的租戶標識符。 在實踐中,您必須想象多個線程隨請求一起進入,并且它們將根據(jù)登錄的用戶設(shè)置當(dāng)前的租戶標識符:
startProcessInstances("joram"); startProcessInstances("joram"); startProcessInstances("raphael"); completeTasks("raphael");上面的startProcessInstances方法將使用標準Activiti API設(shè)置當(dāng)前用戶和租戶標識符,并啟動幾個流程實例, 就好像根本沒有多租戶一樣 ( completeTasks方法類似地完成了一些任務(wù))。
同樣很酷的是, 您可以通過使用與構(gòu)建流程引擎時相同的方法來動態(tài)注冊(和刪除)新的租戶 。 Activiti引擎將確保已創(chuàng)建或驗證數(shù)據(jù)庫架構(gòu)。
config.registerTenant("dailyplanet", createDataSource("jdbc:h2:mem:activiti-mt-daily;DB_CLOSE_DELAY=1000", "sa", ""));這是一部電影,顯示正在運行的單元測試以及有效隔離的數(shù)據(jù):
多租戶執(zhí)行器
最后一個難題是工作執(zhí)行者。 常規(guī)Activiti API調(diào)用“借用”當(dāng)前線程以執(zhí)行其操作,因此可以使用之前在該線程上設(shè)置的任何用戶/租戶上下文。
但是,作業(yè)執(zhí)行程序使用后臺線程池運行,并且沒有此類上下文。 由于Activiti中的AsyncExecutor是一個接口,因此實現(xiàn)多模式多租戶作業(yè)執(zhí)行器并不難。 當(dāng)前,我們添加了兩個實現(xiàn)。 第一個實現(xiàn)稱為SharedExecutorServiceAsyncExecutor :
config.setAsyncExecutorEnabled(true); config.setAsyncExecutorActivate(true); config.setAsyncExecutor(new SharedExecutorServiceAsyncExecutor(tenantInfoHolder));此實現(xiàn)(顧名思義)對所有租戶使用一個線程池。 每個租戶確實有其自己的作業(yè)獲取線程,但是一旦獲取了作業(yè),便將其放到共享線程池中。 該系統(tǒng)的好處是Activiti使用的線程數(shù)受到限制。
第二種實現(xiàn)稱為ExecutorPerTenantAsyncExecutor :
config.setAsyncExecutorEnabled(true); config.setAsyncExecutorActivate(true); config.setAsyncExecutor(new ExecutorPerTenantAsyncExecutor(tenantInfoHolder));顧名思義,該類充當(dāng)“代理” AsyncExecutor。 對于每個注冊的租戶,將啟動完整的默認AsyncExecutor。 每個都有自己的獲取線程和執(zhí)行線程池。 “代理”僅委托給正確的AsyncExecutor實例。 這種方法的好處是,每個租戶都可以根據(jù)租戶的需求進行細粒度的作業(yè)執(zhí)行者配置。
結(jié)論
一如既往,歡迎所有反饋。 放手多方案多租戶,讓我們知道您的想法以及將來可以改進的地方!
翻譯自: https://www.javacodegeeks.com/2015/10/multi-tenancy-with-separate-database-schemas-in-activiti.html
總結(jié)
以上是生活随笔為你收集整理的Activiti中具有单独数据库模式的多租户的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 掼蛋安卓版下载(掼蛋安卓版)
- 下一篇: 常见的误解:这会创建多少个对象?