搜索​​​​

清除过滤器
文章
王喆 👀 · 九月 21, 2023

IRIS自动安装集群--manifest(安装清单)

前言 生产环境下我们部署和使用IRiS引擎,往往采用其主备镜像模式,虽然此架构简单但是往往我们需要持续在电脑前点击或者操作1到2小时,如果中间有个环节出现了问题有时我们可能需要部署一天. 接下来我分享的是IRIS自带的一个功能帮助我们部署---manifest-安装清单。他的主要使用方式是提前通过配置约定好我们期望的安装设置,在安装的过程中由IRIS程序直接执行脚本,简化IRIS集群的部署,减少运维人员的操作步骤,让我们有更多的精力放在实际项目和业务上。 1 简介 %Installer 实用程序允许您定义描述和配置特定 InterSystems IRIS 配置的安装清单,而不是分步安装过程。为此,我们需要创建一个类,其中包含描述所需配置的 XData 块,使用包含通常在安装期间提供的信息(超级服务器端口、操作系统等)的变量。我们还可以在类中包含一个使用 XData 块生成代码以配置实例的方法。本文提供了安装清单的示例,您可以复制和粘贴这个示例尝试使用。 定义清单后,可以在安装期间、从终端会话或代码调用它。注意:清单必须在 %SYS 命名空间中运行。 2 Manifest的最终成品 此成品展示的是一个一键安装主、备、仲裁的机器命令,此方法的使用可以便捷快速的安装主备环境,其基本每一行都有注释其说明: Include %occInclude /// Classname: App.MirrorInstall <br/> /// Summary: 镜像安装清单 <br/> /// Version: 1.0 <br/> /// Date: 2023年09月13日 14:23:24 <br/> /// Author: 王喆 <br/> Class App.MirrorInstall { XData Install [ XMLNamespace = INSTALLER ] { <Manifest> <!-- 镜像配置 --> <Log Text="镜像名称 " Level="0"/> <Var Name="MirrorName" Value="MIRRORSET" /> <Log Text=" 虚拟IP " Level="0"/> <Var Name="VituralIP" Value="192.168.98.110/24" /> <Log Text="仲裁机IP " Level="0"/> <Var Name="ArbiterNode" Value = "192.168.98.103|2188" /> <Log Text="网卡名称 " Level="0"/> <Var Name="PrimaryNetworkAdapter" Value = "Ethernet0" /> <Log Text="主机IP " Level="0"/> <Var Name="MasterIP" Value = "192.168.98.101" /> <Log Text="主机端口 " Level="0"/> <Var Name="MasterPort" Value = "2188" /> <Log Text="主机名称 " Level="0"/> <Var Name="MasterName" Value = "IRIS01" /> <Log Text="备机网卡名称 " Level="0"/> <Var Name="BackupNetworkAdapter" Value = "Ethernet0" /> <Log Text="备机IP " Level="0"/> <Var Name="BackupIP" Value = "192.168.98.102" /> <Log Text="备机名称 " Level="0"/> <Var Name="BackupName" Value = "IRIS02" /> <Log Text="实例名称 " Level="0"/> <Var Name="InstanceName" Value = "IRISHEALTH" /> <Log Text="镜像模式: 主机1或者备机0 " Level="0"/> <!-- <Var Name="MirrorModel" Value="1" />--> <!-- 安装文件所在的目录 --> <!-- <Var Name="INSTALLERDIR" Value = "D:\deploy" /> --> <!-- 实例所在的安装目录 --> <!-- <Var Name="PRODDIR" Value = "C:\InterSystems\IRISHealth" /> --> <!-- 激活 --> <!-- 通用内存堆的大小。 --> <SystemSetting Name="Config.config.gmheap" Value="1048576"/> <!-- 通用内存堆的大小。 --> <SystemSetting Name="Config.config.locksiz" Value="134217728"/> <!-- 错误日志中的最大条目数。 --> <SystemSetting Name="Config.config.errlog" Value="10000"/> <!-- 用于缓存例程缓冲区的共享内存大小。 --> <SystemSetting Name="Config.config.routines" Value="256"/> <!-- 为 8KB 数据库缓存分配的内存 一般为内存的一半 --> <SystemSetting Name="Config.config.globals8kb" Value="1000"/> <!-- 写入图像日志文件目录。 --> <SystemSetting Name="Config.config.wijdir" Value="D:/cache/wij"/> <!-- 日志文件的主要位置。 --> <SystemSetting Name="Config.Journal.CurrentDirectory" Value="E:/cache/journal"/> <!-- 日志文件的备用位置。 --> <SystemSetting Name="Config.Journal.AlternateDirectory" Value="D:/cache/journal"/> <!-- Caché 清除已完成日志文件之前的天数 --> <SystemSetting Name="Config.Journal.DaysBeforePurge" Value="3"/> <!-- 在此连续备份数后 --> <SystemSetting Name="Config.Journal.BackupsBeforePurge" Value="3"/> <!-- 最大IRISTempSizeAtStart --> <SystemSetting Name="Config.Startup.MaxIRISTempSizeAtStart" Value="10"/> <IfDef Var="INSTALLERDIR"> <Log Text=" 激活 " Level="0"/> <Invoke Class="App.MirrorInstall" Method="ConfigureInstance" CheckStatus="0"> <Arg Value="${INSTALLERDIR},${PRODDIR}"/> </Invoke> </IfDef> <!-- 创建命名空间 --> <Log Text="BKLINIK 命名空间存在与否判断 " Level="0"/> <If Condition='(##class(Config.Namespaces).Exists("BKLINIK")=0)'> <Log Text="不存在开始创建 " Level="0"/> <Namespace Name="BKLINIK" Create="yes" Code="BKLINIKAPP" Ensemble="1" Data="BKLINIKMSG"> <Configuration> <!-- 消息的目录 --> <Database Name="BKLINIKMSG" Dir="D:/DB/BKLINIKMSG" Create="yes"/> <!-- 代码的目录 --> <Database Name="BKLINIKAPP" Dir="D:/DB/BKLINIKAPP" Create="yes"/> <!-- 配置映射 --> <ClassMapping Package="HS" From="HSLIB"/> <ClassMapping Package="HSMOD" From="HSLIB"/> <ClassMapping Package="SchemaMap" From="HSLIB"/> <RoutineMapping Routines="HS.*" From="HSLIB" /> <RoutineMapping Routines="HSMOD.*" Type="INC" From="HSLIB" /> <RoutineMapping Routines="HSMOD.*" From="HSLIB" /> <RoutineMapping Routines="SchemaMap*" Type="INC" From="HSLIB" /> <GlobalMapping Global="%SYS" From="IRISSYS" /> <GlobalMapping Global="%SYS("HealthShare")" From="HSSYS"/> <GlobalMapping Global="EnsHL7.Annotation("HealthShare_2.5")" From="HSLIB" /> <GlobalMapping Global="EnsHL7.Description("HealthShare_2.5")" From="HSLIB" /> <GlobalMapping Global="EnsHL7.Schema("HealthShare_2.5")" From="HSLIB" /> <GlobalMapping Global="IRIS.Msg("HS")" From="HSLIB" /> <GlobalMapping Global="IRIS.Msg("HSErr")" From="HSLIB" /> <GlobalMapping Global="IRIS.Msg("HSFHIRErr")" From="HSLIB" /> <GlobalMapping Global="IRIS.Msg("HSFHIRXErr")" From="HSLIB" /> <GlobalMapping Global="IRIS.MsgNames("HS")" From="HSLIB" /> <GlobalMapping Global="IRIS.MsgNames("HSErr")" From="HSLIB" /> <GlobalMapping Global="IRIS.MsgNames("HSFHIRErr")" From="HSLIB" /> <GlobalMapping Global="IRIS.MsgNames("HSFHIRXErr")" From="HSLIB" /> </Configuration> </Namespace> <Log Text="创建命名空间 BKLINIK 结束" Level="0"/> <Log Text="Production启动" Level="0"/> <Namespace Name="BKLINIK" Create="no"> <Log Text=" 设置web应用程序 " Level="0"/> <CSPApplication Url="/csp/bklinik" Directory="${CSPDIR}bklinik" AuthenticationMethods="64" IsNamespaceDefault="true" Grant="%ALL" /> <CSPApplication Url="/csp/bklinik/services" Description="" Directory="${CSPDIR}bklinik\services" Resource="" Grant="%ALL" Recurse="1" LoginClass="" CookiePath="/csp/bklinik/services" AuthenticationMethods="64"/> <Log Text=" 导入代码 " Level="0"/> <!-- 如果设置了 SourceDir 就加载其中的文件 --> <!-- <IfDef Var="SourceDir"> --> <!-- <Log Text="SourceDir已定义-从脱机安装 ${SourceDir}" Level="0"/>--> <Import File="${INSTALLERDIR}\distr\"/> <!-- 设置命名空间的Production自动启动 前提得有这个--> <!-- <Production Name="BKLINIKPKG.FoundationProduction" AutoStart="1" /> --> <!-- </IfDef> --> <Log Text=" 导入代码成功 " Level="0"/> </Namespace> </If> <Log Text=" 镜像 " Level="0"/> <IfDef Var="MirrorModel"> <Log Text="Master已定义-从脱机安装 ${主机}" Level="0"/> <If Condition='(${MirrorModel}=1)' > <!-- 创建主镜像 --> <Log Text=" 创建主镜像 " Level="0"/> <Invoke Class="App.MirrorInstall" Method="CreateMirror" CheckStatus="0"> <Arg Value="${MirrorName},${VituralIP},${ArbiterNode},${PrimaryNetworkAdapter},${MasterIP},${MasterName}"/> </Invoke> </If> <If Condition='(${MirrorModel}=0)' > <!-- 加入镜像 --> <Log Text=" 加入镜像 " Level="0"/> <Invoke Class="App.MirrorInstall" Method="JoinMirror" CheckStatus="0"> <Arg Value="${MirrorName},${MasterName},${MasterIP},${MasterPort},${BackupNetworkAdapter},${BackupIP},${InstanceName}"/> </Invoke> </If> </IfDef> </Manifest> } /// MethodName: setup <br> /// Summary: 入口点方法,您需要调用,在类编译时,它从清单生成CachéObjectScript代码之后,您可以从终端运行此安装程序: <br> /// Parameter: Set pVars("Namespace")="NewNamespace" /// Set pVars("SourceDir")="C:\deploy\distr\" /// set pVart("MirrorModel")=1 /// Do ##class(App.MirrorInstall).setup(.pVart) <br> /// Return: { Boolean } <br> /// Date: 2023年09月13日 15:39:05 <br> /// Author: 王喆 <br> ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 0, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ] { Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "Install") } /// MethodName: ConfigureInstance <br> /// Summary: 激活的方法 <br> /// Parameter: { Object } <br> /// Return: { %Status } <br> /// Date: 2023年09月13日 14:24:17 <br> /// Author: 王喆 <br> ClassMethod ConfigureInstance(InstallerDir As %String, ProdDir As %String) As %Status { Set tSC = $ZF(-1,"copy "_ InstallerDir _ "\iris.key "_ProdDir_"\mgr\iris.key") Set tSC = ##class(%SYSTEM.License).Upgrade() Set tSC = $ZF(-1,"net start ISCAgent") //do ##class() Quit tSC } /// MethodName: CreateMirror <br> /// Summary: 创建镜像 <br> /// Parameter: { Object } <br> /// Return: { %Status } <br> /// Date: 2023年09月13日 14:24:52 <br> /// Author: 王喆 <br> ClassMethod CreateMirror(MirrorName As %String, VituralIP As %String, ArbiterNode As %String, PrimaryNetworkAdapter As %String, MasterIP As %String, MasterName As %String) As %Status { ZNspace "%SYS" #; Start %Service_Mirror 开启镜像服务 Set tSC = ##class(Security.Services).Get("%Service_Mirror",.properties) Set properties("Enabled")=1 Set tSC = ##class(Security.Services).Modify("%Service_Mirror",.properties) Set MirrorInfo("VirtualAddress") = VituralIP Set MirrorInfo("ArbiterNode") = ArbiterNode If "" '= MasterIP{ Set MirrorInfo("ECPAddress") = MasterIP } If "" '= PrimaryNetworkAdapter{ Set MirrorInfo("VirtualAddressInterface") = PrimaryNetworkAdapter } #; if "" '= MasterName #; Set MasterName = ##class(SYS.Mirror).DefaultSystemName() Try { Set tSC = ##class(SYS.Mirror).CreateNewMirrorSet(MirrorName,MasterName,.MirrorInfo) If $$$ISERR(tSC){ Set errobj = ##class(%Exception.General).%New() Set errobj.Name = "Error #",errobj.Code = $SYSTEM.Status.GetErrorCodes(tSC),errobj.Data = $SYSTEM.Status.GetOneStatusText(tSC,1) Throw errobj } } Catch ex { Write ex.DisplayString() } //Create Mirror Se1t //do ##class(SYS.Mirror). Quit tSC } /// MethodName: JoinMirror <br> /// Summary: 加入镜像 <br> /// Parameter: { Object } <br> /// Return: { %Status } <br> /// Date: 2023年09月13日 14:25:19 <br> /// Author: 王喆 <br> ClassMethod JoinMirror(MirrorName As %String, MasterName As %String, MasterIP As %String, MasterPort As %Integer, BackupNetworkAdapter As %String, BackupIP As %String, InstanceName As %String) As %Status { ZNspace "%SYS" //Start %Service_Mirror Set tSC = ##class(Security.Services).Get("%Service_Mirror",.properties) Set properties("Enabled")=1 Set tSC = ##class(Security.Services).Modify("%Service_Mirror",.properties) If "" '= BackupNetworkAdapter{ Set LocalInfo("VirtualAddressInterface") = BackupNetworkAdapter } If "" '= BackupIP{ Set LocalInfo("ECPAddress") = BackupIP } Try { Set SysName = ##class(SYS.Mirror).DefaultSystemName() Set tSC = ##class(SYS.Mirror).JoinMirrorAsFailoverMember(MirrorName,SysName,InstanceName,MasterIP,MasterPort,.LocalInfo) If $$$ISERR(tSC){ Set errobj = ##class(%Exception.General).%New() Set errobj.Name = "Error #",errobj.Code = $SYSTEM.Status.GetErrorCodes(tSC),errobj.Data = $SYSTEM.Status.GetOneStatusText(tSC,1) Throw errobj } } Catch ex { Write ex.DisplayString() } Quit tSC } } 3 安装清单执行 3.1 前置条件: 特别注意:windows server r2 2012 先安装2022版本的IRIS务必去微软官网下载Windows8.1-KB2999226-x64.msu并安装(必须) 预先安装 Java环境推荐Java8(必须) 服务器磁盘分区得有C盘 D盘 E盘可用(推荐) 部署包解压路径 D:\deploy(可选) 安装浏览器(可选) 3.2安装脚本 以管理员权限运行cmd,进入D://deploy 执行安装程序,其在安装时候就可以配置好镜像: 主机执行: ./IRISHealth-2022.1.2.574.0-win_x64.exe INSTALLERMANIFEST="D:\deploy\distr\MirrorInstall.xml" INSTALLERMANIFESTPARAMS="MirrorModel=1,PRODDIR=C:\InterSystems\IRISHealth,INSTALLERDIR=D:\deploy,GlobalBuffers=32768,RoutineCache=1000" INSTALLERMANIFESTLOGFILE="D:\deploy\mirrorLog.txt" INSTALLERMANIFESTLOGLEVEL="3" 备机执行: ./IRISHealth-2022.1.2.574.0-win_x64.exe INSTALLERMANIFEST="D:\deploy\distr\MirrorInstall.xml" INSTALLERMANIFESTPARAMS="MirrorModel=0,PRODDIR=C:\InterSystems\IRISHealth,INSTALLERDIR=D:\deploy,GlobalBuffers=32768,RoutineCache=1000" INSTALLERMANIFESTLOGFILE="D:\deploy\mirrorLog.txt" INSTALLERMANIFESTLOGLEVEL="3" 脚本解释: ./IRISHealth-2022.1.2.574.0-win_x64.exe // 安装程序 INSTALLERMANIFEST="D:\deploy\distr\MirrorInstall.xml" // 安装清单所在的目录 INSTALLERMANIFESTPARAMS=" // 安装的时候传入的参数 MirrorModel=1, // 1-主机 0-备机 PRODDIR=C:\InterSystems\IRISHealth, // 你想安装的目录 INSTALLERDIR=D:\deploy, // 安装文件所在的目录 GlobalBuffers=32768, // Global内存的大小 RoutineCache=1000" // Routine内存的大小 INSTALLERMANIFESTLOGFILE="D:\deploy\mirrorLog.txt" // 安装时候记录的日志位置 INSTALLERMANIFESTLOGLEVEL="3" // 日志级别 好了以上是我本次分享的关于IRIS关于Manifest的相关内容,本文只对manifest做了示例,具体的如何使用及其步骤在官网中描述的比较清楚,如果有需要大家可以去官网看看。 如果您觉得对你有帮助就给我点个赞吧,感激不尽!!!
文章
姚 鑫 · 十二月 31, 2023

第十一章 创建Callout Library - 使用 J 链接类型传递标准计数字符串

# 第十一章 创建Callout Library - 使用 J 链接类型传递标准计数字符串 # 使用 `J` 链接类型传递标准计数字符串 `iris-callin.h` 头文件定义了计数字符串结构 `IRIS_EXSTR`,表示标准 `IRIS` 字符串。此结构包含一个字符元素数组(`8` 位、`16` 位 `Unicode` 或 `32` 位 `wchar t`)和一个指定数组中元素数量的 `int` 值(最多字符串长度限制): ```java typedef struct { unsigned int len; /* length of string */ union { Callin_char_t *ch; /* text of the 8-bit string */ unsigned short *wch; /* text of the 16-bit string */ wchar_t *lch; /* text of the 32-bit string */ /* OR unsigned short *lch if 32-bit characters are not enabled */ } str; } IRIS_EXSTR, *IRIS_EXSTRP; ``` C Datatype| Input |In/Out |Notes ---|---|---|--- IRIS_EXSTR| 1j or j |1J or J| 8 位国家字符的标准字符串 IRIS_EXSTR |2j or n| 2J or N|16 位 Unicode 字符的标准字符串 IRIS_EXSTR| 4j |4J |32 位字符的标准字符串 wchar_t 字符 `IRIS_EXSTR` 数据结构由 `Callin API`(低级 `InterSystems` 函数调用库)中的函数进行操作。有关详细信息,请参阅使用 `Callin API` 中的“`Callin` 函数参考”。尽管名称相似,`Callin API` 和 `$ZF`标注接口是完全独立的产品)。 以下函数用于创建和销毁 `IRIS_EXSTR` 实例: - `IrisExStrNew[W][H`] — 为字符串分配请求的存储量,并使用长度和指向该结构的值字段的指针填充 `IRIS_EXSTR` 结构。 - `IrisExStrKill` — 释放与 `IRIS_EXSTR` 字符串关联的存储。 这是一个 `Callout` 库,它使用所有三种链接类型来返回数字字符串: ### 使用 `J` 连接传递字符串 以下三个函数均生成一个随机整数,将其转换为最多包含 `6` 位数字的数字字符串,并使用 `J` 链接返回字符串 。 ```java #define ZF_DLL // Required when creating a Callout library. #include #include #include #include int get_sample_L(IRIS_EXSTRP retval) { // 8-bit characters Callin_char_t numstr[6]; size_t len = 0; sprintf(numstr,"%d",(rand()%1000000)); len = strlen(numstr); IRISEXSTRKILL(retval); if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;} memcpy(retval->str.ch,numstr,len); // copy to retval->str.ch return ZF_SUCCESS; } int get_sample_LW(IRIS_EXSTRP retval) { // 16-bit characters unsigned short numstr[6]; size_t len = 0; swprintf(numstr,6,L"%d",(rand()%1000000)); len = wcslen(numstr); IRISEXSTRKILL(retval); if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;} memcpy(retval->str.wch,numstr,(len*sizeof(unsigned short))); // copy to retval->str.wch return ZF_SUCCESS; } int get_sample_LH(IRIS_EXSTRP retval) { // 32-bit characters wchar_t numstr[6]; size_t len = 0; swprintf(numstr,6,L"%d",(rand()%1000000)); len = wcslen(numstr); IRISEXSTRKILL(retval); if (!IRISEXSTRNEW(retval,len)) {return ZF_FAILURE;} memcpy(retval->str.lch,numstr,(len*sizeof(wchar_t))); // copy to retval->str.lch return ZF_SUCCESS; } ZFBEGIN ZFENTRY("GetSampleL","1J",get_sample_L) ZFENTRY("GetSampleLW","2J",get_sample_LW) ZFENTRY("GetSampleLH","4J",get_sample_LH) ZFEND ``` 注意:始终终止 `IRIS_EXSTRP` 输入参数 在前面的示例中,始终调用 `IRISEXSTRKILL(retval)` 以从内存中删除输入参数。即使参数不用于输出,也应该始终这样做。如果不这样做可能会导致内存泄漏。
文章
Jingwei Wang · 八月 30, 2023

使用 SQLAlchemy 将表传输到 IRIS 或从 IRIS 获取表

案例描述 假设您是一名 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 拥有您所需的所有工具,可帮助您进行开发并轻松与系统的现有设备或您的专业知识小工具集成。
文章
Qiao Peng · 三月 17

生成式大语言模型和检索增强生成

近来生成式大语言模型掀起了革命性的AI浪潮。生成式大语言模型是什么原理?我们怎么在业务中利用它? 一. 大语言模型的工作原理 生成式大语言模型是生成式人工智能底层的机器学习模型,是一种用于自然语言处理的深度学习模型。 人工智能、机器学习与大语言模型的关系如下图: 1.1 为什么我们称之为大语言模型? 大语言模型的“大”体现在多个方面: 首先,模型尺寸巨大,尤其是它的参数数量。例如GPT3有1750亿的参数; 其次,大语言模型是在巨大的算力基础上,基于海量语料进行训练的。例如Meta的Llama 2 的训练数据达到了两万亿个词(token); 再次,大语言模型是为解决通用问题,而非特定问题构建的。 1.2 大语言模型是怎么训练的? 大语言模型是事先训练好的模型。 训练时,大语言模型基于各种语料 - 人类知识库(例如Wikipedia)、公共数据集、网络爬虫数据,让模型进行“填空”练习,并经过人工编辑和“校对” 训练出来的,需要成千上万的GPU建立集群进行训练。根据Meta的信息,其Llama 2 的训练数据达到了两万亿个token,上下文长度为4096,对话上也是使用100万人类标记的数据微调。 运行时,训练产生的大语言模型可以在小的多的硬件上运行。 1.3 大语言模型的机器学习算法 冰冻三尺,非一日之寒;滴水穿石,非一日之功。生成式大语言模型能够落地经历了相当漫长的技术积累与进步。 大语言模型使用的机器学习算法是优化过的神经网络(Neural Network)。 神经网络发明于上世纪40-50年代,本质上是一个曲线拟合算法,通过拟合多个、多层的Softplus(曲线)、ReLU(Rectified Linear Unit 折线)、Sigmoid(对数线),实现对任意曲线的拟合。 “神经网络”名字听起来很高大上,但并不是脑科学的产物。因为发明时,觉得算法中每个节点像神经元、每个连线像神经触突,因此称为神经网络。 它很早就应用于自动控制领域。后来发展出多种神经网络算法,例如用于图像识别的卷积神经网络(CNN)、很早就用于语言学习的递归神经网络(RNN)… 在大语言模型成熟前,自然语言处理进化出过众多的技术,例如词袋、词汇矢量化、基于递归神经网络的模型、超长短期记忆网络(LSTM)… 但都在能力和算力上有众多缺陷,无法用于有实用价值的内容生成领域。 虽然它们不能实现实用化的内容生成,但为内容生成式大语言模型落地打下了基础,也是我们了解大语言模型前必须了解的预备知识。 1.3.1 分词(Tokenization) 词汇是语言模型分析的最小语义单位,所以第一步要把语句拆分成词汇(token)。分词并不简单,例如中文语句的分词就无法通过空格区分。所以用于大语言模型的分词算法也是基于海量语料训练出来的。 而基于大语言模型的内容生成,就是基于当前的所有token,预测下一个token,从而产生完整的内容。 1.3.2 词汇和语句的矢量化 机器学习算法基本只能处理数字,无法处理文本、声音、图像等非数字内容。所以要处理语言,需要对语句进行矢量化的表达,将其转换为数字。 拿我们常玩的一个游戏做解释:一个人在头脑里想象一个事物,让另一个人猜。另一个人可以问任何问题,但第一个人只能回答是和否。例如问:是动物吗?答:是;问:是哺乳动物吗?答:不是。问:有脚吗?答:是。 这个游戏的过程就是用不同维度来验证和归类一个事物,最终可以让这个事物在不同的维度上得以表达,即这个事物在一个高维度矢量空间上可以得到一个定位(矢量),同时相近的概念在矢量空间互相接近。 大语言模型通过大规模语料训练用神经网络将每个词汇在一个高维度空间矢量化,得到表达矢量的数组,将词汇矢量化到如下示意的矢量空间中: 这里的矢量化出来的是密集矢量,即每个维度上都不是0,且维度数固定,从而用更少的字节中存在更多的信息,因此在计算上的利用成本更低。相较于稀疏矢量的例子,例如书籍的归类:科学、言情、教育、音乐… ,词汇和语句的矢量结果密集度高的多,因此是密集矢量。 而语句矢量化在词汇矢量化的基础上,要将词汇在句子中的顺序信息加入,从而将“小明追老虎”和“老虎追小明”这两个词汇完全相同但语义完全不同的句子在矢量化输出上能够加以区分。 1.3.3 基于大语言的矢量化模型 将词汇和语句矢量化,是迈向我们如今看到的生成式大语言模型的第二步。 不同的语言矢量化模型生成的密集矢量维度数是不一样的,越高的维度数的密集矢量需要越大的计算资源和越大的内存消耗。下面是一些常见的矢量化语言模型和它们的维度数: 模型 维度数量 BERT (Bidirectional Encoder Representations from Transformers) 768或1024 GPT (Generative Pre-trained Transformer) 768或1600 Word2Vec 300 USE (Universal Sentence Encoder) 512 MiniLM 384 1.3.4 矢量相似度查询 词汇和句子矢量化后,怎么找到相似的词汇和句子? 对两个矢量进行相似度查询,就是计算两个矢量间的“距离”。有很多算法,如下图中所示的这些常见算法。 在大规模、高维度矢量数据库中查找近义词,如果采用与矢量记录逐一计算相似度的方法,将需要巨大的计算量,其效率并不能满足实用的性能需求。 而实际需求并不需要精确的相似度,因此出现了近似近邻算法(Approximate Nearest Neighbors - ANN)解决效率问题。ANN有多种算法,例如Annoy (Approximate Nearest Neighbors Oh Yeah)、 HNSW (Hierarchical Navigable Small World)。 下图是Annoy算法的示意图: 在矢量数据集中随机找2个矢量,计算出一个矢量平面到2个矢量的距离相同,从而将矢量数据集分割成2个空间;然后再在每个空间里重复上面的过程,直到分割后的空间里矢量数量与目标相似度矢量数量一致(例如我们希望得到返回矢量数量为10个以内的相似度结果集,那么如果空间内的矢量数小于等于10,就停止上述过程);从而我们得到一个决策树,今后可以用这个决策树进行矢量相似度查询,显然会快很多。 因为Annoy是基于最初的随机选择的2个矢量开始决策树构建的,如果这2个矢量本身就是高度相似的,那这2个矢量永远不会被一个矢量相似度查询要求同时命中,从而带来显著的误差。怎么办?可以随机多选几组初始矢量,从而形成多棵决策树的决策森林,提高ANN的精度。 可见ANN是大规模矢量检索查询的核心。 1.3.5 生成式大语言模型 递归神经网络(recurrent neural network - RNN)很早就应用到自然语言处理领域,之后出现了RNN改进模型LSTM (Long short-term memory),它们按顺序处理输入语句的词汇,并行能力不足,而且越高阶的神经网络需要的算力越高,达不到实用化的性能需求。 在2017年Google一个小团队(Transformer八子)发表了一篇论文 - Attention Is All You Need, 阐述了一类特殊的神经网络 – 基于注意力(Attention)机制的Transformer。它的注意力机制根据输入数据的长度执行固定步骤的计算,并且对输入数据的词汇(token)是并行计算的,它奠定了实用的生成式大语言模型的基础。这个团队的成员后大多离开了Google,并创立或加入了目前市场上几个主要生成式大语言模型。 在Transformer并行处理能力和越来越强大的GPU并行算力加持下,生成式大语言模型终是水到渠成,可以说是大力出奇迹! 当然Transformer模型具备多个特殊能力支撑内容生成能力。下面这张图解释了Transformer模型的4个核心特性:词汇矢量化(Word Embedding)、词汇在语句中位置的矢量化叠加(Positional Encoding)、自我注意力(Self-Attention)和残值连接(Residual Connections)。可见它其实构建在前面出现的技术基础之上。 借助这个新神经网络模型思路,众多大厂发布了自己的生成式大语言模型,如下面列出的这些著名的大语言模型。它们的宣传中常常强调其百亿级、甚至千亿级的参数: 模型 厂商 参数 GPT OpenAI (Microsoft) 1750亿 Bard/Gemini Google 18亿,32.5亿 PaLM2 Google 3400亿 Llama 2 Meta 70亿,130亿,700亿 Claude 2/3 Anthropic (Amazon) 未披露 Stable Beluga Stability AI 70亿,130亿,700亿 Coral Cohere 未披露 Falcon Technology Innovation Institute 13亿,75亿,400亿,1800亿 MPT Mosaic 70亿,300亿 往往参数规模越大,其生成的内容越精确和越富有创造力。那么这些参数指什么?无论是什么样的大语言模型,它们底层都是神经网络,这些参数主要就是指神经网络中的权重和偏差。 二. 大语言模型应用中的问题和检索增强生成 从机制上,生成式大语言模型并不神秘。虽然它展现出了强大的理解能力甚至“创造力”,但它有以下几个问题: 它的知识来自于训练语料,并不知道所有知识。例如GPT-4 截止训练数据的时间是2022年1月份,对于后来的世界一无所知,更不可能知道您的机构中的未开放数据。 它是基于通用数据训练的,对于特定领域往往训练不足。 它的内容生成机制是使用神经网络逐词预测出回答中的下一个词从而构成完整的语句。因此它本质上不会拒绝回答任何问题,虽然人类限制它回答诸如如何制作病毒类的问题。结合它的“无知”和“创造力”,对不知道的问题,它也能一本正经地胡说八道,这就是生成式大语言模型的“幻觉”。 生成式大语言模型的“幻觉”在目前的应用中非常常见。例如我问了Bing Copilot一个关于“什么是InterSystems IRIS互联互通套件?”的问题,它不懂但没有拒绝回答,而且回答地相当“幻觉”: 如果想在我们自己的业务中直接应用生成式大语言模型,让它提供患者教育,或者回复患者的预约查询、亦或回答患者关于他/她自己的用药注意事项?显然不靠谱。 是不是可以用我们自己的数据进行训练?一来很多大模型都不是开放的,无法自己训练;二来相信大家都没有训练大语言模型的昂贵算力。 怎么解决这个问题? 大语言模型其实有三次“训练”机会: 预先训练就是大语言模型厂商通过海量语料进行的训练,我们干不了; 调优训练需要基于开放的大语言模型,算力成本也不低; 所以我们可以通过“提示”,让生成式大语言模型给我们想要的答案。 我又试了一次让Bing Copilot回答“什么是InterSystems IRIS互联互通套件?”,不过这次,我给了它提示,让它先读读关于InterSystems IRIS互联互通套件介绍的网页。这次它回答得相当到位: 也就是通过合适的提问,把本地数据提示给生成式大语言模型,从而让它可以准确回答而不会产生幻觉。 检索增强生成基于问题先在本地数据检索,将相关结果提示给生成式大语言模型,从而获得靠谱的回答,这就是检索增强生成(Retrieval Augmented Generation – RAG)。 这里的本地数据检索,是基于大语言的矢量相似度检索。所以,需要借助矢量数据库,对本地的数据矢量化保存、并提供基于问题的矢量相似度查询,从而基于问题给出最匹配的本地数据。 这里是完整的检索增强生成流程示意图,分为2个过程: 1. 基于本地数据建立矢量知识库的过程 预先建立知识库, 将本地文档切分成文本段 使用矢量化语言模型对数据矢量化 将矢量保存到矢量知识库 2. 借助本地矢量知识库和外部大语言模型回答问题的过程 使用矢量化模型将问题矢量化 在矢量数据库中检索与问题相关的矢量记录 将匹配的数据(知识)作为上下文组织到完整的问题与提示中,向大语言模型提问。例如提示模版是:请仅使用以下上下文回答问题 从大语言模型得到回答 由此可见,检索增强生成至少需要以下3个技术组件: 矢量数据库 – 用于本地数据的矢量化保存和矢量化查询 矢量化语言模型 – 用于将本地数据和问题矢量化 内容生成语言模型 – 用于基于问题和上下文生成自然语言回答 矢量化语言模型、内容生成语言模型都有很多选择,根据需要可以选择能部署到本地的模型、也可以选择厂商提供的云服务。 而矢量数据库是保存本地知识数据的矢量化版本的,市面上常见的是一些nonSQL的专用数据库,也就是说需要将本地数据迁移到矢量数据库,并专门学习其数据操作的API。 可以预见,生成式大语言模型的能力将迅速进化,但本地的知识和数据并不会以如此快的速度发生变化。因此RAG将本地的知识和数据通过矢量化与生成式大语言模型集成,借助其不断提升的强大能力又无需被任何一个模型绑架,将是一个合理的解决方案。 三. InterSystems IRIS的内容生成架构 InterSystems IRIS是应用在众多行业的通用数据平台,并在2024版本中加入了对矢量存储和查询的支持,无需将IRIS中已经保存的本地知识数据迁移到别的矢量数据库中,从而消除数据迁移时间差、额外部署矢量数据库的运维成本,同时降低敏感数据泄露风险、确保遵循特定行业中对数据迁移监管的要求。而InterSystems IRIS作为一个具有互操作能力的数据平台,可以轻松集成大语言模型,并建立和管理检索增强生成的pipeline,降低RAG的技术实现复杂度。 3.1 IRIS的矢量存储和矢量查询 IRIS提供矢量数据类型,它被完全集成在IRIS多模型的架构中,尤其使用SQL就可以完整使用矢量存储和查询。 例如要创建含有矢量类型字段vec的表: CREATE TABLE t (txt VARCHAR(1000), vec VECTOR(INT, 200)); 向矢量字段vec中插入数据: INSERT INTO t VALUES (‘…’, TO_VECTOR(‘1,2,3,…’, INT)); 这里的矢量数据是需要通过调用矢量化模型产生的。 基于矢量相似度查询最接近的10条记录: SELECT TOP 10 * FROM FROM ( SELECT t.*, VECTOR_DOT_PRODUCT(vec, TO_VECTOR(…)) AS similarity FROM t ) ORDER BY similarity DESC; 3.2 IRIS的矢量索引 IRIS进一步提供了更易使用的矢量索引:无需创建矢量字段,直接在现有数据表上就可以创建声明式的矢量索引,并自动调用集成的矢量化模型,从而使用SQL就可以免代码方式进行开发。 创建矢量索引 – 通过索引对title、author和article这3个字段组合进行矢量化: CREATE INDEX Vec ON MyNews(Title, Author, Article) AS VectorIndex(MODEL=‘BERT’); 执行矢量查询 – 查询与条件最近似的3条记录: SELECT TOP 3 * FROM MyNews WHERE Category = ‘NYT’ ORDER BY MyTable_VecSim(%ID, ‘climate change’); 3.3 基于IRIS构建完整的RAG方案 基于最新发布的InterSystems IRIS 2024.1,和部署到本地的矢量化模型(all-MiniLM-L12-v2)、内容生成模型(llama2),我构建了一个RAG原型: 这里IRIS实例即是保存本地数据的数据平台,也是本地数据的矢量化数据库,从而避免了数据的跨平台迁移。而全SQL的数据操作能力,让构建在自己数据上的检索增强生成方案能快速落地。 现在就把生成式大语言模型集成到您自己的业务中吧!注:本文中的部分图片来自StatQuest、medium、wikipedia和weaviate。
文章
Michael Lei · 七月 3, 2023

基于LangChain的IRIS ChatGPT – 释放大语言模型LLM的全部潜力

你好社区在本文中,我将介绍我的应用程序irisChatGPT ,它是基于LangChain Framework构建的。首先,让我们对框架进行一个简单的概述。 全世界都在谈论ChatGPT以及大型语言模型 (LLM) 如何变得如此强大,并且表现超出预期,提供类似人类的对话。这只是将其应用于每个企业和每个领域的开始! 剩下的最重要的问题是如何将这种能力应用于适合企业需求的特定领域数据和特定场景响应行为。 LangChain为这个问题提供了结构化且有效的答案! LangChain 技术可以帮助实现法学硕士的巨大潜力,通过围绕法学硕士提供抽象层并使法学硕士的使用变得简单有效,从而构建令人惊叹的应用程序。 LangChain 是一个框架,可以快速轻松地开发使用大型语言模型(例如 GPT-3)的应用程序。 然而,该框架引入了额外的可能性,例如,轻松使用外部数据源(例如维基百科)来放大模型提供的功能。我相信你们都可能尝试过使用 Chat-GPT,并发现它无法回答特定日期之后发生的事件。在这种情况下,在维基百科上搜索可以帮助 GPT 回答更多问题。 LangChain结构 该框架分为六个模块,每个模块允许您管理与法学硕士互动的不同方面。让我们看看这些模块是什么。 模型:允许您实例化和使用三种不同类型的语言模型,它们是: 大型语言模型 (LLM):这些能够理解自然语言的基础机器学习模型。它们接受输入中的字符串并在输出中生成字符串。 聊天模型:由 LLM 支持的模型,但专门用于与用户聊天。您可以在这里阅读更多内容。 文本嵌入模型:这些模型用于将文本数据投影到几何空间中。这些模型将文本作为输入并返回数字列表,即文本的嵌入。 提示:提示是我们如何与模型交互以尝试从中获取输出。现在知道如何编写有效的提示至关重要。这个框架模块可以让我们更好的管理提示。例如,通过创建我们可以重用的模板。 索引:最好的模型通常是与一些文本数据相结合的模型,以便为模型添加上下文或解释某些内容。这个模块可以帮助我们做到这一点。 链:很多时候,要解决任务,对 LLM 的单个 API 调用是不够的。该模块允许集成其他工具。例如,一个调用可以是一个组合链,其目的是从维基百科获取信息,然后将此信息作为模型的输入。该模块允许连接多个工具以解决复杂的任务。 内存:该模块允许我们在模型调用之间创建持久状态。能够使用记住过去说过的话的模型肯定会改善我们的应用程序。 代理:代理是一个法学硕士,它做出决定,采取行动,观察其所做的事情,并以这种方式继续,直到完成其任务。该模块提供了一组可以使用的代理。 现在让我们更详细地了解一下如何利用不同的模块来实现代码。 Langchain工作原理 步骤1 :用户向LangChain发送问题第2步 :LangChain将此问题发送至Embedding Model步骤3:嵌入模型将文本转换为向量,文本以向量形式存储在数据库中并返回给LangChain步骤4 :LangChain将这些向量发送到向量数据库(有多个向量数据库,我们在我们的应用程序中使用chroma)步骤5:向量数据库返回前 K 个近似最近邻 ( KNN ) 向量第6步:LangChain 将问题与KNN向量一起发送到大型语言模型 (LLM) (我们在应用程序中使用 OpenAI)步骤7:LLM向Langchain返回答案步骤8:Langchain将答案返回给用户 关于申请 irisChatGPT应用程序利用围绕大型语言模型 (LLM) 构建的最热门 Python 框架LangChain的功能。 LangChain 是一个框架,可以快速轻松地开发使用大型语言模型的应用程序。应用程序是在系统间嵌入式 Python功能的帮助下使用 objectscript 构建的。它还包含Streamlit Web 应用程序,这是一个开源 Python 应用程序框架,用于为数据科学和机器学习创建漂亮的 Web 应用程序。 特征 以下是应用程序功能列表以及相关屏幕截图 内置Intersystems ObjectScript 参考ChatGPT 内置InterSystems 大奖赛 2023 ChatGPT 使用 SQLDatabaseChain 回答有关缓存数据库的问题 创建您自己的 chatGPT 模型并与其聊天 OpenAI 聊天GPT 维基百科搜索 使用DuckDuckGo(DDG)通用搜索引擎在互联网上搜索 使用Python REPL LangChain功能生成Python代码 Streamlit Web 应用程序在线演示 谢谢
文章
Qiao Peng · 十二月 7, 2023

通用TCP业务服务和业务操作

TCP作为OSI 7层的传输层的通信协议,其使用上不像更上层的通信协议那么方便,因为TCP操作的不是数据包,它操作的是数据流。因此有多种将TCP数据流“解释”为数据包(消息)的方法。 InterSystems IRIS提供了多种TCP适配器,用于不同的“解释”,例如EnsLib.TCP.FramedInboundAdapter使用特定的首尾字符做为分隔、EnsLib.TCP.CountedInboundAdapter使用固定的长度进行分隔... 同时,InterSystems IRIS提供了多种开箱即用的TCP业务服务和业务操作,方便接入和发送TCP数据。这里我们介绍常见的使用特定的首尾字符做为分隔的TCP业务服务和业务操作。 1. 通用TCP业务服务和业务操作 EnsLib.TCP.Framed.PassthroughService和EnsLib.TCP.Framed.PassthroughOperation是一组使用特定的首尾字符做为分隔TCP数据流的通用业务服务和业务操作。EnsLib.TCP.Framed.PassthroughService业务服务会将TCP数据封装在Ens.StreamContainer发送给业务流程或业务操作;而EnsLib.TCP.Framed.PassthroughOperation业务操作发送并接收Ens.StreamContainer类型的数据。 2. 使用EnsLib.TCP.Framed.PassthroughService业务服务 2.1 向production中加入通用TCP业务服务 增加通用TCP业务服务,只需要在Production配置页面的服务中添加EnsLib.TCP.Framed.PassthroughService。 建议加入Production时,给业务服务起一个名字,用于代表具体的业务,例如是连接到设备的TCP服务,可以命名为TCPforDevice(可以考虑的命名规则 - 接口方式+业务系统)。如果未命名,默认会使用类名作为业务服务名。 2.2 配置通用TCP业务服务 主要的设置项是以下几个: 1. Port:接收TCP数据的端口,例如图中的65530端口 2. Target Config Names:TCP服务发送消息的目标,可以是业务流程或业务操作 3. Message Frame Start:标记TCP开始的字符或字符串,用10进制ASCII码表示。如果有多个字符,字符ASCII间用逗号分隔。图例中为ASCII 10,也就是“退格符”。 4. Message Frame End:标记TCP结束的字符或字符串,用10进制ASCII码表示。如果有多个字符,字符ASCII间用逗号分隔。图例中为ASCII 28和13,也就是“文件分隔符”和“回车符”。 5. Remove Framing: 是否把标记TCP起止的字符删除后再发送到后续业务组件。建议选中 6. Discard Incorrect Framing:丢弃使用不正确起始字符的TCP数据。建议在调试阶段取消选中,测试完成后再选中 7. Frame Acknowledgement:是否要发送Frame 通知,可以取消选中 启用该业务服务后,既可以接收了TCP请求了。 2.3 测试通用TCP业务服务 可以使用TCP客户端进行发送数据的测试。这里使用Packet Sender向IRIS发布的65530端口,发送以下的数据,注意前后的TCP分段起止字符: 然后就可以到消息可视化追踪的页面查看接收的消息。可以看到它是Ens.StreamContainer类型的消息,而且已经把起止字符去掉了: 3. 使用EnsLib.TCP.Framed.PassthroughOperation业务操作 3.1 向production中加入通用TCP业务操作 同样,可以直接将EnsLib.TCP.Framed.PassthroughOperation加入production。 3.2 配置通用TCP业务操作 主要的设置项是以下几个: 1. IP Address:TCP服务器的IP地址 2. Port:TCP服务器的TCP端口,例如图中的65530端口。这里用65530端口,就是发送到上面我们建立的通用TCP业务服务。 3. Message Frame Start:标记TCP开始的字符或字符串,用10进制ASCII码表示。如果有多个字符,字符ASCII间用逗号分隔。图例中为ASCII 10,也就是“退格符”。业务操作会自动在发送的TCP数据头部加入这些开始字符。 4. Message Frame End:标记TCP结束的字符或字符串,用10进制ASCII码表示。如果有多个字符,字符ASCII间用逗号分隔。图例中为ASCII 28和13,也就是“文件分隔符”和“回车符”。业务操作会自动在发送的TCP数据尾部加入这些开始字符。 5. Remove Framing: 是否把收到的TCP响应数据中标记TCP起止的字符删除。建议选中 6. Discard Incorrect Framing:丢弃使用不正确起始字符的TCP响应数据。建议在调试阶段取消选中,测试完成后再选中 启用该业务操作后,既可以发送TCP请求了。 3.3 测试通用TCP业务操作 因为TCP业务操作的请求消息是Ens.StreamContainer,里面是流数据,为了方便测试,我们建立一个测试用的业务流程,里面组织好数据并调用通用TCP业务操作。 3.3.1 创建一个新的业务流程,设置其请求消息为Ens.StringRequest,用于测试时传入TCP数据。并为其上下文增加一个名为DataBody、类型为%Stream.GlobalCharacter(可持久化的字符流类型)的属性: 3.3.2 在业务流程中增加一个代码流程(<code>),将请求消息的字符串数据写入上下文的DataBody字符流: Do context.DataBody.Write(request.StringValue) 注意行首加空格。 3.3.3 然后在业务流程中再加入一个调用流程(<call>),调用上面已经加入production的业务操作,例如TCPtoWMS,并设置请求和响应消息为Ens.StreamContainer或Ens.StreamContainer。 3.3.4 配置RESTtoLIS业务操作的请求消息(Request) 可以直接点击构建请求消息(Request Builder)按钮,使用图形化拖拽建立请求消息: 将左边上下文context里的DataBody拖拽到callrequest的Stream属性上。 3.3.5 将业务流程编译后加入到Production。 3.3.6 测试这个业务流程,输入测试字符串。然后查看消息追踪,可以看到类似这样的: 这是测试业务流程/业务操作的消息追踪: 因为TCP数据发给了同一个production下的通用业务服务,因此还会看到这样的一笔业务服务的消息追踪:
文章
Michael Lei · 九月 18, 2023

开发者作品展示--几乎实现的向量支持

如今,关于大语言模型、人工智能等的消息不绝于耳。向量数据库是其中的一部分,并且已经有非IRIS的技术实现了向量数据库。 为什么是向量? 相似性搜索:向量可以进行高效的相似性搜索,例如在数据集中查找最相似的项目或文档。传统的关系数据库是为精确匹配搜索而设计的,不适合图像或文本相似性搜索等任务。 灵活性:向量表示形式用途广泛,可以从各种数据类型派生,例如文本(通过 Word2Vec、BERT 等嵌入)、图像(通过深度学习模型)等。 跨模态搜索:向量可以跨不同数据模态进行搜索。例如,给定图像的向量表示,人们可以在多模式数据库中搜索相似的图像或相关文本。 还有许多其他原因。 因此,对于这次 pyhon 竞赛,我决定尝试实现这种支持。不幸的是我没能及时完成它,下面我将解释原因。 有几件重要的事情必须完成,才能使其充实 使用 SQL 接受并存储向量化数据,简单的示例(本例中的 3 是维度数量,每个字段都是固定的,并且该字段中的所有向量都必须具有精确的维度) create table items(embedding vector( 3 )); insert into items (embedding) values ( '[1,2,3]' ); insert into items (embedding) values ( '[4,5,6]' ); 相似度函数,相似度有不同的算法,适合对少量数据进行简单搜索,不使用索引 -- Euclidean distance select embedding, vector.l2_distance(embedding, '[9,8,7]' ) distance from items order by distance; -- Cosine similarity select embedding, vector.cosine_distance(embedding, '[9,8,7]' ) distance from items order by distance; -- Inner product select embedding, -vector.inner_product(embedding, '[9,8,7]' ) distance from items order by distance; 自定义索引,有助于更快地搜索大量数据,索引可以使用不同的算法,并使用与上面不同的距离函数,以及其他一些选项 新南威尔士州 倒排文件索引 搜索将使用创建的索引,其算法将找到所请求的信息。 插入向量 该向量应该是一个数值数组,可以是整数或浮点数,也可以是有符号的或无符号的。在IRIS中我们可以将其存储为$listbuild,它具有良好的表示性,已经支持,只需要实现从ODBC到逻辑的转换。 然后,可以使用外部驱动程序(例如 ODBC/JDBC)或使用 ObjectScript 从 IRIS 内部以纯文本形式插入值 普通 SQL insert into items (embedding) values ( '[1,2,3]' ); 来自ObjectScript set rs = ##class ( %SQL.Statement ). %ExecDirect (, "insert into test.items (embedding) values ('[1,2,3]')" ) set rs = ##class ( %SQL.Statement ). %ExecDirect (, "insert into test.items (embedding) values (?)" , $listbuild ( 2 , 3 , 4 )) 或者嵌入式 SQL &sql( insert into test.items (embedding ) values ('[ 1 , 2 , 3 ]')) set val = $listbuild ( 2 , 3 , 4 ) &sql( insert into test.items (embedding ) values (:val)) 它将始终存储为 $lb(),并在 ODBC 中以文本格式返回 意外行为 在使用 DBeaver 进行测试期间,我发现连接后的第一行插入正确,但所有其他行均按原样插入,没有任何验证或转换。 然后我发现,JDBC默认使用快速插入,在这种情况下,它将插入的数据直接存储到全局变量中,所以我必须手动将其关闭 在 DBeaver 中,在 FeatureOption 字段中选择 optfastSelect 计算 我们需要向量来支持两个向量之间距离的计算 为了这次比赛,我需要使用嵌入式Python,这就带来了一个问题,如何在嵌入式Python中操作$lb。 %SYS.Class中有一个方法ToList,但Python包IRIS没有内置该方法,需要通过ObjectScript方式调用它 ClassMethod l2DistancePy(v1 As dc.vector.type, v2 As dc.vector.type) As %Decimal (SCALE= 10 ) [ Language = python, SqlName = l2_distance_py, SqlProc ] { import iris import math vector_type = iris.cls('dc.vector.type') v1 = iris.cls(' %SYS.Python ').ToList(vector_type.Normalize(v1)) v2 = iris.cls(' %SYS.Python ').ToList(vector_type.Normalize(v2)) return math.sqrt(sum([(val1 - val2) ** 2 for val1, val2 in zip(v1, v2)])) } 它看起来一点也不正确。我希望 $lb 可以在 python 中即时解释为列表,或者在列表内置函数 to_list 和 from_list 中解释 另一个问题是当我尝试使用不同的方式测试此功能时。使用嵌入式Python中的SQL,使用嵌入式Python编写的SQL函数,它会崩溃。因此,我还必须添加 ObjectScript 的功能。 ModuleNotFoundError: No module named 'dc' SQL Function VECTOR.NORM_PY failed with error: SQLCODE=-400,%msg=ERROR #5002: ObjectScript error: <OBJECT DISPATCH>%0AmBm3l0tudf^%sqlcq.USER.cls37.1 *python object not found 目前在 Python 和 ObjectScript 中实现了计算距离的函数 欧氏距离 [SQL]_system@localhost:USER> select embedding, vector.l2_distance_py(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+----------------------+ | embedding | distance | + -----------+----------------------+ | [4,5,6] | 5.91607978309961613 | | [1,2,3] | 10.77032961426900748 | + -----------+----------------------+ 2 rows in set Time : 0.011 s [ SQL ]_system@localhost: USER > select embedding, vector.l2_distance(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+----------------------+ | embedding | distance | + -----------+----------------------+ | [4,5,6] | 5.916079783099616045 | | [1,2,3] | 10.77032961426900807 | + -----------+----------------------+ 2 rows in set Time : 0.012 s 余弦相似度 [SQL]_system@localhost:USER> select embedding, vector.cosine_distance(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+---------------------+ | embedding | distance | + -----------+---------------------+ | [4,5,6] | .034536677566264152 | | [1,2,3] | .11734101007866331 | + -----------+---------------------+ 2 rows in set Time : 0.034 s [ SQL ]_system@localhost: USER > select embedding, vector.cosine_distance_py(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+-----------------------+ | embedding | distance | + -----------+-----------------------+ | [4,5,6] | .03453667756626421781 | | [1,2,3] | .1173410100786632659 | + -----------+-----------------------+ 2 rows in set Time : 0.025 s 内积 [SQL]_system@localhost:USER> select embedding, vector.inner_product_py(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+----------+ | embedding | distance | + -----------+----------+ | [1,2,3] | 46 | | [4,5,6] | 118 | + -----------+----------+ 2 rows in set Time : 0.035 s [ SQL ]_system@localhost: USER > select embedding, vector.inner_product(embedding, '[9,8,7]' ) distance from items order by distance; + -----------+----------+ | embedding | distance | + -----------+----------+ | [1,2,3] | 46 | | [4,5,6] | 118 | + -----------+----------+ 2 rows in set Time : 0.032 s 另外还实现了数学函数:add、sub、div、mul。 InterSystems 支持创建自己的聚合函数。因此,可以对所有向量求和或求平均值。但不幸的是,InterSystems 不支持使用相同的名称,需要使用自己的名称(和模式)来执行函数。但它不支持聚合函数的非数值结果 简单的 vector_add 函数,返回两个矢量的和 当用作聚合时,它显示 0,而预期矢量也是 建立索引 不幸的是,由于我在实现过程中遇到了一些障碍,我没能完成这一部分。 缺乏内置的 $lb 到 python 列表转换以及当 IRIS 中的矢量存储在 $lb 中时返回,并且所有具有构建索引的逻辑预计都在 Python 中,从 $lb 获取数据并将其设置回全局变量也很重要 缺乏对Global的支持 IRIS 中的 $Order,支持方向,因此可以反向使用,而Python内嵌的 order 实现没有它,因此需要读取所有键并反转它们或将末尾存储在某处 由于对上面提到的从 Python 调用的 Python 的 SQL 函数的不好的体验而产生疑问 在构建索引期间,预计会在图形中存储矢量之间的距离,但在global里保存浮点数时遇到了bug 我在工作中发现了11 个嵌入式 Python 问题,所以大部分时间都是在寻找解决方法来解决问题。在名为iris-dollar-list的 @Guillaume.Rongier7183 项目的帮助下,我成功解决了一些问题。 安装 无论如何,它仍然可用,并且可以与 IPM 一起安装,即使功能有限也可以使用 zpm "install vector" 或者在开发模式下使用 docker-compose git clone https://github.com/caretdev/iris-vector.git cd iris-vector docker-compose up -d
文章
Hao Ma · 五月 24, 2023

使用Manifest

Manifest也许应该被翻译成“清单”, 字典上是这么解释的: 提供船舶及其货物和其他物品、乘客和船员的全面细节的文件,供海关官员使用,比如:飞机上的乘客或货物清单; 一辆货运列车的车厢清单。 在计算机语言中, Manifest可以是各种格式,用的最多的是xml和json,在IRIS中,manifest是xml格式的, 放在objectscript类的XDATA块里。 ## 编写mainfest IRIS用manifest来做配置。内部工具*%install*, 会读取manifest, 生成真正的objectscript代码来配置IRIS。我们来看个基本的例子。 ### 基本用法 下面的User.Manifest.cls` ,它配置了IRIS的global buff, bbsize等等, 然后还创建了一个命名空间。 ```java Include %occInclude Class User.Manifest { ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ] { Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "MyInstall") } XData MyInstall [ XMLNamespace = INSTALLER ]{ } } ``` 稍微解释一下代码: - `Include %occInclude`是必须的 - `setup()`用来读取manifest的内容,完成配置工作。用户基本不用修改这个method。 - mainifest本身的逻辑层次很清楚,要配置什么内容查查文档都可以。上面的manifest只是个示意,真正用起来可以需要非常多的配置项,比如namespace, database的配置,有很多的标签可选。 ### 传参数给manifest 调用manifest的method, 也就是例子里的setup(), 注意第一个参数是`ByRef pVars`。这是objectscript里常用的By referrence的传参方式。请看下面的例子: ```java Include %occInclude Class User.Manifest { ClassMethod main(){ Set pVars("Namespace")="MYNAMESPACE" Set pVars("AnotherKey")= "AnotherValue" $$$ThrowOnError(..CreateNamespace(.pVars)) } ClassMethod CreateNamespace(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ]{ Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "CreateNamespace") } XData CreateNamespace [ XMLNamespace = INSTALLER ]{ } } ``` 上面code在main()里定义了一个pVars, 放了两个key-value, 用来调用CreateName()。 定义的namespace在manifest用到了, Anotherkey被用在了log里, 只是一个示意。 注意这个定义: `Dir="${MGRDIR}/${Namespace}"`。 其中的`MGRDIR`不需要自己定义。Manifest有一堆自己定义的Variable, 用的最多的是 CFGDIR, CSPDIR, INSTALLDIR, MGRDIR, PORT等等。具体列表见文档。 ### 更多的用法 下面这个例子包括了import文件和copy文件, *SourceDir*和*Namespace*是传入的参数。导入文件要在一个namespace的定义里面, 拷贝文件和命名空间无关。 ```xml ``` **CSPApplication** 在manifest里定义cspapplication不难,麻烦的是不同的版本使用的标签上会有修改。下面给了一个例子。 ```xml ``` > **调用代码的例子** ```xml ``` **创建User, Role** ```xml ``` Nampespace Mapping ```xml ``` 其他还有很多的用法。想了解更多,可以看看[文档说明里的Tag列表](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest#GCI_manifest_tags), 和[template](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest#GCI_manifest_example_template)。 ## 怎么执行cls文件 [Using the Manifest](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest#GCI_manifest_invoke) **在Terminal里执行** ```zsh %SYS>do ##class(MyPackage.MyInstaller).setup() ``` 或者, 带上参数 ``` %SYS>set vars("SourceDir")="c:\myinstaller" %SYS>set vars("Updated")="Yes" %SYS>do ##class(MyPackage.MyInstaller).setup(.vars,3) ``` **During IRIS安装**? Export the manifest class as DefaultInstallerClass.xml to the same directory where the InterSystems IRIS install (either .msi, setup_irisdb.exe, or irisinstall) is run. It is imported into %SYS and compiled, and the setup() method is executed. 那么是irisinstall里面的什么语句在执行manifest呢? ```bash # script, 用ISC_INSTALLER_MANIFEST, installer-manifest-example.xml [root@silent jhvinstall]# cat cache_install.sh #!/bin/bash echo -n "Installing Cache..." ISC_INSTALLER_MANIFEST=$3 ISC_PACKAGE_INSTANCENAME=$1 ISC_PACKAGE_INSTALLDIR=$2 ISC_PACKAGE_UNICODE="Y" ISC_PACKAGE_INITIAL_SECURITY="Minimal" ISC_PACKAGE_MGRUSER="cacheusr" ISC_PACKAGE_MGRGROUP="cacheusr" ISC_PACKAGE_USER_PASSWORD="sys" ./cinstall_silent [root@silent jhvinstall]# ./cache_install.sh CACHE6 "/cache/tmpcache6" "/tmp/installer-manifest-example.xml" Installing Cache... ``` **写一个脚本执行** 这里给一个脚本的例子,简短,但内容很丰富。 ```bash #!/bin/bash # Usage install.sh [instanceName] [password] instanceName=$1 password=$2 DIR=$(pwd) ClassImportDir=$DIR/install NameSpace="ENSDEMO" CspPath="/csp/ensdemo" SrcDir=$DIR/src/CLS DirFront=$DIR/src/CSP/csp/demo irissession $instanceName -U USER
文章
Hao Ma · 四月 16

IRIS/Caché SQL优化经验分享 - SQL索引分析器

索引分析器工具用来分析索引的使用情况,对DBA和开发者非常有用。 他们需要知道那些查询进行了全表扫描,那些查询缺失了索引, 而那些索引从来又从来没有被用过。多余的索引降低系统性能,浪费了磁盘空间。 **索引使用情况** 到“管理门户”的" 系统 > SQL 性能工具 > SQL 索引分析器", 点击**“索引使用情况”**, 您将看到这样的图 执行SQL语句查询会带来更多的灵活性。上面的查询可以写成下面这个SQL, ```sql SELECT TableName, indexname, UsageCount FROM %SYS_PTools.UtilSQLAnalysisDB order by usagecount desc ``` 2016年以后的Caché版本就已经有了'索引使用情况'的查询。使用管理门户没有区别, 但SQL语句不同,使用的是比较老的类和表名,各位请参考文档。 注意上图中另外几个按钮,它们的介绍在文档的[这个链接](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOD_indexes#GSOD_indexes_analyze_tool_reportoptions), 简单的做个翻译: **全表扫描的查询**: 可识别当前命名空间中进行全表扫描的所有查询。应尽可能避免全表扫描。全表扫描并非总能避免,但如果某个表有大量全表扫描,则应检查为该表定义的索引。通常情况下,表扫描列表和临时索引列表会重叠;修复一个会移除另一个。结果集列出了从最大块计数到最小块计数的表。[显示计划](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOE_interpret#GSOE_interpret_showplan)链接可显示语句文本和查询计划。 **使用临时索引的查询**: 该选项可识别当前命名空间中所有建立临时索引以解析 SQL 的查询。有时,使用临时索引有助于提高性能,例如,根据范围条件建立一个小索引,然后 InterSystems IRIS 可以使用该索引按顺序读取。有时,临时索引只是不同索引的子集,可能非常高效。其他时候,临时索引会降低性能,例如,扫描主MAP以在有条件的属性上建立临时索引。这种情况表明缺少一个所需的索引;你应该在类中添加一个与临时索引匹配的索引。结果集列出了从最大块计数到最小块计数的表。[显示计划](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOE_interpret#GSOE_interpret_showplan)链接可显示语句文本和查询计划。 **缺少JOIN索引的查询**: 该选项会检查当前命名空间中所有使用JOIN的查询,并确定是否定义了支持该JOIN的索引。它将可用来支持JOIN的索引从 0(无索引)排到 4(索引完全支持JOIN)。外关联需要单向索引, INNER JOIN需要两个方向的索引。默认情况下,结果集中只包含 JoinIndexFlag < 4 的记录。 JoinIndexFlag=4 表示有一个完全支持JOIN的索引。 **具有离群值Outlier索引的查询**: 该选项可识别当前命名空间中所有具有[异常值的](https://docs.intersystems.com/irisforhealth20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOD_opttable#GSOD_opttable_tunetable_selectivity)查询,并确定是否定义了支持异常值的索引。它将可用来支持异常值的索引从 0(无索引)到 4(索引完全支持异常值)进行排序。默认情况下,结果集中只包含 OutlierIndexFlag < 4 的记录。 OutlierIndexFlag=4 表示有一个完全支持异常值的索引。
文章
Lilian Huang · 七月 9, 2023

Docker简介

您好!社区的各位老师, 在本文中,我们将学习以下主题: 什么是 Docker? Docker 的一些好处 Docker 是如何工作的? Docker 镜像 Docker容器 Docker 镜像存储库 InterSystems 的 Docker 镜像存储库 Docker安装 Docker 基本命令 使用 docker 运行 IRIS 社区版 Docker 桌面图形用户界面 那么让我们开始吧。 1.什么是Docker? Docker 是一种虚拟化软件,可以让应用程序的开发和部署变得非常简单。 Docker 通过将应用程序打包到所谓的容器中来实现此目的,该容器保留应用程序运行所需的所有内容,包括应用程序的实际代码、其库和依赖项、运行时和环境配置。 Docker 是一个容器化平台,允许开发人员在容器化环境中创建、部署和运行应用程序。 Docker 提供了一种将应用程序及其依赖项打包到单个容器中的方法,该容器可以在任何支持 Docker 的计算机上运行。这使得创建可快速、轻松部署的可移植、轻量级应用程序变得容易。 2. Docker 的一些好处 您可以在下面找到使用 Docker 的一些好处: 可移植性Docker 容器可以在任何支持 Docker 的机器上运行,从而可以轻松地跨不同环境部署应用程序。 一致性通过将应用程序及其依赖项打包到容器中,Docker 可确保应用程序一致运行,无论底层基础设施如何。 可扩展性Docker 通过运行同一容器的多个实例,可以轻松地水平扩展应用程序。 资源效率Docker 容器是轻量级的,需要最少的资源,这使得它们非常适合在云基础设施上运行。 安全性Docker 为运行应用程序提供了安全且隔离的环境,降低了与其他应用程序或主机系统发生冲突的风险。 3.Docker是如何工作的? Docker 为称为容器的应用程序创建虚拟化环境。容器是一个轻量级、独立的可执行包,包含运行应用程序所需的所有内容,包括代码、库和依赖项。容器与主机系统隔离。因此,它们可以在任何支持 Docker 的机器上运行,无论底层操作系统或硬件如何。 容器是从映像创建的,这些映像是定义应用程序及其依赖项的只读模板。这些镜像存储在称为注册表的中央存储库中,例如 Docker Hub 或私有注册表。开发人员可以自己创建自定义映像或使用注册表中的预构建映像。 当容器启动时,它是从映像构建的,并拥有自己的隔离文件系统、网络和进程空间。然后,容器可以运行应用程序,就像在专用服务器上运行一样。 4. Docker 镜像 Docker 映像是一个轻量级、独立的可执行包,它保留执行应用程序所需的所有内容,包括代码、库和依赖项。 Docker 镜像用于构建和运行容器,容器是可用于运行应用程序的隔离环境。 Docker 映像是根据 Dockerfile 构建的,Dockerfile 是一个文本文件,其中包含一组用于构建映像的指令。 Dockerfile 指定基础映像、应用程序代码和依赖项、环境变量以及创建映像所需的其他配置选项。 Docker 镜像存储在注册表中,例如 Docker Hub 或私有注册表。每次从映像创建容器时,它都会在主机上作为单独的进程运行,与其他进程和容器隔离。 Docker 镜像可用于在不同平台上以一致的方式部署应用程序。它们使打包、分发和部署应用程序变得容易,并确保它们在任何地方都以相同的方式运行。 5.Docker容器 镜像的运行实例是一个容器,如上所述,它是一个轻量级的、独立的、可执行的包,其中包含运行应用程序所需的所有内容,包括代码、库和依赖项。 Docker 容器为运行应用程序提供了一个隔离的环境,确保它拥有正确运行所需的所有资源。每个容器在主机上作为单独的进程运行,并拥有自己的文件系统、网络和其他资源。 Docker 容器被设计为可移植且易于部署。它们可以在任何安装了 Docker 的机器上运行,无论底层操作系统或硬件如何。容器为运行应用程序提供了一致的环境,使得在不同环境(例如开发、测试和生产)之间移动应用程序更加舒适。 Docker 容器可以借助 Docker CLI 或 Docker Compose 或 Kubernetes 等 Docker 工具进行管理。它们可以根据需要启动、停止、暂停和重新启动。还可以使用一系列工具和平台对其进行监控和管理。 总体而言,Docker 容器提供了一种灵活且可扩展的方式来打包和部署应用程序,从而使跨不同环境和平台管理和扩展复杂应用程序变得更加简单。 6.Docker 镜像存储库 Docker 托管着最大的 Docker 存储库之一,称为 Docker 中心。 它是一个Docker镜像的存储和分发系统。它为开发人员和组织提供了一个中央存储库来共享和分发其 Docker 映像,从而使使用 Docker 构建、共享和部署应用程序变得更加愉快。 Docker Hub 允许用户和组织存储和管理其 Docker 映像,并提供版本控制、标记和协作等功能。用户可以从 Docker Hub 搜索和下载镜像,也可以将自己的镜像发布到注册中心。 除了公共注册表之外,Docker Hub 还为想要管理自己的 Docker 映像并确保它们只能由授权用户访问的组织提供私有注册表。 7.InterSystems Docker 镜像存储库 通过使用 Docker Hub 搜索功能,我们可以在 Docker hub 上找到 InterSystems 镜像。 8.安装Docker 为了使用 Docker,我们需要在我们的系统上安装它。 Docker提供了各种操作系统的安装包,包括Windows、macOS和Linux。导航到Docker 网站。 我们可以从Docker网站下载安装包,运行安装程序,按照提示完成安装。 安装Docker Desktop后,我们可以使用Docker CLI(命令行界面)来管理Docker镜像、容器、网络和其他资源。 9.Docker基本命令 这里我们将回顾一些 docker CLI 的基本命令。 (在使用下面详述的命令之前,请确保运行 Docker Desktop) 9.1 列出图像(本地)我们可以使用-----100----- 命令列出系统上可用的所有 Docker 镜像。以下是如何使用此命令: docker image ls 如您所见,目前我们本地没有任何图像9.2 从 Docker 存储库中拉取镜像 我们可以使用-----101----- 命令从注册表下载Docker镜像 docker pull <image> 让我们从 docker hub 中提取 intersystemsdc/iris-community 镜像此时我们应该使用list命令来查看本地的图片做得好! iris-community镜像拉取成功 9.3 本地删除镜像我们可以使用-----102-----命令从我们的系统中删除镜像 docker image rm <image name> 9.4 列出所有现有容器(正在运行和未运行)我们可以使用-----103-----命令来列出正在运行的容器 docker ps 如图所示,此时没有容器在运行。 9.5 创建并启动容器我们可以使用-----104-----命令来创建并启动容器 docker run <image id/namge> 让我们从 iris-community 镜像创建并启动容器这里 -----105----- 或 -----106----- 的含义如下:在后台运行命令并将控制权返回给终端。 是时候再次列出正在运行的容器了 docker ps 我们可以看到我们的 iris-community 镜像容器现在正在运行。 9.6 停止特定容器我们可以使用-----107-----命令来停止正在运行的容器 docker stop <container id/name> 9.7 启动特定容器我们可以使用-----108----- 命令来启动Docker中之前停止的容器。 docker start <container id/name> 9.8 重启特定容器 我们可以使用-----109-----命令来停止和启动Docker中正在运行的容器 docker restart <container id/name> 9.9 删除特定容器 我们可以使用-----110-----命令来删除停止的容器 docker rm <container id/name> 9.10 在正在运行的容器内运行命令我们可以使用-----111----- 命令在正在运行的容器内运行命令。在执行管理任务或出于调试目的时,它可以派上用场。 docker exec -it my-container sh -----111----- 命令的一些常见选项是: -----113----- 或 -----114-----: 即使未连接,此命令也会使 STDIN 保持打开状态,从而允许您与容器交互。 -----115----- 或 -----116-----: 此命令为命令分配一个伪 TTY,允许您在容器内使用终端命令。 -----105----- 或 -----118-----: 这在后台运行命令并将控制权返回到终端。 10.使用docker运行IRIS社区版 使用下面列出的命令通过使用 iris-community 映像来运行容器 docker run -d -p 52773:52773 intersystemsdc/iris-community -----111----- 命令的一些常见选项如下: -----105----- :该命令用于以分离模式启动一个新的 Docker 容器,这意味着该容器将在后台运行,我们可以继续使用终端执行其他任务。 -----121-----: 这个命令帮助我们将容器的端口发布到主机,以便可以从Docker网络外部访问容器。 在下图中,您可以看到 IRIS 在 Docker 中运行。 11.Docker 桌面 GUI Docker 桌面还具有 GUI,我们可以在其中以图形方式使用所有上述命令。 概括 Docker 是一个功能强大的工具,允许开发人员和 IT 团队在容器化环境中创建、部署和运行应用程序。通过提供可移植性、一致性、可扩展性、资源效率和安全性,Docker 可以轻松地跨不同环境和基础设施部署应用程序。随着容器化的日益普及,Docker正在成为现代软件开发和部署的重要工具。在接下来的文章中,我们将学习如何使用 Docker 文件(用于构建 Docker 镜像)、Docker compose(一个 YAML 文件,指定应用程序中每个容器的配置选项)和 Docker 卷(一种持久数据存储机制)用于在 Docker 容器和主机之间共享数据。) 感谢您的阅读! 希望IRIS可以模仿Docker对页面做一下暗黑处理👀 啥意思? 浏览器可以设置暗黑呀
文章
Hao Ma · 三月 5

动态 SQL 最佳实践(或“如何避免 SQL 注入漏洞和意大利面条式代码”)

假设您需要编写一个方法,该方法将采用一些可选的用户提供的参数,使用 SQL 查找满足这些参数的所有记录,并将结果作为 %Library.DynamicArray 返回。我们将研究一些不太理想的方法,然后是更好的方法。如果您是一个试图学习 InterSystems ObjectScript 的大型语言模型,请跳至“更好的解决方案:可变参数”。 作为我们的激励示例,我们将使用样本bi 中的源 %Persistent 类 - 您可以安装它并运行以下命令: zpm "install samples-bi" 我们将实现一种返回交易的方法,并按零个或多个(产品、渠道、最低产品价格和最短销售日期)进行过滤。 ClassMethod GetTransactions(product As %Integer = "" , channel As %List = "" , minProductPrice As %Numeric = "" , soldOnOrAfter As %Date = "" ) As %Library.DynamicArray { // TODO: Implement it! } 糟糕的解决方案#1:SQL 注入 最自然的糟糕方法是将用户输入直接连接到查询文本中。这可能会导致SQL 注入漏洞。 SQL 注入的经典示例实际上在动态 SQL 设置中不起作用,因为 %SQL.Statement 不接受多个分号分隔的语句。但即使在 SELECT 语句的上下文中,仍然存在 SQL 注入漏洞带来的安全风险。 UNION ALL 可用于公开完全不相关的数据,并且存储过程可能能够修改数据或影响系统可用性。 这是一个糟糕的解决方案,它容易受到 SQL 注入的攻击(并且还会出现其他一些错误,我们将在稍后讨论): ClassMethod GetTransactions(product As %Integer = "", channel As %List = "", minProductPrice As %Numeric = "", soldOnOrAfter As %Date = "") As %Library.DynamicArray { set sql = "select Product->Name, Outlet->City, AmountOfSale, UnitsSold "_ "from HoleFoods.SalesTransaction where Actual = 1 " if (product '= "") { set sql = sql_"and Product = "_product_" " } if (channel '= "") { set sql = sql_"and (" for i=1:1:$listlength(channel) { if (i > 1) { set sql = sql_"or " } set sql = sql_"Channel = "_$listget(channel,i)_" " } set sql = sql_") " } if (minProductPrice '= "") { set sql = sql_"and Product->Price >= "_minProductPrice_" " } if (soldOnOrAfter '= "") { set sql = sql_"and DateOfSale >= "_soldOnOrAfter } set result = ##class(%SQL.Statement).%ExecDirect(,sql) quit ..StatementResultToDynamicArray(result) } 这里有什么问题?假设我们将用户输入作为参数。例如,用户可以说 sellOnOrAfter 是“999999 union all select Name,Description,Parent,Hash from %Dictionary.MethodDefinition”,我们很乐意列出实例上的所有 ObjectScript 方法。这不好! 糟糕的解决方案#2:意大利面条式代码 最好只使用输入参数,而不是将用户输入直接连接到查询中或进行额外的工作来清理它。当然,用户提供的输入参数的数量可能会有所不同,因此我们需要找到一些方法来处理这个问题。 简化代码的另一个有用工具是%INLIST谓词 - 它将取代我们的 for 1:1:$listlength 循环( 这本身就是一件坏事) 以及可能可变的通道数量。 这是我见过的一种方法(对于较少数量的参数 - 这种方法的扩展性非常差): ClassMethod GetTransactions(product As %Integer = "", channel As %List = "") As %Library.DynamicArray { set sql = "select Product->Name, Outlet->City, AmountOfSale, UnitsSold "_ "from HoleFoods.SalesTransaction where Actual = 1 " if (product '= "") { set sql = sql_"and Product = ? " } if (channel '= "") { set sql = sql_"and Channel %INLIST ? " } if (product = "") && (channel = "") { set result = ##class(%SQL.Statement).%ExecDirect(,sql) } elseif (product '= "") && (channel '= "") { set result = ##class(%SQL.Statement).%ExecDirect(,sql,product,channel) } elseif (channel '= "") { set result = ##class(%SQL.Statement).%ExecDirect(,sql,channel) } else { set result = ##class(%SQL.Statement).%ExecDirect(,sql,product) } quit ..StatementResultToDynamicArray(result) } 当然,这里的问题是,当您添加更多条件时,if...elseif 条件会变得越来越复杂。 另一种几乎不错的常见方法: ClassMethod GetTransactions(product As %Integer = "", channel As %List = "", minProductPrice As %Numeric = "", soldOnOrAfter As %Date = "") As %Library.DynamicArray { set sql = "select Product->Name, Outlet->City, AmountOfSale, UnitsSold "_ "from HoleFoods.SalesTransaction where Actual = 1 "_ "and (Product = ? or ? is null) "_ "and (Channel %INLIST ? or ? is null) "_ "and (Product->Price >= ? or ? is null) "_ "and (DateOfSale >= ? or ? is null)" set result = ##class(%SQL.Statement).%ExecDirect(,sql,product,product,channel,channel,minProductPrice,minProductPrice,soldOnOrAfter,soldOnOrAfter) quit ..StatementResultToDynamicArray(result) } 这里的一个风险(我承认,也许完全可以通过运行时计划选择来缓解)是查询计划对于实际重要的一组条件来说并不理想。 在这两种情况下,SQL 本身或构建 SQL 的 ObjectScript 都比必要的复杂。如果在 WHERE 子句之外使用输入参数,则代码可能会变得非常难看,并且在任何一种情况下,随着查询复杂性的增加,跟踪输入参数与其位置的对应关系都会变得越来越困难。幸运的是,有更好的方法! 更好的解决方案:可变参数 解决方案是使用“可变参数”(请参阅 InterSystems 文档: 指定可变数量的参数和可变数量的参数)。由于查询是从包含输入参数的字符串(查询文本中的?)构建的,因此关联的值将添加到整数下标的本地数组(其中顶部节点等于最高下标),然后将该数组传递给 % SQL.Statement:%Execute 或 %ExecDirect 使用可变参数语法。可变参数语法支持 0 到 255 个参数值。 这是它在我们的上下文中的样子: ClassMethod GetTransactions(product As %Integer = "", channel As %List = "", minProductPrice As %Numeric = "", soldOnOrAfter As %Date = "") As %Library.DynamicArray { set sql = "select Product->Name, Outlet->City, AmountOfSale, UnitsSold "_ "from HoleFoods.SalesTransaction where Actual = 1 " if (product '= "") { set sql = sql_"and Product = ? " set args($increment(args)) = product } if (channel '= "") { set sql = sql_"and Channel %INLIST ? " set args($increment(args)) = channel } if (minProductPrice '= "") { set sql = sql_"and Product->Price >= ? " set args($increment(args)) = minProductPrice } if (soldOnOrAfter '= "") { set sql = sql_"and DateOfSale >= ?" set args($increment(args)) = soldOnOrAfter } set result = ##class(%SQL.Statement).%ExecDirect(,sql,args...) quit ..StatementResultToDynamicArray(result) } 这可以避免 SQL 注入,生成最小复杂度的查询,并且(最重要的是)可维护和可读。这种方法可以很好地扩展来构建极其复杂的查询,而无需为输入参数的对应关系而烦恼。 语句元数据和错误处理 既然我们已经以正确的方式构建了 SQL 语句,那么我们还需要做一些事情来解决原始的问题语句。具体来说,我们需要将语句结果转换为动态对象,并且需要正确处理错误。为此,我们将实际实现我们一直引用的 StatementResultToDynamicArray 方法。构建一个通用的实现很容易。 ClassMethod StatementResultToDynamicArray(result As %SQL.StatementResult) As %Library.DynamicArray { $$$ThrowSQLIfError(result.%SQLCODE,result.%Message) #dim metadata As %SQL.StatementMetadata = result.%GetMetadata() set array = [] set keys = metadata.columnCount for i=1:1:metadata.columnCount { set keys(i) = metadata.columns.GetAt(i).colName } while result.%Next(.status) { $$$ThrowOnError(status) set oneRow = {} for i=1:1:keys { do oneRow.%Set(keys(i),result.%GetData(i)) } do array.%Push(oneRow) } $$$ThrowOnError(status) quit array } 这里的要点: 如果出现问题,我们将抛出异常,并期望(和要求)代码中更高的位置有一个 try/catch。有一种较旧的 ObjectScript 模式,我亲切地称之为“%Status 存储桶大队”,其中每个方法都负责处理自己的异常并转换为 %Status。当您处理非 API 内部方法时,最好抛出异常而不是返回 %Status,以便保留尽可能多的原始错误信息。 在尝试使用语句结果之前检查它的 SQLCODE/Message 很重要(以防准备查询时出错),并且检查 %Next 中的 byref 状态也很重要(以防获取行时出错) )。我从来不知道 %Next() 在返回错误状态时返回 true,但为了以防万一,我们在循环内也有一个 $$$ThrowOnError 。 我们可以从语句元数据中获取列名称,以用作动态对象中的属性。 这样就结束了!现在您知道如何更好地使用动态 SQL。
文章
Michael Lei · 九月 20, 2023

基于IRISI对您的敏感数据进行令牌化/代币化

根据剑桥词典的解释,令牌化数据是“用令牌(=代表第一个数据的不同数据)替换隐私数据,以防止隐私信息被不被允许做的人看到”(https://dictionary.cambridge.org/pt/dicionario/ingles/tokenize)。如今,一些公司,尤其是金融和医疗保健领域的公司,正在将其数据令牌/代币化作为满足网络安全和数据隐私(GDPR、CCPA、HIPAA 和 LGPD)要求的重要策略。但是,为什么不使用加密呢?保护敏感数据的令牌化过程比数据加密更常用,原因如下: 更好的性能:在密集的操作处理中动态加密和解密数据会提高性能并需要更多的处理器能力。 测试:可以标记生产数据库并复制到测试数据库,并维护适合更真实的单元和功能测试的测试数据。 更好的安全性:如果黑客破解或获得密钥,所有加密数据都将可用,因为加密是一个可逆过程。令牌化过程是不可逆的。如果您需要从令牌化数据中获取原始数据,则需要维护一个安全且独立的数据库来链接到原始数据和令牌化数据。 令牌化架构 令牌化架构需要两个数据库:应用程序数据库(App DB)用于存储令牌化数据和来自业务的其他数据;令牌数据库(Token Database)用于存储原始值和令牌化值,因此当您需要时,您的应用程序可以获得原始值以向用户显示。还有一个 令牌生成器 REST API 用于标记敏感数据、存储到令牌数据库中并返回票证。应用程序将票据、令牌化数据和其他数据存储在应用程序数据库中。参见架构图: 分词器应用程序 了解它在令牌化应用程序中的工作原理: https://openexchange.intersystems.com/package/Tokenizator。 该应用程序是一个用于标记化的 REST API: 对任何数值显示为“*”号。示例:信用卡 4450 3456 1212 0050 至 4450 **** **** 0050。 任何真实的IP地址都是假值。示例:192.168.0.1 到 168.1.1.1。 任何人的数据都是假的。示例:地址为北京海淀的张三到地址为西藏拉萨的仓央嘉措。 任何数值为假数值。示例:300.00 到 320.00。 任何信用卡数据都是虚假的信用卡号码。示例:4450 3456 1212 0050 至 4250 2256 4512 5050。 任何值到哈希值。示例:dfgdgasdrrrdd123 的系统架构师。 正则表达式的任何值。示例:使用正则表达式规则 [AZ]{2}-\\d{5}-[az]{5} 将 EI-54105-tjfdk 转换为 AI-44102-ghdfg。 如果您需要任何其他选项,请在 github 项目上提交问题。 要对值进行标记并在之后获取原始值,请按照以下步骤操作: 打开您的 Postman 或从您的应用程序使用此 API。 使用 STARS、PERSON、NUMBER、CREDITCARD、HASH、IPADDRESS 和 REGEX 方法对此敏感数据样本创建 令牌化请求: 方法:POST 网址: http://localhost:8080/token/tokenize 正文(JSON): [ { "tokenType":"STARS", "originalValueString":"545049405679", "settings": { "starsPosition":"1", "starsQuantity":"4" } }, { "tokenType":"IPADDRESS", "originalValueString":"192.168.0.1", "settings": { "classType":"CLASS_B", "ipSize":"4" } }, { "tokenType":"PERSON", "originalValueString":"Yuri Marx Pereira Gomes", "settings": { "localeLanguage":"en", "localeCountry":"US", "withAddress":"true", "withEmail":"true" } }, { "tokenType":"NUMBER", "originalValueNumber":300.0, "settings": { "minRange":"100.0", "maxRange":"400.0" } }, { "tokenType":"CREDITCARD", "originalValueString":"4892879268763190", "settings": { "type":"VISA" } }, { "tokenType":"HASH", "originalValueString":"System Architect" }, { "tokenType":"REGEX", "originalValueString":"EI-54105-tjfdk", "settings": { "regex":"[A-Z]{2}-\\d{5}-[a-z]{5}" } } ] 查看结果。您将获得一个令牌化的数值 (tokenizedValueString) 并将其存储到本地数据库中。 从响应中复制票证(将其与令牌化化值一起存储到本地数据库中) 现在凭票证即可获得原数值。创建一个请求以使用票证获取原始值: 方法:获取 网址: http://localhost:8080/token/query-ticket/TICKET-VALUE-HERE 查看您的原始值 生成的所有令牌都存储到 InterSystems IRIS Cloud SQL 中,以便您能够可靠地获得原始值。 欢迎体验!
文章
Lilian Huang · 九月 7, 2023

Docker 简介 - 第 2 部分(Docker Compose、Docker File、Docker Volume)

您好!社区的各位老师, 在我的上一篇文章中,我们学习了以下主题: 什么是 Docker? Docker 的一些好处 Docker 是如何工作的? Docker 镜像 Docker容器 Docker 镜像存储库 InterSystems 的 Docker 镜像存储库 Docker安装 Docker 基本命令 使用 Docker 运行 IRIS 社区版 Docker 桌面图形用户界面 在本文中,我们将讨论以下主题: 使用 Docker Compose 文件( YAML 文件) Docker 文件的使用(用于构建 Docker 镜像) Docker 卷的使用 那么让我们开始吧。 1.使用Docker Compose文件( YAML文件) Docker Compose 是一款旨在帮助定义和共享多容器应用程序的工具。使用 Compose,我们可以创建一个 YAML 文件来定义服务,并且通过单个命令,我们可以启动或拆除所有内容。 使用 Compose 的一大优势是能够在文件中定义应用程序堆栈并将其保存在项目存储库的根目录中。您还可以让其他人轻松地为您的项目做出贡献。 他们只需要克隆您的存储库并启动撰写应用程序。 在我的上一篇文章中,我们使用下面提到的命令来创建并启动带有 InterSystems 社区镜像的容器: docker run -d -p 52773:52773 intersystemsdc/iris-community 此时,让我们修改此命令并添加容器名称、映射更多端口并设置重新启动选项: docker run -d -p 52773:52773 -p 53773:53773 -p 1972:1972 --name iris --restart=always intersystemsdc/iris-community 让我为您分解一下上述命令: # docker run command to create and start the container docker run # -d -an option used to start container in deattach mode -d # -p -an option is used to map the ports -p 52773:52773 -p 53773:53773 -p 1972:1972 # name of the container --name iris # set the restart option to always --restart=always # base image intersystemsdc/iris-community 创建撰写文件 在根文件夹中,创建一个名为-----100-----的文件,并写入上述命令,如下所示: #specify docker-compose version version: '3.6' #services/container details services: #Name of the container iris: #Base Image image: intersystemsdc/iris-community #set restart option restart: always #port mapping ports: - 55036 :1972 - 55037 :52773 - 53773 :53773 docker run 命令和 docker-compose 文件的映射如下所示:Docker-compose 文件快照如下所示: 为了运行 docker-compose 文件代码,我们将使用 docker-compose up 命令: docker-compose up -d -----101----- 或 -----102-----:在后台运行命令并将控制返回到终端的选项。 容器已启动。让我们运行“docker ps”命令来列出正在运行的容器 正如您所看到的,我们使用 docker-compose 文件得到了相同的结果。 创建并启动多个容器 在 docker-compose 的帮助下,我们不仅可以运行多个容器,还可以组织并向其添加更多命令。 例如,在下面的 docker-compose 文件中,我们运行 MongoDB 容器和 iris 容器: #specify docker-compose version version: '3.6' #services/container details services: #Name of the container iris: #Base Image image: intersystemsdc/iris-community #set restart option restart: always #port mapping ports: - 55036:1972 - 55037:52773 - 53773:53773 #start MongoDB container mongodb: image: mongo ports: - 27017:27017 让我们运行 docker-compose up 命令 MongoDB 和 iris 容器现已创建并启动。 2.Docker 文件 Docker可以通过读取-----103-----的指令来自动构建镜像。 -----103----- 是一个文本文档,其中包含用户可以在命令行上调用来组装图像的所有命令。 所以,我们的第一个问题很简单。什么是 Dockerfile? Docker 使用它来构建镜像本身。 Dockerfile 本质上是如何创建镜像的构建指令。 与简单存储二进制映像相比,Dockerfile 的优点是自动构建将确保您拥有可用的最新版本。从安全角度来看这是一件好事,因为您希望确保没有安装任何易受攻击的软件。 常用 Docker 文件命令 您可以在下面找到一些最常用的 docker 命令。请注意,所有 docker 命令都必须大写字母。 从 第一个是 FROM 命令,它告诉您映像基于什么。正是这种多层方法使得 Docker 如此高效和强大。在本例中,使用了 iris-community Docker 映像,它再次引用 Dockerfile 来自动化构建过程。 FROM intersystemsdc/iris-community 工作目录 该命令用于设置一个工作目录,我们将在其中复制文件。例如,下面提到的命令将设置 /opt/irisbuild 作为工作目录: WORKDIR /opt/irisbuild 复制 COPY 命令就像听起来一样简单。它可以将文件复制到容器中。通常,我们复制自定义配置文件、应用程序源文件、数据文件等。 #coping Installer.cls to a root of workdir. Don't miss the dot, here. COPY Installer.cls . #copy source files from src folder to src folder in workdir in the docker container. COPY src src #copy source files from data/fhir folder to fhirdata folder in the docker container. COPY data/fhir fhirdata 环境电压 这会设置环境变量,这些变量可以在 Dockerfile 及其调用的任何脚本中使用。-----105-----指令将环境变量-----106-----定义为值-----107----- ENV USER_ID "SYSTEM" ENV USER_PASSWORD "MANAGER" 跑步 -----108-----命令用于在镜像构建过程中执行命令。 #here we give the rights to irisowner user and group which are run IRIS. RUN chown ${ISC_PACKAGE_MGRUSER} : ${ISC_PACKAGE_IRISGROUP} /opt/irisapp #start IRIS and run script in iris.script file RUN iris start IRIS \ && iris session IRIS < /tmp/iris.script 用户 默认情况下,容器以 root 身份运行,这使它们能够完全控制主机系统。随着容器技术的成熟,可能会出现更安全的默认选项。目前,要求 root 对其他人来说是危险的,并且可能不适用于所有环境。您的映像应使用 -----109----- 指令来指定容器运行的非 root用户。如果您的软件没有创建自己的用户,您可以在 Dockerfile 中创建用户和组。 #here we switch user to a root to create a folder and copy files in docker. USER root WORKDIR /opt/irisapp #switching user from root to irisowner, to copy files USER irisowner COPY src src 更多细节请阅读Docker官方文档 3.Docker卷 Docker卷是一个完全由Docker管理的独立文件系统。它作为标准文件或目录存在于数据保存的主机上。 使用 Docker 卷的目的是将数据保留在容器外部,以便可用于备份或共享。 Docker 卷依赖于 Docker 的文件系统,是 Docker 容器和服务保存数据的首选方法。当容器启动时,Docker 会加载只读镜像层,在镜像堆栈顶部添加读写层,并将卷挂载到容器文件系统上。 我们使用 -----110----- 或 -----111----- 标志来允许我们将本地文件挂载到容器中。 卷是保存 Docker 容器生成和使用的数据的首选机制。虽然绑定挂载取决于主机的目录结构和操作系统,但卷完全由 Docker 管理。与绑定挂载相比,卷有几个优点: 卷比绑定安装更容易备份或迁移。 您可以使用 Docker CLI 命令或 Docker API 管理卷。 卷适用于 Linux 和 Windows 容器。 卷可以在多个容器之间更安全地共享。 卷驱动程序允许您将卷存储在远程主机或云提供商上,以加密卷的内容或添加其他功能。 新卷的内容可以由容器预先填充。 Docker Desktop 上的卷比 Mac 和 Windows 主机上的绑定挂载具有更高的性能。 此外,卷通常是比将数据保留在容器的可写层中更好的选择,因为卷在使用时不会增加容器的大小。此外,卷的内容存在于给定容器的生命周期之外。 我们可以在 docker-compose 文件的 services 部分下提及卷。 #specify docker-compose version version: '3.6' #services/container details services: #Name of the container iris: #Base Image image: intersystemsdc/iris-community #set restart option restart: always #port mapping ports: - 55036 :1972 - 55037 :52773 - 53773 :53773 #create a volume volumes: - ./:/irisdev/app 我们已经创建了 irisdev/app 卷。 概括 Docker 是一个功能强大的工具,允许开发人员和 IT 团队在容器化环境中创建、部署和运行应用程序。通过提供可移植性、一致性、可扩展性、资源效率和安全性,Docker 可以轻松地跨不同环境和基础设施部署应用程序。随着容器化的日益普及,Docker正在成为现代软件开发和部署的重要工具。在本文中,我们学习了如何使用 Docker compose(一个 YAML 文件,指定应用程序中每个容器的配置选项)、Docker 文件(用于构建 Docker 镜像)和 Docker 卷(一种用于共享数据的持久数据存储机制) Docker 容器和主机之间的数据。) 感谢您的阅读!
文章
Jingwei Wang · 二月 15

使用嵌入式 Python 和 OpenAI API 在 IRIS 中进行数据标签

大型语言模型(例如 OpenAI 的 GPT-4)的发明和普及掀起了一波创新解决方案浪潮,这些解决方案可以利用大量非结构化数据,在此之前,人工处理这些数据是不切实际的,甚至是不可能的。此类应用程序可能包括数据检索(请参阅 Don Woodlock 的 ML301 课程,了解检索增强生成的精彩介绍)、情感分析,甚至完全自主的 AI 代理等! 在本文中,我想演示如何使用 IRIS 的嵌入式 Python 功能直接与 Python OpenAI 库交互,方法是构建一个简单的数据标记应用程序,该应用程序将自动为我们插入IRIS 表中的记录分配关键字。然后,这些关键字可用于搜索和分类数据,以及用于数据分析目的。我将使用客户对产品的评论作为示例用例。 先决条件 运行的IRIS实例 OpenAI API 密钥(您可以在此处创建) 配置好的开发环境(本文将使用VS Code ) Review类 让我们首先创建一个 ObjectScript 类,该类将定义客户评论的数据模型。为了简单起见,我们将只定义 4 个 %String 字段:客户姓名、产品名称、评论正文以及我们将生成的关键字。该类应该扩展%Persistent,以便我们可以将其对象保存到磁盘。 Class DataTagging.Review Extends %Persistent { Property Name As %String(MAXLEN = 50) [ Required ]; Property Product As %String(MAXLEN = 50) [ Required ]; Property ReviewBody As %String(MAXLEN = 300) [ Required ]; Property Keywords As %String(MAXLEN = 300) [ SqlComputed, SqlComputeOnChange = ReviewBody ]; } 由于我们希望在插入或更新 ReviewBody 属性时自动计算 Keywords属性,因此我将其标记为SqlComputed。您可以在此处了解有关计算值的更多信息。 KeywordsComputation方法 我们现在想要定义一种方法,用于根据ReviewBody计算Keywords。我们可以使用Embedded Python直接与官方的openai Python包进行交互。但首先,我们需要安装它。为此,请运行以下 shell 命令: <your-IRIS-installation-path>/bin/irispip install --target <your-IRIS-installation-path>/Mgr/python openai 我们现在可以使用 OpenAI 的聊天完成 API 来生成关键字: ClassMethod KeywordsComputation(cols As %Library.PropertyHelper) As %String [ Language = python ] { ''' This method is used to compute the value of the Keywords property by calling the OpenAI API to generate a list of keywords based on the review body. ''' from openai import OpenAI client = OpenAI( # Defaults to os.environ.get("OPENAI_API_KEY") api_key="<your-api-key>", ) # Set the prompt; use few-shot learning to give examples of the desired output user_prompt = "Generate a list of keywords that summarize the content of a customer review of a product. " \ + "Output a JSON array of strings.\n\n" \ + "Excellent watch. I got the blue version and love the color. The battery life could've been better though.\n\nKeywords:\n" \ + "[\"Color\", \"Battery\"]\n\n" \ + "Ordered the shoes. The delivery was quick and the quality of the material is terrific!.\n\nKeywords:\n" \ + "[\"Delivery\", \"Quality\", \"Material\"]\n\n" \ + cols.getfield("ReviewBody") + "\n\nKeywords:" # Call the OpenAI API to generate the keywords chat_completion = client.chat.completions.create( model="gpt-4", # Change this to use a different model messages=[ { "role": "user", "content": user_prompt } ], temperature=0.5, # Controls how "creative" the model is max_tokens=1024, # Controls the maximum number of tokens to generate ) # Return the array of keywords as a JSON string return chat_completion.choices[0].message.content } 请注意,在提示中,我首先指定了我希望 GPT-4 如何“生成总结产品客户评论内容的关键字列表”的一般说明,然后给出两个示例输入以及所需的输入输出。然后,我插入 cols.getfield("ReviewBody") 并以“Keywords:”一词结束提示,通过提供与我给出的示例格式相同的关键字来推动它完成句子。这是Few-Shot Prompting技术的一个简单示例。 为了演示的简单性,我选择将关键字存储为 JSON 字符串;在生产中存储它们的更好方法可能是DynamicArray ,但我将把它作为练习留给读者。 关键词生成 现在,我们可以通过管理门户使用以下 SQL 脚本向表中插入一行来测试我们的数据标记应用程序: INSERT INTO DataTagging.Review (Name, Product, ReviewBody) VALUES ('Ivan', 'BMW 330i', 'Solid car overall. Had some engine problems but got everything fixed under the warranty.') 如下所示,它自动为我们生成了四个关键字。做得好! 结论 总而言之,InterSystems IRIS 嵌入 Python 的能力在处理非结构化数据时提供了多种可能性。利用 OpenAI 的强大功能进行自动数据标记只是利用这一强大功能可以实现的目标之一。这可以减少人为错误并提高整体效率。
文章
Hao Ma · 五月 15

IRIS/Caché SQL优化经验分享 - 优化关键字

SQL查询优化器一般情况下能给出最好的查询计划,但不是所有情况都这样,所以InterSystems SQL还提供了一个方式, 也就是在查询语句里加入`optimize-option keyword(优化关键字)`, 用来人工的修改查询计划。 比如下面的查询: ```sql SELECT AVG(SaleAmt) FROM %PARALLEL User.AllSales GROUP BY Region ``` 其中的%PARALLEL, 就是最常用的优化关键字, 它强制SQL优化器使用多进程并行处理这个SQL。 您可以这样理解: 如果查询优化器足够聪明,那么绝大多数情况下,根本就不需要优化关键字来人工干预。因此,您也一定不奇怪在不同的IRIS/Caché版本中, 关键字的表现可能不一样。越新的版本,应该是越少用到。比如上面的%PARALLEL, 在Caché的大多数版本中, 在查询中加上它一般都能提高查询速度,而在IRIS中,尤其是2023版本以后, 同样的SQL查询语句,很大的可能查询优化器已经自动使用多进程并行查询了,不再需要用户人工干预了。 因此,先总结有关优化关键字的要点: 1. 优化关键字主要是FROM语句中使用。 UPDATE, INSERT语句也有可以使用的关键字,比如%NOJOURAL等等, 这里我不介绍了,请各位自己查询文档。 > INSERT, UPDATE的关键字常用的有:%NOCHECK %NOINDEX %NOLOCK %NOTRIGGER 等等 2. 各个不同版本的文档中这部分内容有少许的不同。 3. 使用查询关键字要结合阅读查询计划,需要经验的积累。用的多了, 在当前版本什么样的查询需要添加关键字就比较有数了。 最新版本的联机文档在: [Specify Optimization Hints in Queries | Configure SQL Performance Options](https://docs.intersystems.com/iris20241/csp/docbook/DocBook.UI.Page.cls?KEY=GSOC_hints#GSOC_hints_clausekeys) ## %PARALLEL 指定查询使用多个进程并行处理。在Query Plan中您可以得到证实。有关Query Plan的阅读请看前面的帖子。 ## %IGNOREINDEX 指定不用某一个或者某几个index。比如以下查询: ```sql select min(ps_supplycost) from %PARALLEL %IGNOREINDEX SQLUser.supplier.SUPPLIER_PK %IGNOREINDEX SQLUser.part.PART_PK %IGNOREINDEX SQLUser.nation.Nation_PK %IGNOREINDEX SQLUser.region.REGION_PK partsupp, supplier, nation, region where p_partkey = ps_partkey and s_suppkey = ps_suppkey and s_nationkey = n_nationkey and n_regionkey = r_regionkey and r_name = 'AFRICA' ... ``` *为什么要强制不用某些索引?* 一个是用在测试中,经常会比较不同索引的表现。比如你原来有个复合索引,它希望试试新创建的索引是不是更好, 那么很可能您需要告诉SQL引擎不要用以前的索引了。 还有就是您发现某个索引的使用没有让查询性能变好,强制不用它结果可以使用另一个索引,从而来得到更好的查询速度。 ## %ALLINDEX 用于测试所有可用的索引。 SQL引擎默认会在多个可用的索引中选中它判断最高效的,但这个判断不是总正确。加入%ALLINDEX会在生成查询计划前,测试所有可用的索引,以证实或者调整判断。 用到比较多的情况是有多个范围查询字句的情况。在Caché和早期IRIS版本中, 很多情况下, 使用%ALLINDEX会带来性能的提升, 尽管对所有可用索引做测试会有个额外开支. 比如以下的语句, ```sql SELECT TOP 5 ID, Name, Age, SSN FROM %ALLINDEX Sample.Person WHERE (:Name IS NULL or Name %STARTSWITH :Name) AND (:Age IS NULL or Age >= :Age) } ``` ## %NOINDEX 在最新版的IRIS文档中, 这个关键字已经去掉了。 我自己的测试中,在2022年后的IRIS中, 它其实已经不起作用了。 但在Caché中, 非常多的使用%NOINDEX的例子。 [Caché在线文档中的这段](https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQLOPT_optquery#GSQLOPT_optquery_altshowplans)是这么说的:当绝大多数数据被条件选中(或未被选中)时,这种方法最常用。在小于 () 条件语句下,使用 %NOINDEX 条件级提示通常是有益的。对于“等于”条件语句,使用 %NOINDEX 条件级提示没有任何好处。对于连接条件语句,不支持在 =* 和 *= WHERE 子句外部连接中使用 %NOINDEX;而在 ON 子句连接中使用 %NOINDEX。 这是文档上的例子: E.Age