高效的前 10 种多列排序

Posted

技术标签:

【中文标题】高效的前 10 种多列排序【英文标题】:Efficient top 10 sort of multiple columns 【发布时间】:2022-01-06 15:10:15 【问题描述】:

寻找一种从 Linux 服务器上的大型数据集中获取前 3 个(可扩展)数字字段的有效方法;这是后续https://***.com/a/70117001/8823709 的最佳建议

“我有一个 awk 数组,用于聚合和下载字节。我可以 按字节向下或向上对输出进行排序,然后通过管道传送到 谈话最多的人;是否可以使用不同的输出两种类型 钥匙?”

曾经:

zgrep '^1' 20211014T00*.gz|
awk '
    NR > 1 
         key = $1 " " $2
         bytesdown[key] += $3
         bytesup[key] += $4
     
     END 
         cmd = "sort -rn | head -3"
         for ( key in bytesDown ) 
             print bytesDown[key], bytesUp[key], key | cmd
         
         close(cmd)
 
         cmd = "sort -rnk2 | head -3"
         for ( key in bytesDown ) 
             print bytesDown[key], bytesUp[key], key | cmd
         
         close(cmd)
     
'

但是,由于数据集的范围可以从 1000 行到数百万行,而不是将整个集合读入一个数组,排序并丢弃绝大多数,将前 10 位的数组作为数据来维护是否可行被读入了吗?与内存消耗相比,绝对速度不是问题,内存消耗是服务器上相对有限的资源。

例如,给定以下示例输入:

ip1     fqdn101 101     10
ip2     fqdn102 102     11
ip3     fqdn103 103     12
ip4     fqdn104 104     13
ip1     fqdn101 105     14
ip1     fqdn102 106     15
ip1     fqdn103 107     16
ip1     fqdn104 108     17
ip2     fqdn103 109     16
ip2     fqdn104 110     17

应该输出

ip1 fqdn101 206 24
ip2 fqdn104 110 17
ip2 fqdn103 109 16

ip1 fqdn101 206 24
ip2 fqdn104 110 17
ip1 fqdn104 108 17

对 awk 以外的选项开放——尽管这将是我的默认起点——只要它们在我获得的企业 Linux 服务器版本上可用...

【问题讨论】:

日志可以转到数据库而不是平面文件吗? 他们有,但由于政治原因,我无权访问该数据库。我的团队并行存储了 5TB 的压缩日志,这对于抽查来说很好,但意味着需要对更大规模的工作进行分块和聚合。大多数数据都有一条长尾,通常很少/没有兴趣,因此需要高效的 top N 机制。 您能否将平面文件放入您自己的数据库中以避免您的经理应该知道的政治阻碍您的工作? TBH,我没有要求数据库。日志文件的副本需要保持原样,因此数据库需要更强大的服务器和另外 5TB 以上的磁盘。并非不可能,也并非没有重大开销,但作为一种选择。 由于您的文件未排序并且您在运行中添加到您的计数器中,我看不出如何避免跟踪所有 $1 $2 键。事实上,如果在某一时刻,您丢弃了一个计数为 CNT 的条目 ipN fqdnXXX,但您的巨型文件的最后一行将 100 添加到 CNT 并使其再次成为最重要的键之一,您将错过它。您的数据集是否还有另一个您忘记提及的特征可以解决这个问题? 【参考方案1】:

既然您说“绝对速度比内存消耗问题更小”,以下是如何快速执行您想要的操作并使用最少的内存:

$ cat tst.sh
#!/usr/bin/env bash

tmp=$(mktemp) || exit 1
trap 'rm -f "$tmp"; exit' 0

sort -k1,1 -k2,2 "$@:--" |
awk '
     key = $1 " " $2 
    key != prev 
        if ( NR>1 ) 
            print prev, tot3, tot4
        
        tot3 = tot4 = 0
        prev = key
    
    
        tot3 += $3
        tot4 += $4
    
    END 
        print key, tot3, tot4
    
' > "$tmp"

sort -nrk3,3 "$tmp" | head -3
printf '\n'
sort -nrk4,4 "$tmp" | head -3

$ ./tst.sh file
ip1 fqdn101 206 24
ip2 fqdn104 110 17
ip2 fqdn103 109 16

ip1 fqdn101 206 24
ip2 fqdn104 110 17
ip1 fqdn104 108 17

上面唯一需要一次处理整个输入的工具是sort,它被设计为使用请求分页等来处理大文件,因此它不需要能够存储整个输入在内存中才能工作。上面使用的临时文件将比您的原始输入文件小得多,因为它只存储每个密钥对的总数。

如果您不想使用临时文件,那么您可以这样做,这将需要更长的时间(可能是 1.5 倍?)来运行:

$ cat tst.sh
#!/usr/bin/env bash

do1tot() 
    local totPos="$1"
    shift

    sort -k1,1 -k2,2 "$@:--" |
    awk '
         key = $1 " " $2 
        key != prev 
            if ( NR>1 ) 
                print prev, tot3, tot4
            
            tot3 = tot4 = 0
            prev = key
        
        
            tot3 += $3
            tot4 += $4
        
        END 
            print key, tot3, tot4
        
    ' |
    sort -nrk"$totPos,$totPos" |
    head -3


do1tot 3 "$@:--"
printf "\n"
do1tot 4 "$@:--"

$ ./tst.sh file
ip1 fqdn101 206 24
ip2 fqdn104 110 17
ip2 fqdn103 109 16

ip1 fqdn101 206 24
ip2 fqdn104 110 17
ip1 fqdn104 108 17

【讨论】:

谢谢;什么是“$@:--”,非 tmp 文件版本的转变有什么作用?有趣的是,键的默认结尾是行尾。 "$@" 是传递给函数/脚本的参数列表。 "$@:--" 如果存在则使用 args,但如果不存在 (:-) 则使用标准输入 (-) 所以"$@:--" 表示使用提供的任何参数或标准输入,因此函数/脚本可以对文件进行操作或从管道。 shift 是通过作为第一个参数传递给函数的34 位置值,从而只留下"$@" 中的输入文件。我不知道你说的the default end of a key is the end of line是什么意思。 我一直认为排序键是指定的字段,即 -k1 在字段 1 上排序,但请注意它默认包含该行的其余部分;因此 -k1,1 您用来指定排序键的结尾。 是的,没错,-k 采用 start,end 说明符。

以上是关于高效的前 10 种多列排序的主要内容,如果未能解决你的问题,请参考以下文章

八大排序老忘?视图结合高效写出代码(上)!

八大排序老忘?视图结合高效写出代码(下)!

linux分析apache日志获取最多访问的前10个IP

快速排序算法

查询三个集合、对它们进行排序和限制的最高效方法?

如何高效的对有序数组去重