spring的入门程序详细过程(spring从入门到放弃)

spring的入门程序详细过程(spring从入门到放弃)实现方案的复杂度 QPS 有多高 是否可以解决热点 key 的问题 当数据库中的数据发生变化时 缓存是否可以感知到 缓存数据更新的延迟 缓存过期之后能否及时更新最新数据 并发场景修改缓存时是否会写入旧数据 是否支持选择性的写入缓存 以解决资源 是否会周期性的同步 DB 数据到缓存从而给 DB 带来压力 是否可以快速接入复用 而不与业务耦合 在多副本的场景中 一致性和延迟总有一个要权衡 在主库写成功后

大家好,我是讯享网,很高兴认识大家。



  • 实现方案的复杂度
  • QPS有多高
  • 是否可以解决热点key的问题
  • 当数据库中的数据发生变化时,缓存是否可以感知到
  • 缓存数据更新的延迟
  • 缓存过期之后能否及时更新最新数据
  • 并发场景修改缓存时是否会写入旧数据
  • 是否支持选择性的写入缓存,以解决资源
  • 是否会周期性的同步DB数据到缓存从而给DB带来压力
  • 是否可以快速接入复用,而不与业务耦合

在多副本的场景中,一致性和延迟总有一个要权衡:

  1. 在主库写成功后,主程序可以抛出 MQ 消息来,将异地需要使用到的数据都写在消息中。
  2. 通过缓存来解决,在缓存中写一份给异地读用。
  • 静态数据: 一般不变,类似于字典表
  • 准静态数据:变化频率很低,部门结构设置,全国行政区划数据等
  • 中间状态数据:一些计算的可复用中间数据,变量副本,配置中心的本地副本
  • 热数据:使用频率高
  • 读写比较大:读的频率 >> 写的频率
    这些数据适合于使用缓存的方式访问 广义上来说,为了加速数据处理,让业务更快访问的临时存放冗余数据,都是缓存;
    狭义上,现在我们一般在分布式系统里把缓存到内存的数据叫做内存缓存

内存 ~ 可以看做是 CPU 和 磁盘之间的缓存
CPU与内存的处理速度也不一致,出现 L1&L2 Cache
网络处理,数据库引擎的各种Buffer,都可以看做是缓存
GUI的Double Buffer(双缓冲),是一个经典的性能优化方法
缓存的本质: 系统各级处理速度不匹配,导致利用空间换时间
缓存是提升系统性能的一个简单有效的办法

  1. 启动全量加载 ==> 全局有效,使用简单
  2. 懒加载
  • 同步使用加载
    先看缓存是否有数据,没有的话从数据库读取
    读取的数据,先放到内存,然后返回给调用方
  • 延迟异步加载 从缓存获取数据,不管是否为空直接返回
    策略1异步)如果为空,则发起一个异步加载的线程,负责加载数据
    策略2解耦)异步线程负责维护缓存的数据,定期或根据条件触发更新
  • 读写比 对数据的写操作导致数据变动。意味着维护成本N:1;
  • 命中率 命中缓存意味着缓存存放数据被使用,意味着价值。90%+ 【对于数据一致性、性能、成本的综合衡量,是引入缓存的必须指标】
  • 系统预热导致启动慢
    试想一下,一个系统启动需要预热半个小时。
    导致系统不能做到快速应对故障宕机等问题。
  • 系统内存资源耗尽
    只加入数据,不能清理旧数据。
    旧数据处理不及时,或者不能有效识别无用数据。

一级缓存,session级别。
二级缓存,sessionFactory级别。
MyBatis:

 

讯享网

Hibernate:

讯享网
 

此外,还可以显式清除、统计信息、移除事件的监听器、自动加载等功能。

  1. 基于注解和AOP,使用非常方便
  2. 可以配置Condition和SPEL,非常灵活
  3. 需要注意:绕过Spring的话,注解无效
    核心功能:@Cacheable、@CachePut、@CacheEvict
  1. 在多集群环境中,当集群规模增大,在同步数据的场景下,缓存的读写放大;
  2. 在JVM中会长期占用内存,如果占用的是堆内存,会影响GC;
  3. 缓存数据的调度处理,会抢占资源,进而影响业务线程的执行; 【集中式处理缓存】应运而生
  • 按FIFO或LRU
  • 按固定时间过期
  • 按业务时间加权:例如3+5x

问题:大量并发查询不存在的KEY,导致都直接将压力透传到数据库。
分析:为什么会多次透传呢?不存在一直为空。
需要注意让缓存能够区分KEY不存在和查询到一个空值。
解决办法:

  1. 缓存空值的KEY,这样第一次不存在也会被加载会记录,下次拿到有这个KEY。
  2. Bloom过滤或RoaringBitmap 判断KEY是否存在。
  3. 完全以缓存为准,使用 延迟异步加载 的策略2,这样就不会触发更新。

问题:某个KEY失效的时候,正好有大量并发请求访问这个KEY。
分析:跟前面一个其实很像,属于比较偶然的。
解决办法:

  1. KEY的更新操作添加全局互斥锁。
  2. 完全以缓存为准,使用 延迟异步加载 的策略,这样就不会触发更新。

问题:当某一时刻发生大规模的缓存失效的情况,会有大量的请求进来直接打到数据库,导致数 据库压力过大升值宕机。
分析:一般来说,由于更新策略、或者数据热点、缓存服务宕机等原因,可能会导致缓存数据同 一个时间点大规模不可用,或者都更新。所以,需要我们的更新策略要在时间上合适,数据要均 匀分散,缓存服务器要多台高可用。
解决办法:

  1. 更新策略在时间上做到比较均匀。
  2. 使用的热数据尽量分散到不同的机器上。
  3. 多台机器做主从复制或者多副本,实现高可用。
  4. 实现熔断限流机制,对系统进行负载能力控制。
  • 1.字符串(string)~ 简单来说就是三种:int、string、byte[]
    字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这便 意味着该类型可以接受任何格式的数据,如JPEG图像数据或json对象描述信息等。在 Redis中字符串类型的value最多可以容纳的数据长度是512M。
    set/get/getset/del/exists/append
    incr/decr/incrby/decrby
    注意:
  1. 字符串append:会使用更多的内存
  2. 整数共享:如何能使用整数,就尽量使用整数,限制了redis内存+LRU
  3. 整数精度问题:redis大概能保证16~,,17-18位的大整数就会丢失精确
  • 2.散列(hash)- Map ~ Pojo Class
    Redis中的Hash类型可以看成具有String key 和String value的map容器。所以该类型 非常适合于存储对象的信息。如Username、password和age。如果Hash中包含少量 的字段,那么该类型的数据也将仅占用很少的磁盘空间。 hset/hget/hmset/hmget/hgetall/hdel/hincrby
    hexists/hlen/hkeys/hvals
  • 3.列表(list)~ java的LinkedList
    在Redis中,List类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表 一 样,我们可以在其头部(Left)和尾部(Right)添加新的元素。在插入时,如果该键并不存 在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除, 那么该键也将会被从数据库中删除。
    lpush/rpush/lrange/lpop/rpop
  • 4.集合(set)~ java的set,不重复的list
    在redis中,可以将Set类型看作是没有排序的字符集合,和List类型一样,我们也可以 在该类型的数值上执行添加、删除和判断某一元素是否存在等操作。这些操作的时间复 杂度为O(1),即常量时间内完成依次操作。 和List类型不同的是,Set集合中不允许出现重复的元素。
    sadd/srem/smembers/sismember ~ set.add, remove, contains
    sdiff/sinter/sunion ~ 集合求差集,求交集,求并集
  • 5.有序集合(sorted set) sortedset和set极为相似,他们都是字符串的集合,都不允许重复的成员出现在一个 set中。他们之间的主要差别是sortedset中每一个成员都会有一个分数与之关联。redis 正是通过分数来为集合的成员进行从小到大的排序。sortedset中分数是可以重复的。
    zadd key score member score2 member2... : 将成员以及该成员的分数存放到sortedset中
    zscore key member : 返回指定成员的分数
    zcard key : 获取集合中成员数量
    zrem key member [member...] : 移除集合中指定的成员,可以指定多个成员
    zrange key start end [withscores] : 获取集合中脚注为start-end的成员,[withscores]参数表明返回的成员 包含其分数 zrevrange key start stop [withscores] : 按照分数从大到小的顺序返回索引从start到stop之间的所有元素 (包含两端的元素) zremrangebyrank key start stop : 按照排名范围删除元素
  • Bitmaps:setbit/getbit/bitop/bitcount/bitpos bitmaps不是一个真实的数据结构。而是String类型上的一组面向bit操作的集合。由于 strings是二进制安全的blob,并且它们的最大长度是512m,所以bitmaps能最大设置 2^32个不同的bit。
  • Hyperloglogs:pfadd/pfcount/pfmerge 在redis的实现中,您使用标准错误小于1%的估计度量结束。这个算法的神奇在于不再 需要与需要统计的项相对应的内存,取而代之,使用的内存一直恒定不变。最坏的情况 下只需要12k,就可以计算接近2^64个不同元素的基数。
  • GEO:geoadd/geohash/geopos/geodist/georadius/georadiusbymember Redis的GEO特性在 Redis3.2版本中推出,这个功能可以将用户给定的地理位置(经 度和纬度)信息储存起来,并对这些信息进行操作。

IO线程:

  • redis 6之前(2020年5月), 单线程
  • redis 6之后,多线程,NIO模型 ==> 主要的性能提升点
    内存处理线程:
  • 单线程 ==> 高性能的核心
  • 通用数据缓存,string,int,list,map等。
  • 实时热数据,最新500条数据。
  • 会话缓存,token缓存等。
  • 非严格一致性要求的数据:评论,点击等。
  • 业务数据去重:订单处理的幂等校验等。
  • 业务数据排序:排名,排行榜等。
  • 全局流控计数
  • 秒杀的库存计算
  • 抢红包
  • 全局ID生成
  • id去重,记录访问ip等全局bitmap操作
  • UV、PV等访问量==>非严格一致性要求
  • Pub-Sub 模拟队列
  • subscribe comments
  • publish comments java
  • PUB/SUB,订阅/发布模式
  • 基于List的 LPUSH+BRPOP 的实现
  • 基于Sorted-Set的实现
  • Redis Stream 是 Redis 5.0 版本新增加的数据结构。 Redis Stream 主要用于消息队列(MQ,Message Queue)
  • 获取锁--单个原子性操作
    SET dlock my_random_value NX PX 30000
  • 释放锁--lua脚本-保证原子性+单线程,从而具有事务性
    if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) elsereturn 0 end
    关键点:原子性、互斥、超时
  • Jedis
    官方客户端,类似于JDBC,可以看做是对redis命令的包装。
    基于BIO,线程不安全,需要配置连接池管理连接。
  • Lettuce
    目前主流推荐的驱动,基于Netty NIO,API线程安全。
  • Redission 基于Netty NIO,API线程安全。
    亮点:大量丰富的分布式功能特性,比如JUC的线程安全集合和工具的分布式版本,分布式的基本数据类型和锁等。
  • Spring Data Redis 核心是 RedisTemplate(可以配置基于Jedis,Lettuce,Redisson)
    使用方式类似于MongoDBTemplate,JDBCTemplate或JPA
    封装了基本redis命令操作。
  • Redis 事务命令: 开启事务:multi
    命令入队
    执行事务:exec
    撤销事务:discard
  • Watch 实现乐观锁
    watch 一个key,发生变化则事务失败
  • 类似于数据库的存储过程,mongodb的js脚本 直接执行:
    eval "return'hello java'" 0
    eval "redis.call('set',KEYS[1],ARGV[1])" 1 lua-key lua-value
    预编译:
    script load script脚本片段
    返回一个SHA-1签名 shastring
    evalsha shastring keynum [key1 key2 key3 ...] [param1 param2 param3 ...]

合并操作批量处理,且不阻塞前序命令

  1. 备份:
    执行 save 即可在redis数据目录生成数据文件 dump.rdb
    也可以异步执行 bgsave
  2. 恢复:
    将备份文件 (dump.rdb) 移动到 redis 数据目录并启动服务即可
  1. 备份:

如果 appendonly 配置为 yes,则以 AOF 方式备份 Redis 数据,那么此时 Redis 会按 照配置,在特定的时候执行追加命令,用以备份数据。
appendfilename "appendonly.aof"
# appendfsync always
# appendfsync everysec
# appendfsync no......
AOF 文件和 Redis 命令是同步频率的,假设配置为 always,其含义为当 Redis 执行 命令的时候,则同时同步到 AOF 文件,这样会使得 Redis 同步刷新 AOF 文件,造成 缓慢。而采用 evarysec 则代表每秒同步一次命令到 AOF 文件。


讯享网

  1. 恢复
    自动加载

#Redis 性能优化

  • 核心优化点:
  1. 内存优化
    redis.io/topics/memo…
    hash-max-ziplist-value 64 zset-max-ziplist-value 64
  2. CPU优化
    不要阻塞
    谨慎使用范围操作
    SLOWLOG get 10 默认10毫秒,默认只保留最后的128条

设计问题:

  1. 容量
  2. 分区
  1. 性能:
  • 线程数与连接数;
  • 监控系统读写比和缓存命中率;
  1. 容量:
  • 做好容量评估,合理使用缓存资源;
  1. 资源管理和分配:
  • 尽量每个业务集群单独使用自己的Redis,不混用;
  • 控制Redis资源的申请与使用,规范环境和Key的管理(以一线互联网为例);
  • 监控CPU 100%,优化高延迟的操作。

极简的风格, 从节点执行:
> SLAVEOF 127.0.0.1 6379 也可以在配置文件中设置。
注意:从节点只读、异步复制。

  • redis-sentinel sentinel.conf - redis-server redis.conf --sentinel
    sentinel.conf配置:
    sentinel monitor mymaster 127.0.0.1 6379 2
    sentinel down-after-milliseconds mymaster 60000
    sentinel failover-timeout mymaster
    sentinel parallel-syncs mymaster 1
    不需要配置从节点,也不需要配置其他sentinel信息

主从复制从容量角度来说,还是单机。
Redis Cluster通过一致性hash的方式,将数据分散到多个服务器节点:先设计【16384个哈希槽】,分配到多台redis-server。当需要在 Redis Cluster中存取一个 key时, Redis 客户端先对 key 使用 crc16 算法计算一个数值,然后对 16384 取模,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,然后在 此槽对应的节点上操作。
> cluster-enabled yes
注意:

  1. 节点间使用【gossip】通信,规模<1000
  2. 默认所有槽位可用,才提供服务
  3. 一般会配合主从模式使用

Hazelcast IMGD(in-memory data grid) 是一个标准的内存网格系统;它具有以下的一 些基本特性:

  1. 分布式的:数据按照某种策略尽可能均匀的分布在集群的所有节点上。
  2. 高可用:集群的每个节点都是 active 模式,可以提供业务查询和数据修改事务;部 分节点不可用,集群依然可以提供业务服务。
  3. 可扩展的:能按照业务需求增加或者减少服务节点。
  4. 面向对象的:数据模型是面向对象和非关系型的。在 java 语言应用程序中引入 hazelcast client api是相当简单的。
  5. 低延迟:基于内存的,可以使用堆外内存。

首先查询缓存,如果不存在就读取数据库并更新到缓存当中;
如果是更新数据库,那么操作完数据库后,删除缓存.
缓存中的内容是不做更新操作的,只有写入和删除操作.

  • 问题场景一:
    请求1查询不到缓存,查询数据库;请求2更新数据,删除缓存,此时请求1写入缓存.这时缓存当中的数据就是旧的数据.
    解决方法:
    给缓存的数据加一个过期的时间,缓存的过期时间也不宜过短,过短导致缓存的作用减少.
  • 问题场景二: 更新频繁的场景下会导致缓存频繁的被删除,降低了缓存的作用
    解决方法: 使用Write through,可以减少这种情况的发生.
  • 适用场景 用于读多写少的场景.实现简单.

核心策略:以缓存为操作为主,数据先存在于缓存,缓存的数据是不会过期的.

  • Read Through:先从缓存中查询数据是否存在,如果存在则直接返回,如果不存在,则由缓存组件负责从数据库中同步加载数据.
  • Write Through:先查询要写入的数据在缓存中是否已经存在,如果已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中,如果缓存中数据不存在,我们把这种情况叫做Write Miss(写失效)。
    Write Miss解决方式:一个是“Write Allocate(按写分配)”,做法是写入缓存相应位置,再由缓存组件同步更新到数据库中;
    另一个是“no write allocate(不按写分配)”,做法是不写入缓存中,而是直接更新到数据库中.
  • 适用场景 用于读操作较多.相较于Cache aside而言更适合缓存一致的场景.
    使用简单屏蔽了底层数据库的操作,只是操作缓存
    需要频繁读取相同数据
    不能忍受数据丢失(相对Write-Behind而言)和数据不一致

Write back是相较于Write Through而言的一种异步回写策略. 异步写可以减少与物理磁盘存储的交互,也可以进行合并写等优化.

  • 问题 实现比较复杂,可能会丢失数据.
  • 适用场景 用于读少写多的场景,Linux系统的页缓存和MySQL InnoDB 引擎的Cache Pool其实就是使用的Write Back策略. 相较于Write through而言拥有更高的写入性能.

本文整理自网络

小讯
上一篇 2025-05-16 07:23
下一篇 2025-05-12 17:03

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/190573.html