PromQL,让你轻松实现监控可视化!快来了解一下吧!

news2024/9/23 1:41:11

Prometheus 中的一些关键设计,比如注重标准和生态、监控目标动态发现机制、PromQL等。

PromQL 是 Prometheus 的查询语言,使用灵活方便,但很多人不知道如何更好利用它,发挥不出优势。

PromQL主要用于时序数据的查询和二次计算场景。

1 时序数据

可理解成一个以时间为轴的矩阵,如下案例有三个时间序列,在时间轴对应不同值:

^
│     . . . . . . . . . .   node_load1{host="host01",zone="bj"}
│     . . . . . . . . . .   node_load1{host="host02",zone="sh"}
│     . . . . . . . . . .   node_load1{host="host11",zone="sh"}
v
<------- 时间 ---------->

每一个点称为一个

1.1 样本(sample)

1.1.1 组成

  • 指标(metric):metric name和描述当前样本特征的labelsets
  • 时间戳(timestamp):ms的时间戳
  • 值(value):该时间样本的值

PromQL就是对这样一批样本数据做查询、计算。

2 应用场景

时序数据的查询和二次计算。

PromQL第一个核心价值

2.1 筛选

查询靠的查询选择器

查询选择器

每个监控图表的渲染或者每条告警规则的处理,都只是针对有限的几条数据,所以 PromQL第一个需求就是过滤

假设我有两个需求:

  • 查询上海所有机器1min的负载
  • 查询所有以host0为前缀的机器1min的负载
# = 做 zone 的匹配过滤
node_load1{zone="sh"}
# =~ 做 host 的正则过滤
node_load1{host=~"host0.*"}

大括号里写过滤条件,主要是针对标签过滤,操作符除了等于号和正则匹配之外,还有不等于 != 和正则非 !~

metric name可以写到大括号,比如我想【同时查看上海机器的 load1、load5、load15三指标】,可对 __name__,即 metric 名字做正则过滤:

{__name__=~"node_load.*", zone="sh"}

上面例子给出的3条PromQL都叫即时查询(Instant Query),返回内容叫即时向量( Instant Vector)。

即时查询返回的是当前最新值,如 10:00发起的查询,返回的就是 10:00 这时刻对应数据。但监控数据是周期性上报,并非每时每刻都有数据上报,10:00时可能恰无数据进来,此时 Prometheus 就会往前看,看看9:59、9:58、9:57等时间点有没有上报数据。

最多应该往前看多久?

由Prometheus启动参数 --query.lookback-delta 控制,默认 5 min。从监控角度,建议调短,改成 1 min --query.lookback-delta=1m

某人使用 Telegraf 做 HTTP 探测,配置了一个告警规则: response_code 连续 3 min != 200 才告警。实际上只有一个数据点的 response_code!=200,过了3min还是报警。

主要原因

  • Telegraf 的 HTTP 探测,会默认把 status code 放到标签,导致标签非稳态结构(这行为不好,最好是把这类标签直接丢弃或使用categraf、blackbox_exporter做采集器),平时 code=200,出问题时 code=500,在 Prometheus 生态里,标签变了就是新的时间序列
  • query.lookback-delta 有关,虽只有一个点异常,即code=500的这个时间序列只有一个点,但告警规则每次执行查询时,都是查到这个异常点,连续5min都如此。所以就满足规则里连续3min才告警的条件。这就是为何建议把 --query.lookback-delta 调短

除了即时查询,PromQL还有范围查询(Range Query),返回内容叫 Range Vector。

{__name__=~"node_load.*", zone="sh"}[1m]

范围查询就是多加个时间范围1min。即时查询每个指标返回一个点,范围查询会返回多个点。假设数据10秒钟采集一次,1分钟有6个点,都会返回。

Prometheus 官方文档 介绍各函数时,都会讲解函数参数,标明 Range Vector or Instant Vector。

PromQL的另一个核心价值

2.2 计算

有算术、比较、逻辑、聚合运算符等。

算术运算符

就常用的加减乘除、取模之类的符号。

# 计算内存可用率,就是内存可用量除以内存总量,又希望按照百分比呈现,所以最后乘以100
mem_available{app="clickhouse"} / mem_total{app="clickhouse"} * 100

# 计算北京区网口出向的速率,原始数据的单位是byte,网络流量单位一般用bit,所以乘以8
irate(net_sent_bytes_total{zone="beijing"}[1m]) * 8

比较运算符

比较运算符就是大于、小于、等于、不等于之类的,简单但意义重大, 告警规则的逻辑就是靠比较运算符支撑的

mem_available{app="clickhouse"} / mem_total{app="clickhouse"} * 100 < 20

irate(net_sent_bytes_total{zone="beijing"}[1m]) * 8 / 1024 / 1024 > 700

带比较运算符的 PromQL 就是告警规则的核心,如内存可用率的告警,在 Prometheus 这样配置:

groups:
- name: host
  rules:
  - alert: MemUtil
  	# 指定了查询用的 PromQL
    expr: mem_available{app="clickhouse"} / mem_total{app="clickhouse"} * 100 < 20
    # 偶尔一次低于 20% 不是啥大事,只有连续1min每次查询都低于20%才告警,这就是 `for: 1m` 意义
    for: 1m
    labels:
      severity: warn
    annotations:
      summary: Mem available less than 20%, host:{{ $labels.ident }}

告警引擎会根据用户的配置,周期执行查询:

  • 查不到就说明一切正常,没有机器的内存可用率低于20%
  • 查到了,说明触发了告警,查到几条就触发几条告警

逻辑运算符

and、or和unless,用于 instant-vector 间的运算。and 是求交集,or是求并集,unless是求差集。

and使用场景,关于磁盘使用率,有的分区大如16T,有的分区小如50G,只用磁盘使用率做告警就不合理,如 disk_used_percent{app="clickhouse"} > 70 表示磁盘使用率大于70%就告警。对于小盘,这策略合理,但对大盘,70%使用率表示还有很多空间,就不合理。这时我们希望给这个策略加个限制,只有小于200G的硬盘在使用率超过70%时才告警,就能用 and:

disk_used_percent{app="clickhouse"} > 70 and disk_total{app="clickhouse"}/1024/1024/1024 < 200

向量匹配

向量间的操作想要在右侧的向量中,为左侧向量的每个条目找到一个匹配的元素,匹配行为分为:one-to-one、many-to-one、one-to-many。刚才介绍的磁盘使用率的例子,就是典型的 one-to-one 类型,左右两侧的指标,除了指标名,其余标签都是一样的,非常容易找到对应关系。但是有时候,我们希望用 and 求交集,但是两侧向量标签不同,怎么办呢?

此时我们可以使用关键字 on 和 ignoring 来限制用于做匹配的标签集。

mysql_slave_status_slave_sql_running == 0
and ON (instance)
mysql_slave_status_master_server_id > 0

这个PromQL想表达的意思是如果这个MySQL实例是个slave(master_server_id>0),就检查其slave_sql_running的值,如果slave_sql_running==0,就表示slave sql线程没有在运行。

但mysql_slave_status_slave_sql_running和mysql_slave_status_master_server_id这两个metric的标签,可能并非完全一致。不过好在二者都有个instance标签,且相同的instance标签的数据从语义上来看就表示一个实例的多个指标数据,那我们就可以用关键字on来指定只使用instance标签做匹配,忽略其他标签。

与on相反的是关键字ignoring,顾名思义,ignoring是忽略掉某些标签,用剩下的标签来做匹配。我们拿 Prometheus 文档中的例子来说明。

## example series
method_code:http_errors:rate5m{method="get", code="500"}  24
method_code:http_errors:rate5m{method="get", code="404"}  30
method_code:http_errors:rate5m{method="put", code="501"}  3
method_code:http_errors:rate5m{method="post", code="500"} 6
method_code:http_errors:rate5m{method="post", code="404"} 21
method:http_requests:rate5m{method="get"}  600
method:http_requests:rate5m{method="del"}  34
method:http_requests:rate5m{method="post"} 120

## promql
method_code:http_errors:rate5m{code="500"}
/ ignoring(code)
method:http_requests:rate5m

## result
{method="get"}  0.04            //  24 / 600
{method="post"} 0.05            //   6 / 120

例子里都是 one-to-one 的对应关系,这个好理解。难理解的是 one-to-many 和 many-to-one,这种情况下,做指标运算时就要借助关键字 group_left 和 group_right 了。left、right 指向高基数那一侧的向量。还是用上面method_code:http_errors:rate5m和method:http_requests:rate5m 这两个指标来举例,你可以看一下使用 group_left 的PromQL和输出的结果。

## promql
method_code:http_errors:rate5m
/ ignoring(code) group_left
method:http_requests:rate5m

## result
{method="get", code="500"}  0.04            //  24 / 600
{method="get", code="404"}  0.05            //  30 / 600
{method="post", code="500"} 0.05            //   6 / 120
{method="post", code="404"} 0.175           //  21 / 120

比如针对 method="get" 的条目,右侧的向量中只有一个记录,但是左侧的向量中有两个记录,所以高基数的一侧是左侧,故而使用 group_left。

这里我再举一个例子,来说明 group_left、group_right 的一个常见用法。比如我们使用 kube-state-metrics 来采集 Kubernetes 各个对象的指标数据,其中针对 pod 有个指标是 kube_pod_labels,该指标会把 pod 的一些信息放到标签里,指标值是1,相当于一个元信息。

kube_pod_labels{
[...]
  label_name="frontdoor",
  label_version="1.0.1",
  label_team="blue"
  namespace="default",
  pod="frontdoor-xxxxxxxxx-xxxxxx",
} = 1

假设某个 Pod 是接入层的,统计了很多 HTTP 请求相关的指标,我们想统计 5xx 的请求数量,希望能按 Pod 的 version 画一个饼图。这里有个难点:接入层的请求类指标没有 version 标签,version 信息只出现在 kube_pod_labels 里,怎么让二者联动?

sum(
  rate(http_request_count{code=~"^(?:5..)$"}[5m])) by (pod)
*
on (pod) group_left(label_version) kube_pod_labels

我们把这个 PromQL掰开揉碎,乘号前面的部分,是一个统计每秒 5xx 数量的典型语法,按照 pod 维度做分组统计。

然后乘以 kube_pod_labels,这个值是1。任何值乘以1都是原来的值,所以对整体数值没有影响,而 kube_pod_labels 有多个标签,而且和 sum 语句的结果向量的标签不一致,所以通过 on(pod) 语法来指定只按照 pod 标签来建立对应关系。

最后,利用 group_left(label_version),把 label_version 附加到了结果向量里,高基数的部分显然是 sum 的部分,所以使用 group_left 而非 group_right。

聚合运算

针对单指标的多个 series,还有聚合需求。如查看100台机器的平均内存可用率或排序,取数值最小的10台。

这种需求使用 PromQL 内置聚合函数。

# 求取 clickhouse 的机器的平均内存可用率
avg(mem_available_percent{app="clickhouse"})

# 把 clickhouse 的机器的内存可用率排个序,取最小的两条记录
bottomk(2, mem_available_percent{app="clickhouse"})

分组统计

分别统计 clickhouse 和 canal 的机器内存可用率,使用 by 指定分组统计的维度(与 by 相反的without)。

avg(mem_available_percent{app=~"clickhouse|canal"}) by (app)

这些聚合运算可理解为 纵向拟合。100 台机器的内存可用率,在折线图上有100条线,如想把这100条线拟合成一条线,就相当于把每个时刻的100个点拟合成1个点。咋让100个点变成1个点?求平均值或最大值之类,所以有了这些聚合运算符。

横向拟合

<aggregation>_over_time 等函数。这些函数接收范围向量,因为范围向量是一个时段内有多个值, <aggregation> 就是对这多个值做运算。

# [2m]:获取这个指标最近 2 分钟的所有数据点。若15秒采集一个点,2min就是8个点
# max_over_time:对这8个点求最大值,相当于对各个时间序列做横向拟合
max_over_time(target_up[2m])

3 容易误解的函数

increase 函数

字面意求取一个增量,接收一个 range-vector,range-vector 显然会返回多个 value+timestamp 的组合。直观理解就是,直接把时间范围内的最后一个值减去第一个值,不就可以得到增量?No!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-14wDpCXz-1682918864743)(images/623851/5fe64d3408bba9b26b88f556865b2983.png)]

promql: net_bytes_recv{interface="eth0"}[1m] @ 1661570908
965304237246 @1661570850
965307953982 @1661570860
965311949925 @1661570870
965315732812 @1661570880
965319998347 @1661570890
965323899880 @1661570900

promql: increase(net_bytes_recv{interface="eth0"}[1m]) @1661570909
23595160.8

监控数据10s上报一次,所以虽然两次 PromQL 查询时间不同:

  • 一次 1661570908
  • 一次 1661570909,但查询的原始数据内容一样,就是 1661570850~1661570900 几个时间点对应数据。

直观理解,在这几个时间点对应的数据上求取 increase,无非就是最后一个值减去第一个值,即965323899880-965304237246=19662634。但实际23595160.8,差别大。

实际上,increase 这个 PromQL 发起请求的时间是1661570909,时间范围是[1m],相当于告诉Prometheus,我要查询1661570849(由1661570909-60得出)~1661570909之间的 increase 数值。但原始监控数据并没有 1661570849、1661570909 这两个时刻的数值,咋办?Prometheus只能基于现有数据做外推,即使用最后一个点数值 - 第一个点的数值的结果,除以时间差,再乘60。

( 965323899880.0 − 965304237246.0 ) d i v ( 1661570900.0 − 1661570850.0 ) t i m e s 60 = 23595160.8 (965323899880.0-965304237246.0)\\div(1661570900.0-1661570850.0)\\times60=23595160.8 (965323899880.0965304237246.0)div(1661570900.01661570850.0)times60=23595160.8
这样最终就得到1分钟的 increase 值,是小数。

rate 函数

increase是求取的时间段内增量,且有数据外推

rate函数则求取每s的变化率,也有数据外推,increase结果除以 range-vector 的时间段大小=rate值。

rate(net_bytes_recv{interface="eth0"}[1m])
== bool
increase(net_bytes_recv{interface="eth0"}[1m])/60.0

== 后跟个bool,表示希望返回一个 bool 值,如果是 True 就会返回 1,如果是 False 就返回 0。我们观察结果后发现,这个表达式永远都会返回 1,即等号前后的两个 PromQL 语义上是相同的。

rate 函数求取的变化率,相对平滑。因为是拿时间范围内的最后一个值和第一个值做数据外推,一些毛刺现象就会被平滑掉。如果想要得到更敏感的数据,我们可以使用 irate 函数。irate 是拿时间范围内的最后两个值来做计算,变化就会更剧烈,我们拿网卡入向流量这个指标来做个对比。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jjA7oHHy-1682918864746)(images/623851/68ac7b49091b60970b9a8f8a106500c3.png)]

蓝色的变化得更剧烈的线是用 irate 函数计算得到的,而紫色的相对平滑的线是用 rate 函数计算得到的,对比还是很强烈的。

4 总结

PromQL核心价值:

  • 筛选

    靠查询选择器,查询分为即时查询和范围查询

  • 计算

    有算术、比较、逻辑、聚合运算符,还有向量匹配逻辑

5 FAQ

Prometheus 中提供了一个函数叫 absent,用于做数据缺失告警,使用得也很广泛,但是坑也挺大。这里我留给你一个问题:如果我想对 100 台机器的 node_load1 做数据缺失告警,应该如何配置?这个需求用 absent 解决合适吗?你能否给出 absent 的最佳使用场景?

对于100台机器的node_load1数据缺失告警需求,使用absent函数不太适合,原因在于:

  1. Absent函数是用于监控某个指标是否消失(即不存在),而非该指标数据是否有缺失。如果只是某一个节点出现了一段时间的缺失,Absent将会误报为不存在。

  2. 在涉及到多个节点的情况下,每个节点可能由于各种原因导致其不能向Prometheus发送监控数据,从而触发错误的告警。所以要针对每个节点单独设置告警,即设置一个请求每个节点数据的查询语句,同时确保查询结果正常,并且对于每个节点的告警条件进行区分。

对于 Absent 函数最佳使用场景,它可以过滤掉一些无效的告警,比如对于一些稀有的事件或者异常数据点,当这些事件或者数据出现时我们就需要告警,但是如果出现的很少,我们就会被一堆“false”positive告警搞得心烦意乱。此时, Absent 函数就派上了用场,可以排除掉这些稀有数据并避免告警的的误判,提高告警的可靠性和准确性。

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

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

相关文章

【Docker】4、Docker 数据卷

目录 一、数据卷介绍二、数据卷相关命令三、创建一个数据卷&#xff0c;并查看数据卷在宿主机的目录位置四、数据卷挂载到容器五、数据卷挂载练习&#xff08;MySQL&#xff09;(1) 加载 MySQL 镜像(2) 根据镜像创建容器 一、数据卷介绍 &#x1f384; 数据卷&#xff08;volum…

Lychee图床 - 本地配置属于自己的相册管理系统并远程访问

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站&#xff0c;可以看做是云存储的一部分&#xff0c;既可…

HCIA-RS实验-路由配置-配置RIPv1 和RIPv2

书接上回。。。这篇主要以实验为主&#xff0c;实验的主要目标也是理解RIP路由协议的防环机制 &#xff0c;掌握RIPv1和v2的配置方法&#xff1b;大致拓扑图如下&#xff1a; 拓扑图&#xff1a; 配置对应的IP&#xff1a; R1.2.3 测试R1与R2间的连通性。 配置IP后测试连通性…

设计模式——设计模式简介和七大原则

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线设计模式牛客面试题 目录 一、通过经典面试题掌握重点 二、设计模式的目的和核心原则 三、设计模式七大原则 3.1 单一职责原则&#xff08;Single Respo…

Linux 安装 NFS 实现文件目录共享

一、背景介绍 项目中&#xff0c;之前是单节点部署服务&#xff0c;关于文件的上传和下载&#xff0c;只要配置好路径即可。 而当集群化部署后&#xff0c;就会有问题。比如&#xff1a;文件上传的时候&#xff0c;访问的是主机1&#xff0c;而想要现在文件的时候&#xff0c…

【MST】ABC235 E - MST + 1

一开始想的是分类讨论&#xff0c;看那条边加了之后成不成环&#xff0c;如果不成环且权值在前n-1&#xff0c;则一定在MST里&#xff0c;不在前n-1则不在MST里&#xff1b;如果成环了&#xff0c;如果权值不在前n-1&#xff0c;则不在MST里&#xff0c;如果权值在前n-1&#x…

spark 数据的加载和保存(Parquet、JSON、CSV、MySql)

spark数据的加载和保存 SparkSQL 默认读取和保存的文件格式为 parquet 1.加载数据 spark.read.load 是加载数据的通用方法 scala> spark.read. csv format jdbc json load option options orc parquet schema table text textFile 如果读取不同格式的数据&#xff0c;可以…

docker安装fastdfs

1 拉取镜像 docker pull morunchang/fastdfs如果网速下载慢&#xff0c;可以参考资料文件夹中给大家导出的镜像包上传到 Linux服务器上&#xff0c;通过docker load -i my_fdfs.tar 加载镜像。 使用 docker images查看是否成功 2 运行tracker docker run -d --name tracker -…

MySQL高阶——索引设计的推演

前言 MySQL在我们工作中都会用到&#xff0c;那么我们最常接触的就是增删改查&#xff0c;而对于增删改查来说&#xff0c;我们更多的是查询。但是面试中&#xff0c;面试官又不会问你什么查询是怎么写的&#xff0c;都是问一些索引啊&#xff0c;事务啊&#xff0c; 底层结构…

C. Multiplicity(DP + 分解因数)

Problem - C - Codeforces 给定一个整数数组a1&#xff0c;a2&#xff0c;...&#xff0c;an。 如果可以从a中删除一些元素得到b&#xff0c;则称数组b为a的子序列。 当且仅当对于每个i&#xff08;1≤i≤k&#xff09;&#xff0c;bi是i的倍数时&#xff0c;数组b1&#xff…

Spring Data JPA 快速上手

一、JPA的概述 JPA的全称是Java Persisitence API&#xff0c;即JAVA持久化API&#xff0c;是sum公司退出的一套基于ORM的规范&#xff0c;内部是由一些列的接口和抽象类构成。JPA通过JDK5.0注解描述对象-关系表的映射关系&#xff0c;并将运行期的实体对象持久化到数据库中。…

Mysql 分库分表 Mycat

0 课程视频 https://www.bilibili.com/video/BV1Kr4y1i7ru?p163&spm_id_frompageDriver&vd_sourceff8b7f852278821525f11666b36f180a 1 单库问题 1.1 热点数据多 -> 缓冲区不足 ->内存不足 1.2 数据多 -> 磁盘不足 1.3 请求数据量多 -> 带宽不足 1…

C/C++的命名空间和调用函数的详细讲解

目录 空函数 调用函数 调用 执行流程 命名空间 在创建函数时&#xff0c;必须编写其定义。所有函数定义包括以下组成部分&#xff1a; 名称&#xff1a;每个函数都必须有一个名称。通常&#xff0c;适用于变量名称的规则同样也适用于函数名称。形参列表&#xff1a;调用函…

算法记录lday4 LinkedList链表交换 删除倒数N个点 环形链表

今日任务 ● 24. 两两交换链表中的节点 ● 19.删除链表的倒数第N个节点 ● 面试题 02.07. 链表相交 ● 142.环形链表II 两两交换链表中的节点 题目描述 Given a linked list, swap every two adjacent nodes and return its head. You must solve the problem without modi…

一天吃透Redis面试八股文

Redis连环40问&#xff0c;绝对够全&#xff01; Redis是什么&#xff1f; Redis&#xff08;Remote Dictionary Server&#xff09;是一个使用 C 语言编写的&#xff0c;高性能非关系型的键值对数据库。与传统数据库不同的是&#xff0c;Redis 的数据是存在内存中的&#xf…

Golang每日一练(leetDay0052)

153. 寻找旋转排序数组中的最小值 Find Minimum In Rotated Sorted Array 已知一个长度为 n 的数组&#xff0c;预先按照升序排列&#xff0c;经由 1 到 n 次 旋转 后&#xff0c;得到输入数组。例如&#xff0c;原数组 nums [0,1,2,4,5,6,7] 在变化后可能得到&#xff1a; …

纸模图纸的发展历程+纸模图纸免费下载-手工985

纸模图纸是一种用于制作模型的图纸&#xff0c;通常用于制作卡通纸模图纸、飞机纸模图纸、船只纸模图纸、汽车纸模图纸、建筑纸模图纸等模型。 纸模图纸的历史 纸模图纸的历史可以追溯到19世纪末期。当时&#xff0c;一些欧洲的制图师开始使用纸板和纸张来制作建筑模型&#xf…

【MATLAB数据处理实用案例详解(21)】——利用SOM自组织映射网络实现癌症样本数据分类和利用SOM自组织映射网络实现柴油机故障分类详解

目录 一、SOM原理分析二、SOM拓扑结构分析三、三种SOM拓扑结构3.1 Gridtop()网格拓扑结构3.2 Hextop()六角形拓扑结构3.3 Randtop()随机拓扑结构 四、利用SOM自组织映射网络实现癌症样本数据分类4.1 问题描述4.2 输入数据4.3 网络建立和训练4.4 验证4.5 预测4.6 运行结果 五、利…

wait/waitpid(重点)介绍

谢谢你的阅读&#xff0c;如有错误请大佬留言 目录 引子&#xff1a; waitpid 返回值介绍 参数介绍 pid status options: 引子&#xff1a; 当一个进程创建子进程后&#xff0c;如果子进程工作结束后会进入僵尸状态&#xff0c;等待父进程回收子进程资源&#xff08;退…

烟花智能直播助手,直播带货必备爆单工具【直播助手脚本+技术教程】

烟花智能直播助手软件教程介绍&#xff1a; 1.账号管理:可以登陆多个账号,一键切换 2.商品批量管理&#xff1a;可一键删除/添加直播商品,一键设置商品卖点 3.自动弹讲解:可设置指定,单个,列表循环自动弹商品讲解 4.智能文字客服:可设置指定关键词对公屏信息进行回复,不限添加条…