Hadoop集群可视化管理- Hue

一、课前准备

  1. 准备好大数据集群,启动所有的服务,例如hadoop,hbase,impala,hiveserver2,mysql等各种服务

二、课堂主题

本堂课主要介绍hue这个图形化的界面工具,以及与其他工具之间的整合使用

三、课堂目标

  1. 实现hue与其他框架的整合使用

四、知识要点

1、hue的基本介绍

HUE=Hadoop User Experience

Hue是一个开源的Apache Hadoop UI系统,由Cloudera Desktop演化而来,最后Cloudera公司将其贡献给Apache基金会的Hadoop社区,它是基于Python Web框架Django实现的。

通过使用Hue我们可以在浏览器端的Web控制台上与Hadoop集群进行交互来分析处理数据,例如操作HDFS上的数据,运行MapReduce Job,执行Hive的SQL语句,浏览HBase数据库等等。

HUE链接

· Site: http://gethue.com/

· Github: https://github.com/cloudera/hue

· Reviews: https://review.cloudera.org

Hue的架构

1571452158221

核心功能

· SQL编辑器,支持Hive, Impala, MySQL, Oracle, PostgreSQL, SparkSQL, Solr SQL, Phoenix…

· 搜索引擎Solr的各种图表

· Spark和Hadoop的友好界面支持

· 支持调度系统Apache Oozie,可进行workflow的编辑、查看

HUE提供的这些功能相比Hadoop生态各组件提供的界面更加友好,但是一些需要debug的场景可能还是需要使用原生系统才能更加深入的找到错误的原因。

HUE中查看Oozie workflow时,也可以很方便的看到整个workflow的DAG图,不过在最新版本中已经将DAG图去掉了,只能看到workflow中的action列表和他们之间的跳转关系,想要看DAG图的仍然可以使用oozie原生的界面系统查看。

1,访问HDFS和文件浏览

2,通过web调试和开发hive以及数据结果展示

3,查询solr和结果展示,报表生成

4,通过web调试和开发impala交互式SQL Query

5,spark调试和开发

7,oozie任务的开发,监控,和工作流协调调度

8,Hbase数据查询和修改,数据展示

9,Hive的元数据(metastore)查询

10,MapReduce任务进度查看,日志追踪

11,创建和提交MapReduce,Streaming,Java job任务

12,Sqoop2的开发和调试

13,Zookeeper的浏览和编辑

14,数据库(MySQL,PostGres,SQlite,Oracle)的查询和展示

一句话总结:Hue是一个友好的界面集成框架,可以集成我们各种学习过的以及将要学习的框架,一个界面就可以做到查看以及执行所有的框架

2、Hue的安装

Hue的安装支持多种方式,包括rpm包的方式进行安装,tar.gz包的方式进行安装以及cloudera manager的方式来进行安装等,我们这里使用tar.gz包的方式来进行安装

第一步:下载Hue的压缩包并上传到linux解压

Hue的压缩包的下载地址:

http://archive.cloudera.com/cdh5/cdh/5/

我们这里使用的是CDH5.14.2这个对应的版本,具体下载地址为

http://archive.cloudera.com/cdh5/cdh/5/hue-3.9.0-cdh5.14.2.tar.gz

下载然后上传到node03服务器的/kkb/soft路径下

cd /kkb/soft
tar -zxvf hue-3.9.0-cdh5.14.2.tar.gz -C  /kkb/install

第二步:编译安装启动

2.1、linux系统安装依赖包:

联网安装各种必须的依赖包

sudo yum install ant asciidoc cyrus-sasl-devel cyrus-sasl-gssapi cyrus-sasl-plain gcc gcc-c++ krb5-devel libffi-devel libxml2-devel libxslt-devel make  mysql mysql-devel openldap-devel python-devel sqlite-devel gmp-devel libffi  gcc gcc-c++ kernel-devel openssl-devel gmp-devel openldap-devel
2.2、开始配置Hue
cd /kkb/install/hue-3.9.0-cdh5.14.2/desktop/conf

vim  hue.ini
#通用配置

[desktop]
    secret_key=jFE93j;2[290-eiw.KEiwN2s3['d;/.q[eIW^y#e=+Iei*@Mn<qW5o
    http_host=node03.kaikeba.com
    is_hue_4=true
    time_zone=Asia/Shanghai
    server_user=hadoop
    server_group=hadoop
    default_user=hadoop
    default_hdfs_superuser=hadoop

#配置使用mysql作为hue的存储数据库,大概在hue.ini的587行左右

[[database]]
    engine=mysql
    host=node03.kaikeba.com
    port=3306
    user=root
    password=123456
    name=hue
2.3、创建mysql数据库

创建hue数据库

create database hue default character set utf8 default collate utf8_general_ci;

注意:实际工作中,还需要为hue这个数据库创建对应的用户,并分配权限,我这就不创建了,所以下面这一步不用执行了

grant all on hue.* to 'hue'@'%' identified by 'hue';
2.4、准备进行编译

node03服务器执行以下命令准备进行编译

cd /kkb/install/hue-3.9.0-cdh5.14.2
make apps

注意:如果编译失败,请参照第一步重新解压

2.5、linux系统添加普通用户hue

为了方便也可以使用已经存在的hadoop用户来操作hue。

而工作中往往会单独创建一个用户hue:需要的话可在node03执行以下命令,创建普通用户hue

sudo useradd hue
sudo passwd hue
2.6、启动hue进程

node03执行以下命令启动hue

cd /kkb/install/hue-3.9.0-cdh5.14.2
sudo build/env/bin/supervisor
2.7、页面访问

http://node03:8888

第一次访问的时候,需要设置管理员用户和密码。我们这里的管理员的用户名与密码尽量保持与我们安装hadoop的用户名和密码一致,我们安装hadoop的用户名与密码分别是hadoop 123456,初次登录使用hadoop,密码为123456。

访问页面异常

如果忘记在hue.ini文件中修改元数据库引擎由sqlite改为mysql,访问页面将会遇到如下错误

File "/usr/local/lib/python2.7/site-packages/django/db/backends/sqlite3/base.py", line 323, in execute
  return Database.Cursor.execute(self, query, params)
OperationalError: attempt to write a readonly database

解决办法,检查hue.ini配置是否将元数据库引擎由sqlite改为mysql,以及其他配置是否正确:

[[database]]
engine=mysql        #默认是sqlite3作为元数据库,这里改为mysql
host=192.168.186.36 #<mysql所在服务器>
port=3306           #<mysql端口,一般就是3306>
user=hive           #<mysql用户名>
password=hive1234   #<mysql用户密码>
name=hue            #<数据库名称,新数据库,专门用于hue,里面现在没有任何表>

完成以上的这个配置,启动Hue,通过浏览器访问,仍然会发生错误

DatabaseError: (1146, "Table 'hue.desktop_settings' doesn't exist")

原因是mysql数据没有被初始化,解决办法, 初始化数据库(执行一次即可):

sudo build/env/bin/hue syncdb
sudo build/env/bin/hue migrate

同步数据库的时候会询问是否创建用户,可以选择创建hue用户,并设计密码为hue。

这一步是可选的,因为就算不创建,登陆的时候也需要创建,一般使用Linux中一样的用户名和密码即可。(我用的是hadoop用户)

然后再启动supervisor服务(前台运行,如果需要后台运行可以添加-d选项 )

sudo build/env/bin/supervisor

3、hue与其他框架的集成

如果登陆后仍然由报错,很可能是因为hue和hadoop的hdfs和yarn的集成没有设置好。

3.1、hue与hadoop的HDFS以及yarn集成

第一步:更改所有hadoop节点的core-site.xml配置

记得更改完core-site.xml之后一定要重启hdfs与yarn集群

三台机器更改core-site.xml

<property>
    <name>hadoop.proxyuser.hadoop.hosts</name>
    <value>*</value>
</property>

<property>
    <name>hadoop.proxyuser.hadoop.groups</name>
    <value>*</value>
</property> 
第二步:更改所有hadoop节点的hdfs-site.xml

所有服务器更改hdfs-site.xml添加以下配置

<property>
    <name>dfs.webhdfs.enabled</name>
    <value>true</value>
</property>
第三步:重启hadoop集群

在node01机器上面执行以下命令

cd /kkb/install/hadoop-2.6.0-cdh5.14.2

sbin/stop-dfs.sh
sbin/start-dfs.sh
sbin/stop-yarn.sh
sbin/start-yarn.sh
第四步:停止hue的服务,并继续配置hue.ini

停止hue的服务,然后进入到以下路径,重新配置hue.ini这个配置文件(880行左右)

cd /kkb/install/hue-3.9.0-cdh5.14.2/desktop/conf

vim hue.ini

#配置我们的hue与hdfs集成]]
[[hdfs_clusters]]
    [[[default]]]
        fs_defaultfs=hdfs://node01.kaikeba.com:8020
        webhdfs_url=http://node01.kaikeba.com:50070/webhdfs/v1
        hadoop_hdfs_home=/kkb/install/hadoop-2.6.0-cdh5.14.2
        hadoop_bin=/kkb/install/hadoop-2.6.0-cdh5.14.2/bin
        hadoop_conf_dir=/kkb/install/hadoop-2.6.0-cdh5.14.2/etc/hadoop

#配置我们的hue与yarn集成

[[yarn_clusters]]

    [[[default]]]

      resourcemanager_host=node01
      resourcemanager_port=8032
      submit_to=True
      resourcemanager_api_url=http://node01:8088
      history_server_api_url=http://node01:19888

注意:如果是hadoop 3的版本,那么webhdfs_url端口一般需要修改成9870

webhdfs_url=http://node01.kaikeba.com:9870/webhdfs/v1

配置完成之后重新启动hue的服务

node03执行以下命令进行重新启动hue的服务

$ cd /kkb/install/hue-3.9.0-cdh5.14.2/
$ sudo build/env/bin/supervisor

如果出现8888端口占用,需要先kill掉再启动服务

3.2、配置hue与hive集成

如果需要配置hue与hive的集成,我们需要启动hive的metastore服务以及hiveserver2服务(impala需要hive的metastore服务,hue需要hvie的hiveserver2服务)

更改hue的配置hue.ini

停止hue的服务,然后重新编辑修改hue.ini这个配置文件

修改hue.ini

cd /kkb/install/hue-3.9.0-cdh5.14.2/desktop/conf
vim hue.ini

[beeswax]
    hive_server_host=node03.kaikeba.com
    hive_server_port=10000
    hive_conf_dir=/kkb/install/hive-1.1.0-cdh5.14.2/conf
    server_conn_timeout=120
    auth_username=hadoop
    auth_password=123456

[metastore]

  #允许使用hive创建数据库表等操作
  enable_new_create_table=true
启动hive的metastore服务

去node03机器上启动hive的metastore以及hiveserver2服务

cd /kkb/install/hive-1.1.0-cdh5.14.2/conf
nohup bin/hive --service metastore &
nohup bin/hive --service hiveserver2 &

重新启动hue,然后就可以通过浏览器页面操作hive了

node03执行以下命令进行重新启动hue的服务

cd /kkb/install/hue-3.9.0-cdh5.14.2/
sudo build/env/bin/supervisor
3.3、配置hue与impala的集成

停止hue的服务进程

修改hue.ini配置文件

cd /kkb/install/hue-3.9.0-cdh5.14.2/desktop/conf
vim hue.ini

[impala]

  server_host=node03
  server_port=21050
  impala_conf_dir=/etc/impala/conf

然后node03执行以下命令,重新启动hue的服务即可

cd /kkb/install/hue-3.9.0-cdh5.14.2/
sudo build/env/bin/supervisor

3.4、配置hue与mysql的集成

找到databases 这个选项,将这个选项下面的mysql注释给打开,然后配置mysql即可,大概在1547行

停止hue的服务,然后修改hue.ini

cd /kkb/install/hue-3.9.0-cdh5.14.2/desktop/conf
vim hue.ini

[[[mysql]]]
    nice_name="My SQL DB"
    engine=mysql
    host=node03.kaikeba.com
    port=3306
    user=root
    password=123456
    options={"init_command":"set names utf8;SET CHARACTER SET utf8;SET character_set_connection=utf8;"}

更改完了配置,重新启动hue的服务

cd /kkb/install/hue-3.9.0-cdh5.14.2/
build/env/bin/supervisor

3.5、配置hue与hbase的集成

第一步:修改hue.ini

如果hue已经启动,需要先停止hue的服务,然后继续修改hue的配置文件hue.ini

cd /kkb/install/hue-3.9.0-cdh5.14.2/desktop/conf

vim hue.ini

[hbase]
  hbase_clusters=(Cluster|node01:9090)
  hbase_conf_dir=/kkb/install/hbase-1.2.0-cdh5.14.2/conf
第二步:启动hbase的thrift server服务

第一台机器执行以下命令启动hbase的thriftserver

cd /kkb/install/hbase-1.2.0-cdh5.14.2

bin/start-hbase.sh 
bin/hbase-daemon.sh start thrift  

检查hbase主节点状态: http://hadoop101:16010/master-status

第三步:启动hue

第三台机器执行以下命令启动hue

cd /kkb/install/hue-3.9.0-cdh5.14.2/

build/env/bin/supervisor
第四步:页面访问

http://node03:8888/hue/

image-20210613184533712

Views: 104

高速Hive查询引擎 – Impala

一、课前准备

安装好hive以及hadoop运行环境,并正常启动hadoop以及hive的

二、课堂主题

实现impala集群环境正常安装,并掌握impala的基本语法

三、课堂目标

熟练使用impala的语法

四、知识要点

离线任务处理流程概述

离线任务处理流程

由于大部分的软件框架,CDH都提供了压缩包的安装方式,但是由于impala有部分代码使用C++编写,所以impala在安装包的选择上面,cloudera公司没有提供tar包的安装方式,只提供了rpm的安装方式,我们可以通过下载rpm包来进行安装。注意:rpm包是linux操作系统上面的一种安装压缩包

1、 impala的概述

imala基本介绍

impala是cloudera提供的一款高效率的sql查询工具,提供实时的查询效果,官方测试性能比hive快10到100倍,其sql查询比sparkSQL还要更加快速,号称是当前大数据领域最快的查询sql工具,impala是参照谷歌的新三篇论文(Caffeine、Pregel、Dremel)当中的Dremel实现而来,其中旧三篇论文分别是(BigTable,GFS,MapReduce)分别对应我们即将学的HBase和已经学过的HDFS以及MapReduce

impala是基于hive并使用内存进行计算,兼顾数据仓库,具有实时,批处理,多并发等优点

impala与hive的关系

impala是基于hive的大数据分析查询引擎,直接使用hive的元数据库metadata,意味着impala元数据都存储在hive的metastore当中,并且impala兼容hive的绝大多数sql语法。所以需要安装impala的话,必须先安装hive,保证hive安装成功,并且还需要启动hive的metastore服务

impala的优点

1、impala比较快,非常快,特别快,因为所有的计算都可以放入内存当中进行完成,只要你内存足够大

2、摈弃了MR的计算,改用C++来实现,有针对性的硬件优化

3、具有数据仓库的特性,对hive的原有数据做数据分析

4、支持ODBC,jdbc远程访问

impala的缺点:

1、基于内存计算,对内存依赖性较大

2、改用C++编写,意味着维护难度增大

3、基于hive,与hive共存亡,紧耦合

4、稳定性不如hive,不存在数据丢失的情况

impala的架构以及查询计划

img

Impala的架构模块:

  • impala-server

    • 启动的守护进程,执行我们的查询计划 从节点,官方建议与所有的datanode装在一起,可以通过hadoop的短路读取特性实现数据的快速查询
  • impala-statestore

    • 状态存储区 主节点
  • impalas-catalog

    • 元数据管理区 主节点

查询执行

impalad分为frontend和backend两个层次, frondend用java实现(通过JNI嵌入impalad), 负责查询计划生成, 而backend用C++实现, 负责查询执行。

frontend**生成查询计划分为两个阶段:**

(1)生成单机查询计划,单机执行计划与关系数据库执行计划相同,所用查询优化方法也类似。

(2)生成分布式查询计划。 根据单机执行计划, 生成真正可执行的分布式执行计划,降低数据移动, 尽量把数据和计算放在一起。

![http://www.aboutyun.com/data/attachment/forum/201507/27/134940kb7luw6ix8in3w3s.png](https://imgs.delucia.cn/imgs/2021/clip_image004 - 副本.jpg)

上图是SQL查询例子, 该SQL的目标是在三表join的基础上算聚集, 并按照聚集列排序取topN。

impala的查询优化器支持代价模型: 利用表和分区的cardinality,每列的distinct值个数等统计数据, impala可估算执行计划代价, 并生成较优的执行计划。 上图左边是frontend查询优化器生成的单机查询计划, 与传统关系数据库不同, 单机查询计划不能直接执行, 必须转换成如图右半部分所示的分布式查询计划。 该分布式查询计划共分成6个segment(图中彩色无边框圆角矩形), 每个segment是可以被单台服务器独立执行的计划子树。

![img](https://imgs.delucia.cn/imgs/2021/clip_image006 - 副本.jpg)

2、impala的安装环境准备

需要提前安装好hadoop,hive,这两个框架,并且hive需要将hive的安装包,拷贝到所有的服务器上面都保存一份,因为impala需要引用hive的安装目录下面的一些依赖的jar包

3、下载impala的所有依赖包

由于impala没有提供tar包供我们进行安装,只提供了rpm包,所以我们在安装impala的时候,需要使用rpm包来进行安装,rpm包只有cloudera公司提供了,所以我们去cloudera公司网站进行下载rpm包即可,但是另外一个问题,impala的rpm包依赖非常多的其他的rpm包,可以一个个的将依赖找出来,也可以将所有的rpm包下载下来,制作成我们本地yum源来进行安装。我们这里就选择制作我们本地的yum源来进行安装,所以首先我们需要下载到所有的rpm包,下载地址如下

http://archive.cloudera.com/cdh5/repo-as-tarball/5.14.2/cdh5.14.2-centos7.tar.gz

下载好了之后,保留下,留作备用

将我们下载好的压缩包,上传到node03服务器的/kkb/soft路径下,并进行解压

cd /kkb/soft
tar -zxvf cdh5.14.2-centos7.tar.gz

4、制作本地yum源

镜像源是centos当中下载相关软件的地址,我们可以通过制作我们自己的镜像源指定我们去哪里下载impala的rpm包,这里我们使用httpd这个软件来作为服务端,启动httpd的服务来作为我们镜像源的下载地址

这里我们选用第三台机器作为镜像源的服务端

node03机器上执行以下命令

sudo yum  -y install httpd
sudo service httpd start

cd /etc/yum.repos.d
sudo vim localimp.repo 

[localimp]
name=localimp
baseurl=http://node03/cdh5.14.2/
gpgcheck=0
enabled=1

创建apache httpd的读取链接

sudo ln -s /kkb/soft/cdh/5.14.2 /var/www/html/cdh5.14.2

页面访问本地yum源,出现这个界面表示本地yum源制作成功

http://node03/cdh5.14.2

如果能够正常访问到文件浏览页面,证明我们的本地yum源安装成功

将制作好的localimp配置文件发放到所有需要安装impala的节点上去

node03执行以下命令进行分发
cd /etc/yum.repos.d/

sudo scp localimp.repo  node02:$PWD
sudo scp localimp.repo  node01:$PWD

5、开始安装impala

安装规划

服务名称 node01 node02 node03
impala-catalog 不安装 不安装 安装
impala-state-store 不安装 不安装 安装
impala-server 安装 安装 安装
#主节点node03执行以下命令进行安装

sudo yum  install  impala -y
impala-server
impala-state-store
impala-catalog
impala-shell

#从节点node01与node02安装以下服务
sudo yum install impala-server -y

6、所有节点配置impala

第一步:修改hive-site.xml

node03机器修改hive-site.xml内容如下

hive-site.xml配置

vim /kkb/install/hive-1.1.0-cdh5.14.2/conf/hive-site.xml
添加以下三个配置属性

<property>
    <name>hive.server2.thrift.bind.host</name>
    <value>node03.hadoop.com</value>
</property>
 <property>
     <name>hive.metastore.uris</name>
     <value>thrift://node03.kaikeba.com:9083</value>
 </property>
<property>
    <name>hive.metastore.client.socket.timeout</name>
    <value>3600</value>
 </property>

第二步:将hive的安装包发送到node02与node01机器上

在node03机器上面执行

cd /kkb/install/

scp -r hive-1.1.0-cdh5.14.2/ node02:$PWD
scp -r hive-1.1.0-cdh5.14.2/ node01:$PWD

第三步:node03启动hive的metastore服务

启动hive的metastore服务

node03机器启动hive的metastore服务

cd  /kkb/install/hive-1.1.0-cdh5.14.2

nohup bin/hive --service metastore &
nohup bin/hive -- service hiveserver2 &

注意:一定要保证mysql的服务正常启动,否则metastore的服务不能够启动

第四步:所有hadoop节点修改hdfs-site.xml添加以下内容

所有节点创建文件夹

sudo mkdir -p /var/run/hdfs-sockets

修改所有节点的hdfs-site.xml添加以下配置,修改完之后重启hdfs集群生效

vim  /kkb/install/hadoop-2.6.0-cdh5.14.2/etc/hadoop/hdfs-site.xml

<property>
    <name>dfs.client.read.shortcircuit</name>
    <value>true</value>
</property>

<property>
     <name>dfs.domain.socket.path</name>
     <value>/var/run/hdfs-sockets/dn</value>
</property>

<property>
    <name>dfs.client.file-block-storage-locations.timeout.millis</name>
    <value>10000</value>
</property>
<property>
     <name>dfs.datanode.hdfs-blocks-metadata.enabled</name>
     <value>true</value>
</property>

三台机器执行以下命令给文件夹授权

sudo  chown  -R  hadoop:hadoop   /var/run/hdfs-sockets/

第五步:重启hdfs

重启hdfs文件系统

node01服务器上面执行以下命令

cd /kkb/install/hadoop-2.6.0-cdh5.14.2/

sbin/stop-dfs.sh
sbin/start-dfs.sh

第六步:创建hadoop与hive的配置文件的连接

impala的配置目录为 /etc/impala/conf

这个路径下面需要把core-site.xmlhdfs-site.xml以及hive-site.xml拷贝到这里来,但是我们这里使用软连接的方式会更好

所有节点执行以下命令创建链接到impala配置目录下来

sudo ln -s /kkb/install/hadoop-2.6.0-cdh5.14.2/etc/hadoop/core-site.xml /etc/impala/conf/core-site.xml

sudo ln -s /kkb/install/hadoop-2.6.0-cdh5.14.2/etc/hadoop/hdfs-site.xml /etc/impala/conf/hdfs-site.xml

sudo ln -s /kkb/install/hive-1.1.0-cdh5.14.2/conf/hive-site.xml /etc/impala/conf/hive-site.xml

第七步:修改impala的配置文件

所有节点修改impala默认配置

所有节点更改impala默认配置文件以及添加mysql的连接驱动包

sudo vim /etc/default/impala

IMPALA_CATALOG_SERVICE_HOST=node03
IMPALA_STATE_STORE_HOST=node03

所有节点创建mysql的驱动包的软连接

sudo mkdir -p /usr/share/java
sudo ln -s /kkb/install/hive-1.1.0-cdh5.14.2/lib/mysql-connector-java-5.1.38.jar /usr/share/java/mysql-connector-java.jar
所有节点修改bigtop的java路径

修改bigtop的java_home路径

sudo vim /etc/default/bigtop-utils

export JAVA_HOME=/kkb/install/jdk1.8.0_141

第八步:启动impala服务

启动impala服务

主节点node03启动以下三个服务进程

sudo service impala-state-store start
sudo service impala-catalog start
sudo service impala-server start

从节点启动node01与node02启动impala-server

sudo service  impala-server  start

三台机器可以通过以下命令,查看impala进程是否存在

ps -ef | grep impala

注意:启动之后所有关于impala的日志默认都在/var/log/impala 这个路径下,node03机器上面应该有三个进程,node02与node01机器上面只有一个进程,如果进程个数不对,去对应目录下查看报错日志

浏览器页面访问:

访问impalad的管理界面

http://node03:25000/

访问statestored的管理界面

http://node03:25010/

访问catalog的管理界面

http://node03:25020

7、impala的使用

执行impala-shell即可进入交互界面

$ impala-shell

Starting Impala Shell without Kerberos authentication
Connected to node03:21000
Server version: impalad version 2.11.0-cdh5.14.2 RELEASE (build ed85dce709da9557aeb28be89e8044947708876c)
**********************************************************************
Welcome to the Impala shell.
(Impala Shell v2.11.0-cdh5.14.2 (ed85dce) built on Tue Mar 27 13:39:48 PDT 2018)
You can run a single query from the command line using the '-q' option.
**********************************************************************
[node03:21000] >

exit;退出交互界面

1、impala-shell语法

1.1、impala-shell的外部命令参数语法

不需要进入到impala-shell交互命令行当中即可执行的命令参数

impala-shell后面执行的时候可以带很多参数:

-h 查看帮助文档

impala-shell -h

-r 刷新整个元数据,数据量大的时候,比较消耗服务器性能

impala-shell -r

-v 查看对应版本

impala-shell -v -V

-f 执行查询文件

cd /kkb/install

vim impala-shell.sql

select * from course.score;

通过-f 参数来执行执行的查询文件

impala-shell -f impala-shell.sql

-p 显示查询计划

impala-shell -f impala-shell.sql -p

9.1.2、impala-shell的内部命令行参数语法

进入impala-shell命令行之后可以执行的语法

help命令

帮助文档

connect命令

connect hostname 连接到某一台机器上面去执行

refresh 命令

refresh dbname.tablename 增量刷新,刷新某一张表的元数据,主要用于刷新hive当中数据表里面的数据改变的情况

refresh course.score;

invalidate metadata 命令:

invalidate  metadata
全量刷新,性能消耗较大,主要用于hive当中新建数据库或者数据库表的时候来进行刷新

explain 命令:

用于查看sql语句的执行计划

explain select * from course.score;

explain的值可以设置成0,1,2,3等几个值,其中3级别是最高的,可以打印出最全的信息

set explain_level=3;

profile命令:

执行sql语句之后执行,可以打印出更加详细的执行步骤,

主要用于查询结果的查看,集群的调优等

select * from course.score;

profile;

注意:在hive窗口当中插入的数据或者新建的数据库或者数据库表,在impala当中是不可直接查询到的,需要刷新数据库,在impala-shell当中插入的数据,在impala当中是可以直接查询到的,不需要刷新数据库,其中使用的就是catalog这个服务的功能实现的,catalog是impala1.2版本之后增加的模块功能,主要作用就是同步impala之间的元数据

2、创建数据库

impala-shell进入到impala的交互窗口

2.1、查看所有数据库

show databases;

2.2、创建与删除数据库

创建数据库

CREATE DATABASE IF NOT EXISTS mydb1;
drop database  if exists  mydb;

创建数据库表并指定数据库表数据存放hdfs的位置(与hive建表语法类似)

hdfs dfs -mkdir -p /input/impala

create  external table  t3(id int ,name string ,age int )  row  format  delimited fields terminated  by  '\t' location  '/input/impala/external';

3、 创建数据库表

创建student表

CREATE TABLE IF NOT EXISTS mydb1.student (name STRING, age INT, contact INT );

创建employ表

create table employee (Id INT, name STRING, age INT,address STRING, salary BIGINT);
3.1、 数据库表中插入数据
insert into employee (ID,NAME,AGE,ADDRESS,SALARY)VALUES (1, 'Ramesh', 32, 'Ahmedabad', 20000 );
insert into employee values (2, 'Khilan', 25, 'Delhi', 15000 );
Insert into employee values (3, 'kaushik', 23, 'Kota', 30000 );
Insert into employee values (4, 'Chaitali', 25, 'Mumbai', 35000 );
Insert into employee values (5, 'Hardik', 27, 'Bhopal', 40000 );
Insert into employee values (6, 'Komal', 22, 'MP', 32000 );

数据的覆盖

Insert overwrite employee values (1, 'Ram', 26, 'Vishakhapatnam', 37000 );
执行覆盖之后,表中只剩下了这一条数据了

另外一种建表语句

create table customer as select * from employee;
3.2、 数据的查询
select * from employee;

select name,age from employee;
3.3、 删除表
DROP table  mydb1.employee;
3.4、 清空表数据
truncate  employee;
3.5、 查看视图数据
select * from employee_view;

4、 order by语句

基础语法

select * from table_name ORDER BY col_name [ASC|DESC] [NULLS FIRST|NULLS LAST]
Select * from employee ORDER BY id asc;

5、group by 语句

Select name, sum(salary) from employee Group BY name;

6、 having 语句

基础语法

select * from table_name ORDER BY col_name [ASC|DESC] [NULLS FIRST|NULLS LAST]

按年龄对表进行分组,并选择每个组的最大工资,并显示大于20000的工资

select max(salary) from employee group by age having max(salary) > 20000;

7、 limit语句

select * from employee order by id limit 4;

8、impala当中的数据表导入几种方式

第一种方式,通过load hdfs的数据到impala当中去

create table user(id int ,name string,age int ) row format delimited fields terminated by "\t";

准备数据user.txt并上传到hdfs的 /user/impala路径下去

1   hello   15
2   zhangsan    20
3   lisi    30
4   wangwu  50

加载数据

load data inpath '/user/impala/' into table user;

查询加载的数据

select  *  from  user;

如果查询不不到数据,那么需要刷新一遍数据表

refresh  user;

第二种方式:

create  table  user2   as   select * from  user;

第三种方式:

insert into 不推荐使用 因为会产生大量的小文件

千万不要把impala当做一个数据库来使用

第四种:

insert  into  select  用的比较多

9、impala的java开发

在实际工作当中,因为impala的查询比较快,所以可能有会使用到impala来做数据库查询的情况,我们可以通过java代码来进行操作impala的查询

第一步:导入jar包
  <repositories>
        <repository>
            <id>cloudera</id>
            <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
        </repository>
        <repository>
            <id>central</id>
            <url>http://repo1.maven.org/maven2/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.6.0-cdh5.14.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-common</artifactId>
            <version>1.1.0-cdh5.14.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-metastore</artifactId>
            <version>1.1.0-cdh5.14.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-service</artifactId>
            <version>1.1.0-cdh5.14.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-jdbc</artifactId>
            <version>1.1.0-cdh5.14.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hive</groupId>
            <artifactId>hive-exec</artifactId>
            <version>1.1.0-cdh5.14.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.thrift/libfb303 -->
        <dependency>
            <groupId>org.apache.thrift</groupId>
            <artifactId>libfb303</artifactId>
            <version>0.9.0</version>
            <type>pom</type>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.thrift/libthrift -->
        <dependency>
            <groupId>org.apache.thrift</groupId>
            <artifactId>libthrift</artifactId>
            <version>0.9.0</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.2.5</version>
        </dependency>
    </dependencies>
第二步:impala的java代码查询开发
public class ImpalaJdbc {
     public static void main(String[] args) throws Exception {
     //定义连接驱动类,以及连接url和执行的sql**语句     
     String driver = "org.apache.hive.jdbc.HiveDriver";
     String driverUrl = "jdbc:hive2://192.168.52.120:21050/mydb1;auth=noSasl";
     String sql = "select * from student";

     //通过反射加载数据库连接驱动*    
     Class.forName(driver);
     Connection connection = DriverManager.getConnection(driverUrl);
     PreparedStatement preparedStatement = connection.prepareStatement(sql);
     ResultSet resultSet = preparedStatement.executeQuery();
     //通过查询,得到数据一共有多少列     
     int col = resultSet.getMetaData().getColumnCount();
     //遍历结果集     
     while (resultSet.next()){
         for(int i=1;i<=col;i++){
             System.out.print(resultSet.getString(i)+"\t");
         }
         System.out.print("\n");
     }
     preparedStatement.close();
     connection.close();
 }
 }

Views: 84

Flume 进阶

一、flume事务

file

put事务流程

1、doPut

将批量数据先写入临时缓冲区putList

2、doCommit

检查Channel内存队列是否足够,

(1)达到一定时间没有数据写入到putList

(2)达到了putListCapcity容量

3、doRollback

Channel内存队列空间不足,回滚数据到putList,会被channel打回来

take事务流程:

1、doTake

将数据取到临时缓冲区takeList,并将数据发送到HDFS

2、doCommit

如果数据全部发送成功,则清除临时缓冲区takeList

3、doRollback

数据发送过程中如果出现异常,rollback将临时缓冲区takeList中数据全部打回给Channel内存队列

二、Flume Agent内部原理

file

重要组件:
1)ChannelSelector
ChannelSelector的作用就是选出event将要被发往哪个Channel。

共有两种类型:Replicating(复制)和Multiplexing(多路复用)

ReplicatingSelector会将同一个event发往所有的Channel

MultiplexingSelector会根据相应的原则,将不同的event发往不同的Channel

2)SinkProcessor
sinkProcessor共有三种类型:DefaultSinkProcessor、LoadBalancingProcessor和FailoverSinkProcessor

DefaultSinkProcessor:对应的是单个sink

LoadBalancingProcessor:对应的是sink group,可以实现负载均衡

FailoverSinkProcessor:对应的是sink group,可以实现故障恢复

三、flume拓扑结构

1、简单串联

file

将多个flume顺序连接起来,从最初的Source开始到最终sink传送的目的存储系统。

此模式不建议桥接过多的flume数量,flume数据过多不仅会影响传输速率,而且一旦传输过程中某个节点flume宕机,会影响整个传输系统

2、复制和多路复用

file

flume支持将事件流向一个或者多个目的地。

这种模式可以将相同数据复制到多个Channel中,或者将不同数据分发到不同的Channel中,sink可以选择传送到不同的目的地

3、负载均衡和故障转移

file

flume支持使用将多个sink逻辑上分到一个sink组,sink组配合不同的sinkProcessor可以实现负载均衡和错误恢复的功能

4、聚合

file

这种模式是我们最常见的,也非常实用,日常web应用通常分布在上百个服务器,大者甚至上千个、上万个服务器。产生的日志,处理  起来也非常麻烦。

用flume的这种组合方式能很好的解决这一问题,每台服务器部署一个flume采集日志,传送到一个集中收集日志的flume,再由此flume  上传到hdfs、hive、hbase等,进行日志分析。

Views: 168

Presto分布式SQL查询引擎

一、课前准备

  1. jdk版本要求:Java 8 Update 151 or higher (8u151+), 64-bit
  2. 安装好hadoop集群
  3. 安装好hive

二、课堂主题

  1. 介绍presto
  2. presto架构
  3. prsto安装部署
  4. presto使用

三、课堂目标

  1. 理解presto
  2. 独立完成presto安装部署
  3. 使用presto

四、知识要点

1. Presto是什么?

  • Hadoop提供了大数据存储与计算的一整套解决方案;但是它采用的是MapReduce计算框架,只适合离线和批量计算,无法满足快速实时的Ad-Hoc查询计算的性能要求

  • Hive使用MapReduce作为底层计算框架,是专为批处理设计的。但随着数据越来越多,使用Hive进行一个简单的数据查询可能要花费几分到几小时,显然不能满足交互式查询的需求。

  • Facebook于2012年秋开始开发了Presto,每日查询数据量在1PB级别。Facebook称Presto的性能比Hive要快上10倍多。2013年Facebook正式宣布开源Presto。

  • Presto是apache下开源的==OLAP的分布式SQL查询引擎==,数据量支持从GB到PB级别的数据量的查询,并且查询时,能做到秒级查询。

  • 另外,Presto虽然可以解析SQL,但它并非是标准的数据库;不能替代如MySQL、PostgreSQL、Oracle关系型数据库,不是用于处理OLTP的

  • presto是利用分布式查询,高效的对海量数据进行查询;

  • presto可以用来查询hdfs上的海量数据;但是,presto不仅仅可以用来查询hdfs的数据,它还被设计成能够对很多其他的数据源的数据做查询;

  • 比如数据源有HDFS、Hive、Druid、Kafka、kudu、MySQL、Redis等;下图是Presto 0.237支持的数据源

    image-20200628160739942

2. Presto架构

  • Presto查询引擎是一个Master-Slave的架构,Coordinator是主,worker是从;
  • 一个presto集群,由一个Coordinator节点,一个Discovery Server节点(通常内嵌于Coordinator节点中),多个Worker节点组成
    • Coordinator负责接收查询请求、解析SQL语句、生成执行计划、任务调度给Worker节点执行、worker管理。
    • Worker节点是工作节点;负责实际执行查询任务Task;Worker节点启动后向Discovery Server服务注册;Coordinator从Discovery Server获得可以正常工作的Worker节点。
  • Presto CLI提交查询到Coordinator
  • catalog表示数据源;每个catalog包含Connector及Schema
    • 其中Connector是数据源的适配器;presto通过Connector与不同的数据源(如Redis、Hive、Kafka)连接;如果配置了Hive Connector,需要配置一个Hive MetaStore服务为Presto提供Hive元信息,Worker节点与HDFS交互读取数据。
    • Schema类似于MySQL中的数据库的概念;Schema中又包含Table,类似于MySQL中的表

3. Presto特点

1. 优点

  • 高性能:Presto基于内存计算,减少数据的落盘,计算更快;轻量快速,支持近乎实时的查询
  • 多数据源:通过配置不同的Connector,presto可以连接不同的数据源,所以可以将来自不同数据源的表进行连接查询
  • 支持SQL:完全支持ANSI SQL,并提供了sql shell命令行工具
  • 扩展性:可以根据实际的需要,开发特定的数据源的Connector,从而可以sql查询此数据元的数据

2. 缺点

  • 虽然Presto是基于内存做计算;但是数据量大时,数据并非全部存储在内存中;
    • 比如Presto可针对PB级别的数据做计算,但Presto并非将所有数据全部存储在内存中,不同场景有不同做法;
    • 比如count, avg等聚合运算,会读部分数据,计算,在清理内存;再读数据再计算、清理内存;所以占据内存并不是很高;
    • 但是如果做join操作,中间可能会产生大量的临时数据,造成执行速度变慢;join时,hive的数据反而更快些。所以如果join的话,建议在hive中,先进行join生成宽表,再使用presto查询此宽表数据

3. presto与impala对比

  • impala性能比presto稍好
  • 但是,impala只能对接hive;而presto能对接很多种类的数据源

4. 安装部署Presto

官网地址:https://prestodb.io/

github地址

presto集群规划

主机名 角色
node01 coordinator
node02 worker
node03 worker

1. 安装部署Presto Server

presto要求

image-20200629145103059

确认python版本是2.4+

python -V

image-20200629160952988

确认java版本是8u151+;若如下图,是151之前的版本,安装presto时,需要特殊处理

image-20200629161305214

1. 下载安装包

https://repo1.maven.org/maven2/com/facebook/presto/presto-server/0.237/presto-server-0.237.tar.gz

然后将tar.gz包上传到node01的/kkb/soft目录

2. 解压

cd /kkb/soft/
tar -xzvf presto-server-0.237.tar.gz -C /kkb/install/

3. 配置JAVA

若java版本低于8u151,那么需要上传8u151+的版本压缩包到/kkb/soft;若不低于,则跳过此步骤

解压

cd /kkb/soft/
tar -xzvf jdk-8u251-linux-x64.tar.gz -C /kkb/install/
cd /kkb/install/
scp -r jdk1.8.0_251/ node02:$PWD
scp -r jdk1.8.0_251/ node03:$PWD

presto-server目录创建软链接

ln -s presto-server-0.237/ presto

指定presto使用的java版本(3个节点都要修改)

vim /kkb/install/presto/bin/launcher

添加如下内容

PATH=/kkb/install/jdk1.8.0_251/bin:$PATH
java -version

注意:需要加在exec "$(dirname "$0")/launcher.py" "$@"之前

image-20200629174956052

3. 创建相关目录

创建存储数据文件夹;presto将存储log及其他数据到此目录

cd /kkb/install
cd presto
mkdir data

创建存储配置文件的文件夹

mkdir etc

4. 添加JVM配置文件

etc目录下添加jvm.config配置文件

cd /kkb/install/presto/etc
vim jvm.config

内容如下

-server
-Xmx16G
-XX:+UseG1GC
-XX:G1HeapRegionSize=32M
-XX:+UseGCOverheadLimit
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:+ExitOnOutOfMemoryError

5. 配置数据源

  • presto支持不同的数据源,通过catalog进行配置;不同的数据源,有不同的catalog
  • 现以hive数据源为例,创建个hive的catalog
  • etc中创建目录catalog
cd /kkb/install/presto-server-0.237/etc
mkdir catalog
cd catalog
vim hive.properties
  • 添加如下内容
connector.name=hive-hadoop2
hive.metastore.uri=thrift://node03:9083

6. 分发presto

cd /kkb/install/
scp -r presto node02:/kkb/install/
scp -r presto node03:/kkb/install/

7. 配置node.properties

  • 进入三台节点的/kkb/install/presto/etc目录,修改node.properties文件
cd /kkb/install/presto/etc
vim node.properties
  • 三台节点的内容分别如下
# node01如下内容
node.environment=production
node.id=ffffffff-ffff-ffff-ffff-fffffffffff1
node.data-dir=/kkb/install/presto/data

# node2如下内容
node.environment=production
node.id=ffffffff-ffff-ffff-ffff-fffffffffff2
node.data-dir=/kkb/install/presto/data

# node03如下内容
node.environment=production
node.id=ffffffff-ffff-ffff-ffff-fffffffffff3
node.data-dir=/kkb/install/presto/data

说明:

node.environment 环境的名称;presto集群各节点的此名称必须保持一致

node.id presto每个节点的id,必须唯一

node.data-dir 存储log及其他数据的目录

8. 配置config.properties

  • 通过配置config.properties文件,指明server是coordinator还是worker

  • 虽然presto server可以同时作为coordinator和worker;但是为了更好的性能,一般让server要么作为coordinator,要么作为worker

  • presto是主从架构;主是coordinator,从是worker

  • 现设置node01作为coordinator节点;node02、node03节点作为worker节点

  • node01上配置coordinator

cd /kkb/install/presto/etc
vim config.properties
  • 添加如下内容
coordinator=true
node-scheduler.include-coordinator=false
http-server.http.port=8880
query.max-memory=50GB
query.max-memory-per-node=1GB
discovery-server.enabled=true
discovery.uri=http://node01:8880

说明:

coordinator=true 允许此presto实例作为coordinator

node-scheduler.include-coordinator 是否允许在coordinator上运行work

http-server.http.port presto使用http服务进行内部、外部的通信;指定http server的端口

query.max-memory 一个查询运行时,使用的所有的分布式内存的总量的上限

query.max-memory-per-node query在执行时,使用的任何一个presto服务器上使用的内存上限

discovery-server.enabled presto使用discovery服务,用来发现所有的presto节点

discovery.uri discovery服务的uri

  • node02、node03上配置worker
cd /kkb/install/presto/etc
vim config.properties
  • 添加如下内容
coordinator=false
http-server.http.port=8880
query.max-memory=50GB
discovery.uri=http://node01:8880

9. 启动presto server

  • 若要用presto对接hive数据,需要启动hive metastore服务
  • 上课环境:hive安装在node03上,所以在node03启动metastore服务
nohup hive --service metastore > /dev/null 2>&1 &
  • 在node01、node02、node03上分别启动presto server,执行以下命令
cd /kkb/install/presto
# 前台启动,控制台打印日志
bin/launcher run
# 或使用后台启动presto
bin/launcher start
  • jps查看,各节点出现名为PrestoServer的进程
  • 日志所在目录
/kkb/install/presto/data/var/log

2. 安装部署Presto命令行接口

1. 下载安装包

2. 重命名文件

cd /kkb/soft
mv presto-cli-0.237-executable.jar prestocli

3. 增加可执行权限

chmod u+x prestocli

4. 启动presto cli

  • 注意:先启动HDFS

  • 查看presto客户端jar包的使用方式

./prestocli --help
  • 两种方式;方式一
./prestocli --server node01:8880 --catalog hive --schema default

说明:

--catalog hive 中的hive指的是presto/etc/catalog中的hive.properties的文件名

  • 方式二
java -jar presto-cli-0.237-executable.jar --server node01:8880 --catalog hive --schema default
  • 退出presto cli
quit

5. 体验命令操作

Presto的命令行操作,相当于Hive命令行操作。每个表必须要加上schema前缀;例如

select * from schema.<tablename> limit 5;

或者切换到指定的schema,再查询表数据

use myhive;
select * from score limit 3;

异常处理:在node01进行presto查询时没有响应

presto:default> show tables;

Query 20191008_100336_00007_kdyy2, WAITING_FOR_RESOURCES, 0 nodes, 0 splits

解决办法:这是因为在前面的配置中node01只配置了coordinator,不允许worker同时运行,如果想要node01既运行coordinator又同时运行worker,需要修改配置文件:

vim ./etc/config.properties

...
node-scheduler.include-coordinator=true
...

3. 安装部署Presto 可视化客户端

1. 下载安装包

  • presto有个开源的带可视化界面的客户端yanagishima
  • 源码下载地址:yanagishima
  • 官网地址
  • 将下载的包yanagishima-18.0.zip上传到node01点/kkb/soft目录

2. 解压缩

cd /kkb/soft
unzip -d /kkb/install yanagishima-18.0.zip

# 若出现-bash: unzip: command not found,表示没有安装unzip;需要安装;然后再解压缩
sudo yum -y install unzip zip

cd /kkb/install/yanagishima-18.0

3. 修改配置文件

  • 修改yanagishima.properties文件
cd /kkb/install/yanagishima-18.0/conf
vim yanagishima.properties
  • 添加如下内容
jetty.port=7080
presto.datasources=kkb-presto
presto.coordinator.server.kkb-presto=http://node01:8880
catalog.kkb-presto=hive
schema.kkb-presto=default
sql.query.engines=presto

注意这里kkb-presto是数据源的名称,可以自定义

4. 启动yanagishima

nohup bin/yanagishima-start.sh >yanagishima.log 2>&1 &
  • 注意一定要进入yanagishima的安装目录下以相对路径方式执行以上命令启动

  • node01上多出名为YanagishimaServer的进程

  • 启动web界面

    http://node01:7080

    在界面中进行查询了

    若ui界面显示很慢,或者不显示,可以尝试将node01替换成相应的ip地址

  • 查看表结构;

  • 每个表后面都有个复制键,点一下会复制完整的表名,然后再上面框里面输入sql语句,ctrl+enter组合键或Run按钮执行显示结果

image-20200630111827193

  • 这里有个Tree View,可以查看所有表的结构,包括Schema、表、字段等。

  • 比如执行select * from hive.myhive.score,这个句子里Hive这个词可以删掉,即变成select * from myhive.score;hive是上面配置的Catalog名称

  • 特别注意:sql语句末尾千万不要加分号;否则报错

image-20200630112056143

5. Presto查询及优化

1. Presto sql语法

以下用presto的客户端连接hive connector来进行演示

如果使用UI界面演示去掉SQL语句最后面的分号即可

查看schema有哪些(对应hive中的database)

SHOW SCHEMAS;

进入schema

use myhive;

查看有哪些表

SHOW TABLES;

创建schema

语法:CREATE SCHEMA [ IF NOT EXISTS ] schema_name

CREATE SCHEMA testschema;

删除schema

语法:DROP SCHEMA [ IF EXISTS ] schema_name
drop schema testschema;

创建表

语法:CREATE TABLE [ IF NOT EXISTS ]
table_name (column_name data_type [ COMMENT comment],... ]

create table stu4(id int, name varchar(20));

创建表CTAS

语法:
CREATE TABLE [ IF NOT EXISTS ] table_name [ ( column_alias, ... ) ]
[ COMMENT table_comment ]
[ WITH ( property_name = expression [, ...] ) ]
AS query
[ WITH [ NO ] DATA ]

create table if not exists myhive.stu5 as select id, name from stu1;

删除表中符合条件的行

语法:DELETE FROM table_name [ WHERE condition ]
说明:hive connector只支持一次性的删除一个完整的分区;不支持删除一行数据

DELETE FROM order_partition where month='2019-03';

查看表的描述信息

DESCRIBE hive.myhive.stu1;

ANALYZE获得表及列的统计信息

语法:ANALYZE table_name

ANALYZE hive.myhive.stu1;

prepare 给statement起一个名称,等待将来的执行

execute执行一个准备好的statement

语法:PREPARE statement_name FROM statement

prepare my_select1 from select * from score;
execute my_select1;

prepare my_select2 from select * from score where s_score < 90 and s_score > 70;
execute my_select2;

prepare my_select3 from select * from score where s_score < ? and s_score > ?;
execute my_select3 using 90, 70;

EXPLAIN:查询一个statement的逻辑计划或分布式执行计划,或校验statement

语法:
EXPLAIN [ ( option [, ...] ) ] statement

where option can be one of:

    FORMAT { TEXT | GRAPHVIZ | JSON }
    TYPE { LOGICAL | DISTRIBUTED | VALIDATE | IO }

查询逻辑计划语句:
explain select s_id, avg(s_score) from score group by s_id;
等价于
explain (type logical)select s_id, avg(s_score) from score group by s_id;

查询分布式执行计划distributed execution plan
explain (type distributed)select s_id, avg(s_score) from score group by s_id;

校验语句的正确性
explain (type validate)select s_id, avg(s_score) from score group by s_id;

explain (type io, format json)select s_id, avg(s_score) from score group by s_id;

SELECT查询

语法:
[ WITH with_query [, ...] ]
SELECT [ ALL | DISTINCT ] select_expr [, ...]
[ FROM from_item [, ...] ]
[ WHERE condition ]
[ GROUP BY [ ALL | DISTINCT ] grouping_element [, ...] ]
[ HAVING condition]
[ { UNION | INTERSECT | EXCEPT } [ ALL | DISTINCT ] select ]
[ ORDER BY expression [ ASC | DESC ] [, ...] ]
[ LIMIT [ count | ALL ] ]

from_item:
table_name [ [ AS ] alias [ ( column_alias [, ...] ) ] ]
from_item join_type from_item [ ON join_condition | USING ( join_column [, ...] ) ]

join_type:
[ INNER ] JOIN
LEFT [ OUTER ] JOIN
RIGHT [ OUTER ] JOIN
FULL [ OUTER ] JOIN
CROSS JOIN

grouping_element:
()
expression
GROUPING SETS ( ( column [, ...] ) [, ...] )
CUBE ( column [, ...] )
ROLLUP ( column [, ...] )

语句:
with语句:用于简化内嵌的子查询
select a, b
from (
select s_id as a, avg(s_score) as b from score group by s_id
) as tbl1;

等价于:
with tbl1 as (select s_id as a, avg(s_score) as b from score group by s_id)
select a, b from tbl1;

多个子查询也可以用with
WITH
  t1 AS (SELECT a, MAX(b) AS b FROM x GROUP BY a),
  t2 AS (SELECT a, AVG(d) AS d FROM y GROUP BY a)
SELECT t1.*, t2.*
FROM t1
JOIN t2 ON t1.a = t2.a;

with语句中的关系可以串起来(chain)
WITH
  x AS (SELECT a FROM t),
  y AS (SELECT a AS b FROM x),
  z AS (SELECT b AS c FROM y)
SELECT c FROM z;

group by:
select s_id as a, avg(s_score) as b from score group by s_id;
等价于:
select s_id as a, avg(s_score) as b from score group by 1;
1代表查询输出中的第一列s_id

select count(*) as b from score group by s_id;

2. 存储优化

  • 合理设置分区

    与Hive类似,Presto会根据元信息读取分区数据,合理的分区能减少Presto数据读取量,提升查询性能。

  • 使用列式存储

    Presto对ORC文件读取做了特定优化,因此在Hive中创建Presto使用的表时,建议采用ORC格式存储。相对于Parquet,Presto对ORC支持更好。

  • 使用压缩

    数据压缩可以减少节点间数据传输对IO带宽压力,对于即席查询需要快速解压,建议采用snappy压缩

  • 预先排序

    对于已经排序的数据,在查询的数据过滤阶段,ORC格式支持跳过读取不必要的数据。比如对于经常需要过滤的字段可以预先排序。

3. SQL优化

  • 列剪裁

    只选择使用必要的字段: 由于采用列式存储,选择需要的字段可加快字段的读取、减少数据量。避免采用*读取所有字段

[GOOD]: SELECT s_id, c_id FROM score

[BAD]:  SELECT * FROM score
  • 过滤条件必须加上分区字段

    对于分区表,where语句中优先使用分区字段进行过滤。day是分区字段,vtime是具体访问时间

[GOOD]: SELECT vtime, stu, address FROM tbl where day=20200501

[BAD]:  SELECT * FROM tbl where vtime=20200501
  • Group By语句优化:

    合理安排Group by语句中字段顺序对性能有一定提升。将Group By语句中字段按照每个字段distinct数据多少进行降序排列, 减少GROUP BY语句后面的排序一句字段的数量能减少内存的使用.

uid个数多;gender少
[GOOD]: SELECT GROUP BY uid, gender

[BAD]:  SELECT GROUP BY gender, uid
  • Order by时使用Limit, 尽量避免ORDER BY: Order by需要扫描数据到单个worker节点进行排序,导致单个worker需要大量内存
[GOOD]: SELECT * FROM tbl ORDER BY time LIMIT 100

[BAD]:  SELECT * FROM tbl ORDER BY time
  • 使用近似聚合函数: 对于允许有少量误差的查询场景,使用这些函数对查询性能有大幅提升。比如使用approx_distinct() 函数比Count(distinct x)有大概2.3%的误差
select approx_distinct(s_id) from score;
  • 用regexp_like代替多个like语句: Presto查询优化器没有对多个like语句进行优化,使用regexp_like对性能有较大提升
SELECT
...
FROM
access
WHERE
method LIKE '%GET%' OR
method LIKE '%POST%' OR
method LIKE '%PUT%' OR
method LIKE '%DELETE%'

优化:
SELECT
...
FROM
access
WHERE
regexp_like(method, 'GET|POST|PUT|DELETE')
  • 使用Join语句时将大表放在左边: Presto中join的默认算法是broadcast join,即将join左边的表分割到多个worker,然后将join右边的表数据整个复制一份发送到每个worker进行计算。如果右边的表数据量太大,则可能会报内存溢出错误。
[GOOD] SELECT ... FROM large_table l join small_table s on l.id = s.id
[BAD] SELECT ... FROM small_table s join large_table l on l.id = s.id
  • 使用Rank函数代替row_number函数来获取Top N

  • UNION ALL 代替 UNION :不用去重

  • 使用WITH语句: 查询语句非常复杂或者有多层嵌套的子查询,请试着用WITH语句将子查询分离出来

6. 其他注意事项

1. 字段名引用

  • 避免和关键字冲突:MySQL对字段加反引号`;Presto对字段加双引号分割

    当然,如果字段名称不是关键字,可以不加这个双引号。

2. 函数

  • 对于Timestamp,需要进行比较的时候,需要添加Timestamp关键字,而MySQL中对Timestamp可以直接进行比较。
/*MySQL的写法*/
SELECT t FROM a WHERE t > '2020-05-01 00:00:00'; 

/*Presto的写法*/
SELECT t FROM a WHERE t > timestamp '2020-05-01 00:00:00';

3. 不支持INSERT OVERWRITE语法

  • Presto中不支持insert overwrite语法,只能先delete,然后insert into。

4. QUET格式

  • Presto目前支持Parquet格式,支持查询,但不支持insert

五、拓展

Views: 32

编写控制台游戏程序

现在让我们使用所学的知识完成一个游戏程序。这里我们将不使用任何图形界面,而是制作一个简单的、控制台上运行的字符界面的游戏。

需要注意Windows的控制台程序和Linux的控制台程序需要使用各自不同的方法来实现诸如光标移动,颜色设置等操作,下面分别讲解。

清屏

在Windows下控制台窗口的控制是基于win32 api, 就是那些在cmd下可以执行的命令, 使用之前需要引入头文件windows.h

例如Windows下清除屏幕:

system("cls");

而在Linux下是通过Shell命令,只需要引入stdlib.h即可,比如要实现清除屏幕:

system("clear);

休眠

Window的控制台中休眠,可以调用windows.h中的Sleep(),比如Sleep(1000)函数,这里1000为毫秒数,功能为延时1s后程序向下运行,下面是一个3秒倒计时程序:

for(int i = 3; i >= 1; i--) {
  printf("%d\n", i);
  Sleep(1000);
}

Linux的控制台休眠可以使用stdlib.h中的system()调用Shell命令sleep n, 这里n代表秒数。

Linux下程序需要这样改写:

for(int i = 3; i >= 1; i--) {
  printf("%d\n", i);
  system("sleep 1");
}

windows控制台窗口操作API

Windows.h中定义的用于控制台窗口操作的API函数如下:

  • GetConsoleScreenBufferInfo 获取控制台窗口信息
  • GetConsoleTitle 获取控制台窗口标题
  • ScrollConsoleScreenBuffer 在缓冲区中移动数据块
  • SetConsoleScreenBufferSize 更改指定缓冲区大小
  • SetConsoleTitle 设置控制台窗口标题
  • SetConsoleWindowInfo 设置控制台窗口信息
#include <windows.h>
SetConsoleTitle("hello world!"); // 设置

由于Linux中往往不安装图形界面的,因此一般也不需要控制控制台窗口,这里就不介绍了。

控制台初始化

#include <iostream>
#include <windows.h>
using namespace std;

int main()
{
    //设置控制台窗口标题
    SetConsoleTitle("hello world!");

    //获取控制台窗口信息;
    //GetConsoleScreenBufferInfo(HANDLE hConsoleOutput, CONSOLE_SCREEN_BUFFER_INFO *bInfo)
    //第一个hConsoleOutput参数(标准控制句柄)通过GetStdHandle()函数返回值获得
    //第二个参数CONSOLE_SCREEN_BUFFER_INFO 保存控制台信息结构体指针
        /*数据成员如下:
        {
            COORD dwSize; // 缓冲区大小
            COORD dwCursorPosition; //当前光标位置
            WORD wAttributes; //字符属性
            SMALL_RECT srWindow; //当前窗口显示的大小和位置
            COORD dwMaximumWindowSize; //最大的窗口缓冲区大小
        }
        */
    HANDLE hOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO bInfo;
    GetConsoleScreenBufferInfo(hOutput, &bInfo);
    cout << "窗口缓冲区大小:" << bInfo.dwSize.X << ", " << bInfo.dwSize.Y << endl;
    cout << "窗口坐标位置:" << bInfo.srWindow.Left << ", " << bInfo.srWindow.Top
         << ", "<< bInfo.srWindow.Right << ", " << bInfo.srWindow.Bottom << endl;

    //设置显示区域坐标
    //SetConsoleWindowInfo(HANDLE, BOOL, SMALL_RECT *);
    SMALL_RECT rc = {0,0, 79, 24}; // 坐标位置结构体初始化
    SetConsoleWindowInfo(hOutput,true ,&rc);
    cout << "窗口显示坐标位置:" << bInfo.srWindow.Left << ", " << bInfo.srWindow.Top
         << ", "<< bInfo.srWindow.Right << ", " << bInfo.srWindow.Bottom << endl;

    //更改指定缓冲区大小
    //SetConsoleScreenBufferSize(HANDLE hConsoleOutput, COORD dwSize)
    //COORD为一个数据结构体
    COORD dSiz = {80, 25};
    SetConsoleScreenBufferSize(hOutput, dSiz);
    cout << "改变后大小:" << dSiz.X << ", " << dSiz.Y << endl;

    //获取控制台窗口标题
    //GetConsoleTitle(LPTSTR lpConsoleTitle, DWORD nSize)
    //lpConsoleTitle为指向一个缓冲区指针以接收包含标题的字符串;nSize由lpConsoleTitle指向的缓冲区大小
    char cTitle[255];
    GetConsoleTitleA(cTitle, 255);
    cout << "窗口标题:" << cTitle << endl;

    // 关闭标准输出设备句柄
    CloseHandle(hOut);
    return 0;
}

设置文本颜色和光标移动控制

#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
    /*设置文本属性
    BOOL SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttributes);//句柄, 文本属性*/

    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); // 获取标准输出设备句柄
    WORD wr1 = 0xfa;//定义颜色属性;第一位为背景色,第二位为前景色
    SetConsoleTextAttribute(hOut, wr1);
    cout << "hello world!" << endl;

    WORD wr2 = FOREGROUND_RED | FOREGROUND_INTENSITY;//方法二用系统宏定义颜色属性
    SetConsoleTextAttribute(hOut, wr2);
    cout << "hello world!" << endl;

    /*移动文本位置位置
    BOOL ScrollConsoleScreenBuffer(HANDLE hConsoleOutput, CONST SMALL_RECT* lpScrollRectangle, CONST SMALL_RECT* lpClipRectangle,
                                   COORD dwDestinationOrigin,CONST CHAR_INFO* lpFill);
                                  // 句柄// 裁剪区域// 目标区域 // 新的位置// 填充字符*/
    //输出文本
    SetConsoleTextAttribute(hOut, 0x0f);
    cout << "01010101010101010101010101010" << endl;
    cout << "23232323232323232323232323232" << endl;
    cout << "45454545454545454545454545454" << endl;
    cout << "67676767676767676767676767676" << endl;

    SMALL_RECT CutScr = {1, 2, 10, 4}; //裁剪区域与目标区域的集合行成剪切区域
    SMALL_RECT PasScr = {7, 2, 11, 9}; //可以是NULL,即全区域
    COORD pos = {1, 8};     //起点坐标,与裁剪区域长宽构成的区域再与目标区域的集合为粘贴区

    //定义填充字符的各个参数及属性
    SetConsoleTextAttribute(hOut, 0x1);
    CONSOLE_SCREEN_BUFFER_INFO Intsrc;
    GetConsoleScreenBufferInfo(hOut, &Intsrc);
    CHAR_INFO chFill = {'A',  Intsrc.wAttributes}; //定义剪切区域填充字符
    ScrollConsoleScreenBuffer(hOut, &CutScr, &PasScr, pos, &chFill); //移动文本

    CloseHandle(hOut); // 关闭标准输出设备句柄
    return 0;
}

WORD文本属性预定义宏:(可以直接用16进制表示,WORD w = 0xf0;前一位表示背景色,后一位代表前景色)

FOREGROUND_BLUE 蓝色
FOREGROUND_GREEN 绿色
FOREGROUND_RED 红色
FOREGROUND_INTENSITY 加强
BACKGROUND_BLUE 蓝色背景
BACKGROUND_GREEN 绿色背景
BACKGROUND_RED 红色背景
BACKGROUND_INTENSITY 背景色加强
COMMON_LVB_REVERSE_VIDEO 反色

当前文本属性信息可通过调用函数
GetConsoleScreenBufferInfo后,在CONSOLESCREEN BUFFER_INFO结构成员wAttributes中得到。
在指定位置处写属性

BOOL WriteConsoleOutputAttribute(HANDLE hConsoleOutput, CONST WORD *lpAttribute, DWORD nLength, 
                                COORD dwWriteCoord, LPDWORD lpNumberOfAttrsWritten);
                                //句柄, 属性, 个数, 起始位置, 已写个数*/

填充指定数据的字符

BOOL FillConsoleOutputCharacter(HANDLE hConsoleOutput, TCHAR cCharacter,DWORD nLength, 
                               COORD dwWriteCoord, LPDWORD lpNumberOfCharsWritten);
                               // 句柄, 字符, 字符个数, 起始位置, 已写个数*/

在当前光标位置处插入指定数量的字符

BOOL WriteConsole(HANDLE hConsoleOutput, CONST VOID *lpBuffer, DWORD nNumberOfCharsToWrite,
                 LPDWORD lpNumberOfCharsWritten,LPVOID lpReserved);
                 //句柄, 字符串, 字符个数, 已写个数, 保留*/

向指定区域写带属性的字符

BOOL WriteConsoleOutput(HANDLE hConsoleOutput, CONST CHAR_INFO *lpBuffer, COORD dwBufferSize,
                       COORD dwBufferCoord,PSMALL_RECT lpWriteRegion );
                       // 句柄 // 字符数据区// 数据区大小// 起始坐标// 要写的区域*/

在指定位置处插入指定数量的字符

BOOL WriteConsoleOutputCharacter(HANDLE hConsoleOutput, LPCTSTR lpCharacter, DWORD nLength,
                                COORD dwWriteCoord, LPDWORD lpNumberOfCharsWritten);
                                // 句柄// 字符串// 字符个数// 起始位置// 已写个数*/

填充字符属性

BOOL FillConsoleOutputAttribute(HANDLE hConsoleOutput, WORD wAttribute,DWORD nLength,
                               COORD dwWriteCoord, LPDWORD lpNumberOfAttrsWritten);
                               //句柄, 文本属性, 个数, 开始位置, 返回填充的个数*/

设置代码页,代码页是字符集编码的别名,也有人称"内码表"。

SetConsoleOutputCP(437);
//如(简体中文) 设置成936

光标操作控制

#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
    cout << "hello world!" << endl;

    //设置光标位置
    //SetConsoleCursorPosition(HANDLE hConsoleOutput,COORD dwCursorPosition);
    //设置光标信息
    //BOOL SetConsoleCursorInfo(HANDLE hConsoleOutput, PCONST PCONSOLE_CURSOR_INFO lpConsoleCursorInfo);
    //获取光标信息
    //BOOL GetConsoleCursorInfo(HANDLE hConsoleOutput,  PCONSOLE_CURSOR_INFO lpConsoleCursorInfo);
    //参数1:句柄;参数2:CONSOLE_CURSOR_INFO结构体{DWORD dwSize;(光标大小取值1-100)BOOL bVisible;(是否可见)}

    Sleep(2000);//延时函数
    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD w = {0, 0};
    SetConsoleCursorPosition(hOut, w);
    CONSOLE_CURSOR_INFO cursorInfo = {1, FALSE};
    Sleep(2000);//延时函数
    SetConsoleCursorInfo(hOut, &cursorInfo);
    CloseHandle(hOut); // 关闭标准输出设备句柄
    return 0;
}

键盘操作控制

#include <iostream>
#include <windows.h>
#include <conio.h>
using namespace std;
HANDLE hOut;
//清除函数
void cle(COORD ClPos)
{
    SetConsoleCursorPosition(hOut, ClPos);
    cout << "            " << endl;
}
//打印函数
void prin(COORD PrPos)
{
    SetConsoleCursorPosition(hOut, PrPos);
    cout << "hello world!" << endl;
}
//移动函数
void Move(COORD *MoPos, int key)
{
    switch(key)
    {
    case 72: MoPos->Y--;break;
    case 75: MoPos->X--;break;
    case 77: MoPos->X++;break;
    case 80: MoPos->Y++;break;
    default: break;
    }
}

int main()
{
    cout << "用方向键移动下行输出内容" << endl;
    hOut = GetStdHandle(STD_OUTPUT_HANDLE);//取句柄
    COORD CrPos = {0, 1};//保存光标信息
    prin(CrPos);//打印
    //等待键按下
    while(1)
    {
        if(kbhit())
        {
            cle(CrPos);//清除原有输出
            Move(&CrPos, getch());
            prin(CrPos);
        }
    }
    return 0;
}

可以用方向键任意移动hello world!

注意区分:

getch()getche()getcher()函数

Linux下控制台操作替代办法

字体颜色

在 Linux 下若想输出 类似与 Windows 下的多颜色字体如何做呢?本文就来介绍实现的方法。
首先,来看下 在Linux 下颜色的表示

注意自定义的配置需在写在\033[和m之间

\033[22;30m - black
\033[22;31m - red
\033[22;32m - green
\033[22;33m - brown
\033[22;34m - blue
\033[22;35m - magenta
\033[22;36m - cyan
\033[22;37m - gray
\033[01;30m - dark gray
\033[01;31m - light red
\033[01;32m - light green
\033[01;33m - yellow
\033[01;34m - light blue
\033[01;35m - light magenta
\033[01;36m - light cyan
\033[01;37m - white

可以看出都是使用的转义字体来实现的。
比如:
Linux 终端输入:echo -e "\033[35;1m Shocking \033[0m"
C代码: printf("\033[34mThis is blue.\033[0m\n");
是不是出错不同的颜色了,记得最后要 "\033[0m" 关闭所有属性,这样又回到了系统默认的颜色了。

一个定义宏的办法如下:([0;是用来清除之前或之后的设置)

#define COLOR(msg, code) "\033[0;1;" #code "m" msg "\033[0m"
#define RED(msg)    COLOR(msg, 31)
#define GREEN(msg)  COLOR(msg, 32)
#define YELLOW(msg) COLOR(msg, 33)
#define BLUE(msg)   COLOR(msg, 34)

光标控制

Linux 下终端 C 语言控制光标的技巧

// 清除屏幕
#define CLEAR() printf("\033[2J")

// 上移光标
#define MOVEUP(x) printf("\033[%dA", (x))

// 下移光标
#define MOVEDOWN(x) printf("\033[%dB", (x))

// 左移光标
#define MOVELEFT(y) printf("\033[%dD", (y))

// 右移光标
#define MOVERIGHT(y) printf("\033[%dC",(y))

// 定位光标

#define MOVETO(x,y) printf("\033[%d;%dH", (x), (y))

// 光标复位

#define RESET_CURSOR() printf("\033[H")

// 隐藏光标

#define HIDE_CURSOR() printf("\033[?25l")

// 显示光标

#define SHOW_CURSOR() printf("\033[?25h")

//反显

#define HIGHT_LIGHT() printf("\033[7m")

#define UN_HIGHT_LIGHT() printf("\033[27m")

模拟按键监听

windows平台接受字符并不回显可以调用<conio.h>库的getch函数, 但是这是依赖于windows的BIOS。
linux下可以使用命令stty -echo来关闭回显,当接受字符后,使用stty echo命令恢复设置即可.

linux下如何向windows的getch或者getche一样,不需要回车就可以接受字符?
这里可以使用命令raw开启一次接受一个字符的模式,接受字符后,再次使用cooked(回车之后一锅端模式)模式即可

#include <stdio.h>
#include <stdlib.h>
#define ENTER_KEY 13
#define ESCAPE_KEY 27

char getch()
{
    char ch;
    system("stty -echo raw");
    ch = getchar();
    system("stty echo cooked");
    return ch;
}

int main(int argc, char *argv[])
{
    char ch;

    system("clear\n");
    while (1)
    {
        printf("Press Enter to continue, ESC to exit\n");
        ch = getch();
        if (ch == ESCAPE_KEY)
            break;
        if (ch == ENTER_KEY)
        {
            printf("Input a character\n");
            ch = getch();
            printf("=%c\n", ch);
        }
    }

    return 0;
}

Hangman 猜词游戏

我们将从构建经典的猜词游戏,需要有两个玩家,一个玩家负责出题,另一个玩家负责猜。如果你从来没有听过这个游戏,

规则

  1. 在给定的选词范围内,比如"动物"或""国家",玩家一选择一个秘密单词并根据字母数画相应数量的短横,即“”,短横用来表示单词中的每个字母。比如秘密单词是"cat"则可以用" "表示。

  2. 游戏开始后,玩家二猜一个字母,玩家一判断这个字符是否在秘密单词中出现,如果出现则将对应位置的短横替换成正确的字符。比如猜的是a,则表示成_ _ a

  3. 如果玩家二猜错了字母,玩家一会逐步从上到下的完成一副小人上吊的图像,如果玩家2在完整的图像被画出来之前猜出来秘密单词的所有字母他就赢了,否则当完整的图像画出来之后,小人的脚离开地面就预示着玩家二输了。

    第1次猜错

    ________   

    第2次猜错

    ________   
    |      |   

    第3次猜错

    ________   
    |      |   
    |      0   

    第4次猜错

    ________   
    |      |   
    |      0   
    |     /|\  

    第5次猜错

    ________   
    |      |   
    |      0   
    |     /|\  
    |     / \  

    第6次猜错,小人脚离地,玩家二输了,游戏结束

    ________   
    |      |   
    |      0   
    |     /|\  
    |     / \  
    |          
  4. 如果玩家在小人脚离地之前猜出了所有字母,例如 c a t.则玩家二获胜。

注意:以下代码是在linux环境中运行:

游戏的初始化
// 初始化词库
char words[5][100] = {"china", "japan", "india", "korea", "syria"};
// 初始化词库的数量
int words_num = sizeof(words) / sizeof(words[0]);
// 初始化要猜的词
char secret_word[100] = "";
// 初始化要猜的词的字母数
int secret_word_len = 0;
// 显示的题板, 未猜出的字母用_代替
// 打印的上吊小人的画像
char stages[][15] = {"  ________    ",   // stage 1
                     "  |      |    ",   // stage 2
                     "  |      0    ",   // stage 3
                     "  |     /|\\  ",   // stage 4
                     "  |     / \\  ",   // stage 5
                     "  |________   "};  // stage 6
int stage_num     = sizeof(stages) / sizeof(stages[0]);
// 初始化猜错的次数
int wrong_guess = 0;
// 初始化是否赢得游戏
bool win = false;

// FUNCTION PROTOTYPES
void PrintBoard(int stage);
void StartGame();
char getch();

int main(int argc, char *argv[]) {

    // init
    srand((unsigned int)time(NULL));
    int random_index = rand() % words_num;
    strcpy(secret_word, words[random_index]);
    secret_word_len = strlen(secret_word);

    StartGame();
    return 0;
}
玩法部分

游戏的玩法部分封装到了StartGame函数中,玩法部分通常是个循环


// GAME LOOP
while (wrong_guess < stage_num) {
...
}

判断是否猜中

char *find_pos = strstr(remaining_letters, guess);
if (find_pos != NULL) {
    int pos                = find_pos - remaining_letters;
    remaining_letters[pos] = '$';
    letter_board[pos]      = guess[0];
}
else {
    wrong_guess++;
    PrintBoard(wrong_guess);
    system("sleep 1");
}

如果输了就打印上吊图像


void PrintBoard(int stage) {
    for (int i = 0; i < stage_num; i++) {
        if (i < stage)
            printf("%s\n", stages[i]);
        else
            puts("");
    }
}

练习

你也可以试着完成一个属于你自己的控制台小游戏, 多花些时间做些类似的练习可以帮助你提高编程能力,同时也能让你对编程保持兴趣。

我还记得在DOS时期(95年左右)我第一个使用QuickBasic语言制作的猜飞机头的游戏,虽然是简单的游戏但是最终被我制作的越来越复杂,比如增加关卡,添加声效,游戏记录的保存等,可惜是存储在软盘中现在已经遗失了。

  • 三子棋或者五子棋游戏

  • 根据心情选歌

  • 根据品牌打印广告语。

寻求帮助

如果你写代码时卡壳,大多数的问题通过百度都可以解决,不行的话就CSDN或者知乎。

如果英文水平不赖就科学上网去谷歌搜索,这里要强调的是浏览Stack Exchange这个神奇的网站会很有帮助。有两个版块非常有用。

一个是Stack Overflow,程序员应该没有不知道它的,上面几乎涵盖所有编程可能遇到的问题。即便你的问题是独一无二其他人都没有经历过的,这是一个QA类型的网站,你可以在上面发布问题,会有大佬帮你解答的。

https://www.codementor.io/ama/0926528143/stackoverflow-python-moderator-martijn-pieters-zopatista

http://programmers.stackexchange.com/questions/44177/what-is-the-single-most-effective-thing-you-did-to-improve-your-programming-skil

另外一个有用的板块叫Code Review, 你只要发布你的代码就会有人给你"指手画脚", 告诉你哪些地方做的好,哪些地方做的不好,如何改进之类的建议。

Views: 39