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

面试题手册

Promise 的微任务机制是什么?如何理解事件循环?

Promise 的微任务机制是 JavaScript 事件循环中的重要概念,理解它对于掌握异步编程和解决复杂的异步问题至关重要。事件循环基础JavaScript 是单线程的,通过事件循环来处理异步操作。事件循环负责协调执行栈、宏任务队列和微任务队列。事件循环的执行顺序执行同步代码(执行栈)执行所有微任务执行一个宏任务重复步骤 2-3微任务 vs 宏任务微任务(Microtask)Promise.then/catch/finallyqueueMicrotask()MutationObserver宏任务(Macrotask)setTimeoutsetIntervalsetImmediate(Node.js)I/O 操作UI 渲染执行顺序示例基本示例console.log('1. 同步代码');setTimeout(() => { console.log('2. setTimeout(宏任务)');}, 0);Promise.resolve().then(() => { console.log('3. Promise.then(微任务)');});console.log('4. 同步代码');// 输出顺序:// 1. 同步代码// 4. 同步代码// 3. Promise.then(微任务)// 2. setTimeout(宏任务)复杂示例console.log('1. 开始');setTimeout(() => { console.log('2. setTimeout 1'); Promise.resolve().then(() => { console.log('3. setTimeout 1 中的微任务'); });}, 0);Promise.resolve().then(() => { console.log('4. Promise.then 1'); setTimeout(() => { console.log('5. Promise.then 1 中的 setTimeout'); }, 0);});Promise.resolve().then(() => { console.log('6. Promise.then 2');});setTimeout(() => { console.log('7. setTimeout 2');}, 0);console.log('8. 结束');// 输出顺序:// 1. 开始// 8. 结束// 4. Promise.then 1// 6. Promise.then 2// 2. setTimeout 1// 7. setTimeout 2// 3. setTimeout 1 中的微任务// 5. Promise.then 1 中的 setTimeoutPromise 的微任务机制Promise 状态改变触发微任务const promise = new Promise((resolve) => { console.log('1. Promise 构造函数(同步)'); resolve('成功');});promise.then(() => { console.log('2. Promise.then(微任务)');});console.log('3. 同步代码');// 输出顺序:// 1. Promise 构造函数(同步)// 3. 同步代码// 2. Promise.then(微任务)链式调用的微任务Promise.resolve() .then(() => { console.log('1. 第一个 then'); return '结果'; }) .then(() => { console.log('2. 第二个 then'); }) .then(() => { console.log('3. 第三个 then'); });console.log('4. 同步代码');// 输出顺序:// 4. 同步代码// 1. 第一个 then// 2. 第二个 then// 3. 第三个 then嵌套 Promise 的微任务Promise.resolve() .then(() => { console.log('1. 外层 then'); Promise.resolve().then(() => { console.log('2. 内层 then'); }); }) .then(() => { console.log('3. 外层第二个 then'); });console.log('4. 同步代码');// 输出顺序:// 4. 同步代码// 1. 外层 then// 2. 内层 then// 3. 外层第二个 then实际应用场景1. 确保代码在下一个事件循环执行function nextTick(callback) { Promise.resolve().then(callback);}console.log('1. 开始');nextTick(() => { console.log('2. 下一个事件循环');});console.log('3. 结束');// 输出顺序:// 1. 开始// 3. 结束// 2. 下一个事件循环2. 批量更新 DOMfunction batchUpdate(updates) { // 使用微任务确保在当前事件循环结束后执行 Promise.resolve().then(() => { updates.forEach(update => { update(); }); });}// 使用示例batchUpdate([ () => document.body.style.backgroundColor = 'red', () => document.body.style.color = 'white', () => document.body.style.fontSize = '16px']);3. 避免阻塞渲染function processLargeData(data) { let index = 0; function processChunk() { const chunkSize = 1000; const end = Math.min(index + chunkSize, data.length); for (; index < end; index++) { processDataItem(data[index]); } if (index < data.length) { // 使用微任务让出控制权,避免阻塞渲染 Promise.resolve().then(processChunk); } } processChunk();}常见面试题1. Promise 和 setTimeout 的执行顺序setTimeout(() => { console.log('1. setTimeout');}, 0);Promise.resolve().then(() => { console.log('2. Promise.then');});// 输出顺序:// 2. Promise.then// 1. setTimeout原因:微任务优先级高于宏任务,会在当前宏任务执行完后立即执行。2. 多个 Promise 的执行顺序Promise.resolve() .then(() => console.log('1')) .then(() => console.log('2')) .then(() => console.log('3'));Promise.resolve() .then(() => console.log('4')) .then(() => console.log('5')) .then(() => console.log('6'));// 输出顺序:// 1// 4// 2// 5// 3// 6原因:每个 Promise 链的第一个 then 都是微任务,按照注册顺序执行。3. Promise 构造函数中的同步代码console.log('1. 开始');const promise = new Promise((resolve) => { console.log('2. Promise 构造函数'); resolve();});promise.then(() => { console.log('3. Promise.then');});console.log('4. 结束');// 输出顺序:// 1. 开始// 2. Promise 构造函数// 4. 结束// 3. Promise.then原因:Promise 构造函数中的代码是同步执行的,then 回调是微任务。性能考虑1. 避免过长的微任务链// 不推荐:过长的微任务链可能导致性能问题function processItems(items) { return items.reduce((promise, item) => { return promise.then(() => processItem(item)); }, Promise.resolve());}// 推荐:使用 Promise.all 并行处理function processItems(items) { return Promise.all(items.map(item => processItem(item)));}2. 合理使用微任务// 不推荐:不必要的微任务function doSomething() { Promise.resolve().then(() => { console.log('执行'); });}// 推荐:直接执行同步代码function doSomething() { console.log('执行');}Node.js 中的微任务Node.js 中的微任务机制与浏览器略有不同:process.nextTick(() => { console.log('1. process.nextTick');});Promise.resolve().then(() => { console.log('2. Promise.then');});setImmediate(() => { console.log('3. setImmediate');});// 输出顺序:// 1. process.nextTick// 2. Promise.then// 3. setImmediate注意:process.nextTick 的优先级高于 Promise.then。总结微任务优先级高于宏任务:微任务会在当前宏任务执行完后立即执行Promise.then 是微任务:Promise 的回调会在微任务队列中执行事件循环的执行顺序:同步代码 → 微任务 → 宏任务 → 微任务 → 宏任务…链式调用的执行顺序:每个 then 都会创建一个微任务,按顺序执行Promise 构造函数是同步的:构造函数中的代码立即执行合理使用微任务:避免不必要的微任务,注意性能影响Node.js 的差异:process.nextTick 优先级高于 Promise.then
阅读 0·2月22日 14:06

MobX 中 action 的作用和使用方法是什么?

在 MobX 中,action 是修改 observable 状态的唯一推荐方式。使用 action 可以确保状态变化是可追踪、可预测的,并且能够优化性能。Action 的作用批量更新:action 内部的所有状态变化会被批量处理,减少不必要的重新计算可追踪性:所有状态变化都集中在 action 中,便于调试和追踪事务性:action 内部的状态变化是原子的,要么全部成功,要么全部失败性能优化:减少 reaction 的触发次数,提高性能Action 的使用方式1. 使用装饰器import { observable, action } from 'mobx';class TodoStore { @observable todos = []; @action addTodo(text) { this.todos.push({ text, completed: false }); } @action.bound removeTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); } @action async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); this.todos = data; }}2. 使用 makeObservableimport { makeObservable, observable, action } from 'mobx';class TodoStore { todos = []; constructor() { makeObservable(this, { todos: observable, addTodo: action, removeTodo: action.bound, fetchTodos: action }); } addTodo(text) { this.todos.push({ text, completed: false }); } removeTodo(id) { this.todos = this.todos.filter(todo => todo.id !== id); } async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); this.todos = data; }}3. 使用 runInActionimport { observable, runInAction } from 'mobx';class TodoStore { todos = []; async fetchTodos() { const response = await fetch('/api/todos'); const data = await response.json(); runInAction(() => { this.todos = data; }); }}Action 的类型1. action最基础的 action 装饰器,用于包装状态修改方法。@actionupdateTodo(id, updates) { const todo = this.todos.find(t => t.id === id); if (todo) { Object.assign(todo, updates); }}2. action.bound自动绑定 this 的 action,避免在回调函数中丢失 this 上下文。@action.boundhandleClick() { this.count++;}// 使用时不需要 bind<button onClick={this.handleClick}>Click</button>3. runInAction在异步操作中修改状态时使用。async loadData() { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; });}Action 的最佳实践1. 始终在 action 中修改状态// ❌ 错误:直接修改状态class Store { @observable count = 0; increment() { this.count++; // 不是 action }}// ✅ 正确:在 action 中修改状态class Store { @observable count = 0; @action increment() { this.count++; }}2. 使用 action.bound 处理事件处理器class Component { @observable count = 0; @action.bound handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}3. 异步操作中使用 runInAction@actionasync fetchUser(id) { this.loading = true; try { const response = await fetch(`/api/users/${id}`); const data = await response.json(); runInAction(() => { this.user = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}4. 合理拆分 action// ❌ 过于复杂的 action@actionhandleComplexOperation(data) { this.loading = true; this.data = data; this.filtered = data.filter(item => item.active); this.count = this.filtered.length; this.timestamp = Date.now(); this.loading = false;}// ✅ 拆分为多个小 action@actionhandleComplexOperation(data) { this.setLoading(true); this.setData(data); this.processData(data); this.setLoading(false);}@actionsetLoading(loading) { this.loading = loading;}@actionsetData(data) { this.data = data;}@actionprocessData(data) { this.filtered = data.filter(item => item.active); this.count = this.filtered.length; this.timestamp = Date.now();}常见错误1. 在异步回调中直接修改状态// ❌ 错误@actionasync fetchData() { const data = await fetch('/api/data'); this.data = await data.json(); // 不在 action 中}// ✅ 正确@actionasync fetchData() { const data = await fetch('/api/data'); runInAction(() => { this.data = data.json(); });}2. 忘记使用 action.bound// ❌ 错误:this 可能丢失class Component { @action handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}// ✅ 正确:使用 action.boundclass Component { @action.bound handleClick() { this.count++; } render() { return <button onClick={this.handleClick}>Click</button>; }}性能优化使用 action 批量更新:减少 reaction 触发次数避免在循环中创建 action:复用 action 函数合理使用 runInAction:只在必要时使用使用 action.bound:避免重复 bind总结action 是修改 observable 状态的唯一推荐方式使用 action 可以批量更新、提高性能、便于调试优先使用 action.bound 处理事件处理器异步操作中使用 runInAction 修改状态合理拆分复杂的 action,提高可维护性
阅读 0·2月22日 14:06

RPC 调用中的分布式事务问题如何解决?常见的分布式事务解决方案有哪些?

分布式事务是 RPC 调用中的难点问题,涉及多个服务之间的数据一致性保证:问题背景:在微服务架构中,一个业务操作可能涉及多个服务的调用,如何保证这些服务之间的数据一致性是一个挑战。分布式事务理论:1. CAP 定理一致性(Consistency):所有节点同时看到相同的数据可用性(Availability):每个请求都能得到响应分区容错性(Partition Tolerance):系统在网络分区时仍能继续运行结论:在分布式系统中,只能同时满足两个特性2. BASE 理论基本可用(Basically Available):系统保证基本可用软状态(Soft State):允许数据存在中间状态最终一致性(Eventually Consistent):经过一段时间后数据最终一致常见解决方案:1. 两阶段提交(2PC)准备阶段:协调者询问所有参与者是否可以提交提交阶段:根据参与者反馈决定提交或回滚优点:强一致性缺点:性能差,存在单点故障,阻塞适用场景:对一致性要求极高的场景2. 三阶段提交(3PC)在 2PC 基础上增加预提交阶段减少阻塞时间仍然存在性能问题3. TCC(Try-Confirm-Cancel)Try 阶段:预留资源,检查业务规则Confirm 阶段:确认执行业务操作Cancel 阶段:取消操作,释放资源优点:性能较好,最终一致性缺点:代码侵入性强,需要实现三个接口实现示例: public interface OrderService { // Try:创建订单,预扣库存 @Compensable boolean createOrder(Order order); // Confirm:确认订单 void confirmOrder(Long orderId); // Cancel:取消订单,回滚库存 void cancelOrder(Long orderId); }4. 本地消息表在本地数据库中记录消息定时任务扫描消息表并发送消息消费成功后删除记录优点:实现简单,可靠性高缺点:需要额外的存储和定时任务适用场景:异步场景,最终一致性5. 事务消息(如 RocketMQ)发送半消息执行本地事务提交或回滚消息消费者消费消息优点:性能好,支持高并发缺点:依赖消息中间件适用场景:高并发场景,最终一致性6. Saga 模式将长事务拆分为多个本地事务每个本地事务都有对应的补偿操作按顺序执行,失败时执行补偿优点:性能好,适合长事务缺点:需要设计补偿逻辑,可能产生脏读适用场景:长事务,业务流程复杂7. Seata(阿里开源)支持 AT、TCC、SAGA、XA 模式AT 模式:一阶段:解析 SQL,记录前镜像和后镜像二阶段:提交或回滚,通过镜像数据恢复优点:对业务代码侵入小缺点:需要数据库支持适用场景:Java 生态,Spring Cloud 微服务实现示例(Seata AT 模式):@GlobalTransactional(name = "create-order", rollbackFor = Exception.class)public void createOrder(Order order) { // 扣减库存 inventoryService.deductStock(order.getProductId(), order.getQuantity()); // 创建订单 orderMapper.insert(order); // 扣减余额 accountService.deductBalance(order.getUserId(), order.getAmount());}选择建议:强一致性要求:2PC、3PC、Seata XA高并发场景:TCC、事务消息、Seata AT长事务:Saga 模式简单场景:本地消息表Java 生态:Seata 框架最终一致性可接受:TCC、Saga、事务消息注意事项:根据业务场景选择合适的方案考虑性能和一致性的平衡做好幂等性设计完善的监控和告警机制定期进行故障演练
阅读 0·2月22日 14:06

MobX 中 computed 的作用和使用场景有哪些?

在 MobX 中,computed 是基于 observable 状态自动更新的派生值,类似于 Vue 的 computed 属性。computed 值会缓存计算结果,只有当依赖的 observable 状态发生变化时才会重新计算。Computed 的特性自动缓存:计算结果会被缓存,避免重复计算懒计算:只有在被访问时才会计算自动追踪依赖:自动追踪依赖的 observable 状态自动更新:依赖的状态变化时自动重新计算纯函数:computed 应该是纯函数,不应该有副作用Computed 的使用方式1. 使用装饰器import { observable, computed } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @computed get activeTodos() { return this.todos.filter(todo => !todo.completed); } @computed get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}2. 使用 makeObservableimport { makeObservable, observable, computed } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, activeTodos: computed, filteredTodos: computed }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } get activeTodos() { return this.todos.filter(todo => !todo.completed); } get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}3. 使用 makeAutoObservable(推荐)import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } get activeTodos() { return this.todos.filter(todo => !todo.completed); } get filteredTodos() { switch (this.filter) { case 'completed': return this.completedTodos; case 'active': return this.activeTodos; default: return this.todos; } }}4. 使用 computed 函数import { observable, computed } from 'mobx';const store = observable({ todos: [], filter: 'all'});const completedTodos = computed(() => store.todos.filter(todo => todo.completed));const activeTodos = computed(() => store.todos.filter(todo => !todo.completed));console.log(completedTodos.get()); // 获取计算值Computed 的配置选项1. name为 computed 设置名称,便于调试。@computed({ name: 'completedTodos' })get completedTodos() { return this.todos.filter(todo => todo.completed);}2. equals自定义比较函数,决定是否需要重新计算。@computed({ equals: (a, b) => a.length === b.length && a.every(item => b.includes(item))})get filteredTodos() { return this.todos.filter(todo => todo.completed);}3. keepAlive保持计算值活跃,即使没有被访问。@computed({ keepAlive: true })get expensiveComputation() { return this.largeArray.reduce((sum, item) => sum + item.value, 0);}Computed 的最佳实践1. 使用 computed 缓存复杂计算class Store { @observable items = []; @computed get totalValue() { return this.items.reduce((sum, item) => sum + item.value, 0); } @computed get averageValue() { return this.items.length > 0 ? this.totalValue / this.items.length : 0; }}2. 避免在 computed 中产生副作用// ❌ 错误:computed 中有副作用@computed get filteredItems() { console.log('Filtering items'); // 副作用 return this.items.filter(item => item.active);}// ✅ 正确:使用 reaction 处理副作用@computed get filteredItems() { return this.items.filter(item => item.active);}constructor() { reaction( () => this.filteredItems, (items) => console.log('Filtered items:', items) );}3. 合理使用 computed 链class Store { @observable products = []; @observable category = 'all'; @observable minPrice = 0; @observable maxPrice = Infinity; @computed get filteredByCategory() { return this.category === 'all' ? this.products : this.products.filter(p => p.category === this.category); } @computed get filteredByPrice() { return this.filteredByCategory.filter( p => p.price >= this.minPrice && p.price <= this.maxPrice ); } @computed get sortedProducts() { return [...this.filteredByPrice].sort((a, b) => a.price - b.price); }}4. 使用 computed 处理表单验证class FormStore { @observable username = ''; @observable email = ''; @observable password = ''; @computed get usernameError() { if (this.username.length < 3) { return 'Username must be at least 3 characters'; } return ''; } @computed get emailError() { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(this.email)) { return 'Invalid email format'; } return ''; } @computed get passwordError() { if (this.password.length < 8) { return 'Password must be at least 8 characters'; } return ''; } @computed get isValid() { return !this.usernameError && !this.emailError && !this.passwordError; }}Computed vs Reaction| 特性 | Computed | Reaction ||------|----------|----------|| 用途 | 计算派生值 | 执行副作用 || 返回值 | 返回计算结果 | 不返回值 || 缓存 | 自动缓存 | 不缓存 || 触发时机 | 被访问时 | 依赖变化时立即执行 || 适用场景 | 数据转换、过滤、聚合 | 日志、API 调用、DOM 操作 |性能优化合理使用 computed:避免过度使用,只在需要缓存时使用避免复杂计算:如果计算非常耗时,考虑使用 memoization使用 computed 链:将复杂计算拆分为多个小的 computed避免在 computed 中创建新对象:可能导致不必要的重新计算常见错误1. 在 computed 中修改状态// ❌ 错误:computed 中修改状态@computed get filteredItems() { this.lastFilterTime = Date.now(); // 修改状态 return this.items.filter(item => item.active);}// ✅ 正确:使用 reaction 处理副作用@computed get filteredItems() { return this.items.filter(item => item.active);}constructor() { reaction( () => this.filteredItems, () => { this.lastFilterTime = Date.now(); } );}2. 在 computed 中使用异步操作// ❌ 错误:computed 中使用异步操作@computed async get userData() { const response = await fetch('/api/user'); return response.json();}// ✅ 正确:使用 reaction 处理异步操作constructor() { reaction( () => this.userId, async (id) => { const response = await fetch(`/api/user/${id}`); runInAction(() => { this.userData = await response.json(); }); } );}总结computed 是基于 observable 状态自动更新的派生值computed 会自动缓存计算结果,提高性能computed 应该是纯函数,不应该有副作用合理使用 computed 链处理复杂计算避免在 computed 中修改状态或使用异步操作使用 reaction 处理副作用和异步操作
阅读 0·2月22日 14:05

MobX 中 observable 的使用方法和注意事项有哪些?

在 MobX 中,observable 是核心概念之一,它将普通的 JavaScript 对象、数组、Map、Set 等数据结构转换为可观察对象,使 MobX 能够追踪其变化。Observable 的使用方式1. 使用装饰器(Decorator)import { observable, action, computed } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get completedTodos() { return this.todos.filter(todo => todo.completed); } @action addTodo(text) { this.todos.push({ text, completed: false }); }}2. 使用 makeObservableimport { makeObservable, observable, action, computed } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeObservable(this, { todos: observable, filter: observable, completedTodos: computed, addTodo: action }); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}3. 使用 makeAutoObservable(推荐)import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; filter = 'all'; constructor() { makeAutoObservable(this); } get completedTodos() { return this.todos.filter(todo => todo.completed); } addTodo(text) { this.todos.push({ text, completed: false }); }}4. 使用 observable 函数import { observable } from 'mobx';const state = observable({ todos: [], filter: 'all'});const completedTodos = observable(() => state.todos.filter(todo => todo.completed));Observable 的特性1. 自动追踪依赖当 observable 对象被读取时,MobX 会自动建立依赖关系。import { observable, autorun } from 'mobx';const store = observable({ count: 0});autorun(() => { console.log('Count is:', store.count);});store.count = 1; // 自动触发 autorun2. 深度可观察observable 默认是深度可观察的,会递归地将对象的所有属性都转换为可观察。const store = observable({ user: { name: 'John', address: { city: 'New York' } }});store.user.address.city = 'Boston'; // 会被追踪3. 数组和 Map 的特殊处理observable 数组和 Map 提供了额外的 API,但保持与原生 API 兼容。const list = observable([1, 2, 3]);list.push(4); // MobX 会追踪这个变化list.splice(0, 1); // 也会被追踪Observable 配置选项1. shallow(浅层可观察)只追踪对象本身的变化,不追踪嵌套对象。import { observable } from 'mobx';const store = observable({ user: { name: 'John' }}, {}, { deep: false });store.user = { name: 'Jane' }; // 会被追踪store.user.name = 'Bob'; // 不会被追踪2. asMap、asArray显式指定数据结构类型。const map = observable.map({ key: 'value' });const array = observable.array([1, 2, 3]);注意事项不要在 action 外部修改 observable 状态避免在 constructor 中使用装饰器合理使用 computed 缓存计算结果对于大型对象,考虑使用 shallow 优化性能observable 对象不能被冻结(Object.freeze)最佳实践优先使用 makeAutoObservable 简化配置对于简单状态,使用 observable 函数对于复杂状态管理,使用类 + 装饰器或 makeObservable合理使用 computed 避免重复计算始终在 action 中修改 observable 状态
阅读 0·2月22日 14:05

MobX 中 reaction 的类型和使用场景是什么?

在 MobX 中,reaction 是用于处理副作用的机制,当 observable 状态发生变化时自动执行指定的函数。reaction 类似于 React 的 useEffect,但更加灵活和高效。Reaction 的类型1. autorun自动追踪依赖并在依赖变化时立即执行,适合需要立即执行的场景。import { observable, autorun } from 'mobx';class TodoStore { @observable todos = []; constructor() { autorun(() => { console.log('Total todos:', this.todos.length); // 保存到 localStorage localStorage.setItem('todos', JSON.stringify(this.todos)); }); }}2. reaction提供更细粒度的控制,可以指定追踪的数据和执行函数,适合需要控制执行时机的场景。import { observable, reaction } from 'mobx';class TodoStore { @observable todos = []; @observable filter = 'all'; constructor() { reaction( () => this.todos.length, // 追踪的数据 (length) => { console.log('Todo count changed:', length); }, { fireImmediately: false } // 配置选项 ); }}3. when当条件满足时执行一次,然后自动清理,适合一次性操作。import { observable, when } from 'mobx';class TodoStore { @observable todos = []; @observable loading = false; constructor() { when( () => !this.loading && this.todos.length === 0, () => { console.log('Ready to load todos'); this.loadTodos(); } ); } @action loadTodos() { this.loading = true; // 加载数据... }}Reaction 的配置选项1. fireImmediately是否立即执行一次。reaction( () => this.filter, (filter) => { console.log('Current filter:', filter); }, { fireImmediately: true } // 立即执行一次);2. delay延迟执行,防抖效果。reaction( () => this.searchQuery, (query) => { this.performSearch(query); }, { delay: 300 } // 延迟 300ms 执行);3. equals自定义比较函数,决定是否触发 reaction。reaction( () => this.items, (items) => { console.log('Items changed'); }, { equals: (a, b) => { return a.length === b.length && a.every(item => b.includes(item)); } });4. name为 reaction 设置名称,便于调试。reaction( () => this.todos, (todos) => { console.log('Todos updated'); }, { name: 'saveTodosToLocalStorage' });Reaction 的使用场景1. 数据持久化class TodoStore { @observable todos = []; constructor() { // 从 localStorage 加载 this.todos = JSON.parse(localStorage.getItem('todos') || '[]'); // 保存到 localStorage autorun(() => { localStorage.setItem('todos', JSON.stringify(this.todos)); }); }}2. 日志记录class Store { @observable user = null; @observable actions = []; constructor() { reaction( () => this.user, (user) => { console.log('User changed:', user); this.actions.push({ type: 'USER_CHANGE', user, timestamp: Date.now() }); } ); }}3. API 调用class ProductStore { @observable categoryId = null; @observable products = []; @observable loading = false; constructor() { reaction( () => this.categoryId, async (categoryId) => { if (categoryId) { this.loading = true; try { const response = await fetch(`/api/products?category=${categoryId}`); const data = await response.json(); runInAction(() => { this.products = data; this.loading = false; }); } catch (error) { runInAction(() => { this.loading = false; }); } } } ); }}4. 搜索防抖class SearchStore { @observable query = ''; @observable results = []; @observable loading = false; constructor() { reaction( () => this.query, async (query) => { if (query.length > 2) { this.loading = true; try { const response = await fetch(`/api/search?q=${query}`); const data = await response.json(); runInAction(() => { this.results = data; this.loading = false; }); } catch (error) { runInAction(() => { this.loading = false; }); } } }, { delay: 300 } // 防抖 300ms ); }}5. 条件初始化class AppStore { @observable initialized = false; @observable user = null; constructor() { when( () => this.initialized, () => { this.loadUserData(); } ); } @action loadUserData() { // 加载用户数据 }}Reaction 的清理1. 使用 dispose 清理class Component { disposer = null; componentDidMount() { this.disposer = reaction( () => this.store.todos, (todos) => { console.log('Todos changed:', todos); } ); } componentWillUnmount() { if (this.disposer) { this.disposer(); // 清理 reaction } }}2. 使用 useEffect 清理import { useEffect } from 'react';import { reaction } from 'mobx';function TodoList({ store }) { useEffect(() => { const disposer = reaction( () => store.todos, (todos) => { console.log('Todos changed:', todos); } ); return () => disposer(); // 清理 reaction }, [store]); return <div>{/* ... */}</div>;}Reaction vs Computed| 特性 | Reaction | Computed ||------|----------|----------|| 用途 | 执行副作用 | 计算派生值 || 返回值 | 不返回值 | 返回计算结果 || 缓存 | 不缓存 | 自动缓存 || 触发时机 | 依赖变化时立即执行 | 被访问时才计算 || 适用场景 | 日志、API 调用、DOM 操作 | 数据转换、过滤、聚合 |最佳实践1. 合理选择 reaction 类型// ✅ 使用 autorun:需要立即执行autorun(() => { console.log('Current state:', this.state);});// ✅ 使用 reaction:需要控制执行时机reaction( () => this.userId, (id) => this.loadUser(id));// ✅ 使用 when:一次性操作when( () => this.ready, () => this.start());2. 避免在 reaction 中修改依赖的状态// ❌ 错误:在 reaction 中修改依赖的状态reaction( () => this.count, (count) => { this.count = count + 1; // 会导致无限循环 });// ✅ 正确:使用 action 修改其他状态reaction( () => this.count, (count) => { this.setCount(count + 1); });3. 使用 delay 防抖// ✅ 使用 delay 防抖,避免频繁触发reaction( () => this.searchQuery, (query) => this.performSearch(query), { delay: 300 });4. 记得清理 reaction// ✅ 在组件卸载时清理 reactionuseEffect(() => { const disposer = reaction( () => store.data, (data) => console.log(data) ); return () => disposer();}, [store]);常见错误1. 忘记清理 reaction// ❌ 错误:忘记清理 reactionclass Component { componentDidMount() { reaction(() => this.store.data, (data) => { console.log(data); }); }}// ✅ 正确:清理 reactionclass Component { disposer = null; componentDidMount() { this.disposer = reaction(() => this.store.data, (data) => { console.log(data); }); } componentWillUnmount() { if (this.disposer) { this.disposer(); } }}2. 在 reaction 中直接修改状态// ❌ 错误:在 reaction 中直接修改状态reaction( () => this.count, (count) => { this.count = count + 1; // 不在 action 中 });// ✅ 正确:在 action 中修改状态reaction( () => this.count, (count) => { runInAction(() => { this.count = count + 1; }); });总结reaction 是 MobX 中处理副作用的机制autorun 适合需要立即执行的场景reaction 提供更细粒度的控制when 适合一次性操作使用 delay 可以实现防抖效果记得在组件卸载时清理 reaction避免在 reaction 中修改依赖的状态使用 action 或 runInAction 修改状态
阅读 0·2月22日 14:05

MobX 中如何处理异步操作?

在 MobX 中,异步操作需要特别注意,因为状态变化必须在 action 中进行。MobX 提供了多种方式来处理异步操作。处理异步操作的方式1. 使用 runInActionimport { observable, action, runInAction } from 'mobx';class UserStore { @observable users = []; @observable loading = false; @observable error = null; @action async fetchUsers() { this.loading = true; this.error = null; try { const response = await fetch('/api/users'); const data = await response.json(); runInAction(() => { this.users = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); } }}2. 使用 async actionimport { observable, action } from 'mobx';class UserStore { @observable users = []; @observable loading = false; @observable error = null; @action.bound async fetchUsers() { this.loading = true; this.error = null; try { const response = await fetch('/api/users'); const data = await response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } }}3. 使用 flowimport { observable, flow } from 'mobx';class UserStore { @observable users = []; @observable loading = false; @observable error = null; fetchUsers = flow(function* () { this.loading = true; this.error = null; try { const response = yield fetch('/api/users'); const data = yield response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } });}详细对比runInAction优点:灵活性高,可以在任何地方使用适合处理复杂的异步逻辑可以精确控制状态更新的时机缺点:需要手动包裹状态更新代码代码结构可能不够清晰适用场景:需要在异步操作的不同阶段更新状态复杂的异步逻辑需要精确控制状态更新时机@actionasync complexOperation() { this.loading = true; try { const data1 = await this.fetchData1(); runInAction(() => { this.data1 = data1; }); const data2 = await this.fetchData2(data1.id); runInAction(() => { this.data2 = data2; }); const result = await this.processData(data1, data2); runInAction(() => { this.result = result; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}async action优点:代码结构清晰自动处理状态更新不需要手动包裹代码缺点:灵活性较低所有状态更新都在同一个 action 中适用场景:简单的异步操作不需要精确控制状态更新时机代码结构优先的场景@action.boundasync simpleFetch() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); this.data = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; }}flow优点:代码结构最清晰自动处理状态更新支持取消操作更好的错误处理缺点:需要学习 generator 语法兼容性问题(需要 polyfill)适用场景:复杂的异步流程需要取消操作的场景需要更好的错误处理fetchUsers = flow(function* () { this.loading = true; this.error = null; try { const response = yield fetch('/api/users'); const data = yield response.json(); this.users = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; }});// 可以取消 flowconst fetchTask = store.fetchUsers();fetchTask.cancel();最佳实践1. 统一使用 async actionclass ApiStore { @observable data = null; @observable loading = false; @observable error = null; @action.bound async fetchData(url) { this.loading = true; this.error = null; try { const response = await fetch(url); const data = await response.json(); this.data = data; this.loading = false; } catch (error) { this.error = error.message; this.loading = false; } }}2. 使用 flow 处理复杂流程class OrderStore { @observable orders = []; @observable loading = false; @observable error = null; createOrder = flow(function* (orderData) { this.loading = true; this.error = null; try { // 验证订单 const validated = yield this.validateOrder(orderData); // 创建订单 const order = yield this.createOrderApi(validated); // 支付订单 const paid = yield this.payOrder(order.id); // 更新状态 this.orders.push(paid); this.loading = false; return paid; } catch (error) { this.error = error.message; this.loading = false; throw error; } });}3. 使用 runInAction 处理分步更新class UploadStore { @observable progress = 0; @observable files = []; @observable uploading = false; @action async uploadFiles(files) { this.uploading = true; this.progress = 0; try { for (let i = 0; i < files.length; i++) { const file = files[i]; await this.uploadFile(file); runInAction(() => { this.files.push(file); this.progress = ((i + 1) / files.length) * 100; }); } runInAction(() => { this.uploading = false; }); } catch (error) { runInAction(() => { this.uploading = false; }); throw error; } }}4. 使用 reaction 处理自动加载class DataStore { @observable userId = null; @observable userData = null; @observable loading = false; constructor() { reaction( () => this.userId, (userId) => { if (userId) { this.loadUserData(userId); } } ); } @action.bound async loadUserData(userId) { this.loading = true; try { const response = await fetch(`/api/users/${userId}`); const data = await response.json(); this.userData = data; this.loading = false; } catch (error) { this.loading = false; } }}5. 错误处理和重试class Store { @observable data = null; @observable loading = false; @observable error = null; @observable retryCount = 0; @action.bound async fetchDataWithRetry(url, maxRetries = 3) { this.loading = true; this.error = null; for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; this.retryCount = 0; }); return data; } catch (error) { runInAction(() => { this.retryCount = i + 1; }); if (i === maxRetries - 1) { runInAction(() => { this.error = error.message; this.loading = false; }); throw error; } // 等待后重试 await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))); } } }}常见错误1. 在 async 函数中直接修改状态// ❌ 错误:在 async 函数中直接修改状态@actionasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); this.data = data; // 不在 action 中 this.loading = false;}// ✅ 正确:使用 runInAction 或 async action@actionasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; });}// 或者@action.boundasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); this.data = data; this.loading = false;}2. 忘记处理错误// ❌ 错误:忘记处理错误@actionasync fetchData() { this.loading = true; const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; });}// ✅ 正确:处理错误@actionasync fetchData() { this.loading = true; this.error = null; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}3. 忘记重置 loading 状态// ❌ 错误:忘记重置 loading 状态@actionasync fetchData() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; }); } catch (error) { runInAction(() => { this.error = error.message; }); }}// ✅ 正确:在所有分支中重置 loading 状态@actionasync fetchData() { this.loading = true; try { const response = await fetch('/api/data'); const data = await response.json(); runInAction(() => { this.data = data; this.loading = false; }); } catch (error) { runInAction(() => { this.error = error.message; this.loading = false; }); }}性能优化1. 使用 debounce 防抖import { debounce } from 'lodash';class SearchStore { @observable query = ''; @observable results = []; @observable loading = false; constructor() { this.debouncedSearch = debounce(this.performSearch.bind(this), 300); } @action setQuery(query) { this.query = query; this.debouncedSearch(); } @action.bound async performSearch() { if (this.query.length < 2) { this.results = []; return; } this.loading = true; try { const response = await fetch(`/api/search?q=${this.query}`); const data = await response.json(); this.results = data; this.loading = false; } catch (error) { this.loading = false; } }}2. 使用 requestAnimationFrame 优化 UI 更新@actionasync loadData() { this.loading = true; const data = await this.fetchData(); // 使用 requestAnimationFrame 优化 UI 更新 requestAnimationFrame(() => { runInAction(() => { this.data = data; this.loading = false; }); });}总结使用 async action 处理简单的异步操作使用 runInAction 处理需要精确控制状态更新时机的场景使用 flow 处理复杂的异步流程始终处理错误和重置 loading 状态使用 reaction 处理自动加载使用 debounce 防抖优化性能使用 requestAnimationFrame 优化 UI 更新
阅读 0·2月22日 14:05

如何在 React 中使用 MobX?

在 React 中使用 MobX 需要将 MobX 的响应式状态与 React 的渲染机制连接起来。MobX 提供了多种方式来实现这种集成。安装依赖npm install mobx mobx-react-lite# 或者npm install mobx mobx-react使用方式1. 使用 observer 高阶组件(mobx-react)import React from 'react';import { observer } from 'mobx-react';import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; }}const todoStore = new TodoStore();// 使用 observer 包装组件const TodoList = observer(() => { return ( <div> <h1>Todo List</h1> <ul> {todoStore.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => todoStore.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => todoStore.addTodo('New Todo')}> Add Todo </button> </div> );});export default TodoList;2. 使用 useObserver Hook(mobx-react-lite)import React from 'react';import { useObserver } from 'mobx-react-lite';import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; }}const todoStore = new TodoStore();function TodoList() { return useObserver(() => ( <div> <h1>Todo List</h1> <ul> {todoStore.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => todoStore.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => todoStore.addTodo('New Todo')}> Add Todo </button> </div> ));}export default TodoList;3. 使用 useLocalObservable Hook(mobx-react-lite)import React from 'react';import { observer, useLocalObservable } from 'mobx-react-lite';function TodoList() { const store = useLocalObservable(() => ({ todos: [], addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); }, toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } })); return ( <div> <h1>Todo List</h1> <ul> {store.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => store.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => store.addTodo('New Todo')}> Add Todo </button> </div> );}export default observer(TodoList);4. 使用 React Context 共享 Storeimport React, { createContext, useContext } from 'react';import { observer } from 'mobx-react';import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; }}const TodoContext = createContext(null);function TodoProvider({ children }) { const store = new TodoStore(); return ( <TodoContext.Provider value={store}> {children} </TodoContext.Provider> );}function useTodoStore() { const store = useContext(TodoContext); if (!store) { throw new Error('useTodoStore must be used within TodoProvider'); } return store;}const TodoList = observer(() => { const store = useTodoStore(); return ( <div> <h1>Todo List</h1> <ul> {store.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => store.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => store.addTodo('New Todo')}> Add Todo </button> </div> );});function App() { return ( <TodoProvider> <TodoList /> </TodoProvider> );}export default App;5. 使用 Provider 和 inject(mobx-react 旧版)import React from 'react';import { Provider, observer, inject } from 'mobx-react';import { makeAutoObservable } from 'mobx';class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; }}const todoStore = new TodoStore();@inject('todoStore')@observerclass TodoList extends React.Component { render() { const { todoStore } = this.props; return ( <div> <h1>Todo List</h1> <ul> {todoStore.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => todoStore.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => todoStore.addTodo('New Todo')}> Add Todo </button> </div> ); }}function App() { return ( <Provider todoStore={todoStore}> <TodoList /> </Provider> );}export default App;最佳实践1. 使用 observer 包裹需要响应式更新的组件// ✅ 正确:只包裹需要响应式更新的组件const TodoItem = observer(({ todo }) => ( <li> <input type="checkbox" checked={todo.completed} onChange={() => todo.toggle()} /> <span>{todo.text}</span> </li>));const TodoList = ({ store }) => ( <ul> {store.todos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul>);// ❌ 错误:包裹整个应用,可能导致不必要的渲染const App = observer(() => ( <div> <Header /> <TodoList /> <Footer /> </div>));2. 合理拆分 Store// ✅ 正确:按功能拆分 Storeclass TodoStore { @observable todos = []; @observable filter = 'all'; @action addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); }}class UserStore { @observable user = null; @action setUser(user) { this.user = user; }}class AppStore { todoStore = new TodoStore(); userStore = new UserStore();}// 使用 Context 共享const StoreContext = createContext(new AppStore());3. 使用 computed 优化性能class TodoStore { @observable todos = []; @observable filter = 'all'; @computed get filteredTodos() { switch (this.filter) { case 'completed': return this.todos.filter(todo => todo.completed); case 'active': return this.todos.filter(todo => !todo.completed); default: return this.todos; } }}const TodoList = observer(({ store }) => ( <ul> {store.filteredTodos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul>));4. 使用 React.memo 优化子组件const TodoItem = observer(React.memo(({ todo }) => ( <li> <input type="checkbox" checked={todo.completed} onChange={() => todo.toggle()} /> <span>{todo.text}</span> </li>)));5. 使用 useEffect 清理副作用import { useEffect } from 'react';import { reaction } from 'mobx';const TodoList = observer(({ store }) => { useEffect(() => { const disposer = reaction( () => store.todos.length, (length) => { console.log('Todo count changed:', length); } ); return () => disposer(); }, [store]); return ( <ul> {store.todos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul> );});常见问题1. 组件不更新// ❌ 错误:忘记使用 observerfunction TodoList({ store }) { return ( <ul> {store.todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> );}// ✅ 正确:使用 observerconst TodoList = observer(({ store }) => ( <ul> {store.todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul>));2. 在 observer 组件外修改状态// ❌ 错误:在 observer 组件外直接修改状态function App() { const store = useStore(); useEffect(() => { store.todos.push({ id: 1, text: 'Todo' }); // 不在 action 中 }, []); return <TodoList store={store} />;}// ✅ 正确:在 action 中修改状态function App() { const store = useStore(); useEffect(() => { store.addTodo('Todo'); // 在 action 中 }, []); return <TodoList store={store} />;}3. 过度使用 observer// ❌ 错误:过度使用 observerconst Header = observer(() => <header>Header</header>);const Footer = observer(() => <footer>Footer</footer>);const Main = observer(() => <main>Main</main>);// ✅ 正确:只在需要响应式更新的组件使用 observerconst Header = () => <header>Header</header>;const Footer = () => <footer>Footer</footer>;const Main = observer(() => <main>Main</main>);总结使用 observer 或 useObserver 将组件连接到 MobX使用 React Context 共享 Store合理拆分 Store,按功能模块组织使用 computed 优化性能使用 React.memo 优化子组件记得在 useEffect 中清理 reaction避免过度使用 observer始终在 action 中修改状态
阅读 0·2月22日 14:05

RPC 框架中常见的序列化协议有哪些?它们各有什么优缺点?

序列化是 RPC 框架中的核心组件,直接影响性能和效率。常见的序列化协议各有特点:1. Protobuf(Protocol Buffers)特点:Google 开发,二进制格式,高效紧凑优势:序列化/反序列化速度快数据体积小,传输效率高支持多语言(Java、Python、Go、C++等)向后兼容性好定义清晰的数据结构(.proto 文件)劣势:可读性差,需要 .proto 文件不支持动态类型适用场景:高性能要求的微服务通信2. Thrift特点:Facebook 开发,支持多种协议和传输方式优势:支持多种序列化格式(Binary、JSON、Compact)支持多种传输协议(TCP、HTTP、Memory)代码生成功能强大支持异步和同步调用劣势:学习曲线较陡文档相对较少适用场景:跨语言、多协议的复杂场景3. JSON特点:文本格式,易读易写优势:人类可读,调试方便通用性强,所有语言都支持灵活,支持动态类型浏览器原生支持劣势:数据体积大,传输效率低序列化/反序列化速度慢类型安全性差适用场景:对外 API、Web 应用4. Avro特点:Apache 项目,支持模式演化优势:支持动态模式,无需代码生成模式演化能力强压缩率高适合大数据场景劣势:学习成本较高相对小众适用场景:大数据处理、日志收集5. MessagePack特点:二进制 JSON,高效紧凑优势:比 JSON 更小更快保持 JSON 的数据类型支持多种语言劣势:可读性不如 JSON生态系统相对较小适用场景:需要 JSON 兼容性但要求更高性能的场景6. Hessian特点:二进制序列化,动态类型优势:序列化速度快支持动态类型跨语言支持劣势:数据体积相对较大社区活跃度不高适用场景:Java 生态的 RPC 调用性能对比(大致排序):序列化速度:Protobuf > Hessian > Thrift > MessagePack > Avro > JSON数据体积:Protobuf > MessagePack > Thrift > Hessian > Avro > JSON可读性:JSON > Avro > MessagePack > Thrift > Protobuf > Hessian选择建议:高性能内部服务:Protobuf、Thrift对外 API:JSON大数据场景:Avro需要 JSON 兼容性:MessagePackJava 生态:Hessian
阅读 0·2月22日 14:05

RPC 框架中的负载均衡算法有哪些?它们各有什么优缺点和适用场景?

负载均衡是 RPC 框架中的核心组件,负责将请求分发到多个服务实例,提高系统性能和可用性:常见负载均衡算法:1. 随机算法(Random)原理:随机选择一个服务实例加权随机:根据实例权重设置选择概率优点:实现简单,请求分布均匀缺点:不考虑实例当前负载适用场景:实例性能相近的场景实现示例: public class RandomLoadBalancer { private List<ServiceInstance> instances; private Random random = new Random(); public ServiceInstance select() { int index = random.nextInt(instances.size()); return instances.get(index); } }2. 轮询算法(Round Robin)原理:按顺序依次选择服务实例加权轮询:根据权重分配请求比例优点:请求分布均匀,实现简单缺点:不考虑实例响应时间差异适用场景:实例性能相近的场景实现示例: public class RoundRobinLoadBalancer { private List<ServiceInstance> instances; private AtomicInteger index = new AtomicInteger(0); public ServiceInstance select() { int idx = index.getAndIncrement() % instances.size(); return instances.get(idx); } }3. 最少连接算法(Least Connections)原理:选择当前连接数最少的实例优点:考虑实例当前负载,动态分配缺点:需要维护连接数统计适用场景:请求处理时间差异较大的场景实现示例: public class LeastConnectionsLoadBalancer { private List<ServiceInstance> instances; public ServiceInstance select() { return instances.stream() .min(Comparator.comparingInt(ServiceInstance::getActiveConnections)) .orElse(null); } }4. 一致性哈希算法(Consistent Hash)原理:将请求和服务实例映射到哈希环上优点:相同请求总是路由到同一实例实例增减时影响范围小支持会话保持缺点:实现复杂,可能分布不均适用场景:需要会话保持或缓存一致的场景实现示例: public class ConsistentHashLoadBalancer { private TreeMap<Integer, ServiceInstance> ring = new TreeMap<>(); public void addInstance(ServiceInstance instance) { for (int i = 0; i < 100; i++) { int hash = hash(instance.getAddress() + "#" + i); ring.put(hash, instance); } } public ServiceInstance select(String key) { int hash = hash(key); Map.Entry<Integer, ServiceInstance> entry = ring.ceilingEntry(hash); if (entry == null) { entry = ring.firstEntry(); } return entry.getValue(); } }5. 最小响应时间算法(Least Response Time)原理:选择平均响应时间最短的实例优点:动态适应实例性能变化缺点:需要维护响应时间统计适用场景:实例性能差异较大的场景6. IP 哈希算法(IP Hash)原理:根据客户端 IP 进行哈希优点:相同客户端总是访问同一实例缺点:可能导致负载不均适用场景:需要会话保持的场景7. 加权算法(Weighted)原理:根据实例权重分配请求加权随机:按权重随机选择加权轮询:按权重比例轮询优点:可以根据实例性能分配不同权重适用场景:实例性能差异较大的场景Dubbo 负载均衡配置:<dubbo:reference interface="com.example.UserService" loadbalance="random"/><!-- 可选值:random, roundrobin, leastactive, consistenthash -->gRPC 负载均衡:客户端负载均衡:客户端维护服务列表并选择实例服务端负载均衡:通过代理(如 Envoy)进行负载均衡支持策略:轮询、随机、最少连接等Nginx 负载均衡配置:upstream backend { # 轮询 server 192.168.1.1:8080; server 192.168.1.2:8080; # 加权轮询 server 192.168.1.1:8080 weight=3; server 192.168.1.2:8080 weight=1; # IP 哈希 ip_hash; # 最少连接 least_conn;}选择建议:实例性能相近:随机、轮询需要会话保持:一致性哈希、IP 哈希实例性能差异大:加权算法、最少连接、最小响应时间缓存一致性要求高:一致性哈希简单场景:随机、轮询注意事项:结合健康检查剔除故障实例定期更新服务实例列表监控负载均衡效果根据实际情况调整算法参数
阅读 0·2月22日 14:03