`JOB`的正确打开方式

news2024/12/23 9:16:32

文章目录

  • `JOB`的正确打开方式
    • 简介
    • 工作原理
    • 使用场景
    • 使用方式
    • 注意事项
    • 启动`JOB`失败的情况
    • `JOB`正确打开方式
      • 错误方式
      • 正确方式
      • 进阶方式
      • 终极方式
    • 总结

JOB的正确打开方式

  • 最近有一些小伙伴在使用JOB时,由于使用不当,引起一些问题。例如把license占满,JOB运行的方法有一些没有执行等。

通常我们这样使用Job命令:j ##class(className).methodName(args),实际上这种方式会引起一些未知问题。正确方式见下文。

  • 笔者通过这篇文章把JOB的正确使用方式介绍一下,避免在日后使用JOB时出现如上的一些问题。

简介

JOB 命令用于在操作系统级别启动一个新的进程。该进程可以执行特定的方法。

工作原理

当使用 JOB 命令时,会在当前进程的上下文中启动一个新的进程。新的进程会从特定的代码开始执行,并在该代码或命令返回后退出。

注:因为JOB每次启动一个进程。由于IRIS的特性,单用户启用超过20个进程时,这20个进程将转化为占用对应进程数量的license

注:JOB 命令功能类似于JAVA开启一个线程,在单独的环境中运行程序。但是线程是轻量级的,一个进程可以包含多个线程。进程是重量级的,开销要比线程大。在IRISCaché中,没有线程的概念。如果想使用异步功能,只能由JOB命令,开启一个进程。

使用场景

  • 多进程:开发人员可以使用 JOB 命令来创建新的进程,从而提高应用程序的并发性能和响应能力。例如,可以在后台启动一个长时间运行的任务(如数据导入)而不会阻塞前台服务。
  • 异步:执行第三方应用程序接口:JOB 命令可以在新的进程中直接执行不需要判断返回值且耗时比较长的第三方应用程序接口。

使用方式

  • JOB的简写方式为J,可以使用以下参数方式启动:
JOB:pc routine(routine-params):(process-params):timeout
JOB:pc routine(routine-params)[joblocation]:(process-params):timeout

J:pc ##class(className).methodName(args):(process-params):timeout
J:pc ..methodName(args):(process-params):timeout
J:pc $CLASSMETHOD(className,methodName,args):(process-params):timeout
  1. 简单启用JOB

编写一个需要运行的方法,该方法挂起10秒。

ClassMethod Process()
{
	h 10
	w "执行Process",!
	s ^yx("PID") = $job
}
ClassMethod Job()
{
	w "do执行",!
	d ..Process()
	w "do结束",!
	
	w "job执行",!
	j ..Process()
	w "job结束"
}

在这里插入图片描述

可以看到通过DO运行时,当前进程挂起10秒后继续运行,JOB运行时,该方法为瞬间启动,没有阻塞当前进程。

  • 启动JOB进程后,会为其分配单独的内存分区,并为其分配唯一的JOB编号(也称为进程IDPID)。JOB进程号存储在$JOB特殊变量中。

注:当JOB启动的方法运行完成时,创建的进程也随之销毁。

在这里插入图片描述

  1. 使用JOB调用其他命名空间方法。

示例如下,在其他命名空间创建如下方法:

Class M.Job Extends %RegisteredObject
{

ClassMethod JobOtherNamespace()
{
	s ^yx("JobOtherNamespace") = $i(^yx("JobOtherNamespace"))
}

}

在这里插入图片描述

  • 使用JOB命令时,要指定:(namespace)参数。
ClassMethod JobNameSpace()
{
	j ##class(M.Job).JobOtherNamespace():("yx")
}

如下可以看到,每次启动JOB时,yx命名空间Global自增1

USER>d ##class(M.Job).JobNameSpace()
 
USER>zw ^|"yx"|yx("JobOtherNamespace")
^|"yx"|yx("JobOtherNamespace")=1
 
USER>d ##class(M.Job).JobNameSpace()
 
USER>zw ^|"yx"|yx("JobOtherNamespace")
^|"yx"|yx("JobOtherNamespace")=2
 
USER>d ##class(M.Job).JobNameSpace()
 
USER>zw ^|"yx"|yx("JobOtherNamespace")
^|"yx"|yx("JobOtherNamespace")=3

注意事项

  • JOB只能按值传递参数,不能传递引用参数与多维数组传。如果使用JOB使用引用传递参数,Studio将提示如下错误。

在这里插入图片描述

  • JOB不能使用可变参数。

在这里插入图片描述

  • 不能将对象引用传递给JOB进程。 这是因为对调用上下文中存在的对象的引用不能在新的进程JOB上下文中引用。传递OREF会将空字符串("")传递到JOB进程。
ClassMethod JobObj()
{
	s m = ..%New()
	w "当前对象", m,!
	j ..Obj(m)
}

ClassMethod Obj(m)
{
	s ^yx("Obj") = m
}
USER>d ##class(M.Job).JobObj()
当前对象1@M.Job
 
USER>zw ^yx("Obj")
^yx("Obj")=""
  • 启动Job时需要指定参数timeout - 在超时并中止JOB之前等待JOB进程启动的秒数。前面的冒号是必需的。必须将超时指定为整数值或表达式。如果JOB进程超时,则将停止该进程,并将$test特殊变量设置为0
ClassMethod JobTimeOut()
{
	for i = 1 : 1 : 500 {
		j ..Process()::3
		w "i:",i," $test:",$test,!
	}
}

注:如果启动JOB没有指定超时参数,那么该进程将无限期等待,导致死进程。

在这里插入图片描述

启动JOB失败的情况

  • 内存不足。
  • 超过许可的进程数量。
  • 其他等。

JOB正确打开方式

错误方式

在介绍如何正确使用JOB之前,首先看一下错误的使用方式造成的后果。运行JobTimeOut()方法。

在这里插入图片描述

可以看到当license被当前用户占满,这种情况下,其他正常的请求将都会等待,导致业务卡顿。

在这里插入图片描述

在这里插入图片描述

正确方式

首先需要了解2个系统变量$ZPARENT$ZCHILD

  • $ZPARENT包含JOB处理当前进程的进程的PID,如果当前进程不是通过JOB命令创建的,则为0

Process()方法里增加s ^yx("PARENT") = $zparent

ClassMethod JobParent()
{
	w "$job前:",$job,!
	w "$zparent前:",$zparent,!
	j ..Process()::3
	w "$job后:",$job,!
	w "$zparent后:",$zparent,!
}
USER>d ##class(M.Job).JobParent()
$job前:20568
$zparent前:0
$job后:20568
$zparent后:0
 
USER>zw ^yx("PARENT")
^yx("PARENT")=20568
  • $ZCHILD 包含JOB命令创建的最后一个进程的PID,无论是否成功。
ClassMethod JobChild()
{
	w "$job前:",$job,!
	w "$zchild前:",$zchild,!
	j ..Process()::3
	w "$job后:",$job,!
	w "$zchild后:",$zchild,!
}
USER>d ##class(M.Job).JobChild()
$job前:20568
$zchild前:23940
$job后:20568
$zchild后:25880
 
USER>d ##class(M.Job).JobChild()
$job前:20568
$zchild前:25880
$job后:20568
$zchild后:32308
  • 确认JOB是否启用成功,必须要判断$zchild前后的值与超时。

通过使用$ZCHILD,可以通过比较运行JOB命令前后的$ZCHILD值来确定远程JOB的执行状态。如果BEFOREAFTER值不同,并且AFTER值为非零,则AFTER``$ZCHILD值是新创建的JOBPID,表示进程已成功创建。如果AFTER值为零,或者AFTER值与BEFORE值相同,则不会创建JOB

正确使用JOB的方式如下:

  1. 设置JOB超时参数。
  2. 判断是否超时。
  3. 判断$ZCHILD系统变量前后是否不一致并且不为0
  4. 根据返回值判断JOB是否启动成功。如果不判断JOB是否启动成功,会发生JOB运行的方法有没有执行等问题。
ClassMethod JobCorrect()
{
	s curChildID = $zchild
	j ..Process()::3
	s nowChildID = $zchild
	if '$test {
		q "JOB启动超时" _ " "_  $zchild
	}
	if (nowChildID '= 0)&&(curChildID '= nowChildID){
		q "JOB启动成功" _ " "_  $zchild
	}
	q "JOB启动失败" _ " "_  $zchild
}

注:$ZCHILD只能告诉JOB已创建;不会告诉JOB是否成功运行程序。要确定JOB进程运行的程序是否无错误并运行到完成,最好的做法是在正在运行的代码中提供某种类型的日志记录和错误捕获。JOB机制不会以任何方式通知父进程进程错误或进程终止,无论成功与否。

注:JOB进程之间的通信。首选方案是使用父子进程都知晓的Global。使用Global目的是允许进程之间交换信息。

进阶方式

前面"正确的方式",仅能判断JOB是否正确启动。但无法判断由JOB引起的死进程。

  1. 首先创建一个 通用的JOB程序。
  • 由于JOB不能使用可变参数,所以需要将参数做一次处理转化。
ClassMethod Run(className, methodName, params)
{
	s $zt = "Error"
	if (params '= "") {
		s arg = $ll(params)
		for i = 1 : 1 : arg {
			s arg(i) = $lg(params, i)
		}
	} else {
		s arg = 0
	}	

	d $classmethod(className , methodName, arg...)
	q $$$OK
Error 
	s $zt = ""
	q $ze
}
  • 该方法为JOB启动通用方法,该方法为布尔类型,返回$$$YES代表JOB启动成功,返回$$$NO代表启动失败。
ClassMethod IsRunJob(classname, methodname, arg...) As %Boolean
{
	s $zt = "Error"
	s params = ""
	if ($d(arg)) {
		for i = 1 : 1 : arg {
		   	s params = params _ $lb(arg(i))
		}
	}

	s curChildID = $zchild
	j ..Run(classname, methodname, params)::3
	s nowChildID = $zchild
	if '$test {
		q $$$NO
	}
	if (nowChildID '= 0)&&(curChildID '= nowChildID){
		q $$$YES
	}
	q $$$NO
	
Error 
	s $zt = ""
	q $$$NO
}
  1. 在循环中启动350JOB,将JOB开启的进程全部结束掉防止有出现死进程。
  • 这里将JOB进程进行缓存,用于统一结束。
ClassMethod JobFinishProcess(num)
{
	s array = ""
	for i = 1 : 1 : num {
		s bool = ..IsRunJob("M.Job","Process")
		if (bool) {
			s array(i) = $zchild
			w "进程开启成功:" _ i _ " JOB进程ID:" _ $zchild,!
		} else {
			w "进程开启失败:" _ i _ " JOB进程ID:" _ $zchild,!
		}
	}
	
	d ..TerminateProcess(.array, 0)

	q $$$OK
}
  • TerminateProcess方法,增加了程序挂起时间,可以设定一个阈值,当程序在这个阈值内没有结束,认为为死进程,将其结束掉。
ClassMethod TerminateProcess(ByRef array, time)
{
	h time
	s i = ""
	for {
		s i = $o(array(i))
		q:(i = "")
		s ret = $system.Process.Terminate(array(i))
		s desc = $s(ret = "1" : "成功", ret = "0" : "失败 - 进程无法结束", ret = "-1" : "失败 - 进程没有响应", ret = "-2" : "失败 - 进程处于非活动状态死进程", ret = "-3" : "失败 - 没有此pid进程", ret = "-4" : "失败 - 进程包含开放的事务,该事务当前处于停止或回滚状态。")
		w "结束进程状态:" _ ret _ " " _ desc,!
	}
	q $$$OK
}
  • 如下图可以观察到,成功将没有结束的进程终止掉。

注:实际上结束进程状态:-3 失败 - 没有此pid进程,是缓存的JOB进程ID已经运行完成进程已结束,认为成功。

在这里插入图片描述

代码d ..TerminateProcess(.array, 0)如果设置了阈值时间,例如设置10分钟,会导致生产程序返回结果耗时过久,这里可以将DO调用改为JOB调用。

  • 设置临时Global缓存Job进程ID,通过s bool = ..IsRunJob("M.Job", "TerminateProcessJob", 0)调用后台结束进程程序。
ClassMethod JobFinishProcess1(num)
{
	k ^yx("JobPid")
	s array = ""
	for i = 1 : 1 : num {
		s bool = ..IsRunJob("M.Job","Process")
		if (bool) {
			s ^yx("JobPid", i) = $zchild
			w "进程开启成功:" _ i _ " JOB进程ID:" _ $zchild,!
		} else {
			w "进程开启失败:" _ i _ " JOB进程ID:" _ $zchild,!
		}
	}
	
	s bool = ..IsRunJob("M.Job", "TerminateProcessJob", 0)
	if ('bool) {
		w "终结程序启动失败",!
	}else {
		w "终结程序启动成功",!
	}

	q $$$OK
}
ClassMethod TerminateProcessJob(time)
{
	s ^yx("TerminateProcessJob") = time
	h time
	s i = ""
	for {
		s i = $o(^yx("JobPid", i))
		q:(i = "")
		s ret = $system.Process.Terminate(^yx("JobPid", i))
		s desc = $s(ret = "1" : "成功", ret = "0" : "失败 - 进程无法结束", ret = "-1" : "失败 - 进程没有响应", ret = "-2" : "失败 - 进程处于非活动状态死进程", ret = "-3" : "失败 - 没有此pid进程", ret = "-4" : "失败 - 进程包含开放的事务,该事务当前处于停止或回滚状态。")
		w "结束进程状态:" _ ret _ " " _ desc,!
	}
	q $$$OK
}

终极方式

细心的朋友应该发现进阶方式存在了如下问题:

  • 终止进程方法也使用JOB启用,如果JOB启用失败了,那么死进程还是结束不了。
  • 终止进程方法结束的PID也有问题,如果JOBPID已经完成,其他并发的用户请求在此时间段内又生成了相同的PID进程号,会把正常的业务进程结束掉。
  • 用户启用的JOB数量没有限制,单个用户就能把license占满,导致正常的业务请求失败,应该限制单用户开启的JOB数量,结合当前的license数量,应设置一个阈值,超过此阈值的JOB不允许开启。
  • 如果不判断返回值,如何保证调用的JOB程序全部启用成功,应设置缓存失败的JOB方法与参数,设置后台重试。

这里我已经将代码封装到拓展系统命令ZSAFEJOB中,使用拓展系统命令ZSAFEJOB启用需要后台调用的程序即可。

ZSAFEJOB命令包含功能:

下面演示ZSAFEJOB命令使用方式:

ZSAFEJOB "classname_methodname(params)"
ZSJ "classname_methodname(params)"
  • classname - 类名。
  • methodname - 查询名。
  • params - 使用单引号包裹参数,逗号分割的参数列表。
  • 需要返回值时,可使用%zjob 变量判断Job是否启用成功。

  • 有参数时:需要将参数使用单引号包裹,多个参数使用逗号分割。如下使用ZSJ调用如下Sum方法。
ClassMethod Sum(a, b, c)
{
	s sum = a + b + c
	s ^yx("Sum") = sum
	q sum
}
USER>zsj "M.Job_Sum('1','2','3')"

在这里插入图片描述

  • 无参数时,可省略括号。如下使用ZSJ调用如下NoParams方法。
ClassMethod Process()
{
	s ^yx("NoParams",$i(^yx("NoParams"))) = ""
	h 20
	w "执行Process",!
}

示例Process()方法挂起进程20秒,zsj命令为异步,并没有挂起等待,输出记录的Global验证调用成功。

USER>zsj "M.Job_Process"

在这里插入图片描述

使用其他拓展系统命令可参考如下连接:https://yaoxin.blog.csdn.net/article/details/130157870


  • 验证超过license阈值,使用job失败,防止占用过多license,导致正常业务失败。示例中的阈值为当前license最大数量300*0.8 = 240
ClassMethod SafeJob(num)
{
	k ^yx("JobPid")
	s array = ""
	for i = 1 : 1 : num {
		zsj "M.Job_Process"
		w i," ",%zjob,! 
	}
	q $$$OK
}

在这里插入图片描述

  • 验证将启动失败的Job方法,进行重新调用。如下图示例:238239,启用的JOB失败,然后使用方法JobTask进行重试,观察可发现重试2次成功。

在这里插入图片描述

JobTask方法需要挂任务进行重试,包含以下功能:

  • 删除由Job开启的死进程,判断规则为进程持续时间超过10分钟,则把该进程进行结束。
  • 重试Job启动失败的方法。

在这里插入图片描述

总结

本篇由浅入深讲解了Job命令的使用方式,了解Job命令的原理,可防止一些意外的情况,并推荐使用ZSAFEJOB命令使用JOB命令功能。

以上是个人对JOB命令的一些理解,由于个人能力有限,欢迎大家提出意见,共同交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/563931.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ASEMI代理长电可控硅MCR100-8特征,MCR100-8应用

编辑-Z 长电可控硅MCR100-8参数: 型号:MCR100-8 VDRM/VRRM:600V IT(RMS):0.8A 结点温度Tj:-40~125℃ 储存温度Tstg:-55 ~ 150℃ 通态电压VTM:1.7V 栅极触发电压VGT:0.8V 正…

泰克MDO4104C(Tektronix) mdo4104c混合域示波器

泰克 MDO4104C混合域示波器,1 GHz,4 通道,2.5 - 5 GS/s,20 M 点 ​泰克 MDO4104C 示波器是一款 6 合 1 集成示波器,可以配置可选的频谱分析仪、任意波形/函数发生器、逻辑分析仪、协议分析仪和 DVM/频率计数器。当配置…

黑盒测试能发现以下几类错误

黑盒测试能发现以下几类错误 黑盒测试是指在不考虑被测试软件的内部结构和工作原理的情况下,通过输入输出的方式对被测试软件进行测试。它主要关注被测试软件的功能是否达到预期的要求。黑盒测试能够发现以下几类错误。 1. 输入错误:黑盒测试可以检查被测…

如何增加网站权重?有效提高网站权重的技巧方法

权重对于网站优化来说非常的重要,那什么是网站权重呢?网站权重是指搜索引擎给网站(包括网页)赋予一定的权威值,对网站(含网页)权威的评估评价。一个网站权重越高,在搜索引擎所占的份…

【C++】虚表和虚基表到底有哪些区别?

虚表和虚基表 虚表虚基表虚拟继承和虚函数都存在时的对象模型 虚表 我们知道,如果类中声明了的方法是用virtual进行修饰的,则说明当前这个方法要作为虚函数,而虚函数的存储和普通函数的存储是有区别的 当有虚函数声明时,编译器会…

运营-16.个性化推荐

个性化推荐 个性化推荐,是根据用户的行为来分析用户的喜好,进而做商品精准推荐。 为什么要做个性化推荐? 1. 收集用户信息,精准获取用户需求; 2. 减少用户搜索商品的页面层级,提高转化率; …

聊聊 Milvus GC:从一次数据丢失事件展开

QueryNode 日志中频繁报错?对象存储数据离奇消失[1]? 令人震惊的数据丢失事件就这样发生了,一位来自 BOSS 直聘的 AI 研发工程师无意卷入到此次的风波中,他和 Milvus 社区的伙伴经过层层排查、抽丝剥茧,成功找出了问题…

还在用 JS 做节流吗?CSS 也可以防止按钮重复点击

目录 一、CSS 实现思路分析 二、CSS 动画的精准控制 三、CSS 实现的其他思路 四、总结一下 众所周知,函数节流(throttle)是 JS 中一个非常常见的优化手段,可以有效的避免函数过于频繁的执行。 举个例子:一个保存按…

opencv_c++学习(二十)

一、形态学应用案例 开、闭运算、形态学梯度等原理: 相关函数: morphologyEx(InputArray src, OutputArray dst, int op, lnputArray kernel, Point anchor Point(-1,-1), int iterations 1, int borderType BORDER_CONSTANT, const Scalar & border…

Android中静态和动态文字的绘制和测量

Android中静态和动态文字的绘制和测量 Android中自定义视图的时候存在两种情况,静态文字和动态文字。 顾名思义,静态文字就是显示内容是固定的,不会产生变化的文字,而动态文字则是内容会不断产生变化的文字信息。 在说明为什么…

Revit技巧 | Revit中图元不可见怎么办?

在revit中,控制图元课件性的设置有很多种,因此图元不可见,也会有各种各样的原因,这也是经常困扰新手的问题,下面我把这些解决办法做一些归纳总结。 图元如果过远偏离当前视图的中心,将导致视图不可见这时&…

MySQL:数据库的查询与连接

目录 1.复合查询 1.1 多表查询(联合查询) 1.2 join on (inner join) 1.3 自连接 1.4 子查询 1.5 合并查询 2.内外连接 3.关于高内聚、低耦合 1.复合查询 1.1 多表查询(联合查询) 什么是多表插叙?实际开发中往…

网络安全管理员证书有什么用?2023证书怎么考?证书报考条件?

网络安全管理员是做什么工作的呢?现如今,网络高速发展,带动了很多行业的兴起,比如说电商行业,今天已经步入到足不出户即可购物的时代了,当然网络也是一把“双刃剑”,带来了好处的同时&#xff0…

Sui Move Object讲解

要了解Sui的独特特性,首先要了解Sui中以对象为中心的数据模型。 Sui的设计初衷是重新定义数字资产所有权的可能性。重新设计的一个基本部分 — — Sui是以对象为中心的数据模型,也是Sui和其他Layer 1区块链之间的一个显著区别。 其他L1如何处理资产所有…

day8 - 使用不同的滤波核进行图像降噪

本期主要介绍用于图像平滑处理的滤波,分别是方框滤波、均值滤波、中值滤波、高斯滤波,比较不同滤波的效果;并了解自定义滤波器进行图像处理。 完成本期内容,你可以: 会使用方框滤波、均值滤波、中值滤波、高斯滤波进行…

实时聊天组合功能,你了解吗?

你有兴趣安装实时聊天组合功能吗?如果您选择了SaleSmartly(ss客服),您的实时聊天插件可以不仅仅只是聊天通道,还可以有各种各样的功能,你不需要包含每一个功能,正所谓「宁缺勿滥」,功…

Windows主机中构建适用于K8S Operator开发环境

基于 win 10 打造K8S应用开发环境 一、wsl子系统安装 在cmd命令行终端或powershell中操作 1.1 确认windows操作系统版本 1.2 开启wsl功能 1.3 wsl配置 PS C:\Users\cpf> wsl提示:适用于 Linux 的 Windows 子系统没有已安装的分发版。可以通过访问 Microsoft St…

使用canvas给图片添加水印

上接文章“图片处理” canvas元素其实就是一个画布,我们可以很方便地绘制一些文字、线条、图形等,它也可以将一个img标签里渲染的图片画在画布上。 我们在上传文件到后端的时候,使用input标签读取用户本地文件后得到的其实是一个Blob对象&a…

Redis7实战加面试题-基础篇(Redis持久化,Redis事务,Redis管道,Redis发布订阅)

Redis持久化 RDB (Redis DataBase) RDB(Redis 数据库):RDB 持久性以指定的时间间隔执行数据集的时间点快照。实现类似照片记录效果的方式,就是把某一时刻的数据和状态以文件的形式写到磁盘上,也就是快照。这样一来即使…

HCIA-ARP、MAC、交换机工作原理

目录 万能数据转发模型 ARP协议:地址解析协议 以太网帧的交换 IP地址和Mac地址的区别: 以太网交换机介绍: 交换机的工作原理: ​编辑交换机处理数据的三种方式: Mac表和ARP表的区别: 万能数据转发模…