1、你们的项目中使用的设计模式有哪些?/你了解设计模式,项目具体怎么用?

回答:用到了哪些设计模式;设计模式是什么,解决什么问题的;怎么用;

1.1、构建者模式(Builder)

是什么:基于 lombok插件实现 @Builder;
解决的问题:可快速构建一个对象,避免大量的setter,提高代码开发效率;
84e25d268fb4f2eca324e976dab3612.png
如何使用:ResponseWrapBuild : 构建者模式的实现体现

  1. /**
  2. * @Description 构造ResponseWrap工具
  3. */
  4. public class ResponseWrapBuild {
  5. public static <T>ResponseWrap<T> build(IBasicEnum basicEnumIntface, T t){
  6. //从UserVoContext中拿到userVoString
  7. String userVoString = UserVoContext.getUserVoString();
  8. UserVo userVo = null;
  9. if (!EmptyUtil.isNullOrEmpty(userVoString)){
  10. userVo = JSONObject.parseObject(userVoString, UserVo.class);
  11. }else {
  12. userVo = new UserVo();
  13. }
  14. //构建对象
  15. return ResponseWrap.<T>builder()
  16. .code(basicEnumIntface.getCode())
  17. .msg(basicEnumIntface.getMsg())
  18. .operationTime(new Date())
  19. .userId(userVo.getId())
  20. .userName(userVo.getUsername())
  21. .datas(t)
  22. .build();
  23. }
  24. }

1.2、工厂模式(Factory):

基于类的全路径生成对应的实现对象,我们现在项目中基于Spring IOC容器完成

1.3、适配器模式(Adapter):

解决不同实现类不能兼容在一起工作的问题
特殊问题总结--面试必问问题 - 图2
适配器设计模式:使接口不兼容的类可以一起工作,我们在项目中使用适配器设计的思想,根据支付渠道适配不同的支付实现类,如支付渠道为支付宝时,我们就匹配支付宝的支付服务实现类,若为微信,则适配微信支付。
解决了不同支付实现类兼容的问题,并且提高了代码的可拓展性和维护性,后期如果有业务需求需新增支付渠道,我们只需在IOC容器中构建的Map添加对应的键值对,并按照三方接口添加对应的配置类及业务代码实现即可。

1.4、策略模式(Strategy)

c26f58d57d712eeb4b68ae3b3876f8a.png
解决了:
1、if/else 过多的问题
2、代码扩展性, 满足开闭原则(对新增开放,对修改关闭)

1.5设计模式优缺点:

缺点:
1、代码结构复杂度变高
2、耗费内存也会变高

优点:
1、代码扩展性
2、代码结构清晰,可维护增强
3、解耦项目的业务

2、你们项目中你印象最深的一个问题是什么? 项目遇到了什么样bug?

  • 双写一致性:时刻保证 mysql和redis库存一致
    在项目中,我们采用预扣方案解决商品超卖问题,我们将商品的库存数据使用redisson的atomiclong缓存到redis中做预扣,再同步更新mysql中的商品库存(atomiclong可保持数据的++i,—i操作的原子性)
    具体实现步骤:
    1、先将所有的菜品对应的库存保存到Redis, AtomicLong(key:dishId, vlaue:num)—>在并发情况下,保持数据++i,—i操作的原子性,使得数据保持一致性
    2、更改数据库的库存之前,先去在redis 预扣
    3、判断 redis中库存 >=0 库存足,可以扣减数据库库存, <0 库存不足,终止当前请求

(四)小结

特殊问题总结--面试必问问题 - 图4
针对读写缓存时:同步直写,更新数据库+更新缓存
特殊问题总结--面试必问问题 - 图5
针对只读缓存时:更新数据库+删除缓存
特殊问题总结--面试必问问题 - 图6
较为通用的一致性策略拟定:

在并发场景下,使用“更新数据库+更新缓存”需要用分布式锁保证缓存和数据一致性,且可能存在“缓存资源浪费”和“机器性能浪费”的情况;一般推荐使用“更新数据库+删除缓存”的方案。如果根据需要,热点数据较多,可以使用“更新数据库+更新缓存”策略。

在“更新数据库+删除缓存”的方案中,推荐使用推荐用“先更新数据库,再删除缓存”策略,因为先删除缓存可能会导致大量请求落到数据库,而且延迟双删的时间很难评估。

在“先更新数据库,再删除缓存”策略中,可以使用“消息队列+重试机制”的方案保证缓存的删除。并通过“订阅binlog”进行缓存比对,加上一层保障。

此外,需要通过初始化缓存预热、多数据源触发、延迟消息比对等策略进行辅助和补偿。【多种数据更新触发源:定时任务扫描,业务系统MQ、binlog变更MQ,相互之间作为互补来保证数据不会漏更新】

三、数据不一致性需注意其他问题

(二)避免其他问题导致缓存服务器崩溃,进而简直导致数据一致性策略失效缓存穿透、缓存击穿、缓存雪崩、机器故障等问题

特殊问题总结--面试必问问题 - 图7

参考链接:
https://blog.csdn.net/yulidrff/article/details/86296714
https://mp.weixin.qq.com/s/LqWiwzr1jdX0N9tAxw9gMQ

3、你们项目中哪些地方涉及到了分布式事务的问题?

1、问题的背景(项目调用链路)
2、解决方案
3、工作原理
4、注意事项

3.1涉及分布式事务的模块及功能:

1、品牌管理:涉及品牌表、附件表,需调用了品牌管理服务、附件上传服务
2、用户管理:涉及用户表、角色表、附件表,需调用了用户管理服务、角色管理服务、附件上传服务
3、菜品管理:涉及菜品表、口味表,需调用菜品管理服务、数据字典查询口味字段
4、开桌操作:涉及桌台表、订单表,需调用桌台管理服务、订单管理服务
5、库存管理:涉及菜品表、订单表、附件表,增删改时会调用菜品管理服务、订单管理服务、附件上传服务
6、购物车管理:涉及菜品表、订单表、附件表,增删改时会调用菜品管理服务、订单管理服务、附件上传服务

3.2解决方案:

采用seata的AT模式的分布式事务进行管理;

3.3工作原理:

特殊问题总结--面试必问问题 - 图8
使用前提: 1. 数据库必须是支持事务的关系型数据库
2. 必须是java项目,并且用JDBC去访问数据库

@GlobalTransactional 标记的方法说明开启了全局事务,seata会为该方法准备一个拦截器
当该方法被调用时,拦截器中 会向TC端 注册一个全局事务信息(xid)
注册好全局事务后,开始执行每个分支事务:
整体流程分为两个阶段;
1. 执行 每一个分支事务
seata为datasource创建了代理对象,每个服务中操作数据库的方法执行时会创建分支事务
执行分支事务时:
seata会先解析要执行的sql语句 生成前置镜像 ==> 执行sql ==> 生成后置镜像 ,将镜像内容存入undolog日志中, 分支事务执行完毕
2. 根据第一阶段结果,提交 或 回滚
如果所有的分支事务都执行成功, 说明全局事务成功,异步的删除undolog日志即可
如果有任何一个环节失败, 根据undolog日志进行事务回滚
回滚时: 会判断当前数据内容和后置镜像是否一致,一致回滚;不一致,就一直重试

3.4注意事项:

seata 会出现脏读问题:
默认 会出现脏读,因为每个分支事务 真实提交了数据库的事务
如果不想出现脏读,可以在select语句后加上for update,
seata重写了语法解析, 会通过判断全局锁的方式 将隔离级别提升为 读已提交
—> 性能不好
seata 不会出现脏写问题:
不会,虽然有脏读,但如果另一个事务拿到脏数据想修改,还是需要获取全局锁才可以
特殊问题总结--面试必问问题 - 图9