React语境下前端DDD的长年探索经验

‍‍‍‍‍

导语 | 腾讯前端工程师唐霜在React项目中,尝试使用DDD方法论为业务对象建模,其所在团队形成良好的业务沟通规范和业务逻辑沉淀流程,构建了更加稳固的业务系统。作者将多年的积累探索经验总结分享出来,从对业务的思考、react项目的特征出发,阐述在项目中进行的前端DDD探索。欢迎阅读和交流。

前言

我们所处的业务团队服务于腾讯某投资部门,系统涉及的投资相关业务在国内具有典型意义,是覆盖一级市场、二级市场、基金的多品类全流程的投资系统。其中包含了对投资项目本身的业务处理,也包含了投资流程的工作处理(类比OA系统),还包括了其他大部分系统需要考虑的技术建设(例如基于安全性考虑的数据、合同文件、电子签等)。方方面面使得我们在建设这套系统时除了要有技术本身之外,还要有对业务的掌握能力。虽然技术团队跟随产品团队完成技术开发,但是在一些具体问题上如果对业务不熟悉,很难真正处理好开发过程中的某些具体的逻辑代码。而对于投资系统而言,准确实现业务是最基本的要求,否则带来的风险可想而知。这也给我们技术团队带来了巨大挑战:如何在如此复杂的系统中,比较合理地掌握每一个技术细节背后的业务逻辑,确保业务实现的准确呢?

我们把目光瞄准了DDD,它是由Eric Evans在Domain-Driven Design: Tackling Complexity in the Heart of Software(2004)总结提出的一整套概念和方法论。我们尝试探索在前端践行DDD的可能性。DDD的理念,可以帮助我们团队合理的形成良好的业务沟通规范和业务逻辑沉淀流程;DDD在技术方面的指导,帮助我们构建更加稳固的业务系统。基于这样的想法,我们开启了相关的探索。

什么是DDD?

Domain-Driven Design是从实际业务出发,站在解决领域问题的角度去思考和设计系统的方法论。它包含了两个方面的内容:沟通方法论和研发方法论。

Eric从另一个角度让我们技术人员重新思考自己的工作方式,对于面临复杂业务的系统开发的技术人员,不能一上来就开始进行系统设计和代码实现。他们要做的第一步,应该是和领域专家一道,基于某种大家都能理解的专业语言,构建出一套领域模型,这也就是我们所讲的“沟通方法论”。而这个步骤,是我们现在的很多开发团队完全没有考虑过的事情。首先,这里的“领域”(Domain)是指一类事物的集合,比如我们常讲的金融领域、通信领域、数学领域等等。你可以很明显的感知到,“领域”意味着“边界”,意味着某些“共性”和某些“特性”。领域专家就是这些特定业务领域的资深工作者,对该领域的业务非常了解,是该领域的行家。要让技术人员和领域专家坐在一起构建出对应业务的领域模型,必须摒弃他们各自在自己工作范围内的狭义概念,大家找到一种相互都可以理解的概念表达方式,来最终确认各自提出的问题和回答对方都能准确理解。这在我们看来,就是世界上最高效的沟通方式,没有之一。

我们可以使用DSL(领域专用语言)来完成这一沟通,领域专家和技术专家基于统一语言构建领域模型,可想而知,这一模型是无法直接作为代码运行的。开发人员要做的,是基于该领域模型,用代码将其准确实现。而Eric充分考虑到一个系统在技术实现时和现实世界存在差异,提出了非常多的技术建模方案(Scheme)和配套的架构理念,让技术专家在和领域专家构建领域模型时,有意无意地引导领域专家一起构建出更加符合后期技术实现的模型架构体系。

图1 统一语言、领域模型与代码实现的关系

因此,DDD不是某种架构而是一种设计。不同的业务团队,可以基于这种设计实现一种符合DDD的架构,来帮助自己更好的构建自己的系统。阅读上书过程中我们反复意识到,这本书是写给技术人员的看上去都是技术字眼但实际上是在传播工作方法的一本方法论书籍。如果有这样的想法,那么在阅读此书,特别是后面的章节时,就不会越来越混乱。因为你不再是在这本书中去找到某些具体的实现或架构,而是在思考如何去发现规律。它不能帮助你完成某个架构,但是它可以帮助你思考如何设计这个架构。

现在,回到我们的业务中,为什么DDD正好可以帮助解决复杂业务的系统设计呢?

业务的技术语义

1)技术上讲,什么是业务?

业务(Business) 专指商业活动,是实现企业生产到利益回收的一个环节。它的总和,构成了该企业盈利活动的整个流程。一般而言,我们所指的业务是企业商业活动中的一个部分,有的甚至小到一个环节,例如“结算”这个环节。业务系统则是辅助这些商业活动的计算机在线系统,以信息化的形式管理和决策企业的商业活动(理论上没有业务系统企业也能运转,但信息化社会没有业务系统会让企业寸步难行)。

业务模块:是以业务系统的建设者(领域专家、系统工程师等)的角度看待业务系统时,将庞大的业务系统,按照某个业务活动的边界,进行划分的某个单元。但是技术上,一般一个模块还是粒度比较大的单元。一般而言,业务模块囊括了系统关于该业务的所有内容,且和其他业务有明显的界限,理论上,可以在脱离了其他业务模块的情况下独立运行。

业务逻辑:是只用代码实现的真实业务的规则映射。简单说,一个业务中,存在什么逻辑,可以通过在纸上画出不同业务对象之间的联系和约束,并将这些联系和约束一条条列出来,形成一个列表,而这列表中的每一条,就是一条规则,这些规则的总和,就是这个业务的全部业务逻辑。既然是一条条的规则,那么我们就可以在代码层面对规则进行管理。对于前端开发者来说,最熟悉的规则管理,莫过于路由管理。

业务系统:本质上是基于人机交互的管理工具。不同角色在系统中管理的内容不同,但总体上,是进行基于业务上下游数据进行职权范围内的业务管理活动。

从个人的感官上,业务系统的开发是最复杂的,为什么呢?我们前端开发对不同的前端应用的开发有非常明显的感知差别。把前端应用归纳为3类,分别是业务系统、通用应用、工具类应用。它们虽然都是前端应用,但是在开发工作中呈现出来的关注度却非常不同。

表1 业务系统、通用应用和工具类应用的横向对比

由于开发过程中关注的重点的不同,在开发时解决问题的思路也迥异。

图2 业务系统、通用应用和工具类应用开发时解决问题的思路对比

这种解决问题的思路差异决定了我们在开发工作中方法论的差异。而业务系统涉及对象多,联系密,维度广等特征,导致在实际开发时,其复杂度比其他类应用高很多。(但在难度上往往并不高,业务系统常常不需要实现非常多需要基于特殊技术才能实现的功能。)

总结起来,业务系统之所以复杂的因素,我们认为主要有以下一些方面:UI交互的不复杂与反复实现的矛盾;特定业务逻辑对原有架构的挑战;可持续维护(长期稳定性)与破坏性共存;模块与模块间数据、事件通知耦合;业务准确性要求高与需求多变之间的时间争夺;前后端耦合

基于这些因素,我们需要谨慎对待开发的每一个功能。因为对于业务而言,这一功能可能会在5年甚至10年后还要继续使用,而如果功能不够健壮,或者设计之初没有充分考虑业务发展后所需要的扩展能力,很有可能在之后的迭代中给自己挖下巨大的坑。这是很多开发普适性强的大众类产品的开发者们无法体会到的。

前端语境下DDD的价值主张

1)前端需要DDD吗?

这个问题可以细化为,前端需要与业务方领域专家进行沟通吗?在设计系统或功能时,需要基于沟通结构的领域模型展开完成模块的搭建吗?我们需要在前端建模吗?我们需要在前端分层吗?等等。我无法给出标准答案,但我们思考:如今的前端开发,特别是类似和我们一样的业务系统项目中,如果我们仍然是按照设计稿完成实现,是否能准确满足需求?或者说,对于前端开发者而言,我们是否有必要掌握业务细节,通过一整套的方法论,在代码中将这些细节管理起来?我们想从另一个角度为前端开发者描绘他的工作场景:

图3 前端开发人员的沟通域

上图中,我们描述了前端开发者在整个业务开发过程中所处的境地。黑色圆圈表示在这过程中出现的角色,黑色实线表示与前端沟通时两个角色的联系,虚线圈表示当问题发生时参与该问题讨论的相关角色和内容。可以看到,前端开发者在与不同的角色进行沟通时,常常需要切换思维和角度,他所要面临的问题,所处的境地,如果没有一套方法论支撑,很难在繁杂的工作中不迷失。

2)前端可以DDD吗?

包括Eric著作在内的DD作品,讨论DDD多半是在后端环境下。作为思想武器,DDD可以帮我们思考如何去设计业务系统架构,前端是否只需要基于后端接口输出渲染界面就可以了呢?或许,前端根本无法按照DDD进行设计?并不是。

但随着业务的深入,我们发现如今的业务系统(基于B/S架构),渐渐的靠近重客户端的方向(类似C/S架构中的C端)。前端代码中的业务逻辑逐渐越来越丰富,甚至很多逻辑只能由前端完成,后端无能为力,或者前后端一起推进时成本更高。运行在C端的前端代码,承载的业务逻辑与UI混杂在一起,导致组件或controller的代码被撑的越来越大,越来越无法维护。我们曾指出过:基于vue写的应用,当业务足够复杂时,你根本无法区分vue组件中哪些是业务的,哪些是交互的。而这种情况持续下去,便是组件的不可维护,代码的腐化。

作为前端开发者我们需要思考,如何让我们的代码组织更健壮,更能体现业务的核心逻辑?基于沟通域的思考,我们认为,前端所关注的主要内容包含:业务模型、数据服务、UI交互组件体系。

图4 前端关注的主要内容

前端开发注定不可能只关注业务模型。在前端特别是web领域,除业务之外关注由后端吐出的数据和界面交互是一件必须的事。甚至有这样一种情况,产品需求文档中指出,“用户点击该按钮时,需要弹出二次确认窗口,点击确认后该签署流转为已完成”。在这个描述中,用户点击按钮弹出确认对话框,是否属于业务逻辑呢?从传统后端的角度,当然不属于。但是,如果去掉这个交互逻辑,能否完整表达这一业务过程呢?这是一个值得思考的问题,前端如果要实施DDD,不可能照搬后端的实践。前后端要解决的问题大不相同:

表2 前后端要解决的问题对比

这也就意味着,在前端语境下,我们关注的内容范畴比后端还要大。

图5 前后端DDD范畴对比

这些思考反过来促使我们问自己:对于前端而言,DDD的价值是什么?前端工程师需要重新审视自己的这一职位,从视觉交互的实现者陷阱中跳出来,正视自己作为工程师的一面。解决业务项目中的关键问题,持续的与后端一道提供稳定可靠的业务服务,这才是前端工程师的本职。而这些都是我们从DDD中发现的,对于前端而言,实施DDD有肉眼可见的好处:稳定的业务知识体系;可传承的代码体系;脱离UI的单元测试;跨端开发、多端共用的便捷性;明确的团队分工;需求变更的快速响应;持续敏捷

这些好处对于需要持续迭代的项目团队而言,非常有价值,特别是需要持续支撑业务团队在下线完成更多以前无法完成的任务的价值,是业务系统最不可替代的。

前端领域建模

我们在腾讯投资系统中践行前端建模已经超过两年,这期间的收益与之前一股脑通过数据绑定的方式相比,可谓天差地别。在此前的开发中,我们虽然觉得麻烦,但是到底能把功能堆砌出来。但是随着时间的推移,一些稍微老一点的逻辑就再也不敢动。所谓牵一发而动全身,一点改动,重则可能带来整个业务流程的瘫痪,轻则影响相关模块的正确呈现。归根结底,在于我们的系统是线性的(可以回头看上文线性思维属于哪种类型的应用),我们开发团队的知识是不可复制的,一位开发者负责一个业务功能,只有他知道这业务里面的逻辑到底有哪些;其他开发者即使重新研读代码,也很难梳理出准确全面的业务逻辑。如果需要重构,只能按照代码逻辑慢慢复制。

两年前,我们开始思考我们所遇到的大部分业务场景的共性,结合开发中遇到的痛点,我们急需有一种方式,可以对我们在应用界面的切换、数据的流转中,有一种把握业务核心逻辑的能力。我们开始了建模探索。经过两年的积累,我们现在才能将这些经验总结出来。

1)思考单一业务的核心与边界

我们在开始用代码建模之前,我们需要和领域专家(也就是业务方,或者与我们对接的产品人员)开会讨论某一业务的核心概念有哪些、有哪些可能的事件会发生、它怎么和其他概念发生联系?我们在记事本上梳理出与要开发的业务发生联系的各个概念以及它们之间的连线,这样我们基本掌握了有关这一业务的大部分知识。

当开始用代码表达这些知识的时候,我们遇到的首要问题是:哪些是必须的,哪些是与该业务关联但非必须的,我们该以何种形式表达这些概念?

图6 前端领域建模的首要问题是划清核心与边界

使用OOP的范式进行建模是比较常见且直接的方式,通过建立形形色色的class来创建一个又一个的对象。关键的问题在于,这些对象的核心是什么,边界又在哪里?这些都是我们必须在一开始考虑清楚的。

2)建模方法

DDD给我们提供了一些具体的建模方案,例如ENTITY、VALUE OBJECT、SERVICE、AGGREGATE、REPOSITORY、FACTORY等等。

图7 DDD的建模方案

在建立对象模型时,我们根据对象在业务中所表达的意义,选择其中对应的方案来进行建模。例如,我们为一个投资对象进行建模,首先需要区分,投资对象的边界在哪里?比如一个投资中我们可能要走一些审批流程,会产生一些特殊数据,它们是否属于该投资对象的核心?其次,我们需要了解一个投资它都由哪些资源构成,比如其中有一个字段叫做“投资主体”,投资主体即在投资中作为合同上付钱的那一家公司出现,它自己本身也具有自己的一些属性,那么投资主体是否是投资这个对象的核心资源呢?我们应该处理投资主体?是用一个普通的JS对象来表达,还是创建一个子模型来管理?类似这样的思考,在我们的建模过程中常常发生,而且有的时候,我们并不能一击即中。有的时候需要在迭代更新中进行调整。

在长期摸索实践中,我们形成了一套标准的建模步骤(如下图)。在这一标准步骤中,我们抽象出了建模的基类,基于这些基类进行extends,根据每个对象的特征,选择性使用某些建模手段。具体如下:

图8 前端领域建模步骤

在上图中,完整呈现了我们在代码层面实现领域模型的所有素材。建模的最小单位是Attribute即原子属性。原子属性描述一个字段(Field)或属性(Property)的具体属性,即元数据中的某一项。Meta是最小的模型,由Attribute组成,即关于单个字段的模型。Attribute和Meta不单独存在,它们是颗粒度最小的素材,Attribute一般不可复用(或者说没必要),Meta可限制性复用。

在上图中,Model和本文讲的领域模型概念稍有不同,Model是代码层面可以表达完整业务对象的最小单位,它由Meta和其他资源组成。其他资源包括代码层面的方法(概念上的Factory)、静态属性(概念上的Value Object)等。另外,和后端模型最大的不同,前端模型必须为UI视图层留足空间,我们为Model提供了响应式能力,以便在与UI结合时,可以观察到模型内变化,以触发界面的更新。具体从代码层面,我们用Mobx进行举例。

图9 两个简易模型

上图中,我们创建了两个模型Todo和TodoList,其中TodoList是对Todo的聚合,我们会在下文讲。这是两个再普通不过的class进行建模,现在的问题是:如果我们在前端使用这两个模型,我们无法与我们已有的UI框架配合使用,比如在react中或vue中使用。有没有一种方法,可以让我们可以以最小的代价扩展模型的能力呢?我们使用mobx这个库对模型进行改造

图10 基于Mobx的简易模型

使用mobx提供的装饰器,我们以最少的改动增强模型。这一改动几乎不会对原始模型的原有阅读产生任何干扰,但却使得该模型是可被观察的。再配合mobx的工具,就可以与UI框架配合,就可以在应用中无缝对接。

图11 基于Mobx的模型和UI对接

与后端框架不同,后端以Controller作为入口,对模型和视图进行消费。而前端主要的消费者是UI框架,视图是入口。模型需要被实例化为UI状态后消费。因此,像Mobx这样提供与UI框架对接的工具,是比较合理的设计方式。不过,这已经超出建模本身的话题,此处只是延伸。

我们回到建模步骤,在前端建模过程中,我们认为最难的一点在于如何划定一个模型的边界。很多场景下,一个业务对象的某个逻辑,又依赖于另外一个业务对象的某个信息,跨模型的关系如何去描述?实践中,我们通过嵌套模型来表达,也就是聚合Aggregate。通过Aggregate,我们把高于业务对象本身的逻辑进行梳理。一般而言,一个聚合根囊括了该业务的所有实体,也就是说,它界定了该业务在核心实体上的边界,该业务只在这些对象之间运转。

但业务的运转不仅只包含实体,也包含业务的流转逻辑。概念上的领域事件,以及背后的领域服务——业务对象在该业务运转过程中发生变化。对于前端而言,与后端对接数据也是很关键的一个点,我们基于Respository方案建立可响应式的接口数据管理方案,创建Service单例来在整个应用中响应事件,流转业务对象的状态,从而让界面跟着业务的流转而发生变化。

以上的建模成果被组织在一个Module(模块)中,一个模块不是一个单一文件,它是有特定目录结构的一系列文件组成的功能单元。不过需要注意,这里的Module和我们应用开发中通常讲的Module概念上稍有不同,这里的Module主要是对领域模型完成代码实现后组成的组织单元。本质上,它还是建模的一部分,和我们通常意义上的业务模块(包含UI等)有所区别。

在React项目中设计业务模块

作为优秀的视图驱动库,react实现了完美的从数据到视图的映射。对比vue的优势在于,它对数据本身的形式没有要求。在vue中,你给定的数据包含getter(Object.defineProperty)或由Proxy创建抑或由特定的class实例化而来,会导致vue丢失部分响应式的能力。而react中没有这层限制。因此,在我们实施DDD的过程中,更有亲和力。

react虽然解决了数据到视图的映射,却没有在反过来的视图到数据的映射上提供方案。我们在react中,会在onClick等基于它内置的合成事件系统中执行回调函数来完成视图到数据的处理,然而这种处理显然是不利于建模的。因此,在react本身之外,我们创建了一套基于RxJS的单例服务来处理来自交互的事件与模型层的绑定。在具体的react组件中,我们只暴露给组件它渲染和交互需要用到的数据(状态)和事件接口。

我们称这种独立于react组件本身之外的体系为“无视图交互模型”。该模型在撰写时,站在视图层的角度处理业务模型的实例化、修改等,处理来自视图层的交互事件等等,但是在该模型中,没有任何具体的UI实现。

如果你还有印象,你可能还记得我们在前文问到“用户点击按钮弹出确认对话框,是否属于业务逻辑呢?”。在“无视图交互模型”的设计下,我们可以将“用户点击按钮弹出对话框”这一交互转化为模型的一个部分,在该模型中,它提供了用户点击动作的接口,而该接口处理时会流转模型内持有的其他具体业务模型,进而达到需求文档中所描述的这一要求。但在具体UI中,这个按钮长什么样子、在哪个位置、弹出框样式,都需要在UI层(组件中)具体去实现。而在实现时,开发者不需要考虑该按钮点击事件的具体效果,只需要调用模型接口即可

截止到这一步,你会发现,在我们的业务模块中,还没有任何有关UI的具体实现,但是,我们几乎已经把需求文档中有关业务的实体对象、某些与业务流转相关的交互,都用代码表达出来了。

图12 项目中实现业务模块的具体流程

从需求分析,基于统一语言建立领域模型,到具体代码去实现领域模型,再到建立无视图交互模型。到这里,我们有关业务的建模基本完成,但我们还没有任何的界面效果。对于以前我们拿起react就开撸的开发方式而言,简直不可思议,怎么界面都还没有就已经一大堆代码了?

我们的系统包含PC端、APP端和微信内嵌H5,APP端和H5端交互基本相同,稍有些细节差异,但PC和移动端的差异大的就不止一点点。然而,对于某一业务模块而言,两端的交互虽然不同,却在业务逻辑上(包含基于业务逻辑的交互逻辑)是必须一致的,否则业务本身就会出现问题

上图中虚线的右侧即我们多端共用的部分。业务模型在各端复用并非完全为了代码重用,更多的是为了保证业务的一致性。而虚线的左侧,则是我们常见的react组件体系的编写。对于不同端,我们的组件很少有能复用的,基本上各自一套,但却能够保证在业务逻辑上没有出入。一旦业务(数据和交互)被业务模型规范下来,对于不同端的视图层,就是换皮操作(有点夸张)。

这一体系给了我们很多想象的空间,虽然我们团队目前并没有再深入,但是从个人的角度而言,我们可以在这一模式基础上,做到更容易的业务单元测试、基于后端输出的统一UI DSL、业务模块服务化、小程序等等。

基于DDD的前端应用架构分层

分层也是DDD的重要思想。在Eric原书中,他对系统分层做了梳理,同时,在行业中,后续继承者们对该分层进行了扩展,主要包括:依赖倒置四层架构、六边形架构、Clean Architecture等。四层架构典型示意描述如图:

图13 遵循DDD的系统分层架构示意图

前端与后端不同的是,前端是胖UI瘦数据,前端不需要去考虑数据的存储读写所带来的一系列问题。但是,在整体的分层划分上,前后端具有一致性。

图14 遵循DDD的前端应用分层示意图

我们以前的前端应用开发也有分层的思想。但是分层实践很微弱,大部分情况下还是把所有逻辑杂糅到react组件和状态管理中。这也是我们以前的代码在开发完一段时间后可维护性就极速变弱的原因。而按照DDD的设计,我们将代码分层组织和管理。其中的核心层是领域层,这一层决定了整个应用的大部分代码逻辑,虽然在控制层和UI层也有一些逻辑,但是它们多是处理交互的,与具体的业务无关。它完整的描述了这一系统所反应的业务面貌。也正因如此,在多端复用(例如PC和APP两端写同一个业务)时,只需要重写UI层,而不需要再写其他层。

结语

复杂的业务系统无论前端还是后端,都面临着巨大的挑战。除了系统本身的功能之外,最大的风险在于业务逻辑需要被准确实现的同时,给研发团队的实现时间却很紧张。紧随而来的系统的代码在长时间迭代中,越来越难以维护。DDD从沟通和研发两个角度为我们提供方法论,是面对复杂业务时的思想工具。它启发了我们,让我们在工作方式上不再莽撞的马上开始写代码,而是先与业务方深入沟通、掌握业务的知识网络,基于统一语言进行领域建模,之后再来考虑如何在react中结合,开发整个应用。

在前端语境下,由于前端关注的内容的异质性,我们不可能直接照搬后端的DDD实践,不得不探索前端DDD的特殊途径。基于DDD的设计,我们的架构剖离出不同的分层,在领域层和控制层完完全全描述了业务需求。因此,可以不考虑UI层就可以进行业务编程,并实施有效的业务单元测试。而对于跨端复用而言,就是换UI壳的处理,内在的业务逻辑代码可直接复用。

以上是我们在前端DDD话题下的探索。当然,DDD不是唯一的选择,它给了我们启发,也允许我们在它的基础上持续颠覆。欢迎在评论区分享交流。

你可能感兴趣的腾讯工程师作品

| 由浅入深读透vue源码:diff算法

| 优雅应对故障:QQ音乐怎么做高可用架构体系?

| QQ浏览器是如何提升搜索相关性的?

从Linux零拷贝深入了解Linux-I/O

技术盲盒:前端后端AI与算法运维工程师文化

后台回复“DDD”,获得更多DDD阅读材料。

?关注我并点亮星标?

工作日晚8点 看腾讯技术、学专家经验

点赞|分享|在看 传递好技术

本文转载于网络 如有侵权请联系删除

相关文章

  • Android平台GB28181设备接入端对接编码前后音视频源类型浅析

    前言今天主要对Android平台GB28181设备接入模块支持的接入数据类型,做个简单的汇总:编码前数据(目前支持的有YV12/NV21/NV12/I420/RGB24/RGBA32/RGB565等数据类型),其中,Android平台前后摄像头数据,或者屏幕数据,或者Unity拿到的数据,均属编码前数据;编码后数据(如无人机等264/HEVC数据,或者本地解析的MP4音视频数据);拉取RTSP或RTMP流并接入至GB28181平台(比如其他IPC的RTSP流,可通过Android平台GB28181接入到国标平台)。支持的音视频数据类型编码前音视频数据编码前音视频数据支持的类型接口如下,除了常规的音视频数据外,我们还设计支持了实时动态水印,比如实时文字水印、图片水印,音频这块,我们实现了混音机制,支持2路混音输入:/* *SmartPublisherJniV2.java *SmartPublisherJniV2 * *WebSite:https://daniusdk.com *Github:https://github.com/daniulive/SmarterStreaming * *

  • mysql 主从1146_mysql 主从复制1146错误处理办法

    大家好,又见面了,我是你们的朋友全栈君。错误现象:Replicate_Wild_Ignore_Table:Last_Errno:1146Last_Error:Error‘Table‘mydb.test1146′doesn’texist’onquery.Defaultdatabase:‘mydb’.Query:‘insertintotest1146values(‘bigdiao’)’方法一、在slave上重建缺失的表mysql>stopslave;QueryOK,0rowsaffected(0.01sec)mysql>createtabletest1146(namechar(25));QueryOK,0rowsaffected(0.00sec)mysql>startslave;QueryOK,0rowsaffected(0.00sec)方法二、在slave的my.cnf文件中添加一个参数,然后重启slave##my.cnf文件中添加此行内容replicate-ignore-table=mydb.test1146##重启slave/etc/init.d/mysqldres

  • 编写一个程序,将 a.txt文件中的单词与b.txt文件中的单词交替合并到c.txt 文件中,a.txt文件中的单词用回车符分隔,b.txt文件中用回车或空格进行分隔

    importjava.io.File; importjava.io.FileNotFoundException; importjava.io.FileReader; importjava.io.FileWriter; importjava.io.IOException; publicclassnewManagerFile{ Stringwords[]; intpos=0; publicnewManagerFile(StringfileName,charspilt[])throwsException{ Filefile=newFile(fileName); FileReaderfr=newFileReader(file); charbuf[]=newchar[(int)file.length()]; intlen=fr.read(buf); StringbufString=newString(buf,0,len); StringBuffertemp=newStringBuffer(""); temp.append(spilt[0]

  • 解决 GraphQL 的限流难题

    在上一篇微服务架构设计模式的总结[1]的结尾,提到了GraphQL的问题。之前在某公司落地查询API方案时,我们没有选择GraphQL,是因为:GraphQL对于数据用户来说有一定的学习成本GraphQL的稳定性很难做,难以限流学习成本倒也不是特别大的问题,程序员们本能上还是喜欢接触新东西的,这会让他们有一种虚假的获得感。关键是这个限流问题,是真的很难做,开放GraphQL的API,就像你在MySQL上直接开了一个SQL接口一样,用SQL可以一次只查一条数据,也可以一次查一亿条数据。所有查询都只用主键做条件查一条数据,那单MySQL实例可以搞出百万QPS。如果一个查询要查一亿条数据,那这条查询就把MySQL实例的CPU/内存打爆了[doge]。GraphQL里类似的情况是这样:querymaliciousQuery{ album(id:”some-id”){ photos(first:9999){ album{ photos(first:9999){ album{ photos(first:9999){ album{ #...Repeatthis10000times... } } }

  • KonaJDK 助力微服务国密算法使用特性一览

    导读本次TencentKona8版本更新到8.0.4,在同步到社区版本8u272的基础上,还有哪些新的特性呢?本文为您一一介绍:Updatetojdk8u272TencentSMProviderforSM2/SM3/SM4supportParallelFullGCforG1ParallelHeapInspectionforG1andParallelScavengeHeapOtherperformanceenhancementBugfixes作者介绍臧琳腾讯云中间件JVM工程师主要负责腾讯云中间件JDK定制化开发及优化工作专注于JVM中内存管理、Runtime运行时以及执行引擎在云业务中的性能分析及优化国密SM2/SM3/SM4算法支持JCEProvider TencentSMProvider随着国密算法等商密算法国家标准的推出,云上客户对于Java版本的国密算法需求越来越多。KonaJDK8内置了国密算法的JCEProvider,Java用户只需要使用JCEAPI即可使用国密SM2/SM3/SM4算法。下面利用微服务加密信息传输为例,具体说明TencentSMProvider的使用方法

  • Docker的简单使用

    安装在官网下载对应系统的安装包https://hub.docker.com/?overlay=onboarding配置加速器#官方 https://registry.docker-cn.com #Azure中国镜像 https://dockerhub.azk8s.cn #中国科学技术大学 https://docker.mirrors.ustc.edu.cn #腾讯 https://mirror.ccs.tencentyun.com #网易 https://hub-mirror.c.163.com复制获取镜像命令格式为:dockerpull[选项][DockerRegistry地址[:端口号]/]仓库名[:标签]复制例如:dockerpullubuntu:18.04复制运行dockerrun-it--rmubuntu:18.04bash复制列出镜像dockerimagels REPOSITORYTAGIMAGEIDCREATEDSIZE centoslatest0584b3d2cf6d3weeksago196.5MB redisalpine501ad78535f03weeksa

  • GPT迭代成本「近乎荒谬」,Karpathy 300行代码带你玩转迷你版

    新智元报道来源:reddit编辑:小智【新智元导读】最近,特斯拉AI总监Karpathy开源了一个名为minGPT的项目,用300行代码实现了GPT的训练。没有OpenAI的超级算力,该如何调整GPT这类语言模型的各种超参数?上周AndrejKarpathy发布了一个最小GPT实现的项目,短短一周就收获了4200星。从代码来看,他的minGPT实现确实精简到了极致,利用Karpathy的代码,你只需要实例化一个GPT模型,定好训练计划就可以开始了,整个实现只有300行PyTorch代码。但是最有趣的部分是300行代码背后的故事。特别是在说明文档末尾,他解构了GPT-3的各种参数:GPT-3:96层,96个头,d_模型12,288(175B参数)。GPT-1-like:12层,12个头,d_模型768(125M参数)GPT-3使用与GPT-2相同的模型和架构,包括修改后的初始化方法等。在transformer层使用交替密集和局部稀疏注意力,类似于稀疏Transformer。前馈层是瓶颈层的四倍,即dff=4*dmodel。所有模型都使用nctx=2048token的上下文窗口。Adam的

  • 【iOS】修改checkra1n+chimera环境(chimera1n)

    就在前不久coolstar大佬发布了chimera1n的脚本咱也不是大佬,只能在危险的边缘试探只有无限接近死亡才会知道怎么操作小声bb:今天我自己都不知道rootfs了几次了 因为coolstar的那个脚本下载太慢了(小编手机开的热点,公司宿舍没宽带)所以小编找了另一套比较简单的脚本 但是会损坏cydia包,也就是后期装cydia的时候可能有点麻烦,这个的话下个教程再讲吧,随缘本篇教程仅讲述小编本人更改越狱环境的步骤教程仅限动手修复能力好的人操作,出问题概不负责 小编仅测试8p13.3.1系统 其他设备系统自行测试理论支持13.0-13.4.1所有可以用checkra1n越狱的设备感谢嘻哈大佬做的部分技术指导下面教程以macOS系统为例开始首先需要安装usbmuxd,否则后续会报错打开电脑终端输入brewinstallusbmuxd复制扩展如果报错请去brew官网安装官网:https://brew.sh/index_zh-cn复制然后换源(如果你觉得自己网络够好的话可以跳过这一小段)执行下面这句命令,更换为中科院的镜像:gitclonegit://mirrors.ustc.edu.cn

  • DLL/OCX文件的注册与数据执行保护DEP

    注册/反注册dll或ocx文件时,无论是用regsvr32还是DllRegisterServer/DllUnregisterServer,可能会遇到【内存位置访问无效】的问题:此时把操作系统的数据执行保护(DataExecutionPrevention,下称DEP)彻底关掉,重启,应该就能解决问题。操作:NT6.x系统:运行  bcdedit/setnxalwaysoffNT5.x系统:修改%systemdrive%\boot.ini文件,将当前操作系统条目的/noexecute参数的值改为AlwaysOff,若没有则添加。若是多系统,要注意修改到正确的条目本文主要是讨论,作为开发者,当需要在自己的程序中注册dll时(反注册的情况一样,下文只拿注册说事,其实适用于所有受DEP影响的问题),如何避免改动系统DEP,避免重启地把问题解决掉。其实这个问题的关键是,执行注册的进程是否启用了DEP,启用就不能注册,关闭就能,跟系统DEP没有直接关系,但进程DEP受系统DEP的影响。来自系统DEP的原因系统DEP策略有4种,每种策略下对进程DEP的影响如下(注意,64位程序总是启用DEP,且不可禁

  • 每日算法系列【LeetCode 121】买卖股票的最佳时机

    题目描述给定一个数组,它的第i个元素是一支给定股票第i天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。注意你不能在买入股票前卖出股票。示例1输入: [7,1,5,3,6,4] 输出: 5 解释: 在第2天(股票价格=1)的时候买入,在第5天(股票价格=6)的时候卖出,最大利润=6-1=5。 注意利润不能是7-1=6,因为卖出价格需要大于买入价格。复制示例2输入: [7,6,4,3,1] 输出: 0 解释: 在这种情况下,没有交易完成,所以最大利润为0。复制题解这是【买卖股票的最佳时机】系列题目的第一题。这道题目要求只能买卖一次股票。所以最佳策略一定是挑一个最低的价格买入,再挑一个最高的价格卖出。但是还有另一个限制条件,那就是你得先买,然后才能卖出去。所以我们只需要枚举每个股票,把它当作卖出的那一只股票,然后只需要求出它之前价格最低的那一只股票就行了。价格最低的股票可以用一个变量来进行维护,然后枚举所有卖出的股票,减去它之前价格最低的那一只股票,然后和最优答案进行比较,最终就能得到最多能赚多少钱了。最终答案就是:时间复杂度是。代码p

  • Kubernetes集群环境常见问题解决

    ”本文主要分享了k8s集群环境下,镜像不能自动拉取、容器执行错误、镜像导入导出、集群崩溃常见问题解决“ 1、Kubernetes集群环境下各个node镜像不能自动拉取 一般情况下遇到这种情况下,比较笨的办法是可以通过人肉的方式登录到每个节点通过dockerlogindockerpull的形式获取镜像,想象下,如果成百上千个节点...... 一般出现此类问题在私有仓库和云厂商分别解决方法如下。 专有镜像的集群,比如一些个人或者公司搭建私有仓库,以下是解决该问题步骤 dockerlogin[server]针对要使用的每组凭据运行。这将更新$HOME/.docker/config.json;$HOME/.docker/config.json在编辑器中查看,以确保它仅包含您要使用的凭据;将当前节点.docker/config.json复制到其它节点,命令如下;fornin$nodes;doscp~/.docker/config.jsonroot@$n:/var/lib/kubelet/config.json;done复制4.创建pod测试拉取镜像;kubectlapply-f-<<

  • 共享租车的理想、现实和未来

    最近几个月大量融资新闻来自于共享经济领域,创业者们正在绞尽脑汁将一切物品、资源和服务通过互联网共享给他人。共享经济在硅谷产生了AirBnb和Uber两家现象级公司,在中国产生了百亿美元级公司滴滴快的,围绕汽车的共享除了Uber为代表的车位共享之外,还有一类则是整车共享模式的P2P租车,该领域的凹凸租车和PP租车分别拿到了经纬和IDG两家知名投资机构领投的融资,正在成为下一个巨头标的。不持有的用车方式共享租车平台解决用户只租车不租司机的“干租”需求。资源一侧接入了拥有多余车辆或者车辆某个时段闲置的车主,需求一侧则接入了需要租车自行驾驶的用户。这些需求在过去由神州租车为代表的传统汽车租赁公司负责解决,共享租车平台除了更具用户体验思维之外,还有其模式的优越性,同时野心更大。共享租车平台将私家车租赁出去,释放了闲置汽车以及车位等资源,提高了汽车整体利用效率。其接入的私家车“闲着也是闲着”,通过平台租赁出去成本将比传统租车方式更低,再加上减少了门店成本,所以共享租车在价格上具有绝对优势,大概低30%-50%。这意味着,高频率、长时间租车成为可能。正是依赖这一优势,共享租车平台想要重新定义汽车租赁

  • 如何在Rocky/Alma Linux 9 上安装 Java 17?

    在本指南中,我们将探讨如何在RockyLinux9中安装JavaRuntimeEnvironment(JRE)和JavaDeveloperKit(JDK),本指南也适用于任何基于RHEL9的系统。许多软件都需要Java和JVM(Java的虚拟机),包括Tomcat、Jetty、Glassfish、Cassandra和Jenkins。Java是一种高级的、基于类的、面向对象的编程语言,旨在尽可能减少实现依赖项。Java是由SunMicrosystems(现在是Oracle的子公司)于1995年开发的。JamesGosling被称为Java之父。先决条件最新的RockyRockyLinux9系统具有sudo访问权限的服务器或用户的root访问权限从服务器访问Internet步骤概览确保服务器是最新的安装java测试安装切换多个Java版本配置环境变量确保服务器是最新的在继续之前,让我们确保我们的服务器是最新的,并且所有的包都是最新版本,使用这些命令来实现这一点:sudodnf-yupdate复制如果有要升级的软件包,上述命令可能需要几分钟。让我们也安装一些我们可能需要的常用软件包,我使用v

  • 机器学习中的蛋白质组学

    鸟枪法蛋白质组学数据集肽识别的半监督学习 鸟枪法蛋白质组学使用液相色谱-串联质谱鉴定复杂生物样品中的蛋白质。我们描述了一种算法,称为Percolator,用于提高从串联质谱收集的可靠肽识别率。Percolator使用半监督机器学习来区分正确和虚假光谱识别,相对于完全监督的方法,正确地将肽分配给来自胰蛋白酶酿酒酵母数据集的17%以上的光谱,以及来自非胰蛋白酶消化物的高达77%以上的光谱。  Percolator的目标是对一组候选PSM进行排序,以最大化在目标错误发现率下识别的pep-tides的数量。我们的方法,我们称之为Percolator,分三个阶段进行(见算法1)。最初,我们使用一个未改组的和一个改组的序列数据库对光谱运行两次现有的肽识别算法。虽然我们选择使用从混洗序列中得到的诱饵来演示Percolator,但是我们的软件可以使用任何类型的诱饵,包括从反向数据库中生成的诱饵。对于每个频谱,我们根据每个数据库存储得分最高的PSM。我们将这些分别称为目标和诱饵PSM。对于每个目标和诱饵PSM,我们计算一个20个特征的向量,总结在表1中。这些特征在算法期间保持固定。我们将诱

  • HDU 6406 Taotao Picks Apples 线段树维护

    题意:给个T,T组数据; 每组给个n,m;n个数,m个操作; (对序列的操作是,一开始假设你手上东西是-INF,到i=1时拿起1,之后遍历,遇到比手头上的数量大的数时替换(拿到手的算拿走),问最后拿走几个) 每次操作是将p位变为q;问此时序列能拿走几个数;   思路:假设p位变了,不管变大变小,我们都得知道一件事,就是要找到在p之前最长的序列;   因为这个是不变的,可以预处理,所以说我们就在第i位置记录1~i的最大值,和最大取走的数量以及最大数的id,这样就可以知道p-1位置的信息就是1~p-1位置最大的数的信息,O(1)得到;   (如果q比1~p-1最大的数小,那么就将q变为那个最大的数,因为这样我们就可以不用分情况去查询)   之后就是要找到比q大的第一个数,我是用线段树维护的,找比q大的第一个数的位置;找到了之后你就会希望能得到以这个数为起点的最大可取数量,那么我们就要计算出每个位置开始可以得到的最大数量;实现方法就是倒过来遍历那n个数,每次查询i~n里比a[i]大的第一个数的位置cnt,然后假设记录数组为dp,那么就是dp[i]=dp[cnt]+1;没找到的情况就

  • struts2 自定义拦截,防止非法操作

    <packagename="defaults"extends="struts-default"> <interceptors> <interceptorname="login" class="com.zqgame.interceptor.CheckLoginInterceptor"/> <interceptor-stackname="myinterceptor"> <interceptor-refname="login"> <paramname="excludeMethods">validateLogin</param> </interceptor-ref> <interceptor-refname="defaultStack"/> </interceptor-stack> </interceptors> <!--设置所有Action自动调用的拦截器堆栈--> <default-interceptor-refname="myinterc

  • 部署到Linux系列教材 (七)- SecurityCRT - 常用操作

      步骤1:常用命令步骤2:切换目录步骤3:查看目录下的文件步骤4:查看当前所处目录步骤5:创建目录步骤6:创建文件步骤7:删除文件步骤8:删除目录步骤9:查看文件内容步骤10:其他常用命令 步骤 1 : 常用命令 使用控制台方式连接Linux,需要用到各种命令,这里列出了一些常用的简单命令,大家热热身 步骤 2 : 切换目录 cd/usr 切换到/usr目录下注: Linux的文件系统和Windows不一样,windows有c盘,d盘,e盘,Linux看上去就只有一个盘,/usr就是相当于硬盘上的usr目录。 步骤 3 : 查看目录下的文件 ls ls-lh ls用来遍历当前目录下所有的文件和目录 ls-lh遍历详细信息,如权限,所属用户,创建日期,大小等等信息 步骤 4 : 查看当前所处目录 pwd 有时候敲着敲着就忘记自己处于哪个目录下了,那么

  • 城市分级

    “新一线”城市为成都、杭州、南京、武汉、天津、西安、重庆、青岛、沈阳、长沙、大连、厦门、无锡、福州、济南等15个,它们或为直辖市,拥有雄厚的经济基础和庞大的中产阶层人群,以及可观的政治资源;或为区域中心城市,对周边多个省份具有辐射能力,有雄厚的教育资源、深厚的文化积淀和便利的交通;或为东部经济发达地区的省会城市和沿海开放城市,有良好的经济基础、便利的交通和独特的城市魅力。这些城市理所当然也是各大公司的战略要地。   二线城市为浙江宁波、云南昆明、河南郑州、吉林长春、安徽合肥、黑龙江哈尔滨、江苏常州等36个城市,按照传统的方式来描述,它们多数都是中东部地区的省会城市、沿海开放城市和经济较发达的地级市。从现代的城市意义上讲,这些城市往往有一定的经济基础,商业活跃度相对较强,对大公司、大品牌和优秀人才有一定的吸引力,它们也正在或者即将成为未来几年大公司布局的重点。   三线城市为海南三亚、海口、浙江绍兴、内蒙古鄂尔多斯(600295,股吧)、新疆乌鲁木齐等73个城市,它们多数都是中东部地区省域内的区域中心城市、经济条件较好的地级市和全国百强县,也包括一些西部地区的省会首府城市,它们的人口规模

  • linux usb简介

    参考书:《linuxdevicedrivers》、《usb2.0规范》《usb3.1规范》《usb白皮书》   以linux为例来说明usb系统。   先看一下usb蓝牙适配器的详细信息: $lsusb|grepCambridge Bus001Device006:ID0a12:0001CambridgeSiliconRadio,LtdBluetoothDongle(HCImode) $sudolsusb-s001:006-v[sudo]passwordforhost:Bus001Device006:ID0a12:0001CambridgeSiliconRadio,LtdBluetoothDongle(HCImode)DeviceDescriptor: bLength               18 bDescriptorType     &nbs

  • 如何通过学校系统漏洞注册到 @edu.cn 邮箱账号?

    此文章仅针对我自己学校的系统进行分析,并不代表所有学校的系统都是如此。 我们学校比较“抠”,可能是为了节省学校的带宽资源然后禁止学生注册教育邮箱账号。不过像一部电影所说的那样“没有绝对安全的系统”,有时候如果多动一下脑子并不需要“进谷歌,找注入;没注入,就旁注;没旁注,用Oday......”等一些繁琐的工作就可以达到目的。 声明 漏洞已提交学校网络中心,并已修复! 漏洞已提交学校网络中心,并已修复! 漏洞已提交学校网络中心,并已修复! 开始 在我们学校的官网上有一个邮箱服务的入口: 点进去之后如下图所示: 不过这里仅仅是留给老师申请和更改密码的入口!当我试图注册的时候就给出了下图中不能注册的提示。 分析 登陆学校的综合教务系统 很明显这是一个使用html框架技术做的页面。其中在左下角,有一些其它系统入口。点击这些系统的链接,除了【数字图书馆】其他的系统都可以不用再输入密码直接登录。这样的话,可能是在登陆综合教务系统时会产生一个“令牌”,然后与其他几个系统之间可以通用。 再仔细看看【进入系统】这个板块,下面还有这么一大块空白,会不会因为我是以学生身份登录的所以才只显示学生常

  • Altaro 可以备份 Microsoft Teams 吗?

    TeamsChats备份目前正在使用仍处于测试阶段的MicrosoftGraphAPI调用。更多信息在这里。 AltaroOffice365Backup能够备份MicrosoftTeams聊天(用户和组)。您可以在此处找到备份中包含的内容的完整列表: 频道消息频道附件 私人频道(至少需要备份1个成员) 私人频道附件一对一聊天1:1聊天附件公共频道文件私人频道文件 代码片段 应用程序(Wiki页面、任务、会议、批准) 手动备份 请务必注意,当通过单击[TakeBackup]从上下文菜单触发手动备份时,整个租户的所有聊天都会备份,因为Microsoft将聊天存储在租户级别而不是用户级别。 许可 仅“365TotalBackupSubscription”计划支持TeamsChats备份。 限制 不备份和恢复对消息的反应 通过聊天发送的文件将第一次备份,如果文件稍后通过OneDrive修改并且不在团队聊天中重新发送,则在聊天备份期间不会检测到此文件。但是,这将通过OneDrive/SharePoint备份进行备份。 如果自上次备份以来TeamsChat发生变化,我们只能检测到新用户已添加到聊天

相关推荐

推荐阅读