清除过滤器
文章
Claire Zheng · 八月 17, 2021
在医疗行业要实现互操作,应该要达到语义级别。只有达到语义级别才能保障医疗信息的准确和医疗行为的安全。而要达到语义级别,我们需要基于标准。
这里标准特别多,我们把它称之为五位一体。标准有5个方面。
首先是词汇/术语标准,是相互通讯的健康医疗信息系统需要依赖于结构化的词汇术语、代码值集、分类系统来进行相应的表述。词汇和术语的标准就是表达健康概念的标准。例如我们在使用的世界卫生组织的ICD-10,当然还有其他的,比如SNOMED-CT等等。
第二是内容标准,是描述信息交换过程中间的数据内容和结构的标准。它还包括了通用数据的定义,例如我们熟悉的HL7 CDA、 HL7 V2、 C-CDA,这些都是内容标准。
第三是传输标准,定义了计算机系统、文档架构、临床模板、用户界面,数据的连接之间的交换的消息的格式,传输的方式等等。通过传输方式,可以来确定是通过推、还是拉的方式来进行数据的共享交换。在这个层面DICOM 、IHE都是传输的一个标准。
第四是隐私与安全的标准,也就是说我们要确定谁、什么时候、出于什么目的、可以使用哪种个人健康信息的权利,以及我们如何来保护这些健康信息的机密性、可用性、完整性的一些标准。美国有HIPPA,欧洲的GDPR,这些都是关于隐私和安全的。
第五是标识符的标准,标识符的标准是用来唯一标识患者、机构、医护技、设备等等的标准,例如说我们互联互通里面用到的OID。
注:本文根据InterSystems中国技术总监乔鹏演讲整理而成。
文章
姚 鑫 · 二月 19, 2021
# 第四十一章 Caché 变量大全 $ZSTORAGE 变量
包含进程的最大可用内存。
# 大纲
```
$ZSTORAGE
$ZS
```
# 描述
`$ZSTORAGE`包含`JOB`的进程私有内存的最大内存量(以`KB`为单位)。此内存可用于局部变量、堆栈和其他表。此内存限制不包括例程目标代码的空间。此内存根据需要分配给进程,例如在分配数组时。
一旦将此内存分配给进程,通常在该进程退出之前不会释放它。但是,当大量内存被使用(例如,大于32MB)然后被释放时,系统间IRIS会尝试在可能的情况下将释放的内存释放回操作系统。
还可以使用`$ZSTORAGE`设置最大内存大小。例如,以下语句将作业的最大进程专用内存设置为`524288 KB`:
```java
SET $ZSTORAGE=524288
```
更改`$ZSTORAGE`会更改`$STORAGE`特殊变量的初始值,该变量包含进程的当前可用内存(以字节为单位)。
`$ZSTORAGE`的最大值为`2147483647`。`$ZSTORAGE`默认值为`262144`。`$ZSTORAGE`的最小值为`128`。`$ZSTORAGE`值大于最大值或小于最小值会自动默认为最大值或最小值。`$ZSTORAGE`设置为整数值;InterSystems IRIS截断任何小数部分(如果指定)。
可以通过更改最大每进程内存(KB)系统配置设置来更改`$ZSTORAGE`默认值。在管理门户中,依次选择System Administration、Configuration、Systtem Configuration、Memory和Startup。可以根据需要增加每个进程的最大内存(KB),最大为2147483647 KB。更改每个进程的最大内存(KB)会更改后续启动的进程的`$ZSTORAGE`值;对当前进程的`$ZSTORAGE`值没有影响。


# 示例
以下示例将`$ZSTORAGE`设置为其最大值和最小值。尝试将`$ZSTORAGE`设置为小于最小值的值(16)时,会自动将`$ZSTORAGE`设置为其最小值(128):
```java
/// d ##class(PHA.TEST.SpecialVariables).ZS()
ClassMethod ZS()
{
SET $ZS=128
WRITE "minimum storage=",$ZS,!
SET $ZS=16
WRITE "less than minimum storage=",$ZS,!
SET $ZS=2147483647
WRITE "maximum storage=",$ZS,!
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZS()
minimum storage=128
less than minimum storage=128
maximum storage=2147483647
```
问题
j ay · 三月 22, 2023
1、默认samples空间没有Backup.General无法进行外部备份
2、%SYS空间有Backup.General
Backup.General怎么在其他空间里创建这个类 我没明白问题, 外部备份是在%sys命名空间执行的呀, 为什么要在在sample里执行备份呢? 1、我对这个cache不太理解,第一次做这个,我目前做的外部备份是:
前提:所有数据都是用sample命名空间进行添加的
a. 用sample命名空间,去连接cache,调用Backup.General ExternalFreeze进行冻结
b. 备份mgr目录下面sample数据库文件CACHE.DAT
c. 调用ExternalThaw解冻
问题:1.如果我用%SYS命名空间去冻结解冻,备份mgr目录下面sample数据库文件CACHE.DAT,数据是恢复不了的
2.日志journal备份后在重新写入恢复,cache会启动不了
我不知道这样的备份恢复流程对不对,能说一下正确的外部备份流程吗 冻结其实是控制系统的写进程,让它暂时挂起来,在数据库日志上做个标记。这些都不是那一个数据库比如sample的事情,是整个系统的操作。
你的问题1: 如果我用%SYS命名空间去冻结解冻,备份mgr目录下面sample数据库文件CACHE.DAT,数据是恢复不了的。 怎么恢复不了?那一步出错了?
问题2: 你是先恢复数据库, 再恢复日志的是吗?出的什么错误?
这样, 你先看看这两个链接, 如果在有问题, 我找我们的专家和你私聊。第2个帖子有点长,里面连冻结解冻的脚本都包括了, 很详细 ,:)
https://cn.community.intersystems.com/post/faq-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E7%B3%BB%E5%88%97-%E7%B3%BB%E7%BB%9F%E7%AE%A1%E7%90%86%E7%AF%87-%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E6%95%B0%E6%8D%AE%E5%BA%93%E5%A4%87%E4%BB%BD
https://community.intersystems.com/post/intersystems-data-platforms-and-performance-%E2%80%93-vm-backups-and-cach%C3%A9-freezethaw-scripts
文章
Michael Lei · 六月 7, 2023
你好社区!
今天我继续我的全球峰会活动,欢迎你们来到第二天。
上午的演讲专门针对 IRIS 数据平台(太棒了!)。
Scott Gnau 首先解释了为什么 IRIS 被称为 IRIS。我居然才知道!
吃完饭后,我们每个人都去参加我们注册的会议、研讨会和活动。
开发者社区展位所在的“技术交流”区域,一如既往地迅速爆满。
有人在谈论容器,它把很多人聚集在一起!
白天,社区版主会在展位前停下来询问发生了什么事并与我聊天,例如@Muhammad.Waseem 、@Dmitry.Maslennikov和@Guillaume.Rongier7183。
或者还有@John.Murray 和@David.Loveluck
或者我会在走廊里找到他们,比如@José.Pereira。
全球峰会的仙女教母@Maureen.Flaherty 也过来询问是否一切顺利。
@Raj.Singh5479 也拜访了我们。
@Guillaume.Rongier7183 照顾了最后一个在展台前停下的人。还看到了@Jim.Regan 。
在工作日结束时,我们能够享受轻松的时光。本届全球峰会以“Road to the races”为主题,在“Gulfstream Park”的“Carousel Club”举行。
我们都很开心!
这就是今天的全部……明天更多!
文章
姚 鑫 · 五月 16, 2021
# 第二章 使用%UnitTest进行单元测试
本教程的第二部分介绍了如何使用%UnitTest包对InterSystems IRIS代码进行单元测试。完成本教程的这一部分后,将能够:
- 解释`%UnitTest`包中三个主要类的角色。
- 列出基于`%UnitTest`包的单元测试类和方法的要求。
- 创建并执行方法的单元测试。
- 浏览`%UnitTest.Manager`创建的测试报告。
- 执行单元测试时,使用`%UnitTest.TestCase`方法初始化和还原数据库数据。
# 什么是%UnitTest?
`%UnitTest`包是一组为IRIS提供测试框架的类。在结构上,它类似于`xUnit`测试框架。`%UnitTest`为创建和执行以下各项的单元测试提供类和工具:
- 类和方法
- ObjectScript例程(routines)
- InterSystems SQL脚本
- Productions
# 创建和执行单元测试套件
以下是创建和执行一套单元测试的基本步骤:
1. 创建一个(或多个)包含要测试的方法的类。
2. 创建扩展`%UnitTest.TestCase`的测试类(或多个测试类)。
3. 将方法添加到将测试方法输出的测试类。在每个方法中至少使用一个断言(`AssertX`宏)。每个测试方法名称都以`Test`开头。
4. 将测试类导出到文件。
5. 打开终端并切换到包含要测试的类的名称空间。为`^UnitTestRoot`分配一个字符串,该字符串包含包含导出的测试类文件的目录的父目录的路径。
6. 在终端中,运行`%UnitTest.Manager.RunTest`,向其传递包含测试类文件的(子)目录的名称。
7. 查看测试报告。终端中的输出包括网页的URL,该网页以易于阅读的表格形式显示结果。
# %UnitTest类
此表描述了用于为InterSystems IRIS类和方法创建和执行单元测试的主要`%UnitTest`类。
- `TestCase` 扩展此类以创建包含测试方法的类。如果一个或多个`AssertX`方法返回`False`,则测试失败;否则测试通过。将使用关联的宏调用`AssertX`方法。这些方法和宏是:
- `AssertEqualsViaMacro`-如果表达式相等,则返回`TRUE`。使用`$$$AssertEquals`宏调用。
- `AssertNotEqualsViaMacro`-如果表达式不相等,则返回`TRUE`。使用`$$$AssertNotEquals`宏调用。
- `AssertStatusOKViaMacro`-如果返回的状态代码为1,则返回`TRUE`。使用`$$$AssertStatusOK`宏调用。
- `AssertStatusNotOKViaMacro`-如果返回的状态码为0,则返回`TRUE`。使用`$$$AssertStatusNotOK`宏调用。
- `AssertTrueViaMacro`-如果表达式为TRUE,则返回TRUE。使用`$$$AssertTrue`宏调用。
- `AssertNotTrueViaMacro`-如果表达式不为TRUE,则返回`TRUE`。使用`$$$AssertNotTrue`宏调用。
- `AssertFilesSameViaMacro`-如果两个文件相同,则返回`TRUE`。使用`$$$AssertFilesSame`宏调用。
- `LogMessage`-将日志消息写入`^UnitTestLog`全局。使用`$$$LogMessage`宏调用。
- 设置和拆除条件的方法包括:
- `OnBeforeOneTest`-紧接在测试类中的每个测试方法之前执行。
- `OnBeforeAllTests`-在测试类中的任何测试方法之前执行一次。
- `OnAfterOneTest`-在测试类中的每个测试方法之后立即执行。
- `OnAfterAllTests`-在测试类中的所有测试方法执行完毕后执行一次。
- `Manager` 使用此类启动测试。其方法包括:
- `RunTest` -在目录中执行一个测试或一组测试。
- `DebugRunTestCase`-执行一个测试或一组测试,而不加载或删除任何测试类。
- `Report` 定义报告执行一个测试或一组测试的结果的网页。
# 断言方法和宏
单元测试的主要测试操作来自`AssertX`方法及其关联宏。将直接调用宏来测试方法的输出。宏测试方法是否为给定的输入创建所需的输出。只要`AssertX`宏返回`FALSE`(或以错误结束),包含它的测试就会失败。
在创建代码时,请计划将创建的单元测试以测试代码。在这里的示例中,已经创建了一个名为`TestMe`的类,其中包含一个名为`Add`的方法。现在想测试一下新的`TestMe`类,看看它是否工作。
以下命令运行`AssertEquals`宏以测试`Add`方法的输入`(2,2)`是否等于`4`。
```java
Do $$$AssertEquals(##class(MyPackage.TestMe).Add(2,2),4, "Test Add(2,2)=4")
```
`AssertEquals`宏比较两个值并接受三个参数:
1. `##class(MyPackage.TestMe).Add(2,2)`-第一个值是以`2,2`作为输入进行测试的方法。
2. `4`-第二个值。
3. `"Test Add(2,2)=4"`-写在结果页上的文本说明。(此参数不影响测试。如果不包含测试描述,该类将使用求值的表达式创建一个测试描述。)
以下是用于测试对象是否正确保存的`AssertStatusOK`宏的示例。
```java
Do $$$AssertStatusOK(contact.%Save(),"Saving a Contact")
```
此`AssertStatusOk`宏计算方法返回的状态。如果为1,则测试通过。
1. `Contact.%Save`-返回状态代码的表达式。
2. `"Saving a Contact"` -文本说明。这是测试报告的文档。这不会影响测试。
# 创建要在示例中使用的类
要完成以下动手示例,请使用`Atelier`创建以下类:`MyPackage.TestMe`和`MyPackage.Contact`。
- `MyPackage.TestMe`
```java
Class MyPackage.TestMe Extends %RegisteredObject
{
ClassMethod Add(arg1 As %Integer, arg2 As %Integer) As %Integer
{
Return arg1 + arg2
}
ClassMethod CreateContact(name As %String, type As %String) As MyPackage.Contact
{
Set contact = ##class(MyPackage.Contact).%New()
Set contact.Name=name
Set contact.ContactType=type
Return contact
}
ClassMethod GetContactsByType(type As %String) As %ListOfObjects
{
Set list=##class(%Library.ResultSet).%New()
}
}
```
- `MyPackage.Contact`
```java
Class MyPackage.Contact Extends (%Persistent, %Populate, %XML.Adaptor)
{
/// 描述联系的性质:: Personal or Business
Property ContactType As %String(TRUNCATE = 1, VALUELIST = ",Business,Personal");
/// 表示联系人的姓名
Property Name As %String(POPSPEC = "Name()", TRUNCATE = 1) [ Required ];
Query ByContactType(type As %String) As %SQLQuery(CONTAINID = 1)
{
SELECT %ID FROM Contact
WHERE (ContactType = :type)
ORDER BY Name
}
Storage Default
{
%%CLASSNAME
ContactType
Name
^MyPackage.ContactD
ContactDefaultData
^MyPackage.ContactD
^MyPackage.ContactI
^MyPackage.ContactS
%Storage.Persistent
}
}
```
# 示例:创建并导出测试类
类`MyPackage.TestMe`包含一个名为`Add`的方法,该方法将两个整数相加。在此示例中,将创建并运行单元测试以检查`Add`方法是否正确地将两个整数相加。
创建将包含单元测试的测试类。以下是方法:
1. 使用Atelier在`MyPackage`包中创建名为`Tests`的新类。测试必须扩展`%UnitTest.TestCase`。
2. 添加以下名为`TestAdd`并编译测试的方法:
```java
Class MyPackage.Tests Extends %UnitTest.TestCase
{
Method TestAdd()
{
do $$$AssertEquals(##class(MyPackage.TestMe).Add(2,2),4, "Test Add(2,2)=4")
do $$$AssertNotEquals(##class(MyPackage.TestMe).Add(2,2),5,"Test Add(2,2)'=5")
}
}
```
3. 将类测试导出到单元测试目录中的XML文件。如果尚未创建测试目录,请创建一个。此示例使用 `C:\unittests\mytests\。
a. 在Atelier中,单击文件>导出。
b. 在“Atelier ”下,单击“旧版XML文件”。单击下一步
c. 选择项目`Test.cls`和`c:\unittests\mytests\` 目录。
d. 单击Finish(完成)。
e. Atelier将测试类导出到`C:\unittests\mytests\cls\MyPackage`。
注意,目录名(在本例中为`mytest`)是一套测试的名称,也是`^UnitTestRoot`指定的目录的子级。运行`Manager.RunTest(“mytest”)`运行存储在`mytest`目录中的所有测试。
注意:还可以将测试类导出为`.cls`文件,而不是`XML`文件。也可以简单地从Atelier工作区复制它们,而不是导出它们。
# [源码](https://download.csdn.net/download/yaoxin521123/18703118)
文章
姚 鑫 · 七月 5, 2021
[toc]
# 第二十八章 定制SAX解析器创建自定义内容处理程序
# 创建自定义内容处理程序
如果直接调用InterSystems IRIS SAX解析器,则可以根据自己的需要创建自定义内容处理程序。本节讨论以下主题:
- Overview
- 要在内容处理程序中自定义的方法的描述
- `%XML.SAX.Parser`类中解析方法的参数列表摘要
- 示例
## 创建自定义内容处理程序概述
要定制InterSystems IRIS SAX解析器导入和处理XML的方式,请创建并使用定制的SAX内容处理程序。具体地说,创建`%XML.SAX.ContentHandler`的子类。然后,在新类中,重写任何默认方法以执行所需的操作。在解析XML文档时使用新的内容处理程序作为参数;为此,需要使用`%XML.SAX.Parser`类的解析方法。
此操作如下图所示:

创建和使用自定义导入机制的过程如下:
1. 创建扩展`%XML.SAX.ContentHandler`的类。
2. 在该类中,包括希望覆盖的方法,并根据需要提供新定义。
3. 在使用`%XML.SAX.Parser`的分析方法之一(即`ParseFile()`、`ParseStream()`、`ParseString()`或`ParseURL()`)编写读取XML文档的类方法。
调用分析方法时,请将自定义内容处理程序指定为参数。
## SAX内容处理程序的可定制方法
`%XML.SAX.ContentHandler`类在特定时间自动执行某些方法。通过覆盖它们,您可以自定义内容处理程序的行为。
### 响应事件
`%XML.SAX.ContentHandle`类分析XML文件,并在它到达XML文件中的特定点时生成事件。根据事件的不同,会执行不同的方法。这些方法如下:
- `OnPostParse()` — 在XML解析完成时触发。
- `characters()` — 由字符数据触发。
- `comment()` — 注释触发
- `endCData()` —由CDATA部分的末尾触发。
- `endDocument()` —由文档结尾触发。
- `endDTD()` — 由DTD结束触发。
- `endElement()` —由元素的末尾触发。
- `endEntity()` — 由一个实体的终结触发。
- `endPrefixMapping()` — 由名称空间前缀映射的结束触发。
- `ignorableWhitespace()` — 由元素内容中的可忽略空格触发。
- `processingInstruction()` — 由XML处理指令触发。
- `skippedEntity()` — 被跳过的实体触发。
- `startCData()` —由CDATA部分的开头触发。
- `startDocument()` — 由文档的开头触发。
- `startDTD()` — 由DTD的开头触发。
- `startElement()` — 由元素的开始触发。
- `startEntity()` — 由一个实体的开始触发。
- `startPrefixMapping()` — 由名称空间前缀映射的开始触发。
默认情况下,这些方法是空的,可以在自定义内容处理程序中覆盖它们。
### 处理错误
`%XML.SAX.ContentHandler`类在遇到某些错误时也会执行方法:
- `error()` — 由可恢复的解析器错误触发。
- `fatalError()` — 由致命的XML解析错误触发。
- `warning()` — 由解析器警告通知触发。
默认情况下,这些方法为空,可以在自定义内容处理程序中重写它们。
### 计算事件掩码
当调用InterSystems IRIS SAX解析器(通过`%XML.SAX.Parser`类)时,可以指定一个掩码参数来指示哪些回调是感兴趣的。如果未指定掩码参数,解析器将调用内容处理程序的`Mask()`方法。此方法返回一个整数,该整数指定与内容处理程序的重写方法相对应的复合掩码。
例如,假设创建了一个自定义内容处理程序,其中包含`startElement()`和`endElement()`方法的新版本。在本例中,`Mask()`方法返回一个数值,该数值等于`$$$SAXSTARTELEMENT`和`$$$SAXENDELEMENT`,之和,这两个标志对应于这两个事件。如果没有为解析方法指定掩码参数,则解析器将调用内容处理程序的`Mask()`方法,因此只处理这两个事件。
### 其他有用的方法
`%XML.SAX.ContentHandler`类提供在特殊情况下有用的其他方法:
- `LocatePosition()`-通过引用返回两个参数,这两个参数指示解析的文档中的当前位置。第一个表示行号,第二个表示行偏移。
- `PushHandler()`-在堆栈上推送新的内容处理程序。SAX的所有后续回调都将转到这个新的内容处理程序,直到该处理程序完成处理。
如果在解析一种类型的文档时遇到想要以不同方式解析的一段XML,则可以使用此方法。在本例中,当检测到要以不同方式处理的段时,调用`PushHandler()`方法,该方法将创建一个新的内容处理程序实例。所有回调都会转到此内容处理程序,直到调用`PopHandler()`返回上一个内容处理程序。
- `PopHandler()`-返回堆栈上的上一个内容处理程序。
这些是`final`方法,不能重写。
## SAX解析方法的参数列表
要指定文档源,请使用`%XML.SAX.Parser`类的`ParseFile()`、`ParseStream()`、`ParseString()`或`ParseURL()`方法。在任何情况下,源文档都必须是格式良好的XML文档;也就是说,它必须遵守XML语法的基本规则。完整的参数列表按顺序如下:
1. pFilename, pStream, pString, or pURL — 文档源.
2. pHandler — 内容处理程序,它是`%XML.SAX.ContentHandler`类的实例。
3. pResolver — 分析源时使用的实体解析器。
4. pFlags — 用于控制SAX解析器执行的验证和处理的标志。
5. pMask — 用于指定XML源中感兴趣的项的掩码。通常不需要指定此参数,因为对于`%XML.SAX.Parser`的解析方法,默认掩码为`0`。这意味着解析器调用内容处理程序的`Mask()`方法。该方法通过检测(在编译期间)在事件处理程序中自定义的所有事件回调来计算掩码。只处理那些事件回调。
6. pSchemaSpec — 验证文档源所依据的架构规范。此参数是一个字符串,其中包含以逗号分隔的命名空间/URL对列表:
```
"namespace URL,namespace URL"
```
这里,`Namespace`是用于模式的XML名称空间,`URL`是提供模式文档位置的`URL`。名称空间和`URL`值之间有一个空格字符。
7. pHttpRequest (For the ParseURL() method only) — 这里,`Namespace`是用于模式的XML名称空间,URL是提供模式文档位置的URL。名称空间和URL值之间有一个空格字符。
8. pSSLConfiguration — 客户端`SSL/TLS`配置的配置名称。
注意:请注意,此参数列表与`%XML.TextReader`类的解析方法略有不同。有一点不同,`%XML.TextReader`不提供指定自定义内容处理程序的选项。
## SAX处理程序示例
想要一个文件中出现的所有XML元素的列表。要做到这一点,只需记录每个开始元素。那么这个过程是这样的:
1. 创建一个名为`MyApp.Handler`的类,它扩展`%XML.SAX.ContentHandler`:
```
Class MyApp.Handler Extends %XML.SAX.ContentHandler
{
}
```
2. 使用以下内容覆盖`startElement()`方法:
```
Class MyApp.MyHandler extends %XML.SAX.ContentHandler
{
// ...
Method startElement(uri as %String, localname as %String,
qname as %String, attrs as %List)
{
//we have found an element
write !,"Element: ",localname
}
}
```
3. 将一个类方法添加到读取和分析外部文件的Handler类:
```
Class MyApp.MyHandler extends %XML.SAX.ContentHandler
{
// ...
ClassMethod ReadFile(file as %String) as %Status
{
//create an instance of this class
set handler=..%New()
//parse the given file using this instance
set status=##class(%XML.SAX.Parser).ParseFile(file,handler)
//quit with status
quit status
}
}
```
请注意,这是一个类方法,因为它在应用程序中被调用以执行其处理。此方法执行以下操作:
1. 它创建内容处理程序对象的实例:
```
set handler=..%New()
```
2. 它在一个调用`%XML.SAX.Parser`的`ParseFile()`方法。这将验证并解析文档(由`fileName`指定),并调用内容处理程序对象的各种事件处理方法:
```
set status=##class(%XML.SAX.Parser).ParseFile(file,handler)
```
每次在解析器解析文档时发生事件(如开始或结束元素)时,解析器都会调用内容处理程序对象中的适当方法。在本例中,唯一被覆盖的方法是`startElement()`,它随后写出元素名称。对于其他事件,例如到达`End`元素,不会发生任何事情(默认行为)。
3. 当`ParseFile()`方法到达文件末尾时,它返回。处理程序对象超出作用域,并自动从内存中删除。
4. 在应用程序中的相应点,调用`ReadFile()`方法,将文件传递给解析:
```
Do ##class(Samples.MyHandler).ReadFile(filename)
```
其中,filename是正在读取的文件的路径。
例如,如果文件的内容如下:
```
Edwards,Angela U.
1980-04-19
K8134
Vail
94059
Uberoth,Wilma I.
Wells,George H.
```
则此示例的输出如下所示:
```
Element: Root
Element: Person
Element: Name
Element: DOB
Element: GroupID
Element: HomeAddress
Element: City
Element: Zip
Element: Doctors
Element: Doctor
Element: Name
Element: Doctor
Element: Name
```
# 使用HTTPS
`%XML.SAX.Parser`支持`HTTPS`。也就是说,可以使用此类执行以下操作:
- (对于`ParseURL()`)解析`HTTPS`位置提供的XML文档。
- (对于所有解析方法)解析`HTTPS`位置的实体。
在所有情况下,如果这些项目中的任何一个是`在HTTPS`位置上提供的,请执行以下操作:
1. 使用管理门户创建包含所需连接详细信息的`SSL/TLS`配置。这是一次性的步骤。
2. 调用`%XML.SAX.Parser`的适用解析方法时,请指定`pSSLConfiguration`参数。
默认情况下,InterSystems IRIS使用`Xerces`图元解析。`%XML.SAX.Parser`仅在以下情况下使用其自己的实体解析:
- `PSSLConfiguration`参数非空。
- 已配置代理服务器。
文章
Jingwei Wang · 十二月 23, 2021
正如你在我的简介中看到的,我在一所大学教书,我想分享我对IRIS(或之前的Caché)教学的看法。
已经有一段时间了,但我还记得在今年早些时候看到YURI MARX GOMES关于 "用InterSystems对象和SQL开发 "一系列课程。他对第1天、第2天和第3天的课程内容进行了简要的描述,并附有讲师Joel Solon的评论。我心想,分享我自己的经验可能会有趣。
在我真正写下我教给学生的东西之前,先简单介绍一下我的经历。
在我毕业拿到硕士学位回到大学工作后,我们系决定更新我们的课程,在普通课程中增加几门新课程。其中一门是 "后关系型数据库"。它是为研究生第一年的学生讲授的。开始时,它包括72小时的讲座和72小时的实践。现在变为秋季学期72小时的讲座和36小时的实践。
由于我是一名新员工,而且是一名年轻有为的员工,我被赋予了讲授这门新学科的职责。我感到惊讶和惊恐是不言而喻的。首先,我根本没有任何教学方面的实践经验。其次,我只有夏天的三个月时间来学习一项对我来说完全陌生的技术并准备课程的讲授。幸运的是,我已经知道了应该教授哪个数据库。这个数据库就是InterSystems Caché。
总之,我或多或少地准备好了,然后我亲爱的学生们的问题开始了。例如,为什么他们必须学习这个数据库,他们在哪里以及如何使用这个数据库,等等。由于当时是2010年,我还很年轻,没有经验,而且这方面的书也不多,我决定直接去找源头,即InterSystems。不知怎的,我最终与Solon先生交谈,他给了我一些很好的提示,还把我介绍给了Evgeny Shvarov。从那时起,一切都变得更加容易和清晰了。
在接下来的几年里,我根据现代的趋势和要求,对我的课程内容做了相当多的改变。在开始的时候,为了展示如何从其他应用程序中利用这个数据库,我使用了Java绑定和.NET管理提供者。后来出现了eXTreme for Java,然后是eXTreme for .NET。ZEN在一两年后被教授和遗忘。现在是RESTful和SOAP服务以及CSP。很多东西都变了,但核心部分几乎是一样的。
既然我们在这里讨论的是讲授硕士水平的课程,我有很多的期望。首先,我希望我的学生知道面向对象的范式,并且能够绘制正确的UML类图。其次,我希望他们了解关系型数据库(包括索引、键等)和SQL。最后,他们应该至少有一个简单的网络开发的知识。
考虑到所有的先决条件,以下是我的教学大纲的大纲:
第1节 后关系型数据库概述:面向对象,对象关系型,以及不同种类的NoSQL数据库,并有模型和例子的描述。多模型数据库和例子。
第2节 架构、结构和管理IRIS的基础知识,作为后关系型DBMS的示例,IRIS管理的基础知识 : 数据库和命名空间一起工作。语法、命令、变量、表达式,以及ObjectScript的一些功能。还有用户代码、异常和事务。
第3节 分层模型 : 列表和多维数组(以及globals)。处理列表和数组的函数。这里是第一个任务--设计一个有4个下标层次的global,并使用至少4个函数与数组一起工作,对数据进行处理。
第4节 对象模型: 类,分层,继承,参数,不同类型的属性(以及如何使用它们),不同类型的方法(使用计算属性的例子,单元测试,使用Populate创建测试数据,用户数据类型),参数(以及如何使用它们)。数据是如何存储的,以及如何设置存储。这里的任务包括设计一个具有不同类型属性的类图(引用、内置对象、关系、集合和流);创建这些类和每个类的几个对象,并在IDE中把所有东西连接起来;创建一个计算属性、一个用户数据类型、单元测试以检查所有约束条件是否工作(如最小或最大长度/值、必需属性、唯一属性等)和生成测试数据。
第5节 关系模型:对象模型和关系模型之间的相关性。嵌入式(简单语句和游标)和动态SQL。类查询(基于SQL和COS)。隐式连接。IDKEY与其附带的方法。这里的任务是将不同类型的SQL查询通过join和参数添加到之前的任务中的类中,创建一个触发器,将唯一的属性改为IDKEY,看看它是如何改变相关的globals的。
第6节。从IRIS外部访问数据 :CSP和关于它的一切。RESTful服务和客户端(处理JSON的类)。SOAP服务和客户端(处理XML的类)。这里的任务是从之前的任务中选择一个类,并创建一个CSP页面,列出所选类的所有对象,并给出编辑、创建和删除对象的功能。然后使用RESTful和SOAP服务做同样的事情(创建方法来返回所有对象、一个特定的对象、更新和删除一个特定的对象)。
课程安排就这样了。然而,现在有了嵌入式Python,我可能会改用它,因为所有关于ObjectScript语法的问题和评论都很扰乱我的神经。
当然,COVID-19和学校的封锁给整个工作方式带来了一些变化。我认为,这些变化是最好的。而且,这真的取决于事情的组织方式。例如,其中一个好处是,现在我可以向学生展示知道我在整个学期所讲的所有内容的有用性。
一般来说,在学期末,他们有一份作业,来检查他们对课程主要部分的知识的掌握。以前,他们在课堂上写作业,我可以监督他们。之后,由于大流行病的发生非常意外,我不得不随机应变,为每个学生提供一套独特的任务。不幸的是,在那个时候,没有现成的软件,所以我决定写一个简单的网络门户来处理这个作业。由于现在已经过去了将近两年,我的简单门户变成了一个RESTful服务,从不同的集合中随机选择问题,将它们分配给学生,并接收答案。所有这些都非常现代化,而且易于使用(和编写) .
现在轮到了我现在的学生来写这个作业(使这个社区里有一些乐趣),你可以参加测试并发送你的答案, 当然,问题将与我的学生的问题不同(而且更容易)。我将使用InterSystems学习实验室的服务器,所以你只有几天的时间来做这件事并检查你的知识。
我在问题中使用的类图如下:
要获得这些问题,只需向以下地址发送一个带有你姓名的GET请求:
http://52773-1-e5a0b608.labs.learning.intersystems.com/community/task/%3CYour_name>
你就会得到你的4套问题:
要发送答案,只需向以下地址发送一个POST请求:
http://52773-1-e5a0b608.labs.learning.intersystems.com/community/answer/<Your_name>
在body中填写一个JSON:
{
"Answer1": "answer 1",
"Answer2": "answer 2",
"Answer3": "answer 3",
"Answer4": "answer 4"
}
我会在某个时候检查你的答案(因为有这个讨厌的东西叫做时差),你可以通过向以下地址发送GET请求来获得你的结果:
http://52773-1-e5a0b608.labs.learning.intersystems.com/controlwork/marks/<Your_name>
如果分数是空的,说明我还没有时间去检查(或者我还没有起床)。分数如下。
0 分意味着答案是完全错误的。
1 分意味着答案或多或少是正确的。
2 分意味着答案是正确的。
第一个发送答案的人将得到我的一个虚拟拥抱(或者一个真正的拥抱,如果我们见面的话)
总之,如果你对我的课程有任何意见或问题,不要犹豫,请在评论区写出来。
查看原帖 由 @Irene.Mikhaylova 撰写 第1天、第2天和第3天的课程,这三篇文章都有中文的,请换成中文社区链接,谢谢!
文章
姚 鑫 · 十二月 15, 2022
# 第十四章 使用系统监视器 - 跟踪系统监视器通知
通常,应调查任何系统监视器警报(严重性 `2` 的通知)或系统监视器警告序列(严重性 `1`)。 `Health Monitor` 还可以生成系统监视器警报和警告。
系统监视器警报、警告和状态消息(严重性 `0`)被写入消息日志 (`install-dir\mgr\messages.log`)。 (所有系统监视器和健康监视器状态消息都写入系统监视器日志,`install-dir\mgr\SystemMonitor.log`。应用程序监视器警报不会写入日志,但可以通过电子邮件发送或传递给指定的通知方法。 )
要跟踪系统监视器警报和警告,可以执行以下操作:
- 使用 `^%SYSMONMGR` 实用程序查看系统监视器警报。此选项允许显示所有传感器或特定传感器的警报,并查看所有记录的警报或仅查看在指定时间段内发生的警报,但它不显示警告。
- 监控消息日志。请记住,当在短时间内为给定传感器生成一系列系统监视器警报时,只有第一个会写入消息日志。
注意:在消息日志中,`System Monitor` 状态通知以首字母大写标记,例如 `[System Monitor]` 以 `%SYS` 开头,而 `warnings`、`alerts` 和 `OK` 消息则以大写标记,例如 `[SYSTEM MONITOR] CPUusage Warning: CPUusage = 90`(警告值为 `85`)。
配置 `Log Monitor` 以发送消息日志中出现的警报(和可选警告)的电子邮件通知(而不是将它们写入警报日志,这是默认设置)。当依赖此方法时,请记住日志监视器不会为配置严重性的每个消息日志条目生成通知;当在大约一个小时内有来自给定进程(例如系统监视器)的一系列条目时,仅针对第一个条目生成通知。例如,如果网络问题导致在 `15` 分钟内生成多个有关 `ECP` 连接和打开事务的系统监视器警报,则日志监视器仅生成一个通知(针对第一个警报)。因此,在从日志监视器收到单个系统监视器通知后,应该立即查看系统监视器警报并查阅消息日志。
# 系统监视器状态和资源指标
下表列出了系统监视器采样的系统状态和资源使用指标,以及导致警告(严重性 `1`)、警报(严重性 `2`)和“状态正常”(严重性 `0`)通知的每个通知阈值和规则。
Metric| Description| Notification Rules
---|---|---
`Disk Space`| 数据库目录中的可用空间 | < 250MB — warning < 50MB — alert > 250MB (after warning/alert) — OK
Journal Space| 日志目录中的可用空间 |< 250MB — warning < 50MB — alert > 250MB (after warning/alert) — OK
Paging| 已用物理内存和分页空间的百分比 |paging space > 30% — warning (physical memory > 96%) + (paging space > 50%) — alert
Lock Table| 正在使用的锁表的百分比 |> 85% — warning > 95% — alert < 85% after warning/alert — OK
write daemon| 写守护进程的状态 |写入守护进程处于唤醒状态并正在处理其(非空)队列,但在一个周期中的运行时间比配置的写入守护进程周期时间(默认 80 秒)长至少 10 秒 - alert 写入守护进程在警报后完成传递 - OK
ECP Connections| 与 ECP 应用程序服务器或 ECP 数据服务器的连接状态|状态至少有五 (5) 秒出现故障 — 警报
Shared Memory Heap (Generic Memory Heap)| 共享内存堆(SMH)的状态,也称为通用内存堆(gmheap)| SMH (gmheap) status 1 — warning SMH (gmheap) status 2 — alert
Open Transactions| 最长打开本地或远程 (ECP) 事务的持续时间 |> 10 minutes — warning > 20 minutes — alert
License Expiration| 许可证到期前的天数 | 7 days — warning 5 days or fewer — alert (daily)
SSL/TLS Certificate Expiration| 证书过期前的天数|个人证书将在 30 天内到期 - 警告(每天重复) 一个或多个每日过期证书警告 - 警报(警告摘要,每天一个)
ISCAgent (mirror members only)| ISCAgent status |无响应
问题
hongyuan du · 三月 15, 2022
无法下载老版本的cache,请问哪位可以分享下安装包 只有现有Cache客户可以下载,如果是我们客户欢迎访问wrc.intersystems.com 下载,谢谢!
文章
姚 鑫 · 七月 22, 2021
# 第九章 触发器定义
描述触发器定义的结构。
# 介绍
触发器是在SQL中发生特定事件时执行的代码段。InterSystems IRIS支持基于执行`INSERT`、`UPDATE`和`DELETE`命令的触发器。根据触发器定义,指定的代码将在相关命令执行之前或之后立即执行。每个事件可以有多个触发器,只要它们被分配了执行顺序。
可以向持久类添加触发器定义。它们在其他类中没有意义。
# 详情
触发器定义具有以下结构:
```java
/// description
Trigger name [ keyword_list ]
{ implementation }
```
- `description` 描述(可选)旨在显示在“类参考”中。默认情况下,描述为空白。
- `name`(必需)是触发器的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。
- `keyword_list`(必需)是以逗号分隔的关键字列表,用于进一步定义触发器。
- `implementation` 实现(必需)是零行或多行ObjectScript代码,用于定义触发触发器时要执行的代码。
# 示例
```java
/// 此触发器在每次插入后更新日志表
Trigger LogEvent [ Event = INSERT, Time = AFTER ]
{
// 获取插入行的行id
NEW id
SET id = {ID}
// 将值插入日志表
&sql(INSERT INTO LogTable (TableName, IDValue) VALUES ('MyApp.Person', :id))
}
```
# 第十章 扩展数据块
描述XData块的结构。
# 介绍
`XData`块是包含在类定义中的命名数据单元,通常由类中的方法使用。最常见的情况是,它是一个XML文档,但是它可以由其他形式的数据组成,例如`JSON`或`YAML`。
# 详情
`XData`块具有以下结构:
```java
/// description
XData name [ keyword_list ]
{
data
}
```
- `description` 描述(可选)旨在显示在“类别参考”中。默认情况下,描述为空白。
- `name`(必需)是`XData`块的名称。这必须是有效的类成员名称,并且不能与任何其他类成员名称冲突。
- `data` 数据(可选)包含扩展数据块的有效载荷。如果是XML,则它必须是格式良好的文档(只有一个根元素),开头没有XML声明。
- `keyword_list`(可选)是以逗号分隔的关键字列表,进一步定义了`XData`块。如果省略此列表,也要省略方括号。
# 示例
```java
Class Demo.CoffeeMakerRESTServer Extends %CSP.REST
{
Parameter HandleCorsRequest = 1
XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
}
```
# 第十一章 类关键字 - Abstract
指定这是否是抽象类。
# 用法
要将类标记为抽象类,请使用以下语法:
```java
Class MyApp.MyClass [ Abstract ]
{ //class members }
```
否则,省略此关键字或将单词Not放在关键字的前面。
# 详解
如果一个类是抽象的,就不能创建它的实例。
# 对子类的影响
此关键字不是继承的。
# 默认
如果省略这个关键字,这个类就不是抽象的。
# 第十二章 类关键字 - ClassType
指定此类的类型(或行为)。
# 用法
要指定类的类型(如果需要),请使用以下语法:
```java
Class MyApp.MyClass [ ClassType = classtype ]
{ //class members }
```
其中`classtype`是下列之一:
- `datatype` — 该类是一个数据类型类,用于表示文字值。
- `persistent` 持久—该类表示要存储在数据库中的数据。
- `serial` —该类表示要存储在另一个持久对象中(处于序列化状态)的数据。
- `stream` —该类表示流数据。
- `view`视图—该类用于定义一个SQL视图。
- `index` —该类是一个索引类,一个定义索引接口的专用类。
- 空字符串,表示此类没有特定类型。抽象类通常不指定类类型。
如果未指定此关键字,则类类型从主超类继承(如果有)。
请注意,`ClassType`是为`%RegisteredObject`、`%SerialObject`、`%Persistent`和数据类型类等系统类指定的,因此如果对这些类进行子类化,通常不需要指定此关键字。
# 详解
此关键字指定如何使用此类。类别编译器使用类别类型关键字来决定如何编译类别。例如,如果`ClassType`是持久性的,则类编译器还会调用存储编译器来为类生成持久性代码。除非明确定义,否则`ClassType`的值要么是默认值,要么是从主超类继承而来的。
对于持久性类,只有在标准持久性行为被重写时,才需要显式的`ClassType`语句。如果一个类定义包含这样的语句,要么是因为开发人员指定了它,要么是因为这个类起源于用旧版本的InterSystems IRIS开发的代码。
# 对子类的影响
这个关键字是从主超类继承的。子类可以覆盖关键字的值。
# 默认
如果省略此关键字,类类型将从主超类继承(如果有)。
注意:分片类的类类型不能有持久以外的任何值。
文章
姚 鑫 · 二月 27, 2021
# 第四十九章 Caché 变量大全 ^$ROUTINE 变量
提供例程信息。
# 大纲
```java
^$|nspace|ROUTINE(routine_name)
^$|nspace|R(routine_name)
```
## 参数
- `|nspace|`或`[nspace]` 可选-扩展SSVN引用,可以是显式名称空间名称,也可以是隐含名称空间。必须计算为带引号的字符串,该字符串括在方括号(`[“nspace”]`)或竖线(`|“nspace”|`)中。命名空间名称不区分大小写;它们以大写字母存储和显示。
- routine_name 计算结果为包含例程名称的字符串的表达式。
# 描述
可以将`^$ROUTINE`结构化系统变量用作`$DATA`、`$ORDER`和`$QUERY`函数的参数,以从当前命名空间(默认)或指定命名空间返回例程信息。`^$ROUTINE`返回有关例程的OBJ代码版本的例程信息。
在InterSystems ObjectScript中,一个例程有三个代码版本:MAC(用户编写的代码,可能包括宏预处理器语句)、INT(编译的MAC代码,用于执行宏预处理)和OBJ(可执行目标代码)。可以使用`^$ROUTINE global`返回关于int代码版本的信息。可以使用`^$ROUTINE`返回有关OBJ代码版本的信息。
# 参数
## nspace
此可选参数允许使用扩展SSVN引用在另一个命名空间中指定全局。可以显式地将命名空间名称指定为带引号的字符串文字或变量,也可以通过指定隐含的命名空间来指定。命名空间名称不区分大小写。可以使用方括号语法`[“user”]`或环境语法`|“user”|`。Nspace分隔符前后不允许有空格。
```java
WRITE ##class(%SYS.Namespace).Exists("USER"),! ; an existing namespace
WRITE ##class(%SYS.Namespace).Exists("LOSER") ; a non-existent namespace
```
可以使用`$NAMESPACE`特殊变量来确定当前名称空间。更改当前名称空间的首选方式是新建`$NAMESPACE`,然后设置`$NAMESPACE=“nspace ename”`。
## routine_name
计算结果为包含现有例程名称的字符串的表达式。例程名称在前255个字符内必须是唯一的;应避免超过220个字符。
# 示例
以下示例使用`^$`例程作为`$DATA`、`$ORDER`和`$QUERY`函数的参数。
## 作为$DATA的参数
`$DATA(^$|nspace|ROUTINE(routine_name))`
`^$ROUTINE`作为`$DATA`的参数将返回一个整数值,该整数值指定例程名OBJ代码版本是否作为`^$ROUTINE`中的节点存在。下表显示了`$DATA`可以返回的整数值。
Value| Meaning
---|---
0 |例程不存在
10 |例程存在
下面的Terminal示例测试myrou例程的OBJ代码版本是否存在。此示例假定在`USER`名称空间中有一个名为myrou的已编译MAC例程:
```java
USER>WRITE ^ROUTINE("myrou",0,"GENERATED") // INT code version exists
1
USER>WRITE $DATA(^$ROUTINE("myrou")) // OBJ code version exists
1
USER>KILL ^rOBJ("myrou") // Kills the OBJ code version
USER>DO ^myrou
DO ^myrou
^
*myrou
USER>WRITE ^ROUTINE("myrou",0,"GENERATED") // INT code version exists
1
USER>WRITE $DATA(^$ROUTINE("myrou")) // OBJ code version does not exist
0
USER>
```
## 作为`$ORDER`的参数
`$ORDER(^$|nspace|ROUTINE( routine_name),direction)`
`^$ROUTINE`作为`$ORDER`的参数,按整理顺序返回指定的例程名称的下一个或上一个例程名称。如果在`^$ROUTINE`中没有这样的例程名称作为节点存在,则`$ORDER`返回空字符串。
direction参数指定是否返回下一个或上一个例程名称:1 =下一个,-1 =上一个。如果不提供方向参数,则InterSystems IRIS将按整理顺序将下一个例程名称返回到指定的名称。
以下子例程搜索USER名称空间,并将例程名称存储在名为ROUTINE的本地数组中。
```java
/// d ##class(PHA.TEST.SpecialVariables).ROUTINE()
ClassMethod ROUTINE()
{
SET rname=""
FOR I=1:1 {
SET rname=$ORDER(^$|"USER"|ROUTINE(rname))
QUIT:rname=""
SET ROUTINE(I)=rname
WRITE !,"Routine name: ",rname
}
WRITE !,"All routines stored"
QUIT
}
```
```java
Routine name: INFORMATION.SCHEMA.TABLECONSTRAINTS.1
Routine name: INFORMATION.SCHEMA.TABLES.0
Routine name: INFORMATION.SCHEMA.TABLES.1
Routine name: INFORMATION.SCHEMA.TRIGGERS.0
Routine name: INFORMATION.SCHEMA.TRIGGERS.1
Routine name: INFORMATION.SCHEMA.VIEWCOLUMNUSAGE.0
Routine name: INFORMATION.SCHEMA.VIEWCOLUMNUSAGE.1
Routine name: INFORMATION.SCHEMA.VIEWS.0
Routine name: INFORMATION.SCHEMA.VIEWS.1
Routine name: INFORMATION.SCHEMA.VIEWTABLEUSAGE.0
Routine name: INFORMATION.SCHEMA.VIEWTABLEUSAGE.1
All routines stored
```
## 作为$QUERY的参数
`$QUERY(^$|nspace|ROUTINE(routine_name))`
`^$ROUTINE`作为`$QUERY`的参数,按整理顺序将下一个例程名称返回到指定的例程名称。指定的例程名称不必存在。如果以后在排序序列中没有例程名称,则`$QUERY(^$ROUTINE)`返回一个空字符串。
在下面的示例中,两个`$QUERY`函数在`USER`名称空间中指定例程名称之后返回下一个例程。
```java
/// d ##class(PHA.TEST.SpecialVariables).ROUTINE1()
ClassMethod ROUTINE1()
{
SET rname=""
WRITE !,"1st routine: ",$QUERY(^$|"USER"|ROUTINE(rname))
SET rname="%m"
WRITE !,"1st ",rname, " routine: ",$QUERY(^$|"USER"|ROUTINE(rname))
QUIT
}
```
```java
DHC-APP>d ##class(PHA.TEST.SpecialVariables).ROUTINE1()
1st routine: ^$|"USER"|ROUTINE("%APILIB")
1st %m routine: ^$|"USER"|ROUTINE("%mgw1")
``` 能够理解总结知识,但是结合到场景不知道如何使用。 知识讲的很仔细全面,GET了
文章
姚 鑫 · 九月 8, 2021
# 第十章 SQL命令 CREATE PROCEDURE(一)
创建作为`SQL`存储过程公开的方法或查询。
# 大纲
```sql
CREATE PROCEDURE procname(parameter_list)
[ characteristics ]
[ LANGUAGE SQL ]
BEGIN
code_body ;
END
CREATE PROCEDURE procname(parameter_list)
[ characteristics ]
LANGUAGE OBJECTSCRIPT
{ code_body }
CREATE PROCEDURE procname(parameter_list)
[ characteristics ]
LANGUAGE { JAVA | PYTHON | DOTNET } EXTERNAL NAME external-stored-procedure
CREATE PROC procname(parameter_list)
[ characteristics ]
[ LANGUAGE SQL ]
BEGIN
code_body ;
END
CREATE PROC procname(parameter_list)
[ characteristics ]
LANGUAGE OBJECTSCRIPT
{ code_body }
CREATE PROC procname(parameter_list)
[ characteristics ]
LANGUAGE { JAVA | PYTHON | DOTNET } EXTERNAL NAME external-stored-procedure
```
# 参数
- `procname` - 要在存储过程类中创建的过程的名称。
`procname`必须是一个有效的标识符。
过程名可以是限定的(`schema.procname`),也可以是非限定的(`procname`)。
非限定过程名接受默认模式名。
`procname`后面必须跟圆括号,即使没有指定参数。
- `parameter_list` - 可选——传递给过程的一个包含零个或多个参数的列表。
参数列表用圆括号括起来,列表中的参数用逗号分隔。
即使没有指定参数,括号也是必须的。
每个参数由(按顺序)组成:一个可选的 `IN`、`OUT`或`INOUT`关键字;
变量名;
数据类型;
和一个可选的`DEFAULT`子句。
- `characteristics` - 可选-一个或多个关键字,指定过程的特征。
在创建方法时,允许的关键字是`FINAL`、`FOR`、`PRIVATE`、`RETURNS`、`SELECTMODE`。
在创建查询时,允许的关键字是`CONTAINID`、`FINAL`、`FOR`、`RESULTS`、`SELECTMODE`。可以指定特征关键字短语`RESULT SETS`、`DYNAMIC RESULT SETS`或`DYNAMIC RESULT SETS n`,其中`n`是整数。
这些短语是同义词;
`DYNAMIC`关键字和n整数为`no-ops`,提供兼容性。
多个特征由空格(一个空格或换行符)分隔。
特性可以以任何顺序指定。
- `LANGUAGE OBJECTSCRIPT`,`LANGUAGE SQL` - 可选-一个关键字子句,指定用于`code_body`的编程语言。
指定语言`OBJECTSCRIPT`(用于`OBJECTSCRIPT`)或语言`SQL`。
如果省略`LANGUAGE`子句,则默认为`SQL`。
- `LANGUAGE JAVA`,`LANGUAGE PYTHON`,`LANGUAGE DOTNET` - 可选-关键字子句,指定用于调用指定语言中的现有外部存储过程的编程语言。
被调用的例程必须是一个静态方法。
- `code_body` - 该程序的程序代码。`SQL`程序代码以`BEGIN`关键字开头,以`END`关键字结尾。
`code_body`中的每个完整`SQL`语句都以分号(`;`)结束。`ObjectScript`程序代码用花括号括起来。
ObjectScript代码行必须缩进。
# 描述
`CREATE PROCEDURE`语句创建一个方法或查询,该方法或查询将自动作为`SQL`存储过程公开。
存储过程可以由当前名称空间中的所有进程调用。
存储过程由子类继承。
- 如果使用`SQL`语言,则`code_body`必须包含`SELECT`语句,以便生成公开为存储过程的查询。
如果代码不包含`SELECT`语句,则`CREATE PROCEDURE`创建一个方法。
- 如果`LANGUAGE OBJECTSCRIPT`,则`code_body`必须调用`Execute()`和`Fetch()`方法,以生成公开为存储过程的查询。
它也可以调用`Close()`, `FetchRows()`和`GetInfo()`方法。
如果代码没有调用`Execute()`和`Fetch()`,则`CREATE PROCEDURE`创建一个方法。
默认情况下,`CREATE PROCEDURE`创建公开为存储过程的方法。
要创建未公开为存储过程的方法,请使用 `CREATE METHOD`或`CREATE FUNCTION`语句。
若要创建未公开为存储过程的查询,请使用`CREATE QUERY`语句。
通过指定`procedure`特征关键字,这些语句还可以用于创建作为存储过程公开的方法或查询。
为了创建一个过程,必须具有`GRANT`命令指定的`%CREATE_PROCEDURE`管理权限。
如果为具有已定义所有者的现有类创建过程,则必须作为该类的所有者登录。
否则,操作将失败,并出现`SQLCODE -99`错误。
如果类定义是已部署的类,则不能在类中创建过程。
此操作失败,并出现一个带有`%msg`的`SQLCODE -400`错误`Unable to execute DDL that modifies a deployed class: 'classname'`.
使用CALL语句执行存储过程。
# 参数
## procname
要创建为存储过程的方法或查询的名称。即使没有指定参数,`procname`后面也必须有括号。过程名可以采用以下任何形式:
- `Unqualified`不限定的:接受默认模式名。例如,`MedianAgeProc()`。
- `Qualified`:提供模式名称。例如,`Patient.MedianAgeProc()`。
- `Multilevel`多级:限定为一个或多个模式层,以并行相应的类包成员。
在这种情况下,`procname`可能只包含一个句点字符;
对应类方法名中的其他句点将被下划线字符替换。
在最低级别的类包成员之前指定句点。
例如,`%SYSTEM.SQL_GetROWID()`或`%SYS_PTools.StatsSQL_Export()`。
非限定的`procname`接受默认模式名。
可以使用`$SYSTEM.SQL.Schema.Default()`方法确定当前系统范围的默认模式名。
系统范围的初始默认模式名是`SQLUser`,它对应于类包名`User`。
注意,`FOR`特征(将在下面描述)覆盖了在`procname`中指定的类名。
如果已经存在具有此名称的过程,则操作将失败,并出现`SQLCODE -361`错误。
SQL使用`SQL procname`生成相应的类名。
该名称由与模式名对应的包名、点、`" proc "`和指定的过程名组成。
例如,如果非限定过程名`RandomLetter()`接受默认模式`SQLUser`,则产生的类名将是:`User.procRandomLetter()`。
SQL不允许指定只以字母大小写不同的`procname`。
指定一个只在字母大小写上与现有过程名不同的`procname`将导致`SQLCODE -400`错误。
如果指定的`procname`已经存在于当前命名空间中,系统将生成`SQLCODE -361`错误。
要确定指定的`procname`是否已经存在于当前命名空间中,请使用`$SYSTEM.SQL.Schema.ProcedureExists()`方法。
注意: `SQL`过程名称和 `TSQL`过程名称共享同一组名称。
因此,不能在同一命名空间中创建与`TSQL`过程同名的SQL过程。
尝试这样做会导致`SQLCODE -400`错误。
## parameter_list
用于将值传递给方法或查询的参数列表。
形参列表用圆括号括起来,列表中的形参声明用逗号分隔。
括号是必须的,即使没有指定参数。
列表中的每个参数声明由(按顺序)组成:
- 一个可选关键字,指定参数模式是`IN`(输入值)、`OUT`(输出值)还是`INOUT`(修改值)。如果省略,默认参数模式为`IN`。
- 参数名称。参数名称区分大小写。
- 参数的数据类型。
- 可选:默认值。
可以指定`DEFAULT`关键字后跟一个默认值;
`DEFAULT`关键字是可选的。
如果没有指定默认值,则假定默认值为`NULL`。
下面的示例创建了一个具有两个输入参数的存储过程,这两个参数都具有默认值。
一个输入参数指定可选的`DEFAULT`关键字,另一个输入参数忽略该关键字:
```sql
CREATE PROCEDURE AgeQuerySP(IN topnum INT DEFAULT 10,IN minage INT 20)
BEGIN
SELECT TOP :topnum Name,Age FROM Sample.Person
WHERE Age > :minage ;
END
```
下面的示例在功能上与上面的示例相同。可选的`DEFAULT`关键字省略:
```sql
CREATE PROCEDURE AgeQuerySP(IN topnum INT 10,IN minage INT 20)
BEGIN
SELECT TOP :topnum Name,Age FROM Sample.Person
WHERE Age > :minage ;
END
```
下面是这个过程中所有有效的`CALL`语句:
```sql
CALL AgeQuerySP(6,65); CALL AgeQuerySP(6); CALL AgeQuerySP(,65); CALL AgeQuerySP().
```

下面的示例创建了一个公开为具有三个参数的存储过程的方法:
```sql
CREATE PROCEDURE UpdatePaySP
(IN Salary INTEGER DEFAULT 0,
IN Name VARCHAR(50),
INOUT PayBracket VARCHAR(50) DEFAULT 'NULL')
BEGIN
UPDATE Sample.Employee SET Salary = :Salary
WHERE Name=:Name ;
END
```
存储过程不执行参数的自动格式转换。
例如,`ODBC`格式或`Display`格式的输入参数仍然保持该格式。
调用过程的代码和过程代码本身负责以适合应用程序的格式处理`IN/OUT`值,并执行任何必要的转换。
因为方法或查询是作为存储过程公开的,所以它使用过程上下文处理程序在过程及其调用方之间来回传递过程上下文。调用存储过程时,`%Library.SQLProcContext`类的对象在`%sqlcontext`变量中实例化。这用于在过程及其调用者(例如ODBC服务器)之间来回传递过程上下文。
`%sqlcontext`由几个属性组成,包括`Error`对象、`SQLCODE`错误状态、`SQL`行数和错误消息。
下面的示例显示了用于设置其中几个参数的值:
```sql
SET %sqlcontext.%SQLCODE=SQLCODE
SET %sqlcontext.%ROWCOUNT=%ROWCOUNT
SET %sqlcontext.%Message=%msg
```
`SQLCODE`和`%ROWCOUNT`的值是在执行SQL语句时自动设置的。
`%sqlcontext`对象在每次执行之前都被重置。
或者,也可以通过实例化`%SYSTEM`来建立错误上下文。
对象,并将其设置为`%sqlcontext.Error`。
文章
Hao Ma · 三月 26, 2021
目前,机器学习的使用过程仍然有些困难,需要消耗大量的数据科学家服务。 AutoML 技术的诞生便是为了帮助组织降低这种复杂性和对专业 ML 人员的依赖。
AutoML 允许用户指向一个数据集,选择感兴趣的主题(特征)并设置影响主题的变量(标签)。 接着,用户告知模型名称,然后基于机器学习创建预测或数据分类模型。
这不需要了解认知算法,因为 AutoML 会在内部执行市面上的主要算法,并指出预测或分类数据的最佳算法。
用户现在可以从经过训练的模型中指向新数据,轻松进行预测和分类。
新的 InterSystems IRIS Advanced Analytics 除了与非常著名的 H2O 或 DataRobot(作为附加选项)搭配使用之外,还具有 AutoML 引擎 IntegratedML。
AutoML 运算通过 SQL 命令执行,详见:https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=GIML
这就是 Sapphire 的用武之地。 它是 Open Exchange上的一个 Web 应用 (https://openexchange.intersystems.com/package/SAPPHIRE),允许连接到 IRIS Advanced Analytics,以可视方式创建和训练 IntegratedML 模型,抽象化在数据库命令行上交互的需求。
示例如下:
1) 按照 https://openexchange.intersystems.com/package/integratedml-demo-template 的说明运行 AutoML 环境。
2) 按照 https://openexchange.intersystems.com/package/SAPPHIRE 的说明运行 Sapphire 实例。
3) 访问 localhost: 8080(或在 docker 上配置的端口),输入问题数据,如下图所示:
.png)
4) 按 Save 按钮保存模型。 按 Test 按钮测试连接。
5) 在 AutoML 菜单顶部选择 Model Definition:
.png)
6) 在模型定义中,选择第 4 步中创建的问题,并按示例填写字段:
.png)
提示:要加载 IRIS 表,应填写 IRIS Schema 并点击 Get tables。
7) 点击 Save 按钮保存数据。 保存后,点击 Create Model 以在 IntegratedML 中创建 ML 模型。
8) 训练模型。 在 AutoML 菜单中选择 Train Model:
.png)
9) 选择模型,点击 Train Model,将模型训练到 IntegratedML。 示例:
.png)
10) 您的模型已经准备好预测新数据了!
文章
Claire Zheng · 三月 14, 2023
本文根据InterSystems中国技术总监乔鹏( @Peng.Qiao )的演讲“互联互通套件赋能数据利用与应用创新”整理而成。
IRIS医疗版互联互通套件的缘起与发展演进
来源HL7:正在到来的挑战
http://hl7.org/fhir/change.html
这是来自HL7官网上的一张图,描述了我们在医疗卫生行业面临的一些挑战,以及信息化建设在应对挑战中发挥的作用。当今,医疗卫生、生物学、信息技术有很强的融合趋势,加之社会变革带来的经济方面的需求,同时构成颠覆传统医疗卫生行业的因素。
这张图显示了从“被动医疗”转向“主动医疗”过程中信息的爆炸式增长,信息共享交换推动了我们对信息的利用,在这一进程中,医疗卫生信息化起着核心作用——而让信息更具价值,赋予信息标准化和互操作能力的过程,这也是InterSystems一直努力的方向,我们在国内支持大量医院实现了互联互通建设。在建设过程中,我们注意到项目的定量部分的建设成本占比是比较高的,很多的工作都花在了合规性和相关管理工具的开发上——应用标准的实施是有成本的,而对于标准的理解在各个项目上水平不尽相同,这就进一步影响了互联互通项目的建设成果。有鉴于此,2021年我们发布了InterSystems IRIS医疗版互联互通套件1.0版,初衷在于将我国的互联互通建设标准放到我们自己的技术平台里,为用户提供开箱即用的、标准合规性的基础和相应工具——这套工具包括了我们的医疗卫生信息模型、文档模型与管理、互联互通服务接口等,从而可以降低用户的实施成本和相应的实施风险。
在互联互通套件1.0版,我们提供了这样一些能力:
· 在医疗卫生信息模型部分,我们提供了完整的卫生数据元与值集管理、数据集管理,并且提供了本地术语注册,以及针对于本地术语和标准术语之间的转换服务。
· 在文档部分,我们提供了电子病历文档模型和模型管理能力、电子病历文档的校验、数据源质量的分析,以及自动生成合规的互联互通文档。
· 在服务部分,我们提供了互联互通标准服务接口、互联互通标准消息模型、ESB服务总线,以及通用的服务发布/订阅功能。
我们知道,互联互通评测的目标之一就是“以评促用”,实现合规仅仅是第一步,我们希望可以为用户提供更多能力,让用户可以真正把符合互联互通标准的医院信息平台应用起来。于是我们继续发布了InterSystems IRIS医疗版互联互通套件2.0版。
传统数据中心建设面临的一个普遍问题是标准缺乏,导致数据中心的建设成本相对较高,而且复用性很差,很难形成生态。因为缺乏标准,所以数据治理、上面可以运行的数据应用,大多都是由数据厂商自己开发的,这就像早期不同的手机厂商提供的功能手机,就那么几个应用可以用,它不是智能手机,功能是非常有限的。
所以2.0版强调的是生态。在规划2.0版的时候,我们将重点放在了核心数据资产上——我们希望在2.0中建设、治理、保存核心数据资产,助力用户应用好数据资产,并且通过基于标准的开放能力,推动医疗卫生信息化的生态建设。
我们在2.0版打通了国内互联互通和FHIR两大生态,希望通过这种生态融合提升数据资产的价值。我们借助FHIR来治理数据资产,令互联互通消息和互联互通文档都能够转换成FHIR资源,直接保存在FHIR资源仓库里面。
我们使用FHIR资源模型来提供统一的行业语义,通过FHIR API,包括InterSystems提供的互联互通服务,向用户提供统一的、基于行业语义的互操作能力,这样一来,用户能够以生态的方式来扩展数据资产的利用,体现数据资产的核心价值。
我们也注意到,行业中的确已经有很多关于微服务架构的思考。在2.0版本上,我们还扩展了服务架构,新增了对新的应用开发架构——微服务架构——的支持,这对于微服务架构在行业中的落地肯定是有推动作用的。
此外,我们增加了全面的API网关能力,用户可以通过 API网关的形式,对访问互联互通的医院信息平台的这些客户端来进行流量控制、认证管理等,实现服务的全生命周期管理。
在2.0版中,我们还提供了一个完整的FHIR服务器,以打通FHIR生态与国内互联互通生态。在决策支持上,FHIR生态已经提供决策支持架构和利用机器学习的一些用例,我们也基于FHIR为决策者提供完整的、关联的、实时的决策要素,比如说通过底层互操作架构来提供整个流程闭环的基础,通过内置的机器学习、自然语言处理、商业智能等工具,为用户提供数据决策支持工具。
在平台监控方面,2.0版新增了互联互通监控指标,以及针对通用的互操作性的指标监控能力。
在实际工作中,我们注意到互联互通项目建成之后,有一些项目的建设成果并没有得到全面地利用。例如,根据最近一位北京专家的调研结果,很多用户在电子健康档案的相关项目建设上面花费了巨额经费,但是在数据调阅频次上面却“低得出人意料”——我想这是相当一部分项目的现状。
互联互通建设的成果是我们的医院信息平台,我们是以医院互联互通为标准来建设医院信息平台的,那么医院信息平台上的数据利用应该是我们的核心。
因此在3.0的版本里,我们希望以最佳实践的方式来展现如何充分利用我们的互联互通的生态,提升互联互通价值,通过向用户提供一些用例,抛砖引玉地引导用户来正确使用互联互通建设成果,真正发挥出数据的价值。我们规划了两类互联互通应用方向:一个是数据利用,另一个是应用创新。
数据利用可以有很多方向,例如数字孪生,我们可以通过更完整、更实时的数据来建立准确、实时的患者画像;例如数据编织(data fabric),我们可以把所有数据来源进行统一编织,建立一个跨数据来源的、统一的语义平台;例如在数据流通领域发挥核心价值。
在应用创新上,借助打通FHIR和互联互通发展生态,我们可以利用FHIR应用能力来建设互联互通的应用生态。例如在FHIR生态里有Smart on FHIR,这是一个全新的、即插即用的软件开发的架构;再如基于FHIR生态的CDS Hooks决策支持架构等。这些都是我们可以来参考、引进和学习的。
我借助两则近期的新闻来介绍一下如何利用互联互通建设生态来满足数据利用和应用创新。
“数据二十条”:打破数据垄断、实现数据要素价值
2022年12月19日,国务院发布了《中共中央 国务院关于构建数据基础制度更好发挥数据要素作用的意见》(以下简称“《意见》”)。
这份意见为数据流通奠定了一个政策基础。简单梳理一下这份重要的意见里面关键词可以发现其核心就是打破数据垄断、实现数据要素价值。
关键词梳理,依据《中共中央国务院关于构建数据基础制度更好发挥数据要素作用的意见》
在实现方法上,我们就要重视对数据质量标准化体系的建设,基于信息安全和标准化的数据采集,整合互联互通和互操作性,《意见》在实现方法上面提出了一些非常重要的观点,如原始数据不出域,数据可用不可见等等这样的一些关键点,都是我们未来在医疗卫生信息数据流通上的指导思想和指导原则。
那么互联互通套件如何来支持这种政策要求?
在互联互通套件2.0架构里面,我们治理后的医疗卫生信息保存在FHIR资源仓库里。FHIR这种对象模型非常适合表达医疗卫生的复杂数据模型,但是对类似数据分析上报这些传统应用来说,通常会使用基于SQL的数据分析和操作工具,如果使用FHIR资源,操作起来是很难的。此外,要让用户直接使用FHIR资源仓库,也不符合《意见》提到的“原始数据不出域”的要求。
所以互联互通套件提供了FHIR SQL构建器,这是一个图形化的工具,用户可以将FHIR资源按需求映射成不同的SQL表——例如数据上报需要用到数据的一部分内容,可能内部数据用户对于数据需求范围是比较大的,外部用户对于数据需求范围可能就会更窄一些——不管是用户需求是什么样的,都可以通过映射的方式提供相应的SQL表。这种方式既满足了数据用户,使他们能够通过熟悉的工具(SQL)获得数据和操作数据,同时也满足了“原始数据不出域”的要求。
另外需要强调的一点是,SQL映射不是创建一份数据拷贝。它并没有把FHIR资源仓库原始数据拷贝给用户,而是通过映射的方式,映射出来一份虚拟SQL表。用户可以以SQL的访问方式来操作它——通过这种模式提供的数据是实时的、完整的、按需提供的。
Epic的生态创新:以标准为基础,推动应用建设的专业化
互联互通赋能应用创新这部分,面临着很多基于标准的问题和限制。
以美国为例,美国国家卫生IT协调员办公室研究发现,只有29%的用户能够将外部来源的卫生信息和应用整合到自己的电子健康档案中,也就是说美国是没有实现“应用通”的,只有不到1/3的电子健康档案是可以使用别的厂商开发的应用的。
2022年12月还有一个非常重要的新闻,12月9日的这则新闻来自全球电子病历领头羊Epic,Epic发布了名为“Connection Hub”的平台,这个平台向所有开发者开放。
这意味着什么?
我们来看看Epic目前拥有的能力。作为一个电子病历厂商,Epic有自己的应用市场,类似于智能手机的应用市场。
截至2022年12月22日,这个应用市场里共有619个应用,这些应用都是不同厂商开发的,这些应用都可以作为一个即插即用的应用直接下载并安装在Epic的电子健康档案系统上,所以这是一个非常有意思的事情。
作为行业里最重要的电子病历厂商之一,Epic对这些应用的开发趋势,包括它持有的这种开放的、应用的生态理念,我相信会为行业应用创新带来很多价值。
回顾一下我们面临的现状,我们的数据中心是缺乏标准的,每个厂商的数据中心都是不同的数据模型,提供厂商自定义的数据接口,所以当基于这些数据来进行应用开发的时候,我们做的是低水平重复建设和无法复用的应用建设。
例如,几乎每个项目的数据中心产品里都会部署一个患者360视图或类似应用,但这个应用每个厂商、甚至每个项目都会重新开发一次,所以这是一个低水平重复建设的典型应用。而且即便觉得好,如果换一个数据中心产品,这个应用也无法直接移植过去。
在缺乏标准的情况下,这些数据中心及相关的应用建设变得非常昂贵。如果我们整个行业能够采用一个标准的语义来建设数据中心或者电子健康档案,那么通过提供基于行业统一语义的数据和基于标准的数据接口,就可以让应用开发厂商只需关注在业务,潜心开发高水平应用。而且基于生态标准的应用可以运行在任何基于标准的数据中心(或电子健康档案)上,这就为释放数据应用价值奠定了非常好的基础。
回到前面这则新闻,Epic的标准是什么?Epic采用了FHIR标准,它基于最新的R4开放了55个资源和450个FHIR API,这样一来,它就可以运行类似于“儿童生长发育曲线”这类的Smart on FHIR应用,加速应用创新。
Epic能做到的,我相信我们也可以借鉴,InterSystems IRIS医疗版互联互通套件已经提供了针对FHIR仓库的能力,并且打通了互联互通与FHIR生态。
InterSystems IRIS医疗版
互联互通套件能力
以互联互通为基础,让医院信息平台成为数字化转型的核心
我想借助这张IDC于2019年发布的“数字化转型平台”示意图作为结尾。这张图是一张概念图,描述了我们数字化转型平台的功能,以及通过无限循环的路径来摆脱传统的技术栈的思维模式。
它将数字化转型平台定义为一个加速企业数字化转型的技术架构,用来支撑我们快速创建面向市场的数字化的服务和体验的平台。同时通过它来积极推动机构内部IT环境的现代化,使它成为将数据运用于业务的智能核心——基于互联互通将数据、算法、代码和模型进行资产化管理、整理、复用和共享。
数字化转型已经颠覆了很多行业,我相信医疗卫生信息行业也不会例外。我们希望通过互联互通套件,以互联互通为基础,让医院信息平台成为医疗卫生行业数字化转型的智能核心。
文章
Weiwei Gu · 五月 4, 2023
我们客户的一个共同需求是配置 HealthShare HealthConnect 和 IRIS的高可用性模式。
市场上的其他集成引擎通常被宣传为具有“高可用性”配置,但事实并非如此。通常,这些解决方案与外部数据库一起使用,因此,如果这些数据库未配置为高可用性,当发生数据库崩溃或与它的连接丢失时,整个集成工具将变得不可用。
对于 InterSystems 解决方案,这个问题不存在,因为数据库是工具本身的一部分和核心。 InterSystems 如何解决高可用性问题?深奥的配置会把我们拖入异化和疯狂的漩涡?不!在 InterSystems,我们倾听并处理了您的投诉(正如我们一直努力做的那样 ;)),并且我们已将镜像功能提供给所有用户和开发人员。
镜像
镜像如何工作?这个概念本身非常简单。如您所知,IRIS 和 HealthShare 都使用一个日志系统,该系统记录每个实例的数据库上的所有更新操作。这个日志系统是后来帮助我们在崩溃后恢复实例而不会丢失数据的系统。好吧,这些日志文件在镜像中配置的实例之间发送,允许并保持镜像中配置的实例永久更新。
架构
让我们简要解释一下在 Mirror 中配置的系统架构是什么样的:
在故障转移模式下配置的两个实例:
主动节点——接收所有常规的读/写操作。
被动节点:在读取模式下,它同步接收主动节点产生的任何变化。
0-14个异步实例:你可以使用多个异步实例,它们可以是两种类型:
DR 异步(灾难恢复):处于读取模式的节点不是故障转移的一部分,尽管它们可以被手动提升成故障转移节点。如果是这样,它们可以在其他两个故障转移节点发生故障时自动提升为主节点。您的数据更新是异步的,所以不能保证其数据是最新的。
报告异步:异步更新节点,用于 BI 任务或数据挖掘。它们不能升级为故障转移,因为可以对数据执行写入。
ISCAgent:安装在每个实例所在的服务器上。它将负责监视所述服务器实例的状态。这是镜像服务器之间除了直接通信之外的另一种通信方式。
Arbiter:它是一个独立于构成镜像的服务器安装的 ISCAgent,并允许通过监视安装的 ISCAgent 和 IRIS/HealthShare 实例来提高安全性和内部故障转移的控制。它的安装不是必须的。
这是一个由只有两个节点的故障转移形成的镜像的操作:
先前的警告
与本文相关的项目没有允许配置镜像的活动许可证。如果你想尝试,直接给我发邮件或者在文末添加评论,我会联系你。
在 Docker 中部署
对于本文,我们将在 Docker 中建立一个小项目,允许我们设置 2 个故障转移实例和一个Arbiter 。默认情况下,可用于 Docker 的 IRIS 映像已经安装并配置了 ISCAgent,因此我们可以跳过该步骤。有必要配置与来自 Visual Studio Code 的文章关联的项目,因为这将使我们以后可以更轻松地使用服务器文件。
让我们看看我们的 docker-compose.yml 会有什么形式:
version: '3.3'
services:
arbiter:
container_name: arbiter
hostname: arbiter
image: containers.intersystems.com/intersystems/arbiter:2022.1.0.209.0
init: true
command:
- /usr/local/etc/irissys/startISCAgent.sh 2188
mirrorA:
image: containers.intersystems.com/intersystems/iris:2022.1.0.209.0
container_name: mirrorA
depends_on:
- arbiter
ports:
- "52775:52773"
volumes:
- ./sharedA:/shared
- ./install:/install
- ./management:/management
command:
--check-caps false
--key /install/iris.key
-a /install/installer.sh
environment:
- ISC_DATA_DIRECTORY=/shared/durable
hostname: mirrorA
mirrorB:
image: containers.intersystems.com/intersystems/iris:2022.1.0.209.0
container_name: mirrorB
depends_on:
- arbiter
- mirrorA
ports:
- "52776:52773"
volumes:
- ./sharedB:/shared
- ./install:/install
- ./management:/management
command:
--check-caps false
--key /install/iris.key
-a /install/installer.sh
environment:
- ISC_DATA_DIRECTORY=/shared/durable
hostname: mirrorB
可以看到我们定义了3个容器:
Arbiter :它对应于将被部署以控制将形成 Mirror Failover 的 IRIS 实例的 ISCAgent(即使图像称为 Arbiter)。当启动容器时,它将执行一个 shell 文件,该文件将启动侦听容器端口 2188 的 ISCAgent。
mirrorA :将部署 IRIS v.2022.1.0.209 映像的容器,稍后我们将其配置为主故障转移节点。
mirrorB :将部署 IRIS v.2022.1.0.209 镜像的容器,稍后我们将其配置为辅助故障转移节点。
当我们执行docker-compose up -d命令时,定义的容器将部署在我们的 Docker 中,它在我们的 Docker 桌面中应该看起来像这样(如果我们从 Windows 执行此操作)。
镜像配置。
部署容器后,我们将继续访问我们将在镜像中配置的实例,第一个将在端口 52775 ( mirrorA ) 上侦听,第二个在 52776 ( mirrorB ) 上侦听。访问用户和密码将是superuser/ SYS
由于实例部署在 Docker 中,我们将有两个选项来配置我们服务器的 IP。第一种是在配置中直接使用我们容器的名称(这是最简单的方法)或检查 Docker 为每个容器分配的 IP(打开控制台并执行返回分配的 IP 的 ifconfig)。为了清楚起见,我们将在示例中使用我们为每个容器指定的名称作为 Docker 中每个容器的地址。
首先,我们将配置我们将用作故障转移活动节点的实例。在我们的例子中,它将是我们所说的mirrorA 。
第一步是启用镜像服务,因此我们将从管理门户访问镜像菜单: System Administration --> Configuration --> Mirror Settings --> Enable Mirror Service并标记Service Enabled检查:
启用服务后,我们可以开始配置我们的活动节点。启用该服务后,您将能够在镜像菜单中看到新选项已启用:
在这种情况下,由于我们还没有创建任何镜像配置,我们必须使用Create Mirror选项创建一个新配置。当我们访问这个选项时,管理门户将打开一个新窗口,我们可以从中配置我们的镜像:
让我们仔细看看每个选项:
Mirror Name :我们将用来标识我们的镜像的名称。对于我们的示例,我们将其称为 MIRRORSET
需要 SSL/TLS :对于我们的示例,我们不会使用 SSL/TLS 配置连接,尽管在生产环境中,防止在实例之间没有任何类型的加密的情况下共享日志文件会比操作方便更重要。如果您有兴趣配置它,您可以在文档的以下URL中获得所有必要的信息。
使用 Arbiter :此选项不是强制性的,但强烈推荐,因为它为我们的镜像配置增加了一层安全性。对于我们的示例,我们将选中它并指示我们运行 Arbiter 的 IP。对于我们的示例,IP 将位于容器名称arbiter中。
用户虚拟 IP :在 Linux/Unix 环境中,此选项非常有趣,因为它允许我们为将由我们的镜像管理的活动节点配置虚拟 IP。此虚拟 IP 必须与故障转移节点属于同一子网。虚拟IP的操作非常简单,当主动节点出现故障时镜像会自动在待提升的被动节点所在的服务器上配置虚拟IP。这样,被动节点到主动节点的升级对用户来说将是完全透明的,因为他们将继续连接到同一个 IP,即使它将配置在不同的服务器上。如果您想了解有关虚拟 IP 的更多信息,可以查看文档的此URL 。
其余的配置可以保持原样。在屏幕右侧我们会看到镜像中这个节点的相关信息:
Mirror Member Name :这个镜像成员的名称,默认情况下它将采用服务器的名称以及实例的名称。
超级服务器地址:这个节点的超级服务器 IP 地址,在我们的例子中是mirrorA 。
Agent Port :配置了该节点对应的ISCAgent的端口。默认为2188 。
配置必要的字段后,我们可以继续保存镜像。我们可以从镜像监视器(系统操作-->镜像监视器)检查配置情况。
完美,这里我们有了新配置的镜像。如您所见,只有我们刚刚创建的活动节点出现。很好,接下来让我们在故障转移中添加我们的被动节点。我们访问mirrorB管理门户并访问镜像设置菜单。正如我们已经为mirrorA实例所做的那样,我们必须启用镜像服务。我们重复该操作,一旦菜单选项更新,我们将选择Join as Failover 。
这里我们有镜像连接屏幕。让我们简要解释一下每个字段的含义:
镜像名称:我们在创建时为镜像指定的名称,在我们的示例中为MIRRORSET 。
Agent Address on Other System :部署主动节点ISCAgent的服务器IP,对我们来说就是mirrorA。
代理端口:我们创建镜像的服务器的ISCAgent监听端口。默认为2188 。
InterSystems IRIS 实例名称:主动节点上 IRIS 实例的名称。在这种情况下,它与被动节点IRIS的一致。
保存镜像数据后,我们将可以选择定义与我们正在配置的被动节点相关的信息。我们再来看看被动节点可以配置的字段:
镜像成员名称:被动节点将在镜像中使用的名称。默认情况下由服务器名称和实例组成。
超级服务器地址:被动节点中超级服务器的 IP 地址。在这种情况下mirrorB 。
代理端口:我们配置的被动节点服务器上安装的ISCAgent的监听端口。默认为2188 。
SSL/TLS 要求:在此示例中不可配置,我们不使用 SSL/TLS。
镜像私有地址:被动节点的 IP 地址。正如我们所见,在使用 Docker 时,我们可以使用容器名称mirrorB 。
代理地址:安装 ISCAgent 的服务器的 IP 地址。和以前一样, mirrorB 。
我们按照指示保存配置,然后返回到镜像监视器以验证我们是否已正确配置所有内容。我们可以将mirrorA中的主动节点和mirrorB中的被动节点的监视器可视化。让我们看看这两个实例之间的差异。
活动节点mirrorA上的镜像监视器:
被动节点mirrorB上的镜像监视器:
如您所见,显示的信息是相似的,基本上改变了故障转移成员的顺序。选项也不同,让我们看看其中的一些:
主动节点mirrorA :
设置无故障转移:防止在作为其中一部分的任何实例停止的情况下执行故障转移。
降级其他成员:从镜像配置中删除其他故障转移成员(在本例中为mirrorB )。
被动节点mirrorB :
Stop Mirror On This Member :停止故障转移被动节点上的镜像同步。
降级为 DR 成员:将此节点从其实时同步故障转移的一部分降级为异步模式下的灾难恢复模式。
完美,我们已经配置了节点,现在让我们看看配置的最后一步。我们必须决定哪些表将成为镜像的一部分,并在两个节点上进行配置。如果您查看与本文相关的 Open Exchange 项目的 README.md,您将看到我们配置和部署了两个通常用于训练的应用程序。当我们启动 Docker 容器时,这些应用程序会自动部署,默认情况下会创建 NAMESPACES 和数据库。
第一个应用程序是COMPANY ,它允许我们保存公司记录,第二个应用程序是PHONEBOOK ,它允许我们添加与注册公司以及客户相关的个人联系人。
让我们添加一家公司:
现在让我们为之前的公司创建个人联系人:
公司数据将在COMPANY数据库中注册,联系人数据在PERSONAL中,这两个数据库都已映射,以便可以从命名空间 PHONEBOOK 访问它们。如果我们检查两个节点中的表,我们将看到在mirrorA中我们有公司和联系人的数据,但在mirrorB中仍然没有任何数据,这是合乎逻辑的。
mirrorA注册的公司:
好的,让我们继续在我们的镜像上配置数据库。为此,从我们的活动节点 ( mirrorA ),我们访问本地数据库管理屏幕(系统管理员-->配置-->系统配置-->本地数据库)并单击添加到镜像选项,我们必须选择从列表中我们要添加的所有数据库并从屏幕上读取消息:
一旦我们将数据库从主动节点添加到镜像中,我们必须对其进行备份或复制数据库文件 (IRIS.dat) 并将它们还原到被动节点上。如果您决定直接复制 IRIS.dat 文件,请记住您必须冻结要复制的数据库中的写入,您可以在文档的以下URL中查看必要的命令。在我们的例子中,没有必要暂停,因为除了我们之外没有人在写入数据。
在复制数据库文件之前,让我们从活动节点的监视器上检查镜像的状态:
让我们看看被动节点:
正如我们所看到的,从被动节点我们被告知,虽然我们在镜像中配置了 3 个数据库,但配置尚未完成。让我们继续将数据库从主动节点复制到被动节点,不要忘记我们必须卸载被动节点的数据库才能进行复制,为此我们将从管理门户访问系统配置—— >数据库并访问它们中的每一个,我们继续卸载它们。
完美的!卸载的数据库。让我们从 Visual Studio Code 访问与本文相关的项目代码,看看我们有 IRIS 安装所在的文件夹, sharedA用于mirrorA和sharedB用于mirrorB 。让我们访问 COMPANY、CUSTOMER 和 PERSONAL 数据库所在的文件夹 ( /sharedA/durable/mgr ),然后继续将镜像中每个数据库的 IRIS.dat 复制到 mirrorB 的相应目录 ( /sharedB/durable/mgr) ).
复制完成后,我们再次挂载mirrorB数据库并从mirrorB中的镜像监视器检查已配置数据库的状态:
答对了!我们的镜像已经识别出数据库,现在我们只需要激活和更新它们。为此,我们将点击Activate操作,然后点击Catchup ,这将在激活后出现。让我们看看他们是如何结束的:
完美,我们的数据库已经在镜像中正确配置,如果我们查询 COMPANY 数据库,我们应该看到我们之前从mirrorA注册的记录:
显然我们的COMPANY数据库中有我们之前在mirrorA中输入的记录,毕竟我们已经复制了整个数据库。让我们继续从mirrorA添加一个新公司,我们将其称为“另一家公司”,然后再次查询 COMPANY 数据库表:
在这里,我们完成了。我们只需要确保我们在镜像中配置的数据库对于被动节点mirrorB处于只读模式:
他们在那里!是只读模式R 。好吧,我们已经配置了镜像并同步了数据库。如果我们的产品正在运行,这不会成为问题,因为镜像会自动负责管理它们,并在主动节点出现故障时在被动节点中启动它们。
非常感谢大家走到这一步!它很长,但我希望你觉得它有用。