0%

生命周期流程图

States

Active 活动

在 Active 阶段,网页处于可见状态,且拥有输入焦点。

Passive 被动,消极

在 Passive 阶段,网页可见,但没有输入焦点,无法接受输入。UI 更新(比如动画)仍然在执行。该阶段只可能发生在桌面同时有多个窗口的情况。

Hidden 隐藏

在 Hidden 阶段,用户的桌面被其他窗口占据,网页不可见,但尚未冻结。UI 更新不再执行。

Frozen 冻结

如果网页处于 Hidden 阶段的时间过久,用户又不关闭网页,浏览器就有可能冻结网页,使其进入 Frozen 阶段。不过,也有可能,处于可见状态的页面长时间没有操作,也会进入 Frozen 阶段。

这个阶段的特征是,网页不会再被分配 CPU 计算资源。定时器、回调函数、网络请求、DOM 操作都不会执行,不过正在运行的任务会执行完。浏览器可能会允许 Frozen 阶段的页面,周期性复苏一小段时间,短暂变回 Hidden 状态,允许一小部分任务执行。

Terminated 终止

在 Terminated 阶段,由于用户主动关闭窗口,或者在同一个窗口前往其他页面,导致当前页面开始被浏览器卸载并从内存中清除。注意,这个阶段总是在 Hidden 阶段之后发生,也就是说,用户主动离开当前页面,总是先进入 Hidden 阶段,再进入 Terminated 阶段。

这个阶段会导致网页卸载,任何新任务都不会在这个阶段启动,并且如果运行时间太长,正在进行的任务可能会被终止。

Discarded 丢弃

如果网页长时间处于 Frozen 阶段,用户又不唤醒页面,那么就会进入 Discarded 阶段,即浏览器自动卸载网页,清除该网页的内存占用。不过,Passive 阶段的网页如果长时间没有互动,也可能直接进入 Discarded 阶段。

这一般是在用户没有介入的情况下,由系统强制执行。任何类型的新任务或 JavaScript 代码,都不能在此阶段执行,因为这时通常处在资源限制的状况下。

网页被浏览器自动 Discarded 以后,它的 Tab 窗口还是在的。如果用户重新访问这个 Tab 页,浏览器将会重新向服务器发出请求,再一次重新加载网页,回到 Active 阶段。

获取当前阶段

1
2
3
4
5
6
7
8
9
const getState = () => {
if (document.visibilityState === "hidden") {
return "hidden";
}
if (document.hasFocus()) {
return "active";
}
return "passive";
};

另一方面,frozen 和 terminated 状态只能在状态发生变化时在其各自的事件侦听器(freeze and pagehide)中检测到。

如何防止在页面隐藏时被 Frozen 或 Discarded

  • 播放音频
  • 使用 WebRTC
  • 使用 WebSockets
  • 更新 title 或 favicon
  • 显示 alert
  • 发送 push 消息

具体参考: Heuristics for Freezing & Discarding

测试工具

chrome://discards

参考连接

https://www.ruanyifeng.com/blog/2018/11/page_lifecycle_api.html
https://developer.chrome.com/blog/page-lifecycle-api/

文章目录

1、打开调试工具

打开 Chrome 浏览器,在地址栏输入chrome://webrtc-internals/即可打开调试工具,如下图所示
在这里插入图片描述

Edge 83 及以上版本已采用 Chromium 作为内核,在 Edge 浏览器上也可以在地址栏输入edge://webrtc-internals/打开调试工具,如下图所示
在这里插入图片描述

2、导出调试信息

如下图,点击Create Dump 即可展开,进行导出调试信息的操作
在这里插入图片描述

若需要调试声音,可以勾选 Enable diagnostic audio recordings

最后点击 Download the PeerConnection updates and stats data 按钮,下载相关的统计信息

3、切换获取统计信息的方式

可以在Read Stats From处,切换获取统计信息的方式

  • Standardized (promise-based) getStats() API,通过标准的(基于 promise)getStats API 获取
  • Legacy Non-Standard (callback-based) getStats() API,通过遗留的非标准的(基于回调)getStats API 获取
    在这里插入图片描述

一般使用默认的选择,即通过标准 API 获取即可

4、相关统计信息

4.1 获取用户媒体设备请求(GetUserMedia Request)

如下图所示,点击GetUserMedia Request字样的区块,可以看到最近浏览器请求获取用户媒体设备权限的日志,可以看到每次请求获取用户媒体设备权限的时间、来源 URL 及音视频参数等信息
在这里插入图片描述

4.2 通道(RTCPeerConnection)列表

如下图所示,除GetUserMedia Request外的其它区块就是通道列表,每个区块都是一个RTCPeerConnection通道,可以通过点击每个区块查看每个通道的事件过程及统计信息。
在这里插入图片描述
我们的应用使用了 4 个通道,可以从上图看到,他们依次是:

  • 第一个主流发送通道
  • 第二个主流接收通道
  • 第三个辅流发送通道
  • 第四个辅流接收通道

4.3 查看每个通道(RTCPeerConnection)的信息

4.3.1 查看事件信息

若调试工具webrtc-internals先于应用页面开启,则如下图所示,可以看到完整的事件过程,每个事件都可以展开查看详细内容,从中可以跟踪相关事件信息及 SDP 信息
在这里插入图片描述

若调试工具webrtc-internals晚于应用页面开启,则只能看到应用页面后续的事件信息,不能看到之前的事件信息。

如果想看到完整的事件过程,打开调试工具webrtc-internals后,再打开应用页面,若应用页面已打开,则刷新重新加载页面即可。

4.3.2 查看统计信息

如下图所示,每个通道都有Stats Tables统计信息,每个信息都可以展开查看详细内容,分为两部分:

  • 统计数据
  • 统计图表(Stats graphs for ... 字样开头的信息)
    在这里插入图片描述

4.3.3 统计数据说明(样例)

RTCPeerConnection (peer-connection)
名称 数值 注释
timestamp 2020/7/28 下午 6:30:33
type peer-connection
dataChannelsOpened 0 生命周期内 open 状态的 DataChannel 数量
dataChannelsClosed 0 生命周期内 close 状态的 DataChannel 数量
RTCTransport_0_1 (transport)
名称 数值 注释
timestamp 2020/7/28 下午 6:32:03
type transport
bytesSent 39391140 发送字节数
[bytesSent_in_bits/s] 45805.813414400436 发送比特率
bytesReceived 5538693 接收字节数
[bytesReceived_in_bits/s] 4676.677453498226 接收比特率
dtlsState connected DTLS 状态
selectedCandidatePairId RTCIceCandidatePair_p0AAYh7Z_Tf02029O 候选者对
localCertificateId RTCCertificate_97:01:5C:9A:03:E5:38:E6:FE:8B:9A:2C:F7:29:FC:3D:2B:86:61:77:FB:1D:B0:9E:AD:AD:D5:05:E5:F9:8A:90 本地候选者 ID
remoteCertificateId RTCCertificate_D9:AF:1E:93:28:D0:64:F3:14:5D:81:3E:BF:F4:61:1E:D1:68:AE:CA 远端候选者 ID
tlsVersion FEFD
dtlsCipher TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
srtpCipher AEAD_AES_256_GCM
selectedCandidatePairChanges 1 候选者变更计数器
新增
名称 数值 注释
packetsSent 13804 发送包数
[packetsSent/s] 70.61591710493629
packetsReceived 3575 接收包数
[packetsReceived/s] 70.61591710493629
dtlsRole server 或 client 或 auto DTLS 握手期间的角色,用于确定哪个对等方将充当 DTLS 客户端,哪个将充当 DTLS 服务器
iceRole controlling 或 controlled ICE 代理的角色,用于确定哪个端将发起 ICE 连接,哪个端将接受连接
iceLocalUsernameFragment 2eIo 本地用户名
iceState connected ice 状态
RTCCertificate_XXX (certificate)
名称 数值
timestamp 2020/7/28 下午 5:24:20
fingerprint(指纹秘钥) 97:01:5C:9A:03:E5:38:E6:FE:8B:9A:2C:F7:…
fingerprintAlgorithm(指纹秘钥算法) sha-256
base64Certificate MIIBFjCBvaADAgE…
RTCIceCandidatePair_XXX (candidate-pair)
名称 数值 说明
timestamp 2020/7/28 下午 5:28:58
transportId RTCTransport_0_1
localCandidateId RTCIceCandidate_p0AAYh7Z
remoteCandidateId RTCIceCandidate_Tf02029O
state(状态) succeeded
priority 4623781745793973759 优先级
nominated true
writable true 可写
bytesSent(发送的字节数) 21185440
**[bytesSent_in_bits/s]**(发送比特率) 13440
bytesReceived 3491205
**[bytesReceived_in_bits/s]**(接收比特率) 4480
totalRoundTripTime 2.059 STUN 连接性检查花费总时间
[totalRoundTripTime/responsesReceived_in_ms] 1.000000000000334
currentRoundTripTime 0.001
availableOutgoingBitrate(上行码率) 300000 预估带宽
requestsReceived 0 收到的连接性检查请求的总数(包括重传)
requestsSent 1 发送的连接性检查请求的总数(不包括重传)
responsesReceived 2575 收到的连接性检查响应的总数
responsesSent 0 发送的连接性检查响应的总数
consentRequestsSent 2574 发送的同意请求的总数
新增
名称 数值 说明
packetsSent 66602
[packetsSent/s] 70
packetsReceived 21175
[packetsReceived/s] 21
packetsDiscardedOnSend 0 由于套接字错误而被丢弃的数据包总数,即在将数据包移交给套接字时发生了套接字错误。这可能是由于各种原因造成的,包括缓冲区已满或没有可用内存。
bytesDiscardedOnSend 0
lastPacketReceivedTimestamp 1681960810017 收到最后一个数据包的时间戳,不包括 STUN 数据包
lastPacketSentTimestamp 1681960810017 发送最后一个数据包的时间戳,不包括 STUN 数据包
说明:

####### state(状态)

  • frozen:表示 ICE 候选对已经被冻结,即不再尝试连接。
  • waiting:表示 ICE 候选对正在等待连接。
  • in-progress:表示 ICE 候选对正在尝试连接。
  • failed:表示 ICE 候选对连接失败。
  • succeeded:表示 ICE 候选对连接成功。
RTCIceCandidate_XXX (remote-candidate)
名称 数值
timestamp 2020/7/28 下午 5:51:37
transportId RTCTransport_0_1
isRemote true
ip(远端地址) 172.16.186.220
port(远端端口) 60349
protocol udp
candidateType host
priority 1076558079
deleted false
RTCIceCandidate_XXX (local-candidate)
名称 数值
timestamp 2020/7/28 下午 3:41:39
transportId RTCTransport_0_1
isRemote false
networkType ethernet
ip(本地地址) 192.168.186.99
port(本地端口) 53988
protocol udp
candidateType host
priority 2122129151
deleted false
RTCAudioSource_XXX (media-source)
名称 数值
timestamp 2020/7/28 下午 3:42:04
trackIdentifier f13d531c-457d-4fdc-a685-ab9d6d07848d
kind audio
audioLevel 6.1037e-05
totalAudioEnergy(声音能量值) 2.23667e-06
[Audio_Level_in_RMS] 0.00006737952211169176
totalSamplesDuration(采样时长) 39.41
RTCVideoSource_XXX (media-source)
名称 数值
timestamp 2020/7/28 下午 6:50:34
trackIdentifier 24dde0fd-93fb-43e4-9e7c-187ca30ade3e
kind video
width(宽) 640
height(高) 360
framesPerSecond(每秒帧数) 16
RTCMediaStream_XXX (stream)
名称 数值
timestamp 2020/7/28 下午 6:10:18
streamIdentifier 5M9qqLa9fnMAvL7qt5ZfJZVtzmP3kRdyLKNw
trackIds [“RTCMediaStreamTrack_sender_0”]
RTCOutboundRTPAudioStream_XXX (outbound-rtp)
名称 数值
timestamp 2020/7/28 下午 6:12:15
ssrc 648299959
isRemote false
mediaType audio
kind audio
trackId RTCMediaStreamTrack_sender_7
transportId RTCTransport_0_1
codecId RTCCodec_1_Outbound_111
**[codec]**(编码格式) opus (111, useinbandfec=1)
mediaSourceId RTCAudioSource_7
packetsSent 2875
**[packetsSent/s]**(发送包率) 50
retransmittedPacketsSent 0
**[retransmittedPacketsSent/s]**(重传包率) 0
bytesSent 112617
**[bytesSent_in_bits/s]**(发送比特率) 18936
headerBytesSent 69000
**[headerBytesSent_in_bits/s]**(发送包头比特率) 9600
retransmittedBytesSent 0
**[retransmittedBytesSent_in_bits/s]**(重传比特率) 0
remoteId RTCRemoteInboundRtpAudioStream_648299959
RTCOutboundRTPVideoStream_XXX (outbound-rtp)
名称 数值
timestamp 2020/7/28 下午 6:19:19
ssrc(SSRC 编号) 2866008429
isRemote false
mediaType video
kind(类型) video
trackId RTCMediaStreamTrack_sender_8
transportId RTCTransport_0_1
codecId RTCCodec_2_Outbound_125
**[codec]**(编码格式) H264 (125, level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e032;x-google-start-bitrate=1000)
firCount 0
pliCount 73
nackCount 0
mediaSourceId RTCVideoSource_8
packetsSent 157671
**[packetsSent/s]**(发送包率) 16
retransmittedPacketsSent 0
**[retransmittedPacketsSent/s]**(重传包率) 0
bytesSent 23341242
**[bytesSent_in_bits/s]**(发送比特率) 15800
headerBytesSent 3785744
**[headerBytesSent_in_bits/s]**(发送包头比特率) 3072
retransmittedBytesSent 0
**[retransmittedBytesSent_in_bits/s]**(重传比特率) 0
framesEncoded 152831
[framesEncoded/s] 16
keyFramesEncoded(已编码关键帧数) 115
totalEncodeTime 181.663
[totalEncodeTime/framesEncoded_in_ms] 0.8750000000006253
totalEncodedBytesTarget 0
[totalEncodedBytesTarget_in_bits/s] 0
framesSent 152831
hugeFramesSent 0
totalPacketSendDelay 1583.23
[totalPacketSendDelay/packetsSent_in_ms] 6.875000000007958
qualityLimitationReason none
qualityLimitationResolutionChanges 0
encoderImplementation(编码器实现) OpenH264
qpSum 1847067
[qpSum/framesEncoded] 12
remoteId RTCRemoteInboundRtpVideoStream_2866008429
frameWidth(帧宽) 640
frameHeight(帧高) 360
framesPerSecond(每秒帧数) 16
RTCInboundRTPAudioStream_XXX (inbound-rtp)
名称 数值
timestamp 2020/7/29 上午 9:56:58
ssrc 981291154
isRemote false
mediaType audio
** kind **(类型) audio
trackId RTCMediaStreamTrack_receiver_39
transportId RTCTransport_0_1
codecId RTCCodec_1_Inbound_120
**[codec]**(编码格式) opus (120, minptime=10;useinbandfec=1)
packetsReceived 56844
**[packetsReceived/s]**(接收包率) 50.0500464188291
fecPacketsReceived 0
fecPacketsDiscarded 0
bytesReceived 2160062
**[bytesReceived_in_bits/s]**(接收比特率) 15239.238133605084
headerBytesReceived 2501136
[headerBytesReceived_in_bits/s] 17617.616339427845
packetsLost(丢包数) 15
lastPacketReceivedTimestamp 609191
jitter 0.009
RTCInboundRTPVideoStream_XXX (inbound-rtp)
名称 数值
timestamp 2020/7/29 上午 10:02:51
ssrc 435698252
isRemote false
mediaType video
kind(类型) video
trackId RTCMediaStreamTrack_receiver_46
transportId RTCTransport_0_1
codecId RTCCodec_7_Inbound_106
**[codec]**(编码格式) H264 (106, level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f)
firCount 0
pliCount 5
nackCount 15
packetsReceived 45799
**[packetsReceived/s]**(接收包率) 28.94211995549519
bytesReceived 20438271
**[bytesReceived_in_bits/s]**(接收比特率) 146499.0231898983
headerBytesReceived 1465784
[headerBytesReceived_in_bits/s] 7409.182708606769
packetsLost(丢包数) 18
lastPacketReceivedTimestamp 609544
framesDecoded 44611
**[framesDecoded/s]**(每秒解码帧数) 28.94211995549519
keyFramesDecoded(关键帧解码数) 54
totalDecodeTime 93.285
[totalDecodeTime/framesDecoded_in_ms] 2.3793103448276764
totalInterFrameDelay 1488.32
[totalInterFrameDelay/framesDecoded_in_ms] 34.137931034483074
totalSquaredInterFrameDelay 52.1927
[interFrameDelayStDev_in_ms] 8.716706661908054
decoderImplementation ExternalDecoder
RTCRemoteInboundRtpAudioStream_XXX (remote-inbound-rtp)
名称 数值
timestamp 2020/7/28 下午 6:44:23
ssrc(SSRC 编号) 648299959
kind(类型) audio
transportId RTCTransport_0_1
codecId RTCCodec_1_Outbound_111
packetsLost(丢包数) 0
jitter 0
localId RTCOutboundRTPAudioStream_648299959
roundTripTime 0.001
RTCRemoteInboundRtpVideoStream_XXX (remote-inbound-rtp)
名称 数值
timestamp 2020/7/28 下午 6:44:23
ssrc(SSRC 编号) 2866008429
kind(类型) video
transportId RTCTransport_0_1
codecId RTCCodec_2_Outbound_125
packetsLost(丢包数) 0
jitter 0
localId RTCOutboundRTPVideoStream_2866008429
roundTripTime 0.001
RTCMediaStreamTrack_sender_XXX (track)

1、 音频

名称 数值
timestamp 2020/7/28 下午 3:42:04
trackIdentifier(track 编号) f13d531c-457d-4fdc-a685-ab9d6d07848d
mediaSourceId RTCAudioSource_7
remoteSource false
ended false
detached false
kind(类型) audio

2、视频

名称 数值
timestamp 2020/7/28 下午 5:59:52
trackIdentifier(track 编号) 24dde0fd-93fb-43e4-9e7c-187ca30ade3e
mediaSourceId RTCVideoSource_8
remoteSource false
ended false
detached false
kind(类型) video
frameWidth(帧宽) 640
frameHeight(帧高) 360
framesSent 133998
**[framesSent/s]**(每秒发送帧数) 16.01601485402531
hugeFramesSent 0
RTCMediaStreamTrack_receiver_XXX (track)

1、音频

名称 数值
timestamp 2020/7/29 上午 10:46:05
trackIdentifier f3yl6DQ8S76dHOHHjCXbvA==:qj7eQTkvT-q5CAyVLtLzew==
remoteSource true
ended false
detached false
kind(类型) audio
jitterBufferDelay 1.14074e+07
[jitterBufferDelay/jitterBufferEmittedCount_in_ms] 64.58333333333334
jitterBufferEmittedCount 168742080
totalAudioEnergy 0
[Audio_Level_in_RMS] 0
totalSamplesReceived 177985440
totalSamplesDuration 4083.85
concealedSamples 9929280
silentConcealedSamples 9032510
concealmentEvents 787
insertedSamplesForDeceleration 108914
removedSamplesForAcceleration 794247
jitterBufferFlushes 0
delayedPacketOutageSamples 720318
relativePacketArrivalDelay 2525.05
jitterBufferTargetDelay 1.08559e+07
interruptionCount 12
totalInterruptionDuration 2.433
audioLevel 0

2、视频

名称 数值
timestamp 2020/7/29 上午 10:53:38
trackIdentifier NWXoF57gS7CGb7TfVPq-zg==:qj7eQTkvT-q5CAyVLtLzew==
remoteSource true
ended false
detached false
kind(类型) video
jitterBufferDelay 5704.2
[jitterBufferDelay/jitterBufferEmittedCount_in_ms] 37.931034482739804
jitterBufferEmittedCount 135940
frameWidth 480
frameHeight 270
framesReceived 135942
[framesReceived/s] 29
[framesReceived-framesDecoded] 1
framesDecoded 135941
framesDropped 0
freezeCount 1
pauseCount 0
totalFreezesDuration 0.19
totalPausesDuration 0
totalFramesDuration 4535.28
sumOfSquaredFramesDuration 159.925

4.4 统计信息关系图

在这里插入图片描述

4.5 W3C 标准文档

W3C 标准文档
https://www.w3.org/TR/webrtc-stats/

————————————————
版权声明:本文为 CSDN 博主「cgs1999」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cgs1999/article/details/110084983

Janus项目附带实例里有很多业务代码,初学无法抓住重点。
这个例子仅仅包含可以完成一个多人视频会议的最基本的代码。
可查看注释和console输出,快速了解基本流程

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
<!-- index.html -->
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<button id="start">Start</button>
<div id="localVideoWarp">
<div>localVideo</div>
</div>
<div id="remoteVideoWarp">
<div>remoteVideos</div>
</div>

<script type="text/javascript" src="cdnjs/adapter.min.js"></script>
<script type="text/javascript" src="cdnjs/jquery.min.js"></script>
<script type="text/javascript" src="janus.js"></script>
<script type="text/javascript" src="test.js"></script>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// test.js

// TODO 服务器已创建好的房间ID
const roomId = 0000;
// 一个随机字符串,可以用来归类来自同一个用户的所有handle
const opaqueId = Janus.randomString(20);
// 本地音视频流
let localStream;
// janus 实例
let janus;
// TODO server地址
const server = 'wss://xxxxx';

// 如果房间配置中require_pvtid===true, 则privateId在订阅流时必须提供该ID
let privateId;
// 发送的远端的字符串,可以传递一些信息,如(用户名,json字符串等)
const display = 'display';
// 用户ID,需保持唯一
const uid = Date.now();

// 本地用户video room handle
let localVideoRoomHandle;
// 远端用户video room handle
const remoteVMHandles = [];

console.log('Janus init start.');
Janus.init({
// debug: 'all',
callback() {
console.log('Janus init success.');

$('#start').click(async () => {
localStream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
console.log('mediaDevices.getUserMedia success. localStream: ', localStream);
addLocalVideo(localStream);

console.log('Create janus instance start.');
janus = new Janus({
server,
iceServers: [{urls: 'stun:stun.stunprotocol.org'}],
success() {
console.log('Create janus instance success. janus:', janus);

console.log('Attach video room plugin start.');
janus.attach({
plugin: 'janus.plugin.videoroom',
opaqueId,
success(pluginHandle) {
localVideoRoomHandle = pluginHandle;
console.log('Attach video room plugin success. localVideoRoomHandle: ', localVideoRoomHandle);
join();
},
onmessage: function(msg, jsep) {
const event = msg['videoroom'];
switch (event) {
case 'joined': {

const {id, private_id} = msg;
privateId = private_id;
console.log('Join video room success. ', `id: ${id} privateId: ${privateId}`);

// 发布流
publish();

// 如果有远端流。订阅
if (msg['publishers']) {
for (const publisher of msg['publishers']) {
subscribe(publisher.id, publisher.display, publisher.audio, publisher.video);
}
}

break;
}
case 'event': {
// 如果有远端流。订阅
if (msg['publishers']) {
for (const publisher of msg['publishers']) {
subscribe(publisher.id, publisher.display, publisher.audio, publisher.video);
}
}
break;
}

}

// 协商
if (jsep) {
localVideoRoomHandle.handleRemoteJsep({jsep});
}
},
error(err) {
console.error(err);
},
});
},
error(error) {
console.error(error);
},
destroyed() {
console.log('destroyed');
},
});
});
},
});

function join() {
console.log('Join video room start.');
var register = {
request: 'join',
room: roomId,
ptype: 'publisher',
id: uid,
display: display,
};
localVideoRoomHandle.send({message: register});
}

function publish() {
console.log('Publish video room start.');
localVideoRoomHandle.createOffer({
media: {
audioRecv: false,
videoRecv: false,
audioSend: true,
videoSend: true,
},
success(jsep) {
var publish = {request: 'configure', audio: true, video: true};
localVideoRoomHandle.send({message: publish, jsep: jsep});
},
});
}

function subscribe(id, display, audio, video) {
console.log('subscribe start. params:', id, display, audio, video);
let remoteHandle;
janus.attach(
{
plugin: 'janus.plugin.videoroom',
opaqueId,
success: function(pluginHandle) {
remoteHandle = pluginHandle;
const subscribe = {
request: 'join',
room: roomId,
ptype: 'subscriber',
feed: id,
private_id: privateId,
};
remoteHandle.videoCodec = video;
remoteHandle.send({message: subscribe});
console.log('remoteHandle:', remoteHandle);
remoteVMHandles.push(remoteHandle);
},
onmessage(msg, jsep) {
var event = msg['videoroom'];
if (jsep) {
Janus.debug('Handling SDP as well...', jsep);
remoteHandle.createAnswer(
{
jsep: jsep,
media: {audioSend: false, videoSend: false}, // We want recvonly audio/video
success: function(jsep) {
var body = {request: 'start', room: roomId};
remoteHandle.send({message: body, jsep: jsep});
},
});
}
},
onremotestream: function(stream) {
addRemoteVideo(stream, remoteHandle);
},
});
}

function addLocalVideo(stream) {
const videoEle = document.createElement('video');
videoEle.srcObject = stream;
videoEle.width = 320;
videoEle.height = 240;
videoEle.autoplay = true;
$('#localVideoWarp').append(videoEle);
}

function addRemoteVideo(stream, remoteHandle) {

// 同一个远端用户会多次触发onremotestream,需要去重
if ($(`#remoteHandle${remoteHandle.id}`)[0]) {
return;
}

const videoEle = document.createElement('video');
videoEle.srcObject = stream;
videoEle.width = 320;
videoEle.height = 240;
videoEle.autoplay = true;
videoEle.id = `remoteHandle${remoteHandle.id}`;
$('#remoteVideoWarp').append(videoEle);
}

1
2
3
partial interface MediaStreamTrack {
attribute DOMString contentHint;
};

track内容类型提示,用于提示消费端应该如何使用这个track
默认为””,表示没有任何提示

音频

说明
“” 没有提供任何提示,应该程序推测
“speech” 包含语音数据。应开启噪声抑制或提高传入信号的可理解性
“speech-recognition” 包含用于机器语音识别目的的数据。提高传入信号的可理解性以进行转录并关闭用于人类语音的音频处理组件。
“music” 曲目应被视为包含音乐数据。通常,这可能意味着调整或关闭用于处理语音数据的音频处理组件,以防止音频失真。

视频

说明
“” 没有提供任何提示,应该程序推测
“motion” 流畅优先,帧率优先。适用于视频通话,电影,游戏等场景
“detail” 分辨率优先。适用于具有文本内容、绘画或线条艺术的演示文稿或网页
“text” 视频细节特别重要,并且经常出现明显的锐利边缘和颜色一致的区域,并且可以利用优化文本渲染的编码器工具。适用于文本内容,演示文稿,网页。

例子

1
2
3
4
5
6
7
8
9
const track = stream.getVideoTracks()[0];
if ('contentHint' in track) {
track.contentHint = hint;
if (track.contentHint !== hint) {
console.log('Invalid video track contentHint: \'' + hint + '\'');
}
} else {
console.log('MediaStreamTrack contentHint attribute not supported');
}

sip

SIP协议定义了6种基本方法

方法 说明
REGISTER 注册
INVITE 初始化一个会话、发起一个呼叫
ACK 对INVITE消息的最终响应
CANCEL 取消一个等待处理或正在处理的请求
BYE 终止一个会话
OPTIONS 查询服务器和能为,也可以用作ping测试

简介

dbadmin是steem网站的管理平台,使用Ruby on Rails, activeadmin编写,是假设steem必须的一个项目

安装

安装rvm

1
2
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB
\curl -sSL https://get.rvm.io | bash -s stable

安装ruby

ruby要求2.3.1

1
2
source ~/.rvm/scripts/rvm
rvm install 2.3.1

安装Rails

1
2
gem install bundler
gem install rails

安装dbadmin

1
2
3
4
5
git clone https://github.com/steemit/dbadmin
cd dbadmin

# 安装相关依赖
bundle install

修改数据库配置

app/config/database.yml

1
2
3
4
5
6
development:
<<: *default
host: xxx.xxx.xxx.xxx
username: xxx
password: xxx
port: xxx

写入数据库

1
2
rake db:migrate RAILS_ENV=development
rake db:seed

启动

1
bundle exec unicorn -c config/unicorn.rb -d -E development

#管理平台地址
http://127.0.0.1:5000/
默认用户名密码:
admin@example.com
password

管理平台

一些问题

GeoLite2-City.mmdb

异常:

1
dbadmin/app/models/user.rb:6:in `initialize': GeoIP2Compat - Error opening the specified MaxMind DB file: /xxx/dbadmin/GeoLite2-City.mmdb (GeoIP2Compat::Error)

下载:http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz
解压后放到dbadmin/目录下,文件名GeoLite2-City.mmdb

找不到 libmysqlclient.18.dylib

异常:

1
/xxx/.rvm/gems/ruby-2.3.1/gems/mysql2-0.4.4/lib/mysql2.rb:31:in `require': dlopen(/xxx/.rvm/gems/ruby-2.3.1/gems/mysql2-0.4.4/lib/mysql2/mysql2.bundle, 9): Library not loaded: libmysqlclient.18.dylib  (LoadError)

解决方法:

1
sudo ln -s /usr/local/mysql/lib/libmysqlclient.18.dylib /usr/lib/libmysqlclient.18.dylib

如果是mac, 上面一第命令执行失败,提示Operation not permitted,就需要关闭Rootless机制

重启按住 Command+R,进入恢复模式,打开Terminal

1
csrutil disable

重启即可。如果要恢复默认,那么

1
csrutil enable

安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cd ~/tmp/

#下载
wget https://jaist.dl.sourceforge.net/project/zabbix/ZABBIX%20Latest%20Stable/3.0.9/zabbix-3.0.9.tar.gz
tar -zxvf zabbix-3.0.9.tar.gz
cd zabbix-3.0.9

#编译安装server和agent
./configure --prefix=/xxx/zabbix --enable-server --enable-agent --with-mysql --with-libcurl
make
make install

#导入数据
cd database;

#导入数据,导入顺序错误,可能会报错
mysql -h{host} -P{port} -u{user} -p{password} {dbname} < mysql/schema.sql
mysql -h{host} -P{port} -u{user} -p{password} {dbname} < mysql/images.sql
mysql -h{host} -P{port} -u{user} -p{password} {dbname} < mysql/data.sql

#复制管理平台到对应目录
#网有第一次访问的时候有安装向导,会检测服务器配置和输入数据库用户名和密码
cd ..
cp -r frontends/php ~/www/zabbix

修改zabbix server配置

1
2
3
4
5
6
7
8
9
#日志
LogFile=/xxx/server.log

#数据库相关配置,zabbix无法读取网站的配置,需要单独配置
DBHost=xxx
DBName=xxx
DBUser=xxx
DBPassword=xxx
DBPort=xxx

修改zabbix agent配置

1
2
3
4
5
LogFile=/xxx/agentd.log
#server IP列表,相当于一个白名单,只有在白名单内的IP才可以读取数据
Server=xxx.xx.xxx.xx,xxx.xx.xxx.xx
#加载子配置,如果要自定义采集点,建议开启,对应的配置文件可以复制到这个目录里
Include=/xxx/zabbix_agentd.conf.d/*.conf

管理平台配置

  • 用apache/nginx配置
  • 需要PHP支持
  • 有安装向导
  • 默认用户名/密码:admin / zabbix

一些问题

短信配置

脚本

本人有短信接口
/xxx/zabbix/share/zabbix/alertscripts/sendsms.php
“/xxx/zabbix” 为zabbix安装目录

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
#!/usr/bin/env php
<?php
error_reporting(0);
$to=$argv[1];
$subject=$argv[2];
$body=$argv[3];

$data = [
'mobile'=>$to,
'message'=>$subject. '【xxxx】',
];

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://xxxx/v1/send.json");

curl_setopt($ch, CURLOPT_HTTP_VERSION , CURL_HTTP_VERSION_1_0 );
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_HEADER, FALSE);

curl_setopt($ch, CURLOPT_HTTPAUTH , CURLAUTH_BASIC);
curl_setopt($ch, CURLOPT_USERPWD , 'api:xxxx');

curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);

$res = curl_exec( $ch );
curl_close( $ch );
$res = curl_error( $ch );
var_dump($res);

脚本需要可执行权限

管理平台配置

管理平台配置

邮件配置

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#!/usr/bin/env php
<?php
$to = $argv[1];
$subject = $argv[2];
$body = $argv[3];

function thislog($info) {
$fp = fopen('/xxx/zabbix/script.log', 'a');
fwrite($fp, date('Y-m-d H:i:s'). ' '. $info . PHP_EOL);
fclose($fp);
}

thislog("start! to:$to subject:$subject body:$body");
try {

require __DIR__.'/PHPMailer/PHPMailerAutoload.php';

$mail = new PHPMailer;

//$mail->SMTPDebug = 3; // Enable verbose debug output

$mail->isSMTP(); // Set mailer to use SMTP
$mail->Host = 'smtp.exmail.qq.com'; // Specify main and backup SMTP servers
$mail->SMTPAuth = true; // Enable SMTP authentication
$mail->Username = 'xxx'; // SMTP username
$mail->Password = 'xxx'; // SMTP password
$mail->SMTPSecure = 'ssl'; // Enable TLS encryption, `ssl` also accepted
$mail->Port = 465; // TCP port to connect to

$mail->setFrom('xxx', 'Zabbix');
$mail->addAddress($to, 'bin');

$mail->Subject = $subject;
$mail->Body = $body;

if (!$mail->send()) {

thislog("send fail! error:$mail->ErrorInfo to:$to subject:$subject body:$body");
exit(0);
}
else {
thislog("send success! to:$to subject:$subject body:$body");
exit(1);
}
} catch (Exception $e) {
$msg = $e->getMessage();
thislog("exception: $msg to:$to subject:$subject body:$body");
}

依赖 https://github.com/PHPMailer/PHPMailer/

管理平台配置

与短信相同

报警配置

必须配置:

  • 配置(Administration) -> 动作(Actions)
  • 管理(Administration) -> 用户(Users) -> 报警媒介(Media)

自定义监控项

以后再说吧。。。

图表中文乱码解决

上传中文字体到网站:/fonts/目录
本例为微软雅黑字体(下载 uploads/YaHei.Consolas.ttf )
修改文件 /include/defines.inc.php

1
sed -i 's/DejaVuSans/YaHei.Consolas/g' ~/www/zabbix/include/defines.inc.php

一些技巧

技巧 说明
sudo !! 以root权限执行上一条命令(注意上一条命令的内容,以免发生意外)
!+数字 执行history中相应编号的命令
^foo^bar 将上一条命令中的第一个字符串”foo”替换成“bar”后执行
!!:gs/foo/bar 将上一条命令中的字符串“foo”全部替换成”bar”,然后执行
C-x e 打开编辑器编辑命令,这个编辑器是环境变量中EDITOR的值
Alt+./ESC+. 将最近一条命令的参数输出——不是作为结果打印出来,而是在终端提示符后面输出,还可以进行编辑

一些命令

查找src目录下有printf这个内容的cpp文件

1
grep src/*.cpp -e printf | cut -d ':' -f1 | uniq

按文件大小增序打印出当前目录下的文件名及其文件大小(单位字节)

1
ls -l | sed '1d' | sort -n -k5 | awk '{printf "%15s %10s\n", $9,$5}'

将当前目录下最大的5个文件移动到 ~/five-biggest/ 这个目录下

1
ls -l | sed '1d' | sort -n -r -k5 | head -n 5 | xargs -I {} mv {} ~/five-biggest/

删除文件中的空行

1
cat a.txt | sed -e '/^$/d'

列出用的最多的命令

1
history -1 | awk '{a[$2]++}END{for(i in a){print a[i] " " i}}' | sort -rn | head

相关参数

fastcgi_cache_path

syntax: fastcgi_cache_path path [ levels = levels ] keys_zone = name : size [ inactive = time ] [ max_size = size ] [ loader_files = number ] [ > loader_sleep = time ] [ loader_threshold = time ]
default: none
context: http

1
fastcgi_cache_path /dev/shm/nginx_cache levels=1:2 keys_zone=fcgicache:10m inactive=50m max_size=128m;
  • /tmp/nginx/fcgi/cache 缓存目录
  • keys_zone=fcgicache:10m keys_zone是这个缓存空间的名字,10m是用多少内存(主要缓存key和文件元信息,不会缓存页面),这个会在fastcgi_cache中引用
  • levels=1:2 设置目录哈希层级,比如2:2会生成256*256个字目录
  • inactive 失效时间
  • max_size 最多用多少空间

fastcgi_temp_path

Syntax: fastcgi_temp_path path [ level1 [ level2 [ level3 ]]]
Default: fastcgi_temp
Context: http server location

生成fastcgi_cache临时文件目录,fastcgi_cache缓存是先写在fastcgi_temp_path再移到fastcgi_cache_path,所以这两个目录最好在同一个分区,从0.8.9之后可以在不同的分区,不过还是建议放同一分区

fastcgi_cache

Syntax: fastcgi_cache zone | off;
Default: off;
Context: http, server, location

用哪个缓存空间,fastcgi_cache_path定义

1
fastcgi_cache fcgicache;

Syntax: fastcgi_cache_key string;
Default: —
Context: http, server, location

设置缓存的key

1
fastcgi_cache_key "$scheme$request_method$host$request_uri";

建议:在http层定义一个,如果在server或location要修改的必要,再进行修改

fastcgi_cache_methods

Syntax: fastcgi_cache_methods GET | HEAD | POST …;
Default: GET HEAD;
Context: http, server, location

允许缓存的请求类型

fastcgi_cache_min_uses

Syntax: fastcgi_cache_min_uses number;
Default: 1;
Context: http, server, location

最少要被请求多少次才会缓存

fastcgi_cache_use_stale

syntax: fastcgi_cache_use_stale [error | timeout | invalid_header | updating | http_500 | http_503 | http_403 | http_404 | off]
default: fastcgi_cache_use_stale off;
context: http, server, location

定义哪些情况下用过期缓存

1
fastcgi_cache_use_stale error timeout invalid_header http_500 http_503 updating;

fastcgi_cache_valid

Syntax: fastcgi_cache_valid [code …] time;
Default: —
Context: http, server, location

针对不同的状态,设置不同的缓存时间,可多行

1
2
3
4
fastcgi_cache_valid 200 302  10m;
fastcgi_cache_valid 301 1h;
fastcgi_cache_valid any 1m;
fastcgi_cache_valid 1m; #等同于 fastcgi_cache_valid 200 301 302 1m;

在响应头中设置缓存时间

缓存的参数也可以直接在响应头中设置。这比使用指令设置缓存时间具有更高的优先级。

  • “X-Accel-Expires”标头字段设置响应的缓存时间(以秒为单位)。零禁用缓存。如果值以@前缀开始,则它设置以当前时间开始的以秒为单位的绝对时间,直到响应可以被缓存。
  • 如果报头不包括“X-Accel-Expires”字段,则可以在报头字段“Expires”或“Cache-Control”中设置高速缓存的参数。
  • 如果头包括“Set-Cookie”字段,则这样的响应将不被缓存。
  • 如果头中Vary *,则这样的响应将不被缓存(1.7.7)。如果报头包括具有另一个值的“Vary”字段,则这样的响应将被缓存,考虑相应的请求报头字段(1.7.7)。

为什么没有被缓存?

  • Nginx fastcgi_cache在缓存后端fastcgi响应时,当响应里包含“set-cookie”时,不缓存;
  • 当响应头包含Expires时,如果过期时间大于当前服务器时间,则nginx_cache会缓存该响应,否则,则不缓存;
  • 当响应头包含Cache-Control时,如果Cache-Control参数值为no-cache、no-store、private中任意一个时,则不缓存
  • 头中Vary * (1.7.7)

可以使用fastcgi_ignore_headers指令禁用对这些响应头字段中的一个或多个的处理。

1
2
fastcgi_ignore_headers Expires Cache-Control;
fastcgi_ignore_headers Set-Cookie; #如果加入这条,则后端无法设置cookie,session

配置实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
http{
#...
fastcgi_cache_path /dev/shm/nginx_cache levels=1:2 keys_zone=fcgicache:10m inactive=30m max_size=128m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_temp_path /dev/shm/nginx_tmp;
#...

server{
#...
location ~ \.php($|/) {
#...
fastcgi_cache fcgicache; #用哪个缓存空间
fastcgi_cache_valid 1m; #200 301 302缓存1分钟
fastcgi_cache_valid 404 500 502 503 504 0s; #404 500 502 503 504不缓存
fastcgi_cache_valid any 3h; #其它缓存3小时
fastcgi_cache_min_uses 1; #经过一次请求后缓存
fastcgi_cache_use_stale error timeout invalid_header http_500 http_503 updating; #哪些情况下用过期缓存
fastcgi_ignore_headers Expires Cache-Control; #忽略Expires Cache-Control头
#...
}
#...
}
}

调试

  • $upstream_response_time为过期时间
  • $upstream_cache_status 表示此请求响应来自cache的状态

缓存状态:

状态 说明
MISS 未命中
EXPIRED expired, request was passed to backend Cache已过期
UPDATING expired, stale response was used due to proxy/fastcgi_cache_use_stale updating Cache已过期,(被其他nginx子进程)更新中
STALE expired, stale response was used due to proxy/fastcgi_cache_use_stale Cache已过期,响应数据不合法,被污染HIT 命中cache

PHP

逐个测试,测试时,注释其他的

1
2
3
4
5
6
7
8
9
10
header("Expires: ".gmdate("D, d M Y H:i:s", time()+10000).' GMT');
header("Expires: ".gmdate("D, d M Y H:i:s", time()-99999).' GMT');
header("X-Accel-Expires:5"); // 5s
header("Cache-Control: no-cache"); //no cache
header("Cache-Control: no-store"); //no cache
header("Cache-Control: private"); //no cache
header("Cache-Control: max-age=10"); //cache 10s
setcookie('hello',"testaaaa"); //no cache
echo date("Y-m-d H:i:s",time());
exit;

程序调用session_start时,php的session拓展自己输出的。session.cache_limit参数来决定输出包含哪种Expires的header,默认是nocache,修改php.ini的session.cache_limit参数为“none”即可让session模块不再输出这些http 响应头。或在调用session_start之前,使用session_cache_limiter函数来指定下该参数值。那为什么要在使用session时,发Expires、Cache-Control的http response header呢?我猜测了下,需要session时,基本上是用户跟服务器有交互,那么,既然有交互,就意味着用户的每次交互结果也可能不一样,就不能cache这个请求的结果,给返回给这个用户。同时,每个用户的交互结果都是不一样的,nginx也就不能把包含特殊Cache-Control的个人响应cache给其他人提供了。

非要用session_start 可以如下方式

1
2
3
4
session_cache_limiter("none");
session_start();
echo date("Y-m-d H:i:s",time());
exit;

#Mysql性能分析

执行mysql语句,只要在前面加上 explain 就可以分析查询语句的效率。

例如:

1
explain select focus_uid from user_focus where uid = xxx;

explain显示了mysql如何使用索引来处理select语句以及连接表。可以帮助选择更好的索引和写出更优化的查询语句。

具体解释如下:

id 本次 select 的标识符。在查询中每个 select都有一个顺序的数值。
select_type select 的类型,可能会有以下几种:

  • simple: 简单的 select (没有使用 union或子查询)

  • primary: 最外层的 select。

  • union: 第二层,在select 之后使用了 union。

  • dependent union: union 语句中的第二个select,依赖于外部子查询

  • subquery: 子查询中的第一个 select

  • dependent subquery: 子查询中的第一个 subquery依赖于外部的子查询

  • derived: 派生表 select(from子句中的子查询)

  • table:显示这一行的数据是关于哪张表的

  • type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_ref、ref、range、index 和ALL

  • system:表只有一行记录(等于系统表)。这是 const表连接类型的一个特例。

  • const:表中最多只有一行匹配的记录,它在查询一开始的时候就会被读取出来。由于只有一行记录,在余下的优化程序里该行记录的字段值可以被当作是一个 恒定值。const表查询起来非常快,因为只要读取一次!const 用于在和 primary key 或unique 索引中有固定值比较的情形。下面的几个查询中,tbl_name 就是 c表了。

  • eq_ref:从该表中会有一行记录被读取出来以和从前一个表中读取出来的记录做联合。与const类型不同的是,这是最好的连接类型。它用在索引所有部 分都用于做连接并且这个索引是一个primary key 或 unique 类型。eq_ref可以用于在进行”=”做比较时检索字段。比较的值可以是固定值或者是表达式,表达示中可以使用表里的字段,它们在读表之前已经准备好 了。

  • ref: 该表中所有符合检索值的记录都会被取出来和从上一个表中取出来的记录作联合。ref用于连接程序使用键的最左前缀或者是该键不是 primary key 或 unique索引(换句话说,就是连接程序无法根据键值只取得一条记录)的情况。当根据键值只查询到少数几条匹配的记录时,这就是一个不错的连接类型。 ref还可以用于检索字段使用 =操作符来比较的时候。

  • ref_or_null: 这种连接类型类似 ref,不同的是mysql会在检索的时候额外的搜索包含null 值的记录。

  • range: 只有在给定范围的记录才会被取出来,利用索引来取得一条记录。key字段表示使用了哪个索引。key_len字段包括了使用的键的最长部分。这种类型时 ref 字段值是 null。range用于将某个字段和一个定植用以下任何操作符比较时 =, <>, >,>=, <, <=, is null, <=>, between, 或 in。

  • index: 连接类型跟 all 一样,不同的是它只扫描索引树。它通常会比 all快点,因为索引文件通常比数据文件小。mysql在查询的字段知识单独的索引的一部分的情况下使用这种连接类型。

  • all: 将对该表做全部扫描以和从前一个表中取得的记录作联合。这时候如果第一个表没有被标识为const的话就不大好了,在其他情况下通常是非常糟糕的。正常地,可以通过增加索引使得能从表中更快的取得记录以避免all。

得保证查询至少达到range级别,最好能达到ref,否则就可能会出现性能问题。

  • possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句
  • key: 实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,MYSQL会选择优化不足的索引。这种情况下,可以在SELECT语句中使用force INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MYSQL忽略索引
  • key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好
  • ref: 显示索引的哪一列被使用了,如果可能的话,是一个常数
  • rows: MYSQL认为必须检查的用来返回请求数据的行数
  • Extra: 关于MYSQL如何解析查询的额外信息。将在表4.3中讨论,但这里可以看到的坏的例子是Using temporary和Using filesort,意思MYSQL根本不能使用索引,结果是检索会很慢

extra列返回的描述的意义

  • Distinct:一旦MYSQL找到了与行相联合匹配的行,就不再搜索了

  • Not exists: MYSQL优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了

  • Range checked for each Record(index map:#):没有找到理想的索引,因此对于从前面表中来的每一个行组合,MYSQL检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一

  • Using filesort: mysql需要额外的做一遍从而以排好的顺序取得记录。排序程序根据连接的类型遍历所有的记录,并且将所有符合 where条件的记录的要排序的键和指向记录的指针存储起来。这些键已经排完序了,对应的记录也会按照排好的顺序取出来。

  • Using index: 列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候

  • using temporary: mysql需要创建临时表存储结果以完成查询。这种情况通常发生在查询时包含了groupby 和 order by 子句,它以不同的方式列出了各个字段。

  • using where

where子句将用来限制哪些记录匹配了下一个表或者发送给客户端。除非你特别地想要取得或者检查表种的所有记录,否则的话当查询的extra 字段值不是 using where 并且表连接类型是 all 或 index时可能表示有问题。

如果你想要让查询尽可能的快,那么就应该注意 extra 字段的值为usingfilesort 和 using temporary 的情况。