wy132
wy132
发布于 2024-07-22 / 0 阅读
0
0

黑马点评学习笔记

一、Redis的应用

1 基于Redis实现共享session登录

2 缓存更新策略

主动更新,先写数据库再删缓存

3 缓冲穿透实战

缓存穿透:缓存和数据库都没有数据

采用缓冲空对象方法

4 缓冲雪崩实战

缓存雪崩:大量key同时过期

5 缓冲击穿实战

缓存击穿:缓存没有而数据库有该数据

实践:基于互斥锁方式解决缓存击穿问题

获取锁:商铺id作键,来存储到内存中;setIfAbsent相当于redis中的SETNX key value,当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。来实现锁

String key = CACHE_SHOP_KEY + id;
stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);

释放锁:删除key值就可以了

stringRedisTemplate.delete(key);

实践:基于逻辑过期方式解决缓存击穿问题

:逻辑时间指自己在value上再加多一个时间属性,而不是为key设定过期时间,每当命中该key时,用其value附带的时间属性来判断是否过期。

6 优惠券秒杀功能

Redis实现全局唯一id

全局唯一id特性:唯一性、高可用、高性能、递增性、安全性

实现方法:使用redis自增特性, 满足上面的高可用、高性能、递增性

id组成:该id满足唯一性、安全性

具体实现方法:生成该类型的id,将其存到缓存中。

其他策略:

  1. UUID

  2. 雪花算法

  3. 数据库自增

实践:秒杀下单功能

redis实现订单id全局唯一

超卖问题

高并发引发的问题

解决方法:乐观锁

具体实现方式:

  1. 版本号控制:数据表中加上一个 version 字段来实现的,表示数据被修改的次数,当执行写操作并且写入成功后,version = version + 1,当线程A要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

  2. CAS算法:在更新时,加上where判断值是否等于已经查询的值,非阻塞,无锁

产生问题:成功率降低

解决方法:where判断改为是否大于0

一人一单

在原有的业务上再加上查询,是否存在该用户id和优惠卷id的订单数据

对扣减库存、创建订单业务进行加锁,根据用户id来作条件

7 redis实现分布式锁

分布式锁

  • 满足分布式系统或集群下的多进程可见并互斥的锁

  • 特性:多进程可见、互斥、高可用、高性能、安全性

常见三种实现方法:

redis实现分布式锁

基于setnx实现

基本思路:

基本方法:基于setnx,并设定过期时间

具体流程:将用户id作key,当前运行的线程的id作value

问题1:锁过时释放,另一线程获取锁,原线程将其释放调,产生问题

解决方法:在释放锁时判断是否自己的锁,即根据其value值(线程id)判断

问题2:判断锁一致和释放锁两个动作之间产生阻塞,发生错误

解决:使判断和释放操作具有原子性,使用Lua脚本实现多条Redis命令的原子性

基于setnx方法存在以下问题:

基于Redisson框架

redis优化秒杀

多线程,将业务划分,一部分给redis,一部分给另一线程,实现异步优化

异步优化后的业务

阻塞队列,线程池,java多线程等知识

具体步骤:

  1. 接到请求,先执行Lua脚本,判断是否有资格下单,没有则返回

  2. 有则创建订单信息,并将订单信息放到阻塞队列

  3. 另一线程不断获取阻塞队列的数据,并将数据写入数据库

存在问题:

  1. 阻塞队列基于jvm的内存,高并发下大量订单对象放到队列,造成内存溢出,

  2. 基于内存保存订单对象,服务宕机,导致内存数据丢失,jvm内存没有持久化

Redis消息队列实现异步秒杀

业务划分:跟阻塞队列方法类似,只不过是用消息队列解决业务

Redis实现消息队列:

  • list结构:模拟消息队列

    • 优点:利用redis存储,不受限jvm内存上限,基于redis持久化机制,保障数据安全性,满足消息有序

    • 缺点:无法避免消息丢失,只支持单消费者

  • PubSub:基本的点对点模型,redis的发布订阅机制

    • 优点:支持多生产多消费

    • 缺点:不支持数据持久化,无法避免消息丢失,消息存储有限

  • Stream:比较完善的消息队列模型,是redis的新数据类型

    • 优点:消息可回溯、支持多消费者获取、可阻塞读取

    • 缺点:阻塞读取有消息漏读的风险

基于Stream的消息队列-消费者组

消费者监听消息的基本思路:

实现异步秒杀主要业务

Redis实战

redis完善点赞功能

业务:避免重复点赞

思路:

  1. 用redis中的set存储("blog:liked:" + blog.id,userId)键值对,blog.id映射所有点赞的用户id

  2. 点赞时,判断是否点赞过,根据set存储的结果+-点赞数,避免重复点赞

点赞排行榜

业务:打开blog时,发送请求,查询5名点赞用户,并返回

方法:根据点赞时间排序

将上面的Set存储(blogId,userId)改为用ZSet存储,时间戳作排序规则,在查询

时,查询该ZSet集合的前5名用户id,得到5名用户信息

共同关注功能

业务需求:点击查看某用户信息的共同关注时,显示该用户与本机用户的共同关注的用户

方法:用redis的set存储(用户id,该用户关注的用户id),请求查询时,查询set中本机用户与要查看的用户的并集结果,得到共同关注用户信息

附近商户搜索

思路:

使用redis中的GEO数据结构存储经纬度数据,当查询附近商户请求时,查询redis,按距离排序、分页,得到商户数据

用户签到

签到功能

需求:实现签到请求,将当前用户当天签到信息保存到Redis中

方法:使用BitMap数据结构

  1. 获取当前用户id和日期,拼接作key

  2. 获取今天是这月的第几天,index

  3. 存储到redis中BitMap,(key, index, true),index指定

统计连续签到天数

方法:

  1. 获取当前用户id和日期,拼接作key

  2. 根据key从上面的BitMap数据中取出对应签到数据long

  3. 根据数据和1做与运算来统计

UV统计

UV:独立访客量,用户数

PV:页面访问量

redis中的HyperLogLog用于确认非常大的集合的基数,而不需要存储其所值

技巧

将user数据实现线程共享

public class UserHolder {
    private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();

    public static void saveUser(UserDTO userId){
        tl.set(userId);
    }

    public static UserDTO getUser(){
        return tl.get();
    }

    public static void removeUser(){
        tl.remove();
    }
}

二级拦截

用于不同拦截的需求

这样无论访问什么路径都刷新token时间

一些常用的值尽量用常量来代替

public class SystemConstants {
    public static final String IMAGE_UPLOAD_DIR = "D:\\lesson\\nginx-1.18.0\\html\\hmdp\\imgs\\";
    public static final String USER_NICK_NAME_PREFIX = "user_";
    public static final int DEFAULT_PAGE_SIZE = 5;
    public static final int MAX_PAGE_SIZE = 10;
}

操作Redis中数据

使用StringRedisTemplate工具类,该类指定key,value的类型为string

//从Hash数据中找key对应的数据,返回Map类型
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
//userMap存储user属性对,将其存储到Redis的Hash结构中
stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
//存储简单key,value值
stringRedisTemplate.opsForValue().set(phone, code, 30, TimeUnit.MINUTES);

注解

@TableField(exist = false)

注明非数据库字段属性,不需要数据库存储的属性(暂时属性)

hutool工具类

//将 userDTO类转换为Map类型存储
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);
//验证手机号格式
RegexUtils.isPhoneInvalid(phone)
//生成随机数,比原生的效果更好
RandomUtil.randomNumbers(6)
//时间常量类,多用在指定redis时间的单位
TimeUnit
//随机生成UUID
UUID.randomUUID()
//复制Bean对象
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
//判断对象是字符串是否为空白
StrUtil.isBlank(token)
//json反序列化为对象
RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);


评论