Redis学习总结

Redis学习总结

文章目录

  • Redis学习总结
    • Radis基本介绍
    • docker的安装
    • 基本数据结构
      • 通用命令
      • 字符型
      • key的层次结构
      • Hash类型
      • List
      • set
    • sortedset集合
      • redis的java客户端
        • jedis的使用
        • jedis连接池的配置
      • SpringDataRedis
      • 自定义redistemplate的序列化与反序列化方式
      • stringtemplate的使用
    • redis实战开发
      • 短信登录
    • 缓存
      • 什么是缓存
      • 缓存更新策略
      • 缓存穿透的解决方案
      • 缓存雪崩的问题
      • 缓存击穿
      • 优惠券秒杀
      • 超卖问题
      • 一人一单的问题
      • 分布式锁
      • Redisson操作

Radis基本介绍

  • Radis是非关系型数据库,常被用作缓存使用。
    在这里插入图片描述

docker的安装

docker安装redis

基本数据结构

在这里插入图片描述

通用命令

字符型

在这里插入图片描述
在这里插入图片描述

key的层次结构

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Hash类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

List

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

set

在这里插入图片描述

在这里插入图片描述

  • 案例练习
    在这里插入图片描述

sortedset集合

在这里插入图片描述

  • 常见命令
  • zrank默认是升序,其他的也是如此,如果想要降序在z后面添加rev
  • zrevrank是降序
  • 案例
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

redis的java客户端

在这里插入图片描述

jedis的使用

  • 引入依赖
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.6.1</version>
</dependency>

  • 建立连接
//         创建jedis对象
        jedis = new Jedis("192.168.253.129",6379);
//         输入连接密码
        jedis.auth("123456");
//        选择数据库
        jedis.select(0);
  • 使用jedis的api,提供的api函数与redis客户端命令一致
//        插入字符串
        jedis.set("name","wangwu");
        System.out.println("name="+jedis.get("name"));

  • 关闭连接
    @AfterEach
    public void after(){
        if (jedis!=null){
            jedis.close();
        }
    }

  • 完整代码
package com.example.dockerfile;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.test.context.SpringBootTest;
import redis.clients.jedis.Jedis;

@SpringBootTest
class DockerfileApplicationTests {


    private Jedis jedis;
    @Test
    void contextLoads() {
//         创建jedis对象
        jedis = new Jedis("192.168.253.129",6379);
//         输入连接密码
        jedis.auth("123456");
//        选择数据库
        jedis.select(0);
//        插入字符串
        jedis.set("name","wangwu");
        System.out.println("name="+jedis.get("name"));

    }

    @AfterEach
    public void after(){
        if (jedis!=null){
            jedis.close();
        }
    }

}


jedis连接池的配置

public class JedisPoolFactory {
    private static final JedisPool jdedisppool ;

    static {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//        设置最大连接数
        jedisPoolConfig.setMaxTotal(8);
//设置最大空闲连接数
        jedisPoolConfig.setMaxIdle(8);
//        设置最小空闲连接数
        jedisPoolConfig.setMinIdle(0);
//        如果长时间空闲,连接池中的对象会被清理
//        设置等待时间
//        Duration duration = new Duration(1000);
        jedisPoolConfig.setMaxWaitMillis(1000);
        jdedisppool=new JedisPool(jedisPoolConfig,"192.168.253.129",6379,1000,"123456");

    }

//    获取资源
    public static Jedis getjedis(){
        return jdedisppool.getResource();
    }

}


SpringDataRedis

  • 引入依赖
 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
<!--            <version>2.4.0</version>-->
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
<!--            <version>2.7.0</version>-->
        </dependency>
  • 配置文件
spring:
  redis:
    host: 192.168.253.129
    port: 6379
    password: 123456
    lettuce:
      pool:
        max-active: 8
        min-idle: 0
        max-idle: 8
        max-wait: 1000
  • 基本使用
@SpringBootTest
public class SpringRedisTest {

    @Resource
    private RedisTemplate redisTemplate;


    @Test
    public void test01(){
        redisTemplate.opsForValue().set("name","lmx");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }
}

自定义redistemplate的序列化与反序列化方式

  • 如果不设置序列化方式,使用原生的redistemplate添加的对象,无法在控制台上获取到,自动实现java对象的序列化与反序列化
  • 在这里插入图片描述
@Configuration
public class RedisConfigure {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
//        创建redistemplate对象
        RedisTemplate<String, Object> stringObjectRedisTemplate = new RedisTemplate<>();

//        设置连接工厂
        stringObjectRedisTemplate.setConnectionFactory(connectionFactory);
//        设置json序列化工具
        GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

//        设置key的序列化方式
        stringObjectRedisTemplate.setKeySerializer(RedisSerializer.string());
        stringObjectRedisTemplate.setHashKeySerializer(RedisSerializer.string());
//        设置值的序列化方式
        stringObjectRedisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        stringObjectRedisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);

        return stringObjectRedisTemplate;
    }
}

stringtemplate的使用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • stringredistemplete使用案例
@SpringBootTest
public class SpringRedisStringTest {

    @Resource
    private StringRedisTemplate stringRedisTemplate;


    @Test
    public void test01(){
        Person person = new Person("李满祥", 18);
        String s = JSONObject.toJSONString(person);

        stringRedisTemplate.opsForValue().set("person",s);
        String person1 = stringRedisTemplate.opsForValue().get("person");
        System.out.println(person1);
        Person person2 = JSONObject.parseObject(person1, Person.class);
        System.out.println(person2);
    }
}

redis实战开发

短信登录

  • 基于session完成
    • 流程图
  • 向手机发送验证码功能
@Override
    public Result SendPhone(String phone, HttpSession session) {
        // 校验手机验证码
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        //生成验证码
        String s = RandomUtil.randomNumbers(4);
        log.info("生成的验证码是:" + s);


//        验证码保存在session中
        session.setAttribute(SavePattern.PHONECODE, s);

        return Result.ok();
    }
  • 登录功能
 @Override
    public Result LoginService(LoginFormDTO loginForm, HttpSession session) {


//        校验手机号
        if (RegexUtils.isPhoneInvalid(loginForm.getPhone())){
            return Result.fail("手机号格式错误");
        }
//        校验验证码
        Object sessioncode = session.getAttribute(SavePattern.PHONECODE);
        if (loginForm.getCode()==null || !loginForm.getCode().equals(sessioncode)){
            return Result.fail("验证码错误");
        }
//        数据库中查询用户
        User user = query().eq("phone", loginForm.getPhone()).one();

//        如果没有该用户,插入数据库
        if (user==null){
            user=new User();
            user.setPhone(loginForm.getPhone());
//            随机生成昵称
            String s = RandomUtil.randomString(10);
            user.setNickName("user_"+s);
            save(user);
        }
//        用户信息保存到session中
        UserDTO userDTO = new UserDTO();
        userDTO.setId(user.getId());
        userDTO.setNickName(user.getNickName());
        session.setAttribute(SavePattern.LOGINUSER,userDTO);
        log.info("登录用户的信息已存入session中");
        return  Result.ok();
    }

  • 在拦截器中拦截获取登录状态请求
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        return HandlerInterceptor.super.preHandle(request, response, handler);
        HttpSession session = request.getSession();

        UserDTO attribute = (UserDTO) session.getAttribute(SavePattern.LOGINUSER);

//        将用户的信息存入thradlocal中
        if (attribute==null){
            response.setStatus(401);

            return false;
        }
//        不放行;
//        身份不通过
        UserHolder.saveUser(attribute);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        UserHolder.removeUser();

        //        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

  • 注册拦截器
package com.hmdp.config;

import com.hmdp.controller.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;

@Configuration
public class Webconfigure implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        ArrayList<String> patterns = new ArrayList<>();
        patterns.add("/user/code");
        patterns.add("/user/login");
        patterns.add("/user/logout");
//        patterns.add("/user/me");
        patterns.add("/shop/**");
        patterns.add("/shop-type/**");
        patterns.add("/upload/**");

        registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns(patterns);

    }
}


    • 集群的session共享问题
      在这里插入图片描述
  • 使用redis代替session,只要将项目中出现session的地方替换成redis即可,但又以下问题需要注意
    • session的过期时间的30分钟,每次访问session都会重置它的过期时间,但是redis中无法自动更新,所以需要手动进行过期时间的更新,例如在前端请求登录状态的时候,后端进行redis的ttl更新操作
    • 需要给前端返回在redis中存取值的key,再次选择使用phone存取验证码,后端生成的token存取用户的信息

缓存

什么是缓存

在这里插入图片描述
在这里插入图片描述

  • 添加商户缓存
    在这里插入图片描述
@Override
    public Result queryByid(Long id) {

        String o = "shop:" + id;
        String s = stringRedisTemplate.opsForValue().get(o);

        if (s!=null){
//            缓存有商铺信息
            Shop shop = JSONObject.parseObject(s, Shop.class);
            return Result.ok(shop);
        }

//        如果不存在,数据库中查询
        Shop shop = query().eq("id", id).one();
        if (shop==null){
            return Result.fail("无此商铺信息");
        }

        String shopstring = JSONObject.toJSONString(shop);

        stringRedisTemplate.opsForValue().set(o,shopstring);

        return Result.ok(shop);


    }

缓存更新策略

  • 内存淘汰 超时剔除,主动更新
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    @Override
    @Transactional
    public Result updateByid(Shop shop) {
//        先做校验
        if (shop == null && shop.getId() == 0L) {
            return Result.fail("商铺信息或商铺id不能为空");
        }
//  更新数据库操作
        updateByid(shop);

//        删除缓存
        stringRedisTemplate.delete(pre+shop.getId());
        
        
        return Result.ok();

    }


缓存穿透的解决方案

在这里插入图片描述
在这里插入图片描述

 @Override
    public Result queryByid(Long id) {


        String o = pre + id;
        String s = stringRedisTemplate.opsForValue().get(o);

//        if (s!=null && ){
//            return Result.fail("d店铺不存在");
//        }

        if (s != null) {
//            解决缓存穿透的问题
            if ("".equals(s)){
                return Result.fail("店铺不存在");
            }
//            缓存有商铺信息
            Shop shop = JSONObject.parseObject(s, Shop.class);
            return Result.ok(shop);
        }

//        如果不存在,数据库中查询
        Shop shop = query().eq("id", id).one();
        if (shop == null) {
//            将空对象写入缓存中,解决缓存穿透的问题
            stringRedisTemplate.opsForValue().set(o,"",2L,TimeUnit.MINUTES);// 有效时间是两分钟
            return Result.fail("无此商铺信息");
        }

        String shopstring = JSONObject.toJSONString(shop);

        stringRedisTemplate.opsForValue().set(o, shopstring);
        stringRedisTemplate.expire(o, 30L, TimeUnit.MINUTES);//设置过期时间为30 分钟

        return Result.ok(shop);


    }


缓存雪崩的问题

在这里插入图片描述

缓存击穿

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 基于互斥锁的方式
    @Override
    public Result queryByid(Long id) {

//        使用缓存穿透的方法解决
//        Shop shop=getshopWithCatchThrouw(id);
//       解决缓存击穿的问题,使用互斥锁
        String o = pre + id;
        String s = stringRedisTemplate.opsForValue().get(o);

//        if (s!=null && ){
//            return Result.fail("d店铺不存在");
//        }

        if (s != null) {
//            解决缓存穿透的问题
            if ("".equals(s)) {
                return Result.fail("店铺不存在");
            }
//            缓存有商铺信息
            Shop shop = JSONObject.parseObject(s, Shop.class);
            return Result.ok(shop);
        }


        Shop shop = null;

        try {
            boolean getlock = getlock();
            if (!getlock) {
//            进行等待
                Thread.sleep(50);
//            进行重试
                queryByid(id);

            }

            shop = query().eq("id", id).one();

            if (shop == null) {
//            将空对象写入缓存中,解决缓存穿透的问题
                stringRedisTemplate.opsForValue().set(o, "", 2L, TimeUnit.MINUTES);// 有效时间是两分钟
                return Result.fail("店铺不存在");
            }



            String shopstring = JSONObject.toJSONString(shop);
            stringRedisTemplate.opsForValue().set(o, shopstring);
            stringRedisTemplate.expire(o, 30L, TimeUnit.MINUTES);//设置过期时间为30 分钟
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }finally {
            unlock();
        }

        
        return Result.ok(shop);


    }

    //    得到锁,使用redis的sentnx方法,如果redis中有key,则不会创建,返回false
    public boolean getlock() {
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock:shop", "1", 10, TimeUnit.SECONDS);
        return aBoolean;
    }

    //    释放锁
    public void unlock() {
        stringRedisTemplate.delete("lock:shop");

    }


在这里插入图片描述

 private final ExecutorService executorService = Executors.newFixedThreadPool(10);
//    解决缓存击穿的问题,使用逻辑过期的方法
    private Shop getshopWithLogicLock(Long id) {
//        查询缓存中是否有数据
//        如果有数据,则判断是否过期,如果没有过期,直接返回
//        如果已过期,进行缓存重建,返回旧数据

        String o = pre + id;
        String s = stringRedisTemplate.opsForValue().get(o);
//        如果未命中,返回空
        if (s == null) {
            return null;
        }

        RedisData redisData = JSONObject.parseObject(s, RedisData.class);
        LocalDateTime ecpiretime = redisData.getEcpiretime();//过期时间
        JSONObject data = (JSONObject) redisData.getData();
        Shop shop = JSON.toJavaObject(data, Shop.class);
//        过期时间在现在时间之前,说明过期
        if (ecpiretime.isBefore(LocalDateTime.now())) {
//            如果获取到锁进行缓存重建,如果没有则,将其他进程在进行缓存重建,直接放回旧数据
            boolean getlock = getlock();
            if (getlock) {
                executorService.submit(() -> {
                    try {
                        saveshopwithlogic(id, 2L);// 设置过期时间是2秒
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e.getMessage());
                    } finally {
                        unlock();
                    }

                });
            }

        }

        return shop;
    }


注:java中的线程池方法:

 private final ExecutorService executorService = Executors.newFixedThreadPool(10);
 executorService.submit(() -> {
                    try {
                        saveshopwithlogic(id, 2L);// 设置过期时间是2秒
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e.getMessage());
                    } finally {
                        unlock();
                    }

                });

优惠券秒杀

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

全局唯一id格式
在这里插入图片描述

@Component
public class RedisOneID {
    //     生成全局唯一id,生成的id 符号位+时间戳+序列号
    private final static long starttime = 1286064000L;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    public  long getLongId(String pre) {
//         获取当前的时间戳
        long l = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
//        long l1 = LocalDateTime.of(2010, 10, 3, 0, 0, 0).toEpochSecond(ZoneOffset.UTC);
//        System.out.println(l1);

//
        long time = l - starttime;

        time=time<<32; //时间戳向左移32=位,空出位置

        String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));

        Long increment = stringRedisTemplate.opsForValue().increment("ice" + pre + format);// 拼接的字符串

        long l1 = time | increment;
        return l1;
    }



}

超卖问题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • boolean voucher_id = update().setSql(“stock=stock-1”).eq(“voucher_id”, voucherId).eq(“stock”,seckillVoucher.getStock())
  • 通过在更新时判断此时的库存是否与开始查询到的库存一致,如果一致说明未被修改,可以更新
package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.SeckillVoucherMapper;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisOneID;
import com.hmdp.utils.UserHolder;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * <p>
 * 秒杀优惠券表,与优惠券是一对一关系 服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2022-01-04
 */
@Service
public class SeckillVoucherServiceImpl extends ServiceImpl<SeckillVoucherMapper, SeckillVoucher> implements ISeckillVoucherService {


    @Resource
    private VoucherOrderMapper voucherOrderMapper;
    @Resource
    private RedisOneID redisOneID;

    @Override
    public Result getseckill(Long voucherId) {
//        查询优惠券
        SeckillVoucher seckillVoucher = query().eq("voucher_id", voucherId).one();
//         判断时间是否开始
        boolean after = seckillVoucher.getBeginTime().isAfter(LocalDateTime.now());
        if (after) {
            return Result.fail("抢购时间未开始");

        }
//         查询库存是否够
        if (seckillVoucher.getStock() < 1) {
            return Result.fail("库存不足");
        }


//         更新库存数据 ,判断向前查到的库存与是否发生改变
        boolean voucher_id = update().setSql("stock=stock-1").eq("voucher_id", voucherId).eq("stock",seckillVoucher.getStock())
                .update();
        if (!voucher_id) {
            return Result.fail("库存扣减失败");
        }

        //        插入订单数据
        VoucherOrder voucherOrder = new VoucherOrder();


        long order = redisOneID.getLongId("order");
        voucherOrder.setId(order);

        voucherOrder.setVoucherId(voucherId);

        voucherOrder.setUserId(UserHolder.getUser().getId());

//         插入订单
        voucherOrderMapper.insert(voucherOrder);


        return Result.ok(order);


    }
}


一人一单的问题

在这里插入图片描述

  • 使用添加悲观锁的方法解决
    @Transactional
    // 给该方法添加事务,事务的提交时机是在方法结束之后才提交,
    // 所以需要将synchronized控制块添加当方法的外围,确保在锁中的代码事务提交之后在释放锁
  • 如果不使用intern方法,那么每次synchronized 中的值就是新的string对象,通过intern方法可以获取到常量池中的唯一备份,确保不同线程的同一用户,synchronized中的值相同
  • 使用@transactional注解进行事务管理时,在类内部调用方法时需要通过当前类的代理对象来获取,不能使用this对象直接调用
  • 需要在启动类上添加@EnableAspectJAutoProxy(exposeProxy = true)注解,暴露代理类
  • //      intern可以获取string常量池中的对象,确保synchronized中锁定的同一个用户
        synchronized (UserHolder.getUser().getId().toString().intern()) {
            ISeckillVoucherService o = (ISeckillVoucherService)AopContext.currentProxy();// @Transactional 对象的原理其实是通过aop代理实现的,
            // 所以在调用方法时需要获取带当前对象的代理对象
            return o.createorder(voucherId,seckillVoucher);
        }
    
~~~java
@Service
public class SeckillVoucherServiceImpl extends ServiceImpl<SeckillVoucherMapper, SeckillVoucher> implements ISeckillVoucherService {


    @Resource
    private VoucherOrderMapper voucherOrderMapper;
    @Resource
    private RedisOneID redisOneID;

    @Override
    @Transactional
    public Result getseckill(Long voucherId) {

        //        查询优惠券
        SeckillVoucher seckillVoucher = query().eq("voucher_id", voucherId).one();
//         判断时间是否开始
        boolean after = seckillVoucher.getBeginTime().isAfter(LocalDateTime.now());
        if (after) {
            return Result.fail("抢购时间未开始");

        }
//         查询库存是否够
        if (seckillVoucher.getStock() < 1) {
            return Result.fail("库存不足");
        }



//      intern可以获取string常量池中的对象,确保synchronized中锁定的同一个用户
        synchronized (UserHolder.getUser().getId().toString().intern()) {
            ISeckillVoucherService o = (ISeckillVoucherService)AopContext.currentProxy();// @Transactional 对象的原理其实是通过aop代理实现的,
            // 所以在调用方法时需要获取带当前对象的代理对象
            return o.createorder(voucherId,seckillVoucher);
        }


    }


    @Transactional
    // 给该方法添加事务,事务的提交时机是在方法结束之后才提交,
    // 所以需要将synchronized控制块添加当方法的外围,确保在锁中的代码事务提交之后在释放锁

    public Result createorder(Long voucherId,SeckillVoucher seckillVoucher){

        LambdaQueryWrapper<VoucherOrder> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(VoucherOrder::getUserId, UserHolder.getUser().getId()).eq(VoucherOrder::getVoucherId, voucherId);
        List<VoucherOrder> voucherOrders = voucherOrderMapper.selectList(queryWrapper);
        if (voucherOrders == null) {
            return Result.fail("请勿重复抢购");
        }

//         更新库存数据 ,判断向前查到的库存与是否发生改变
        boolean voucher_id = update().setSql("stock=stock-1").eq("voucher_id", voucherId).eq("stock", seckillVoucher.getStock())
                .update();
        if (!voucher_id) {
            return Result.fail("库存扣减失败");
        }

        //        插入订单数据
        VoucherOrder voucherOrder = new VoucherOrder();


        long order = redisOneID.getLongId("order");
        voucherOrder.setId(order);

        voucherOrder.setVoucherId(voucherId);

        voucherOrder.setUserId(UserHolder.getUser().getId());
//        voucherOrder.setUserId(1011L);

//         插入订单
        voucherOrderMapper.insert(voucherOrder);


        return Result.ok(order);
    }
}

分布式锁

在这里插入图片描述
在这里插入图片描述

  • 分布式锁方案
    在这里插入图片描述
  • 基于Redis的分布式锁
    在这里插入图片描述
  • 其他的线程将不属于自己的锁释放了,造成并发执行,此时的解决方案
  • 在释放锁时通过判断是否是当前线程的标识
    在这里插入图片描述
  • 解决方案流程图
    在这里插入图片描述
    在这里插入图片描述
public class SimpleRedisLock implements Ilock {


    private String name; // 给哪个对象加锁,的名字
    private StringRedisTemplate redisTemplate;
    private final String KEY_PRE = "lock:";
    private final String ID_PRE= UUID.randomUUID().toString().replace("-","")+"-";
    public SimpleRedisLock(String name, StringRedisTemplate redisTemplate) {
        this.name = name;
        this.redisTemplate = redisTemplate;
    }

    /*
     *
     * @Pgrm 尝试获取锁
     * */
    @Override
    public boolean trylocak(Long timeoustSecond) {


        long id = Thread.currentThread().getId(); // 获取当前线程的id值,充当sentnx的vlaue
        String ids=ID_PRE+id;
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(KEY_PRE + name, ids + "", timeoustSecond, TimeUnit.SECONDS);


//        在进行自动拆箱的过程中,可能会返回null对象,导致发生异常
        return Boolean.TRUE.equals(aBoolean);
    }

    @Override
    public void unlock() {
//        判断当前的id值,是否与缓存内存取的id值相等
        long id = Thread.currentThread().getId();
        String ids= ID_PRE+id;
        String s = redisTemplate.opsForValue().get(KEY_PRE + name);

        if (ids.equals(s)){
            redisTemplate.delete(KEY_PRE + name);
        }

    }
}


  • 存在一种极端情况,当判断锁标识是否相等后,这时,线程发生阻塞(由于垃圾回收的原因),这时在阻塞结束后,因为已经判断过,所以不再判断直接删除,安全隐患发生的时机在于判断之后,未删除之前,所以需要将该操作变为原子操作
if (ids.equals(s)){
            redisTemplate.delete(KEY_PRE + name);
        }

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Redisson操作

在这里插入图片描述
在这里插入图片描述

  • 引入依赖
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.16.8</version>
</dependency>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/58596.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

windows创建占用特定端口程序

默认情况下&#xff0c;远程桌面使用的是3389端口。如果您想将远程桌面端口更改为8005&#xff0c;以达到模拟程序占用端口8005的情况&#xff0c;可以执行以下操作&#xff1a; 如执行以下命令&#xff0c;则1&#xff0c;2&#xff0c;3步相同操作可以跳过&#xff0c;直接往…

【Java】Springboot脚手架生成初始化项目代码

Springboot配置生成初始化项目代码可以通过mvn的mvn archetype:generate 和阿里云原生应用脚手架&#xff08;地址&#xff09;、spring官方提供的start初始化生成页面(地址&#xff09;。 1、mvn archetype:generate 通过mvn选择对应的脚手架可以快速生成初始化代码&#xf…

一次有趣的Webshell分析经历

一次有趣的Webshell分析经历 1.拉取源代码2.解密后门代码3.分析webshell逻辑4.分析404的原因5.附&#xff1a;格式化后的php代码 1.拉取源代码 在对某目标做敏感目录收集时发现对方网站备份源代码在根目录下的 backup.tar.gz&#xff0c;遂下载&#xff0c;先使用D盾分析有没有…

shiro快速入门

文章目录 权限管理什么是权限管理&#xff1f;什么是身份认证&#xff1f;什么是授权&#xff1f; 什么是shiro&#xff1f;shiro的核心架构shiro中的三个核心组件 shiro中的认证shiro中的授权shiro使用默认Ehcache实现缓存shiro使用redis作为缓存实现 权限管理 什么是权限管理…

31.利用linprog 解决 投资问题(matlab程序)

1.简述 语法&#xff1a;[X,FVAL] linprog(f,a,b,Aeq,Beq,LB,UB,X0)&#xff1b; X 为最终解 &#xff0c; FVAL为最终解对应的函数值 *注意&#xff1a;求最大值时&#xff0c;结果FVAL需要取反* f 为决策函数的系数矩阵。 *注意&#xff1a;当所求为最大值…

SpringBoot实现数据库读写分离

SpringBoot实现数据库读写分离 参考博客https://blog.csdn.net/qq_31708899/article/details/121577253 实现原理&#xff1a;翻看AbstractRoutingDataSource源码我们可以看到其中的targetDataSource可以维护一组目标数据源(采用map数据结构)&#xff0c;并且做了路由key与目标…

LeetCode 26 题:删除有序数组的重复项

思路 在写这一个题时&#xff0c;我突然想到了Python中的 set&#xff08;&#xff09;函数可能会有大用处&#xff0c;便选择了用Python写。 set&#xff08;&#xff09;函数可以将列表转化为集合&#xff0c;集合会保证元素的单一性&#xff0c;所以会自动删去相同字符。 …

sqlserver命令插入另一个数据库的数据主键自增

1、数据情况 两个数据库字段是一致的&#xff0c;其中OBJECTID是主键字段&#xff0c;而且两个表都是从1自增排序 2、需求 现在需要将另一个数据库中的数据&#xff0c;通过sqlserver语句的方法&#xff0c;来插入数据&#xff0c;保持自增字段是自增的 解决方法 sqlserv…

基于边缘无线协同感知的低功耗物联网LPIOT技术:赋能智慧园区方案以及数字工厂领域

回到2000年左右&#xff0c;物联网的底层技术支撑还是“ZigBee”&#xff0c;虽然当时ZigBee的终端功耗指标其实也并不庞大&#xff0c;但是&#xff0c;“拓扑复杂导致工程实施难度大”、“网络规模小导致的整体效率低下”都成为限制其发展的主要因素。 LPWAN&#xff0c;新一…

推荐两款github敏感信息搜集工具(gsil、gshark)

推荐两款github敏感信息搜集工具&#xff08;gsil、gshark&#xff09; - 云社区 - 腾讯云 (tencent.com) github敏感信息泄露是很多企业时常忽视的一个问题&#xff0c;国外有一份研究报告显示&#xff0c;在超过24,000份的GitHub公开数据中&#xff0c;发现有数千个文件中可能…

ChatGPT已打破图灵测试,新的测试方法在路上

生信麻瓜的 ChatGPT 4.0 初体验 偷个懒&#xff0c;用ChatGPT 帮我写段生物信息代码 代码看不懂&#xff1f;ChatGPT 帮你解释&#xff0c;详细到爆&#xff01; 如果 ChatGPT 给出的的代码不太完善&#xff0c;如何请他一步步改好&#xff1f; 全球最佳的人工智能系统可以通过…

在windows配置redis的一些错误及解决方案

目录 Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException:用客户端Redis Desktop Manager一样的密码端口&#xff0c;是可以正常连接的&#xff0c;但是运行java程序之后使用接口请求就会报错 Unable to connect to Redis; nested e…

使用正则表达式 移除 HTML 标签后得到字符串

需求分析 后台返回的数据是 这样式的 需要讲html 标签替换 high_light_text: "<span stylecolor:red>OPPO</span> <span stylecolor:red>OPPO</span> 白色 01"使用正则表达式 function stripHTMLTags(htmlString) {return htmlString.rep…

【腾讯云Cloud Studio实战训练营】使用Cloud Studio迅捷开发一个3D家具个性化定制应用

目录 前言&#xff1a; 一、腾讯云 Cloud Studio介绍&#xff1a; 1、接近本地 IDE 的开发体验 2、多环境可选&#xff0c;或连接到云主机 3、随时分享预览效果 4、兼容 VSCode 插件 5、 AI代码助手 二、腾讯云Cloud Studio项目实践&#xff08;3D家具个性化定制应用&…

WPF中自定义Loading图

纯前端方式&#xff0c;通过动画实现Loading样式&#xff0c;如图所示 <Grid Width"35" Height"35" HorizontalAlignment"Center" VerticalAlignment"Center" Name"Loading"><Grid.Resources><DrawingBrus…

程序员之马上结束任务

计算机系的男同学追班里一女同学&#xff0c;结果此女总是躲躲闪闪。 男的看没戏&#xff0c;就另找了一个去追&#xff0c;结果这女的不满意了&#xff0c;质问这男的为啥抛弃她。 男的问&#xff1a;“请教一个电脑问题&#xff0c;如果你点击一个程序&#xff0c;总是提示…

[openCV]基于赛道追踪的智能车巡线方案V1

import cv2 as cv import os import numpy as npimport time# 遍历文件夹函数 def getFileList(dir, Filelist, extNone):"""获取文件夹及其子文件夹中文件列表输入 dir&#xff1a;文件夹根目录输入 ext: 扩展名返回&#xff1a; 文件路径列表""&quo…

ffmpeg.c源码与函数关系分析

介绍 FFmpeg 是一个可以处理音视频的软件&#xff0c;功能非常强大&#xff0c;主要包括&#xff0c;编解码转换&#xff0c;封装格式转换&#xff0c;滤镜特效。FFmpeg支持各种网络协议&#xff0c;支持 RTMP &#xff0c;RTSP&#xff0c;HLS 等高层协议的推拉流&#xff0c…

docker创建镜像并上传云端服务器

docker创建镜像并上传云端服务器 docker容器与镜像的关系1.基本镜像相关文件创建1.1 创建dockerfile文件1.2.创建do.sh文件1.3 创建upload_server_api.py文件1.4 创建upload_server_webui.py文件1.5 文件保存位置 2. 创建镜像操作2.1 创建镜像2.3 创建容器2.2 进入环境容器2.3 …

Jenkins工具系列 —— 启动 Jenkins 服务报错

错误显示 apt-get 安装 Jenkins 后&#xff0c;自动启动 Jenkins 服务报错。 排查原因 直接运行jenkins命令 发现具体报错log&#xff1a;Failed to start Jetty或Failed to bind to 0.0.0.0/0.0.0.0:8080或Address already in use 说明&#xff1a;这里提示的是8080端口号…