精华内容
下载资源
问答
  • 分布式缓存
    千次阅读
    2022-01-17 22:36:23

    目录

    1. 从数据的使用说起
    2. 本地缓存
    3. 远程缓存
    4. 缓存策略
    5. 缓存常见问题
    6. 总结回顾与作业实践

    1. 从数据的使用说起

    我们把数据的使用频率和方式分个类

    • 静态数据:一般不变,类似于字典表

    • 准静态数据:变化频率很低,部门结构设置,全国行政区划数据等

    • 中间状态数据:一些计算的可复用中间数据,变量副本,配置中心的本地副本

    • 热数据:使用频率高

    • 读写比较大:读的频率 >> 写的频率
      这些数据适合于使用缓存的方式访问

      广义上来说,为了加速数据处理,让业务更快访问的临时存放冗余数据,都是缓存 狭义上,现在我们一般在分布式系统里把缓存到内存的数据叫做内存缓存 还有没有其他数据?

    缓存无处不在 内存

    ~ 可以看做是 CPU 和 磁盘之间的缓存
    CPU与内存的处理速度也不一致,出现 L1&L2 Cache
    网络处理,数据库引擎的各种Buffer,都可以看做是缓存
    GUI的Double Buffer(双缓冲),是一个经典的性能优化方法

    缓存的本质: 系统各级处理速度不匹配,导致利用空间换时间
    缓存是提升系统性能的一个简单有效的办法

    缓存加载时机

    1、启动全量加载 ==> 全局有效,使用简单
    2、懒加载
    同步使用加载 ==>
    - 先看缓存是否有数据,没有的话从数据库读取
    - 读取的数据,先放到内存,然后返回给调用方
    延迟异步加载 ==>
    - 从缓存获取数据,不管是否为空直接返回 ==>
    - 策略1异步)如果为空,则发起一个异步加载的线程,负责加载数据
    - 策略2解耦)异步线程负责维护缓存的数据,定期或根据条件触发更新

    缓存的有效性与数据同步

    在这里插入图片描述

    缓存使用不当导致的问题

    1、系统预热导致启动慢 试想一下,一个系统启动需要预热半个小时。 导致系统不能做到快速应对故障宕机等问题。
    2、系统内存资源耗尽 只加入数据,不能清理旧数据。 旧数据处理不及时,或者不能有效识别无用数据。

    2. 本地缓存

    最简单的本地缓存

    在这里插入图片描述

    Hibernate/MyBatis都有Cache

    在这里插入图片描述

    Guava Cache

    在这里插入图片描述

    Spring Cache

    在这里插入图片描述

    3. 远程缓存

    考虑一下本地缓存有什么缺点?

    1、在多个集群环境同步?当集群规模增大,缓存的读写放大。
    2、在JVM中长期占用内存?如果是堆内存,总是会影响GC。
    3、缓存数据的调度处理,影响执行业务的线程,抢资源。
    == > 集中处理缓存
    聪明的你,思考一下:有什么缺点呢?

    Redis/Memcached 缓存中间件

    REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储 系统。Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内 存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
    Memcached是以LiveJournal旗下Danga Interactive公司的Brad Fitzpatric为首开发的 一款开源高性能,分布式内存对象缓存系统。
    Redis 官网:https://redis.io/
    Redis 在线测试:http://try.redis.io/
    Redis 命令参考:http://doc.redisfans.com/
    《Redis 设计与实现》:http://redisbook.com/
    Memcached 官网:https://memcached.org/

    Hazelcast/Ignite 内存网格

    4. 缓存策略

    容量

    资源有限

    • 缓存数据容量是必须要考虑的问题
    • 思考系统的设计容量、使用容量、峰值,应该是我们做架构设计的一个常识

    过期策略

    • 按FIFO或LRU - 按固定时间过期
    • 按业务时间加权:例如3+5x

    5. 缓存常见问题

    缓存穿透

    问题:大量并发查询不存在的KEY,导致都直接将压力透传到数据库。
    分析:为什么会多次透传呢?不存在一直为空。 需要注意让缓存能够区分KEY不存在和查询到一个空值。
    解决办法:
    1、缓存空值的KEY,这样第一次不存在也会被加载会记录,下次拿到有这个KEY。
    2、Bloom过滤或RoaringBitmap 判断KEY是否存在。
    3、完全以缓存为准,使用 延迟异步加载 的策略2,这样就不会触发更新。

    缓存击穿

    问题:某个KEY失效的时候,正好有大量并发请求访问这个KEY。
    分析:跟前面一个其实很像,属于比较偶然的。
    解决办法:
    1、KEY的更新操作添加全局互斥锁。
    2、完全以缓存为准,使用 延迟异步加载 的策略2,这样就不会触发更新。

    缓存雪崩

    问题:当某一时刻发生大规模的缓存失效的情况,会有大量的请求进来直接打到数据库,导致数 据库压力过大升值宕机。
    分析:一般来说,由于更新策略、或者数据热点、缓存服务宕机等原因,可能会导致缓存数据同 一个时间点大规模不可用,或者都更新。所以,需要我们的更新策略要在时间上合适,数据要均 匀分散,缓存服务器要多台高可用。
    解决办法:
    1、更新策略在时间上做到比较均匀。
    2、使用的热数据尽量分散到不同的机器上。
    3、多台机器做主从复制或者多副本,实现高可用。
    4、实现熔断限流机制,对系统进行负载能力控制。

    更多相关内容
  • 单机缓存及分布式缓存redis培训ppt,分为4部分 1.缓存开篇词/单机缓存介绍与应用 2.分布式缓存Redis基础 3. Redis高级应用 4. Redis分布式
  • #资源达人分享计划#
  • 分布式缓存介绍,主要介绍目前比较流行的分布式缓存架构
  • 分布式缓存

    2019-01-16 21:53:55
    分布式缓存原理书籍 电子书, 分布式 缓存
  • 无论是单机缓存还是分布式缓存都有其适应场景和优缺点,当今存在的缓存产品也是数不胜数,最常见的有redis和memcached等,既然是分布式,那么他们是怎么实现分布式的呢?本文主要介绍分布式缓存服务mencached的...
  • 分布式缓存RedisTemplate取不到数据的问题(csdn)————程序
  • 本文实例讲述了thinkPHP实现MemCache分布式缓存功能。分享给大家供大家参考,具体如下: 两天在研究MemCache分布式缓存的问题时,发现ThinkPHP其实并不支持分布式缓存功能,这可以从官方提供的CacheMemcache.class....
  • 介绍从零开始实现一个GO语言版本的分布缓存系统。在这一个过程你将学习到缓存相关的技术原理和系统构架。
  • reidis介绍、为什么使用内存缓存数据库、Redis作为单线程模型为什么效率还这么高、Redis服务安装及常用命令解析、数据持久化、持久化化文件是如何恢复的、高可用主从搭建、故障转移哨兵模式、集群
  • 沙锅砂锅是一个分布式的http缓存,重点是: 大物件带宽最小化多层分布式缓存共享本地记忆集群内存集群永久存储没有单点故障砂锅用Go编写,并集成了经过良好测试的分布式库,例如etcd和groupcache。上游服务器考虑...
  • Redis_Redis分布式缓存_

    2021-09-30 14:37:01
    实现Redis分布式缓存,此文件为相关原理
  • 分布式缓存服务器的源代码,memcacaed-1.4.25,了解它,对大家以后编写一些服务器的缓存,自己的实现,会有所启发和帮助
  • 分布式缓存 redis 分布式缓存
  • 分布式缓存常见知识点(高并发、高可用、通信模型、主从架构、主备切换、哨兵、redis cluster集群、持久化、过期策略、缓存雪崩、穿透等等)
  • 主要讲解下encache的原理、分布式缓存集群环境配置、与在spring中的使用
  • 学习后可以掌握: 1.掌握redis/Memcached底层实现与高级特性 2.剖析缓存在秒杀、计数器、Feed流中的应用 3.掌握大规模穿透、雪崩等经典问题的解决方案 4.揭秘新浪微博的百万级QPS技术核心
  • 《高并发网站与分布式缓存Redis与开发实战》3.0.pdf c#
  • Redis实现分布式缓存

    千次阅读 2022-03-27 22:37:49
    分布式缓存分布式缓存中常常使用redis的技术实现方案。 单节点redis存在的问题及解决方案 数据丢失问题 解决方案:实现redis持久化 并发能力问题 解决方案:搭建主从集群,实现读写分离 故障恢复问题 ...

    分布式缓存

    在分布式缓存中常常使用redis的技术实现方案。

    单节点redis存在的问题及解决方案

    • 数据丢失问题
      • 解决方案:实现redis持久化
    • 并发能力问题
      • 解决方案:搭建主从集群,实现读写分离
    • 故障恢复问题
      • 解决方案:利用Redis哨兵,实现健康检测和自动恢复
    • 存储能力问题
      • 解决方案:搭建分片集群,利用插槽机制实现动态扩容

    Redis持久化

    RDB持久化

    RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。

    快照文件称为RDB文件,默认是保存在当前运行目录

    sava #由Redis主进程来执行RDB,会阻塞所有命令
    
    bgsava #由子进程来执行RDB
    

    bgsava开始时会fork主进程的到子进程,子进程共享主进程的内存数据。完成fork后读取内存数据并写入RDB文件

    fork采用copy-on-write技术:

    • 当主进程执行读操作时,访问共享内存;

    • 当主进程执行写操作时,则会拷贝一份数据,执行写操作

    注意:关闭Redis时会自动执行一次RDB

    那么是否只有关闭Redis时才会执行RDB呢?当然不是!

    Redis内部有触发RDB机制,可以在redis.conf文件中找到格式如下:

    # sava 时间(单位秒) key修改次数
    sava 900 1 #900秒内如果有一个key被修改则执行bgsava
    

    注意:如果是 sava ""则关闭RDB

    RDB的其他配置也可以在redis.conf文件中设置

    #是否压缩,建议不开启,压缩也会消耗cpu,磁盘资源相对便宜
    rdbcompression yes
    	
    #RDB文件名称
    dbfilename dump.rdb
    
    #文件保存的目录
    dir ./
    

    建议:生产环境下的redis不要修改避免造成数据丢失

    AOF持久化

    AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。

    AOF默认是关闭的,需要修改redis.conf配置文件来开启AOF:

    #是否开启AOF功能,默认是no
    appendsync yes
    
    #AOF文件名称
    appendfilename "appendonly.aof"
    

    AOF的命令记录的频率也可以通过redis.conf文件来配

    #表示每执行一次写命令,立即记录到AOF文件
    appendsync always
    
    #写命令执行完先放入缓冲区,然后每隔1秒将缓冲区数据写入到AOF文件,是默认方案
    appendsync everysec
    
    #写命令执行完先放入缓冲区,由系统决定何时将缓冲区内容写回磁盘
    appendsync no
    
    配置项刷盘时机优点缺点
    Always同步刷盘可靠性高,几乎不丢数据性能影响大
    everysec没秒刷盘性能适中最多丢失1秒数据
    no操作系统控制性能最好可靠性差,可能丢失大量数据

    因为AOF是记录命令,所以AOF文件会比RDB文件大很多。而且AOF会记录对同一个key的多次写操作,但只有最后一次写操作才有意义。通过执行bgrewriteaof命令,可以让AOF文件执行重写功能,用最少的命令达到相同效果。

    Redis也会在出发阈值时自动去重写AOF文件,阈值也可以在Redis.conf中配置

    #AOF文件比上次文件增长超过多少百分比则触发重写
    auto-aof-rewrite-percentage 100
    #AOF文件体积超过多少就触发重写
    auto-aof-rewrite-min-size 64mb
    

    RDB与AOF对比

    RDB和AOF各有自己的优缺点,如果对数据的安全要求比较高就使用AOF反之则使用RDB,在实际的开发中往往会二者结合使用。

    RDBAOF
    持久化方式定时对整个内存做快照记录每一次写命令
    数据完整性不完整,两次备份之间会丢失相对完整,取决于刷盘策略
    文件大小会有压缩,文件体积较小记录命令,文件体积很大
    宕机恢复速度很快
    数据恢复优先级低,因为数据完整性不如AOF高,因为数据完整性更高
    系统资源占用高,大量cpu和内存消耗低,主要是磁盘IO资源,但AOF重写时会占用大量CPU和内存资源
    使用场景可以容忍数分钟的数据丢失,追求更快的启动速度对数据安全性要求高

    Redis主从

    单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离

    数据同步

    master如何判断slave是不是第一次来同步数据?这里会用到两个概念:

    • Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
    • offset:偏移量,随着记录在rep_baklog中的数据增多而逐渐增大。slave完成同时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新,因此slave做数据同步,必须向master声明自己的replication id和offset

    全量同步步骤

    • slave节点请求增量同步
    • master节点判断replid,发现不一致拒绝增量同步
    • master将完整内存数据生成RDB,发送RDB到slave
    • slave清空本地数据,加载master的RDB
    • master将RDB期间的命令记录到rep_baklog,并持续将log中的命令发送给slave
    • slave执行接收到命令,保持与master之间的同步

    增量同步步骤

    • slave节点携带replid和offset请求增量同步
    • master节点判断replid和offset,replid一致,offset落后于maset回复continue
    • maset去repl_baklog中获取offset后的数据发送给slave
    • slave执行命令

    注意:repl_baklog大小有上限,写满后会覆盖最早的数据。如果slave断开时间过久,导致尚未备份的数据被覆盖,则无法基于log做增量同步,只能再次全量同步

    Redis主从数据同步优化

    • 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘io(会比较吃网络性能,带宽高的情况下使用)
    • Redis单节点上的内存占用不要太大,减少RDB导致的过多内存损耗
    • 适当提高repl-baklog的大小,发现slave宕机尽快实现故障恢复,尽可能避免全量同步
    • 限制一个master上的slave节点数量,如果slave节点实在太多可以采用主-从-主链式结构,减少master压力

    Redis哨兵

    在主从结构中slave宕机之后可以从master节点恢复数据,那么master节点宕机之后呢?

    这里就需要使用redis的哨兵来进行故障恢复,节点选举,服务监控

    • 监控:Sentinel会不断检查你的master和slave是否按预期在工作
    • 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
    • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis客户端

    Sentinel基于心跳机制检测服务状态,每隔1秒向集群的每个实例发送ping命令:

    • 主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线
    • 客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。

    quorun的值最好超过sentinel实例数量的一半

    选举原则

    一单发现master故障,sentinel需要在salve中选择一个新的master:

    • 首先判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds*10)则会排除该slave节点
    • 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永远不参与选举
    • 如果slave-proity值一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
    • 最后判断slave节点的运行id,越小优先级越高

    故障迁移

    当选中了其中一个slave为新的master后(例如slave1),故障迁移的步骤如下:

    • sentinel给备选的slave1节点发送slaveof no one命令,让该节点成为master
    • sentinel给所有其它slave发送slaveof 新master IP 命令,让这些slave成为新master的从节点,开始从新的master上同步数据
    • 最后,sentinel将故障节点标记为slave,当故障节点恢复后自动成为新的slave节点

    RedisTemplate集成哨兵模式

    依赖

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    

    配置

    spring:
      redis:
        sentinel:
          master: mymaster
          nodes:
            - 127.0.0.1:27001
            - 127.0.0.1:27002
            - 127.0.0.1:27003
    
    

    配置主从读写分离

    @Bean
        public LettuceClientConfigurationBuilderCustomizer configurationBuilderCustomizer(){
            return configBuilder -> configBuilder.readFrom (ReadFrom.REPLICA_PREFERRED);
        }
    

    ReadFrom读取策略

    •MASTER:从主节点读取

    •MASTER_PREFERRED:优先从master节点读取,master不可用才读取replica

    •REPLICA:从slave(replica)节点读取

    •REPLICA _PREFERRED:优先从slave(replica)节点读取,所有的slave都不可用才读取master

    分片集群

    主从和哨兵可以解决高可用,高并发读的问题,但是依然有两个问题没有解决:

    • 海量数据存储问题
    • 高并发写问题

    使用分片集群可以解决上述问题

    • 集群中有多个master,每个master保存不同数据
    • 每个master都可以有多个slave节点
    • master之间通过ping监测彼此健康状态
    • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

    散列插槽

    Redis会把每一个master节点映射到0~16383共16384个插槽(hash slot)上

    数据key与插槽绑定。redis会根据kety有效部分计算插槽值,分两种情况:

    key中包含“{}”,且"{}“中至少包含一个字符,”{}"中的部分是有效部分

    key中不包含“{}”,整个key都是有效部分

    例如:{typeId}typeName typeId是计算插槽值有效部分,key为typeIdtypeNema

    展开全文
  • 这一章想讲一下Spark的缓存是如何实现的。这个persist方法是在RDD里面的,所以我们直接打开RDD这个类。它调用SparkContext去缓存这个RDD,追杀下去。它居然是用一个HashMap来存的,具体看这个map的类型是...
  • 分布式缓存详解

    千次阅读 2021-08-25 21:23:54
    今天无聊来撩一下分布式缓存,希望你们喜欢~ 编者荐语: 此篇文章对于分布式缓存讲解的非常透彻! 目录前言一. 常用的两种缓存技术的服务端特点1. Memcache服务端2. Redis服务端二. 缓存结构化选型三. Redis构造大...

    今天无聊来撩一下分布式缓存,希望你们喜欢~

    编者荐语:

    此篇文章对于分布式缓存讲解的非常透彻!

    图片

    前言

    目前工作中用到的分布式缓存技术有redismemcached两种,缓存的目的是为了在高并发系统中有效降低DB的压力,但是在使用的时候可能会因为缓存结构设计不当造成一些问题,这里会把可能遇到的坑整理出来,方便日后查找。

    一. 常用的两种缓存技术的服务端特点

    1. Memcache服务端

    Memcache(下面简称mc)服务端是没有集群概念的,所有的存储分发全部交由mc client去做,我这里使用的是xmemcached,这个客户端支持多种哈希策略,默认使用key与实例数取模来进行简单的数据分片。

    这种分片方式会导致一个问题,那就是新增或者减少节点后会在一瞬间导致大量key失效,最终导致缓存雪崩的发生,给DB带来巨大压力,所以我们的mc client启用了xmemcached的一致性哈希算法来进行数据分片:

    XMemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses(servers));
    builder.setOpTimeout(opTimeout);
    builder.setConnectTimeout(connectTimeout);
    builder.setTranscoder(transcoder);
    builder.setConnectionPoolSize(connectPoolSize);
    builder.setKeyProvider(keyProvider);
    builder.setSessionLocator(new KetamaMemcachedSessionLocator()); //启用ketama一致性哈希算法进行数据分片
    

    根据一致性哈希算法的特性,在新增或减少mc的节点只会影响较少一部分的数据。但这种模式下也意味着分配不均匀,新增的节点可能并不能及时达到均摊数据的效果,不过mc采用了虚拟节点的方式来优化原始一致性哈希算法(由ketama算法控制实现),实现了新增物理节点后也可以均摊数据的能力。

    最后,mc服务端是多线程处理模式,mc一个value最大只能存储1M的数据,所有的k-v过期后不会自动移除,而是下次访问时与当前时间做对比,过期时间小于当前时间则删除,如果一个k-v产生后就没有再次访问了,那么数据将会一直存在在内存中,直到触发LRU

    2. Redis服务端

    redis服务端有集群模式,key的路由交由redis服务端做处理,除此之外redis有主从配置以达到服务高可用。

    redis服务端是单线程处理模式,这意味着如果有一个指令导致redis处理过慢,会阻塞其他指令的响应,所以redis禁止在生产环境使用重量级操作(例如keys,再例如缓存较大的值导致传输过慢)

    redis服务端并没有采用一致性哈希来做数据分片,而是采用了哈希槽的概念来做数据分片,一个redis cluster整体拥有16384个哈希槽(slot),这些哈希槽按照编号区间的不同,分布在不同节点上,然后一个key进来,通过内部哈希算法(CRC16(key))计算出槽位置;

    然后将数据存放进对应的哈希槽对应的空间,redis在新增或者减少节点时,其实就是对这些哈希槽进行重新分配,以新增节点为例,新增节点意味着原先节点上的哈希槽区间会相对缩小,被减去的那些哈希槽里的数据将会顺延至下一个对应节点,这个过程由redis服务端协调完成,过程如下:

    图片图1

    迁移过程是以槽为单位,将槽内的key按批次进行迁移的(migrate)。

    二. 缓存结构化选型

    mc提供简单的k-v存储,value最大可以存储1M的数据,多线程处理模式,不会出现因为某次处理慢而导致其他请求排队等待的情况,适合存储数据的文本信息。

    redis提供丰富的数据结构,服务端是单线程处理模式,虽然处理速度很快,但是如果有一次查询出现瓶颈,那么后续的操作将被阻塞,所以相比k-v这种可能因为数据过大而导致网络交互产生瓶颈的结构来说,它更适合处理一些数据结构的查询、排序、分页等操作,这些操作往往复杂度不高,且耗时极短,因此不太可能会阻塞redis的处理。

    使用这两种缓存服务来构建我们的缓存数据,目前提倡所有数据按照标志性字段(例如id)组成自己的信息缓存存储,这个一般由mc的k-v结构来完成存储。

    而redis提供了很多好用的数据结构,一般构建结构化的缓存数据都使用redis的数据结构来保存数据的基本结构,然后组装数据时根据redis里缓存的标志性字段去mc里查询具体数据,例如一个排行榜接口的获取:

    图片图2

    上图redis提供排行榜的结构存储,排行榜里存储的是idscore,通过redis可以获取到结构内所有信息的id,然后利用获得的id可以从mc中查出详细信息,redis在这个过程负责分页、排序,mc则负责存储详细信息。

    上面是比较合适的缓存做法,建议每条数据都有一个自己的基本缓存数据,这样便于管理,而不是把一个接口的巨大结构完全缓存到mc或者redis里,这样划分太粗,日积月累下来每个接口或者巨大方法都有一个缓存,key会越来越多,越来越杂

    三. Redis构造大索引回源问题

    Redis如果做缓存使用,始终会有过期时间存在,如果到了过期时间,使用redis构建的索引将会消失,这个时候回源,如果存在大批量的数据需要构建redis索引,就会存在回源方法过慢的问题,这里以某个评论系统为例;

    评论系统采用有序集合作为评论列表的索引,存储的是评论id,用于排序的score值则按照排序维度拆分,比如发布时间、点赞数等,这也意味着一个资源下的评论列表根据排序维度不同存在着多个redis索引列表,而具体评论内容存mc,正常情况下结构如下:

    图片图3

    上面是正常触发一个资源的评论区,每次触发读缓存,都会顺带延长一次缓存的过期时间,这样可以保证较热的内容不会轻易过期,但是如果一个评论区时间过长没人访问过,redis索引就会过期,如果一个评论区有数万条评论数据,长时间没人访问,突然有人过去考古,那么在回源构建redis索引时会很缓慢,如果没有控制措施,还会造成下面缓存穿透的问题,从而导致这种重量级操作反复被多个线程执行,对DB造成巨大压力。

    对于上面这种回源构建索引缓慢的问题,处理方式可以是下面这样:

    图片图4

    相比直接执行回源方法,这种通过消息队列构造redis索引的方法更加适合,首先仅构建单页或者前面几页的索引数据,然后通过队列通知job(这里可以理解为消费者)进行完整索引构造,当然,这只适合对一致性要求不高的场景。

    四. 一致性问题

    一般情况下缓存内的数据要和数据库源数据保持一致性,这就涉及到更新DB后主动失效缓存策略(通俗叫法:清缓存),大部分会经过如下过程:

    图片图5

    假如现在有两个服务,服务A和服务B,现在假设服务A会触发某个数据的写操作,而服务B则是只读程序,数据被缓存在一个Cache服务内,现在假如服务A更新了一次数据库,那么结合上图得出以下流程:

    1. 服务A触发更新数据库的操作
    2. 更新完成后删除数据对应的缓存key
    3. 只读服务(服务B)读取缓存时发现缓存miss
    4. 服务B读取数据库源信息
    5. 写入缓存并返回对应信息

    这个过程乍一看是没什么问题的,但是往往多线程运转的程序会导致意想不到的结果,现在来想象下服务A和服务B被多个线程运行着,这个时候重复上述过程,就会存在一致性问题。

    1. 并发读写导致的一致性问题

    图片图6

    1. 运行着服务A的线程1首先修改数据,然后删除缓存
    2. 运行着服务B的线程3读缓存时发现缓存miss,开始读取DB中的源数据,需要注意的是这次读出来的数据是线程1修改后的那份
    3. 这个时候运行着服务A的线程2上线,开始修改数据库,同样的,删除缓存,需要注意的是,这次删除的其实是一个空缓存,没有意义,因为本来线程3那边还没有回源完成
    4. 运行着服务B的线程3将读到的由线程1写的那份数据回写进Cache

    上述过程完成后,最终结果就是DB里保存的最终数据是线程2写进去的那份,而Cache经过线程3的回源后保存的却是线程1写的那份数据,不一致问题出现。

    2. 主从同步延时导致的一致性问题

    这种情况要稍微修改下程序的流程图,多出一个从库:

    图片图7

    现在读操作走从库,这个时候如果在主库写操作删除缓存后,由于主从同步有可能稍微慢于回源流程触发,回源时读取从库仍然会读到老数据。

    3. 缓存污染导致的一致性问题

    每次做新需求时更新了原有的缓存结构,或去除几个属性,或新增几个属性,假如新需求是给某个缓存对象O新增一个属性B,如果新逻辑已经在预发或者处于灰度中,就会出现生产环境回源后的缓存数据没有B属性的情况,而预发和灰度时,新逻辑需要使用B属性,就会导致生产&预发缓存污染。过程大致如下:

    图片图8

    五. 如何应对缓存一致性问题?

    缓存一致性问题大致分为以下几个解决方案,下面一一介绍。

    1. binlog+消息队列+消费者del cache

    图片图9

    上图是现在常用的清缓存策略,每次表发生变动,通过mysql产生的binlog去给消息队列发送变动消息,这里监听DB变动的服务由canal提供,canal可以简单理解成一个实现了mysql通信协议的从库,通过mysql主从配置完成binlog同步,且它只接收binlog,通过这种机制,就可以很自然的监听数据库表数据变动了,可以保证每次数据库发生的变动,都会被顺序发往消费者去清除对应的缓存key

    2. 从库binlog+消息队列+消费者del cache

    上面的过程能保证写库时清缓存的顺序问题,看似并没有什么问题,但是生产环境往往存在主从分离的情况,也就是说上面的图中如果回源时读的是从库,那上面的过程仍然是存在一致性问题的:

    图片图10

    从库延迟导致的脏读问题,如何解决这类问题呢?

    只需要将canal监听的数据库设置成从库即可,保证在canal推送过来消息时,所有的从库和主库完全一致,不过这只针对一主一从的情况,如果一主多从,且回源读取的从库有多个,那么上述也是存在一定的风险的(一主多从需要订阅每个从节点的binlog,找出最后发过来的那个节点,然后清缓存,确保所有的从节点全部和主节点一致)。

    不过,正常情况下,从库binlog的同步速度都要比canal发消息快,因为canal要接收binlog,然后组装数据变动实体(这一步是有额外开销的),然后通过消息队列推送给各消费者(这一步也是有开销的),所以即便是订阅的master库的表变更,出问题的概率也极小。

    3. 更新后key升级

    针对上面的一致性问题(缓存污染),修改某个缓存结构可能导致在预发或者灰度中状态时和实际生产环境的缓存相互污染,这个时候建议每次更新结构时都进行一次key升级(比如在原有的key名称基础上加上_v2的后缀)。

    ⚡⚡⚡binlog是否真的是准确无误的呢?⚡⚡⚡

    图片图11

    并不是,比如上面的情况:

    1. 首先线程1走到服务A,写DB,发binlog删除缓存
    2. 然后线程3运行的服务B这时cache miss,然后读取DB回源(这时读到的数据是线程1写入的那份数据)
    3. 此时线程2再次触发服务ADB,同样发送binlog删除缓存
    4. 最后线程3把读到的数据写入cache,最终导致DB里存储的是线程2写入的数据,但是cache里存储的却是线程1写入的数据,不一致达成

    这种情况比较难以触发,因为极少会出现线程3那里写cache的动作会晚于第二次binlog发送的,除非在回源时做了别的带有阻塞性质的操作;

    所以根据现有的策略,没有特别完美的解决方案,只能尽可能保证一致性,但由于实际生产环境,处于多线程并发读写的环境,即便有binlog做最终的保证,也不能保证最后回源方法写缓存那里的顺序性。除非回源全部交由binlog消费者来做,不过这本就不太现实,这样等于说服务B没有回源方法了。

    针对这个问题,出现概率最大的就是那种写并发概率很大的情况,这个时候伴随而来的还有命中率问题。

    六. 命中率问题

    通过前面的流程,抛开特殊因素,已经解决了一致性的问题,但随着清缓存而来的另一个问题就是命中率问题。

    比如一个数据变更过于频繁,以至于产生过多的binlog消息,这个时候每次都会触发消费者的清缓存操作,这样的话缓存的命中率会瞬间下降,导致大部分用户访问直接访问DB;

    而且这种频繁变更的数据还会加大问题出现的概率,所以针对这种频繁变更的数据,不再删除缓存key,而是直接在binlog消费者那里直接回源更新缓存,这样即便表频繁变更,用户访问时每次都是消费者更新好的那份缓存数据,只是这时候消费者要严格按照消息顺序来处理;

    否则也会有写脏的危险,比如开两个线程同时消费binlog消息,线程1接收到了第一次数据变更的binlog,而线程2接收到了第二次数据变更的binlog,这时线程1读出数据(旧数据),线程2读出数据(新数据)更新缓存,然后线程1再执行更新,这时缓存又会被写脏;

    所以为了保证消费顺序,必须是单线程处理,如果想要启用多线程均摊压力,可以利用keyid等标识性字段做任务分组,这样同一个idbinlog消息始终会被同一个线程执行。

    七. 缓存穿透

    1. 什么是缓存穿透?

    正常情况下用户请求一个数据时会携带标记性的参数(比如id),而我们的缓存key则会以这些标记性的参数来划分不同的cache value,然后我们根据这些参数去查缓存,查到就返回,否则回源,然后写入cache服务后返回。

    这个过程看起来也没什么问题,但是某些情况下,根据带进来的参数,在数据库里并不能找到对应的信息,这个时候每次带有这种参数的请求,都会走到数据库回源,这种现象叫做缓存穿透,比较典型的出现这种问题的情况有:

    1. 恶意攻击或者爬虫,携带数据库里本就不存在的数据做参数回源
    2. 公司内部别的业务方调用我方的接口时,由于沟通不当或其他原因导致的参数大量误传
    3. 客户端bug导致的参数大量误传

    2. 如何解决缓存穿透问题?

    目前我们提倡的做法是回源查不到信息时直接缓存空数据(注意:空数据缓存的过期时间要尽可能小,防止无意义内容过多占用Cache内存),这样即便是有参数误传、恶意攻击等情况,也不会每次都打进DB。

    但是目前这种做法仍然存在被攻击的风险,如果恶意攻击时携带少量参数还好,这样不存在的空数据缓存仅仅会占用少量内存,但是如果攻击者使用大量穿透攻击,携带的参数千奇百怪,这样就会产生大量无意义的空对象缓存,使得我们的缓存服务器内存暴增。

    这个时候就需要服务端来进行简单的控制:按照业务内自己的估算,合理的id大致在什么范围内,比如按照用户id做标记的缓存,就直接在获取缓存前判断所传用户id参数是否超过了某个阈值,超过直接返回空。(比如用户总量才几十万或者上百万,结果用户id传过来个几千万甚至几亿明显不合理的情况)

    八. 缓存击穿

    1. 什么是缓存击穿?

    缓存击穿是指在一个key失效后,大量请求打进回源方法,多线程并发回源的问题。

    这种情况在少量访问时不能算作一个问题,但是当一个热点key失效后,就会发生回源时涌进过多流量,全部打在DB上,这样会导致DB在这一时刻压力剧增。

    2. 如何解决缓存击穿?

    1. 回源方法内追加互斥锁:这个可以避免多次回源,但是n台实例群模式下,仍然会存在实例并发回源的情况,这个量级相比之前大量打进,已经大量降低了。
    2. 回源方法内追加分布式锁:这个可以完全避免上面多实例下并发回源的情况,但是缺点也很明显,那就是又引入了一个新的服务,这意味着发生异常的风险会加大。

    九. 缓存雪崩

    1. 什么是缓存雪崩?

    缓存雪崩是指缓存数据某一时刻出现大量失效的情况,所有请求全部打进DB,导致短期内DB负载暴增的问题,一般来说造成缓存雪崩有以下几种情况:

    1. 缓存服务扩缩容:这个是由缓存的数据分片策略的而导致的,如果采用简单的取模运算进行数据分片,那么服务端扩缩容就会导致雪崩的发生。
    2. 缓存服务宕机:某一时刻缓存服务器出现大量宕机的情况,导致缓存服务不可用,根据现有的实现,是直接打到DB上的。

    2. 如何避免雪崩的发生?

    1. 缓存服务端的高可用配置:上面mcredis的分片策略已经说过,所以扩缩容带来的雪崩几率很小,其次redis服务实现了高可用配置:启用cluster模式,一主一从配置。由于对一致性哈希算法的优化,mc宕机、扩缩容对整体影响不大,所以缓存服务器服务端本身目前是可以保证良好的可用性的,尽可能的避免了雪崩的发生(除非大规模宕机,概率很小)。
    2. 数据分片策略调整:调整缓存服务器的分片策略,比如上面第一部分所讲的,给mc开启一致性哈希算法的分片策略,防止缓存服务端扩缩容后缓存数据大量不可用。
    3. 回源限流:如果缓存服务真的挂掉了,请求全打在DB上,以至于超出了DB所能承受之重,这个时候建议回源时进行整体限流,被限到的请求紫自动走降级逻辑,或者直接报错。

    十. 热key问题

    1. 什么是热key问题?

    了解了缓存服务端的实现,可以知道某一个确定的key始终会落到某一台服务器上,如果某个key在生产环境被大量访问,就导致了某个缓存服务节点流量暴增,等访问超出单节点负载,就可能会出现单点故障,单点故障后转移该key的数据到其他节点,单点问题依旧存在,则可能继续会让被转移到的节点也出现故障,最终影响整个缓存服务集群。

    2. 如何解决热key问题?

    1. 多缓存副本:预先感知到发生热点访问的key,生成多个副本key,这样可以保证热点key会被多个缓存服务器持有,然后回源方法公用一个,请求时按照一定的算法随机访问某个副本key。

    图片图12

    1. 本地缓存:针对热点key外面包一层短存活期的本地缓存,用于缓冲热点服务器的压力。

    总结

    障,最终影响整个缓存服务集群。

    2. 如何解决热key问题?

    1. 多缓存副本:预先感知到发生热点访问的key,生成多个副本key,这样可以保证热点key会被多个缓存服务器持有,然后回源方法公用一个,请求时按照一定的算法随机访问某个副本key。

    [外链图片转存中…(img-qKciXBOW-1629897803726)]图12

    1. 本地缓存:针对热点key外面包一层短存活期的本地缓存,用于缓冲热点服务器的压力。

    总结

    如果觉得文章不错,点个赞分享转发下,谢谢支持!!!

    展开全文
  • 9分布式缓存架构.xmind

    2020-04-23 14:39:49
    本xmind从各方面分析分布式缓存的架构,从客户端缓存,网络缓存,服务器缓存出发来解决实际项目开发的问题,并提供了多种详细解决方案。
  • 分布式缓存实践示例

    千次阅读 多人点赞 2022-03-26 20:55:24
    本文介绍了分布式缓存系统Redis在Spring Boot中的使用,并演示了如何通过Lua脚本强化Redis的功能。

    博客主页:https://tomcat.blog.csdn.net
    博主昵称:农民工老王
    主要领域:Java、Linux、K8S
    期待大家的关注💖点赞👍收藏⭐留言💬
    家乡

    《浅谈缓存的理论与实践》这篇文章中,我们以 Guava 的 LoadingCache 为例,介绍了堆内缓存的特点以及一些注意事项。同时,还了解了缓存使用的场景,这对分布式缓存来说,同样适用。

    那什么叫分布式缓存呢?它其实是一种集中管理的思想。如果我们的服务有多个节点,堆内缓存在每个节点上都会有一份;而分布式缓存,所有的节点,共用一份缓存,既节约了空间,又减少了管理成本。

    在分布式缓存领域,使用最多的就是 Redis。Redis 支持非常丰富的数据类型,包括字符串(string)、列表(list)、集合(set)、有序集合(zset)、哈希表(hash)等常用的数据结构。当然,它也支持一些其他的比如位图(bitmap)一类的数据结构。

    说到 Redis,就不得不提一下另外一个分布式缓存 Memcached(以下简称 MC)。MC 现在已经很少用了,但面试的时候经常会问到它们之间的区别,这里简单罗列一下:
    在这里插入图片描述

    SpringBoot 如何使用 Redis

    Redis 在互联网中,几乎是标配。我们接下来,先简单看一下 Redis 在 Spring 中是如何使用的,然后,再介绍一下在秒杀业务中,Redis是如何帮助我们承接瞬时流量的。

    使用 SpringBoot 可以很容易地对 Redis 进行操作(完整代码见仓库)。Java 的 Redis的客户端,常用的有三个:jedis、redisson 和 lettuce,Spring 默认使用的是 lettuce。

    lettuce 是使用 netty 开发的,操作是异步的,性能比常用的 jedis 要高;redisson 也是异步的,但它对常用的业务操作进行了封装,适合书写有业务含义的代码。

    通过加入下面的 jar 包即可方便地使用 Redis。

    <dependency> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-data-redis</artifactId> 
    </dependency>
    

    上面这种方式,我们主要是使用 RedisTemplate 这个类。它针对不同的数据类型,抽象了相应的方法组。
    在这里插入图片描述
    另外一种方式,就是使用 Spring 抽象的缓存包 spring-cache。它使用注解,采用 AOP的方式,对 Cache 层进行了抽象,可以在各种堆内缓存框架和分布式框架之间进行切换。这是它的 maven 坐标:

    <dependency> 
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-cache</artifactId> 
    </dependency>
    

    与 spring-cache 类似的,还有阿里的 jetcache,都是比较好用的。

    使用 spring-cache 有三个步骤:

    • 在启动类上加入 @EnableCaching 注解;

    • 使用 CacheManager 初始化要使用的缓存框架,使用 @CacheConfig 注解注入要使用的资源;

    • 使用 @Cacheable 等注解对资源进行缓存。

    我们这里使用的是 RedisCacheManager,由于现在只有这一个初始化实例,第二个步骤是可以省略的。

    针对缓存操作的注解,有三个:

    • @Cacheable 表示如果缓存系统里没有这个数值,就将方法的返回值缓存起来;

    • @CachePut 表示每次执行该方法,都把返回值缓存起来;

    • @CacheEvict 表示执行方法的时候,清除某些缓存值。

    Redis使用示例:秒杀

    场景介绍

    对于秒杀系统来说,仅仅使用这三个注解是有局限性的,需要使用更加底层的 API,比如 RedisTemplate,来完成逻辑开发,下面就来介绍一些比较重要的功能。

    秒杀,是对正常业务流程的考验。因为它会产生突发流量,平常一天的请求,可能就集中在几秒内就要完成。比如,京东的某些抢购,可能库存就几百个,但是瞬时进入的流量可能是几十上百万。
    在这里插入图片描述
    如果参与秒杀的人,等待很长时间,体验就非常差,想象一下拥堵的高速公路收费站,就能理解秒杀者的心情。同时,被秒杀的资源会成为热点,发生并发争抢的后果。比如 12306 的抢票,如果单纯使用数据库来接受这些请求,就会产生严重的锁冲突,这也是秒杀业务难的地方。

    大家可以回忆一下浅谈缓存的理论与实践这篇文章的内容,此时,秒杀前端需求与数据库之间的速度是严重不匹配的,而且秒杀的资源是热点资源。这种场景下,采用缓存是非常合适的。

    处理秒杀业务有三个绝招:

    • 第一,选择速度最快的内存作为数据写入;

    • 第二,使用异步处理代替同步请求;

    • 第三,使用分布式横向扩展。

    下面,我们就来看一下 Redis 是如何助力秒杀的。

    实现

    一个秒杀系统是非常复杂的,一般来说,秒杀可以分为一下三个阶段:

    • 准备阶段,会提前载入一些必需的数据到缓存中,并提前预热业务数据,用户会不断刷新页面,来查看秒杀是否开始;

    • 抢购阶段,就是我们通常说的秒杀,会产生瞬时的高并发流量,对资源进行集中操作;

    • 结束清算,主要完成数据的一致性,处理一些异常情况和回仓操作。
      在这里插入图片描述
      下面,我将介绍一下最重要的秒杀阶段。

    我们可以设计一个 Hash 数据结构,来支持库存的扣减。

    seckill:goods:${goodsId}{ 
        total: 100, 
        start: 0, 
        alloc:0 
    }
    

    在这个 Hash 数据结构中,有以下三个重要部分:

    • total 是一个静态值,表示要秒杀商品的数量,在秒杀开始前,会将这个数值载入到缓存中。

    • start 是一个布尔值。秒杀开始前的值为 0;通过后台或者定时,将这个值改为 1,则表示秒杀开始。

    • 此时,alloc 将会记录已经被秒杀的商品数量,直到它的值达到 total 的上限。

    static final String goodsId = "seckill:goods:%s";
    
    String getKey(String id) {
        return String.format(goodsId, id);
    }
    
    public void prepare(String id, int total) {
        String key = getKey(id);
        if (redisTemplate.hasKey(key)) {
            return;
        }
        Map<String, String> goods = new HashMap<>();
        goods.put("total", String.valueOf(total));
        goods.put("start", "0");
        goods.put("alloc", "0");
        redisTemplate.opsForHash().putAll(key, goods);
    }
    

    秒杀的时候,首先需要判断库存,才能够对库存进行锁定。这两步动作并不是原子的,在分布式环境下,多台机器同时对 Redis 进行操作,就会发生同步问题。

    为了解决同步问题,一种方式就是使用 Lua 脚本,把这些操作封装起来,这样就能保证原子性;另外一种方式就是使用分布式锁,分布式锁我们将在后续的文章中介绍。

    下面是一个调试好的 Lua 脚本,可以看到一些关键的比较动作,和 HINCRBY 命令,能够成为一个原子操作。

    local falseRet = "0"
    local n = tonumber(ARGV[1])
    local key = KEYS[1]
    local goodsInfo = redis.call("HMGET",key,"total","alloc")
    local total = tonumber(goodsInfo[1])
    local alloc = tonumber(goodsInfo[2])
    if not total then
        return falseRet
    end
    if total >= alloc + n  then
        local ret = redis.call("HINCRBY",key,"alloc",n)
        return tostring(ret)
    end
    return falseRet
    

    对应的秒杀代码如下,由于我们使用的是 String 的序列化方式,所以会把库存的扣减数量先转化为字符串,然后再调用 Lua 脚本。

    public int secKill(String id, int number) {
        String key = getKey(id);
        Object alloc =  redisTemplate.execute(script, Arrays.asList(key), String.valueOf(number));
        return Integer.valueOf(alloc.toString());
    }
    

    执行仓库里的 testSeckill 方法。启动 1000 个线程对 100 个资源进行模拟秒杀,可以看到生成了 100 条记录,同时其他的线程返回的是 0,表示没有秒杀到。
    在这里插入图片描述

    完整的java代码如下:

    package cn.wja.cache;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.core.io.ClassPathResource;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.data.redis.core.script.DefaultRedisScript;
    import org.springframework.scripting.support.ResourceScriptSource;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class SeckillRedisTest {
    
        DefaultRedisScript script;
    
        @Before
        public void init() {
            script = new DefaultRedisScript();
            script.setScriptSource(new ResourceScriptSource(
                    new ClassPathResource("seckill.lua")
            ));
            script.setResultType(Integer.class);
        }
    
        @Autowired
        StringRedisTemplate redisTemplate;
    
        static final String goodsId = "seckill:goods:%s";
    
        String getKey(String id) {
            return String.format(goodsId, id);
        }
    
        public void prepare(String id, int total) {
            String key = getKey(id);
            if (redisTemplate.hasKey(key)) {
                return;
            }
            Map<String, String> goods = new HashMap<>();
            goods.put("total", String.valueOf(total));
            goods.put("start", "0");
            goods.put("alloc", "0");
            redisTemplate.opsForHash().putAll(key, goods);
        }
    
        public int secKill(String id, int number) {
            String key = getKey(id);
            Object alloc =  redisTemplate.execute(script, Arrays.asList(key), String.valueOf(number));
            return Integer.parseInt(alloc.toString());
        }
    
        @Test
        public void testSeckill() {
            String id = "114";
            prepare(id, 100);
            ExecutorService executor = Executors.newCachedThreadPool();
    
            for (int i = 0; i < 1000; i++) {
                executor.submit(() -> {
                    int alloc = secKill(id, 1);
                    System.out.println("count==================" + alloc);
                });
            }
    
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new IllegalStateException();
            }
    
            executor.shutdown();
        }
    }
    
    

    小结

    本文和浅谈缓存的理论与实践这篇文章,都是围绕着缓存展开的,它们之间有很多知识点也比较相似。对于分布式缓存来说,Redis 是现在使用最广泛的。我们先简单介绍了一下它和 Memcached 的一些区别,介绍了 SpringBoot 项目中 Redis 的使用方式,然后以秒杀场景为主,学习了库存扣减这一个核心功能的 Lua 代码。这段代码主要是把条件判断和扣减命令做成了原子性操作。

    Redis 的 API 使用非常简单,速度也很快,但同时它也引入了很多问题。如果不能解决这些异常场景,那么 Redis 的价值就大打折扣,这也是我下一篇博客要谈的内容。


    如需转载,请注明本文的出处:农民工老王的CSDN博客https://blog.csdn.net/monarch91 。

    展开全文
  • #资源达人分享计划#
  • 分布式缓存解决方案

    千次阅读 2021-08-29 10:59:45
    缓存的概念 Redis缓存的用法 SpringCache的用法 缓存穿透解决方案 缓存雪崩解决方案 缓存的数据一致性解决策略
  • 分布式缓存与本地缓存的区别 转载自:https://ost.51cto.com/posts/1002 缓存的概念: 在服务端中,缓存主要是指将数据库的数据加载到内存中,之后对该数据的访问都在内存中完成,从而减少了对数据库的访问;解决...
  • #资源达人分享计划#
  • 我们是怎么支撑双11万亿流量的—— 阿里分布式缓存(Tair)技术分享_姜志锋@阿里巴巴.pdf
  • Spring分布式缓存

    千次阅读 2021-06-06 18:11:31
    什么是分布式缓存 在实际开发场景中,往往单机应用无法满足当前的需求,需要对项目进行分布式部署,由此每个项目中的缓存都是属于自己独立服务的,并不能共享,其次当某个服务更新了缓存,其他服务并不知道,当用户...
  • redis分布式缓存例子

    2018-06-22 10:47:56
    redis分布式缓存+spring整合及 集群、分片等配置使用例子

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 390,621
精华内容 156,248
关键字:

分布式缓存

友情链接: demo8.rar