{{π型人才培养计划}}Redis 缓存

Redis 缓存

1.为什么使用缓存

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

2.使用缓存存在的问题

2.1缓存穿透
概念:

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

解决办法:

​ 1.布隆过滤

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

​ 2.强而有力

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

2.2缓存雪崩
概念:

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

解决办法:

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

2.3缓存的击穿
概念:

​ 一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量 大、压力骤增。

解决办法:

​ “永远不过期”:

​ 这里的“永远不过期”包含两层意思: (1) 从redis上看,确实没有设置过期时间,这就保证了,不会出现热点key过期问题,也就是“物理”不过期。 (2) 从功能上看,如果不过期,那不就成静态的了吗?所以我们把过期时间存在key对应的value里,如果发现要过期了,通过一个后台的异步线程进行缓存的构建,也就是“逻辑”过期 从实战看,这种方法对于性能非常友好,唯一不足的就是构建缓存时候,其余线程(非构建缓存的线程)可能访问的是老数据,但是对于一般的互联网功能来说这个还是可以忍受。

3.mybatis一级,二级缓存

​ 一级缓存:

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

​ 二级缓存:

​ 二级缓存的作用域是mapper的同一个namespace。不同的sqlSession两次执行相同的namespace下的s ql语句,且向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的安装

1. 安装gcc运行环境

   	yum -y install gcc 

   	或手动导入安装

2. 上传redis的资料包

   	在CRT中 alt+p , 直接拖进

3. 解压redis的压缩包

   	tar -zxvf xxx.zip

4. 进入redis包执行指令 

   	make

5. 编译完成之后安装redis

   	make install PREFIX=/usr/redis

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

    		<dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
            	<groupId>org.springframework.boot</groupId>
            	<artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
    
  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);
    }

return result;
}


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

~~~java
	@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、付费专栏及课程。

余额充值