缓存解决方案SpringDataRedis

缓存解决方案SpringDataRedis

学习目标

  • 掌握SpringDataRedis 的常用操作
  • 能够理解并说出什么是缓存穿透、缓存击穿、缓存雪崩,以及对应的解决方案
  • 使用缓存预热的方式实现商品分类导航缓存
  • 使用缓存预热的方式实现广告轮播图缓存
  • 使用缓存预热的方式实现商品价格缓存

1.SpringDataRedis

1.1 SpringDataRedis简介

SpringDataRedis 属于Spring Data 家族一员,用于对redis的操作进行封装的框架 ,Spring Data : Spring 的一个子项目.Spring 官方提供一套数据层综合解决方案,用 于简化数据库访问,支持NoSQL和关系数据库存储。包括Spring Data JPA 、Spring Data Redis 、SpringDataSolr 、SpringDataElasticsearch 、Spring DataMongodb 等 框架。

1.2 SpringDataRedis入门

1.2.1 准备工作

(1)创建SpringDataRedisDemo工程,在pom.xml中配置相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.qingcheng.springdataredis</groupId>
    <artifactId>SpringDataRedisDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.5.RELEASE</version>
        </dependency>
    </dependencies>

</project>

(2)在src/main/resources下创建properties文件redis-config.properties

redis.host=127.0.0.1
redis.port=6379
redis.pass=
redis.database=0
redis.maxIdle=300
redis.maxWait=3000

maxIdle :最大空闲数

maxWaitMillis: 连接时的最大等待毫秒数

(3)在src/main/resources下创建applicationContext-redis.xml

<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
   
   
   <context:property-placeholder location="classpath:redis-config.properties" />
   
   <!-- redis 相关配置 --> 
   <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">  
     <property name="maxIdle" value="${redis.maxIdle}" />   
     <property name="maxWaitMillis" value="${redis.maxWait}" />  
   </bean>  
  
   <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>  
   
   <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">  
    	<property name="connectionFactory" ref="jedisConnectionFactory" />
   </bean>


</beans>  

1.2.2 值类型操作

package com.qingcheng.springdataredis.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-redis.xml")
public class TestValue {
   
    @Autowired
    private RedisTemplate redisTemplate;

    /** * 存值 / 修改(当key相同,value不同,把原来覆盖掉) */
    @Test
    public void setValue(){
   
        redisTemplate.boundValueOps("name").set("qingcheng");
    }

    /** * 取值 */
    @Test
    public void getValue(){
   
        String str = (String) redisTemplate.boundValueOps("name").get();
        System.out.println(str);
    }

    /** * 删除 */
    @Test
    public void deleteValue(){
   
        Boolean name = redisTemplate.delete("name");
        
    }
}

1.2.3 Set类型操作

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Set;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-redis.xml")
public class TestSet {
   
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void set(){
   
        redisTemplate.boundSetOps("names").add("曹操");
        redisTemplate.boundSetOps("names").add("刘备");
        redisTemplate.boundSetOps("names").add("孙权");
    }
    @Test
    public void get(){
   
        //获取所有
        Set names = redisTemplate.boundSetOps("names").members();
        System.out.println(names);
    }

    @Test
    public void deleteValue(){
   
        Long remove = redisTemplate.boundSetOps("names").remove("曹操");
        System.out.println(remove);
    }

    @Test
    public void deleteAll(){
   
        Boolean isDelete = redisTemplate.delete("names");
        System.out.println(isDelete);
    }
}

1.2.3 List类型操作

package com.qingcheng.springdataredis.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;
import java.util.Set;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-redis.xml")
public class TestList {
   
    @Autowired
    private RedisTemplate redisTemplate;

    /** * 右压栈 (后添加的排在后面) * */
    @Test
    public void setRightValue(){
   
        redisTemplate.boundListOps("names").rightPush("刘备");
        redisTemplate.boundListOps("names").rightPush("关羽");
        redisTemplate.boundListOps("names").rightPush("张飞");
    }


    /** * 左压栈(后添加的在前面) */
    @Test
    public void setLeftValue(){
   
        redisTemplate.boundListOps("nams_").leftPush("刘备");
        redisTemplate.boundListOps("nams_").leftPush("关羽");
        redisTemplate.boundListOps("nams_").leftPush("张飞");
    }

    /** * range(start,end) * start:开始位置 * end 查询多少个 ,如果是"-1" 表示查询所有 */
    @Test
    public void seachAll(){
   
        List names = redisTemplate.boundListOps("nams_").range(0, -1);
        System.out.println(names);
    }

    @Test
    public void searchByIndex(){
   
        Object name = redisTemplate.boundListOps("nams_").index(2);
        System.out.println(name);
    }

    /** * 移除集合中某个元素 * List集合可以重复 * remove(count,Object) 第一个参数表示移除个数 第二个参数表示移除那个元素 * 总之就是移除相同元素的个数 */
    @Test
    public void remove(){
   
        redisTemplate.boundListOps("nams_").remove(2,"关羽");
    }
    @Test
    public void deleteAll(){
   
        redisTemplate.delete("nams_");
    }
}

1.2.4 Hash类型操作

类似于Java中的Map

package com.qingcheng.springdataredis.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;
import java.util.Set;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-redis.xml")
public class TestHash {
   
    @Autowired
    private RedisTemplate redisTemplate;

     @Test
    public void setHashValue(){
   
         redisTemplate.boundHashOps("hashName").put("a","唐僧");
         redisTemplate.boundHashOps("hashName").put("s","孙悟空");
         redisTemplate.boundHashOps("hashName").put("z","猪八戒");
     }

     @Test
    public void getHashKeys(){
   
         Set keys = redisTemplate.boundHashOps("hashName").keys();
         System.out.println(keys);
     }

     @Test
    public void getHashValues(){
   
         List values = redisTemplate.boundHashOps("hashName").values();
         System.out.println(values);
     }

    /** * 根据key获取value */
    @Test
    public void getValueByKey(){
   
        Object o = redisTemplate.boundHashOps("hashName").get("a");
        System.out.println(o);
    }

    @Test
    public void deleteByKey(){
   
        redisTemplate.boundHashOps("hashName").delete("a");
    }

    @Test
    public void delete(){
   
        redisTemplate.delete("hashName");
    }
}

1.2.5 zset类型操作

zset是set的升级版本,它在set的基础上增加了格顺序属性,这属性在添加元素

的同时可以指定,每次指定后,zset会自动重新按照新的值调整顺序。可以理解为有两列 的mysql表,列存储value,列存储分值。

比如主播的人气榜、富豪榜

/* package com.qingcheng.springdataredis.test;*/
package com.qingcheng.springdataredis.test;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;
import java.util.Set;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext-redis.xml")
public class Testzset {
   

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void testSetValue(){
   
        redisTemplate.boundZSetOps("nameszset").add("曹操",1000);
        redisTemplate.boundZSetOps("nameszset").add("刘备",100);
        redisTemplate.boundZSetOps("nameszset").add("孙权",10);
    }

    /** * 默认是由低到高 */
    @Test
    public void testGetValue(){
   
        Set nameszset = redisTemplate.boundZSetOps("nameszset").range(0, -1);
        System.out.println(nameszset);
    }

    /** * 前两位富豪 */

    @Test
    public void testTuHaoBang(){
   
        Set nameszset = redisTemplate.boundZSetOps("nameszset").reverseRange(0, 1);
        System.out.println(nameszset);
    }

    @Test
    public void addScort(){
   
        redisTemplate.boundZSetOps("nameszset").incrementScore("孙权",200);
    }
    //查询分数和值
    //TypeTuple类型的对象
    @Test
    public void getValueAndScore(){
   
        Set<ZSetOperations.TypedTuple> nameszset = redisTemplate.boundZSetOps("nameszset").reverseRangeWithScores(0, 9);
        for (ZSetOperations.TypedTuple typedTuple : nameszset) {
   
            System.out.println(typedTuple.getValue()+"----"+typedTuple.getScore());
        }

    }
}

1.2.6 设置过期时间

    @Test
    public void setTimeOut(){
   
        //第一个参数表示设置过期的数字
        //第二个参数表示数字的单位, seconds 表示秒
        redisTemplate.boundValueOps("name").expire(10, TimeUnit.SECONDS);
    }

2.缓存穿透、击穿、雪崩

2.1 缓存穿透

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id

为“1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据 库压力过大。如下面这段代码就存在缓存穿透的问题

public Integer findPrice(Long id) {
    
    //从缓存中查询 
    Integer sku_price = (Integer)redisTemplate.boundHashOps("sku_price").get(id); 	     
    if(sku_price==null){
    
        //缓存中没有,从数据库查询 
        Sku sku = skuMapper.selectByPrimaryKey(id); 
       
        if(sku!=null){
    
            //如果数据库有此对象 
            sku_price = sku.getPrice();                       		      
            redisTemplate.boundHashOps("sku_price").put(id,sku_price); 
        } 
    }
    return sku_price; 
}

解决方案:

1.接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;

2.从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为 key-0。这样可以防止攻击用户反复用同一个id暴力攻击。

代码举例:

public Integer findPrice(Long id) {
    
    //从缓存中查询 
    Integer sku_price = (Integer)redisTemplate.boundHashOps("sku_price").get(id); 	     
    if(sku_price==null){
    
        //缓存中没有,从数据库查询 
        Sku sku = skuMapper.selectByPrimaryKey(id); 
        if(sku!=null){
    
            //如果数据库有此对象 
            sku_price = sku.getPrice();                       		      
            redisTemplate.boundHashOps("sku_price").put(id,sku_price); 
        }else{
   
           redisTemplate.boundHashOps("sku_price").put(id,0);  
        } 
    }
    return sku_price; 
}

  1. 使用缓存预热 ,缓存预热就是将数据提前加入到缓存中,当数据发生变更,再将最新的数据更新到缓

存。后边我们就用缓存预热的方式实现对分类导航、广告轮播图等数据的缓存。

2.2 缓存击穿

缓存击穿是指缓存中没有但数据库中有的数据。这时由于并发用户特别多,同时读

缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压 力。

以下代码可能会产生缓存击穿:

@Autowired 
private RedisTemplate redisTemplate;
public List<Map> findCategoryTree() {
   
    //从缓存中查询 
    List<Map> categoryTree= (List<Map>)redisTemplate.boundValueOps("categoryTree").get(); 
    if(categoryTree==null){
    
        Example example=new Example(Category.class); 
        Example.Criteria criteria = example.createCriteria(); criteria.andEqualTo("isShow","1");
        //显示 
        List<Category> categories = categoryMapper.selectByExample(example); 
        categoryTree=findByParentId(categories,0); 
        redisTemplate.boundValueOps("categoryTree").set(categoryTree); 
        //过期时间设置 ...... 
    }
    return categoryTree; 
}

主要是缓存过期时间造成的。

解决方案:

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

2.缓存预热

2.3 缓存雪崩

缓存雪崩是指缓存数据大批量到过期时间,而查询数据量巨大,引起数据库压力过

大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据缓存雪崩是不同

数据都过期了,很多数据都查不到从而查数据库

解决方案:

1.缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。

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

3.使用缓存预热

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/100763.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 600万行代码项目(几万行查代码)

    作者:小傅哥博客:https://bugstack.cn沉淀、分享、成长,让自己和他人都能有所收获!????一、前言20万行代码写完,毕业了找一份工作不是问题!刚一毕业因为找不到工作,就得报名去参加Java培训的大有人在。并不是说参加培训就不好,只不过以你现在这个毕业的时间点参加,就会显得特别匆忙。因为你的压力既来自于培训还需要花家里一笔不小的费用,也有同班同学已经找到一份不错的工作开始赚钱的比对。大学四年其实有足够的时间让你学会编程,也能从一个较长时间的学习中,知道自己适合不适合做程序员。

  • mysql有关运维的面试题_mysql数据库运维面试题「建议收藏」

    mysql有关运维的面试题_mysql数据库运维面试题「建议收藏」1.登陆数据库(1)单实例mysql-uroot-poldboy(2)多实例mysql-uroot-poldboy-S/data/3306/mysql.sock2.查看数据库版本及当前登录用户是什么mysql>selectversion();查看版本+————+|version()|+————+|5.5.22-log|+——-…

  • idea2021.8激活码-激活码分享

    (idea2021.8激活码)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.cn/100143.htmlKU…

  • CTK框架介绍

    CTK框架介绍转(http://blog.csdn.net/xinqidian2015/article/details/50537325)CTK插件框架可以简单的描述为C++的动态组件系统DesignCTK插件框架的设计有很大的灵感来自OSGi并且使得应用程序由许多不同的组件组合成一个可扩展模型。这个模型允许通过那些组件间共享对象的服务通信。框架的分层模型被展示在图片1中包括:P

  • 从头开始学MySQL——-存储过程与存储函数(1)

    从头开始学MySQL——-存储过程与存储函数(1)10.1.1创建存储过程存储过程就是一条或者多条SQL语句的集合,可以视为批文件。它可以定义批量插入的语句,也可以定义一个接收不同条件的SQL。创建存储过程的语句为CREATEPROCEDURE,创建存储函数的语句为CREATEFUNCTION。调用存储过程的语句为CALL。调用存储函数的形式就像调用MyS……

  • 从源码的角度深入理解spring AOP原理及流程

    从源码的角度深入理解spring AOP原理及流程

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号