redis实战

短信登录

session共享问题

    redis替代session

  

推荐使用哈希

 @Override
    public Result sendCode(String phone, HttpSession session) {
        //校验手机号
        if(RegexUtils.isPhoneInvalid(phone))
            return Result.fail("手机号格式错误");
        //如果不符合返回错误信息
        //生产验证码
        String code = RandomUtil.randomNumbers(6);
        //保存在redis,设置有效期2分钟,使用哈希
        stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,2, TimeUnit.MINUTES);
        //再返发送
        log.debug("发送短信验证码成功"+code);
        return Result.ok();
    }
private StringRedisTemplate stringRedisTemplate;//key value都必须是String
@Override
    public Result login(LoginFormDTO loginForm, HttpSession session) {
        String phone = loginForm.getPhone();
        //校验手机号
        if(RegexUtils.isPhoneInvalid(phone))
            return Result.fail("手机号格式错误");
        //redis获取校验验证码,报错
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);

        if (cacheCode==null || !cacheCode.toString().equals(loginForm.getCode()))
            return Result.fail("账号与验证码不符合");
        //看手机号查出用户
        User user = query().eq("phone", phone).one();
        //判断用户是否存在,不存在创建并保存
        //保存用户信息到redis
        //随机生成token登录令牌
        String token = UUID.randomUUID().toString(true);
        //将uer对象转为哈希
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        //确保都是String类
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((filedName,filedValue) -> filedValue.toString()));
        //存储
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
        //并设置token有效期30分钟
        stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
        //如果使用则刷新Token有效期

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

第一个拦截一切,作用是得到用户并刷新

第二个拦截器是拦截登录的 

如果采用第一个方案,那么假设我们每次操作数据库后,都操作缓存,但是中间如果没有人查询,那么这个更新动作实际上只有最后一次生效,中间的更新动作意义并不大,我们可以把缓存删除,等待再次查询时,将缓存中的数据加载出来

  • 删除缓存还是更新缓存?

    • 更新缓存:每次更新数据库都更新缓存,无效写操作较多   X

    • 删除缓存:更新数据库时让缓存失效,查询时再更新缓存   

  • 如何保证缓存与数据库的操作的同时成功或失败?

    • 单体系统,将缓存与数据库操作放在一个事务

    • 分布式系统,利用TCC等分布式事务方案

应该具体操作缓存还是操作数据库,我们应当是先操作数据库,再删除缓存,原因在于,如果你选择第一种方案,在两个线程并发来访问时,假设线程1先来,他先把缓存删了,此时线程2过来,他查询缓存数据并不存在,此时他写入缓存,当他写入缓存后,线程1再执行更新动作时,实际上写入的就是旧的数据,新的数据被旧数据覆盖了。

  • 先操作缓存还是先操作数据库?

    • 先删除缓存,再操作数据库      X

    • 先操作数据库,再删除缓存     

 缓存更新策略

{
  "area": "大关",
  "openHours": "10:00-22:00",
  "sold": 4215,
  "address": "金华路锦昌文华苑29号",
  "comments": 3035,
  "avgPrice": 80,
  "score":37,
    "name": "105茶餐厅",
  "typeId": 1,
  "id": 1
}

 缓存移除了

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

缓存穿透(缓存和数据库中都不存在)

缓存穿透问题的解决思路

缓存穿透 :缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

常见的解决方案有两种:

  • 缓存空对象

    • 优点:实现简单,维护方便

    • 缺点:

      • 额外的内存消耗(设置短一点的有效期)

      • 可能造成短期的不一致

  • 布隆过滤

    • 优点:内存占用较少,没有多余key

    • 缺点:

      • 实现复杂

      • 存在误判可能

 左侧存储空、右侧布隆过滤器

选择第一种 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值