背景

      我接到了任务,大体是说财务需要对账,所以需要Paypal的交易记录,直接去Paypal的后台去对是很浪费时间的事情.讨论下来就是要使用Paypal的Api获取交易记录到本地.然后再想办法使用这些数据,那么现在的任务就是获取Paypal交易记录

任务: 获得Paypal交易记录存到本地

分析问题:

     老实说问题很简单明确.需要用到Paypal获取交易信息的API,好在Paypal提到了.NET的开发SDK,只要配置好相关参数就能使用.通过阅读SDK发现需要使用2个接口,一个TransactionSearch用来获取交易码列表,这是交易的唯一ID,但是这个接口返回的信息有限.需要获取更详细的信息需要用另一个接口transactionDetails,这个接口的参数就是交易码. 总的来说,第一个接口获取交易码列表,第二个接口像是从列表点进去获取详细信息, 做.NET企业级开发的人想都不用想就知道我在说什么.

面对这个需求,首先想到的关键点

        关键点1: 交易记录是不停的随时产生的,每一条交易记录都需要调用一次获取详细信息的api, 如果有需要的时候就运行一次程序,交易记录会越来越多. 我不知道哪天短时间内向Paypal提交上万的请求会不会被屏蔽.所以最好就是程序能一直运行,不停的调用api,免得 一次过多调用被屏蔽.而且, Paypal份额服务器在国外,而且API都是基于http协议,每次调用延时都比较厉害,分散获取交易信息,避免了哪天要用的时候等上1个2个小时的情况

        关键点2: 获取交易码列表的API假定每次调用能获取100个,然后调用100次获取详细清晰的API,这一次下来耗时也挺让人崩溃,明显使用多线程,每个线程调用一次TransactionDetail,这样能减少等待时间,加快速度

      有上上面的基调,基本上我确定了第一版的程序是什么样的了,我做个winform程序,弄个按钮,程序运行之后,点击按钮开始抓取交易信息,然后最小化到系统托盘显示这个程序还活着一直在执行.程序运行的时候,一个线程会处于循环状态, 执行获取列表的的调用,一旦抓到交易码,就创造多个线程来获取详细信息,处理完毕,休眠1个小时,如此循环.

从拿到任务到做出这个设计很明显是个正确的方向,肯定是能获得数据的,但是我并没有开始写代码. 我需要花时间的做好详细的设计,并在设计的过程中希望能发现潜在的问题.

问题来了:

     1. 线程崩溃, 执行transactionDetails的线程, 因为异常崩溃掉, 比如网络超时等. 如果所有执行transactionDetails的线程都崩溃了,获取的交易码列表没有处理吗,那程序不就会卡在这里了吗.

解决办法:

     为了避免这样的情况,执行TransactionSearch的线程,执行GetTransactionDetail的线程要同时创建,都要一直执行, 都要变成守护线程这样的东东.   执行TransactionSearch的线程变成了生产者,执行GetTransactionDetail的线程变成了消费者, 把这个问题变成了一个生产者和消费者问题.

为了解决这个问题的第二版设计设计出来了, 依然是这个winform程序,运行方式也一样. 程序运行的时候,会创建一个生产者线程调用TransactionSearch创建3个线程调用transactionDetails,生产者获取的交易码写入一个队列,消费者从队列中获取交易码,调用api获取详细信息,写入数据库.生产者和消费者之前的同步问题,Google和baidu都能帮忙搞定.

项目结构

     到这里的时候我打算开始写代码了,因为暂时我实在想不出有什么问题会让程序没法运行. 所以我创建了工程,做好了模块划分

       Paypalapi的API接口部分划入PaypalHelper类. 这个类就两个静态方法,分别是对TransactionSearch,transactionDetails的封装

      数据库的访问部分只有比较简单的操作,划入DBHelper. 这部分的功能主要是把交易码和交易记录细节写入数据库,甚至硬编码也没什么问题.

     加入一个模型表示交易详细信息, TransactionDetail,用来在各模块交换信息

      加入一个Executor类表示执行部分. 用来封装消费者和生产者线程的创建和同步

      加入一个winform作为主界面,调用Executor

虽然功能不多,但是分层分模块的想法也要始终贯彻啊,知道了要怎么做,加上详细的模块划分,编码其实就是很机械的动作了.话了点时间编写调试,终于可以运行了.运行之后交易记录慢慢的一条一条的存入数据量了,

第二版

        问题:  运行一段时间之后就发现和Paypal后台看到的交易记录不同,中间会漏掉一些交易记录,而且刚一个时间段之内的.

        分析问题: 我发现是生产者休眠1个小时带来的问题,考虑到1个小时的休眠时间是因为我不喜欢程序不停的访问Paypal的API, 问题在于,如果抓来的数据处理完成的时间超过1个小时,生产者线程还要等待1个小时后之后再抓取数据了,所以中间会漏掉一个时间段的交易记录了.

        解决办法: 明显生产者线程的执行收到消费线程处理速度的影响,那么这个生产者现场就要和消费者线程分离, 那么何必不单纯创建一个线程,这个线程就是只执行TransactionSearch,然后把交易码存入数据库,然后休眠1小时,而之前的生产者线程从数据里面获取交易码,消费者线程处理之后更新数据库,标识交易码已处理.总体来说,就是增加一个线程来执行获取交易码列表,而生产者和消费者线程的模式基本不变.

      第二版很快就做了出来,,在我的电脑上运行了一整天,检查数据之后,发现问题解决了. 到了这一步,我又很快的认为,我暂时想不到有什么问题会干扰程序的正确性了,于是我远程桌面连接上服务器准备运行了,服务器上正常运行没有任何问题,于是我就退出了,过了2个小时我去检查数据库发现没数据,太奇怪了,于是我登上服务器,发现系统托盘不见了,打开任务管理器看不到进程,我检查下日志,发现没有任何异常记录.于是我很愤怒认为是那个家伙把我的程序关掉了.重新运行之后我就退出了远程桌面,然后到办公室里问,是那个家伙不长眼, 结果没问出来,回来再看程序又退出了!!! 我意识到,可能还是程序的问题,搜索一下发现是远程桌面的问题,远程桌面的用户会话结束后,会终结掉用户的所有程序.

第三版

    分析问题:一般的桌面应用都有用户,自己的电脑一直有用户登录,所以我怎么测试都没测不出来的,服务器上运行的程序是没有用户的,通过远程桌面运行的程序,在退出桌面之后就会终结. windows上面不用登陆就能运行的程序是什么?windows服务!!

       现在要做的就是要把我的程序编程一个Windows服务.老实说,我之前根本也没写过服务,但是.net下创建一个服务器并不是什么难事. baidu了一下,找了一篇详细介绍怎么创建和安装Windows服务的的博客,对着创建一个服务类型的工程,把代码放在指定的接口里面就成了,事实上,第二版里面程序的入口在winfom下,只需要去掉winform,把代码转到服务入口就行. 第三版很快就出来了,服务安装之后正常运行, 数据终于正常了.

第四版

      新的需求又来了,他们需要定制获取交易码的时间. 从第三版的程序可以看到,程序中获取交易码的线程是在程序开始运行之后每1个小时获取一次.事实上Paypal的交易记录产生有是规律的,半天明显交易多,晚上交易就少了.

所以最好能制定一个更合理的任务规划来获取交易码列表.

      分析问题: 当时我就觉得一阵眩晕,这么详细的任务调度控制我之前本来没做过,唯一能想到的就是用计划任务,这很可能就会导致程序分拆成两个.获取交易码列表的功能单独做个一个程序,然后用计划任务来执行.

      解决办法: 最先想到的办法就是用计划任务,带来的问题就是两个程序分别运行.一个没多少功能的程序这样拆分我觉得是没什么意义的,于是我上网搜索了下, 目的明确又简单,就是想找个任务调度之类的什么组件,免得自己开发个,那就要吐血了. 果然,很快我就发现了Quartz.NET, 详细的阅读Quartz.NET的介绍和文档之后发现足够满足需求,但是还有点不放心,于是我写了个Demo,来测试Quertz.NET,测试之后我就决定引入项目了.

       第四版的很快就出来了,想对于第三版.变化就是取消了获取交易码列表的线程,把它变成了用Quartz.NET实现的一个任务,能很细粒度的控制获取交易码的时间. 而对交易码的处理的生产者和消费者线程基本不变

      进一步的改进方向

       一是服务是在后面运行的,没有GUI. 二是程序中生产者和消费者线程的数量是固定的,休眠时间也是固定的. 可以考虑外界一个GUI程序来启动和停止服务, 并且用来配置生产者和消费者线程的数量及等待时间

posted on 2013-07-11 10:52  xx念  阅读(3537)  评论(14编辑  收藏  举报