- 1.能够说出Redison框架的作用?
- 2.能够说出Redison对象桶有哪些? 什么是对象桶?
- 3.原子性对象有哪些?
- 4.redisson-分布式集合数据结构是什么? 主key和辅key有什么作用?
- 5.redisson-分布式锁
- 6.原理是什么?
- 7.怎么用?
- 8.WatchDog自动延期机制是什么?
- 9.桌台是否开台的前提条件是什么?
- 10.查询相关主体时需要统一查询哪些数据? 核心的字段是什么?
- 11.开桌主要完成哪些操作?
- 12.开桌的实现流程是什么?
- 13.开桌为什么需要加分布式锁? 如果不加分布式锁是否可以? key是什么?
- 14.sys同步锁和分布式锁区别
- 15.什么是幂等性? 开桌时为什么需要考虑冪等操作?
- 16.订单创建时机是什么?
- 17.什么是可核算订单项? 什么是购物车项? 分别存到哪里?
1.能够说出Redison框架的作用?
Redisson是一个在Redis的基础上实现的java驻内存数据网格,它不仅提供了一系列的分布式的java常用对象,还提供了许多分布式服务:(redisson是对redis的增强)redisson提供了使用redis的最简单和最便捷的方法。
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的作用: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.原理是什么?
加锁机制
在分布式环境下,多个线程在调用多个服务之前获得锁。获取成功:执行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.查询相关主体时需要统一查询哪些数据? 核心的字段是什么?
桌台信息(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.开桌主要完成哪些操作?
**1.用户扫码,请求携带public ResponseWrap<Boolean> isOpen(@PathVariable("tableId") Long tableId)
方法,RPC远程调用dubbo接口
在AppletFace接口(dubbo接口)定义Boolean isOpen(Long tableId);
接口方法
2.在DB数据库根据桌台ID查询桌台信息,判断桌台是否在使用中;
在AppletFaceImpl类中(dubbo的服务提供方)具体业务实现:
//1、查询桌台信息,是否为使用中
Table table = tableService.getById(tableId);
Boolean flagTableStatus = table.getTableStatus().equals(SuperConstant.USE);
//2、是否已经有【待支付、支付中】订单存在
OrderVo orderVoResult = orderService.findOrderByTableId(tableId);
Boolean flagOrderVo = !EmptyUtil.isNullOrEmpty(orderVoResult);
if (flagTableStatus||flagOrderVo){
return true;
}
return false;
3.再根据桌台ID在mysql数据库的订单表(tab_order)中,查询该桌台是否存在待付款和付款中的订单,返回结果给客户端该桌台是否已开桌
在IOrderService接口定义OrderVo findOrderByTableId(Long tableId);
接口方法。
在OrderServiceImpl类实现业务逻辑,确定当前桌台无待付款或付款中的订单。
//创建查询条件:桌台id,有效,支付中(SuperConstant.DFK)或待支付(SuperConstant.FKZ)
LambdaQueryWrapper<Order> queryWrapper = Wrappers.<Order>lambdaQuery()
.eq(Order::getTableId,tableId)
.eq(Order::getEnableFlag,SuperConstant.YES)
.and(wrapper->wrapper.eq(Order::getOrderState,TradingConstant.FKZ)
.eq(Order::getOrderState,TradingConstant.DFK));
//getOne获得结果
Order order = getOne(queryWrapper);
return BeanConv.toBean(order,OrderVo.class);
4.客户端传入开桌请求携带就餐人数和桌台ID参数
public ResponseWrap<OrderVo> openTable(
@PathVariable("tableId") Long tableId,
@PathVariable("personNumbers") Integer personNumbers)
5.定义已开台状态,给桌台加上分布式锁,防止多并发重复创建订单
1)开桌需要幂等性,需要再一次通过tableId查询订单情况
2)若当前桌台未开桌,先通过tableId查询桌台信息后,使用构造者模式构建该桌台的订单对象,将订单保存到mysql数据库中,再修改桌台状态为使用中。
3)根据tableId调用处理可核算订单项和购物车订单项显示接口
//1、开台状态定义
boolean flag = true;
//2、锁定桌台,防止并发重复创建订单
String key = AppletCacheConstant.OPEN_TABLE_LOCK + tableId;
RLock lock = redissonClient.getLock(key);
try{
if (lock.tryLock(AppletCacheConstant.REDIS_WAIT_TIME,
AppletCacheConstant.REDIS_LEASETIME,
TimeUnit.MINUTES)) {
//3、幂等性:再次查询桌台订单情况
OrderVo orderVoResult = orderService.findOrderByTableId(tableId);
//4、未开台,为桌台创建当订单
if (EmptyUtil.isNullOrEmpty(orderVoResult)) {
//4.1、查询桌台信息
Table table = tableService.getById(tableId);
//4.2、构建订单
Order order = Order.builder()
.tableId(tableId)
.tableName(table.getTableName())
.storeId(table.getStoreId())
.areaId(table.getAreaId())
.enterpriseId(table.getEnterpriseId())
.orderNo((long) identifierGenerator.nextId(tableId))
.orderState(TradingConstant.DFK)
.isRefund(SuperConstant.NO)
.discount(new BigDecimal(10))
.refund(new BigDecimal(0))
.personNumbers(personNumbers)
.reduce(new BigDecimal(0))
.useScore(0)
.acquireScore(0L)
.build();
orderService.save(order);
//5、修改桌台状态为使用中
TableVo tableVo = TableVo.builder()
.id(tableId)
.tableStatus(SuperConstant.USE)
.build();
tableService.updateTable(tableVo);
}
}
//6、订单处理:处理可核算订单项和购物车订单项,可调用桌台订单显示接口
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.开桌的实现流程是什么?
**1.用户扫码,请求携带public ResponseWrap<Boolean> isOpen(@PathVariable("tableId") Long tableId)
方法,RPC远程调用dubbo接口
在AppletFace接口(dubbo接口)定义Boolean isOpen(Long tableId);
接口方法
2.在DB数据库根据桌台ID查询桌台信息,判断桌台是否在使用中;
在AppletFaceImpl类中(dubbo的服务提供方)具体业务实现:
//1、查询桌台信息,是否为使用中
Table table = tableService.getById(tableId);
Boolean flagTableStatus = table.getTableStatus().equals(SuperConstant.USE);
//2、是否已经有【待支付、支付中】订单存在
OrderVo orderVoResult = orderService.findOrderByTableId(tableId);
Boolean flagOrderVo = !EmptyUtil.isNullOrEmpty(orderVoResult);
if (flagTableStatus||flagOrderVo){
return true;
}
return false;
3.再根据桌台ID在mysql数据库的订单表(tab_order)中,查询该桌台是否存在待付款和付款中的订单,返回结果给客户端该桌台是否已开桌
在IOrderService接口定义OrderVo findOrderByTableId(Long tableId);
接口方法。
在OrderServiceImpl类实现业务逻辑,确定当前桌台无待付款或付款中的订单。
//创建查询条件:桌台id,有效,支付中(SuperConstant.DFK)或待支付(SuperConstant.FKZ)
LambdaQueryWrapper<Order> queryWrapper = Wrappers.<Order>lambdaQuery()
.eq(Order::getTableId,tableId)
.eq(Order::getEnableFlag,SuperConstant.YES)
.and(wrapper->wrapper.eq(Order::getOrderState,TradingConstant.FKZ)
.eq(Order::getOrderState,TradingConstant.DFK));
//getOne获得结果
Order order = getOne(queryWrapper);
return BeanConv.toBean(order,OrderVo.class);
4.客户端传入开桌请求携带就餐人数和桌台ID参数
public ResponseWrap<OrderVo> openTable(
@PathVariable("tableId") Long tableId,
@PathVariable("personNumbers") Integer personNumbers)
5.定义已开台状态,给桌台加上分布式锁,防止多并发重复创建订单
1)开桌需要幂等性,需要再一次通过tableId查询订单情况
2)若当前桌台未开桌,先通过tableId查询桌台信息后,使用构造者模式构建该桌台的订单对象,将订单保存到mysql数据库中,再修改桌台状态为使用中。
3)根据tableId调用处理可核算订单项和购物车订单项显示接口
//1、开台状态定义
boolean flag = true;
//2、锁定桌台,防止并发重复创建订单
String key = AppletCacheConstant.OPEN_TABLE_LOCK + tableId;
RLock lock = redissonClient.getLock(key);
try{
if (lock.tryLock(AppletCacheConstant.REDIS_WAIT_TIME,
AppletCacheConstant.REDIS_LEASETIME,
TimeUnit.MINUTES)) {
//3、幂等性:再次查询桌台订单情况
OrderVo orderVoResult = orderService.findOrderByTableId(tableId);
//4、未开台,为桌台创建当订单
if (EmptyUtil.isNullOrEmpty(orderVoResult)) {
//4.1、查询桌台信息
Table table = tableService.getById(tableId);
//4.2、构建订单
Order order = Order.builder()
.tableId(tableId)
.tableName(table.getTableName())
.storeId(table.getStoreId())
.areaId(table.getAreaId())
.enterpriseId(table.getEnterpriseId())
.orderNo((long) identifierGenerator.nextId(tableId))
.orderState(TradingConstant.DFK)
.isRefund(SuperConstant.NO)
.discount(new BigDecimal(10))
.refund(new BigDecimal(0))
.personNumbers(personNumbers)
.reduce(new BigDecimal(0))
.useScore(0)
.acquireScore(0L)
.build();
orderService.save(order);
//5、修改桌台状态为使用中
TableVo tableVo = TableVo.builder()
.id(tableId)
.tableStatus(SuperConstant.USE)
.build();
tableService.updateTable(tableVo);
}
}
//6、订单处理:处理可核算订单项和购物车订单项,可调用桌台订单显示接口
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”
String key = AppletCacheConstant.OPEN_TABLE_LOCK + tableId
OPEN_TABLE_LOCK = PREFIX+"openTable:lock:"
PREFIX= "applet:"
14.sys同步锁和分布式锁区别
sys是对jvm层面加锁,即对每个服务加锁。适用于单体架构,本项目是分布式架构,存在很多服务,所以sys依然不能保证开桌数据的安全性。
分布式锁,适用于分布式架构中的,是对调用多个服务统一加一把锁,来保证数据的一致性。使用技术:redisson
15.什么是幂等性? 开桌时为什么需要考虑冪等操作?
幂等性:不管前端请求多少次,得到的结果都是一致的。
是为了保证最终的结果一致,避免脏数据的产生。在本项目中,为了避免开桌时存在还未付款或在付款中的订单。