数据库缓存是处理这些性能问题的最常见策略之一。缓存涉及将数据库查询的结果保存在更快,更容易访问的位置。正确完成后,缓存将减少查询响应时间,减少数据库负载并降低成本。
但是,缓存也需要小心处理,因为它们实际上是在另一个位置创建另一个信息副本。保持数据库和缓存同步并保持最新可能比您预期的更棘手。在下一节中,我们将讨论一些最常见的数据库缓存策略。
什么是不同的缓存策略?
手动缓存(也称为缓存搁置策略)涉及直接管理数据库和缓存。您的应用程序在启动数据库查询之前检查缓存,并在对数据库进行任何更改后更新缓存。
虽然如果正确实现有效,但手动缓存可能非常繁琐,尤其是在您需要查询多个数据库时。出于这些原因,开发人员发明了许多替代性的缓存策略。
直读缓存策略
在读取缓存中,应用程序首先查询缓存以查看其所需的信息是否在内部。如果没有,它将从数据库中检索信息并使用它来更新缓存。缓存提供程序或缓存库负责查询和更新缓存的详细逻辑。
当应用程序重复请求相同的数据时,读取策略最适合读取繁重的工作负载:例如,一遍又一遍地加载相同文章的新闻网站。
读取策略的一个缺点是对缓存的第一次查询将始终导致未命中,因为保证所请求的信息不在内部。为了解决这个问题,开发人员通常会使用用户可能要求的信息提前“加热”缓存。
直写缓存策略
在直写式高速缓存中,首先对高速缓存进行更新,然后对数据库进行更新。从应用程序到缓存以及从缓存到数据库都有一条直接线。与直读式缓存结合使用时,直写式策略可确保您的数据保持一致,从而无需手动缓存失效。
后写缓存策略
在后写式缓存(也称为回写式高速缓存)中,应用程序首先将数据写入高速缓存。经过一段设定的延迟后,缓存也会将此信息写入数据库。后写缓存最适合写入繁重的工作负载,即使出现一些故障和停机也可以很好地执行。
基于Java的Redis缓存与Redisson
Redis 是NoSQL数据库最受欢迎的选项之一,它使用键值系统来存储数据。Redisson是Java编程语言中的Redis客户端库,可以使用所有熟悉的Java集合轻松访问Redis功能。
Redisson 允许您将数据放在外部存储中的地图中。您可以使用此功能实现数据库,Web服务或任何其他数据源的缓存。
Redis中的直读缓存
下面是一个Java示例,说明如何在Redis和Redisson中使用直读缓存。
如果请求的条目在缓存中不存在,则它将由 MapLoader 对象加载:
MapLoader<String, String> mapLoader = <b>new</b> MapLoader<String, String>() { @Override <b>public</b> Iterable<String> loadAllKeys() { List<String> list = <b>new</b> ArrayList<String>(); Statement statement = conn.createStatement(); <b>try</b> { ResultSet result = statement.executeQuery(<font>"SELECT id FROM student"</font><font>); <b>while</b> (result.next()) { list.add(result.getString(1)); } } <b>finally</b> { statement.close(); } <b>return</b> list; } @Override <b>public</b> String load(String key) { PreparedStatement preparedStatement = conn.prepareStatement(</font><font>"SELECT name FROM student where id = ?"</font><font>); <b>try</b> { preparedStatement.setString(1, key); ResultSet result = preparedStatement.executeQuery(); <b>if</b> (result.next()) { <b>return</b> result.getString(1); } <b>return</b> <b>null</b>; } <b>finally</b> { preparedStatement.close(); } } }; </font>
配置使用案例:
MapOptions<K, V> options = MapOptions.<K, V>defaults() .loader(mapLoader); RMap<K, V> map = redisson.getMap(<font>"test"</font><font>, options); </font><font><i>// or</i></font><font> RMapCache<K, V> map = redisson.getMapCache(</font><font>"test"</font><font>, options); </font><font><i>// or with boost up to 45x times </i></font><font> RLocalCachedMap<K, V> map = redisson.getLocalCachedMap(</font><font>"test"</font><font>, options); </font><font><i>// or with boost up to 45x times </i></font><font> RLocalCachedMapCache<K, V> map = redisson.getLocalCachedMapCache(</font><font>"test"</font><font>, options); </font>
Redis中的直写缓存
下面是一个Java示例,说明如何在Redis中使用Redis中的Redis使用直写缓存。
在 MapWriter 对象更新缓存和数据库之前,缓存更新方法不会返回:
MapWriter<String, String> mapWriter = <b>new</b> MapWriter<String, String>() { @Override <b>public</b> <b>void</b> writeAll(Map<String, String> map) { PreparedStatement preparedStatement = conn.prepareStatement(<font>"INSERT INTO student (id, name) values (?, ?)"</font><font>); <b>try</b> { <b>for</b> (Entry<String, String> entry : map.entrySet()) { preparedStatement.setString(1, entry.getKey()); preparedStatement.setString(2, entry.getValue()); preparedStatement.addBatch(); } preparedStatement.executeBatch(); } <b>finally</b> { preparedStatement.close(); } } @Override <b>public</b> <b>void</b> write(String key, String value) { PreparedStatement preparedStatement = conn.prepareStatement(</font><font>"INSERT INTO student (id, name) values (?, ?)"</font><font>); <b>try</b> { preparedStatement.setString(1, key); preparedStatement.setString(2, value); preparedStatement.executeUpdate(); } <b>finally</b> { preparedStatement.close(); } } @Override <b>public</b> <b>void</b> deleteAll(Collection<String> keys) { PreparedStatement preparedStatement = conn.prepareStatement(</font><font>"DELETE FROM student where id = ?"</font><font>); <b>try</b> { <b>for</b> (String key : keys) { preparedStatement.setString(1, key); preparedStatement.addBatch(); } preparedStatement.executeBatch(); } <b>finally</b> { preparedStatement.close(); } } @Override <b>public</b> <b>void</b> delete(String key) { PreparedStatement preparedStatement = conn.prepareStatement(</font><font>"DELETE FROM student where id = ?"</font><font>); <b>try</b> { preparedStatement.setString(1, key); preparedStatement.executeUpdate(); } <b>finally</b> { preparedStatement.close(); } } }; </font>
使用配置案例:
MapOptions<K, V> options = MapOptions.<K, V>defaults() .writer(mapWriter) .writeMode(WriteMode.WRITE_THROUGH); RMap<K, V> map = redisson.getMap(<font>"test"</font><font>, options); </font><font><i>// or</i></font><font> RMapCache<K, V> map = redisson.getMapCache(</font><font>"test"</font><font>, options); </font><font><i>// or with boost up to 45x times </i></font><font> RLocalCachedMap<K, V> map = redisson.getLocalCachedMap(</font><font>"test"</font><font>, options); </font><font><i>// or with boost up to 45x times </i></font><font> RLocalCachedMapCache<K, V> map = redisson.getLocalCachedMapCache(</font><font>"test"</font><font>, options); </font>
Redis中的后写缓存
MapWriter接口还用于异步提交对Map对象(缓存)和外部存储(数据库)的更新。后台写入操作执行中使用的线程数量通过 writeBehindThreads设置设置。
下面,我们看到Redisson中基于Redis的后写缓存实现的配置的Java示例:
MapOptions<K, V> options = MapOptions.<K, V>defaults() .writer(mapWriter) .writeMode(WriteMode.WRITE_BEHIND) .writeBehindThreads(8); RMap<K, V> map = redisson.getMap(<font>"test"</font><font>, options); </font><font><i>// or</i></font><font> RMapCache<K, V> map = redisson.getMapCache(</font><font>"test"</font><font>, options); </font><font><i>// or with boost up to 45x times </i></font><font> RLocalCachedMap<K, V> map = redisson.getLocalCachedMap(</font><font>"test"</font><font>, options); </font><font><i>// or with boost up to 45x times </i></font><font> RLocalCachedMapCache<K, V> map = redisson.getLocalCachedMapCache(</font><font>"test"</font><font>, options); </font>
上述所有讨论的策略可用于 Redisson 的RMap,RMapCache,RLocalCachedMap和RLocalCachedMapCache对象。使用后两个对象可以使Redis中的读取操作速度提高45倍。