搜索​​​​

清除过滤器
文章
Tete Zhang · 九月 14, 2022

集成平台消息相关的常见存储问题

从消息查看器看到清除周期以外的消息没有被正常清除 这种情况先抽查这些消息所处的会话中是否有未完成操作周期的消息(状态为除“Completed”“Error”“Discarded”之外的状态)。如有,且定期清除任务配置了“KeepIntegrity”,且该环境并不需要保留这些消息,可通过关闭清除任务中的“KeepIntegrity”配置清除这些会话和包含的消息。如果有这类消息,但是定期清除任务未配置“KeepIntegrity”,可能是定期清除任务的逻辑或消息数据问题导致清楚任务查找的时候没有覆盖这些消息,请联系WRC帮助排查具体原因。 有关定期清除任务的更多信息请参见文档 Purging Production Data | Managing Productions | InterSystems IRIS for Health 2022.1 从消息查看器看不到清除周期之外的消息,但是^%GSIZE显示有global占据了很大的磁盘空间 这种情况需要具体排查每个较大的global。可能有以下原因: 系统定义的global占用很大空间。您可以联系WRC帮助排查具体原因。 自定义的global占用很大空间。这可能是消息中嵌套的持久化数据或流数据,或者是和消息没有直接关系的独立的表里面的数据。请对创建这种global的代码进行复盘,找到创建逻辑并增加相应的数据管理逻辑。 孤立消息 (Orphan Messages) 孤立消息是指不存在配套Message Header的所有消息对象。 定期清除任务会根据Message Header里的时间信息和状态信息去判断一条消息是否符合清除条件。Message Header是在消息从一个组件发向另一个组件的时候被创建的。所以,当我们创建将被永久存储的对象之前,我们都要思考:这个对象会被保存吗?被保存后会被发送到另一个组件吗?如果不会被发送,该对象将不存在配套的Message Header,也就不会被定期清除。这种情况我们需要开发相应的自定义逻辑去定期管理该表中的数据,或确保该对象被发送到某个组件以创建Message Header。消息对象中嵌套对象或流的情况要尤其注意,对每一个嵌套的对象或流都要定义相对应的%OnDelete()删除逻辑。 在测试阶段我们可以做如下的测试: 测试前跑^%GSIZE报告并检查磁盘存储 跑一套测试消息 用清除任务删除系统上所有的消息(DaysToKeep=0) 跑^%GSIZE报告并检查磁盘存储 如果对比前后的^%GSIZE报告和磁盘空间之后,发现清除任务完成后没有遗留多余的数据,那么这就证明我们的逻辑中对消息及相关嵌套数据进行了很好的管理。反之如果发现了遗留数据,我们可以在研发测试阶段就对问题进行排查,尽量避免开放生产环境以后出现磁盘满或数据库过大的问题。 如果发现了环境中有孤立消息的问题,请联系WRC进行排查和消息清除管理。 HL7v2:孤立字段(Orphan Segment) HL7v2在数据库中的存储逻辑如下。 EnsLib.HL7.Message对象存在以下两个global里: ^EnsLib.H.MessageD ^EnsHL7.Segment 示例: HL7v2消息 (^EnsLib.H.MessageD global): 1: ^EnsLib.H.MessageD = 1257406 2: ^EnsLib.H.MessageD(1257406) = $lb("","","2.3:ORU_R01",0,"2019-06-03 15:28:38.819","2.3.1","C:\Support\inarchive\testoru.txt_2019-06-03_11.28.38.814","","") 3: ^EnsLib.H.MessageD(1257406,"segs") = 5 4: ^EnsLib.H.MessageD(1257406,"segs",1) = "11612,25" 5: ^EnsLib.H.MessageD(1257406,"segs",2) = "11612,26" 6: ^EnsLib.H.MessageD(1257406,"segs",3) = "11612,27" 7: ^EnsLib.H.MessageD(1257406,"segs",4) = "11612,28" 8: ^EnsLib.H.MessageD(1257406,"segs",5) = "11612,29" 其中, 125706是该HL7v2消息的Object ID。Global值"11612,25","11612,26"指向相应的HL7v2字段。 HL7v2字段 (^EnsHL7.Segment global): 1: ^EnsHL7.Segment(11612) = 30 2: ^EnsHL7.Segment(11612,25) = "|^~\&MSH|^~\&||GA0000||VAERS PROCESSOR|20010331605||ORU^R01|20010422GA03|T|2.3.1|||AL|" 3: ^EnsHL7.Segment(11612,25,0,1257406) = "" 其中,11612是创建该HL7v2消息的进程 ID (PID)。^EnsHL7.Segment(11612,25) 存储了该字段的具体数据。^EnsHL7.Segment(11612,25,0,1257406) 中的第四个值(1257406)是这个字段所属消息的Object ID。 从以上示例可以看出,HL7v2字段数据存储于^EnsHL7.Segment global。所以在^%GSIZE中看到^EnsHL7.Segment global比 ^EnsLib.H.MessageD global大是正常现象。使用平台自带的逻辑在最新版本上目前也没有已知问题会导致孤立字段。如果您持续观察^%GSIZE报告,发现^EnsHL7.Segment global的大小出现异常增长,可以联系WRC排查是否有孤立字段的情况。
文章
Jingwei Wang · 九月 16, 2022

Python应用程序连接到InterSystemsIRIS数据库 - 使用 DB-API

连接前准备: Python 开发环境 DB-API驱动:irispython wheel 文件 Connection String 步骤: 安装irispython wheel 文件 pip install intersystems_irispython-3.2.0-py3-none-any.whl Connection String:其中import iris 用来导入iris, connection = iris.connect是connection string。connection.close()用来断开连接。 import iris def main(): connection_string = "localhost:1972/USER" username = "SQLAdmin" password = "deployment-password" connection = iris.connect(connection_string, username, password) # when finished, use the line below to close the connection # connection.close() if __name__ == "__main__": main()
文章
Michael Lei · 五月 30, 2022

部分IRIS 2022 年度编程大奖赛作品展示——基于红帽Openshift 容器平台的IRIS 互操作消息处理测试软件

我的团队在在红帽OpenShift容器平台上运行IRIS互操作性解决方案。我想在数据被存储在Mirror的数据pods中的情况下,测试运行中的webgateway pods和计算节点 pods能处理多少消息。 为了增加测试难度,我部署了多个feeder容器,并在每个feeder上安排了任务,以在同一时间发送大量的消息。为了进入下一阶段的测试,我希望有多种类型的测试文件可以按需使用。我创建了test-data应用程序,能够请求生成大量的多种类型的文件。 我早期的一些测试依赖于复制一个样本文件和处理它。这在一次只复制一份的情况下效果不错。为了获得同一样本文件的许多副本,MakeFile函数获取一个样本文件、保存其副本、并以唯一的时间戳进行重命名。MakeFiles函数有一个参数,用于确定要制作的文件数量。 我找到了一个样本文件,它的输入和输出都是带分隔符和固定符的。我把它包含在我的应用程序中,并添加了一个转换来操作测试数据文件。在这种情况下,我把测试文件中的识别ID号替换成在一个类方法中生成的识别ID号,并且是随着文件而递增的。 我想在处理后审查测试文件中的数据,我喜欢看到一系列有顺序的数字,而不是一系列随机数字。 具体代码下载:https://openexchange.intersystems.com/package/test-data
文章
姚 鑫 · 六月 29, 2021

第二十二章 计算XPath表达式

# 第二十二章 计算XPath表达式 `XPath`(XML路径语言)是一种基于XML的表达式语言,用于从XML文档获取数据。使用类中的`%XML.XPATH.Document`,可以轻松地计算`XPath`表达式(给定提供的任意XML文档)。 注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码,InterSystems IRIS将使用本书前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。 ## IRIS中XPath表达式求值概述 要使用InterSystems IRIS XML支持使用任意XML文档计算`XPath`表达式,请执行以下操作: 1. 创建`%XML.XPATH.Document`的实例。为此,请使用以下类方法之一:`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()``。使用这些方法中的任何一种,都可以将输入XML文档指定为第一个参数,并接收%XML.XPATH.Document`的一个实例作为输出参数。 这一步使用内置的XSLT处理器解析XML文档。 2. 使用`%XML.XPATH.Document`实例的`EvaluateExpression()`方法。对于此方法,需要指定节点上下文和要计算的表达式。 节点上下文指定要在其中计算表达式的上下文。这使用`XPath`语法来表示到所需节点的路径。例如: ```java "/staff/doc" ``` 要计算的表达式还使用`XPath`语法。例如: ```java "name[@last='Marston']" ``` 可以将结果作为输出参数(作为第三个参数)接收。 注意:如果要迭代一大组文档并计算每个文档的`XPath`表达式,建议在处理完文档后,在打开下一个文档之前将该文档的`OREF`设置为`NULL`。这绕过了第三方软件的一个限制。在循环中处理大量文档时,此限制会导致CPU使用率略有增加。 # 创建XPath文档时的参数列表 若要在创建`%XML.XPATH.Document`的实例,请使用该类的`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()`类方法。对于这些类方法,完整的参数列表按顺序如下: 1. PSource、pStream或pString-源文档。 - 对于`CreateFromFile()`,此参数是文件名。 - 对于`CreateFromStream()`,此参数是二进制流。 - 对于`CreateFromString()`,此参数是一个字符串。 2. PDocument-作为输出参数返回的结果。这是`%XML.XPATH.Document`的实例。 3. PResolver-解析源时使用的可选实体解析器。 4. PErrorHandler-一个可选的自定义错误处理程序。 5. PFlags-控制SAX解析器执行的验证和处理的可选标志。 6. PSchemaSpec-可选的架构规范,用于验证文档源。此参数是一个字符串,其中包含以逗号分隔的命名空间/URL对列表: ```java "namespace URL,namespace URL" ``` 这里,`Namespace`是用于模式的XML名称空间,URL是提供模式文档位置的URL。名称空间和URL值之间有一个空格字符。 7. PPrefix Mappings-可选的前缀映射字符串。 `CreateFromFile()`、`CreateFromStream()`和`CreateFromString()`方法返回应检查的状态。例如: ```java Set tSC=##class(%XML.XPATH.Document).CreateFromFile("c:\sample.xml",.tDocument) If $$$ISERR(tSC) Do $System.OBJ.DisplayError(tSC) ``` # 为默认命名空间添加前缀映射 当XML文档使用默认名称空间时,这会给`XPath`带来问题。请考虑以下示例: ```xml Mr. Marston Mr. Bertoni Mr. Leslie Ms. Farmer ``` 在本例中, `` 元素属于名称空间,但没有名称空间前缀。`XPath`不提供访问 `` 元素的简单方法。 - 可以设置`%XML.XPATH.Document`实例的`Prefix Mappings`属性。该属性旨在为源文档中的每个默认名称空间提供唯一的前缀,以便`XPath`表达式可以使用这些前缀,而不是使用完整的名称空间URI。 `PrefixMappings` 属性是一个由逗号分隔的列表组成的字符串;每个列表项都是一个前缀,后跟一个空格,后跟一个命名空间URI。 - 调用`CreateFromFile()`、`CreateFromStream()`或`CreateFromString()`时,可以指定`PrefixMappings`参数。此字符串的格式必须与前面描述的相同。 然后以与使用任何名称空间前缀相同的方式使用这些前缀。 例如,假设将前面的XML读入`%XML.XPATH.Document`的实例时,按如下方式指定了前缀映射: ```java "s http://www.staff.org" ``` 在本例中,可以使用`"/s:staff/s:doc"`访问`` 元素。 请注意,可以使用实例方法`GetPrefix()`来获取先前为文档中的给定路径指定的前缀。 # 计算XPath表达式 要计算`XPath`表达式,请使用`%XML.XPATH.Document`实例的`EvaluateExpression()`方法。对于此方法,请按顺序指定以下参数: 1. PContext-节点上下文,指定在其中计算表达式的上下文。指定一个字符串,该字符串包含指向所需节点的路径的`XPath`语法。例如: ```java "/staff/doc" ``` 2. PExpression-选择特定结果的谓词。指定包含所需XPath语法的字符串。例如: ```java "name[@last='Marston']" ``` 注意:对于其他技术,通常的做法是将谓词连接到节点路径的末尾。类中的`%XML.XPATH.Document`不支持此语法,因为基础XSLT处理器需要节点上下文和谓词作为单独的参数。 3. PResults-作为输出参数返回的结果。 `EvaluateExpression()`方法返回应该检查的状态。例如: ```java Set tSC=tDoc.EvaluateExpression("/staff/doc","name[@last='Smith']",.tRes) If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)} ``` # 使用XPath结果 XPath表达式可以返回XML文档的一个子树、多个子树或标量结果。在`%XML.XPATH.Document`的`EvaluateExpression()`方法旨在处理所有这些情况。具体地说,它返回一个结果列表。该列表中的每个项目都有一个Type属性,该属性具有下列值之一: - `$$$XPATHDOM`-指示该项包含XML文档的子树。此项目是`%XML.XPATH.DOMResult`实例,提供导航和检查子树的方法。 - `$$$XPATHVALUE`-指示该项是单个标量结果。此项目是`%XML.XPATH.ValueResult`实例。 这些宏在`%occXSLT.inc`包含文件中定义。 ## 检查XML子树 本节介绍如何导航由`%XML.XPATH.DOMResult`表示的XML子树,以及如何获取有关您在该子树中当前位置的信息。 ### 导航子树 要导航`%XML.XPATH.DOMResult`的实例,可以使用该实例的以下方法:`Read()`、`MoveToAttributeIndex()`、`MoveToAttributeName()`、`MoveToElement()`和`Rewind()`。 要移动到文档中的下一个节点,请使用`read()`方法。`Read()`方法返回TRUE值,直到没有更多节点可读为止(即,直到到达文档末尾)。 导航到某个元素时,如果该元素具有属性,则可以使用以下方法导航到这些属性: - 使用`MoveToAttributeIndex()`方法按索引(属性在元素中的序号位置)移动到特定属性。此方法只有一个参数:属性的索引号。请注意,可以使用`AttributeCount`属性来了解给定元素有多少个属性。 - 使用`MoveToAttributeName()`方法按名称移动到特定属性。此方法有两个参数:属性名称和命名空间URI(可选)。 完成当前元素的属性后,可以通过调用其中一个导航方法(如`read()`)移动到文档中的下一个元素。或者,可以调用`MoveToElement()`方法返回到包含当前属性的元素。 这里描述的所有方法都在文档中前进,但`Rewind()`方法除外,它导航到文档的开头并重置所有属性。 ### 节点的属性 除`Type`属性外,`%XML.XPATH.DOMResult`的以下属性还提供有关当前位置的信息。 #### AttributeCount 如果当前节点是元素,则此属性指示元素的属性数。 #### EOF 如果读取器已到达源文档的末尾,则为true;否则为false。 #### HasAttributes 如果当前节点是一个元素,则如果该元素具有属性,则此属性为true(如果没有属性,则为false)。如果当前节点是属性,则此属性为true。 对于任何其他类型的节点,此属性为False。 #### HasValue 如果当前节点是具有值的节点类型(即使该值为空),则为True。否则,此属性为false。 #### LocalName 对于属性或元素类型的节点,这是当前元素或属性的名称,不带命名空间前缀。对于所有其他类型的节点,此属性为`NULL`。 #### Name 当前节点的完全限定名称,视节点类型而定。 #### NodeType 当前节点的类型,如下之一:`attribute`, `chars`, `cdata`, `comment`, `document`, `documentfragment`, `documenttype`, `element`, `entity`, `entityreference`, `notation`,或处理指令。 #### Path 对于元素类型的节点,这是到元素的路径。 对于所有其他类型的节点,此属性为空。 #### ReadState 表示总体读状态,有以下几种: - `“initial”`表示`Read()`方法还没有被调用。 - `“cursoractive”`意味着`Read()`方法至少被调用过一次。 - `“eof”`表示已经到达文件的末尾。 #### Uri 当前节点的URI。 返回的值取决于节点的类型。 #### Value 值(如果有的话),适合于节点类型。 如果该值小于`32kb`,则为字符串。 否则,它是一个字符流。 ## 检查标量结果 本节介绍在类中使用由`%XML.XPATH.ValueResult`表示的`XPath`结果。除`Type`属性外,该类还提供`Value`属性。 请注意,如果该值的长度大于32KB,则会自动将其放入流对象中。除非确定将收到的结果类型,否则应该检查`Value`是否为流对象。为此,可以使用`$IsObject`函数。(也就是说,如果此值是对象,则它是流对象,因为它是唯一可以是对象的类型。) ```java // 如果结果长度大于32KB,则值可以是流 Set tValue=tResult.Value If $IsObject(tValue){ Write ! Do tValue.OutputToDevice() } else { Write tValue } ``` ## 一般方法 除非可以确定在计算`XPath`表达式时会收到什么样的结果,否则应该编写代码来处理最常见的情况。代码的可能组织如下: 1. 查找返回结果列表中的元素数量。遍历此列表。 2. 对于每个列表项,检查`Type`属性。 - 如果`Type`为`$$$XPATHDOM`,, 在类中使用`%XML.XPATH.DOMResult`的方法导航并检查此XML子树。 - 如果`Type`为`$$$XPATHVALUE`,请检查`Value`属性是否为流对象。如果是流对象,则使用常用的流接口访问数据。否则,`Value`属性为字符串。 # 示例 本节中的示例针对以下`XML`文档计算`XPath`表达式: ```xml Yao Xin Mr. Bertoni Mr. Leslie Ms. Farmer Ms. Midy Mr. Dick Mr. Boag Mr. Curcuru Mr. Kesselman Mr. Auriemma ``` ## 计算具有子树结果的XPath表达式 ```java /// 计算返回DOM Result的XPath表达式 ClassMethod Example1() { Set tSC=$$$OK do { Set tSC=##class(%XML.XPATH.Document).CreateFromFile(filename,.tDoc) If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC) Quit} Set context="/staff/doc" Set expr="name[@last='Marston']" Set tSC=tDoc.EvaluateExpression(context,expr,.tRes) If $$$ISERR(tSC) Quit Do ##class(%XML.XPATH.Document).ExampleDisplayResults(tRes) } while (0) If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)} Quit } ``` 本例选择了``元素的`last`属性等于`Yao`的任何节点。 该表达式在``元素的``节点中计算。 请注意,此示例使用`%XML.XPATH.Document`的`ExampleDisplayResults()`类方法。 执行`example1()`方法时,将先前的XML文件作为输入提供,您会看到以下输出: ```java DHC-APP>d ##class(PHA.TEST.Xml).Example1("E:\temp\xmlXpath.txt") XPATH DOM element: name attribute: first Value: Xin attribute: last Value: Yao chars : #text Value: Yao Xin ``` ## 计算具有标量结果的`XPath`表达式 下面的类方法读取XML文件并计算返回标量结果的XPath表达式: ```java /// 计算返回值结果的XPath表达式 /// d ##class(PHA.TEST.Xml).Example2("E:\temp\xmlXpath.txt") ClassMethod Example2(filename) { Set tSC=$$$OK do { Set tSC=##class(%XML.XPATH.Document).CreateFromFile(filename,.tDoc) If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC) Quit} Set tSC=tDoc.EvaluateExpression("/staff","count(doc)",.tRes) If $$$ISERR(tSC) Quit Do ##class(%XML.XPATH.Document).ExampleDisplayResults(tRes) } while (0) If $$$ISERR(tSC) {Do $System.OBJ.DisplayError(tSC)} Quit } ``` 这个例子统计``子节点。 该表达式在``元素中求值。 当执行`Example2()`方法,提供前面的XML文件作为输入时,会看到以下输出: ```java DHC-APP> d ##class(PHA.TEST.Xml).Example2("E:\temp\xmlXpath.txt") XPATH VALUE 2 ```
文章
Lele Yang · 六月 8, 2023

没有虚拟 IP 地址的数据库镜像

++ 更新:2018 年 8 月 1 日 使用内置于 Caché 数据库镜像的 InterSystems 虚拟 IP (VIP) 地址有一定的局限性。特别是,它只能在镜像成员驻留在同一网络子网时使用。当使用多个数据中心时,由于增加了网络复杂性( 此处有更详细的讨论),网络子网通常不会“延伸”到物理数据中心之外。出于类似的原因,当数据库托管在云端时,虚拟 IP 通常无法使用。 负载均衡器(物理或虚拟)等网络流量管理设备可用于实现相同级别的透明度,为客户端应用程序或设备提供单一地址。网络流量管理器自动将客户端重定向到当前镜像主服务器的真实 IP 地址。自动化旨在满足灾难后 HA 故障转移和 DR 升级的需求。 网络流量管理器的集成 当今市场上有许多支持网络流量重定向的选项。这些中的每一个都支持类似甚至多种方法来根据应用程序要求控制网络流量。为了简化这些方法,我们考虑了三个类别:数据库服务器调用 API、网络设备轮询或两者的组合。 下一节将概述这些方法中的每一个,并就如何将这些方法与 InterSystems 产品集成提供指导。在所有情况下,仲裁器都用于在镜像成员无法直接通信时提供安全的故障转移决策。可以在此处找到有关仲裁器的详细信息。 出于本文的目的,示例图将描述 3 个镜像成员:主机、备份和 DR 异步。但是,我们知道您的配置可能比这更多或更少。 选项 1:网络设备轮询(推荐) 在这种方法中,网络负载均衡设备使用其内置的轮询机制与两个镜像成员通信以确定主镜像成员。 使用 2017.1 中可用的 CSP 网关的mirror_status.cxw页面的轮询方法可以用作 ELB 健康监视器中对添加到 ELB 服务器池的每个镜像成员的轮询方法。只有主镜像会响应“SUCCESS”,从而将网络流量仅定向到活动的主镜像成员。 此方法不需要向 ^ZMIRROR 添加任何逻辑。请注意,大多数负载均衡网络设备对运行状态检查的频率都有限制。通常,最高频率不少于 5 秒,这通常可以接受以支持大多数正常运行时间服务级别协议。 对以下资源的 HTTP 请求将测试本地缓存配置的镜像成员状态。 /csp/bin/mirror_status.cxw 对于所有其他情况,这些镜像状态请求的路径应该使用与请求真实 CSP 页面所用的相同的层次机制解析到适当的缓存服务器和名称空间。 示例:测试 /csp/user/ 路径中应用程序配置服务的镜像状态: /csp/user/mirror_status.cxw 注意:调用镜像状态检查不会消耗 CSP 许可证。 根据目标实例是否是活动主机,网关将返回以下 CSP 响应之一: ** 成功(是主镜像成员) =============================== HTTP/1.1 200 OK Content-Type: text/plain Connection: close Content-Length: 7 SUCCESS ** 失败(不是主镜像成员) =============================== HTTP/1.1 503 Service Unavailable Content-Type: text/plain Connection: close Content-Length: 6 FAILED ** 失败(Caché服务器不支持Mirror_Status.cxw请求) =============================== HTTP/1.1 500 Internal Server Error Content-Type: text/plain Connection: close Content-Length: 6 FAILED 考虑下图作为轮询的示例。 同步故障转移镜像成员之间自动发生故障转移: 下图演示了将 DR 异步镜像成员提升到负载均衡池中,这通常假设同一个负载均衡网络设备正在为所有镜像成员提供服务(地理分割方案将在本文后面介绍)。根据标准 DR 程序,灾难恢复成员的提升涉及人为决策,然后是数据库级别的简单管理操作。但是,一旦采取该操作,就不需要对网络设备执行任何管理操作:它会自动发现新的主要设备。 选项 2:数据库服务器调用 API 在这种方法中,使用了网络流量管理设备,它有一个用故障转移镜像成员和潜在的 DR 异步镜像成员定义的服务器池。 当镜像成员成为主镜像成员时,向网络设备发出 API 调用以调整优先级或权重,以立即指示网络设备将网络流量定向到新的主镜像成员。 相同的模型适用于在主镜像成员和备份镜像成员都不可用的情况下提升 DR 异步镜像成员。 此 API 在 ^ZMIRROR 代码中定义为过程调用的一部分: $$CheckBecomePrimaryOK^ZMIRROR() 在此过程调用中,插入可用于相应网络设备的任何 API 逻辑和方法,例如 REST API、命令行界面等。与虚拟 IP 一样,这是网络配置的突然更改,不涉及任何应用程序逻辑以通知连接到故障主镜像成员的现有客户端正在发生故障转移。根据故障的性质,这些连接可能由于应用程序超时或错误、新主实例强制旧主实例关闭或客户端使用的TCP 保持活动计时器过期造成的故障本身而关闭。 因此,用户可能必须重新连接并登录。您的应用程序的行为将决定此行为。 选项 3:地理分散部署 在具有多个数据中心和可能地理分散的部署(例如具有多个可用性区域和地理区域的云部署)的配置中,需要使用基于 DNS 的负载均衡和本地负载均衡在一个简单且易于支持的模型中考虑地理重定向实践。 通过这种组合模型,引入了与 DNS 服务配合使用的附加网络设备,如 Amazon Route 53、F5 Global Traffic Manager、Citrix NetScaler Global Server Load Balancing 或 Cisco Global Site Selector,在每个数据中心、可用性区域或云地理区域与网络负载均衡器相结合。 在此模型中,前面提到的轮询(推荐)或 API 方法在本地用于操作任何镜像成员(故障转移或 DR 异步)的位置。这用于向地理/全球网络设备报告它是否可以将流量定向到任一数据中心。同样在此配置中,本地网络流量管理设备将其自己的 VIP 提供给地理/全球网络设备。 在正常稳定状态下,活动主镜像成员向本地网络设备报告它是主镜像成员并提供“启动”状态。此“启动”状态被转发到地理/全球设备以调整和维护 DNS 记录,以将所有请求转发到此活动的主镜像成员。 在同一数据中心内的故障转移场景中(备份同步镜像成员成为主镜像成员),API 或轮询方法与本地负载均衡器一起使用,现在重定向到同一数据中心内的新主镜像成员。由于新的主镜像成员处于活动状态,因此本地负载均衡器仍以“启动”状态响应,因此未对地理/全局设备进行任何更改。 出于本示例的目的,API 方法在下图中用于本地集成到网络设备。 在使用 API 或轮询方法到不同数据中心(备用数据中心中的同步镜像或 DR 异步镜像成员)的故障转移场景中,新提升的主镜像成员开始向本地网络设备报告为主要成员。 在故障转移期间,曾经包含主镜像成员的数据中心现在不再从本地负载均衡器向地理/全球报告“Up”。地理/全球设备不会将流量定向到该本地设备。备用数据中心的本地设备将向地理/全球设备报告“Up”,并将调用 DNS 记录更新以现在定向到备用数据中心的本地负载均衡器提供的虚拟 IP。 选项 4:多层和地理分散的部署 为了使解决方案更进一步,引入了一个单独的 Web 服务器层,既可以作为私有 WAN 的内部,也可以通过 Internet 访问。此选项可能是大型企业应用程序的典型部署模型。 以下示例显示了使用多个网络设备安全隔离和支持 Web 和数据库层的示例配置。在此模型中,使用了两个地理位置分散的位置,其中一个位置被视为“主要”位置,另一个位置纯粹是数据库层的“灾难恢复”位置。数据库层灾难恢复位置将在主要位置因任何原因停止服务的情况下使用。此外,此示例中的 Web 层将显示为双活,这意味着用户将根据各种规则(例如最低延迟、最低连接数、IP 地址范围或您认为合适的其他路由规则)定向到任一位置。 如上例所示,如果在同一位置发生故障转移,则会发生自动故障转移,并且本地网络设备现在指向新的主机。用户仍然连接到任一位置的 Web 服务器, Web 服务器及其关联的 CSP 网关继续指向位置 A。 在下一个示例中,考虑在位置 A 发生的整个故障转移或中断,其中主要和备份故障转移镜像成员都无法使用。然后,DR 异步镜像成员将被手动提升为主要和备份故障转移镜像成员。在升级后,新指定的主镜像成员将允许位置 B 的负载均衡设备使用前面讨论的 API 方法(轮询方法也是一个选项)报告“Up”。由于本地负载均衡器现在报告“启动”,基于 DNS 的设备将识别这一点并将流量从位置 A 重定向到现在的位置 B 以用于数据库服务器服务。 结论 在没有虚拟 IP 的情况下设计镜像故障转移有许多可能的排列。这些选项可应用于最简单的高可用性场景或具有多层的多地理区域部署,包括故障转移和 DR 异步镜像成员,以获得高可用性和容灾解决方案,旨在为您的应用程序维持最高水平的运营弹性. 希望本文提供了一些关于成功部署具有故障转移的数据库镜像的可能的不同组合和用例的见解,这些组合和用例适合您的应用程序和可用性要求。
文章
Jingwei Wang · 七月 21, 2022

DeepSee 的开发 - 第三部分 - 扩展Cube

增加level 到目前为止,我们所创建的每个维度都包含一个具有一个level的层级结构。在这一节中,我们将在HomeD维度的层级结构中添加一个level。 在Tutorial模型中,为HomeD维度添加一个level,如下所示。 在类浏览器中,展开HomeCity。 拖动PostalCode并把它放到HomeD维度的H1层级结构上。这一步是在City 层之后添加新的PostalCode层。 点击PostalCode,在详细信息栏中,将名称改为ZIP Code。 编译这个立方体。 Build这个立方体。 进入分析器。 展开左边的HomeD维度。将ZIP Code级别显示为行。 注意,有些成员有相同的名字。有时,有多个成员具有相同的名字是正确的。然而,在这种情况下,这是一个错误,因为邮政编码是唯一的。 一个级别只有两种方式可以拥有多个同名的成员。 级别名称是基于一个级别属性,而这个属性是不唯一的。(对于一个例子,请看我们在前一章定义的医生级别)。 该级别有一个父级别。当DeepSee创建一个级别的成员时,它不仅考虑源属性或表达式;它还考虑父成员。 在现实中,邮政编码和城市之间存在着多对多的关系,所以两者都不是对方的父级。 当我们添加邮政编码级别时,我们把它放在城市级别之后,这意味着城市是邮政编码的父级。这影响了系统为ZIP Code生成成员的方式。例如,系统认为城市Juniper的ZIP Code 32006与城市Spruce的ZIP Code 32006是不一样的。 回到模型,纠正HomeD维度。 点击ZIP Code层。 点击向上箭头按钮。 编译这个立方体。模型会保存这个立方体。 Build这个立方体。 进入分析器。 展开左边的HomeD维度。将邮政编码级别显示为行。现在显示已经正确了。 双击第32006邮政编码,现在系统会显示这个邮政编码内的所有城市。 可以选择做以下工作,看看这个变化对事实表和水平表有什么影响。 进入管理门户,进入SAMPLES命名空间,如前所述。 点击系统资源管理器 > SQL。 在左边的区域,导航到表Tutorial_Cube.Fact。 注意这个表现在除了DxNameViaHomeCity之外,还有一个字段DxPostalCodeViaHomeCity。也就是说,事实表为每个级别都存储了一个值,甚至级别之间也有关联。 在左边的区域,导航到并打开表StarNameViaHomeCity。 注意,现在该表为每个城市存储了该城市所属的邮政编码。 关闭这个表,并导航到Tutorial_Cube.StarPostalCodeViaHomeCity这个表。 这个级别表和其他级别表一样:每个级别成员有一行。 时间level 在教程的这一部分,我们向立方体添加时间级别。 Patients类包括病人的出生日期的几种形式(这样你可以用DeepSee尝试不同的格式)。 Property BirthDate As %Date; Property BirthDateTimeStamp As %TimeStamp; Property BirthDateMV As %MV.Date; DeepSee内置支持所有这三种格式,以及$HOROLOG格式和其他格式。 该类还包括病人的出生时间,作为BirthDateTimeStamp属性的一部分或作为以下属性。 Property BirthTime As %Time; 最灵活的属性是BirthDateTimeStamp,因为它同时包含了出生日期和出生时间,所以我们将用它作为时间level的基础。 进入模型,显示Tutorial cube。 点击添加元素。 在输入新元素名称时,输入 BirthD。 点击时间维度,点击确定。系统创建了一个维度,level和层级结构。 对该维度做如下修改。 点击属性旁边的搜索按钮(放大镜图标),点击BirthDateTimeStamp,然后点击确定。 将level重命名为 Year。 从函数提取值下拉列表中,选择Year。这个选项意味着这个level只基于病人的出生年份。 添加另一个level,在这个维度中点击层级结构H1。 点击添加元素。 在 "输入新元素名称 "中,输入 Month Year。 点击级别。 点击确定。系统在层级结构H1中,现有的年份level之后创建一个新的level,。 对于 Month Year级别,做如下修改。 从函数提取值下拉列表中,选择MonthYear。 这个选项意味着这个level是基于出生年份和月份的组合。 为BirthD维度添加另一个level和级别,点击BirthD维度名称。 点击添加元素。 在输入新元素名称中,键入H2。 点击层次。 点击确定。系统会创建一个新的level和级别。 对于新的level,做以下修改。 把这个level重命名为Time。 从函数提取值下拉列表中,选择HourNumber。这个选项意味着这个level是基于病人出生时的时间。 编译这个立方体。 Build立方体。 访问分析器。 当你在左边区域展开BirthD - Year,'现在' 是一个特殊的成员,指的是当前的一年。 使用集合属性 你可以基于集合属性来创建级别。具体来说,系统可以直接使用$LIST、%List返回的类型的列表,或者以字符分隔的列表。如果一个集合属性以某种其他方式存储数据,则有必要提取必要的数据并创建一个支持的列表类型。 DeepSee.Study.Patient类有几个集合属性,包括Allergies和DiagnosesAsLB。DiagnosesAsLB属性定义如下。 Property DiagnosesAsLB As %List; Property Allergies As list Of BI.Study.PatientAllergy; 这一部分告诉你如何创建使用这些属性的level和度量。 访问模型,显示Tutorial cube。 添加一个使用DiagnosesAsLB属性的维度、层次结构和level。 点击添加元素。 在输入新元素名称中,输入DiagD。 点击数据维度。 点击确定。系统创建了一个维度,层级结构和级别。 把这个level重命名为 "Diagnoses"。 当level被选中时,点击属性的搜索按钮,选择DiagnosesAsLB属性,并点击确定。 对于源值是一个列表的类型,在资源值列表类型下拉菜单中选择 $LIST结构。这种类型指的是具有$LIST函数返回的格式或具有%List类型的数据。 保存立方体类。 在模型中,像以前一样添加一个维度、level和级别,并做如下修改。 维度名称应该是AllerD。 level名称应该是Allergies。不要为属性指定一个值。因为没有我们可以直接使用的属性。我们有必要通过表达式来提取Allergy列表。 为表达式指定以下值。 ##class(Tutorial.Cube).GetAllergies(%source.%ID) 当系统Build立方体时,对于事实表中的每一行,系统都会评估这个表达式一次。 变量%source指的是当前记录。这个表达式得到病人的ID,调用utility方法(我们还没有写),并返回病人的过敏列表。 在资源值列表类型下拉菜单中选择 $LIST结构。 然后保存。 下一步将是编写这个实用方法。 打开Studio,访问SAMPLES命名空间。打开你的cube类,Tutorial.Cube。 添加一个名为GetAllergies()的方法,如下所示。 ClassMethod GetAllergies(ID As %Numeric) As %List { Set allergies=##class(BI.Study.Patient).%OpenId(ID,0).Allergies If (allergies.Count()=0) {Quit $LISTFROMSTRING("")} Set list="" For i=1:1:allergies.Count() { Set $LI(list,i)=allergies.GetAt(i).Allergen.Description } Quit list } 给定一个病人的ID,这个方法返回该病人的过敏列表,格式是我们创建的级别所期望的。 %OpenId()的第二个参数指定了要使用的并发锁的级别。因为我们只需要从对象中读取数据,所以我们把这个值指定为0,这样就不会建立并发锁定,从而运行得更快。 在Studio中保存并编译你的cube类。 添加一个包含病人拥有的过敏症数量的度量。为此,我们使用Allergies属性,如下所示。 返回到模型。 点击添加元素。 在 "输入新元素名称 "中,输入 "Avg Allergy Count"。 点击 度量。 点击确定。 在汇总中,单击AVG。 对于表达式,输入以下内容。 ##class(Tutorial.Cube).GetAllergyCount(%source.%ID) 我们将在以后写这个方法。 点击保存。因为你已经在Studio中编辑了这个类,模型显示了一个对话框,询问你是否要覆盖存储的定义。点击 OK。模型只覆盖你可以在模型中编辑的类定义的部分,也就是说,它不覆盖你已经添加到类中的任何方法。 在 Studio 中,给你的立方体类添加以下方法。 ClassMethod GetAllergyCount(ID As %Numeric) As %Numeric { Set allergies=##class(BI.Study.Patient).%OpenId(ID,0).Allergies Quit allergies.Count() } 保存并编译Studio中的立方体类。 重新Build DeepSee立方体。要做到这一点,你可以返回到模型,用你之前的方法重建。或者你可以打开一个终端窗口,在SAMPLES命名空间输入以下命令。 do ##class(%DeepSee.Utils).%BuildCube("tutorial") 注意,该方法使用了立方体的逻辑名称(而不是类的名称)。也注意到立方体的名字不区分大小写。 访问分析器。 以行的形式显示诊断。 将新的过敏显示为行,并显示Count和Avg Allergy Count度量。 null成员(称为None)代表了Allergies属性为null的病人。因为假设这些病人没有过敏症是不正确的,所以这个成员的名字有误导性。一个更好的名字是No Data Available。 请注意,对于属于None成员的病人,平均过敏数测量值为0。对这些病人来说,平均过敏数的测量值应该是空的。 同样注意到,对于没有已知过敏症的病人,平均过敏计数是1。这是因为Allergies属性确实包括特殊的nil known allergies。这些病人的平均过敏数应该是0。在本节后面,我们将纠正None成员的名称,并调整我们对平均过敏数测量的逻辑。 返回到模型。 点击 Allergies(过敏)层。 对于空替换字符串,指定No Data Available。 保存立方体类。 在Studio中,编辑GetAllergyCount()方法,如下。 ClassMethod GetAllergyCount(ID As %Numeric) { Set allergies=##class(BI.Study.Patient).%OpenId(ID,0).Allergies //check to see if patient has any recorded allergy data //if not, count is null If allergies.Count()=0 { Set allcount="" } //check to see if patient has "Nil known allergies" //in this case, the patient has one "allergen" whose code is 000 Elseif ((allergies.Count()=1) && (allergies.GetAt(1).Allergen.Code="000")) { Set allcount=0 } Else { Set allcount=allergies.Count() } Quit allcount } 保存立方体类。编译cube类。 在模型中Build立方体。 访问分析器。将过敏症显示为行,并显示计数和平均过敏症计数的度量。 可以选择做以下工作,看看基于列表的级别是如何在事实和级别表中表示的。 进入管理门户,进入SAMPLES命名空间,点击系统资源管理器>SQL。 在左侧区域,导航并打开表Tutorial_Cube.Fact,滚动到字段DxDiagnosesAsLB。 这个字段包含病人的诊断。注意它在某些情况下包含多个值。该表也显示过敏水平,这个字段的名字不太明显,因为它是生成的,因为这个级别本身是基于表达式的。 现在导航到并打开Tutorial_Cube.StarDiagnosesAsLB这个表。这个level表和其他level表一样:每个level成员有一行。 定义替换值 在本教程的这一部分,我们使用将level的原始值转换为其他值的选项。这里我们将使用病人的年龄属性。我们将定义level,将病人放入大于一年的桶中。 Age Group级别将有以下成员。 0到29岁的成员由30岁以下的病人组成。 30至59岁的成员包括30至59岁的病人。 60岁以上成员由60岁以上的病人组成。 同样,Age Bucket级别将有0到9岁的成员,10到19岁的成员,以此类推。 访问模型。 按以下方法给AgeD维度增加一个level。 点击Age层。这样可以确保新的level,将被添加到Age 层之前。 点击添加元素。 在输入新元素名称时,键入Age Group。 点击级别。 点击确定。 重新定义新的年龄组级别,使其有一个范围表达,如下所示。 点击Age Group级别。 对于事实表中的字段名,指定DxAgeGroup这将使我们更容易看到级别定义如何影响生成的表。 对于属性,输入Age。 单击 "范围表达式 "旁边的搜索按钮。系统会显示一个对话框,你在这里指定一组替换。 对于数字数据,对于每一个替换,你要指定一个原始值的范围,以及一个新的值来代替。 在To中键入29。 点击To右边的按钮,使其变为中括号。 在Replacement Value中键入0到29。结果如下。 点击添加替换值。 在新行中,点击From和To旁边的切换按钮。 在From中输入30,在To中输入59。 在替换值中键入30至59。 单击 "添加替换",添加最后一行,使结果如下。 单击 "确定"。 保存立方体。 对于Age Bucket级别,我们可以使用同样的技术。然而,我们将使用一个替代方法:一个源表达式,将以Age为单位的年龄转换为一个字符串,对应于适当的十年。 在Studio中,打开BI.Tutorial.Cube类。 看看GetAgeBucket()方法的定义,其内容如下。 ClassMethod GetAgeBucket(age As %Numeric) As %String { If (age="") {Set return=""} ElseIf (age<10) {Set return="0 to 9"} ElseIf (age<20) {Set return="10 to 19"} ElseIf (age<30) {Set return="20 to 29"} ElseIf (age<40) {Set return="30 to 39"} ElseIf (age<50) {Set return="40 to 49"} ElseIf (age<60) {Set return="50 to 59"} ElseIf (age<70) {Set return="60 to 69"} ElseIf (age<80) {Set return="70 to 79"} ElseIf (age>=80) {Set return="80+"} Else {Set return=""} Quit return } 注意这个方法的输入只是一个数字,而不是一个病人的标识符。 在模型中,按以下方法给AgeD增加一个level。 点击Age层。这样可以确保新的level,即颗粒度较小的level,将在Agelevel之前被添加。 点击 Add Element。 对于输入新元素名称,输入 Age Bucket。 点击级别。 点击确定。 新的级别被添加到Age之前,但在Age Group之后。 对于事实表中的字段名,指定DxAgeBucket这将使我们更容易看到级别定义如何影响生成的表。 对于表达式,输入以下内容。 ##class(BI.Tutorial.Cube).GetAgeBucket(%source.Age) 注意。 在实践中,你更有可能在一个中心位置包括Utility方法,如使用它们的立方体类。这个练习的一个要点是证明你可以调用这个命名空间中可以访问的任何类方法。同样地,你也可以调用任何常规或系统函数。 保存立方体。因为你已经在Studio中编辑了这个类,模型显示了一个对话框,询问你是否想覆盖存储的定义。点击确定。模型只覆盖了你可以在模型中编辑的类定义的部分,也就是说,它不覆盖你添加到类中的任何方法。 编译这个立方体。 重新Build立方体。 进入分析器。 显示Age Group 为行。将Age Bucket级别显示为行。你现在应该看到像下面这样的东西。 检查其中一个新的级别表,了解系统做了什么。 访问管理门户,进入SAMPLES命名空间,如前所述。点击系统资源管理器>SQL。 在左边的区域,导航到并打开表Tutorial_Cube.Fact。 这个表现在有三个字段来存储AgeD层级结构的值。 导航到并打开Tutorial_Cube.DxAgeGroup表。 打开Tutorial_Cube.DxAgeBucket表。因为这个level不在层级结构的顶端,所以它包含了一个引用,对于每个元素,它的父级成员在年龄组level;见DxAgeGroup列。 访问其他类 DeepSee 模型提供了对基类中大多数属性的简单访问,但我们也可以使用其他属性,包括你只能通过 SQL 访问的类的属性。在本教程的这一部分,我们使用DeepSee.Study.PatientDetails类中的数据作为我们立方体中的level。 DeepSee.Study.Patient和DeepSee.Study.PatientDetails类没有通过类属性连接,也没有任何正式的连接。相反,这两个表都有一个PatientID属性,它通过惯例将它们连接起来。也就是说,要找到某个病人的信息,你必须在这两个表中找到具有相同PatientID的记录。 在这个练习中,我们检查DeepSee.Study.PatientDetails中的数据,尝试各种SQL查询,并将查询包在一个方法中,用于定义一个级别。如果你对SQL比较熟练,你可能想跳过前面的一些步骤。 进入管理门户,进入SAMPLES命名空间,如前所述。点击系统资源管理器 > SQL。执行下面的查询。 SELECT PatientID FROM DeepSee_Study.Patient 记下其中的一个PatientID值,执行下面的查询。 SELECT * FROM DeepSee_Study.PatientDetails WHERE PatientID='SUBJ_100301' SELECT FavoriteColor FROM DeepSee_Study.PatientDetails WHERE PatientID='SUBJ_100301' 现在我们需要写一个类方法,运行一个类似的查询,并返回查询得到的值。这个方法将包含一个用&sql()包裹的查询。 在Studio中,在你的立方体类Tutorial.Cube中添加以下方法。 ClassMethod GetFavoriteColor(patientID As %String) As %String { &sql(SELECT FavoriteColor INTO :ReturnValue FROM BI_Study.PatientDetails WHERE PatientID=:patientID) If (SQLCODE'=0) { Set ReturnValue="" } Quit ReturnValue } 注意: 在DeepSee.Study.PatientDetails中的PatientID字段上有一个索引。这使得查询的运行速度比其他方式更快。 保存并编译该类。 在终端中,按如下方式测试该方法。 SAMPLES>write ##class(Tutorial.Cube).GetFavoriteColor("SUBJ_100301") 访问模型。 创建一个新的维度、level和级别,如下所示。 点击添加元素。 在输入新元素名称时,输入ColorD。 点击数据维度。 点击确定。系统会创建一个维度、level和级别。 将level重命名为喜爱的颜色。 对于事实表中的字段名,指定DxFavColor这将使我们更容易看到level定义如何影响生成的表。 对于级别,在表达式中键入以下内容。 ##class(Tutorial.Cube).GetFavoriteColor(%source.PatientID) 保存立方体。因为你已经在Studio中编辑了这个类,Architect显示一个对话框,询问你是否要覆盖存储的定义。点击 OK。模型只覆盖你可以在模型中编辑的类定义的部分,也就是说,它不覆盖你已经添加到类中的任何方法。 编译这个立方体。 重新build立方体。 系统对基表的每条记录执行一次你的方法和它的嵌入式 SQL。 打开分析器,将新的级别显示为行。
文章
Nicky Zhu · 一月 11, 2021

ObjectScript包管理器中的单元测试和测试覆盖率

本文将描述通过ObjectScript包管理器(见https://openexchange.intersystems.com/package/ObjectScript-Package-Manager-2)运行单元测试的过程,包括测试覆盖率测量(见https://openexchange.intersystems.com/package/Test-Coverage-Tool)。 ## ObjectScript中的单元测试 关于在ObjectScript中编写单元测试,已经有很好的文档,因此我就不再赘述了。您可以在这里找到单元测试教程:https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=TUNT_preface 最好的做法是将单元测试代码单独放在源代码树中,无论它只是“/tests”还是其他名字。在InterSystems中,我们最终使用/internal/testing/unit_tests/作为我们事实上的标准,这是有意义的,因为测试是内部/非发布的,而且除了单元测试还有其他类型的测试,但这对于简单的开源项目来说可能有点复杂。您可以在我们的一些GitHub仓库中看到这种结构。 从工作流的角度来看,这在VSCode中非常简单,您只需创建目录并将类放在里面。对于较老的以服务器为中心的源代码控制方法(Studio中使用的方法),您需要正确地地映射这个包,使用的方法会因源代码控制程序而异。 从单元测试类命名的角度来看,我个人的偏好(以及我的团队的最佳实践)是: UnitTest.[.] 例如,如果在类MyApplication.SomeClass 中对方法Foo进行单元测试,单元测试类将被命名为UnitTest.MyApplication.SomeClass.Foo;如果测试是针对整个类的,那么名字就是UnitTest.MyApplication.SomeClass。 ## ObjectScript 包管理器中的单元测试 让ObjectScript包管理器知道您的单元测试,很简单!只需按如下所示向module.xml中添加一行代码(来自https://github.com/timleavitt/ObjectScript-Math/blob/master/module.xml - 这是Open Exchange上的@Peter Steiwer的出色数学扩展包,我以它作为简单的正面例子): ```objectscript   ...   ``` 这些代码的意思是: - 单元测试位于模块根目录下的“tests”目录中。 - 单元测试在“UnitTest.Math”包中。这样很直观,因为被测试的类就在“Math”包中。 - 单元测试在包生命周期的“测试”阶段运行。(当然还有一个可以运行它们的“验证”阶段,这里不赘述。) ## 运行单元测试 对于上述定义的单元测试,包管理器提供了一些实用工具来运行它们。您仍然可以像平常使用%UnitTest.Manager那样设置^UnitTestRoot等,但下面的方法可能更简单,尤其在同一个环境下做几个项目的时候。 您可以克隆上述objectscript-math仓库,然后用 ```zpm "load /path/to/cloned/repo/" ```加载,或者在您自己的包上使用包名(和测试名)替换“objectscript-math”来尝试所有这些方法。 重新加载模块,然后运行所有单元测试: ```zpm "objectscript-math test" ``` 只运行单元测试(不重新加载): ```zpm "objectscript-math test -only" ``` 只运行单元测试(不重新加载)并提供详细输出: ```zpm "objectscript-math test -only -verbose" ``` 运行一个特定的测试套件(指一个测试目录 - 在本例中,是UnitTest/Math/Utils中的所有测试)而不重新加载,并提供详细输出: ```zpm "objectscript-math test -only -verbose -DUnitTest.Suite=UnitTest.Math.Utils" ``` 运行一个特定的测试用例(在本例中,是UnitTest.Math.Utils.TestValidateRange)而不重新加载,并提供详细输出: ```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange" ``` 如果您只是想解决单个测试方法中的小问题: ```zpm "objectscript-math test -only -verbose -DUnitTest.Case=UnitTest.Math.Utils.TestValidateRange -DUnitTest.Method=TestpValueNull" ``` ## 通过ObjectScript包管理器进行测试覆盖率测量 怎样评估单元测试的质量?测量测试覆盖率虽然并不全面,但至少有参考意义。早在2018年的全球峰会上,我就展示过。 见 - https://youtu.be/nUSeGHwN5pc。 首先需要安装“测试覆盖率”包: ```zpm "install testcoverage" ``` 注意,并不需要ObjectScript包管理器才能安装/运行;可以在Open Exchange上了解更多信息:https://openexchange.intersystems.com/package/Test-Coverage-Tool 不过如果您已经在用ObjectScript包管理器,那么您可以更好地利用这个“测试覆盖率”工具。 运行测试前,需要指定测试所覆盖的类/routine宏。这一点很重要,因为在非常大的代码库中(例如,HealthShare),测试和收集项目中所有文件的测试覆盖率所需要的内存可能超出您的系统内存。(提一句,如果您感兴趣,可以使用逐行监视器的gmheap。) 文件列表在您的单元测试根目录下的coverage.list文件中;单元测试的不同子目录(套件)可以拥有它们自己的副本,以覆盖在测试套件运行时将跟踪的类/例程。 有关objectscript-math的简单示例,见:https://github.com/timleavitt/ObjectScript-Math/blob/master/tests/UnitTest/coverage.list;测试覆盖率工具用户指南有更详细的介绍。 要在启用测试覆盖率测量的情况下运行单元测试,只需再向命令添加一个参数,指定应使用TestCoverage.Manager而非%UnitTest.Manager 来运行测试: ```zpm "objectscript-math test -only -DUnitTest.ManagerClass=TestCoverage.Manager" ``` 输出(即使是非详细模式)将包括一个URL,供您查看您的类/routine(宏)的哪些行被单元测试覆盖了,以及一些汇总统计信息。 ## 接下来的步骤 这些能不能在CI中自动化?能不能报告单元测试的结果和覆盖率分数/差异?答案是:能!您可以使用Docker,Travis CI和codecov.io来试一下这个简单示例,见https://github.com/timleavitt/ObjectScript-Math;我打算以后写篇文章来详细讲讲,介绍几种不同的方法。
文章
姚 鑫 · 五月 13, 2021

第五章 管理全局变量(二)

# 第五章 管理全局变量(二) # 在全局变量中查找值 “查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。 要访问和使用此页,请执行以下操作: 1. 显示“全局变量”页。 2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。 3. 单击查找按钮。 4. 对于查找内容,输入要搜索的字符串。 5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。 6. 单击Find First或Find All。 然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。 7. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。 8. 完成后,单击关闭窗口。 ## 执行批量更换 注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。 出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项: 1. 显示“全局”页面。 2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击替换按钮。 4. 使用此页面查找上一节中描述的值。 5. 为“替换为”指定一个值。 6. 单击全部替换。 7. 单击确定确认此操作。然后,页面会显示变更的预览。 8. 如果结果可以接受,请单击保存。 9. 单击确定确认此操作。 # 导出全局变量 注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统; “导出全局”页面允许导出全局。 要访问和使用此页面: 1. 显示“全局”页面。 2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击导出按钮。 4. 指定要将全局文件导出到的文件。为此,请在输入服务器上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。 5. 使用字符集列表选择导出文件的字符集。 6. 在页面的中央框中:选择输出格式,选择记录格式 7. 选择或清除“在此检查”以在后台运行导出... 8. 单击导出。 9. 如果文件已经存在,请单击“确定”用新版本覆盖它。 导出会创建一个. gof文件。 # 导入全局变量 注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。 “导入全局”页面允许导入全局。要访问和使用此页面: 1. 显示“全局”页面。 2. 单击导入按钮。 3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。 4. 使用字符集列表选择导入文件的字符集。 5. 选择下一步。 6. 使用表中的复选框选择要导入的全局。 7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。 8. 单击导入。 # 删除全局变量 注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。 “删除全局”页面允许删除全局。要访问和使用此页面: 1. 显示“全局”页面。 2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击删除按钮。 4. 单击确定确认此操作。 # 管理任务的应用程序接口 InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务: - 类`%SYSTEM.OBJ`提供了以下方法: - `Export()`使能够将全局导出到一个XML文件。 - `Load()`和`LoadDir()`使能够导入包含在`XML`文件中的全局。 这两者都可以通过$SYSTEM变量获得,例如:`$SYSTEM.OBJ.Export` - 类别`%Library.Global`提供了以下方法: - `Export()`使能够将全局导出到`.gof`和其他文件格式(不包括XML)。 - `Import()`使能够将全局导入到`.gof`和其他文件格式(不包括XML)。 `%Library.Global` 还提供了`Get()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。 ![image](/sites/default/files/inline/images/1_44.png)
文章
姚 鑫 · 五月 13, 2021

第五章 管理全局变量(二)

# 第五章 管理全局变量(二) # 在全局变量中查找值 “查找全局变量字符串”页使可以在下标或选定全局变量的值中查找给定的字符串。 要访问和使用此页,请执行以下操作: 1. 显示“全局变量”页。 2. 选择要使用的全局变量。为此,请参阅“全局页简介”一节中的步骤2和3。 3. 单击查找按钮。 4. 对于查找内容,输入要搜索的字符串。 5. (可选)清除大小写匹配。默认情况下,搜索区分大小写。 6. 单击Find First或Find All。 然后,页面显示选定全局变量中下标或值包含给定字符串的第一个节点或所有节点。该表左侧显示了节点下标,右侧显示了相应的值。 7. 如果使用的是Find First,请根据需要单击Find Next以查看下一个节点。 8. 完成后,单击关闭窗口。 ## 执行批量更换 注意:在进行任何编辑之前,请确保知道IRIS使用哪个全局系统,以及应用程序使用哪个全局系统;参见“一般建议”此选项会永久更改数据。不建议在生产系统中使用。 出于开发目的,“查找全局字符串”页面还提供了对全局节点中的值进行整体更改的选项。要使用此选项: 1. 显示“全局”页面。 2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击替换按钮。 4. 使用此页面查找上一节中描述的值。 5. 为“替换为”指定一个值。 6. 单击全部替换。 7. 单击确定确认此操作。然后,页面会显示变更的预览。 8. 如果结果可以接受,请单击保存。 9. 单击确定确认此操作。 # 导出全局变量 注意:因为导入全局是非常容易的(这是一个不可逆转的变化),所以最好只导出需要导入的全局。请注意,如果导出所有全局变量,导出将包括所有包含代码的全局变量。请确保知道IRIS使用哪些全局系统,以及应用程序使用哪些全局系统; “导出全局”页面允许导出全局。 要访问和使用此页面: 1. 显示“全局”页面。 2. 指定要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击导出按钮。 4. 指定要将全局文件导出到的文件。为此,请在输入服务器上的导出路径和名称字段中输入文件名(包括其绝对或相对路径名),或者单击浏览并导航到该文件。 5. 使用字符集列表选择导出文件的字符集。 6. 在页面的中央框中:选择输出格式,选择记录格式 7. 选择或清除“在此检查”以在后台运行导出... 8. 单击导出。 9. 如果文件已经存在,请单击“确定”用新版本覆盖它。 导出会创建一个. gof文件。 # 导入全局变量 注意:在导入任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。将全局导入现有全局(从而合并数据)后,无法将全局恢复到其以前的状态。 “导入全局”页面允许导入全局。要访问和使用此页面: 1. 显示“全局”页面。 2. 单击导入按钮。 3. 指定导入文件。为此,请在输入导入文件的路径和名称字段中输入文件(包括其绝对或相对路径名),或者单击浏览并导航到该文件。 4. 使用字符集列表选择导入文件的字符集。 5. 选择下一步。 6. 使用表中的复选框选择要导入的全局。 7. 也可以选择在后台运行导入。如果选择此项,任务将在后台运行。 8. 单击导入。 # 删除全局变量 注意:在删除任何全局变量之前,请确保知道IRIS使用哪些全局变量,以及应用程序使用哪些全局变量;参见“一般建议”没有撤消选项。无法恢复已删除的全局。 “删除全局”页面允许删除全局。要访问和使用此页面: 1. 显示“全局”页面。 2. 选择要使用的全局。为此,请参见“全球页面简介”一节中的步骤2和3 3. 单击删除按钮。 4. 单击确定确认此操作。 # 管理任务的应用程序接口 InterSystems IRIS还提供了以下应用编程接口来执行本章中描述的一些任务: - 类`%SYSTEM.OBJ`提供了以下方法: - `Export()`使能够将全局导出到一个XML文件。 - `Load()`和`LoadDir()`使能够导入包含在`XML`文件中的全局。 这两者都可以通过$SYSTEM变量获得,例如:`$SYSTEM.OBJ.Export` - 类别`%Library.Global`提供了以下方法: - `Export()`使能够将全局导出到`.gof`和其他文件格式(不包括XML)。 - `Import()`使能够将全局导入到`.gof`和其他文件格式(不包括XML)。 `%Library.Global` 还提供了`Get()`类查询,根据给定的搜索条件,可以使用该查询来查找全局。 ![image](/sites/default/files/inline/images/1_44.png)
文章
Michael Lei · 四月 17, 2022

翻译博客文章--浏览医疗保健的未来

在最近一次探索马里兰小镇的 "度假 "期间,我偶然发现了一家非常令人愉快的书店,在那里我愉快地消磨了一下午。我和我的家人都是读者,喜欢各种类型的书--新的、二手的、印刷的、电子的。我们尽量在当地购物,以帮助零售店保持运营。 这次访问促使我思考图书行业所发生的事情与我们的医疗保健系统所发生的事情之间的一些相似之处。 医疗保健行业与图书行业的趋势 数字化 我们阅读内容的格式已经发生了根本性的变化。在2020年,电子书几乎占美国市场的四分之一。音频书占美国图书收入的10亿美元。许多印刷书籍是按需出版的,而不是保存在库存中。同样,医疗保健早已不再是一个“伸出舌头说啊 ”的行业,基因组测试、由人工智能算法读取的X射线、可植入设备和远程医疗访问已经改变了医疗的面貌。 虚拟服务 书店现在有多种形式,医疗机构也是如此。订阅图书服务,从当地独立的小公司、大的连锁店、电子零售的网上订单。而与你的本地门诊竞争的是你手机上的一个应用程序。同样,你的治疗师可能是一个机器人,你的基层医疗服务可能由你社区附近药店的驻店医师提供,你可能在一个办公园区做手术。在所有这些竞争中,我们如何确保在我们需要时仍有健康的、提供全面服务的医院? 更智能的算法 分析和预测模型现在几乎和个人推荐一样重要。过去,当我想要一本书的建议,或者一个医生,我就会问朋友。虽然我仍然这样做,但我也同样有可能去看Goodreads,或查看在线医生评论。当我进行搜索时,亚马逊、苹果或谷歌也同样可能提供他们的建议,不管我是否要求它们。他们知道我是谁,我的购买模式是什么,我检查过哪些疾病和症状,以及在当地急诊科订购书籍或看医生的等待时间是什么。 合并和收购 无论你是卖书还是卖医疗服务,改变或死亡都是关键词。我们附近的一家大的巴诺书店(Barnes & Nobles,美国最大的实体书店)最近搬到了一个不到以前一半的地方。大多数独立书店出售的礼品和书籍一样多,而且许多书店同时出售新货和二手货。像Alibris这样的网站将当地的小企业与世界各地的买家联系起来。同样,根据普华永道的数据,2021年医疗保健业的合并和收购增长了56%,预计这一趋势在2022年还会继续。IQVIA艾昆纬研究所的一份报告发现,在主要的应用程序商店中,有大约35万个数字健康应用程序。而美国最大的零售商都在医疗保健领域进行了大量投资。例如,亚马逊是颠覆性的电子书业务的主要参与者,现在也有实体书店,它正积极地进入医疗保健服务领域,包括线上和线下服务。 对未来医疗的影响 对未来医疗的影响是什么?有趣的是,这两种业务都唤起了人们对也许是神话般的过去的相当大的怀念。虽然现在的实体书店比以前少了,但书籍实际上比以前更容易获得。对很大一部分人来说,亲切的家庭医生上门服务从来就不是那么容易的,而且不同地区采用的差异巨大的护理标准也不一定能带来最好的结果。 新世界秩序的便利性和选择令人难以置信地吸引人,无论我是在手机上购买一本书,还是在街上走到我附近的CVS公司购买Covid 疫苗加强针。但是,一方面浏览完所有这些选择也会让人感到困惑,另一方面我不想失去浏览当地商店的货架或者我年迈的母亲随时获得住院床位的选择。 没有任何整齐划一的策略可以向前推进。因此,作为一个消费者,我将继续光顾本地小店来帮助他们经营下去。作为一名医疗IT专业人士,我将继续关注如何利用信息来指导未来,带着一点点害怕,但更多的还是兴奋来展望未来。 关于作者: Kathleen Aller负责InterSystems公司的医疗市场战略。她在医疗和技术领域有多年的经验,在分析、患者管理、电子健康记录、医疗信息共享以及质量和绩效评估方面有专长。 博客原文:https://www.intersystems.com/pulse-blog/browsing-the-future-of-healthcare
文章
Michael Lei · 六月 18, 2023

医疗行业的未来--数据与人的融合

在数字化时代,数据的重要性无可置疑。数据作为新型生产要素,不仅在宏观政策层面得到党和政府的大力推动,也是医院高质量发展的关键和改变医疗行业的驱动力。随着医疗信息化的迅猛发展,我们正迈向一个数据随处可及、人人可用易用的医疗信息化时代。这一时代将数据与人的需求相结合,致力于让数据能“主动”找到需要他们的医护人员和患者,每一个行业从业者,都应致力于为医护人员和患者提供简单易用的软件解决方案,减少工作量,提高效率,推动医疗行业的进步。 数据与人的融合是实现医疗行业数字化转型的核心。当然,医疗数据的收集、存储和管理对于提供高质量的医疗服务至关重要。然而,仅仅有大量的数据并不足够,我们需要将数据与人的需求紧密结合起来。这意味着我们应该让更多的数据关联起来,并且能服务于更多的人群,让患者能够随时随地访问他们的电子病历,让医生和科研人员也能及时有效地获取病人在医院围墙内外进行治疗和健康管理的数据,并且以直观易懂的方式呈现给医护人员和患者,使他们能够快速、准确地获取所需的信息。数据的融合还包括将不同来源的数据整合起来,为医护人员提供全面、完整的视图,同时基于医疗诊断的规则,不管是通过CDSS的形式,还是通过ChatBot(聊天机器人),帮助他们做出更好的决策。 实现数据和人的融合要按照人的需求投放数据。数字化转型的重要目标是为医护人员和患者提供所需的数据,以支持决策和治疗过程。这意味着我们应该了解用户的需求,将数据按照他们的角色、职责和关注点进行分类和投放。医生可能需要即时的患者数据、病历历史和最新的医学研究,而患者可能需要查看自己的健康记录、预约医生和接收个性化的健康建议。通过根据人的需求进行数据投放,新型软件可以提供个性化的服务和支持,形成千人千面,为每个用户提供有价值的信息。 简单易用是实现数字化转型成功的另一个关键。医护人员和患者使用的软件解决方案应该简单易用,不需要复杂的培训和技术知识。界面应该简单、直观、友好,操作流程简化和优化,以确保用户能够快速上手并高效地使用软件。简单易用的软件不仅能够减少用户的学习曲线和工作负担,还能提高用户满意度和工作效率。(比如Apple的医疗软件Apple Health,通过FHIR 技术,通过一个app能够连接数千家医院的病历数据,让患者可以通过一个app实现多家医院的互联网服务和数据整合) 无论是数字化转型还是高质量发展,软件为人服务始终是医疗信息化的核心宗旨。我们应该将软件看作是为人服务的工具,旨在帮助医护人员提供更好的医疗服务,提升患者的体验和健康结果。软件应该以用户体验为中心,并不断优化和改进,不断进行供给侧改革,以满足不断变化和不同人群的需求,而不是增加负担。 最后,数据会在安全可靠的前提下进行传递和流通。在互联网发展的早期时代,由于无法可依,野蛮生长,数据的滥用、隐私保护等存在很大问题。但随着《数据安全法》等法律法规的发布,相信未来的医疗行业数据一定会在更加安全、可靠、合规的前提下进行有序流动。 在未来的医疗信息化发展中,数据与人的关系将变得更加密不可分。通过数据的融合、按需投放、简单易用、安全可靠和以人为本的新一代软件,我们可以实现数据随处可及、人人可用易用的医疗信息化目标。这将为医护人员和患者提供更好的工作环境和医疗体验,推动整个医疗行业向前迈进。InterSystems公司作为创新性的数据平台解决方案供应商,我们始终致力于助力合作伙伴开发创新的解决方案,与合作伙伴一起共同实现这一愿景,改善医疗服务的质量和效率,提高患者体验的获得感的同时帮助医院降本增效,实现高质量发展。
文章
姚 鑫 · 六月 14, 2023

第五十七章 镜像中断程序 - 在手动故障转移之前确定备份是否处于活动状态

# 第五十七章 镜像中断程序 - 在手动故障转移之前确定备份是否处于活动状态 ## 在手动故障转移之前确定备份是否处于活动状态 假设有两个名为 `IRIS A` 和`IRIS B` 的故障转移成员。如果 `^MIRROR` 例程确认备份 (`IRIS B`) 在与主 (`IRIS A`) 丢失联系时处于活动状态,因此具有最新的来自 `IRIS A` 的日志数据,可以使用单个过程手动进行故障转移。当连接因主要故障而丢失时,不会造成数据丢失的风险。但是,当发生多个故障时,活动备份可能没有来自主服务器的所有最新日志数据,因为主服务器在连接丢失后继续运行了一段时间。 使用以下过程确定备份是否处于活动状态: 1. 确认 `IRIS` 实例 `IRIS A` 上的 `ISCAgent` 实际上已关闭(并确保它们在整个手动故障转移过程中保持关闭状态)。 2. 在 `IRIS B` 上,在终端的 `%SYS` 命名空间中运行 `^MIRROR` 例程(请参阅使用 `^MIRROR` 例程)。 3. 在主菜单中选择镜像管理,显示如下子菜单: ```java 1) Add mirrored database(s) 2) Remove mirrored database(s) 3) Activate or Catchup mirrored database(s) 4) Change No Failover State 5) Try to make this the primary 6) Connect to Mirror 7) Stop mirroring on this member 8) Modify Database Size Field(s) 9) Force this node to become the primary 10) Promote Async DR member to Failover member 11) Demote Backup member to Async DR member 12) Mark an inactive database as caught up 13) Manage mirror dejournaling on async member (disabled) 14) Pause dejournaling for database(s) ``` 4. 选择 `Force this node to become the primary` 选项。如果在联系丢失时备份处于活动状态,则会显示如下消息: ```java This instance was an active backup member the last time it was connected so if the primary has not done any work since that time, this instance can take over without having to rebuild the mirror when the primary reconnects. If the primary has done any work beyond this point (file #98), C:\InterSystems\MyIRIS\mgr\journal\MIRROR-GFS-20180815.009 then the consequence of forcing this instance to become the primary is that some operations may be lost and the other mirror member may need to be rebuilt from a backup of this node before it can join as a backup node again. Do you want to continue? ``` 如果有权访问主要文件的日志文件,则可以在继续之前确认引用的文件是最新的。 如果在与主服务器失去联系时备份未处于活动状态,则会显示如下消息: ```java Warning, this action can result in forcing this node to become the primary when it does not have all of the journal data which has been generated in the mirror. The consequence of this is that some operations may be lost and the other mirror member may need to be rebuilt from a backup of this node before it can join as a backup node again. Do you want to continue? ``` ## 手动故障转移到活动备份 如果 `^MIRROR` 例程的 `Force this node to become the primary` 选项确认备份在失去与主节点的连接时处于活动状态,请完成手动故障转移过程,如下所示: 1. 在要继续吗?提示继续该过程。 `Force this node to become the primary` 选项等待 `60` 秒以使镜像成员成为主要节点。如果操作未在 `60` 秒内成功完成,`^MIRROR` 报告操作可能未成功并指示您检查消息日志以确定操作是失败还是仍在进行中。 2. 一旦 `^MIRROR` 例程确认备份已成为主要备份,请在可以这样做时重新启动 `IRIS A`。当 `IRIS` 实例重新启动时, `IRIS A` 作为备份加入镜像。 ## 备份不活动时手动故障转移 即使 `^MIRROR` 例程未确认备份 ( `IRIS B`) 在与主 ( `IRIS A`) 失去连接时处于活动状态,仍然可以使用以下过程继续手动故障转移过程,但是如果这样做,会有数据丢失的风险。如本程序所述,可以在手动故障转移之前将最新的镜像日志文件从 `IRIS A`(如果有权访问)复制到 `IRIS` B,从而最大限度地降低这种风险。 1. 如果有权访问主服务器的镜像日志文件,请将最新的文件复制到 `IRIS B`,从 `IRIS B` 上的最新日志文件开始,然后包括来自 `IRIS A` 的任何后续文件。例如,如果 `MIRROR-MIRRORA-20180220.001`是 `IRIS B` 上的最新文件,复制 `MIRROR-MIRRORA-20180220.001` 和 `IRIS A` 上的任何更新文件。检查文件的权限和所有权,并在必要时更改它们以匹配现有日志文件。 2. 如果接受数据丢失的风险,请在提示时输入 `y` 以确认要继续;备份成为主要的。 `Force this node to become the primary` 选项等待 `60` 秒以使镜像成员成为主要节点。如果操作未在 `60` 秒内成功完成,`^MIRROR` 报告操作可能未成功并指示您检查消息日志以确定操作是失败还是仍在进行中。 3. 一旦 `^MIRROR` 例程确认备份已成为主要备份,请在可以这样做时重新启动 `IRIS A`。 - 如果 `IRIS A` 在 `IRIS` 实例重新启动时加入镜像作为备份,则不需要进一步的步骤。任何在故障成员上但不在当前主成员上的日志数据都已被丢弃。 - 如果在 `IRIS` 实例重新启动时 `IRIS A` 无法加入镜像,如重建镜像成员中描述的引用不一致数据的消息日志消息所示 `IRIS A` 上的最新数据库更改晚于最新的日志数据当 `IRIS B` 被迫成为主服务器时,它会出现在 `IRIS B` 上。要解决此问题,请按照该部分中的描述重建 `IRIS A`。
文章
姚 鑫 · 七月 27, 2021

类关键字PropertyClass,ServerOnly,Sharded,SoapBindingStyle

# 第二十九章 类关键字 - PropertyClass 向该类添加属性参数。 # 用法 要向该类添加属性参数,请使用以下语法: ```java Class PropClass.MyClass Extends %RegisteredObject [ PropertyClass = PropClass.MyPropertyClass ] { //class members } ``` 其中·propertyclasslist·是下列之一: ```java [ PropertyClass = PropClass.MyPropertyClass ] ``` - 用逗号分隔的类名列表,用括号括起来。 # 详情 如果需要添加自定义属性参数,请执行以下操作: 1. 定义并编译一个定义一个或多个类参数的类。例如: ```java Class PropClass.MyPropertyClass { Parameter MYPARM As %String = "XYZ"; } ``` 这些类参数在下一步中成为属性参数。 2. 在定义属性的类中,指定`PropertyClass`关键字。 # 对子类的影响 子类继承这个关键字添加的自定义行为。如果子类为关键字指定了一个值,则该值会指定一个或多个为该类的属性指定参数的附加类。 # 第三十章 类关键字 - ServerOnly 指定此类是否被投影到Java客户端。 # 用法 要覆盖将类投影到`Java`客户端的默认方式,请使用以下语法: ```java Class Sample.NewClass1 [ ServerOnly = serveronlyvalue ] { //class members } ``` 其中`serveronlyvalue`是以下值之一: - 0表示可以投影此类。 - 1表示这个类不会被投影。 # 详解 如果该关键字为`1`,则该类不会被投影到`Java`客户端。如果该关键字为`0`,则将投影该类。 # 对子类的影响 此关键字不是继承的。 # 默认 如果省略这个关键字,这个类如果不是存根就会被投影(但是如果是存根就不会被投影)。 # 第三十一章 类关键字 - Sharded 指定此类是否被分片。仅适用于包含分片集群的环境中的持久类。 # 用法 要将类定义为分片类,请使用以下语法: ```java Class MyApp.MyClass Extends %Persistent [ Sharded = 1 ] { //class members } ``` 否则,省略此关键字。 # 详解 分片是一种水平扩展数据存储的机制。如果一个类被分片,该类的实例将分布在分片集群中任何已定义的数据节点上。 如果有一个分片环境,并且将一个类定义为未分片,那么该类的实例只存储在第一个数据节点上,尽管所有节点都可以看到该数据。 # 对子类的影响 这个关键字是继承的。 # 默认 如果省略这个关键字,类就不会被分割。 # 第三十二章 类关键字 - SoapBindingStyle 指定此类中定义的任何web方法使用的绑定样式或SOAP调用机制。仅适用于定义为web服务或web客户端的类。 # 用法 要指定此类中定义`web method`使用的绑定样式,请使用以下语法: ```java Class MyApp.MyClass [ SoapBindingStyle = soapbindingstyle ] { //class members } ``` 其中`soapbindingstyle`是下列之一: - `document` 文档(默认)—默认情况下,此类中的`web method`使用文档样式的绑定。 使用这种绑定风格,`SOAP`消息被格式化为文档,并且通常只有一个部分。 在`SOAP`消息中,``元素通常包含一个子元素。``元素的每个子元素对应于一个消息部分。 - `rpc` —默认情况下,此类中的`web method`使用`rpc`(远程过程调用)样式的绑定。 使用这种绑定风格,`SOAP`消息被格式化为具有多个部分的消息。 在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。这个元素是一个生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。 如果`SoapBindingStyle`是文档,如果`ARGUMENTSTYLE`是消息,那么消息样式与`RPC`非常相似; 重要提示:对于手动创建的`web service`,该关键字的默认值通常是合适的。当使用`SOAP`向导从`WSDL`生成`web客户端或服务`时,InterSystems IRIS会将此关键字设置为适合该`WSDL`;如果修改该值,`web客户端或服务`可能不再工作。 # 详解 此关键字允许指定此类中定义的任何`web method`使用的默认绑定样式。它影响`SOAP`主体的格式(但不影响任何SOAP头)。 通过使用`SoapBindingStyle`方法关键字或`SoapBindingStyle`查询关键字,可以重写单个方法的绑定样式。 # 对子类的影响 此关键字不是继承的。 # 默认 默认值为文档。 # 与WSDL的关系 `SoapBindingStyle`类关键字指定了`WSDL`的``部分中``元素的样式属性的值。例如,如果`SoapBindingStyle`是文档,则`WSDL`可能如下所示: ```xml ... ... ``` 如这里所示,在`WSDL`的``部分中,`SoapBindingStyle`类关键字还指定了``元素的样式属性的默认值;该属性由`SoapBindingStyle`方法关键字进一步控制。 相比之下,如果`SoapBindingStyle`是`rpc`,则`WSDL`可以改为如下所示: ```xml ... ... ``` 绑定样式也会影响``元素,如下所示: - 如果绑定样式是文档,默认情况下,消息只有一个部分。例如: ```xml ``` 如果`ARGUMENTSTYLE`参数是`message`,那么一条消息可以有多个部分。例如: ```xml ``` - 如果绑定样式是`rpc`,消息可以有多个部分。例如: ```xml ``` # 对SOAP消息的影响 对`SOAP`消息的主要影响是控制`SOAP`主体是否可以包含多个子元素。 对于使用`RPC`样式绑定和编码样式消息的`web method`,下面显示了请求消息正文的示例: ```xml 10 5 17 2 ``` 相比之下,下面显示了使用文字绑定和编码样式消息的`web method`的请求消息正文的示例: ```xml 10 5 17 2 ``` 在这种情况下,`SOAP`主体只有一个子元素。 # 与 `%XML.DataSet` 一起使用 对于 `%XML.DataSet`, 类型的对象,并非所有 `SoapBindingStyle` 和 `SoapBodyUse` 关键字的排列都是允许的,,如下表总结: type | supported?| supported? ---|---|--- 空 |SoapBodyUse=literal(默认) | SoapBodyUse=encoded SoapBindingStyle=document(default) |supported| not supported SoapBindingStyle=rpc| supported |supported
文章
姚 鑫 · 八月 19, 2021

查询关键字Private,SoapBindingStyle,SoapBodyUse,SoapNameSpace

# 第121章 查询关键字 - Private 指定查询是否为私有查询。 # 用法 要指定此查询为私有查询,请使用以下语法: ```java Query name(formal_spec) As classname [ Private ] { //implementation } ``` 否则,请省略此关键字或将该词放在该关键字之前。 # 详解 私有类成员只能由同一类(或其子类)的其他成员使用。请注意,其他语言通常使用单词`Protected`来描述这种可见性,使用单词`Private`来表示从子类不可见。 # 默认 如果省略此关键字,则此查询不是私有的。 # 第122章 查询关键字 - SoapBindingStyle 指定此查询用作`Web方法`时使用的绑定样式或`SOAP`调用机制。仅适用于定义为`Web服务`或`Web客户端`的类。 # 用法 要覆盖查询使用的默认绑定样式(当它用作`Web方法`时),请使用以下语法: ```java Query name(formal_spec) As classname [ WebMethod, SoapBindingStyle = soapbindingstyle ] { //implementation } ``` 其中`soapbindingstyle`为下列值之一: - `document` - 此`Web方法`使用文档式调用。 使用这种绑定样式,`SOAP`消息被格式化为文档,并且通常只有一个部分。 在`SOAP`消息中,``元素通常包含单个子元素。``元素的每个子元素对应一个消息部分。 - `rpc` - 此`Web方法`使用`RP`C(远程过程调用)风格的调用。 使用这种绑定样式,`SOAP`消息被格式化为具有多个部分的消息。 在`SOAP`消息中,``元素包含一个子元素,其名称取自相应的操作名称。此元素是生成的包装元素,它为方法的参数列表中的每个参数包含一个子元素。 重要提示:对于手动创建的`Web服务`,此关键字的默认值通常比较合适。当使用`SOAP`向导从`WSDL`生成`Web客户端`或`服务`时,InterSystems IRIS会将此关键字设置为适用于该`WSDL`;如果修改此值,`Web客户端`或服务可能不再工作。 # 详情 此关键字允许指定此查询在作为`Web方法`调用时使用的绑定样式。 对于给定查询,此关键字覆盖`SoapBindingStyle`类关键字。 # 默认 如果忽略此关键字,``元素的`style`属性将由`SoapBindingStyle`类关键字的值决定。 # WSDL的关系 (请注意,与方法关键字和查询关键字相比,同名的`class关键字`对`WSDL`的影响更大。) # 对SOAP消息的影响 有关信息,请参阅`SoapBindingStyle`类关键字的条目。 # 第123章 查询关键字 - SoapBodyUse 指定该查询用作`web方法`时,输入和输出使用的编码。 仅应用于定义为`web服务`或`web客户端`的类。 # 用法 要覆盖查询的输入和输出使用的默认编码(当它被用作`web方法`时),请使用以下语法: ```java Query name(formal_spec) As classname [ WebMethod, SoapBodyUse = encoded ] { //implementation } ``` 其中,`soapbodyuse`是下列值之一: - `literal` - 这个`web方法`使用文字数据。 也就是说,`SOAP`消息的``中的`XML`与`WSDL`中给出的模式完全匹配。 - `encoded` = 这个web方法使用soap编码的数据。 也就是说,`SOAP`消息的``中的`XML`根据所使用的`SOAP`版本使用适当的SOAP编码,满足以下规范的要求: - `SOAP 1.1` (https://www.w3.org/TR/2000/NOTE-SOAP-20000508/) - `SOAP 1.2` (https://www.w3.org/TR/soap12-part2/) 重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。 当使用`SOAP`向导从`WSDL`生成`web客户端`或服务时, IRIS将此关键字设置为适合该`WSDL`的; 如果修改了该值,`web客户端`或服务可能不再工作。 # 详解 该关键字允许您指定该查询作为`web方法`调用时的输入和输出的编码。 对于给定的查询,此关键字覆盖`SoapBodyUse`类关键字。 # 默认 如果忽略此关键字,则使用`SoapBodyUse`类关键字的值。 # 与WSDL的关系以及对SOAP消息的影响 有关信息,请参阅`SoapBodyUse`类关键字的条目。 # 第124章 查询关键字 - SoapNameSpace 在`WSDL`中的绑定操作级别指定名称空间。 仅应用于定义为`web服务`或`web客户端`的类。 # 用法 要在绑定操作级别覆盖默认命名空间(当查询被用作`web方法`时),请使用以下语法: ```java Query name(formal_spec) As classname [ SoapNameSpace = "soapnamespace", WebMethod ] { //implementation } ``` 其中,`soapnamespace`是一个名称空间`URI`。 注意,如果`URI`包含冒号(`:`),则字符串必须用引号括起来。 也就是说,你可以使用以下方法: ```java Query MyQuery() [ SoapNameSpace = "http://www.mynamespace.org", WebMethod ] ``` 或以下: ```java Query MyQuery() [ SoapNameSpace = othervalue, WebMethod ] ``` 但以下情况并非如此: ```java Query MyQuery() [ SoapNameSpace = http://www.mynamespace.org, WebMethod ] ``` 重要提示:对于手工创建的`web服务`,这个关键字的默认值通常是合适的。 当使用`SOAP`向导从`WSDL`生成`web客户`端或服务时, IRIS将此关键字设置为适合该WSDL的; 如果修改了该值,`web客户端`或服务可能不再工作。 # 详解 该关键字允许指定查询作为`web方法`调用时使用的`XML`名称空间。 注意:此关键字仅在查询使用`rpc`样式绑定时有效。 也就是说,查询(或包含它的类)必须用等于`rpc`的`SoapBindingStyle`标记。 (如果为使用文档样式绑定的查询指定此关键字,则`WSDL`将不是自一致的。) # 默认 如果忽略此关键字,则`web方法`位于由`web服务`或客户端类的`namespace`参数指定的命名空间中。 # 与WSDL的关系以及对SOAP消息的影响 有关信息,请参阅`SoapNameSpace`方法关键字的条目。
文章
Frank Ma · 三月 2, 2022

如何成为时间领主 - 诞生

好人不需要规则。 神秘博士 要成为日期和时间的主人并不是一件容易的事,在任何编程语言中,这总是一个问题,有时会让人感到困惑,我们将澄清并提出一些提示,使这项任务尽可能简单。 坐上TARDIS,我将把你变成一个时间领主。 让我们从基本知识开始 如果你通常使用其他语言,请记住,Intersystems Object Script(以下简称IOS,不要与苹果手机混淆)的日期有点特殊。当我们在终端运行$HOROLOG 命令时,为了得到当前的日期和时间,你会看到它被分为两部分: WRITE $HOROLOG > 66149,67164 第一个值是天数,确切地说,是自1840年12月31日以来的天数,也就是说,值1是1841年1月1日;第二个值是自今天00:00以来的秒钟。 在这个例子中,66149对应于09/02/2022(欧洲格式的日/月/年的2月9日),67164对应于18:39:24。我们将这种格式称为数据和时间的内部格式。 感到困惑吗?好吧,我们将开始揭示宇宙的伟大秘密(日期和时间)。 如何将内部格式转换为更清晰的格式? 为此,我们将用到命令 $ZDATETIME 基本的命令是 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow) > 02/09/2022 18:39:24 默认情况下,它使用美国格式月/日/年(mm/dd/yyyy)。如果你想使用其他格式的日期,我们将使用第二个参数,比如欧洲格式日/月/年(dd/mm/yyyy),在这种情况下,我们将给它一个值4(关于更多的格式,见文档$ZDATETIME.dformat)。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,4) > 09/02/2022 18:39:24 该选项使用我们在本地变量中定义的分隔符和年份格式。 如果我们还想放另一种时间格式,例如12小时格式(AM/PM)而不是24小时格式,我们使用第三个参数,其值为3,如果我们不想显示秒,我们将使用值4(见文件$ZDATETIME.tformat)。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,4,3) > 09/02/2022 06:39:24PM WRITE $ZDATETIME(RightNow,4,4) > 09/02/2022 06:39PM 现在是不是更清楚了?那么让我们更深入地了解一下。 ODBC 格式 这个格式与你的本地配置无关,它将始终显示为年/月/日格式 yyyy-mm-dd,其值为3。 如果我们想创建要导出文件的数据,如CSV、HL7文件等,建议使用它。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATETIME(RightNow,3) > 2022-02-09 18:39:24 一周的日子,星期名称,一年中的某天 Value 值 描述 10 一周的日子将是一个介于0和6之间的值,0代表星期天,6代表星期六。 11 星期的缩写名称,它将根据你定义的本地配置返回,IRIS的默认安装是 enuw (English, United States, Unicode) 12 长格式的星期名称,与11相同。 14 一年中的某一天,自1月1日以来的天数。 如果我们只是想分别处理日期和时间,应该分别使用$ZDATE和$ZTIME命令。格式的参数与 $ZDATETIME.dformat 和 $ZDATETIME.tformat中定义的参数相同。 SET RightNow = $HOROLOG WRITE RightNow > 66149,67164 WRITE $ZDATE(RightNow,10) > 3 WRITE $ZDATE(RightNow,11) > Wed WRITE $ZDATE(RightNow,12) > Wednesday 那我如何将日期转换为内部格式? 好了,现在我们来看看相反的步骤,即有一个带有日期的文本,并将其转换成IOS格式。对于这个任务,我们将使用命令 $ZDATETIMEH。 这一次,我们必须指出日期和时间的格式(如果我们使用$ZDATETIMEH),或者分别指出日期($ZDATEH)和时间($ZTIMEH)。 格式是相同的,也就是说,如果我们有一个ODBC格式(yyyy-mm-dd)的日期字符串,那么我们将使用值3。 SET MyDatetime = "2022-02-09 18:39:24" SET Interna1 = $ZDATETIMEH(MyDatetime, 3, 1) // ODBC Format SET MyDatetime = "09/02/2022 18:39:24" SET Interna2 = $ZDATETIMEH(MyDatetime, 4, 1) // European format SET MyDatetime = "02/09/2022 06:39:24PM" SET Interna3 = $ZDATETIMEH(MyDatetime, 1, 3) // American format with time in 12h AM/PM WRITE Interna1,!,Interna2,!,Interna3 > 66149,67164 66149,67164 66149,67164 从逻辑上讲,如果我们说字符串使用的是一种特殊的格式,而我们给它提供了错误的参数,那么任何事情都可能发生,比如它理解为2月9日,而不是9月2日。 不要混合格式,这样以后会出现问题。 SET MyDatetime = "09/02/2022" /// American format SET InternalDate = $ZDATEH(MyDatetime, 1) /// European format SET OtherDate = $ZDATETIME(InternalDate, 4) WRITE InternalDate,!,OtherDate > 66354 02/09/2022 不用说,如果我们试图设定一个欧洲的日期并试图将其转化为美国的日期...... 在情人节会发生什么? SET MyDatetime = "14/02/2022" SET InternalDate = $ZDATEH(MyDatetime, 1) // American format. month 14 doesn't exists!!! ^ <ILLEGAL VALUE> 嗯,就像所有的情人节一样......破碎的心,嗯......在这种情况下,破碎的代码。 好吧,让我们用你已经学到的东西做一些事情。 READ !,"Please indicate your date of birth (dd/mm/yyyy): ",dateOfBirth SET internalFormat = $ZDATEH(dateOfBirth, 4) SET dayOfWeek= $ZDATE(internalFormat, 10) SET nameOfDay = $ZDATE(internalFormat, 12) WRITE !,"The day of the week of your birth is: ",nameOfDay IF dayOfWeek = 5 WRITE "you always liked to party!!!" // was born on friday 以后我们将看到其他的做事方法,以及如何处理错误。 下一章:如何进行时间旅行 好奇 如果你想知道为什么01/01/1841的值被当作1的值,那是因为选择这个日期是因为它是在世的最年长的美国公民出生前的非闰年,当MUMPS编程语言被设计时,他是一个121岁的内战老兵,它从这个语言中扩展了对象脚本。