GeoIP 是一个非常有用的信息,也是使用 ELKstack 时一般都会加上的过滤器插件。不过 geoip 插件的性能,有些时候却会成为整个系统的瓶颈。另一个问题,则是 GeoIP 数据文件的准确度,在国内比较头疼。即使你有一个自己处理出来的准确度较高的 IP 库,GeoIP 也没有提供现成的修改数据文件内容的工具。这个时候,MaxMind 公司的 GeoIP2 就进入我的视线了。
GeoIP2 在字段上比 GeoIP 更丰富。而且还提供了 MaxMind::DB::Writer 库方便使用者自己生成 GeoIP2 数据文件!感谢@纯白色燃烧童鞋用自己的 CPAN 库成功倒逼 MaxMind 公司。
据@纯白色燃烧 介绍,GeoIP2 比 GeoIP 有六到七倍的性能提升。不过他是在 C 平台下,使用 libmaxminddb 库做的测试,而 logstash 是 JRuby 平台,所以我们需要的是验证如何在 JRuby 上使用 GeoIP2,以及跟 GeoIP 的性能对比。
在 JRuby 上用模块,有两种方式,一种是纯 Ruby 实现,一种是纯 Java 实现。MaxMind 提供了纯 Java 实现,社区另外有一个纯 Ruby 实现的库。下面开始测试。
首先需要准备环境。安装 JRuby,纯 Ruby 实现的 maxminddb 库;然后下载 GeoIP2 数据文件,下载 Java 实现的 MaxMind-Java 库。
sudo port install jruby
sudo jgem install maxminddb
wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz
gzip -d GeoLite2-City.mmdb.gz
wget https://github.com/maxmind/GeoIP2-java/releases/download/v2.1.0/geoip2-2.1.0-with-dependencies.zip
unzip geoip2-2.1.0-with-dependencies.zip
准备就绪,然后就是如何测试的问题了。为了贴近 logstash 运行环境,我扒拉了一下 logstash 最核心的 pipeline.rb
文件,简化出来了一个测试程序。相当于是 logstash -w 20 -e 'input {generator {}} filter {geoip{}} output {null{}}
的效果:
#!/usr/bin/env jruby
require "geoip"
require "maxminddb"
require "thread"
require "java"
# 测试数据
ip = '202.106.0.20'
# 加载 maxmind-java 的所有 jar 包
Dir["/Users/raochenlin/geoip2-2.1.0/lib/*.jar"].each { |jar| require jar }
# 导入关键性的 java 类
import com.maxmind.geoip2.DatabaseReader
import java.net.InetAddress
# 这个原生的 java 写法是:
# File database = new File("/Users/raochenlin/GeoLite2-City.mmdb")
# DatabaseReader reader = new DatabaseReader.Builder(database).build()
# 之前对 java 不太懂,想直接 import Builder 进来
# 其实 Builder 是DatabaseReader 类里的静态类(public final static class),不能直接 import
database = java.io.File.new("/Users/raochenlin/GeoLite2-City.mmdb")
@reader = DatabaseReader::Builder.new(database).build()
# 纯 Ruby 实现的库
@db = MaxMindDB.new('/Users/raochenlin/GeoLite2-City.mmdb')
# 老的 GeoIP 库,需要制定不同的数据文件类型,这部分直接抄自 logstash 源码
@geo = GeoIP.new('/Users/raochenlin/Downloads/logstash-1.4.2/vendor/geoip/GeoLiteCity.dat')
@geoip_type = case @geo.database_type
when GeoIP::GEOIP_CITY_EDITION_REV0, GeoIP::GEOIP_CITY_EDITION_REV1
:city
when GeoIP::GEOIP_COUNTRY_EDITION
:country
when GeoIP::GEOIP_ASNUM_EDITION
:asn
when GeoIP::GEOIP_ISP_EDITION, GeoIP::GEOIP_ORG_EDITION
:isp
else
raise RuntimeException.new "This GeoIP database is not currently supported"
end
# 开始 logstash 流程
# 创建从 input 到 filter 的缓冲队列,固定大小 20
# SizedQueue 是 thread 库导入的
@input_to_filter = SizedQueue.new(20)
# 具体的 geoip 过滤器线程
def geoworker
begin
while true
ip = @input_to_filter.pop
# GeoIP 查询方法
# data = @geo.send(@geoip_type, ip)
# puts data.to_hash[:city_name]
# MaxMind-java 查询方法,注意传入的是 InetAddress 对象
data = @reader.city(InetAddress.getByName(ip))
# puts data.getCountry().getName()
# maxminddb 查询方法
# data = @db.lookup(ip)
# puts data.country.name
end
end
end
# 定义 input 线程,传入一百万次 IP 到缓冲队列
lines_num = 1000000
input = Thread.new do
lines_num.times.each do |i|
@input_to_filter.push(ip)
end
# IP 发送完毕,计算每秒处理的速率
end_time = Time.now.to_f * 1000
puts lines_num * 1000 / (end_time - @start_time)
end
# 定义 filter 线程,启动 20 个
arr = 20.times.collect do
Thread.new do
geoworker
end
end
# 记录开始时间,运行定义好的各线程
@start_time = Time.now.to_f * 1000
input.join
arr.each{|t| t.join}
在一百万次查询的测试中,结果如下:
可见,对于这部分有性能要求的,完全可以改用 maxmind-java
库,可以数倍提高。