Jack Huang's Blog


  • 首页

  • 标签

  • 归档

  • 搜索

分布式系统的CAP理论简介

发表于 2019-02-17

分布式系统定义

分布式系统是其组件分布正在连网的计算机上,组件之间通过传递消息进行通信和动作协调的系统[1]。通过该定义可知,分布式系统具有以下重要特征:组件的并发性、缺乏全局时钟、组件故障的独立性。

CAP理论

CAP理论:一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。

  • 一致性(Consistency)
  • 可用性(Availability)
  • 分区容错性(Partition Tolerance)

一致性(Consistency)

在分布式系统中,是指对于一组服务器,给定一组操作,我们需要一个协议使得最后它们的结果达成一致。更详细的解释就是,当其中某个服务器收到客户端的一组指令时,它必须与其它服务器交流以保证所有的服务器都是以同样的顺序收到同样的指令,这样的话所有的服务器会产生一致的结果,看起来就像是一台机器一样。

分布式系统的一致性算法分为:

  • 弱一致性(最终一致性),例如DNS域名解析。
  • 强一致性,例如主从同步、多数派(读/写)、Paxos、Raft(multi Paxos)、ZAB(multi Paxos)。

可用性(Availability)

对于一个可用性的分布式系统,每一个非故障的节点必须对每一个请求作出响应。所以,一般我们在衡量一个系统的可用性的时候,都是通过停机时间来计算的。

通常我们描述一个系统的可用性时,我们说淘宝的系统可用性可以达到5个9,意思就是说他的可用水平是99.999%,即全年停机时间不超过 $(1-0.99999)36524*60 = 5.256 min$ ,这是一个极高的要求。

分区容错性(Partition Tolerance)

分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。

好的分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,或者是机器之间有网络异常,将分布式系统分隔未独立的几个部分,各个部分还能维持分布式系统的运作,这样就具有好的分区容错性。

简单点说,就是在网络中断,消息丢失的情况下,系统如果还能正常工作,就是有比较好的分区容错性。

参考连接

  1. George Coulouris, Jean Dollimore, Tim Kindberg,Gordon Blair,金蓓弘,马应龙,等译. 分布式系统概念与设计[M].2013.
  2. Zookeeper之分布式系统的一致性算法, by 养兔子的大叔.
  3. 分布式计算,by wikipedia.
  4. 分布式系统的CAP理论,by HollisChuang.

最优化算法之动态规划入门

发表于 2019-02-10

动态规划(英语:Dynamic programming,简称DP)是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。

动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。

动态规划中包含三个重要子概念:

  • 最优子结构
  • 边界
  • 状态转移公式

对有重叠子问题和最优子结构性质的问题,在建模之后,即获得其状态转移公式和边界之后,可采用下列算法求解:

  • 递归求解
  • 备忘录算法
  • 动态规划求解

参考链接

  1. 动态规划,by wikipedia.
  2. 漫画:什么是动态规划?,by 程序员小灰.

PID控制算法原理分析

发表于 2019-02-09

最近研究深度强化学习算法,进而对控制理论感兴趣,发现了PID这个广泛使用的控制算法。大概了解记录一下。PID控制器(比例-积分-微分控制器),由比例单元(P)、积分单元(I)和微分单元(D)组成。透过Kp,Ki和Kd三个参数的设定。PID控制器主要适用于基本上线性,且动态特性不随时间变化的系统。

PID控制器的方块图

图1 PID控制器的方块图

PID是以它的三种纠正算法而命名。受控变数是三种算法(比例、积分、微分)相加后的结果,即为其输出,其输入为误差值(设定值减去测量值后的结果)或是由误差值衍生的信号。若定义 $u(t)$为控制输出,PID算法可以用下式表示:

$$ {u}(t)= {MV}(t)=K_{p}e(t)+K_i\int _{0}^{t}{e(\tau )}{d\tau }+K_d{\frac{d}{dt}}e(t)$$

其中:

$K_{p}$:比例增益,是调适参数

$K_{i}$:积分增益,也是调适参数

$K_{d}$:微分增益,也是调适参数

$e$:误差=设定值(SP)- 回授值(PV)

$t$:目前时间

$\tau$ :积分变数,数值从0到目前时间 $t$

参考链接

  1. PID控制器,by wikipedia.
  2. PID控制算法原理(抛弃公式,从本质上真正理解PID控制),by 确定有穷自动机.

分布式系统架构入门

发表于 2019-02-01 | 更新于 2024-03-02

随着互联网高速公路的不断发展,以往的单机应用系统逐渐没落,分布式系统逐渐成为主流。

分布式系统定义

分布式系统是其组件分布正在连网的计算机上,组件之间通过传递消息进行通信和动作协调的系统[1]。通过该定义可知,分布式系统具有以下重要特征:组件的并发性、缺乏全局时钟、组件故障的独立性。

现代分布式系统的例子有:

  • Web搜索
  • 大型多人在线游戏
  • 金融交易

分布式系统架构演变

大多数的开发者最开始接触的是单机系统架构,即所有的数据和程序都在一台计算机上,这是分布式系统架构演变的起点。随着用户规模的不断增长和用户需求的不断变化,分布式系统架构开始不断演变。

系统架构演化历程-初始阶段架构

系统架构演化历程-初始阶段架构

初始阶段的小型系统中应用程序、数据库、文件等所有的资源都在一台服务器上。随着业务量的增长,小型系统的负载将越来越重。但如果没有达到单台机器的性能瓶颈,则根本没必要进行分布式架构,可以考虑机器升级,提高机器配置解决问题。或者考虑技术升级,更换更加高效或者场景适合的技术。

系统架构演化历程-应用服务和数据服务分离

系统架构演化历程-应用服务和数据服务分离

数据量增加,单台服务器性能及存储空间不足,需要将应用和数据分离,并发处理能力和数据存储空间得到了很大改善。

系统架构演化历程-使用缓存改善性能

系统架构演化历程-使用缓存改善性能

系统访问特点遵循二八定律,即80%的业务访问集中在20%的数据上。缓存分为本地缓存和远程分布式缓存,本地缓存访问速度更快但缓存数据量有限,同时存在与应用程序争用内存的情况。

系统架构演化历程-使用应用服务器集群

系统架构演化历程-使用应用服务器集群

使用集群是系统解决高并发、海量数据问题的常用手段。通过向集群中追加资源,提升系统的并发处理能力,使得服务器的负载压力不再成为整个系统的瓶颈。

系统架构演化历程-数据库读写分离

系统架构演化历程-数据库读写分离

数据库访问通常是读多写少。针对这个情况,就是写一个主库,但是主库挂多个从库,然后从多个从库来读,以支撑更高的读并发压力。

系统架构演化历程-反向代理和CDN加速

系统架构演化历程-反向代理和CDN加速

为了应付复杂的网络环境和不同地区用户的访问,通过CDN和反向代理加快用户访问的速度,同时减轻后端服务器的负载压力。CDN与反向代理的基本原理都是缓存。

系统架构演化历程-分布式文件系统和分布式数据库

系统架构演化历程-分布式文件系统和分布式数据库

任何强大的单一服务器都满足不了大型系统持续增长的业务需求,数据库读写分离随着业务的发展最终也将无法满足需求,需要使用分布式数据库及分布式文件系统来支撑。分布式数据库是系统数据库拆分的最后方法,只有在单表数据规模非常庞大的时候才使用,更常用的数据库拆分手段是业务分库,将不同的业务数据库部署在不同的物理服务器上。

系统架构演化历程-使用NoSQL和搜索引擎

系统架构演化历程-使用NoSQL和搜索引擎

随着业务越来越复杂,对数据存储和检索的需求也越来越复杂,系统需要采用一些非关系型数据库如NoSQL和分数据库查询技术如搜索引擎。应用服务器通过统一数据访问模块访问各种数据,减轻应用程序管理诸多数据源的麻烦。

系统架构演化历程-业务拆分

系统架构演化历程-业务拆分

为了应对日益复杂的业务场景,通常使用分而治之的手段将整个系统业务分成不同的产品线,应用之间通过超链接建立关系,也可以通过消息队列进行数据分发,当然更多的还是通过访问同一个数据存储系统来构成一个关联的完整系统。

  • 纵向拆分:将一个大应用拆分为多个小应用,如果新业务较为独立,那么就直接将其设计部署为一个独立的Web应用系统纵向拆分相对较为简单,通过梳理业务,将较少相关的业务剥离即可。

  • 横向拆分:将复用的业务拆分出来,独立部署为分布式服务,新增业务只需要调用这些分布式服务横向拆分需要识别可复用的业务,设计服务接口,规范服务依赖关系。

系统架构演化历程-分布式服务

系统架构演化历程-分布式服务

分布式服务关键技术

分布式服务应用将会面临以下问题:

  • 当服务越来越多时,服务URL配置管理变得非常困难,硬件负载均衡器的单点压力也越来越大。
  • 当进一步发展,服务间依赖关系变得错踪复杂,甚至分不清哪个应用要在哪个应用之前启动,架构师都不能完整的描述应用的架构关系。
  • 接着,服务的调用量越来越大,服务的容量问题就暴露出来,这个服务需要多少机器支撑?什么时候该加机器?
  • 服务多了,沟通成本也开始上升,调某个服务失败该找谁?服务的参数都有什么约定?
  • 一个服务有多个业务消费者,如何确保服务质量?
  • 随着服务的不停升级,总有些意想不到的事发生,比如cache写错了导致内存溢出,故障不可避免,每次核心服务一挂,影响一大片,人心慌慌,如何控制故障的影响面?服务是否可以功能降级?或者资源劣化?

为解决上述问题,可采用以下关键技术。

消息队列架构

消息队列通过消息对象分解系统耦合性,不同子系统处理同一个消息。

消息队列原理

面向服务架构

服务框架通过接口分解系统耦合性,不同子系统通过相同的接口描述进行服务启用服务框架是一个点对点模型服务框架面向同构系统适合:移动应用、互联网应用、外部系统。

面向服务架构原理

服务总线架构

服务总线架构同面向服务架构一样,均是通过接口分解系统耦合性,不同子系统通过相同的接口描述进行服务启用。服务总线架构是一个总线式的架构模型。

服务总线架构原理

分布式系统交互的通信模式

分布式系统交互的通信模式共有五种:

  • request/response模式(同步模式):客户端发起请求一直阻塞到服务端返回请求为止。
  • Callback(异步模式):客户端发送一个RPC请求给服务器,服务端处理后再发送一个消息给消息发送端提供的callback端点,此类情况非常合适以下场景:A组件发送RPC请求给B,B处理完成后,需要通知A组件做后续处理。
  • Future模式:客户端发送完请求后,继续做自己的事情,返回一个包含消息结果的Future对象。客户端需要使用返回结果时,使用Future对象的.get(),如果此时没有结果返回的话,会一直阻塞到有结果返回为止。
  • Oneway模式:客户端调用完继续执行,不管接收端是否成功。
  • Reliable模式:为保证通信可靠,将借助于消息中心来实现消息的可靠送达,请求将做持久化存储,在接收方在线时做送达,并由消息中心保证异常重试。

常用的分布式服务框架

现在业界比较成熟的服务框架有很多,比如:Hessian、CXF、Dubbo、Dubbox、Spring Cloud、gRPC、thrift等技术实现,都可以进行远程调用。

  • Spring Cloud:Spring全家桶,用起来很舒服,只有你想不到,没有它做不到。可惜因为发布的比较晚,国内还没出现比较成功的案例,大部分都是试水,不过毕竟有Spring作背书,还是比较看好。
  • Dubbox:相对于Dubbo支持了REST,估计是很多公司选择Dubbox的一个重要原因之一,但如果使用Dubbo的RPC调用方式,服务间仍然会存在API强依赖,各有利弊,懂的取舍吧。
  • Thrift:如果你比较高冷,完全可以基于Thrift自己搞一套抽象的自定义框架吧。
  • Montan:可能因为出来的比较晚,目前除了新浪微博16年初发布的,
  • Hessian:如果是初创公司或系统数量还没有超过5个,推荐选择这个,毕竟在开发速度、运维成本、上手难度等都是比较轻量、简单的,即使在以后迁移至SOA,也是无缝迁移。
  • rpcx/gRPC:在服务没有出现严重性能的问题下,或技术栈没有变更的情况下,可能一直不会引入,即使引入也只是小部分模块优化使用。

参考链接

  1. George Coulouris, Jean Dollimore, Tim Kindberg,Gordon Blair,金蓓弘,马应龙,等译. 分布式系统概念与设计[M].2013.
  2. 聊聊分布式系统的架构套路,by 大蕉.
  3. 分布式架构的演进,by 稳稳的幸福y.
  4. 互联网 Java 工程师进阶知识完全扫盲,by dooc.
  5. 聊聊Dubbo(一):为何选择, by 猿码道.
  6. “12306”的架构到底有多牛逼?,by 绘你一世倾城.
  7. How Pinterest scaled to 11 million users with only 6 engineers,by engineerscodex.

元强化学习研究笔记

发表于 2019-01-27 | 更新于 2019-01-31

元强化学习定义

什么是元强化学习?这得从深度学习开始说起。

Deep Learning研究一个从x到y的映射mapping,只是这个映射函数f是用一个端到端的深度神经网络来表示。如果是计算机视觉中的图像识别,那么x就是图片,y就是标签;如果是自然语言处理中的文本翻译,那么x就是比如中文,y就是英文;如果是深度增强学习中的玩Atari游戏,那么x就是屏幕画面,y就是输出的动作。深度学习研究的就是通过深度神经网络来学习一个针对某一特定任务task的模型。通过大量的样本进行训练,训练完,这个模型就可以用在特定任务上。

而Meta Learning研究Task!Meta Learning的目的是希望学习很多很多的task,然后有了这些学习经验之后,在面对新的task的时候可以游刃有余,学的快又学的好!那为什么叫Meta呢?Deep Learning是在Task里面研究,现在Meta Learning是在Task外面,更高层级来研究。也就是在Meta Learning的问题上,Task是作为样本来输入的。

Meta RL(Meta Reinforcement Learning)是Meta Learning应用到Reinforcement Learning的一个研究方向,核心的想法就是希望AI在学习大量的RL任务中获取足够的先验知识Prior Knowledge然后在面对新的RL任务时能够 学的更快,学的更好,能够自适应新环境!

元强化学习意义

元强化学习试图解决深度强化学习存在的如下问题:

  • 它的样本利用率非常低。换言之为了让模型的表现达到一定高度需要极为大量的训练样本。
  • 最终表现很多时候不够好。在很多任务上用非强化学习甚至非学习的其它方法,如基于模型的控制(model based control),线性二次型调节器(Linear Quadratic Regulator)等等可以获得好得多的表现。最气人的是这些模型很多时候样本利用率还高。当然这些模型有的时候会有一些假设比如有训练好的模型可以模仿,比如可以进行蒙特卡洛树搜索等等。
  • DRL成功的关键离不开一个好的奖励函数(reward function),然而这种奖励函数往往很难设计。在Deep Reinforcement Learning That Matters作者提到有时候把奖励乘以一个常数模型表现就会有天和地的区别。但奖励函数的坑爹之处还不止如此。奖励函数的设计需要保证:
    • 加入了合适的先验,良好的定义了问题和在一切可能状态下的对应动作。坑爹的是模型很多时候会找到作弊的手段。Alex举的一个例子是有一个任务需要把红色的乐高积木放到蓝色的乐高积木上面,奖励函数的值基于红色乐高积木底部的高度而定。结果一个模型直接把红色乐高积木翻了一个底朝天。仔啊,你咋学坏了,阿爸对你很失望啊。
    • 奖励函数的值太过稀疏。换言之大部分情况下奖励函数在一个状态返回的值都是0。这就和我们人学习也需要鼓励,学太久都没什么回报就容易气馁。都说21世纪是生物的世纪,怎么我还没感觉到呢?21世纪才刚开始呢。我等不到了啊啊啊啊啊。
    • 有的时候在奖励函数上下太多功夫会引入新的偏见(bias)。
    • 要找到一个大家都使用而又具有好的性质的奖励函数。这里Alex没很深入地讨论,但链接了一篇陶神(Terence Tao)的博客,大家有兴趣可以去看下。
  • 局部最优/探索和剥削(exploration vs. exploitation)的不当应用。Alex举的一个例子是有一个连续控制的环境里,一个类似马的四足机器人在跑步,结果模型不小心多看到了马四脚朝天一顿乱踹后结果较好的情况,于是你只能看到四脚朝天的马了。
  • 对环境的过拟合。DRL少有在多个环境上玩得转的。你训练好的DQN在一个Atari game上work了,换一个可能就完全不work。即便你想要做迁移学习,也没有任何保障你能成功。
  • 不稳定性。
    • 读DRL论文的时候会发现有时候作者们会给出一个模型表现随着尝试random seed数量下降的图,几乎所有图里模型表现最终都会降到0。相比之下在监督学习里不同的超参数或多或少都会表现出训练带来的变化,而DRL里运气不好可能很长时间你模型表现的曲线都没有任何变化,因为完全不work。
    • 即便知道了超参数和随机种子,你的实现只要稍有差别,模型的表现就可以千差万别。这可能就是Deep Reinforcement Learning That Matters一文里John Schulman两篇不同文章里同一个算法在同一个任务上表现截然不同的原因。
    • 即便一切都很顺利,从我个人的经验和之前同某DRL研究人员的交流来看只要时间一长你的模型表现就可能突然从很好变成完全不work。原因我不是完全确定,可能和过拟合和variance过大有关。

元强化学习方法

Meta RL(Meta Reinforcement Learning)是Meta Learning应用到Reinforcement Learning的一个研究方向。因此元强化学习的研究借鉴了元学习的思想和方法。

元学习方法

HyperNetwork 生成参数

HyperNetwork是一个蛮有名的网络,简单说就是用一个网络来生成另外一个网络的参数。那么我们这里非常直接,我们的设想就是希望用一个hypernetwork输入训练集数据,然后给我输出我的对应模型也就是上图f的参数,我们希望输出的这个参数能够使得在测试图片上取得好的识别效果。那么,有了这样设计,这个hypernetwork其实就是一个meta network。大家可以看到,本来基本的做法是用训练集直接训练这个模型f,但是现在我们用这个hypernetwork不训练了,直接给你输出参数,这等价于hypernetwork学会了如何学习图像识别,这也是为什么meta learning也同时叫做learning to learn的原因。我们通过hypernetwork学会学习。训练好了这个模型,连反向传播梯度下降都不要了,直接给你参数,是不是很酷?

Conditional Neural Network 条件神经网络

直接把D_train当做条件输入到f中,那么这个f本身就变成一个meta network了。也就是条件神经网络实际上能够得到和上面的hypernetwork一样的意义。因为我们可以想,只要条件D_train变了,那么y_test肯定也就变了。所以这里就非常非常直接了。把数据全部输入进去,让神经网络自己学就行了,不外乎就是去设计一个合适的网络结构而已。那么,这里最最简单粗暴的网络结构就是SNAIL算法使用temporal convolutional network,也就是wavenet的架构:

MAML 基于梯度的做法

MAML的核心步骤就是

  • 采集Task,得到D_train和D_test

  • 使用D_train对神经网络f训练少数几步,得到新的参数

  • 利用新的参数训练D_test,然后使得梯度下降更新一开始的参数。

三种解决办法的优缺点

先说HyperNetwork生成参数的做法。这种做法最大的问题就在于参数空间是很大的,所以要生成合适的参数特别是巨量的参数其实是比较困难的,所以目前绝大多数生成参数的做法都是只生成少量参数,比如一层的MLP,或者对于参数的空间进行一定的限制,比如就在[-1,1]之间,否则空间太多,有无数种选择输出一样的结果,就很难训了。但是采样HyperNetwork又有其灵活性,意味着我们可以只更新少部分参数,而不用全部。

接下来就是条件神经网络了。这又有什么问题呢?我觉得在性能上绝对会是最好的,很直接,但是不好看,一直要拖着一个条件,网络很大。不管是生成参数还是MAML,他们的模型网络就是独立的,之后只要输入x就行了,而条件神经网络每次都要输入条件,很烦啊。

那么MAML呢?可能最烦人的就是二次梯度了,这意味着MAML的训练会很慢,那么就很难hold住大网络了。实际上MAML目前对于大的网络结构比如Resnet效果并不好。然后MAML是使用D_train的Loss来更新整个网络,对比HyperNetwork缺少灵活性。这个Loss就是最好的吗?不见得。如果D_train是无监督数据,那怎么办?所以MAML是有局限性的。

目前各种各样的Meta Learning研究,在方法论上都逃不出这三种方法。要么改改网络结构,要么结合一下上面的方法,比如先MAML再生成参数,或者hypernetwork和conditional neural network混着用等等。那么什么才是终极必杀呢?可能还是要具体问题具体看吧,对于不同的问题采用不同办法效果会不一样。这些都值得我们去探索。

元强化学习方法

参考链接

  1. Meta Learning单排小教学,by Flood Sung.
  2. 最前沿: Meta RL论文解读,by Flood Sung.
  3. 这里有一篇深度强化学习劝退文,by Frankenstein.
  4. 周志华:满足这三大条件,可以考虑不用深度神经网络,by 周志华教授.

依赖注入入门

发表于 2019-01-26

依赖注入定义

在软件工程中,依赖注入是种实现控制反转用于解决依赖性设计模式。一个依赖关系指的是可被利用的一种对象(即服务提供端) 。依赖注入是将所依赖的传递给将使用的从属对象(即客户端)。该服务是将会变成客户端的状态的一部分。 传递服务给客户端,而非允许客户端来建立或寻找服务,是本设计模式的基本要求。

依赖注入意义

依赖存在的问题

如果在 Class A 中,有 Class B 的实例,则称 Class A 对 Class B 有一个依赖。例如下面类 Human 中用到一个 Father 对象,我们就说类 Human 对类 Father 有一个依赖。

1
2
3
4
5
6
7
8
public class Human {
...
Father father;
...
public Human() {
father = new Father();
}
}

仔细看这段代码我们会发现存在一些问题:

  • 如果现在要改变 father 生成方式,如需要用new Father(String name)初始化 father,需要修改 Human 代码;
  • 如果想测试不同 Father 对象对 Human 的影响很困难,因为 father 的初始化被写死在了 Human 的构造函数中;
  • 如果new Father()过程非常缓慢,单测时我们希望用已经初始化好的 father 对象 Mock 掉这个过程也很困难。

依赖注入的好处

上面将依赖在构造函数中直接初始化是一种 Hard init 方式,弊端在于两个类不够独立,不方便测试。我们还有另外一种 Init 方式,如下:

1
2
3
4
5
6
7
8
public class Human {
...
Father father;
...
public Human(Father father) {
this.father = father;
}
}

上面代码中,我们将 father 对象作为构造函数的一个参数传入。在调用 Human 的构造方法之前外部就已经初始化好了 Father 对象。像这种非自己主动初始化依赖,而通过外部来传入依赖的方式,我们就称为依赖注入。

现在我们发现上面 1 中存在的两个问题都很好解决了,简单的说依赖注入主要有两个好处:

  • 解耦,将依赖之间解耦。
  • 因为已经解耦,所以方便做单元测试,尤其是 Mock 测试。

依赖注入实现

Java中的依赖注入

依赖注入的实现有多种途径,而在 Java 中,使用注解是最常用的。通过在字段的声明前添加 @Inject 注解进行标记,来实现依赖对象的自动注入。

1
2
3
4
5
6
7
public class Human {
...
@Inject Father father;
...
public Human() {
}
}

上面这段代码看起来很神奇:只是增加了一个注解,Father 对象就能自动注入了?这个注入过程是怎么完成的?

实质上,如果你只是写了一个 @Inject 注解,Father 并不会被自动注入。你还需要使用一个依赖注入框架,并进行简单的配置。现在 Java 语言中较流行的依赖注入框架有 Google Guice、Spring 等,而在 Android 上比较流行的有 RoboGuice、Dagger 等。

PHP中的依赖注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Application
{
function __construct(Auth $auth, Session $session)
{
$this->auth = $auth;
$this->session = $session;
}

// ... 程式 ...
}

$auth = new Auth('localhost', 'root', '');
$session = new Session();
$application = new Application($auth, $session);

$application->login('admin', 'admin');

参考链接

  1. 依赖注入,by wikipedia.
  2. 依赖注入,by android-cn.

模仿学习研究笔记

发表于 2019-01-26 | 更新于 2019-01-30

模仿学习定义

模仿学习是指从示教者提供的范例中学习,一般提供人类专家的决策数据

$${ \tau_1,\tau_2,\ldots,\tau_m }$$

,每个决策包含状态和动作序列

$$\tau_i = <s_1^i,a_1^i,s_2^i,a_2^i,\ldots,s_{n_ni+1}^i>$$

,将所有「状态-动作对」抽取出来构造新的集合

$$\mathcal{D}={ (s_1,a_1),(s_2,a_2),(s_3,a_3),\ldots }$$

。之后就可以把状态作为特征(feature),动作作为标记(label)进行分类(对于离散动作)或回归(对于连续动作)的学习从而得到最优策略模型。模型的训练目标是使模型生成的状态-动作轨迹分布和输入的轨迹分布相匹配。

模仿学习意义

在传统的强化学习任务中,通常通过计算累积奖赏来学习最优策略(policy),这种方式简单直接,而且在可以获得较多训练数据的情况下有较好的表现。然而在多步决策(sequential decision)中,学习器不能频繁地得到奖励,且这种基于累积奖赏及学习方式存在非常巨大的搜索空间。而模仿学习(Imitation Learning)的方法经过多年的发展,已经能够很好地解决多步决策问题,在机器人、 NLP 等领域也有很多的应用。

模仿学习实现

当前主要以下几种方法实现模型学习:

行为克隆(Behavior Cloning)

行为克隆(Behavior Cloning)根据人类提供的状态动作对来习得策略,是作为监督学习的模仿学习。

逆强化学习(Inverse Reinforcement Learning )

IRL 是 反过来的 RL,RL 是根据 reward 进行参数的调整,然后得到一个 policy。

但是, IRL 就不同了,因为他没有显示的 reward,只能根据 人类行为,进行 reward的估计(反推 reward 的函数)。在得到 reward 函数估计出来之后,再进行 策略函数的估计。

逆强化学习是在给定一个专家之后(expert policy),通过不断地寻找 reward function 来满足给定的 statement(即,解释专家的行为,explaining expert behavior)。

结构化预测(Structured prediction)

在机器学习过程中,对数据的拟合其实就是在找一个拟合函数f,比如对于回归问题(Regression)来说,这个函数输出一个标量(scalar),对于分类问题(Classification)来说,这个函数输出一个类别(一个one-hot的向量),但是有一类的预测,它并不是输出一个标量或者一个类别,而是输出些有结构的输出,比如,一个序列,一个句子,一个图,一颗树。

生成对抗网络(GAN for Imitation Learning)

那么如何用 GAN 来做这个事情呢?对应到这件事情上,我们知道,我们想得到的 轨迹 是属于某一个高维的空间中,而 expert 给定的那些轨迹,我们假设是属于一个 distribution,我们想让我们的 model,也去 predict 一个分布出来,然后使得这两者之间尽可能的接近。从而完成 actor 的训练过程。

参考链接

  1. 模仿学习(Imitation Learning)完全介绍(一),by 我勒个矗.
  2. 深度强化学习之:模仿学习(imitation learning),by wangxiaocvpr.
  3. 机器人学习Robot Learning之模仿学习Imitation Learning的发展,by c2a2o2.
  4. 行为克隆,by XINGYES.
  5. 最前沿:用模仿学习来学习增强学习,by Flood Sung.
  6. 机器人学习最前沿:一眼模仿学习(One-Shot Imitation Learning)的三级跳,by Flood Sung.
  7. 深度学习课程笔记(七):模仿学习(imitation learning),by WangXiao.

软件架构的一些思考

发表于 2019-01-25 | 更新于 2023-03-21

以前对软件架构总是雾里看花,似懂非懂,最近好像有点悟了,赶紧记录一下。

软件架构的定义(What)

软件架构是有关软件整体结构与组件的抽象描述,用于指导大型软件系统各个方面的设计[1]。

软件体系结构是构建计算机软件实践的基础。与建筑师设定建筑项目的设计原则和目标,作为绘图员画图的基础一样,软件架构师或者系统架构师陈述软件架构以作为满足不同客户需求的实际系统设计方案的基础。从和目的、主题、材料和结构的联系上来说,软件架构可以和建筑物的架构相比拟。一个软件架构师需要有广泛的软件理论知识和相应的经验来实施和管理软件产品的高级设计。软件架构师定义和设计软件的模块化,模块之间的交互,用户界面风格,对外接口方法,创新的设计特性,以及高层事物的对象操作、逻辑和流程。

软件架构的意义(Why)

软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口来实现。

软件架构是软件架构师与项目干系人沟通的工具。例如:软件架构师与客户商谈概念上的事情,与经理商谈广泛的设计问题,与软件工程师商谈创新的结构特性,与程序员商谈实现技巧,外观和风格。

软件架构的选择(How)

用户需求决定软件架构的选择。用户需求可分成功能性需求和非功能性需求。功能性需求即用户要求软件产品应实现什么样的功能,非功能性需求即用户要求软件产品的质量属性达到一定标准。以开发一个购物网站为例,用户要求实现商品展示功能、购物车功能即功能性需求,用户要求在1秒内打开购物网站、网站平均无故障时间是一个月即非功能性需求。通常用户的非功能性需求对软件架构产生决定性影响。

常见的软件架构

分层架构

分层架构(layered architecture)是最常见的软件架构,也是事实上的标准架构。如果你不知道要用什么架构,那就用它。

这种架构将软件分成若干个水平层,每一层都有清晰的角色和分工,不需要知道其他层的细节。层与层之间通过接口通信。

虽然没有明确约定,软件一定要分成多少层,但是四层的结构最常见:

  • 表现层(presentation):用户界面,负责视觉和用户互动
  • 业务层(business):实现业务逻辑
  • 持久层(persistence):提供数据,SQL 语句就放在这一层
  • 数据库(database) :保存数据

事件驱动架构

事件(event)是状态发生变化时,软件发出的通知。

事件驱动架构(event-driven architecture)就是通过事件进行通信的软件架构。它分成四个部分。

  • 事件队列(event queue):接收事件的入口
  • 分发器(event mediator):将不同的事件分发到不同的业务逻辑单元
  • 事件通道(event channel):分发器与处理器之间的联系渠道
  • 事件处理器(event processor):实现业务逻辑,处理完成后会发出事件,触发下一步操作

微核架构

微核架构(microkernel architecture)又称为”插件架构”(plug-in architecture),指的是软件的内核相对较小,主要功能和业务逻辑都通过插件实现。

内核(core)通常只包含系统运行的最小功能。插件则是互相独立的,插件之间的通信,应该减少到最低,避免出现互相依赖的问题。

微服务架构

微服务架构(microservices architecture)是服务导向架构(service-oriented architecture,缩写 SOA)的升级。

每一个服务就是一个独立的部署单元(separately deployed unit)。这些单元都是分布式的,互相解耦,通过远程通信协议(比如REST、SOAP)联系。

微服务架构分成三种实现模式。

  • RESTful API 模式:服务通过 API 提供,云服务就属于这一类
  • RESTful 应用模式:服务通过传统的网络协议或者应用协议提供,背后通常是一个多功能的应用程序,常见于企业内部
  • 集中消息模式:采用消息代理(message broker),可以实现消息队列、负载均衡、统一日志和异常处理,缺点是会出现单点失败,消息代理可能要做成集群

云架构

云结构(cloud architecture)主要解决扩展性和并发的问题,是最容易扩展的架构。

它的高扩展性,主要原因是没使用中央数据库,而是把数据都复制到内存中,变成可复制的内存数据单元。然后,业务处理能力封装成一个个处理单元(prcessing unit)。访问量增加,就新建处理单元;访问量减少,就关闭处理单元。由于没有中央数据库,所以扩展性的最大瓶颈消失了。由于每个处理单元的数据都在内存里,最好要进行数据持久化。

这个模式主要分成两部分:处理单元(processing unit)和虚拟中间件(virtualized middleware)。

  • 处理单元:实现业务逻辑
  • 虚拟中间件:负责通信、保持sessions、数据复制、分布式处理、处理单元的部署。

虚拟中间件又包含四个组件。

  • 消息中间件(Messaging Grid):管理用户请求和session,当一个请求进来以后,决定分配给哪一个处理单元。
  • 数据中间件(Data Grid):将数据复制到每一个处理单元,即数据同步。保证某个处理单元都得到同样的数据。
  • 处理中间件(Processing Grid):可选,如果一个请求涉及不同类型的处理单元,该中间件负责协调处理单元
  • 部署中间件(Deployment Manager):负责处理单元的启动和关闭,监控负载和响应时间,当负载增加,就新启动处理单元,负载减少,就关闭处理单元。

客户端/服务器架构

这种架构由两部分组成:一个服务器和多个客户端。服务器组件将为多个客户端组件提供服务。客户端从服务器请求服务,服务器为这些客户端提供相关服务。此外,服务器持续侦听客户机请求。

模型/视图/控制器架构

这种架构,也称为MVC模式,把一个交互式应用程序划分为3个部分,

  • 模型:包含核心功能和数据
  • 视图:将信息显示给用户(可以定义多个视图)
  • 控制器:处理用户输入的信息

这样做是为了将信息的内部表示与信息的呈现方式分离开来,并接受用户的请求。它分离了组件,并允许有效的代码重用。

参考文献

  1. 软件架构,by wikipedia.
  2. 软件架构入门,by 阮一峰.
  3. 10种常见的软件架构模式,by 尽信书不如无书.
  4. MVC,by wikipedia.
  5. 1.软件架构编年史(译),by qinyu.
  6. MVC MVP MVVM Redux 架构介绍,by 吃货要健康.
  7. 深入理解MVC,by 陈宏鸿.
  8. 什么是MVVM框架?,by siki学院.

FlightGear技术分析

发表于 2019-01-24 | 更新于 2022-11-21

FlightGear是一个始于1997年多平台飞行模拟器、开源软件项目[1]。该项目适用的操作系统主要包括Linux、Microsoft Windows和Mac OS X,采用C++编写。

FlightGear体系结构

当前体系结构

FlightGear体系结构基于一个名为“主循环”的无限循环[2]。其流程图如图1所示。在主循环中将依次完成如下工作:

  • ATC模拟
  • 控制AI对象
  • 在多用户环境中更新其他飞机
  • 飞行动力学计算
  • 风景更新
  • 音频调度
  • 渲染

FlightGear主循环流程图

图1 FlightGear主循环流程图

高级体系结构

FlightGear正在支持高级体系结构(High Level Architecture, HLA)[3][4]。

HLA简介

高级体系结构(HLA)是分布式仿真的标准,用于通过组合(联合)多个仿真来构建用于更大目的的仿真。该标准是在美国国防部的领导下于90年代开发的,后来转变为开放的国际IEEE标准。它是北约通过STANAG 4603推荐的标准。今天,HLA被用于许多领域,包括国防和安全以及民用应用。该体系结构指定以下组件:

  • 运行时基础结构(RTI),通过不同的编程语言提供标准化的服务集。这些服务包括信息交换,同步和联合管理
  • 联邦成员(Federates)是使用RTI服务的单独仿真系统,由多个联邦成员对象构成。
  • 联邦成员对象模型(FOM),指定用于交换数据的对象类和交互类。 FOM可以描述任何域的信息。

HLA构成

图2 HLA构成

HLA标准由三部分组成:

  • IEEE Std 1516-2010框架和规则,它规定了组件或整个联合应遵守的十个体系结构规则。
  • IEEE Std 1516.1-2010联邦接口规范,规定了RTI应提供的服务。这些服务以C ++和Java API以及Web服务的形式提供。
  • IEEE Std 1516.2-2010对象模型模板规范,它规定了HLA对象模型(如FOM)应使用的格式。

HLA优势

与单机仿真相比,这有三大优势[5](例如FlightGear V3.6):

  • 它提供了一个强大的环境,使模拟器具有多线程,利用具有多个内核的计算机,或者在不同的计算机(甚至包括不同的平台和操作系统)上运行模拟的不同部分。
  • 它允许我们分离模拟器的部分,如AI(通过解耦AI交通系统)、FDM、Nasal脚本和渲染器,以及较少时间关键的子系统,如天气,这样我们就可以获得一致(也许更高)的帧速率(即减少Nasal垃圾收集对帧速率的影响)。
  • 它提供了一个非常好的框架,允许任何人使用除C / C ++之外的编程语言(想想Ada,Java,Python等)创建与FlightGear交互的组件,这些编程语言可能在他们自己的线程中运行,并且驻留在单独的二进制文件中,这也更容易调试/排除故障(想想回归测试,即在专用的gdb / valgrind会话中运行一个自包含的子系统),而不必知道如何修改/补丁和重建FlightGear。

FlightGear组件构成

FlightGear由很多开源组件或程序构成[6],具体包括:

RTI

RTI是HLA架构的关键组件,相当于中间件。

OpenRTI

OpenRTI是一个包含了rti 1.3、rti 1516、rti 1516e标准接口实现的rti库。OpenRTI有如下关键特性:

  • 易用性高,非常便于使用;
  • 直接提供C++调用接口,也可以提供Java接口,但目前还没有编码实现;
  • 可扩展性强;
  • 一直在维护中的RTI开源项目;
  • 树状的服务器结构;
  • 最短路径在内存中不拷贝数据;
  • 不依赖其它项目,仅仅需要C++编译器,特别的不需要boost;
  • 支持线程间通信、rti通信和管道间通信,未来可能支持http通信;
  • 到处都可以运行,能够在Linux、Win32、MacOS和Solaris上运行。

FDM

飞行动力学模型(Flight Dynamics Model, FDM)是模拟器内控制飞机物理飞行的数学模型。飞机的3D模型实质是一张图片,其与飞行动力学无关,本质上由FDM控制飞机如何飞行。在FlightGear中主要使用JSBSim和YASim两个飞行动力学模型。

JSBSim

JSBSim是一个用C++实现的开源跨平台飞行器动力学模型软件。Flightgear也采用了JSBSim作为其中的飞行器动力模型之一。同时JSBSim也可以作为一个单独的动力学模型软件进行运行。

YASim

YASim使用飞机的几何形状生成基本飞行特征。虽然这表明了一种“现实的”或开箱即用的方法,但在获得接近现实主义的结果之前,这只是一种粗略的近似,需要进行大量的调整。如果您的飞机有稳定的飞行数据,例如风洞数据,或者您希望最终生成超逼真的模拟,那么JSBSim可能是更好的方法。 如果你缺乏这样的数据但是知道飞机的几何形状并且能够获得与真实飞行员相同的飞行特性和限制,那么YASim可以提供足以满足大多数模拟需求的解决方案。

Atlas

Atlas旨在为FlightGear(一种开源飞行模拟器)的用户制作和展示世界高质量的图表。 这是通过两个主要部分实现的:地图创建者(简称为Map)和Atlas查看器。

  • 地图创建者从FlightGear获取风景数据并将其转换为漂亮的地图图片,可以使用您可能已安装的任何位图图像程序或使用Atlas查看应用程序查看。

  • Atlas查看应用程序可用于浏览您的地图,但也可以直接连接到FlightGear,并在所谓的移动地图显示上显示您的飞机当前位置。

FlightGear Multiplayer Server

FGMS或FlightGear多人游戏服务器是FlightGear的独立网络服务器,并根据GPL许可。它允许通过FGFS内的网络与其他飞行员一起飞行。

可以在服务器配置中配置的服务器列表类型:

  • 中继服务器 - 网络中的其他服务器。 每个都必须有完整的列表(减去自己)以获得适当的网络功能。
  • 交叉馈送服务器 - 服务器从本地用户和其他服务器接收的所有内容都将转发到交叉馈送服务器。 用于在同一主机上运行多个连接的fgms实例,例如 用于提供跟踪和未跟踪服务,而不会产生额外的外部流量。
  • 跟踪器 - 服务器每10秒向跟踪器发送一个每个本地用户的摘要更新。
  • HUB - 通常服务器不会将从服务器接收的数据包发送到其他中继。 HUB服务器将数据从服务器发送到它知道的所有中继。

FGCOM

FGCom是一种语音通信功能。这样您就可以在飞行途中与其他飞行员和空域管制员进行通信。

主要思想是重现真实的航空通信,换句话说,此功能旨在使您的航班期间尽可能真实地进行无线电通信。

FGCom有两种方式:

集成到FlightGear(FGCom内置):这当然是更好的解决方案,因为更容易使用
外部软件(FGCom standalone):只有在计划在特殊情况下使用FGCom时才应使用此软件

MPmap

MPMap是一个实用程序,可以在世界地图上显示在FlightGear世界中飞行的飞机。 除此之外,它还提供对导航数据的访问,例如ILS频率,跑道号和修正。由于它使用谷歌地图,人们可以选择地图或卫星视图。

SimGear

SimGear是FlightGear使用的一组开源软件库。该项目仍在开发中,但其目标是成为“仿真内核”,并由其他非FlightGear项目使用。该项目于2000年启动。SimGear与FlightGear和TerraGear一样,需要PLIB进行构建。

SimGear是一个仿真架构工具集(simulation construction tools),是FlightGear的仿真引擎,完成了数据结构操作、星历计算、模拟天空、坐标系转换等大部分的工作,它也是一个开源库。

PLib

PLIB(便携式游戏库)是由Steve J. Baker编写的一套用于编写游戏的软件库。 FlightGear在其大部分开发中使用了PLIB。 它也被FlightGear相关程序Atlas使用。 PLIB是开源的,并且是在GNU Library General Public License下发布的。

FlightGear 1.0(2007年发布)是在向OpenSceneGraph(OSG)过渡之前使用PLIB进行3D场景图的最后一次公开发布。 FlightGear仍然使用PLIB执行各种任务; 例如,读取操纵杆输入并显示图形用户界面(GUI)。 在后一种用法中,PLIB的PUI组件最终将由Canvas和Phi在FlightGear中替换,这将极大地改善当前GUI的功能,超出PUI提供的功能。

构建FlightGear时需要PLIB。

OSG

OSG是OpenSceneGraph的缩写,OpenSceneGraph图形系统是一个基于工业标准OpenGL的软件接口,它让程序员能够更加快速、便捷地创建高性能、跨平台的交互式图形程序。相比于工业标准OpenGL或者其他图形库,OpenSceneGraph的优点明显,除了程序开源和平台无关性以外,其封装并提供了数量众多的提升程序运行时性能的算法、针对包括分页数据库在内的几乎所有的主流数据格式的直接数据接口、 以及针对脚本语言系统Pthyon和Tcl的支持。

OpenGL是Open Graphics Library的缩写,其独立于硬件,独立于窗口系统,在运行各种操作系统的各种计算机上都可用,并能在网络环境下以客户/服务器模式工作,是专业图形处理、科学计算等高端应用领域的标准图形库。

场景相关工具

TerraSync

要查看飞机下方的地形,您必须安装相应的风景。这可以通过在安装风景的文章中描述的在飞行之前下载某些景点来实现。

或者,如果您有稳定且相当快速的互联网连接,则可以使用TerraSync。它是一个实用程序,可在模拟器运行时自动下载所需FlightGear场景的最新版本。 TerraSync在后台运行(可选择作为单独的进程),监视您的位置,并从“主时间”服务器“及时”下载(或更新)最新的场景。一段时间以来,TerraSync已经集成到核心FlightGear流程中,因此不需要为典型用户处理TerraSync。

TerraSync的主存储库,即TerraSync从中下载文件的在线资源,每天与FlightGear Scenery数据库同步一次。因此,当使用TerraSync时,您将永远拥有

  • 最新的.stg文件,告诉FlightGear放置对象的位置
  • 最新的对象静态模型。 (静态模型定义仅存在于一个地方的唯一对象,例如着名的建筑物或地标。)
  • 最新的对象共享模型。 (通用模型在不同的地方使用不止一次,每个都可以代表许多不同的对象,如通用房屋或船舶)

TerrGear

TerraGear是开源工具和渲染库的集合,可以在地球的3D表示(即3D模型或3D地图)中转换公开可用的GIS数据,以用于实时渲染项目。 TerraGear可以导入3D数据集,例如DEM地形网格,2D多边形数据集(如海岸线,城市轮廓,湖泊轮廓)和2D栅格数据集,例如1 km NAOO土地利用/土地覆盖数据。它还具有基于可用的FAA数据生成逼真的机场,跑道和照明的工具。

TerraGear是用于为FlightGear项目生成场景的主要工具。

如果没有terragear,可以更改Terrain纹理,但不能更改地形。如果要更改城市的纹理,请更改材质文件。如果你想改变海岸线,你需要terragear。检查目录FGDATA/Material中的材料文件,你需要terragear。

FlightGear事件处理

请参考FlightGear Command Doc

参考链接

  1. FlightGear,by wikipedia.
  2. A NEW ARCHITECTURE FOR FLIGHTGEAR FLIGHT SIMULATOR,by flightgear.
  3. FlightGear high-level architecture support,by flightgear
  4. Developing with HLA,by flightgear.
  5. High-Level Architecture,by flightgear.
  6. High-level architecture,by wikipedia.
  7. FlightGear related projects,by wikipedia.
  8. OpenRTI,by openrti.
  9. JSBSim,by wikipedia.
  10. YASim,by flightgear.
  11. JSBSim vs YASim,by flightgear.
  12. Atlas,by atlas.
  13. FGCom 3.0,by wikipedia.
  14. FlightGear Multiplayer Server,by flightgear.
  15. SimGear,by flightgear.
  16. PLIB,by flightgear.
  17. OSG,by flightgear.
  18. Flightgear操作方式以及几种典型飞机的起飞方法,by Mosquito_蚊子.
  19. Sentry的使用,by KJxiaoxiao.

面向方面编程简介

发表于 2019-01-23

什么是面向方面编程(What)

面向侧面的程序设计(aspect-oriented programming,AOP,又译作面向方面的程序设计、观点导向编程、剖面导向程序设计)是计算机科学中的一个术语,指一种程序设计范型。该范型以一种称为侧面(aspect,又译作方面)的语言构造为基础,侧面是一种新的模块化机制,用来描述分散在对象、类或函数中的横切关注点(crosscutting concern)。

侧面的概念源于对面向对象的程序设计的改进,但并不只限于此,它还可以用来改进传统的函数。与侧面相关的编程概念还包括元对象协议、主题(subject)、混入(mixin)和委托。

基本概念

  • 关注点(concern):对软件工程有意义的小的、可管理的、可描述的软件组成部分,一个关注点通常只同一个特定概念或目标相关联。
  • 主关注点(core concern):一个软件最主要的关注点。
  • 关注点分离(separation of concerns,SOC):标识、封装和操纵只与特定概念、目标相关联的软件组成部分的能力,即标识、封装和操纵关注点的能力。
  • 方法(method):用来描述、设计、实现一个给定关注点的软件构造单位。
  • 横切(crosscut):两个关注点相互横切,如果实现它们的方法存在交集。
  • 支配性分解(dominant decomposition):将软件分解成模块的主要方式。传统的程序设计语言是以一种线性的文本来描述软件的,只采用一种方式(比如:类)将软件分解成模块;这导致某些关注点比较好的被捕捉,容易进一步组合、扩展;但还有一些关注点没有被捕捉,弥散在整个软件内部。支配性分解一般是按主关注点进行模块分解的。
  • 横切关注点(crosscutting concerns):在传统的程序设计语言中,除了主关注点可以被支配性分解方式捕捉以外,还有许多没有被支配性分解方式捕捉到的关注点,这些关注点的实现会弥散在整个软件内部,这时这些关注点同主关注点是横切的。
  • 侧面(aspect):在支配性分解的基础上,提供的一种辅助的模块化机制,这种新的模块化机制可以捕捉横切关注点。

从主关注点中分离出横切关注点是面向侧面的程序设计的核心概念。分离关注点使得解决特定领域问题的代码从业务逻辑中独立出来,业务逻辑的代码中不再含有针对特定领域问题代码的调用,业务逻辑同特定领域问题的关系通过侧面来封装、维护,这样原本分散在在整个应用程序中的变动就可以很好的管理起来。

对于一个信用卡应用程序来说,存款、取款、帐单管理是它的主关注点,日志和持久化将成为横切整个对象结构的横切关注点。

为什么需要面向方面编程(Why)

AOP技术的优势是显而易见的。在面向对象的世界里,人们提出了各种方法和设计原则来保障系统的可复用性与可扩展性,以期建立一个松散耦合、便于扩展的软件系统。例如GOF提出的“设计模式”,为我们提供了设计的典范与准则。设计模式通过最大程度的利用面向对象的特性,诸如利用继承、多态,对责任进行分离、对依赖进行倒置,面向抽象,面向接口,最终设计出灵活、可扩展、可重用的类库、组件,乃至于整个系统的架构。在设计的过程中,通过各种模式体现对象的行为、暴露的接口、对象间关系、以及对象分别在不同层次中表现出来的形态。然而鉴于对象封装的特殊性,“设计模式”的触角始终在接口与抽象中大做文章,而对于对象内部则无能为力。

通过“横切”技术,AOP技术就能深入到对象内部翻云覆雨,截取方法之间传递的消息为我所用。由于将核心关注点与横切关注点完全隔离,使得我们能够独立的对“方面”编程。它允许开发者动态地修改静态的OO模型,构造出一个能够不断增长以满足新增需求的系统,就象现实世界中的对象会在其生命周期中不断改变自身,应用程序也可以在发展中拥有新的功能。

设计软件系统时应用AOP技术,其优势在于:

  • 在定义应用程序对某种服务(例如日志)的所有需求的时候。通过识别关注点,使得该服务能够被更好的定义,更好的被编写代码,并获得更多的功能。这种方式还能够处理在代码涉及到多个功能的时候所出现的问题,例如改变某一个功能可能会影响到其它的功能,在AOP中把这样的麻烦称之为“纠结(tangling)”。
  • 利用AOP技术对离散的方面进行的分析将有助于为开发团队指定一位精于该项工作的专家。负责这项工作的最佳人选将可以有效利用自己的相关技能和经验。
  • 持久性。标准的面向对象的项目开发中,不同的开发人员通常会为某项服务编写相同的代码,例如日志记录。随后他们会在自己的实施中分别对日志进行处理以满足不同单个对象的需求。而通过创建一段单独的代码片段,AOP提供了解决这一问题的持久简单的方案,这一方案强调了未来功能的重用性和易维护性:不需要在整个应用程序中一遍遍重新编写日志代码,AOP使得仅仅编写日志方面(logging aspect)成为可能,并且可以在这之上为整个应用程序提供新的功能。

总而言之,AOP技术的优势使得需要编写的代码量大大缩减,节省了时间,控制了开发成本。同时也使得开发人员可以集中关注于系统的核心商业逻辑。此外,它更利于创建松散耦合、可复用与可扩展的大型软件系统。

如何实现面向方面编程(How)

使用js实现before(前置通知)、after(后置通知)、around(环绕通知)。

before(前置通知)

before函数,用来实现函数的前置通知。在目标函数的前面执行一些前置操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// AOP 前置通知函数声明
/**
* 给方法加入前置切片函数
* 可以在执行方法之前执行一些操作,
* 前置切片的返回值为false时,不影响原方法的执行
* @param func {Function} 被前置执行的函数
* @return {Function} 加入前置通知的函数
*/
Function.prototype._before = function(func){
var __self = this;
return function(){
func.apply(__self, arguments);
return __self.apply(__self, arguments);
}
}

// 代码
function a(){
console.log('I\'m a');
}

a = a._before(function(){
console.log('before');
});

a();
// 结果:
// before
// I'm a

after(后置通知)

after函数,用来实现函数的后置通知。在目标函数的后面面执行一些后置操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// AOP 后置通知函数声明
/**
* 给方法加入后置切片函数
* 可以在执行方法之之后执行一些操作
* 后置切片的返回值为false时,不影响原方法的执行
* @param func {Function} 被后置执行的函数
* @return {Function} 加入后置通知的函数
* @constructor
*/
Function.prototype._after = function(func){
var __self = this;
return function(){
var ret = __self.apply(__self, arguments);
func.apply(__self, arguments);
return ret;
}
}

// 代码
function b(){
console.log('I\'m b');
}

b = b._after(function(){
console.log('after');
});

b();
// 结果:
// I'm b
// after

around(环绕通知)

在around函数中,引入了一个JoinPoint对象。JoinPoint对象封装了目标函数和目标函数的参数。在调用JoinPoint对象的invoke函数时,会去调用原来的目标函数。在调用invoke时,如果需要改变目标函数的this对象,需要将对象传入到invoke的参数中。around函数,可以在目标函数的前面和后面随意加入逻辑代码,也可以根据条件判断是否执行目标函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// AOP 环绕通知函数声明
/**
* 切入点对象
* 不允许切入对象多次调用
* @param obj 对象
* @param args 参数
* @constructor
*/
function JoinPoint(obj, args){
var isapply = false; // 判断是否执行过目标函数
var result = null; // 保存目标函数的执行结果

this.source = obj; // 目标函数对象
this.args = args; // 目标函数对象传入的参数

/**
* 目标函数的代理执行函数
* 如果被调用过,不能重复调用
* @return {object} 目标函数的返回结果
*/
this.invoke = function(thiz){
if(isapply){ return; }
isapply = true;
result = this.source.apply(thiz || this.source, this.args);
return result;
};

// 获取目标函数执行结果
this.getResult = function(){
return result;
}
}

/**
* 方法环绕通知
* 原方法的执行需在环绕通知方法中执行
* @param func {Function} 环绕通知的函数
* 程序会往func中传入一个JoinPoint(切入点)对象, 在适当的时机
* 执行JoinPoint对象的invoke函数,调用目标函数
*
* @return {Function} 切入环绕通知后的函数,
*/
Function.prototype._around = function(func){
var __self = this;
return function(){
var args = [new JoinPoint(__self, arguments)];
return func.apply(this, args);
}
}

// 代码

var isAdmin = true;

function c(){
console.log('show user list');
}

c = c._around(function(joinpoint){
if(isAdmin){ // 满足条件时,执行目标函数
console.log('is admin');
joinpoint.invoke(this);
}
});

c();
// 结果
// if isAdmin == true
// is admin
// show user list
// if isAdmin == false

参考链接

  1. 面向侧面的程序设计,by wikipedia.
  2. 什么是面向切面编程AOP?,by 知乎.
  3. 什么是面向方面编程,by liuweitoo.
  4. AOP面向方面编程,by 规速.
  5. 团队开发框架实战—面向切面的编程 AOP,by Bobby0322.
  6. 轻松理解AOP(面向切面编程),by -望远-.
  7. AOP在JS中的实现及应用,by _Sirius.
上一页1…434445…53下一页

Jack Huang

521 日志
67 标签
© 2025 Jack Huang
由 Hexo 强力驱动
|
主题 — NexT.Muse