搜索​​​​

清除过滤器
问题
jingwei lu · 三月 13, 2022

关于Cache高可用架构的选择

现在我们院区也遇到这样一个问题。关于cache的高可用架构现有有两个方案: 1.选择双节点的完全无共享架构的自动转移镜像集群外加一个灾难恢复镜像 2.就像你提问的那种,两台主机先做Rose HA用一套双活存储实现高可用,然后再弄一台服务器做Rose HA的单机镜像(有可能是同步也有可能是异步那种比如灾难恢复镜像) 现在想问问如果单纯考虑切换时候对业务的影响,如果切换的中断时长什么的,用哪个最好啊?第二种方案真有很多单位用么? 希望有大神能替我解答一下,谢谢。 可以参考下历史的帖子。https://community.intersystems.com/post/backup-strategy-%E5%8F%8C%E6%9C%BA%E5%A4%87%E4%BB%BD%E7%AD%96%E7%95%A5 如果是我,会选择方案1,原因:既然 intersystems 提供了完整的 mirror 高可用方案,就没必要再和第三方的方案掺和在一起,凭空增加了问题排错的复杂度。如果真出现了服务中断,这个时候要第一要务是尽快做出正确的决策,以便尽快恢复服务。架构的复杂性会给自己造成很多不必要的麻烦以及决策上的复杂性 (个人建议,仅供参考。) 我们推荐的高可用性方案是Mirror。也就是配置至少主备两个镜像成员+灾备异步镜像成员,可选配置多个异步报表镜像成员以及多个灾备异步镜像成员。 当然同时推荐的是建立健全的外部备机机制,以及使用外部备份进行恢复的演练,手动升级灾备异步镜像成员为主机的演练。 对于大型应用一般使用ECP创建多台应用服务器,进行多用户的负载分配,再将多台应用服务器连接至数据服务器,数据服务器进行高可用性配置。 感谢分享 感谢分享 常见问题系列-系统管理篇-如何进行数据库备份 InterSystems Caché系统高可用与数据库镜像
文章
Michael Lei · 六月 26, 2022

跟踪数据更改 - 审计日志 - 下篇

在前一篇文章中,我已经演示了一种简单的方法来记录数据的变化。在这个时候,我改变了负责记录审计数据的 "审计抽象类 "和记录审计日志的数据结构。 我已经将数据结构改为父子结构,其中将有两个表来记录 "交易 "和在该交易中改变的 "字段的值"。 看一下新的数据模型: 看看从 "审计类 "改变的代码吧: Class Sample.AuditBase [ Abstract ]{ Trigger SaveAuditAfter [ CodeMode = objectgenerator, Event = INSERT/UPDATE, Foreach = row/object, Order = 99999, Time = AFTER ]{ #dim %compiledclass As %Dictionary.CompiledClass #dim tProperty As %Dictionary.CompiledProperty #dim tAudit As Sample.Audit Do %code.WriteLine($Char(9)_"; get username and ip adress") Do %code.WriteLine($Char(9)_"Set tSC = $$$OK") Do %code.WriteLine($Char(9)_"Set tUsername = $USERNAME") Set tKey = "" Set tProperty = %compiledclass.Properties.GetNext(.tKey) Set tClassName = %compiledclass.Name Do %code.WriteLine($Char(9)_"Try {") Do %code.WriteLine($Char(9,9)_"; Check if the operation is an update - %oper = UPDATE") Do %code.WriteLine($Char(9,9)_"if %oper = ""UPDATE"" { ") Do %code.WriteLine($Char(9,9,9)_"Set tAudit = ##class(Sample.Audit).%New()") Do %code.WriteLine($Char(9,9,9)_"Set tAudit.Date = +$Horolog") Do %code.WriteLine($Char(9,9,9)_"Set tAudit.UserName = tUsername") Do %code.WriteLine($Char(9,9,9)_"Set tAudit.ClassName = """_tClassName_"""") Do %code.WriteLine($Char(9,9,9)_"Set tAudit.Id = {id}") Do %code.WriteLine($Char(9,9,9)_"Set tSC = tAudit.%Save()") do %code.WriteLine($Char(9,9,9)_"If $$$ISERR(tSC) $$$ThrowStatus(tSC)") Do %code.WriteLine($Char(9,9,9)_"Set tAuditId = tAudit.%Id()") While tKey '= "" { set tColumnNbr = $Get($$$EXTPROPsqlcolumnnumber($$$pEXT,%classname,tProperty.Name)) Set tColumnName = $Get($$$EXTPROPsqlcolumnname($$$pEXT,%classname,tProperty.Name)) If tColumnNbr '= "" { Do %code.WriteLine($Char(9,9,9)_";") Do %code.WriteLine($Char(9,9,9)_";") Do %code.WriteLine($Char(9,9,9)_"; Audit Field: "_tProperty.SqlFieldName) Do %code.WriteLine($Char(9,9,9)_"if {" _ tProperty.SqlFieldName _ "*C} {") Do %code.WriteLine($Char(9,9,9,9)_"Set tAuditField = ##class(Sample.AuditField).%New()") Do %code.WriteLine($Char(9,9,9,9)_"Set tAuditField.Field = """_tColumnName_"""") Do %code.WriteLine($Char(9,9,9,9)_"Set tAuditField.OldValue = {"_tProperty.SqlFieldName_"*O}") Do %code.WriteLine($Char(9,9,9,9)_"Set tAuditField.NewValue = {"_tProperty.SqlFieldName_"*N}") Do %code.WriteLine($Char(9,9,9,9)_"Do tAuditField.AuditSetObjectId(tAuditId)") Do %code.WriteLine($Char(9,9,9,9)_"Set tSC = tAuditField.%Save()") do %code.WriteLine($Char(9,9,9,9)_"If $$$ISERR(tSC) $$$ThrowStatus(tSC)") Do %code.WriteLine($Char(9,9,9)_"}") } Set tProperty = %compiledclass.Properties.GetNext(.tKey) } Do %code.WriteLine($Char(9,9)_"}") Do %code.WriteLine($Char(9)_"} Catch (tException) {") Do %code.WriteLine($Char(9,9)_"Set %msg = tException.AsStatus()") Do %code.WriteLine($Char(9,9)_"Set %ok = 0") Do %code.WriteLine($Char(9)_"}") Set %ok = 1} } 通过Test()类方法改变数据,现在你可以看到审计类(Sample.Audit)中的 "父记录 "和 "审计字段 "类中的 "子字段 "被改变。(Sample.AuditField)。 d ##class(Sample.Person).Test(1)INSERT INTO Sample.Person (Name, Age) VALUES ('TEST PARENT-CHILD', '01')SQLCODE: 0ID Age Name1 01 TEST PARENT-CHILD 1 Rows(s) AffectedUPDATE Sample.Person SET Name = 'INTERSYSTEMS DEVELOPER COMMUNITY', Age = '100' WHERE Name = 'TEST PARENT-CHILD'SQLCODE:0ID Age Name1 100 INTERSYSTEMS DEVELOPER COMMUNITY 1 Rows(s) Affected 审计类: 注意Sample.AuditField记录通过Audit字段=1对Sample.Audit类进行了引用。你可以通过使用两个类的关系来查询数据,如下所示: 这样就搞定了。这样,我们有一个不同的审计日志数据结构。
文章
Jingwei Wang · 七月 4, 2022

IRIS/HealthConnect 高可用机制

高可用性(HA)指的是使系统或应用程序在很高比例的时间内保持运行,最大限度地减少计划内和计划外的停机时间。 维持系统高可用性的主要机制被称为故障转移。在这种方法下,一个故障的主系统被一个备份系统所取代;也就是说,生产系统故障转移到备份系统上。许多HA配置还提供了灾难恢复(DR)的机制,即在HA机制无法保持系统的可用性时,也能及时恢复系统的可用性。 本文简要讨论了可用于基于InterSystems IRIS的应用程序的HA策略机制,提供了HA解决方案的功能比较,并讨论了使用分布式缓存的故障转移策略。 操作系统级别的集群HA 在操作系统层面上提供的一个常见的HA解决方案是故障转移集群,其中主要的生产系统由一个(通常是相同的)备用系统补充,共享存储和一个跟随活动成员的集群IP地址。在生产系统发生故障的情况下,备用系统承担生产工作量,接管以前在故障主系统上运行的程序和服务。备用机必须能够处理正常的生产工作负载,只要恢复故障主机所需的时间就可以了。也可以选择让备用机成为主机,一旦主机恢复,故障主机将成为备用机。 InterSystems IRIS的设计可以轻松地与所支持的平台的故障转移集群技术相结合(如InterSystems支持的平台中所述)。InterSystems IRIS实例安装在集群的共享存储设备上,以便两个集群成员都能识别它,然后添加到集群配置中,这样它将作为故障转移的一部分在备用机上自动重新启动。在故障转移后重新启动时,系统自动执行正常的启动恢复,保持结构和逻辑的完整性,就像InterSystems IRIS在故障系统上重新启动一样。如果需要,可以在一个集群上安装多个InterSystems IRIS实例。 InterSystems IRIS 镜像 具有自动故障转移功能的 InterSystems IRIS 数据库镜像为计划内和意外停机提供了经济有效的高可用性解决方案。镜像依赖于数据复制而不是共享存储,避免了存储故障导致的重大服务中断。 InterSystems IRIS 镜像由两个物理上独立的 InterSystems IRIS 系统组成,称为故障转移成员。每个故障转移成员在镜像中维护每个镜像数据库的副本;应用程序更新是在主故障转移成员上进行的,而备机故障转移用户的数据库则通过主成员的日志文件保持数据同步。 镜像会自动将主节点的角色分配给两个故障转移成员中的一个,而另一个故障转移成员则自动成为备机系统。当主 InterSystems IRIS 实例失败或变得不可用时,备机将自动快速接管并成为主机。 第三个系统称为仲裁机,它与故障转移成员保持持续的联系,在无法直接通信时安全地做出故障转移决策,为他们提供所需信息。在每个故障转移系统主机上运行的代理(称为 ISCAgents )进程也有助于自动故障转移逻辑。除非备机能够确认主节点确实处于停机状态或不可用状态,并且无法再作为主节点运行,否则备机将无法接管。在仲裁机和 ISCAgents 之间,这可以在几乎每个中断场景下完成。 当镜像配置使用 virtual IP address(虚拟 IP 地址)时,将应用程序连接重定向到新主节点是透明的。如果连接是通过 ECP,它们会自动重置到新的主节点。用于重定向应用程序连接的其他机制也是可用的。 当主机实例恢复正常时,它将自动成为备机实例。操作员启动的故障转移也可在计划的维护或升级停机期间维护可用性。 自动故障转移机制 镜像主旨在当主节点失败或变得不可用时,为备机提供安全的自动故障转移。本部分描述了允许这种情况发生的机制,包括: • 自动故障转移的安全性要求 • 自动故障转移规则 • 对于各种中断情况的镜像响应 • 自动故障转移机制详节 自动故障转移的安全性要求 备机的 InterSystems IRIS 实例只有在能够确保满足以下两个条件时才能自动接管主节点: • 备机实例已从主节点接收到最新的日志数据。 这一要求保证了主节点上镜像数据库的所有持久更新都已经或将要对备机的相同数据库进行,从而确保不会丢失任何数据。 • 主机实例不再作为主机实例运行,并且在没有手动干预的情况下无法这样做。 这个要求消除了两个故障转移成员同时作为主节点的可能性,这可能导致逻辑数据库性能下降和完整性损失。 自动故障转移规则 备机状态和自动故障转移 在正常镜像操作期间,备机故障转移成员的日志传输状态为活动,这意味着它已从主节点接收到所有日志数据,并与其同步。活跃的备机接收写在主机上的当前日志数据,主机等待备机确认收到日志之后才考虑该数据的持久化。因此,备机为活跃状态满足故障转移的第一个条件。 如果活跃备机在Quality of Service(QoS)(服务质量超时)内不能确定已从主节点接收新数据,主节点会撤销备机的活动状态,断开备机并暂时进入故障状态。当处于故障状态时,主节点不提交任何新的日志数据(可能导致应用程序暂停),确保充足的时间,使故障转移成员在不发生异步的情况下恢复联系或进行适当的安全故障转移决策 当备机重新连接到主节点时,它首先通过从主节点获取所有最近的日志数据来跟上,然后变为活动状态。当备机通过从主节点同步了最新的日志数据并确认其接收而跟上时,将恢复其活动状态。 备机处于活动状态时的自动故障转移 当备机处于活动状态时,如果它能够确认故障转移的第二个条件,主节点不可以再作为主节点,并且在没有人工干预的情况下不能继续成为主节点,那么它就有资格作为主机进行接管。备机可以通过以下三种方式中的任意一种来接管主节点: • 通过接收来自主机的请求接管的通信 这发生在主机实例正常关闭期间或主机实例检测到主机实例挂起时。一旦主节点发送了这条消息,它就不能再作为主机了,活动备机可以安全地接管它。如果前一个主机被挂起,新的主机就会迫使它关机。 • 通过从仲裁机处收到信息,得知其已与主机失去联系。 当一个网络事件同时将主节点与备机和仲裁机隔离时,它将无限期地进入故障状态。因此,如果一个活跃备机与主机失去联系,并且从仲裁机那里得知它也与主机用失去了联系,那么备机可以安全地接管,因为主机用必须要么已经出现故障,要么被隔离并处于故障状态,因此不能再作为主节点运行了。当连接恢复时,如果前一个主节点挂起,新的主节点就会将之前的主节点强制关闭。 • 通过从主机的 ISCAgent 接收主机实例已关闭或挂起的信息。 当仲裁机不可用或未配置仲裁机时,与主机实例失去联系的活动备机可以尝试联系主机实例的 ISCAgent(只有在主机实例主机系统仍在运行时才可能)以确认主机实例已关闭或处于挂起状态。一旦代理确认主节点不能再作为主机,并且故障转移因此是安全的,备机就会接管。 备机不处于活动状态时的自动故障转移 不活动的备机可以尝试与主机实例的 ISCAgent 联系,以确认主机实例处于关闭状态,或在挂起时强制关闭主机实例,并从代理获取主机实例最近的日志数据。如果在这两方面都成功,那么备机可以安全地作为主机接管。 不活动且无法与主机的 ISCAgent 联系的备机无法确保主机不再作为主机,并且无法保证此时的备机拥有主机最新的日志更新,因此无法接管主机。 当备机没有活动时,仲裁机在故障转移机制中不起任何作用。 对于各种中断情况的镜像响应 自动故障转移中主机中断场景的响应 在以下几种主要的主节点中断情况下,活动的备机故障转移成员会自动接管: 主节点的计划中断(例如出于维护目的)通过关闭其 InterSystems IRIS 实例来启动。 自动故障转移发生是因为主机指示活跃备机接管。 主 InterSystems IRIS 实例由于意外情况而挂起。 自动故障转移发生是因为主节点检测到它被挂起并指示活跃备机接管。 主 InterSystems IRIS 实例由于意外情况而被迫关闭或完全失去响应。 在此情况下,主节点不能指示备机接管。然而,活动备机可以在从仲裁机那里得知它也与主节点失去联系后接管,也可以通过与主机的 ISCAgent 联系并获得主机停机的确认后接管。 主机存储的子系统失败。 存储失败的典型后果是主机实例挂起,原因是 I/O 错误,在这种情况下,主节点检测到它被挂起,并指示活动的备机接管(如场景 2)。然而,在某些情况下,场景 3 或场景 5 描述的行为可能适用。 主机的主机系统发生故障或失去响应。 如果活动的备机从仲裁机获悉它也与主机失去联系,则发生自动故障转移。 如果没有配置仲裁机,或者仲裁机在主机故障前已经不可用,则不可能进行自动故障转移;在这些情况下,可以选择手动强制备机成为主机。 一个网络问题隔离了主机。 如果配置了仲裁机,并且在网络故障时两个故障转移成员都不能连接到它,则主节点将无限地进入故障状态。 • 如果活动的备机从仲裁机获悉它也与主机失去联系,则发生自动故障转移。 • 如果备机在与主节点失去联系的同时失去与仲裁机的联系,则不可能进行自动故障转移。如果两个故障转移成员都已启动,则在还原网络时,备机将与主节点联系,主节点恢复操作。或者,可以手动指定主节点。 如果没有配置仲裁机,或者在网络故障发生前,故障转移成员之一已经与仲裁机断开,则无法进行自动故障转移,主节点将继续作为主机运行。 一个未活动的备机(例如备机正在启动或未追上主机最新日志)可以通过与主节点的 ISCAgent 联系并获得最新的日志数据,在上述场景 1 到 4 下接管。未活动的备机不能在场景 5 和 6 中接管,因为它不能与 ISCAgent 联系;在这些情况下,手动强制使备机成为主机可能是一种选择。 自动故障转移机制详解 代理控制模式 当镜像启动时,故障转移成员在代理控制模式下开始操作。如果仲裁机不可用或未配置仲裁机,则保持此模式。当处于代理控制模式时,故障转移成员响应彼此之间的联系丢失,如下所述。 主机对失联的反应 如果主节点失去了与活动备机的连接,或者超过了等待它确认数据接收的 QoS timeout (QoS 超时),则主程序撤消备机的活动状态并进入故障状态,等待备机确认其不再活动。当主节点收到来自备机的确认或故障超时(是 QoS 超时的两倍)过期时,主程序退出故障状态,恢复为主程序运行。 如果主节点失去了与非活动备机的连接,则它将继续作为主程序运行,不会进入故障状态。 备机对失联的反应 如果备机失去了与主节点的连接,或者超过了等待来自主机的消息的 QoS timeout (QoS 超时),那么它将尝试与主客户的 ISCAgent 联系。如果代理报告主机实例仍作为主机实例运行,则备机重新连接。如果代理确认主节点处于关闭状态,或者它已将其强制关闭,则备机行为如下: • 如果备机处于活动状态,且代理确认主节点在故障超时内处于停机状态,则备机将作为主服务端接管。 • 如果备机未处于活动状态,或者超过了故障超时时间,那么如果代理确认主节点已停机,并且能够从代理获得最新的日志数据,则备机将接管。 无论是否处于活动状态,备机永远无法在代理控制模式下自动接管,除非主节点自身确认它已挂起,或者主服务的代理确认主服务已停机(可能是在强制停机之后),如果主节点已停机或网络隔离,这两种情况备机无法自动接管。 注意: 当其中一个故障转移成员重启时,它会尝试联系另一个成员的 ISCAgent,其行为与不活动备机的描述一样。 仲裁机控制模式 当故障转移成员相互连接时,两者都连接到仲裁机,并且备机是活动的,它们进入仲裁机控制模式,在该模式中,故障转移会员根据仲裁机提供的关于另一个故障转移成员的信息对它们之间的联系丢失做出响应。因为每个故障转移成员通过测试其与另一个故障转移成员的连接来响应其仲裁连接的丢失,反之亦然,由单个网络事件引起的多个连接丢失被作为单个事件处理。 在仲裁机控制模式中,如果故障转移成员仅丢失其仲裁机连接,或者备机 丢失其活动状态,则故障转移成员协调切换到代理控制模式。 如果主节点和备机节点之间的连接在仲裁机控制模式下断开,则每个故障转移成员根据仲裁机连接的状态进行响应,如下图所述。 所有三个系统连接: 镜像进入仲裁机控制模式(如果尚未进入仲裁机控制模式) 备机失去与仲裁机的连接,但仍连接到主节点: 镜像切换到代理控制模式 主节点继续作为主节点运行 备机尝试重新连接仲裁机 主节点失去与仲裁机的连接,但仍连接到备机: 镜像切换到代理控制模式 主节点继续作为主节点运行 主节点尝试重新连接仲裁程序 故障转移成员彼此失去连接,仍然连接到仲裁机: 镜像切换到代理控制模式 主节点继续作为主节点运行 备机尝试重新连接主节点 仲裁机失败或隔离-故障转移成员失去与仲裁机的连接,但仍彼此连接: 镜像切换到代理控制模式 主节点继续作为主节点运行 两个故障转移成员都尝试重新连接仲裁机 备机中断或被隔离-主节点和仲裁机失去与备机的连接,但仍相互连接: 主节点切换到代理控制模式并继续作为主节点操作 备机(如果在操作中)切换到代理控制模式并尝试重新连接到主节点 主节点中断或被隔离-备机和仲裁机失去与主节点的连接,但仍相互连接: 主节点(如果在运行中)将无限期地保持在仲裁控制模式和故障状态 备机作为主机接管,切换到代理控制模式,并在恢复连接时强制主机关闭 三个连接全部丢失: 主节点(如果在运行中)将无限期地保持在仲裁控制模式和故障状态;如果与备机设备联系,则切换到代理控制模式并恢复主设备的运行 备机(如果在操作中)切换到代理控制模式并尝试重新连接到主节点 注意: 由于单个事件(或多个同时发生的事件)而导致所有连接丢失的情况很少见。在大多数情况下,镜像在所有连接丢失之前切换到代理控制模式,在这种情况下: 主节点(如果在运行)继续作为主节点运行 备机(如果正在运行)尝试重新连接到主节点
文章
Claire Zheng · 三月 3, 2021

投票必读:如何成为社区活跃用户(Active user)

亲爱的社区用户: InterSystems社区会举办丰富的竞赛活动,项目提交后最重要的环节就是“投票”! 一般来说社区新注册用户在投票时会发现无法投票并收到以下提示: 如果遇到这种情况,解决方案分两步: 第一步:登陆社区(cn.community.intersystems.com)后进行发帖、回复、回答问题等操作; 第二步:如果您在社区已经完成了上述第一步的操作,那么接下来只需要……等一天。因为我们的系统会每天进行一次active user更新。 成为Active user后,就可以继续给你心仪的项目投票啦! 建议中国用户登录社区中文版(cn.community.intersystems.com)进行操作,以便获得更快的支持! 请再等一天 @Sheng.Li @Jianjun.Miao @gu.jingguo , Thx! 明白了,十分感谢! 明白了,十分感谢! 原来有中文社区,好不容易才找到,,, 通过学习期望也能参加到竞赛!加油! 碰到了这个问题,完美解决,谢谢! 注册满一天留言才能投票
文章
姚 鑫 · 七月 4, 2021

第二十七章 定制SAX解析器的执行自定义实体解析

# 第二十七章 定制SAX解析器的执行自定义实体解析 # 执行自定义实体解析 XML文档可能包含对外部DTD或其他实体的引用。默认情况下,InterSystems IRIS尝试查找这些实体的源文档并解析它们。要控制InterSystems IRIS解析外部实体的方式,请使用以下步骤: 1. 定义实体解析程序类。 此类必须在扩展`%XML.SAX.EntityResolver`,并且必须实现 `resolveEntity()`方法,该方法具有以下签名: ```java method resolveEntity(publicID As %Library.String, systemID As %Library.String) as %Library.Integer ``` 每当XML处理器找到对外部实体(如DTD)的引用时,就会调用该方法;这里的public ID和systemID是该实体的Public和系统标识符字符串。 该方法应获取实体或文档,将其作为流返回,然后在将流包装在`%XML.SAX.StreamAdapter`的实例中。此类提供了用于确定流特征的必要方法。 如果无法解析该实体,则该方法应返回`$$$NULLOREF` ,以向SAX解析器指示该实体无法解析)。 尽管方法签名指示返回值为`%Library.Integer`,但该方法应返回`%XML.SAX.StreamAdapter`的实例或该类的子类。 此外,引用外部实体的标识符始终传递给文档中指定的`resolveEntity()`方法。具体地说,如果这样的标识符使用相对URL,则该标识符将作为相对URL传递,这意味着引用文档的实际位置不会传递给`resolveEntity()`方法,并且无法解析该实体。在这种情况下,请使用默认实体解析器,而不是自定义实体解析器。 2. 读取XML文档时,请执行以下操作: a. 创建实体解析程序类的实例。 b. 读取XML文档时使用该实例,如本章前面的“指定解析器选项”中所述。 ## 示例 例如,以下XML文档: ```xml Some < xhtml-content > with custom entities &entity1; and &entity2;. Here is another paragraph with &entity1; again. ``` 本文档使用以下DTD: ```xml ``` 要阅读本文档,需要如下所示的自定义实体解析器: ```java Class CustomResolver.Resolver Extends %XML.SAX.EntityResolver { Method resolveEntity(publicID As %Library.String, systemID As %Library.String) As %Library.Integer { Try { Set res=##class(%Stream.TmpBinary).%New() //check if we are here to resolve a custom entity If systemID="http://www.intersystems.com/xml/entities/entity1" { Do res.Write("Value for entity1") Set return=##class(%XML.SAX.StreamAdapter).%New(res) } Elseif systemID="http://www.intersystems.com/xml/entities/entity2" { Do res.Write("Value for entity2") Set return=##class(%XML.SAX.StreamAdapter).%New(res) } Else //otherwise call the default resolver { Set res=##class(%XML.SAX.EntityResolver).%New() Set return=res.resolveEntity(publicID,systemID) } } Catch { Set return=$$$NULLOREF } Quit return } } ``` 下面的类包含一个demo方法,该方法解析前面显示的文件并使用此自定义解析器: ```java Include (%occInclude, %occSAX) Class CustomResolver.ParseFileDemo { ClassMethod ParseFile() { Set res= ##class(CustomResolver.Resolver).%New() Set file="c:/temp/html.xml" Set parsemask=$$$SAXALLEVENTS+$$$SAXERROR Set status=##class(%XML.TextReader).ParseFile(file,.textreader,res,,parsemask,,0) If $$$ISERR(status) {Do $system.OBJ.DisplayError(status) Quit } Write !,"Parsing the file ",file,! Write "Custom entities in this file:" While textreader.Read() { If textreader.NodeType="entity"{ Write !, "Node:", textreader.seq Write !," name: ", textreader.Name Write !," value: ", textreader.Value } } } } ``` 下面显示了此方法在终端会话中的输出: ```java GXML>d ##class(CustomResolver.ParseFileDemo).ParseFile() Parsing the file c:/temp/html.xml Custom entities in this file: Node:13 name: entity1 value: Value for entity1 Node:15 name: entity2 value: Value for entity2 Node:21 name: entity1 value: Value for entity1 ``` ## 示例2 例如,读取包含以下内容的XML文档: ```xml ``` 在本例中,将在`publicId`设置为 `-//OASIS//DTD DocBook XML V4.1.2//EN`并将`systemId`设置为`c:\test\doctypes\docbook\docbookx.dtd.`的情况下调用`resolveEntity`方法。 `resolveEntity`方法确定外部实体的正确源,将其作为流返回,并将其包装在`%XML.StreamAdaptor`的实例中。XML解析器从这个专用流中读取实体定义。 例如,请参考InterSystems IRIS库中包含的`%XML.Catalog`和`%XML.CatalogResolverclass。%XML.Catalog`类定义一个简单的数据库,该数据库将公共和系统标识符与URL相关联。`%XML.CatalogResolver`类是一个实体解析器类,它使用此数据库查找给定标识符的URL。`%XML.Catalogclass`可以从SGML样式的编录文件加载其数据库;该文件将标识符映射到标准格式的URL。
文章
姚 鑫 · 三月 10, 2022

第七十七章 SQL函数 LENGTH

# 第七十七章 SQL函数 LENGTH 返回字符串表达式中字符数的字符串函数。 # 大纲 ```java LENGTH(string-expression) {fn LENGTH(string-expression)} ``` # 参数 - `string-expression` - 字符串表达式,可以是列名、字符串文字或另一个标量函数的结果,其中基础数据类型可以表示为任何字符类型(例如 `CHAR` 或 `VARCHAR`)。 `LENGTH` 返回 `INTEGER` 数据类型。 # 描述 `LENGTH` 返回一个整数,表示给定字符串表达式的字符数,而不是字节数。字符串表达式可以是字符串(从中删除尾随空格)或数字( IRIS 将其转换为规范形式)。 请注意,`LENGTH` 可用作 ODBC 标量函数(使用花括号语法)或 SQL 通用函数。 `LENGTH` 和其他长度函数(`$LENGTH`、`CHARACTER_LENGTH`、`CHAR_LENGTH` 和 `DATALENGTH`)都执行以下操作: - `LENGTH` 返回字段的逻辑(内部数据存储)值的长度,而不是显示值,无论 `SelectMode` 设置如何。所有 SQL 函数始终使用字段的内部存储值。 - `LENGTH` 返回数字的规范形式的长度。规范形式的数字不包括前导零和尾随零、前导符号(单个减号除外)和尾随小数分隔符。 `LENGTH` 返回数字字符串的字符串长度。数字字符串不会转换为规范形式。 - `LENGTH` 不排除字符串中的前导空格。可以使用 `LTRIM` 函数从字符串中删除前导空格。 在执行以下操作时,`LENGTH` 与其他长度函数(`$LENGTH`、`CHARACTER_LENGTH`、`CHAR_LENGTH` 和 `DATALENGTH`)不同: - `LENGTH` 不包括尾随空格和字符串终止字符。 `$LENGTH`、`CHARACTER_LENGTH`、`CHAR_LENGTH` 和 `DATALENGTH` 不排除尾随空格和终止符。 - 如果传递一个 `NULL` 值,`LENGTH` 返回 `NULL`,如果传递一个空字符串,则返回 `0`。 如果传递 `NULL` 值,`CHARACTER_LENGTH`、`CHAR_LENGTH` 和 `DATALENGTH` 也返回 `NULL`,如果传递空字符串,则返回 `0`。如果传递一个 `NULL` 值,则 `$LENGTH` 返回 `0`,如果传递一个空字符串,则返回 `0`。 - `LENGTH` 不支持数据流字段。为字符串表达式指定流字段会导致 `SQLCODE -37`。 `$LENGTH` 也不支持流字段。 `CHARACTER_LENGTH`、`CHAR_LENGTH` 和 `DATALENGTH` 函数确实支持数据流字段。 # 示例 在以下示例中, IRIS 首先将每个数字转换为规范形式(删除前导零和尾随零,解析前导符号,并删除尾随小数分隔符)。每个 `LENGTH` 返回长度为 `1`: ```sql SELECT {fn LENGTH(7.00)} AS CharCount, {fn LENGTH(+007)} AS CharCount, {fn LENGTH(007.)} AS CharCount, {fn LENGTH(00000.00)} AS CharCount, {fn LENGTH(-0)} AS CharCount 1 1 1 1 1 ``` 在以下示例中,第一个 `LENGTH` 删除前导零,返回长度值 `2`;第二个 `LENGTH` 将数值视为字符串,并且不删除前导零,返回长度值 `3`: ```sql SELECT LENGTH(0.7) AS CharCount, LENGTH('0.7') AS CharCount 2 3 ``` 以下示例返回值 `12`: ```sql SELECT LENGTH('INTERSYSTEMS') AS CharCount 12 ``` 以下示例显示了 `LENGTH` 如何处理前导和尾随空格。第一个 `LENGTH `返回 `15`,因为 `LENGTH` 不包括尾随空格,但不包括前导空格。第二个 `LENGTH` 返回 `12`,因为 `LTRIM` 排除了前导空格: ```sql SELECT LENGTH(' INTERSYSTEMS ') AS CharCount, LENGTH(LTRIM(' INTERSYSTEMS ')) AS CharCount 15 12 ``` 以下示例返回 `Sample.Person` 表中每个 `Name` 值中的字符数: ```sql SELECT Name,{fn LENGTH(Name)} AS CharCount FROM Sample.Person ORDER BY CharCount ``` 以下示例返回 `DOB`(出生日期)字段中的字符数。请注意,返回的长度(由 `LENGTH`、`CHAR_LENGTH` 和 `CHARACTER_LENGTH`)是日期的内部 (`$HOROLOG`) 格式,而不是显示格式。 `DOB`的显示长度为十个字符;所有三个长度函数都返回 5 的内部长度: ```sql SELECT DOB,{fn LENGTH(DOB)} AS LenCount, CHAR_LENGTH(DOB) AS CCount, CHARACTER_LENGTH(DOB) AS CtrCount FROM Sample.Person ``` 以下嵌入式 SQL 示例给出了 `Unicode` 字符字符串的长度。返回的长度是字符数 (7),而不是字节数。 ```java /// d ##class(PHA.TEST.SQLCommand).Length() ClassMethod Length() { s a = $CHAR(920,913,923,913,931,931,913) &sql(SELECT LENGTH(:a) INTO :b ) if SQLCODE'=0 { w !,"Error code ",SQLCODE } else { w !,"The Greek Sea: ",a,!,$LENGTH(a),!,b } } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLCommand).Length() The Greek Sea: ΘΑΛΑΣΣΑ 7 7 ```
文章
姚 鑫 · 九月 12, 2022

第三十章 管理许可(三)

# 第三十章 管理许可(三) # 确定许可证容量和使用情况 如何知道已使用了多少许可证以及由谁使用?类中的 `%SYSTEM.License` 提供了到 `IRIS` 许可证应用程序编程接口 (`API`) 的接口,并提供了许多方法和相关查询,可以使用这些方法和相关查询来查询许可证容量和当前使用情况。 可以使用 `%Library.%ResultSet` 类的 `RunQuery` 方法运行多个许可查询。例如: ``` USER>do ##class(%ResultSet).RunQuery("%SYSTEM.License","Summary") LicenseUnitUse:Local:Distributed: 当前使用的软件许可单元 :2:2: 使用的最大软件许可单元数 :3:2: 授权的软件许可单元 :25:25: 当前连接 :2:2: 最大连接数 :6:6: ``` 可以从管理门户的许可证使用页面(系统操作 > 许可证使用)查看这些查询的输出,详细信息如下表所示: 许可证使用页面上的链接| `License Query` ---|--- `Summary` |`Summary()` — 返回许可证使用摘要,如 `$System.License.ShowSummary` 所示。 `Usage by Process`| `ProcessList()` — 返回操作系统进程标识符 (`PID`) 使用的许可证,如 `$System.License.DumpLocalPID` 所示。 `Usage by User`| `UserList()` —按用户 ID 返回许可证使用。 `Distributed License Usage`| `AllKeyConnectionList()` — 返回按用户排序的当前分布式许可证使用情况。 (当没有连接许可服务器时禁用此功能。) 还可以使用 `%SYSTEM.License` 中的以下类方法来显示信息,或将许可证数据库转储到文件中: `$System.License.CKEY` 显示密钥。该子例程由 `^CKEY` 程序调用,该程序为保持兼容性而保留: ```java USER>Do $System.License.CKEY() InterSystems IRIS Key display: Based on the active key file 'c:\intersystems\irishealth\mgr\iris.key' LicenseCapacity = InterSystems IRIS 2021.2 Enterprise - Concurrent Users for x86-64 (Microsoft Windows):25, Natural Language Processing (NLP), En CustomerName = ISC DC Moderators - Xin Yao OrderNumber = 202224285 ExpirationDate = 7/15/2023 AuthorizationKey = 4125500002500002500000XXXXXXXXXXXXXXXXX01 MachineID = 当前可用 = 23 最小可用 = 22 最大可用 = 25 ``` `$System.License.ShowCounts` 总结了在本地系统共享内存中跟踪的许可证使用情况: ```java USER> Do $System.License.ShowCounts() 本地软件许可使用视图. 25 授权的总数量 LU 23 当前可用 LU 22 最小可用 LU 2 当前用户处于活动状态 3 处于活动状态的最大用户数 1 当前 CSP 用户处于活动状态 1 处于活动状态的最大 CSP 用户数 0 当前 CSP 会话处于宽限期 0 处于宽限期的最大 CSP 会话数 ``` `.License.ShowServer` 显示活动的许可证服务器地址和端口: ```java USER> Do $System.License.ShowServer() 活动软件许可服务器地址 = 127.0.0.1 端口 = 4002 ``` 如果开发了基于 `REST` 的应用程序,许可证将随着使用而消耗。为防止这种情况发生,请配置可以建立的 `Web Gateway` 连接数。从 `Web Gateway` 管理部分的管理门户: 1. 导航到服务器访问。 2. 选择无状态参数。 3. 将最大值设置为比许可证小 `2` 或 `3` 的数字,以允许服务器端登录。 **注意:根据应用程序的服务器端需求,需要对此进行调整。** 通过在所有可用连接都忙时执行此操作,新请求将排队而不是被拒绝。由于超出许可计数,不会看到拒绝。随着数量的增长,客户端的响应时间会减慢。这表明需要购买更多许可证。 如果开发了基于 REST 的应用程序,许可证将随着使用而消耗。为防止这种情况发生,请配置可以建立的 Web Gateway 连接数。从 Web Gateway 管理部分的管理门户 姚老师,上面描述的这段是从哪里进入的啊,可以给与相应的导航图吗
文章
Michael Lei · 三月 21, 2023

参赛文章选编- 使用嵌入式 Python教程

InterSystems IRIS 是一个高性能、可靠且可扩展的数据平台,用于为医疗保健、金融服务和其他行业构建和部署关键任务应用程序。它提供了广泛的功能,包括数据管理、集成、分析等。 IRIS 提供的功能之一是能够将 Python 代码嵌入到 ObjectScript 代码中。这意味着您可以在 IRIS 应用程序中使用 Python 库和函数,让您可以访问大量的工具和资源。在本文中,我们将了解如何在 InterSystems IRIS 中使用嵌入式 Python。 设置嵌入式 Python 在 IRIS 中开始使用嵌入式 Python 之前,您需要设置环境。这涉及安装 Python 解释器和配置 IRIS 以识别它。 第一步是安装 Python。您可以从官方网站 ( https://www.python.org/downloads/ ) 下载最新版本的 Python。安装 Python 后,需要将其添加到系统的 PATH 环境变量中。这允许 IRIS 找到 Python 解释器。 接下来,您需要配置 IRIS 以识别 Python。为此,您需要创建一个 Python 网关。网关是一个在 IRIS 之外运行的进程,充当 IRIS 和 Python 之间的桥梁。 要创建网关,请打开一个终端窗口并导航到 Python 安装目录。然后运行以下命令: python -m irisnative 此命令启动 Python 网关并创建到 IRIS 的连接。您应该看到类似于以下内容的输出: Python Gateway Version: 0.7 Python Version: 3.8.5 (default, Jan 27 2021, 15:41:15) [GCC 9.3.0] Connected to: IRIS 这表明 Python 网关正在运行并连接到 IRIS。 在 IRIS 中使用嵌入式 Python 现在您已经设置了环境,您可以开始在 IRIS 中使用 Python。为此,您需要使用 ##class(%Net.Remote.Gateway).%New() 方法创建到 Python 网关的连接。此方法返回对可用于执行 Python 代码的 Python 对象的引用。 以下是如何使用嵌入式 Python 计算两个数字之和的示例: Set gateway = ## class (%Net.Remote.Gateway).% New () Set result = gateway.% Execute ( "2 + 3" ) Write "Result: " , result , ! 在此示例中,我们使用 %New() 方法创建到 Python 网关的连接。然后我们执行Python代码"2 + 3",返回结果5。我们将结果存储在result-变量中并输出到控制台。 在 IRIS 中使用 Python 库 在 IRIS 中使用嵌入式 Python 的好处之一是能够使用 Python 库。 Python 拥有庞大的库生态系统,用于数据处理、机器学习等。通过使用这些库,您可以扩展 IRIS 应用程序的功能。 要在 IRIS 中使用 Python 库,您首先需要使用 pip包管理器安装该库。例如,要安装 numpy 库,请运行以下命令: pip install numpy 安装该库后,您可以通过使用 Python 网关导入它来在 IRIS 应用程序中使用它。以下是如何使用 numpy库创建矩阵的示例: vbnet Set gateway = ##class(%Net.Remote.Gateway).%New() Set np = gateway.%Get("numpy") Set matrix = np.%New("array", 10, 10) 在这个例子中, 我们使用 %New()方法创建到 Python 网关的连接。然后我们使用 -%Get() 方法检索对 numpy 库的引用。我们使用 numpy 库中的 array 方法创建一个新矩阵,大小为 10 x 10。我们将矩阵存储在matrix 变量,然后我们可以在我们的 IRIS 应用程序中使用它。 结论 在本文中,我们了解了如何在 InterSystems IRIS 中使用嵌入式 Python。我们已经了解了如何在 IRIS 中设置环境、执行 Python 代码和使用 Python 库。使用嵌入式 Python 可以扩展 IRIS 应用程序的功能,使您能够访问大量的工具和资源。通过将 Python 的强大功能与 IRIS 的性能和可靠性相结合,您可以构建满足组织需求的关键任务应用程序。
文章
姚 鑫 · 十二月 4, 2021

第五章 SQL谓词 BETWEEN

# 第五章 SQL谓词 BETWEEN # 大纲 ``` scalar-expression BETWEEN lowval AND highval ``` # 参数 - `scalar-expression` - 一种标量表达式(最常见的是数据列),将其值与低值和高值(包括高值)之间的值范围进行比较。 - `lowval` - 解析为低排序规则序列值的表达式,指定与标量表达式中的每个值匹配的值范围的开始。 - `highval` - 解析为高排序规则序列值的表达式,指定要与标量表达式中的每个值匹配的值范围的末尾。 # 描述 `BETWEEN`谓词允许选择`lowval`和`highval`指定范围内的数据值。 这个范围包括低值和高值本身。 这等价于一对大于或等于操作符和一对小于或等于操作符。 下面的例子展示了这种比较: ```sql SELECT Name,Age FROM Sample.Person WHERE Age BETWEEN 18 AND 21 ORDER BY Age ``` 这将返回`Sample`中的所有记录。 年龄值介于`18`到`21`之间的人员表,包括这些值。 注意,必须按升序指定`BETWEEN`值; 例如`BETWEEN 21 AND 18`这样的谓词将返回空字符串。 如果标量表达式的值都不在指定的范围内,则`BETWEEN`返回空字符串。 与大多数谓词一样,`BETWEEN`可以使用`NOT`逻辑运算符进行反转。 `BETWEEN`和`NOT BETWEEN`都不能用于返回`NULL`字段。 返回`NULL`字段使用`IS NULL`。 `NOT BETWEEN`的示例如下: ```sql SELECT Name,Age FROM Sample.Person WHERE Age NOT BETWEEN 20 AND 55 ORDER BY Age ``` 这将返回`Sample`中的所有记录。 年龄值小于`20`或大于`55`的人表,不包括这些值。 # 排序类型 `BETWEEN`通常用于按数字顺序排序的数值范围。 但是,`BETWEEN`可用于任何数据类型值的排序规则序列范围。 `BETWEEN`使用与它所匹配的列相同的排序规则类型。 默认情况下,字符串数据类型排序为`SQLUPPER`,这是不区分大小写的。 如果查询为列分配了不同的排序规则类型,则还必须将此排序规则类型应用于`BETWEEN`子字符串。 下面的例子说明了这一点: 在下面的示例中,`BETWEEN`使用字段的默认字母大小写排序规则`SQLUPPER`,它不区分大小写。 它返回`Name`的字母顺序比`Home_State`高,`Home_State`的字母顺序比`Home_City`高的记录: ```sql SELECT Name,Home_State,Home_City FROM Sample.Person WHERE Home_State BETWEEN Name AND Home_City ORDER BY Home_State ``` 在下例中,`BETWEEN`字符串比较不区分大小写,因为`Home_State`字段被定义为`SQLUPPER`。 这意味着低`val`和高`val`在功能上是相同的,在任何字母中选择`'MA'`: ```sql SELECT Name,Home_State FROM Sample.Person WHERE Home_State BETWEEN 'MA' AND 'Ma' ORDER BY Home_State ``` 在下面的示例中,`%SQLSTRING`排序函数使`BETWEEN`字符串比较区分大小写。 它选择那些`Home_State`值为`'MA'`到`'MA'`的记录,在这个数据集中包括`'MA'`, `'MD'`, `'ME'`, `'MO'`, `'MS'`和`'MT':` ```sql SELECT Name,Home_State FROM Sample.Person WHERE %SQLSTRING(Home_State) BETWEEN %SQLSTRING('MA') AND %SQLSTRING('Ma') ORDER BY Home_State ``` 在以下示例中,`BETWEEN`字符串比较不区分大小写,并且忽略空格和标点符号: ```sql SELECT Name FROM Sample.Person WHERE %STRING(Name) BETWEEN %SQLSTRING('OA') AND %SQLSTRING('OZ') ORDER BY Name ``` 下面的示例显示了在内部连接操作`ON`子句中使用的BETWEEN。 它正在执行一个不区分大小写的字符串比较: ```sql SELECT P.Name AS PersonName,E.Name AS EmpName FROM Sample.Person AS P INNER JOIN Sample.Employee AS E ON P.Name BETWEEN 'an' AND 'ch' AND P.Name=E.Name ``` # %SelectMode 如果`%SelectMode`设置为逻辑格式以外的值,那么`BETWEEN`谓词值必须以`%SelectMode`格式(`ODBC`或`Display`)指定。 这主要适用于日期、时间和 IRIS格式列表(`%List`)。 以逻辑格式指定谓词值通常会导致`SQLCODE`错误。 例如,`SQLCODE -146`“无法将日期输入转换为有效的逻辑日期值”。 在下面的动态SQL示例中,BETWEEN谓词必须以`%SelectMode=1` (ODBC)的格式指定日期: ```sql ClassMethod Between() { s q1 = "SELECT Name,DOB FROM Sample.Person " s q2 = "WHERE DOB BETWEEN '1950-01-01' AND '1960-01-01'" s myquery = q1_q2 s tStatement = ##class(%SQL.Statement).%New() s tStatement.%SelectMode=1 s qStatus = tStatement.%Prepare(myquery) if qStatus'=1 { w "%Prepare failed:" d $System.Status.DisplayError(qStatus) q } s rset = tStatement.%Execute() d rset.%Display() w !,"End of data" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLCommand).Between() Name DOB Houseman,Martin D. 1955-09-25 Ingrahm,Yan S. 1954-06-15 Smith,Elvis Y. 1955-06-29 Gore,Alfred M. 1958-09-15 Yoders,Liza U. 1959-06-05 Ng,Liza Z. 1955-10-05 Yeats,Debby G. 1951-12-06 Zweifelhofer,Zelda J. 1954-02-19 Solomon,Emily D. 1953-01-28 Isaacs,Elvis V. 1952-04-05 Pantaleo,Robert U. 1950-03-29 Zampitello,Josephine Q. 1953-08-14 Xiang,Molly F. 1953-03-21 Nichols,Heloisa M. 1957-07-19 Hertz,Uma C. 1954-07-25 LaRocca,David X. 1956-01-11 Houseman,Alice R. 1957-12-07 Alton,Phil T. 1953-02-25 Davis,Jane E. 1953-07-28 Vanzetti,Alexandra O. 1953-12-29 Uhles,Dmitry P. 1951-08-23 Jafari,Christine Z. 1950-04-11 22 Rows(s) Affected End of data ```
文章
bai hongtao · 十月 7, 2022

第三方HA软件结合MIRROR使用方法探讨

在Cache 2018之前的版本中,数据库的高可用是通过第三方HA软件保障的,Cache数据库在2018以后及IRIS支持MIRROR技术,通过MIRROR可以保障数据库的高可用及数据的冗余,那么在新版本中,第三方HA软件与MIRROR是否可以同时使用以实现更高的数据库可用性?使用起来有哪些需要注意的?本文重点介绍探讨上述两个问题。 为得出正确结论,我们搭建了如下实验环境: 我们采用3个服务器节点A、B、C分别部署IRIS 2021.1数据库,其中A节点、B节点部署第三方HA软件组成数据库高可用主备集群(本例中,采用的是基于POWER平台的PowerHA),该集群中定义A节点为主节点,B节点为备用节点,HA集群的共享资源组存放在共享SAN存储上,通过HA,生成HA集群的对外服务IP,即我们通常说的Service ip,保证在生产节点发生网络故障、主机故障、以及操作系统故障、手动切换等情况下,IRIS服务、共享资源组、以及HA的Service IP可自动切换至另外一台服务器,保障IRIS高可用,经测试,HA集群内部节点间服务切换时间约为30秒。 搭建IRIS的MIRROR集群,其中A、B两个节点组成的HA集群通过Service IP添加至MIRROR集群中,做为MIRROR集群的一个Failover member,C节点做为MIRROR集群的另外一个Failover member或者DR节点,镜像服务质量超时时间保持默认(8000ms),D节点只部署ISC Agent,做为Arbiter(Mirror的仲裁程序节点),整个集群通过Mirror集群的VIP对外提供服务。 测试环境的架构视图下所示: 首先,我们将HA集群做为Mirror的主Failover Member,C为Mirror的备Failover Member,此时HA Service IP及Mirror VIP均位于A节点上,数据库通过A节点向外提供服务,此时,我们在默认的镜像服务质量超时时间(8s)及调试后的镜像服务质量超时时间(80s)下,分别模拟A节点故障、A节点B节点几秒内先后故障、通过HA执行数据库服务从A节点向B节点手动切换三种场景,测试中观察到的现象及最终结果如下表所示: 镜像服务质 量超时时间 测试项 现象观察 最终结果 默认(8s) A节点故障 A节点故障约8秒后MIRROR VIP切换至C节点,数据库服务恢复正常 Mirror集群报告Member Fail,HA集群开始内部切换 约30秒后HA Service IP及集群切换至B节点,B节点接替A节点加入Mirror集群 HA集群单节点故障 Mirror集群正常 A节点,B节点几秒内先后故障 A节点故障约8秒后Mirror VIP切换至C节点,数据库服务恢复正常 HA集群故障不可用 Mirror集群报告Member Fail 通过HA,执行数据库服务从A节点向B节点手动切换 开始执行手动切换约8秒后Mirror VIP切换至C节点,数据库服务恢复正常 约20秒后HA Service IP切换至B节点 HA集群正常 Mirror集群正常 调整为(80s) A节点故障 A节点故障约30秒后Mirror VIP及HA Service IP切换至B节点,B节点接替A节点加入Mirror集群 HA集群单节点故障 Mirror集群正常 A节点,B节点几秒内先后故障 A节点故障约80秒后Mirror VIP切换至C节点 HA集群故障不可用 Mirror集群报告Member Fail 通过HA,执行数据库服务从A节点向B节点手动切换 约20秒后HA Service IP及Mirror VIP切换至B节点 HA集群正常 Mirror集群正常 测试结论: IRIS 数据库服务在Mirror 集群的Failover Member之间切换,在journal一致的前提下,只需要进行Mirror VIP漂移就可完成,而第三方HA集群切换则至少需要进行故障节点共享存储资源释放、接管节点的存储资源装载、接管节点数据库服务启动、HA Service IP漂移等一系列动作,故Mirror集群的内部切换要快于第三方HA集群的内部切换 可调整镜像服务质量超时时间,实现不同的切换效果,当镜像服务质量超时时间设置得长于HA的切换时间时,HA将先于Mirror切换,数据库服务中断时间相对长,切换逻辑相对简单。当镜像服务质量超时时间设置得短于HA的切换时间时,Mirror将先于HA切换,数据库服务中断时间短,切换逻辑相对复杂。 IRIS中Mirror 集群的Failover节点数量为1-2个,可以通过第三方HA软件为Failover节点提供“热备”,提升IRIS数据库的可用性及业务连续性。
文章
Qiao Peng · 一月 30, 2022

多语言字符集系列文章-- 第二篇 各种技术对字符集使用的声明

各种技术在交换数据的时候,就需要知道对方给的数据使用什么字符集和字符编码,否则很可能就解码错了。这里列举了医疗行业常见的数据交换技术方式和它们对字符集使用的声明方式。 2.1 文件 文件是字符型数据最常见的交换方式,文本编辑工具通常在保存时都会让用户选择保存成什么字符编码。对于不同的字符编码,文件是如何保存的呢? 通常会在文件头使用字节顺序标志(BOM,Byte Order Mark)来标记文件的编码。下表是常见的编码格式对应的BOM,注意ANSI并不需要BOM,我把它列在这里的目的是希望一目了然。 字节 编码格式 00 00 FE FF UTF-32, big-endian FF FE 00 00 UTF-32, little-endian FE FF UTF-16, big-endian FF FE UTF-16, little-endian EF BB BF UTF-8 空 ANSI 例如,汉字的“中”的各种编码如下: 用Windows的写字板软件将这个汉字分别保存成UTF-8、ANSI、Unicode编码,在UltraEdit打开其16进制模式查看,就可以看到如下的输出: 这里ANSI其实保存的是GBK码(使用GBK代码页936),Unicode其实保存的是UTF16编码。试图以ANSI保存文件时,对超出了GBK编码范围的汉字,Windows写字板软件会提示包含Unicode文字,以ANSI保存会丢失数据,如下图: 对于ANSI保存的包含汉字的文件,代码页信息并不在文件里。并不是所有的文本编辑器和文字处理代码都能正确解析这样的文件,因为它们并不知道代码页。这是可能造成文件中文乱码的一个原因。 另外,前面提到通过BOM可以确定文件编码方式,但并不是所有的文件都使用了BOM。因此特定的文本编辑器和文字处理代码对中文都可能产生显示乱码。 2.2 HTTP 对于HTTP消息内容,包括SOAP、RESTful,浏览器/客户端和服务器怎么知道字符编码呢? HTTP头的Content-Type可以通过参数charset指定文字编码。例如: Content-Type: text/html; charset = UTF-8 如果没有正确配置charset,就可能产生乱码。例如网页表单提交的中文数据,服务器没有正确解码从而产生乱码。 不同的Web服务器都可以设置默认的charset,例如Apache可以修改httpd.conf文件,通过配置AddDefaultCharset指定默认字符集编码。 2.3 XML XML文件当然可以使用前面提到的文件编码方式设置,以HTTP传递的XML数据也可以使用HTTP的文字编码设置。同时,XML规范自己也定义了文字编码声明的方式,从而保证通过任何方式传递(例如TCP)的XML都可以被正确解析。 XML定义的编码方式是设置encoding属性,例如: <?xml version="1.0" encoding="UTF-8"?> 注意,JSON并没有声明文字编码的设置。通常JSON数据都是通过HTTP传递的,因此使用HTTP的文字编码设置。 2.4 数据库连接 数据库连接也是造成文字乱码的一个重灾区。客户端从数据库服务器获取数据、向数据库服务器提交数据,怎么知道数据的文字编码呢? 和数据库相关的文字编码有2部分:数据库内码、数据库连接使用的字符编码。 数据库内码: 由于Unicode码历史并不悠久,数据库管理系统的历史远早于Unicode,直到最近这20年,数据库厂商才开始支持Unicode内码。当然,每个厂商的Unicode内码编码也不一样,例如InterSystems的Caché和IRIS是UTF-16格式,而Oracle是UTF-8和UTF-16。因为UTF-8处理效率低,大多数Unicode数据库都使用UTF-16。现在依然能看到不是Unicode内码的老版本数据库。 很多数据库相关的字符编码问题和数据库内码设置有关。例如国内不少的Oracle安装时没设置过字符编码,数据库内码NLS_CHARACTERSET默认为US7ASCII,客户端字符集NLS_LANG默认为AMERICAN_AMERICA.US7ASCII。这并不会造成中文无法保存,因为数据库只是将客户端的数据逐位保存下来,无论什么编码。但这样保存的中文数据只有Oracle自己的客户端能正常显示,其它的客户端可能就按ASCII处理从而造成乱码。因此正确设置数据库的内码很关键。 数据库连接使用的字符编码: ODBC: 直到ODBC 3.5标准(1997年)之前绝大多数的ODBC连接的函数调用和字符串编码都是ANSI(单字节或双字节),所以中文生僻字大多都会处理异常。 ODBC 3.5 规定ODBC驱动管理器要能够透明地处理Unicode和ANSI之间的转换,从而让ANSI和Unicode的数据库客户端都可以正确地向数据库获取和提交数据。 当然,并不是所有的Unicode字符都能转为ANSI码,例如中文“”字,所以ANSI的数据库客户端依然会遇到生僻字乱码。 JDBC: Java对字符串使用UTF-16编码,如果数据库内码也使用UTF-16,那么不会有乱码问题,例如InterSystems IRIS。但很多数据库并非使用UTF-16,这些数据库的JDBC驱动就需要支持UTF-16和数据库内码之间的转换。通常可以通过设置JDBC的连接字符串的特定属性来实现。 例如mysql的连接字符串中指定characterEncoding: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8 其它连接方式: 另外,很多数据库还提供XDBC之外的连接方式,这些连接方式有自己的字符编码逻辑。 2.5 HL7 V2 在医疗行业,HL7 V2是目前最为广泛使用的消息交换标准。它可以通过文件、TCP、HTTP、SOAP 等多种通道交换。因此,HL7 V2消息标准中,也有设置消息字符编码的字段:MSH-18。正确设置该字段有助于不同的系统正确地处理HL7 V2消息里的字符数据。 例如下面的HL7消息在MSH段设置其字符编码为UTF-8: MSH|^~\&||PHLS|||||ORU^R01|||2.5|||AL|||UTF-8| PID||S2345|S2345^^^PHLS^MR|C9876^^^COR^XX~S45008787^^^MA^DL|张^三||19301019|M|||1 Memorial Drive^^剑桥市^MA^02142||||||||063070516 PV1||O|||||ISCGP001^建国^李|||||||EO|||||HSVN00008|||||||||||||||||||||||||20200912090700|20200912090700 ORC||00265-001|0606:H00550R||||^^^202009120910||202009120910|||ISCGP001^建国^李|PHLS||||||||PHLS||||||||LAB OBR||00265-001|0606:H00550R|CBCD^血常规^L|||202009121049|||||||202009120937|Blood|ISCGP001^Moore^James||||||202009121227|||F OBX||NM|WBC^WHITE BLOOD CELL COUNT||6.24|10(9)/L|4.0-10.6||||F|||202009121049 OBX||NM|RBC^RED BLOOD CELL COUNT||4.99|10x12/L|4.5-5.9||||F|||202009121049 OBX||NM|HGB^HEMOGLOBIN||13.6|g/dL|12.0-16.0||||F|||202009121049 OBX||NM|HCT^HEMATOCRIT||41.6|Percent|36.0-46.0||||F|||202009121049 2.6 其它 在上面提到的和字符集相关的乱码之外,有时我们会混淆一些其它的、并非真正乱码的情况。下面这些中文显示的“乱码”,并非乱码: URL编码: 根据RFC 3986,如果URL的路径中有URL的保留字,就需要对URL路径中的保留字使用转义符% 进行转码,也叫做百分号编码。例如ASCII中下面的字符都需要转义: ! # $ & ' ( ) * + , / : ; = ? @ [ ] %21 %23 %24 %26 %27 %28 %29 %2A %2B %2C %2F %3A %3B %3D %3F %40 %5B %5D 对于非ASCII码字符,按其UTF-8编码字节顺序加%转义。例如“https://cn.community.intersystems.com/post/多语言字符集系列文章-第一篇-多语言字符集和相关标准简史”会被转义为: https://cn.community.intersystems.com/post/%E5%A4%9A%E8%AF%AD%E8%A8%80%E5%AD%97%E7%AC%A6%E9%9B%86%E7%B3%BB%E5%88%97%E6%96%87%E7%AB%A0-%E7%AC%AC%E4%B8%80%E7%AF%87-%E5%A4%9A%E8%AF%AD%E8%A8%80%E5%AD%97%E7%AC%A6%E9%9B%86%E5%92%8C%E7%9B%B8%E5%85%B3%E6%A0%87%E5%87%86%E7%AE%80%E5%8F%B2 其中%E5%A4%9A就是“多”字的UTF-8编码E5A49A的3个字节加上%。 在InterSystems技术平台上,可以用$ZCVT("%E5%A4%9A","I","URI")将URL编码翻译为中文字符,或使用$ZCVT("多","O","URI")将汉字进行URL编码。 HTML实体编码(转义): HTML实体编码(HTML Entity Encode)将字符编码为&开头;结尾的字符串,例如"<"编码为"<"。而中文字符就会被按其unicode编码进行转义,例如“广”字的unicode 10进制码为24191,所以它的HTML实体编码是"广"。在InterSystems技术平台上,可以用$ZCVT("广","I","HTML")将其翻译为中文字符。 Base64 编码: 当使用SOAP传递的数据是一个XML字符串或其它应被视为二进制类型的数据时,通常使用Base64对其进行编码,从而不会破坏XML结构解析。所以它也不是乱码。
文章
姚 鑫 · 五月 7, 2021

第三章 使用多维存储(全局变量)(三)

# 第三章 使用多维存储(全局变量)(三) # 在全局变量中复制数据 若要将全局变量(全部或部分)的内容复制到另一个全局变量(或局部数组)中,请使用ObjectScript `Merge`命令。 下面的示例演示如何使用`Merge`命令将`OldData`全局变量的全部内容复制到`NewData`全局变量中: ``` Merge ^NewData = ^OldData ``` 如果合并命令的`source`参数有下标,则复制该节点及其后代中的所有数据。如果`Destination`参数有下标,则使用目标地址作为顶级节点复制数据。例如,以下代码: ```java Merge ^NewData(1,2) = ^OldData(5,6,7) ``` 将`^OldData(5,6,7)`及其下的所有数据复制到`^NewData(1,2)`。 # 维护全局变量内的共享计数器 大规模事务处理应用程序的一个主要并发瓶颈可能是创建唯一标识符值。例如,考虑一个订单处理应用程序,在该应用程序中,必须为每一张新发票指定一个唯一的标识号。传统的方法是维护某种计数器表。每个创建新发票的进程都会等待获取此计数器上的锁,递增其值,然后将其解锁。这可能会导致对此单个记录的激烈资源争用。 为了解决此问题,InterSystems IRIS提供了ObjectScript `$INCREMENT`函数。`$INCREMENT`自动递增全局节点的值(如果该节点没有值,则设置为1)。`$INCREMENT`的原子性意味着不需要锁;该函数保证返回一个新的增量值,不会受到任何其他进程的干扰。 可以使用`$INCREMENT`,如下所示。首先,必须决定在其中存放计数器的全局节点。接下来,无论何时需要新的计数器值,只需调用`$INCREMENT`: ```java SET counter = $INCREMENT(^MyCounter) ``` InterSystems IRIS对象和SQL使用的默认存储结构使用`$INCREMENT`来分配唯一的对象(行)标识符值。 # 对全局变量中的数据进行排序 存储在全局变量中的数据会根据下标的值自动排序。例如,下面的ObjectScript代码定义了一组全局变量(按随机顺序),然后遍历它们以演示全局节点按下标自动排序: ```java /// w ##class(PHA.TEST.Global).GlobalSort() ClassMethod GlobalSort() { Kill ^Data Set ^Data("Cambridge") = "" Set ^Data("New York") = "" Set ^Data("Boston") = "" Set ^Data("London") = "" Set ^Data("Athens") = "" Set key = $Order(^Data("")) While (key '= "") { Write key,! Set key = $Order(^Data(key)) } q "" } ``` ```java DHC-APP> w ##class(PHA.TEST.Global).GlobalSort() Athens Boston Cambridge London New York ``` 应用程序可以利用全局函数提供的自动排序来执行排序操作或维护对某些值的有序、交叉引用的索引。 InterSystems SQL和ObjectScript使用全局变量自动执行这些任务。 # 全局变量节点排序规则 全局变量节点的排序顺序(称为排序)在两个级别上进行控制:全局变量本身内部和使用全局变量的应用程序。 在应用程序级别,可以通过对用作下标的值执行数据转换来控制全局节点的排序方式(InterSystems SQL和对象通过用户指定的排序函数来执行此操作)。 例如,如果创建一个按字母顺序排序但忽略大小写的名称列表,那么通常你会使用名称的大写版本作为下标: ```java /// w ##class(PHA.TEST.Global).GlobalSortAlpha() ClassMethod GlobalSortAlpha() { Kill ^Data For name = "Cobra","jackal","zebra","AARDVark" { Set ^Data($ZCONVERT(name,"U")) = name } Set key = $Order(^Data("")) While (key '= "") { Write ^Data(key),! Set key = $Order(^Data(key)) } q "" } ``` ``` DHC-APP>w ##class(PHA.TEST.Global).GlobalSortAlpha() AARDVark Cobra jackal zebra ``` 此示例将每个名称转换为大写(使用`$ZCONVERT`函数),以便对下标进行排序,而不考虑大小写。每个节点都包含未转换的值,以便可以显示原始值。 ## 数值和字符串值下标 **数字值在字符串值之前进行排序;也就是说,值`1`在值`“a”`之前。如果对给定的下标同时使用数值和字符串值,则需要注意这一点。如果将全局变量用于索引(即根据值对数据进行排序),则最常见的是将值排序为数字(如薪水`salaries`)或字符串(如邮政编码`postal codes`)。** **对于按数字排序的节点,典型的解决方案是使用一元`+`运算符将下标值强制为数字值。例如,如果要构建按年龄对`id`值进行排序的索引,则可以强制年龄始终为数字:** ```java Set ^Data(+age,id) = "" ``` **如果希望将值排序为字符串(如`“0022”`、`“0342”`、`“1584”`),则可以通过添加空格(`“”`)字符来强制下标值始终为字符串。例如,如果正在构建一个按邮政编码对`id`值进行排序的索引,则可以强制`zipcode`始终为字符串:** ```java Set ^Data(" "_zipcode,id) = "" ``` 这确保带有前导零的值(如`“0022”`)始终被视为字符串。 ## `$SORTBEGIN`和`$SORTEND`函数 通常,不必担心在InterSystems IRIS中对数据进行排序。无论使用SQL还是直接全局访问,排序都是自动处理的。 然而,在某些情况下,可以更有效地进行排序。 具体来说,在以下情况下(`1`)需要设置大量随机(即未排序)的全局节点,(`2`)生成的全局节点的总大小接近InterSystems IRIS缓冲池的很大一部分,那么性能可能会受到不利影响- 因为很多SET操作涉及到磁盘操作(因为数据不适合缓存)。 这种情况通常出现在涉及创建索引全局函数的情况下,例如批量数据加载、索引填充或对临时全局函数中的未索引值进行排序 为了有效地处理这些情况,ObjectScript提供了`$SORTBEGIN`和`$SORTEND`函数。 `$SORTBEGIN`函数为全局变量(或其中的一部分)启动了一种特殊模式,在这种模式中,进入全局变量的数据集被写入一个特殊的临时缓冲区,并在内存(或临时磁盘存储)中进行排序。 当在操作结束时调用`$SORTEND`函数时,数据将按顺序写入实际的全局存储中。 总体操作效率更高,因为实际的写操作是按照要求更少磁盘操作的顺序完成的。 `$SORTBEGIN`函数很容易使用; 在开始排序操作之前,用你想要排序的全局变量的名称调用它,并在操作完成时调用`$SORTEND`: ```java /// w ##class(PHA.TEST.Global).GlobalSortBeginEnd() ClassMethod GlobalSortBeginEnd() { Kill ^Data // 为^Data全局初始化排序模式 Set ret = $SortBegin(^Data) For i = 1:1:10000 { Set ^Data($Random(1000000)) = "" } Set ret = $SortEnd(^Data) // ^Data现在已经设置和排序 Set start = $ZH // 现在迭代并显示(按顺序) Set key = $Order(^Data("")) While (key '= "") { Write key,! Set key = $Order(^Data(key)) } Set elap = $ZH - start Write "Time (seconds): ",elap q "" } ``` `$SORTBEGIN`函数是为全局变量创建的特殊情况而设计的,在使用时必须小心。 特别地,在`$SORTBEGIN`模式下,不能从正在写入的全局变量中读取数据; 由于数据没有写入,读取将是不正确的。 InterSystems SQL自动使用这些函数创建临时全局索引(例如对未索引的字段进行排序)。 ## 在全局变量中使用间接 通过间接方式,ObjectScript提供了一种在运行时创建全局变量引用的方法。 这对于在程序编译时不知道全局变量结构或名称的应用程序非常有用。 间接操作符@支持间接操作,它解除了对包含表达式的字符串的引用。 根据@操作符的使用方式,有几种间接类型。 下面的代码提供了一个名称间接引用的示例,在这个示例中,使用`@`操作符对包含全局引用的字符串进行解引用: ```java /// w ##class(PHA.TEST.Global).GlobalIndirect() ClassMethod GlobalIndirect() { Kill ^Data Set var = "^Data(100)" // 现在使用间接设置^Data(100) Set @var = "This data was set indirectly." // 现在直接显示值: Write "Value: ",^Data(100) q "" } ``` ```java DHC-APP> w ##class(PHA.TEST.Global).GlobalIndirect() Value: This data was set indirectly. ``` 也可以使用下标间接在间接语句中混合表达式(变量或文字值): ```java /// w ##class(PHA.TEST.Global).GlobalIndirect1() ClassMethod GlobalIndirect1() { Kill ^Data Set glvn = "^Data" For i = 1:1:10 { Set @glvn@(i) = "This data was set indirectly." } Set key = $Order(^Data("")) While (key '= "") { Write "Value ",key, ": ", ^Data(key),! Set key = $Order(^Data(key)) } q "" } ``` ```javan DHC-APP>w ##class(PHA.TEST.Global).GlobalIndirect1() Value 1: This data was set indirectly. Value 2: This data was set indirectly. Value 3: This data was set indirectly. Value 4: This data was set indirectly. Value 5: This data was set indirectly. Value 6: This data was set indirectly. Value 7: This data was set indirectly. Value 8: This data was set indirectly. Value 9: This data was set indirectly. Value 10: This data was set indirectly. ``` 间接是ObjectScript的一个基本特性; 它并不局限于全局引用。
文章
姚 鑫 · 六月 9, 2021

第二章 从对象写入XML输出

# 第二章 从对象写入XML输出 本章介绍如何从InterSystems IRIS对象生成XML输出。 # 创建XML编写器概述 InterSystems IRIS提供了用于为InterSystems IRIS对象生成`XML`输出的工具。可以指定XML投影的详细信息,如将对象投影到`XML`中所述。然后创建一个`Writer`方法,该方法指定`XML`输出的整体结构:字符编码、对象的显示顺序、是否包括处理指令等。 基本要求如下: - 如果需要特定对象的输出,则该对象的类定义必须扩展`%XML.Adaptor`。除了少数例外,该对象引用的类还必须扩展`%XML.Adaptor`。 - 输出方法必须创建`%XML.Writer`的实例,然后使用该实例的方法。 下面的终端会话显示了一个简单的示例,在该示例中,我们访问启用了XML的对象并为其生成输出: ```java /// d ##class(Sample.Person).Populate(100) /// w ##class(PHA.TEST.Xml).Obj2Xml(1) ClassMethod Obj2Xml(ID) { s obj = ##class(Sample.Person).%OpenId(ID) s xml = ##class(%XML.Writer).%New() s xml.Indent=1 s status = xml.RootObject(obj) q "" } ``` ```java DHC-APP>w ##class(PHA.TEST.Xml).Obj2Xml(1) yaoxin 111-11-1117 1990-04-25 889 Clinton Drive St Louis WI 78672 9619 Ash Avenue Ukiah AL 56589 濮氶懌 111-11-1115 Red Orange Yellow Green Red Orange Yellow 31 ``` # 创建输出方法 输出方法按照指定的顺序逐段构造一个XML文档。输出方法的整体结构取决于需要输出完整的XML文档,还是仅仅输出一个片段。 ## 输出方法的整体结构 方法应按以下顺序执行以下部分或全部操作: 1. 如果使用的对象可能无效,请调用该对象的`%ValidateObject()`方法并检查返回的状态。如果对象无效,则XML也将无效。 **`%XML.Writer` 在导出对象之前不会对其进行验证。这意味着,如果刚刚创建了一个对象,但尚未对其进行验证,则该对象(以及XML)可能是无效的(例如,因为缺少必需的属性)。** 2. 创建`%XML.Writer`类的实例,并根据需要设置其属性。 特别是,需要设置以下属性: - `Indent` 缩进-控制输出是在缩进和换行中生成(如果缩进等于1),还是作为单个长行生成(如果缩进等于0)。后者是默认设置。 - `IndentChars` 缩进字符-指定用于缩进的字符。默认值为两个空格的字符串。如果缩进为0,则此属性无效。 - `Charset` 字符集-指定要使用的字符集。 为了提高可读性,本文档中的示例使用缩进等于1。 3. 指定输出目标。 默认情况下,输出写入当前设备。要指定输出目标,请在开始编写文档之前调用以下方法之一: - `OutputToDevice()`-将输出定向到当前设备。 - `OutputToFile()`-将输出定向到指定文件。可以指定绝对路径或相对路径。请注意,该目录路径必须已经存在。 - `OutputToString()`-将输出定向到字符串。稍后,可以使用另一种方法来检索此字符串。 - `OutputToStream()`-将输出定向到指定的流。 4. 启动文档。可以使用`StartDocument()`方法。请注意,如果尚未通过`StartDocument()`启动文档,则以下方法会隐式启动文档:`Write()`、`WriteDocType()`、`RootElement()`、`WriteComment()`和`WriteProcessingInstruction()`。 5. 可以选择写入文档的序言行。可以使用以下方法: - `WriteDocType()` - 编写DOCTYPE声明。 - `WriteProcessingInstructions()`-编写处理指令。 6. 可以选择指定默认命名空间。编写器将其用于没有定义的XML命名空间的类。 7. 可以选择将命名空间声明添加到根元素。为此,可以在启动根元素之前调用几个实用程序方法。 8. 启动文档的根元素。详细信息取决于该文档的根元素是否对应于InterSystems IRIS对象。有两种可能性: - 根元素可能直接对应于InterSystems IRIS对象。如果要为单个对象生成输出,通常会出现这种情况。 在本例中,使用`RootObject()`方法,该方法将指定的启用XML的对象作为根元素写入。 - 根元素可能只是一组元素的包装器,而这些元素是InterSystems IRIS对象。 在本例中,使用`RootElement()`方法,该方法插入具有指定名称的根级元素。 9. 如果使用`RootElement()`方法,请调用方法来为根元素内的一个或多个元素生成输出。可以按照选择的任何顺序或逻辑在根元素中编写任何元素。有几种方法可以编写单个元素,并且可以结合使用这些技术: - 可以使用`object()`方法,该方法写入启用XML的对象。可以指定此元素的名称,也可以使用由对象定义的默认值。 - 可以使用`element()`方法,该方法使用提供的名称写入元素的开始标记。然后,可以使用`WriteAttribute()`、`WriteChars()`、`WriteCData()`等方法编写内容、属性和子元素。子元素可以是另一个`Element()`,也可以是`Object()`。使用`EndElement()`方法指示元素的结束。 - 可以使用`%XML.Element`并手动构造元素。 10. 如果使用的是`RootElement()`方法,请调用`EndRootElement()`方法。此方法关闭文档的根元素,并根据需要减少缩进(如果有)。 11. 如果文档是从`StartDocument()`开始的,请调用`EndDocument()`方法关闭文档。 12. 如果将输出定向到字符串,请使用`GetXMLString()`方法检索该字符串。 还有许多其他可能的组织,但请注意,某些方法只能在某些上下文中调用。具体地说,一旦开始一个文档,在结束第一个文档之前,不能开始另一个文档。如果这样做,`Writer`方法将返回以下状态: ```java #6275: Cannot output a new XML document or change %XML.Writer properties until the current document is completed. #6275:在当前文档完成之前,无法输出新的XML文档或更改%XML。Writer属性。 ``` `StartDocument()`方法的作用是:显式启动文档。如前所述,其他方法隐式启动文档:`write()`、`WriteDocType()`、`RootElement()`、`WriteComment()`和`WriteProcessingInstruction()`。 注意:这里描述的方法旨在使够向XML文档写入特定的单元,但在某些情况下,可能需要更多的控制。在`%XML.Writer`提供了一个额外的方法`Write()`,可以使用该方法将任意字符串写入输出中的任何位置。 此外,还可以使用`Reset()`方法重新初始化编写器属性和输出方法。如果已经生成了一个XML文档,并且希望在不创建新的编写器实例的情况下生成另一个文档,这将非常有用。 ## 错误检查 **%XML.Writer的大多数方法都会返回状态。应该在每个步骤之后检查状态,并在适当的情况下退出。** ## 插入注释行 如前所述,使用`WriteComment()`方法插入注释行。可以在文档中的任何位置使用此方法。如果尚未启动XML文档,此方法将隐式启动文档。 ## 示例 ```java /// w ##class(PHA.TEST.Xml).Write() ClassMethod Write() As %Status { s obj = ##class(Sample.Person).%OpenId(1) set writer=##class(%XML.Writer).%New() set writer.Indent=1 set status=writer.OutputToDevice() if $$$ISERR(status) { do $System.Status.DisplayError(status) quit $$$ERROR($$$GeneralError, "输出目标无效") } set status=writer.RootObject(obj) if $$$ISERR(status) { do $System.Status.DisplayError(status) quit $$$ERROR($$$GeneralError, "写入根对象时出错") } quit status } ``` 请注意,此方法使用`OutputToDevice()`方法将输出定向到当前设备(默认目标)。这不是必需的,但仅用于演示目的。 当然,输出取决于所使用的类,但可能如下所示: ```java DHC-APP>w ##class(PHA.TEST.Xml).Write() xiaoli 111-11-1111 test 2662 1 ``` ## 有关缩进选项的详细信息 如前所述,可以使用编写器的缩进属性来获取包含附加换行符的输出,以获得更好的可读性。此格式没有正式规范。本节介绍`%XML.Writer`使用的规则。如果缩进等于`1`: - 任何只包含空格字符的元素都会转换为空元素。 - 每个元素都放在自己的行上。 - 如果某个元素是前一个元素的子元素,则该元素相对于该父元素缩进。缩进由`IndentChars`属性确定,该属性默认为两个空格。
文章
Louis Lu · 六月 12, 2023

使用 Synthea 生成 FHIR 测试数据

文章相关视频参见Synthea生成FHIR测试数据,以及FHIR服务器加载FHIR资源文件 1. 什么是Synthea Synthea是一个开源软件包,可以模拟生成患者就诊数据。他的github地址在这里。 生成的就诊模版从最初的模拟美国前十种常见病、前十种慢性病到现今超过90种不同的模型。详细模型参见这里。 基于当前版本,Synthea的特性包括: 从出生到死亡的全生命周期 可配置的人口统计学信息(默认为美国马萨诸塞州人口普查数据) 模块化规则系统 插入通用模块 用于附加功能的自定义 Java 规则模块 主要医疗事件就诊、急诊室就诊和症状驱动的就诊 症状、 过敏、药品、 疫苗接种、观察/生命体征、实验室、处置、 护理计划 支持格式 HL7 FHIR(R4、STU3 v3.0.1 和 DSTU2 v1.0.2) ndjson 格式的批量 FHIR(设置 exporter.fhir.bulk_data = true 以激活) C-CDA (设置 exporter.ccda.export = true 以激活) CSV (设置 exporter.csv.export = true 以激活) CPCDS (设置 exporter.cpcds.export = true 以激活) 使用Graphviz可视化呈现规则和疾病模块 支持的参数可见下图 比如 -p 5 生成5条测试数据 -g M 生成男性测试数据 -a 60-65 生成年龄在60-65周岁患者测试数据 2. 使用Synthea 生成测试数据 为了方便使用,也将该软件做成了docker,所以你可以简单的执行下面命令行 docker run --rm -v $PWD/output:/output --name synthea-docker intersystemsdc/irisdemo-base-synthea:version-1.3.4 -p 5 该命令会在当前路径的output文件夹下生成5条患者符合FHIR标准的就诊数据,数据相关摘要信息如下面终端输出: 3. 加载生成的 FHIR 数据至 InterSystems IRIS for Health 生成完FHIR数据后,需要加载到FHIR服务器(FHIR资源仓库)中。 我们在输出目录下可以看到生成7条json数据,其中5条患者就诊相关,1条就诊医院信息,一条参与者(就诊医生)信息。 在InterSystems IRIS for health中可以方便的使用DataLoader类中的方法,批量加载FHIR资源数据,进入FHIR 资源仓库命名空间后执行: zw ##class(HS.FHIRServer.Tools.DataLoader).SubmitResourceFiles("/external/fhir/","FHIRServer","/fhir/r4") 该方法中的第一个参数是fhir资源文件路径; 第二个参数服务类型,这里一般是FHIRServer; 第三个参数FHIRServer的service名称。 执行后显示如下: 之后我们可以进入管理门户,或者使用SQL客户端查询相关存储表,表明数据被正确导入
文章
Frank Ma · 一月 5, 2022

在Hadoop大数据存储库库中使用SQL (Apache Hive)

本文译自 https://community.intersystems.com/post/using-sql-apache-hive-hadoop-big-data-repositories 大家好, 在使用Spark做Hadoop时,InterSystems IRIS有一个很好的连接器。但市场上也提供了大数据Hadoop访问的其他优秀替代方案-Aparche Hive。请看区别: HIVE SPARK Hive是一个数据库,用类似于RDBMS数据库的表格形式存储数据。 Spark不是一个数据库,它是一个数据分析框架,可以在内存中对大至PB字节的大容量数据进行复杂的数据分析。 使用称作HiveQL的自己的SQL引擎,数据可以从Hive中抽取出来。只能使用SQLs来抽取数据。 Spark既能使用复杂SQLs(Complex SQLs)也能使用MapReduce机制进行数据分析。它支持Java, Scala 和Python写的分析框架。 Hive在Hadoop之上运行。 Spark没有自己专用的存储。实际上,它是从外部的分布式数据存储如运行在Hadoop和MongoDB上的Hive、HBase中抽取数据。 Hive是一个基于数据仓库技术的数据库 Spark更适合在内存中进行复杂和快速的数据分析以及对数据进行流式处理。 对于那些需要在可横向扩展的RDBMS数据库上运行数据仓库操作的应用来说,Hive是最适合的。 Spark最适合于那些要求比MapReduce机制更快地进行大数据分析的应用。 来源: https://dzone.com/articles/comparing-apache-hive-vs-spark 我做了一个PEX互操作性服务,可以让你在你的InterSystems IRIS应用内部使用Apache Hive。请试用如下步骤: 1. 在iris-hive-adapter 项目上做一个Git Clone: $ git clone https://github.com/yurimarx/iris-hive-adapter.git 2. 在这个目录内打开terminal 并运行: $ docker-compose build 3. 运行IRIS容器: $ docker-compose up 4. 打开项目中的Hive Prouction,运行一个Hello样例): http://localhost:52773/csp/irisapp/EnsPortal.ProductionConfig.zen?PRODUCTION=dc.irishiveadapter.HiveProduction 5. 点击“开始”运行Production. 6. 现在我们来测试应用! 7. 运行你的REST客户端应用程序(比如Postman),在body部分使用项目的URLS和命令(使用POST请求): 7.1 在大数据中生成一个新的表:POST http://localhost:9980/?Type=DDL. 在BODY中: CREATE TABLE helloworld (消息字符串) 7.2 在表中插入: POST http://localhost:9980/?Type=DDL. 在BODY中: INSERT INTO helloworld VALUES ("hello") 7.3 T从表中得到结果清单: POST http://localhost:9980/?Type=DML. 在BODY中: SELECT * FROM helloworld (注意:这里的类型是) 现在,你有了2个在IRIS中使用大数据的选项:Hive 或者Spark。希望你喜欢。