Activiti中具有单独数据库模式的多租户

我们过去听到的一项功能请求是以多租户方式运行Activiti引擎,使租户的数据与其他租户的数据隔离。 当然,在某些云/ SaaS环境中,这是必须的。

几个月前,波恩大学的学生拉斐尔·吉伦(Raphael Gielen)与我接触,他正在撰写有关Activiti中多租户的硕士论文。 几周前,我们在一个共同工作的咖啡馆聚会,反弹想法,并为租户一起破解了具有数据库模式隔离的第一个原型。 很有趣 :-)。

无论如何,我们一直在完善和完善该代码,并将其提交给Activiti代码库。 让我们在下面的前两个部分中了解使用Activiti进行多租户的现有方法。 在第三部分中,我们将深入研究新的多租户多模式功能,其中包含一些实际工作的代码示例!

共享数据库多租户

Activiti已经有多租户功能了一段时间(自版本5.15起)。 所采用的方法是共享数据库 :拥有一个(或多个)Activiti引擎,并且它们都进入同一个数据库。 数据库表中的每个条目都有一个租户标识符 ,最好将其理解为该数据的一种标记。 然后,Activiti引擎和API会读取并使用该租户标识符在租户的上下文中执行其各种操作。

例如,如下图所示,两个不同的租户可以具有使用相同密钥的流程定义。 引擎和API确保没有数据混合。

截图-2015-10-06-12.57.00

这种方法的好处是部署简单,因为与设置“常规” Activiti引擎没有区别。 缺点是您必须记住使用正确的API调用(即那些考虑了租户标识符的调用)。 而且,它与任何具有共享资源的系统都存在相同的问题:租户之间总是存在对资源的竞争。 在大多数用例中,这很好,但是有些用例不能以这种方式完成,例如为某些租户提供更多或更少的系统资源。

多引擎多租户

自Activiti的第一个版本以来,另一种可能的方法是为每个租户简单地拥有一个引擎实例:

截图-2015-10-06-13.12.56

在此设置中,每个租户可以具有不同的资源配置,甚至可以在不同的物理服务器上运行。 当然,此图中的每个引擎可以是多个引擎,以提高性能/故障转移/等等。 现在的好处是,资源是为租户量身定制的。 缺点是设置比较复杂(多个数据库架构,每个租户都有不同的配置文件,等等)。 每个引擎实例都将占用内存(但是Activiti会占用很少的内存)。 另外,您无需编写一些路由组件 ,就可以以某种方式知道当前租户上下文并路由到正确的引擎。

截图-2015-10-06-13.18.36-300x206

多架构多租户

Activiti多租户故事的最新添加是在两周前添加的(这是commit ),同时是版本5和6的添加。这里,每个租户都有一个数据库(模式),但只有一个引擎实例。 再次,在实践中,可能有多个用于性能/故障转移/实例的实例,但是概念是相同的:

截图-2015-10-06-13.41.20

好处显而易见:只有一个引擎实例可以管理和配置,而且API与非多租户引擎完全相同。 但最重要的是,租户的数据与其他租户的数据完全分开。 缺点(类似于多引擎多租户方法)是有人需要管理和配置不同的数据库。 但是复杂的引擎管理已不复存在。

我上面链接到的提交还包含一个单元测试 ,该测试显示了多模式多租户引擎的工作方式。

构建流程引擎很容易,因为有一个MultiSchemaMultiTenantProcessEngineConfiguration可抽象出大多数细节:

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();

这看起来与启动常规Activiti流程引擎实例非常相似。 主要区别在于我们是使用引擎注册租户。 每个租户都需要添加其唯一的租户标识符和数据源实现。 当然,数据源实现需要有自己的连接池。 这意味着您可以根据使用情况有效地为某些租户提供不同的连接池配置。 Activiti引擎将确保已创建或验证每个数据库架构都是正确的。

魔术以使这一切工作是TenantAwareDataSource 。 这是一个javax.sql.DataSource实现,它根据当前租户标识符委托给正确的数据源。 此类的想法在很大程度上受到Spring的AbstractRoutingDataSource (站在其他开源项目的肩膀上!)的影响。

通过从TenantInfoHolder实例获取当前的租户标识符 ,可以路由到正确的数据源。 正如您在上面的代码片段中看到的那样,在构造MultiSchemaMultiTenantProcessEngineConfiguration时,这也是必需的参数。 TenantInfoHolder是您需要实现的接口,具体取决于您环境中用户和租户的管理方式。 通常,您将使用ThreadLocal来存储由某些安全过滤器填充的当前用户/租户信息(类似于Spring Security)。 该类有效地充当下图中的路由组件”:

截图-2015-10-06-13.53.131

在单元测试示例中,我们确实使用ThreadLocal来存储当前的租户标识符 ,并用一些演示数据填充它:

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;}

现在,我们开始一些流程实例,同时还要切换当前的租户标识符。 在实践中,您必须想象多个线程随请求一起进入,并且它们将根据登录的用户设置当前的租户标识符:

startProcessInstances("joram");
startProcessInstances("joram");
startProcessInstances("raphael");
completeTasks("raphael");

上面的startProcessInstances方法将使用标准Activiti API设置当前用户和租户标识符,并启动几个流程实例, 就好像根本没有多租户一样completeTasks方法类似地完成了一些任务)。

同样很酷的是, 您可以通过使用与构建流程引擎时相同的方法来动态注册(和删除)新的租户 。 Activiti引擎将确保已创建或验证数据库架构。

config.registerTenant("dailyplanet", createDataSource("jdbc:h2:mem:activiti-mt-daily;DB_CLOSE_DELAY=1000", "sa", ""));

这是一部电影,显示正在运行的单元测试以及有效隔离的数据:

多租户执行器

最后一个难题是工作执行者。 常规Activiti API调用“借用”当前线程以执行其操作,因此可以使用之前在该线程上设置的任何用户/租户上下文。

但是,作业执行程序使用后台线程池运行,并且没有此类上下文。 由于Activiti中的AsyncExecutor是一个接口,因此实现多模式多租户作业执行器并不难。 当前,我们添加了两个实现。 第一个实现称为SharedExecutorServiceAsyncExecutor

config.setAsyncExecutorEnabled(true);
config.setAsyncExecutorActivate(true);
config.setAsyncExecutor(new SharedExecutorServiceAsyncExecutor(tenantInfoHolder));

此实现(顾名思义)对所有租户使用一个线程池。 每个租户确实有其自己的作业获取线程,但是一旦获取了作业,便将其放到共享线程池中。 该系统的好处是Activiti使用的线程数受到限制。

第二种实现称为ExecutorPerTenantAsyncExecutor

config.setAsyncExecutorEnabled(true);
config.setAsyncExecutorActivate(true);
config.setAsyncExecutor(new ExecutorPerTenantAsyncExecutor(tenantInfoHolder));

顾名思义,该类充当“代理” AsyncExecutor。 对于每个注册的租户,将启动完整的默认AsyncExecutor。 每个都有自己的获取线程和执行线程池。 “代理”仅委托给正确的AsyncExecutor实例。 这种方法的好处是,每个租户都可以根据租户的需求进行细粒度的作业执行者配置。

结论

一如既往,欢迎所有反馈。 放手多方案多租户,让我们知道您的想法以及将来可以改进的地方!

翻译自: https://www.javacodegeeks.com/2015/10/multi-tenancy-with-separate-database-schemas-in-activiti.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/356614.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dfs手写栈模板

在竞赛中如果系统栈很小的话,过深的递归会让栈溢出,这个时候我们就要自己手写栈,将递归转化成手工栈。 方法其实也很简单。 基本思路上,我们就是用栈不断的pop,push。但是何时push,何时pop呢? 在《算法导论…

html5自动把某个层放在屏幕底部,告诉你一个将 footer 保持在底部的最好方法

当你在布局网页时,有可能会遇到类似下面的这种情况broken_layout.png导致这一问题的原因是页面内容太少,无法将内容区域撑开,从而在 footer 下面留下一大块空白。本文将介绍一种现代化的方法,�确保 footer 始终处于页面…

前后数据交互(ajax) -- 初始化页面表格

// 初始化员工信息列表 function loadpage ( pageNum ) {var keywords $("#keywords").val();$("#tbody").html("");if ( isNaN(pageNum) ) {pageNum 1;}$.$.ajax({type: GET,url: contextPath "/getaccountList/" pageNum "…

html的扇形代码导航,CSS3--利用transform和transition属性制作扇形导航

在前面,我们已经讲解了transform这个属性以及案例,那么本文会进一步结合transform和transition两个属性,并制作一个简单的案例进行说明。一、transition属性说明接下来简单分析一下transition这个属性的定义以及子属性。1)ansition-property …

UCI数据

http://archive.ics.uci.edu/ml/datasets.html?format&task&att&area&numAtt&numIns&type&sortinstDown&viewtable转载于:https://www.cnblogs.com/zangrunqiang/p/5999238.html

常见的误解:这会创建多少个对象?

总览 一个常见的问题是一段代码创建多少个对象或多少个字符串。 答案通常不是您的想法,也不是您真正需要知道的。 了解何时创建对象是很有用的,但是有很多其他因素通常要考虑的重要得多,这可能意味着应用程序总数不是您所想的。 字符串不是一…

cvs配电保护断路器_电工电器(三)-配电电器-断路器类-剩余电流保护断路器

本文文字836,阅读时间6分钟左右。此系列文章 从配电电器的大类等进行慢慢阐述,有不足的地方 欢迎大家相互探讨交流。配电电器,顾名思义就是进行电力分配的设备。电,从发电机出来之后,由总线输出,不可能直接…

html模块殃射,【Web前端问题】webpack打包后,module模块中的函数无法在html标签的事件中调用?...

折腾了好久,求大佬指点~~最近才开始接触webpack以及ES6的module,可能理解的有问题吧。。。希望大佬来指点一下我这个菜鸟。我的想法是在一个module中定义函数,在HTML的中用onclick事件调用这个函数。module模块代码:— base.js —…

Linux网络参数设置

1、ifconfig 查询、设定网络卡与ip 设置桥接网络 # vi /etc/sysconfig/network-script/ifcfg-br0DEVICEbr0 #设备名称BOOTPROTOstatic #设置静态地址IP6INITnoMTU1500 #设置MTU连接数 (可以设定不同的MTU数值)NM_CONTROLLEDnoONBOOTyesIPADDR…

delhpi7 tcombobox清楚重复项_专利数据统计中需要搞清楚的首要问题(2)

上一篇专利数据统计中需要搞清楚的首要问题(1)介绍了通过合并申请号避免重复统计,理清了专利篇数和专利件数的问题,那么还有一类统计就是对发明项数的统计,这里就涉及到对同一项发明的重复统计问题。上一篇介绍的相同申请号但不同公开号的专利…

【Electron】Electron开发入门(八):自定义electron框架外壳(shell)的菜单(Menu)...

1.自定义electron框架外壳(shell)的菜单(Menu) electron的main.js里代码: const Menu require(electron).Menu; var template [{label: 关闭,click: function () { win.close();console.log("关闭")},// s…

交通大数据应用细分_盈海科技 | 交通大数据可视化“掘金”数据价值

面对日益拥堵的交通状况智能交通布局虽在不断完善但交通管理依旧收效甚微问题究竟出在了哪里?数据独立存储难以融合应用数据内在规律难寻数据可视化程度低……问题繁多 困难重重怎么办???交通大数据可视化解决方案上岗啦&#xff…

书评– Kubernetes Up&Running,作者:Kelsey Hightower

欢呼! 正如我在以前的帖子中所写的那样,当您开始研究和使用所有这些新颖的,有光泽的容器/编排技术时,很有可能最终导致您的翻译“迷失”。 很多信息,很多技术,很多开发 ,很多承诺,很…

relative布局html,CSS的四种布局方式static/relative/fixed/absolute

staticstatic布局是HTML元素默认的布局方式,并且static布局的元素不会受到top/left/bottom/right属性的影响。布局元素的位置即其在标准文档流中的位置。relative相对布局是相对其在标准文档流中的位置而言的。设置其top/left/bottom/right属性会使该元素脱离标准文…

MFC CListCtrl

列名、行内容的添加、删除。 据列名、行内容长度设置列宽。 排序。 提升权限 BOOL CDemoListCtrlApp::EnableDebugPrivilege() { HANDLE token; if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&token)) { return FALSE; } TOKEN_PRIVILEGES tkp…

快手用旺旺瓶子做机器人_100品牌入榜,在快手的品牌运营怎么做?|11月快手品牌新势力榜揭晓...

11月榜单见证了许多新入驻快手的品牌迅速成长。母婴品牌「安慕斯」实现垂类专业场景剧情化,引起用户发注,激发用户参与,单月涨粉突破60万;服饰箱包运动垂类下的珠宝品牌「DR钻戒」以“一生唯一真爱”的理念抢占消费者心智&#xf…

认识计算机ppt课件游戏,《认识计算机》PPT课件

《认识计算机》PPT课件 认知主义认为 学习是个体对环 境的作用,而并不仅是环境刺激引起的行为改变;环境只能提供潜在刺激,至于这些潜在刺激是否受到注意或被加工,这主要取决于学习者内部的认知结构。 认 识 计 算 机 认知主义认为…

UITextView: 响应键盘的 return 事件(收回键盘)

UITextView: 响应键盘的 return 事件(收回键盘) 此篇文章将要介绍UITextView: 响应键盘的 return 事件(收回键盘)的相关介绍,具体实例请看下文 UITextView: 响应键盘的 return 事件 UITextFieldDelegate代理里面响应re…

hashdos_调查HashDoS问题

hashdos近一个月前,我就如何在不与供应商互动的情况下临时解决 28C3上出现的HashDoS问题或其他代码缺陷发表了一些想法。 现在是时候更深入地研究复杂性攻击并查看源了。 我完全假设java.util.HashMap和java.util.Hashtable是受此攻击影响的最常用的Java数据结构&am…

ssb门限_画出滤波法ssb信号调制器模型,并说明低通和高通滤波器分别得到哪个边带信号...

匿名用户1级2016-05-25 回答如果输出已调信号的频谱和输入调制信号的频谱之间满足线性搬移关系,则称为线性调制,通常也称为幅度调制。线性调制的主要特征是调制前后的信号频谱从形状上看没有发生根本变化,仅仅是频谱的幅度和位置发生了变化。…