当前位置: 首页 > news >正文

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"));
  • 关闭连接
    @AfterEachpublic 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;@Testvoid 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"));}@AfterEachpublic 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.129port: 6379password: 123456lettuce:pool:max-active: 8min-idle: 0max-idle: 8max-wait: 1000
  • 基本使用
@SpringBootTest
public class SpringRedisTest {@Resourceprivate RedisTemplate redisTemplate;@Testpublic void test01(){redisTemplate.opsForValue().set("name","lmx");System.out.println(redisTemplate.opsForValue().get("name"));}
}

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

  • 如果不设置序列化方式,使用原生的redistemplate添加的对象,无法在控制台上获取到,自动实现java对象的序列化与反序列化
  • 在这里插入图片描述
@Configuration
public class RedisConfigure {@Beanpublic 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 {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Testpublic 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完成
    • 流程图
  • 向手机发送验证码功能
@Overridepublic 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();}
  • 登录功能
 @Overridepublic 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 {@Overridepublic 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;}@Overridepublic 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 {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic 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存取用户的信息

缓存

什么是缓存

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

  • 添加商户缓存
    在这里插入图片描述
@Overridepublic 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@Transactionalpublic Result updateByid(Shop shop) {
//        先做校验if (shop == null && shop.getId() == 0L) {return Result.fail("商铺信息或商铺id不能为空");}
//  更新数据库操作updateByid(shop);//        删除缓存stringRedisTemplate.delete(pre+shop.getId());return Result.ok();}

缓存穿透的解决方案

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

 @Overridepublic 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);}

缓存雪崩的问题

在这里插入图片描述

缓存击穿

在这里插入图片描述

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

  • 基于互斥锁的方式
    @Overridepublic 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,则不会创建,返回falsepublic 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;@Resourceprivate 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 {@Resourceprivate VoucherOrderMapper voucherOrderMapper;@Resourceprivate RedisOneID redisOneID;@Overridepublic 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 {@Resourceprivate VoucherOrderMapper voucherOrderMapper;@Resourceprivate RedisOneID redisOneID;@Override@Transactionalpublic 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 尝试获取锁* */@Overridepublic boolean trylocak(Long timeoustSecond) {long id = Thread.currentThread().getId(); // 获取当前线程的id值,充当sentnx的vlaueString ids=ID_PRE+id;Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(KEY_PRE + name, ids + "", timeoustSecond, TimeUnit.SECONDS);//        在进行自动拆箱的过程中,可能会返回null对象,导致发生异常return Boolean.TRUE.equals(aBoolean);}@Overridepublic 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>

相关文章:

Redis学习总结

Redis学习总结 文章目录 Redis学习总结Radis基本介绍docker的安装基本数据结构通用命令字符型key的层次结构Hash类型Listset sortedset集合redis的java客户端jedis的使用jedis连接池的配置 SpringDataRedis自定义redistemplate的序列化与反序列化方式stringtemplate的使用 redi…...

云原生全栈体系(二)

Kubernetes实战入门 第一章 Kubernetes基础概念 一、是什么 我们急需一个大规模容器编排系统kubernetes具有以下特性&#xff1a; 服务发现和负载均衡 Kubernetes 可以使用 DNS 名称或自己的 IP 地址公开容器&#xff0c;如果进入容器的流量很大&#xff0c;Kubernetes 可以负…...

C++设计模式之建造者设计模式

C建造者设计模式 什么是建造者设计模式 建造者设计模式是一种创建型设计模式&#xff0c;它是一种将复杂对象的分解为多个独立部分的模式&#xff0c;以便于构建对象的过程可以被抽象出来并独立变化。 该模式有什么优缺点 优点 灵活性&#xff1a;建造者设计模式允许对象的…...

HDFS Erasure coding-纠删码介绍和原理

HDFS Erasure coding-纠删码介绍和原理 三副本策略弊端Erasure Coding&#xff08;EC&#xff09;简介Reed- Solomon&#xff08;RS&#xff09;码 EC架构 三副本策略弊端 为了提供容错能力&#xff0c;hdfs回根据replication factor&#xff08;复制因子&#xff09;在不同的…...

STM32 DHT11

DHT11 DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。 使用单总线通信 该传感器包括一个电容式感湿元件和一个NTC测温元件&#xff0c;并于一个高性能8位单片机相连&#xff08;模数转换&#xff09;。 DHT11引脚说明 开漏模式下没有输出高电平的能…...

词法分析器

词法分析器 在早期编译1.0时代&#xff0c;我们的目标是完成程序语言到机器语言的翻译&#xff0c;所以重点在编译器前端&#xff0c;于是我们花费大量时间研究词法分析、语法分析、语义分析等内容。如今的本科编译原理课程&#xff0c;基本上也就到这一层面吧。 在编译2.0时…...

【Spring】Spring之启动过程源码解析

概述 我们说的Spring启动&#xff0c;就是构造ApplicationContext对象以及调用refresh()方法的过程。 Spring启动过程主要做了这么几件事情&#xff1a; 构造一个BeanFactory对象解析配置类&#xff0c;得到BeanDefinition&#xff0c;并注册到BeanFactory中 解析ComponentS…...

状态模式(State)

状态模式是一种行为设计模式&#xff0c;允许一个对象在其内部状态改变时改变它的行为&#xff0c;使其看起来修改了自身所属的类。其别名为状态对象(Objects for States)。 State is a behavior design pattern that allows an object to change its behavior when its inter…...

【uniapp】样式合集

1、修改uni-data-checkbox多选框的样式为单选框的样式 我原先是用的单选&#xff0c;但是单选并不支持选中后&#xff0c;再次点击取消选中&#xff1b;所以我改成了多选&#xff0c;然后改变多选样式&#xff0c;让他看起来像单选 在所在使用的页面上修改样式即可 <uni-d…...

【Spring框架】SpringBoot统一功能处理

目录 用户登录权限校验用户登录拦截器排除所有静态资源练习&#xff1a;登录拦截器拦截器实现原理 统一异常处理统一数据返回格式为什么需要统⼀数据返回格式&#xff1f;统⼀数据返回格式的实现 用户登录权限校验 用户登录拦截器 1.自定义拦截器 package com.example.demo.…...

51单片机学习--按键控制流水灯模式定时器时钟

TMOD负责确定T0和T1的工作模式&#xff0c;TCON控制T0和T1的启动或停止计数&#xff0c;同时包含定时器状态 TF1&#xff1a;定时器1溢出标志 TF0&#xff1a;定时器0溢出标志 0~65535 每隔1微秒计数器1&#xff0c;总时间65535微秒&#xff0c;赋上初值64535&#xff0c;则只…...

Django教程_编程入门自学教程_菜鸟教程-免费教程分享

教程简介 Django是一个开放源代码的Web应用框架&#xff0c;由Python写成。采用了MTV的框架模式&#xff0c;即模型M&#xff0c;视图V和模版T。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的&#xff0c;即是CMS&#xff08;内容管理系统&#xf…...

VGG卷积神经网络-笔记

VGG卷积神经网络-笔记 VGG是当前最流行的CNN模型之一&#xff0c; 2014年由Simonyan和Zisserman提出&#xff0c; 其命名来源于论文作者所在的实验室Visual Geometry Group。 测试结果为&#xff1a; 通过运行结果可以发现&#xff0c;在眼疾筛查数据集iChallenge-PM上使用VGG…...

Python爬虫如何实现IP代理池搭建

大家好&#xff0c;作为一名IP代理产品供应商&#xff0c;我知道很多人在使用Python爬虫时遇到了一些麻烦。有时候&#xff0c;我们的爬虫在爬取过程中会被目标网站识别并封禁IP&#xff0c;导致我们的爬取任务受阻。今天我要分享的就是如何搭建一个高效稳定的IP代理池&#xf…...

单例模式:保证一个类只有一个实例

单例模式&#xff1a;保证一个类只有一个实例 什么是单例模式&#xff1f; 在软件开发中&#xff0c;有些类只需要一个实例&#xff0c;比如数据库连接池、线程池等。单例模式就是一种设计模式&#xff0c;用于确保一个类只有一个实例&#xff0c;并提供一个全局访问点。 实…...

【新版系统架构补充】-七层模型

网络功能和分类 计算网络的功能 &#xff1a;数据通信、资源共享、管理集中化、实现分布式处理、负载均衡 网络性能指标&#xff1a;速率、带宽&#xff08;频带宽度或传送线路速率&#xff09;、吞吐量、时延、往返时间、利用率 网络非性能指标&#xff1a;费用、质量、标准化…...

第2章 C语言概述

本章介绍以下内容&#xff1a; 运算符&#xff1a; 函数&#xff1a;main()、printf() 编写一个简单的C程序 创建整型变量&#xff0c;为其赋值并在屏幕上显示其值 换行字符 如何在程序中写注释&#xff0c;创建包含多个函数的程序&#xff0c;发现程序的错误 什么是关键字 C程…...

vscode vue3开发常用插件(附Prettier格式化配置)

必不可少插件(名称可能不全)&#xff1a; 1、Chinese (Simplified) (简体中文) Language 2、Prettier - Code formatter 3、Vue 3 Snippets 4、Vue Language Features (Volar) 可选插件&#xff1a; 5、Auto Close Tag 6、Vue Theme Prettier格式化配置&#xff1a; 按ctr…...

【微信小程序】van-uploader实现文件上传

使用van-uploader和wx.uploadFile实现文件上传&#xff0c;后端使用ThinkPHP。 1、前端代码 json&#xff1a;引入van-uploader {"usingComponents": {"van-uploader": "vant/weapp/uploader/index"} }wxml&#xff1a;deletedFile是删除文件函…...

人工智能在计算机视觉中的应用与挑战

引言 计算机视觉是人工智能领域的一个重要分支&#xff0c;旨在让计算机能够像人一样理解和解释视觉信息&#xff0c;实现图像和视频的自动识别、理解和分析。计算机视觉技术已经在许多领域产生了深远的影响&#xff0c;如人脸识别、自动驾驶、医学影像分析等。本篇博客将深入…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)

漏洞概览 漏洞名称&#xff1a;Apache Flink REST API 任意文件读取漏洞CVE编号&#xff1a;CVE-2020-17519CVSS评分&#xff1a;7.5影响版本&#xff1a;Apache Flink 1.11.0、1.11.1、1.11.2修复版本&#xff1a;≥ 1.11.3 或 ≥ 1.12.0漏洞类型&#xff1a;路径遍历&#x…...

Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档&#xff09;&#xff0c;如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下&#xff0c;风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...

在 Spring Boot 中使用 JSP

jsp&#xff1f; 好多年没用了。重新整一下 还费了点时间&#xff0c;记录一下。 项目结构&#xff1a; pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

用鸿蒙HarmonyOS5实现中国象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...

Linux中《基础IO》详细介绍

目录 理解"文件"狭义理解广义理解文件操作的归类认知系统角度文件类别 回顾C文件接口打开文件写文件读文件稍作修改&#xff0c;实现简单cat命令 输出信息到显示器&#xff0c;你有哪些方法stdin & stdout & stderr打开文件的方式 系统⽂件I/O⼀种传递标志位…...

ubuntu22.04有线网络无法连接,图标也没了

今天突然无法有线网络无法连接任何设备&#xff0c;并且图标都没了 错误案例 往上一顿搜索&#xff0c;试了很多博客都不行&#xff0c;比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动&#xff0c;重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...

【安全篇】金刚不坏之身:整合 Spring Security + JWT 实现无状态认证与授权

摘要 本文是《Spring Boot 实战派》系列的第四篇。我们将直面所有 Web 应用都无法回避的核心问题&#xff1a;安全。文章将详细阐述认证&#xff08;Authentication) 与授权&#xff08;Authorization的核心概念&#xff0c;对比传统 Session-Cookie 与现代 JWT&#xff08;JS…...