PHP 如何高效地根据 IP 获得城市?

请设计一个实现方式,可以给某个ip找到对应的省和市,要求效率尽可能的高。

答案

可以用 Redis 的有序集合(Sorted Set)实现,将 IP 保存为整形作为集合的score,再根据 score 范围查找。

  1. /**
  2. * 时间复杂度: O(log(N)+M),N为有序集的基数,M为被结果集的基数。
  3. */
  4. class Ip
  5. {
  6. private $redis = null;
  7. private $key = 'ip';
  8. public function __construct()
  9. {
  10. $this->redis = new Redis();
  11. $this->redis->connect('127.0.0.1');
  12. }
  13. /**
  14. * 添加城市和最大最小IP,其中最小ip的城市名称前要加“#”作为前缀
  15. * @param $cityName
  16. * @param $ip
  17. */
  18. public function addIp($cityName, $ip)
  19. {
  20. $this->redis->zAdd($this->key, ip2long($ip), $cityName);
  21. }
  22. function getCity($ip)
  23. {
  24. // 从 score 为IP到正无穷之间获取一个值
  25. $city = $this->redis->zRangeByScore(
  26. $this->key,
  27. ip2long($ip),
  28. '+inf',
  29. array('limit' => array(0, 1))
  30. );
  31. if ($city) {
  32. if (strpos($city[0], "#") === 0) {
  33. return false;
  34. } else {
  35. return $city[0];
  36. }
  37. } else {
  38. return false;
  39. }
  40. }
  41. }

答案解析

  1. $ip = new Ip();
  2. $ip->addIp('#bj', '1.1.1.0');
  3. $ip->addIp('bj', '1.1.1.254');
  4. $ip->addIp('#sh', '2.2.2.0');
  5. $ip->addIp('sh', '2.2.2.254');
  6. var_dump($ip->getCity('0.0.0.50')); // 内部 $city = ['#bj'], 返回 false
  7. var_dump($ip->getCity('1.1.1.50')); // 内部 $city = ['bj'], 返回 true
  8. var_dump($ip->getCity('2.2.2.50')); // 内部 $city = ['sh'], 返回 true
  9. var_dump($ip->getCity('3.3.3.50')); // 内部 $city = false, 返回 false