当前位置:首页 > 问答 > 正文

Redis缓存 前端异步实现从Redis中获取数据,前端如何异步从redis取数据

Redis缓存 | 前端如何优雅地异步获取Redis数据

场景引入:电商首页的性能瓶颈

想象一下,你正在开发一个电商平台的首页,每次用户刷新页面,都需要从数据库加载商品推荐、促销活动、用户个性化设置等数据,随着流量增长,数据库开始不堪重负,页面加载时间从1秒逐渐恶化到3-4秒,这时候,技术团队决定引入Redis作为缓存层,但问题来了:前端如何高效地从Redis获取这些数据?

前端与Redis的交互原理

首先需要明确的是,前端通常不会直接连接Redis,Redis是内存数据库,设计上是为后端服务提供高速缓存能力,前端与Redis的交互需要通过后端API作为桥梁,大致流程是这样的:

  1. 前端发起异步请求(如fetch或axios)
  2. 后端API接收到请求
  3. 后端首先检查Redis中是否有缓存数据
  4. 如果有,直接从Redis返回;如果没有,从数据库获取并存入Redis
  5. 数据返回给前端

具体实现方案

常规HTTP API调用

这是最常见的方式,前端像调用普通API一样获取数据,完全不用关心后端是否用了Redis。

async function fetchProductRecommendations() {
  try {
    const response = await fetch('/api/recommendations');
    if (!response.ok) {
      throw new Error('Network response was not ok');
    }
    const data = await response.json();
    console.log('推荐商品:', data);
    return data;
  } catch (error) {
    console.error('获取推荐失败:', error);
    // 可以在这里添加降级处理,比如返回本地缓存或默认数据
    return getLocalFallbackData();
  }
}

后端对应的Node.js代码示例:

import redis from 'redis';
const client = redis.createClient();
const cacheKey = 'product:recommendations';
app.get('/api/recommendations', async (req, res) => {
  try {
    // 先尝试从Redis获取
    const cachedData = await client.get(cacheKey);
    if (cachedData) {
      return res.json(JSON.parse(cachedData));
    }
    // Redis中没有,从数据库获取
    const dbData = await db.query('SELECT * FROM products WHERE recommended = true');
    // 存入Redis,设置1小时过期
    await client.setex(cacheKey, 3600, JSON.stringify(dbData));
    res.json(dbData);
  } catch (err) {
    res.status(500).json({ error: '服务器错误' });
  }
});

WebSocket实时推送

对于需要实时更新的数据,可以考虑WebSocket方案:

const socket = new WebSocket('wss://your-api.com/realtime');
socket.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'recommendations') {
    updateUI(data.payload);
  }
};
// 初始化时请求一次数据
socket.onopen = () => {
  socket.send(JSON.stringify({ 
    action: 'subscribe',
    channel: 'recommendations'
  }));
};

后端需要维护WebSocket连接并在Redis数据变更时推送:

// Node.js + WebSocket示例
wss.on('connection', (ws) => {
  // 订阅Redis频道
  const subscriber = redis.createClient();
  subscriber.subscribe('recommendations_update');
  subscriber.on('message', (channel, message) => {
    if (channel === 'recommendations_update') {
      ws.send(message);
    }
  });
  // 客户端断开连接时取消订阅
  ws.on('close', () => {
    subscriber.unsubscribe();
  });
});

Server-Sent Events (SSE)

SSE是另一种实时通信方案,比WebSocket更简单:

Redis缓存 前端异步实现从Redis中获取数据,前端如何异步从redis取数据

const eventSource = new EventSource('/api/recommendations/stream');
eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  updateRecommendations(data);
};
eventSource.onerror = () => {
  console.log('SSE连接错误,尝试重新连接...');
  // EventSource会自动重连
};

后端实现:

app.get('/api/recommendations/stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  // 初始发送一次数据
  sendCurrentData();
  // 订阅Redis变更
  const subscriber = redis.createClient();
  subscriber.subscribe('recommendations_update');
  subscriber.on('message', (channel, message) => {
    if (channel === 'recommendations_update') {
      res.write(`data: ${message}\n\n`);
    }
  });
  // 客户端断开时清理
  req.on('close', () => {
    subscriber.unsubscribe();
  });
  async function sendCurrentData() {
    const data = await getRecommendations();
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  }
});

性能优化技巧

  1. 批量请求:合并多个小请求为一个批量请求

    async function fetchMultipleResources() {
      const [userData, recommendations, promotions] = await Promise.all([
        fetch('/api/user'),
        fetch('/api/recommendations'),
        fetch('/api/promotions')
      ]);
      // 处理数据...
    }
  2. 客户端缓存:使用localStorage或sessionStorage暂存数据

    async function getCachedData(key, fetchFunc) {
      const cached = localStorage.getItem(key);
      if (cached) {
        return JSON.parse(cached);
      }
      const freshData = await fetchFunc();
      localStorage.setItem(key, JSON.stringify(freshData));
      return freshData;
    }
  3. 过期策略:为客户端缓存设置合理的过期时间

    function isCacheValid(key, maxAge) {
      const entry = localStorage.getItem(key + '_meta');
      if (!entry) return false;
      const { timestamp } = JSON.parse(entry);
      return Date.now() - timestamp < maxAge;
    }
  4. 请求去重:避免短时间内重复请求相同资源

    Redis缓存 前端异步实现从Redis中获取数据,前端如何异步从redis取数据

    const pendingRequests = {};
    async function dedupedFetch(url) {
      if (pendingRequests[url]) {
        return pendingRequests[url];
      }
      const promise = fetch(url).finally(() => {
        delete pendingRequests[url];
      });
      pendingRequests[url] = promise;
      return promise;
    }

错误处理与降级方案

即使有了Redis缓存,网络请求仍可能失败,良好的错误处理至关重要:

async function getDataWithFallback() {
  try {
    const response = await fetch('/api/data');
    if (!response.ok) throw new Error('API错误');
    return await response.json();
  } catch (error) {
    console.warn('使用备用数据:', error);
    // 1. 尝试从localStorage获取缓存
    const cached = localStorage.getItem('fallbackData');
    if (cached) return JSON.parse(cached);
    // 2. 使用硬编码的默认数据
    return getDefaultData();
    // 3. 根据业务需求,可以返回空数据或显示错误界面
  }
}

安全考虑

  1. 敏感数据:即使Redis中的数据是缓存,也要像处理数据库数据一样考虑敏感信息过滤
  2. 缓存穿透:前端异常请求可能导致缓存失效,后端应实现防护
  3. 缓存中毒:确保存入Redis的数据经过严格验证
  4. HTTPS:所有API请求必须使用加密连接

实际案例:电商首页优化

假设我们有一个电商首页需要展示:

  • 个性化商品推荐
  • 促销活动信息
  • 用户浏览历史
  • 库存状态

优化后的数据流:

  1. 页面加载时发起单个组合请求:

    async function loadHomePageData() {
      const response = await fetch('/api/home?userId=123');
      const {
        recommendations,
        promotions,
        history,
        inventory
      } = await response.json();
      renderRecommendations(recommendations);
      renderPromotions(promotions);
      // ...其他渲染逻辑
    }
  2. 后端实现:

    Redis缓存 前端异步实现从Redis中获取数据,前端如何异步从redis取数据

    app.get('/api/home', async (req, res) => {
      const userId = req.query.userId;
      const cacheKey = `home:${userId}`;
      try {
        const cached = await redis.get(cacheKey);
        if (cached) {
          return res.json(JSON.parse(cached));
        }
        const [recs, promos, hist, inv] = await Promise.all([
          getRecommendations(userId),
          getPromotions(),
          getHistory(userId),
          getInventoryStatus()
        ]);
        const responseData = {
          recommendations: recs,
          promotions: promos,
          history: hist,
          inventory: inv
        };
        // 缓存5分钟
        await redis.setex(cacheKey, 300, JSON.stringify(responseData));
        res.json(responseData);
      } catch (err) {
        res.status(500).json({ error: err.message });
      }
    });
  3. 实时库存更新通过SSE推送:

    const inventoryUpdates = new EventSource('/api/inventory/updates');
    inventoryUpdates.onmessage = (event) => {
      const update = JSON.parse(event.data);
      updateInventoryDisplay(update.productId, update.stock);
    };

通过Redis缓存结合前端异步请求,可以显著提升应用性能,关键点包括:

  1. 前端通过常规API、WebSocket或SSE与后端交互
  2. 后端负责Redis缓存逻辑,对前端透明
  3. 实现批量请求、客户端缓存等优化技巧
  4. 完善的错误处理和降级方案
  5. 注意安全性和实时性需求

Redis是性能优化的利器,但不要过度依赖,合理的缓存策略加上优雅的前端实现,才能打造出真正流畅的用户体验。

发表评论