NginxQuickIN

ENGINE X - Nginx 快速上手指南

[TOC]

🌐 1. Nginx 基本概念

Nginx 是目前最流行的 Web 服务器软件,也是互联网公司和网站的首选。一开始是由一个名为 Igor Sysoev 的俄罗斯程序员开发的,目的是用于解决 C10K 问题,C10K 问题是指服务器同时支持成千上万个并发客户端的问题。这个问题在当时是非常严重的,因为当时的服务器软件都是单线程的,所以在高并发的情况下,服务器的性能会变得非常的差。

Nginx 的出现很好的解决了这个问题,在 Nginx 官方的测试结果中,Nginx 可以支持 50000 个并发连接,在后来的发展中,Nginx 也逐渐成为了最流行的 Web 服务器软件。

Nginx 具有 epoll I/O多路复用高并发高性能低内存消耗热部署等优势。

🌐 2. Nginx 安装配置

2.1 通过包管理工具安装 Nginx

① 在 Linux 环境下:

执行 sudo apt update 更新 apt 包,并执行 sudo apt install nginx 安装 Nginx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
nilera@nilera-virtual-machine:~$ sudo apt update
[sudo] password for nilera:
Get:2 http://security.ubuntu.com/ubuntu jammy-security InRelease [129 kB]
Hit:3 https://packages.microsoft.com/repos/edge stable InRelease
Hit:1 https://packages.microsoft.com/repos/code stable InRelease
Hit:4 https://ppa.launchpadcontent.net/christian-boxdoerfer/fsearch-stable/ubuntu jammy InRelease
Hit:5 https://ppa.launchpadcontent.net/git-core/ppa/ubuntu jammy InRelease
Hit:6 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy InRelease
Hit:7 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates InRelease
Hit:8 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-backports InRelease
Fetched 129 kB in 4s (29.4 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
271 packages can be upgraded. Run 'apt list --upgradable' to see them.

nilera@nilera-virtual-machine:~$ sudo apt install nginx
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
libnginx-mod-http-geoip2 libnginx-mod-http-image-filter libnginx-mod-http-xslt-filter libnginx-mod-mail libnginx-mod-stream libnginx-mod-stream-geoip2 nginx-common nginx-core
Suggested packages:
fcgiwrap nginx-doc
The following NEW packages will be installed:
libnginx-mod-http-geoip2 libnginx-mod-http-image-filter libnginx-mod-http-xslt-filter libnginx-mod-mail libnginx-mod-stream libnginx-mod-stream-geoip2 nginx nginx-common nginx-core
0 upgraded, 9 newly installed, 0 to remove and 271 not upgraded.
Need to get 697 kB of archives.
After this operation, 2,395 kB of additional disk space will be used.
Do you want to continue? [Y/n] Y
Get:1 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates/main amd64 nginx-common all 1.18.0-6ubuntu14.4 [40.0 kB]
Get:2 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates/main amd64 libnginx-mod-http-geoip2 amd64 1.18.0-6ubuntu14.4 [11.9 kB]
Get:3 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates/main amd64 libnginx-mod-http-image-filter amd64 1.18.0-6ubuntu14.4 [15.4 kB]
Get:4 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates/main amd64 libnginx-mod-http-xslt-filter amd64 1.18.0-6ubuntu14.4 [13.7 kB]
Get:5 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates/main amd64 libnginx-mod-mail amd64 1.18.0-6ubuntu14.4 [45.7 kB]
Get:6 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates/main amd64 libnginx-mod-stream amd64 1.18.0-6ubuntu14.4 [72.9 kB]
Get:7 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates/main amd64 libnginx-mod-stream-geoip2 amd64 1.18.0-6ubuntu14.4 [10.1 kB]
Get:8 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates/main amd64 nginx-core amd64 1.18.0-6ubuntu14.4 [484 kB]
Get:9 http://mirrors.tuna.tsinghua.edu.cn/ubuntu jammy-updates/main amd64 nginx amd64 1.18.0-6ubuntu14.4 [3,872 B]
Fetched 697 kB in 5s (128 kB/s)

debconf: unable to initialize frontend: Dialog
debconf: (Dialog frontend requires a screen at least 13 lines tall and 31 columns wide.)
debconf: falling back to frontend: Readline
Preconfiguring packages ...
Selecting previously unselected package nginx-common.
(Reading database ... 229379 files and directories currently installed.)
Preparing to unpack .../0-nginx-common_1.18.0-6ubuntu14.4_all.deb ...
Unpacking nginx-common (1.18.0-6ubuntu14.4) ...
Selecting previously unselected package libnginx-mod-http-geoip2.
Preparing to unpack .../1-libnginx-mod-http-geoip2_1.18.0-6ubuntu14.4_amd64.deb ...
Unpacking libnginx-mod-http-geoip2 (1.18.0-6ubuntu14.4) ...
Selecting previously unselected package libnginx-mod-http-image-filter.
Preparing to unpack .../2-libnginx-mod-http-image-filter_1.18.0-6ubuntu14.4_amd64.deb ...
Unpacking libnginx-mod-http-image-filter (1.18.0-6ubuntu14.4) ...
Selecting previously unselected package libnginx-mod-http-xslt-filter.
Preparing to unpack .../3-libnginx-mod-http-xslt-filter_1.18.0-6ubuntu14.4_amd64.deb ...
Unpacking libnginx-mod-http-xslt-filter (1.18.0-6ubuntu14.4) ...
Selecting previously unselected package libnginx-mod-mail.
Preparing to unpack .../4-libnginx-mod-mail_1.18.0-6ubuntu14.4_amd64.deb ...
Unpacking libnginx-mod-mail (1.18.0-6ubuntu14.4) ...
Selecting previously unselected package libnginx-mod-stream.
Preparing to unpack .../5-libnginx-mod-stream_1.18.0-6ubuntu14.4_amd64.deb ...
Unpacking libnginx-mod-stream (1.18.0-6ubuntu14.4) ...
Selecting previously unselected package libnginx-mod-stream-geoip2.
Preparing to unpack .../6-libnginx-mod-stream-geoip2_1.18.0-6ubuntu14.4_amd64.deb ...
Unpacking libnginx-mod-stream-geoip2 (1.18.0-6ubuntu14.4) ...
Selecting previously unselected package nginx-core.
Preparing to unpack .../7-nginx-core_1.18.0-6ubuntu14.4_amd64.deb ...
Unpacking nginx-core (1.18.0-6ubuntu14.4) ...
Selecting previously unselected package nginx.
Preparing to unpack .../8-nginx_1.18.0-6ubuntu14.4_amd64.deb ...
Unpacking nginx (1.18.0-6ubuntu14.4) ...
Setting up nginx-common (1.18.0-6ubuntu14.4) ...
debconf: unable to initialize frontend: Dialog
debconf: (Dialog frontend requires a screen at least 13 lines tall and 31 columns wide.)
debconf: falling back to frontend: Readline
Created symlink /etc/systemd/system/multi-user.target.wants/nginx.service → /lib/systemd/system/nginx.service.
Setting up libnginx-mod-http-xslt-filter (1.18.0-6ubuntu14.4) ...
Setting up libnginx-mod-http-geoip2 (1.18.0-6ubuntu14.4) ...
Setting up libnginx-mod-mail (1.18.0-6ubuntu14.4) ...
Setting up libnginx-mod-http-image-filter (1.18.0-6ubuntu14.4) ...
Setting up libnginx-mod-stream (1.18.0-6ubuntu14.4) ...
Setting up libnginx-mod-stream-geoip2 (1.18.0-6ubuntu14.4) ...
Setting up nginx-core (1.18.0-6ubuntu14.4) ...
* Upgrading binary nginx [ OK ]
Setting up nginx (1.18.0-6ubuntu14.4) ...
Processing triggers for man-db (2.10.2-1) ...
Processing triggers for ufw (0.36.1-4ubuntu0.1) ...

② 在 Mac OS 环境下:

1
brew install nginx

③ 在 Windows 环境下:

1
scoop install nginx

或者使用:

1
choco install nginx

2.2 通过源码编译的方式安装 Nginx

Nginx 是使用 C 语言开发的,因此可以像其他 C 语言的项目一样,下载 Nginx 的源码到自己的服务器上,然后执行预编译、编译和安装的过程。

① 预编译

1
2
3
4
5
6
7
./configure
--sbin-path=/usr/local/nginx/nginx \
--conf-path=/usr/local/nginx/nginx.conf \
--pid-path=/usr/local/nginx/nginx/pid \
--with-http_ssl_module \
--with-pcre=../pcre2-10.39 \
--with-zlib=../zlib-1.2.11

② 编译

1
make

③ 安装

1
make install

2.3 使用 Docker 来安装 Nginx

1
docker pull nginx

🌐 3. Nginx 常用命令

3.1 服务的启动和停止

3.1.1 服务的启动

安装完成后,可以直接在命令行输入 nginx 启动服务,若是没有出现信息则说明启动成功;若是启动失败,则会输出启动失败的原因,此时需要根据错误原因进行排查。

1
nginx

可能会遇到的问题:

问题描述 问题原因 解决方案
运行 nginx 命令出现错误:
nginx: [alert] could not open error log file: open() "/var/log/nginx/error.log" failed (13: Permission denied)
权限问题导致无法正常启动 Nginx 使用 sudo nginx 命令提高用户执行的权限。
运行 sudo nginx 命令出现错误:
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Unknown error) ... nginx: [emerg] still could not bind()
大概率是 80 端口已经被绑定了,或者是之前已经启动了 Nginx 导致端口冲突。可以查看 80 端口的占用情况进行进一步操作。 使用 kill 命令杀死占用端口的程序,然后重新运行 sudo nginx 命令。

访问 <your ipaddr>:8080 出现以下欢迎界面则表示 Nginx 启动成功。

3.1.2 查看 Nginx 的进程

使用 ps -ef|grep nginx 可以查看 Nginx 进程。这个其实并不是 Nginx 的命令,而是 Linux 自带的命令。

1
2
3
4
5
6
7
nilera@nilera-virtual-machine:~$ ps -ef|grep nginx
root 14461 1 0 10:43 ? 00:00:00 nginx: master process nginx
www-data 14462 14461 0 10:43 ? 00:00:00 nginx: worker process
www-data 14463 14461 0 10:43 ? 00:00:00 nginx: worker process
www-data 14464 14461 0 10:43 ? 00:00:00 nginx: worker process
www-data 14465 14461 0 10:43 ? 00:00:00 nginx: worker process
nilera 14542 11143 0 10:43 pts/0 00:00:00 grep --color=auto nginx

这里可以看到 Nginx 的进程包括 Master 进程Worker 进程

  • Master 进程就是Nginx 的主进程,他主要负责读取和验证配置文件,并管理 Worker进程,将工作分配给 Worker进程
  • Worker 进程就是Nginx 的工作进程,其主要负责实际的请求,完成具体的工作。
  • Master进程只有一个,而**Worker进程**可以有很多个,Worker 进程的数量可以通过配置文件来调整。

或者我们可以使用 lsof -i:80 来查看 80 端口的占用情况,需要注意:lsof 命令并不适用于所有的 Linux 发行版,有些发行版可能需要安装该命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
nilera@nilera-virtual-machine:~$ sudo lsof -i:80
[sudo] password for nilera:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
nginx 14461 root 6u IPv4 141914 0t0 TCP *:http (LISTEN)
nginx 14461 root 7u IPv6 141915 0t0 TCP *:http (LISTEN)
nginx 14462 www-data 6u IPv4 141914 0t0 TCP *:http (LISTEN)
nginx 14462 www-data 7u IPv6 141915 0t0 TCP *:http (LISTEN)
nginx 14463 www-data 6u IPv4 141914 0t0 TCP *:http (LISTEN)
nginx 14463 www-data 7u IPv6 141915 0t0 TCP *:http (LISTEN)
nginx 14464 www-data 6u IPv4 141914 0t0 TCP *:http (LISTEN)
nginx 14464 www-data 7u IPv6 141915 0t0 TCP *:http (LISTEN)
nginx 14465 www-data 6u IPv4 141914 0t0 TCP *:http (LISTEN)
nginx 14465 www-data 7u IPv6 141915 0t0 TCP *:http (LISTEN)
3.1.3 服务的停止

我们可以使用 nginx -s <signal> 来控制 Nginx 的停止或重启。<signal> 可以是 quit优雅退出、**stop立即停止reload重载配置文件reopen重新打开日志文件**中的一个。

1
2
3
nilera@nilera-virtual-machine:~$ sudo nginx -s quit
nilera@nilera-virtual-machine:~$ ps -ef|grep nginx
nilera 15527 11143 0 11:16 pts/0 00:00:00 grep --color=auto nginx

3.2 查看 Nginx 的版本、路径等信息

1
2
3
4
5
nilera@nilera-virtual-machine:~$ nginx -V
nginx version: nginx/1.18.0 (Ubuntu)
built with OpenSSL 3.0.2 15 Mar 2022
TLS SNI support enabled
configure arguments: --with-cc-opt='-g -O2 -ffile-prefix-map=/build/nginx-zctdR4/nginx-1.18.0=. -flto=auto -ffat-lto-objects -flto=auto -ffat-lto-objects -fstack-protector-strong -Wformat -Werror=format-security -fPIC -Wdate-time -D_FORTIFY_SOURCE=2' --with-ld-opt='-Wl,-Bsymbolic-functions -flto=auto -ffat-lto-objects -flto=auto -Wl,-z,relro -Wl,-z,now -fPIC' --prefix=/usr/share/nginx --conf-path=/etc/nginx/nginx.conf --http-log-path=/var/log/nginx/access.log --error-log-path=/var/log/nginx/error.log --lock-path=/var/lock/nginx.lock --pid-path=/run/nginx.pid --modules-path=/usr/lib/nginx/modules --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-proxy-temp-path=/var/lib/nginx/proxy --http-scgi-temp-path=/var/lib/nginx/scgi --http-uwsgi-temp-path=/var/lib/nginx/uwsgi --with-compat --with-debug --with-pcre-jit --with-http_ssl_module --with-http_stub_status_module --with-http_realip_module --with-http_auth_request_module --with-http_v2_module --with-http_dav_module --with-http_slice_module --with-threads --add-dynamic-module=/build/nginx-zctdR4/nginx-1.18.0/debian/modules/http-geoip2 --with-http_addition_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_sub_module

其中 --conf-path 包含了 Nginx 的配置文件的位置。在这里我们可以看到路径为:--conf-path=/etc/nginx/nginx.conf

或者可以使用 nginx -t 来快速定位到 配置文件的位置。nginx -t 同时也可以用于检测配置文件是否存在问题,如果配置文件出现错误,执行 nginx -t 会提示错误位置和错误信息。

1
2
3
nilera@nilera-virtual-machine:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Ubuntu 22.04 中,安装的 Nginx 版本为 nginx/1.18.0(Ubuntu)。在该版本中的配置文件和一些最新的版本有一些区别,体现在如下几个方面:

  • 主配置文件:/etc/nginx/nginx.conf
  • 站点配置文件目录:/etc/nginx/sites-available/etc/nginx/sites-enabled

在该版本通常将站点配置文件放在 sites-available 目录中,并通过创建符号链接到 sites-enabled 目录来启用它们。例如,可以通过以下命令创建一个简单的站点配置文件:

1
sudo nano /etc/nginx/sites-available/my_site

nginx/1.25.1 版本中,站点的配置文件和主配置文件是在写在一起的。

3.3 Nginx 的配置文件

Nginx 的配置文件可以帮助我们进行 Nginx 的一些设置,如 worker 进程数等。

我们通过以下步骤修改 Nginx 的配置文件。

① 编辑 Nginx 配置文件

1
vim /etc/nginx/nginx.conf

② 修改相应的配置项

Nginx 的配置项包括全局块、events 块、http 块。

  • 全局块

    主要是一些全局配置,比如 Worker 进程数、指定运行服务的用户等。

  • events

    主要是服务器和客户端之间网络连接的一些配置等,比如指定每个 Worker 进程可以同时接收多少个网络连接、网络I/O模型等。

  • http

    http 块是 Nginx 修改最频繁的部分,如虚拟主机、反向代理、负载均衡等,都是在 http 块中进行配置的。

    • server

      http 块下具有多个 server 块,也叫虚拟主机。

      我们可以在文件下面 include servers/*,这样就可以将 servers 目录下所有的配置文件都包含进来,这样就可以将每个虚拟主机都放在一个单独的文件中。从而让主配置文件看起来更加的清晰。

      1.18.0(Ubuntu) 版本中,可以看到主配置文件中包含 include /etc/nginx/sites-enabled/*;,而在 /etc/nginx/site-enabled 中,可以看到 default 软连接到 /etc/nginx/sites-available/default 中。

      include 命令还可以包含其他的配置文件,如 mime.types

下面是 Ubuntu 版本的配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
user www-data;
worker_processes auto; -- 这里可以用于修改 Work 进程的进程数
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
worker_connections 768;
# multi_accept on;
}

http {

##
# Basic Settings
##

sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
# server_tokens off;

# server_names_hash_bucket_size 64;
# server_name_in_redirect off;

include /etc/nginx/mime.types;
default_type application/octet-stream;

##
# SSL Settings
##

ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
ssl_prefer_server_ciphers on;

##
# Logging Settings
##

access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;

##
# Gzip Settings
##

gzip on;

# gzip_vary on;
# gzip_proxied any;
# gzip_comp_level 6;
# gzip_buffers 16 8k;
# gzip_http_version 1.1;
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

##
# Virtual Host Configs
##

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}


#mail {
# # See sample authentication script at:
# # http://wiki.nginx.org/ImapAuthenticateWithApachePhpScript
#
# # auth_http localhost/auth.php;
# # pop3_capabilities "TOP" "USER";
# # imap_capabilities "IMAP4rev1" "UIDPLUS";
#
# server {
# listen localhost:110;
# protocol pop3;
# proxy on;
# }
#
# server {
# listen localhost:143;
# protocol imap;
# proxy on;
# }
#}

③ 使用 nginx -t 命令检查配置文件是否存在问题。

④ 使用 nginx -s reload 修改配置文件后需要重新加载配置文件。

🌐 4. Nginx 常用操作

4.1 静态站点部署

我们可以使用 Hexo 来生成一个简单的静态博客网站,并将其部署到 Nginx 中。

Hexo 是一个静态博客站点生成工具,是一个基于 Node.js 的博客框架。它可以将 Markdown 格式的文档转化成静态页面,非常适合用于做个人技术博客。

下面先了解以下如何安装和启动 Hexo

① 前置要求:安装 Node.jsGit

1
2
3
4
nilera@nilera-virtual-machine:~$ sudo apt update
nilera@nilera-virtual-machine:~$ sudo apt install -y nodejs npm

nilera@nilera-virtual-machine:~$ sudo apt install git

② 检测 Node.jsnpmGit 的版本

1
2
3
4
5
6
7
8
nilera@nilera-virtual-machine:~$ nodejs -v
v12.22.9

nilera@nilera-virtual-machine:~$ npm -v
8.5.1

nilera@nilera-virtual-machine:~$ git -v
git version 2.43.0

③ 安装 Hexo

1
nilera@nilera-virtual-machine:~$ npm install hexo-cli -g

④ 安装 Hexo 有可能会出现安装速度过慢的情况,这时我们进行 npm 换源操作。

  • 查看源
1
2
nilera@nilera-virtual-machine:~$ npm config get registry
https://registry.npmjs.org/
  • 更换淘宝源

    这里需要注意,https://registry.npm.taobao.org/ 已经于 2022年05月31日 废弃,现在需要更换为新的淘宝镜像源 https://registry.npmmirror.com/。换源的配置会持久化保存到 ~/.npmrc 文件中,可以通过手动修改该文件来修改配置。

    此外我们可以安装 nrm 包来简化这一操作,nrm 其实就是 NPM Registry Manager,是一个用于管理 NPM 源的简单命令行工具。具体操作可以参考文章:NPM 如何换源 ?

1
2
nilera@nilera-virtual-machine:~$ npm config set registry https://registry.npm.taobao.org/
nilera@nilera-virtual-machine:~$ npm config set registry https://registry.npmmirror.com/
  • 恢复官方源
1
nilera@nilera-virtual-machine:~$ npm config set registry https://registry.npmjs.org
  • 删除注册表
1
nilera@nilera-virtual-machine:~$ npm config delete registry

⑤ 初始化

执行完 hexo init blog-demo 后,会在执行该目录下生成 blog-demo 目录。

1
2
3
4
nilera@nilera-virtual-machine:~$ hexo init blog-demo
INFO Cloning hexo-starter https://github.com/hexojs/hexo-starter.git
INFO Install dependencies
INFO Start blogging with Hexo!

⑥ 安装依赖

1
2
nilera@nilera-virtual-machine:~$ cd blog-demo
nilera@nilera-virtual-machine:~$ npm install

⑦ 使用 hexo g 生成网站静态页面

该命令的主要作用是将 Markdown 格式的文章,转化成静态页面,并放到 public 目录中。第一次执行该命令,会生成 public 目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
nilera@nilera-virtual-machine:~/blog-demo$ hexo g
INFO Validating config
INFO Start processing
INFO Files loaded in 1.18 s
INFO Generated: archives/2024/index.html
INFO Generated: archives/index.html
INFO Generated: archives/2024/07/index.html
INFO Generated: index.html
INFO Generated: fancybox/jquery.fancybox.min.css
INFO Generated: js/script.js
INFO Generated: css/style.css
INFO Generated: css/images/banner.jpg
INFO Generated: 2024/07/28/hello-world/index.html
INFO Generated: fancybox/jquery.fancybox.min.js
INFO Generated: js/jquery-3.6.4.min.js
INFO 11 files generated in 7.46 s

⑦ 使用 hexo shexo server 本地运行(启动本地服务器)

1
2
3
4
5
nilera@nilera-virtual-machine:~/blog-demo$ hexo server
nilera@nilera-virtual-machine:~/blog-demo$ hexo s
INFO Validating config
INFO Start processing
INFO Hexo is running at http://localhost:4000/ . Press Ctrl+C to stop.

⑧ 实际生产环境中,我们会将页面部署到 Nginx 上,我们只需要将 blog-demo 中的 public 目录复制到 Nginx 用于存放静态页面的目录下即可。

1
nilera@nilera-virtual-machine:~/blog-demo$ sudo cp -rf public/* /var/www/html/

⑨ 访问 Nginx 页面,即可进行博客网站的访问。

4.2 反向代理

反向代理是相对于正向代理来说的,简单来说,正向代理就是代理客户端,反向代理就是代理服务端。

例如,当我们想要访问一个国外的网站(如:Google)时,可能由于网络的原因无法访问到相关内容。这个时候我们就可以使用 VPN 这种代理服务器,让代理服务器作为客户端,代理我们去访问这些网站,再将访问的结果返回给我们。这就是一个典型的正向代理的例子,这里的代理服务器代理的是客户端,而且这个过程客户端是知道的,但是对于服务端来说是透明的。

反向代理是代理服务端的,比如当我们准备访问一个网站(如:Google)的时候,他后面可能有着成千上万台服务器,但是对外暴露的只有一个域名,我们也只能通过这个域名来进行访问,然后我们的请求会被转发到后面的服务器上。这种方式隐藏了真实的服务器IP地址、端口等信息。这就是反向代理的典型例子,这里的代理服务器代理的是服务端,而且这个过程服务端是知道的,但是对于客户端来说是透明的。

4.3 负载均衡

4.4 HTTPS 配置

HTTPS 协议是 HTTP 协议的安全版本,它通过对传输的数据进行加密来保证数据的安全性。HTTP 协议的默认端口号是 80 ,而 HTTPS 协议的默认端口号是 443

HTTPS 协议需要使用到 SSL 证书,再主流的云平台上都可以免费申请到 SSL 证书。证书申请完成之后,会得到密钥文件和证书文件。如果没有云平台也没有关系,我们可以通过 OpenSSL 命令来生成一个自签名的 SSL 证书,步骤如下:

① 生成私钥文件(Private Key

1
openssl genrsa -out private.key 2048

② 根据私钥生成证书签名请求文件(Certificate Signing Request, CSR

1
openssl req -new -key private.key -out cert.csr

③ 使用私钥对证书申请进行签名从而生成证书文件(pem

1
openssl x509 -req -in cert.csr -out cacert.pem -signkey private.key

4.5 虚拟主机

DockerQuickIN

Docker 快速入门

[TOC]

宝藏视频:https://www.bilibili.com/video/BV14s4y1i7Vf

如果你遇到过:

  • 应用程序的部署和环境配置过于复杂
  • 在开发环境好用,但是到了测试和生产环境都不好用的情况
  • 作为新人加入项目组,需要花费大量的时间来配置开发环境
  • 花费一整天甚至更长时间,一步一步按照配置部署文档来配置环境,但是最终却卡在中间某个步骤上,再也无法前进

那么,Docker 可以帮我们完美解决上面这些问题。

🐳 1. Docker 的基本概念

1.1 Docker 的简介

Docker 是一个用于构建(Build)运行(Run)传送(Share)应用程序的平台。

有了 Docker,我们就可以将应用程序和运行他所需要的各种依赖包、第三方软件库、配置文件等打包在一起。以便在任何环境中都能正确的运行。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口,更重要的是容器性能开销极低。

Docker17.03 版本之后分为 社区版 CE(Community Edition) 和 企业版 EE(Enterprise Edition)

1.2 为什么 要使用 Docker

为什么要使用 Docker 呢?

举个简单的例子:我们写了一个网站,用到了现在不叫流行的前后端分离架构,前端使用 Vue 框架来构建网站的界面,后端使用 JavaSpringBoot 框架来提供各种服务和接口,并且使用 MySQL 数据库来存储数据。

如果没有 Docker,我们的步骤大概是这样的:

  • Step-01:安装 NodeJS 环境。
  • Step-02:安装各种 npm 依赖。
  • Step-03:安装 Java 运行时环境。
  • Step-04:安装各种第三方依赖包。
  • Step-05:安装 MySQL 数据库。
  • Step-06:配置各种环境变量、启动脚本。
  • Step-07:配置 Redis
  • Step-08:配置 Nginx 负载均衡。
  • Step-09:其他配置项…

这仅仅是开发环境所需要进行的操作,当我们需要将我们的网站部署到测试环境或生产环境上,那么这些所有的步骤都需要重新再来一遍。

但是如果我们有了 Docker,我们就可以将他们打包成一个个集装箱,只要我们在开发环境中运行成功了,那么在测试环境中一定也是可以运行成功的。

1.3 Docker 和 虚拟机的区别

我们可能了解或使用过一些虚拟机软件,如:VMwareVirtalboxParallels Desktop,或者 WindowWSLMicrosoft Hyper-V 等功能。

他们都是完整的操作系统,和实际上使用操作系统一样,可以在实际的操作系统中运行应用程序,这是通过一种叫做虚拟化(Hypervisor)的技术实现的。虚拟化技术是一种将物理资源虚拟为多个逻辑资源的技术,他可以将一台物理服务器虚拟成多个逻辑服务器,每个逻辑服务器都有自己的操作系统、CPU、内存、硬盘和网络接口等。不同的逻辑服务器之间是完全隔离的,可以独立运行。

虚拟机在一定程度上实现了资源的整合,可以将要一台服务器的计算能力、存储能力、网络资源分配给多个逻辑服务器。实现多台服务器的功能。但是其缺点也同样明显,每台虚拟机需要占用大量的资源,比如CPU、内存、硬盘、网络等,而且启动速度非常慢。但是其实我们的每台服务器上只需要运行一个主要对外提供服务的应用程序就可以了。并不需要一个完整的操作系统所提供的所有功能。

也许我们需要的只是一个Web服务器,但是使用虚拟机却需要启动一个完整的操作系统,包括操作系统的内核、各种系统服务、各种工具甚至图形界面等,这些我们并不需要的服务占用了大量的资源,导致了资源的浪费和启动速度慢的问题。

了解了虚拟机后,我们再来看一下 Docker 是如何解决的。需要注意,Docker 中***容器(Container)的概念。需要注意的是,Docker容器是两个不同的概念,但是因为 Docker 的流行性,以至于很多人将 Docker容器 混为一谈,实际上 Docker只是容器的一种实现,是一个容器化的解决方案和平台。而容器是一种虚拟化技术,和虚拟机类似*,也是一个独立的环境,可以在这个环境中运行应用程序。和虚拟机不同的是,他并不需要在容器中运行一个完整的操作系统,而是使用宿主机的操作系统,所以启动速度非常快,通常只需要几秒钟。同时,因为需要的资源更少,所以可以在一台物理服务器上运行更多的容器,以便于更加充分的利用服务器的资源,减少资源的闲置和浪费。

正因如此,一台物理服务器上也许可以运行几个虚拟机,但是却可以运行上百个容器。

1.4 Docker 的基本原理和主要概念

镜像 Image:镜像是一个只读的模板,它可以用来创建容器。

容器 Container:容器是 Docker运行实例,他提供了一个独立的、可移植的环境,可以在这个环境中运行应用程序。

仓库 RegistryDocker 的仓库是一个用于存储 Docker镜像的地方。最流行、最常用的仓库就是 Docker Hub

类似于 JavaC++镜像类似于,而容器则就是类的实例,可以有一到多个容器。

Docker 使用的是 Client-Server 架构模式,Docker ClientDocker Daemon 之间通过 socketRESTful API 进行通信。Docker Daemon 就是 Docker 服务端的守护进程,他负责管理 Docker 的各种资源。Docker Client 负责向 Docker Daemon 发送请求,Docker Daemon 接收到请求后进行处理,然后将要结果返回给 Docker ClientDocker Daemon 是一个后台进程,用于接收并处理来自 Docker 客户端的请求,然后将结果返回给客户端。因此我们在终端中输入的各种 Docker 命令,其实都是通过 Docker Client 发送给 Docker Daemon 的,Docker Daemon 进行处理,最后将处理结果返回给 Docker Client,然后就可以再终端中看到执行结果。

🐳 2. Docker 的安装配置

2.1 Windows 环境下下载安装 Docker

Docker 官网(目前国内无法正常访问):https://www.docker.com/

可以找一些镜像网站来进行下载安装。

下载完成后,需要运行 Docker,才可以使用 Docker 的一系列命令。

Windows 系统下需要开启 Hyper-V 功能。开启方式如下:

  • 在设置中搜索 启用或关闭 Windows 功能
  • 勾选 Hyper-V 选项,按照提示重启系统即可。

2.2 Linux(Ubuntu) 环境下下载安装 Docker

2.2.1 安装

① 使用官方安装脚本自动安装

1
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

② 或者使用国内daocloud一键安装命令:

1
curl -sSL https://get.daocloud.io/docker | sh

③ 手动安装

  • 卸载旧版本

    Docker 的旧版本被称为 dockerdocker.iodocker-engine。如果已安装,请卸载它们。

    1
    sudo apt-get remove docker docker-engine docker.io containerd runc
  • 设置仓库

    更新 apt 包索引

    1
    sudo apt-get update

    安装 apt 依赖包,用于通过HTTPS来获取仓库:

    1
    2
    3
    4
    5
    sudo apt-get install apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

    添加 Docker 的官方 GPG 密钥:

    1
    curl -fsSL https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg | sudo apt-key add -

    设置稳定存储库:

    1
    2
    3
    4
    sudo add-apt-repository "deb [arch=amd64] \
    https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/ \
    $(lsb_release -cs) \
    stable"
  • 安装 Docker Engine-Community

    更新 apt 包索引:

    1
    sudo apt-get update

    安装最新版本的 Docker Engine-Communitycontainerd ,或者转到下一步安装特定版本:

    1
    sudo apt-get install docker-ce docker-ce-cli containerd.io

    安装特定版本的 Docker Engine-Community

    1
    2
    3
    4
    5
    apt-cache madison docker-ce

    docker-ce | 5:18.09.1~3-0~ubuntu-xenial | https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu xenial/stable amd64 Packages
    docker-ce | 5:18.09.0~3-0~ubuntu-xenial | https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu xenial/stable amd64 Packages
    ...
    1
    sudo apt-get install docker-ce=<VERSION_STRING> docker-ce-cli=<VERSION_STRING> containerd.io
  • 测试安装成功

    1
    $ sudo docker run hello-world
2.2.2 卸载
1
2
3
4
5
6
7
8
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

2.3 Linux(CentOS7) 环境下下载安装 Docker

安装 Docker 的必要前提条件:要安装 Docker Engine 需要 CentOS 7 的维护版本。不支持或未测试存档版本。centos-extras 库必须启用。默认情况下,此存储库是启用的。建议使用 overlay2 存储驱动程序。

可以通过 uname –acat /etc/redhat-release 查询当前OS系统:

1
2
[nilera@bigdata ~]$ uname -a
Linux bigdata 3.10.0-1160.el7.x86_64 #1 SMP Mon Oct 19 16:18:59 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
1
2
[nilera@bigdata ~]$ cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
2.3.1 安装

可以根据需要以不同的方式安装 Docker Engine

  1. 大多数用户会设置 Docker 的存储库并从中进行安装,以简化安装和升级任务。这是推荐的方法,这种方式需要访问互联网。
  2. 一些用户下载并手动安装 RPM 软件包,并完全手动管理升级。这在无法访问互联网的空白系统上安装 Docker 的情况下非常有用。
  3. 在测试和开发环境中,一些用户选择使用自动便携式的脚本来安装 Docker

① 卸载旧版本

较旧的 Docker 版本称为 dockerdocker-engine。如果已安装这些程序,请卸载它们以及相关的依赖项。

② 使用存储库安装 Docker

  • 设置存储库:

    安装 yum-utils 软件包(提供 yum-config-manager 实用程序)并且 device mapper 存储驱动程序需要 device-mapper-persistent-datalvm2

    1
    2
    3
    4
    5
    6
    # 完整命令如下
    # 安装 yum-utils、device-mapper-persistent-data、lvm2
    sudo yum install -y yum-utils device-mapper-persistent-data lvm2

    # 命令行执行效果
    [nilera@bigdata ~]$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2

    设置稳定的存储库(使用官方源地址,比较慢)。

    1
    [nilera@bigdata ~]$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
  • 现在将 Docker Engine 软件包称为 docker-ce。分为2种方式:

    1. 安装最新版本的 Docker Engine 和容器,或转到下一步以安装特定版本。

      如果提示接受 GPG 密钥,请验证指纹是否匹配 060A 61C5 1B55 8A7F 742B 77AA C52F EB6B 621E 9F35,如果是,则接受它。

      1
      [nilera@bigdata ~]$ sudo yum install docker-ce docker-ce-cli containerd.io
    2. 要安装特定版本的 Docker Engine-Community,需要在存储库中列出可用版本,然后选择并安装。

      列出并排序存储库中可用的版本。本示例按版本号(从高到低)对结果进行排序,并被截断:

      1
      [nilera@bigdata ~]$ sudo yum list docker-ce --showduplicates | sort -r

      通过其完整的软件包名称安装特定版本,该软件包名称是软件包名称docker-ce加上版本字符串。例如:docker-ce-18.09.1

      1
      [nilera@bigdata ~]$ sudo yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io
  • 使用存储库安装 Docker CE 完成

    Docker 已安装但尚未启动。docker组已被创建,但没有用户添加到该组。

    1
    2
    3
    4
    5
    6
    7
    [nilera@bigdata ~]$ cat /etc/group
    root:x:0:
    bin:x:1:
    ...
    ntp:x:38:
    cgred:x:996:
    docker:x:995:
  • 启动 Docker 服务

    1
    [nilera@bigdata ~]$ sudo systemctl start docker
  • 通过运行 hello-world 映像来验证是否正确安装了 Docker Engine

    1
    2
    [nilera@bigdata ~]$ sudo docker version
    [nilera@bigdata ~]$ sudo docker run hello-world

③ 使用便捷脚本安装 Docker CE

Dockerget.docker.comtest.docker.com 上提供了便捷脚本,用于将 Docker Engine-Community 的边缘版本和测试版本快速且非交互地安装到开发环境中。脚本的源代码在 docker-install 存储库中。

不建议在生产环境中使用这些脚本

具有一定风险:

  1. 脚本需要运行 root 或具有 sudo 特权。在运行脚本之前,应仔细检查和审核脚本。
  2. 这些脚本尝试检测 Linux 发行版和版本,并配置软件包管理系统。此外,脚本不允许自定义任何安装参数。从Docker的角度或自己组织的准则和标准角度来看,这可能导致不支持的配置。
  3. 这些脚本将安装软件包管理器的所有依赖项和建议,而无需进行确认。根据主机的当前配置,可能会安装大量软件包。
  4. 该脚本未提供用于指定要安装哪个版本的 Docker 的选项,而是安装了在”edge”通道中发布的最新版本。如果已使用其他机制将Docker安装在主机上,请不要使用便捷脚本
2.3.2 卸载
1
2
3
4
5
6
7
8
$ sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

2.4 Docker 测试

启动之后,即可在终端使用 Docker 的各种命令了。我们可以使用一条简单的命令来进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
C:\Users\NilEra>docker version
Client:
Cloud integration: v1.0.28
Version: 20.10.17
API version: 1.41
Go version: go1.17.11
Git commit: 100c701
Built: Mon Jun 6 23:09:02 2022
OS/Arch: windows/amd64
Context: default
Experimental: true

Server: Docker Desktop 4.11.1 (84025)
Engine:
Version: 20.10.17
API version: 1.41 (minimum version 1.12)
Go version: go1.17.11
Git commit: a89b842
Built: Mon Jun 6 23:01:23 2022
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.6
GitCommit: 10c12954828e7c7c9b6e0ea9b0c02b01407d3ae1
runc:
Version: 1.1.2
GitCommit: v1.1.2-0-ga916309
docker-init:
Version: 0.19.0
GitCommit: de40ad0

我们可以看到 ClientServer 的信息,如果只能看到 Client,则表示 Docker 没有启动,需要启动 Docker 之后才可以看到 Server

🐳 3. Docker 的常用命令

3.1 Docker Desktop 的使用

3.1.1 Docker Containers & Images 容器和镜像

Docker DesktopDocker 的图形化界面,Docker Desktop 封装了容器日常使用和管理的各种常用功能,打开控制台面板后,可以看到 Containers 容器和**Images镜像**,这两个菜单项分别用于查看本机的容器列表和镜像列表。

对于菜单项中的内容,我们可以进行一系列操作。

比如,我们可以点击 RUN 按钮运行一个镜像,等同于执行了 docker run 命令。点击三个小圆点,有几个选项 InspectPullPush to HubRemove,分别用于查看镜像的详细信息、拉取镜像、推送镜像、移除镜像。

3.1.2 Docker Volumes 逻辑卷

此外,还有 Volumes 逻辑卷 菜单项,在这个菜单项列表中可以看到容器的逻辑卷,逻辑卷是 Docker 中用来存储的。

Docker 容器有一个特点,就是容器中的数据是不会持久化的,当我们创建一个容器的时候,它通常以一个干净的文件系统开始,容器启动之后,我们可以在文件中创建文件、修改文件,但是当容器停止之后,容器中的所有数据都会丢失掉。

如果我们想要对于 Docker 中的数据进行容器的持久化应该怎么做?

逻辑卷就是用来解决这个问题的,它可以将容器中的目录或者指定路径映射到宿主机的某一目录或者位置上,这样就可以将数据保存到宿主机的磁盘上,实现了数据的持久化。

3.1.3 Docker Dev Environments 开发环境

用于管理开发环境,简单来说就是可以创建一个开发环境,然后通过一些代码配置这个开发环境,这样就可以将这个开发环境共享给项目中的其他开发人员,让每个人都在一个相同的环境下进行开发,避免因为环境的不一致导致的各种问题。

🐳 4. 如何从零开始构建一个 Docker 镜像

4.1 容器化和 Dockerfile

**容器化(Containerization)**:就是将应用程序打包成容器,然后在容器中运行应用程序的过程。

容器化的过程可以分为以下几个步骤:

  1. 创建一个 Dockerfile:来告诉 Docker 构建应用程序镜像所需要的步骤和配置。
  2. 使用 Dockerfile 构建镜像。
  3. 使用镜像创建和运行容器。

Dockerfile 是一个文本文件,里面包含了一条条指令,用于告诉 Docker 如何构建镜像,这个镜像中包括了我们应用程序执行的所有命令,如各种依赖、配置环境和运行应用程序所需要的所有内容,一般来说包括:精简版的操作系统(如Alpine)、应用程序的运行时环境(如NodeJSPythonJava)、应用程序(如打包好的 jar 包)、应用程序的第三方依赖库或包、应用程序的配置文件、环境变量等。

一般来说,我们会在项目的根目录下创建一个叫 Dockerfile 的文件,在这个文件中写入构建镜像所需要的各种指令之后,Docker 就会根据这个 Dockerfile 文件来构建一个镜像,有了这个镜像后,我们就可以根据这个镜像来创建容器,然后在容器中运行应用程序。

4.2 编写 Dockerfile

  1. 新建一个名为 HelloDocker 的文件夹,使用 VSCode 打开该文件夹
  2. 在文件夹中创建一个 index.js 文件,简单的输入一些代码内容,比如输出一段内容到控制台
1
console.log("Hello Docker!");
  1. 我们可以使用 NodeJS 来测试这个 JavaScript 文件,NodeJS 是一个运行时环境,它可以让我们在浏览器之外的环境运行 JavaScript 代码。JavaScriptNodeJS 的关系就像 JavaJRE 的关系一样,如果想在浏览器之外的环境中运行 JavaScript 代码就需要 NodeJS。可以使用下面的命令运行 index.js
1
PS D:\HelloDocker> node index.js
  1. 所以,当我们想要在另一个环境中运行这个应用程序时,都需要执行那些步骤呢?

    • 安装操作系统
    • 安装 JavaScript 的运行环境
    • 复制应用程序、依赖包、配置文件
    • 执行启动命令运行程序

    而有了 Docker 后,我们可以将上述的步骤写入到 Dockerfile 中,剩下的工作交给 Docker 自动完成。

  2. 创建 Dockerfile 文件

    Dockerfile 中,我们需要先指定一个基础镜像,镜像是按层次结构来构建的,每一层都是基于上一层的,所以我们需要先指定一个基础镜像,然后在此基础上添加我们的应用程序。

    注意 Dockerfile 中不需要注释,这里只是为了便于理解所以添加了注释

1
2
3
4
5
6
7
8
9
# 指定基础镜像
FROM node:14-alpine

# 复制 本地路径的 JS 文件到镜像的根目录下
COPY index.js /

# 使用 CMD 命令运行该程序 CMD ["arg1", "arg2", "arg..."], 其中 arg1 表示可执行程序的名字, arg2 及以后的参数表示可执行程序的参数
# 可以写成这种形式: CMD node /index.js
CMD ["node", "/index.js"]

4.3 创建镜像

  1. 构建 Docker 镜像

    执行 docker build 命令,hello-docker 是镜像名称;. 表示当前目录,即 Dockerfile 所在目录。

1
PS D:\HelloDocker> docker build -t hello-docker .
  1. 执行完成后,我们可以使用命令docker imagesdocker image ls 查看所有镜像。
1
2
3
PS D:\HelloDocker> docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-docker latest 31429fa0e1e7 22 seconds ago 119MB

4.4 启动容器

  1. 使用 docker run 命令运行容器。
1
2
PS D:\HelloDocker> docker run hello-docker
Hello Docker!

4.5 在其他设备上测试容器

  1. 为了测试我们的容器,我们可以将我们的镜像上传到 Docker Hub 或者 Harbor 镜像仓库中,上传完成之后,可以使用 Play with Docker 进行测试。

    上传镜像可以使用 docker push 命令。需要注意的是,推送镜像可能会出现问题:docker:denied: requested access to the resource is denied,遇到这个问题时,可以参考解决docker:denied: requested access to the resource is denied_docker login - CSDN

    ① 确保自己登录了 Docker

    ② 将镜像打 TAG,使用 docker tag hello-docker:latest nilerak/hello-docker:1.0.0

    ③ 推送镜像,使用 docker push nilerak/hello-docker:1.0.0

  2. 使用 docker pull 命令下载镜像文件。

1
docker pull nilerak/hello-docker:1.0.0

🐳 5. 如何运行一个 Docker 容器

🐳 6. Docker ComposeKubernetes

6.1 Docker Compose

Docker Compose 是 Docker 官方的开源项目,是一个用于定义和运行多容器 Docker 应用程序的工具,使用 Yaml 文件配置应用程序的服务,只需运行一条命令即可创建并启动所有的服务。

例如我们的项目可能需要前端(Vue)、后端(SpringBoot)、数据库(MySQL)、缓存(Redis)、负载均衡(Nginx)等多个服务器,这些服务相互独立,但是又存在一定的关联,需要相互配合来工作,前端需要连接后端、后端需要连接数据库。这些服务之间的关联关系,就是 Docker Compose 要解决的问题。它通过一个单独的 docker-compose.yaml 文件来将这一组互相关联的容器组合在一起,形成一个项目,然后使用一条命令 docker compose up 即可启动、停止、重建这些服务,这样就可以非常方便的管理这些服务了。

参考文献

Docker超详细基础教程 - CSDN博客

CppGamingDEV_PVZ_BASE_EASYX

🎮 从零开始进行 C++ 游戏开发

[TOC]

游戏场景概念

  • 什么是场景?
    如果将要游戏程序比作是一场有玩家参与的盛大演出,那场景就是演出过程中的一幕。在不同的幕中,会有不同的剧本逻辑,也可能会有不同的角色登场,这些角色即游戏开发中常提到的 GameObject 的概念。
    不论是玩家、敌人还是子弹、道具等,这些从概念上讲都是 GameObject 的范畴。他们接受着不同的场景剧本的指挥,进行着不同逻辑的演出。
    了解了这些,我们就可以对程序的流程进行宏观的划分,游戏的主菜单是一个场景,玩家角色选择界面也是一个场景,游戏局内的逻辑也需要放置在一个单独的场景中。
    所以我们就可以定义一个 Scene 场景基类,主菜单角色选择局内游戏 作为新类分别继承 Scene 类。

游戏主循环框架概念

  • 什么是游戏主循环框架?
    游戏程序的主体是一个永不停歇的死循环。在每次循环中,我们读取玩家的操作信息,并根据这些操作,处理玩家数据更新,在最后的绘图阶段将游戏画面根据这些更新后的数据渲染出来。
    1
    2
    3
    4
    5
    6
    7
    初始化();
    while(true) {
    读取操作();
    处理数据();
    绘制画面();
    }
    施放资源;

C++ 头文件

  • 为什么需要头文件卫士?
    如果不使用头文件卫士,可能会导致出现重定义的问题。例如,我们有一个头文件 A.h ,有一个头文件 B.h,头文件 B.h 中使用了 A.h 的内容。当我们在主函数里面使用了 A.hB.h 的时候,使用 #include 时会将头文件中的内容全部复制#include 的部分。这样的话如果不加头文件卫士,就会导致重定义的问题,如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // A.h
    int a;

    // B.h
    #include "A.h"
    int b;

    // main.cpp
    #include "A.h" // int a;
    #include "B.h" // int a;
    // int b;
    // 出现重定义错误

    // 错误信息如下
    In file included from B.h:1,
    from main.cpp:2:
    A.h:1:5: error: redefinition of 'int a'
    1 | int a;
    | ^
    In file included from main.cpp:1:
    A.h:1:5: note: 'int a' previously declared here
    1 | int a;
    | ^
    MSVC 编译器中,头文件卫士如下:
    1
    #pragma once
    在其他的一些编译器中,常写为这样:
    1
    2
    3
    4
    #ifndef __HEADER_H__
    #define __HEADER_H__

    #endif

场景管理器

游戏程序是一个巨大的死循环,也是一个巨大的状态机。不同的游戏场景代表着不同的状态,管理着这些状态的“状态机”,在游戏开发中有一个特殊的名字——场景管理器。

DataVisualization_USE_R_ECHARTS

📺 数据可视化基础(使用RECharts

本文会从 RECharts 两个方面来展开数据可视化技术的一些使用方法。

[TOC]

📊 1. 数据可视化概述

  • 数据可视化概念

    • 狭义概念:指的是数据用统计图表方式呈现
    • 广义概念:是数据可视化、信息可视化以及科学可视化等等多个领域的统称
  • 数据可视化分类

    • 数据可视化
    • 信息可视化
    • 科学可视化
  • ⭐*** 数据可视化作用***

    • 数据表达:通过计算机图形图形技术来更加友好地展示数据信息,以方便人们理解和分析数据。
    • 数据操作:以计算提供的界面、接口和协议等条件为基础完成人与数据的交互需求。
    • 数据分析:通过计算机获得多维、多源、异构和海量数据所隐含信息的核心手段,它是数据存储、数据转换、数据计算和数据可视化的综合应用。
  • 数据可视化的工具

    • REChartsD3
    • PythonpyechartsExcel
    • 第三方商业工具:FineBIFineReport
  • 学习数据可视化需要了解(具备)什么?

    • 对色彩的感知:了解原色、间色、复色等,并且了解色彩的视觉感受(心理感受),当然也就是了解即可,色彩这种东西天赋更重要一些,知道三原色是啥就行。

      • 原色:红、黄、蓝。
      • 间色:可存在多个间色,当原色之间 1:1 匹配时则为:橙、绿、紫。
      • 复色:可存在多个复色(一定包含三原色),两个间色或一种原色和其对应的间色(黄 + 紫、蓝 + 橙)相混合得到复色。
      • 色彩感受:轻重感、冷暖感、前后感、收缩感 …
    • 数据可视化的基本流程

      • 数据采集
      • 数据处理
      • 可视化映射
      • 用户感知
    • 数据可视化的一些设计技巧

      • 设计原则:注重用户的视觉体验

      • 设计技巧:

        ① (颜色)建立视觉层次,用醒目的颜色突出数据,淡化其他元素

        ② (内容)高亮显示重点内容

        ③ (跨度)提升不同区域的色阶跨度

        ④ (场景)借助场景来表现数据指标

        ⑤ (转换)将抽象的不易理解的数字转换为容易被人感知的图表

        ⑥ (简洁)尽量让图表简洁

    • 数据可视化的图表类型

      在统计图表中每一种类型的图表中都可包含不同的数据可视化图形,如:柱状图、饼图、气泡图、热力图、趋势图、直方图、雷达图、色块图、漏斗图、和弦图、仪表盘、面积图、折线图、密度图 以及 K线图等。

🎫 2. R - R语言快速入门

参考文献:数据科学中的 R 语言 (bookdown.org)

2.1 R 语言概述

R语言是为数学研究工作者设计的一种数学编程语言,主要用于统计分析、绘图、数据挖掘数据可视化。

目前主流的数据分析语言有 RPythonMatlab 等。

R Python Matlab
语言学习难易程度 入门难度低 入门难度一般 入门难度一般
使用场景 数据分析、数据挖掘、机器学习、数据可视化等。 数据分析、机器学习、矩阵运算、科学数据可视化、数字图像处理、Web应用、网络爬虫、系统运维等。 矩阵计算、数值分析、科学数据可视化、机器学习、符号计算、数字图像处理、数字信号处理、仿真模拟等。
第三方支持 拥有大量的 Packages,能够调用 CC++FortranJava等其他程序语言。 拥有大量的第三方库,能够简便地调用CC++FortranJava等其他程序语言。 拥有大量专业的工具箱,在新版本中加入了对CC++Java的支持。
流行领域 工业界 ≈ 学术界 工业界 > 学术界 工业界 ≤ 学术界
软件成本 开源免费 开源免费 商业收费

2.2 R 语言中包的概念和使用

  • 软件包和软件库

    • 软件包:R 中的包是 R 函数、数据、预编译代码以一种定义完善的格式组成的集合。

    • 软件库:软件库指的是一个包含了若干个包的目录。你可以拥有一个系统级别的软件库,也可以为每个用户单独设立一个软件库。

    • R 自带了一系列默认包(包括 basedatasetsutilsgrDevicesgraphicsstats以及methods),它们提供了种类繁多的默认函数和数据集。

      • base:包含基础的 R 函数
      • database:自带的数据包,里面的数据结构包括矩阵、向量、数据框等
      • utils:工具函数包
      • grDevices:基础绘图工具包,提供调整图形颜色和字体的功能,可用于配色
      • graphics:基础绘图工具包
      • stats:统计函数的扩展包
      • methodsR 对象和其他编程工具定义方法和类比的扩展包
  • 包的相关操作

    • 查看包的安装路径:.libPaths()
    1
    2
    > .libPaths()
    [1] "D:/R/R-4.3.3/library"
    • 查看已安装的包:library()
    • 查看编译环境下已载入的包的列表:search()
    1
    2
    3
    > search()
    [1] ".GlobalEnv" "tools:rstudio" "package:stats" "package:graphics" "package:grDevices"
    [6] "package:utils" "package:datasets" "package:methods" "Autoloads" "package:base"
    • 下载包:

      • 通过 CRAN (The Comprehensive R Archive Network) 下载。CRAN 是 R 语言的综合档案网络。用户可以从 CRAN 上下载并安装各种第三方包来扩展 R 语言的功能。

        ① 指定包名进行安装:install.packages("PackageName")

        ② 使用图形界面安装:install.packages()

      • 手动安装:https://cran.r-project.org/web/packages/XML/index.html

      • 使用 RStudio 安装

    • 包的卸载:remove.packages("PackageName")

    • 包的载入:library("PackageName")

    1
    2
    3
    4
    5
    > library("XML")
    > search() # 可以看到 "package:XML" 已经被加载进来了
    [1] ".GlobalEnv" "package:XML" "tools:rstudio" "package:stats" "package:graphics"
    [6] "package:grDevices" "package:utils" "package:datasets" "package:methods" "Autoloads"
    [11] "package:base"
    • 包的取消载入:detach("package:PackageName", unload = True)
    1
    2
    3
    4
    > detach("package:XML", unload = TRUE)
    > search()
    [1] ".GlobalEnv" "tools:rstudio" "package:stats" "package:graphics" "package:grDevices"
    [6] "package:utils" "package:datasets" "package:methods" "Autoloads" "package:base"

2.3 R 语言的工作空间的概念和使用

  • 概念:工作空间(Work Space) 就是当前 R 的工作环境,它储存着所有用户定义的对象(向量、矩阵、函数、数据框、列表)。在一个 R 会话结束时,用户可以将当前工作空间保存到一个镜像中,并在下次启动 R 时自动载入它。
  • 设置工作目录:setwd() 或使用 RStudio 可视化窗口进行设置(SessionSet Working Directory
  • 获取工作目录:getwd()

2.4 R 语言的六大原子数据类型

  • 数字型(Numeric)
  • 逻辑型(Logical)
  • 整型(Integer)
  • 字符型(Character)
  • 复合型(Complex)
  • 原子型(Raw)

对于不同原子数据类型可以按照如下方式定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
> # 数值型(Numeric)
> varNumeric = 12.5
> varNumeric
[1] 12.5

> # 逻辑型(Logical)
> varLogical = TRUE
> varLogical
[1] TRUE

> # 整型(Integer)
> varInteger = 25L
> varInteger
[1] 25

> # 字符型(Character)
> varCharacter = "Hello World"
> varCharacter
[1] "Hello World"

> # 复合型(Complex)
> varComplex = 2 + 3i
> varComplex
[1] 2+3i

> # 原子型(Raw)
> varRaw = charToRaw('Hello')
> varRaw
[1] 48 65 6c 6c 6f

2.5 R 语言变量使用

  • 定义变量并赋值:使用 <-=->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> var1 = 12.5		# 定义 var1, 并赋值为 12.5
> print(var1) # 输出 var1
[1] 12.5

> var2 <- 13 # 定义 var2, 并赋值为 13(由右向左)
> print(var2) # 输出 var2
[1] 13

> 14 -> var3 # 定义 var3, 并赋值为 14(由左向右)
> print(var3) # 输出 var3
[1] 14

> var2 -> var3
> print(var3)
[1] 13
  • 变量的打印:print()cat()

    print()cat() 都可以向控制台输出文字,区别在于 print() 有返回值,其会返回输出的内容,无法使用转义字符,当使用转义字符时,会直接输出转义字符;而 cat() 没有返回值,可以使用转义字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> varX = "Hello World"
> varY = "Hello World\n"

> printX <- print(varX)
[1] "Hello World"
> catX <- cat(varX, "\n") # 如果不添加转义字符会无法换行
Hello World

> printY <- print(varY)
[1] "Hello World\n" # 使用 print() 直接将转义字符进行了输出
> catY <- cat(varY)
Hello World # 使用 cat() 没有将转义字符直接输出, 而是转化成了响应的格式, 如"换行"

> print(printX) # print() 有返回值
[1] "Hello World"
> print(catX) # cat() 没有返回值
NULL
  • 变量的查看:ls()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> ls()						# 输出所有变量
[1] "catX" "catY" "cyl.f" "printX" "printY" "score" "tempdens" "var1" "var2" "var3"
[11] "varX" "varY"

> ls(pattern = "var") # 模式匹配部分变量, 包含 "var" 的
[1] "var1" "var2" "var3" "varX" "varY"

> ls(pattern = "*") # 模式匹配部分变量, 使用通配符
[1] "catX" "catY" "cyl.f" "printX" "printY" "score" "tempdens" "var1" "var2" "var3"
[11] "varX" "varY"

> ls(pattern = "2") # 模式匹配部分变量, 包含 "2" 的
[1] "var2"

> ls(all.names = TRUE) #
[1] ".Random.seed" "catX" "catY" "cyl.f" "printX" "printY" "score"
[8] "tempdens" "var1" "var2" "var3" "varX" "varY"
  • 变量的删除:rm()
1
2
3
4
5
6
7
8
> rm("var1", "var2")				# 删除 "var1" 和 "var2"

> rm(list = ls(pattern = "var")) # 删除 匹配到 "var" 的变量, list 是一个可以指定的参数

> rmlist = ls(pattern = "X")
> rmlist
[1] "catX" "printX"
> rm(list = rmlist)
  • [⭐] 变量的数据类型判别和转换

    • 判别:is.XXX(),返回 True/False
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    > # 数值型(Numeric)
    > varNumeric = 12.5
    > varNumeric
    [1] 12.5
    > is.numeric(varNumeric)
    [1] TRUE
    > is.logical(varNumeric)
    [1] FALSE
    > is.integer(varNumeric)
    [1] FALSE
    > is.character(varNumeric)
    [1] FALSE
    > is.complex(varNumeric)
    [1] FALSE
    > is.raw(varNumeric)
    [1] FALSE

    > # 逻辑型(Logical)
    > varLogical = TRUE
    > varLogical
    [1] TRUE
    > is.numeric(varLogical)
    [1] FALSE
    > is.logical(varLogical)
    [1] TRUE
    > is.integer(varLogical)
    [1] FALSE
    > is.character(varLogical)
    [1] FALSE
    > is.complex(varLogical)
    [1] FALSE
    > is.raw(varLogical)
    [1] FALSE
    • 转换:as.XXX()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    > # 变量类型转换
    > varNumToChar <- as.character(varNumeric)
    > varNumToChar
    [1] "12.5"
    > is.numeric(varNumToChar)
    [1] FALSE
    > is.logical(varNumToChar)
    [1] FALSE
    > is.integer(varNumToChar)
    [1] FALSE
    > is.character(varNumToChar)
    [1] TRUE <-------------------- 可以看到已经转换成了字符型
    > is.complex(varNumToChar)
    [1] FALSE
    > is.raw(varNumToChar)
    [1] FALSE
  • [⭐] 变量的数据类型查看

    • mode():查看数据元素类型
    • typeof():查看数据元素类型,基本等同于 mode() ,比 mode() 更为详细
    • class():查看数据结构,vectormatrixarraydataframelist
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    > # 查看变量类型
    > mode(varNumeric)
    [1] "numeric"
    > typeof(varNumeric)
    [1] "double"
    > class(varNumeric)
    [1] "numeric"

    > mode(varLogical)
    [1] "logical"
    > typeof(varLogical)
    [1] "logical"
    > class(varLogical)
    [1] "logical"

    > mode(varInteger)
    [1] "numeric"
    > typeof(varInteger)
    [1] "integer"
    > class(varInteger)
    [1] "integer"

    > mode(varCharacter)
    [1] "character"
    > typeof(varCharacter)
    [1] "character"
    > class(varCharacter)
    [1] "character"

    > mode(varComplex)
    [1] "complex"
    > typeof(varComplex)
    [1] "complex"
    > class(varComplex)
    [1] "complex"

    > mode(varRaw)
    [1] "raw"
    > typeof(varRaw)
    [1] "raw"
    > class(varRaw)
    [1] "raw"
  • [⭐] 字符串的应用

    • R语言中的文本,或者原子数据类型中 Character。在 R 语言中的单引号' ' 或 双引号" " 中写入的任何值都被视为字符串。在字符串构造中应用的规则:
      • 在字符串的开头和结尾的引号应该是两个双引号或两个单引号。它们不能被混合。
      • 双引号可以插入到以单引号开头和结尾的字符串中
      • 单引号可以插入以双引号开头和结尾的字符串。
      • 双引号不能插入以双引号开头和结尾的字符串。
      • 单引号不能插入以单引号开头和结尾的字符串。

2.7 R 语言的运算符的概念和使用

  • 运算符的概念

  • 算数运算符

    • +加、-减、*乘、/
    • %% 整除取余
    • %/% 整除
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    > # 相除
    > 6 / 4
    [1] 1.5

    > # %% 整除取余
    > 6 %% 4
    [1] 2

    > # %/% 整除
    > 6 %/% 4
    [1] 1
  • 关系运算符

    • < 小于、> 大于、= 等于
    • <=小于等于、>= 大于等于
    • != 不等于
  • 逻辑运算符

    • &
    • |
    • &&
    • ||

    &&||为值逻辑,&|为位逻辑
    说人话就是,&&||是讲两个操作目的值做逻辑运算,无论操作对象是向量还是标量,返回值都是一个逻辑值,(NOW)&&|| 运算符只接受长度为 1 的逻辑值作为参数;而 &| 是讲两个对象按位比较,其返回值的长度与对象是标量还是向量有关。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    > # 逻辑运算符
    > v <- c(3, 1, TRUE, 2+3i)
    > t <- c(4, 1, FALSE, 2+3i)
    > print(v&t)
    [1] TRUE TRUE FALSE TRUE

    > v <- c(3, 0, TRUE, 2+2i)
    > t <- c(1, 3, TRUE, 2+3i)
    > print(v && t)
    Error in v && t : 'length = 4' in coercion to 'logical(1)'

    > TRUE && TRUE
    [1] TRUE
    > TRUE && FALSE
    [1] FALSE
    > FALSE && TRUE
    [1] FALSE
    > FALSE && FALSE
    [1] FALSE
  • 赋值运算符

    • <-
    • =
    • ->
    • <<-
    • ->>

    <−=<<− 运算符: 称为左赋值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    > # 赋值运算符
    > # 左赋值
    > v1 <- c(3,1,TRUE,2+3i)
    > v2 <<- c(3,1,TRUE,2+3i)
    > v3 = c(3,1,TRUE,2+3i)

    > print(v1)
    [1] 3+0i 1+0i 1+0i 2+3i

    > print(v2)
    [1] 3+0i 1+0i 1+0i 2+3i

    > print(v3)
    [1] 3+0i 1+0i 1+0i 2+3i

    ->->> 运算符:称为右赋值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    > # 右赋值
    > c(3,1,TRUE,2+3i) -> v1
    > c(3,1,TRUE,2+3i) ->> v2

    > print(v1)
    [1] 3+0i 1+0i 1+0i 2+3i

    > print(v2)
    [1] 3+0i 1+0i 1+0i 2+3i
  • 其他运算符

    • : 为向量创建数字序列。
    1
    2
    3
    > v <- 2:8
    > print(v)
    [1] 2 3 4 5 6 7 8
    • %in% 用于确定元素是否属于向量。
    1
    2
    3
    4
    5
    6
    7
    > v1 <- 8
    > v2 <- 12
    > t <- 1:10
    > print(v1 %in% t) # 8 是否再 1~10 中
    [1] TRUE
    > print(v2 %in% t) # 12 是否再 1~10 中
    [1] FALSE
    • %*% 用于求两个向量的内积,也称为点乘
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    > M = matrix( c(2,6,5,1,10,4), nrow = 2, ncol = 3, byrow = TRUE)

    # M
    2, 6, 5
    1, 10, 4

    # t(M)
    2, 1
    6, 10
    5, 4

    # M %*% t(M)
    2*2 + 6*6 + 5*5, 1*2 + 10* 6 + 4*5
    2*1 + 10*6 + 4*5, 1*1 + 10*10 + 4*4

    > t = M %*% t(M)
    > print(t)
    [,1] [,2]
    [1,] 65 82
    [2,] 82 117

    > #===================================================================#
    > M = matrix(c(2, 6, 5, 1, 10, 4), nrow = 2,ncol = 3, byrow = TRUE)
    > N = matrix(c(1, 2, 3, 4, 5, 6), nrow = 3, ncol = 2, byrow = TRUE)
    > t = M %*% N
    > print(t)
    [,1] [,2]
    [1,] 45 58
    [2,] 51 66

2.8 R 语言的六大数据对象

R 语言有六种基本的数据结构(数据对象):向量vector、列表list、矩阵matrix、数组array、数据框data.frame 和 因子factor 类型。

2.8.1 向量 vector
  • 概念:向量是最基本的 R 语言数据对象,向量的元素支持六种原子数据类型,即逻辑,整数,双精度,复合,字符和原型。

  • 特征:一个向量的所有元素都必须属于相同的类型。如果不是,R将强制执行类型转换。

  • 创建向量:c()seq()rep()

    • c() 这里的c就是 combineconcatenate 的意思,它要求元素之间用英文的逗号分隔,且元素的数据类型是统一的,比如都是数值。c() 函数把一组数据聚合到了一起,就构成了一个向量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    > # 创建向量
    > # 使用 c()
    > low <- c(1, 2, 3)
    > high <- c(4, 5, 6)
    > sequence <- c(low, high)
    > sequence
    [1] 1 2 3 4 5 6

    > # 给变量命名
    > x <- c('a' = 5, 'b' = 6, 'c' = 7, 'd' = 8)
    > x
    a b c d
    5 6 7 8

    > x <- c(5, 6, 7, 8)
    > names(x) <- c('a', 'b', 'c', 'd')
    > x
    a b c d
    5 6 7 8

    如果向量元素很多,用手工一个个去输入,那就成了体力活,不现实。在特定情况下,有几种偷懒方法:

    • seq() 函数可以生成等差数列,from 参数指定数列的起始值,to 参数指定数列的终止值,by 参数指定数值的间距。
    1
    2
    3
    4
    > # 使用 seq()
    > s1 <- seq(from = 0, to = 10, by = 0.5)
    > s1
    [1] 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5 6.0 6.5 7.0 7.5 8.0 8.5 9.0 9.5 10.0
    • rep()repeat(重复)的意思,可以用于产生重复出现的数字序列:x 用于重复的向量,times 参数可以指定要生成的个数,each 参数可以指定每个元素重复的次数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    > s2 <- rep(x = c(0, 1), times = 3)
    > s2
    [1] 0 1 0 1 0 1

    > s3 <- rep(x = c(0, 1), each = 3)
    > s3
    [1] 0 0 0 1 1 1

    > s4 <- rep(x = c(0, 1), time = 3, each = 3)
    > s4
    [1] 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1
  • 向量元素访问

    1
    2
    3
    4
    5
    > # 定义一个向量
    > t <- seq(from = 10, to = 20, by = 0.4)
    > t
    [1] 10.0 10.4 10.8 11.2 11.6 12.0 12.4 12.8 13.2 13.6 14.0 14.4 14.8 15.2 15.6 16.0 16.4 16.8 17.2 17.6 18.0 18.4 18.8
    [24] 19.2 19.6 20.0
    • t[index]:使用这种方式进行访问,index 默认从 1 开始。
    1
    2
    3
    4
    5
    > # 方式 1
    > t[1]
    [1] 10
    > t[2]
    [1] 10.4
    • t[Logic Index]TRUE 表示读取,FALSE 为不读取。
    1
    2
    3
    4
    5
    6
    7
    > # 方式 2
    > t[c(TRUE, TRUE, FALSE, TRUE)]
    [1] 10.0 10.4 11.2 11.6 12.0 12.8 13.2 13.6 14.4 14.8 15.2 16.0 16.4 16.8 17.6 18.0 18.4 19.2 19.6 20.0
    > t[c(1, 0, 2, 0, 3, 0, 1)]
    [1] 10.0 10.4 10.8 10.0
    > t[c(6, 0, 0, 0, 1, 0, 1)]
    [1] 12 10 10
    • t[name]:通过 name 进行访问。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    > # 方式 3
    > names(t) <- c("v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", "v10")
    > t
    v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
    10.0 10.4 10.8 11.2 11.6 12.0 12.4 12.8 13.2 13.6 14.0 14.4 14.8 15.2 15.6 16.0 16.4 16.8 17.2 17.6 18.0 18.4 18.8
    <NA> <NA> <NA>
    19.2 19.6 20.0
    > t["v1"]
    v1
    10
    • t[-index]:索引为负数,则会删除该位置的元素。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    > # 方式 4
    > t
    v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
    10.0 10.4 10.8 11.2 11.6 12.0 12.4 12.8 13.2 13.6 14.0 14.4 14.8 15.2 15.6 16.0 16.4 16.8 17.2 17.6 18.0 18.4 18.8
    <NA> <NA> <NA>
    19.2 19.6 20.0
    > t[-1]
    v2 v3 v4 v5 v6 v7 v8 v9 v10 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
    10.4 10.8 11.2 11.6 12.0 12.4 12.8 13.2 13.6 14.0 14.4 14.8 15.2 15.6 16.0 16.4 16.8 17.2 17.6 18.0 18.4 18.8 19.2
    <NA> <NA>
    19.6 20.0
    > t[c(-2: -4)]
    v1 v5 v6 v7 v8 v9 v10 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
    10.0 11.6 12.0 12.4 12.8 13.2 13.6 14.0 14.4 14.8 15.2 15.6 16.0 16.4 16.8 17.2 17.6 18.0 18.4 18.8 19.2 19.6 20.0
  • 向量元素运算

    • 长度相同的向量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    > # 长度不同的向量
    > v3 = c(10, 20)
    > v4 = c(10, 20, 10, 20, 10, 20)
    > add.result = v1 + v3
    > add.result = v1 + v4
    > # 向量的运算
    > # 长度相同的向量
    > v1 = c(1, 2, 3, 4, 5, 6)
    > v2 = c(6, 7, 8, 9, 10, 11)

    > add.result = v1 + v2
    > sub.result = v1 - v2

    > add.result
    [1] 7 9 11 13 15 17
    > sub.result
    [1] -5 -5 -5 -5 -5 -5
    • 长度不同的向量

      如果长度不同,较短的向量会循环补充到与较长的向量长度相同,在进行运算。但是前提是:长的向量的长度必须为短的向量的整数倍。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    > # 长度不同的向量
    > v3 = c(10, 20)
    > v4 = c(10, 20, 10, 20, 10, 20)
    > add.result = v1 + v3
    > add.result
    [1] 11 22 13 24 15 26
    > add.result = v1 + v4
    > add.result
    [1] 11 22 13 24 15 26
2.8.2 列表 list
  • 概念:列表是 R 语言的对象集合,可以用来保存不同类型的数据,可以是数字、字符串、向量、另一个列表等,当然还可以包含矩阵和函数。R语言创建列表使用 list() 函数。
  • 列表的创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
> # 列表的创建
> list_data = list("Hello", "World", c(1, 10), TRUE, 51.23, 119L)
> list_data
[[1]]
[1] "Hello"

[[2]]
[1] "World"

[[3]]
[1] 1 10

[[4]]
[1] TRUE

[[5]]
[1] 51.23

[[6]]
[1] 119
  • 列表元素的重命名(names 函数)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
> # 列表的重命名
> list_data <- list(c("Hello", "World"), matrix(c(1, 2, 3, 4, 5, 6), nrow=2), list("Happy", 1314L))
> list_data
[[1]]
[1] "Hello" "World"

[[2]]
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6

[[3]]
[[3]][[1]]
[1] "Happy"

[[3]][[2]]
[1] 1314

> names(list_data) <- c("HelloString", "MatrixElem", "ListElem")
> list_data
$HelloString
[1] "Hello" "World"

$MatrixElem
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6

$ListElem
$ListElem[[1]]
[1] "Happy"

$ListElem[[2]]
[1] 1314
  • 列表元素的访问——增删改查

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    > # (查)
    > # 根据索引访问
    > list_data[0]
    named list()
    > list_data[3]
    $ListElem
    $ListElem[[1]]
    [1] "Happy"

    $ListElem[[2]]
    [1] 1314

    > # 根据元素名称访问
    > list_data$HelloString
    [1] "Hello" "World"
    > list_data$ListElem
    [[1]]
    [1] "Happy"

    [[2]]
    [1] 1314

    > list_data[4]<-"Hello"
    > list_data
    $HelloString
    [1] "Hello" "World"

    $MatrixElem
    [,1] [,2] [,3]
    [1,] 1 3 5
    [2,] 2 4 6

    $ListElem
    $ListElem[[1]]
    [1] "Happy"

    $ListElem[[2]]
    [1] 1314

    [[4]]
    [1] "Hello"
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    > # (增)
    > list_data[4]<-"Hello"
    > list_data
    $HelloString
    [1] "Hello" "World"

    $MatrixElem
    [,1] [,2] [,3]
    [1,] 1 3 5
    [2,] 2 4 6

    $ListElem
    $ListElem[[1]]
    [1] "Happy"

    $ListElem[[2]]
    [1] 1314


    [[4]]
    [1] "Hello"
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    > # (改)
    > list_data[4] <- 12
    > list_data
    $HelloString
    [1] "Hello" "World"

    $MatrixElem
    [,1] [,2] [,3]
    [1,] 1 3 5
    [2,] 2 4 6

    $ListElem
    $ListElem[[1]]
    [1] "Happy"

    $ListElem[[2]]
    [1] 1314

    [[4]]
    [1] 12
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    > # (删)
    > list_data[4] <- NULL
    > list_data
    $HelloString
    [1] "Hello" "World"

    $MatrixElem
    [,1] [,2] [,3]
    [1,] 1 3 5
    [2,] 2 4 6

    $ListElem
    $ListElem[[1]]
    [1] "Happy"

    $ListElem[[2]]
    [1] 1314
  • 列表的合并:使用 c 可以将多个列表合并为一个列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
> # 合并列表
> list1 <- list(1, 2, 3)
> list2 <- list("A", "B", "C")
> class(list1)
[1] "list"
> class(list2)
[1] "list"
> merged.list <- c(list1, list2)
> merged.list
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

[[4]]
[1] "A"

[[5]]
[1] "B"

[[6]]
[1] "C"
> class(merged.list)
[1] "list"
  • 列表和向量之间的转换
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
> # 列表转换为向量
> list1 <- list(1 : 5)
> list2 <- list(10: 14)
> list3 <- list("hello", matrix(c(1, 2, 3, 4), nrow=2), 3)
> list1
[[1]]
[1] 1 2 3 4 5

> list2
[[1]]
[1] 10 11 12 13 14

> list3
[[1]]
[1] "hello"

[[2]]
[,1] [,2]
[1,] 1 3
[2,] 2 4

[[3]]
[1] 3

> v1 <- unlist(list1)
> v2 <- unlist(list2)
> v3 <- unlist(list3)
> class(v1)
[1] "integer"
> class(v2)
[1] "integer"
> class(v3)
[1] "character"
> v3
[1] "hello" "1" "2" "3" "4" "3"
2.8.3 矩阵 matrix
  • 概念:R 语言为线性代数的研究提供了矩阵类型,这种数据结构很类似于其它语言中的二维数组。
  • 矩阵的创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
> # 矩阵的创建
> m1 = matrix(data=c(3: 14))
> m1
[,1]
[1,] 3
[2,] 4
[3,] 5
[4,] 6
[5,] 7
[6,] 8
[7,] 9
[8,] 10
[9,] 11
[10,] 12
[11,] 13
[12,] 14
> m2 = matrix(data=c(3: 14), nrow=4)
> m2
[,1] [,2] [,3]
[1,] 3 7 11
[2,] 4 8 12
[3,] 5 9 13
[4,] 6 10 14
> m2 = matrix(data=c(3: 14), ncol=4)
> m2
[,1] [,2] [,3] [,4]
[1,] 3 6 9 12
[2,] 4 7 10 13
[3,] 5 8 11 14
> m3 <- matrix(data=c(3, 14), nrow=4, byrow=TRUE) # 默认为 TRUE
> m3
[,1]
[1,] 3
[2,] 14
[3,] 3
[4,] 14

> rownames <- c("row1", "row2", "row3", "row4")
> colnames <- c("col1", "col2", "col3")
> m4 <- matrix(data=c(3: 14), nrow=4, byrow=TRUE, dimnames=list(rownames, colnames))
> m4
col1 col2 col3
row1 3 4 5
row2 6 7 8
row3 9 10 11
row4 12 13 14
  • 矩阵转置
1
2
3
4
5
6
7
> # 矩阵转置 t()
> # R 语言矩阵提供了 t() 函数, 可以实现矩阵的行列转换
> t(m4)
row1 row2 row3 row4
col1 3 6 9 12
col2 4 7 10 13
col3 5 8 11 14
  • 矩阵元素访问
1
2
3
4
> m4[c("row1", "row4"), c("col1", "col3")]
col1 col3
row1 3 5
row4 12 14
  • 矩阵相关操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
> # 矩阵元素操作
> # 矩阵计算
> m1 <- matrix(data=c(3: 14), nrow=4)
> m1
[,1] [,2] [,3]
[1,] 3 7 11
[2,] 4 8 12
[3,] 5 9 13
[4,] 6 10 14
> m2 <- matrix(data=c(1: 12), nrow=4)
> m2
[,1] [,2] [,3]
[1,] 1 5 9
[2,] 2 6 10
[3,] 3 7 11
[4,] 4 8 12
> m1 + m2
[,1] [,2] [,3]
[1,] 4 12 20
[2,] 6 14 22
[3,] 8 16 24
[4,] 10 18 26

> # 矩阵转换
> # 矩阵 -> 向量
> is.matrix(m1)
[1] TRUE
> class(m1)
[1] "matrix" "array"
> v1 <- as.vector(m1)
> v1
[1] 3 4 5 6 7 8 9 10 11 12 13 14
> class(v1)
[1] "integer"

> # 矩阵组合
> # cbind() 把其自变量横向拼成一个大矩阵, 横向组合, 行数一致
> # rbind() 把其自变量纵向拼成一个大矩阵, 纵向组合, 列数一致
> v1 = c(1, 2, 3, 4, 5)
> v2 = c(6, 7, 8, 9,10)
> y1 = cbind(v1, v2)
> y2 = rbind(v1, v2)
> y1
v1 v2
[1,] 1 6
[2,] 2 7
[3,] 3 8
[4,] 4 9
[5,] 5 10
> y2
[,1] [,2] [,3] [,4] [,5]
v1 1 2 3 4 5
v2 6 7 8 9 10
>
2.8.4 数组 array
  • 概念:数组也是 R 语言的对象,R 语言可以创建一维或多维数组。
  • 数组的创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
> # 创建数组
> # 一维数组
> v1 <- c(5, 9, 1)
> a1 <- array(v1)
> a1
[1] 5 9 1
> is.array(a1)
[1] TRUE
> a <- array(10: 15, dim=9)
> a
[1] 10 11 12 13 14 15 10 11 12
> a <- array(10: 15, dim=c(2))
> a
[1] 10 11
>
> # 二维数组
> a <- array(10: 15, dim=c(2, 3)) # 10, 11, 12, 13, 14, 15
> a
[,1] [,2] [,3]
[1,] 10 12 14
[2,] 11 13 15
> is.array(a)
[1] TRUE
>
> # 三维数组
> a <- array(10: 15, dim=c(2, 3, 2))
> a
, , 1
[,1] [,2] [,3]
[1,] 10 12 14
[2,] 11 13 15

, , 2
[,1] [,2] [,3]
[1,] 10 12 14
[2,] 11 13 15

> column.names <- c("COL1", "COL2", "COL3")
> row.names <- c("ROW1", "ROW2", "ROW3")
> matrix.names <- c("Matrix1", "Matrix2")
>
> vector1 <- c(5, 9, 3)
> vector2 <- c(10, 11, 12, 13, 14, 15)
> a <- array(c(vector1, vector2), dim=c(3, 3, 2), dimnames = list(row.names, column.names, matrix.names))
> a
, , Matrix1
COL1 COL2 COL3
ROW1 5 10 13
ROW2 9 11 14
ROW3 3 12 15

, , Matrix2
COL1 COL2 COL3
ROW1 5 10 13
ROW2 9 11 14
ROW3 3 12 15
  • 数组元素访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
> # 数组访问
> a[1] # 一维数组的访问
[1] 5
> a[1: 6] # 一维数组的访问——向量访问
[1] 5 9 3 10 11 12
>
> dim2_a <- array(c(vector1, vector2), dim=c(3, 3))
> dim2_a
[,1] [,2] [,3]
[1,] 5 10 13
[2,] 9 11 14
[3,] 3 12 15
> dim2_a[1: 1] # 二维数组的访问, 访问的必须是二维数组
[1] 5
> dim2_a[1: 2, 1: 2] # 二维数组的访问——矩阵访问
[,1] [,2]
[1,] 5 10
[2,] 9 11
>
> a[3, 3, 1] # 三维数组
[1] 15
2.8.5 数据框 data.frame
  • 概念:数据框 DataFrame 可以理解成我们常说的表格,数据框是 R 语言的数据结构,是特殊的二维列表。数据框每一列都有一个唯一的列名,长度都是相等的,同一列的数据类型需要一致,不同列的数据类型可以不一样。数据框的特点:

    • 列名称应为非空。
    • 行名称应该是唯一的。
    • 存储在数据框中的数据可以是数字,字符型等。
    • 每个列应包含相同数量的数据项。
  • 数据框的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
> # 数据框
> # 创建数据框对象
> df1 <- data.frame(
+ # tag = value
+ 姓名 = c("张三", "李四", "王五"),
+ 工号 = c("001", "002", "003"),
+ 月薪 = c(1000, 2000, 3000)
+ )
> df1
姓名 工号 月薪
1 张三 001 1000
2 李四 002 2000
3 王五 003 3000
  • 查看数据框结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> # 查看数据框结构(Structure)
> str(df1)
'data.frame': 3 obs. of 3 variables:
$ 姓名: chr "张三" "李四" "王五"
$ 工号: chr "001" "002" "003"
$ 月薪: num 1000 2000 3000
> # 查看数据框描述性统计量
> summary(df1)
姓名 工号 月薪
Length:3 Length:3 Min. :1000
Class :character Class :character 1st Qu.:1500
Mode :character Mode :character Median :2000
Mean :2000
3rd Qu.:2500
Max. :3000
  • 数据框访问、新增列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
> # 提取数据框中的列
> df1$姓名
[1] "张三" "李四" "王五"
> df2 <- data.frame(df1$姓名, df1$月薪)
> df2
df1.姓名 df1.月薪
1 张三 1000
2 李四 2000
3 王五 3000
>
> # 提取数据框中的行
> # 提取前两行
> result <- df1[1:2, ]
> result
姓名 工号 月薪
1 张三 001 1000
2 李四 002 2000
>
> # 提取前两行, 前两列
> result <- df1[1:2, 1:3]
> result
姓名 工号 月薪
1 张三 001 1000
2 李四 002 2000
>
> # 提取第1、3行, 第1、2列的数据
> result <- df1[c(1, 3), c(1, 2)]
> result
姓名 工号
1 张三 001
3 王五 003
>
> # 新增列
> df1$部门 <- c("运营", "技术", "运营")
> df1
姓名 工号 月薪 部门
1 张三 001 1000 运营
2 李四 002 2000 技术
3 王五 003 3000 运营
  • 数据框合并
1
2
# 数据框合并
# cbind()/rbind()
  • 数据框筛选
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
> # 数据框筛选
> student <- data.frame(
+ sno = c(1, 2, 3),
+ sname = c("zhangsan", "lisi", "wangwu"),
+ sage = c(10, 12, 14)
+ )
> student
sno sname sage
1 1 zhangsan 10
2 2 lisi 12
3 3 wangwu 14
>
> student[1] # 第一个字段
sno
1 1
2 2
3 3
> student[1, 2] # 第一行, 第二列
[1] "zhangsan"
> student$sage>12 # FALSE FALSE TRUE
[1] FALSE FALSE TRUE
>
> # [...] 里面一个参数取字段, 两个参数取行列
> student[student$sage>11] # 执行判断, 得到结果 c(FALSE, TRUE, TRUE)
sname sage
1 zhangsan 10
2 lisi 12
3 wangwu 14
> student[c(FALSE, TRUE, TRUE)] # 使用 c(FALSE, TRUE, TRUE) 进行运算
sname sage
1 zhangsan 10
2 lisi 12
3 wangwu 14
> student[student$sage>11, ] # 选择年龄大于 11 的, 并取所有字段
sno sname sage
2 2 lisi 12
3 3 wangwu 14
> student[c(FALSE, TRUE, TRUE), ] # 选择年龄大于 11 的行, 并取所有字段
sno sname sage
2 2 lisi 12
3 3 wangwu 14
> student[student$sage>11, 2] # 选择年龄大于 11 的, 并取第 2 个字段
[1] "lisi" "wangwu"
> student[student$sage>11, c(1, 2)] # 选择年龄大于 11 的, 并取第 1、2 个字段
sno sname
2 2 lisi
3 3 wangwu
>
> subset(student, select = c("sname", "sage")) # 可以理解为: SELECT sname, sage FROM student;
sname sage
1 zhangsan 10
2 lisi 12
3 wangwu 14
> subset(student, select = c("sname", "sage"), sage>11 & sno == 2) # 可以理解问: SELECT sname, sage FROM student WHERE sage>11 AND sno=2;
sname sage
2 lisi 12
2.8.6 因子 factor
  • 概念:因子用于存储不同类别的数据类型,例如:人的性别有 两个类别,年龄来分可以有 未成年人成年人
  • 因子创建
1
2
3
4
5
6
7
8
9
10
11
12
> # 因子创建
> # 例 1:男 2:女
> x <- c(1, 2, 1, 2, 2, 1, 3)
> factor(x)
[1] 1 2 1 2 2 1 3
Levels: 1 2 3
> factor(x, levels = c(1, 2))
[1] 1 2 1 2 2 1 <NA>
Levels: 1 2
> factor(x, levels = c(1, 2), labels = c("男", "女"))
[1] 男 女 男 女 女 男 <NA>
Levels: 男 女

2.9 R 语言判断、控制、循环

2.9.1 判断语句
  • if 语句
1
2
3
4
5
6
7
8
9
10
11
> # 控制语句
> x <- TRUE
> if (x) {
+ print("x 为真")
+ }
[1] "x 为真"
>
> if (is.logical(x)) {
+ print("x 是逻辑型数据")
+ }
[1] "x 是逻辑型数据"
  • if...else... 语句 和 if...else if... 语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
> x <- 10L
> if (is.logical(x)) {
+ print("x 是逻辑型数据")
+ } else if(is.numeric(x)) {
+ print("x 是数值型数据")
+ } else {
+ print("x 不是逻辑型数据, 也不是数值型数据")
+ }
[1] "x 是数值型数据"
>
> y <- c("a", "b", "c", "e")
> if("e" %in% y) {
+ print("y 中包含 `e`")
+ } else if ("a" %in% y) {
+ print("y 中包含 `a`")
+ } else {
+ print("不存在")
+ }
[1] "y 中包含 `e`"
  • switch 语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
> # switch语句
> # x <- switch (object,
> # case = action
> # )
>
> # expression(object) 是一个常量表达式, 可以是整数或字符串
> # 如果是整数, 则返回对应 case 的位置值, 如果整数不在范围内则返回 NULL
> x <- switch (1,
+ "a",
+ "b",
+ "c",
+ "d"
+ )
> x
[1] "a"
>
> x <- switch (1,
+ "a" = "a1",
+ "b" = "b1",
+ "c" = "c1",
+ "d" = "d1"
+ )
> x
[1] "a1"
>
> # expression 如果是字符串, 则对应的是 case 中变量名对应的值, 没有匹配则没有返回值
> x <- switch ("c",
+ "a" = 1,
+ "b" = 2,
+ "c" = "cn"
+ )
>
> x
[1] "cn"
2.9.2 循环语句
  • repeat + if(...) { break }
1
2
3
4
5
6
7
8
9
10
11
12
13
> # 循环语句
> # repeat + if(...) { break }
> v <- c("Google", "Micrsoft", "OpenAI", "LinkIn")
> cnt <- 3
> repeat {
+ print(v[cnt])
+ cnt <- cnt + 1
+ if(cnt > 6) { break; }
+ }
[1] "OpenAI"
[1] "LinkIn"
[1] NA
[1] NA
  • while()
1
2
3
4
5
6
7
8
9
10
11
> 
> # while 循环
> v <- c("Google", "Micrsoft", "OpenAI", "LinkIn")
> cnt <- 3
> while(cnt <= 5) {
+ print(v[cnt])
+ cnt <- cnt + 1
+ }
[1] "OpenAI"
[1] "LinkIn"
[1] NA
  • for()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
> # for 循环
> letters
[1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
> LETTERS
[1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"
> LETTERS[1: 6]
[1] "A" "B" "C" "D" "E" "F"
> for (i in LETTERS[1: 6]) {
+ print(i)
+ }
[1] "A"
[1] "B"
[1] "C"
[1] "D"
[1] "E"
[1] "F"
2.9.3 控制语句
  • break
1
2
3
4
5
6
7
8
9
10
> # break语句
> for (i in LETTERS[1: 6]) {
+ if (i == "D") {
+ break;
+ }
+ print(i)
+ }
[1] "A"
[1] "B"
[1] "C"
  • next
1
2
3
4
5
6
7
8
9
10
11
12
> # next语句
> for (i in LETTERS[1: 6]) {
+ if (i == "D") {
+ next;
+ }
+ print(i)
+ }
[1] "A"
[1] "B"
[1] "C"
[1] "E"
[1] "F"

2.10 R 语言函数

概念:函数是一组一起执行一个任务的语句。

2.10.1 内置函数:
  • print()
  • cat()
  • list()
  • array()
2.10.2 自定义函数

R 语言函数通常由以下几个部分组成:

  • 函数名
  • 参数
  • 函数体
  • 返回值

具体格式如下:

1
2
3
4
5
function_name <- function(arg_1, arg_2, ...) {
# 函数体
# 执行的代码块
return(output) # 返回值
}

下面由一些例子来演示:

只演示几种情况,其他情况(如有参函数、有默认参数值的函数都和其他语言类似)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> # R 语言函数
> new.function <- function() {
+ # 函数体
+ for(i in 1: 6){
+ print(i^2)
+ }
+ }
>
> new.function()
[1] 1
[1] 4
[1] 9
[1] 16
[1] 25
[1] 36

2.11 R语言读写数据文件、数据库

2.11.1 读写数据文件
读写类型 读写方法 备注
从键盘输入数据 edit() 使用方法见下方代码
读写 .txt 格式文件 read.table
write.table
-
读写 .csv 格式文件 read.csv()
write.csv()
-
读写 .xlsx 格式文件 read.xlsx()
write.xlsx()
使用之前需要下载并加载 R语言的 xlsx
  1. 从键盘中输入数据
1
2
3
4
5
6
7
8
9
10
11
> # 从键盘读入数据, edit() 和 fix() 可以可视化的输入数据
> mydata <- data.frame(age=numeric(0), gender=character(0), weight=numeric(0))
> mydata
[1] age gender weight
<0> (0-长度的row.names)
> edit(mydata)
age gender weight
1 12 2 120
2 13 2 NA
>
> fix(mydata)
  1. 读写 .txt 格式文件
1
2
3
4
5
stutxt <- read.table("/path/to/file.txt", header=TRUE, sep=",")
stutxt
class(stutxt)

write.table(stutxt, "/path/to/write.txt", sep=",")
  1. 读写 .csv 格式文件
1
2
3
4
csvfile <- read.csv("/path/to/file.csv")
csvfile

write.csv(csvfile, "/path/to/write.csv")
  1. 读写 .xlsx 格式文件
1
2
3
4
5
6
7
8
9
10
11
12
install.packages("xlsx")
library()
search()
library("x1sx")

student <- read.xlsx("/path/to/file.xlsx", sheetIndex=1, encoding = "UTF-8")
student

city<-read.xlsx("/path/to/file.xlsx", sheetName = "city",encoding = "UTF-8")
city

write.xlsx(city, "/path/to/write.xlsx", sheetName = "city")
2.11.2 读写数据库

非重点,有需要可自行了解

2.12 R 语言基础绘图

2.12.1 R 语言绘制barplot() 条形图、堆叠条形图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
####### 1. 绘制条形图 barplot()
# 1.1 height: 高度, 通过这个参数可以指定要画多少个柱子, 以及每个柱子的高度, 其值有两种——向量、矩阵
# 1.1.1 向量 vector, 此时会根据向量的长度确定图中有多少个柱子, 向量中的每个值就是高度
barplot(height = 1: 5)

# 1.1.2 矩阵 matrix, 此时用于画堆积柱状图
data <- cbind(a = 1: 4, b = 1: 4)
data
class(data)
barplot(height = data)

# 1.2 width: 宽度, 控制每个柱子的宽度, 默认值为 1, 值得注意的是, 这个参数的值是可以循环使用的
barplot(height = 1: 5)
barplot(height = 1: 5, width=1)
barplot(height = 1: 5, width=c(1, 1, 1, 1, 1))
barplot(height = 1: 5, width=1: 5)

data <- cbind(a = 1: 4, b = 1:4)
barplot(height = data)
barplot(height = data, width=1)
barplot(height = data, width=1:2)

# 1.3 space: 间隔, 指定每个柱子左边的空白区域的宽度, 这个值为一个百分比, 默认值为 0.2
barplot(height=1: 5, space=1)
barplot(height=1: 5, space=5: 1)
barplot(height=data, space=1)
barplot(height=data, space=1: 2)

# 1.4 names.arg: 每个柱子下面的标记, 当 height 为 vector 时, 默认的标记为向量的 names 属性
barplot(height=1:3, names.arg=c("a","b","c"))
v <- 1:3
names(v) <- c("c" ,"b" ,"a")
v
barplot(height=v)

# 1.5 horiz: 逻辑值, 默认为 FALSE, 当值为 TRUE 时, 将 X 轴和 Y 轴转置
barplot(height=1:3, horiz = FALSE)
barplot(height=1:3, horiz = TRUE)
data

barplot(height=data, horiz= FALSE)
barplot(height=data, horiz = TRUE)

# 1.6 col: 柱子的填充色, 默认为灰色
# 1.7 border: 柱子边框的颜色, 默认为 black, 当 border为 NA时, 表示没有边框
# 1.8 main: 设置图形标题
c <- colors()
c
c[2: 5]

barplot(1: 4, col=c[2:5], border = NA, main="图形标题")

barplot(1: 4, col=rainbow(4))
barplot(1: 4, col=c("red", "green", "antiquewhite", "white"))
barplot(data, col=rainbow(4))

# 1.9 & 1.10 density 设置柱子用线条填充, density 控制线条的密度,angle 控制线条的角度
par(mfrow=c(1, 3))
par

barplot(rep(1, 4), density = 1, angle = 30, main = "density = 1")
barplot(rep(1, 4), density = 2, angle = 90, main = "density = 2")
barplot(rep(1, 4), density = 3, angle = 180, main = "density = 3")

# 1.11 axes: 逻辑值, 控制是否显示轴线(如 y 轴线)
barplot(data, axes = F)
barplot(data, axes = T)

# 1.12 axisnames: 控制是否显示柱子的标签
barplot(data, axisnames = F)
barplot(data, axisnames = T)


# 1.13 beside: 当输入参数为矩阵时, 默认是堆积柱状图, beside 默认值为 FALSE;
# beside = FALSE 时, 条形图的高度是矩阵的数值, 矩形条是水平堆叠的
# beside = TRUE 时, 条形图的高度是矩阵的数值, 矩形条是并列的
data
barplot(data, beside = F)
barplot(data, beside = T)

# 1.14 legend.text: 图例的文字说明
# 只有当 height 参数的值是 matrix 时, 才起作用
# 默认显示的是 matrix 的 rownames 属性
# 其值有两种指定形式:
# 第一种: 逻辑值, 是否需要显示图例, 默认为FALSE
par(mfrow=c(1, 1))
data
rownames(data) <- c("A1", "A2", "A3", "A4")
data
barplot(data, legend.text = TRUE)

# 第二种, 指定图例中的文字内容
# 相当于修改了 matrix 的 rownames 属性
barplot(data, legend.text = c("D", "E", "F", "G"))
2.12.2 R 语言绘制 饼图、3D饼图绘制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
####### 2. 绘制饼图
# 2.1 x: 数值向量, 表示每个扇形的面积
x <- c(21, 62, 10, 53)
pie(x)

# 2.2 labels: 字符型向量, 表示各扇形面积标签
x<-c(21, 62, 10, 53)
names(x) <- c("London", "New York", "singapore", "Mumbai")
x
pie(x)

x<-c(21, 62, 10, 53)
pie(x)
pie(x, labels = c("London", "New York", "singapore", "Mumbai"))

# 2.3 radius: 饼图的半径
x<-c(21, 62, 10, 53)
pie(x, labels = c("London", "New York", "singapore", "Mumbai"), radius = 1)

# 2.4 main: 饼图的标题
pie(x, labels = c("London", "New York", "singapore", "Mumbai"), radius =1, main="饼图")

# 2.5 clockwise: 一个逻辑值, 用来指定饼图各个切片是否按顺时针做出分割
pie(x, labels = c("London", "New York", "singapore", "Mumbai"), radius =1, main="饼图", clockwise = TRUE)

# 2.6 & 2.7
# angle: 设置底纹的斜率
# density: 底纹的密度, 默认值为NULL
pie(x, labels = c("London", "New York", "singapore", "Mumbai"), radius =1, main="饼图", clockwise = TRUE, density = 10, angle = 20)

# 2.8 col: 每个扇面的颜色, 相当于调色板
pie(x, labels = c("London", "New York", "singapore", "Mumbai"),
radius =1, main="饼图", clockwise = TRUE,
density = 10, angle = 20, col = rainbow(length(x)))

pie(x, labels = c("London", "New York", "singapore", "Mumbai"),
radius =1, main="饼图", clockwise = TRUE,
col = rainbow(length(x)))

# 2.9 设置百分数
piepercent <- round(x/sum(x) * 100, 1)
pie(x, piepercent, radius=0.5, main="饼图", clockwise= TRUE, col=rainbow(length(x)))

# 2.10 设置图例
legend("topright", c("London", "New York", "Singapore", "Mumbai"),
fill = rainbow(length(x)), cex = 0.8)

legend("topright", c("London", "New York", "Singapore", "Mumbai"),
fill = rainbow(length(x)), horiz = TRUE, cex = 0.5)

# 2.11 绘制 3D 饼图
install.packages("plotrix")
library("plotrix")
lbl <- c("London", "New York", "Singapore", "Mumbai")
pie3D(x, labels = lbl, explode = 0.1, main = "城市 3D 饼图")
2.12.3 R 语言散点图、折线图、散点矩阵图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
####### 3. 绘制散点图、折线图、散点矩阵图
# 3.1 绘制散点图 plot()
# x: 横坐标, x轴的数据集合
# y: 纵坐标, y轴的数据集合
# type: 绘图的类型, type="p" 为点(默认);
# type="l" 在图形中数据显示为线;
# type="b" 在图形中数据显示为点和连接线;
# type="o" 在图形中数据点覆盖在线上;
# type="h" 在图形中数据显示为从点到x轴的垂直线;
# type="s" 在图形中数据显示为阶梯图;
# type="n" 在图形中数据不显示
# main: 图表标题
# xlab/ylab: x轴/y轴的标签名称
# xlim/ylim: x轴/y轴的范围
# axes: 布尔值, 是否绘制两个坐标轴
# pch: 点的样式由 pch 的取值决定
# cex: 表示相对于默认大小缩放倍数的数值。默认大小为1, 1.5表示放大为默认值的1.5倍; 0.5表示做小为默认大小的0.5倍;

cars # 加载车辆时速-刹车距离数据集
cars$speed # 查看车辆时速
cars$dist # 查看车辆刹车距离

plot(cars$speed, cars$dist)
plot(cars[, 1], cars[, 2])
plot(cars)

plot(cars, main="车辆时速-刹车距离散点图", xlab="车速", ylab="刹车距离",
col="red", pch=4, cex=1)

with(classData, plot(height, weight))

# 3.2 绘制折线图 plot()
# type: 绘图的类型, type="p" 为点(默认);
# type="l" 在图形中数据显示为线;
# type="b" 在图形中数据显示为点和连接线;
# type="o" 在图形中数据点覆盖在线上;
# type="h" 在图形中数据显示为从点到 x 轴的垂直线(直线条形图);
# type="s" 在图形中数据显示为阶梯图;
# type="c" 在图形中数据虚线图;
# type="n" 在图形中数据不显示
plot(cars$speed, cars$dist, type="l")
plot(cars$speed, cars$dist, type="b")
plot(cars$speed, cars$dist, type="o")

# plot 可以配合lines() 函数绘制出多条折线图
# lines() 函数必须与 plot() 函数配合才能使用, 先用 plot() 函数画出一个图形
# round(x, digits = 0) 舍入函数, x 为数值向量, digits表示有效位数, 可以为负数, 负数表示舍入十位(-1)、百位(-2)...等
x <- 1: 10
beijing <- round(rnorm(10, mean = 20, sd = 2), 2)
beijing
shanghai <- round(rnorm(10, mean = 23, sd = 3), 2)
shanghai
guangzhou <- round(rnorm(10, mean = 18, sd = 1), 2)
guangzhou

plot(x, beijing, type = "l", ylim = c(16, 30), lwd=2, main = "北上广近十天气温变化趋势图")
lines(x, shanghai, type = "b", col="blue", lwd=2)
lines(x, guangzhou, type = "l", col="red", lwd=2)

# 用 plot() 会单独绘制一张新图
plot(x, shanghai, type = "l", ylim = c(16, 30), lwd=2, main = "北上广近十天气温变化趋势图")
plot(x, guangzhou, type = "l", ylim = c(16, 30), lwd=2, main = "北上广近十天气温变化趋势图")

# 3.3 绘制散点矩阵图
mtcars # 加载 mtcars 数据集, 数据来自1974年美国汽车趋势杂志, 包括32辆汽车(1973-74款) 的油耗和10个方面的汽车设计和性能
# mpg(Miles Per Gallon): 油耗(每加仑英里(美国)), 功能更强大, 更重的汽车往往消耗更多的燃油
# cyl(cylinders): 汽缸数, 功率更大的汽车通常具有更多的汽缸
# disp(displacement, in cubic inches): 排量(立方英寸), 发动机气缸的总容积
# hp(horsepower): 总马力:这是汽车产生的功率的量度
# drat:driveshaft ratio:后轴比率:这描述了驱动轴的转动与车轮的转动如何对应。较高的值会降低燃油效率。
# wt(weight): 重量(1000磅)
# qsec(1/4 mile time; a measure of acceleration): 1/4英里时间:汽车的速度和加速度
# vs(‘V’ or straight - engine shape): 发动机缸体, 表示车辆的发动机形状是"V" 形还是更常见的直形。
# am(transmission; auto or manual): 变速箱, 这表示汽车的变速箱是自动(0)还是手动(1)。
# gear(abbreviation of gears): 前进挡的数量, 跑车往往具有更多的挡位
# carb(abbreviation of carburetors): 化油器数量, 与更强大的发动机相关

pairs(mtcars[, c("wt", "mpg", "disp", "cyl")])
pairs(vs~wt + mpg + disp + cyl, data=mtcars)
2.12.4 R 语言绘制直方图
1

2.12.5 R 语言绘制核密度图
1

2.12.6 R 语言绘制箱线图
1

2.12.7 R 语言小提琴图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
####### 7. 小提琴图 
# vioplot 包中的 vioplot 函数用于绘制小提琴图, 小提琴图是核密度图与箱线图的结合
# install.packages("vioplot")
# install.packages("vioplot")
library("vioplot")

# `==` 意为比较等号两端是否相等
mpg4 <- mtcars$mpg[mtcars$cyl == 4]
mpg4
mpg6 <- mtcars$mpg[mtcars$cyl == 6]
mpg6
mpg8 <- mtcars$mpg[mtcars$cyl == 8]
mpg8

vioplot(mpg4, mpg6, mpg8, names=c("4-cyl", "6-cyl", "8-cyl"), col="gold")
title("四/六/八缸 车辆的油耗 (英里/加仑)", ylab="Miles Per Gallon", xlab="Num Of Cylinders")

# 图片的保存
pdf(file="fig-vioplot.pdf", height = 10, width = 10, family = "GB1")

vioplot(mpg~cyl, data=mtcars, xlab="气缸数", ylab="加仑每英里", main="里程数据", names=c("4-cyl", "6-cyl", "8-cyl"))
dev.off # 关闭图像设备, 我们经常使用图像设备来创建和保存图形输出
# 当我们完成图形绘制并且不再需要将图形输出到设备上时, 我们需要关闭图像设备以释放系统资源并确保图形正确保存
# 在 R 中, 我们可以使用 dev.off() 函数来关闭当前活动的图像设备

2.13 R 绘图常用拓展库 ggplot2 绘图

简介:ggplot2是一款强大的图形可视化R包,其作图方式易于理解,且生成的图形精美,定制化程度也很高,是 R 语言中很流行的可视化工具之一。

ggplot2 包是 R 的一个作图用的扩展包,它实现了 图形的语法,将一个作图任务分成若干子任务,只需要完成各个子任务就可以完成作图。在绘制常用图形时,只需要两个步骤:

  1. 将图形所展现的数据输入到 ggplot() 函数中

  2. 调用某个 geom_xxx() 函数,指定图形类型,如散点图、面积图、曲线图、盒型图等。geom_xxx() 提供了各种基本图形,如geom_blank() 不绘制图形、geom_point() 每个观测为一个散点、geom_hline(), geom_vline(), geom_abline() 绘制线等。

ggplot2 的作图的一般步骤为:

  1. 准备数据:一般为数据框,且一般为长表,即每个观测时间占一行,每个观测变量占一列
  2. 输入数据:将数据输入到 ggplot() 函数中,并指定参与作图的每个变量分别映射到哪些图形特性
  3. 选择图形类型:选择一个合适的图形类型,函数名以 geom_ 开头,如 geom_point() 表示散点图。
  4. 设定标题和图例位置等,如 labs(),仍用加号连接。

模板如下:

1
2
3
4
5
6
7
8
p <- ggplot(data = <输入数据框>, 
mapping = aes(<维度>=<变量名>,
<维度>=<变量名>,
<...>=<...>))
p + geom_<图形类型>(<...>) +
scala_<映射>_<类型>(<...>) +
coord_<类型>(<...>) +
labs(<...>)
2.13.1 ggplot2 绘制散点图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
####### ggplot2 绘图
# install.packages("ggplot2") # 安装 ggplot2 绘图库
# install.packages("gapminder") # 安装 gapminder 数据集

library("ggplot2") # 绘图包
library("gapminder") # 数据集包

gapminder

dev.new() # 打开绘图设备
p <- ggplot(data = gapminder,
mapping = aes(
x = gdpPercap,
y = lifeExp))

p <- ggplot(data = gapminder,
aes(gdpPercap, lifeExp))

p + geom_point() # 散点图
p + geom_smooth() # 拟合曲线图
p + geom_point() + geom_smooth()
dev.off() # 关闭绘图设备
2.13.2 ggplot2 绘制面积图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 面积图
# 定义数据框 data.frame
df <- data.frame(
year=c(1700, 1701, 1702, 1703, 1704,
1705, 1706, 1707, 1708, 1709,
1710, 1711, 1712, 1713, 1714,
1715, 1716),
sunspots=c( 5.0, 11.0, 16.0, 23.0, 36.0,
58.0, 29.0, 20.0, 10.0, 8.0,
3.0, 0.0, 0.0, 2.0, 11.0,
27.0, 47.0)
)

df

p <- ggplot(data = df, mapping = aes(year, sunspots))
p + geom_area(fill = "blue", alpha = 0.2, col = "red")
2.13.3 ggplot2 绘制堆叠面积图
1

🎫 3. ECharts - ECharts的简单使用

3.1 ECharts 简介

  • ECharts 概念:一个基于 JavaScript 的开源可视化图表库(别人写好了 .js,我直接使用),可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE 9/10/11ChromeFirefoxSafari 等),底层依赖矢量图形库 *ZRender*,提供直观,交互丰富,可高度个性化定制的数据可视化图表。
  • 官网:ECharts
  • 特性:ECharts Feature
  • 获取 Apache ECharts
    • 通过 GitHub 获取

      • Apache/ECharts 项目的 release 页面可以找到各个版本的链接。点击下载页面下方 Assets 中的 Source code,解压后即为包含完整 ECharts 功能的文件。
      • echarts-5.4.0/echarts-5.4.0/dist/echarts.common.js echarts-5.4.0/echarts-5.4.0/dist/echarts.common.min.js:体积适中,常用版,支持常见的图表和组件
      • echarts-5.4.0/echarts-5.4.0/dist/echarts.jsecharts-5.4.0/echarts-5.4.0/dist/echarts.min.js:体积最大,完整版,包含所有支持的图表和组件。
      • echarts-5.4.0/echarts-5.4.0/dist/echarts.simple.jsecharts-5.4.0/echarts-5.4.0/dist/echarts.simple.min.js:体积较小,精简版,包含最常用的图表和组件。
    • npm 获取

      • npm install echarts
    • CDN 获取

      • <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
    • 在线定制

3.2 第一个 ECharts 程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>第一个 ECharts 程序</title>
<!-- 1. 引入 ECharts.js 类库文件 -->
<!-- <script type="text/javascript" src="js/echarts.js"></script> -->
<script src="http://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>

<script>
window.onload=function() {
// 3. 实例化 ECharts 对象
var dom = document.getElementById("main");
var myChart = echarts.init(dom);

// 4. 指定图表的配置项和数据
var option = {
title: {
text: "ECharts 入门案例"
},
tooltip: { },
legend: {
data: ['销量']
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '帽子', '高跟鞋', '袜子']
},
yAxis: { },
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 26, 10, 20, 8]
}]
};

// 5. 使用刚指定的配置项和数据显示图表
myChart.setOption(option);
}
</script>
</head>

<body>
<!-- 2. 准备一个放图表的容器 -->
<div id="main" style="width: 900px; height: 400px"></div>
</body>
</html>

3.3 ECharts 绘图流程

  1. 新建一个 HTML 文件,并在网页头部 <head> ... </head> 部分引入在线的 ECharts 类库或者本地已经下载好的类库。
1
2
3
4
5
6
<head>
<meta charset="utf-8" />
<!-- 引入在线的 ECharts 类库或者本地已经下载好的类库 -->
<!-- <script type="text/javascript" src="js/echarts.js"></script> -->
<script src="http://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
</head>
  1. 在网页创建一个容器,为 ECharts 准备一个具备大小(宽高)的 DOM (Document Object Model, 文件对象模型 )
1
2
3
4
<body>
<!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
<div id="main" style="width: 600px; height: 400px"></div>
</body>
  1. 当页面加载时,获取 DOM,并初始化 ECharts 对象
1
2
3
4
5
6
<script>
window.onload=function() {
var dom = document.getElementById("main");
var myChart = echarts.init(dom);
}
</script>
  1. 指定配置项信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
window.onload=function() {
// 省略上一步的操作
var options = {
title: {
text: "ECharts 入门案例"
},
tooltip: { },
legend: {
data: ['销量']
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: { },
series: [
{name:'销量', type: 'bar', data: [5, 20, 36, 10, 10, 20]}
]
}
}
</script>
  1. 用刚指定的配置项和数据显示图表
1
2
3
4
5
6
<script>
window.onload=function() {
// 省略上一步的操作...
myChart.setOption(option);
}
</script>

3.4 ECharts 常用配置项详解

完成简单柱状图的绘制需要包含的组件包括:

  • xAxis:X 轴组件
  • yAxis:Y 轴组件
  • series:系列组件(我更喜欢称其序列组件)

完善图形补充组件:

  • title:标题组件
  • legend:图例组件
  • tooltip:提示框组件
  • toolbox:工具栏配置项
  • datazoom:数据区域缩放

3.5 ECharts 样式设置

3.5.1 ECharts 颜色主题(Theme)
  • 内置颜色主题(darklight
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>样式设置</title>
<!-- <script type="text/javascript" src="js/echarts.min.js"></script> -->
<script type="text/javascript" src="http://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<div id="main" class="main" style="height=900px; width:400px"></div>
<script>
window.onload=function() {
var dom = document.getElementById("main");
// 内置颜色主题
// var myCharts = echarts.init(dom, "light");
var myCharts = echarts.init(dom, "dark");
var options = {
title: {
text: "ECharts 入门案例"
},
tooltip: { },
legend: {
data: ['销量']
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: { },
series: [
{name:'销量', type: 'bar', data: [5, 20, 36, 10, 10, 20]}
]
}
myCharts.setOption(options);
}
</script>
</body>
</html>
  • 自定义颜色主题

    • 通过 JavaScript 定义颜色主题
    1
      
    • 通过 Json + jQuery 定义颜色主题
    1
      
3.5.2 ECharts 调色盘
  • 全局调色盘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>ECharts 全局调色盘</title>
<!-- <script type="text/javascript" src="js/echarts.min.js"></script> -->
<script type="text/javascript" src="http://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<div id="main" class="main" style="width: 900px; height: 400px"></div>
<script>
window.onload=function() {
var dom = document.getElementById('main'); // 获取到 DOM
var myCharts = echarts.init(dom); // 初始化 ECharts对象
// 定义选项
var options = {
// 颜色组件——全局调色盘
color: ["red", "blue", "yellow"],
xAxis: {
show: true,
position: "bottom",
type: 'category',
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {
type: 'value',
max: 50
},
// 系列组件
series: [
{ type: "bar", name: "销量1", data: [5, 20, 36, 10, 10, 20] },
{ type: "line", name: "销量2", data: [4, 30, 26, 14, 8, 15] }
]
}
myCharts.setOption(options);
}
</script>
</body>
</html>
  • 系列调色盘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>ECharts 系列调色盘</title>
<!-- <script type="text/javascript" src="js/echarts.min.js"></script> -->
<script type="text/javascript" src="http://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<div id="main" class="main" style="width: 900px; height: 400px"></div>
<script>
window.onload=function() {
var dom = document.getElementById('main'); // 获取到 DOM
var myCharts = echarts.init(dom); // 初始化 ECharts对象
// 定义选项
var options = {
// 颜色组件——全局调色盘
color: ["red", "blue", "yellow"],
xAxis: {
show: true,
position: "bottom",
type: 'category',
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {
type: 'value',
max: 50
},
// 系列组件
series: [
{ type: "bar", name: "销量1", data: [5, 20, 36, 10, 10, 20], color: ["red", "green"] },
{ type: "line", name: "销量2", data: [4, 30, 26, 14, 8, 15] }
]
}
myCharts.setOption(options);
}
</script>
</body>
</html>
  • 系列调色盘与全局调色盘同时出现:
    • 当系列调色盘与全局调色盘同时出现时,图表使用系列调色盘中配置的颜色。
3.5.3 ECharts 直接样式设置

直接样式设置(itemStylelineStyleareaStylelabel、…)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>ECharts 全局调色盘</title>
<!-- <script type="text/javascript" src="js/echarts.min.js"></script> -->
<script type="text/javascript" src="http://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<div id="main" class="main" style="width:900px; height: 400px;"></div>
<script>
window.onload=function() {
var dom = document.getElementById("main");
var myChart = echarts.init(dom);
var options = {
xAxis: {
show: true,
position: 'bottom',
type: 'category',
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {
type: 'value',
max: 50
},
series: [
{
type: 'scatter',
name: '销量1',
data: [5, 20, 36, 10, 10, 20],
symbol: 'triangle',
symbolSize:20,
itemStyle: {
color: 'red',
borderWidth: 2,
borderColor: 'blue',
},
emphasis: {
itemStyle: {
color: 'green'
},
label: {
show: true,
formmatter: "{a}\n{b}{c}", // 这是一个 label
position: 'right'
}
}
},{
type: 'scatter',
name: '销量2',
data: [4, 30, 26, 14, 8, 15]
}
],
title: {
show: true,
text: '主标题-点击访问百度',
link: 'https//www.baidu.com/',
target: 'blank', // 或者 self 也可以
subtext: '副标题-点击访问个人网站',
sublink: 'http://hello-nilera.com/',
subtarget
textStyle: {
color: 'red',
fontSize: 30,
fontStyle: 'italic',
fontWeight: 'normal'
}
}
}

myChart.setOption(options);
}
</script>
</body>
</html>
3.5.4 ECharts 视觉映射(Visual Map)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>样式设置——视觉映射组件</title>
<!-- <script type="text/javascript" src="js/echarts.min.js"></script> -->
<script type="text/javascript" src="http://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<div id="main" class="main" style="width: 900px; height: 400px"></div>
<script>
window.onload=function() {
var dom = document.getElementById('main');
var myChart = echarts.init(dom);
var options = {
// 连续型视觉映射组件
/*
visualMap: [
{
type: 'continuous',
min: 0,
max: 40,
range: [4, 15],
calculable: true
}
],
*/
// 分段型视觉映射组件
visualMap: [{
type: 'piecewise',
min: 0,
max: 40
}],
title: {
text: 'ECharts 视觉映射组件'
},
tooltip: { },
legend: {
data: ['销量']
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: { },
series: [
{
name: '销量 1',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
},{
name: '销量 2',
type: 'bar',
data: [8, 12, 30, 15, 18, 26]
}
]
}
myChart.setOption(options);
}
</script>
</body>
</html>

3.6 ECharts 中的事件和行为

概念:Apache ECharts 的图表中用户的操作将会触发相应的事件。开发者可以监听这些事件,然后通过回调函数做相应的处理。

事件分类:ECharts 中事件分为两种类型,一种是用户鼠标操作点击,或者 hover 图表的图形时触发的事件,还有一种是用户在使用可以交互的组件后触发的行为事件,例如在切换图例开关时触发的 legendselectchanged 事件(这里需要注意切换图例开关是不会触发事件的),数据区域缩放时触发的 datazoom 事件等等。

3.7 ECharts 实例

3.7.1 柱状图

绘制简单的柱状图,一些配置项:

  • xAxis/yAxis
    • typecategory 类目轴;value 数值轴;time 时间;log 对数轴
    • data:设置类目的数据(数组)
  • series
    • type:设置系列的类型(设置绘制什么图表)
    • name:设置系列名称,会用在 tooltiplegend
    • data:系列中的数据内容数组,定义当前这个系列中每个柱子的高度
    • label:设置图形上的文本标签(把数值显示在每个柱子上)
    • itemStyle:设置图形样式
    • showBackground:设置是否显示背景色(默认不显示)
    • backgroundStyle:设置背景色
3.7.2 折线图、堆叠折线图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- <script type="text/javascript" src="js/echarts.min.js"></script> -->
<script type="text/javascript" src="http://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<div id="main" class="main" style="width: 900px; height: 400px"></div>
<script>
window.onload=function() {
var dom = document.getElementById("main");
var myCharts = echarts.init(dom);
var options = {
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五']
},
yAxis: {
type: 'value'
},
series: [
{ type: 'line', data: [10, 20, 30, 20, 10]}
],
toolbox: {
feature: {
show: true,
dataZoom: { // 数据区域缩放
// 框选型数据区域缩放组件(dataZoomSelect): 提供一个选框进行区域缩放。
// 即 toolbox.feature.dataZoom
yAxisIndex: 'none'
},
dataView: {
readOnly: true
},
magicType: {
type: ['line', 'bar']
},
restore: { // 配置项还原
},
saveAsImage: {
type: 'jpg'
}
}
}
}
myCharts.setOption(options);
}
</script>
</body>
</html>
3.7.3 面积图和堆叠面积图
3.7.4 雷达图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- <script type="text/javascript" src="js/echarts.min.js"></script> -->
<script type="text/javascript" src="http://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<script type="text/javascript" src="http://cdn.staticfile.org/jquery/2.2.4/jquery.min.js"></script>
</head>
<body>
<div id="main" class="main" style="width:900px; height: 400px"></div>
<script>
var dom = document.getElementById("main");
var myCharts = echarts.init(dom);
var options = {
radar: {
indicator: [
{ name: '身高' },
{ name: '体重' },
{ name: '年龄' },
{ name: '跑步速度' }
],
},
series: [
{
type: 'radar',
data: [
{ name: '训练前', value: [178, 60, 20, 50] },
{ name: '训练后', value: [178, 58, 20, 52] },
{ name: '训练一年后', value: [180, 55, 21, 55]}
]
},
],
tooltip: {

},
toolbox: {
show:true,
feature:{
saveAsImage:{
show:true
}
}
},
}

myCharts.setOption(options);
</script>
</body>
</html>

HBaseExamReview

HBase 考察知识点复习

知识点(选择)

  1. Region的分裂

    HBase中,Region 是数据管理的基本单位,类似于关系型数据库中的分区。它是 HBase 数据存储和组织的核心概念之一。
    Region 的概念和特点:
    RegionHBase 数据管理的基本单位,它负责存储一定范围内的数据。每个 Region 由一个起始 RowKey 和 终止 RowKey 定义,负责存储该范围内的数据。与关系型数据库中的分区类似,HBase 中的 Region 可以根据数据量的大小进行动态调整。当一个Region 的数据量过大时,它会被 分裂 成两个新的 Region。相反,当数据量较小时,两个或多个 Region 可能会合并成一个新的Region

    MemStore 的数据超过阈值时,将数据溢写磁盘,生成一个 StoreFile 文件。当 Region 中最大Store 的大小超过阈值时,Region 分裂,等分成两个 Region,实现数据访问的负载均衡。新的 Region 的位置由 HMaster 来确定在哪个RegionServer 中。

  2. 查看表结构的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[subowen@bigdata ~]$ hbase shell
2024-07-05 23:04:35,655 WARN [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
HBase Shell
Use "help" to get list of supported commands.
Use "exit" to quit this interactive shell.
For Reference, please visit: http://hbase.apache.org/2.0/book.html#shell
Version 2.1.8, rd8333e556c8ed739cf39dab58ddc6b43a50c0965, Tue Nov 19 15:29:04 UTC 2019
Took 0.0028 seconds
hbase(main):001:0> list
TABLE
logs
logs_20240606
logs_20240607
user
sdutcm:bigdata
5 row(s)
Took 1.2756 seconds
=> ["logs", "logs_20240606", "logs_20240607", "user", "sdutcm:bigdata"]

hbase(main):002:0> describe 'sdutcm:bigdata'
Table sdutcm:bigdata is ENABLED
sdutcm:bigdata
COLUMN FAMILIES DESCRIPTION
{NAME => 'info', VERSIONS => '5', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}
{NAME => 'msg', VERSIONS => '5', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}
2 row(s)
Took 0.3826 seconds

hbase(main):003:0> scan 'sdutcm:bigdata'
ROW COLUMN+CELL
2001 column=info:name, timestamp=1719026353219, value=wang
1 row(s)
Took 0.2295 seconds
hbase(main):004:0>
  1. 启动和关闭 HBase Shell 的命令

    启动 HBase Shell 之前需要保证 ZooKeeperHadoop HDFSHBase 已经启动。可以使用 hbase shell 进入 HBase Shell,在 HBase Shell 中可以使用 exitquit 退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
[subowen@bigdata ~]$ zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /home/subowen/apps/zookeeper-3.4.12/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

[subowen@bigdata ~]$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /home/subowen/apps/zookeeper-3.4.12/bin/../conf/zoo.cfg
Mode: standalone

[subowen@bigdata ~]$ start-dfs.sh
Starting namenodes on [bigdata]
bigdata: starting namenode, logging to /home/subowen/apps/hadoop-2.7.6/logs/hadoop-subowen-namenode-bigdata.out
bigdata: starting datanode, logging to /home/subowen/apps/hadoop-2.7.6/logs/hadoop-subowen-datanode-bigdata.out
Starting secondary namenodes [0.0.0.0]
0.0.0.0: starting secondarynamenode, logging to /home/subowen/apps/hadoop-2.7.6/logs/hadoop-subowen-secondarynamenode-bigdata.out

[subowen@bigdata ~]$ start-hbase.sh
running master, logging to /home/subowen/apps/hbase-2.1.8/logs/hbase-subowen-master-bigdata.out
bigdata: running regionserver, logging to /home/subowen/apps/hbase-2.1.8/logs/hbase-subowen-regionserver-bigdata.out

[subowen@bigdata ~]$ jps
1746 NameNode
1572 QuorumPeerMain
2071 SecondaryNameNode
2874 Jps
2379 HRegionServer
1852 DataNode
2285 HMaster

[subowen@bigdata ~]$ hbase shell
2024-07-05 22:47:57,331 WARN [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
HBase Shell
Use "help" to get list of supported commands.
Use "exit" to quit this interactive shell.
For Reference, please visit: http://hbase.apache.org/2.0/book.html#shell
Version 2.1.8, rd8333e556c8ed739cf39dab58ddc6b43a50c0965, Tue Nov 19 15:29:04 UTC 2019
Took 0.0051 seconds
hbase(main):001:0> exit

[subowen@bigdata ~]$ hbase shell
2024-07-05 22:52:46,337 WARN [main] util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
HBase Shell
Use "help" to get list of supported commands.
Use "exit" to quit this interactive shell.
For Reference, please visit: http://hbase.apache.org/2.0/book.html#shell
Version 2.1.8, rd8333e556c8ed739cf39dab58ddc6b43a50c0965, Tue Nov 19 15:29:04 UTC 2019
Took 0.0025 seconds
hbase(main):001:0> quit
[subowen@bigdata ~]$
  1. 启动ZooKeeper和查看 ZooKeeper 的运行状态的命令
1
2
3
4
5
6
7
8
9
[subowen@bigdata ~]$ zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /home/subowen/apps/zookeeper-3.4.12/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

[subowen@bigdata ~]$ zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /home/subowen/apps/zookeeper-3.4.12/bin/../conf/zoo.cfg
Mode: standalone
  1. 修改表结构的命令

    使用 alter 命令可以对表的结构进行修改:

    表名创建时写的所有和列族相关的信息,都可以后续通过alter修改,包括增加删除列族。

    ① 增加列族和修改信息都使用覆盖的方法

    ​ 修改列族的版本,VERSIONS => 6

    1
    2
    3
    4
    5
    hbase(main):001:0> alter 'bigdata:person', NAME => 'name', VERSIONS => 6
    Updating all regions with the new schema...
    1/1 regions updated.
    Done.
    Took 4.0145 seconds

    ​ 添加列族 tel

    1
    2
    3
    4
    5
    hbase(main):002:0> alter 'bigdata:person', NAME => 'tel', VERSIONS => 6
    Updating all regions with the new schema...
    1/1 regions updated.
    Done.
    Took 2.4498 seconds

    ​ 查看修改后的数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    hbase(main):003:0> describe 'bigdata:person'
    Table bigdata:person is ENABLED
    bigdata:person
    COLUMN FAMILIES DESCRIPTION
    {NAME => 'msg', VERSIONS => '6', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'fal
    se', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN
    _MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}

    {NAME => 'name', VERSIONS => '5', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'fa
    lse', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', I
    N_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}

    {NAME => 'tel', VERSIONS => '6', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE => 'fal
    se', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN
    _MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE => '65536'}
    3 row(s)
    Took 0.0795 seconds

    ② 删除列族

    ​ 删除列族可以用以下两种方式:

    1
    2
    3
    4
    5
    hbase(main):001:0> alter 'bigdata:person', NAME => 'tel', METHOD => 'delete'
    Updating all regions with the new schema...
    1/1 regions updated.
    Done.
    Took 2.1046 seconds
    1
    2
    3
    4
    5
    hbase(main):002:0> alter 'bigdata:person', 'delete' => 'msg'
    Updating all regions with the new schema...
    1/1 regions updated.
    Done.
    Took 2.9721 seconds
  2. HBaseJava API中,数据表描述的对象是什么?TableDescriptor

    1
    TableDescriptor
  3. ZooKeeper 的访问端口号,HBase 的Web访问端口号

    ZooKeeper 的访问端口号为 2181

    HBase 的 Web 访问端口号为 16010

  4. 修改表中列族的Version版本的命令

1
alter 'table_name', {NAME => 'column_family', VERSIONS => number_of_versions}
  1. 删除表中数据的命令

    1
    delete 'ns:tb', 'rk', 'cf:col'
  2. 启动和关闭ZooKeeper的命令

    1
    2
    3
    4
    5
    # 启动 ZooKeeper
    zkServer.sh start

    # 关闭 ZooKeeper
    zkServer.sh stop
  3. 更新表数据的命令

    1
    alter 'ns:tb', {NAME => 'cf:col', VERSIONS => 5}
  4. HBase安装时,配置文件有那几个?

    • hbase-env.sh
    • hbase-site.xml
    • regionservers
    • .bashrc
  5. 关于NoSQL数据库有哪些?

    • HBase
    • Cassandra
    • BigTable
  6. 删除表操作

1
2
disable 'ns:tb'
drop 'ns:tb'
  1. HBase进程名,ZooKeeper 的进程名等相关进程

    • HMaster
    • HRegionServer
    • QuorumPeerMain
  2. HBase 依托于哪个文件存储系统?

    HBase 主要依托于 Hadoop HDFS

  3. 创建表的语法结构

    1
    create 'ns:tb', {NAME => 'cf', VERSIONS=5}
  4. 查询表数据的Shell语法

    1
    2
    3
    get 'r1', {COLUMN => 'cf:col'}

    scan 'ns:tb'
  5. 什么是列族COLUMN FAMILY

  6. HBase的数据类型

    • 数据类型
      • 基本数据类型
        • 字符串(String):用于存储文本数据,如名称、描述等。
        • 整数(Int):用于存储整数数据,如计数、编号等。
        • 浮点数(Float):用于存储小数数据,如金额、比率等。
        • 布尔值(Boolean):用于存储逻辑值,如是否、有效等。
      • 复合数据类型
        • 列族(Column Family):列族是HBase表中数据的组织方式,用于存储一组相关的列。列族是HBase表中数据的基本组织单位,每个列族对应一个数据节点。
        • 列(Column):列是HBase表中数据的基本单位,用于存储一组相关的单元格。列可以包含多个单元格,每个单元格对应一个数据值。
        • 单元格(Cell):单元格是HBase表中数据的基本单位,用于存储一组相关的数据值。单元格包含一个键(Row Key)、一个列(Column)和一个值(Value)。
    • 数据模型
      • NameSpace:命名空间,类似于关系型数据库的 Database 概念,每个命名空间下有多个表。HBase 两个自带的命名空间,分别是 hbasedefaulthbase 中存放的是 HBase 内置的表,default表是用户默认使用的命名空间。
      • Table:类似于关系型数据库的表概念。不同的是,HBase 定义表时只需要声明列族即可,不需要声明具体的列。因为数据存储是稀疏的,所以往 HBase 写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase 能够轻松应对字段变更的场景。
      • Row:HBase 表中的每行数据都由一个 RowKey 和多个 Column(列)组成,数据是按照 RowKey 的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要。
      • Column:HBase 中的每个列都由 Column Family(列族)和 Column Qualifier(列限定符)进行限定,例如 info:nameinfo:age。建表时,只需指明列族,而列限定符无需预先定义。
      • Time Stamp:用于标识数据的不同版本Version,每条数据写入时,系统会自动为其加上该字段,其值为写入 HBase 的时间。
      • Cell:由 {rowkey, column Family:Column Qualifier, timestamp} 唯一确定的单元。Cell 中的数据全部是字节码形式存贮。

判断题

  1. HBase 本身是用 C++ 语言编写的,因此具有很高的执行效率。( × )

    HBase 本身是由 Java 语言编写的,尽管使用 Java,其执行效率还是较高的。

  2. HBase 优化目前主要工作集中于 Rowkey 优化。( × )

    HBase 的优化目前主要工作集中在 RowKey 的设计、参数优化、JVM调优等方面。

  3. 可以想象 HBase 是通过**”三维”**的表来存储数据的,通过时间戳保存数据的历史版本。( √ )

  4. 安装 HBase 完全分布式时,需要确保 SSHJDK 是可以正常使用的。( √ )

    在安装 HBase 的完全分布式部署时,确保 SSHJDK 可以正常使用是非常重要的。具体来说:

    • SSH:在分布式部署中,HBase 的各个节点之间需要进行通信和协作,而 SSH 是用于在不同节点之间建立安全连接的工具。确保节点之间可以互相访问、通信和传输文件是部署 HBase 所必需的。通常需要设置免密登录,以便在节点之间进行信息交换而无需手动输入密码。

    • JDK:HBase 是基于 Java 编程语言开发的,因此必须确保在每个节点上安装并配置了适当的 JDKHBase 运行需要 Java 环境,且为了确保 HBase 能够正常工作,必须保证每个节点都能够使用正确的 JDK 版本。

  5. Hadoop 中引入 HBase 的其中一个原因是:HDFS 在处理连续数据时比较吃力。( × )·

    HDFS 的设计目标之一就是为了有效地处理大规模数据,包括连续数据(如大文件、数据流等)和离散数据(如小文件、批量数据等)。Hadoop 中之所以引入 HBase,是需要 HBase 提供实时访问数据的能力,使得 Hadoop 生态系统更全面(此外还有高可伸缩性、高容错、多版本策略等)。

  6. 利用 HBase 技术可在廉价 PC Server 上搭建起大规模结构化存储集群。( √ )

  7. HBaseHadoop 生态系统的一个组成部分。( √ )

    HBase 是继 Google Bigtable 之后创建的分布式、开源、版本化、非关系型数据库。它是 Hadoop 生态系统的重要组件,利用 HDFS 的容错功能,提供对数据的实时读写访问。 HBase 尽管是数据库,但也可以称为数据存储系统,因为它不提供触发器、查询语言和二级索引等 RDBMS 功能。

  8. 使用 HBase 时,无需启动服务就可以直接在开发环境中调用 HBase 的相关功能。( × )

    HBase 是基于 Hadoop 的,在使用 HBase 之前需要启动 ZooKeeperHadoop 才能使用 HBase

  9. 配置 HBase 分布式部署的过程中,可以通过 cp 命令把文件复制到运行在不同机器上的节点中。( × )

    可以使用 scp 命令、rsync命令,或者基于 rsync 编写工具 xsync 进行传输。

填空题

  1. 判断表是否存在,禁用表,删除表,创建表,查看命名空间等 Shell 命令。

    • 判断表是否存在 :exists 'namespace_name:table_name'
    • 禁用/启用表:disable 'namespace_name:table_name' / enable namespace_name:table_name
    • 删除表:删除表之前需要禁用表,disable 'namespace_name:table_name',然后再删除表,drop 'namespace_name:tablename'
    • 创建表:create 'namespace_name:table_name', {NAME => 'cf1', VERSION=5}, {NAME => 'cf2', VERSION=5}
    • 查看命名空间:list_namespace
  2. HBase 的进程有哪些:关于这些进程的详细说明见 QuickPassHBase简答题 T4

    • HBase 的主要进程:HMasterHRegionServer
    • HBase 所依赖的两个外部的服务:ZooKeeperHDFS
  3. 行键是什么:rowkey

    HBase 中,行键唯一标识了 HBase 表的一行,可以通过单个行键或行键区间的方式访问表。行键保存为 字节数组 ,以字典序 排序存储。

  4. HBase 是一个什么样的分布式存储系统。(非关系型)

    Apache HBase™ 是以 HDFS 为数据存储的,一种分布式可扩展NoSQL 数据库。HBase 是一款面向列存储,用于存储处理海量数据的 NoSQL 数据库。

    HBase 的理论原型是 GoogleBigTable 论文。他是一个高可靠性高性能面向列可伸缩分布式存储系统。

简答题

  1. HBase 的基础核心组件有哪些?分别什么作用?

    • HBase-Client
      客户端,用来访问 HBase 集群。可以和 Hbase 交互,也可以和 HRegionServer 交互。通过 HBase RPC 来访问对应的接口。

      这里的客户端模式有多种,可以是 ThriftAvroRest 等。

      另外,hbase-client 自身会缓存 region 的一些信息。

    • ZooKeeper
      作用:

      • HMaster 的高可用
      • 存储 ROOT 表的地址、HMaster 的地址
      • 存储所有 HRegionServer 的状态,监控 HRegionServer 的上下限
      • 存储 HBase 的一些 SchemaTable 的元数据
    • HMaster

      HMaster可以启动多个,通过选举机制来保证只有一个 HMaster 正常运行并提供服务,其他的 HMaster 作为 standby 来保证高可用。HMaster 主要负责 Region 的管理工作。如:

      • 用户对表的增删改查
      • 管理 RegionServer 的负载均衡,调整 Region 的分布
      • RegionServer 宕机或下线后,负责迁移 RegionServer 上的 Region 到其他的 RegionServer
      • Region 在分裂后,负责分配新的 Region
    • HRegionServer
      HRegionServerHBase 中真正的工作节点,主要负责响应用户的 I/O 请求,向 HDFS 文件系统读写数据,以及 Region 的数据文件的合并和拆分等,是 HBase 中最核心的模块。

      HBase 中,一张表由多个的 HRegion 组成,一个 HRegionServer 中管理着多个 HRegion 对象。而一个 HRegion 由多个HStore 组成,每个HStore对象都对应着表的一个列族 (Column Family)。之后,一个HStore又由一个MemStore和多个StoreFile组成。这些 StoreFile 就是hbase 存储在 HDFS上的数据文件,MemStore 表示还在内存中未刷新到文件上的那些数据。

  2. HBase 的写流程, 读流程?

    • 写流程

      写流程顺序正如 API 编写顺序,首先创建 HBase 的重量级连接。

      ① 首先访问 ZooKeeper,获取 hbase:meta 表位于哪个 Region Server

      ② 访问对应的 Region Server,获取 hbase:meta 表,将其缓存到连接中,作为连接属性 MetaCache,由于 meta 表具有一定的数据量,导致了创建连接比较慢;

      之后使用创建的连接获取 Table,这是一个轻量级的连接,只有在第一次创建的时候会检查表格是否存在访问RegionServer,之后在获取 Table 时不会访问 RegionServer

      ③ 调用 Tableput 方法写入数据,此时还需要解析 RowKey,对照缓存的 MetaCache,查看具体写入的位置有哪个 RegionServer

      ④ 将数据顺序写入(追加)到 WAL,此处写入是直接落盘的,并设置专门的线程控制 WAL 预写日志的滚动(类似 Flume);

      ⑤ 根据写入命令的 RowKeyColumnFamily 查看具体写入到哪个 MemStore,并且在 MemStore 中排序

      ⑥ 向客户端发送 ACK

      ⑦ 等达到 MemStore 的刷写时机后,将数据刷写到对应的 Store 中。

    • 读流程

      读流程创建连接的方式同写流程。创建完连接后:

      ① 创建 Table 对象发送 get 请求。

      ② 优先访问 Block Cache(读缓存),查找是否之前读取过,并且可以读取 HFile 的索引信息和布隆过滤器。

      ③ 不管读缓存中是否已经有数据了(可能已经过期了),都需要再次读取写缓存和 store 中的文件。

      ④ 最终将所有读取到的数据合并版本,按照 get 的要求返回即可。

  3. HBase 的特点有哪些?优点或缺点?

    HBase的特点:(强 / 自动 / 高 / 海集 / 并 / 列 / 多 / 块 / 运行 / 稀)

    • 强一致性的读/写HBase不是”最终一致性”数据库,它非常适合于诸如高速计数器聚合等任务。

    • 自动分片HBase 中的表通过 Region 分布在集群上,而且 Region 会随着数据的增长自动拆分和重新分布。

    • 高可靠性:自动 RegionServer 故障转移,WAL 机制保证了数据写入时不会因集群异常而导致写入数据丢失,Replication 机制保证了在集群出现严重的问题时,数据不会发生丢失或损坏。而且 HBase 底层使用 HDFSHDFS 本身也有备份。

    • Hadoop/HDFS 集成&海量存储:,HBase支持 HDFS开箱即用作为其分布式文件系统。HBase 作为一个开源的分布式 Key-Value 数据库,其主要作用是面向 PB 级别数据的实时入库和快速随机访问。这主要源于上述易扩展的特点,使得 HBase 通过扩展来存储海量的数据。

    • 并行处理HBase 通过 MapReduce 支持大规模并行处理,将 HBase 用作源和接收器。

    • 列式存储HBase 是根据列族来存储数据的。列族下面可以有非常多的列。列式存储的最大好处就是,其数据在表中是按照某列存储的,这样在查询只需要少数几个字段时,能大大减少读取的数据量。(面向列)

    • 多种语言的 APIHBase 支持使用 JavaAPI 来编程进行数据的存取,还支持使用 Thrift 语言和 REST 语言的 API 来编程进行数据的存取。

    • 块缓存和布隆过滤器HBase支持 Block CacheBloom过滤器进行大容量查询优化。

    • 运行管理HBase 为业务洞察和 JMX 度量提供内置网页。

    • 稀疏性:为空的列可以不占存储空间,表可以设计的非常稀疏。

    HBase 的优点和缺点

    • HBase的优点:(动节/海存/负载/并拓)

      • 动态增加&节省空间:在传统的关系数据库中,如果数据结构发生了变化,就需要停机维护,而且需要修改表结构,而在 HBase 中数据表内的列可以做到动态增加,并且列为空的时候不存储数据,从而节省存储空间。
      • 海量数据存储HBase 适合存储 PB 数量级的海量数据,PB 级的数据在只采用廉价 PC 来存储的情况下,也可以在几十到一百毫秒内返回数据。这与 HBase 的极易扩展息息相关,正因如此,HBase 为海量数据的存储提供了便利。
      • 负载均衡:传统的通用关系数据库无法应对在数据规模剧增时导致的系统扩展性问题和性能问题。HBase 可以做到自动切分数据,并且会随着数据的增长自动地拆分和重新分布。
      • 高并发HBase 可以提供高并发的读写操作,而且可以利用廉价的计算机来处理超过 10 亿行的表数据
      • 高拓展性HBase 具有可伸缩性,如果当前集群的处理能力明显下降,可以增加集群的服务器数量来维持甚至提高处理能力。
    • HBase 的缺点:(条查/复杂/JOIN/ACID/SQL)

      • 不支持条件查询:不能支持条件查询,只支持按照 RowKey(行键)来查询,也就是只能按照主键来查询。这样在设计 RowKey 时,就需要完美的方案以设计出符合业务的查询。
      • 架构设计复杂:架构设计复杂,且使用 HDFS 作为分布式存储,因此只是存储少量数据,它也不会很快。在大数据量时,它慢的不会很明显。
      • 不支持 Join 操作HBase 不支持表的关联操作,因此数据分析是 HBase 的弱项。常见的 group byorder by 只能通过编写 MapReduce 来实现。
      • 不支持ACIDHBase部分支持了 ACID
      • 不支持SQL语句查询:查询 HBase 时不支持通过 SQL 语句进行查询。
  4. MasterRegionServer 的作用是什么?

    HBase 包含一个 Master 和许多个 RegionServer

    • Master:实现类为 HMaster,负责监控集群中所有的 RegionServer 实例。主要作用如下:

      • 管理元数据表格 hbase:meta,接收用户对表格创建修改删除的命令并执行

      • 监控 Region 是否需要进行负载均衡,故障转移和 Region 的拆分。

    • RegionServer:实现类为 HRegionServer,主要作用如下:

      • 负责数据 Cell 的处理,例如写入数据 put,查询数据 get 等。

      • 拆分合并 Region 的实际执行者,有 master 监控,有 RegionServer 执行。

编程题

Shell

  1. 创建命名空间的 Shell

    1
    create_namespace 'namespace_name'
  2. 创建表的 Shell

    1
    create 'namespace_name:table_name', {NAME => 'cf1', VERSION => 5} 
  3. 修改表的 Shell

    1
    alter 'namespace_name:table_name', NAME => 'cf1', METHOD => 'delete'
  4. 插入数据的 Shell

    1
    put 'ns:tb', 'rk', 'cf:col', 'value'
  5. 查询数据的 Shell

    • get 最大范围是一行数据,也可以进行列的过滤,读取的结果为多行 CellCell 的格式如下:{rowkey, column Familycolumn Qualifier, time Stamp}
    1
    get 'ns:tb', 'rk' , {COLUMN => 'cf:col'}
    • scan 用于扫描数据,能够读取多行数据,不建议扫描过多的数据,推荐使用 startRowstopRow 来控制读取的数据,默认范围左闭右开
    1
    scan 'namespace_name:table_name', {STARTROW => '1001',STOPROW => '1002'}

API

读写操作时

  1. 加载配置信息
  2. 获取 HBase 的链接对象
  3. 获取 Admin 对象
  4. 构造 TableName 对象
  5. 判断表是否存在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
public class Test {
public static Connection connection = null;
static {
Configuration conf = new Configuration(); // 加载配置信息
conf.set("hbase.zookeeper.quorum", "bigdata");
try {
connection = ConnectionFactory.createConnection(conf);
} catch (IOException e) {
e.printStackTrace();
}
}

public static void closeConntction() throw IOException {
if (connection != null) {
connection.close();
}
}

/**
* @brief 判断表格是否存在
* @param namespace 命名空间名
* @param tableName 表名
* @return true 存在; false 不存在
*/
public static boolean isTableExists(String namespace, String tableName) throws IOException {
// 1. 获得 admin
Admin admin = HBaseConnection.connection.getAdmin();

// 2. 使用对应的方法, 判断表格是否存在
boolean result = false;
try {
result = admin.tableExists(TableName.valueOf(namespace, tableName));
} catch (IOException e) {
e.printStackTrace();
}

// 3. 关闭 admin
admin.close();

// 返回结果
return result;
}

/**
* @brief 向表格中插入数据
* @param namespace 命名空间名
* @param tableName 表名
* @param rowKey 行键
* @param columnFamily 列族名
* @param columnName 列名
* @param value 插入值
*/
public static void putCell(String namespace,
String tableName,
String rowKey,
String columnFamily,
String columnName,
String value) throws IOException {
// 1. 获取 Table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2. 调用相关方法插入数据
// 2.1 创建 Put 对象
Put put = new Put(Bytes.toBytes(rowKey));

// 2.2. 给 Put 对象添加数据
put.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(columnName), Bytes.toBytes(value));

// 2.3 将对象写入对应的方法
try {
table.put(put);
} catch (IOException e) {
e.printStackTrace();
}

// 3. 关闭 Table
table.close();
}

/**
* @brief 读取数据 读取对应一行的某一列
* @param namespace 命名空间名
* @param tableName 表名
* @param rowKey 行键
* @param columnFamily 列族名
* @param columnName 列名
*/
public static void getCells(String namespace,
String tableName,
String rowKey,
String columnFamily,
String columnName) throws IOException {
// 1. 获取 Table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));

// 2. 调用相关方法插入数据
// 2.1 创建 Get 对象
Get get = new Get(Bytes.toBytes(rowKey)); // 如果此时不使用 addColumn 进行参数的添加, 此时则读取一整行的数据
// 2.2 如果想读取某一列的数据, 需要添加对应的参数
get.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(columnName));

// 2.3 也可以设置读取数据的版本
get.readAllVersions();

try {
// 3. 读取数据, 写入 Result 对象
Result result = table.get(get);

// 4. 处理数据
Cell[] cells = result.rawCells();

// 5. 测试方法: 直接把读取到的数据打印到控制台
// 如果是实际开发, 需要再额外写方法, 对应处理数据
for (Cell cell : cells) {
// Cell 存储数据比较底层, 需要进一步处理
String value = new String(CellUtil.cloneValue(cell));
System.out.println(value);
}
} catch (IOException e) {
e.printStackTrace();
}

// 6. 关闭 Table
table.close();
}

private static main(String[] args) throw IOException {
putCell("sdutcm", "bigdata", "2001", "info", "name", "zhangsan");
connection.close();
}
}

参考文献

Hadoop 综合揭秘——HBase的原理与应用 - 风尘浪子 - 博客园 (cnblogs.com)

HBase的组件

SparkQuickIN

快速入门 Spark

[TOC]

⛳︎ 1. 开始 Spark

Spark官网Apache Spark™ - Unified Engine for large-scale data analytics

1.1 什么是Spark

Spark官网的解释:Apache Spark™ is a unified analytics engine for large-scale data processing.

Apache Spark 是专为大规模数据处理而设计的快速通用的计算引擎。Spark是加州大学伯克利分校的 AMP实验室 所开源的类 Hadoop MapReduce 的通用并行计算框架,Spark 拥有 Hadoop MapReduce 所具有的优点,但不同于 MapReduce 的是:Job 中间输出结果可以缓存在内存中,从而不再需要读写 HDFS,减少磁盘数据交互,因此 spark 能更好地适用于数据挖掘与机器学习等需要迭代的算法。

SparkScala 编写,方便快速编程。

其特点是:高速、使用简单、通用、可以在多处运行。

1.2 总体技术栈讲解

Spark 提供了 Sparkcore RDDSpark SQLSpark StreamingSpark MLlibSpark GraphX等技术组件,可以一站式的完成大数据领域的离线批处理、交互式查询、流式计算、机器学习、图计算等常见的任务。这就是Spark一站式开发的特点。

1.3 SparkMapReduce 的区别

1.3.1 MapReduce 的原理

MapReduce 在运算时需要多次进行磁盘 I/O。下面是一个简单的 MapReduce 过程:

视频链接:https://www.bilibili.com/video/BV1TB4y1i7kk/

在这个视频中,可以看出MapReduce 过程中需要多次磁盘 I/O,落地到HDFS上。

1.3.2 Spark 是如何做的

可以看到,MapReduce 的多个 Job 之间相互独立,每个 Job 完成后的数据都需要存储到文件系统中。每个 Job 中也可能会存在大量的磁盘 I/O ,这样会使得 MapReduce 的速度很慢。相比于 MapReduceSpark使用了 DAG 有向无环图。使多个任务串联起来,将结果存储在内存中(当然内存不够还是要将数据缓存在磁盘中)直接进行运算,避免了大量的磁盘I/O。

1.3.3 SparkMapReduce 的一些联系

SparkMapReduce 都是分布式计算框架,Spark 计算中间结果基于内存缓存,MapReduce 基于HDFS存储。也正因此,Spark处理数据的能力一般是 MapReduce的三到五倍以上,Spark 中除了基于内存计算这一个计算快的原因,还有DAG(DAG Schedule)有向无环图来切分任务的执行先后顺序。

1.4 Spark API

Spark API 有多种语言支持,分别包括:ScalaJavaPythonRSQL 等。

1.5 Spark 的运行模式

  • Local:多用于本地测试,如在:EclipseIDEA 中编写测试程序等。
  • StandaloneSpark 自带的资源调度框架,它支持完全分布式。Standalone模式也叫作独立模式其自带完整的服务,可单独部署到一个集群中,无序依赖任何其他资源管理系统。 从一定程度上来说,该模式是 Local 模式和 Yarn 模式的基础。
  • Yarn: Hadoop 生态圈里的一种资源调度框架,Spark也是可以基于 Yarn 来计算的。 若要使用 Yarn 来进行资源调度,必须实现ApplicationMaster 接口,Spark 实现了这个接口,所以可以基于 Yarn 来进行资源调度。
  • Mesos:也是一种资源调度框架(了解即可)。

🥑 2. SparkCore

2.1 RDD

2.1.1 RDD 的概念

RDD(Resilient Distribute Dataset):弹性分布式数据集。

RDD算子Other RDDRDD 经过算子的运算会变成其他的 RDD

(重点)RDD的特点:① 分区的;② 并行操作的;③ 不可变的。

2.1.2 RDD 的五大特性
  1. 每个RDD 由一系列的 Partition 组成。
  2. 函数是作用在每一个 Partition (Split) 上的。
  3. RDD 中有一系列的依赖关系,或者说每个RDD都会依赖其他一系列的RDD
  4. 分区器是作用在 <K, V> 格式的 RDD 上,即:<K, V>RDD 可以通过 Partitioner 进行自定义分区。
  5. RDD提供一系列的最佳计算位置。数据在哪里,计算就在哪里,移动数据不如移动计算。
2.1.3 RDD 理解图

Spark 中读取文件是使用 SparkContext 对象调用 textFile 方法,实际上底层和 MapReduce 读取 HDFS 文件的方式是相同的,读取之前先要进行 split 切片。默认情况下 Split 的大小和 Block 块的大小相同。

一些问题:

  1. RDD的分布式体现在那些方面?

    RDD 由一系列的 Partition 构成,并且 Partition 是分布在不同的节点上的。这就体现了 RDD 的分布式。

  2. 哪里体现了 RDD 的弹性?

    RDD 由一系列的 Partition 组成,其大小和数量都是可以改变的。默认情况下,Partition 的个数和 Block 块的个数相同。

  3. 哪里体现了 RDD 的容错?

    RDD 之间存在一系列的依赖关系,子RDD 可以找到对应的父RDD ,然后通过一系列计算得到得出响应的结果,这就是容错的体现。

    RDD 提供计算最佳位置,体现了数据本地化,体现了大数据中”移动数据不如移动计算“的理念。

一些注意事项:

  1. textFile 方法底层封装的是 MapReduce 读取文件的方式,读取文件之前先进行 Split 切片,默认 Split 大小是一个 Block 的大小。
  2. RDD 实际上不存储数据,但是为了方便理解,可以理解为存储数据。
  3. 什么是 <K, V> 格式的 RDD,如果 RDD 里面存储的数据都是二元组对象,那么这个 RDD 我们就叫做 <K, V> 格式的 RDD
2.1.4 SparkRDD 编程模型
  1. 创建 SparkContext 对象

  2. 创建 RDD

  3. 计算 RDD

  4. 输出结果(如控制台打印测试,存储等)

  5. 关闭 SparkContext

2.1.5 WordCount 案例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
object WordCount {
def main(args: Array[String]): Unit = {
// 第一步: 创建 SparkContext 对象
// 对于每个 Spark 程序来说, 最重要的就是两个对象: SparkConf 和 SparkContext
val conf = SparkConf()
conf.setAppName("WordCount").setMaster("Local")
val sparkContext = SparkContext(conf)

// 创建 RDD
val line: RDD[String] = sc.textFile("files/order.csv")

// 计算 RDD
val word: RDD[String] = sc.flatMap(x => x.split(" "))
val pair: RDD[(String, Int)] = word.map(x => (x, 1))
// val result: RDD[(String, Int)] = pair.reduceByKey((x, y) => {x + y})
val result: RDD[(String, Int)] = pair.reduceByKey((x, y) => {
println(x + ":" + y)
x + y
})

// 输出 RDD
result.foreach(println)

// 关闭 SparkContext
sparkContext.stop()
}
}

2.2 Spark 任务执行原理

从下图中,我们可以看到 Spark 的主要角色:

SparkExecutor

一些名词的解释:

  • Master Node 主节点
  • Worker Node 从节点
  • Driver 驱动程序
  • Executor 执行节点
  • Cluster Manager 集群管理者
2.2.1 Spark 架构的类比

我们可以简单的将这个架构和 YARN 对比一下:Master 就相当于 YARN 中的 ResourceManagerWorker 就相当于 YARN 中的 NodeManagerDriver 相当于 YARN 中的 Application

2.2.2 Spark 执行原理详细说明
  1. MasterWorker 节点

    搭建 Spark 集群的时候我们就已经设置好了 Master 节点和 Worker 节点,一个集群有多个Master节点和多个Worker节点。

    • Master 节点常驻 Master 守护进程,负责管理 Worker 节点,我们从 Master 节点提交应用。

    • Worker 节点常驻 Worker 守护进程,与 Master 节点通信,并且管理 Executor 进程。

    一台机器可以同时作为 MasterWorker 节点(e.g. 有四台机器,可以选择一台设置为 Master节点,然后剩下三台设为 Worker节点,也可以把四台都设为 Worker 节点,这种情况下,有一个机器既是 Master 节点又是 Worker 节点)。

    一个 Spark 应用程序分为一个驱动程序 Driver 和多个执行程序 Executors 两种。

  2. DriverExecutor 进程
    Driver 进程就是应用的 main() 函数并且构建 SparkContext 对象,当我们提交了应用之后,便会启动一个对应的 Driver 进程,Driver 本身会根据我们设置的参数占有一定的资源(主要指 CPU CoreMemory)。

    根据部署模式的不同,Driver 可以运行在 Master 上,也可以运行 Worker上,Driver 与集群节点之间有频繁的通信。上图展示了 DriverMaster 上的部署的情况。

    • 如上图所示,Driver首先会向**集群管理者Cluster Manager**,如StandaloneYarnMesos 申请 Spark 应用所需的资源,也就是Executor,然后集群管理者会根据 Spark 应用所设置的参数在各个 Worker 上分配一定数量的 Executor,每个 Executor 都占用一定数量的 CPUMemory

    • 在申请到应用所需的资源以后,Driver 就开始调度和执行我们编写的应用代码了。Driver 进程会将我们编写的 Spark 应用代码拆分成多个 Stage,每个Stage 执行一部分代码片段,并为每个 Stage 创建一批 Tasks,然后将这些 Tasks分配到各个 Executor中执行。这一步即 Driver ---Task--> Worker

    • Executor 进程宿主在 Worker 节点上,一个 Worker可以有多个 Executor。每个 Executor 持有一个线程池,每个线程可以执行一个 TaskExecutor 执行完 Task 以后将结果返回给 Driver,每个 Executor 执行的 Task 都属于同一个应用。此外 Executor 还有一个功能就是为应用程序中要求缓存的 RDD 提供内存式存储,RDD 是直接缓存在 Executor进程内的,因此任务可以在运行时充分利用缓存数据加速运算。这一步即 Worker ---Result--> Driver

    • Driver 负责任务 Tasks 的分发结果Results的回收,即任务的调度。如果 Task 的计算结果非常大就不要回收了,会造成OOM(我们在执行程序时可以通过参数指定 Driver 的内存大小,如 1G,如果一个 Worker 的结果是 510M,那么两个接节点上的结果就会超过 1G,导致 OOM)。

2.2 RDD 算子

RDD有两种操作算子:分别为转换算子(Transformation) 和 **行动算子(Action)**。算子其实就是函数,只不过在 Scala 中称为算子。

下面表格列出了部分 RDD 算子,完整内容可以查看 [SparkRDD](RDD Programming Guide - Spark 3.0.1 Documentation (apache.org)) 文档。

算子类型 算子方法 算子转换
Transformations map(f: T=>U) RDD[T] => RDD[U]
filter(f: T=>Bool) RDD[T] => RDD[T]
flatMap(f: T=>Seq[U]) RDD[T] => RDD[U]
sample(fraction: Float) RDD[T] => RDD[T](Deterministic sampling)
groupByKey() RDD[(K, V)] => RDD[(K, Seq[V])]
reduceByKey(f: (V, V)=>V) RDD[(K, V)] => RDD[(K, V)]
union() (RDD[T], RDD[T]) => RDD[T]
join() (RDD[(K, V)], RDD[(K, W)]) => RDD[(K, (V, W))]
cogroup() (RDD[(K, V)], RDD[(K, W)]) => RDD([K, (Seq[V], Seq[W])])
crossProduct() (RDD[T], RDD[U]) => (RDD[(T, U)])
mapValues(f: V=>W) RDD[(K, V)] => RDD[(K, W)](Preserves Partitioning)
sort(c: Comparator[K]) RDD[(K, V)] => RDD[(K, V)]
partitionBy(p: Partitioner[K]) RDD[(K, V)] => RDD[(K, V)]
... ...
Actions count() RDD[T] => Long
collect() RDD[T] => Seq[T]
reduce(f: (T, T)=>T) RDD[T] => T
lookup(k: K) RDD[(K, V)] => Seq[V](On hash/range partitioned RDDs)
save(path: String) Outputs RDD to a Storage System, e.g. HDFS
foreach(func) -
... ...
2.2.1 Transformation 转换算子

点击快速跳转到转换算子列表:Transformations

Transformation 转换算子有延迟执行的特点,具有**懒加载(Lazy)**的特性。

下面列出常用的行动算子及用法:

  • map(func) 返回一个新的分布式数据集,由每个原元素经过func函数转换后组成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
object MapDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("MapDemo")
Logger.getLogger("org.apache.spark").setLevel(Level.OFF)
val sc = new SparkContext(conf)

val list = List(0, 10, 15, 20, 25, 30, 35, 40, 45, 50)
val listRDD = sc.parallelize(list, 5)
listRDD.foreach(println)
println()
val retRDD = listRDD.map(num => num * 7)
retRDD.foreach(num => println(num))

sc.stop()
}
}
  • mapPartition(func) 将函数用在每个RDD的分区上
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
object MapPartitionDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("MapPartitionDemo")
Logger.getLogger("org.apache.spark").setLevel(Level.OFF)
val sc = new SparkContext(conf)

val list = List(0, 10, 15, 20, 25, 30, 35, 40, 45, 50)
val listRDD = sc.parallelize(list, 3) // 设置三个分区

// 对比 map 和 mapPartition 的区别
listRDD.map( x => {
println("Map 执行一次")
x + 1
}).foreach(println)

println()

listRDD.mapPartitions( x => {
println("MapPartition 执行一次")
x.map(x => {println("mapPartition 里的map"); x + 1}) // 这个 map 不是 RDD 里的 map, 而是 Iterator 中的 map
}).foreach(println)
}
}
  • filter(func)返回一个新的数据集,由经过func函数后返回值为true的元素组成。
1
2
3
4
5
6
7
8
9
10
11
12
13
object FilterDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("FilterDemo")
Logger.getLogger("org.apache.spark").setLevel(Level.OFF)
val sc = new SparkContext(conf)

val list = List(0, 10, 15, 20, 25, 30, 35, 40, 45, 50)
val listRDD = sc.parallelize(list)
val retRDD = listRDD.filter(num => num % 2 == 0)
retRDD.foreach(println)
sc.stop()
}
}
  • flatMap(func)类似于map,但是每一个输入元素,会被映射为 0 到多个输出元素(因此,func 函数的返回值是一个 Seq,而不是单一元素)。
1
2
3
4
5
6
7
8
9
10
11
12
13
object FlatMapDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("FlatMapDemo")
Logger.getLogger("org.apache.spark").setLevel(Level.OFF)
val sc = new SparkContext(conf)

val list = List("hello you", "hello he", "hello me")
val listRDD = sc.parallelize(list)
val wordsRDD = listRDD.flatMap(line => line.split(" "))
wordsRDD.foreach(println)
sc.stop()
}
}
  • sample(withReplacement, frac, seed)根据给定的随机种子 seed,随机抽样出数量为 frac 的数据。
1

  • union(otherDataset)返回一个新的数据集,由原数据集和参数联合而成。
1

  • groupByKey([numTasks])在一个由 <K, V> 对组成的数据集上调用,返回一个 <K, Seq[V]> 对的数据集。注意:默认情况下,使用 8 个并行任务进行分组,你可以传入 numTask 可选参数,根据数据量设置不同数目的 Task。使用该算子可以将相同Key的元素聚集到一起,最终把所有相同Key的元素合并成一个元素,该元素的Key不变,Value则聚集到一个集合中。
1

  • reduceByKey(func, [numTasks])在一个<K, V>对的数据集上使用,返回一个<K, V>对的数据集,key相同的值,都被使用指定的reduce函数聚合到一起。和 groupByKey类似,任务的个数是可以通过第二个可选参数来配置的。
1

  • join(otherDataset, [numTasks])在类型为<K, V><K, W>类型的数据集上调用,返回一个<K, <V, W>>对,每个key中的所有元素都在一起的数据集。
  • sortByKey()sortBy()sortByKey() 函数需要在类型为 <K, V> 类型的数据集上调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
object SortByKeyDemo {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("SortByKeyDemo")
Logger.getLogger("org.apache.spark").setLevel(Level.OFF)
val sc = new SparkContext(conf)

val list = List(
"1,李 磊,22,175",
"2,刘银鹏,23,175",
"3,齐彦鹏,22,180",
"4,杨 柳,22,168",
"5,敦 鹏,20,175"
)
val listRDD: RDD[String] = sc.parallelize(list)
listRDD.foreach(println)
println()

// 3,齐彦鹏,22,180
// 1,李磊,22,175
// 2,刘银鹏,23,175
val resultRDD = listRDD.map(x => {
val fields = x.split(",")
println(fields(0) + " " + fields(1) + " " + fields(2) + " " + fields(3) + " ")
(fields(0), fields(1), fields(2), fields(3))
}).map(x => {(x._3, x._1)})

// 分区数会影响最终打印结果, 设置分区为 2, 所有分区最终会聚合为两个分区, 打印时显示每个分区的排序
// 这就可能导致打印输出时出现不同分区排序数据交叉的情况。
// 所以我们一般设置分区为 1, 表示排序结果聚合到一个分区
resultRDD.sortByKey(true, 2).foreach(println)

println()
val sortBy_RDD = listRDD.map(x => {
val fields = x.split(",")
// println(fields(0) + " " + fields(1) + " " + fields(2) + " " + fields(3) + " ")
(fields(0), fields(1), fields(2), fields(3))
})
println("SortBy_001")
sortBy_RDD.sortBy(_._1, true, 1).foreach(println)

println("SortBy_002")
sortBy_RDD.sortBy(_._2, true, 1).foreach(println)

println("SortBy_003")
sortBy_RDD.sortBy(_._3, true, 1).foreach(println)

println("SortBy_004")
sortBy_RDD.sortBy(_._4, true, 1).foreach(println)

sc.stop()
}
}
2.2.2 Action 行动算子

Action 行动算子具有触发执行的特点,一个 Application 应用程序有几个 Action 类算子执行,就有几个 Job 运行。

点击快速跳转到 SparkRDD Action 列表: Actions

下面列出常用的行动算子及用法:

  • reduce(func) 通过函数 func 聚集数据集中的所有元素。func 函数接受 2 个参数,返回 1 个值。这个函数必须是关联性的,确保可以被正确的并发执行。关于 reduce 的执行过程,可以对比 Scala 中类似的 reduce函数。

    不同于 Transformation 算子,执行后结果是RDD,执行 Action 算子之后,其结果不再是 RDD,而是一个标量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
object Action_Reduce {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local[2]").setAppName("Action_Reduce")
Logger.getLogger("org.apache.spark").setLevel(Level.OFF)
val sc = new SparkContext(conf)
// val list = List(0, 5, 15, 20, 25, 30) // 如果用这个 list 输出为: 95
val list = List(
"1,李 磊,22,175",
"2,刘银鹏,23,175",
"3,齐彦鹏,22,180",
"4,杨 柳,22,168",
"5,敦 鹏,20,175"
)
val listRDD = sc.parallelize(list)
listRDD.foreach(println)
println()
val ret = listRDD.reduce(
(v1, v2) => {
println("[LOGS] v1 > " + v1 + " ")
println("[LOGS] v2 > " + v2 + " ")
println("[LOGS] v1 + v2 > " + (v1 + v2))
v1 + v2
}
)

println("ret: " + ret)
sc.stop()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
输出如下:
3,齐彦鹏,22,180
1,李 磊,22,175
4,杨 柳,22,168
2,刘银鹏,23,175
5,敦 鹏,20,175

[LOGS] v1 > 3,齐彦鹏,22,180
[LOGS] v2 > 4,杨 柳,22,168
[LOGS] v1 + v2 > 3,齐彦鹏,22,1804,杨 柳,22,168
[LOGS] v1 > 3,齐彦鹏,22,1804,杨 柳,22,168
[LOGS] v2 > 5,敦 鹏,20,175
[LOGS] v1 + v2 > 3,齐彦鹏,22,1804,杨 柳,22,1685,敦 鹏,20,175
[LOGS] v1 > 1,李 磊,22,175
[LOGS] v2 > 2,刘银鹏,23,175
[LOGS] v1 + v2 > 1,李 磊,22,1752,刘银鹏,23,175
[LOGS] v1 > 1,李 磊,22,1752,刘银鹏,23,175
[LOGS] v2 > 3,齐彦鹏,22,1804,杨 柳,22,1685,敦 鹏,20,175
[LOGS] v1 + v2 > 1,李 磊,22,1752,刘银鹏,23,1753,齐彦鹏,22,1804,杨 柳,22,1685,敦 鹏,20,175
ret: 1,李 磊,22,1752,刘银鹏,23,1753,齐彦鹏,22,1804,杨 柳,22,1685,敦 鹏,20,175
  • collectDriver 的程序中,以数组的形式,返回数据集的所有元素。这通常会在使用 filter 或者其它操作后,返回一个足够小的数据子集再使用,直接将整个 RDDCollect返回,很可能会让 Driver 程序 OOM,这点尤其需要注意。
1

  • count
1

  • take
1

  • first
1

  • saveAsTextFile
1

  • foreach
1

  • saveAsNewAPIHadoopFile
1

2.2.3 其他算子

Spark中还有许多其他种类的算子,体现了 Spark 算子的灵活性,其中包括将数据进行持久化的算子。

  • cache:懒加载执行的,必须有一个 Action 触发算子触发执行。
  • persist:懒加载执行的,必须有一个 Action 触发算子触发执行。
  • checkpoint:算子不仅能将 RDD 持久化到磁盘,还能切断 RDD 之间的依赖关系。
2.2.3 宽依赖和窄依赖
  1. 宽依赖

    ① 子 RDD 的每个分区依赖于所有的父 RDD 分区

    ② 对单个 RDD 基于 Key进行重组和 Reduce,如 groupByKeyreduceByKey

    ③ 对两个 RDD 基于 Key 进行 join 和重组,如 join

    ④ 经过大量 shuffle 生成的 RDD,建议进行缓存。这样避免失败后重新计算带来的开销。

  2. 窄依赖

    ① 子RDD的每个分区依赖于常数个父分区(与数据规模无关)

    ② 输入输出一对一的算子,且结果RDD的分区结构不变。主要是map/flatmap

    ③ 输入输出一对一的算子,但结果RDD的分区结构发生了变化,如union/coalesce

    ④ 从输入中选择部分元素的算子,如 filterdistinctsubstractsample

SpringBoot-Annotation

Java 注解

[TOC]

🍭 1. 注解的分类

通常来说注解分为以下三类:

分类 说明
元注解 Java 内置的注解,标明该注解的使用范围、生命周期等。
标准注解 Java 提供的基础注解,标明过期的元素/标明是复写父类方法的方法/标明抑制警告。
自定义注解 第三方定义的注解,含义和功能由第三方来定义和实现。

1.1 元注解

用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。一般我们说“元**”,都代表最基本最原始的东西。因此,元注解就是最基本不可分解的注解,我们不能去改变它,只能使用它来定义自定义的注解。

元注解包含以下五种:@Retention@Target@Documented@Inherited@Repeatable,其中最常用的是 @Retention@Target 下面分别介绍一下这五种元注解。

注解 说明
@Retention 从编写 Java 代码到运行主要周期为 源文件Class文件运行时数据@Retention 则标注了自定义注解的信息要保留到哪个阶段,分别对应的 value 取值为SOURCECLASSRUNTIME
@Documented

参考文章

Java 注解入门到精通,这一篇就够了 - CSDN博客

OnlineTravelBigdataPlatform

🌳 在线旅游大数据平台项目

100%

日期任务清单

  • 项目开始日期: 2024-06-11

    • 了解项目的背景以及整个系统的架构
    • 了解系统需要完成的主要功能
    • 了解系统整个架构
    • 完成数据服务端的部署
    • 完成数据客户端的部署
    • 了解数据集
    • 认识消息队列 Kafka
    • 完成消息队列 Kafka 的部署
    • 了解消息队列 Kafka 的基本应用
    • 使用 Flume 收集数据到 Kafka
  • 2024-06-12

    • 了解实时数据分析所用到的技术
    • 了解 SparkStreamingFlink
    • 了解 SparkStreaming 的核心概念
    • 了解数据源
    • 借助netcat实践Kafka
    • 了解转换操作
    • 具体实施任务:处理数据
  • 2024-06-13

    • 了解数据库连接池
    • 了解如何向 MySQL 数据库中写入数据
    • 借助 alibaba Druid 库实现一个数据库连接工具类
    • 编写案例:WordCount
    • 具体实施任务,将代码中生成的数据写入MySQL
  • 2024-06-14

    • 了解什么是 Kafka Offset
    • 维护 Kafka Offset
    • 具体实施任务,将 Kafka 的 Offset 配合 MySQL 用代码进行维护。
  • 2024-06-17

    • 进行后端开发
    • 进行前端开发
  • 2024-06-18

    • 进行热力图的绘制
    • 进行人流量柱状图的绘制
    • 进行人流量趋势图的绘制
  • 2024-06-19 项目结束日期

文件目录

[TOC]

阅读更多