IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> springboot整合redis -> 正文阅读

[Java知识库]springboot整合redis

1 springboot整合redis

springboot整合redis时提供了两个模板工具类,StringRedisTemplate和RedisTemplate.

1.1 StringRedisTemplate

(1) 引入相关的依赖

? <dependency>
??????????? <groupId>org.springframework.boot</groupId>
??????????? <artifactId>spring-boot-starter-data-redis</artifactId>
??????? </dependency>

(2)注入StringRedisTemplate该类对象 ?

?@Autowired
?private StringRedisTemplate redisTemplate;

?(3)使用StringRedisTemplate

该类把对每种数据类型的操作,单独封了相应的内部类

 //里面所有的key还是value field它的类型必须都是String类型。
    //因为key和value获取field他们使用的都是String的序列化方式
 @Autowired
    private StringRedisTemplate redisTemplate;
    @Test
    public void Test01(){
        //key操作
        Boolean aBoolean = redisTemplate.hasKey("k1");
        System.out.println(aBoolean);
        Boolean k1 = redisTemplate.delete("k1");
        System.out.println(k1);
        //String操作
        ValueOperations<String, String> forValue = redisTemplate.opsForValue();
        forValue.set("k1","value1",30,  TimeUnit.SECONDS);
        String k11 = forValue.get("k1");
        System.out.println(k11);
        Boolean absent = forValue.setIfAbsent("k2", "xzj", 30, TimeUnit.SECONDS);
        System.out.println(absent);
       // Hash操作
        HashOperations<String, Object, Object> forHash = redisTemplate.opsForHash();
        forHash.put("people","name","张三");
        forHash.put("people","age","22");

        Map<Object, Object> people = forHash.entries("people");
        System.out.println(people);
    }

@Test
    public void test02(){
        //对hash类型的操作。
        HashOperations<String, Object, Object> forHash = redisTemplate.opsForHash();
        forHash.put("k1","name","张三");
        forHash.put("k1","age","15");
        Map<String,String> map=new HashMap<>();
        map.put("name","李四");
        map.put("age","25");
        forHash.putAll("k2",map);

        Object o = forHash.get("k1", "name");
        System.out.println(o);

        Set<Object> k1 = forHash.keys("k1");
        System.out.println(k1);
        List<Object> k11 = forHash.values("k1");
        System.out.println(k11);


        //获取k1对于的所有的field和value
        Map<Object, Object> k12 = forHash.entries("k1");
        System.out.println(k12);

    }

    @Test
    void contextLoads() {
        //删除指定的key
//        redisTemplate.delete();
        //查看所有的key
//        redisTemplate.keys()
        //是否存在指定的key
//         redisTemplate.hasKey()

        //对字符串数据类型的操作ValueOperations
        ValueOperations<String, String> forValue = redisTemplate.opsForValue();
        //存储字符串类型--key  value long unit  setex();
        forValue.set("k1","张三",30, TimeUnit.SECONDS);
        //等价于setnx  存入成功返回true,失败返回false
        Boolean absent = forValue.setIfAbsent("k11", "李四", 30, TimeUnit.SECONDS);
        System.out.println(absent);
        Integer append = forValue.append("k11", "真帅");
        String key = forValue.get("k11");



    }

}

1.2 RedisTemplate

 //当你存储的value类型为对象类型使用redisTemplate
    //存储的value类型为字符串。StringRedisTemplate  验证码
     @Autowired
     private RedisTemplate redisTemplate;

     @Test
     public void test01(){
         //必须认为指定序列化方式
         redisTemplate.setKeySerializer(new StringRedisSerializer());
         redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));

         //对String类型操作类
         ValueOperations forValue = redisTemplate.opsForValue();
         //redis中key和value都变成乱码了。
         //key和value都没有指定序列化方式,默认采用jdk的序列化方式。
         forValue.set("k1","张三");


         //value默认采用jdk,类必须实现序列化接口
         forValue.set("k2",new User(1,"刘德华",22));
     }

}

上面的RedisTemplate需要每次都指定key value以及field的序列化方式,能不能搞一个配置类,已经为RedisTemplate指定好序列化。以后再用就无需指定。

package com.xzj.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;

/**
 * @author xuan
 */
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化  filed value
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.setHashKeySerializer(redisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}
package com.xzj;

import com.xzj.entity.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

@SpringBootTest
class Day0802springbootRedisApplicationTests02 {

    @Test
    void contextLoads() {
    }
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    public void Test01(){
        ValueOperations forValue = redisTemplate.opsForValue();
        forValue.set("k1","张三");
        forValue.set("k2",new User(1,"小杨",23));

    }



}

2 redis的使用场景

2.1 作为缓存

(1)数据存储在内存中,数据查询速度快。可以分摊数据库压力。

(2)什么样的数据适合放入缓存

查询频率比较高,修改频率比较低。

安全系数低的数据

(3)使用redis作为缓存 ?

package com.xzj.service;

import com.xzj.dao.DeptMapper;
import com.xzj.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @author xuan
 */
@Service
public class DeptService  {
    @Autowired
    private DeptMapper deptMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    //@Cacheable(cacheNames = {"dept"},key="#id")
    public Dept findById(Integer id){
        ValueOperations forValue = redisTemplate.opsForValue();
        Object o = forValue.get("dept::" + id);
        if(o!=null){
            return (Dept) o;
        }
        Dept dept = deptMapper.selectById(id);
        if(dept!=null){
            forValue.set("dept::"+id,dept,2, TimeUnit.HOURS);
        }
        return dept;
    }

    public int deleteById(Integer id){
        redisTemplate.delete("dept::"+id);
        int id1 = deptMapper.deleteById(id);
        return id1;
    }

    public Dept insert(Dept dept){
        int insert = deptMapper.insert(dept);
        return dept;
    }

    public Dept update(Dept dept){
        ValueOperations forValue = redisTemplate.opsForValue();
        forValue.set("dept::"+dept.getId(),dept,2, TimeUnit.HOURS);
        int update = deptMapper.updateById(dept);
        return dept;
    }
}

controller层

package com.xzj.controller;

import com.xzj.entity.Dept;
import com.xzj.service.DeptService02;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author xuan
 */
@RestController
public class DeptController {
    @Autowired
    private DeptService02 deptService;

    @GetMapping("findById/{id}")
    public Dept finsById(@PathVariable Integer id){
        Dept byId = deptService.findById(id);
        return byId;
    }

    @GetMapping("insert/{id}/{deptname}/{address}")
    public Dept findInsert(Dept dept){
        Dept insert = deptService.insert(dept);
        return insert;
    }
    @GetMapping("deleteById/{id}")
    public String deleteById(@PathVariable Integer id){
        int row = deptService.deleteById(id);
        return row>0?"删除成功":"删除失败";
    }
}

查看的缓存: 前部分代码相同@before通知,后部分代码也相同后置通知。 我们可以AOP完成缓存代码和业务代码分离。

spring框架它应该也能想到。--使用注解即可完成。解析该注解。

(1)把缓存的配置类加入

 @Bean
 public CacheManager cacheManager(RedisConnectionFactory factory) {
     RedisSerializer<String> redisSerializer = new StringRedisSerializer();
     Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
     //解决查询缓存转换异常的问题
     ObjectMapper om = new ObjectMapper();
     om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
     om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
     jackson2JsonRedisSerializer.setObjectMapper(om);
     // 配置序列化(解决乱码的问题),过期时间600秒
     RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
             .entryTtl(Duration.ofSeconds(600)) //缓存过期10分钟 ---- 业务需求。
             .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))//设置key的序列化方式
             .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) //设置value的序列化
             .disableCachingNullValues();
     RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
             .cacheDefaults(config)
             .build();
     return cacheManager;
 }

(2)使用开启缓存注解

?(3)使用注解

package com.xzj.service;

import com.xzj.dao.DeptMapper;
import com.xzj.entity.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

/**
 * @author xuan
 */
@Service
public class DeptService02 {
    @Autowired
    private DeptMapper deptMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    //业务代码
    //使用查询注解:cacheNames表示缓存的名称 key:唯一标志---dept::key
    //先从缓存中查看key为(cacheNames::key)是否存在,如果存在则不会执行方法体,如果不存在则执行方法体并把方法的返回值存入缓存中
    @Cacheable(cacheNames = {"dept"},key="#id")
    public Dept findById(Integer id){

        Dept dept = deptMapper.selectById(id);

        return dept;
    }
    //先删除缓存在执行方法体。
    @CacheEvict(cacheNames = {"dept"},key = "#id")
    public int deleteById(Integer id){

        int id1 = deptMapper.deleteById(id);
        return id1;
    }

    public Dept insert(Dept dept){
        int insert = deptMapper.insert(dept);
        return dept;
    }
    //这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。
    @CachePut(cacheNames = "dept",key="#dept.id")
    public Dept update(Dept dept){
        int update = deptMapper.updateById(dept);
        return dept;
    }
}

2.2 分布式锁

使用压测工具测试高并发下带来线程安全问题

?我们看到同一个库存被使用了n次。以及数据库中库存为负数。 线程安全问题导致。

?解决方案: 使用 synchronized 或者lock锁

@Service
public class ProductStockServiceImpl2 implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;

    @Override
    public  String decreaseStock(Integer productId) {
              synchronized (this) {
                  //查看该商品的库存数量
                  Integer stock = productStockDao.findStockByProductId(productId);
                  if (stock > 0) {
                      //修改库存每次-1
                      productStockDao.updateStockByProductId(productId);
                      System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                      return "success";
                  } else {
                      System.out.println("扣减失败!库存不足!");
                      return "fail";
                  }
              }

    }
}

使用synchronized 或者lock锁 如果我们搭建了项目集群,那么该锁无效

?使用idea开集群项目

?

?发现又出现: 重复数字以及库存为负数。

@Service
public class ProductStockServiceImpl_redis implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Override
    public  String decreaseStock(Integer productId) {
        ValueOperations<String, String> forValue = redisTemplate.opsForValue();
        Boolean flag = forValue.setIfAbsent("aaa::" + productId, "~~~~~~~~~~~~~~~~~~~~~~");
            if(flag) {
                try {
                    //查看该商品的库存数量
                    Integer stock = productStockDao.findStockByProductId(productId);
                    if (stock > 0) {
                        //修改库存每次-1
                        productStockDao.updateStockByProductId(productId);
                        System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                        return "success";
                    } else {
                        System.out.println("扣减失败!库存不足!");
                        return "fail";
                    }
                }finally {
                    redisTemplate.delete("aaa::" + productId);
                }
            }


        return "服务器正忙,请稍后在试......";
    }
}

3. redis的解决分布式锁的bug

?

可以使用:redission依赖,redission解决redis超时问题的原理

?

为持有锁的线程开启一个守护线程,守护线程会每隔10秒检查当前线程是否还持有锁,如果持有则延迟生存时间。

使用:

?? <dependency>
??????????? <groupId>org.redisson</groupId>
??????????? <artifactId>redisson</artifactId>
??????????? <version>3.13.4</version>
??????? </dependency>
?????? ?
???????? //获取redisson对象并交于spring容器管理
??? @Bean
??? public Redisson redisson(){
??????? Config config =new Config();
??????? config.useSingleServer().
??????????????? setAddress("redis://localhost:6379").
??????????????? //redis默认有16个数据库
??????????????? setDatabase(0);
??????? return (Redisson) Redisson.create(config);
??? }

?

package com.xzj.service.impl;

import com.xzj.dao.ProductStockDao;
import com.xzj.service.ProductStockService;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class ProductStockServiceImpl_redis implements ProductStockService {
    @Autowired
    private ProductStockDao productStockDao;

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private Redisson redisson;
    @Override
    public  String decreaseStock(Integer productId) {
        RLock lock = redisson.getLock("xxx::" + productId);
        try {
            lock.lock(30, TimeUnit.SECONDS);
            //查看该商品的库存数量
            Integer stock = productStockDao.findStockByProductId(productId);
            if (stock > 0) {
                //修改库存每次-1
                productStockDao.updateStockByProductId(productId);
                System.out.println("扣减成功!剩余库存数:" + (stock - 1));
                return "success";
            } else {
                System.out.println("扣减失败!库存不足!");
                return "fail";
            }
        } finally {
            lock.unlock();
        }
    }
}

4. redis中常见的面试题

4.1 什么是缓存穿透?怎么解决?

数据库中没有该记录,缓存中也没有该记录,这时由人恶意大量访问这样的数据。这样就会导致该请求绕过缓存,直接访问数据,从而造成数据库压力过大。

2.解决办法:
?? [1]在controller加数据校验。
?? [2]我们可以在redis中存入一个空对象,而且要设置过期时间不能太长。超过5分钟
?? [3]我们使用布隆过滤器。底层:有一个bitmap数组,里面存储了该表的所有id.

//伪代码
String?get(String?key)?{ //布隆过滤器钟存储的是数据库表钟对应的id
??? String?value?=?redis.get(key);??//先从缓存获取。?
??? if?(value??==?null)?{ //缓存没有命中
??????? if(!bloomfilter.mightContain(key)){//查看布隆过滤器钟是否存在
??????????? return?null;
??????? }else{
??????????? value?=?db.get(key);?//查询数据库
??????????? redis.set(key,?value);
??????? }???
??? }
??? return?value;
}

?

4.2 什么是缓存雪崩?如何解决?

缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
1.什么下会发生缓存雪崩:
? [1]项目刚上线,缓存中没有任何数据
? [2]缓存出现大量过期。
? [3]redis宕机
?
2.解决办法:
?? 1.上线前预先把一些热点数据放入缓存。
?? 2.设置过期时间为散列值
?? 3.搭建redis集群

4.4 什么是缓存击穿?如何解决?

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

缓存击穿解决方案:
1.设置永久不过期。【这种只适合内存】
2.使用互斥锁(mutex key)业界比较常用的做法。

?

4.5 Redis 淘汰策略有哪些?

?

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:29:56  更:2022-08-06 10:32:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 18:19:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码