佛山网站制作专业公司,wordpress 主题js,在库言库建筑网站,苏州互联网公司多吗Redis学习总结 文章目录 Redis学习总结Radis基本介绍docker的安装基本数据结构通用命令字符型key的层次结构Hash类型Listset sortedset集合redis的java客户端jedis的使用jedis连接池的配置 SpringDataRedis自定义redistemplate的序列化与反序列化方式stringtemplate的使用 redi…Redis学习总结 文章目录 Redis学习总结Radis基本介绍docker的安装基本数据结构通用命令字符型key的层次结构Hash类型Listset sortedset集合redis的java客户端jedis的使用jedis连接池的配置 SpringDataRedis自定义redistemplate的序列化与反序列化方式stringtemplate的使用 redis实战开发短信登录 缓存什么是缓存缓存更新策略缓存穿透的解决方案缓存雪崩的问题缓存击穿优惠券秒杀超卖问题一人一单的问题分布式锁Redisson操作 Radis基本介绍
Radis是非关系型数据库常被用作缓存使用。
docker的安装
docker安装redis
基本数据结构 通用命令
字符型 key的层次结构 Hash类型 List set 案例练习
sortedset集合 常见命令 zrank默认是升序其他的也是如此如果想要降序在z后面添加revzrevrank是降序案例
redis的java客户端 jedis的使用
引入依赖
dependencygroupIdredis.clients/groupIdartifactIdjedis/artifactIdversion3.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(namejedis.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(namejedis.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);jdedisppoolnew JedisPool(jedisPoolConfig,192.168.253.129,6379,1000,123456);}// 获取资源public static Jedis getjedis(){return jdedisppool.getResource();}}
SpringDataRedis
引入依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
!-- version2.4.0/version--/dependency!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 --dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId
!-- version2.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 RedisTemplateString,Object redisTemplate(RedisConnectionFactory connectionFactory){
// 创建redistemplate对象RedisTemplateString, 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 (usernull){usernew 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 (attributenull){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) {ArrayListString 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 (shopnull){return Result.fail(无此商铺信息);}String shopstring JSONObject.toJSONString(shop);stringRedisTemplate.opsForValue().set(o,shopstring);return Result.ok(shop);}
缓存更新策略
内存淘汰 超时剔除主动更新 OverrideTransactionalpublic Result updateByid(Shop shop) {
// 先做校验if (shop null shop.getId() 0L) {return Result.fail(商铺信息或商铺id不能为空);}
// 更新数据库操作updateByid(shop);// 删除缓存stringRedisTemplate.delete(preshop.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 shopgetshopWithCatchThrouw(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;timetime32; //时间戳向左移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(“stockstock-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 ServiceImplSeckillVoucherMapper, 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(stockstock-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 ServiceImplSeckillVoucherMapper, SeckillVoucher implements ISeckillVoucherService {Resourceprivate VoucherOrderMapper voucherOrderMapper;Resourceprivate RedisOneID redisOneID;OverrideTransactionalpublic 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){LambdaQueryWrapperVoucherOrder queryWrapper new LambdaQueryWrapper();queryWrapper.eq(VoucherOrder::getUserId, UserHolder.getUser().getId()).eq(VoucherOrder::getVoucherId, voucherId);ListVoucherOrder voucherOrders voucherOrderMapper.selectList(queryWrapper);if (voucherOrders null) {return Result.fail(请勿重复抢购);}// 更新库存数据 判断向前查到的库存与是否发生改变boolean voucher_id update().setSql(stockstock-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 idsID_PREid;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_PREid;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 --
dependencygroupIdorg.redisson/groupIdartifactIdredisson/artifactIdversion3.16.8/version
/dependency