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


最后一行显示了测试报告的URL。
**注意:以这种方式运行测试会在它们执行后从InterSystems IRIS中删除它们。如果在执行测试后返回到Atelier查看测试,将看到一个指示,表明Atelier中可见的文件与服务器不同步。可以保存或重新编译该类,以将代码添加回服务器。
如果使用的是`.cls`文件而不是XML文件,则必须向`RunTest`提供`/loadudl`限定符。**
```java
USER>do ##class(%UnitTest.Manager).RunTest("mytests","/loadudl")
```
# 示例:UnitTest Portal
运行单元测试将生成测试报告。InterSystems IRIS提供了一个用于查看报告的`UnitTest`门户。报告按命名空间组织。
可以使用系统资源管理器System Explorer > Tools > UnitTest Portal导航到UnitTest门户。如有必要,请切换到用户命名空间。

# 示例:在单元测试门户中查看报告
门户将测试结果组织成一系列报告。每个测试报告将测试结果组织到一系列超链接页面中。按照链接查找越来越具体的信息。
第一页提供了所有测试套件的摘要。在这种情况下,所有测试套件都通过了。

单击要查看的报告的`ID`列中的`ID`号。
第二个页面显示每个测试套件的结果。在本例中,`mytest`是测试套件,并且通过了测试。

单击 `mytests`.
第三个页面显示每个测试用例的结果。在本例中,通过了单个测试用例`MyPackage.Tests`。

单击 `MyPackage.Tests`
第四页显示了通过测试方法得出的结果。这里通过了单个测试方法`TestAdd`。

单击 TestAdd.
最后一页显示测试方法中使用的每个`AssertX`宏的结果。在本例中,`AssertEquals`和`AssertNotEquals`都通过了。

# 设置和拆卸
`%UnitTest.TestCase`类提供的方法可用于在一个测试或一组测试执行之前设置测试环境,然后在测试完成后拆除该环境。以下是对这些方法的说明:
方法 | 描述
---|---
`OnBeforeAllTests` |在测试类中的任何测试方法执行之前执行一次。可以设置测试环境。
`OnAfterAllTests` |在测试类中的所有测试方法执行后执行一次。可以破坏测试环境。
`OnBeforeOneTest` |在测试类中的每个测试方法执行之前立即执行。
`OnAfterOneTest` |在文本类中的每个测试方法执行后立即执行。
# 示例:向测试类添加Setup和Tear Down方法
在本例中,将添加一个名为`TestEditContact`的测试方法。此方法验证`MyPackage.Contact`类的`ContactType`属性是否限制为`“Personal”`或`“Business”`。添加了一个`OnBeforeAllTests`方法,该方法在测试执行之前准备数据库。还可以添加一个`OnAfterAllTests`方法,该方法在测试执行后还原数据库状态。
1. 在`Studio`中打开`MyPackage.Tests`(可能需要从`^UnitTestRoot`目录导入它)。
2. 添加`OnBeforeAllTests`和`OnAfterAllTests`方法。
```java
Method OnBeforeAllTests() As %Status
{
Do ##class(MyPackage.Contact).Populate(1)
Return $$$OK
}
```
```java
Method OnAfterAllTests() As %Status
{
Do ##class(MyPackage.Contact).%KillExtent()
Return $$$OK
}
```
`OnBeforeAllTests`方法使用单个`Contact`实例填充数据库。`OnAfterAllTests`方法从数据库中删除所有`Contact`实例。
3. 现在将`TestEditContact`测试方法添加到`MyPackage.Tests`:
```java
Method TestEditContact()
{
set contact=##class(MyPackage.Contact).%OpenId(1)
set contact.Name="Rockwell,Norman"
set contact.ContactType="Friend"
Do $$$AssertStatusNotOK(contact.%Save(),"ContactType = Friend")
Set contact.ContactType="Personal"
Do $$$AssertStatusOK(contact.%Save(),"ContactType = Personal")
}
```
该方法在两种情况下测试执行`%Save on Contact`返回的状态值:为`ContactType`分配无效值之后和为`ContactType`分配有效值之后。
4. 将测试导出到`c:\unittest\mytest`,覆盖现有的`Tests.xml`。
# [源码](https://download.csdn.net/download/yaoxin521123/18703118)
文章
姚 鑫 · 五月 29, 2021
# 第十章 使用FTP
IRIS提供了一个类`%Net.FtpSession`,可以使用它从InterSystems IRIS内建立与FTP服务器的会话。
# 建立FTP会话
要建立FTP会话,请执行以下操作:
1. 创建`%Net.FtpSession`的实例。
2. 可以选择设置此实例的属性,以控制会话的常规行为:
- `Timeout` 超时指定等待FTP服务器回复的时间(以秒为单位)。
- `SSLConfiguration`指定用于连接的激活的`SSL/TLS`配置(如果有)。如果`FTP`服务器使用HTTPS,请使用此选项。
- `TranslateTable`指定在读取文件内容或写入文件内容时要使用的转换表。
- `UsePASV`启用PASV模式。
- 当`FTP`服务器使用`https`时,`SSLCheckServerIdentity`适用。默认情况下,当`%Net.FtpSession`的实例连接到`SSL/TLS`服务器时,它会检查证书服务器名称是否与用于连接到服务器的DNS名称匹配。如果这些名称不匹配,则不允许连接。
若要禁用此检查,请将`SSLCheckServerIdentity`属性设置为0。
3. 调用`Connect()`方法以连接到特定的FTP服务器。
4. 调用`ascii()`或`binary()`方法将传输模式分别设置为ASCII模式或二进制模式。要查看当前传输模式,请检查实例的Type属性的值。
注意:`%Net.FtpSession`的每个方法都返回一个状态,应该检查该状态。这些方法还设置提供有关会话状态的有用信息的属性的值:
- 如果当前已连接,则`CONNECTED`为TRUE,否则为FALSE。
- `ReturnCode`包含上次与FTP服务器通信时的返回代码。
- `ReturnMessage`包含上次与FTP服务器通信时的返回消息。
`Status()`方法返回(通过引用)FTP服务器的状态。
## 命令的转换表
`%Net.FtpSession`在FTP服务器上查看文件名和路径名时,使用`RFC 2640`中介绍的技术自动处理字符集转换。当`%Net.FtpSession`的实例连接到FTP服务器时,它会使用Feat消息来确定服务器是否使用`UTF-8`字符。如果是,它将命令通道通信切换到`UTF-8`,以便所有文件名和路径名都可以正确地与`UTF-8`相互转换。
如果服务器不支持`FEAT`命令或未报告支持`UTF-8`,`%Net.FtpSession`实例将使用`RAW`模式并读取或写入RAW字节。
在极少数情况下,如果需要指定要使用的转换表,请设置`%Net.FtpSession`实例的`CommandTranslateTable`属性。一般情况下,应该没有必要使用此属性。
# FTP文件和系统方法
一旦建立了FTP会话,就可以调用会话实例的方法来执行FTP任务。`%Net.FtpSession`提供以下读写文件的方法:
### Delete()
删除文件。
### Retrieve()
将文件从FTP服务器复制到InterSystems IRIS流中,并通过引用返回该流。要使用此流,请使用标准流方法:`Write()`、`WriteLine()`、`Read()`、`ReadLine()`、`Rewind()`、`MoveToEnd()`和`Clear()`。还可以使用流的`Size`属性。
### RetryRetrieve()
允许继续检索文件,因为给定的流是由上一次使用`Retrieve()`创建的。
### Store()
将 IRIS流的内容写入FTP服务器上的文件。
### Append()
将流的内容追加到指定文件的末尾。
### Rename()
重命名文件。
此外,`%Net.FtpSession`提供了导航和修改FTP服务器上的文件系统的方法:`GetDirectory()`、`SetDirectory()`、`SetToParentDirectory()`和`MakeDirectory()`。
要检查文件系统的内容,请使用`list()`或`NameList()`方法。
- `List()`创建一个流,其中包含其名称与给定模式匹配的所有文件的列表,并通过引用返回该流。
- `NameList()`创建文件名数组并通过引用返回该数组。
还可以使用`ChangeUser()`方法更改为其他用户;这比注销并再次登录要快。使用`Logout()`方法注销。
`System()`方法返回(通过引用)有关托管FTP服务器的计算机类型的信息。
`Size()`和`MDTM()`方法分别返回文件的大小和修改时间。
使用通用`sendCommand()`方法向FTP服务器发送命令并读取响应。此方法可用于发送`%Net.FtpSession`中未明确支持的命令。
# 使用链接的流上载大文件
如果要上传大文件,请考虑使用流接口的`LinkToFile()`方法。也就是说,不是创建流并将文件读入其中,而是创建流并将其链接到文件。在调用`%Net.FtpSession`的`Store()`方法时使用此链接流。
```java
Method SendLargeFile(ftp As %Net.FtpSession, dir As %String, filename As %String)
{
Set filestream=##class(%FileBinaryStream).%New()
Set sc=filestream.LinkToFile(dir_filename)
If $$$ISERR(sc) {do $System.Status.DisplayError(sc) quit }
//上传的文件将与原始文件同名
Set newname=filename
Set sc=ftp.Store(newname,filestream)
If $$$ISERR(sc) {do $System.Status.DisplayError(sc) quit }
}
```
# 自定义FTP服务器发出的回调
可以自定义`FTP`服务器生成的回调。例如,通过这样做,可以向用户提供服务器仍在处理大型传输的指示,或允许用户中止传输。
要自定义FTP回调,请执行以下操作:
1. 创建`%Net.FtpCallback`的子类。
2. 在这个子类中,实现`RetrieveCallback()`方法,该方法在从FTP服务器接收数据时定期调用。
3. 还要实现`StoreCallback()`方法,在将数据写入FTP服务器时会定期调用该方法。
4. 创建`FTP`会话时(如“建立FTP会话”中所述),将回调属性设置为等于的子类`%Net.FtpCallback`。
文章
Qiao Peng · 十月 20, 2022
在InterSystems IRIS医疗版里有一个文件压缩解压的适配器HS.Util.Zip.Adapter和对应的文件压缩解压业务操作HS.Util.Zip.Operations。集成产品可以使用它们进行文件的压缩和解压操作。这2个类的联机文档说明较少,这里介绍它们的使用方法。
1. 基础配置
InterSystems IRIS使用操作系统的压缩和解压缩能力,因此需要注册操作系统执行压缩解压的命令。
在管理门户的Health标签页下,选中配置注册(Configuration Registry):
在其中增加2个注册项目:
\ZipUtility\UnZipCommand 和\ZipUtility\ZipCommand,分别代表解压和压缩命令。适配器HS.Util.Zip.Adapter会检查这2个注册项并得到相应的命令。各个操作系统的命令并不一样,示例如下:
\ZipUtility\UnZipCommand 解压缩命令
Windows
"c:\program files\7-zip\7z" x %1 -o. -r
非Windows
unzip %1 -d .
\ZipUtility\ZipCommand 压缩命令
Windows
"c:\program files\7-zip\7z" a %1 . -r
非Windows
zip -rm %1 .
注意,其中要有%1,代表解压后的或压缩后的目标文件路径。
2. 适配器配置
可以直接使用业务操作HS.Util.Zip.Operations,无需自己开发业务操作类。这个系统提供的业务操作使用的就是适配器HS.Util.Zip.Adapter。
将业务操作HS.Util.Zip.Operations加入Production,配置其工作目录(WorkingDirectory),就是上面提到压缩/解压命令中%1代表的目录。注意,这个目录并非HS.Util.Zip.Operations输出的目标路径,它只是压缩/解压过程中用到的临时目录。这个业务操作执行压缩/解压后,会自动删除临时目录中的文件,而将目标文件流数据保存到响应消息里。
3. 调用压缩/解压业务操作
这个业务操作的请求消息是HS.Message.ZipRequest,响应消息是HS.Message.ZipResponse。
3.1 请求消息准备
HS.Message.ZipRequest里有如下属性:
Operation:执行的操作,压缩或解压缩。可用值为"FromZip" - 解压缩,和"ToZip" 压缩。
File:解压文件的Stream数据,类型为%Stream.GlobalBinary。在执行文件解压时才需要设置该属性 - 需要将解压文件的Stream赋值给File属性。
Items: 文件项目列表,列表元素类型为HS.Types.ZipItem。在执行文件压缩时才需要设置该属性 - 需要将多个压缩文件的数据赋值给Items列表元素的以下属性:
Filename:未来解压缩出来的目标文件名
Path:未来解压缩出来的目标文件子目录
File:需要压缩的文件流数据
3.2 处理响应消息
HS.Message.ZipResponse有如下属性:
File:在执行文件压缩操作时,该属性保存压缩后的流数据。在执行解压操作时,该属性为空。
Items:文件项目列表,列表元素类型为HS.Types.ZipItem。在执行文件解压操作时,该列表属性的元素保护以下属性:
Filename:解压缩出来的目标文件名
Path:解压缩出来的目标文件子目录
File:解压缩出来的流数据
4. 示例代码
以下是直接调用HS.Util.Zip.Operations的业务服务代码示例。
4.1 压缩示例
Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject) As %Status
{
//实例化请求消息
Set tReq = ##class(HS.Message.ZipRequest).%New()
//设置源文件目录
Set tSrcFolder = "/Users/test/irishealth/mgr/Temp/"
//设置请求消息为压缩操作
Set tReq.Operation = "ToZip"
Set tFile = ##class(%Stream.FileBinary).%New()
Set sc=tFile.LinkToFile(tTgtFolder_"output.zip")
Set tReq.File = tFile
//压缩的第一个文件
Set tItem1 = ##class(HS.Types.ZipItem).%New()
//设置未来解压缩的文件名
Set tItem1.Filename = "test.png"
//设置未来解压缩的子文件夹
Set tItem1.Path = "pic/"
//打开目标文件,获取流数据
Set tFileSrc = ##class(%Stream.FileBinary).%New()
Do tFileSrc.LinkToFile(tSrcFolder_tItem1.Filename)
Do tItem1.File.CopyFrom(tFileSrc)
kill tFileSrc
//将压缩文件信息插入列表
Do tReq.Items.Insert(tItem1)
//压缩的第二个文件
Set tItem2 = ##class(HS.Types.ZipItem).%New()
//设置未来解压缩的文件名
Set tItem2.Filename = "HS.SDA3.xsd"
//设置未来解压缩的子文件夹
Set tItem2.Path = "code/"
//打开目标文件,获取流数据
Set tFileSrc = ##class(%Stream.FileBinary).%New()
Do tFileSrc.LinkToFile(tSrcFolder_tItem2.Filename)
Do tItem2.File.CopyFrom(tFileSrc)
kill tFileSrc
//将压缩文件信息插入列表
Do tReq.Items.Insert(tItem2)
//调用业务操作
Do ..SendRequestSync("HS.Util.Zip.Operations",tReq,.tRes)
//设置目标路径
Set tFolder = "/Users/test/irishealth/mgr/Temp/Target/"
//保存压缩文件到目标路径
Set tTgtFile = ##class(%Stream.FileBinary).%New()
Set tFullPath = tFolder_"output.zip"
Set tTgtFile.Filename = tFullPath
Set tSC = tTgtFile.CopyFrom(tRes.File)
Set tSC= tTgtFile.%Save()
Quit $$$OK
}
4.2 解压缩示例
Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject) As %Status
{
//实例化请求消息
Set tReq = ##class(HS.Message.ZipRequest).%New()
//设置请求消息为解压缩操作
Set tReq.Operation = "FromZip"
//打开压缩文件,获取流数据
Set tFileSrc = ##class(%Stream.FileBinary).%New()
Set sc=tFileSrc.LinkToFile("/Users/test/Downloads/TestCase.zip")
//Set tReq.File = tFileSrc
Do tReq.File.CopyFrom(tFileSrc)
kill tFileSrc
//调用业务操作
Do ..SendRequestSync("HS.Util.Zip.Operations",tReq,.tRes)
//设置目标文件路径
Set tTgtFolder = "/Users/test/irishealth/mgr/Temp/"
//获取解压后的数据,并保存到目标文件路径下
For i=1:1:tRes.Items.Count()
{
Set tFileItem = tRes.Items.GetAt(i)
Set tSubFolder = tFileItem.Path
Set tFileName = tFileItem.Filename
Set tTgtFile = ##class(%Stream.FileBinary).%New()
Set tFullPath = tTgtFolder_tSubFolder_tFileName
Set tTgtFile.Filename = tFullPath
Set tSC = tTgtFile.CopyFrom(tFileItem.File)
Set tSC= tTgtFile.%Save()
Kill tTgtFile
}
Quit $$$OK
}
文章
Weiwei Gu · 十一月 29, 2021
不是所有的多模型数据库都是相同的
作者:David Menninger
今天,许多现代应用程序需要的数据库管理能力往往不能通过一种方式就能实现。例如,当我建立一个支持旅游推荐和预订业务的应用程序时,我可能需要使用一些不同类型的数据库,包括用于用户会话的键值存储,用于产品目录的文档数据库,用于推荐的图形数据库,以及用于财务数据的关系数据库。由于各种原因,选择一个(关系型)数据库就可以了这种一刀切的数据库时代,已经离我们很远了。虽然关系型数据库仍然是许多应用的正确选择,但对于某些类型的应用,非关系型数据库提供了关系型数据库根本无法提供的优势。
关系型数据库用表和行表示数据,并使用结构化查询语言(SQL)来访问和操作数据。对于需要可靠性和ACID(原子性、一致性、隔离性和持久性)保证的事务性应用,以及需要SQL查询和报告的效率和简单性的应用,它们是一个很好的选择。 但是,关系型数据库是有代价的;它们需要数据库管理员,需要遵守预先定义的关系型结构,而且随着数据规模和工作负载的增加,它们的扩展也不经济。尽管如此,关系型数据库仍然是许多关键任务应用的正确选择,并继续为其提供动力。
相比之下,非关系型数据库,包括文档、对象、图形和键值数据库等,比关系型数据库具有某些优势--尤其是在灵活性和扩展性方面。非关系型数据库不需要DBA创建预先定义的模式,应用程序开发人员能够更容易地存储和管理数据,而不必担心映射固定的数据结构。
幸运的是,所有这些不同种类的数据库技术都是很成熟稳健的,也为应用开发者提供了丰富的功能,他们可以善加利用。
今天市场上有数以百计的数据库;非关系型数据库约占所有部署到生产中的数据库的一半。
但是,将多种数据库技术纳入一个应用程序并不总是那么简单。
多模型的一种方法--称为混合持久化--采用不同的数据库来支持每种类型的数据结构。 这是一种最佳的方法。 但是,在应用程序的整个生命周期中实施、同步和维护不同的数据库系统是复杂的,而且容易出错。
另一种方法是使用业界所称的多模型数据库;一种在同一 "产品 "中支持各种数据表示的数据库。但不是所有的多模型数据库都是一样的。有些数据库坚持混合范式,为不同的数据表示法采用多个独立的数据库引擎,造成数据的重复,并需要在不同的数据存储之间进行映射和整合。 还有一些支持引擎内的不同模型,但不支持相同的数据。
在InterSystems,我们已经开发了一种纯粹的多模型数据库管理方法。我们的产品,InterSystems IRIS数据平台存储了数据的单一表示。它利用了一个单一的数据库引擎,支持关系型和非关系型的数据访问和操作,没有重复。对于非关系型访问,它不需要预先定义的模式。它提供事务性的ACID保证,可以纵向和横向扩展,并支持本地、公共和私有云环境,以及混合(公共/公共、公共/私有、云/本地)部署环境。同样的数据可以使用SQL访问和操作,也可以作为文档、对象或键值数据。应用程序开发人员不需要使用多个数据库,也不需要在多个数据存储中整合和同步数据。
我们的多模型方法整合了关系型和非关系型技术的优点,而摒弃了其缺点,也没有与多角化持久性相关的复杂性或低效率。 所有这些都整合在我们从头开始建立的这个单一的数据库管理系统中了。
(David Menninger是Ventana Research的高级副总裁和研究总监,也是长期的行业老兵,他对多种数据表示的需求以及多模型数据库的各种方法的优势和劣势进行了深刻的分析。你可以在这里阅读他的报告。)
https://www.intersystems.com/data-excellence-blog/not-all-multi-model-dbms-are-created-equal/
文章
姚 鑫 · 四月 30, 2021
# 第八章 解释SQL查询计划(二)
# SQL语句的详细信息
有两种方式显示SQL语句的详细信息:
- 在SQL Statements选项卡中,通过单击左侧列中的Table/View/Procedure Name链接选择一个SQL Statement。
这将在单独的选项卡中显示SQL语句详细信息。
该界面允许打开多个选项卡进行比较。
它还提供了一个Query Test按钮,用于显示SQL Runtime Statistics页面。
- 从表的Catalog Details选项卡(或SQL Statements选项卡)中,通过单击右边列中的Statement Text链接选择一个SQL语句。
这将在弹出窗口中显示SQL语句详细信息。
可以使用“SQL语句详细信息”显示来查看查询计划,并冻结或解冻查询计划。
“SQL语句详细信息”提供冻结或解冻查询计划的按钮。
它还提供了一个Clear SQL Statistics按钮来清除性能统计,一个`Export`按钮来将一个或多个SQL语句导出到一个文件,以及一个`Refresh`和`Close`页面按钮。
SQL语句详细信息显示包含以下部分。
每个部分都可以通过选择部分标题旁边的箭头图标展开或折叠:
- 语句详细信息,其中包括性能统计
- 编译设置
- 语句在以下例程中定义
- 语句使用如下关系
- 语句文本和查询计划(在其他地方描述)
## 声明的细节部分
- 语句散列Statement hash:语句定义的内部散列表示形式,用作SQL语句索引的键(仅供内部使用)。
有时,看起来相同的SQL语句可能具有不同的语句散列项。
需要生成不同SQL语句的代码的设置/选项的任何差异都会导致不同的语句散列。
这可能发生在支持不同内部优化的不同客户端版本或不同平台上。
- 时间戳`Timestamp`:最初,创建计划时的时间戳。
这个时间戳会在冻结/解冻之后更新,以记录计划解冻的时间,而不是重新编译计划的时间。
可能必须单击Refresh Page按钮来显示解冻时间戳。
将Plan Timestamp与包含该语句的例程/类的`datetime`值进行比较,可以知道,如果再次编译该例程/类,它是否使用了相同的查询计划。
- 版本Version:创建计划的InterSystems IRIS版本。
如果“计划”状态是“冻结/升级”,则这是InterSystems IRIS的早期版本。
解冻查询计划时,“计划”状态变为“解冻”,“版本”变为当前的InterSystems IRIS版本。
- 计划状态Plan state:冻结/显式、冻结/升级、解冻、解冻/并行。
Frozen/Explicit意味着该语句的计划已被显式用户操作冻结,无论生成此SQL语句的代码发生了什么变化,该冻结的计划都将是将要使用的查询计划。
冻结/升级意味着该语句的计划已被InterSystems IRIS版本升级自动冻结。
解冻意味着该计划目前处于解冻状态,可能被冻结。
Unfrozen/Parallel表示该计划被解冻,并使用`%Parallel`处理,因此不能被冻结。
`NULL`(空白)计划状态意味着没有关联的查询计划。
- 自然查询Natural query:一个布尔标志,指示该查询是否是“自然查询”。
如果勾选此项,则该查询是自然查询,不会记录查询性能统计信息。
如果不检查,性能统计可能会被记录;
其他因素决定了统计数据是否真正被记录下来。
自然查询被定义为嵌入式SQL查询,它非常简单,记录统计数据的开销会影响查询性能。
将统计信息保存在自然查询上没有任何好处,因为查询已经非常简单了。
一个很好的自然查询示例是`SELECT Name INTO:n FROM Table WHERE %ID=?`
这个查询的`WHERE`子句是一个相等条件。
此查询不涉及任何循环或任何索引引用。
动态SQL查询(缓存查询)不会被标记为自然查询;
缓存查询的统计数据可能被记录,也可能不被记录。
- 冻结计划不同Frozen plan different:冻结计划时,会显示该字段,显示冻结的计划与未冻结的计划是否不同。
冻结计划时,语句文本和查询计划将并排显示冻结的计划和未冻结的计划,以便进行比较。
本节还包括五个查询性能统计字段,将在下一节中进行描述。
## 性能统计数据
执行查询会将性能统计数据添加到相应的SQL语句。
此信息可用于确定哪些查询执行得最慢,哪些查询执行得最多。
通过使用这些信息,您可以确定哪些查询将通过优化提供显著的好处。
除了SQL语句名称、计划状态、位置和文本之外,还为缓存查询提供了以下附加信息:
- 计数Count:运行此查询次数的整数计数。
如果对该查询产生不同的查询计划(例如向表中添加索引),则将重置该计数。
- 平均计数Average count:每天运行此查询的平均次数。
- 总时间Total time:运行此查询所花费的时间(以秒为单位)。
- 平均时间Average time:运行此查询所花费的平均时间(以秒为单位)。
如果查询是缓存的查询,则查询的第一次执行所花费的时间很可能比从查询缓存中执行优化后的查询所花费的时间要多得多。
- 标准差Standard deviation:总时间和平均时间的标准差。
只运行一次的查询的标准偏差为0。
运行多次的查询通常比只运行几次的查询具有更低的标准偏差。
- 第一次看到的日期Date first seen:查询第一次运行(执行)的日期。
这可能与`Last Compile Time`不同,后者是准备查询的时间。
`UpdateSQLStats`任务会定期更新已完成的查询执行的查询性能统计数据。
这将最小化维护这些统计信息所涉及的开销。
因此,当前运行的查询不会出现在查询性能统计中。
最近完成的查询(大约在最近一个小时内)可能不会立即出现在查询性能统计中。
可以使用Clear SQL Statistics按钮清除这6个字段的值。
InterSystems IRIS不单独记录`%PARALLEL`子查询的性能统计数据。
%PARALLEL子查询统计信息与外部查询的统计信息相加。
由并行运行的实现生成的查询没有单独跟踪其性能统计信息。
InterSystems IRIS不记录“自然”查询的性能统计数据。
如果系统收集了统计信息,则会降低查询性能,而自然查询已经是最优的,因此没有进行优化的可能。
可以在“SQL语句”选项卡显示中查看多个SQL语句的查询性能统计信息。
您可以按任何列对SQL Statements选项卡列表进行排序。
这使得很容易确定,例如,哪个查询具有最大的平均时间。
还可以通过查询`INFORMATION.SCHEMA.STATEMENTS`类属性来访问这些查询性能统计数据,如查询SQL语句中所述。
## 编译设置部分
- 选择模式`Select mode`:编译语句时使用的`SelectMode`。
对于DML命令,可以使用`#SQLCompile Select`;
默认为Logical。
如果`#SQLCompile Select=Runtime`,调用`$SYSTEM.SQL.Util.SetOption()`方法的`SelectMode`选项可以改变查询结果集的显示,但不会改变SelectMode值,它仍然是Runtime。
- 默认模式Default schema(s):编译语句时设置的默认模式名。
这通常是在发出命令时生效的默认模式,尽管SQL可能使用模式搜索路径(如果提供的话)而不是默认模式名来解析非限定名称的模式。
但是,如果该语句是嵌入式SQL中使用一个或多个`#Import`宏指令的DML命令,则`#Import`指令指定的模式将在这里列出。
- 模式路径Schema path:编译语句时定义的模式路径。
如果指定,这是模式搜索路径。
如果没有指定架构搜索路径,则此设置为空。
但是,对于在`#Import`宏指令中指定搜索路径的DML Embedded SQL命令,`#Import`搜索路径显示在默认模式设置中,并且该模式路径设置为空白。
- 计划错误Plan Error:该字段仅在使用冻结计划时发生错误时出现。
例如,如果一个查询计划使用一个索引,则该查询计划被冻结,然后该索引从表中删除,就会出现如下的计划错误:`Map 'NameIDX' not defined in table 'Sample.Person', but it was specified in the frozen plan for the query`.
删除或添加索引将导致重新编译表,从而更改“最后编译时间”值。
一旦导致错误的条件得到纠正,`Clear Error`按钮可用于清除`Plan Error`字段——例如,通过重新创建缺失的索引。
在错误条件被纠正后使用“清除错误”按钮会导致“计划错误”字段和“清除错误”按钮消失。
## 例程和关系部分
语句在以下例程部分中定义:
- 例程`Routine`:与缓存查询关联的类名(对于动态SQL DML),或者例程名(对于嵌入式SQL DML)。
- 类型:类方法或`MAC`例程(对于嵌入式SQL DML)。
- 上次编译时间`Last Compile Time`:例程的上次编译时间或准备时间。如果SQL语句解冻,重新编译MAC例程会同时更新此时间戳和Plan时间戳。如果SQL语句已冻结,则重新编译MAC例程仅更新此时间戳;在您解冻计划之前,Plan时间戳不会更改;然后Plan时间戳将显示计划解冻的时间。
语句使用以下关系部分列出了一个或多个用于创建查询计划的定义表。对于使用查询从另一个表提取值的`INSERT`,或者使用`FROM`子句引用另一个表的`UPDATE`或`DELETE`,这两个表都在此处列出。每个表都列出了下列值:
- 表或视图名称`Table or View Name`:表或视图的限定名称。
- 类型`Type`:表或视图。
- 上次编译时间`Last Compile Time`:表(持久化类)上次编译的时间。
- `Classname`:与表关联的类名。
本节包括用于重新编译类的编译类选项。如果重新编译解冻计划,则所有三个时间字段都会更新。如果重新编译冻结的计划,则会更新两个上次编译时间字段,但不会更新计划时间戳。解冻计划并单击刷新页面按钮后,计划时间戳将更新为计划解冻的时间。
# 查询SQL语句
可以使用`SQLTableStatements()`存储查询返回指定表的SQL语句。下面的示例显示了这一点:
```java
/// w ##class(PHA.TEST.SQL).SQLTableStatements()
ClassMethod SQLTableStatements()
{
SET mycall = "CALL %Library.SQLTableStatements('Sample','Person')"
SET tStatement = ##class(%SQL.Statement).%New()
SET qStatus=tStatement.%Prepare(mycall)
IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
SET rset=tStatement.%Execute()
IF rset.%SQLCODE '= 0 {WRITE "SQL error=",rset.%SQLCODE QUIT}
DO rset.%Display()
}
```
```java
DHC-APP>w ##class(PHA.TEST.SQL).SQLTableStatements()
Dumping result #1
SCHEMA RELATION_NAME PLAN_STATE LOCATION STATEMENT
SAMPLE PERSON 0 %sqlcq.DHCdAPP.cls228.1 DECLARE C CURSOR FOR SELECT * INTO :%col(1) , :%col(2) , :%col(3) , :%col(4) , :%col(5) , :%col(6) , :%col(7) , :%col(8) , :%col(9) , :%col(10) , :%col(11) , :%col(12) , :%col(13) , :%col(14) , :%col(15) FROM SAMPLE . PERSON
SAMPLE PERSON 0 Sample.Person.1 SELECT AGE , DOB , FAVORITECOLORS , HOME , NAME , OFFICE , SSN , SPOUSE , X__CLASSNAME , HOME_CITY , HOME_STATE , HOME_STREET , HOME_ZIP , OFFICE_CITY , OFFICE_STATE , OFFICE_STREET , OFFICE_ZIP INTO :%e ( ) FROM %IGNOREINDEX * SAMPLE . PERSON WHERE ID = :%rowid
...
CURSOR FOR SELECT P . NAME , P . AGE , E . NAME , E . AGE FROM %ALLINDEX SAMPLE . PERSON AS P LEFT OUTER JOIN SAMPLE . EMPLOYEE AS E ON P . NAME = E . NAME WHERE P . AGE > 21 AND %NOINDEX E . AGE < 65
SAMPLE PERSON 0 PHA.TEST.SQL.1 SELECT NAME , SPOUSE INTO :name , :spouse FROM SAMPLE . PERSON WHERE SPOUSE IS NULL
SAMPLE PERSON 0 PHA.TEST.ObjectScript.1 SELECT NAME , DOB , HOME INTO :n , :d , :h FROM SAMPLE . PERSON
70 Rows(s) Affected
```
可以使用`INFORMATION_SCHEMA`包表来查询SQL语句列表。InterSystems IRIS支持以下类:
- `INFORMATION_SCHEMA.STATEMENTS`:包含当前名称空间中的当前用户可以访问的SQL语句索引项。
- `INFORMATION_SCHEMA.STATEMENT_LOCATIONS`:包含调用SQL语句的每个例程位置:持久类名或缓存查询名。
- `INFORMATION_SCHEMA.STATEMENT_RELATIONS`:包含SQL语句使用的每个表或视图条目。
以下是使用这些类的一些示例查询:
下面的示例返回命名空间中的所有SQL语句,列出哈希值(唯一标识规范化SQL语句的计算ID)、冻结状态标志(值0到3)、准备语句和保存计划时的本地时间戳以及语句文本本身:
```sql
SELECT Hash,Frozen,Timestamp,Statement FROM INFORMATION_SCHEMA.STATEMENTS
```

以下示例返回所有冻结计划的SQL语句,指示冻结的计划是否与未冻结的计划不同。请注意,解冻语句可以是`Frozen=0`或`Frozen=3`。不能冻结的单行INSERT等语句在冻结列中显示NULL:
```sql
SELECT Frozen,FrozenDifferent,Timestamp,Statement FROM INFORMATION_SCHEMA.STATEMENTS
WHERE Frozen=1 OR Frozen=2
```
以下示例返回给定SQL表的所有SQL语句和语句所在的例程。(请注意,指定表名(`SAMPLE.PERSON`)时必须使用与SQL语句文本中相同的字母大小写:全部大写字母):
```sql
SELECT Statement,Frozen,STATEMENT_LOCATIONS->Location AS Routine,STATEMENT_LOCATIONS->Type AS RoutineType
FROM INFORMATION_SCHEMA.STATEMENTS
WHERE STATEMENT_RELATIONS->Relation='SAMPLE.PERSON'
```

以下示例返回当前命名空间中具有冻结计划的所有SQL语句:
```sql
SELECT Statement,Frozen,Frozen_Different,STATEMENT_LOCATIONS->Location AS Routine,STATEMENT_LOCATIONS->Type AS RoutineType
FROM INFORMATION_SCHEMA.STATEMENTS
WHERE Frozen=1 OR Frozen=2
```
以下示例返回当前命名空间中包含`COUNT(*)`聚合函数的所有SQL语句。(请注意,指定语句文本(`COUNT(*)`)时必须使用与SQL语句文本相同的空格):
```sql
SELECT Statement,Frozen,STATEMENT_LOCATIONS->Location AS Routine,STATEMENT_LOCATIONS->Type AS RoutineType
FROM INFORMATION_SCHEMA.STATEMENTS
WHERE Statement [ ' COUNT ( * )
```

# 导出和导入SQL语句
可以将SQL语句作为`XML`格式的文本文件导出或导入。这使可以将冻结的计划从一个位置移动到另一个位置。SQL语句导出和导入包括关联的查询计划。
可以导出单个SQL语句,也可以导出命名空间中的所有SQL语句。
可以导入先前导出的包含一个或多个SQL语句的XML文件。
注意:将SQL语句作为XML导入不应与从文本文件导入和执行SQL DDL代码混淆。
## 导出SQL语句
导出单个SQL语句:
- 使用SQL语句详细资料页导出按钮。在管理门户系统资源管理器SQL界面中,选择SQL语句选项卡,然后单击语句以打开SQL语句详细信息页。选择导出按钮。这将打开一个对话框,允许选择将文件导出到服务器(数据文件)或浏览器。
- 服务器(默认):输入导出`XML`文件的完整路径名。第一次导出时,此文件的默认名称为`statementexport.xml`。当然,可以指定不同的路径和文件名。成功导出SQL语句文件后,上次使用的文件名将成为默认值。
默认情况下,未选中在后台运行导出复选框。
- Browser:将文件`statementexport.xml`导出到用户默认浏览器中的新页面。可以为浏览器导出文件指定其他名称,或指定其他软件显示选项。
- 使用`$SYSTEM.SQL.Statement.ExportFrozenPlans()`方法。
导出命名空间中的所有SQL语句:
- 使用管理门户中的导出所有对帐单操作。从管理门户系统资源管理器SQL界面中,选择操作下拉列表。从该列表中选择Export all Statements。这将打开一个对话框,允许您将命名空间中的所有SQL语句导出到服务器(数据文件)或浏览器。
- 服务器(默认):输入导出XML文件的完整路径名。第一次导出时,此文件的默认名称为`statementexport.xml`。当然,可以指定不同的路径和文件名。成功导出SQL语句文件后,上次使用的文件名将成为默认值。
默认情况下,在后台运行导出复选框处于选中状态。这是导出所有SQL语句时的建议设置。选中在后台运行导出时,系统会为提供一个查看后台列表页面的链接,可以在该页面中查看后台作业状态。
- Browser:将文件`statementexport.xml`导出到用户默认浏览器中的新页面。可以为浏览器导出文件指定其他名称,或指定其他软件显示选项。
使用`$SYSTEM.SQL.Statement.ExportAllFrozenPlans()`方法。
## 导入SQL语句
从先前导出的文件导入一条或多条SQL语句:
- 使用管理门户中的导入对帐单操作。从管理门户系统资源管理器SQL界面中,选择操作下拉列表。从该列表中选择Import Statements。这将打开一个对话框,允许指定导入XML文件的完整路径名。
默认情况下,在后台运行导入复选框处于选中状态。这是导入SQL语句文件时的推荐设置。选中在后台运行导入时,系统会为您提供一个查看后台列表页面的链接,可以在该页面中查看后台作业状态。
使用`$SYSTEM.SQL.Statement.ImportFrozenPlans()`方法。
## 查看和清除后台任务
在管理门户系统操作选项中,选择后台任务,查看导出和导入后台任务的日志。可以使用清除日志按钮清除此日志。
文章
Michael Lei · 六月 7, 2023
你好社区!
今天我继续我的全球峰会活动,欢迎你们来到第二天。
上午的演讲专门针对 IRIS 数据平台(太棒了!)。
Scott Gnau 首先解释了为什么 IRIS 被称为 IRIS。我居然才知道!
吃完饭后,我们每个人都去参加我们注册的会议、研讨会和活动。
开发者社区展位所在的“技术交流”区域,一如既往地迅速爆满。
有人在谈论容器,它把很多人聚集在一起!
白天,社区版主会在展位前停下来询问发生了什么事并与我聊天,例如@Muhammad.Waseem 、@Dmitry.Maslennikov和@Guillaume.Rongier7183。
或者还有@John.Murray 和@David.Loveluck
或者我会在走廊里找到他们,比如@José.Pereira。
全球峰会的仙女教母@Maureen.Flaherty 也过来询问是否一切顺利。
@Raj.Singh5479 也拜访了我们。
@Guillaume.Rongier7183 照顾了最后一个在展台前停下的人。还看到了@Jim.Regan 。
在工作日结束时,我们能够享受轻松的时光。本届全球峰会以“Road to the races”为主题,在“Gulfstream Park”的“Carousel Club”举行。
我们都很开心!
这就是今天的全部……明天更多!
文章
姚 鑫 · 五月 21, 2021
# 第二章 设置和获取HTTP标头
# 设置和获取HTTP标头
可以设置和获取HTTP标头的值。
`%Net.HttpRequest`的以下每个属性都包含具有相应名称的HTTP标头的值。如果不设置这些属性,则会自动计算它们:
- `Authorization`
- `ContentEncoding`
- `ContentLength`(此属性为只读。)
- `ContentType` (指定`Content-Type`标头的Internet媒体类型(MIME类型)。)
- `ContentCharset` (指定`Content-Type`标题的字符集部分。如果设置此属性,则必须首先设置`ContentType`属性。)
- `Date`
- `From`
- `IfModifiedSince`
- `Pragma`
- `ProxyAuthorization`
- `Referer`
- `UserAgent`
`%Net.HttpRequest`类提供可用于设置和获取主HTTP标头的常规方法。这些方法忽略`Content-Type`和其他实体标头。
### ReturnHeaders()
返回包含此请求中的主`HTTP`标头的字符串。
### OutputHeaders()
将主`HTTP`标头写入当前设备。
### GetHeader()
返回此请求中设置的任何主HTTP标头的当前值。此方法接受一个参数,即头的名称(不区分大小写);这是一个字符串,如Host或Date
### SetHeader()
设置标题的值。通常,可以使用它来设置非标准标头;大多数常用标头都是通过Date等属性设置的。此方法有两个参数:
- 标头的名称(不区分大小写),不带冒号(`:`)分隔符;这是一个字符串,如Host或Date
- 标头值
不能使用此方法设置实体标头或只读标头(`Content-Length`和`Connection`)。
# 管理保活(Keep-alive)行为
如果重复使用`%Net.HttpRequest`的同一实例来发送多个HTTP请求,则默认情况下,InterSystems IRIS会使TCP/IP套接字保持打开状态,这样InterSystems IRIS就不需要关闭并重新打开它。
如果不想重复使用TCP/IP套接字,请执行以下任一操作:
- 设置`SocketTimeout`属性为0。
- 在你的HTTP请求中添加`'Connection: close'` HTTP头。
要做到这一点,在发送请求之前添加如下代码:
```java
Set sc=http.SetHeader("Connection","close")
```
注意,每个请求之后都会清除HTTP请求头,因此需要在每个请求之前包含此代码。
`%Net.HttpRequest`的`SocketTimeout`属性指定InterSystems IRIS将重用给定套接字的时间窗口(以秒为单位)。此超时旨在避免使用可能已被防火墙静默关闭的套接字。此属性的默认值为115。可以将其设置为不同的值。
# 处理HTTP请求参数
发送HTTP请求时(请参阅“发送HTTP请求”),可以在位置参数中包括参数;例如:`"/test.html?PARAM=%25VALUE"`将`PARAM`设置为等于`%value`。
还可以使用以下方法控制`%Net.HttpRequest`实例处理参数的方式:
### InsertParam()
将参数插入到请求中。此方法接受两个字符串参数:参数的名称和参数的值。例如:
```java
do req.InsertParam("arg1","1")
```
可以为给定参数插入多个值。如果这样做,这些值将接收从1开始的下标。在其他方法中,可以使用这些下标来引用目标值。
### DeleteParam()
从请求中删除参数。第一个参数是参数的名称。第二个参数是要删除的值的下标;仅当请求包含同一参数的多个值时才使用此参数。
### CountParam()
统计与给定参数关联的值数。
### GetParam()
获取请求中给定参数的值。第一个参数是参数的名称。如果请求没有同名的参数,则第二个参数是要返回的默认值;该默认值的初始值为空值。第三个参数是要获取的值的下标;仅当请求包含同一参数的多个值时才使用此参数。
### IsParamDefined()
检查是否定义了给定参数。如果参数有值,则此方法返回`TRUE`。参数与`DeleteParam()`相同。
### NextParam()
通过`$order()`对参数名称进行排序后,检索下一个参数的名称(如果有)。
### ReturnParams()
返回此请求中的参数列表。
# 包括请求正文
HTTP请求可以包括请求正文或表单数据。要包括请求正文,请执行以下操作:
1. 创建`%GlobalBinaryStream`的实例或子类。将此实例用于HTTP请求的`EntityBody`属性。
2. 使用标准流接口将数据写入此流。例如:
```java
Do oref.EntityBody.Write("Data into stream")
```
例如,可以读取一个文件并将其用作自定义HTTP请求的实体正文:
```java
set file=##class(%File).%New("G:\customer\catalog.xml")
set status=file.Open("RS")
if $$$ISERR(status) do $System.Status.DisplayError(status)
set hr=##class(%Net.HttpRequest).%New()
do hr.EntityBody.CopyFrom(file)
do file.Close()
```
# 发送分块请求
如果使用的是HTTP1.1,则可以分块发送HTTP请求。这涉及到设置`Transfer-Encoding`以指示消息已分块,并使用大小为零的块来指示完成。
当服务器返回大量数据并且在完全处理请求之前不知道响应的总大小时,分块编码非常有用。在这种情况下,通常需要缓冲整个消息,直到可以计算出内容长度(`%Net.HttpRequest`会自动计算)。
要发送分块请求,请执行以下操作:
1. 创建`%Net.ChunkedWriter`的子类,`%Net.ChunkedWriter`是定义以块形式写入数据的接口的抽象流类。在这个子类中,实现`OutputStream()`方法。
2. 在`%Net.HttpRequest`的实例中,创建`%Net.ChunkedWriter`子类的实例,并用要发送的请求数据填充它。
3. 将`%Net.HttpRequest`实例的`EntityBody`属性设置为等于此`%Net.ChunkedWriter实`例。
当发送HTTP请求时(请参见“发送HTTP请求”),它将调用`EntityBody`属性的`OutputStream()`方法。
在`%Net.ChunkedWriter`的子类中,`OutputStream()`方法应该检查流数据,决定是否分块以及如何分块,并调用类的继承方法来编写输出。
有以下方法可用:
### WriteSingleChunk()
接受字符串参数并将该字符串作为非分块输出写入。
### WriteFirstChunk()
接受字符串参数。写入适当的`Transfer-Encoding`标题以指示分块的消息,然后将字符串作为第一个分块写入。
### WriteChunk()
接受字符串参数并将字符串作为块写入。
### WriteLastChunk()
接受字符串参数,并将字符串作为块写入,后跟零长度块以标记结尾。
如果非NULL,则`TranslateTable`属性指定用于在写入时转换每个字符串的转换表。前面的所有方法都检查此属性。
# 发送表单数据
HTTP请求可以包括请求正文或表单数据。要包括表单数据,请使用以下方法:
### InsertFormData()
将表单数据插入到请求中。此方法接受两个字符串参数:表单项的名称和关联值。可以为给定表单项插入多个值。如果这样做,值将接收从1开始的下标。在其他方法中,可以使用这些下标来引用目标值
### DeleteFormData()
从请求中删除表单数据。第一个参数是表单项的名称。第二个参数是要删除的值的下标;仅当请求包含同一表单项的多个值时才使用此参数。
### CountFormData()
统计请求中与给定名称关联的值数。
### IsFormDataDefined()
检查是否定义了给定的名称
### NextFormData()
通过`$order()`对名称进行排序后,检索下一个表单项的名称(如果有)。
例1
插入表单数据后,通常调用`Post()`方法。例如:
```java
Do httprequest.InsertFormData("element","value")
Do httprequest.Post("/cgi-bin/script.CGI")
```
例2
```java
Set httprequest=##class(%Net.HttpRequest).%New()
set httprequest.SSLConfiguration="MySSLConfiguration"
set httprequest.Https=1
set httprequest.Server="myserver.com"
set httprequest.Port=443
Do httprequest.InsertFormData("portalid","2000000")
set tSc = httprequest.Post("/url-path/")
Quit httprequest.HttpResponse
```
# 插入、列出和删除Cookie
`%Net.HttpRequest`自动管理从服务器发送的`Cookie`;如果服务器发送`Cookie`,`%Net.HttpRequest`实例将在下一次请求时返回此`Cookie`。(要使此机制正常工作需要重用`%Net.HttpRequest`的同一实例。)
使用以下方法管理`%Net.HttpRequest`实例中的`Cookie`:
### InsertCookie()
将`Cookie`插入到请求中。指定以下参数:
- `Cookie`的名称。
- `Cookie`的值。
- 应存储`Cookie`的路径。
- 要从中下载`Cookie`的计算机的名称。
- `Cookie`过期的日期和时间。
### GetFullCookieList()
返回`Cookie`的数量,并(通过引用)返回`Cookie`数组。
### DeleteCookie()
请记住,`Cookie`是特定于HTTP服务器的。当插入`Cookie`时,使用的是到特定服务器的连接,而该`Cookie`在其他服务器上不可用。
文章
Qiao Peng · 一月 14, 2021

您好! 本文介绍另一种为基于 InterSystems Caché 的解决方案创建安装程序的简单方法。 主题将涵盖只需一项操作即可安装或从 Caché 中完全删除的应用程序。 如果您仍在编写需要执行多个步骤才能安装应用程序的安装说明,是时候将这个过程自动化了。
问题的提出
假设我们为 Caché 开发了一个小型实用程序,之后我们想要将其分发。 当然,最好不要让不必要的配置和安装细节打扰到安装它的用户。 此外,这些说明必须非常全面,而且要面向可能对 Caché 一无所知的用户。如果是 Web 实用程序,安装程序不仅会要求用户将其类导入 Caché,而且至少还要配置 Web 应用程序才能对其进行访问,这是相当大的工作量:



当然,所有这些操作都可以通过编程方式执行。 您只需要了解如何实现。 但即使是这样,我们也需要让用户执行操作,例如在终端中执行一个命令。
通过单次导入操作进行安装
Caché 允许我们在类导入期间执行安装。 这意味着用户只需要使用任一方便的方法导入包含类包的 XML 文件:
将 XML 文件拖放到 Studio 区域。
通过管理门户:系统资源管理器 -> 类 -> 导入。
通过终端:do $system.OBJ.Load("C:\FileToImport.xml","ck")。
我们为安装应用程序而预先准备的代码将在类导入和编译后立即执行。 如果用户要卸载我们的应用程序(删除软件包),我们还可以清理应用程序在安装过程中创建的所有内容。
创建投影
为了扩展 Caché 编译器的行为,或者,在我们的示例中,为了在类的编译或反编译期间执行代码,我们需要在软件包中创建一个投影类。 它是一个扩展了 %Projection.AbstractProjection 的类,并重载了它的两个方法:CreateProjection(在编译过程中执行)和 RemoveProjection(在重新编译或删除类时触发)。
通常,将这个类命名为 Installer 是个好方法。 我们来看一个名为“MyPackage”的软件包的简单安装程序示例:
Class MyPackage.Installer Extends %Projection.AbstractProjection [ CompileAfter = (Class1, Class2) ]
{Projection Reference As Installer;/// This method is invoked when a class is compiled.ClassMethod CreateProjection(cls As %String, ByRef params) As %Status{ write !, "Installing..."}/// This method is invoked when a class is 'uncompiled'.ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status{ write !, "Uninstalling..."}}
这里的行为可以描述为:
第一次导入和编译软件包时,只触发 CreateProjection 方法。
以后再编译 MyApp.Installer 时,或者导入“新”的安装程序类覆盖“旧”类时,将触发旧类的 RemoveProjection 方法,且 %recompile 参数等于 1,之后调用新类的 CreateProjection 方法。
删除软件包(同时删除 MyApp.Installer)时,将只调用 RemoveProjection 方法,参数 recompile = 0。
还需要注意以下几点:
类关键字 CompileAfter 应该包括应用程序的类名列表,在执行投影类的方法之前,需要对它们进行编译。 始终建议在此列表中填入应用程序中的所有类,因为如果安装过程中出错,我们不需要执行投影类的代码;
两个方法都接受 cls 参数 - 它是顶级类名,在我们的示例中为 MyApp.Installer。 这个理念来自于创建投影类的本义 - 通过从派生自 %Projection.AbstractProjection 的类再派生,可以单独为我们的应用程序的任何类制作“安装程序”。 只有在这种情况下才会体现出意义,但对于我们的任务来说是多余的;
CreateProjection 和 RemoveProjection 方法都接受第二个参数 params - 它是一个关联数组,以“参数名称”-“值”对的形式处理有关当前编译设置和当前类的参数值的信息。 通过执行 zwrite params 可以非常容易地探索该参数的内容;
RemoveProjection 方法接受 recompile 参数,只有删除类时,该参数才等于 0,重新编译时不等于 0。
类 %Projection.AbstractProjection 还有其他方法,我们可以重新定义这些方法,但我们的任务并不需要这样做。
一个示例
让我们更深入地了解为我们的实用程序创建 Web 应用程序的任务,并创建一个简单示例。 假设我们的实用程序是一个 REST 应用程序,在浏览器中打开时,它只发送一个响应“I am installed!”。 要创建这样的应用程序,我们需要创建一个描述它的类:
Class MyPackage.REST Extends %CSP.REST{XData UrlMap{ }ClassMethod Index() As %Status{ write "I am installed!" return $$$OK}}
创建并编译该类后,我们需要将其注册为 Web 应用程序入口点。 我在本文的顶部图示了如何进行配置。 在执行所有这些步骤后,最好通过访问 http://localhost:57772/myWebApp/ 来检查我们的应用程序是否正常工作(注意以下几点:1. 末尾的斜线是必需的;2. 端口 57772 在您的系统中可能有所不同。 它将匹配您的管理门户端口)。
当然,所有这些步骤都可以通过 Web 应用程序创建方法 CreateProjection 以及删除方法 RemoveProjection 中的一些代码来自动执行。 在这种情况下,我们的投影类如下所示:
Class MyPackage.Installer Extends %Projection.AbstractProjection [ CompileAfter = MyPackage.REST ]{Projection Reference As Installer;Parameter WebAppName As %String = "/myWebApp";Parameter DispatchClass As %String = "MyPackage.REST";ClassMethod CreateProjection(cls As %String, ByRef params) As %Status{ set currentNamespace = $Namespace write !, "Changing namespace to %SYS..." znspace "%SYS" // we need to change the namespace to %SYS, as Security.Applications class exists only there write !, "Configuring WEB application..." set cspProperties("AutheEnabled") = $$$AutheUnauthenticated // public application set cspProperties("NameSpace") = currentNamespace // web-application for the namespace we import classes to set cspProperties("Description") = "A test WEB application." // web-application description set cspProperties("IsNameSpaceDefault") = $$$NO // this application is not the default application for the namespace set cspProperties("DispatchClass") = ..#DispatchClass // the class we created before that handles the requests return ##class(Security.Applications).Create(..#WebAppName, .cspProperties)}ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status{ write !, "Changing namespace to %SYS..." znspace "%SYS" write !, "Deleting WEB application..." return ##class(Security.Applications).Delete(..#WebAppName)}}
在此示例中,每次编译 MyPackage.Installer 类都将创建一个 Web 应用程序,每次“反编译”都将其删除。 最好在创建或删除应用程序之前检查该应用程序是否存在(例如,使用 ##class(Security.Applications).Exists(“Name”) ),但是为了使本示例简单起见,这个作业就留给阅读本文的读者来完成了。
在创建 MyPackage.REST 和 MyPackage.Installer 类之后,我们可以将这些类导出为一个 XML 文件,并将该文件分享给所有有需要的人。 导入此 XML 的用户将自动设置 Web 应用程序,然后可以开始在浏览器中使用。
结果
与 InterSystems 社区上介绍的使用 %Installer 类部署应用程序的方法不同,这种方法有以下优点:
使用“纯”Caché ObjectScript。 至于 %Installer,需要用特定标签来填充 xData 块,大量文档对此进行了介绍。
安装我们的应用程序的方法是在类编译后立即执行的,我们不需要手动执行;
如果类(包)被删除,将自动执行删除我们的应用程序的方法,这不能通过使用 %Installer 来实现。
我的项目中已经使用这种应用程序安装方法 - Caché WEB Terminal、Caché Class Explorer 和 Caché Visual Editor。 您可以在此处找到 Installer 类的示例。
顺便提一下,开发者社区还有一个帖子介绍了投影的功能,作者是 John Murray。
另外值得一提的是 Package Manager 项目,该项目旨在让 InterSystems 数据平台的第三方应用程序只需通过一个命令或一次点击即可安装,就像类似 npm 的包管理器一样。
文章
姚 鑫 · 六月 26, 2021
# 第十九章 使用%XML.TextReader
`%XML.TextReader`类提供了一种简单、容易的方法来读取可能直接映射到InterSystems IRIS对象,也可能不直接映射到InterSystems IRIS对象的任意XML文档。具体地说,该类提供了导航格式良好的XML文档并查看其中信息(元素、属性、注释、名称空间URI等)的方法。该类还基于`DTD`或`XML`架构提供完整的文档验证。但是,与`%XML.Reader`不同的是,`%XML.TextReader`不提供返回`DOM`的方法。如果需要DOM,请参阅前面的“将XML导入对象”一章。
**注意:使用的任何XML文档的XML声明都应该指明该文档的字符编码,并且文档应该按照声明的方式进行编码。如果未声明字符编码,InterSystems IRIS将使用前面的“输入和输出的字符编码”中描述的默认值。如果这些默认值不正确,请修改XML声明,使其指定实际使用的字符集。**
# 创建文本阅读器`Text Reader`方法
要读取不一定与 IRIS对象类有任何关系的任意XML文档,可以调用`%XML.TextReader`类的方法,该类将打开文档并将其作为文本阅读器对象加载到临时存储中。文本阅读器对象包含一个可导航的节点树,每个节点都包含有关源文档的信息。然后,方法可以导航该文档并查找有关该文档的信息。对象的属性提供有关文档的信息,这些信息取决于在文档中的当前位置。如果存在验证错误,这些错误也可以作为树中的节点使用。
## 整体结构
法应执行以下部分或全部操作:
1. 通过以下方法之一的第一个参数指定文档源:
`Method`| `First Argument`
---|---
`ParseFile()` |文件名,带有完整路径。请注意,文件名和路径只能包含ASCII字符。
`ParseStream()`|流
`ParseString()`| 字符串
`ParseURL()`| URL
**在任何情况下,源文档都必须是格式良好的XML文档;也就是说,它必须遵守XML语法的基本规则。这些方法中的每一个都返回一个状态(`$OK`或失败代码),以指示结果是否成功。可以使用常用机制测试状态;特别是可以使用`$System.Status.DisplayError(status)`查看错误消息的文本。**
对于这些方法中的每一个,如果该方法返回$OK,则它通过引用(其第二个参数)返回包含XML文档中的信息的文本阅读器对象。
其他参数允许控制实体解析、验证、找到哪些项等。这些内容将在本章后面的“解析方法的参数列表”中介绍。
2. 检查解析方法返回的状态,并在适当的情况下退出。
如果解析方法返回$OK,则有一个与源XML文档相对应的文本阅读器对象。可以导航此对象。
文档可能包含`“element”`、`“endelement”`、`“startprefixmapping”`等节点。
重要提示:在任何验证错误的情况下,文档包含“错误”或“警告”节点。
代码应该检查这些节点。
3. 使用以下实例方法之一开始读取文档。
- 使用`Read()`导航到文档的第一个节点。
- 使用`ReadStartElement()`导航到特定类型的第一个元素。
- 使用`MoveToContent()`导航到类型为`“chars”`的第一个节点。
4. 获取该节点感兴趣的属性的值(如果有的话)。可用的属性包括名称、值、深度等。
5. 根据需要继续在文档中导航并获取属性值。
如果当前节点是元素,则可以使用`MoveToAttributeIndex()`或`MoveToAttributeName()`方法将焦点移至该元素的属性。若要返回到元素(如果适用),请使用`MoveToElement()`。
6. 如果需要,可以使用`Rewind()`方法返回到文档的开头(第一个节点之前)。这是唯一可以在源代码中倒退的方法。
方法运行后,文本读取器对象将被销毁,所有相关的临时存储都将被清除。
## 示例1
下面是一个简单的方法,它可以读取任何XML文件,并显示每个节点的序列号、类型、名称和值:
```java
/// w ##class(PHA.TEST.Xml).WriteNodes("E:\temp\textReader.txt")
ClassMethod WriteNodes(myfile As %String)
{
set status = ##class(%XML.TextReader).ParseFile(myfile,.textreader)
//检查状态
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
//逐个节点遍历文档
while textreader.Read()
{
Write !, "Node ", textreader.seq, " is a(n) "
Write textreader.NodeType," "
If textreader.Name'="" {
Write "named: ", textreader.Name
} Else {
Write "and has no name"
}
Write !, " path: ",textreader.Path
If textreader.Value'="" {
Write !, " value: ", textreader.Value
}
}
q ""
}
```
此示例执行以下操作:
1. 它调用`ParseFile()`类方法。这将读取源文件,创建一个文本阅读器对象,并通过引用在变量doc中返回该对象。
2. 如果`ParseFile()`成功,则该方法然后调用`read()`方法来查找文档中的每个后续节点。
3. 对于每个节点,该方法写入包含节点序列号、节点类型、节点名称(如果有)、节点路径和节点值(如果有)的输出行。输出将写入当前设备。
以下示例源文档:
```xml
yaoxin
1990-04-25
```
对于此源文档,前面的方法生成以下输出:
```java
DHC-APP>w ##class(PHA.TEST.Xml).WriteNodes("E:\temp\textReader.txt")
Node 1 is a(n) processinginstruction named: xml-stylesheet
path:
value: type="text/css" href="mystyles.css"
Node 2 is a(n) element named: Root
path: /Root
Node 3 is a(n) startprefixmapping named: s01
path: /Root
value: s01 http://www.root.org
Node 4 is a(n) element named: s01:Person
path: /Root/s01:Person
Node 5 is a(n) element named: Name
path: /Root/s01:Person/Name
Node 6 is a(n) chars and has no name
path: /Root/s01:Person/Name
value: yaoxin
Node 7 is a(n) endelement named: Name
path: /Root/s01:Person/Name
Node 8 is a(n) element named: DOB
path: /Root/s01:Person/DOB
Node 9 is a(n) chars and has no name
path: /Root/s01:Person/DOB
value: 1990-04-25
Node 10 is a(n) endelement named: DOB
path: /Root/s01:Person/DOB
Node 11 is a(n) endelement named: s01:Person
path: /Root/s01:Person
Node 12 is a(n) endprefixmapping named: s01
path: /Root
value: s01
Node 13 is a(n) endelement named: Root
path: /Root
```
请注意,注释已被忽略;默认情况下,`%XML.TextReader`忽略注释。
## Example 2
下面的示例读取一个XML文件并列出其中的每个元素
```java
/// w ##class(PHA.TEST.Xml).ShowElements("E:\temp\textReader.txt")
ClassMethod ShowElements(myfile As %String)
{
set status = ##class(%XML.TextReader).ParseFile(myfile,.textreader)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
while textreader.Read()
{
if (textreader.NodeType = "element")
{
write textreader.Name,!
}
}
q ""
}
```
此方法使用`NodeType`属性检查每个节点的类型。如果节点是元素,则该方法将其名称打印到当前设备。对于前面显示的XML源文档,此方法生成以下输出:
```java
DHC-APP>w ##class(PHA.TEST.Xml).ShowElements("E:\temp\textReader.txt")
Root
s01:Person
Name
DOB
```
# 节点类型
文档的每个节点都是以下类型之一:
文本阅读器文档中的节点类型
Type| Description
---|---
`"attribute"` |XML属性。
`"chars"` |一组字符(如元素的内容)。`%XML.TextReader`类识别其他节点类型(`“CDATA”`、`“EntityReference”`和`“EndEntity”`),但自动将它们转换为“字符”。
`"comment"`| XML注释。
`"element"`|XML元素的开始。
`"endelement"`|XML元素的结束。
`"endprefixmapping"`| 声明名称空间的上下文的结束。
`"entity"`|XML实体。
`"error"`| 解析器发现的验证错误。
`"ignorablewhitespace"`| 混合内容模型中标记之间的空白。
`"processinginstruction"`|XML处理指令。
`"startprefixmapping"`|XML命名空间声明,它可能包括也可能不包括命名空间。
`"warning"`| 解析器发现验证警告。
请注意,XML元素由多个节点组成。例如,以下XML片段:
```xml
Willeke,Clint B.
1925-10-01
```
SAX解析器将此XML视为以下节点集:
文档节点示例
Node Number |Type of Node| Name of Node, If Any| Value of Node, If Any
---|---|---|---
1 |element |Person
2| element |Name
3| chars || Willeke,Clint B.
4 |endelement| Name
5| element| DOB
6| chars || 1925-10-01
7| endelement| DOB
8| endelement| Person
例如,注意``元素被认为是三个节点:一个元素节点、一个字符节点和一个结束元素节点。还要注意,该元素的内容只能作为`chars`节点的值使用。
文章
姚 鑫 · 九月 11, 2022
# 第二十九章 管理许可(二)
# 激活许可证密钥
`IRIS` 使用许可证密钥来确保其注册站点的正常运行、定义可用容量并控制对 `IRIS` 功能的访问。 许可证密钥以许可证密钥文件的形式提供,通常命名为 `iris.key`。
安装 `IRIS` 后,使用以下程序激活许可证密钥。始终可以使用相同的过程为任何已安装的实例激活新的许可证密钥(即升级密钥)。可以激活放置在管理门户可访问的任何位置的许可证密钥;作为激活的一部分,许可证密钥将作为 `iris.key` 复制到实例的 `install-dir/mgr` 目录(如果尚未命名)。
注意:也可以在 `Windows` 安装期间选择许可证密钥。执行此操作时,许可证会自动激活,并且许可证密钥会作为 `iris.key` 复制到实例的 `install-dir/mgr` 目录中;不需要此处描述的激活过程。
本节还讨论了许可证故障排除和在所有许可证单元都在使用时从操作系统命令行升级许可证。
要激活许可证密钥,请使用以下过程:
1. 导航到许可证密钥页面(系统管理 > 许可 > 许可证密钥)。将显示有关当前活动许可证密钥的信息。如果尚未激活任何许可证,则会显示这一点,例如通过标记客户名称:缺少许可证或不可读。此页面包含一个打印按钮,可让轻松打印显示的信息。
2. 单击激活许可证密钥并浏览到要激活的许可证密钥文件。当选择一个文件时,会显示有关它的信息,以便激活它之前验证是否拥有正确的许可证密钥;例如,它提供了所需的容量,并具有正确的到期日期。如果密钥无效,则会在错误消息中指出。如果许可证当前处于活动状态,则并排显示有关当前和选定许可证的信息。如果需要在激活后重新启动实例以使许可证密钥生效,则会记录这一点并提供原因。此对话框包括一个打印按钮,可让轻松打印有关当前活动许可证和选择的新许可证密钥的信息。
3. 单击激活以激活新的许可证密钥;它作为 `iris.key` 复制到实例的 `install-dir/mgr` 目录,覆盖之前的许可证密钥(如果有)。如果需要,确认对话框会提醒重新启动实例,并在新许可证启用的功能少于当前许可证时向发出警告。
通过使用 `Config.Startup`的 `LicenseID` 属性,可以将实例配置为从许可证服务器请求许可证密钥。在实例启动时,如果不存在 `iris.key` 文件并且已定义 `LicenseID`,则实例会从许可证服务器请求并激活许可证密钥。
**注意:相同的 `LicenseID` 必须在许可证密钥文件中,以及在需要下载许可证的实例上定义**
一般情况下无需重启实例,但升级许可证密钥时存在限制。如果将许可证类型从 `Power Unit` 更改为任何其他类型,则不会自动激活新密钥;这应该是一个罕见的事件。
另一个限制是许可证升级从通用内存堆 (`gmheap`) 空间中消耗的内存量。如果 `gmheap` 空间不可用,则无法扩展许可表条目的数量。如果没有足够的 `gmheap` 空间可用于许可证升级,则会将一条消息写入消息日志。可以从“高级内存设置”页面(系统管理 > 配置 > 高级内存设置)增加 `gmheap` 设置的大小。
如果新的许可证密钥比现有密钥消耗至少 `1000` 个 `64 KB` 页的 `gmheap` 空间,则必须重新启动 `IRIS` 实例才能完全激活新的许可证密钥。这种情况很少遇到,因为每个页面至少代表 `227` 个许可证。
## 更新许可证密钥
要更新许可证密钥,请替换 `KeyDirectory` 中的密钥文件并运行 `ReloadKeys^%SYS.LICENSE`。每个实例上的许可证监视器 (`^LMFMON`) 每 30 分钟检查一次,以查看配置的 `LicenseID` 是否有不同的密钥,如果有,则尝试执行升级。
**注意:虽然大多数升级在实时实例上成功,但某些情况可能需要重新启动实例。在这种情况下,许可证监视器会记录一个错误,并且直到第二天才尝试再次升级密钥(以避免记录重复的错误)。实例重启会在启动时加载新密钥。**
## 许可证故障排除
如果在输入许可证并重新启动 `IRIS` 后只有一位用户可以登录,请使用管理门户进行调查。当选择按进程时,许可证使用页面(系统操作 > 许可证使用)显示正在运行的进程数。还可以使用门户从许可证密钥页面(系统管理 > 许可 > 许可证密钥)显示许可证信息,如激活许可证密钥中所述。如果密钥无效,则 `CustomerName` 字段包含说明。
还可以在消息日志和系统监控日志中查看许可证错误消息,可以在 `Portal` 的消息日志页面(系统操作 > 系统日志 > 消息日志)和系统监控日志页面(系统操作 > 系统日志)中查看> 系统监控日志),分别。 `System Monitor` 将许可证到期警告和警报写入这些日志,而 `Health Monitor` 则写入许可证获取警报和警告。当超过许可限制时,许可模块会将警报写入消息日志。在 `Application Monitor` 中,可以配置基于许可证指标的警报以发送电子邮件通知或呼叫通知方法。
`$System.License.Help` 显示可用于解决许可证问题的方法列表:
```java
Do $System.License.Help()
```
### Administrator Terminal Session
有几个问题会阻止获得终端会话。当 `IRIS` 无法正常启动并进入单用户模式时,或者只是在没有可用许可证时,可能会发生这种情况。在这些情况下,可能需要创建管理员终端会话,该会话使用特殊许可证来解决问题。
### Administrator Session on Windows
使用命令提示符导航到 `install-dir\bin`。然后,以管理员身份执行以下命令:
```java
irisdb -s\mgr -B
```
这将从 `IRIS` 安装 `bin` 目录 (`install-dir\bin`) 运行 `IRIS` 可执行文件,指示 `install-dir\mgr` 的路径名(使用 `-s` 参数),并禁止所有登录,除了一个紧急登录(使用 `- B` 参数)。
例如,在默认目录中有一个名为 `MyIRIS` 的实例,该命令如下所示:
```java
c:\InterSystems\MyIRIS\bin>irisdb -sc:\InterSystems\MyIRIS\mgr -B
```
### Administrator Session on UNIX®, Linux, and macOS
使用命令提示符导航到 `install-dir/bin` 目录。然后,执行以下命令:
```java
iris terminal -B
```
例如,在默认目录中安装了一个名为 `MyIRIS` 的实例,该命令如下所示:
```java
User:/InterSystems/MyIRIS/bin$ iris terminal MyIRIS -B
```
## 从操作系统命令行升级许可证
`%SYSTEM.License.Upgrade()` 方法激活已复制到 `installdir\mgr` 目录的新许可证密钥。如果所有许可证单元都被用户使用,导致无法打开终端窗口,可以从命令行运行此方法以激活更大容量的新许可证密钥,如下所示:
```java
iris terminal -U %SYS '##Class(%SYSTEM.License).Upgrade()'
```
文章
Jingwei Wang · 八月 30, 2023
案例描述
假设您是一名 Python 开发人员或拥有一支训练有素的 Python 专业团队,但您分析 IRIS 中某些数据的期限很紧迫。当然,InterSystems 提供了许多用于各种分析和处理的工具。然而,在给定的场景中,最好使用旧的 Pandas 来完成工作,然后将 IRIS 留到下次使用。对于上述情况和许多其他情况,您可能需要从 IRIS 获取表来管理 InterSystems 产品之外的数据。但是,当您有任何格式(即 CSV、TXT 或 Pickle)的外部表时,您可能还需要以相反的方式执行操作,您需要在其上导入并使用 IRIS 工具。无论您是否必须处理上述问题,Innovatium让我明白,了解更多解决编码问题的方法总是能派上用场。好消息是,从 IRIS 引入表时,您不需要经历创建新表、传输所有行以及调整每种类型的繁琐过程。本文将向您展示如何通过几行代码快速将 IRIS 表转换为 Pandas 数据框架并向后转换。您可以在我的GitHub上查看代码,您可以在其中找到包含本教程每个步骤的 Jupiter Notebook。
从 IRIS 引入一张Table
当然,您应该首先导入该项目所需的库。
import pandas as pd import sqlalchemy as db
下一步将是在 Python 文件和 IRIS 实例之间创建连接。为此,我们将使用 SQLAlchemy 的函数 create_engine(),并以字符串作为参数。该字符串应包含有关操作方法、用户名和密码、实例的主机和端口以及目标命名空间的信息。有关使用 sqlalchemy-iris 的基本概念的更多信息,请查看我之前的一篇文章SQLAlchemy - 在 IRIS 数据库中使用 Python 和 SQL 的最简单方法。
engine = db.create_engine( "iris://_system:SYS@localhost:1972/SAMPLE" ) connection = engine.connect()
然后,我们可以声明将保存数据帧的变量,并在此连接上调用 Pandas 的 read_sql_table() 函数,将表名指定为带有Schema的字符串。您还可以在另一个参数中声明Schema,事实上,这是更好的选择,因为名称字符串上有一个点在某些情况下可能会导致错误。
df = pd.read_sql_table( "NameAge" , connection, schema= "PD" )
最好仔细检查我们正在使用的表是否存在于我们想要使用的Schema中,当然,首先还要检查是否存在我们需要的Schema。在本文的最后一部分中,您将了解如何执行此操作以及更多提示。从现在开始,如果您有办法使用 Pandas,您可以执行任何您想要的更改和分析,因为您知道该怎么做。探索以下示例以了解其工作原理。
向 IRIS 发送表
在开始之前,让我们更改数据框中的某些内容作为示例。我们可以调整列的值以满足我们的需求(例如,添加行和列等)。经过一番尝试后,我将名称改为小写,并根据现有数据添加了一个新人和一列。您可以查看下图来查看结果。
现在我们可以用一行代码将其发送回 IRIS。我们需要的只是指定引擎和表名。
df.to_sql( "NameAge" , con=engine, schema= "PD" , if_exists= "replace" )
再次,我们需要将Schema与表名分开放在参数中,以避免一些错误和不良行为。除此之外,if_exists 参数指定如果给定Schema中已经存在同名表时要执行的操作。可能的值为:replace、fail(默认)和append。当然,replace 选项会删除表并使用 SQL 命令创建一个新表,而append 会将数据添加到现有表中。请记住,此方法不会检查重复值,因此使用此属性时要小心。最后,失败值会引发以下错误:
请记住,如果您指定的表名不存在,该函数将创建它。
现在,您可以查询 IRIS 以查看新增内容,或者转至管理门户查看专用于 SQL 的部分。请记住,如果您使用替换值,您应该考虑该类的源代码,因为该方法完全重写了它。这意味着如果您实现了任何方法,则应该将它们保留在超类中。
有关 sqlalchemy-iris 的更多提示
如果您有任何问题无法通过其他社区或论坛中共享的与您的应用程序代码相关的信息来解决,您可能会在本节中找到所需的帮助。在这里您将找到有关如何查找有关engine和dialect的详细信息的提示列表。
方言特有的特征
SQL Alchemy 使用根据您的engine自动选择的dialect。当您使用函数 create_engine() 连接到 IRIS 数据库时,选择的dialect是Dmitry Maslennikov 的 sqlalchemy-iris 。您可以使用engine的dialect属性访问和编辑其功能。
engine = db.create_engine( "iris://_system:SYS@localhost:1972/SAMPLE" )
engine.dialect
通过 VSCode 的 IntelliCode 扩展,您可以从此属性中搜索每个选项,或者在CaretDev 的 GitHub上检查源代码。
检查engine中的可用Schema
该dialect中值得强调的一个特殊函数是 get_schema_names() 函数。注意!如果您想避免代码和迭代中出现错误,以下信息可能对您至关重要。
connection = engine.connect()
engine.dialect.get_schema_names(connection)
检查Schema中的可用表
我们来看看类似的情况。您可能还需要了解Schema中的可用表。在这种情况下,您可以使用检查。在引擎上运行函数inspect()并将其保存在变量中。您将使用相同的变量来访问另一个函数 get_table_names()。它将返回一个列表,其中包含指定Schema中的表名称或默认的“SQLUser”。
inspection = db.inspect(engine)
inspection.get_table_names(schema= "Sample" )
此外,如果您想在数据上使用更多 SQL Alchemy 功能,您可以声明一个基础并使其元数据反映引擎中的Schema。
b = db.orm.declarative_base()
b.metadata.reflect(engine, schema= "Sample" )
如果您需要更多信息来解决此问题,请查看SQL Alchemy 文档和sqlalchemy-iris GitHub 存储库。或者,您也可以给我留言或发表评论,我们将一起尝试揭开这个秘密。
最后的考虑因素
本文中的实现方法强调使用 IRIS 实例作为云提供商,并使得可以在不同的基础上进行分析。它可以轻松地同时监控所有这些设备的任何质量问题并比较它们的性能和使用情况。如果您将这些知识与另一篇关于用 Django 制作的门户的文章中描述的开发结合起来,您可以根据需要快速构建一个强大的管理器,用于任意数量的特性和实例。此实现也是将数据从 IRIS 外部移动到构建良好的类的有效方法。由于您可能熟悉 Pandas 中用于处理多种不同语言的其他一些函数,即 CSV、JSON、HTML、Excel 和 Pickle,因此您可以轻松地将 read_sql_table 更改为 read_csv、read_json 或任何其他选项。是的,我应该警告您,某些类型与 InterSystems 的集成不是内置功能,因此可能不是很容易。然而,SQL Alchemy 和 Pandas 的结合在从 IRIS 导出数据时总是会派上用场。因此,在本文中,我们了解到 IRIS 拥有您所需的所有工具,可帮助您进行开发并轻松与系统的现有设备或您的专业知识小工具集成。
文章
Jingwei Wang · 三月 28, 2023
IRIS 配置和用户帐户包含需要跟踪的各种数据元素,许多人难以在 IRIS 实例之间复制或同步这些系统配置和用户帐户。那么如何简化这个过程呢?
在软件工程中,CI/CD 或 CICD 是持续集成 (CI) 和(更常见的)持续交付或(较少见的)持续部署 (CD) 的组合实践集。 CI/CD 能消除我们所有的挣扎吗?
我在一个开发和部署 IRIS 集群的团队工作。我们在 Red Hat OpenShift 容器平台上的容器中运行 IRIS。
如果您当前没有使用 Kubernetes,请不要停止阅读。即使您没有使用 Kubernetes 或在容器中运行 IRIS,您也可能会遇到与我和我的团队面临的挑战类似的挑战。
我们决定将代码与配置分开,并将它们放在不同的 GitHub 存储库中。每次在代码库中进行提交时,都会触发管道运行。结果,从代码库中的文件构建了一个新image。
我们通过将 YAML 文件和其他配置工件添加到部署 GitHub 存储库,将配置定义为以 GitOps 方式使用的代码。 GitOps 是一个软件开发框架,它使组织能够持续交付软件应用程序,同时使用 Git 作为单一事实来源有效地管理 IT 基础设施(以及更多)。 GitOps 的好处之一是能够轻松回滚。您所需要做的就是恢复到 Git 中的先前状态。
DevOps 是软件开发和 IT 行业的一种方法论。作为一套实践和工具使用,DevOps 将软件开发(Dev) 和IT 运营(Ops) 的工作集成并自动化,作为改进和缩短系统开发生命周期的一种手段。 [1]
我在维基百科上读到持续交付是“当团队在短周期内以高速和频率生产软件时,以便可以随时发布可靠的软件,并在决定部署时采用简单且可重复的部署过程。”
同时,维基百科将持续部署定义为“当新软件功能完全自动推出时”。
我们已决定将 YAML 文件存储在部署 GitHub 存储库中。
iris-cpf(上面的第 19 行)指的是一个 ConfigMap,其中包含用于 CPF Merge 的文件。
有多种可用的 CD 管道工具可以将配置作为代码推送部署,而不必手动应用文件。
例如,我的团队使用Argo CD 。它是一个 GitOps 工具,作为 Kubernetes 扩展部署在集群中。它很特别,因为它在集群中具有可见性。它的用户界面在浏览器中显示应用程序状态,因为 Argo CD 是 Kubernetes 扩展。
与仅启用基于推送的部署的外部 CD 工具不同,Argo CD 可以从 Git 存储库中拉取更新的(类似)代码并将其直接部署到 Kubernetes 资源。
像 Argo CD 这样的拉动部署工具将我们的 Kubernetes 集群的实际状态与我们的部署 repo 中描述的期望状态进行比较。
Argo CD 监视我们的部署 repo 和我们的 Kubernetes 集群。我们的部署repo 是唯一的真实来源。如果 GitHub 存储库中发生某些更改,Argo CD 将更新集群以匹配存储库中定义的所需状态。
Argo CD 代理同步 GitHub 存储库和 Kubernetes 集群。如果我们手动应用一个更改,当 Argo CD 将已部署的应用程序同步到 Git 中定义的所需状态时,它将被删除。
为了将不同的配置部署到不同的集群,我们使用Kustomize 。我们在部署仓库中定义了一个基本配置。我们还在部署 GitHub 存储库中定义了覆盖,以便为开发、SQA、阶段和生产等各种环境配置不同的系统默认设置和不同的images。
在第 417 行中,我们确定基于环境的系统默认设置存储在 SDS_ENV.xml 中。
那些没有使用 Kubernetes 的情况呢?我创建了许多可在 Open Exchange 中使用的应用程序。我学习了如何使用 Installer 类在 GitHub 存储库中定义 IRIS 配置,然后构建image并运行容器。
然而,如果在部署应用程序后需要对配置或用户帐户进行一些修改,情况会怎样呢?当涉及到持久卷时,事情就变得复杂了。发生这种情况是因为我们不想丢失存在于持久卷上的数据。
存储在持久卷上的所有这些东西是什么? IRIS 配置保存在数据目录的 mgr 目录中。 CPF Merge 功能应该使我们能够修改 iris.cpf 中的任何配置设置。
我的团队已将大量代码添加到 %ZSTART routines中,这些routines在 IRIS 计算或数据实例启动时执行。我们担心的一个问题是所谓的零大小 CPF 错误。我们经常遇到 IRIS 实例因大小为零的 CPF 文件而崩溃的情况。不幸的是,我们尚未发现该问题的根本原因。我们怀疑 CPF 合并操作以及在 %ZSTART routine中添加和删除的大量Routine、Global和包映射会导致零大小的 CPF 错误。
我们编写了代码来删除所有系统默认设置,并从作为卷安装在 IRIS 容器中的 Kubernetes ConfigMap 中导入它们。事实上,我们有两组系统默认设置:一组是在所有环境中导入的基本设置,另一组是因环境而异的环境特定设置。
我们决定从 XML 文件中导入用户。有时,当我们直接从 %ZSTART routine 执行这段代码时,我们会遇到问题。我们根据 InterSystems 的建议将此代码移至计划任务中。显然,我们发现了一个在某些情况下可能会破坏全局安全性的错误。无论如何,出于某种我现在不记得的原因,当用户帐户导入由计划从 %ZSTART routine 按需运行的任务执行时,此问题不再是问题。这一定是时间问题。计划任务晚于 %ZSTART routine运行。
我们创建了一个自定义密码验证routine来强制执行密码规则。
当我们需要一个新的 Web 应用程序时,我们应该怎么做?除了 CPF Merge,CSP Merge 怎么样?
我相信 Web 应用程序存储在 %SYS IRIS.dat 文件中。我考虑尝试在可以使用 ConfigMap 挂载的文件中定义 Web 应用程序。我们可以将代码添加到 %ZSTART 例程或添加另一个计划任务来查找文件并创建 IRIS 中尚不存在的任何 Web 应用程序。
使用 InterSystems Kubernetes Operator 部署的 Webgateway 容器具有一个持久数据卷,其中包含 CSP.conf 和 CSP.ini。但是,我们还没有实现在添加新的 Web 应用程序时根据需要自动更新这些文件的方法。
Lorenzo Scalese 创建了可在 Open Exchange 中使用的 config-api 和 config-copy 应用程序。他建议在您的应用程序安装程序模块中使用 IRS-Config-API 库。
IRIS-Config-API 可以在一个环境将 IRIS 配置导出到 JSON 文档,并在另一个环境中从 JSON 文档导入 IRIS 配置。
Lorenzo 创建了 iris-config-copy 工具,用于从一个 InterSystems IRIS 实例导出配置并将其导入另一个实例。如果我们在源实例和目标实例上都安装 iris-config-copy,则目标实例使用 REST 从源实例获取配置。
我们需要在源实例上创建一个 Web 应用程序,以使目标实例能够从源实例中检索 IRIS 配置。
Iris-config-copy 可以导出本地实例或远程实例的 IRIS 配置。
有一些方法可以导入特定的配置文件。我们可以导入Security、包含 SQL 连接的globals、CPF 配置数据或任务。
问题
hongyuan du · 三月 15, 2022
无法下载老版本的cache,请问哪位可以分享下安装包 只有现有Cache客户可以下载,如果是我们客户欢迎访问wrc.intersystems.com 下载,谢谢!
文章
Nicky Zhu · 一月 18, 2021
在最近的项目里,多方同时连接同一个数据库并执行增删改查等各项数据操作。研发人员不时发现一些数据在不合规的情况下被新增甚至删除。因此,在实际工作中会有监控数据操作以便识别和处理异常操作的需求。本文将以监控和识别删除操作为例,介绍如何通过IRIS的审计功能实现对数据操作的监控和查询。
# 注意事项
在应用审计功能之前,必须注意的是:
1. 开启审计功能会事无巨细地记录每一条对应的操作(如被执行的SQL),因此对于存储空间的需求将急剧增加。举例而言,仅开启对XDBCStatement的监控后,对于一张只由5个简单(整型,VARCHAR型)字段构成的表中插入100万条记录,在Audit数据库中将占用300~400MB的空间。在因业务所需确实需要开启审计功能时,必须预先分配更多磁盘空间给IRIS Audit数据库,并在审计功能开启期间定时巡检磁盘空间,避免因日志占满磁盘导致其他数据无法写入引发系统挂起的故障。
2. 在研发环境中多人、多单位需要连接数据库时,应为不同的开发者和数据来源分配独立的数据库账户和权限,避免多人共用超级账户,导致数据异常时难以追踪异常操作究竟从何而来。也就意味着为不同角色的开发、测试、用户等参与者开启独立的用户,分配各自所需的的数据库权限以及管理数据库账户这样一系列项目正常运行所依赖的实践并不能被审计功能所替代。在项目进展过程中,您更希望见到的,一定不是出现问题后再来跟踪问题和耽误工期,而是通过良好的协作规程和研发习惯减少非技术问题出现的概率。
# IRIS中的审计功能
IRIS提供了丰富的数据库审计功能,用于记录从系统权限变更到数据删除的各类操作,用户可通过我们的[**官方文档**](https://docs.intersystems.com/irisforhealthlatest/csp/docbook/Doc.View.cls?KEY=GCAS_audit)查看系统支持的各类审计事件。
对于SQL操作而言,可用到的审计事件包括三大类:
- %System/%SQL/DynamicStatement 可捕获通过动态SQL执行的SQL操作
- %System/%SQL/EmbeddedStatement 可捕获通过嵌入式SQL执行的SQL操作
- %System/%SQL/XDBCStatement 可捕获通过第三方(JDBC/ODBC)SQL连接执行的SQL操作
# 开启数据库审计
1. 由于默认安装时IRISAUDIT数据库通常位于IRIS安装目录,空间有限,因此在需要开启审计时,应先将IRISAUDIT数据库从系统安装目录迁移到空间更充裕的数据库目录
2. 在System Administration -> Security -> Auditing -> Configure System Events下,开启对%System/%SQL/XDBCStatement的监控,如下图

3. 预先配置好删除AuditLog的任务,以便在AuditLog过大时清除数据
系统虽然自带定时删除Audit数据库的任务,但其默认的触发条件为在Journal发生切换时才执行,因此不适用于需要扩展监控范围导致审计库将剧增的情况,应配置额外的任务便于随时执行。

并将其设置为按需启动

4. 待可疑操作发生时,使用SQL工具或portal在%SYS命名空间下运行如下SQL:
SELECT
ID, AuditIndex, Authentication, CSPSessionID, ClientExecutableName, ClientIPAddress, Description, Event, EventData, EventSource, EventType, GroupName, JobId, JobNumber, Namespace, OSUsername, Pid, Roles, RoutineSpec, Status, SystemID, UTCTimeStamp, UserInfo, Username
FROM %SYS.Audit
where Description = 'SQL DELETE Statement'
order by UTCTimeStamp desc
即可找到最近的删除操作。通过Truncate table或delete语句执行的操作均可由该日志捕获到。如需监控insert或update等操作,在上述Description字段的选择上加入对insert或update语句的筛选即可。
通过这些日志,可以查看到操作发生的时间,来源IP和数据库用户等信息,如下所示:

5. 必须再次提醒各位,一旦开启对语句的监控,Audit数据库会快速增长,需要每日甚至每日多次巡检,确保磁盘空间不会被占满导致系统崩溃。
一旦发现Audit所在磁盘占有量过大(例如大于80%)或Audit库本身的占用过大(例如大于20G),即应运行步骤3中 配置的任务,然后对Audit库进行压缩和截断操作释放空间。