使用Redis和Java探索数据库缓存,并查看一些不同的缓存策略。最近在优锐课学习收获颇多,记录下来大家一起进步!
为什么数据库缓存如此重要?
你在数据库中拥有的信息越多,随着时间的推移它将变得越慢。 即使是经过精心设计以支持许多并发请求的数据库管理系统,最终也会达到其极限。
数据库缓存是处理这些性能问题的最常见策略之一。缓存涉及将数据库查询的结果保存在更快,更易于访问的位置。正确完成缓存后,缓存将减少查询响应时间,减少数据库负载并降低成本。
但是,缓存也需要谨慎处理,因为它们实际上是在单独的位置中复制了你的另一份信息。保持数据库和缓存同步并保持最新状态可能是比你预期的棘手的挑战。在下一节中,我们将讨论一些最常见的数据库缓存策略。
有哪些不同的缓存策略?
手动缓存(也称为备用缓存策略)涉及对数据库和缓存的直接管理。你的应用程序在启动数据库查询之前检查缓存,并且在对数据库进行任何更改后都会更新缓存。
如果正确实施,手动缓存虽然有效,但可能非常繁琐,尤其是在需要查询多个数据库的情况下。由于这些原因,开发人员发明了许多替代的缓存策略。
Read-Through缓存策略
在Read-Through中,应用程序首先查询缓存以查看其所需的信息是否在内部。如果不是,它将从数据库中检索信息并使用它来更新缓存。缓存提供程序或缓存库负责查询和更新缓存的详细逻辑。
当应用程序反复请求相同的数据时,Read-Through策略最适合于繁重的工作负载:例如,一个新闻网站一遍又一遍地加载相同的文章。
Read-Through策略的一个缺点是,对高速缓存的第一个查询将始终导致未命中,因为保证了所请求的信息不会在内部。为了解决此问题,开发人员通常会提前用用户可能会请求的信息“预热”缓存。
Write-Through 缓存策略
在Write-Through缓存中,首先对缓存进行更新,然后对数据库进行更新。从应用程序到缓存以及从缓存到数据库都有一条直线。 与Read-Through缓存结合使用时,Write-Through策略可确保你的数据保持一致,从而无需手动进行高速缓存失效。
Write-Behind缓存策略
在Write-Behind缓存(也称为Write-Back缓存)中,应用程序首先将数据写入高速缓存。 经过一段时间的延迟后,高速缓存还将这些信息写入数据库。Write-Behind缓存最适合于繁重的写工作负载,即使出现一些故障和停机也可以表现良好。
使用Redisson的基于Java的Redis缓存
Redis是NoSQL数据库中最受欢迎的选项之一,它使用键值系统存储数据。Redisson是Java编程语言中Redis的客户端库,可使用所有熟悉的Java集合轻松访问Redis功能。
Redisson允许你将数据放置在外部存储的“地图”中。你可以使用此功能为数据库,Web服务或任何其他数据源实现缓存。
Redis中的Read-Through缓存
下面是一个Java示例,说明如何在Redis和Redisson中使用Read-Through缓存。
如果请求的条目在缓存中不存在,它将由MapLoader对象加载:
1 MapLoader<String, String> mapLoader = new MapLoader<String, String>() { 2 @Override 3 public Iterable<String> loadAllKeys() { 4 List<String> list = new ArrayList<String>(); 5 Statement statement = conn.createStatement(); 6 try { 7 ResultSet result = statement.executeQuery("SELECT id FROM student"); 8 while (result.next()) { 9 list.add(result.getString(1)); 10 } 11 } finally { 12 statement.close(); 13 } 14 return list; 15 } 16 @Override 17 public String load(String key) { 18 PreparedStatement preparedStatement = conn.prepareStatement("SELECT name FROM student where id = ?"); 19 try { 20 preparedStatement.setString(1, key); 21 ResultSet result = preparedStatement.executeQuery(); 22 if (result.next()) { 23 return result.getString(1); 24 } 25 return null; 26 } finally { 27 preparedStatement.close(); 28 } 29 } 30 };
Configuration example:
1 MapOptions<K, V> options = MapOptions.<K, V>defaults() 2 .loader(mapLoader); 3 RMap<K, V> map = redisson.getMap("test", options); 4 // or 5 RMapCache<K, V> map = redisson.getMapCache("test", options); 6 // or with boost up to 45x times 7 RLocalCachedMap<K, V> map = redisson.getLocalCachedMap("test", options); 8 // or with boost up to 45x times 9 RLocalCachedMapCache<K, V> map = redisson.getLocalCachedMapCache("test", options);
Redis 中的Write-Through缓存
下面是一个Java示例,说明如何在Redis和Redisson中的Redis中使用Write-Through缓存。
直到MapWriter对象更新了缓存和数据库后,缓存更新方法才会返回:
1 MapWriter<String, String> mapWriter = new MapWriter<String, String>() { 2 @Override 3 public void write(Map<String, String> map) { 4 PreparedStatement preparedStatement = conn.prepareStatement("INSERT INTO student (id, name) values (?, ?)"); 5 try { 6 for (Entry<String, String> entry : map.entrySet()) { 7 preparedStatement.setString(1, entry.getKey()); 8 preparedStatement.setString(2, entry.getValue()); 9 preparedStatement.addBatch(); 10 } 11 preparedStatement.executeBatch(); 12 } finally { 13 preparedStatement.close(); 14 } 15 } 16 @Override 17 public void delete(Collection<String> keys) { 18 PreparedStatement preparedStatement = conn.prepareStatement("DELETE FROM student where id = ?"); 19 try { 20 for (String key : keys) { 21 preparedStatement.setString(1, key); 22 preparedStatement.addBatch(); 23 } 24 preparedStatement.executeBatch(); 25 } finally { 26 preparedStatement.close(); 27 } 28 } 29 };
Configuration example:
1 MapOptions<K, V> options = MapOptions.<K, V>defaults() 2 .writer(mapWriter) 3 .writeMode(WriteMode.WRITE_THROUGH); 4 RMap<K, V> map = redisson.getMap("test", options); 5 // or 6 RMapCache<K, V> map = redisson.getMapCache("test", options); 7 // or with boost up to 45x times 8 RLocalCachedMap<K, V> map = redisson.getLocalCachedMap("test", options); 9 // or with boost up to 45x times 10 RLocalCachedMapCache<K, V> map = redisson.getLocalCachedMapCache("test", options);
Redis 中的Write-Behind缓存
MapWriter接口还用于将更新异步提交到Map对象(缓存)和外部存储(数据库)。所有地图更新都分批累积,并以定义的延迟异步写入。
writeBehindDelay —批处理写入或删除操作的延迟。默认值为1000毫秒。
writeBehindBatchSize —批处理的大小。每批包含“映射条目”写入或删除命令。默认值为50。
下面,我们看到一个Redisson中基于Redis的Write-Behind缓存实现的Java配置示例:
1 MapOptions<K, V> options = MapOptions.<K, V>defaults() 2 .writer(mapWriter) 3 .writeMode(WriteMode.WRITE_BEHIND) 4 .writeBehindDelay(5000) 5 .writeBehindBatchSize(100); 6 RMap<K, V> map = redisson.getMap("test", options); 7 // or 8 RMapCache<K, V> map = redisson.getMapCache("test", options); 9 // or with boost up to 45x times 10 RLocalCachedMap<K, V> map = redisson.getLocalCachedMap("test", options); 11 // or with boost up to 45x times 12 RLocalCachedMapCache<K, V> map = redisson.getLocalCachedMapCache("test", options);
所有讨论的策略都可用于Redisson中的RMap,RMapCache,RLocalCachedMap和RLocalCachedMapCache对象。使用后两个对象可以使Redis中的读取操作快45倍。
感谢阅读!