https://pic4.zhimg.com/v2-e263475646652726731e13e44b5d2479_im.jpg

Linux静态路由表

查看路由表 route -n netstat -rn Destination是0.0.0.0表示是默认路由,只有其他路由都匹配不到时才会走这个路由 Gateway为0.0.0.0表示没有网关,静态路由表中的网关只能配置一个,配置多个则只会使用其中一个 ​ 路由转发流程 工具目标IP个子网掩码匹配静态路由表中的条目 如果匹配到了则将数据包发送到路由表项对应的网卡上 如果路由表项都匹配不成功则走默认路由,也就是Destinaion为0.0.0.0的路由表项,因为默认路由配置了网关所以数据包会从网关发送出去,而在局域网内的通讯则不会走网关 比如我现在要访问172.17.0.10,则会匹配到docker0这个虚拟网桥,然后将数据包转发到docker0中由docker0进行转发 如果我现在要访问192.168.0.10,则会匹配到192.168.0.0这个条目走wlp0s20f3网络接口,这个网络接口是wifi,则会直接发送数据包到wifi猫中,因为是局域网地址所以不会通过网关转发到外网,直接转发到局域网内的目标主机中 如果我现在要访问121.196.169.248,则全部匹配不到会走默认路由,直接转发到网关上然后再转发到外网 ​ route命令 route -n route add default gw 10.0.0.1 dev eth0 #指定默认路由 并且设置网关和出口设备 route add -net 10.2.0.0/24 dev eth1 #指定其它路由 route del -net 10.0.0.0/24 #删除路由 ​ 参考 掌握Linux路由这一篇就够了!

Linux文件管理

文件类型 Linux一切皆文件,很多东西都可以使用VFS抽象为文件来实现 文件类型 符号 举例 普通文件 - 纯文本文件、二进制可执行文件、压缩文件…. 目录文件 d / socket文件 s / 管道文件 p / 软链接文件 l (L小写) / 查看文件类型的方式主要有下面几个命令 ls -l a.txt file a.txt stat a.txt #查看文件的详细信息 ​ 文件时间 文件时间 描述 Access 文件最近被访问的时间 Modify 文件最近被修改的时间,ls -l默认列出的就是修改时间 Change 文件对应的inode节点被改动的时间(chmod、chown) 对于一个首次创建的文件,那么上面三大时间都是一样的 使用touch一个已经存在的文件则不会清空文件,而是将文件的三大时间都修改为最新 使用ls命令查看文件的三大时间 ls -l filename #列出文件的 mtime (Modity) 等价于`ll` ls -lc filename #列出文件的 ctime(Change Time) ls -lu filename #列出文件的 atime(Access Time) ls --time-style long-iso #指定时间格式 long-iso最完美 cat一个文件,只会第一次变化,当再次cat的时候如果Access时间是新于Modify和Change的时间的则文件不会变化 同时还需要注意,往文件里面追加内容或则使用sed等更改文件内容的命令会同时更新文件的Modify和Change时间。这是因为文件内容变了那么文件的一些inode属性就变了,最直接的大小变了,此时 Change时间也会改变

为什么栈上内存分配效率高

栈为什么这么快 栈寄存器的支持 CPU有专门的栈寄存器 ESP栈顶寄存器,EBP栈基地址寄存器,对于栈内存的访问CPU可以直接根据栈寄存器保存的地址获取到数据 但是堆内存的访问需要先获取到指令然后从指令中获取到堆内存地址保存在寄存器中,最后再通过寄存器的地址进行寻址 栈空间内存分配和释放比较快 栈空间在编译时就分配内存给变量了,而堆空间需要程序允许时动态的进行系统调用来分配内存。并且栈在分配内存和释放内存只需要移动栈寄存器的指针即可,所以栈的内存分配和释放都是比较快的 栈空间的管理不需要程序员手动管理,而堆空间需要用户程序手动管理,包括一些GC算法等都是针对堆空间的 栈适合函数调用场景 函数调用场景时候用栈这种数据结构,比如 函数调用结束之后,函数里的局部变量都没用了,则此时应该立即释放其内存 函数嵌套调用 ​ 栈的缺点 栈因为结构简单,所以无法应对一些需要动态分配的场景 栈空间需要类型大小是固定,缺乏灵活性 ​ Go的内存逃逸 Go使用new make 等创建的对象不一定都是分配在堆上的,这个和其他语言有点不一样 编译器在编译的时候会进行逃逸分析,可能会将函数内的一些局部变量分配到堆上,逃逸分析的命令如下 go build -gcflags '-m -l' main.go #逃逸分析 函数返回指针,编译器会把变量分配到堆上 type User struct { Name string } func hello() *User { u := User{Name: "lyer"} return &u } new一个对象,make一个对象并不一定会分配到堆上 type User struct { Name string } func hello() { u := new(User) u.Name = "lyer" c := make([]int, 10) c[0] = 1 } 下面的代码也不会逃逸,传入的地址是栈上的地址

编程语言自举

什么是自举 Go1.5完成了自举,也就是说Go现在的编译器就是用Go语言本身写的 编译器也是一个二进制程序,作用就是把语言代码的文本翻译为机器平台对应的二进制可执行文件,机器平台二进制可执行文件可以和汇编语言指令一一对应,每个汇编指令都有其对应的二进制格式的机器码 比如我现在要发明一个E语言,我现在用C语言写一个E语言的编译器然后将这个编译器编译为二进制,这样我们就可以用这个C语言写的E语言编译器去翻译E语言代码了 接着我用E语言实现写了和C语言的那个编译器同样功能的编译器,然后用C编译器编译这个E写的编译器,那么就可以用E写的编译器编译E语言了,这就完成了自举

Linux权限和用户

用户和用户组 一个用户必须属于某个组,创建新用户的时候如果没有指定用户组则会创建一个同名的默认用户组 用户组和用户的关系是多对多的关系,一个组里可以有多个用户,同理一个用户也可以属于多个用户组,但是每个用户都会有一个默认组,而其他组则是附加组在/etc/passwd文件中记录的用户组就是用户所属的默认组,而在/etc/group中会记录每个用户组里面的其它用户成员列表(用户组的主成员不记录在此) /etc/passwd记录的是用户相关的一些信息,每个用户占用一行 用户名:口令(都为x):用户ID:用户组ID:用户注释:用户家目录:shell解释器 testuser:x:1001:1001:TmpUser:/home/testuser:/bin/sh /etc/group记录的是用户组相关的信息,每个用户组占一行 组名:口令(都为x):用户组ID:组内用户列表 audio:x:29:pulse,pb #主用户不会记录在这里 /etc/shadow 记录的是用户加密之后的密码信息 /etc/default/useradd 新用户默认设置的配置文件 SHELL=/bin/sh #新用户的默认shell EXPIRE=2020/06/08 #新用户的默认过期时间 不配置则永久 .... ​ 用户和用户组相关的Linux命令 我们直接修改配置文件也是可以的,但是Linux也为我们提供了一些命令,本质上都是修改底层的配置文件 新建/删除/修改用户 useradd/userdel/usermod 用户组增删改 groupadd/groupdel/groupmod 修改用户密码 passwd 为用户添加到指定的组中 (直接修改/etc/group命令也可) #添加用户到指定的用户组中 不加-a则表示替换 #这里需要注意: 用户的默认组是不会被修改掉的 这个操作只能修改附加组 usermod -a -G g1,g2,g3 查看用户信息 id/who/users/whoami/last/lastb/groups ​ sudo命令和sudoers配置 sudo的作用是一个用户借用其它用户的权限来执行相关命令,需要配置/etc/sudoers文件,Linux会判断是否符合sudo文件的配置才会决定是否能够借权 用户名 主机=(用户:用户组) NOPASSWD:命令列表 #pb用户可以在 所有主机登入 执行 所有用户:所有组 的所有命令 并且不需要输入密码 pb ALL=(ALL:ALL) NOPASSWD:ALL #表示admin组内的用户规则 %admin ALL=(ALL:ALL) ALL 下面再来看几个案例 #相当于(root:root) peter ALL=(root) NOPASSWD: ALL lucy ALL=(ALL) chown,chmod,useradd papi ALL=/usr/sbin/*,/sbin/*,!

性能指标QPS

TPS每秒事务数 Transactions Per Second 每秒处理的事务数目,一个事务是指一个客户机向服务器发送请求然后服务器做出反应的过程 比如我请求访问twitter页面,则twitter服务器会返回一个twitter完整的页面给我,这个页面可能还包括一些css、js、各种数据的请求,这一整个页面的访问和全部数据的响应就叫TPS TPS过程包括: 客户端请求服务端+服务端内部处理+服务端返回客户端 tps=处理事务数/秒 ​ QPS每秒查询率 Queries Per Second 每秒查询率,表示一台服务器每秒能够响应的查询次数,代表的是服务器的机器的性能最大吞吐能力 qps基本类似于 tps,但是不同的是对于一个页面的一次访问,形成一个 TPS,但一次页面请求,可能产生多次对服务器后端API的请求,服务器对这些请求就可计入QPS之中 简单来说QPS就是对每个后端请求的度量,比如一个页面要请求多个接口,那么对每个接口的请求可以归结为QPS,当用户请求页面到页面完整的展示给用户这一个过程可以归结为TPS,也就是说如果一个页面只会访问一个接口一个请求,则TPS=QPS 比如现在有100个线程,每个请求的响应平均时间为0.5s,所以服务器每秒可以处理200个请求,qps计算方式如下: qps=100/0.5=200 所以为了提高QPS,我们可以使用下面两个方法 提高并发数 减少每个请求的处理时间 如果在10s内有10000个请求过来,每个请求平均处理时间还是0.5s,则此时需要最低的qps计算如下: qps=10000/10=1000 RT=0.5s 最大并发数=qps*rt=1000*0.5s=500 明显上面的qps=200<1000,所以说需要500个线程才不会造成等待,如果按照200的qps则1s最多只能支撑2000个请求 ​ PV页面浏览量 page view 页面浏览量,用户每次对页面的访问都计入PV,比如我对一个页面刷新了5次那么PV就加5 ​ UV独立访客数 Unique Visitor 独立访客访问数,可以理解为IP访问次数,比如我对一个页面刷新了100次,但是UV还是只加1,因为IP还是我

缓存一致性

什么是缓存一致性 操作缓存比操作Mysql数据库要快,如果流量大的话可以先将数据保存在缓存中,等空闲的时候再将缓存中的数据持久化到数据库中 还有一些热点数据我们也可以预先从数据库中加载到缓存中,那样在流量大的时候就不需要每次都从数据库里面去读取数据了 因为缓存和数据库是两个不同的地方,最容易出现的问题就是缓存的数据和数据库中的数据不一致问题: 可能从缓存中读取的数据是旧值,又或者我们保存在缓存中的数据还没有持久化到Mysql数据库中 为了保证缓存一致性,减少数据不一致的情况,我们必须合理的使用缓存 ​ 不更新缓存,而是删除缓存 后端数据库如果改变的话,我们不应该更新缓存而应该删除缓存,直到下一次数据读取的时候再加载数据库中最新的缓存 如果数据改变了,我们如果去更新缓存的话则会出现很多问题 一、线程安全,脏数据问题 下面有两个并发请求A、B同时更新了数据库 A更新数据库 B更新数据库 B更新缓存 A更新缓存 由于网络原因,虽然A是先于B更新数据库,但是此时A却后于B更新缓存,此时的缓存的数据就是A的数据而不是最新的数据B,这样就造成了数据不一致的问题 如果我们在更新数据时直接删除数据,此下次再来读取的时候加载一次数据库即可,那么加载的数据一定是最新的 二、频繁更新缓存造成不必要的浪费 如果在写多读少的场景下,缓存数据会进行频繁更新,但是读数据却很少,则会造成资源浪费,还不如直接将缓存删除,下次读的时候只需要一次cache miss的消耗 同时如果数据需要进过复杂的运算和逻辑处理才能写入缓存,那么频繁的更新缓存也会造成不必要的消耗 ​ 先操作数据库,再删除缓存 在写数据的时候,我们一般是先写数据库,再删除缓存 这样才能减少数据不一致的问题 先删除缓存,再操作数据库 此方案在并发读写的时候也会造成数据不一致性的问题 假设有线程A、B 线程A请求写操作,于是线程A会删除缓存数据再进行数据库写操作 此时线程B在A写操作的过程中进行读操作,引发一次cache miss 由于B是在A写事务执行过程中来读取的,于是B加载到缓存中的数据还是之前的老数据 此时A写操作完成,这样就造成了缓存和数据库的数据不一致性,如果缓存的数据没有过期时间的话则其他客户端读取到的数据就一直都是脏数据直到下次 发生数据写操作删除缓存 先更新数据库,再删除缓存 这种方案不会引发上面的缓存不一致的问题,一旦数据库的数据更新完毕之后就会立即删除缓存中的数据,则下次再读取时就会引发一次cache miss就能读取到最新的数据 但是也会有很小的概率会发生数据不一致的状态 假设有两个并发线程A、B A请求读取数据,此时缓存凑巧失效了,A引发cache miss之后读取数据 B请求在A读取数据的同时在写入新值到数据库中,并且写入完成之后删除了缓存 此时A又将读取到的旧值加载到了缓存中,引发数据不一致的问题 上面的情况发生的概率是比较小的 ​ 参考 缓存一致性问题

Go文件读写和IO操作

IO接口 io.Reader 读IO接口 type Reader interface { Read(p []byte) (n int, err error) } io.Writer 写IO接口 type Writer interface { Write(p []byte) (n int, err error) } io.Closer 关闭IO接口 type Closer interface { Close() error } ​ 文件读取 方式一: 无缓冲直接读取 func fileDemo1() { file, err := os.Open("/go-test-learn/io/t1.go") CheckError("", err) defer file.Close() content := []byte{} buf := make([]byte, 100) //每次读取的最大byte数组 for { n, err := file.Read(buf) //返回真实读取的数据字节大小 (<=len(buf)) if err == io.

常见的开源数据库

KV数据库 BoltDB go语言写的kv数据库,现在已经停止维护 https://github.com/boltdb/bolt 但是etcd扩展了BoltDB,重新命名叫 BBolt,作为是etcd的底层kv存储 https://github.com/etcd-io/bbolt levelDB google开源的,使用c++写的kv数据库 https://github.com/google/leveldb ​ 时序数据库 influxDB go语言写的时序数据库,分为商业版和开源版,开源版本不支持分布式 https://github.com/influxdata/influxdb TDengine 用C写的时序数据库,支持分布式,全部开源 https://github.com/taosdata/TDengine 关于时序数据库参考博客 时序数据库 InfluxDB(一) 时序数据库InfluxDB使用详解

一致性Hash

分布式缓存 为了提高Redis缓存的读写能力,一般会建一个Redis集群,此时我们就需要通过hash算法将key存储到指定的一台节点中,这样每次取这个key的时候到key对应的节点中去获取即可 假如现在有10台Redis,计算key对应的节点的hash公式如下: node = hash(key)%10 静态分配策略在集群节点数量固定的时候是没问题的,但是当集群节点数量进行动态扩缩的情况下就会出现 缓存雪崩 问题 比如现在新加了10个节点,那么上面的公式就变成了如下: node = hash(key)%20 也就是说之前所有的key计算出的节点在集群扩大的时候再次计算时就可能不是同一个节点,那么就无法在对应节点中获取到key的缓存。如果这样的key数量很多的话就会造成缓存雪崩问题,那么后端数据库的压力就会在集群扩容的时候剧增 为了解决这个问题,于是就有了后面的 一致性Hash算法 ​ 一致性Hash算法 一致性hash算法将节点映射到这个环上,同时也将key映射到一个2^32大小的环上,并且沿着key所在的位置顺时针查找,找到的第一个节点就是这个key要保存的节点 如果加了一个node或则减少了一个node,那么只会影响环上的node顺时针对应的前一个node之间的数据 ​ 节点倾斜问题 当hash环上的节点数量较少时就可能造成节点倾斜问题,比如所有节点都被映射到了同一边的一个角落,那么就会有大量的key只存在一个node上,而其他node则没有多少key 为了解决节点倾斜问题,引入了 虚拟节点 。 就是在每个实际的节点下虚拟出几个不存在的节点将其映射到hash环上,让hash环上节点分布的比较均匀。如果key保存在虚拟节点上,则实际上是保存在这个虚拟节点对应的实际节点中。通过虚拟节点就解决了数据倾斜问题 ​ 参考 好刚: 7分钟视频详解一致性hash 算法 一致性hash