由于xxx平台上自己的博客已经很久没更新了,一直以来都是用的印象笔记来做工作中知识的积累存根,不知不觉印象笔记里已经有了四、五百遍文章。为了从新开始能与广大攻城狮共同提高技术能力与水平,随决心另起炉灶在新的博客与大家分享

  经过一段时间项目的沉淀之后,对实际应用中的多线程开发及队列使用产生了深厚的兴趣,也将<<java并发编程实战>>仔细的阅读了两三遍,也看了很多并发编程的实践项目,也有了深刻的理解与在实践中合理应用队列、多线程开发的应用场景

  1、真实应用场景描述:

   由于一段时间以来要针对公司整个电商平台包括官网、移动端所有的交易数据进行统计,统计指标包括:pv、uv、实付金额、转化率、毛利率等等,按照各种不同的维度来统计计算出当前交易系统的各个指标的数据,但要求该项目是独立的,没有任务其它资源的协助及接品提供。经过一番xxxx思考讨论之后。业务上决定用以下解决方案:

    A: 用一个定时服务每隔10秒去别的系统数据库抓取上一次查询时间以来新确认的订单(这种订单表示已经支付完在或者客户已经审核确认了),然后将这些订单的唯一编号放入redis队列。

    B: 由于用到了队列,根据经验自然而然的想到了  启动单独的线程去redis队列中不断获取要统计处理的订单编号,然后将获取到的订单编号放入线程池中进行订单的统计任务处理。

    开发实现:

    FetchConfirmOrdersFromErpJob.java

 /**
* 1、从redis中获取上次查询的时间戳
* 2、将当前时间戳放入到redis中,以便 下次按这个时间查询
* 3、去erp订单表查询confirm_time>=上次查询的时间的订单,放入队列中
*/
@Scheduled(cron = "0/30 * * * * ?")
public void start(){
logger.info("FetchConfirmOrdersFromErpJob start................."+ new Date());
StopWatch watch=new StopWatch();
watch.start();
//上次查询的时间
String preQueryTimeStr=this.readRedisService.get(Constans.CACHE_PREQUERYORDERTIME); Date now=new Date();
if(StringUtils.isBlank(preQueryTimeStr)){
preQueryTimeStr=DateFormatUtils.format(DateUtils.addHours(now, -1), Constans.DATEFORMAT_PATTERN_YYYYMMDDHHMMSS);//第一次查询之前一个小时的订单
// preQueryTimeStr="2015-05-07 10:00:00";//本地测试的时候使用
}
//设置当前时间为上次查询的时间
this.writeRedisService.set(Constans.CACHE_PREQUERYORDERTIME, DateFormatUtils.format(now, Constans.DATEFORMAT_PATTERN_YYYYMMDDHHMMSS)); List<Map<String, Object>> confirmOrderIds = this.erpOrderService.selectOrderIdbyConfirmtime(preQueryTimeStr);
if(confirmOrderIds==null){
logger.info("query confirmOrderIds is null,without order data need dealth..........");
return;
}
for (Map<String, Object> map : confirmOrderIds) {
         //将订单编号放入队列中
this.writeRedisService.lpush(Constans.CACHE_ORDERIDS, map.get("channel_orderid").toString());
logger.info("=======lpush orderid:"+map.get("channel_orderid").toString());
} watch.stop();
logger.info("FetchConfirmOrdersFromErpJob end................."+ new Date()+" total cost time:"+watch.getTime()+" dealth data count:"+confirmOrderIds.size());
}

    OrderCalculate.java    队列获取订单线程

 public class OrderCalculate {

     private static final Log logger = LogFactory.getLog(OrderCalculate.class);

     @Autowired
private static WriteRedisService writeRedisService; private static ExecutorService threadPool=Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*4
,new TjThreadFactory("CalculateAmount"));
static{
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
QueuePop.stop();
threadPool.shutdown();
}
}));
} public void init(){
if(writeRedisService==null){
writeRedisService=SpringContext.getBean(WriteRedisService.class);
}
new Thread(new QueuePop(),"OrderIdQueuePop").start();//由于是用redis做的队列,所以只要使用一个线程从队列里拿就ok
} static class QueuePop implements Runnable{ volatile static boolean stop=false; @Override
public void run() {
while(!stop){
//不断循环从队列里取出订单id
String orderId=null;
try {
orderId = writeRedisService.rpop(Constans.CACHE_ORDERIDS);
if(orderId!=null){
logger.info("pop orderId:"+orderId);
                //将获取的订单编号交给订单统计任务处理线程处理
threadPool.submit(new CalculateAmount(Integer.parseInt(orderId),new Date()));
}
} catch (Exception e1) {
logger.error("",e1);
}
//根据上线后的业务反馈来确定是否改成wait/notify策略来及时处理确认的订单
try {
Thread.sleep(10);
} catch (InterruptedException e) {
logger.error("",e);
// Thread.currentThread().interrupt();
//stop=true;//线程被打算继续执行,不应该被关闭,保证该线程永远不会死掉
}
}
} public static void stop(){
stop=true;
} } }

      CalculateAmoiunt.java   订单任务处理

 public class CalculateAmount implements Runnable {
private static final Log logger = LogFactory.getLog(CalculateAmount.class);
private int orderId;
private Date now;//确认时间 这个时间有一定的延迟,基本可以忽略,如果没什么用
private OrderService orderServices;
private OrdHaveProductService ordHaveProductService;
private OrdPayByCashbackService ordPayByCashbackService;
private OrdPayByCouponService ordPayByCouponService;
private OrdPayByGiftCardService ordPayByGiftCardService;
private StatisticsService statisticsService;
private WriteRedisService writeRedisService;
private ReadRedisService readRedisService;
private ErpOrderGoodsService erpOrderGoodsService;
private ErpOrderService erpOrderService; public CalculateAmount(int orderId,Date now) {
super();
this.orderId = orderId;
this.now=now;
orderServices=SpringContext.getBean(OrderService.class);
ordHaveProductService=SpringContext.getBean(OrdHaveProductService.class);
ordPayByCashbackService=SpringContext.getBean(OrdPayByCashbackService.class);
ordPayByCouponService=SpringContext.getBean(OrdPayByCouponService.class);
ordPayByGiftCardService=SpringContext.getBean(OrdPayByGiftCardService.class);
statisticsService=SpringContext.getBean(StatisticsService.class);
writeRedisService=SpringContext.getBean(WriteRedisService.class);
readRedisService=SpringContext.getBean(ReadRedisService.class);
erpOrderGoodsService=SpringContext.getBean(ErpOrderGoodsService.class);
erpOrderService=SpringContext.getBean(ErpOrderService.class);
} @Override
public void run() {
logger.info("CalculateAmount task run start........orderId:"+orderId);
StopWatch watch=new StopWatch();
watch.start();
/**
* 取出订单相关的所有数据同步到统计的库中
*/
//TODO 考虑要不要将下面所有操作放到一个事务里面
List<Map<String, Object>> orders = this.orderServices.selectOrderById(orderId);
if(orders!=null&&orders.size()>0){
Map<String, Object> order = orders.get(0); String orderSN=U.nvl(order.get("OrderSN"));//订单编号
Integer userId=U.nvlInt(order.get("usr_UserID"),null);//用户d
Integer status=U.nvlInt(order.get("Status"),null);//状态
Date createTime=now;//(Date)order.get("CreateTime");//创建时间
Date modifyTime=now;//(Date)order.get("ModifyTime");// 更新时间
BigDecimal discountPrice=U.nvlDecimal(order.get("DiscountPrice"),null);//优惠总额 满减金额
BigDecimal payPrice=U.nvlDecimal(order.get("PayPrice"), null);//实付金额
BigDecimal totalPrice=U.nvlDecimal(order.get("TotalPrice"), null);//总金额 //从erp里查询出订单的确认时间
int dbConfirmTime=0;
try {
dbConfirmTime = this.erpOrderService.selectConfirmTimeByOrderId(orderId);
} catch (Exception e2) {
logger.error("",e2);
}
Date ct=new Date(dbConfirmTime*1000L); int[] dates=U.getYearMonthDayHour(ct);//
if(modifyTime!=null){
dates=U.getYearMonthDayHour(modifyTime);//
}
int year=dates[0];//年
int month=dates[1];//月
int day=dates[2];//日
int hour=dates[3];//小时 String ordersId=orderId+"";//生成订单id //查询订单的来源和搜索引擎关键字
String source="";
String seKeyWords="";
List<OrdersData> orderDataList=this.statisticsService.selectOrdersDataByOrdersId(orderSN);
if(orderDataList!=null&&!orderDataList.isEmpty()){
OrdersData ordersData = orderDataList.get(0);
source=ordersData.getSource();
seKeyWords=ordersData.getSeKeyWords();
} //TODO 将订单入库
ArrayList<RelOrders> relOrdersList = Lists.newArrayList();
RelOrders relOrders=new RelOrders(orderSN,userId+"",Byte.valueOf(status+""),source,seKeyWords,IsCal.未计算.getFlag(),(byte)U.getSimpleYearByYear(year),(byte)month,(byte)day,(byte)hour,ct,createTime,modifyTime);
relOrdersList.add(relOrders); try {
relOrders.setConfirmTime(ct);
//查询RelOrders是否存在
RelOrders dbOrders=this.statisticsService.selectByPrimaryKey(orderSN);
if(dbOrders!=null){
//更新
dbOrders.setStatus(Byte.valueOf(status+""));
dbOrders.setConfirmTime(ct);
dbOrders.setModifyTime(modifyTime);
this.statisticsService.updateByPrimaryKeySelective(dbOrders);
return;
}else{
Integer relResult=this.statisticsService.insertRelOrdersBatch(relOrdersList);
}
} catch (Exception e) {
logger.error("insertRelOrdersBatch error",e);
}
/**
* 查这个订单的返现、优惠券、礼品卡 的金额
*/
List<Map<String, Object>> cashs = this.ordPayByCashbackService.selectDecutionPriceByOrderId(orderId);
List<Map<String, Object>> coupons = this.ordPayByCouponService.selectDecutionPriceByOrderId(orderId); BigDecimal cashAmount=U.getValueByKey(cashs, "DeductionPrice", BigDecimal.class, BigDecimal.ZERO);//返现金额
BigDecimal couponAmont=U.getValueByKey(coupons, "DeductionPrice", BigDecimal.class, BigDecimal.ZERO);//红包金额
/**
* 查询出这个订单的所有商品
*/
List<Map<String, Object>> products=null;
Map<String,Object> productToKeyWordMap=Maps.newHashMap();
try {
products = this.ordHaveProductService.selectByOrderId(orderId);
List<OrdersItemData> ordersItemDataList=this.statisticsService.selectOrdersItemDataByOrdersId(orderSN);
if(ordersItemDataList!=null){
for (OrdersItemData ordersItemData : ordersItemDataList) {
productToKeyWordMap.put(ordersItemData.getItemId(), ordersItemData.getKeyWords());
}
}
} catch (Exception e1) {
logger.error("",e1);
}
if(products!=null){
ArrayList<RelOrdersItem> relOrdersItemList = Lists.newArrayList();
for (Map<String, Object> product : products) {
Integer productId=U.nvlInt(product.get("pro_ProductID"), null);//商品Id
Integer buyNo=U.nvlInt(product.get("BuyNo"), 0);//购买数量
String SN=U.nvl(product.get("SN"),"");
BigDecimal buyPrice=U.nvlDecimal(product.get("BuyPrice"), BigDecimal.ZERO);//购买价格
BigDecimal buyTotalPrice=U.nvlDecimal(product.get("BuyTotalPrice"), null);//购买总价格
BigDecimal productPayPrice=U.nvlDecimal(product.get("PayPrice"), null);//单品实付金额 BigDecimal cost=null;//商品成本 TODO 调别人的接口
BigDecimal realtimeAmount=null;//实付金额 BigDecimal pdCashAmount=BigDecimal.ZERO;//每个商品的返现
BigDecimal pdcouponAmont=BigDecimal.ZERO;//每个商品的优惠券 //商品价格所占订单比例
if(buyTotalPrice!=null&&totalPrice!=null&&totalPrice.doubleValue()!=0){
pdCashAmount=buyTotalPrice.divide(totalPrice,8,BigDecimal.ROUND_HALF_UP).multiply(cashAmount).setScale(2,BigDecimal.ROUND_HALF_UP);
pdcouponAmont=buyTotalPrice.divide(totalPrice,8,BigDecimal.ROUND_HALF_UP).multiply(couponAmont).setScale(2,BigDecimal.ROUND_HALF_UP);
discountPrice=buyTotalPrice.divide(totalPrice,8,BigDecimal.ROUND_HALF_UP).multiply(discountPrice).setScale(2,BigDecimal.ROUND_HALF_UP);
} realtimeAmount=buyTotalPrice.subtract((pdCashAmount.add(pdcouponAmont).add(discountPrice))).setScale(2,BigDecimal.ROUND_HALF_UP); RelOrdersItem item=new RelOrdersItem(U.randomUUID(),orderSN,productId,SN,buyNo,realtimeAmount,U.nvl(productToKeyWordMap.get(productId))); relOrdersItemList.add(item); //如果确认时间属于同一天的话,将商品实付金额放入到redis排行榜中
if((status==1||status==5||status==6||status==7||status==11)&&DateUtils.isSameDay(new Date(), ct)){
//如果订单的状态是这几种,刚将该商品加入到实付金额的排行 榜中
dates=U.getYearMonthDayHour(ct);//
int days=dates[2];
//某一个商品某一天的实付金额
BigDecimal itemRelAmount=BigDecimal.ZERO;
//从redis里取出这个商品的实付金额,然后累加
String itemRelAmountStr=readRedisService.get(Constans.CACHE_PERITEMRELAMOUNTSS_KEY_PREFIX+productId+Constans.CACHE_KEY_SEPARATOR+days);
if(StringUtils.isNotBlank(itemRelAmountStr)){
itemRelAmount=new BigDecimal(itemRelAmountStr);
}
realtimeAmount=itemRelAmount.add(realtimeAmount);
writeRedisService.set(Constans.CACHE_PERITEMRELAMOUNTSS_KEY_PREFIX+productId+Constans.CACHE_KEY_SEPARATOR+days, realtimeAmount.toPlainString());
writeRedisService.lpush(Constans.CACHE_DELKEYS_KEY_PRDFIX+days, Constans.CACHE_PERITEMRELAMOUNTSS_KEY_PREFIX+productId+Constans.CACHE_KEY_SEPARATOR+days);
writeRedisService.zadd(Constans.CACHE_ITEMREALAMOUNTSS_KEY+days, realtimeAmount.doubleValue(), productId+"");
//确认的销量
Long itemCount= writeRedisService.incrBy(Constans.CACHE_ITEMSALES_KEY_PRDFIX+productId+Constans.CACHE_KEY_SEPARATOR+days,buyNo);
writeRedisService.zadd(Constans.CACHE_ITEMSALES_SS_KEY_PRDFIX+days, itemCount, productId+""); String itemType="";
Map<String, String> pMap = this.readRedisService.hmget(Constans.CACHE_PRODUCT_KEY+productId);
itemType=pMap.get("categoryId");
if(StringUtils.isNotBlank(itemType)){
if(ProductCategory.isGuanBai(itemType)){
//如果是白酒 官白的访客数排行
this.writeRedisService.zadd(Constans.CACHE_ITEMREALAMOUNTWHITESS_KEY+days, realtimeAmount.doubleValue(), productId+"");//
//确认的销量排行
this.writeRedisService.zadd(Constans.CACHE_ITEMSALESWHITE_SS_KEY_PRDFIX+days, itemCount, productId+"");//
}else if(ProductCategory.isGuanHong(itemType)){
//官红的访客数排行
this.writeRedisService.zadd(Constans.CACHE_ITEMREALAMOUNTREDSS_KEY+days, realtimeAmount.doubleValue(), productId+"");//
//确认的销量排行
this.writeRedisService.zadd(Constans.CACHE_ITEMSALESRED_SS_KEY_PRDFIX+days, itemCount, productId+"");//
}
} //某一个商品的销量加入删除列表
writeRedisService.lpush(Constans.CACHE_DELKEYS_KEY_PRDFIX+days, Constans.CACHE_ITEMSALES_KEY_PRDFIX+productId+Constans.CACHE_KEY_SEPARATOR+days);
}
}
try {
//TODO 将订单商品明细入库
this.statisticsService.insertRelOrdersItemBatch(relOrdersItemList);
//再将订单的状态改为已计算
this.statisticsService.updateIsCal(orderSN,IsCal.已计算.getFlag());//将是否计算改成已计算
//该订单的所有商品的成本同步到现在的库中。
this.calOrderProductCostSync(orderId,orderSN,products);
} catch (Exception e) {
logger.error("insertRelOrdersItemBatch or updateIsCal error",e);
}
}
}
watch.stop();
logger.info("CalculateAmount task run end........total cost time:"+watch.getTime()+" orderId:"+orderId);
} private void calOrderProductCostSync(int orderId,String orderSN,List<Map<String, Object>> products){
List<Map<String, Object>> ordersList = this.erpOrderGoodsService.selectProductCostByOrderSN(orderSN);
if(ordersList==null||ordersList.isEmpty()){
logger.error("according orderId to query some data from erp return is null.........");
return;
}
Map<String, String> itemIdToItemSnMap = U.convertToMapByList(products, "pro_ProductID", "SN"); List<RelItemCosts> list=Lists.newArrayList();
for (Map<String, Object> map : ordersList) {
RelItemCosts itemCost=new RelItemCosts();
if(map==null){
continue;
}
Integer itemId=U.nvlInt(map.get("goods_id"),-99);
BigDecimal costs=U.nvlDecimal(map.get("Dynamic_price"), BigDecimal.ZERO);
itemCost.setId(U.randomUUID());
itemCost.setOrdersId(orderId+"");
itemCost.setOrdersNo(orderSN);
itemCost.setItemId(itemId);
itemCost.setItemNo(itemIdToItemSnMap.get(itemId+""));
itemCost.setCosts(costs);
itemCost.setCreateTime(new Date());
itemCost.setModifyTime(new Date());
list.add(itemCost);
} this.statisticsService.insertRelItemCostsBatch(list); } }

  注意:

    1、redis2.6版本使用lpush、rpop出列的时候会丢失数据。换成2.8及以上的版本运行正常。

    2、由于应用会部署到多个结点,所以无法直接采用java的BlockingQueue阻塞队列,帮采用redis提供的队列支持。

    3、如果要做到统计的绝对实时,最好采用大数据的实时计算的解决方案:kafka+storm 来实现

  以上为队列结合线程的实践案例,供大家一起探讨。

    转载请注明出处 ,请大家尊重作者的劳动成果。

redis队列及多线程应用的更多相关文章

  1. Redis 6.0 多线程重磅发布!!!

    Redis 6.0在5.2号这个美好的日子里悄无声息的发布了,这次发布在IT圈犹如一颗惊雷一般,因为这是redis最大的一次改版,首次加入了多线程. 作者Antirez在RC1版本发布时在他的博客写下 ...

  2. [bigdata] 使用Redis队列来实现与机器无关的Job提交与执行 (python实现)

    用例场景: 定时从远程多台机器上下载文件存入HDFS中.一开始采用shell 一对一的方式实现,但对于由于网络或者其他原因造成下载失败的任务无法进行重试,且如果某台agent机器down机,将导致它对 ...

  3. 我心中的核心组件~MSMQ与Redis队列

    回到目录 这个文章其实是我心中的核心组件的第七回,确实在时间上有些滞后了,但内容并不滞后!本文MSMQ只是个引题,我确实不太想说它,它是微软自己集成的一套消息队列,寄宿在Window服务里,稳定性十在 ...

  4. c#之Redis队列在邮件提醒中的应用

    场景 有这样一个场景,一个邮件提醒的windows服务,获取所有开启邮件提醒的用户,循环获取这些用户的邮件,发送一条服务号消息.但问题来了,用户比较少的情况下,轮询一遍时间还能忍受,如果用户多了,那用 ...

  5. c#之Redis队列

    摘要 这两天一直在考虑redis队列:一个生产者,多个消费者的情况,这里弄了一个demo进行测试. 一个例子 关于如何引用Redisclient 可以参考之前的这篇文章:c#之Redis实践list, ...

  6. redis队列的实现

    redis中文官网:http://www.redis.cn/ 关于redis队列的实现方式有两种: 1.生产者消费者模式. 2.发布者订阅者模式. 详解: 1.生产者消费者模式. 普通版本: 比如一个 ...

  7. (3)redis队列功能

    Redis队列功能介绍 List 常用命令: Blpop删除,并获得该列表中的第一元素,或阻塞,直到有一个可用 Brpop删除,并获得该列表中的最后一个元素,或阻塞,直到有一个可用 Brpoplpus ...

  8. 转载:【高并发简单解决方案 | 靠谱崔小拽 】redis队列缓存 + mysql 批量入库 + php离线整合

    需求背景:有个调用统计日志存储和统计需求,要求存储到mysql中:存储数据高峰能达到日均千万,瓶颈在于直接入库并发太高,可能会把mysql干垮. 问题分析 思考:应用网站架构的衍化过程中,应用最新的框 ...

  9. redis 队列缓存 + mysql 批量入库 + php 离线整合

    问题分析 思考:应用网站架构的衍化过程中,应用最新的框架和工具技术固然是最优选择:但是,如果能在现有的框架的基础上提出简单可依赖的解决方案,未尝不是一种提升自我的尝试. 解决: 问题一:要求日志最好入 ...

随机推荐

  1. poj 3264 线段树

    题目意思:给定Q(1<=Q<=200000)个数A1,A2,```,AQ, 多次求任一区间Ai-Aj中最大数和最小数的差 线段树太弱了,题目逼格一高连代码都读不懂,今天开始重刷线段树,每天 ...

  2. C#高级编程9 第11章 Linq

    Linq 1.Linq概述 列表和实体 准备数据: public class Championship { public int Year { get; set; } public string Fi ...

  3. C#高级编程9-第4章 继承

    继承是面向对象的一大特征.要深刻学习继承,需要学会使用调试的技巧来学习它,因为它比较抽象. 继承 继承是指一个具体的类型直接使用另一类型的某些数据成员或函数成员,继承的类是基类(父类),被继承的类是派 ...

  4. vue各生命周期适合做的业务逻辑

    一.实际项目中使用最多的Vue生命周期大概是  created  mounted  updated 二.各自适合做的业务逻辑 1. created   相当于是页面刚开始加载的状态,此时不能操作实例的 ...

  5. linux 内核大牛-谢宝友

    http://blog.chinaunix.net/uid/25845340.html 谢宝友:毕业于四川省税务学校税收专业,现供职于中兴通讯操作系统团队,对操作系统内核有较强的兴趣.专职于操作系统内 ...

  6. 追踪CPU跑满

    http://blog.donghao.org/2014/04/24/%e8%bf%bd%e8%b8%aacpu%e8%b7%91%e6%bb%a1/

  7. CentOS 6.5系统下安装和配置NFS服务

    一.环境介绍: 服务器:centos 192.168.1.225 客户端:centos 192.168.1.226 二.安装: NFS的安装配置: centos 5 : 1 yum -y instal ...

  8. python笔记1-用python解决小学生数学题

    前几天有人在群里给小编出了个数学题: 假设你有无限数量的邮票,面值分别为6角,7角,8角,请问你最大的不可支付邮资是多少元? 小编掰着手指头和脚趾头算了下,答案是:1.7元 那么问题来了?为啥是1.7 ...

  9. java中ant包中的org.apache.tools.zip实现压缩和解压缩

    其实apache中的ant包(请自行GOOGLE之ant.jar)中有一个更好的类,已经支持中文了,我们就不重复制造轮子了,拿来用吧,这里最主要的功能是实现了 可以指定多个文件 到同一个压缩包的功能 ...

  10. 试用log4jdbc

    近日发现一个好东东log4jdbc,他是一个JDBC驱动器,能够记录SQL日志和SQL执行时间等信息.log4jdbc使用SLF4J(Simple Logging Facade For Java)作为 ...