Redis实现SpringBoot框架下的Mybatis二级缓存

Redis 缓存

1.为什么使用缓存

缓存就是在内存中存储的数据备份,当数据没有发生本质变化的时候,我们避免数据的查询操作直接连接数据库,而是去内存中读取数据,这样就大大降低了数据库的读写次数,而且从内存中读数据的速度要比从数据库查询要快很多,极大的提升了应用程序的性能和效率,特别是数据查询方面

2.使用缓存存在的问题

2.1缓存穿透

概念:

	是指查询数据库中一定不存在的数据。先在缓存中查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存中。如果数据库查询对象空,则不放进缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。

解决办法:

	1.布隆过滤

	最常见的则是采用布隆过滤器,将所有可能存在的数据存到一个足够大的bitmap中,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,一个一定不存在的数据会被bitmap拦截掉从而避免了对底层存储系统的查询压力。

	2.强而有力

		访问key未在DB查询到的空值写进缓存,设置较短过期时间

2.2缓存雪崩

概念:

	大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤	增,引起雪崩。

解决办法:

	可以给缓存设置过期时间时加上一个随机值时间,使得每个key的过期时间分布开来,不会集中在同一时刻失效。

2.3缓存的击穿

概念:

	一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量	大、压力骤增。缓存击穿不同于缓存雪崩的一点是,击穿是高并发访问同一个刚好过期的数据,而雪崩是大量不同数据过期

解决办法:

设置热点数据永远不过期。

     加互斥锁,互斥锁:

互斥锁,是一种信号量,常用来防止两个进程或线程在同一时刻访问相同的共享资源。可以保证以下三点:

原子性:把一个互斥量锁定为一个原子操作,这意味着操作系统(或pthread函数库)保证了如果一个线程锁定了一个互斥量,没有其他线程在同一时间可以成功锁定这个互斥量。

唯一性:如果一个线程锁定了一个互斥量,在它解除锁定之前,没有其他线程可以锁定这个互斥量。

非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何cpu资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥量。

3.mybatis一级,二级缓存

一级缓存:

	一级缓存基于sqlSession默认开启,在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap	用于存储缓存数据。不同的SqlSession之间的缓存数据区域是互相不影响的

二级缓存:

	二级缓存的作用域是mapper的同一个namespace。不同的sqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数	据写到缓存,第二次查询会从缓存中获取数据,不再去底层数据库查询,从而提高效率,多表联合查询时会造	成脏读的情况	

4.redis

4.1简介

Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。

Redis 与其他 key - value 缓存产品有以下三个特点:
  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供string,list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

4.2Redis的5种数据类型及其适用场景

(1)String:可以包含任何数据,比如jpg图片或者序列化的对象.。

(2)List:链表(双向链表),增删快,提供了操作某一段元素的API。适用于:最新消息排行等功能;消息队列。

(3)Set:集合。哈希表实现,元素不重复,为集合提供了求交集、并集、差集等操作。适用于:共同好友;利用唯一性,统计访问网站的所有独立ip;好友推荐时,根据tag求交集,大于某个阈值就可以推荐。

(4)Hash 键值对集合,即编程语言中的Map类型。适合存储对象,并且可以像数据库中update一个属 性一样只修改某一项属性值。适用于:存储、读取、修改用户属性。

(5)Sorted Set:有序集合。将Set中的元素增加一个权重参数score,元素按score有序排列。数据插入集合时,已经进行天然排序。适用于:排行榜;带权重的消息队列。

4.3优势

  • 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
  • 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
  • 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性.
  • 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

4.4其他key-value存储有什么不同?

  • Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
  • Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。

5.redis实现缓存

5.1redis实现mybatis的二级缓存

用redis做mybatis的缓存,只能用于mybatis

5.2.基于aop,实现通用的缓存

aop的机制:

	AOP是Spring框架面向切面的编程思想,AOP采用一种称为“横切”的技术,将涉及多业务流程的通用功能	抽取并单独封装,形成独立的切面,在合适的时机将这些切面横向切入到业务流程指定的位置中。

6.Redis的安装

搭建基础环境
vi /etc/sysconfig/network-scripts/ifcfg-ens33
重启
systemctl restart network
关闭防火墙
systemctl stop firewalld


1. 安装gcc运行环境

   	yum -y install gcc 

   	或手动导入安装

2. 上传redis的资料包

   	在CRT中 alt+p , 直接拖进

3. 解压redis的压缩包,将解压后的文件移到/usr目录下

   	tar -zxvf xxx.zip
   	mv redis-4.0.9 /usr/

4. 进入redis包执行指令 

   	make

5. 编译完成之后安装redis,并将redis.conf文件移到bin目录

   	make install PREFIX=/usr/redis
   	mv redis.conf /usr/redis-4.0.9/bin

6. 启动redis

   	./redis-server  /root/redis-4.0.9/redis.conf

7. 使用redis的客户端连接redis服务

   	./redis-cli –p 6379 

8. 需要后台链接redis
   	 vi redis-4.0.9/redis.conf
9. 关闭redis
   	 ./redis-cli –p 6379 shutdown






10. 启动redis的服务

	./redis-server  /root/redis-4.0.9/redis.conf

9.Redis缓存后台实现

  1. 思路

  2. 需要的jar

    org.springframework.boot
    spring-boot-starter-data-redis


    org.springframework.boot
    spring-boot-starter-aop

  3. 实现redis缓存
    3.1基于切方法

    @Configuration
    @Aspect//声明当前类是一个切面供容器读取
    public class RedisCache {
        @Autowired
        private RedisTemplate redisTemplate;
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Around("execution(* com.baizhi.service.*.select*(..))")
        public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            RedisSerializer stringSerializer = new StringRedisSerializer();
            redisTemplate.setKeySerializer(stringSerializer);
            redisTemplate.setHashKeySerializer(stringSerializer);
            StringBuilder sb = new StringBuilder();
            //获取类名
            String className = proceedingJoinPoint.getTarget().getClass().getName();
            sb.append(className);
            //获取方法名
            String methodName = proceedingJoinPoint.getSignature().getName();
            sb.append(methodName);
            //获取所有的参数
            Object[] args = proceedingJoinPoint.getArgs();
            for (Object arg : args) {
                sb.append(arg);
            }
            String s = sb.toString();
            ValueOperations valueOperations = redisTemplate.opsForValue();
            Object result = null;
            if(redisTemplate.hasKey(s)){
                result = valueOperations.get(s);
            }else{
                result = proceedingJoinPoint.proceed();
                valueOperations.set(s,result);
    
            }
            return result;
        }
    
        @After("execution(* com.baizhi.service.*.delete*(..))")
        public void after(JoinPoint joinPoint){
            Set<String> keys = stringRedisTemplate.keys("*");
            for (String key : keys) {
                stringRedisTemplate.delete(key);
            }
        }
    

注意:

发现key值出现 \xac\xed\x00\x05t\x00\tb,redisTemplate 默认的序列化方式为 jdkSerializeable,   	  	 StringRedisTemplate的默认序列化方式为StringRedisSerializer

3.2基于切自定义注解

1.自定义注解
//添加缓存
@Target(ElementType.METHOD)//声明注解使用的位置
@Retention(RetentionPolicy.RUNTIME)
public @interface SaveRedisCache {
	//可以加属性
    //public String/int/... value();
}
//移除缓存
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RemoveRedisCache {
    //可以加属性
    //public String/int/... value();
}


@Configuration//当前类为配置类
@Aspect//作用是把当前类标识为一个切面供容器读取
public class RedisCache {
    @Autowired
    private RedisTemplate redisTemplate;
    @Around("@annotation(com.baizhi.annotation.SaveRedisCache)")
    public Object arround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        StringBuilder sb = new StringBuilder();
        //获取类名
        String className = proceedingJoinPoint.getTarget().getClass().getName();
        //获取方法名
        String methodName = proceedingJoinPoint.getSignature().getName();
        sb.append(methodName);
        //获取所有的参数
        Object[] args = proceedingJoinPoint.getArgs();
        for (Object arg : args) {
            sb.append(arg);
        }
        String s = sb.toString();
        HashOperations hashOperations = redisTemplate.opsForHash();
        Boolean aBoolean = redisTemplate.hasKey(className);
        Object result = null;
        if(aBoolean){
            result = hashOperations.get(className,s);
        }else {
            result = proceedingJoinPoint.proceed();
            HashMap<String, Object> map = new HashMap<>();
            map.put(s, result);
            hashOperations.putAll(className, map);
            redisTemplate.expire(className,10, TimeUnit.SECONDS);
        }
        return  result;
    }

2. 写的操作需要清除缓存

	@After("@annotation(com.baizhi.annotation.RemoveRedisCache)")
    public void after(JoinPoint joinPoint){
        String className = joinPoint.getTarget().getClass().getName();
        redisTemplate.delete(className);
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值