AWS SAM本地运行连接Redis报Client Error如何解决
接口请求触发Lambda函数访问Redis完成数据读写时,基础配置下逻辑运行失败,报错信息如下:
INFO Redis Client Error Error: connect at TCPConnectWrap.afterConnect [as oncomplete] (node} port: 6379 127.0.0.1',
已知前置条件:
- 本地redis-server服务已正常启动,配置访问凭证后监听
127.0.0.1:6379端口 - 已知AWS SAM本地运行基于容器实现,判断连接问题大概率由网络配置导致,目前已知AWS SAM CLI仅提供
--host启动参数,需要定位并修复该连接异常。
Lambda业务代码
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { createClient } from 'redis'; import processData from './src/lambda-data-dictionary-read/core/service/controllers/processData'; export async function lambdaHandler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> { const body: any = await processData(event.queryStringParameters); const url = process.env.REDIS_URL || 'redis://127.0.0.1:6379'; const client = createClient({ url, }); client.on('error', (err) => console.log('Redis Client Error', err)); await client.connect(); await client.set('key', 'value'); const value = await client.get('key'); console.log('----', value, '----'); const response: APIGatewayProxyResult = { statusCode: 200, body, }; if (body.error) { return { statusCode: 404, body, }; } return response; }
template.yaml配置
Transform: AWS::Serverless-2016-10-31 Description: > lambda-data-dictionary-read Sample SAM Template for lambda-data-dictionary-read Globals: Function: Timeout: 0 Resources: IndexFunction: Type: AWS::Serverless::Function Properties: CodeUri: app/ Handler: index.lambdaHandler Runtime: nodejs16.x Timeout: 10 Architectures: - x86_64 Environment: Variables: ENV: !Ref develope REDIS_URL: !Ref redis://127.0.0.1:6379 Events: Index: Type: Api Properties: Path: /api/lambda-data-dictionary-read Method: get Metadata: BuildMethod: esbuild BuildProperties: Minify: true Target: 'es2020' Sourcemap: true UseNpmCi: true
本地启动脚本
"scripts": { "dev": "sam build --cached --beta-features && sam local start-api --port 8080 --host 127.0.0.1" }
核心原因
SAM本地启动的Lambda运行在独立Docker容器内,容器内部的127.0.0.1指向容器自身的回环地址,并非宿主机的回环地址,因此无法直接访问宿主机上监听127.0.0.1的Redis服务,同时现有配置存在模板语法错误、连接复用缺失的问题。
调整Redis监听配置
打开Redis配置文件(默认路径通常为/etc/redis/redis.conf,自定义启动则使用你指定的配置文件),将bind 127.0.0.1修改为bind 0.0.0.0,保存后重启redis-server,允许Docker网段的IP访问Redis服务。
注意:该配置仅适用于本地开发环境,生产环境禁止直接绑定0.0.0.0,需通过安全组、访问密码、IP白名单限制访问来源修正SAM启动网络配置
Linux环境下直接修改启动脚本,添加--docker-network host参数,让Lambda容器直接复用宿主机网络栈,修改后脚本如下:"scripts": { "dev": "sam build --cached --beta-features && sam local start-api --port 8080 --host 0.0.0.0 --docker-network host" }Mac/Windows环境运行Docker Desktop时,host网络模式不生效,无需添加该参数,直接将Redis连接地址改为Docker内置的宿主机解析域名
host.docker.internal即可。修复template.yaml语法错误
现有配置中ENV和REDIS_URL字段错误使用了!Ref语法,!Ref仅用于引用模板内定义的参数、资源,直接赋值字符串不需要加该语法,修改后对应配置段如下:Environment: Variables: ENV: develope REDIS_URL: redis://127.0.0.1:6379Mac/Windows环境下将
REDIS_URL的值替换为redis://host.docker.internal:6379即可。优化Redis连接逻辑
现有代码每次请求都会新建Redis连接,高并发场景下会打满Redis连接数上限,将客户端初始化逻辑移到Handler外部,复用Lambda冷启动后的长连接,减少重复建连开销,修改后代码参考:import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { createClient } from 'redis'; import processData from './src/lambda-data-dictionary-read/core/service/controllers/processData'; // 冷启动阶段初始化一次Redis客户端 const redisClient = createClient({ url: process.env.REDIS_URL || 'redis://127.0.0.1:6379', }); redisClient.on('error', (err) => console.log('Redis Client Error', err)); const redisConnectPromise = redisClient.connect(); export async function lambdaHandler(event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> { await redisConnectPromise; // 等待连接就绪后处理业务 const body: any = await processData(event.queryStringParameters); await redisClient.set('key', 'value'); const value = await redisClient.get('key'); console.log('----', value, '----'); if (body.error) { return { statusCode: 404, body, }; } return { statusCode: 200, body, }; }
所有配置修改完成后,重新执行npm run dev启动本地服务,触发接口即可正常完成Redis读写操作。
内容的提问来源于stack exchange,提问作者Martín LC

