前端缓存实战:用Service Worker和IndexedDB应对高并发场景

更新日期: 2025-10-26 阅读: 30 标签: 高并发

凌晨三点,我被一阵急促的告警声惊醒。手机屏幕上显示:"接口响应超时率超过80%,网关持续熔断。"

这不是服务器故障,也不是数据库问题,而是用户量突然暴增带来的连锁反应。某个功能被大规模使用,海量请求涌向几个核心接口,形成了调用风暴。

后端团队紧急扩容服务器,但资源有限,响应速度依然缓慢。更糟糕的是,用户开始频繁刷新页面,每次刷新都产生新的请求,形成了恶性循环。

我们意识到:单纯依靠后端已经无法应对这种情况。

有没有办法在用户端拦截重复请求,用本地缓存来分担压力?不改动接口、不调整架构、不影响现有业务逻辑。

答案是:Service Worker + IndexedDB。

我们通过独立部署的sw.js文件,在浏览器底层拦截高频请求。命中的请求直接返回缓存结果,未命中的才走网络请求。整个过程用户无感知,业务代码无需修改。

上线后,关键接口的外部请求量下降70%,页面加载速度大幅提升,系统告警解除。

这不是技术炫技,而是一次真实的前端"反向兜底"实践。下面分享这套方案的完整细节。


核心策略:区分请求类型处理

1. GET请求使用Cache Storage

对于行情主表这类GET接口,使用Cache Storage是最佳选择:

self.addEventListener('fetch', function(event) {
    const url = event.request.url;
    if (url.includes('finance/stock/maintable/index_merge')) {
        handleCache(event, 'STOCK_MAIN', 60 * 1000); // 缓存1分钟
    }
});

缓存处理逻辑:

function handleCache(event, cacheName, maxAge) {
    event.respondWith(
        caches.match(event.request).then(cached => {
            // 如果缓存存在且未过期,直接返回
            if (cached) {
                const cachedTime = new Date(cached.headers.get('Date')).getTime();
                if (Date.now() - cachedTime < maxAge) {
                    return cached;
                }
            }
            // 缓存不存在或已过期,走网络请求
            return fetch(event.request).then(response => {
                if (response.ok) {
                    const clone = response.clone();
                    caches.open(cacheName).then(cache => {
                        cache.put(event.request, clone);
                    });
                }
                return response;
            });
        })
    );
}

优点:浏览器原生支持,性能优秀,适合幂等性GET请求。

2. POST请求使用IndexedDB

很多高频接口是POST请求,比如指标计算、文本分析等。这些请求参数不同,结果也不同。

Cache Storage只支持GET请求,这时候就需要IndexedDB:

if (event.request.method === 'POST' && url.includes('statistic/component/ptext')) {
    handlePostCache(event, 'POST_TEXT_CACHE');
}

POST请求缓存处理:

function handlePostCache(event, storeName) {
    event.respondWith((async function() {
        const request = event.request.clone();
        const body = await request.text(); // 使用请求体作为缓存键

        // 尝试从缓存获取
        const cached = await idbGet(storeName, body);
        if (cached) {
            return new Response(cached.body, cached.options);
        }

        // 缓存未命中,发起网络请求
        const response = await fetch(event.request.clone());
        if (response.ok) {
            const responseBody = await response.clone().text();
            const options = {
                status: response.status,
                statusText: response.statusText,
                headers: Array.from(response.headers.entries())
            };
            // 缓存响应结果
            await idbSet(storeName, body, { 
                body: responseBody, 
                options: options 
            });
        }
        return response;
    })());
}

关键设计:

  • 使用请求体内容作为缓存键,确保不同参数返回不同结果

  • 缓存响应头和状态码,完整还原接口行为

  • 异步操作,不阻塞主线程

3. IndexedDB轻量封装

提供简单的读写封装:

function idbGet(store, key) {
    return new Promise((resolve, reject) => {
        const open = indexedDB.open('sw-cache-db', 1);
        
        open.onupgradeneeded = function(e) {
            const db = e.target.result;
            if (!db.objectStoreNames.contains(store)) {
                db.createObjectStore(store);
            }
        };
        
        open.onsuccess = function(e) {
            const db = e.target.result;
            const tx = db.transaction(store, 'readonly');
            const req = tx.objectStore(store).get(key);
            req.onsuccess = () => resolve(req.result);
            req.onerror = () => reject(req.error);
        };
        
        open.onerror = () => reject(open.error);
    });
}

function idbSet(store, key, value) {
    return new Promise((resolve, reject) => {
        const open = indexedDB.open('sw-cache-db', 1);
        
        open.onsuccess = function(e) {
            const db = e.target.result;
            const tx = db.transaction(store, 'readwrite');
            tx.objectStore(store).put(value, key);
            tx.oncomplete = () => resolve();
            tx.onerror = () => reject(tx.error);
        };
        
        open.onerror = () => reject(open.error);
    });
}


页面注册:简单接入

html页面中添加注册代码:

<script>
if ('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
        navigator.serviceWorker.register('/sw.js')
            .then(function(registration) {
                console.log('Service Worker注册成功');
            })
            .catch(function(error) {
                console.log('Service Worker注册失败:', error);
            });
    });
}
</script>


实际效果

  • 接口平均响应时间:从800ms降低到80ms

  • 后端请求压力:高峰期减少65%

  • 用户刷新体验:接近本地应用速度

  • 代码改动:业务代码零侵入,只增加sw.js文件


使用原则和最佳实践

1. 选择合适的缓存接口

  • 高频访问的接口

  • 数据时效性要求不高的接口

  • 静态配置、公告信息等

2. 设置合理的缓存策略

// 不同接口设置不同的缓存时间
const cacheConfig = {
    'stock/data': 60 * 1000,      // 1分钟
    'news/list': 5 * 60 * 1000,   // 5分钟
    'config/static': 24 * 60 * 60 * 1000 // 24小时
};

3. 控制缓存容量

定期清理过期缓存,防止存储空间无限增长:

// 定期清理过期缓存
function cleanExpiredCache() {
    // 清理逻辑
}

4. 监控缓存效果

使用Chrome开发者工具监控:

  • Application → Cache Storage

  • Application → IndexedDB

  • Network面板查看请求命中情况


缓存更新策略

1. 版本控制

通过修改Service Worker版本号触发更新:

const CACHE_VERSION = 'v1.2.0';
const CACHE_NAME = `app-cache-${CACHE_VERSION}`;

2. 增量更新

只更新变化的资源,减少数据传输:

// 检查资源是否需要更新
function needsUpdate(cachedResponse, networkResponse) {
    // 比较ETag或Last-Modified
    return cachedResponse.headers.get('etag') !== 
           networkResponse.headers.get('etag');
}


错误处理和降级方案

1. 网络异常处理

function handleCache(event, cacheName, maxAge) {
    event.respondWith(
        caches.match(event.request).then(cached => {
            // ...缓存逻辑
        }).catch(error => {
            // 降级到网络请求
            return fetch(event.request);
        })
    );
}

2. 缓存失败处理

async function handlePostCache(event, storeName) {
    try {
        // 缓存处理逻辑
    } catch (error) {
        // 直接返回网络请求
        return fetch(event.request);
    }
}


总结

面对高并发场景,前端不再只能被动等待后端扩容。使用Service Worker和IndexedDB的组合方案,可以主动分担系统压力,提升用户体验。

这次实战经验告诉我们:前端完全可以成为系统稳定性的重要保障。

如果你的项目面临以下挑战:

  • 高频接口调用

  • 弱网络环境

  • 用户体验瓶颈

建议尝试这个技术组合:Service Worker + Cache Storage + IndexedDB。

这个方案经过实际验证,在保证数据准确性的同时,显著提升了系统性能和用户体验。

本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!

链接: https://fly63.com/article/detial/13054

数据库高并发解决方法总结

一个项目刚开始的时候是为了实现基本功能,随着版本和功能的迭代,大数据和高并发成了软件设计必须考虑的问题!本质很简单,一个是慢,一个是等。解决核心 一个是短,一个是少,一个是分流,最后一个是集群/横向扩张/读写分离/建立主从

高并发大流量网站 10 个解决方法

高并发大流量网站 10 个解决方法:硬件升级、负载均衡、服务器集群、数据库读写分离、数据库分表技术(垂直分割,水平分割)、表建立相应的索引、页面静态化、缓存技术(MemCache、Redis)、禁止外部盗链、控制大文件的下载

PHP如何解决网站大流量和高并发

并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行,通常我们所定义的高并发并非上述解释,简单的来说就是在某个时间点、有多少个访问同时到来。

当我们在说并发、多线程,说的是什么?

在这篇文章中,你将了解到并发与多线程相关的一系列概念,通过一些例子我们可以在不纠结于具体的技术细节的情况下形成对并发与多线程相关的各种概念的抽象理解。有了这些概念以后,我们再去学习具体的理论和技术细节就是手到擒来的事了。

高并发下如何缩短响应时间?

网站响应时间是指系统对请求作出响应的时间。通俗来讲就是我们把网址输入进浏览器然后敲回车键开始一直到浏览器把网站的内容呈现给用户的这段时间。网站响应时间是越短越好,因为网站页面打开速度越快

nodejs支持高并发吗?

nodejs是支持高并发的。那么为什么单线程的nodejs可以高并发?下面本篇文章就来给大家介绍一下,希望对大家有所帮助。nodejs支持高并发的原因:nodejs是非阻塞异步操作。针对每个并发请求,服务端给请求注册一个激发事件(I/O),并给一个回调函数(这个过程没有阻塞新的连接请求)。

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!