Dunwu Blog

大道至简,知易行难

CAT 快速入门

CAT 简介

CAT(Central Application Tracking),是基于 Java 开发的分布式实时监控系统。CAT 在基础存储、高性能通信、大规模在线访问、服务治理、实时监控、容器化及集群智能调度等领域提供业界领先的、统一的解决方案。CAT 目前在美团的产品定位是应用层的统一监控组件,基本接入了美团所有核心应用,在中间件(RPC、数据库、缓存、MQ 等)框架中得到广泛应用,为各业务线提供系统的性能指标、健康状况、实时告警等。

CAT 的优势

  • 实时处理:信息的价值会随时间锐减,尤其是事故处理过程中
  • 全量数据:最开始的设计目标就是全量采集,全量的好处有很多
  • 高可用:所有应用都倒下了,需要监控还站着,并告诉工程师发生了什么,做到故障还原和问题定位
  • 故障容忍:CAT 本身故障不应该影响业务正常运转,CAT 挂了,应用不该受影响,只是监控能力暂时减弱
  • 高吞吐:要想还原真相,需要全方位地监控和度量,必须要有超强的处理吞吐能力
  • 可扩展:支持分布式、跨 IDC 部署,横向扩展的监控系统

支持的消息类型

CAT 监控系统将每次 URL、Service 的请求内部执行情况都封装为一个完整的消息树、消息树可能包括 Transaction、Event、Heartbeat、Metric 等信息。

  • Transaction 适合记录跨越系统边界的程序访问行为,比如远程调用,数据库调用,也适合执行时间较长的业务逻辑监控,Transaction 用来记录一段代码的执行时间和次数
  • Event 用来记录一件事发生的次数,比如记录系统异常,它和 transaction 相比缺少了时间的统计,开销比 transaction 要小
  • Heartbeat 表示程序内定期产生的统计信息, 如 CPU 利用率, 内存利用率, 连接池状态, 系统负载等
  • Metric 用于记录业务指标、指标可能包含对一个指标记录次数、记录平均值、记录总和,业务指标最低统计粒度为 1 分钟

img

CAT 部署

Cat 部署可以参考 官方 Wiki - 服务端部署 ,非常详细,不赘述。

CAT 报表

与其他监控工具(如 Zipkin、SkyWalking)相比,CAT 的报表功能最丰富。支持以下报表类型:

  • Transaction 报表 - 一段代码运行时间、次数,比如 URL、Cache、SQL 执行次数和响应时间
  • Event 报表 - 一行代码运行次数,比如出现一个异常
  • Problem 报表 - 根据 Transaction/Event 数据分析出来系统可能出现的异常,包括访问较慢的程序等
  • Heartbeat 报表 - JVM 内部一些状态信息,比如 Memory,Thread 等
  • Business 报表 - 业务监控报表,比如订单指标,支付等业务指标

CAT 配置

CAT 提供了以下配置:

  • 项目配置 包括项目基本信息、机器分组配置
  • 告警配置 包括基本告警配置、告警规则、以及具体告警配置
  • 全局配置 包括服务端配置、消息采样配置、客户端路由
  • 业务指标 包括业务监控配置、业务标签配置

CAT 架构

CAT 主要分为三个模块:

  • cat-client - 提供给业务以及中间层埋点的底层 SDK。
  • cat-consumer - 用于实时分析从客户端的提供的数据。
  • cat-home - 作为用户提供给用户的展示的控制端。

在实际开发和部署中,cat-consumer 和 cat-home 是部署在一个 jvm 内部,每个 CAT 服务端都可以作为 consumer 也可以作为 home,这样既能减少整个 CAT 层级结构,也可以增加整个系统稳定性。

img

上图是 CAT 目前多机房的整体结构图:

  • 路由中心是根据应用所在机房信息来决定客户端上报的 CAT 服务端地址
  • 每个机房内部都有的独立的原始信息存储集群 HDFS
  • cat-home 可以部署在一个机房也可以部署在多个机房,在做报表展示的时候,cat-home 会从 cat-consumer 中进行跨机房的调用,将所有的数据合并展示给用户
  • 实际过程中,cat-consumer、cat-home 以及路由中心都是部署在一起,每个服务端节点都可以充当任何一个角色

参考资料

时间服务器 - NTP

NTP 简介

网络时间协议(英语:Network Time Protocol,缩写:NTP)是在数据网络潜伏时间可变的计算机系统之间通过分组交换进行时钟同步的一个网络协议,位于 OSI 模型的应用层。自 1985 年以来,NTP 是目前仍在使用的最古老的互联网协议之一。NTP 由特拉华大学的 David L. Mills(英语:David L. Mills)设计。

NTP 意图将所有参与计算机的协调世界时(UTC)时间同步到几毫秒的误差内

NTP 要点:

  • 地球共有 24 个时区,而以格林威治时间 (GMT) 为标准时间;
  • 中国本地时间为 GMT +8 小时;
  • 最准确的时间为使用原子钟 (Atomic clock) 所计算的,例如 UTC (Coordinated Universal Time) 就是一例;
  • Linux 系统本来就有两种时间,一种是 Linux 以 1970/01/01 开始计数的系统时间,一种则是 BIOS 记载的硬件时间;
  • Linux 可以透过网络校时,最常见的网络校时为使用 NTP 服务器,这个服务启动在 udp port 123
  • 时区档案主要放置于 /usr/share/zoneinfo/ 目录下,而本地时区则参考 /etc/localtime
  • NTP 服务器为一种阶层式的服务,所以 NTP 服务器本来就会与上层时间服务器作时间的同步化, 因此 nptdntpdate 两个指令不可同时使用;
  • NTP 服务器的联机状态可以使用 ntpstatntpq -p 来查询;
  • NTP 提供的客户端软件为 ntpdate 这个指令;
  • 在 Linux 下想要手动处理时间时,需以 date 设定时间后,以 hwclock -w 来写入 BIOS 所记录的时间。
  • NTP 服务器之间的时间误差不可超过 1000 秒,否则 NTP 服务会自动关闭。

更多 NTP 详情可以参考:鸟哥的 Linux 私房菜– NTP 时间服务器

ntpd 服务

环境:CentOS

yum 安装

CentOS 安装 NTP 很简单,执行以下命令即可:

1
yum -y install ntp

ntpd 配置

ntp 的配置文件路径为: /etc/ntp.conf ,参考配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 先处理权限方面的问题,包括放行上层服务器以及开放区网用户来源:
# restrict default kod nomodify notrap nopeer noquery # 拒绝 IPv4 的用户
# restrict -6 default kod nomodify notrap nopeer noquery # 拒绝 IPv6 的用户
restrict default nomodify notrap nopeer noquery
#restrict 192.168.100.0 mask 255.255.255.0 nomodify # 放行同局域网来源(根据网关和子网掩码决定)
restrict 127.0.0.1 # 默认值,放行本机 IPv4 来源
restrict ::1 # 默认值,放行本机 IPv6 来源

# 2. 设定 NTP 主机来源
# 注释掉默认 NTP 来源
# server 0.centos.pool.ntp.org iburst
# server 1.centos.pool.ntp.org iburst
# server 2.centos.pool.ntp.org iburst
# server 3.centos.pool.ntp.org iburst
# 设置国内 NTP 来源
server cn.pool.ntp.org prefer # 以这个主机为优先
server ntp1.aliyun.com
server ntp.sjtu.edu.cn

# 3. 预设时间差异分析档案与暂不用到的 keys 等,不需要更改它:
driftfile /var/lib/ntp/drift
keys /etc/ntp/keys
includefile /etc/ntp/crypto/pw

注意:如果更改配置,必须重启 NTP 服务(systemctl restart ntpd)才能生效。

放开防火墙限制

NTP 服务的端口是 123,使用的是 udp 协议,所以 NTP 服务器的防火墙必须对外开放 udp 123 这个端口。

如果防火墙使用 **iptables**,执行以下命令:

1
iptables -A INPUT -p UDP -i eth0 -s 192.168.0.0/24 --dport 123 -j ACCEPT

如果防火墙使用 **firewalld**,执行以下命令:

1
firewall-cmd --zone=public --add-port=123/udp --permanent

ntpd 服务命令

1
2
3
4
5
6
7
systemctl enable ntpd.service  # 开启服务(开机自动启动服务)
systemctl disable ntpd.service # 关闭服务(开机不会自动启动服务)
systemctl start ntpd.service # 启动服务
systemctl stop ntpd.service # 停止服务
systemctl restart ntpd.service # 重启服务
systemctl reload ntpd.service # 重新载入配置
systemctl status ntpd.service # 查看服务状态

查看 ntp 服务状态

验证 NTP 服务正常工作

执行 ntpstat 可以查看 ntp 服务器有无和上层 ntp 连通,,如果成功,可以看到类似以下的内容:

1
2
3
4
$ ntpstat
synchronised to NTP server (5.79.108.34) at stratum 3
time correct to within 1129 ms
polling server every 64 s

查看 ntp 服务器与上层 ntp 的状态

1
2
3
4
5
6
ntpq -p
remote refid st t when poll reach delay offset jitter
==============================================================================
*ntp1.ams1.nl.le 130.133.1.10 2 u 36 64 367 230.801 5.271 2.791
120.25.115.20 10.137.53.7 2 u 33 64 377 25.930 15.908 3.168
time.cloudflare 10.21.8.251 3 u 31 64 367 251.109 16.976 3.264

ntpdate 命令

注意:NTP 服务器为一种阶层式的服务,所以 NTP 服务器本来就会与上层时间服务器作时间的同步化, 因此 nptdntpdate 两个指令不可同时使用。

手动执行时间同步

ntpdate 命令是 NTP 的客户端软件,它可以用于请求时间同步。

语法:

1
/usr/sbin/ntpdate <ntp_server>

ntp_server 可以从 [国内 NTP 服务器](#国内 NTP 服务器) 中选择。

示例:

1
2
$ ntpdate cn.pool.ntp.org
11 Feb 10:47:12 ntpdate[30423]: step time server 84.16.73.33 offset -49.894774 sec

自动定时同步时间

如果需要自动定时同步时间,可以利用 Crontab 工具。本质就是用 crontab 定时执行一次手动时间同步命令 ntp。

示例:执行如下命令,就可以在每天凌晨 3 点同步系统时间:

1
2
echo "0 3 * * * /usr/sbin/ntpdate cn.pool.ntp.org" >> /etc/crontab # 修改 crond 服务配置
systemctl restart crond # 重启 crond 服务以生效

四、国内 NTP 服务器

以下 NTP 服务器搜集自网络:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cn.pool.ntp.org  # 最常用的国内NTP服务器,参考:https://www.ntppool.org/zh/use.html
cn.ntp.org.cn # 中国
edu.ntp.org.cn # 中国教育网
ntp1.aliyun.com # 阿里云
ntp2.aliyun.com # 阿里云
ntp.sjtu.edu.cn # 上海交通大学
s1a.time.edu.cn # 北京邮电大学
s1b.time.edu.cn # 清华大学
s1c.time.edu.cn # 北京大学
s1d.time.edu.cn # 东南大学
s1e.time.edu.cn # 清华大学
s2a.time.edu.cn # 清华大学
s2b.time.edu.cn # 清华大学
s2c.time.edu.cn # 北京邮电大学
s2d.time.edu.cn # 西南地区网络中心
s2e.time.edu.cn # 西北地区网络中心
s2f.time.edu.cn # 东北地区网络中心
s2g.time.edu.cn # 华东南地区网络中心
s2h.time.edu.cn # 四川大学网络管理中心
s2j.time.edu.cn # 大连理工大学网络中心
s2k.time.edu.cn # CERNET桂林主节点

参考资料

防火墙 - Firewalld

firewalld 服务命令

1
2
3
4
5
6
7
systemctl enable firewalld.service  # 开启服务(开机自动启动服务)
systemctl disable firewalld.service # 关闭服务(开机不会自动启动服务)
systemctl start firewalld.service # 启动服务
systemctl stop firewalld.service # 停止服务
systemctl restart firewalld.service # 重启服务
systemctl reload firewalld.service # 重新载入配置
systemctl status firewalld.service # 查看服务状态

firewall-cmd 命令

firewall-cmd 命令用于配置防火墙。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
firewall-cmd --version                    # 查看版本
firewall-cmd --help # 查看帮助
firewall-cmd --state # 显示状态
firewall-cmd --reload # 更新防火墙规则
firewall-cmd --get-active-zones # 查看区域信息
firewall-cmd --get-zone-of-interface=eth0 # 查看指定接口所属区域
firewall-cmd --panic-on # 拒绝所有包
firewall-cmd --panic-off # 取消拒绝状态
firewall-cmd --query-panic # 查看是否拒绝

firewall-cmd --zone=public --list-ports # 查看所有打开的端口
firewall-cmd --zone=public --query-port=80/tcp # 查看是否有开放的 80 TCP 端口
firewall-cmd --zone=public --add-port=8080/tcp --permanent # 添加开放端口(--permanent永久生效,没有此参数重启后失效)
firewall-cmd --zone=public --remove-port=80/tcp --permanent # 永久删除开放的 80 TCP 端口

参考资料

定时任务 - crontab

环境:CentOS

通过 crontab 命令,我们可以在固定的间隔时间执行指定的系统指令或 shell script 脚本。时间间隔的单位可以是分钟、小时、日、月、周及以上的任意组合。这个命令非常适合周期性的日志分析或数据备份等工作。

crond 服务

Linux 通过 crond 服务来支持 crontab。

检查 crond 服务

使用 systemctl list-unit-files 命令确认 crond 服务是否已安装。

1
2
$ systemctl list-unit-files | grep crond
crond.service enabled

如果为 enabled,表示服务正运行。

crond 服务命令

开机自动启动 crond 服务:chkconfig crond on

或者,按以下命令手动启动:

1
2
3
4
5
6
7
systemctl enable crond.service  # 开启服务(开机自动启动服务)
systemctl disable crond.service # 关闭服务(开机不会自动启动服务)
systemctl start crond.service # 启动服务
systemctl stop crond.service # 停止服务
systemctl restart crond.service # 重启服务
systemctl reload crond.service # 重新载入配置
systemctl status crond.service # 查看服务状态

crontab

crontab 命令

crontab 命令格式如下:

1
crontab [-u user] file crontab [-u user] [ -e | -l | -r ]

说明:

  • -u user:用来设定某个用户的 crontab 服务;
  • file:file 是命令文件的名字,表示将 file 做为 crontab 的任务列表文件并载入 crontab。如果在命令行中没有指定这个文件,crontab 命令将接受标准输入(键盘)上键入的命令,并将它们载入 crontab。
  • -e:编辑某个用户的 crontab 文件内容。如果不指定用户,则表示编辑当前用户的 crontab 文件。
  • -l:显示某个用户的 crontab 文件内容,如果不指定用户,则表示显示当前用户的 crontab 文件内容。
  • -r:从/var/spool/cron 目录中删除某个用户的 crontab 文件,如果不指定用户,则默认删除当前用户的 crontab 文件。
  • -i:在删除用户的 crontab 文件时给确认提示。

有两种方法写入定时任务:

  • 在命令行输入:crontab -e 然后添加相应的任务,存盘退出。
  • 直接编辑 /etc/crontab 文件,即 vi /etc/crontab,添加相应的任务。

crontab 文件

crontab 要执行的定时任务都被保存在 /etc/crontab 文件中。

crontab 的文件格式如下:

img

标准字段

逗号用于分隔列表。例如,在第 5 个字段(星期几)中使用 MON,WED,FRI 表示周一、周三和周五。

连字符定义范围。例如,2000-2010 表示 2000 年至 2010 年期间的每年,包括 2000 年和 2010 年。

除非用反斜杠()转义,否则命令中的**百分号(%)**会被替换成换行符,第一个百分号后面的所有数据都会作为标准输入发送给命令。

字段 是否必填 允许值 允许特殊字符
Minutes 0–59 *,-
Hours 0–23 *,-
Day of month 1–31 *,-
Month 1–12 or JAN–DEC *,-
Day of week 0–6 or SUN–SAT *,-

/etc/crontab 文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# For details see man 4 crontabs

# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed

# 每两个小时以root身份执行 /home/hello.sh 脚本
0 */2 * * * root /home/hello.sh

crontab 实例

实例 1:每 1 分钟执行一次 myCommand

1
* * * * * myCommand

实例 2:每小时的第 3 和第 15 分钟执行

1
3,15 * * * * myCommand

实例 3:在上午 8 点到 11 点的第 3 和第 15 分钟执行

1
3,15 8-11 * * * myCommand

实例 4:每隔两天的上午 8 点到 11 点的第 3 和第 15 分钟执行

1
3,15 8-11 */2  *  * myCommand

实例 5:每周一上午 8 点到 11 点的第 3 和第 15 分钟执行

1
3,15 8-11 * * 1 myCommand

实例 6:每晚的 21:30 重启 smb

1
30 21 * * * /etc/init.d/smb restart

实例 7:每月 1、10、22 日的 4 : 45 重启 smb

1
45 4 1,10,22 * * /etc/init.d/smb restart

实例 8:每周六、周日的 1 : 10 重启 smb

1
10 1 * * 6,0 /etc/init.d/smb restart

实例 9:每天 18 : 00 至 23 : 00 之间每隔 30 分钟重启 smb

1
0,30 18-23 * * * /etc/init.d/smb restart

实例 10:每星期六的晚上 11 : 00 pm 重启 smb

1
0 23 * * 6 /etc/init.d/smb restart

实例 11:每一小时重启 smb

1
0 * * * * /etc/init.d/smb restart

实例 12:晚上 11 点到早上 7 点之间,每隔一小时重启 smb

1
0 23-7 * * * /etc/init.d/smb restart

参考资料

效率提升方法论

在智力水平相当的前提下,常常会发现:有些人做事,事倍功半;有些人做事,事半功倍。

做任何事,如果有了清晰的思路,正确的指导方针,肯定是比毫无头绪要高效很多。所以,现实中,常常会看到这样一种现象,优秀的人,往往全面优秀,干什么都出彩;而平庸的人,做什么都出不了成绩。

大多数人不是天才,想要变得优秀,唯一的途径就是:按照正确的习惯(方式方法),坚持不懈的努力进步(自律)。

我们日复一日做的事情,决定了我们是怎样的人。因此所谓卓越,并非指行为,而是习惯

We are what we repeatedly do. Excellence, then, is not an act, but a habit.

——莎士比亚

做计划常用方法

名称 图示 方式 优点 缺点
时间法 时间顺序 清晰明确
操作性强
时间分配不合理或
突出情况容易打乱计划
清单法 重要程度 要事优先
事无遗漏
未完成容易形成压力;
容易造成形式主义,为打卡而完成
三段法 完成状态 态度明了
有条不紊
灵活性差,只能电子版或软件实现编辑,
纸质版事项会重复
OKR 法 目标分解 目标导向
高效成事
适合复杂事情或大项目的分解执行跟进
分类法 八个方面 事事周全
面面俱到
越是想顾周全,越难周全,
面面俱到,也会事事难完成
四象限法 img 轻重缓急 要事优先
忽略次要
被要事牵着走,忽略了
人生应该适度娱乐的重要性
甘特图法 日期进度 进度直观
易于理解
进度条只能反映时间进度,
无法反映事项具体完成情况的进度
PDCA 法 流程顺序 流程推进
循环解决
流程化容易形成思维惯性,
并且缺乏压力难以形成创造性

W2H

5W2H 分析法是一种思考问题的启发式思维方式。5W2H 分析法用五个以 W 开头的英语单词和两个以 H 开头的英语单词进行设问,得到关键性问题的答案,最后总结归纳出问题的目标、解决思路、处理方法等,这就叫做 5W2H 法。

5W2H 分析法又叫七问分析法,是二战中美国陆军兵器修理部首创。这种分析法广泛用于企业管理和技术活动,对于决策和执行性的活动措施也非常有帮助,也有助于弥补考虑问题的疏漏。

5W2H 分析法的意义在于:避免遇到一个问题后,不知从何入手。通过设问方式,由点成线,由线成面,把问题的关键点串联起来,整理出问题的解决思路。

5W2H

  • why - 为什么?为什么要这么做?理由何在?原因是什么?
  • what - 是什么?目的是什么?作什么工作?
  • where - 何处?在哪里做?从哪里入手?
  • when - 何时?什么时间完成?什么时机最适宜?
  • who - 谁?有谁来承担?谁来完成?谁负责?
  • how - 怎么做?如何提高效率?如何实施?方法怎么样?
  • how much - 多少?做到什么程度?数量如何?质量水平如何?费用产出如何?

四象限原则

四象限原则是一种时间管理方式

有首歌唱出了大多数职场人的心声:时间都去哪儿了?

事情、任务太多,时间太少,分身乏术。

时间管理四象限法则是美国的管理学家科维提出的一个时间管理的理论,按处理顺序划分为:紧急又重要、重要不紧急、紧急不重要、不紧急不重要。

img

  • 第一象限(重要而紧急

    • 案例:应付难缠的客户、准时完成工作、住院开刀等等。
    • 这是考验我们的经验、判断力的时刻,也是可以用心耕耘的园地。如果荒废了,我们很会可能变成行尸走肉。但我们也不能忘记,很多重要的事都是因为一拖再拖或事前准备不足,而变成迫在眉睫。
    • 该象限的本质是缺乏有效的工作计划导致本处于“重要但不紧急”第二象限的事情转变过来的,这也是传统思维状态下的管理者的通常状况,就是“忙”。
  • 第二象限(重要但不紧急)

    • 案例:学习新技能、建立人际关系、保持身体健康、长期的规划、问题的发掘与预防、参加培训、向上级提出问题处理的建议等等事项。
    • 荒废这个领域将使第一象限日益扩大,使我们陷入更大的压力,在危机中疲于应付。反之,多投入一些时间在这个领域有利于提高实践能力,缩小第一象限的范围。做好事先的规划、准备与预防措施,很多急事将无从产生。这个领域的事情不会对我们造成催促力量,所以必须主动去做,这是发挥个人领导力的领域。
    • 这更是传统低效管理者与高效卓越管理者的重要区别标志,建议管理者要把 80%的精力投入到该象限的工作,以使第一象限的“急”事无限变少,不再瞎“忙”。
  • 第三象限(紧急但不重要)

    • 案例:电话、会议、突发的访客都属于这一类。
    • 表面看似第一象限,因为迫切的呼声会让我们产生“这件事很重要”的错觉——实际上就算重要也是对别人而言。我们花很多时间在这个里面打转,自以为是在第一象限,其实不过是在满足别人的期望与标准。
  • 第四象限(不紧急也不重要)

    • 案例:阅读无聊小说、看毫无内容的电视节目、办公室聊天、刷微博、刷朋友圈等。
    • 简而言之就是浪费生命,所以根本不值得花半点时间在这个象限。但我们往往在一、三象限来回奔走,忙得焦头烂额,不得不到第四象限去疗养一番再出发。这部分范围倒不见得都是休闲活动,因为真正有创造意义的休闲活动是很有价值的。然而像阅读令人上瘾的无聊小说、毫无内容的电视节目、办公室聊天等。这样的休息不但不是为了走更长的路,反而是对身心的毁损,刚开始时也许有滋有味,到后来你就会发现其实是很空虚的。

HBase 快速入门

HBase 简介

为什么需要 HBase

在介绍 HBase 之前,我们不妨先了解一下为什么需要 HBase,或者说 HBase 是为了达到什么目的而产生。

在 HBase 诞生之前,Hadoop 可以通过 HDFS 来存储结构化、半结构甚至非结构化的数据,它是传统数据库的补充,是海量数据存储的最佳方法,它针对大文件的存储,批量访问和流式访问都做了优化,同时也通过多副本解决了容灾问题。

Hadoop 的缺陷在于:它只能执行批处理,并且只能以顺序方式访问数据。这意味着即使是最简单的工作,也必须搜索整个数据集,即:Hadoop 无法实现对数据的随机访问。实现数据的随机访问是传统的关系型数据库所擅长的,但它们却不能用于海量数据的存储。在这种情况下,必须有一种新的方案来同时解决海量数据存储和随机访问的问题,HBase 就是其中之一 (HBase,Cassandra,couchDB,Dynamo 和 MongoDB 都能存储海量数据并支持随机访问)。

注:数据结构分类:

  • 结构化数据:即以关系型数据库表形式管理的数据;
  • 半结构化数据:非关系模型的,有基本固定结构模式的数据,例如日志文件、XML 文档、JSON 文档、Email 等;
  • 非结构化数据:没有固定模式的数据,如 WORD、PDF、PPT、EXL,各种格式的图片、视频等。

什么是 HBase

HBase 是一个构建在 HDFS(Hadoop 文件系统)之上的列式数据库

HBase 是一种类似于 Google’s Big Table 的数据模型,它是 Hadoop 生态系统的一部分,它将数据存储在 HDFS 上,客户端可以通过 HBase 实现对 HDFS 上数据的随机访问。

img

HBase 的核心特性如下:

  • 分布式
    • 伸缩性:支持通过增减机器进行水平扩展,以提升整体容量和性能
    • 高可用:支持 RegionServers 之间的自动故障转移
    • 自动分区:Region 分散在集群中,当行数增长的时候,Region 也会自动的分区再均衡
  • 超大数据集:HBase 被设计用来读写超大规模的数据集(数十亿行至数百亿行的表)
  • 支持结构化、半结构化和非结构化的数据:由于 HBase 基于 HDFS 构建,所以和 HDFS 一样,支持结构化、半结构化和非结构化的数据
  • 非关系型数据库
    • 不支持标准 SQL 语法
    • 没有真正的索引
    • 不支持复杂的事务:只支持行级事务,即单行数据的读写都是原子性的

HBase 的其他特性

  • 读写操作遵循强一致性
  • 过滤器支持谓词下推
  • 易于使用的 Java 客户端 API
  • 它支持线性和模块化可扩展性。
  • HBase 表支持 Hadoop MapReduce 作业的便捷基类
  • 很容易使用 Java API 进行客户端访问
  • 为实时查询提供块缓存 BlockCache 和布隆过滤器
  • 它通过服务器端过滤器提供查询谓词下推

什么时候使用 HBase

根据上一节对于 HBase 特性的介绍,我们可以梳理出 HBase 适用、不适用的场景:

HBase 不适用场景:

  • 需要索引
  • 需要复杂的事务
  • 数据量较小(比如:数据量不足几百万行)

HBase 适用场景:

  • 能存储海量数据并支持随机访问(比如:数据量级达到十亿级至百亿级)
  • 存储结构化、半结构化数据
  • 硬件资源充足

一言以蔽之——HBase 适用的场景是:实时地随机访问超大数据集

HBase 的典型应用场景

  • 存储监控数据
  • 存储用户/车辆 GPS 信息
  • 存储用户行为数据
  • 存储各种日志数据,如:访问日志、操作日志、推送日志等。
  • 存储短信、邮件等消息类数据
  • 存储网页数据

HBase 数据模型简介

前面已经提及,HBase 是一个列式数据库,其数据模型和关系型数据库有所不同。其数据模型的关键术语如下:

  • Table:HBase 表由多行组成。
  • Row:HBase 中的一行由一个行键和一个或多个列以及与之关联的值组成。 行在存储时按行键的字母顺序排序。 为此,行键的设计非常重要。 目标是以相关行彼此靠近的方式存储数据。 常见的行键模式是网站域。 如果您的行键是域,您应该将它们反向存储(org.apache.www、org.apache.mail、org.apache.jira)。 这样,所有 Apache 域在表中彼此靠近,而不是根据子域的第一个字母展开。
  • Column:HBase 中的列由列族和列限定符组成,它们由 :(冒号)字符分隔。
  • Column Family:通常出于性能原因,列族在物理上将一组列及其值放在一起。 每个列族都有一组存储属性,例如它的值是否应该缓存在内存中,它的数据是如何压缩的,它的行键是如何编码的,等等。 表中的每一行都有相同的列族,尽管给定的行可能不在给定的列族中存储任何内容。
  • 列限定符:将列限定符添加到列族以提供给定数据片段的索引。 给定列族内容,列限定符可能是 content:html,另一个可能是 content:pdf。 尽管列族在表创建时是固定的,但列限定符是可变的,并且行之间可能有很大差异。
  • Cell:单元格是行、列族和列限定符的组合,包含一个值和一个时间戳,代表值的版本。
  • Timestamp:时间戳写在每个值旁边,是给定版本值的标识符。 默认情况下,时间戳表示写入数据时 RegionServer 上的时间,但您可以在将数据放入单元格时指定不同的时间戳值。

img

特性比较

HBase vs. RDBMS

RDBMS HBase
RDBMS 有它的模式,描述表的整体结构的约束 HBase 无模式,它不具有固定列模式的概念;仅定义列族
支持的文件系统有 FAT、NTFS 和 EXT 支持的文件系统只有 HDFS
使用提交日志来存储日志 使用预写日志 (WAL) 来存储日志
使用特定的协调系统来协调集群 使用 ZooKeeper 来协调集群
存储的都是中小规模的数据表 存储的是超大规模的数据表,并且适合存储宽表
通常支持复杂的事务 仅支持行级事务
适用于结构化数据 适用于半结构化、结构化数据
使用主键 使用 row key

HBase vs. HDFS

HDFS HBase
HDFS 提供了一个用于分布式存储的文件系统。 HBase 提供面向表格列的数据存储。
HDFS 为大文件提供优化存储。 HBase 为表格数据提供了优化。
HDFS 使用块文件。 HBase 使用键值对数据。
HDFS 数据模型不灵活。 HBase 提供了一个灵活的数据模型。
HDFS 使用文件系统和处理框架。 HBase 使用带有内置 Hadoop MapReduce 支持的表格存储。
HDFS 主要针对一次写入多次读取进行了优化。 HBase 针对读/写许多进行了优化。

行式数据库 vs. 列式数据库

行式数据库 列式数据库
对于添加/修改操作更高效 对于读取操作更高效
读取整行数据 仅读取必要的列数据
最适合在线事务处理系统(OLTP) 不适合在线事务处理系统(OLTP)
将行数据存储在连续的页内存中 将列数据存储在非连续的页内存中

列式数据库的优点:

  • 支持数据压缩
  • 支持快速数据检索
  • 简化了管理和配置
  • 有利于聚合查询(例如 COUNT、SUM、AVG、MIN 和 MAX)的高性能
  • 分区效率很高,因为它提供了自动分片机制的功能,可以将较大的区域分配给较小的区域

列式数据库的缺点:

  • JOIN 查询和来自多个表的数据未优化
  • 必须为频繁的删除和更新创建拆分,因此降低了存储效率
  • 由于非关系数据库的特性,分区和索引的设计非常困难

HBase 安装

HBase Hello World 示例

  1. 连接 HBase

    在 HBase 安装目录的 /bin 目录下执行 hbase shell 命令进入 HBase 控制台。

    1
    2
    $ ./bin/hbase shell
    hbase(main):001:0>
  2. 输入 help 可以查看 HBase Shell 命令。

  3. 创建表

    可以使用 create 命令创建一张新表。必须要指定表名和 Column Family。

    1
    2
    3
    4
    hbase(main):001:0> create 'test', 'cf'
    0 row(s) in 0.4170 seconds

    => Hbase::Table - test
  4. 列出表信息

    使用 list 命令来确认新建的表已存在。

    1
    2
    3
    4
    5
    6
    hbase(main):002:0> list 'test'
    TABLE
    test
    1 row(s) in 0.0180 seconds

    => ["test"]

    可以使用 describe 命令可以查看表的细节信息,包括配置信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    hbase(main):003:0> describe 'test'
    Table test is ENABLED
    test
    COLUMN FAMILIES DESCRIPTION
    {NAME => 'cf', VERSIONS => '1', 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 => 'f
    alse', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE
    => '65536'}
    1 row(s)
    Took 0.9998 seconds
  5. 向表中写数据

    可以使用 put 命令向 HBase 表中写数据。

    1
    2
    3
    4
    5
    6
    7
    8
    hbase(main):003:0> put 'test', 'row1', 'cf:a', 'value1'
    0 row(s) in 0.0850 seconds

    hbase(main):004:0> put 'test', 'row2', 'cf:b', 'value2'
    0 row(s) in 0.0110 seconds

    hbase(main):005:0> put 'test', 'row3', 'cf:c', 'value3'
    0 row(s) in 0.0100 seconds
  6. 一次性扫描表的所有数据

    使用 scan 命令来扫描表数据。

    1
    2
    3
    4
    5
    6
    hbase(main):006:0> scan 'test'
    ROW COLUMN+CELL
    row1 column=cf:a, timestamp=1421762485768, value=value1
    row2 column=cf:b, timestamp=1421762491785, value=value2
    row3 column=cf:c, timestamp=1421762496210, value=value3
    3 row(s) in 0.0230 seconds
  7. 查看一行数据

    使用 get 命令可以查看一行表数据。

    1
    2
    3
    4
    hbase(main):007:0> get 'test', 'row1'
    COLUMN CELL
    cf:a timestamp=1421762485768, value=value1
    1 row(s) in 0.0350 seconds
  8. 禁用表

    如果想要删除表或修改表设置,必须先使用 disable 命令禁用表。如果想再次启用表,可以使用 enable 命令。

    1
    2
    3
    4
    5
    hbase(main):008:0> disable 'test'
    0 row(s) in 1.1820 seconds

    hbase(main):009:0> enable 'test'
    0 row(s) in 0.1770 seconds
  9. 删除表

    使用 drop 命令可以删除表。

    1
    2
    hbase(main):011:0> drop 'test'
    0 row(s) in 0.1370 seconds
  10. 退出 HBase Shell

    使用 quit 命令,就能退出 HBase Shell 控制台。

参考资料

Nosql 技术选型

img

一、Nosql 简介

传统的关系型数据库存在以下缺点:

  • 大数据场景下 I/O 较高 - 因为数据是按行存储,即使只针对其中某一列进行运算,关系型数据库也会将整行数据从存储设备中读入内存,导致 I/O 较高。
  • 存储的是行记录,无法存储数据结构
  • 表结构 schema 扩展不方便 - 如要需要修改表结构,需要执行执行 DDL(data definition language),语句修改,修改期间会导致锁表,部分服务不可用。
  • 全文搜索功能较弱 - 关系型数据库下只能够进行子字符串的匹配查询,当表的数据逐渐变大的时候,LIKE 查询的匹配会非常慢,即使在有索引的情况下。况且关系型数据库也不应该对文本字段进行索引。
  • 存储和处理复杂关系型数据功能较弱 - 许多应用程序需要了解和导航高度连接数据之间的关系,才能启用社交应用程序、推荐引擎、欺诈检测、知识图谱、生命科学和 IT/网络等用例。然而传统的关系数据库并不善于处理数据点之间的关系。它们的表格数据模型和严格的模式使它们很难添加新的或不同种类的关联信息。

随着大数据时代的到来,越来越多的网站、应用系统需要支撑海量数据存储,高并发请求、高可用、高可扩展性等特性要求。传统的关系型数据库在应付这些调整已经显得力不从心,暴露了许多能以克服的问题。由此,各种各样的 NoSQL(Not Only SQL)数据库作为传统关系型数据的一个有力补充得到迅猛发展。

nosql-history

NoSQL,泛指非关系型的数据库,可以理解为 SQL 的一个有力补充。

在 NoSQL 许多方面性能大大优于非关系型数据库的同时,往往也伴随一些特性的缺失,比较常见的,是事务库事务功能的缺失。 数据库事务正确执行的四个基本要素:ACID 如下:

名称 描述
A Atomicity (原子性) 一个事务中的所有操作,要么全部完成,要么全部不完成,不会在中间某个环节结束。 事务在执行过程中发生错误,会被回滚到事务开始前的状态,就像这个事务从来没有执行过一样。
C Consistency 一致性 在事务开始之前和事务结束以后,数据的数据的一致性约束没有被破坏。
I Isolation 隔离性 数据库允许多个并发事务同时对数据进行读写和修改的能力。隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
D Durability 持久性 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

下面介绍 5 大类 NoSQL 数据针对传统关系型数据库的缺点提供的解决方案:

二、列式数据库

列式数据库是以列相关存储架构进行数据存储的数据库,主要适合于批量数据处理和即时查询

相对应的是行式数据库,数据以行相关的存储体系架构进行空间分配,主要适合于小批量的数据处理,常用于联机事务型数据处理。

基于列式数据库的列列存储特性,可以解决某些特定场景下关系型数据库 I/O 较高的问题

列式数据库原理

传统关系型数据库是按照行来存储数据库,称为“行式数据库”,而列式数据库是按照列来存储数据。

将表放入存储系统中有两种方法,而我们绝大部分是采用行存储的。 行存储法是将各行放入连续的物理位置,这很像传统的记录和文件系统。 列存储法是将数据按照列存储到数据库中,与行存储类似,下图是两种存储方法的图形化解释:

按行存储和按列存储模式

列式数据库产品

  • HBase

    HBase

    HBase 是一个开源的非关系型分布式数据库(NoSQL),它参考了谷歌的 BigTable 建模,实现的编程语言为 Java。它是 Apache 软件基金会的 Hadoop 项目的一部分,运行于 HDFS 文件系统之上,为 Hadoop 提供类似于 BigTable 规模的服务。因此,它可以容错地存储海量稀疏的数据。

  • BigTable

    img

    BigTable 是一种压缩的、高性能的、高可扩展性的,基于 Google 文件系统(Google File System,GFS)的数据存储系统,用于存储大规模结构化数据,适用于云端计算。

列式数据库特性

优点如下:

  • 高效的储存空间利用率

列式数据库由于其针对不同列的数据特征而发明的不同算法,使其往往有比行式数据库高的多的压缩率,普通的行式数据库一般压缩率在 3:1 到 5:1 左右,而列式数据库的压缩率一般在 8:1 到 30:1 左右。 比较常见的,通过字典表压缩数据: 下面中才是那张表本来的样子。经过字典表进行数据压缩后,表中的字符串才都变成数字了。正因为每个字符串在字典表里只出现一次了,所以达到了压缩的目的(有点像规范化和非规范化 Normalize 和 Denomalize)

通过字典表压缩数据

  • 查询效率高

读取多条数据的同一列效率高,因为这些列都是存储在一起的,一次磁盘操作可以数据的指定列全部读取到内存中。 下图通过一条查询的执行过程说明列式存储(以及数据压缩)的优点

img

1
2
3
4
5
6
执行步骤如下:
i. 去字典表里找到字符串对应数字(只进行一次字符串比较)。
ii. 用数字去列表里匹配,匹配上的位置设为1
iii. 把不同列的匹配结果进行位运算得到符合所有条件的记录下标。
iv. 使用这个下标组装出最终的结果集。
复制代码
  • 适合做聚合操作
  • 适合大量的数据而不是小数据

缺点如下:

  • 不适合扫描小量数据
  • 不适合随机的更新
  • 不适合做含有删除和更新的实时操作
  • 单行的数据是 ACID 的,多行的事务时,不支持事务的正常回滚,支持 I(Isolation)隔离性(事务串行提交),D(Durability)持久性,不能保证 A(Atomicity)原子性, C(Consistency)一致性

列式数据库使用场景

以 HBase 为例说明:

  • 大数据量 (100s TB 级数据) 且有快速随机访问的需求。增长量无法预估的应用,需要进行优雅的数据扩展的 HBase 支持在线扩展,即使在一段时间内数据量呈井喷式增长,也可以通过 HBase 横向扩展来满足功能。
  • 写密集型应用,每天写入量巨大,而相对读数量较小的应用 比如 IM 的历史消息,游戏的日志等等
  • 不需要复杂查询条件来查询数据的应用 HBase 只支持基于 rowkey 的查询,对于 HBase 来说,单条记录或者小范围的查询是可以接受的,大范围的查询由于分布式的原因,可能在性能上有点影响,HBase 不适用于有 join,多级索引,表关系复杂的数据模型。
  • 对性能和可靠性要求非常高的应用,由于 HBase 本身没有单点故障,可用性非常高。
  • 存储结构化和半结构化的数据

三、K-V 数据库

K-V 数据库指的是使用键值(key-value)存储的数据库,其数据按照键值对的形式进行组织、索引和存储

KV 存储非常适合存储不涉及过多数据关系业务关系的数据,同时能有效减少读写磁盘的次数,比 SQL 数据库存储拥有更好的读写性能,能够解决关系型数据库无法存储数据结构的问题

K-V 数据库产品

  • Redis

    img

    Redis 是一个使用 ANSI C 编写的开源、支持网络、基于内存、可选持久性的键值对存储数据库。从 2015 年 6 月开始,Redis 的开发由 Redis Labs 赞助,而 2013 年 5 月至 2015 年 6 月期间,其开发由 Pivotal 赞助。在 2013 年 5 月之前,其开发由 VMware 赞助。根据月度排行网站 DB-Engines.com 的数据显示,Redis 是最流行的键值对存储数据库。

  • Cassandra

    img

    Apache Cassandra(社区内一般简称为 C*)是一套开源分布式 NoSQL 数据库系统。它最初由 Facebook 开发,用于储存收件箱等简单格式数据,集 Google BigTable 的数据模型与 Amazon Dynamo 的完全分布式架构于一身。Facebook 于 2008 将 Cassandra 开源,此后,由于 Cassandra 良好的可扩展性和性能,被 Apple, Comcast,Instagram, Spotify, eBay, Rackspace, Netflix 等知名网站所采用,成为了一种流行的分布式结构化数据存储方案。

  • LevelDB

    img

    LevelDB 是一个由 Google 公司所研发的键/值对(Key/Value Pair)嵌入式数据库管理系统编程库, 以开源的 BSD 许可证发布。

K-V 数据库特性

以 Redis 为例:

优点如下:

  • 性能极高 - Redis 能支持超过 10W 的 TPS。
  • 丰富的数据类型 - Redis 支持包括 String,Hash,List,Set,Sorted Set,Bitmap 和 hyperloglog。
  • 丰富的特性 - Redis 还支持 publish/subscribe、通知、key 过期等等特性。

缺点如下: 针对 ACID,Redis 事务不能支持原子性和持久性(A 和 D),只支持隔离性和一致性(I 和 C) 特别说明一下,这里所说的无法保证原子性,是针对 Redis 的事务操作,因为事务是不支持回滚(roll back),而因为 Redis 的单线程模型,Redis 的普通操作是原子性的

大部分业务不需要严格遵循 ACID 原则,例如游戏实时排行榜,粉丝关注等场景,即使部分数据持久化失败,其实业务影响也非常小。因此在设计方案时,需要根据业务特征和要求来做选择

K-V 数据库使用场景

  • 适用场景 - 储存用户信息(比如会话)、配置文件、参数、购物车等等。这些信息一般都和 ID(键)挂钩。

  • 不适用场景

    • 需要通过值来查询,而不是键来查询。Key-Value 数据库中根本没有通过值查询的途径。
    • 需要储存数据之间的关系。在 Key-Value 数据库中不能通过两个或以上的键来关联数据
    • 需要事务的支持。在 Key-Value 数据库中故障产生时不可以进行回滚。

四、文档数据库

文档数据库(也称为文档型数据库)是旨在将半结构化数据存储为文档的一种数据库,它可以解决关系型数据库表结构 schema 扩展不方便的问题。文档数据库通常以 JSON 或 XML 格式存储数据

由于文档数据库的 no-schema 特性,可以存储和读取任意数据。由于使用的数据格式是 JSON 或者 XML,无需在使用前定义字段,读取一个 JSON 中不存在的字段也不会导致 SQL 那样的语法错误。

文档数据库产品

  • MongoDB

    img

    MongoDB是一种面向文档的数据库管理系统,由 C++ 撰写而成,以此来解决应用程序开发社区中的大量现实问题。2007 年 10 月,MongoDB 由 10gen 团队所发展。2009 年 2 月首度推出。

  • CouchDB

    img

    Apache CouchDB 是一个开源数据库,专注于易用性和成为”完全拥抱 web 的数据库“。它是一个使用 JSON 作为存储格式,JavaScript 作为查询语言,MapReduce 和 HTTP 作为 API 的 NoSQL 数据库。其中一个显著的功能就是多主复制。CouchDB 的第一个版本发布在 2005 年,在 2008 年成为了 Apache 的项目。

文档数据库特性

以 MongoDB 为例进行说明

优点如下:

  • 容易存储复杂数据结构 - JSON 是一种强大的描述语言,能够描述复杂的数据结构。
  • 容易变更数据结构 - 无需像关系型数据库一样先执行 DDL 语句修改表结构,程序代码直接读写即可。
  • 容易兼容历史数据 - 对于历史数据,即使没有新增的字段,也不会导致错误,只会返回空值,此时代码兼容处理即可。

缺点如下:

  • 部分支持事务
    • Atomicity(原子性) 仅支持单行/文档级原子性,不支持多行、多文档、多语句原子性。
    • Isolation(隔离性) 隔离级别仅支持已提交读(Read committed)级别,可能导致不可重复读,幻读的问题。
  • 不支持复杂查询 - 例如 join 查询,如果需要 join 查询,需要多次操作数据库。

MongonDB 还是支持多文档事务的 Consistency(一致性)和 Durability(持久性)

虽然官方宣布 MongoDB 将在 4.0 版本中正式推出多文档 ACID 事务支持,最后落地情况还有待见证。

文档数据库使用场景

适用场景

  • 大数据量,且未来数据增长很快
  • 表结构不明确,且字段在不断增加,例如内容管理系统,信息管理系统

不适用场景

  • 支持事务 - 在不同的文档上需要添加事务。Document-Oriented 数据库并不支持文档间的事务
  • 支持复杂查询 - 多个文档直接需要复杂查询,例如 join

五、全文搜索引擎

传统关系型数据库主要通过索引来达到快速查询的目的,在全文搜索的业务下,索引也无能为力,主要体现在:

  • 全文搜索的条件可以随意排列组合,如果通过索引来满足,则索引的数量非常多
  • 全文搜索的模糊匹配方式,索引无法满足,只能用 LIKE 查询,而 LIKE 查询是整表扫描,效率非常低

而全文搜索引擎的出现,正是解决关系型数据库全文搜索功能较弱的问题

搜索引擎原理

全文搜索引擎的技术原理称为 **倒排索引(inverted index)**,是一种索引方法,其基本原理是建立单词到文档的索引。与之相对是,是“正排索引”,其基本原理是建立文档到单词的索引。

现在有如下文档集合:

img

正排索引得到索引如下:

img

可见,正排索引适用于根据文档名称查询文档内容

简单的倒排索引如下:

img

带有单词频率信息的倒排索引如下:

img

可见,倒排索引适用于根据关键词来查询文档内容

搜索引擎产品

  • Elasticsearch

    img

    Elasticsearch 是一个基于 Lucene 的搜索引擎。它提供了一个分布式,多租户 -能够全文搜索与发动机 HTTP Web 界面和无架构 JSON 文件。Elasticsearch 是用 Java 开发的,并根据 Apache License 的条款作为开源发布。根据 DB-Engines 排名,Elasticsearch 是最受欢迎的企业搜索引擎,后面是基于 Lucene 的 Apache Solr。

  • Solr

    img

    Solr 是 Apache Lucene 项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如 Word、PDF)的处理。Solr 是高度可扩展的,并提供了分布式搜索和索引复制

搜索引擎特性

以 Elasticsearch 为例: 优点如下:

  • 查询效率高 - 对海量数据进行近实时的处理
  • 可扩展性 - 基于集群环境可以方便横向扩展,可以承载 PB 级数据
  • 高可用 - Elasticsearch 集群弹性-他们将发现新的或失败的节点,重组和重新平衡数据,确保数据是安全的和可访问的

缺点如下:

  • 部分支持事务 - 单一文档的数据是 ACID 的,包含多个文档的事务时不支持事务的正常回滚,支持 I(Isolation)隔离性(基于乐观锁机制的),D(Durability)持久性,不支持 A(Atomicity)原子性,C(Consistency)一致性
  • 对类似数据库中通过外键的复杂的多表关联操作支持较弱。
  • 读写有一定延时,写入的数据,最快 1s 中能被检索到
  • 更新性能较低,底层实现是先删数据,再插入新数据
  • 内存占用大,因为 Lucene 将索引部分加载到内存中

搜索引擎场景

适用场景如下:

  • 搜索引擎和数据分析引擎 - 全文检索,结构化检索,数据分析
  • 对海量数据进行近实时的处理 - 可以将海量数据分散到多台服务器上去存储和检索

不适用场景如下:

  • 数据需要频繁更新
  • 需要复杂关联查询

六、图数据库

img

图形数据库应用图论存储实体之间的关系信息。最常见例子就是社会网络中人与人之间的关系。关系型数据库用于存储“关系型”数据的效果并不好,其查询复杂、缓慢、超出预期,而图形数据库的独特设计恰恰弥补了这个缺陷,解决关系型数据库存储和处理复杂关系型数据功能较弱的问题。

图数据库产品

  • Neo4j

    img

    Neo4j 是由 Neo4j,Inc。开发的图形数据库管理系统。由其开发人员描述为具有原生图存储和处理的符合 ACID 的事务数据库,根据 DB-Engines 排名, Neo4j 是最流行的图形数据库。

  • ArangoDB

    img

    ArangoDB 是由 triAGENS GmbH 开发的原生多模型数据库系统。数据库系统支持三个重要的数据模型(键/值,文档,图形),其中包含一个数据库核心和统一查询语言 AQL(ArangoDB 查询语言)。查询语言是声明性的,允许在单个查询中组合不同的数据访问模式。ArangoDB 是一个 NoSQL 数据库系统,但 AQL 在很多方面与 SQL 类似。

  • Titan

    img

    Titan 是一个可扩展的图形数据库,针对存储和查询包含分布在多机群集中的数百亿个顶点和边缘的图形进行了优化。Titan 是一个事务性数据库,可以支持数千个并发用户实时执行复杂的图形遍历。

图数据库特性

以 Neo4j 为例:

Neo4j 使用数据结构中图(graph)的概念来进行建模。 Neo4j 中两个最基本的概念是节点和边。节点表示实体,边则表示实体之间的关系。节点和边都可以有自己的属性。不同实体通过各种不同的关系关联起来,形成复杂的对象图。

针对关系数据,2 种 2 数据库的存储结构不同:

2种存储结构

Neo4j 中,存储节点时使用了”index-free adjacency”,即每个节点都有指向其邻居节点的指针,可以让我们在 O(1)的时间内找到邻居节点。另外,按照官方的说法,在 Neo4j 中边是最重要的,是”first-class entities”,所以单独存储,这有利于在图遍历的时候提高速度,也可以很方便地以任何方向进行遍历

img

如下优点:

  • 高性能 - 图的遍历是图数据结构所具有的独特算法,即从一个节点开始,根据其连接的关系,可以快速和方便地找出它的邻近节点。这种查找数据的方法并不受数据量的大小所影响,因为邻近查询始终查找的是有限的局部数据,不会对整个数据库进行搜索
  • 设计的灵活性 - 数据结构的自然伸展特性及其非结构化的数据格式,让图数据库设计可以具有很大的伸缩性和灵活性。因为随着需求的变化而增加的节点、关系及其属性并不会影响到原来数据的正常使用
  • 开发的敏捷性 - 直观明了的数据模型,从需求的讨论开始,到程序开发和实现,以及最终保存在数据库中的样子,它的模样似乎没有什么变化,甚至可以说本来就是一模一样的
  • 完全支持 ACID - 不像别的 NoSQL 数据库 Neo4j 还具有完全事务管理特性,完全支持 ACID 事务管理

缺点如下:

  • 存在支持节点,关系和属性的数量的限制。
  • 不支持拆分。

图数据库场景

适用场景如下:

  • 关系性强的数据中,如社交网络
  • 推荐引擎。如果我们将数据以图的形式表现,那么将会非常有益于推荐的制定

不适用场景如下:

  • 记录大量基于事件的数据(例如日志条目或传感器数据)
  • 对大规模分布式数据进行处理
  • 保存在关系型数据库中的结构化数据
  • 二进制数据存储

七、总结

关系型数据库和 NoSQL 数据库的选型,往往需要考虑几个指标:

  • 数据量
  • 并发量
  • 实时性
  • 一致性要求
  • 读写分布和类型
  • 安全性
  • 运维成本

常见软件系统数据库选型参考如下:

  • 中后台管理型系统 - 如运营系统,数据量少,并发量小,首选关系型数据库。
  • 大流量系统 - 如电商单品页,后台考虑选关系型数据库,前台考虑选内存型数据库。
  • 日志型系统 - 原始数据考虑选列式数据库,日志搜索考虑选搜索引擎。
  • 搜索型系统 - 例如站内搜索,非通用搜索,如商品搜索,后台考虑选关系型数据库,前台考虑选搜索引擎。
  • 事务型系统 - 如库存,交易,记账,考虑选关系型数据库+K-V 数据库(作为缓存)+分布式事务。
  • 离线计算 - 如大量数据分析,考虑选列式数据库或关系型数据。
  • 实时计算 - 如实时监控,可以考虑选内存型数据库或者列式数据库。

设计实践中,要基于需求、业务驱动架构,无论选用 RDB/NoSQL/DRDB,一定是以需求为导向,最终数据存储方案必然是各种权衡的综合性设计

参考资料

JavaWeb 之 Jsp 指南

简介

什么是 Java Server Pages

JSP全称Java Server Pages,是一种动态网页开发技术。

它使用 JSP 标签在 HTML 网页中插入 Java 代码。标签通常以 <% 开头以 %> 结束。

JSP 是一种 Java servlet,主要用于实现 Java web 应用程序的用户界面部分。网页开发者们通过结合 HTML 代码、XHTML 代码、XML 元素以及嵌入 JSP 操作和命令来编写 JSP。

JSP 通过网页表单获取用户输入数据、访问数据库及其他数据源,然后动态地创建网页。

JSP 标签有多种功能,比如访问数据库、记录用户选择信息、访问 JavaBeans 组件等,还可以在不同的网页中传递控制信息和共享信息。

为什么使用 JSP

JSP 也是一种 Servlet,因此 JSP 能够完成 Servlet 能完成的任何工作。

JSP 程序与 CGI 程序有着相似的功能,但和 CGI 程序相比,JSP 程序有如下优势:

  • 性能更加优越,因为 JSP 可以直接在 HTML 网页中动态嵌入元素而不需要单独引用 CGI 文件。
  • 服务器调用的是已经编译好的 JSP 文件,而不像 CGI/Perl 那样必须先载入解释器和目标脚本。
  • JSP 基于 Java Servlets API,因此,JSP 拥有各种强大的企业级 Java API,包括 JDBC,JNDI,EJB,JAXP 等等。
  • JSP 页面可以与处理业务逻辑的 servlets 一起使用,这种模式被 Java servlet 模板引擎所支持。

最后,JSP 是 Java EE 不可或缺的一部分,是一个完整的企业级应用平台。这意味着 JSP 可以用最简单的方式来实现最复杂的应用。

JSP 的优势

以下列出了使用 JSP 带来的其他好处:

  • 与 ASP 相比:JSP 有两大优势。首先,动态部分用 Java 编写,而不是 VB 或其他 MS 专用语言,所以更加强大与易用。第二点就是 JSP 易于移植到非 MS 平台上。
  • 与纯 Servlets 相比:JSP 可以很方便的编写或者修改 HTML 网页而不用去面对大量的 println 语句。
  • 与 SSI 相比:SSI 无法使用表单数据、无法进行数据库链接。
  • 与 JavaScript 相比:虽然 JavaScript 可以在客户端动态生成 HTML,但是很难与服务器交互,因此不能提供复杂的服务,比如访问数据库和图像处理等等。
  • 与静态 HTML 相比:静态 HTML 不包含动态信息。

JSP 工作原理

JSP 是一种 Servlet,但工作方式和 Servlet 有所差别。

Servlet 是先将源代码编译为 class 文件后部署到服务器下的,先编译后部署

Jsp 是先将源代码部署到服务器再编译,先部署后编译

Jsp 会在客户端第一次请求 Jsp 文件时被编译为 HttpJspPage 类(Servlet 的一个子类)。该类会被服务器临时存放在服务器工作目录里。所以,第一次请求 Jsp 后,访问速度会变快就是这个道理。

JSP 工作流程

网络服务器需要一个 JSP 引擎,也就是一个容器来处理 JSP 页面。容器负责截获对 JSP 页面的请求。本教程使用内嵌 JSP 容器的 Apache 来支持 JSP 开发。

JSP 容器与 Web 服务器协同合作,为 JSP 的正常运行提供必要的运行环境和其他服务,并且能够正确识别专属于 JSP 网页的特殊元素。

下图显示了 JSP 容器和 JSP 文件在 Web 应用中所处的位置。

img

工作步骤

以下步骤表明了 Web 服务器是如何使用 JSP 来创建网页的:

  • 就像其他普通的网页一样,您的浏览器发送一个 HTTP 请求给服务器。
  • Web 服务器识别出这是一个对 JSP 网页的请求,并且将该请求传递给 JSP 引擎。通过使用 URL 或者.jsp 文件来完成。
  • JSP 引擎从磁盘中载入 JSP 文件,然后将它们转化为 servlet。这种转化只是简单地将所有模板文本改用 println()语句,并且将所有的 JSP 元素转化成 Java 代码。
  • JSP 引擎将 servlet 编译成可执行类,并且将原始请求传递给 servlet 引擎。
  • Web 服务器的某组件将会调用 servlet 引擎,然后载入并执行 servlet 类。在执行过程中,servlet 产生 HTML 格式的输出并将其内嵌于 HTTP response 中上交给 Web 服务器。
  • Web 服务器以静态 HTML 网页的形式将 HTTP response 返回到您的浏览器中。
  • 最终,Web 浏览器处理 HTTP response 中动态产生的 HTML 网页,就好像在处理静态网页一样。

以上提及到的步骤可以用下图来表示:

一般情况下,JSP 引擎会检查 JSP 文件对应的 servlet 是否已经存在,并且检查 JSP 文件的修改日期是否早于 servlet。如果 JSP 文件的修改日期早于对应的 servlet,那么容器就可以确定 JSP 文件没有被修改过并且 servlet 有效。这使得整个流程与其他脚本语言(比如 PHP)相比要高效快捷一些。

JSP 生命周期

理解 JSP 底层功能的关键就是去理解它们所遵守的生命周期。

JSP 生命周期就是从创建到销毁的整个过程,类似于 servlet 生命周期,区别在于 JSP 生命周期还包括将 JSP 文件编译成 servlet。

以下是 JSP 生命周期中所走过的几个阶段:

  • 编译阶段:servlet 容器编译 servlet 源文件,生成 servlet 类
  • 初始化阶段:加载与 JSP 对应的 servlet 类,创建其实例,并调用它的初始化方法
  • 执行阶段:调用与 JSP 对应的 servlet 实例的服务方法
  • 销毁阶段:调用与 JSP 对应的 servlet 实例的销毁方法,然后销毁 servlet 实例

很明显,JSP 生命周期的四个主要阶段和 servlet 生命周期非常相似,下面给出图示:

img

JSP 编译

当浏览器请求 JSP 页面时,JSP 引擎会首先去检查是否需要编译这个文件。如果这个文件没有被编译过,或者在上次编译后被更改过,则编译这个 JSP 文件。

编译的过程包括三个步骤:

  • 解析 JSP 文件。
  • 将 JSP 文件转为 servlet。
  • 编译 servlet。

JSP 初始化

容器载入 JSP 文件后,它会在为请求提供任何服务前调用 jspInit()方法。如果您需要执行自定义的 JSP 初始化任务,复写 jspInit()方法就行了,就像下面这样:

1
2
3
public void jspInit(){
// 初始化代码
}

一般来讲程序只初始化一次,servlet 也是如此。通常情况下您可以在 jspInit()方法中初始化数据库连接、打开文件和创建查询表。

JSP 执行

这一阶段描述了 JSP 生命周期中一切与请求相关的交互行为,直到被销毁。

当 JSP 网页完成初始化后,JSP 引擎将会调用 _jspService() 方法。

_jspService() 方法需要一个 HttpServletRequest 对象和一个 HttpServletResponse 对象作为它的参数,就像下面这样:

1
2
3
4
void _jspService(HttpServletRequest request,
HttpServletResponse response) {
// 服务端处理代码
}

_jspService() 方法在每个 request 中被调用一次并且负责产生与之相对应的 response,并且它还负责产生所有 7 个 HTTP 方法的回应,比如 GET、POST、DELETE 等等。

JSP 清理

JSP 生命周期的销毁阶段描述了当一个 JSP 网页从容器中被移除时所发生的一切。

jspDestroy()方法在 JSP 中等价于 servlet 中的销毁方法。当您需要执行任何清理工作时复写 jspDestroy()方法,比如释放数据库连接或者关闭文件夹等等。

jspDestroy()方法的格式如下:

1
2
3
public void jspDestroy() {
// 清理代码
}

语法

脚本

脚本程序可以包含任意量的 Java 语句、变量、方法或表达式,只要它们在脚本语言中是有效的。

脚本程序的语法格式:

1
<% 代码片段 %>

或者,您也可以编写与其等价的 XML 语句,就像下面这样:

1
2
3
<jsp:scriptlet>
代码片段
</jsp:scriptlet>

任何文本、HTML 标签、JSP 元素必须写在脚本程序的外面。

下面给出一个示例,同时也是本教程的第一个 JSP 示例:

1
2
3
4
5
6
7
8
9
<html>
<head>
<title>Hello World</title>
</head>
<body>
Hello World!<br />
<% out.println("Your IP address is " + request.getRemoteAddr()); %>
</body>
</html>

注意:请确保 Apache Tomcat 已经安装在 C:\apache-tomcat-7.0.2 目录下并且运行环境已经正确设置。

将以上代码保存在 hello.jsp 中,然后将它放置在 C:\apache-tomcat-7.0.2\webapps\ROOT 目录下,打开浏览器并在地址栏中输入 http://localhost:8080/hello.jsp 。运行后得到以下结果:

img

中文编码问题

如果我们要在页面正常显示中文,我们需要在 JSP 文件头部添加以下代码:<>

1
2
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

接下来我们将以上程序修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
Hello World!<br />
<% out.println("你的 IP 地址 " + request.getRemoteAddr()); %>
</body>
</html>

这样中文就可以正常显示了。

JSP 声明

一个声明语句可以声明一个或多个变量、方法,供后面的 Java 代码使用。在 JSP 文件中,您必须先声明这些变量和方法然后才能使用它们。

JSP 声明的语法格式:

1
<%! declaration; [ declaration; ]+ ... %>

或者,您也可以编写与其等价的 XML 语句,就像下面这样:

1
2
3
<jsp:declaration>
代码片段
</jsp:declaration>

程序示例:

1
<%! int i = 0; %> <%! int a, b, c; %> <%! Circle a = new Circle(2.0); %>

JSP 表达式

一个 JSP 表达式中包含的脚本语言表达式,先被转化成 String,然后插入到表达式出现的地方。

由于表达式的值会被转化成 String,所以您可以在一个文本行中使用表达式而不用去管它是否是 HTML 标签。

表达式元素中可以包含任何符合 Java 语言规范的表达式,但是不能使用分号来结束表达式。

JSP 表达式的语法格式:

1
<%= 表达式 %>

同样,您也可以编写与之等价的 XML 语句:

1
2
3
<jsp:expression>
表达式
</jsp:expression>

程序示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<p>
今天的日期是: <%= (new java.util.Date()).toLocaleString()%>
</p>
</body>
</html>

运行后得到以下结果:

1
今天的日期是: 2016-6-25 13:40:07

JSP 注释

JSP 注释主要有两个作用:为代码作注释以及将某段代码注释掉。

JSP 注释的语法格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>JSP注释示例</title>
</head>
<body>
<%-- 该部分注释在网页中不会被显示--%>
<p>
今天的日期是: <%= (new java.util.Date()).toLocaleString()%>
</p>
</body>
</html>

运行后得到以下结果:

1
今天的日期是: 2016-6-25 13:41:26

不同情况下使用注释的语法规则:

语法 描述
<%-- 注释 --%> JSP 注释,注释内容不会被发送至浏览器甚至不会被编译
<!-- 注释 --> HTML 注释,通过浏览器查看网页源代码时可以看见注释内容
<% 代表静态 <% 常量
%> 代表静态 %> 常量
' 在属性中使用的单引号
" 在属性中使用的双引号

控制语句

JSP 提供对 Java 语言的全面支持。您可以在 JSP 程序中使用 Java API 甚至建立 Java 代码块,包括判断语句和循环语句等等。

if…else 语句

If…else块,请看下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%> <%! int day = 1; %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>02.JSP语法 - if...else示例</title>
</head>
<body>
<h3>IF...ELSE 实例</h3>
<% if (day == 1 | day == 7) { %>
<p>今天是周末</p>
<% } else { %>
<p>今天不是周末</p>
<% } %>
</body>
</html>

运行后得到以下结果:

1
2
IF...ELSE 实例
今天不是周末

switch…case 语句

现在来看看 switch…case 块,与 if…else 块有很大的不同,它使用 out.println(),并且整个都装在脚本程序的标签中,就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%> <%! int day = 3; %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>02.JSP语法 - switch...case示例</title>
</head>
<body>
<h3>Sswitch...case示例</h3>
<% switch(day) { case 0: out.println("星期天"); break; case 1:
out.println("星期一"); break; case 2: out.println("星期二"); break; case 3:
out.println("星期三"); break; case 4: out.println("星期四"); break; case 5:
out.println("星期五"); break; default: out.println("星期六"); } %>
</body>
</html>

浏览器访问,运行后得出以下结果:

1
2
3
SWITCH...CASE 实例

星期三

循环语句

在 JSP 程序中可以使用 Java 的三个基本循环类型:for,while,和 do…while。

让我们来看看 for 循环的例子,以下输出的不同字体大小的”菜鸟教程”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%> <%! int fontSize; %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<h3>For 循环实例</h3>
<%for ( fontSize = 1; fontSize <= 3; fontSize++){ %>
<font color="green" size="<%= fontSize %>">
菜鸟教程 </font
><br />
<%}%>
</body>
</html>

运行后得到以下结果:

img

将上例改用 while 循环来写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%> <%! int fontSize; %>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<h3>While 循环实例</h3>
<%while ( fontSize <= 3){ %>
<font color="green" size="<%= fontSize %>">
菜鸟教程 </font
><br />
<%fontSize++;%> <%}%>
</body>
</html>

浏览器访问,输出结果为(fontSize 初始化为 0,所以多输出了一行):

img

JSP 运算符

JSP 支持所有 Java 逻辑和算术运算符。

下表罗列出了 JSP 常见运算符,优先级从高到底:

类别 操作符 结合性
后缀 () [] . (点运算符) 左到右
一元 ++ - - ! ~ 右到左
可乘性 * / % 左到右
可加性 + - 左到右
移位 >> >>> << 左到右
关系 > >= < <= 左到右
相等/不等 == != 左到右
位与 & 左到右
位异或 ^ 左到右
位或 ` `
逻辑与 && 左到右
逻辑或 `
条件判断 ?: 右到左
赋值 `= += -= *= /= %= >>= <<= &= ^= =`
逗号 , 左到右

JSP 字面量

JSP 语言定义了以下几个字面量:

  • 布尔值(boolean):true 和 false;
  • 整型(int):与 Java 中的一样;
  • 浮点型(float):与 Java 中的一样;
  • 字符串(string):以单引号或双引号开始和结束;
  • Null:null。

指令

JSP 指令用来设置整个 JSP 页面相关的属性,如网页的编码方式和脚本语言。

JSP 指令以开<%@开始,以%>结束。

JSP 指令语法格式如下:

1
<%@ directive attribute="value" %>

指令可以有很多个属性,它们以键值对的形式存在,并用逗号隔开。

JSP 中的三种指令标签:

指令 描述
<%@ page ... %> 定义网页依赖属性,比如脚本语言、error 页面、缓存需求等等
<%@ include ... %> 包含其他文件
<%@ taglib ... %> 引入标签库的定义,可以是自定义标签

Page 指令

Page 指令为容器提供当前页面的使用说明。一个 JSP 页面可以包含多个page指令。

Page 指令的语法格式:

1
<%@ page attribute="value" %>

等价的 XML 格式:

1
<jsp:directive.page attribute="value" />

例:

1
2
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8" %>

属性

下表列出与 Page 指令相关的属性:

属性 描述
buffer 指定 out 对象使用缓冲区的大小
autoFlush 控制 out 对象的 缓存区
contentType 指定当前 JSP 页面的 MIME 类型和字符编码
errorPage 指定当 JSP 页面发生异常时需要转向的错误处理页面
isErrorPage 指定当前页面是否可以作为另一个 JSP 页面的错误处理页面
extends 指定 servlet 从哪一个类继承
import 导入要使用的 Java 类
info 定义 JSP 页面的描述信息
isThreadSafe 指定对 JSP 页面的访问是否为线程安全
language 定义 JSP 页面所用的脚本语言,默认是 Java
session 指定 JSP 页面是否使用 session
isELIgnored 指定是否执行 EL 表达式
isScriptingEnabled 确定脚本元素能否被使用

Include 指令

JSP 可以通过include指令来包含其他文件。

被包含的文件可以是 JSP 文件、HTML 文件或文本文件。包含的文件就好像是该 JSP 文件的一部分,会被同时编译执行。

Include 指令的语法格式如下:

1
<%@ include file="文件相对 url 地址" %>

include 指令中的文件名实际上是一个相对的 URL 地址。

如果您没有给文件关联一个路径,JSP 编译器默认在当前路径下寻找。

等价的 XML 语法:

1
<jsp:directive.include file="文件相对 url 地址" />

Taglib 指令

JSP 允许用户自定义标签,一个自定义标签库就是自定义标签的集合。

taglib指令引入一个自定义标签集合的定义,包括库路径、自定义标签。

taglib指令的语法:

1
<%@ taglib uri="uri" prefix="prefixOfTag" %>

uri 属性确定标签库的位置,prefix 属性指定标签库的前缀。

等价的 XML 语法:

1
<jsp:directive.taglib uri="uri" prefix="prefixOfTag" />

JSP 动作元素

JSP 动作元素是一组 JSP 内置的标签,只需要书写很少的标记代码就能使用 JSP 提供的丰富功能。JSP 动作元素是对常用的 JSP 功能的抽象与封装,包括两种,自定义 JSP 动作元素与标准 JSP 动作元素。

与 JSP 指令元素不同的是,JSP 动作元素在请求处理阶段起作用。JSP 动作元素是用 XML 语法写成的。

利用 JSP 动作可以动态地插入文件、重用 JavaBean 组件、把用户重定向到另外的页面、为 Java 插件生成 HTML 代码。

动作元素只有一种语法,它符合 XML 标准:

1
<jsp:action_name attribute="value" />

动作元素基本上都是预定义的函数,JSP 规范定义了一系列的标准动作,它用 JSP 作为前缀,可用的标准动作元素如下:

语法 描述
jsp:include 在页面被请求的时候引入一个文件。
jsp:useBean 寻找或者实例化一个 JavaBean。
jsp:setProperty 设置 JavaBean 的属性。
jsp:getProperty 输出某个 JavaBean 的属性。
jsp:forward 把请求转到一个新的页面。
jsp:plugin 根据浏览器类型为 Java 插件生成 OBJECT 或 EMBED 标记。
jsp:element 定义动态 XML 元素
jsp:attribute 设置动态定义的 XML 元素属性。
jsp:body 设置动态定义的 XML 元素内容。
jsp:text 在 JSP 页面和文档中使用写入文本的模板

常见的属性

所有的动作要素都有两个属性:id 属性和 scope 属性。

  • id 属性:id 属性是动作元素的唯一标识,可以在 JSP 页面中引用。动作元素创建的 id 值可以通过 PageContext 来调用。
  • scope 属性:该属性用于识别动作元素的生命周期。 id 属性和 scope 属性有直接关系,scope 属性定义了相关联 id 对象的寿命。 scope 属性有四个可能的值: (a) page, (b)request, (c)session, 和 (d) application。

<jsp:include>

<jsp:include> 用来包含静态和动态的文件。该动作把指定文件插入正在生成的页面。

如果被包含的文件为 JSP 程序,则会先执行 JSP 程序,再将执行结果包含进来。

语法格式如下:

1
<jsp:include page="相对 URL 地址" flush="true" />

前面已经介绍过 include 指令,它是在 JSP 文件被转换成 Servlet 的时候引入文件,而这里的 jsp:include 动作不同,插入文件的时间是在页面被请求的时候。

以下是 include 动作相关的属性列表。

属性 描述
page 包含在页面中的相对 URL 地址。
flush 布尔属性,定义在包含资源前是否刷新缓存区。

示例:

以下我们定义了两个文件 date.jspmain.jsp,代码如下所示:

date.jsp 文件代码:

1
2
3
4
5
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<p>
今天的日期是: <%= (new java.util.Date())%>
</p>

main.jsp 文件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<h2>include 动作实例</h2>
<jsp:include page="date.jsp" flush="true" />
</body>
</html>

现在将以上两个文件放在服务器的根目录下,访问 main.jsp 文件。显示结果如下:

1
2
3
include 动作实例

今天的日期是: 2016-6-25 14:08:17

<jsp:useBean>

jsp:useBean 动作用来加载一个将在 JSP 页面中使用的 JavaBean。

这个功能非常有用,因为它使得我们可以发挥 Java 组件复用的优势。

jsp:useBean 动作最简单的语法为:

1
<jsp:useBean id="name" class="package.class" />

在类载入后,我们既可以通过 jsp:setProperty 和 jsp:getProperty 动作来修改和检索 bean 的属性。

以下是 useBean 动作相关的属性列表。

属性 描述
class 指定 Bean 的完整包名。
type 指定将引用该对象变量的类型。
beanName 通过 java.beans.Beans 的 instantiate() 方法指定 Bean 的名字。

在给出具体实例前,让我们先来看下 jsp:setProperty 和 jsp:getProperty 动作元素:

<jsp:setProperty>

jsp:setProperty 用来设置已经实例化的 Bean 对象的属性,有两种用法。首先,你可以在 jsp:useBean 元素的外面(后面)使用 jsp:setProperty,如下所示:

1
2
3
<jsp:useBean id="myName" ... />
...
<jsp:setProperty name="myName" property="someProperty" .../>

此时,不管 jsp:useBean 是找到了一个现有的 Bean,还是新创建了一个 Bean 实例,jsp:setProperty 都会执行。第二种用法是把 jsp:setProperty 放入 jsp:useBean 元素的内部,如下所示:

1
2
3
4
<jsp:useBean id="myName" ... >
...
<jsp:setProperty name="myName" property="someProperty" .../>
</jsp:useBean>

此时,jsp:setProperty 只有在新建 Bean 实例时才会执行,如果是使用现有实例则不执行 jsp:setProperty。

jsp:setProperty 动作有下面四个属性,如下表:

属性 描述
name name 属性是必需的。它表示要设置属性的是哪个 Bean。
property property 属性是必需的。它表示要设置哪个属性。有一个特殊用法:如果 property 的值是”*“,表示所有名字和 Bean 属性名字匹配的请求参数都将被传递给相应的属性 set 方法。
value value 属性是可选的。该属性用来指定 Bean 属性的值。字符串数据会在目标类中通过标准的 valueOf 方法自动转换成数字、boolean、Boolean、 byte、Byte、char、Character。例如,boolean 和 Boolean 类型的属性值(比如”true”)通过 Boolean.valueOf 转换,int 和 Integer 类型的属性值(比如”42”)通过 Integer.valueOf 转换。    value 和 param 不能同时使用,但可以使用其中任意一个。
param param 是可选的。它指定用哪个请求参数作为 Bean 属性的值。如果当前请求没有参数,则什么事情也不做,系统不会把 null 传递给 Bean 属性的 set 方法。因此,你可以让 Bean 自己提供默认属性值,只有当请求参数明确指定了新值时才修改默认属性值。

<jsp:getProperty>

jsp:getProperty 动作提取指定 Bean 属性的值,转换成字符串,然后输出。语法格式如下:

1
2
3
<jsp:useBean id="myName" ... />
...
<jsp:getProperty name="myName" property="someProperty" .../>

下表是与 getProperty 相关联的属性:

属性 描述
name 要检索的 Bean 属性名称。Bean 必须已定义。
property 表示要提取 Bean 属性的值

实例

以下实例我们使用了 Bean:

1
2
3
4
5
6
7
8
9
10
11
12
package com.runoob.main;

public class TestBean {
private String message = "菜鸟教程";

public String getMessage() {
return(message);
}
public void setMessage(String message) {
this.message = message;
}
}

编译以上实例文件 TestBean.java :

1
$ javac TestBean.java

编译完成后会在当前目录下生成一个 TestBean.class 文件, 将该文件拷贝至当前 JSP 项目的 WebContent/WEB-INF/classes/com/runoob/main 下( com/runoob/main 包路径,没有需要手动创建)。

下面是一个 Eclipse 中目录结构图:

img

下面是一个很简单的例子,它的功能是装载一个 Bean,然后设置/读取它的 message 属性。

现在让我们在 main.jsp 文件中调用该 Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<h2>Jsp 使用 JavaBean 实例</h2>
<jsp:useBean id="test" class="com.runoob.main.TestBean" />

<jsp:setProperty name="test" property="message" value="菜鸟教程..." />

<p>输出信息....</p>

<jsp:getProperty name="test" property="message" />
</body>
</html>

浏览器访问,执行以上文件,输出如下所示:

img

<jsp:forward>

jsp:forward 动作把请求转到另外的页面。jsp:forward 标记只有一个属性 page。语法格式如下所示:

1
<jsp:forward page="相对 URL 地址" />

以下是 forward 相关联的属性:

属性 描述
page page 属性包含的是一个相对 URL。page 的值既可以直接给出,也可以在请求的时候动态计算,可以是一个 JSP 页面或者一个 Java Servlet.

实例

以下实例我们使用了两个文件,分别是: date.jsp 和 main.jsp。

date.jsp 文件代码如下:

1
2
3
4
5
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<p>
今天的日期是: <%= (new java.util.Date()).toLocaleString()%>
</p>

main.jsp 文件代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<h2>forward 动作实例</h2>
<jsp:forward page="date.jsp" />
</body>
</html>

现在将以上两个文件放在服务器的根目录下,访问 main.jsp 文件。显示结果如下:

1
今天的日期是: 2016-6-25 14:37:25

<jsp:plugin>

jsp:plugin 动作用来根据浏览器的类型,插入通过 Java 插件 运行 Java Applet 所必需的 OBJECT 或 EMBED 元素。

如果需要的插件不存在,它会下载插件,然后执行 Java 组件。 Java 组件可以是一个 applet 或一个 JavaBean。

plugin 动作有多个对应 HTML 元素的属性用于格式化 Java 组件。param 元素可用于向 Applet 或 Bean 传递参数。

以下是使用 plugin 动作元素的典型实例:

1
2
3
4
5
6
7
8
9
10
<jsp:plugin type="applet" codebase="dirname" code="MyApplet.class"
width="60" height="80">
<jsp:param name="fontcolor" value="red" />
<jsp:param name="background" value="black" />

<jsp:fallback>
Unable to initialize Java Plugin
</jsp:fallback>

</jsp:plugin>

如果你有兴趣可以尝试使用 applet 来测试 jsp:plugin 动作元素,<fallback> 元素是一个新元素,在组件出现故障的错误是发送给用户错误信息。

<jsp:element><jsp:attribute><jsp:body>

<jsp:element><jsp:attribute><jsp:body> 动作元素动态定义 XML 元素。动态是非常重要的,这就意味着 XML 元素在编译时是动态生成的而非静态。

以下实例动态定义了 XML 元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<jsp:element name="xmlElement">
<jsp:attribute name="xmlElementAttr">
属性值
</jsp:attribute>
<jsp:body>
XML 元素的主体
</jsp:body>
</jsp:element>
</body>
</html>

浏览器访问以下页面,输出结果如下所示:

img

<jsp:text>

jsp:text动作元素允许在 JSP 页面和文档中使用写入文本的模板,语法格式如下:

1
<jsp:text>模板数据</jsp:text>

以上文本模板不能包含其他元素,只能只能包含文本和 EL 表达式(注:EL 表达式将在后续章节中介绍)。请注意,在 XML 文件中,您不能使用表达式如 ${whatever > 0},因为>符号是非法的。 你可以使用 ${whatever gt 0}表达式或者嵌入在一个 CDATA 部分的值。

1
<jsp:text><![CDATA[<br>]]></jsp:text>

如果你需要在 XHTML 中声明 DOCTYPE,必须使用到 <jsp:text> 动作元素,实例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<jsp:text><![CDATA[<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"DTD/xhtml1-strict.dtd">]]>
</jsp:text>
<head><title>jsp:text action</title></head>
<body>

<books><book><jsp:text>
Welcome to JSP Programming
</jsp:text></book></books>

</body>
</html>

你可以对以上实例尝试使用jsp:text及不使用该动作元素执行结果的区别。

JSP 隐式对象

JSP 隐式对象是 JSP 容器为每个页面提供的 Java 对象,开发者可以直接使用它们而不用显式声明。JSP 隐式对象也被称为预定义变量。

JSP 所支持的九大隐式对象:

对象 描述
request HttpServletRequest类的实例
response HttpServletResponse类的实例
out PrintWriter类的实例,用于把结果输出至网页上
session HttpSession类的实例
application ServletContext类的实例,与应用上下文有关
config ServletConfig类的实例
pageContext PageContext类的实例,提供对 JSP 页面所有对象以及命名空间的访问
page 类似于 Java 类中的 this 关键字
Exception Exception类的对象,代表发生错误的 JSP 页面中对应的异常对象

request 对象

request对象是javax.servlet.http.HttpServletRequest 类的实例。

每当客户端请求一个 JSP 页面时,JSP 引擎就会制造一个新的request对象来代表这个请求。

request对象提供了一系列方法来获取 HTTP 头信息,cookies,HTTP 方法等等。

response 对象

response对象是javax.servlet.http.HttpServletResponse类的实例。

当服务器创建request对象时会同时创建用于响应这个客户端的response对象。

response对象也定义了处理 HTTP 头模块的接口。通过这个对象,开发者们可以添加新的 cookies,时间戳,HTTP 状态码等等。

out 对象

out对象是javax.servlet.jsp.JspWriter类的实例,用来在response对象中写入内容。

最初的JspWriter类对象根据页面是否有缓存来进行不同的实例化操作。可以在page指令中使用buffered='false'属性来轻松关闭缓存。

JspWriter类包含了大部分java.io.PrintWriter类中的方法。不过,JspWriter新增了一些专为处理缓存而设计的方法。还有就是,JspWriter类会抛出IOExceptions异常,而PrintWriter不会。

下表列出了我们将会用来输出booleancharintdoubleStringobject等类型数据的重要方法:

方法 描述
out.print(dataType dt) 输出 Type 类型的值
out.println(dataType dt) 输出 Type 类型的值然后换行
out.flush() 刷新输出流

session 对象

session对象是javax.servlet.http.HttpSession类的实例。和 Java Servlets 中的session对象有一样的行为。

session对象用来跟踪在各个客户端请求间的会话。

application 对象

application对象直接包装了 servlet 的ServletContext类的对象,是javax.servlet.ServletContext类的实例。

这个对象在 JSP 页面的整个生命周期中都代表着这个 JSP 页面。这个对象在 JSP 页面初始化时被创建,随着jspDestroy()方法的调用而被移除。

通过向application中添加属性,则所有组成您 web 应用的 JSP 文件都能访问到这些属性。

config 对象

config对象是javax.servlet.ServletConfig类的实例,直接包装了 servlet 的ServletConfig类的对象。

这个对象允许开发者访问 Servlet 或者 JSP 引擎的初始化参数,比如文件路径等。

以下是 config 对象的使用方法,不是很重要,所以不常用:

1
config.getServletName();

它返回包含在<servlet-name>元素中的 servlet 名字,注意,<servlet-name>元素在WEB-INF\web.xml文件中定义。

pageContext 对象

pageContext对象是javax.servlet.jsp.PageContext类的实例,用来代表整个 JSP 页面。

这个对象主要用来访问页面信息,同时过滤掉大部分实现细节。

这个对象存储了request对象和response对象的引用。application对象,config对象,session对象,out对象可以通过访问这个对象的属性来导出。

pageContext对象也包含了传给 JSP 页面的指令信息,包括缓存信息,ErrorPage URL,页面 scope 等。

PageContext类定义了一些字段,包括 PAGE_SCOPE,REQUEST_SCOPE,SESSION_SCOPE, APPLICATION_SCOPE。它也提供了 40 余种方法,有一半继承自javax.servlet.jsp.JspContext 类。

其中一个重要的方法就是removeArribute(),它可接受一个或两个参数。比如,pageContext.removeArribute(“attrName”)移除四个 scope 中相关属性,但是下面这种方法只移除特定 scope 中的相关属性:

1
pageContext.removeAttribute("attrName", PAGE_SCOPE);

page 对象

这个对象就是页面实例的引用。它可以被看做是整个 JSP 页面的代表。

page对象就是this对象的同义词。

exception 对象

exception对象包装了从先前页面中抛出的异常信息。它通常被用来产生对出错条件的适当响应。

EL 表达式

EL 表达式是用${}括起来的脚本,用来更方便地读取对象。EL 表达式写在 JSP 的 HTML 代码中,而不能写在<%%>引起的 JSP 脚本中。

JSP 表达式语言(EL)使得访问存储在 JavaBean 中的数据变得非常简单。JSP EL 既可以用来创建算术表达式也可以用来创建逻辑表达式。在 JSP EL 表达式内可以使用整型数,浮点数,字符串,常量 true、false,还有 null。

一个简单的语法

典型的,当您需要在 JSP 标签中指定一个属性值时,只需要简单地使用字符串即可:

1
<jsp:setProperty name="box" property="perimeter" value="100" />

JSP EL 允许您指定一个表达式来表示属性值。一个简单的表达式语法如下:

1
${expr}

其中,expr 指的是表达式。在 JSP EL 中通用的操作符是”.”和”[]”。这两个操作符允许您通过内嵌的 JSP 对象访问各种各样的 JavaBean 属性。

举例来说,上面的 <jsp:setProperty> 标签可以使用表达式语言改写成如下形式:

1
2
3
4
5
<jsp:setProperty
name="box"
property="perimeter"
value="${2*box.width+2*box.height}"
/>

当 JSP 编译器在属性中见到”${}”格式后,它会产生代码来计算这个表达式,并且产生一个替代品来代替表达式的值。

您也可以在标签的模板文本中使用表达式语言。比如 <jsp:text> 标签简单地将其主体中的文本插入到 JSP 输出中:

1
2
3
<jsp:text>
<h1>Hello JSP!</h1>
</jsp:text>

现在,在jsp:text标签主体中使用表达式,就像这样:

1
2
3
<jsp:text>
Box Perimeter is: ${2*box.width + 2*box.height}
</jsp:text>

在 EL 表达式中可以使用圆括号来组织子表达式。比如 ${(1 + 2) _ 3} 等于 9,但是 ${1 + (2 _ 3)} 等于 7。

想要停用对 EL 表达式的评估的话,需要使用 page 指令将 isELIgnored 属性值设为 true:

1
<%@ page isELIgnored ="true|false" %>

这样,EL 表达式就会被忽略。若设为 false,则容器将会计算 EL 表达式。

EL 中的基础操作符

EL 表达式支持大部分 Java 所提供的算术和逻辑操作符:

操作符 描述
. 访问一个 Bean 属性或者一个映射条目
[] 访问一个数组或者链表的元素
( ) 组织一个子表达式以改变优先级
+
- 减或负
*
/ or div
% or mod 取模
== or eq 测试是否相等
!= or ne 测试是否不等
< or lt 测试是否小于
> or gt 测试是否大于
<= or le 测试是否小于等于
>= or ge 测试是否大于等于
&& or and 测试逻辑与
|| or or 测试逻辑或
! or not 测试取反
empty 测试是否空值

JSP EL 中的函数

JSP EL 允许您在表达式中使用函数。这些函数必须被定义在自定义标签库中。函数的使用语法如下:

1
${ns:func(param1, param2, ...)}

ns 指的是命名空间(namespace),func 指的是函数的名称,param1 指的是第一个参数,param2 指的是第二个参数,以此类推。比如,有函数 fn:length,在 JSTL 库中定义,可以像下面这样来获取一个字符串的长度:

1
${fn:length("Get my length")}

要使用任何标签库中的函数,您需要将这些库安装在服务器中,然后使用 <taglib> 标签在 JSP 文件中包含这些库。

JSP EL 隐含对象

JSP EL 支持下表列出的隐含对象:

隐含对象 描述
pageScope page 作用域
requestScope request 作用域
sessionScope session 作用域
applicationScope application 作用域
param Request 对象的参数,字符串
paramValues Request 对象的参数,字符串集合
header HTTP 信息头,字符串
headerValues HTTP 信息头,字符串集合
initParam 上下文初始化参数
cookie Cookie 值
pageContext 当前页面的 pageContext

您可以在表达式中使用这些对象,就像使用变量一样。接下来会给出几个例子来更好的理解这个概念。

pageContext 对象

pageContext 对象是 JSP 中 pageContext 对象的引用。通过 pageContext 对象,您可以访问 request 对象。比如,访问 request 对象传入的查询字符串,就像这样:

1
${pageContext.request.queryString}

Scope 对象

pageScope,requestScope,sessionScope,applicationScope 变量用来访问存储在各个作用域层次的变量。

举例来说,如果您需要显式访问在 applicationScope 层的 box 变量,可以这样来访问:applicationScope.box。

param 和 paramValues 对象

param 和 paramValues 对象用来访问参数值,通过使用 request.getParameter 方法和 request.getParameterValues 方法。

举例来说,访问一个名为 order 的参数,可以这样使用表达式:${param.order},或者${param["order"]}

接下来的例子表明了如何访问 request 中的 username 参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page import="java.io.*,java.util.*" %> <% String title = "Accessing Request
Param"; %>
<html>
<head>
<title><% out.print(title); %></title>
</head>
<body>
<center>
<h1><% out.print(title); %></h1>
</center>
<div align="center">
<p>${param["username"]}</p>
</div>
</body>
</html>

param 对象返回单一的字符串,而 paramValues 对象则返回一个字符串数组。

header 和 headerValues 对象

header 和 headerValues 对象用来访问信息头,通过使用 request.getHeader 方法和 request.getHeaders 方法。

举例来说,要访问一个名为 user-agent 的信息头,可以这样使用表达式:${header.user-agent},或者 ${header["user-agent"]}

接下来的例子表明了如何访问 user-agent 信息头:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<%@ page import="java.io.*,java.util.*" %> <% String title = "User Agent
Example"; %>
<html>
<head>
<title><% out.print(title); %></title>
</head>
<body>
<center>
<h1><% out.print(title); %></h1>
</center>
<div align="center">
<p>${header["user-agent"]}</p>
</div>
</body>
</html>

运行结果如下:

img

header 对象返回单一值,而 headerValues 则返回一个字符串数组。

JSTL

JSP 标准标签库(JSTL)是一个 JSP 标签集合,它封装了 JSP 应用的通用核心功能。

JSTL 支持通用的、结构化的任务,比如迭代,条件判断,XML 文档操作,国际化标签,SQL 标签。 除了这些,它还提供了一个框架来使用集成 JSTL 的自定义标签。

根据 JSTL 标签所提供的功能,可以将其分为 5 个类别。

  • 核心标签
  • 格式化标签
  • SQL 标签
  • XML 标签
  • JSTL 函数

JSTL 库安装

Apache Tomcat 安装 JSTL 库步骤如下:

从 Apache 的标准标签库中下载的二进包(jakarta-taglibs-standard-current.zip)。

下载 jakarta-taglibs-standard-1.1.2.zip 包并解压,将 jakarta-taglibs-standard-1.1.2/lib/ 下的两个 jar 文件:standard.jarjstl.jar 文件拷贝到 /WEB-INF/lib/ 下。

将 tld 下的需要引入的 tld 文件复制到 WEB-INF 目录下。

接下来我们在 web.xml 文件中添加以下配置:

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
<?xml version="1.0" encoding="UTF-8"?>
<web-app
version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
>
<jsp-config>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/fmt</taglib-uri>
<taglib-location>/WEB-INF/fmt.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/fmt-rt</taglib-uri>
<taglib-location>/WEB-INF/fmt-rt.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/core</taglib-uri>
<taglib-location>/WEB-INF/c.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/core-rt</taglib-uri>
<taglib-location>/WEB-INF/c-rt.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/sql</taglib-uri>
<taglib-location>/WEB-INF/sql.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/sql-rt</taglib-uri>
<taglib-location>/WEB-INF/sql-rt.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/x</taglib-uri>
<taglib-location>/WEB-INF/x.tld</taglib-location>
</taglib>
<taglib>
<taglib-uri>http://java.sun.com/jsp/jstl/x-rt</taglib-uri>
<taglib-location>/WEB-INF/x-rt.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>

使用任何库,你必须在每个 JSP 文件中的头部包含 <taglib> 标签。

核心标签

核心标签是最常用的 JSTL 标签。引用核心标签库的语法如下:

1
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
标签 描述
<c:out> 用于在 JSP 中显示数据,就像<%= … >
<c:set> 用于保存数据
<c:remove> 用于删除数据
<c:catch> 用来处理产生错误的异常状况,并且将错误信息储存起来
<c:if> 与我们在一般程序中用的 if 一样
<c:choose> 本身只当做 <c:when><c:otherwise> 的父标签
<c:when> <c:choose> 的子标签,用来判断条件是否成立
<c:otherwise> <c:choose> 的子标签,接在 <c:when> 标签后,当 <c:when> 标签判断为 false 时被执行
<c:import> 检索一个绝对或相对 URL,然后将其内容暴露给页面
<c:forEach> 基础迭代标签,接受多种集合类型
<c:forTokens> 根据指定的分隔符来分隔内容并迭代输出
<c:param> 用来给包含或重定向的页面传递参数
<c:redirect> 重定向至一个新的 URL.
<c:url> 使用可选的查询参数来创造一个 URL

格式化标签

JSTL 格式化标签用来格式化并输出文本、日期、时间、数字。引用格式化标签库的语法如下:

1
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
标签 描述
<fmt:formatNumber> 使用指定的格式或精度格式化数字
<fmt:parseNumber> 解析一个代表着数字,货币或百分比的字符串
<fmt:formatDate> 使用指定的风格或模式格式化日期和时间
<fmt:parseDate> 解析一个代表着日期或时间的字符串
<fmt:bundle> 绑定资源
<fmt:setLocale> 指定地区
<fmt:setBundle> 绑定资源
<fmt:timeZone> 指定时区
<fmt:setTimeZone> 指定时区
<fmt:message> 显示资源配置文件信息
<fmt:requestEncoding> 设置 request 的字符编码

SQL 标签

JSTL SQL 标签库提供了与关系型数据库(Oracle,MySQL,SQL Server 等等)进行交互的标签。引用 SQL 标签库的语法如下:

1
<%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %>
标签 描述
<sql:setDataSource> 指定数据源
<sql:query> 运行 SQL 查询语句
<sql:update> 运行 SQL 更新语句
<sql:param> 将 SQL 语句中的参数设为指定值
<sql:dateParam> 将 SQL 语句中的日期参数设为指定的 java.util.Date 对象值
<sql:transaction> 在共享数据库连接中提供嵌套的数据库行为元素,将所有语句以一个事务的形式来运行

XML 标签

JSTL XML 标签库提供了创建和操作 XML 文档的标签。引用 XML 标签库的语法如下:

1
<%@ taglib prefix="x" uri="http://java.sun.com/jsp/jstl/xml" %>

在使用 xml 标签前,你必须将 XML 和 XPath 的相关包拷贝至你的 <Tomcat 安装目录>\lib 下:

标签 描述
<x:out> <%= ... >,类似,不过只用于 XPath 表达式
<x:parse> 解析 XML 数据
<x:set> 设置 XPath 表达式
<x:if> 判断 XPath 表达式,若为真,则执行本体中的内容,否则跳过本体
<x:forEach> 迭代 XML 文档中的节点
<x:choose> <x:when><x:otherwise> 的父标签
<x:when> <x:choose> 的子标签,用来进行条件判断
<x:otherwise> <x:choose> 的子标签,当 <x:when> 判断为 false 时被执行
<x:transform> 将 XSL 转换应用在 XML 文档中
<x:param> <x:transform> 共同使用,用于设置 XSL 样式表

JSTL 函数

JSTL 包含一系列标准函数,大部分是通用的字符串处理函数。引用 JSTL 函数库的语法如下:

1
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
函数 描述
fn:contains() 测试输入的字符串是否包含指定的子串
fn:containsIgnoreCase() 测试输入的字符串是否包含指定的子串,大小写不敏感
fn:endsWith() 测试输入的字符串是否以指定的后缀结尾
fn:escapeXml() 跳过可以作为 XML 标记的字符
fn:indexOf() 返回指定字符串在输入字符串中出现的位置
fn:join() 将数组中的元素合成一个字符串然后输出
fn:length() 返回字符串长度
fn:replace() 将输入字符串中指定的位置替换为指定的字符串然后返回
fn:split() 将字符串用指定的分隔符分隔然后组成一个子字符串数组并返回
fn:startsWith() 测试输入字符串是否以指定的前缀开始
fn:substring() 返回字符串的子集
fn:substringAfter() 返回字符串在指定子串之后的子集
fn:substringBefore() 返回字符串在指定子串之前的子集
fn:toLowerCase() 将字符串中的字符转为小写
fn:toUpperCase() 将字符串中的字符转为大写
fn:trim() 移除首尾的空白符

Taglib

JSP 自定义标签

自定义标签是用户定义的 JSP 语言元素。当 JSP 页面包含一个自定义标签时将被转化为 servlet,标签转化为对被 称为 tag handler 的对象的操作,即当 servlet 执行时 Web container 调用那些操作。

JSP 标签扩展可以让你创建新的标签并且可以直接插入到一个 JSP 页面。 JSP 2.0 规范中引入 Simple Tag Handlers 来编写这些自定义标记。

你可以继承 SimpleTagSupport 类并重写的 doTag()方法来开发一个最简单的自定义标签。

创建”Hello”标签

接下来,我们想创建一个自定义标签叫作ex:Hello,标签格式为:

1
<ex:Hello />

要创建自定义的 JSP 标签,你首先必须创建处理标签的 Java 类。所以,让我们创建一个 HelloTag 类,如下所示:

1
2
3
4
package com.runoob; import javax.servlet.jsp.tagext.*; import
javax.servlet.jsp.*; import java.io.*; public class HelloTag extends
SimpleTagSupport { public void doTag() throws JspException, IOException {
JspWriter out = getJspContext().getOut(); out.println("Hello Custom Tag!"); } }

以下代码重写了 doTag()方法,方法中使用了 getJspContext()方法来获取当前的 JspContext 对象,并将”Hello Custom Tag!”传递给 JspWriter 对象。

编译以上类,并将其复制到环境变量 CLASSPATH 目录中。最后创建如下标签库:<Tomcat安装目录>webapps\ROOT\WEB-INF\custom.tld

1
2
3
4
5
6
7
8
9
10
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>2.0</jsp-version>
<short-name>Example TLD</short-name>
<tag>
<name>Hello</name>
<tag-class>com.runoob.HelloTag</tag-class>
<body-content>empty</body-content>
</tag>
</taglib>

接下来,我们就可以在 JSP 文件中使用 Hello 标签:

1
2
3
4
5
6
7
8
9
<%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%>
<html>
<head>
<title>A sample custom tag</title>
</head>
<body>
<ex:Hello />
</body>
</html>

以上程序输出结果为:

1
Hello Custom Tag!

访问标签体

你可以像标准标签库一样在标签中包含消息内容。如我们要在我们自定义的 Hello 中包含内容,格式如下:

1
2
3
<ex:Hello>
This is message body
</ex:Hello>

我们可以修改标签处理类文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.runoob;

import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.io.*;

public class HelloTag extends SimpleTagSupport {

StringWriter sw = new StringWriter();
public void doTag()
throws JspException, IOException
{
getJspBody().invoke(sw);
getJspContext().getOut().println(sw.toString());
}

}

接下来我们需要修改 TLD 文件,如下所示:

1
2
3
4
5
6
7
8
9
10
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>2.0</jsp-version>
<short-name>Example TLD with Body</short-name>
<tag>
<name>Hello</name>
<tag-class>com.runoob.HelloTag</tag-class>
<body-content>scriptless</body-content>
</tag>
</taglib>

现在我们可以在 JSP 使用修改后的标签,如下所示:

1
2
3
4
5
6
7
8
9
10
11
<%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%>
<html>
<head>
<title>A sample custom tag</title>
</head>
<body>
<ex:Hello>
This is message body
</ex:Hello>
</body>
</html>

以上程序输出结果如下所示:

1
This is message body

自定义标签属性

你可以在自定义标准中设置各种属性,要接收属性,值自定义标签类必须实现 setter 方法, JavaBean 中的 setter 方法如下所示:

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
package com.runoob;

import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import java.io.*;

public class HelloTag extends SimpleTagSupport {

private String message;

public void setMessage(String msg) {
this.message = msg;
}

StringWriter sw = new StringWriter();

public void doTag()
throws JspException, IOException
{
if (message != null) {
/* 从属性中使用消息 */
JspWriter out = getJspContext().getOut();
out.println( message );
}
else {
/* 从内容体中使用消息 */
getJspBody().invoke(sw);
getJspContext().getOut().println(sw.toString());
}
}

}

属性的名称是”message”,所以 setter 方法是的 setMessage()。现在让我们在 TLD 文件中使用的 <attribute> 元素添加此属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
<taglib>
<tlib-version>1.0</tlib-version>
<jsp-version>2.0</jsp-version>
<short-name>Example TLD with Body</short-name>
<tag>
<name>Hello</name>
<tag-class>com.runoob.HelloTag</tag-class>
<body-content>scriptless</body-content>
<attribute>
<name>message</name>
</attribute>
</tag>
</taglib>

现在我们就可以在 JSP 文件中使用 message 属性了,如下所示:

1
2
3
4
5
6
7
8
9
<%@ taglib prefix="ex" uri="WEB-INF/custom.tld"%>
<html>
<head>
<title>A sample custom tag</title>
</head>
<body>
<ex:Hello message="This is custom tag" />
</body>
</html>

以上实例数据输出结果为:

1
This is custom tag

你还可以包含以下属性:

属性 描述
name 定义属性的名称。每个标签的是属性名称必须是唯一的。
required 指定属性是否是必须的或者可选的,如果设置为 false 为可选。
rtexprvalue 声明在运行表达式时,标签属性是否有效。
type 定义该属性的 Java 类类型 。默认指定为 String
description 描述信息
fragment 如果声明了该属性,属性值将被视为一个 JspFragment

以下是指定相关的属性实例:

1
2
3
4
5
6
7
8
.....
<attribute>
<name>attribute_name</name>
<required>false</required>
<type>java.util.Date</type>
<fragment>false</fragment>
</attribute>
.....

如果你使用了两个属性,修改 TLD 文件,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
.....
<attribute>
<name>attribute_name1</name>
<required>false</required>
<type>java.util.Boolean</type>
<fragment>false</fragment>
</attribute>
<attribute>
<name>attribute_name2</name>
<required>true</required>
<type>java.util.Date</type>
</attribute>
.....

JavaWeb 面经

Servlet

什么是 Servlet

Servlet(Server Applet),即小服务程序或服务连接器。Servlet 是 Java 编写的服务器端程序,具有独立于平台和协议的特性,主要功能在于交互式地浏览和生成数据,生成动态 Web 内容。

  • 狭义的 Servlet 是指 Java 实现的一个接口。
  • 广义的 Servlet 是指任何实现了这个 Servlet 接口的类。

Servlet 运行于支持 Java 的应用服务器中。从原理上讲,Servlet 可以响应任何类型的请求,但绝大多数情况下 Servlet 只用来扩展基于 HTTP 协议的 Web 服务器。

Servlet 和 CGI 的区别

Servlet 技术出现之前,Web 主要使用 CGI 技术。它们的区别如下:

  • Servlet 是基于 Java 编写的,处于服务器进程中,他能够通过多线程方式运行 service() 方法,一个实例可以服务于多个请求,而且一般不会销毁;
  • CGI(Common Gateway Interface),即通用网关接口。它会为每个请求产生新的进程,服务完成后销毁,所以效率上低于 Servlet。

Servlet 版本以及主要特性

版本 日期 JAVA EE/JDK 版本 特性
Servlet 4.0 2017 年 10 月 JavaEE 8 HTTP2
Servlet 3.1 2013 年 5 月 JavaEE 7 非阻塞 I/O,HTTP 协议升级机制
Servlet 3.0 2009 年 12 月 JavaEE 6, JavaSE 6 可插拔性,易于开发,异步 Servlet,安全性,文件上传
Servlet 2.5 2005 年 10 月 JavaEE 5, JavaSE 5 依赖 JavaSE 5,支持注解
Servlet 2.4 2003 年 11 月 J2EE 1.4, J2SE 1.3 web.xml 使用 XML Schema
Servlet 2.3 2001 年 8 月 J2EE 1.3, J2SE 1.2 Filter
Servlet 2.2 1999 年 8 月 J2EE 1.2, J2SE 1.2 成为 J2EE 标准
Servlet 2.1 1998 年 11 月 未指定 First official specification, added RequestDispatcher, ServletContext
Servlet 2.0 JDK 1.1 Part of Java Servlet Development Kit 2.0
Servlet 1.0 1997 年 6 月

Servlet 和 JSP 的区别

  1. Servlet 是一个运行在服务器上的 Java 类,依靠服务器支持向浏览器传输数据。
  2. JSP 本质上就是 Servlet,每次运行的时候 JSP 都会被编译成 .java 文件,然后再被编译成 .class 文件。
  3. 有了 JSP,Servlet 不再负责动态生成页面,转而去负责控制程序逻辑的作用,控制 JSP 与 JavaBean 之间的流转。
  4. JSP 侧重于视图,而 Servlet 侧重于控制逻辑,在 MVC 架构模式中,JSP 适合充当视图 View,Servlet 适合充当控制器 Controller。

简述 Servlet 生命周期

img

Servlet 生命周期如下:

  1. 加载 - 第一个到达服务器的 HTTP 请求被委派到 Servlet 容器。容器通过类加载器使用 Servlet 类对应的文件加载 servlet;
  2. 初始化 - Servlet 通过调用 init () 方法进行初始化。
  3. 服务 - Servlet 调用 service() 方法来处理客户端的请求。
  4. 销毁 - Servlet 通过调用 destroy() 方法终止(结束)。
  5. 卸载 - Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。

如何现实 servlet 的单线程模式

1
<%@ page isThreadSafe="false" %>

Servlet 中如何获取用户提交的查询参数或者表单数据

  • HttpServletRequest 的 getParameter() 方法。
  • HttpServletRequest 的 getParameterValues() 方法。
  • HttpServletRequest 的 getParameterMap() 方法。

request 的主要方法

  • setAttribute(String name,Object):设置名字为 name 的 request 的参数值
  • getAttribute(String name):返回由 name 指定的属性值
  • getAttributeNames():返回 request 对象所有属性的名字集合,结果是一个枚举的实例
  • getCookies():返回客户端的所有 Cookie 对象,结果是一个 Cookie 数组
  • getCharacterEncoding():返回请求中的字符编码方式
  • getContentLength():返回请求的 Body 的长度
  • getHeader(String name):获得 HTTP 协议定义的文件头信息
  • getHeaders(String name):返回指定名字的 request Header 的所有值,结果是一个枚举的实例
  • getHeaderNames():返回所以 request Header 的名字,结果是一个枚举的实例
  • getInputStream():返回请求的输入流,用于获得请求中的数据 getMethod():获得客户端向服务器端传送数据的方法
  • getParameter(String name):获得客户端传送给服务器端的有 name 指定的参数值
  • getParameterNames():获得客户端传送给服务器端的所有参数的名字,结果是一个枚举的实例
  • getParameterValues(String name):获得有 name 指定的参数的所有值
  • getProtocol():获取客户端向服务器端传送数据所依据的协议名称
  • getQueryString():获得查询字符串
  • getRequestURI():获取发出请求字符串的客户端地址
  • getRemoteAddr():获取客户端的 IP 地址
  • getRemoteHost():获取客户端的名字
  • getSession([Boolean create]):返回和请求相关
  • Session getServerName():获取服务器的名字
  • getServletPath():获取客户端所请求的脚本文件的路径
  • getServerPort():获取服务器的端口号
  • removeAttribute(String name):删除请求中的一个属性

JSP

JSP 的内置对象

  1. request:包含客户端请求的信息
  2. response:包含服务器传回客户端的响应信息
  3. session:主要用来区分每个用户信息和会话状态
  4. pageContext:管理页面属性
  5. application:服务器启动时创建,服务器关闭时停止,保存所有应用系统中的共有数据,一个共享的内置对象(即一个容器中的多个用户共享一个 application 对象);
  6. out:向客户端输出数据
  7. config:代码片段配置对象,用于初始化 Servlet 的配置参数
  8. page:指网页本身
  9. exception:处理 JSP 文件执行时发生的错误和异常,只要在错误页面里才能使用。

JSP 的作用域

  1. page:一个页面;
  2. request:一次请求;
  3. session:一次会话;
  4. application:服务器从启动到停止。

JSP 中 7 个动作指令和作用

  1. jsp:forward - 执行页面转向,把请求转发到下一个页面;
  2. jsp:param - 用于传递参数,必须与其他支持参数的标签一起使用;
  3. jsp:include - 用于动态引入一个 JSP 页面
  4. jsp:plugin - 用于下载 JavaBean 或 Applet 到客户端执行
  5. jsp:useBean - 寻求或者实例化一个 JavaBean;
  6. jsp:setProperty - 设置 JavaBean 的属性值;
  7. jsp:getProperty - 获取 JavaBean 的属性值。

JSP 中动态 INCLUDE 和静态 INCLUDE 有什么区别

  • 静态 INCLUDE:用 include 伪码实现,不会检查所含文件的变化,适用于包含静态页面<%@ include file=”页面名称.html” %>先合并再编译
  • 动态 INCLUDE:用 jsp:include 动作实现 <jsp:include page=”页面名称 .jsp” flush=”true”> 它总是会检查文件中的变化,适用于包含动态页面,并且可以带参数先编译再合并

原理

请求转发(forward)和重定向(redirect)的区别

  • 效率上
    • 转发(forward) > 重定向(redirect)
  • 显示上
    • 重定向(redirect):显示新的 URL
    • 转发(forward):地址栏不变
  • 数据上
    • 转发(forward):可以共享 request 里面的数据
    • 重定向(redirect):不能
  • 请求次数
    • 重定向(redirect)是两次
    • 转发(forward)是一次

get 请求和 post 请求的区别

img

  • GET:
    • 从服务器上获取数据,一般不能使用在写操作接口
    • 由 URL 所限制,GET 方式传输的数据大小有所限制,传送的数据量不超过 2KB
    • 请求的数据会附加在 URL 之后,以?分隔 URL 和传输数据,多个参数用&连接
    • 安全性差
  • POST:
    • 向服务器提交数据,一般处理写业务
    • POST 方式传送的数据量比较大,一般被默认为没有限制
    • 安全性高
    • 请的求的数据内容放置在 HTML HEADER 中

用户在浏览器中输入 URL 之后,发什么了什么?写出请求和响应的流程

  1. 域名解析
  2. TCP 三次握手
  3. 浏览器向服务器发送 http 请求
  4. 浏览器发送请求头信息
  5. 服务器处理请求
  6. 服务器做出应答
  7. 服务器发送应答头信息
  8. 服务器发送数据
  9. TCP 连接关闭

什么是 Web Service?

  1. WebService 就是一个应用程序,它向外界暴露出一个能够通过 Web 进行调用的 API。
  2. 它是基于 HTTP 协议传输数据,这使得运行在不同机上的不同应用程序,无须借助附加的、专门的第三方 软件或硬件,就可以相互交换数据或集成。

会话跟踪技术有哪些?

由于 HTTP 协议本身是无状态的,服务器为了区分不同的用户,就需要对用户会话进行跟踪,简单的说就是为用户进行登记,为用户分配唯一的 ID,下一次用户在请求中包含此 ID,服务器根据此判断到底是哪一个用户。

  • URL 重写:在 URL 中添加会话信息作为请求的参数,或者将唯一的会话 ID 添加到 URL 结尾,以表示一个会话。设置表单隐藏域:将和会话跟踪相关的字段添加到隐藏域中,这些信息不会在浏览器显示,但是提交表单时会提交给服务器。
  • cookie:cookie 有两种:
    • 一种是基于窗口的,浏览器关闭后,cookie 就没有了;
    • 另一种是将信息存储在一个临时文件中,并设置其有效路径和最大存活时间。当用户通过浏览器和服务器建立一次会话后,会话 ID 就会随相应信息储存在基于窗口的 cookie 中,那就意味着只要浏览器没有关闭,会话没有超时,下一次请求时这个会话 ID 又会提交给服务器,让服务器识别用户身份。
    • 在使用 cookie 时要注意几点:
      • 首先不要在 cookie 中存放敏 感信息;
      • 其次 cookie 存储的数据量有限(4k),不能将过多的内容存储 cookie 中;
      • 再者浏览器通常只允许一个站点最多存放 20 个 cookie。
      • 当然,和用户会话相关的其他信息(除了会话 ID)也可以存在 cookie 方便进行会话 跟踪;
  • HttpSession:在所有会话跟踪技术中,HttpSession 对象是最强大也是功能最多的。当一个用户第一次访问某个网站时会自动创建 HttpSession,每个用户可以访问他自己的 HttpSession。可以通过 HttpServletRequest 对象的 getSession 方法获得 HttpSession,通过 HttpSession 的 setAttribute 方法可以将一个值放在 HttpSession 中,通过调用 HttpSession 对象的 getAttribute 方法,同时传入属性名就可以获取保存在 HttpSession 中的对象。
    • 与上面三种方式不同的是,HttpSession 放在服务器的内存中,因此不要将过大的对象放在里面,即使目前的 Servlet 容器可以在内存将满时将 HttpSession 中的对象移到其他存储设备中,但是这样势必影响性能。
    • 添加到 HttpSession 中 的值可以是任意 Java 对象,这个对象最好实现了 Serializable 接口,这样 Servlet 容器在必要的时候可以将其序列 化到文件中,否则在序列化时就会出现异常。

响应结果状态码有哪些,并给出中文含义?

  • 1**:信息性状态码
  • 2**:成功状态码
    • 200:请求正常成功
    • 204:指示请求成功但没有返回新信息
    • 206:指示服务器已完成对资源的部分 GET 请求
  • 3**:重定向状态码
    • 301:永久性重定向
    • 302:临时性重定向
    • 304:服务器端允许请求访问资源,但未满足条件
  • 4**:客户端错误状态码
    • 400:请求报文中存在语法错误
    • 401:发送的请求需要有通过 HTTP 认证的认证信息
    • 403:对请求资源的访问被服务器拒绝了
    • 404:服务器上无法找到请求的资源
  • 5**:服务器错误状态码
    • 500:服务器端在执行请求时发生了错误
    • 503:服务器暂时处于超负载或正在进行停机维护,现在无法处理请求

XML 文档定义有几种形式?它们之间有何本质区别?解析 XML 文档有哪几种方式?

(1)XML 文档有两种约束方式:

  1. DTD 约束
  2. Schema 约束

(2)XML 文档区别:
1 DTD 不符合 XML 的语法结构,schema 符合 XML 的语法结构;
2 DTD 的约束扩展性比较差,XML 文档只能引入一个 DTD 的文件。schema 可以引入多个文件;
3 DTD 不支持名称空间(理解包结构),schema 支持名称空间;
4 DTD 支持数据比较少,schema 支持更多的数据类型;

(3)解析方式主要有三种:

  • DOM 解析:
    • (a)加载整个 xml 的文档到内存中,形成树状结构,生成对象;
    • (b)容易产生内存溢出;
    • (c)可以做增删改
  • SAX 解析
    • (a)边读边解析;
    • (b)不可以做增删改
  • DOM4J 解析(hibernate 底层采用)
    • (a)可让 SAX 解析也产生树状结构。
    • (b)主要 api 开发步骤:
      • 1)SAXReader.read(xxx.xml)代表解析 xml 的文档,返回对象是 Document;
      • 2)Document.getRootElement(),返回的是文档的根节点,是 Element 对象;
      • 3)Element:
        • .element(…)– 获得指定名称第一个子元素。可以不指定名称;
        • .elements(…)– 获得指定名称的所有子元素。可以不指定名称;
        • .getText()– 获得当前元素的文本内容;
        • .elementText(…)– 获得指定名称子元素的文本值
        • .addElement()– 添加子节点
        • .setText()– 设置子标签内容
      • 4)XMLWriter.write(“..”)– 写出
      • 5)XMLWriter.close()– 关闭输出流

参考资料

SkyWalking 快速入门

SkyWalking 是一个基于 Java 开发的分布式系统的应用程序性能监视工具,专为微服务、云原生架构和基于容器(Docker、K8s、Mesos)架构而设计。

一、SkyWalking 简介

SkyWalking 是观察性分析平台和应用性能管理系统。

提供分布式追踪、服务网格遥测分析、度量聚合和可视化一体化解决方案。

img

SkyWalking 特性

  • 多种监控手段,语言探针和 service mesh
  • 多语言自动探针,Java,.NET Core 和 Node.JS
  • 轻量高效,不需要大数据
  • 模块化,UI、存储、集群管理多种机制可选
  • 支持告警
  • 优秀的可视化方案

SkyWalking 核心概念

  • Service - 服务。代表一组为传入请求提供相同的行为的工作负载。 使用代理或 SDK 时,可以定义服务名称。
  • Service Instance - 服务实例。服务组中的每个工作负载都称为一个实例。就像 Kubernetes 中的 Pod 一样,它在 OS 中不必是单个进程。
  • Endpoint - 端点。它是特定服务中用于传入请求的路径,例如 HTTP URI 路径或 RPC 服务类+方法签名。

二、SkyWalking 架构

从逻辑上讲,SkyWalking 分为四个部分:探针(Probes),平台后端,存储和 UI。

SkyWalking 架构

  • 探针(Probes) - 探针是指集成到目标系统中的代理或 SDK 库。它们负责收集数据(包括跟踪数据和统计数据)并将其按照 SkyWalking 的要求重新格式化为。
  • 平台后端 - 平台后端是一个提供后端服务的集群。它用于聚合、分析和驱动从探针到 UI 的流程。它还为传入格式(如 Zipkin 的格式),存储实现程序和集群管理提供可插入功能。 您甚至可以使用 Observability Analysis Language 自定义聚合和分析。
  • 存储 - 您可以选择一个 SkyWalking 已实现的存储,如由 Sharding-Sphere 管理的 ElasticSearch,H2 或 MySQL 集群,也可以自行实现一个存储。
  • UI - 用户界面很酷,对于 SkyWalking 最终用户而言非常强大。它也可以自定义以匹配您的自定义后端。

三、SkyWalking 安装

进入 Apache SkyWalking 官方下载页面,选择安装版本,下载解压到本地。

SkyWalking 组件

安装分为三个部分:

参考资料