最坏的设计 写最合理的程序
我发现我最前面的socket、2pc系列的代码式博文,没有什么评论和推荐,这直接导致我不再想写一些更深入一点的代码式博文了。还是写写这种乱七八糟的博文吧,这种的看起来比较受欢迎。
拿前一段时间比较热门的出行类app(滴滴,快的等)来举例说明,用最坏的设计,写最合理的程序。从另一个角度,来看程序设计。
当一个乘客,设置好了路线的起点和终点,并点击了叫车以后,服务器这个时候就需要做什么呢?
根据乘客的起点来进行匹配,将它的这次请求,发送给他附近的一些出租车司机。假定为方圆1km以内称为附近的司机(因:出租车不需要像顺路车一样,需要考虑方向,暂时也不用考虑拒载),再由司机决定是否接受,并在司机接受以后,提示其他司机该乘客已上车。
官方数据,上海市(目前仅考虑上海市场)有80k辆出租车,假定根据各方面的计算,单个线程每s需要处理的并发量,大约在100,则单次乘客的叫车请求(不考虑网络耗时,仅考虑服务器处理),在10ms以内处理完毕即可满足市场需求,那最坏的设计下,80k个司机全部遍历一次,如果时间在10ms以内就能完成,那我们这样做程序设计,就完全没有任何问题,简单直接,且满足需求,这便是合理的程序设计。
那么问题来了,由于司机的所在地理位置,按120km/h(33m/s)计算,数据在一直不停变更,存放司机数据的内存,需要不停的上锁,直接做foreach处理可能远远达不到我们的需要。这时候该怎么解决呢?
于是我们想出了第二种解决方案,把司机的数据,直接存放在rmdb,并对司机的地理位置,做非聚簇索引,80k条数据做一次单表select,这个是速度是极快极快的。但由于数据是在不断变更的,按33m/s来计算,1km大约是33s,500m大约是16s,最坏的设计下,我们只需要测试,rmdb能够撑住的update,在80k/16s (5k/s) 的情况下,那么我们这样做程序设计,也完全没有任何问题,简单直接,且满足需求,这也是合理的程序。
那么问题来了,如果我们不是做出租车,而是做私家车呢?根据官方数据,上海私家车,约为140w辆,且每年以约20%的速度增长。这个时候,放在DB就远远满足不了我们的需求。那怎么办呢?
于是我们想出了第三种解决方案,把司机的数据,按照地理的经纬度位置分组,以每平方公里切分成1个方格数据(上海有7000平方公里),则约为7000个分组,则1400k/7000=200,根据乘客的起点,计算出隶属于哪一个方块,则我们只需要遍历该方格,和周边的8个方块(参照9宫格)数据即可,200*9=1800。然后根据司机的地理位置变化,不断的将司机从一个方格移动到另外一个方格。那最坏的设计下,单次遍历1800个司机数据,能够满足我们的需求的话,那么我们这样做程序设计,就完全没有任何问题,简单直接,且满足需求,这便是最合理的程序。如果还是慢,则可以按0.5平方公里切分。一般到这里,就结束了,这个设计几乎满足任何出行类应用app。
说个题外话:假如数据还是很大,依然无法满足,我们则可以继续针对方格数据,对里面的司机进行排序 或 区间处理等方式,继续降低遍历的复杂度,算法题,不在此文所陈述范畴。固不做具体描述。
请注意看,我前面用的是一般到这里,就结束了,那么不一般的情况呢?
这个时候高潮来了,假如司机手贱,老子就是不接客,且乘客也手贱,发现长时间没有司机接单,老子依旧不取消叫车。并且当有司机/乘客随着移动,进入到另外一个方格时,需要自动提示该司机,有这个乘客在叫车。注:该叫车请求没结束之前,有地理位置变化,所产生的新的 满足匹配需求的,以及不再满足匹配需求的,需要推送消息,告诉司机该乘客已超出范围 或 有新乘客叫车。
随着叫车请求的不断累加,为了保证系统的正常运作,需要处理的并发数,就会不断递增。
虽然这种场景不太可能发生,但是这种变态的开发需求,在一些非定点叫车的沿途类出行app里面,经常会出现。
那么问题来了,当这种需求来临时,该怎么处理呢?
还是老办法,最坏的设计下,单个线程能处理的请求数,假定为1000个,则将所有的请求数,入一个队列。当有新的请求进来时,判定该队列里面是否已经有1000个请求,在处理中。如果有,则该请求不处理 或是将该请求入另外一个线程的队列 或 分发到另外一台机器。一直持续到该队列的个数低于1000(用户取消叫车,超时自动放弃等)。当然了,具体的处理方式可以有很多种。但总体来说,都是按最糟糕的情况,来设计程序的临界值和处理方式。
总体来说,一个合理的程序,一定是满足其最坏的设计。