-
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文件找不到。  2. 执行lsof | grep delete 查找被删除打开中的文件,发现/var/chroot/MRS/test_hf.log 文件大小534457784196/1024/1024/1024=497.75g,与期望文件大小相匹配。  3. 查看/var/chroot/MRS 路径发现log确实已经被删除。  4. 根据占用文件进程PID 8734找到对应进程,ps –ef | grep 8734,发现/opt/dws/tmp下一个.sh脚本运行中打开了log文件。  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的重要性也是不言而喻的。
-
# 一、问题现象 某个线下环境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观察。
-
1、关于鲲鹏 1.1、鲲鹏介绍 鲲鹏计算产业是基于鲲鹏处理器的基础软硬件设施、行业应用及服务,涵盖从底层硬件、基础软件到上层行业应用的全产业链条。华为作为鲲鹏计算产业的成员,聚焦计算架构创新、处理器和开源基础软件的研发,以及华为云服务,致力于推动鲲鹏生态发展。通过战略性、长周期的研发投入,吸纳全球计算产业的优秀人才和先进技术,持续推进全栈计算技术的创新发展,加快构筑面向多样性计算的全球开源体系与产业标准。基于“硬件开放、软件开源、使能伙伴、发展人才”的策略推动鲲鹏计算产业发展。 1.2、鲲鹏解决方案 鲲鹏全栈解决方案,主要应用在金融、互联网、运营商、政府、电力、交通等行业。其中应用使能套件BoostKit可应用于大数据、分布式存储、数据库、虚拟化ARM原生等方面。基础软件可应用于openGauss企业级开源数据库、openEuler开源操作系统。开发套件DevKit包含鲲鹏代码迁移工具、鲲鹏编译器、鲲鹏性能分析工具、动态二进制翻译工具。 2、鲲鹏开发套件DevKit 2.1、DevKit介绍 鲲鹏开发套件DevKit提供涵盖代码开发、编译调试、云测服务、性能分析及系统诊断等各环节的开发使能工具,方便开发者快速开发出鲲鹏亲和的高性能软件,帮助开发者加速应用迁移和算力升级。同时面向全研发作业流程,提升应用迁移和调优效率,加速原生开发。 鲲鹏开发套件DevKit以开发者为中心,并提升全流程开发效率。 开发套件DevKit包含鲲鹏代码迁移工具、鲲鹏编译器、性能分析工具、动态二进制翻译工具等。 2.2、鲲鹏代码迁移工具 2.2.1、工具简介 鲲鹏代码迁移工具是一款可以简化客户应用迁移到基于鲲鹏916/920的服务器的过程的工具。工具仅支持x86 Linux到Kunpeng Linux的扫描与分析,不支持Windows软件代码的扫描、分析与迁移。 当客户有x86平台上源代码的软件要迁移到基于鲲鹏916/920的服务器上时,既可以使用该工具分析可迁移性和迁移投入,也可以使用该工具自动分析出需修改的代码内容,并指导用户如何修改。 鲲鹏代码迁移工具既解决了客户软件迁移评估分析过程中人工分析投入大、准确率低、整体效率低下的痛点,通过该工具能够自动分析并输出指导报告;也解决了用户代码兼容性人工排查困难、迁移经验欠缺、反复依赖编译调错定位等痛点。 2.2.2、应用场景 软件迁移评估:自动扫描并分析软件包(非源码包)、已安装的软件,提供可迁移性评估报告。 源码迁移:当用户有软件要迁移到基于鲲鹏916/920的服务器上时,可先用该工具分析源码并得到迁移修改建议。 软件包重构:帮助用户重构适用于鲲鹏平台的软件安装包。 专项软件迁移:使用华为提供的软件迁移模板修改、编译并产生指定软件版本的安装包,该软件包适用于鲲鹏平台。 增强功能:支持x86和鲲鹏平台GCC 4.8.5~GCC 9.3.0版本32位应用向64位应用迁移的64位运行模式检查,结构体字节对齐检查、缓存行对齐检查和鲲鹏平台上的内存一致性检查。 2.2.3、部署方式 单机部署,即将鲲鹏代码迁移工具部署在用户的开发、测试的x86服务器或者基于鲲鹏916/920的服务器。 2.3、鲲鹏性能分析工具 2.3.1、工具简介 鲲鹏性能分析工具由四个子工具组成,分别为:系统性能分析、Java性能分析、系统诊断和调优助手。 系统性能分析是针对基于鲲鹏的服务器的性能分析工具,能收集服务器的处理器硬件、操作系统、进程/线程、函数等各层次的性能数据,分析系统性能指标,定位到系统瓶颈点及热点函数,并给出优化建议。该工具可以辅助用户快速定位和处理软件性能问题。 Java性能分析是针对基于鲲鹏的服务器上运行的Java程序的性能分析和优化工具,能图形化显示Java程序的堆、线程、锁、垃圾回收等信息,收集热点函数、定位程序瓶颈点,帮助用户采取针对性优化。 系统诊断是针对基于鲲鹏的服务器的性能分析工具,提供内存泄漏诊断(包括内存未释放和异常释放)、内存越界诊断、内存消耗信息分析展示、OOM诊断能力、网络丢包等,帮助用户识别出源代码中内存使用的问题点,提升程序的可靠性,工具还支持压测系统,如:网络IO诊断,评估系统最大性能。 调优助手是针对基于鲲鹏的服务器的调优工具,能系统化组织性能指标,引导用户分析性能瓶颈,实现快速调优。 2.3.2、应用场景 客户软件在基于鲲鹏的服务器上运行遇到性能问题时,可用系统性能分析来快速分析和定位。 系统性能分析工具将采集系统如下数据: 系统软硬件配置和运行信息,例如:CPU类型、内存部署槽位、Kernel版本、内核参数、文件系统、系统运行日志参数等。 系统的CPU、内存、存储IO、磁盘IO等性能指标。 处理器PMU、SPE的性能数据。 处理器访问Cache/内存的次数、带宽、吞吐率等。 系统内核进行CPU资源调度、IO操作等数据。 进程/线程的CPU、内存、存储IO、上下文切换、系统调用等数据;进程命令行信息,包括:进程名、进程参数。 系统的热点函数及其调用栈;热点函数归属的程序/动态库(包含绝对路径);热点函数的汇编指令和热点指令;热点函数所对应的源代码(需要用户自行提供)。 2.3.3、部署方式 当前版本支持灵活部署,即将系统性能分析所有组件部署在一台服务器上、不同服务器上及混合部署,完成性能数据采集和分析。 2.4、鲲鹏开发套件插件工具(VSCode) 2.4.1、工具简介 鲲鹏开发套件插件工具是基于Visual Studio Code提供给开发者面向鲲鹏平台进行应用软件开发、迁移、编译调试、性能调优等一系列端到端工具,即插即用。一体化呈现代码迁移插件、鲲鹏开发框架插件、编译插件及性能分析插件的完整开发套件。 鲲鹏开发套件插件工具是一个工具集,由多个插件组成,支持IDE前端界面,支持一键式安装后端,代码编辑体验增强,自动检测安装鲲鹏编译器,编译调试,用例可视化,编码辅助,工程分析扫描。用户可以通过安装Kunpeng DevKit插件直接将四个插件都安装好,也可以单独选择个别插件安装使用。 2.4.2、代码迁移插件 鲲鹏代码迁移插件作为客户端调用服务端的功能,完成扫描迁移任务,可以对待迁移软件进行快速扫描分析,并提供专业的代码迁移指导,极大简化客户应用迁移到鲲鹏平台的过程。当客户有软件需要迁移到鲲鹏平台上时,可先用该工具分析可迁移性和迁移投入,以解决客户软件迁移评估中分析投入大、准确率低、整体效率低下的痛点。 代码迁移工具支持五个功能特性: 软件迁移评估:自动扫描并分析软件包(非源码包)、已安装的软件,提供可迁移性评估报告。 源码迁移:能够自动检查并分析出用户源码、C/C++/ASM/Fortran/解释型语言/汇编软件构建工程文件、C/C++/ASM/Fortran/解释型语言/汇编软件构建工程文件使用的链接库、x86汇编代码中需要修改的内容,并给出修改指导,以解决用户代码兼容性排查困难、迁移经验欠缺、反复依赖编译调错定位等痛点。 软件包重构:通过分析x86平台软件包(RPM格式、DEB格式)的软件构成关系及硬件依赖性,重构适用于鲲鹏平台的软件包。 专项软件迁移:基于鲲鹏解决方案的软件迁移模板,进行自动化迁移修改、编译、构建软件包,帮助用户快速迁移软件。 2.4.3、性能分析插件 鲲鹏开发套件是Visual Studio Code的一款扩展工具,通常将此类工具称作集成开发环境(IDE)插件。 鲲鹏性能分析插件是其中一个子工具,作为客户端调用服务端的功能。 鲲鹏性能分析工具由四个子工具组成,分别为:系统性能分析、Java性能分析、系统诊断和调优助手。 系统性能分析是针对基于鲲鹏的服务器的性能分析工具,能收集服务器的处理器硬件、操作系统、进程/线程、函数等各层次的性能数据,分析系统性能指标,定位到系统瓶颈点及热点函数,并给出优化建议。该工具可以辅助用户快速定位和处理软件性能问题。 Java性能分析是针对基于鲲鹏的服务器上运行的Java程序的性能分析和优化工具,能图形化显示Java程序的堆、线程、锁、垃圾回收等信息,收集热点函数、定位程序瓶颈点,帮助用户采取针对性优化。 系统诊断是针对基于鲲鹏的服务器的性能分析工具,提供内存泄漏诊断(包括内存未释放和异常释放)、内存越界诊断、内存消耗信息分析展示、OOM诊断能力,帮助用户识别出源代码中内存使用的问题点,提升程序的可靠性;压测网络,获得网络最大能力,为网络IO性能优化提供基础参考数据;诊断网络,定位网络疑难问题,解决因网络配置和异常而导致的网络IO性能问题;压测存储IO,获得存储设备最大能力,包括:吞吐量、IOPS、时延等,并以此评估存储能力,为存储IO性能优化提供基础参考数据。 2.5、二进制动态翻译工具 2.5.1、相关概念 ExaGear是一款二进制指令动态翻译软件,运行在ARM64服务器上,通过将x86的指令在运行时翻译为ARM64指令并执行,使得绝大部分Linux on x86应用无需重新编译就可运行在ARM64服务器上,实现低成本、快速迁移Linux on x86应用到ARM64服务器。 2.5.2、关键特性 支持多种部署方式:支持在物理机、虚拟机、容器等平台上部署; 部署简单:一键式快速安装,x86应用部署和运行与迁移前保持一致; 支持多版本Linux OS:目前支持CentOS 7、CentOS 8、Ubuntu18、Ubuntu20、OpenEuler 20.03,并且根据用户需求,未来可定制支持更多Linux OS发行; 低损耗: 大多数场景的应用,翻译损耗在20%以内。 3、结束语 对鲲鹏开发套件有兴趣的同学可参考如下链接进行进一步学习。 相关链接:https://support.huaweicloud.com/kunpengdevps/kunpengdevps.html
-
问题现象:分段扩容,重分步阶段UDF业务报错问题版本:HC DWS 8.1.1.202问题原因:udf相关作业中部分表未重分布,部分表已完成重分布,此种场景会跨nodegroup,会产生额外的stream线程,导致线程数过多,作业中涉及udf函数的线程增加最200左右的udf线程,从而业务报错。规避方法:手动将报错中的表、视图中的表手动提前进行重分布,在手动拉起重分步1.获取业务涉及的需要重分步的表比如获取视图中的表explain (stats on) 语句 可以获取到作业中所有的表cat xxxx |grep -i 'create table'|sort |uniqselect oid::regclass from pg_class where relname in (xxxx,xxxx);找到对应的表 按如下格式存成文本(没有列头)库名 schema 表名vmall_oar vmall_oar_cs behavior_content_liveactivityvmall_oar dwrdim dwr_dim_sku_dvmall_oar dwrdim dwr_dim_sys_ssid_cfgvmall_oar vmall_oc_0 ord_activity2.手动执行重分布gs_redis -p 8000 -d postgres -j 7 -r -m read-only -P /home/Ruby/aaa.txt3.待这些表重分布完成后,手动调起重分布nohup gs_expand -t redistribute --fast-redis --parallel-jobs=7 --redis-mode=insert &
-
上一篇:【Go实现】实践GoF的23种设计模式:SOLID原则简单的分布式应用系统(示例代码工程):https://github.com/ruanrunxue/Practice-Design-Pattern–Go-Implementation简述GoF 对单例模式(Singleton)的定义如下:Ensure a class only has one instance, and provide a global point of access to it.也即,保证一个类只有一个实例,并且为它提供一个全局访问点。在程序设计中,有些对象通常只需要一个共享的实例,比如线程池、全局缓存、对象池等。实现共享实例最简单直接的方式就是全局变量。但是,使用全局变量会带来一些问题,比如:客户端程序可以创建同类实例,从而无法保证在整系统上只有一个共享实例。难以控制对象的访问,比如想增加一个“访问次数统计”的功能就很难,可扩展性较低。把实现细节暴露给客户端程序,加深了耦合,容易产生霰弹式修改。对这种全局唯一的场景,更好的是使用单例模式去实现。单例模式能够限制客户端程序创建同类实例,并且可以在全局访问点上扩展或修改功能,而不影响客户端程序。但是,并非所有的全局唯一都适用单例模式。比如下面这种场景:考虑需要统计一个API调用的情况,有两个指标,成功调用次数和失败调用次数。这两个指标都是全局唯一的,所以有人可能会将其建模成两个单例SuccessApiMetric和FailApiMetric。按照这个思路,随着指标数量的增多,你会发现代码里类的定义会越来越多,也越来越臃肿。这也是单例模式最常见的误用场景,更好的方法是将两个指标设计成一个对象ApiMetric下的两个实例ApiMetic success和ApiMetic fail。那么,如何判断一个对象是否应该被建模成单例?通常,被建模成单例的对象都有“中心点”的含义,比如线程池就是管理所有线程的中心。所以,在判断一个对象是否适合单例模式时,先思考下,是一个中心点吗?UML结构代码实现根据单例模式的定义,实现的关键点有两个:限制调用者直接实例化该对象;为该对象的单例提供一个全局唯一的访问方法。对于 C++ / Java 而言,只需把对象的构造函数设计成私有的,并提供一个 static 方法去访问该对象的唯一实例即可。但 Go 语言并没有构造函数的概念,也没有 static 方法,所以需要另寻出路。我们可以利用 Go 语言 package 的访问规则来实现,将单例对象设计成首字母小写,这样就能限定它的访问范围只在当前package下,模拟了 C++ / Java 的私有构造函数;然后,在当前 package 下实现一个首字母大写的访问函数,也就相当于 static 方法的作用了。示例在简单的分布式应用系统(示例代码工程)中,我们定义了一个网络模块 network,模拟实现了网络报文转发功能。network 的设计也很简单,通过一个哈希表维持了 Endpoint 到 Socket 的映射,报文转发时,通过 Endpoint 寻址到 Socket,再调用 Socket 的 Receive 方法完成转发。因为整系统只需一个 network 对象,而且它在领域模型中具有中心点的语义,所以我们很自然地使用单例模式来实现它。单例模式大致可以分成两类,“饿汉模式”和“懒汉模式”。前者是在系统初始化期间就完成了单例对象的实例化;后者则是在调用时才进行延迟实例化,从而一定程度上节省了内存。“饿汉模式”实现// demo/network/network.go package network // 1、设计为小写字母开头,表示只在network包内可见,限制客户端程序的实例化 type network struct { sockets sync.Mapvar instancevar instance } // 2、定义一个包内可见的实例对象,也即单例 var instance = &network{sockets: sync.Map{}} // 3、定义一个全局可见的唯一访问方法 func Instance() *network { return instance } func (n *network) Listen(endpoint Endpoint, socket Socket) error { if _, ok := n.sockets.Load(endpoint); ok { return ErrEndpointAlreadyListened } n.sockets.Store(endpoint, socket) return nil } func (n *network) Send(packet *Packet) error { record, rOk := n.sockets.Load(packet.Dest()) socket, sOk := record.(Socket) if !rOk || !sOk { return ErrConnectionRefuse } go socket.Receive(packet) return nil }那么,客户端就可以通过 network.Instance() 引用该单例了:// demo/sidecar/flowctrl_sidecar.go package sidecar type FlowCtrlSidecar struct {...} // 通过 network.Instance() 直接引用单例 func (f *FlowCtrlSidecar) Listen(endpoint network.Endpoint) error { return network.Instance().Listen(endpoint, f) } ...“懒汉模式”实现众所周知,“懒汉模式”会带来线程安全问题,可以通过普通加锁,或者更高效的双重检验加锁来优化。不管是哪种方法,都是为了保证单例只会被初始化一次。type network struct {...} // 单例 var instance *network // 定义互斥锁 var mutex = sync.Mutex{} // 普通加锁,缺点是每次调用 Instance() 都需要加锁 func Instance() *network { mutex.Lock() if instance == nil { instance = &network{sockets: sync.Map{}} } mutex.Unlock() return instance } // 双重检验后加锁,实例化后无需加锁 func Instance() *network { if instance == nil { mutex.Lock() if instance == nil { instance = &network{sockets: sync.Map{}} } mutex.Unlock() } return instance }对于“懒汉模式”,Go 语言还有一个更优雅的实现方式,那就是利用 sync.Once。它有一个 Do 方法,方法声明为 func (o *Once) Do(f func()),其中入参是 func() 的方法类型,Go 会保证该方法仅会被调用一次。利用这个特性,我们就能够实现单例只被初始化一次了。type network struct {...} // 单例 var instance *network // 定义 once 对象 var once = sync.Once{} // 通过once对象确保instance只被初始化一次 func Instance() *network { once.Do(func() { // 只会被调用一次 instance = &network{sockets: sync.Map{}} }) return instance }扩展提供多个实例虽然单例模式从定义上表示每个对象只能有一个实例,但是我们不应该被该定义限制住,还得从模式本身的动机来去理解它。单例模式的一大动机是限制客户端程序对对象进行实例化,至于实例有多少个其实并不重要,根据具体场景来进行建模、设计即可。比如在前面的 network 模块中,现在新增一个这样的需求,将网络拆分为互联网和局域网。那么,我们可以这么设计:type network struct {...} // 定义互联网单例 var inetInstance = &network{sockets: sync.Map{}} // 定义局域网单例 var lanInstance = &network{sockets: sync.Map{}} // 定义互联网全局可见的唯一访问方法 func Internet() *network { return inetInstance } // 定义局域网全局可见的唯一访问方法 func Lan() *network { return lanInstance }虽然上述例子中,network 结构有两个实例,但是本质上还是单例模式,因为它做到了限制客户端实例化,以及为每个单例提供了全局唯一的访问方法。提供多种实现单例模式也可以实现多态,如果你预测该单例未来可能会扩展,那么就可以将它设计成抽象的接口,让客户端依赖抽象,这样,未来扩展时就无需改动客户端程序了。比如,我们可以 network 设计为一个抽象接口:// network 抽象接口 type network interface { Listen(endpoint Endpoint, socket Socket) error Send(packet *Packet) error } // network 的实现1 type networkImpl1 struct { sockets sync.Map } func (n *networkImpl1) Listen(endpoint Endpoint, socket Socket) error {...} func (n *networkImpl1) Send(packet *Packet) error {...} // networkImpl1 实现的单例 var instance = &networkImpl1{sockets: sync.Map{}} // 定义全局可见的唯一访问方法,注意返回值时network抽象接口! func Instance() network { return instance } // 客户端使用示例 func client() { packet := network.NewPacket(srcEndpoint, destEndpoint, payload) network.Instance().Send(packet) }如果未来需要新增一种 networkImpl2 实现,那么我们只需修改 instance 的初始化逻辑即可,客户端程序无需改动:// 新增network 的实现2 type networkImpl2 struct {...} func (n *networkImpl2) Listen(endpoint Endpoint, socket Socket) error {...} func (n *networkImpl2) Send(packet *Packet) error {...} // 将单例 instance 修改为 networkImpl2 实现 var instance = &networkImpl2{...} // 单例全局访问方法无需改动 func Instance() network { return instance } // 客户端使用也无需改动 func client() { packet := network.NewPacket(srcEndpoint, destEndpoint, payload) network.Instance().Send(packet) }有时候,我们还可能需要通过读取配置来决定使用哪种单例实现,那么,我们可以通过 map 来维护所有的实现,然后根据具体配置来选取对应的实现:// network 抽象接口 type network interface { Listen(endpoint Endpoint, socket Socket) error Send(packet *Packet) error } // network 具体实现 type networkImpl1 struct {...} type networkImpl2 struct {...} type networkImpl3 struct {...} type networkImpl4 struct {...} // 单例 map var instances = make(map[string]network) // 初始化所有的单例 func init() { instances["impl1"] = &networkImpl1{...} instances["impl2"] = &networkImpl2{...} instances["impl3"] = &networkImpl3{...} instances["impl4"] = &networkImpl4{...} } // 全局单例访问方法,通过读取配置决定使用哪种实现 func Instance() network { impl := readConf() instance, ok := instances[impl] if !ok { panic("instance not found") } return instance }典型应用场景日志。每个服务通常都会需要一个全局的日志对象来记录本服务产生的日志。全局配置。对于一些全局的配置,可以通过定义一个单例来供客户端使用。唯一序列号生成。唯一序列号生成必然要求整系统只能有一个生成实例,非常合适使用单例模式。线程池、对象池、连接池等。xxx池的本质就是共享,也是单例模式的常见场景。全局缓存…优缺点优点在合适的场景,使用单例模式有如下的优点:整系统只有一个或几个实例,有效节省了内存和对象创建的开销。通过全局访问点,可以方便地扩展功能,比如新增加访问次数的统计。对客户端隐藏实现细节,可避免霰弹式修改。缺点虽然单例模式相比全局变量有诸多的优点,但它本质上还是一个“全局变量”,还是避免不了全局变量的一些缺点:函数调用的隐式耦合。通常我们都期望从函数的声明中就能知道该函数做了什么、依赖了什么、返回了什么。使用使用单例模式就意味着,无需通过函数传参,就能够在函数中使用该实例。也即将依赖/耦合隐式化了,不利于更好地理解代码。对测试不友好。通常对一个方法/函数进行测试,我们并不需要知道它的具体实现。但如果方法/函数中有使用单例对象,我们就不得不考虑单例状态的变化了,也即需要考虑方法/函数的具体实现了。并发问题。共享就意味着可能存在并发问题,我们不仅需要在初始化阶段考虑并发问题,在初始化后更是要时刻注意。因此,在高并发的场景,单例模式也可能存在锁冲突问题。单例模式虽然简单易用,但也是最容易被滥用的设计模式。它并不是“银弹”,在实际使用时,还需根据具体的业务场景谨慎使用。与其他模式的关联工厂方法模式、抽象工厂模式很多时候都会以单例模式来实现,因为工厂类通常是无状态的,而且全局只需一个实例即可,能够有效避免对象的频繁创建和销毁。
-
需要使用C++的thread库,请问可以在CMakeLists中加-pthread吗
-
【功能模块】python写多进程处理时,利用 multiprocessing 中的 Manager 定义全局变量会导致线上报错:程序运行失败。【操作步骤&问题现象】1、线上提交如下代码:from multiprocessing import ManagerOUTPUT_PATH = "/output/solution.txt"if __name__ == '__main__': a = Manager().dict() with open(OUTPUT_PATH, "w") as f: pass会显示程序运行失败。2、但是将定义语句去掉,即:from multiprocessing import ManagerOUTPUT_PATH = "/output/solution.txt"if __name__ == '__main__': # a = Manager().dict() with open(OUTPUT_PATH, "w") as f: pass则不会显示程序运行失败,而是显示输出格式错误。【问题描述】上述操作都是在python自带的包中进行的。这说明在利用Manager定义多进程全局变量的时候会导致线上环境报错,而在线下是不会出现报错问题的。请问线上环境是出现这种报错是什么原因呢?
-
可能原因:安装过SysChecker未卸载干净,端口被占用1.检查SysChecker安装是否OMS主节点2.查看相关日志/opt/SysChecker/Output/SysCheckerRestLog.txt,发现报错信息Address already used3.检查端口24443是否被占用,4.netstat -ano|grep 24443发现24443端口被占用,SysChecker未卸载干净,有进程残留5.卸载Syschecker后,重新安装Syschecker对接环境 这个基本上是Syschecker出问题 由于现场不能使用root直接登录,所以在填入对接信息的时候不需要写入root密码,但是需要手动在主OMS节点安装Syschecker 重新安装后还是相同的问题,需要查看相关日志/opt/SysChecker/Output/SysCheckerRestLog.txt 发现报错信息 Address already used 判断可能是端口被占用了 netstat -anp| grep 24443 确认端口被占用。 杀掉占用端口的进程后,重新启动或重启安装Syschecker后问题解决。
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签