在微服务系列的这篇文章中,我们将讨论API网关以及它们如何帮助我们解决基于微服务架构的一些重要问题。我们在本系列的第一篇文章中描述了这些和其他问题。
什么是API网关以及为什么要使用它?
在所有基于服务的体系结构中,有几个关注点在所有(或大多数)服务之间共享。基于微服务的架构也不例外。正如我们在第一篇文章中所说,微服务几乎是孤立开发的。交叉问题由软件堆栈中的上层处理。API网关是其中一个层。以下是API网关处理的常见问题列表:
- 认证
- 运输安全
- 负载均衡
- 请求调度(包括容错和服务发现)
- 依赖性解决方案
- 运输转型
认证
大多数网关对每个请求(或一系列请求)执行某种身份验证。根据特定于每个服务的规则,网关将请求路由到所请求的微服务或返回错误代码(或更少的信息)。大多数网关在将请求传递给后面的微服务时将身份验证信息添加到请求中。这允许微服务在需要时实现用户特定的逻辑。
安全
许多网关作为公共API的单一入口点。在这种情况下,网关处理传输安全性,然后通过使用不同的安全通道或通过删除内部网络内不必要的安全约束来分派请求。例如,对于RESTful HTTP API,网关可以执行“SSL终止”:在客户端和网关之间建立安全SSL连接,然后通过非SSL连接将代理请求发送到内部服务。
“许多网关作为公共API的单一入口点。”
负载均衡
在高负载情况下,网关可以根据自定义逻辑在微服务实例之间分发请求。每项服务可能都有特定的扩展限制。网关旨在通过考虑这些限制来平衡负载。例如,某些服务可能通过在不同的内部端点下运行多个实例来扩展。网关可以将请求分派给这些端点(甚至请求更多端点的动态实例化)来处理负载。
请求调度
即使在正常负载情况下,网关也可以为调度请求提供自定义逻辑。在大型体系结构中,随着团队工作或生成新的微服务实例(例如,由于拓扑更改),会添加和删除内部端点。网关可以与服务注册/发现过程或描述如何分派每个请求的数据库协同工作。这为开发团队提供了出色的灵活性。此外,故障服务可以路由到备份或通用服务,这些服务允许请求完成而不是完全失败。
依赖性解决方案
由于微服务处理非常具体的问题,一些基于微服务的架构往往变得“健谈”:要执行有用的工作,需要将许多请求发送到许多不同的服务。出于方便和性能的原因,网关可以提供在内部路由到许多不同微服务的外观(“虚拟”端点)。
传输转换
正如我们在本系列的第一篇文章中所了解到的那样,微服务通常是孤立开发的,开发团队在选择开发平台时具有很大的灵活性。这可能导致微服务返回数据并使用对于网关另一侧的客户端不方便的传输。网关必须执行必要的转换,以便客户端仍然可以与其后面的微服务进行通信。
API网关示例
我们的示例是一个简单的node.js网关。它处理HTTP请求并将它们转发到适当的内部端点(在传输过程中执行必要的转换)。它处理以下问题:
认证
使用JWT进行身份验证。单个端点处理初始身份验证:/ login。用户详细信息存储在Mongo数据库中,对端点的访问受角色限制。
* Simple login: returns a JWT if login data is valid.
*/function doLogin(req, res) {
getData(req).then(function(data) { try { var loginData = JSON.parse(data);
User.findOne({ username: loginData.username }, function(err, user) { if(err) {
logger.error(err);
send401(res); return;
} if(user.password === loginData.password) { var token = jwt.sign({ jti: uuid.v4(), roles: user.roles
}, secretKey, { subject: user.username, issuer: issuerStr
});
res.writeHeader(200, { 'Content-Length': token.length, 'Content-Type': "text/plain"
});
res.write(token);
res.end();
} else {
send401(res);
}
}, 'users');
} catch(err) {
logger.error(err);
send401(res);
}
}, function(err) {
logger.error(err);
send401(res);
});
}/*
* Authentication validation using JWT. Strategy: find existing user.
*/function validateAuth(data, callback) { if(!data) {
callback(null); return;
}
data = data.split(" "); if(data[0] !== "Bearer" || !data[1]) {
callback(null); return;
} var token = data[1];
try { var payload = jwt.verify(token, secretKey); //Your custom validation logic goes here.
if(!payload.jti || revokedTokens[payload.jti]) {
logger.debug('Revoked token, access denied: ' + payload.jti);
callback(null);
} else {
callback({jwt: payload});
}
} catch(err) {
logger.error(err);
callback(null);
}
}
免责声明:此帖中显示的代码未准备好生产。它仅用于显示概念。不要盲目复制粘贴:)
传输安全
传输安全性通过TLS处理:所有公共请求首先由具有样本证书的反向nginx代理设置接收。
负载均衡
负载平衡由nginx处理。请参阅示例配置。
动态调度,数据聚合和故障
根据存储在数据库中的配置动态调度请求。支持两种类型的请求:HTTP和AMQP。
请求还支持在多个微服务之间拆分请求的聚合策略:单个公共端点可以聚合来自许多不同内部端点(微服务)的数据。所有返回的数据都是JSON格式。看看Netflix关于这个策略如何帮助他们实现更好性能的优秀帖子。另请查看我们关于Falcor的帖子,该帖子允许从多个来源轻松获取数据。
通过记录错误并返回少于请求的信息来处理失败的内部请求。
* Parses the request and dispatches multiple concurrent requests to each
* internal endpoint. Results are aggregated and returned.
*/function serviceDispatch(req, res) { var parsedUrl = url.parse(req.url);
Service.findOne({ url: parsedUrl.pathname }, function(err, service) { if(err) {
logger.error(err);
send500(res); return;
} var authorized = roleCheck(req.context.authPayload.jwt, service); if(!authorized) {
send401(res); return;
}
// Fanout all requests to all related endpoints.
// Results are aggregated (more complex strategies are possible).
var promises = [];
service.endpoints.forEach(function(endpoint) {
logger.debug(sprintf('Dispatching request from public endpoint ' + '%s to internal endpoint %s (%s)',
req.url, endpoint.url, endpoint.type)); switch(endpoint.type) { case 'http-get': case 'http-post':
promises.push(httpPromise(req, endpoint.url,
endpoint.type === 'http-get')); break; case 'amqp':
promises.push(amqpPromise(req, endpoint.url)); break; default:
logger.error('Unknown endpoint type: ' + endpoint.type);
}
}); //Aggregation strategy for multiple endpoints.
Q.allSettled(promises).then(function(results) { var responseData = {};
results.forEach(function(result) { if(result.state === 'fulfilled') {
responseData = _.extend(responseData, result.value);
} else {
logger.error(result.reason.message);
}
});
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(responseData));
});
}, 'services');
}
角色检查
}));var Service = servicesDb.model('Service', new mongoose.Schema ({ name: String, url: String, endpoints: [ new mongoose.Schema({ type: String, url: String
}) ], authorizedRoles: [ String ]
}));function roleCheck(jwt_, service) { var intersection = _.intersection(jwt_.roles, service.authorizedRoles); return intersection.length === service.authorizedRoles.length;
}
传输和数据转换
执行传输转换以在HTTP和AMQP请求之间进行转换。
日志
日志记录是集中的:所有日志都发布到控制台和内部消息总线。在消息总线上侦听的其他服务可以根据这些日志采取措施。
获取完整代码。
旁白:webtask和Auth0如何实现这些模式?
我们在系列的第一篇文章中告诉过你关于webtasks的事情。由于webtasks是微服务,它们也在网关后面运行。webtasks网关处理身份验证,动态调度和集中式日志记录,因此您也没有。
- 对于身份验证,Auth0是令牌的发布者,webtask将验证这些令牌。它们之间存在信任关系,因此可以验证令牌。
- 对于实时日志记录,webtask实现了无状态弹性ZeroMQ架构,该架构可在整个集群中运行。
- 对于动态调度,有一个定制的Node.js代理,它使用CoreOS etcd作为pub-sub机制来相应地路由webtasks。
结论
API网关是任何基于微服务的架构的重要组成部分。可以以方便且通用的方式处理诸如认证,负载平衡,依赖性解析,数据转换和动态请求调度之类的横切关注点。然后,微服务可以专注于他们的特定任务,而无需重复代码。这使得每个微服务的开发更容易和更快速。