当前位置: 首页 > 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;如人脸识别、自动驾驶、医学影像分析等。本篇博客将深入…...

HTML----列表与表格

一、列表标签1.<ul>:无序列表标签&#xff0c;用来放没有先后顺序的并列内容2.<ol>:有序列表标签&#xff0c;用来存放有明确先后顺序的步骤内容3.<li>:列表项&#xff0c;不管是<ul>还是<ol>里面都只能放.<li>&#xff0c;不能直接写文字…...

SecGPT-14B惊艳效果:对同一CVE编号,SecGPT生成厂商通告、PoC分析、修复验证三段式内容

SecGPT-14B惊艳效果&#xff1a;对同一CVE编号&#xff0c;SecGPT生成厂商通告、PoC分析、修复验证三段式内容 1. 网络安全分析新范式 在网络安全领域&#xff0c;漏洞分析通常需要安全专家投入大量时间查阅资料、编写报告。传统流程中&#xff0c;厂商通告、漏洞利用分析(Po…...

Qwen3-ASR-0.6B垂直场景:方言保护项目中的粤语/闽南语识别实践

Qwen3-ASR-0.6B垂直场景&#xff1a;方言保护项目中的粤语/闽南语识别实践 方言保护面临的最大挑战是什么&#xff1f;不是没有人会说&#xff0c;而是年轻一代听不懂、不会说。当地方言正在以惊人的速度消失&#xff0c;而语音识别技术为我们提供了一种全新的保护方式。 1. 方…...

数字化电价执行错误识别新模式:原理、模型与工程实现

目录 一、研究背景与业务痛点(为什么要做数字化识别) 1.1 电价执行合规的核心意义 1.2 传统电价核查模式的核心痛点(附业务具象化) 1.3 数字化识别模式的核心价值 二、总体模型设计思路(核心逻辑拆解) 三、行业细分与用电行为定性分析(高风险场景聚焦) 3.1 高风险…...

MindSpore 动态图与静态图深度解析

MindSpore 动态图与静态图深度解析前言在深度学习框架的世界里&#xff0c;动态图&#xff08;Dynamic Graph&#xff09;和静态图&#xff08;Static Graph&#xff09;是两种核心的执行模式。它们各有优劣&#xff0c;理解它们的区别对于深度学习开发者来说至关重要。本文将深…...

百元电视盒子如何变身高性能Linux服务器?Armbian系统刷机全攻略

百元电视盒子如何变身高性能Linux服务器&#xff1f;Armbian系统刷机全攻略 【免费下载链接】amlogic-s9xxx-armbian Supports running Armbian on Amlogic, Allwinner, and Rockchip devices. Support a311d, s922x, s905x3, s905x2, s912, s905d, s905x, s905w, s905, s905l,…...

8.4 启动优化与闪屏

App 冷启动速度直接影响用户留存。Flutter 项目的启动优化涉及原生闪屏配置、Dart 代码初始化策略和渲染首帧时间缩短。一、Native Splash Screen 1.1 flutter_native_splash&#xff08;推荐&#xff09; dependencies:flutter_native_splash: ^2.4.0# pubspec.yaml 或 flutte…...

微软在 Windows 手持设备 Xbox 模式测试虚拟鼠标光标,无需第三方软件轻松激活!

微软自研虚拟鼠标光标&#xff0c;提升手持设备操作体验 微软开始在基于 Windows 的手持设备的 Xbox 模式中测试自研的虚拟鼠标光标——“游戏手柄光标”&#xff08;Gamepad Cursor&#xff09;。该功能可将 Xbox Ally X 这类手持设备的左摇杆转变为虚拟鼠标&#xff0c;为用户…...

第5章,[标签 Win32] :GDI 的其他方面的分类

专栏导航 上一篇&#xff1a;第5章&#xff0c;[标签 Win32] &#xff1a;GDI 的基本图形 回到目录 下一篇&#xff1a;无 本节前言 对于本节所讲解的知识&#xff0c;有可能&#xff0c;你会需要时不时地参考本专栏的其它文章。真的遇到了需要参考之前的文章的知识点&…...

信号发生器选型避坑指南:如何根据测试需求选择合适波形/频率范围(附主流型号对比)

信号发生器选型避坑指南&#xff1a;如何根据测试需求选择合适波形/频率范围&#xff08;附主流型号对比&#xff09; 在电子测试测量领域&#xff0c;信号发生器如同乐队的指挥&#xff0c;决定了整个测试系统的节奏与精度。无论是研发新型通信设备&#xff0c;还是调试工业控…...