使用Redis事务
- watch命令是可以监控Redis的一些键;
- multi命令是开始事务,开始事务后,该客户端的命令不会马上被执行,而是存放在一个队列里,这点是需要注意的地方,也就是在这时我们执行一些返回数据的命令,Redis也是不会马上执行的,而是把命令放到一个队列里,所以此时调用Redis的命令,结果都是返回null;
- exe命令的意义在于执行事务,只是它在队列命令执行前会判断被watch监控的Redis的键的数据是否发生过变化(即使赋予与之前相同的值也会被认为是变化过),如果它认为发生了变化,那么Redis就会取消事务,否则就会执行事务,Redis在执行事务时,要么全部执行,要么全部不执行,而且不会被其他客户端打断,这样就保证了Redis事务下数据的一致性。
流程图如下:
使用Redis流水线
在很多情况下并不是Redis性能不佳,而是网络传输的速度造成瓶颈,使用流水线后就可以大幅度地在需要执行很多命令时提升Redis的性能。
与事务一样,使用流水线的过程中,所有的命令也只是进入队列而没有执行,所以执行的命令返回值也为空,这也是需要注意的地方。
使用Redis发布订阅
企业分配任务之后,可以通过邮件、短信或者微信通知到相关的责任人,这就是一种典型的发布订阅模式。
首先是Redis提供一个渠道,让消息能够发送到这个渠道上,而多个系统可以监听这个渠道,如短信、微信和邮件系统都可以监听这个渠道,当一条消息发送到渠道,渠道就会通知它的监听者,这样短信、微信和邮件系统就能够得到这个渠道给它们的消息了,这些监听者会根据自己的需要去处理这个消息,于是我们就可以得到各种各样的通知了。
使用Lua脚本
为了增强Redis的计算能力,Redis在2.6版本后提供了Lua脚本的支持,而且执行Lua脚本在Redis中还具备原子性,所以在需要保证数据一致性的高并发环境中,我们也可以使用Redis的Lua语言来保证数据的一致性,且Lua脚本具备更加强大的运算功能,在高并发需要保证数据一致性时,Lua脚本方案比使用Redis自身提供的事务要更好一些。
在Redis中有两种运行Lua的方法:
- 直接发送Lua到Redis服务器去执行.
- 先把Lua发送给Redis,Redis会对Lua脚本进行缓存,然后返回一个SHA1的32位编码回来,之后只需要发送SHA1和相关参数给Redis便可以执行了。
- 如果Lua脚本很长,那么就需要通过网络传递脚本给Redis去执行了,而现实的情况是网络的传递速度往往跟不上Redis的执行速度,所以网络就会成为Redis执行的瓶颈。如果只是传递32位编码和参数,那么需要传递的消息就少了许多,这样就可以极大地减少网络传输的内容,从而提高系统的性能。
使用Spring缓存注解操作Redis
缓存管理器配置
# SPRING CACHE (CacheProperties)
spring.cache.cache-names= # 如果由底层的缓存管理器支持创建,以逗号分隔的列表来缓存名称
spring.cache.caffeine.spec= # caffeine缓存配置细节
spring.cache.couchbase.expiration=0ms # couchbase缓存超时时间,默认是永不超时
spring.cache.ehcache.config= # 配置ehcache缓存初始化文件路径
spring.cache.infinispan.config= #infinispan缓存配置文件
spring.cache.jcache.config= #jcache缓存配置文件
spring.cache.jcache.provider= #jcache缓存提供者配置
**spring.cache.redis.cache-null-values=true # 是否允许Redis缓存空值
spring.cache.redis.key-prefix= # Redis的键前缀
spring.cache.redis.time-to-live=0ms # 缓存超时时间戳,配置为0则不设置超时时间
spring.cache.redis.use-key-prefix=true # 是否启用Redis的键前缀
spring.cache.type= # 缓存类型,在默认的情况下,Spring会自动根据上下文探测**
配置Redis缓存管理器
spring.cache.type=REDIS
spring.cache.cache-names=redisCache
启用缓存机制
package com.springboot.chapter7.main;
/**** imports ****/
@SpringBootApplication(scanBasePackages = "com.springboot.chapter7")
@MapperScan(basePackages = "com.springboot.chapter7", annotationClass = Repository.class)
@EnableCaching
public class Chapter7Application {
......
}
用户实现类使用Spring缓存注解
package com.springboot.chapter7.service.impl;
/**** imports ****/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao = null;
// 插入用户,最后MyBatis会回填id,取结果id缓存用户
@Override
@Transactional
@CachePut(value ="redisCache", key = "'redis_user_'+#result.id")
public User insertUser(User user) {
userDao.insertUser(user);
return user;
}
// 获取id,取参数id缓存用户
@Override
@Transactional
@Cacheable(value ="redisCache", key = "'redis_user_'+#id")
public User getUser(Long id) {
return userDao.getUser(id);
}
// 更新数据后,更新缓存,如果condition配置项使结果返回为null,不缓存
@Override
@Transactional
@CachePut(value ="redisCache",
condition="#result != 'null'", key = "'redis_user_'+#id")
public User updateUserName(Long id, String userName) {
// 此处调用getUser方法,该方法缓存注解失效,
// 所以这里还会执行SQL,将查询到数据库最新数据
User user =this.getUser(id);
if (user == null) {
return null;
}
user.setUserName(userName);
userDao.updateUser(user);
return user;
}
// 命中率低,所以不采用缓存机制
//因为查询结果随着用户给出的查询条件变化而变化,导致命中率很低。对于命中率很低的场景,使用缓存并不能有效提供系统性能,所以这个方法并不采用缓存机制。此外,对于大数据量等消耗资源的数据,使用缓存也应该谨慎一些。
@Override
@Transactional
public List<User> findUsers(String userName, String note) {
return userDao.findUsers(userName, note);
}
// 移除缓存
@Override
@Transactional
@CacheEvict(value ="redisCache", key = "'redis_user_'+#id",
beforeInvocation = false)
public int deleteUser(Long id) {
return userDao.deleteUser(id);
}
}
-
@CachePut表示将方法结果返回存放到缓存中。
-
@Cacheable 表示先从缓存中通过定义的键查询,如果可以查询到数据,则返回,否则执行该方法,返回数据,并且将返回结果保存到缓存中。
-
@CacheEvict 通过定义的键移除缓存,它有一个Boolean类型的配置项beforeInvocation,表示在方法之前或者之后移除缓存。因为其默认值为false,所以默认为方法之后将缓存移除。
缓存注解自调用失效问题
因为Spring的缓存机制也是基于Spring AOP的原理,而在Spring中AOP是通过动态代理技术来实现的,这里的updateUserName方法调用getUser方法是类内部的自调用,并不存在代理对象的调用,这样便不会出现AOP,也就不会使用到标注在getUser上的缓存注解去获取缓存的值了.
缓存脏数据说明
我们可以规定一个时间,让缓存失效,在Redis中也可以设置超时时间,当缓存超过超时时间后,则应用不再能够从缓存中获取数据,而只能从数据库中重新获取最新数据,以保证数据失真不至于太离谱。
对于数据的写操作,往往采取的策略就完全不一样,需要我们谨慎一些,一般会认为缓存不可信,所以会考虑从数据库中先读取最新数据,然后再更新数据,以避免将缓存的脏数据写入数据库中,导致出现业务问题。
自定义缓存管理器
重置Redis缓存管理器
#禁用前缀
spring.cache.redis.use-key-prefix=false
#允许保存空值
#spring.cache.redis.cache-null-values=true
#自定义前缀
#spring.cache.redis.key-prefix=
#定义超时时间,单位毫秒
spring.cache.redis.time-to-live=600000
自定义缓存管理器
// 注入连接工厂,由Spring Boot自动配置生成
@Autowired
private RedisConnectionFactory connectionFactory = null;
// 自定义Redis缓存管理器
@Bean(name = "redisCacheManager" )
public RedisCacheManager initRedisCacheManager() {
// Redis加锁的写入器
RedisCacheWriter writer= RedisCacheWriter.lockingRedisCacheWriter(connectionFactory);
// 启动Redis缓存的默认设置
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 设置JDK序列化器
config = config.serializeValuesWith(
SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));
// 禁用前缀
config = config.disableKeyPrefix();
//设置10 min超时
config = config.entryTtl(Duration.ofMinutes(10));
// 创建缓Redis存管理器
RedisCacheManager redisCacheManager = new RedisCacheManager(writer, config);
return redisCacheManager;
```