Redis实战篇(视频学习来自黑马程序员)

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

在浏览器上输入地址访问  http://localhost:8081/shop-type/list 可以看到JSON数据

在这里插入图片描述
nginx不能放在中文目录下,否则会报错
在这里插入图片描述

可以看到nginx文件有一个静态文件夹
在这里插入图片描述在这里插入图片描述

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

打开nginx命令行(这里我是直接输入nginx.exe,我是习惯了,你输入start nginx.exe也可以)
在这里插入图片描述
访问http://localhost:8080/

在这里插入图片描述

一.短信登录功能实现

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
修改这段代码:

在这里插入图片描述

UserService接口类添加方法

在这里插入图片描述UserServiceImpl类中实现接口并重写其方法

其方法逻辑实现步骤:

1.校验手机号
2.如果不符合,返回错误信息
3.符合,生成验证码
4.保存验证码到session
5.发送验证码
6.返回ok

我们首先看一下进行校验的工具类
在这里插入图片描述

package com.hmdp.utils;

import cn.hutool.core.util.StrUtil;

/**
 * @author 虎哥
 */
public class RegexUtils {
    /**
     * 是否是无效手机格式
     * @param phone 要校验的手机号
     * @return true:符合,false:不符合
     */
    public static boolean isPhoneInvalid(String phone){
        return mismatch(phone, RegexPatterns.PHONE_REGEX);
    }
    /**
     * 是否是无效邮箱格式
     * @param email 要校验的邮箱
     * @return true:符合,false:不符合
     */
    public static boolean isEmailInvalid(String email){
        return mismatch(email, RegexPatterns.EMAIL_REGEX);
    }

    /**
     * 是否是无效验证码格式
     * @param code 要校验的验证码
     * @return true:符合,false:不符合
     */
    public static boolean isCodeInvalid(String code){
        return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);
    }

    // 校验是否不符合正则格式
    private static boolean mismatch(String str, String regex){
        if (StrUtil.isBlank(str)) {
            return true;
        }
        return !str.matches(regex);
    }
}

在这里插入图片描述

 @Override
    public Result sendCode(String phone, HttpSession session) {
        // 1.校验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            //2.如果不符合,发送错误信息
            return Result.fail("手机号格式错误");
        }

        //3.如果符合,生成验证码
        String code = RandomUtil.randomNumbers(6);

        //4.保存验证码到session
        session.setAttribute("code", code);

        //5.发送验证码,必须加上@Slf4j注解才有这个方法
        log.debug("发送短信验证码成功, 验证码: {}", code);

        //6.返回ok
        return Result.ok();
 }

测试一下:

在浏览器访问 http://localhost:8080/login.html 可以看到一个短信验证页面

然后输入手机号进行验证
在这里插入图片描述
可以看到验证成功:

在这里插入图片描述

1.实现短信验证码登录功能和注册功能

首先可以看到这个实体类有三个属性(手机号,验证码,密码):

在这里插入图片描述

UserController实现登录功能
在这里插入图片描述
在这里插入图片描述
登录功能实现步骤:

1.校验手机号
2.校验验证码
3.不一致,报错
4.一直,根据手机号进行查询
5.判断用户是否存在
6.保存用户到session

 @Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号格式错误");
        }
        Object cacheCode = session.getAttribute("code");
        String code = loginForm.getCode();
        if (cacheCode == null || cacheCode.toString().equals(code)) {
            return Result.fail("验证码错误");
        }
         //一致,根据用户手机号查询用户select * from tb_user where phone = ?
        User user = query().eq("phone", phone).one();
        // 判断用户是否存在
        if (user == null) {
            // 如果用户不存在,创建新用户并保存。
            user = createUserWithPhone(phone);
        }
        session.setAttribute("user", user);
        return Result.ok();
    }
    // 创建用户
    private User createUserWithPhone(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        save(user);
        return user;
    }

注意
在这里插入图片描述
我们因为继承了ServiceImpl<UserMapper,User> ,所以可以直接调用query()方法和save()方法。

测试一下
在这里插入图片描述
输入验证码,登录成功。
在这里插入图片描述
实现登录校验拦截器功能:
在这里插入图片描述

在这里插入图片描述

实现登录拦截器思路:
1.获取session
2.获取session中的用户
3.判断用户是否存在
4.如果不存在,则进行拦截
5.存在,保存用户信息到ThreadLocal
6.放行

先修改这段代码:
在这里插入图片描述

然后写一个登录拦截器LoginInterceptor
在这里插入图片描述

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        //2.获取session中的用户
        Object user = session.getAttribute("user");
        // 判断用户是否存在
        if (user == null) {
            // 如果用户不存在,则进行拦截
            response.setStatus(401);
            return false;
        }

        //5.存在,保存用户信息到ThreadLocal
        UserHolder.saveUser((User) user);
        //6.放行

        return true;
    }

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

创建配置类MvcConfig实现WebMvcConfigurer接口

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/shop/**",
                        "/voucher/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/blog/hot",
                        "/user/code",
                        "/user/login");
        WebMvcConfigurer.super.addInterceptors(registry);
    }
}

在这里插入图片描述

 @GetMapping("/me")
    public Result me(){
        // TODO 获取当前登录的用户并返回
        User user = UserHolder.getUser();
        return Result.ok(user);
    }

测试一把:
在这里插入图片描述

user对象的内容拷贝到UserDTO对象身上,是为了隐藏一些用户敏感信息,只显示用户不敏感的数据。
在这里插入图片描述
查看以下UserDTO
在这里插入图片描述
这里改回原始的数据
在这里插入图片描述
在这里插入图片描述
测试一下:(这样可以避免敏感信息被访问到)
在这里插入图片描述

2.集群的session共享问题:

在这里插入图片描述

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

在这里插入图片描述

用Redis来代替session
在这里插入图片描述

前端代码分析:
在这里插入图片描述

在这里插入图片描述

保存验证到redis中
在这里插入图片描述

//保存验证码到redis // set key value ex 120
        stringRedisTemplate.opsForValue().set("login:code:" + phone, code, 2, TimeUnit.MINUTES);

代码优化以下:
在这里插入图片描述
在这里插入图片描述

从redis获取缓存对象
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

 // 7.保存用户信息到redis中
        // 7.1.随机生成token,作为登录令牌 /这个UUID是属于 cn.hutool.core.lang.UUID;
        String token = UUID.randomUUID().toString(true);
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        // 7.2.将User对象转为HashMap存储
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);

        String tokenKey = LOGIN_USER_KEY+token;
        // 7.3.存储
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        // 7.4.s设置token有效期
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.返回token
        return Result.ok(token);

在这里插入图片描述
不能使用依赖注入,只能使用构造方法。因为LoginInterceptorRegister使我们自己手动new出来的。但是我们可以在WvcConfig配置类在属性StringRedisTemplate上使用依赖注入进行赋值
在这里插入图片描述

在这里插入图片描述

 @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //HttpSession session = request.getSession();
        //2.获取session中的用户
        //Object user = session.getAttribute("user");


        // 1.获取请求头中的token
        String token = request.getHeader("authorization");

        if (StrUtil.isBlank(token)) {
            response.setStatus(401);
            return false;
        }
        // 2.基于TOKEN获取redis中的用户
        String key = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            // 4.如果用户不存在,则进行拦截
            response.setStatus(401);
            return false;
        }
        // 5.将查询到的Hash数据转换为UserId对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        //6.存在,保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);
        //7.刷新token有效期

        stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        //8.放行

        return true;
    }

测试一下,发现异常。
在这里插入图片描述

java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
	at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:36) ~[spring-data-redis-2.3.9.RELEASE.jar:2.3.9.RELEASE]
	at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:185) ~[spring-data-redis-2.3.9.RELEASE.jar:2.3.9.RELEASE]
	at org.springframework.data.redis.core.DefaultHashOperations.putAll(DefaultHashOperations.java:147) ~[spring-data-redis-2.3.9.RELEASE.jar:2.3.9.RELEASE]
	at com.hmdp.service.impl.UserServiceImpl.login(UserServiceImpl.java:99) ~[classes/:na]
	at com.hmdp.service.impl.UserServiceImpl$$FastClassBySpringCGLIB$$9cac0aa5.invoke(<generated>) ~[classes/:na]
	at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688) ~[spring-aop-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at com.hmdp.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$9b0f4aaa.login(<generated>) ~[classes/:na]
	at com.hmdp.controller.UserController.login(UserController.java:56) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_292]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_292]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_292]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_292]
	at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:190) ~[spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:138) ~[spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:105) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:878) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:792) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1040) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:943) ~[spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) [spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:652) [tomcat-embed-core-9.0.46.jar:4.0.FR]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) [spring-webmvc-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) [tomcat-embed-core-9.0.46.jar:4.0.FR]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) [tomcat-embed-websocket-9.0.46.jar:9.0.46]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) [spring-web-5.2.15.RELEASE.jar:5.2.15.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_292]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_292]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.46.jar:9.0.46]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_292]


在这里插入图片描述

解决办法:
在这里插入图片描述

 Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create()
                .setIgnoreNullValue(true)
                .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));

测试一下:成功运行,登录验证成功

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里我们要改善一些问题,只要用户一直操作,token就不会消失。

在这里插入图片描述

删掉LoginInterceptor原来的代码
在这里插入图片描述
在这里插入图片描述

测试一下:数据库有缓存数据

后来测试发现验证码错误,原因时我少打了一个!号,应该是判断缓存数据与输入的用户数据是否相等,如果不相等,则返回验证码错误。,
在这里插入图片描述

在这里插入图片描述

二.商户查询缓存

在这里插入图片描述

1.添加商品缓存

添加Redis缓存:

在这里插入图片描述

ShopController
在这里插入图片描述

public interface IShopService extends IService<Shop> {

    Result queryById(Long id);
}

实现思路:
1.从redis查询商铺缓存
2.判断是否存在
3.存在,直接返回
4.不存在,根据id查询数据
5.不存在,返回数据库
6.存在,写入redis
7.返回

在这里插入图片描述

在这里插入图片描述

 @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result queryById(Long id) {
        String key = CACHE_SHOP_KEY + id;
        // 1.从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) {
            // 3.存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }

        //4.不存在,根据id查询数据库

        Shop shop = getById(id);

        //5.不存在,返回错误
        if (shop == null) {
            return Result.fail("店铺不存在");
        }

        //6.存在写入redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));

        return Result.ok(shop);
    }

缓存练习题分析:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
先删除缓存,再操作数据库的情况:

先删除缓存,后更新数据库
该方案也会出问题,此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)

请求A进行写操作,删除缓存
请求B查询发现缓存不存在
请求B去数据库查询得到旧值
请求B将旧值写入缓存
请求A将新值写入数据库
上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。

正常的情况下:

在这里插入图片描述

异常情况下:(删缓存很快,但更新数据库很慢,在多线程并发时,会存在线程安全问题)

当数据从数据库写入缓存时在这里插入图片描述

另外一个线程对数据库进行了更新操作。数据库和缓存产生数据不一致问题。
在这里插入图片描述

先操作数据库,再删除缓存的情况下:

在正常情况下:

在这里插入图片描述
异常情况下:

在这里插入图片描述

先操作数据库,再删除缓存这种方案更好。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  @Override
    @Transactional
    public Result update(Shop shop) {
        Long id = shop.getId();
        if (id == null) {
            return Result.fail("店铺id不能为空");
        }
        //1.更新数据库
        updateById(shop);
        //2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
        return null;
    }

在这里插入图片描述

在这里插入图片描述

2.缓存穿透

在这里插入图片描述

在这里插入图片描述

在redis中缓存空值
在这里插入图片描述

 stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);

在这里插入图片描述

为了解决缓存穿透引发的数据不一致问题:

我们需要提前检查redis中key对应的值是否是空值,如果是空值,则返回错误信息。注意这里的null是存在的,只不过是一个空值,它与什么数据都没有有着本质的·区别。
在这里插入图片描述


        // 判断命中的是否是是空值
        if (shopJson != null) {
            // 返回一个错误信息
            return Result.fail("店铺信息不存在!");
        }

在这里插入图片描述

缓存雪崩:

在这里插入图片描述

缓存击穿:

在这里插入图片描述
怎样解决缓存击穿的问题:
在这里插入图片描述

第一种:互斥锁

在这里插入图片描述

第二种(逻辑过期):

在这里插入图片描述

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

拭去心尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值