一个典型的后台软件系统的设计复盘——(二)如何id一个事物
这个话题,可以从类与对象说起。
Dog dog1 = new Dog();
哪个是类,哪个是对象?这个问题搞不清楚,后面就无从说起了。然后两个程序员之间沟通说,那个狗有问题。除非两人很默契,不然另一人肯定要懵圈,是狗这个类有问题,还是狗的实例对象的属性有问题。由此引出了今天的话题:如何id一个事物。首先,这个事物是一个类型,还是这个类型中具体的一个对象。
再者,同一个东西,有不同的阶段不同的形态。
比如,扎啤。从仓库里搬出来的时候是整桶的,销售的时候却是一扎一扎的。计算一扎的利润时,就要进行转换换算了。所以通常设计的时候会有:采购环节的形态,称之为产品;销售环节的形态,称之为商品;两者之间有一定的换算方式,用于转换数量,也用于计算价格(成本)。
既然举例是食品类的,那么必定有另一个问题。某天发现卖出的一件商品过期了,如何追踪到这件商品是什么时候谁采购的、同批的还有哪些?这便是另一个维度的信息,批次与库存:店内这款商品共N+M个,其中过期这批的有N个,过期这批来自什么时候谁的采购,总共数量有N+M+L个,另外有L个散落在哪些店。当然了,这是个强调保质期强调批次的例子,有些品类的库存只需要记个数量,比如服装类,这一层也可以做薄,要看具体场景。
有那么一个极端场景,下架商品(改状态)失败,事务等待超时。原因竟然是,订单请求太多太久,修改库存时锁定了商品记录,所以状态也改不了。那么问题来了,库存信息和状态信息是同个数据库表里同个记录?偷懒这么做,请求量一上来就扛不住了。为什么?因为动静要分离啊,高并发的信息和长事务的信息要尽量拆分开。商品名、商品分类这些描述信息,偶尔才改动一次,可以认为是静态的;状态(是否可见、是否可售)、价格这些信息,改动也不频繁,也可以认为是静态的;而库存信息是变动非常频繁的,是动态的。需要动静分离的场景,更典型的例子就是,库存和基础信息放同个表,批量导入改商品标语的一个长事务锁住了表,结果订单扣库存锁等待,严重影响了成交。所以,动静要分离。
在库存和基础信息这个场景里,指的基本上是同个东西,就是这种商品(所以才可能放在同个数据库表)。另一种场景的动静分离,则并不是同个东西。比如顾客3天前买了A商品2件,当时单价1元,今天顾客要退货退款,但A商品单价涨到2元了,是否去取A商品当前价格去计算退款多少钱?显然不是。所以在这种场景下,商品的当前价格是动态的,时不时会浮动的;而某一刻交易的价格是静态的,是既成事实的。所以订单信息中还应记录着每个商品成交那一刻的价格,我们经常称之为商品快照。当然,这只是设计的基本思想,具体实现可以冗余在订单里,也可以是专门的商品快照表记录而订单直接引用商品快照。
所以,即便是同一个东西,比如商品,也会有采购、销售等不同阶段,也会有基础信息、批次库存、历史快照等不同形态。它们已经是不同的实体,需要用不同的记录去id去唯一标识。
再者,同一个过程,有不同的颗粒度。
完成一个订单,可能需要捡货-打包-送出等,且称之为step1-step2-step3,那么也需要一个颗粒度把“执行这次订单”这个事件给组织起来,且称之为message,异步消息投递嘛。单次投送的时刻和状态当然要有。问题是倘若一个订单拆分成若干次执行,如何表达这次订单执行完成了没?可以考虑多一层job的概念,即job与订单对应,job里的多个message表示多个拆分,message里的step表示具体执行的步骤。所以,如果要追踪订单现在执行到哪一步,可以通过job-message-step找到;反过来要追踪某一步失败影响到哪张单也不难。
正常情况下,job这一层也不是必须的,状态和时间直接挂在订单上也不是不可以。但如果特殊场景呢?比如不只是订单,促销也会发起若干个消息投递。再比如,订单被取消了,要插队追加个撤销,有个job层级做标记,就好操作很多了。
回过头来看颗粒度。如何唯一识别一个订单任务?整个单的是job,这个单的拆分则是message,具体每个步骤则是step。那么,订单中某个项呢?必定是包含在某次拆分中。所以,message应该引用着一个订单拆分项,而step则应引用着若干个订单拆分项明细。如此,便可以追踪到订单中某个商品的进度了。(当然,还是要看具体场景,是不是需要拆分订单,是不是需要追踪某个商品的进度。)
最后,即便同一个对象,也不一定是同个事物
比如,java JPA中的entity bean。一个新建的实体对象,save前后是完全不一样的。保存前只是个纯粹的java对象,而保存后则是跟数据库记录有绑定的实体对象。所以,把未保存的对象存到引用的字段里是会运行时报错的,比如product实体引用了品牌brand,品牌对象未保存(未绑定到数据库记录)时就存入product.setBrand(brand),运行时就会报错。
而实体类和承载web接口返回的类,其实也不该是同一个。新手经常会遇到,在controller层报错找不到数据库连接,一轮排查后发现,实体之间的join使用了懒加载,而在controller层是没事务没数据库连接的。其实问题的症结在于,实体类实体对象不应该返回给controller层,应该返回的是定制好了的POJO、符合接口返回的普通的java对象。我们经常偷懒把实体对象返回给controller层,也不是不可以,但是用的时候心里要明白,这里拿到的对象已经不是同个东西了,这里已经没事务没数据库连接了。
如果哪天又有同事找你看看那个是什么问题,不要犹豫,掀桌而起!那个是哪个啊?什么环境(生产还是测试)什么场景(用户行为)什么环节有什么现象啊?如何id一个事物都讲不清楚,前方绝壁有大坑。。。那就慷慨就义跳进去填吧~
一个典型的后台软件系统的设计复盘——(二)如何id一个事物的更多相关文章
- 一个典型的后台软件系统的设计复盘——(三)打通任督二脉-context
武侠小说练功讲究打通任督二脉.程序设计练到一定程度也讲究打通任督二脉.好奇心强的同学可以搜搜“打通任督二脉有什么感觉”. spring的任督二脉ApplicationContext 最经典的任督二脉莫 ...
- Linux下一个简单的日志系统的设计及其C代码实现
1.概述 在大型软件系统中,为了监测软件运行状况及排查软件故障,一般都会要求软件程序在运行的过程中产生日志文件.在日志文件中存放程序流程中的一些重要信息, 包括:变量名称及其值.消息结构定义.函数返回 ...
- ASP.NET#在设计窗口上添加了一个SqlDataSource控件后,没有显示出来?
在设计窗口上添加了一个SqlDataSource控件后,没有显示出来,但后台代码是有的 处理的办法:菜单栏->视图->可视辅助->ASP.NET非可视控件 (我用的是VS2012)
- 如何在Visual Studio 2017中使用C# 7+语法 构建NetCore应用框架之实战篇(二):BitAdminCore框架定位及架构 构建NetCore应用框架之实战篇系列 构建NetCore应用框架之实战篇(一):什么是框架,如何设计一个框架 NetCore入门篇:(十二)在IIS中部署Net Core程序
如何在Visual Studio 2017中使用C# 7+语法 前言 之前不知看过哪位前辈的博文有点印象C# 7控制台开始支持执行异步方法,然后闲来无事,搞着,搞着没搞出来,然后就写了这篇博文,不 ...
- 推荐一个 Laravel admin 后台管理插件
如何优雅的写代码,我想是每位程序员的心声.自从15年初第一次接触 Laravel 4.2 开始,我就迷上使用 Laravel 框架了.我一直都想找个时间好好写写有关 Laravel 的使用文章,由浅入 ...
- 一文搞懂后台高性能服务器设计的常见套路, BAT 高频面试系列
微信搜索「编程指北」,关注这个写干货的程序员,回复「资源」,即可获取后台开发学习路线和书籍 先赞后看,养成习惯~ 前言 金九银十,又是一年校招季. 经历过,才深知不易.最近,和作为校招面试官的同事聊了 ...
- Linux内核设计第三周——构造一个简单的Linux系统
Linux内核设计第三周 ——构造一个简单的Linux系统 一.知识点总结 计算机三个法宝: 存储程序计算机 函数调用堆栈 中断 操作系统两把宝剑: 中断上下文的切换 进程上下文的切换 linux内核 ...
- 使用PHP开发一个简单的后台接口(响应移动端的get请求和post请求)
写一个简单的后台,在接到app请求数据的时候,返回对应的内容: index.php文件如下: <?php $id = $_POST['id']; if($id==001){ echo json_ ...
- ExtJS是一种主要用于创建前端用户界面,是一个基本与后台技术无关的前端ajax框架。
ExtJS是一种主要用于创建前端用户界面,是一个基本与后台技术无关的前端ajax框架.
随机推荐
- 《C++ Primer(第五版)》知识巩固
运行平台:ubuntu 12.04/GCC 4.8.0 第二章:基本内置类型 1.decltype类型指示符 当我们从表达式的类型来推断要定义的类型时,可以使用decltype()来解析:declty ...
- Delphi对Word的基本操作
一.Delphi程序启动Word 采用CreateOleObjects的方法来启动Word,调用VBA代码,具体实现过程为: 首先使用GetActiveOleObject('Word.Applicat ...
- 案例20-页面使用redis缓存显示类别菜单
1 准备工作 1 需要导入所需要的jar包. 2 启动windows版本的redis服务端 3 准备JedisUtils工具类的配置文件redis.properties redis.maxIdle= ...
- idea开发环境中maven控制台乱码解决
在pom文件中加入 红色那行, <properties> <project.build.sourceEncoding>UTF-8</project.buil ...
- 【ExtJS】FormPanel 布局(二)
周末2天好好学习了下布局,现在都给实现了吧. 5.border布局: Border布局将容器分为五个区域:north.south.east.west和center.除了center区域外,其他区域都需 ...
- Go语言备忘录(3):net/http包的使用模式和源码解析
本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导! 转摘本文也请注明出处:Go语言备忘录(3):net/http包的使用模式和源码解析,多谢! 目录: 一.h ...
- 《Maven实战》关联实际工作的核心知识
通读了<Maven实战>这本书,由于在实际的工作中,对其有一定的操作上的经验.因此,再回头去通读这本书,就能够更加精准的把握里面的核心知识了. 以下我主要从两点去介绍之—— 1> m ...
- js校验数字是否为小数
js校验数字是否为小数: function checkDot(c) {c = parseFloat(c); -]?[-]*\.[-]*[-]+$/; return r.test(c); }
- 2017年11月27日 C#MDI窗体创建&记事本打印&记事本查找、自动换行
MDI窗体第一个父窗体 把属性里的IsMdiContainer设置为true就可以了 父窗体连接子窗体 //创建一个新的类,用来连接别的窗体,并且别的窗体为唯一窗体 List<Form> ...
- mysql三表联合查询
-- SELECT d.userId, d.userPhoNum, a.orderId, a.productType, b.courseId, b.courseName, c.payJe -- FRO ...