美文网首页
架构和产品的制衡——说说竞价拍卖那点事

架构和产品的制衡——说说竞价拍卖那点事

作者: ac3 | 来源:发表于2016-02-25 16:18 被阅读401次

    2016年1月份,我们的产品上线版已经是1.1.0,开发版已经做到1.2.0。这些版本之间除了UI上做了一些改动(可以在我的产品分类文章里看到我为什么会做这些改动),大部分是些底层的优化,比如App端缓存架构的修改,后台Server的API优化等等。第一个阶段的产品规划此时已经进入一个相对平缓的时期,这个平缓不是说产品做完了,而是说产品从无到有(0到1)之后,进入2(大需求改动)之前一个节奏相对平衡的时期。在这个时候,我终于开始腾出手来重点关注一个一直悬而未决的需求——线上竞价。

    注:本文里竞价和拍卖代表一个概念。这里的竞价指的是传统意义上的拍卖的线上版本,不是指像某些SP商提供的线上广告位自动竞价系统。两者有一定区别。

    线上竞价这个概念,在有淘宝的今天,已经不是什么新鲜事了。但是亲手做一个完全模拟线下拍卖流程的在线竞价系统,这样的需求不会很多。这对我们团队对于我个人来说,即是一个机会也是一个挑战。

    好,怎么做呢?首先看看能不能借用别人的轮子,能不重复造当然不要自己造。可是在网上搜索一大圈,几天下来,能用的资料寥寥无几。可能是一个竞价系统太简单了,做的人都不屑于不好意思写下来;又或者是做的人实在是太少了,都没来得及写;也可能是凡是做竞价的都是商业需要,不能写;还有可能我搜索技术太烂,找不到。不管怎样,看起来我们又是得从零做起,而且我得写点什么,好歹将来一无是处的时候给自己的子孙吹个NB说,“你爷爷曾经做过竞价系统!”“啥破玩意?…”“……”

    从头做,还是得分几步:

    1. 首先要熟悉拍卖本身,至少得先懂;没有太多的时间让我一点点的完全熟悉拍卖这个市场,于是我拉上运营部门里做过真实拍卖交易的人,不断聊天了解概念,然后又亲自和他们参加了几场真实的现场拍卖会,首先对真实线下情况混个八九成熟;

    2. 分析竞品,了解别人都做了什么;拉上UI设计师一起,测试机器上装上4,5个有相同功能的App,我们两个人一起分析每一个页面里的内容和操作流程,互相讨论;

    3. 根据竞品和我们的实际需求,整理出基本的数据模型;在这个过程中,需要非常多得取舍决策,因为一个完整的线下拍卖有太多的概念可以抽象到数据,哪些真的需要用在产品里,哪些不重要,这个完全考察的是经验和理解决策力;

    4. 根据线下规则,竞品常态和我们的实际需求,设计我们自己的业务流程;这部分,有时间的话,我想在产品分类文章里分享下我是怎么设计我们的竞价产品的;

    5. 根据流程,设计系统架构。

    6. 根据架构原型实现中遇到的问题,优化细节。

    下文将着重讨论5,6这两部分。

    一、竞价的系统架构

    我们来看下常规状态下,一个线下拍卖的核心模式。拍卖,简单来说,就是一个人吆喝卖,多个人抢着买,在卖的人规定的条件下(一般为时间限制),出价最高者得。好,那么整体模式可以概括成下面这张图:

    那我现在把这张图,变化一下,只保留他们之间传递的信息,再看一下:

    这像什么?对了,聊天室。整个拍卖过程在结构上本质上就是一个群聊。只不过,普通的聊天室的实现,服务器端只是单纯的广播消息,客户端只是单纯的收发消息;而竞价系统,服务器端除了负责广播消息之外,还需要对每一条消息进行判定,并在最终聊天室关闭时(也就是竞价结束时)对所有消息进行一次综合判定,而各个不同客户端发出的消息时,互相之间有依赖关系(后一次出价必定在前一次基础之上)。

    这样做起来思路就相对清晰了:两大模块:聊天系统+判定逻辑。

    俗话说,条条大路通罗马。同样的需求,做起来有不同的方法,只是不同的方法最后的效果可能不同。同样是做巧克力,Teuscher的手工巧克力就是比超市里卖的廉价巧克力口感好,卖的价格高。就看具体的要求标准是什么。同理,做一个聊天系统,可以是实时系统,也可以是一个非实时系统;可以用长连接,也可以用短连接;可以用轮询驱动,也可以用事件驱动。关键看需求标准。

    那么,回到我们的产品。最深层次的需求是:

    1) 客户端能发起出价请求;

    2) 当价格更新时,所有参与出价的客户端能够看到最新价格;

    3) 系统功能要求远高于性能要求;

    4) 开发周期紧。

    摆在我们面前的,最明显的有2种做法:

    1) 非实时的polling:

    所有客户端的页面定时向服务器发起更新请求,拉取最新价格和出价历史列表。

    2) 实时事件驱动:

    所有价格更新以事件方式主动更新到每一个激活客户端。

    两种方式优劣对比:

    到这肯定有人会有这样的问题:聊天系统想都不用想——很自然的应当选择第二种直接上Node.js+socket.io啊。可是,我做任何设计,总是喜欢考虑这些问题:

    我是不是非得用这种技术,是否有其他更快的方法,“杀鸡要不要用牛刀”,“我是不是只有一把刀”……

    那么落到手头的实际项目,我要考虑的是:

    a) 不能直接使用Node.js操作数据库,因为Node的ORM和系统的Django ORM不匹配

    b) 必须尽可能复用已有系统架构,在最短时间内完成功能

    c) 必须顾及竞价系统可能存在的复杂功能的要求

    d) 用户体验优先级最高

    这些东西已考虑下来,最终就形成了现在的系统方案,方案1)和方案2)的结合:

    我们利用原有系统架构(图中右下部框内部分),使用Redis作为桥梁,直接让Node服务器连接在Django服务器:

    1)Node服务器负责维护WebSocket连接并且广播消息,通知客户端实时更新价格;

    2)Django服务器负责响应出价POST请求,直接操作数据库;然后通知Node服务器更新事件;

    这样,我们做出的权衡就是:

    舍弃Node的高并发的能力,把系统压力仍然嫁接在已有Server之上;

    利用Node对WebSocket的优秀支持,提升用户的实时体验;

    复杂度上,增加客户端(Web和App)支持WebSocket的能力,但是利用Redis队列润滑系统整体框架,复用已有系统的处理逻辑和方式。

    二、细节的打磨

    上面提到的是竞价基础架构的设计思路,那么想要把一个聊天系统真正的变成一个竞价系统,还得靠消息判定逻辑。

    原本来说,来一个出价POST请求,通过App Server判断价格是否生效,生效修改数据库并通知,失效则拒绝。多个请求到来时,FIFO排队,或者数据加锁。

    很简单,和普通的Web请求读写思路没有太大区别。但是竞价系统在这里有一个不得不去考虑的特殊需求:代理出价。

    在这里需要解释一下代理出价:代理出价就是“替你出价,当你不在场的时候”。相当于一个自动机器人,每当别人的报价高于自己的报价则在价格到达自己限定的最高上限之前都会自动加价后报价。比如,某件商品起拍价1000,加价幅度为100,我设定的代理出价额度为2000,那么只要价格低于2000,你无论出价多少,我都会自动比你高100出价。你出1100,我出1200,你出1300,我出1400……这个过程中,我不必在线出价,系统自动帮我出价。

    那这个代理出价的需求会带来哪些新的问题:

    1) 惊群效应:在有多个代理人的情况下,因为自动竞争,价格会在最短时间内上升到最高上限。比如有3个人都设置了代理出价,分别设定最高限额是M1,M2,M3。那么一旦竞价开始价格将飚升至Max(Mi)。那么,系统要不要控制这个过程?

    2) 如果M1=M2=M3,最终获胜的应该是谁?

    3) 如果把系统自动出价的行为看做是一个后台的机器人,那么这个机器人的“地位”如何?是把每一个设置代理出价的用户都看做是单独的机器人独立出价,还是由一位机器人统一负责协调所有的代理出价?

    其中,前2个问题是需求策略问题,最后一个是技术实现问题。

    关于惊群的问题,首先来看从产品的角度要不要“刻意”控制:系统刻意延缓这个“互相抬价”的过程,还是放任让价格在很短的时间内的飙涨到代理人群中的最高出价。开发人员在第一版的设计里,是考虑了“用户体验”,加入了一个定时机制,刻意延缓了整个代理“互相出价”的进程,这样还能让手动出价的用户能够在代理竞争出价的“间隙”里也能有机会出价,即时这个价格并没有竞争力。理由是:这样看起来更像是大家坐在一起互相出价,不然看起来“很假”。由于做这个设计的时候我正好休假,所以开发人员并没有和我沟通就直接把代码写完了。我回来以后,我的第一反应是很开心,因为至少说明团队的开发人员能“主动思考用户体验”,而且证明他做的很快,想到方案并快速原型实现是必须的。但是,我习惯性的问了自己一个问题:“真的有必要让它看起来不那么假么?用户真的在意出价过程中如果出现多个代理出价的情形,自己还必须要有机会出价么?”。要回答这个问题,我们退一步,退回到线下拍卖的真实场景中:

    现实拍卖中,拍卖公司或者组织者会给每一个有代理出价需求的客户提前安排好专门的出价代表,并和现场其他到场客户坐在一起举牌。线上“惊群效应”其实就是现实场景中,这些代表们争先恐后的举到自己代理的客户承受的最高价才能力保客户最大可能获得最终胜利。那么在这个过程中,现场其他的客户很有可能没有机会来的及举牌,会在意么?我看不会,为什么,因为竞价不是“享受过程”,而是“获得结果”!你们在争相举牌,我没机会,但是我无所谓,因为当代理举牌完毕后,只可能有两种结果:当前价格我还能再加,或者当前价格已经超过我的预期。那么这两种情况都不会降低我的体验,我想加我就等你们消停了我再举牌,这个时候只要你们代理不修改代理价格,你们就都不是我的竞争对手了;而如果当前哄抬的价格已经超过我的心理价位,前面我有没有举过牌对我来说显然一点意义都没有,反正我肯定是拿不到这个拍品了。所以,我问开发“把这个定时器拿掉对你的代码有多大改动?”“不大”“好,拿掉。不需要增加额外复杂度。”

    所以这里我的权衡是:即使让开发翻工(当然是在可以接受的范围内),也必须去掉过度的“结构设计”。

    第二个问题,理解好线下拍卖对这个问题的处理方式就比较好办。线下的方法是:“拍卖师说的算!”其实就是说,系统保留所有解释权。那就好办了,自己定义了一个规则就可以。

    关于第三个问题,字面上不太好理解,我们来看一个图:

    这就是描述第一种情况中的“机器人是作为独立出价人单独出价”,指的就是系统并不对代理出价者做特殊处理,每一个代理在接收到价格更新事件后都可以直接对竞价系统发起出价请求。这种设计优劣如下:

    优点:系统功能角色划分清晰,每个代理可以享有独立出价权,更接近真实拍卖场景;

    缺点:因为每个代理都可以接收到更新事件,每个代理都可以发出新的出价请求,而这些出价请求又会被其他代理收到,再响应出新的出价请求。所以必须额外增加一个机制,防止多个代理循环出价带来监听的“竞价事件”爆发问题。

    我们再来看看第二种可能的设计:

    在这里,所有的代理出价者被纳入内部的“Coordination System”中,而不再和外部的手动出价人并列。当任何一个出价事件发生时,首先由这个“Coordinator”在众多代理出价者中协调出一个最终胜者,然后再向竞价服务器发出一个最终竞价请求。Coordinator选出唯一胜者的同时,也把所有其他的代理给淘汰掉了。也就是说,只需要1次出价,就能够把所有可能的代理者全部“洗清”,只剩一个最高代理者和其他手动出价人竞争。在这个“协调”过程中,忽略所有其他手动出价。这种设计优劣如下:

    优点:系统实现清晰简单,不会产生“惊群”问题;

    缺点:系统逻辑和真实场景有直观上的差距;

    这两种都有明显的优缺点,从模拟真实线下逻辑来说,第一种更优,理解起来也更容易;而从系统结构实现角度来说,我更喜欢第二种,做起来更直接更简单。你会问我第二种的话,如果用户体验很差怎么办?其实这种场景,用户体验差只有一种情况,就是很多人真实在线出价,同时有大量的人设置了代理出价。而这种情况对于我们目前的业务而言,显然遥不可及。更何况,如果真的是这种大规模并发的场景,整个系统结构都可能要做调整了。虽然我个人倾向第二种但是我们线上版本用的是第一种,因为……在我休假期间我们NB的开发把第一种做完了!(摊手)。最后想来好像应该怪我应该提前把设计提前讨论到这层再交给开发,但是仔细想想,以目前的情况看,这两种并不会对产品本身有本质影响。所以既然已经做完了,那就先用。

    至于使用第一种设计,我们额外设计了附加的措施来解决具体的“惊群”问题:

    因为我们是使用遍历列表的方式扫描所有的出价代理的,那么当某个代理成功发起出价请求后,在本次扫描循环周期内,后续的代理不再出价,而只是扫描其设置的封顶价格是否失效,是则忽略,否则从列表中移除。而当每个代理接收到价格更新事件时,首先确认此次出价是否为本人,是则忽略此次更新,否则继续发起出价事件。

    三、总结

    本文主要记录的是我在做产品的竞价功能的前后遇到的问题和设计思路。我个人认为,这是一个很好的关于产品需求和架构设计之间互相权衡乃至制衡的例子。做互联网产品的架构师,不能只是掉在架构的黑洞里钻研架构本身,而是应该千方百计的用最合理的架构用最快的时间去满足产品的功能。我一直认为,做产品和做架构本身是不应该分离的,我坚持认为研发出身的人如果去能做产品,应该比其他人更有优势,至少在产品本身的实现上,更有说服力更有效。当然,关于市场和运营本身的能力也是至关重要,但那是另外的话题了。

    2016年2月25日,完稿于南京。

    相关文章

      网友评论

          本文标题:架构和产品的制衡——说说竞价拍卖那点事

          本文链接:https://www.haomeiwen.com/subject/oknikttx.html