标准化电商平台开发系统定制开发怎么做?

有一位老板创业,打算做一款行业平台,因为是同事关系,做之前我们探讨了很多次,也给他一些建议。不过,经过权衡,他最终找了别的软件公司签订了合同。半年后,项目进展不顺利,告诉我,说当时找那家软件公司,主要看中那家软件公司规模大,有自己成熟的业务板块,不至于项目做到一半公司破产了。一年后,原定6个月的项目目还未交付,无奈,自己雇了一位5年经验的前端和一位经验丰富的后端,希望从软件公司接手继续开发。前段时间了解到,自己雇人似乎也没接起来。有一位南方的老板,考虑到长期合作,希望找有实力的大公司合作,也没选择我。这点我能理解,毕竟跟我共事多年的同事也担心我创业几天就破产了。好在创业两年了,相比以往,虽然规模并未成倍扩大,但也算是一步一个脚印前进的很踏实。这位南方老板本周也找我,反馈了项目进展不顺利的问题,我给了几个补救建议。我做定制化软件开发,大概有18年了,主打的就是一个交付能力,别人搞不定的,我们能搞定。不过,做软件定制化开发很累,也有很多酸甜苦辣,尤其是跟着有丰富想象力的老板,更是“苦不堪言”,好在能在锻炼中成长,于是,我在2016年还写了一本C#工具书,机械工业出版社出版,书名是《31天学会CRM项目开发》,写书的初衷是给IT人员赋能,如何让IT人员找到企业真正的需求,如何通过软件开发技能将老板的想法、业务需求落地。可以说,做软件定制化开发,我是很认真的。创业两年了,接触到很多国内电商、跨境电商、贸易公司的老板,他们很年轻、很优秀,不仅个人业务能力很强,流程标准化意识也很强,他们思维活跃,在各自的领域探索各种创新模式并获得了成功,他们乐于分享,也求同存异。这些公司人员规模虽然不大,但相比一些规模工业企业,有很强的盈利能力。以前,软件定制开发动不动几十万、上百万的成本,只有大公司才能“玩得起”,小公司只能望而却步。而在今天,随着电商的快速发展,AI的应用,越来越多的电商团队开始依赖大数据、依赖标准化流程提升选品、协同效率。面对这样的竞争环境,很多年轻老板开始寻找可长期合作的、性价比高的软件开发资源。为了让各位老板了解软件公司和软件定制开发服务,少走弯路,我总结了十个误区,希望能帮助到各位老板。误区1:认为一定要找大公司定制开发本质上是一种定制开发服务,服务由人提供,服务质量好坏跟公司大小没有直接关系,跟提供服务的人密切相关。这种服务并不是大公司要就一定做得好,关键看投入到项目的项目经理、成员的经验水平和沟通能力。如果要开发ERP系统,一定要找有ERP系统开发经验的项目经理,如果是开发商城,一定要找成熟产品的公司。与其说是找有实力的公司,还不如说是找一个有交付能力的人,这种交付能力体现在,可以很好的理解客户需求,有对应的行业经验,可以调动软件开发资源,必要时可以自己亲自上阵,注重交付口碑等等。误区2:认为软件开发很简单网上有个段子,问:“有老板要出价2000元开发一个百度,问接不接?”程序员纷纷回答,接!开发一个外壳,将百度嵌入进去就行。这就应那句话“你糊弄我我糊弄你”。很多老板都认为定制开发很简单,从一开始沟通就以此为由各种压价,否定软件的价值,此时可能有一个事实,他已找了很多软件公司但都没有谈成。认为软件开发很简单,就很容易否定开发人员的劳动成果,这就破坏了相互尊重的原则,沟通、协作一定是不平等的,想达按时交付几乎不可能。将定制开发当成一种技术服务,对这种技术服务持有一个客观评价很重要。记得在国企工作时,跨部门协助,嘴上多几句“辛苦了”、“麻烦了”,都会让协作更加顺利。误区3:认为项目越“高大上”越能吸引软件公司很多老板为了能降低综合报价,吸引软件公司,喜欢将项目往大的说,如果能打上“平台”的标签更好,生怕别人不知道自己的“宏伟蓝图”。这种以“我的活很多”的方式降低项目报价,可能会适得其反。此时,容易让软件公司真的觉得这真是一个”大项目“,投入少不了,报价都不会低,即便现在低,后期也会补回来,既然客户已经投入那么多了,软件公司容易“拿捏”。误区4:认为定制开发要一步到位有的老板担心项目交付后再找软件公司就麻烦,所以项目刚开始就考虑了各种可能出现的潜在情况,将一些“伪需求”也当成刚性需求加入到项目中,最终导致项目需求很多,虽然需求都开发上了,但是因为没有业务支撑,上线后很长一段时间不会启用,等真正启用的时候,发现存在各种问题。定制开发是一个持续的过程,尤其是企业信息管理系统,随着企业业务的发展变化及系统的深入应用,系统需要持续优化,所以,从一开始,就要做好长期作战的思想准备,和软件供应商保持良好的合作关系。所以,一定要找到可靠的开发资源再启动项目、一定要找到可靠的开发资源再启动项目、一定要找到可靠的开发资源再启动项目,重要的事情说三遍。判断一个软件公司是否可靠,可以先合作一个小项目试一试,看看过程中沟通是否通常,交付是否达到预期。误区5:认为项目源代码很重要很多老板纠结要不要买项目源代码,要不要买开发平台将来自己搞二次开发。真相可能是,花钱买的项目源代码对未来软件升级开发几乎没什么用,有新需求还得找软件公司。虽然有源代码,但是自己雇人写代码或者找其他供应商写代码可能都用不上。比如软件公司各家使用的开发语言不一样,技术框架也不一样,基于已有源代码改,可能还不如重新开发。况且,软件公司提供的源代码也未必是完整的源代码,大概率给的只是业务层面的源代码,核心代码是不会提供的,离开了核心代码能解决的问题也有限。也有可能,提供的源代码是初级程序员写的代码,虽然功能实现了,但其代码在其他程序员看来“不忍直视”,要知道普通程序员都有一个“通病”,看不上别人写的代码。一个定制化项目,最重要的可能不是项目源代码,甚至也不是软件本身,最重要的可能是老板与项目参与人员对自身业务及信息化认知的提升。只要思想改变了,有经验了,再做就容易事半功倍。误区6:认为如果需求反复变更“给钱”就行定制开发是一种服务,本质上也是一种“伺候人”的工作,既然是伺候人,有的时候拿钱砸是可以,有的时候给钱不一定管用。有的人即便是给钱也不一定会“伺候”。所以,在项目沟通过程中,尽可能的控制需求变更,想好了再开发,要尊重、认可技术人员的付出。误区7:认为有合同约束就万事大吉定制化开发项目,如果已经到了按照合同条款约束甲乙双方行为了,这个项目一定出现延期交、沟通或无法交付的问题了,没有人“力缆狂澜”,项目失败是大概率的。定制化开发项目,老板要承认,自己不一定能把需求说明白,也要让软件公司知道,定制化开发就存在需求不明确的事实,既然承接了定制化项目,就在在一定限度内消化需求变更。此外,软件公司主要按照人工核算项目成本,如果因为项目范围未控制好,导致追加项目成本超过可承受的范围,软件公司一定会及时止损,并要求客户追加投资。如果客户不愿意承担追加,这个项目一定会失败,这种情况,打官司赢得可能性很小。误区8:认为有IT人员负责,自己当甩手掌柜就行在有IT部门的企业,推行IT系统时都经常强调,IT系统是一把手工程,一把手必须要亲自抓。其核心是,IT系统往往会改变业务流程、工作习惯,要求员工学习新系统,初期还会增加员工工作量,甚至还会懂了某些人的利益。通常,在企业里,除了老板自己,估计很难找到第二个人会站在企业利益上考虑IT系统建设的必要性,大部分员工都没有动力改变现状、上什么新系统的。对于中小规模团队,老板要亲自上,还要安排一名兼职IT人员落地具体的事情。误区9:认为购买软件开发服务就是够买一件商品很多老板喜欢将软件定制化开发项目当成普通商品进行采购,寄希望交付标准、合同条款、项目尾款达到交付目的,而实际上,软件定制化开发是一种由技术人员提供的技术服务,是一种体验式服务,很多服务细节是无法落实到合同,也无法直接约束。所以,在整个项目执行过程中,沟通是最重要的环节,注重高效沟通,让项目人员从被要求做变成主动去做。误区10:认为软件定制化是唯一出路搞软件(这里特指业务管理系统)定制化开发很不容易,而且是一个长期过程,各行各业的企业管理软件已经比较成熟,必须要自己定制开发的应用场景并不多。在确定定制化开发之前,一定要再三评估,慎重决策,接受已有软件系统的不足,改变固有思维,充分论证定制化开发比现在做得更好的情况下再开始。(完)
学习优秀的开源系统来优化我们业务的架构设计,这是我们作为业务开发的必修课,这篇文章从经典的mysql系统原理引申到业务系统设计思考,让人耳目一新,值得我们学习。(点击头像关注我们,可查看更多阿里工程师干货。)——————————————————————————————————如果一个系统能存活5年,看到里面的代码我可能觉得要重构了,看到一个系统存活了10年,那么我就万万不敢动了。mysql能够从1979的一个报表工具,2000年开源,到现在支持高并发,高可用,成为互联网的活化石“世一库”,靠的是无数开源人对技术的热爱,创始人Monty Widenius的人格魅力,以及不断进化的能力……前言之前在处理一些慢sql和索引失效问题的时候复习了一波mysql,加上给团队分享设计模式的时候,乱翻了好多源码和课程,越发觉得mysql写的很不错。mysql不仅仅是一个数据库,更是一个优秀的系统……我们不仅可以使用它,我们也可以借鉴它沉淀了数年的设计,技术升级我们的业务系统。因为很多mysql的知识点大家都清楚,所以着重讨论,略过一些基础。时间匆忙,错误望指正,补充的请留言。WAL和二阶段提交日志开头肯定是绕不开mysql中经常提到的WAL技术,为了避免发生数据丢失的问题,当前事务数据库系统普遍都采用了 WAL(Write Ahead Log)策略:即当事务提交时,先写redo log,再修改页(先修改缓冲池,再刷新到磁盘);当由于发生宕机而导致数据丢失时,通过 redo log来完成数据的恢复。关键点是日志先行,再写磁盘。那么记录什么样的日志呢?引擎层会记录redolog,服务层会记录binlog。redo log是物理日志,记录的是“在XXX数据页上做了XXX修改”;binlog是逻辑日志,记录的是原始逻辑,其记录是对应的SQL语句;binlog 是追加写入的,就是说 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志;而 redo log 是循环写入的。用户如果对数据库中的数据进行了修改,必须保证日志先于数据落盘。当日志落盘后,就可以给用户返回操作成功,并不需要保证当时对数据的修改也落盘。如果数据库在日志落盘前crash,那么相应的数据修改会回滚。在日志落盘后crash,会保证相应的修改不丢失。在日志先行技术之前,数据库只需要把修改的数据刷回磁盘即可,用了这项技术,除了修改的数据,还需要多写一份日志,也就是磁盘写入量反而增大,但是由于日志是顺序的且往往先存在内存里然后批量往磁盘刷新,相比数据的离散写入,日志的写入开销比较小。那么mysql是如何去做日志刷新和数据刷新的呢?当用户线程产生日志的时候,首先缓存在一个线程私有的变量(mtr)里面,只有完成某些原子操作的时候,才把日志提交到全局的日志缓存区中。当线程的事务执行完后,把日志从缓冲区刷到磁盘。当把日志成功拷贝到全局日志缓冲区后,会继续把当前已经被修改过的脏页加入到一个全局的脏页链表中。这个链表是order by modified time asc的且用一个字段来记录。这种机制保证从老到新刷入磁盘。这里最重要的是,脏页链表的有序性。每个 InnoDB 存储引擎至少有 1 个redo log文件组,多个redo log文件。为了得到更高的可靠性,用户可以设置多个镜像日志组(mirrored log groups),将不同的文件组放在不同的磁盘上,以此提高 redo log 的高可用性。在日志组中每个 redo log file 的大小一致,并以循环写入的方式运行。write pos 和 CheckPoint 之间的就是 redo log file 上还空着的部分,可以用来记录新的操作。如果 write pos 追上 CheckPoint,就表示 redo log file 满了,这时候不能再执行新的更新,得停下来先覆盖一些 redo log,把CheckPoint 推进一下。业务可以借鉴-类WAL机制实现合并处理,异步处理,异常恢复回滚等其实这一块mysql有很多贴近数据层面的设计,但是把数据想象为业务,数据的记录和回滚--->业务操作的记录和回滚,数据的原子性--->业务操作的原子性,那么会有一些灵感。其实现在很多关注数据强一致性的系统,都会记录操作(记录入数据库)来达到异常恢复和回滚的效果。比如结算账单的发起收佣和分账,商品的发品上下架,交易订单的打标去标,等等,都会将业务操作记录下来,作为落库保障稳定性,同时支持错误情况下的回滚凭证。不仅如此,也可以实现异步和外部系统交互的操作。达到重试和异步的机制。下面是mysql更新数据操作和结算系统分账操作的对比图。mysql的“用户调用-日志记录-磁盘”就类比于系统的“操作发起者-持久化操作-下游”。都是运用了WAL机制,首先从用户调用(业务层)查询或初始化等操作,然后在内存(or业务领域层)记录即将执行的原子性的操作,之后采用不同机制(mysql使用内存刷取机制or结算系统运用异步调用及其他机制)来执行最终操作(mysql磁盘or业务系统底层服务)。这里第二张图把回滚机制和多次重试的机制统一放到处理机制里面,并且和各内存中操作用双箭头表示调用和恢复回滚。持久化的方式:最常见的,就用各种数据库把操作记录或者账单或者领域事件的状态记录下来,单条多次更新;或者比较少见使用日志文件记录下来每一次变更,就如同mysql写log一样;处理机制中正向处理一般是单次的同步即时调用,也可以考虑的是:合并处理减少调用量,在并发量较高的情况下,合并请求,或者也可以将一些更新操作合并到内存中进行调用;定时捞取请求异步处理削峰,这种是比较常见的不关注实时性的请求处理,在系统水位不紧张的情况下,内存中定时异步捞取持久化的请求去调用。或者是使用消息机制比如metaq,去慢慢消费处理调用;异常恢复:服务调用的原子操作包括:a,b,c。当其中c服务调用超时或者失败,那么就会依次执行回滚操作c’,b’,a’。使用wal机制将服务执行的commit和rollback之前保留重要执行信息。举个简单例子,卖场佣金代扣到旗舰店-->调用平台收佣-->销账,当销账失败无数次由于比如风控原因不能重试成功的时候,需要采用异常回滚。那么将依次采取三个服务的回归方法,进行事务回滚。将钱最终返回原来卖场,否则钱岂不就是卡在旗舰店。springboot提供的拦截完全可以达到事务识别,同时各服务添加服务id,类似mysql的xid。这里可以参考一些github上的事务回滚框架。举例,只有一个初级想法,可以讨论一下:/**
* 回滚的具体方法
*/
public @interface Transactionable {
String rollbackMethod();
}
/**
* 事务的状态
*/
public enum TransactionState {
INIT(1),
COMMIT(2),
ROLLBACK(3);
}
/**
* 各个服务的commit 和 rollback调用实体
*/
public interface Invocation {
Class<?> getTargetClassType();
String
getMethodName();
Object[] getArgumentValues();
Class<?>[] getArgumentTypes();
Map<String,Object> getExtraAttachMap();
Object getExtraAttachInfoByKey(String key,Object defaultValue);
void putExtraAttachItem(String key,Object value);
}
/**
* 分布式事务的服务的核心结构
*/
public class aService implements Serializable{
private static final long serialVersionUID = -4512371127490746819L;
private
String xid;
private
String serviceName;
private String methodName;
......
}
/**
* transaction核心载体
*/
public class Transaction implements Serializable {
private static final long serialVersionUID = 6648691752838557325L;
private final TransactionGlobalId transactionGlobalId;
private TransactionState transactionState;
.....
}二阶段提交redolog原是innodb引擎的东西,binlog是mysql server的东西,逻辑是独立的,可以理解为事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。假如我们不使用二阶段提交。先写 redo log 后写 binlog。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来。但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行值与原库的值不同。先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行无更新。但是 binlog 里面已经记录了这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 与原库的值不同。同时log会有完整格式及xid来确认完整性和关联。业务可以借鉴-保证数据一致性其实这个已经有很多分布式事务的理论都写了,通过多段式来保证数据一致性。这里写一下自己业务的应用,两个例子。比如在配置结算规则,业务方会设置一个规则,并进行自己的校验,平台方也会保存规则,进行校验和打款;再比如预约单核销创建尾款单的场景,需要保障电子凭证状态和订单状态的数据一致性。其中有一些是需要强一致性,有些是需要弱一致性但是需要最终一致性的。可以参考base version的上图,进行一些定制。结算规则设置就选择强一致性的二阶段请求,如下:
/**
* 单协议插入:二阶段强一致-先落库初始化,再调用服务H,成功后再次落库生效
*/
private void addAgreement(SettleAgreement settleAgreement, SettleAgreementSaveReqDTO settleAgreementSaveReqDTO){
settleAgreement.setEffectStatus(0);
int num = agreementWriteRespository.insertAgreement(settleAgreement);
if(num != 1){
throw new SettleBizException(CommonErrorDef.DB_HANDLE_FAIL);
}
// 实际生效规则需要同步H系统
if(settleAgreementSaveReqDTO.getStatus() == 1){
settleAgreement = agreementReadRespository.getAgreementsByOutId(SettleAgreementReq.of(settleAgreementSaveReqDTO.getRuleRelatedId(),settleAgreementSaveReqDTO.getBizCode(), null));
if(settleAgreement.needCallOut()) {
// 同步H系统
syncRule(settleAgreement);
}
settleAgreement.setEffectStatus(1);
// 调用成功后生效本地规则
num = agreementWriteRespository.updateAgreement(settleAgreement);
if(num != 1){
throw new SettleBizException(CommonErrorDef.DB_HANDLE_FAIL);
}
}
}其实也是init本地-prepare-提交另一侧数据-commit。如果那一方的系统异常(类比于mysql的宕机)则失败,且回滚。而选择弱一致性(最终一致)的二阶段请求则需要设计回补方案。比如预约单核销创建尾款单的场景。尾款单的订单状态需要和电子凭证的状态保持一致(电子凭证未冻结-现订单可创建,电子凭证已冻结-已有尾款单创建,电子凭证已核销-已有尾款单支付成功)。但是创单是p0场景,不能完全依赖电子凭证服务,电子凭证服务不可用或者延迟,不能影响创单,那么我们可以使用下面的方式,弱依赖+异步回补机制。一些结构引擎Mysql的引擎不是固定的,比较常用的是innodb和myisam,很多模块都是通过插件的形式的方式加载到Mysql主程序上的,这其中不仅有一些日志,状态等插件,还有数据引擎等核心的插件。在Mysql中访问接口的方式主要有两类,一类是通过注册使用观察者模式来调用;另外一类就是数据库引擎通过handlerton的方式来实现。在数据存储引擎中,对表及事务的相关操作都是通过这种方式来访问相关的引擎插件的。handlerton的源码太长,复制过来很丑就略了。基本分成两大块,是一系列的相关的变量定义,比如state、type、slot等等;另外是一系列的函数指针,诸如binlog_func等。在Mysql中是通过全局变量来管理这个插件的,它其实是一个插件相关的哈希数组,它可以通过plugin_find_internal来发现插件。像innobase_hton,myisam_hton之类的。像实现的时候,引擎去初始化其实就是调用相关的函数plugin_initialize来实现,调用的话就是从plugin_foreach开始的。业务可借鉴-可拔插的思路这种可拔插的,使用观察者和handlerton的形式来支持扩展的设计模式,其实中台大部分代码都是这样,就不多做引申了。内存的运用和一些算法内存管理结构mysql划分架构Server 层与引擎层(innodb),使用不同的方式进行管理。其中Server 层是由 mem_root 来进行内存管理,包括Sharing与Thead memory;而引擎层则主要由 Free List,LRU List,FLU List 等多个链表来统一管理 Innodb_buffer_pool。一张网图,侵删。业务开发的话关于mem_root了解一下即可,其实就是一个函数初始化一块较大的内存空间,向内存分配器申请内存空间,然后另一个函数在这块内存空间中分配出内存进行使用,其目的就是将多次零散的操作合并请求,以提升性能。并且不同的线程会产生不同的mem_root来管理各自的内存。在innodb内存管理中,有一些分配方式。内存分配方式由于 CPU速度与磁盘速度之间的不匹配,通常会使用缓冲池技术来提高数据库的整体性能。通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。前文聊到了查询和更新页操作,就是依赖这个buffer pool:从磁盘读到的页存放在缓冲池中,下一次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。修改操作的具体步骤就是这样的:修改在缓冲池中的页;然后再以一定的频率刷新到磁盘上。控制poos和包含配置的主结构是buf_pool_t,控制数据页的是buf_page_t。这个地方用了一个内存分配算法,在释放一个内存块的时候没有直接放回,而是先查看其伙伴是否也空闲,如果是则进行合并,再尝试对合并后的内存块进行合并。如果其伙伴是在使用的状态,这里做了一次重新分配操作,将其内容拷贝到其它空闲的内存块上,再进行对它合并。另外一个比较好聊的是LRU list的算法,即最少使用的老数据先从buffer pool驱逐,新的页数据加入到list的中间位置,这就是所谓的中点插入策略。一般情况下list 头部存放的是热数据,就是所谓的young page,list尾部存放的就是old page。这个算法就保证了最近经常使用的page信息会被保存在最近访问的sublist,相反的不被经常访问的就会保存在old sublist。一般比例是对半分或young page少点。这样既能支持热点数据的读取写入,又防止了大量数据对全表数据的影响。业务可以借鉴-缓存的思路关于内存的思路没什么太多可以借鉴。主要是一些缓存的想法,包括热点商品的插入可以使用lru算法,在一些占用性能较大的服务上使用伙伴算法,等等。动态地看待锁mysql大量使用锁包括全局锁,表锁,行锁,mdl锁,间隙锁等等,来处理并发问题。作为共享的资源,当出现并发访问的时候,数据库需要合理地控制资源的访问规则。而锁就是用来实现这些访问规则的重要数据结构。在mysql锁的设计中,在不同场景下使用不同粒度的锁,且锁也是放在最合适的地方,来提升并发度。比如全库逻辑备份的时候,使用全局锁;当对一个表做增删改查操作的时候,加MDL读锁;当要对表做结构变更操作的时候,加 MDL 写锁;对于行的更新操作,最小粒度加行锁。事务顺序而如果事务中需要锁多个行,也会把最可能造成锁冲突,最可能影响并发度的锁尽量往后放。举个简单例子,交易发货。那么需要做:1. 更新消费者订单状态;2. 该货品量扣减;3. 插一条发货记录。为了保证交易的原子性,我们要把这三个操作放在一个事务中,很显然如果随意加锁的话,会产生大量锁冲突。比如两笔订单发货的是同一个货品,那这个货品这一行数据就会冲突。所以,如果把语句 2 安排在最后,比如按照 3-1-2 这样的顺序,那么该货品这一行的锁时间就最少,大量减少事务之间的锁等待,提升了并发度。锁的退化虽然集团貌似为了避免死锁用的是Read Committed,而mysql默认的是Repeatable Reads。但是Repeatable Reads下的next key lock我觉得还是需要了解一下的也挺有意思。查找过程中访问到的对象会加next key lock;索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁;索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。这种资源的降级退化思路是可以借鉴的。状态机的使用状态流转mysql中有很多的状态,包括Checking table,Closing tables,Killed,Locked,Sending data,Sleeping,Waiting for tables……等等状态,状态之间会互相流转。比如checking table状态在查询之后会进入locked,以及更新状态会进入locked,查到锁冲突时会进入waiting等等。能够在如此复杂的状态中进行精准流转,且代码并不臃肿。在mysql其中一个版本的分支代码中,尝试使用二维的形式来流转状态,是可以借鉴的。业务可以借鉴-二维简化复杂状态流转一般业务系统中,状态机的使用是流程调用中set不同的状态,并允许在特定状态下进行特定操作。最简单的做法是分支逻辑,即if-else,将每一个状态转移,原模原样地直译成代码。这种会使得极易漏写或者错写某个状态转移,可读性和可维护性都很差。具体请参考各种老系统的状态流转。平时常见的做法是充血模式状态机,所有的状态转移和动作执行的代码逻辑,都集中在业务的实体类中,代码分散开来,同时存在一个状态机类作为流转。其实这种方案是比较好的,但是当状态很多的时候,会引入更多状态类和操作,代码会越来越臃肿。实际上,除了用状态转移图来表示之外,状态机还可以用二维映射来表示,也叫做查表法,比如说,一维表示状态,另一维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。实现更加清晰,可读性和可维护性更好。当修改状态机时,我们只需要修改二维映射即可。比如交易订单是有很多状态的。这里写的不一定对,举个例子而已。状态\操作关闭订单付款发货确认收获退款。。。交易关闭/////待付款交易关闭待发货///待发货交易关闭/待确认收货/交易关闭待确认收货交易关闭/待确认收货交易成功交易关闭交易成功////。。。。。。public enum Event {
closeOrder(0),
pay(1),
sendGoods(2),
receiveGoods(3),
refund(4);
private int value;
private Event(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
public class OrderStateMachine {
private State currentState;
private static final State[][] transitionTable = {
{trade_close, trade_close, trade_close, trade_close, trade_close},
{trade_close, wait_sendgoods, wait_pay, wait_pay, wait_pay},
{trade_close, wait_sendgoods, wait_receivegoods, wait_sendgoods, trade_close},
{trade_close, wait_receivegoods, wait_receivegoods, trade_success, trade_close},
{trade_success, trade_success, trade_success, trade_success, trade_success}
};
public OrderStateMachine() {
this.currentState = State.trade_init;
}
public void closeOrder() {
executeEvent(Event.closeOrder);
}
public void pay() {
executeEvent(Event.pay);
}
public void sendGoods() {
executeEvent(Event.sendGoods);
}
public void receiveGoods() {
executeEvent(Event.receiveGoods);
}
public void refund() {
executeEvent(Event.refund);
}
...
...
private void executeEvent(Event event) {
int stateValue = currentState.getValue();
int eventValue = event.getValue();
this.currentState = transitionTable[stateValue][eventValue];
}
public State getCurrentState() {
return this.currentState;
}
}这个只是简化一下。复杂做法,可以具体excute方法可以在各模块或产品包,映射模型也可以统一维护在配置文件中。只是为了将状态流转放到一处去维护。同时除了订单状态,有些操作会引发物流单,支付单等状态,可以将二维升级为三维等等。而不是把各种状态的流转放到event代码中,会很难维护。针对不同的业务身份,不同业务类型,也可以设置不同的状态流转配置。当然这种方式适合event比较简单,但是状态较多的场景,比如mysql中,其实很多事件只是加个锁,发个数据,等等。像交易如果越做越重的话,还是使用状态充血模式,需要依业务来选型。如何“删库跑路”首先,大家应该是没有单独数据权限的,且有审批,所以删库跑路还是不要多想了!从恢复难易程度来看几个删除数据的方法。使用 rm 命令删除整个 MySQL 实例:登上机器,查看mysql安装路径然后查找是否存在服务,之后直接kill并rm带mysql的东西即可。这种方式的恢复方法,就是即使删除一个节点的实例,集群也会推举出新的主库,然后根据集群其他节点数据恢复这个节点的数据即可。对于高可用+跨机房的集群来说,除非批量全下掉实例,不然应该是最好恢复的。删库/删表:使用drop database直接删除数据库,drop table 或者 truncate table来删除表。此时恢复需要全量备份,并且新的操作会有实时增量binlog,使用这些binlog恢复一个临时库,然后设置主备关系即可。如果binlog也删除了直接从binlog备份系统中找到需要的 binlog,再放回备库中,这样恢复事件一般很长。dba应该有些其他科技来加速。比如使用一些并行的方式。使用delete语句删除一些数据行:除了简单delete外,搞复杂点比如delete完再insert一条不想干的,然后再update一下。其实对恢复来说复杂度差不多,使用binlog解析工具把语句反译一下,反过来执行一下放回备库重放,但是需要确保binlog_format=row 和 binlog_row_image=FULL,这个应该是默认的所以不用担心。总结本短文大致介绍了一下mysql的wal机制,一些内部结构和算法,锁和状态机的视角,以及程序员经常碰到的“删除”。mysql发展这么多年了,涌现了很多专业分析和经典课程,本文主要是另辟蹊径从业务借鉴的角度来看看它的设计,给大伙儿提供一个引子,希望后续继续和评论区讨论。其实在当前技术同学视角下,最常见的两方面,一是完成一个业务研发活动,比如商品的3d详情,交易的改价分摊,双十一的秒杀;二是实现技术上的突破,比如缓存tair支持sql,mq消息队列的升级,部署安全等等。因为这些都是容易让人获得成就感的,是容易量化的。然而还有一些比如合理设计系统架构,构建开放开源文化,不同技术互相融合,是容易让人忽略的,却也是非常重要的。(本文作者:闻尘)——————————————————————————————————————————阿里巴巴集团大淘宝技术官方账号。 点击下方主页关注我们,你将收获更多来自阿里一线工程师的技术实战技巧&成长经历心得。另,不定期更新最新岗位招聘信息和简历内推通道,欢迎各位以最短路径加入我们。
核心产品深耕细分行业,提供更加专业的行业解决方案社区团购
抖音小程序系统
社群团购
全渠道电商系统
裂变营销系统
幸运拼系统
视频号带货系统
同城生活服务系统
以社区为单位,由宝妈或店主成为团长,在线上借助微信群、小程序等组织社区居民拼团、线下完成交付,当日线上下单、次日社区内自提,由平台提供采购、物流仓储及售后支持。按销售额,团长抽取一定比例佣金。社区团购核心功能消费者端
团长端
供应商端
平台运营端
司机端
骑手端
营销中心
数据中心
配送中心
直播系统
群接龙系统
合伙人系统
连接抖音庞大的公共流量池,同一个小程序可以同步上线今日头条、抖音、头条极速版等多个字节系产品,覆盖文章、视频等重要的流量入口。应用行业餐饮外卖
知识付费
线下门店
教育机构
媒体行业
娱乐行业
生活服务行业
其他行业
更好的经营微信私域客户,实现业绩暴涨应用行业自媒体IP如何升级,实现带货变现?
网红主播如何让自身流量有效吸金,突破带货瓶颈?
电商私域流量如何通过社交人、货、场变现?
多平台开店,支持微信、QQ、百度、支付宝、抖音等多平台小程序商城一键生成。全渠道电商解决方案,助力企业构建私域流量进入品牌电商新时代。功能强大的电商系统全渠道覆盖微信
QQ
百度
支付宝
抖音
快应用
苹果APP
安卓APP
H5
操作简单,快速上手,创建微信裂变获客活动,支持多渠道传播和转化,获得倍增收益。功能亮点分销裂变
任务裂变
定金裂变
零元裂变
一元解锁
幸运拼系统是2021年最新社交裂变玩法,简单,可操作性强。应用行业美妆护肤
服饰箱包
珠宝饰品
母婴玩具
家电家具
生鲜果蔬
茶饮酒水
其他行业
视频号带货系统:视频号正在打通整个微信生态,成为私域流量精细化运营必选,助力商家和广大的视频号主播进行合作,带货增收。将本地城市的餐饮、外卖、商城、健身医疗等线下实体商家资源整合,并通过本地生活服务平台,提供给用户方便快捷、全面的商家信息,同时通过线上引流促成线下消费,为线下实体商家提供免费的推广渠道。应用场景跑腿
外卖
表白墙
论坛
二手交易
预约
商圈
团购券
优惠券
点击查看大图 长按可保存

我要回帖

更多关于 电商平台开发 的文章

 

随机推荐