1.能够说出Redison框架的作用?

Redisson是一个在Redis的基础上实现的java驻内存数据网格,它不仅提供了一系列的分布式的java常用对象,还提供了许多分布式服务:(redisson是对redis的增强)redisson提供了使用redis的最简单和最便捷的方法。
Redisson提供的服务.png

2.能够说出Redison对象桶有哪些? 什么是对象桶?

通用对象桶(Bucket)
批量通用对象桶(Buckets)
对象桶是用来存放任意类型的对象的容器,每个redisson对象实例都会有一个与之对应的redis数据实例,通过调用get**方法来取得redis数据实例的名称(key),对象桶的属性,方法被封装在BucketOperation类中
可以通过
RBuket接口**实现批量操作多个RBucket对象

3.原子性对象有哪些?

原子整长形
Redisson的分布式整长形atomicLong与java.util.concurrent.atomic.AtomicLong类似
incrementAndGet( )方法先递增再获取元素,类似(++i)
getAndIncrement( )方法先获取元素再递增,类似(i++)
decrementAndGet( )方法先递减再获取元素,类似(—i)
getAndDecrement( )方法先获取元素再递减,类似(i—)
原子双精度浮点
RAtomicDubble弥补了java(atomicDubble)自身的不足
incrementAndGet( )方法先递增再获取元素,类似(++i)
getAndIncrement( )方法先获取元素再递增,类似(i++)
decrementAndGet( )方法先递减再获取元素,类似(—i)
getAndDecrement( )方法先获取元素再递减,类似(i—)

4.redisson-分布式集合数据结构是什么? 主key和辅key有什么作用?

Hash结构,类似与java的Map>,有一个主key和辅key
主key的作用:1.给当前的数据设置过期时间;2.获取当前数据的实例,添加元素;3.对数据进行标记定位。
辅key的作用:1.保证数据的唯一性;2.可以根据辅key获取当前元素的value;3.可以根据辅key对当前数据进行排序
RMap对象和RMapCache对象
RMap实现了java.util.ConcurrentMap接口和java.util.Map接口。但是和HashMap相比,RMap保持了元素的插入顺序。
RMapCache对RMap做了增强,实现了针对单个元素的淘汰机制。应用:为每个子元素添加不同的过期时间
注意:
1.目前的redis自身不支持散列(hash)当中的元素淘汰,所有的过期元素都是通过org.redisson.EvictionScheduler实例来实现定期清理的。
2.为了保证资源(CPU和内存平衡)的有效利用,每次运行最多清理300个过期元素。任务的启动时间将根据上次实际时间清理数量自动调整,间隔时间趋于1秒到1小时之间。比如:此次清理删除了300条元素,那么下次执行清理的时间将在1秒以后(最小间隔时间),但是当此次清理数量少于上次清理数量,时间间隔将增加1.5倍
映射监听器
元素添加事件org.redisson.api.map.event.EntryCreatedListener
元素过期事件org.redisson.api.map.event.EntryExpiredListener
元素删除事件org.redisson.api.map.event.EntryRemovedListener
元素更新事件org.redisson.api.map.event.EntryUpdatedListener
可以实现延迟队列的效果,但是时间不可控(所以不建议选择)。

5.redisson-分布式锁

是为了保证在并发访问的环境中保证数据的一致性,即数据安全

6.原理是什么?

redisson分布式锁实现原理.png
加锁机制
在分布式环境下,多个线程在调用多个服务之前获得锁。获取成功执行lua脚本保存数据到redis数据库中;获取失败,一直通过while循环不断尝试获取锁,直到得到锁。
WatchDog自动延期看门狗机制
情况1:在分布式环境下,加入一个线程得到锁,突然服务器宕机,那么有效时间(默认是30秒)后这个锁就会自动释放,设置有效时间的目的是防止死锁的发生。
情况2:线程A业务还没有执行完,时间就过了,但是线程A还想持有锁的话,就会启动WatchDog后台线程(子线程或守护线程),不断的延长锁key的生存时间
释放锁机制
得到锁的线程,执行unlock( ) 方法后,就释放锁。
lua脚本-保证原子性操作
如果业务逻辑复杂的话,通过封装在lua脚本中后发送给redis,redis是单线程的,这样就可以保证复杂业务逻辑执行的原子性。

7.怎么用?

加锁:
**lock(long leaseTime,TimeUnit unit)**参数:leaseTime 锁有效时间 unit 时间单位(小时、分、秒、毫秒等),有效时间默认30秒;
**tryLock(long waitTime,long leaseTime,TimeUnit Unit)**参数:waitTime 等待时间 leaseTime锁有效时间 unit时间单位(小时、分、秒、毫秒等)。该方法含义:尝试获取锁,获取成功返回true,获取失败返回false,加上参数后,会等待一定的时间,在这个时间段还拿不到锁返回false,拿到锁了返回true。注意:等待时间(waitTime)一定要略大于有效时间(leaseTime)
如果主未释放线程,且当前锁未调用unlock( )方法,则进入watchDog机制;
如果主线程未释放,且当前锁调用unlock( )方法,则直接释放锁。
解锁:
**unlock()**

8.WatchDog自动延期机制是什么?

情况1:在分布式环境下,加入一个线程得到锁,突然服务器宕机,那么有效时间(默认是30秒)后这个锁就会自动释放,设置有效时间的目的是防止死锁的发生。
情况2:线程A业务还没有执行完,时间就过了,但是线程A还想持有锁的话,就会启动WatchDog后台线程(子线程或守护线程),不断的延长锁key的生存时间

9.桌台是否开台的前提条件是什么?

1.当前桌台处于空闲状态且无订单,则认为桌台未开台,可以使用。
2.当前桌台处于使用状态存在(待付款、付款中)的订单,则认为当前桌台已开台,在使用中。

10.查询相关主体时需要统一查询哪些数据? 核心的字段是什么?

餐掌柜开桌所关联的表.png
桌台信息(table_id、table_name),门店信息(store_id、store_name),品牌信息(brand_id、brand_name),菜品分类信息(category_name、store_id),订单信息(table_id、table_name、order_status),菜品信息(id、dish_name、price、dish_number、store_id),菜品口味信息(data_key、dish_id),数字字典(data_key、data_value),文件附件信息(bussiness_id)

11.开桌主要完成哪些操作?

点餐平台开桌及主体信息功能开发.png
餐掌柜开桌功能解析.png
**1.用户扫码,请求携带public ResponseWrap<Boolean> isOpen(@PathVariable("tableId") Long tableId)方法,RPC远程调用dubbo接口
在AppletFace接口(dubbo接口)定义Boolean isOpen(Long tableId);接口方法

2.在DB数据库根据桌台ID查询桌台信息,判断桌台是否在使用中;
在AppletFaceImpl类中(dubbo的服务提供方)具体业务实现:

  1. //1、查询桌台信息,是否为使用中
  2. Table table = tableService.getById(tableId);
  3. Boolean flagTableStatus = table.getTableStatus().equals(SuperConstant.USE);
  4. //2、是否已经有【待支付、支付中】订单存在
  5. OrderVo orderVoResult = orderService.findOrderByTableId(tableId);
  6. Boolean flagOrderVo = !EmptyUtil.isNullOrEmpty(orderVoResult);
  7. if (flagTableStatus||flagOrderVo){
  8. return true;
  9. }
  10. return false;

3.再根据桌台ID在mysql数据库的订单表(tab_order)中,查询该桌台是否存在待付款和付款中的订单,返回结果给客户端该桌台是否已开桌
在IOrderService接口定义OrderVo findOrderByTableId(Long tableId);接口方法。
在OrderServiceImpl类实现业务逻辑,确定当前桌台无待付款或付款中的订单。

  1. //创建查询条件:桌台id,有效,支付中(SuperConstant.DFK)或待支付(SuperConstant.FKZ)
  2. LambdaQueryWrapper<Order> queryWrapper = Wrappers.<Order>lambdaQuery()
  3. .eq(Order::getTableId,tableId)
  4. .eq(Order::getEnableFlag,SuperConstant.YES)
  5. .and(wrapper->wrapper.eq(Order::getOrderState,TradingConstant.FKZ)
  6. .eq(Order::getOrderState,TradingConstant.DFK));
  7. //getOne获得结果
  8. Order order = getOne(queryWrapper);
  9. return BeanConv.toBean(order,OrderVo.class);

4.客户端传入开桌请求携带就餐人数和桌台ID参数

  1. public ResponseWrap<OrderVo> openTable(
  2. @PathVariable("tableId") Long tableId,
  3. @PathVariable("personNumbers") Integer personNumbers)

5.定义已开台状态,给桌台加上分布式锁,防止多并发重复创建订单
1)开桌需要幂等性,需要再一次通过tableId查询订单情况
2)若当前桌台未开桌,先通过tableId查询桌台信息后,使用构造者模式构建该桌台的订单对象,将订单保存到mysql数据库中,再修改桌台状态为使用中。
3)根据tableId调用处理可核算订单项和购物车订单项显示接口

  1. //1、开台状态定义
  2. boolean flag = true;
  3. //2、锁定桌台,防止并发重复创建订单
  4. String key = AppletCacheConstant.OPEN_TABLE_LOCK + tableId;
  5. RLock lock = redissonClient.getLock(key);
  6. try{
  7. if (lock.tryLock(AppletCacheConstant.REDIS_WAIT_TIME,
  8. AppletCacheConstant.REDIS_LEASETIME,
  9. TimeUnit.MINUTES)) {
  10. //3、幂等性:再次查询桌台订单情况
  11. OrderVo orderVoResult = orderService.findOrderByTableId(tableId);
  12. //4、未开台,为桌台创建当订单
  13. if (EmptyUtil.isNullOrEmpty(orderVoResult)) {
  14. //4.1、查询桌台信息
  15. Table table = tableService.getById(tableId);
  16. //4.2、构建订单
  17. Order order = Order.builder()
  18. .tableId(tableId)
  19. .tableName(table.getTableName())
  20. .storeId(table.getStoreId())
  21. .areaId(table.getAreaId())
  22. .enterpriseId(table.getEnterpriseId())
  23. .orderNo((long) identifierGenerator.nextId(tableId))
  24. .orderState(TradingConstant.DFK)
  25. .isRefund(SuperConstant.NO)
  26. .discount(new BigDecimal(10))
  27. .refund(new BigDecimal(0))
  28. .personNumbers(personNumbers)
  29. .reduce(new BigDecimal(0))
  30. .useScore(0)
  31. .acquireScore(0L)
  32. .build();
  33. orderService.save(order);
  34. //5、修改桌台状态为使用中
  35. TableVo tableVo = TableVo.builder()
  36. .id(tableId)
  37. .tableStatus(SuperConstant.USE)
  38. .build();
  39. tableService.updateTable(tableVo);
  40. }
  41. }
  42. //6、订单处理:处理可核算订单项和购物车订单项,可调用桌台订单显示接口
  43. return showOrderVoforTable(tableId);

6.在后端通过table_id查询桌台信息,在桌台信息中取出sotre_id查询门店信息,在门店信息中取出brand_id查询品牌信息,根据store_id查询菜品分类信息,通过table_id查询订单信息,根据store_id查询菜品信息,根据dish_id查询菜品口味信息,在菜品口味中间表中通过dish_id查询出data_key后通过此字段查询数字字典中菜品口味信息,根据brand_id和dish_id分别查询出品牌图片和菜品图片文件附件信息。注意点:因为桌台信息、门店信息、品牌信息、菜品分类信息、订单信息、菜品信息、菜品口味信息、品牌图片、菜品图片,从数据库查询出来是po对象,返回客户端是vo对象,所以需要po转vo,以上信息可以统一封装在AppletInfoVo中,可以通过构造者模式来创建并传入。

12.开桌的实现流程是什么?

点餐平台开桌及主体信息功能开发.png
餐掌柜开桌功能解析.png
**1.用户扫码,请求携带public ResponseWrap<Boolean> isOpen(@PathVariable("tableId") Long tableId)方法,RPC远程调用dubbo接口
在AppletFace接口(dubbo接口)定义Boolean isOpen(Long tableId);接口方法

2.在DB数据库根据桌台ID查询桌台信息,判断桌台是否在使用中;
在AppletFaceImpl类中(dubbo的服务提供方)具体业务实现:

  1. //1、查询桌台信息,是否为使用中
  2. Table table = tableService.getById(tableId);
  3. Boolean flagTableStatus = table.getTableStatus().equals(SuperConstant.USE);
  4. //2、是否已经有【待支付、支付中】订单存在
  5. OrderVo orderVoResult = orderService.findOrderByTableId(tableId);
  6. Boolean flagOrderVo = !EmptyUtil.isNullOrEmpty(orderVoResult);
  7. if (flagTableStatus||flagOrderVo){
  8. return true;
  9. }
  10. return false;

3.再根据桌台ID在mysql数据库的订单表(tab_order)中,查询该桌台是否存在待付款和付款中的订单,返回结果给客户端该桌台是否已开桌
在IOrderService接口定义OrderVo findOrderByTableId(Long tableId);接口方法。
在OrderServiceImpl类实现业务逻辑,确定当前桌台无待付款或付款中的订单。

  1. //创建查询条件:桌台id,有效,支付中(SuperConstant.DFK)或待支付(SuperConstant.FKZ)
  2. LambdaQueryWrapper<Order> queryWrapper = Wrappers.<Order>lambdaQuery()
  3. .eq(Order::getTableId,tableId)
  4. .eq(Order::getEnableFlag,SuperConstant.YES)
  5. .and(wrapper->wrapper.eq(Order::getOrderState,TradingConstant.FKZ)
  6. .eq(Order::getOrderState,TradingConstant.DFK));
  7. //getOne获得结果
  8. Order order = getOne(queryWrapper);
  9. return BeanConv.toBean(order,OrderVo.class);

4.客户端传入开桌请求携带就餐人数和桌台ID参数

  1. public ResponseWrap<OrderVo> openTable(
  2. @PathVariable("tableId") Long tableId,
  3. @PathVariable("personNumbers") Integer personNumbers)

5.定义已开台状态,给桌台加上分布式锁,防止多并发重复创建订单
1)开桌需要幂等性,需要再一次通过tableId查询订单情况
2)若当前桌台未开桌,先通过tableId查询桌台信息后,使用构造者模式构建该桌台的订单对象,将订单保存到mysql数据库中,再修改桌台状态为使用中。
3)根据tableId调用处理可核算订单项和购物车订单项显示接口

  1. //1、开台状态定义
  2. boolean flag = true;
  3. //2、锁定桌台,防止并发重复创建订单
  4. String key = AppletCacheConstant.OPEN_TABLE_LOCK + tableId;
  5. RLock lock = redissonClient.getLock(key);
  6. try{
  7. if (lock.tryLock(AppletCacheConstant.REDIS_WAIT_TIME,
  8. AppletCacheConstant.REDIS_LEASETIME,
  9. TimeUnit.MINUTES)) {
  10. //3、幂等性:再次查询桌台订单情况
  11. OrderVo orderVoResult = orderService.findOrderByTableId(tableId);
  12. //4、未开台,为桌台创建当订单
  13. if (EmptyUtil.isNullOrEmpty(orderVoResult)) {
  14. //4.1、查询桌台信息
  15. Table table = tableService.getById(tableId);
  16. //4.2、构建订单
  17. Order order = Order.builder()
  18. .tableId(tableId)
  19. .tableName(table.getTableName())
  20. .storeId(table.getStoreId())
  21. .areaId(table.getAreaId())
  22. .enterpriseId(table.getEnterpriseId())
  23. .orderNo((long) identifierGenerator.nextId(tableId))
  24. .orderState(TradingConstant.DFK)
  25. .isRefund(SuperConstant.NO)
  26. .discount(new BigDecimal(10))
  27. .refund(new BigDecimal(0))
  28. .personNumbers(personNumbers)
  29. .reduce(new BigDecimal(0))
  30. .useScore(0)
  31. .acquireScore(0L)
  32. .build();
  33. orderService.save(order);
  34. //5、修改桌台状态为使用中
  35. TableVo tableVo = TableVo.builder()
  36. .id(tableId)
  37. .tableStatus(SuperConstant.USE)
  38. .build();
  39. tableService.updateTable(tableVo);
  40. }
  41. }
  42. //6、订单处理:处理可核算订单项和购物车订单项,可调用桌台订单显示接口
  43. return showOrderVoforTable(tableId);

6.在后端通过table_id查询桌台信息,在桌台信息中取出sotre_id查询门店信息,在门店信息中取出brand_id查询品牌信息,根据store_id查询菜品分类信息,通过table_id查询订单信息,根据store_id查询菜品信息,根据dish_id查询菜品口味信息,在菜品口味中间表中通过dish_id查询出data_key后通过此字段查询数字字典中菜品口味信息,根据brand_id和dish_id分别查询出品牌图片和菜品图片文件附件信息。注意点:因为桌台信息、门店信息、品牌信息、菜品分类信息、订单信息、菜品信息、菜品口味信息、品牌图片、菜品图片,从数据库查询出来是po对象,返回客户端是vo对象,所以需要po转vo,以上信息可以统一封装在AppletInfoVo中,可以通过构造者模式来创建并传入。

13.开桌为什么需要加分布式锁? 如果不加分布式锁是否可以? key是什么?

为了在同一时间段保持开桌数据的一致性
不可以
key是 “Applet:openTable:lock + tableId”

  1. String key = AppletCacheConstant.OPEN_TABLE_LOCK + tableId
  2. OPEN_TABLE_LOCK = PREFIX+"openTable:lock:"
  3. PREFIX= "applet:"

14.sys同步锁和分布式锁区别

sys是对jvm层面加锁,即对每个服务加锁。适用于单体架构,本项目是分布式架构,存在很多服务,所以sys依然不能保证开桌数据的安全性。
分布式锁,适用于分布式架构中的,是对调用多个服务统一加一把锁,来保证数据的一致性。使用技术:redisson

15.什么是幂等性? 开桌时为什么需要考虑冪等操作?

幂等性:不管前端请求多少次,得到的结果都是一致的。
是为了保证最终的结果一致,避免脏数据的产生。在本项目中,为了避免开桌时存在还未付款或在付款中的订单。

16.订单创建时机是什么?

是客户扫码开桌成功后,创建一个空白无菜品的订单。

17.什么是可核算订单项? 什么是购物车项? 分别存到哪里?