搞定資料庫之後, 最後就是如何下查詢條件的問題. 目標只有一個: 給定一個 IP, 查詢該一筆資料出來. 但是可能是以前 RDBMS 資料庫玩太久,觀念一時轉不過來. 一直想寫出這樣的語法:

SELECT *
FROM ip_geo_table
WHERE IP_START <= '8.8.8.8' AND
      IP_END >= '8.8.8.8'

很不幸的,DynamoDB 跟 RDBMS 本質上還是不太一樣的. 在這裡除非整個Table Scan一次才有辦法處理這樣的查詢, 但是20+萬筆全部Scan一次是很慢的, 只好另尋它法.

簡化問題

稍微簡化問題一下, 假如我們有一個相似但是比較簡單的Table如下, 我們輸入數字 25, 那要如何找出正解,也就是20~29這個第二筆?

--

允許的方式只能使用主鍵(前兩個欄位) 以及 Sort Key(num欄位)可供雙向排序. 但是只要這方式組合一下就可以完成查詢.

步驟1: 找出所有小於 25 (或等於)的資料

Query條件設為: partition = 'ipv4' , num1 <=25

--

資料剩下兩筆,很接近答案了

步驟2: 修改排序方向

Query條件設為: partition = 'ipv4' , num1 <=25 ,排序: 倒著排

--

只要取出第一筆,就是正確答案.

查詢

回到正題, 如果指定一個 IP '8.8.8.8' ,也用相同的方式查詢: 首先先把 8.8.8.8 轉成數字 => 134744072 然後使用相同的邏輯進行查詢

--

盲點

然而這個邏輯有個盲點, 當資料是不連續情況 , 例如下圖,拿掉了第二筆 20~29的資料,但是照樣用 25 進去查詢.

--

相同的邏輯,小於等於25的第一筆資料卻是 10~19 的這筆. 這不是我們想要的結果.

但是用程式很容易解決. 只要比對一下 25 是不是在 10~19的區間內,排除掉即可.

補充:

測試了一下, 這份資料庫裡還真的有一些區段是空白的, 原因不明, 免費的資料庫無法要求太多。 不過, 上述的驗證方式看來得實作出來,整趟工程才能算完成.

以下是電腦亂數選的幾個IP ,是在這份GEOIP免費資料庫裡查不到的:

151.219.168.215
102.105.233.217
154.169.7.126
103.177.49.97

Python Query DynamoDB 範例

以下依照上面一樣的邏輯,所做的查詢範例

import socket
import struct
import boto3
from boto3.dynamodb.conditions import Key

DYNAMODB = boto3.resource('dynamodb', region_name='us-west-2')
IPGEO_TABLE = DYNAMODB.Table('ip_geo_table')


def main():

    country = dynamodb_query('8.8.8.8')
    print(country)


def ip2int(addr):
    '''ip to int
    '''
    return struct.unpack("!I", socket.inet_aton(addr))[0]


def dynamodb_query(s_ip):
    '''Query
    '''
    the_ip = ip2int(s_ip)

    response = IPGEO_TABLE.query(
        KeyConditionExpression=Key('partition').eq(
            'ipv4') & Key('num_start').lte(the_ip),
        ScanIndexForward=False,
        Limit=1
    )

    return response

搭配Lambda + API Gateway服務

將上面的範例改寫成 Lambda Function, 前端再掛上 API Gateway服務, 搖身一變就是一個 IP/國家查詢 的 RESTFUL API!

--