关于系统设计:稳定性秘密武器功能开关技术-京东物流技术团队

一、背景继上篇【稳定性:对于缩短MTTR的摸索】后,看到一些线上问题应急预案采纳的是回滚计划,然而在大部分牵扯代码场景下,开关技术才是线上问题疾速止血的最佳形式。比方履约平台组的Promise作为下单黄金链路,如遇线上问题的话,采纳通用的回滚形式须要5-10+分钟(500+台机器)并且回滚如果操作不当会减轻问题,而采纳开关技术则是秒级。同时Promise在解决日常迭代需要和稳定性保障方面,性能开关技术同样施展了重要的作用。针对改变范畴大、影响面广的需要,我通常会问上线了最坏状况是什么?应急预案是什么?你带开关了吗?。当然开关也是有老本的,接下来本篇跟大家一起交换下高频公布撑持下的性能开关技术实践与实际联合的点点滴滴。 二、什么是性能开关?性能开关其实就是一个轻量级的动静配置框架,它能够帮忙您在代码中动静治理配置项(你能够了解能够动静干涉代码逻辑走向)。通过应用性能开关,您能够依据须要为利用开启或敞开局部性能。这种办法通常实用于以下场景:设置黑白名单、降级业务性能、流量切量以及大促流动时的动静调整日志级别等。 从代码的角度来讲,每个开关的实质就是一个"if......else"条件语句块。三、开关用处对于高频率的公布上线来说,开关技术是一种正当的技术手段,被赋予了两种新的用处。 疾速止血:一旦生产环境出了问题,间接找到对应性能的开关选项,将其设置为“敞开”。隔离:行将性能代码隔离在线上执行门路之外,对用户不产生影响。四、开关老本应用开关技术也会带来老本。 首先,每个开关选项起码有两个状态,“开”和“闭”。当咱们在公布之前对软件进行性能验证时,须要思考每个开关在零碎中的状态,有时候甚至要进行组合测试,开关的数量越多,可能就会产生越多组合测试的老本。其次,并不是所有的开关代码都能以优雅的形式实现,给代码的编写和保护都带来了肯定的复杂性,须要仔细设计。最初,开关在零碎中存在的工夫越长,保护它的老本就越高。比方Promise零碎历史起因曾经200多个开关了,没有及时清理当初不敢动。五、开关治理为了可能最大化利用开关带来的益处,并尽可能减少它带来的老本,应该对开关进行系统化的治理,并尽可能遵循以下准则。 在满足业务需要及稳定性的前提下,尽可能少用开关技术。开关实质上是if...else...的语句,它会带来程序的复杂性,尤其是代码设计凌乱、代码模块职责不清晰时,更容易出错。易于治理:软件团队应答开关配置进行对立治理,不便查找和查看状态。开关策略标准化:开关策略是指开关的定义、命名以及如何配置。性能开关应该遵循对立的规范和标准,以便不同团队之间的合作和沟通更加顺畅。目前小组开关命名等也不标准,正在标准化途程中。可扩展性:性能开关应该具备可扩展性,以便在须要时可能轻松地增加新的性能或批改现有的性能。这能够通过应用模块化的设计和凋谢的接口来实现。在确保稳定性的前提下,尽量定期检查和清理不必要的开关项。Promise新性能开关逐渐清理中。6. 安全性:性能开关应该具备足够的安全措施,以确保只有受权的用户能力批改和配置开关状态。此外,性能开关还应该可能避免未经受权的拜访和攻打。如DUCC权限治理及XBP审批治理。 总之,继续交付中应用性能开关技术的准则应该是灵便、牢靠、平安、标准化、自动化、可追溯性和可扩展性的综合体现,以确保零碎可能在不同的环境和需要下保持稳定和高效。 六、典型利用场景开关可分为公布开关、运维开关、A/B试验开关、权限开关。具体利用场景如下: • 性能公布更加灵便:这些开关容许该代码性能提前部署到生产环境中,但性能不失效。比方Promise零碎在下单黄金链路属于上游,很多需要须要零碎先上线,待上游都上线实现后再关上开关进行业务验证。如下图DUCC配置: capactiySwitch.enable=true• 黑白名单性能:黑白名单是罕用的访问控制规定,通过性能开关能够疾速实现黑白名单性能。比方Promise中的KA时效白名单开关。如下图DUCC配置: kaPromiseSwitch.whiteList=010***,011***,012***• 线上验证:零碎上线后,业务须要在生产环境中测试验证,因为生产环境中测试验证存在肯定的危险,性能开关能够配置相干的验证参数组合(比方下单前依据用户pin、下单后订单号、仓库ID等),这样能够在生产环境中不影响其余用户体验的状况上来测试性能,能够更早地发现问题。如下图DUCC配置: jitSwitch.storeId=1-1,1-2,1-3,1-4,****• 运行时动静调整日志级别:在利用运行时动静批改日志级别的性能。比方Promise在618&双11大促峰值期间对日志进行降级(只打印出入参及上游依赖的出入参),TP99从30ms升高到13ms,待大促峰值过后日志调整回来,不便排查。如下图DUCC配置: log4j.logger=info• 降级业务性能:例如在大促到来的时候,能够通过开关将非核心的业务逻辑降级,缩小一些非必要的资源耗费。或者依赖上游JSF问题,如业务有损可承受,也可进行开关降级,通过开关敞开则不调用上游JSF。如下图DUCC配置: commonSwith.fence=true• 切量验证:重构新性能上线后,依据订单号或者pin百分比逐渐切量进行线上验证。如下图DUCC配置: commonSwith.percent=10• 管制客户端行为能力:对于APP来说,这种管制可能意味着客户端周期性地和服务器分割,例如多久同步一次和重试的频率、心跳工夫等 七、开关实际**7.1、**复用型开关比方很多场景发送MQ,目前可通过复用开关来配置发送MQ是异步还是同步形式。而不是每个topic配置一个开关,把雷同的场景对立设置为一个通用的开关。但须要留神通用开关的隔离性差,如果不进行配置校验验证则可能影响其余开关性能。 jmqUtil.asyncTopics=topic1,topic2,topic3,topic4,....比方依赖上游JSF三方接口较多,设计一个复用型开关判断是否须要降级上游 **7.2、**特定工夫失效开关开关个性:开关可配置多个属性值,依据指定工夫失效对应value 应用场景:比方仓库产能审批,之前业务是要求0点开关要失效对应版本,研发须要0点的时候配置,长期这样配置,研发效率低下,并且还须要按时按点对ducc开关进行批改。故设计为一个开关可提前配置好失效工夫和失效的value值。比方上面是产能审批的ducc开关,effectiveTime代表失效日期,version代表对应失效版本。 [ { "effectiveTime": "2023-03-09 12:00", "version": "76" }, { "effectiveTime": "2023-04-20 12:00", "version": "77" }, { "effectiveTime": "2023-05-14 00:00", "version": "78" }]八、总结总的来说,性能开关能够帮忙技术团队更无效地工作,同时还能够改善用户体验,升高公布新性能的危险。 参考: 继续交付2.0业务引领的DevOps精要 作者:京东物流 冯志文 起源:京东云开发者社区 自猿其说Tech 转载请注明起源

September 28, 2023 · 1 min · jiezi

关于系统设计:vivo-手机云服务建设之路平台产品系列04

作者:vivo 互联网平台产品研发团队 - He Zhichuang、Han Lei手机云服务目前作为每家手机厂商必备的一项根底服务,其服务能力和服务质量对用户来说能够说是十分重要。用户将本人大量的信息数据存储在云端,那咱们的云端服务如何保障服务的稳固和数据的平安,以及如何应答越来越多用户群体的应用?本文将次要介绍 vivo 手机云服务零碎的建设历程。 一、背景简直每家手机厂商都为用户提供了信息存储的云服务能力。通过一个账号,用户能够将手机零碎中的各种罕用的信息备份到云端,以便后续在适合的工夫点查看或复原本身的数据。然而因为用户量级微小,服务在设计零碎的时候须要思考的因素特地多,比方如何保障服务的稳定性,如何保障大文件的传输效率,以及如何保障用户文件的数据持久性等等。 除此之外,随着越来越多的终端用户开始应用vivo云服务,存储和计算的老本也一劳永逸。可能有局部人理解,某些手机厂商的云服务产品年度亏损数亿级别,而次要老本之处来自用户私人文件的存储老本。 另外,在平安方面,云服务在这块须要承当的使命更是重中之重。某些厂商的云服务已经呈现过用户数据泄露,竟然能够通过搜索引擎间接查问到用户的私人文件,这种事件一旦呈现,对企业品牌的打击和影响能够说是十分微小。 如上所述,云服务在建设过程中能够说是困难重重,那么vivo云服务在建设过程中,又是如何兼顾产品性能、资源老本、服务稳定性、数据安全等等诸多因素而进行设计的?且听后文细细合成。 二、产品能力与设计2.1 性能介绍2.1.1 多设施数据同步能力 当今智能设施倒退迅速,各个手机厂商相继推出了PAD、智能手表等设施。因而不同设施之间的数据互通诉求也随之而来,一个帐号实现数据互通。 拿vivo为例,vivo帐号能够同时在vivo手机、vivo PAD、PC上登录,用户能够在手机、PAD、PC上同时对联系人、日历等内容进行编辑,一端编辑,多端可见。 这种多设施数据同步互通就是云服务的一项外围性能。以后云服务反对同步的数据项:联系人、日历、便签、书签、黑名单、蓝牙、WLAN,后续会反对更多的数据项。 2.1.2 整机数据打包备份、恢复能力 手机行业性能更新迭代很快,新的亮点性能会吸引用户购买新机。然而新机购买回来后,各种数据的设置、新增对用户来说是个繁琐、头疼的事件。 为了不便用户将旧手机的数据迁徙到新手机上来,手机厂商提供了一些数据迁徙工具。如我司的互传,新老手机放在一起通过蓝牙能够不便的将数据导入新手机上。然而互传必须要求2个手机在一起,如果用户旧手机不在身边呢? 此场景下,云服务提供的整机打包性能很好的解决了此痛点。用户在应用旧手机期间,能够关上云服务的整机备份开关,云服务会主动将用户手机数据打成数据包备份至云端。 在用户购买新手机换机时,能够通过云服务疾速抉择老手机的打包数据进行复原,方便快捷。 2.1.3 云盘能力 云盘是一种业余的互联网存储工具,是互联网云技术的产物,它通过互联网为企业和集体提供信息的贮存,读取,下载等服务。具备平安稳固、海量存储的特点。 在vivo云服务中,除了诸如联系人、短信等数据类型内容的备份恢复能力之外,文件类型的云端存储能力,即云盘的能力同样重要。 用户能够将本人本地重要的图片、视频、文档等重要文件备份到云端,以便后续能够在云端后者在其余设施上能够拜访到该文件,同时借助于云盘的能力,用户也能够方便快捷的开释整顿本地空间。 除此之外,云盘还提供了丰盛的文件周边性能,例如压缩文件的在线解压,视频的在线播放,以及文档在线合作等等。 2.2 能力建设2.2.1 多设施数据一致性同步方案设计云服务数据同步的计划采纳的是相似于Git版本治理的概念,次要波及2个行为: 推数据:将本地设施增量数据推送至云端。拉数据:将云端增量数据拉取至本地。次要须要理解的有以下2点: 增量数据辨认;数据抵触解决。(1)增量数据辨认 云服务采纳的是基于数据版本的辨认计划:云端每条数据都有本身的版本号,版本号逐渐递增。 次要同步流程如下图: 如图可见,增量数据同步过程并不简单,整个流程总结如下: 客户端获取云端以后最大的数据版本sv;若客户端本地数据最大版本lv < sv,则证实云端数据有更新,客户端须要拉取云端增量数据;若lv = sv,则客户端判断本地是否存在增量更新数据,若有则将本地增量数据推送到云端。(2)数据抵触解决 数据抵触呈现在多设施同时应用的过程中,同时对同一条数据进行操作,造成数据抵触的状况。 因而同步数据流程须要思考数据抵触的场景。 常见的抵触解决方案有2种:a、主动为用户解决抵触。b、用户手动自行解决抵触。 主动为用户解决抵触个别有以下计划: 以最新的数据批改工夫为准,以批改工夫最迟的设施的数据为准。(多设施时钟无奈对立问题,前面上报的数据可能并不是用户实际上最初批改的)2条数据都保留。(会给用户造成数据反复的错觉,影响体验)用户手动自行解决抵触: 参照git的抵触解决形式,抵触数据展现给用户,由用户自行抉择内容的存留,最初将最终数据推送到云端。 因为云服务对接了很多不同模块的数据:联系人、日程、浏览器书签,不同数据的个性不一样,每种数据的抵触解决的规定也不一样。因而云服务采纳了将抵触数据返回给业务模块,供业务模块自行解决的策略,于业务方采纳上述哪种解决形式,由业务方自行决策。 2.2.2 整机数据打包备份、复原设计云服务采纳的是将本地不同模块的数据打包成文件,上传至云盘的计划。 通过树形构造,将整个包、不同模块、不同模块的数据文件进行关联,复原数据时,通过父包parentId即可获取到所有的数据文件的metaId,进行复原即可。 此计划的长处:不便疾速扩大备份手机其余模块的数据,服务器基本上不须要进行革新。 当然此计划也存在劣势:设施不同模块的数据格式对服务器属于黑盒,服务器难以针对模块的数据做实时的解析和展现。 2.2.3 云盘方案设计绝对于数据我的项目的同步备份,云盘模块次要聚焦的是文件类型的云端存取。 和一般业务模型相比,云盘业务的显著特点是逻辑简单,须要思考的细节很多:例如空间占用、数据安全、大文件传输效率等等,因而整个的零碎设计更加简单。 1、对象存储简介 《对象存储》是由云存储供应商提供的一套基于对象的海量存储服务,个别能够为用户能够提供海量、平安、高牢靠、低成本的数据存储能力。 在vivo云服务的存储逻辑中,用户的图片、视频、音频等文件目前均存储在对象存储服务中。 因为晚期vivo外部并无自建的对象存储能力,故一开始这部分数据均寄存在私有云,随着近两年vivo自建对象存储能力的欠缺,目前私有云数据已齐全迁徙到了自建存储。 2、云盘零碎架构 如上图所示,云盘波及到的周边模块泛滥,然而最外围的还是元数据模块、空间治理模块、文件解决这三个模块,概述如下: 元数据模块:次要用来形容文件的属性,例如文件的名称,文件的大小,媒体文件的长宽低等等。更形象的,元数据模块保留了除了文件实体内容之外的所有信息。 ...

March 31, 2023 · 1 min · jiezi

关于系统设计:带读-Designing-DataIntensive-Applications中文数据密集型系统设计

我在实习和工作的过程中发现技术计划的设计这一环节是后端开发散发魅力并且深深吸引我的中央,好的技术计划/架构设计 能够让整个零碎开发的开发者开发更轻松 后续的保护 以及拓展更容易 遇到高并发、各种软硬件生效人为谬误带来的挑战时更牢靠,总而言之,这让我感觉像是在设计本人世界乐高,我对此很有趣味,于是找到了这本书。 数据系统根底这里先给出了一个全文检索服务器的例子(之前《Go In Action》也拿搜寻例子做为开篇,看来文本检索很受大家欢送呀),,假设某个利用蕴含缓存层(例如Memcached)与全文索引服务器(如Elasticsearch或Solr),二者与主数据库放弃关联,通常由利用代码负责缓存、索引与主数据库之间的同步,如下图所示 在设计这个零碎的时候,咱们须要 通过缓存正确地刷新来保障客户端们看到的成果统一;零碎呈现部分生效的时候 保证数据的正确性和完整性零碎产生降级时为客户提供统一良好的体现。负载减少时零碎便于拓展...这本书次要围绕零碎设计的 可靠性可扩展性可维护性开展可靠性硬件故障 在设计零碎思考硬件可靠性的时候 咱们须要思考到 硬盘解体、内存故障、电网停电、网线被拔掉等状况。应答的计划大抵有:1.增加硬件冗余(比方磁盘配置RAID,服务器配置双电源,热插拔CPU,数据中心增加备用电源,发电机等)2.滚动降级(前面会介绍)软件谬误特点工夫值导致服务器解体,比方2012年6月30日产生的闰秒触发的Linux内核bug、依赖服务故障、级联故障等。软件谬误很多时候没方法疾速解决,须要在零碎设计之初认真查看各种交互和依赖,进行全面的测试,过程隔离,提前设计好异样告警。人为谬误经考察发现,运维人员的配置谬误是某大型互联网服务下线的首要起因。所以在零碎设计的时候,咱们能够先假设人是不牢靠的,用最小的出错形式来设计零碎、拆散出最易出错的中央、充沛测试、制订出错是疾速复原机制等。未完待续。。。 参考 Kleppmann M. Designing data-intensive applications: The big ideas behind reliable, scalable, and maintainable systems[M]. " O'Reilly Media, Inc.", 2017.

January 16, 2023 · 1 min · jiezi

关于系统设计:职工管理系统设计思路

需要概述:既然设计这个零碎,就要思考对应需要,职工管理系统,顾名思义,是为了治理职工而设计,零碎中最根本的“增删改查”天然少不了。 首先设计头文件首先,咱们定义多个头文件(设立头文件的目标次要是:提供全局变量、全局函数的申明或专用数据类型的定义,从而实现拆散编译和代码复用。 同时,也有利于强化安全性。) 别离如下(以下每个头文件与每种身份的人对应): boss.h #pragma once#include<iostream>using namespace std;class boss :public worker{public: boss(int id, string name, int post); virtual void showinfo(); virtual string getpost();};employee.h #pragma onceclass employee :public worker{public: employee(int id, string name, int post); virtual void showinfo(); virtual string getpost();};manager.h #pragma onceclass Manager :public Worker{public: Manager(int id, string name, int post); virtual void ShowInfo(); virtual string getPost();};worker manager.h #pragma once#include<iostream>using namespace std;#include"boss.h"#include"manager.h"#include"worker.h"#include"employee.h"#include<fstream>#define TXT "empfine.txt"class worker_manager{public: worker_manager(); void showmenu(); int m_EmpNum; worker** m_EmpArray; void Add_Emp(); void save(); bool_m_File; int get_EmpNum(); void In_emp(); void Show_emp(); int Ison_Emp(int id); void Del_Emp(); void Mod_Emp(); void Find_Emp(); void Sort_Emp(); void Clean_File(); ~worker_manager();};worker.h ...

June 9, 2022 · 7 min · jiezi

关于系统设计:Drawio-使用总结

1. 介绍Drawio是一款开源的流程图绘制工具,领有大量的收费素材和模板,能够绘制流程图,类图,时序图,组织架构图等。 2. 装置Drawio 桌面版分为 installer版 、no-installer版 、网页版(公共/自建): installer版点击装置后可建设文件后缀名关联(通常应用该版本) 安装包下载地址:https://github.com/jgraph/dra... no-installer版无需装置,点击即用。网页版(公共) 网页版拜访地址:https://www.draw.io/ Drawio 网页版(自建) 从GitHub上下载其源码和公布包。公布包能够部署到本人的Tomcat服务器中,启动后能够在浏览器中应用Drawio。实用于网络环境不佳或局域网内应用。 3. 应用3.1 泳道图3.1.1 横向泳道图特点:只能横向新增泳道,鼠标选中要增加的地位,会呈现蓝色的小箭头,无论是点击横向的还是纵向的,后果都是增加横向泳道。 有两种横向泳道图: 有题目无标题有题目横向泳道图 无标题横向泳道图 3.1.2 纵向泳道图增加形式和特点与横向统一,如下图所示: 3.2 ER图E-R图也称实体-分割图(Entity Relationship Diagram),提供了示意实体类型、属性和分割的办法,用来形容事实世界的概念模型。 3.2.1 手动创立ER图手动创立ER图流程如下: 抉择 E-R图 手动创立增加 E-R 图 向列表(list)或UML类形态(UML class shape)增加一行,两种形式: 从Entity Relation形态库(shape library)中拖动List Item形态(shape),而后将其拖放到列表形态(list shape)上以插入新条目。将Item形态从UML形态库中拖放到类形态(class shape)上,以插入新的属性(attribute)或办法(method)。 3.2.2 通过sql创立ER图通过SQL 创立 E-R 图,须要对建表语句 Sql 做一些批改,具体如下: 将主键挪到第一个,其余外键能够紧随在主键前面。主键前面追加PRIMARY KEY关键字,以便后续主动生成款式追加;以示意完结地位留下所有的NOT NULL标记,移除不必要的内容仅保留字段名、字段类型、是否反对为空、正文信息一个 Demo 如下: CREATE TABLE DatabaseName '数据表名'( Id bigint(20) NOT NULL PRIMARY KEY VersionNo bigint(20) '版本号' .....);在Draw.io中部菜单找到+号菜单,找到高级中的从SQL导入 ...

April 18, 2022 · 1 min · jiezi

关于系统设计:面试官说你来设计一个短链接生成系统吧

引言置信大家在生活中,特地是最近的双十一流动期间,会收到很多短信,而那些短信都有两个特色,第一个是简直都是垃圾短信,这个特点此处能够忽略不计,第二个特点是链接很短,比方上面这个: 咱们晓得,短信有些是有字数限度的,间接放一个带满各种参数的链接,不适合,另外一点是,不想裸露参数。益处无非以下: 太长的链接容易被限度长度短链接看着简洁,长链接看着容易懵平安,不想裸露参数能够对立链接转换,当然也能够实现统计点击次数等操作那背地的原理是什么呢?怎么实现的?让你实现这样的零碎,你会怎么设计呢?【来自于某鹅场面试官】 短链接的原理短链接展现的逻辑这里最重要的知识点是重定向,先温习一下http的状态码: 分类含意1**服务器收到申请,须要请求者继续执行操作2**胜利,操作被胜利接管并解决3**重定向,须要进一步的操作以实现申请4**客户端谬误,申请蕴含语法错误或无奈实现申请5**服务器谬误,服务器在解决申请的过程中产生了谬误那么以 3 结尾的状态码都是对于重定向的: 300:多种抉择,能够在多个地位存在301:永恒重定向,浏览器会缓存,主动重定向到新的地址302:长期重定向,客户端还是会持续应用旧的URL303:查看其余的地址,相似于301304:未修改。所申请的资源未修改,服务器返回此状态码时,不会返回任何资源。305:须要应用代理能力拜访到资源306:废除的状态码307:长期重定向,应用Get申请重定向整个跳转的流程: 1.用户拜访短链接,申请达到服务器2.服务器将短链接装换成为长链接,而后给浏览器返回重定向的状态码301/302 301永恒重定向会导致浏览器缓存重定向地址,短链接零碎统计拜访次数会不正确302长期重定向能够解决次数不准的问题,然而每次都会到短链接零碎转换,服务器压力会变大。3.浏览器拿到重定向的状态码,以及真正须要拜访的地址,重定向到真正的长链接上。从下图能够看出,的确链接被302重定向到新的地址下来,返回的头外面有一个字段Location就是所要重定向的地址: <img src="https://markdownpicture.oss-cn-qingdao.aliyuncs.com/blog/20211110235518.png" style="zoom:70%;" /> 短链接怎么设计的?全局发号器必定咱们第一点想到的是压缩,像文件压缩那样,压缩之后再解压还原到原来的链接,重定向到原来的链接,然而很可怜的是,这个是行不通的,你有见过什么压缩形式能把这么长的数字间接压缩到这么短么?事实上不可能。就像是Huffman树,也只能对那种反复字符较多的字符串压缩时效率较高,像链接这种,可能带很多参数,而且各种不规则的状况都有,间接压缩算法不事实。 那https://dx.10086.cn/tzHLFw与https://gd.10086.cn/gmccapp/webpage/payPhonemoney/index.html?channel=之间的装换是怎么样的呢?后面门路不变,变动的是前面,也就是tzHLFw与gmccapp/webpage/payPhonemoney/index.html?channel=之间的转换。 理论也很简略,就是数据库外面的一条数据,一个id对应长链接(相当于全局的发号器,全局惟一的ID): idurl1https://gd.10086.cn/gmccapp/w...这里用到的,也就是咱们之前说过的分布式全局惟一ID,如果咱们间接用id作为参数,貌似也能够:https://dx.10086.cn/1,拜访这个链接时,去数据库查问取得真正的url,再重定向。 单机的惟一ID很简略,用原子类AtomicLong就能够,然而分布式的就不行了,简略点能够用 redis,或者数据库自增,或者能够思考Zookeeper之类的。 id 转换策略然而间接用递增的数字,有两个害处: 数字很大的时候,还是很长递增的数字,不平安,规律性太强了显著咱们平时看到的链接也不是数字的,个别都是大小写字母加上数字。为了缩短链接的长度,咱们必须把id转换掉,比方咱们的短链接由a-z,A-Z,0-9组成,相当于62进制的数字,将id转换成为62进制的数字: public class ShortUrl { private static final String BASE = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static String toBase62(long num) { StringBuilder result = new StringBuilder(); do { int i = (int) (num % 62); result.append(BASE.charAt(i)); num /= 62; } while (num > 0); return result.reverse().toString(); } public static long toBase10(String str) { long result = 0; for (int i = 0; i < str.length(); i++) { result = result * 62 + BASE.indexOf(str.charAt(i)); } return result; } public static void main(String[] args) { // tzHLFw System.out.println(toBase10("tzHLFw")); System.out.println(toBase62(27095455234L)); }}id转 62位的key 或者key装换成为id都曾经实现了,不过计算还是比拟耗时的,不如加个字段存起来,于是数据库变成了: ...

December 4, 2021 · 1 min · jiezi

关于系统设计:System-Design-学习笔记3

本文是GitHub热门我的项目零碎设计入门的学习笔记第3篇,本篇介绍了StackOverflow的架构。参考文章:Stack Overflow: The Architecture - 2016 Edition。架构图如下: The Internets网络接入即图中最下面那一部分,简略来说,StackOverflow用了CloudFlare的DNS服务器,同时也有本人的DNS服务器。接入了4个ISP服务供应商,路由器也都是主/主复制的模式。 Load Balancers (HAProxy)2台机器,用的是HAProxy做负载平衡,SSL也到这一层终止。机器内存较大用于缓存TLS会话。 Web Tier这一层是11台Web服务器,其中有2台是dev测试的。 Service Tier与Web Tier相似,提供的是外部的Web服务。 Cache & Pub/Sub (Redis)Master/Slave的Redis集群,保留HTTP缓存(之前的服务器中有local缓存)。Protobuf格局。本人开发的Redis客户端。 Search (Elasticsearch)专门给搜寻设置的服务器。每个数据中心都是一个3节点的集群。 Databases (SQL Server)2个SQL集群,外面的各个机器按地区离开。

April 7, 2021 · 1 min · jiezi

关于系统设计:System-Design-学习笔记2

本文是GitHub热门我的项目零碎设计入门的学习笔记第2篇,本篇记录了零碎架构中常常用到的各个组件。 略过的内容DNSCDN反向代理缓存平安负载平衡常见的负载平衡算法有: 随机最小负载/连贯轮询/加权轮询哈希负载平衡还能够分为四层负载平衡和七层负载平衡,例如AWS提供的NLB和ALB就是这样辨别的。简略来说,四层负载平衡(NLB)工作在TCP那一层,七层负载平衡(ALB)工作在HTTP那一层,所以NLB性能好,能解决所有TCP流量,ALB性能略微差点,只能解决HTTP流量,但能依据HTTP自身的数据进行路由(例如申请的门路、参数、Header不同)。 数据库关系型数据库关系型数据库的一些技术及其细节: 主从复制 主库负责读写,从库只负责读主库在复制数据到从库之前挂掉会造成数据失落从库太多写入提早主库挂掉时,从库晋升为主库须要额定的逻辑主主复制 须要额定的逻辑来决定写入哪个数据库与主从复制一样的问题联结 把数据按不同性能分为多个库,例如用户库、产品库不不便JOIN利用须要额定逻辑来确定读写哪个库分片 一个表分为多个表,如用户表按姓名首字母分为多个SQL语句更简单分片不合理可能导致数据不平衡,例如某个姓的用户很多不不便JOIN非规范化 即不齐全遵循关系型数据库的标准来贮存数据冗余信息来缩小JOIN操作,适宜写少读多的状况SQL调优 慢查问应用索引非关系型数据库BASE实践: Basically Available: 分布式系统在呈现不可预知故障的时候容许损失局部可用性Soft state: 容许存在中间状态Eventually consistent: 数据最终会统一类型: KeyValue型,如redis文档型,如MongoDB列式存储,如HBase图数据库SQL还是NoSQL抉择SQL的起因 结构化数据关系型数据,须要join事务等抉择NoSQL的起因 半结构化数据非关系型数据库高吞吐量等适宜NoSQL的场景 埋点和日志数据热数据元数据等异步工作队列和音讯队列 音讯队列接管,保留和传递音讯,如RabbitMQ、Kafka工作队列接管工作及其相干数据,并执行工作,例如Celery通信略过的内容OSI 7层模型UDP协定RPCRESTHTTP协定HTTP2.0TCP协定序列号和校验码确认包和主动重传流量管制和拥塞管制

April 6, 2021 · 1 min · jiezi

关于系统设计:System-Design-学习笔记1

本文是GitHub热门我的项目零碎设计入门的学习笔记第1篇,本篇记录了Anki记忆软件和一些根本的实践概念。 工具AnkiAnki是一个卡片式的用于帮忙记忆的软件,它的数据文件是以.apkg结尾的,这种数据文件叫做抽认卡堆。零碎设计入门里提供了抽认卡堆,帮忙咱们记忆一些外围概念,咱们能够在手机上或者电脑上下载一个Anki并关上抽认卡堆。目前零碎设计入门里提供的抽认卡堆都是英文的。 基本概念可扩展性(Scalability)简略来说,如果零碎的性能(Performance)的增长与资源的减少是成比例的,服务就是可扩大的。另一个角度来对待性能与可扩展性: 如果你的零碎有性能问题,对于单个用户来说是迟缓的;如果你的零碎有可扩展性问题,单个用户较快但在高负载下会变慢;CAP实践在一个分布式计算零碎中,只能同时满足下列的两点: 一致性(Consistency):每次拜访都能取得最新数据但可能会收到谬误响应可用性(Availability): 每次拜访都能收到非错响应,但不保障获取到最新数据分区容错性(Partition Tolerance): 在任意分区网络故障的状况下零碎仍能持续运行这里没有举例子,因而感觉对于之前没有概念的人来说不太好了解,举荐再看一下这篇【CAP 实践常被解释为一种“三选二”定律,这是否是一种误会】。 一致性模式弱一致性最终一致性强一致性可用性模式故障切换 Active-PassiveActive-Active复制 主-从主-主

April 6, 2021 · 1 min · jiezi

SOA架构电商商城系统平台框架

对以往做过的电商项目技术架构做一次总结。 使用SOA分布式架构的方式去治理电商项目,解决分布式、高并发、高可用、集群、负载均衡等问题。 互联网大型网站项目架构拆分: 一、前端 1.资源静态化 网上商城系统网站静态化资源就是请求一个url访问一个服务器上面的网页,而且这个网页上的资源基本不会发生变化,所以我们的每次请求其实都是重复请求。 2.负载均衡 负载均衡是分布式服务架构设计必须考虑的因素之一,它将请求/数据均匀的平摊到各个操作元件中。 二、应用服务 1.业务模块化 将应用程序根据网上电子商城系统业务模块进行拆分,使每个模块能够独立运行在服务器上。在发布时,某个模块的问题不会影响到整个应用程序,只需解决出现问题的模块,然后将其发布,是相对粗粒度的服务应用。 2.服务总线 所有的应用之间需要连接时,如果应用变多,相对的连接数也会成倍增长,这时需要一个服务总线,将所有的服务接口透明化出来,对于应用于应用之间的连接,只需经过服务总线这个过程,实现1对1的连接。 3.消息队列 对于互联网架构异步操作必不可少,使用消息队列可以解决各种操作的同步性,将部分操作变成异步。异步可以防止互联网网站的高峰操作。 同时消息队列对应用之间进行解耦,应用之间的操作不需要约定,也可以处理相应的操作。 4.读写分离 数据库的写比较耗时,而数据库的读效率很高,所以数据库的写操作影响了查询效率。在应用中通过切换数据源实现读写分离。 5.缓存 缓存能够减轻数据库访问的一定压力,加快访问速度,是互联网架构中必不可少的元素。 三、数据库 1、分库分表 垂直分表:主要是分散系统负载,让一台机器做的事情变成几台服务器做。 水平分表:缩小索引区大小,使查找更快。 2、分布式系统 1.分布式的应用和服务 将应用和服务进行分割,应用和服务模块分布式部署。这样做不仅提高并发访问能力、减少数据库连接和资源消耗,还能使不同 应用复用服务,利于扩展。 2.分布式静态资源 对网站静态资源如JS、CSS、图片资源进行分布式部署,减轻应用服务器负载压力,提高访问速度。 原创文章作者:数商云,转载请标注来源

May 7, 2019 · 1 min · jiezi

Software-System-Analysis-and-Design-5

使用 UMLet 建模

May 6, 2019 · 1 min · jiezi

Software-System-Analysis-and-Design-6

1、使用类图,分别对 Asg_RH 文档中 Make Reservation 用例以及 Payment 用例开展领域建模

May 6, 2019 · 1 min · jiezi

Software System Analysis and Design | 4

用例的概念用例(use case),或使用案例、用况,是软件工程或系统工程中对系统如何反应外界请求的描述,是一种通过用户的使用场景来获取需求的技术。每个用例提供了一个或多个场景,该场景说明了系统是如何和最终用户或其它系统互动,也就是谁可以用系统做什么,从而获得一个明确的业务目标。编写用例时要避免使用技术术语,而应该用最终用户或者领域专家的语言。用例一般是由软件开发者和最终用户共同创作的。用例和场景的关系?什么是主场景或 happy path?用例是是一组相关的成功和失败的场景(success and failure scenarios),这些场景是用于描述一个actor使用系统去support一个目标(goal)。主成功场景或happy path是用例从触发事件开始,一步一步执行,最终满足用例利益的步骤集合。主成功场景应该包括以下信息:两个执行者之间的交互。如,用户提交了订单。为保证主成功场景得以继续的确认。如,系统确认用户密码。主成功场景推进过程中的内部变化。如,系统扣除用户账户余额。用例有哪些形式?摘要:简洁的一段式概要,通常用于主成功场景。在早期需求分析过程中,为快速了解主题和范围,通常花费少量时间快速编写。非正式形式:非正式的段落格式,用几个段落覆盖不同的场景。详述:详细编写所有步骤和各种变化,同时具有补充部分,如前置条件和成功保证。确定并以摘要形式编写大量用例后,在第一次需求讨论中,详细地编写其中少量的具有重要架构意义和高价值的用例。对于复杂业务,为什么编制完整用例非常难?复杂业务的场景较多,场景较为复杂。在前期的考虑中,很难不遗漏一些业务条件和需求,且这些需求条件还可能发生变化。所以对于复杂业务,编制完整用例且不遗漏情景、良好地安排每个场景、场景内元素地关系非常困难。什么是用例图?用例图是描述系统与其他外部系统以及用户之间交互的图形,即用例图描述了谁将使用系统,用户希望以什么方式与系统交互。用例图确定系统中所包含的参与者、用例和两者之间的对应关系, 它描述的是关于系统功能的一个概述, 描述软件应具备哪些功能模块以及这些模块之间的调用关系。 用例图包含了用例和参与者, 用例之间用关联来连接以求把系统的整个结构和功能反映给非技术人员(通常是软件的用户)。用例图的基本符号与元素?参与者:表示与您的应用程序或系统进行交互的用户、组织或外部系统。用一个小人表示。用例(Use Case): 用例就是外部可见的系统功能,对系统提供的服务进行描述。 用椭圆表示子系统(Subsystem):用来展示系统的一部分功能,这部分功能联系紧密。关系用例图中涉及的关系有:关联、泛化、包含、扩展;关联(Association):表示参与者与用例之间的通信,任何一方都可发送或接受消息。【箭头指向】:指向消息接收方泛化(Inheritance):就是通常理解的继承关系,子用例和父用例相似,但表现出更特别的行为;子用例将继承父用例的所有结构、行为和关系。子用例可以使用父用例的一段行为,也可以重载它。父用例通常是抽象的。【箭头指向】:指向父用例包含(Include):包含关系用来把一个较复杂用例所表示的功能分解成较小的步骤;【箭头指向】:指向分解出来的功能用例扩展(Extend):扩展关系是指 用例功能的延伸,相当于为基础用例提供一个附加功能。【箭头指向】:指向基础用例依赖(Dependency):表示源用例依赖于目标用例;【箭头指向】:指向被依赖项项目(Artifact):用依赖关系把某个用例依赖到项目上。用例图的画法与步骤确定参与者,包括:主要参与者:谁将使用系统的主要功能、谁将需要系统的支持以完成工作等协作参与者:谁将提供对应的系统功能、谁将维护系统,保证系统处于工作状态等幕后参与者:谁会对系统产生的结果感兴趣根据用户需求识别和创作用例,主要重点在于:识别使用系统的主要参与者(primary actors)/角色(roles)识别系统依赖的外部系统识别用例(服务)识别用户级别用例(user goal level)识别子功能级别的用例(sub function level)建立 Actor 和 Use Cases 之间的关联。用例图给利益相关人与开发者的价值有哪些?对于利益相关人:可以直观看到系统的结果和用户的功能体验,保证系统按照用户的需求进行设计。用例能够根据需要对复杂程度和形式化程序进行增减调节,即能够响应用户(利益相关人)提出的需求,而用例图则使得这种调节更加便利,可以通过修改图形间的关系实现。对于开发者来说:用例图是设计者设计过程的结论与参考,设计者与开发者之间的交流工具,开发者开发过程的蓝图。用例图使得开发者能够更明确地获得需求,更好地理解需求。用例图可以指导开发和测试,同时可以在整个过程中对其他工作流起到指导作用。选择2-3个你熟悉的类似业务的在线服务系统(或移动 APP),如定旅馆(携程、去哪儿等)、定电影票、背单词APP等,分别绘制它们用例图。并满足以下要求:1. 请使用用户的视角,描述用户目标或系统提供的服务2. 粒度达到子用例级别,并用 include 和 exclude 关联它们3. 请用色彩标注出你认为创新(区别于竞争对手的)用例或子用例4. 尽可能识别外部系统和服务订旅馆:定电影票:1. 为什么相似系统的用例图是相似的?相似系统面对的参与者和用例是相似的,用例之间的关系也是同构的。用户预期的功能都是相似的,即不同的同类系统一定具有一致基本功能以及带有自己特色的扩展功能。所以体现在用例图上也是相似的。2. 如果是定旅馆业务,请对比 Asg_RH 用例图,简述如何利用不同时代、不同地区产品的用例图,展现、突出创新业务和技术不同时代对预定的酒店的需求不同。可以让筛选算法与时俱进,满足一些不同的主流要求。且用户会需要更加优秀、好用、有参考价值的评价系统,也需要随时更新。而不同地区的消费特点不同,旅游胜地和普通城市用户对于酒店预订的需求有差别,可以在用例图上突出一些特点。3. 如何利用用例图定位创新思路(业务创新、或技术创新、或商业模式创新)在系统中的作用应该通过创新点在图中的位置来判断。如果创新位于较高的父级,则作用比较大。如果是子类或者是被包括的关系,则作用相对较小。4. 请使用 SCRUM 方法,选择一个用例图,编制某定旅馆开发的需求(backlog)开发计划表IDNameImpEstHow to demo1find hotel103Find the hotel by name or location or type2make reservation74determine hotel, room type, time and then confirm3pay reservation73payment supported by pay system4modify reservation52modify reservation and hotel make approval5comment hotel42Comment of the hotel and show to others5. 根据任务4,参考 使用用例点估算软件成本,给出项目用例点的估算根据用户点方法,对用例分配权重的标准是:简单用例:1 到 3 个事务,权重=5一般用例:4 到 7 个事务,权重=10复杂用例:多于 7 个事务,权重=15用例业务计算原因UC比重查找酒店32 简单下订单76 一般支付21 简单修改订单32 简单评论32 简单 ...

April 10, 2019 · 1 min · jiezi

聊聊开发中幂等性问题

原文地址:聊聊开发中幂等性问题幂等 (idempotence) 的概念幂等的数学概念幂等是源于一种数学概念。其主要有两个定义如果在一元运算中,x 为某集合中的任意数,如果满足 f(x) = f(f(x)) ,那么该 f 运算具有幂等性,比如绝对值运算 abs(a) = abs(abs(a)) 就是幂等性函数。如果在二元运算中,x 为某集合中的任意数,如果满足 f(x,x) = x,前提是 f 运算的两个参数均为 x,那么我们称 f 运算也有幂等性,比如求大值函数 max(x,x) = x 就是幂等性函数。幂等性在开发中的概念在数学中幂等的概念或许比较抽象,但是在开发中幂等性是极为重要的。简单来说,对于同一个系统,在同样条件下,一次请求和重复多次请求对资源的影响是一致的,就称该操作为幂等的。比如说如果有一个接口是幂等的,当传入相同条件时,其效果必须是相同的。特别是对于现在分布式系统下的 RPC 或者 Restful 接口互相调用的情况下,很容易出现由于网络错误等等各种原因导致调用的时候出现异常而需要重试,这时候就必须保证接口的幂等性,否则重试的结果将与第一次调用的结果不同,如果有个接口的调用链 A->B->C->D->E,在 D->E 这一步发生异常重试后返回了错误的结果,A,B,C也会受到影响,这将会是灾难性的。在生活中常见的一些要求幂等性的例子:博客系统同一个用户对同一个文章点赞,即使这人单身30年手速疯狂按点赞,那么实际上也只能给这个文章 +1 赞在微信支付的时候,一笔订单应当只能扣一次钱,那么无论是网络问题或者bug等而重新付款,都只应该扣一次钱幂等性与并发安全在查阅网络资料的时候,我看到许多文章把幂等性和并发安全的问题有些混淆了。幂等性是系统接口对外的一种承诺,而不是实现,承诺多次相同的操作的结果都会是一样的。而并发安全问题是当多个线程同时对同一个资源操作时,由于操作顺序等原因导致结果不正确。这两个实际上是完全独立的两个问题,比如说同一笔订单即使你不停的提交支付,如果扣除了多次钱,就说明该操作不幂等。而有多笔订单同时进行支付,最后扣除金额不是这多笔金额的总和,那么说明该操作有并发安全问题。所以幂等性和并发安全是完全两个维度的问题,要分开讨论解决。我在一些讨论幂等性的文章中看到中给出的解决方案为‘悲观锁’和‘乐观锁’,这两个方案可以很好的解决并发问题,但是却不应该是幂等性问题的解决方案,特别是悲观锁是用于防止多个线程同时修改一个资源的。倒是乐观锁的版本号机制可以勉强以 token 或者状态标识 作为版本号来实现幂等性(下文解释token 和状态标识),勉强说的过去。所以说幂等性与并发安全是不同的,在本文就只讨论幂等性的问题,对于并发安全问题不做讨论Http 协议与幂等性如果把操作按照功能分类,那就是增删改查四种,在 http 协议中则表现为 Get、Post、Put、Delete 四种。查询操作 (Get)Get 方法用于获取资源,不应当对系统资源进行改变,所以是幂等的。注意这里的幂等提现在对系统资源的改变,而不是返回数据的结果,即使返回结果不相同但是该操作本身没有副作用,所以幂等。删除操作 (Delete)Delete 方法用于删除资源,虽然改变了系统资源,但是第一次和第N次删除操作对系统的作用是相同的,所以是幂等的。比如要删除一个 id 为 1234 的资源,可能第一次调用时会删除,而后面所有调用的时候由于系统中已经没有这个 id 的资源了,但是第一次操作和后面的操作对系统的作用是相同的,所以这也是幂等的,调用者可以多次调用这个接口不必担心错误。修改操作 (Put)修改操作有可能是幂等的也可能不幂等。如果修改的资源为固定的,比如说把账户中金额改为 1000 元,无论调用几次都是幂等的。假如资源不固定,比如账户中金额减少50元,调用一次和调用多次的结果肯定不一样,这时候就不幂等了。在修改操作中想要幂等在下文中讨论。新增操作 (Post)Post 新增操作天生就不是一个幂等操作,其在 http 协议的定义如下:The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. (https://www.w3.org/Protocols/…)在其定义中表明了 Post 请求用于创建新的资源,这意味着每次调用都会在系统中产生新的资源,所以该操作注定不是幂等操作。这时候想要幂等就必须在业务中实现,方案在下文会讨论。实现幂等性的方案在上面提到的幂等性还是比较理论,下面结合一些常见的实际业务场景来讨论幂等性设计方案。去重表利用数据库的特性来实现幂等。通常是在表上构建一个唯一索引,那么只要某一个数据构建完毕,后面再次操作也无法成功写入。常见的业务就是博客系统点赞功能,一个用户对一个博文点赞后,就把用户 id 与 博文 id 绑定,后续该用户点赞同一个博文就无法插入了。或是在金融系统中,给用户创建金融账户,一个用户肯定不能有多个账户,就在账户表中增加唯一索引来存储用户 id,这样即使重复操作用户也只能拥有一个账户。状态标识状态标识是很常见的幂等设计方式,主要思路就是通过状态标识的变更,保证业务中每个流程只会在对应的状态下执行,如果标识已经进入下一个状态,这时候来了上一个状态的操作就不允许变更状态,保证了业务的幂等性。状态标识经常用在业务流程较长,修改数据较多的场景里。最经典的例子就是订单系统,假如一个订单要经历 创建订单 -> 订单支付取消 -> 账户计算 -> 通知商户 这四个步骤。那么就有可能一笔订单支付完成后去账户里扣除对应的余额,消耗对应的优惠卷。但是由于网络等原因返回了错误信息,这时候就会重试再次去进行账户计算步骤造成数据错误。所以为了保证整个订单流程的幂等性,可以在订单信息中增加一个状态标识,一旦完成了一个步骤就修改对应的状态标识。比如订单支付成功后,就把订单标识为修改为支付成功,现在再次调用订单支付或者取消接口,会先判断订单状态标识,如果是已经支付过或者取消订单,就不会再次支付了。Token 机制Token 机制应该是适用范围最广泛的一种幂等设计方案了,具体实现方式也很多样化。但是核心思想就是每次操作都生成一个唯一 Token 凭证,服务器通过这个唯一凭证保证同样的操作不会被执行两次。这个 Token 除了字面形式上的唯一字符串,也可以是多个标志的组合(比如上面提到的状态标志),甚至可以是时间段标识等等。举个例子,在论坛中发布一个新帖子,这是一个典型的 Post 新增操作,要怎样防止用户多次点击提交导致产生多个同样的帖子呢。可以让用户提交的时候带一个唯一 Token,服务器只要判断该 Token 存在了就不允许提交,便能保证幂等性。上面这个例子比较容易理解,但是业务比较简单。由于 Token 机制适用较广,所以其设计中要注意的要求也会根据业务不同而不同。Token 在何时生成,怎么生成?这是该机制的核心,就拿上面论坛系统来说,如果你在用户提交帖子的时候才生成 Token,那用户每次点提交都会生成新的 Token 然后都能提交成功,就不是幂等的了。必须在用户提交内容之前,比如进入编辑页面的时候生成 Token,用户在提交的时候内容带着 Token 一起提交,对于同一个页面无论用户提交多少次,就至多能成功一次。所以 Token 生成的时机必须保证能够使该操作具多次执行都是相同的效果才行。使用 Token 机制就要求开发者对业务流程有较好的理解。结语幂等性是开发当中很常见也很重要的一个需求。尤其是金融、支付等行业对其要求更加严格,既要有好的性能也要有严格的幂等性。除了对其概念的掌握,理解自身业务需求更是实现幂等功能的要点,必须处理好每一个结点细节,一旦某个地方没有设计完善,最后的结果可能仍旧达不到要求。 ...

April 9, 2019 · 1 min · jiezi

IM系统设计实践(一)

IM系统设计实践

April 6, 2019 · 1 min · jiezi

Software System Analysis and Design | 2

一、简答题1. 用简短的语言给出对分析、设计的理解分析(analysis):强调对问题和需求的调查和研究,而不是解决方案。设计(design):强调的是满足需求的概念上的解决方案(在软件方面和硬件方面),而不是其实现。2. 用一句话描述面向对象的分析与设计的优势面向对象的分析与设计可以解决传统软件研发过程中由于软件模块化结构化程度不高带来的软件重用性差、软件可维护性差、开发出的软件不能满足用户需求等方面的问题,提高了软件开发的复用性、扩展性、可维护性、开发弹性。3. 简述 UML(统一建模语言)的作用UML(统一建模语言):是面向对象的可视化建模语言。UML中有3种构造块:事物、关系和图,事物是对模型中最具有代表性的成分的抽象,关系是把事物结合在一起,图聚集了相关的事物UML中有九种图:1、用例图描述角色以及角色与用例之间的连接关系。说明的是谁要使用系统,以及他们使用该系统可以做些什么。2、类图类图是描述系统中的类,以及各个类之间的关系的静态视图。能够让我们在正确编写代码以前对系统有一个全面的认识。类图是一种模型类型,确切的说,是一种静态模型类型。3、对象图与类图极为相似,它是类图的实例,对象图显示类的多个对象实例,而不是实际的类。它描述的不是类之间的关系,而是对象之间的关系。4、活动图描述用例要求所要进行的活动,以及活动间的约束关系,有利于识别并行活动。能够演示出系统中哪些地方存在功能5、状态图描述类的对象所有可能的状态,以及事件发生时状态的转移条件。可以捕获对象、子系统和系统的生命周期。他们可以告知一个对象可以拥有的状态,并且事件(如消息的接收、时间的流逝、错误、条件变为真等)会怎么随着时间的推移来影响这些状态。一个状态图应该连接到所有具有清晰的可标识状态和复杂行为的类;该图可以确定类的行为,以及该行为如何根据当前的状态变化,也可以展示哪些事件将会改变类的对象的状态。状态图是对类图的补充。6、序列图(顺序图)序列图是用来显示你的参与者如何以一系列顺序的步骤与系统的对象交互的模型。顺序图可以用来展示对象之间是如何进行交互的。顺序图将显示的重点放在消息序列上,即强调消息是如何在对象之间被发送和接收的。7、协作图和序列图相似,显示对象间的动态合作关系。可以看成是类图和顺序图的交集,协作图建模对象或者角色,以及它们彼此之间是如何通信的。如果强调时间和顺序,则使用序列图;如果强调上下级关系,则选择协作图;这两种图合称为交互图。8、构件图 (组件图)描述代码构件的物理结构以及各种构建之间的依赖关系。用来建模软件的组件及其相互之间的关系,这些图由构件标记符和构件之间的关系构成。在组件图中,构件时软件单个组成部分,它可以是一个文件,产品、可执行文件和脚本等。9、部署图是用来建模系统的物理部署。例如计算机和设备,以及它们之间是如何连接的。部署图的使用者是开发人员、系统集成人员和测试人员。在需求阶段:采用用例图来描述需求在分析阶段:采用类图来描述静态结构在设计阶段:采用类图、包图对类的接口进行设计在实现阶段:将类用某个面向对象的语言实现在集成与交付阶段:构件图、包图、部署图在测试阶段:单元测试使用类图和类的规格说明书 集成测试阶段使用类图、包图、构件图和合作图 系统测试使用用例图来测试系统功能考试会考用例图,静态图、行为图,实现图。4. 从软件本质的角度,解释软件范围(需求)控制的可行性计算机软件是于操作有关的程序、以及与之相关的文档集合。由于软件本身的复杂性、不可见性、一致性、可变性,软件范围多数情况下对于客户和开发者都是模糊的,这形成软件产品与其他产品不同的开发过程。因此,范围管理是软件项目管理的重中之重。在多数情况下,客户与开发者能就项目的 20% 内容给出严格的需求约定,80% 的内容都是相对模糊的。因此,围绕客户目标,发现并满足客户感兴趣的内容是最关键的。所以对于软件需求,当与客户进行了全面的讨论协商,细化了20%的具体内容和有效需求后就能够满足客户80%的需求,从而有效控制软件需求。二、项目管理实践1. 看板使用练习2. UML绘图工具练习《UML和模式应用》Page9 图1-6 UML的不同透视图_

March 13, 2019 · 1 min · jiezi

coder,你会设计交易系统吗(概念篇)?

文中我们从严谨的角度一步步聊到支付如何演变成独立的系统。内容包括:系统演进过程、接口设计、数据库设计以及代码如何组织的示例。若有不足之处,欢迎讨论共同学习。从模块到服务我记得最开始工作的时候,所有的功能:加购物车/下单/支付 等逻辑都是放在一个项目里。如果一个新的项目需要某个功能,就把这个部分的功能包拷贝到新的项目。数据库也原封不动的拷贝过来,稍微根据需求改改。这就是所谓的 单体应用 时代,随着公司产品线开始多元,每条产品线都需要用到支付服务。如果支付模块调整了代码,那么就会处处改动、处处测试。另一方面公司的交易数据割裂在不同的系统中,无法有效汇总统一分析、管理。这时就到了系统演进的时候,我们把每个产品线的支付模块抽离成统一的服务。对自己公司内部提供统一的API使用,可以对这些API进一步包装成对应的SDK,供内部业务线快速接入。这里服务使用HTTP或者是RPC协议都可以根据公司实际情况决定。不过如果考虑到未来给第三方使用,建议使用HTTP协议,系统的演变过程:总结下,将支付单独抽离成服务后,带来好处如下:避免重复开发,数据隔离的现象出现;支付系统周边功能演进更容易,整个系统更完善丰满。如:对账系统、实时交易数据展示;随时可对外开发,对外输出Paas能力,成为有收入的项目;专门的团队进行维护,系统更有机会演进成顶级系统;公司重要账号信息保存一处,风险更小。系统能力如果我们接手该需求,需要为公司从零搭建支付系统。我们该从哪些方面入手?这样的系统到底需要具备什么样的能力呢?首先支付系统我们可以理解成是一个适配器。他需要把很多第三方的接口进行统一的整合封装后,对内部提供统一的接口,减少内部接入的成本。做为一个最基本的支付系统。需要对内提供如下接口出来:发起支付,我们取名:/gopay发起退款,我们取名:/refund接口异步通知,我们取名:/notify/支付渠道/商户交易号接口同步通知,我们取名:/return/支付渠道/商户交易号交易查询,我们取名:/query/trade退款查询,我们取名:/query/refund账单获取,我们取名:/query/bill结算明细,我们取名:/query/settle一个基础的支付系统,上面8个接口是肯定需要提供的(这里忽略某些支付中的转账、绑卡等接口)。现在我们来基于这些接口看看都有哪些系统会用到。下面按照系统维度,介绍下这些接口如何使用,以及内部的一些逻辑。应用系统一般支付网关会提供两种方式让应用系统接入:网关模式,也就是应用系统自己需要开发一个收银台;(适合提供给第三方)收银台模式,应用系统直接打开支付网关的统一收银台。(内部业务)下面为了讲清楚设计思路,我们按照 网关模式 进行讲解。对于应用系统它需要能够请求支付,也就是调用 gopay 接口。这个接口会处理商户的数据,完成后会调用第三方网关接口,并将返回结果统一处理后返回给应用方。这里需要注意,第三方针对支付接口根据我的经验大致有以下情况:支付时,不需要调用第三方,按照规则生成数据即可;支付时,需要调用第三方多个接口完成逻辑(这可能比较慢,大型活动时需要考虑限流/降配);返回的数据是一个url,可直接跳转到第三方完成支付(wap/pc站);返回的数据是xml/json结构,需要拼装或作为参数传给她的sdk(app)。这里由于第三方返回结构的不统一,我们需要统一处理成统一格式,返回给商户端。我推荐使用json格式。{ “errno”:0, “msg”:“ok”, “data”:{ }}我们把所有的变化封装在 data 结构中。举个例子,如果返回的一个url。只需要应用程序发起 GET 请求。我们可以这样返回:{ “errno”:0, “msg”:“ok”, “data”:{ “url”:“xxxxx”, “method”:“GET” }}如果是返回的结构,需要应用程序直接发起 POST 请求。我们可以这样返回:{ “errno”:1, “msg”:“ok”, “data”:{ “from”:"<form action=“xxx” method=“POST”>xxxxx</form>", “method”:“POST” }}这里的 form 字段,生成了一个form表单,应用程序拿到后可直接显示然后自动提交。当然封装成 from表单这一步也可以放在商户端进行。上面的数据格式仅仅是一个参考。大家可根据自己的需求进行调整。一般应用系统除了会调用发起支付的接口外,可能还需要调用 支付结果查询接口。当然大多数情况下不需要调用,应用系统对交易的状态只应该依赖自己的系统状态。对账系统对于对账,一般分为两个类型:交易对账 与 结算对账交易对账交易对账的核心点是:检查每一笔交易是否正确。它主要目的是看我们系统中的每一笔交易与第三方的每一笔交易是否一致。这个检查逻辑很简单,对两份账单数据进行比较。它主要是使用 /query/bill 接口,拿到在第三方那边完成的交易数据。然后跟我方的交易成功数据进行比较。检查是否存在误差。这个逻辑非常简单,但是有几点需要大家注意:我方的数据需要正常支付数据+重复支付数据的总和;对账检查不成功主要包括:金额不对、第三方没有找到对应的交易数据、我方不存在对应的交易数据。针对这些情况都需要有对应的处理手段进行处理。在我的经验中上面的情况都有过遇到。金额不对:主要是由于第三方的问题,可能是系统升级故障、可能是账单接口金额错误;第三方无交易数据: 可能是拉去的账单时间维度问题(比如存在时差),这种时区问题需要自己跟第三方确认找到对应的时间差。也可能是被攻击,有人冒充第三方异步通知(说明系统校验机制又问题或者密钥泄漏了)。自己系统无交易数据: 这种原因可能是第三方通知未发出或者未正确处理导致的。上面这些问题的处理绝大部份都可以依赖 query/trade query/refund 来完成自动化处理。结算对账那么有了上面的 交易对账 为什么还需要 结算对账 呢?这个系统又是干嘛的?先来看下结算的含义。结算,就是第三方网关在固定时间点,将T+x或其它约定时间的金额,汇款到公司账号。下面我们假设结算周期是: T+1。结算对账主要使用到的接口是 /query/settle,这个接口获取的主要内容是:每一笔结算的款项都是由哪些笔交易组成(交易成功与退款数据)。以及本次结算扣除多少手续费用。它的逻辑其实也很简单。我们先从自己的系统按照 T+1 的结算周期,计算出对方应该汇款给我们多少金额。然后与刚刚接口获取到的数据金额比较:银行收款金额 + 手续费 = 我方系统计算的金额这一步检查通过后,说明金额没有问题。接下来需要检查本次结算下的每一笔订单是否一致。结算系统是 强依赖 对账系统的。如果对账发现异常,那么结算金额肯定会出现异常。另外结算需要注意的一些问题是:银行可能会自行退款给用户,因为用户可直接向自己发卡行申请退款;结算也存在时区差问题;结算接口中的明细交易状态与我方并不完全一致。比如:银行结算时发现某笔退款完成,但我方系统在进行比较时按照未退款完成的逻辑在处理。针对上面的问题,大家根据自己的业务需求需要做一些方案来进行自动化处理。财务系统财务系统有很多内部业务,我这里只聊与支付系统相关的。(当然上面的对账系统也可以算是财务范畴)。财务系统与支付主要的一个关系点在于校验交易、以及退款。这里校验交易可以使用 query/trade query/refund这两个接口来完成。这个逻辑过程就不需要说了。下面重点说下退款。我看到很多的系统退款是直接放在了应用里边,用户申请退款直接就调用退款接口进行退款。这样的风险非常高。支付系统的关于资金流向的接口一定要慎重,不能过多的直接暴露给外部,带来风险。退款的功能应该是放到财务系统来做。这样可以走内部的审批流程(是否需要根据业务来),并且在财务系统中可以进行更多检查来觉得是否立即进行退款,或者进入等待、拒绝等流程。第三方网关针对第三方主要使用到的其实就是异步通知与同步通知两个接口。这一部分的逻辑其实非常简单。就是根据第三方的通知完成交易状态的变更。以及通知到自己对应的应用系统。这部分比较复杂的是,第三方的通知数据结构不统一、通知的类型不统一。比如:有的退款是同步返回结果、有的是异步返回结果。这里如何设计会在后面的 系统设计 中给出答案。第一部份的内容就到此结束了。如果有什么疑问欢迎到我们GitHub主页留言。GitHub: https://github.com/skr-shop

March 11, 2019 · 1 min · jiezi

消息通知系统模型设计

本篇主要明确消息通知系统的概念和具体实现,包括数据库设计、技术方案、逻辑关系分析等。消息通知系统是一个比较复杂的系统,这里主要分析站内消息如何设计和实现。我们常见的消息推送渠道有以下几种:设备推送站内推送短信推送邮箱推送我们常见的站内通知有以下几种类别:公告 Announcement提醒 Remind资源订阅提醒「我关注的资源有更新、评论等事件时通知我」资源发布提醒「我发布的资源有评论、收藏等事件时通知我」系统提醒「平台会根据一些算法、规则等可能会对你的资源做一些事情,这时你会收到系统通知」私信 Mailbox以上三种消息有各自特点,实现也各不相同,其中「提醒」类通知是最复杂的,下面会详细讲。数据模型设计公告公告是指平台发送一条含有具体内容的消息,站内所有用户都能收到这条消息。方案一:【适合活跃用户在5万左右】 公告表「notify_announce」 表结构如下:id: {type: ‘integer’, primaryKey: true, autoIncrement:true} //公告编号;senderID: {type: ‘string’, required: true} //发送者编号,通常为系统管理员;title: {type: ‘string’, required: true} //公告标题;content: {type: ’text’, required: true} //公告内容;createdAt: {type: ’timestamp’, required: true} //发送时间;用户公告表「notify_announce_user」 表结构如下:id: {type: ‘integer’, primaryKey: true, autoIncrement:true} //用户公告编号;announceID: {type: ‘integer’} //公告编号;recipientID: {type: ‘string’, required: true} //接收用户编号;createdAt:{type: ’timestamp’, required: true} //拉取公告时间;state: {type: ‘integer’, required: true} //状态,已读|未读;readAt:{type: ’timestamp’, required: true} //阅读时间;平台发布一则公告之后,当用户登录的时候去拉取站内公告并插入notify_announce_user表,这样那些很久都没登陆的用户就没必要插入了。「首次拉取,根据用户的注册时间;否则根据notify_announce_user.createdAt即上一次拉取的时间节点获取公告」方案二:【适合活跃用户在百万-千万左右】 和方案一雷同,只是需要把notify_announce_user表进行哈希分表,需事先生成表:notify_announce_<hash(uid)>。 用户公告表「notify_announce_<hash(uid)>」 表结构如下:id: {type: ‘integer’, primaryKey: true, autoIncrement:true} //用户公告编号;announceID: {type: ‘integer’} //公告编号;recipientID: {type: ‘string’, required: true} //接收用户编号;createdAt:{type: ’timestamp’, required: true} //拉取公告时间;state: {type: ‘integer’, required: true} //状态,已读|未读;readAt:{type: ’timestamp’, required: true} //阅读时间;提醒提醒是指「我的资源」或「我关注的资源」有新的动态产生时通知我。提醒的内容无非就是: 「someone do something in someone’s something」 「谁对一样属于谁的事物做了什么操作」 常见的提醒消息例子,如: XXX 关注了你 - 「这则属于资源发布提醒」 XXX 喜欢了你的文章 《消息通知系统模型设计》 - 「这则属于资源发布提醒」 你喜欢的文章《消息通知系统模型设计》有新的评论 - 「这则属于资源订阅提醒」 你的文章《消息通知系统模型设计》已被加入专题 《系统设计》 - 「这则属于系统提醒」 小明赞同了你的回答 XXXXXXXXX -「这则属于资源发布提醒」最后一个例子中包含了消息的生产者(小明),消息记录的行为(赞同),行为的对象(你的回答内容)分析提醒类消息的句子结构: someone = 动作发起者,标记为「sender」 do something = 对资源的操作,如:评论、喜欢、关注都属于一个动作,标记为「action」 something = 被作用对象,如:一篇文章,文章的评论等,标记为「object」 someone’s = 动作的目标对象或目标资源的所有者,标记为「objectOwner」总结:sender 和 objectOwner 就是网站的用户,object 就是网站资源,可能是一篇文章,一条文章的评论等等。action 就是动作,可以是赞、评论、收藏、关注、捐款等等。提醒设置提醒通常是可以在「设置-通知」里自定义配置的,用户可以选择性地「订阅」接收和不接收某类通知。呈现在界面上是这样的:通知设置我发布的 publish 文章 被 评论 是/否 通知我 被 收藏 是/否 通知我 被 点赞 是/否 通知我 被 喜欢 是/否 通知我 被 捐款 是/否 通知我我订阅的 follow 文章 有 更新 是/否 通知我 被 评论 是/否 通知我订阅一般系统默认是订阅了所有通知的。系统在给用户推送消息的时候必须查询通知「订阅」模块,以获取某一事件提醒消息应该推送到哪些用户。也就是说「事件」和「用户」之间有一个订阅关系。那么接下来我们分析下「订阅」有哪些关键元素: 比如我发布了一篇文章,那么我会订阅文章《XXX》的评论动作,所以文章《XXX》每被人评论了,就需要发送一则提醒告知我。分析得出以下关键元素:订阅者「subscriber」订阅的对象「object」订阅的动作「action」订阅对象和订阅者的关系「objectRelationship」什么是订阅的目标关系呢? 拿知乎来说,比如我喜欢了一篇文章,我希望我订阅这篇文章的更新、评论动作。那这篇文章和我什么关系?不是所属关系,只是喜欢。objectRelationship = 我发布的,对应着 actions = [评论,收藏]objectRelationship = 我喜欢的,对应着 actions = [更新,评论]讲了那么多,现在来构建「提醒」的数据结构该吧! 提醒表「notify_remind」 表结构如下:id: {type: ‘integer’, primaryKey: true, autoIncrement:true} //主键;remindID: {type: ‘string’, required: true} //通知提醒编号;senderID: {type: ‘string’, required: true} //操作者的ID,三个0代表是系统发送的;senderName: {type: ‘string’, required: true} //操作者用户名;senderAction: {type: ‘string’, required: true} //操作者的动作,如:赞了、评论了、喜欢了、捐款了、收藏了;objectID: {type: ‘string’, required: true}, //目标对象ID;object: {type: ‘string’, required: false}, //目标对象内容或简介,比如:文章标题;objectType: {type: ‘string’, required: true} //被操作对象类型,如:人、文章、活动、视频等;recipientID: {type: ‘string’} //消息接收者;可能是对象的所有者或订阅者;message: {type: ’text’, required: true} //消息内容,由提醒模版生成,需要提前定义;createdAt:{type: ’timestamp’, required: true} //创建时间;status:{type: ‘integer’, required: false} //是否阅读,默认未读;readAt:{type: ’timestamp’, required: false} //阅读时间;假如:特朗普关注了金正恩,以下字段的值是这样的senderID = 特朗普的IDsenderName = 特朗普senderAction = 关注objectID = 金正恩的IDobject = 金正恩objectType = 人recipientID = 金正恩的IDmessage = 特朗普关注了金正恩这种情况objectID 和 recipientID是一样的。这里需要特别说下消息模版,模版由「对象」、「动作」和「对象关系」构成唯一性。通知提醒订阅表「notify_remind_subscribe」 表结构如下:id: {type: ‘integer’, primaryKey: true, autoIncrement:true} //订阅ID;userID: {type: ‘string’, required: true},//用户ID,对应 notify_remind 中的 recipientID;objectType: {type: ‘string’, required: true} //资源对象类型,如:文章、评论、视频、活动、用户;action: {type: ‘string’, required: true} //资源订阅动作,多个动作逗号分隔如: comment,like,post,update etc.objectRelationship: {type: ‘string’, required: true} //用户与资源的关系,用户发布的published,用户关注的followed;createdAt:{type: ’timestamp’, required: true} //创建时间;特别说下「objectRelationship」字段的作用,这个字段用来区分「消息模版」,为什么呢?因为同一个「资源对象」和「动作」会有两类订阅者,一类是该资源的Owner,另一类是该资源的Subscriber,这两类人收到的通知消息内容应该是不一样的。聚合假如我在抖音上发布了一个短视频,在我不在线的时候,被评论了1000遍,当我一上线的时候,应该是收到一千条消息,类似于:「* 评论了你的文章《XXX》」? 还是应该收到一条信息:「有1000个人评论了你的文章《XXX》」? 当然是后者更好些,要尽可能少的骚扰用户。消息推送是不是感觉有点晕了,还是先上一张消息通知的推送流程图吧: 订阅表一共有两张噢,一张是「通知订阅表」、另一张是用户对资源的「对象订阅表」。 具体实现就不多讲了,配合这张图,理解上面讲的应该不会有问题了。私信通常私信有这么几种需求:点到点:用户发给用户的站内信,系统发给用户的站内信。「1:1」点到多:系统发给多个用户的站内信,接收对象较少,而且接收对象无特殊共性。「1:N」点到面:系统发给用户组的站内信,接收对象同属于某用户组之类的共同属性。「1:N」点到全部:系统发给全站用户的站内信,接收对象为全部用户,通常为系统通知。「1:N」这里主要讲「点到点」的站内信。私信表「notify_mailbox」表结构如下:id: {type: ‘integer’, primaryKey: true, autoIncrement:true} //编号;dialogueID: {type: ‘string’, required: true} //对话编号; senderID: {type: ‘string’, required: true} //发送者编号;recipientID: {type: ‘string’, required: true} //接收者编号;messageID: {type: ‘integer’, required: true} //私信内容ID;createdAt:{type: ’timestamp’, required: true} //发送时间;state: {type: ‘integer’, required: true} //状态,已读|未读;readAt:{type: ’timestamp’, required: true} //阅读时间;Inbox私信列表select * from notify_inbox where recipientID=“uid” order by createdAt desc对话列表select * from notify_inbox where dialogueID=“XXXXXXXXXXXX” and (recipientID=“uid” or senderID=“uid”) order by createdAt asc私信回复时,回复的是dialogueIDOutbox私信列表select * from notify_inbox where senderID=“uid” order by createdAt desc对话列表select * from notify_inbox where dialogueID=“XXXXXXXXXXXX” and (senderID=“uid” or recipientID=“uid”) order by createdAt asc私信内容表「notify_inbox_message」表结构如下:id: {type: ‘integer’, primaryKey: true, autoIncrement:true} //编号;senderID: {type: ‘string’, required: true} //发送者编号;content: {type: ‘string’, required: true} //私信内容; createdAt:{type: ’timestamp’, required: true}参考消息系统设计与实现 通知系统设计 ...

February 21, 2019 · 2 min · jiezi

设计一个短网址系统

提出问题如何设计一个像百度短网址一样的服务,一个生成短网址、将短网址定向到原始URL的服务。解决方案首先,需要一个一对一的映射表,去获取短URL,然后根据他恢复出完整的URL。这将会涉及到将这些数据保存到数据库。我们要考虑下面这些问题:短网址转换时候的长度是多少?映射函数是什么?提供服务的是单机还是集群?转换长度我们假设我们设计的系统可以提供超过10000亿的URL,如果我们以从62个字符[a-z,A-Z,0-9]中选取n个作为短网址,我们可以存储62^n个网址。所以,在满足条件的情况下,我们选择最小的n。对于我们的需求来说,我们取n=7,这样可以提供短网址的个数是62^7 ~= 35000亿。一般方案一切从简,我们假定短网址类似http://dwz.com/<alias_hash>,其中alias_hash是一个计算过得字符串。最开始呢,我们把所有的映射存到一个单机数据库,直接生成一个随机的长度为7的字符串alias_hash作为映射中的键ID。所以,我们只需要存储<ID, URL>。当使用者输入完整网址http://www.google.com,系统生成一个像abcd123一样的长度为7的随机字符串作为ID,存储到数据库中就是<abcd123, http://www.google.com>。使用的时候,访问http://dwz.com/abcd123,系统搜索IDabcd123,找到后重定向到http://www.google.com。此方案存在的问题我们不能保证通过长URL生成alias_hash的唯一性。生成alias_hash的时候可能会发生冲突(2个长URL映射到同一个短URL),但我们需要每个长URL生成的短URL都是唯一的,这样我们才能根据短URL定位到唯一的长URL,但是计算alias_hash的函数是单向函数。这里是不是单向函数其实不重要,我们不需要根据短网址再次做计算,只需要搜索,不知道作者为什么这么写。改进方案一个很简单但依然高效的方案,建立这样的数据表:Table Tiny_Url( ID : int PRIMARY_KEY AUTO_INC, Original_url : varchar, Short_url : varchar)其中自增主键ID用来做这样的转换:(ID, 10) <==> (short_url, BASE)。使用者随时插入一个长URL,会返回最新的插入ID,把他转化为短URL标识,保存这个短URL标识,并返回给使用者。转换方法源码(用于ID和短URL的互相转换)string idToShortURL(long int n){ // Map to store 62 possible characters char map[] = “abcdefghijklmnopqrstuvwxyzABCDEF” “GHIJKLMNOPQRSTUVWXYZ0123456789”; string shorturl; // Convert given integer id to a base 62 number while (n) { shorturl.push_back(map[n%62]); n = n/62; } // Reverse shortURL to complete base conversion reverse(shorturl.begin(), shorturl.end()); return shorturl;} // Function to get integer ID back from a short urllong int shortURLtoID(string shortURL){ long int id = 0; // initialize result // A simple base conversion logic for (int i=0; i < shortURL.length(); i++) { if (‘a’ <= shortURL[i] && shortURL[i] <= ‘z’) id = id62 + shortURL[i] - ‘a’; if (‘A’ <= shortURL[i] && shortURL[i] <= ‘Z’) id = id62 + shortURL[i] - ‘A’ + 26; if (‘0’ <= shortURL[i] && shortURL[i] <= ‘9’) id = id*62 + shortURL[i] - ‘0’ + 52; } return id;}服务器集群如果我们的服务要处理大量数据,分布式存储可以提高我们的吞吐量。思路也很简单,计算出原始URL的hash值,然后去对应的及其存储,然后就和单机情况一样了。通常使用一致性hash路由到集群中对应的节点。下面的例子是伪代码:获取短URL将原始URL哈希为2为数字,假设为hash_val。用hash_val定位一致性hash环上的机器。像对应的数据库中插入原始URL,通过idToShortURL()获取短URL合并hash_val和短URL作为我们最终短URL(长度=8),返回给使用者。根据短URL获取原始URL从最终短URL中获取前两个字符作为机器标识hash_val。通过hash_val定位到机器。通过最终短URL中的剩余6个字符作为短URL,去数据库获取对应的记录。返回原始URL给使用者。这里可能有问题,坐着的意思可能是最多62台机器,应该是第一个字符是标识机器的,值可能是[a-z,A-Z,0-9]中的一个。看了评论,这里有个问题,如果某台机器挂了,摘掉之后,需要将该机器上的数据复制到一致性hash环上的下一台机器的时候,会发生冲突,所以还是使用redis之类的生成全局唯一的ID,直接落到环上对应的机器,这样设计更好。其他考虑这里我想深入讨论的是使用GUID(全局唯一标识)作为ID,与增量ID相比有哪些优缺点?如果你深入insert/query处理语句的话,你会发现使用随机字符串作为ID可能会牺牲一小部分性能。特别是当已经有无数的记录的时候,插入是很昂贵的操作,数据库需要寻找合适的页存储插入的ID。但是,使用增量的ID,插入会简单很多,直接找到最后的页就行了。 ...

January 20, 2019 · 1 min · jiezi

财务系统设计【序】

新年伊始,组织团队小伙伴进行了一次头脑风暴,畅想了下财务系统2019的愿景,自己也思考颇多,决定针对【财务系统设计】做个专栏,落笔为记!一、一次头脑风暴头脑风暴前,除了准备泡面,花生,矿泉水,还列了几个比较现实的问题公司:你做的事情,值多少钱?公司凭什么给你升职,涨薪?财务系统到底还能给公司带来多少收益?自己:为什么要留下来?干一年财务系统,到底能给每个人带来多大成长?目标:下次跳槽时,你希望自己成长成什么样子?落地:规划每个人的模块方向,如何把自身成长需求和业务成长绑票?与我个人而言,期望通过这次头脑风暴,让团队小伙伴们能够对自己的模块有个规划,能够在业务成长的过程中实现个人技能成长,能够通过促进模块收益来提升自己薪资职级!无他,也希望各位看官能够思考一下二、财务系统设计专栏头脑风暴后,小拽也一直在思考,2018年干了一年财务系统了,2019年如何搞?具体的需求拆解,模块设计,架构图,暂时先不祭出来了,毕竟还需要深入的拆解和剖析!但结合年初的flag,小拽决定2019年完善【财务系统设计】专栏^^,期望能够通过专栏,自己能够体系化的梳理下财务系统,抽象出更通用的解决方案!废话不提先列下2018年亏欠的文章和目录,今年一定补上^^!热点账户问题思考和常用解决方案数据最终一致性保证幂等健设计原则全局ID生成思考和解决方案财务系统异步和同步的思考国际化账务系统思考财务数仓有哪些坑?通用账单分级模型设计账户模型设计和思考账户流水设计和思考三、专栏目录长远的看,小拽的财务模块设计最终会把所有文章落到各个模块中,暂时先梳理了下目录!财务系统专栏├── 在线系统设计和实现│ ├── 分账模块│ ├── 提现模块│ ├── 收银模块│ ├── 结算模块│ ├── 记账模块│ └── 账户模块├── 支撑系统设计和实现│ ├── MIS系统│ ├── openAPI│ ├── 任务系统│ ├── 财务网关│ ├── 数据质量中心│ └── 监控预警系统├── 数据中心设计和实现│ ├── ARCHIVE│ ├── GraphDB│ ├── HBASE│ ├── HIVE│ ├── KV│ ├── RDS│ └── TSDB└── 离线系统设计和实现 ├── 对账引擎 ├── 经营分析 ├── 结算引擎 ├── 财务报表 ├── 资金安全 └── 预算引擎【转载请注明:财务系统设计【序】 | 靠谱崔小拽 】

January 6, 2019 · 1 min · jiezi

深入浅出排序学习:写给程序员的算法系统开发实践

引言我们正处在一个知识爆炸的时代,伴随着信息量的剧增和人工智能的蓬勃发展,互联网公司越发具有强烈的个性化、智能化信息展示的需求。而信息展示个性化的典型应用主要包括搜索列表、推荐列表、广告展示等等。很多人不知道的是,看似简单的个性化信息展示背后,涉及大量的数据、算法以及工程架构技术,这些足以让大部分互联网公司望而却步。究其根本原因,个性化信息展示背后的技术是排序学习问题(Learning to Rank)。显然,市面上大部分关于排序学习的文章,要么偏算法、要么偏工程。虽然算法方面有一些系统性的介绍文章,但往往对读者的数学能力要求比较高,也偏学术论文,对于非算法同学来说门槛非常高。而大部分工程方面的文章也比较粗糙,基本上停留在Google的Two-Phase Scheme阶段,从工程实施的角度来说,远远还不够具体。对于那些由系统开发工程师负责在线排序架构的团队来说,本文会采用通俗的例子和类比方式来阐述算法部分,希望能够帮助大家更好地理解和掌握排序学习的核心概念。如果是算法工程师团队的同学,可以忽略算法部分的内容。本文的架构部分阐述了美团点评到店餐饮业务线上运行的系统,可以作为在线排序系统架构设计的参考原型直接使用。该架构在服务治理、分层设计的理念,对于保障在线排序架构的高性能、高可用性、易维护性也具有一定的参考价值。包括很多具体环节的实施方案也可以直接进行借鉴,例如流量分桶、流量分级、特征模型、级联模型等等。总之,让开发工程师能够理解排序学习算法方面的核心概念,并为在线架构实施提供细颗粒度的参考架构,是本文的重要目标。算法部分机器学习涉及优化理论、统计学、数值计算等多个领域。这给那些希望学习机器学习核心概念的系统开发工程师带来了很大的障碍。不过,复杂的概念背后往往蕴藏着朴素的道理。本节将尝试采用通俗的例子和类比方式,来对机器学习和排序学习的一些核心概念进行揭秘。机器学习什么是机器学习?典型的机器学习问题,如下图所示:机器学习模型或算法(Model/Algorithm)会根据观察到的特征值(Feature)进行预测,给出预测结果或者目标(Prediction/Target)。这就像是一个函数计算过程,对于特定X值(Feature),算法模型就像是函数,最终的预测结果是Y值。不难理解,机器学习的核心问题就是如何得到预测函数。Wikipedia的对机器学习定义如下:“Machine learning is a subset of artificial intelligence in the field of computer science that often uses statistical techniques to give computers the ability to learn with data, without being explicitly programmed.” 机器学习的最重要本质是从数据中学习,得到预测函数。人类的思考过程以及判断能力本质上也是一种函数处理。从数据或者经验中学习,对于人类来说是一件再平常不过的事情了。例如人们通过观察太阳照射物体影子的长短而发明了日晷,从而具备了计时和制定节气的能力。古埃及人通过尼罗河水的涨落发明了古埃及历法。又比如人们通过观察月亮形状的变化而发明了阴历。如果机器能够像人一样具备从数据中学习的能力,从某种意义上讲,就具备了一定的“智能”。现在需要回答的两个问题就是:到底什么是“智能”?如何让机器具备智能?什么是智能?在回答这个问题之前,我们先看看传统的编程模式为什么不能称之为“智能”。传统的编程模式如下图所示,它一般要经历如下几个阶段:人类通过观察数据(Data)总结经验,转化成知识(Knowledge)。人类将知识转化成规则(Rule)。工程师将规则转化成计算机程序(Program)。在这种编程模式下,如果一个问题被规则覆盖,那么计算机程序就能处理。对于规则不能覆盖的问题,只能由人类来重新思考,制定新规则来解决。所以在这里“智能”角色主要由人类来承担。人类负责解决新问题,所以传统的程序本身不能称之为“智能”。所以,“智能”的一个核心要素就是“举一反三”。如何让机器具备智能?在讨论这个问题之前,可以先回顾一下人类是怎么掌握“举一反三”的能力的?基本流程如下:老师给学生一些题目,指导学生如何解题。学生努力掌握解题思路,尽可能让自己的答案和老师给出的答案一致。学生需要通过一些考试来证明自己具备“举一反三”的能力。如果通过了这些考试,学生将获得毕业证书或者资格从业证书。学生变成一个从业者之后将会面临并且处理很多之前没有碰到过的新问题。机器学习专家从人类的学习过程中获得灵感,通过三个阶段让机器具备“举一反三”的能力。这三个阶段是:训练阶段(Training)、测试阶段(Testing)、推导阶段(Inference)。下面逐一进行介绍:训练阶段训练阶段如下图所示:人类给机器学习模型一些训练样本(X,Y),X代表特征,Y代表目标值。这就好比老师教学生解题,X代表题目,Y代表标准答案。机器学习模型尝试想出一种方法解题。在训练阶段,机器学习的目标就是让损失函数值最小。类比学生尝试让自己解题的答案和老师给的标准答案差别最小,思路如出一辙。测试阶段测试阶段如下图所示:人类给训练好的模型一批完全不同的测试样本(X,Y)。这就好比学生拿到考试试卷。模型进行推导。这个过程就像学生正在考试答题。人类要求测试样本的总损失函数值低于设定的最低目标值。这就像学校要求学生的考试成绩必须及格一样。推导阶段推导阶段如下图所示:在这个阶段机器学习模型只能拿到特征值X,而没有目标值。这就像工作中,人们只是在解决一个个的问题,但不知道正确的结果到底是什么。在推导阶段,机器学习的目标就是预测,给出目标值。排序学习什么是排序学习?Wikipedia的对排序学习的定义如下:“Learning to rank is the application of machine learning, typically supervised, semi-supervised or reinforcement learning, in the construction of ranking models for information retrieval systems. Training data consists of lists of items with some partial order specified between items in each list. This order is typically induced by giving a numerical or ordinal score or a binary judgment (e.g. “relevant” or “not relevant”) for each item. The ranking model’s purpose is to rank, i.e. produce a permutation of items in new, unseen lists in a way which is “similar” to rankings in the training data in some sense.”排序学习是机器学习在信息检索系统里的应用,其目标是构建一个排序模型用于对列表进行排序。排序学习的典型应用包括搜索列表、推荐列表和广告列表等等。列表排序的目标是对多个条目进行排序,这就意味着它的目标值是有结构的。与单值回归和单值分类相比,结构化目标要求解决两个被广泛提起的概念:列表评价指标列表训练算法列表评价指标以关键词搜索返回文章列表为例,这里先分析一下列表评价指标要解决什么挑战。第一个挑战就是定义文章与关键词之间的相关度,这决定了一篇文章在列表中的位置,相关度越高排序就应该越靠前。第二个挑战是当列表中某些文章没有排在正确的位置时候,如何给整个列表打分。举个例子来说,假如对于某个关键词,按照相关性的高低正确排序,文档1、2、3、4、5应该依次排在前5位。现在的挑战就是,如何评估“2,1,3,4,5”和“1,2,5,4,3”这两个列表的优劣呢?列表排序的评价指标体系总来的来说经历了三个阶段,分别是Precision and Recall、Discounted Cumulative Gain(DCG)和Expected Reciprocal Rank(ERR)。我们逐一进行讲解。Precision and Recall(P-R)本评价体系通过准确率(Precision)和召回率(Recall)两个指标来共同衡量列表的排序质量。对于一个请求关键词,所有的文档被标记为相关和不相关两种。Precision的定义如下:Recall的定义如下:举个列子来说,对于某个请求关键词,有200篇文章实际相关。某个排序算法只认为100篇文章是相关的,而这100篇文章里面,真正相关的文章只有80篇。按照以上定义:准确率=80/100=0.8召回率=80/200=0.4。Discounted Cumulative Gain(DCG)P-R的有两个明显缺点:所有文章只被分为相关和不相关两档,分类显然太粗糙。没有考虑位置因素。DCG解决了这两个问题。对于一个关键词,所有的文档可以分为多个相关性级别,这里以rel1,rel2…来表示。文章相关性对整个列表评价指标的贡献随着位置的增加而对数衰减,位置越靠后,衰减越严重。基于DCG评价指标,列表前p个文档的评价指标定义如下:对于排序引擎而言,不同请求的结果列表长度往往不相同。当比较不同排序引擎的综合排序性能时,不同长度请求之间的DCG指标的可比性不高。目前在工业界常用的是Normalized DCG(nDCG),它假定能够获取到某个请求的前p个位置的完美排序列表,这个完美列表的分值称为Ideal DCG(IDCG),nDCG等于DCG与IDCG比值。所以nDCG是一个在0到1之间的值。nDCG的定义如下:IDCG的定义如下:|REL|代表按照相关性排序好的最多到位置p的结果列表。Expected Reciprocal Rank(ERR)与DCG相比,除了考虑位置衰减和允许多种相关级别(以R1,R2,R3…来表示)以外,ERR更进了一步,还考虑了排在文档之前所有文档的相关性。举个例子来说,文档A非常相关,排在第5位。如果排在前面的4个文档相关度都不高,那么文档A对列表的贡献就很大。反过来,如果前面4个文档相关度很大,已经完全解决了用户的搜索需求,用户根本就不会点击第5个位置的文档,那么文档A对列表的贡献就不大。ERR的定义如下:列表训练算法做列表排序的工程师们经常听到诸如Pointwise、Pairwise和Listwise的概念。这些是什么东西呢,背后的原理又是什么呢?这里将逐一解密。仍然以关键词搜索文章为例,排序学习算法的目标是为给定的关键词对文章列表进行排序。做为类比,假定有一个学者想要对各科学生排名进行预测。各种角色的对应关系如下:首先我们要告诉学者每个学生的各种属性,这就像我们要告诉排序算法文档特征。对于目标值,我们却有三种方式来告诉学者:对于每个学科,我们可以告诉学者每个学生的成绩。比较每个学生的成绩,学者当然可以算出每个学生的最终排名。这种训练方法被称为Pointwise。对于Pointwise算法,如果最终预测目标是一个实数值,就是一个回归问题。如果目标是概率预测,这就是一个分类问题,例如CTR预估。对于每个学科,我们可以告诉学者任意两个学生的相互排名。根据学生之间排名的情况,学者也可以算出每个学生的最终排名。这种训练方法被称为Pairwise。Pairwise算法的目标是减少逆序的数量,所以是个二分类问题。对于每个学科,我们可以直接告诉学者所有学生的整体排名。这种训练方法被称为Listwise。Listwise算法的目标往往是直接优化nDCG、ERR等评价指标。这三种方法表面看上去有点像玩文字游戏,但是背后却是工程师和科学家们不断探索的结果。最直观的方案是Pointwise算法,例如对于广告CTR预估,在训练阶段需要标注某个文档的点击概率,这相对来说容易。Pairwise算法一个重要分支是Lambda系列,包括LambdaRank、LambdaMart等,它的核心思想是:很多时候我们很难直接计算损失函数的值,但却很容易计算损失函数梯度(Gradient)。这意味着我们很难计算整个列表的nDCG和ERR等指标,但却很容易知道某个文档应该排的更靠前还是靠后。Listwise算法往往效果最好,但是如何为每个请求对所有文档进行标注是一个巨大的挑战。在线排序架构典型的信息检索包含两个阶段:索引阶段和查询阶段。这两个阶段的流程以及相互关系可以用下图来表示:索引阶段的工作是由索引器(Indexer)读取文档(Documents)构建索引(Index)。查询阶段读取索引做为召回,然后交给Topn Retriever进行粗排,在粗排后的结果里面将前n个文档传给Reranker进行精排。这样一个召回、粗排、精排的架构最初是由Google提出来的,也被称为“Two-Phase Scheme”。索引部分属于离线阶段,这里重点讲述在线排序阶段,即查询阶段。三大挑战在线排序架构主要面临三方面的挑战:特征、模型和召回。特征挑战包括特征添加、特征算子、特征归一化、特征离散化、特征获取、特征服务治理等。模型挑战包括基础模型完备性、级联模型、复合目标、A/B实验支持、模型热加载等。召回挑战包括关键词召回、LBS召回、推荐召回、粗排召回等。三大挑战内部包含了非常多更细粒度的挑战,孤立地解决每个挑战显然不是好思路。在线排序作为一个被广泛使用的架构值得采用领域模型进行统一解决。Domain-driven design(DDD)的三个原则分别是:领域聚焦、边界清晰、持续集成。基于以上分析,我们构建了三个在线排序领域模型:召回治理、特征服务治理和在线排序分层模型。召回治理经典的Two-Phase Scheme架构如下图所示,查询阶段应该包含:召回、粗排和精排。但从领域架构设计的角度来讲,粗排对于精排而言也是一种召回。和基于传统的文本搜索不同,美团点评这样的O2O公司需要考虑地理位置和距离等因素,所以基于LBS的召回也是一种召回。与搜索不同,推荐召回往往基于协同过滤来完成的,例如User-Based CF和Item-Based CF。综上所述,召回总体而言分成四大类:关键词召回,我们采用Elasticsearch解决方案。距离召回,我们采用K-D tree的解决方案。粗排召回。推荐类召回。特征服务治理传统的视角认为特征服务应该分为用户特征(User)、查询特征(Query)和文档特征(Doc),如下图:这是比较纯粹的业务视角,并不满足DDD的领域架构设计思路。由于特征数量巨大,我们没有办法为每个特征设计一套方案,但是我们可以把特征进行归类,为几类特征分别设计解决方案。每类技术方案需要统一考虑性能、可用性、存储等因素。从领域视角,特征服务包含四大类:列表类特征。一次请求要求返回实体列表特征,即多个实体,每个实体多个特征。这种特征建议采用内存列表服务,一次性返回所有请求实体的所有特征。避免多次请求,从而导致数量暴增,造成系统雪崩。实体特征。一次请求返回单实体的多个特征。建议采用Redis、Tair等支持多级的Key-Value服务中间键提供服务。上下文特征。包括召回静态分、城市、Query特征等。这些特征直接放在请求内存里面。相似性特征。有些特征是通过计算个体和列表、列表和列表的相似度而得到的。建议提供单独的内存计算服务,避免这类特征的计算影响在线排序性能。本质上这是一种计算转移的设计。在线排序分层模型如下图所示,典型的排序流程包含六个步骤:场景分发(Scene Dispatch)、流量分配(Traffic Distribution)、召回(Recall)、特征抽取(Feature Retrieval)、预测(Prediction)、排序(Ranking)等等。按照DDD的设计原则,我们设计了如下在线排序分层模型,包括:场景分发(Scene Dispatch)、模型分发(Model Distribution)、排序(Ranking)、特征管道(Feature Pipeline)、预测管道(Prediction Pipeline)。我们将逐一进行介绍。场景分发(Scene Dispatch)场景分发一般是指业务类型的分发。对于美团点评而言包括:分平台、分列表、分使用场景等。如下图所示:模型分发(Model Distribution)模型分发的目标是把在线流量分配给不同的实验模型,具体而言要实现三个功能:为模型迭代提供在线流量,负责线上效果收集、验证等。A/B测试,确保不同模型之间流量的稳定、独立和互斥、确保效果归属唯一。确保与其他层的实验流量的正交性。流量的定义是模型分发的一个基础问题。典型的流量包括:访问、用户和设备。如何让一个流量稳定地映射到特定模型上面,流量之间是否有级别?这些是模型分发需要重点解决的问题。流量分桶原理采用如下步骤将流量分配到具体模型上面去:把所有流量分成N个桶。每个具体的流量Hash到某个桶里面去。给每个模型一定的配额,也就是每个策略模型占据对应比例的流量桶。所有策略模型流量配额总和为100%。当流量和模型落到同一个桶的时候,该模型拥有该流量。举个例子来说,如上图所示,所有流量分为32个桶,A、B、C三个模型分别拥有37.5%、25%和37.5%的配额。对应的,A、B、C应该占据12、8和12个桶。为了确保模型和流量的正交性,模型和流量的Hash Key采用不同的前缀。流量分级每个团队的模型分级策略并不相同,这里只给出一个建议模型流量分级:基线流量。本流量用于与其他流量进行对比,以确定新模型的效果是否高于基准线,低于基准线的模型要快速下线。另外,主力流量相对基线流量的效果提升也是衡量算法团队贡献的重要指标。实验流量。该流量主要用于新实验模型。该流量大小设计要注意两点:第一不能太大而伤害线上效果;第二不能太小,流量太小会导致方差太大,不利于做正确的效果判断。潜力流量。如果实验流量在一定周期内效果比较好,可以升级到潜力流量。潜力流量主要是要解决实验流量方差大带来的问题。主力流量。主力流量只有一个,即稳定运行效果最好的流量。如果某个潜力流量长期好于其他潜力流量和主力流量,就可以考虑把这个潜力流量升级为主力流量。做实验的过程中,需要避免新实验流量对老模型流量的冲击。流量群体对于新模型会有一定的适应期,而适应期相对于稳定期的效果一般会差一点。如果因为新实验的上线而导致整个流量群体的模型都更改了,从统计学的角度讲,模型之间的对比关系没有变化。但这可能会影响整个大盘的效果,成本很高。为了解决这个问题,我们的流量分桶模型优先为模型列表前面的模型分配流量,实验模型尽量放在列表尾端。这样实验模型的频繁上下线不影响主力和潜力流量的用户群体。当然当发生模型流量升级的时候,很多流量用户的服务模型都会更改。这种情况并不是问题,因为一方面我们在尝试让更多用户使用更好的模型,另一方面固定让一部分用户长期使用实验流量也是不公平的事情。排序(Ranking)排序模块是特征模块和预测模块的容器,它的主要职责如下:获取所有列表实体进行预测所需特征。将特征交给预测模块进行预测。对所有列表实体按照预测值进行排序。特征管道(Feature Pipeline)特征管道包含特征模型(Feature Model)、表达式(Expression)、原子特征(Atomic Feature)、 特征服务代理(Feature Proxy)、特征服务(Feature Service)。如下图所示:特征管道要解决两个核心问题:给定特征名获取对应特征值。这个过程很复杂,要完成特征名->特征服务->特征类->特征值的转化过程。特征算子问题。模型使用的特征往往是对多个原子特征进行复合运算后的结果。另外,特征离散化、归一化也是特征算子需要解决的问题。完整的特征获取流程如下图所示,具体的流程如下:Ranking模块从FeatureModel里面读取所有的原始特征名。Ranking将所有的原始特征名交给Feature Proxy。Feature Proxy根据特征名的标识去调用对应的Feature Service,并将原始特征值返回给Ranking模块。Ranking模块通过Expression将原始特征转化成复合特征。Ranking模块将所有特征交给级联模型做进一步的转换。特征模型(Feature Model)我们把所有与特征获取和特征算子的相关信息都放在一个类里面,这个类就是FeatureModel,定义如下: //包含特征获取和特征算子计算所需的meta信息 public class FeatureModel { //这是真正用在Prediction里面的特征名 private String featureName; //通过表达式将多种原子特征组合成复合特征。 private IExpression expression; //这些特征名是真正交给特征服务代理(Feature Proxy)去从服务端获取特征值的特征名集合。 private Set<String> originalFeatureNames; //用于指示特征是否需要被级联模型转换 private boolean isTransformedFeature; //是否为one-hot特征 private boolean isOneHotIdFeature; //不同one-hot特征之间往往共享相同的原始特征,这个变量>用于标识原始特征名。 private String oneHotIdKey; //表明本特征是否需要归一化 private boolean isNormalized; }表达式(Expression)表达式的目的是为了将多种原始特征转换成一个新特征,或者对单个原始特征进行运算符转换。我们采用前缀法表达式(Polish Notation)来表示特征算子运算。例如表达式(5-6)7的前缀表达式为 - 5 6 7。复合特征需要指定如下分隔符:复合特征前缀。区别于其他类型特征,我们以“$”表示该特征为复合特征。表达式各元素之间的分隔符,采用“_”来标识。用“O”表示运算符前缀。用“C”表示常数前缀。用“V”表示变量前缀。例如:表达式v1 + 14.2 + (2*(v2+v3)) 将被表示为 $O+_O+_Vv1_C14.2_O*_C2_O+_Vv2_Vv3原子特征(Atomic Feature)原子特征(或者说原始特征)包含特征名和特征值两个部分。原子特征的读取需要由4种实体类共同完成:POJO用于存储特征原始值。例如DealInfo保存了所有与Deal实体相关的特征值,包括Price、maxMealPerson、minMealPerson等等。ScoringValue用于存储从POJO中返回的特征值。特征值包含三种基本类型,即数量型(Quantity)、序数型(Ordinal)、类别型(Categorical)。ScoreEnum实现特征名到特征值的映射。每类原子特征对应于一个ScoreEnum类,特征名通过反射(Reflection)的方式来构建对应的ScoreEnum类。ScoreEnum类和POJO一起共同实现特征值的读取。FeatureScoreEnumContainer用于保存一个算法模型所需所有特征的ScoreEnum。一个典型的例子如下图所示:DealInfo是POJO类。DealInfoScoreEnum是一个ScoreEnum基类。对应于平均用餐人数特征、价格等特征,我们定义了DIAveMealPerson和DIPrice的具体ScoreEnum类。FeatureScoreEnumContainer用于存储某个模型的所有特征的ScoreEnum。复杂系统设计需要充分的利用语言特性和设计模式。建议的优化点有三个:为每个原子特征定义一个ScoreEnum类会导致类数量暴增。优化方式是ScoreEnum基类定义为Enum类型,每个具体特征类为一个枚举值,枚举值继承并实现枚举类的方法。FeatureScoreEnumContainer采用Build设计模式将一个算法模型的所需特征转换成ScoreEnum集合。ScoreEnum从POJO类中读取具体特征值采用的是Command模式。这里稍微介绍一下Command设计模式。Command模式的核心思想是需求方只要求拿到相关信息,不关心谁提供以及怎么提供。具体的提供方接受需求方的需求,负责将结果交给需求方。在特征读取里面,需求方是模型,模型仅仅提供一个特征名(FeatureName),不关心怎么读取对应的特征值。具体的ScoreEnum类是具体的提供方,具体的ScoreEnum从POJO里面读取特定的特征值,并转换成ScoringValue交给模型。特征服务代理(Feature Proxy)特征服务代理负责远程特征获取实施,具体的过程包括:每一大类特征或者一种特征服务有一个FeatureProxy,具体的FeatureProxy负责向特征服务发起请求并获取POJO类。所有的FeatureProxy注册到FeatureServiceContainer类。在具体的一次特征获取中,FeatureServiceContainer根据FeatureName的前缀负责将特征获取分配到不同的FeatureProxy类里面。FeatureProxy根据指定的实体ID列表和特征名从特征服务中读取POJO列表。只有对应ID的指定特征名(keys)的特征值才会被赋值给POJO。这就最大限度地降低了网络读取的成本。预测管道(Prediction Pipeline)预测管道包含:预测(Prediction)、级联模型(Cascade Model)、表达式(Expression)、特征转换(Transform)、计分(Scoring)和原子模型(Atomic Model)。预测(Prediction)预测本质上是对模型的封装。它负责将每个列表实体的特征转化成模型需要的输入格式,让模型进行预测。级联模型(Cascade Model)我们构建级联模型主要是基于两方面的观察:基于Facebook的《Practical Lessons from Predicting Clicks on Ads at Facebook》的Xgboost+LR以及最近很热门的Wide&Deep表明,对一些特征,特别是ID类特征通过树模型或者NN模型进行转化,把转化后的值做为特征值交给预测模型进行预测,往往会能实现更好的效果。有些训练目标是复合目标,每个子目标需要通过不同的模型进行预测。这些子目标之间通过一个简单的表达式计算出最终的预测结果。举例如下图所示,我们自上而下进行讲解:该模型有UserId、CityId、UserFeature、POI等特征。UserId和CityId特征分别通过GBDT和NN模型进行转换(Transform)。转换后的特征和UserFeature、POI等原始特征一起交给NN和LR模型进行算分(Scoring)。最终的预测分值通过表达式Prediction Score = NNScore + LRScore/(1+)来完成。表达式中的 、和是事先设置好的值。原子模型(Atomic Model)在这里原子模型指的是一种原子计算拓扑结构,比如线性模型、树模型和网络模型。常用的模型像Logistic Regression和Linear Regression都是线性模型。GBDT、Random Forest都是树模型。MLP、CNN、RNN都是网络模型。这里定义的原子模型主要的目的是为了工程实施的便利。一个模型被认定为原子模型有如下两个原因:该模型经常做为独立预测模型被使用。该模型有比较完整的实现代码。总结本文总结了作者在美团点评解决到店餐饮个性化信息展示的相关经验,从算法和架构两方面进行阐述。在算法部分,文章采用通俗的例子和类比方式进行讲解,希望非算法工程师能够理解关键的算法概念。架构部分比较详细地阐述了到店餐饮的排序架构。根据我们所掌握的知识,特征治理和召回治理的思路是一种全新的视角,这对于架构排序系统设计有很大的帮助。这种思考方式同样也适用于其他领域模型的构建。与Google提供的经典Two-Phase Scheme架构相比,在线排序分层模型提供了更细颗粒度的抽象原型。该原型细致的阐述了包括分流、A/B测试、特征获取、特征算子、级联模型等一系列经典排序架构问题。同时该原型模型由于采用了分层和层内功能聚焦的思路,所以它比较完美地体现了DDD的三大设计原则,即领域聚焦、边界清晰、持续集成。作者简介刘丁,曾就职于Amazon、TripAdvisor。2014年加入美团,先后负责美团推荐系统、智能筛选系统架构、美团广告系统的架构和上线、完成美团广告运营平台的搭建。目前负责到店餐饮算法策略方向,推进AI在到店餐饮各领域的应用。参考文章:[1]Gamma E, Helm R, Johnson R, et al. Design Patterns-Elements of Reusable Object-Oriented Software. Machinery Industry, 2003.[2]Wikipedia,Learning to rank.[3]Wikipedia,Machine learning.[4]Wikipedia,Precision and recall.[5]Wikipedia,Discounted cumulative gain.[6]Wikipedia,Domain-driven design.[7]Wikipedia,Elasticsearch.[8]Wikipedia,k-d tree.[9]百度百科,太阳历.[10]百度百科,阴历.[11]Xinran H, Junfeng P, Ou J, et al. Practical Lessons from Predicting Clicks on Ads at Facebook[12]Olivier C, Donald M, Ya Z, Pierre G. Expected Reciprocal Rank for Graded Relevance[13]Heng-Tze C, Levent K, et al. Wide & Deep Learning for Recommender Systems ...

December 24, 2018 · 2 min · jiezi

第三方支付的流程分析与总结

这几年的工作中一直与支付打交到,借着 skr-shop 这个项目来与大家一起分享探索一下支付系统该怎么设计、怎么做。我们先从支付的一些常见流程出发分析,找出这些支付的共性,抽象后再去探讨具体的数据库设计、代码结构设计。相关项目:PHP 版本的支付SDKGo 版本的支付SDK-开发中支付整体而言的一个流程是:给第三方发起了一笔交易,用户通过第三方完成支付,第三方告诉我支付成功,我把用户购买的产品给用户。看似简单的流程,这里边不同的支付机构却有不同的处理。下面以我接触过的一些支付来总结一下国内支付国内的典型支付代表是:支付宝、微信、银行(以招商银行为例),由于国内的支付都支持多种渠道的支付方式,为了描述简单,我们均以pc上的支付为例进行讲解。支付宝支付宝的接入是我觉得最简单的一种支付。对于在PC上的支付能力,支付宝提供了【电脑支付】。当用户下单后,商户系统根据支付宝的规则构建好一个url,用户跳转到这个url后进入到支付宝的支付页面,然后完成支付流程。在支付成功后,支付宝会通过 同步通知、异步通知 两种方式告诉商户系统支付成功,并且两种通知方式的结果都是可信的,而且异步通知的消息延迟也非常短暂。对于退款流程,支付宝支持全额、部分退款。并且能够根据商户的退款单号区分是否是同一笔退款进而避免了重复退款的可能。支付的退款是调用后同步返回结果,不会异步通知。微信支付微信并没有提供真的PC支付能力,但是我们可以利用【扫码支付】来达成电脑支付的目的。扫码支付有两种模式,这里以模式二为例。微信调用下单接口获取到这个二维码链接,然后用户扫码后,进入支付流程。完成支付后微信会 异步通知,但是这里并没有 同步通知,因此前端页面只能通过定时轮训的方式检查这笔交易是否支付,直到查询到成功、或者用户主动关闭页面。退款流程与支付宝最大的不同是,有一个 异步通知 需要商户系统进行处理。第一个不同点:异步通知的接口需要处理多种不同类型的异步消息招商银行随着在线支付在国内的蓬勃发展,各家银行也是不断推出自己的在线支付能力。其中的佼佼者当属 招商银行。大家经常用的滴滴上面就有该支付方式,可以体验一下。招商支付使用的是银行卡,因此首次用户必须进行绑卡。因此这里可能就多了一个流程,首先得记录用户是否绑过卡,然后用于签名的公钥会发生变化,需要定期更新。招商所有平台的支付体验都是一致的,会跳转到招行的H5页面完成逻辑,支付成功后并不会自动跳回商户,也就是没有 同步通知,它的支付结果只会走异步通知流程,延迟非常短暂。退款流程与支付宝一样,也是同步返回退款结果,没有异步通知。第二个不同点:支付前需要检查用户是否签约过,有签约流程小结国内在线支付流程相对都比较完善,接入起来也非常容易。需要注意的一点是:退款后之前支付的单子依然是支付成功状态,并不会变成退款状态。因为退款与支付属于不同的交易。这一点基本上是国内在线支付的通用做法。国际支付国际支付的平台非常多,包括像支付宝、微信也在扩展这一块市场。我以我接触的几家支付做一个简单的总结。WorldPay这是比较出名的一家国际支付公司,它主要做的是银行卡支付,公司在英国支付流程上,也是根据规则构建好请求的url后,直接跳转到 WorldPay 的页面,通过信用卡完成支付。这里比较麻烦的处理机制是:支付成功后,他首次给你的异步/同步消息通知并不能作为支付成功的依据。真的从银行确认划款成功后,才会给出真的支付成功通知。这中间还可能会异步通知告诉你支付请求被拒绝。最头痛的是不同状态的异步消息时间间隔都是按照分钟以上级别的延迟来计算退款流程上,状态跟微信一样,需要通过异步消息来确认退款状态。其次它的不同点在于无法根据商户退款单号来确认是否已经发起过退款,因此对于它来说只要请求一次退款接口,那它就默认发起了一次退款。第三、四不同点:支付成功后的通知状态有多种,涉及到商户系统业务流程的特殊处理退款不支持商户退款单号,无法支持防重复退款需要商户自己处理Assist这是俄罗斯的一家支付公司,这也是一家搞死人不偿命的公司,看下面介绍它的支付发起是需要构建一个form表单,向它post支付相关的数据。成功后会跳转到它的支付页,用户完成支付即可。对于 同步通知,它需要用户手动触发跳回商户,与招商的逻辑很像,同步也仅仅是做返回并不会真的告知支付结果。异步通知 才是真的告知支付状态。比较恶心的是,支付时必须传入指定格式的商品信息,这会在部分退款时用到。现在来说退款,退款也是与 WorldPay 一样,不支持商户的退款单号,因此防重方面也许自己的系统进行设计。并且如果是部分退款,需要传入指定的退款商品,这就会出现一个非常尴尬的局面:部分退款的金额与任何一个商品金额都对应不上,退款则会失败。第五个不同点:部分退款时需要传入部分退款的商品信息,并且金额要一致Doku接下来再来聊聊印尼的这家支付机构 doku。由于印尼这个国家信用卡的普及程度并不高,它的在线支付提供一种超商支付方式。什么是超商支付呢?也就是用户在网络上完成下单后,会获取到一个二维码或者条形码。用户拿着这个条形码到超商(711、全家这种)通过收银员扫码,付现金给超商,完成支付流程。这种方式带来的问题是,用户长时间不去支付,导致订单超时关单后才去付款。对整个业务流程以及用户体验带来很多伤害。再来说退款,由于存在超商这种支付方式,导致这种支付无法支持在线自动退款,需要人工收集用户银行卡信息,然后完成转账操作。非常痛苦不堪。第六个不同点:线上没有付款,只有获取付款码,退款需要通过人工操作AmazonPay亚马逊出品,与支付宝非常类似。提供的是集成式的钱包流程。支付时直接构建一个url,然后跳转到亚马逊即可完成支付。它还提供一种授权模式,能够不用跳转amazon,再商户端即完成支付。支付成功后也会同步跳转,同步通知 的内容可以作为支付是否成功的判断依据。经过实际检查 异步通知 的到达会稍有延迟,大概10s以内。退款方面也支持商户退款单号可以依赖此进行防重。但是退款的状态也是基于异步来的。总结这其中还有一些国际支付,如:PayPal、GooglePay、PayTM 等知名支付机构没有进行介绍,是因为基本它们的流程也都在上面的模式之中。我们后续的代码结构设计、数据库设计都基于满足上面的各种支付模型来完成设计。最后,赠送大家一副脑图,这是接入一家支付时必须弄清楚的问题清单下篇预告:《支付数据库与代码结构设计》这是我们几个小伙伴利用业余时间思考的一些业务设计,如果有写的不对或者不完善的地方,希望大家多多评论,互相学习互相进步~项目地址: https://github.com/skr-shop/m...skr-shop项目成员简介排名不分先后,字典序昵称简介个人博客AStraw研究生创业者, 现于小米科技海外商城组从事商城后端研发工作——–DayuPayment开源作者,服务端开发者dayutalk.cnlwhcv曾就职于百度/融360, 现于小米科技海外商城组从事商城后端研发工作——–TIGERBPHP框架EasyPHP作者,拥有A/B/C轮电商创业公司工作经验,现于小米科技海外商城组从事商城后端研发工作TIGERB.cn

November 26, 2018 · 1 min · jiezi