用Nginx轻松实现国际化访问!不同国家用户看到不同页面的完整攻略
最近公司有个项目需要针对不同国家的用户展示不同的页面内容,说实话一开始我还想着要不要用什么复杂的CDN方案,后来发现用Nginx就能轻松搞定。今天就把整个实现过程分享给大家,包括踩过的坑和一些优化技巧。
这个需求其实挺常见的,比如做跨境电商的朋友,针对美国用户显示英文页面和美元价格,针对中国用户显示中文页面和人民币价格。或者有些网站需要根据不同地区的法律法规展示不同的内容条款。
实现原理其实很简单
核心思路就是通过Nginx的GeoIP模块来识别用户的IP地址对应的国家,然后根据国家信息将请求转发到不同的后端或者直接返回不同的静态页面。
不过这里有个关键点,Nginx默认是不带GeoIP模块的,我们需要先安装相关的模块和数据库。
第一步:准备GeoIP数据库
# CentOS/RHEL系统
yum install -y geoip-devel libmaxminddb-devel
# Ubuntu/Debian系统
apt-get install -y libgeoip-dev libmaxminddb-dev
# 下载GeoIP数据库文件
mkdir -p /etc/nginx/geoip
cd /etc/nginx/geoip
# 下载免费的GeoLite2数据库
wget https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_LICENSE_KEY&suffix=tar.gz -O GeoLite2-Country.tar.gz这里要注意,MaxMind从2019年开始要求注册账号才能下载数据库了。你需要去他们官网注册个免费账号,然后生成license key。
解压数据库文件:
(下载不了可以后台联系找我要)
tar -xzf GeoLite2-Country.tar.gz
cp GeoLite2-Country_*/GeoLite2-Country.mmdb ./检查Nginx是否支持GeoIP
nginx -V 2>&1 | grep -o with-http_geoip_module如果没有输出,说明当前的Nginx不支持GeoIP模块,需要重新编译安装或者安装带GeoIP模块的版本。
对于CentOS,可以这样安装:
yum install -y nginx-mod-http-geoip配置Nginx实现地理位置识别
在nginx.conf的http块中添加GeoIP配置:
http {
# 加载GeoIP模块
geoip_country /etc/nginx/geoip/GeoLite2-Country.mmdb;
# 定义国家代码映射
map $geoip_country_code $country_path {
default /default;
CN /cn;
US /us;
JP /jp;
KR /kr;
GB /gb;
DE /de;
FR /fr;
}
# 定义语言映射
map $geoip_country_code $lang {
default en;
CN zh;
JP ja;
KR ko;
DE de;
FR fr;
}
server {
listen 80;
server_name example.com;
# 设置根目录
root /var/www/html;
index index.html index.php;
# 根据国家跳转到不同目录
location / {
try_files $country_path$uri $country_path/index.html $uri /default/index.html;
}
# API接口可以返回JSON格式的国家信息
location /api/country {
add_header Content-Type application/json;
return 200 '{"country":"$geoip_country_code","country_name":"$geoip_country_name","lang":"$lang"}';
}
}
}目录结构设计
按照上面的配置,我们需要创建对应的目录结构:
mkdir -p /var/www/html/{default,cn,us,jp,kr,gb,de,fr}
# 创建不同国家的首页
echo '<h1>Welcome to our global site!</h1>' > /var/www/html/default/index.html
echo '<h1>欢迎访问我们的中国站点!</h1>' > /var/www/html/cn/index.html
echo '<h1>Welcome to our US site!</h1>' > /var/www/html/us/index.html
echo '<h1>日本のサイトへようこそ!</h1>' > /var/www/html/jp/index.html这种方式比较简单直接,但如果页面内容差异不大,可能会造成文件冗余。
更灵活的方案:动态内容替换
有时候我们不想维护多套完整的页面,只是希望替换其中的部分内容,比如货币符号、语言文本等。这时候可以用Nginx的sub_filter模块:
server {
listen 80;
server_name shop.example.com;
root /var/www/shop;
# 美国用户
location / {
if ($geoip_country_code = "US") {
sub_filter '{{CURRENCY}}' '$';
sub_filter '{{LANGUAGE}}' 'en';
sub_filter '{{COUNTRY_NAME}}' 'United States';
sub_filter_once off;
}
# 中国用户
if ($geoip_country_code = "CN") {
sub_filter '{{CURRENCY}}' '¥';
sub_filter '{{LANGUAGE}}' 'zh';
sub_filter '{{COUNTRY_NAME}}' '中国';
sub_filter_once off;
}
# 欧元区用户
if ($geoip_country_code ~ "^(DE|FR|IT|ES)$") {
sub_filter '{{CURRENCY}}' '€';
sub_filter '{{LANGUAGE}}' 'eu';
sub_filter '{{COUNTRY_NAME}}' 'Europe';
sub_filter_once off;
}
try_files $uri $uri/ /index.html;
}
}然后在HTML模板中使用占位符:
<!DOCTYPE html>
<html lang="{{LANGUAGE}}">
<head>
<title>Shop - {{COUNTRY_NAME}}</title>
</head>
<body>
<h1>Welcome to our {{COUNTRY_NAME}} store!</h1>
<p>All prices are shown in {{CURRENCY}}.</p>
</body>
</html>进阶配置:结合上游服务器
在实际生产环境中,我们通常需要将请求转发给不同的后端服务器,而不只是返回静态页面:
upstream china_backend {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
upstream us_backend {
server 192.168.2.10:8080;
server 192.168.2.11:8080;
}
upstream default_backend {
server 192.168.3.10:8080;
server 192.168.3.11:8080;
}
server {
listen 80;
server_name api.example.com;
# 设置自定义header传递国家信息给后端
proxy_set_header X-Country-Code $geoip_country_code;
proxy_set_header X-Country-Name $geoip_country_name;
location / {
# 根据国家选择上游服务器
if ($geoip_country_code = "CN") {
proxy_pass http://china_backend;
break;
}
if ($geoip_country_code = "US") {
proxy_pass http://us_backend;
break;
}
proxy_pass http://default_backend;
}
}处理特殊情况和优化
实际使用中会遇到一些特殊情况,需要特别处理:
# 定义白名单IP,不进行地理位置检测
geo $whitelist_ip {
default 0;
192.168.1.0/24 1; # 内网IP
10.0.0.0/8 1; # 内网IP
127.0.0.1/32 1; # 本地IP
}
server {
listen 80;
server_name example.com;
# 设置真实IP(如果使用了CDN或负载均衡)
real_ip_header X-Forwarded-For;
set_real_ip_from 192.168.1.0/24; # CDN的IP段
location / {
# 白名单IP直接访问默认页面
if ($whitelist_ip) {
root /var/www/html/default;
break;
}
# 允许用户手动选择国家站点
if ($arg_country) {
set $manual_country $arg_country;
root /var/www/html/$manual_country;
break;
}
# 正常的GeoIP判断
root /var/www/html$country_path;
try_files $uri $uri/ /index.html;
}
}这样配置后,用户可以通过 example.com?country=cn 这样的URL手动选择要访问的国家站点。
缓存策略很重要
因为GeoIP查询还是有一定开销的,建议合理设置缓存:
# 在http块中设置
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=geo_cache:10m max_size=100m inactive=60m;
server {
listen 80;
server_name example.com;
# 缓存策略
location / {
proxy_cache geo_cache;
proxy_cache_key "$scheme$request_method$host$request_uri$geoip_country_code";
proxy_cache_valid 200 10m;
# 其他配置...
}
}调试和测试技巧
开发过程中,调试GeoIP功能还是有点麻烦的,因为你的IP通常是固定的。这里有几个技巧:
# 添加调试location
location /debug {
add_header Content-Type text/plain;
return 200 "Your IP: $remote_addr\nCountry: $geoip_country_code\nCountry Name: $geoip_country_name\nPath: $country_path\nLang: $lang";
}
# 允许通过header模拟不同国家
location / {
if ($http_x_debug_country) {
set $geoip_country_code $http_x_debug_country;
}
# 其他配置...
}测试的时候可以这样:
# 正常访问看到的国家信息
curl http://example.com/debug
# 模拟美国用户
curl -H "X-Debug-Country: US" http://example.com/debug性能监控不能忽视
这种基于GeoIP的路由会增加一些延迟,建议加上监控:
# 记录处理时间
log_format geo_log '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $request_time $geoip_country_code';
server {
access_log /var/log/nginx/geo_access.log geo_log;
# 其他配置...
}然后可以用awstats或者其他日志分析工具来分析不同国家用户的访问情况和响应时间。
遇到的坑和解决方案
部署过程中踩了几个坑,分享给大家避免:
坑1:GeoIP数据库更新问题
MaxMind的免费数据库每月更新,如果不及时更新,可能会出现IP识别不准确的问题。建议写个定时脚本:
#!/bin/bash
# update_geoip.sh
cd /etc/nginx/geoip
wget -O GeoLite2-Country-new.tar.gz "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_LICENSE_KEY&suffix=tar.gz"
tar -xzf GeoLite2-Country-new.tar.gz
cp GeoLite2-Country_*/GeoLite2-Country.mmdb ./GeoLite2-Country.mmdb.new
mv GeoLite2-Country.mmdb.new GeoLite2-Country.mmdb
nginx -s reload
rm -rf GeoLite2-Country_* GeoLite2-Country-new.tar.gz加到crontab里每月执行一次:
0 2 1 * * /path/to/update_geoip.sh坑2:CDN和负载均衡器的IP问题
如果网站使用了CDN,Nginx获取到的IP是CDN节点的IP,不是真实用户IP。需要配置:
set_real_ip_from 103.21.244.0/22; # Cloudflare IP段,根据实际CDN调整
real_ip_header CF-Connecting-IP; # Cloudflare的真实IP头坑3:移动网络的IP识别
移动网络的IP经常变动,而且有些运营商会用代理,导致地理位置识别不准。这种情况下可以结合浏览器的地理位置API:
// 前端JavaScript辅助识别
navigator.geolocation.getCurrentPosition(function(position) {
fetch('/api/update-location', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
lat: position.coords.latitude,
lng: position.coords.longitude
})
});
});安全性考虑
这种基于地理位置的访问控制还有安全方面的考虑:
# 限制某些国家访问敏感接口
location /admin {
# 只允许特定国家访问
if ($geoip_country_code !~ "^(US|CN|JP)$") {
return 403 "Access denied from your location";
}
# 其他安全配置
auth_basic "Admin Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}
# 对某些国家做更严格的频率限制
http {
# 为不同国家设置不同的限制策略
map $geoip_country_code $rate_limit_key {
default $binary_remote_addr;
~^(CN|RU|KP)$ $binary_remote_addr; # 对某些国家使用更严格限制
}
limit_req_zone $rate_limit_key zone=by_country:10m rate=1r/s;
server {
location / {
limit_req zone=by_country burst=5;
# 其他配置...
}
}
}实际使用下来,这套方案还是很稳定的。我们线上跑了几个月,基本没出过什么问题。用户体验也提升不少,不同国家的用户都能看到符合本地化需求的内容。
当然,如果你的网站访问量特别大,也可以考虑把GeoIP判断逻辑前置到CDN层面,比如用Cloudflare的Workers或者AWS的Lambda@Edge来实现,这样可以进一步减少源站的压力。
总的来说,Nginx的GeoIP功能还是很强大的,配合一些创意可以实现很多有意思的功能。比如我见过有人用它来做A/B测试,不同地区的用户看到不同版本的页面,然后统计转化率。
希望这篇文章对你有帮助!如果在实施过程中遇到问题,欢迎交流讨论。记得关注@运维躬行录,我会继续分享更多实用的运维技术和踩坑经验。觉得文章有用的话,别忘了转发给更多需要的朋友!
公众号:运维躬行录
个人博客:躬行笔记