推荐下载操作redis的可视化工具:https://rdm.dev/

NOSQL系统之一,存储结构灵活

  1. 基于key-value进行存储
  2. 读写速度快
  3. 支持多种数据结构:string(字符串),list(列表),hash(哈希),set(无序集合),zset(有序集合)
  4. 支持持久化,通过内容进行存储,也可以存到硬盘
  5. 支持过期时间,支持事务
  6. 一般存储经常进行查询,不经常修改,不是特别重要的数据放到redis作为缓存(读多、写少)

主要流程:

redission netty版本 netty连接redis_redis

引入redis依赖:

<!--        
1、springboot2.0以后默认使用lettuce作为redis的客户端。它使用netty进行网络通信
2、lettuce的bug导致netty堆外内存溢出 -Xmx300m;netty如果没有指定堆外内存,默认使用-Xmx300m
   可以通过-Dio.netty.maxDirectMemory进行设置
   解决方案:不能使用-Dio.netty.maxDirectMemory只去调大堆外内存

1)、升级lettuce客户端。 
2)、切换使用jedis
    redisTemplate:
    lettuce、jedis都是操作redis的最底层客户端。spring再次封装redisTemplate【可以这样理解为redisTemplate是接口,lettuce、jedis是实现类】

->
<!-- 引入redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
<!-- 引入jedis -->
     <dependency>
             <groupId>redis.clients</groupId>
             <artifactId>jedis</artifactId>
     </dependency>

在application配置文件中配置redis:

spring.redis.host:【redis的主机地址】
spring.redis.password:【redis密码】
spring.redis.port:【redis的端口号,默认是6379】

redis操作使用:

/**整合redis
 * 1)、引入data-redis-starter
 * 2)、简单配置redis的host等信息
 * 3)、使用springboot自动配置好的SpringRedisTemplate来操作redis
 *      redis->Map:存放数据key,数据值value
*/


//注入redis
@Autowired
StringRedisTemplate stringRedisTemplate;
   @Test
    public void  teststringRedisTemplate(){
        //给缓存中放json字符串,拿出的json字符串,还要逆转为能用的对象类型,【序列化与反序列化】                       
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        //创建集合        
        Map<String,String> map=new HashMap<>();
        map.put("a","hello");
        //将对象转为json再放入到缓存中【序列化】
        String jsonString = JSON.toJSONString(map);
        //保存,储存数据到redis
        ops.set("hello",jsonString);
        //查询,获取redis里的值
        String hello = ops.get("hello");
        //将从缓存中数据取出转为指定对象格式【反序列化】
        Map<String,String> result= JSON.parseObject(hello,new TypeReference<Map<String,String>>{});
        System.out.println("之前保存的数据是:"+result);
    }

高并发下的redis常见问题:缓存穿透、缓存击穿、缓存雪崩

缓存穿透:查询redis不存在的数据,直接访问数据库也不存在此数据,一直请求,解决方案:允许缓存null值,加入短暂的过期时间

缓存击穿:一些经常查询到的热点数据刚好时间过期,大并发查询直接访问数据库,解决方案:只让一个人去查,其他人等待,查到以后释放锁,其他人获取到锁,先查询到就会有缓存数据,不用去数据库查

缓存雪崩:大批量的数据集体时间过期,直接访问数据库,解决方案多缓存架构或到期时间随机值或快到期时通知延长到期时间

加锁的方式:

1、单体应用加锁

//只要是同一把锁,就能锁住需要这个锁的所有线程
//1、 synchronized (this):springboot所有的组件在容器中都是单利的
//TODO 本地锁:synchronized ,JUC(lock),在分布式情况下,想要锁住所有,必须使用分布式锁

//单体应用加锁
synchronized (this){
//得到锁以后,我们应该再去缓存中确定一次,如果没有才需要继续查询
    ...

}

2、分布式锁

主要流程:

redission netty版本 netty连接redis_redis_02

1)、去redis占坑位加锁,用set,给redis保存一个key-value,只要能加上就能执行  使用set lock 【要set的数据】 NX命令,能占成功的人就会返回ok,获取锁 

//1、占用分布式锁,使用redis占位,如果占位成功返回true
//设置key-value,过期时间,时间格式
//设置过期时间,必须和加锁是同步的,原子的
String uuid = UUID.randomUUID().toString();
Boolean lock=redisTemplate.opsForValue.setIfAbsent("lock",uuid,300,TimeUnit.SECONDS)
if(lock){
  try {
        //执行业务逻辑
        ...

        }finally {
 //解锁一致
 //lua脚本
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
 //删除锁
 Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
}

}else{
    //加锁失败,重试加锁。
    //休眠100ms重试
       
    return 方法 //重新调用方法,自旋的方式

}

2)、使用Redisson

1、引入依赖

<!--       以后使用Redisson作为所有分布式锁,分布式对象等功能框架-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.12.0</version>
        </dependency>

2、配置,通过构建Config对象实例来配置

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;


@Configuration
public class MyRedissonConfig {
    /**
     * 所有对Redisson的使用都是通过RedissonClient
     * @return
     * @throws IOException
     */
    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson() throws IOException {
        //1、创建配置
        Config config = new Config();
        //2、redis地址
        config.useSingleServer().setAddress("redis://192.168.56.10:6379");

        //3、根据Config创建出RedissonClient实例
        //Redis url should start with redis:// or rediss://
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }
}

使用

@Autowired
RedissonClient redisson;

 @ResponseBody
 @GetMapping("/hello")
 public String hello(){
        //1、获取一把锁,只要锁的名字一样,就是同一把锁
        RLock lock = redisson.getLock("my-lock");
        //2、加锁
        //lock.lock();//阻塞式等待,默认加的锁都是30s时间
        //1)、锁的自动续期,如果业务超长,运行期间自动给锁续上新的30s,不用担心业务时间长,锁自动过期被删掉
        //2)、加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认在30s以后自动删除

        lock.lock(10, TimeUnit.SECONDS);//10秒钟自动解锁,自动解锁时间一定要大于业务的执行时间
        //问题:lock.lock(10, TimeUnit.SECONDS)在锁时间到了以后,不会自动续期
        //如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间
        //如果未指定超时时间,就使用30*1000默认时间
        //只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】
        //最佳实战
        //lock.lock(10, TimeUnit.SECONDS);省掉了整个续期操作,手动解锁
        try{
            //3、解锁 假设解锁代码没有运行  rediesson会不会出现死锁
            System.out.println("加锁成功,执行业务.."+Thread.currentThread().getId());
            Thread.sleep(30000);

        }catch (Exception e){

        }
        finally {
            //3、解锁
            System.out.println("释放锁..."+Thread.currentThread().getId());
            lock.unlock();
        }

        return "hello";
    }