连云港优化网站团队,大前端最新网站,建筑智能化工程技术,烟台网站建设九七拿破仑说#xff1a;胜利属于坚持到最后的人。 而正巧#xff0c;咱们今天就是要聊一个#xff0c;关于怎么让系统在狂轰乱炸甚至泰山压顶的情况下#xff0c;都屹立不倒并坚持到最后的话题——缓存。 Victory belongs to the most persevering. — Napoleon Bonaparte, Fr…拿破仑说胜利属于坚持到最后的人。 而正巧咱们今天就是要聊一个关于怎么让系统在狂轰乱炸甚至泰山压顶的情况下都屹立不倒并坚持到最后的话题——缓存。 Victory belongs to the most persevering. — Napoleon Bonaparte, French military and political leader 目录体系 下面我们先简单浏览一下这个分享的目录体系。 今天我会分五个方面给大家介绍关于缓存使用的问题包括原理、实践、技术选型和常见问题。 这个目录体系就是一副人体骨骼只有把各种内脏、器官和血肉都填充进去缓存之美才能跃然纸上。接下来我就邀请大家跟我一起来做这件事情. 让我们不止步于Hello World一起来聊聊缓存。 关于缓存 What 缓存是什么 缓存是实际工作中非常常用的一种提高性能的方法。 而在java中所谓缓存就是将程序或系统经常要调用的对象存在内存中再次调用时可以快速从内存中获取对象不必再去创建新的重复的实例。 这样做可以减少系统开销提高系统效率。 目前缓存的做法分为两种模式: 内存缓存缓存数据存放在服务器的内存空间中。 优点速度快。缺点资源有限。
复制代码 文件缓存缓存数据存放在服务器的硬盘空间中。 优点容量大。缺点速度偏慢尤其在缓存数量巨大时。
复制代码 why 为什么要使用缓存 对于为什么要使用缓存我见过的最精炼的回答是来源一个梦想那就是多快好省的构建社会主义社会。 但这是一种很矛盾的说法就好像你不是高富帅还想迎娶白富美好像是痴人说梦啊。 因为多就不可能快好就不能省怎么做到多又快好而且省呢 答案就是用缓存 下面我们就聊聊怎么用缓存实现这个梦想。 首先我想先声明一下我什么会想到做这样一个分享。 其实从第一次使用 Java整型的缓存到了解CDN的代理缓存从初次接触 MySQL内置的查询缓存到使用 Redis缓存Session我越来越发现使用缓存的重要性和普遍性。 因此我觉得自己有必要把自己的所学所用梳理出来用于工作并造福大家因此才有了这样一个技术分享。 聊缓存之前我们先聊聊数据库。 在增删改查中数据库查询占据了数据库操作的80%以上 非常频繁的磁盘I/O读取操作会导致数据库性能极度低下。 而数据库的重要性就不言而喻了 数据库通常是企业应用系统最核心的部分数据库保存的数据量通常非常庞大数据库查询操作通常很频繁有时还很复杂我们知道对于多数Web应用整个系统的瓶颈在于数据库。 原因很简单Web应用中的其他因素例如网络带宽、负载均衡节点、应用服务器包括CPU、内存、硬盘灯、连接数等、缓存都很容易通过水平的扩展俗称加机器来实现性能的提高。 而对于MySQL由于数据一致性的要求无法通过简单的增加机器来分散向数据库 写数据 带来的压力。虽然可以通过前置缓存Redis等、读写分离、分库分表来减轻压力但是与系统其它组件的水平扩展相比受到了太多的限制而切会大大增加系统的复杂性。 因此数据库的连接和读写要十分珍惜。 可能你会想到那就直接用缓存呗但大量的用、不分场景的用缓存显然是不科学的。我们不能手里有了一把锤子看什么都是钉子。 但缓存也不是万能的要慎用缓存想要用好缓存并不容易。因此我花了点时间整理了一下关于缓存的实现以及常见的一些问题。 when 首先简单梳理一下Web请求的过程以及不同节点缓存的作用。 how 先不讲代码对于缓存是如何工作的简单的缓存数据请求流程就如下图。 设计缓存的时候需要考虑的最关键的两个缓存策略。 - TTLTime To Live 存活期 即从缓存中创建时间点开始直到它到期的一个时间段不管在这个时间段内有没有访问都将过期 TTITime To Idle 空闲期 即一个数据多久没被访问将从缓存中移除的时间后面讲到缓存雪崩的时候会讲到如果缓存策略设置不当将会造成如何的灾难性后果以及如何避免这里先按下不表。 自定义缓存 如何实现 前面介绍了关于缓存的一些概念那么实现缓存或者确切的说实现存储的前置缓存很难吗 答案是不难。 JVM本身就是一个高速的缓存存储场所同时Java为我们提供了线程安全的ConcurrentMap可以非常方便的实现一个完全由你自定义的缓存实例。 后面你会发现Spring Cache的缺省实现SimpleCacheManager也是这样设计自己的缓存的。 这里放上简单的实现代码不过36行就实现了对缓存的存储、更新、读取和删除等基本操作。 再结合实际的业务代码就能不依赖任何三方的实现在JVM中轻松玩转缓存了。 但是我想作为有追求的技术人各位是绝对不会止步于此的。 那么我们思考一下我们自定义的缓存实现有哪些优缺点呢 同与自定义的缓存相比就能更深刻的理解Spring Cache的原理以及优点。 这里先把Spring Cache的特性列举出来下面还会介绍它的原理和具体用法。 Spring Cache Spring Cache是Spring提供的对缓存功能的抽象即允许绑定不同的缓存解决方案如Ehcache、Redis、Memcache、Map等等但本身不直接提供缓存功能的实现。 它支持注解方式使用缓存非常方便。 Spring Cache的实现本质上依赖了Spring AOP对切面的支持。 知道了Spring Cache的原理你会对Spring Cache的注解的使用有更深入的认识。 Spring Cache主要用到的注解有4个。 CacheEvict对于保证缓存一致性非常重要后面会专门讲一下这个问题。 同时Spring还支持自定义的缓存Key以及SpringEL这里不详细讲了感兴趣的同学可以参考Spring Cache的文档。 缓存三高音 正如写得再好的乐谱都需要歌唱家演唱出来才能美妙动听一样。 上面讲到Spring Cache是对缓存的抽象那么常用的缓存的实现有哪些呢 歌唱界有世界三大男高音那么缓存界如果来评选一下话三大高音会是谁呢 Redis redis是一个key-value存储系统这点和Memcached类似。 不同的是它支持存储的value类型相对更多包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash哈希类型。这些数据类型都支持push/pop、add/remove及取交集并集和差集。 和Memcached一样为了保证效率数据都是缓存在内存中。 区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件并且在此基础上实现了master-slave(主从)同步。 Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。 存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制使得从数据库在任何地方同步树时可订阅一个频道并接收主服务器完整的消息发布记录。 同步对读取操作的可扩展性和数据冗余很有帮助。 Redis有哪些适合的场景 会话缓存Session Cache:用Redis缓存会话比其他存储如memcached的优势在于redis提供持久化。全页缓存FPC:除基本的会话token之外Redis还提供很简便的FPC平台。队列Redis在内存存储引擎领域的一大优点是提供list和set操作这使得Redis能作为一个很好的消息队列平台来使用。排行榜/计数器Redis在内存中对数据进行递增递减的操作实现的非常好。订阅/发布缺点 持久化。Redis直接将数据存储到内存中要将数据保存到磁盘上Redis可以使用两种方式实现持久化过程。 定时快照snapshot每隔一段时间将整个数据库写到磁盘上每次均是写全部数据代价非常高。 基于语句追加aof只追踪变化的数据但是追加的log可能过大同时所有的操作均重新执行一遍回复速度慢。 耗内存占用内存过高。 Ehcache Ehcache 是一个成熟的缓存框架你可以直接使用它来管理你的缓存。 Java缓存框架 EhCache EhCache 是一个纯Java的进程内缓存框架具有快速、精干等特点是Hibernate中默认的CacheProvider。 特性可以配置内存不足时启用磁盘缓存(maxEntriesLoverflowToDiskocalDisk配置当内存中对象数量达到maxElementsInMemory时Ehcache将会对象写到磁盘中)。 Memcached Memcached 是一个高性能的分布式内存对象缓存系统用于动态Web应用以减轻数据库负载。它基于一个存储键/值对的hashmap。 其守护进程daemon 是用C写的但是客户端可以用任何语言来编写并通过memcached协议与守护进程通信。 Memcached通过在内存中缓存数据和对象来减少读取数据库的次数从而提高动态、数据库驱动网站的速度。 同属于个key-value存储系统Memcached与Redis常常一起比: Memcached的数据结构和操作较为简单不如Redis支持的结构丰富。使用简单的key-value存储的话Memcached的内存利用率更高 而如果Redis采用hash结构来做key-value存储由于其组合式的压缩其内存利用率会高于Memcached。由于Redis只使用单核而Memcached可以使用多核所以平均每一个核上Redis在存储小数据时比Memcached性能更高。 而在100k以上的数据中Memcached性能要高于Redis虽然Redis最近也在存储大数据的性能上进行优化但是比起Memcached还是稍有逊色。Redis虽然是基于内存的存储系统但是它本身是支持内存数据的持久化的而且提供两种主要的持久化策略RDB快照和AOF日志。而memcached是不支持数据持久化操作的。 Memcached是全内存的数据缓冲系统Redis虽然支持数据的持久化但是全内存毕竟才是其高性能的本质。作为基于内存的存储系统来说机器物理内存的大小就是系统能够容纳的最大数据量。如果需要处理的数据量超过了单台机器的物理内存大小就需要构建分布式集群来扩展存储能力。Memcached本身并不支持分布式因此只能在客户端通过像一致性哈希这样的分布式算法来实现Memcached的分布式存储。 相较于Memcached只能采用客户端实现分布式存储Redis更偏向于在服务器端构建分布式存储。最新版本的Redis已经支持了分布式存储功能。 缓存三高音比较 缓存进阶 缓存由于其高并发和高性能的特性已经在项目中被广泛使用。尤其是在高并发、分布式和微服务的业务场景和架构下。 无论是高并发、分布式还是微服务都依赖于高性能的服务器。而谈到高性能服务器就必谈缓存。 所谓高性能主要体现在高可用情况下业务处理时间短数据正确。 数据处理及时就是个“空间换时间”的问题利用分布式内存或者闪存等可以快速存取的设备来替代部署在一般服务器上的数据库机械硬盘上存储的文件这是缓存提升服务器性能的本质。 高并发High Concurrency 是互联网分布式系统架构设计中必须考虑的因素之一它通常是指通过设计保证系统能够同时并行处理很多请求。 分布式 是以缩短单个任务的执行时间来提升效率的。 比如一个任务由10个子任务组成每个子任务单独执行需1小时则在一台服务器上执行改任务需10小时。 采用分布式方案提供10台服务器每台服务器只负责处理一个子任务不考虑子任务间的依赖关系执行完这个任务只需一个小时。 微服务 架构强调的第一个重点就是业务系统需要彻底的组件化和服务化原有的单个业务系统会拆分为多个可以独立开发设计运行和运维的小应用。这些小应用之间通过服务完成交互和集成。 缓存一致性问题 缓存一致性是如何发生的先写数据库再淘汰缓存: 第一步写数据库成功第二步淘汰缓存失败则会引发一次严重的缓存不一致问题。
复制代码 如何避免缓存不一致的问题先淘汰缓存再写数据库 第一步淘汰缓存成功第二步写数据库失败则只会引发一次Cache miss。
复制代码 分布式缓存一致性 我们使用zookeeper来协调各个缓存实例节点zookeeper是一个分布式协调服务包含一个原语集可以通知所有watch节点的client端并保证事件发生顺序和client收到消息的顺序一致使用zookeeper集群可非常容易的实现这场景。 一致性Hash算法通过一个叫做一致性Hash环的数据结构实现KEY到缓存服务器的Hash映射。 缓存雪崩 产生原因1. a. 由于Cache层承载着大量请求有效的保护了Storage层(通常认为此层抗压能力稍弱)所以Storage的调用量实际很低所以它很爽。 b. 但是如果Cache层由于某些原因(宕机、cache服务挂了或者不响应了)整体crash掉了也就意味着所有的请求都会达到Storage层所有Storage的调用量会暴增所以它有点扛不住了甚至也会挂掉 产生原因2. 我们设置缓存时采用了相同的过期时间导致缓存在某一时刻同时失效请求全部转发到DBDB瞬时压力过重雪崩。 雪崩问题在国外叫做stampeding herd(奔逃的野牛)指的的cache crash后流量会像奔逃的野牛一样打向后端。 解决方案 加锁/队列 保证缓存单线程的写失效时的雪崩效应对底层系统的冲击非常可怕。 大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线 程进程写从而避免失效时大量的并发请求落到底层存储系统上。 加锁排队只是为了减轻数据库的压力并没有提高系统吞吐量。 假设在高并发下缓存重建期间key是锁着的这是过来1000个请求999个都在阻塞的。同样会导致用户等待超时这是个治标不治本的方法 加锁排队的解决方式分布式环境的并发问题有可能还要解决分布式锁的问题线程还会被阻塞用户体验很差因此在真正的高并发场景下很少使用 避免缓存同时失效将缓存失效时间分散开比如我们可以在原有的失效时间基础上末尾增加一个随机值。 缓存降级当访问量剧增、服务出现问题如响应时间慢或不响应或非核心服务影响到核心流程的性能时仍然需要保证服务还是可用的即使是有损服务。 系统可以根据一些关键数据进行自动降级也可以配置开关实现人工降级。 降级的最终目的是保证核心服务可用即使是有损的。而且有些服务是无法降级的如加入购物车、结算。 在进行降级之前要对系统进行梳理看看系统是不是可以丢卒保帅从而梳理出哪些必须誓死保护哪些可降级。 比如可以参考日志级别设置预案 1一般比如有些服务偶尔因为网络抖动或者服务正在上线而超时可以自动降级 2警告有些服务在一段时间内成功率有波动如在95~100%之间可以自动降级或人工降级并发送告警 3错误比如可用率低于90%或者数据库连接池被打爆了或者访问量突然猛增到系统能承受的最大阀值此时可以根据情况自动降级或者人工降级 4严重错误比如因为特殊原因数据错误了此时需要紧急人工降级。 缓存击穿/缓存穿透 缓存穿透是指查询一个一定不存在的数据由于缓存是不命中时被动写的并且出于容错考虑如果从存储层查不到数据则不写入缓存这将导致这个不存在的数据每次请求都要到存储层去查询失去了缓存的意义。在流量大时可能DB就挂掉了要是有人利用不存在的key频繁攻击我们的应用这就是漏洞。 缓存穿透-解决方案1 一个简单粗暴的方法如果一个查询返回的数据为空不管是数 据不存在还是系统故障我们仍然把这个空结果进行缓存 但它的过期时间会很短最长不超过五分钟。 缓存穿透-解决方案2 最常见的则是采用布隆过滤器将所有可能存在的数据哈希到一个足够大的bitmap中一个一定不存在的数据会被 这个bitmap拦截掉从而避免了对底层存储系统的查询压力。 例如商城有100万用户数据将所有用户id刷入一个Map。 当请求过来以后先判断Map中是否包含该用户id不包含直接返回包含的话先去缓存中查是否有这条数据有的话返回没有的话再去查数据库。 这样不仅减轻了数据库的压力缓存系统的压力也将大大降低。 寄语 古人云纸上得来终觉浅绝知此事要躬行。 别人的经验和智慧需要经过你亲自验证才知道是不是真理要经过亲手实践才能为我所用。 别人的知识只是一些树枝需要你把它们编织成一架梯子才能助你高升。 参考链接 百度百科 - 缓存Spring思维导图让Spring不再难懂cache篇importnew : Spring Cache图解分布式架构的演进EHCACHEehcache官方文档ehcache入门基础示例ehcache详细解读ehcache memcache redis 三大缓存男高音网站缓存技术 ehcache memcache redis 的比较缓存击穿、失效及热点key问题Cache 应用中的服务过载案例研究Bloom Filter布隆过滤器缓存在高并发场景下的常见问题缓存雪崩问题再聊缓存技术缓存穿透问题微服务化之缓存的设计缓存与数据库一致性保证分布式之缓存击穿CDN缓存小结ava 中整型的缓存机制mysql的查询缓存使用Spring Session和Redis解决分布式Session跨域共享问题学习Spring-SessionRedis实现session共享详解 MySQL 基准测试和 sysbench 工具分布式之缓存击穿分布式之数据库和缓存双写一致性方案解析转载于:https://juejin.im/post/5c8481d95188257a323f52b5