Giter Club home page Giter Club logo

notes's People

Contributors

jasonje avatar linjunyi22 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

notes's Issues

面向对象中__new__和__init__区别

  • __new__至少要有一个参数cls,代表当前类,此参数在实例化时由Python解释器自动识别;

  • __new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类(通过super(当前类名,cls))__new__出来的实例,或者直接是object__new__出来的实例;

  • __init__有一个参数self,就是这个__new__返回的实例,__init____new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值;

  • 如果__new__创建的是当前类的实例,会自动调用__init__函数,通过return语句里面调用的__new__函数的第一个参数是cls来保证是当前类实例,如果是其他类的类名,那么实际创建返回的就是其他类的实例,其实就不会调用当前类的__init__函数,也不会调用其他类的__init__函数。

Python中copy()和deepcopy()的区别

浅复制仅仅复制了容器中元素的地址;深复制,完全拷贝了一个副本,容器内部元素地址都不一样。

  • 复制不可变数据类型,不管copy()还是deepcopy(),都是同一个地址,和=效果一样,对象的id 值与浅复制原来的值相同;

  • 复制的值是可变对象(列表和字典)

    • 浅拷贝copy()有两种情况:

第一种情况:复制的对象中无复杂 子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。

第二种情况:复制的对象中有复杂 子对象 (例如列表中的一个子元素是一个列表), 改变原来的值中的复杂子对象的值 ,会影响浅复制的值。

    • 深拷贝deepcopy():完全复制独立,包括内层列表和字典

接口题目

  1. 一个指针可以指向一个接口类型吗

函数参数为interface{}时候可以接收任何类型的值。

永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。

  1. 正确的接口赋值示范
type Fragment interface {
        Exec(transInfo *TransInfo) error
}
type GetPodAction struct {
}
func (g GetPodAction) Exec(transInfo *TransInfo) error {
        ...
        return nil
}

正确的赋值方式有:

var fragment Fragment = new(GetPodAction)
var fragment Fragment = &GetPodAction{}
var fragment Fragment = GetPodAction{}
  1. 下面哪一行代码会 panic
package main

func main() {
  var x interface{}
  var y interface{} = []int{3, 5}
  _ = x == x
  _ = x == y
  _ = y == y
}

解析:_ = y == y行。因为两个比较值的动态类型为同一个不可比较类型。

Nginx 缓存配置

http {
    ...
    
    proxy_cache_path /path/to/cache 
    levels=1:2 
    keys_zone=nginx_cache:1024m 
    max_size=10g 
    inactive=7d 
    use_temp_path=off; # 最好写在同一行
    
    server {
        ...
        
        proxy_cache_key $uri;
        proxy_cache_methods GET POST;
        proxy_cache nginx_cache;
        proxy_cache_valid  200 206 304 301 302 7d;
        
        set $nocache 0;
        # 这里可以写相关的匹配规则,来对不同的请求进行缓存或不缓存
        
        proxy_no_cache     $nocache;
        proxy_cache_bypass $nocache;
    }
}

参数说明:

  • proxy_cache_path:缓存存放路径;

  • levels:设置缓存文件目录层次;如果所有的缓存放入一个文件夹,则影响效率。这里设置的是levels=1:2 ,表示两级目录;

  • keys_zone:在共享内存中设置一块存储区域来存放缓存的 keymetadata,这样 nginx 可以快速判断一个 request 是否命中或者未命中缓存,1m 可以存储 8000key10m 可以存储80000个key

  • max_size:最大的缓存空间,如果不指定,会使用掉所有的硬盘空间,当达到配额后,会删除最少使用的 cache 文件;

  • inactive:删除指定时间内未被访问的缓存文件;

  • use_temp_path:如果为 off,则 nginx 会将缓存文件直接写入指定的 cache 文件中,而不是使用 temp_path 存储,建议为 off,避免文件在不同文件系统中不必要的拷贝;

  • proxy_cache:启用 proxy cache,指定声明好的缓存区域 keys_zone;

  • proxy_cache_valid 200 206 304 301 302 7d:为对 http状态为 200 206 304 301 302 等的缓存结果缓存 7 天;

  • proxy_cache_key $uri:定义缓存唯一 key,通过唯一 key 来进行 hash 存取;

  • proxy_no_cacheproxy_cache_bypass :当为 0 时表示缓存,不为 0 时表示不缓存。

注意:

    • $upstream_cache_status变量可以查看缓存命中的状态,相关的日志中可以设置。
    • 如果是使用 uwsgi 进行代理,上面的全部参数只要将 proxy 换成 uwsgi 即可,类似 uwsgi_cache_path

InnoDB 的 redo log 记录方式

redo log的大小是固定的,在MySQL中可以通过修改配置参数innodb_log_files_in_groupinnodb_log_file_size 配置日志文件数量和每个日志文件大小,redolog采用循环写的方式记录,当写到结尾时,会回到开头循环写日志。

redo log记录方式

write pos表示日志当前记录的位置,一边写一边后移;

ib_logfile_3写满后,会从ib_logfile_0从头开始记录;

checkpoint 表示将日志记录的修改写入磁盘,完成写入后,checkpoint 会将日志上的相关记录擦除掉;

write pos->checkpoint 之间的部分是 redo log 空着的部分,用于记录新的记录;

checkpoint->write pos 之间是redo log待落盘的数据修改记录;

write pos追上checkpoint时,得先停下记录,先推动checkpoint向前移动,空出位置记录新的日志。

基于主键索引和普通索引查询的区别

主键索引的叶子节点存的是整行数据。在 InnoDB 里,主键索引也被称为聚簇索引 (clustered index);

非主键索引的叶子节点内容是主键的值。在 InnoDB 里,非主键索引也被称为二级索引 (secondary index)。

create table T(
ID int primary key,
k int not null,
name varchar(16),
index (k))engine=InnoDB;

如果语句是 select * from T where ID=500,即主键查询方式,则只需要搜索 ID 这棵 B+ 树;

如果语句是 select * from T where k=5,即普通索引查询方式,则需要先搜索 k 索引 树,得到 ID 的值为 500,再到 ID 索引树搜索一次。这个过程称为回表

MySQL中INSERT IGNORE INTO和REPLACE INTO的区别

2019.10.11

INSERT INTO表示插入数据,数据库会检查主键,如果出现重复会报错;

REPLACE INTO表示插入替换数据,需求表中有PrimaryKey,或者unique索引,如果数据库已经存在数据,则用新数据替换,如果没有数据效果则和insert into一样;

INSERT IGNORE INTO表示,如果中已经存在相同的记录,则忽略当前新数据。

SQLAlchemy表对象模型生成器——sqlacodegen

2019.10.14

安装:

pip install sqlacodegen

使用:

sqlacodegen postgresql:///some_local_db
sqlacodegen mysql+oursql://user:password@localhost/dbname
sqlacodegen sqlite:///database.db

多重继承

经典类中MRO搜索采用简单的从左到右的深度优先顺序,而新式类是广度优先。

collections.defaultdict 类

  • dict.setdefault(key, default)

接收两个参数,第一个参数是健的名称,第二个参数是默认值。

假如字典中不存在给定的键,则返回参数中提供的默认值;反之,则返回字典中保存的值。

  • collections.defaultdict
from collections import defaultdict
mydict = defaultdict(list)

上面的初始化函数接受一个类型作为参数,当所访问的键不存在的时候,可以实例化一个值作为默认值。

其只有在通过dict[key]或者dict.__getitem__(key)访问的时候才有效,例如使用mydict.pop('something')就无效。

该类除了接受类型名称作为初始化函数的参数之外,还可以使用任何不带参数的可调用函数,到时该函数的返回结果作为默认值。

  • collections.defaultdict 的实现

定义了__missing__()方法,当使用__getitem__()访问一个不存在的键的时,会调用__missing__()方法获取默认值,并将该键添加到字典中去。

dict中并不存在这个__missing__()方法,需要在派生的子类中自行实现。

常量、变量相关题目

  1. 判断下面代码能否通过编译
var(
   size := 1024
   max_size = size*2
)

func main() {
    fmt.Println(size,max_size)
}

解析:变量声明的简短形式中,需要使用显示初始化,编译器会自动推导数据类型,所以不需要提供数据类型,只能在函数内部使用简短形式。

  1. iota的使用
const (
    x = iota
    _
    y
    z = "zz"
    k 
    p = iota
)

func main()  {
    fmt.Println(x,y,z,k,p)
}

输出:

0 2 zz zz 5

解析:

  • iota只在常量表达式中使用;

  • 每次 const 出现,都会让const初始化为0

  • 自增常量经常包含一个自定义的枚举类型,允许依靠编译器完成自增设置;

  • 使用 _ 跳过不需要的值;

  • 定义在一行的情况:

const (
    Apple, Banana = iota +1, iota + 2
    Cherimoya, Durian
    Elderberry, Fig
)

上面变量的结果如下:

Apple: 1
Banana: 2
Cherimoya: 2
Durian: 3
Elderberry: 3
Fig: 4
  • 中间插队:
const (
    i = iota
    j = 3.14
    k = iota
    l
) 

上面各个变量对应的结果就是0, 3.14, 2, 3

  1. 下面的赋值是否正确
var x error = nil

解析:nil只能赋值给指针、chanfuncinterfacemapslice

error的定义如下:

type error interface {
    Error() string
}
  1. 下面代码的输出是什么
func hello() []string {  
    return nil
}

func main() {  
    h := hello
    if h == nil {
        fmt.Println("nil")
    } else {
        fmt.Println("not nil")
    }
}

输出:not nil

解析:hhello(),所以其不为空,能通过编译。

  1. iotaconst 关键字
const (
    a = iota
    b = iota
)
const (
    name = "name"
    c    = iota
    d    = iota
)
func main() {
    fmt.Println(a)
    fmt.Println(b)
    fmt.Println(c)
    fmt.Println(d)
}

解析:输出:0 1 1 2iota只能在常量的表达式中使用。iotaconst 关键字出现时将被重置为 0const 中每新增一行常量声明将使 iota 计数一次。

  1. 下面这段代码输出什么
type Direction int

const (
    North Direction = iota
    East
    South
    West
)

func (d Direction) String() string {
    return [...]string{"North", "East", "South", "West"}[d]
}

func main() {
    fmt.Println(South)
}

输出:South

解析:根据 iota 的用法推断出 South 的值是 2;另外,如果类型定义了 String() 方法,当使用 fmt.Printf()fmt.Print()fmt.Println() 会自动使用 String() 方法,实现字符串的打印。

  1. 下面这段代码输出什么
var p *int

func foo() (*int, error) {
    var i int = 5
    return &i, nil
}

func bar() {
    //use p
    fmt.Println(*p)
}

func main() {
    p, err := foo()
    if err != nil {
        fmt.Println(err)
        return
    }
    bar()
    fmt.Println(*p)
}

解析:问题出在操作符:=,对于使用:=定义的变量,如果新变量与同名已定义的变量不在同一个作用域中,那么 Go 会新定义这个变量。对于本例来说,main() 函数里的 p 是新定义的变量,会遮住全局变量 p,导致执行到 bar() 时程序,全局变量 p 依然还是 nil,程序随即 Crash

  1. 下面代码输出什么
func main() {
    i := 1
    s := []string{"A", "B", "C"}
    i, s[i-1] = 2, "Z"
    fmt.Printf("s: %v \n", s)
}

输出:[Z, B, C]

解析:多重赋值时候,上例中,先计算等号左边的索引表达式和取值表达式,接着解读等号右边的表达式,然后进行赋值。所以赋值运算等同于 i, s[0] = 2, "Z"

  1. i++i--Go中是语句,不是表达式,所以不能进行赋值操作。

  2. 下面代码有什么问题

const i = 100
var j = 123

func main() {
    fmt.Println(&j, j)
    fmt.Println(&i, i)
}

结果:编译报错cannot take the address of i

解析:常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,所以常量无法寻址。

  1. 下面代码能否通过编译
func GetValue(m map[int]string, id int) (string, bool) {
    if _, exist := m[id]; exist {
        return "exist", true
    }
    return nil, false
}
func main() {
    intmap := map[int]string{
        1: "a",
        2: "b",
        3: "c",
    }

    v, err := GetValue(intmap, 3)
    fmt.Println(v, err)
}

结果:不能通过编译。

解析:nil 可以用作 interfacefunctionpointermapslicechannel 的空值。但是如果不特别指定的话,Go 语言不能识别类型,所以会报错: cannot use nil as type string in return argument.

  1. 下面代码能否通过编译
func Foo(x interface{}) {
    if x == nil {
        fmt.Println("empty interface")
        return
    }
    fmt.Println("non-empty interface")
}
func main() {
    var x *int = nil
    Foo(x)
}

输出:non-empty interface

解析:接口除了有静态类型,还有动态类型和动态值,当且仅当动态值动态类型都为 nil 时,接口类型值才为 nil。这里的 x 的动态类型是 *int,所以 x 不为 nil

  1. 下面代码错误的地方
func main() {

    var s []int
    s = append(s,1)

    var m map[string]int
    m["one"] = 1 
}

解析:不能对 nilmap 直接赋值,需要使用 make() 初始化。但可以使用 append() 函数对为 nilslice 增加元素。

  1. 通过指针变量 p 访问其成员变量 name,有两种方式:p.name(*p).name

  2. 下面的代码有什么问题

func main() {  
    var x = nil 
    _ = x
}

解析:nil 用于表示 interface、函数、mapssliceschannels 的零值。如果不指定变量的类型,编译器猜不出变量的具体类型,导致编译错误。

修复代码:

func main() {
    var x interface{} = nil
    _ = x
}
  1. 下面代码输出什么
func test(x byte)  {
    fmt.Println(x)
}

func main() {
    var a byte = 0x11 
    var b uint8 = a
    var c uint8 = a + b
    test(c)
}

解析:34。与 runeint32 的别名一样,byteuint8 的别名,别名类型无须转换,可直接计算。

  1. 下面代码可以编译通过吗
func main() {
    const x = 123
    const y = 1.23
    fmt.Println(x)
}

解析:编译可以通过。常量是一个简单值的标识符,在程序运行时,不会被修改的量。不像变量,常量未使用是能编译通过的。

  1. 下面代码输出什么
const (
    x uint16 = 120
    y
    s = "abc"
    z
)

func main() {
    fmt.Printf("%T %v\n", y, y)
    fmt.Printf("%T %v\n", z, z)
}

输出:

uint16 120
string abc

解析:常量组中如不指定类型和初始化值,则与上一行非空常量右值相同。

  1. 下面的代码输出什么
func (n N) value(){
    n++
    fmt.Printf("v:%p,%v\n",&n,n)
}

func (n *N) pointer(){
    *n++
    fmt.Printf("v:%p,%v\n",n,*n)
}


func main() {
    var a N = 25

    p := &a
    p1 := &p

    p1.value()
    p1.pointer()
}

结果:编译错误。

calling method value with receiver p1 (type **N) requires explicit dereference
calling method pointer with receiver p1 (type **N) requires explicit dereference

解析:不能使用多级指针调用方法。

cookie和session的用法和区别

    1. session 保存在服务器端,cookie 保存在客户端;
    1. session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id);
    1. cookie可以跟踪会话,也可以保存用户喜好或者保存用户名密码;session用来跟踪会话。

函数和方法相关题目

  1. 这段代码有什么缺陷
func funcMui(x,y int)(sum int,error){
    return x+y,nil
}

结果:第二个返回值没有命名。

解析:

  • 如果函数存在多个返回值,只要一个返回值存在命名,则另一个其他的返回值也必须命名;

  • 如果有多个返回值,必须加上括号;如果只有一个带有命名的返回值,也必须带上括号。

  1. init() 函数有几个需要注意的地方:
  • init() 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等;

  • 一个包可以出现多个 init() 函数,一个源文件也可以包含多个 init() 函数;

  • 同一个包中多个 init() 函数的执行顺序没有明确定义,但是不同包的init函数是根据包导入的依赖关系决定的;;

  • init() 函数在代码中不能被显示调用、不能被引用(赋值给函数变量),否则出现编译错误;

  • 一个包被引用多次,如 A import B,C import B,A import CB 被引用多次,但 B 包只会初始化一次;

  • 引入包,不可出现死循环。即 A import B,B import A,这种情况编译失败。

  1. cap()函数适用于array, slice, channel类型。

  2. 可变函数的定义与调用方式

func add(args ...int) int {

    sum := 0
    for _, arg := range args {
        sum += arg
    }
    return sum
}
  • 定义:func f(elems ...Type)

  • 调用:f(a, b, c);f([]Type{a, b, c}...)

  1. 下面代码输出什么
func main() {
    var fn1 = func() {}
    var fn2 = func() {}

    if fn1 != fn2 {
        println("fn1 not equal fn2")
    }
}

解析:编译错误,函数只能与 nil 比较。

  1. 下面的代码输出什么
type N int

func (n N) test(){
    fmt.Println(n)
}

func main()  {
    var n N = 10
    fmt.Println(n)

    n++
    f1 := N.test
    f1(n)

    n++
    f2 := (*N).test
    f2(&n)
}

输出:10 11 12

解析:通过类型引用的方法表达式会被还原成普通函数样式,接收者是第一个参数,调用时显示传参。类型可以是 T*T,只要目标方法存在于该类型的方法集中就可以。

还可以直接使用方法表达式调用:

func main()  {
    var n N = 10

    fmt.Println(n)

    n++
    N.test(n)

    n++
    (*N).test(&n)
}
  1. 下面代码输出什么
type N int

func (n N) test(){
    fmt.Println(n)
}

func main()  {
    var n N = 10
    p := &n

    n++
    f1 := n.test

    n++
    f2 := p.test

    n++
    fmt.Println(n)

    f1()
    f2()
}

输出:13 11 12

解析:基于实例或指针引用的 method value,参数签名不会改变,依旧按正常方式调用。但当 method value 被赋值给变量或作为参数传递时,会立即计算并赋值该方法执行所需的 receiver 对象与其绑定,以便在稍后执行,能隐式传入 receiver 参数。

XML/JSON/Protobuf 比较

    1. JSON:键值对,轻量级的数据交换格式,具有良好的可读和便于快速编写的特性。
    1. XML: 在WebService中应用最为广泛,但是相比于JSON,它的数据更加冗余,因为需要成对的闭合标签。
    1. Protobuf:谷歌开源的一种数据格式,适合高性能,对响应速度有要求的数据传输场景。因为Protobuf是二进制数据格式,需要编码和解码。数据本身不具有可读性。因此只能反序列化之后得到真正可读的数据。

Protobuf优势:

    1. 序列化后体积相比JSONXML很小,适合网络传输;
    1. 支持跨平台多语言;
    1. 消息格式升级和兼容性还不错;
    1. 序列化反序列化速度很快,快于JSON的处理速度。

make 和 new 题目

  1. 下面两段代码的输出是什么
// 1.
func main() {
    s := make([]int, 5)
    s = append(s, 1, 2, 3)
    fmt.Println(s)
}

// 2.
func main() {
    s := make([]int, 0)
    s = append(s,1,2,3,4)
    fmt.Println(s)
}

输出:

[0 0 0 0 0 1 2 3]
[1 2 3 4]
  1. new()make() 的区别

new(T)make(T, args)Go 语言内建函数,用来分配内存,但适用的类型不同。

new(T) 会为 T 类型的新值分配已置零的内存空间,并返回地址(指针),即类型为 *T 的值。换句话说就是,返回一个指针,该指针指向新分配类型为 T 的零值。适用于值类型,如数组、结构体等。

make(T, args) 返回初始化之后的 T 类型的值,这个值并不是 T 类型的零值,也不是指针 *T,是经过初始化之后的 T 的引用。make() 只适用于 slicemapchannel

  1. 判断下面代码能否通过编译
func main() {
    list := new([]int)
    list = append(list1)
    fmt.Println(list)
}

结果:不能通过编译。

解析:new([]int)返回一个*[]int类型的指针,不能进行append的操作。正确的方式是使用make()或者字面量进行初始化。

MySQL中count(1), count(*), count(col) 的区别

count(*)count(1) 都会对全表进行扫描,统计所有记录的条数,包括那些为null的记录。

count(col) 会统计该字段不为null的记录条数。

    1. 在表没有主键时,count(1)count(*)快;
    1. 有主键时,主键作为计算条件,count(主键)效率最高;
    1. 若表格只有一个字段,则count(*)效率较高。

右加 __radd__()

实现反射加法操作。

正常运算时,比如:a + b,是以a为主导的,这里调用了魔法方法__add__()。但是如果表达式中的a没有__add__()方法时,就会调用b的反运算方法__radd__()

>>> class Nint(int):
...     def __radd__(self, other):
...             return int.__sub__(self, other) # 剪法运算
...
>>> a = Nint(6)
>>> b = Nint(4)
>>> a + b
10
>>> 1 + b # 1 没有 __add__()方法,就调用 b 中的反运算方法 __radd__(),返回 int.__sub__(4, 1)
3
>>>

写一个守护进程

2019.03.28

守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。

#!/bin/sh

#添加本地执行路径
export LD_LIBRARY_PATH=./

while true; do
        # 启动一个循环,定时检查进程是否存在
        server=`ps aux | grep myServer | grep -v grep`
        if [ ! "$server" ]; then
            # 如果不存在就重新启动
            nohup ./myServer -c 1 &
            # 启动后沉睡10s
            sleep 10
        fi
        # 每次循环沉睡1s
        sleep 5
done

什么是CPU亲和性(Affinity)

CPU的亲和性,就是进程要在指定的CPU上尽量长时间地运行而不被迁移到其他处理器,也称为CPU关联性。

即需要将进程或线程绑定到相应的CPU上;在多核运行的机器上,每个CPU本身自己会有缓存,缓存着进程使用的信息,而进程可能会被OS调度到其他CPU上,如此,CPU cache命中率就低了,当绑定CPU后,程序就会一直在指定的CPU跑,不会由操作系统调度到其他CPU上,性能有一定的提高。

软亲和性:就是进程要在指定的CPU上尽量长时间地运行而不被迁移到其他处理器,Linux 内核进程调度器天生就具有被称为软亲和性的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。

硬亲和性:简单来说就是利用Linux内核提供给用户的API,强行将进程或者线程绑定到某一个指定的CPU核运行。

解释:在Linux内核中,所有的进程都有一个相关的数据结构,称为task_struct。这个结构中与亲和性相关度最高的是cpus_allowed位掩码。这个位掩码由n位组成,与系统中的n个逻辑处理器一一对应。具有4个物理CPU的系统可以有4位。如果这些CPU都启用了超线程,那么这个系统就有一个8位的位掩码。如果为给定的进程设置了给定的位,那么这个进程就可以在相关的CPU上运行。因此,如果一个进程可以在任何CPU上运行,并且能够根据需要在处理器之间进行迁移,那么位掩码就全是1,这就是Linux中进程的缺省值。

  • 查看goroutineCPU中的迁移
package main

import (
    "fmt"
    "math/rand"
    "runtime"
    "time"
)

/*
#define _GNU_SOURCE
#include <sched.h>
*/
import "C"

func randSleep() {
    time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
}

func worker(id int) {
    for {
        fmt.Printf("worker: %d, CPU: %d\n", id, C.sched_getcpu())
        randSleep()
    }
}

func main() {
    for i := 0; i < runtime.NumCPU(); i++ {
        go worker(i)
    }
    time.Sleep(2 * time.Second)
}
  • 绑定goroutine到指定CPU
package main

import (
    "fmt"
    "math/rand"
    "os"
    "runtime"
    "time"
)

/*
#define _GNU_SOURCE
#include <sched.h>
#include <pthread.h>

void lock_thread(int cpuid) {
        pthread_t tid;
        cpu_set_t cpuset;

        tid = pthread_self();
        CPU_ZERO(&cpuset);
        CPU_SET(cpuid, &cpuset);
    pthread_setaffinity_np(tid, sizeof(cpu_set_t), &cpuset);
}
*/
import "C"

func randSleep() {
    time.Sleep(time.Duration(rand.Intn(300)) * time.Millisecond)
}

func setAffinity(cpuID int) {
    runtime.LockOSThread()
    C.lock_thread(C.int(cpuID))
}

func worker(id int, lock bool) {
    if lock {
        setAffinity(id)
    }

    for {
        fmt.Printf("worker: %d, CPU: %d\n", id, C.sched_getcpu())
        randSleep()
    }
}

func main() {
    lock := len(os.Getenv("LOCK")) > 0
    for i := 0; i < runtime.NumCPU(); i++ {
        go worker(i, lock)
    }
    time.Sleep(2 * time.Second)
}

defer 题目

  1. deferpanic的执行顺序
package main
 
import (
    "fmt"
)
 
func main() {
    defer_call()
}

func defer_call() {
    defer func() { fmt.Println("打印前") }()
    defer func() { fmt.Println("打印中") }()
    defer func() { fmt.Println("打印后") }()
    panic("触发异常")
}

输出:

打印后
打印中
打印前
panic: 触发异常

解析:defer 的执行顺序是后进先出。当出现 panic 语句的时候,会先按照 defer 的后进先出的顺序执行,最后才会执行 panic

  1. 下面代码输出什么
func increaseA() int {
    var i int
    defer func() {
        i++
    }()
    return i
}

func increaseB() (r int) {
    defer func() {
        r++
    }()
    return r
}

func main() {
    fmt.Println(increaseA())
    fmt.Println(increaseB())
}

解析:increaseA() 的返回参数是匿名,increaseB() 是具名。所以结果是0, 1

  1. 下面代码输出什么
func f1() (r int) {
    defer func() {
        r++
    }()
    return 0
}

func f2() (r int) {
    t := 5
    defer func() {
        t = t + 5
    }()
    return t
}

func f3() (r int) {
    defer func(r int) {
        r = r + 5
    }(r)
    return 1
}

解析:

  • f1()结果为1,拆解过程:
func f1() (r int) {

    // 1.赋值
    r = 0

    // 2.闭包引用,返回值被修改
    defer func() {
        r++
    }()

    // 3.空的 return
    return
}
  • f2()结果为5,拆解过程:
func f2() (r int) {
    t := 5
    // 1.赋值
    r = t

    // 2.闭包引用,但是没有修改返回值 r
    defer func() {
        t = t + 5
    }()

    // 3.空的 return
    return
}
  • f3()结果为1,拆解过程:
func f3() (r int) {

    // 1.赋值
    r = 1

    // 2.r 作为函数参数,不会修改要返回的那个 r 值
    defer func(r int) {
        r = r + 5
    }(r)

    // 3.空的 return
    return
}

2步,r 是作为函数参数使用,是一份复制,defer 语句里面的 r 和 外面的 r 其实是两个变量,里面变量的改变不会影响外层变量 r,所以不是返回 6 ,而是返回 1

  1. 下面代码输出什么
type Person struct {
    age int
}

func main() {
    person := &Person{28}

    // 1. 
    defer fmt.Println(person.age)

    // 2.
    defer func(p *Person) {
        fmt.Println(p.age)
    }(person)  

    // 3.
    defer func() {
        fmt.Println(person.age)
    }()

    person.age = 29
}

解析:

  • 1中将 28 当做 defer 函数的参数,会把 28 缓存在栈中,等到最后执行该 defer 语句的时候取出,即输出 28

  • 2缓存的是结构体 Person{28} 的地址,最终 Person{28}age 被重新赋值为 29,所以 defer 语句最后执行的时候,依靠缓存的地址取出的 age 便是 29,即输出 29

  • 3中闭包引用,输出 29

  1. 下面代码输出什么
func hello(i int) {  
    fmt.Println(i)
}
func main() {  
    i := 5
    defer hello(i)
    i = i + 10
}

解析:hello() 函数的参数在执行 defer 语句的时候会保存一份副本在实际调用 hello() 函数时用,所以是 5

  1. return 之后的 defer 语句会执行吗,下面这段代码输出什么?
var a bool = true
func main() {
    defer func(){
        fmt.Println("1")
    }()
    if a == true {
        fmt.Println("2")
        return
    }
    defer func(){
        fmt.Println("3")
    }()
}

解析:defer 关键字后面的函数或者方法想要执行必须先注册,return 之后的defer是不能注册的, 也就不能执行后面的函数或方法。

  1. 下面这段代码输出什么
func f(n int) (r int) {
    defer func() {
        r += n
        recover()
    }()

    var f func()

    defer f()
    f = func() {
        r += 2
    }
    return n + 1
}

func main() {
    fmt.Println(f(3))
}

输出:7

解析:第一步执行r = n + 1,接着执行第二个 defer,由于此时 f() 未定义,引发异常,随即执行第一个 defer,异常被 recover(),程序正常执行,最后 return`。

  1. 下面的代码输出什么
func F(n int) func() int {
    return func() int {
        n++
        return n
    }
}

func main() {
    f := F(5)
    defer func() {
        fmt.Println(f())
    }()
    defer fmt.Println(f())
    i := f()
    fmt.Println(i)
}

输出:7 6 8

解析:defer 后面的函数如果带参数,会优先计算参数,并将结果存储在栈中,到真正执行 defer 的时候取出。

  1. recover 的调用
func main() {
    defer func() {
        recover()
    }()
    panic(1)
}

recover()必须在 defer() 函数中直接调用才有效。

那么下面代码输出什么

func main() {
    defer func() {
        fmt.Print(recover())
    }()
    defer func() {
        defer fmt.Print(recover())
        panic(1)
    }()
    defer recover() 
    panic(2)
}

defer recover() 行代码捕获是无效的,在调用 defer() 时,便会计算函数的参数并压入栈中,所以执行 defer fmt.Print(recover()) 行代码时,此时便会捕获 panic(2)。此后的 panic(1),会被上一层的 recover() 捕获,最终输出 2 1

is和==区别

is:比较的是两个对象的id值是否相等,也就是比较俩对象是否为同一个实例对象,是否指向同一个内存地址;

==:比较的两个对象的内容/值是否相等,默认会调用对象的eq()方法。

UDP 如何实现可靠传输

传输层UDP是不可靠的连接,如果要实现可靠传输,就要在传输层的上一层(或者直接在应用层)实现类似于 TCP 协议的实现:超时重传(定时器)、有序接受 (添加包序号)、应答确认 (Seq/Ack应答机制)、滑动窗口流量控制等机制 (滑动窗口协议)等。

目前已经有一些实现UDP可靠传输的机制,比如UDT

Python新式类和经典类的区别

a. 在Python里凡是继承了object的类,都是新式类;

b. Python3里只有新式类;

c. Python2里面继承object的是新式类,没有写父类的是经典类;

d. 经典类目前在Python里基本没有应用;

e. 保持classtype的统一对新式类的实例执行a.__class__type(a)的结果是一致的,对于旧式类来说就不一样了;

f.对于多重继承的属性搜索顺序不一样新式类是采用广度优先搜索,旧式类采用深度优先搜索。

MySQL之间的数据复制

MySQL之间数据复制的基础是二进制日志文件(binary log file)。

一台MySQL数据库一旦启用二进制日志后,其作为master,它的数据库中所有操作都会以事件的方式记录在二进制日志中,其他数据库作为slave通过一个I/O线程与主服务器保持通信,并监控master的二进制日志文件的变化,如果发现master二进制日志文件发生变化,则会把变化复制到自己的中继日志中,然后slave的一个SQL线程会把相关的事件执行到自己的数据库中,以此实现从数据库和主数据库的一致性,也就实现了主从复制。

单例模式

  1. __new__
class SingleTon(object):
    _instance = None
    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = object.__new__(cls)
        return cls._instance

class MyClass(SingleTon):
    def __init__(self, val):
        self.val = val

if __name__ == '__main__':
    a = MyClass(1)
    b = MyClass(2)
    print(a is b)
    print(id(a), id(b))
    print(type(a))
    print(type(b))
  1. 装饰器
from functools import wraps

def singleton(cls):
    _instance = {}

    @wraps(cls)
    def single(*args, **kwargs):
        if cls not in _instance:
            _instance[cls] = cls(*args, **kwargs)
        return _instance[cls]
    return single

@singleton
class SingleTon(object):
    def __init__(self, val):
        self.val = val

if __name__ == '__main__':
    a = SingleTon(1)
    b = SingleTon(2)
    print(a is b)
    print(id(a), id(b))
    print(type(a))
    print(type(b))
  1. __metaclass__
class SingletonType(type):
    _instance = None
    def __call__(cls, *args, **kwargs):
        if not cls._instance:
            cls._instance = super(SingletonType, cls).__call__(*args, **kwargs)
        return cls._instance

class MyClass(metaclass = SingletonType): # 注意类名不能为 Singleton
    def __init__(self, val):
        self.val = val

if __name__ == '__main__':
    a = MyClass(1)
    b = MyClass(2)
    print(a is b)
    print(id(a), id(b))
    print(type(a))
    print(type(b))

类型、结构体相关题目

  1. 新类型和类型别名
package main

import "fmt"

type MyInt1 int
type MyInt2 = int

func main() {
    var i int =0
    var i1 MyInt1 = i 
    var i2 MyInt2 = i
    fmt.Println(i1,i2)
}

解析:type MyInt1 int 是基于int类型创建了新类型,而type MyInt2 = int 是创建了 int 类型的别名MyInt2

var i1 MyInt1 = i 是将int类型的变量赋值给MyInt1Go是强类型的语言,编译不能通过,正确的做法是进行强制类型转换,即var i1 MyInt1 = MyInt1(i);而MyInt2int的别名,能够进行赋值。

  1. 下面代码能否通过编译
func GetValue() int {
    return 1
}

func main() {
    i := GetValue()
    switch i.(type) {
    case int:
        println("int")
    case string:
        println("string")
    case interface{}:
        println("interface")
    default:
        println("unknown")
    }
}

结果:不能通过编译。

解析:只有接口类型才能进行类型选择。

  1. 下面的代码输出是什么
func main() {  
    a := 5
    b := 8.1
    fmt.Println(a + b)
}

解析:编译出错,Go语言是强类型语言,不同类型之间不能直接相加。

  1. 下面的代码能正常编译吗
func (i int) PrintInt ()  {
    fmt.Println(i)
}

func main() {
    var i int = 1
    i.PrintInt()
}

解析:编译错误。基于类型创建的方法必须定义在同一个包内;解决的办法可以定义一种新的类型。即:

type Myint int

func (i Myint) PrintInt ()  {
    fmt.Println(i)
}

func main() {
    var i Myint = 1
    i.PrintInt()
}
  1. 下面的代码能正常编译吗
type People interface {
    Speak(string) string
}

type Student struct{}

func (stu *Student) Speak(think string) (talk string) {
    if think == "speak" {
        talk = "speak"
    } else {
        talk = "hi"
    }
    return
}

func main() {
    var peo People = Student{}
    think := "speak"
    fmt.Println(peo.Speak(think))
}

解析:编译错误。值类型 Student 没有实现接口的 Speak() 方法,而是指针类型 *Student 实现该方法。

  1. 判断下面代码能否通过编译
func main() {
    sn1 := struct {
        age  int
        name string
    }{age11name: "qq"}
    sn2 := struct {
        age  int
        name string
    }{age11name: "qq"}

    if sn1 == sn2 {
        fmt.Println("sn1 == sn2")
    }

    sm1 := struct {
        age int
        m   map[string]string
    }{age11mmap[string]string{"a": "1"}}
    sm2 := struct {
        age int
        m   map[string]string
    }{age11mmap[string]string{"a": "1"}}

    if sm1 == sm2 {
        fmt.Println("sm1 == sm2")
    }
}
  • 结构体只能比较是否相等,但是不能比较大小。

  • 相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关。下面的sn3 与 sn1 就是不同的结构体:

sn3:= struct {
    name string
    age  int
}{age:11,name:"qq"}
  • 如果 struct 的所有成员都可以比较,则该 struct 就可以通过 ==!= 进行比较是否相等,比较时逐个项进行比较,如果每一项都相等,则两个结构体才相等,否则不相等。
  1. 下面的代码能正常编译吗
type People interface {
    Show()
}

type Student struct{}

func (stu *Student) Show() {

}

func main() {

    var s *Student
    if s == nil {
        fmt.Println("s is nil")
    } else {
        fmt.Println("s is not nil")
    }
    var p People = s
    if p == nil {
        fmt.Println("p is nil")
    } else {
        fmt.Println("p is not nil")
    }
}

解析:输出:s is nilp is not nil。当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。上面的代码,给变量 p 赋值之后,p 的动态值是 nil,但是动态类型却是 *Student,是一个 nil 指针,所以相等条件不成立。

  1. 下面代码输出什么
func main() {
    x := interface{}(nil)
    y := (*int)(nil)
    a := y == x
    b := y == nil
    _, c := x.(interface{})
    println(a, b, c)
}

输出:false true false

解析:类型断言语法:i.(Type),其中 i 是接口,Type 是类型或接口。编译时会自动检测 i 的动态类型与 Type 是否一致。但是,如果动态类型不存在,则断言总是失败。

  1. 下面代码能编译通过吗
type info struct {
    result int
}

func work() (int,error) {
    return 13,nil
}

func main() {
    var data info

    data.result, err := work() 
    fmt.Printf("info: %+v\n",data)
}

解析:编译失败。non-name data.result on left side of :=。不能使用短变量声明设置结构体字段值。

修复代码:

func main() {
    var data info

    var err error
    data.result, err = work() //ok
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(data)   
}
  1. 下面这段代码输出什么
type People struct {
    name string `json:"name"`
}

func main() {
    js := `{
        "name":"seekload"
    }`
    var p People
    err := json.Unmarshal([]byte(js), &p)
    if err != nil {
        fmt.Println("err: ", err)
        return
    }ex
    fmt.Println(p)
}

输出:struct field name has json tag but is not exported

解析:结构体访问控制,因为 name 首字母是小写,导致其他包不能访问。

  1. 下面这段代码输出什么
type T struct {
    ls []int
}

func foo(t T) {
    t.ls[0] = 100
}

func main() {
    var t = T{
        ls: []int{1, 2, 3},
    }

    foo(t)
    fmt.Println(t.ls[0])
}

输出:100

解析:调用 foo() 函数时虽然是传值,但 foo() 函数中,字段 ls 依旧可以看成是指向底层数组的指针。

  1. 下面的代码有什么问题
type X struct {}

func (x *X) test()  {
    println(x)
}

func main() {

    var a *X
    a.test()

    X{}.test()
}

解析:X{} 是不可寻址的,不能直接调用方法。在方法中,指针类型的接收者必须是合法指针(包括 nil),或能获取实例地址。

  1. 下面的代码输出什么
type T struct {
    x int
    y *int
}

func main() {
    i := 20
    t := T{10,&i}

    p := &t.x // 先计算 t.x ,然后得到 t.x 的地址

    *p++ // 先获取 p 的值,然后累加
    *p-- // 同上,做递减

    t.y = p

    fmt.Println(*t.y) // 取值
}

输出:10

解析:递增运算符 ++ 和递减运算符 -- 的优先级低于解引用运算符 * 和取址运算符 &,解引用运算符和取址运算符的优先级低于选择器 . 中的属性选择操作符。

  1. 下面的代码输出什么
type N int

func (n *N) test(){
    fmt.Println(*n)
}

func main()  {
    var n N = 10
    p := &n

    n++
    f1 := n.test

    n++
    f2 := p.test

    n++
    fmt.Println(n)

    f1()
    f2()
}

输出:13 13 13

解析:当目标方法的接收者是指针类型时,那么被复制的就是指针。

  1. 下面哪一行代码会 panic
package main

type T struct{}

func (*T) foo() {
}

func (T) bar() {
}

type S struct {
  *T
}

func main() {
  s := S{}
  _ = s.foo
  s.foo()
  _ = s.bar
}

解析:_ = s.bar行。因为 s.bar 将被展开为 (*s.T).bar,而 s.T 是个空指针,解引用会 panic

  1. 下面哪一行代码会 panic
func main() {
    nil := 123
    fmt.Println(nil)
    var _ map[string]int = nil
}

解析:var _ map[string]int = nil行。当前作用域中,预定义的 nil 被覆盖,此时 nilint 类型值,不能赋值给 map 类型。

  1. 下面的代码输出什么

17.1

type T struct {
    n int
}

func main() {
    ts := [2]T{}
    for i, t := range ts {
        switch i {
        case 0:
            t.n = 3
            ts[1].n = 9
        case 1:
            fmt.Print(t.n, " ")
        }
    }
    fmt.Print(ts)
}

输出:0 [{0} {9}]

解析:此时 for-range 使用的是数组 ts 的副本,所以 t.n = 3 的赋值操作不会影响原数组。

17.2

type T struct {
    n int
}

func main() {
    ts := [2]T{}
    for i, t := range &ts {
        switch i {
        case 0:
            t.n = 3
            ts[1].n = 9
        case 1:
            fmt.Print(t.n, " ")
        }
    }
    fmt.Print(ts)
}

输出:9 [{0} {9}]

解析:for-range 循环中的循环变量 t 是原数组元素的副本。如果数组元素是结构体值,则副本的字段和原数组字段是两个不同的值。

17.3

type T struct {
    n int
}

func main() {
    ts := [2]T{}
    for i := range ts[:] {
        switch i {
        case 0:
            ts[1].n = 9
        case 1:
            fmt.Print(ts[i].n, " ")
        }
    }
    fmt.Print(ts)
}

输出:9 [{0} {9}]

解析:for-range 切片时使用的是切片的副本,但不会复制底层数组,所以此副本切片与原数组共享底层数组。

Nginx多进程间连接平衡的控制

2019.10.16

ngx_accept_disabled = ngx_cycle->connection_n / 8
    - ngx_cycle->free_connection_n;

if (ngx_accept_disabled > 0) {
    ngx_accept_disabled--;

} else {
    if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
        return;
    }

    if (ngx_accept_mutex_held) {
        flags |= NGX_POST_EVENTS;

    } else {
        if (timer == NGX_TIMER_INFINITE
                || timer > ngx_accept_mutex_delay)
        {
            timer = ngx_accept_mutex_delay;
        }
    }
}

nginx使用一个叫ngx_accept_disabled的变量来控制是否去竞争accept_mutex锁。在第一段代码中,计算ngx_accept_disabled的值,这个值是nginx单进程的所有连接总数的八分之一,减去剩下的空闲连接数量,得到的这个ngx_accept_disabled有一个规律,当剩余连接数小于总连接数的八分之一时,其值才大于0,而且剩余的连接数越小,这个值越大。

再看第二段代码,当ngx_accept_disabled大于0时,不会去尝试获取accept_mutex锁,并且将ngx_accept_disabled减1,于是,每次执行到此处时,都会去减1,直到小于0。不去获取accept_mutex锁,就是等于让出获取连接的机会,很显然可以看出,当空余连接越少时,ngx_accept_disable越大,于是让出的机会就越多,这样其它进程获取锁的机会也就越大。不去accept,自己的连接就控制下来了,其它进程的连接池就会得到利用,这样,nginx就控制了多进程间连接的平衡了。

TCP连接状态

CLOSED        表示socket连接没被使用。

LISTENING     表示正在监听进入的连接。

SYN_SENT      表示正在试着建立连接。

SYN_RECEIVED  进行连接初始同步。

ESTABLISHED   表示连接已被建立。

CLOSE_WAIT    表示远程计算器关闭连接,正在等待socket连接的关闭。

FIN_WAIT_1    表示socket连接关闭,正在关闭连接。

CLOSING       先关闭本地socket连接,然后关闭远程socket连接,最后等待确认信息。

LAST_ACK      远程计算器关闭后,等待确认信号。

FIN_WAIT_2    socket连接关闭后,等待来自远程计算器的关闭信号。

TIME_WAIT     连接关闭后,等待远程计算器关闭重发。

字符串相关题目

  1. 下面的代码输出什么
func main() {  
    i := -5
    j := +5
    fmt.Printf("%+d %+d", i, j)
}

解析:%d表示输出十进制数字,+表示输出数值的符号,不表示取反。

  1. 下面的代码输出什么
func main() {
    str := "hello"
    str[0] = 'x'
    fmt.Println(str)
}

解析:编译出错,Go 语言中的字符串是只读的。

  1. 下面的代码输出什么
func main() {  
    i := 65
    fmt.Println(string(i))
}

解析:UTF-8 编码中,十进制数字 65 对应的符号是 A

  1. 下面代码输出什么
type ConfigOne struct {
    Daemon string
}

func (c *ConfigOne) String() string {
    return fmt.Sprintf("print: %v", c)
}

func main() {
    c := &ConfigOne{}
    c.String()
}

解析:出现运行时错误。如果类型实现 String() 方法,当格式化输出时会自动使用 String() 方法。

上面这段代码是在该类型的 String() 方法内使用格式化输出,导致递归调用,最后抛错。

runtime: goroutine stack exceeds 1000000000-byte limit
fatal error: stack overflow

动态获取和设置对象的属性

if hasattr(Parent, 'x'):
    print(getattr(Parent, 'x'))
    setattr(Parent, 'x',3)
print(getattr(Parent,'x'))
  • hasattr(object,name)函数:

判断一个对象里面是否有name属性或者name方法,返回bool值,有name属性(方法)返回True,否则返回False

  • getattr(object, name[,default])函数:

获取对象object的属性或者方法,如果存在则打印出来,如果不存在,打印默认值,默认值可选。

注意:如果返回的是对象的方法,则打印结果是:方法的内存地址,如果需要运行这个方法,可以在后面添加括号()

  • setattr(object, name, values)函数:

给对象的属性赋值,若属性不存在,先创建再赋值。

tcpdump 抓包

tcpdump 默认只截取前 96 字节的内容。

要想截取所有的报文内容,可以使用-s numbernumber就是要截取的报文字节数.

如果是0的话,表示截取报文全部内容。

  1. 常用参数
  • -n 表示不要解析域名,直接显示 ip

  • -nn 不要解析域名和端口;

  • -X 同时用 hexascii 显示报文的内容;

  • -XX 同 -X,但同时显示以太网头部;

  • -S 显示绝对的序列号(sequence number),而不是相对编号;

  • -i any 监听所有的网卡;

  • -v, -vv, -vvv 显示更多的详细信息;

  • -c number 截取 number 个报文,然后结束;

  • -A 只使用 ascii 打印报文的全部数据,不要和 -X 一起使用。截取 http 请求的时候可以用 sudo tcpdump -nSA port 80

  1. 过滤器
  • host: 过滤某个主机的数据报文
tcpdump host 1.2.3.4
  • src, dst: 过滤源地址和目的地址
tcpdump src 2.3.4.5
tcpdump dst 3.4.5.6
  • net: 过滤某个网段的数据,CIDR 模式
tcpdump net 1.2.3.0/24
  • proto: 过滤某个协议的数据,支持 tcpudpicmp。使用的时候可以省略 proto 关键字。
tcpdump icmp
  • port: 过滤通过某个端口的数据报
tcpdump port 3389
  • src/dst, port, protocol: 结合三者
tcpdump src port 1025 and tcp
tcpdump udp and src port 53
  • 指定 port 范围
tcpdump portrange 21-23
  • 指定数据报文范围(单位:字节)
tcpdump less 32
tcpdump greater 128
tcpdump > 32
tcpdump <= 128
  1. 逻辑组合

常用:AND, &&, OR, ||, not, !

  • 源地址是 10.5.2.3,目的端口是 3389 的数据报
tcpdump -nnvS src 10.5.2.3 and dst port 3389
  • 192.168 网段到 10 或者 172.16 网段的数据报
tcpdump -nvX src net 192.168.0.0/16 and dat net 10.0.0.0/8 or 172.16.0.0/16
  • Mars 或者 Pluto 发出的数据报,并且目的端口不是 22
tcpdump -vv src mars or pluto and not dat port 22
  1. 输出
  • 输出到文件
tcpdump -w capture_file.pcap port 80
  • 读取文件并显示
tcpdump -nXr capture_file.pcap host web30
  • 输出理解
21:27:06.995846 IP (tos 0x0, ttl 64, id 45646, offset 0, flags [DF], proto TCP (6), length 64)
    192.168.1.106.56166 > 124.192.132.54.80: Flags [S], cksum 0xa730 (correct), seq 992042666, win 65535, options [mss 1460,nop,wscale 4,nop,nop,TS val 663433143 ecr 0,sackOK,eol], length 0

21:27:07.030487 IP (tos 0x0, ttl 51, id 0, offset 0, flags [DF], proto TCP (6), length 44)
    124.192.132.54.80 > 192.168.1.106.56166: Flags [S.], cksum 0xedc0 (correct), seq 2147006684, ack 992042667, win 14600, options [mss 1440], length 0

21:27:07.030527 IP (tos 0x0, ttl 64, id 59119, offset 0, flags [DF], proto TCP (6), length 40)
    192.168.1.106.56166 > 124.192.132.54.80: Flags [.], cksum 0x3e72 (correct), ack 2147006685, win 65535, length 0
  1. 192.168.1.106.56166 > 124.192.132.54.80:源地址 ip192.168.1.106,源端口是 56166,目的地址是 124.192.132.54,目的端口是 80> 符号代表数据的方向。

  2. 常见的 TCP 报文的 Flags

    • [S]SYN(开始连接)
    • [.]: 没有 Flag
    • [P]: PSH(推送数据)
    • [F]: FIN (结束连接)
    • [R]: RST(重置连接)
    • [S.]: SYN-ACKSYN 报文的应答报文)

什么是defer

2019.07.13

含有defer的函数,都会在该函数返回前,调用defer函数,即便外部函数出现panic异常,也能执行。

defer DeferredFunc() // 这类的函数称为延迟调用函数(Deferred Function)
func example() {
    defer func() {
        fmt.Println("later")
    }

    fmt.Println("first")
}

先打印first,然后在example()返回前,打印later

  • defer的作用域只局限于包围它的函数中。

  • defer的调用是存在一个栈中,按照后进先出的顺序执行。

用途

  1. 释放已经获取的资源
func NewFile(path string) (c *Config, err error) {
    c = &Config{}

    file, err := os.Open(path)
    if err != nil {
        return
    }
    defer file.Close()

    err = json.NewDecoder(file).Decode(c)
    if err != nil {
        // err != nil时,执行file.Close()
        return
    }
    // 执行file.Close()
    return
}

不管NewFile()是否返回错误,在return前都会执行file.Close()这个延迟函数。

  1. panic中恢复
defer func() {
    if err := recover(); err != nil {
        fmt.Println(err)
    }
}()

panic("oops!")

defer中通过recover捕获异常,然后对异常进行处理。

函数不会因为异常而终止。

  1. 延迟闭包
func example() {
    num := 42
    defer func() {
        fmt.Println(num)
    }()

    num = 13
}

延迟调用函数能够获取外围函数中局部变量的最终状态,所以上述的结果是13

  1. 参数即时求值
func count(i int) (n int) {
	defer func(i int) {
		n = n + i // (3)
	}(i) // (1)

	i = i * 2 // (2)
	n = i // (2)
	return n
}

调用count(10)时候,上面函数的结果是30,其中值的变化顺序是:

(1) i = 10 --> (2) i = 20; n = i --> (3) n = 20 + 10 --> (4) return n

延迟调用函数在i传入之前就记录了i的值10,在return前执行了延迟调用函数,其结果是n = 20; i = 10的加和。

  1. 对象方法

(1) 没有指针作为接收者

type Car struct {
	model string
}

func (c Car) PrintModel() {
	fmt.Println(c.model)
}

func main() {
	c := Car{model: "DeLorean DMC-12"}
	defer c.PrintModel()

	c.model = "Chevrolet Impala"
}

结果为DeLorean DMC-12

(2) 指针作为接收者

type Car struct {
	model string
}

func (c *Car) PrintModel() {
	fmt.Println(c.model)
}

func main() {
	c := Car{model: "DeLorean DMC-12"}
	defer c.PrintModel()

	c.model = "Chevrolet Impala"
}

结果为Chevrolet Impala

(1)中结果是因为接收者在声明时候拷贝了值,其执行defer c.PrintModel()会使用拷贝的值;

(2)中的结果是因为接收者是指针对象,虽然会产生新的指针变量,但是指向的地址仍然是原地址,即作用于同一对象中。

参考链接

Go Defer Simplified with Practical Visuals

阿里开发强制要求的11条索引创建规范

  1. 【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。

说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。

  1. 【强制】超过三个表禁止join。需要join的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。

说明:即使双表join也要注意表索引、SQL性能。

  1. 【强制】在varchar字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度即可。

说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为20的索引,区分度会高达90%以上,可以使用count(distinct left(列名, 索引长度))/count(*)的区分度来确定。

  1. 【强制】页面搜索严禁左模糊或者全模糊,如果需要请走搜索引擎来解决。

说明:索引文件具有B Tree的最左前缀匹配特性,如果左边的值未确定,那么无法使用此索引。

  1. 【推荐】如果有order by的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现file_sort的情况,影响查询性能。

正例:where a=? and b=? order by c; 索引:a_b_c
反例:索引中有范围查找,那么索引有序性无法利用,如:where a>10 order by b; 索引a_b无法排序。

  1. 【推荐】利用覆盖索引来进行查询操作,避免回表。

说明:如果一本书需要知道第11章是什么标题,会翻开第11章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。

正例:能够建立索引的种类分为主键索引、唯一索引、普通索引三种,而覆盖索引只是一种查询的一种效果,用explain的结果,extra列会出现:using index

  1. 【推荐】利用延迟关联或者子查询优化超多分页场景。

说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。

正例:先快速定位需要获取的id段,然后再关联: SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id

  1. 【推荐】 SQL性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。 说明:

consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。

ref 指的是使用普通的索引(normal index)。

range 对索引进行范围检索。

反例:explain表的结果,type=index,索引物理文件全扫描,速度非常慢,这个index级别比较range还低,与全表扫描是小巫见大巫。

  1. 【推荐】建组合索引的时候,区分度最高的在最左边。

说明:存在非等号和等号混合时,在建索引时,请把等号条件的列前置。如:where c>? and d=? 那么即使c的区分度更高,也必须把d放在索引的最前列,即索引idx_d_c

正例:如果where a=? and b=? ,如果a列的几乎接近于唯一值,那么只需要单建idx_a索引即可。

  1. 【推荐】防止因字段类型不同造成的隐式转换,导致索引失效。

  2. 【参考】创建索引时避免有如下极端误解:

宁滥勿缺。认为一个查询就需要建一个索引。

宁缺勿滥。认为索引会消耗空间、严重拖慢更新和新增速度。

抵制惟一索引。认为业务的惟一性一律需要在应用层通过“先查后插”方式解决。

channel 题目

  1. 下面代码会触发异常吗
func main() {
    runtime.GOMAXPROCS(1)
    int_chan := make(chan int, 1)
    string_chan := make(chan string, 1)
    int_chan <- 1
    string_chan <- "hello"
    select {
    case value := <-int_chan:
        fmt.Println(value)
    case value := <-string_chan:
        panic(value)
    }
}

解析:可能触发异常,也可能不会触发异常。

  1. channel特性说明
  • 给一个 nil channel 发送数据,造成永远阻塞;

  • 从一个 nil channel 接收数据,造成永远阻塞;

  • 给一个已经关闭的 channel 发送数据,引起 panic

  • 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值,缓冲区不为空,返回已缓冲数据;

  • 无缓冲的channel是同步的,而有缓冲的channel是非同步的。

  1. 下面代码输出什么
func main() {
    ch := make(chan int, 100)
    // A
    go func() {              
        for i := 0; i < 10; i++ {
            ch <- i
        }
    }()
    // B
    go func() {
        for {
            a, ok := <-ch
            if !ok {
                fmt.Println("close")
                return
            }
            fmt.Println("a: ", a)
        }
    }()
    close(ch)
    fmt.Println("ok")
    time.Sleep(time.Second * 10)
}

解析:当 A 协程还没起时,主协程已经将 channel 关闭了,当 A 协程往关闭的 channel 发送数据时会 panicpanic: send on closed channel

  1. 下面的代码有什么问题
func Stop(stop <-chan bool) {
    close(stop)
}

解析:有方向的 channel 不可以被关闭。

  1. 下面代码输出什么
func main() {
    var ch chan int
    select {
    case v, ok := <-ch:
        println(v, ok)
    default:
        println("default") 
    }
}

输出:default

解析:chnil,读写都会阻塞。

  1. 下面的代码输出什么
var o = fmt.Print

func main() {
    c := make(chan int, 1)
    for range [3]struct{}{} {
        select {
        default:
            o(1)
        case <-c:
            o(2)
            c = nil
        case c <- 1:
            o(3)
        }
    }
}

输出:321

解析:第一次循环,写操作已经准备好,执行 o(3),输出 3;第二次,读操作准备好,执行 o(2),输出 2 并将 c 赋值为 nil;第三次,由于 cnil,走的是 default 分支,输出 1

  1. 下面的代码有什么问题
type data struct {
    sync.Mutex
}

func (d data) test(s string)  {
    d.Lock()
    defer d.Unlock()

    for i:=0;i<5 ;i++  {
        fmt.Println(s,i)
        time.Sleep(time.Second)
    }
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2)
    var d data

    go func() {
        defer wg.Done()
        d.test("read")
    }()

    go func() {
        defer wg.Done()
        d.test("write")
    }()

    wg.Wait()
}

解析:锁失效。将 Mutex 作为匿名字段时,相关的方法必须使用指针接收者,否则会导致锁机制失效。

修复代码:

type data struct {
    *sync.Mutex     // 嵌入 *Mutex 来避免复制的问题,但是需要初始化
}

func (d data) test(s string) {    // 值方法
    d.Lock()
    defer d.Unlock()

    for i := 0; i < 5; i++ {
        fmt.Println(s, i)
        time.Sleep(time.Second)
    }
}

func main() {

    var wg sync.WaitGroup
    wg.Add(2)

    d := data{new(sync.Mutex)}   // 初始化

    go func() {
        defer wg.Done()
        d.test("read")
    }()

    go func() {
        defer wg.Done()
        d.test("write")
    }()

    wg.Wait()
}

for ...range 题目

  1. for range和数组的取值方式
func main() {
    slice := []int{0,1,2,3}
    m := make(map[int]*int)
    for key,val := range slice {
        m[key] = &val
    }

    for k,v := range m {
        fmt.Println(k,"->",*v)
    }
}

输出:

0 -> 3
1 -> 3
2 -> 3
3 -> 3

解析:for range循环时候,会创建每个元素的副本,而不是每个元素的引用,所以m[key] = &val取得的是变量val的地址,所以最后map中的所有元素的值都是变量val的地址,因为最后val被赋值为3,所以输出都是3

正确的写法:

func main() {

    slice := []int{0,1,2,3}
    m := make(map[int]*int)

    for key,val := range slice {
        value := val
        m[key] = &value
    }
    for k,v := range m {
        fmt.Println(k,"===>",*v)
    }
}
  1. 下面这段代码输出什么
func main() {
    m := map[int]string{0:"zero",1:"one"}
    for k,v := range m {
        fmt.Println(k,v)
    }
}

输出:

0 zero
1 one
// 或者
1 one
0 zero

解析:map 的输出是无序的。

  1. 下面这段代码能否正常结束
func main() {
    v := []int{1, 2, 3}
    for i := range v {
        v = append(v, i)
    }
}

解析:不会出现死循环,能正常结束。循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数。

  1. 下面这段代码输出什么
func main() {

    var m = [...]int{1, 2, 3}

    for i, v := range m {
        go func() {
            fmt.Println(i, v)
        }()
    }

    time.Sleep(time.Second * 3)
}

输出:

2 3
2 3
2 3

解析:for range 使用短变量声明 (:=) 的形式迭代变量,需要注意的是,变量 iv 在每次循环体中都会被重用,而不是重新声明。

各个 goroutine 中输出的 i、v 值都是 for range 循环结束后的 iv 最终值,而不是各个 goroutine 启动时的iv值。可以理解为闭包引用,使用的是上下文环境的值。

两种可行的修正方法:

a. 使用函数传递

for i, v := range m {
    go func(i,v int) {
        fmt.Println(i, v)
    }(i,v)
}

b. 使用临时变量保留当前值

for i, v := range m {
    i := i // 这里的 := 会重新声明变量,而不是重用
    v := v
    go func() {
        fmt.Println(i, v)
    }()
}
  1. 下面这段代码输出什么
func main() {
    var a = [5]int{1, 2, 3, 4, 5}
    var r [5]int

    for i, v := range a {
        if i == 0 {
            a[1] = 12
            a[2] = 13
        }
        r[i] = v
    }
    fmt.Println("r = ", r)
    fmt.Println("a = ", a)
}

输出:

r =  [1 2 3 4 5]
a =  [1 12 13 4 5]

解析:range 表达式是副本参与循环,就是说例子中参与循环的是 a 的副本,而不是真正的 a

修复写法:

func main() {
    var a = [5]int{1, 2, 3, 4, 5}
    var r [5]int

    for i, v := range &a {
        if i == 0 {
            a[1] = 12
            a[2] = 13
        }
        r[i] = v
    }
    fmt.Println("r = ", r)
    fmt.Println("a = ", a)
}

输出:

r =  [1 12 13 4 5]
a =  [1 12 13 4 5]
  1. 下面这段代码输出什么
func main() {
    var a = []int{1, 2, 3, 4, 5}
    var r [5]int

    for i, v := range a {
        if i == 0 {
            a[1] = 12
            a[2] = 13
        }
        r[i] = v
    }
    fmt.Println("r = ", r)
    fmt.Println("a = ", a)
}

输出:

r =  [1 12 13 4 5]
a =  [1 12 13 4 5]

解析:这的 a 是一个切片,切片在 Go 的内部结构有一个指向底层数组的指针,当 range 表达式发生复制时,副本的指针依旧指向原底层数组,所以对切片的修改都会反应到底层数组上,所以通过 v 可以获得修改后的数组元素。

  1. 下面的代码输出什么
type Foo struct {
    bar string
}

func main() {
    s1 := []Foo{
        {"A"},
        {"B"},
        {"C"},
    }
    s2 := make([]*Foo, len(s1))
    for i, value := range s1 {
        s2[i] = &value
    }
    fmt.Println(s1[0], s1[1], s1[2])
    fmt.Println(s2[0], s2[1], s2[2])
}

输出:

{A} {B} {C}
&{C} &{C} &{C}

解析:for range 使用短变量声明(:=)的形式迭代变量时,变量 ivalue 在每次循环体中都会被重用,而不是重新声明。所以 s2 每次填充的都是临时变量 value 的地址,而在最后一次循环中,value 被赋值为{c}。因此,s2输出的时候显示出了三个&{c}`。

  1. 下面代码里的 counter 的输出值
func main() {

    var m = map[string]int{
        "A": 21,
        "B": 22,
        "C": 23,
    }
    counter := 0
    for k, v := range m {
        if counter == 0 {
            delete(m, "A")
        }
        counter++
        fmt.Println(k, v)
    }
    fmt.Println("counter is ", counter)
}

输出:23

解析:for range map是无序的。

  1. 下面代码输出什么
func main() {
    x := []string{"a", "b", "c"}
    for v := range x {
        fmt.Print(v)
    }
}

输出:0 1 2

  1. 下面代码输出什么
func main() {
    var k = 9
    for k = range []int{} {}
    fmt.Println(k)

    for k = 0; k < 3; k++ {
    }
    fmt.Println(k)


    for k = range (*[3]int)(nil) {
    }
    fmt.Println(k)
}

输出:9 3 2

map 题目

  1. 下面的代码输出是什么
// a
type person struct {  
    name string
}

func main() {  
    var m map[person]int
    p := person{"mike"}
    fmt.Println(m[p])
}

// b
func main() {  
    s := make(map[string]int)
    delete(s, "h")
    fmt.Println(s["h"])
}

输出:都是0

解析:打印一个 map 中不存在的值时,返回元素类型的零值。删除 map 不存在的键值对时,不会报错。

  1. 下面代码中 A, B 两处应该怎么修改才能顺利编译
func main() {
    var m map[string]int        //A
    m["a"] = 1
    if v := m["b"]; v != nil {  //B
        fmt.Println(v)
    }
}

解析:A处只进行了map声明,不能直接赋值,需要使用make()或者字面量的方式直接初始化map,所以应该改为m := make(map[string]int);B处改为v,k := m["b"]keyb 的元素不存在的时候,v 会返回值类型对应的零值,k 返回 false

  1. 下面代码能正常通过编译吗
type Math struct {
    x, y int
}

var m = map[string]Math{
    "foo": Math{2, 3},
}

func main() {
    m["foo"].x = 4
    fmt.Println(m["foo"].x)
}

解析:编译错误。对于类似 X = Y 的赋值操作,必须知道 X 的地址,才能够将 Y 的值赋给 X,但 go 中的 mapvalue 本身是不可寻址的。

有两个解决方法:

  • 使用临时变量
type Math struct {
    x, y int
}

var m = map[string]Math{
    "foo": Math{2, 3},
}

func main() {
    tmp := m["foo"]
    tmp.x = 4
    m["foo"] = tmp
    fmt.Println(m["foo"].x)
}
  • 修改数据结构
type Math struct {
    x, y int
}

var m = map[string]*Math{
    "foo": &Math{2, 3},
}

func main() {
    m["foo"].x = 4
    fmt.Println(m["foo"].x)
    fmt.Printf("%#v", m["foo"])   // %#v 格式化输出详细信息
}
  1. 下面这段代码存在什么问题
type Param map[string]interface{}

type Show struct {
    *Param
}

func main() {
    s := new(Show)
    s.Param["day"] = 2
}

解析:map 需要初始化才能使用;指针不支持索引。

修复代码如下:

func main() {
    s := new(Show)
    p := make(Param)
    p["day"] = 2
    s.Param = &p
    tmp := *s.Param
    fmt.Println(tmp["day"])
}
  1. 下面代码有什么问题
func main() {
    m := make(map[string]int,2)
    cap(m) 
}

解析:使用 cap() 获取 map 的容量。

使用 make 创建 map 变量时可以指定第二个参数,不过会被忽略;

cap() 函数适用于数组、数组指针、slicechannel,不适用于 map,可以使用 len() 返回 map 的元素个数。

  1. 下面代码输出什么
type T struct {
    n int
}

func main() {
    m := make(map[int]T)
    m[0].n = 1
    fmt.Println(m[0].n)
}

解析:编译错误。map[key]structstruct 是不可寻址的,所以无法直接赋值。

正则表达式的(.*)和(.*?)

(.*)是贪婪匹配,会把满足正则的尽可能多的往后匹配;

(.*?)是非贪婪匹配,会把满足正则的尽可能少匹配。

Keep-Alive模式下客户端判断请求所得到的响应数据已经接收完成的方式

HTTP Keep-Alive模式,客户端如何判断服务器的数据已经发生完成

使用消息首部字段Conent-LengthConent-Length表示实体内容长度,客户端(服务器)可以根据这个值来判断数据是否接收完成。

使用消息首部字段Transfer-Encoding:当客户端向服务器请求一个静态页面或者一张图片时,服务器可以很清楚的知道内容大小,然后通过Content-length消息首部字段告诉客户端需要接收多少数据。

但是如果是动态页面等时,服务器是不可能预先知道内容大小,这时就可以使用Transfer-Encodingchunk模式来传输数据了。即如果要一边产生数据,一边发给客户端,服务器就需要使用Transfer-Encoding: chunked这样的方式来代替Content-Length

chunk编码将数据分成一块一块的发生。Chunked编码将使用若干个Chunk串连而成,由一个标明长度为0chunk标示结束。每个Chunk分为头部和正文两部分,头部内容指定正文的字符总数(十六进制的数字)和数量单位(一般不写),正文部分就是指定长度的实际内容,两部分之间用回车换行(CRLF)隔开。在最后一个长度为0Chunk中的内容是称为footer的内容,是一些附加的Header信息(通常可以直接忽略)。

自己实现一个字典

import collections

class MyDict(collections.UserDict):
    def __missing__(self, key):
        if isinstance(key, str):
            raise KeyError(key)
        return self[str(key)]

    def __contains__(self, key):
        return str(key) in self.data

    def __setitem__(self, key, item):
        self.data[str(key)] = item

    def __getattr__(self, key):
        return self.data[str(key)]

Python3.8海象操作符

旧:

a = [1, 2, 3, 4, 5]
n = len(a)
if n > 4:
    print(n)

错误:

a = [1, 2, 3, 4, 5]
if (n = len(a)) > 4: # SyntaxError: invalid syntax
    print(n)

无法在if语句中进行赋值比较。

新:

新语法通过:=让表达式内部进行变量赋值变成可能。

a = [1, 2, 3, 4, 5]
if (n := len(a)) > 4:
    print(n)

上下文管理器

上下文即程序运行的环境状态。

上下文管理器的常用于一些资源的操作,需要在资源的获取与释放相关的操作。

Python的上下文管理协议则是__enter__()__exit__()

any()和all()方法

any():只要迭代器中有一个元素为真就为真;

all():迭代器中所有的判断项返回都是真,结果才为真。

super()原理

def super(cls, inst):
    mro = inst.__class__.mro() # inst负责生成MRO的list
    return mro[mro.index(cls) + 1] # 定位到当前cls在MRO中的位置,然后后移一位,返回此处的类

MySQL 查询性能优化

  • EXPLAIN使用

MySQL 提供了一个 EXPLAIN 命令,它可以对 SELECT 语句进行分析,并输出 SELECT 执行的详细信息,以供开发人员针对性优化。

mysql> explain select * from user_info where id = 2\G
*************************** 1. row ***************************
           id: 1 # SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符.
  select_type: SIMPLE # SELECT 查询的类型.
        table: user_info # 查询的是哪个表
   partitions: NULL # 匹配的分区
         type: const # join 类型
possible_keys: PRIMARY # 此次查询中可能选用的索引
          key: PRIMARY # 此次查询中确切使用到的索引
      key_len: 8
          ref: const # 哪个字段或常数与 key 一起被使用
         rows: 1 # 显示此查询一共扫描了多少行,这个是一个估计值
     filtered: 100.00 # 表示此查询条件所过滤的数据的百分比
        Extra: NULL # 额外的信息
1 row in set, 1 warning (0.00 sec)

比较重要的字段:

  1. select_type 表示了查询的类型, 它的常用取值有:

SIMPLE:表示此查询不包含 UNION 查询或子查询;

PRIMARY:表示此查询是最外层的查询;

UNION:表示此查询是 UNION 的第二或随后的查询;

DEPENDENT UNIONUNION 中的第二个或后面的查询语句, 取决于外面的查询;

UNION RESULTUNION 的结果;

SUBQUERY:子查询中的第一个 SELECT

DEPENDENT SUBQUERY:子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果。

  1. table,表示查询涉及的表或衍生表。

  2. type 字段,判断此次查询是全表扫描还是索引扫描等。常用取值有:

system:表中只有一条数据,这个类型是特殊的 const 类型;

const:针对主键或唯一索引的等值查询扫描,最多只返回一行数据。const 查询速度非常快, 因为它仅仅读取一次即可。

eq_ref:此类型通常出现在多表的 join 查询,表示对于前表的每一个结果,都只能匹配到后表的一行结果。并且查询的比较操作通常是 =, 查询效率较高。

ref:此类型通常出现在多表的 join 查询,针对于非唯一或非主键索引,或者是使用了最左前缀规则索引的查询。

range:表示使用索引范围查询,通过索引字段范围获取表中部分数据记录。这个类型通常出现在 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN() 操作中。

typerange 时, 那么 EXPLAIN 输出的 ref 字段为 NULL,并且 key_len 字段是此次查询中使用到的索引的最长的那个。

index:表示全索引扫描(full index scan),和 ALL 类型类似,只不过 ALL 类型是全表扫描,而 index 类型则仅仅扫描所有的索引,而不扫描数据。

index 类型通常出现在:所要查询的数据直接在索引树中就可以获取到,而不需要扫描数据。当是这种情况时,Extra 字段 会显示 Using index

ALL:表示全表扫描, 这个类型的查询是性能最差的查询之一。

不同的 type 类型的性能关系如下:ALL < index < range ~ index_merge < ref < eq_ref < const < system

  1. keyMySQL 在当前查询时所真正使用到的索引。

  2. rowsMySQL 查询优化器根据统计信息,估算 SQL 要查找到结果集需要扫描读取的数据行数。

  • 避免向数据库请求不需要的数据

在访问数据库时,应该只请求需要的行和列。请求多余的行和列会消耗 MySQL 服务器的CPU和内存资源,并增加网络开销。

例如在处理分页时,应该使用 LIMIT 限制 MySQL 只返回一页的数据,而不是向应用程序返回全部数据后,再由应用程序过滤不需要的行。

当一行数据被多次使用时可以考虑将数据行缓存起来,避免每次使用都要到 MySQL 查询。

避免使用 SELECT * 这种方式进行查询,应该只返回需要的列。

  • 查询数据的方式

查询数据的方式有全表扫描、索引扫描、范围扫描、唯一索引查询、常数引用等。这些查询方式,速度从慢到快,扫描的行数也是从多到少。可以通过EXPLAIN语句中的type列反应查询采用的是哪种方式。

通常可以通过添加合适的索引改善查询数据的方式,使其尽可能减少扫描的数据行,加快查询速度。

例如,当发现查询需要扫描大量的数据行但只返回少数的行,那么可以考虑使用覆盖索引,即把所有需要用到的列都放到索引中。这样存储引擎无须回表获取对应行就可以返回结果了。

  • 分解大的查询

可以将一个大查询切分成多个小查询执行,每个小查询只完成整个查询任务的一小部分,每次只返回一小部分结果。

删除旧的数据是一个很好的例子。如果只用一条语句一次性执行一个大的删除操作,则可能需要一次锁住很多数据,占满整个事务日志,耗尽系统资源、阻塞很多小的但重要的查询。

将一个大的删除操作分解成多个较小的删除操作可以将服务器上原本一次性的压力分散到多次操作上,尽可能小地影响 MySQL 性能,减少删除时锁的等待时间。同时也减少了 MySQL 主从复制的延迟。

另一个例子是分解关联查询,即对每个要关联的表进行单表查询,然后将结果在应用程序中进行关联。下面的这个查询:

SELECT * FROM tag
    JOIN tag_post ON tag_post.tag_id=tag.id    JOIN post ON tag_post.post_id=post.idWHERE tag.tag = 'mysql';

可以分解成下面这些查询来代替:

SELECT * FROM tag WHERE tag = 'mysql';SELECT * FROM tag_post WHERE tag_id = 1234;SELECT * FROM post WHERE post.id in (123,456,567,9098,8904);

将一个关联查询拆解成多个单表查询有如下优点:

让缓存的效率更高。如果缓存的是关联查询的结果,那么其中的一个表发生变化,整个缓存就失效了。而拆分后,如果只是某个表很少的改动,并不会破坏所有的缓存;

可以减少锁的竞争;

更容易对数据库进行拆分,更容易做到高性能和可扩展;

查询本身的效率也有可能会有所提升。例如上面用IN()代替关联查询比随机的关联更加高效。

  • 优化MIN()MAX()

添加索引可以优化MIN()MAX()表达式。

例如,要找到某一列的最小值,只需要查询对应B Tree索引的最左端的记录即可。

类似的,如果要查询列中的最大值,也只需要读取B Tree索引的最后一条记录

对于这种查询,EXPLAIN中可以看到Select tables optimized away,表示优化器已经从执行计划中移除了该表,并以一个常数取而代之。

  • IN()取代OR

MySQL 中,IN()先将自己列表中的数据进行排序,然后通过二分查找的方式确定列的值是否在IN()的列表中,这个时间复杂度是O(logn)

如果换成OR操作,则时间复杂度是O(n)

所以,对于IN()的列表中有大量取值的时候,用IN()替换OR操作将会更快。

  • 优化关联查询

MySQL 中,任何一个查询都可以看成是一个关联查询,即使只有一个表的查询也是如此。

MySQL 对任何关联都执行嵌套循环的关联操作,例如对于下面的SQL语句:

SELECT tbl1.col1, tbl2.col2 FROM tbl1 INNER JOIN tbl2 USING(col3) WHERE tbl1.col1 IN(5, 6);

下面的伪代码表示 MySQL 将如何执行这个查询:

out_iter = iterator over tbl1 where col1 IN(5,6)
outer_row = out_iter.next # 先从第一个表中取出符合条件的所有行

# 在while循环中遍历第一个表结果集的每一行
while outer_row
    # 对于第一个表结果集中的每一行,在第二个表中找出符合条件的所有行
    inner_iter = iterator over tbl2 where col3 = outer_row.col3
    inner_row = inner_iter.next
    while inner_row
        # 将第一个表的结果列和第二个表的结果列拼装在一起作为结果输出
        output[outer_row.col1, inner_row.col2]
        inner_row = inner_iter.next
    end
    # 回溯,再根据第一个表结果集的下一行,继续上面的过程
    outer_row = outer_iter.next
end

对于单表查询,那么只需要完成上面外层的基本操作。

优化关联查询,要确保ON或者USING子句中的列上有索引,并且在建立索引时需要考虑到关联的顺序。通常来说,只需要在关联顺序中的第二个表的相应列上创建索引。

例如,当表A和表B用列c关联的时候,假设关联的顺序是BA,那么就不需要在B表的c列上建立索引。没有用到的索引只会带来额外的负担。

此外,确保任何的GROUP BYORDER BY中的表达式只涉及到一个表中的列,这样才能使用索引来优化这个过程。

  • 临时表(派生表)的概念

上面提到在 MySQL 中,任何一个查询实质上都是一个关联查询。那么对于子查询或UNION查询是如何实现关联操作的呢。

对于UNION查询, MySQL 先将每一个单表查询结果放到一个临时表中,然后再重新读出临时表数据来完成UNION查询。MySQL 读取结果临时表和普通表一样,也是采用的关联方式。

当遇到子查询时,先执行子查询并将结果放到一个临时表中,然后再将这个临时表当做一个普通表对待。

MySQL 的临时表是没有任何索引的,在编写复杂的子查询和关联查询的时候需要注意这一点。

  • 排序优化

应该尽量让MySQL使用索引进行排序。当不能使用索引生成排序结果的时候,MySQL需要自己进行排序。

如果数据量小于“排序缓冲区”的大小,则MySQL使用内存进行“快速排序”操作。

如果数据量太大超过“排序缓冲区”的大小,那么MySQL只能采用文件排序,而文件排序的算法非常复杂,会消耗很多资源。

无论如何排序都是一个成本很高的操作,所以从性能角度考虑,应尽可能避免排序。所以让MySQL根据索引构造排序结果非常的重要。

  • 子查询优化

MySQL的子查询实现的非常糟糕。最糟糕的一类查询是WHERE条件中包含IN()的子查询语句。

应该尽可能用关联替换子查询,可以提高查询效率。

  • 优化COUNT()查询

COUNT()有两个不同的作用:统计某个列值的数量,即统计某列值不为NULL的个数;统计行数。

当使用COUNT(*)时,统计的是行数,它会忽略所有的列而直接统计所有的行数。而在括号中指定了一个列的话,则统计的是这个列上值不为NULL的个数。

可以考虑使用索引覆盖扫描或增加汇总表对COUNT()进行优化。

  • 优化LIMIT分页

处理分页会使用到LIMIT,当翻页到非常靠后的页面的时候,偏移量会非常大,这时LIMIT的效率会非常差。

例如对于LIMIT 10000,20这样的查询,MySQL需要查询10020条记录,将前面10000条记录抛弃,只返回最后的20条。这样的代价非常高,如果所有的页面被访问的频率都相同,那么这样的查询平均需要访问半个表的数据。

优化此类分页查询的一个最简单的办法就是尽可能地使用索引覆盖扫描,而不是查询所有的列。然后根据需要与原表做一次关联操作返回所需的列。对于偏移量很大的时候,这样的效率会提升非常大。

考虑下面的查询:

SELECT film_id, description FROM sakila.film ORDER BY title LIMIT 50, 5;

如果这个表非常大,那么这个查询最好改写成下面的这样子:

SELECT film.film_id, film.description FROM sakila.film INNER JOIN (SELECT film_id FROM sakila.film ORDER BY title LIMIT 50, 5) AS lim USING (film_id);

注意优化中关联的子查询,因为只查询film_id一个列,数据量小,使得一个内存页可以容纳更多的数据,这让MySQL扫描尽可能少的页面。在获取到所需要的所有行之后再与原表进行关联以获得需要的全部列。

LIMIT的优化问题,其实是OFFSET的问题,它会导致MySQL扫描大量不需要的行然后再抛弃掉。

可以借助“书签”的**记录上次取数据的位置,那么下次就可以直接从该“书签”记录的位置开始扫描,这样就避免了使用OFFSET

可以把主键当做“书签”使用,例如下面的查询:

SELECT * FROM sakila.rental ORDER BY rental_id DESC LIMIT 20;

假设上面的查询返回的是主键为1604916030的租借记录,那么下一页查询就可以直接从16030这个点开始:

SELECT * FROM sakila.rental WHERE rental_id < 16030 ORDER BY rental_id DESC LIMIT 20;

该技术的好处是无论翻页到多么后面,其性能都会很好。

此外,也可以用关联到一个冗余表的方式提高LIMIT的性能,冗余表只包含主键列和需要做排序的数据列。

  • 优化UNION查询

除非确实需要服务器消除重复的行,否则一定要使用UNION ALL

如果没有ALL关键字,MySQL会给临时表加上DISTINCT选项,这会导致对整个临时表的数据做唯一性检查。这样做的代价非常高。

HTTPS和HTTP的区别

    1. https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
    1. http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
    1. httphttps使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443
    1. http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

array, slice 题目

  1. 判断下面代码能否通过编译
func main() {
   s1 := []int{123}
   s2 := []int{45}
   s1 = append(s1s2)
   fmt.Println(s1)
}

解析:append的第二个参数不能直接使用slice,需要使用...操作符,将一个切片追加到另一个切片上,或者直接跟上元素append(s1, 1, 2, 3)

  1. 以下代码的输出是什么
func main() {
	a := []int{7, 8, 9}
	fmt.Printf("%+v\n", a)
	ap(a)
	fmt.Printf("%+v\n", a)
	app(a)
	fmt.Printf("%+v\n", a)

}

func ap(a []int) {
	a = append(a, 10)
}

func app(a []int) {
	a[0] = 1
}

输出:

[7, 8, 9]
[7, 8, 9]
[1, 8, 9]

解析:append导致底层数组重新分配了内存,ap中的a的底层数组与外部的a不是同一个,并未改变外部的。

  1. 切片的截取操作符
package main

import (  
    "fmt"
)

func main() {  
    a := [5]int{1, 2, 3, 4, 5}
    t := a[3:4:4]
    fmt.Println(t[0])
}

解析:假设一个底层数组a的大小是n,截取切片的操作是a[i: j],则获得的切片长度为j - i,容量为n - i。如果截取操作符存在第三个参数k,其是用来限制新切片容量的。

  1. 下面代码下划线处可以填入哪个选项
func main() {
    var s1 []int
    var s2 = []int{}
    if __ == nil {
        fmt.Println("yes nil")
    }else{
        fmt.Println("no nil")
    }
}

答案:s1

解析:nil 切片和 nil 相等,一般用来表示一个不存在的切片;空切片和 nil 不相等,表示一个空的集合。

  1. 两种切片声明
  • var a []int声明nil切片;

  • a := []int{}声明长度和容量都为0的空切片。

  1. 切片 abc 的长度和容量分别是多少?
func main() {
    s := [3]int{1, 2, 3}
    a := s[:0]
    b := s[:2]
    c := s[1:2:cap(s)]
}

答案:abc 的长度和容量分别是 0 32 31 2

解析:假设一个底层数组a的大小是n,截取切片的操作是a[i: j],则获得的切片长度为j - i,容量为n - i。如果截取操作符存在第三个参数k,其是用来限制新切片容量的。

  1. 下面这段代码输出什么
func main() {

    s1 := []int{1, 2, 3}
    s2 := s1[1:]
    s2[1] = 4
    fmt.Println(s1)
    s2 = append(s2, 5, 6, 7)
    fmt.Println(s1)
}

输出:

[1 2 4]
[1 2 4]

解析:

Go 中切片底层的数据结构是数组。当使用 s1[1:] 获得切片 s2,和 s1 共享同一个底层数组,这会导致 s2[1] = 4 语句影响 s1

append 操作会导致底层数组扩容,生成新的数组,因此追加数据后的 s2 不会影响 s1

但是为什么对 s2 赋值后影响的却是 s1 的第三个元素呢?这是因为切片 s2 是从数组的第二个元素开始,s2 索引为 1 的元素对应的是 s1 索引为 2 的元素。

  1. 下面的代码有什么问题
func main() {
    fmt.Println([...]int{1} == [2]int{1})
    fmt.Println([]int{1} == []int{1})
}

解析:go 中不同类型是不能比较的,而数组长度是数组类型的一部分,所以 […]int{1} 和 [2]int{1} 是两种不同的类型,不能比较;切片是不能比较的。

  1. 下面这段代码输出什么
func change(s ...int) {
    s = append(s,3)
}

func main() {
    slice := make([]int,5,5)
    slice[0] = 1
    slice[1] = 2
    change(slice...)
    fmt.Println(slice)
    change(slice[0:2]...)
    fmt.Println(slice)
}

输出:

[1 2 0 0 0]
[1 2 3 0 0]

解析:

Go 提供的语法糖...,可以将 slice 传进可变函数,不会创建新的切片。

第一次调用 change() 时,append() 操作使切片底层数组发生了扩容,原 slice 的底层数组不会改变;

第二次调用 change() 函数时,获得一个新的切片,它的底层数组和原切片底层数组是重合的,不过长度、容量分别是 2、5,所以在 change() 函数中对底层数组的修改会影响到原切片。

  1. 下面这段代码输出什么
var x = []int{2: 2, 3, 0: 1}

func main() {
    fmt.Println(x)
}

输出:[1 0 2 3]

解析:字面量初始化切片时候,可以指定索引,没有指定索引的元素会在前一个索引基础之上加一,所以输出 [1 0 2 3],而不是 [1 3 2]

  1. 下面的这段代码输出是什么
func main() {
    a := [2]int{5, 6}
    b := [3]int{5, 6}
    if a == b {
        fmt.Println("equal")
    } else {
        fmt.Println("not equal")
    }
}

编译出错,Go中的数组是值类型,可以进行比较,同时数组长度也是数组类型的组成部分,并且Go语言是强类型语言,所以上面的a, b不能进行比较,编译出错。

  1. 下面的这段代码输出什么
package main

import "fmt"

func main() {
    s := make([]int, 3, 9)
    fmt.Println(len(s)) 
    s2 := s[4:8]
    fmt.Println(len(s2)) 
}

输出:3 4

解析:从一个基础切片派生出的子切片的长度可能大于基础切片的长度。

  1. 下面哪一行代码会 panic
package main

func main() {
  var m map[int]bool // nil
  _ = m[123]
  var p *[5]string // nil
  for range p {
    _ = len(p)
  }
  var s []int // nil
  _ = s[:]
  s, s[0] = []int{1, 2}, 9
}

解析:s, s[0] = []int{1, 2}, 9 行。因为左侧的 s[0] 中的 snil

  1. 下面这段代码输出什么
func main() {
    var k = 1
    var s = []int{1, 2}
    k, s[k] = 0, 3
    fmt.Println(s[0] + s[1])
}

输出:4

解析:多重赋值按照以下的两个步骤进行:计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式;赋值。

gevent底层实现原理,协程上下文切换实现

异步IO

谈谈Python协程技术的演进

gevent是基于greenlet实现。通过栈的复制切换来实现不同协程之间的切换。

Python基于yield实现协程。

  1. 事件循环 (event loop)。事件循环需要实现两个功能,一是顺序执行协程代码;二是完成协程的调度,即一个协程“暂停”时,决定接下来执行哪个协程。

  2. 协程上下文的切换。基本上Python 生成器的 yield 已经能完成切换,Python3中还有特定语法支持协程切换。

Python代码执行原理

2019.03.27

运行Python文件程序的时候,Python解释器将源代码转换为字节码,然后再由Python解释器来执行这些字节码。

词法分析 --> 语法分析 --> 生成字节码(.pyc文件) --> 执行

Pythonint是怎么实现的

PyIntObject结构体表示:

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

PyObject_HEAD宏中定义的两个属性分别是:

int ob_refcnt;        
struct _typeobject *ob_type;

这两个属性是所有Python对象固有的:

  • ob_refcnt:对象的引用计数,与Python的内存管理机制有关,它实现了基于引用计数的垃圾收集机制
  • ob_type:用于描述Python对象的类型信息。

PyIntObject就是一个对C语言中long类型的数值的扩展。

出于性能考虑,对于小整数,Python使用小整数对象池small_ints缓存了[-5,257)之间的整数,该范围内的整数在Python系统中是共享的。

而超过该范围的整数即使值相同,但对象不一定是同一个,如下所示:当ab的值都是10000,但并不是同一个对象,而值为1的时候,ab属于同一个对象。

>>> a = 1
>>> b = 1
>>> a is b
True
>>> a = 10000
>>> b = 10000
>>> a is b
False

对于超出了[-5, 257)之间的其他整数,Python同样提供了专门的缓冲池,供这些所谓的大整数使用,避免每次使用的时候都要不断的malloc分配内存带来的效率损耗。这块内存空间就是PyIntBlock

struct _intblock {

    struct _intblock *next;
    PyIntObject objects[N_INTOBJECTS];
};
typedef struct _intblock PyIntBlock;

static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

这些内存块(PyIntBlock)通过一个单向链表组织在一起,表头是block_list,表头始终指向最新创建的PyIntBlock对象。

PyIntBlock有两个属性:nextobjectsnext指针指向下一个PyIntBlock对象,objects是一个PyIntObject数组(最终会转变成单向链表),它是真正用于存储被缓存的PyIntObjet对象的内存空间。

free_list单向链表是所有PyIntBlock内存块中空闲的内存。所有空闲内存通过一个链表组织起来的好处就是在Python需要新的内存来存储新的PyIntObject对象时,能够通过free_list快速获得所需的内存。

创建一个整数对象时,如果它在小整数范围内,就直接从小整数缓冲池中直接返回,如果不在该范围内,就开辟一个大整数缓冲池内存空间:

[intobject.c]
PyObject* PyInt_FromLong(long ival)
{
     register PyIntObject *v; 
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
     //[1] :尝试使用小整数对象池
     if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
        v = small_ints[ival + NSMALLNEGINTS];
        Py_INCREF(v);
        return (PyObject *) v;
    }
#endif
    //[2] :为通用整数对象池申请新的内存空间
    if (free_list == NULL) {
        if ((free_list = fill_free_list()) == NULL)
            return NULL;
    }
    //[3] : (inline)内联PyObject_New的行为
    v = free_list;
    free_list = (PyIntObject *)v->ob_type;
    PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *) v;
}

fill_free_list就是创建大整数缓冲池内存空间的逻辑,该函数返回一个free_list链表,当整数对象ival创建成功后,free_list表头就指向了v->ob_typeob_type不是所有Python对象中表示类型信息的字段吗?怎么在这里作为一个连接指针呢?这是Python在性能与代码优雅之间取中庸之道,对名称的滥用,放弃了对类型安全的坚持。把它理解成指向下一个PyIntObject的指针即可。

[intobject.c]
static PyIntObject* fill_free_list(void)
{
    PyIntObject *p, *q;
    // 申请大小为sizeof(PyIntBlock)的内存空间
    // block list始终指向最新创建的PyIntBlock
    p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
    ((PyIntBlock *)p)->next = block_list;
    block_list = (PyIntBlock *)p;

    //将PyIntBlock中的PyIntObject数组(objects)转变成单向链表
    p = &((PyIntBlock *)p)->objects[0];
    q = p + N_INTOBJECTS;
    while (--q > p)
        // ob_type指向下一个未被使用的PyIntObjectq->ob_type = (struct _typeobject *)(q-1);
    q->ob_type = NULL;
    return p + N_INTOBJECTS - 1;
}

不同的PyIntBlock里面的空闲的内存是怎样连接起来构成free_list的呢?这个秘密放在了整数对象垃圾回收的时候,在PyIntObject对象的tp_dealloc操作中可以看到:

[intobject.c]
static void int_dealloc(PyIntObject *v)
{
    if (PyInt_CheckExact(v)) {
        v->ob_type = (struct _typeobject *)free_list;
        free_list = v;
    }
    else
        v->ob_type->tp_free((PyObject *)v);
}

原来PyIntObject对象销毁时,它所占用的内存并不会释放,而是继续被Python使用,进而将free_list表头指向了这个要被销毁的对象上。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.