简单生活

  • 概述

    在2018年,AWS首次推出Graviton EC2实例,2020年7月AWS RDS正式支持Graviton 2的实例,就在前两天,在最新的AWS re:Invent大会上,AWS已经推出了第四代Graviton 4实例。现在,AWS的Graviton已经较为成熟,也在大量的企业和应用被广泛使用。AWS官方也宣称使用Graviton 2的RDS实例能够有52%的性价比提升(参考)。这里,来通过标准的Sysbench测试来实测一下,看看实际Graviton 2实例的效果。

    与上次阿里云测试相同,这次依旧是使用了同样的测试工具和场景,对较为常用的4c16gb,即db.m6g.xlarge和db.m5.xlarge实例,进行并发数分别为2 4 8 16 24 32 48 64 96 128的测试。

    测试结论

    在性能上,平均来看,x86要比Graviton实例性能高约12.7%;x86规格延迟要比Graviton规格低15%。也注意到,在超高并发的情况下(并发超过96时),Graviton实例与x86实例无论是在性能,还是延迟上,是比较接近的,不过,这时候系统压力太大、延迟太高,对实际使用并没有太大的参考价值。

    为了更加直观的做性价比的比较,这里选取16并发时的数据进行对比。在16并发下,Graviton实例的TPS是275,db.m6g.xlarge价格是$0.836/小时;x86实例的TPS是341,价格是$0.94/小时。那么每100TPS,Graviton价格是0.304,x86是0.275。所以,在16并发时,相比之下,x86规格的性价比更高,高出Graviton实例10%。这个结果与测试之前预期还是非常不一样的,也与AWS宣称的完全不同。

    只有在超高的并发情况下(96/128并发),Graviton实例的吞吐量才与x86接近,这时,Graviton实例才表现出接近10%的性价比优势。但这种高并发在实际场景中并不是常态,所以并没有很强的参考价值。

    这个测试结果与预期的差别比较大,所以后来又再做过一次测试,结果与这次基本相同。所以,性能到底怎样,还是最终要自己实际测试,因为宣传的数据,通常都是非常极端的适配该产品的场景,并不是真实的场景。

    一下两幅图分别是TPS和平均延迟的对比图:

    横坐标是sysbench的并发线程数,纵坐标分别为tps和平均的延迟。

    测试模型说明

    这里使用了sysbench的读写混合模型(oltp_read_write)进行测试,单表大小为100万,共十个表,单次测试时长为300秒,分别测试了如下的并发度的性能表现:2、4、8、10、12、14、16、24、32

    实例配置与价格

    这里选择了较为常用4c16gb的实例进行测试,各个选项尽量选择默认选项,以更加接近的模拟用户实际场景,具体的,版本是AWS默认的8.0.33、多可用区版、存储默认加密、gp3存储、100GB空间、3000 IOPS、Performance Insight也默认开启。完整的选项参考如下:

    AWS的价格分为计算节点价格(CPU与内存)、存储价格、IOPS价格,这里仅关注计算节点价格。存储和IOPS对于ARM和x86实例来说,是相同的。这里的选择的是东京地区、多可用区实例的价格,如下:

    后续,也还将测试基于 io1(Provisioned IOPS SSD) 存储的RDS。

    详细测试数据参考

    AWS RDS Graviton(db.m6g.xlarge/gp3/100gb/3000iops/8.0.33)

    threads|transactions| queries| time |avg/Latency|95%/Latency
          2|       11951|  239020|300.03|      50.21|      55.82
          4|       23322|  466440|300.04|      51.45|      57.87
          8|       43654|  873080|300.05|      54.98|      65.65
         16|       82519| 1650380|300.05|      58.17|      70.55
         24|      120541| 2410820|300.06|      59.74|      73.13
         32|      156680| 3133600|300.07|      61.28|      74.46
         48|      218709| 4374180|300.06|      65.85|      81.48
         64|      269430| 5388600|300.08|      71.27|      90.78
         96|      329366| 6587320|300.07|      87.45|     121.08
        128|      351579| 7031580|300.11|     109.24|     164.45

    AWS x86实例(db.m5.xlarge/gp3/100gb/3000iops/8.0.33)

    threads|transactions| queries| time |avg/Latency|95%/Latency
          2|       13357|  267140|300.03|      44.92|     112.67
          4|       27539|  550780|300.03|      43.57|      50.11
          8|       55330| 1106600|300.04|      43.38|      51.94
         16|      102408| 2048160|300.05|      46.87|      56.84
         24|      145718| 2914360|300.05|      49.41|      61.08
         32|      186619| 3732380|300.05|      51.44|      63.32
         48|      260415| 5208300|300.04|      55.30|      69.29
         64|      306939| 6138780|300.08|      62.56|      82.96
         96|      330131| 6602620|300.09|      87.25|     123.28
        128|      348095| 6961900|300.12|     110.34|     155.80

    小结

    AWS RDS在发布Graviton 2实例时,曾宣传Graviton 2实例有52%的性价比提升。但是在这里的Sysbench混合读写的测试场景下,反而是x86性价比优势更加明显,16并发是,x86性价比要高出Graviton实例10%。而仅是在超高并发时,Graviton实例性价比才比x86高10%。但是,一般我们不会让4c16的实例,运行在如此高的压力下,所以后面这种情况的参考意义并不强。

    虽然AWS曾大量宣传Graviton实例,但是实测下来并没有什么性价比的优势。所以,在数据库应用场景下,使用AWS Graviton实例的必要性,似乎并不高。另外,也注意到,RDS Graviton 3的实例一直都没有推出,也许这是其中的原因之一。

    参考

  • Protected: 最后的一分钟

    ·

    This content is password protected. To view it please enter your password below:

  • 引言

    这是一个系列文章,旨在了解如何使用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;}
    ;

  • This content is password protected. To view it please enter your password below:

  • 概述:mysqldump是MySQL官方自带的备份工具,被广泛使用着。本文详细介绍了如何使用mysqldump获得一个一致的备份,以及可能遇到的一些问题。

    1. 是什么备份的一致性?

    一致性对于备份来说是非常重要的,如果一个备份不具备一致性,那么再恢复之后,可能会让软件出现各种奇怪的问题。我们来详细看看什么是备份的一致性,例如,一个备份流程中,首先耗时一分钟(12:00:00-12:01:00)完成了用户表U表备份,然后继续其他业务表A、B等的备份,于此同时,业务线程开始向用户表U写入新注册用户X的信息,接着,业务线程又向事件表E写入X用户的事件Y信息。接着,备份线程在完成其他业务表之后,开始并完成备份事件表E。

    如果不考虑一致性,在备份事件表E的时候,会记录X用户的事件信息,但是在用户表U中却没有该用户的注册信息,也就出现了不一致的数据。如果事件U表记录的是,企业核心数据,例如账务、计费等信息,恢复这样数据,则难以达到备份与恢复的目的。

    一致性的备份是指,所有备份中的数据,可以对应到数据库在某一时刻的状态。在上面的案例中,就要求,所有备份的数据与用户表U开始备份时刻的数据处于同一个状态。

    2. mysqldump备份的一致性保障方式

    2.1 使用 –lock-tables, -l

    默认情况下,参数–opt是打开的,所以,不加任何相关参数的话,会默认带上参数–lock-tables,该参数,会在备份某个数据库之前,将该数据库的所有的表都加上锁,阻止所有的写入操作,所以,总是可以让单个数据库保持一致的状态。

    2.2 使用–lock-all-tables, -x

    另一个加锁的参数是–lock-all-tables, -x,与–lock-tables差别在于,该参数是在备份任务开始时,将需要备份的所有数据库的所有表,都加上锁,阻止所有的写入操作,所以,使用该参数,就可以获得整个实例级别的一致性,而–lock-tables参数是可以获得单个数据库级别的一致性。当然,如果你的实例中,数据库数量非常多,而且关联性并不大,则还是应该尽量使用-l参数,避免加锁时间过长。

    2.3 使用最常用 –single-transaction

    –single-transaction参数应该是mysqldump备份中最有用的参数了。对于InnoDB表,使用该参数一方面可以获得一致性的数据,另一方面,也不需要在备份期间持续的对数据库进行加锁操作。

    一般来说,使用了该参数,就可以获得一个一致的备份,并且在备份过程中,无需阻塞读取或者写入操作。

    3. 使用–single-transaction的一些例外情况

    因为MySQL两层架构设计,导致了Server层和引擎层在很多功能上并不能很好的契合。在使用–single-transaction参数备份时,如果数据库层正在执行某些DDL,那么还是可能会出现不一致。

    在mysqldump的文档中也明确提到,如果在mysqldump执行过程中,数据库上执行了ALTER TABLE, CREATE TABLE, DROP TABLE, RENAME TABLE, TRUNCATE TABLE等DDL,还是会有不一致出现。因为文档写得比较简单,而一致性又是一个比较大的问题,所以,这里详细探讨一下这种情况。

    3.1 –single-transaction备份时,如果有ALTER TABLE,数据是否还一致?

    在备份的过程中,如果有部分表执行了ALTER TABLE操作,这部分表是否还可以正常备份呢?这样的备份是否有一致性?

    简单的回答,在使用–single-transaction参数备份时,如果执行了ALTER TABLE,并且该操作属于COPY(或INPLACE)类型,那么可能会导致备份数据出现错误,事实上,可能会导致,该表中的数据无法被备份出来。

    这是一个mysqldump的限制。因为与MySQL事务、DDL实现机制、InnoDB事务机制都有较深的关系,所以,并不容易绕过,这个问题的底层原因在很早就已经在Bug系统中汇报了,但是一直都没有好的、彻底的修复策略,参考:MySQL Bug#28432。当然,也可能是修复的代价太高,而收益比较小。

    从原理上,简单的来说,当对InnoDB表做DDL操作时,而且该DDL是一个COPY类型的(ALGORITHM为COPY的时候),MySQL会先对该表加上一个全局的锁,不允许任何的写操作,然后新建好一个临时表(新的结构),然后将原表中的数据拷贝到新的临时表中,完成拷贝之后,然后再将原表删除,并将新的临时表重命名为原表。在这个过程中,所有新拷贝的数据都使用新的事务ID,而原表的数据又被删除了。所以,在这个DDL之前开始的事务,都不再能够读取DDL之后的新表的数据,即便这个新表的数据本身并没有被其他任何事务修改。从事务一致性的角度来看,这应该是不可以被接受的,使用了Repeatable Read隔离级别的事务,在某个时刻开始之后,能够读取的数据,应该总是一致的。所以,也比较明确,这就是MySQL的一个已知的限制。

    3.2 ALTER语句的ALGORITHM到底是COPY、INPLACE,还是INSTANT呢?

    那么一个ALTER TABLE语句的DDL到底是COPY、INPLACE,还是INSTANT呢?这个问题没有一个简单答案,也不再本文的讨论范围之内,详细内容可以参考:

    另外,因为INSTANT是8.0版本才引入的,所以,5.7的版本要么是COPY、要么是INPLACE。

    3.3 延伸说明,ALTER语句对于事务隔离性的破坏

    某些ALTER语句执行时,是会破坏事务的隔离性的。这里也做了一个简单的测试验证:

    在上面的例子中可以看到,如果在实际的备份中,先备份t_b,而另一个线程对t_a进行了ALTER操作的DDL(注:并且是ALGORITHM为COPY)时,备份线程再读取t_a的数据,就会失败,在实际的mysqludmp备份中,就只能备份t_a的表结构(show create table可以执行,且显示的是新的表结构),但无法备份该表的数据。

    3.4 –single-transaction与其他DDL语句

    除了ALTER语句之外,其他还有CREATE TABLE, DROP TABLE, RENAME TABLE, TRUNCATE TABLE等语法,都会破坏InnoDB RR隔离级别下的一致性,所以也都会有类似的问题。这些DDL相比ALTER要更简单一些(没有ALGORITHM选项),如果在mysqldump执行过程中执行这些DDL(如果是上面示例中的顺序),也会有类似的不一致问题,这里就不再详述。

    3.5 –single-transaction与FLUSH TABLES WITH READ LOCK

    mysqldump在–single-transaction和–master-data(–source-data)结合使用的时候,会通过FTWRL命令将数据库锁住,并获取全局的、一致的日志位点。但是,因为MySQL的设计原因,FTWRL比较容易导致数据库阻塞,尤其是数据库的负载比较大的时候,参考:

    所以,如果备份发生在主库,且负载比较大,则会有一定概率阻塞数据库,导致服务不可用。

    4. 小结

    虽然,说了这么多问题。但是,总体上mysqldump和–single-transaction组合起来用,通常都能够帮助你获得一份有效的、一致的备份。

    但是,如果你是负责一个大型系统(数据库非常多)的备份,数据库实例的数量非常多,开发人员也非常多,那么虽然概率小,但依旧一定会遇上这些情况。希望本文能够帮助你理解这种现象,以及尽量避免这种情况的发生。例如,可以考虑在备库/副本上备份,或者使用Xtrabackup的物理备份作为补充等。

  • 当我们乘着缆车,看着两边的绝壁上屹立着众多挺拔的“黄山松”;当我们来到玉屏楼后,看到立仞千米的山峰;当风来雾散时,看到神奇而孤独的小“猴子”;当站在迎客松前,想到它挺拔于此千年。人群的喧闹,而数亿年来山峰从不言语,只是静静伫立,一时让人恍惚,到底是谁在看着谁?

    也早就听说过:“五岳归来不看山,黄山归来不看岳”,也在孩子的课本上看到过各式各样“黄山奇石”、黄山奇松”。而当所有的一切呈现在眼前时,给内心的震撼是巨大的,一面感叹,徐霞客诚不欺我,一面再一次理解“纸上得来终觉浅,绝知此事要躬行”。

    当我们爬到迎客松所在处,抬头,左侧是天都峰,右侧是黄山最高峰–莲花峰,背后是玉屏楼。山峰立仞千米,平滑处可竖立百米而鲜有植被,山顶多有造型各异的奇石。恰如徐霞客所言:“左天都,右莲花,背倚玉屏风,两峰秀色,俱可手擥。四顾奇峰错列,众壑纵横,真黄山绝胜处”。

    雾中黄山

    到达黄山的第二天,已经是绵绵的阴雨天,这大概是登黄山最让人担心的了。因为黄山的奇、秀都在远处的山中,在阴雨天会升起大雾,能见度很低,几乎什么景色都看不大。因为是早就安排好的行程,定好的酒店也无法变更,只能按原计划登山,缘分观看各处景色。

    一早约六点,我就和大娃起床,再次来到酒店门口的迎客松,此时已是大雾锁山,好在迎客松就在近处,虽是朦胧,但依旧可以看得清晰。

    离开玉屏楼,按行程,我们就向光明顶方向走去,经过莲花亭,再过鳌鱼峰,抵达白云宾馆处,再取到西边,体验了一下西海大峡谷的“网红”小火车,但因为云雾缭绕的缘故,并没有看到好的效果。再向前,就是光明顶了,据导游介绍这里是观看日出、日落最好的地方,也是能够尽收半个黄山美景之处,只是也因为大雾,几乎什么都看不到。再继续向西,经过黑虎松,很快就到达了第二天入住的酒店狮林大酒店。稍作修整,期待接下来能够天气转好。

    三寻“猴子观海”

    大概是海拔高的原因,黄山的天气有一些变幻莫测。我们第一日去晴天,次日入住狮林大酒店后,天气就开始持续的小雨,整个黄山就笼罩在或薄或厚的雾气之中。“猴子观海”最佳观赏点就在酒店旁边约十五分钟登顶的一个小山峰之上。当天晚上,搬完入住之后,外面一直在淅淅沥沥的下着一些小雨,到傍晚约6点左右,雨虽然停了,但是雾气一直没散,听酒店工作人员说,这样的雾气下是很难看到“猴子观海”的。不过,如果运气好的话,一阵大风过来,雾气吹散开,也能看到。这时候,就有一些担心,这次可能会看不到这个著名的景点了。

    吃晚饭的时候,kiki说想一个人出去探探,半小时后回来,兴奋的跟我们说,大雾散开了一会儿,大概也就30分钟,恰好看到了传说中的猴子观海。我本想也立刻去看看,但此时,天已经黑了,加之大雾再起,所以今天已经看不到了。看着她拍的视频和照片,希望明天能够亲眼看看。后来,听酒店的人说,今天一整天,只有那大概30分钟能够看到“猴子观海”,今天能够看到,实属机缘。

    次日清晨,起床第一件事,就是拉开窗帘看看外面的雨雾是否消散,看起来雾气似乎比昨天要小一些,于是,一早就带着大娃向山上出发,来到一处观景台,一眼望去尽是大雾,虽然风来会隐隐消散一点,但是能见度依旧很低,什么多看不到。于是,只能带着大娃败兴而归。

    到了上午,雾气似乎又散去了一些。kiki大概看出来,我对“猴子观海”似乎有一些执着,于是带着我,说再去碰碰运气。走到半路,kiki看着山中大雾就说,这大概也是什么都看不到的。不过,我俩还是决心上到观景台再说。这时,路上还会时不时遇到行人从上面下来,都说什么都看不到。这时,心里也已经不抱希望了。来到山顶,果然,大雾没有让我们“意外”,依旧是什么都没看到。不过,这次好在把地方了解准确了。原来,早上我和大娃所到之处,还没有到真正的“猴子观海”观景台,是还需要再向上爬五分钟的。虽是大雾,还是在那里拍了一张“猴子观海”。

    等了大概十来分钟,大雾始终没有消散,只能下山。当走到“麒麟松”所在处,kiki抬头发现,能见度高了很多,竟然能够看到远处的酒店了,她说,现在上去可能能看到。于是,我果断掉头,再向上爬,并且担心大雾随时再起,一路加快了步伐,但因为是向上爬,一路气喘得非常厉害,大概离山顶还有五分钟路程的时候,突然听到一阵欢呼声,这时候,心里就猜到了,一定是“猴子观海”处的大雾散去,众人看到了这一景观。这时,虽然因为爬的急,已经很累了,但是依旧是一鼓作气,咬牙爬上山顶,到达了猴子观海观景台。

    雾依旧有一些,但风也起来了,风吹着雾气到处乱逃。远处的胜景–“猴子观海”终于呈现在了眼前。随着大风的持续作用,猴子观海周围的各式山峰也都明朗了起来。

    大概,在这时候,心里就暗下决心,黄山一定还是要来的,因为此番景色,只能用眼睛、用双脚、用心灵去体会。

    猴子观海@2023年4月29日 清晨

    长恨春归无觅处

    因为气候的原因,山上更冷,春意也更晚。很多城市里的花都已经盛开,甚至开始凋落,但是注意黄山之上,杜鹃花(据说是一种比较独特的杜鹃)却刚刚盛开,很多的小树也还是刚刚冒出新芽。想恰如白居易所言:“长恨春归无觅处,不知转入此中来”。

    黄山山顶的酒店

    这次我们一行5人,老爸年过六旬,小娃刚刚六岁,所以整个行程安排得非常“松散”,首日上线,先入住了玉屏楼酒店,次日走过光明顶,再到狮林大酒店,第三日经过始信峰后从云谷索道下山。

    酒店的价格大家可以在携程等站点查询,会因为假期等原因波动很大。但,无论如何,黄山山顶的酒店都不便宜,一方面酒店数量不多,就那么几家,另一方面山顶所有使用的物品都需要从山下经由索道运至山腰,然后再由挑夫人力挑至山顶,走过一趟的都能体会这种不易,所以,价格贵一些,都是正常。

    另外,如果住在山上,吃饭是另一个比较大的费用,一般酒店的自助餐(似乎只有自助餐)大概在每人120~160元之间。另外,山顶有一些休息站,会供应一些盒饭,价格在50~80元。

    总得来说,没有必要带很多的水和食物,基本都能够买到,虽然略微贵一些。

    再说一说玉屏楼酒店吧。这个酒店所在的位置比较险峻,并且正对着著名的“迎客松”,左边是天都峰,右边是莲花峰,背后也有各式各样的奇石奇松,不远处可以到莲花亭,可以观看日出日落。可以说是非常有特色的酒店。

    上面的照片是在去莲花亭的路上所拍,远处的高峰正是“天都峰”,中间靠左隐约可见的房子就是玉屏楼酒店,如果注意看的话,酒店靠右一点则是黄山著名的“迎客松”。