搜索​​​​

清除过滤器
文章
姚 鑫 · 六月 4, 2023

第二十四章 开发Productions - ObjectScript Productions - 定义业务服务

# 第二十四章 开发Productions - ObjectScript Productions - 定义业务服务 本页介绍如何定义业务服务类。 提示: `IRIS® `提供使用特定入站适配器的专用业务服务类,其中之一可能适合需要。如果是这样,则不需要编程。有关部分列表,请参阅 `Introducing Interoperability Productions` 中的连接选项。 # 介绍 业务服务负责接受来自外部应用程序的请求到 `IRIS`。下图显示了它是如何工作的: 请注意,此图仅显示数据的输入流,而不是可选响应。 业务服务负责以下活动: - 等待特定的外部事件(例如来自应用程序的通知、收到 `TCP` 消息等)。 - 读取、解析和验证伴随此类事件的数据, - 如果需要,返回对外部应用程序的确认,表明已收到事件。 - 创建请求消息的实例并将其转发到适当的业务流程或业务操作以进行处理。 业务服务的目的通常是接收数据输入。在大多数情况下,业务服务有一个与之关联的入站适配器。但是,在某些情况下不需要适配器,因为应用程序能够将请求消息发送到服务中,或者因为业务服务已被编写为处理特定类型的外部调用,例如来自复合应用程序的调用。这种类型的业务服务称为无适配器业务服务。 当业务服务具有入站适配器时,它处于数据拉取(而不是推送)模式。在这种模式下,业务服务会定期轮询适配器,看它是否有数据。同时,如果适配器随时遇到输入数据,它会调用业务服务来处理输入。 当业务服务没有适配器时,它不会拉取数据。相反,客户端应用程序调用业务服务并告诉它处理输入(这是一种数据推送模式)。 # 关键原则 首先,务必阅读 `Programming in InterSystems IRIS`。 在业务服务中,可以访问关联适配器的属性和方法,这些适配器作为业务服务的 Adapter 属性提供。这意味着可以更改适配器的默认行为;这样做可能合适也可能不合适。记住封装原则很有用。封装的思想是适配器类应该负责技术特定的逻辑,而业务服务类应该负责生产特定的逻辑。 如果发现有必要在业务服务类中大量或频繁地改变适配器类的行为,那么创建适配器类的自定义子类可能更合适。请参阅不太常见的任务。 这个原则也适用于商业运作。 # 定义业务服务类 要创建一个业务服务类,定义一个类如下: - 类必须在(或子类)中扩展 `Ens.BusinessService`。 - 在类中,`ADAPTER` 参数必须等于此业务服务要使用的适配器类的名称。 提示:如果只是希望业务服务定期唤醒和运行而不关心 `IRIS` 外部的事件,请使用适配器类 `Ens.InboundAdapter`。 - 类必须实现 `OnProcessInput()` 方法,如实现 `OnProcessInput()` 方法中所述。 - 类可以添加或删除设置。请参阅添加和删除设置。 - 类可以实现任何或所有启动和拆卸方法。请参阅覆盖启动和停止行为。 - 类可以包含完成自身内部工作的方法。 有关业务服务类的示例,请参阅适配器指南。 # 实施 `OnProcessInput()` 方法 在业务服务类中, `OnProcessInput()` 方法可以具有以下通用签名: ```java Method OnProcessInput(pInput As %RegisteredObject, pOutput As %RegisteredObject) As %Status ``` 这里的`pInput`是适配器要发送给这个业务服务的输入对象,`pOutput`是输出对象。 首先查看选择的适配器类。 建议编辑 `OnProcessInput()` 方法签名以使用适配器所需的特定输入参数。 `OnProcessInput()` 方法应该执行以下部分或全部操作: 1. 可选地设置业务服务类的属性(在任何适当的时间)。最受关注的业务服务属性是 `%WaitForNextCallInterval`。它的值控制 `IRIS` 调用适配器的 `OnTask()` 方法的频率。 有关其他属性,请参阅 `Ens.BusinessService`的类参考。 2. 如有必要,验证输入对象。 3. 检查输入对象并决定如何使用它。 4. 创建请求消息类的实例,这将是业务服务发送的消息。 5. 对于请求消息,使用输入对象中的值适当地设置其属性。 6. 确定要将请求消息发送到哪里。当发送消息时,将需要在生产中使用业务主机的配置名称。 7. 将请求消息发送到生产(业务流程或业务操作)中的目的地。请参阅下一节。 8. 确保设置输出参数 (`pOutput`)。通常,将其设置为等于您收到的响应消息。此步骤是必需的。 9. 返回适当的状态。此步骤是必需的。
文章
Kelly Huang · 七月 12, 2023

当 GPT 与 FHIR 碰撞出火花:利用Open API 的规范力量

FHIR 通过提供标准化数据模型来构建医疗保健应用程序并促进不同医疗保健系统之间的数据交换,彻底改变了医疗保健行业。由于 FHIR 标准基于现代 API 驱动的方法,因此移动和 Web 开发人员更容易使用它。然而,与 FHIR API 交互仍然具有挑战性,尤其是在使用自然语言查询数据时。 隆重推出FHIR - AI 和 OpenAPI 链应用程序,该解决方案允许用户使用自然语言查询与 FHIR API 进行交互。该应用程序使用OpenAI 、 LangChain和Streamlit构建,简化了查询 FHIR API 的过程并使其更加用户友好。 FHIR OpenAPI 规范是什么? OpenAPI 规范(以前称为 Swagger,目前是OpenAPI Initiative的一部分)已成为软件开发领域的重要工具,使开发人员能够更有效地设计、记录 API 并与 API 交互。 OpenAPI 规范定义了一种标准的机器可读格式来描述 RESTful API,提供了一种清晰一致的方式来理解其功能并有效地使用它们。 在医疗保健领域,FHIR 成为数据交换和互操作性的领先标准。为了增强FHIR的互操作能力, HL7正式记录了FHIR OpenAPI规范,使开发人员能够将FHIR资源和操作无缝集成到他们的软件解决方案中。 FHIR OpenAPI 规范的优点: 标准化 API 描述:OpenAPI 规范提供 FHIR 资源、操作和交互的全面且标准化的描述。开发人员可以轻松了解基于 FHIR 的 API 的结构和功能,从而更轻松地构建集成并与医疗保健系统交互。 促进互操作性:促进开发人员之间的协作,推动 FHIR 标准和最佳实践的采用。该规范提供了一种通用语言和框架,用于讨论基于 FHIR 的集成和实现,促进开发人员之间的协作。 增强的文档和测试:交互式文档和测试套件,以便更好地理解和验证。开发人员可以创建详细的API文档,使其他开发人员更容易理解和使用基于FHIR的API。基于规范的测试套件可以对API集成进行全面的测试和验证,确保医疗数据交换的可靠性和准确性。 改进的开发人员体验:自动生成客户端库和 SDK 以实现无缝集成。这简化了集成过程,并减少了将 FHIR 功能合并到应用程序中所需的时间和精力 FHIR、OpenAI 和 OpenAPI Chain 如何协同工作? FHIR - AI 和 OpenAPI Chain应用程序利用 LangChain 来加载和解析 OpenAPI 规范( OpenAPI Chain )。之后,根据这些规范,通过 OpenAI 给出的提示链旨在理解自然语言查询并将其转换为适当的 FHIR API 请求。用户可以用简单的语言提出问题,应用程序将与所选的 FHIR API 交互以检索相关信息。 例如,用户可能会问:“患者 John Doe (ID 111) 的最新血压读数是多少?”然后,应用程序会将此查询转换为 FHIR API 请求,获取所需的数据,并以易于理解的格式将其呈现给用户。 FHIR - AI 和 OpenAPI 链的优势 用户友好的交互:通过允许用户使用自然语言查询与 FHIR API 交互,该应用程序使非技术用户可以更轻松地访问和分析医疗保健数据。 提高效率:该应用程序简化了查询 FHIR API 的过程,减少了获取相关信息所需的时间和精力。此外,它还有可能减少从应用程序中查找任何特定信息的点击次数(花费的时间)。 可定制:FHIR 标准简化了从任何 FHIR 服务器检索一致数据的过程,从而可以轻松定制。它可以轻松配置为与任何 FHIR API 无缝集成,为不同的医疗保健数据需求提供灵活且适应性强的解决方案。 FHIR 入门 - AI 和 OpenAPI 链 要开始使用 FHIR - AI 和 OpenAPI Chain 应用程序,请按照以下步骤操作: 从OpenAI Platform获取 OpenAI API 密钥。 获取 FHIR 服务器 API 端点。您可以使用自己的示例 FHIR 服务器(需要未经身份验证的访问),也可以按照InterSystems IRIS FHIR 学习平台中给出的说明创建临时示例服务器。 在线试用该应用程序或使用提供的说明在本地进行设置。 通过集成人工智能和自然语言处理功能,FHIR - AI 和 OpenAPI Chain 应用程序提供了一种与 FHIR API 交互的更直观的方式,使所有技术背景的用户都更容易访问和分析医疗数据。 如果您发现我们的应用程序很有前途,请在大奖赛中投票! 如果您能想到使用此实现的任何潜在应用程序,请随时在讨论线程中分享它们。 @Ikram Shah 致敬原创作者~
文章
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。 打开分析器,将新的级别显示为行。
问题
Yufeng Li · 六月 14, 2021

IRISHealth-2019.1.0.510.4 能在 CentOS 7.5上部署吗?

IRISHealth-2019.1.0.510.4 能在 CentOS 7.5上部署吗? 可以的,您可以通过WRC账号下载红帽版本安装即可。
文章
Michael Lei · 五月 30, 2022

部分IRIS 2022 年度编程大奖赛作品展示——FHIR 匿名化代理软件(实现FHIR数据动态匿名化)

你好,我很高兴地宣布向OpenExchange和目前的比赛提交的一个作品,即FHIR匿名化代理。FHIR匿名化代理为任何现有的FHIR服务器增加了一个透明的匿名化层,使客户能够在FHIR服务器上进行查询--其中可能包含个人识别信息--并收到一个即时的匿名化数据版本。 代理机制是通过互操作性Production、BPLs和DTLs以及FHIR互操作性适配器在IRIS for Health平台上实现的。匿名化包括所有身份ID和个人数据,并可通过DTLs进行配置。 OpenExchange的演示应用程序带有一个内置的FHIR endpoint,并以会填充一些示例数据。试一下,或者将你自己的FHIR服务器配置为代理目标亲自测试一下
文章
姚 鑫 · 六月 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 异步镜像成员,以获得高可用性和容灾解决方案,旨在为您的应用程序维持最高水平的运营弹性. 希望本文提供了一些关于成功部署具有故障转移的数据库镜像的可能的不同组合和用例的见解,这些组合和用例适合您的应用程序和可用性要求。
文章
姚 鑫 · 六月 10, 2022

第五章 数据类型(四)

# 第五章 数据类型(四) # Strings `%Library.String` 数据类型支持的最大字符串长度为 `3,641,144` 个字符。通常,极长的字符串应分配为 `%Stream.GlobalCharacter` 数据类型之一。 因为 IRIS 支持 xDBC 协议 50 和更高版本,所以没有强制执行 ODBC 或 JDBC 字符串长度限制。如果 IRIS 实例和 ODBC 驱动程序支持不同的协议,则使用两个协议中较低的一个。实际使用的协议记录在 ODBC 日志中。 请注意,默认情况下 IRIS 建立系统范围的 ODBC `VARCHAR` 最大长度为 `4096`;此 ODBC 最大长度是可配置的。 # 列表结构 IRIS 支持列表结构数据类型 `%List`(数据类型类 `%Library.List`)。这是一种压缩的二进制格式,不会映射到 SQL 的相应本机数据类型。在其内部表示中,它对应于数据类型 `VARBINARY`,默认 `MAXLEN` 为 `32749`。 IRIS 支持列表结构数据类型 `%ListOfBinary`(数据类型类 `%Library.ListOfBinary`)对应于数据类型 `VARBINARY`,默认 `MAXLEN` 为 `4096`。 因此,动态 SQL 不能在 `WHERE` 子句比较中使用 `%List` 数据。也不能使用 `INSERT` 或 `UPDATE` 来设置 `%List` 类型的属性值。 动态 SQL 将列表结构化数据的数据类型返回为 `VARCHAR`。要确定查询中的字段是数据类型 `%List` 还是 `%ListOfBinary`,可以使用 `select-item columns metadata isList` 布尔标志。这些数据类型的 `CType`(客户端数据类型)整数代码是 `6`。 如果使用 ODBC 或 JDBC 客户端,则使用 `LogicalToOdbc` 转换将 `%List` 数据投影到 `VARCHAR` 字符串数据。列表被投影为一个字符串,其元素由逗号分隔。这种类型的数据可以用在 `WHERE` 子句以及 `INSERT` 和 `UPDATE` 语句中。请注意,默认情况下,IRIS 建立系统范围的 ODBC `VARCHAR` 最大长度为 `4096`;此 ODBC 最大长度是可配置的。 SQL 支持八种列表函数:`$LIST`、`$LISTBUILD`、`$LISTDATA`、`$LISTFIND`、`$LISTFROMSTRING`、`$LISTGET`、`$LISTLENGTH` 和 `$LISTTOSTRING`。 ObjectScript 支持三个额外的列表函数:`$LISTVALID` 用于确定表达式是否为列表,`$LISTSAME` 用于比较两个列表,以及 `$LISTNEXT` 用于从列表中顺序检索元素。 # 位数据类型 `BIT (%Library.Boolean)` 数据类型接受 `0`、`1` 和 `NULL` 作为有效值。 - 在逻辑和 ODBC 模式下,唯一接受的值是 `0`、`1` 和 `NULL`。 - 在显示模式下,`DisplayToLogical` 方法首先将非空输入值转换为 `0` 或 `1`,如下所示: - 非零数字或数字字符串 = 1,例如 `3`, `'0.1'`, `'-1'`, `'7dwarves'` - 非数字字符串 = 0。例如,`“true”`或`“false”`。 - 空字符串 = 0。例如`''`。 # 流数据类型 `Stream` 数据类型对应于 IRIS 类属性数据类型 `%Stream.GlobalCharacter`(用于 `CLOB`)和 `%Stream.GlobalBinary`(用于 `BLOB`)。这些数据类型类可以使用指定的 `LOCATION` 参数定义流字段,或者省略该参数并默认为系统定义的存储位置。 具有 `Stream` 数据类型的字段不能用作大多数 SQL 标量、聚合或一元函数的参数。尝试这样做会生成 `SQLCODE -37` 错误代码。 具有 `Stream` 数据类型的字段不能用作大多数 SQL 谓词条件的参数。尝试这样做会生成 `SQLCODE -313` 错误代码。 Stream 数据类型在索引中的使用以及在执行插入和更新时也受到限制。 # 串行数据类型 具有 `SERIAL (%Library.Counter)` 数据类型的字段可以采用用户指定的正整数值,或者 IRIS 可以为其分配一个连续的正整数值。 `%Library.Counter` 扩展了 `%Library.BigInt`。 `INSERT` 操作为 `SERIAL` 字段指定以下值之一: - 无值、0(零)或非数字值: IRIS 忽略指定值,而是将此字段的当前串行计数器值增加 1,并将结果整数插入该字段。 - 正整数值:IRIS 将用户指定的值插入到字段中,并将该字段的串行计数器值更改为此整数值。 因此,`SERIAL` 字段包含一系列增量整数值。这些值不一定是连续的或唯一的。例如,以下是 SERIAL 字段的有效值系列:`1、2、3、17、18、25、25、26、27`。连续整数要么是 IRIS 生成的,要么是用户提供的;非连续整数是用户提供的。如果希望 `SERIAL` 字段值是唯一的,则必须对该字段应用 `UNIQUE` 约束。 `UPDATE` 操作对自动分配的 `SERIAL` 计数器字段值没有影响。但是,使用 `INSERT OR UPDATE` 执行的更新会导致对 `SERIAL` 字段的后续插入操作跳过整数序列。 如果该字段当前没有值(`NULL`),或者它的值为 `0`,则 `UPDATE` 操作只能更改串行字段值。否则,将生成 `SQLCODE -105` 错误。 IRIS 对表中的 `SERIAL` 字段的数量没有限制。 # ROWVERSION 数据类型 `ROWVERSION` 数据类型定义了一个只读字段,该字段包含一个唯一的系统分配的正整数,从 1 开始。 IRIS 分配顺序整数作为每个插入、更新或 `%Save` 操作的一部分。这些值不是用户可修改的。 IRIS 在命名空间范围内维护一个单行版本计数器。命名空间中包含 `ROWVERSION` 字段的所有表共享相同的行版本计数器。因此,`ROWVERSION` 字段提供行级版本控制,允许确定对命名空间中一个或多个表中的行进行更改的顺序。 每个表只能指定一个 `ROWVERSION` 数据类型的字段。 `ROWVERSION` 字段不应包含在唯一键或主键中。 `ROWVERSION` 字段不能是 `IDKey` 索引的一部分。 # ROWVERSION 和 SERIAL 计数器 作为 `INSERT` 操作的一部分,`ROWVERSION` 和 `SERIAL` `(%Library.Counter)` 数据类型字段都从内部计数器接收顺序整数。但是这两个计数器有很大的不同,并且用于不同的目的: - `ROWVERSION` 计数器位于命名空间级别。 `SERIAL` 计数器位于表级别。这两个计数器完全相互独立,独立于 `RowID` 计数器。 - `ROWVERSION` 计数器通过插入、更新或 `%Save` 操作递增。 `SERIAL` 计数器仅由插入操作递增。使用 `INSERT OR UPDATE` 执行的更新可能会导致 `SERIAL` 计数器序列出现间隙。 - `ROWVERSION` 字段值不能由用户指定;该值始终由 `ROWVERSION` 计数器提供。如果没有为该字段指定值,则在插入期间从表的内部计数器提供一个 `SERIAL` 字段值。如果插入提供了一个 `SERIAL` 整数值,则插入该值而不是当前计数器值: - 如果插入提供的 `SERIAL` 字段值大于当前内部计数器值, IRIS 将该值插入该字段并将内部计数器重置为该值。 - 如果插入提供的 `SERIAL` 字段值小于当前计数器值, IRIS 不会重置内部计数器。 - 插入可以提供 `SERIAL` 字段值作为负整数或小数。 IRIS 将小数截断为其整数部分。如果提供的 `SERIAL` 字段值为 `0` 或 `NULL`, IRIS 将忽略用户提供的值并插入当前的内部计数器值。 - 不能更新现有的 `SERIAL` 字段值。 - `ROWVERSION` 字段值始终是唯一的。因为可以插入用户指定的 `SERIAL` 字段值,所以必须指定 `UNIQUE` 字段约束以保证唯一的 `SERIAL` 字段值。 - 无法重置 `ROWVERSION` 计数器。 `TRUNCATE TABLE` 重置 `SERIAL` 计数器;对所有行执行 `DELETE` 不会重置 `SERIAL` 计数器。 - 每个表只允许一个 `ROWVERSION` 字段。可以在一个表中指定多个 `SERIAL` 字段。 # ODBC / JDBC 公开的 DDL 数据类型 ODBC 公开了 DDL 数据类型的子集,并将其他数据类型映射到该数据类型的子集。这些映射是不可逆的。例如,语句 `CREATE TABLE mytable (f1 BINARY)` 创建一个 IRIS 类,该类作为 `mytable (f1 VARBINARY)` 投影到 ODBC。 IRIS 列表数据类型作为 `VARCHAR` 字符串投影到 ODBC。 ODBC 公开以下数据类型:`BIGINT`、`BIT`、`DATE`、`DOUBLE`、`GUID`、`INTEGER`、`LONGVARBINARY`、`LONGVARCHAR`、`NUMERIC`、`OREF`、`POSIXTIME`、`SMALLINT`、`TIME`、`TIMESTAMP`、`TINYINT`、`VARBINARY`、`VARCHAR`。请注意,默认情况下 IRIS 建立系统范围的 ODBC `VARCHAR` 最大长度为 `4096`;此 ODBC 最大长度是可配置的。 当这些 ODBC/JDBC 数据类型值之一映射到 SQL 时,会发生以下操作: 使用 `$DOUBLE` 强制转换 `DOUBLE` 数据。 `NUMERIC` 数据使用 `$DECIMAL` 进行转换。 `GUID` 数据类型对应于 `SQL UNIQUEIDENTIFIER` 数据类型。未能为 `GUID / UNIQUEIDENTIFIER` 字段指定有效值会生成 `#7212` 一般错误。要生成 `GUID` 值,请使用 `%SYSTEM.Util.CreateGUID()` 方法。 # 查询元数据返回数据类型 可以使用动态 SQL 返回有关查询的元数据,包括查询中指定列的数据类型。 以下动态 SQL 示例为 `Sample.Person` 和 `Sample.Employee` 中的每个列返回列名和 ODBC 数据类型的整数代码: ```java /// d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType() ClassMethod QueryMetadataReturnsDataType() { s myquery = "SELECT * FROM Sample.Person" s tStatement = ##class(%SQL.Statement).%New() s tStatus = tStatement.%Prepare(myquery) s x = tStatement.%Metadata.columnCount while x > 0 { s column = tStatement.%Metadata.columns.GetAt(x) w !,x," ",column.colName," ",column.ODBCType s x = x-1 } w !,"end of columns" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType() 16 Office_Zip 12 15 Office_Street 12 14 Office_State 12 13 Office_City 12 12 Home_Zip 12 11 Home_Street 12 10 Home_State 12 9 Home_City 12 8 Spouse 4 7 SSN 12 6 Name 12 5 FavoriteColors 12 4 DOB 9 3 Age 4 2 AddDateTime 11 1 ID 4 end of columns ``` ```java /// d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType1() ClassMethod QueryMetadataReturnsDataType1() { s myquery = "SELECT * FROM Sample.Employee" s tStatement = ##class(%SQL.Statement).%New() s tStatus = tStatement.%Prepare(myquery) s x = tStatement.%Metadata.columnCount while x > 0 { s column = tStatement.%Metadata.columns.GetAt(x) w !,x," ",column.colName," ",column.ODBCType s x = x - 1 } w !,"end of columns" } ``` ```java DHC-APP>d ##class(PHA.TEST.SQLFunction).QueryMetadataReturnsDataType1() 20 Office_Zip 12 19 Office_Street 12 18 Office_State 12 17 Office_City 12 16 Home_Zip 12 15 Home_Street 12 14 Home_State 12 13 Home_City 12 12 Title 12 11 Spouse 4 10 Salary 4 9 SSN 12 8 Picture -4 7 Notes -1 6 Name 12 5 FavoriteColors 12 4 DOB 9 3 Company 4 2 Age 4 1 ID 4 end of columns ``` 列出结构化数据(例如 `Sample.Person` 中的 `FavoriteColors` 列)返回数据类型 `12 (VARCHAR)`,因为 ODBC 将 ObjectScript `%List` 数据类型值表示为逗号分隔值的字符串。 `Steam` 数据(例如 `Sample.Employee` 中的 `Notes` 和 `Picture` 列)返回数据类型 -1 (`LONGVARCHAR`) 或 -4 (`LONGVARBINARY`)。 `ROWVERSION` 字段返回数据类型 `-5`,因为 `%Library.RowVersion` 是 `%Library.BigInt` 的子类。
文章
Hao Ma · 五月 17, 2023

IRIS, Caché监控指导 - 指标监控(1)

Caché, IRIS在系统产生了最严重的问题时会产生错误信息并通知客户,但这并不足够。一是客户需要更多更灵活的通知消息,二是客户通常会有第3方的监控系统,因此得到Cache, IRIS的监控指标是必须的。 在所有的指标中,用户最关心的是以下几类: - 硬件资源的使用,CPU, 内存, IO性能 - 数据库使用的硬盘的占用 - Cache, IRIS Journal的硬盘占有 - Mirror的状态 - License的使用情况 - Caché的性能指标 除此之外,第3方监控系统还需要获得Caché的一些系统信息,比如版本,instance名字等等。 ## 指标的获得 有以下几个获得指标的方法 ### 1. 系统仪表板及其Web服务 Caché的系统仪表板显示的数据包括:系统性能;系统运行状态 (运行时间,上一次备份,数据库,Journal状况等; 事务和进程情况;软件许可使用情况;任务,ECP等,还有就是错误和警告的数量。 系统仪表板包含4个子面板:Global和Routine统计数据;ECP统计数据;磁盘和缓冲器统计数据;系统资源统计数据。(IRIS和Caché稍有不同,缺少了“磁盘和缓冲器统计数据”) 。它们分别对应主仪表板中的相应模块,给出了更详细的数据。 ![image](/sites/default/files/inline/images/image-20230517111905809.png) Monitoring Web Service默认随系统的启动而开启,远程用户或者第3方系统可以访问该服务得到所以的系统监控指标,也就是系统仪表板上的所有数据。用户还可以使用该服务订阅Caché的警告消息。访问地址是:http://localhost:57772/csp/sys/SYS.WSMon.Service.cls, 或者IRIS : http://localhost:52773/csp/sys/SYS.WSMon.Service.cls *Monitoring Web Service的WSDL* ![image](/sites/default/files/inline/images/image-20230517131516506.png) 调用服务方法的结果示例: GetDashboard() ```xml 204604.00 50183591134 2396232998 264876546 4040613709 60130 585372 77743.51 OK 0 OK 0 OK OK 10d 4h 38m Normal Normal Normal 18304233 Normal Normal 39 1 0 0 256 HealthShare 2018.1.1, Enterprise:256, Concurrent User, Platform Independent, Multi, DeepSee, DSV Reporting, NLP 2 4 1 2 ``` ### 2. REST API 这是IRIS的新特性,参考文档在[这里](https://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_rest)。 下面是我的一个测试环境的指标列表: ``` iris_cpu_usage 0 iris_csp_activity{id="127.0.0.1:52773"} 44 iris_csp_actual_connections{id="127.0.0.1:52773"} 7 iris_csp_gateway_latency{id="127.0.0.1:52773"} .772 iris_csp_in_use_connections{id="127.0.0.1:52773"} 1 iris_csp_private_connections{id="127.0.0.1:52773"} 0 iris_csp_sessions 1 iris_cache_efficiency 57.260 iris_db_expansion_size_mb{id="DEMO"} 0 iris_db_expansion_size_mb{id="ENSLIB"} 0 iris_db_expansion_size_mb{id="HCC"} 0 iris_db_expansion_size_mb{id="HSCUSTOM"} 0 iris_db_expansion_size_mb{id="HSLIB"} 0 iris_db_expansion_size_mb{id="HSSYS"} 0 iris_db_expansion_size_mb{id="IRISAUDIT"} 0 iris_db_expansion_size_mb{id="IRISLOCALDATA"} 0 iris_db_expansion_size_mb{id="IRISSYS"} 0 iris_db_expansion_size_mb{id="IRISTEMP"} 0 iris_db_expansion_size_mb{id="MOCKSYS"} 0 iris_db_expansion_size_mb{id="OEESP"} 0 iris_db_expansion_size_mb{id="SMART"} 0 iris_db_expansion_size_mb{id="USER"} 0 iris_db_free_space{id="DEMO"} 10 iris_db_free_space{id="ENSLIB"} 17 iris_db_free_space{id="HCC"} 11 iris_db_free_space{id="HSCUSTOM"} 9.5 iris_db_free_space{id="HSLIB"} 131 iris_db_free_space{id="HSSYS"} 9 iris_db_free_space{id="IRISAUDIT"} 8.7 iris_db_free_space{id="IRISLOCALDATA"} 19 iris_db_free_space{id="IRISSYS"} 9.3 iris_db_free_space{id="IRISTEMP"} 161 iris_db_free_space{id="MOCKSYS"} 8.7 iris_db_free_space{id="OEESP"} 6.9 iris_db_free_space{id="SMART"} 16 iris_db_free_space{id="USER"} 4.9 iris_db_latency{id="DEMO"} 0.566 iris_db_latency{id="ENSLIB"} 0.143 iris_db_latency{id="HCC"} 0.191 iris_db_latency{id="HSCUSTOM"} 0.163 iris_db_latency{id="HSLIB"} 0.153 iris_db_latency{id="HSSYS"} 0.143 iris_db_latency{id="IRISAUDIT"} 0.133 iris_db_latency{id="IRISSYS"} 0.200 iris_db_latency{id="IRISTEMP"} 0.145 iris_db_latency{id="MOCKSYS"} 0.157 iris_db_latency{id="OEESP"} 0.565 iris_db_latency{id="SMART"} 1.113 iris_db_latency{id="USER"} 0.236 iris_db_max_size_mb{id="DEMO"} 0 iris_db_max_size_mb{id="ENSLIB"} 0 iris_db_max_size_mb{id="HCC"} 0 iris_db_max_size_mb{id="HSCUSTOM"} 0 iris_db_max_size_mb{id="HSLIB"} 0 iris_db_max_size_mb{id="HSSYS"} 0 iris_db_max_size_mb{id="IRISAUDIT"} 0 iris_db_max_size_mb{id="IRISLOCALDATA"} 0 iris_db_max_size_mb{id="IRISSYS"} 0 iris_db_max_size_mb{id="IRISTEMP"} 0 iris_db_max_size_mb{id="MOCKSYS"} 0 iris_db_max_size_mb{id="OEESP"} 0 iris_db_max_size_mb{id="SMART"} 0 iris_db_max_size_mb{id="USER"} 0 iris_db_size_mb{id="HCC",dir="/usr/irissys/mgr/HCC/"} 365 iris_db_size_mb{id="DEMO",dir="/external/demo/"} 229 iris_db_size_mb{id="USER",dir="/usr/irissys/mgr/user/"} 11 iris_db_size_mb{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 1219 iris_db_size_mb{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 21 iris_db_size_mb{id="OEESP",dir="/external/oeesp/"} 102 iris_db_size_mb{id="SMART",dir="/external/smart/"} 162 iris_db_size_mb{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 209 iris_db_size_mb{id="IRISSYS",dir="/usr/irissys/mgr/"} 127 iris_db_size_mb{id="MOCKSYS",dir="/usr/irissys/mgr/MOCKSYS/"} 11 iris_db_size_mb{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 21 iris_db_size_mb{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 162 iris_db_size_mb{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 11 iris_db_size_mb{id="IRISLOCALDATA",dir="/usr/irissys/mgr/irislocaldata/"} 21 iris_directory_space{id="HCC",dir="/usr/irissys/mgr/HCC/"} 17142 iris_directory_space{id="DEMO",dir="/external/demo/"} 34188 iris_directory_space{id="USER",dir="/usr/irissys/mgr/user/"} 17142 iris_directory_space{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 17142 iris_directory_space{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 17142 iris_directory_space{id="OEESP",dir="/external/oeesp/"} 34188 iris_directory_space{id="SMART",dir="/external/smart/"} 34188 iris_directory_space{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 17142 iris_directory_space{id="IRISSYS",dir="/usr/irissys/mgr/"} 17142 iris_directory_space{id="MOCKSYS",dir="/usr/irissys/mgr/MOCKSYS/"} 17142 iris_directory_space{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 17142 iris_directory_space{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 17142 iris_directory_space{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 17142 iris_disk_percent_full{id="HCC",dir="/usr/irissys/mgr/HCC/"} 83.61 iris_disk_percent_full{id="DEMO",dir="/external/demo/"} 92.83 iris_disk_percent_full{id="USER",dir="/usr/irissys/mgr/user/"} 83.61 iris_disk_percent_full{id="HSLIB",dir="/usr/irissys/mgr/hslib/"} 83.61 iris_disk_percent_full{id="HSSYS",dir="/usr/irissys/mgr/hssys/"} 83.61 iris_disk_percent_full{id="OEESP",dir="/external/oeesp/"} 92.83 iris_disk_percent_full{id="SMART",dir="/external/smart/"} 92.83 iris_disk_percent_full{id="ENSLIB",dir="/usr/irissys/mgr/enslib/"} 83.61 iris_disk_percent_full{id="IRISSYS",dir="/usr/irissys/mgr/"} 83.61 iris_disk_percent_full{id="MOCKSYS",dir="/usr/irissys/mgr/MOCKSYS/"} 83.61 iris_disk_percent_full{id="HSCUSTOM",dir="/usr/irissys/mgr/HSCUSTOM/"} 83.61 iris_disk_percent_full{id="IRISTEMP",dir="/usr/irissys/mgr/iristemp/"} 83.61 iris_disk_percent_full{id="IRISAUDIT",dir="/usr/irissys/mgr/irisaudit/"} 83.61 iris_ecp_conn 0 iris_ecp_conn_max 2 iris_ecp_connections 0 iris_ecp_latency 0 iris_ecps_conn 0 iris_ecps_conn_max 1 iris_glo_a_seize_per_sec 0 iris_glo_n_seize_per_sec 0 iris_glo_ref_per_sec 0 iris_glo_ref_rem_per_sec 0 iris_glo_seize_per_sec 0 iris_glo_update_per_sec 0 iris_glo_update_rem_per_sec 0 iris_jrn_block_per_sec 0 iris_jrn_entry_per_sec 0 iris_jrn_free_space{id="WIJ",dir="default"} 11802.25 iris_jrn_free_space{id="primary",dir="/usr/irissys/mgr/journal/"} 11802.25 iris_jrn_free_space{id="secondary",dir="/usr/irissys/mgr/journal/"} 11802.25 iris_jrn_size{id="WIJ"} 237 iris_jrn_size{id="primary"} 1 iris_jrn_size{id="secondary"} 0 iris_license_available 4 iris_license_consumed 1 iris_license_percent_used 20 iris_log_reads_per_sec 0 iris_obj_a_seize_per_sec 0 iris_obj_del_per_sec 0 iris_obj_hit_per_sec 0 iris_obj_load_per_sec 0 iris_obj_miss_per_sec 0 iris_obj_new_per_sec 0 iris_obj_seize_per_sec 0 iris_page_space_percent_used 30 iris_phys_mem_percent_used 61 iris_phys_reads_per_sec 0 iris_phys_writes_per_sec 0 iris_process_count 30 iris_rtn_a_seize_per_sec 0 iris_rtn_call_local_per_sec 0 iris_rtn_call_miss_per_sec 0 iris_rtn_call_remote_per_sec 0 iris_rtn_load_per_sec 0 iris_rtn_load_rem_per_sec 0 iris_rtn_seize_per_sec 0 iris_sam_get_db_sensors_seconds .021465 iris_sam_get_interop_sensors_seconds .000165 iris_sam_get_jrn_sensors_seconds .002873 iris_sam_get_sql_sensors_seconds .000258 iris_sam_get_wqm_sensors_seconds .000125 iris_smh_available{id="Classes_Instantiated"} 47360 iris_smh_available{id="DB_Name_&_Directory"} 64326 iris_smh_available{id="Global_Mapping"} 60384 iris_smh_available{id="Lock_Table"} 192816 iris_smh_available{id="Routine_Buffer_In_Use_Table"} 61952 iris_smh_available{id="Security_System"} 0 iris_smh_available{id="Semaphores_objects"} 61440 iris_smh_available{id="TTY_Hash_Table"} 32760 iris_smh_percent_full{id="Classes_Instantiated"} 98 iris_smh_percent_full{id="DB_Name_&_Directory"} 2 iris_smh_percent_full{id="Global_Mapping"} 69 iris_smh_percent_full{id="Lock_Table"} 2 iris_smh_percent_full{id="Routine_Buffer_In_Use_Table"} 5 iris_smh_percent_full{id="Semaphores_objects"} 6 iris_smh_percent_full{id="TTY_Hash_Table"} 50 iris_smh_total 3801088 iris_smh_total_percent_full 6 iris_smh_used{id="Classes_Instantiated"} 1984256 iris_smh_used{id="DB_Name_&_Directory"} 1210 iris_smh_used{id="Global_Mapping"} 136224 iris_smh_used{id="Lock_Table"} 3792 iris_smh_used{id="Routine_Buffer_In_Use_Table"} 3584 iris_smh_used{id="Security_System"} 65536 iris_smh_used{id="Semaphores_objects"} 4096 iris_smh_used{id="TTY_Hash_Table"} 32776 iris_sql_queries_avg_runtime{id="%SYS"} .000057375 iris_sql_queries_avg_runtime{id="all"} .000057375 iris_sql_queries_avg_runtime_std_dev{id="%SYS"} .00000000001060414620168293732 iris_sql_queries_avg_runtime_std_dev{id="all"} .00000000001060414620168293732 iris_sql_queries_per_second{id="%SYS"} .1333333333333333333 iris_sql_queries_per_second{id="all"} .1333333333333333333 iris_system_alerts 3 iris_system_alerts_log 3 iris_system_alerts_new 1 iris_system_state 2 iris_trans_open_count 0 iris_trans_open_secs 0 iris_trans_open_secs_max 0 iris_wd_buffer_redirty 2 iris_wd_buffer_write 2 iris_wd_cycle_time 6 iris_wd_proc_in_global 0 iris_wd_size_write 16 iris_wd_sleep 9969 iris_wd_temp_queue 51 iris_wd_temp_write 0 iris_wdwij_time 3 iris_wd_write_time 2 iris_wij_writes_per_sec 0 iris_wqm_active_worker_jobs{id="SYS"} 0 iris_wqm_commands_per_sec{id="SYS"} 103 iris_wqm_globals_per_sec{id="SYS"} 4 iris_wqm_max_active_worker_jobs{id="SYS"} 0 iris_wqm_max_work_queue_depth{id="SYS"} 0 iris_wqm_waiting_worker_jobs{id="SYS"} 1 ``` ### 3. 通过SNMP 在Caceh' 和IRIS的安装文件夹的SNMP子目录下, 你可以找到.mib文件, 分别是ISC-cache.mib或者ISC-IRIS.mib。如果是ensemble或者health connect, 还会有isc-ensemble.mib。 通过SNMP GET, 您可以使用第3方工具的SNMP客户端获得Caché和IRIS的全部指标。 ### 4. 历史监视器(History Monitor) 如果用户没有专用的监控系统,那么使用历史监视器可以是一个很好的选择。 历史监视器把数据库的读写和系统使用情况的历史数据存在一个表里, 用户可以通过SQL访问。 它的统计的内容为: - CPU Usage - 数据库的大小和Journal的大小 - Global Reference and Updates - 物理读写(Physical reads and writes) - License使用率 这是一个轻量级的监控工具,几乎不对生产环境增加有意义的开销。它收集数据的表的尺寸也很小, 因此SQL查询的速度很快。 (即使收集小时级别的统计数据,每年的硬盘占用也只有130MB左右)。所有它经常被用于建立一个系统性能的基准线,便于日后的性能问题分析,以及容量规划 (capacity planning)。 **启动Caché History Monitor操作** ``` %SYS>do ^%SYSMONMGR 1) Start/Stop System Monitor 2) Set System Monitor Options 3) Configure System Monitor Classes 4) View System Monitor State 5) Manage Application Monitor 6) Manage Health Monitor 7) View System Data 8) Exit Option? 5 1) Set Sample Interval 2) Manage Monitor Classes 3) Change Default Notification Method 4) Manage Email Options 5) Manage Alerts 6) Exit Option? 2 1) Activate/Deactivate Monitor Class 2) List Monitor Classes 3) Register Monitor System Classes 4) Remove/Purge Monitor Class 5) Set Class Sample Interval 6) Debug Monitor Classes 7) Exit Option? 1 Class? %Monitor.System.HistoryMemory Activate class? Yes => yes 1) Activate/Deactivate Monitor Class 2) List Monitor Classes 3) Register Monitor System Classes 4) Remove/Purge Monitor Class 5) Set Class Sample Interval 6) Debug Monitor Classes 7) Exit Option? 1 Class? %Monitor.System.HistoryPerf Activate class? Yes => yes 1) Activate/Deactivate Monitor Class 2) List Monitor Classes 3) Register Monitor System Classes 4) Remove/Purge Monitor Class 5) Set Class Sample Interval 6) Debug Monitor Classes 7) Exit Option? 1 Class? %Monitor.System.HistorySys Activate class? Yes => yes 1) Activate/Deactivate Monitor Class 2) List Monitor Classes 3) Register Monitor System Classes 4) Remove/Purge Monitor Class 5) Set Class Sample Interval 6) Debug Monitor Classes 7) Exit Option? 1 Class? %Monitor.System.HistoryUser Activate class? Yes => yes 1) Activate/Deactivate Monitor Class 2) List Monitor Classes 3) Register Monitor System Classes 4) Remove/Purge Monitor Class 5) Set Class Sample Interval 6) Debug Monitor Classes 7) Exit Option? 2 Class Active SampleInterval ----- ------ -------------- %Monitor.System.HistoryMemory Y default %Monitor.System.HistoryPerf Y default %Monitor.System.HistorySys Y default %Monitor.System.HistoryUser Y default %Monitor.System.AuditCount N default %Monitor.System.AuditEvents N default %Monitor.System.Clients N default %Monitor.System.Diskspace N default %Monitor.System.Freespace N default %Monitor.System.Globals N default %Monitor.System.Journals N default %Monitor.System.License N default %Monitor.System.LockTable N default %Monitor.System.Processes N default %Monitor.System.Routines N default %Monitor.System.Servers N default %Monitor.System.SystemMetrics N default %Monitor.System.CSPGateway N default MyMetric.Freespace N default ``` 注意: 激活后需要重新启动System Monitor。统计数据默认保留60天, 如果希望保留更长的时间, 下面的命令设置为保留一年: `%SYS>do ##class(SYS.History.Hourly).SetPurge(365)` **读取History Monitor数据** 历史监视器把数据存在SQL表里, 表明和其中的字段请参考[在线文档: History Monitor](https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_historymon) !!!注意: SYS_History.PerfData给出采样间隔内的测量值,而daily, hourly的值为计算出的每秒的平均值。 这里给出使用历史监视器数据获得数据的2个例子 1. 读取平均CPU占用 ```sql SELECT Substr(DS.Daily,1,5) as DateH, (100-DS.Sys_CPUIdle) as AvgCPUBusy FROM SYS_History.Daily_SYS DS WHERE element_key='Avg' ORDER BY DateH ``` 2. 读取每天的CPU占用;9am-12am的CPU占用,global reference and update ```sql SELECT Substr(DS.Daily,1,5) Day, (100-DS.Sys_CPUIdle) as Daily_Avg_CPU, Round(AVG(100-H1.Sys_CPUIdle),2) Morning_Avg_CPU , DP.Perf_GloRef, DP.Perf_GloUpdate FROM SYS_History.Daily_Perf DP, SYS_History.Daily_SYS DS, SYS_History.Hourly_Sys H1 WHERE DP.Daily=DS.Daily and DP.element_key='Avg' and DS.element_key='Avg' and H1.element_key='Avg'and substr(DS.Daily,1,5)=Substr(H1.Hourly,1,5) and Substr(H1.Hourly,8,12) in (32400,36000,39600) GROUP BY DS.daily ``` ### 其他 除了上面的4个方式,Caché和IRIS还提供了另外的采集指标的方法,比如说执行routine, 存储过程,查看其他SQL表等等, 后面的文章中会对不同类型的指标做更近一步的介绍。
文章
姚 鑫 · 四月 28, 2021

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

# 第八章 解释SQL查询计划(一) # SQL语句 这个SQL语句列表为每个表提供了SQL查询和其他操作的记录,包括插入、更新和删除。 这些SQL语句链接到一个查询计划,该链接提供冻结该查询计划的选项。 系统为每个SQL DML操作创建一条SQL语句。 这提供了一个按表、视图或过程名称列出的SQL操作列表。 如果更改表定义,可以使用此SQL Statements列表来确定每个SQL操作的查询计划是否会受到此DDL更改的影响,以及/或是否需要修改某个SQL操作。 然后,可以: - 确定每个SQL操作使用哪个查询计划。 可以决定使用反映对表定义所做更改的修改后的查询计划。 或者可以冻结当前查询计划,保留在更改表定义之前生成的查询计划。 - 根据对表定义所做的更改,确定是否对对该表执行SQL操作的例程进行代码更改。 注意:SQL语句是一个SQL例程列表,它们可能会受到表定义更改的影响。 它不应该用作表定义或表数据更改的历史记录。 # 创建SQL语句操作 下面的SQL操作会创建相应的SQL语句: 数据管理(DML)操作包括对表的查询、插入、更新和删除操作。 每个数据管理(DML)操作(动态SQL和嵌入式SQL)在执行时都会创建一个SQL语句。 - 动态SQL `SELECT`命令在准备查询时创建SQL语句。 此外,在管理门户缓存查询列表中创建了一个条目。 - 嵌入式SQL基于指针的`SELECT`命令在`OPEN`命令调用声明的查询时创建SQL语句。管理门户缓存查询列表中不会创建单独的条目。 如果查询引用多个表,则在名称空间的SQL语句中创建一条SQL语句,该语句列出表/视图/过程名列中的所有被引用表,并且对于每个单独的被引用表,该表的SQL语句列表都包含该查询的条目。 SQL语句是在第一次准备查询时创建的。如果多个客户端发出相同的查询,则只记录第一次准备。例如,如果JDBC发出一个查询,然后ODBC发出一个相同的查询,那么SQL语句索引将只有关于第一个JDBC客户端的信息,而不是关于ODBC客户端的信息。 大多数SQL语句都有关联的查询计划。 创建该查询计划时,将解冻该查询计划; 可以随后将该查询计划指定为冻结计划。 带有查询计划的SQL语句包括涉及`SELECT`操作的DML命令。 下面的“计划状态”部分列出了没有查询计划的SQL语句。 注意:SQL语句只列出SQL操作的最新版本。 除非冻结SQL语句,否则InterSystems IRIS®数据平台将用下一个版本替换它。 因此,在例程中重写和调用SQL代码将导致旧的SQL代码从SQL语句中消失。 ## 其他SQL语句操作 下面的SQL命令执行更复杂的SQL语句操作: - `CREATE TRIGGER`: 在定义触发器的表中,无论是在定义触发器还是在提取触发器时,都不会创建SQL语句。 但是,如果触发器对另一个表执行DML操作,那么定义触发器将在被触发器代码修改过的表中创建一个SQL语句。 `Location`指定在其中定义触发器的表。 在定义触发器时定义SQL语句; 删除触发器将删除SQL语句。 触发触发器不会创建SQL语句。 - `CREATE VIEW` 不创建SQL语句,因为没有编译任何内容。 它也不会更改源表的SQL语句的Plan Timestamp。 然而,为视图编译DML命令会为该视图创建一个SQL语句。 # List SQL语句 本节介绍使用Management Portal界面列出SQL语句的详细信息。 也可以使用`^rINDEXSQL`全局返回SQL语句的索引列表。 注意,这个SQL语句List可能包含过时的(不再有效的)List 从Management Portal SQL界面可以列出如下SQL语句: - SQL语句选项卡:此选项卡列出名称空间中的所有SQL语句,先按模式排序,然后按每个模式中的表名/视图名排序。此列表仅包括当前用户拥有权限的那些表/视图。如果SQL语句引用多个表,则表/视图/过程名列将按字母顺序列出所有被引用的表。 - 通过单击列标题,可以按表/视图/过程名、计划状态、位置、SQL语句文本或列表中的任何其他列对SQL语句列表进行排序。这些可排序列使能够快速查找,例如,所有冻结计划(计划状态)、所有缓存查询(位置)或最慢的查询(平均时间)。 - 可以使用此选项卡提供的`Filter`选项将列出的SQL语句缩小到指定的子集。 指定的筛选器字符串筛选SQL语句列表中的所有数据,最有用的是模式或模式。 表名、例程位置或SQL语句文本中找到的子字符串。 过滤字符串不区分大小写,但必须紧跟语句文本标点空格`(name , age, not name,age)`。 如果查询引用了多个表,如果它选择了表/视图/过程名称列中的任何引用表,则`Filter`包括SQL语句。 过滤选项是用户自定义的。 - 最大行选项默认为`1,000`。 最大值为`10,000`。 最小值为`10`。 要列出超过`10,000`条SQL语句,请使用`INFORMATION_SCHEMA.STATEMENTS`。 页面大小和最大行选项是用户自定义的。 - Catalog Details选项卡:选择一个表并显示其Catalog详细信息。 此选项卡提供了一个表的SQL语句按钮,用于显示与该表关联的SQL语句。 注意,如果一个SQL语句引用了多个表,那么它将在表的SQL语句列表中列出每个被引用的表,但只有当前选择的表在表名列中列出。 通过单击列标题,可以根据列表的任何列对表的SQL语句列表进行排序。 可以使用`SQLTableStatements()`目录查询或`INFORMATION_SCHEMA`。 语句,列出根据各种条件选择的SQL语句,如下面的查询SQL语句中所述。 ## 列表列 SQL语句选项卡列出名称空间中的所有SQL语句。目录详细信息选项卡表的SQL语句按钮列出了所选表的SQL语句。这两个列表都包含以下列标题: - `#`:列表行的顺序编号。这些数字与特定的SQL语句没有关联。 - 表/视图/过程名:限定的SQL表(或视图或过程)名:`schema.name`。如果SQL语句查询引用了多个表或视图,则所有这些表或视图都会在此处列出。 - 计划状态:请参阅下面的计划状态。 - 新计划:见“冻结计划”一章中不同的新计划。 - 自然查询:请参阅下面的语句详细信息部分。 - 计数:请参阅下面的性能统计数据。 - 平均计数:请参阅下面的性能统计数据。 - 总时间:请参阅下面的性能统计数据。 - 平均时间:请参阅下面的性能统计数据。 - 标准开发人员:请参阅下面的性能统计数据。 - Location(S):编译查询的位置,例程名称(对于嵌入式SQL)或缓存查询名称(对于动态SQL)。如果包名为`%sqlcq`,则SQL语句为缓存查询。 - SQL语句文本:规范化格式的SQL语句文本(截断为`128`个字符),可能与以下SQL语句文本中指定的命令文本不同。 ## 计划状态 计划状态列出以下内容之一: - 解冻Unfrozen:未冻结,可冻结。 - 解冻/平行Unfrozen/Parallel::未冻结,不能冻结。 - 冻结/显式Frozen/Explicit:由用户动作冻结,可以解冻。 - Frozen/Upgrade:被InterSystems IRIS版本升级冻结,可以解冻。 - blank:没有关联的查询计划: - `INSERT... VALUES()` 命令创建的SQL语句没有关联的查询计划,因此无法解冻或冻结(计划状态列为空)。尽管此SQL命令不会生成查询计划,但它在SQL语句中的列表仍然很有用,因为它允许快速定位针对该表的所有SQL操作。例如,如果向表中添加一列,则可能需要找出该表的所有SQL插入的位置,以便可以更新这些命令以包括此新列。 - 基于游标的`UPDATE`或`DELETE`命令没有关联的查询计划,因此不能解冻或冻结(“计划状态”列为空)。对已声明的游标执行`OPEN`命令会生成一条带有关联查询计划的SQL语句。使用该游标的嵌入式SQL语句(`FETCH cursor, UPDATE...WHERE CURRENT OF cursor, DELETE...WHERE CURRENT OF cursor, and CLOSE cursor`)不生成单独的SQL语句。即使基于游标的`UPDATE`或`DELETE`不会产生查询计划,但SQL语句中列出的查询计划仍然很有用,因为它允许快速定位针对该表的所有SQL操作。 ## SQL语句文本 SQL语句文本通常不同于SQL命令,因为SQL语句生成规范化了字母和空格。 其他差异如下: 如果从Management Portal接口或SQL Shell接口发出查询,所得到的SQL语句与在`SELECT`语句前面加上`DECLARE QRS CURSOR FOR`(其中“QRS”可以是各种生成的游标名称)的查询不同。 这允许语句文本与Dynamic SQL缓存的查询相匹配。 如果SQL命令指定了一个非限定的表或视图名,那么生成的SQL语句将使用模式搜索路径(如果提供了DML)或默认模式名来提供模式。 SQL语句文本在`1024`个字符之后被截断。 要查看完整的SQL语句文本,请显示SQL语句详细信息。 一个SQL命令可能会产生多个SQL语句。 例如,如果一个查询引用一个视图,SQL Statements将显示两个语句文本,一个列在视图名称下,另一个列在基础表名称下。 冻结任意一条语句都会导致两个语句的Plan State为Frozen。 当通过xDBC准备SQL语句时,如果需要这些选项来生成语句索引散列,则SQL语句生成会向语句文本添加SQL Comment Options (`# Options`)。 如下面的例子所示: ```sql DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , :%col(5) FROM SAMPLE . COMPANY /*#OPTIONS {"xDBCIsoLevel":0} */ ``` # 陈旧的SQL语句 删除与SQL语句关联的例程或类时,不会自动删除SQL语句列表。这种类型的SQL语句列表称为陈旧。由于访问此历史信息以及与SQL语句相关联的性能统计信息通常很有用,因此这些过时的条目将保留在管理门户SQL语句列表中。 可以使用`Clean Stale`(清除陈旧)按钮删除这些陈旧条目。清除陈旧删除关联例程或类(表)不再存在或不再包含SQL语句查询的所有非冻结SQL语句。清除陈旧不会删除冻结的SQL语句。 可以使用`$SYSTEM.SQL.Statement.Clean()`方法执行相同的清除陈旧操作。 如果删除与SQL语句关联的表(持久化类),则会修改表/视图/过程名称列,如下例所示:`SAMPLE.MYTESTTABLE - Deleted??;` ;已删除表的名称将转换为全部大写字母,并标记为“`DELETED??`”。或者,如果SQL语句引用了多个表:`SAMPLE.MYTESTTABLE - Deleted?? Sample.Person`. - 对于动态SQL查询,删除表时`Location`列为空,因为与该表关联的所有缓存查询都已自动清除。`CLEAN STALE`删除SQL语句。 - 对于嵌入式SQL查询,`Location`列包含用于执行查询的例程的名称。当更改例程使其不再执行原始查询时,位置列为空。`CLEAN STALE`删除SQL语句。删除查询使用的表时,该表被标记`“Deleted??”`;Clean Stale不会删除SQL语句。 注:系统任务在所有名称空间中每小时自动运行一次,以清除任何可能过时或具有过时例程引用的SQL语句的索引。执行此操作是为了维护系统性能。此内部清理不会反映在管理门户SQL语句列表中。可以使用管理门户监视此每小时一次的清理或强制其立即执行。要查看此任务上次完成和下次调度的时间,请依次选择系统操作、任务管理器、任务调度,然后查看清理SQL语句索引任务。可以单击任务名称查看任务详细信息。在Task Details(任务详细信息)显示中,可以使用Run(运行)按钮强制立即执行任务。请注意,这些操作不会更改SQL语句清单;必须使用Clean Stale来更新SQL语句清单。 # 数据管理(DML)SQL语句 创建SQL语句的数据管理语言(DML)命令包括:`INSERT`、`UPDATE`、`INSERT`或`UPDATE`、`DELETE`、`TRUNCATE TABLE`、`SELECT`和`OPEN CURSOR`(用于声明的基于游标的`SELECT`)。可以使用动态SQL或嵌入式SQL来调用DML命令。可以为表或视图调用DML命令,InterSystems IRIS将创建相应的SQL语句。 注意:系统在准备动态SQL或打开嵌入式SQL游标时(而不是在执行DML命令时)创建SQL语句。SQL语句时间戳记录此SQL代码调用的时间,而不是查询执行的时间(或是否)。因此,SQL语句可能表示从未实际执行的表数据更改。 准备动态SQL DML命令将创建相应的SQL语句。与此SQL语句关联的位置是缓存查询。动态SQL是在从管理门户SQL界面、SQL Shell界面执行SQL或从`.txt`文件导入时准备的。清除未冻结的缓存查询会将相应的SQL语句标记为清除陈旧删除。清除冻结的缓存查询会删除相应SQL语句的位置值。解冻SQL语句会将其标记为Clean Stale删除。 执行非游标嵌入式SQL数据管理语言(DML)命令将创建相应的SQL语句。每个嵌入式SQL DML命令都会创建相应的SQL语句。如果一个例程包含多个嵌入式SQL命令,则每个嵌入式SQL命令都会创建一个单独的SQL语句。(某些嵌入式SQL命令会创建多条SQL语句。)。SQL语句清单的Location列指定包含嵌入式SQL的例程。通过这种方式,SQL语句维护每个嵌入式SQL DML命令的记录。 打开基于游标的嵌入式SQL数据管理语言(DML)例程将创建带有查询计划的SQL语句。 关联的嵌入式SQL语句(`FETCH`游标、`CLOSE`游标)不会生成单独的SQL语句。 在FETCH游标之后,一个关联的`UPDATE table WHERE CURRENT OF cursor 或DELETE FROM table WHERE CURRENT OF cursor`会生成一个单独的SQL语句,但不会生成单独的`Query Plan`。 插入文字值的`INSERT`命令将创建一个“计划状态”列为空的SQL语句。 由于该命令不会创建查询计划,因此无法冻结SQL语句。 ## select命令 调用查询将创建相应的SQL语句。 它可以是一个简单的`SELECT`操作,也可以是一个基于指针的`SELECT/FETCH`操作。 可以对表或视图发出查询。 - 包含`JOIN`的查询为每个表创建相同的SQL语句。 Location是清单中存储的每个表的相同查询。 如SQL语句详细信息例程和关系部分所述,该语句使用以下关系列出所有表。 - 包含选择项子查询的查询为每个表创建相同的SQL语句。 `Location`是清单中存储的每个表的相同查询。 如SQL语句详细信息例程和关系部分所述,该语句使用以下关系列出所有表。 - 引用外部(链接)表的查询不能被冻结。 - 一个包含`FROM`子句`%PARALLEL`关键字的查询可以创建多个SQL语句。 你可以通过调用来显示这些生成的SQL语句: ![image](E3927D1DDE6B49FFA01E0F96682173B7) 这将显示包含原始查询的语句哈希的`Statement`列和包含生成的查询版本的语句哈希的`ParentHash`列。 `%PARALLEL`查询的SQL语句的计划状态为“未冻结/并行”,不能被冻结。 - 不包含FROM子句(因此不引用任何表)的查询仍然创建SQL语句。 例如:`SELECT $LENGTH('this string')`创建一个SQL语句,表列值`%TSQL_sys.snf`。
文章
Michael Lei · 六月 23, 2021

使用 Ansible 自动化部署 Caché 应用程序 - 第 1

部分 Ansible 帮助我解决了快速部署 Caché 和应用程序组件以进行数据平台基准测试的问题。 您可以使用相同的工具和方法来建立您的测试实验室、培训系统、开发或其他环境。 如果在客户站点部署应用程序,可以将大量部署自动化,并确保系统、Caché 和应用程序的配置符合您的应用程序最佳做法标准。 ## 概述 作为一名技术架构师,我们的团队职责之一是在不同供应商的硬件和操作系统上对 InterSystems 数据平台进行基准测试。 通常,基础架构是预发布版,在必须归还或移交给其他人之前,我们的时间有限,因此,快速准确地设置基准测试,让我们有尽可能多的时间来做真正的基准测试工作,这一点至关重要。  多年来,我们通过 shell 脚本小程序来自动执行许多基准测试安装任务,并从速查表和检查清单中剪切和粘贴,但此类操作非常密集,而且容易出错,特别是有许多服务器并且在不同的操作系统之间切换时 - 在 SLES 11、Red Hat 6、Red Hat 7、AIX 等操作系统上安装或使用服务的差异可能很微小,让人厌烦。 在研究了几个可用于自动化配置和管理系统的软件选项之后,我选择了 Ansible 来执行预置数据平台、应用程序和基准测试组件的任务。 需要注意的是,我并没有规定 Ansible 是部署和配置的 **THE** 解决方案。 在选择 Ansible 之前,我研究了其他工具(如 Puppet 和 Chef)的功能和操作。 如果您的组织已经在使用其他工具,您可以使用它们 — 我在 Ansible 中使用的方法和命令等等应该可以转换到其他软件中,我希望这些帖子可以帮助您,不管您使用的是什么工具。 这是本系列的第一个帖子,将介绍在部署 InterSystems 数据平台应用程序时如何使用 Ansible。 本帖介绍如何通过安装 Caché 打下基础,下一帖将扩展解决方案以包括应用程序安装,包括使用 %installer 类。 本帖涵盖: * Ansible 的概述和安装 * Ansible 便于管理和扩展的布局。 * 同时在一个或多个服务器上安装 Caché。 ## 什么是 Ansible? 通过 Ansible 可以在配置一个或多个服务器的同时将复杂的任务自动化,并可以非常简单地添加新服务器。 任务会设计成幂等(您可以在同一台服务器上多次运行相同的脚本,得到的服务器配置将是相同的)。 我选择 Ansible 执行预置任务的一个关键原因是它对系统的要求最低(Python 2.7,Linux 服务器上自带 ),而且它是一个自包含解决方案 — Ansible 代码只安装在控制服务器上,并使用推送架构,通过 OpenSSH 在目标服务器上运行命令和脚本。 所预置的服务器上不需要任何代理。 作为对比,Chef 和 Puppet 采用拉取架构,软件在客户端服务器(Web、数据库等)上加载,并且客户端不断轮询主服务器以查找更新。 Ansible 的推送架构也适合按照您的计划需求逐步实施服务器。 Ansible 是开源的,由社区维护。 Ansible, Inc 从 2015 年开始为 Red Hat 所拥有。 Ansible, Inc 有一个高级的生命周期产品 (Ansible Tower),并提供收费的支持和培训,不过本帖中的所有内容均使用开源命令行版本。 还有一个活跃的社区 (Ansible Galaxy),您可以从中下载许多预制的解决方案来完成大量任务,如安装 Web 服务器、ftp、kerbros,不胜枚举。 例如,对于完整的基准测试部署项目,我包括了一个下载的 Apache 模块,并自定义成在 RHEL、SLES 或 Solaris(以及其他平台)上安装和配置 Apache 2.x。 Ansible 的下载和安装说明可以在 Ansible 网站和 Github 上找到。 如果您有问题或希望做出贡献,可以访问活跃社区。 https://www.ansible.com/get-startedhttp://docs.ansible.com ## 安装 Ansible 本帖中的示例已经在运行 Red Hat 7.0 和 7.2 的虚拟机上进行了测试 - 我也在我的安装了 Centos 7 的笔记本电脑上使用 virtual box 和 vagrant 对 Ansible 控制器服务器进行了初始测试。 Caché 不需要安装在控制器上,所以您的操作系统选择要多于 Caché 支持的平台列表。 为了简单起见,我使用了当前适用于 Red Hat 的 rpm 版本的 Ansible (Ansible 1.9.4),更高的版本可以从 GitHub 获取。 在示例中,我安装的是 cache-2015.2.2.805.0-lnxrhx64,但相同的常规过程也适用于 HealthShare 或 Ensemble 发行版。 您将在后面看到,我们使用特定文件名、目录路径等变量来参数化安装选项。 在第一个帖子中,我将任务削减为基本的 Caché 安装,因此大多数任务是独立于平台的。 当 Ansible playbook 启动时,首要任务之一是获取目标机器的清单 — 操作系统、接口卡、内存详细信息、CPU 数量、磁盘布局等,当运行命令以从目标上运行的实际命令(例如,Red Hat 上的 service start httpd 与 SLES 上的 /etc/init.d/apache2 restart)中提取 Ansible 脚本命令时,将使用目标操作系统的这些信息。 我假定您已读过说明,并且已按照您的平台说明在控制机上安装了 Ansible。 Ansible 必须使用 Linux 系统作为控制器,但目标系统可以是 Linux 或 Windows。 有关 Windows 目标的更多信息,请参见 Ansible 文档。 ### 控制器系统安装示例:在 RHEL/CentOS 7 64 位上安装 Ansible 在 Red Hat 或 CentOS 上,必须先安装 epel-release (Extra Packages for Enterprise Linux) RPM,其中包含 Ansible。 epel 项目面向主要 Linux 发行版设计,提供了许多有用的开源软件包(网络、系统管理、监视等)。 [root@localhost tmp]# wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm : : [root@localhost tmp]# rpm -ivh epel-release-7-5.noarch.rpm : : [root@localhost tmp]# yum --enablerepo=epel info ansible Loaded plugins: langpacks, product-id, search-disabled-repos, subscription-manager Installed Packages Name : ansible Arch : noarch Version : 1.9.4 Release : 1.el7 Size : 7.0 M Repo : installed From repo : epel Summary : SSH-based configuration management, deployment, and task execution system URL : http://ansible.com License : GPLv3+ Description : : Ansible is a radically simple model-driven configuration management, : multi-node deployment, and remote task execution system. Ansible works : over SSH and does not require any software or daemons to be installed : on remote nodes. Extension modules can be written in any language and   : are transferred to managed machines automatically. [root@localhost tmp]# [root@localhost tmp]# sudo yum install ansible : : [root@localhost tmp]# ansible --version ansible 1.9.4     configured module search path = None **很好... 准备开始!** ## Ansible 方面 关于不同的 Ansible 组件(清单、Playbook、模块、角色等)的用法,应查看 Ansible 文档。 为了简化管理以及避免使用大而复杂的脚本文件,使用了预定义的目录结构和搜索路径。 在本帖中,我将使用一个采用 Ansible 建议的文件结构,当您考虑构建更大的安装示例时,可以将本结构用作模型。 所使用的 Ansible 模块带有注释和自我说明,可以在 Github 上获取。 下载文件并通读以了解工作流程。 我们的示例的基本目录包含以下文件; * ansible.cfg:对 Ansible 默认值的更改。 * 清单:定义和描述工作环境。 例如服务器名称/IP。 * <各种>.yml 文件:这些文件描述了将为特定服务器角色运行的任务集。 ### 术语 为了使后面的讨论更清晰,下面是一些 Ansible 术语的快速解释。 模块是用于创建对系统执行的自动化操作的构建块。 每个模块都为特定任务构建,可以使用参数更改该任务。 例如复制文件、创建用户、运行命令、启动服务等。 目前,默认的 Ansible 安装已包含 400 多个模块,另外还有更多来自社区的模块,您也可以创建您自己的模块。 模块组合在一起生成 play 和 playbook,作为执行自动化工作流程的一种方式。 一个 Play 可以有多个任务,一个 Playbook 可以有多个 Play。 角色允许您组合 Playbook。 角色可以被视为根据目标服务器使用情况分组在一起的服务器组件配置。 在本帖的示例中,角色构建配置层以构建服务器。 在我的基准测试设置中,我有以下角色来构建服务器: * hs\_server\_common:配置操作系统,安装 Apache,安装 Caché。 *  webserver:复制 Web 文件(csp、html、js 等),针对应用程序配置 Apache。 * generator:复制文件,创建和配置 webstress 生成器数据库、命名空间、全局映射等。 * dbserver:复制文件,配置数据库服务器系统设置、应用程序数据库、命名空间、全局映射等。 可以组合这些角色来构建不同的服务器类型: * hs\_server\_common + webserver + generator = webstress 生成器服务器。 * hs\_server\_common + webserver = 应用程序 Web 服务器。 * hs\_server\_common + dbsevrer = 数据库服务器。 角色的组成以及每个角色中包含的配置将非常特定于要部署的应用程序。 本帖中的示例将使用最小的任务集,并假定操作系统已预先配置,但是,使用 Ansible 和 Galaxy 上的模块可以实现更复杂和全功能的系统配置。 ## 关于安装 Caché 的说明 {.MsoNormal} 我写了几个例子来介绍一些有趣和有用的特性,下面是一些精华部分。 注意:这些示例可以用作 InterSystems 数据平台(Caché、HealthShare 和 Ensemble)的安装指南。 我写的示例是安装 HealthShare,但 HealthShare 和 Caché 示例中的操作是相同的。 **./testserver/roles/hs\_server\_common/tasks/main.yml** 这是配置操作系统、安装 Apache、安装 Caché 等常见任务的主线脚本。 本帖中对其进行了删减,使其只包含用于 Red Hat 的文件,以及只安装和配置 Caché。 您可以看到,Ansible 在启动后已经在 _ansible_*_ 变量(包括 _ansible\_os\_family_)中保存了操作系统信息,我们可以在脚本中使用这些变量做出决策。 **./testserver/roles/hs\_server\_common/tasks/configure-healthshare2015.yml** 这是用于安装 Caché 的主脚本。 浏览该脚本,会看到针对目标执行的任务的逻辑工作流程,包括: * 创建操作系统用户和组。 * 从控制器上的清单文件夹复制安装文件。 * 解压缩安装文件。 * 使用静默安装方式安装 Caché(请参见下面的注释)。 * 复制 Caché 密钥文件 * 设置默认 Caché 实例 * 重启 Apache * 重启 Caché Caché 的静默安装有几个选项,包括: * 使用 parameters.is 文件。 模板 .isc 文件由以前的安装创建,可以按原样使用,也可以修改。 * 使用 cinstall_silent 以及环境中设置的键值对。 * 使用 %installer 类。 在此示例中,我选择了使用 install\_silent,但是我还包括了一个注释掉的使用参数文件的备用方法,以说明如何在 Ansible 中使用模板文件(请参见 /roles/hs\_server\_common/templates/parameters\_hs20152_rh64.isc)。 在以后的帖子中,我将说明如何使用 %installer 类安装 Caché 以及设置数据库和命名空间。 有关安装选项的详细信息,请参见 Caché 在线文档,社区中也有一个非常好的帖子介绍了 %installer 类的使用。 当您想要安装并配置 Caché,以将 CSPGateway 与除了旧版本 Caché 内部的 Apache 版本以外的 Web 服务器一起使用时,参数文件很有用。 自 Caché 2016.1 起,%installer 提供此功能。 **./testserver/roles/hs\_server\_common/tasks/setup_RedHat.yml** 包含此示例是为了说明如何使用系统特定变量 (ansible_*) 和设置操作系统变量。 **./testserver/roles/hs\_server\_common/vars/*** 变量文件包含键:值对形式的变量,如您所见,这是一种在不同环境和情况下重复使用相同脚本的方法。 ## 运行 Caché 安装 对于此示例,我假定系统可用,并按如下方式进行设置。 1. 控制器已安装 Ansible,并且以下目录填充了来自 Github 的文件和结构。 * **./testserver/***:包含清单、.yml 文件等等的目录树。 包括... * **./testserver/Distribution_Files/Cache**:(包含 Caché 分发包和 cache.key 的清单)。 2. 目标机器已安装 Red Hat 和 Apache。 您需要编辑以下文件来为您的测试环境自定义安装。 1. **inventory_test** 您需要编辑测试服务器名称或 IP 地址。 2. .**/testserver/roles/hs\_server\_common/vars/healthshare2015.yml** 您必须编辑路径以适合您的测试环境。 查看目标服务器的以下路径: * **common\_install\_base_path**:清单文件将复制到该位置后解压,并运行 Caché 安装。 * **ISC\_PACKAGE\_INSTALLDIR**:Caché 安装目录 如果目标服务器上不存在这些目录路径,将会创建。 注:自动化部署的特性之一是并行构建多个服务器。 如果清单文件中有多个服务器,在各个目标服务器上将并发运行每个步骤,直至该步骤完成,然后再在组中的每个服务器上开始下一个步骤。 如果任何服务器上有步骤失败,脚本将停止。 您将看到一条错误消息,帮助您更正问题。 更正错误后,只需从头重新运行 — 这是脚本的一个关键特性 — 脚本设计成幂等。 幂等的意思是,在某个步骤运行某个模块(例如复制文件)时,如果文件已经存在,则该步骤不会重新运行,脚本只是继续下一个步骤。 copy 之类的模块有参数可以设置为强制复制,但这不是默认设置。 仔细检查脚本会发现,在某些情况下使用了“creates”参数,例如:   - name: unattended install of hs using cinstall_silent shell: > ISC_PACKAGE_INSTANCENAME="{{ ISC_PACKAGE_INSTANCENAME }}" ISC_PACKAGE_INSTALLDIR="{{ ISC_PACKAGE_INSTALLDIR }}" ISC_PACKAGE_UNICODE="{{ ISC_PACKAGE_UNICODE }}" ISC_PACKAGE_INITIAL_SECURITY="{{ ISC_PACKAGE_INITIAL_SECURITY }}" ISC_PACKAGE_MGRUSER="{{ ISC_PACKAGE_MGRUSER }}" ISC_PACKAGE_MGRGROUP="{{ ISC_PACKAGE_MGRGROUP }}" ISC_PACKAGE_USER_PASSWORD="{{ ISC_PACKAGE_USER_PASSWORD }}" ISC_PACKAGE_CACHEUSER="{{ ISC_PACKAGE_CACHEUSER }}" ISC_PACKAGE_CACHEGROUP="{{ ISC_PACKAGE_CACHEGROUP }}" ./cinstall_silent chdir="{{ common_install_base_path }}/{{ hs_install_unpack_path }}" args: creates: "{{ ISC_PACKAGE_INSTALLDIR }}/cinstall.log" 上面一节使用 creates 参数告诉 Ansible 模块(在本例中是 shell 模块),此操作创建 cinstall.log 文件。 如果模块发现该文件(Caché 已经安装),则此步骤将不会运行。 好了,全部设置完毕后,我们可以运行安装了。 $ ansible-playbook dbserver.yml PLAY [dbservers] ************************************************************** GATHERING FACTS *************************************************************** ok: [db1] TASK: [hs_server_common | include_vars healthshare2015.yml] ******************* ok: [db1] TASK: [hs_server_common | include_vars os-RedHat.yml] ************************* ok: [db1] etc etc etc TASK: [hs_server_common | Create default cache group] ************************* changed: [db1] TASK: [hs_server_common | Create default cache manager group] ***************** changed: [db1] TASK: [hs_server_common | Create default cache user] ************************** changed: [db1] TASK: [hs_server_common | Create default cache system users] ****************** changed: [db1] TASK: [hs_server_common | Create full hs install temp directory] ************** changed: [db1] TASK: [hs_server_common | Check tar file (gunzipped already) does not exist] *** ok: [db1] TASK: [hs_server_common | Copy healthshare install file] ********************** changed: [db1] TASK: [hs_server_common | un zip hs folder] *********************************** changed: [db1] TASK: [hs_server_common | un tar hs install] ********************************** changed: [db1] TASK: [hs_server_common | Create hs install directory] ************************ changed: [db1] TASK: [hs_server_common | touch ztrak.conf.] ********************************** changed: [db1] TASK: [hs_server_common | Process parameters file] **************************** changed: [db1] TASK: [hs_server_common | unattended install of hs using cinstall_silent] ***** changed: [db1] TASK: [hs_server_common | copy hs key] **************************************** changed: [db1] TASK: [hs_server_common | Set default hs instance] **************************** changed: [db1] TASK: [hs_server_common | restart apache to initialize CSP.ini file] ********** changed: [db1] NOTIFIED: [hs_server_common | restart healthshare] **************************** changed: [db1] PLAY RECAP ******************************************************************** db1 : ok=32 changed=21 unreachable=0 failed=0 如果我们查看目标服务器 — 数据库服务器 Caché 现在已经启动并运行。 $ ccontrol list Configuration 'H2015' (default) directory: /test/hs2015 versionid: 2015.2.1.705.0 conf file: cache.cpf (SuperServer port = 1972, WebServer = 57772) status: running, since Wed Feb 17 15:59:11 2016 state: ok ## 总结 在后续的帖子中,我将构建包含其他任务的脚本,如编辑配置文件和使用 %installer 类配置应用程序。 如果您对此感兴趣并开始创建您自己的部署,请随时与我联系,提出问题或建议。 我经常在全球峰会上发表关于虚拟化和性能的演讲 - 因此,如果您参加今年的全球峰会,请介绍一下您自己,我非常乐意和您聊一聊 Ansible 的使用经验或任何其他系统架构话题。
文章
姚 鑫 · 七月 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