面试题答案
一键面试策略
- 读写锁策略:
- 在读取数据时,多个线程可以同时获取读锁进行读取操作。当有线程要进行数据更新(写操作)时,先获取写锁,此时其他读、写操作都被阻塞,直到写操作完成并释放写锁。这样可以保证在数据更新时,缓存和数据库的数据一致性,避免读脏数据的情况。
- 缓存更新策略:
- 先更新数据库,再更新缓存:应用先将数据更新到数据库,成功后再更新缓存中的数据。这样在数据更新成功后,缓存中的数据也保持一致。但这种方式在并发场景下,如果先更新数据库成功,在更新缓存前,另一个读请求过来,会读到旧的缓存数据,有短暂的数据不一致窗口。
- 先删除缓存,再更新数据库:应用先删除缓存中的数据,然后更新数据库。后续读请求发现缓存中没有数据,会从数据库读取最新数据并更新到缓存,从而保证数据一致性。不过,如果在删除缓存后,更新数据库前,有读请求过来,会读取到旧数据并更新到缓存,同样存在短暂的数据不一致窗口。另外,这种方式如果在高并发场景下频繁删除和更新缓存,可能会导致缓存雪崩等问题。
- 先更新数据库,再删除缓存:这是比较常用的方式。应用先更新数据库,成功后删除缓存。后续读请求发现缓存不存在数据,会从数据库获取最新数据并重新填充缓存。相比先删除缓存再更新数据库,它减少了读取旧数据并更新到缓存的风险,因为数据库已经是最新的。但在高并发场景下,如果更新数据库和删除缓存之间有短暂延迟,还是可能存在短暂的数据不一致情况。
- 事务策略:
- 将缓存更新和数据库更新放在同一个事务中。如果事务中的所有操作(包括缓存和数据库更新)都成功,则提交事务;如果有任何一个操作失败,则回滚事务,保证缓存和数据库的数据一致性。不过,实现这种方式需要缓存系统支持事务操作,不是所有缓存系统都能满足。
实现思路
- 基于读写锁策略实现:
- 在代码层面,可以使用编程语言提供的读写锁机制。例如在Java中,可以使用
ReentrantReadWriteLock
。在应用的读取方法中,获取读锁,示例代码如下:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); ReadLock readLock = lock.readLock(); readLock.lock(); try { // 读取数据逻辑 } finally { readLock.unlock(); }
- 在更新方法中,获取写锁,示例代码如下:
WriteLock writeLock = lock.writeLock(); writeLock.lock(); try { // 更新数据库和缓存逻辑 } finally { writeLock.unlock(); }
- 在代码层面,可以使用编程语言提供的读写锁机制。例如在Java中,可以使用
- 基于缓存更新策略实现:
- 先更新数据库,再更新缓存:
- 在应用代码中,使用数据库操作库(如JDBC连接MySQL数据库)更新数据库记录。例如在Java中:
Connection conn = DriverManager.getConnection(url, username, password); PreparedStatement pstmt = conn.prepareStatement("UPDATE your_table SET column1 =? WHERE id =?"); pstmt.setString(1, newValue); pstmt.setInt(2, id); pstmt.executeUpdate();
- 然后使用缓存操作库(如Jedis操作Redis缓存)更新缓存数据,示例代码如下:
Jedis jedis = new Jedis("localhost", 6379); jedis.set(key, newValue);
- 先删除缓存,再更新数据库:
- 首先使用缓存操作库删除缓存数据,如:
Jedis jedis = new Jedis("localhost", 6379); jedis.del(key);
- 然后使用数据库操作库更新数据库记录,同上述更新数据库代码。
- 先更新数据库,再删除缓存:
- 先使用数据库操作库更新数据库记录,代码同上。
- 再使用缓存操作库删除缓存数据,代码为:
Jedis jedis = new Jedis("localhost", 6379); jedis.del(key);
- 先更新数据库,再更新缓存:
- 基于事务策略实现:
- 如果使用支持事务的缓存系统(如Redis的事务功能),结合数据库事务操作。例如在Java中使用Spring框架,配置事务管理器,将数据库操作和缓存操作放在同一个事务方法中。
上述代码只是示例,实际应用中需要根据具体的缓存系统、数据库系统和编程语言进行调整和完善。同时,在容器化场景下,要注意容器之间的通信、缓存和数据库的连接管理等问题,以确保整个系统的稳定性和数据一致性。@Transactional public void updateData(String key, String newValue) { // 使用JdbcTemplate更新数据库 jdbcTemplate.update("UPDATE your_table SET column1 =? WHERE id =?", newValue, id); // 使用RedisTemplate更新缓存(假设Redis支持事务) redisTemplate.execute((RedisCallback<Object>) connection -> { connection.multi(); connection.set(key.getBytes(), newValue.getBytes()); return connection.exec(); }); }