最近因为身体原因没怎么学习,深深的体会到身体才是最重要的。以后一定加强锻炼。
切入正题,最近项目中需要实现在线挂号功能,初步设计把排班生成的号源看做库存,挂的号看做一个个的订单,生成了订单自动锁号,十分钟不支付自动取消订单,退回号源。
排班那一套就不做详细说明了。
库存扣减和锁
初步设想有几种方案:
1、代码同步, 例如使用 synchronized,lock 等同步方法,看着貌似挺合理的。
但是synchronized 作用范围是单个jvm实例, 如果做了集群,分布式等,就没用了。
而且synchronized是作用在对象实例上的,如果不是单例,则多个实例间不会同步(这个一般用spring管理bean,默认就是单例)
所以这儿就不考虑使用这种方法。
2、不查询,直接更新
rmcDoctorArrange=getEntity(RmcDoctorArrange.class, doctorArrangeId);
String sql=” update Rmc_Doctor_Arrange t set t.available_Num=(available_Num-1) where ”
+ ” id=? and t.available_Num>’0′ “;
System.out.println(sql);
result=executeSql(sql,newObject[]{doctorArrangeId});
System.out.println(result);if (result<= 0) {//号源不足
opFlag=”0″;returnopFlag;//}
}//记录日志…//其他业务…
3、使用数据库锁,select xx for update(悲观锁)
String sql= “select count(id) from Rmc_Doctor_Arrange where doctor_name = ? for update”;
result=executeSql(sql,newObject[]{doctorArrangeId});if (result > 0) {int count = update table set available = (available – 1) where doctor_name =doctorName ;
}else{return “库存不足”;
}//其他业务
在查询时,避免其他用户以该表进行插入,修改或删除等操作,造成表的不一致性,select ** from t for update 会等待行锁释放之后,返回查询结果。
缺点:统一入口:所有库存操作都需要统一使用 select for update ,这样才会阻塞, 如果另外一个方法还是普通的select, 是不会被阻塞的。
加锁顺序:如果有多个锁,那么加锁顺序要一致,否则会出现死锁.
4、使用CAS, update table set surplus = aa where id = xx and version = y
public voidbuy(String productName, Integer buyQuantity) {//其他校验…
Product product =getByDB(productName);int 影响行数 = update table set surplus = (surplus – buyQuantity) where id = 1 and surplus =查询的剩余数量 ;while (result == 0) {
product=getByDB(productName);if (查询的剩余数量 >buyQuantity) {
影响行数= update table set surplus = (surplus – buyQuantity) where id = 1 and surplus =查询的剩余数量 ;
}else{return “库存不足”;
}
}//记录日志…//其他业务…
}
这儿会造成一个经典的ABA问题,所以我们可以给数据库加上版本号。
update t set surplus = 90 ,version = version+1 where id = x and version = oldVersion ;
项目最终采用了这种方式
5、使用redis等一些分布式锁实现。
订单自动失效
订单失效也经历了几种解决办法。
1、后台线程扫描,业务量大了之后服务器肯定会吃不住的。不考虑
2、考虑过放入redis中,失效时间24小时,如果订单生成,写入库中,删除red is中的订单。(最后考虑到这个项目中redis用处不太大,不想集成进来)
3、定时任务,每分钟定时扫一次,先不说时间上有偏差(用户并不会察觉或者并不怎么关心是否刚好是十分钟取消订单),光是每分钟扫做一下订单表的扫描就觉得可怕。
4、delayedQueue延时队列。
delayedQueue本质是由PriorityQueue和BlockingQueue实现的阻塞优先级队列
我们可以将生成的订单按顺序放入delayedQueue中,设置到期时间,其中的对象只能在到期时才能从队列中取走,它又符合队列的先进先出的设计原理,简直完美的适合订单超时取消。
但是最终也放弃了这种方案,因为它不支持分布式、不支持分布式、不支持分布式。重要的事说三遍。
5、RabbitMQ和ActiveMq
虽然网上都说RabbitMQ性能要优越很多(没有具体测试),但是最终还是选择了ActiveMq,因为考虑到ActiveMq是用java写的,可能会更适合java项目。
ActiveMq
可以去选择适合的版本下载,安装都很简单。
1、集成到spring中。
class=”org.springframework.jms.connection.CachingConnectionFactory”>
class=”org.springframework.jms.listener.DefaultMessageListenerContainer”>
生成订单后将id返回放入队列中。
if(StringUtil.isNotEmpty(id)){
sendTxtMessage(destination,id);
}
public void sendTxtMessage(Destination destination, finalString message) {if (null ==destination) {
destination=jmsTemplate.getDefaultDestination();
}
String delay=null;
delay=ResourceUtil.getConfigByName(“queneDelay”);if(StringUtil.isNotEmpty(delay)){
Long queneDelay=Long.parseLong(delay)*60*1000;
jmsTemplate.convertAndSend((Object)message,new ScheduleMessagePostProcessor(queneDelay));//}//jmsTemplate.send(destination, new MessageCreator() {//public Message createMessage(Session session) throws JMSException {//session.setMessageListener(arg0);//return session.createTextMessage(message);//}//});
System.out.println(“springJMS send text message…”);
}
监听器监听
public class QueueMessageListener implementsMessageListener {
@ResourceprivateRmcRegisterService rmcRegisterService;public voidonMessage(Message message) {
TextMessage tm=(TextMessage) message;try{
System.out.println(“QueueMessageListener监听到了文本消息:\t”
+tm.getText());
String id=tm.getText();
RmcOrder order=rmcRegisterService.getEntity(RmcOrder.class, id);
System.out.println(“order:”+JsonUtil.bean2json(order));
System.out.println(newDate());if(“01”.equals(order.getRmcStatus())){
//取消订单
String sql=” update Rmc_order set rmc_status=? where id=? “;
Integer cont=rmcRegisterService.executeSql(sql,new Object[]{“02”,id});
System.out.println(“count:”+cont);
}
}catch(JMSException e) {
e.printStackTrace();
}
}
}
生成订单后,可以在查看队列的详细信息http://127.0.0.1:8161/admin/。
后期优化方向:成功付款或者手动取消了的订单可否移出队列或者简化这些类型订单的处理?