Redis应用:查找IP所属城市以及国家

本文部分翻译自《Redis in Action》(Josiah L Carlson)。

利用IP定位用户以提供地方化的服务是目前Web的常用做法。使用Redis,我们可以很方便的实现该功能。

对于开发,我们可以从http://dev.maxmind.com/geoip/geolite下载免费的IP数据库。这个数据库包含两个重要的文件:Geo-LiteCity-Blocks.csv和GeoLiteCity-Location.csv,分别为IP段与城市ID的映射以及ID所对应的城市信息(如市、区/周/省、国家名称等)。

我们可以建立两张表,分别对应以上两个csv文件。

第一张表,我们可以将其放入ZSET中并以城市ID为member,以IP为score。当然,这里我们会对IP做一定的转换(即通过a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d转换为一个整数)以达到该目的,具体的代码实现如下:

def ip_to_score(ip_address):
    score = 0
    for v in ip_address.split('.'):
        score = score * 256 + int(v, 10)
    return score
接下来,我们就可以将数据库导入了,因为一个城市会对应多个IP,因此我们可以利用ZSET的特性,记录城市ID对应的第一个IP地址,通过IP范围来定位城市(具体之后来看)。以下是数据导入的代码:
def import_ips_to_redis(conn, filename):
    csv_file = csv.reader(open(filename, 'rb'))
    for count, row in enumerate(csv_file):
        start_ip = row[0] if row else ''
        if 'i' in start_ip.lower():
            continue
        if '.' in start_ip:
            start_ip = ip_to_score(start_ip)
        elif start_ip.isdigit():
            start_ip = int(start_ip, 10)
        else:
            continue
        city_id = row[2] + '_' + str(count)
        conn.zadd('ip2cityid:', city_id, start_ip)
另外一张表当然就是城市ID与详情的映射关系,可以用HASH来实现:

def import_cities_to_redis(conn, filename):
    for row in csv.reader(open(filename, 'rb')):
        if len(row) < 4 or not row[0].isdigit():
            continue
        row = [i.decode('latin-1') for i in row]
        city_id = row[0]
        country = row[1]
        region = row[2]
        city = row[3]
        conn.hset('cityid2city:', city_id,
            json.dumps([city, region, country]))

之前,我们利用ZSET建立了一张城市ID与起始IP地址的对应表。要查询一个IP,我们首先需要使用与之前一样的办法,即将IP转换为10进制整数。之后找到比该IP值相等或较小的最大起始IP。

之前已经提到,之所以用到ZSET,就是方便这里的查询。即我们可以利用ZREVRANGEBYSCORE,通过传递START=0,NUM=1,从而实现预想的查询方法。正确获取城市ID后,我们再利用ID到城市ID与信息映射表(HASH)中查询到对应的城市信息。

具体的实现代码如下:

def find_city_by_ip(conn, ip_address):
    if isinstance(ip_address, str):
        ip_address = ip_to_score(ip_address)
    city_id = conn.zrevrangebyscore(
        'ip2cityid:', ip_address, 0, start=0, num=1)
    if not city_id:
        return None
    city_id = city_id[0].partition('_')[0]
    return json.loads(conn.hget('cityid2city:', city_id))
了解更多,可以参看Josiah L Carlson的《Redis in Action》这本书。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值