在微服務系列的這篇文章中,我們將討論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 User = userDb.model(User, new mongoose.Schema ({
username: String,
password: String,
roles: [ String ]
}));
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網關是任何基於微服務的架構的重要組成部分。 可以以方便且通用的方式處理諸如認證,負載平衡,依賴性解析,數據轉換和動態請求調度之類的橫切關注點。 然後,微服務可以專註於他們的特定任務,而無需重複代碼。 這使得每個微服務的開發更容易和更快速。


推薦閱讀:
相關文章