搜索​​​​

清除过滤器
文章
Jingwei Wang · 五月 4, 2022

IntegratedML 实践实验室

你是否尝试过[InterSystems IRIS IntegratedML学习平台](https://learning.intersystems.com/course/view.php?id=1535&ssoPass=1)?在这个平台中,你可以在再入院数据集上训练和测试一个模型,并能够预测一个病人何时会再入院,或计算其再入院的概率。 你不需要在你的系统上进行任何安装就可以尝试,你所要做的就是启动一个虚拟实验室环境(Zeppelin),然后玩一玩! 在这篇文章中,我们将利用这个实验室向你简要介绍IntegratedML,向你介绍要处理的问题,如何使用IntegratedML来创建一个再入院预测模型,以及如何分析其性能指标的一些见解。 ## 什么是IntegratedML? ![IntegratedML infographic](https://user-images.githubusercontent.com/8899513/85149599-7848f900-b21f-11ea-9b65-b5d703752de3.PNG) 来源: https://github.com/intersystems-community/integratedml-demo-template 在开始本教程之前,让我们简单谈谈IRIS IntegratedML。这个工具使你能够直接在SQL语句中执行机器学习(ML)任务,抽象出复杂过程的实现,例如,选择哪些列和ML算法是对目标列进行分类或回归的最佳选择。 IntegratedML的另一个伟大功能是易于部署。一旦你的模型被训练并表现良好,你只需要运行SQL语句,以便让你的模型投入生产。 IntegratedML让你选择使用哪个 [ML提供者](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_Configuration_Providers)。默认的提供者是 [AutoML](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AUTOML), 这是InterSystems公司使用Python实现的ML库 [scikit-learn](https://scikit-learn.org/stable/). 但是你也可以选择其他三个提供者: PMML, [H2O](https://www.h2o.ai/) and [DataRobot](https://www.datarobot.com/). 在这篇文章中,我们将使用AutoML。 ## 问题和解决方案 现在,让我们来介绍一下这个问题,以及我们如何努力提出一种方法来尽量减少其影响。我们将尝试减少由于再入院而产生的问题。 [根据维基百科](https://en.wikipedia.org/wiki/Hospital_readmission), 医院再入院是指病人被批准离开医院(出院),但此人在一个意想不到的短时间间隔内再次回到医院(再入院)的事件。 ![Hospital readmission inforgraphic](https://news.yale.edu/sites/default/files/styles/horizontal_image/public/d6_files/Hospital_v03_YNews.jpg) 来源: [https://news.yale.edu/2015/01/15/when-used-effectively-discharge-summari...](https://news.yale.edu/2015/01/15/when-used-effectively-discharge-summaries-reduce-hospital-readmissions) 再入院会造成病人护理质量的损失--出院和再入院之间的时间可能很关键,医院的资源也会优化。 克服这个问题的一个方法是尝试使用历史数据库来创建一个数据集,其中过去的再入院事件可以通过机器学习算法进行分析,创建一个ML模型。如果数据集足够丰富和干净,再入院模式可以被正确检测出来,新的事件可以通过这个模型计算出其概率。 因此,通过使用ML模型来预测再入院,能够避免错误的出院,肯定会成为医院提高服务质量和利润的一个有价值的选择。 ## 使用IntegratedML创建和使用一个ML模型 ### 模型创建 使用IntegratedML创建一个ML模型,就像执行一个SQL语句一样容易。你只需要定义数据所在的历史数据集,并为你的模型起一个名字。 ``` CREATE MODEL Readmission PREDICTING (MxWillReAdmit) FROM EncountersHistory ``` After that statement, you have declared and created a model called Readmission intended to predict the values for a column called MxWillReAdmit, based on a dataset called EncountersHistory. You can find more information about this statement [here](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_CREATEMODEL). For example, in design phase, it’s handy forces the same results on training, so you can use the USING argument with some arbitrary constant number, like this: 在该声明之后,你已经声明并创建了一个名为Readmission的模型,基于名为EncountersHistory的数据集,预测名为MxWillReAdmit的列的值。 你可以找到关于这个声明的更多信息,[点击这里](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_CREATEMODEL)。例如,在设计阶段,很方便地迫使训练中出现相同的结果,所以你可以用USING参数加上一些任意的常数,像这样。 ``` CREATE MODEL Readmission PREDICTING (MxWillReAdmit) FROM EncountersHistory USING {"seed": 3} ``` ### 模型训练 现在,通过使用TRAIN MODEL语句,你的模型已经准备好被训练。 ``` TRAIN MODEL Readmission ``` 在这里,IntegratedML为你做了很多工作(使用AutoML ML供应商): - [Feature engineering](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Feature): 使用哪些列 - [Data encoding](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Keys): 所选栏目中的数据必须如何呈现给ML算法 - [ML algorithm selection](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Algos): 哪种算法能带来最好的结果 - [Model selection](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_AutoML#GIML_AutoML_Selection): 根据目标列的数据类型,应该选择一个分类模型或回归模型。在这个例子中,一旦目标列有布尔值,就会选择一个分类模型。 这个声明可能需要一些时间,取决于数据集的大小和你的数据的复杂性。 ### 模型验证 到现在,我们应该急于知道我们的模型到底有多好。Integrated ML有一个计算性能指标的声明,基于另一个数据集,而不是用于训练的数据集 这个新的数据集被称为测试或验证数据集,这取决于你使用哪种策略进行验证。这个数据集通常是从用于创建训练数据集的同一个数据集中检索出来的。一个常见的方法是随机选择70%或80%的数据集用于训练,其余的用于测试/验证。 在实验室里,之前已经为这项任务准备了一个新的可使用的数据集:EncountersNew数据集。 现在,我们可以找出我们的模型有多好(或多坏)。 ``` VALIDATE MODEL Readmission FROM EncountersNew ``` 为了得到结果,你应该查询 INFORMATION_SCHEMA.ML_VALIDATION_METRICS 表。 ``` SELECT * FROM INFORMATION_SCHEMA.ML_VALIDATION_METRICS ``` 模型的性能是用 [IntegratedML的四个指标](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_VALIDATEMODEL#GIML_VALIDATEMODEL_Metrics): - Accuracy: 预测正确率(数值接近1表示正确率高)。 - Precision:对模型所做的所有阳性预测的正确率(接近1的值意味着很少有假阳性预测)。 - Recall:关于数据集中所有实际阳性值的正确阳性预测率(接近1的值意味着很少有假阴性预测)。 - F-Measure: 另一种衡量准确率的方法,当准确率表现不佳时使用,一般用于不平衡问题(数值接近1意味着高正确率)。 ### 关于性能指标的一些讨论 在这里,我们可以看到,该模型的准确率为82%。但是,这个指标不应该被单独分析,其他的指标如精确度和召回率也必须被评估。 让我们思考一下错误预测的影响--假阳性和假阴性。对于我们的模型来说,假阳性意味着预测的再入院并不是实际的再入院。而假阴性,意味着一个病人已经再入院,而模型预测这个病人不会再入院。 这两种情况都会导致错误的决定。一个假阳性的预测可能会导致一个超过必要的病人留在医院的决定;而一个假阴性的预测可能会导致一个提前出院的决定,然后再让这个病人重新入院。 请注意,在我们的模型中,假阴性会导致再入院病例,这正是我们要避免的。所以,我们必须选择能减少假阴性病例的模型,即使假阳性病例的数量增加。 因此,为了选择具有低假阴性率的模型,我们需要选择具有高召回率的模型。这是因为召回率越高,假阴性就越低。 由于我们的模型有84%的召回率,让我们暂时假定它是一个合理的值。 ### 预测 一旦你训练好了你的模型,而且它的性能可以接受,现在你就可以执行这个模型,以便预测结果。 你应该使用 [PREDICT](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_PREDICT) 函数,以便用户对模型进行预测。 ``` SELECT TOP 100 ID, PREDICT(Readmission) AS PredictedReadmission, MxWillReAdmit AS ActualReadmission FROM EncountersNew ``` 这个函数根据内部阈值,评估模型的目标列的行是真值还是假值的概率。 你可以看到我们的模型做了一些错误的预测--这很正常。 另一个可以用来进行预测的函数,是 [PROBABILITY](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GIML_PROBABILITY) 函数. ``` SELECT TOP 10 ID, PROBABILITY(Readmission FOR '1') AS ReadmissionProbability, PREDICT(Readmission) AS PredictedReadmission, MxWillReAdmit AS ActualReadmission FROM EncountersNew ``` 与PREDICTION相同,PROBABILITY使用一行的列作为指定模型的输入,但在这里我们需要定义我们想要计算的概率是哪一类。 如果我们想定制用于预测的阈值,这个函数可能很有用。 ### 查询训练好的模型 在创建和训练你的模型后,你可以使用 INFORMATION_SCHEMA.ML_TRAINED_MODELS 表来查询它们。 ``` SELECT * FROM INFORMATION_SCHEMA.ML_TRAINED_MODELS ``` ## 结论 在这篇文章中,我们对历史上的病人再入院数据集进行了创建、训练、验证和预测。这些预测可以作为寻求更好决策的另一个工具,从而改善对病人的护理,降低医院服务的成本。 所有这些步骤都是通过InterSystems IntegratedML使用SQL语言完成的,它为广大的开发者带来了机器学习的力量。
文章
Hao Ma · 三月 25, 2021

为什么 COVID-19 对机器学习也有危险? (Part II)

继[上一部分](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i),现在要利用 IntegratedML VALIDATION MODEL 语句提供信息以监视您的 ML 模型。 您可以在[此处](https://www.youtube.com/watch?v=q9ORM32zPjs)观看实际运作。 此处所示代码衍生自 [InterSystems IntegragedML 模板](https://openexchange.intersystems.com/package/integratedml-demo-template)或 [IRIS 文档](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_healthmon)提供的示例,我主要是把代码混合了起来。 这是一个简单的示例,目的是为进一步讨论和未来工作提供一个起点。 注:此处提供的代码仅作说明之用。 如果您想尝试,我开发了一个 Open Exchange 技术示例应用 ([iris-integratedml-monitor-example](https://openexchange.intersystems.com/package/iris-integratedml-monitor-example)),并将其提交到 InterSystems IRIS AI Contest。 读完这篇文章后您可以去看看,如果喜欢,就请[投我一票吧](https://openexchange.intersystems.com/contest/current)! :) # 目录 ### 第一部分: * [IRIS IntegratedML 和 ML 系统](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i#iris_integratedml_and_ml_systems) * [新旧常态之间](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i#between_the_old_and_new_normal) ### 第二部分: * [监视 ML 性能](#monitoring_ml_performance) * [简单用例](#a_simple_use_case) * [未来工作](#future_works) # 监视 ML 性能 要监视 ML 模型,至少需要两个功能: 1) 性能指标提供程序 2) 监视和通知服务 幸运的是,IRIS 为我们提供了这两个必要的功能。 ## 获取 ML 模型性能指标 如[上一部分](https://community.intersystems.com/post/why-covid-19-also-dangerous-machine-learning-part-i)所示,IntegratedML 提供了 VALIDATE MODEL 语句来计算以下性能参数: * 准确率:模型的好坏(值接近 1 表示正确答案率高) * 精度:模型处理误报的能力如何(值接近 1 表示**无**误报率高) * 召回率:模型处理漏报的能力如何(值接近 1 表示**无**漏报率高) * F 度量:另一种测量准确率的方法,用于准确率表现不佳的情况(值接近 1 表示正确答案率高) 注:这些定义并不是正式的,而且非常浅显! 我推荐您花些时间[了解它们](https://medium.com/analytics-vidhya/accuracy-vs-f1-score-6258237beca2)。 最妙的是,每次调用 VALIDATE MODEL 时,IntegrateML 都会存储它的性能指标,这样的功能可以很好地用于监视。 ## 监视引擎 InterSystems IRIS 提供 System Monitor 框架用于处理监视任务。 它还允许您定义自定义规则,以根据这些指标上应用的谓词触发通知。 默认提供磁盘、内存、进程、网络等一系列指标。 此外,System Monitor 还可以让您扩展监视器,覆盖无限的可能性。 这样的自定义监视器在系统监视器术语中称为应用监视器。 您可以在[此处](https://irisdocs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GCM_healthmon)了解有关 System Monitor 的更多信息。 ## 整合 现在,有了一种获取各模型验证性能指标值的方法,还有一个可以根据应用于自定义指标源的自定义规则触发警报的工具.……那么,是时候把它们结合起来了。 首先,我们需要通过扩展 %Monitor.Abstract 类创建自定义应用监视器类,并实现 *Initialize* 和 *GetSample* 方法。 ``` Class MyMetric.IntegratedMLModelsValidation Extends %Monitor.Adaptor { /// Initialize the list of models validation metrics. Method Initialize() As %Status { Return $$$OK } /// Get routine metric sample. /// A return code of $$$OK indicates there is a new sample instance. /// Any other return code indicates there is no sample instance. Method GetSample() As %Status { Return $$$OK } } ``` 系统监视器会定期发出调用以监视类,获取一组称为样本的指标。 这样的样本可以仅用于监视,也可用于检查是否必须提高警报规则。 您可以通过在监视器类中定义标准的非内部属性来定义此类样本的结构。 需要注意的是,必须在参数 INDEX 中指定其中一个属性作为每个样本的主键,否则将抛出键重复错误。 ``` Class MyMetric.IntegratedMLModelsValidation1 Extends %Monitor.Adaptor { Parameter INDEX = "ModelTrainedName"; /// Name of the model definition Property ModelName As %Monitor.String; /// Name of the trained model being validated Property ModelTrainedName As %Monitor.String; /// Validation error (if encountered) Property StatusCode As %Monitor.String; /// Precision Property ModelMetricPrecision As %Monitor.Numeric; /// Recall Property ModelMetricRecall As %Monitor.Numeric; /// F-Measure Property ModelMetricFMeasure As %Monitor.Numeric; /// Accuracy Property ModelMetricAccuracy As %Monitor.Numeric; ... } ``` *Initialize* 方法在每次监视器调用时被调用一次,*GetSample* 方法则被调用到返回 0 为止。 因此,我们可以在 IntegrateML 验证历史上设置 SQL,向监视器提供指标信息,实现 *Initialize* 和*GetSample* 方法: ``` /// Initialize the list of models validation metrics. Method Initialize() As %Status { // Get the latest validation for each model validated by VALIDATION MODEL statement Set sql = "SELECT MODEL_NAME, TRAINED_MODEL_NAME, STATUS_CODE, %DLIST(pair) AS METRICS_LIST FROM ("_ "SELECT m.*, $LISTBUILD(m.METRIC_NAME, m.METRIC_VALUE) pair, r.STATUS_CODE "_ "FROM INFORMATION_SCHEMA.ML_VALIDATION_RUNS r "_ "JOIN INFORMATION_SCHEMA.ML_VALIDATION_METRICS m "_ "ON m.MODEL_NAME = r.MODEL_NAME "_ "AND m.TRAINED_MODEL_NAME = r.TRAINED_MODEL_NAME "_ "AND m.VALIDATION_RUN_NAME = r.VALIDATION_RUN_NAME "_ "GROUP BY m.MODEL_NAME, m.METRIC_NAME "_ "HAVING r.COMPLETED_TIMESTAMP = MAX(r.COMPLETED_TIMESTAMP)"_ ") "_ "GROUP BY MODEL_NAME" Set stmt = ##class(%SQL.Statement).%New() $$$THROWONERROR(status, stmt.%Prepare(sql)) Set ..Rspec = stmt.%Execute() Return $$$OK } /// Get routine metric sample. /// A return code of $$$OK indicates there is a new sample instance. /// Any other return code indicates there is no sample instance. Method GetSample() As %Status { Set stat = ..Rspec.%Next(.sc) $$$THROWONERROR(sc, sc) // Quit if we have done all the datasets If 'stat { Quit 0 } // populate this instance Set ..ModelName = ..Rspec.%Get("MODEL_NAME") Set ..ModelTrainedName = ..Rspec.%Get("TRAINED_MODEL_NAME")_" ["_$zdt($zts,3)_"]" Set ..StatusCode = ..Rspec.%Get("STATUS_CODE") Set metricsList = ..Rspec.%Get("METRICS_LIST") Set len = $LL(metricsList) For iMetric = 1:1:len { Set metric = $LG(metricsList, iMetric) Set metricName = $LG(metric, 1) Set metricValue = $LG(metric, 2) Set:(metricName = "PRECISION") ..ModelMetricPrecision = metricValue Set:(metricName = "RECALL") ..ModelMetricRecall = metricValue Set:(metricName = "F-MEASURE") ..ModelMetricFMeasure = metricValue Set:(metricName = "ACCURACY") ..ModelMetricAccuracy = metricValue } // quit with return value indicating the sample data is ready Return $$$OK } ``` 编译监视器类后,您需要重新启动系统监视器,使系统意识到一个新的监视器已经创建并可以使用。 您可以使用 ^%SYSMONMGR 例程或 %SYS.Monitor 类来完成这一步。 # 简单用例 这样就有了所需的工具来收集、监视和发布 ML 性能指标的警报。 接下来要做的是定义自定义警报规则,模拟已部署的 ML 模型开始对性能造成负面影响的场景。 首先,我们必须配置电子邮件警报及其触发规则。 这可以使用 ^%SYSMONMGR 例程完成。 不过,为了方便,我创建了一个设置方法,它可以设置所有电子邮件配置和警报规则。 您需要将 <> 之间的值替换为您的电子邮件服务器和帐户参数。 ``` ClassMethod NotificationSetup() { // Set E-mail parameters Set sender = "" Set password = "" Set server = "" Set port = "" Set sslConfig = "default" Set useTLS = 1 Set recipients = $LB("") Do ##class(%Monitor.Manager).AppEmailSender(sender) Do ##class(%Monitor.Manager).AppSmtpServer(server, port, sslConfig, useTLS) Do ##class(%Monitor.Manager).AppSmtpUserName(sender) Do ##class(%Monitor.Manager).AppSmtpPassword(password) Do ##class(%Monitor.Manager).AppRecipients(recipients) // E-mail as default notification method Do ##class(%Monitor.Manager).AppNotify(1) // Enable e-mail notifications Do ##class(%Monitor.Manager).AppEnableEmail(1) Set name = "perf-model-appointments-prediction" Set appname = $namespace Set action = 1 Set nmethod = "" Set nclass = "" Set mclass = "MyMetric.IntegratedMLModelsValidation" Set prop = "ModelMetricAccuracy" Set expr = "%1 < .8" Set once = 0 Set evalmethod = "" // Create an alert Set st = ##class(%Monitor.Alert).Create(name, appname, action, nmethod, nclass, mclass, prop, expr, once, evalmethod) $$$THROWONERROR(st, st) // Restart monitor Do ##class(MyMetric.Install).RestartMonitor() } ``` 在以前的方法中,警报在监视器获得的准确率值小于 90% 时发出。 现在,设置警报规则后,让我们用前 500 条记录创建、训练和验证履约/失约预测模型,并通过前 600 条记录进行验证。 注:*种子*参数只是为了保证可重复性(即没有随机值),通常在生产中必须避免。 ``` -- Creates the model CREATE MODEL AppointmentsPredection PREDICTING (Show) FROM MedicalAppointments USING {\"seed\": 3} -- Train it using first 500 records from dataset TRAIN MODEL AppointmentsPredection FROM MedicalAppointments WHERE ID
文章
Hao Ma · 九月 17, 2022

IRIS的镜像配置(1)

因为篇幅太长, 我把它分为3篇贴在社区 # 配置前的准备 配置Mirror前要准备三件事儿: 1. 规划网络连接。 2. 在所有的服务器中启动ISCAgent服务。 3. 准备服务器的SSL/TLS证书。可选, 但非常推荐。 我假设您在动手前一定已经对Mirror的原理和架构已经不陌生了,对镜像成员,DR(灾备)成员, Arbiter, ISCAgent等术语已经自动切换的概念有大概的认识。如果不是这样,请先阅读在线文档,或者这篇文章。 ## 规划网络连接 Mirror应该配置两个网段:一个用于IRIS和外部的通信;另一个用于两个Mirror成员间的内部通信,也就是数据的同步。 尽管不是必须的,但Mirror作为一个高可用方案,为了保证服务器之间的内部通信不受和外部连接的干扰,把内部通信放在单独的网段是通常的做法,尤其是在生产环境。 下图来自IRIS的在线文档:其中绿色所示的是IRIS提供服务的网段,IRIS到所有外部系统的连接,ECP应用服务器,和Arbiter在工作在这个网段。紫色的“Data Center Private LAN for Mirror Communication"用于内部通信,准确的说, 用于journal的同步。为了方便, 我会在后面的步骤中简单的把这两个网段简单的称为**外网网段**和**内网网段**。 ![Mirror的网络链接](https://docs.intersystems.com/iris20212/csp/docbook/images/gha_mirror_netconfig_simple.png) 也是来自在线文档,上图的IP地址配置像这个样子。(请忽略C栏和D栏,它们是DR服务器的地址) ![mirror网络配置](https://docs.intersystems.com/iris20221/csp/docbook/images/gha_mirror_netconfig_ecp_table.png) 在安装配置Mirror之前, 您需要检查的是: ( Agent address用的是公网地址,但书上有句话:When attempting to contact this member’s agent, other members try this address first. Critical agent functions (such as those involved in failover decisions) will retry on the mirror private and superserver addresses (if different) when this address is not accessible. Because the agent can send journal data to other members, journal data may travel over this network. ) - ServerA, ServerB, Arbiter三台机器的在绿色网段可以相互访问。ServerA, ServerB的1972端口可以访问,Arbiter的2188端口可以访问。 - ServerA, ServerB在紫色网段可以互相访问2188端口。 - Virtual IP绑定在Server A, 并且IRIS的服务和连接通过Virtual IP提供。 下面是我用的两个服务器的网络配置,因为不方便使用(懒的修改)上图的地址,我自己做的地址配置如下 | Virtual IP Address | | | | ------------------------------ | -------------- | -------------- | | Arbiter Address | 172.16.58.100 | | | Member-Specific Mirror Address | serverA | serverB | | SuperServer Address | 172.16.58.101 | 172.16.58.102 | | Mirror Private Address | 172.16.159.101 | 172.16.159.102 | | Agent Address | 172.16.58.101 | 172.16.58.102 | 其中172.16.58.0网段为外网网段; 172.16.159.0网段为内网网段。 在操作系统上查看IP, 是这个样子: **servera** ```sh #servera上的端口配置 [root@servera mgr]# ip -4 -br addr lo UNKNOWN 127.0.0.1/8 ens33 UP 172.16.58.101/24 ens36 UP 172.16.159.101/24 [root@servera mgr]# firewall-cmd --list-ports 1972/tcp 52773/tcp 2188/tcp [root@servera ~]# #serverb上的端口配置 [root@serverb isc]# ip -4 -br addr lo UNKNOWN 127.0.0.1/8 ens33 UP 172.16.58.102/24 ens36 UP 172.16.159.102/24 [root@serverb isc]# # firewall-cmd --list-ports 1972/tcp 52773/tcp 2188/tcp ``` ## 在所有的镜像成员启动ISCAgent服务 无论是同步成员,异步成员,还是Arbiter,它们之间的通信都依赖ISCAgent服务。在操作系统上,它是一个独立于IRIS的服务,IRIS的默认安装也没有把它设置为自动启动,所以您需要在安装IRIS的机器,也就是同步,异步成员上手工启动这个服务。至于Arbiter,您可以理解Arbiter就是一个装了ISCAgent服务的机器,可以是任何一台客户的机器,装上ISCAgent, 能帮助IRIS主备成员自动切换判断,它就是Arbiter了。简单说, 您需要 1. 在Mirror的所有成员上启动ISCAgent 2. 在一台机器上安装ISCAgent并启动,它从此就是这个Mirror的Arbiter了。 Arbiter需要和IRIS服务器用相同的操作系统吗?没必要。很多客户的IRIS装在Linux上,而Arbiter是一个Windows机器, 跑着和IRIS无关的业务, 都不用是Server版的Windows。 默认配置下, ISCAgent通过TCP的2188端口和远端连接,启动ISC Agent后请检查防火墙,保证2188端口访问是可以访问的。 下面是具体的配置细节。 ### 在Mirror的所有成员上启动ISCAgent IRIS服务器不需要单独安装ISCAgent。 你需要做的是启动服务,并服务器重启后ISCAgent能自动启动。 - Windows: 进入管理工具—服务,选择ISCAgent,将启动类型改为自动。点启动ISCAgent,并确认服务已启动。 - Linux: 使用systemctl启动ISCAgent, 并加入系统自启动列表。 ```sh [root@servera isc]# systemctl start ISCAgent [root@servera isc]# systemctl status ISCAgent ● ISCAgent.service - InterSystems Agent Loaded: loaded (/etc/systemd/system/ISCAgent.service; disabled; vendor preset: disabled) Active: active (running) since Fri 2022-04-15 10:26:20 CST; 11s ago Process: 3651 ExecStart=/usr/local/etc/irissys/ISCAgent (code=exited, status=0/SUCCESS) Main PID: 3653 (ISCAgent) CGroup: /system.slice/ISCAgent.service ├─3653 /usr/local/etc/irissys/ISCAgent └─3654 /usr/local/etc/irissys/ISCAgent Apr 15 10:26:20 servera systemd[1]: Starting InterSystems Agent... Apr 15 10:26:20 servera systemd[1]: Started InterSystems Agent. Apr 15 10:26:20 servera ISCAgent[3653]: Starting Apr 15 10:26:20 servera ISCAgent[3654]: Starting ApplicationServer on *:2188 [root@servera isc]#systemctl enable ISCAgent Created symlink from /etc/systemd/system/multi-user.target.wants/ISCAgent.service to /etc/systemd/system/ISCAgent.service. [root@servera isc]# ``` ### Arbiter(仲裁服务) 你需要到WRC的下载网址下载ISCAgent的软件。下面是在Linux下安装ISCAgent的过程。 ```sh [root@serverc isc]# cd ISCAgent-2022.1.0.164.0-lnxrh7x64 [root@serverc ISCAgent-2022.1.0.164.0-lnxrh7x64]# ls agentinstall cplatname dist package tools [root@serverc ISCAgent-2022.1.0.164.0-lnxrh7x64]# ./agentinstall Your system type is 'Red Hat Enterprise Linux (x64)'. Please review the installation options: ------------------------------------------------------------------ ISCAgent version to install: 2022.1.0.164.0 ------------------------------------------------------------------ Do you want to proceed with the installation ? yes Starting installation... Installation completed successfully [root@serverc ISCAgent-2022.1.0.164.0-lnxrh7x64] ``` 安装后同上一小节使用systemctl命令启动。 ## 准备服务器的SSL/TLS证书 您的每个服务器需要准备两个证书:一个本机的证书, 一个是CA的证书。(如果是公开证书也要吗,openssl应该是没有,除非自己去下载)。 如果您使用的是IRIS自带的PKI签发的self-signed证书,那么每台服务器不仅仅要自己的证书,非CA所在的服务器要:“获取证书颁发机构证书”(‘Get Certificate Authority Certificate’)。这个选项“证书颁发机构客户端的”从证书颁发机构获取证书页面。 举例: servera, serverb为镜像成员。 ```sh [root@servera mgr]# ls *.cer *.key iris.key iscCA.cer iscCASigned.cer iscCASigned.key ``` 忽略iris.key。 其他的iscCA.cer是CA的证书, iscCASigned.cer是servera的证书, iscCASigned.key是servera的私钥。 同样, 在serverb: ```sh [root@serverb ~]# cd /isc/iris/mgr [root@serverb mgr]# ls *.cer *.key iris.key iscCA.cer iscCASignedserverb.cer iscCASignedserverb.key [root@serverb mgr]# ``` ## 增加gmheap的大小 Mirror成员间的数据同步默认使用“Parallel dejournaling", 也就是多个进程并行处理同步数据,而这要求增大gmheap资源。 gmheap又被称为shared memory heap(SMH)。它的默认配置是38MB, 但在实际的生产系统中,gmheap通常配置比这要大。 每一个"parallel dejournalling job"需要增加200M的gmheap。比如说, 为了支持4个并行的同步任务, gmheap至少需要800M。如果系统资源足够,最多可以有16个并行同步任务。 **折中的考虑,当您配置镜像时,请将您系统的gmheap值增加为1GB。** 后面我们进入正式的Mirror的配置工作。Mirror的配置和把数据库添加入镜像。
文章
Hao Ma · 三月 25, 2021

为什么 COVID-19 对机器学习也有危险?(Part I)

几个月前,我在 MIT Technology Review 读到一篇很有意思的[文章](https://www.technologyreview.com/2020/05/11/1001563/covid-pandemic-broken-ai-machine-learning-amazon-retail-fraud-humans-in-the-loop/),作者解释了新冠疫情如何给全球 IT 团队带来关乎机器学习 (ML) 系统的难题。 这篇文章引起我对 ML 模型部署后如何处理性能问题的思考。 我在一个 Open Exchange 技术示例应用 ([iris-integratedml-monitor-example](https://openexchange.intersystems.com/package/iris-integratedml-monitor-example)) 中模拟了一个简单的性能问题场景,并提交到 InterSystems IRIS AI Contest。 读完这篇文章后您可以去看看,如果喜欢,就请[投我一票吧](https://openexchange.intersystems.com/contest/current)! :) # 目录 ### 第一部分: * [IRIS IntegratedML 和 ML 系统](#iris_integratedml_and_ml_systems) * [新旧常态之间](#between_the_old_and_new_normal) ### 第二部分: * [监视 ML 性能](#monitoring_ml_performance) * [简单用例](#a_simple_use_case) * [未来工作](#future_works) # IRIS IntegratedML 和 ML 系统 讨论 COVID-19 以及它对全球 ML 系统的影响之前,我们先来简单谈谈 InterSystems IRIS IntegratedML。 通过将特征选择之类的任务及其与标准 SQL 数据操作语言的集成自动化,IntegratedML 可以协助开发和部署 ML 解决方案。 例如,对医疗预约的数据进行适当的操作和分析后,可以使用以下 SQL 语句设置 ML 模型,预测患者的履约/失约情况: ```sql CREATE MODEL AppointmentsPredection PREDICTING (Show) FROM MedicalAppointments TRAIN MODEL AppointmentsPredection FROM MedicalAppointments VALIDATE MODEL AppointmentsPredection FROM MedicalAppointments ``` AutoML 提供程序将选择性能最好的特征集和 ML 算法。 这里,AutoML 提供程序使用 scikit-learn 库选择了逻辑回归模型,获得 90% 的准确率。 ``` | | MODEL_NAME | TRAINED_MODEL_NAME | PROVIDER | TRAINED_TIMESTAMP | MODEL_TYPE | MODEL_INFO | |---|------------------------|-------------------------|----------|-------------------------|----------------|---------------------------------------------------| | 0 | AppointmentsPredection | AppointmentsPredection2 | AutoML | 2020-07-12 04:46:00.615 | classification | ModelType:Logistic Regression, Package:sklearn... | ``` ``` | METRIC_NAME | Accuracy | F-Measure | Precision | Recall | |--------------------------|----------|-----------|-----------|--------| | AppointmentsPredection21 | 0.9 | 0.94 | 0.98 | 0.91 | ``` 集成到 SQL 后,您可以通过估计履约和失约的患者,将 ML 模型无缝集成到现的预约系统中以提高其性能: ```sql SELECT PREDICT(AppointmentsPredection) As Predicted FROM MedicalAppointments WHERE ID = ? ``` 您可以在[此处](https://docs.intersystems.com/iris20202/csp/docbook/DocBook.UI.Page.cls?KEY=GIML)详细了解 IntegrateML。 有关这个简单的预测模型的更多详细信息,可以参考[此处](https://github.com/jrpereirajr/iris-integratedml-monitor-example/blob/master/jupyter-samples/IntegeratedML-Monitor-Example.ipynb)。 然而,由于 AI/ML 模型在设计上是为了直接或间接地适应社会行为,因此当相关行为快速变化时,这些模型可能会受到很大影响。 最近,由于新冠疫情,我们(很遗憾地)得以实验这种场景。 # 新旧常态之间 如[MIT Technology Review 的文章](https://www.technologyreview.com/2020/05/11/1001563/covid-pandemic-broken-ai-machine-learning-amazon-retail-fraud-humans-in-the-loop/)所解释,新冠疫情一直在显著且迅速地改变着社会行为。 我在 Google Trends 中查询了一些文章中引用的词语,如 N95 口罩、卫生纸和消毒洗手液,确认在全球大流行中这些词语的热度有所提高: 文章中提到: > “但是它们(指由 COVID-19 引起的变化)也影响了人工智能,给库存管理、欺诈检测、营销等幕后运行的算法造成干扰。 根据正常人类行为进行训练的机器学习模型现在发现,所谓的‘正常’已经发生变化,有些模型因而不再能发挥应有的作用。” 即,在“旧常态”和“新常态”之间,我们正在经历一种“新异常”。 文章中还有这样一段话: > “机器学习模型虽然是为了应对变化而设计的, 但大多数也很脆弱。当输入数据与训练的数据相差太大时,它们的表现就会很糟糕。 (...) AI 是一种活着的引擎。” 本文继续列出一些 AI/ML 模型的示例,这些示例有的是性能突然开始受到负面影响,有的需要立即进行更改。 一些示例: * 零售公司的非常规产品在批量订购后缺货; * 由于媒体文章内容过于悲观,投资推荐服务根据情绪分析提出的建议失准; * 自动短语生成器由于新的语境而开始生成不合适的内容; * Amazon 更改了卖家推荐系统,选择自己送货的卖家,避免对其仓库物流的过度需求。 因此,我们要监控我们的 AI/ML 模型,确保模型能可靠地持续帮助客户。 到这里,希望您已经明白,对 ML 模型的创建、训练和部署并不是全部,跟踪过程也是必不可少的。 在下一篇文章中,我将展示如何使用 IRIS %Monitor.Abstract 框架来监视 ML 系统的性能,以及如何根据监视器的指标设置警报触发器。 *同时,我很想知道您是否遇到过疫情导致的问题,以及您又是如何应对的。请在评论区留言吧!* 敬请关注!保重身体 😊!
文章
姚 鑫 · 五月 17, 2021

第三章 执行测试

# 第三章 执行测试 # 示例:执行测试 现在使用`%UnitTest.Manager.RunTest`执行单元测试。以下是方法: 1. 在包含单元测试的名称空间中打开终端;在本例中为用户。如果终端未在正确的命名空间中打开,请使用ZN更改命名空间。 2. 将`^UnitTestRoot`全局值设置为包含导出的测试类的目录的父级。 ```java DHC-APP>Set ^UnitTestRoot="d:\Temp" ``` 3. 使用方法`%UnitTest.Manager.RunTest`执行测试。 ```java DHC-APP>do ##class(%UnitTest.Manager).RunTest("test") ``` 4. IRIS从`XML`文件加载测试类,编译类,执行测试,从服务器删除测试代码,并向终端发送报告。 ```java HC-APP>do ##class(%UnitTest.Manager).RunTest("test") =============================================================================== Directory: D:\Temp\test\ =============================================================================== test begins ... Load of directory started on 05/14/2021 14:07:17 '*.xml;*.XML;*.cls;*.mac;*.int;*.inc;*.CLS;*.MAC;*.INT;*.INC' Loading file D:\Temp\test\Tests.xml as xml Imported class: MyPackage.Tests Compilation started on 05/14/2021 14:07:17 with qualifiers '', using up to 4 worker jobs Compiling class MyPackage.Tests Compiling routine MyPackage.Tests.1 Compilation finished successfully in 0.019s. Load finished successfully. MyPackage.Tests begins ... TestAdd() begins ... AssertEquals:Test Add(2,2)=4 (passed) AssertNotEquals:Test Add(2,2)'=5 (passed) LogMessage:Duration of execution: .000061 sec. TestAdd passed MyPackage.Tests passed test passed Use the following URL to view the result: http://172.18.18.159:52773/csp/sys/%25UnitTest.Portal.Indices.cls?Index=3&$NAMESPACE=DHC-APP All PASSED ``` ![image](/sites/default/files/inline/images/1_46.png) ![image](/sites/default/files/inline/images/2_27.png) 最后一行显示了测试报告的URL。 **注意:以这种方式运行测试会在它们执行后从InterSystems IRIS中删除它们。如果在执行测试后返回到Atelier查看测试,将看到一个指示,表明Atelier中可见的文件与服务器不同步。可以保存或重新编译该类,以将代码添加回服务器。 如果使用的是`.cls`文件而不是XML文件,则必须向`RunTest`提供`/loadudl`限定符。** ```java USER>do ##class(%UnitTest.Manager).RunTest("mytests","/loadudl") ``` # 示例:UnitTest Portal 运行单元测试将生成测试报告。InterSystems IRIS提供了一个用于查看报告的`UnitTest`门户。报告按命名空间组织。 可以使用系统资源管理器System Explorer > Tools > UnitTest Portal导航到UnitTest门户。如有必要,请切换到用户命名空间。 ![image](/sites/default/files/inline/images/3_22.png) # 示例:在单元测试门户中查看报告 门户将测试结果组织成一系列报告。每个测试报告将测试结果组织到一系列超链接页面中。按照链接查找越来越具体的信息。 第一页提供了所有测试套件的摘要。在这种情况下,所有测试套件都通过了。 ![image](/sites/default/files/inline/images/4_16.png) 单击要查看的报告的`ID`列中的`ID`号。 第二个页面显示每个测试套件的结果。在本例中,`mytest`是测试套件,并且通过了测试。 ![image](/sites/default/files/inline/images/5_7.png) 单击 `mytests`. 第三个页面显示每个测试用例的结果。在本例中,通过了单个测试用例`MyPackage.Tests`。 ![image](/sites/default/files/inline/images/6_6.png) 单击 `MyPackage.Tests` 第四页显示了通过测试方法得出的结果。这里通过了单个测试方法`TestAdd`。 ![image](/sites/default/files/inline/images/7_2.png) 单击 TestAdd. 最后一页显示测试方法中使用的每个`AssertX`宏的结果。在本例中,`AssertEquals`和`AssertNotEquals`都通过了。 ![image](/sites/default/files/inline/images/8_3.png) # 设置和拆卸 `%UnitTest.TestCase`类提供的方法可用于在一个测试或一组测试执行之前设置测试环境,然后在测试完成后拆除该环境。以下是对这些方法的说明: 方法 | 描述 ---|--- `OnBeforeAllTests` |在测试类中的任何测试方法执行之前执行一次。可以设置测试环境。 `OnAfterAllTests` |在测试类中的所有测试方法执行后执行一次。可以破坏测试环境。 `OnBeforeOneTest` |在测试类中的每个测试方法执行之前立即执行。 `OnAfterOneTest` |在文本类中的每个测试方法执行后立即执行。 # 示例:向测试类添加Setup和Tear Down方法 在本例中,将添加一个名为`TestEditContact`的测试方法。此方法验证`MyPackage.Contact`类的`ContactType`属性是否限制为`“Personal”`或`“Business”`。添加了一个`OnBeforeAllTests`方法,该方法在测试执行之前准备数据库。还可以添加一个`OnAfterAllTests`方法,该方法在测试执行后还原数据库状态。 1. 在`Studio`中打开`MyPackage.Tests`(可能需要从`^UnitTestRoot`目录导入它)。 2. 添加`OnBeforeAllTests`和`OnAfterAllTests`方法。 ```java Method OnBeforeAllTests() As %Status { Do ##class(MyPackage.Contact).Populate(1) Return $$$OK } ``` ```java Method OnAfterAllTests() As %Status { Do ##class(MyPackage.Contact).%KillExtent() Return $$$OK } ``` `OnBeforeAllTests`方法使用单个`Contact`实例填充数据库。`OnAfterAllTests`方法从数据库中删除所有`Contact`实例。 3. 现在将`TestEditContact`测试方法添加到`MyPackage.Tests`: ```java Method TestEditContact() { set contact=##class(MyPackage.Contact).%OpenId(1) set contact.Name="Rockwell,Norman" set contact.ContactType="Friend" Do $$$AssertStatusNotOK(contact.%Save(),"ContactType = Friend") Set contact.ContactType="Personal" Do $$$AssertStatusOK(contact.%Save(),"ContactType = Personal") } ``` 该方法在两种情况下测试执行`%Save on Contact`返回的状态值:为`ContactType`分配无效值之后和为`ContactType`分配有效值之后。 4. 将测试导出到`c:\unittest\mytest`,覆盖现有的`Tests.xml`。 # [源码](https://download.csdn.net/download/yaoxin521123/18703118)
文章
姚 鑫 · 五月 29, 2021

第十章 使用FTP

# 第十章 使用FTP IRIS提供了一个类`%Net.FtpSession`,可以使用它从InterSystems IRIS内建立与FTP服务器的会话。 # 建立FTP会话 要建立FTP会话,请执行以下操作: 1. 创建`%Net.FtpSession`的实例。 2. 可以选择设置此实例的属性,以控制会话的常规行为: - `Timeout` 超时指定等待FTP服务器回复的时间(以秒为单位)。 - `SSLConfiguration`指定用于连接的激活的`SSL/TLS`配置(如果有)。如果`FTP`服务器使用HTTPS,请使用此选项。 - `TranslateTable`指定在读取文件内容或写入文件内容时要使用的转换表。 - `UsePASV`启用PASV模式。 - 当`FTP`服务器使用`https`时,`SSLCheckServerIdentity`适用。默认情况下,当`%Net.FtpSession`的实例连接到`SSL/TLS`服务器时,它会检查证书服务器名称是否与用于连接到服务器的DNS名称匹配。如果这些名称不匹配,则不允许连接。 若要禁用此检查,请将`SSLCheckServerIdentity`属性设置为0。 3. 调用`Connect()`方法以连接到特定的FTP服务器。 4. 调用`ascii()`或`binary()`方法将传输模式分别设置为ASCII模式或二进制模式。要查看当前传输模式,请检查实例的Type属性的值。 注意:`%Net.FtpSession`的每个方法都返回一个状态,应该检查该状态。这些方法还设置提供有关会话状态的有用信息的属性的值: - 如果当前已连接,则`CONNECTED`为TRUE,否则为FALSE。 - `ReturnCode`包含上次与FTP服务器通信时的返回代码。 - `ReturnMessage`包含上次与FTP服务器通信时的返回消息。 `Status()`方法返回(通过引用)FTP服务器的状态。 ## 命令的转换表 `%Net.FtpSession`在FTP服务器上查看文件名和路径名时,使用`RFC 2640`中介绍的技术自动处理字符集转换。当`%Net.FtpSession`的实例连接到FTP服务器时,它会使用Feat消息来确定服务器是否使用`UTF-8`字符。如果是,它将命令通道通信切换到`UTF-8`,以便所有文件名和路径名都可以正确地与`UTF-8`相互转换。 如果服务器不支持`FEAT`命令或未报告支持`UTF-8`,`%Net.FtpSession`实例将使用`RAW`模式并读取或写入RAW字节。 在极少数情况下,如果需要指定要使用的转换表,请设置`%Net.FtpSession`实例的`CommandTranslateTable`属性。一般情况下,应该没有必要使用此属性。 # FTP文件和系统方法 一旦建立了FTP会话,就可以调用会话实例的方法来执行FTP任务。`%Net.FtpSession`提供以下读写文件的方法: ### Delete() 删除文件。 ### Retrieve() 将文件从FTP服务器复制到InterSystems IRIS流中,并通过引用返回该流。要使用此流,请使用标准流方法:`Write()`、`WriteLine()`、`Read()`、`ReadLine()`、`Rewind()`、`MoveToEnd()`和`Clear()`。还可以使用流的`Size`属性。 ### RetryRetrieve() 允许继续检索文件,因为给定的流是由上一次使用`Retrieve()`创建的。 ### Store() 将 IRIS流的内容写入FTP服务器上的文件。 ### Append() 将流的内容追加到指定文件的末尾。 ### Rename() 重命名文件。 此外,`%Net.FtpSession`提供了导航和修改FTP服务器上的文件系统的方法:`GetDirectory()`、`SetDirectory()`、`SetToParentDirectory()`和`MakeDirectory()`。 要检查文件系统的内容,请使用`list()`或`NameList()`方法。 - `List()`创建一个流,其中包含其名称与给定模式匹配的所有文件的列表,并通过引用返回该流。 - `NameList()`创建文件名数组并通过引用返回该数组。 还可以使用`ChangeUser()`方法更改为其他用户;这比注销并再次登录要快。使用`Logout()`方法注销。 `System()`方法返回(通过引用)有关托管FTP服务器的计算机类型的信息。 `Size()`和`MDTM()`方法分别返回文件的大小和修改时间。 使用通用`sendCommand()`方法向FTP服务器发送命令并读取响应。此方法可用于发送`%Net.FtpSession`中未明确支持的命令。 # 使用链接的流上载大文件 如果要上传大文件,请考虑使用流接口的`LinkToFile()`方法。也就是说,不是创建流并将文件读入其中,而是创建流并将其链接到文件。在调用`%Net.FtpSession`的`Store()`方法时使用此链接流。 ```java Method SendLargeFile(ftp As %Net.FtpSession, dir As %String, filename As %String) { Set filestream=##class(%FileBinaryStream).%New() Set sc=filestream.LinkToFile(dir_filename) If $$$ISERR(sc) {do $System.Status.DisplayError(sc) quit } //上传的文件将与原始文件同名 Set newname=filename Set sc=ftp.Store(newname,filestream) If $$$ISERR(sc) {do $System.Status.DisplayError(sc) quit } } ``` # 自定义FTP服务器发出的回调 可以自定义`FTP`服务器生成的回调。例如,通过这样做,可以向用户提供服务器仍在处理大型传输的指示,或允许用户中止传输。 要自定义FTP回调,请执行以下操作: 1. 创建`%Net.FtpCallback`的子类。 2. 在这个子类中,实现`RetrieveCallback()`方法,该方法在从FTP服务器接收数据时定期调用。 3. 还要实现`StoreCallback()`方法,在将数据写入FTP服务器时会定期调用该方法。 4. 创建`FTP`会话时(如“建立FTP会话”中所述),将回调属性设置为等于的子类`%Net.FtpCallback`。
文章
Weiwei Gu · 十一月 29, 2021

翻译文章: 不是所有的多模型数据库都是相同的

不是所有的多模型数据库都是相同的 作者:David Menninger 今天,许多现代应用程序需要的数据库管理能力往往不能通过一种方式就能实现。例如,当我建立一个支持旅游推荐和预订业务的应用程序时,我可能需要使用一些不同类型的数据库,包括用于用户会话的键值存储,用于产品目录的文档数据库,用于推荐的图形数据库,以及用于财务数据的关系数据库。由于各种原因,选择一个(关系型)数据库就可以了这种一刀切的数据库时代,已经离我们很远了。虽然关系型数据库仍然是许多应用的正确选择,但对于某些类型的应用,非关系型数据库提供了关系型数据库根本无法提供的优势。 关系型数据库用表和行表示数据,并使用结构化查询语言(SQL)来访问和操作数据。对于需要可靠性和ACID(原子性、一致性、隔离性和持久性)保证的事务性应用,以及需要SQL查询和报告的效率和简单性的应用,它们是一个很好的选择。 但是,关系型数据库是有代价的;它们需要数据库管理员,需要遵守预先定义的关系型结构,而且随着数据规模和工作负载的增加,它们的扩展也不经济。尽管如此,关系型数据库仍然是许多关键任务应用的正确选择,并继续为其提供动力。 相比之下,非关系型数据库,包括文档、对象、图形和键值数据库等,比关系型数据库具有某些优势--尤其是在灵活性和扩展性方面。非关系型数据库不需要DBA创建预先定义的模式,应用程序开发人员能够更容易地存储和管理数据,而不必担心映射固定的数据结构。 幸运的是,所有这些不同种类的数据库技术都是很成熟稳健的,也为应用开发者提供了丰富的功能,他们可以善加利用。 今天市场上有数以百计的数据库;非关系型数据库约占所有部署到生产中的数据库的一半。 但是,将多种数据库技术纳入一个应用程序并不总是那么简单。 多模型的一种方法--称为混合持久化--采用不同的数据库来支持每种类型的数据结构。 这是一种最佳的方法。 但是,在应用程序的整个生命周期中实施、同步和维护不同的数据库系统是复杂的,而且容易出错。 另一种方法是使用业界所称的多模型数据库;一种在同一 "产品 "中支持各种数据表示的数据库。但不是所有的多模型数据库都是一样的。有些数据库坚持混合范式,为不同的数据表示法采用多个独立的数据库引擎,造成数据的重复,并需要在不同的数据存储之间进行映射和整合。 还有一些支持引擎内的不同模型,但不支持相同的数据。 在InterSystems,我们已经开发了一种纯粹的多模型数据库管理方法。我们的产品,InterSystems IRIS数据平台存储了数据的单一表示。它利用了一个单一的数据库引擎,支持关系型和非关系型的数据访问和操作,没有重复。对于非关系型访问,它不需要预先定义的模式。它提供事务性的ACID保证,可以纵向和横向扩展,并支持本地、公共和私有云环境,以及混合(公共/公共、公共/私有、云/本地)部署环境。同样的数据可以使用SQL访问和操作,也可以作为文档、对象或键值数据。应用程序开发人员不需要使用多个数据库,也不需要在多个数据存储中整合和同步数据。 我们的多模型方法整合了关系型和非关系型技术的优点,而摒弃了其缺点,也没有与多角化持久性相关的复杂性或低效率。 所有这些都整合在我们从头开始建立的这个单一的数据库管理系统中了。 (David Menninger是Ventana Research的高级副总裁和研究总监,也是长期的行业老兵,他对多种数据表示的需求以及多模型数据库的各种方法的优势和劣势进行了深刻的分析。你可以在这里阅读他的报告。) https://www.intersystems.com/data-excellence-blog/not-all-multi-model-dbms-are-created-equal/
文章
Qiao Peng · 十月 20, 2022

在集成产品中压缩解压文件

InterSystems IRIS医疗版里有一个文件压缩解压的适配器HS.Util.Zip.Adapter和对应的文件压缩解压业务操作HS.Util.Zip.Operations。集成产品可以使用它们进行文件的压缩和解压操作。这2个类的联机文档说明较少,这里介绍它们的使用方法。 1. 基础配置 InterSystems IRIS使用操作系统的压缩和解压缩能力,因此需要注册操作系统执行压缩解压的命令。 在管理门户的Health标签页下,选中配置注册(Configuration Registry): 在其中增加2个注册项目: \ZipUtility\UnZipCommand 和\ZipUtility\ZipCommand,分别代表解压和压缩命令。适配器HS.Util.Zip.Adapter会检查这2个注册项并得到相应的命令。各个操作系统的命令并不一样,示例如下: \ZipUtility\UnZipCommand 解压缩命令 Windows "c:\program files\7-zip\7z" x %1 -o. -r 非Windows unzip %1 -d . \ZipUtility\ZipCommand 压缩命令 Windows "c:\program files\7-zip\7z" a %1 . -r 非Windows zip -rm %1 . 注意,其中要有%1,代表解压后的或压缩后的目标文件路径。 2. 适配器配置 可以直接使用业务操作HS.Util.Zip.Operations,无需自己开发业务操作类。这个系统提供的业务操作使用的就是适配器HS.Util.Zip.Adapter。 将业务操作HS.Util.Zip.Operations加入Production,配置其工作目录(WorkingDirectory),就是上面提到压缩/解压命令中%1代表的目录。注意,这个目录并非HS.Util.Zip.Operations输出的目标路径,它只是压缩/解压过程中用到的临时目录。这个业务操作执行压缩/解压后,会自动删除临时目录中的文件,而将目标文件流数据保存到响应消息里。 3. 调用压缩/解压业务操作 这个业务操作的请求消息是HS.Message.ZipRequest,响应消息是HS.Message.ZipResponse。 3.1 请求消息准备 HS.Message.ZipRequest里有如下属性: Operation:执行的操作,压缩或解压缩。可用值为"FromZip" - 解压缩,和"ToZip" 压缩。 File:解压文件的Stream数据,类型为%Stream.GlobalBinary。在执行文件解压时才需要设置该属性 - 需要将解压文件的Stream赋值给File属性。 Items: 文件项目列表,列表元素类型为HS.Types.ZipItem。在执行文件压缩时才需要设置该属性 - 需要将多个压缩文件的数据赋值给Items列表元素的以下属性: Filename:未来解压缩出来的目标文件名 Path:未来解压缩出来的目标文件子目录 File:需要压缩的文件流数据 3.2 处理响应消息 HS.Message.ZipResponse有如下属性: File:在执行文件压缩操作时,该属性保存压缩后的流数据。在执行解压操作时,该属性为空。 Items:文件项目列表,列表元素类型为HS.Types.ZipItem。在执行文件解压操作时,该列表属性的元素保护以下属性: Filename:解压缩出来的目标文件名 Path:解压缩出来的目标文件子目录 File:解压缩出来的流数据 4. 示例代码 以下是直接调用HS.Util.Zip.Operations的业务服务代码示例。 4.1 压缩示例 Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject) As %Status { //实例化请求消息 Set tReq = ##class(HS.Message.ZipRequest).%New() //设置源文件目录 Set tSrcFolder = "/Users/test/irishealth/mgr/Temp/" //设置请求消息为压缩操作 Set tReq.Operation = "ToZip" Set tFile = ##class(%Stream.FileBinary).%New() Set sc=tFile.LinkToFile(tTgtFolder_"output.zip") Set tReq.File = tFile //压缩的第一个文件 Set tItem1 = ##class(HS.Types.ZipItem).%New() //设置未来解压缩的文件名 Set tItem1.Filename = "test.png" //设置未来解压缩的子文件夹 Set tItem1.Path = "pic/" //打开目标文件,获取流数据 Set tFileSrc = ##class(%Stream.FileBinary).%New() Do tFileSrc.LinkToFile(tSrcFolder_tItem1.Filename) Do tItem1.File.CopyFrom(tFileSrc) kill tFileSrc //将压缩文件信息插入列表 Do tReq.Items.Insert(tItem1) //压缩的第二个文件 Set tItem2 = ##class(HS.Types.ZipItem).%New() //设置未来解压缩的文件名 Set tItem2.Filename = "HS.SDA3.xsd" //设置未来解压缩的子文件夹 Set tItem2.Path = "code/" //打开目标文件,获取流数据 Set tFileSrc = ##class(%Stream.FileBinary).%New() Do tFileSrc.LinkToFile(tSrcFolder_tItem2.Filename) Do tItem2.File.CopyFrom(tFileSrc) kill tFileSrc //将压缩文件信息插入列表 Do tReq.Items.Insert(tItem2) //调用业务操作 Do ..SendRequestSync("HS.Util.Zip.Operations",tReq,.tRes) //设置目标路径 Set tFolder = "/Users/test/irishealth/mgr/Temp/Target/" //保存压缩文件到目标路径 Set tTgtFile = ##class(%Stream.FileBinary).%New() Set tFullPath = tFolder_"output.zip" Set tTgtFile.Filename = tFullPath Set tSC = tTgtFile.CopyFrom(tRes.File) Set tSC= tTgtFile.%Save() Quit $$$OK } 4.2 解压缩示例 Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject) As %Status { //实例化请求消息 Set tReq = ##class(HS.Message.ZipRequest).%New() //设置请求消息为解压缩操作 Set tReq.Operation = "FromZip" //打开压缩文件,获取流数据 Set tFileSrc = ##class(%Stream.FileBinary).%New() Set sc=tFileSrc.LinkToFile("/Users/test/Downloads/TestCase.zip") //Set tReq.File = tFileSrc Do tReq.File.CopyFrom(tFileSrc) kill tFileSrc //调用业务操作 Do ..SendRequestSync("HS.Util.Zip.Operations",tReq,.tRes) //设置目标文件路径 Set tTgtFolder = "/Users/test/irishealth/mgr/Temp/" //获取解压后的数据,并保存到目标文件路径下 For i=1:1:tRes.Items.Count() { Set tFileItem = tRes.Items.GetAt(i) Set tSubFolder = tFileItem.Path Set tFileName = tFileItem.Filename Set tTgtFile = ##class(%Stream.FileBinary).%New() Set tFullPath = tTgtFolder_tSubFolder_tFileName Set tTgtFile.Filename = tFullPath Set tSC = tTgtFile.CopyFrom(tFileItem.File) Set tSC= tTgtFile.%Save() Kill tTgtFile } Quit $$$OK }
文章
姚 鑫 · 四月 30, 2021

第八章 解释SQL查询计划(二)

# 第八章 解释SQL查询计划(二) # SQL语句的详细信息 有两种方式显示SQL语句的详细信息: - 在SQL Statements选项卡中,通过单击左侧列中的Table/View/Procedure Name链接选择一个SQL Statement。 这将在单独的选项卡中显示SQL语句详细信息。 该界面允许打开多个选项卡进行比较。 它还提供了一个Query Test按钮,用于显示SQL Runtime Statistics页面。 - 从表的Catalog Details选项卡(或SQL Statements选项卡)中,通过单击右边列中的Statement Text链接选择一个SQL语句。 这将在弹出窗口中显示SQL语句详细信息。 可以使用“SQL语句详细信息”显示来查看查询计划,并冻结或解冻查询计划。 “SQL语句详细信息”提供冻结或解冻查询计划的按钮。 它还提供了一个Clear SQL Statistics按钮来清除性能统计,一个`Export`按钮来将一个或多个SQL语句导出到一个文件,以及一个`Refresh`和`Close`页面按钮。 SQL语句详细信息显示包含以下部分。 每个部分都可以通过选择部分标题旁边的箭头图标展开或折叠: - 语句详细信息,其中包括性能统计 - 编译设置 - 语句在以下例程中定义 - 语句使用如下关系 - 语句文本和查询计划(在其他地方描述) ## 声明的细节部分 - 语句散列Statement hash:语句定义的内部散列表示形式,用作SQL语句索引的键(仅供内部使用)。 有时,看起来相同的SQL语句可能具有不同的语句散列项。 需要生成不同SQL语句的代码的设置/选项的任何差异都会导致不同的语句散列。 这可能发生在支持不同内部优化的不同客户端版本或不同平台上。 - 时间戳`Timestamp`:最初,创建计划时的时间戳。 这个时间戳会在冻结/解冻之后更新,以记录计划解冻的时间,而不是重新编译计划的时间。 可能必须单击Refresh Page按钮来显示解冻时间戳。 将Plan Timestamp与包含该语句的例程/类的`datetime`值进行比较,可以知道,如果再次编译该例程/类,它是否使用了相同的查询计划。 - 版本Version:创建计划的InterSystems IRIS版本。 如果“计划”状态是“冻结/升级”,则这是InterSystems IRIS的早期版本。 解冻查询计划时,“计划”状态变为“解冻”,“版本”变为当前的InterSystems IRIS版本。 - 计划状态Plan state:冻结/显式、冻结/升级、解冻、解冻/并行。 Frozen/Explicit意味着该语句的计划已被显式用户操作冻结,无论生成此SQL语句的代码发生了什么变化,该冻结的计划都将是将要使用的查询计划。 冻结/升级意味着该语句的计划已被InterSystems IRIS版本升级自动冻结。 解冻意味着该计划目前处于解冻状态,可能被冻结。 Unfrozen/Parallel表示该计划被解冻,并使用`%Parallel`处理,因此不能被冻结。 `NULL`(空白)计划状态意味着没有关联的查询计划。 - 自然查询Natural query:一个布尔标志,指示该查询是否是“自然查询”。 如果勾选此项,则该查询是自然查询,不会记录查询性能统计信息。 如果不检查,性能统计可能会被记录; 其他因素决定了统计数据是否真正被记录下来。 自然查询被定义为嵌入式SQL查询,它非常简单,记录统计数据的开销会影响查询性能。 将统计信息保存在自然查询上没有任何好处,因为查询已经非常简单了。 一个很好的自然查询示例是`SELECT Name INTO:n FROM Table WHERE %ID=?` 这个查询的`WHERE`子句是一个相等条件。 此查询不涉及任何循环或任何索引引用。 动态SQL查询(缓存查询)不会被标记为自然查询; 缓存查询的统计数据可能被记录,也可能不被记录。 - 冻结计划不同Frozen plan different:冻结计划时,会显示该字段,显示冻结的计划与未冻结的计划是否不同。 冻结计划时,语句文本和查询计划将并排显示冻结的计划和未冻结的计划,以便进行比较。 本节还包括五个查询性能统计字段,将在下一节中进行描述。 ## 性能统计数据 执行查询会将性能统计数据添加到相应的SQL语句。 此信息可用于确定哪些查询执行得最慢,哪些查询执行得最多。 通过使用这些信息,您可以确定哪些查询将通过优化提供显著的好处。 除了SQL语句名称、计划状态、位置和文本之外,还为缓存查询提供了以下附加信息: - 计数Count:运行此查询次数的整数计数。 如果对该查询产生不同的查询计划(例如向表中添加索引),则将重置该计数。 - 平均计数Average count:每天运行此查询的平均次数。 - 总时间Total time:运行此查询所花费的时间(以秒为单位)。 - 平均时间Average time:运行此查询所花费的平均时间(以秒为单位)。 如果查询是缓存的查询,则查询的第一次执行所花费的时间很可能比从查询缓存中执行优化后的查询所花费的时间要多得多。 - 标准差Standard deviation:总时间和平均时间的标准差。 只运行一次的查询的标准偏差为0。 运行多次的查询通常比只运行几次的查询具有更低的标准偏差。 - 第一次看到的日期Date first seen:查询第一次运行(执行)的日期。 这可能与`Last Compile Time`不同,后者是准备查询的时间。 `UpdateSQLStats`任务会定期更新已完成的查询执行的查询性能统计数据。 这将最小化维护这些统计信息所涉及的开销。 因此,当前运行的查询不会出现在查询性能统计中。 最近完成的查询(大约在最近一个小时内)可能不会立即出现在查询性能统计中。 可以使用Clear SQL Statistics按钮清除这6个字段的值。 InterSystems IRIS不单独记录`%PARALLEL`子查询的性能统计数据。 %PARALLEL子查询统计信息与外部查询的统计信息相加。 由并行运行的实现生成的查询没有单独跟踪其性能统计信息。 InterSystems IRIS不记录“自然”查询的性能统计数据。 如果系统收集了统计信息,则会降低查询性能,而自然查询已经是最优的,因此没有进行优化的可能。 可以在“SQL语句”选项卡显示中查看多个SQL语句的查询性能统计信息。 您可以按任何列对SQL Statements选项卡列表进行排序。 这使得很容易确定,例如,哪个查询具有最大的平均时间。 还可以通过查询`INFORMATION.SCHEMA.STATEMENTS`类属性来访问这些查询性能统计数据,如查询SQL语句中所述。 ## 编译设置部分 - 选择模式`Select mode`:编译语句时使用的`SelectMode`。 对于DML命令,可以使用`#SQLCompile Select`; 默认为Logical。 如果`#SQLCompile Select=Runtime`,调用`$SYSTEM.SQL.Util.SetOption()`方法的`SelectMode`选项可以改变查询结果集的显示,但不会改变SelectMode值,它仍然是Runtime。 - 默认模式Default schema(s):编译语句时设置的默认模式名。 这通常是在发出命令时生效的默认模式,尽管SQL可能使用模式搜索路径(如果提供的话)而不是默认模式名来解析非限定名称的模式。 但是,如果该语句是嵌入式SQL中使用一个或多个`#Import`宏指令的DML命令,则`#Import`指令指定的模式将在这里列出。 - 模式路径Schema path:编译语句时定义的模式路径。 如果指定,这是模式搜索路径。 如果没有指定架构搜索路径,则此设置为空。 但是,对于在`#Import`宏指令中指定搜索路径的DML Embedded SQL命令,`#Import`搜索路径显示在默认模式设置中,并且该模式路径设置为空白。 - 计划错误Plan Error:该字段仅在使用冻结计划时发生错误时出现。 例如,如果一个查询计划使用一个索引,则该查询计划被冻结,然后该索引从表中删除,就会出现如下的计划错误:`Map 'NameIDX' not defined in table 'Sample.Person', but it was specified in the frozen plan for the query`. 删除或添加索引将导致重新编译表,从而更改“最后编译时间”值。 一旦导致错误的条件得到纠正,`Clear Error`按钮可用于清除`Plan Error`字段——例如,通过重新创建缺失的索引。 在错误条件被纠正后使用“清除错误”按钮会导致“计划错误”字段和“清除错误”按钮消失。 ## 例程和关系部分 语句在以下例程部分中定义: - 例程`Routine`:与缓存查询关联的类名(对于动态SQL DML),或者例程名(对于嵌入式SQL DML)。 - 类型:类方法或`MAC`例程(对于嵌入式SQL DML)。 - 上次编译时间`Last Compile Time`:例程的上次编译时间或准备时间。如果SQL语句解冻,重新编译MAC例程会同时更新此时间戳和Plan时间戳。如果SQL语句已冻结,则重新编译MAC例程仅更新此时间戳;在您解冻计划之前,Plan时间戳不会更改;然后Plan时间戳将显示计划解冻的时间。 语句使用以下关系部分列出了一个或多个用于创建查询计划的定义表。对于使用查询从另一个表提取值的`INSERT`,或者使用`FROM`子句引用另一个表的`UPDATE`或`DELETE`,这两个表都在此处列出。每个表都列出了下列值: - 表或视图名称`Table or View Name`:表或视图的限定名称。 - 类型`Type`:表或视图。 - 上次编译时间`Last Compile Time`:表(持久化类)上次编译的时间。 - `Classname`:与表关联的类名。 本节包括用于重新编译类的编译类选项。如果重新编译解冻计划,则所有三个时间字段都会更新。如果重新编译冻结的计划,则会更新两个上次编译时间字段,但不会更新计划时间戳。解冻计划并单击刷新页面按钮后,计划时间戳将更新为计划解冻的时间。 # 查询SQL语句 可以使用`SQLTableStatements()`存储查询返回指定表的SQL语句。下面的示例显示了这一点: ```java /// w ##class(PHA.TEST.SQL).SQLTableStatements() ClassMethod SQLTableStatements() { SET mycall = "CALL %Library.SQLTableStatements('Sample','Person')" SET tStatement = ##class(%SQL.Statement).%New() SET qStatus=tStatement.%Prepare(mycall) IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT} SET rset=tStatement.%Execute() IF rset.%SQLCODE '= 0 {WRITE "SQL error=",rset.%SQLCODE QUIT} DO rset.%Display() } ``` ```java DHC-APP>w ##class(PHA.TEST.SQL).SQLTableStatements() Dumping result #1 SCHEMA RELATION_NAME PLAN_STATE LOCATION STATEMENT SAMPLE PERSON 0 %sqlcq.DHCdAPP.cls228.1 DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , :%col(5) , :%col(6) , :%col(7) , :%col(8) , :%col(9) , :%col(10) , :%col(11) , :%col(12) , :%col(13) , :%col(14) , :%col(15) FROM SAMPLE . PERSON SAMPLE PERSON 0 Sample.Person.1 SELECT AGE , DOB , FAVORITECOLORS , HOME , NAME , OFFICE , SSN , SPOUSE , X__CLASSNAME , HOME_CITY , HOME_STATE , HOME_STREET , HOME_ZIP , OFFICE_CITY , OFFICE_STATE , OFFICE_STREET , OFFICE_ZIP INTO :%e ( ) FROM %IGNOREINDEX * SAMPLE . PERSON WHERE ID = :%rowid ... CURSOR FOR SELECT P . NAME , P . AGE , E . NAME , E . AGE FROM %ALLINDEX SAMPLE . PERSON AS P LEFT OUTER JOIN SAMPLE . EMPLOYEE AS E ON P . NAME = E . NAME WHERE P . AGE > 21 AND %NOINDEX E . AGE < 65 SAMPLE PERSON 0 PHA.TEST.SQL.1 SELECT NAME , SPOUSE INTO :name , :spouse FROM SAMPLE . PERSON WHERE SPOUSE IS NULL SAMPLE PERSON 0 PHA.TEST.ObjectScript.1 SELECT NAME , DOB , HOME INTO :n , :d , :h FROM SAMPLE . PERSON 70 Rows(s) Affected ``` 可以使用`INFORMATION_SCHEMA`包表来查询SQL语句列表。InterSystems IRIS支持以下类: - `INFORMATION_SCHEMA.STATEMENTS`:包含当前名称空间中的当前用户可以访问的SQL语句索引项。 - `INFORMATION_SCHEMA.STATEMENT_LOCATIONS`:包含调用SQL语句的每个例程位置:持久类名或缓存查询名。 - `INFORMATION_SCHEMA.STATEMENT_RELATIONS`:包含SQL语句使用的每个表或视图条目。 以下是使用这些类的一些示例查询: 下面的示例返回命名空间中的所有SQL语句,列出哈希值(唯一标识规范化SQL语句的计算ID)、冻结状态标志(值0到3)、准备语句和保存计划时的本地时间戳以及语句文本本身: ```sql SELECT Hash,Frozen,Timestamp,Statement FROM INFORMATION_SCHEMA.STATEMENTS ``` ![image](/sites/default/files/inline/images/1_42.png) 以下示例返回所有冻结计划的SQL语句,指示冻结的计划是否与未冻结的计划不同。请注意,解冻语句可以是`Frozen=0`或`Frozen=3`。不能冻结的单行INSERT等语句在冻结列中显示NULL: ```sql SELECT Frozen,FrozenDifferent,Timestamp,Statement FROM INFORMATION_SCHEMA.STATEMENTS WHERE Frozen=1 OR Frozen=2 ``` 以下示例返回给定SQL表的所有SQL语句和语句所在的例程。(请注意,指定表名(`SAMPLE.PERSON`)时必须使用与SQL语句文本中相同的字母大小写:全部大写字母): ```sql SELECT Statement,Frozen,STATEMENT_LOCATIONS->Location AS Routine,STATEMENT_LOCATIONS->Type AS RoutineType FROM INFORMATION_SCHEMA.STATEMENTS WHERE STATEMENT_RELATIONS->Relation='SAMPLE.PERSON' ``` ![image](/sites/default/files/inline/images/2_24.png) 以下示例返回当前命名空间中具有冻结计划的所有SQL语句: ```sql SELECT Statement,Frozen,Frozen_Different,STATEMENT_LOCATIONS->Location AS Routine,STATEMENT_LOCATIONS->Type AS RoutineType FROM INFORMATION_SCHEMA.STATEMENTS WHERE Frozen=1 OR Frozen=2 ``` 以下示例返回当前命名空间中包含`COUNT(*)`聚合函数的所有SQL语句。(请注意,指定语句文本(`COUNT(*)`)时必须使用与SQL语句文本相同的空格): ```sql SELECT Statement,Frozen,STATEMENT_LOCATIONS->Location AS Routine,STATEMENT_LOCATIONS->Type AS RoutineType FROM INFORMATION_SCHEMA.STATEMENTS WHERE Statement [ ' COUNT ( * ) ``` ![image](/sites/default/files/inline/images/3_20.png) # 导出和导入SQL语句 可以将SQL语句作为`XML`格式的文本文件导出或导入。这使可以将冻结的计划从一个位置移动到另一个位置。SQL语句导出和导入包括关联的查询计划。 可以导出单个SQL语句,也可以导出命名空间中的所有SQL语句。 可以导入先前导出的包含一个或多个SQL语句的XML文件。 注意:将SQL语句作为XML导入不应与从文本文件导入和执行SQL DDL代码混淆。 ## 导出SQL语句 导出单个SQL语句: - 使用SQL语句详细资料页导出按钮。在管理门户系统资源管理器SQL界面中,选择SQL语句选项卡,然后单击语句以打开SQL语句详细信息页。选择导出按钮。这将打开一个对话框,允许选择将文件导出到服务器(数据文件)或浏览器。 - 服务器(默认):输入导出`XML`文件的完整路径名。第一次导出时,此文件的默认名称为`statementexport.xml`。当然,可以指定不同的路径和文件名。成功导出SQL语句文件后,上次使用的文件名将成为默认值。 默认情况下,未选中在后台运行导出复选框。 - Browser:将文件`statementexport.xml`导出到用户默认浏览器中的新页面。可以为浏览器导出文件指定其他名称,或指定其他软件显示选项。 - 使用`$SYSTEM.SQL.Statement.ExportFrozenPlans()`方法。 导出命名空间中的所有SQL语句: - 使用管理门户中的导出所有对帐单操作。从管理门户系统资源管理器SQL界面中,选择操作下拉列表。从该列表中选择Export all Statements。这将打开一个对话框,允许您将命名空间中的所有SQL语句导出到服务器(数据文件)或浏览器。 - 服务器(默认):输入导出XML文件的完整路径名。第一次导出时,此文件的默认名称为`statementexport.xml`。当然,可以指定不同的路径和文件名。成功导出SQL语句文件后,上次使用的文件名将成为默认值。 默认情况下,在后台运行导出复选框处于选中状态。这是导出所有SQL语句时的建议设置。选中在后台运行导出时,系统会为提供一个查看后台列表页面的链接,可以在该页面中查看后台作业状态。 - Browser:将文件`statementexport.xml`导出到用户默认浏览器中的新页面。可以为浏览器导出文件指定其他名称,或指定其他软件显示选项。 使用`$SYSTEM.SQL.Statement.ExportAllFrozenPlans()`方法。 ## 导入SQL语句 从先前导出的文件导入一条或多条SQL语句: - 使用管理门户中的导入对帐单操作。从管理门户系统资源管理器SQL界面中,选择操作下拉列表。从该列表中选择Import Statements。这将打开一个对话框,允许指定导入XML文件的完整路径名。 默认情况下,在后台运行导入复选框处于选中状态。这是导入SQL语句文件时的推荐设置。选中在后台运行导入时,系统会为您提供一个查看后台列表页面的链接,可以在该页面中查看后台作业状态。 使用`$SYSTEM.SQL.Statement.ImportFrozenPlans()`方法。 ## 查看和清除后台任务 在管理门户系统操作选项中,选择后台任务,查看导出和导入后台任务的日志。可以使用清除日志按钮清除此日志。
文章
Michael Lei · 四月 13, 2022

用Globals 作为图数据库来存储和抽取图结构数据

这篇文章是对我的 iris-globals-graphDB 应用的介绍。在这篇文章中,我将演示如何在Python Flask Web 框架和PYVIS交互式网络可视化库的帮助下,将图形数据保存和抽取到InterSystems Globals中。 建议 阅读相关文档 使用 Globals 原生 SDK 介绍 PYVIS 互动式网络可视化库 第一步 : 通过使用Python 原生SDK建立与IRIS Globals的链接 #create and establish connection if not self.iris_connection: self.iris_connection = irisnative.createConnection("localhost", 1972, "USER", "superuser", "SYS") # Create an iris object self.iris_native = irisnative.createIris(self.iris_connection) return self.iris_native 第二步 : 使用 iris_native.set( ) 功能把数据保存到Globals 里 #import nodes data from csv file isdefined = self.iris_native.isDefined("^g1nodes") if isdefined == 0: with open("/opt/irisapp/misc/g1nodes.csv", newline='') as csvfile: reader = csv.DictReader(csvfile) for row in reader: self.iris_native.set(row["name"], "^g1nodes", row["id"]) #import edges data from csv file isdefined = self.iris_native.isDefined("^g1edges") if isdefined == 0: with open("/opt/irisapp/misc/g1edges.csv", newline='') as csvfile: reader = csv.DictReader(csvfile) counter = 0 for row in reader: counter = counter + 1 #Save data to globals self.iris_native.set(row["source"]+'-'+row["target"], "^g1edges", counter) 第三步: 使用iris_native.get() 功能把节点和边缘数据从Globals传递给PYVIS #Get nodes data for basic graph def get_g1nodes(self): iris = self.get_iris_native() leverl1_subscript_iter = iris.iterator("^g1nodes") result = [] # Iterate over all nodes forwards for level1_subscript, level1_value in leverl1_subscript_iter: #Get data from globals val = iris.get("^g1nodes",level1_subscript) element = {"id": level1_subscript, "label": val, "shape":"circle"} result.append(element) return result #Get edges data for basic graph def get_g1edges(self): iris = self.get_iris_native() leverl1_subscript_iter = iris.iterator("^g1edges") result = [] # Iterate over all nodes forwards for level1_subscript, level1_value in leverl1_subscript_iter: #Get data from globals val = iris.get("^g1edges",level1_subscript) element = {"from": int(val.rpartition('-')[0]), "to": int(val.rpartition('-')[2])} result.append(element) return result Step4: Use PYVIS Javascript to generate graph data <script type="text/javascript"> // initialize global variables. var edges; var nodes; var network; var container; var options, data; // This method is responsible for drawing the graph, returns the drawn network function drawGraph() { var container = document.getElementById('mynetwork'); let node = JSON.parse('{{ nodes | tojson }}'); let edge = JSON.parse('{{ edges | tojson }}'); // parsing and collecting nodes and edges from the python nodes = new vis.DataSet(node); edges = new vis.DataSet(edge); // adding nodes and edges to the graph data = {nodes: nodes, edges: edges}; var options = { "configure": { "enabled": true, "filter": [ "physics","nodes" ] }, "nodes": { "color": { "border": "rgba(233,180,56,1)", "background": "rgba(252,175,41,1)", "highlight": { "border": "rgba(38,137,233,1)", "background": "rgba(40,138,255,1)" }, "hover": { "border": "rgba(42,127,233,1)", "background": "rgba(42,126,255,1)" } }, "font": { "color": "rgba(255,255,255,1)" } }, "edges": { "color": { "inherit": true }, "smooth": { "enabled": false, "type": "continuous" } }, "interaction": { "dragNodes": true, "hideEdgesOnDrag": false, "hideNodesOnDrag": false, "navigationButtons": true, "hover": true }, "physics": { "barnesHut": { "avoidOverlap": 0, "centralGravity": 0.3, "damping": 0.09, "gravitationalConstant": -80000, "springConstant": 0.001, "springLength": 250 }, "enabled": true, "stabilization": { "enabled": true, "fit": true, "iterations": 1000, "onlyDynamicEdges": false, "updateInterval": 50 } } } // if this network requires displaying the configure window, // put it in its div options.configure["container"] = document.getElementById("config"); network = new vis.Network(container, data, options); return network; } drawGraph(); </script> 第五步: 从app.py 主文件调用上面的代码 #Mian route. (index) @app.route("/") def index(): #Establish connection and import data to globals irisglobal = IRISGLOBAL() irisglobal.import_g1_nodes_edges() irisglobal.import_g2_nodes_edges() #getting nodes data from globals nodes = irisglobal.get_g1nodes() #getting edges data from globals edges = irisglobal.get_g1edges() #To display graph with configuration pyvis = True return render_template('index.html', nodes = nodes,edges=edges,pyvis=pyvis) 下面是关于此项目的 介绍视频: 欢迎大家来我们的 Bilibili主页观看更多视频! 谢谢!
文章
姚 鑫 · 九月 30, 2021

第三十一章 SQL命令 DROP DATABASE

# 第三十一章 SQL命令 DROP DATABASE 删除数据库(命名空间)。 # 大纲 ```sql DROP DATABASE dbname [RETAIN_FILES] ``` ## 参数 - `dbname` - 要删除的数据库(命名空间)的名称。 - `RETAIN_FILES` - 可选-如果指定,则不会删除物理数据库文件(`IRIS.DAT`文件)。默认情况下,删除`.dat`文件以及命名空间和其他数据库实体。 # 描述 `DROP DATABASE`命令删除命名空间及其关联的数据库。 指定的`dbname`是包含相应数据库文件的命名空间和目录的名称。指定`dbname`作为标识符。命名空间名称不区分大小写。如果指定的`DBNAME`命名空间不存在, IRIS将发出`SQLCODE-340`错误。 `DROP DATABASE`命令是一个特权操作。 在使用`DROP DATABASE`之前,必须以`%Admin_Manage`资源的用户身份登录。 用户还必须拥有用于例程和全局数据库定义的资源的`READ`权限。 如果不这样做,将导致`SQLCODE -99`错误(权限冲突)。 使用`$SYSTEM.Security.Login()`方法为用户分配适当的权限: ```java DO $SYSTEM.Security.Login("_SYSTEM","SYS") &sql( ) ``` 必须具有`%Service_Login:Use`权限才能调用`$SYSTEM.Security.Login`方法。 不管权限如何,`DROP DATABASE`都不能用于删除系统命名空间。尝试这样做会导致`SQLCODE-342`错误。 `DROP DATABASE`不能用于删除当前正在使用或连接到的命名空间。尝试这样做会导致`SQLCODE-344`错误。 还可以使用管理门户删除命名空间。依次选择System Administration、Configuration、System Configuration、Namespaces以列出现有的`Namespace`。单击要删除的命名空间的删除按钮。 ## RETAIN_FILES 如果指定此选项,则保留物理文件结构;删除数据库及其关联的命名空间。执行此操作后,后续尝试使用`DBNAME`将导致以下结果: - `DROP DATABASE`不带`RETAIN_FILES`无法删除此物理文件结构。相反,它会导致`SQLCODE-340`错误(未找到数据库)。 - `DROP DATABASE WITH RETAIN_FILES`还会导致`SQLCODE-340`错误(找不到数据库)。 - `CREATE DATABASE`无法创建同名的新数据库。相反,它会导致`SQLCODE-341`错误(无法为数据库创建数据库文件)。 - 尝试使用此命名空间会导致``错误。 ## 服务器初始化和断开代码 服务器初始化代码和服务器断开代码可以通过`$SYSTEM.SQL.Util.SetOption("ServerInitCode",value)`和`$SYSTEM.SQL.Util.SetOption("ServerDisconnectCode",value)`方法分配给命名空间。 可以使用相应的`$SYSTEM.SQL.Util.GetOption()`方法选项来确定当前值。 使用`DROP DATABASE`或其他接口删除命名空间,将删除这些`Server Init Code`和`Server Disconnect Code`值。 因此,删除并重新创建名称空间需要重新指定这些值。 # 示例 ```sql CREATE DATABASE DocTestDB ON DIRECTORY 'c:\InterSystems\IRIS142\mgr\DocTestDB' ``` ```sql DROP DATABASE DocTestDB RETAIN_FILES ```
文章
姚 鑫 · 九月 23, 2022

第四十一章 使用多个 IRIS 实例(一)

# 第四十一章 使用多个 IRIS 实例(一) 可以在单个主机系统上安装和运行多个 `IRIS®` 数据平台实例。每个实例都是一个独特的、独立的 `IRIS` 环境。 # 管理 IRIS 实例 有许多方法可以连接和管理 `IRIS` 实例,它可能是安装在给定系统上的几种方法之一。两种最常用的方法如下: - 安装在 `Windows` 系统上的每个 `IRIS` 实例在系统托盘中都有自己的启动器,除其他选项外,还可以: - 通过打开管理门户、 `Terminal`和 `Studio` 开发者客户端连接到实例。 - 启动、停止和重新启动实例。 - 打开用户和开发人员文档。 从启动器中,还可以管理多个远程 `IRIS` 实例,包括但不限于运行远程备份、编辑配置设置以及创建和编译远程对象和例程。 - `iris command` 在操作系统命令行上执行 iris 命令可让管理访问 `IRIS` 实例,其中包括其他选项,可以: - 使用 `Terminal`连接到实例。 - 启动、停止和重新启动实例。 - 显示有关该实例以及系统上安装的其他实例的信息。 要在远程服务器上使用 `iris` 命令,请使用 `Telnet` 或 `SSH` 客户端;要将它与容器化实例一起使用,请在容器内使用它,或者使用 `docker exec` 命令从容器外部运行它。 # 连接到 `IRIS` 实例 `Terminal` 是一个命令行,可以在 `IRIS` 实例的任何命名空间中使用。使用命令 `iris terminal instname` 打开正在运行的实例的终端,其中 `instname` 是在安装时为实例指定的名称。容器化实例通常被命名为 `IRIS`。 使用在安装期间提供的密码或创建的帐户使用预定义的用户帐户之一登录。显示的提示指示登录命名空间,例如: ```java # iris terminal IRISHealth Node: intersystems2588, Instance: IRIS27 Username: admin Password: ******** USER> ``` 要退出终端并关闭窗口,请输入命令 `halt`。 当使用`docker exec` 命令打开容器化实例的终端时(如在部署和探索 `IRIS` 中使用 终端进行交互中所述),将自动以 `irisowner` 身份登录,无需进行身份验证。 在 `Windows` 系统上,必须从其位置( `IRIS` 实例的 `install-dir\bin` 目录)执行命令,或在命令中包含完整路径,例如 `c:\InterSystems\IRIS27\bin\iris terminal IRISHealth` .可以执行给定实例的二进制文件以连接到该实例或另一个;无论哪种方式,实例名称都是必需的。
文章
姚 鑫 · 二月 12, 2023

第七十四章 使用 irisstat 实用程序监控 IRIS - 查看 irisstat 输出

# 第七十四章 使用 irisstat 实用程序监控 IRIS - 查看 irisstat 输出 # 查看 `irisstat` 输出 可以立即查看 `irisstat` 数据(通过终端)或重定向到输出文件以供以后分析。查看数据的最常见方法是: 注意:当 `IRIS` 被强制关闭时,`irisstat` 会运行以捕获系统的当前状态。作为紧急关闭程序的一部分,输出被添加到消息日志中。 ## irisstat 文本文件 `irisstat` 报告可以重定向到文件而不是终端,如果想收集一组 `IRIS` 工具(诊断报告任务、`IRISHung` 脚本、`^SystemPerformance` 实用程序)未提供的一组 `irisstat` 选项,这可能很有用或者如果在运行这些工具时遇到问题。 ## 诊断报告任务 诊断报告任务会创建一个包含基本信息和高级信息的 HTML 日志文件,InterSystems 全球响应中心 (WRC) 可以使用该文件来解决系统问题。 注意:诊断报告任务不能在挂起的系统上运行;如果系统挂起,请参阅本附录中的 `IRISHung` 脚本。 ## IRISHung 脚本 `IRISHung` 脚本是一个操作系统工具,用于在 `IRIS` 实例挂起时收集系统数据。位于 `install-dir\bin` 目录中的脚本名称是特定于平台的,如下表中指定: Platform| Script name ---|--- `Microsoft Windows`| `IRISHung.cmd` `UNIX®/Linux` |`IRISHung.sh` `IRISHung` 脚本应以管理员权限运行。与诊断报告任务一样,`IRISHung` 脚本运行 `irisstat` 两次,间隔 `30` 秒,以防状态发生变化,并将报告与其他收集的数据一起打包到一个 `html` 文件中。从 `IRISHung` 获取的 `irisstat` 报告使用以下选项 ```java irisstat -e2 -f-1 -m-1 -n3 -j5 -g1 -L1 -u-1 -v1 -p-1 -c-1 -q1 -w2 -E-1 -N65535 ``` `IRISHung` 还运行仅使用 `-S2` 选项的第三个 `irisstat`,它将其写入一个单独的输出部分,称为“自诊断”。 `-S2` 选项导致可疑进程留下小型转储;因此,运行 `IRISHung` 可能会收集有关负责挂起的特定进程的信息,而简单地强制实例关闭不会收集此信息。 此外,`IRISHung` 生成的 `irisstat` 输出文件通常非常大,在这种情况下,它们被保存到单独的 `.txt` 文件中。请记住在收集输出时检查这些文件。 ## `^SystemPerformance Utility` `^SystemPerformance` 实用程序收集有关 `IRIS` 实例及其运行平台的详细性能数据。在 `IRIS` 内运行一段可配置的时间,在该时间间隔内收集样本,并在完成时生成报告。
文章
姚 鑫 · 十二月 31, 2023

第十一章 创建Callout Library - 使用 J 链接类型传递标准计数字符串

# 第十一章 创建Callout Library - 使用 J 链接类型传递标准计数字符串 # 使用 `J` 链接类型传递标准计数字符串 `iris-callin.h` 头文件定义了计数字符串结构 `IRIS_EXSTR`,表示标准 `IRIS` 字符串。此结构包含一个字符元素数组(`8` 位、`16` 位 `Unicode` 或 `32` 位 `wchar t`)和一个指定数组中元素数量的 `int` 值(最多字符串长度限制): ```java typedef struct { unsigned int len; /* length of string */ union { Callin_char_t *ch; /* text of the 8-bit string */ unsigned short *wch; /* text of the 16-bit string */ wchar_t *lch; /* text of the 32-bit string */ /* OR unsigned short *lch if 32-bit characters are not enabled */ } str; } IRIS_EXSTR, *IRIS_EXSTRP; ``` C Datatype| Input |In/Out |Notes ---|---|---|--- IRIS_EXSTR| 1j or j |1J or J| 8 位国家字符的标准字符串 IRIS_EXSTR |2j or n| 2J or N|16 位 Unicode 字符的标准字符串 IRIS_EXSTR| 4j |4J |32 位字符的标准字符串 wchar_t 字符 `IRIS_EXSTR` 数据结构由 `Callin API`(低级 `InterSystems` 函数调用库)中的函数进行操作。有关详细信息,请参阅使用 `Callin API` 中的“`Callin` 函数参考”。尽管名称相似,`Callin API` 和 `$ZF`标注接口是完全独立的产品)。 以下函数用于创建和销毁 `IRIS_EXSTR` 实例: - `IrisExStrNew[W][H`] — 为字符串分配请求的存储量,并使用长度和指向该结构的值字段的指针填充 `IRIS_EXSTR` 结构。 - `IrisExStrKill` — 释放与 `IRIS_EXSTR` 字符串关联的存储。 这是一个 `Callout` 库,它使用所有三种链接类型来返回数字字符串: ### 使用 `J` 连接传递字符串 以下三个函数均生成一个随机整数,将其转换为最多包含 `6` 位数字的数字字符串,并使用 `J` 链接返回字符串 。 ```java #define ZF_DLL // Required when creating a Callout library. #include #include #include #include int get_sample_L(IRIS_EXSTRP retval) { // 8-bit characters Callin_char_t numstr[6]; size_t len = 0; sprintf(numstr,"%d",(rand()%1000000)); len = strlen(numstr); IRISEXSTRKILL(retval); if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;} memcpy(retval->str.ch,numstr,len); // copy to retval->str.ch return ZF_SUCCESS; } int get_sample_LW(IRIS_EXSTRP retval) { // 16-bit characters unsigned short numstr[6]; size_t len = 0; swprintf(numstr,6,L"%d",(rand()%1000000)); len = wcslen(numstr); IRISEXSTRKILL(retval); if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;} memcpy(retval->str.wch,numstr,(len*sizeof(unsigned short))); // copy to retval->str.wch return ZF_SUCCESS; } int get_sample_LH(IRIS_EXSTRP retval) { // 32-bit characters wchar_t numstr[6]; size_t len = 0; swprintf(numstr,6,L"%d",(rand()%1000000)); len = wcslen(numstr); IRISEXSTRKILL(retval); if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;} memcpy(retval->str.lch,numstr,(len*sizeof(wchar_t))); // copy to retval->str.lch return ZF_SUCCESS; } ZFBEGIN ZFENTRY("GetSampleL","1J",get_sample_L) ZFENTRY("GetSampleLW","2J",get_sample_LW) ZFENTRY("GetSampleLH","4J",get_sample_LH) ZFEND ``` 注意:始终终止 `IRIS_EXSTRP` 输入参数 在前面的示例中,始终调用 `IRISEXSTRKILL(retval)` 以从内存中删除输入参数。即使参数不用于输出,也应该始终这样做。如果不这样做可能会导致内存泄漏。
文章
Qiao Peng · 一月 14, 2021

使用类投影安装 Caché 应用程序

![](/sites/default/files/inline/images/i2.png) 您好! 本文介绍另一种为基于 InterSystems Caché 的解决方案创建安装程序的简单方法。 主题将涵盖只需一项操作即可安装或从 Caché 中完全删除的应用程序。 如果您仍在编写需要执行多个步骤才能安装应用程序的安装说明,是时候将这个过程自动化了。 问题的提出 假设我们为 Caché 开发了一个小型实用程序,之后我们想要将其分发。 当然,最好不要让不必要的配置和安装细节打扰到安装它的用户。 此外,这些说明必须非常全面,而且要面向可能对 Caché 一无所知的用户。如果是 Web 实用程序,安装程序不仅会要求用户将其类导入 Caché,而且至少还要配置 Web 应用程序才能对其进行访问,这是相当大的工作量: ![](/sites/default/files/inline/images/p1.png) ![](/sites/default/files/inline/images/2016-05-05_221105.png) ![](/sites/default/files/inline/images/2016-05-05_221716.png) 当然,所有这些操作都可以通过编程方式执行。 您只需要了解如何实现。 但即使是这样,我们也需要让用户执行操作,例如在终端中执行一个命令。 通过单次导入操作进行安装 Caché 允许我们在类导入期间执行安装。 这意味着用户只需要使用任一方便的方法导入包含类包的 XML 文件: 将 XML 文件拖放到 Studio 区域。 通过管理门户:系统资源管理器 -> 类 -> 导入。 通过终端:do $system.OBJ.Load("C:\FileToImport.xml","ck")。 我们为安装应用程序而预先准备的代码将在类导入和编译后立即执行。 如果用户要卸载我们的应用程序(删除软件包),我们还可以清理应用程序在安装过程中创建的所有内容。 创建投影 为了扩展 Caché 编译器的行为,或者,在我们的示例中,为了在类的编译或反编译期间执行代码,我们需要在软件包中创建一个投影类。 它是一个扩展了 %Projection.AbstractProjection 的类,并重载了它的两个方法:CreateProjection(在编译过程中执行)和 RemoveProjection(在重新编译或删除类时触发)。 通常,将这个类命名为 Installer 是个好方法。 我们来看一个名为“MyPackage”的软件包的简单安装程序示例: Class MyPackage.Installer Extends %Projection.AbstractProjection [ CompileAfter = (Class1, Class2) ] {Projection Reference As Installer;/// This method is invoked when a class is compiled.ClassMethod CreateProjection(cls As %String, ByRef params) As %Status{    write !, "Installing..."}/// This method is invoked when a class is 'uncompiled'.ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status{    write !, "Uninstalling..."}} 这里的行为可以描述为: 第一次导入和编译软件包时,只触发 CreateProjection 方法。 以后再编译 MyApp.Installer 时,或者导入“新”的安装程序类覆盖“旧”类时,将触发旧类的 RemoveProjection 方法,且 %recompile 参数等于 1,之后调用新类的 CreateProjection 方法。 删除软件包(同时删除 MyApp.Installer)时,将只调用 RemoveProjection 方法,参数 recompile = 0。 还需要注意以下几点: 类关键字 CompileAfter 应该包括应用程序的类名列表,在执行投影类的方法之前,需要对它们进行编译。 始终建议在此列表中填入应用程序中的所有类,因为如果安装过程中出错,我们不需要执行投影类的代码; 两个方法都接受 cls 参数 - 它是顶级类名,在我们的示例中为 MyApp.Installer。 这个理念来自于创建投影类的本义 - 通过从派生自 %Projection.AbstractProjection 的类再派生,可以单独为我们的应用程序的任何类制作“安装程序”。 只有在这种情况下才会体现出意义,但对于我们的任务来说是多余的; CreateProjection 和 RemoveProjection 方法都接受第二个参数 params - 它是一个关联数组,以“参数名称”-“值”对的形式处理有关当前编译设置和当前类的参数值的信息。 通过执行 zwrite params 可以非常容易地探索该参数的内容; RemoveProjection 方法接受 recompile 参数,只有删除类时,该参数才等于 0,重新编译时不等于 0。 类 %Projection.AbstractProjection 还有其他方法,我们可以重新定义这些方法,但我们的任务并不需要这样做。 一个示例 让我们更深入地了解为我们的实用程序创建 Web 应用程序的任务,并创建一个简单示例。 假设我们的实用程序是一个 REST 应用程序,在浏览器中打开时,它只发送一个响应“I am installed!”。 要创建这样的应用程序,我们需要创建一个描述它的类: Class MyPackage.REST Extends %CSP.REST{XData UrlMap{    }ClassMethod Index() As %Status{    write "I am installed!"    return $$$OK}} 创建并编译该类后,我们需要将其注册为 Web 应用程序入口点。 我在本文的顶部图示了如何进行配置。 在执行所有这些步骤后,最好通过访问 http://localhost:57772/myWebApp/ 来检查我们的应用程序是否正常工作(注意以下几点:1. 末尾的斜线是必需的;2. 端口 57772 在您的系统中可能有所不同。 它将匹配您的管理门户端口)。 当然,所有这些步骤都可以通过 Web 应用程序创建方法 CreateProjection 以及删除方法 RemoveProjection 中的一些代码来自动执行。 在这种情况下,我们的投影类如下所示: Class MyPackage.Installer Extends %Projection.AbstractProjection [ CompileAfter = MyPackage.REST ]{Projection Reference As Installer;Parameter WebAppName As %String = "/myWebApp";Parameter DispatchClass As %String = "MyPackage.REST";ClassMethod CreateProjection(cls As %String, ByRef params) As %Status{    set currentNamespace = $Namespace    write !, "Changing namespace to %SYS..."    znspace "%SYS" // we need to change the namespace to %SYS, as Security.Applications class exists only there    write !, "Configuring WEB application..."    set cspProperties("AutheEnabled") = $$$AutheUnauthenticated // public application    set cspProperties("NameSpace") = currentNamespace // web-application for the namespace we import classes to    set cspProperties("Description") = "A test WEB application." // web-application description    set cspProperties("IsNameSpaceDefault") = $$$NO // this application is not the default application for the namespace    set cspProperties("DispatchClass") = ..#DispatchClass // the class we created before that handles the requests    return ##class(Security.Applications).Create(..#WebAppName, .cspProperties)}ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status{    write !, "Changing namespace to %SYS..."    znspace "%SYS"    write !, "Deleting WEB application..."    return ##class(Security.Applications).Delete(..#WebAppName)}} 在此示例中,每次编译 MyPackage.Installer 类都将创建一个 Web 应用程序,每次“反编译”都将其删除。 最好在创建或删除应用程序之前检查该应用程序是否存在(例如,使用 ##class(Security.Applications).Exists(“Name”) ),但是为了使本示例简单起见,这个作业就留给阅读本文的读者来完成了。 在创建 MyPackage.REST 和 MyPackage.Installer 类之后,我们可以将这些类导出为一个 XML 文件,并将该文件分享给所有有需要的人。 导入此 XML 的用户将自动设置 Web 应用程序,然后可以开始在浏览器中使用。 结果 与 InterSystems 社区上介绍的使用 %Installer 类部署应用程序的方法不同,这种方法有以下优点: 使用“纯”Caché ObjectScript。 至于 %Installer,需要用特定标签来填充 xData 块,大量文档对此进行了介绍。 安装我们的应用程序的方法是在类编译后立即执行的,我们不需要手动执行; 如果类(包)被删除,将自动执行删除我们的应用程序的方法,这不能通过使用 %Installer 来实现。 我的项目中已经使用这种应用程序安装方法 - Caché WEB Terminal、Caché Class Explorer 和 Caché Visual Editor。 您可以在此处找到 Installer 类的示例。 顺便提一下,开发者社区还有一个帖子介绍了投影的功能,作者是 John Murray。 另外值得一提的是 Package Manager 项目,该项目旨在让 InterSystems 数据平台的第三方应用程序只需通过一个命令或一次点击即可安装,就像类似 npm 的包管理器一样。