Saturday, January 8, 2022

SegmentFault 最新的文章

SegmentFault 最新的文章


从2021分布式数据库开发者大会里,我们找出了这8个关键词

Posted: 07 Jan 2022 02:41 AM PST

历经近12个小时酣畅淋漓的在线直播,DC 2021分布式数据库开发者大会于1月6日晚上21:00圆满结束。本次大会以"数聚未来"为主题,由中国电子技术标准化研究院指导、CSDN主办、OceanBase承办,木兰开源社区、开源中国、51CTO、思否、极客邦科技、稀土掘金协办。

大会由中国电子技术标准化研究院研究室主任杨丽蕴女士开场致辞,并特别邀请了MySQL之父、MariaDB 创始人 Michael"Monty"Widenius 与 PostgreSQL 全球开发组联合创始人 Bruce Momjian 带来深度的行业解析。同时 OceanBase 创始人阳振坤、CEO杨冰、CTO 杨传辉、巨杉首席架构师 & 研发副总裁陈元熹、PingCAP 公司副总裁刘松,以及腾讯分布式数据库 TDSQL 首席架构师李海翔、华为云数据库首席架构师冯柯等多位重磅嘉宾也都莅临直播间,为开发者们贡献了一场分布式数据库领域的技术"盛宴"。

大会干货之多,嘉宾之丰富可以称得上是 2022 头一份了,为了更好的让读者们了解本次开发者大会的精彩,小编特意从这场大会里精选出8个关键词和大家分享。

分布式-Key Word 1

中国电子技术标准化研究院研究室主任杨丽蕴:我国互联网等新应用场景的快速发展背景下,具有大规模横向扩展能力的分布式数据库随之成长起来,且并不落后于全球的领先产品。分布式、云数据库等新一代数据库类型,没有传统数据库存量市场的旧有包袱,因此近年来在国内如雨后春笋般涌现。在近年国家科技发展之下,分布式数据库在互联网大规模场景下快速发展之后,正走向更广阔的市场,例如金融、通信、政务、物联网等企业级应用场景,都有分布式数据库承担创新业务的身影,并在逐步进入核心系统领域。

客观上,与传统集中式数据库相比,分布式数据库在产品成熟度和技术普及度上还存在差距。所以分布式数据库在快速发展同时,也在不断应对挑战,打磨产品。我相信,在国家科技发展战略下,以及云计算和 AI 智能化深入应用下,我国分布式数据库软件顺应了数字化发展的需求,必将取得快速创新和发展。

PingCAP公司副总裁刘松:分布式数据库就是数据库技术和分布式架构的一个结合。所以新一代的分布式数据库既具有经典数据库有的联机交易和在线分析的能力,同时要具备新一代分布式架构有的高扩展性、自动运维,包括新一代的云原生这种承接能力。

华为云数据库首席架构师冯柯:分布式数据库六大关键技术方向:全球多活高可用、软硬深度协同、企业级混合负载、云原生、数据安全与可信、AI-Native 阐述了华为 GaussDB 的根技术能力打造之路。

OceanBase CTO杨传辉:11年来我们一直是原生分布式数据库的信仰者和开拓者,我认为原生分布式数据库的几个核心特性为:无限扩展,永远在线,在一套引擎同时支持 TP 和 AP 的混合负载,保证强一致性。

OceanBase 原生分布式数据库经历了三次技术迭代,从最早的 NoSQL 系统走向第一代分布式数据库,第二代分布式数据库采用搭积木的方式,在 NoSQL 的基础之上,引入了 SQL 的支持,支持基本的 SQL 功能,但往往都牺牲了单机的性能和成本。目前,追求极致的第三代原生分布式数据库支持完整的企业级功能,并且做到单机性能与集中数据库基本相当。

开源、生态-Key Word 2

PostgreSQL 全球开发组联合创始人 Bruce Momjian:他认为开源对于全球的开发者而言都是一个绝好的机遇,在开源的整体环境下,开发者的作品能够在全球范围内得到认可,其本人能够有机会在国际性会议上发言。谈到分布式数据发展,他认为随着市场成熟与价值的显露,会有越来越多的人将目光投向分布式,而对于从业者而言,更多是要投入到创新与保障整体项目的健康度之上,这样才能做到真正的市场先行。

PingCAP 公司副总裁刘松:分布式数据库开源化这个潮流势不可挡。未来数据库最大的使命就是让各行各业数字化,这也是最大的应用需求。而在这个需求之上的技术演进要靠开源,源源不断的给更多的技术引擎供给。与此同时想要服务企业客户,还需要新一代云基础设施,尤其是跨云的云原生来承载。应用需求+开源+云基础设施这就是一个三角形,移动互联网时代,分布式数据库的架构演进到今天,甚至到未来十年,都可能是在这一个三角形的框架里面继续发展。

华为云数据库首席架构师冯柯:分布式数据库契合当前中国的发展阶段,是由中国的人口红利驱动的流量运用下产生的一种新的数据库形态。分布式数据库就像是高铁,单机就像是轿车。开发分布式尽管复杂,就像我们没办法把高铁做成像轿车那样方便灵活,但二者都是通向同样的智能化目标。

云、开放性-Key Word 3

CSDN 创始人&董事长,极客帮创投创始合伙人蒋涛:我们看到分布式的核心价值之一是可扩展,这点我们原有技术架构难以满足。其次是高可用,现在不管是云上还是在混合云,多地多中心部署已经成为常态。所以这个核心价值的核心是什么呢?在蒋涛眼里,是开放性,这点值得每个分布式数据库开发者长铭于心。

PingCAP 公司副总裁刘松:我们开始进入到分布数据库的下一个时代,从最初的互联网需求到金字塔顶端的数字化需求,是驱动全社会关注分布数据库行业的最大背景之一。现在很多云端数据库不一定满足高并发、高扩展的需求,跨云问题一直悬而未决,但新一代的云原生应用场景对分布式数据库的需求非常强烈,分布式数据库未来最大使命便是促成千行百业完成数字化目标。

一致性-Key Word 4

腾讯分布式数据库 TDSQL 首席架构师李海翔:在演讲中他回溯了数据库体系建立以来对于数据异常的定义与概括,并详细阐述了数据异常与整个事务处理领域关于数据异常、隔离级别与一致性三者之间的关系。TDSQL 的研究团队通过定义冲突关系,构建冲突图,建立图与异常的映射并进一步对数据异常进行分类的方式,成功建立了体系化的研究数据异常的框架,并初步描述了并发访问算法。当数据异常之后,以向环图为例,顶点和边的个数是无穷多个的,这意味着数据异常是有无穷多个的。对于无穷的我们怎么去加以认知呢?所以我们要对数据异常进行分类。对数据异常分类能够概括总结就得到一个表格,这个表格概括了所有的数据异常。然后当我们对所有的数据异常进行了分离之后,我们就可以去定义什么叫做隔离级别,什么叫做一致性了。简单来说,有数据异常即不满足一致性,满足一致性等于无数据异常。

HTAP混合负载-Key Word 5

OceanBase 创始人兼首席科学家阳振坤:OceanBase 创始人兼首席科学家阳振坤认为,分布式数据库是"一个"可水平扩展且一份数据存储既进行交易处理又进行分析处理的数据库。为什么说让数据库既做 OLTP 又做 OLAP 即 HTAP,面临非常大的挑战,就是因为 OLTP 与 OLAP 存在巨大差异,且这种差异始终存在。

image.png

阳振坤列举了挑战的4个方面。首先是分布式的事务处理,为什么必须是分布式的?因为分析处理所需的庞大的数据量和计算量,要求整个系统必须是分布式的。其次,事物的优先级分析的大查询需要消耗大量的 CPU 内存和 IO 资源,很可能导致交易的小查询无法得到所需的资源导致等待超时。第三,由于行存对交易事务处理友好,而列存对分析处理友好,HTAP 系统既需要行存又需要列存,也就是行列的混合存储。第四是 HTAP 的性能评估,今天的各种 benchmark 都是单一的性能评估,要么是事物处理,要么是分析处理。但 HTAP 同时需要两者。阳振坤坚信:人类的智慧是无穷的,HTAP 的这些挑战部分已经在克服。在不久的将来,这些挑战都将会被克服。

一体化架构-Key Word 6

OceanBase CTO杨传辉:OceanBase 作为原生分布式数据库的代表,它背后的核心技术便是一体化架构,一方面原生分布式架构能够享受到分布式技术的无限扩展,另一方面对外体现了对传统数据库的完美兼容。通过一体化架构发挥分布式加集中式的双重技术优势,它的底层仍然是一个原生分布式架构,能够充分享受到分布式技术无限扩展,永远在线的技术红利。

在2021年,OceanBase 取得了包括 OLTP 到 HTAP 整体性能、单核性价比、跑批能力、Oracle 平滑迁移、易用性五大核心产品技术突破。同时在本次大会上,杨传辉正式公布了 OceanBase 全新的3.X工具家族—运维监控工具 OCP、开发者工具 ODC 以及迁移同步工具 OMA&OMB,并发布了 OceanBase 社区版3.1.2。

核心首选-Key Word 7

OceanBase CEO 杨冰:在各种数据库类型中,原生分布式数据库以其诸多特性,正引领数据库管理技术发展趋势。据 Gartner 近日发布的报告,以 OceanBase 为代表的原生分布式数据库具备高可用、可扩展、多地域、多部署形态、混合负载、多租户以及透明兼容性等优势,正成为企业核心系统升级的首选。作为企业级原生分布式数据库代表,OceanBase 在过去一年中快速发展。

杨冰在会上透露,2021年 OceanBase 的客户数实现翻倍达到400多家。在金融等核心优势场景,OceanBase 持续深耕,目前全国TOP 200的头部金融机构中,有1/4都将 OceanBase 作为核心系统升级的首选。在区域性的银行、保险、证券及基金公司中,OceanBase 市场占比达到行业第一。除了金融场景,OceanBase 已经深入到各行各业,并在政务、能源和通信等关系国计民生的重要领域得到应用。

据杨冰介绍,来自非金融类客户的营收占比已达到 OceanBase 总营收的35%,且在快速增长中。值得一提的是,随着产品不断迭代,OceanBase 的客户结构也在持续优化,大量中小客户开始青睐原生分布式数据库。目前 OceanBase 有近七成的客户为中小客户。

"OceanBase 的使命是用技术让海量数据管理和使用更简单。我们相信长期主义,坚持'以产品驱动增长'的业务模式。期待与合作伙伴、客户、行业同仁、开发者一起,在最好的时代,为数据库行业的发展做出贡献,不断创造数据管理技术的未来。"杨冰表示。

客户价值-Key Word 8

MySQL 之父兼 MariaDB 创始人 Michael "Monty" Widenius:他认为庞大的用户群是指引数据库发展方向的重要对象,在创建 Maria DB 之时正是通过对于用户需求的分析,同用户一同去解决问题,才能从容地应对挑战。他表示:分布式数据库能够在不同节点上进行基本计算,所以在处理大量数据以及组计算的时候有很大的优势,但在事务处理方面则会慢一些,所以对于技术而言没有绝对的完美,更多的基于需求的权衡。

巨杉首席架构师 & 研发副总裁陈元熹:我想说分布式数据库实际上是从客户的角度、从应用场景的角度来驱动,那么如何从分布式,包括这种可扩展的存储以及海量并发的算力解决实际客户场景当中遇到的问题,这个是我们分布式发展当中面临的巨大挑战。分布式技术的发展来源于数据,中国有最好的数据市场,但是选择什么样分布式架构,实际上我觉得两个驱动,一个是这个产品本身创始团队的基因,另一个是面向的客户市场来决定的。巨杉数据库从2011年开始做分布式数据库的研发,虽然我们基于原生分布式数据库技术,但是我们在对客户进行能力分析以及产品推介的时候,仍然会根据客户的场景,进行更有效、更高效的介绍。


寥寥数语无法囊括本届分布式数据库大会的全部精彩,但不变的是我们对于"技术"、对于"开发者"的尊重。本次大会基于"开发",高潮仍落于"开发"。

日新月异的技术背后,是无数的开发者日以继夜的研究奉献。为此,在本次分布式数据库大会上,CSDN 联合极客邦、思否、开源中国、51CTO、掘金、木兰开源社区共同发起了海纳奖评选,选出了分布式技术领域"2021年度海纳奖 | 分布式数据库十佳实践人物"(* 附获奖名单,排名不分先后)。

image.png

在恭喜这些默默无闻开发者的同时,我们更希望通过他们背后的故事,为分布式数据库行业发展树立榜样,推动分布式数据库产业的发展。当然本次大会的精彩远不止于此,下午场四个小时的两场技术分论坛以及夜晚的"极客夜宵"同样精彩纷呈(* 敬请关注后续推送)。

科技发展战略之下,数据库等基础软件正逐步站上 IT 产业发展的舞台中央。在本次 DC2021分布式数据库开发者大会上,我们更是见证了被称之为"数据库技术未来"——分布式数据库技术的风采。相信在政产学研四界的共同推动下,数据库技术将迈入崭新篇章。

一岁一会,DC 2021分布式数据库开发者大会圆满结束,期待我们明年以更好的面貌再见!

扫码观看"直播回放"
image.png

阿里自研标准化协议库 XQUIC 正式开源!

Posted: 07 Jan 2022 02:59 AM PST

开源地址https://github.com/alibaba/xquic

XQUIC 是什么?

XQUIC[1]是阿里自研的IETF QUIC标准化传输协议库。XQUIC是基于IETF QUIC协议实现的UDP传输框架,包含加密可靠传输、HTTP/3两大块主要内容,为应用提供可靠、安全、高效的数据传输功能,可以极大改善弱网和移动网络下产品的用户网络体验。这项技术研发由大淘宝平台技术团队发起和主导,当前有达摩院XG实验室、阿里云CDN等多个团队参与其中。

现今QUIC有多家开源实现,为什么选择标准协议 + 自研实现的道路?我们从14年开始关注Google在QUIC上的实践(手机淘宝在16年全面应用HTTP/2),从17年底开始跟进并尝试在电商场景落地GQUIC[2],在18年底在手淘图片、短视频等场景落地GQUIC并拿到了一定的网络体验收益。然而在使用开源方案的过程中,或多或少碰到了一些问题,例如包大小过大、依赖复杂等等。最终促使我们走上自研实现的道路。

为什么要选择IETF QUIC[3]标准化草案的协议版本?过去我们也尝试过自研私有协议,在端到端都由内部控制的场景下,私有协议的确是很方便的,并且能够跟随业务场景的需求快速迭代演进;但私有协议方案很难走出去建立一个生态圈 / 或者与其他的应用生态圈结合(遵循相同的标准化协议实现互联互通);另一方面从云厂商的角度,私有协议也很难与外部客户打通;同时由于IETF开放讨论的工作模式,协议在安全性、扩展性上会有更全面充分的考量。因此我们选择IETF QUIC标准化草案版本来落地。截止目前,IETF工作组已经发布QUIC v1版本[4]RFC,XQUIC已经支持该版本,并能够与其他开源实现基于QUIC v1互通。

XQUIC 的优势

XQUIC是一个轻量、高性能、标准化的跨平台协议库:

轻量性:

  • XQUIC在Android/iOS双端的编译产物均小于400KB
  • 除TLS/1.3能力依赖SSL库之外,无其他外部依赖,可以方便地部署到移动设备和各种嵌入式设备中
  • 适用于需要高性能但同时又对包大小敏感的移动端APP场景(为了减少新用户的安装成本,移动端APP希望能尽量减少APP包大小)

高性能传输:

  • XQUIC已经在手机淘宝实现核心导购、短视频链路大规模使用,并相对于内核态TCP+HTTP/2优化20%的网络请求耗时
  • 支持0-RTT功能
  • 支持多通道传输加速能力[5]

标准化:

  • XQUIC实现了整套IETF QUIC标准协议,包含传输层、加密层、应用层协议栈
  • 协议版本支持QUIC version 1,以及draft-29
  • SSL库兼容适配BoringSSL或BabaSSL(可任意选择其中之一)

易用性:

  • 跨平台:支持Linux/Android/iOS/Mac等平台,后续也会支持Windows平台适配,客户端可以通过SDK方式很方便地接入并使用。
  • 支持Wireshark解析、qlog事件日志标准,方便问题排查
  • 完善的文档(中文/英文对照)、demo示例和单测

XQUIC核心介绍

模块设计

XQUIC是IETF QUIC草案版本的一个C协议库实现,端到端的整体链路架构设计如下图所示。XQUIC内部包含了QUIC-Transport(传输层)、QUIC-TLS(加密层、与TLS/1.3对接)和HTTP/3.0(应用层)的实现。除了每层的协议栈功能模块之外,在公共模块部分,XQUIC也支持了qlog[5]日志标准。

拥塞控制算法框架

拥塞控制算法模块,在传输协议栈中承担了发动机的职能。为了能够方便地实现多套拥塞控制算法、并方便针对各类典型场景进行优化,我们将拥塞控制算法流程抽象成7个回调接口,其中最核心的两个接口onAck和onLost用于让算法实现收到报文ack和检测到丢包时的处理逻辑。XQUIC内部实现了多套拥塞控制算法,包括最常见的Cubic、New Reno,以及时下比较流行的BBR v1和v2,每种算法都只需要实现这7个回调接口即可实现完整算法逻辑。

为了方便用数据驱动网络体验优化,我们将连接的丢包率、RTT、带宽等信息通过采样和分析的方式,结合每个版本的算法调整进行效果分析。同时在实验环境下模拟真实用户的网络环境分布,更好地预先评估算法调整对于网络体验的改进效果。

传输层能力和应用协议协商

XQUIC提供两套接口,分别是使用标准HTTP3的7层接口和直接使用传输层能力的4层接口,同时XQUIC支持ALPN[6]协商机制,可以通过向ALPN接口注册新的应用层协议回调,并通过握手期间的协商实现多套应用层协议的兼容。

7层协议的扩展能力和易用性: XQUIC的接口中,将QUIC Transport事件归类为通用传输层事件和面向应用层协议的事件。连接会话、Stream事件面向Application-Layer-Protocol定义;而剩余的通用传输层事件,因为在不同的应用层协议之间,具备高度共性,可以复用。这种设计保证了在扩展多种7层协议的时候,开发者只需要关注7层协议对于连接会话、Stream数据的处理,而不需要重复对QUIC传输层通用事件进行开发。

TLS层设计

QUIC Transport层,对TLS模块有如下依赖:加密握手协商、数据加解密、密钥更新、session resumption、0-RTT、传输参数、ALPN协商。TLS层,则需要依赖底层SSL库,来支撑上述功能。因此,TLS模块存在数据的多样性,以及依赖的多样性,数据流程和代码结构会比较复杂。TLS层需要对这些数据流进行归类整理,从而来简化上下游的依赖关系,降低代码的复杂度。

XQUIC适配了babassl、boringssl两种底层的ssl库,向上提供统一的接口,从而消除了它们之间接口、流程的差异,并抽象为统一的内部数据流程,仅针对不同ssl库提供轻薄的适配层,减少重复适配的代码逻辑,达到降低代码复杂度、提升可维护性的效果。同时XQUIC也提供了编译选项,方便开发者根据自身应用的情况,选择适合自己的依赖库。

XQUIC开源历史

为什么要做XQUIC

我们从18年左右,开始探索从TCP转向UDP方向,最早是基于GQUIC,主要应用在手淘的图片和短视频等内容分发的场景。在18年底19年初,当时大家有一个共同的判断是要走标准化道路,一方面整个标准化协议的设计和安全性都有更完备的考量,另一方面是因为从网络加速产品角度,私有协议解决方案更难被用户认可。在决定选择标准化道路之后,当时市面上也没有特别成熟并适用于移动端的IETF QUIC协议栈实现,所以手淘就启动了自研XQUIC项目。

经过1年半的研发和打磨,于20年的6月份开始全面上线,并在20年8月在手淘核心导购RPC请求场景进行规模化验证。在21年初与CDN IETF QUIC产品实现对接,并在短视频场景上开始逐步应用IETF QUIC技术。在去年的9月份我们实现了IETF QUIC整套协议栈在短视频场景下的规模化应用。之后,我们经历了2021年双十一的考验,XQUIC的性能和稳定性都有了很好的验证,因此在今年的1月7号,我们完成XQUIC的对外开源,后续也将持续更新迭代开源版本。

我们为什么要开源XQUIC

通过开源可以帮助整个社区更好地了解这项技术,可以帮助我们改进,同时可以通过社区的影响力对这项技术加以推广。社区的反馈也能够帮助我们吸收更多的需求场景输入,帮助我们更好地迭代这项技术。我们期望XQUIC在服务于淘宝技术的同时积极回馈社会,也欢迎网络技术研发的爱好者加入开源社区与我们交流。

应用场景和效果

目前,XQUIC已经在手淘Android/iOS双端正式版本、以及集团统一接入网关大规模应用,比如我们打开手机淘宝的首页,或是搜索我们感兴趣的商品,或是打开逛逛浏览达人的视频,XQUIC都为这些场景提供更快的网络数据传输,每天稳定为超过百亿量级的网络请求提供端到端加速能力。在2021年的双十一购物节中,XQUIC在核心导购链路、短视频场景下也经过了大规模验证。

后续Roadmap

我们计划每1~2个月发布一个稳定版本,当前计划如下:

新功能特性方面:

  • 互通性功能补充,包括Key update、Retry以及ECN
  • WG draft版本的多路径功能支持
  • 适配开源Tengine的module支持
  • 非可靠传输datagram支持
  • Masque特性支持

由于当前Multi-path QUIC[5]草案正处于即将被IETF QUIC Working Group接收的流程中,并且WG draft版本与XQUIC前期支持的多路径版本有部分差异,因此我们暂时在开源版本去掉了这部分功能。后续我们将在2月份基于Working Group草案版本更新多路径功能。

性能优化方面:

  • UDP性能优化feature
  • XUDP适配支持

跨平台支撑方面:

  • windows平台支持

配套工具方面:

  • 网络性能测量工具

文档及中文资料:

  • 开源仓库已经提供了基于draft-34校订的草案中文翻译,后续会陆续更新RFC8999-9002的中文译版

附录:

[1] XQUIC: https://github.com/alibaba/xquic

[2] GQUIC: 指Google QUIC版本,与IETF QUIC草案版本有一定差异

[3] IETF QUIC: 指IETF工作组推进的QUIC标准 https://datatracker.ietf.org/...,包括RFC8999~9002,以及还在推进中的HTTP/3.0、QPACK等一系列内容

[4] QUIC v1: RFC8999~9002所描述的QUIC协议版本

[5] MPQUIC: 即将被IETF QUIC WG工作组接收的草案 https://datatracker.ietf.org/...

[6] ALPN协商: https://datatracker.ietf.org/...

团队介绍

XQUIC团队隶属于大淘宝平台技术-移动技术中台团队,希望能通过网络技术演进给用户带来更丝滑的体验。如果对XQUIC、网络技术、高性能网络传输等领域比较感兴趣,欢迎点击"阅读原文"关注我们的 GitHub 仓库:

https://github.com/alibaba/xquic

如果在使用XQUIC相关产品中遇到问题,欢迎加入XQUIC社区钉钉群反馈&交流:

图片

如果你想加入我们,欢迎投递简历至miaoji.lym#alibaba-inc.com(投递简历时请把#换成@)

关注【阿里巴巴移动技术】微信公众号,每周 3 篇移动技术实践&干货给你思考!

基于 PageSpeed 的性能优化实践

Posted: 03 Jan 2022 01:59 AM PST

前言

网站性能至关重要,会影响 SEO 排名、转换率、用户跳出率以及用户体验等。在浏览器中加载缓慢的网站可能会慢慢失去用户,相反能够快速响应的网站通常会有机会获取更多流量,带来更大效益。

最近我们在站点做了一版性能优化,把主要着陆页面的 PageSpeed 分数从原本 30 左右提升到 80 分以上。

在这里分享下在这个过程中的一些经验,介绍下我们是如何达成这个结果,其中又涉及到哪些技术。

文章将从性能指标、性能测量与优化实践方案三个方面展开,期望可以给大家提供一些思路与参考。

性能指标

性能指标的发展与演进

针对线上项目做性能优化,首先需要有一个确定的可量化的评判标准,用以来判断优化工作是否有效。

传统的性能指标最典型的是 DOM Ready 时间 和页面加载时间(load time):前者指的是初始 HTML文档被完全加载和解析完成,一般是通过监听 DOMContentLoaded 事件获得;后者指的是整个页面所需的资源(包括脚本、样式、图片等)加载完成的时间,通过监听全局的 load 事件获取。

在早先前后端耦合的时代,是通过在服务端使用模板引擎渲染出 HTML,能比较好地反映网站性能。后来前端领域的迅猛发展,尤其是随着客户端渲染方案的盛行,以及各种动态技术的大量运用,这两个指标差不多已经失去其原有的意义,无法准确反映性能。

后来浏览器提供了 Navigation Timing API ,通过 perperformance.timing 可以获取从页面开始加载到结束整个过程中不同阶段的时间点。这很不错,开发者可以从多个维度去定义一些指标,通过简单的差值计算去监控站点性能。

比如在内部的用户行为追踪脚本(UBT)中就基于 timming API 主要定义了以下 7 个关键指标 DNS Connect Request Response Blank Domready Onload

  • DNS (domainLookupEnd - domainLookupStart)
  • Connect (connectEnd - connectStart)
  • Request (responseStart - requestStart)
  • Response (responseEnd - responseStart)
  • Blank (domInteractive - responseStart)
  • Domready (domContentLoadedEventEnd - navigationStart)
  • Onload (loadEventEnd - navigationStart)

同样,这些指标更侧重于技术细节,并不能很好地反映用户真正关心的问题。在做性能优化的时候,很可能面临的一种场景是,已经把某些特定指标如加载时间的数值大幅减少,但用户体验仍然很差。基于此,Chrome 团队和 W3C 性能工作组推出了一组 以用户为中心的性能指标,从用户角度更好地去评判页面性能。

这些主要指标包含:

指标介绍

FCP

FCP 指标测量的是页面从开始加载到页面内容的任何部分在屏幕上完成渲染的时间。"内容"可以是文本、图像(包括背景图像)、<svg> 元素或非白色的 <canvas> 元素。

这个指标回答了一个用户问题,正在运行吗

还有一个从名称上很接近的指标,FP (首次绘制),它们之间的区别如下:

  • FP first-paint 大致可以认为是白屏时间
  • FCP first-contentful-paint 大致可以认为是首屏时间

LCP

这个指标对应的关键用户问题是,是否有用,即页面是否已经呈现出对用户有用的内容。

早先有过一些类似的指标比如 FMP (首次有效绘制),但有效绘制的定义是什么通常很难解释,而且算法常常容易出错。

相反,最大内容绘制的定义简单明了,这里的"内容"和 FCP 中的定义基本一致,指的是在可视区域内的最大图片或文本块完成渲染的时间。

元素大小指的是内容占据的面积大小,即 size = width * height ,不包含边距边框。

大多数情况下,页面上最吸引用户的内容往往就是最大元素,可以认为这就是页面中最重要的元素。

TTI

可交互时间,对应的用户关注点是 可以使用吗

早期,关于可交互时间一直并没有一个清晰明确的定义。刀耕火种的时代,开发者自定义时间节点,并在代码中埋点来获取相关数据。

比如通过在 setTimeout 中放一个任务获取执行时间点,再计算到页面开始加载的差值。

setTimeout(function() {     tti = new Date() - navigationStartTime }, 0)

而在 Lighthouse 中,可交互时间指标有了更通用、标准化的定义。TTI 应从 FCP 时间点开始沿时间轴查找,如果出现 5 秒的静默窗口(没有长任务并且不超过 2 个正着处理的 GET 请求),那么最后一个长任务介绍的时间点即为可交互时间。

长任务指的是执行时间超过 50 ms 的任务。

主线程上若是存在导致阻塞状态的长任务,将导致无法响应用户交互。

tti

TBT

TBT 和 TTI 是一对配套指标,用于衡量在页面可交互之前的阻塞程度。

TBT 是指在 FCP 和 TTI 之间所有长任务超过 50ms 的部分的时间总和(注意不是长任务的时间总和)。

tbt

CLS

累积布局偏移指标用于衡量页面视觉稳定性。

单次布局偏移分数是影响分数(不稳定区域占可是区域的百分比)与距离分数(不稳定元素最大位移距离占比)的乘积。

CLS 指标本身一直在不断进化 ,便于更加准确地去衡量布局偏移对用户的影响。

其他

性能测量

了解了需要关注的性能指标,那应该怎么样去有效测量呢?

性能测量分两种类型,实验室测量与现场测量(真实用户监控)。有的指标只能通过实验室测量,或是只能现场测量。

实验室测量

实验室测量指的是在一个受控环境下,使用预定义的硬件设备和网络配置等规则去运行网站页面,进行性能数据采集,提取性能指标。

目前最流行的工具是 Google 的 Lighthouse ,最初作为一个独立的浏览器扩展程序需要开发者自行安装(支持 Firefox),目前已经集成到 Chrome DevTools 。

Lighthouse 不仅仅是一个性能测量工具,除此之外还提供 PWA 、SEO 、可访问性、最佳实践等审计报告。

在做性能优化的时候,如何有效评估优化方案的效果是一个问题,由于还没有发布到线上环境无法采集真实用户性能数据,这时候使用工具进行实验室测量就显得至关重要。

同时,Lighthouse 提供开源 CI 工具 Lighthouse CI 开发者能自行部署服务,并集成到现有的 CI 体系中。

现场测量

现场测量,也称真实用户监控(RUM),即实时采集真实用户性能数据。

实验室测量的是在一系列特定条件下的性能数据,不能完全反映现实世界中用户的真实情况。现场测量的优势在于样板量足够大,包罗各种不同设备不同网络环境下的数据,从统计上更能反映真实性能情况。另一方面,现场测量需基于浏览器提供的性能 Web API ,受限于当前设备采集到的数据不及实验室测量丰富。

定量评估的问题与方案

定量评估每一项优化方案的效果并不容易,原因包括环境差异问题,分数计算问题等。

解决方案是:

  • 开发模式启动站点应用与生产模式差别较大,将应用发布到测试服务器再进行性能测量
  • 本地启动 Lighthouse 进行测量,设备在不同时间的系统状态存在较大差异,应部署测量工具到固定服务器
  • 由于环境影响单次测量的差异可能很大,基于 lighthouse NPM 包一次性跑 10 次,去除最大值和最小值之后再取中位数和平均值作为参考
  • 性能分数有六大性能指标计算而来,某些指标的数值优化最终在分数上体现几乎没有差异,分开看具体指标数值更合理

性能优化方案

确定优化方向,并且有了可定量评估的方案之后,接下来要做的就是如何实施具体的优化方案。

性能优化是一个老生常谈,同时与时俱进的主题。早期大名鼎鼎的 雅虎 35 条性能军规 到现在大部分仍然适用,另一方面随着技术的发展,基于上述以用户为中心的性能指标,能更有针对性地实施方案。同时借助 Lighthouse 工具,能帮助我们有效评估具体方案的效果。

我们的应用是基于 React 技术栈,以下部分内容基于 React 来进行阐述。

减小包体积

网站应用与传统客户端应用很不同的一点在于,应用所需资源文件都是存放在远端服务器上的,每次访问都有相当大的性能开销是用于资源加载。

如何让资源高效加载成了一个非常重要的问题,其中最重要的一环是网络传输,专用的 CDN 服务器包含就近访问,资源缓存和压缩等功能,能节省大量网络传输时间,这是基础设施的角度。

从开发者的角度,首先可以对应用包体积进行瘦身。

包体积的问题主要表现在:

  • 不再使用的冗余代码
  • 复制粘贴的重复代码
  • 非必要的大体积类库
  • 未经优化的图片文件

冗余代码

冗余代码的产生有多种,比如是已经废弃不用但仍然被导入的功能模块,或者是在做 AB 实验完成后未完全移除的版本代码等。

借助相关工具,比如 Webapck 插件 webpack-bundle-analyzer 能用一种可视化的方式呈现每个包的具体模块信息,大小、包含关系一目了然。而 Chrome DevTools Coverage 工具能分析出运行过程中文件(脚本和样式)的使用情况,可作为参考更好地针对性地瘦身优化。

重复代码

重复代码很大一部分是实现相似功能的过程中,直接复制粘贴一方代码进行修改导致,借助 jsinspect 可以检测到相同和相似代码,然后进行合理抽象。还有一种情况是,依赖 NPM 包提供多种方式的代码,比如 dist 目录下的打包代码,lib 目录下的 CommonJS 代码,和 es 目录下的 ES Modules 代码。若是不小心在不同地方引入不同方式的包,就等同于是引入重复功能模块。更甚一步,在跨团队合作中依赖包只提供打包版本,也会出现 babel polyfill 代码多次重复,并且无从分析。解决方案是制定统一的标准,推荐 NPM 包都提供仅 babel 编译不打包版本。

类库开销

在类库的使用上同样需要注意,比如仅使用一两个方法就引入整个 lodash 库,推荐做法是按需引入,不用改变写法加入 babel-plugin-lodash 这类插件就能在代码构建时转换。另外一种情况是引入 moment 这类体积较大的库用作时间处理与格式化,可以视实际情况采用体积更小的替代品。对于更简单的需求,则完全可以基于原生 API 自行实现封装一些方法。

图片文件

未经优化的图片可高达几百 KB ,应在保证图片清晰度的情况压缩大小。

另一方面,为现代浏览器提供有更高效压缩算法的图片格式,相比传统的 PNG 和 JPG 格式,WebP 在同等质量下有更小的体积,注意做好降级方案。

优化资源加载

作为开发者做好包体积优化能节省网络传输时间,以及一部分代码执行时间,但更重要的是让资源有效加载,可从资源加载顺序和优先级方面着手。

Resource Hints

为了使页面可以快速加载,我们基于 PRPL 模式 进行优化。PRPL 是四个词的首字母缩写,分别代表:

  • Preload 预加载最重要的资源
  • Render 尽快渲染初始内容
  • Pre-cache 预缓存其他资源
  • Lazy load 懒加载其他路由和非关键资源

首先,我们需要优化关键路径资源,页面中要呈现的内容很多,但不是所有内容都需要第一时间呈现,优先呈现最重要的内容。浏览器并不知道哪些资源是最重要的,基于 Resource Hints 可以告诉浏览器资源优先级。常用的有以下几类:

  • preconnect 启动早期连接,包括 DNS 查找,TCP 握手等
  • preload 预加载资源并缓存,以便需要时立即使用
  • prefetch 预获取资源,优先级比 preload 低,浏览器自行判断合理时间执行操作

在使用过程需要注意:

  • 不要无限制的滥用,因为其自身会消耗资源,尤其是添加了但却未使用
  • 资源设置 crossorigin ,对应预处理提示也要设置,否则两者不匹配导致重复加载

Service Worker

使用 Service worker 缓存预载资源,对后续访问会有极大的性能提升,能节省大量网路传输开销。

在项目中推荐采用 Google 提供的 Workbox 库,可以通过配置的方式对不同类型资源应用不同缓存策略。

Service Worker 带来的优化效果不能从 PageSpeed Insights 网站上的分数直接体现,因为 PageSpeed 总是单次分数并且不使用缓存。

优化加载第三方脚本

应用依赖的第三方脚本通常会减慢页面加载速度,一般采用以下方式:按需加载和延迟加载。

按需加载

需用户交互才用到的功能模块应按需加载。举个例子,用户登录时要调用一个第三方验证模块,就没必要在页面一开始就引入该脚本,在用户执行登录操作时引入更合理。

延迟加载

像是 Google analysis 和合作商营销等第三方日志埋点脚本,业务需要无法移除,加载后占用大量性能资源。

由于本身没有依赖关系,可使用 defer script 延迟脚本的解析执行。更进一步,延迟到在可交互时间之后加载就基本不会有任何影响。

组件懒加载

可视区域之外的内容,和需要用户交互时才呈现的组件,都可采用懒加载,保证页面首要内容快速呈现。

要做懒加载,首先需要合理定义拆分点进行代码分割,然后基于动态导入和 React.lazy 即可实现。

对于大部分点击触发的组件来说,这样已经足够,但针对页面底部可视区域之外需常规滚动查看的内容,还要做一些额外的工作。可以自行封装实现一个组件,在内部进行判断内容是否可视,并监听 scroll 事件重新渲染。

实际中,我们结合 react-lazyload@loadable/component 实现所需功能,如下:

import React from 'react'; import loadable from '@loadable/component'; import LazyLoad from 'react-lazyload';  const LazyComponent = loadable(() => import(/* webpackChunkName: "home_lazy" */ './LazyComponent'));  export function HomePage() {     return (         <>             <MainComponenet />             <LazyLoad>                 <LazyComponent />             </LazyLoad>         </>     );) }

懒加载可能导致懒加载组件自身体验下降,可对用户比较频繁使用的组件预加载。

过度拆分可能会产生很多体积很小的包,可以适当地进行合并。借助 webpack magic comment ,配置相同的 chunk name 可以合并打包。

import loadable from '@loadable/component';  export const SortLayer = loadable(() => import(/* webpackChunkName: "depart_select_layer" */ './SortLayer')); export const StopLayer = loadable(() => import(/* webpackChunkName: "depart_select_layer" */ './StopLayer')); export const TimeLayer = loadable(() => import(/* webpackChunkName: "depart_select_layer" */ './TimeLayer'));

优化渲染方式

  • 服务端渲染
  • 预渲染

服务端渲染

CSR (客户端渲染)的最大问题在于受用户环境影响太大,一方面是网络层面脚本文件的加载,一方面是浏览器的执行效率,不同场景下差异可能非常大。

SSR (服务端渲染)则能解决这个问题,直出 HTML 能快速呈现页面主要内容,能很好地改善 FCP 和 LCP 指标。

SSR 相对 CSR 本质上来讲就两点:

  • 将渲染(这里是指 JavaScript 执行层面的)工作转移到服务端,毕竟服务端相对更可控
  • 在首屏之前避免减少资源网络传输,从而减少耗时,因为网络是更不可控的一个因素

实际上,大部分时候都是结合二者,针对首屏采用服务端渲染,让用户更快看到内容,其他仍使用客户端渲染的模式,减轻服务器压力,毕竟将大量用户的渲染任务转移到服务端会是一笔不小的开销。这时,结合缓存机制可以大大节省渲染时间。

预渲染

基于构建时的预渲染,是使用 webpack 和 babel 等工具提前生成对应的 HTML 以及引用的脚步和样式文件。还有一种方式是基于运行时的,使用 headless 浏览器。但预渲染并不适用于有大量动态内容的页面。

优化长任务

Long Task (长任务)的定义是执行时间超过 50 ms 的任务。我们知道,JavaScript 是单进程单线程的模型,主线程上一旦有耗时长的任务存在时,就会造成阻塞,无法响应用户输入。

Long Task 跟 Lighthouse 中的两个重要性能指标 TTI 和 TBT 息息相关,而这两个指标占比为 40% ,可以说优化好 Long Task 能大幅提升页面性能。

Long Task 可借助对应的 Long Task Web API 进行监控,开发过程中则使用 Chrome DevTools Performace 面板查看。需要注意的是,开发者的电脑配置可能很强,但用户尤其是移动端的用户环境并没有那么乐观,应该适当调低硬件配置和网络速度,这样能发现更多的 Long Task 。

任务类型有多种,除了最常见的脚本执行之外,还包括脚本解析编译、HTML 解析、CSS 解析、布局、渲染等。脚本执行是长任务的主要表现形式,这里着重说明在 JavaScript 执行上的一些优化方式:

  • requestIdleCallback API
  • Web Worker
  • 记忆函数
  • Debounce 和 Throttle

requestIdleCallback API

针对一些不重要的任务比如埋点日志可以直接丢到 requestIdleCallback 中,浏览器会在空闲时间执行。在不支持的环境可使用 shim) ,基于 setTimeout 实现近似的功能。

idlize 中封装了一些非常实用的帮助函数,使用这些方法可把任务延迟到需要的时候再执行。

Web Worker

如果项目中确实存在比较复杂的计算,可启动 Web Worker 单独另开一个线程来计算,并使用 message 通信。

记忆函数

如果一个函数被大量调用,合理运用记忆函数一个很好的选择,有大量的库可供我们选择,也可以根据使用场景自行实现。

Debounce 和 Throttle

针对 input change 和 scroll 等可能频繁触发的事件,避免无节制地调用。

React 性能优化

在 React 框架使用上有一些性能优化的实践,个人认为比较重要的有:

  • shouldComponenetUpdate
  • useMemouseCallback
  • 不可变数据

默认的 shouldComponenetUpdate 总是返回 true 但开发者知道什么时候应该更新,则可自行实现该生命周期方法。推荐大部分组件都使用 pureComponent 代替,函数组件则可使用 Memo

useMemouseCallback 都是记忆函数,可结合 Memo 避免不必要的重新渲染,或者是对昂贵计算的记忆。

state 和 props 都是不可变数据,在更新深层嵌套数据使用深拷贝不是一种好方式,可借助 Immer 这类库更好地编写。

最后说明一点,在必要的时候进行性能优化,大部分时候无需考虑,而且滥用方法反而损害性能。

减少布局偏移

如何调试监控

有对应的 Layout Instability API 可以帮助收集用户的布局偏移数据。

在开发调试中,Layout Shift 同样可以使用 Chrome DevTools Performance 进行分析,能查看每一次布局偏移的分数,进行针对性优化。

常用的优化方案有:

  • 为动态元素预静态预留空间
  • 图片宽高尺寸固定

预留空间可减少其他页面元素的偏移,比如出现在最顶部的广告位,在数据还未获取到的时候预先设置好一个容器,可避免后续大幅偏移。

针对整页动态的内容,使用骨架屏是一种很好的模式,业界已有不少成熟方案可自动生成。

设置图片宽高,则可以保证浏览器在加载图片过程中始终能分配正确的空间大小。

总结反思

借助上述中提到的性能测量方式,我们逐步实施优化方案并发布上线,经过近两个月断断续续的时间,最终让性能分数稳定在 80 分左右。

score

性能优化也适用于二八定律,优化方式很多,只是简单地堆砌使用很可能适得其反。不同场景下的优化方案千差万别,关键在于找准最核心的问题。以上仅提供一些思路作为参考。有些方案对特定指标效果很好,有些方案不会反映到指标分数,但有助提升用户体验。

再者,指标衡量的是单个页面速度,而作为开发者还应衡量后续页面,从整体的维度去平衡,真正从用户角度考虑。

我们自研的 Ice 规则引擎开源了

Posted: 05 Jan 2022 06:21 PM PST

前言

背景介绍

   规则/流程引擎想必大家并不陌生,耳熟能详的就有Drools,Esper,Activiti,Flowable等,很多大厂也热衷于研究自己的规则引擎,都是用于解决灵活场景下的复杂规则与流程问题,想要做到改改配置就可以生成/生效新的规则,脱离硬编码的苦海。毕竟改改配置和在已有基础上编排规则/流程,比硬编码的成本低很多,但是使用市面上现有的规则引擎来编排,一来接入成本和学习成本都不低,二来随着时间的推移,规则变的越发庞大以及一些场景的不适用,更加让人叫苦不迭。

「设计思路」

为了方便理解,设计思路将伴随着一个简单的充值例子展开。

「举例」

X公司将在国庆放假期间,开展一个为期七天的充值小活动,活动内容如下:

活动时间:(10.1-10.7)

活动内容:

充值100元 送5元余额(10.1-10.7)

充值50元   送10积分 (10.5-10.7)

活动备注:不叠加送(充值100元只能获得5元余额,不会叠加赠送10积分)

简单拆解一下,想要完成这个活动,我们需要开发如下模块:

图中发现有待发放key,这个key是从哪里来呢:

如图,当用户充值成功后,会产生对应充值场景的参数包裹Pack(类Activiti/Drools的Fact),包裹里会有充值用户的uid,充值金额spend,充值的时间requestTime等信息。我们可以通过定义的key,拿到包裹中的值(类似map.get(key))。

模块怎么设计无可厚非,重点要讲的是后面的怎么编排实现配置自由,接下来将通过已有的上述节点,讲解不同的规则引擎在核心的编排上的优缺点,并比较ice是怎么做的。

「流程图式实现」

类Activiti、 Flowable实现

流程图式实现,应该是我们最常想到的编排方式了~ 看起来非常的简洁易懂,通过特殊的设计,如去掉一些不必要的线,可以把UI做的更简洁一些。但由于有时间属性,其实时间也是一个规则条件,加上之后就变成了:

看起来也还好。

「执行树式实现」

类Drools实现(When X Then Y)

这个看起来也还好,再加上时间线试试:

依旧比较简洁,至少比较流程图式,我会比较愿意修改这个。

「变动」

上面两种方案的优点在于,可以把一些零散的配置结合业务很好的管理了起来,对配置的小修小改,都是信手拈来,但是真实的业务场景,可能还是要锤爆你,有了灵活的变动,一切都不一样了。

「理想」

不会变的,放心吧,就这样,上线。

「现实」

①充值100元改成80吧,10积分变20积分吧,时间改成10.8号结束吧(微微一笑,毕竟我费了这么大劲搞规则引擎,终于体现到价值了!)

②用户参与积极性不高啊,去掉不叠加送吧,都送(稍加思索,费几个脑细胞挪一挪还是可以的,怎么也比改代码再上线强吧!)

③5元余额不能送太多,设置个库存100个吧,对了,库存不足了充100元还是得送10积分的哈(卒…早知道还不如硬编码了)

以上变动其实并非看起来不切实际,毕竟真实线上变动比这离谱的多的是,流程图式和执行树式实现的主要缺点在于,牵一发而动全身,改动一个节点需要瞻前顾后,如果考虑不到位,很容易弄错,而且这还只是一个简单的例子,现实的活动内容要比这复杂的多的多,时间线也是很多条,考虑到这,再加上使用学习框架的成本,往往得不偿失,到头来发现还不如硬编码。

怎么办?

「ice是怎么做的?」

「引入关系节点」

关系节点为了控制业务流转 

【AND】

所有子节点中,有一个返回false 该节点也将是false,全部是true才是true,在执行到false的地方终止执行,类似于Java的&& 

【ANY】

所有子节点中,有一个返回true 该节点也将是true,全部false则false,在执行到true的地方终止执行,类似于Java的|| 

【ALL】 

所有子节点都会执行,有任意一个返回true该节点也是true,没有true有一个节点是false则false,没有true也没有false则返回none,所有子节点执行完毕终止 

【NONE】

所有子节点都会执行,无论子节点返回什么,都返回none 

【TRUE】

所有子节点都会执行,无论子节点返回什么,都返回true,没有子节点也返回true(其他没有子节点返回none)

「引入叶子节点」

叶子节点为真正处理的节点 【Flow】 一些条件与规则节点,如例子中的ScoreFlow 【Result】 一些结果性质的节点,如例子中的AmountResult,PointResult 【None】 一些不干预流程的动作,如装配工作等,如下文会介绍到的TimeChangeNone 有了以上节点,我们要怎么组装呢?

如图,使用树形结构(对传统树做了镜像和旋转),执行顺序还是类似于中序遍历,从root执行,root是个关系节点,从上到下执行子节点,若用户充值金额是70元,执行流程:

这个时候可以看到,之前需要剥离出的时间,已经可以融合到各个节点上了,把时间配置还给节点,如果没到执行时间,如发放积分的节点10.5日之后才生效,那么在10.5之前,可以理解为这个节点不存在。

「变动与问题的解决」

对于①直接修改节点配置就可以

对于②直接把root节点的ANY改成ALL就可以(叠加送与不叠加送的逻辑在这个节点上,属于这个节点的逻辑就该由这个节点去解决)

对于③由于库存的不足,相当于没有给用户发放,则AmountResult返回false,流程还会继续向下执行,不用做任何更改

再加一个棘手的问题,当时间线复杂时,测试工作以及测试并发要怎么做?一个10.1开始的活动,一定是在10.1之前开发上线完毕,比如我在9.15要怎么去测试一个10.1开始的活动?在ice中,只需要稍微修改一下:

如图,引入一个负责更改时间的节点TimeChangeNone(更改包裹中的requestTime),后面的节点执行都是依赖于包裹中的时间即可,TimeChangeNone类似于一个改时间的插件一样,如果测试并行,那就给多个测试每人在自己负责的业务上加上改时间插件即可。

「特性」

为什么这么拆解呢?为什么这样就能解决这些变动与问题呢?

其实,就是使用树形结构解耦,流程图式和执行树式实现在改动逻辑的时候,不免需要瞻前顾后,但是ice不需要,ice的业务逻辑都在本节点上,每一个节点都可以代表单一逻辑,比如我改不叠加送变成叠加送这一逻辑就只限制在那个ANY节点逻辑上,只要把它改成我想要的逻辑即可,至于子节点有哪些,不用特别在意,节点之间依赖包裹流转,每个节点执行完的后续流程不需要自己指定。

因为自己执行完后的执行流程不再由自己掌控,就可以做到复用:

如图,参与活动这里用到的TimeChangeNone,如果现在还有个H5页面需要做呈现,不同的呈现也与时间相关,怎么办?只需要在呈现活动这里使用同一个TimeChangeNone实例,更改其中一个,另一个也会被更新,避免了到处改时间的问题。

同理,如果线上出了问题,比如sendAmount接口挂了,由于是error不会反回false继续执行,而是提供了可选策略,比如将Pack以及执行到了哪个节点落盘起来,等到接口修复,再继续丢进ice重新跑即可(由于落盘时间是发生问题时间,完全不用担心活动结束了的修复不生效问题),同样的,如果是不关键的业务如头像服务挂了,但是依然希望跑起来,只是没有头像而已,这样可以选择跳过错误继续执行。这里的落盘等规则不细展开描述。同样的原理也可以用在mock上,只需要在Pack中增加需要mock的数据,就可以跑起来。

「引入前置节点」

上面的逻辑中可以看到有一些AND节点紧密绑定的关系,为了视图与配置简化,增加了前置(forward)节点概念,当且仅当前置节点执行结果为非false时才会执行本节点,语义与AND相连的两个节点一致。

专业的文档

code

Talk is cheap. Show me the code… 

github: https://github.com/zjn-zjn/ice 

gitee: https://gitee.com/waitmoon/ice 

doc: http://waitmoon.com/docs/#/

可视化编码!前端无缝接入代码可视化编辑

Posted: 31 Dec 2021 02:36 AM PST

背景

研发提效是亘古不变的话题,提效的基本思路离不开向 标准化、规范化 对齐,后续甚至走向智能化

主流的商业化低代码平台,基本就是希望将研发全链路都推向标准化、规范化,来达到提效的效果,但是对于需求本身是变化的,同时会形成对低代码平台的依赖,后续维护均需要依赖平台能力实现;

所以今天跟大家介绍的不是传统主流的低代码平台,介绍的是面向研发的、代码可视设计编辑平台;它更像是 dreamweaver、gui 可视编辑 之于 程序员。

它用于解决的问题有:

  1. 对低代码平台不形成依赖,二次开发可以无缝进入代码开发模式
  2. 同时支持所见即所得的可视编辑,用于提效,提示开发体验
  3. 提供物料生态,可自定义物料,提升物料使用体验,提升复用率

方案说明

image.png

功能 & 特性

编辑

反向定位

支持从视图定位代码位置
mometa-locate.gif

插入物料

可视化插入物料
mometa-insert-material.gif

删除视图

mometa-delete.gif

移动视图

mometa-move.gif

编辑代码

mometa-edit.gif

预览

路由隔离

单页应用中的路由跳转能够进行拦截控制,其他和正常预览保持一致

mometa-preview-url.gif

如何使用

安装依赖

npm i @mometa/editor -D

使用 antd 物料

  1. 安装 antd 物料
npm i @mometa/materials-generator @mometa-mat/antd -D
  1. 在项目根目录中创建 mometa-material.config.js
const { resolveLibMatConfig } = require('@mometa/materials-generator')  module.exports = [resolveLibMatConfig('antd')]

你也可以创建自己的物料库,数据结构规则见 Material 定义

接入编辑器

webpack.config.js 修改如下:

const MometaEditorPlugin = require('@mometa/editor/webpack')  module.exports = {   module: {     rules: [       {         test: /\.(js|mjs|jsx|ts|tsx)$/,         // 注意,只需要处理你需要编辑的文件目录         include: paths.appSrc,         loader: require.resolve('babel-loader'),         options: {           plugins: [isEnvDevelopment && require.resolve('@mometa/editor/babel/plugin-react')]         }       }     ]   },   plugins: [isEnvDevelopment && new MometaEditorPlugin()] }

注意:使用时,不需要开启官方预设的 react-refresh,mometa 默认会开启 react-refresh 能力

启动 webpack dev server,开启 http://localhost:${port}/mometa/ 即可

提供的例子可见 @mometa/app

其他

完整开源实现见 mometa,请不要吝啬你的点赞与star,后面会产出文章详细说明如何实现!持续关注

一份简单够用的 Nginx Location 配置讲解

Posted: 03 Jan 2022 04:24 AM PST

前言

Location 是 Nginx 中一个非常核心的配置,这篇重点讲解一下 Location 的配置问题以及一些注意事项。

语法

关于 Location,举个简单的配置例子:

http {    server {       listen 80;         server_name www.yayujs.com;         location / {           root /home/www/ts/;           index index.html;         }   } }

大致的意思是,当你访问 www.yayujs.com80 端口的时候,返回 /home/www/ts/index.html 文件。

我们看下 Location 的具体语法:

location [ = | ~ | ~* | ^~ ] uri { ... }

重点看方括号中的 [ = | ~ | ~* | ^~ ],其中 | 分隔的内容表示你可能会用到的语法,其中:

  • = 表示精确匹配,比如:
location = /test {   return 200 "hello"; }  # /test ok # /test/ not ok # /test2 not ok # /test/2 not ok
  • ~ 表示区分大小写的正则匹配,比如:
location ~ ^/test$ {   [ configuration ]  }  # /test ok # /Test not ok # /test/ not ok # /test2 not ok
  • ~* 表示不区分大小写的正则匹配
location ~* ^/test$ {          [ configuration ]  }  # /test ok # /Test ok # /test/ not ok # /test2 not ok
  • ^~ 表示 uri 以某个字符串开头
location ^~ /images/ {         [ configuration ]  }  # /images/1.gif ok

而当你不使用这些语法的时候,只写 uri 的时候:

/ 表示通用匹配:

location / {          [ configuration ]  }  # /index.html ok
location /test {     [ configuration ]  }  # /test ok # /test2 ok # /test/ ok

匹配顺序

当存在多个 location 的时候,他们的匹配顺序引用 Nginx 官方文档就是:

A location can either be defined by a prefix string, or by a regular expression. Regular expressions are specified with the preceding "~*" modifier (for case-insensitive matching), or the "~" modifier (for case-sensitive matching). To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

If the longest matching prefix location has the "^~" modifier then regular expressions are not checked.

Also, using the "=" modifier it is possible to define an exact match of URI and location. If an exact match is found, the search terminates. For example, if a "/" request happens frequently, defining "location = /" will speed up the processing of these requests, as search terminates right after the first comparison. Such a location cannot obviously contain nested locations.

翻译整理后就是:

location 的定义分为两种:

  • 前缀字符串(prefix string)
  • 正则表达式(regular expression),具体为前面带 ~*~ 修饰符的

而匹配 location 的顺序为:

  1. 检查使用前缀字符串的 locations,在使用前缀字符串的 locations 中选择最长匹配的,并将结果进行储存
  2. 如果符合带有 = 修饰符的 URI,则立刻停止匹配
  3. 如果符合带有 ^~ 修饰符的 URI,则也立刻停止匹配。
  4. 然后按照定义文件的顺序,检查正则表达式,匹配到就停止
  5. 当正则表达式匹配不到的时候,使用之前储存的前缀字符串

再总结一下就是:

在顺序上,前缀字符串顺序不重要,按照匹配长度来确定,正则表达式则按照定义顺序。

在优先级上,= 修饰符最高,^~ 次之,再者是正则,最后是前缀字符串匹配。

我们举几个简单的例子复习下:

server {     location /doc {         [ configuration A ]      }     location /docu {         [ configuration B ]      } }  # 请求 /document 使用 configuration B # 虽然 /doc 也能匹配到,但在顺序上,前缀字符串顺序不重要,按照匹配长度来确定
server {     location ~ ^/doc {         [ configuration A ]      }     location ~ ^/docu {         [ configuration B ]      } }  # 请求 /document 使用 configuration A # 虽然 ~ ^/docu 也能匹配到,但正则表达式则按照定义顺序
server {     location ^~ /doc {         [ configuration A ]      }     location ~ ^/docu {         [ configuration B ]      } }  # 请求 /document 使用 configuration A # 虽然 ~ ^/docu 也能匹配到,但 ^~ 的优先级更高
server {     location /document {         [ configuration A ]      }     location ~ ^/docu {         [ configuration B ]      } }  # 请求 /document 使用 configuration B # 虽然 /document 也能匹配到,但正则的优先级更高

root 与 alias 的区别

当我们这样设置 root 的时候:

location /i/ {     root /data/w3; }

当请求 /i/top.gif/data/w3/i/top.gif 会被返回。

当我们这样设置 alias 的时候:

location /i/ {     alias /data/w3/images/; }

当请求 /i/top.gif/data/w3/images/top.gif 会被返回。

乍一看两者很像,但细一看,就能看出两者的区别,root 是直接拼接 root + location 而 alias 是用 alias 替换 location,所以 root 中最后的路径里有 /i/,而 alias 中最后的路径里没有 /i/

所以如果你这样使用 allias 定义一个路径:

location /images/ {     alias /data/w3/images/; }

其实使用 root 会更好:

location /images/ {     root /data/w3; }

server 和 location 中的 root

server 和 location 中都可以使用 root,举个例子:

http {    server {       listen 80;         server_name www.yayujs.com;         root /home/www/website/;         location / {           root /home/www/ts/;           index index.html;         }   } }

如果两者都出现,是怎样的优先级呢?

简单的来说,就是就近原则,如果 location 中能匹配到,就是用 location 中的 root 配置,忽略 server 中的 root,当 location 中匹配不到的时候,则使用 server 中的 root 配置。

系列文章

博客搭建系列是我至今写的唯一一个偏实战的系列教程,讲解如何使用 VuePress 搭建博客,并部署到 GitHub、Gitee、个人服务器等平台。

  1. 一篇带你用 VuePress + GitHub Pages 搭建博客
  2. 一篇教你代码同步 GitHub 和 Gitee
  3. 还不会用 GitHub Actions ?看看这篇
  4. Gitee 如何自动部署 Pages?还是用 GitHub Actions!
  5. 一份前端够用的 Linux 命令

微信:「mqyqingfeng」,加我进冴羽唯一的读者群。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者 有所启发,欢迎 star,对作者也是一种鼓励。

No comments:

Post a Comment