语法 :循环 ibatis iterate mybatis foreach mybaits 字段不能为空,否则报类型错误
java.util UUID.randomUUID().toString().replaceAll("-", "")
一、整体数据流图
流程描述: 下单成功、购买成功、计息开始、续约成功、退出审核通过、已退出为理财单状态变化的业务节点,在这些业务节点处生成Json格式的“状态消息”保存到数据库的“状态消息表”中,然后在p2pweb启动一个轮询监听,持续的从数据库中读取出来状态消息记录,发送到activemq消息队列,消息队列使用虚拟topic的形式,消费端从队列中读取消息并进行相应的业务处理。
二、数据库设计
状态消息表
表名:finance_status_message
列名 | 格式 | 是否为空 | 注释 |
MESSAGE_ID | NUMBER(16) | 不可为空 | 消息id |
MESSAGE_CONTENT | VARCHAR2(2000) | 不可为空 | 消息体,json格式 |
FINANCE_ID | VARCHAR2(32) | 不可为空 | 理财单id |
RETRY_TIMES | NUMBER(2) | 不可为空 | 重试次数 |
STATUS | NUMBER(2) | 不可为空 | 发送状态,0:未发送,1:发送成功,2:发送失败。 |
CREATE_TIME | DATE | 不可为空 | 消息创建时间 |
UPDATE_TIME | DATE | 不可为空 | 更新时间 |
三、 Json格式消息的结构
{ msgId : 消息id
msgCreateTime : 消息创建时间
msgVersion : 消息版本号
fId : 理财单id
investAmt : 投资金额
channelCode : 购买渠道
productType : "产品类型(0 宜定盈e 1 宜定盈v 2 节节高)"
productName : 产品名称
userId : 用户id
msgType : 通知类型
detailMessage: {
createDate : 理财单创建时间【下单成功】
fPurchaseId : 申购id【下单成功】
incomeRate : 利率【购买成功】
sequestDate : 购买成功时间【购买成功】
closurePeriod : 产品封闭期【购买成功】
closurePeriodType : "封闭期类型(0表示按月,1表示按天)"【购买成功】
productCycle : 产品周期【购买成功】
productCycleType : "产品周期类型(0表示按月,1表示按天)"【购买成功】
beginDate : 计息开始时间【计息开始】
endDate : 理财单结束时间【计息开始】
applyId : 续约申请id【续约成功】
renewTime : 续约期【续约成功】
renewTimeType : "续约类型(0表示按月,1表示按天)"【续约成功】
oldEndDate : 续约前理财到期时间【续约成功】
newEndDate : 续约后理财到期时间【续约成功】
exitType : "退出类型(0.正常退出 1.提前退出)(与理财单到期时间无关)"【退出中】
endDate : 理财到期时间【退出中】
auditDate : 退出审核通过时间【退出中】
fundDirection : 退回方式(0,转至理财账户【正常】;1,转至划扣银行卡;2,转至理财账户【异常】)【已退出】
totalAmt : 退出总额【已退出】
incomeRate : 利率【已退出】
exitDate : 退出时间【已退出】
}
}
DetailMessage中的字段,只有与messageType相同状态的字段才会有,不同的状态会有不同的字段,消费端需自行解析该json数据,进行相应的业务处理。detailMessage中各字段的注释中“【】”中的内容表示只有在这个状态下该字段才会出现
四、 后台轮询发送消息流程
该过程重复运行,发现有未成功发送的消息则尝试进行发送,重试次数暂定5次
五、 消息队列选择
1、 消息队列采用activemq,activemq是业绩使用非常广泛的消息中间件产品,在宜人贷内部也已经有成功的应用经验,其特性也满足本业务需要。其虚拟topic的特性,可以用来避免普通topic的一些缺陷,因此,选用虚拟topic的形式来广播消息。
2、 Activemq有持久保存消息的特性,消息持久化存储,理论上硬盘空间够就可以一直堆积。业务系统目前每天应该是1~2万的消息,消息的有效期暂定1周,最多堆积10万左右的消息
3、 mq需要外部访问,功能与现有mq存在差异,建议独立部署一套mq
4、 部署主从节点实现高可用
5、 Topic名:VirtualTopic.finance_status
6、 消费者命名规则:Consumer.系统_业务.VirtualTopic.finance_status 例:Consumer. p2pweb_MsgAndEmail.VirtualTopic.finance_status
7、 目前的消费者:Consumer.yingChat_Msg.VirtualTopic.finance_status
六、权限控制
理财状态需要跨业务系统广播通知,因此有可能需要暴露在外网,需要增加权限控制,消费端需要用户名和密码才能访问队列,并且对消费端只提供接收消息的权限,activemq提供了权限控制的功能,按照配置文档进行相应配置
七、异常监控
状态为发送失败的数据,由监控系统监控,发送报警,1分钟检查1次即可,发现报警后如果再未超过重试次数,后台的轮询会尝试重发,如果超过了重发次数就需要人工介入处理
八、如何保证消息不多发不漏发
1、关于漏发的问题,“状态消息表”数据的插入跟业务逻辑处理在同一个事务中,业务处理成功之后通过数据库事务可以保证“状态消息表”中肯定存在一条记录,后台轮询发送消息时,只有在消息确认发送成功之后才会修改“状态消息表”中,消息的发送状态,保证不会漏发,并且,消息队列中的消息时持久化保存的,不会丢失。
2、关于多发的问题,由于保证不漏发的机制可能会导致多次发送同一条消息到消息队列,造成多发,我们通过messageid来解决这个问题,我们使用“状态消息表”中消息记录的唯一id来作为jms消息的messageid,activemq可以保证相同id的消息值接收一次,从而保证不多发
九、其他问题
1、 消息发送存在一定延迟的可能性,实时性特别高的业务不建议使用此方案来同步状态
十、如何保证只有一台p2pweb启动轮询
1、 分布式锁,这个主要得益于ZooKeeper为我们保证了数据的强一致性, zk集群中任意节点(一个zk server)上的相同znode的数据是一定是相同的。使用锁服务保持独占特性,就是所有试图来获取这个锁的客户端,最终只有一个可以成功获得这把锁。做法是把zk上的一个 znode看作是一把锁,通过create znode的方式来实现。所有客户端都去创建 /sendFinanceStatusMsg 节点,最终成功创建的那个客户端也即拥有了这把锁。
十一、如何保证一台客户端挂了,能有其他服务启动轮询
利用ZooKeeper有两个特性,就可以实时另一种集群机器存活性监控系统:
a. 客户端在节点 /sendFinanceStatusMsg上注册一个Watcher,那么如果节点变化了,会通知该客户端。
b. 创建EPHEMERAL类型的节点,一旦客户端和服务器的会话结束或过期,那么该节点就会消失。
(节点图)
十二、相关类路径
消息发送:com.yirendai.p2pbusiness.finances.notice.service.FinanceStatusMsgService
消息类型枚举:com.yirendai.p2pbusiness.finances.notice.enums.NoticeTypeEnum
购买理财后续业务流程方法:
com.creditease.p2p.finacemanager.service.impl.FinancesManagerServiceImpl.doBuyFinancesProduct(Long)
前台购买理财步骤:
1. 下单方法:com.creditease.p2p.lender.controller.FinanceController.buySubmit(String,String, String, HttpSession)
2. 锁定金额:
com.yirendai.p2pbusiness.finances.financesproduct.service.impl.FinanceLockedAmountServiceImpl.updateFinanceOnlineLockedAmount(BigDecimal,String, BigDecimal)
3. 支付方法:
com.creditease.p2p.user.controller.PayCenterController.payBefore(HttpServletRequest,HttpServletResponse)
4.更新起投日期timer:com.creditease.p2p.autolend.timer.AutoLendUpdateBeiginInvestTimer
5.其他timer见excel文档。
十三、相关表
申购表finance_product_purchase_audit