客户需求如下,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. SqlAlchemy的简单使用

    1.SQLAlchemy SQLAlchemy是python的一个通用的ORM框架 1.1 创建数据表 from sqlalchemy.ext.declarative import declarati ...

  2. oracle表复杂查询--多表查询

    多表查询是指基于两个和两个以上的表或是视图的查询,在实际应用中,查询单个表可能不能满足你的要求,如显示sales部门位置和其员工的姓名,这种情况下需要使用到dept表和emp表.     select ...

  3. CVE-2019-0708漏洞利用

    20190514,微软发布补丁,修复了一个严重的RDP远程代码执行漏洞.该漏洞无需身份认证和用户交互,可能形成蠕虫爆发,影响堪比wannycry. 影响范围: Windows 7 Windows Se ...

  4. UVa 10520【递推 搜索】

    UVa 10520 哇!简直恶心的递推,生推了半天..感觉题不难,但是恶心,不推出来又难受..一不小心还A了[]~( ̄▽ ̄)~*,AC的猝不及防... 先递推求出f[i][1](1<=i< ...

  5. Xcode编译报错:< Apple Mach-O Linker Warning > clang: error: no such file or directory: 'xxxx'

    Xcode编译报错概述: clang: error: no such file or directory: 'CoreGraphics' 一般原因是链接库内容导入丢失,这种的排查下target - B ...

  6. python代码实现树莓派3b+驱动步进电机

    python代码实现树莓派3b+驱动步进电机 之前买了个树莓派,刚买回来那会儿热情高涨,折腾了一段时间,然后就放那吃灰了.前几天忽然想起来这个东西了,决定再玩玩儿,于是就从某宝上购买了一套步进电机.驱 ...

  7. linux扫盲之CPU模式

    相信研究linux的大大都知道linux有实模式.保护模式.虚拟模式三种. 不多说,拷贝黏贴!  80386开始,cpu有三种工作方式:实模式,保护模式和虚拟8086模式.只有在刚刚启动的时候是rea ...

  8. saltStack 状态模块(状态间的关系)

    unless onlyif:状态间的条件判断,主要用于cmd状态模块 常用方法:    onlyif:检查的命令,仅当'onlyif'  选项指向的命令返回true时才执行name 定义的命 unle ...

  9. day1-初识Python之变量

    1.python安装与环境配置 1.1.Windows下的python解释器安装 打开官网 https://www.python.org/downloads/windows/ 下载中心 测试安装是否成 ...

  10. Android Studio(十一):代码混淆及打包apk

    Android Studio相关博客: Android Studio(一):介绍.安装.配置 Android Studio(二):快捷键设置.插件安装 Android Studio(三):设置Andr ...