客户需求如下,nginx的访问日志中ip,匹配出对应的国家,省份和城市,然后给我了一个maxmind的连接参考。

查找资料,有做成hive udf的使用方式, 我们项目中一直使用 waterdrop 来做数据处理,所以决定开发一个 waterdrop的插件。

关于这个功能,waterdrop本身提供有两个商用组件,geopip2(也是使用maxmind) 另一个是国内的 ipipnet。

如果有人不懂 waterdrop,可以参考 https://interestinglab.github.io/waterdrop/#/zh-cn/quick-start

开发使用 scala语言,开发完毕后,使用 mvn clean package 打包即可,生成的包是不含有 依赖的,请注意把依赖放到spark classpath中去使用。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.student</groupId>
<artifactId>GeoIP2</artifactId>
<version>1.0-SNAPSHOT</version> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<scala.version>2.11.8</scala.version>
<scala.binary.version>2.11</scala.binary.version>
<spark.version>2.4.0</spark.version>
<waterdrop.version>1.4.0</waterdrop.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties> <dependencies>
<dependency>
<groupId>io.github.interestinglab.waterdrop</groupId>
<artifactId>waterdrop-apis_2.11</artifactId>
<version>${waterdrop.version}</version>
</dependency>
<dependency>
<groupId>com.typesafe</groupId>
<artifactId>config</artifactId>
<version>1.3.1</version>
</dependency> <dependency>
<groupId>com.maxmind.db</groupId>
<artifactId>maxmind-db</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.maxmind.geoip2</groupId>
<artifactId>geoip2</artifactId>
<version>2.6.0</version>
</dependency> </dependencies> <build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.0.2</version>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin> <plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<executions>
<execution>
<id>scala-compile-first</id>
<phase>process-resources</phase>
<goals>
<goal>add-source</goal>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>scala-test-compile</id>
<phase>process-test-resources</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin> </plugins>
</build>
</project>

主要的程序文件只有一个Geoip2:

package com.student

import io.github.interestinglab.waterdrop.apis.BaseFilter
import com.typesafe.config.{Config, ConfigFactory}
import org.apache.spark.sql.{Dataset, Row, SparkSession}
import org.apache.spark.sql.functions.{col, udf} import scala.collection.JavaConversions._
import com.maxmind.geoip2.DatabaseReader
import java.io.{File, InputStream}
import java.net.InetAddress import com.maxmind.db.CHMCache
import org.apache.spark.SparkFiles object ReaderWrapper extends Serializable {
@transient lazy val reader = {
val geoIPFile = "GeoLite2-City.mmdb";
val database = new File(SparkFiles.get(geoIPFile));
val reader: DatabaseReader = new DatabaseReader.Builder(database)
//.fileMode(com.maxmind.db.Reader.FileMode.MEMORY)
.fileMode(com.maxmind.db.Reader.FileMode.MEMORY_MAPPED)
.withCache(new CHMCache()).build();
reader
}
} class GeoIP2 extends BaseFilter { var config: Config = ConfigFactory.empty() /**
* Set Config.
**/
override def setConfig(config: Config): Unit = {
this.config = config
} /**
* Get Config.
**/
override def getConfig(): Config = {
this.config
} override def checkConfig(): (Boolean, String) = { val requiredOptions = List("source_field")
val nonExistsOptions: List[(String, Boolean)] = requiredOptions.map { optionName =>
(optionName, config.hasPath(optionName))
}.filter { p =>
!p._2
} if (nonExistsOptions.length == 0) {
(true, "")
} else {
(false, "please specify setting as non-empty string")
} } override def prepare(spark: SparkSession): Unit = { val defaultConfig = ConfigFactory.parseMap(
Map(
"source_field" -> "raw_message",
"target_field" -> "__ROOT__"
)
) config = config.withFallback(defaultConfig) } override def process(spark: SparkSession, df: Dataset[Row]): Dataset[Row] = { val srcField = config.getString("source_field")
val func = udf { ip: String => ip2Locatation(ip) } val ip2Country=udf{ip:String => ip2Location2(ip,1)}
val ip2Province=udf{ip:String => ip2Location2(ip,2)}
val ip2City=udf{ip:String => ip2Location2(ip,3)} //df.withColumn(config.getString("target_field"), func(col(srcField)))
df.withColumn("__country__", ip2Country(col(srcField)))
.withColumn("__province__", ip2Province(col(srcField)))
.withColumn("__city__", ip2City(col(srcField))) } def ip2Locatation(ip: String) = {
try {
val reader = ReaderWrapper.reader
val ipAddress = InetAddress.getByName(ip)
val response = reader.city(ipAddress)
val country = response.getCountry()
val subdivision = response.getMostSpecificSubdivision()
val city = response.getCity()
(country.getNames().get("zh-CN"), subdivision.getNames.get("zh-CN"), city.getNames().get("zh-CN"))
}
catch {
case ex: Exception =>
ex.printStackTrace()
("", "", "")
}
} def ip2Location2(ip: String,index: Int) = {
try {
val reader = ReaderWrapper.reader
val ipAddress = InetAddress.getByName(ip)
val response = reader.city(ipAddress) index match {
case 1 => response.getCountry().getNames().get("zh-CN")
case 2 => response.getMostSpecificSubdivision().getNames.get("zh-CN")
case 3 => response.getCity().getNames().get("zh-CN")
case _ => ""
}
}
catch {
case ex: Exception =>
ex.printStackTrace()
""
}
} }

测试类的代码如下:

package com.student

import com.typesafe.config._
import org.apache.spark.sql.{DataFrame, SparkSession} object TestIt { def main(args: Array[String]): Unit = { val spark: SparkSession = SparkSession.builder().appName("demo").master("local[1]")
.config("spark.files","/Users/student.yao/code/sparkshell/GeoLite2-City.mmdb")
//.enableHiveSupport()
.getOrCreate() spark.sparkContext.setLogLevel("WARN") //获取到第一个conf,复制给插件的实例
val firstConf:Config = ConfigFactory.empty() //实例化插件对象
val pluginInstance: GeoIP2 = new GeoIP2
pluginInstance.setConfig(firstConf)
pluginInstance.prepare(spark)
//虚拟一些数据 import spark.implicits._
val sourceFile: DataFrame = Seq((1, "221.131.74.138"),
(2, "112.25.215.84"),
(3,"103.231.164.15"),
(4,"36.99.136.137"),
(5,"223.107.54.102"),
(6,"117.136.118.125")
).toDF("id", "raw_message") sourceFile.show(false)
val df2 = pluginInstance.process(spark,sourceFile)
df2.show(false)
}
}

遇到的问题

1.一开始的时候,把 GeoLite2-city.mmdb放到了 Resources文件夹,想把它打到jar包里,然后在项目中 使用 getClass().getResourceAsStream("/GeoLite2-city")

运行中发现这种方式特别慢,说这种慢,是和 使用 sparkFieles.get方式比较起来,遂改成sparkFiles获取的方式,先把文件放到 hdfs,然后在spark作业配置荐中配置:

spark.files="hdfs://nameservicesxxx/path/to/Geolite2-city.mmdb",这样 SparkFiles.get("GeoLite2-city.mmdb") 就可以获取文件使用。

2。性能优化的过程中,想把 Reader提出来放在 Prepare方法里面是很自然的一个想法,在本机测试的时侯没问题,因为是单机的,没发现,在生产上时发现报 不可序列化的异常。

其实在 ideaj中可以使用 spark.sparkContext.broadcast(reader)的方式,就可以发现这个异常。如何解决这个异常,通常的解决方式是 在 dataframe|rdd的 foreachpartition|mappartitions中

生成对象,这样就不会报错了。进一步可以使用 单例 这样效果更好些 。但这个是 插件,没法这样做, 就想到了可以使用一个外壳包起来,让reader不序列化即可。

所以有了 ReaderWrapper extends Serializable @transient lazy val 这一段。

3。初始化 reader的时候进行缓存

经过这些处理,性能得到了提升,经测试,4G,4core,每5秒一个批次,2万条数据处理3秒钟。(kafka->waterdrop-json->es)全过程。

maxmind geoip2使用笔记的更多相关文章

  1. 企业运维实践-Nginx使用geoip2模块并利用MaxMind的GeoIP2数据库实现处理不同国家或城市的访问最佳实践指南

    关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 本章目录 目录 0x00 前言 ...

  2. 通过GeoIP2分析访问者IP获取地理位置信息

    原文链接:http://blog.csdn.net/johnnycode/article/details/42028841 MaxMind GeoIP2 服务能识别互联网用户的地点位置与其他特征,应用 ...

  3. Hive UDF IP解析(二):使用geoip2数据库自定义UDF

    开发中经常会碰到将IP转为地域的问题,所以以下记录Hive中自定义UDF来解析IP. 使用到的地域库位maxmind公司的geoIP2数据库,分为免费版GeoLite2-City.mmdb和收费版Ge ...

  4. Update(Stage5):DMP项目_业务介绍_框架搭建

    DMP (Data Management Platform) 导读 整个课程的内容大致分为如下两个部分 业务介绍 技术实现 对于业务介绍, 比较困难的是理解广告交易过程中各个参与者是干什么的 对于技术 ...

  5. 获取用户登陆所在的ip及获取所属信息

    package com.tcl.topsale.download.entity; public class GeoLocation { private String countryCode; priv ...

  6. Hive UDF IP解析(一):依赖包兼容性问题

    Java依赖环境: <dependency> <groupId>org.apache.hive</groupId> <artifactId>hive-e ...

  7. spark streaming 使用geoIP解析IP

    1.首先将GEOIP放到服务器上,如,/opt/db/geo/GeoLite2-City.mmdb 2.新建scala sbt工程,测试是否可以顺利解析 import java.io.Fileimpo ...

  8. Java 根据IP获取地址

    用淘宝接口:(源码:java 根据IP地址获取地理位置) pom.xml: <!-- https://mvnrepository.com/artifact/net.sourceforge.jre ...

  9. 【日志处理、监控ELK、Kafka、Flume等相关资料】

    服务介绍 随着实时分析技术的发展及成本的降低,用户已经不仅仅满足于离线分析.目前我们服务的用户包括微博,微盘,云存储,弹性计算平台等十多个部门的多个产品的日志搜索分析业务,每天处理约32亿条(2TB) ...

随机推荐

  1. 【错误收集】SVN冲突解决 标签: 错误收集 2016-03-13 08:44 624人阅读 评论(24) 收藏

    最近在倒代码,这真的是一件挺低效率的事情的,但是为了之后工作的进行,必须把这些已经做好的界面,做好的功能搬到新的框架上来,所以安排了10来个同学一起倒代码,因为大家共用一个解决方案,所以使用svn来进 ...

  2. 在centos搭建git服务器时,不小心把/home/git目录删除了,我是怎么恢复的

    在centos搭建git服务器时,不小心把/home/git目录删除了,我是怎么恢复的 在删除掉/home/git目录后,每次 git push提交时,都让填写密码,烦 第一步:在本地找到id_rsa ...

  3. SQL if语句简要

    if语句 可以作为表达式用 可以在存储过程中作为流程控制语句用 表达式 IF(条件,条件true,条件false) 示例 sex字段m,f 互换 update salary set sex = if( ...

  4. C++结构体中的静态变量

    分享一个挺有意思的代码: #include <bits/stdc++.h> using namespace std; struct Point { static int cnt; Poin ...

  5. iOS iOS8注册通知

    http://blog.csdn.net/apple_app/article/details/39228221 极光推送 action设置 http://docs.jpush.cn/display/d ...

  6. @atcoder - AGC034D@ Manhattan Max Matching

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 考虑一个二维平面,执行共 2*N 次操作: 前 N 次,第 i ...

  7. Flask学习之七 单元测试

    英文博客地址:http://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-vii-unit-testing 中文翻译地址:http ...

  8. C# Find vs FirstOrDefault

    本文告诉大家,在获得数组第一个元素时,使用哪个方法性能更高. 需要知道,两个方法都是 Linq 的方法,使用之前需要引用 Linq .对于 List 等都是继承可枚举Enumerable这时获取第一个 ...

  9. oracle函数 SUBSTR(c1,n1[,n2])

    [功能]取子字符串 [说明]多字节符(汉字.全角符等),按1个字符计算 [参数]在字符表达式c1里,从n1开始取n2个字符;若不指定n2,则从第y个字符直到结束的字串. [返回]字符型 [示例] SQ ...

  10. oracle函数 LTRIM(c1,[,c2])

    [功能]删除左边出现的字符串 [参数]C1 字符串 c2 追加字符串,默认为空格 [返回]字符型 [示例] SQL> select LTRIM('   gao qian jing',' ') t ...