性能利器——Redis(二)

使用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;
   ```
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值