Geth并发创建以太坊账户耗时长、网络冻结原因及解决方案
现象本质与底层原理
并发创建账户卡顿、节点冻结问题,核心来自Geth客户端personal_newAccount接口的实现逻辑和RPC请求处理机制,和区块链共识层没有直接关系:
personal_newAccount不是链上交易操作,完全是Geth节点本地的密钥库处理逻辑:生成secp256k1密钥对→用scrypt内存硬哈希算法做密钥派生(单次要占用32MB+内存、100-300ms单核心CPU时间)→加密私钥后写入本地keystore目录。- Geth的keystore模块持有全局互斥写锁,同一时间只能处理1个账户写入操作,无论并发发起多少个请求,实际都是串行排队执行,并发数提升不会带来任何速度增益,只会堆积请求。
- Geth的Websocket/HTTP RPC服务默认会把所有收到的请求放入待处理队列,即使客户端断开连接,已经入队的请求也不会被清除,节点会逐个执行完所有队列任务才会释放资源。
- 当并发请求数超过15左右时,RPC队列开始堆积:每个排队的请求都会占用连接上下文内存,等待锁的过程会阻塞RPC工作协程,队列长度到千级时,大量scrypt计算任务会占满节点CPU、内存资源,直接阻塞节点主事件循环,导致出块、P2P通信、其他RPC请求全部无法处理,表现为节点冻结;即使终止脚本,队列里的任务仍会继续执行,通常需要数十分钟才能跑完,体感上和永久故障一致,重启节点会直接清空未处理队列,因此可以立刻恢复。
- 同步串行创建没有异常,是因为同一时间节点只会收到1个待处理请求,不会出现队列堆积。
问题对应的并发实现代码如下:
const Web3 = require('web3'); const web3 = new Web3(new Web3.providers.WebsocketProvider("ws://127.0.0.1:8545")); const generateAccount = () => { const count = 30; let promises = [] for (let i=0; i<count; i++) { promises.push(web3.eth.personal.newAccount("Hello@12345")); } Promise.allSettled(promises).then( status => { console.log(status) process.exit(0) } ).catch( err => { console.log(err) process.exit(1) } ) } generateAccount()
该写法会在极短时间内向节点一次性推送count个创建请求,是导致队列堆积的直接原因。
大规模创建账户最佳实践
针对千级以上批量创建账户的需求,按优先级推荐以下方案:
- 优先采用离线本地生成方案(性能最高、零节点负载)
以太坊账户本质是独立的secp256k1密钥对,完全不需要连接节点即可生成,生成后只要把加密后的keystore文件放入Geth的keystore目录,节点重启后会自动加载所有账户,整个过程不会对节点运行造成任何影响。
参考实现:
const Web3 = require('web3'); const fs = require('fs'); const path = require('path'); // 不需要连接节点,初始化空web3实例即可 const web3 = new Web3(); const password = "Hello@12345"; // 替换为你的Geth keystore实际目录 const keystoreDir = "./ethereum/keystore"; const count = 1000; const accountList = []; console.log(`开始生成${count}个账户...`); for (let i = 0; i < count; i++) { // 本地生成密钥对,无网络、无RPC开销 const account = web3.eth.accounts.create(); // 生成和Geth完全兼容的keystore加密格式 const keystore = account.encrypt(password); // 按照Geth keystore命名规范写入文件 const fileName = `UTC--${new Date().toISOString().replace(/[:.]/g,'-')}--${account.address.slice(2).toLowerCase()}`; fs.writeFileSync(path.join(keystoreDir, fileName), JSON.stringify(keystore)); accountList.push(account.address); } console.log(`生成完成,共${accountList.length}个账户,重启Geth节点即可加载`);
该方案生成1000个账户仅需数秒,完全不会出现节点卡顿问题。如果不想重启节点,可以在生成keystore后逐个调用personal_importRawKey接口导入,配合2-3的低并发控制即可,不会造成队列堆积。
- 受限于架构必须走RPC创建时,严格控制并发数
因为keystore全局锁的存在,并发数超过2就不会有任何速度提升,必须通过并发池限制同时在途的请求数不超过2-3,避免请求堆积。
参考并发控制逻辑:
const Web3 = require('web3'); const web3 = new Web3(new Web3.providers.WebsocketProvider("ws://127.0.0.1:8545")); const generateAccount = async () => { const count = 1000; const concurrency = 2; // 不要调大,无性能收益 const runningTasks = new Set(); const results = []; for (let i = 0; i < count; i++) { const task = web3.eth.personal.newAccount("Hello@12345") .then(addr => { results.push(addr); runningTasks.delete(task); }); runningTasks.add(task); if (runningTasks.size >= concurrency) { await Promise.race(runningTasks); } } await Promise.all(runningTasks); console.log(`创建完成,共${results.length}个账户`); process.exit(0); } generateAccount().catch(err => { console.error(err); process.exit(1); });
该方案不会打满节点RPC队列,但创建速度远低于离线生成方案,千级账户预计需要数分钟完成。
- 不推荐通过调整Geth RPC参数(如队列长度、连接数上限)的方式提升批量创建性能,这类调整无法解决keystore全局锁和scrypt计算资源占用的核心问题,只会提升节点OOM、卡死的概率。
内容的提问来源于stack exchange,提问作者Mitesh Agrawal
相关产品推荐
相关产品推荐

