架构师教你:如何实现两个完全独立闭环业务系统的融合。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Gupaoxueyuan/article/details/81589090

本篇文章讲述的是京东服务市场和京麦服务市场融合的项目内容,两个系统完全是独立的闭环业务系统,都有自己完整的服务商流程、服务发布(服务类商品)流程、订单支付流程等,如何实现两个系统的融合,是我们面临的最大挑战,期间有过多版方案的争吵,最终的实现也是曲折的。

梳理系统

首先对服务市场和插件市场进行了系统的功能梳理,并对页面、功能、流程、数据的融合进行了架构拆解。

最终架构

在明确系统融合的大方针之后,就是进入融合流程的细节讨论,其中主要包括如下几个环节:

数据的融合:完成底层服务商、服务(细分版本、模块,按模块)、订购、订单、结算、退款数据融合

流程的融合:完成订单订购流程、交易支付流程、服务发布审核流程、结算流程、发票流程、退款流程、取消订单流程、评价评分流程、服务搜索流程融合

功能的整合/迁移:服务商后台完成服务管理、交易管理、结算管理、运营管理、信息管理、合同管理功能迁移,运营后台完成服务管理(服务、订单、订购管理、退款、发票管理)、结算管理(结算明细、结算对账)、基础管理(work管理)功能迁移

对外服务改造:完成服务器启动流程改造,前后端分离架构改造

服务融合

1. 服务发布审核流程融合

原流程 服务商后台ifw发布服务到服务市场后提供服务,服务商工作台isv发布服务到插件市场提供服务,两套服务发布流程一样相互独立。

过渡方案 服务融合上线后,在服务商后台ifw点击发布发布服务时,重定向到服务商工作台isv,由此实现发布入口的统一。但由于上游应用流程未融合,所以会出现两个地方创建应用一个地方发布服务,所以对于插件市场创建的应用在发布服务时,要添加单边流程的双写机制,这里的双写是指在发布服务时判断是插件市场的时候,不仅要写入服务市场数据库,也要回源插件市场的数据库。

最终方案 关闭服务商后台的入口,只有服务商工作台的入口可以发布服务。同时在上游应用流程融合完毕之后,对插件市场双写流程下线,由商品中台统一对前台提供服务查询。

2. 评价评分流程融合

原流程服务市场查看、发布评论直接访问服务市场数据库;服务商后台对商家评论进行回复直接访问服务市场数据库。插件市场评论评分功能已关闭。

过渡方案服务市场查看、发布评价流程不变;服务商后台对商家评论时重定向服务商工作台,在服务商工作台进行评价回复,评价调用商品中台写入服务市场数据库。

最终方案服务市场前台查看、发布评价信息也调用商品中台进行数据库操作。

3. 服务搜索流程融合

温馨提示:java专业交流群  705194503

原流程服务搜索的逻辑是通过定时任务,先查询服务市场和插件市场数据库的商品数据,进行数据组装后写入缓存和Solr,从而提供给前台列表页和单品页的服务查询。服务市场和插件市场是有两个定时任务单独运行。

过渡方案将两个定时任务合并为一个,在商品数据融合后,统一查询新商品库获取原服务市场和插件市场的所有商品数据,而每个服务商品的使用(订购)人数则通过调用订购履约中台进行查询,最后通过数据组装写入服务市场Solr,对外提供统一查询服务。

最终方案将Solr改为Es。

交易融合

温馨提示:java专业交流群  705194503

1. 订单订购流程融合

原流程 服务市场订购订单写服务市场数据库,从服务市场数据库提供服务;插件市场订单订购写插件市场数据库,从插件市场数据库提供服务。两个流程完全独立。

过渡方案 由于服务市场和插件市场融合切换是无缝的(不能停服务),且两侧订购、订单数据结构又不完全一致,同时数据切流又是逐步放量。所以订购、订单流程的融合采用数据库双写的方式,所谓双写方式,是在写原服务市场数据库和插件市场数据库时,通过订阅Binlog,使用BinLake框架订阅写入sql再整理数据写入新数据库。最终所有服务通过调用新库进行查询服务。

这种双写机制可以很好的进行回滚,当新流程出现问题的时候,可以切回到老数据库进行降级。当新订购流程出现问题时,也可以通过关闭切流控制影响范围,采用老流程进行订购订单处理流程。

最终方案 在完成订购、订单完成切流之后,去掉双写流程,统一由订购履约中台提供前台、后台等服务调用。同时,插件市场服务整体下线。届时,商品数据统一由商品中台提供服务,支付能力由支付中心提供。

2. 结算流程融合

温馨提示:java专业交流群  705194503

原流程结算流程有些复杂,简单讲一笔订购会产生一笔订单,一笔订单会产生三笔正向结算单,一笔退款会产生三笔逆向结算单。三笔结算单包括服务费、货款和佣金,其中服务费是商家向京东结算,货款是京东向服务商结算,佣金是服务商向京东结算。服务市场结算数据写入服务市场数据库,插件市场结算数据写入插件市场数据库。

过渡方案 服务市场和插件市场结算单数据通过BinLake双写到新结算数据库,同订购订单双写模式相同,最终由新数据库提供结算单统一查询服务。其中服务市场运营后台的退款功能,随着切流迁移调用支付中心进行逆向结算,切流结算数据写入插件市场数据库,通过BinLake双写到新结算数据库。

最终方案 插件市场下线,去掉结算双写流程。订购订单和退款融合调用订购履约中台,由订购履约中台调用支付中心进行结算,结算数据写入新结算数据库,对外统一提供服务。

3. 退款流程融合

原流程退款业务原只有服务市场支持,插件市场不支持。发起退款一是由ISV发起,另一个是运营发起,由运营审核后,在运营后台实现退款业务,退款调用下游京米或POP结算进行逆向结算,结算单数据写入服务市场数据库,退款单数据写入服务市场数据库。

过渡方案融合完毕之后,需要对插件市场融合的服务支持退款业务。服务商工作台和运营后台发起退款,统一改调用支付中心完成逆向结算的业务,由运营后台生成结算单和退款单。

最终方案 将退款业务整合到订购履约中台,支付结算能力下沉到支付中心,退款业务通过调用支付中心生成逆向结算单,并调用后端服务完成退款逆向结算业务,在订购履约中台生成退款单,实现整体业务解耦。

4. 取消订单流程融合

温馨提示:java专业交流群  705194503

原流程服务市场取消订单先修改服务市场数据订单状态为完成,后直接调用下游完成订单取消。插件市场取消订单先通过调用支付中心完成订单取消,后异步接收支付中心成功消息后再并修改插件市场数据订单状态为完成,两个流程完全不同且独立。

过渡方案服务市场取消订单改造先修改服务市场数据订单状态为完成,后调用支付中心完成订单取消流程;插件市场取消订单流程不变。服务市场和插件市场各自写入各自数据库,通过订阅BinLake同步更新服务市场新数据库。

最终方案插件市场入口下线,服务市场先调用订购履约中台进行取消订单,中台调用支付中心进行取消订单,通过接收异步成功消息修改服务市场数据库订单状态。

5. 发票流程融合

(1)创建发票流程融合

原流程发票在订单订购过程中产生,服务市场写入服务市场数据库,插件市场通过支付中心写入插件市场数据库,两个流程完全独立。

过渡方案服务市场随订单切流调用订购履约中台,中台调用支付中心创建发票写入插件市场数据库;未切流订单依旧写入服务市场数据库,通过双写订阅BinLake同步写入新数据库,对外提供统一服务。

最终方案插件市场会下线,服务市场作为唯一入口,在订购订单过程中,由订购履约中台调用支付中心创建发票信息写入新数据库中。

(2)查询发票流程融合

原流程服务市场、服务商后台、运营后台查询发票会直接读取服务市场数据库进行查询,服务商工作台查询发票会直接读取插件市场数据库,两个系统完全独立。

过渡方案服务商后台发票功能迁移服务商工作台,与服务市场和运营后台一起改造查询发票时,通过调用订购履约中台查询订单,由中台调用支付中心查询发票信息,在支付中心区分是服务市场订单或插件市场订单,查询服务市场数据库发票或插件市场数据库发票。

最终方案 随着订单切流完毕,发票数据融合完毕,支付中心查询发票信息统一从新数据库进行查询。

对外服务改造

1. 服务启动流程改造

由于融合项目涉及到端的服务启动,那么对这些服务的改造也是非常关键的一部分,原业务逻辑是服务市场前台(Web)直接调用后端服务启动,而服务要在客户端(App)内启动,这些规则、业务、玩法会根据不同场景而不同,所以这些业务放在商品中台是不合适的,所以新搭建一个系统叫服务引擎,它不涉及数据,而仅是对业务、规则、玩法的加速和处理。由引擎提供不同端的服务支撑。

2. 前后台分离架构改造

前后台分离的架构设计是解决H5加载性能慢的问题,技术实现包括jsonp和nginx跳转,对比之后采用nginx,因为jsonp会产生额外的安全问题。当前端访问nginx时,根据请求路径判断是html资源请求时,则通过nginx的pass_proxy重定向到前端的nginx,由前端nginx去请求html资源,否则直接请求后端服务。与cdn不同的是,前端ngnx是由前端域名,但前端域名是不会暴露给前端。

总结

服务市场系统的技术域侧重在质,要求高可用的数据服务稳定性,以及数据一致性。由于是交易系统,数据流的异常会造成资金流的极大问题,如服务费、佣金和货款等收支不一致等。这有别于之前做网关系统,技术域侧重于量,要求高并发、高负载、高可用,即无论如何请求都不能造成网关系统的瘫痪。而服务市场更多的是服务治理和架构治理。

 

展开阅读全文

如何融合两个java开源项目

10-30

一个开源项目bamboo,用了一个Oceanstore项目的类生产了jar作为lib,现在我需要修改oceanstore里面的类来使得bamboo支持ipv6,在原来jar文件里面,Nodeid,相关的类包含了四个class文件::NodeId$1.class, NodeId$BadFormat.class, NodeId$Both.class,NodeId.class文件,而我现在下来Oceanstore源码,修改后编译只有两个calss文件,NodeId$BadFormat.class,NodeId.class。我用这两个文件运行jar -uf 更新jar里面的这两个文件,在运行bamboo程序就出了错误。。rn我的问题是:rnNodeId$1.class和odeId$Both.class是什么意思,编译源代码为什么没有两个文件产成,rn而且我这种直接更新jar文件,是因为其他两个nodeid类文件的.class没有更新出现的问题,还是其他原因使得运行发生错误rn在bamboo的lib里面有makefile文件,是不是更新里面的lib要使用make命令呢,为什么我使用是提示 Nothing to be done for `all'rnrn我该怎么去融合两个开源项目呢,重新编写makefile有太麻烦,这么直接更新又出现错误。rnrn下面是NodeId.java 文件和lib里面的makefile文件rnrnpackage ostore.util;rnimport java.io.*;rnimport java.net.*;rnrnpublic class NodeId implements QuickSerializable, Comparable, Cloneable rnrn public static final boolean DEBUG = ostore.util.DebugFlags.NODEID;rnrn public class BadFormat extends Exception rn public BadFormat (String msg) rn super (msg);rn rnrn public String toString () rn return "BadFormat: " + getMessage ();rn rn rn rn /**rn * Construct a new NodeId.rn *rn * @param port The network port; must be between 0 and 65535 inclusive. rn * @param addr The IP addressrn */rn public NodeId (int port, InetAddress addr) rn _addr = addr;rn _port = port;rnrn rnrn /**rn * Read this NodeId in from a string of the same format as produced byrn * toString (), below.rn */rn public NodeId (String peer) throws BadFormat, UnknownHostException rn rn rn int colon = peer.indexOf ((int) ':');rn if (colon < 0)rn throw new BadFormat (peer);rn String ip_str = peer.substring (0, colon);rn String port_str = peer.substring (colon+1, peer.length ());rn _port = Integer.parseInt (port_str);rn // try to parse out the ip bytes first, then go with a resolvern // operation if that doesn't workrnrn _ip_bytes = new byte [4];rn String munch = ip_str;rn int b = 0;rn for ( ; b < 4; ++b) rn int dot = munch.indexOf ((int) '.');rn if (dot == -1) break;rn String this_byte = munch.substring (0, dot);rn try rn _ip_bytes [b] = Byte.parseByte (this_byte);rn rn catch (NumberFormatException e) rn break;rn rn munch = munch.substring (dot+1, munch.length ());rn rnrn if (b != 4) rn _ip_bytes = null;rn _addr = InetAddress.getByName (ip_str);rn rnrn if( DEBUG ) rn if( _port > 10000 ) rn Debug.printtagln( "NodeId.init", _addr +rn " has a big port: " + _port );rn rn rn rnrn public NodeId () rnrnrnrn public void serialize (OutputBuffer buffer)rn rn if (_ip_bytes == null)rn _ip_bytes = _addr.getAddress ();rn buffer.add((short) _port);rn buffer.add((byte) _ip_bytes.length);rn buffer.add(_ip_bytes);rn rnrn public NodeId (InputBuffer buffer) throws QSException rn _port = 0xFFFF & (int) buffer.nextShort ();rn int length = (int) buffer.nextByte ();rn if (length > 16) // big enough for IPv4 and IPv6rn throw new QSException ("IP addr len = " + length);rn _ip_bytes = new byte [length];rn buffer.nextBytes (_ip_bytes, 0, length);rn _addr = null;rn rnrn /**rn * Return the InetAddress associated with this NodeId; does not workrn * under the simulator.rn *rn * JUST TO MAKE THAT CLEAR, THIS FUNCTION WILL NOT WORK IN THErn * OCEANSTORE SIMULATOR. YOU HAVE BEEN WARNED.rn */rn public InetAddress address () rn if (_addr == null) rn String addr = "";rn for (int i = 0; i < _ip_bytes.length; ++i) rn long value = ByteUtils.byteToUnsignedInt (_ip_bytes [i]);rn addr += value;rn if ((i + 1 ) != _ip_bytes.length)rn addr += ".";rn rn try rn _addr = InetAddress.getByName (addr);rn rn catch (UnknownHostException e) rn if (DEBUG) Debug.printtagln ("NodeId.address",rn "Could not construct an InetAddress from " + addr rn + ": Unknown host exception.");rn return null;rn rn rn return _addr;rn rnrn public int port () rn return _port;rn rnrn public String toString () rn // Do not change this format. It was chosen specifically to bern // compatible with Steve Gribble's networking code.rn return address ().getHostAddress () + ":" + _port;rn rnrn /**rn * Specified by the Comparable interface. rn */rn public int compareTo (Object other) rnrn if( equals(other) ) rn return 0;rn else if( less_than( (NodeId) other) ) rn return -1;rn else rn return 1;rn rnrn rnrn public boolean equals (Object other) rn if (other == null)rn return false;rn NodeId rhs = (NodeId) other; rn if (_port != rhs._port)rn return false;rn return address ().equals (rhs.address ()); rn rn rn /**rn * a.less_than (b) returns true iff a is lessrn * than b. Used for sorting NodeIds.rn */rn public boolean less_than (NodeId other) rn if (_ip_bytes == null)rn rn _ip_bytes = _addr.getAddress ();rn if (_ip_bytes == null) rn Carp.die ("_ip_bytes == null, _addr = " + _addr + rn ". This usually happens because you're running " +rn "with Java 1.4 under SandStorm (NBIO, to be exact)."+rn " Make sure you're running with Java 1.3, and it " +rn "should go away.");rn rn rn if (other._ip_bytes == null)rn other._ip_bytes = other._addr.getAddress ();rn for (int i = 0; i < _ip_bytes.length; ++i) rn if (_ip_bytes [i] < other._ip_bytes [i])rn return true;rn else if (_ip_bytes [i] > other._ip_bytes [i])rn return false;rn rn // addresses are equal, compare portsrn return _port < other._port;rn rnrn public int hashCode() rn if (_ip_bytes == null)rn rn _ip_bytes = _addr.getAddress ();rn if (_ip_bytes == null) rn Carp.die ("_ip_bytes == null, _addr = " + _addr + rn ". This usually happens because you're running " +rn "with Java 1.4 under SandStorm (NBIO, to be exact)."+rn " Make sure you're running with Java 1.3, and it " +rn "should go away.");rn rn rn int result = 0;rn for (int i = 0; i < _ip_bytes.length; ++i) rn result <<= 8;rn result |= (_ip_bytes [i] >= 0) ? _ip_bytes [i] rn : (_ip_bytes [i] * -1);rn rn result ^= _port;rn return result;rn rnrn /**rn * Create an exact copy of this NodeIdrn * @return new NodeId identical in value to this onern * @exception CloneNotSupportedException if clone() is not supported */rn public Object clone() throws CloneNotSupportedException rn rn return new NodeId(port(), address());rn rnrn private byte _ip_bytes[];rn private InetAddress _addr;rn private int _port;rnrnrnrnMakefile文件rnrnBAMBOO_HOME = ../rninclude ../src/bamboo/Makefile.includernrnJAR_FILES = \rn #省略了其他.class文件rnrn -C ~/oceanstore/pond ostore/util/NodeId\$$BadFormat.class \rn -C ~/oceanstore/pond ostore/util/NodeId.class \rn -C ~/oceanstore/pond ostore/util/NonceAckMsg.class \rn rn #省略其他.class文件rnrnall:rnrnostore-seda-emu.jar: rn rm -f $@rn $(JAVAHOME)/bin/jar cf $@ $(JAR_FILES)rn 论坛

没有更多推荐了,返回首页