本文部分翻译自《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]))
准备工作就绪,接下来可以开始查询工作了。