PostgreSQL ash —— pgsentinel插件

一、 插件作用

       众所周知,pg是没有像oracle那样的ash视图的,因此要回溯历史问题不太方便。pgsentinel插件会将pg_stat_activity与pg_stat_statements视图内容定期快照,并存入pg_active_session_history和pg_stat_statements_history视图中。

1. pg_active_session_history视图字段

ColumnType备注
ash_timetimestamp with time zone采样时间
datidoid
datnametext
pidinteger
leader_pidinteger若有并行,其leader进程的pid
usesysidoiduser id
usenametext
application_nametext
client_addrtext
client_hostnametext
client_portinteger
backend_starttimestamp with time zone
xact_starttimestamp with time zone
query_starttimestamp with time zone
state_changetimestamp with time zone
wait_event_typetext
wait_eventtext
statetext
backend_xidxid
backend_xminxid
top_level_querytext执行函数、存储过程时的外层SQL(开pg_stat_statements.track = all才会有区别)
querytext
cmdtypetext

queryidbigint
backend_typetext
blockersintegerblockers数量
blockerpidinteger
blocker_statetext

2. pg_stat_statements_history视图字段

与对应版本的pg_stat_statements视图字段含义相同

ColumnType备注
ash_timetimestamp with time zone
useridoid
dbidoid
queryidbigint
callsbigint
total_exec_timedouble precision
rowsbigint
shared_blks_hitbigint
shared_blks_readbigint
shared_blks_dirtiedbigint
shared_blks_writtenbigint
local_blks_hitbigint
local_blks_readbigint
local_blks_dirtiedbigint
local_blks_writtenbigint
temp_blks_readbigint
temp_blks_writtenbigint
blk_read_timedouble precision
blk_write_timedouble precision
plansbigint
total_plan_timedouble precision
wal_recordsbigint
wal_fpibigint
wal_bytesnumeric

二、 插件安装配置

1. 下载

GitHub - pgsentinel/pgsentinel: postgresql extension providing Active session history

2. 安装

# poatgres用户执行
unzip pgsentinel-master.zip 
cd pgsentinel-master/src
make# root用户执行(要配环境变量,参考下面)
make install

具体安装过程

-bash-4.2$ unzip pgsentinel-master.zip 
-bash-4.2$ cd pgsentinel-master/src
-bash-4.2$ make
gcc -std=gnu99 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -g -O2 -fPIC -I. -I./ -I/data/postgres/base/14.0/include/server -I/data/postgres/base/14.0/include/internal  -D_GNU_SOURCE   -c -o pgsentinel.o pgsentinel.c
gcc -std=gnu99 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -g -O2 -fPIC -I. -I./ -I/data/postgres/base/14.0/include/server -I/data/postgres/base/14.0/include/internal  -D_GNU_SOURCE   -c -o get_parsedinfo.o get_parsedinfo.c
gcc -std=gnu99 -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -g -O2 -fPIC -shared -o pgsentinel.so pgsentinel.o get_parsedinfo.o -L/data/postgres/base/14.0/lib    -Wl,--as-needed -Wl,-rpath,'/data/postgres/base/14.0/lib',--enable-new-dtags -lm  

[root@linux01 ~]# vi .bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/bin

export PATH
 
export PGHOME=/data/postgres/base/14.0
export PGDATA=/data/postgres/pg5432/data
export PATH=$PGHOME/bin:$PATH:$HOME/bin
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PGHOME/lib
export LANG=en_US.UTF-8                                                  
~                                 
[root@linux01 ~]# source .bash_profile
[root@linux01 ~]# 
[root@linux01 ~]# cd .../pgsentinel-master/src
[root@linux01 src]# make install 
/usr/bin/mkdir -p '/data/postgres/base/14.0/lib'
/usr/bin/mkdir -p '/data/postgres/base/14.0/share/extension'
/usr/bin/mkdir -p '/data/postgres/base/14.0/share/extension'
/usr/bin/install -c -m 755  pgsentinel.so '/data/postgres/base/14.0/lib/pgsentinel.so'
/usr/bin/install -c -m 644 .//pgsentinel.control '/data/postgres/base/14.0/share/extension/'
/usr/bin/install -c -m 644 .//pgsentinel--1.0.sql  '/data/postgres/base/14.0/share/extension/'

创建插件

CREATE EXTENSION pgsentinel;

3. 插件配置

  • 必须配置
vi postgresql.conf
shared_preload_libraries = 'pg_stat_statements,auto_explain,pgsentinel'

若未配置,查询会报错

postgres=# select * from pg_active_session_history ; 
ERROR:  pg_active_session_history must be loaded via shared_preload_libraries

重启db生效

pg_ctl stop -m fast
pg_ctl start -D $PGDATApostgres=# select * from pg_active_session_history ;
(0 rows)

  • 可选配置

可以直接在postgresql.conf中修改,也可以alter system设置

 alter system set pgsentinel_pgssh.enable=on;
参数名参数含义默认值建议值备注
pgsentinel_ash.sampling_period采样时间(秒)110,视业务负载及需求而定reload生效
pgsentinel_ash.max_entries

pg_active_session_history

最大记录条数(占用ring buffer大小,单位为字节)

1000视业务负载及需求而定。注意非常耗内存,设置1000万约占内存28G重启生效。设置过大可能内存不足,DB启动失败
pgsentinel.db_name数据存在哪个db中postgrespgawr重启生效
pgsentinel_ash.track_idle_trans是否记录 idle in transaction 状态会话offonreload生效
pgsentinel_pgssh.max_entries

pg_stat_statements_history

最大记录条数(占用ring buffer大小,单位为字节)

10000视业务负载及需求而定重启生效。设置过大可能内存不足,DB启动失败
pgsentinel_pgssh.enable是否启用 pg_stat_statements_historyoffon重启生效

注意:测试设置 pgsentinel_ash.max_entries=10000000,启动DB报错

-bash-4.2$ pg_ctl start -D $PGDATA
waiting for server to start....2023-10-07 19:43:09.865 CST [2210] FATAL:  could not map anonymous shared memory: Cannot allocate memory
2023-10-07 19:43:09.865 CST [2210] HINT:  This error usually means that PostgreSQL's request for a shared memory segment exceeded available memory, swap space, or huge pages. To reduce the request size (currently 29601538048 bytes), reduce PostgreSQL's shared memory usage, perhaps by reducing shared_buffers or max_connections.
2023-10-07 19:43:09.865 CST [2210] LOG:  database system is shut down
 stopped waiting
pg_ctl: could not start server
Examine the log output

为什么会占用这么大,待研究(估算每条记录占用280B,1000万行共占28G?)

这部分对应源码

从中也可以看到各参数含义、默认值、最小最大值,是否需重启生效等

static void
pgsentinel_load_params(void)
{DefineCustomIntVariable("pgsentinel_ash.sampling_period","Duration between each pull (in seconds).",NULL,&ash_sampling_period,1,1,INT_MAX,PGC_SIGHUP,0,NULL,NULL,NULL);DefineCustomBoolVariable("pgsentinel_ash.track_idle_trans","Track session in idle transaction state.",NULL,&ash_track_idle_trans,false,PGC_SIGHUP,0,NULL,NULL,NULL);if (!process_shared_preload_libraries_in_progress)return;/* can't define PGC_POSTMASTER variable after startup */DefineCustomIntVariable("pgsentinel_ash.max_entries","Maximum number of ash entries.",NULL,&ash_max_entries,1000,1000,INT_MAX,PGC_POSTMASTER,0,NULL,NULL,NULL);EmitWarningsOnPlaceholders("pgsentinel_ash");DefineCustomIntVariable("pgsentinel_pgssh.max_entries","Maximum number of pgssh entries.",NULL,&pgssh_max_entries,10000,10000,INT_MAX,PGC_POSTMASTER,0,NULL,NULL,NULL);DefineCustomBoolVariable("pgsentinel_pgssh.enable","Enable pg_stat_statements_history.",NULL,&pgssh_enable,false,PGC_POSTMASTER,0,NULL,NULL,NULL);EmitWarningsOnPlaceholders("pgsentinel_pgssh");DefineCustomStringVariable("pgsentinel.db_name",gettext_noop("Database on which the worker connect."),NULL,&pgsentinelDbName,"postgres",PGC_POSTMASTER,GUC_SUPERUSER_ONLY,NULL, NULL, NULL);
}

  • 其他相关参数

查询语句保留长度

# 为每个活动会话的pg_stat_activity.query字段所保留的内存量(字节,默认1024)
track_activity_query_size = 2048

跟踪层级

       pgsentinel依赖于pg_stat_statements插件的数据,如果想要更详细,可以调整相应参数(但必须注意对系统的负载)

# 记录函数和存储过程中的子语句
pg_stat_statements.track = all

四、 实现原理

       插件最核心的就是pg_active_session_history,pg_stat_statements_history两个视图,所以源码中最重要的,也就是这两个视图的创建。

1. 视图创建

源码中的 pgsentinel--1.0.sql,可以看到这两个视图内容来自两个函数,并进行授权

CREATE VIEW pg_active_session_history ASSELECT * FROM pg_active_session_history();GRANT SELECT ON pg_active_session_history TO PUBLIC;CREATE VIEW pg_stat_statements_history ASSELECT * FROM pg_stat_statements_history();GRANT SELECT ON pg_stat_statements_history TO PUBLIC;

而这两个函数实际是用c语言编写的

2. 函数创建


CREATE FUNCTION pg_active_session_history(OUT ash_time timestamptz,OUT datid Oid,OUT datname text,OUT pid integer,OUT leader_pid integer,OUT usesysid Oid,OUT usename text,OUT application_name text,OUT client_addr text,OUT client_hostname text,OUT client_port integer,OUT backend_start timestamptz,OUT xact_start timestamptz,OUT query_start timestamptz,OUT state_change timestamptz,OUT wait_event_type text,OUT wait_event text,OUT state text,OUT backend_xid xid,OUT backend_xmin xid,OUT top_level_query text,OUT query text,OUT cmdtype text,OUT queryid bigint,OUT backend_type text,OUT blockers integer,OUT blockerpid integer,OUT blocker_state text
)
RETURNS SETOF record
AS 'MODULE_PATHNAME', 'pg_active_session_history'
LANGUAGE C STRICT VOLATILE PARALLEL SAFE;-- Register a view on the function for ease of use.
CREATE VIEW pg_active_session_history ASSELECT * FROM pg_active_session_history();GRANT SELECT ON pg_active_session_history TO PUBLIC;CREATE FUNCTION pg_stat_statements_history(OUT ash_time timestamptz,OUT userid Oid,OUT dbid Oid,OUT queryid bigint,OUT calls bigint,OUT total_exec_time double precision,OUT rows bigint,OUT shared_blks_hit bigint,OUT shared_blks_read bigint,OUT shared_blks_dirtied bigint,OUT shared_blks_written bigint,OUT local_blks_hit bigint,OUT local_blks_read bigint,OUT local_blks_dirtied bigint,OUT local_blks_written bigint,OUT temp_blks_read bigint,OUT temp_blks_written bigint,OUT blk_read_time double precision,OUT blk_write_time double precision,OUT plans bigint,OUT total_plan_time double precision,OUT wal_records bigint,OUT wal_fpi bigint,OUT wal_bytes numeric
)
RETURNS SETOF record
AS 'MODULE_PATHNAME', 'pg_stat_statements_history'
LANGUAGE C STRICT VOLATILE PARALLEL SAFE;

既然如此,我们看看源码中究竟是怎么实现的这些函数


五、 源码学习

1. pg_active_session_history函数内容

它有两个分支,另外根据不同pg版本有不同语句(这里只挑了一个版本):

  • 启用pgsa_query_no_track_idle,即只记录active会话
select act.datid, act.datname, act.pid, act.usesysid, act.usename, \act.application_name, text(act.client_addr), act.client_hostname, \act.client_port, act.backend_start, act.xact_start, act.query_start,  \act.state_change, case when act.wait_event_type is null then 'CPU' \else act.wait_event_type end as wait_event_type,case when act.wait_event is null \then 'CPU' else act.wait_event end as wait_event, act.state, act.backend_xid, \act.backend_xmin, act.query, act.backend_type,(pg_blocking_pids(act.pid))[1], \cardinality(pg_blocking_pids(act.pid)),blk.state,gpi.*, act.leader_pid \from pg_stat_activity act left join pg_stat_activity blk  \on (pg_blocking_pids(act.pid))[1] = blk.pid,get_parsedinfo(act.pid) gpi \where act.state ='active' and act.pid != pg_backend_pid()";
  • 启用 pgsa_query_track_idle,即记录active和idle in transaction会话
select act.datid, act.datname, act.pid, act.usesysid, act.usename, \act.application_name, text(act.client_addr), act.client_hostname, \act.client_port, act.backend_start, act.xact_start, act.query_start,  \act.state_change, case when act.wait_event_type is null then 'CPU' \else act.wait_event_type end as wait_event_type,case when act.wait_event is null \then 'CPU' else act.wait_event end as wait_event, act.state, act.backend_xid, \act.backend_xmin, act.query, act.backend_type,(pg_blocking_pids(act.pid))[1], \cardinality(pg_blocking_pids(act.pid)),blk.state,gpi.*, act.leader_pid \from pg_stat_activity act left join pg_stat_activity blk  \on (pg_blocking_pids(act.pid))[1] = blk.pid,get_parsedinfo(act.pid) gpi \where act.state in ('active', 'idle in transaction') and act.pid != pg_backend_pid()";

2. pg_stat_statements_query函数内容

也有版本区分,这里只取其中一版

 select userid, dbid, queryid, calls, total_exec_time, rows, shared_blks_hit, \shared_blks_read, shared_blks_dirtied, shared_blks_written, local_blks_hit, \local_blks_read, local_blks_dirtied, local_blks_written, temp_blks_read, \temp_blks_written, blk_read_time, blk_write_time, \plans, total_plan_time, wal_records, wal_fpi, wal_bytes \from pg_stat_statements \where queryid in  (select queryid from pg_active_session_history  \where ash_time in (select ash_time from pg_active_session_history  \order by ash_time desc limit 1))";

3. 记录内容

每一行记录叫做一个entry

  • pg_active_session_history对应叫ashEntry
  • pg_stat_statements_query对应叫pgsshEntry
/* ash entry */
typedef struct ashEntry
{int pid;
#if PG_VERSION_NUM >= 130000int leader_pid;
#endifint client_port;uint64 queryid;TimestampTz ash_time;Oid datid;Oid usesysid;char *usename;char *datname;char *application_name;char *wait_event_type;char *wait_event;char *state;char *blocker_state;char *client_hostname;int blockers;int blockerpid;char *top_level_query;char *query;char *cmdtype;char *backend_type;char *client_addr;TransactionId backend_xmin;TransactionId backend_xid;TimestampTz backend_start;TimestampTz xact_start;TimestampTz query_start;TimestampTz state_change;
} ashEntry;/* pg_stat_statement_history entry */
typedef struct pgsshEntry
{TimestampTz ash_time;Oid userid;Oid dbid;uint64 queryid;int64 calls;double total_time;int64 rows;int64 shared_blks_hit;int64 shared_blks_read;int64 shared_blks_dirtied;int64 shared_blks_written;int64 local_blks_hit;int64 local_blks_read;int64 local_blks_dirtied;int64 local_blks_written;int64 temp_blks_read;int64 temp_blks_written;double blk_read_time;double blk_write_time;
#if PG_VERSION_NUM >= 130000int64 plans;double total_plan_time;int64 wal_records;int64 wal_fpi;uint64 wal_bytes;
#endif
} pgsshEntry; 

每个字段有一个buffer变量,记录共享内存用量,例如

static char *AshEntryUsenameBuffer = NULL;
static char *AshEntryDatnameBuffer = NULL;
static char *AshEntryAppnameBuffer = NULL;

       ash_entry_memsize和pgssh_entry_memsize估算entry所需内存,如果占用量过大,DB启动可能会失败。基本原理是:

  • 每行占用内存 = 各字段占用内存之和
  • 总占用内存 = 每行占用内存 * 最大行数 ash_max_entries
/* Estimate amount of shared memory needed for ash entry */
static Size
ash_entry_memsize(void)
{Size            size;/* AshEntryArray */size = mul_size(sizeof(ashEntry), ash_max_entries);/* AshEntryUsenameBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));/* AshEntryDatnameBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));/* AshEntryAppnameBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));/* AshEntryClientaddrBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));/* AshEntryWaitEventTypeBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));/* AshEntryWaitEventBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));/* AshEntryStateBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));/* AshEntryClientHostnameBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));/* AshEntryQueryBuffer */size = add_size(size, mul_size(pgstat_track_activity_query_size,ash_max_entries));/* AshEntryCmdTypeBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));/* AshEntryTopLevelQueryBuffer */size = add_size(size, mul_size(pgstat_track_activity_query_size,ash_max_entries));/* AshEntryBackendTypeBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));/* AshEntryBlockerStateBuffer */size = add_size(size, mul_size(NAMEDATALEN, ash_max_entries));return size;
}

参考:

GitHub - pgsentinel/pgsentinel: postgresql extension providing Active session history

一种PostgreSQL数据库监控和溯源分析的方法和系统与流程

PostgreSQL 12.2官方手册学习( 第19章 运行时统计数据) - 墨天轮

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

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

相关文章

基于LADRC自抗扰控制的VSG三相逆变器预同步并网控制策略(Simulink仿真实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

网络相关的基础知识整理

一、历史 1.1 早期阿帕网特点⭐⭐⭐ 没有纠错功能不能互联不同类型的计算机和不同类型的操作系统 1. 2 TCP/IP协议 点击【此处】跳转🔗 TCP:用来检测网络传输中差错的传输控制协议IP:专门负责对不同网络进行互联的互联网协议&#xff08…

数学建模预测模型MATLAB代码大合集及皮尔逊相关性分析(无需调试、开源)

已知2010-2020数据,预测2021-2060数据 一、Logistic预测人口 %%logistic预测2021-2060年结果 clear;clc; X[7869.34, 8022.99, 8119.81, 8192.44, 8281.09, 8315.11, 8381.47, 8423.50, 8446.19, 8469.09, 8477.26]; nlength(X)-1; for t1:nZ(t)(X(t1)-X(t))/X(t1…

【MVDiffusion】完美复刻场景,可多视图设计的生成式模型

文章目录 MVDiffusion1. 自回归 生成 全景图1.1 错误积累1.2 角度变换大 2. 模型结构2.1 多视图潜在扩散模型(mutil-view LDM)2.1.1 Text-conditioned generation model2.1.2 Image&text-conditioned generation model2.1.3 额外的卷积层 2.2 Correspondence-aware Attenti…

使用python-opencv检测图片中的人像

最简单的方法进行图片中的人像检测 使用python-opencv配合yolov3模型进行图片中的人像检测 1、安装python-opencv、numpy pip install opencv-python pip install numpy 2、下载yolo模型文件和配置文件: 下载地址: https://download.csdn.net/down…

参与现场问题解决总结(Kafka、Hbase)

一. 背景 Kafka和Hbase在现场应用广泛,现场问题也较多,本季度通过对现场问题就行跟踪和总结,同时结合一些调研,尝试提高难点问题的解决效率,从而提高客户和现场满意度。非难点问题(历史遇到过问题&#xf…

三模块七电平级联H桥整流器电压平衡控制策略Simulink仿真

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

[ubuntu]ubuntu设置虚拟内存

首先查看自己是否加过虚拟内存或者查看虚拟内存当前状态可以命令: free -mh 创建交换分区: sudo mkdir /swap cd /swap sudo dd if/dev/zero ofswapfile bs1024 count12582912 其中count是自己分配内存大小,上面为12GB,你可…

travel总结:

目录 1、前期准备: 2、项目期间: (1)注册功能的实现: 1、前端: 1、表单数据的校验:(js) 2、使用ajax完成表单提交 3、注册成功跳转页面 2、web: 1、获取表单数据、封装数据 2、调…

【Java8】线程问题排查分析

文章目录 thread dump简介JDK 工具jstackjattachjvisualvm Java程序CPU消耗较高,怎么快速看出是那个线程导致的呢?我们可以使用命令 jstack/jattach来快速定位问题 thread dump简介 thread dump 是 Java 进程的所有线程状态的快照。每个线程的状态都通过…

练[MRCTF2020]套娃

[MRCTF2020]套娃 文章目录 [MRCTF2020]套娃掌握知识解题思路代码分析1代码分析2 关键paylaod 掌握知识 ​ 参数的_符号的替换,正则和强等于联合绕过,jsfuck编码,bp请求方式的转变,本地ip的请求头,代码审计&#xff0c…

操作系统的五种IO模型

高级IO 阻塞IO 在内核将数据准备好之前,系统调用会一直等待,所有的套接字默认都是阻塞方式。 非阻塞IO 如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码。 非阻塞IO往往需要程序员循环的方式反复尝试…

TCP四次挥手

四次挥手发生在断开连接的时候,在程序中当调用了close()会使用TCP协议进行四次挥手。 客户端和服务器端都可以主动发起断开连接,谁先调用close()谁就是发起。 因为在TCP连接的时候,采用三次握手建立的的连接是双向的,在断开的时候…

电脑被删除的文件怎么恢复?2023年数据恢复方法分享

大多数人在使用电脑时都可能会遇到误删文件的情况。一不小心,重要的文件或数据就消失了,情急之下,大多会感到慌乱和无助。但其实,文件误删除并非不可挽回的灾难。本文将为大家介绍几种有效的文件恢复方法,以帮助大家在…

【力扣】42. 接雨水

这道题我卡了差不多1个小时,不是不会做,是不知道怎么能用栈来实现,后面看了一个博主的视频,豁然开朗,我主要的纠结点在于当指针指到7的时候,我计算出4到7的水块是2,但实际上是0,因为…

SpringBoot 可以同时处理多少请求

目录 一、前言 二、相关配置 1、配置信息 2、配置说明 3、案例说明 三、代码验证 1、测试代码 2、测试步骤 四、总结 一、前言 首先,在Spring Boot应用中,我们可以使用 Tomcat、Jetty、Undertow 等嵌入式 Web 服务器作为应用程序的运行容器。这…

Vue中如何进行拖拽与排序功能实现

在Vue中实现拖拽与排序功能 在Web应用程序中,实现拖拽和排序功能是非常常见的需求,特别是在管理界面、任务列表和图形用户界面等方面。Vue.js作为一个流行的JavaScript框架,提供了许多工具和库来简化拖拽和排序功能的实现。本文将介绍如何使…

抖捧自动直播市场火热,实体行业如何实现高效开播?

在AI数字人热度之后,最近抖捧场景自动直播开始火遍全网,因为操作简单成本极低,只需要一部手机放在店里,就可以高效开播,深受广大实体行业老板的认可,那么抖捧实景自动直播的方式,具体是怎么实现…

P1014 [NOIP1999 普及组] Cantor 表

#include <bits/stdc.h> using namespace std; int main() {int n,k1;cin>>n;while (n>k) {nn-k;k;}if(k%20) cout<<n<<"/"<<(k1-n);else cout<<k1-n<<"/"<<n;return 0; }