技术细节

  • 引言

    这是一个系列文章,旨在了解如何使用Flex/Lex和Yacc/Bison进行词法和语法解析。在前面,已经完成了使用Lex/flex做基础的词法解析。接着,开始这个系列的第二部分,使用flex和bison完成一个简单的计算器。

    为了简化实现,将注意力放在简单flex和bison使用上,这里对计算器做了几个简化:

    • 只支持加、减、乘计算,暂时不支持除法,除法可能涉及到浮点类型,故暂时忽略
    • 不考虑整数溢出的问题,这里使用int类型(那么他存储与计算范围是有限的)

    也就是该程序可以计算加法、减法、乘法,以及他们的任意组合,如: 3+4、 3+4*2、 3+4*2-3、 3+4*2-3*2

    后续,还将考虑增加更急复杂的计算器,包括:

    • 实现,带有变量的计算器程序
    • 实现带来循环、带有条件判断的程序

    这里先从简单的开始。

    初次手写一个cal.l和cal.y

    这是在vim中写出的第一遍代码,包含了词法文件cal.l和语法文件cal.y,详细如下。这其中当然是有很多错误的,之所以,依旧写出来,是为了和正确代码对比,以此看看对哪些概念理解有偏差。如果你是找一个正确例子的话,可以跳过这一段。

    %{
    #inlcude <stdlib.h>
    %}
    /* 十进制整数 */
    %token INTEGER
    
    %union { 
        int a;
    }
    
    /* 操作符 + - * / */
    %token OPERATOR
    
    %%
    program:
        program expression \n { printf("%d",$2); }   // 这里就是以回车结尾,也可以考虑使用 = 结尾
        |
    
    expression:
    		  INTEGER
    		| expression '+' expression {$$ = $1 + $2}
    		| expression '-' expression {$$ = $1 - $2}
    		| expression '*' expression {$$ = $1 * $2}
    
    dec_integer: 
    		INTEGER
    			{
    			$$ = $1.a;
    			}

    这里也有几个已知的问题,例如:运算符的优先级没有定义,也就说4+3*2可能会算成14。没错,如果眼尖的话,还发现有一些拼写错误。

    接着是cal.l文件:

    #inlcude <stdlib.h>
    #include "y.tab.h"
    ​
    %}
    [:digit:]+ {
                yylval.a = atoi(yytext)
                RETURN INTEGER;
    }

    当然,这里面有很多的错误。一会儿来看后面正确的内容。

    修改cal.l和cal.y

    • 首先,是去解决已知的问题:运算符优先级如何去解决?关于什么是优先级、什么是结合律这里不做详述,这里有一篇文章讲得比较细致,几幅图也非常直观:Precedence and Associativity of Operators in Python。虽然是不同的语言,但意思是一样的。理解这个点还是比较重要的,例如在关系型数据库中,之前有遇到过这样的案例,可以思考一下如下的表达式 t.col < 2 or t.col < 11 and t.col > 4 是什么意思:
    -- 猜测一下,如下的 SELECT 查询是否能够返回记录:
    
    CREATE TABLE t(col INT); 
    
    INSERT INTO t values (1);
    
    WHERE t.col < 2 or  t.col < 11 and  t.col > 4

    扯远了,再回到cal.y文件,优先级和结合律需要进行如下修改,以定义”*”优先级高于”+”、”-“:

    %left '+' '-'
    %left '*'

    这里的代码先后,就定义了优先级;优先级越高,定义在越在后面;left表示,左结合。

    • 除了优先级,在cal.y语法规则中的定义部分,如果有字符,并没有使用引号。例如上面的cal.y的第17行的\n,是需要加上引号的,即 '\n'

    • 对于cal.y的中定义的语法规则,并没有定义返回值存储在联合体(YYSTYPE,也就是如下这里cal.y定义的唯一的那个联合体)哪个类型中。例如,没有定义”expression”这个语法规则,返回值是使用哪个值,虽然这里的联合体只定义了一个类型(即int a)。具体的,添加了如下代码:
    %token <a> INTEGER
    %type <a> expression

    完成这样的定义后,在lex的文件cal.l中,就可以对yylval进行赋值,如:yylval.a = atoi(yytext); 这时,对于yacc文件中cal.y,如果引用这个TOKEN,就可以使用$1(或者是$2、$3)来引用lex解析后的返回值,如:expression: INTEGER { $$ = $1;}

    • 于是,重新使用了独立的Token重新定义了运算符,并定义了优先级,如下:
    cat cal.y
    ...
    %token O_ADD O_MINUS O_MULTIPLY O_EQ
    
    %left O_ADD O_MINUS
    %left O_MULTIPLY 
    
    %token <a> INTEGER
    %type <a> expression
    ...
    
    cat cal.l
    ...
    "=" { return O_EQ;};
    "+" { return O_ADD;};
    "-" { return O_MINUS;};
    "*" { return O_MULTIPLY;};
    ...
    • 没有定义 yyerror 函数,程序也会编译不过去,会报如下错误:
    cal.tab.c:(.text+0x53b): undefined reference to `yyerror'

    按照文档,可以定义一个最简单的yerror函数(参考:The Error Reporting Function yyerror),如下:

    void
    yyerror (char const *s)
    {
      fprintf (stderr, "something error: %s\n over", s);
    }
    • 删除了无效的dec_integer规则,如果有无效的规则,也会失败
    • 将[:digit:]修改为[0-9]+。至于为什么[:digit:]不生效,后面做了一些搜索。为了避免歧义,需要额外再加一层中括号,写法也就是[[:digit:]]。详细参考:Patterns@Lexical Analysis With Flex

    完整的计算器程序文件cal.l cal.y

    补充一个main入口函数,修改cal.l、cal.y即可。

    修正后的cal.l

    %{
        #include "cal.tab.h"
        #include <stdio.h> 
    %}
    %option noyywrap
    %%
    [0-9]+ {
    			yylval.a = atoi(yytext);
    			return INTEGER;
        	   }
    
    "=" { return O_EQ;};
    "+" { return O_ADD;};
    "-" { return O_MINUS;};
    "*" { return O_MULTIPLY;};
    
    %%

    修正后的cal.y

    %{
    	#include <stdlib.h>
    	#include <stdio.h>
    
    
    int main(){
    	yyparse();
    }
    
    void
    yyerror (char const *s)
    {
      fprintf (stderr, "something error: %s\n over", s);
    }
    
    %}
    
    
    %union { 
        int a;
    }
    
    %token O_ADD O_MINUS O_MULTIPLY O_EQ
    %token <a> INTEGER
    
    %left O_ADD O_MINUS
    %left O_MULTIPLY 
    %type <a> expression
    
    %%
    program:
        |
        program expression O_EQ { printf("result is : %d",$2); }   
    ;
    expression:
    		  INTEGER { $$ = $1;}
    		| expression O_ADD expression {$$ = $1 + $3; }
    		| expression O_MINUS expression {$$ = $1 - $3; }
    		| expression O_MULTIPLY expression {$$ = $1 * $3;}
    ;

    编译与执行

    然后,就可以生成c文件并编译可执行文件了:

    lex cal.l 
    bison -d cal.y 
    gcc cal.tab.c lex.yy.c -o a.out
    ./a.out
    4+3*2=
    
    也可以像这样:
    lex cal.l && bison -d cal.y && gcc cal.tab.c lex.yy.c -o a.out && ./a.out

    附带注释说明的cal.y文件

    %{                                    // %{ ... %}  包含了完整的C语言代码        
    	#include <stdlib.h>           // 这里包含需要的头文件、入口函数main
    	#include <stdio.h>            //    以及一个简答的yyerror函数
    
    
    int main(){
    	yyparse();
    }
    
    void
    yyerror (char const *s)
    {
      fprintf (stderr, "something error: %s\n over", s);
    }
    
    %}
    
    
    %union {                             // 这是一个非常重要的联合体,用于定义
        int a;                           // 各种不同类型的Token所返回的数据
    }                                    // 广泛的被yylex使用,并用于与.yy文件共享数据
                                         // 也就是 YYSTYPE 
    
    %token O_ADD O_MINUS O_MULTIPLY O_EQ
    %token <a> INTEGER                   // 这里表示INTEGER(这是一个被lex识别的token)
                                         // INTEGER(被lex识别的token返回值为YYSTYPE.a
    %left O_ADD O_MINUS                  // 这里定义 + -为左结合律
    %left O_MULTIPLY                     // 这里定义 * 为左结合律,并且优先级高于 + -
    %type <a> expression                 // 这里定义语法规则(grammer rule)expression
                                         // 的返回值为 YYSTYPE.a
    %%
    program:                             // 这是start symbol,所有的program都需要符合该定义
        |
        program expression O_EQ { printf("result is : %d",$2); }   
    ;
    expression:
    		  INTEGER { $$ = $1;}
    		| expression O_ADD expression {$$ = $1 + $3; }
    		| expression O_MINUS expression {$$ = $1 - $3; }
    		| expression O_MULTIPLY expression {$$ = $1 * $3;}
    ;

  • 最近,Google Cloud SQL(Google云上的RDS)做了一次大的产品调整与发布:将原来的Cloud SQL分为了两个版本,分别为”Enterprise”和”Enterprise Plus”版本。本文概述了两个版本的异同,以帮助大家更好的了解Google Cloud SQL。

    关于”Enterprise”和”Enterprise Plus”版的Cloud SQL实例

    整体上:

    • Google Cloud SQL相当于AWS或阿里云平台的RDS,Cloud SQL for MySQL就相当于AWS RDS for MySQL
    • 这次调整之后,原来的Cloud SQL for MySQL/PostgreSQL全部变更为Enterprise版本,价格和产品本身没有什么改动,只是名称发生了变化
    • 新增的Enterprise Plus版本提供了更高的产品能力,例如通过硬件/软件优化提供的Data Cache能力,可以提升MySQL的读取和写入性能;Enterprise Plus还提供了更大的规格、更灵活的CPU/内存配比等能力
    • Enterprise Plus资源的价格比Enterprise版本贵约30%,Data Cache空间是额外计费的
    • 本质上,猜测,Enterprise Plus使用了更新更强的机型,具备非常强的本地存储能力(本地可能使用NVMe SSD或者其他存储设备),在数据库层则利用该能力使得用户获得更好的读与写的能力
    • 从产品发布节奏来看,今年GCP(Google云)增加了在Cloud SQL上的投入,这一点与阿里云现在应该是比较类似的。而在以前,在Google数据产品体系中,Spanner、AlloyDB或者BigTable才是投入最大的产品,而Cloud SQL似乎不受重视。现在大概发现,无论Spanner、AlloyDB架构多领先,最简单的Cloud SQL才一直都是更多用户的首选。
    • 这次更名调整,感觉是比较混乱的。对于Cloud SQL for MySQL来说,”Enterprise”和”Enterprise Plus”之间的区别还是比较明显,一个有Data Cache、另一个没有。但对于PostgreSQL来说,两者的区别更像是对资源多少的使用的一种简单限制(最大规格、cpu内存比、PITR的时间限制等)。另外,当前SQL Server又并不区分这两个类型。所以,整体给人感觉比较混乱。而像AWS Aurora新增一个I/O-Optimized选项的做法可能会更简单一点。GCP这种”化简为繁”做法,也许和业务的压力有一定的关系,更像是为了概念而做的产品决策,最终让用户对混乱的产品名称和定位买单,这种事情在大公司的企业级产品中,还是经常出现的。事情做不了什么突破,那么就在产品名称上做一些突破吧(这是一段不负责任的“臆断”,谨慎参考)。
    (more…)
  • 引用80后初中英语课本中的一句话:“how time flies”。是不是还在突然放开后,全家感染的错(meng)愕(bi)中,是的,一年又过去了。在过去的一年中,云数据库技术行业动态一共发布了28期(参考),趁这会儿来做个年度的总结吧。

    云数据库的年度发布

    • Google云发布AlloyDB(参考);火山引擎veDB公测上线
    • 阿里云数据库李飞飞发布全新品牌 “瑶池”,涵括PolarDB、AnalyticDB、Lindorm、DMS等产品,旨在打造云原生一站式数据管理与服务
    • TiDB发布了发布6.0.0版本,TiFlash正式开源,TiDB Cloud正式上线
    • OceanBase发布4.0版本,OceanBase Cloud上线
    • 腾讯云发布KeeWiDB,兼容Redis
    • SQL Server 2022发布,支持Windows和Linux,全面集成Azure云服务
    • Aurora Serverless v2 发布(参考)
    • 华为云GaussDB发布三大架构演进:Serverless、Regionless和Modeless
    • Gartner数据库魔力象限2022发布:阿里领先、腾讯再次进入、华为退出(参考)

    盘点小结:可能很多开发者还没有真正摸清什么是云原生数据库,但是在云厂商一侧,云原生数据库已经是”现在进行时”了。自2014年AWS发布Aurora,2017年阿里云发布PolarDB,几乎所有的云厂商都跟进这一方向,今年,一向”高冷”的Google也发布了AlloyDB,火山引擎发布了veDB。华为云则有自己的GaussDB系列,腾讯云则是TDSQL系列。

    今年,Aurora和阿里云RDS/PolarDB则又在云原生上再向前走了一大步,将数据库的Serverlss弹性能力提升到秒级别(参考)。

    阿里云今年也则在数据库产品平台层面做了一个大动作,发布了全新的”瑶池”品牌,涵括PolarDB、AnalyticDB、Lindorm、DMS等产品,旨在打造云原生一站式数据管理与服务。

    各个厂商都在All in Serverless

    在这个充满变化的时代,数据库的Serverless是明确的。

    • AWS发布Opensearch Serverless之后,实现了整个分析类全线产品Serverless
    • AWS上线了“Neptune Serverless”
    • 阿里云RDS/PolarDB发布Serverless实现秒级弹性;几乎整个瑶池体系的数据库均在All In Serverless
    • PingCAP 推出 TiDB Cloud Serverless Tier BETA 版
    • TDSQL-C 的Serverless能力实现秒级”启停”
    • 中国信通院云大所联合阿里云发布《Serverless数据库技术研究报告》:详情

    盘点小结:在AWS推出Lambda之后,就在全面的All In Serverless,整个基础服务从底层架构、基础计费、产品形态都在向Serverless发展。相比国内Serverless,AWS则有两层含义,一方面是产品规格与计费实现细粒度的按量付费;另一方面,则是全面的与AWS Lamdba等无服务产品的集成,面向用户提供一个完整的无服务的云计算服务。这一点,在阿里云近期的一篇文章中也有介绍,感兴趣的可以去阅读:关系型数据库与Serverless。国内大多数厂商的Serverless都更多的是偏向于实现前者,也就是更细粒度的变配与计费。今年,阿里云RDS在推出Serverless同时,也很快推出了Data API的能力。算是,走到了Serverless的第二层了,具体的可以参考这篇文章:云数据库RDS MySQL Serverless已来

    不过,无奈,据了解,阿里云的”函数计算”使用率应该极低,与Lamdba在AWS的地位完全不可同日而语,所以,这里的Data API能够给用户提供的价值也是受限的,只能算是一个面向未来的布局吧。

    开源:比看起来更困难

    • 6月,Apache IoTDB(Timecho)完成近亿元天使轮融资,专注于IoT领域时序数据存储:参考
    • 7月,开源一体化实时 HTAP 数据库 StoneDB正式发布:参考 参考
    • 8月,蚂蚁集团时序数据库CeresDB正式开源:参考
    • 9月,华为云正式开放时序数据库openGemini源代码,参考
    • 9月,火山引擎也计划将ByConity中部分模块开源:参考

    盘点小结:在全球范围内,开源数据库在市场上占领着重要位置,也有非常多商业和市场成功的案例(MongoDB、ClickHouse等)。在国内,TiDB也凭借开源,在分布式关系型数据库领域取得比较高的市场知名度。在今年,也有部分大厂的数据库选择了开源,也有部分新的开源项目。值得一提的是,在之前StarRocks(原DorisDB)就是基于Apache Doris,今年SelectDB也基于Doris推出,如果围绕开源发展,其早期就要做好知识产权与品牌的管理,否则在产品发展到一定程度的时候,比较容易出问题。

    近期,MariaDB 通过SPAC模式在美上市。上市后,市场预期情况并不理想,连续三个交易股价持续下跌。MySQL构筑的产品、市场、生态以及开发者心智已经非常强大,MariaDB想在这个方向再取得更大的战果,其实并不容易。

    开源有多重含义,其中一重则是”市场”,通过开源培育市场,让初阶开发者、小的业务场景免费试用,然后通过原厂云服务降低用户使用成本、又或是通过企业级能力再进行盈利,从而形成商业的闭环。总的来看,这条路并不很容易。

    2022中国数据库年度人物

    (注:仅代表本文作者观点)

    • 阿里云数据库负责人李飞飞
    • OceanBase创始人阳振坤
    • 达梦数据库创始人冯裕才

    没什么小结的,只有敬佩,他们已经/正在/将要影响中国数据库技术的发展。

    虽然是寒冬,但数据库领域投资火热

    虽然,在今年整个行业大环境遇冷,尤其是下半年,各个大厂纷纷裁员并收紧支出。但从投融资来看,基础软件投资与研发依旧非常火热,在今年的数据库项目方向,处于早期的项目融资就接近20亿。另外,据了解,今年还有很多项目有非常大额的融资,只是还没有公开。

    整体来看,2022年可以说是一个中国数据基础软件创业的小高潮。

    2020年snowflake在美国IPO,2021年Databricks融资估值达380亿美元。以这两家公司为代表的基础技术企业融资,可以说点燃了资本和技术创业的火苗,在2022年,虽然整体投资是在收缩,资本一定程度上,依旧延续了去年的势头,在基础领域资本依旧在增加下注,相比之前,只是更加谨慎了。而过去两年,消费市场投资虽然低迷了很多,但之前消费市场成长起来的大型企业,其背后的基础技术和团队都已经非常强大,这也是这波浪潮背后,除了国家基础设施被重视之外的另一个重要原因。

    还可以看到,面向特定场景(图、时序、向量等)的数据库在兴起。除了经典的OLTP数据库以及分析方向,诸如图数据库NebulaGraph、时序数据库Timecho、向量数据库Milvus也在持续”发育”。

    投融资的热闹,离产品的成功还有非常…非常(非常多的非常)远的距离。产品最终需要获得市场和用户的检验,非常期待这批初创企业,在未来能够成长出更多的参天大树:

    • 4月,实时数仓SelectDB完成超3亿元融资(天使和天使+轮)
    • 6月,DataStax获1.15亿美元投资,开始关注中国市场
    • 6月,开源时序数据库Timecho完成近亿元天使轮融资
    • 7月,达梦数据库科创板上市申请被受理
    • 8月,MariaDB收购地理信息技术提供商“CubeWerx”。
    • 8月,向量数据库公司 Zilliz 宣布完成 B+ 轮 6000 万美元融资
    • 8月,专注于云数据平台的大数据服务商「数新网络」完成数千万元Pre-A轮融资
    • 8月,YAOBASE云尧科技完成千万元种子轮融资,多位业内大咖作为天使投资人参与本轮融资
    • 9月,NebulaGraph获得获得数千万美元的A轮融资
    • 9月,「格睿云Greptime」完成数百万美元天使轮融资
    • 9月,「开云集致」完成千万元级别的Pre-A轮融资
    • 10月,国产数据库公司瀚高获浪潮数亿元战略投资
    • 10月,南大通用宣布完成数亿元新一轮融资
    • 10月,MonoGraphDB完成数千万元天使轮融资
    • 10月,星环科技成功登陆科创板
    • 11月,DuckDB 融资4750万美元
    • 11月,EdgeDB 宣布完成 1500 万美元A轮融资
    • 12月,达梦数据库科创板上市获批
    • 12月,MariaDB 通过SPAC模式在美上市

    “脸谱化”的云数据库厂商

    一直以来,文章风格都很严肃,这次轻松一点。”脸谱化”本来大多数时候是贬义词,不过这里是希望通过拟人的方式看看各个云数据库厂商的情况:如果各个云数据库厂商是一个班的同学,那么会是怎样?

    • 先说说领头羊AWS数据库。AWS像是班上的班长,成绩优秀,学习刻苦,为人诚恳,虽然已经每次考试都名列前茅,但是依旧非常努力。毕竟,后面的第二、三名各个都虎视眈眈。
    • Azure是一个回头浪子,原来对开源很抵触,云计算的起步也比较晚,但是非常有天赋,加上底子其实还是非常不错的,现在又极其努力,所以已经成为了班上的前几名。
    • GCP是一个走竞赛路线的极客,学习成绩一直一般,但是在“开源”“和“技术深度”上都非常强,一直希望通过“开源”、“数据分析”等技术能够保送到理想大学,超过前面同学。
    • Oracle像是一个数学竞赛特长生,在单个领域极其强,现在Oracle想要成为一个各个科目(云计算)都非常强的人,要和所有玩家一起参加全国统一高考,所以现在在全力的补课。不过,现在他的物理、化学(IaaS等服务)等学科落后比较多,追赶起来很吃力。
    • 阿里云是班上的学习委员,虽然出生在遥远的东方,但是学习一直非常刻苦,也很执着,加上天赋还可以,目前,也已经成为班级的前几名。
    • 火山引擎则是一个转学来的插班生,之前在机器学习、视频、分析技术上非常强,现在也进入了云计算领域,还在奋力追赶班上其他同学。
    • 华为云则是一个中等生,家境虽然殷实,但是危机不断。自己则重点发展openGauss和GaussDB和这两个方向。这个同学一直非常努力,也拿了很多的“国家奖学金”,不过因为家庭的其他事情很闹心,学习也时常分心。
    • 诸如人民云等国资云都是属于家里有矿的大户人家,也有着完全不一样的使命和目标,不过也因为身份特殊,不一定什么时候就回家继承家族产业去了。
    • 腾讯云则勉强算是班上的优等生,并在最近一次模拟考试中取得了不错的成绩。

    不过,总的来说,云计算所在的学校,应该算是一个贵族学校。早期就需要非常大的投入,而且投入周期也很长,如果不是家里有矿的,则很难支撑。不过,目前,云计算的价值和其战略意义已经早就没有人质疑了,所以到底谁能够站到最后,还未可知。

    其他

    ARM vs x86:国内和国外云厂商都已经开始在加注ARM,不过两者的原因略有一些不同。AWS发布了新的Graviton3、Azure推出AMD实例、阿里云也发布了基于ARM的架构的RDS MySQL、PostgreSQL。在海外,推出ARM芯片核心在于从性价比角度去解决用户问题,而国内,除此之外,还在尝试去解决芯片依赖的问题。

    多云:“Oracle的Larry和微软Azure的Satya宣布将进一步推进两家公司在多云战略上的合作”。多云已经是用户的必然选择,云厂商之间也在通过一些合作,增加用户使用多云的便利性。

    PostgreSQL在经历一场持续的崛起:今年GCP的AlloyDB发布选择了优先支持PostgreSQL、Azure Cosmos DB也支持PostgreSQL、Google Spanner也提供了PostgreSQL接口、StackOverflow的调研等都明确显示了PostgreSQL正在经历一场慢热的崛起,大家可以考虑投入更多的关注。好了,就做这些小结吧。

    祝大家自己和家人都快点从新冠中恢复过来,迎接全新的2023吧。

  • 博客老早就长满了草,最近在锄草。

    发现问题

    最近总有人告诉我,说博客不能访问了。开始只是直接去重启一下httpd,恢复了就不管了。不过最近有点频繁出现不能访问的情况,甚至Google给我发邮件说”Increase in “404” pages on http://orczhou.com/”:

    Snip20160427_25

    于是,打算探究一下原因。 (more…)

  • Amazon RDS价格一瞥

    ·

    本文尝试通过一些直观的数据和表格,来看看Amazon某个规格的RDS实例到底是什么价格以及如何计费。

    亚马逊RDS计费分为两个主要的部分,一个是“实例费用”(CPU和内存),另一个是“存储费用”(磁盘容量和IOPS)。这两类资源的费用,又细分为单可用区和多可用区,另外,还可以选择“按小时计费”、又或者是“包年计费”的方式购买,这些对价格都有很大影响。本文分多个部分细致介绍了亚马逊如何计算一个RDS实例的价格。

    “实例费用”

    “基本规格”

    基本规格根据CPU和内存使用来划分,Amazon RDS有如下基本规格:

    Snip20150319_9 (more…)

  • 在上上周给下厨房做过一次数据恢复(故障回顾:故障发生的技术总结 致歉信),恢复使用了开源工具Percona Data Recovery Tool for InnoDB(后面简称PDRTI),这里分享一下期间的注意事项,和遇到MySQL数据丢失的一些应对。

    本文主要介绍在使用Percona Data Recovery Tool for InnoDB时候的一些注意事项,并不包括具体的step by step的使用步骤,使用文档可以参考:Reference Manual and Documentation(more…)