Unittest

单元测试基本原则

宏观上,单元测试要符合AIR原则;微观上,单元测试的代码层面要符合BCDE原则

AIR 原则

单元测试的价值在业务代码线上运行时,难以感觉到器测试用例的存在和价值,但在代码质量的保障上,却是非常关键的。新增代码应该同步增加测试用例,修改代码逻辑是也应该同步保证测试用例成功执行。 AIR原则具体包括:

  • A: 自动化 Automatic
  • I: 独立性 Independent
  • R: 可重复 Repeatable

单元测试应该是全自动执行的。测试用例通常会被频繁的触发执行,执行过程必须完全自动化才有意义。如果单元测试的输出结果需要人工介入检查,那么它一定是不合格的。单元测试中不允许使用控制台输出来进行人工验证,而必须使用断言来验证。

为了保证单元测试稳定可靠且便于维护,需要保证其独立性。用例之间不允许相互调用,也不允许出现执行次序的先后依赖。 如: testFunc2()需要调用testFunc1(),在执行testFunc2时会重复执行验证testFunc1,导致运行效率降低,且testFun1的验证失败会影响testFun2的执行。通常主流的测试框架中,测试用例的执行顺序都是无序的。

单元测试时可以重复执行的,不能收到外界环境的影响。比如单元测试通常会被放到持续集成中,每次有代码提交时单元测试都会被触发执行。如果单测对外部环境(网络、服务、中间件等)有依赖,则容易导致持续集成机制的不可用。

BCDE 原则

编写单元测试时要保证测试粒度足够小,这样有助于精确定位问题,单元测试用例默认是方法级别的。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试需要覆盖的范围。编写单元测试用例时,为了保证北侧模块的交付质量,需要符合BCDE原则。

  • B: Border,边界值测试,包括循环便捷、特殊取值、特殊时间点、数据顺序等。
  • C: Correct,正确的输入,并得到预期的结果。
  • D: Design,与设计文档相结合,来编写单元测试。
  • E: Error,单元测试的目的是证明程序有错,而不是程无错。为了发现代码中潜在的错误,我们需要在编写测试用例时有一些强制的错误输入(如非法数据、异常流程、非业务允许输入等)来得到预期的错误结果。

由于单元测试只是系统集成测试前的小模块测试,有些因素往往是不具备的,因此需要进行Mock,例如:

  1. 功能因素。如:被测试方法内部调用的功能不可用。
  2. 时间因素。如:关键日企还没到来,与此时间相关的功能点。
  3. 环境因素。如:政策环境,多端环境;
  4. 数据因素。如:线下数据样本过小,难以覆盖各种线上真是场景。
  5. 其他因素。为了简化测试编写,开发者也可以将一些负责的依赖采用Mock方式实现。

最简单的Mock方式是硬编码,更为优雅的方式是使用配置文件,最佳的方式是使用相应的Mock框架。Mock的本质是让我们写出更稳定的单元测试,隔离上述因素对单元测试的影响,使结果变得可预测,做到真正的单元测试。

单元测试覆盖率

单元测试是一种白盒测试,测试者依据程序的内部结果来实现测试代码。单测的覆盖率是指业务代码被单测测试的比例和程度,它是衡量单元测试好坏的一个很重要的指标,各种覆盖率从粗到细、从弱到强排列如下:

粗粒度的覆盖

  • 类覆盖
  • 方法覆盖

细粒度的覆盖

  • 行覆盖
  • 分支覆盖
  • 条件判定覆盖
  • 条件组合覆盖
  • 路径覆盖

golang

testing

func TestAdd(t *testing.T) {
    got := Add(10)
    if got == 11 {
        t.Errorf("wrong result of Add(10)")
    }
}
  • 每个test文件必须import一个testing

  • test文件文件下的每一个testcase 均必须以Test开头并且符合TestXxxx形式,否则go test会直接跳过测试不执行;

  • testcase 的入参为t *testing.Tb *testing.B

  • t.SkipNow()为跳过当前test,并且直接按pass处理继续下一个test

    如果遇到testcase不想跑,或者testcase暂时有问题但是短时间内fix不了而以后肯定是会用的,此时会用t.SkipNow()跳过当前testcase并继续下一个testcase。

    t.skipNow()一定要写在testcase的第一行。写在中间或者末尾则不起作用

  • go的test不会保证多个testcase是顺序执行的,但通常会按顺序执行。

    当遇到多个testcase可能会互相依赖,比如testcase1的结果作为testcase2的输入,或者多个testcase必须得按顺序执行同时结果也得按顺序输出,此时testcase是有顺序的,可以使用subtests的方式解决。

    使用t.Run来执行subtests可以控制testcases的输出以及执行顺序;

    func TestPrint(t *testing.T) {
        t.Run("a1", func(t *testing.T) { fmt.Println("a1"))t.Run("a2", func(t *testing.T) { fmt.Println("a2"))
        t.Run("a3", func(t *testing.T) { fmt.Println("a3"))
    }
    
  • 使用TestMain作为初始化test,并且使用m.Run()来调用其他tests可以完成一些需要初始化操作的testing,比如数据库连接、文件打开、服务登录等。

    func TestMain(m *testing.M) {
        fmt.Println("run test main first")
        m.Run()
    }
    

    如果没有在TestMain中调用m.Run(),则除了TestMain以外的其他tests都不会执行

BenchMark

  • benchmark函数一般以Benchmark为前缀
  • benchmark函数一般会会跑b.N次,而且每次执行都会如此
  • 在执行过程中会根据实际case的执行时间是否稳定来增加b.N的次数已达到稳态
$ go test -bench=.
func BenchmarkAll(b *testing.B) {
    for n := 0; n < b.N; n++ {
        Add(n)
    }
}
func unstable(n int) int {
    if n > 0 {
        n--
    }
    return n
}
func BenchMarUnstable(b *testing.B) {
    for n := 0; n < b.N; n++ {
        unstable(n)
    }
}

Media-Basic

[TOC]

音视频基础知识

视频基础

RGB

数字图像使用RGB表示

  • R: 红光
  • G: 绿光
  • B: 蓝光

每个RGB分量可以用8bit表示;(256个色度),例如:

  • #FF0000 = 红色

  • #FFFFFF = 白色

也有使用10bit表示的

  • RGBA_8888: RGBA分量各占8bit(A表示alpha不透明度参数)
  • RGB_565: R分量5bit,G分量6bit,B分量5bit
  • RGB_888:RGB分量各8bit

720的RGB_888图像的大小:

1280 * 720 * 3 = 2.637MB

如果是90分钟电影,每秒25帧,则大小:

2.637MB * 90 * 15 = 347.651MB

因此需要压缩;

  • 图像压缩:jpeg, png…
  • 视频压缩: h264, h265…

YUV

YUV主要应用于优化彩色视频信号的传输,使其向后兼容老式黑白电视。

“Y”表示明亮度(Luminance或Luma),也称灰阶值;而“U”和“V”表示的则是色度(Chrominance或Chroma),它们的作用是描述影像的色彩及饱和度,用于指定像素的颜色。

YUV格式有两大类: planar和packed

  • planar: 先连续存储所有像素点的Y,接着存储所有像素点的U,随后是所有像素点的V;
    YYYYYYYYYYYY
    YYYYYYYYYYYY
    UUUUUUUUUUUU
    UUUUUUUUUUUU
    VVVVVVVVVVVV
    VVVVVVVVVVVV
    
    YUVYUVYUVYUV
    YUVYUVYUVYUV
    YUVYUVYUVYUV
    YUVYUVYUVYUV
    
  • packed:每个像素点的Y、U、V是连续交替存储的;
    YUVYUVYUVYUV
    YUVYUVYUVYUV
    YUVYUVYUVYUV
    YUVYUVYUVYUV
    
    YUV存在多种格式,不同的YUV格式的数据在存储时的排列顺序不一样。开发时不注意会导致画面不正常,如:花屏、绿屏等
  • YUV 4:4:4 每一个Y分量对应一组UV分量
  • YUV 4:2:2 每两个Y分量共用一组UV分量
  • YUV 4:2:0 每四个Y分量共用一组UV分量

YUV420p

YYYYYYYY
YYYYYYYY
YYYYYYYY
YYYYYYYY
UUUUUUUU
VVVVVVVV

YUV420sp

YYYYYYYY
YYYYYYYY
YYYYYYYY
YYYYYYYY
UVUVUVUV
UVUVUVUV

相较于RGB,我们可以计算一帧为1280×720的视频帧,用YUV420P的格式来表示,其数据量的大小如下

1280*720*1+1280*720*0.5 = 1.318MB

如果fps(1秒的视频帧数目)是25,按照一般电影的长度90分钟来计算,那么这部电影用YUV420P的数据格式来表示的话,其数据量的大小就是:

1.318MB*25fps*90min*60s = 173.76GB

libyuv: google开源的实现各种YUV和RGB之间相互转换、旋转、缩放、裁剪的库。

主要概念

  • 视频码率:kb/s, 视频文件在单位时间内使用的数据流量,也叫码流率。码率越大,说明单位时间内采样率越大,数据流精度就越高;
  • 视频帧率:fps,单位时间内的视频帧数,准率越高,给人的视觉越流畅。

I帧

Infra coded frames

I帧通常是每个GOP(MPEG所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,作为随机访问的参考点,可以当成静态图像。I帧可以看作一个图像经过压缩后的产物,I帧压缩可以得到6:1的压缩比而不会产生任何可觉察的模糊现象。

I帧自身可以通过视频解压算法解压成一张单独的完整视频画面,所以I帧去掉的是视频帧在空间维度上的冗余信息。

P帧

前向预测编码帧(predictiveframe),通过将图像序列中前面已编码帧的时间冗余信息充分去除来压缩传输数据量的编码图像,也称为预测帧。

P帧需要参考其前面的一个I帧或者P帧来解码成一张完整的视频画面。

B帧

双向预测内插编码帧(bidirectionalinterpolatedpredictionframe),既考虑源图像序列前面的已编码帧,又顾及源图像序列后面的已编码帧之间的时间冗余信息,来压缩传输数据量的编码图像,也称为双向预测帧。

B帧则需要参考其前一个I帧或者P帧及其后面的一个P帧来生成一张完整的视频画面,所以P帧与B帧去掉的是视频帧在时间维度上的冗余信息。

GOP

两个I帧之间形成的一组图片,就是GOP(Group Of Picture)的概念。

|I|P|B|B|P|B|B|I|B|B|B|......
|<--  GOP  -->|

常用视频压缩算法

  • MPEG2 (MPEG)
  • H264 (MPEG)
  • H265 (MPEG)
  • AVS (中国)
  • VP8 (Google)
  • VP9 (Google)

H264

视频编码分析工具:Elecard streameye tools

NALU

TODO

帧内预测

TODO

帧间预测

TODO

HEVC

TODO

音频基础

物理特性

声音是由物体振动而产生的

  • 频率:频率(过零率)越高,波长就越短。低频声响的波长则较长,所以其可以更容易地绕过障碍物,因此能量衰减就小,声音就会传得远,反之则会得到完全相反的结论。

  • 响度:响度其实就是能量大小的反映,用不同的力度敲击桌子,声音的大小势必也会不同。在生活中,分贝常用于描述响度的大小。声音超过一定的分贝,人类的耳朵就会受不了。

  • 音色:音色其实也不难理解,在同样的音调(频率)和响度(振幅)下,钢琴和小提琴的声音听起来是完全不相同的,因为它们的音色不同。波的形状决定了其所代表声音的音色,钢琴和小提琴的音色不同就是因为它们的介质所产生的波形不同。

音频主要概念

  • 比特率: 每秒传输的bit数,单位为bps,是简介衡量声音质量的一个标准。没有压缩的音频数据采样率=采样频率采样精度通道数
  • 码率:压缩后的音频数据比特率。常见的码率:
    • 96kbps:FM质量
    • 28-160kbps:一般质量音频
    • 192kbps: CD质量
    • 256-320kbps:高质量音频
      码率越大,压缩效率越低,音质越好,压缩后数据越大;
      码率=音频文件大小/时长

音频帧

音频的帧的概念没有视频帧那么清晰。

音频压缩单位:一帧多少个采样点

  • aac: 通常1024,有时2048(16khz)
  • mp3: 通常1152

帧长:

  • 可以指每帧采样数播放的时间,如:mp3 48k, 1152个采样点,每帧则为24ms;aac则是每帧1024个采样点。攒够一帧的数据才送去编码。
  • 也可以指压缩后每帧的数据长度。
    所以讲到帧的时候要注意其适用的场合。

每帧的持续时间=每帧的采样点数/采样频率(HZ)

交错模式:数据音频信号的存储方式。数据以连续帧的方式存放,即首先记录帧1的左声道样本和右声道样本,再开始帧2的记录…

1024个采样点一帧为例
|0 |1 |2 |3 |...|1023
|LR|LR|LR|LR|...|LR|......

非交错模式:首先记录的是一个周期内所有帧的左声道样本,再记录所有右声道样本。

1024个采样点一帧为例
0......1023
LLLLLL...L
RRRRR....R
LLLLLL...L
RRRRR....R

PCM

TODO

AAC

TODO

OGG

TODO

封装格式基础

封装格式(也叫容器)就是将已经编码压缩好的视频流、音频流、字幕按照一定方案放到一个文件中,便于播放软件播放。一般来说,视频文件的后缀名,就是它的封装格式。

封装格式不一样,后缀名也就不一样。

FLV

流式

------
Header
------
Audio
------
Video
------
Audio
------
Video
------
......

适合直播

TODO

MP4

索引

-----------
Header
-----------
audio index
-----------
video index
-----------
audio index
-----------
video index
-----------
......
-----------
audio
-----------
video
-----------
audio
-----------
video
-----------

适合本地播放

传输协议基础

RTMP

TODO

HLS

苹果推出的视频传输协议

https://developer.apple.com/streaming/

HTTP Live Streaming

TODO

m3u8

按一定格式编写的文本文件,m3u8 = m3u + utf8
TODO

RTSP

RFC 2326 - Real Time Streaming Protocol

类http文本

方法定义

Options
Describe
Announce
Setup
Play
Pause
Teardown

TODO

RTCP

Real Time Control Protocol (RTCP) attribute in Session Description Protocol (SDP)
TODO

RTP

RFC 3550 - RTP: A Transport Protocol for Real-Time Application

TODO

SDP

会话描述协议
TODO

WebRTC

TODO

音视频同步基础

DTS

Decoding Time Stamp

PTS

Presentation Time Stamp

音视频同步方式

  • Audio Master: 同步视频到音频
  • Video Master: 同步音频到视频
  • External Clock Master: 同步音频和视频到外部时钟

一般情况下:
Audio Master > Video Master > External Clock Master

直播简要流程

graph LR
A[摄像头采集]-->B[YUV420P]
B --> C[H264压缩]
C --> D[RTMP传输]
D --> E[H264解码]
E --> F[YUV420p]
F --> G[RGB显示]

开源项目

ffmpeg

http://ffmpeg.org

TODO

avpacket

mpegts

avfilter

……
TODO

librtmp

TODO

srs

simple rtmp server

https://github.com/ossrs/srs

TODO

nginx-rtmp-module

https://github.com/arut/nginx-rtmp-module

TODO

janus

https://github.com/meetecho/janus-gateway

TODO

live555

http://www.live555.com

TODO

ijkplayer

https://github.com/Bilibili/ijkplayer

参考资料

书籍

  1. WebRTC权威指南
  2. 音视频开发进阶指南:基于Android和iOS平台的实践
  3. FFmpeg从入门到精通
  4. 新一代视频压缩编码标准:H.264/AVC

博客

  1. https://blog.csdn.net/leixiaohua1020

go slice

Go 切片:用法和本质

2011/01/05

引言

Go的切片类型为处理同类型数据序列提供一个方便而高效的方式。 切片有些类似于其他语言中的数组,但是有一些不同寻常的特性。 本文将深入切片的本质,并讲解它的用法。

数组

Go的切片是在数组之上的抽象数据类型,因此在了解切片之前必须要先理解数组。

数组类型定义了长度和元素类型。例如, [4]int 类型表示一个四个整数的数组。 数组的长度是固定的,长度是数组类型的一部分( [4]int[5]int 是完全不同的类型)。 数组可以以常规的索引方式访问,表达式 s[n] 访问数组的第 n 个元素。

var a [4]int
a[0] = 1
i := a[0]
// i == 1

数组不需要显式的初始化;数组的零值是可以直接使用的,数组元素会自动初始化为其对应类型的零值:

// a[2] == 0, int 类型的零值

类型 [4]int 对应内存中四个连续的整数:

img

Go的数组是值语义。一个数组变量表示整个数组,它不是指向第一个元素的指针(不像 C 语言的数组)。 当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。 (为了避免复制数组,你可以传递一个指向数组的指针,但是数组指针并不是数组。) 可以将数组看作一个特殊的struct,结构的字段名对应数组的索引,同时成员的数目固定。

数组的字面值像这样:

b := [2]string{"Penn", "Teller"}

当然,也可以让编译器统计数组字面值中元素的数目:

b := [...]string{"Penn", "Teller"}

这两种写法, b 都是对应 [2]string 类型。

切片

数组虽然有适用它们的地方,但是数组不够灵活,因此在Go代码中数组使用的并不多。 但是,切片则使用得相当广泛。切片基于数组构建,但是提供更强的功能和便利。

切片类型的写法是 []TT 是切片元素的类型。和数组不同的是,切片类型并没有给定固定的长度。

切片的字面值和数组字面值很像,不过切片没有指定元素个数:

letters := []string{"a", "b", "c", "d"}

切片可以使用内置函数 make 创建,函数签名为:

func make([]T, len, cap) []T

其中T代表被创建的切片元素的类型。函数 make 接受一个类型、一个长度和一个可选的容量参数。 调用 make 时,内部会分配一个数组,然后返回数组对应的切片。

var s []byte
s = make([]byte, 5, 5)
// s == []byte{0, 0, 0, 0, 0}

当容量参数被忽略时,它默认为指定的长度。下面是简洁的写法:

s := make([]byte, 5)

可以使用内置函数 lencap 获取切片的长度和容量信息。

len(s) == 5
cap(s) == 5

接下来的两个小节将讨论长度和容量之间的关系。

切片的零值为 nil 。对于切片的零值, lencap 都将返回0。

切片也可以基于现有的切片或数组生成。切分的范围由两个由冒号分割的索引对应的半开区间指定。 例如,表达式 b[1:4]创建的切片引用数组 b 的第1到3个元素空间(对应切片的索引为0到2)。

b := []byte{'g', 'o', 'l', 'a', 'n', 'g'}
// b[1:4] == []byte{'o', 'l', 'a'}, sharing the same storage as b

切片的开始和结束的索引都是可选的;它们分别默认为零和数组的长度。

// b[:2] == []byte{'g', 'o'}
// b[2:] == []byte{'l', 'a', 'n', 'g'}
// b[:] == b

下面语法也是基于数组创建一个切片:

x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // a slice referencing the storage of x

切片的内幕

一个切片是一个数组片段的描述。它包含了指向数组的指针,片段的长度, 和容量(片段的最大长度)。

img

前面使用 make([]byte, 5) 创建的切片变量 s 的结构如下:

img

长度是切片引用的元素数目。容量是底层数组的元素数目(从切片指针开始)。 关于长度和容量和区域将在下一个例子说明。

我们继续对 s 进行切片,观察切片的数据结构和它引用的底层数组:

s = s[2:4]

img

切片操作并不复制切片指向的元素。它创建一个新的切片并复用原来切片的底层数组。 这使得切片操作和数组索引一样高效。因此,通过一个新切片修改元素会影响到原始切片的对应元素。

d := []byte{'r', 'o', 'a', 'd'}
e := d[2:]
// e == []byte{'a', 'd'}
e[1] = 'm'
// e == []byte{'a', 'm'}
// d == []byte{'r', 'o', 'a', 'm'}

前面创建的切片 s 长度小于它的容量。我们可以增长切片的长度为它的容量:

s = s[:cap(s)]

img

切片增长不能超出其容量。增长超出切片容量将会导致运行时异常,就像切片或数组的索引超 出范围引起异常一样。同样,不能使用小于零的索引去访问切片之前的元素。

切片的生长(copy and append 函数)

要增加切片的容量必须创建一个新的、更大容量的切片,然后将原有切片的内容复制到新的切片。 整个技术是一些支持动态数组语言的常见实现。下面的例子将切片 s 容量翻倍,先创建一个2倍 容量的新切片 t ,复制 s 的元素到 t ,然后将 t赋值给 s

t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
for i := range s {
        t[i] = s[i]
}
s = t

循环中复制的操作可以由 copy 内置函数替代。copy 函数将源切片的元素复制到目的切片。 它返回复制元素的数目。

func copy(dst, src []T) int

copy 函数支持不同长度的切片之间的复制(它只复制较短切片的长度个元素)。 此外, copy 函数可以正确处理源和目的切片有重叠的情况。

使用 copy 函数,我们可以简化上面的代码片段:

t := make([]byte, len(s), (cap(s)+1)*2)
copy(t, s)
s = t

一个常见的操作是将数据追加到切片的尾部。下面的函数将元素追加到切片尾部, 必要的话会增加切片的容量,最后返回更新的切片:

func AppendByte(slice []byte, data ...byte) []byte {
    m := len(slice)
    n := m + len(data)
    if n > cap(slice) { // if necessary, reallocate
        // allocate double what's needed, for future growth.
        newSlice := make([]byte, (n+1)*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:n]
    copy(slice[m:n], data)
    return slice
}

下面是 AppendByte 的一种用法:

p := []byte{2, 3, 5}
p = AppendByte(p, 7, 11, 13)
// p == []byte{2, 3, 5, 7, 11, 13}

类似 AppendByte 的函数比较实用,因为它提供了切片容量增长的完全控制。 根据程序的特点,可能希望分配较小的活较大的块,或则是超过某个大小再分配。

但大多数程序不需要完全的控制,因此Go提供了一个内置函数 append , 用于大多数场合;它的函数签名:

func append(s []T, x ...T) []T

append 函数将 x 追加到切片 s 的末尾,并且在必要的时候增加容量。

a := make([]int, 1)
// a == []int{0}
a = append(a, 1, 2, 3)
// a == []int{0, 1, 2, 3}

如果是要将一个切片追加到另一个切片尾部,需要使用 ... 语法将第2个参数展开为参数列表。

a := []string{"John", "Paul"}
b := []string{"George", "Ringo", "Pete"}
a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])"
// a == []string{"John", "Paul", "George", "Ringo", "Pete"}

由于切片的零值 nil 用起来就像一个长度为零的切片,我们可以声明一个切片变量然后在循环 中向它追加数据:

// Filter returns a new slice holding only
// the elements of s that satisfy fn()
func Filter(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

可能的“陷阱”

正如前面所说,切片操作并不会复制底层的数组。整个数组将被保存在内存中,直到它不再被引用。 有时候可能会因为一个小的内存引用导致保存所有的数据。

例如, FindDigits 函数加载整个文件到内存,然后搜索第一个连续的数字,最后结果以切片方式返回。

var digitRegexp = regexp.MustCompile("[0-9]+")

func FindDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    return digitRegexp.Find(b)
}

这段代码的行为和描述类似,返回的 []byte 指向保存整个文件的数组。因为切片引用了原始的数组, 导致 GC 不能释放数组的空间;只用到少数几个字节却导致整个文件的内容都一直保存在内存里。

要修复整个问题,可以将感兴趣的数据复制到一个新的切片中:

func CopyDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    b = digitRegexp.Find(b)
    c := make([]byte, len(b))
    copy(c, b)
    return c
}

可以使用 append 实现一个更简洁的版本。这留给读者作为练习。

Django

WSGI

Web Server Gateway Interface。这是python中定义的一个网关协议,规定了app如何与webserver交互。WSGI是python中定义的一个网关协议规定web server如何跟web应用程序交互。 这个协议最主要的目的就是保证Python中所有web server程序或者gateway程序能够通过统一的协议跟web框架或者说web应用程序进行交互,这对于部署web程序来说很重要,你可以选择任何一个实现了WSGI协议的web server跑你的程序。

如果没有这个协议,那么每个程序每个web server可能都会实现各自的接口,最终的结果是一团乱。使用统一协议的另一个好处是web应用框架只需要实现wsgI就可以跟外部请求进行交互了,不用去针对某个web server来独立开发,交互逻辑开发者可以把精力放在框架本身。

wsgi分为两部分,其中一部分是web server或者gateway它监听某个端口上接受外部的请求,另一部分是web application,那不是我接受请求之后会通过wsgi协议规定的方式把数据传递给web application。Web application中处理完之后,设置对应的状态和header之后返回body部分,web server拿到返回数据之后,再进行HTTP协议的封装,最终返回完整的HTTP-Response数据。

HDFS

HDFS

存储模型

文件线性切割成块(block),每个block可以根据其字节内容在原文件中的偏移量offset确定,即第一个block的第一个字节的offset为0、第二个block的第一个字节的offset为block_size。block分散存储到集群节点当中,单一文件的block大小一致,不同文件之间的block大小可以不一样。block可以设置副本数,block的副本分散在不同节点中(即副本不会出现在同一节点中),这样可以很好地支持多个程序处理文件,但副本数不能超过节点数量。文件上传可以设置block的大小和副本数,且已上传的文件block副本数可以调整,但是大小不能改变。只支持一次写入多次读取,同一时刻只有一个写入者。可以append追加数据,但不可以修改block。

架构模型

文件组成分为:

  • 元数据metadata,
  • 文件数据

主从架构:

  • NameNode节点保存文件元数据:单节点,维护一个虚拟目录树,映射到DataNode的文件系统的Block。
  • DataNode节点保存文件block数据:多节点;

hdfs-architecture

DataNode和NameNode保持心跳,提交block列表;

HdfsClient和NameNode交互元数据信息;

HdfsClient和DataNode交互文件Block信息。

HdfsClient与NameNode交互后,向DataNode交互,NameNode起到了一定的负载均衡作用。

NameNode

基于内存存储,不会和磁盘发生交换。数据只存在内存中,但会通过快照或增量日志做持久化策略。

主要功能:

  • 接受客户端的读写服务;
  • 收集DataNode汇报的Block列表信息。

其保存的metadata信息包括:

  • 文件ownership, permissions;
  • 文件大小,时间
  • block列表,block偏移量,位置信息;
  • block每个副本的位置(由DataNode上报)

NameNode持久化

NameNode的metadata在启动后会加载到内存;

metadata存储到磁盘的文件名为fsimage;

block的位置信息不会保存到fsimage;

edits记录对metadata的操作记录;

DataNode

本地磁盘目录存储数据block,使文件形式,同时存储block的元数据信息文件幂md5;

启动datanode时会向namenode汇报block信息;

通过向nanenode发送心跳保持与其联系(3秒1次),如果namenode在10分钟内没有收到datanode的信息,则认为其已经lost,并copy其上的block(从其他节点的副本拷贝扩散,保证副本数一致)到其他datanode。

HDFS优点

  • 高容错性:
    • 数据自动保存多个副本;
    • 副本数据丢失,则会自动回复;
  • 适合批处理
    • 移动计算程序而不是数据;
    • 数据位置暴露给计算框架;
  • 适合大数据处理
    • GB、TB、PB
    • 百万规模以上的文件数量
    • 10k+节点
  • 可构建在廉价机器上(可靠性越高的机器越贵)
    • 通过多副本提高可靠性;
    • 提供了容错和恢复机制;

HDFS缺点

  • 低延迟数据访问
    • 毫秒级随机访问
    • 低延迟与高吞吐率
  • 小文件存取
    • 占用NameNode大量内存
    • 寻道时间超过读取时间
  • 并发写入、文件随机修改
    • 一个文件只能有一个写者
    • 仅支持append

SecondaryNameNode

SNN不是NN的备份(但是可以做备份),其主要工作是帮助NN合并edits log,减少NN的启动时间。
SNN执行合并时间:

  • 根据配置文件设置的时间间隔fs.checkpoint.period,默认为3600s;
  • 根据配置文件设置edits log大小fs.checkpoint.size规定edits log文件的最大值默认是64MB

安全模式

  • namenode启动时,首先将映像文件fsimage载入内存,并执行编辑日志(edits)中的各项操作。
  • 一旦在内存中成功建立文件系统元数据的映射,则创建一个新的fsimage文件(这个操作不需要SecondaryNameNode)和一个空的编辑日志,这里也是namenode唯一一次创建fsimage。
  • 此刻namenode运行在安全模式。即namenode的文件系统对于客户端来说只是只读的。(显示目录、文件内容等。写、删除、重命名等操作都会失败)
  • 在此阶段Namenode收集各个datanode的报告,当书记块达到最小副本数以上时,会被认为是”安全”的,在一定比例(可设置)的数据块被确定为”安全”后,再过若干时间,安全模式结束。
  • 当检测到副本数不足的数据块是,该快会被复制到达到最小副本数,系统中数据块大的位置并不是由namenode维护的,而是以块列表形式存储在datanode中。

Block的副本防置策略

  • 第一个副本:防置在上传的DN;如果是集群外提交,则随机挑选一台磁盘不是太满,CPU不太忙的节点;
  • 第二个副本:防置在于第一个副本不同的机架(Rack)的节点上。
  • 第三个副本:与第二个副本相同机架的节点。
  • 更多副本:随机节点。

HDFS写流程

流水线

距离优先

HDFS读流程

HDFS文件权限 POSIX

  • 与Linux文件权限类似

    • r:read; w:write; x:execute
    • 权限x对于文件忽略,对于文件夹表示十分允许访问其内容
  • 如果Linux系统用户A使用hadoop命令创建一个文件,那么这个文件在HDFS中的owner就是A;

  • HDFS的权限目的:组织好人做错事,而不是阻止坏人做坏事。HDFS
    相信,你告诉我你是谁,我就认为你是谁。

Hadoop2

Hadoop2产生背景

Hadoop1中HDFS和MapReduce在高可用、扩展性等方面存在问题

  • NameNode单点故障,难以应用于在线场景, HA
  • NameNode压力过大,且内存首先,影响扩展性, F
    MapReduce存在的问题:
  • JobTracker访问压力大,影响系统扩展性
  • 难以支持出MapReduce之外的计算框架,比如spark、Storm等

Hadoop2.x由HDFS、MapReduce和YARN三个分支构成。

  • HDFS: NN Federation(联邦)、HA;2.x只支持2个节点HA
    ,3.0实现一主多从;
  • MapReduce: 运行在YARN上的MR;离线计算,基于磁盘I/O计算
  • YARN:资源管理系统

HDFS2

解决HDFS1中单点故障和内存受限的问题

解决单点故障:

  • HDFS HA :通过贮备NameNode解决
  • 如果中NameNode发生故障,则切换到备NameNode上;

解决内存受限问题

  • HDFS Federation 联邦
  • 水平扩展,支持多个NameNode;
    • 每个NameNode分管一部分目录;
    • 所有NameNode共享所有DataNode存储资源;
      HDFS2仅是架构上发生了变化,使用方式不变,对HDFS使用者透明,HDFS1中的命令和API仍可以使用。

Linux NFS
JournalNodes
最终一致性

Redis Notes

Redis Notes

commands

info

> info server
 Server
redis_version:5.0.3
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:457be8ba4fa3e51f
redis_mode:standalone
os:Darwin 18.6.0 x86_64
arch_bits:64
multiplexing_api:kqueue
atomicvar_api:atomic-builtin
gcc_version:4.2.1
process_id:47187
run_id:f322e32c6eeac70063c80d37217a32f62da783ef
tcp_port:6379
uptime_in_seconds:400
uptime_in_days:0
hz:10
configured_hz:10
lru_clock:4641146
executable:/usr/local/opt/redis/bin/redis-server
config_file:/usr/local/etc/redis.conf

Ubuntu Notes

Ubuntu Notes

[TOC]


Command

查看软链接实际指向的文件位置

$ ls -l <filename>

ssh-keygen

  1. 在master生成秘钥, id_rsa, id_rsa.pubid_dsa, id_dsa.pub
    ```bash
    $ ssh-keygen -t [rsa|dsa]

$ ls ~/.ssh
id_rsa id_rsa.pub known_hosts

$ cat id_rsa
—–BEGIN OPENSSH PRIVATE KEY—–
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
……
……
……
iXQVQHy5W5hX8kykh9PfVeh745kzWDQqcp9JOBlaWcRxY+g7A5xfYaEeTQqjd/ZX8692UP
I/EFmILN9N26WIE3AAAAD2Z3ekBmd3otbWJwLmxhbgECAw==
—–END OPENSSH PRIVATE KEY—–

$ cat id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCpQZCHx6E5sj3/AZec3nFBjnWi83g/9brGVuiAV8yv+q/PksiGmZ5XGoGStq6iG9DWVLJa1Tv26Kh8Evi9rvpSbGaPbPPA09Gzi+UNZMieydSeqpFIDanB0X9H8VfGEcOzIw21wOVtIYthu5cwyjdfO3zYAV/scJcQon4m/bttk3cAPmnaQXw2QVg/gFWVdSadgRC1MaQJcG5zJxEdVKSHYyk6gCVKZLYyc029iZndbNfqtKcsV+vkuSCwY/nD/5B+GqnXjPSgVL9I7QIC8Gf9k+C012hBF3Vir5p1edft2Fn+CN77WcOyj9ye/ZSLvNDgW5W/0Ab27klWe/pI1eBT fwz@fwz-mbp.lan

2. 把master生成的公钥发送到slave

$ ssh-copy-id -i ~/.ssh/id_rsa.pub username@ip_address

此时,在slave上生成authorized_keys文件,内容为id_rsa.pub中的内容。

3. 从master通过ssh登录到slave,免密成功。

LVS

LVS

基于LB的集群

简单拓扑

ip-topo.001.png

  • LB: 负载均衡器;IP;转发,不做三次握手;保证三次握手到四次分手不可分割;
  • CIP: Client IP;
  • VIP: Vitual IP;
  • DIP: Director’s IP;
  • RIP: Real IP;

NAT

S_NAT

数据包过路由器时,修改源的IP地址。

D_NAT

数据包过路由器时,修改目标的IP地址。

sequenceDiagram

participant client
participant director
participant server

client ->> director: CIP-VIP
director ->> server: DIP-RIP
server -->> director: DIP-RIP
director -->> client: CIP-VIP

client 请求server的过程为DNAT,server回复client的过程为SNAT。

基于网络层

I/O 瓶颈:带宽是不对称的,下行的速度是大于上行的速度的。(请求往往比较小,通常可能只包含几十k的请求头,而返回通常数据量比较大,包含多媒体数据)。

DR

直接路由模型

LB只处理上行数据,分发给real-server,real-server处理完数据后直接返回给客户端,以解决LB上NAT的I/O瓶颈问题。

sequenceDiagram

participant clients
participant director
participant servers

client ->> director: CIP-VIP
director ->> server: (RIP's MAC)CIP-VIP
server -->> client: CIP-VIP

改MAC地址的行为要求LB和real-server在同一网段下,否则如果超过一跳,则会导致修改的MAC地址被替换。这是基于MAC地址的欺骗。类似于网关的方式。

这里要求real-server默认网关指向运营商(ISP),且拥有公网IP地址PIP。

TUN

tunnel,隧道;点对点的方式。类似于VPN。

sequenceDiagram

participant client
participant director
participant server

client ->> director: CIP-VIP
director ->> server: (DIP-RIP(CIP-VIP))
server -->> director: (RIP-DIP(VIP-RIP))
director -->> client: VIP-CIP

调度

静态调度:

  • rr
  • wrr
  • sh
  • dh

动态调度

  • lc: 最少连接
  • wlc: 加权最少连接;
  • sed: 最短期望延迟;
  • nq: never queue;
  • LBLC:基于本地的最少连接
  • DH:
  • LBLCR:

ipvsadm

Spring Cloud

JAXB 异常

在运行spring-cloud-eureka-server时,会报错,日志打印错误栈如下:

java.lang.TypeNotPresentException: Type javax.xml.bind.JAXBContext not present
    at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:117) ~[na:na]
    at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:125) ~[na:na]
    at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49) ~[na:na]
    at java.base/sun.reflect.generics.visitor.Reifier.reifyTypeArguments(Reifier.java:68) ~[na:na]
    at java.base/sun.reflect.generics.visitor.Reifier.visitClassTypeSignature(Reifier.java:138) ~[na:na]
    at java.base/sun.reflect.generics.tree.ClassTypeSignature.accept(ClassTypeSignature.java:49) ~[na:na]
    at java.base/sun.reflect.generics.repository.ClassRepository.computeSuperInterfaces(ClassRepository.java:117) ~[na:na]
    at java.base/sun.reflect.generics.repository.ClassRepository.getSuperInterfaces(ClassRepository.java:95) ~[na:na]
    at java.base/java.lang.Class.getGenericInterfaces(Class.java:1138) ~[na:na]
    at com.sun.jersey.core.reflection.ReflectionHelper.getClass(ReflectionHelper.java:629) ~[jersey-core-1.19.1.jar:1.19.1]
    at com.sun.jersey.core.reflection.ReflectionHelper.getClass(ReflectionHelper.java:625) ~[jersey-core-1.19.1.jar:1.19.1]
    at com.sun.jersey.core.spi.factory.ContextResolverFactory.getParameterizedType(ContextResolverFactory.java:202) ~[jersey-core-1.19.1.jar:1.19.1]
    at com.sun.jersey.core.spi.factory.ContextResolverFactory.init(ContextResolverFactory.java:89) ~[jersey-core-1.19.1.jar:1.19.1]
    at com.sun.jersey.server.impl.application.WebApplicationImpl._initiate(WebApplicationImpl.java:1332) ~[jersey-server-1.19.1.jar:1.19.1]
    at com.sun.jersey.server.impl.application.WebApplicationImpl.access$700(WebApplicationImpl.java:180) ~[jersey-server-1.19.1.jar:1.19.1]
    at com.sun.jersey.server.impl.application.WebApplicationImpl$13.f(WebApplicationImpl.java:799) ~[jersey-server-1.19.1.jar:1.19.1]
    at com.sun.jersey.server.impl.application.WebApplicationImpl$13.f(WebApplicationImpl.java:795) ~[jersey-server-1.19.1.jar:1.19.1]
    at com.sun.jersey.spi.inject.Errors.processWithErrors(Errors.java:193) ~[jersey-core-1.19.1.jar:1.19.1]
    at com.sun.jersey.server.impl.application.WebApplicationImpl.initiate(WebApplicationImpl.java:795) ~[jersey-server-1.19.1.jar:1.19.1]
    at com.sun.jersey.server.impl.application.WebApplicationImpl.initiate(WebApplicationImpl.java:790) ~[jersey-server-1.19.1.jar:1.19.1]
    at com.sun.jersey.spi.container.servlet.ServletContainer.initiate(ServletContainer.java:509) ~[jersey-servlet-1.19.1.jar:1.19.1]
    at com.sun.jersey.spi.container.servlet.ServletContainer$InternalWebComponent.initiate(ServletContainer.java:339) ~[jersey-servlet-1.19.1.jar:1.19.1]
    at com.sun.jersey.spi.container.servlet.WebComponent.load(WebComponent.java:605) ~[jersey-servlet-1.19.1.jar:1.19.1]
    at com.sun.jersey.spi.container.servlet.WebComponent.init(WebComponent.java:207) ~[jersey-servlet-1.19.1.jar:1.19.1]
    at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:394) ~[jersey-servlet-1.19.1.jar:1.19.1]
    at com.sun.jersey.spi.container.servlet.ServletContainer.init(ServletContainer.java:744) ~[jersey-servlet-1.19.1.jar:1.19.1]
    at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:270) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:106) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4530) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5169) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) ~[na:na]
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:841) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1384) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1374) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264) ~[na:na]
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:140) ~[na:na]
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:909) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:262) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.StandardService.startInternal(StandardService.java:421) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:932) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.apache.catalina.startup.Tomcat.start(Tomcat.java:456) ~[tomcat-embed-core-9.0.21.jar:9.0.21]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:105) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:86) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:414) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:178) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:179) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:152) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:742) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:389) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:311) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1213) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1202) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at com.example.eurekaserver.EurekaServerApplication.main(EurekaServerApplication.java:12) ~[classes/:na]
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBContext
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583) ~[na:na]
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) ~[na:na]
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521) ~[na:na]
    at java.base/java.lang.Class.forName0(Native Method) ~[na:na]
    at java.base/java.lang.Class.forName(Class.java:398) ~[na:na]
    at java.base/sun.reflect.generics.factory.CoreReflectionFactory.makeNamedType(CoreReflectionFactory.java:114) ~[na:na]
    ... 65 common frames omitted

2019-07-27 22:34:12.507 ERROR 19315 --- [           main] o.apache.catalina.core.StandardContext   : One or more Filters failed to start. Full details will be found in the appropriate container log file
2019-07-27 22:34:12.508 ERROR 19315 --- [           main] o.apache.catalina.core.StandardContext   : Context [] startup failed due to previous errors
2019-07-27 22:34:12.511  WARN 19315 --- [           main] o.a.c.loader.WebappClassLoaderBase       : The web application [ROOT] appears to have started a thread named [spring.cloud.inetutils] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
 java.base@11.0.2/jdk.internal.misc.Unsafe.park(Native Method)
 java.base@11.0.2/java.util.concurrent.locks.LockSupport.park(LockSupport.java:194)
 java.base@11.0.2/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2081)
 java.base@11.0.2/java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:433)
 java.base@11.0.2/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1054)
 java.base@11.0.2/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1114)
 java.base@11.0.2/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
 java.base@11.0.2/java.lang.Thread.run(Thread.java:834)
2019-07-27 22:34:12.540  INFO 19315 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2019-07-27 22:34:12.546  WARN 19315 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
2019-07-27 22:34:12.557  INFO 19315 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-07-27 22:34:12.564 ERROR 19315 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:155) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543) ~[spring-context-5.1.8.RELEASE.jar:5.1.8.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:742) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:389) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:311) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1213) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1202) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at com.example.eurekaserver.EurekaServerApplication.main(EurekaServerApplication.java:12) ~[classes/:na]
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:124) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:86) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:414) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:178) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:179) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:152) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    ... 8 common frames omitted
Caused by: java.lang.IllegalStateException: StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[] failed to start
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.rethrowDeferredStartupExceptions(TomcatWebServer.java:169) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:108) ~[spring-boot-2.1.6.RELEASE.jar:2.1.6.RELEASE]
    ... 13 common frames omitted

可以看到主要的错误信息是:java.lang.TypeNotPresentException: Type javax.xml.bind.JAXBContext not present

这是因为java11把java.xml.bind从classpath中移除了:

  • java7: include
  • java8: include
  • java9: deprecated
  • java10: deprecated
  • java11: removed
    因此需要在依赖中手动引入JAXB依赖,maven的pom.xml添加的依赖如下:
    <dependency>
      <groupId>com.sun.xml.bind</groupId>
      <artifactId>jaxb-core</artifactId>
      <version>2.3.0.1</version>
    </dependency>
    <dependency>
      <groupId>javax.xml.bind</groupId>
      <artifactId>jaxb-api</artifactId>
      <version>2.3.1</version>
    </dependency>
    <dependency>
      <groupId>com.sun.xml.bind</groupId>
      <artifactId>jaxb-impl</artifactId>
      <version>2.3.1</version>
    </dependency>