模拟从1亿个ip中访问次数最多的IP--PHP

前提:

  • 存储在一个文件中
  • 内存有限,时间无限。

思路:

  • 采用类似分表的办法,将文件拆分到各个文件中再做统计运算,提高检索速度,分散内存压力
  • 涉及到 ip 的,首先要考虑的就是将 ip 转程长整形,理由是整型省内存,并且支持一般都支持整形索引,比字符串索引速度快
  • 数组存储的数据结构,采用冗余的策略,记录一组数组中重复最多的 ip 和数据
  • 独立小文件胜出的,再和分出来的其他的 N-1 个小文件胜出的比较
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
<?php

/**
* 模拟从1亿个ip中访问次数最多的IP
* 创建文件
* Created by PhpStorm.
* User: admin
* Date: 2017/3/14
* Time: 16:19
*/
class Test
{
const MAX_NUM = 100000000;

const HASH_NUM = 1000;

const FILE_NAME = 'IpFile.txt';

public static $hashIps = [];

public static function generateIp()
{
// 大概15个字节byte
$number = '192.168';
$number = $number . '.' . random_int(0, 255) . '.' . random_int(0, 255);

return $number;
}

public static function generateIpFiles()
{
/**
* fopen 的mode,如果加上b,例如wb的话,是说明强调说明这个文件是二进制文件
* 如果1亿次的话,那就是15byte*1E = 15E byte /1024
* 由于机器性能比较差,文件我只生成了700M的数据。可能只有5千万条数据
*/
$handler = fopen(self::FILE_NAME, 'w');
for ($i = 0; $i < self::MAX_NUM; $i++)
{
$ip = self::generateIp();
fwrite($handler, $ip . PHP_EOL);
}
fclose($handler);
}

/**
* ip转成长整形
*
* @param string $ip
*
* @return int
*/
public static function ipToLong(string $ip)
{
list($position1, $position2, $position3, $position4) = explode('.', $ip);

return ($position1 << 24) | ($position2 << 16) | ($position3 << 8) | ($position4);
}

/**
* 哈希取模
*
* @param string $ip
*
* @return int
*/
public static function hash(string $ip)
{
$longInt = self::ipToLong($ip);

return $longInt % self::HASH_NUM;
}

/**
* 拆分文件
* 考虑:
* 1.1E行数据,一次性加载文件这是不明智的做法,所以我们需要一行一行来读取,也可以理解为缓存读取。
* 2.假如1E个ip,我们直接一行行读写操作的话,需要操作的io就是1E次,所以这个是十分不明智的,所以我们可以根据自身服务器的内存比例来进行划分出预定的数组来保存
*/
public static function divideIpsFiles()
{
$handler = fopen(self::FILE_NAME, 'r');
$count = 0;
while (($ip = fgets($handler)) !== false)
{
$count++;
$hashIp = self::hash($ip);
self::$hashIps[ $hashIp ][] = $ip;

// 当处理了50000万条数据的时候,就写入一次性写入文件。
if ($count == 50000)
{
foreach (self::$hashIps as $key => $hip)
{
$handler2 = fopen($key . '.txt', 'a');
while (($ip = current($hip)) !== false)
{
$byte = fwrite($handler2, $ip);
if ($byte === false)
{
die('Shutdown');
}
next($hip);
}
fclose($handler2);
}
$count = 0;
self::$hashIps = [];
}
}
fclose($handler);
}

public static function calus()
{
$last = [];
for ($i = 0; $i < 1000; $i++)
{
$first = [
'item' => [],
'max' => ['ip' => 0, 'count' => 0]
];
$handler = fopen($i . '.txt', 'r');
while (($ip = fgets($handler)) !== false)
{
if (array_key_exists($ip, $first['item']))
{
$first['item'][ $ip ]++;
} else
{
$first['item'][ $ip ] = 1;
}

if ($first['item'][ $ip ] > $first['max']['count'])
{
$first['max']['ip'] = $ip;
$first['max']['count'] = $first['item'][ $ip ];
}
}
fclose($handler);

$last[ $first['max']['ip'] ] = $first['max']['count'];

unset($first);
}

echo 'IP最多重复的是:' . array_search(($count = max($last)), $last) . ',重复次数为:' . $count;
}

/**
* 移除文件
*/
public static function deleteFiles()
{
for ($i = 0; $i < 1000; $i++)
{
if (file_exists($i . '.txt'))
{
unlink($i . '.txt');
} else
{
return false;
}
}

echo '移除完毕' . PHP_EOL;

return true;
}

}

if (!file_exists(Test::FILE_NAME))
{
Test::generateIpFiles();
}
//
//Test::deleteFiles();
//
//Test::divideIpsFiles();

Test::calus();