最近做一个项目时需要用到IP地址库, IP地址库这东西用的人应该不少,索性就把代码贴出来方便分离给大家使用。做IP地址库有多种方式,比如直接使用文件来做,也可以使用数据库来做,当然也可以使用内存数据库或缓存来做。前两种实现方式性能比较低,所以我们这里直接使用redis缓存来实现了。这个实现在我的小本子上的性能可以达到了15190QPS以上,已经可以用于生产了。IP地址库使用的是纯真的IP库。不多说了代码如下:
<?php
/**
* @author( author = 'xbruce' )
* @datetime( datetime = '2017年7月2日 下午3:29:44' )
* @comment( comment='ip地址工具' )
*/
class IpUtils
{
private static $_instance = null;
private $_nredis = null;
private $arrIpsTmp = array();
const REDIS_SS_IPS = 'l_ss_ips';
const GREP_IP = "/[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}/";
private function __construct( $redis = null )
{
if( $redis )
{
$this->_nredis = $redis;
}
assert( $redis, 'redis实例不能为空!' );
}
static public function getInstance( $redis = null )
{
assert( $redis, 'redis实例不能为空!' );
if( !$redis )
{
echo 'redis实例不能为空!', PHP_EOL;
return null;
}
if( is_null( self::$_instance ) )
{
self::$_instance = new IpUtils( $redis );
}
return self::$_instance;
}
/**
*
* @author( author='xbruce' )
* @date( date = '2017年7月2日' )
* @comment( comment = '读文件到内存中以方便写查询缓存' )
* @method( method = '' )
* @op( op = '' )
* @param string $strFileName
* @return boolean
*/
public function initIpLibFromFile( string $strFileName = null )
{
set_time_limit( 0 );
ini_set( 'memory_limit', '1024M' );
if( !file_exists( $strFileName ))
{
return false;
}
// $this->removeSetKeys();
$fIp = fopen( $strFileName, 'r' );
if( !$fIp )
{
return false;
}
echo '正在进行缓存请稍后...' . PHP_EOL;
$ip = array();
$iIndex = 0;
while( ($line = trim( fgets( $fIp ))) != '' )
{
$line = mb_convert_encoding( $line, 'UTF8', 'GBK' );
preg_match_all( "/[^\s]+/", $line, $arrMatches );
if( count( $arrMatches[0] ) < 3 )
{
return false;
}
$arrIpInfo = $arrMatches[0];
$ip['ip_begin'] = $this->ip2longCust( $arrIpInfo[0] );
$ip['ip_end'] = $this->ip2longCust( $arrIpInfo[1] );
$ip['info'] = '';
$key = $arrIpInfo[2];
$iInfoCnt = count( $arrIpInfo );
for( $i = 2; $i < $iInfoCnt; ++$i )
{
$ip['info'] .= ' ' . $arrIpInfo[$i];
}
$strKey = md5( $key );
$this->arrIpsTmp[ $strKey ][$ip['ip_begin']] = $ip;
unset($ip);
}
fclose( $fIp );
echo '写缓存结束!' . PHP_EOL;
return true;
}
/**
*
* @author( author='xbruce' )
* @date( date = '2017年7月2日' )
* @comment( comment = '' )
* @method( method = '' )
* @op( op = '' )
*/
public function combineAdjacentIp()
{
ini_set( 'memory_limit', '1024M' );
$arrDupIps = array();
$arrKeys = $this->arrIpsTmp;
$iIndex = 0;
foreach( $arrKeys as $v )
{
$arrIps = $v;
foreach( $arrIps as $ip )
{
$arrDupIps[ $ip['ip_begin'] ] = $ip;
}
unset( $arrIps );
if( uksort( $arrDupIps, function( $left, $right ){
if( $left < $right )
{
return -1;
}
else if( $left == $right )
{
return 0;
}
else
{
return 1;
}
}))
{
$iCnt = 0;
$arrTemp = array();
foreach( $arrDupIps as $key => $val )
{
$arrTemp[$iCnt++] = $val;
}
unset( $arrDupIps );
$iTargetIndex = 0;
$curIp = $arrTemp[0];
if( $iCnt > 1 )
{
for( $i = 0; $i < $iCnt; ++$i )
{
if( $i + 1 < $iCnt && ( $curIp['ip_end'] + 1 == $arrTemp[ $i + 1 ]['ip_begin'] ))
{
$curIp['ip_end'] = $arrTemp[ $i + 1 ]['ip_end'];
}
else
{
++$iIndex;
$this->_nredis->zAdd( self::REDIS_SS_IPS, $curIp['ip_begin'], json_encode( $curIp ));
if( $i + 1 < $iCnt )
{
$curIp = $arrTemp[$i+1];
}
}
}
}
else if( 1 == $iCnt )
{
$this->_nredis->zAdd( self::REDIS_SS_IPS, $curIp['ip_begin'], json_encode( $curIp ));
++$iIndex;
}
unset( $arrTargets );
unset( $arrTemp );
echo '已处理到第', $iIndex, '个', PHP_EOL;
}
else
{
echo '排序失败,请检查错误原因!', PHP_EOL;
exit();
}
}
echo PHP_EOL, '成功精简IP地址库!', PHP_EOL;
}
/**
*
* @author( author='xbruce' )
* @date( date = '2017年7月2日' )
* @comment( comment = '取IP地址相关信息' )
* @method( method = '' )
* @op( op = '' )
* @param string $strIp
*/
public function getIpLocation( string $strIp = null )
{
if( empty( $strIp ) || ( $strIp = trim( $strIp )) == '' )
{
return false;
}
$lIp = $this->ip2longCust( $strIp );
$r = $this->_nredis->zRevRangeByScore( self::REDIS_SS_IPS, $lIp, -1, array( 'limit' => array( 0, 1 )));
if( count( $r ) >= 1 )
{
$objIp = json_decode( $r[0] );
if( $objIp->ip_begin <= $lIp && $lIp <= $objIp->ip_end )
{
return $objIp;
}
return false;
}
return false;
}
public function test()
{
return $this->getIpLocation( '8.8.8.8');
}
/**
* @author( author='xbruce' )
* @date( date = '2017年7月2日' )
* @comment( comment = '解决因ip2long bug而引起的bug' )
* @method( method = '' )
* @op( op = '' )
* @param string $strIp
*/
private function ip2longCust( string $strIp )
{
if( preg_match( self::GREP_IP, $strIp ) && is_string( $strIp ) )
{
$arrIps = explode( '.', $strIp );
$strIpTmp = '';
foreach( $arrIps as $k => $v )
{
$strIpTmp .= intval( $v ) . '.';
}
$strIpTmp = rtrim( $strIpTmp, '\.' );
return ip2long( $strIpTmp );
}
return false;
}
}
// $redis = new Redis();
$redis = new Redis();
$redis->connect( '127.0.0.1', 6379 );
$redis->auth( 'pass' );//redis requirepass
// $redis->select( 0 );
$iu = IpUtils::getInstance( $redis );
// var_dump( $iu->initIpLibFromFile( '/git/phcms/czip.txt' ) );//初始化文件到临时文件
// $iu->combineAdjacentIp();//合并同一地的相邻IP
$t = microtime( true );
for( $i = 0; $i < 100000; ++$i )
{
$objRes = $iu->test();
}
echo microtime( true ) - $t;
//在我的配置很低小本子上可以达到15190QPS以上