sqlite wordpress,新手怎么做seo,国外域名。国内网站,旅游信息网站开发缓存基本概念
概念
对于缓存#xff0c;最普遍的理解是能让打开某些页面速度更快的工具。从技术角度来看#xff0c;其本质上是因为缓存是基于内存建立的#xff0c;而内存的读写速度相比之于硬盘快了xx倍#xff0c;因此用内存来代替硬盘作为读写的介质当然能大大提高访…缓存基本概念
概念
对于缓存最普遍的理解是能让打开某些页面速度更快的工具。从技术角度来看其本质上是因为缓存是基于内存建立的而内存的读写速度相比之于硬盘快了xx倍因此用内存来代替硬盘作为读写的介质当然能大大提高访问数据的速度。
应用读取数据时首先会从缓存中查询数据如果有则执行没有则需要从数据库中查找而数据库的读写操作比缓存的数据慢得多因此通过缓存把访问量较高的热点数据从传统的关系型数据库中加载到内存中对同样的数据进行二次访问时就相当于从内存中加载数据减少了对数据库的访问量解决了高并发场景下容易造成数据库宕机的问题
缓存优点 降低冗余的数据传输访问处于高并发场景下的一个原始服务期页面时服务器会传输多次同一份文档这使得每次传输过程中一些相同的字节会在网络中重复多次传输这会消耗昂贵的网络带宽降低传输速度家中web服务器负载。缓存可以保留第一次服务器响应的副本后继请求就可以用副本来应对减少了服务器的流入流出的重复流量降低带宽瓶颈缓存缓解了网络瓶颈的问题不需要更多的带宽就能够更快地加载页面缓存还可以缓解网络的瓶颈问题,很多网络为本地网络客户端提供的带宽比为远程服务器提供的带宽要宽客户端会以路径上最慢的网速访问服务器如果客户端从一个快速局域网的缓存中得到了一份副本那么缓存就可以提高性能尤其是要传输比较大的文件时降低瞬间堵塞很多人同时访问同一个Web文档是就会出现瞬间堵塞由此造成的过多流量峰值可能会使网络和Web服务器产生灾难性的崩溃而缓存降低了对原始服务器的要求服务器可以更快地响应避免了过载降低距离时延从较远的地方加载页面会慢一点除了带宽距离本身使得所要传输的数据经过的每台路由器都会增加因特网流量的时延甚至光速也会造成时延缓存则可以减少数据从服务器流入流出的流量
分类
缓存基本上分为三类本地缓存、分布式缓存、多级缓存
本地缓存
本地缓存指和应用程序在同一个进程内的内存空间取存储数据数据的读写都是在同一个进程内完成的
优点读取速度快但是不能进行大数据量存储本地缓存不需要远程网络请求取操作内存空间没有额外的性能消耗因此读取速度快但是由于本地缓存占用了应用进程的内存空间故不能进行大数据量的存储
缺点应用程序集群部署时会存在数据更新问题数据更新不一致数据会随着应用程序的重启而丢失
本地缓存一般只能被同一个应用进程的程序访问不能被其他应用程序进程访问。
在单体应用集群部署时如果数据库有数据需要更新就要同步更新不同服务器节点上的本地缓存的数据来保证数据的一致性但是这种操作的复杂度较高容易出错。
本地缓存的数据是存储在应用进程的内存空间的因此当应用进程重启时本地缓存的数据会丢失
分布式缓存
分布式缓存分布式缓存是独立部署的服务进程并且和应用程序没有部署在同一台服务器上所以是需要通过远程网络请求来完成分布式缓存的读写操作并且分布式缓存主要应用在应用程序集群部署的环境下。
优点支持大数据量存储数据不会随着应用程序重启而丢失数据集中存储保证数据的一致性数据读写分离高性能高可用
分布式缓存是独立部署的进程拥有自身独自的内存空间不需要占用应用程序进程的内存空间并且还支持横向扩展的集群方式部署因此可以进行大数据量存储。
分布式缓存和本地缓存不同拥有自身独立的内存空间不会收到应用程序进程重启的影响在应用程序重启时分布式缓存的存储数据仍然存在。
当应用程序采用集群方式部署时集群的每个部署节点都有一个统一的分布式缓存进行数据的读写操作所以不会存在像本地缓存中数据更新问题保证了不同服务器节点的数据一致性。
分布式缓存一般支持数据副本机制实现读写分离可以解决高并发场景中的数据读写性能问题。而且在多个缓存节点冗余存储数据提高了缓存数据的可用性避免某个节点宕机导致数据不可用问题
缺点数据跨网络传输读写性能不如本地缓存
分布式缓存是一个独立的服务进程并且和应用程序进程不在同一台机器上所以数据的读写要通过远程网络请求这样相对于本地缓存的数据读写来说性能要低一些
分布式缓存典例MemCached和Redis
多级缓存
基于本地缓存和分布式缓存的优缺点在实际的业务开发中一般采用多级缓存注本地缓存一般存储更新频率低访问频率高的数据分布式缓存一般存储更新频率很高的数据
多级缓存请求流程本地缓存作为一级缓存分布式缓存作为二级缓存。用户获取数据时先从一级缓存中获取数据乳沟一级缓存有数据则返回数据否则从二级缓存中获取数据。如果二级缓存中有数据则更新一级缓存并将数据返回给客户端。如果二级缓存没有数据则去数据库查询数据然后更新二级缓存接着再更新一级缓存最后将数据返回给客户端 多级缓存的实现可以使用Guava或者Caffeine作为一级缓存Redis作为二级缓存
注在应用程序集群部署时如果数据库的数据有更新的情况一级缓存的数据更新容易出现数据不一致的情况。因为是集群部署多个部署节点实现一级缓存数据更新难度比较大不过可以通过Redis的消息发布/订阅机制来实现多个节点缓存数据一致性问题
Java标准库中的缓存
Java 的 java.util 包提供了多个集合类这些类在不同的场景下使用具有各自的特性和适用范围。下面详细介绍 HashMap、WeakHashMap、ConcurrentHashMap 集合类的 Java 缓存机制。
1. HashMap缓存
优点
简单易用: HashMap 是 Java 标准库中的常用类API 简单容易上手。开发者无需引入外部库即可实现基本的缓存功能。可以快速存取数据插入和读取操作的平均时间复杂度为 O(1)非常适合用于简单的、无需复杂缓存逻辑的场景。
无额外依赖: 使用 HashMap 作为缓存不需要依赖外部库这使得代码更加轻量级适合那些希望避免外部依赖的项目。
灵活性: 可以根据需求灵活地进行扩展例如可以手动实现缓存过期、最大容量限制等功能。
缺点
无缓存失效机制: HashMap 不提供任何内置的缓存失效机制如时间过期、LRU最近最少使用策略等。需要开发者必须手动管理缓存项的有效期这增加了实现的复杂性。
线程安全问题: HashMap 不是线程安全的在多线程环境下同时进行读写操作可能导致数据不一致或其他并发问题。
内存管理: HashMap 会占用内存而不会自动清理无用的缓存项。随着缓存数据的增加可能导致内存占用过多。如果长时间不进行处理可能会导致内存泄漏。
无持久化支持: HashMap 在内存中存储数据应用程序关闭后数据将丢失。如果需要持久化缓存必须额外实现文件存储或数据库支持。
示例代码
首先创建一个管理缓存的类定义一个hashmap并将其进行初始化决定缓存内一开始有哪些数据
交给springboot管理进行测试PostConstruct注解可以使这个方法默认执行
Component
public class LocalCache {public static HashMapString,String cache new HashMap();static {String name 1 - UUID.randomUUID().toString();LocalCache.cache.put(String.valueOf(1),name);System.out.println(id为 1 的数据加入到了缓存);}PostConstructpublic void init(){String name 2 - UUID.randomUUID().toString();LocalCache.cache.put(String.valueOf(2),name);System.out.println(id为 2 的数据加入到了缓存);}
}
编写接口进行测试
RestController
public class CacheController {RequestMapping(/test/{id})public String test(PathVariable Long id) {String name LocalCache.cache.get(String.valueOf(id));if(name ! null) {System.out.println(缓存中存在查询缓存);System.out.println(name);return name;}System.out.println(缓存中不存在查询数据库);name id - UUID.randomUUID().toString();System.out.println(name);LocalCache.cache.put(String.valueOf(id), name);return name;}
}
效果 2. WeakHashMap
Java中WeakHashMap类是一种基于弱引用实现的Map集合弱引用当一个对象只被弱引用所引用时它就可以被垃圾回收器回收它的特点是当Map中的某个键值对的键不再被强引用指向时该键值对就会被自动清除。
示例代码使用WeakHashMap来实现缓存当一个缓存项中的键不再被强引用指向时该缓存项就会被自动清除。当某个键对应的值已经被清除时我们就需要重新创建从网络或数据库中搜索这个值将其放入缓存中。 3. ConcurrentHashMap
ConcurrentHsshMap是 Java 中的一个线程安全的哈希表实现通常用于在多线程环境下缓存数据。通过分段锁等技术允许多个线程并发的访问或修改不同的桶减少了锁的争用提升了并发性能。
线程安全ConcurrentHashMap 内部使用了多把锁默认情况下是16把每个锁控制一部分数据即一个桶多个线程可以并发地操作不同的部分而不会相互干扰。高效的并发操作在大多数情况下读操作是无锁的除了少数特定的场景。而写操作只会锁住当前操作的部分桶而不会锁住整个表。支持高效的并发遍历虽然 ConcurrentHashMap 不会抛出 ConcurrentModificationException但是在遍历期间结构的修改如插入或删除可能不会被遍历时立即反映出来但遍历操作本身仍然是线程安全的。
优点
线程安全 使用 ConcurrentHashMap 确保了缓存的线程安全性多线程环境下的并发读写操作不会引发数据不一致或死锁问题。
TTL支持 提供了 TTL的功能可以设置缓存项的存活时间自动删除过期的缓存项
内存管理 在缓存项过期时会自动从 ConcurrentHashMap 中移除避免过期数据占用内存虽然实现上是懒删除但已经能够在一定程度上控制内存的使用。
缺点
没有主动清理机制 当前实现是惰性删除即只有在调用 get 方法时才会检查并删除过期的缓存项。如果不频繁访问可能会有大量的过期数据滞留在缓存中导致内存占用增加。可以通过定期扫描或后台线程清理来改进。
没有容量控制 这个实现没有设置缓存的容量上限在数据量非常大时可能导致内存溢出。通常缓存需要有一个容量限制并通过 LRU等策略来管理缓存中的对象。
TTL粒度有限 当前的 TTL 实现是基于系统时间的对于精确性要求较高的场景比如需要精确到毫秒级别的过期管理可能不够准确。此外TTL 是全局的并没有提供按需调整的能力。
无统计功能 缺乏缓存命中率等统计信息。通常在使用缓存时我们希望能够知道缓存的命中率、失效率等以便优化系统性能。
未支持序列化 当前的实现不支持缓存项的序列化和反序列化对于分布式缓存或持久化缓存的需求这种实现不适用。
适用场景
小型应用适用于简单的单机应用或开发阶段的临时缓存需求。低并发环境尽管 ConcurrentHashMap 是线程安全的但当并发度很高时可能仍需要更为复杂的缓存管理机制来保证性能和可靠性。短生命周期的缓存特别适合那些缓存数据生命周期较短且缓存大小相对可控的场景。
常用缓存框架
1、Guava Cache
Guava Cache 是由 Google 提供的一个轻量级内存缓存实现作为 Guava 库的一部分。它主要用于单机内存缓存管理提供了多种灵活的缓存回收和失效策略。
主要特点
缓存回收策略支持基于时间的回收策略如基于访问时间expireAfterAccess和写入时间expireAfterWrite以及基于缓存大小的回收策略maximumSize。缓存加载可以通过CacheLoader实现自动缓存加载当缓存中没有对应的值时自动加载数据。统计信息提供缓存命中率、加载时间等详细的统计信息。柔性引用支持软引用和弱引用允许 JVM 在内存不足时回收缓存对象。
private static LoadingCacheString, String cache CacheBuilder.newBuilder().maximumSize(1000) // 最大缓存条目数.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期.expireAfterAccess(5, TimeUnit.MINUTES) // 最后一次访问后5分钟过期.refreshAfterWrite(1, TimeUnit.MINUTES) // 写入后1分钟刷新.weakKeys() // 使用弱引用存储键.weakValues() // 使用弱引用存储值.concurrencyLevel(4) // 设置并发级别.recordStats() // 开启统计信息.build(new CacheLoaderString, String() {Overridepublic String load(String key) throws Exception {return fetchDataFromDatabase(key);}});
2、Ehcache
Ehcache 是一个广泛使用的 Java 缓存框架主要用于 JVM 进程内的缓存管理。它提供了灵活的配置选项支持磁盘持久化并且可以通过集成第三方库实现分布式缓存。
主要特点
持久化支持Ehcache 支持将缓存的数据持久化到磁盘适用于需要长时间保留缓存数据的场景。分布式缓存通过与 Terracotta 集成Ehcache 可以实现分布式缓存支持多节点之间的数据同步。丰富的缓存策略Ehcache 支持LRU、LFU、FIFO等缓存驱逐策略。灵活配置Ehcache 提供了 XML 和 Java API 两种配置方式可以根据应用需求进行精细化配置。与 Spring 集成良好Ehcache 可以与 Spring 框架无缝集成作为 Spring 的缓存管理器。
ehcache xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:noNamespaceSchemaLocationhttp://ehcache.org/ehcache.xsdupdateChecktruemonitoringautodetectdynamicConfigtruediskStore pathjava.io.tmpdir/ehcache/cache namemyCachemaxEntriesLocalHeap1000maxEntriesLocalDisk10000eternalfalsetimeToIdleSeconds300timeToLiveSeconds600overflowToDisktruediskSpoolBufferSizeMB30memoryStoreEvictionPolicyLFUtransactionalModeoffpersistence strategylocalRestartable//cache/ehcacheCacheManager cacheManager CacheManager.create(path/to/ehcache.xml);Cache cache cacheManager.getCache(myCache);// 添加缓存条目cache.put(new Element(key, value));// 获取缓存条目Element element cache.get(key);if (element ! null) {System.out.println(element.getObjectValue());}// 关闭缓存管理器cacheManager.shutdown();
3、Caffeine
Caffeine 是一个高性能的 Java 缓存库被认为是 Guava Cache 的升级版。它提供了更高效的缓存策略支持异步加载和复杂的缓存淘汰机制。
主要特点
高性能Caffeine 在设计上经过了大量优化提供了比 Guava Cache 更快的读写性能。灵活的缓存策略支持基于时间、访问频率和缓存权重的淘汰策略使用Window TinyLFU算法进行缓存管理。异步支持支持异步缓存加载和刷新适用于高并发场景。统计信息提供详细的缓存统计信息包括命中率、加载时间、淘汰次数等。可定制性Caffeine 的配置非常灵活几乎可以满足任何缓存需求。
private static AsyncLoadingCacheString, String cache Caffeine.newBuilder().maximumSize(1000) // 最大缓存条目数.expireAfterWrite(10, TimeUnit.MINUTES) // 写入后10分钟过期.expireAfterAccess(5, TimeUnit.MINUTES) // 最后一次访问后5分钟过期.refreshAfterWrite(1, TimeUnit.MINUTES) // 写入后1分钟刷新.weakKeys() // 使用弱引用存储键.weakValues() // 使用弱引用存储值.recordStats() // 开启统计信息.buildAsync(key - fetchDataFromDatabase(key)); // 异步加载数据
分布式缓存
1、Redis
Redis 是一个开源的、高性能的分布式内存缓存数据库支持多种数据结构如字符串、哈希、列表、集合等并且具有持久化特性。Redis 不仅可以用作缓存还可以用作消息队列、会话存储等。
主要特点
分布式支持Redis 原生支持分布式可以在多个节点之间实现数据复制和高可用性。持久化虽然 Redis 是内存数据库但它支持将数据持久化到磁盘防止数据丢失。丰富的数据结构Redis 支持多种数据结构不仅限于键值对还支持哈希、列表、集合、有序集合等复杂数据结构。高性能由于 Redis 数据存储在内存中读写操作非常快速适用于高并发场景。丰富的生态Redis 提供了丰富的功能如发布/订阅、事务、Lua 脚本等生态系统非常完善。
Beanpublic RedisTemplateString, Object redisTemplate() {RedisTemplateString, Object template new RedisTemplate();template.setConnectionFactory(redisConnectionFactory);template.setKeySerializer(new StringRedisSerializer());template.setValueSerializer(new StringRedisSerializer());template.setHashKeySerializer(new StringRedisSerializer());template.setHashValueSerializer(new StringRedisSerializer());template.afterPropertiesSet();return template;}
2、Memcached
使用场景
分布式缓存Memcached 主要用于分布式系统中的缓存需求适合需要在多个节点之间共享缓存数据的场景。它广泛用于提升数据库查询性能减少数据库负载。Web 应用加速在大型网站和高并发的应用中Memcached 常用于缓存数据库查询结果、API 响应、会话数据等以加速页面加载时间和响应速度。简单数据结构适用于缓存简单的数据结构如字符串、整数和序列化后的对象数据。它不支持复杂的数据类型如列表、集合等通常用于键值对形式的数据缓存。
特点
高性能Memcached 是一个纯内存的缓存系统具有极高的读写性能适用于高并发的场景。分布式特性Memcached 支持将缓存分布在多个服务器节点上通过一致性哈希算法来分布和查找缓存数据。数据非持久化Memcached 的数据仅存储在内存中重启或崩溃后数据会丢失因此它主要用于缓存临时性数据而不是用于存储关键的持久性数据。简单协议Memcached 使用简单的文本协议或二进制协议与各种编程语言如 Java、Python、PHP 等有良好的兼容性。缺乏高级功能与 Redis 等更复杂的缓存系统相比Memcached 不支持数据持久化、复杂数据结构、发布订阅等高级功能。
private static MemcachedClient memcachedClient;// 初始化 Memcached 客户端static {memcachedClient new MemcachedClient(new InetSocketAddress(localhost, 11211));}// 缓存数据public void cacheData(String key, Object value, int expiration) {OperationFutureBoolean future memcachedClient.set(key, expiration, value);try {future.get(5, TimeUnit.SECONDS); // 等待操作完成} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) {MemcachedExample example new MemcachedExample();// 设置缓存example.cacheData(key, value, 3600);// 获取缓存System.out.println(example.getData(key));// 删除缓存example.deleteData(key);// 关闭客户端example.shutdown();}// 获取缓存数据public Object getData(String key) {return memcachedClient.get(key);}// 删除缓存数据public void deleteData(String key) {memcachedClient.delete(key);}// 关闭客户端public void shutdown() {memcachedClient.shutdown();}
}
缓存一致性
缓存一致性是指 缓存中的数据与后端数据源如数据库之间保持一致的状态。当数据库中的数据发生变化时如何保证缓存中的数据及时更新或者失效以避免缓存中的数据与数据库中的数据不一致是缓存一致性需要解决的问题。缓存一致性是分布式系统中常见且复杂的问题特别是在高并发环境下。
下面借用两篇文章如何保证缓存和数据库的一致性 缓存和数据库的一致性问题
缓存策略
LRULeast Recently Used最近最少使用 描述淘汰最近最少使用的数据。适用场景适合常见的缓存场景假设最近使用的数据更可能被再次使用。
LFULeast Frequently Used最少频繁使用 描述淘汰访问频率最低的数据。适用场景适合数据访问频率较为固定的场景。
FIFOFirst In First Out先进先出 描述按照数据进入缓存的时间顺序进行淘汰最早进入的数据优先被淘汰。适用场景适合简单的时间序列或队列式缓存。
TTLTime to Live存活时间 描述每个缓存项都有固定的过期时间到期后自动失效。适用场景适合动态数据缓存如配置文件或临时数据。
MRUMost Recently Used最近最常使用 描述优先淘汰最近最常使用的数据。适用场景适合某些更新频繁的数据场景。
Random Replacement随机替换 描述随机淘汰缓存中的一项数据。适用场景适合对淘汰数据没有明确偏好的场景。
缓存的设计与实现设计和实现一个缓存系统需要考虑多个方面包括缓存的大小、过期策略、缓存粒度等。下面将详细介绍这些方面的设计考量以及如何实现缓存逻辑。
1. 缓存的设计
1.1 缓存大小
缓存大小决定了可以存储的数据量。缓存太小会导致频繁的缓存失效和加载降低系统性能缓存太大会占用过多内存资源。缓存大小的设计通常需要考虑以下因素
可用内存: 需要确保缓存占用的内存不会影响系统的其他重要功能。数据访问模式: 如果某些数据被频繁访问可以优先缓存这些数据即采用热点数据缓存的策略。性能要求: 需要平衡缓存命中率和内存消耗以满足系统的性能要求。
在设计时可以使用一些缓存替换算法如LRULeast Recently Used最近最少使用、LFULeast Frequently Used最不经常使用等来决定在缓存满时如何移除旧数据。
1.2 过期策略
缓存的过期策略决定了缓存中数据的有效时间。常见的过期策略有
TTLTime to Live生存时间: 每个缓存项都有一个生存时间过了这个时间数据就会失效。LRU过期: 根据最近访问时间决定哪些数据应该被淘汰。手动过期: 开发者可以在特定条件下手动清除或更新缓存。
结合使用这些策略可以灵活管理缓存的有效性。例如可以使用TTL保证数据不会过期过久同时使用LRU来移除不常用的数据。
1.3 缓存粒度
缓存粒度指的是缓存中存储数据的大小和结构。缓存粒度的设计通常需要在以下几个方面进行权衡
缓存命中率: 细粒度缓存可以更精确地存储和检索数据但可能导致缓存项过多而增加管理复杂性。存储开销: 粗粒度缓存例如整页缓存可以减少缓存项的数量但可能会导致不必要的数据缓存从而增加存储和带宽开销。一致性要求: 细粒度缓存更容易管理数据的一致性但维护成本较高。
在设计时可以根据业务需求选择适合的粒度比如将整个网页缓存作为一个缓存项或者将用户会话数据细分为多个缓存项。
2. 缓存的实现
2.1 缓存加载
缓存加载涉及将数据加载到缓存中的过程这通常发生在以下情况
缓存不命中: 当请求的数据不在缓存中时需要从数据源如数据库中加载数据并存入缓存。预加载: 在系统初始化时或在预测某些数据即将被频繁访问时可以提前加载数据到缓存中。
实现缓存加载的关键点在于确保数据的一致性和及时性。例如在分布式系统中需要考虑多个实例之间缓存的一致性问题。
2.2 缓存更新
缓存更新指的是在数据源发生变化后更新缓存中的数据。常见的更新策略有
写通Write-Through: 数据在写入缓存的同时也会同步写入数据源。写回Write-Back: 数据先写入缓存稍后再异步写入数据源。写旁路Write-Around: 数据直接写入数据源而不更新缓存通常在读操作时才更新缓存。
缓存更新策略的选择需要根据系统对数据一致性和延迟的要求进行调整。
2.3 缓存失效
缓存失效是指缓存项不再有效需要从缓存中移除或更新。失效策略通常与过期策略密切相关
主动失效: 通过TTL或LRU等策略自动判断缓存项是否需要失效。被动失效: 通过业务逻辑判断某些条件是否满足从而手动触发缓存失效。分布式失效: 在分布式系统中缓存失效可能需要广播或一致性协议来通知各个实例更新缓存。
3. 实现缓存系统的示例
以Java中的Redis为例展示如何实现一个简单的缓存系统
import redis.clients.jedis.Jedis;public class CacheSystem {private Jedis jedis;private int ttl; // 缓存的生存时间秒public CacheSystem(String redisHost, int redisPort, int ttl) {this.jedis new Jedis(redisHost, redisPort);this.ttl ttl; // 默认缓存时间}public static void main(String[] args) throws InterruptedException {CacheSystem cacheSystem new CacheSystem(localhost, 6379, 30); // 缓存时间设置为30秒// 获取缓存如果没有则加载System.out.println(cacheSystem.get(user:1001));// 等待10秒后再获取Thread.sleep(10000);System.out.println(cacheSystem.get(user:1001));// 使缓存失效cacheSystem.invalidate(user:1001);// 再次获取缓存应该重新加载System.out.println(cacheSystem.get(user:1001));}// 获取缓存public String get(String key) {String value jedis.get(key);if (value null) {System.out.println(Cache miss for key: key);value loadFromSource(key);set(key, value);} else {System.out.println(Cache hit for key: key);}return value;}// 设置缓存public void set(String key, String value) {jedis.setex(key, ttl, value);System.out.println(Set cache for key: key with TTL: ttl seconds);}// 模拟从数据源加载数据private String loadFromSource(String key) {return Value for key from source;}// 使缓存失效public void invalidate(String key) {jedis.del(key);System.out.println(Invalidated cache for key: key);}
}实际案例的缓存实践
在实际项目中缓存系统的设计和实现需要根据具体的业务场景进行细致的调整以确保系统的高效性、稳定性和一致性。下面对上述的最佳实践进行更详细的说明。
1. 合理选择缓存策略
缓存策略决定了哪些数据应该被保留在缓存中哪些应该被淘汰。根据业务访问模式选择合适的缓存策略是优化缓存系统的重要一步。
LRULeast Recently Used: 最近最少使用算法。适用于那些数据访问有时间局限性的场景即最近访问的数据更有可能被再次访问。例如在电商网站上用户最近浏览过的商品很可能会再次被查看因此适合使用LRU缓存策略。LFULeast Frequently Used: 最少频繁使用算法。适用于那些访问频率决定数据重要性的场景。例如热门新闻或文章的访问频率高可以优先保存在缓存中而冷门内容可以较快淘汰。FIFOFirst In First Out: 先进先出算法。适合那些数据有固定有效期的场景例如缓存某些定期更新的数据如实时天气信息每隔一段时间进行刷新。
选择缓存策略时需要综合考虑系统的访问模式和数据的生命周期尽可能提高缓存的命中率。
2. 监控缓存性能
缓存的性能直接影响系统的响应速度因此对缓存性能的监控至关重要。以下是一些常见的缓存性能指标及其监控方法
缓存命中率: 这是缓存系统的核心指标表示从缓存中获取到的数据占总请求数据的比例。命中率越高系统性能提升越明显。可以通过定期检查缓存命中率来判断缓存配置是否合理。如果命中率低可能需要调整缓存策略或扩大缓存容量。缓存加载时间: 当缓存未命中时数据从数据源加载到缓存所需的时间。如果加载时间过长会导致响应延迟可能需要优化数据加载的过程或提高数据源的响应速度。缓存失效和淘汰: 监控哪些数据被淘汰什么时间淘汰为什么被淘汰如过期、达到容量限制等。通过分析这些数据可以更好地理解缓存的使用情况进一步优化缓存策略。
监控工具可以使用如Prometheus结合Grafana进行实时监控也可以通过日志分析工具对缓存系统进行离线分析。
3. 处理缓存穿透和击穿
在高并发场景下缓存系统可能会遇到缓存穿透、击穿和雪崩等问题需要采取有效措施进行预防和处理。
缓存穿透: 发生在对数据库或其他数据源中不存在的数据进行频繁请求时这些请求直接落到数据库上绕过缓存造成数据库压力。处理方法包括 缓存空结果: 当查询结果为空时仍然将其缓存但设置一个较短的TTL避免频繁请求同样的数据。布隆过滤器: 在访问缓存之前使用布隆过滤器判断数据是否存在于数据源中从而减少对无效请求的处理。
缓存击穿: 发生在某个热点数据在缓存失效时突然大量请求打到数据库导致数据库压力激增。处理方法包括 设置互斥锁: 在缓存失效时只有一个线程去加载数据其他线程等待避免并发加载。热点数据预加载: 对一些热点数据定时刷新确保在高峰期不会失效。
缓存雪崩: 发生在大量缓存同时失效时所有请求直接打到数据库导致数据库崩溃。处理方法包括 随机化过期时间: 为不同的缓存设置不同的TTL避免在同一时间大量缓存失效。多级缓存架构: 通过引入多级缓存如本地缓存和远程缓存相结合分散请求压力。
4. 考虑分布式一致性
在分布式系统中缓存的一致性问题尤为复杂因为数据可能同时存在于多个节点的缓存中。以下是一些常见的解决方法
一致性哈希: 将数据按照哈希分布到不同的缓存节点上以减少缓存节点变更时的数据重新分配量。通过一致性哈希可以确保即使某些缓存节点发生变化仍然有大部分数据保持在原有节点上从而提高缓存命中率和稳定性。分布式锁: 在缓存更新或失效时使用分布式锁如Redis的RedLock算法来确保只有一个节点能够更新缓存从而防止并发写入造成的数据不一致问题。订阅发布机制: 通过使用Redis的订阅发布Pub/Sub功能或消息队列如Kafka、RabbitMQ当某个缓存节点的缓存失效或更新时通知其他节点进行同步更新确保全局缓存的一致性。双写一致性: 在写入数据库和缓存时确保两者同步写入。可以在数据库事务提交之后更新缓存以确保数据的一致性。对于复杂场景还可能需要采用最终一致性方案通过异步方式在一定时间内保证数据一致。
总结
缓存系统在提升系统性能的同时也带来了诸如一致性、穿透、击穿等复杂性问题。在实际项目中必须根据业务特点选择合适的缓存策略和技术手段进行合理的设计和优化才能真正发挥缓存的优势并确保系统的稳定性和高效性。通过监控和不断调整可以实现最佳的缓存效果为系统提供有力的性能保障。
附其他学习文档
Java HashMap详解
锁学习synchronized隐式锁Lock显式锁、volatile、CAS
什么是死锁如何解决
架构之高并发缓存
Java强引用软引用弱引用和虚引用
HashMap学习JDK7
ConcurrentHashMap学习
弱引用实现弱缓存策略
ConcurrentHashMap为什么放弃了分段锁
高并发解决方案详解
Java缓存机制