Seiring perkembangan platform big data, kini tersedia kemampuan untuk memproses berbagai jenis data tidak terstruktur dan semi-terstruktur. Salah satu skenario umum adalah mengonversi alamat IP menjadi lokasi geografisnya. Artikel ini menjelaskan cara menggunakan MaxCompute UDF untuk mengonversi alamat IPv4 atau IPv6 menjadi lokasi geografis.
Latar Belakang
Untuk mengonversi alamat IPv4 atau IPv6 menjadi lokasi geografis, Anda memerlukan database alamat IP. File database tersebut harus diunduh dan diunggah ke proyek MaxCompute sebagai resource. Setelah itu, Anda perlu mengembangkan MaxCompute UDF dan mendaftarkan fungsi berdasarkan file database IP tersebut sehingga dapat dipanggil dalam pernyataan SQL untuk mengonversi alamat IP menjadi lokasi geografisnya.
File database alamat IP yang disediakan dalam artikel ini hanya digunakan untuk memverifikasi praktik terbaik ini. Harap sesuaikan dan kelola sendiri file database alamat IP sesuai dengan kebutuhan bisnis Anda.
Prasyarat
-
Telah Install MaxCompute Studio.
Alur Operasi
Alur operasi untuk mengonversi alamat IPv4 atau IPv6 menjadi lokasi geografis menggunakan MaxCompute UDF adalah sebagai berikut:
-
Langkah 1: Unggah file database alamat IP
Unggah file database alamat IP sebagai resource ke proyek MaxCompute. MaxCompute UDF yang akan dibuat selanjutnya bergantung pada resource ini.
-
Langkah 2: Buat koneksi proyek
Koneksikan ke proyek MaxCompute dan buat MaxCompute Java Module.
-
Langkah 3: Tulis kode MaxCompute UDF
Tulis kode MaxCompute UDF di IntelliJ IDEA.
-
Langkah 4: Daftarkan MaxCompute UDF
Daftarkan MaxCompute UDF sebagai fungsi.
-
Langkah 5: Panggil MaxCompute UDF untuk mengonversi alamat IP menjadi lokasi geografis
Panggil fungsi yang telah didaftarkan dalam pernyataan SQL untuk mengonversi alamat IP menjadi lokasi geografisnya.
Langkah 1: Unggah file database alamat IP
-
Unduh file database alamat IP ke lokal, ekstrak untuk mendapatkan ipv4.txt dan ipv6.txt, lalu letakkan di direktori instalasi klien MaxCompute
...\odpscmd_public\bin. -
Install and log on to the MaxCompute client, lalu masuk ke proyek MaxCompute target.
-
Jalankan perintah
add fileuntuk mengunggah ipv4.txt dan ipv6.txt sebagai resource tipe File ke proyek MaxCompute.ADD file ipv4.txt -f; ADD file ipv6.txt -f;Untuk informasi lebih lanjut tentang penambahan resource, lihat Manage resources.
-
(Untuk debugging lokal) Salin ipv4.txt dan ipv6.txt ke direktori proyek lokal
warehouse/example_project/_resources_.
Langkah 2: Buat koneksi proyek
-
Koneksikan ke proyek MaxCompute. Untuk detail operasional, lihat Manage project connections.
-
Buat MaxCompute Java Module. Untuk detail operasional, lihat Create a MaxCompute Java module.
Langkah 3: Tulis kode MaxCompute UDF
-
Buat objek Java Class.
Kode MaxCompute UDF yang ditulis pada langkah-langkah berikutnya akan menggunakan Java Class yang dibuat di sini.
-
Masuk ke antarmuka IntelliJ IDEA, di area Project, klik kanan pada direktori sumber kode Module (yaitu src > main > java), lalu pilih New > Java Class.
-
Pada dialog New Java Class, masukkan nama Class, tekan tombol Enter, lalu ketikkan kode di area editor.
Anda perlu membuat 3 objek Java Class secara berurutan. Nama Class dan kode yang sesuai adalah sebagai berikut. Kode dapat langsung disalin dan digunakan tanpa perlu modifikasi.
-
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的string形式的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; } }
-
-
-
Tulis kode MaxCompute UDF.
-
Di area Project, klik kanan pada direktori sumber kode Module (yaitu src > main > java), lalu pilih New > MaxCompute Java.
-
Pada dialog Create new MaxCompute java class, klik UDF, isi Name, lalu tekan Enter dan ketikkan kode di area editor.
Sebagai contoh, nama Java Class adalah IpLocation. Kode lengkapnya adalah sebagai berikut dan dapat langsung disalin tanpa perlu modifikasi.
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() { } } }
-
-
Siapkan data untuk debugging lokal.
-
Di direktori proyek lokal
warehouse/example_project/__tables__/wc_in2/p1=2/p2=1/, buka file data. -
Ubah data pada kolom terakhir file data menjadi alamat IP dari ipv4.txt (pilih 3 alamat IP dari ipv4.txt), lalu simpan.
-
-
Debug MaxCompute UDF untuk memastikan kode dapat dijalankan dengan sukses.
Untuk informasi lebih lanjut tentang debugging, lihat Perform a local run to debug the UDF.
-
Klik kanan pada skrip MaxCompute UDF yang telah selesai ditulis, lalu pilih Run.
-
Pada dialog Run/Debug Configurations, konfigurasikan parameter, lalu klik OK.
Contoh konfigurasi parameter: Name diatur sebagai
IpLocation, Main class diatur sebagaicom.aliyun.odps.udf.udfFunction.IpLocation. Kolom khusus MaxCompute: *MaxCompute project pilihlocaldanexample_project, *MaxCompute table isiwc_in2, *Table partition isip2=1,p1=2(format seperti p1=1,p2=2), *Table columns isicolc(format seperti c1,c2). Download Record limit diatur sebagai100, Data Column Separator diatur sebagai koma. Setelah konfigurasi selesai, klik OK. Jika tidak ada error, artinya kode berhasil dijalankan dan Anda dapat melanjutkan ke langkah berikutnya. Jika terjadi error, ikuti petunjuk error dari IntelliJ IDEA.CatatanParameter dapat diisi mengacu pada ilustrasi yang tersedia.
-
Langkah 4: Daftarkan MaxCompute UDF
-
Klik kanan pada skrip MaxCompute UDF yang telah dikompilasi, lalu pilih Deploy to server….
-
Pada dialog Package a jar, submit resource and register function, konfigurasikan informasi parameter.
Untuk penjelasan parameter lebih lanjut, lihat Package, upload, and register. MaxCompute project pilih proyek target (misalnya
doc_test_dev), Resource file pilih file JAR yang telah dipaketkan, Resource name akan otomatis terisi sebagaiudf-1.0-SNAPSHOT.jar, Extra resources pilih file resource tambahan yang diperlukan (misalnyaipv4.txtdanipv6.txt), Main class isi nama lengkap kelas utama UDF (misalnyacom.aliyun.odps.udf.udfFunction.IpLocation), Function name isi nama fungsi (misalnyaipv4_ipv6_aton), centang Force update if already exists, lalu klik OK. Extra resources harus mencakup file database alamat IP ipv4.txt dan ipv6.txt yang diunggah pada Langkah 1. Misalkan nama fungsi yang telah didaftarkan adalah ipv4_ipv6_aton.
Langkah 5: Panggil MaxCompute UDF untuk mengonversi alamat IP menjadi lokasi geografis
-
Jalankan pernyataan SQL SELECT untuk memanggil MaxCompute UDF guna mengonversi alamat IPv4 atau IPv6 menjadi lokasi geografis.
Berikut contoh perintahnya.
-
Mengonversi alamat IPv4 menjadi lokasi geografis
select ipv4_ipv6_aton('116.11.XX.XX');Hasil yang dikembalikan adalah sebagai berikut.
北海市,广西壮族自治区 -
Mengonversi alamat IPv6 menjadi lokasi geografis
select ipv4_ipv6_aton('2001:0250:080b:0:0:0:0:0');Hasil yang dikembalikan adalah sebagai berikut.
保定市,河北省
-