Repository navigation
Expand file tree
/
Copy pathatom.xml
More file actions
3649 lines (3543 loc) · 153 KB
/
Copy pathatom.xml
File metadata and controls
3649 lines (3543 loc) · 153 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>http://www.zerokirin.online</id>
<title>个人学习</title>
<updated>2022-03-27T09:01:38.592Z</updated>
<generator>https://github.com/jpmonette/feed</generator>
<link rel="alternate" href="http://www.zerokirin.online"/>
<link rel="self" href="http://www.zerokirin.online/atom.xml"/>
<subtitle>记录</subtitle>
<logo>http://www.zerokirin.online/images/avatar.png</logo>
<icon>http://www.zerokirin.online/favicon.ico</icon>
<rights>All rights reserved 2022, 个人学习</rights>
<entry>
<title type="html"><![CDATA[字节二面面经]]></title>
<id>http://www.zerokirin.online/post/zi-jie-er-mian-mian-jing/</id>
<link href="http://www.zerokirin.online/post/zi-jie-er-mian-mian-jing/">
</link>
<updated>2021-08-31T09:15:00.000Z</updated>
<content type="html"><![CDATA[<h2 id="问题">问题</h2>
<ul>
<li>dns工作方式</li>
<li>dns多个ip地址怎么选择</li>
<li>http长连接怎么控制</li>
<li>长连接怎么关闭呢</li>
<li>https是什么</li>
<li>rsa握手流程</li>
<li>给证书签名的作用是什么</li>
<li>tcp传输的可靠性是靠什么保证的</li>
<li>什么是聚簇索引呢</li>
<li>myisam是否是聚簇索引,他的索引保存的是什么</li>
<li>索引是通过什么数据结构保存的</li>
<li>B+树和B树的优缺点</li>
<li>有用过事务吗,是什么场景下使用呢</li>
<li>事务有几个特性</li>
<li>caid是什么</li>
<li>原子性是怎么保证的</li>
<li>undo log 里面有哪些东西</li>
<li>持久性是怎么保证的</li>
<li>隔离性是怎么保证的</li>
<li>mvcc是怎么实现的</li>
<li>lru怎么实现</li>
</ul>
<h2 id="浅析">浅析</h2>
<h3 id="计网">计网</h3>
<h4 id="dns">dns</h4>
<ol>
<li>
<p>dns工作方式</p>
<ul>
<li>dns可以理解为一个转换域名到ip地址的服务器,默认端口是53,默认使用udp协议</li>
<li>主要的工作方式有两种,迭代和递归</li>
<li>迭代就是服务器去一个一个问其他上级dns服务器,直到根域名或者有缓存过的记录</li>
<li>递归是服务器只管向上级问一次,上级dns再向上级问,对每个服务器来说,只有一次请求,一次返回</li>
<li>目前常采用迭代方式,也就是向dns服务器发送解析请求,服务器自己挨个向上级查询,然后返回结果</li>
</ul>
</li>
<li>
<p>dns多个ip地址怎么选择</p>
<ul>
<li>随机,我也不知道这有什么好问的,就是随机啊。。。</li>
</ul>
</li>
</ol>
<h4 id="http长连接">http长连接</h4>
<p>http/1.0引入了长连接的方式,也就是在一个tcp连接中尽可能地多传输数据,减少tcp握手挥手的性能损耗</p>
<ul>
<li>
<p>http怎么控制长连接</p>
<pre><code>Connection: keep-alive
Connection: close
</code></pre>
<p>通过在请求头中增加字段,http/1.0 默认是<code>close</code>,到http/1.1时,默认为<code>keep-alive</code></p>
<ul>
<li>当设置为keep-alive时,tcp连接不会主动断开,只有超过了服务器设置的超时时间才会主动停止</li>
<li>close时就断开tcp连接</li>
</ul>
</li>
</ul>
<h4 id="https是什么">https是什么</h4>
<p>在http的基础上,增加了tls作为加密协议,将原来的明文通信变成密文通信</p>
<p>最早是ssl,套接字安全层,ssl3.0完善后成为为tls1.0,传输层安全协议,主要目的就是建立双方的加密通信</p>
<p>目前主流是1.2版本,1.3去年才刚刚上线,还没有普及</p>
<ul>
<li>
<p>https握手流程怎么样的</p>
<ul>
<li>1.2中的握手主要有两类,rsa和dh,但rsa在1.3版本中被废弃,dh的实际实现也换成了安全性更高的ecdh(用椭圆曲线的计算取缔了素数分解问题,效率更高,安全性更高)</li>
<li>握手流程可以看那篇讲https的文章,不赘述了</li>
</ul>
</li>
<li>
<p>数字签名的作用</p>
<ul>
<li>保证信息完整性,验证信息来源是否真实</li>
<li>对摘要做一次私钥加密,这样只有公钥才可以解开,再验证一次摘要是否正确,可以保证未被修改</li>
<li>CA证书的验证也是基于数字签名,每台电脑会内置受信任的CA机构的公钥,对其解密再验证摘要就可以保证CA证书没有被篡改</li>
</ul>
</li>
</ul>
<h4 id="tcp">TCP</h4>
<ul>
<li>tcp传输的可靠性靠什么保证
<ul>
<li>确认和重传机制
<ul>
<li>建立连接时约定好的起始序列号seq</li>
<li>确认号ack</li>
<li>校验和checksum</li>
<li>丢包时重传</li>
</ul>
</li>
<li>数据排序
<ul>
<li>依据滑动窗口接受机制可以确定最后一个正确到达的数据包,定位错误数据</li>
</ul>
</li>
<li>流量控制
<ul>
<li>双方通过约定滑动窗口的大小,控制发送速率</li>
</ul>
</li>
<li>拥塞控制
<ul>
<li>慢启动</li>
<li>拥塞避免</li>
<li>快速重传</li>
<li>快速恢复</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="mysql">mysql</h4>
<ul>
<li>
<p>什么是聚簇索引呢</p>
<p>数据和索引放在一起,索引记录的就是数据本身</p>
</li>
<li>
<p>myisam是否是聚簇索引,他的索引保存的是什么</p>
<p>不是,它里面存放的是数据的文件地址</p>
</li>
<li>
<p>索引是通过什么数据结构保存的</p>
<p>innoDB和myisam都是B+Tree,也支持hash</p>
</li>
<li>
<p>B+树和B树的优缺点</p>
<ul>
<li>B+树的数据都在叶子节点,因此可以将索引放到一起,减少磁盘io更快的定位数据,实现范围查找更加容易</li>
<li>B树在索引到数据时可以快速返回,无需到达叶子节点,对于单一查询来说性能更好</li>
</ul>
</li>
<li>
<p>事务有几个特性</p>
<p>四个,acid</p>
</li>
<li>
<p>caid是什么</p>
<p>原子性和隔离性保证了一致性,最后可以持久化到磁盘中</p>
<ul>
<li>
<p>原子性</p>
<p>要么全部成功,要么全部失败</p>
</li>
<li>
<p>隔离性</p>
<p>A事务的操作对B不可见,B事务的操作也不应该影响到A的正确性</p>
</li>
<li>
<p>一致性</p>
<p>完整性保持一致(p话)</p>
<p>其实就是说,没有并发错误,所有的事情都是按照预想的来的,没有破坏任何对完整性的保护</p>
</li>
<li>
<p>持久性</p>
<p>事务正确结束后,所做的更改就应该永久留在数据库中</p>
</li>
</ul>
</li>
<li>
<p>原子性是怎么保证的</p>
<p>undo log</p>
</li>
<li>
<p>undo log 里面有哪些东西</p>
<p>事务操作的反向操作,比如:</p>
<pre><code>insert a
insert b
insert c
undo log中应该是:
delete c
delete b
delete a
</code></pre>
</li>
<li>
<p>持久性是怎么保证的</p>
<p>redo log,每次先写日志,再写磁盘,这样就算断电,也能保证以重做日志为准进行恢复</p>
<ul>
<li>
<p>为什么是redo log而不是直接写到数据中</p>
<p>因为磁盘的io,不管你读多少数据,都要从磁盘中找到对应的页块,一般来说是16kb,然后将其整体读入,那每次修改都直接修改磁盘,就会导致整个数据库对于磁盘的io非常依赖,而众所周知,磁盘的读写速度慢得离谱,所以为了保证性能,将所有记录优先记录到日志中,因为日志是连续读写的,没有随机读写所带来的io问题(其实也有,但已经很少了)</p>
</li>
</ul>
</li>
<li>
<p>隔离性是怎么保证的</p>
<p>在mysql中,隔离分为四个级别</p>
<ul>
<li>读未提交</li>
<li>读已提交</li>
<li>可重复读</li>
<li>串行化</li>
</ul>
<p>真正与隔离性有关的主要是读已提交和可重复读,这里要引入个概念叫做封锁协议,它描述的是加锁时机,实际上可以把它当作一个思想来看</p>
<p>三级封锁协议:</p>
<ol>
<li>任何写操作需要加X锁,直到事务结束</li>
<li>在一级封锁协议的基础上,读取数据时必须加S锁,<strong>读取完马上</strong>释放S锁</li>
<li>在一级封锁协议的基础上,读取数据时必须加S锁,<strong>直到事务结束</strong>才释放</li>
</ol>
<p>笔者认为可以把2,3级分别看作是读已提交和可重复读的实现思想,而锁和mvcc分别对应着悲观锁和乐观锁的具体实现</p>
<p>当使用for update 等查询语句时,我们称此次查询为<strong>当前</strong>读,读已提交和可重复读正好对应着封锁协议中的二三级,通过添加对应的行锁(可重复读下有gap和next-key锁)来实现隔离</p>
<p>若是直接使用select查询,则会在该事务的第一次查询时,创建一个readview,也因此其被称之为<strong>快照读</strong>,同时每个数据行里都额外的保存了三个字段:最后一个对其改动的事务id,行的id(也就是主键,或者是union id),回滚指针,分别为trx,id,roll</p>
<p>读已提交的readview是每次查询都创建一个新的readview,可重复读是在事务开始的第一次查询创建,分别对应着二三级封锁协议的思想,每次查询时根据readview和隐藏字段的对比,选择出可以展示的数据行</p>
</li>
<li>
<p>mvcc是怎么实现的</p>
<p>readview中,主要包括了<strong>当前活跃</strong>的所有事务的事务组trxs,以及trxs的最小事务id,最大事务id,创建readview的事务id,我们分别称之为min,max,cur</p>
<p>数据行的隐藏字段主要是对其更改的事务id,我们称为trx,还有个回滚指针,他们的关系如下图</p>
<figure data-type="image" tabindex="1"><img src="https://i.loli.net/2021/09/21/pyok36AFfZcONtu.png" alt="image-20210921002134858" loading="lazy"></figure>
<p>因为min,max是以创建readview时活跃的事务的最大最小来划分的,所以</p>
<ul>
<li>绿色的区域代表着创建时,一定是提交了的事务,所以他的提交一定是可以看到的</li>
<li>红色代表着,创建时还没开始的事务,那么它的任何改动都无法看到</li>
<li>蓝色区域则表示存在活跃的事务,同时也存在后来才开始的但已提交了的事务,因此蓝色部分的判断为,该trx是否存在于活跃事务组中
<ul>
<li>若trx存在于trxs中,则创建时还未提交,因此不能展示</li>
<li>若不存在,则证明已经提交,所以可以展示</li>
<li>若trx正好就是cur,那自己对自己做的修改肯定可以看到</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="设计思路">设计思路</h4>
<ul>
<li>
<p>lru是怎么实现的</p>
<p>首先,lru的意思是,Least Recently Used,最近最少使用,是一种缓存淘汰机制,由于是缓存淘汰机制,因此对性能有很高的要求,要求O(1)的读取速度,O(1)的删除速度,那么符合条件的数据结构就很清楚了</p>
<p>读取方面使用hashmap,而lru中,每次将读取的数据推后,保证最近最少使用的那个处于队头,这就涉及到空间的转移,那毫无疑问就是链表了</p>
<p>因此hashmap中存储对应元素项的指针,链表可以使用双向链表,每次使用的时候将该数据存放到队头或队尾,与此同时若长度超过限制则删除队尾或队头的数据</p>
</li>
</ul>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[字节一面面经]]></title>
<id>http://www.zerokirin.online/post/zi-jie-yi-mian-mian-jing/</id>
<link href="http://www.zerokirin.online/post/zi-jie-yi-mian-mian-jing/">
</link>
<updated>2021-08-21T12:05:14.000Z</updated>
<content type="html"><![CDATA[<h2 id="问题">问题</h2>
<ul>
<li>进程线程区别</li>
<li>进程间通信</li>
<li>线程间通信</li>
<li>go的channel是怎么实现的</li>
<li>go的调度模型</li>
<li>p的大小通常设置为多少,如果是io密集型应用呢</li>
<li>go的map实现方式</li>
<li>map退化成链表了怎么办</li>
<li>tcp的time wait状态</li>
<li>如果有太多的time wait状态该怎么办</li>
<li>mysql索引有哪几种</li>
</ul>
<h2 id="浅析">浅析</h2>
<ol>
<li>
<p>OS</p>
<ul>
<li>进程线程区别?
<ul>
<li>线程依赖于进程,比进程更轻量</li>
<li>进程是资源分配的基本单位</li>
<li>线程是调度的基本单位</li>
<li>线程可以共享同一个进程的资源</li>
<li>进程必须依靠IPC来通信</li>
</ul>
</li>
<li>进程间通信方式
<ul>
<li>信号</li>
<li>信号量</li>
<li>有名管道</li>
<li>匿名管道</li>
<li>共享内存</li>
<li>消息队列</li>
<li>套接字</li>
</ul>
</li>
<li>线程间通信
<ul>
<li>线程间通信的主要目的是用于多线程同步</li>
<li>互斥锁,条件变量(casp操作),读写锁,自旋锁</li>
<li>信号</li>
<li>信号量</li>
</ul>
</li>
</ul>
</li>
<li>
<p>go</p>
<ul>
<li>channel怎么实现的
<ul>
<li>核心是由发送队列,接收队列,和缓冲区组成</li>
<li>目前是用锁机制保护了先进先出的特性</li>
<li>发送的时候如果接收队列中有等待的goroutine则直接复制过去</li>
<li>接收的时候为了保证先进先出,会先从缓冲区复制到当前接收的goroutine,然后再把等待发送的goroutine唤醒,把内容复制到缓冲区,然后把阻塞的发送goroutine放回p队列</li>
<li>发送的时候还有一个保活操作,因为在阻塞的时候,该数据地址有可能已经没有引用了,gc扫描后会为白色,直接清理掉,因此在唤醒操作的后满做一次无意义的引用,可以使得该地址免于gc扫描</li>
</ul>
</li>
<li>go的调度模型
<ul>
<li>其实就是gmp模型,车轱辘话来回说,细节详见后续博文</li>
<li>P的大小设置为多少,io密集型应用呢?
<ul>
<li>默认设置为操作系统线程数量,例如我的4800h就是8核16线程,所以会初始化16个P队列</li>
<li>在调度过程中如果发生系统调用,P队列会主动与M解绑,由M去等待操作系统的io结束后,再自行寻找P队列进行操作</li>
<li>但是P和M的解绑是有延迟的,只有每次调度发生的时候才会彻底解绑,因此在io密集型应用中,会有大量的P处于等待状态,等待M的解绑,等待获取新的M</li>
<li>所以在io密集型应用中,需要将P设置的更大,更多,这样即使有很多很多P都在等待,依然有足够的P和M绑定,能够运行G,压榨cpu</li>
</ul>
</li>
</ul>
</li>
<li>map退化成链表后?
<ul>
<li>go里通过多个hash函数,每次初始化的时候有个随机的种子,保证最大限度地随机,如果还是这么头铁,那go里面没有任何优化手段,实质上就是在一个bucket列表中挨个查找了</li>
</ul>
</li>
</ul>
</li>
<li>
<p>tcp time-wait状态</p>
<ul>
<li>time-wait太多会怎样?
<ul>
<li>占用太多端口</li>
<li>占用大量无意义的资源,即每次传输的数据远小于一次连接的建立断开所花费的经历</li>
</ul>
</li>
<li>time-wait状态太多,怎么办?
<ul>
<li>tcp连接复用,人为在应用层将数据汇总发送,尽量让一个tcp连接承担更多的短数据传输&l
10BC8
t;/li>
<li>使用linux内核参数<code>tcp_tw_reuse</code>,这个参数能够在创建tcp连接的时候随机选一个处于time-wait状态的连接进行复用,此时,之前如果有延迟到来的数据,则会被抛弃,因为相当于开启了一个新的连接</li>
</ul>
</li>
</ul>
</li>
<li>
<p>mysql</p>
<ul>
<li>索引有哪几种
<ul>
<li>主键索引</li>
<li>唯一索引</li>
<li>联合索引</li>
<li>普通索引</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="算法">算法</h2>
<ol>
<li>如何判断二叉树是否对称?
<ul>
<li>层次遍历同时遍历左右节点就行</li>
</ul>
</li>
</ol>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[端点面经]]></title>
<id>http://www.zerokirin.online/post/duan-dian-mian-jing/</id>
<link href="http://www.zerokirin.online/post/duan-dian-mian-jing/">
</link>
<updated>2021-07-19T10:51:11.000Z</updated>
<content type="html"><![CDATA[<h2 id="问题">问题</h2>
<ul>
<li>有缓冲的channel 读取的时候是否会阻塞</li>
<li>如果没有数据还想读取应该怎么办</li>
<li>如果没有数据select去读会不会阻塞</li>
<li>函数中对一个slice做append,在没有触发扩容的情况下,函数外部是否会改变</li>
<li>协程什么时候会切出去</li>
<li>协程是怎么主动放弃执行权的</li>
<li>http中path在请求头还是请求体</li>
<li>http 怎么指定json呢</li>
<li>http 怎么指定body编码格式</li>
<li>https 握手流程</li>
<li>什么是restful api</li>
<li>客户端怎么保证证书是服务端发来的</li>
<li>内连接左连接和右连接有什么区别</li>
<li>什么情况下需要用事务</li>
<li>事务的隔离级别有哪些</li>
<li>系统调用的过程是怎样的</li>
<li>怎么样从用户态切换到内核态</li>
<li>进程间的内存隔离</li>
<li>进程间通信方式</li>
<li>有名管道和匿名管道有什么区别呢</li>
</ul>
<h2 id="浅析">浅析</h2>
<ol>
<li>
<p>channel</p>
<ul>
<li>有缓冲的channel在读取的时候如果没有数据依然是会阻塞的</li>
<li>读取channel的函数原型中有个形参叫做block,只有在select语句中,这个block的值是false,它控制了是否会进入自旋或是将读取的goroutine添加到接收队列中,如果是false的话,无法读取数据就直接返回了</li>
<li>函数的返回值有两个,selected和received
<ul>
<li>received控制着是否有数据返回,也就是具体有没有读取到数据</li>
<li>selected只在select中用来判断是否执行该case</li>
</ul>
</li>
</ul>
</li>
<li>
<p>slice</p>
<p>slice的结构已经是老生常谈的话题了,它包括指向了具体是数据地址的<code>unsafe.Pointer</code>,len和cap</p>
<p>常问的一般是函数外调用及扩容问题,这次的问题是在函数内进行了一次没有扩容的append操作会怎样,首先看看底层实现</p>
<figure data-type="image" tabindex="1"><img src="http://img.zerokirin.online/blog/4txn95.png" alt="slice" loading="lazy"></figure>
<ul>
<li>
<p>如果是直接传值调用的话,相当于对s做了一次拷贝,结果如图</p>
<figure data-type="image" tabindex="2"><img src="http://img.zerokirin.online/blog/02ejm3.png" alt="slice2" loading="lazy"></figure>
</li>
<li>
<p>在函数内部,直接修改s1的值的话,可以看到,其指向的地址和原变量s是相同的,因此外部也会跟着变化</p>
</li>
<li>
<p>但是执行append的时候稍有不同,在函数内部,对s1做append,势必会改变len和cap的值,而这两个值存在于新的变量s1中,在原变量s中是没有变化的,也就是说,哪怕数据已经写到了unsafe.Pointer指向的地址中,但是由于s无法看到len和cap的更改,因此他并不知道新的数据进来</p>
</li>
<li>
<p>要想在外部读取到数据,就需要用到unsafe.Pointer</p>
</li>
<li>
<pre><code class="language-go">len := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&s)) +uintptr(8)))
//为什么是+8,因为unsafe.Pointer的最底层也是个int,而go中int的大小不固定,最小是32,随操作系统位数而定,因此不能看做int32的别名,在这里我们通过 fmt.Println(unsafe.Sizeof(s)) 的结果是24,可以推断出,每个int占8个字节,因此后移8位
*len = 5
</code></pre>
</li>
</ul>
</li>
<li>
<p>http</p>
<ul>
<li>
<p>path在哪个部分</p>
<figure data-type="image" tabindex="3"><img src="http://img.zerokirin.online/blog/jxkhp1.png" alt="url构成" loading="lazy"></figure>
<p>上面的url转换到http请求中就是如下格式</p>
<pre><code class="language-http">GET /path/to/myfile.html?key1=value1&key2=value2 HTTP/1.1
Host: www.example.com
</code></pre>
</li>
<li>
<p>http协议版本,path和query在请求行中</p>
</li>
<li>
<p>域名(host)在请求头中(host是唯一必备的请求头)</p>
</li>
<li>
<p>锚点不进行http传输,仅仅为浏览器定位页面使用</p>
</li>
<li>
<p>通过Content-Type指定传输的数据格式,常见的有</p>
<ul>
<li>text/html 文本方式的html</li>
<li>text/plain 纯文本</li>
<li>text/xml 文本方式的xml</li>
<li>application/x-www-from-urlencoded 表单提交(普通表单,非上传)</li>
<li>application/json 数据以json形式编码</li>
<li>application/xml 数据以xml形式编码</li>
<li>multipart/form-data 表单上传图片、文件类型等附件时必须用该类型</li>
</ul>
</li>
<li>
<p>编码格式通过Content-Encoding和Content-Type 中的字段charset指定</p>
<ul>
<li>charset 设置的是文本具体用什么编码,例如utf-8,gbk</li>
<li>Content-Encoding 设置的是消息体通过什么编码方式压缩</li>
</ul>
</li>
<li>
<p>https握手流程,目前tls1.1刚刚被废除,tls1.3还没有普及,tls1.2还是主流,主要分为两种形式,rsa和dh</p>
<ul>
<li>
<p>rsa版本如下</p>
<ol>
<li>客户端初始化一个随机数client_random,向服务端发送client hello消息</li>
<li>服务端返回server hello消息,包括ca证书(ca证书里有服务端公钥)和随机数server_random,数字签名</li>
<li>客户端根据数字签名验证ca证书,同时生成新的随机数PreMaster,将PreMaster用服务端公钥加密,发送给服务端</li>
<li>现在双方都有client_random,server_random,PreMaster,通过这三个算出真正的主密钥</li>
<li>客户端算出来后给服务端发送 finished消息</li>
<li>服务段收到finished消息后验证是否有误,同时给客户端发送finished</li>
<li>双方都无误则成功握手</li>
</ol>
</li>
<li>
<p>dh版本,主要是前面交换key的步骤不同</p>
<ol>
<li>客户端初始化随机字符串client_random,向服务器发送client hello消息</li>
<li>服务端返回server hello,包括随机数server_random,再发送ca证书,server_pubkey</li>
<li>客户端验证证书及server_pubkey无误后向服务端发送client_pubkey</li>
<li>因为双方此时都有了下面这些,因此主密钥是可以通过dh算法算出来的(现在更多的用更安全的ECDHE算法)
<ul>
<li>server_pubkey和client_pubkey可以生成PreMaster</li>
<li>client_random和server_random</li>
</ul>
</li>
<li>所以在客户端发送pubkey的时候就可以把自己的finished一起发送出去</li>
<li>服务端收到后验证是否有误,同时发送finished给客户端</li>
<li>双方确认无误,成功握手</li>
</ol>
</li>
<li>
<p>客户端如何保证证书是服务端发来的</p>
<p>进行签名算法,上一步tls握手阶段的server hello消息会携带双方约定好的加密流程,例如</p>
<pre><code>Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
</code></pre>
<figure data-type="image" tabindex="4"><img src="http://img.zerokirin.online/blog/image-20210715010753948.png" alt="image-20210715010753948" loading="lazy"></figure>
<p>可以看到这里密钥交换用了ECDHE方法因此就是dh流程,而身份验证则是RSA算法,这是一种非对称加密方式,而私钥只有服务器才有,所以只要服务端对发送的证书等信息做一次摘要加密(hash)后,再对摘要用私钥加密,然后将密文和摘要同时发送给客户端,客户端收到后,自行计算一次摘要得到hash-1,再用公钥解密服务端发送来的密文得到hash-2,如果两个相同,说明服务器的确有和证书里说的公钥的对应私钥,那么这个证书的来源就是可靠的</p>
</li>
</ul>
</li>
<li>
<p>什么是restful api</p>
<blockquote>
<p>看Url就知道要什么<br>
看http method就知道干什么<br>
看http status code就知道结果如何</p>
<p>——<a href="https://www.zhihu.com/question/28557115/answer/41267240">怎样用通俗的语言解释REST,以及RESTful? - 知乎 (zhihu.com)</a></p>
</blockquote>
<p>这是我见过最简单,最清楚的描述,也是restful的核心思想,并不应该拘泥于patch,post等方法,重点是将api设计为人类高度可读的形式</p>
<p>url就是资源,method就是操作方式,code就是结果</p>
</li>
</ul>
</li>
<li>
<p>Mysql</p>
<ul>
<li>
<p>左连接和右连接有什么区别</p>
<ul>
<li>内连接是指,左右两张表中相同的数据组成的集合</li>
<li>左连接是内连接和左边全表的并集</li>
<li>右连接是内连接和右边全表的并集</li>
</ul>
</li>
<li>
<p>事务的隔离级别有哪些</p>
<ul>
<li>读未提交</li>
<li>读已提交</li>
<li>可重复读</li>
<li>可串行化</li>
</ul>
</li>
<li>
<p>什么时候使用事务</p>
<p>一次性对多个数据用多条sql语句修改时,在一个事务中,要么都执行,要么都不执行,这是原子性的体现,隔离性保证了多个事务之间互不相关,进而在原子性和隔离性的作用下,保证了数据库整体的一致性(这里的一致性指的是数据库的状态一直都是符合所有约束的)</p>
</li>
</ul>
</li>
<li>
<p>OS</p>
<ul>
<li>
<p>系统调用的过程是怎样的,以printf函数为例</p>
<ol>
<li>printf调用编写好的系统库函数(api)</li>
<li>api找到该系统调用的系统调用号,将它还有该调用所需的参数一同放入各个寄存器中</li>
<li>用int指令,像系统发送软中断,中断号是 0x80</li>
<li>cpu收到中断,切换到内核态,准备函数运行需要的栈空间,同时保存cpu上下文</li>
<li>根据系统调用号执行对应的函数</li>
<li>准备函数返回值,并从内核态中恢复cpu上下文</li>
<li>执行ret指令切回用户态</li>
</ol>
</li>
<li>
<p>怎样从用户态切换到内核态</p>
<ul>
<li>每个进程在运行的时候会在用户空间和内核空间分别分配栈,将<strong>用户空间的栈指针</strong>保存到<strong>内核空间的栈</strong>中,将<strong>cpu的栈指针寄存器</strong>的值改为<strong>内核空间的栈</strong></li>
<li>同时修改cpu的特权等级,将ring3(用户态)修改为ring0(内核态)</li>
</ul>
</li>
<li>
<p>进程间的内存是如何保证相互隔离的</p>
<p>虚拟内存技术,有如下优点:</p>
<ul>
<li>
<p>保证内存安全,防止用户空间访问内核空间</p>
</li>
<li>
<p>对进程透明,每个进程都有自己独立的内存空间,将进程的内存空间隔离,防止访问其他内存</p>
</li>
<li>
<p>可以灵活分配内存,例如,可以将一段连续的虚拟地址映射到分散的物理地址空间中</p>
</li>
<li>
<p>将部分内存中的数据存入磁盘,该磁盘被称为swap device,这样原内存就空出来了,可以存放更多的新数据,说白了就是强行扩充内存</p>
</li>
</ul>
</li>
<li>
<p>进程间通信方式</p>
<ul>
<li>管道(匿名管道) pipe</li>
<li>有名管道 fifo</li>
<li>信号 signal</li>
<li>消息队列 message</li>
<li>信号量 semaphore</li>
<li>共享内存 share memory</li>
<li>套接字 socket</li>
</ul>
</li>
<li>
<p>有名管道和匿名管道的区别(翻译)</p>
</li>
</ul>
<table>
<thead>
<tr>
<th></th>
<th><strong>PIPE</strong></th>
<th><strong>FIFO</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>描述</strong></td>
<td>无名的IPC对象</td>
<td>有名的IPC对象</td>
</tr>
<tr>
<td><strong>通信</strong></td>
<td>PIPE是系统本地的,不能用于网络的通信</td>
<td>FIFO可以在不同设备和网络间通信</td>
</tr>
<tr>
<td><strong>可见</strong></td>
<td>不存在于文件系统之中</td>
<td>在文件系统中存在</td>
</tr>
<tr>
<td><strong>Process</strong></td>
<td>在子进程和父进程之间通信</td>
<td>多个进程间,类似于服务器和客户端</td>
</tr>
<tr>
<td><strong>Creation</strong></td>
<td>PIPE is created by pipe () function.</td>
<td>FIFO is created by mkfifo () function.</td>
</tr>
<tr>
<td><strong>Read And Write Operation</strong></td>
<td>读写需要同时进行</td>
<td>无需同时进行</td>
</tr>
<tr>
<td><strong>Existence</strong></td>
<td>被关闭或者父子进程退出时,PIPE消亡</td>
<td>FIFO一直存在,即使进程退出,它仍会存在直到系统重启</td>
</tr>
<tr>
<td><strong>Control Over Ownership And Permissions</strong></td>
<td>无法控制所有权和权限</td>
<td>FIFO就像文件一样,你可以控制所有权和他的权限</td>
</tr>
<tr>
<td><strong>流通方向</strong></td>
<td>PIPE只能单项流通</td>
<td>FIFO是双向的,同一个FIFO可以被用作读写</td>
</tr>
<tr>
<td><strong>Mode Of Communication</strong></td>
<td>PIPE 提供简单数据流</td>
<td>FIFO 提供半双工数据流</td>
</tr>
<tr>
<td><strong>Communication</strong></td>
<td>具有共同祖先的进程的交流方式</td>
<td>共同祖先不是必要的</td>
</tr>
</tbody>
</table>
</li>
</ol>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Channel底层如何实现阻塞的]]></title>
<id>http://www.zerokirin.online/post/channel-di-ceng-ru-he-shi-xian-zu-sai-de/</id>
<link href="http://www.zerokirin.online/post/channel-di-ceng-ru-he-shi-xian-zu-sai-de/">
</link>
<updated>2021-06-29T06:24:06.000Z</updated>
<content type="html"><![CDATA[<blockquote>
<p>每日一问系列(一)</p>
</blockquote>
<p>我们都知道,channel在缓冲区满或者无缓冲的时候,发送数据会阻塞,缓冲区空或无缓冲的时候,接受也会阻塞</p>
<p>那么这个阻塞,是怎么实现的呢?</p>
<p>之前我们只是粗略的讲解了channel的实现的结构体,这次我们详细的看看发送和接受的源码</p>
<p>源码位于<code>go 1.15.4 src/runtime/chan.go</code></p>
<h2 id="读取的两种方式">读取的两种方式</h2>
<pre><code class="language-go">ch := make(chan int, 10)
a := <-ch
b,ok := <-ch
</code></pre>
<p>golang有两种读取channel 的方式,</p>
<pre><code class="language-go">// entry points for <- c from compiled code
//go:nosplit
func chanrecv1(c *hchan, elem unsafe.Pointer) {
chanrecv(c, elem, true)
}
//go:nosplit
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) { //看这里多了个返回值,received
_, received = chanrecv(c, elem, true)
return
}
</code></pre>
<p>这两个函数也就是读取的两个方法,第二个带有一个名为received的返回值</p>
<p>他们同时都调用了<code>chanrecv</code>函数,所以重点就在这个函数中</p>
<h2 id="读取函数">读取函数</h2>
<p>函数原型</p>
<pre><code class="language-go">// chanrecv receives on channel c and writes the received data to ep.
// ep may be nil, in which case received data is ignored.
// If block == false and no elements are available, returns (false, false).
***重点在这,如果block是false,且无法获取到数据,则直接返回(false,false)
***这个的另一个意思就是,即便缓冲区buf为空,也不会阻塞,而是直接返回,那么这个的使用场景是?
// Otherwise, if c is closed, zeros *ep and returns (true, false).
// Otherwise, fills in *ep with an element and returns (true, true).
// A non-nil ep must point to the heap or the caller's stack.
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool)
</code></pre>
<p>上面调用chanrecv的函数里面都是写死了block为true,那么什么时候是false呢?</p>
<pre><code class="language-go">// compiler implements
//
// select {
// case v = <-c:
// ... foo
// default:
// ... bar
// }
//
// as
//
// if selectnbrecv(&v, c) {
// ... foo
// } else {
// ... bar
// }
//
func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected bool) {
selected, _ = chanrecv(c, elem, false)
return
}
// compiler implements
//
// select {
// case v, ok = <-c:
// ... foo
// default:
// ... bar
// }
//
// as
//
// if c != nil && selectnbrecv2(&v, &ok, c) {
// ... foo
// } else {
// ... bar
// }
//
func selectnbrecv2(elem unsafe.Pointer, received *bool, c *hchan) (selected bool) {
// TODO(khr): just return 2 values from this function, now that it is in Go.
selected, *received = chanrecv(c, elem, false)
return
}
</code></pre>
<p>select的用法就是,哪个可以取到数据就执行哪个case,而如果无法读取到则不会执行,所以肯定不能是阻塞的,所以两个用法的block的都是false</p>
<p>chanrecv的返回值名字也很直白,selected和received</p>
<p>selected只在执行select语句的时候判断case是否可以执行,至于数据是否获取到,则要看第二个参数received</p>
<p>那么我们详细来看chanrecv的内容吧,过程中我删掉了部分race检测的代码以及原有的英文注释</p>
<pre><code class="language-go">//原子操作检测是否能读取或者缓冲是否为空
func empty(c *hchan) bool {
// c.dataqsiz is immutable.
if c.dataqsiz == 0 { //无缓冲,检测发送队列是否是nil
return atomic.Loadp(unsafe.Pointer(&c.sendq.first)) == nil
}
//有缓冲,检测当前数量有多少
return atomic.Loaduint(&c.qcount) == 0
}
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
//如果是nil
if c == nil {
//在select语句中block是false,所以会在这直接返回(false,false),也就是说select永远不会进入一个对nil的chan操作的步骤
if !block {
return
}
//阻塞了,所以读取nil并不会直接报错
gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
throw("unreachable")
}
// chan中是空的,没有办法直接获取值,且在select中,无需等待取值
if !block && empty(c) {
//这里官方用了很长一段来描述为什么会这么写,大意是说
//在竞争检测关闭时,可能会有多个goroutine对channel进行操作
//比如说,一开始是,没有close的,非空的
//后来被关闭了,然后把数据排空了
//这时候由于检测语句的不合理安排以及并发访问
//很有可能会得出一个,没有close的,空了的结果
//所以对empty和closed都是原子操作去检测
if atomic.Load(&c.closed) == 0 { //未关闭
//由于channel不能关了再开,所以如果检测到未关闭
//则证明在一开始就是未关闭的
//返回false,false
//select不会继续下去,因为他还有收到值的可能性
return
}
//执行到这一定是closed的了,再检测一次empty是因为
//有可能在上面判断closed之前,有了新的数据,那么可能会是closed和非空
if empty(c) {
//确定是空,若接受体不为空,也就是不用_抛弃,则直接置为0值
if ep != nil {
typedmemclr(c.elemtype, ep)
}
//selected,可以执行这条路,但是v,ok=<-ch的ok是false
return true, false
}
}
//后文初始化sudog的条件(暂时看不懂干嘛的,下次研究)
var t0 int64
if blockprofilerate > 0 {
t0 = cputicks()
}
//加锁
lock(&c.lock)
//closed,且 缓冲buf 是空的
//无论有无缓冲区,都是这个流程
if c.closed != 0 && c.qcount == 0 {
unlock(&c.lock)
if ep != nil { //清空接收者
typedmemclr(c.elemtype, ep)
}
return true, false
}
//如果发送队列不为空的话执行,注意
//也就是要么无缓冲,要么缓冲buf满了,两种情况处理方法不一样,recv函数待会再讲
if sg := c.sendq.dequeue(); sg != nil {
recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
return true, true
}
//数据个数大于0,那肯定是有缓冲类型的
if c.qcount > 0 {
//通过这个chanbuf,获取到数据地址,存到qp中
qp := chanbuf(c, c.recvx)
//把qp移动到ep中
if ep != nil {
typedmemmove(c.elemtype, ep, qp)
}
//原来的数据置空
//还有一点就是,如果上一个ep是nil,也就是用_忽略了的话,这一步也会把数据清掉
typedmemclr(c.elemtype, qp)
//循环链表,所以下标正常增长
c.recvx++
//涨到最大值后变为0,循环嘛
if c.recvx == c.dataqsiz {
c.recvx = 0
}
//取数据了,所以肯定要减小
c.qcount--
unlock(&c.lock)
return true, true
}
/* 前面我们已经排除了
* closed的,有发送队列的(缓冲类型溢出了,无缓冲类型有被阻塞的发送),缓冲区有值的
* 剩下的就是,未closed的,缓冲区没数据且发送队列空的
* 人话就是,轮到阻塞等待有缘人发送数据了
*/
//select中没必要阻塞,直接就略过了
if !block {
unlock(&c.lock)
return false, false
}
//从这里开始就是阻塞的流程
//获取当前运行的g的指针
gp := getg()
//获取一个sudog结构体
mysg := acquireSudog()
mysg.releasetime = 0
if t0 != 0 {
mysg.releasetime = -1
}
// No stack splits between assigning elem and enqueuing mysg
// on gp.waiting where copystack can find it.
mysg.elem = ep
mysg.waitlink = nil
//当前g正在等待的sudog
gp.waiting = mysg
//对应的g
mysg.g = gp
//是否在select语句中
mysg.isSelect = false
//标识是哪个chan
mysg.c = c
//唤醒时传递的参数,一般都是空
gp.param = nil
//将这个sudog放到接收队列中,等待唤醒
c.recvq.enqueue(mysg)
// Signal to anyone trying to shrink our stack that we're about
// to park on a channel. The window between when this G's status
// changes and when we set gp.activeStackChans is not safe for
// stack shrinking.
//这里设置个标志位,通知任何想要缩小栈空间情况,告诉他们这个g现在在等待channel的唤醒
//这里会有线程安全问题,所以用atomic操作
atomic.Store8(&gp.parkingOnChan, 1)
//暂停当前g,也就是实质上的阻塞,函数原型放在最后
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanReceive, traceEvGoBlockRecv, 2)
// someone woke us up
//唤醒,检查下有没有呗错误唤醒
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
//等待的置空
gp.waiting = nil
gp.activeStackChans = false
if mysg.releasetime > 0 {
blockevent(mysg.releasetime-t0, 2)
}
closed := gp.param == nil
gp.param = nil
mysg.c = nil
//释放sudog
releaseSudog(mysg)
return true, !closed
}
// Puts the current goroutine into a waiting state and calls unlockf.
// If unlockf returns false, the goroutine is resumed.
// unlockf must not access this G's stack, as it may be moved between
// the call to gopark and the call to unlockf.
// Reason explains why the goroutine has been parked.
// It is displayed in stack traces and heap dumps.
// Reasons should be unique and descriptive.
// Do not re-use reasons, add new ones.
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int)
</code></pre>
<p>再来让我们看看上面说的recv函数,上面的调用方式是这样的</p>
<p><code>recv(c, sg, ep, func() { unlock(&c.lock) }, 3)</code></p>
<pre><code class="language-go">// recv processes a receive operation on a full channel c.
// There are 2 parts:
// 1) The value sent by the sender sg is put into the channel
// and the sender is woken up to go on its merry way.
// 2) The value received by the receiver (the current G) is
// written to ep.
// For synchronous channels, both values are the same.
// For asynchronous channels, the receiver gets its data from
// the channel buffer and the sender's data is put in the
// channel buffer.
// Channel c must be full and locked. recv unlocks c with unlockf.
// sg must already be dequeued from c.
// A non-nil ep must point to the heap or the caller's stack.
//上面注释写的很清楚,两个情况,一个是缓冲区满了,一个是没有缓冲区的
//总归都是sg不为空,也就是发送队列有人在等着发送数据
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
//缓冲区0,也就是无缓冲
if c.dataqsiz == 0 {
if ep != nil {
// copy data from sender
//直接从发送者的sg中提取内容