Spring缓存
为什么需要缓存
- 减轻服务器压力
客户端缓存 | CDN对象存储 |
接入服务器 NGIX缓存 | |
应用服务器Tomcat Mybatis && Hibernate缓存 | 分布式缓存/Redis |
Spring Cache
- Spring Cache 是Spring 提供的一整套的缓存解决方案(JSR- 107),
- 提供一整套的接口和代码规范、配置、注解等,用于整合各种缓 存方案,如Redis、Caffeine、Guava Cache、Ehcache
不推荐原因
- 无法测试:无法进行切片测试
- 不支持复杂结构,只能进行简单对象查询存储
MyBatis缓存
一级缓存和二级缓存
在参数和 SQL 完全一样的情况下,优先命中一级缓存,避免直接对数据库进行查询,提高性能。
把执行的方法和参数通过算法生成缓存的键值, 将键值和结果存放在一 个 Map 中, 如果后续的键值一样, 则直接从 Map 中获取数据;
缓存发霉 解决方案
任何的 UPDATE, INSERT, DELETE 语句都会清空缓存
一级缓存
- Mybatis强制开启一级缓存
- 在一个事务中,若
同一个事务中
,出现相同的查询SQL,应用服务器则会将第一次查询结果进行本地缓存,后续相同Hash值
语句返回缓存的相同结果 - 不通事务的相同语句查询结果互不干扰
- 若Database事务隔离级别在 Read Commited或更低时 则会导致数据异常
二级缓存
二级缓存需额外存储介质,查询结果会进入二级缓存,多应用服务会在第三方介质中以不同NameSpace进行隔离
开启二级缓存后,会将一级缓存结果存入二级缓存,后续查询优先查询二级缓存Hash数据,若没有则查询一级缓存数据。
可以用参数控制开启二级缓存,但是多实例应用连接会导致数据异常
缓存发霉解决方案只能解决当前服务器操作的结果,当A进行修改,B服务器缓存不会被删除,此时Load Balance请求使用B服务器查询到Cache则数值异常
所有当前服务器前端连接 查询相同语句的 结果都会被相互共用
不建议开启二级缓存
Redis缓存
- Redis 是高性能的 内存 key-value 数据库
- 支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用(RDB/AOF 在宕机后仍会丢弃数据)
- 不仅支持简单的key-value类型的数据,同时还提供list,set,zset, hash等数据结构的存储。
- 支持数据的备份,即master-slave模式的数据备份
- Redis采用IO多路复用,单线程结构 – 读的速度是110000次/s, 写的速度是81000次/s
- Redis的所有操作都是原子性的。多个操作也支持事务
Redis的常用数据类型
类型 | 简介 | 特性 |
---|---|---|
String(字符串) | 是 Redis 最基本的数据类型并且 是二进制安全的 | 可以包含任何数据,比如jpg图片或者序列 化的对象,一个键最大能存储512M |
Hash(字典) | 键值对集合 | 适合存储对象,并且可以像数据库中 update一个属性一样只修改某一项属性值 |
List(列表) | 链表(双向链表),按照插入顺序排 序。 | 增删快,提供了操作某一段元素的API |
Set(集合) | 哈希表实现,元素不重复 | 添加、删除,查找的复杂度都是O(1) 。为 集合提供了求交集、并集、差集等操作 |
Bitmap | 是一种实现对位的操作 | 借助字符串进行位操作 |
字符串
可以是字符串、数字、二进制图片、音频等,大小不超过512MB
1
2
3
4
5
6
7
8//SET key value [ex seconds] [px milliseconds] [nx|xx]
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS)
//GET key
redisTemplate.opsForValue().get(key)
//INCRBY key delta
redisTemplate.opsForValue().increment(key, delta)
//EXISTS key
redisTemplate.hasKey(key)
Hash
键值对集合,可以像数据库中update一个属性一样只修改某一项属性值
1
2
3
4//HSET key field
redisTemplateopsForHash().put(key, field, value)
//HGET key field
redisTemplate.opsForHash().get(key, field)
List
链表(双向链表),按照插入顺序排序。
1
2
3
4
5
6//LPOP key
redisTemplate.opsForList().leftPop(key)
//LLEN key
redisTemplate.opsForList().size(key)
//RPUSHX key value
redisTemplate.opsForList().rightPush(key, value)
Set
为集合提供了求交集、并集、差集等操作
1
2
3
4
5
6
7
8
9//SADD key value1 ... valueN
redisTemplate..opsForSet().add(key, values)
//SMEMBERS key
redisTemplate.opsForSet().members(key)
//SUNIONSTORE destination key1 key2
redisTemplate.opsForSet().unionAndStore(key1, key2, destination) //SCARD key
redisTemplate.opsForSet().size(key)
//SREM key value1 ... valueN
redisTemplate.opsForSet().remove(key, value1, ..., valueN)
Bitmap
进行位操作
1
2
3
4
5
6//SETBIT key offset value
redisTemplate.opsForValue().setBit(key, offset, value)
//GETBIT key offset
redisTemplate.opsForValue().getBit(key, offset)
//BITCOUNT key start end
redisTemplate.execute((RedisCallback<Long>) connection -> connection.bitCount(key.getBytes(), start, end))
Lua操作
- Redis内置 Lua 解释器
- 使用 EVAL 命令对 Lua 脚本进行执行,并获取脚本的返回值
- Redis是单线程的,脚本也是原子性的,因此只能执行完脚本后才能执行后续命令。因此脚步不能复杂,否则会带来性能问题。
- 不建议使用
- 动态语言类型定义问题
- 无法调试
应用场景
利用缓存存储对象,加快访问的速度(Spring事务结束前,一定要删除一次RedisCache)
利用Set,计算权限的并集
利用Redis实现库存的高并发扣减
- 将库存量读入Redis中,利用Redis的原子性操作实现库存的高并发扣减
- 不更新数据库,用带事务的消息队列 (如RocketMQ) 将库存量写回
Bloom过滤器
是1970年由布隆提出的,用以判断一个元素是否在一个集合里
用一个固定大小的空间,记录所有的Key
原理
- 利用多个Hash函数,针对同一个元素进行Hash计算后取模,再根据结果更改位置数值,尽量避免Hash冲突问题
有误判率,当位数容量不足以满足元素空间数量时,会导致误判(K = m/n ln2) ,若所有位都被填充位1时,会导致所有判断失效
使用场景
可以避免恶意访问导致的缓存穿透、缓存雪崩问题(需定期删除避免正常访问数据查询异常)
缓存穿透
故意去请求缓存中不存在的数据,导致所有的请求都穿透到数据库访问上,造成数据库的负载增加缓存雪崩
• 缓存同一时间大面积的失效,导致所有请求同时到达数据库,造成数据 库负载增加
内存管理
惰性删除
- 当访问到该键时,如果该键值已过期,则返回空,并删除该键。
定期删除
- 定期运行,随机中选取一些键并删除其中过期的键
内存管理机制
- volatile-lru:利用LRU算法移除有过期时间的key。
- LRU(Least recently used,最近最少使用) • 随机采样一部分键值,淘汰其中最久没有访问的键
- volatile-random:随机移除有过期时间的key。
- volatile-ttl:移除即将过期的key,根据最近过期时间来删除
- allkeys-lru:利用LRU算法移除任何key。
- allkeys-random:随机移除任何key。
- noeviction(默认):不移除任何key,只是返回一个写错误
来源
https://www.icourse163.org/learn/XMU-1462056168#/learn/content