搜索​​​​

清除过滤器
文章
姚 鑫 · 二月 9, 2021

第二十九章 Caché 变量大全 $ZERROR 变量

# 第二十九章 Caché 变量大全 $ZERROR 变量 包含上一个错误的名称和位置。 # 大纲 ``` $ZERROR $ZE ``` # 描述 `$ZERROR`包含最新错误的名称,最新错误的位置(在适用的情况下)以及(对于某些错误代码而言)有关导致错误的原因的其他信息。 `$ZERROR`始终包含相应语言模式的最新错误。 `$ZERROR`值旨在错误后立即使用。由于`$ZERROR`值可能不会在例程调用中保留,因此希望保留`$ZERROR`值以供以后使用的用户应将其复制到变量中。**强烈建议用户在使用后立即将`$ZERROR`设置为空字符串(“”)。** $ZERROR中包含的字符串可以是以下任何一种形式: ```java entryref info entryref info ``` - `` 错误名称。错误名称始终以全部大写字母返回,并用尖括号括起来。它可能包含空格。 - `entryref` 对发生错误的代码行的引用。它由标签名称和距该标签的行偏移量组成,后跟`^`和程序名称。此`entryre`f紧跟在错误名称的右尖括号之后。从终端调用`$ZERROR`时,此`entryref`信息没有意义,因此不会返回。对最近使用`ZLOAD`加载到例程缓冲区中的例程的引用。 - `info` 特定于某些错误类型的附加信息(见下表)。此信息与``或`entryref`之间用空格分隔。如果有多个组件要提供信息,则用逗号分隔。 例如,一个程序(名为`zerrortest`)包含以下例程(名为`ZerrorMain`),该例程试图写入`fred`(一个未定义的局部变量)的内容: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR() ClassMethod ZERROR() { ZerrorMain TRY { SET $ZERROR="" WRITE "$ZERROR = ",$ZERROR,! WRITE fred } CATCH { WRITE "$ZERROR = ",$ZCVT($ZERROR,"O","HTML") } } ``` ```java DHC-APP> d ##class(PHA.TEST.SpecialVariables).ZERROR() $ZERROR = $ZERROR = <UNDEFINED>zZERROR+5^PHA.TEST.SpecialVariables.1 *fred ``` 在上面的示例中,第一个`$ZERROR`包含一个空字符串(`“”`),因为自从`$ZERROR`重置为空字符串以来没有发生任何错误。尝试写入未定义的变量会设置`$ZERROR`并将其抛给`CATCH`块。此`$ZERROR`包含`ZerrorMain+4^zerrortest*fred`,指定错误的名称、位置和特定于该类型错误的附加信息。在本例中,附加信息是未定义的局部变量`fred`的名称;星号前缀表示它是局部变量。(请注意,本例中使用`$ZCVT($ZERROR,“O”,“HTML”)`,因为Caché错误名称用尖括号括起来,并且本例从Web浏览器运行。) `Entryref`可能如下所示: - `ZerrorMain+4^zerrortest`--程序`zerrortest`中标签`ZerrorMain`的4行偏移量 - `ZerrorMain^zerrortest`--在程序`zerrortest`中没有与标签`ZerrorMain`的偏移量;标签行中出现错误 - `+3^zerrortest`--从程序`zerrortest`开始的3行偏移量;错误行前面没有标签 `$ZERROR`值的最大长度为512个字符。超过该长度的值将被截断为512个字符。 ## AsSystemError() Method `%Exception.SystemException`类的`AsSystemError()`方法返回与`$ZERROR`相同的值。下面的示例显示了这一点: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR1() ClassMethod ZERROR1() { TRY { KILL mylocal WRITE mylocal } CATCH myerr { WRITE "AsSystemError is: ",myerr.AsSystemError(),! WRITE "$ZERROR is: ",$ZERROR } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR1() AsSystemError is: zZERROR1+3^PHA.TEST.SpecialVariables.1 *mylocal $ZERROR is: zZERROR1+3^PHA.TEST.SpecialVariables.1 *mylocal ``` **在`Try/Catch`异常处理块结构中,`AsSystemError()`比`$ZERROR`更可取,因为`$ZERROR`可能会被异常处理期间发生的错误覆盖。** ## 有关某些错误的其他信息 当发生某些类型的错误时,`$ZERROR`将以以下格式返回错误: ```java entryref info ``` `INFO`组件包含有关错误原因的附加信息。下表列出了错误列表,其中包括附加信息和该信息的格式。错误代码与`INFO`组件之间用空格字符分隔。 错误代码 |信息组件 ---|--- `` | 未定义变量的名称(包括使用的任何下标)。这可以是局部变量、进程私有全局属性、全局属性或多维类属性。局部变量名称以星号作为前缀。多维属性名以句点开头,以区别于本地变量名。通过设置`%SYSTEM.Process.Unfined()`方法,可以更改Caché行为,以便在引用未定义的变量时不会生成``错误。 `` | 错误的下标引用:生成错误的行引用(例程和行偏移)、下标变量以及错误的下标级别。对于结构化系统变量(SSVN),仅提供行引用(例程和行偏移量)。通过设置`%SYSTEM.Process.NullSubscript()`方法,可以更改默认行为,以便在引用字符串下标为空的全局变量时不会生成错误。局部变量不允许使用空字符串下标。 `` |前缀为星号,即引用的例程名称。 `` | 前缀为星号,即引用的类名。 `` | 前缀为星号(引用属性的名称),后跟逗号分隔符和应该在其中的类名。 `` |前缀是星号,即调用的方法的名称,后跟逗号分隔符和应该在其中的类名。 `` | 全局引用的名称和包含全局引用的目录的名称,用逗号分隔。 `` |前缀为星号、对象名称,后跟`DisplayString()`方法返回的值。 `` | 当不在事务中调用`TCOMMIT`时,`INFO`组件为`*NoTransaction`。当调用不返回值的用户定义函数时,`INFO`组件是一条消息,其中包含本应返回值的命令的位置。 `` |以星号为前缀的无效目录的完整路径名。 `` | 当``错误终止进程时,带有附加信息的``错误将作为消息写入`mgr/cconsole.log`。信息性消息显示已终止进程的进程ID(PID)和产生错误的行引用(例程和行偏移量)。例如:`(PID)0at+13^|“user\|mytest` 例程(或方法)本地变量的名称以及未定义例程、类、属性和方法的名称都以星号(`*`)为前缀。进程-专用全局变量由其`^||`前缀标识。全局变量由它们的`^`(插入符号)前缀标识。类名以其`%`前缀形式表示。 以下示例显示了指定错误原因的其他错误信息。在每种情况下,指定的项都不存在。请注意,生成的错误的`INFO`组件与错误名称之间用空格分隔。星号(`*`)表示局部变量、类、属性或方法。插入符号(`^`)表示全局,`^||`表示进程私有全局。 ``错误示例: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR2() ClassMethod ZERROR2() { UndefTest ; SET $NAMESPACE="SAMPLES" KILL x,abc(2) KILL ^xyz(1,1),^|"USER"|xyz(1,2) KILL ^||ppg(1),^||ppg(2) TRY { WRITE x } // 未定义的局部变量 CATCH { WRITE $ZERROR,! } TRY { WRITE abc(2) } // 未定义的下标局部变量 CATCH { WRITE $ZERROR,! } TRY { WRITE ^xyz(1,1) } // 未定义的全局变量 CATCH { WRITE $ZERROR,! } TRY { WRITE ^|"USER"|xyz(1,2) } // 另一个命名空间中未定义的全局变量 CATCH { WRITE $ZERROR,! } TRY { WRITE ^||ppg(1) } // 未定义的进程专用全局变量 CATCH { WRITE $ZERROR,! } TRY { WRITE ^|"^"|ppg(2) } // 未定义的进程专用全局变量 CATCH { WRITE $ZERROR,! } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR2() zZERROR2+7^PHA.TEST.SpecialVariables.1 *x zZERROR2+13^PHA.TEST.SpecialVariables.1 *abc(2) zZERROR2+19^PHA.TEST.SpecialVariables.1 ^xyz(1,1) zZERROR2+25^PHA.TEST.SpecialVariables.1 ^xyz(1,2) zZERROR2+31^PHA.TEST.SpecialVariables.1 ^||ppg(1) zZERROR2+37^PHA.TEST.SpecialVariables.1 ^||ppg(2) ``` ``错误的示例: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR3() ClassMethod ZERROR3() { SubscriptTest ; DO $SYSTEM.Process.NullSubscripts(0) KILL abc,xyz TRY { SET abc(1,2,3,"")=123 } CATCH { WRITE $ZERROR,! } TRY { SET xyz(1,$JUSTIFY(1,1000))=1 } CATCH { WRITE $ZERROR,! } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR3() zZERROR3+5^PHA.TEST.SpecialVariables.1 *abc() Subscript 4 is "" zZERROR3+11^PHA.TEST.SpecialVariables.1 *xyz() Subscript 2 > 511 chars ``` ``错误的示例: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR4() ClassMethod ZERROR4() { NoRoutineTest ; KILL ^NotThere TRY { DO ^NotThere } CATCH { WRITE $ZERROR,! } TRY { JOB ^NotThere } CATCH { WRITE $ZERROR,! } TRY { GOTO ^NotThere } CATCH { WRITE $ZERROR,! } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR4() zZERROR4+4^PHA.TEST.SpecialVariables.1 *NotThere zZERROR4+10^PHA.TEST.SpecialVariables.1 *NotThere zZERROR4+16^PHA.TEST.SpecialVariables.1 *NotThere ``` 对象错误的示例: ```java DHC-APP>DO $SYSTEM.SQL.MyMethod() DO $SYSTEM.SQL.MyMethod() ^ *MyMethod,%SYSTEM.SQL DHC-APP>WRITE $SYSTEM.XXQL.MyMethod() WRITE $SYSTEM.XXQL.MyMethod() ^ *%SYSTEM.XXQL DHC-APP>SET x=##class(%SQL.Statement).%New() DHC-APP>WRITE x.MyProp WRITE x.MyProp ^ *MyProp,%SQL.Statement ``` ``错误的示例(在Windows上): ```java // 用户没有%SYS名称空间的访问权限 SET x=^|"%SYS"|var ^var,c:\intersystems\cache\mgr\ ``` 调用用户定义函数时的``错误示例。在本例中,`MyFunc Quit`命令不返回值。这将生成一个``错误,其中`entryref`指定`$$MyFunc`调用的位置,`INFO`消息指定`QUIT`命令的位置: ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR5() ClassMethod ZERROR5() { Main TRY { KILL x SET x=$$MyFunc(7,10) WRITE "returned value is ",x,! RETURN } CATCH { WRITE "$ZERROR = ",$ZCVT($ZERROR,"O","HTML"),! } MyFunc(a,b) SET c=a+b QUIT } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR5() $ZERROR = <COMMAND>zZERROR5+4^PHA.TEST.SpecialVariables.1 *Function must return a value at zZERROR5+13^PHA.TEST.SpecialVariables.1 ``` 使用`PUBLIC`关键字将函数作为过程调用时,出现相同的``错误: ```java Main TRY { KILL x SET x=$$MyFunc(7,10) WRITE "returned value is ",x,! RETURN } CATCH { WRITE "$ZERROR = ",$ZCVT($ZERROR,"O","HTML"),! } MyFunc(a,b) PUBLIC { SET c=a+b QUIT } ``` ``错误示例(在Windows上): ```java /// d ##class(PHA.TEST.SpecialVariables).ZERROR6() ClassMethod ZERROR6() { TRY { SET prev=$SYSTEM.Process.CurrentDirectory("bogusdir") WRITE "previous directory: ",prev,! RETURN } CATCH { WRITE "$ZERROR = ",$ZCVT($ZERROR,"O","HTML"),! QUIT } } ``` ```java DHC-APP>d ##class(PHA.TEST.SpecialVariables).ZERROR6() $ZERROR = <DIRECTORY>zCurrentDirectory+2^%SYSTEM.Process.1 *e:\dthealth\db\dthis\data\bogusdir\ ``` ## 5.1版本之前的错误处理代码 在Caché5.1和后续版本的这些错误代码中添加`INFO`组件的结果是,假设`$ZERROR`中的字符串格式的5.1版本之前的错误处理例程可能需要重新设计才能像以前一样工作。例如,以下内容在5.1版中将不再有效: ```java WRITE "Error line: ", $PIECE($ZERROR, ">", 2) ``` 并应更改为类似以下内容: ```java WRITE "Error line: ", $PIECE($PIECE($ZERROR, ">", 2), " ", 1) ``` # 注意 ## ZLOAD和错误消息 在`ZLOAD`操作之后,加载到例程缓冲区中的例程的名称出现在后续错误消息的`entryref`部分。这将在整个过程中持续存在,或者直到使用`ZREMOVE`删除,或者被另一个`ZLOAD`删除或替换。以下终端示例显示例程缓冲区内容的此显示: ```java SAMPLES>ZLOAD Sample.Person.1 SAMPLES>WRITE 6/0 ^Sample.Person.1 SAMPLES>WRITE fred ^Sample.Person.1 *fred SAMPLES>WRITE ^fred ^Sample.Person.1 ^fred SAMPLES>ZNAME "USER" USER>WRITE 7/0 ^Sample.Person.1 USER>ZREMOVE USER>WRITE ^fred ^fred ``` ## $ZERROR和程序栈 `$ZERROR`字符串的``部分包含最新的错误消息。`$ZERROR`字符串的`entryref`部分的内容反映了最近错误的堆栈级别。以下终端会话试图调用无意义的命令`gobbledegook`,导致``错误。它还运行`ZerrorMain`(上面指定),产生`$ZERROR`值``。此终端会话期间的后续`$ZERROR`值反映了此程序调用,如下所示: ```java SAMPLES>gobbledegook SAMPLES>WRITE $ZERROR SAMPLES>DO ^zerrortest SAMPLES>WRITE $ZERROR ZerrorMain+2^zerrortest *FRED SAMPLES 2d0>gobbledegook SAMPLES 2d0>WRITE $ZERROR ^zerrortest SAMPLES 2d0>QUIT SAMPLES>WRITE $ZERROR ^zerrortest SAMPLES>gobbledegook SAMPLES>WRITE $ZERROR ``` ## 设置`$ZTRAP`时的`$ZERROR`操作 发生错误并设置`$ZTRAP`时,Caché在`$ZERROR`中返回错误消息,并分支到为`$ZTRAP`指定的错误陷阱处理程序 ## 设置`$ZERROR` 只有在Caché模式下,才能使用`set`命令将`$ZERROR`设置为最多512个字符的值。长度超过512个字符的值将被截断为512。 **强烈建议在错误处理后将`$ZERROR`重置为空字符串(`“”`)。**
文章
Hao Ma · 五月 26, 2023

IRIS镜像配置(4)_配置后的步骤

题外话:我刚刚翻译了InterSystems专家Bob Binstock的[Caché Mirroring 101:简要指南和常见问题解答](https://cn.community.intersystems.com/post/cach%C3%A9-mirroring-101%EF%BC%9A%E7%AE%80%E8%A6%81%E6%8C%87%E5%8D%97%E5%92%8C%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E8%A7%A3%E7%AD%94)。 尽管题目是Caché Mirror 101, 而且是写于2016年,但因为讲解的都是Mirror的基本原理,所以在大量使用IRIS的今天也完全适用。 前面的3篇文章,包括了配置Mirror的各个方面。如果您照着操作,现在已经有了一个工作的mirror环境,并加入了您的数据库。然而,还没完,这篇我来讨论一下后面的工作,首先的问题是: **Mirror不复制什么** 简单说,Caché/IRIS镜像是**数据库复制(Database Replication)**。在Caché/IRIS里什么是数据库?也就是**Cache.dat和iris.dat**文件。数据库的修改日志,也就是journal,从主机被传送到其他镜像成员。而除此之外的内容,需要维护人员来分别的个个处理, 解决这些内容在各个镜像成员间的拷贝。需要很多的计划和细心。 >系统数据库, 包括IRISSYS, IRISTEMP, IRISLIB等等, 这些Caché/IRIS本身的数据库不应该被加入Mirror,在大多数Caché/IRIS版本里也都设置成不可以加入入MIRROR。 > >例外的HealthCare产品, HSSYS需要做Mirror, HSCustom可以做Mirror, 而HSLIB不可以Mirror 我们可以把问题转换成下面的题目: ## 需要人工在镜像成员中同步的项目 ### 命名空间(namespace)和Mapping 命名空间是应用开发的概念,它使用数据库。命名空间定义了3种映射关系:Package Mapping, Routing Mapping, Global Mapping。这样在一个命名空间可以使用多个数据库的内容。 通常情况下,用户会在主机创建命名空间的同时,创建一个新的带有mirror属性的数据库,然后会在其他mirror成员中手工一个个的创建命名空间,加入镜像的数据库。之后,管理员无需考虑更多的操作。 然而,对命名空间的修改,比如要添加或者删除命名空间的某些mapping,这偶尔会需要,尤其是应用迭代和系统扩容的情况下,那么,管理员/实施人员,必须清楚Mirror无法同步这个修改,您必须手工同步修改到其他机器去。 如果配置的mapping比较多, 我建议使用Manifest来操作。Mainfest是一个xml的文本,用来安装或者修改Caché/IRIS的配置,你可以参考[在线文档: Using a Manifest](https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls?KEY=GCI_manifest), 或者社区文章[使用Manifest](https://cn.community.intersystems.com/post/%E4%BD%BF%E7%94%A8manifest)。 这里给一个配置mapping的例子: ```xml ``` 如果是资深的Caché维护工程师,懂得如果修改CPF文件并在不重启实例的情况下应用修改后的内容,可以考虑把主机上的CPF中的mapping部分复制粘贴到其他机器。如果您没有这方面的经验,我不建议这种方式。 另外,在IRIS 2022后的版本中有了一个新工具,Configuration Merge。 文档在[这里](https://docs.intersystems.com/iris20231/csp/docbook/Doc.View.cls?KEY=ACMF)。可惜只有最新版的IRIS或者Health Connect 用户有的用。 ### 数据库的修改 数据库的内容会通过Journal从主机同步到其他成员,但修改不会,一般会遇到的是**压缩和截断**。 由于某种错误操作,某个数据库,会扩展到不正常的大,而当错误修正后,用户可能需要对该数据库进行压缩和截断,以释放被错误占用的空闲的磁盘空间。 由于除主机外,其他镜像成员的数据库都是只读的,这个操作的顺序应该是这样: 1. 在主机A执行压缩和截断 2. 切换到备机B, 再次执行压缩和截断。 3. 异步成员DR。 一种方案是吧DR提升到备机。这时当前的备机A会将为灾备,然后再切换DR为主机,再进行压缩和截断。 还有一个选择,就是重新配置DR上的这个数据库,这需要从主机到DR的数据库备份和恢复。 ### IRIS实例的配置 从最常用的内存的配置,Service的配置, **用户,权限,资源**的配置等等。它们都不会被MIRROR同步。如果您在MIRROR主机里做了修改了缩表的大小,或者启动了一个,比如TELNET服务, 您需要人工在其他机器上做相同操作。 像上面的mapping配置一样,这里还是建议使用Manifest人工同步IRIS得修改。注意的是,Mainfest不保证能支持所有的配置。比如在Caché的版本下, 比如您在主机上启动了TELNET服务, Manifest没有相应的标签。这种情况下, 如果您熟悉ObjectScript语言,可以把ObjectScript实现加入执行Manifest的方法,比如说: ```java ClassMethod main(){ //执行Manifest修改命名空间 Set pVars("Namespace")="MYNAMESPACE" $$$ThrowOnError(..ModifyNamespace(.pVars)) //启动IRIS的TELNET服务 set properties("Enabled")=1 // 有効 set sts=##class(Security.Services).Modify("%Service_Telnet",.properties) } ``` 当然,如果您缺乏开发实施的知识,在用户界面上一个个机器的操作是最省心的办法。 问题是,打开一个服务,修改一个配置参数操作都很简单,但是如果要添加大量的用户和权限怎么办? 用Manifest管理是一个办法。但根本上,如果您经常有大量的用户管理的工作,其实使用Kerberos或者LDAP管理用户身份认证和授权的工作, 在有多个镜像成员的情况下,尤其的合适。 关于这部分内容,请参考[在线文档:Authentication and Authorization](https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls?KEY=PAGE_security_authentication_authorization) ### 定时任务(TASK) 在主机上创建的定时任务, 您需要人工在其他机器上做相同操作。这里有2个步骤: 1. 在主机上创建新任务的时候,要选择”**应如何为镜像运行任务**“。 这是个下拉菜单,选项有*”仅在主镜像成员上运行“,“仅在非主镜像成员上运行“ ,“在任何镜像成员上运行"。* 选择的出发点是:非主镜像成员的数据库是只读的。因此,比如一个Ensemble的镜像配置中, 删除Ensemble消息的定时任务, 一定是”仅在主镜像成员上运行“。 2. 把新的定时任务从主机同步到其他成员。 ​ 如果是一个或者少量几个TASK, 那么手工在其他各个镜像成员上添加是最简单直接的做法。而如果是有很长 的任务列表,尤其在配置Mirror得时候可以需要同步一个长长的列表时, 您可以考虑**从主机导出Task到其 他机器导入**,我只知道使用ObjectScript命令的方法, 使用`%SYS.Task.ExportTask()`和 `%SYS.Task.ImportTasks()`。 文档在[这里](https://docs.intersystems.com/iris20231/csp/documatic/%25CSP.Documatic.cls?LIBRARY=%25SYS&CLASSNAME=%25SYS.Task)。 ### Web Application 主机上配置的Web Applicaiton 也要同步到其他镜像成员。如果要同步的Web Application比较多,推荐的方式依然是Manifest, 下面是一个例子。 ```xml ``` 麻烦的是不同的版本Caché/IRIS使用的标签上会略有不同,要稍微仔细的查看一下您的版本的文档。 如果您对ZPM, 现在称为IPM熟悉的话, 用ZPM做同步也是个好选择。关于zpm, 您可以参考这个帖子[zpm介绍](https://cn.community.intersystems.com/post/zpm%E4%BB%8B%E7%BB%8D1)。提醒一下的是,程序因为是存在数据库里面的,如果该数据库是被镜像的,您其实不需要用ZPM把程序代码拷贝到其他镜像成员。 ### Gateway 一般用到的有**SQL Gateway**和**External Language Gateway**,它们分别用于连接其他的数据库和使用其他语音的代码包。 SQL Gateway 记录保存在%SYS命名空间的*%Library.sys_SQLConnection*数据表里。简单的方法是使用工具把表记录导入导出。 External Language Gateway(外部语言网关) 新版的IRIS系统内嵌了外部语言服务器,包括%Python Server, %Java Server, %Dotnet Server等。如果您使用的是默认配置,各个镜像成员是一致的,无需操心。如果只是IP端口的修改,手工同步一下也很容易,毕竟工作量有限,只是您需要清楚的记得,这个也是不被Mirror自动同步的。 ### 文件 我把文件分为两类, 一类是“固定文件”,包括一下几个部分, - CSP文件,js文件,css文件,html文件等 - XSLT文件 - 其他语言的程序代码,Java文件,python文件, .Net文件 这类文件上传到主机的时候, 也必须上传到其他镜像成员,这是个简单的操作,别忘了就行。 麻烦的是**流文件**。在ObjectScript里如果使用了%Stream.FileBinary, %Stream.FileCharacter等类,那么数据不是保存到Cache.Dat或者IRIS.data, 而是保存在和.Dat同目录的一个stream的子目录下,而这个目录是不会被镜像同步的。 而且,因为这是实时数据,你也不可能手工的把它拷来拷去。 如果您的应用里用到了文件流,我任务您需要一个文件服务器保证流文件在各个各个镜像成员间的同步。 ### Ensemble Production Consideration 对于Ensemble和Health Connect用户,您需要阅读这部分在线文档: [Production Considerations for Mirroring](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_manage#GHA_mirror_set_ensemble) , 简单总结一下: - 创建的带有ensemble或者Inteoprability的命名空间,数据库要创建为Mirror的数据库。 - **"production是否自动启动“**应该在主机和备机上,甚至DR上都配置为“自动启动”。 在Mirror配置下的Production会先检查这个实例是不是主机,如果不是,“自动启动”的配置也不会生效,这样保证了Production只在主机上运行,而切换后也不需要人工干预。 上面的这些并不是完整的内容,尽管在大多少情况下这些内容差不多够了。如果您想要确保Mirror的主机的工作内容完全同步到了备机和DR, 请仔细阅读在线文档的这一部分:[Mirror Configuration Guidelines](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_set_config#GHA_mirror_set_config_guidelines) 另外,对于各种需要人工同步的内容的操作,还建议阅读[在线文档:Server Migration](https://docs.intersystems.com/irislatest/csp/docbook/Doc.View.cls?KEY=AMIG#AMIG_migration_external)。 如果是最新的IRIS用户,请参考[在线文档:Deploy Mirrors Using Configuration Merge](https://docs.intersystems.com/iris20223/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_set_config#GHA_mirror_set_config_auto_merge)
文章
Hao Ma · 九月 17, 2022

IRIS镜像配置(3)

# 把数据库添加进Mirror 以往的经验里, 用户在把数据库添加到镜像时遇到过各种各样的问题,以致必须请求外部帮助才能解决。除了步骤本身比较繁琐,很大的原因是阅读文档不细致。还有一个,就是对英文水平不太高的用户,有些英文句式并不是很好懂,比如说,文档中有这一句其实非常关键: > If you attempt to add a new database to the mirror on a nonprimary member that was not created as a mirrored database on the primary, but rather added to the mirror after it was created, an error message notes this and you cannot complete the operation. 我用最好的翻译器DeepL翻译后的中文是: **如果你试图在一个非主要成员上向镜像添加一个新的数据库,而这个数据库并不是在主要成员上作为镜像数据库创建的,而是在创建后添加到镜像中的,那么就会出现错误信息提示,你无法完成操作。** 很讨厌的是它没用说明错误信息是什么,以致于很多用户, 当他们在Backup成员中把一个数据库添加到镜像时,遇到相关的错误时,没有把问题和这句话关联起来,这个错误提示是这样的: >“错误 #2105: 与成员 SERVERA/IRIS 中的相匹配的数据库 :mirror:AUGEST:DEMO 未被创建为镜像数据库”。 或者用英文, > ERROR #2105: Matching mirrored DB :mirror:AUGEST:DEMO in member SERVERA/IRIS was not created as mirrored DB 我来解释一下这句话,它说的是: ”嘿, 你在本机要添加的:mirror:AUGEST:DEMO数据库, 它在主镜像成员SERVERA/IRIS里, 未被创建为镜像数据库。“ 如果您看了我的解释, 还觉得莫名其秒,我相信您其实是没懂这个关键点: ​ **“一个数据库创建成镜像数据库,和创建成普通数据库后面后再添加到镜像里,它们是不同的。”** 关于这一点,其实文档也有说明,啰嗦,但说明了原因。直接上翻译: > 创建镜像数据库(即添加一个不含数据的新数据库)的过程与向镜像添加现有数据库的过程不同。作为镜像数据库创建的数据库上的Global操作从一开始就被记录在镜像Journal中,因此镜像可以访问它所需要的所有数据,以便在镜像成员之间同步数据库。但现有数据库在被添加到镜像之前的Global操作包含在非镜像Journal文件中,镜像不能访问这些文件。由于这个原因,一个现有的数据库在被添加到镜像后,必须在主故障转移成员上进行备份,并在备份故障转移成员和它要所在的任何异步成员上进行恢复。一旦这样做了,你必须激活并赶上数据库,使其与主数据库保持同步。 清楚了这个关键, 您才能理解为什么安装步骤分为下面的两个类型, - **创建新的镜像数据库** - **将已有的数据库加入镜像** > TIP: 另外,还有一个值得提醒的:只有用户自己的数据库可以被加入镜像。系统本身的数据库, 比如IRISSYS, IRISLIB, IRISTEMP等等,都不能加入镜像。早期有些版本可以,NOMORE! ## 创建新的镜像数据库 - 在**主镜像成员**的系统维护界面上,选择System Administration – Configuration – System Configuration – Local Databases , 选择Create New Database. **在数据库创建向导窗口,在“镜像数据库?”下拉菜框,选择'是‘(Yes)**。 SQL"页面, 确认表Persons同步到了所有的镜像成员。 ​ > 这里如果您遇到上面提到的“Error 2105“, 那就是这个数据库在Primary上先是创建成一般数据库,然后加入的镜像,那您应该按下面的步骤操作了。 > > 如果有人好奇:在Primary上的这种区别,Backup是怎么知道的,它不是还没加入到镜像吗? 故事是这样的: 镜像日志中同步的不是只有镜像数据库的数据的修改, 还包括IRISSYS, IRISAUDIT,等库的内容。NEWDB在主成员中是怎么加入到镜像的, IRISSYS里的Global Set是不一样的,而这个set, 是同步给备用成员backup的。 又一个没用的知识。 ## 将已有的数据库加入镜像 **已有的(Existing)数据库是指原本在主成员里按普通数据库创建的,然后加入镜像的数据库。** 这样的情况,哪怕同样名称,配置的数据库在其他成员上已经有了。能直接加入镜像吗?比如你主成员上有个User, 备用成员上也有,您能在主机, 备机直接把它们加入镜像吗? 答案是肯定不行。系统根本没法保证这两个库里面已有的数据是一样的。**您要在主成员上备份数据库,在其他成员恢复, 而恢复操作成功后,在其他成员上,这个数据库自动变成了“镜像数据库”**, 也就是加入了镜像。 这个同名的数据库要先在其他成员上创建。创建成普通数据库。如果其他成员上已经有了,也不用删除,就直接用主机的备份文件覆盖就好。 以下是详细的步骤: - 在主机的“系统>配置>本地数据库“页面, 点击**添加到镜像**按钮。然后在跳出窗口中选中您要添加的数据库,可以一次选多个。 数据库很大或者多个数据库同时加入是,可以选中”在后台运行“。通常这个添加动作是在秒级时候内完成的,无所谓是否后台运行。 - 到镜像监视器查看添加的结果。被添加的数据库状态这时候应该是"一般"(Normal) 。 - 到其他镜像成员的镜像监视器查看, 您会看到主机来的通知引发的提醒: - 在备机检查自己的数据库状态。如果没有DEMO或者USER数据库,那么创建它们,创建时下拉框”是否镜像?”选择否或者NO。之后在本地数据库列表中它们应该是这样,注意没有在镜像里。 - 在Primary做数据库的在线备份, 用于后面步骤里到其他成员上去做数据库恢复。 以下过程仅供参考: ```sh # 在主成员备份,并发送给备份成员serverb %SYS>do ^BACKUP 1) Backup 2) Restore ALL 3) Restore Selected or Renamed Directories 4) Edit/Display List of Directories for Backups 5) Abort Backup 6) Display Backup volume information 7) Monitor progress of backup or restore Option? 1 *** The time is: 2022-09-17 15:27:48 *** InterSystems IRIS Backup Utility -------------------------- What kind of backup: 1. Full backup of all in-use blocks 2. Incremental since last backup 3. Cumulative incremental since last full backup 4. Exit the backup program 1 => 1 Specify output device (type STOP to exit) Device: /isc/FullDBList_user.cbk => /isc/setmirror.cbk Backing up to device: /isc/setmirror.cbk Description: Backing up the following directories: /isc/data/demo/ /isc/iris/mgr/user/ Start the Backup (y/n)? => y Journal file switched to: /isc/jrnpri/MIRROR-AUGEST-20220917.011 Starting backup pass 1 Backing up /isc/data/demo/ at 09/17/2022 15:28:26 Copied 82 blocks in 0.004 seconds Finished this pass of copying /isc/data/demo/ Backing up /isc/iris/mgr/user/ at 09/17/2022 15:28:28 Copied 908 blocks in 0.475 seconds Finished this pass of copying /isc/iris/mgr/user/ Backup pass 1 complete at 09/17/2022 15:28:29 Starting backup pass 2 Backing up /isc/data/demo/ at 09/17/2022 15:28:31 Copied 2 blocks in 0.000 seconds Finished this pass of copying /isc/data/demo/ Backing up /isc/iris/mgr/user/ at 09/17/2022 15:28:33 Copied 2 blocks in 0.000 seconds Finished this pass of copying /isc/iris/mgr/user/ Backup pass 2 complete at 09/17/2022 15:28:33 Starting backup pass 3 Journal file '/isc/jrnpri/MIRROR-AUGEST-20220917.010' and the subsequent ones are required for recovery purpose if the backup were to be restored Journal marker set at offset 197572 of /isc/jrnpri/MIRROR-AUGEST-20220917.011 - This is the last pass - Suspending write daemon Backing up /isc/data/demo/ at 09/17/2022 15:28:35 Copied 2 blocks in 0.000 seconds Finished this pass of copying /isc/data/demo/ Backing up /isc/iris/mgr/user/ at 09/17/2022 15:28:35 Copied 2 blocks in 0.001 seconds Finished this pass of copying /isc/iris/mgr/user/ Backup pass 3 complete at 09/17/2022 15:28:35 ***FINISHED BACKUP*** Global references are enabled. Backup complete. 1) Backup 2) Restore ALL 3) Restore Selected or Renamed Directories 4) Edit/Display List of Directories for Backups 5) Abort Backup 6) Display Backup volume information 7) Monitor progress of backup or restore Option? %SYS>!scp /isc/setmirror.cbk root@172.16.58.102:/isc Enter passphrase for key '/root/.ssh/id_rsa': root@172.16.58.102's password: setmirror.cbk 100% 8448KB 49.4MB/s 00:00 %SYS> ``` - 在其他成员上恢复数据库,这里分两种情况: - 其他成员上没有这个数据库: 比如我的serverb没有DEMO数据库,要做的是:创建一个DEMO数据库,使用和servera一样的设置,除了**在下拉框“镜像数据库?“,回答”NO“** - 其他成员上有这个库,比如备机serverb里有User, 不用管它,下面我们就可以直接把它覆盖掉。 请参考下面的数据库恢复过程。 **提醒一点:不要使用第一个选项“All Directories", 该选项不能用其他机器的备份文件恢复本机。** ```sh # 在Backup成员serverb上执行,恢复用源文件拷贝自servera %SYS>do ^DBREST Cache DBREST Utility Restore database directories from a backup archive Restore: 1. All directories 2. Selected and/or renamed directories 3. Display backup volume information 4. Exit the restore program 1 => 2 Do you want to set switch 10 so that other processes will be prevented from running during the restore? Yes => Specify input file for volume 1 of backup 1 (Type STOP to exit) Device: /isc/setmirror.cbk This backup volume was created by: IRIS for UNIX (Red Hat Enterprise Linux 7 for x86-64) 2022.1 The volume label contains: Volume number 1 Volume backup SEP 17 2022 03:28PM Full Previous backup SEP 16 2022 09:11AM Full Last FULL backup SEP 16 2022 09:11AM Description Buffer Count 0 Mirror name AUGEST Failover Member SERVERA/IRIS Is this the backup you want to start restoring? Yes => This backup was made on the other mirror member. Limit restore to mirrored databases? yes For each database included in the backup file, you can: -- press RETURN to restore it to its original directory; -- type X, then press RETURN to skip it and not restore it at all. -- type a different directory name. It will be restored to the directory you specify. (If you specify a directory that already contains a database, the data it contains will be lost). /isc/data/demo/ (:mirror:AUGEST:DEMO) => /isc/iris/mgr/user/ (:mirror:AUGEST:USER) => Do you want to change this list of directories? No => Restore will overwrite the data in the old database. Confirm Restore? No => Yes ***Restoring /isc/data/demo/ at 15:47:09 82 blocks restored in 0.0 seconds for this pass, 82 total restored. Expanding /isc/iris/mgr/user/ ... Expanding /isc/iris/mgr/user/ from 1 MB to 654 MB ***Restoring /isc/iris/mgr/user/ at 15:47:12 908 blocks restored in 0.0 seconds for this pass, 908 total restored. ***Restoring /isc/data/demo/ at 15:47:12 2 blocks restored in 0.0 seconds for this pass, 84 total restored. ***Restoring /isc/iris/mgr/user/ at 15:47:12 2 blocks restored in 0.0 seconds for this pass, 910 total restored. ***Restoring /isc/data/demo/ at 15:47:12 2 blocks restored in 0.0 seconds for this pass, 86 total restored. ***Restoring /isc/iris/mgr/user/ at 15:47:12 2 blocks restored in 0.0 seconds for this pass, 912 total restored. Specify input file for volume 1 of backup following SEP 17 2022 03:28PM (Type STOP to exit) Device: Do you have any more backups to restore? Yes => no Mounting /isc/data/demo/ which is a mirrored DB /isc/data/demo/ ... (Mounted) Mounting /isc/iris/mgr/user/ which is a mirrored DB /isc/iris/mgr/user/ ... (Mounted) Journal records for mirrored DBs were restored successfully. %SYS> ``` - 检查数据库列表中的状态,注意它们已经成了AUGEST的镜像数据库了, **而且它们是只读模式**。 - 在serverb上查看镜像监视器,确认它们的状态是Dejournaling 后面您可以像上面提到的,在主机上操作数据, 确认数据修改同步给了备机。到此这部分工作才算结束。 > 如果只有外部备份文件: > > 按照文档上的说法,如果用外部备份在非主成员恢复,恢复后需要在镜像监视器的”镜像数据库列表里“点击"ACtiviate", 直到看到状态为Caaught up为至。请参考文档,我不是很清楚细节。 # 其他的镜像操作 这里我说说怎么删除镜像, 以及其他的一些常用操作的要点, 比如什么时候使用“SET NO FAILOVER”等等。 TO BE CONTINUED...
文章
Qiao Peng · 十二月 4, 2023

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

1. 通用RESTful业务服务和业务操作 InterSystems IRIS 提供了一组通用的RESTful 业务服务和业务操作类,用户无需开发自定义的业务服务和业务操作类,就可以直接向外提供RESTful服务和调用外部的RESTful API。 BS EnsLib.REST.GenericService 通用REST业务服务 BS EnsLib.REST.SAMLGenericService 检查SAML令牌的签名和时间戳的REST业务服务 BO EnsLib.REST.GenericOperation 通用REST业务操作 BO EnsLib.REST.GenericOperationInProc 用于透传模式的通用REST业务操作 2. 通用RESTful 消息 通用的RESTful 业务服务和业务操作类使用一个通用的RESTful消息类 - EnsLib.REST.GenericMessage,它是EnsLib.HTTP.GenericMessage的子类,二者数据结构都是 HTTPHeaders 记录http头的数组 Stream 记录http体的数据流 Type 数据流类型,例如是字符流还是二进制流。自动赋值,无需设置 Attributes 记录属性的数组 OriginalFilename 无需使用 OutputFolder 无需使用 OutputFilename 无需使用 因此EnsLib.REST.GenericMessage和EnsLib.HTTP.GenericMessage都可以被通用RESTful业务操作和业务服务所使用。 3. 通用RESTful 业务操作 使用通用的RESTful业务操作,可以连接到任何第三方的RESTful服务器,调用其RESTful API。 3.1 向production中加入通用RESTful业务操作 增加通用RESTful业务操作,只需要在Production配置页面的操作中添加EnsLib.REST.GenericOperation。 建议加入Production时,给业务操作起一个名字,用于代表具体的业务,例如是连接到LIS的RESTful 服务,可以命名为RESTtoLIS(可以考虑的命名规则 - 接口方式+业务系统)。如果未命名,默认会使用类名作为业务操作名。 3.2 配置通用RESTful业务操作 主要的设置项是以下3个: 1. HTTP服务器:目标RESTful服务器的服务器名或IP地址 2. HTTP端口:目标RESTful服务器提供RESTful API的端口号 3. URL:RESTful API的服务端点 启用该业务操作后,既可以访问外部RESTful API了。 3.3 测试通用RESTful业务操作 启用后,加入的通用的RESTful业务操作即可测试了。因为EnsLib.HTTP.GenericMessage的REST消息体是一个流类型的属性,为了测试时方便输入这个数据,我们增加一个业务流程。 1. 创建一个新的业务流程,设置其请求消息为Ens.StringRequest,用于测试时传入REST body数据。并为其上下文增加一个名为DataBody、类型为%Stream.GlobalCharacter(可持久化的字符流类型)的属性: 2. 在业务流程中增加一个代码流程(<code>),将请求消息的字符串数据写入上下文的DataBody字符流: Do context.DataBody.Write(request.StringValue) 注意行首加空格。 3. 然后在业务流程中再加入一个调用流程(<call>),调用上面已经加入production的业务操作,例如RESTtoLIS,并设置请求和响应消息为EnsLib.REST.GenericMessage或EnsLib.HTTP.GenericMessage。 4. 配置RESTtoLIS业务操作的请求消息(Request) 可以直接点击构建请求消息(Request Builder)按钮,使用图形化拖拽建立请求消息: 4.1 将左边上下文context里的DataBody拖拽到callrequest的Stream属性上; 4.2 对callrequest的HTTPHeaders赋值,它是一个元素类型为字符串的数组,代表HTTP请求的头。以下3个HTTP头是必须要填写的: HTTP头属性说明 下标 值 HTTP方法 "httprequest" 例如"POST" HTTP消息体的内容类型 "content-type" 例如"application/json" 客户端希望接收的内容类型 "Accept" 例如"*/*" 这3个数组元素赋值,可以通过在添加操作下拉列表中设置(Set)进行赋值。 5. 将业务流程加入Production,并测试 确保Production的设置是允许调试。在Production配置页面中选中这个业务流程,在右侧的操作标签页中选择测试按钮,并在弹出的测试消息页面里填入测试用的数据,并点击调用测试服务: 然后可以检查测试的消息处理流程,并确认REST消息体和HTTP消息头被正确地传递到目标REST API 4. 通用RESTful 业务服务 使用通用的RESTful业务服务,可以向外发布能处理任何RESTful API调用请求的RESTful服务端。 4.1 将通用RESTful业务服务加入Production 在Production配置页面,点击服务后面的加号。弹出的向导页面,服务类选择EnsLib.REST.GenericService;输入服务名,建议写一个能代表组件功能的名字,例如向HIS系统开放的REST服务,可以起名RESTforHIS;选中立即启用。 RESTful通用业务服务可以通过2种方式向外提供RESTful API服务:第一种通过Web服务器向外提供服务,第二种使用IRIS服务器的特定TCP端口向外提供服务。第二种方式不依赖于独立的Web服务器,但推荐使用Web服务器,从而得到更好的性能和安全性。 这里我们使用Web服务器提供REST服务,因此在业务服务的端口配置中,保持空白。在接受消息的目标名称中,选择接收RESTful API请求的业务流程或业务操作,这里我们测试使用一个空的业务流程。点击应用激活这些设置。 4.2 建立一个向外提供RESTful API的Web应用 向外发布RESTful服务,不仅涉及到服务发布的URL,还涉及到安全。我们通过创建一个专用的Web应用来进行管理和控制。 在IRIS系统管理门户>系统管理>安全>应用程序>Web应用程序 中,点击新建Web应用程序按钮,新建一个Web应用程序,并做以下配置: 1. 名称,填写一个计划发布的服务端点,例如/IRISRESTServer。注意前面的/ 2. NameSpace,选择Production所在的命名空间 3. 选中启用 REST,并设置分派类为EnsLib.REST.GenericService 4. 根据安全需要,配置安全设置部分。这里方便测试起见,允许的身份验证方法选择了未验证(无需验证)。如果是生产环境,或者您在做性能压力测试,都应该选择密码或Kerberos安全的身份验证方式! 注意,请保证同一个命名空间下,仅有一个分派类为EnsLib.REST.GenericService的REST类型的Web应用。 4.3 测试RESTful业务服务 现在就可以测试这个RESTful业务服务了。这个RESTful服务可以响应任何REST API的请求,如何响应则是后续业务流程/业务操作的事。 它的完整的RESTful URL是:[Web服务器地址]:[Web服务器端口]/[Web应用的名称]/[通用REST服务在production中的配置名]/[API名称和参数],例如我在IRIS本机的私有Apache的52773端口上访问上面创建的REST通用业务服务,调用PlaceLabOrder的API (注意,这里我们并没有实现过PlaceLabOrder这个API,但我们依然可以响应,而不会报404错误),那么完整的REST 调用地址是: 127.0.0.1:52773/IRISRESTServer/RESTforHIS/PlaceLabOrder 打开POSTMAN,用POST方法,发起上面REST API的调用: 在IRIS里会得到类似这样的消息追踪结果,如果你没有实现过处理REST API请求的业务流程,会得到一个500错,但依然可以查看IRIS产生的EnsLib.HTTP.GenericMessage消息内容: 这个通用RESTful业务服务会把REST请求转换为EnsLib.HTTP.GenericMessage消息,向目标业务操作/业务流程发送。因此,通过解析它的消息内容,就知道REST API请求的全部信息: 1. Stream里是POST的数据 2. HTTPHeaders 的下标"HttpRequest"是HTTP的方法 3. HTTPHeaders 的下标"URL"是完整的API路径,包括了服务端点(在"CSPApplication"下标下)、REST业务服务名称(在"EnsConfigName"下标下)和API 后续业务流程可以通过这些数据对REST API请求进行响应。 4.4 使用业务流程对REST API调用进行路由 有了通用RESTful业务服务生成的EnsLib.HTTP.GenericMessage消息,我们就可以使用消息路由规则或业务流程对REST API请求进行路由。这里我使用业务流程方法对REST API请求进行路由演示。 构建一个新的业务流程,请求消息和响应消息都是EnsLib.REST.GenericMessage或EnsLib.HTTP.GenericMessage,同时为context增加一个名为ReturnMsg的字符串类型的属性,并设置它默认值为:"{""Code"":-100,""Msg"":""未实现的API""}"。 在业务流程里增加一个<switch>流程,然后在<switch>下增加2个条件分支,分别为: 名称:下达检验医嘱,条件:判断是否http头的URL为PlaceLabOrder,且http头的HttpRequest为POST: (request.HTTPHeaders.GetAt("URL")="/IRISRESTServer/RESTforHIS/PlaceLabOrder") && (request.HTTPHeaders.GetAt("HttpRequest")="POST") 名称:查询检验项目,条件:判断是否http头的URL为GetLabItems,且http头的HttpRequest为GET: (request.HTTPHeaders.GetAt("URL")="/IRISRESTServer/RESTforHIS/GetLabItems") && (request.HTTPHeaders.GetAt("HttpRequest")="GET") 在两个分支里,分别增加<code>, 产生返回的REST消息内容: Set context.ReturnMsg="{""Code"":200,""Msg"":""检验医嘱下达成功""}" Set context.ReturnMsg="{""Code"":200,""Msg"":""查询检验项目成功""}" 最后在<switch>后增加一个<code>,构建响应消息: // 初始化响应消息 set response = ##class(EnsLib.REST.GenericMessage).%New() // 初始化响应消息的流数据 Set response.Stream = ##class(%Stream.GlobalCharacter).%New() // 将REST返回数据写入流 Do response.Stream.Write(context.ReturnMsg) 编译这个业务流程,并将其加入Production。 之后修改通用RESTful业务服务的设置,将接收消息的目标名称改为这个新建的业务流程。 现在再通过POSTMAN测试一下各种API,并查看返回REST响应: 在真实项目中,根据实际情况,将上面<switch>流程分支的<code>替换为API响应业务流程或业务操作即可。 总结:使用通用RESTful业务操作和业务服务,无需创建自定义的RESTful 业务组件类,就可以调用外部RESTful API和向外提供RESTful API服务,降低开发和实施成本,实现低代码开发。 后记:关于EnsLib.REST.GenericService对CORS(跨域资源共享)的支持 CORS是一种基于 HTTP 头的机制,通过允许服务器标示除了它自己以外的其它origin(域、协议和端口)等信息,让浏览器可以访问加载这些资源。所以要让EnsLib.REST.GenericService支持CORS,需要让它的响应消息增加对于CORS支持的HTTP头的信息,这里不详细介绍这些头含义了,大家可以去W3C的网站或者搜索引擎查询具体定义,最简单可以使用以下代码替代上面4.4中的初始化响应消息代码: // 设置HTTP响应的头信息 set tHttpRes=##class(%Net.HttpResponse).%New() set tHttpRes.Headers("Access-Control-Allow-Origin")="*" set tHttpRes.Headers("Access-Control-Allow-Headers")="*" set tHttpRes.Headers("Access-Control-Allow-Methods")="*" // 初始化响应消息 set response = ##class(EnsLib.REST.GenericMessage).%New(,,tHttpRes)
文章
姚 鑫 · 十一月 4, 2021

第六十六章 SQL命令 REVOKE

# 第六十六章 SQL命令 REVOKE 从用户或角色中删除特权。 # 大纲 ```sql REVOKE admin-privilege FROM grantee REVOKE role FROM grantee REVOKE [GRANT OPTION FOR] object-privilege ON object-list FROM grantee [CASCADE | RESTRICT] [AS grantor] REVOKE [GRANT OPTION FOR] SELECT ON CUBE[S] object-list FROM grantee REVOKE column-privilege (column-list) ON table FROM grantee [CASCADE | RESTRICT] ``` ## 参数 - `admin-privilege` - 管理员级特权或以前授予要撤销的管理员级特权的以逗号分隔的列表。 可用的`syspriv`选项包括`16`个对象定义权限和`4`个数据修改权限。对象定义权限为:`%CREATE_FUNCTION`, `%DROP_FUNCTION`, `%CREATE_METHOD`, `%DROP_METHOD`, `%CREATE_PROCEDURE`, `%DROP_PROCEDURE`, `%CREATE_QUERY`, `%DROP_QUERY`, `%CREATE_TABLE`, `%ALTER_TABLE`, `%DROP_TABLE`, `%CREATE_VIEW`, `%ALTER_VIEW`, `%DROP_VIEW`, `%CREATE_TRIGGER`, `%DROP_TRIGGER`。 或者,可以指定`%DB_OBJECT_DEFINITION`,这将撤销所有`16`个对象定义特权。数据修改权限为`INSERT`、`UPDATE`、`DELETE`操作的`%NOCHECK`、`%NOINDEX`、`%NOLOCK`、`%NOTRIGGER`权限。 - `grantee` - 拥有SQL系统权限、`SQL`对象权限或角色的一个或多个用户的列表。 有效值是一个以逗号分隔的用户或角色列表,或`“*”`。 星号(`*`)指定当前定义的所有没有`%all`角色的用户。 - `AS grantor` - 此子句允许通过指定原始授予者的名称来撤销另一个用户授予的特权。 有效的授予者值是用户名、以逗号分隔的用户名列表或`“*”`。 星号(`*`)指定当前定义的所有授予者。 要使用`AS`授予器子句,必须具有`%All`角色或`%Admin_Secure`资源。 - `role` - 一个角色或以逗号分隔的角色列表,这些角色的权限将从用户被撤销。 - `object-privilege` - 基本级别特权或先前授予要撤销的基本级别特权的逗号分隔列表。 该列表可以包含以下一个或多个:`%ALTER`、`DELETE`、`SELECT`、`INSERT`、`UPDATE`、`EXECUTE`和`REFERENCES`。 要撤销所有特权,可以使用`“all [privileges]”`或`“*”`作为此参数的值。 注意,您只能从多维数据集撤销`SELECT`特权,因为这是惟一可授予的多维数据集特权。 - `object-list` - 一个以逗号分隔的列表,其中包含一个或多个正在撤销对象特权的表、视图、存储过程或多维数据集。 可以使用`SCHEMA`关键字指定从指定模式中的所有对象撤销对象特权。 可以使用`" * "`指定从当前命名空间中的所有对象撤销对象特权。 - `column-privilege` - 从一个或多个列列表列出的列撤销基本权限。 可用选项有`SELECT`、`INSERT`、`UPDATE`和`REFERENCES`。 - `column-list` - 由一个或多个列名组成的列表,用逗号分隔,用括号括起来。 - `table` - 包含列列表列的表或视图的名称。 # 描述 `REVOKE`语句撤销允许用户或角色在指定的表、视图、列或其他实体上执行指定任务的权限。 `REVOKE`还可以撤销用户分配的角色。 `REVOKE`撤销`GRANT`命令的操作; 特权只能由授予特权的用户撤消,或者通过`CASCADE`操作(如下所述)。 可以从指定用户、用户列表或所有用户(使用`*`语法)撤销角色或特权。 因为`REVOKE`的准备和执行速度很快,而且通常只运行一次,所以`IRIS`不会在`ODBC`、`JDBC`或动态SQL中为`REVOKE`创建缓存查询。 即使不能执行实际的撤销(例如,指定的特权从未被授予或已经被撤销),`REVOKE`也会成功地完成。 但是,如果在`REVOKE`操作期间发生错误,`SQLCODE`将被设置为负数。 ## 撤销的角色 角色可以通过`SQL GRANT`和`REVOKE`命令授予或撤销,也可以通过`^SECURITY IRIS System SECURITY`命令授予或撤销。 可以使用`REVOKE`命令从某个用户撤消一个角色,也可以从另一个角色撤消一个角色。 不能使用`IRIS System Security`将角色授予或撤销给其他角色。 特殊变量`$ROLES`不显示授予角色的角色。 `REVOKE`可以指定单个角色,也可以指定要撤销的角色列表,以逗号分隔。 `REVOKE`可以从指定的用户(或角色)、用户(或角色)列表或所有用户(使用*语法)中撤销一个或多个角色。 `GRANT`命令可以将一个不存在的角色授予用户。 可以使用`REVOKE`命令从现有用户撤销不存在的角色。 但是,角色名必须使用与授予角色时相同的字母大小写来指定。 如果试图从不存在的用户或角色撤销现有角色, IRIS将发出`SQLCODE -118`错误。 如果不是超级用户,并且试图撤销一个不拥有且没有`ADMIN OPTION`的角色,InterSystems IRIS将发出`SQLCODE -112`错误。 ## 撤销对象权限 对象特权赋予用户或角色对特定对象的某些权限。 从一个被授予者的对象列表上撤销一个对象特权。 对象列表可以在当前名称空间中指定一个或多个表、视图、存储过程或多维数据集。 通过使用逗号分隔的列表,单个`REVOKE`语句可以从多个用户和/或角色中撤销多个对象上的多个对象特权。 可以使用星号(`*`)通配符作为对象列表值,从当前名称空间中的所有对象撤销对象特权。 例如,`REVOKE SELECT ON * FROM Deborah`将撤销该用户对所有表和视图的SELECT权限。 `REVOKE EXECUTE ON * FROM Deborah`将撤销该用户对所有非隐藏存储过程的`EXECUTE`权限。 可以使用`SCHEMA SCHEMA -name`作为对象列表值,以撤销指定模式中当前名称空间中的所有表、视图和存储过程的对象特权。 例如,`REVOKE SELECT ON SCHEMA Sample FROM Deborah`将撤销该用户对`Sample`模式中所有对象的`SELECT`权限。 可以将多个模式指定为逗号分隔的列表; 例如,`REVOKE SELECT ON SCHEMA Sample,Cinema FROM Deborah`撤销`Sample`和`Cinema`模式中所有对象的`SELECT`权限。 可以从用户或角色撤消对象特权。 如果从某个角色撤销该权限,则仅通过该角色拥有该权限的用户将不再拥有该权限。 不再拥有特权的用户不能再执行需要该对象特权的现有缓存查询。 当`REVOKE`撤销对象特权时,它将成功完成并将`SQLCODE`设置为0。 如果`REVOKE`没有执行实际的撤销(例如,指定的对象权限从未被授予或已经被撤销),它将成功完成,并将`SQLCODE`设置为`100`(不再有数据)。 如果在`REVOKE`操作期间发生错误,它将`SQLCODE`设置为负数。 多维数据集是不受模式名称限制的SQL标识符。 要指定多维数据集对象列表,必须指定`CUBE`(或cubes)关键字。 因为多维数据集只能有`SELECT`权限,所以您只能从多维数据集撤销`SELECT`权限。 对象权限可以通过以下任意方式撤销: - `REVOKE command`. - `$SYSTEM.SQL.Security.RevokePrivilege()`方法。 - 通过IRIS系统安全。 转到管理门户,选择系统管理、安全、用户(或系统管理、安全、角色),为所需的用户或角色选择`Edit`,然后选择SQL表或SQL视图选项卡。 在下拉列表中选择`Namespace`。 向下滚动到所需的表,然后单击`revoke`来撤销权限。 可以通过调用`%CHECKPRIV`命令来确定当前用户是否具有指定的对象特权。 通过调用`$SYSTEM.SQL.Security.CheckPrivilege()`方法,可以确定指定的用户是否具有指定的表级对象特权。 ## 撤销对象所有者特权 如果从对象的所有者那里撤消对SQL对象的特权,那么所有者仍然隐式地拥有对对象的特权。 为了从对象的所有者完全撤销对象上的所有特权,必须更改对象以指定不同的所有者或没有所有者。 ## 撤销表级和列级特权 `REVOKE`可用于撤销表级特权或列级特权的授予。 表级特权提供对表中所有列的访问。 列级特权提供对表中每个指定列的访问。 向表中的所有列授予列级特权在功能上等同于授予表级特权。 然而,这两者在功能上并不完全相同。 列级`REVOKE`只能撤销在列级授予的权限。 不能向表授予表级特权,然后在列级为一个或多个列撤销此特权。 在这种情况下,`REVOKE`语句对已授予的权限没有影响。 ## CASCADE 或 RESTRICT IRIS支持可选的`CASCADE`和`ESTRICT关`键字来指定`REVOKE`对象特权行为。 如果没有指定关键字,则默认为`RESTRICT`。 可以使用`CASCADE`或`RESTRICT`来指定从一个用户撤销对象特权或列特权是否也会从通过`WITH GRANT OPTION`接收到该特权的任何其他用户撤销该特权。 `CASCADE`撤销所有这些关联的特权。 当检测到关联的特权时,`RESTRICT(默认值)`导致`REVOKE`失败。 相反,它设置`SQLCODE -126`错误`“REVOKE with RESTRICT failed”`。 下面的例子展示了这些关键字的使用: ```sql --UserA GRANT Select ON MyTable TO UserB WITH GRANT OPTION ``` ```sql --UserB GRANT Select ON MyTable TO UserC ``` ```sql --UserA REVOKE Select ON MyTable FROM UserB -- This REVOKE fails with SQLCODE -126 ``` ```sql --UserA REVOKE Select ON MyTable FROM UserB CASCADE -- This REVOKE succeeds -- It revokes this privilege from UserB and UserC ``` 注意,`CASCADE`和`RESTRICT`对`UserB`创建的引用`MyTable`的视图没有影响。 ## 对缓存查询的影响 当撤销特权或角色时, IRIS将更新系统上所有缓存的查询,以反映特权中的这一更改。 但是,当无法访问某个名称空间时——例如,当连接到数据库服务器的ECP连接关闭时——`REVOKE`会成功完成,但不会对该名称空间中的缓存查询执行任何操作。 这是因为`REVOKE`不能更新不可达名称空间中的缓存查询,以撤销缓存查询级别的特权。 没有发出错误。 如果数据库服务器稍后启动,则该名称空间中缓存查询的权限可能不正确。 如果某个角色或特权可能在某个名称空间不可访问时被撤销,建议清除该名称空间中的缓存查询。 ## IRIS Security REVOKE命令是一个特权操作。 在嵌入式SQL中使用`REVOKE`之前,必须以具有适当特权的用户身份登录。 如果不这样做,将导致`SQLCODE -99`错误(特权冲突)。 使用`$SYSTEM.Security.Login()`方法为用户分配适当的权限: ``` DO $SYSTEM.Security.Login("_SYSTEM","SYS") &sql( ) ``` 必须具有`%Service_Login:Use`权限才能调用`$SYSTEM.Security`。 登录方法。 # 示例 下面的嵌入式SQL示例创建两个用户,创建一个角色,并将角色分配给用户。 然后,它使用星号(`*`)语法从所有用户撤销该角色。 如果用户或角色已经存在,`CREATE`语句将发出`SQLCODE -118`错误。 如果用户不存在,`GRANT`或`REVOKE`语句将发出`SQLCODE -118`错误。 如果用户存在但角色不存在,则`GRANT`或`REVOKE`语句发出`SQLCODE 100`。 如果用户和角色存在,则`GRANT`或`REVOKE`语句发出`SQLCODE 0`。 即使已经完成了角色的授予或撤销,如果您试图撤销从未被授予的角色,也是如此。 ```java ClassMethod Revoke() { d $SYSTEM.Security.Login("_SYSTEM","SYS") &sql( CREATE USER User1 IDENTIFY BY fredpw ) &sql( CREATE USER User2 IDENTIFY BY barneypw ) w !,"CREATE USER error code: ",SQLCODE &sql( CREATE ROLE workerbee ) w !,"CREATE ROLE error code: ",SQLCODE &sql( GRANT workerbee TO User1,User2 ) w !,"GRANT role error code: ",SQLCODE &sql( REVOKE workerbee FROM * ) w !,"REVOKE role error code: ",SQLCODE } ``` 在下面的示例中,使用`AS`授予子句,一个用户(`Joe`)授予一个特权,另一个用户(`John`)撤销该特权: ```sql /* User Joe */ GRANT SELECT ON Sample.Person TO Michael ``` ```sql /* User John */ REVOKE SELECT ON Sample.Person FROM Michael AS Joe ``` 注意,`John`必须具有`%All`角色或`%Admin_Secure`资源。
文章
Nicky Zhu · 一月 6, 2023

《数据二十条》的号角声

国务院于2022年12月19日发布了《中共中央 国务院关于构建数据基础制度更好发挥数据要素作用的意见》(后简称《数据二十条》),如何有效利用数据已经成为下一步的趋势。另一方面,无论是基于数据中台还是数据编织理念,两者也都对如何利用数据提出了构想。因此医疗行业数字化建设的目标已不能再局限于如何收集数据,建立医疗行业数据的流通机制将会是为越来越普遍的需求。 时钟拨回几年前,数据中台概念开始火爆。人们对数据中台的定义、诠释尽管有诸多差异,通过数据中台降低数据共享和利用的成本则是共同的期望。但经过这几年的探索之后,中台已死的观点也在涌现。究其原因,除去中台概念在技术上的不确定,数据流通过程中的责权益的不清晰也是严重的制约因素。毕竟,数据中台自身作为一套技术框架并不能代替法律法规与市场自动将数据转变为商品从而创造出流通价值。 那么,如何能够使数据的流通合规合法,使数据能够如货币和商品一般自由流动,则是我们需要思考和探索的主题,这次《数据二十条》的出现,无疑为医疗信息技术工作者提供了一个明确的思考方向。 政策利好与约束 鉴于《数据二十条》对数据行业生态的覆盖范围之广,涉及数据权属界定、数据产品流通、数据收益分配和数据市场有效监管等各方面,本文将无法全面展开每一条政策进行解读和思考,因此将聚焦于与每个从业人员都息息相关的数据产权和数据产品流通两方面进行。 产权与使用权的破与立 还记得数年前与信息科同事谈及基于医疗数据的统计与分析时,医院的同事对于数据被第三方访问的恐惧远多于期待。对数据要素的权属及其确立规则的不清晰使得每个从业人员都无法在具备法律法规保障的前提下运用数据。本次《数据二十条》对于个人数据、企业数据和公共数据进行了产权定义,还提出了数据资源持有权、数据加工使用权、数据产品经营权等分置的产权运行机制,从而打破了这样无法可依的尴尬局面。 可以预见的是,通过对数据的产权与使用权进行分离,在取得数据所有者(如个人或企业)授权的前提下,对数据进行加工处理,通过数据洞察进行盈利将成为合理合法的业务形态。 数据供应链的建立 《数据二十条》第三章对数据供应链体系做了一系列的规划,包括数据流通过程中参与方的角色,如数据商和第三方专业服务机构;包括流通场所,如数据交易所以及对应的流程合规与监管规则体系的远景。这样一个体系的构建,其规模和复杂性并不亚于为汽车工业组织零部件生产和消费的供应链。 特别需要注意的是,正如《数据二十条》中明确指出的,数据供应链的建立必将依托数据质量标准化体系,推进对数据采集和接口的标准化,依赖于数据整合互通和互操作。 这些概念和体系对于医疗信息技术工作者来说并不陌生。然而在既往的工作中,跨企业、跨区域医疗行业数据共享的产业规模并未对标准化产生强劲的推力。尽管近年来随着互联互通标准化评测工作的开展,医疗信息互操作在标准化方面得到了极大的进展,但是医疗行业数据与上下游生态企业(如药企、保险、养老机构等)间进行数据流通所需的统一语义和标准还未确立和应用,势必将在不远的未来对医疗信息技术工作者提出更高的挑战。 另一方面,在鼓励数据交易所发挥作用的同时,《数据二十条》也倡导在数据流程合规与受规则体系监管的前提下,培育一批数据商和第三方专业服务机构,依法依规在场内和场外采取开放、共享、交换、交易等方式流通数据,也为创建数据供应、数据托管和数据服务代理等多种模式的数据经济形态创造了条件。 医疗行业数据流通案例 医疗数据产业并不是一个已经成熟的规模化产业,即使对于美国、英国这些在医疗信息化方面较早起步的国家,医疗数据产品和流通也仍然处于初步的市场探索阶段。我们可以看到一个案例。 Epic COSMOS数据集 美国最大的电子病历厂商Epic于2019年推出了数据集产品COSMOS(https://COSMOS.epic.com/)。所有Epic电子病历系统的用户都可以自愿与Epic签约成为COSMOS合作伙伴,在开放自己的医疗健康数据的同时共享同样加入了COSMOS网络其他用户的数据。时至今日,COSMOS已经收录了1亿6千7百万患者的数据,覆盖一千余家医院和两万余家诊所。 图 1 COSMOS数据流 如上图所示,Epic采用了非常传统的前置机+中心化存储方案构建。在置于院端的前置机中,以批量上传和事件触发上传两种方式加载数据集,在前置机一侧对数据进行标准化和匿名化,并通过HL7 CDA标准以文档的形式将数据传到数据中心。置于AWS云端的数据中心将负责对数据进行去重及合并。其中,数据在云端将以非结构化的Global形态存储于InterSystems的Caché中,并利用Caché自带的后结构化能力将非结构化的Global转换并存储为关系型数据对外提供SQL访问能力。 在这个过程中,COSMOS进行的若干细节处理非常值得即将面临数据开放的医疗信息技术工作者参考和借鉴。读者可参考相关论文查阅(如https://www.thieme-connect.com/products/ejournals/pdf/10.1055/s-0041-1731004.pdf)。 真实数据的可访问性:COSMOS本质上只解决了分散的,真实的医疗数据的可访问性问题,还没有运用任何颠覆性的BI、AI技术。作为美国最大的电子病历厂商,手握上亿人的医疗数据的Epic,需要从基础数据的准备切入市场,这从侧面反映了当前医疗行业所面临的客观现实,即供应链底层的数据原料并不存在稳定的供给,从而阻碍了其他技术的演进。这同样是我们面临的现状。 非常传统的数据采集:COSMOS只采集EHR中的结构化数据,并不收纳任何影像、视频和除实验室检测结果外的文本等多媒体数据,也未采用实时数据流进行采集。这并不意味着多媒体数据没有价值,也不意味着实时数据流没有价值,而是意味着半静态的,结构化的数据中的价值并没有得以充分提炼和发挥,仅通过收集整理结构化数据形成规模一项工作已足以支撑起庞大的价值链条,尽管这些静态数据并不是唯一的价值来源。 去识别化与个人数据授权:作为对患者信息进行隐私保护的首要手段,COSMOS及与之相似的数据集产品Cerner Real-World Data(CRWD)均遵循美国自1996年通过的HIPPA法案,只开放法案允许开放的数据集,并按照HIPPA的要求对可能暴露患者隐私或反向识别个人的数据字段进行匿名化处理。需要注意的是,尽管CRWD相关的论文中声明,由于对数据进行了匿名化处理,对个人医疗数据的使用不需要患者本人的授权(https://www.sciencedirect.com/science/article/pii/S2352340922003304),但COSMOS仍然提供了供患者撤回数据授权,将本人的数据从COSMOS网络中退出的工作流。因此,即使在美国,数据所有者和数据使用者之间的权益平衡仍保留了相当的灵活性,我国在制定相关法规时也会对基于所有权和使用权定义相应的细则。 数据访问控制:在前置机与云端数据中心通信过程中,CDA文档将被加密并通过专网传输,避免在公网传输并被截获和解析的可能。另一方面,尽管COSMOS收集了诊断、药嘱、手术史、社会史和家族史等患者个人的明细数据,但它并不对最终用户开放这些数据;COSMOS用户可以通过查询门户,制定条件,查询基于这些明细的统计数据,例如在一定行政区域内罹患新冠的患者数量及其年龄分布等,但无法查询到对应的个人,因此经过认证的科研机构在COSMOS中发起查询并不再需要特定的审查委员会审核;同时COSMOS也不提供将数据从COSMOS网络中导出的渠道,避免数据流出网络。从这些控制手段上来看,COSMOS选择的技术路线和服务模式与《新二十条》中“原始数据不出域、数据可用不可见”的要求和“以模型、核验等产品和服务等形式向社会提供”的倡导高度吻合,值得借鉴。 数据标准化:COSMOS在前置机上收集一家医院的数据时已落实了数据的标准化,采用固定的数据结构和术语集。医院需要先完成对数据和术语的标准化映射,才能接入COSMOS网络。而在云端存储中,原始数据也是以标准化的CDA文档形式保存,进一步巩固了数据标准。也正是在标准化数据存储的基础上,最终用户才能够通过统一的查询构建器,在同一种语义环境下同时访问来自于不同医院,采用了不同术语标准的医疗数据。因此通过数据标准术语标准达成语义一致性的重要性不言而喻,这是医疗数据的利用迅速得到规模效应的客观需求。 医疗数据产品发展前景 如前所述,基于数据所有权与数据使用权分离的假设,很难想象未来医疗数据产品的发展方向以生数据产品的形态,开放对个人数据的(即使经过了匿名化)访问。相反的,基于医疗数据需求的多样性以及个人、企业、公共数据管理规则的差异性,以生数据为基础,以对药企、保险等企业提供潜在可招募患者的区域锁定或针对患者的年龄、诊断、家族史的普遍特征与医疗支出进行精算为例,针对人群展开的数据洞察和数据分析服务,更可能得到业界的认可并在数据价值利用和数据隐私保护间取得平衡,有很大概率成为率先得以实现的商业模式。同时,作为一个新兴产业,生产者(数据工程师)群体的培养和储备,以及与之配套的生产资料的制造和积累,则是医疗数据产业能够成型的前提,值得医疗信息技术工作者关注和投入。 因此,在未来相当长的一段时间内,对医疗行业数据的利用,将以各医院、集团和企业建立的数据中心为基础,通过对真实数据进行洞察分析和价值挖掘的形态,以数据服务的形式对外提供,从而迅速释放这些被积累了很久的数据的价值。 后续我们还会继续阐述和分析在医疗数据流通领域中的生产者和生产资料的特征,欢迎大家与我们交流,谢谢。
文章
Weiwei Gu · 七月 12, 2022

Globals - 存储数据的魔剑-树:第二部分

开始 - 请拉到页面底部查看该系列文章第一部分 . 3. 使用globals时结构的变体 一个结构,比如说一个有序排列的“树”,有各种特殊的情况。让我们来看看那些对使用globals有实际价值的情况。 3.1 特殊情况1. 一个没有分支的节点 Globals不仅可以像数组一样使用,而且可以像普通变量一样使用。例如,用于创建一个计数器: Set ^counter = 0 ; setting counter Set id=$Increment(^counter) ; atomic incrementation 同时,一个global除了值以外,还可以有分支。一个并不排斥另一个。 3.2 特殊情况2. 一个节点和多个分支 事实上,这是一个典型的键值库。而如果我们把健和值都存下来而不是仅仅是存值的话,那我们会得到一个有主键的普通表。 为了实现一个基于globals的表,我们将不得不从列值中形成字符串,然后通过主键将它们保存到global中。为了能够在读取过程中把字符串分割成列,我们可以使用以下方法。 分隔符 Set ^t(id1) = "col11/col21/col31" Set ^t(id2) = "col12/col22/col32" 一个固定的方案,即每个字段占据特定数量的字节。在关系型数据库中通常就是这样做的。 一个特殊的$LB 一个特殊的 $LB函数(从Caché开始引入的),可以从值中组成一个字符串。 Set ^t(id1) = $LB("col11", "col21", "col31") Set ^t(id2) = $LB("col12", "col22", "col32") 有趣的是,使用globals做一些类似于关系型数据库中外键的事情并不难。我们把这种结构称为index globals。Index globals是一个补充"树",用于快速搜索那些不属于主Global主键组成部分的字段。你需要编写额外的代码来填充和使用它。 下面,让我们在第一列的基础上创建一个Index global. Set ^i("col11", id1) = 1 Set ^i("col12", id2) = 1 要想通过第一列快速搜索,你需要查看^i global,并找到与第一列中必要值对应的主键(id)。 当插入一个值时,我们可以同时为必要的字段创建值和Index global。为了保证可靠性,让我们把它包装成一个事务(transaction)。 TSTART Set ^t(id1) = $LB("col11", "col21", "col31") Set ^i("col11", id1) = 1 TCOMMIT 更多的信息可以从这里查看 making tables in M using globals and emulation of secondary keys. 如果用COS/M编写插入/更新/删除函数并进行编译,这些表的工作速度将与传统DB一样快(甚至更快)。 我通过对一个单一的双列表进行大量的INSERT和SELECT操作,同时使用TSTART和TCOMMIT命令(transactions事务)来验证这个声明。 我没有测试更复杂的并发访问和并行事务的情况。 在不使用transactions事务的情况下,一百万个值的插入速度为778,361次/秒。 对于3亿个值,速度是422,141次/秒。 当使用transactions交易时,对于5000万个值,速度达到572,082次插入/秒。所有的操作都是通过编译的M代码运行的。我使用了普通的硬盘,而不是SSD。RAID5有回写功能。所有运行在Phenom II 1100T CPU上。 为了对SQL数据库进行同样的测试,我们需要写一个存储过程,在一个循环中进行插入。当使用同样的方法测试MySQL 5.5(InnoDB存储)时,我从来没有得到超过每秒11K次的插入。 确实,用globals实现表比在关系型数据库中做同样的事情要复杂。这就是为什么基于globals的工业数据库会有SQL访问,以来简化表格数据的工作。 一般来说,如果数据模式不会经常改变,插入的速度不是很关键,而且整个数据库可以很容易地用规范化的表来表示,那么使用SQL就比较容易,因为它提供了一个更高的抽象层次。 在这种情况下,我想表明globals可以被用作创建其他DB的构造函数。就像汇编语言可以用来创建其他语言一样。而这里有一些使用globals来创建对应的 键值key-values, 列表lists, 集合sets, 表格-tabular, 文档数据库-document-oriented DB 的例子。 如果你需要以最小的努力创建一个非标准的数据库,你应该考虑使用globals。 3.3 特殊情况 3.一个有两个层级的“树”,每个二级节点都有固定数量的分支 你可能已经猜到了:这是一个使用globals的表格的可选实现形式。我们把它与之前的那个进行比较。 两层树中的表 VS .一层树中的表 缺点 优点 1.插入速度慢,因为节点的数量必须设置为与列的数量相等。2 更高的硬盘空间消耗,因为带有列名的全局索引(如数组索引)占用了硬盘空间,并且每一行都是重复的。 1.对特定列的值的访问速度更快,因为你不需要解析字符串。根据我的测试,对于2个列来说,它的速度要快11.5%,对于更多的列来说,速度甚至更快。2. 更容易改变数据模式3. 更容易阅读代码 结论:没什么可写的。由于性能是globals的关键优势之一,使用这种方法几乎没有任何意义,因为它不可能比关系型数据库中的普通表工作得更快。 3.4 一般情况。"树"和有序键 任何可以被表示为"树"的数据结构都能完美地适合globals。 3.4.1 有子对象的对象 这就是传统上使用 globals 的领域。在医疗领域有无数的疾病、药物、症状和治疗方法。为每个病人创建一个有一百万个字段的表是不合理的,尤其是99%的字段都是空白的。 想象一下,一个由以下表格组成的SQL数据库。"病人"~10万个字段,"药物 "10万个字段,"治疗 "10万个字段,"并发症 "10万个字段,等等。作为一个替代方案,你可以创建一个有数千个表的数据库,每个表都代表一个特定的病人类型(它们也可以重叠!)、治疗、药物,以及这些表之间关系的数千个表。 Globals就像一只手套一样适合医疗行业,因为它使每个病人都有完整的病例记录、治疗方法列表、使用的药物及其效果--所有这些都以"树"的形式存在,而不会像关系型数据库那样在空的列上浪费太多的磁盘空间。 当任务是最大限度地积累和系统化关于客户的各种个人数据时,Globals用于记录个人各种细节的数据库非常有效。这对于医疗、银行、营销、档案和其他领域来说尤其重要。 不言而喻,SQL也能让你只用几个表(EAV, 1,2,3,4,5,6, 7,8)来模拟一棵树, 但它要复杂得多,工作速度也慢。从本质上讲,我们必须写一个基于表的Global,并将所有与表有关的routines隐藏在一个抽象层下。用高层技术(SQL)来模拟底层技术(globals)是不正确的 改变巨大的表的数据模式(ALTER TABLE)可能需要相当长的时间,这并不是什么秘密。例如,MySQL在执行ALTER TABLE ADD|DROP COLUMN操作时,会将所有数据从旧表复制到新表(我在MyISAM和InnoDB上测试过)。这可能会使一个有数十亿条记录的生产数据库停滞几天,甚至几周。 如果我们使用globals,改变数据结构对我们来说是没有成本的。我们可以在任何时候向层次结构中任何级别的任何对象添加任何新的属性。需要对分支进行重命名的改变可以在后台模式下应用,同时数据库也会启动并运行。 因此,当涉及到存储具有大量可选属性的对象时,globals工作得非常好! 我也提醒一下各位,对任何一个属性的访问都是即时的,因为在global中,所有的路径都是一个B-tree。 在一般情况下,基于globals的数据库也是一种面向文档的数据库,支持存储分层信息。因此,在存储医疗卡的领域,面向文档的数据库可以有效地与globals竞争。 但是,现在还不完全是这样。 让我们以MongoDB为例。在这个领域,它输给了globals,原因如下: 1.文档大小 存储单元是一个JSON格式的文本(确切地说,是BSON),最大尺寸为16MB左右。引入这个限制的目的是为了确保JSON数据库在解析过程中不会变得太慢,当一个巨大的JSON文档被保存到数据库中时,需要处理特定的字段值。这个文件应该有关于病人的完整信息。我们都知道病人卡可以有多“厚”。如果卡的最大大小被限制在16MB,它就会立即过滤掉卡中包含核磁共振扫描、X光扫描和和其他材料的病人。Global的一个分支可以有数千兆字节和数万兆字节的数据。这算是说明了一切,但我还可以告诉你更多。 2. 创建/改变/删除病人卡上的新属性所需的时间 这样一个数据库需要将整个卡片复制到内存中(大量的数据!),解析BSON数据,添加/改变/删除新的节点,更新索引,将其全部打包回BSON并保存到磁盘。而一个Global只需要寻址必要的属性并执行必要的操作。3.对特定属性的访问速度 如果文档有许多属性和多级结构,对特定属性的访问会更快,因为Global中的每个路径都是B-Tree。在BSON中,你需要对文档进行线性解析以找到必要的属性。 3.3.2 关联数组 关联数组(即使是嵌套数组)可以完美地与globals一起工作。例如,这个PHP数组将看起来像3.3.1中的第一个插图。 $a = array( "name" => "Vince Medvedev", "city" => "Moscow", "threatments" => array( "surgeries" => array("apedicectomy", "biopsy"), "radiation" => array("gamma", "x-rays"), "physiotherapy" => array("knee", "shoulder") ) ); 3.3.3 层次化的文件。XML、JSON 也可以很容易地存储在globals中并以不同的方式进行分解。 XML 将XML分解成globals的最简单方法是将标签属性存储在节点中。而如果你需要快速访问标签属性,我们可以把它们放在单独的分支中。 <note id=5> <to>Alex</to> <from>Sveta</from> <heading>Reminder</heading> <body>Call me tomorrow!</body> </note> 在COS中,代码将看起来像这样。 Set ^xml("note")="id=5" Set ^xml("note","to")="Alex" Set ^xml("note","from")="Sveta" Set ^xml("note","heading")="Reminder" Set ^xml("note","body")="Call me tomorrow!" 注意:对于XML、JSON和关联数组,你可以想出很多方法来在globals中显示它们。在这个特殊的例子中,我们没有在 "note "标签中反映嵌套标签的顺序。在^xml global中,嵌套标签将按字母顺序显示。为了精确地显示顺序,你可以使用下面的模式,比如: JSON 这个JSON文档的内容显示在第3.3.1节的第一个插图中 var document = { "name": "Vince Medvedev", "city": "Moscow", "threatments": { "surgeries": ["apedicectomy", "biopsy"], "radiation": ["gamma", "x-rays"], "physiotherapy": ["knee", "shoulder"] }, }; 3.3.4 由等级关系约束的相同结构 例子:销售办公室的结构组成,传销组织结构中人的位置,国际象棋的首秀。 关于首秀的数据库。 你可以使用棋力评估作为Global的节点索引的值。在这种情况下,你需要选择一个具有最高权重的分支来确定最佳棋步。在Global中,每一层的所有分支都将按棋力进行排序。 销售办公室的结构,传销公司的人。节点可以存储一些反映整个子树特征的缓存值。例如,这个特定子树的销售人员情况。我们可以在任何时候获得关于任何分支的销售成果的确切信息。 4. 使用globals有好处的情况 第一栏包含了使用globals会在性能方面给你带来相当大的优势的情况列表,第二栏则包含了使用globals会简化开发或数据模型的情况列表。 Speed 数据处理/呈现的便利性 1. 插入[每层都有自动排序],[通过主键建立索引]。2. 移除子树3. 具有大量嵌套属性的对象,你需要对其进行单独访问4. 分层结构,可以从任何一个分支开始,甚至是不存在的分支,进行子分支的遍历。5.深入的树形遍历 1.具有大量非必需[和/或嵌套]属性/物质的对象/物质 2.无模式的数据--经常可以添加新的属性和删除旧的属性。3.你需要创建一个非标准的DB。4.路径数据库和解决方案树。当路径可以方便地表示为一棵"树“的时候。5.在不使用递归的情况下删除层次结构 下一章继续第三篇,未完待续!(待翻译) "Globals - Magic swords for Storing Data. Sparse Arrays. Part 3" Disclaimer: this article and my comments on it reflect my opinion only and have nothing to do with the official position of the InterSystems Corporation.
文章
Hao Ma · 六月 13, 2023

IRIS镜像的监控和警告

在维护IRIS的镜像前,管理员需要清楚的了解以下一些概念: ## Mirror的切换模式(failover mode) 切换模式在镜像监视器里被翻译成”故障转移模式“。 有两种模式: - Agent Controlled模式: - Arbiter Controlled模式:(页面上翻译为“仲裁程序受控制”) 通常情况,生产环境的镜像是安装了arbiter(仲裁者)的。Mirror启动时,在还没有连接上arbiter的时候,自动进入Agent-Controlled模式。而后当两台机器,主机,备机都连通了Arbiter,会保持在这个模式。 - 主备之间有连接; - 又都连到arbiter; - backup is active, 满足上面的条件,就进入arbiter controlled mode。而如果主备的任一方,失去了和arbiter的连接,或者备用侧丢了active, 开始尝试连接另一方,退回到agent-controlled模式。 ## Mirror同步成员的状态 [Mirror Member Journal Transfer and Dejournaling Status](https://docs.intersystems.com/irisforhealth20231/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_manage#GHA_mirror_set_status). 请注意,这里面有两个概念:一个是**Mirror成员的状态**,一个是**Journal传输和Dejournaling的状态**。下面的图中是3个字段: STATUS, Journal传输,Dejournaling. **STATUS** 镜像成员的状态。 正常工作状态 - 对于同步成员,是Primary(主), Backup(备机)。 - 对于异步成员,正常状态是Connected(已连接) - In Trouble : 如果主机In Trouble, 是失去了到backup的连接。备机收到主机的同步数据是要返回证实(Ack)消息的。一旦出现问题,主机无法收到备机的Ack, 主机就会把备机标为"In trouble", 从此再也不会向备机发同步数据。 - Transition: 暂时状态,进程正在查看一个成员的状态,很快会转换到一个稳定状态。 如果在mirror配置的member中发现了primary,本机会进入Synchronizing状态,否则自己会尝试进入primary状态。 - Sychronizing: 从Primary接收journal,同步数据库。 ## Journal Transfer and Dejournaling Status Journal Transfer是主机向其他成员发送Journal文件。而Dejournal是把Journal文件读入数据库。 对于backup或者asycn成员,**Journal Transfer**状态表示镜像成员是否有来自主数据库的最新日志数据,如果没有,则表示日志传输的落后程度,**Dejournaling**表示从主数据库收到的所有日志数据是否已经被dejournaled(应用到成员的镜像数据库),如果没有,则表示dejournaling的落后程度。 上图中显示的是正常的状态,其中主机 Journal Transfer 和 Dejournaling 都是N/A, 表示不适用。 对于其他成员,我们分开看: Journal Transfer状态 - Active: backup的正常状态。说明backup从primary收到了最新的journal。注意哪怕是Dejournal状态只是“x秒落后“,而不是"被捕获",Journal Transfer状态也可以是Active,只要是从主机收到了最新的Journal更新。 - Caught up(被捕获) : 备机被捕获状态,说明备机从主机收到了最新的journal数据,但主机没有在等待备机的证实消息。 这通常是一个暂时的过程,当备机在连接主机的时候会出现。 异步成员,因为不需要向主机发证实,所以正常的状态就是“被捕获” If the Primary Failover Member does not receive an acknowledgment from the Backup every Heartbeat Interval period, it demotes the Backup system from Active status to Catch-Up mode. - time behind (多少秒落后) - Disconnected on time(断开): 在一个时间点上这个成员和primary断开了。 Dejournaling状态 - Caught up - time behind - Disconnected on time - Warning! Some Databases need attention - Wanring! Dejournaling is stopped **正常状态下的图;** 备机Backup MirrorB, Journal Transfer是Active, Dejournaling是Caught up, 异步机器MirrorDR的Journal Transfer状态和Dejournaling状态都是Caught up. 表示它们收到了最新的journal数据,并且也都把最新的global修改写入了自己的数据库。 ## Mirror的自动切换 Mirror的核心是自动切换。Backup接替主机的工作有两个前提:1. 备机在同步(Active) 状态, 2. 主机不能正常工作。在这两个前提下,我们来看看自动切换的触发条件,涉及主机,备机,仲裁机之间的通信, **自动切换触发条件** 1. Primary要求Backup接替。这种情况,主机会发生一个请求消息给备机, 要求备机接替。 - 主机IRIS正常退出 - 主机发现自己hung 2. 备机收到arbiter的请求,报告失去了到主机的连接。 仲裁机要求是和外部系统以及应用服务器部署在一个网段的。如果仲裁机无法联络主机,可以认为其他的应用系统和服务器也无法连接主机。有可能主机宕机, 也有可能主机还在正常工作,但外界已经无法联络它了, 这时候也是需要备机接手的。 这时备机也要再去核实一下,是不是能联络到主机。如果能联络到, 备机会发请求让主机Down。如果不能, 说明主机要么死了, 要么失联了, 备机先接手,等联络上再让对方force down. 3. 从主机的ISCAgent收到消息,报告Primary已经down or hung. 在agent-controlled的情况。 primary的服务器还活着。备机主动去问主机的agent, 一旦agent报告主机死了, 那备机就可以上位了。 ## Mirror的进程 管理员应该了解mirror涉及的那些进程。当出现故障时,这些进程名字,或者称为User, 经常会出现在message log记录的故障描述中。 On Primary Failover Member(主机) ![image](/sites/default/files/inline/images/image-20230519103522380.png) 我们来一个个的看看这些进程: - Mirror Master: 系统启动时自动启动,负载mirror control 和管理。 - Mirror Primary: 出向数据传输通道。 上图中有两个Mirror Primary进程,状态时RUNW, 一个连接MirrorB, 一个连接MirrorDR. - Mirror Svr: Rd*: 入向证实通道(inbound acknowledgement), 也是单向的。 上图中同样有两个此进程,状态都是READ, IP地址分别是MirrorB和MirrorDR. - Mirror Arbiter: 到aibiter的通信进程,注意它的状态是"EVTW", 也是个单向写的频道。 On Backup Member/Async member(备机) ![image](/sites/default/files/inline/images/image-20230519103445811.png) Mirror Masht, Mirror Arbiter不再重复解释,我们看看其他进程是干什么的。 - Mirror JrnRead: Mirror Journal从Primary发送到backup是先写到硬盘的。 JrnRead进程把收到的journal同步读到内存里,然后才进行下一步,Dejournal的工作。 - Mirror Dejour: backup机器的dejournal job进程。它把从Primary收到的journal中记录的global改变(set and kill)保存到本机的镜像数据库。 - Mirror Prefetch: 这个稍微有点难懂。当收到的journal修改中包括了使用当前backup的journal中已有的内容时,比如收到了一个修改:set ^A=^B+1, 而^B当前存在backup里, Prefetch进程会把^B从硬盘拿到内存,以加快dejournal的速度。 - Mirror Backup: two-way channel, 把收到的primary的journal写到backup的mirror journal,并且返回证实(ACK) 这里我省略了在DR上的进程,如果有兴趣,请自己查看文档。 ## MIRROR状态的监控 根据不同的场景,查看Mirror的状态有以下几种途径 ### **[使用镜像监视器](https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls?KEY=GHA_mirror_manage#GHA_mirror_monitor_portal)** ### 使用^MIRROR 如果您只是要简单的获得Mirror成员的状态,最直接的方法是使用^Mirror程序。 我们先看看在IRIS Terminal下^MIRROR的执行。 ```bash %SYS>do ^MIRROR 1) Mirror Status 2) Mirror Management 3) Mirror Configuration Option? 1 1) List mirrored databases 2) Display mirror status of this node 3) Display journal file info 4) Status Monitor Option? 4 Status of Mirror MIRRORTEST at 08:09:24 on 05/19/2023 Arbiter Connection Status: Arbiter Address: arbiter|2188 Failover Mode: Agent Controlled Connection Status: This member is not connected to the arbiter Journal Transfer Member Name+Type Status Latency Dejournal Latency -------------------------- --------- --------------- -------------- MIRRORA Failover Primary N/A N/A Press RETURN to refresh, D to toggle database display, Q to quit, or specify new refresh interval D Database display is now on Status of Mirror MIRRORTEST at 08:09:29 on 05/19/2023 Arbiter Connection Status: Arbiter Address: arbiter|2188 Failover Mode: Agent Controlled Connection Status: This member is not connected to the arbiter Journal Transfer Member Name+Type Status Latency Dejournal Latency -------------------------- --------- --------------- -------------- MIRRORA Failover Primary N/A N/A Mirror Databases: Record To Name Directory path Status Dejournal ------------- ----------------------------------- ----------- ----------- TEST /isc/mirrorA/TESTDB/ Normal N/A Press RETURN to refresh, D to toggle database display, Q to quit, or specify new refresh interval ``` **在操作系统中执行^MIRROR** 您可以把以下的代码写入您的脚本语言,查看mirror的状态 ```bash irisowner@mirrorA:~$ iris session iris -U "%sys" "Monitor^MIRROR" Status of Mirror MIRRORTEST at 02:57:08 on 06/13/2023 Arbiter Connection Status: Arbiter Address: arbiter|2188 Failover Mode: Arbiter Controlled Connection Status: Both failover members are connected to the arbiter Journal Transfer Member Name+Type Status Latency Dejournal Latency -------------------------- --------- --------------- -------------- MIRRORA Failover Primary N/A N/A MIRRORB Failover Backup Active Caught up MIRRORDR Disaster Recovery Connected Caught up Caught up Press RETURN to refresh, D to toggle database display, Q to quit, or specify new refresh interval q Doneirisowner@mirrorA:~$ ``` 或者更简单的,只查看本机的mirror成员状态: ```bash irisowner@mirrorA:~$ iris session iris -U "%sys" "LocalMirrorStatus^MIRROR" This instance is a Failover member Status for mirror MIRRORTEST is "Primary" Current mirror file #2 ends at 681224 Min trans file #2 min trans index: 680744 irisowner@mirrorA:~$ ``` 如果您熟悉ObjectScript, 也可以使用`$SYSTEM.Mirror`类的各个method来查看: ```bash irisowner@mirrorB:~$ echo "write \$SYSTEM.Mirror.GetMemberStatus(),! halt" |iris session iris -U "%sys" Node: mirrorB, Instance: IRIS %SYS> Backup irisowner@mirrorB:~$ ``` 如果您要查看更多的内容,您可以更多的使用%SYSTEM.Mirror类的其他方法,比如%SYSTEM.Mirror.GetFailoverMemberStatus(.pri,.alt), $SYSTEM.Mirror.ArbiterState()等等。 ### 使用Mirror_MemberStatusList存储过程 如果您从第3方的工具查询mirror成员的状态,还有一个简单的方案,就是调用%SYS命名空间的存储过程。下图是从iris管理门户调用的截图,你可以使用任何SQL客户端调用。 如果是从iris里执行, ``` %SYS>do ##class(%ResultSet).RunQuery("SYS.Mirror","MemberStatusList") Member Name:Current Role:Current Status:Journal Transfer Latency:Dejournal Latency:Journal Transfer Latency:Dejournal Latency:Display Type:Display Status: MDCHCNDBSL1.HICGRP.COM/STAGE:Primary:Active:N/A:N/A:N/A:N/A:Failover:Primary: MDCHCNDBSL2.HICGRP.COM/STAGE:Backup:Active:Active:Caught up:Active:Caught up:Failover:Backup: CDCHCNDRSL.HICGRP.COM/STAGE:Async:Async:Caught up:Caught up:Caught up:Caught up:Disaster Recovery:Connected: ``` ### 通过SNMP获得 如果使用监控工具,您可以通过SNMP获得Mirror的状态,下面是最新的ISC-IRIS.mib中有关Mirror得指标部分。 ``` .4.1.12 = irisMirrorTab | Table of current Mirror Members status and information -- .4.1.12.1 = irisMirrorRow | Conceptual row for Mirror status and metrics | INDEX = irisSysIndex, irisMirrorIndex -- .4.1.12.1.1 = irisMirrorIndex | unique index for each Mirror Member | INTEGER -- .4.1.12.1.2 = irisMirrorName | Name of the mirror this system is a member of | STRING -- .4.1.12.1.3 = irisMirrorMember | Mirror member name | STRING -- .4.1.12.1.4 = irisMirrorRole | "Primary", "Backup", or "Async". | STRING -- .4.1.12.1.5 = irisMirrorStatus | "Active" or "Activate". | STRING -- .4.1.12.1.6 = irisMirrorJrnLatency | Mirror journal latency "Caught up", "Catchup", or "N/A". | STRING -- .4.1.12.1.7 = irisMirrorDBLatency | Mirror database latency "Caught up", "Catchup", or "N/A". | STRING ``` ## MIRROR的日志和告警 通常情况下, 维护人员是通过mirror的日志和警告来获得Mirror状态,Mirror成员之间的连接情况,而不必须定时的用命令或者调用存储过程来查看。 Cache'和IRIS的日志和警告保存在两个文件: console.log/messages.log和alert.log, 其中alert.log中记录了console.log/messages.log中级别为2,3的记录, 并必须实时发送给管理员。有关这部分内容,请参考在线文档,或者我的帖子: 我们来看看在日志中有哪些mirror的记录: **Becoming primary mirror server** 系统固有的通知消息, level =2。当一个iris实例从备机变成了主机,此信息会写到此实例的alert.log, 同时发送给管理员。 可以查看这个[链接](https://docs.intersystems.com/iris20231/csp/docbook/DocBook.UI.Page.cls?KEY=GCM_monitor#GCM_monitor_errors)。 在Mirror切换时,管理员除了从刚刚接手的机器中收到Becoming primary mirror server的通知。如果原来的主机没有宕机或者从宕机中恢复,它也会将引起切换的故障从alert.log发送给管理员,是一个level2, 或者level3的记录。 **Arbiter connection lost** level =2 , 自动发送给管理员。 当主机和arbiter失去连接后,在主机上会出现此警告。此时在备机上会出现“Switched from Arbiter Controlled to Agent Controlled failover on request from primary”的提示,是个level0的信息。 **MirrorServer: Connection to xxxx(backup) terminated** **MirrorServer: Connection to MIRRORDR (async member) terminated** 当主机和备机(backup)失去连接,在主机上会出现level2的警告。 而和异步成员丢失连接,主机会出现level1的消息。尽管level1的消息不能自动通知管理员,但这时如果同时监控该异步成员的alert.log, 通常会有level2的警告消息发出,能提醒管理员检查MIRRORDR这个镜像成员的状态。 举例说明:如果在MirrorDR中操作系统重启,IRIS启动后会出现这样的level2的警告:“Previous system shutdown was abnormal, ^SHUTDOWN forced down” **Async member for MirrorSetName started but failed to connect to primary** level =2 , 自动发送给管理员 其他更多的关于Mirror出错的level2, 也就是警告记录, 比如: - Could not open mirror journal log to read checksum, errno = 2 - Preserving all mirror journal files for offline failover member - Server^MIRRORCOMM(d): Failed to notify MIRRORB for mirror configuration change - Failed to become either Primary or Backup at startup 这不是个完整的列表,实际环境中会出现各种各样的告警通知。读懂这些通知,需要管理员了解镜像的原理,架构,以及上面介绍的镜像状态和进程的功能。 除此之外,绝大多数的level2日志的同时,会有更多的level0,level1的有关mirror变化的记录。这些内容不需要通知管理员,只是用于分析问题。 如图,下面是在一个messages.log里一个iris从备机变成主机的过程。 ``` 06/13/23-07:16:25:472 (2189) 0 [Generic.Event] MirrorClient: Switched from Arbiter Controlled to Agent Controlled failover on request from primary 06/13/23-07:16:26:274 (2189) 1 [Generic.Event] MirrorClient: Mirror_Client: Primary closed down, last # read = 504 06/13/23-07:16:26:301 (2189) 0 [Generic.Event] MirrorClient: Backup waiting for old Dejournal Reader (pid: 2190, job #31) to exit 06/13/23-07:16:27:394 (2189) 0 [Generic.Event] MirrorClient: Set status for MIRRORTEST to Transition 06/13/23-07:16:28:477 (1996) 0 [Utility.Event] [SYSTEM MONITOR] Mirror status changed. Member type = Failover, Status = Transition 06/13/23-07:16:30:261 (2177) 0 [Utility.Event] Returning to restart, old primary reported: "DOWN 06/13/23-07:16:31:524 (11721) 0 [Utility.Event] Applying journal data for mirror "MIRRORTEST" starting at 1538184 in file #2(/isc/mirrorB/mgr/journal/MIRROR-MIRRORTEST-20230613.001) 06/13/23-07:16:31:804 (2177) 0 [Utility.Event] Manager initialized for MIRRORTEST 06/13/23-07:16:31:986 (2177) 0 [Utility.Event] MIRRORA reports it is DOWN, becoming primary mirror server 06/13/23-07:16:32:381 (2177) 0 [Generic.Event] INTERSYSTEMS IRIS JOURNALING SYSTEM MESSAGE Journaling switched to: /isc/mirrorB/mgr/journal/MIRROR-MIRRORTEST-20230613.002 06/13/23-07:16:32:426 (2177) 0 [Utility.Event] Scanning /isc/mirrorB/mgr/journal/MIRROR-MIRRORTEST-20230613.001 06/13/23-07:16:32:479 (2177) 0 [Utility.Event] No open transactions to roll back 06/13/23-07:16:32:485 (2177) 0 [Generic.Event] MirrorServer: New primary activating databases which are current as of 1538184 (0x00177888) in mirror journal file #2 06/13/23-07:16:32:488 (2177) 0 [Generic.Event] Changed database /isc/mirrorB/TESTDB/ (SFN 5) to read-write due to becoming primary. 06/13/23-07:16:32:924 (2177) 0 [Utility.Event] Initializing Interoperability during mirror initialization 06/13/23-07:16:32:930 (2177) 2 [Utility.Event] Becoming primary mirror server ``` 更多的有关mirror监控和排除的问题, 请各位留言。 谢谢
文章
Claire Zheng · 七月 14, 2022

中南大学湘雅医院冯嵩:业财融合一体化信息平台的建设

2022年7月1日,由国家卫生健康委医院管理研究所指导、《中国数字医学》杂志社有限公司主办、《中国数字医学》杂志社陕西通联站协办、东华医为科技有限公司与InterSystems中国支持的“医院数字化转型研讨会”在西安召开。以下为中南大学湘雅医院网络信息中心主任冯嵩在此次论坛上的分享。 大家好,今天非常有幸参加这次“医院数字化转型研讨会”,借此机会向大家汇报一下湘雅医院业财融合运营一体化平台的建设情况,包括建设思路和我们的建设的具体落地实施情况。我主要从建设的背景、建设思路和建设落地情况三个方面与大家分享。 01 建设背景 1.1 两个趋势 医院信息化发展给医院建设带来了巨大变革,从传统信息化时代跨越到数据化时代,医院信息化建设已不再仅局限于医院的内部,而通过互联网不断向外部去延伸,不仅连接医院的用户,同时也在不断连接企业、政府等,基于互联网建立各种连接,已经成为了一个趋势。 另外一个趋势就是平台化趋势,基于平台化的统一数据建设的基础服务能力,运用可配置的弹性的、动态的配置方案来实现个性化的管理,平台化已经成为了信息化建设的一种趋势。 1.2 政策指导 数字化转型,实际上是以数据为核心、构建数字化管理服务体系,是一个不断打破数据孤岛的过程。医院通过建设数据平台和集成平台,将分散在不同系统的数据资源整合起来,形成数据资源、数据资产,通过统一的数据资源来支撑医院的精细化管理,加快整个医院智慧化的建设。目前,医院的信息化建设也在不断地从临床走向全面信息化建设,我们通过整合各类运营管理的资源,建设统一标准的数据服务体系,实现对数据的统一管理,形成了统一的运营数据平台,为医院的运营和管理提供数据支持,打造精细化管理亮点,并运用一系列新技术,建设智慧医院。 近年来,国家从政策方面不断提倡“深化体制改革、加强运营管理”,特别是在2020年的《关于加强公立医院运行管理的指导意见》中提出,医院要以资源配置、流程再造、绩效考核为导向,建立全面的运营管理体系。这个体系就要求医院建立全面的、统一管理的信息体系,在这个基础上形成考核支撑和决策支撑。 1.3 医院运营管理需求及存在的问题 根据国家卫健委最新政策要求,公立医院运营管理需要推进“数据+财务+业务”的融合,提出了对业财融合一体化运营体系的构建需求。 在这个体系中,要将医院预算管理、会计、成本等和业务流程融合在一起,实现运营过程中的风险管控,提高运营效率和运营过程的可计划性,严格按照预算去推进落实医院的各项工作。因此需要建立运营数据中心,包括运营数据中心、运营管理系统、运营数据仓库、决策分析平台。 医院内部实际上存在很多数据的问题,首当其冲的是数据不准确问题。主要体现在以下几点。 第一,数据统计口径没有根据用途统一,形成标准。有些数据是用于上报;有些是用于院内质控;有些是用于成本统计;有些是用于绩效与奖金。用途不同,口径与计算方法、统计源头都会不同。 第二,信息化建设不完整和不同建设时期厂家对医院数据定义的混乱。比如HIS数据、病案数据、HRP(人财物管理系统)数据之间的冲突,数据覆盖不足与数据统计口径的模糊状态下,医院职能科室只能通过多年磨合以半自动半手工方式纠偏完成数据清洗。 第三,数据团队对医院日常管理的需求无法深入。团队缺乏数据对接,清洗,标化能力,对HIS、EMR、HRP等系统的前端业务缺乏深刻理解,缺乏从数据到系统,再到与医院高层管理意图贯通理解的能力。 运营管理系统面临的第二个大问题是“决策无模型”。现在很多医院都成立了运营管理部门,除负责绩效核算以外,职能还包括组织推动各项运营管理措施任务有效落实;组织开展运营效果分析评价,撰写运营效果分析报告。医院的管理非常复杂与多样,从开始的追求规模化赢利,逐渐转变为现在的精准化赢利模式的变革,会涉及大量的量化管理模型。不同医院在不同的发展阶段侧重点会不同,没有一个管理模型可以一直适用。例如,领导想了解手术患者预约为什么已经排到半年后,能够从这个数据中发现我们这是个瓶颈在哪里?这就需要有一个决策模型。 02 运营数据管理平台建设思路 针对上述问题,我们医院启动了整体搭建运营数据管理系统的建设,形成了一套完整方案,整体规划搭建医院运营数据架构,然后强化运营数据的主数据管理,建立数据中台,基于中台融合业务流程,包括数据分析和挖掘的应用场景。 首先,我们对整个平台体系进行了整体设计。我们做了一个分层数据架构,建立了数据的基础,我们叫做数据的治理层,主要完成数据的业务标准、模型标准、主数据标准和元数据的相关的管理。 第二,在这个基础上,我们建立数据的加工层,主要是完成数据的采集、汇聚、加工,包括建立完整的数据质控体系。 第三,最上面就是我们的数据应用层,我们针对医院的运营管理、协同服务、职能服务、医疗保障、行政后勤供应等体系提供数据相关的支撑服务。 2.1 强化主数据管理 在建设过程中,我们要求要有统一的标准、统一的来源、统一的接口,去完成相关的数据的支撑。首先就是强化主数据管理,因为医院的主数据肯定不只涉及我们运营部门,主数据应该是全院一盘棋,实现统一标准、统一来源、统一接口、统一服务。 这些统一的接口不仅仅可服务于我们的运营管理系统,还可以服务于临床系统和各类对外服务应用,这样就保证了我们整个基础数据的统一,也便于后续整个数据的共享共用,在强化主数据管理的基础上,我们更进一步把主数据进行了细分管理。 我们将主数据分为人事、财务、物资、项目、合同管理业务等等10个类进行标准制定。在制定标准的过程中,我们也发现有些数据是没有标准的。比如组织主数据,人力资源管理架构中组织单元、财务会计核算组织、成本管理中心、预算管理组织没有建立对应关系,带来了大量的手工数据提取、归集、拆分的工作,影响多个部门工作效率,数据支撑作用弱,决策成本高,后续的分析统计是无从下手。所以我们把整个组织架构进行重新梳理,重新做好相关的映射关系,保证了组织架构能够在运营体系中得到很好的适配和应用。 我们也制定了主数据管理规范,一是建立主数据管理组织架构,哪些人和对组织对数据负责,相应的管理部门是什么?管理岗位是什么?数据审核应该谁来完成?等等。第二是在整个业务流程中强化对数据标准的执行和管理,包括数据的事前、事中、事后的整个管理。 同时,我们制定了很多相关的管理制度,包括《主数据管理办法》《主数据标准规范》《主数据提案指南》《主数据维护细则》《主数据管理工具操作手册》等等,以制度手段确保主数据管理的规范和一致性。 2.2 规划好数据中台,实现数据的业务化 我们通过这些数据的汇集,从分散的数据系统里面,形成统一的运营数据平台。 业务数据方面,各部门以前都是各自建系统,数据分散在不同的系统里,这一次我们在建设的过程中,把该停的系统停掉,该统一的标准统一起来,保证数据在标准基础上实现了整合,保证了数据一致性,这样才能为事后数据分析和决策提供好数据基础。 数据中台在建设过程中一定要打破部门之间的壁垒,要用业务流程去串联各个部门,那么最后整个的数据的使用模式就会发生变化——以前是我们有什么数据就只能做什么事,各系统的数据基本上是只能够沿着该系统业务范围内去做相关的分析。而通过统一的数据管理以后,我们是根据管理需求拿数据——就这样,我们逐步实现了数据的业务化。 我们建立了统一的数据中心,数据不仅来源于院内的临床系统,还包括财务系统、HRP、SPD、供应商平台(如设备编码、材料编码等)、公共数据、社会数据(发票验真、银联号)等,通过多种方式完成数据汇聚后,将收集起来的数据通过分类形成数据标签,建立数据安全策略,同时结合数据质控确保数据质量,这样就形成了建立统一服务平台的基础。 2.3 规范数据分析流程 传统的数据分析流程是我们根据各个临床的需求收集数据,现在则转变为在系统建设初期,我们充分了解领导的决策需求,根据需求建立决策模型,然后根据模型进行数据汇集,继而形成我们的数据的采集方式和相关的数据分析体系。 在整个数据挖掘的过程中,我们首先要满足管理层决策需求,挖掘管理决策的痛点,通过数据归一化和结构化统一数据标准,把数据能够更好地应用于管理决策的分析体系。 3 业财一体化系统的实践和落地 3.1 三阶段实施建设业财一体化系统 首先我们把业财一体化系统的实施规划分成三个阶段。 第一阶段是财务信息化阶段,这个阶段我们建立起一些基本的财务系统,实施完成后,实现了费用核算域和项目经费域业务全程信息化,为下一步实现财务智能化打下坚实的基础。 第二阶段是以整体的财务的信息化建设为依据,推动全面实现财务智能化,实施完成后,实现医院全业务流程信息化,保证医院支出全流程可控、可视、可查。实现医院收入稽核准确高效。 第三阶段我们进行了财务的数字化建设,实施完以后,实现医院业务数据和财务数据汇集、沉淀。实现医院数据价值开发,为领导决策、医院数字化发展提供基础。 在这个过程中其实牵涉到很多系统,保证不同的厂商、不同的系统的统一。首先是定义了统一标准的接口,收集了相关系统数据和功能,制定了系统之间的数据流、业务流程,形成了一套完整的规范。其次,在此基础上,建立了系统接口的规范,针对HIS系统制定了至少8个接口标准,包括医保对HIS接口,HIS对医保的接口,HIS对营收的接口,HIS对智能报账的接口等等,我们在这个基础上制定结构,打通的 OA审批。最后,我们统一门户登录后,能够完成医院所有运营管理工作。 第二,梳理业务流程。比如传统的报账流程是人工初审、线下报账和预算管理,我们通过统一梳理,实现了标准化的线上报账,申报后,通过系统和税务系统对接,对发票进行真伪查验,从而完成整个报账过程。 再如采购系统,我们实现了采购一体化,实现了从采购申请、预算审批的整个完整流程的一体化,在这个过程中我们要打通不同系统,通过信息匹配来完成整个发票和相关证明材料的匹配。此外我们开放了网上的各项的应用来提升我们的效率,包括自主采购,比如医院采购部门可以我们通过我们的网上商城去提出需求,通过审批,然后形成整个的网络的一体化的采购。 医院在预算、绩效、成本方面是一体化建设,基于运营数据,我们以“三位一体”的理念打造整个财务的体系,从而实现了全面预算的管理:首先,实现了三级预算的组织,实现了从医院科室到专科的全业务预算。其次,通过医院的长期规划制定专项的预算,形成预算的跟踪分析报告;第三,针对预算建立了预算模型,从医院的战略出发,制定基本预算和科研预算的分类,不同预算在使用过程中都进行了预算管控。 在成本部分,我们通过建立统一的成本的决策知识模型,建立了成本的统一管理。 3.2 管理决策支持体系 所有的信息最后汇聚到我们运营数据中心,在数据基础上建立了管理决策的支持体系。 首先建立了指标库和知识库,在应用的时候,只要业务部门有什么需求,我们会把这些指标库进行组合,形成相关的管理主题,提供给业务部门做相关的支撑。 此外,建立了管理决策的支持模型,管理层可以通过统一的决策支持平台看到医院的运行状态,包括医院每天的运营报表、科室层面的收入工作量、人员结构、患者特征疾病等等基本情况分析,个体层面的医生的收入、人事和工作量排名,考评层面的核心医生考评体系、科室等等,我们还对学科建立了分析主题,包括学科人员结构、学历支撑、认知等等这些结合起来,进行了统一分析决策,能够通过这个平台比较分析去科室优势和现有不足。在运营方面,对医院患者的转化率可以进行统一的分析,帮助医院寻找“堵点”。 面向未来,医院运营管理会更加精细化。管理从“终末管理”转为“环节管理”,从“事后提醒”转为“即时反馈”,从“事务孤岛”转为“互通关联”,从“经验管理”转为“标准管理”。随着医院运营体系不断建设完善,信息化的运营体系将为医院管理决策发挥更大价值。谢谢大家!
文章
姚 鑫 · 四月 11, 2021

人月神话

# 人月神话 # 焦油坑 1. 编程系统产品开发的工作量是供个人使用的,独立开发的构件程序的9倍。我估计软件构件产品化引起了3倍工作量,将软件构件整合成完成系统所需要的设计,集成和测试又强加了3倍工作量,这些高成本的构件在根本上是互相独立的。 2. 编程行业“满足我们内心深处的创造渴望和愉悦所有人的共有情感”,其提供了五种乐趣: - 创建事物的快乐 - 开发对其他人有用的东西的乐趣 - 将可以活动,相互啮合的零部件组装成类似迷宫的东西,这个过程所体现出令人神魂颠倒的魅力。 - 面对不重复的任务,不断学习的乐趣。 - 纯粹的思维活动。 3. 同样,这个行业具有一些内在固有的苦恼: - 将做事方式调正到追求完美是学习编程的最困难的部分。 - 由其他人设定目标,并且必须依靠自己无法控制的事物,权威不等同于责任 - 任何创造性活动都伴随着枯燥艰苦的劳动,编程也不例外 - 人们通常期望项目在接近结束时,软件项目能收敛得快一些,然后,情况却是越接近完成,收敛得越慢。 - 产品在完成前总面临着陈旧过时的威胁;只有实际需要时,才会用到最新的设想。 # 人月神话 1. 缺乏合理的时间进度是造成项目滞后的主要原因,它比其他所有因素的总和影响还大。 2. 所有的编程人员都是乐观主义者“一切都将运作良好” 3. 由于编程人员通过纯粹的思维活动来开发,我们期待在实现过程中不会碰到困难。 4. 但是,我们的构思本身是有缺陷的,因此总会有bug。 5. 围绕着成本合算的估计技术,混淆了工作量和项目进度。人月是危险和带有欺骗性的神话,因为它暗示了人员数量和时间可以互相替换的。 6. 在若干人员中分解任务会引发额外的沟通工作量-培训和相互沟通。 7. 关于进度安排,我的经验是1/3计划,1/6编码,1/4构件测试以及1/4系统测试。 8. 作为一门学科,我们缺乏数据估计。 9. 我们对自己的估计技术不确定,因为在管理和客户的压力下,我们常常缺乏坚持的勇气。 10. Brooks法则:为进度落后的项目增加人手,只会使进度更加落后。 11. 向软件项目中增派人手三个方面增加了项目必要的总体工作量:任务重新分配本身和所造成的工作中断:培训新人员:额外的互相沟通。 # 外科手术队伍 1. 同样有两年讲演而且在收到同样培训的情况下,优秀的专业程序员的生产率是较差的程序员的10倍。 2. 小型,精干队伍是最好的-思绪尽可能少。 3. 实际上,绝大数大型编程系统的经验显示,一拥而上的开发方法是高成本,速度缓慢,低效的,开发出的产品无法进行概念上的集成。 4. 一位首席程序员,类似于外科手术队伍的团队架构提供了一种方法-既能获得由少数头脑产生的产品完整性,又能得到多位协助人员的总体生产率,还彻底减少了沟通的工作量。 # 贵族专制,民主政治和系统设计 1. “概念完整性是系统设计中最重要的考虑因素。” 2. “功能与理解上的复杂程度的比值才是系统设计的最终测试标准”,而不是丰富的功能。 3. 为了获得概念完整性,设计必须由一个人或者具有共识的小型团队来完成。 4. 将体系结构方面与具体实现相分离是获得概念完整性的强有力方法。 5. 如果要得到系统概念上的完整性,就必须有人控制这些概念。实际上是一种无序任何歉意的贵族专制统治。 6. 概念上统一的系统能更快地开发和测试。 7. 体系结构,设计实现,物理实现的许多工作可以并行。 # 画蛇添足 1. 尽早交流和持续沟通能使结构师有较好的成本意识,使开发人员获得对设计的信心,并且不会混淆各自的责任分工。 2. 结构师如何成功地影响实现: - 牢记开发人员承担创造性的实现责任;结构师只能提出建议。 - 时刻准备着所指定的说明建议一种实现的方法,准备接受任何其他可行的方法 - 对上述建议保持低调和平静。 - 准备对所建议的改进放弃坚持。 - 听取开发人员在体系结构上改进的建议。 3. 第二个系统是人们所设计的最危险的系统,通常的倾向是过分地进行设计。 # 贯彻执行 1. 即使是大型的设计团队,设计结果也必须由一个或两个人来完成,以确保这些决定是一致的。 2. 必须明确定义体系结构与先前定义不同的地方。 3. 处于精确性的考虑,我们需要形式化地设计定义,同样,我们需要记叙性定义来加深理解。 # 为什么巴比伦塔会失败 1. 巴比伦塔项目的失败是因为缺乏交流以及交流的结果 - 组织。 2. “因为左手不知道右手在做什么,从而进度灾难,功能的不合理和系统缺陷纷纷出现。”由于存在对其他人的各种假设,团队成员之间的理解开始出现偏差。 3. 团队应该以尽可能多的方式进行互相之间交流,非正式地进行简要技术陈述的常规项目会议,共享的正式项目工作手册。 4. 项目工作手册“不是独立的一篇文档”它是对项目必须产生的一系列文档进行组织的一种结构。 5. 项目所有的文档都必须是该结构的一部分。 6. 需要尽早和仔细地设计工作手册结构。 7. 事先制定良好结构的工作手册“可将后来书写的文字放置在合适的章节中”并且可以提高产品手册的质量。 8. 每一个团队成员称该了解所有的材料。 9. 实时更新是至关重要的。 10. 共享的电子手册是能达到所有这些目标的,更好的,更加低廉的,更加简单的机制。 11. 团队组织的目标是为了减少必须的交流和协作量。 12. 组织中的交流是网状么,而不是树状结构, 因此所有的特殊组织机制(往往体现为组织结构中的虚线部分)都是为了进行调整,以克服树状组织结构中交流缺乏的困难。 13. 每个子项目具有里那个领导角色 - 产品负责任,技术主管或结构师。这两个角色的职能有很大的区别,需要不同的技能。 14. 两个角色的任意组合都可以是非常有效的。 # 胸有成竹 1. 仅仅通过对编码部分时间的估计,然后乘以其他部分的相对系数,是无法得出对整项工作的精确估计的。 2. 构建独立小程序的数据不适用于编程系统项目。 3. 当使用适当的高级语言时,程序编制的生产率可以提高五倍。 # 削足适履 1. 除了运行时间意外,程序所占据的内存空间也是主要的开销。 2. 从系统整体出发以及面相用户的态度是软件编程管理人员最重要的职能。 3. 为了取的良好的空间- 时间折中,开发队伍需要得到特定的某种语言或机型的编程技能培训,特别是在使用新语言或者新机器时。 4. 编程需要技术积累,每个项目需要自己的标准组件库。 5. 精炼,充分和快速的程序往往是战略性突破的结果,而不仅仅是技巧上的提高。 6. 这种突破常常是一种新型算法。 # 提纲挈领 1. 在一片文件的汪洋中,少数文档成为了关键的枢纽,每个项目管理的工作都围绕着他们运转。 2. 对于计算机硬件开发项目,关键文档是目标,手册,进度,预算,组织结构图,空间分配以及机器本身的报价,预测和价格。 3. 对文档进行规范化。 4. 项目经理的基本职责是使每个人都向着相同的方向进行。 5. 项目经历的主要日常工作是沟通,而不是做出决定,文档使各项计划和决策在整个团队范围内得到交流。 # 未雨绸缪 1. 第一个开发的系统对于大多数项目并合用。它可能太慢,太大,而且难以使用,或者三者兼而有之。 2. 系统的丢弃和重新设计可以一步完成,也可以一块块地实现,但这是必须完成的步骤。 3. 开发人员交付的是用户满意陈谷,而不仅仅是实际的产品。 4. 用户的实际需要和用户感觉会随着程序的构建,测试和使用而变化。 5. 软件产品易于掌握的特性和不可见性,导致它的构建人员面临着永恒的需求变更。 6. 目标上的一些正常变化无可避免,事先为他们做准备总比假设他们不会出现要好的多。 7. 高级语言的使用,编译时操作,通过引用的声明整合和自文档技术能减少变更引起的错误。 8. 程序员不愿意为设计书写闻到那股,不仅仅是因为惰性,更多的是源于设计人员的踌躇 - 要为自己尝试性的设计决策进行辩解。 9. 为变更组建团队比为变更进行设计更加困难。 10. 只要管理人员和技术人才的天赋允许,老板必须对他们的能力培养给予极大的关注,使管理人员和技术人才具有互换性,特别是希望在技术和管理角色之间自由地分配人手的时候。 11. 对于一个广泛使用的程序,其维护总成本通常是开发成本的40%或更多。 12. 维护成本受用户数目的影响。用户越多,所发现的错误也越多。 13. 缺陷修复总会以20%-50%的几率引入新的bug。 14. 每次修复之后,必须重新运行先前所有的测试用例,确保系统不会以更隐蔽的方式被破坏。 15. 所有修改都倾向与破坏系统的架构,增加了系统的混乱程度。即使是最熟练的软件维护工作,也只是延缓了系统退化到不可修复的混乱状态的进程,以致必须要重新进行设计。 # 干将莫邪 1. 抛开理论不谈,一次分配给某个小组的连续的目标时间快被证明是最好的安排方法,比不同小组的穿插使用更为有效。 2. 主程序库应划分为 1 一系列独立的私有开发库 2 正处在系统测试下的系统集成子库 3 发布版本。正式的分离和进度提供了控制。 3. 高级语言不仅提高了生产率,还改进了调试,bug更少,而且更容易寻找。 # 整体部分 1. 许许多多的失败完全源于那些产品未精确定义的地方。 2. 有时必须回退,推翻顶层设计,重新开始。 3. 系统调试所花费的时间会比预料的更长。 4. 系统调试的困难程度证明了需要一种完备系统化和可计划的方法。 5. 开发大量的辅助调试平台和测试代码是很值得的,代码量甚至可能有测试对象的一半。 6. 必须有人对变更和版本进行控制和文档化。 # 祸起萧墙 1. 一天一天的进度落后比起重大灾难更难以识别,更不容易防范和更加难以拟补。 2. 李成碧必须是具体的,特定的和可度量的事件,能进行 清晰的定义。 3. 慢性进度偏离是士气杀手。 4. 进取对于杰出的软件开发团队是不可缺乏的必要品德。 5. PERT饿准备工作是PERT图使用中最有价值的部分。 6. 第一份PERT图总是很恐怖。 7. 每个老板同时需要采取行动的异常信息以及用来进行分析和早期预警的状态数据。 8. 状态的获取是困难的,因为下属经理有充分的理由不提供信息共享。 9. 老板的不良反应肯定会对信息的完全公开造成压制,相反,仔细区分状态报告,毫无惊慌地接收报告,决不越俎代庖,将能鼓励诚实的汇报。 # 另外一面 1. 对于软件编程产品来说,程序向用户所呈现的面貌- 闻到那股,与提供给机器识别的内容同样重要。 2. 即使是完全开发给自己使用的程序,描述性文字也是必须的,因为他们会被用户-作者所遗忘。 3. 培训和管理人员基本上没有向编程人员成功地灌输对待闻到那股的积极态度 - 文档能在整个生命周期对客服懒惰和进度的压力起促进和激励作用。 4. 大多数闻到那股只提供了很少的总结性内容。必须放慢脚步,稳妥地进行。 5. 流程图是被吹捧的最过分的一种程序文档。 6. 如果这样,很少有程序需要一页纸以上的流程图 7. 程序员改人员所使用的文档中,除了描述事件如何,还应阐述它为什么那样,对于加深理解,目的是非常关键的,即使是高级语言的语法,也不能表达目的。 8. 软件系统可能是人类创造中最错综复杂的事物。 9. 软件工程的焦油坑在将来很长一段事件内仍然会使人们举步维艰,无法自拔。 - 从程序到编程产品至少是开发程序时间的三倍,需要有完备的文档,每个人都可加已使用,修复和扩展。 # 职业的乐趣 - 这种快乐是一种创造事物的纯粹快乐,特别是自己进行设计。 - 其次,这种快乐来自于开发对他人有用的东西。内心深处。我们期望我们的劳动成果能够被他人使用,并能对他们有所帮助。 - 第三,快乐来自于整体过程体现出的一股强大的而魅力,并收到了预期的效果。 - 持续学习的快乐,来自于这项工作的非重复特性,人们所面临的问题总有这样那样的不同,因而解决问题的人可以从中学习新的事物。 - 程序员就像诗人一样,几乎仅仅在单纯的思考中工作。程序员凭空地运用自己的想象,来建造自己的“城堡”。 # 职业的苦恼 - 苦恼来自于追求完美。我认为,学习编程最困难的部分,是将做事的方式向追求完美的方向调整。 - 其次,苦恼来自由他人来设定目标,供给资源和提供信息。 - 对于系统编程人员而言,对其他人的依赖是一件非常痛苦的事情。他依靠其他人的程序,而这些程序往往设计的并不合理,实现出拙劣,发布不完成整,文档记录很糟糕。 - 寻找琐碎的bug是一项重复行的劳动,往往枯燥沉闷。 # 人月 - 用人月作为衡量工作的规模是一个危险和带有欺骗性的神话。 - 人数和时间的互换仅仅适用于,他们之间不需要相互交流。 - 沟通所增加的负担:培训和相互交流。在一个不可分解的任务时,人数的增加反而会使得项目时间增加。 - 软件开发本质是一项系统工作 - 错综复杂关系下的一种实践,沟通,交流的工作量非常大,添加更多的人手,实际上延长了而不是缩短了时间进度。 # 系统测试 - 软件任务的进度安排。1/3计划,1/6编码,1/4构件测试和早期系统测试,1/4系统测试,所有的构件完成。 - 不为系统测试安排足够的时间,简直就是一场灾难。 # 进度灾难 - 向进度落后的项目增加人手,只会使得进度更加落后。 - 项目的时间依赖于顺序上的限制 - 人员的最大数量依赖于独立子任务的数量 # 问题 - 一流人才组成的小型,精干的队伍,而不是那些几百人大型团队,这里的“人”当然暗指平庸的程序员 - 优秀的程序员和较差的程序员之间生产率的差异,实际测量出的差异领人吃惊,最好的和最差的表现在生产率上平局能为10:1,在编程速度和空间上具有5:1的惊人差异 - 简言之,20000美元/年的程序员的生产率可能是10000美元/年的程序员10倍。 - 实际上,绝大多数大型编程系统的经验显示出,一拥而上的开发方法是高成本的,速度缓慢的,低效的,开发出的是无法在概念上集成的产品。 # 组织架构 - 我认为产品负责任作为管理者是更合适的安排。 # 控制规模 - 更深刻的教训体现在以上的经验中,项目规模本身很大,缺乏管理和沟通,以至于每个团队成员认为自己是争取小红花的学生,而不是勾践系统软件产品的人员。为了满足目标,每个人都在局部优化自己的程序,很少会有人听再来,考虑一下对客户的整体影响。 # 文档 - 技术,周边组织机构,行业传统等若干因素凑在一起,定义了项目必须准备的一些文书供祖宗。对于一个刚从技术人员中任命的项目经历来说,这减脂是一件彻头彻尾,令人生厌的事情。 - 慢慢地,他逐渐认识到这些文档的某些部分包含和表达了一些管理 方面的工作。每分文档的准备工作是集中考虑,并使各种讨论意见明细化的主要时刻。如果不这样,项目往往会处于无休止的混乱状态中,文档的跟踪维护是项目监督和预警的机制。文档本身可以作为检查列表,状态控制,也可以作为回报的数据基础。 # 未雨绸缪 - 对于大多数项目,第一个开发的系统并不合用。它可能太慢,太大,而且难以使用,或者三者兼而有之。要解决所有的问题,除了重新开始以外,没有其他的办法。即开始一个更灵巧或更好的系统,系统的丢弃和重新设计可以一步完成,也可以一块块地实现,所有大型系统的经验都显示,这是必须完成的步骤。而且,新的系统概念或新技术会不断出现,必须构建一个用来抛弃的系统,因为即使是最优秀的项目经历,也不能无所不知地在最开始解决这些问题。 - 我从不建议顾客所有的目标和需求的变更必须,能够或者应该整合到设计中,项目开始时建立的基准,肯定会随着开发的进行越来越高,甚至开发不出任何产品。 - 程序员不愿意为设计书写文档的原因,不仅仅是因为惰性或者时间的压力。相反,设计人员通常不愿意提交尝试行的设计决策,再为他们进行辩解。“通过设计文档化,设计人员讲自己暴露在每个人的批评之吓,他必须能够为他书写的一切进行辩护。如果团队加固因此收到任何形式的威胁,则没有任何东西会被文档化,除非架构是完全受到保护的。” - 对于一个广泛使用的程序,其维护总成本通常是开发成本的40%或更多。令人吃惊的是,该成本受用户数目的影响很大。用户越多,所发现的错误就越多。 - 一个有趣的循环,上一个版本中被发现和修复的bug,在新的版本中仍会出现,新版本中的新功能会产生新的bug。解决了这些问题以后,程序会正常运行几个月,接着,错误率会重新攀升,这是因为用户的使用达到了新的熟练水平,开始运用新的功能,这种高强度的考验查处了新功能中很多不易察觉的问题。 - 用在修复原有设计上瑕疵的工作量越来越少,而早期维护活动本身所引起的漏洞的修复工作越来越多,随着时间的推移,系统变得越来越无序,修复工作迟早会失去根基。每一步前进都伴随着一步后退,尽管系统在理论上一直可用,但实际上,整个系统已经面目全非。 - 系统软件开发是减少混乱度的过程,所以它本身是处于亚稳态的,软件维护是提高混乱度的过程。即使是熟练的软件维护工作,也只是放缓了系统退化到非稳态的过程。此时就需要大规模重构。 # 工具 - 就工具而言,即使是现在,很多软件项目仍然像经营一家五金店。每个骨干人员都仔细地保管自己工作生涯的一套工具集,这些工具成为个人技能的直观证明,正式如此,每个编程人员也保留着编辑器,排序,内存信息转储和磁盘空间使用程序的巩固。 - 所以在前面关于软件开发队伍的讨论中,我建议为每个团队配备一个工具管理人员。这个角色管理所有通用工具,能知道他的客户和老板如何使用工具。同时,他还能编制老板需要的专业工具。 # 文档 - 面对那些文档“简约”的程序,我们中的大多数人都不免曾经暗骂那些远在他方的匿名作者。因为,一些视图向新人慢慢地灌输文档的重要性,旨在延长软件的生命期,克服惰性和进度的压力。但是,很多次尝试都失败了,我想可能是由于我们使用了错误的方法。 - 每个用户都需要一段对程序进行描述的文字。可是太多数文档只提供了很少的总结性内容,无法达到用户要求,就像是描绘了树木,形容了树皮和树叶,但确没有一幅森林的图案,为了得到一份有用的文字描述,就必须放慢脚步,稳妥前行 - 流程图是被吹捧的最过分的一种程序文档,事实上,很多程序甚至不需要流程图,很少有程序需要一页纸以上的流程图。 - 现实中,我从来没有看过一个有经验的编程人员,在开始编写程序之前,会例行公事地绘制详尽的流程图。在一些要求流程图的组织中,流程图总是事后才补上的。 # 如何培养杰出的设计人员 - 尽可能早地,有系统地识别顶级的设计人员,最好的通常不是最有经验的人员 - 为设计人员指派一位职业导师,负责他们技术方面的成长,仔细地为他们规划职业生涯。 - 为每个方面指定和维护一份职业计划,包括与设计大师的,经过仔细挑选的学习过程,正式的高级教育和短期的课程,所有这些穿插在设计和技术领导能力的培养安排中。 - 为成长中的设计人员提供交流和激励的机会。 # 面向对象 - 因为OO和各种复杂语言的联系已经很紧密,人们并没有被告诉OO是一种设计方式,并xang他们讲授设计方法和原理,大家只是被告知OO是一种设计方法,并向他们讲授设计方法和原理,大家只是被告知OO是这一种特殊工具,而我们可以用任何工具写出优质或低劣的代码。除非我们给人们讲解无任何设计,否则语言所起的所用非常小,结果人们使用这种语言做出不好的设计,没有从中获得多少价值。而一旦获得的价值太少,它就不会流行。 - 面相对象技术包含了很多方法学上的进步,OO的前期投入很多 ,主要是重新培训程序员用很新的方法进行思考。面相对象在整个开发周期中得到了应用,但是真正的获益只有在后续开发,拓展和维护活动中才能体现出来。Coggin说:面相对象技术不会加快首次或第二次的开发,产品族群中第五个项目的开发将会异乎寻常的循序。 - 为了预期中的,但有写不确定的收益,冒着风险投入资金是投资人每天在做的事情。不过,在很多软件工资,这需要真正的管理勇气,一种比技术竞争力或者优秀管理能力更少有的精神。我认为极度的前期投入和收益的推后是使OO技术应用迟缓的最大原因。 # 重用的情况 - 大多数的丰富经验的程序员都拥有自己的私人开发库,使用大约30%的重用代码来开发软件。公司级别的重用能提供70%的重用代码量,它需要特殊的开发库和管理支持。公司级别的重用代码意味着需要对项目中的变更进行统计和度量,从而提高重用的可信程度。 这是那本《人月神话》摘出来的吗?