-
1.1 高健壮性机器人要求对于复杂度为O(n2)的脚本或者脚本涉及到UI页面操作就必须使用Try-catch+SubProcess保证高健壮性。Tips:在读写文件,比如读Excel的时候,也可以放入到Try-Catch-Finally里,如果Excel被打开了则脚本执行打开Excel会出错,可以在Catch里调用脚本杀掉Excel进程,在下一次Try的时候就可以顺利打开文件了1.2 转变成机器思维的要求比如将一个Excel表格里的内容,循环100次录入到web页面里。如果在没有模板可以一次导入的情况下,需要循环100次。人的操作思维:1, 登陆网页。2, 选中相应的内容copy到网页里,并提交。3, 循环填入下一条。4, 结束。 当用脚本实现时,我们建议:1, 打开excel读取1000条内容到内存里。可以避免文件交叉操作导致任务失败。2, 循环内存中1000条数据的第一条,并判断网页是否ready可以填入内容,并提交。如果网页未ready那么就打开网页。3, 循环填入下一条。4, 结束。这样做的好处是:不管网页是否稳定,即便网络中断几分钟,都可以顺利的保证完成录入任务。1.3 便于定位问题添加必要打印的要求当开发完成转维后,想要再去查找问题就比较困难, 所以在开发初期增加足够的日志,便于后期定位问题。例如在进入新脚本的时候、查询到结果的时候、关键节点等。1.4 查询再执行类增加校验的要求比如下图处理当前单处理的问题,要把单号记录下来,便于出现问题后排查问题,如果允许的话可以在失败的时候截图保留截图。 如果查询结果不一致,则继续查询一次,多次后未果,则发送通知消息。1.5 使用文字或者相对路径定位的要求xpath=//button[text()="Reset"]xpath=//a[contains(text(),'我的任务')]xpath=//*[contains(@id,"datatable")]/div[2]/div[1]/div/div[4]/divxpath=//*[text()="提交"]或者使用相对路径,这样在网页变动后还能增加成功定位的概率。网页操作中xpath的定位非常重要,可以参考网上资料进行深入学习。1.6 使用GETTABLE代替循环的要求在很多网页中某些表比较长,当前窗口显示不全,可以使用gettable,这样可以避免取不到拖动后才能看到的元素,如下图右侧的元素定位。当然,有些看上去是表的展示,单实际并不是表,还是要循环去操作。对于gettable的使用可以登陆www.ilearningx.huawei.com,搜索AntRobot,查看高级课程的9.2章节1.7 双重保护确保任务达成的要求比如对于鼠标移动网页元素上面的时候才会出现的菜单,actionchains 不一定会每次稳定运行。此时可以增加ClickPicxx的操作。1.8 串行任务改并行的要求比如登陆网站下载几千张图片,并对图片做OCR识别,比较推荐的解决办法是: 1,WeAutomate负责下载 图片,并拆分成不同的小组,比如100张一组。2,一组完成下载后即调用Java或者python等实现处理每个小组的图片,并继续下载下一组照片3,WeAutomate判断图片所有图片识别结束后进行信息汇总。4,最后一个小组与倒数第二个小组都完成,或者所有小组都完成,则执行完毕。1.9 拆分超长流程的要求不要将所有的流程全部放在一个子脚本/Script里,跟写代码时不允许一个函数体超过150行或者200行一个道理。缺点: 1,太长的流程很难读懂,别人很难很快了解流程的结构2,没有异常处理逻辑3,如果系统中出现小问题,则进程将终止 改进建议:主页保留流程主线,把大部分二级处理逻辑移到子页之后对完成的任务进行合理的拆分。1.10 安全开发规范要求1,WeAutomate Studio支持敏感信息本地存储、使用双重加密2,管理中心运行账号与开发账号隔离3,开发人员只接触测试账号1.11 其它要求1,流程应该简洁、干净且有逻辑2,应当在在定义好流程的输入、输出、处理过程后,再着手进行开发3,保存流程前,应该先检查所有Error。开发过程中的Error应当保证可控;开发完成后流程中不应当存在Error
-
openGauss数据库自2020年6月30日开源以来,吸引了众多内核开发者的关注。那么openGauss的多线程是如何启动的,一条SQL语句在 SQL引擎,执行引擎和存储引擎的执行过程是怎样的,酷哥做了一些总结,第一期内容主要分析openGauss 多线程架构启动过程。openGauss数据库是一个单进程多线程的数据库,客户端可以使用JDBC/ODBC/Libpq/Psycopg等驱动程序,向openGauss的主线程(Postmaster)发起连接请求。openGauss为什么要使用多线程架构随着计算机领域多核技术的发展,如何充分有效的利用多核的并行处理能力,是每个服务器端应用程序都必须考虑的问题。由于数据库服务器的服务进程或线程间存在着大量数据共享和同步,而多线程可以充分利用多CPU来并行执行多个强相关任务,例如执行引擎可以充分的利用线程的并发执行以提供性能。在多线程的架构下,数据共享的效率更高,能提高服务器访问的效率和性能,同时维护开销和复杂度更低,这对于提高数据库系统的并行处理能力非常重要。多线程的三大主要优势:优势一:线程启动开销远小于进程启动开销。与进程相比,它是一种非常“节俭”的多任务操作方式。在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间。优势二:线程间方便的通信机制:对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。优势三:线程切换开销小于进程切换开销,对于Linux系统来讲,进程切换分两步:1.切换页目录以使用新的地址空间;2.切换内核栈和硬件上下文。对线程切换,第1步是不需要做的,第2步是进程和线程都要做的,所以明显线程切换开销小。openGauss主要线程有哪些后台线程功能介绍Postmaster主线程入口函数PostmasterMain,主要负责内存、全局信息、信号、线程池等的初始化,启动辅助线程并监控线程状态,循环**接收新的连接Walwriter日志写线程入口函数WalWriterMain,将内存的预写日志页数据刷新到预写日志文件中,保证已提交的事物永久记录,不会丢失Startup数据库启动线程入口函数StartupProcessMain,数据库启动时Postmaster主线程拉起的第一个子线程,主要完成数据库的日志REDO(重做)操作,进行数据库的恢复。日志REDO操作结束,数据库完成恢复后,如果不是备机,Startup线程就退出了。如果是备机,那么Startup线程一直在运行,REDO备机接收到新的日志Bgwriter后台数据写线程入口函数BackgroundWriterMain,对共享缓冲区的脏页数据进行下盘PageWriter入口函数ckpt_pagewriter_main,将脏页数据拷贝至双写区域并落盘Checkpointer检查点线程入口函数CheckpointerMain,周期性检查点,所有数据文件被更新,将数据脏页刷新到磁盘,确保数据库一致;崩溃回复后,做过checkpointer更改不需要从预写日志中恢复StatCollector统计线程入口函数PgstatCollectorMain,统计信息,包括对象、sql、会话、锁等,保存到pgstat.stat文件中,用于性能、故障、状态分析WalSender日志发送线程入口函数WalSenderMain,主机发送预写日志WalReceiver日志接收线程入口函数WalReceiverMain,备机接收预写日志Postgres业务处理线程入口函数PostgresMain:处理客户端连接请求,执行相关SQL业务数据库启动后,可以通过操作系统命令ps查看线程信息(进程号为17012)openGauss启动过程下面主要介绍openGauss数据库的启动过程,包括主线程,辅助线程及业务处理线程的启动过程gs_ctl启动数据库gs_ctl是openGauss提供的数据库服务控制工具,可以用来启停数据库服务和查询数据库状态。主要供数据库管理模块调用,启动数据库使用如下命令gs_ctl start -D /opt/software/data -Z single_nodegs_ctl的入口函数在“src/bin/pg_ctl/pg_ctl.cpp”,gs_ctl进程fork一个进程来运行 gaussdb进程,通过shell命令启动。上图中的cmd为“/opt/software/openGauss/bin/gaussdb -D /opt/software/openGauss/data”,进入到数据库运行调用的第一个函数是main函数,在“src/gausskernel/process/main/main.cpp”文件中,在main.cpp文件中,主要完成实例Context(上下文)的初始化、本地化设置,根据main.cpp文件的入口参数调用BootStrapProcessMain函数、GucInfoMain函数、PostgresMain函数和PostmasterMain函数。BootStrapProcessMain函数和PostgresMain函数是在initdb场景下初始化数据库使用的。GucInfoMain函数作用是显示GUC(grand unified configuration,配置参数,在数据库中指的是运行参数)参数信息。正常的数据库启动会进入PostmasterMain函数。下面对这个函数进行更详细的介绍。MemoryContextInit:内存上下文系统初始化,主要完成对ThreadTopMemoryContext,ErrorContext,AlignContext和ProfileLogging等全局变量的初始化pg_perm_setlocale:设置程序语言环境相关的全局变量check_root: 确认程序运行者无操作系统的root权限,防止的意外文件覆盖等问题如果gaussdb后的第一个参数是—boot,则进行数据库初始化,如果gaussdb后的第一个参数是--single,则调用PostgresMain(),进入(本地)单用户版服务端程序。之后,与普通服务器端线程类似,循环等待用户输入SQL语句,直至用户输入EOF(Ctrl+D),退出程序。如果没有指定额外启动选项,程序进入PostmasterMain函数,开始一系列服务器端的正常初始化工作。PostmasterMain函数下面具体介绍PostmasterMain。设置线程号相关的全局变量MyProcPid、PostmasterPid、MyProgName和程序运行环境相关的全局变量IsPostmasterEnvironment调用postmaster_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt,...),在目前线程的top_mem_cxt下创建postmaster_mem_cxt全局变量和相应的内存上下文MemoryContextSwitchTo(postmaster_mem_cxt)切换到postmaster_mem_cxt内存上下文调用getInstallationPaths(),设置my_exec_path(一般即为gaussdb可执行文件所在路径)调用InitializeGUCOptions(),根据代码中各个GUC参数的默认值生成ConfigureNamesBool、ConfigureNamesInt、ConfigureNamesReal、ConfigureNamesString、ConfigureNamesEnum等 GUC参数的全局变量数组,以及统一管理GUC参数的guc_variables、num_guc_variables、size_guc_variables全局变量,并设置与具体操作系统环境相关的GUC参数while (opt = ...) SetConfigOption, 若在启动gaussdb时用指定了非默认的GUC参数,则在此时加载至上一步中创建的全局变量中调用checkDataDir(),确认数据库安装成功以及PGDATA目录的有效性调用CreateDataDirLockFile(),创建数据目录的锁文件调用process_shared_preload_libraries(),处理预加载库为每个ListenSocket创建**reset_shared,设置共享内存和信号,主要包括页面缓存池、各种锁缓存池、WAL日志缓存池、事务日志缓存池、事务(号)概况缓存池、各后台线程(锁使用)概况缓存池、各后台线程等待和运行状态缓存池、两阶段状态缓存池、检查点缓存池、WAL日志复制和接收缓存池、数据页复制和接收缓存池等。在后续阶段创建出的客户端后台线程以及各个辅助线程均使用该共享内存空间,不再单独开辟将启动时手动设置的GUC参数以文件形式保存下来,以供后续后台服务端线程启动时使用为不同信号设置handler调用pgstat_init(),初始化状态收集子系统;调用load_hba(),加载pg_hba.conf文件,该文件记录了允许连接(指定或全部)数据库的客户端物理机的地址和端口;调用load_ident(),加载pg_ident.conf文件,该文件记录了操作系统用户名与数据库系统用户名的对应关系,以便后续处理客户端连接时的身份认证调用 StartupPID = initialize_util_thread(STARTUP),进行数据一致性校验。对于服务端主机来说,查看pg_control文件,若上次关闭状态为DB_SHUTDOWNED且recovery.conf文件没有指定进行恢复,则认为数据一致性成立;否则,根据pg_control中检查点的redo位置或者recovery.conf文件中指定的位置,读取WAL日志或归档日志进行replay(回放),直至数据达到预期的一致性状,主要函数StartupXLOG最后进入ServerLoop()函数,循环响应客户端连接请求ServerLoop函数下面来讲ServerLoop函数主流程调用gs_signal_setmask(&UnBlockSig, NULL)和gs_signal_unblock_sigusr2(),使得线程可以响应用户或其它线程的、指定的信号集每隔PM_POLL_TIMEOUT_MINUTE时间修改一次socket文件和socket锁文件的访问和修改时间,以免**作系统淘汰判断线程状态(pmState),若为PM_WAIT_DEAD_END,则休眠100毫秒,并且不接收任何连接;否则,通过系统调用poll()或select()来阻塞地读取**端口上传入的数据,最长阻塞时间PM_POLL_TIMEOUT_SECOND调用gs_signal_setmask(&BlockSig, NULL)和gs_signal_block_sigusr2()不再接收外源信号判断poll()或select()函数的返回值,若小于零,**出错,服务端进程退出;若大于零,则创建连接ConnCreate(),并进入后台服务线程启动流程BackendStartup()。对于父线程,即postmaster线程,在结束BackendStartup()的调用以后,会调用ConnFree(),清除连接信息;若poll()或select()的返回值为零,即没有信息传入,则不进行任何操作调用ADIO_RUN()、ADIO_END() ,若AioCompleters没有启动,则启动之检查各个辅助线程的线程号是否为零,若为零,则调用initialize_util_thread启动以非线程池模式为例,介绍线程的启动逻辑。BackendStartup函数是通过调用initialize_worker_thread(WORKE,port)创建一个后台线程处理客户请求。后台线程的启动函数initialize_util_thread和工作线程的启动函数initialize_worker_thread,最后都是调用initialize_thread函数完成线程的启动。initialize_thread函数调用gs_thread_create函数创建线程,调用InternalThreadFunc函数处理线程ThreadId initialize_thread(ThreadArg* thr_argv) { gs_thread_t thread; int error_code = gs_thread_create(&thread, InternalThreadFunc, 1, (void*)thr_argv); if (error_code != 0) { ereport(LOG, (errmsg("can not fork thread[%s], errcode:%d, %m", GetThreadName(thr_argv->m_thd_arg.role), error_code))); gs_thread_release_args_slot(thr_argv); return InvalidTid; } return gs_thread_id(thread); }InternalThreadFunc函数根据角色调用GetThreadEntry函数,GetThreadEntry函数直接以角色为下标,返回对应GaussdbThreadEntryGate数组对应的元素。数组的元素是处理具体任务的回调函数指针,指针指向的函数为GaussDbThreadMainstatic void* InternalThreadFunc(void* args) { knl_thread_arg* thr_argv = (knl_thread_arg*)args; gs_thread_exit((GetThreadEntry(thr_argv->role))(thr_argv)); return (void*)NULL; } GaussdbThreadEntry GetThreadEntry(knl_thread_role role) { Assert(role > MASTER && role < THREAD_ENTRY_BOUND); return GaussdbThreadEntryGate[role]; } static GaussdbThreadEntry GaussdbThreadEntryGate[] = {GaussDbThreadMain<MASTER>, GaussDbThreadMain<WORKER>, GaussDbThreadMain<THREADPOOL_WORKER>, GaussDbThreadMain<THREADPOOL_LISTENER>, ......};在GaussDbThreadMain函数中,首先初始化线程基本信息,Context和信号处理函数,接着就是根据thread_role角色的不同调用不同角色的处理函数,进入各个线程的main函数,角色为WORKER会进入PostgresMain函数,下面具体介绍PostgresMain函数PostgresMain函数process_postgres_switches(),加载传入的启动选项和GUC参数为不同信号设置handler调用sigdelset(&BlockSig, SIGQUIT),允许响应SIGQUIT信号调用BaseInit(),初始化存储管理系统和页面缓存池计数调用on_shmem_exit(),设置线程退出前需要进行的内存清理动作。这些清理动作构成一个链表(on_shmem_exit_list全局变量),每次调用该函数都向链表尾端添加一个节点,链表长度由on_shmem_exit_index记录,且不可超过MAX_ON_EXITS宏。在线程退出时,从后往前调用各个节点中的动作(函数指针),完成清理工作调用gs_signal_setmask (&UnBlockSig),设置屏蔽的信号类型调用InitBackendWorker进行统计系统初始化、syscache初始化工作BeginReportingGUCOptions如有需要则打印GUC参数调用on_proc_exit(),设置线程退出前需要进行的线程清理动作。设置和调用机制与on_shmem_exit()类似调用process_local_preload_libraries(),处理GUC参数设定后的预加载库AllocSetContextCreate创建MessageContext、RowDescriptionContext、MaskPasswordCtx上下文调用sigsetjmp(),设置longjump点,若后续查询执行中出错,在某些情况下可以返回此处重新开始调用gs_signal_unblock_sigusr2(),允许线程响应指定的信号集然后进入for循环,进行查询执行 调用pgstat_report_activity()、pgstat_report_waitstatus(),告诉统计系统后台线程正处于idle状态设置全局变量DoingCommandRead = true调用ReadCommand(),读取客户端SQL语句设置全局变量DoingCommandRead=false若在上述过程中收到SIGHUP信号,表示线程需要重新加载修改过的postgresql.conf配置文件进入switch (firstchar),根据接收到的信息进行分支判断思考如何新增一个辅助线程参考其他线程完成涉及修改文件Postmaster.cpp涉及修改函数GaussdbThreadGate – 定义Serverloop – 启动线程Reaper – 回收线程GaussDBThreadMain – 入口函数
-
与其说是线程安全,不如说是内存安全,堆是共享内存,可以被所有线程访问。堆 是进程和线程共有的空间,每一个进程里面有多个线程,分全局堆和局部堆,全局堆就是所有没有分配的空间,局部堆就是分配给用户的空间,堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了还要还给操作系统,要不然就是内存泄漏。栈 是每个线程独有的,所以栈是线程安全的,每个线程的栈互相独立。目前主流的操作系统都是多任务的,即多个进程同事运行,为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存),进程内的所有线程都可以访问这个区域,所以这个区域是线程不安全的。
-
前言什么是线程的概念我就不在介绍,不懂的自行百度,我想百分之九十九的人都是知道的,至于多线程,通俗的就是有很多的线程在一起工作从而完成某一件事,从而提升效率。这就是使用多线程的好处之一,举个列子,一家酒店,几个厨子,杂工,切菜工,服务员…大家各司其职,各完成一道工序,这道菜才可以快速做成,如果一直只要一个厨师去做,哈哈哈,告诉你,人多,店就等着被关门吧。由于,我在做Java或android的时候,经常会遇到多线程问题,今天我想深刻总结一下,如有不当之处,希望大佬们不吝赐教。好了,今天我们就来详细的探究一下多线程吧。今天讲的大概就是以上流程图的三个部分。多线程优化基础简要介绍多线程优化的基础知识,包括线程的介绍和线程调度基本原理。多线程优化问题多线程优化需要预防的一些问题,包括线程安全问题的介绍和实现线程安全的办法。多线程优化方法多线程优化可以使用的一些方法,包括线程之间的协作方式与 Android 执行异步任务的常用方式。好了,下面就一步一步探究一下多线程吧。1.能不能不用多线程?首先,不管你会不会多线程,都必须要学习和使用多线程,因为,它实在是太重要了。使用多线程前,需要明白几个关键点。GCGarbage Collector(垃圾回收器)Garbage Collection(垃圾回收动作)GC线程假如我们现在运行的是一个啥也没有的 demo 项目,那也不代表我们运行的是一个单线程应用。因为这个应用是运行在 ART 上的,而 ART(啥是ART下面有介绍) 自带了 GC 线程,再加上主线程,它依旧是一个多线程应用。ARTAndroid Runtime(Android 应用运行时环境)JVMJava Virtual Machine(Java 虚拟机)JUCjava.util.concurrent(Java 并发包)因此,不管你是否懂何为多线程,你也要用到多线程,因此,学习多线程是必要的。第三方线程在我们开发应用的过程中,即使我们没有直接创建线程,也间接地创建了线程。因为我们日常使用的第三方库,包括 Android 系统本身都用到了多线程。比如 Glide 就是使用工作线程从网络上加载图片,等图片加载完毕后,再切回主线程把图片设置到 ImageView 中。2. 为什么要做多线程优化?做多线程优化是为了解决多线程的安全性和活跃性问题。这两个问题会导致多线程程序输出错误的结果以及任务无法执行,下面我们就来看看这两个问题的表现。安全性问题假如现在有两个厨师小张和老王,他们两个人分别做两道菜,大家都知道自己的菜放了多少盐,多少糖,在这种情况下出问题的概率比较低。但是如果两个人做一个菜呢?小张在做一个菜,做着做着锅被老王抢走了,老王不知道小张有没有放盐,就又放了一次盐,结果炒出来的菜太咸了,没法吃,然后他们就决定要出去皇城 PK。这里的“菜”对应着我们程序中的数据。而这种现象就是导致线程出现安全性的原因之一:竞态(Race Condition)。之所以会出现竞态是由 Java 的内存模型和线程调度机制决定的,关于 Java 的线程调度机制,在后面会有更详细的介绍。活跃性问题自从上次出了皇城 PK 的事情后,经理老李出了一条规定,打架扣 100,这条规定一出,小张和老王再也不敢 PK 了,不过没过几天,他们就找到了一种新的方式来互怼。有一天,小张在做菜,小张要先放盐再放糖,而老王拿着盐,老王要先放糖再放盐,结果过了两个小时两个人都没把菜做出来,经理老李再次陷入懵逼的状态。这就是线程活跃性问题的现象之一:死锁(Deadlock)。关于线程安全性的三个问题和线程活跃性的四个问题,在本文后面会做更详细的介绍。3. 什么是线程?3.1 线程简介线程是进程中可独立执行的最小单位,也是 CPU 资源分配的基本单位。进程是程序向操作系统申请资源的基本条件,一个进程可以包含多个线程,同一个进程中的线程可以共享进程中的资源,如内存空间和文件句柄。操作系统会把资源分配给进程,但是 CPU 资源比较特殊,它是分配给线程的,这里说的 CPU 资源也就是 CPU 时间片。进程与线程的关系,就像是饭店与员工的关系,饭店为顾客提供服务,而提供服务的具体方式是通过一个个员工实现的。线程的作用是执行特定任务,这个任务可以是下载文件、加载图片、绘制界面等。下面我们就来看看线程的四个属性、六个方法以及六种状态。3.2 线程的四个属性线程有编号、名字、类别以及优先级四个属性,除此之外,线程的部分属性还具有继承性,下面我们就来看看线程的四个属性的作用和线程的继承性。3.2.1 编号作用线程的编号(id)用于标识不同的线程,每条线程拥有不同的编号。注意事项不能作为唯一标识某个编号的线程运行结束后,该编号可能被后续创建的线程使用,因此编号不适合用作唯一标识只读编号是只读属性,不能修改3.2.2 名字每个线程都有自己的名字(name),名字的默认值是 Thread-线程编号,比如 Thread-0 。除了默认值,我们也可以给线程设置名字,以我们自己的方式去区分每一条线程。作用给线程设置名字可以让我们在某条线程出现问题时,用该线程的名字快速定位出问题的地方.3.2.3 类别线程的类别(daemon)分为守护线程和用户线程,我们可以通过 setDaemon(true) 把线程设置为守护线程。当 JVM 要退出时,它会考虑是否所有的用户线程都已经执行完毕,是的话则退出。而对于守护线程,JVM 在退出时不会考虑它是否执行完成。作用守护线程通常用于执行不重要的任务,比如监控其他线程的运行情况,GC 线程就是一个守护线程。注意事项setDaemon() 要在线程启动前设置,否则 JVM 会抛出非法线程状态异常抛出(IllegalThreadStateException)。3.2.4 优先级作用线程的优先级(Priority)用于表示应用希望优先运行哪个线程,线程调度器会根据这个值来决定优先运行哪个线程。取值范围Java 中线程优先级的取值范围为 1~10,默认值是 5,Thread 中定义了下面三个优先级常量。最低优先级:MIN_PRIORITY = 1默认优先级:NORM_PRIORITY = 5最高优先级:MAX_PRIORITY = 10注意事项不保证线程调度器把线程的优先级当作一个参考值,不一定会按我们设定的优先级顺序执行线程线程饥饿优先级使用不当会导致某些线程永远无法执行,也就是线程饥饿的情况。3.2.5 继承性线程的继承性指的是线程的类别和优先级属性是会被继承的,线程的这两个属性的初始值由开启该线程的线程决定。假如优先级为 5 的守护线程 A 开启了线程 B,那么线程 B 也是一个守护线程,而且优先级也是 5 。这时我们就把线程 A 叫做线程 B 的父线程,把线程 B 叫做线程 A 的子线程。3.3 线程的六个方法线程的常用方法有六个,它们分别是三个非静态方法 start()、run()、join() 和三个静态方法 currentThread()、yield()、sleep() 。下面我们就来看下这六个方法都有哪些作用和注意事项。start()方法start()是经常用到的方法,它的作用作用是使用事项如下作用start() 方法的作用是启动线程。注意事项该方法只能调用一次,再次调用不仅无法让线程再次执行,还会抛出非法线程状态异常。run()方法作用run() 方法中放的是任务的具体逻辑,该方法由 JVM 调用,一般情况下开发者不需要直接调用该方法。注意事项如果你调用了 run() 方法,加上 JVM 也调用了一次,那这个方法就会执行两次。join()方法作用join() 方法用于等待其他线程执行结束。如果线程 A 调用了线程 B 的 join() 方法,那线程 A 会进入等待状态,直到线程 B 运行结束。注意事项join() 方法导致的等待状态是可以被中断的,所以调用这个方法需要捕获中断异常Thread.currentThread()currentThread() 方法是一个静态方法,用于获取执行当前方法的线程。我们可以在任意方法中调用 Thread.currentThread() 获取当前线程,并设置它的名字和优先级等属性。Thread.yield()作用yield() 方法是一个静态方法,用于使当前线程放弃对处理器的占用,相当于是降低线程优先级。调用该方法就像是是对线程调度器说:“如果其他线程要处理器资源,那就给它们,否则我继续用”。注意事项该方法不一定会让线程进入暂停状态。Thread.sleep(ms)sleep(ms) 方法是一个静态方法,用于使当前线程在指定时间内休眠(暂停)。线程不止提供了上面的 6 个方法给我们使用,而其他方法的使用在文章的后面会有一个更详细的介绍。3.4 线程的六种状态3.4.1 线程的生命周期和 Activity 一样,线程也有自己的生命周期,而且生命周期事件也是由用户(开发者)触发的。从 Activity 的角度来看,用户点击按钮后打开一个 Activity,就相当于是触发了 Activity 的 onCreate() 方法。从线程的角度来看,开发者调用了 start() 方法,就相当于是触发了 Thread 的 run() 方法。如果我们在上一个 Activity 的 onPause() 方法中进行了耗时操作,那么下一个 Activity 的显示也会因为这个耗时操作而慢一点显示,这就相当于是 Thread 的等待状态。线程的生命周期不仅可以由开发者触发,还会受到其他线程的影响,下面是线程各个状态之间的转换示意图。
-
我们都知道future对象来实现的是并发操作,future根据我们的需求的并发类型(I/O密集型,或是cpu密集型)打包了multiprocessing和threading。它们并不能完全解决以外修改共享的问题,但却帮助我们更容易地追踪这一问题。Future为不同进程或进程间提供了明确的边界。和多进程池类似 他们用于调用并回答 类型的交互 其中处理过程可以在另外一个进程中,并且在未来的某个节点(毕竟就是这样命名的),你可以询问结果向他。Future实际上就是对进程池和线程池的封装,不过它提供了更加清楚的API。Future对象基本上封装了一个函数调用。函数调用进行在线程或进程的后台。Future对象的方法检查future是否结束,并在结束后获取结果。例子:From concurrent.futures import threadpoolexecutorFrom pathlib import pathFrom os.path import sep as pathsepFrom collections import dequeDef find _files(path,query_string):Subdirs=[]For p in path.iterdir(): Full_path = str(p.absolute()) If p.is_dir() and not p.is_symlink(): Subdirs.append(p) If query_string in full_path: Print(full_path) Return subdirsQuery = ‘.py’Futures = deque()Basedir = path(pathsep).absolute()With threadpoolexecutor(max_workers=10) as executor:Futures.append(Executor.submit(find_files,basedir,query))While futures:Futures=futures.popleft()If futures.exeception():ContinueElif futere,done():Subdirs=future.result()For subdir in subdirs: Futures.append(executor.submit( Find_files,subdir,query))Else: Futures.append(future)这段代码是由find_files的函数组成 运行在另外一个线程中 这个函数没有其他的特殊地方但是要注意他访问全局变量的过程,我们在开始之前先定义几个变量,在这个例子中我们会搜索所有带.py字符的文件我们有一个队列的future对象 稍后进行讨论 basedir变量指向文件系统的根目录 unnix系统中的是’/’windows系统中的是C:\.我们简单回顾下搜索的理论是什么,这一算是通过广度优先搜索遍历来实现的 而不是用深度优先搜索遍历实现的访问所有的路径 这一算法将所有当前目录的子目录添加到队列中 然后是所有这些目录的子目录来类推这段程序的主体是一个事件的循环 我们可以将 threadpoolexecutor构建为一个上下文管理器,这样一来在结束时就可以进行自动清理关闭所有的进程,需要一个max_workers参数来表明同时允许执行多个线程 如果提交了超过这一数量的任务将会排队等待 直到有能用的线程出现为止。在使用processpoolexecutor时 这一限制通常为机器的cpu数量 但对于线程 这一数值可以高很高 这依赖于同时等待i/o的数量。每个线程都需要占据一定的内存因此数量也不需要很多,数量太多的话磁盘速度而不是并发请求数量将会成为瓶颈
-
**关键词:根目录满、根目录使用率高、根目录大文件找不到** 【现象】 巡检发现集群一个节点根目录占用率高,现场排查未能找到占用空间的文件。 【案例排查】 1. 集群节点根目录df –h,根目录878G占用643G,现场du计算大约有150g左右,大约有500g文件找不到。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/9/1652079031764904460.png) 2. 执行lsof | grep delete 查找被删除打开中的文件,发现/var/chroot/MRS/test_hf.log 文件大小534457784196/1024/1024/1024=497.75g,与期望文件大小相匹配。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/9/1652079386367311809.png) 3. 查看/var/chroot/MRS 路径发现log确实已经被删除。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/9/1652079689640261830.png) 4. 根据占用文件进程PID 8734找到对应进程,ps –ef | grep 8734,发现/opt/dws/tmp下一个.sh脚本运行中打开了log文件。 ![](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/20225/9/1652079816162695142.png) 5. 查找对应的执行脚本,发现已被删除。 6. 在确认该脚本已无实际用途后,kill -9 8734,杀掉占用文件进程即可释放空间。 【根因】 1. 执行中脚本为死循环脚本,脚本被删除,生成log文件被删除,但未kill脚本进程。 2. 在linux中,当我们使用rm在linux上删除了大文件,但是,如果有进程打开了这个大文件,却没有关闭这个文件的句柄,那么:linux内核还是不会释放这个文件的磁盘空间,最后造成磁盘空间占用率高。
-
DbContext 生存期DbContext 的生存期从创建实例时开始,并在释放实例时结束。 DbContext 实例旨在用于单个工作单元。 这意味着 DbContext 实例的生存期通常很短。使用 Entity Framework Core (EF Core) 时的典型工作单元包括:创建 DbContext 实例根据上下文跟踪实体实例。 实体将在以下情况下被跟踪正在从查询返回正在添加或附加到上下文根据需要对所跟踪的实体进行更改以实现业务规则调用 SaveChanges 或 SaveChangesAsync。 EF Core 检测所做的更改,并将这些更改写入数据库。释放 DbContext 实例ASP.NET Core 依赖关系注入中的 DbContext在许多 Web 应用程序中,每个 HTTP 请求都对应于单个工作单元。 这使得上下文生存期与请求的生存期相关,成为 Web 应用程序的一个良好默认值。使用依赖关系注入配置 ASP.NET Core 应用程序。 可以使用 Startup.cs 的 ConfigureServices 方法中的 AddDbContext 将 EF Core 添加到此配置。使用“new”的简单的 DbContext 初始化可以按照常规的 .NET 方式构造 DbContext 实例,例如,使用 C# 中的 new。 可以通过重写 OnConfiguring 方法或通过将选项传递给构造函数来执行配置。使用 DbContext 工厂(例如对于 Blazor)某些应用程序类型(例如 ASP.NET Core Blazor)使用依赖关系注入,但不创建与所需的 DbContext 生存期一致的服务作用域。 即使存在这样的对齐方式,应用程序也可能需要在此作用域内执行多个工作单元。 例如,单个 HTTP 请求中的多个工作单元。在这些情况下,可以使用 AddDbContextFactory 来注册工厂以创建 DbContext 实例。DbContextOptions所有 DbContext 配置的起始点都是 DbContextOptionsBuilder。 可以通过三种方式获取此生成器:在 AddDbContext 和相关方法中在 OnConfiguring 中使用 new 显式构造上述各节显示了其中每个示例。 无论生成器来自何处,都可以应用相同的配置。 此外,无论如何构造上下文,都将始终调用 OnConfiguring。 这意味着即使使用 AddDbContext,OnConfiguring 也可用于执行其他配置。配置数据库提供程序每个 DbContext 实例都必须配置为使用一个且仅一个数据库提供程序。 (DbContext 子类型的不同实例可用于不同的数据库提供程序,但一个实例只能使用一个。)一个数据库提供程序要使用一个特定的 Use* 调用进行配置。下表包含常见数据库提供程序的示例。数据库系统配置示例NuGet 程序包SQL Server 或 Azure SQL.UseSqlServer(connectionString)Microsoft.EntityFrameworkCore.SqlServerAzure Cosmos DB.UseCosmos(connectionString, databaseName)Microsoft.EntityFrameworkCore.CosmosSQLite.UseSqlite(connectionString)Microsoft.EntityFrameworkCore.SqliteEF Core 内存中数据库.UseInMemoryDatabase(databaseName)Microsoft.EntityFrameworkCore.InMemoryPostgreSQL*.UseNpgsql(connectionString)Npgsql.EntityFrameworkCore.PostgreSQLMySQL/MariaDB*.UseMySql(connectionString)Pomelo.EntityFrameworkCore.MySqlOracle*.UseOracle(connectionString)Oracle.EntityFrameworkCore其他 DbContext 配置其他 DbContext 配置可以链接到 Use* 调用之前或之后(这不会有任何差别)。 例如,若要启用敏感数据日志记录DbContextOptions 与 DbContextOptions<TContext>大多数接受 DbContextOptions 的 DbContext 子类应使用 泛型DbContextOptions<TContext>变体。这可确保从依赖关系注入中解析特定 DbContext 子类型的正确选项,即使注册了多个 DbContext 子类型也是如此。设计时 DbContext 配置EF Core 设计时工具(如 EF Core迁移)需要能够发现并创建 DbContext 类型的工作实例,以收集有关应用程序的实体类型及其如何映射到数据库架构的详细信息。 只要该工具可以通过与在运行时的配置方式类似的方式轻松地创建 DbContext,就可以自动执行此过程。虽然向 DbContext 提供必要配置信息的任何模式都可以在运行时正常运行,但在设计时需要使用 DbContext 的工具只能使用有限数量的模式。 设计时上下文创建中包含更多详细信息。避免 DbContext 线程处理问题Entity Framework Core 不支持在同一 DbContext 实例上运行多个并行操作。 这包括异步查询的并行执行以及从多个线程进行的任何显式并发使用。 因此,始终立即 await 异步调用,或对并行执行的操作使用单独的 DbContext 实例。当 EF Core 检测到尝试同时使用 DbContext 实例的情况时,你将看到 InvalidOperationException,其中包含类似于以下内容的消息:在上一个操作完成之前,第二个操作已在此上下文中启动。 这通常是由使用同一个 DbContext 实例的不同线程引起的,但不保证实例成员是线程安全的。检测不到并发访问时,可能会导致未定义的行为、应用程序崩溃和数据损坏。一些常见错误可能会无意中导致并发访问同一 DbContext 实例:异步操作缺陷使用异步方法,EF Core 可以启动以非阻挡式访问数据库的操作。 但是,如果调用方不等待其中一个方法完成,而是继续对 DbContext 执行其他操作,则 DbContext 的状态可能会(并且很可能会)损坏。始终立即等待 EF Core 异步方法。通过依赖关系注入隐式共享 DbContext 实例默认情况下 AddDbContext 扩展方法使用DbContext范围内生存期来注册 类型。这样可以避免在大多数 ASP.NET Core 应用程序中出现并发访问问题,因为在给定时间内只有一个线程在执行每个客户端请求,并且每个请求都有单独的依赖关系注入范围(因此有单独的 DbContext 实例)。 对于 Blazor Server 托管模型,一个逻辑请求用来维护 Blazor 用户线路,因此,如果使用默认注入范围,则每个用户线路只能提供一个范围内的 DbContext 实例。任何并行显式执行多个线程的代码都应确保 DbContext 实例不会同时访问。使用依赖关系注入可以通过以下方式实现:将上下文注册为范围内,并为每个线程创建范围(使用 IServiceScopeFactory),或将 DbContext 注册为暂时性(使用采用 ServiceLifetime 参数的 AddDbContext 的重载)。
-
大家好,本篇文章主要讲的是安装tomcat后可能出现的问题介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览 1. 没有开tomcat服务 在浏览器的地址栏中输入localhost:8080 回车会出现如下界面 tomcat没有开服务。在cmd中输入startup.bat。不要关闭开启的tomcat窗口。 2.输入url路径错误 在浏览器中输入url显示404,可能是输入的路径不存在或输入错误路径。 3. Java JDK环境变量未设置好 在cmd输入startup.bat时,报错。 如图: 解决方案: 这个是Java jdk的环境变量名设置不规范,将jdk的路径设置变量名必须是JAVA_HOME。 找到系统变量界面,更改错误的jdk变量名。 更改完后: 修改完之后,重新打开cmd在输入startup.bat就可以正常启动了 4.重复开启服务 服务已经开启,不需要重复打开。 5.端口被占用 通过网页访问出现 Access Error错误,端口被占用 打开cmd输入指令:将占用你所指定的端口号进程删掉netstat -ano :查看所有端口信息netstat ano | findstr "8080" :查看端口8080占用信息tasklist :查看所有进程tasklist | findstr "1": 查看某进程taskkill /f /pid 进程号: 删除某进程号转载自https://www.jb51.net/article/233667.htm
-
openGauss开机自启动,我们先来了解一下自定义服务的配置文件组成部分,共分为[Unit]、[Service]、[Install]三个部分,下面以centos7.6为例。[Unit] Description=当前服务的简单描述Documentation= 服务配置文件的位置 Before= 在某服务之前启动 After= 在某服务之后启动 Wants= 与某服务存在“依赖”关系,依赖服务退出,不影响本服务运行 Requires= 与某服务存在“强依赖”关系,依赖服务故障,本服务也随之退出 [Service] Type= --simple(默认值):ExecStart字段启动的进程为主进程。 --forking:ExecStart字段将以fork()方式启动,后台运行。 --oneshot:类似于simple,只执行一次,Systemd会等它执行完,才启动其他服务。 --dbus:类似于simple,等待D-Bus信号后再启动。--notify:类似于simple,启动结束后会发出通知信号,Systemd再启动其他服务。 --idle:类似于simple,等其他任务都执行完,才会启动该服务。 User= 服务运行的用户 Group= 服务运行的用户组 ExecStart= 启动服务的命令,可以是可执行程序、系统命令或shell脚本,必须是绝对路径。ExecReload= 重启服务的命令,可以是可执行程序、系统命令或shell脚本,必须是绝对路径。 ExecStop= 停止服务的命令,可以是可执行程序、系统命令或shell脚本,必须是绝对路径。ExecStartPre= 启动服务之前执行的命令ExecStartPost= 启动服务之后执行的命令ExecStopPost= 停止服务之后执行的命令PrivateTmp= True表示给服务分配独立的临时空间KillSignal= 信号量,一般为SIGQUITTimeoutStartSec= 启动超时时间TimeoutStopSec= 停止超时时间 TimeoutSec= 同时设置 TimeoutStartSec= 与 TimeoutStopSec= 的快捷方式 PIDFile= PID文件路径 KillMode= Systemd停止sshd服务方式 --control-group(默认值):所有子进程,都会被杀掉。 --process:只杀主进程。 --mixed:主进程将收到SIGTERM信号,子进程收到SIGKILL信号。 --none:没有进程会被杀掉,只是执行服务的stop命令。 Restart=服务程序退出后,Systemd的重启方式 --no(默认值):退出后不会重启。 --on-success:只有正常退出时(退出状态码为0),才会重启。 --on-failure:只有非正常退出时(退出状态码非0,包括被信号终止和超时),才会重启。 --on-abnormal:只有被信号终止和超时,才会重启。--on-abort:只有在收到没有捕捉到的信号终止时,才会重启。 --on-watchdog:超时退出,才会重启。 --always:总是重启。 RestartSec= 重启服务之前,需要等待的秒数RemainAfterExit= yes 进程退出以后,服务仍然保持执行 [Install] WantedBy=multi-user.target --WantedBy字段,表示该服务所在的 Targe,target的含义是服务组,表示一组服务 --multi-user.target,表示多用户命令行状态 --graphical.target,表示图形用户状态,它依赖于multi-user.targetopenGauss单机自启动模版配置自定义服务--/usr/lib/systemd/system/mogdb.service [Unit] Description=openGauss Documentation=openGauss ServerAfter=syslog.target After=network.target [Service] Type=forking User=omm Group=dbgrp Environment=PGDATA=/data/opengauss/data Environment=GAUSSHOME=/data/opengauss/app Environment=LD_LIBRARY_PATH=/data/opengauss/app/lib ExecStart=/data/opengauss/app/bin/gaussdb ExecReload=/bin/kill -HUP $MAINPID KillMode=mixed KillSignal=SIGINT TimeoutSec=0 [Install] WantedBy=multi-user.target 添加到开机自启动systemctl daemon-reload systemctl enable opengauss systemctl start opengauss systemctl status opengauss systemctl stop opengauss openGauss集群自启动模版配置自定义服务-/usr/lib/systemd/system/opengauss_om.service [Unit] Description=openGauss Documentation=openGauss Server After=syslog.target After=network.target [Service] Type=forking User=omm Group=dbgrp Environment=GPHOME=/data/opengauss/gausstools Environment=PGDATA=/data/opengauss/data Environment=GAUSSHOME=/data/opengauss/app Environment=LD_LIBRARY_PATH=/data/opengauss/app/lib ExecStart=/data/opengauss/gausstools/script/gs_om -t start ExecReload=/bin/kill -HUP $MAINPID KillMode=mixed KillSignal=SIGINT TimeoutSec=0 [Install] WantedBy=multi-user.target 添加到开机自启动systemctl daemon-reload systemctl enable opengauss_om systemctl start opengauss_om systemctl status opengauss_om systemctl stop opengauss_om
-
在信息技术飞速发展的今天,各种类型数据库层出不穷。由于支持数据在异构数据库间同步,逻辑复制的重要性与日俱增。当前openGauss逻辑复制串行解码平均性能为3~5MBps,在业务压力大的场景下难以满足实时同步的需求,导致日志堆积,从而影响生产集群业务。因此,我们设计了并行解码特性,令多个线程协同并行解码从而提高解码性能,在基础场景下解码性能可达到100MBps.设计思路——为什么考虑并行解码?原有的串行解码逻辑,从读取日志,到日志解码,以及结果拼接发送都是由单线程处理的,主要流程及耗时的大致比例如下图所示:可以看出,整个流程的主要时间消耗在解码步骤中,需要通过多线程解码来进行优化;而发送步骤的耗时也较为明显,可以使用批量发送来做进一步优化。工作流程——并行解码消息序列图如下所示,在并行解码中,openGauss的数据节点(Data Node, DN)上的工作线程被分为三种:Sender/Collector线程,负责接收客户的解码请求,以及拼接各解码线程的结果并发送给客户,该线程在一次解码请求中仅建立一个;Reader/Dispatcher线程,负责读取WAL日志并分发给各解码线程进行解码,该线程在一次解码请求中仅建立一个;Decoder线程,即解码线程,负责将Reader/Dispatcher线程发给自己的日志解码(当该线程已经在解码时,该部分日志暂存于read change队列中),并将解码结果(当尚未解到提交日志时,该部分结果暂存于decode change队列中)发给Sender/Collector线程,该线程在一次解码请求中可以建立多个。该消息序列图说明如下:客户端向DN发送逻辑复制请求,该DN可为主机或备机。逻辑复制选项里可以设置参数选择仅连接备机,以防止主机压力过大。除了接收到客户请求的Sender线程外,DN还需建立Reader/Dispatcher线程和若干Decoder线程。Reader读取xlog日志,进行预处理。如果相关日志涉及TOAST列,需在此步骤完成TOAST拼接。Dispatcher将预处理后的日志派发给各Decoder线程。Decoder线程各自独立进行解码。这里可以通过配置选项来设置解码格式(json、text或bin)。Decoder将解码结果发送给Collector。Collector以事务为单位汇总解码结果。为减少发送次数,降低网络I/O对解码性能的影响,在开启批量发送功能(即sending-batch参数设置为1)时,Sender积攒一定量的日志后(阈值设置为1MB),批量向客户返回解码结果。客户如需停止逻辑复制流程,断开与DN的逻辑复制连接即可。Sender向Reader/Dispatcher线程和Decoder线程发送退出信号。各线程收到退出信号后,释放占用的资源,完成环境清理并退出。技术细节1——可见性改造在逻辑解码中,由于是对历史日志进行解析,因此对日志中元组的可见性判断至关重要。在原有串行解码的逻辑里,我们使用活跃事务链表机制来进行可见性判断。但是对于并行解码,每个解码线程各自维护一个活跃事务链表代价较大,会对解码性能产生不利影响,因此我们做了可见性改造,使用CSN(Commit Sequence Number,即提交事务号)来进行元组可见性判断。针对每一个事务号xid,其可见性流程如下:其主要流程为:根据xid获取用来判断可见性的CSN,这里确保可以根据任一xid获取到CSN值。如果xid为异常值,会返回表示特定状态的CSN,这种CSN也可用于可见性判断;若CSN已提交,将其与快照中CSN比较,该事务CSN较小则不可见,否则可见;若CSN不是正在提交,则不可见。基于上述逻辑,在并行解码中,判断一个元组的快照可见性的逻辑则为依次判断元组Xmin(插入时的事务号)和Xmax(删除/更新时的事务号)的快照可见性。这里的整体思路是,Xmin不可见/未提交或Xmax可见则元组不可见,而Xmin可见且Xmax不可见/未提交则元组可见。元组中的各标记位维持其原有含义参与可见性判断。技术细节2——批量发送在使用并行解码之后,解码过程占用的时间出现显著下降,但此时解码结果发送线程又将成为瓶颈,对于每条解码结果均执行一次完整的发送流程代价太大。因此这里我们采用了批量发送的方式,将解码的结果暂时收集起来,在超过阈值时一并发送给客户端。在批量发送时,需额外记录每条解码结果的长度,以及约定好的分隔符,以便并行解码功能的使用者对批量发送的日志进行拆分处理。使用方式我们为并行解码新增了一些可选配置项:解码线程并发度通过配置选项parallel-decode-num,指定并行解码的Decoder线程数量。其取值范围为1~20的int型,取1表示按照原有的串行逻辑进行解码,不会进入本特性代码逻辑。默认值为1。当该选项配置为1时,禁止配置下面的解码格式选项decode-style。解码白名单通过配置选项white-table-list,指定需要解码的表。其取值为text类型包含白名单中表名的字符串,不同的表以’,'为分隔符进行隔离。例:select * from pg_logical_slot_peek_changes(‘slot1’, NULL, 4096, ‘white-table-list’, ‘public.t1,public.t2’);。限制仅备机解码通过配置选项standby-connection,指定是否限制仅备机解码。其取值为bool型,取true代表限制仅允许连接备机解码,连接主机解码时会报错退出;取false时不做限制。默认值为false。解码格式通过配置选项decode-style,指定解码格式。其取值为char型的字符’j’、’t’或’b’,分别代表json格式、text格式及二进制格式。默认值为’b’即二进制格式解码。批量发送通过配置选项sending-batch,指定是否批量发送解码结果。其取值为int型的0或1,0代表不开启批量发送,1代表解码结果累计达到或刚超过1MB时批量发送解码结果,这里默认值为0,即默认不开启批量发送。以使用JDBC进行并行解码为例,建立连接时需进行如下配置:PGReplicationStream stream = conn .getReplicationAPI() .replicationStream() .logical() .withSlotName(replSlotName) .withSlotOption("include-xids", true) .withSlotOption("skip-empty-xacts", true) .withSlotOption("parallel-decode-num", 10) .withSlotOption("white-table-list", "public.t1,public.t2") .withSlotOption("standby-connection", true) .withSlotOption("decode-style", "t") .withSlotOption("sending-bacth", 1) .start();从倒数第6行到倒数第2行为新增逻辑,代表10并发解码、仅解码表public.t1和public.t2、启用备机连接、解码格式为text且开启批量发送功能,在配置参数超出范围时,会报错并提示参数允许取值范围。辅助功能——监控函数在进行并行解码时,为在解码速度较慢的场景下方便定位解码性能瓶颈,我们增加了gs_get_parallel_decode_status()函数,用来查看当前DN上各解码线程的存储尚未解码日志的read change队列和存储尚未发送解码结果的decode change队列的长度。该函数没有入参,返回结果共四列,分别是slot_name、parallel_decode_num、read_change_queue_length、decode_change_queue_length。slot_name为复制槽名,类型为text;parallel_decode_num为并行解码线程数,类型为int;read_change_queue_length类型为text,其记录了每个解码线程的read change)队列长度;decode_change_queue_length类型为text,其记录了每个解码线程的decode change队列长度。使用方式如下所示:对于解码失速场景,在解码DN上执行此函数,然后查看查询结果的read_change_queue_length,这里记录每个解码线程里读取日志队列长度,如果这里的值过小表示日志读取阻塞,需继续定位是否为磁盘I/O不足。查看查询结果的decode_change_queue_length,这里记录每个解码线程里解码日志队列长度,如果这里值过小表示解码速度过慢,可以适当增加解码线程数量。如果read_change_queue_length和decode_change_queue_length均比较大,说明解码日志发送被阻塞,需检测并行解码使用者在目标数据库的回放日志速度。通常来说,解码失速场景一般有CPU、I/O、或内存资源不足造成,通过使用备机解码保证以上资源充足一般可以避免解码失速问题。结语并行解码可以大幅提升逻辑复制解码性能,其对解码实例增加的业务压力相较而言显得不那么重要。作为异构数据库数据复制的关键技术方案,并行解码对于openGauss的重要性也是不言而喻的。
-
这篇总结主要是基于我之前JVM系列文章而形成的的。主要是把重要的知识点用自己的话说了一遍,可能会有一些错误,还望见谅和指点。谢谢#更多详细内容可以查看我的专栏文章:深入理解JVM虚拟机https://blog.csdn.net/column/details/21960.htmlJVM介绍和源码首先JVM是一个虚拟机,当你安装了jre,它就包含了jvm环境。JVM有自己的内存结构,字节码执行引擎,因此class字节码才能在jvm上运行,除了Java以外,Scala,groovy等语言也可以编译成字节码而后在jvm中运行。JVM是用c开发的。JVM内存模型内存模型老生常谈了,主要就是线程共享的堆区,方法区,本地方法栈。还有线程私有的虚拟机栈和程序计数器。堆区存放所有对象,每个对象有一个地址,Java类jvm初始化时加载到方法区,而后会在堆区中生成一个Class对象,来负责这个类所有实例的实例化。栈区存放的是栈帧结构,栈帧是一段内存空间,包括参数列表,返回地址,局部变量表等,局部变量表由一堆slot组成,slot的大小固定,根据变量的数据类型决定需要用到几个slot。方法区存放类的元数据,将原来的字面量转换成引用,当然,方法区也提供常量池,常量池存放-128到127的数字类型的包装类。字符串常量池则会存放使用intern的字符串变量。JVM OOM和内存泄漏这里指的是oom和内存泄漏这类错误。oom一般分为三种,堆区内存溢出,栈区内存溢出以及方法区内存溢出。堆内存溢出主要原因是创建了太多对象,比如一个集合类死循环添加一个数,此时设置jvm参数使堆内存最大值为10m,一会就会报oom异常。栈内存溢出主要与栈空间和线程有关,因为栈是线程私有的,如果创建太多线程,内存值超过栈空间上限,也会报oom。方法区内存溢出主要是由于动态加载类的数量太多,或者是不断创建一个动态代理,用不了多久方法区内存也会溢出,会报oom,这里在1.7之前会报permgem oom,1.8则会报meta space oom,这是因为1.8中删除了堆中的永久代,转而使用元数据区。内存泄漏一般是因为对象被引用无法回收,比如一个集合中存着很多对象,可能你在外部代码把对象的引用置空了,但是由于对象还被集合给引用着,所以无法被回收,导致内存泄漏。测试也很简单,就在集合里添加对象,添加完以后把引用置空,循环操作,一会就会出现oom异常,原因是内存泄漏太多了,导致没有空间分配新的对象。常见调试工具命令行工具有jstack jstat jmap 等,jstack可以跟踪线程的调用堆栈,以便追踪错误原因。jstat可以检查jvm的内存使用情况,gc情况以及线程状态等。jmap用于把堆栈快照转储到文件系统,然后可以用其他工具去排查。visualvm是一款很不错的gui调试工具,可以远程登录主机以便访问其jvm的状态并进行监控。class文件结构class文件结构比较复杂,首先jvm定义了一个class文件的规则,并且让jvm按照这个规则去验证与读取。开头是一串魔数,然后接下来会有各种不同长度的数据,通过class的规则去读取这些数据,jvm就可以识别其内容,最后将其加载到方法区。JVM的类加载机制jvm的类加载顺序是bootstrap类加载器,extclassloader加载器,最后是appclassloader用户加载器,分别加载的是jdk/bin ,jdk/ext以及用户定义的类目录下的类(一般通过ide指定),一般核心类都由bootstrap和ext加载器来加载,appclassloader用于加载自己写的类。双亲委派模型,加载一个类时,首先获取当前类加载器,先找到最高层的类加载器bootstrap让他尝试加载,他如果加载不了再让ext加载器去加载,如果他也加载不了再让appclassloader去加载。这样的话,确保一个类型只会被加载一次,并且以高层类加载器为准,防止某些类与核心类重复,产生错误。defineclass findclass和loadclass类加载classloader中有两个方法loadclass和findclass,loadclass遵从双亲委派模型,先调用父类加载的loadclass,如果父类和自己都无法加载该类,则会去调用findclass方法,而findclass默认实现为空,如果要自定义类加载方式,则可以重写findclass方法。常见使用defineclass的情况是从网络或者文件读取字节码,然后通过defineclass将其定义成一个类,并且返回一个Class对象,说明此时类已经加载到方法区了。当然1.8以前实现方法区的是永久代,1.8以后则是元空间了。JVM虚拟机字节码执行引擎jvm通过字节码执行引擎来执行class代码,他是一个栈式执行引擎。这部分内容比较高深,在这里就不献丑了。编译期优化和运行期优化编译期优化主要有几种1 泛型的擦除,使得泛型在编译时变成了实际类型,也叫伪泛型。2 自动拆箱装箱,foreach循环自动变成迭代器实现的for循环。3 条件编译,比如if(true)直接可得。运行期优化主要有几种1 JIT即时编译Java既是编译语言也是解释语言,因为需要编译代码生成字节码,而后通过解释器解释执行。但是,有些代码由于经常被使用而成为热点代码,每次都编译太过费时费力,干脆直接把他编译成本地代码,这种方式叫做JIT即时编译处理,所以这部分代码可以直接在本地运行而不需要通过jvm的执行引擎。2 公共表达式擦除,就是一个式子在后面如果没有被修改,在后面调用时就会被直接替换成数值。3 数组边界擦除,方法内联,比较偏,意义不大。4 逃逸分析,用于分析一个对象的作用范围,如果只局限在方法中被访问,则说明不会逃逸出方法,这样的话他就是线程安全的,不需要进行并发加锁。1JVM的垃圾回收1 GC算法:停止复制,存活对象少时适用,缺点是需要两倍空间。标记清除,存活对象多时适用,但是容易产生随便。标记整理,存活对象少时适用,需要移动对象较多。2 GC分区,一般GC发生在堆区,堆区可分为年轻代,老年代,以前有永久代,现在没有了。年轻代分为eden和survior,新对象分配在eden,当年轻代满时触发minor gc,存活对象移至survivor区,然后两个区互换,等待下一场gc,当对象存活的阈值达到设定值时进入老年代,大对象也会直接进入老年代。老年代空间较大,当老年代空间不足以存放年轻代过来的对象时,开始进行full gc。同时整理年轻代和老年代。一般年轻代使用停止复制,老年代使用标记清除。3 垃圾收集器serial串行parallel并行它们都有年轻代与老年代的不同实现。然后是scanvage收集器,注重吞吐量,可以自己设置,不过不注重延迟。cms垃圾收集器,注重延迟的缩短和控制,并且收集线程和系统线程可以并发。cms收集步骤主要是,初次标记gc root,然后停顿进行并发标记,而后处理改变后的标记,最后停顿进行并发清除。g1收集器和cms的收集方式类似,但是g1将堆内存划分成了大小相同的小块区域,并且将垃圾集中到一个区域,存活对象集中到另一个区域,然后进行收集,防止产生碎片,同时使分配方式更灵活,它还支持根据对象变化预测停顿时间,从而更好地帮用户解决延迟等问题。
-
管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。命名管道FIFO:未命名的管道只能在两个相关的进程之间通信,通过命名管道FIFO,不相关的进程也能交换数据。消息队列:消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。消息队列允许一个或多个进程向它写入与读取消息。管道和命名管道的通信数据都是先进先出原则,消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取,比FIFO更有优势。共享内存:共享内存是允许一个或多个进程共享的一块内存区域。信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。通常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。
-
这篇总结主要是基于我之前JVM系列文章而形成的的。主要是把重要的知识点用自己的话说了一遍,可能会有一些错误,还望见谅和指点。谢谢#更多详细内容可以查看我的专栏文章:深入理解JVM虚拟机https://blog.csdn.net/column/details/21960.htmlJVM介绍和源码首先JVM是一个虚拟机,当你安装了jre,它就包含了jvm环境。JVM有自己的内存结构,字节码执行引擎,因此class字节码才能在jvm上运行,除了Java以外,Scala,groovy等语言也可以编译成字节码而后在jvm中运行。JVM是用c开发的。JVM内存模型内存模型老生常谈了,主要就是线程共享的堆区,方法区,本地方法栈。还有线程私有的虚拟机栈和程序计数器。堆区存放所有对象,每个对象有一个地址,Java类jvm初始化时加载到方法区,而后会在堆区中生成一个Class对象,来负责这个类所有实例的实例化。栈区存放的是栈帧结构,栈帧是一段内存空间,包括参数列表,返回地址,局部变量表等,局部变量表由一堆slot组成,slot的大小固定,根据变量的数据类型决定需要用到几个slot。方法区存放类的元数据,将原来的字面量转换成引用,当然,方法区也提供常量池,常量池存放-128到127的数字类型的包装类。字符串常量池则会存放使用intern的字符串变量。JVM OOM和内存泄漏这里指的是oom和内存泄漏这类错误。oom一般分为三种,堆区内存溢出,栈区内存溢出以及方法区内存溢出。堆内存溢出主要原因是创建了太多对象,比如一个集合类死循环添加一个数,此时设置jvm参数使堆内存最大值为10m,一会就会报oom异常。栈内存溢出主要与栈空间和线程有关,因为栈是线程私有的,如果创建太多线程,内存值超过栈空间上限,也会报oom。方法区内存溢出主要是由于动态加载类的数量太多,或者是不断创建一个动态代理,用不了多久方法区内存也会溢出,会报oom,这里在1.7之前会报permgem oom,1.8则会报meta space oom,这是因为1.8中删除了堆中的永久代,转而使用元数据区。内存泄漏一般是因为对象被引用无法回收,比如一个集合中存着很多对象,可能你在外部代码把对象的引用置空了,但是由于对象还被集合给引用着,所以无法被回收,导致内存泄漏。测试也很简单,就在集合里添加对象,添加完以后把引用置空,循环操作,一会就会出现oom异常,原因是内存泄漏太多了,导致没有空间分配新的对象。常见调试工具命令行工具有jstack jstat jmap 等,jstack可以跟踪线程的调用堆栈,以便追踪错误原因。jstat可以检查jvm的内存使用情况,gc情况以及线程状态等。jmap用于把堆栈快照转储到文件系统,然后可以用其他工具去排查。visualvm是一款很不错的gui调试工具,可以远程登录主机以便访问其jvm的状态并进行监控。class文件结构class文件结构比较复杂,首先jvm定义了一个class文件的规则,并且让jvm按照这个规则去验证与读取。开头是一串魔数,然后接下来会有各种不同长度的数据,通过class的规则去读取这些数据,jvm就可以识别其内容,最后将其加载到方法区。JVM的类加载机制jvm的类加载顺序是bootstrap类加载器,extclassloader加载器,最后是appclassloader用户加载器,分别加载的是jdk/bin ,jdk/ext以及用户定义的类目录下的类(一般通过ide指定),一般核心类都由bootstrap和ext加载器来加载,appclassloader用于加载自己写的类。双亲委派模型,加载一个类时,首先获取当前类加载器,先找到最高层的类加载器bootstrap让他尝试加载,他如果加载不了再让ext加载器去加载,如果他也加载不了再让appclassloader去加载。这样的话,确保一个类型只会被加载一次,并且以高层类加载器为准,防止某些类与核心类重复,产生错误。defineclass findclass和loadclass类加载classloader中有两个方法loadclass和findclass,loadclass遵从双亲委派模型,先调用父类加载的loadclass,如果父类和自己都无法加载该类,则会去调用findclass方法,而findclass默认实现为空,如果要自定义类加载方式,则可以重写findclass方法。常见使用defineclass的情况是从网络或者文件读取字节码,然后通过defineclass将其定义成一个类,并且返回一个Class对象,说明此时类已经加载到方法区了。当然1.8以前实现方法区的是永久代,1.8以后则是元空间了。JVM虚拟机字节码执行引擎jvm通过字节码执行引擎来执行class代码,他是一个栈式执行引擎。这部分内容比较高深,在这里就不献丑了。编译期优化和运行期优化编译期优化主要有几种1 泛型的擦除,使得泛型在编译时变成了实际类型,也叫伪泛型。2 自动拆箱装箱,foreach循环自动变成迭代器实现的for循环。3 条件编译,比如if(true)直接可得。运行期优化主要有几种1 JIT即时编译Java既是编译语言也是解释语言,因为需要编译代码生成字节码,而后通过解释器解释执行。但是,有些代码由于经常被使用而成为热点代码,每次都编译太过费时费力,干脆直接把他编译成本地代码,这种方式叫做JIT即时编译处理,所以这部分代码可以直接在本地运行而不需要通过jvm的执行引擎。2 公共表达式擦除,就是一个式子在后面如果没有被修改,在后面调用时就会被直接替换成数值。3 数组边界擦除,方法内联,比较偏,意义不大。4 逃逸分析,用于分析一个对象的作用范围,如果只局限在方法中被访问,则说明不会逃逸出方法,这样的话他就是线程安全的,不需要进行并发加锁。1JVM的垃圾回收1 GC算法:停止复制,存活对象少时适用,缺点是需要两倍空间。标记清除,存活对象多时适用,但是容易产生随便。标记整理,存活对象少时适用,需要移动对象较多。2 GC分区,一般GC发生在堆区,堆区可分为年轻代,老年代,以前有永久代,现在没有了。年轻代分为eden和survior,新对象分配在eden,当年轻代满时触发minor gc,存活对象移至survivor区,然后两个区互换,等待下一场gc,当对象存活的阈值达到设定值时进入老年代,大对象也会直接进入老年代。老年代空间较大,当老年代空间不足以存放年轻代过来的对象时,开始进行full gc。同时整理年轻代和老年代。一般年轻代使用停止复制,老年代使用标记清除。3 垃圾收集器serial串行parallel并行它们都有年轻代与老年代的不同实现。然后是scanvage收集器,注重吞吐量,可以自己设置,不过不注重延迟。cms垃圾收集器,注重延迟的缩短和控制,并且收集线程和系统线程可以并发。cms收集步骤主要是,初次标记gc root,然后停顿进行并发标记,而后处理改变后的标记,最后停顿进行并发清除。g1收集器和cms的收集方式类似,但是g1将堆内存划分成了大小相同的小块区域,并且将垃圾集中到一个区域,存活对象集中到另一个区域,然后进行收集,防止产生碎片,同时使分配方式更灵活,它还支持根据对象变化预测停顿时间,从而更好地帮用户解决延迟等问题。
-
# 一、问题现象 某个线下环境DN发生主备切换 # 二、问题影响 DN实例很频繁发生切换,业务出现断链情况 # 三、问题定位 ## 1.根据“由现象看问题本质”的原则,先看DN实例的日志 ``` source /opt/huawei/Bigdata/mppdb/.mppdbgs_profile cd $GAUSSLOG/pg_log/dn_6003 ll -thr vi postgres-xxx.log ``` 看到DN日志报:Too many open file in systems ## 2.查看OS日志 ``` cd /var/log tail -500f messages ``` 看到OS日志很多行报:VFS:file-max limit 640000 reached ## 3.进一步监控 ``` lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr|more|head -n 20 ``` 本次处理打印了最多的20个文件句柄的进程,打印出结果的第二列为进程ID,可以观察是那个进程打开的文件过过多 ``` lsof -u omm |awk '{print $2}'| sort | uniq -c | sort -nr | head ``` 观察用户打开的文件句柄数 ``` ps -ef|grep 46905 ``` 也可以使用一下脚本监控: ``` #!/bin/bash while true do for PID in `lsof -u omm|awk '{print $2}'|sort|uniq -c|sort -nr| head | awk '{print $2}' | head -n 1` do date +'%Y-%m-%d %H:%M:%S' >> /home/omm/pid-2022-01-26.txt echo $PID >> /home/omm/pid-2022-01-26.txt lsof -p $PID >> /home/omm/pid-2022-01-26.txt sleep 1800 done done ``` ## 4.经确认文件句柄数达到阈值与近期新上的业务语句有关 好多表union all 和 join ## 5.调整omm用户文件句柄数 一般默认是1000000(100W),不建议改动,因为跟主机的综合性能有关,此处改为500W观察。
上滑加载中
推荐直播
-
华为AI技术发展与挑战:集成需求分析的实战指南
2024/11/26 周二 18:20-20:20
Alex 华为云学堂技术讲师
本期直播将综合讨论华为AI技术的发展现状,技术挑战,并深入探讨华为AI应用开发过程中的需求分析过程,从理论到实践帮助开发者快速掌握华为AI应用集成需求的框架和方法。
去报名 -
华为云DataArts+DWS助力企业数据治理一站式解决方案及应用实践
2024/11/27 周三 16:30-18:00
Walter.chi 华为云数据治理DTSE技术布道师
想知道数据治理项目中,数据主题域如何合理划分?数据标准及主数据标准如何制定?数仓分层模型如何合理规划?华为云DataArts+DWS助力企业数据治理项目一站式解决方案和应用实践告诉您答案!本期将从数据趋势、数据治理方案、数据治理规划及落地,案例分享四个方面来助力企业数据治理项目合理咨询规划及顺利实施。
去报名
热门标签