Express应用Redis缓存PostgreSQL瓦片数据返回异常求助
Express瓦片缓存Redis后无法正常加载的问题排查与解决
问题背景
开发Express应用时,通过PostgreSQL的query_tile函数获取MVT瓦片数据,计划将结果缓存至Redis,但遇到以下问题:
- 初始实现:缓存原始瓦片数据,取出后再压缩返回,结果与直接查库的压缩数据不一致,无法正常读取。
- 优化后:改为缓存压缩后的瓦片数据,直接查库的瓦片可正常下载,但缓存的瓦片在浏览器中提示检查网络连接,无法加载。通过Redis Insight观察,缓存的瓦片以二进制形式存储,但被标注为字符串类型。
原始代码
初始实现代码
app.get('/query_tile/:z/:x/:y', async (req: Request, res: Response) => { const client = new Client(connection); await client.connect(); const setHeader = () => { res.set('Content-Encoding', 'br'); res.set('Content-Type', 'application/x-protobuf'); res.set('Vary', 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers'); res.set('Content-Disposition', `attachment; filename=tile_${z}_${x}_${y}.mvt`); } const { z, x, y } = req.params; const cacheKey = `tile_${z}_${x}_${y}`; const cachedData = await redis.get(cacheKey); if (cachedData) { setHeader() res.send(brotliCompressSync(cachedData)); } else { const { rows } = await client.query('SELECT query_tile($1, $2, $3)', [z, x, y]); const data = rows[0]; const compressedData = brotliCompressSync(data.query_tile); await redis.set(cacheKey, data.query_tile); setHeader() res.send(compressedData); } client.end() });
修改后代码
let cachedData = null; if (useCache) { cachedData = await redis.get(cacheKey); } if (cachedData) { const t1 = performance.now() console.log(`SENDING CACHED tile_${z}_${x}_${y}, took ${(t1 - t0) / 1000}s`); setHeader() res.send(cachedData); } else { const { rows } = await client.query('SELECT query_tile($1, $2, $3)', [z, x, y]); const data = rows[0]; const compressedData = brotliCompressSync(data.query_tile); await redis.set(cacheKey, compressedData); const t1 = performance.now() console.log(`SENDING DB tile_${z}_${x}_${y}, took ${(t1 - t0) / 1000}s`); setHeader() res.send(compressedData); }
问题根源
初始代码问题:
PostgreSQL返回的data.query_tile是二进制Buffer,存入Redis时客户端默认将其转为UTF-8字符串,导致原始二进制数据被篡改。取出后再次压缩,结果与直接压缩原始Buffer的输出不一致,无法被浏览器识别。修改后代码问题:
缓存的compressedData虽为Buffer,但Redis客户端仍自动转为字符串存储。取出cachedData时得到的是字符串,Express发送时按字符串编码处理,导致浏览器收到的内容格式错误,触发网络连接提示。
解决方案
核心思路
确保Redis存储和读取二进制数据时保持Buffer格式,避免自动转码。
调整后完整代码
app.get('/query_tile/:z/:x/:y', async (req: Request, res: Response) => { const client = new Client(connection); await client.connect(); const setHeader = () => { res.set('Content-Encoding', 'br'); res.set('Content-Type', 'application/x-protobuf'); res.set('Vary', 'Origin, Access-Control-Request-Method, Access-Control-Request-Headers'); res.set('Content-Disposition', `attachment; filename=tile_${z}_${x}_${y}.mvt`); } const { z, x, y } = req.params; const cacheKey = `tile_${z}_${x}_${y}`; let cachedData = null; if (useCache) { // 读取时指定返回Buffer类型 cachedData = await redis.getBuffer(cacheKey); } if (cachedData) { const t1 = performance.now() console.log(`SENDING CACHED tile_${z}_${x}_${y}, took ${(t1 - t0) / 1000}s`); setHeader() res.send(cachedData); } else { const { rows } = await client.query('SELECT query_tile($1, $2, $3)', [z, x, y]); const data = rows[0]; const compressedData = brotliCompressSync(data.query_tile); // 直接存入Buffer,避免客户端自动转字符串 await redis.set(cacheKey, compressedData); const t1 = performance.now() console.log(`SENDING DB tile_${z}_${x}_${y}, took ${(t1 - t0) / 1000}s`); setHeader() res.send(compressedData); } // 等待数据库连接关闭完成 await client.end(); });
关键修改点
- 读取缓存:使用
getBuffer()替代get(),确保获取原始二进制Buffer,而非转码后的字符串。 - 存入缓存:直接传入Buffer类型的
compressedData,Redis会以二进制形式存储,不会自动转码。 - 数据库连接:将
client.end()改为await client.end(),避免异步查询未完成时提前关闭连接。
验证
修改后通过Redis Insight查看,缓存的瓦片数据会被标注为Binary类型,此时浏览器加载缓存的瓦片即可正常识别。
内容的提问来源于stack exchange,提问作者pcace
相关产品推荐
相关产品推荐

