互联网中对象访问频率的91分布
在互联网领域, 流行着这么一句话:
90%的流量由10%的内容产生.
缓存也由此产生: 只为最频繁访问的10%的内容提供更快的存储, 就可以以很低的成本提供尽可能好的服务质量.
一般符合这种互联网访问模型的曲线是下图这样的. 对每个访问的url做独立计数, 并按照从访问最多到最低排序:
这句是一个经验结论, 从它可以得出我们的频度分布公式: 也就是zipf 模型.
\[f(k) = c/k^s\]这个公式很好, 好就好在可以直接对其左右两边取对数后, 直接转换成了线性关系:
\[log(f(k)) = log(c) - s \times log(k)\]即: k的对数跟y的对数呈现线性关系. 线性太棒了, 简单又好用!
用这个公式我们可以更好的了解和控制数据的访问模型, 做更多的事情. 以下是得出这个结论的愉快的推倒过程.
推倒
首先我们假设, 上面那个比值是p(10%或1%或其他, 根据业务不同而不同),
k个文件中, 最热的pk
个文件的访问占比是(1-p)
,
我们首先假设存在这样一个函数来描述文件热度到文件访问频率的关系:
y=f(k)
那么根据上面的统计规律, 这个函数应该满足这样一个方程:
\[\int_{0}^{pk}f(x) = (1-p)\int_{0}^{k}f(x)\]因为要得到f(k)的函数, 所以对两边对k求个导:
\[p f(pk) = (1-p)f(k)\]好了, 这样一个关系构成了一个数列的递推式. 如果我们先选择曲线上的一个点(k₀, y₀) 满足 $ y_0 = f(k_0) $, 那么根据上面的递推式很容易得到一组都在曲线上的点(kᵢ, yᵢ):
\[k_i = k_0 p^{i-1} \\ y_i = y_0 (\frac{1-p}{p})^{i-1}\]对2个方程都取个对数消去两组数列中的i, 得到kᵢ, yᵢ的关系:
\[\lg y_i = \frac {\lg \frac{1-p}{p}} {\lg p} \lg k_i + (\lg y_0 - \frac{\lg \frac{1-p}{p}}{\lg p} \lg k_0)\]我们再对两边取下指数就可以看到: 基于(k₀, y₀) 选择的这组点是满足 $y = c/k^s$ 的形式. 目前我们已经可以确定曲线上有一组点集是符合我们的频率分布公式的了, 剩下的点再接着处理一下:
不难看出通过选择不同的(k₀, y₀), 我们可以列出曲线上所有的点, 并且我们还可以看到, 选择不同的(k₀, y₀), 只会性影响这里c的值, 而不会影响a的值. a的值只跟最初选择的p相关. 又因为我们假设整个曲线是平滑的, 直观上就可以得出结论:
所有不同的(k₀, y₀) 确定的曲线的c值是一样的(否则距离很近的2个k₀可以反例出不可导的情况.), 也就是说所有点都在一条$y = c/k^s$的曲线上.
在大量的数据统计中s
的值不是一个常量, 在比较热的访问中(k比较小的区域),
s
一般在0到1之间,
在后面长尾部分, s
一般会大于1.
如何将访问计数转换成对应的zipf分布的公式
想要用公式形式$f(k) = c/k^s$替代原来的描点描出的曲线, 先要确定式子里面s
和c
的值
这也很简单, 拿到一段访问日志后, 首先统计独立url计数, 然后 通过多项式回归拟合一条直线
例如我们使用预先准备好的url计数文件 file-access-count.txt 作为例子, 使用这个 find-zipf.py 脚本来拟合:
python2 find-zipf.py
6.796073 - 0.708331x
这样我们就从日志中得到了它的zipf 曲线公式:
\[log(f(k)) = 6.796073 - 0.708331 \times log(k)\]整理成原来的样子, 第k热的对象被访问的次数是:
\[f(k) = \frac {894} {k^{0.708331}}\]标准定义
zipf在标准定义中用全量的相对占比来描述一个对象的访问频率:
\[f(k;s,N)={\frac {1/k^{s}}{\sum \limits_{n=1}^{N}(1/n^{s})}}\]Zipf’s law then predicts that out of a population of N elements, the normalized frequency of elements of rank k, f(k;s,N), is:
有什么用
- 合理配置缓存容量以使缓存成本和性能之间做一个准确的把控.
- 在大量日志分析时, 通过合理近似降低计算量.
- 多了解一些东西. 说不定什么时候就用上了.
我一直觉得, 借来100年的肉身存在在这个世界上的意义, 无非是这3个事儿:
Discover, Design, Distribute
- Discover: 学习和探索, 找到新的东西;
- Design: 如果发现了什么新的东西, 就把它设计出来和实现出来;
- Distribute: 如果实现了什么新的东西, 那就把它带给其他人, 去让他们的生活变得更好.
Archive
- 15 Nov 2020 slimarray: gzip的压缩率, 即时访问
- 28 Oct 2020 200行代码实现基于paxos的kv存储
- 18 Oct 2020 后分布式时代: 多数派读写的'少数派'实现
- 20 Dec 2019 Art of Pull Requests(翻译)
- 21 Nov 2019 掐指算算: 你的CDN多花了几百万?
- 19 Nov 2019 一年的素描练习
- 30 Oct 2019 互联网中对象访问频率的91分布
- 09 Jan 2019 哄好面试官系列-1: 比较2个python dict(多级)是否相同
- 04 Nov 2018 存储中的文件合并策略优化
- 27 Sep 2018 软件工程是个面包机
- 26 Aug 2018 程序员必须知道的事情, 一般人我不告诉他
- 16 Aug 2018 cgexec 无法继承 LD_PRELOAD 环境变量
- 04 Aug 2018 mysql group replication实践记录: 步骤, 问题和注意事项
- 13 Feb 2018 枚举所有整勾股数
- 03 Feb 2018 ansible中的include, include_tasks 和 import_tasks 的差别
- 20 Nov 2017 python 并发subprocess.Popen的坑
- 05 Aug 2017 程序员必读: 摸清hash表的脾性
- 06 May 2017 python 进程内存增长问题, 解决方法和工具
- 01 Feb 2017 xp的分布式系统系列教程之: Erasure-Code: 工作原理, 数学解释, 实践和分析.
- 01 Feb 2017 xp的分布式系统系列教程之: Erasure-Code: 工作原理, 数学解释, 实践和分析.
- 11 Nov 2015 可靠分布式系统基础 Paxos 的直观解释
- 28 Jul 2015 socket关闭: close()和shutdown()的差异
- 17 May 2015 随手改变世界之 git-auto-squash
- 17 Feb 2015 Numbers Programmers Should Know About Hash
- 11 Feb 2015 Vim-tabbar: Simple, stupid and fast tab-bar for VIM
- 24 Jul 2014 1% 慢请求优化
- 31 Jan 2014 Some useful resources
- 31 Jan 2014 jobq.py -- Queue processing engine