Elasticsearch 7.X 安装并使用

ES 7目前安装相当简单,这里简单记录下步骤。

1,https://www.elastic.co/cn/downloads/elasticsearch 官网按照版本下载安装包,比如 LINUX X86_64shaasc

2,解压后基本可以直接使用,但是这里需要简单配置下

vim elasticsearch.yml

开启network,path,cluster.nam node.name相关配置

3,启动/bin/elasticsearch

二 同步数据

这里需要将历史数据同步,采用

https://github.com/elasticsearch-dump/elasticsearch-dump

即可,不过需要更新最新的nodejs,

npm i -g n

n latest

然后同步数据即可!

 

c++编译参数

代码文件
hello.h


#include <stdio.h>
#include <stdlib.h>

void hello();
void main();

hello.c


#include "hello.h"
void hello()
{
printf("this is in hello...\n");
}

void main()
{

printf("main \n");
}

一 简单编译

gcc hello.c -o hello
直接经过了预处理、编译、汇编和链接

二 分步拆解
1,预处理
gcc -E hello.c -o hello.i

预处理结果就是将stdio.h 文件中的内容插入到hello.c中了

2,编译
gcc -S hello.i -o hello.s
gcc的-S选项,表示在程序编译期间,在生成汇编代码后,停止,-o输出汇编代码文件。

3,汇编
gcc -c hello.s -o hello.o

4,链接
gcc hello.o -o test
gcc连接器是gas提供的,负责将程序的目标文件与所需的所有附加的目标文件连接起来,最终生成可执行文件。附加的目标文件包括静态连接库和动态连接库
三 库文件链接

去掉原始文件main相关内容


#include "hello.h"
int main()
{
hello();
return 0;
}

1,生成库文件
gcc hello.c -shared -fPIC -o libhello.so
-shared选项说明编译成的文件为动态链接库,不使用该选项相当于可执行文件
-fPIC 表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的。所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的
2,
gcc hello_b.c -L ./ -l hello -o hello
其中-L ./ 表示链接的文件在当前目录下
-lhello 代表链接的文件名 gcc会自动为其前面添加lib,在其后边添加.so 即libhello.so
3,设置lib库文件地址
export LD_LIBRARY_PATH=/home/work/code/gcc/:$LD_LIBRARY_PATH

scp免密码操作

在一些脚本中scp需要免密操作,这就需要机器通过rsa进行相互授权。

一,client端

1)生成密钥:ssh-keygen -t rsa

2)拷贝密钥 cat ~/.ssh/id_rsa.pub

二,server端

将刚刚拷贝的密钥直接放在~/.ssh/authorized_keys 即可,这样client端访问server就需要密码了,非常简单

VARCHAR(M)最多能存储的数据

我们知道对于VARCHAR(M)类型的列最多可以占用65535个字节。其中的M代表该类型最多存储的字符数量,如果我们使用ascii字符集的话,一个字符就代表一个字节,我们看看VARCHAR(65535)是否可用: mysql> CREATE TABLE varchar_size_demo(

-> c VARCHAR(65535)

-> ) CHARSET=ascii ROW_FORMAT=Compact;
ERROR 1118 (42000): Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change som mysql>

从报错信息里可以看出,MySQL对一条记录占用的最大存储空间是有限制的,除了BLOB或者TEXT类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字 节。所以MySQL服务器建议我们把存储类型改为TEXT或者BLOB的类型。这个65535个字节除了列本身的数据之外,还包括一些其他的数据(storage overhead),比如说我们为了存储一个VARCHAR(M)类型 的列,其实需要占用3部分存储空间:

真实数据
真实数据占用字节的长度
NULL值标识,如果该列有NOT NULL属性则可以没有这部分存储空间

如果该VARCHAR类型的列没有NOT NULL属性,那最多只能存储65532个字节的数据,因为真实数据的长度可能占用2个字节,NULL值标识需要占用1个字节:

CREATE TABLE varchar_size_demo( c VARCHAR(65532)

) CHARSET=ascii ROW_FORMAT=Compact; Query OK, 0 rows affected (0.02 sec)

如果VARCHAR类型的列有NOT NULL属性,那最多只能存储65533个字节的数据,因为真实数据的长度可能占用2个字节,不需要NULL值标识:

mysql> DROP TABLE varchar_size_demo; Query OK, 0 rows affected (0.01 sec)

CREATE TABLE varchar_size_demo( c VARCHAR(65533) NOT NULL

) CHARSET=ascii ROW_FORMAT=Compact; Query OK, 0 rows affected (0.02 sec)

如果VARCHAR(M)类型的列使用的不是ascii字符集,那会怎么样呢?来看一下:

mysql> DROP TABLE varchar_size_demo; Query OK, 0 rows affected (0.00 sec)

CREATE TABLE varchar_size_demo( c VARCHAR(65532)

) CHARSET=gbk ROW_FORMAT=Compact;
ERROR 1074 (42000): Column length too big for column ‘c’ (max = 32767); use BLOB or TEXT instead

CREATE TABLE varchar_size_demo( c VARCHAR(65532)

) CHARSET=utf8 ROW_FORMAT=Compact;
ERROR 1074 (42000): Column length too big for column ‘c’ (max = 21845); use BLOB or TEXT instead

从执行结果中可以看出,如果VARCHAR(M)类型的列使用的不是ascii字符集,那M的最大取值取决于该字符集表示一个字符最多需要的字节数。在列的值允许为NULL的情况下,gbk字符集表示一个字符最多 需要2个字节,那在该字符集下,M的最大取值就是32766(也就是:65532/2),也就是说最多能存储32766个字符;utf8字符集表示一个字符最多需要3个字节,那在该字符集下,M的最大取值就 是21844,就是说最多能存储21844(也就是:65532/3)个字符。

小贴士: 上述所言在列的值允许为NULL的情况下,gbk字符集下M的最大取值就是32766,utf8字符集下M的最大取值就是21844,这都是在表中只有一个字段的情况下说的,一定要记住一个行 中的所有列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过65535个字节!

Elasticsearch 如何做到快速检索 – 倒排索引的秘密

“All problems in computer science can be solved by another level of indirection.”

– David J. Wheeler

“计算机世界就是 trade-off 的艺术”

摘抄了一篇比较好的,上面图和方法在很多博客都被引用,但是相对组织的较好。

一、前言

最近接触的几个项目都使用到了 Elasticsearch (以下简称 ES ) 来存储数据和对数据进行搜索分析,就对 ES 进行了一些学习。本文整理自我自己的一次技术分享。

本文不会关注 ES 里面的分布式技术、相关 API 的使用,而是专注分享下 ”ES 如何快速检索“ 这个主题上面。这个也是我在学习之前对 ES 最感兴趣的部分。


本文大致包括以下内容:

  • 关于搜索

    • 传统关系型数据库和 ES 的差别

    • 搜索引擎原理

  • 细究倒排索引

    • 倒排索引具体是个什么样子的(posting list -> term dic -> term index)

    • 关于 postings list 的一些巧技 (FOR、Roaring Bitmaps)

    • 如何快速做联合查询?

二、关于搜索

先设想一个关于搜索的场景,假设我们要搜索一首诗句内容中带“前”字的古诗,

用 传统关系型数据库和 ES 实现会有什么差别?

如果用像 MySQL 这样的 RDBMS 来存储古诗的话,我们应该会去使用这样的 SQL 去查询

select name from poems where content like "%前%";
复制代码

这种我们称为顺序扫描法,需要遍历所有的记录进行匹配。

不但效率低,而且不符合我们搜索时的期望,比如我们在搜索“ABCD”这样的关键词时,通常还希望看到”A”,”AB”,”CD”,“ABC”的搜索结果。

于是乎就有了专业的搜索引擎,比如我们今天的主角 — ES。

搜索引擎原理

搜索引擎的搜索原理简单概括的话可以分为这么几步,

  • 内容爬取,停顿词过滤

    比如一些无用的像”的”,“了”之类的语气词/连接词

  • 内容分词,提取关键词

  • 根据关键词建立倒排索引

  • 用户输入关键词进行搜索

这里我们就引出了一个概念,也是我们今天的要剖析的重点 – 倒排索引。也是 ES 的核心知识点。

如果你了解 ES 应该知道,ES 可以说是对 Lucene 的一个封装,里面关于倒排索引的实现就是通过 lucene 这个 jar 包提供的 API 实现的,所以下面讲的关于倒排索引的内容实际上都是 lucene 里面的内容。

三、倒排索引

首先我们还不能忘了我们之前提的搜索需求,先看下建立倒排索引之后,我们上述的查询需求会变成什么样子,

这样我们一输入“前”,借助倒排索引就可以直接定位到符合查询条件的古诗。

当然这只是一个很大白话的形式来描述倒排索引的简要工作原理。在 ES 中,这个倒排索引是具体是个什么样的,怎么存储的等等,这些才是倒排索引的精华内容。

1. 几个概念

在进入下文之前,先描述几个前置概念。

term

关键词这个东西是我自己的讲法,在 ES 中,关键词被称为 term

postings list

还是用上面的例子,{静夜思, 望庐山瀑布}是 “前” 这个 term 所对应列表。在 ES 中,这些被描述为所有包含特定 term 文档的 id 的集合。由于整型数字 integer 可以被高效压缩的特质,integer 是最适合放在 postings list 作为文档的唯一标识的,ES 会对这些存入的文档进行处理,转化成一个唯一的整型 id。

再说下这个 id 的范围,在存储数据的时候,在每一个 shard 里面,ES 会将数据存入不同的 segment,这是一个比 shard 更小的分片单位,这些 segment 会定期合并。在每一个 segment 里面都会保存最多 2^31 个文档,每个文档被分配一个唯一的 id,从0(2^31)-1

相关的名词都是 ES 官方文档给的描述,后面参考材料中都可以找到出处。

2. 索引内部结构

上面所描述的倒排索引,仅仅是一个很粗糙的模型。真的要在实际生产中使用,当然还差的很远。

在实际生产场景中,比如 ES 最常用的日志分析,日志内容进行分词之后,可以得到多少的 term?

那么如何快速的在海量 term 中查询到对应的 term 呢?遍历一遍显然是不现实的。

term dictionary

于是乎就有了 term dictionary,ES 为了能快速查找到 term,将所有的 term 排了一个序,二分法查找。是不是感觉有点眼熟,这不就是 MySQL 的索引方式的,直接用 B+树建立索引词典指向被索引的数据。

term index

但是问题又来了,你觉得 Term Dictionary 应该放在哪里?肯定是放在内存里面吧?磁盘 io 那么慢。就像 MySQL 索引就是存在内存里面了。

但是如果把整个 term dictionary 放在内存里面会有什么后果呢?

内存爆了…

别忘了,ES 默认可是会对全部 text 字段进行索引,必然会消耗巨大的内存,为此 ES 针对索引进行了深度的优化。在保证执行效率的同时,尽量缩减内存空间的占用。

于是乎就有了 term index

Term index 从数据结构上分类算是一个“Trie 树”,也就是我们常说的字典树。这是一种专门处理字符串匹配的数据结构,用来解决在一组字符串集合中快速查找某个字符串的问题。

这棵树不会包含所有的 term,它包含的是 term 的一些前缀(这也是字典树的使用场景,公共前缀)。通过 term index 可以快速地定位到 term dictionary 的某个 offset,然后从这个位置再往后顺序查找。就想右边这个图所表示的。(怎么样,像不像我们查英文字典,我们定位 S 开头的第一个单词,或者定位到 Sh 开头的第一个单词,然后再往后顺序查询)

lucene 在这里还做了两点优化,一是 term dictionary 在磁盘上面是分 block 保存的,一个 block 内部利用公共前缀压缩,比如都是 Ab 开头的单词就可以把 Ab 省去。二是 term index 在内存中是以 FST(finite state transducers)的数据结构保存的。

FST 有两个优点:

  • 空间占用小。通过对词典中单词前缀和后缀的重复利用,压缩了存储空间
  • 查询速度快。O(len(str)) 的查询时间复杂度。

FST 的理论比较复杂,本文不细讲

延伸阅读:https://www.shenyanchao.cn/blog/2018/12/04/lucene-fst/

OK,现在我们能得到 lucene 倒排索引大致是个什么样子的了。

四、关于 postings list 的一些巧技

在实际使用中,postings list 还需要解决几个痛点,

  • postings list 如果不进行压缩,会非常占用磁盘空间,
  • 联合查询下,如何快速求交并集(intersections and unions)

对于如何压缩,可能会有人觉得没有必要,”posting list 不是已经只存储文档 id 了吗?还需要压缩?”,但是如果在 posting list 有百万个 doc id 的情况,压缩就显得很有必要了。(比如按照朝代查询古诗?),至于为啥需要求交并集,ES 是专门用来搜索的,肯定会有很多联合查询的需求吧 (AND、OR)。

按照上面的思路,我们先将如何压缩。

1. 压缩

Frame of Reference

在 lucene 中,要求 postings lists 都要是有序的整形数组。这样就带来了一个很好的好处,可以通过 增量编码(delta-encode)这种方式进行压缩。

比如现在有 id 列表 [73, 300, 302, 332, 343, 372],转化成每一个 id 相对于前一个 id 的增量值(第一个 id 的前一个 id 默认是 0,增量就是它自己)列表是[73, 227, 2, 30, 11, 29]在这个新的列表里面,所有的 id 都是小于 255 的,所以每个 id 只需要一个字节存储

实际上 ES 会做的更加精细,

它会把所有的文档分成很多个 block,每个 block 正好包含 256 个文档,然后单独对每个文档进行增量编码,计算出存储这个 block 里面所有文档最多需要多少位来保存每个 id,并且把这个位数作为头信息(header)放在每个 block 的前面。这个技术叫 Frame of Reference

上图也是来自于 ES 官方博客中的一个示例(假设每个 block 只有 3 个文件而不是 256)。

FOR 的步骤可以总结为:

进过最后的位压缩之后,整型数组的类型从固定大小 (8,16,32,64 位)4 种类型,扩展到了[1-64] 位共 64 种类型。

通过以上的方式可以极大的节省 posting list 的空间消耗,提高查询性能。不过 ES 为了提高 filter 过滤器查询的性能,还做了更多的工作,那就是缓存

Roaring Bitmaps (for filter cache)

在 ES 中,可以使用 filters 来优化查询,filter 查询只处理文档是否匹配与否,不涉及文档评分操作,查询的结果可以被缓存。

对于 filter 查询,es 提供了 filter cache 这种特殊的缓存,filter cache 用来存储 filters 得到的结果集。缓存 filters 不需要太多的内存,它只保留一种信息,即哪些文档与 filter 相匹配。同时它可以由其它的查询复用,极大地提升了查询的性能。

我们上面提到的 Frame Of Reference 压缩算法对于 postings list 来说效果很好,但对于需要存储在内存中的 filter cache 等不太合适。

filter cache 会存储那些经常使用的数据,针对 filter 的缓存就是为了加速处理效率,对压缩算法要求更高。

对于这类 postings list,ES 采用不一样的压缩方式。那么让我们一步步来。

首先我们知道 postings list 是 Integer 数组,具有压缩空间。

假设有这么一个数组,我们第一个压缩的思路是什么?用位的方式来表示,每个文档对应其中的一位,也就是我们常说的位图,bitmap。

它经常被作为索引用在数据库、查询引擎和搜索引擎中,并且位操作(如 and 求交集、or 求并集)之间可以并行,效率更好。

但是,位图有个很明显的缺点,不管业务中实际的元素基数有多少,它占用的内存空间都恒定不变。也就是说不适用于稀疏存储。业内对于稀疏位图也有很多成熟的压缩方案,lucene 采用的就是roaring bitmaps

我这里用简单的方式描述一下这个压缩过程是怎么样,

将 doc id 拆成高 16 位,低 16 位。对高位进行聚合 (以高位做 key,value 为有相同高位的所有低位数组),根据低位的数据量 (不同高位聚合出的低位数组长度不相同),使用不同的 container(数据结构) 存储。

  • len<4096 ArrayContainer 直接存值

  • len>=4096 BitmapContainer 使用 bitmap 存储

分界线的来源:value 的最大总数是为2^16=65536. 假设以 bitmap 方式存储需要 65536bit=8kb,而直接存值的方式,一个值 2 byte,4K 个总共需要2byte*4K=8kb。所以当 value 总量 <4k 时,使用直接存值的方式更节省空间。

空间压缩主要体现在:

  • 高位聚合 (假设数据中有 100w 个高位相同的值,原先需要 100w2byte,现在只要 12byte)

  • 低位压缩

缺点就在于位操作的速度相对于原生的 bitmap 会有影响。

这就是 trade-off 呀。平衡的艺术。

2. 联合查询

讲完了压缩,我们再来讲讲联合查询。

先讲简单的,如果查询有 filter cache,那就是直接拿 filter cache 来做计算,也就是说位图来做 AND 或者 OR 的计算。

如果查询的 filter 没有缓存,那么就用 skip list 的方式去遍历磁盘上的 postings list。

以上是三个 posting list。我们现在需要把它们用 AND 的关系合并,得出 posting list 的交集。首先选择最短的 posting list,逐个在另外两个 posting list 中查找看是否存在,最后得到交集的结果。遍历的过程可以跳过一些元素,比如我们遍历到绿色的 13 的时候,就可以跳过蓝色的 3 了,因为 3 比 13 要小。

用 skip list 还会带来一个好处,还记得前面说的吗,postings list 在磁盘里面是采用 FOR 的编码方式存储的

会把所有的文档分成很多个 block,每个 block 正好包含 256 个文档,然后单独对每个文档进行增量编码,计算出存储这个 block 里面所有文档最多需要多少位来保存每个 id,并且把这个位数作为头信息(header)放在每个 block 的前面。

因为这个 FOR 的编码是有解压缩成本的。利用 skip list,除了跳过了遍历的成本,也跳过了解压缩这些压缩过的 block 的过程,从而节省了 cpu

五、总结

下面我们来做一个技术总结(感觉有点王刚老师的味道😂)

  • 为了能够快速定位到目标文档,ES 使用倒排索引技术来优化搜索速度,虽然空间消耗比较大,但是搜索性能提高十分显著。
  • 为了能够在数量巨大的 terms 中快速定位到某一个 term,同时节约对内存的使用和减少磁盘 io 的读取,lucene 使用 “term index -> term dictionary -> postings list” 的倒排索引结构,通过 FST 压缩放入内存,进一步提高搜索效率。
  • 为了减少 postings list 的磁盘消耗,lucene 使用了 FOR(Frame of Reference)技术压缩,带来的压缩效果十分明显。
  • ES 的 filter 语句采用了 Roaring Bitmap 技术来缓存搜索结果,保证高频 filter 查询速度的同时降低存储空间消耗。
  • 在联合查询时,在有 filter cache 的情况下,会直接利用位图的原生特性快速求交并集得到联合查询结果,否则使用 skip list 对多个 postings list 求交并集,跳过遍历成本并且节省部分数据的解压缩 cpu 成本

Elasticsearch 的索引思路

将磁盘里的东西尽量搬进内存,减少磁盘随机读取次数 (同时也利用磁盘顺序读特性),结合各种压缩算法,用及其苛刻的态度使用内存。

所以,对于使用 Elasticsearch 进行索引时需要注意:

  • 不需要索引的字段,一定要明确定义出来,因为默认是自动建索引的
  • 同样的道理,对于 String 类型的字段,不需要 analysis 的也需要明确定义出来,因为默认也是会 analysis 的
  • 选择有规律的 ID 很重要,随机性太大的 ID(比如 Java 的 UUID) 不利于查询

最后说一下,技术选型永远伴随着业务场景的考量,每种数据库都有自己要解决的问题(或者说擅长的领域),对应的就有自己的数据结构,而不同的使用场景和数据结构,需要用不同的索引,才能起到最大化加快查询的目的。

这篇文章讲的虽是 Lucene 如何实现倒排索引,如何精打细算每一块内存、磁盘空间、如何用诡谲的位运算加快处理速度,但往高处思考,再类比一下 MySQL,你就会发现,虽然都是索引,但是实现起来,截然不同。笼统的来说,b-tree 索引是为写入优化的索引结构。当我们不需要支持快速的更新的时候,可以用预先排序等方式换取更小的存储空间,更快的检索速度等好处,其代价就是更新慢,就像 ES。

希望本篇文章能给你带来一些收获~

参考文档

  • https://www.elastic.co/cn/blog/frame-of-reference-and-roaring-bitmaps
  • https://www.elastic.co/cn/blog/found-elasticsearch-from-the-bottom-up
  • http://blog.mikemccandless.com/2014/05/choosing-fast-unique-identifier-uuid.html
  • https://www.infoq.cn/article/database-timestamp-02
  • https://zhuanlan.zhihu.com/p/137574234

作者:Richard_Yi
链接:https://juejin.im/post/6889020742366920712
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

m3u8 文件格式详解

简介

M3U8 是 Unicode 版本的 M3U,用 UTF-8 编码。”M3U” 和 “M3U8” 文件都是苹果公司使用的 HTTP Live Streaming(HLS) 协议格式的基础,这种协议格式可以在 iPhone 和 Macbook 等设备播放。

上述文字定义来自于维基百科。可以看到,m3u8 文件其实是 HTTP Live Streaming(缩写为 HLS) 协议的部分内容,而 HLS 是一个由苹果公司提出的基于 HTTP流媒体网络传输协议

HLS 的工作原理是把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适应不同的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的 extended M3U (m3u8) playlist文件,用于寻找可用的媒体流。
HLS 只请求基本的 HTTP 报文,与实时传输协议(RTP)不同,HLS 可以穿过任何允许 HTTP 数据通过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。

简而言之,HLS 是新一代流媒体传输协议,其基本实现原理为将一个大的媒体文件进行分片,将该分片文件资源路径记录于 m3u8 文件(即 playlist)内,其中附带一些额外描述(比如该资源的多带宽信息···)用于提供给客户端。客户端依据该 m3u8 文件即可获取对应的媒体资源,进行播放。

因此,客户端获取 HLS 流文件,主要就是对 m3u8 文件进行解析操作。

那么,下面就简单介绍下 m3u8 文件。

M3U8 文件简介

m3u8 文件实质是一个播放列表(playlist),其可能是一个媒体播放列表(Media Playlist),或者是一个主列表(Master Playlist)。但无论是哪种播放列表,其内部文字使用的都是 utf-8 编码。

当 m3u8 文件作为媒体播放列表(Meida Playlist)时,其内部信息记录的是一系列媒体片段资源,顺序播放该片段资源,即可完整展示多媒体资源。其格式如下所示:

#EXTM3U
#EXT-X-TARGETDURATION:10

#EXTINF:9.009,
http://media.example.com/first.ts
#EXTINF:9.009,
http://media.example.com/second.ts
#EXTINF:3.003,
http://media.example.com/third.ts

对于点播来说,客户端只需按顺序下载上述片段资源,依次进行播放即可。而对于直播来说,客户端需要 定时重新请求 该 m3u8 文件,看下是否有新的片段数据需要进行下载并播放。

当 m3u8 作为主播放列表(Master Playlist)时,其内部提供的是同一份媒体资源的多份流列表资源(Variant Stream)。其格式如下所示:

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=150000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/low/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=240000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/lo_mid/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=440000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/hi_mid/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=640000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/high/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5"
http://example.com/audio/index.m3u8

该备用流资源指定了多种不同码率,不同格式的媒体播放列表,并且,该备用流资源也可同时提供不同版本的资源内容,比如不同语言的音频文件,不同角度拍摄的视屏文件等等。客户可以根据不同的网络状态选取合适码流的资源,并且最好根据用户喜好选择合适的资源内容。

更多详细内容,可查看:

以上,就是 m3u8 文件的大概内容。下面,我们就对 m3u8 内容格式进行讲解。

m3u8 文件格式简解

m3u8 的文件格式主要包含三方面内容:

  1. 文件播放列表格式定义:播放列表(Playlist,也即 m3u8 文件) 内容需严格满足规范定义所提要求。下面罗列一些主要遵循的条件:
  • m3u8 文件必须以 utf-8 进行编码,不能使用 Byte Order Mark(BOM)字节序, 不能包含 utf-8 控制字符(U+0000 ~ U_001F 和 U+007F ~ u+009F)。
  • m3u8 文件的每一行要么是一个 URI,要么是空行,要么就是以 # 开头的字符串。不能出现空白字符,除了显示声明的元素。
  • m3u8 文件中以 # 开头的字符串要么是注释,要么就是标签。标签以 #EXT 开头,大小写敏感。
  1. 属性列表(Attribute Lists):某些特定的标签的值为属性列表。标签后面的属性列表以 逗号 作为分隔符,分离出多组不带空格的 属性/值 对。
    属性/值 对的语法格式如下:

AttributeName=AttributeValue

其中:

  • 属性AttributeName是由 [A..Z],[0..9] 和 - 组成的不带引号的字符串。因此,属性AttributeName只能使用大写字母,不能使用小写字母,并且AttributeName=中间不能有空格,同理,=AttributeValue之间也不能有空格。
  • AttributeValue的只能取以下类型:
    • 十进制整型(decimal-interger):由 [0..9] 之间组成的十进制不带引号的字符串,范围为 0 ~ 2^{64}(18446744073709551615),字符长度为 1 ~ 20 之间。
    • 十六进制序列:由 [0..9] 和 [A..F] 且前缀为 0x 或 0X 组合成的不带引号的字符串。其序列的最大长度取决于他的属性名AttributeNames
    • 带符号十进制浮点型(signed-decimal-floating-point):由 [0..9],-.组合成的不带引号的字符串。
    • 字符串(quoted-string):由双引号包裹表示的字符串。其中,0xA,0xD 和 双引号"不能出现在该字符串中。该字符串区分大小写。
    • 可枚举字符串(enumerated-string):由AttributeName显示定义的一系列不带引号的字符串。该字符串不能包含双引号",逗号,和空白字符。
    • decimal-resolution:由字符x进行隔离的两个十进制整型数。第一个整型表示水平宽度大小,第二个整型数表示垂直方向高度大小(单位:像素)。
  1. 标签:标签用于指定 m3u8 文件的全局参数或在其后面的切片文件/媒体播放列表的一些信息。

标签的类型可分为五种类型:基础标签(Basic Tags)媒体片段类型标签(Media Segment Tags)媒体播放列表类型标签主播放列表类型标签播放列表类型标签。其具体内容如下所示:

  • 基础标签(Basic Tags):可同时适用于媒体播放列表(Media Playlist)和主播放列表(Master Playlist)。具体标签如下:
    • EXTM3U:表明该文件是一个 m3u8 文件。每个 M3U 文件必须将该标签放置在第一行。
    • EXT-X-VERSION:表示 HLS 的协议版本号,该标签与流媒体的兼容性相关。该标签为全局作用域,使能整个 m3u8 文件;每个 m3u8 文件内最多只能出现一个该标签定义。如果 m3u8 文件不包含该标签,则默认为协议的第一个版本。
  • 媒体片段类型标签(Media Segment Tags):每个切片 URI 前面都有一系列媒体片段标签对其进行描述。有些片段标签只对其后切片资源有效;有些片段标签对其后所有切片都有效,直到后续遇到另一个该标签描述。媒体片段类型标签不能出现在主播放列表(Master Playlist)中。具体标签如下:
    • EXTINF:表示其后 URL 指定的媒体片段时长(单位为秒)。每个 URL 媒体片段之前必须指定该标签。该标签的使用格式为:

      #EXTINF:<duration>,[<title>]
      

      其中:

      • duration:可以为十进制的整型或者浮点型,其值必须小于或等于 EXT-X-TARGETDURATION 指定的值。
        注:建议始终使用浮点型指定时长,这可以让客户端在定位流时,减少四舍五入错误。但是如果兼容版本号 EXT-X-VERSION 小于 3,那么必须使用整型。
    • EXT-X-BYTERANGE:该标签表示接下来的切片资源是其后 URI 指定的媒体片段资源的局部范围(即截取 URI 媒体资源部分内容作为下一个切片)。该标签只对其后一个 URI 起作用。其格式为:

      #EXT-X-BYTERANGE:<n>[@<o>]
      

      其中:

      • n是一个十进制整型,表示截取片段大小(单位:字节)。
      • 可选参数o也是一个十进制整型,指示截取起始位置(以字节表示,在 URI 指定的资源开头移动该字节位置后进行截取)。
        如果o未指定,则截取起始位置从上一个该标签截取完成的下一个字节(即上一个n+o+1)开始截取。
        如果没有指定该标签,则表明的切分范围为整个 URI 资源片段。
        注:使用 EXT-X-BYTERANGE 标签要求兼容版本号 EXT-X-VERSION 大于等于 4。
    • EXT-X-DISCONTINUITY:该标签表明其前一个切片与下一个切片之间存在中断。其格式为:

      #EXT-X-DISCONTINUITY
      

      当以下任一情况变化时,必须使用该标签:

      • 文件格式(file format)
      • 数字(number),类型(type),媒体标识符(identifiers of tracks)
      • 时间戳序列(timestamp sequence)

      当以下任一情况变化时,应当使用该标签:

      • 编码参数(encoding parameters)
      • 编码序列(encoding sequence)

      注:EXT-X-DISCONTINUITY 的一个经典使用场景就是在视屏流中插入广告,由于视屏流与广告视屏流不是同一份资源,因此在这两种流切换时使用 EXT-X-DISCONTINUITY 进行指明,客户端看到该标签后,就会处理这种切换中断问题,让体验更佳。
      更多详细内容,请查看:Incorporating Ads into a Playlist

    • EXT-X-KEY:媒体片段可以进行加密,而该标签可以指定解密方法。
      该标签对所有 媒体片段 和 由标签 EXT-X-MAP 声明的围绕其间的所有 媒体初始化块(Meida Initialization Section) 都起作用,直到遇到下一个 EXT-X-KEY(若 m3u8 文件只有一个 EXT-X-KEY 标签,则其作用于所有媒体片段)。
      多个 EXT-X-KEY 标签如果最终生成的是同样的秘钥,则他们都可作用于同一个媒体片段。
      该标签使用格式为:

      #EXT-X-KEY:<attribute-list>
      

      属性列表可以包含如下几个键:

      • METHOD:该值是一个可枚举的字符串,指定了加密方法。
        该键是必须参数。其值可为NONEAES-128SAMPLE-AES当中的一个。
        其中:
        NONE:表示切片未进行加密(此时其他属性不能出现);
        AES-128:表示表示使用 AES-128 进行加密。
        SAMPLE-AES:意味着媒体片段当中包含样本媒体,比如音频或视频,它们使用 AES-128 进行加密。这种情况下 IV 属性可以出现也可以不出现。
      • URI:指定密钥路径。
        该密钥是一个 16 字节的数据。
        该键是必须参数,除非 METHODNONE
      • IV:该值是一个 128 位的十六进制数值。
        AES-128 要求使用相同的 16字节 IV 值进行加密和解密。使用不同的 IV 值可以增强密码强度。
        如果属性列表出现 IV,则使用该值;如果未出现,则默认使用媒体片段序列号(即 EXT-X-MEDIA-SEQUENCE)作为其 IV 值,使用大端字节序,往左填充 0 直到序列号满足 16 字节(128 位)。
      • KEYFORMAT:由双引号包裹的字符串,标识密钥在密钥文件中的存储方式(密钥文件中的 AES-128 密钥是以二进制方式存储的16个字节的密钥)。
        该属性为可选参数,其默认值为"identity"
        使用该属性要求兼容版本号 EXT-X-VERSION 大于等于 5。
      • KEYFORMATVERSIONS:由一个或多个被/分割的正整型数值构成的带引号的字符串(比如:"1""1/2""1/2/5")。
        如果有一个或多特定的 KEYFORMT 版本被定义了,则可使用该属性指示具体版本进行编译。
        该属性为可选参数,其默认值为"1"
        使用该属性要求兼容版本号 EXT-X-VERSION 大于等于 5。
    • EXT-X-MAP:该标签指明了获取媒体初始化块(Meida Initialization Section)的方法。
      该标签对其后所有媒体片段生效,直至遇到另一个 EXT-X-MAP 标签。
      其格式为:

      #EXT-X-MAP:<attribute-list>
      

      其属性列表取值范围如下:

      • URI:由引号包裹的字符串,指定了包含媒体初始化块的资源的路径。该属性为必选参数。
      • BYTERANGE:由引号包裹的字符串,指定了媒体初始化块在 URI 指定的资源的位置(片段)。
        该属性指定的范围应当只包含媒体初始化块。
        该属性为可选参数,如果未指定,则表示 URI 指定的资源就是全部的媒体初始化块。
    • EXT-X-PROGRAM-DATE-TIME:该标签使用一个绝对日期/时间表明第一个样本片段的取样时间。
      其格式为:

      #EXT-X-PROGRAM-DATE-TIME:<date-time-msec>
      

      其中,date-time-msec是一个 ISO/IEC 8601:2004 规定的日期格式,形如:YYYY-MM-DDThh:mm:ss.SSSZ。

    • EXT-X-DATERANGE:该标签定义了一系列由属性/值对组成的日期范围。
      其格式为:

      #EXT-X-DATERANGE:<attribute-list>
      

      其属性列表取值如下:

      • ID:双引号包裹的唯一指明日期范围的标识。
        该属性为必选参数。
      • CLASS:双引号包裹的由客户定义的一系列属性与与之对应的语意值。
        所有拥有同一 CLASS 属性的日期范围必须遵守对应的语意。
        该属性为可选参数。
      • START-DATE:双引号包裹的日期范围起始值。
        该属性为必选参数。
      • END-DATE:双引号包裹的日期范围结束值。
        该属性值必须大于或等于 START-DATE
        该属性为可选参数。
      • DURATION:日期范围的持续时间是一个十进制浮点型数值类型(单位:秒)。
        该属性值不能为负数。
        当表达立即时间时,将该属性值设为 0 即可。
        该属性为可选参数。
      • PLANNED-DURATION:该属性为日期范围的期望持续时长。
        其值为一个十进制浮点数值类型(单位:秒)。
        该属性值不能为负数。
        在预先无法得知真实持续时长的情况下,可使用该属性作为日期范围的期望预估时长。
        该属性为可选参数。
    • X-<client-attribute>X-前缀是预留给客户端自定义属性的命名空间。
      客户端自定义属性名时,应当使用反向 DNS(reverse-DNS)语法来避免冲突。
      自定义属性值必须是使用双引号包裹的字符串,或者是十六进制序列,或者是十进制浮点数,比如:X-COM-EXAMPLE-AD-ID="XYZ123"
      该属性为可选参数。
    • SCTE35-CMD, SCTE35-OUT, SCTE35-IN:用于携带 SCET-35 数据。
      该属性为可选参数。
    • END-ON-NEXT:该属性值为一个可枚举字符串,其值必须为YES
      该属性表明达到该范围末尾,也即等于后续范围的起始位置 START-DATE。后续范围是指具有相同 CLASS 的,在该标签 START-DATE 之后的具有最早 START-DATE 值的日期范围。
      该属性时可选参数。
  • 媒体播放列表类型标签:媒体播放列表标签为 m3u8 文件的全局参数信息。
    这些标签只能在 m3u8 文件中至多出现一次。
    媒体播放列表(Media Playlist)标签不能出现在主播放列表(Master Playlist)中。
    媒体播放列表具体标签如下所示:

    • EXT-X-TARGETDURATION:表示每个视频分段最大的时长(单位秒)。
      该标签为必选标签。
      其格式为:

      #EXT-X-TARGETDURATION:<s>
      

      其中:参数s表示目标时长(单位:秒)。

    • EXT-X-MEDIA-SEQUENCE:表示播放列表第一个 URL 片段文件的序列号。
      每个媒体片段 URL 都拥有一个唯一的整型序列号。
      每个媒体片段序列号按出现顺序依次加 1。
      如果该标签未指定,则默认序列号从 0 开始。
      媒体片段序列号与片段文件名无关。
      其格式为:

      #EXT-X-MEDIA-SEQUENCE:<number>
      

      其中:参数number即为切片序列号。

    • EXT-X-DISCONTINUITY-SEQUENCE:该标签使能同步相同流的不同 Rendition 和 具备 EXT-X-DISCONTINUITY 标签的不同备份流。
      其格式为:

      #EXT-X-DISCONTINUITY-SEQUENCE:<number>
      

      其中:参数number为一个十进制整型数值。
      如果播放列表未设置 EXT-X-DISCONTINUITY-SEQUENCE 标签,那么对于第一个切片的中断序列号应当为 0。

    • EXT-X-ENDLIST:表明 m3u8 文件的结束。
      该标签可出现在 m3u8 文件任意位置,一般是结尾。
      其格式为:

      #EXT-X-ENDLIST
      
    • EXT-X-PLAYLIST-TYPE:表明流媒体类型。全局生效。
      该标签为可选标签。
      其格式为:

      #EXT-X-PLAYLIST-TYPE:<type-enum>
      

      其中:type-enum可选值如下:

      • VOD:即 Video on Demand,表示该视屏流为点播源,因此服务器不能更改该 m3u8 文件;
      • EVENT:表示该视频流为直播源,因此服务器不能更改或删除该文件任意部分内容(但是可以在文件末尾添加新内容)。
        注:VOD 文件通常带有 EXT-X-ENDLIST 标签,因为其为点播源,不会改变;而 EVEVT 文件初始化时一般不会有 EXT-X-ENDLIST 标签,暗示有新的文件会添加到播放列表末尾,因此也需要客户端定时获取该 m3u8 文件,以获取新的媒体片段资源,直到访问到 EXT-X-ENDLIST 标签才停止)。
    • EXT-X-I-FRAMES-ONLY:该标签表示每个媒体片段都是一个 I-frameI-frames 帧视屏编码不依赖于其他帧数,因此可以通过 I-frame 进行快速播放,急速翻转等操作。
      该标签全局生效。
      其格式为:

      #EXT-X-I-FRAMES-ONLY
      

      如果播放列表设置了 EXT-X-I-FRAMES-ONLY,那么切片的时长(EXTINF 标签的值)即为当前切片 I-frame 帧开始到下一个 I-frame 帧出现的时长。
      媒体资源如果包含 I-frame 切片,那么必须提供媒体初始化块或者通过 EXT-X-MAP 标签提供媒体初始化块的获取途径,这样客户端就能通过这些 I-frame 切片以任意顺序进行加载和解码。
      如果 I-frame 切片设置了 EXT-BYTERANGE,那么就绝对不能提供媒体初始化块。
      使用 EXT-X-I-FRAMES-ONLY 要求的兼容版本号 EXT-X-VERSION 大于等于 4。

  • 主播放列表类型标签:主播放列表(Master Playlist)定义了备份流,多语言翻译流和其他全局参数。
    主播放列表标签绝不能出现在媒体播放列表(Media Playlist)中。
    其具体标签如下:

    • EXT-X-MEDIA:用于指定相同内容的可替换的多语言翻译播放媒体列表资源。
      比如,通过三个 EXT-X-MEIDA 标签,可以提供包含英文,法语和西班牙语版本的相同内容的音频资源,或者通过两个 EXT-X-MEDIA 提供两个不同拍摄角度的视屏资源。
      其格式为:

      #EXT-X-MEDIA:<attribute-list>
      

      其中,属性列表取值范围如下:

      • TYPE:该属性值为一个可枚举字符串。
        其值有如下四种:AUDIOVIDEOSUBTITLESCLOSED-CAPTIONS
        通常使用的都是CLOSED-CAPTIONS
        该属性为必选参数。
      • URI:双引号包裹的媒体资源播放列表路径。
        如果 TYPE 属性值为 CLOSED-CAPTIONS,那么则不能提供 URI
        该属性为可选参数。
      • GROUP-ID:双引号包裹的字符串,表示多语言翻译流所属组。
        该属性为必选参数。
      • LANGUAGE:双引号包裹的字符串,用于指定流主要使用的语言。
        该属性为可选参数。
      • ASSOC-LANGUAGE:双引号包裹的字符串,其内包含一个语言标签,用于提供多语言流的其中一种语言版本。
        该参数为可选参数。
      • NAME:双引号包裹的字符串,用于为翻译流提供可读的描述信息。
        如果设置了 LANGUAGE 属性,那么也应当设置 NAME 属性。
        该属性为必选参数。
      • DEFAULT:该属性值为一个可枚举字符串。
        可选值为YESNO
        该属性未指定时默认值为NO
        如果该属性设为YES,那么客户端在缺乏其他可选信息时应当播放该翻译流。
        该属性为可选参数。
      • AUTOSELECT:该属性值为一个可枚举字符串。
        其有效值为YESNO
        未指定时,默认设为NO
        如果该属性设置YES,那么客户端在用户没有显示进行设置时,可以选择播放该翻译流,因为其能配置当前播放环境,比如系统语言选择。
        如果设置了该属性,那么当 DEFAULT 设置YES时,该属性也必须设置为YES
        该属性为可选参数。
      • FORCED:该属性值为一个可枚举字符串。
        其有效值为YESNO
        未指定时,默认设为NO
        只有在设置了 TYPESUBTITLES 时,才可以设置该属性。
        当该属性设为YES时,则暗示该翻译流包含重要内容。当设置了该属性,客户端应当选择播放匹配当前播放环境最佳的翻译流。
        当该属性设为NO时,则表示该翻译流内容意图用于回复用户显示进行请求。
        该属性为可选参数。
      • INSTREAM-ID:由双引号包裹的字符串,用于指示切片的语言(Rendition)版本。
        TYPE 设为 CLOSED-CAPTIONS 时,必须设置该属性。
        其可选值为:"CC1", "CC2", "CC3", "CC4""SERVICEn"n的值为 1~63)。
        对于其他 TYPE 值,该属性绝不能进行设置。
      • CHARACTERISTICS:由双引号包裹的由一个或多个由逗号分隔的 UTI 构成的字符串。
        每个 UTI 表示一种翻译流的特征。
        该属性可包含私有 UTI。
        该属性为可选参数。
      • CHANNELS:由双引号包裹的有序,由反斜杠/分隔的参数列表组成的字符串。
        所有音频 EXT-X-MEDIA 标签应当都设置 CHANNELS 属性。
        如果主播放列表包含两个相同编码但是具有不同数目 channed 的翻译流,则必须设置 CHANNELS 属性;否则,CHANNELS 属性为可选参数。
    • EXT-X-STREAM-INF:该属性指定了一个备份源。该属性值提供了该备份源的相关信息。
      其格式为:

      #EXT-X-STREAM-INF:<attribute-list>
      <URI>
      

      其中:

      • URI 指定的媒体播放列表携带了该标签指定的翻译备份源。
        URI 为必选参数。
      • EXT-X-STREAM-INF 标签的参数属性列表有如下选项:
        • BANDWIDTH:该属性为每秒传输的比特数,也即带宽。代表该备份流的巅峰速率。
          该属性为必选参数。
        • AVERAGE-BANDWIDTH:该属性为备份流的平均切片传输速率。
          该属性为可选参数。
        • CODECS:双引号包裹的包含由逗号分隔的格式列表组成的字符串。
          每个 EXT-X-STREAM-INF 标签都应当携带 CODECS 属性。
        • RESOLUTION:该属性描述备份流视屏源的最佳像素方案。
          该属性为可选参数,但对于包含视屏源的备份流建议增加该属性设置。
        • FRAME-RATE:该属性用一个十进制浮点型数值作为描述备份流所有视屏最大帧率。
          对于备份流中任意视屏源帧数超过每秒 30 帧的,应当增加该属性设置。
          该属性为可选参数,但对于包含视屏源的备份流建议增加该属性设置。
        • HDCP-LEVEL:该属性值为一个可枚举字符串。
          其有效值为TYPE-0NONE
          值为TYPE-0表示该备份流可能会播放失败,除非输出被高带宽数字内容保护(HDCP)。
          值为NONE表示流内容无需输出拷贝保护。
          使用不同程度的 HDCP 加密备份流应当使用不同的媒体加密密钥。
          该属性为可选参数。在缺乏 HDCP 可能存在播放失败的情况下,应当提供该属性。
        • AUDIO:属性值由双引号包裹,其值必须与定义在主播放列表某处的设置了 TYPE 属性值为 AUDIOEXT-X-MEDIA 标签的 GROUP-ID 属性值相匹配。
          该属性为可选参数。
        • VIDEO:属性值由双引号包裹,其值必须与定义在主播放列表某处的设置了 TYPE 属性值为 VIDEOEXT-X-MEDIA 标签的 GROUP-ID 属性值相匹配。
          该属性为可选参数。
        • SUBTITLES:属性值由双引号包裹,其值必须与定义在主播放列表某处的设置了 TYPE 属性值为 SUBTITLESEXT-X-MEDIA 标签的 GROUP-ID 属性值相匹配。
          该属性为可选参数。
        • CLOSED-CAPTIONS:该属性值可以是一个双引号包裹的字符串或NONE
          如果其值为一个字符串,则必须与定义在主播放列表某处的设置了 TYPE 属性值为 CLOSED-CAPTIONSEXT-X-MEDIA 标签的 GROUP-ID 属性值相匹配。
          如果其值为NONE,则所有的 ext-x-stream-inf 标签必须同样将该属性设置NONE,表示主播放列表备份流均没有关闭的标题。对于某个备份流具备关闭标题,另一个备份流不具备关闭标题可能会触发播放中断。
          该属性为可选参数。
    • EXT-X-I-FRAME-STREAM-INF:该标签表明媒体播放列表文件包含多种媒体资源的 I-frame 帧。
      其格式为:

      #EXT-X-I-FRAME-STREAM-INF:<attribute-list>
      

      该标签的属性列表包含了 EXT-X-I-FRAME-STREAM-INF 标签同样的属性列表选项,除了 FRAME-RATEAUDIOSUBTITLESCLOSED-CAPTIONS。除此之外,其他的属性还有:

      • URI:该属性值由双引号包裹的字符串,指示了 I-frame 媒体播放列表文件的路径,该媒体播放列表文件必须包含 EXT-X-I-FRAMES-ONLY 标签。
    • EXT-X-SESSION-DATA:该标签允许主播放列表携带任意 session 数据。
      该标签为可选参数。
      其格式为:

      #EXT-X-SESSION-DATA:<attribute-list>
      

      其中,其参数属性列表值如下可选项:

      • DATA-ID:由双引号包裹的字符串,代表一个特定的数据值。
        该属性应当使用反向 DNS 进行命名,如"com.example.movie.title"。然而,由于没有中央注册机构,所以可能出现冲突情况。
        该属性为必选参数。
      • VALUE:该属性值为一个双引号包裹的字符串,其包含 DATA-ID 指定的值。
        如果设置了 LANGUAGE,则 VALUE 应当包含一个用该语言书写的可读字符串。
      • URI:由双引号包裹的 URI 字符串。由该 URI 指示的资源必选使用 JSON 格式,否则,客户端可能会解析失败。
      • LANGUAGE:由双引号包裹的,包含一个语言标签的字符串。指示了 VALUE 所使用的语言。
  • EXT-X-SESSION-KEY:该标签允许主播放列表(Master Playlist)指定媒体播放列表(Meida Playlist)的加密密钥。这使得客户端可以预先加载这些密钥,而无需从媒体播放列表中获取。
    该标签为可选参数。
    其格式为:

    #EXT-X-SESSION-KEY:<attribute-list>
    

    其属性列表与 EXT-X-KEY 相同,除了 METHOD 属性的值必须不为NONE

  • 播放列表类型标签:以下标签可同时设置于主播放列表(Master Playlist)和媒体播放列表(Media Playlist)中。
    但是对于在主播放列表中设置了的标签,不应当再次设置在主播放列表指向的媒体播放列表中。
    同时出现在两者播放列表的相同标签必须具备相同的值。这些标签在播放列表中不能出现多次(只能使用一次)。具体标签如下所示:

    • EXT-X-INDEPENDENT-SEGMENTS:该标签表明对于一个媒体片段中的所有媒体样本均可独立进行解码,而无须依赖其他媒体片段信息。
      该标签对列表内所有媒体片段均有效。
      其格式为:

      #EXT-X-INDEPENDENT-SEGMENTS
      

      如果该标签出现在主播放列表中,则其对所有媒体播放列表的所有媒体片段都生效。

    • EXT-X-START:该标签表示播放列表播放起始位置。
      默认情况下,客户端开启一个播放会话时,应当使用该标签指定的位置进行播放。
      该标签为可选标签。
      其格式为:

      #EXT-X-START:<attribute-list>
      

      其参数属性列表的取值范围如下:

      • TIME-OFFSET:该属性值为一个带符号十进制浮点数(单位:秒)。
        一个正数表示以播放列表起始位置开始的时间偏移量。
        一个负数表示播放列表上一个媒体片段最后位置往前的时间偏移量。
        该属性的绝对值应当不超过播放列表的时长。如果超过,则表示到达文件结尾(数值为正数),或者达到文件起始(数值为负数)。
        如果播放列表不包含 EXT-X-ENDLIST 标签,那么 TIME-OFFSET 属性值不应当在播放文件末尾三个切片时长之内。
      • PRECISE:该值为一个可枚举字符串。
        有效的取值为YESNO
        如果值为YES,客户端应当播放包含 TIME-OFFSET 的媒体片段,但不要渲染该块内优先于 TIME-OFFSET 的样本块。
        如果值为NO,客户端应当尝试渲染在媒体片段内的所有样本块。
        该属性为可选参数,未指定则认为NO

到此,m3u8 相关的标签我们已经完全介绍完毕。

下面我们再简单介绍下资源文件的获取具体操作。

上文提到,m3u8 文件要么是媒体播放列表(Meida Playlist),要么是主播放列表(Master Playlist)。但无论是哪种列表,其有效内容均由两部分结构组成:

  • #EXT 开头的为标签信息,作为对媒体资源的进一步描述;
  • 剩余的为资源信息,要么是片段资源(Media Playlist)路径,要么是 m3u8 资源(Master Playlist)路径;

我们先简单介绍下 m3u8 文件媒体片段的表示方法:

  • m3u8 文件中,媒体片段可以采用全路径表示。如下所示:

#EXTINF:10.0,
http://example.com/movie1/fileSequenceA.ts

这样,获取资源片段的路径就是 m3u8 文件内指定的路径,即:http://example.com/movie1/fileSequenceA.ts

  • m3u8 文件中,媒体片段还可以使用相对路径表示。如下所示:

#EXTINF:10.0,
fileSequenceA.ts

这表示片段文件的路径是相对于 m3u8 文件路径的,即假设当前 m3u8 的路径为:https://127.0.0.1/hls/m3u8,那么,片段文件 fileSequenceA.ts 的路径即为:https://127.0.0.1/hls/fileSequenceA.ts

尽管可以在 m3u8 文件中使用绝对路径指定媒体片段资源路径,但是更好的选择是使用相对路径。相对路径相较于绝对路径更轻便,同时是相对于 m3u8 文件的 URL。相比之下,绝对路径增加了 m3u8 文件内容(更多字符),增大了文件内容,同时也增大了网络传输量。

其余一些注意事项

  • 有两种请求 m3u8 播放列表的方法:一是通过 m3u8 的 URI 进行请求,则该文件必须以 .m3u8 或 .m3u 结尾;
    二是通过 HTTP 进行请求,则请求头Content-Type必须设置为 application/vnd.apple.mpegurl或者audio/mpegurl
  • 空行和注释行在解析时都忽略。
  • 媒体播放列表(Media Playlist)的流资源总时长就是各切片资源的时长之和。
  • 每个切片的码率(bit rate)就是切片的大小除以它对应的时长(EXTINF 指定的时长)。
  • 一个标签的属性列表的同一个属性AttributeName只能出现一次。
  • EXT-X-TARGETDURATION 指定的时长绝对不能进行更改。通常该值指定的时长为 10 秒。
  • 对于指定了 EXT-X-I-FRAMES-ONLY 且 第一个媒体片段(或者第一个尾随 EXT-X-DISCONTINUITY 的片段)其资源没有立即携带媒体初始化块的切片,应当增加使用标签 EXT-X-MAP 指定媒体初始化块获取途径。
  • 使用 EXT-X-MAP 标签内含标签 EXT-X-I-FRAMES-ONLY 要求的兼容版本号 EXT-X-VERSION 要大于等于 5;只使用 EXT-X-MAP 要求的兼容版本号要大于等于 6。
  • 由标签 EXT-X-MAP 声明的媒体初始化块可使用 AES-128 方法进行加密,此时,作用于 EXT-X-MAP 标签的 EXT-X-KEY 标签必须设置 IV 属性。
  • 带有属性 END-ON-NEXT=YES 的标签 EXT-X-DATERANGE 必须携带 CLASS 属性,但不能携带 DURATIONEND-DATE 属性。其余带有相同 CLASS 的标签 EXT-X-DATERANGE 不能指定重叠的日期范围。
  • 日期范围如果未指明 DURATIONEND_DATE,END-ON-NEXT=YES 属性时,则其时长(duration)未知,即使其设置了 PLANNED-DURATION 属性。
  • 如果播放列表设置了 EXT-X-DATERANGE 标签,则必须同时设置 EXT-X-PROGRAM-DATE-TIME 标签。
  • 如果播放列表设置了拥有相同 ID 属性值的两个 EXT-X-DATERANGE 标签,则对于相同的属性名,在这两个 EXT-X-DATERANGE 中对应的值必须一致。
  • 如果 EXT-X-DATERANGE 同时设置了 DURATIONEND-DATE 属性,则 END-DATE 属性值必须等于 START-DATE 属性值加上 DURATION 属性值。
  • EXT-X-MEDIA-SEQUENCE 标签必须出现在播放列表第一个切片之前。
  • EXT-X-DISCONTINUITY-DEQUENCE 标签必须出现在播放列表第一个切片之前。
  • EXT-X-DISCONTINUITY-DEQUENCE 标签必须出现在任意 EXT-X-DISCONTINUITY 标签之前。
  • m3u8 文件如果没有设置 EXT-X-PLAYLIST-TYPE 标签,那么播放列表可以随时进行更改。比如,可以更新或删除播放列表中的媒体片段。
  • 每个 EXT-X-I-FRAME-STREAM-INF 标签必须包含一个 BANDWIDTHURI 属性。
  • 每个 EXT-X-SESSION-DATA 标签都必须包含一个 VALUEURI 属性,但不能同时包含两者。
  • 一个播放列表可以包含多个携带相同 DATA-ID 属性的 EXT-X-SESSION-DATA 标签。但是不能包含多个携带相同 DATA-ID 和相同 LANGUAGE 属性的 EXT-X-SESSION-DATA 标签。
  • 如果设置了 EXT-X-SESSION-KEY,那么其 METHODKEYFORMATKEYFORMATVERSIONS 属性值必须与任意相同 URIEXT-X-KEY 标签值相同。
  • 如果多份备用流或者多语言流使用相同的加密密钥和格式,则应当设置 EXT-X-SESSION-KEY 标签。
  • 主播放列表必须不能设置多个具有相同 METHODURIIVKEYFORMATKEYFORMATVERSIONS 属性值得 EXT-X-SESSION-KEY 标签。

附录

作者:Whyn
链接:https://www.jianshu.com/p/e97f6555a070
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

hugegraph图数据库索引详解

前言

在《技术文章之二 hugegraph图数据库概念详解》中我们介绍过IndexLabel,它是索引的元数据,用来描述对顶点/边的属性建立的索引。本文将对hugegraph中的索引做一个较为深入的介绍,并给出每一种索引的适用场景和使用方法的样例。

hugegraph的查询系统是以索引为基础建立的,如果没有索引,hugegraph只能做一些非常简单的查询,比如不带任何条件的查询:g.V()g.E();或者是能够优化为Id查询的条件查询,比如顶点Id策略为PRIMARY_KEY时的主属性(主索引)查询,查询种类十分有限。一旦创建了足够的索引,hugegraph就可以做各种各样复杂的查询了。

hugegraph包含三种索引,分别是:二级索引、范围索引、全文索引,二级索引又可分为单列索引和组合索引,还有一种能联合多种索引的联合索引。给了这么多名次,大家肯定有些头大,不用担心,大家不用太在乎这些索引的名称,暂时知道有多种索引即可,后面阅读完全文,了解这些索引的适用场景和用法即可。

二级索引

二级索引是最常见的索引类型,当用户对某个属性的查询总是“相等”判断时可以使用二级索引二级索引适合建立在大多数普通的属性上,比如:“姓名”、“职业”、“品牌”等容易准确描述的离散值。注意这里的两个措辞:“容易准确描述”和“离散值”并非专业描述,而是用于区别后面的范围索引和全文索引的。

  • “容易准确描述”指用户通常能完整的给出要查找的属性的值,而不是部分的,模糊不清的,比如某人的地址,一大串经常记不住,只能给出“北京市”、“某某小区”这样的部分信息;
  • “离散值”指的是属性的值是一个一个可以罗列的,就像一个一个整数值一样,而不是可以无限细分的浮点数,比如权重、得分这样值。

注意:上面的两个措辞只是一种一般性的描述,可能有些场景下,大的地址已经被限定在“北京市海淀区”里,那地址值就是一些简短的词也不足为奇了,此时也变成了“容易准确描述”的;“离散值”在不同场景下也有不同的定义,所以大家要根据具体的业务场景区分,切勿认死理。

hugegraph中二级索引的逻辑存储结构如下:

field_values | index_label_id | element_ids
  • field_values: 属性的值,可以是单个属性,也可以是多个属性拼接而成
  • index_label_id: 索引标签的Id
  • element_ids: 顶点或边的Id

各种后端具体的“表结构”都按照该逻辑结构设计。

二级索引允许在多个属性上建立索引,在单个属性上建立的索引称为单列索引,在多个属性上建立的索引称之为组合索引,下面分别介绍。

单列索引

先创建一个单列索引标签(元数据):

  1. // 创建索引类型:”personByName”,可以按“name”属性的值快速查询对应的“person”顶点
  2. schema.indexLabel(“personByName”)
  3. .onV(“person”)
  4. .by(“name”)
  5. .secondary()
  6. .create();

再插入几个顶点,有两个顶点的“name”相同:

  1. graph.addVertex(T.label, “person”, T.id, “p1”, “name”, “marko”);
  2. graph.addVertex(T.label, “person”, T.id, “p2”, “name”, “marko”);
  3. graph.addVertex(T.label, “person”, T.id, “p3”, “name”, “josh”);

我们以cassandra后端为例(下同)展示二级索引表的存储结构:

field_values | index_label_id | element_ids
————-±—————±————
marko | 1 | Sp1
marko | 1 | Sp2
josh | 1 | Sp3

表结构中,field_values作为primary key列,index_label_idelement_ids都作为cluster key,这样才能依次存储相同属性的element_id

当用户的查询语句中包含“name”=“marko”这样的条件时,就会命中前面两条记录得到两个element_id,然后再用这两个element_id去顶点表做Id查询得到顶点实体。

在真正的查询流程中,肯定是会先找到index_label_id的,然后用field_valuesindex_label_id一起定位,但是找index_label_id不是这里的重点,一笔带过。

组合索引

先创建一个组合索引标签:

  1. // 创建索引类型:”personByNameAndAge”,可以按“name”和”age”属性的值快速查询对应的“person”顶点
  2. schema.indexLabel(“personByNameAndAge”)
  3. .onV(“person”)
  4. .by(“name”, “age”)
  5. .secondary()
  6. .ifNotExist()
  7. .create();

再插入几个顶点:

  1. graph.addVertex(T.label, “person”, T.id, “p1”, “name”, “marko”, “age”, 29);
  2. graph.addVertex(T.label, “person”, T.id, “p2”, “name”, “vadas”, “age”, 29);
  3. graph.addVertex(T.label, “person”, T.id, “p3”, “name”, “josh”, “age”, 32);

然后再看存储结构:

field_values | index_label_id | element_ids
————-±—————±————
marko | 1 | Sp1
marko | 1 | Sp2
marko!29 | 1 | Sp1
josh!32 | 1 | Sp3
josh | 1 | Sp3
marko!27 | 1 | Sp2

这时我们发现:我只插入了3个顶点,但是一共有6条索引记录,这是为什么呢?

因为组合索引存储时会把多个属性值按顺序拼接在一起作为新的属性值,并且会将该属性值的所有前缀也都存储一份。以第一个顶点为例,它的“name”为“marko”,“age”为29,拼接在一起的新属性值是“marko!29”,会作为一条索引记录存储;然后它的前缀是“marko”,也会存储一份。

以此类推,如果这里创建的属性标签是建立在三个属性上的:“name”、“age”、“city”,假设“city”的值为“Beijing”,那一个顶点会对应三条索引,其field_values分别是“marko”、“marko!29”和“marko!29!Beijing”。

在像rocksdb和hbase这样的后端中,由于rowkey支持前缀匹配,则不会存储这么多的索引记录。

这样的存储结构只提供了前缀匹配的能力,所以当用户查询条件只包含“name”=“marko”时,可以命中前面两条记录;当查询条件包含“name”=“marko”和“age”=29时,可以命中第三条记录;但当查询条件中只包含“age”=29时,则无法命中任何一条记录。所以:创建索引标签时,组合索引中的属性顺序是很重要的。

范围索引

当用户的查询语义是:某属性值大于、小于、大于等于、小于等于、等于某个界限,或者属性值属于某个区间时,适合使用范围索引。比如:“年龄”、“价格”、“得分”等取值比较连续的属性。

要注意的是:该属性必须可以做比较,在Java中也就是实现了Comparable接口,所以Number类型的属性自不必说,像Date这样的属性也是可以创建范围索引的。

hugegraph中二级索引的逻辑存储结构如下:

index_label_id | field_values | element_ids
  • index_label_id: 索引标签的Id
  • field_values: 属性的值,可以是单个属性,也可以是多个属性拼接而成
  • element_ids: 顶点或边的Id

各种后端具体的“表结构”都按照该逻辑结构设计,在cassandra后端中,index_label_id作为primary key列,field_valueselement_ids都作为cluster key。其实相比于二级索引,范围索引表只是将field_valuesindex_label_id调换了一下顺序,这样做是因为在cassandra中,cluster key列是按顺序存储的,而primary key列是哈希存储的。

下面给出一个例子

先创建一个范围索引标签:

  1. // 创建索引类型:”personByAge”,可以按“age”属性的范围快速查询对应的“person”顶点
  2. schema.indexLabel(“personByAge”)
  3. .onV(“person”)
  4. .by(“age”)
  5. .range()
  6. .create();

再插入几个顶点:

  1. graph.addVertex(T.label, “person”, T.id, “p1”, “age”, 29);
  2. graph.addVertex(T.label, “person”, T.id, “p2”, “age”, 27);
  3. graph.addVertex(T.label, “person”, T.id, “p3”, “age”, 32);

然后再看存储结构:

index_label_id | field_values | element_ids
—————±————-±————
1 | 27 | Sp2
1 | 29 | Sp1
1 | 32 | Sp3

可以看到,field_values列的值是按顺序存储的,当用户的查询条件中为“age”>=27 && “age”<30时,可以定位到第一条到第三条记录之间(左闭右开);当查询条件为“age”<29时,可以定位到第一条记录。

全文索引

全文索引用于全文检索,或者说模糊检索,就是用于检索那些“不容易准确描述”的属性值的。比如:“地址”、“描述”等内容很长的属性,全文索引只能对文本类型的属性创建。

hugegraph会使用第三方的分词器将文本切分成多个短词,每个短词分别存储为一条索引记录。查询的时候也会将条件中的词语切分为多个短词,然后每个短词去索引表中查询,最后把每个短词的查询结果取并集就得到了最终的element_ids。全文索引相比于二级索引,只是多个一个分词和将结果合并的步骤,其表结构与二级索引完全相同,这里便不再赘述。

下面给出一个例子

先创建一个全文索引标签:

  1. // 创建索引类型:”personByAddress”,可以根据“address”属性的部分快速查询对应的“person”顶点
  2. schema.indexLabel(“personByAddress”)
  3. .onV(“person”)
  4. .by(“address”)
  5. .search()
  6. .ifNotExist()
  7. .create();

插入几个顶点:

  1. graph.addVertex(T.label, “person”, T.id, “p1”, “address”, “Beijing Haidian Xierqi”);
  2. graph.addVertex(T.label, “person”, T.id, “p2”, “address”, “Shanghai Xuhui”);
  3. graph.addVertex(T.label, “person”, T.id, “p3”, “address”, “Beijing Chaoyang”);

cassandra中的存储结构:

field_values | index_label_id | element_ids
————-±—————±————
shanghai | 1 | Sp2
xuhui | 1 | Sp2
chaoyang | 1 | Sp3
xierqi | 1 | Sp1
haidian | 1 | Sp1
beijing | 1 | Sp1
beijing | 1 | Sp3

可以看到,每个“person”的“address”值都被拆分为了多个部分,比如:“Beijing Haidian Xierqi”被拆分成了“beijing”、“haidian”和“xierqi”,每个单词的首字母还被转换成了小写,但是不影响查询,因为查询的时候也会做同样的转换。

当用户这样查询时:g.V().hasLabel("person").has("address", Text.contains("Beijing"));,分词器会把“Beijing”拆分(转换)为“beijing”,然后命中两条记录;当使用g.V().hasLabel("person").has("address", Text.contains("Beijing Shanghai"));查询时,分词器会把“Beijing Haidian”拆分为“beijing”和“shanghai”,其中“beijing”能命中两条记录“Sp1”和“Sp3”,“shanghai”能命中“Sp2”,然后取并集得到“Sp1”、“Sp2”和“Sp3”。

联合索引

上面提到的索引和查询都是独立工作的,就是说一个查询只会涉及到一个索引类型(IndexLabel),比如示例中:根据“name”=“xxx”查“person”时只会涉及到“personByName”这一个二级索引;根据“age”>xxx查“person”时只会涉及到“personByAge”这一个范围索引;即使是在传入多个条件的二级索引(组合索引)和通过分词产生多条件的全文索引中,也只会涉及到各自的单个索引,即“personByNameAndAge”和“personByAddress”。

相信看到这里,大家会有两个疑惑:

  • 单独创建多个单列索引和创建一个组合索引有什么区别?比如创建两个单列索引:“personByName”、“personByAge”,和创建一个组合索引:“personByNameAndAge”会对查询产生怎样的影响。
  • 似乎只有组合索引支持多个条件的查询,而且组合索引只提供了前缀匹配的能力,那岂不是要将所有的属性排列组合都创建为索引,才能支持任意属性的查询?

其实这两个问题是有关联的,可以合并为一个问题。单独创建多个单列索引和创建一个组合索引的区别在于:单独创建多个单列索引既支持单个属性的查询,也支持多个属性的组合查询,而组合索引只支持前缀属性的查询。

就以“personByName”、“personByAge”和“personByNameAndAge”为例,“personByName”、“personByAge”支持通过“name”查,支持通过“age”查,也支持通过“name”和“age”一起查;但是“personByNameAndAge”只支持通过“name”查和通过“name”和“age”一起查,不支持作为后缀的“age”查。

那多个单列索引是如何支持多个属性的组合查询的呢?很简单,依次查询然后将结果取交集,如果交集为空,则停止查询(这里所说的查询是指查询索引表),这种索引模式称为联合索引。

那又有人问了,既然组合索引能做的联合索引都能做,组合索引的意义何在呢?其实稍微一下就能知道:组合索引提供了前缀匹配的能力,在多属性查询时,一次查询就能命中,而联合查询则需要做最多N(属性个数)次查询,明显组合索引效率更高。所以,大家要根据具体的业务场景创建好索引,如果某些属性经常要组合在一起查询,最好把它创建为组合索引。

下面给出一个例子

先创建几个索引标签:

  1. // 创建组合索引:”personByNameAndAge”,可以根据“name”、“name”+“age”快速查询对应的“person”顶点
  2. schema.indexLabel(“personByNameAndAge”)
  3. .onV(“person”)
  4. .by(“name”, “age”)
  5. .secondary()
  6. .create();
  7. // 创建范围索引:”personByAge”,可以根据“age”属性的范围查询对应的“person”顶点
  8. schema.indexLabel(“personByAge”)
  9. .onV(“person”)
  10. .by(“age”)
  11. .range()
  12. .create();
  13. // 创建全文索引:”personByAddress”,可以根据“address”属性的部分快速查询对应的“person”顶点
  14. schema.indexLabel(“personByAddress”)
  15. .onV(“person”)
  16. .by(“address”)
  17. .search()
  18. .ifNotExist()
  19. .create();

插入几个顶点:

  1. graph.addVertex(T.label, “person”, T.id, “p1”, “name”, “marko”, “age”, 29, “address”, “Beijing Haidian Xierqi”);
  2. graph.addVertex(T.label, “person”, T.id, “p2”, “name”, “marko”, “age”, 27, “address”, “Shanghai Xuhui”);
  3. graph.addVertex(T.label, “person”, T.id, “p3”, “name”, “josh”, “age”, 32, “address”, “Beijing Chaoyang”);

存储结构就不看了,我们直接来做查询:

  1. // 通过“personByNameAndAge”做前缀匹配,命中“p1”和“p2”
  2. g.V().hasLabel(“person”).has(“name”, “marko”).toList();
  3. // 通过“personByNameAndAge”做前缀匹配(全匹配),命中“p1”
  4. g.V().hasLabel(“person”).has(“name”, “marko”).has(“age”, 29).toList();
  5. // 通过“personByNameAndAge”做前缀匹配(全匹配),命中“p1” ,从这里看出,查询时属性的顺序无影响
  6. g.V().hasLabel(“person”).has(“age”, 29).has(“name”, “marko”).toList();
  7. // 通过“personByAge”做范围查询,命中“p1”和“p2”
  8. g.V().hasLabel(“person”).has(“age”, P.between(27, 30)).toList();
  9. // 通过“personByAddress”做全文检索,命中“p1”和“p3”
  10. g.V().hasLabel(“person”).has(“address”, Text.contains(“Beijing”)).toList();
  11. // 通过“personByNameAndAge”和“personByAddress”做联合索引查询,命中“p1”
  12. g.V().hasLabel(“person”).has(“name”, “marko”).has(“address”, Text.contains(“Beijing”)).toList();
  13. // 通过“personByAge”和“personByAddress”做联合索引查询,命中“p1”
  14. g.V().hasLabel(“person”).has(“age”, P.between(27, 30)).has(“address”, Text.contains(“Beijing”)).toList();

 

 

授予每个自然月内发布4篇或4篇以上原创或翻译IT博文的用户。不积跬步无以至千里,不积小流无以成江海,程序人生的精彩需要坚持不懈地积累!

解决git alias权限问题

git aliases causing “Permission denied” error

 

The correct answer to this was actually different. Before git runs the aliases it checks the $PATH. In case the directory does not exist, or lacks permissions, git produces the "fatal: cannot exec 'git-co': Permission denied". It does not ever comes to check the aliases so git foobar will produce the same error.

Good people from the git mailing list also reminded me of an strace tool, that can help finding the entry that is returning EACCES, as in: strace -f -e execve git foobar

 

简单来说就行执行 strace -f -e execve git foobar

看看哪个路径在PATH中,并且没有权限,直接去掉就好了。

echo $PATH 可以查看当前的环境变量

PATH=“xxxx”可以进行赋值

禁用代理APP抓包方法

目前在ios/android上很多app选择禁止使用代理,这就让目前主流的fiddler,charles等无法抓包,但是通过流量采集的wireshark还是可以继续抓包的,目前摘抄一个可用的方法,原文地址:

 

参考文档

www.wireshark.org

Mac OS X上使用Wireshark抓包 (抓取手机网络)
使用wireshark以及filddler配合抓去手机端的TCP以及HTTP请求
Wireshark基本介绍和学习TCP三次握手
wireshark使用教程
Mac下使用Wireshark抓iphone手机数据包
Wireshark抓包分析/TCP/Http/Https及代理IP的识别

Fiddler 教程

一、Mac抓iPhone数据包

原理:使用 Mac 抓取 iPhone 数据包可通过共享和代理两种方式:
  • 使用 Mac 的网络共享功能将 Mac 的网络通过 WiFi 共享给 iPhone 连接;
  • 使用代理软件(例如 Charles)在Mac上建立HTTP代理服务器。
1.1、使用 Mac 的网络共享功能将 Mac 的网络通过 ‘WiFi共享’ 给iPhone连接
1.1.1、原理:

手机->运营商->服务器
手机->PC网卡->运营商->服务器

1.1.2、亲手操作:
  • 1、打开 系统偏好设置 -> 共享
  • 2、我设置了 Wi-Fi 为网络来源,共享给 iPhone USB 端口(因为只有一个网卡的原因,不能以 Wi-Fi 端口共享出去!);
  • 3、关闭 iPhone 上一切网络,用数据线连接到 mac USB 接口;
  • 4、神奇的事情发生了,没有WiFi和4G情况下,手机能正常上网;
开启Mac网络共享
开启Mac网络共享
wireshark监控刚刚我们共享网络的端口
wireshark监控刚刚我们共享网络的端口
wireshark抓到了iPhone的数据包(打开自己的APP请求)
wireshark抓到了iPhone的包

1.2、使用代理软件(例如 Charles)在Mac上建立HTTP代理服务器

1.3、Remote Virtual Interface,RVI

1.3.1、RVI介绍

苹果在 iOS 5 中新引入了“远程虚拟接口(Remote Virtual Interface,RVI)”的特性,可以在 Mac 中建立一个虚拟网络接口来作为 iOS 设备的网络栈,这样所有经过 iOS 设备的流量都会经过此虚拟接口。此虚拟接口只是监听 iOS 设备本身的协议栈(但并没有将网络流量中转到 Mac 本身的网络连接上),所有网络连接都是 iOS 设备本身的,与 Mac 电脑本身联不联网或者联网类型无关。iOS设备本身可以为任意网络类型(WiFi/xG),这样在 Mac 电脑上使用任意抓包工具(tcpdump、Wireshark、CPA)抓取 RVI 接口上的数据包就实现了对 iPhone 的抓包。

1.3.2、终端查看RVI
终端查看RVI
1.4.3、获取UUID
  • 首先,通过 USB 数据线将 iPhone 连接到安装了 Mac 上(老旧的设备可能不行),抓包过程中必须保持连接;
  • 然后,通过 iTunes->Summary 或者 Xcode->Organizer->Devices 获取 iPhone 的 UDID。
1.3.4、终端开启RVI

打开终端,使用“rvictl -s UUID”命令创建 RVI 接口,使用 iPhone 的 UDID 作为参数。

根据UUID开启RVI,并查看list
根据UUID开启RVI
wireshark监控RVI端口
wireshark监控RVI端口
wireshark抓到了iPhone的数据包(某车帝APP请求)
wireshark抓到了iPhone的数据包

二、牛刀小试 – 尝试抓某车帝的数据包

监控iPhone USB端口,打开某车帝APP某个页面。

设置快捷查找表达式查找HTTP请求
某接口
使用Firefox 的 RESTClient进行HTTP模拟请求
模拟HTTP请求

作者:lionsom_lin
链接:https://www.jianshu.com/p/82bcdb1decf7
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

Golang程序员开发效率神器汇总

一. 开发工具

1)sql2go
用于将 sql 语句转换为 golang 的 struct. 使用 ddl 语句即可。
例如对于创建表的语句: show create table xxx. 将输出的语句,直接粘贴进去就行。
http://stming.cn/tool/sql2go.html

2)toml2go
用于将编码后的 toml 文本转换问 golang 的 struct.
https://xuri.me/toml-to-go/

3)curl2go
用来将 curl 命令转化为具体的 golang 代码.
https://mholt.github.io/curl-to-go/

4)json2go
用于将 json 文本转换为 struct.
https://mholt.github.io/json-to-go/

5)mysql 转 ES 工具
http://www.ischoolbar.com/EsParser/

6)golang
模拟模板的工具,在支持泛型之前,可以考虑使用。
https://github.com/cheekybits/genny

7)查看某一个库的依赖情况,类似于 go list 功能
https://github.com/KyleBanks/depth

8)一个好用的文件压缩和解压工具,集成了 zip,tar 等多种功能,主要还有跨平台。
https://github.com/mholt/archiver

9)go 内置命令
go list 可以查看某一个包的依赖关系.
go vet 可以检查代码不符合 golang 规范的地方。

10)热编译工具
https://github.com/silenceper/gowatch

11)revive
golang 代码质量检测工具
https://github.com/mgechev/revive

12)Go Callvis
golang 的代码调用链图工具
https://github.com/TrueFurby/go-callvis

13)Realize
开发流程改进工具
https://github.com/oxequa/realize

14)Gotests
自动生成测试用例工具
https://github.com/cweill/gotests

二.调试工具

1)perf
代理工具,支持内存,cpu,堆栈查看,并支持火焰图.
perf 工具和 go-torch 工具,快捷定位程序问题.
https://github.com/uber-archive/go-torch
https://github.com/google/gops

2)dlv 远程调试
基于 goland+dlv 可以实现远程调式的能力.
https://github.com/go-delve/delve
提供了对 golang 原生的支持,相比 gdb 调试,简单太多。

3)网络代理工具
goproxy 代理,支持多种协议,支持 ssh 穿透和 kcp 协议.
https://github.com/snail007/goproxy

4)抓包工具
go-sniffer 工具,可扩展的抓包工具,可以开发自定义协议的工具包. 现在只支持了 http,mysql,redis,mongodb.
基于这个工具,我们开发了 qapp 协议的抓包。
https://github.com/40t/go-sniffer

5)反向代理工具,快捷开放内网端口供外部使用。
ngrok 可以让内网服务外部调用
https://ngrok.com/
https://github.com/inconshreveable/ngrok

6)配置化生成证书
从根证书,到业务侧证书一键生成.
https://github.com/cloudflare/cfssl

7)免费的证书获取工具
基于 acme 协议,从 letsencrypt 生成免费的证书,有效期 1 年,可自动续期。
https://github.com/Neilpang/acme.sh

8)开发环境管理工具,单机搭建可移植工具的利器。支持多种虚拟机后端。
vagrant常被拿来同 docker 相比,值得拥有。
https://github.com/hashicorp/vagrant

9)轻量级容器调度工具
nomad 可以非常方便的管理容器和传统应用,相比 k8s 来说,简单不要太多.
https://github.com/hashicorp/nomad

10)敏感信息和密钥管理工具
https://github.com/hashicorp/vault

11)高度可配置化的 http 转发工具,基于 etcd 配置。
https://github.com/gojek/weaver

12)进程监控工具 supervisor
https://www.jianshu.com/p/39b476e808d8

13)基于procFile进程管理工具. 相比 supervisor 更加简单。
https://github.com/ddollar/foreman

14)基于 http,https,websocket 的调试代理工具,配置功能丰富。在线教育的 nohost web 调试工具,基于此开发.
https://github.com/avwo/whistle

15)分布式调度工具
https://github.com/shunfei/cronsun/blob/master/README_ZH.md
https://github.com/ouqiang/gocron

16)自动化运维平台 Gaia
https://github.com/gaia-pipeline/gaia

三. 网络工具

四. 常用网站

go 百科全书: https://awesome-go.com/

json 解析: https://www.json.cn/

出口 IP: https://ipinfo.io/

redis 命令: http://doc.redisfans.com/

ES 命令首页:

https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html

UrlEncode: http://tool.chinaz.com/Tools/urlencode.aspx

Base64: https://tool.oschina.net/encrypt?type=3

Guid: https://www.guidgen.com/

常用工具: http://www.ofmonkey.com/

五. golang 常用库

日志
https://github.com/Sirupsen/logrus
https://github.com/uber-go/zap

配置
兼容 json,toml,yaml,hcl 等格式的日志库.
https://github.com/spf13/viper

存储
mysql: https://github.com/go-xorm/xorm
es: https://github.com/elastic/elasticsearch
redis: https://github.com/gomodule/redigo
mongo: https://github.com/mongodb/mongo-go-driver
kafka: https://github.com/Shopify/sarama

数据结构
https://github.com/emirpasic/gods

命令行
https://github.com/spf13/cobra

框架
https://github.com/grpc/grpc-go
https://github.com/gin-gonic/gin

并发
https://github.com/Jeffail/tunny
https://github.com/benmanns/goworker
现在我们框架在用的,虽然 star 不多,但是确实好用,当然还可以更好用.
https://github.com/rafaeldias/async

工具
定义了实用的判定类,以及针对结构体的校验逻辑,避免业务侧写复杂的代码.
https://github.com/asaskevich/govalidator
https://github.com/bytedance/go-tagexpr

protobuf 文件动态解析的接口,可以实现反射相关的能力。
https://github.com/jhump/protoreflect

表达式引擎工具
https://github.com/Knetic/govaluate
https://github.com/google/cel-go

字符串处理
https://github.com/huandu/xstrings

ratelimit 工具
https://github.com/uber-go/ratelimit
https://blog.csdn.net/chenchongg/article/details/85342086
https://github.com/juju/ratelimit

golang 熔断的库
熔断除了考虑频率限制,还要考虑 qps,出错率等其他东西.
https://github.com/afex/hystrix-go
https://github.com/sony/gobreaker

表格
https://github.com/chenjiandongx/go-echarts

tail 工具库
https://github.com/hpcloud/taglshi

作者:Gundy
链接:https://juejin.im/post/5de082a95188256f9a25384f
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。