51mee - AI智能招聘平台Logo
模拟面试题目大全招聘中心会员专区

设计一个移动端离线缓存系统,用于万兴喵库等数据备份工具。系统需要支持:1)缓存策略(如LRU、LFU);2)数据更新机制(本地修改后如何同步到服务器);3)网络恢复后的数据同步;4)缓存数据的安全存储(如加密);5)缓存淘汰策略(如按时间或大小淘汰)。请描述系统架构、核心模块以及关键设计点。

万兴科技移动开发难度:中等

答案

1) 【一句话结论】
针对万兴喵库数据备份工具的离线缓存需求,设计分层架构(存储层+缓存层+同步层),采用LRU/LFU动态缓存策略,结合乐观锁与增量同步机制,支持数据完整性校验(MD5)和版本回滚,确保离线操作的一致性、安全性及高效性,并处理极端场景(如网络断开、密钥泄露)。

2) 【原理/概念讲解】
老师口吻解释系统核心模块:

  • 存储层:持久化存储(SQLite),用于长期保存数据,支持版本控制(记录数据版本号、修改时间),并存储数据完整性校验值(MD5),确保备份文件完整性。
  • 缓存层:内存缓存(LRU/LFU),用于快速访问高频数据,LRU适合图片(高频访问,访问模式稳定),LFU适合文档(访问频率差异大,需保留冷门但可能被访问的内容)。
  • 同步层:网络同步模块,处理本地与服务器数据同步,包含本地修改标记(待同步表)、网络恢复处理(断点续传)。
  • 数据更新机制:本地修改时,标记为“待同步”并附带数据哈希(MD5)和版本号,网络恢复时通过增量同步(比较哈希和版本号,仅传变化数据),避免全量传输。
  • 安全存储:数据加密(AES-256),密钥存储在设备安全区域(Keychain/Keystore),定期轮换(每30天),防止数据泄露。
  • 淘汰策略:按时间(TTL,7天未访问则淘汰)、按大小(容量限制100MB,超过则淘汰)、按访问频率(LRU/LFU),结合文件类型动态调整(大文件优先按时间/大小淘汰,小文件按LRU)。
  • 并发控制:乐观锁(版本号机制),本地修改时检查服务器版本,若冲突则回滚本地数据,并提示用户选择保留服务器或本地版本。
  • 网络恢复同步:断点续传,记录同步进度(已同步条目索引),失败后指数退避重试(1s→2s→4s),保证同步可靠性。

3) 【对比与适用场景】

策略定义特性适用场景(万兴喵库)注意点
LRU根据访问时间排序,淘汰最久未访问的实现简单(O(1)时间复杂度),适合高频访问数据图片、高频小文档(如常用模板),访问模式稳定可能误淘汰近期高频但未访问的数据(如用户刚编辑的图片,但未立即访问)
LFU根据访问频率排序,淘汰访问次数最少的需记录访问次数(O(1)更新,O(n)查询),适合访问频率差异大的数据文档、资料(如热门教程/冷门资料),需保留冷门但可能被访问的内容计数器可能溢出(需定期重置),实现复杂度较高

4) 【示例】
伪代码展示核心逻辑(含乐观锁、版本控制、断点续传、数据校验):

// 存储层:SQLite表结构
// 表1: data_store (key, data_md5, data_version, data, last_access, file_type)
// 表2: pending_sync (key, operation_type, data_md5, data_version, sync_status)

class OfflineCache {
    private Map<String, CacheEntry> cacheMap;
    private SQLiteDatabase db;
    private SyncManager syncManager;
    private static final int MAX_CACHE_SIZE = 100;
    private static final int MAX_PENDING_SIZE = 1000;

    // 添加/更新缓存(带版本控制与MD5校验)
    void put(String key, byte[] data, long serverVersion) {
        String md5 = MD5Util.md5(data);
        CacheEntry entry = new CacheEntry(data, md5, serverVersion, System.currentTimeMillis(), "image");
        cacheMap.put(key, entry);
        db.save(key, md5, serverVersion, data, System.currentTimeMillis(), "image");
        evictIfNeeded();
    }

    // 获取缓存(乐观锁检查与数据校验)
    Object get(String key) {
        CacheEntry entry = cacheMap.get(key);
        if (entry != null && !isExpired(entry)) {
            entry.lastAccess = System.currentTimeMillis();
            return entry.data;
        }
        byte[] data = db.load(key);
        if (data != null) {
            String dbMd5 = db.getMd5(key);
            if (!dbMd5.equals(entry.md5)) { // 数据校验失败,从服务器重新下载
                data = downloadFromServer(key);
                db.update(key, MD5Util.md5(data), serverVersion, data);
            }
            CacheEntry newEntry = new CacheEntry(data, db.getVersion(key), System.currentTimeMillis(), "image");
            cacheMap.put(key, newEntry);
        }
        return data;
    }

    // 本地修改(标记待同步)
    void updateLocal(String key, byte[] newData) {
        String newMd5 = MD5Util.md5(newData);
        CacheEntry entry = cacheMap.get(key);
        if (entry != null) {
            entry.data = newData;
            entry.md5 = newMd5;
            cacheMap.put(key, entry);
            db.markPending(key, "update", newMd5, db.getVersion(key));
        }
    }

    // 淘汰策略(LRU)
    void evictIfNeeded() {
        if (cacheMap.size() > MAX_CACHE_SIZE) {
            String keyToRemove = findLeastRecentlyUsedKey();
            cacheMap.remove(keyToRemove);
            db.delete(keyToRemove);
        }
    }

    // 网络恢复后增量同步(断点续传)
    void onNetworkRecovery() {
        List<String> pending = db.getPendingChanges();
        if (!pending.isEmpty()) {
            syncManager.performIncrementalSync(pending, 0);
        }
    }

    // 同步处理(断点续传与冲突检测)
    void syncIncremental(List<String> keys, int offset) {
        for (int i = offset; i < keys.size(); i++) {
            String key = keys.get(i);
            PendingEntry pending = db.getPending(key);
            if (pending != null) {
                if (pending.operationType.equals("update")) {
                    byte[] serverData = downloadFromServer(key);
                    String serverMd5 = MD5Util.md5(serverData);
                    if (serverMd5.equals(pending.dataMd5)) {
                        db.update(key, serverMd5, pending.dataVersion, serverData);
                        cacheMap.put(key, new CacheEntry(serverData, serverMd5, pending.dataVersion, System.currentTimeMillis(), "image"));
                    } else {
                        // 冲突:服务器数据变化,回滚本地修改
                        byte[] localData = db.load(key);
                        db.markConflict(key, localData, serverMd5);
                        // 通知用户选择保留哪个版本
                        showConflictDialog(key, localData, serverData);
                    }
                } else if (pending.operationType.equals("delete")) {
                    db.delete(key);
                    cacheMap.remove(key);
                }
            }
        }
    }

    // 数据加密(示例)
    private byte[] encryptData(byte[] data) {
        return AES.encrypt(data, keyStore.getKey());
    }
    private byte[] decryptData(byte[] encrypted) {
        return AES.decrypt(encrypted, keyStore.getKey());
    }
}

class CacheEntry {
    byte[] data;
    String md5;
    long serverVersion;
    long lastAccess;
    String fileType;
}

5) 【面试口播版答案】
“面试官您好,针对万兴喵库的数据备份工具,我设计了一个离线缓存系统,核心是分层架构:存储层(SQLite持久化,带版本与MD5校验)、缓存层(LRU/LFU动态策略,图片用LRU,文档用LFU)、同步层(增量同步与断点续传)。数据更新时,本地修改标记为待同步并附带哈希与版本号,网络恢复后通过增量同步(比较哈希和版本号,仅传变化数据)。安全存储用AES-256加密,密钥存设备安全区域,定期轮换。淘汰策略按时间(7天未访问)、按大小(100MB容量)、按访问频率,结合文件类型。并发控制用乐观锁(版本号),冲突时回滚本地数据并提示用户。网络恢复时断点续传,记录进度,失败后指数退避重试。这样确保离线操作的一致性、安全性和高效性,满足万兴喵库的备份需求。”

6) 【追问清单】

  • 问:如何处理并发用户同时修改同一文件?
    答:使用乐观锁(版本号机制),本地修改时检查服务器版本,若冲突则回滚本地数据,并提示用户选择保留服务器或本地版本。
  • 问:网络完全不可用时,本地如何操作?
    答:本地支持离线编辑(标记待同步),数据加密存储,网络恢复后增量同步,确保数据不丢失。
  • 问:密钥泄露后如何恢复?
    答:密钥存储在设备安全区域,若泄露可生成新密钥,解密旧数据(需用户确认),并更新密钥。
  • 问:缓存淘汰时如何避免误淘汰重要数据?
    答:结合文件类型,大文件(如视频)按时间/大小淘汰,小文件按LRU,并设置TTL(7天),避免频繁淘汰。
  • 问:同步冲突时如何回滚?
    答:通过版本号比较,本地版本旧则更新为服务器数据,服务器版本旧则保留本地数据并标记冲突,通知用户手动选择。

7) 【常见坑/雷区】

  • 坑1:忽略数据完整性校验,导致备份文件损坏。
    答:需在存储层添加MD5校验,同步时重新校验,避免使用损坏数据。
  • 坑2:网络恢复时全量同步,导致流量过大。
    答:采用增量同步(比较哈希和版本号),仅传变化数据,减少流量。
  • 坑3:未考虑并发冲突回滚,导致数据不一致。
    答:使用乐观锁,冲突时回滚本地修改,并通知用户,避免数据冲突。
  • 坑4:加密密钥存储在明文,导致数据泄露。
    答:密钥存储在设备安全区域(Keychain/Keystore),定期轮换,防止泄露。
  • 坑5:缓存淘汰策略过于复杂,影响性能。
    答:按文件类型动态调整淘汰策略,大文件用TTL,小文件用LRU,避免频繁淘汰。
51mee.com致力于为招聘者提供最新、最全的招聘信息。AI智能解析岗位要求,聚合全网优质机会。
产品招聘中心面经会员专区简历解析Resume API
联系我们南京浅度求索科技有限公司admin@51mee.com
联系客服
51mee客服微信二维码 - 扫码添加客服获取帮助
© 2025 南京浅度求索科技有限公司. All rights reserved.
公安备案图标苏公网安备32010602012192号苏ICP备2025178433号-1