玩转Markdown——数据的分离存储与组件的原生渲染

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了玩转Markdown——数据的分离存储与组件的原生渲染相关的知识,希望对你有一定的参考价值。

参考技术A


前言

最近笔者把之前写的文章( markdown )数据,全部同步到数据库里,来交给多端去实时渲染。在同步的过程中,却出现了一些问题。

笔者这里举个例子,让大家有所感受:

而且通过这个思路,还演化出 MDX 这个格式,大大的增强了 JSX Markdown 混合书写时的开发体验,增强了它的表现能力。

怎么做到的呢?

我们知道,原生 Markdown 功能很少,不会做任何花哨的事情,这导致它无法满足大量的场景。于是乎,大量的开发人员充分发挥了 主观能动性 ,定制了许多的 Markdown 编译器。

以著名的 Typora 为例,它就集成了 flowchart.js , mermaid 这类的图表库。我们可以在 md 里快速的生成一些简单的图表,但是遇到复杂的 case 时,可操控性还是远远弱于代码的。(这种情况,通常会在编辑器外部,先把图表做好,再把图片导出,插入 md 里)

甚至还出现了 nodeppt 这样,使用 markdown 来制作 ppt 的包。笔者曾经使用过一段时间,认为使用的场景,还是以部门内部的分享为主。受限于许多难记的语法和 md 自身的表现力,在遇到高自定义化的场景时,制作成本会远远超出 powerpoint

markdown 数据的分离存储

那么进入正题了,如何对 markdown 内不同的数据进行归类呢?

我们知道,不进行预处理的话,直接存进数据库里,无非就是一堆字符串。这堆字符串里藏着的数据,去实时处理,就是对计算机算力的浪费。

许多的 markdown 解析器,也都能够支持像 yaml json toml csv 等数据格式,此时预先把它们存进数据库就很有必要了。

怎么解析呢? 通常的做法就2字, 标记 ,在编写时,把它们用特殊的 flag 标识起来,比较通用的做法有:

--- code --- => yaml

---toml code --- => toml

---json code --- => json

这种做法本质上,和代码染色类似:

```js(染色语言) code ```

于是在标记出来之后,我们就可以非常容易的,对这堆字符串,进行 截取解析 分发给不同的解析引擎处理 了。现有的实现也很多,比如 gray-matter

但是这只解决了数据分离的问题,还有一个组件渲染的问题没有解决。

组件的原生渲染

在谈这个之前,先看看 md 是如何转成 html 的:

markded , markdown-it , unified(remark) 为例

它们无非是 把 md 先解析成 tokens/mdast , 例如:

然后再交给 html renderer 去处理的,上述的例子可以很容易的看出它的结果。

那么非转化成 html ,而去转化为原生标签怎么做呢?解决方案也有很多。

先说一下我实现的方案:

这一段字符串原封不动的存入数据库中,

然后在其他平台的场景,都去编写或者移植一个 Markdown 解析器,接着呢

这种做法本质就是 条件渲染 ,相当于一个 if 分支。

这个解决方案需要在不同的平台上,把 icebreaker-love-music 这个组件都实现一遍,并作为插件挂载在 Markdown 解析器中。

它的缺点也是很明显的:

另一种的畅想

另外一种则是我的畅想了,我们能否把组件本身,进行编译,变成一种 IL (Intermediate Language)的存在,交给各个端,进行原生渲染呢?

比如我们知道, web component 浏览器端原生支持

vue 组件可以被 @vue/web-component-wrapper 转化为 web-component

react 则有 react-web-component

那么 web-component 有可能,能依托一个像 QuickJS 这样的 Javascript Engine ,在原生环境进行实时的编译渲染吗?

以上这些就是笔者的一些愚见,如有想法,欢迎大家讨论和指点。

附录(ast的生成与转化)

syntax-tree

mdast-util-from-markdown

mdast-util-to-hast


bucket表:数仓存算分离中CU与DN解绑的关键

摘要:Bucket存储是数据共享中重要的一环,当前阶段,bucket存储可以将列存中的CU数据和DN节点解绑。

本文分享自华为云社区《存算分离之bucket表——【玩转PB级数仓GaussDB(DWS)】》,作者:yd_278301229 。

在云原生环境,用户可以自由配置cup型号、内存、磁盘、带宽等资源,需要在计算和IO之间做平衡;如果计算和存储耦合,扩缩容时数据要在节点之间移动,同时还要对外提供计算,性能会大受影响。如果存算分离,计算出和存储层可以独立增加节点互不干扰,这其中一个关键点是做到数据共享。Bucket存储是数据共享中重要的一环,当前阶段,bucket存储可以将列存中的CU数据和DN节点解绑。

一、bucket表在存算分离中的作用

通过存算分离,把DWS完全的shared nothing架构改造成计算层shared nothing + 存储层shared storage。使用OBS替换EVS,OBS对append only存储友好,与列存CU存储天然适配;由于存算分离数据共享,对写的并发性能不高,在OLAP场景下读多写少更有优势,这一点也是和列存相匹配的,目前主要实现的是列存的存算分。
在当前。bucket表在存储层共享中,为了将CU数据和DN节点解绑,主要做了两件关键的事,CUID和FILEID全局统一管理。我们来看看为什么这两件事能把CU和DN节点解绑以及带来的好处。
为了解释这个问题,先看看目前shared nothing架构中,建库和存储数据的过程。

二,当建立一张列存表并存储数据时,我们在做什么

建一张列存表时,主要要做以下两步:

1,系统表中建立表的数据。
2,为列存建立CUDesc表、Delta表等辅助表

当存储数据时,主要做以下几步:

1,根据数据分布方式,决定数据存储到哪个DN。
2,把列存存储时需要的辅助信息填入CUDesc表、Delta表等辅助表。
3,把存储用户数据的CU存储本地DN。

在上面的过程中,由于DN之间互不干扰,那就需要各自管理自己的存储的表的信息。

CUDesc表的一大功能是CU数据的“指路牌”,就像指针一样,指出CU数据存储的位置。靠的是CUID对应的CUPoint(偏移量),加上存储在DN的文件位置就能标注出具体的CU数据,而文件名就是系统表中的relfilenode。

由于在MPPDB的存算一体中,数据都存储在DN节点,DN节点之间互不干扰,CUID和relfilenode各个DN节点自己管理,只要自己不出问题就行了,也就是“各人自扫门前雪莫管他人瓦上霜”,例如下图,显示一张列存表在集群中的存储状态。

CN把要存储的数据根据分布算法(例如对DN数量做除法取余数)把1,3,5存到DN1,把2,4,6存到DN2。DN1此时生成存储CU文件的relfilenode是12345,每插入一次CUID,就把该表的CUDesc表CUID自增,DN1只要把自己的数据管理好,与DN2无关。DN2同理。

三,数据共享和扩缩容时,遇到的困难

1,数据共享时遇到的困难

如上所述,当用户想查询数据1,2,4,6时,该怎么办。因为DN1和DN2都不可能单独完成任务,就需要共享数据了。问题就来了,DN间肯定是想以最小代价来完成数据共享,系统表最小,CUDesc表也很小(就像指针一样,同等规模下,只有CU数据的1/3000左右),CU数据最大。假如最后决定以DN2来汇聚所有结果,就算DN1把系统表和CUDesc表中的数据传给DN2,DN2也看不懂,因为在DN2上,relfilenode为12345可能是另外一张表,cudesc表为中CUID为1001的CUPoint也不知道是指向哪儿了(data1,data2),没办法,只能是DN1自己计算,最后把data1的CU数据通过stream算子发送给DN2。DN1迫不得已选了最难的那条路,CU的数据量太大,占用了网络带宽,还需要DN1来参与计算,并发上不去。

2,扩缩容时遇到的困难

当用户发现DN1,DN2节点不够用时,想要一个DN3,该怎么办。根据分布假设的算法(对DN数量做除法取余数),data3和data6应该要搬去DN3才对。系统表还比较好搬,无非是在DN3上新建一份数据,CUDesc表也好搬,因为数据量小,再把CUID和CUPoint按照DN3的逻辑写上数据就好了,CU数据也要搬,但是因为CU数据量大,会占用过多的计算资源和带宽,同时还要对外提供计算。真是打了几份工,就赚一份工资。

总结起来,困难主要是

1)系统表元数据(计算元数据)

每个节点有自己的系统表元数据,新增dn必须创建“自己方言”的系统表,而实际上这些系统表内容是“相同的”,但是dn之间互相不理解;

2)CUDesc元数据(存储元数据)

每个节点的CUID自己分配,同一个CUID在不同节点指向不同的数据,CU无法在dn之间迁移,因为迁移后会混乱,必须通过数据重分布生成dn “自己方言的CUID”
CU的可见性信息(clog/csnlog)各自独立管理,dn1读取dn2的cudesc行记录之后,不知道记录是否可见

3)CU用户数据

filepath(relfilenode) dn节点各自独立管理,dn1不知道到哪里读取dn2的数据

四、揭秘CU与DN解绑的关键——bucket表

1,存储映射

bucket表的存储方式如图,CU的管理粒度不再是DN,而是bucket,bucket是抽象出来的一个概念,DN存储的,是系统元数据和bucket对应的CUDesc元数据,由于在bucket作为存储粒度下,CUID和FILEID是全局统一管理的,DN只需要懂全局的规则,并且拿到别的bucket对应的CUDesc元数据,那就可以很方便的去OBS上拿到CU数据了。通过这种方式,把本该存储在DN上的CU数据,映射到OBS上,可以保证DN间共享数据时相互独立,换句话说,每个DN都读的懂其他DN的CUDesc数据,不再需要把CU数据喂到嘴边了。

2,全局CUID和FILEID表生成

CUID不再是各节点自己管理生成,而是全局唯一的。其原因是CUID与bucket号绑定,特定的bucket号只能生成特定的globalCUID,与此同时,relfilenode不再作为存储的文件名,而是作为存储路径。CU存放的文件名为relfilenode/C1_fileId.0,fileId的计算只与bukcet号和seqno有关。

这样V3表就建立起了一套映射关系(以V2表示存算一体表,V3表示为bucket表):

step 1,数据插入哪一个bucket由分布方式来确定,例如是RR分布,那么就是轮巡插入bucket。
step 2,CUID是多少,由bucket粒度级别的CUID来确定,比如+1自增作为下一个CUID。
step 3,在bucket上存储bucket粒度级别的fileId。
step 4,生成全局唯一fileId,由bucketid和bucket粒度级别的fileId来生成,对应生成的CU插入该fileId文件名的文件。
setp 5,生成全局唯一globalCUID。由bucketid和bucket粒度级别的CUID计算得到全局唯一的globalCUID。

CUDesc表中,也为bucket表新建了一个属性fileId,用来让DN查找到OBS上的CU数据所在的文件。

3,共享数据和扩缩容的便利

如上所述,DN可以通过全局CUID和FILEID来找到CU数据,在数据共享时,不再需要其他DN参与大量的计算和搬迁CU,扩缩容时,也不需要搬迁CU了,只需要正确生成系统表中的信息和搬迁CUDesc即可。

最后,来看一看bucket表在OBS上存储的CU数据:

点击关注,第一时间了解华为云新鲜技术~

以上是关于玩转Markdown——数据的分离存储与组件的原生渲染的主要内容,如果未能解决你的问题,请参考以下文章

bucket表:数仓存算分离中CU与DN解绑的关键

云原生数据库的幕后英雄—浅谈分布式数据库的计算和存储分离

云原生数据中台概念-15个问题和解释

分布式1024节点!1天玩转 PolarDB-X 超大规模集群

大话云原生数据库中的存算分离

大话云原生数据库中的存算分离