乐闻世界logo
搜索文章和话题

面试题手册

Promise.allSettled() 的作用是什么?与 Promise.all() 有什么区别?

Promise.allSettled() 是 ES2020 引入的 Promise 静态方法,它允许我们等待所有 Promise 完成(无论成功或失败),并返回每个 Promise 的状态和结果。基本概念Promise.allSettled() 接收一个 Promise 数组作为参数,返回一个新的 Promise。这个新的 Promise 会在所有输入的 Promise 都完成(无论成功或失败)后才完成,返回一个包含所有 Promise 状态和结果的数组。基本用法const promise1 = Promise.resolve(42);const promise2 = Promise.reject('出错了');const promise3 = new Promise(resolve => setTimeout(() => resolve('延迟完成'), 1000));Promise.allSettled([promise1, promise2, promise3]) .then(results => { console.log(results); // 输出: // [ // { status: 'fulfilled', value: 42 }, // { status: 'rejected', reason: '出错了' }, // { status: 'fulfilled', value: '延迟完成' } // ] });返回值结构每个结果对象包含两个属性:status: 'fulfilled'(成功)或 'rejected'(失败)value: 成功时的值(仅当 status 为 'fulfilled' 时存在)reason: 失败时的原因(仅当 status 为 'rejected' 时存在)与 Promise.all() 的对比Promise.all() - 快速失败const promise1 = Promise.resolve(1);const promise2 = Promise.reject('错误');const promise3 = Promise.resolve(3);Promise.all([promise1, promise2, promise3]) .then(results => console.log(results)) .catch(error => console.error('错误:', error)); // 输出: 错误: 错误 // promise3 的结果无法获取Promise.allSettled() - 等待全部const promise1 = Promise.resolve(1);const promise2 = Promise.reject('错误');const promise3 = Promise.resolve(3);Promise.allSettled([promise1, promise2, promise3]) .then(results => { results.forEach(result => { if (result.status === 'fulfilled') { console.log('成功:', result.value); } else { console.error('失败:', result.reason); } }); }); // 输出: // 成功: 1 // 失败: 错误 // 成功: 3实际应用场景1. 批量请求,部分失败不影响其他结果async function fetchMultipleUrls(urls) { const promises = urls.map(url => fetch(url).then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) ); const results = await Promise.allSettled(promises); const successful = results .filter(result => result.status === 'fulfilled') .map(result => result.value); const failed = results .filter(result => result.status === 'rejected') .map(result => result.reason); console.log('成功的请求:', successful.length); console.log('失败的请求:', failed.length); return { successful, failed };}// 使用示例const urls = [ 'https://api.example.com/users', 'https://api.example.com/posts', 'https://api.example.com/comments'];fetchMultipleUrls(urls).then(({ successful, failed }) => { console.log('成功数据:', successful); console.log('失败原因:', failed);});2. 并行处理多个任务,收集所有结果async function processTasks(tasks) { const results = await Promise.allSettled( tasks.map(task => task()) ); return results.map((result, index) => ({ task: tasks[index].name, status: result.status, result: result.status === 'fulfilled' ? result.value : result.reason }));}// 使用示例const tasks = [ { name: '任务1', fn: () => Promise.resolve('完成') }, { name: '任务2', fn: () => Promise.reject('失败') }, { name: '任务3', fn: () => Promise.resolve('完成') }];processTasks(tasks).then(results => { console.table(results);});3. 图片加载,显示成功和失败的图片async function loadImages(imageUrls) { const imagePromises = imageUrls.map(url => { return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => resolve({ url, img }); img.onerror = () => reject(new Error(`Failed to load: ${url}`)); img.src = url; }); }); const results = await Promise.allSettled(imagePromises); const loadedImages = results .filter(result => result.status === 'fulfilled') .map(result => result.value); const failedUrls = results .filter(result => result.status === 'rejected') .map(result => result.reason.message); return { loadedImages, failedUrls };}// 使用示例const imageUrls = [ 'https://example.com/image1.jpg', 'https://example.com/image2.jpg', 'https://example.com/image3.jpg'];loadImages(imageUrls).then(({ loadedImages, failedUrls }) => { console.log('加载成功的图片:', loadedImages); console.log('加载失败的图片:', failedUrls);});错误处理处理部分失败async function fetchWithPartialFailure(urls) { const results = await Promise.allSettled( urls.map(url => fetch(url)) ); const successfulRequests = results .filter(result => result.status === 'fulfilled') .map(result => result.value); const failedRequests = results .filter(result => result.status === 'rejected') .map(result => result.reason); if (failedRequests.length > 0) { console.warn('部分请求失败:', failedRequests); } return successfulRequests;}重试失败的请求async function fetchWithRetry(urls, maxRetries = 3) { const results = await Promise.allSettled( urls.map(url => fetch(url)) ); const failedUrls = results .filter(result => result.status === 'rejected') .map((result, index) => urls[index]); if (failedUrls.length > 0) { console.log(`重试失败的请求 (${maxRetries} 次)`); const retryResults = await Promise.allSettled( failedUrls.map(url => fetch(url)) ); // 处理重试结果... } return results;}性能考虑1. 空数组处理Promise.allSettled([]) .then(results => console.log(results)); // 输出: []2. 非 Promise 值处理Promise.allSettled([1, 2, Promise.resolve(3)]) .then(results => console.log(results)); // 输出: // [ // { status: 'fulfilled', value: 1 }, // { status: 'fulfilled', value: 2 }, // { status: 'fulfilled', value: 3 } // ]与其他 Promise 方法的对比| 方法 | 行为 | 使用场景 ||------|------|----------|| Promise.all() | 全部成功才成功,一个失败就失败 | 需要所有结果都成功 || Promise.allSettled() | 等待全部完成,返回所有结果 | 需要知道每个操作的结果 || Promise.race() | 返回第一个完成的结果 | 需要最快的结果 || Promise.any() | 返回第一个成功的结果 | 需要任何一个成功的结果 |最佳实践1. 检查所有结果async function checkAllResults(promises) { const results = await Promise.allSettled(promises); const hasFailures = results.some(result => result.status === 'rejected'); if (hasFailures) { const failures = results .filter(result => result.status === 'rejected') .map(result => result.reason); console.error('部分操作失败:', failures); } return results;}2. 聚合结果async function aggregateResults(promises) { const results = await Promise.allSettled(promises); return { total: results.length, successful: results.filter(r => r.status === 'fulfilled').length, failed: results.filter(r => r.status === 'rejected').length, results: results };}3. 提供降级方案async function fetchWithFallback(urls, fallbackData) { const results = await Promise.allSettled( urls.map(url => fetch(url).then(r => r.json())) ); const data = results.map((result, index) => { if (result.status === 'fulfilled') { return result.value; } else { console.warn(`请求 ${urls[index]} 失败,使用降级数据`); return fallbackData[index]; } }); return data;}浏览器兼容性Promise.allSettled() 是 ES2020 引入的,现代浏览器都支持:Chrome: 76+Firefox: 71+Safari: 13+Edge: 79+对于旧浏览器,可以使用 polyfill:if (!Promise.allSettled) { Promise.allSettled = function(promises) { return Promise.all( promises.map(promise => Promise.resolve(promise).then( value => ({ status: 'fulfilled', value }), reason => ({ status: 'rejected', reason }) ) ) ); };}总结等待所有 Promise 完成:无论成功或失败,都会等待所有 Promise 完成返回详细结果:每个 Promise 的状态和结果都会被返回适合部分失败场景:当某些操作失败不影响其他操作时使用与 Promise.all() 互补:Promise.all() 快速失败,Promise.allSettled() 等待全部提供更好的错误处理:可以针对每个失败的操作进行单独处理支持 polyfill:旧浏览器可以通过 polyfill 实现兼容
阅读 0·2月22日 14:07

Promise 和回调函数的区别是什么?

Promise 和回调函数(Callback)都是 JavaScript 中处理异步操作的方式,但它们在设计理念、使用方式和代码可读性上有显著差异。回调函数基本概念回调函数是将一个函数作为参数传递给另一个函数,在异步操作完成后被调用。基本用法function fetchData(callback) { setTimeout(() => { const data = { name: 'John', age: 30 }; callback(null, data); }, 1000);}fetchData((error, data) => { if (error) { console.error('出错了:', error); return; } console.log('数据:', data);});回调地狱问题// 回调地狱:代码嵌套过深,难以维护fetch('/api/user', (error, userResponse) => { if (error) { console.error(error); return; } userResponse.json((error, user) => { if (error) { console.error(error); return; } fetch(`/api/posts/${user.id}`, (error, postsResponse) => { if (error) { console.error(error); return; } postsResponse.json((error, posts) => { if (error) { console.error(error); return; } console.log('用户文章:', posts); }); }); });});Promise基本概念Promise 是一个代表异步操作最终完成或失败的对象,提供了一种更优雅的方式来处理异步操作。基本用法function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { const data = { name: 'John', age: 30 }; resolve(data); }, 1000); });}fetchData() .then(data => console.log('数据:', data)) .catch(error => console.error('出错了:', error));链式调用// Promise 链式调用:代码扁平,易于阅读fetch('/api/user') .then(response => response.json()) .then(user => fetch(`/api/posts/${user.id}`)) .then(response => response.json()) .then(posts => console.log('用户文章:', posts)) .catch(error => console.error('出错了:', error));主要区别1. 代码可读性回调函数:// 嵌套过深,难以阅读doSomething1((result1) => { doSomething2(result1, (result2) => { doSomething3(result2, (result3) => { doSomething4(result3, (result4) => { console.log(result4); }); }); });});Promise:// 扁平清晰,易于阅读doSomething1() .then(result1 => doSomething2(result1)) .then(result2 => doSomething3(result2)) .then(result3 => doSomething4(result3)) .then(result4 => console.log(result4));2. 错误处理回调函数:// 错误处理分散,容易遗漏function fetchData(callback) { setTimeout(() => { if (Math.random() > 0.5) { callback(new Error('请求失败')); } else { callback(null, { data: 'success' }); } }, 1000);}fetchData((error, data) => { if (error) { console.error('错误:', error); return; } console.log('数据:', data);});Promise:// 错误处理集中,易于管理function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { if (Math.random() > 0.5) { reject(new Error('请求失败')); } else { resolve({ data: 'success' }); } }, 1000); });}fetchData() .then(data => console.log('数据:', data)) .catch(error => console.error('错误:', error));3. 状态管理回调函数:没有明确的状态概念回调可能被多次调用难以追踪异步操作的状态Promise:有明确的三种状态:pending、fulfilled、rejected状态一旦改变就不可逆可以随时查询 Promise 的状态const promise = new Promise((resolve) => { setTimeout(() => resolve('完成'), 1000);});console.log(promise); // Promise {<pending>}setTimeout(() => { console.log(promise); // Promise {<fulfilled>: '完成'}}, 1500);4. 并行处理回调函数:// 难以并行处理多个异步操作function fetchAllData(callback) { let completed = 0; const results = []; const urls = ['/api/user', '/api/posts', '/api/comments']; urls.forEach((url, index) => { fetch(url, (error, data) => { if (error) { callback(error); return; } results[index] = data; completed++; if (completed === urls.length) { callback(null, results); } }); });}Promise:// 轻松并行处理多个异步操作Promise.all([ fetch('/api/user'), fetch('/api/posts'), fetch('/api/comments')]) .then(responses => Promise.all(responses.map(r => r.json()))) .then(results => console.log('所有数据:', results)) .catch(error => console.error('错误:', error));5. 组合和复用回调函数:// 难以组合和复用function fetchUser(id, callback) { setTimeout(() => callback(null, { id, name: 'John' }), 1000);}function fetchPosts(userId, callback) { setTimeout(() => callback(null, [{ id: 1, title: 'Post 1' }]), 1000);}// 组合使用需要嵌套fetchUser(1, (error, user) => { if (error) return; fetchPosts(user.id, (error, posts) => { if (error) return; console.log({ user, posts }); });});Promise:// 易于组合和复用function fetchUser(id) { return Promise.resolve({ id, name: 'John' });}function fetchPosts(userId) { return Promise.resolve([{ id: 1, title: 'Post 1' }]);}// 组合使用清晰明了fetchUser(1) .then(user => Promise.all([user, fetchPosts(user.id)])) .then(([user, posts]) => console.log({ user, posts }));转换关系回转 Promise// 将回调函数转换为 Promisefunction promisify(fn) { return function(...args) { return new Promise((resolve, reject) => { fn(...args, (error, result) => { if (error) { reject(error); } else { resolve(result); } }); }); };}// 使用示例const readFile = promisify(fs.readFile);readFile('file.txt') .then(data => console.log(data)) .catch(error => console.error(error));Promise 转回调// 将 Promise 转换为回调函数function callbackify(promiseFn) { return function(...args) { const callback = args.pop(); promiseFn(...args) .then(result => callback(null, result)) .catch(error => callback(error)); };}// 使用示例const fetchDataCallback = callbackify(fetchData);fetchDataCallback((error, data) => { if (error) { console.error(error); return; } console.log(data);});性能对比内存占用回调函数:内存占用较小不需要创建额外的 Promise 对象Promise:内存占用略高每个 Promise 都是一个对象,需要额外的内存执行效率回调函数:执行效率略高没有额外的 Promise 对象创建和微任务调度Promise:执行效率略低需要创建 Promise 对象和调度微任务实际影响// 回调函数console.time('callback');for (let i = 0; i < 10000; i++) { setTimeout(() => {}, 0);}console.timeEnd('callback');// Promiseconsole.time('promise');for (let i = 0; i < 10000; i++) { Promise.resolve();}console.timeEnd('promise');使用场景适合使用回调函数的场景Node.js 核心模块:fs、http 等模块使用回调函数一次性简单操作:简单的异步操作不需要 Promise 的复杂性性能敏感场景:需要极致性能的场景// Node.js 文件读取fs.readFile('file.txt', 'utf8', (error, data) => { if (error) { console.error(error); return; } console.log(data);});适合使用 Promise 的场景复杂的异步流程:多个异步操作需要组合需要错误处理:需要集中处理错误现代 JavaScript 开发:async/await 语法糖前端开发:fetch API、现代浏览器 API// 使用 async/awaitasync function fetchData() { try { const user = await fetchUser(); const posts = await fetchPosts(user.id); return { user, posts }; } catch (error) { console.error('错误:', error); throw error; }}总结| 特性 | 回调函数 | Promise ||------|----------|---------|| 代码可读性 | 容易产生回调地狱 | 链式调用,代码扁平 || 错误处理 | 分散,容易遗漏 | 集中,易于管理 || 状态管理 | 无明确状态 | 三种明确状态 || 并行处理 | 需要手动管理 | Promise.all 等方法 || 组合复用 | 难以组合 | 易于组合 || 性能 | 略高 | 略低 || 内存占用 | 较小 | 略高 || 现代支持 | 传统方式 | 现代标准 |最佳实践优先使用 Promise:在现代 JavaScript 开发中,优先使用 Promise使用 async/await:让异步代码更像同步代码处理错误:总是添加错误处理避免嵌套:保持代码扁平合理使用工具:使用 Promise.all、Promise.race 等方法考虑性能:在性能敏感的场景,可以考虑回调函数
阅读 0·2月22日 14:07

RPC 和 RESTful API 有什么区别?在什么场景下选择 RPC?

RPC 和 RESTful API 是两种常见的分布式系统通信方式,它们各有优缺点:RPC(远程过程调用)特点:优势:高性能:使用二进制序列化(如 Protobuf),传输效率高强类型:通过 IDL(接口定义语言)定义服务契约,编译时检查低延迟:支持 HTTP/2 多路复用,减少连接开销双向流:支持双向流式通信,适合实时场景代码生成:自动生成客户端和服务端代码,减少开发工作量劣势:调试困难:二进制协议不易直接查看和调试学习曲线:需要学习特定的 RPC 框架和协议浏览器兼容性:部分 RPC 协议(如 gRPC)需要额外支持耦合度较高:客户端和服务端需要共享接口定义RESTful API 特点:优势:简单易用:基于 HTTP 协议,使用 JSON/XML 格式通用性强:跨平台、跨语言,浏览器原生支持易于调试:可以使用 curl、Postman 等工具直接测试松耦合:客户端和服务端通过 URL 和 HTTP 方法交互缓存友好:利用 HTTP 缓存机制,提高性能劣势:性能较低:文本格式(JSON)传输效率不如二进制冗余数据:每个请求都包含 HTTP 头,增加传输开销无状态:需要额外的机制维护会话状态实时性差:不适合需要实时双向通信的场景选择建议:内部微服务通信:优先选择 RPC(如 gRPC、Dubbo)对外 API:优先选择 RESTful API实时通信场景:选择支持流的 RPC 框架跨语言团队协作:考虑 RESTful API 的通用性性能敏感场景:选择 RPC 以获得更好的性能
阅读 0·2月22日 14:07

如何选择合适的 RPC 框架?主流 RPC 框架(gRPC、Dubbo、Thrift 等)的对比和选择建议是什么?

RPC 框架的选择需要根据项目需求、技术栈、团队经验等多方面因素综合考虑:主流 RPC 框架对比:1. gRPC特点:Google 开源,基于 HTTP/2 和 Protobuf优势:高性能:HTTP/2 多路复用,Protobuf 高效序列化跨语言:支持 10+ 种语言流式通信:支持单向流和双向流强类型:IDL 定义接口,编译时检查生态完善:拦截器、负载均衡、链路追踪劣势:浏览器支持有限(需要 grpc-web)学习曲线较陡调试相对困难(二进制协议)适用场景:微服务内部通信需要流式通信的场景跨语言服务调用高性能要求的场景技术栈:Go、Java、Python、C++、Node.js 等2. Dubbo特点:阿里巴巴开源,Java 生态优势:易用性:与 Spring 深度集成功能全面:服务治理、负载均衡、容错机制性能优秀:基于 Netty,支持长连接社区活跃:阿里巴巴和社区持续维护文档完善:中文文档丰富劣势:主要面向 Java跨语言支持相对较弱适用场景:Java 微服务架构国内企业项目需要完善服务治理的场景技术栈:Java、Spring Boot、Spring Cloud Alibaba3. Thrift特点:Facebook 开源,支持多种协议和传输方式优势:跨语言:支持多种编程语言灵活性:支持多种序列化格式和传输协议代码生成:强大的代码生成功能性能优秀:二进制序列化效率高劣势:学习曲线较陡文档相对较少社区活跃度不如 gRPC适用场景:跨语言、多协议的复杂场景需要灵活配置的场景技术栈:Java、Python、Go、C++、Node.js 等4. Spring Cloud OpenFeign特点:基于 HTTP 的声明式 RPC优势:简单易用:声明式接口定义Spring 集成:与 Spring Cloud 深度集成通用性强:基于 HTTP,跨平台易于调试:文本协议,易于查看劣势:性能相对较低(基于 HTTP/1.x)不支持流式通信适用场景:Spring Cloud 微服务架构对外 API性能要求不高的场景技术栈:Java、Spring Boot、Spring Cloud5. Motan特点:微博开源,Java RPC 框架优势:简单易用:配置简单性能优秀:基于 Netty支持多种协议:RPC、HTTP服务治理:支持服务注册、发现、负载均衡劣势:社区相对较小主要面向 Java适用场景:Java 微服务架构需要简单易用的 RPC 框架技术栈:Java、Spring Boot6. brpc特点:百度开源,C++ RPC 框架优势:高性能:C++ 实现,性能优秀功能全面:支持多种协议、服务治理跨语言:支持多语言客户端劣势:主要面向 C++学习曲线较陡适用场景:C++ 微服务架构高性能要求的场景技术栈:C++、Java、Python、Go 等选择建议:1. 根据技术栈选择Java 生态:Dubbo、Spring Cloud OpenFeign、MotanGo 生态:gRPC、ThriftPython 生态:gRPC、ThriftC++ 生态:gRPC、brpc、Thrift多语言:gRPC、Thrift2. 根据性能要求选择高性能:gRPC、Dubbo、brpc一般性能:Thrift、Motan低性能要求:Spring Cloud OpenFeign3. 根据功能需求选择需要流式通信:gRPC需要完善服务治理:Dubbo、gRPC需要简单易用:Spring Cloud OpenFeign、Motan需要灵活配置:Thrift4. 根据团队经验选择熟悉 Spring:Dubbo、Spring Cloud OpenFeign熟悉 Google 技术:gRPC熟悉 Facebook 技术:Thrift5. 根据项目场景选择内部微服务:gRPC、Dubbo对外 API:Spring Cloud OpenFeign、RESTful API实时通信:gRPC跨语言:gRPC、Thrift性能对比(大致排序):序列化性能:Protobuf (gRPC) > Hessian (Dubbo) > Thrift > JSON (Feign)传输性能:HTTP/2 (gRPC) > TCP (Dubbo) > HTTP/1.x (Feign)综合性能:gRPC > Dubbo > brpc > Thrift > Motan > Feign代码示例对比:gRPC:service UserService { rpc GetUser (GetUserRequest) returns (GetUserResponse) {}}Dubbo:public interface UserService { User getUser(Long id);}Feign:@FeignClient(name = "user-service")public interface UserService { @GetMapping("/user/{id}") User getUser(@PathVariable("id") Long id);}最佳实践:优先选择社区活跃、文档完善的框架考虑团队技术栈和学习成本评估性能和功能需求考虑未来扩展性进行性能测试验证参考行业最佳实践
阅读 0·2月22日 14:06

RPC 调用中的容错机制有哪些?如何处理网络异常和服务故障?

RPC 调用过程中,网络异常、服务故障等问题不可避免,需要完善的容错机制来保证系统稳定性:1. 超时机制(Timeout)作用:防止客户端无限等待实现:设置合理的超时时间(连接超时、读取超时)策略:根据网络状况和业务需求动态调整示例:Dubbo 的 timeout 配置、gRPC 的 deadline2. 重试机制(Retry)适用场景:网络抖动、临时性故障重试策略:指数退避(Exponential Backoff):每次重试间隔逐渐增加固定间隔:每次重试间隔相同最大重试次数:避免无限重试注意事项:幂等性设计,避免重复执行导致数据不一致3. 熔断机制(Circuit Breaker)原理:当故障率达到阈值时,快速失败,避免雪崩状态:关闭(Closed)、开启(Open)、半开启(Half-Open)实现:Hystrix、Resilience4j、Sentinel参数配置:失败率阈值、超时时间、恢复时间4. 限流机制(Rate Limiting)目的:保护服务不被过载算法:令牌桶(Token Bucket)漏桶(Leaky Bucket)固定窗口(Fixed Window)滑动窗口(Sliding Window)实现:Guava RateLimiter、Redis + Lua5. 服务降级(Fallback)作用:服务不可用时提供备用方案策略:返回默认值返回缓存数据调用备用服务返回友好错误提示6. 负载均衡(Load Balancing)算法:轮询(Round Robin)随机(Random)最少连接(Least Connections)一致性哈希(Consistent Hash)健康检查:定期检测服务实例健康状态7. 服务注册与发现作用:动态管理服务实例实现:Consul、Etcd、Zookeeper、Nacos特性:健康检查、服务剔除、自动注册8. 链路追踪(Distributed Tracing)作用:快速定位问题实现:Zipkin、Jaeger、SkyWalking信息:请求 ID、调用链路、耗时统计最佳实践:组合使用多种容错机制根据业务重要性配置不同的容错策略监控和告警及时发现问题定期演练故障场景
阅读 0·2月22日 14:06

RPC 调用中的安全性问题有哪些?如何实现身份认证、数据加密和授权?

RPC 调用涉及网络传输,安全性是必须考虑的重要问题。以下是 RPC 安全性的关键方面和实现方法:1. 身份认证(Authentication)Token 认证客户端在请求中携带 Token服务端验证 Token 有效性Token 可以是 JWT、OAuth2 等实现示例: // gRPC 拦截器实现 Token 认证 public class AuthInterceptor implements ServerInterceptor { @Override public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall( ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { String token = headers.get(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER)); if (!validateToken(token)) { call.close(Status.UNAUTHENTICATED.withDescription("Invalid token"), headers); return new ServerCall.Listener<ReqT>() {}; } return next.startCall(call, headers); } }API Key 认证为每个客户端分配唯一的 API Key简单但安全性相对较低适合内部服务调用双向 TLS(mTLS)客户端和服务端都验证对方证书提供强身份认证适用于高安全要求的场景2. 数据加密(Encryption)传输层加密TLS/SSL:加密整个通信通道HTTPS:基于 HTTP 的 RPC 使用 HTTPSgRPC over TLS:gRPC 支持 TLS 加密实现示例: // gRPC TLS 配置 NettyChannelBuilder.forAddress(host, port) .sslContext(GrpcSslContexts.forClient() .trustManager(new File("ca.pem")) .build()) .build();应用层加密对敏感数据进行额外加密使用 AES、RSA 等加密算法即使传输层被破解,数据仍然安全3. 授权(Authorization)基于角色的访问控制(RBAC)为用户分配角色角色关联权限检查用户是否有权限调用特定服务基于资源的访问控制细粒度控制对资源的访问可以控制到方法级别权限注解使用注解标记需要权限的方法拦截器统一处理权限检查4. 防重放攻击时间戳验证请求中包含时间戳服务端验证时间戳是否在有效范围内防止旧请求被重放Nonce 机制每次请求使用唯一的随机数服务端记录已使用的 Nonce防止相同请求被重复使用请求签名对请求参数进行签名签名包含时间戳和 Nonce服务端验证签名有效性5. 防止 DDoS 攻击限流限制单个客户端的请求频率使用令牌桶、漏桶等算法实现示例: // Guava RateLimiter RateLimiter rateLimiter = RateLimiter.create(100); // 100 QPS if (rateLimiter.tryAcquire()) { // 处理请求 } else { throw new RateLimitExceededException(); }黑名单/白名单拦截来自黑名单 IP 的请求只允许白名单 IP 访问验证码对可疑请求要求验证码防止自动化攻击6. 数据完整性消息认证码(MAC)使用 HMAC 等算法验证消息完整性防止数据在传输中被篡改数字签名使用私钥签名,公钥验证提供不可抵赖性7. 安全审计日志记录记录所有 RPC 调用包括调用者、时间、参数等便于事后审计和问题排查监控告警监控异常调用模式及时发现安全威胁8. 安全配置最佳实践最小权限原则只授予必要的权限定期审查权限配置定期更新证书及时更新过期的证书使用证书自动管理工具安全配置检查定期进行安全扫描使用安全配置检查工具敏感信息保护不在日志中记录敏感信息使用配置中心管理密钥定期轮换密钥9. 框架特定安全配置gRPC 安全启用 TLS使用拦截器实现认证和授权配置 ALTS(Application Layer Transport Security)Dubbo 安全配置 Token 认证使用 Dubbo Filter 实现安全检查支持自定义序列化协议加密Thrift 安全使用 TSSLTransport实现 TProcessor 拦截器自定义协议层加密
阅读 0·2月22日 14:06

如何实现 RPC 的异步调用?异步调用有哪些模式和优势?

异步 RPC 调用是提高系统性能和并发能力的重要技术,允许客户端在等待响应的同时处理其他任务:异步调用模式:1. Future/Promise 模式原理:调用后立即返回 Future 对象,通过 Future 获取结果优点:简单易用,不阻塞调用线程缺点:需要主动获取结果,代码可能不够优雅实现示例: // Dubbo 异步调用 <dubbo:reference interface="com.example.UserService" async="true"/> // 使用 userService.getUser(1L); Future<User> future = RpcContext.getContext().getFuture(); User user = future.get(1000, TimeUnit.MILLISECONDS);2. 回调模式(Callback)原理:调用时传入回调函数,结果返回时执行回调优点:事件驱动,适合异步处理缺点:回调地狱,代码可读性差实现示例: public interface AsyncCallback<T> { void onSuccess(T result); void onFailure(Throwable t); } // 使用 userService.getUserAsync(1L, new AsyncCallback<User>() { @Override public void onSuccess(User user) { // 处理成功结果 } @Override public void onFailure(Throwable t) { // 处理失败 } });3. 响应式编程(Reactive)原理:使用响应式流(Reactive Streams)处理异步数据优点:代码优雅,支持背压,适合流式处理缺点:学习曲线较陡实现示例: // Reactor Mono<User> userMono = userService.getUserReactive(1L); userMono.subscribe( user -> System.out.println(user), error -> System.err.println(error) ); // RxJava Observable<User> userObs = userService.getUserRx(1L); userObs.subscribe( user -> System.out.println(user), error -> System.err.println(error) );4. gRPC 异步调用原理:使用 StreamObserver 处理异步响应优点:支持流式通信,与 gRPC 深度集成实现示例: // 一元异步调用 stub.sayHello(request, new StreamObserver<HelloResponse>() { @Override public void onNext(HelloResponse response) { // 处理响应 } @Override public void onError(Throwable t) { // 处理错误 } @Override public void onCompleted() { // 调用完成 } }); // 双向流 StreamObserver<Request> requestObserver = stub.bidirectionalStream( new StreamObserver<Response>() { @Override public void onNext(Response response) { // 处理响应 } @Override public void onError(Throwable t) { // 处理错误 } @Override public void onCompleted() { // 调用完成 } }); // 发送请求 requestObserver.onNext(request1); requestObserver.onNext(request2); requestObserver.onCompleted();5. CompletableFuture原理:Java 8 引入的异步编程工具优点:功能强大,支持链式调用实现示例: CompletableFuture<User> future = CompletableFuture.supplyAsync( () -> userService.getUser(1L) ); // 链式调用 future.thenAccept(user -> System.out.println(user)) .exceptionally(t -> { System.err.println(t); return null; }); // 组合多个 Future CompletableFuture<User> userFuture = userService.getUserAsync(1L); CompletableFuture<Order> orderFuture = orderService.getOrderAsync(1L); CompletableFuture<Result> resultFuture = userFuture.thenCombineAsync( orderFuture, (user, order) -> new Result(user, order) );异步调用的优势:1. 提高并发能力不阻塞调用线程可以同时处理多个请求充分利用系统资源2. 降低延迟客户端可以并行发起多个调用减少等待时间提高响应速度3. 提高吞吐量单位时间内处理更多请求适合高并发场景4. 更好的用户体验避免界面卡顿实现实时更新异步调用的挑战:1. 代码复杂度异步代码难以理解和调试错误处理复杂需要处理线程安全问题2. 上下文传递异步调用时上下文可能丢失需要显式传递上下文信息解决方案:使用 ThreadLocal、TransmittableThreadLocal3. 超时控制需要合理设置超时时间避免无限等待实现示例: CompletableFuture<User> future = userService.getUserAsync(1L); try { User user = future.get(1000, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { future.cancel(true); }4. 资源管理需要合理管理线程池避免资源耗尽实现示例: ExecutorService executor = Executors.newFixedThreadPool(10); CompletableFuture<User> future = CompletableFuture.supplyAsync( () -> userService.getUser(1L), executor );最佳实践:1. 合理选择异步模式简单场景:Future/Promise事件驱动:回调模式流式处理:响应式编程高性能要求:CompletableFuture2. 完善的错误处理捕获所有异常提供有意义的错误信息实现重试机制3. 超时控制设置合理的超时时间超时后取消请求避免资源泄漏4. 资源管理使用线程池管理线程及时释放资源避免内存泄漏5. 监控和日志记录异步调用日志监控异步调用性能及时发现问题适用场景:高并发场景需要并行调用多个服务流式数据处理实时性要求高的场景长时间运行的任务
阅读 0·2月22日 14:06

什么是服务治理?RPC 框架中的服务治理功能有哪些?如何实现?

服务治理是微服务架构中的核心功能,确保服务的稳定运行和高效管理:核心服务治理功能:1. 服务注册与发现功能:服务实例自动注册和发现实现:Zookeeper、Nacos、Consul、Eureka关键点:健康检查:定期检测服务实例健康状态服务剔除:自动移除不健康的实例动态更新:服务列表实时更新配置示例: // Dubbo 服务注册 <dubbo:registry address="zookeeper://127.0.0.1:2181"/> // Spring Cloud 服务发现 @EnableDiscoveryClient2. 负载均衡功能:将请求分发到多个服务实例算法:随机(Random)轮询(Round Robin)最少连接(Least Connections)一致性哈希(Consistent Hash)配置示例: // Dubbo 负载均衡 <dubbo:reference loadbalance="random"/> // Spring Cloud 负载均衡 @LoadBalanced RestTemplate restTemplate;3. 服务容错功能:处理服务调用失败的情况策略:Failover:失败自动切换,重试其他实例Failfast:快速失败,只发起一次调用Failsafe:失败安全,出现异常时忽略Failback:失败自动恢复,后台记录失败请求Forking:并行调用,只要一个成功即返回Broadcast:广播调用,所有调用都成功才算成功配置示例: // Dubbo 容错策略 <dubbo:reference cluster="failover" retries="2"/> // Hystrix 熔断 @HystrixCommand(fallbackMethod = "fallback") public User getUser(Long id) { return userService.getUser(id); }4. 服务降级功能:服务不可用时提供备用方案策略:返回默认值返回缓存数据调用备用服务返回友好错误提示实现示例: @HystrixCommand(fallbackMethod = "getUserFallback") public User getUser(Long id) { return userService.getUser(id); } public User getUserFallback(Long id) { return new User(id, "默认用户"); }5. 服务限流功能:保护服务不被过载算法:令牌桶(Token Bucket)漏桶(Leaky Bucket)固定窗口(Fixed Window)滑动窗口(Sliding Window)实现示例: // Sentinel 限流 @SentinelResource(value = "getUser", blockHandler = "handleBlock") public User getUser(Long id) { return userService.getUser(id); } public User handleBlock(Long id, BlockException ex) { return new User(id, "限流"); } // Guava RateLimiter RateLimiter rateLimiter = RateLimiter.create(100); if (rateLimiter.tryAcquire()) { // 处理请求 }6. 服务熔断功能:当故障率达到阈值时,快速失败,避免雪崩状态:关闭(Closed):正常状态开启(Open):熔断状态,快速失败半开启(Half-Open):尝试恢复状态实现示例: // Hystrix 熔断配置 @HystrixCommand( commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"), @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"), @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000") } ) public User getUser(Long id) { return userService.getUser(id); }7. 服务路由功能:根据规则将请求路由到特定服务实例策略:条件路由:根据参数条件路由标签路由:根据服务标签路由脚本路由:使用脚本定义路由规则配置示例: // Dubbo 条件路由 <dubbo:router> <dubbo:condition-router rule="host = 192.168.1.1 => provider = 1.0.0"/> </dubbo:router> // Spring Cloud 路由 @RequestMapping("/api/user/**") public String userService() { return "forward:/user-service/api/user/**"; }8. 服务监控功能:监控服务运行状态和性能指标指标:QPS(每秒查询数)TPS(每秒事务数)响应时间(RT)成功率错误率工具:Prometheus + GrafanaSkyWalkingZipkinELK Stack实现示例: // Micrometer 指标收集 @Autowired private MeterRegistry meterRegistry; public User getUser(Long id) { Timer.Sample sample = Timer.start(meterRegistry); try { User user = userService.getUser(id); sample.stop(meterRegistry.timer("user.get", "status", "success")); return user; } catch (Exception e) { sample.stop(meterRegistry.timer("user.get", "status", "error")); throw e; } }9. 服务配置管理功能:集中管理服务配置特性:动态配置更新配置版本管理配置推送配置回滚工具:Nacos ConfigSpring Cloud ConfigApollo配置示例: // Nacos 配置 @Value("${user.service.timeout}") private int timeout; @NacosValue(value = "${user.service.timeout}", autoRefreshed = true) private int dynamicTimeout;10. 服务灰度发布功能:逐步发布新版本服务策略:按比例流量分配按用户标签路由按地域路由实现示例: // 灰度发布配置 @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } // 使用标签路由 @FeignClient(name = "user-service", qualifiers = "v2") public interface UserServiceV2 { // ... }服务治理最佳实践:1. 分层治理基础层:服务注册、发现、负载均衡控制层:限流、熔断、降级监控层:监控、告警、日志配置层:配置管理、灰度发布2. 渐进式实施先实现基础功能逐步添加高级功能持续优化和调整3. 监控和告警完善的监控指标及时的告警机制定期的性能分析4. 容灾演练定期进行故障演练验证容错机制优化应急响应流程
阅读 0·2月22日 14:06

什么是服务注册与发现?主流的注册中心有哪些?它们各有什么特点?

服务注册与发现是微服务架构中的核心组件,解决了服务实例动态管理的问题:核心概念:1. 服务注册(Service Registration)服务启动时向注册中心注册自己的信息注册信息包括:服务名、IP、端口、元数据等定期发送心跳保持注册状态2. 服务发现(Service Discovery)客户端从注册中心获取服务实例列表支持动态更新服务列表实现负载均衡和故障转移3. 健康检查(Health Check)定期检测服务实例健康状态自动剔除不健康的实例支持主动和被动检查主流注册中心对比:1. Zookeeper特点:基于 ZAB 协议的分布式协调服务优势:成熟稳定,社区活跃强一致性(CP)支持临时节点和持久节点劣势:性能相对较低运维复杂不支持 HTTP 接口适用场景:对一致性要求高的场景2. Eureka特点:Netflix 开发的服务发现组件优势:简单易用,与 Spring Cloud 集成好支持自我保护机制高可用性(AP)劣势:已停止维护(2.x 版本)一致性较弱不支持跨数据中心适用场景:Spring Cloud 微服务架构3. Consul特点:HashiCorp 开发的服务网格工具优势:功能全面(服务发现、健康检查、KV 存储)支持 HTTP 和 DNS 接口支持多数据中心支持服务网格劣势:学习曲线较陡资源占用相对较高适用场景:需要多功能集成的场景4. Nacos特点:阿里巴巴开源的服务发现和配置管理平台优势:功能全面(服务发现、配置管理、DNS)支持 AP 和 CP 模式切换与 Spring Cloud、Dubbo 集成好支持动态配置推送中文文档完善劣势:相对较新,生态不如 Consul 成熟适用场景:国内微服务架构,特别是使用 Spring Cloud Alibaba5. Etcd特点:CoreOS 开发的分布式键值存储优势:基于 Raft 协议,强一致性性能优秀支持 gRPC 接口Kubernetes 的核心组件劣势:功能相对单一运维复杂度较高适用场景:Kubernetes 环境,对一致性要求高的场景服务发现模式:1. 客户端发现模式客户端从注册中心获取服务列表客户端自行选择服务实例优点:减少注册中心压力,响应快缺点:客户端逻辑复杂代表:Eureka、Consul2. 服务端发现模式客户端通过负载均衡器调用服务负载均衡器从注册中心获取服务列表优点:客户端逻辑简单缺点:增加一层代理,可能影响性能代表:Kubernetes Service、Nginx实现示例(Nacos):服务提供者:@SpringBootApplication@EnableDiscoveryClientpublic class ProviderApplication { public static void main(String[] args) { SpringApplication.run(ProviderApplication.class, args); }}// 配置spring: application: name: user-service cloud: nacos: discovery: server-addr: localhost:8848服务消费者:@RestControllerpublic class ConsumerController { @Autowired private LoadBalancerClient loadBalancerClient; @GetMapping("/call") public String callService() { ServiceInstance instance = loadBalancerClient.choose("user-service"); String url = String.format("http://%s:%s/api/user", instance.getHost(), instance.getPort()); return restTemplate.getForObject(url, String.class); }}选择建议:Spring Cloud 生态:优先选择 Nacos 或 EurekaKubernetes 环境:使用 Etcd 或 CoreDNS需要多功能集成:选择 Consul对一致性要求高:选择 Zookeeper 或 Etcd国内项目:优先选择 Nacos
阅读 0·2月22日 14:06