Azure Front Door无法基于延迟上传文件至源组问题排查
Azure Front Door 上传目标不符合预期排查
问题背景
- 在Azure Front Door的单个源组中添加了4个Blob存储源,测试Blob SAS令牌,未修改权重、优先级等设置,健康探针已启用
- 用户登录时通过代码计算各存储账户的延迟,上传时尝试选择延迟最低的账户,但实际Front Door始终将文件上传至最后添加的存储账户
用户登录时的延迟计算代码
import { azurePingContainers } from "@/constants/azure-containers"; import axios from "axios"; export async function checkAzureLatency() { const response = await Promise.all(azurePingContainers.map(container => { return new Promise<{ latency: number, account: string }>((resolve) => { const startTime = performance.now(); axios.get(container.url) .then(() => { const endTime = performance.now(); let latency = endTime - startTime; console.log(`${container.account} latency: ${latency}ms`); latency = parseFloat(latency.toFixed(2)); resolve({ latency, account: container.account }); }) .catch(error => { console.error(`Error fetching ${container.account} latency: ${error}`); resolve({ latency: Infinity, account: container.account }); }); }) })) const validResponses = response.filter(result => result.latency !== Infinity) if (validResponses.length === 0) { return null; } return validResponses.sort((a, b) => a.latency - b.latency) } export async function averageLatency() { const latencyChecks = (await Promise.all([ checkAzureLatency(), checkAzureLatency(), checkAzureLatency() ])).filter(result => result !== null) if (latencyChecks.length === 0) { return null } console.log(latencyChecks) const totalLatencyArray = latencyChecks.reduce((acc, val) => { val.forEach((current) => { const sameValue = acc.findIndex((x) => x.account === current.account) if (sameValue>-1) { acc[sameValue] = {latency: parseFloat((current.latency + acc[sameValue].latency).toFixed(2)) ,account: current.account} } else { acc.push(current) } }) return acc }, []) console.log('totalLatencyArray', totalLatencyArray) const averageLatencyArray = totalLatencyArray.map(item => ({ latency: parseFloat((item.latency / latencyChecks.length).toFixed(2)), account: item.account })); console.log(averageLatencyArray) localStorage.setItem('latency', JSON.stringify(averageLatencyArray)) return averageLatencyArray }
上传处理代码
const getSasToken = async (container: string, account: string, ipAddress: string, fileType: string) => { const tokenResponse = await ax.post( `storage/sas`, { container, account, ipAddress, fileType, }, { headers: { ...authHeader(), }, }, ); if (tokenResponse.status !== 201 || !tokenResponse.data?.data?.sasToken) { console.error("Failed to get SAS token from backend!"); return null; } const tokensResponseData = tokenResponse.data.data; const { sasToken } = tokensResponseData return sasToken } const handleUpload = async (container: string, account: string, ipAddress: string, file: File) => { const token = await getSasToken(container, account, ipAddress, file.type) if (!token) return null const blobService = new BlobService(); return await blobService.uploadFile(file, token, container, account); } export const handleVideoUpload = async (file: File, container: string) => { try { let retryCounts = 0 const ipAddress = "192.something"; // await ax // .get("http://api.ipify.org/?format=json") // .then((response) => response.data) // .then((data) => { // if (data) { // ipAddress = data.ip; // } // }); if (file) { let leastLatencyAccounts = JSON.parse(localStorage.getItem('latency') || '[]') if (leastLatencyAccounts && leastLatencyAccounts?.length > 0) { const len = leastLatencyAccounts.length for (const accountInfo of leastLatencyAccounts) { if (len >= 5 && retryCounts === 3 || len <= 4 && retryCounts === 2 || len === retryCounts) { console.error("Failed to upload file with retries") return null } const response = await handleUpload(container, accountInfo.account, ipAddress, file) if (!response) { console.error(`Failed to upload the file with account storage ${accountInfo.account}!`); leastLatencyAccounts.shift() localStorage.setItem('latency', JSON.stringify(leastLatencyAccounts)) retryCounts++; continue; } return response; } } else { const response = await handleUpload(container, azurePingContainers[ 0 ].account, ipAddress, file) if (!response) { console.error(`Failed to upload the file with account storage ${azurePingContainers[ 0 ].account}!`); } return response; } } } catch (error) { console.error("There was an error uploading the file!", error); } };
上传服务代码
export class BlobService { constructor() { } async uploadFile(file: File, token: string, container: string, account: string) { const fileName = this.generateFileName(file); try { const response = await this.uploadToBlobStorage(file, token, account, container, fileName); return { response, fileName }; } catch (e) { console.error('Error processing token:', e); return null } } generateFileName(file: File): string { return `${file.name}-${Date.now()}.${file.name.split('.').pop()}`; } async uploadToBlobStorage(file: File, token: string, account: string, container: string, fileName: string): Promise<{ responseBlob: BlobUploadCommonResponse, account: string } | null> { const blobServiceClient = new BlobServiceClient(`https://${account}.blob.core.windows.net?${token}`); const containerClient = blobServiceClient.getContainerClient(container); const blockBlobClient = containerClient.getBlockBlobClient(fileName); const responseBlob = await this.upload(file, blockBlobClient); if (responseBlob.errorCode) { console.warn('Error in responseBlob:', responseBlob.errorCode); return null; } return { responseBlob, account }; } async upload(file: File, blockBlobClient: BlockBlobClient) { console.log('Starting upload...'); const response = await blockBlobClient.uploadData(file, { blobHTTPHeaders: { blobContentType: file.type }, concurrency: 20 }); store.dispatch(setProgressValue(100)) return response } }
后端代码
async generateSAS(createSasTokenDto: GenerateSasTokenDto) { try { const { container, fileType, ipAddress ,account} = createSasTokenDto const permissions = new ContainerSASPermissions() permissions.write = true const blobServiceClient= await this.getGenericServiceClient(account) const blobContainerClient = await this.getGenericContainerClient(container, blobServiceClient) const zeroedSeconds = new Date().setSeconds(0) const options = { expiresOn: new Date(Date.now() + (1000 * 60 * 60 * 24 * 120)), contentType: fileType, permissions, startsOn: new Date(zeroedSeconds), protocol: SASProtocol.Https } const sasToken = await blobContainerClient.generateSasUrl(options) return { sasToken:sasToken.split(`${container}?`)[1], } } catch (error) { console.error(error) throw new Error(error.message || "Failed to create to SAS") } } async getGenericServiceClient(account:string) { let accountKey=null; switch (account) { case AZURE_STORAGE_ACCOUNTS.US_EAST_2: accountKey = this.configService.getOrThrow('azure-connection-storage-eastus2-key') break; case AZURE_STORAGE_ACCOUNTS.UAE_NORTH: accountKey = this.configService.getOrThrow('azure-connection-storage-uaenorth-key') break; default: throw new Error('Invalid Azure Storage Account') } const url = `https://${account}.blob.core.windows.net` console.log(account,accountKey) return new BlobServiceClient(url,new StorageSharedKeyCredential(account,accountKey)) } async getGenericContainerClient(container: string, blobServiceClient: BlobServiceClient) { return blobServiceClient.getContainerClient(container) }
预期与实际结果
- 预期:文件上传至对终端用户而言最优、最近的存储账户
- 实际:Front Door始终将文件上传至最后添加的存储账户
内容的提问来源于stack exchange,提问作者Sufian Majid
相关产品推荐
相关产品推荐

