最近做一个项目时需要用到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以上