You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);
    }

问题根源

  1. 初始代码问题:
    PostgreSQL返回的data.query_tile是二进制Buffer,存入Redis时客户端默认将其转为UTF-8字符串,导致原始二进制数据被篡改。取出后再次压缩,结果与直接压缩原始Buffer的输出不一致,无法被浏览器识别。

  2. 修改后代码问题:
    缓存的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 03:37:21