黑马点评学习笔记(含代码超详细)

1、包含的表

2、后端部署在tomcat, 前端部署在nginx。单体式项目,要考虑并发能力。若后面想做多并发,可以增加tomcat集群(集群间数据共享的问题)。

3、文件框架:

pox.xml添加依赖;每一个表结构对应一个实体类和一个mapper。Mapper有对应的xml文件写复杂的sql。

由于使用Mybatis-plus,单表增删改查不用写。

Application.yaml文件对文件进行配置。

Utils-工具类/常量类

4、注解:1)@RestController:表示当前类是一个RESTFUL控制器,返回的数据会直接转换为JSON格式。是@Controller和@ResponseBody的组合

2)@RequestMapping(“/shop-type”)这个类处理/shop-type下的所有请求,映射HTTP请求到控制器类或者方法。支持所有HTTP方法。

3)@GetMapping 支持方法,不支持类,是@RequestMapping的简化版本4

4)@Outowired:依赖自动注入

5)@Transactional:Spring Framework提供的一个事务管理注解,声明式地管理数据库事务,通常用于Service层方法或者类上;如果方法正常执行,事务会被提交,数据库修改生效,如果方法抛出运行异常,事务会回滚,数据库不会被修改

6)@Resource:自动注入依赖,类似于autowired,但不局限于spring

7)@Component spring框架中的通用注解,用于标注一个类是Spring容器的组件。

8)@Controller:用于标注控制层

9)@Service:用于标注服务层

10)@Repository:用于标注数据访问层(DAO)

11) @Slf4j: Lombok提供的一个注解,用于在类中自动生成日志对象,简化日志记录操作

12) @Test 标注测试方法,不用手动调试

13)hutool工具类。

14)@RequestParam 从请求的URL连接或者表单数据中获取参数值

5、基于Session实现登录:

短信的发送-基于短信验证码的登录-对登陆状态的校验

Session ID保存在cookie中

拦截器-threadLocal 多线程安全问题

在serviceImpl进行实现

校验手机号:正则表达式检验,调用Utils中功能

生成验证码:使用hotool中randomNumber随机生成验证码。

存入session:session.setAttribute("code", code);

发送验证码:使用阿里云,但是在这里没有具体实现

根据手机号查询用户:MySQL查询,select * from tb_user where phone = ?

应用mybatis-plus,UserServiceImpl继承mybatis-plus提供的ServiceImpl实现无sql语句查询。User user = query().eq(“phone” , phone).one()

创建新用户:

1)创建用户:根据手机号新建,随机创建用户名

2)保存用户:使用Mybatis-plus的save功能

由于基于session,不需要返回登陆凭证,每个session都有对应的session ID,储存在cookie中,请求时带着sessionID,从而找到session并找到用户。

登录状态校验:许多Controller都需要校验登陆状态,但每个controller中都实现校验的业务逻辑太复杂了,使用拦截器,所有请求先到拦截器,将用户信息保存到TreadLocal中,每个请求都是一个独立的线程。拦截器写在Utils中,LoginInterceptor类实现HandlerInterceptor接口(HandlerInterceptor是SpringMVC提供的拦截器接口,拦截HTTP请求,作用1)在请求进入Controller前进行检查2)在Controller执行后进行处理3)在请求完成后进行清理(这里仅实现1+3)

1)在请求进入Controller前检查

3)在请求完成后清理

在Utils新建拦截器后,需要在Config中新建MvcConfig,MvcConfig类实现WebMvcConfigurer接口,用excludePathPatterns排除不需要拦截的路径。

6、隐藏用户敏感信息:

由于User user = UserHolder.getUser();因此获取了UserHolder中的全部用户信息,拦截器直接将信息返回会导致用户隐私泄露,(Session是Tomcat的内存),所以在保存用户到session时,不要将信息完全保存,而是使用hotool的beanUtil(属性拷贝)工具,将user的信息拷贝到UserDTO中。

7、Session共享问题

为应对多线程并发问题,需要Tomcat集群,每个Tomcat有自己的Session,这些Session不共享用户信息,验证码等信息。

Session结构是键值对-key-value。

8、Session->Redis

Key设置为手机号,value设置为验证码。若数据量小,可以使用string,返回json形式数据,如果数据量大,可以用hash,这里使用hash。

在Redis保存用户信息的时候,以token(UUID生成)作为key。 

给Token设置有效期,但此时为设置token后30min过期,需要设置为用户每次访问后30min过期。因此设置拦截器,监控每次用户访问,在拦截器中增加刷新token有效期。

9、缓存

数据交换的缓冲区,读写性能高,降低后端负载,提高读写效率,降低响应时间。

缺点:增加数据一致性成本,增加代码维护成本,缓存需要搭建成集群模式,集群的维护成本较高。

10、添加Redis缓存

@Resource 将Bean注入Spring管理的类中

判断是否存在:StrUtil.isNotBlank()

返回报错:return Result.fail(“店铺不存在!”)

写入Redis: stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop));

11、缓存更新策略

内存淘汰,超时剔除,主动更新

主动更新较复杂,使用Cache Aside Pattern更新策略,由缓存的调用者,在更新数据库的同时更新缓存。

更新缓存和数据库时:

  1. 每次更新数据库时先让缓存失效,查询时再更新缓存。
  2. 为保证缓存和数据库的操作的同时成功或失败:单体系统,将缓存与数据库操作放在一个事务;分布式系统,利用TCC等分布式事务方案。

先操作数据库还是缓存: 

先删除缓存再更新数据库-线程安全问题

先更新数据库再删除缓存-线程安全问题(可能性更低)

高一执行需求:主动更新,以超时剔除作为兜底方案。

12、缓存击穿

热点key过期,导致请求大量直接打到数据库

两种方案:逻辑过期,互斥锁

从Redis中取值,判断该值是否存在使用 StrUtil.isBlank(){}

若命中,需要将json反序列化为对象:

使用Hutool工具库中JSON.Util.toBean方法,将JSON字符串转为Java Bean(对象)

13、缓存工具封装

JSONUtil.toJsonStr(){}将bean对象转为Json字符

设计逻辑过期时间:

redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSecond(time)));

由于unit是不知单位的时间,用toSecond将其转为秒。

不确定返回值是什么类型,使用泛型

缓存穿透,缓存击穿,缓存雪崩,都写到cacheClient工具类中,在实现类中直接调用

14、优惠卷秒杀_全局唯一ID

如果数据库ID自增,会出现:1)ID规律性 明显2)受单表数据量的限制

全局ID生成器,在分布式系统下生成全局唯一ID

不利用Redis自增字符串,基于Redis自增长,而是拼接其他信息:符号位+时间戳+序列号—8个字节,64个比特位

LocalDateTime time = LocalDateTime.of(2022 , 1, 1,1…)

Long second = time.toEpochSecond(ZoneOffset.UTC)
记录开始时间,并且选择时区

Private static final long BEGIN_TIMESTAMP = …;

用当前时间减去开始时间,得到时间戳

将Redis注入

生成的序列号的key每天都不同,日期精确到天作key

如何拼接时间戳和序列号:

全局唯一ID生成策略:UUID(十六进制字符串),Redis自增(总数不超过long,存储空间友好),snowflake算法

15、优惠卷秒杀下单

下单时需要判断:秒杀是否开始或结束,如果尚未开始或已经结束则无法下单;库存是否充足,不足则无法下单。

在controller中注入service实现类,注入后就调用,并输入订单号ID。

将普通卷和秒杀卷的service和实体类分开。

Redis储存以json格式和hash格式。

17、超卖问题

出现问题的原因:

在线程一扣减之前,线程二获取库存为1,导致并发安全问题。

为解决线程安全问题,需要设置悲观锁或乐观锁。

乐观锁:判断查询得到的数据是否被修改过

乐观锁解决超卖:

在扣减库存时,判断此时stock是否等于一开始查询到的stock大小。若等于,则证明在此期间只有一个线程访问,即线程安全。(后改为查询stock是否大于0,若大于零, 则表明可以正常进行)。

如何解决超卖问题:1)悲观锁:添加同步锁,保证线程串行执行

  1. 乐观锁:不加锁,在更新时判断是否有其他线程修改

18、一人一单

做查询:根据优惠卷id和用户id进行查询(userID && voucherId)查询在优惠卷下单库中,是否存在对应的用户和订单号

19、分布式锁

多个线程和JVM配置同一个锁监视器。

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

利用redis可以设置TTL的key。

基本方法:

  1. 获取锁:SETNX lock tread1  确保只有一个线程获取锁
  2. 释放锁:DEL lock  EXPIRE lock 5 设置5s过期时间,超时自动释放,避免死锁(也可手动释放)

SET后可以跟很多操作,使用SET HELP查看。SET lock tread1 EX 10 NX

获取锁失败:return Result.fail(“不允许重复下单”)

获取锁成功:try{}finally{}

释放锁:lock.unlock();

20、误删问题

线程安全问题(如下):在释放锁时,不识别所是否为该线程的,从而误释放其他线程的锁

为解决线程安全问题:增加超时则自动释放锁+释放锁时判断锁是否为该线程对应的锁

如何判断锁是否为该线程对应的锁:1)在获取锁时存入线程标识(用UUID表示)2)在释放锁时先获取锁中的线程表示,判断是否与当前线程标识一致

//生成前缀

Private static final String ID_PREFIX = UUID.randomUUID().toString(true) + “-“;

//调用hutool中的UUID,并使用toSrting删除自动生成的UUID中的下划线。

String threadId = ID_PREFIX + Thread.currentThread().getId();

21、分布式锁的原子性

需要保证判断锁标识和释放锁的原子性,保证这两个操作必须成功

用lua脚本实现流程:

Java调用lua脚本:

 

由两行变为一行,从而保持原子性,避免出现判断锁id一致后阻塞,并在下一个线程进行时释放锁的问题。

22、Redisson

基于Redis Setnx实现的分布式锁存在以下问题

  1. 同一个线程无法多次获取同一个锁
  2. 获取锁只尝试一次就返回false,没有重试机制
  3. 锁超时释放虽然可以避免死锁,但若业务执行耗时较长,会导致锁释放,存在安全隐患

解决方法:不亲自实现以上功能,而是寻找成熟的框架Redisson

1)引入依赖2)配置Redisson客户端

可重入锁:

同一个线程,多次重入

用hash结构,不仅包括KEY和线程id,还包括可重入次

用Lua脚本写保证原子性。

Redisson锁  (wait time , lease time , TimeUnit unit)

23、消息队列

存放消息的队列,包括

  1. 消息队列:存储和管理消息,成为消息代理
  2. 生产者:发送消息到消息队列
  3. 消费者:从消息队列获取消息并处理信息

Redis实现消息队列:list结构,PubSub,Stream

  1. 借助Redis的List结构实现,结合LPUSH,RPOP或者LPOP,RPUSH。

24、达人探店

两张表:探店笔记表,其他用户对探店笔记的评价

探店笔记表

点赞功能

基于Redis的set集合

点赞排行榜:

根据点赞先后顺序排序,返回top5的用户

改成ZSET,不用SET。

 

25、好友关注

26、附近商铺

Redis的GEO,GEOADD添加经纬度,GEOSEARCHSTORE在指定范围内搜索商家,并且按指定点距离排序后返回,范围可为圆形或矩形。

GEO底层使用SORTEDSET。

商家信息存储在MYSQL中,将商家信息导入Redis中,Redis存id和经纬度坐标,查询时到MYSQL中查名字。GEO中有不同类型,如美食或KTV,以typeID作为KEY存在Redis中,对应的value以list形式储存商家id,并用GEO为对应的商家id存储经纬度。

27、用户签到

数据库存储量大,占用空间大,把每个Bit对应当月的每一天,用0和1表示业务状态,这种思路成为位图(BitMap)。Redis中使用string类型数据实现bitMap。

连续签到次数

28、UV统计

HyperLogLog ADD/COUNT 的操作,适合统计不重复的数据。

StringRedisTemplte.opsForHyperLogLog.add();

因为没有真实的这么多独立访客,模拟了1000000条访客数据,每隔1000条,进行重复,最后统计的人数是不重复的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值