一、背景
当我们在做全球业务时,例如网站/机器/游戏,为了支撑全球用户,我们会在不同的位置搭建节点,以满足业务功能和响应实效。
我们会搭建一个这样的架构满足初步的使用。
能用是ok的了,会有什么问题呢?
1、因为服务独立,上架1个SKU需要多次发布到不同的服务。
2、因为域名过多,发布时需要频繁的退出、登录切换不同的二级域名,非常繁琐不说,还可能会遗漏。
3、后期处理数据统计、报表、看板、设备地图,耗时费力。
4、这还是单节点,未考虑高可用的情况下,多区域、多节点部署服务器导致运维难度增加、过多的财务开支。
5、各个节点各玩各的。
有什么方式可以解决这些问题?
AWS服务组件很好支持。
二、AWS架构
AWS提供了非常丰富的组件,列几个常用的:
1、Route 53 负责域名解析流量管理健康检查,高可用可以用到它。
2、VPC 虚拟私有网络,可将同一区域的服务器、数据库置于同一内网段,提升访问速度。
3、CloudFront CDN加速,可以给访问请求、对象存储加速。函数功能极其强大,可配置HTTP、HTTPS的请求跨域,可识别全球流量来源,根据来源分发到不同区域的节点。
4、EC2 服务器,内含了负载均衡器ELB、目标组,可实现同一地区的服务器的负载均衡和高可用。结合CloudFront可将流量分发到就近的服务。
5、Aurora and RDS 数据库,Aurora 作为全球数据库,已实现主从备份,可在1s内实现全球数据同步,结合Route 53的短域名实现读写分离。
6、S3 对象存储,OSS存储图片、视频、文档、音频等。
7、Certificate Manager 安全凭证管理,可申请和管理证书。
架构完是这样的。
三、干活,简单的一笔带过
1、将外部域名解析到Route 53。
2、在Certificate Manager申请证书。
3、在拟定的区域创建VPC内网。
4、购买服务器和数据库时选择上面的创建的VPC内网。
5、在创建Aurora数据库时需要选确认某一个区域为主集群,主集群下会创建写入器实例、读取器实例。
再创建其它区域的读取实例,如下图:
6、在Route 53创建一个私有的短域名,用于数据库连接,无需在域名供应商购买。我这里用 db.com
7、将二级域名指向到Aurora的写域名指向到Aurora的写入器实例的DNS(路由策略:简单),
读域名指向到多个读取器实例的DNS(路由策略:延迟)。数据写入到主库后,1s内可同步到所有的从库
8、在EC2购买服务器后,新建目标组,将服务器添加到目标组中
9、在EC2创建负载均衡器,这里分NLB(服务器之间用)和ELB(服务器内部服务之间用),层级和颗粒度不同。
这里创建NLB,添加转发到的目标组到侦听器,注意选择VPC。创建完即可通过DNS访问。
10、创建CloudFront,将需要经过CDN加速的域名添加到备用域名,添加第2步创建的证书,源添加到访问目标的DNS。成功后CloudFront会分配域名。
11、在Route 53将经过CloudFront加速的域名指向到CloudFront分配域名上。
12、在Route 53创建NLB的健康检查,成功后生成ID。
13、在Route 53添加故障转移域名,用于服务区域之间的高可用,无需额外采购服务器
14、在CloudFront创建函数,将请求按照国家和地区分发到相应的服务器。
代码如下:
import cf from 'cloudfront';
function handler(event) {
const request = event.request;
const headers = request.headers;
const country = headers['cloudfront-viewer-country'] && headers['cloudfront-viewer-country'].value;
// List of countries to ALB endpoints
const countryToContinent = {
// 亚太地区 (Asia-Pacific) -> Asia
'AF': 'Asia',
'AM': 'Asia',
'AZ': 'Asia',
'BD': 'Asia',
'BN': 'Asia',
'BT': 'Asia',
'CC': 'Asia',
'CK': 'Asia',
'CX': 'Asia',
'GE': 'Asia',
'HK': 'Asia',
'ID': 'Asia',
'IN': 'Asia',
'IO': 'Asia',
'JP': 'Asia',
'KG': 'Asia',
'KH': 'Asia',
'KR': 'Asia',
'KZ': 'Asia',
'LA': 'Asia',
'LK': 'Asia',
'MM': 'Asia',
'MN': 'Asia',
'MO': 'Asia',
'MP': 'Asia',
'MV': 'Asia',
'MY': 'Asia',
'NC': 'Asia',
'NF': 'Asia',
'NP': 'Asia',
'NR': 'Asia',
'NU': 'Asia',
'NZ': 'Asia',
'PH': 'Asia',
'PK': 'Asia',
'PN': 'Asia',
'SB': 'Asia',
'SG': 'Asia',
'TH': 'Asia',
'TJ': 'Asia',
'TL': 'Asia',
'TM': 'Asia',
'TO': 'Asia',
'TV': 'Asia',
'TW': 'Asia',
'UZ': 'Asia',
'VU': 'Asia',
'WF': 'Asia',
'WS': 'Asia',
'YE': 'Asia',
// 大洋洲 (Oceania) - 通常归类为亚太地区
'AS': 'Asia',
'AU': 'Asia',
'FJ': 'Asia',
'PF': 'Asia',
'GU': 'Asia',
'KI': 'Asia',
'MH': 'Asia',
'FM': 'Asia',
'PW': 'Asia',
'PG': 'Asia',
'TK': 'Asia',
// 南极洲 (Antarctica) - 通常单独处理,这里暂时归入亚太
'AQ': 'Asia',
'BV': 'Asia',
'GS': 'Asia',
'HM': 'Asia',
'TF': 'Asia',
// 欧洲、非洲、中东 -> Europe
'AL': 'Europe',
'AD': 'Europe',
'AT': 'Europe',
'AX': 'Europe',
'BA': 'Europe',
'BE': 'Europe',
'BG': 'Europe',
'BY': 'Europe',
'CH': 'Europe',
'CY': 'Europe',
'CZ': 'Europe',
'DE': 'Europe',
'DK': 'Europe',
'EE': 'Europe',
'ES': 'Europe',
'FI': 'Europe',
'FO': 'Europe',
'FR': 'Europe',
'GG': 'Europe',
'GI': 'Europe',
'GR': 'Europe',
'HR': 'Europe',
'HU': 'Europe',
'IE': 'Europe',
'IM': 'Europe',
'IS': 'Europe',
'IT': 'Europe',
'JE': 'Europe',
'LI': 'Europe',
'LT': 'Europe',
'LU': 'Europe',
'LV': 'Europe',
'MC': 'Europe',
'MD': 'Europe',
'ME': 'Europe',
'MK': 'Europe',
'MT': 'Europe',
'NL': 'Europe',
'NO': 'Europe',
'PL': 'Europe',
'PT': 'Europe',
'RO': 'Europe',
'RS': 'Europe',
'RU': 'Europe',
'SE': 'Europe',
'SI': 'Europe',
'SJ': 'Europe',
'SK': 'Europe',
'SM': 'Europe',
'UA': 'Europe',
'VA': 'Europe',
'GB': 'Europe',
'DZ': 'Europe',
'AO': 'Europe',
'BJ': 'Europe',
'BW': 'Europe',
'BF': 'Europe',
'BI': 'Europe',
'CV': 'Europe',
'CM': 'Europe',
'CF': 'Europe',
'TD': 'Europe',
'KM': 'Europe',
'CG': 'Europe',
'CD': 'Europe',
'CI': 'Europe',
'DJ': 'Europe',
'EG': 'Europe',
'GQ': 'Europe',
'ER': 'Europe',
'ET': 'Europe',
'GA': 'Europe',
'GM': 'Europe',
'GH': 'Europe',
'GN': 'Europe',
'GW': 'Europe',
'KE': 'Europe',
'LS': 'Europe',
'LR': 'Europe',
'LY': 'Europe',
'MG': 'Europe',
'MW': 'Europe',
'ML': 'Europe',
'MR': 'Europe',
'MU': 'Europe',
'YT': 'Europe',
'MA': 'Europe',
'MZ': 'Europe',
'NA': 'Europe',
'NE': 'Europe',
'NG': 'Europe',
'RW': 'Europe',
'RE': 'Europe',
'SH': 'Europe',
'SN': 'Europe',
'SC': 'Europe',
'SL': 'Europe',
'SO': 'Europe',
'ZA': 'Europe',
'SS': 'Europe',
'SD': 'Europe',
'ST': 'Europe',
'SZ': 'Europe',
'TG': 'Europe',
'TN': 'Europe',
'TZ': 'Europe',
'UG': 'Europe',
'EH': 'Europe',
'ZM': 'Europe',
'ZW': 'Europe',
'AE': 'Europe',
'BH': 'Europe',
'IR': 'Europe',
'IQ': 'Europe',
'IL': 'Europe',
'JO': 'Europe',
'KW': 'Europe',
'LB': 'Europe',
'OM': 'Europe',
'PS': 'Europe',
'QA': 'Europe',
'SA': 'Europe',
'SY': 'Europe',
'TR': 'Europe',
// 美洲 -> America
'AI': 'America',
'AG': 'America',
'AW': 'America',
'BS': 'America',
'BB': 'America',
'BZ': 'America',
'BM': 'America',
'BQ': 'America',
'CA': 'America',
'KY': 'America',
'CR': 'America',
'CU': 'America',
'CW': 'America',
'DM': 'America',
'DO': 'America',
'SV': 'America',
'GD': 'America',
'GL': 'America',
'GP': 'America',
'GT': 'America',
'HT': 'America',
'HN': 'America',
'JM': 'America',
'MQ': 'America',
'MX': 'America',
'MS': 'America',
'NI': 'America',
'PA': 'America',
'PR': 'America',
'BL': 'America',
'KN': 'America',
'LC': 'America',
'MF': 'America',
'PM': 'America',
'VC': 'America',
'SX': 'America',
'TT': 'America',
'TC': 'America',
'US': 'America',
'UM': 'America',
'UY': 'America',
'VE': 'America',
'VG': 'America',
'VI': 'America'
};
const continentToRegion = {
'Asia': 'asia.autovxxxxxx.com', // 亚洲故障转移域名
'Europe': 'europu.autovxxxxxx.com', // 欧洲故障转移域名
'America': 'america.autovxxxxxxx.com' // 美洲故障转移域名
};
const DEFAULT_REGION = 'NLB-Asia-Singapore-5e42xxxxxxxx4853.elb.ap-southeast-1.amazonaws.com'; //改为自己的默认alb 域名
const targetContinent = (country && countryToContinent[country]) || 'Asia';
// const targetContinent = 'Asia';
const targetOrigin = (targetContinent && continentToRegion[targetContinent]) || DEFAULT_REGION;
// 打印所有请求头
console.log("=== 所有请求头信息 ===");
for (var headerName in headers) {
if (headers.hasOwnProperty(headerName)) {
// headers 对象的值是一个包含 'value' 属性的对象
var headerValue = headers[headerName].value;
console.log(headerName + ": " + headerValue);
}
}
console.log('targetOrigin: ' + targetOrigin);
// 使用CloudFront 2.0 API修改origin
cf.updateRequestOrigin({
"domainName": targetOrigin,
"port": 80,
"protocol": 'http',
"timeouts": {
"readTimeout": 30,
"connectionTimeout": 5
}
});
// 添加调试信息 - 确保值是字符串
request.headers['x-debug-country'] = { value: String(country || 'unknown') };
request.headers['x-debug-targetorigin'] = { value: String(targetOrigin) };
return request;
}
解决跨域问题,函数代码如下:
function handler(event) {
var response = event.response;
var headers = response.headers;
// If Access-Control-Allow-Origin CORS header is missing, add it.
// Since JavaScript doesn't allow for hyphens in variable names, we use the dict["key"] notation.
if (!headers['access-control-allow-origin']) {
headers['access-control-allow-origin'] = {value: "*"};
console.log("Access-Control-Allow-Origin was missing, adding it now.");
headers['access-control-allow-headers'] = {value: "Origin, X-Requested-With, Content-Type, Accept"};
headers['access-control-allow-methods'] = {value: "GET,POST,PUT, OPTIONS"};
}
return response;
}
ok,完事儿