ビッグデータプラットフォームの発展に伴い、非構造化データや半構造化データなど、さまざまな種類のデータを処理できるようになりました。中でも、IP アドレスを所在地に変換することは、よくあるシナリオです。本稿では、MaxCompute のユーザー定義関数 (UDF) を使用して IPv4 または IPv6 アドレスを所在地に変換する方法について説明します。
背景情報
IPv4 または IPv6 アドレスを所在地に変換するには、IP アドレスライブラリが必要です。IP アドレスライブラリファイルをダウンロードし、リソースとして MaxCompute プロジェクトにアップロードする必要があります。MaxCompute UDF を開発し、IP アドレスライブラリファイルに基づいて関数を登録することで、SQL ステートメントで関数を呼び出して IP アドレスを所在地に変換できます。
この記事で提供する IP アドレスライブラリファイルは、このベストプラクティスを検証するためにのみ使用されます。実際のビジネス状況に合わせて、IP アドレスライブラリファイルをご自身で管理してください。
前提条件
-
odpscmd のインストールと設定が完了していること。
-
MaxCompute Studio のインストールが完了していること。
操作手順
MaxCompute UDF を使用して IPv4 または IPv6 アドレスを所在地に変換する手順は、次のとおりです。
-
ステップ 1:IP アドレスライブラリファイルのアップロード
IP アドレスライブラリファイルをリソースとして MaxCompute プロジェクトにアップロードします。後ほど作成する MaxCompute UDF は、このリソースに依存します。
-
MaxCompute プロジェクトに接続し、MaxCompute Java モジュールを作成します。
-
IntelliJ IDEA で MaxCompute UDF コードを作成します。
-
MaxCompute UDF を関数として登録します。
-
ステップ 5:MaxCompute UDF の呼び出しによる IP アドレスの所在地変換
SQL ステートメントで登録した関数を呼び出して、IP アドレスを所在地に変換します。
ステップ 1:IP アドレスライブラリファイルのアップロード
-
IP アドレスライブラリファイルをローカルにダウンロードして解凍し、ipv4.txt と ipv6.txt を取得して、MaxCompute クライアントのインストールディレクトリ
...\odpscmd_public\binに配置します。 -
MaxCompute クライアントをインストールしてログオンし、目的の MaxCompute プロジェクトに移動します。
-
add fileコマンドを実行して、ipv4.txt と ipv6.txt を File タイプのリソースとして MaxCompute プロジェクトにアップロードします。ADD file ipv4.txt -f; ADD file ipv6.txt -f;リソースの追加に関する詳細については、「リソースの管理」をご参照ください。
-
(ローカルデバッグ用) ipv4.txt と ipv6.txt をローカルプロジェクトの
warehouse/example_project/_resources_ディレクトリにコピーします。
ステップ 2:プロジェクトへの接続
-
MaxCompute プロジェクトに接続します。詳細な手順については、「プロジェクト接続の管理」をご参照ください。
-
MaxCompute Java モジュールを作成します。詳細な手順については、「MaxCompute Java モジュールの作成」をご参照ください。
ステップ 3:MaxCompute UDF の作成
-
Java Class オブジェクトを作成します。
後続のステップで記述する MaxCompute UDF コードでは、ここで作成した Java Class を使用します。
-
IntelliJ IDEA の画面で、[Project] 領域のモジュールのソースディレクトリ (つまり src > main > java) を右クリックし、[New] > [Java Class] を選択します。
-
[New Java Class] ダイアログボックスでクラス名を入力し、Enter キーを押してコード編集領域にコードを入力します。
3 つの Java Class オブジェクトを順番に作成する必要があります。クラス名と対応するコードは次のとおりです。コードは変更せずに、そのままコピーして使用できます。
-
IpUtils
package com.aliyun.odps.udf.utils; import java.math.BigInteger; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; public class IpUtils { /** * 文字列形式の IP アドレスを long に変換します * * @param ipInString * 文字列形式の IP アドレス * @return long 形式の IP アドレスを返します */ public static long StringToLong(String ipInString) { ipInString = ipInString.replace(" ", ""); byte[] bytes; if (ipInString.contains(":")) bytes = ipv6ToBytes(ipInString); else bytes = ipv4ToBytes(ipInString); BigInteger bigInt = new BigInteger(bytes); // System.out.println(bigInt.toString()); return bigInt.longValue(); } /** * 文字列形式の IP アドレスを long に変換します * * @param ipInString * 文字列形式の IP アドレス * @return BigInt の文字列形式の IP アドレス */ public static String StringToBigIntString(String ipInString) { ipInString = ipInString.replace(" ", ""); byte[] bytes; if (ipInString.contains(":")) bytes = ipv6ToBytes(ipInString); else bytes = ipv4ToBytes(ipInString); BigInteger bigInt = new BigInteger(bytes); return bigInt.toString(); } /** * 整数形式の IP アドレスを文字列形式に変換します * * @param ipInBigInt * 整数形式の IP アドレス * @return 文字列形式の IP アドレス */ public static String BigIntToString(BigInteger ipInBigInt) { byte[] bytes = ipInBigInt.toByteArray(); byte[] unsignedBytes = Arrays.copyOfRange(bytes, 1, bytes.length); // 符号ビットを削除 try { String ip = InetAddress.getByAddress(unsignedBytes).toString(); return ip.substring(ip.indexOf('/') + 1).trim(); } catch (UnknownHostException e) { throw new RuntimeException(e); } } /** * IPv6 アドレスを符号付き byte[17] に変換 */ private static byte[] ipv6ToBytes(String ipv6) { byte[] ret = new byte[17]; ret[0] = 0; int ib = 16; boolean comFlag = false;// IPv4 混合モードのフラグ if (ipv6.startsWith(":"))// 先頭のコロンを削除 ipv6 = ipv6.substring(1); String groups[] = ipv6.split(":"); for (int ig = groups.length - 1; ig > -1; ig--) {// 逆方向にスキャン if (groups[ig].contains(".")) { // IPv4 混合モードが出現 byte[] temp = ipv4ToBytes(groups[ig]); ret[ib--] = temp[4]; ret[ib--] = temp[3]; ret[ib--] = temp[2]; ret[ib--] = temp[1]; comFlag = true; } else if ("".equals(groups[ig])) { // ゼロ圧縮が出現、欠落しているグループ数を計算 int zlg = 9 - (groups.length + (comFlag ? 1 : 0)); while (zlg-- > 0) {// これらのグループを 0 に設定 ret[ib--] = 0; ret[ib--] = 0; } } else { int temp = Integer.parseInt(groups[ig], 16); ret[ib--] = (byte) temp; ret[ib--] = (byte) (temp >> 8); } } return ret; } /** * IPv4 アドレスを符号付き byte[5] に変換 */ private static byte[] ipv4ToBytes(String ipv4) { byte[] ret = new byte[5]; ret[0] = 0; // まず IP アドレス文字列内の「.」の位置を検索 int position1 = ipv4.indexOf("."); int position2 = ipv4.indexOf(".", position1 + 1); int position3 = ipv4.indexOf(".", position2 + 1); // 各ドット間の文字列を整数に変換 ret[1] = (byte) Integer.parseInt(ipv4.substring(0, position1)); ret[2] = (byte) Integer.parseInt(ipv4.substring(position1 + 1, position2)); ret[3] = (byte) Integer.parseInt(ipv4.substring(position2 + 1, position3)); ret[4] = (byte) Integer.parseInt(ipv4.substring(position3 + 1)); return ret; } /** * @param ipAdress IPv4 または IPv6 文字列 * @return 4:IPv4、6:IPv6、0:不正なアドレス * @throws Exception */ public static int isIpV4OrV6(String ipAdress) throws Exception { InetAddress address = InetAddress.getByName(ipAdress); if (address instanceof Inet4Address) return 4; else if (address instanceof Inet6Address) return 6; return 0; } /* * IP が特定の IP 範囲に属するかどうかを検証する * * ipSection: IP 範囲 ('-' で区切られた) * * ip: 検証対象の IP アドレス */ public static boolean ipExistsInRange(String ip, String ipSection) { ipSection = ipSection.trim(); ip = ip.trim(); int idx = ipSection.indexOf('-'); String beginIP = ipSection.substring(0, idx); String endIP = ipSection.substring(idx + 1); return getIp2long(beginIP) <= getIp2long(ip) && getIp2long(ip) <= getIp2long(endIP); } public static long getIp2long(String ip) { ip = ip.trim(); String[] ips = ip.split("\\."); long ip2long = 0L; for (int i = 0; i < 4; ++i) { ip2long = ip2long << 8 | Integer.parseInt(ips[i]); } return ip2long; } public static long getIp2long2(String ip) { ip = ip.trim(); String[] ips = ip.split("\\."); long ip1 = Integer.parseInt(ips[0]); long ip2 = Integer.parseInt(ips[1]); long ip3 = Integer.parseInt(ips[2]); long ip4 = Integer.parseInt(ips[3]); long ip2long = 1L * ip1 * 256 * 256 * 256 + ip2 * 256 * 256 + ip3 * 256 + ip4; return ip2long; } public static void main(String[] args) { System.out.println(StringToLong("2002:7af3:f3be:ffff:ffff:ffff:ffff:ffff")); System.out.println(StringToLong("54.38.XX.XX")); } private class Invalid{ private Invalid() { } } } -
IpV4Obj
package com.aliyun.odps.udf.objects; public class IpV4Obj { public long startIp ; public long endIp ; public String city; public String province; public IpV4Obj(long startIp, long endIp, String city, String province) { this.startIp = startIp; this.endIp = endIp; this.city = city; this.province = province; } @Override public String toString() { return "IpV4Obj{" + "startIp=" + startIp + ", endIp=" + endIp + ", city='" + city + '\'' + ", province='" + province + '\'' + '}'; } public void setStartIp(long startIp) { this.startIp = startIp; } public void setEndIp(long endIp) { this.endIp = endIp; } public void setCity(String city) { this.city = city; } public void setProvince(String province) { this.province = province; } public long getStartIp() { return startIp; } public long getEndIp() { return endIp; } public String getCity() { return city; } public String getProvince() { return province; } } -
IpV6Obj
package com.aliyun.odps.udf.objects; public class IpV6Obj { public String startIp ; public String endIp ; public String city; public String province; public String getStartIp() { return startIp; } @Override public String toString() { return "IpV6Obj{" + "startIp='" + startIp + '\'' + ", endIp='" + endIp + '\'' + ", city='" + city + '\'' + ", province='" + province + '\'' + '}'; } public IpV6Obj(String startIp, String endIp, String city, String province) { this.startIp = startIp; this.endIp = endIp; this.city = city; this.province = province; } public void setStartIp(String startIp) { this.startIp = startIp; } public String getEndIp() { return endIp; } public void setEndIp(String endIp) { this.endIp = endIp; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } }
-
-
-
MaxCompute UDF コードを作成します。
-
[Project] 領域で、モジュールのソースディレクトリ (つまり src > main > java) を右クリックし、[New] > [MaxCompute Java] を選択します。
-
[Create new MaxCompute java class] ダイアログボックスで、[UDF] をクリックし、[Name] を入力してから Enter キーを押し、コード編集領域にコードを入力します。
たとえば、Java Class 名は IpLocation です。コードの内容は次のとおりです。コードは変更せずに、そのままコピーして使用できます。
package com.aliyun.odps.udf.udfFunction; import com.aliyun.odps.udf.ExecutionContext; import com.aliyun.odps.udf.UDF; import com.aliyun.odps.udf.UDFException; import com.aliyun.odps.udf.utils.IpUtils; import com.aliyun.odps.udf.objects.IpV4Obj; import com.aliyun.odps.udf.objects.IpV6Obj; import java.io.*; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; public class IpLocation extends UDF { public static IpV4Obj[] ipV4ObjsArray; public static IpV6Obj[] ipV6ObjsArray; public IpLocation() { super(); } @Override public void setup(ExecutionContext ctx) throws UDFException, IOException { //IPV4 if(ipV4ObjsArray==null) { BufferedInputStream bufferedInputStream = ctx.readResourceFileAsStream("ipv4.txt"); BufferedReader br = new BufferedReader(new InputStreamReader(bufferedInputStream)); ArrayList<IpV4Obj> ipV4ObjArrayList=new ArrayList<>(); String line = null; while ((line = br.readLine()) != null) { String[] f = line.split("\\|", -1); if(f.length>=5) { long startIp = IpUtils.StringToLong(f[0]); long endIp = IpUtils.StringToLong(f[1]); String city=f[3]; String province=f[4]; IpV4Obj ipV4Obj = new IpV4Obj(startIp, endIp, city, province); ipV4ObjArrayList.add(ipV4Obj); } } br.close(); List<IpV4Obj> collect = ipV4ObjArrayList.stream().sorted(Comparator.comparing(IpV4Obj::getStartIp)).collect(Collectors.toList()); ArrayList<IpV4Obj> basicIpV4DataList=(ArrayList)collect; IpV4Obj[] ipV4Objs = new IpV4Obj[basicIpV4DataList.size()]; ipV4ObjsArray = basicIpV4DataList.toArray(ipV4Objs); } //IPV6 if(ipV6ObjsArray==null) { BufferedInputStream bufferedInputStream = ctx.readResourceFileAsStream("ipv6.txt"); BufferedReader br = new BufferedReader(new InputStreamReader(bufferedInputStream)); ArrayList<IpV6Obj> ipV6ObjArrayList=new ArrayList<>(); String line = null; while ((line = br.readLine()) != null) { String[] f = line.split("\\|", -1); if(f.length>=5) { String startIp = IpUtils.StringToBigIntString(f[0]); String endIp = IpUtils.StringToBigIntString(f[1]); String city=f[3]; String province=f[4]; IpV6Obj ipV6Obj = new IpV6Obj(startIp, endIp, city, province); ipV6ObjArrayList.add(ipV6Obj); } } br.close(); List<IpV6Obj> collect = ipV6ObjArrayList.stream().sorted(Comparator.comparing(IpV6Obj::getStartIp)).collect(Collectors.toList()); ArrayList<IpV6Obj> basicIpV6DataList=(ArrayList)collect; IpV6Obj[] ipV6Objs = new IpV6Obj[basicIpV6DataList.size()]; ipV6ObjsArray = basicIpV6DataList.toArray(ipV6Objs); } } public String evaluate(String ip){ if(ip==null||ip.trim().isEmpty()||!(ip.contains(".")||ip.contains(":"))) { return null; } int ipV4OrV6=0; try { ipV4OrV6= IpUtils.isIpV4OrV6(ip); } catch (Exception e) { return null; } //IPv4 の場合 if(ipV4OrV6==4) { int i = binarySearch(ipV4ObjsArray, IpUtils.StringToLong(ip)); if(i>=0) { IpV4Obj ipV4Obj = ipV4ObjsArray[i]; return ipV4Obj.city+","+ipV4Obj.province; }else{ return null; } }else if(ipV4OrV6==6)//IPv6 の場合 { int i = binarySearchIPV6(ipV6ObjsArray, IpUtils.StringToBigIntString(ip)); if(i>=0) { IpV6Obj ipV6Obj = ipV6ObjsArray[i]; return ipV6Obj.city+","+ipV6Obj.province; }else{ return null; } }else{//IPv4 または IPv6 形式でない場合 return null; } } @Override public void close() throws UDFException, IOException { super.close(); } private static int binarySearch(IpV4Obj[] array,long ip){ int low=0; int hight=array.length-1; while (low<=hight) { int middle=(low+hight)/2; if((ip>=array[middle].startIp)&&(ip<=array[middle].endIp)) { return middle; } if (ip < array[middle].startIp) hight = middle - 1; else { low = middle + 1; } } return -1; } private static int binarySearchIPV6(IpV6Obj[] array,String ip){ int low=0; int hight=array.length-1; while (low<=hight) { int middle=(low+hight)/2; if((ip.compareTo(array[middle].startIp)>=0)&&(ip.compareTo(array[middle].endIp)<=0)) { return middle; } if (ip.compareTo(array[middle].startIp) < 0) hight = middle - 1; else { low = middle + 1; } } return -1; } private class Invalid{ private Invalid() { } } }
-
-
ローカルデバッグ用のデータを準備します。
-
ローカルプロジェクトの
warehouse/example_project/__tables__/wc_in2/p1=2/p2=1/ディレクトリで、[data] ファイルを開きます。 -
[data] ファイルの最後の列のデータを ipv4.txt 内の IP アドレスに変更して (ipv4.txt から任意の 3 つの IP アドレスを選択して入力できます)、保存します。
-
-
MaxCompute UDF をデバッグして、コードが正常に実行できることを確認します。
デバッグ操作の詳細については、「ローカル実行による UDF のデバッグ」をご参照ください。
-
作成した MaxCompute UDF スクリプトを右クリックし、[Run] を選択します。
-
[Run/Debug Configurations] ダイアログボックスで、実行パラメーターを設定して、[OK] をクリックします。
実行パラメーターの設定例:[Name] を
IpLocationに、[Main class] をcom.aliyun.odps.udf.udfFunction.IpLocationに設定します。MaxCompute 固有のフィールド:[*MaxCompute project] でlocalとexample_projectを選択し、[*MaxCompute table] にwc_in2を入力し、[*Table partition] にp1=2,p2=1(形式:p1=1,p2=2) を入力し、[*Table columns] にcolc(形式:c1,c2) を入力します。[Download Record limit] を100に、[Data Column Separator] をカンマに設定します。設定が完了したら、[OK] をクリックします。エラーが返されなければ、コードの実行は成功です。後続のステップに進むことができます。エラーが発生した場合は、IntelliJ IDEA のエラーメッセージに従って対処してください。説明実行パラメーターは、図のデータを参照して入力できます。
-
ステップ 4:MaxCompute UDF の登録
-
コンパイルに成功した MaxCompute UDF スクリプトを右クリックして、[Deploy to server…] を選択します。
-
[Package a jar, submit resource and register function] ダイアログボックスで、パラメーター情報を設定します。
パラメーターの詳細については、「パッケージ化、アップロード、登録」をご参照ください。[MaxCompute project] で対象のプロジェクト (例:
doc_test_dev) を選択し、[Resource file] でパッケージ化された JAR ファイルを選択します。[Resource name] は自動的にudf-1.0-SNAPSHOT.jarと入力されます。[Extra resources] で必要な追加リソースファイル (例:ipv4.txtとipv6.txt) を選択し、[Main class] に UDF メインクラスの完全修飾名 (例:com.aliyun.odps.udf.udfFunction.IpLocation) を入力し、[Function name] に関数名 (例:ipv4_ipv6_aton) を入力します。[Force update if already exists] にチェックを入れてから [OK] をクリックします。[Extra resources] では、ステップ 1 でアップロードした IP アドレスライブラリファイル ipv4.txt および ipv6.txt を選択する必要があります。登録する関数名はipv4_ipv6_atonとします。
ステップ 5:MaxCompute UDF の呼び出しによる IP アドレスの所在地変換
-
SQL SELECT ステートメントを実行して MaxCompute UDF を呼び出し、IPv4 または IPv6 アドレスを所在地に変換します。
コマンドの例は次のとおりです。
-
IPv4 アドレスの所在地への変換
select ipv4_ipv6_aton('116.11.XX.XX');次のような結果が返されます。
北海市,广西壮族自治区 -
IPv6 アドレスの所在地への変換
select ipv4_ipv6_aton('2001:0250:080b:0:0:0:0:0');次のような結果が返されます。
保定市,河北省
-