Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wisecsj opened this issue Mar 9, 2019 · 32 comments
Open

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

wisecsj opened this issue Mar 9, 2019 · 32 comments

Comments

@wisecsj
Copy link
Owner

wisecsj commented Mar 9, 2019

No description provided.

@wisecsj wisecsj pinned this issue Mar 9, 2019
@wisecsj wisecsj changed the title [Golang Notes] 不足以构成Post的笔记,将记录在此,不断更新 [Notes] 不足以构成Post的笔记,将记录在此,不断更新 Mar 21, 2019
@wisecsj
Copy link
Owner Author

wisecsj commented Mar 21, 2019

Linux I/O 多路复用

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

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

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

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

参考:epoll详解和使用

@wisecsj
Copy link
Owner Author

wisecsj commented Mar 25, 2019

image

@wisecsj
Copy link
Owner Author

wisecsj commented Mar 27, 2019

Golang 反射三大定律:

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

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

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

@wisecsj
Copy link
Owner Author

wisecsj commented Mar 28, 2019

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

a&(1<<b-1)

@wisecsj
Copy link
Owner Author

wisecsj commented Mar 28, 2019

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
Copy link
Owner Author

wisecsj commented Mar 31, 2019

关于字节对齐的一篇文章

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
Copy link
Owner Author

wisecsj commented Apr 4, 2019

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

  • 保持各层独立,因为除了IP还有其它的网络协议 ,而且这些网络层协议也会优胜劣汰
  • 假若不使用MAC地址,那网卡需将IP地址保存在其ram中,而且每次断电或主机移动都需要重新获取
  • 亦或是,适配器直接不使用地址,接受到的任何链路帧都沿协议栈向上传递,由网络层决定是舍是留

@wisecsj
Copy link
Owner Author

wisecsj commented Apr 4, 2019

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

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

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

image

@wisecsj
Copy link
Owner Author

wisecsj commented May 2, 2019

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
Copy link
Owner Author

wisecsj commented May 4, 2019

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
Copy link
Owner Author

wisecsj commented May 12, 2019

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

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

@wisecsj
Copy link
Owner Author

wisecsj commented Jul 18, 2019

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

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

@wisecsj
Copy link
Owner Author

wisecsj commented Jul 19, 2019

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

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

@wisecsj
Copy link
Owner Author

wisecsj commented Jul 21, 2019

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
Copy link
Owner Author

wisecsj commented Aug 1, 2019

bytes.Buffer -> io.Pipe

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

@wisecsj
Copy link
Owner Author

wisecsj commented Aug 3, 2019

有限状态机(FSM)

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

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

@wisecsj
Copy link
Owner Author

wisecsj commented Aug 7, 2019

Gorm Select

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

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

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

@wisecsj
Copy link
Owner Author

wisecsj commented Aug 21, 2019

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

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

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

@wisecsj
Copy link
Owner Author

wisecsj commented Jun 10, 2020

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
Copy link
Owner Author

wisecsj commented Jun 11, 2020

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

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

@wisecsj
Copy link
Owner Author

wisecsj commented Jun 21, 2020

数据库中的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
Copy link
Owner Author

wisecsj commented Sep 6, 2020

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

后来,人们为了彻底解决定义的时间的流逝不均匀的问题,开始使用原子钟定义时间。人们首先用全世界的原子钟共同为地球确立了一个均匀流动的时间,称为国际原子时(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
Copy link
Owner Author

wisecsj commented Nov 2, 2020

为什么需要 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
Copy link
Owner Author

wisecsj commented Dec 3, 2020

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 wisecsj changed the title [Notes] 不足以构成Post的笔记,将记录在此,不断更新 [Notes] 不足以构成Post的笔记,记录在此,持续更新 Dec 21, 2020
@wisecsj
Copy link
Owner Author

wisecsj commented Dec 21, 2020

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
Copy link
Owner Author

wisecsj commented Jan 2, 2021

分享一个游戏安全的pdf

TenProtect Conficential

游戏安全的攻防艺术

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

@wisecsj
Copy link
Owner Author

wisecsj commented Jan 9, 2021

聊聊 WebScoket和HTTP/2

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

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

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

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

@wisecsj
Copy link
Owner Author

wisecsj commented Mar 6, 2021

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
Copy link
Owner Author

wisecsj commented Mar 16, 2021

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
Copy link
Owner Author

wisecsj commented Apr 18, 2021

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
Copy link
Owner Author

wisecsj commented May 15, 2021

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

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

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

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


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

@wisecsj
Copy link
Owner Author

wisecsj commented Sep 16, 2021

Linux条件变量pthread_condition细节

condition

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant