客户端灰度发布,NGINX+GeoIP2+GeoIP Database

需求的产生 (分地区更新)

我们公司呢是做电商平台的,其主打商品里有一款智能饮水机产品,而每台智能饮水机产品都是装在全国客户的家里。为了售后维护需要服务端如何保存数据并传回这块实现较简单,而在远程展示水机的余额、归属地、出水的质量等功能这块是由水机自身所镶嵌的一块智能PAD屏所完成,其PAD内部安装的是安卓系统;
在由开发完成新功能的开发后需迭代智能PAD屏内部安卓系统APK版本时,在更新版本这块我们一直做的方法是全量更新不做任何更新上的限制。但随着业务量的增加全国大概有30万台水机版本需要更新,显然之前的更新方式不再适用于现有这种高业务量的需求了。所以我们考虑了一个新的更新方案 "按地区更新";

参考的方案

一、最直接的方案是购买阿里云的CDN,利用CDN的缓存来实现。缓存原理如下

  • 通过CDN访问的流程是:客户端-->CDN L1--->CDN L2--->源站;
  • 客户端请求到的CDN节点是CDN的L1节点,当客户端请求到CDN的L1节点向该节点请求一个资源的时候,该CDN节点会查询本节点是否有这个资源,如有这个资源就会直接返回给客户端。如果没有这个资源,CDN L1节点就会向CDN的L2节点去请求资源,L2节点如果有缓存该文件,那么L1向L2拿到数据以后就返回给客户端。如果L2节点也没有缓存该资源,那么CDN的L2节点会回源向源站去请求这个资源然后返回给客户端,并且根据具体的缓存规则把该资源缓存下来,具体缓存多久是根据缓存规则而定一旦缓存过期CDN节点就会清空该资源,等到下一次有客户端请求到该节点的时候,CDN才会回源去获取数据。这里一个L2节点是对应多个L1节点的,如果L1没有命中缓存,L2命中了缓存,那么最终也是命中缓存的;
  • 如果源站更新了APK文件,而CDN缓存还没有过期的话,客户可能会请求到老的APK数据。所以源站更新数据以后,需要到CDN上去刷新下缓存;
  • 后来考虑项目成本的问题就放弃了;

二、采用灰度发布

  • 由于各项目的不互通性,而且灰度发布是在代码层实现的,不好应用到现在这个架构中;
  • 这种灰度的方式是根据版本号来迭代,即前端+CDN+Nginx的方式;
  • 讨论过以后开发周期长,维护人员成本较高
  • 后来思路还是放在Nginx上,Nginx+GeoIP Modules+GeoIP Datebase;
  • 一点想法都没有,该花的钱还是要花啊啊啊;

Nginx+GeoIP Modules+GeoIP Datebase

  • 编译Nginx引入第三方模块,这里注意了需引入之前已编译成功后的第三方模块,不然你就完了...
  • 下载GeoIP库 传送门 ,解压到/usr/share/GeoIP目录下。此类资源在网内csdn还是有的比较有心;
  • 使用系统是CentOS7.2以上版本GeoIP包已安装(rpm -ql GeoIP),但可惜的是它不再自动为您更新库;
  • 如编译报错 "the GeoIP module requires the GeoIP library",请 yum install -y geoip-devel
  • 编译后禁止make install;
  • make 完成后把/root/nginx-1.14.2/objs/nginx 复制到nginx应用程序目录下并覆盖;
[root@node1-master nginx-1.14.2]# cd /usr/share/GeoIP
[root@node1-master GeoIP]# gzip -d GeoLiteCity.dat.gz
[root@node1-master GeoIP]# ln -sv GeoLiteCity.dat GeoCity.dat
[root@node1-master ~]# wget http://nginx.org/download/nginx-1.14.2.tar.gz 
[root@node1-master ~]# tar xf nginx-1.14.2.tar.gz && cd nginx-1.14.2 
[root@node1-master nginx-1.14.2]# nginx -V
[root@node1-master nginx-1.14.2]# ./configure \
--user=nginx \
--group=nginx  \
--with-http_stub_status_module \
--prefix=/usr/local/nginx \
--conf-path=/usr/local/nginx/conf/nginx.conf \
--with-threads \
--with-http_ssl_module \
--with-pcre --with-pcre=/usr/local/nginx/modules/pcre-8.30 \
--with-http_realip_module \
--with-http_gzip_static_module \
--with-stream --with-http_slice_module \
--with-cc-opt=-DTCP_FASTOPEN=23 \
--add-module=/usr/local/nginx/modules/ngx_cache_purge-2.3 \
--add-module=/usr/local/nginx/modules/nginx_upstream_check_module \
--add-module=/usr/local/nginx/modules/file-md5-master \
--add-module=/usr/local/nginx/modyles/nginx-sticky
--with-http_geoip_module  //此模块即为编译时所加入的静态geoip模块
[root@node1-master nginx-1.14.2]# make
[root@node1-master nginx-1.14.2]# cp ./objs/nginx /usr/local/nginx/sbin/nginx
[root@node1-master nginx-1.14.2]# nginx -V
会显示--with-http_geoip_module此模块证明编译成功

Nginx 配置文件参考

user root root;
worker_processes  auto;
worker_cpu_affinity auto;
error_log /usr/local/nginx/logs/error.log  crit;
pid /var/run/nginx.pid;
worker_rlimit_nofile 65535;

events {
worker_connections 65535;
multi_accept on;
}
http {
include mime.types;
default_type application/octet-stream;
access_log logs/access.log  main;

##定义GeoIP数据库及存储路径## 
geoip_city /usr/share/GeoIP/GeoCity.dat;
geoip_proxy 192.168.0.0/24;
geoip_proxy 192.168.1.0/24;
geoip_proxy_recursive on;

##结合GeoIP数据库并引入map指令来自定义变量,充分发挥作用##
map $geoip_city $no_allowed_region {
default 1;
Shanghai yes;
Beijing yes;
Tianjin yes;
}

server {
listen 80;
listen 443 default_server;
server_name you.domain.com;
ssl on;
ssl_certificate  /usr/local/nginx/ssl/you.domain.com.crt;
ssl_certificate_key  /usr/local/nginx/ssl/you.domain.com.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_prefer_server_ciphers on;
root   /html;
index index.html index.htm;
try_files /index_$geoip_city.html ./index.html;

location /static {
root   /data;
    }
location ~ ^/api/pad/available {
default_type application/json;
return 200 '{"success":true}';
    }
#变量判断#
location /files/pad/yim-1.2.9.apk {
imit_conn addr 10;
limit_rate 100k;
if ($no_allowed_region = 1) {
return 550;
        }
    }
#变量判断#
location /files/pad/yimpad-version.json {
limit_conn addr 10;
limit_rate 100k;
if ($no_allowed_region = 1) {
return 550;
        }
    }
location ~ ^/static/water/material {
root       /data/files;
limit_conn addr 10;
limit_rate 100k;
        }
    }
}
  • 匹配apk文件:
    当访问的是/files/pad/yimi-1.2.9.apk文件时以下配置段被命中。if判断读取map指令中源变量$geoip_city的值去匹配后一个变量$no_allowed_region的值,如匹配成功则放行。其它或空则匹配默认规则,默认规则值是 default 1,如果匹配到默认规则的话在http响应状态码中则返回550作为警示码;
    location /files/pad/yimi-1.2.9.apk {
    root /data;
    limit_conn addr 10;
    limit_rate 100k;
    if ($no_allowed_region = 1) {
    return 550;
        }
    }
  • 匹配json文件:
    当访问的是/files/pad/yimipad-version.json文件时以下配置段被命中。if判断读取map指令中源变量$geoip_city的值去匹配后一个变量$no_allowed_region的值,如匹配成功则放行。其它或空则匹配默认规则,默认规则值是 default 1,如果匹配到默认规则的话在http响应状态码中则返回550作为警示码;
    location /files/pad/yimipad-version.json {
    root /data;
    limit_conn addr 10;
    limit_rate 100k;
    if ($no_allowed_region = 1) {
    return 550;
        }
    }
    默认黑名单机制 default 1,即所有请求全部拒绝。只有在map配置段中添加 CITY = YES时规则才有效;

从GeoIP升级到GeoIP2

  • 安装开发包组
[root@node1-master ~]# yum groupinstall -y Development Tools
[root@node1-master ~]# yum install -y pcre-devel openssl openssl-devel zlib-devel
  • 下载编译libmaxmindb依赖库
official help site https://github.com/maxmind/libmaxminddb  
[root@node1-master ~]# cd /usr/local/src && git clone --recursive https://github.com/maxmind/libmaxminddb
[root@node1-master ~]# cd libmaxminddb
[root@node1-master ~]# ./bootstrap
[root@node1-master ~]# ./configure
[root@node1-master ~]# make
[root@node1-master ~]# make install
[root@node1-master ~]# sh -c "echo /usr/local/lib  >> /etc/ld.so.conf.d/local.conf"
[root@node1-master ~]# ldconfig
  • 平滑升级Nginx并编译(ngx_http_geoip2_module)模块
参考"Nginx+GeoIP Modules+GeoIP Datebase"编译nginx的方法,一定要带上原有参数
[root@node1-master ~]# nginx -V
[root@node1-master ~]# cd /usr/local/nginx/modules
[root@node1-master ~]# git clone --recursive https://github.com/leev/ngx_http_geoip2_module
[root@node1-master ~]# cd /root/nginx-1.14.2
[root@node1-master nginx-1.14.2]# ./configure --原有参数 --add-module=/usr/local/nginx/modules/ngx_http_geoip2_module
[root@node1-master nginx-1.14.2]# make 
[root@node1-master nginx-1.14.2]# mv /usr/local/nginx/sbin/nginx{,.baks}
[root@node1-master nginx-1.14.2]# cp objs/nginx /usr/local/nginx/sbin
[root@node1-master nginx-1.14.2]# make upgrade
如果在make upgrade的时候报错 "make: *** [upgrade] error 1" 先完全kill掉nginx主进程,然后使用-c选项启动nginx,再次执行make upgrade
[root@node1-master ~]# /usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf
这里把ngx_http_geoip2_module构建为静态模块,如果构建为动态模块需Nginx版本大于1.9.11+
  • 下载libmaxminddb数据库文件
official help site https://dev.maxmind.com/geoip/geoip2/geolite2
[root@node1-master ~]# wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz
[root@node1-master ~]# wget https://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.tar.gz
[root@node1-master ~]# tar xf GeoLite2-City.tar.gz -C /usr/share/GeoIP2
[root@node1-master ~]# tar xf GeoLite2-Country.tar.gz -C /usr/share/GeoIP2
  • 修改nginx配置替换GeoIP内容如下
##geoip2 with##
geoip2 /usr/share/GeoIP2/GeoLite2-City.mmdb {
auto_reload 60m;
$geoip2_metadata_city_build metadata build_epoch;
$geoip2_data_city_name city names en;
$geoip2_data_city_name default=Shanghai city names en;
}

##map geoip##
map $geoip2_data_city_name  $default_city_list {
default 1;
Shanghai yes;
}

##server or location##
if ($default_city_list = 1) {
        return 770;
}
  • 重载nginx配置文件 nginx -s reload

测试命令 mmdblookup

  • 测试在mmdb数据库中查找数据路径(国家/地区名称en)
  • mmdblookup --file /usr/share/GeoIP2/GeoLite2-City.mmdb --ip 49.7.20.53
上一篇:JSONObject,JSONArray,String,Map间的互转


下一篇:将MaxMind的GeoLite2导入MySQL