Mkless

Mkless 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 www.wjk123.com 编辑
编辑

设计模式、架构设计、编程思想、数据结构

个人动态

Mkless 赞了回答 · 11月2日

c/c++ 为什么函数中定义变量不会出现像 struct 中因内存对齐而导致的空间浪费

非常有意思的问题,网上找到两个回答
第一个说函数本地变量也应该内存对齐:https://bytes.com/topic/c/ans...

第二个说本地变量对齐的代价是非常大的(在3.6.2的开头):https://www.cl.cam.ac.uk/tech...

我自己写了c语言程序

int main(void){
    struct A {
        int a;
        char b;
        int c;
    };
    struct A A1 = {4,5,6};
    int a = 1;
    char b = 2;
    int c = 3;
    return 0;
}

然后用gcc编译为汇编后的关键部分为

    movl    $4, -12(%rbp)
    movb    $5, -8(%rbp)
    movl    $6, -4(%rbp)
    movl    $1, -20(%rbp)
    movb    $2, -21(%rbp)
    movl    $3, -16(%rbp)
    movl    $0, %eax
    popq    %rbp

可以看到struct确实有内存对齐,但是内部变量之间就没有了。个人猜测函数内部变量如果能做到内存对齐那效率自然是最高的。但是这个的难度很大,因为函数内部的变量有可能不是连续的,而struct内部的东西天然就是连续的

关注 1 回答 1

Mkless 关注了问题 · 11月2日

opencv编译出错,急!

image.png

关注 2 回答 0

Mkless 赞了回答 · 11月2日

go控制函数执行时间

可以用context.WithTimeout或者context.WithDeadline

   p := context.TODO()
   c, _ := context.WithTimeout(p, 30*time.Second)
   wg := &sync.WaitGroup{}
   wg.Add(1)
   start := time.Now()
   go func(ctx context.Context) {
      defer wg.Done()
      for{
          select {
          case <-c.Done():
             return
          default:
             doSomething() 
          }
      }
   }(c)
   wg.Wait()
   fmt.Println(time.Since(start))

关注 4 回答 3

Mkless 赞了文章 · 10月17日

MySQL 高频面试题,都在这了

前言

本文主要受众为开发人员,所以不涉及到MySQL的服务部署等操作,且内容较多,大家准备好耐心和瓜子矿泉水.

前一阵系统的学习了一下MySQL,也有一些实际操作经验,偶然看到一篇和MySQL相关的面试文章,发现其中的一些问题自己也回答不好,虽然知识点大部分都知道,但是无法将知识串联起来.

因此决定搞一个MySQL灵魂100问,试着用回答问题的方式,让自己对知识点的理解更加深入一点.

此文不会事无巨细的从select的用法开始讲解mysql,主要针对的是开发人员需要知道的一些MySQL的知识点,主要包括索引,事务,优化等方面,以在面试中高频的问句形式给出答案.

1. 什么是索引?

索引是一种数据结构,可以帮助我们快速的进行数据的查找.

2. 索引是个什么样的数据结构呢?

索引的数据结构和具体存储引擎的实现有关, 在MySQL中使用较多的索引有Hash索引,B+树索引等,而我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引.

3. Hash索引和B+树所有有什么区别或者说优劣呢?

首先要知道Hash索引和B+树索引的底层实现原理:

hash索引底层就是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据.B+树底层实现是多路平衡查找树.对于每一次的查询都是从根节点出发,查找到叶子节点方可以获得所查键值,然后根据查询判断是否需要回表查询数据.

那么可以看出他们有以下的不同:

  • hash索引进行等值查询更快(一般情况下),但是却无法进行范围查询.

因为在hash索引中经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询.而B+树的的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也类似),天然支持范围.

  • hash索引不支持使用索引进行排序,原理同上.
  • hash索引不支持模糊查询以及多列索引的最左前缀匹配.原理也是因为hash函数的不可预测.AAAAAAAAB的索引没有相关性.
  • hash索引任何时候都避免不了回表查询数据,而B+树在符合某些条件(聚簇索引,覆盖索引等)的时候可以只通过索引完成查询.
  • hash索引虽然在等值查询上较快,但是不稳定.性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差.而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子节点,且树的高度较低.

因此,在大多数情况下,直接选择B+树索引可以获得稳定且较好的查询速度.而不需要使用hash索引.

4. 上面提到了B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据,什么是聚簇索引?

在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引. 在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引.如果没有唯一键,则隐式的生成一个键来建立聚簇索引.

当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询.

5. 非聚簇索引一定会回表查询吗?

不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询.

举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行select age from employee where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询.

6. 在建立索引的时候,都有哪些需要考虑的因素呢?

建立索引的时候一般要考虑到字段的使用频率,经常作为条件进行查询的字段比较适合.如果需要建立联合索引的话,还需要考虑联合索引中的顺序.此外也要考虑其他方面,比如防止过多的所有对表造成太大的压力.这些都和实际的表结构以及查询方式有关.

7. 联合索引是什么?为什么需要注意联合索引中的顺序?

MySQL可以使用多个字段同时建立一个索引,叫做联合索引.在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引.

具体原因为:

MySQL使用索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序.

当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格有序,此时可以使用age字段用做索引查找,,,以此类推.因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面.此外可以根据特例的查询或者表结构进行单独的调整.

8. 创建的索引有没有被使用到?或者说怎么才可以知道这条语句运行很慢的原因?

MySQL提供了explain命令来查看语句的执行计划,MySQL在执行某个语句之前,会将该语句过一遍查询优化器,之后会拿到对语句的分析,也就是执行计划,其中包含了许多信息.可以通过其中和索引有关的信息来分析是否命中了索引,例如possilbe_key,key,key_len等字段,分别说明了此语句可能会使用的索引,实际使用的索引以及使用的索引长度.

9. 那么在哪些情况下会发生针对该列创建了索引但是在查询的时候并没有使用呢?

  • 使用不等于查询,
  • 列参与了数学运算或者函数
  • 在字符串like时左边是通配符.类似于'%aaa'.
  • 当mysql分析全表扫描比使用索引快的时候不使用索引.
  • 当使用联合索引,前面一个条件为范围查询,后面的即使符合最左前缀原则,也无法使用索引.

以上情况,MySQL无法使用索引.

事务相关

1. 什么是事务?

理解什么是事务最经典的就是转账的栗子,相信大家也都了解,这里就不再说一边了.

事务是一系列的操作,他们要符合ACID特性.最常见的理解就是:事务中的操作要么全部成功,要么全部失败.但是只是这样还不够的.

2. ACID是什么?可以详细说一下吗?

A=Atomicity

原子性,就是上面说的,要么全部成功,要么全部失败.不可能只执行一部分操作.

C=Consistency

系统(数据库)总是从一个一致性的状态转移到另一个一致性的状态,不会存在中间状态.

I=Isolation

隔离性: 通常来说:一个事务在完全提交之前,对其他事务是不可见的.注意前面的通常来说加了红色,意味着有例外情况.

D=Durability

持久性,一旦事务提交,那么就永远是这样子了,哪怕系统崩溃也不会影响到这个事务的结果.

3. 同时有多个事务在进行会怎么样呢?

多事务的并发进行一般会造成以下几个问题:

  • 脏读: A事务读取到了B事务未提交的内容,而B事务后面进行了回滚.
  • 不可重复读: 当设置A事务只能读取B事务已经提交的部分,会造成在A事务内的两次查询,结果竟然不一样,因为在此期间B事务进行了提交操作.
  • 幻读: A事务读取了一个范围的内容,而同时B事务在此期间插入了一条数据.造成"幻觉".

4. 怎么解决这些问题呢?MySQL的事务隔离级别了解吗?

MySQL的四种隔离级别如下:

  • 未提交读(READ UNCOMMITTED)

这就是上面所说的例外情况了,这个隔离级别下,其他事务可以看到本事务没有提交的部分修改.因此会造成脏读的问题(读取到了其他事务未提交的部分,而之后该事务进行了回滚).

这个级别的性能没有足够大的优势,但是又有很多的问题,因此很少使用.

  • 已提交读(READ COMMITTED)

其他事务只能读取到本事务已经提交的部分.这个隔离级别有 不可重复读的问题,在同一个事务内的两次读取,拿到的结果竟然不一样,因为另外一个事务对数据进行了修改.

  • REPEATABLE READ(可重复读)

可重复读隔离级别解决了上面不可重复读的问题(看名字也知道),但是仍然有一个新问题,就是 幻读,当你读取id> 10 的数据行时,对涉及到的所有行加上了读锁,此时例外一个事务新插入了一条id=11的数据,因为是新插入的,所以不会触发上面的锁的排斥,那么进行本事务进行下一次的查询时会发现有一条id=11的数据,而上次的查询操作并没有获取到,再进行插入就会有主键冲突的问题.

  • SERIALIZABLE(可串行化)

这是最高的隔离级别,可以解决上面提到的所有问题,因为他强制将所以的操作串行执行,这会导致并发性能极速下降,因此也不是很常用.

5. Innodb使用的是哪种隔离级别呢?

InnoDB默认使用的是可重复读隔离级别.

6. 对MySQL的锁了解吗?

当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制.

就像酒店的房间,如果大家随意进出,就会出现多人抢夺同一个房间的情况,而在房间上装上锁,申请到钥匙的人才可以入住并且将房间锁起来,其他人只有等他使用完毕才可以再次使用.

7. MySQL都有哪些锁呢?像上面那样子进行锁定岂不是有点阻碍并发效率了?

从锁的类别上来讲,有共享锁和排他锁.

共享锁: 又叫做读锁. 当用户要进行数据的读取时,对数据加上共享锁.共享锁可以同时加上多个.

排他锁: 又叫做写锁. 当用户要进行数据的写入时,对数据加上排他锁.排他锁只可以加一个,他和其他的排他锁,共享锁都相斥.

用上面的例子来说就是用户的行为有两种,一种是来看房,多个用户一起看房是可以接受的. 一种是真正的入住一晚,在这期间,无论是想入住的还是想看房的都不可以.

锁的粒度取决于具体的存储引擎,InnoDB实现了行级锁,页级锁,表级锁.

他们的加锁开销从大大小,并发能力也是从大到小.

表结构设计

1. 为什么要尽量设定一个主键?

主键是数据库确保数据行在整张表唯一性的保障,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键.设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全.

2. 主键使用自增ID还是UUID?

推荐使用自增ID,不要使用UUID.

因为在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,进而造成插入性能的下降.

总之,在数据量大一些的情况下,用自增主键性能会好一些.

图片来源于《高性能MySQL》: 其中默认后缀为使用自增ID,__uuid为使用UUID为主键的测试,测试了插入100w行和300w行的性能._

关于主键是聚簇索引,如果没有主键,InnoDB会选择一个唯一键来作为聚簇索引,如果没有唯一键,会生成一个隐式的主键.

If you define a PRIMARY KEY on your table, InnoDB uses it as the clustered index.

If you do not define a PRIMARY KEY for your table, MySQL picks the first UNIQUE index that has only NOT NULL columns as the primary key and InnoDB uses it as the clustered index.

3. 字段为什么要求定义为not null?

MySQL官网这样介绍:

NULL columns require additional space in the rowto record whether their values are NULL. For MyISAM tables, each NULL columntakes one bit extra, rounded up to the nearest byte.

null值会占用更多的字节,且会在程序中造成很多与预期不符的情况.

4. 如果要存储用户的密码散列,应该使用什么字段进行存储?

密码散列,盐,用户身份证号等固定长度的字符串应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率.

存储引擎相关

1. MySQL支持哪些存储引擎?

MySQL支持多种存储引擎,比如InnoDB,MyISAM,Memory,Archive等等.在大多数的情况下,直接选择使用InnoDB引擎都是最合适的,InnoDB也是MySQL的默认存储引擎.

  1. InnoDB和MyISAM有什么区别?
  • InnoDB支持事物,而MyISAM不支持事物
  • InnoDB支持行级锁,而MyISAM支持表级锁
  • InnoDB支持MVCC, 而MyISAM不支持
  • InnoDB支持外键,而MyISAM不支持
  • InnoDB不支持全文索引,而MyISAM支持。

零散问题

1. MySQL中的varchar和char有什么区别.

char是一个定长字段,假如申请了char(10)的空间,那么无论实际存储多少内容.该字段都占用10个字符,而varchar是变长的,也就是说申请的只是最大长度,占用的空间为实际字符长度+1,最后一个字符存储使用了多长的空间.

在检索效率上来讲,char > varchar,因此在使用中,如果确定某个字段的值的长度,可以使用char,否则应该尽量使用varchar.例如存储用户MD5加密后的密码,则应该使用char.

2. varchar(10)和int(10)代表什么含义?

varchar的10代表了申请的空间长度,也是可以存储的数据的最大长度,而int的10只是代表了展示的长度,不足10位以0填充.也就是说,int(1)和int(10)所能存储的数字大小以及占用的空间都是相同的,只是在展示时按照长度展示.

3. MySQL的binlog有有几种录入格式?分别有什么区别?

有三种格式,statement,row和mixed.

  • statement模式下,记录单元为语句.即每一个sql造成的影响会记录.由于sql的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制.
  • row级别下,记录单元为每一行的改动,基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大.
  • mixed. 一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row.

此外,新版的MySQL中对row级别也做了一些优化,当表结构发生变化的时候,会记录语句而不是逐行记录.

4. 超大分页怎么处理?

超大的分页一般从两个方向上来解决.

  • 数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于select * from table where age > 20 limit 1000000,10这种查询其实也是有可以优化的余地的. 这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢. 当时我们可以修改为select * from table where id in (select id from table where age > 20 limit 1000000,10).这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以select * from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种,但是核心思想都一样,就是减少load的数据.
  • 从需求的角度减少这种请求....主要是不做类似的需求(直接跳转到几百万页之后的具体某一页.只允许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.

解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.

在阿里巴巴《Java开发手册》中,对超大分页的解决办法是类似于上面提到的第一种.

5. 关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?

在业务系统中,除了使用主键进行的查询,其他的我都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们.

慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?

所以优化也是针对这三个方向来的,

  • 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写.
  • 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引.
  • 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表.

6. 上面提到横向分表和纵向分表,可以分别举一个适合他们的例子吗?

横向分表是按行分表.假设我们有一张用户表,主键是自增ID且同时是用户的ID.数据量较大,有1亿多条,那么此时放在一张表里的查询效果就不太理想.我们可以根据主键ID进行分表,无论是按尾号分,或者按ID的区间分都是可以的. 假设按照尾号0-99分为100个表,那么每张表中的数据就仅有100w.这时的查询效率无疑是可以满足要求的.

纵向分表是按列分表.假设我们现在有一张文章表.包含字段id-摘要-内容.而系统中的展示形式是刷新出一个列表,列表中仅包含标题和摘要,当用户点击某篇文章进入详情时才需要正文内容.此时,如果数据量大,将内容这个很大且不经常使用的列放在一起会拖慢原表的查询速度.我们可以将上面的表分为两张.id-摘要,id-内容.当用户点击详情,那主键再来取一次内容即可.而增加的存储量只是很小的主键字段.代价很小.

当然,分表其实和业务的关联度很高,在分表之前一定要做好调研以及benchmark.不要按照自己的猜想盲目操作.

7. 什么是存储过程?有哪些优缺点?

存储过程是一些预编译的SQL语句。1、更加直白的理解:存储过程可以说是一个记录集,它是由一些T-SQL语句组成的代码块,这些T-SQL语句代码像一个方法一样实现一些功能(对单表或多表的增删改查),然后再给这个代码块取一个名字,在用到这个功能的时候调用他就行了。2、存储过程是一个预编译的代码块,执行效率比较高,一个存储过程替代大量T_SQL语句 ,可以降低网络通信量,提高通信速率,可以一定程度上确保数据安全

但是,在互联网项目中,其实是不太推荐存储过程的,比较出名的就是阿里的《Java开发手册》中禁止使用存储过程,我个人的理解是,在互联网项目中,迭代太快,项目的生命周期也比较短,人员流动相比于传统的项目也更加频繁,在这样的情况下,存储过程的管理确实是没有那么方便,同时,复用性也没有写在服务层那么好.

8. 说一说三个范式

第一范式: 每个列都不可以再拆分.第二范式: 非主键列完全依赖于主键,而不能是依赖于主键的一部分.第三范式: 非主键列只依赖于主键,不依赖于其他非主键.

在设计数据库结构的时候,要尽量遵守三范式,如果不遵守,必须有足够的理由.比如性能. 事实上我们经常会为了性能而妥协数据库的设计.

9. MyBatis中的#和$有什么区别?

乱入了一个奇怪的问题.....我只是想单独记录一下这个问题,因为出现频率太高了.

会将传入的内容当做字符串,而$会直接将传入值拼接在sql语句中.

所以#可以在一定程度上预防sql注入攻击.

作者:呼延十
链接:https://juejin.im/post/5d3513...

关注Java编程鸭微信公众号,后台回复:码农大礼包 可以获取最新整理的技术资料一份。涵盖Java 框架学习、架构师学习等!

查看原文

赞 16 收藏 14 评论 1

Mkless 赞了文章 · 10月17日

你不知道的MySQL,以及MariaDB初体验

MySQL 是一个跨世纪的伟大产品,它最早诞生于 1979 年,距今已经有 40 多年的历史了,而如今比较主流的 Java 语言也只是 1991 年才诞生的,也就是说 MySQL 要比 Java 的诞生还早十几年,不得不惊叹二者强大的生命力。

MySQL 的应用十分广泛,像 Google、Taobao、Facebook、Twitter、Baidu、Tencent 等公司以及绝大多数互联网公司都能见到它的身影,MySQL 也一度是(目前也是)“数据库”的代名词。

MySQL 之所以流行和它开源以及免费的特性是分不开的,因为免费所有很多公司在用,因为被广泛使用所以会有一群狂热的爱好者为它提供技术支持,这样良性的循环将 MySQL 推向了巅峰。

以国内的情况来说,如今的互联网巨头阿里巴巴,当年当发展到一定规模之后就提出了“去 IOE”的口号,所谓的 I 就是 IBM,O 就是 Oracle,而 E 就是 EMC 存储设备,为什么要去 IOE?无非是出于成本的考虑,最后阿里以 MySQL 为基石逐渐去掉了对 Oracle 的依赖,MySQL 在巨头的使用下也在进行着快速的迭代,那么国内的其他小弟看大哥都用 MySQL 了,也就纷纷投入了 MySQL 的怀抱,于是 MySQL 慢慢就成为了各种互联网公司数据库的首选方案。

当然阿里在使用 MySQL 的同时也在研发自己的数据库 OceanBase,并且 OceanBase 在 2019 年的 TPC-C 中也战胜了曾经的老大哥 Oracle 一举成名,让世界看到了中国的实力,这也是我们每个国人的骄傲。

TPC-C 是全球最具公信力的联机交易处理数据库的功能与性能结合的测试标准。通俗来讲 TPC-C 测试是对于商业数据库想要证明自身实力的一个硬性门槛。

image.png

MySQL 被卖

扯远了,说回 MySQL。对于 MySQL 来说 2008 年是一个重要的一年,因为这一年 MySQL 被原来的 Sun 公司以 10 亿美金给收购了,第二年 Sun 公司又以 74 亿美元将自己和所有的产品打包卖给了 Oracle,从此 Oracle DB 和 MySQL 两家并一家都归属于 Oracle 公司了。

这里有一个小插曲,Oracle 在收购了 Sun 公司的第二年,就以一纸诉状将 Google 告上了法庭,原因是 Google 侵权使用了 Java 语言,Oracle 要求赔偿 88 亿美元,这场旷世纪的官司最终在 2018 年以 Oracle 的胜诉而告一段落,那我们可不可以理解 Oracle 如果真拿到了 88 亿美元,在不计算打官司所花的费用来说,既白嫖了 Java 和 MySQL 还白赚了 14 亿美元呢。

MySQL 另起炉灶

看完了 MySQL 的历史,我们在回到 MySQL 产品本身。当年在 Sun 公司收购了 MySQL 之后,MySQL 的创始人 Monty Widenius(迈克尔·维德纽斯)因为不满意 Sun 的工作方式而选择了离职,之后 MySQL 之父带领了一帮兄弟,另起炉灶成立 MySQL 的一个重要分支:MariaDB。

image.png
迈克尔·维德纽斯,图片来源于网络

Maria 是 MySQL 之父 Monty 最小女儿的名字,好神奇!MariaDB 的命名竟然这么的草率,这也充分的说明,有一个好爹就可以让全世界都记住你的名字~

MariaDB 初体验

MariaDB 来自于 MySQL,因此 MariaDB 和 MySQL 在绝大多数方面是兼容的,对于应用程序来说,比如 PHP、Perl、Python、Java、.NET、Ruby 无需做任何改动就可以直接使用二者。值得一提的是 MariaDB 的发展速度很快,新版本的发布速度已经超过了 Oracle 公司官方的 MySQL 版本了。

MariaDB 和 MySQL 更多区别:
https://go.mariadb.com/20Q4-WBN-GLBL-OSSC-MySQL-Comparison-Migration-2020-9-30_Registration-LP.html?_ga=2.27991548.1993405062.1602674827-345855344.1602674827

那接下来我们就尝试体验一下 MariaDB 吧,它的下载地址:https://mariadb.com/downloads/

1-download.png
选择对应的版本和系统类型进行下载。

PS:因为官方下载地址是国外源,所以下载速度很慢,可以关注【Java中文社群】发送“Maria”获取最新版本的国内下载地址。

MariaDB 的安装和 MySQL 的安装比较类似,只是提供了更多可配置项,比如端口号和编码方式、密码等让用户可以友好的配置,安装如下所示:
2.png
3.png
4.png
5.png
输入用户密码和确认密码,选择是否用 UTF8 作为默认的字符集。
6.png
可以修改服务的名称、端口号和内存占用量,设置完之后点击 Next,等待完成之后整个安装就成功了。

自带客户端

我们发现安装完 MariaDB 之后它会自动帮我们安装一个 HeidiSQL 的客户端,使用它我们可以很方便的操作 MariaDB。
image.png
它的操作界面如下图所示:
8.png
10.png
11.png
美否暂且不论,就中文菜单和自带功能(视图、SQL 操作、导入导出数据等功能)来说,给我的第一个感觉就是很实用。

命令行工具

如果我们还是习惯用 MySQL 的操作命令行,在 MariaDB 装完之后(无需安装 MySQL)的情况下也可以直接使用 MySQL 的命令行工具,我们只需要先将 MariaDB 的安装(bin)目录设置在环境变量中,如下图所示:
12.png
再直接敲击 CMD 的命令行就可以直接操作了,如下图所示:
13.png
操作命令还是和操作 MySQL 的命令一样,还是那熟悉的味道~

官方 MySQL

Oracle 虽然收购了 MySQL,并且在前些年更新也很慢(MySQL 8)之前,这个也可以理解,毕竟要把主要的精力和核心的团队放在赚钱的 Oracle DB 身上嘛,但好在一点 Oracle 依然为我们保留了免费的社区版可供使用,Oracle 的 MySQL 相关产品如下:

  • MySQL Community Server 社区版本,开源免费,但不提供官方技术支持;
  • MySQL Enterprise Edition 企业版本,需付费,可以试用 30 天;
  • MySQL Cluster 集群版,开源免费。可将几个 MySQL Server 封装成一个Server;
  • MySQL Cluster CGE 高级集群版,需付费;
  • MySQL Workbench(GUI TOOL)一款专为 MySQL 设计的 ER 数据库建模工具。它是著名的数据库设计工具 DBDesigner4 的继任者。MySQL Workbench 又分为两个版本,分别是社区版(MySQL Workbench OSS)、商用版(MySQL Workbench SE)。

所以想要继续使用 Oracle 的 MySQL 版本也是可以的,有社区版可以免费用,但如果想要尝试一下 MariaDB 也未尝不可,毕竟 MariaDB 提供了更多的功能和优化的改进。

总结

MySQL 的发展一波三折,先被 Sun 收购,最后又归属于 Oracle,后来 MySQL 之父从被收购的公司出来自己又开始做 MySQL 新的分支 MariaDB,它可以看作是 MySQL 的升级版,有兴趣的朋友可以试试。

关注【Java中文社群】发送“Maria”获取下载地址。
查看原文

赞 2 收藏 1 评论 0

Mkless 关注了问题 · 10月6日

Swoole的协程思路是怎样的

我目前对协程浅显的认知是这样的,协程由用户创建,是一个拥有自己上下文环境的函数,跟进程线程不同,进程线程每次切换都需要由操作系统调度,每次切换进程、线程都要保存上下文状态,当进程、线程多了以后,内核频繁切换拷贝数据会导致性能问题,协程应该是一个有自己上下文(局部变量)的函数,每次调用函数如果碰到阻塞的地方,由协程让出CPU执行权,执行后面的逻辑,等到阻塞结束的时候,再按照逻辑返回CPU控制权,也就是返回到上次协程执行到的地方继续处理下面的逻辑,所有的调度都在进程内处理,没有线程参与,也就没有锁的问题,数据在进程内应该是共享的,然后没有多余的用户态-内核态转换的消耗,所以性能会好一点?!?

C语言有<sys/ucontext.h>这个库可保存代码上下文环境实现协程,
然后C还有setjmp,longjmp这两个函数可以在函数栈中跳转,那么是用setjmp协程内要跳转回的位置,在longjmp之前用一个结构体保存当前函数执行的上下文相关信息,等到合适的时机再longjmp回协程内部,通过结构体拿到上下文数据,继续处理就可以实现一个简单的协程了吧?

现在看Swoole的源代码好吃力,甚至连依赖的哪个库都没找到。。。他创建协程的思路是怎样的,内部是如何进行调度的

关注 2 回答 1

Mkless 赞了回答 · 9月2日

解决go打印defer

这个问题,如果你要想了解它最根本的原理,得去看defer相关的源码,相关代码在.../runtime/runtime2.go.../runtime/panic.go下。这里贴一下_defer结构体的源码:

// A _defer holds an entry on the list of deferred calls.
// If you add a field here, add code to clear it in freedefer and deferProcStack
// This struct must match the code in cmd/compile/internal/gc/reflect.go:deferstruct
// and cmd/compile/internal/gc/ssa.go:(*state).call.
// Some defers will be allocated on the stack and some on the heap.
// All defers are logically part of the stack, so write barriers to
// initialize them are not required. All defers must be manually scanned,
// and for heap defers, marked.
type _defer struct {
    siz     int32 // includes both arguments and results
    started bool
    heap    bool
    // openDefer indicates that this _defer is for a frame with open-coded
    // defers. We have only one defer record for the entire frame (which may
    // currently have 0, 1, or more defers active).
    openDefer bool
    sp        uintptr  // sp at time of defer
    pc        uintptr  // pc at time of defer
    fn        *funcval // can be nil for open-coded defers
    _panic    *_panic  // panic that is running defer
    link      *_defer

    // If openDefer is true, the fields below record values about the stack
    // frame and associated function that has the open-coded defer(s). sp
    // above will be the sp for the frame, and pc will be address of the
    // deferreturn call in the function.
    fd   unsafe.Pointer // funcdata for the function associated with the frame
    varp uintptr        // value of varp for the stack frame
    // framepc is the current pc associated with the stack frame. Together,
    // with sp above (which is the sp associated with the stack frame),
    // framepc/sp can be used as pc/sp pair to continue a stack trace via
    // gentraceback().
    framepc uintptr
}

关于你的问题,大概解释一下:
首先,defer关键字后的函数调用执行会在函数返回前发生;
然后,defer关键字的执行顺序是倒序的,也就是写在代码最下方的defer先被调用。这是因为defer实际是一个链表(上面代码中的link *_defer,并且运行时会将后出现的defer追加到链表的最前面,而实际执行时又是从链表头开始执行,所以是倒序。

上面两点应该没什么问题,主要就是你这里a的赋值问题了。

defer关键字后面跟的是一个函数调用,你这里的其实本质上一样:都是调用了带参数的函数并把a作为参数传入,只不过是一个匿名函数。

注意了,defer关键字在代码运行到它(不是调用执行它)的时候,会直接拷贝函数参数,也就是当前函数参数的值,而又是值传递,不是地址传递。所以,在代码执行到这两句时,就已经把当前的a传进去了,因此输出的是1

的不同地方在哪里呢?它没有参数。我们知道,如果函数没有参数,当它内部调用一个变量时,就会去作用域外的作用域找,因此它用的是main()里的a。那么这个a为什么会输出2?就是前面说的:defer关键字后的函数调用会在函数返回前发生,而那时,defer的这个匿名函数作用域外的a已经是2了。

我们对照代码看一下,代码中,除了前面提到的link,还有一个fn *funcval,这货就是defer后面的函数。
当代码运行到defer的这一句时,会创建一个新的_defer结构体,并将defer后面的函数引用传递进来给fn,因此这个fn指向的还是原来那个匿名函数,则它们的作用域自然也相同。

我这里把你问题中的代码稍微改一下,根据上面我说的这些东西,猜猜输出是什么:

package main

import "fmt"

func main() {
    a := 1
    defer func() {
        fmt.Println("一:", a)
    }()

    defer fmt.Println("二:", a)


    defer func(a *int) {
        fmt.Println("四:", *a)
    }(&a)

    a ++
    defer func(a int) {
        fmt.Println("三:", a)
    }(a)
}

输出结果如下:

三: 2
四: 2
二: 1
一: 2

关注 6 回答 5

Mkless 赞了文章 · 8月31日

输入网址按回车,到底发生了什么

关注公众号“码哥字节”,解锁更多硬核

详解输入网址点击回车,后台到底发生了什么。透析 HTTP 协议与 TCP 连接之间的千丝万缕的关系。掌握为何是三次握手四次挥手? time_wait 存在的意义是什么?全面图解重点问题,再也不用担心面试问这个问题。

大致流程

  • URL 解析。
  • DNS 查询。
  • TCP 连接。
  • 服务器处理请求。
  • 客户端接收 HTTP 报文响应。
  • 渲染页面

重点来了

  • 如何理解 TCP 的三次握手与四次挥手?每次握手客户端与服务端是怎样的状态?
  • 为何挥手会出现 2MSL,遇到大量 Socket 处在 TIME_WAIT 或者 CLOSE_WAIT 状态是什么问题?
  • 三次握手与四次挥手的过程是怎样的?
  • HTTP 的报文格式又是怎样的?

继续阅读本文,且听码哥字节答疑解惑,微信搜索 “码哥字节”,关注公众号更多硬核。

URL 解析

地址解析:首先判断你输入的是一个合法的 URL 还是一个待搜索的关键词,并且根据你输入的内容进行自动完成、字符编码等操作。

HSTS 由于安全隐患,会使用 HSTS 强制客户端使用 HTTPS 访问页面。详见:你所不知道的 HSTS[1]。

其他操作 浏览器还会进行一些额外的操作,比如安全检查、访问限制(之前国产浏览器限制 996.icu)。

检查缓存

URL解析

DNS 查询

DNS 查询

  1. 浏览器缓存:先检查是否在缓存中,没有则调用系统库函数进行查询。
  2. 操作系统缓存:操作系统也有自己的 DNS 缓存,但在这之前,会向检查域名是否存在本地的 Hosts 文件里,没有则向 DNS 服务器发送查询请求。
  3. 路由器缓存。
  4. ISP DNS 缓存:ISP DNS 就是在客户端电脑上设置的首选 DNS 服务器,它们在大多数情况下都会有缓存。

根域名服务器查询

在前面所有步骤没有缓存的情况下,本地 DNS 服务器会将请求转发到互联网上的根域,下面这个图很好的诠释了整个流程:

DNS递归查询

需要注意的的是:

  1. 递归方式:一路查下去中间不返回,得到最终结果才返回信息(浏览器到本地 DNS 服务器的过程)
  2. 迭代方式,就是本地 DNS 服务器到根域名服务器查询的方式。
  3. 什么是 DNS 劫持
  4. 前端 dns-prefetch 优化

TCP 连接建立与断开

TCP/IP 分为四层,在发送数据时,每层都要对数据进行封装:

TCP/IP 连接

应用层:发送 HTTP 请求

浏览器从地址栏得到服务器 IP,接着构造一个 HTTP 报文,其中包括:

  • 请求报头(Request Header):请求方法、目标地址、遵循的协议等
  • 请求主体,请求参数,比如 body 里面的参数

传输层:TCP 传输报文

传输层会发起一条到达服务器的 TCP 连接,为了方便传输,会对数据进行分割(以报文段为单位),并标记编号,方便服务器接受时能够准确地还原报文信息。在建立连接前,会先进行 TCP 三次握手。

网络层:IP 协议查询 MAC 地址

将数据段打包,并加入源及目标的 IP 地址,并且负责寻找传输路线。判断目标地址是否与当前地址处于同一网络中,是的话直接根据 Mac 地址发送,否则使用路由表查找下一跳地址,以及使用 ARP 协议查询它的 Mac 地址。

链路层:以太网协议

根据以太网协议将数据分为以“帧”为单位的数据包,每一帧分为两个部分:

  • 标头:数据包的发送者、接受者、数据类型
  • 数据:数据包具体内容

Mac 地址

以太网规定了连入网络的所有设备都必须具备“网卡”接口,数据包都是从一块网卡传递到另一块网卡,网卡的地址就是 Mac 地址。每一个 Mac 地址都是独一无二的,具备了一对一的能力。

主要的请求过程:

  1. 浏览器从地址栏中获取服务器的 IP 和端口号;
  2. 浏览器有服务器之间通过 TCP 三次握手建立连接;
  3. 浏览器向服务器发送报文;
  4. 服务器接收报文处理,同时将响应报文发给浏览器;
  5. 浏览器解析报文,渲染输出到页面;

三次握手

在传输层传输数据之前需要建立连接,也就是三次握手创建可靠连接。

三次握手

首先建立链接前需要 Server 端先监听端口,因此 Server 端建立链接前的初始状态就是 LISTEN 状态,这时 Client 端准备建立链接,先发送一个 SYN 同步包,发送完同步包后,Client 端的链接状态变成了 SYN_SENT 状态。Server 端收到 SYN 后,同意建立链接,会向 Client 端回复一个 ACK。

由于 TCP 是双工传输,Server 端也会同时向 Client 端发送一个 SYN,申请 Server 向 Client 方向建立链接。发送完 ACK 和 SYN 后,Server 端的链接状态就变成了 SYN_RCVD。

Client 收到 Server 的 ACK 后,Client 端的链接状态就变成了 ESTABLISHED 状态,同时,Client 向 Server 端发送 ACK,回复 Server 端的 SYN 请求。

Server 端收到 Client 端的 ACK 后,Server 端的链接状态也就变成了的 ESTABLISHED 状态,此时建连完成,双方随时可以进行数据传输。

在面试时需要明白三次握手是为了建立双向的链接,需要记住 Client 端和 Server 端的链接状态变化。另外回答建连的问题时,可以提到 SYN 洪水攻击发生的原因,就是 Server 端收到 Client 端的 SYN 请求后,发送了 ACK 和 SYN,但是 Client 端不进行回复,导致 Server 端大量的链接处在 SYN_RCVD 状态,进而影响其他正常请求的建连。可以设置 tcp_synack_retries = 0 加快半链接的回收速度,或者调大 tcp_max_syn_backlog 来应对少量的 SYN 洪水攻击

四次挥手

我们只要关注 80 端口与 13743 端口建立的连接断开过程,浏览器通过 13747 端口发送 [FIN, ACK] 这里是不是跟很多网上看到的不一样?

  1. 其实是客户端在发送 [FIN] 报文的时候顺带发了一个 [ACK] 确认上次传输确认。
  2. 接着服务端通过 80 端口响应了 [ACK] ,然后立马响应 [FIN, ACK] 表示数据传输完了,可以关闭连接。
  3. 最后浏览器通过 13743 端口 发送 [ACK] 包给服务端,客服端与服务端连接就关闭了。

具体流程如下图抓包所示:

四次挥手

三次握手与四次挥手

TCP 连接与断开

客户端:

  • SYN_SENT - 客户端发起第 1 次握手后,连接状态为 SYN_SENT ,等待服务端内核进行应答,如果服务端来不及处理(例如服务端的 backlog 队列已满)就可以看到这种状态的连接。
  • ESTABLISHED - 表示连接处于正常状态,可以进行数据传送。客户端收到服务器回复的 SYN+ACK 后,对服务端的 SYN 单独回复(第 3 次握手),连接建立完成,进入 ESTABLISHED 状态。服务端程序收到第 3 次握手包后,也进入 ESTABLISHED 状态。
  • FIN_WAIT_1 - 客户端发送了关闭连接的 FIN 报文后,等待服务端回复 ACK 确认。
  • FIN_WAIT_2 - 表示我方已关闭连接,正在等待服务端关闭。客户端发了关闭连接的 FIN 报文后,服务器发回 ACK 应答,但是没进行关闭,就会处于这种状态。
  • TIME_WAIT - 双方都正常关闭连接后,客户端会维持 TIME_WAIT 一段时间,以确保最后一个 ACK 能成功发送到服务器端。停留时长为 2 倍的 MSL (报文最大生存时间),Linux 下大约是 60 秒。所以在一个频繁建立短连接的服务器上通常可以看到成千上万的 TIME_WAIT 连接。

服务端:

  • LISTEN - 表示当前程序正在监听某个端口时。
  • SYN_RCVD - 服务端收到第 1 次握手后,进入 SYN_RCVD 状态,并回复一个 SYN+ACK(第 2 次握手),再等待对方确认。
  • ESTABLISHED - 表示连接处于正常状态,可以进行数据传送。完成 TCP3 次握手后,连接建立完成,进入 ESTABLISHED 状态。
  • CLOSE_WAIT - 表示客户端已经关闭连接,但是本地还没关闭,正在等待本地关闭。有时客户端程序已经退出了,但服务端程序由于异常或 BUG 没有调用 close()函数对连接进行关闭,那在服务器这个连接就会一直处于 CLOSE_WAIT 状态,而在客户机已经不存在这个连接了。
  • LAST_ACK - 表示正在等待客户端对服务端的关闭请求进行最终确认。

TIME_WAIT 状态存在的理由:

划重点了

  • 可靠地实现 TCP 全双工连接的终止 在进行关闭连接四路握手协议时,最后的 ACK 是由主动关闭端发出的,如果这个最终的 ACK 丢失,服务器将重发最终的 FIN,因此客户端必须维护状态信息允 许它重发最终的 ACK。如 果不维持这个状态信息,那么客户端将响应 RST 分节,服务器将此分节解释成一个错误( 在 java 中会抛出 connection reset 的 SocketException)。因而,要实现 TCP 全双工连接的正常终 止,必须处理终止序列四个分节中任何一个分节的丢失情况,主动关闭 的客户端必须维持状 态信息进入 TIME_WAIT 状态。
  • 允许老的重复分节在网络中消逝 TCP 分节可能由于路由器异常而“迷途”,在迷途期间,TCP 发送端可能因确认超时而重发这个 分节,迷途的分节在路由器修复后也会被送到最终目的地,这个 原来的迷途分节就称为 lost duplicate。在关闭一个 TCP 连接后,马上又重新建立起一个相同的 IP 地址和端口之间的 TCP 连接,后一个连接被称为前一个连接的化身 ( incarnation),那么有可能出现这种情况,前一 个连接的迷途重复分组在前一个连接终止后出现,从而被误解成从属于新的化身。为了避免 这个情 况,TCP 不允许处于 TIME_WAIT 状态的连接启动一个新的化身,因为 TIME_WAIT 状 态持续 2MSL,就可以保证当成功建立一个 TCP 连接的时 候,来自连接先前化身的重复分组已 经在网络中消逝

另外回答断链的问题时,可以提到实际应用中有可能遇到大量 Socket 处在 TIME_WAIT 或者 CLOSE_WAIT 状态的问题。一般开启 tcp_tw_reuse 和 tcp_tw_recycle 能够加快 TIME-WAIT 的 Sockets 回收;而大量 CLOSE_WAIT 可能是被动关闭的一方存在代码 bug,没有正确关闭链接导致的。

简单地说就是

  1. 保证 TCP 协议的全双工连接能够可靠关闭;
  2. 保证这次连接的重复数据段从网络中消失,防止端口被重用时可能产生数据混淆;

服务器处理请求并响应 HTTP 报文

深入分析下 HTTP 报文到底是什么玩意。数据传输都是通过 TCP/IP 协议负责底层的传输工作, HTTP 协议基本不用操心,所谓的 “超文本传输协议” 似乎不怎么例会 “传输” 这个事情,那 HTTP 的核心又是什么呢?

比图 TCP 报文,它在实际要传输的数据之前附加了一个 20 字节的头部数据,存储 TCP 协议必须的额外信息,例如发送方的端口号、接收方的端口号、包序号、标志位等等。

有了这个附加的 TCP 头,数据包才能够正确传输,到了目的地后把头部去掉,就可以拿到真正的数据。这个很容易理解,设置起点与终点,不同协议贴上不同的头部,到了对应目的地就拆下这个头部,提取真正的数据。

HTTP报文

与 TCP/UDP 类似需要在传输数据前设置一些请求头,不同的是 HTTP 是一个 “纯文本” 的协议,所有的头都是 ASCII 码的文本,很容易看出来是什么。

再者就是他的请求报文与响应报文的结构基本一样,主要三大部分组成:

  1. 起始行(Start Line):描述请求或者响应的基本信息。
  2. Header:使用 key-value 的形式详细说明报文信息。
  3. 空行。
  4. 消息正文(Entity):传输的数据,图片、视频、文本等都可以。

这其中前两部分起始行和头部字段经常又合称为“请求头”或“响应头”,消息正文又称为“实体”,但与“header”对应,很多时候就直接称为“body”。

敲黑板了

HTTP 协议规定报文必须包含 Header,而且之后必须有一个 “空行”,也就是“CRLF”,十六进制的“0D0A”,可以没有 “body”。

报文结构如下图所示:

HTTP报文

截取一段报文:

HTTP报文抓取

请求头-起始行

请求行由请求方法字段、URL 字段和 HTTP 协议版本字段 3 个字段组成,它们用空格分隔。例如,GET / HTTP/1.1。

HTTP 协议的请求方法有 GET、POST、HEAD、PUT、DELETE、OPTIONS、TRACE、CONNECT

GET 是请求方法, “/” 是请求的目标资源,“HTTP/1.1” 请求协议版本号。

GET / HTTP/1.1 翻译成文字大概就是:“hello,服务器,我要请求根目录下的默认文件使用的是 HTTP 1.1 协议版本”。

头部 Header

第二部分就是 Header,组成形式是 key:value,使用自定义头需要注意事项:

  1. header 字段不区分大小写,通常是首字母大写;
  2. 字段名不允许有空格,可以使用 “-”,不能使用 “_”;
  3. 字段名必须紧接着 “:”,不能有空格,但是 “:” 后面可以有空格。
  4. 字段名顺序没有意义;

浏览器接收响应并渲染数据

浏览器接收到来自服务器的响应资源后,会对资源进行分析。首先查看 Response header,根据不同状态码做不同的事(比如上面提到的重定向)。如果响应资源进行了压缩(比如 gzip),还需要进行解压。然后,对响应资源做缓存。接下来,根据响应资源里的 MIME[3] 类型去解析响应内容(比如 HTML、Image 各有不同的解析方式)。

接下来将接收到的数据渲染出来,不同的浏览器也不是完全相同,但是大致流程是一样的:

浏览器渲染页面流程

如果觉得阅读后对你有帮助,希望多多分享、点赞与在看素质三连不做白嫖者。关注 【码哥字节】解锁更多硬核。

推荐阅读

以下几篇文章阅读量与读者反馈都很好,推荐大家阅读:

公众号后台回复 ”加群“,加入读者技术群,里面有阿里、腾讯的小伙伴一起探讨技术。

我的个人微信:MageByte1024

查看原文

赞 19 收藏 13 评论 0

Mkless 赞了文章 · 8月20日

美国如果把根域名服务器封了,中国会从网络上消失?

美国如果把根域名服务器封了,中国会从网络上消失?
作者:卫剑钒
来源:公众号微月人话


自从美国宣布“清洁网络”行动后,很多懂点网络的人,第一反应是,美国人会下手根域名服务器吗?

这种忧虑可不是一年两年了。

2014年6月24日的《人民日报》上引用专家发言:“目前美国掌握着全球互联网13台域名根服务器中的10台。理论上,只要在根服务器上屏蔽该国家域名,就能让这个国家的国家顶级域名网站在网络上瞬间“消失”。在这个意义上,美国具有全球独一无二的制网权,有能力威慑他国的网络边疆和网络主权。譬如,伊拉克战争期间,在美国政府授意下,伊拉克顶级域名“.iq”的申请和解析工作被终止,所有网址以“.iq”为后缀的网站从互联网蒸发。”_(1)_

《信息安全与通信保密》杂志2014年第10期的一篇文章写道:“2004年,由于与利比亚在顶级域名管理权问题上发生争执,美国终止了利比亚的顶级域名.LY的解析服务,导致利比亚从网络中消失3天。”_(2)_

对此,我们需要害怕吗?我们需要什么样的反制措施?

不是专家,还真回答不了这个问题。

因为这需要了解DNS的工作原理,了解根域名的管理机制。

这里先给出简要回答:不排除这种可能性,但并不是没有办法。

一句话原因:虽然根不在我们手里,但我们有镜像。

DNS傻瓜书

先了解点基本概念,懂DNS的可以直接跳过本节。

1、DNS是什么?

DNS就是将域名转换为IP的,因为我们人类的记忆力太差,根本记不住IP,而电脑通信又必须用IP,所以人类发明了域名,让我们可以记住baidu.com、taobao.com这种还算能记得住的域名。然后通过DNS,将这些域名转换为电脑需要的IP。

2、DNS是怎么工作的?

每个电脑里面都设置了本地DNS服务器(简称LDNS),需要的时候,就向LDNS发出请求,LDNS在网上问权威域名服务器(简称权威DNS),有时候问一家是不够的,要问一大圈下来,最后才能得到答案。

3、权威DNS是干什么的?

问我一个域名,我告诉你IP,如果我不知道,我告诉你谁可能知道,你再去问它。

4、什么是根域名服务器(简称根DNS)?

当LDNS啥都不知道的时候(也即没有任何缓存),就去问根DNS,根能告诉LDNS下一步该问谁。

5、全世界有多少根DNS?

13个,其中10个在美国,英国和瑞典各1个,日本1个。

6、根DNS的名字和IP都是什么?

在这个网址:

https://www.internic.net/doma...

打开可以看到,里面有13个根的名字和IP,其名字从A.root-servers.net到M.root-servers.net。

A开头那个简称A根,是主根,其他12个(B、C、D、E、F、G、H、I、J、K、L、M)是辅根。

为什么根DNS只有13台?

本节看不懂没关系(一般人都看不懂),你只需要知道,由于历史原因和技术原因,对于IPv4而言,根DNS只能有13个IP。

正宗答案是:DNS主要使用UDP数据报传送报文,不含前面的各种头部,DNS报文要求被控制在512字节之内( RFC1035 ),主要考虑是这个大小几乎可以在互联网上畅通无阻,不会因为路径中某个MTU太小( MTU 通常总会 >= 576,见 RFC791 )而导致IP分片,从而预防了各种不可预期的后果_(3)_。

而每一个根DNS在DNS报文中都要占用一定的字节数,比如根的名称、TTL、IP地址等。这样,13个根域名服务器基本上就把空间占差不多了,剩余的字节还要用于包装DNS报头以及其它协议参数,所以根域名服务器不易太多,13个算是比较合适的数目。具体可以看一下“Why 13 DNS root servers?”这篇文章。_(4 )_

真的只有13台服务器吗?

和很多人想象的完全不一样,这13个根域名服务器,并不是只有13台物理的服务器。

这13个根,只是一个逻辑上的概念,每个根DNS,背后都有多台真正的物理服务器在工作!

截至2020年8月12日,全球一共有1097个根服务器。每一个根都有若干个镜像,分布在全球不同的地方。

这个数目在不断上涨,去年10月1日新中国成立70周年阅兵的时候,我看了一下,是1015个服务器。

这13个根由12个独立的机构管理,比如A根和J根都是由Verisign公司管理,截至2020年8月12日,A根在全球各地有53个站点,J根有185个站点。L根由ICANN管理,全球有167个站点,其中北京2个,上海1个。

在root-servers网站上_(5)_,可以查到所有这些根服务器的分布,从网站展示的根镜像服务器地图上看(2020年8月12日),北京有 5 个根镜像服务器,上海 1 个,杭州 2 个,武汉1个、郑州1个、西宁1个、贵阳1个、广州1个、香港 9 个,台北 6 个。

包含港澳台部分,我国一共有28个根镜像。

我国境内发出的对根DNS的请求,其实都由镜像完成了。这一点后面会解释。

现在,为了增长知识,你该硬着头皮看一些DNS细节了。

DNS到底是怎么工作的?

对于IT从业者,希望你能理解并牢牢记住本节的内容。

因为你迟早会遇到有关DNS的困惑。

先介绍一下域名的级别:

.代表根域名, .com这种是顶级域名,也叫一级域名,baidu.com这种叫二级域名, www.baidu.com这种叫三级域名,依次类推。

注:也有其他叫法的,反正你知道这个意思就可以了。

再介绍一下最常见的两种域名服务器:

权威DNS:负责对请求作出权威的回答。权威DNS中存储着记录,最常见的3种:A记录(记录某域名和其IP的对应),NS记录(记录某域名和负责解析该域的权威DNS),CNAME记录(负责记录某域名及其别名)。权威能直接回答的,就回A记录;需要其他权威DNS回答的,就回NS记录,然后LDNS再去找其他权威DNS问;如果该记录是别名类型的,就回CNAME,LDNS就会再去解析别名。

递归DNS:通常就是LDNS,它接受终端的域名查询请求,负责在网上问一圈后,将答案返回终端。

现在举一个具体的例子:比如终端请求www.baidu.com这个域名的IP。

在没有缓存时,LDNS会从根DNS问起:

1、LDNS问根DNS说:“www.baidu.com的IP是多少啊?”。

2、根DNS说:“我哪有时间管你这么细的问题,你去问com顶级域的DNS吧,我只管到顶级域,喏,这些是com顶级域DNS的名字和IP,你去问它们吧”。(以NS记录回应)

3、LDNS又忙问com的权威DNS,com权威DNS说:“你问的这是三级域名,我不管这么多,你去问baidu.com的权威DNS吧,它的名字是ns.baidu.com,他的IP是XXX(这里可能给出多个权威DNS)”。

4、LDNS继续问baidu.com的权威DNS,这次痛快,因为www.baidu.com正是它管的,它可能直接给出A记录,也可能给出CNAME记录,如果是前者,就直接得到IP,如果是后者,就需要对别名再做查询。

5、最终,LDNS得到www.baidu.com的IP,并将其返回给终端。

美国如果把根域名服务器封了,中国会从网络上消失?

细心的人会问,在第1步中,LDNS问根DNS的时候,他是怎么知道根DNS的IP的?

这13个IP通常是预先配置在LDNS里面的。在LDNS初始化DNS缓存或者缓存失效的时候,LDNS向自己被预先配置的这些IP中的一个,发起对根的查询(也即询问.的NS记录),获得最新的根DNS的信息_(6)_。

对于DNS服务器软件而言,这13个IP,配置在根提示文件(root hints file)中,可能是named.cache或root.ca或root.hints等等之类的文件。

上面就是各种教科书中都会讲到的DNS查询过程,但实际上,没有这么麻烦,因为各个层面都是有缓存的。

实际DNS查询的过程,是这样的:

举个例子,比如用户在浏览器中输入这个域名:123.abc.qq.com.cn

1、浏览器会先看自身有没有对这个域名的缓存,如果有,就直接返回,如果没有,就去问操作系统,操作系统也会去看自己的缓存,如果有,就直接返回,如果没有,再去hosts文件看,也没有,才会去问LDNS。

2、LDNS会去先看看自己有没有123.abc.qq.com.cn的A记录,要有就直接返回,要没有,就去看有没有abc.qq.com.cn的NS记录,如果有,就去问它要答案,如果没有,就去看有无qq.com.cn的NS的记录,如果有,就去问它,没有就去看有无com.cn的DNS,还没有就去看有无cn的DNS,如果连cn的NS记录都没有,才去问根。

所以,有了缓存以后,教科书上那种从根问起的情况,实际上很少发生。

只有在各处都没有缓存的时候,我们才会问根。

根镜像起什么作用?

根镜像承担起和根一样的功能。

根DNS中,最重要的文件就是根区文件(Root Zone file)。所有顶级域名记录都存在根区文件中。

辅根从主根同步数据,根镜像从根同步数据。最终,所有根和镜像都有着同样的根区文件。

而且最有意思的是,根镜像和根有着同样的IP。

我们知道,全球有一千多个根镜像,但是大多数人不知道,它们一起共享13个IP!  对的。因为只有13个根。

这是如何做到的?答案是任播(Anycast,又译泛播)技术。

不关心技术细节的,请直接看本节的最后一句。

任播最初由RFC1546提出,主要用在DNS根服务器上。

任播是指在IP网络上通过一个IP地址标识一组提供特定服务的主机,服务访问方并不关心提供服务具体是哪一台主机提供的,访问该地址的报文可以被IP网络路由到“最近”的一个(最好也只是一个,别送到多个)服务器上。这里“最近”可以是指路由器跳数、服务器负载、服务器吞吐量、客户和服务器之间的往返时间( RTT,round trip time )、链路的可用带宽等特征值。

这样,一方面,用户可以就近访问;另一方面,即便部分根出现故障也没事。

有些同学可能联想到负载均衡,没错,大致上就是这个意思。

对于中国用户来说,对根的请求,一般不会跑到美国去,而是通过任播技术路由到中国境内的根镜像上。

根DNS是怎么管理的?

根DNS目前由12家机构管理。A根是主根,由美国公司Verisign管理。

根DNS中最重要的文件,根区文件,由ICANN管理。

ICANN(The Internet Corporation for Assigned Names and Numbers,互联网名称与数字地址分配机构)是成立于1998年的一家注册在美国的非营利性组织。

根DNS管理的历史变迁过程还是比较复杂的。这里简要说一下。

DNS最初的技术开发者与管理者是美国南加州大学的Jon Postel博士,他掌管互联网初期根DNS的管理和分配。

1988年,美国政府要求Jon Postel采取更安全和更合理的措施来保证互联网核心资源的分配和管理_(7)_。于是,大名鼎鼎的IANA(The Internet Assigned Numbers Authority,互联网数字分配机构)被组建,并在DARPA和南加州大学信息科学研究所(ISI)的合同下管理。

IANA负责互联网全局编号和编码的管理与协调,之所以需要这么个机构,是因为互联网协议的值或参数,必须是全球唯一的,否则无法互联互通,比如HTTP协议默认都在80端口等待用户请求,而404编码则一致代表"未找到页面”。IANA主要职责包括IP地址段的分配、协议代码和编号的分配(如协议号、端口号)、自治系统编号 (ASN) 分配、DNS根区管理(包括通用顶级域名gTLD以及国家和地区顶级域名ccTLD管理)等。_(8)_

1998年ICANN成立之后,美国商务部以合同形式,委托ICANN承担IANA日常运行,IANA从ISI转移到ICANN之下。

对于顶级域名的管理,ICANN的政策是,每个顶级域名(像com、cn、org这种顶级域名,目前有1000多个)都找一个托管商,该域名的所有事项都由托管商负责。

.cn域名的托管商是中国互联网络信息中心(CNNIC),它决定.cn域名的各种政策。

.com、.net 、.name、.gov这四个顶级域名都由Verisign公司托管。

Verisign和ICANN还是闹过几次不愉快的。_(9)_

2003年,Verisign 推出了一项新业务 Site Finder,用户访问没有注册过的.com或.net域名,都会被导向 Verisign 的网站。这意味着,它事实上拥有了所有没有注册过的.com和.net域名。几天之内,Verisign 就挤入了全世界的前10大网站。

ICANN 要求 Verisign 立刻停止该业务,否则将终止域名托管合同。Verisign 屈服了,停止了这项业务,但是接着就把 ICANN 告上了法庭,要求法庭厘请两者之间的合同,ICANN 到底有没有权力干涉它的业务。

2006年底,他们达成了庭外和解。ICANN 同意延长 Verisign 的顶级域名托管合同,并且同意 Verisign 向消费者收取的单个域名注册费的上限,从6美元提高到了7.85美元。这个费用标准,一直沿用到了今天,你去注册一个.com或.net域名,所交的钱有0.18美元是 ICANN 收取的管理费,7.85美元是 Verisign 收取的托管费,其余的钱就是域名零售商的费用。

虽然是ICANN运营着IANA,但毕竟是在美国政府的合同管理之下,全球各国以及民间人士颇有微词,一致认为美国政府应该彻底退出。

2014年3月14日,美国商务部国家通讯与信息管理局(NTIA)宣布愿意将IANA的管理权完全移交给ICANN,并要求ICANN制定移交计划。NTIA尤其强调,移交计划要强化多利益相关方模式,不能以政府间组织或政府领导的组织取代当前NTIA扮演的角色。

2016年3月17日,ICANN向NTIA提交了移交计划。2016年6月9日,NTIA公布审核意见,表示ICANN提交的移交计划满足了此前设定的条件。

2016年8月16日,NTIA宣布不再延期现有合同。

虽然遇到一些阻挠_(10)_,最终,2016年10月1日,ICANN和美国商务部之间关于IANA职能的合同到期且不再续约,ICANN彻底成为独立的非营利机构。IANA部门的员工和其他的相关资源都被转移到ICANN新设立的附属机构PTI(Public Technical Identifiers,公共技术标识符)中。

ICANN使用全球多利益相关方治理模型(global multistakeholder governance model)进行管理。PTI董事会共5席,3席由ICANN委派,2席由全球互联网社群代表组成提名委员会产生。2017年2月,ICANN发布PTI董事竞选公告,经半年多轮面试及背景调查,提名委员会于2017年10月26日宣布我国北龙中网的王伟与另一欧洲代表中选。又经一个半月的利益冲突审查,2017年12月13日ICANN董事会正式确认王伟当选。_(11)_

我国的根镜像由谁管理?

从目前我所找到的资料看,自2003年以来,我国在不断引进根镜像,尤其是去年,根镜像个数增速很快。

2003年,中国电信引入了国内第一个根镜像节点(F根)。

2005年,I根服务器运行机构在 CNNIC 设立了中国第二个根镜像(I根)。

2006年,中国联通(原中国网通)与美国 VeriSign 公司合作, 在国内正式开通J根镜像服务器,同时引入了全球最大的两个顶级域名 “.COM”和“.NET”镜像节点;引进这些镜像的主要目的是提高根域名和顶级域名的解析性能。

2014年,世纪互联与ICANN合作在中国增设L根域名服务器镜像。

2019年6月24日,工信部批准CNNIC设立六台域名根镜像服务器(F、I、K、L)。这六台域名根服务器编号为 JX0001F、JX0002F、JX0003I、JX0004K、JX0005L 和 JX0006L_(12)_,并批准互联网域名系统北京市工程研究中心(ZDNS)设立L根镜像服务器JX0007L_(13)_。

2019年11月6日,工信部批复同意中国信息通信研究院设立L根镜像服务器,编号分别为JX0008L、JX0009L。

2019年12月5日,工信部批复同意中国信息通信研究院设立域名根服务器(K根镜像服务器),编号为JX0010K。

2019年12月9日,工信部批复同意CNNIC设立域名根服务器(J、K根镜像服务器),编号分别为JX0011J、JX0012K。

从工信部的批文中可以了解到,相关单位负责根镜像的运行、维护和管理工作,维护国家利益和用户权益,并接受工信部的管理和监督检查。

工信部在给CNNIC的批文中写道:“你中心应严格遵守《互联网域名管理办法》《通信网络安全防护管理办法》及相关法律法规、行政规章及行业管理规定,接受我部的管理和监督检查,建立符合我部要求的信息管理系统并与我部指定的管理系统对接,保证域名根服务器安全、可靠运行,为用户提供安全、方便的域名服务,保障服务质量,保护用户个人信息安全,维护国家利益和用户权益。”

美国能对根DNS做什么手脚?

虽然ICANN是一个独立的非营利性机构,但如果美国政府动用强制力量,A根(主根)的内容仍然存在被篡改的可能。

也就是根区文件可以被篡改。

会怎么篡改?

我们先看看根区文件长什么样。

从ICANN官网上可以下载根区文件:

https://www.iana.org/domains/...

该文件保存所有顶级域名的信息,目前大小为2.2M,2万余行。

每当有顶级域名的变动时,该文件就会更新。

我们可以看到,和cn域名解析相关的记录也就那么几十行。

美国如果把根域名服务器封了,中国会从网络上消失?

如果删除和cn相关的那些行,很快,就会同步到所有的根中。

然后,在所有的缓存都过期之后,全球所有人都访问不了.cn后缀的网站。

如何应对?

因为我们维护着根镜像,所以我们控制着镜像中的内容。

而中国境内的对根的访问,通过我们的运营商,都会落到对我国根镜像的访问上。

我们可以不同步关于cn的修改。

就这么简单。

可以简单写个程序,每次同步完立刻加上cn记录。

也可以自己搭个主根,完全不和美国的根同步。(相当于另立中央了)

当然,世界各地不在我们管理之下的根和根镜像,如果不加行动,仍然会同步这些删除。

那么,除了中国自己,其他国家的人都无法访问.cn网站。

但是,这些国家很快就会有响应,凡是想访问.cn网站的国家,都会把cn记录加回去,并拒绝同步美国删去的这几行。

最终,只有美国人,访问不了.cn网站。

综上分析,我认为美国这么做的可能性不大,因为这一招过于低劣,将会让美国政府完全颜面扫地,并失去今后在互联网领域的任何话语权。而ICANN也将失去公信力,整个互联网世界,会推选使用新的机构和新的主根。

因为互联网世界的一贯准则就是:如有封禁,就绕过它。

后记

最后,我们看看本文开头所提的两个断网事件是怎么回事:

关于伊拉克域名事件,可以看看清华大学段海新教授的文章:“伊拉克域名.IQ被美国删除的背后以及早期的根域名管理”,里面把整个事件的来龙去脉说的很清楚。主要原因是.iq域名的前任管理者于2002年被关进监狱,新任管理者(NCMC)于2005年才提出申请,而IANA当时还考虑征求新旧代理双方对新授权的一致认可,所以才出现了所谓的“申请和解析工作被终止”。

关于利比亚域名事件,可以看看此文:“利比亚国家顶级域名(.LY)中止服务始末”,事实情况是参与运营.LY的两家机构因争夺归属权而内斗的结果(其中一方关闭了.LY域名服务器的解析)。经过这番变乱,2004年10月,ICANN批准将.LY授予利比亚邮电总公司,.LY事件算是尘埃落定。

本文中提到的风险和应对,主要是我个人的分析,下面看看业内专家的说法。

中国工程院院士、清华大学计算机系主任吴建平在2019年的一次访谈_(14)_中表示,DNS根域名服务器不是互联网的“核按钮”。全球互联网根域名服务器运行者,不可能同时关闭所有的根服务器,包括影子服务器。

互联网域名系统北京市工程研究中心(ZDNS)主任毛伟表示_(15)_:互联网专家一直都在不断完善域名根系统安全保障机制,就算真的断“根”了,也有应急方法来解决。在境内,可以采用根区数据备份并搭建应急根服务器来解决;在全球层面,可以用根镜像、IPv6环境下的根服务器数量扩展、根服务器运行机构备选机制等方法来解决。

现在,了解了这么多,关于根域名服务器,你是不是放心了很多。


参考文献:

  1. 从网络大国走向网络强国(http://opinion.people.com.cn/...
  2. 美国网络霸权浅析(http://www.wanfangdata.com.cn..._type=perio&id=xxaqytxbm201410030)
  3. 为什么域名根服务器只能有13台呢?(https://www.zhihu.com/questio...
  4. Why 13 DNS root servers?(https://miek.nl/2013/november...
  5. https://root-servers.org
  6. Initializing a DNS Resolver with Priming Queries(https://tools.ietf.org/html/d...
  7. 薛虹:互联网全球治理的新篇章(https://zhuanlan.zhihu.com/p/...
  8. ICANN: IANA职能(https://www.icann.org/zh/syst...
  9. 阮一峰:根域名的知识
  10. 徐培喜:IANA职能管理权移交谁是赢家
  11. 北龙中网王伟任职PTI董事 我国专家就任国际互联网治理关键岗位(http://news.sina.com.cn/c/201...
  12. 工业和信息化部关于同意中国互联网络信息中心设立域名根服务器(F、I、K、L根镜像服务器)及域名根服务器运行机构的批复(http://www.miit.gov.cn/n11462...
  13. 工业和信息化部关于同意互联网域名系统北京市工程研究中心有限公司设立域名根服务器(L根镜像服务器)及域名根服务器运行机构的批复(http://www.miit.gov.cn/n11462...
  14. 中国工程院院士吴建平:DNS根服务器不是互联网的核按钮!
  15. ZDNS毛伟:互联网根并不能让中国断网,更应重视企业域名服务风险

原文链接

segmentfault 公众号

查看原文

赞 30 收藏 9 评论 2

Mkless 赞了问题 · 7月11日

解决Golang文本去重的代码优化问题

不好意思啊,第一次学Golang,写了个文本去重来练习,文本内容大概75万行,用go test看了下最后的去重时间,需要17's,想知道还有哪里可以优化的。

代码如下

package distinct

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strings"
)

//DistinctFile 为指定文件去重
func DistinctFile(file string, output string) {
    // 读取需要去重的文件内容
    f, _ := os.Open(file)
    defer func() {
        ferr := f.Close()
        if ferr != nil {
            fmt.Println(ferr.Error())
        }
    }()

    reader := bufio.NewReader(f)

    // 去重map
    var set = make(map[string]bool, 0)
    // 去重后的结果
    var result string

    for {
        line, isPrefix, err := reader.ReadLine()

        if err != nil {
            break
        }

        if !isPrefix {

            lineStr := string(line)

            // key存在则跳出本次循环
            if set[lineStr] {
                continue
            }

            result += fmt.Sprintf("%s\n", lineStr)

            set[lineStr] = true
        }
    }

    // 写入另一个文件
    nf, _ := os.Create(output)
    io.Copy(nf, strings.NewReader(result))

    defer nf.Close()
}

go test的结果是17.654s

package distinct

import "testing"

func TestDistinctFile(t *testing.T) {
    DistinctFile("result.txt", "out.txt")
}

希望师傅们能指点一二

关注 1 回答 2

认证与成就

  • 获得 163 次点赞
  • 获得 75 枚徽章 获得 1 枚金徽章, 获得 24 枚银徽章, 获得 50 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-04-25
个人主页被 1.9k 人浏览