阅读视图

发现新文章,点击刷新页面。
🔲 ☆

别再滥用 ClickHouse 了!单机每秒狂刷 1800 万条数据,拆解 Go+DuckDB 的“微型数仓”降维打击

本文永久链接 – https://tonybai.com/2026/03/13/go-duckdb-micro-data-warehouse-dimensionality-reduction

大家好,我是Tony Bai。

设想这样一个极其普遍的日常工作场景:

产品经理找到你,希望能给业务后台加一个“简单”的数据看板,用来实时统计用户的 PV/UV 漏斗、Nginx 日志的慢查询分析,或者是 IoT 设备的近期时序数据。

面对每天几百万到上千万条的数据量,你陷入了沉思。

如果直接用 MySQL/PostgreSQL 跑 GROUP BY 和 COUNT(DISTINCT),数据库的 CPU 瞬间飙到 100%,不仅查询要等上十几秒,甚至可能把核心交易业务一起拖死。

如果为了这个需求,去大动干戈地部署一套 ClickHouse、Elasticsearch 、Spark 集群或某个大型时序数据库……不仅运维成本上天,对于这点数据量来说,简直是用高射炮打蚊子。

在“传统关系型数据库跑不动”和“大数据集群太沉重”之间,难道就没有一个恰到好处的方案吗?

今天,我想给你介绍一个在海外工程界使用较多的方案。它不仅能把你从沉重的大数据组件中解救出来,还能在你的 Go 语言单二进制文件中,塞进一个性能恐怖的 OLAP(在线分析处理)引擎。

它就是 DuckDB。结合 Go 语言,它能在普通服务器上跑出每秒 1800 万条记录的写入速度,和毫秒级的亿级数据分析延迟。

这绝对是一场对传统数据架构的降维打击。

为什么 MySQL/PG 做不好数据分析?

很多开发者在职业生涯早期都会踩这个坑:试图用 MySQL 解决一切问题。

当你在 PostgreSQL 或 MySQL 中执行一个跨度为 30 天的聚合分析时,为什么会慢得让人绝望?因为它们的底层是“行式存储(Row-oriented)”

在行式存储中,即使你只需要 user_id 和 timestamp 这两列,数据库也必须把每一行的所有字段(包括那些庞大的 JSON 或 Text 字段)全部从磁盘加载到内存中。大量无用的 I/O 消耗,让分析查询变成了灾难。

为了解决这个问题,我们被迫引入了 ClickHouse 等“列式存储(Column-oriented)”数据库。列式存储让分析查询的速度提升了上百倍,但代价是:你需要额外部署和维护分布式集群、学习复杂的表引擎配置等。

DuckDB——OLAP 界的 SQLite

难道列式存储就必须伴随着复杂的集群部署吗?

DuckDB 给出了一个极其优雅的答案:做 OLAP 领域的 SQLite。

DuckDB 是一个纯粹的嵌入式列式数据库。它没有独立的服务器进程,而是内嵌在你的应用进程中,不需要你配置任何网络端口。它有很多语言的binding,包括Go。

在 Go 项目中,你只需要简单地 import “github.com/duckdb/duckdb-go/v2″,它就会作为动态/静态链接库,直接融入你的 Go Application 进程中。

但千万别因为“嵌入式”三个字就觉得它是玩具。社区的一款开源高性能数据库 Arc(基于 Go + DuckDB)给出了一份令人毛骨悚然的实测数据(基于MacBook Pro M3 Max (14 cores, 36GB RAM, 1TB NVMe)):

  • 写入性能:高达 18.6M+(1860万)记录/秒
  • 写入延迟:P50 < 0.5ms,P99 < 4ms
  • 查询性能:6M+(600万)行/秒扫描 (Arrow格式)

它是怎么做到的?除了列式存储,它底层还偷偷藏着两个大杀器:向量化执行引擎(Vectorized Execution) 和对 Parquet 格式的原生支持

手把手拆解 1800 万/秒的极致写入

口说无凭,我们直接上硬核源码。

很多新手刚接入 DuckDB 时,会习惯性地用标准 SQL 的 INSERT INTO … VALUES 去循环写数据。你会发现速度并不快,一秒钟只能写几万条。

真正的降维打击,藏在 DuckDB 专门为 Go 语言暴露的 Appender API 中。

Appender 绕过了繁琐的 SQL 解析器和规划器,直接将 Go 的内存数据格式,以极低的开销批量“灌”入 DuckDB 的底层列存结构中。来看这段极致狂暴的写入代码:

// https://go.dev/play/p/mHXu-kAydDX
package main

import (
    "context"
    "database/sql"
    "fmt"
    "log"
    "time"

    duckdb "github.com/duckdb/duckdb-go/v2"
)

func main() {
    // 1. 用 NewConnector 创建连接器(指定数据库文件)
    connector, err := duckdb.NewConnector("analytics.db", nil)
    if err != nil {
        log.Fatal(err)
    }
    defer connector.Close()

    // 2. 用 sql.OpenDB 打开标准 db(用于建表等 SQL 操作)
    db := sql.OpenDB(connector)
    defer db.Close()

    _, err = db.Exec(CREATE TABLE IF NOT EXISTS metrics (id INTEGER, name VARCHAR, value DOUBLE, ts TIMESTAMP))
    if err != nil {
        log.Fatal(err)
    }

    // 3. 用 connector.Connect() 获取底层 driver.Conn(Appender 需要这个)
    conn, err := connector.Connect(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    // 4. 直接传 driver.Conn,无需 Raw()
    appender, err := duckdb.NewAppenderFromConn(conn, "", "metrics")
    if err != nil {
        log.Fatal(err)
    }
    defer appender.Close()

    startTime := time.Now()

    for i := 0; i < 100000; i++ {
        err := appender.AppendRow(
            int32(i),
            fmt.Sprintf("metric_%d", i%10),
            float64(i%100),
            time.Now(),
        )
        if err != nil {
            log.Fatal(err)
        }
    }

    elapsed := time.Since(startTime)
    fmt.Printf("插入 10 万条数据耗时: %v\n", elapsed)
    fmt.Printf("吞吐量: %.0f 记录/秒\n", 100000.0/elapsed.Seconds())
}

在我的一台2019款 普通 MBP 笔记本(Intel芯片)上,上述这段代码写入 10 万条数据仅需 69 毫秒

插入 10 万条数据耗时: 69.466586ms
吞吐量: 1439541 记录/秒

换算下来,吞吐量轻松突破 143 万条/秒。如果开启并发和更大批次,逼近千万级似乎也毫无压力。这比传统的 SQL INSERT 快了整整 100 倍

替代 ELK,只需一个 Go 二进制文件

掌握了这把利器,我们该如何在实际业务中发挥它的威力?

假设你有一个 10GB 的 Nginx 日志文件(或者 CSV 文件),老板让你马上查一下昨天的 PV、UV 和慢查询排行。

过去,你需要搭建 Logstash -> Elasticsearch -> Kibana 这一套全家桶。

现在,你只需要写几十行 Go 代码。DuckDB 支持直接查询 CSV 和 Parquet 文件,连数据导入都省了

你可以直接把底层的统计逻辑嵌在你的 Go REST API 里(仅作说明使用):

// 直接在 Go 代码中,把 DuckDB 当作微型分析网关
func (adb *AnalyticsDB) GetHourlyStats() (map[string]interface{}, error) {
    // 惊人特性:直接用 SQL 语法查询本地或 S3 上的 Parquet 压缩文件!
    rows, err := adb.db.Query(
        SELECT
            DATE_TRUNC('hour', timestamp) as hour,
            COUNT(*) as pv,
            COUNT(DISTINCT path) as uv
        FROM read_parquet('s3://my-bucket/nginx_logs/*.parquet')  -- 对 Parquet 格式的原生支持与深度优化(谓词下推、列裁剪),可跳过无关数据块,大幅减少实际 I/O
        WHERE timestamp > NOW() - INTERVAL '24 hours'
        GROUP BY hour
        ORDER BY hour DESC
    )
    // ... 解析并返回给前端
}

通过这种架构,你的 Go 语言 Web 服务瞬间拥有了媲美 ClickHouse 的 OLAP 分析能力。

最绝的是,整个系统的部署产物,仅仅是一个几十 MB 的 Go 二进制文件。没有额外的依赖,丢上服务器就能跑。

小结:它不是万能的银弹

虽然 DuckDB 强到离谱,但作为高级工程师,我们必须理智看待边界。

DuckDB 绝对不适合做高并发的 OLTP(在线事务处理)。

如果你用它来扛电商的下单扣库存、或者多用户的并发更新行数据,它会死得很惨。因为它是一头为了“大吞吐分析”而生的巨兽,并没有针对行级锁和高频短事务做优化。

所以,最完美的现代架构公式应该是:

PostgreSQL/MySQL(负责核心业务流) + Go 应用内嵌 DuckDB(负责旁路日志、报表聚合的简单轻量分析)。


** 今日互动探讨:**

你在公司里遇到过哪些“为了小数据杀鸡用牛刀,强行部署大集群”的奇葩架构?或者你平时处理百万级数据分析时,最爱用什么工具?

欢迎在评论区疯狂吐槽或分享!


认知跃迁:掌控架构降维打击的底层逻辑

看到这里,你是否对日常的业务开发有了全新的视角?

在过去,面对复杂的分析需求,CRUD 程序员的本能反应是“引入一个新的重量级中间件”。

但真正的高级架构师,懂得利用底层技术栈的差异性(如行存与列存、向量化与标量计算),用最轻量、最克制的方案完成“降维打击”。

如果你的 Go 技能依然停留在写写简单的增删改查 API,对更深层的并发控制、内存管理和系统级架构选型感到迷茫——

我的极客时间专栏《Go语言进阶课》正是为你量身打造!

在这 30+ 讲硬核内容中,我将带你剥开语法糖,深入理解 Go 的底层运行机制,不仅教你写代码,更教你像顶级大厂架构师一样思考:如何用最少的组件,设计出极高并发、极低延迟的优雅系统。

目标只有一个:助你完成从“Go 熟练工”到“能做顶级架构决策的 Go 专家”的蜕变!

扫描下方二维码,加入专栏,让我们一起用技术实现“四两拨千斤”的震撼。


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


原「Gopher部落」已重装升级为「Go & AI 精进营」知识星球,快来加入星球,开启你的技术跃迁之旅吧!

我们致力于打造一个高品质的 Go 语言深度学习AI 应用探索 平台。在这里,你将获得:

  • 体系化 Go 核心进阶内容: 深入「Go原理课」、「Go进阶课」、「Go避坑课」等独家深度专栏,夯实你的 Go 内功。
  • 前沿 Go+AI 实战赋能: 紧跟时代步伐,学习「Go+AI应用实战」、「Agent开发实战课」、「Agentic软件工程课」、「Claude Code开发工作流实战课」、「OpenClaw实战分享」等,掌握 AI 时代新技能。
  • 星主 Tony Bai 亲自答疑: 遇到难题?星主第一时间为你深度解析,扫清学习障碍。
  • 高活跃 Gopher 交流圈: 与众多优秀 Gopher 分享心得、讨论技术,碰撞思想火花。
  • 独家资源与内容首发: 技术文章、课程更新、精选资源,第一时间触达。

衷心希望「Go & AI 精进营」能成为你学习、进步、交流的港湾。让我们在此相聚,享受技术精进的快乐!欢迎你的加入!

img{512x368}


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

© 2026, bigwhite. 版权所有.

🔲 ☆

ARP 问题诊断

这是 网络断断续续 一文的答案。

答案是,在同一个子网内,同一个 IP 地址分配到了 2 个不同的设备上。

对于这种时而正常时而异常的「幽灵问题」,在没有思路的时候,可以通过对比的方法来寻找线索。

有的时候 TCP 能够连通,有的时候无法连通。那么正常的 TCP SYN 包和异常的 TCP 包之间肯定是有什么字段是不一样的。当然,也有可能 SYN 包一模一样,问题出在了其他的网络设备上或者 TCP 的另一端。但是既然题目出给读者了,那么问题的根源肯定是隐藏在抓包文件里面。

通过对比来寻找答案

正常的 TCP 连接可以完成 TCP 的握手和挥手。

异常的 TCP 连接,发出去的 SYN 直接收到了 RST。

通过对比可以发现,两个 SYN 包的 Dst MAC 是不一样的。

通过推理得出答案

如果不通过对比的方法,顺藤摸瓜,可以用下面的思路。

首先,Src IP 和 Dst IP 分别是 10.210.151.187 和 10.210.151.90,很大概率是属于同一个 /24 子网的 IP,不过要想确认的话需要看下服务器的 IP 地址是如何配置的,子网掩码是不是 /24,这点我们从给出的信息无从得知。

假设确实是属于同一个子网,那么 TCP SYN 包不经过网关,直接发给目的地址,二层的 Dst MAC 地址应该是目的 IP 的 MAC 地址。

假设不属于同一个子网,那么 TCP SYN 包经过网关,TCP SYN 包的目的地址应该是路由器的 MAC 地址。

无论是那种情况,正常情况下发送给同一个 Dst IP 的目的 MAC 应该是唯一且稳定的。通过 Conversation 统计可以看出,通讯的 IP 对只有一对,但是 MAC 的确有 2 对,这是不对的。

Statistics > conversation

原因分析

造成这种现象的原因是,IP 分配重复了,在同一个子网内,同一个 IP 地址 10.210.151.90 被分配给了多个机器。

发送 TCP 包之前,需要知道对应的 Dst IP 地址的 Dst MAC 地址,怎么知道呢?Src 机器会在整个子网内广播 ARP 询问,持有这个 IP 的机器会回复 ARP。现在子网内有两个相同的机器是相同的 IP 地址。这两台机器的 IP 地址一样,MAC 地址不一样。如果我们现在 arping 10.210.151.90 ,会得到如下的回复:

$ arping -c 3 10.210.151.90
ARPING 10.210.151.90
42 bytes from 5a:65:98:0c:82:02 (10.210.151.90): index=0 time=944.055 usec
42 bytes from c2:10:70:f2:ae:ab (10.210.151.90): index=1 time=997.915 usec
42 bytes from 5a:65:98:0c:82:02 (10.210.151.90): index=2 time=13.799 usec
42 bytes from c2:10:70:f2:ae:ab (10.210.151.90): index=3 time=109.388 usec
42 bytes from 5a:65:98:0c:82:02 (10.210.151.90): index=4 time=6.670 usec
42 bytes from c2:10:70:f2:ae:ab (10.210.151.90): index=5 time=48.983 usec

--- 10.210.151.90 statistics ---
3 packets transmitted, 6 packets received,   0% unanswered (3 extra)
rtt min/avg/max/std-dev = 0.007/0.353/0.998/0.438 ms

可以看到,我们发出去 3 个询问,却收到 6 个回复。说明每一个询问得到了 2 个答案。(其实,在诊断和验证这个问题的过程中,最快的方式就是用 arping 来测试一下,而不是抓包来分析。但是这里我们主要讨论抓包技术,所以拿来当作一个分析的案例。)

那么 Src 收到两个 ARP 应答,会以哪一个为准呢?答案是以先收到的为准。

$ ip nei show
10.210.151.90 dev h2-eth0 lladdr 5a:65:98:0c:82:02 REACHABLE

但是 ARP 请求广播出去,这两个 ARP 应答哪一个先到是无法确定的,有的时候 5a:65:98:0c:82:02 先到,有的时候 c2:10:70:f2:ae:ab 先到,所以发送给 10.210.151.90 的包,有的时候发给了 MAC 地址为 5a:65:98:0c:82:02 的机器,有的时候发送给了 MAC 地址为 c2:10:70:f2:ae:ab 的机器。

补充一点,在实际的问题排查过程中,我们的 TCP 连接测试失败,最好的排查方法就是去对端进行抓包,看一下对端的机器是否收到了 SYN 包。以及在实际的现象中,客户端收到了 Connection refused ,通常意味着收到了 RST 报文(可能来自真实对端,也可能来自其他设备)。我们在对端抓包的过程中会发现即没有收到 SYN,也没有发出 RST,这说明包到别的地方去了。进而,我们可以发现字网内还有一个「李鬼」的问题。

那么,为什么 ping 的测试完全正常呢?

因为 ICMP 是无连接协议,,ping 的 ICMP 协议回复,是由 Kernel 负责的,所以无论 ICMP 包发送到哪一个机器上,由于它们都设置了这个 IP 地址,所以都可以做出回复。其实抓包中的回复是来自不同的机器,只不过 ping 不知道,只要是收到了回复,就认为一切正常。

可以把 Src MAC 地址添加到 Column 显示,就可以发现其实 ping 的回复来自不同的设备。

最后一个问题,为什么会有 IP 重复的问题呢?

DHCP 可以用来动态分配 IP 地址给设备,但是一般只用于客户端,比如办公网、家用网络中的终端设备,这些设备一般作为连接的发起方,不需要 listen 一个固定的 IP 地址,IP 地址的动态变化对它们影响不大。但是在 IDC 的网络中,每一个服务器的软件都需要固定的 IP 地址,一般不用 DHCP 来动态分配,而是用中心化的 IPAM 系统1追踪 IP 地址的分配情况。如果里面记录的地址不正确,比如,一个地址已经在使用中了,但是并没有在 IPAM 记录,就造成 IP 地址重复的问题。

  1. https://en.wikipedia.org/wiki/IP_address_management ↩

==抓包破案录==

这篇文章是抓包破案录系列文章(之前叫做《计算机网络实用技术》,后来改名了)中的一篇,这个系列正在连载中,我计划用这个系列的文章来分享一些网络抓包分析的实用技术。这些文章都是总结了我的工作经历中遇到的问题,经过精心构造和编写,每个文件附带抓包文件,通过实战来学习网络抓包与分析。

如果本文对您有帮助,欢迎扫博客右侧二维码打赏支持,正是订阅者的支持,让我公开写这个系列成为可能,感谢!

如果您正在阅读的是题目类的文章,这个目录内容正好用来隔离其他读者的评论。读完题目可以稍作暂停,进行思考,继续向下滑动,可能会被其他的读者剧透答案。

没有链接的目录还没有写完,敬请期待……

  1. 序章
  2. 抓包技术以及技巧
  3. 理解网络的分层模型
  4. 数据是如何路由的
  5. 网络问题排查的思路和技巧
  6. 不可以用路由器?答案和解析
  7. 网工闯了什么祸?答案和解析阅读加餐!
  8. 重新认识 TCP 的握手和挥手答案和解析
  9. 3.5 秒初始延迟问题答案和解析
  10. 网络断断续续……答案和解析
  11. 延迟增加了多少?答案和解析
  12. 压测的时候 QPS 为什么上不去?答案和解析
  13. TCP 下载速度为什么这么慢?答案和解析
  14. 请求为什么超时了?答案和解析
  15. 0.01% 的概率超时问题答案和解析
  16. 后记:学习网络的一点经验分享
与本博客的其他页面不同,本页面使用 署名-非商业性使用-禁止演绎 4.0 国际 协议。
🔲 ☆

谁在悄悄关注你?给博客装上 RSS 订阅计数器

由于博客程序更换为了 Hexo,现在只能通过引用 JavaScript 脚本,来实现博客流量的统计。长时间关注我的朋友都知道,我的博客一直通过 Feed 来向读者提供完整文章内容,以方便在我更新后能让更多的人能够尽快看到文章,所以有很多我博客的观众是通过 Feed 来订阅我的博客,这样就产生一个很大的问题:这部分读者,是通过 Feed 订阅来查看我的文章的,故此这部分读者的数量我无法统计。

诚然,有很多朋友博客的 Feed 是不推送完整的文章内容的,只有标题和摘要。但这样也把订阅的流量放到了整站流量的统计里,可能通过 Referer 域名来看到一小部分的订阅用户(因为有些读者是通过本地客户端来进行订阅的,并非通过 Web 的 Feed 的订阅服务)还会造成重复统计的问题,准确性大大降低,这样也无法获取有多少读者订阅了自己的博客。

很久之前就有这个想法,一般统计订阅的思路是通过网关的访问日志,来估算有多少用户订阅了博客,但这样做一来规整数据会极其麻烦,无法可视化简单的看到具体的用户信息,二来互联网中各种爬虫、Bot 防不胜防,你压根没有办法准确的获取其大概的订阅数量。

前段时间灵光一闪,那么我其实可以根据这部分数据来做点文章,所以写了一个小项目,来解决这个问题。

举例来说,我的 Feed 订阅地址为roy.wang/feed/ 但我的订阅文件位于 roy.wang/feed/index.xml ,看过我这篇《博客托管 Cloudflare Pages 后,关于 RSS 订阅链接的问题》的朋友或者把博客托管到 Cloudflare Pages 的朋友会知道,Cloudflare Pages 是不支持将非 index.html 的页面设置为默认页面的。这样这个项目也可以顺势解决这个问题。

其核心的处理逻辑如下:

  1. 当我访问 /feed/ 时,项目就会记录访问者的信息,并且返回 /feed/index.xml 文件的内容。
  2. 检测访问流量,其 UA 是否包含代码中匹配的 Feed 客户端标识符。如果是,则立即将其视为有效订阅用户。
  3. 如果不匹配,则开始计数。72小时内,访问3次,则视为有效订阅用户。

所有订阅用户有 3 个小时的访问冷却期,以确保单订阅用户多时 Cloudflare Workers & D1 不会产生额外费用或停机。

RSS-Stats

提前声明一下,这个小项目呢,只适合博客域名托管至 Cloudflare 的朋友来使用。

而且需要变更现在实际的订阅文件路径,但原本订阅 URI 需保留。

目前尚未验证 CNAME 接入域名是否可行。

这个小项目只是通过一种统计思路,您也可以将其二次开发,或者使用别的语言重构,来切合您自己的博客。准确性,按照我自己的需求来看,是完全够用的。

项目地址:RSS-Stats

image-20260114231901206

开始部署

部署起来非常简单,首先请 Fork 本项目,使其添加到您的账号。

image-20260114235346699

创建数据库

首先,在 Cloudflare 的控制台左侧的存储和数据库 –> D1 SQL 数据库 –> 右上角创建数据库

填写数据库名称,点击创建即可。

image-20260114235127396

此时,在顶部获取数据UUID,后面会用到。格式如36ef4192-973e-469e-b7b4-1536ab69b109

部署项目

在 Cloudflare 的控制台左侧的计算和AI –> Workers 和 Pages –> 右上角创建应用程序

进入到创建 Worker 的页面后,点击 Continue with GitHub,选择刚刚 Fork 的仓库,点击下一步

image-20260114235705048

或者可以通过 Git URL 克隆公共存储库来进行部署:

1
https://github.com/roywangdev/RSS-Stats

Set up your application 此步骤,需自定义项目名,将部署命令修改为:

1
2
3
4
5
echo 'name = "rss-stats-test"\nmain = "index.js"\ncompatibility_date = "2026-01-14"\n[[d1_databases]]\nbinding = "DB"\ndatabase_id = "cf1391d8-a25e-47bb-8e90-3427137f8574"' > wrangler.toml && npx wrangler deploy

# name 填写自定义的 Workers 项目名
# compatibility_date 建议填写为部署当天的日期(格式:YYYY-MM-DD)
# database_id 填写为创建数据库获取的 UUID

完成后点击部署即可。

设置环境变量

由于未设置环境变量,故部署完成后,打开链接会显示Error: 缺少必须的环境变量配置: RSS_URL,需要设置多个环境变量:

变量名介绍是否必须
RSS_URL此变量填写 RSS 文件实际地址,如https://domain.com/feed/index.xml
STATS_PATH此变量填写 RSS-Stats 控制台访问路径,如 /stats/
FEED_PATHS此变量填写 RSS 订阅路径,如 /feed/
COOKIE_NAME登录 Cookie 名称 (默认 rss_stats_auth)
COOKIE_MAX_AGE登录有效期秒数 (默认 1年)
PER_PAGE列表每页显示条数 (默认 20)

点击项目的设置,找到变量和机密,点击添加。类型选择文本,输入完成后,点击部署。

配置项目

此时即可访问 Workers URL + STATS_PATH 即可开始初始化数据,如:

1
https://rss-stats-test.roywangdev.workers.dev/stats/

image-20260115011603433

点击立即初始化数据库,然后设置账号密码。点击保存并进入后台即可登录,进入仪表盘。

image-20260115011709277

此时还需要设置路径路由,FEED_PATHS 必须设置,STATS_PATH可选。

进入设置,找到域和路由,添加选择路由,配置如下:

1
2
3
区域:博客域名
Feed 路由:domain.com/feed/
Stats 路由:domain.com/stats*

注意,如果 Feed 路由和实际订阅文件 URI 是包含关系,则不要写为 /feed/ 或 /feed**

总结

这个小项目,主旨是提供一种思路,用来统计具体的订阅用户数,如果有什么未讲到的,或者大佬们有什么更好的方案,可以评论区留言一下~

一点问题

还有一点要单独说明一下,如果有朋友发现配置之后访问 /feed/多次后并没有统计,请查看该页面的响应标头是否有 X-Rss-Stats Active 的响应标头。如未有,请检查,是否如文章《博客托管 Cloudflare Pages 后,关于 RSS 订阅链接的问题》所述,已设置 URL 重写来解决 Cloudflare Pages 不支持非 index.html 文件设置为默认页面的问题。

🔲 ☆

解决 Umami 统计脚本被拦截广告插件拦截

使用外挂 JS 的方式来统计数据,虽然可以统计真实的访客记录,但是会被 uBlock 此类的广告拦截插件给直接拦截掉,以至于无法准确的获取访客数据。

简单了解了下此类插件的拦截策略,是通过检测脚本文件名进行的。可以通过修改 JS 文件名来进行防止统计信息脚本被拦截。


方法一(推荐)

官方在 1.26.0 版本上增加了 TRACKER_SCRIPT_NAME 这个环境变量来提供修改统计脚本的操作,这也是本站现在使用的方法。

但是此环境变量中的值,有些情况会失效,更换即可,原因未知。

打开目录中的 .env 文件,添加以下文本:

1
TRACKER_SCRIPT_NAME=自定义名称

img

输入完成后保存即可。

重启服务,修改网站链接中的脚本地址

img重启UMAMI服务

img访问网址,修改已生效

使用 CloudFlare Workers修改名称

这个方法是以为大佬在项目讨论区分享的,也试了以下,效果一致。

新建CloudFlare Workers

左侧选择 Workers,然后创建服务

img

选择启动器,然后创建服务

服务名称自定义,启动器选择 HTTP 处理程序

img

然后点击快速编辑,并将以下信息 修改输入 并 保存。

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
const ScriptName = '/umami.js';     //自定义脚本名称
const Endpoint = '/foo/bar';
const UmamiUrl = 'https://bcon.roywang.cn'; //UMAMI服务域名地址。
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
'Access-Control-Max-Age': '86400',
};
const ScriptWithoutExtension = ScriptName.replace('.js', '')
addEventListener('fetch', event => {
event.passThroughOnException();
event.respondWith(handleRequest(event));
})
async function handleRequest(event) {
const pathname = new URL(event.request.url).pathname
const [baseUri, ...extensions] = pathname.split('.')
const clientIP = event.request.headers.get("CF-Connecting-IP")
if (baseUri.endsWith(ScriptWithoutExtension)) {
return getScript(event, extensions)
} else if (pathname.endsWith(Endpoint)) {
return postData(event)
}
return new Response(null, {status: 404})
}
async function getScript(event, extensions) {
let response = await caches.default.match(event.request);
if (!response) {
response = await fetch(UmamiUrl +"/umami.js");
var js = await response.text();
js = js.replace("/api/collect", Endpoint);
response = new Response(js, {
headers: {
...response.headers,
...corsHeaders,
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Request-Headers'),
},
})
event.waitUntil(caches.default.put(event.request, response.clone()));
}
return response;
}
async function postData(event) {
const request = new Request(event.request);
request.headers.delete('cookie');
response = await fetch(UmamiUrl +"/api/collect", request);
var js = await response.text();
response = new Response(js, {
headers: {
...response.headers,
...corsHeaders,
'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers'),
},
});
return response;
}

img

文件名称修改成功。

CloudFlare Workers 绑定自定义域名

域名需要 NS 接入CloudFlare,增加解析 至 8.8.8.8,并且打开 CloudFlare 代理

img

在左侧选择 Workers,并选择添加路由。

img输入路由,以及选择服务、环境。

此处有个误区,路由在域名后面需要添加 /* 很多用户在使用时都会犯这个错误。

img

修改网站中的脚本地址。

img

第一种方法最简单,也最推荐,当然如果服务的线路非常差,比CloudFlare 还差的话,推荐使用第二种方法。

如后续还被屏蔽,只需重新修改脚本名称即可。

🔲 ☆

Umami - 自建网站访问量统计系统

由于站点较多,而且为了保护隐私,比较抵触使用 CNZZ 或 GOOGLE 等第三方的访问量统计系统。最近发现了一个统计网站访问量的工具。也使用了一段时间,现在将搭建教程分享出来:


项目介绍

项目地址:Umami

UMAMI 基于Node.js 开发,并且支持 MySQL 或 Postgresql 等数据库存储方式,可以将数据掌握在自己手中。并且 UMAMI 还提供了非常详细的流量分析可视化的界面,UI 体验以及统计准确度十分不错(此处所讲统计精准度为实际真是访客的访问量,有可能一天只有1-2请做好心理准备。)

官方文档:Umami Docs

环境需求

由于 Umami 基于Node.js 开发,采用 MySQL 或 Postgresql 数据库存储数据,所以配置要求如下:

  • Node.js 14.18 或更高的版本
  • 服务器上安装了 MySQL 5.7 或以上 或 Postgresql 12.14 或以上

2022-7-22 更新:今天更新 umami 时,发现更换了 umami 构建安装方式,遂更新一波,可能配图不正确,但步骤基本一致。

2023-5-3更新:更新了一下 Node.js 的版本,以及文章上的一些错误。

2023-8-21更新:程序守护更改为 PM2 管理,进而放弃 systemctl ,其在我这次将服务迁移到国内服务器内出现了很大的问题,以及文章上的一些错误。

2024-2-8更新:更新了Node.js 的版本,以及文章上的一些错误。


环境配置

本文基于宝塔面板,使用 MySQL 进行安装

安装配置 Node.js

此处 MySQL 安装方式此处不再进行演示,如您已安装 Node.js 并且配置成功,则此步可以跳过。

安装 Node.js 命令如下

1
2
3
cd /www/server/nodejs
wget https://nodejs.org/dist/v21.6.0/node-v21.6.0-linux-x64.tar.gz
tar -xzvf node-v21.6.0-linux-x64.tar.gz

设置 Node.js 全局变量:

在宝塔面板打开 /etc/profile 文件,将以下配置输入文件最后面,并保存

1
2
export NODE_HOME=/www/server/nodejs/node-v21.6.0-linux-x64
export PATH=$NODE_HOME/bin:$PATH

输入以下命令用于重载全局配置。

1
source /etc/profile

输入 node -v 和 npm -v 返回以下信息即配置完成

img

配置成功

安装 Yarn

1
npm install -g yarn

查看版本信息,如遇 yarn:未找到命令,请看:《NodeJS 和 npm 配置全局变量

img

国内服务器此步骤由于 Node.js 没有大陆节点,速度较慢。请耐心等待

部署 Umami

克隆仓库

1
2
cd /www/wwwroot
git clone https://github.com/umami-software/umami

img

安装依赖

国内服务器此步骤由于 Node.js 没有大陆节点,速度较慢。请耐心等待

千万不要使用淘宝的 registry 镜像源!!!!!!!!

否则安装依赖会出错!!!!!!!!

1
2
cd umami
yarn install

img

依赖安装完成

配置数据库

创建数据库:打开宝塔面板,点击左侧 数据库->添加数据库。输入数据库相关信息即可。

img

配置 .env 文件

在 umami 文件夹中新建一个名为 .env 的文件

img

文件具体配置如下:

1
2
DATABASE_URL=mysql://用户名:密码@localhost:3306/库名
HASH_SALT=roywang (加密字符 随机即可)

img

配置示例

构建 Umami

千万不要使用淘宝的 registry 镜像源!!!!!!!!

否则安装依赖会出错!!!!!!!!

1
yarn build

img

构建完成

导入数据库

1
yarn update-db

运行

1
yarn start

img

默认用户名:admin 默认密码:umami

此时即可通过 IP:3000访问网站了,但为了方便使用,还需进行以下操作

其他配置

设置程序守护,此处以 PM2 为例

打开目录 umami 安装目录

1
2
3
4
5
6
#全局安装 pm2
yarn global add pm2

pm2 start yarn --name umami -- start
pm2 startup
pm2 save

img

设置网站反代

宝塔新建站点 -> 打开SSL - > 反向代理 ->添加反向代理

目标 URL 填写:http://127.0.0.1:3000

img配置反代

img

配置站点

登录 Umami

默认账号:admin
默认密码:umami

img

添加站点,并将代码放到网站

部分来收集数据。

升级配置

此时必须保证 Umami 没有正在运行,重新 拉取仓库、安装依赖、构建项目、启动即可

1
2
3
4
5
6
7
8
9
10
#拉取仓库
git pull
#安装
yarn install
#构建项目
yarn build
#更新数据库
yarn update-db
#启动项目
systemctl start umami

后语

特别强调,无论国外源多慢都强烈要求使用国外源。在使用淘宝源时,构建会失败!!!!!!!!!!!!!

**解决了统计JS被广告拦截插件给屏蔽的问题:**https://bcon.roywang.cn/umami-js-name/

🔲 ⭐

【好玩儿的Docker项目】Umami替代品,又一款网站流量统计工具——Plausible

1.前言

之前和大家介绍过一款精美的网站流量统计工具——Umami

最近,Umami爆出一个严重的安全漏洞(某位小伙伴在博客评论区提醒我的,感谢),

4ce9d75b54d9ead867e1b5ea75f95fe2.png

分享的网站数据链接可能会导致的你的网站统计数据被重置。

这个对个人来说还好,毕竟没多少访问量,但是对于小型团队,尤其是要做数据分析的(同比、环比)的用户来说,还是比较讨厌的。(希望社区的维护人员应该在尽力解决这个问题了。)

这一期,我们就趁着机会,来介绍另一个流量统计的项目——Plausible。

Plausible Analytics 是一个简单、开源、轻量级(< 1 KB)且注重隐私的 Google Analytics 替代品。Plausible 被超过10,000个付费订阅用户信任,用于提供他们网站和业务洞察力。我们完全独立、自筹资金并自给自足。

来自官方Docs

f7d8459c33258f930f7b4fa230448ef8.png

2. 项目展示

直接丢几个图:

62fead07032a298c61a3cb14f0300e4e.png

af05ea78d49628ae5e95ef7aa4fa0141.png

bcdabf1f64a40e3b128c36ba822978aa.png

2.1 特点

  • 支持docker一键部署,轻量级Docker镜像(使用Alpine Linux)
  • 支持谷歌API,链接谷歌分析、谷歌关键词工具
  • 支持自定义事件
  • 颜值高

3. 相关地址

咕咕的Demo:本站流量情况
GitHub官方仓库:https://github.com/plausible/analytics
官方文档地址:https://plausible.io/docs

4. 搭建环境

  • 服务器:腾讯香港轻量应用服务器 24 元 / 月 VPS 一台 咕咕自己搭建用的是香港的腾讯轻量应用服务器 ,(最好选 非大陆的,而且线路还不错的机器)如果是小白刚开始玩的话,也可以选择 莱卡云 美国CN2 GIA 的服务器,(莱卡云服务器介绍:点击查看)或者Racknerd的高性价比服务器(注意地区选美国西部城市的)
  • 系统:Debian 11 (DD 脚本 非必需 DD,用原来的系统也 OK,之后教程都是用 Debian 或者 Ubuntu 搭建~)
  • 安装好 Docker、Docker-compose(相关脚本
  • 【非必需但建议】域名一枚,并做好解析到服务器上(域名购买、域名解析 视频教程
  • 【非必需】提前安装好宝塔面板海外版本 aapanel,并安装好 Nginx(安装地址
  • 【非必需本教程选用】安装好 Nginx Proxy Manager(相关教程

服务器要求:内存建议1G以上

5. 搭建视频

5.1 YouTube

视频地址:https://youtu.be/U0tXcsqVSMo

5.2 哔哩哔哩

哔哩哔哩:https://www.bilibili.com/video/BV1Hh4y1Q7tH/

6. 搭建方式

如果你不是用的腾讯云的轻量应用服务器,可以直接跳到 6.1 部分。

安装系统(腾讯云轻量应用服务器)

e59713fba8726d3cb55ae11bca83fe3c.png

腾讯云轻量服务器最大的特点就是 “轻量”,相比 CVM,更适合小白上手,这边我们之间选择 Docker 基础镜像,就可以省去后面安装 Docker 的步骤 (如果你非要用国内的服务器,这边装的 Docker 镜像还会帮你配置好国内镜像源,让你加速访问 docker 镜像资源) 不建议用国内的 。

登陆(腾讯云轻量应用服务器)

2722040ee311eb4a9ebf2a4945bf38f4.pngc0b5d360053746c4095d592967ee401f.pnge0ba858f021b846ad0abc27acf5008c2.png

6.1 安装 Docker 与 Nginx Proxy Manager

可以直接参考这篇内容:

https://blog.laoda.de/archives/nginxproxymanager/

6.2 创建安装目录

创建一下安装的目录:

sudo -i

mkdir -p /root/data/docker_data/plausible

cd /root/data/docker_data/plausible

git clone https://github.com/plausible/hosting

cd hosting

vim docker-compose.yml

英文输入法下,按 i

以下是咕咕修改的,可以看看有啥不一样(具体见视频)

version: "3.3"
services:
mail:
image: bytemark/smtp
restart: always

plausible_db:
# supported versions are 12, 13, and 14
image: postgres:14-alpine
restart: always
volumes:
- ./db-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=postgres

plausible_events_db:
image: clickhouse/clickhouse-server:23.3.7.5-alpine
restart: always
volumes:
- ./event-data:/var/lib/clickhouse
- ./clickhouse/clickhouse-config.xml:/etc/clickhouse-server/config.d/logging.xml:ro
- ./clickhouse/clickhouse-user-config.xml:/etc/clickhouse-server/users.d/logging.xml:ro
ulimits:
nofile:
soft: 262144
hard: 262144

plausible:
image: plausible/analytics:v2.0
restart: always
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
depends_on:
- plausible_db
- plausible_events_db
- mail
ports:
- 8090:8000
env_file:
- plausible-conf.env

大家可以用默认的设置,也可以和咕咕一样修改一下,修改好之后,注意切换成英文输入法,然后按一下 esc,然后 :wq 保存退出。

openssl rand -base64 64 | tr -d '\n' ; echo

保存下来。

打开环境配置文件:

vim plausible-conf.env
BASE_URL=填你打算给plausible准备的域名,比如https://analytics.baidu.com
SECRET_KEY_BASE=填刚刚生成的那个密钥

修改好之后,注意切换成英文输入法,然后按一下 esc,然后 :wq 保存退出。

更多参数配置,请看文档:https://plausible.io/docs/self-hosting-configuration

6.3 打开服务器防火墙(非必需)并访问网页

打开防火墙的端口 8090

举例,腾讯云打开方法如下(部分服务商没有自带的面板防火墙,就不用这步操作了):

image-20220630215240864image-20220630220546335

图中示例填的是 5230,备注填的是 memos,这边我们填 8090,示例填 plausible ,确定即可(如果你在 docker-compose 文件里换了 9009,这边就需要填 9009,以此类推)

image-20220819145844555

查看端口是否被占用(以 8090 为例),输入:

lsof -i:8090  #查看 8090 端口是否被占用,如果被占用,重新自定义一个端口

如果啥也没出现,表示端口未被占用,我们可以继续下面的操作了~

如果出现:

-bash: lsof: command not found

运行:

apt install lsof  #安装 lsof

如果端口没有被占用(被占用了就修改一下端口,比如改成 8081,注意 docker 命令行里和防火墙都要改)

理论上我们就可以输入 http://ip:8090 访问了。

注意:

1、不知道服务器 IP,可以直接在命令行输入:curl ip.sb,会显示当前服务器的 IP。

2、遇到访问不了的情况,请再次检查在宝塔面板的防火墙和服务商的后台防火墙是否打开对应了端口。

一般情况下,我们都需要给网页加上https!让网页更安全!

namesilo 上面 xyz 后缀的域名一年就 7 块钱,可以年抛。(冷知识,namesilo上 6位数字的xyz续费永远都是0.99美元 = =)

如果想要长期使用,还是建议买 com 后缀的域名,更加正规一些,可以输入 laodade 来获得 1 美元的优惠(不知道现在还有没有)

namesilo 自带隐私保护,咕咕一直在用这家,价格也是这些注册商里面比较低的,关键是他家不像其他家域名注册商,没有七七八八的套路!(就是后台界面有些 古老 = =)

【域名购买】Namesilo 优惠码和域名解析教程(附带服务器购买推荐和注意事项)

我们接着往下看!

7. 反向代理

我们要用域名而不是IP+端口的方式来访问我们的服务!

7.1 利用 Nginx Proxy Manager

在添加反向代理之前,确保你已经完成了域名解析,不会的可以看这个:域名一枚,并做好解析到服务器上域名购买、域名解析 视频教程

image-20221016140213282

之后,登陆 Nginx Proxy Manager(不会的看这个:安装 Nginx Proxy Manager相关教程))

注意:

Nginx Proxy Manager(以下简称 NPM)会用到 80443 端口,所以本机不能占用(比如原来就有 Nginx)

直接丢几张图:

image-20220502123517401image-20230527132253249

注意填写对应的 域名IP端口,按文章来的话,应该是 8090

IP 填写:

如果 Nginx Proxy Manager 和 plausible 在同一台服务器上,可以在终端输入:

ip addr show docker0

查看对应的 Docker 容器内部 IP。

否则直接填 plausible 所在的服务器 IP 就行。

image-20220403104353185

再次打开,勾选这些:

image-20221016140356324

然后就可以用域名来安装访问了。

7.2 利用宝塔面板

发现还是有不少小伙伴习惯用宝塔面板,这边也贴一个宝塔面板的反代配置:

直接新建一个站点,不要数据库,不要 php,纯静态即可。

然后打开下面的配置,修改 Nginx 的配置。

image-20220819150345725image-20220819150542867

代码如下:

location / {
proxy_pass http://127.0.0.1:8090/; # 注意改成你实际使用的端口
rewrite ^/(.*)$ /$1 break;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade-Insecure-Requests 1;
proxy_set_header X-Forwarded-Proto https;
}

此方法对 90% 的反向代理都能生效,然后就可以用域名来安装访问了。

有同学可能会问,为什么不直接用宝塔自带的反向代理功能。

image-20220819150730128

也可以,不过咕咕自己之前遇到过当有多个网站需要反代的时候,在这边设置会报错的情况 = =

所以后来就不用了,直接用上面的方法来操作了。

8. 使用教程

建议参考视频,或者结合官方文档食用:https://plausible.io/docs

8.1 登录

输入域名,登录Plausible

c3fea4fa25e9ee495bdb3c6395ff0519.png

8.2 填写需要统计的网站的名称

d7aff08aa538238172bc5ad6de2186b5.png

8.3 获取统计代码

8e4030aabf1d1548446f1bd8f4f2b38c.png

将这串js代码放到网站的<head></head>之间就行。

比如Hexo博客(以Butterfly主题为例子):

e51d8f5b49a4691a154abb5f565bb1ff.png

Halo博客(以Halo 2.0为例子):

be1e31f92a0c393d793ca39209a15c36.png

WordPress博客(以JustNews主题为例子):

b5341063d3728312825a8b3b1bb3b6cd.png

8.4 查看统计数据

没什么问题的话,访问一下你的网站,很快这边就能显示数据啦。

c3b71dbd83f26694dc82e8121269c8c3.png

更高级的功能,比如如何连接谷歌统计等等,请结合官方文档食用:https://plausible.io/docs

8.5 更新 plausible

cd /root/data/docker_data/plausible

docker-compose down

cp -r /root/data/docker_data/plausible /root/data/docker_data/plausible.archive # 万事先备份,以防万一

docker-compose pull

docker-compose up -d # 请不要使用 docker-compose stop 来停止容器,因为这么做需要额外的时间等待容器停止;docker-compose up -d 直接升级容器时会自动停止并立刻重建新的容器,完全没有必要浪费那些时间。

docker image prune # prune 命令用来删除不再使用的 docker 对象。删除所有未被 tag 标记和未被容器使用的镜像

提示:

WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N]

输入 y

利用 Docker 搭建的应用,更新非常容易~

8.6 卸载 plausible

cd /root/data/docker_data/plausible

docker-compose down

cd ..

rm -rf /root/data/docker_data/plausible # 完全删除映射到本地的数据

可以卸载得很干净。

9. 常见问题

如果不想让别人注册你的plausible,可以选择关闭注册功能,或者开启邀请注册,具体操作如下:


cd /root/data/docker_data/plausible/hosting


vim plausible-conf.env

添加:

DISABLE_REGISTRATION=invite_only

或者


DISABLE_REGISTRATION=true

完整的env配置如下:

BASE_URL=填你打算给plausible准备的域名,比如https://analytics.baidu.com
SECRET_KEY_BASE=填刚刚生成的那个密钥
DISABLE_REGISTRATION=invite_only

更多参数配置,请看文档:https://plausible.io/docs/self-hosting-configuration

10. 结尾

祝大家用得开心,有问题可以去 GitHub 提 Issues,也可以在评论区互相交流探讨。

同时,有能力给项目做贡献的同学,也欢迎积极加入到 项目 中来,贡献自己的一份力量!

最后,感谢开发人员们的辛苦付出,让我们能用到这么优秀的项目!

参考资料

GitHub官方仓库:https://github.com/plausible/analytics
官方文档地址:https://plausible.io/docs

🔲 ⭐

统计知识(1)

前言

最近为了参加一个数据分析比赛,在复习统计这块的知识。首先什么是统计呢?我采用北京大学出版社出版的《应用经济统计学》中的定义:统计学是一门对群体现象数量特征进行计量描述和分析推论的科学。从定义可以看出统计学主要干两件事一个是对群体现象特征进行计量描述,第二个是对群体数量进行分析得出推论。那我们一件一件事来,先来讲一下统计学干的第一件事:对群体现象特征进行计量描述。本文主要理一下脉络,对于细节不过多纠结(主要是Markdown打公式太麻烦……)

描述集中趋势的计量

算数平均数

  • 简单算数平均数
    简单算数平均数就是一组数据N个数值的和除以N。
  • 加权算数平均数
    每个数值的权重乘以数值的和除以数据个数。

缺点:容易受极端值影响

中位数

将一组数据排序,处在数据中点位置的数值就是中位数。(位置平均数)

优点:稳健,不收极端值影响
缺点:缺乏敏感性,不适合代数运算

众数

一组数据中出现次数最多的数值。

优缺点:和中位数一样

三者的关系

我们知道如果一直增加观察项数,同时又缩小组距,那么分布的直方图就接近一条光滑的曲线。按这条曲线来解释的话,均值是数据分布的平衡点或者说是中心,中位数把这个分布划分为两半,众数
正好是分布顶端的数值。并且在对称分布中三个测度重合,斜分布中三个测度分离。

其他测度

1.分位数
中位数的推广,中位数可以看做二分位数,同样也有四分位数,十分位数,N分位数…
2.几何平均数
变量X的n项观察值x_1,x_2,x_3…x_n的乘积的n次根。
3.调和平均数
一组观测值的倒数的算数平均数的倒数。

描述离中趋势的计量

极差

一组观测数据中最大值与最小值的差。表现数据的变动范围。

平均差

一组数据值与其均值之差的绝对值的和的平均值。

方差

一组资料中各数值与其算数平均数离差平方的平均数。

标准差

标准差的平方就是方差

Chebishev定理

对于任何一组资料,观测值落于均值左右k个标准差的区间内的比例至少为(1-1/k^2).

四分位差

四分位差是第三个四分位值于第一个四分位值之差的二分之一。实际就是一组资料中间一半观测值的极差。

异众比率

非众数值的次数之和占总次数之比重。

平均差系数

平均差于均值之比。

❌