wisecsj / wisecsj.github.io

Blog
Apache License 2.0
0 stars 0 forks source link

[Notes] 不足以构成Post的笔记,记录在此,持续更新 #20

Open wisecsj opened 5 years ago

wisecsj commented 5 years ago

Linux I/O 多路复用

  1. select/poll的机制基本是一样的,只是传递到内核的数据格式不同:一个是整型数组,一个是结构体数组

  2. select/poll即使只有一个描述符就绪,也需要遍历整个集合

  3. epoll只返回就绪的描述符集合,epoll同时支持水平触发(默认)和边缘触发

  4. epoll无须每次执行都从用户空间复制句柄到内核

参考:epoll详解和使用

wisecsj commented 5 years ago

image

wisecsj commented 5 years ago

Golang 反射三大定律:

  1. 反射是从接口类型变量到反射对象(reflect.Type,relect.Value)

  2. 然后从反射对象反射到接口类型变量

  3. 如果需要修改一个反射对象,那么它的值需要是settable的

wisecsj commented 5 years ago

a%(2^b) 如何用位运算实现?

a&(1<<b-1)

wisecsj commented 5 years ago

Go语言中的map是用hashtable实现的,解决哈希碰撞是用的拉链法。即数组+链表的组合,go源码中将链表称为bucket,桶。

bucket的结构是这样的: image

topbit 保存的是某个key hash之后得到的数的高八位,如下图中的hashtop实现:

image

每个bucket最多可以保存八个elem,然后查询的时候,go可以利用topbit来快速的进行比对(而不是通过key的比较),加快查询速度。同时为了优化内存布局,每个bucket中的key、value是分开集中存储的(见bucket结构图)

参考:

理解 Golang 哈希表 Map 的原理 Golang Map 的底层实现

wisecsj commented 5 years ago

关于字节对齐的一篇文章

Data alignment: Straighten up and fly right

---- Updated 2020.10.31 ----

今天在查原子操作相关资料又涉及到了这个字节aligned话题。这些话题说实话,跟底层硬件结合的挺紧密的,不像我们写业务代码,只需要关注业务逻辑。

要理解字节对齐产生的原因,需要知道三个东西:

  1. cpu怎么从内存拿数据,可以拿多少byte的数据

    就像cpu有字长的概念,cpu一次能从内存读取的最大数据取决于数据总线宽度(总线带宽= 频率*宽度)

  2. 为什么cpu不支持从任意的内存地址取四个连续字节呢?

    相信一开始很多人会有这个疑问🤔️,很自然。原因其实在于我们的内存是由多个内存芯片而组成,它们并不是一个真正物理上连续的byte数组。Why misaligned address access incur 2 or more accesses?

    总的来说,硬件可以那么做,但是结果是为了20%不到的场景牺牲了整体的性能,同时增加了硬件电路复杂度

image

wisecsj commented 5 years ago

为什么我们除了IP地址还需要MAC地址?

wisecsj commented 5 years ago

子网中的两台主机进行通信,往往需要用到ARP协议(ip地址 -> mac地址)。虽然arp packet里既包含ip地址,也包含mac地址,但是是封装在链路层帧中的。

Linux下,可以通过 arp -a 查看本机 arp table entries。

这是一个展示子网通信对arp表影响的结果截图:

image

wisecsj commented 5 years ago

UNIX-LIKE 系统下如何设置daemon进程/让进程后台运行?

比如我用的是ubuntu桌面版本,因为有很多常用的软件是没有ubuntu版的(netease musical、wechat、qq),然后从github或其他办法下载到其他开发者实现的版本,往往是一个二进制可执行文件。

一般会先设置.bashrc快捷命令,然后比如在shell里执行wechat,但是这些大部分软件自身是没有实现daemon代码的,所以会在前台执行,然后hang住当前terminal,并且关闭当前terminal软件也会退出(相比之下,vscode就不会出现这种情况,自身实现了daemon化)。显然这并不是我们所期望的,so...

常见的办法,利用nohup和&的组合:alias calc='nohup gnome-calculator </dev/null &>/dev/null &'

比如上面这条命令,之后在shell里输入calc,就可以直接打开gnome自带的calculator,并且退出terminal也不会对应用自身有任何影响。并将应用的stdin/stdout重定向到/dev/null,/dev/null代表空设备,这样控制台不会有应用相关的输出显示。

那,有的应用可能要sudo权限,你又想后台运行,如何做?利用 sudo -b:sudo -b netease-cloud-music </dev/null &>/dev/null

有的应用不同,他可能是作为service一直要运行在后台,并且退出需要restart的。这时候就应该使用systemd来创建service。

参考链接:

https://lo-li.cn/1168 http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-part-two.html

wisecsj commented 5 years ago

io.Copy

因为无法确定reader的大小,所以采取的策略是开一个buf字节数组,循环对Reader进行read(buf)操作,然后对Writer执行write(buf)

bytes.NewReader/strings.NewReader

适用于已经得到完整的字节切片数据了,直接构建Reader

bytes.Buffer

当未得到完整数据,或者是需要不断写入的场景时,new bytes.Buffer是更好的选择。它会自动地帮我们进行扩容来存储写进的数据

io.TeeReader(r Reader,w Writer) Reader

override了r的Read方法,当调用r.Read(buf)时,内部也会调用w.Write(buf)

wisecsj commented 5 years ago

Linux下快速找到占用端口的PID:lsof -i:PORT_NUMBER

在配合kill命令即可结束占用端口的对应进程

wisecsj commented 5 years ago

Mysql 目前仍然不支持text类型字段设置默认值,会报错

google也没看到官方一个明确的说法(一个靠谱的说法是因为text的实现机制与其他类型不同,单独存储),只能在应用里显式设置了

wisecsj commented 5 years ago
  1. 关于gorm,准确来说是关于database/sql中的接口,如何实现自定义数据结构的序列化和反序列化: https://github.com/jinzhu/gorm/issues/47

  2. gorm 以结构体形式传参数,进行create/updates时,会自动忽略zero value字段。从而保证只更新我们想更新的字段,避免将数据库中其它字段设为zero value

wisecsj commented 5 years ago

Golang : for range internals

golang中的for range底层实现跟python不太一样。可以认为是在进行for range操作时,是对当时的range的对象进行了拷贝,然后对拷贝对象进行for range。所以在for range里对原range对象进行操作,不影响当下的for range

https://blog.cyeam.com/golang/2018/10/30/for-interals

wisecsj commented 5 years ago

bytes.Buffer -> io.Pipe

避免潜在的内存开销和垃圾回收

wisecsj commented 5 years ago

有限状态机(FSM)

一般我们说状态机特指有限状态机。而有限状态机又分为确定有限状态机,不确定有限状态机(一个状态对于一个输入可能又多个状态转移路径)。

设计优化的流程:不确定有限状态机 -> 确定有限状态机 -> 最小确定有限状态机

wisecsj commented 5 years ago

Gorm Select

某些时候,我们只select了一个字段,并且这个字段的类型为基础类型(int,string,etc)

但是直接用First会报错:destination should be slice or struct

这个时候,可以“回退”到标准库的database/sql用法:.Row().Scan()来解决

wisecsj commented 5 years ago

json.Marshal时关于slice的注意点:

    s := make([]string, 0)
    var m []string
    b, _ := json.Marshal(s)
    a, _ := json.Marshal(m)

a=="[]" b=="null"

wisecsj commented 4 years ago

Golang 数据库操作是否可以通过context来取消?

这里的数据库特指Mysql,其它的不了解。

我们知道在golang中可以利用context包来实现goroutine的“终止”。那当goroutine里的操作为db操作,在golang里cancle后,如果数据库已经在执行对应的statement,是否也会终止呢?

答案是否定的。我们都知道标准库database/sql内含一个db的连接池,每次db操作都会先从池子里去取。而如果需要terminate对应的sql语句,实际上也就是我们在terminal里使用的kill query {{process_list_id}},必然要知道当前连接的process_list_id(connection_id?)。而在连接池的实现下,因为连接会被复用,connection_id也就意味着一样,贸然kill可能会错误地将其它goroutine的sql语句给kill掉。

所以一种解决方案是起一个独立conn,获取其connction_id,在该conn上执行sql语句,并在cancel的时候执行kill

wisecsj commented 4 years ago

一篇很好的讲述mysql连接&&语句执行的文章

https://mysqlserverteam.com/mysql-connection-handling-and-scaling/

wisecsj commented 4 years ago

数据库中的on update CURRENT_TIMESTAMP

(未特殊指定,数据库皆指Mysql InnoDB引擎)

当我们需要知道一条记录的最近一次更新时间,通过我们会add column,名为update_time,在Extra上设置on update CURRENT_TIMESTAMP来实现。之后该行上其它列进行更新,数据库都会自动更新update_time。

之前我的认知是只要执行update语句,update_time字段就会更新。后来实现&&Google后发现如果update返回的rows affected为0的话,是不会更新的。

本以为到此就终结了,后来又发现,不对啊,同样的update语句执行多次后,update_time居然也是会随之更新的(认知再一次被颠覆...)。经过实验(again),发现问题出现在对允许为NULL的字段的更新上:如果字段A可为NULL,当update包含A=NULL时,即时update前后都是NULL,return的rows affected也不为0

wisecsj commented 4 years ago

什么是闰秒,为什么会有闰秒,各个时间系统的诞生背景

后来,人们为了彻底解决定义的时间的流逝不均匀的问题,开始使用原子钟定义时间。人们首先用全世界的原子钟共同为地球确立了一个均匀流动的时间,称为国际原子时(International Atomic Time, TAI)。然后,为了使定义的时间与地球自转相配合,人们通过在TAI的基础上不定期增减闰秒的方式,使定义的时间与世界时(UT1)保持差异在0.9秒以内,这样定义的时间就是协调世界时(Coordinated Universal Time, UTC)。UTC是目前全世界使用的时间标准。UTC与UT1之间的差异被称为DUT1。

目前,“格林尼治标准时间”一词在民用领域常常被认为与UTC相同,不过它在航海领域仍旧指UT1。

1.格林尼治标准时间

2.协调世界时

协调世界时(UTC)正式形成于1963年国际无线电咨询委员会的374号建议中[6],该建议由多国时间实验室共同提出。人们对该时间系统进行过数次调整,直到1972年引入了闰秒机制,调整工作得以简化。也有很多人提议用一个没有闰秒的时间系统来替换掉协调世界时,但目前尚未就此达成一致。

现行的协调世界时根据国际电信联盟的建议《Standard-frequency and time-signal emissions》(ITU-R TF.460-6)所确定[7]。UTC基于国际原子时,并通过不规则的加入闰秒来抵消地球自转变慢的影响[8]。闰秒在必要的时候会被插入到UTC中,以保证协调世界时(UTC)与世界时(UT1)相差不超过0.9秒[9]。

3.闰秒

wisecsj commented 4 years ago

为什么需要 atomic load and store pointer

这篇golang groups里有关的讨论:https://groups.google.com/g/golang-nuts/c/BZ-BTHQ9IYo

总结下来,还是底层硬件实现的差异化:

  1. 4/8 字节的存取操作是原子地前提是字节对齐

  2. 单条cpu指令也可能是非原子的(ARMv7 strd指令)

Ref: Atomic vs. Non-Atomic Operations

wisecsj commented 3 years ago

Mysql: Lock wait timeout exceeded; try restarting transaction

最近一个代码的bug,大致逻辑就是在一个for循环里,每个循环开启一个事务,然后defer Close()。在某些场景下,会导致报如上的错误。

最终定位是因为所使用的mysql默认事务隔离级别是RC,在RC级别下,在事务A里对某行的更新(update),会使得事务A持有该行行锁;如果此时开启事务B,由于RC,导致业务逻辑也会走到更新该行,那便需要持有该行行锁。但是我们是用的defer,导致事务A没有及时commit,导致事务B等待行锁超时,报错返回。

解决方式也很简单,显式调用下tx.Commit。当然了,这是表因,根因的话其实还是需要深入了解innodb各个事务隔离级别下的事务实现(Todo

wisecsj commented 3 years ago

Golang http client Do 方法源码阅读(概览

最近遇到一些关于http 1.1 协议实现的问题和golang在实现上的一个小坑,于是整体浏览下do的工作流程。 (阅读中发现整个http实现的代码不是很清爽,因为夹杂了很多test,trace和超时控制类的逻辑在里面,非核心逻辑都会忽略)

  1. do func主体是个for循环,用于处理类似301的跳转场景(但是最多10次
  2. 首先清楚,cookieJar是绑定在client的;连接池是绑定在transport上的
  3. 首先根据request生成的connctmethod去连接池里尝试拿取alive的连接,没有可用的话就新建(忽略一些对连接数的限制
  4. 拿到连接后,实际是个persistconn结构体,起两个goroutine:readloop,writeloop,从中不断读写数据

Notice:

  1. 根据源码,实际发请求写http数据时,host字段是走的request.Host,而不是你request.Headers.Set的host。所以如果你请求的url和header里的host期望不一致,需要通过request.Host来实现
  2. resp,err := client.Do(&req),是在resp的headers收取完毕后,就返回了的
wisecsj commented 3 years ago

分享一个游戏安全的pdf

TenProtect Conficential

游戏安全的攻防艺术

顺带元旦第一天把cheat engine的9个tutorial给做了,更完整地熟悉了下ce地使用和wg原理。(最一开始接触到ce,还是百度云盘会员300s试用地时候:) )

wisecsj commented 3 years ago

聊聊 WebScoket和HTTP/2

不说他们的具体实现细节吧,就譬如协议的位结构是怎样的啊,这种直接看rfc就好了

说说它们的异同和使用场景。http/2其实基本就是google spdy协议的标准化,将文本协议换位二进制协议,多路复用,支持hpack头压缩算法和其它的一些功能(grpc使用的http/2)

websocket的出现则是为了解决服务器和客户端实时通信的痛点,在之前可能都是通过长轮询的方式来做,开销大且并不实时

所以,它们虽然都是二进制协议,都是基于tcp的应用层协议,http2目的主要为了解决ping-pong响应式的http请求的性能优化(不必再每个请求建一次tcp链接,不会有阻塞问题等);而websocket则是解决实时问题,适用于聊天室,实时协同编辑的场景

wisecsj commented 3 years ago

CPU位数的含义:

cpu在一个时钟周期内能够处理的位数,也称作字长

CPU位数与地址总线位数的关系:

没有必然联系,一般32位cpu地址总线为32位,64位cpu为36或4x位

CPU位数和操作系统位数的关系:

一般相同,但是因为cpu64位兼容32位运行模式(向后兼容)。所以32位的操作系统也可以run在64位cpu上

物理地址和逻辑地址的关系

编程中获取的指针地址都是va,会经由OS(还有专门的硬件辅助MMU)转换成物理地址

操作系统位数和编程语言中指针大小的关系

这个理解起来可能有点难,因为实际上涉及到很多因素。我个人理解,一般来说,因为指针存储的是地址编号,本质也是一个数值(即数据),所以需要跟cpu位数(cpu寄存器)匹配。所以64位cpu的指针一般就是64位(除开运行在32位模式和一些非通用的cpu)

Ref

https://www.cnblogs.com/little-YTMM/p/5058354.html

wisecsj commented 3 years ago

Golang http client 在处理302跳转的逻辑

http包里的client在302跳转的时候会继承原有的header,除了带有私密,安全属性的header。譬如cookies,authentication。这些带有安全性质的header会进行一个check,比如校验是否域名相同或是否为子域,否则时不回带上原有cookie的。

比如 m.tiktok.com -> www.tiktok.com , Cookie Header是不会继承的

同理,cookieJar也时会进行类似的安全校验

---- Updated 2021.4.18 ----

Golang对query的处理逻辑也需要注意。譬如net.URL.Query().Encode(),内部实现是先unescape再escape,并且unescape return error 的pair会直接丢弃——对于rawquery为key=100%含金量,encode后会丢失;第二点就是encode因为用的是map,输出序是不稳定的,所以为了保证结果的稳定性,会先对key进行sort(会破坏原始query的顺序,变成字母序,从而一些req的校验逻辑不通过)

wisecsj commented 3 years ago

Golang Map实现中几个优化点

  1. 取余运算优化为位运算

因为hmap中的Buckets数量保证了是2^B次方,所有当我们需要根据hash之后的值M对len(Buckets)取余时,也就是M%2^B=M&(2^B-1). 思想就是M%2^B其实相当于右移B位,而这个B位的值就是我们需要的余数。所以问题就转化成了如何取M的低B位

  1. 当key,elem的size大于128时转为存储指针

image

这个应该是涉及到内存分配的优化

wisecsj commented 3 years ago

GPS定位原理及为什么需要至少4颗卫星而非3颗?

当你拿着手机仰望天空时,如下图,假设你的地理坐标系坐标为x,y,z:

v2-e0359b4b13dec165b8593c9c2bd8b514_r 可以得到: image 那会奇怪,三元方程,那三颗卫星数据就够了不是么?

问题在于delta t. 我们无法使用手机本身的石英时钟,与原子钟相比误差太大. 而走网络授时中心同步,也会有网络延迟和同步不及时的问题。综上,此刻的时间也需设置为一个未知数,从而需要4颗卫星的数据


另一个问题是误差。误差的来源有很多,比如光在空气中的传播速度受天气影响等。解决的思路是靠标准参照来差分消除误差:譬如基站的地理坐标系是确定的,它同时也作为“手机”接收gps信号并进行坐标计算,从而得到误差值。基站再把这个差值信息传递给手机,进行修正

wisecsj commented 3 years ago

Linux条件变量pthread_condition细节

condition

https://blog.csdn.net/shichao1470/article/details/89856443