MoreRSS

site iconAnZhihe | 安志合修改

国学和传统文化爱好者,IT行业从业者,运维和SRE。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

AnZhihe | 安志合的 RSS 预览

etcd性能问题排查及解决方案

2025-03-31 08:03:40

最近遇到一个客户k8s测试集群经常崩溃,最终定位是etcd磁盘IO性能不足,最终替换成ssd盘解决,记录一下排查过程。

集群是跑在客户的共享虚机上,磁盘是机械硬盘,问题现象如下:

  1. kube-system下涉及高可用的组件 kube-apiserver、kube-controller-manager、kube-scheduler 频繁重启,某些选主模式的组件、服务反复重启,频繁CrashLoopBackoff,查看其日志有"leaderelection lost",“timed out waiting for xxx”等字样

  2. kubectl 反应慢,使用 kubectl get no --v=7,两三秒内才返回结果

  3. 查看kube-apiserver日志,看到有“etcdserver: request timed out”、“etcdserver:leader changed”等字样

  4. 查看etcd日志,有较多“xxx took too long”,“lost leader”等字样,time spent达到了几百ms,甚至两三秒

从现象上看,问题很可能出现在etcd上~

etcd性能瓶颈及稳定性分析链路图

1743405770714156.jpg

图片来源:极客时间《etcd实战课》

etcd集群性能受磁盘IO、时钟同步、master之间的网络以及节点负载影响,根据经验,磁盘IO导致的可能性较大。

1、时间同步验证

使用pssh工具,或ssh 机器执行date命令,对比时钟差异是否超过1s。时钟不同步,可以使用chrony或ntp或自建时钟源,将集群所有节点连接到同一时钟源,确保时钟同步。参考:Chrony详解:代替ntp的时间同步服务

2、master节点负载、CPU、MEM、IO、NET等检察,查看系统日志是否有明显报错,比如硬件报错,OOM(out of memory)等

使用top,iostat,iftop,iotop,sar,netstat,vmstat查看系统基础指标

使用scp,ipref,dd,fio等工具测试网络及磁盘性能:

# 制作1GB文件
dd if=/dev/zero of=/tmp/test-net bs=1M count=1000

# 测试传输速度
scp /tmp/test-net root@$master2:/tmp/
scp /tmp/test-net root@$master3:/tmp/

一般来说千兆带宽得达到约125MiB/s的传输速度;大规模集群( 500节点)需要兆带宽,网络需要达到约1250MiB/s的传输速度

etcd应用层提供了节点之间网络统计的metrics指标,分别如下:

  • etcd_network_active_peer,表示peer之间活跃的连接数;

  • etcd_network_peer_round_trip_time_seconds,表示peer之间RTT延时;

  • etcd_network_peer_sent_failures_total,表示发送给peer的失败消息数;

  • etcd_network_client_grpc_sent_bytes_total,表示server发送给client的总字节数,通过这个指标我们可以监控etcd出流量;

  • etcd_network_client_grpc_received_bytes_total,表示server收到client发送的总字节数,通过这个指标可以监控etcd入流量。

可以使用监控系统监控etcd网络指标,使用相关工具查看是否出现丢包等错误。

# 4k写
fio  -direct=1 -iodepth=128 -thread -rw=write -ioengine=libaio -bs=4k -size=1G -numjobs=1-runtime=600 -group_reporting -name=mytest -filename=/var/lib/etcd/iotest
# 4k读
fio  -direct=1 -iodepth=128 -thread -rw=read -ioengine=libaio -bs=4k -size=1G -numjobs=1-runtime=600 -group_reporting -name=mytest -filename=/var/lib/etcd/iotest
# 4K随机写:
fio  -direct=1 -iodepth=128 -thread  -rw=randwrite -ioengine=libaio -bs=4k -size=1G -numjobs=1-runtime=600 -group_reporting -name=mytest -filename=/var/lib/etcd/iotest
# 4K 随机读
fio  -direct=1 -iodepth=128 -thread -rw=randread -ioengine=libaio -bs=4k -size=1G -numjobs=1-runtime=600 -group_reporting -name=mytest -filename=/var/lib/etcd/iotest
# 1M写
fio  -direct=1 -iodepth=64 -thread -rw=write -ioengine=libaio -bs=1M -size=8G -numjobs=1-runtime=600 -group_reporting -name=mytest -filename=/var/lib/etcd/iotest
# 1M读
fio  -direct=1 -iodepth=64 -thread -rw=read -ioengine=libaio -bs=1M -size=8G -numjobs=1-runtime=600 -group_reporting -name=mytest -filename=/var/lib/etcd/iotest

Master配置一块独立块设备:SSD是首选,推荐 [SSD 4K IOPS>=3300] [企业级SAS硬盘rpm>=10000,最好15000][更高配置的NVMe)],参考:Linux系统下查看硬盘转速

3、kubectl descrbe node查看节点负载信息,在输出中检查 Allocated Resources 和 Conditions 部分,可以将占用高的无状态应用迁走,减轻master节点压力

最后排查发现master节点网络、节点负载、时间同步都正常,环境由于是共享虚拟机环境,机器IO性能很可能存在欺骗现象。

etcd性能不足原因定位:

1、检查 etcd 状态:

kubectl get pods -n kube-system -l component=etcd

2、查看 etcd 日志:[zkqw]

kubectl logs -n kube-system etcd-<node-name>

image.png

image.pngimage.png

3、查看 etcd 集群状态:etcdctl endpoint status

ETCDCTL_API=3 etcdctl --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key endpoint status -w table

4、查看 etcd 集群健康状态:etcdctl endpoint health

ETCDCTL_API=3 etcdctl --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key endpoint health -w table

image.png

5、etcd 集群性能测试:etcdctl check perf

ETCDCTL_API=3 etcdctl --endpoints=10.0.1.6:2379,10.0.1.7:2379,10.0.1.8:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key check perf

结果有FAIL

image.png

6、etcd 磁盘IO指标:

1)etcd_disk_backend_commit_duration_seconds:后端提交延迟,将最近的更改保存到磁盘所需的时间(以秒为单位),一般P99在120ms内。

释义:The latency distributions of commit called by backend.

curl -s --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/peer.crt --key /etc/kubernetes/pki/etcd/peer.key  https://127.0.0.1:2379/metrics | grep backend_commit_duration_seconds

image.png

上面的输出是一个类似CDF的表,表示了每一行“小于等于x秒完成的commit的数目”,最后一行代表了统计的总数。例如line7,表示完成时间<=16ms的commit有1064个,最后一行表示统计总数为9340,所以<=16ms的commit占比达到 1064/9340=11.4%。一般来说,满足要求的指标是小于25ms的超过99%,所以上面这个统计远远达不到要求。当disk_backend_commit指标的异常时候,说明事务提交过程中的B+ tree树重平衡、分裂、持久化dirty page、持久化meta page等操作耗费了大量时间。

2)etcd_disk_wal_fsync_duration_seconds:WAL日志同步延迟,将预写日志 (WAL) 中存在的 pending changes 持久保存到磁盘所需的时间(以秒为单位),一般本地SSD盘P99延时在10ms内。

释义:The latency distributions of fsync called by wal

curl -s --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/peer.crt --key /etc/kubernetes/pki/etcd/peer.key  https://127.0.0.1:2379/metrics | grep wal_fsync_duration_seconds

image.png

一般来说,满足要求的指标是小于10ms的超过99%,16737/33744=50%,达不到要求。

当disk_wal_fsync指标异常的时候,一般是底层硬件出现瓶颈或异常导致。当然也有可能是CPU高负载、cgroup blkio限制导致的。

参考文献:https://etcd.io/docs/v3.5/faq/#what-does-the-etcd-warning-failed-to-send-out-heartbeat-on-time-mean

若disk_backend_commit较高、disk_wal_fsync却正常,说明瓶颈可能并非来自磁盘I/O性能,也许是B+ tree的重平衡、分裂过程中的较高时间复杂度逻辑操作导致。

3)etcd_server_leader_changes_seen_total:领导者变更次数

在etcd中,Leader节点会根据heartbeart-interval参数(默认100ms)定时向Follower节点发送心跳。如果两次发送心跳间隔超过2*heartbeart-interval,就会打印此警告日志。超过election timeout(默认1000ms),Follower节点就会发起新一轮的Leader选举。

etcd默认心跳间隔是100ms,较小的心跳间隔会导致发送频繁的消息,消耗CPU和网络资源。而较大的心跳间隔,又会导致检测到Leader故障不可用耗时过长,影响业务可用性。一般情况下,为了避免频繁Leader切换,建议你可以根据实际部署环境、业务场景,将心跳间隔时间调整到100ms到400ms左右,选举超时时间要求至少是心跳间隔的10倍。

curl -s --cacert /etc/kubernetes/pki/etcd/ca.crt --cert /etc/kubernetes/pki/etcd/peer.crt --key /etc/kubernetes/pki/etcd/peer.key  https://127.0.0.1:2379/metrics | grep etcd_server_leader_changes_seen_total

image.png

4)etcd_mvcc_db_total_size_in_bytes etcd 数据库的大小(以字节为单位),默认情况下,etcd 最多可以存储 2 GiB 的数据。该最大值是可配置的,但 etcd 建议将最大大小设置为不超过 8 GiB。如果达到最大大小,etcd 将不再接受任何写入,并且 Kubernetes 无法执行集群管理任务,例如调度新的 pod。

etcd适合读多写少的业务场景,若写请求较大,很容易出现容量瓶颈,导致高延时的读写请求产生。

etcd是一个对磁盘IO性能非常敏感的存储系统,磁盘IO性能不仅会影响Leader稳定性、写性能表现,还会影响读性能。线性读性能会随着写性能的增加而快速下降。如果业务对性能、稳定性有较大要求,建议尽量使用SSD盘。

etcd机械盘替换ssd盘:

etcd一般是随Master节点一同部署,每个Master节点会跑1个etcd实例,注意每次只对一个Master节点做替换,确认没问题后再操作下一个Master节点上的etcd

# 备份etcd数据
ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/peer.crt --key=/etc/kubernetes/pki/etcd/peer.key snapshot save /root/etcd_backup/etcd-backup.db

# 验证快照命令
etcdctl snapshot status /root/etcd_backup/etcd-backup.db -w table

# 停止kubelet
systemctl stop kubelet

# 静态pod启动的etcd,先将etcd.yaml从/etc/kubernetes/manifests里挪走
mv /etc/kubernetes/manifests/etcd.yaml /tmp/

# 备份etcd数据
cp -rp /var/lib/etcd /root/

# 卸载原来的磁盘
umount /var/lib/etcd

# 假设新的ssd磁盘为/dev/vdb,格式化新的磁盘并挂载,挂载信息注意写入/etc/fstab
mkfs.ext4 -F /dev/vdb
mount /dev/vdb /var/lib/etcd

# 将备份数据还原
cp -rp etcd/* /var/lib/etcd/

# 将etcd.yaml挪回到/etc/kubernetes/manifests里
mv /tmp/etcd.yaml /etc/kubernetes/manifests/

# 启动kubelet,拉起新的etcd pod
systemctl restart kubelet

# 检查etcd是否正常启动,查看etcd集群是否正常,都正常再继续操作下一个节点
kubectl get pods -n kube-system -l component=etcd

更换ssd盘后效果:

image.pngimage.png

image.png


参考:

[/zkqw]

高效学习(三):深度,归纳和坚持实践

2025-03-28 11:24:54

系统地学习

在学习某个技术的时候,我除了会用到上篇文章中提到的知识图,还会问自己很多个为什么。于是,我形成了一个更高层的知识脑图。下面我把这这个方法分享出来。当然学习一门技术时,Go 语言也好,Docker 也好,我都有一个学习模板。只有把这个学习模板中的内容都填实了,我才罢休。这个模板如下。

  1. 这个技术出现的背景、初衷和要达到什么样的目标或是要解决什么样的问题。这个问题非常关键,也就是说,你在学习一个技术的时候,需要知道这个技术的成因和目标,也就是这个技术的灵魂。如果不知道这些的话,那么你会看不懂这个技术的一些设计理念。

  2. 这个技术的优势和劣势分别是什么,或者说,这个技术的 trade-off 是什么。任何技术都有其好坏,在解决一个问题的时候,也会带来新的问题。另外,一般来说,任何设计都有 trade-off(要什么和不要什么),所以,你要清楚这个技术的优势和劣势,以及带来的挑战。

  3. 这个技术适用的场景。任何技术都有其适用的场景,离开了这个场景,这个技术可能会有很多槽点,所以学习技术不但要知道这个技术是什么,还要知道其适用的场景。没有任何一个技术是普适的。注意,所谓场景一般分别两个,一个是业务场景,一个是技术场景。

  4. 技术的组成部分和关键点。这是技术的核心思想和核心组件了,也是这个技术的灵魂所在了。学习技术的核心部分是快速掌握的关键。

  5. 技术的底层原理和关键实现。任何一个技术都有其底层的关键基础技术,这些关键技术很有可能也是其它技术的关键基础技术。所以,学习这些关键的基础底层技术,可以让你未来很快地掌握其它技术。可以参看我在 CoolShell 上写的 Docker 底层技术那一系列文章。

  6. 已有的实现和它之间的对比。一般来说,任何一个技术都会有不同的实现,不同的实现都会有不同的侧重。学习不同的实现,可以让你得到不同的想法和思路,对于开阔思维,深入细节是非常重要的。

基本上来说,如果你按照我上面所提的这 6 大点来学习一门技术,你一定会学习到技术的精髓,而且学习的高度在一开始就超过很多人了。如果你能这样坚持 2-3 年,我相信你一定会在某个领域成为炙手可热的佼佼者。

举一反三

举一反三的道理人人都知道,所以,在这里我并不想讨论为什么要举一反三,而是想讨论如何才能有举一反三的能力。我认为,人与人最大的差别就是举一反三的能力。那些聪明的或者是有经验的人举一反三起来真是太令人惊叹。

我觉得一个人的举一反三能力,可以分解成如下三种基本能力。

  1. 联想能力。这种能力的锻炼需要你平时就在不停地思考同一个事物的不同的用法,或是联想与之有关的别的事物。对于软件开发和技术学习也一样。

  2. 抽象能力。抽象能力是举一反三的基本技能。平时你解决问题的时候,如果你能对这个问题进行抽象,你就可以获得更多的表现形式。抽象能力需要找到解决问题的通用模型,比如数学就是对现实世界的一种抽象。只要我们能把现实世界的各种问题建立成数据模型(如,建立各种维度的向量),我们就可以用数学来求解,这也是机器学习的本质。

  3. 自省能力。所谓自省能力就是自己找自己的难看。当你得到一个解的时候,要站在自己的对立面来找这个解的漏洞。有点像左右手互博。这种自己和自己辩论的能力又叫思辨能力。将自己分裂成正反方,左右方,甚至多方,站在不同的立场上来和自己辩论,从而做到不漏过一个 case,从而获得完整全面的问题分析能力。

在这方面,我对自己的训练如下。

  1. 对于一个场景,制造出各种不同的问题或难题。

  2. 对于一个问题,努力寻找尽可能多的解,并比较这些解的优劣。

  3. 对于一个解,努力寻找各种不同的测试案例,以图让其健壮。

老实说,要获得这三种能力,除了你要很喜欢思考和找其它人来辩论或讨论以外,还要看你自己是否真的善于思考,是否有好奇心,是否喜欢打破沙锅问到底,是否喜欢关注细节,做事是否认真,是否严谨……

这一系列的能力最终能构建出你强大的思考力,而这个思考力会直接转换成你的求知和学习能力。其实,我也是在不断地加强自己的这些能力。

总结和归纳

对自己的知识进行总结和归纳是提高学习能力的一个非常重要的手段。这是把一个复杂问题用简单的语言来描述的能力。就像我小时候上学时,老师让我们写文章的中心思想一样。这种总结和归纳能力会让你更好地掌握和使用知识。

也就是说,我们把学到的东西用自己的语言和理解重新组织并表达出来,本质上是对信息进行消化和再加工的过程,这个过程可能会有信息损失,但也可能会有新信息加入,本质上是信息重构的过程。我们积累的知识越多,在知识间进行联系和区辨的能力就越强,对知识进行总结和归纳也就越轻松。而想要提高总结归纳的能力,首先要多阅读,多积累素材,扩大自己的知识面,多和别人讨论,多思辨,从而见多识广。

不过,我们需要注意的是,如果只学了部分知识或者还没有学透,就开始对知识进行总结归纳,那么总结归纳出来的知识结构也只能是混乱和幼稚的。因此,学习的开始阶段,可以不急于总结归纳,不急于下判断,做结论,而应该保留部分知识的不确定性,保持对知识的开放状态。当对整个知识的理解更深入,自己站的位置更高以后,总结和归纳才会更有条理。总结归纳更多是在复习中对知识的回顾和重组,而不是一边学习一边就总结归纳。

我们来总结一下做总结归纳的方法:把你看到和学习到的信息,归整好,排列好,关联好,总之把信息碎片给结构化掉,然后在结构化的信息中,找到规律,找到相通之处,找到共同之处,进行简化、归纳和总结,最终形成一种套路,一种模式,一种通用方法

要训练自己这方面的能力,你需要多看一些经典的方法论图书,看看别人是怎样总结和归纳知识的。你可以在一开始模仿并把自己的理解的知识给写出来,写博客会是一种很好的方式。另外一种更好的方式是讲一遍给别人听。总之,你需要把你总结归纳的知识公开出来,给别人看,接受别人的批评和反馈,这样你才能成长得更快。其实,我也在锻炼这样的能力。

如果你在 Coolshell 上看过我写的《TCP 的那些事儿》,你就能知道我对《TCP/IP 详解》这本这么厚的书以及一些日常工作经验的总结,我写成了两篇比较简单的博客。你需要像我一样扩大自己的知识面,然后学会写博客,就能慢慢地拥有这种能力了。这种将信息删减、精炼和归纳的方法,可以让你的学习能力得到快速的提升。当你这么做的时候,一方面是在锻炼你抓重点的能力,另一方面是在锻炼你化繁为简的能力。这两种能力都是让你高效学习的能力。

最后,还想说一下,一般来说,拥有这样能力的人,都需要有在更高的维度上思考问题的能力。比如一些名人的金句,就是这种能力的体现。这种能力需要你非常深入的思考,需要你的阅历和经验,当然,和聪明人在一起也是提升这种能力的最有效的选择。

实践出真知

所谓实践出真知,也就是学以致用,不然只是纸上谈兵,误国误民。只有实践过,你才能对学到的东西有更深的体会。就像我看 《Effective C++》和《More Effective C++》这两本书一样,一开始看的时候,我被作者的那种翻来覆去不断找到答案又否定自己的求知精神所折服。但是,作者的这种思维方式只有在我有了很多的实践和经验(错误)后,才能够真正地体会为什么是这样的。

这两书不厚,但是,我看了十多年,书中的很多章节我都可以背出来,但是我想得到的不是这些知识,而是这种思维方式,这需要我去做很多的编程工作才能真正明白,才会有斯科特·迈耶斯(Scott Meyers)那样的思维方式,这才是最宝贵的。

另外,实践出真知也就是英文中的 Eat your own dog food。吃自己的狗粮,你才能够有最真实的体会。那些大公司里的开发人员,写完代码,自己不测试,自己也不运维,我实在不知道他们怎么可能明白什么是好的设计,好的软件?不吃自己的狗粮,不养自己的孩子,他们就不会有痛苦,没有痛苦,就不会想改进,没有改进的诉求也就不会有学习的动力,没有学习,就不会进步,没有进步就只会开发很烂的软件……不断地恶性循环下去。

实践是很累很痛苦的事,但只有痛苦才会让人反思,而反思则是学习和改变自己的动力。Grow up through the pain, 是非常有道理的

坚持不懈

坚持不懈是一句正确的废话。前段时间,我在我的读者群中发起了一个名为 ARTS 的活动。每人每周写一个 ARTS:Algorithm 是一道算法题,Review 是读一篇英文文章,Technique/Tips 是分享一个小技术,Share 是分享一个观点。我希望大家可以坚持一年,但是我也相信,能够坚持下来的人一定很少,绝大多数人都是虎头蛇尾的,但是我依然相信会有人坚持下来的。

坚持是一件反人性的事,所以,它才难能可贵,也更有价值。我从 2003 年写 blog 到今天 15 年了,看书学习写代码,我都会一点一点的坚持。人不怕笨,怕的是懒,怕的是找到各种理由放弃。

这里,我想鼓励一下你。现在很多国外的在线视频课都是 3-5 分钟一节课,一共 20 节课,总时长不到两个小时。然而,你会发现,能坚持看完的不到千分之一。当年 Leetcode 只有 151 道题的时候,一共有十几万人上来做题,但全部做完的只有十几个,万分之一。所以,只要你能坚持,就可以超过这个世界上绝大多数人。想一想,如果全中国有 100 万个程序员,只要你能坚持学习技术 2-3 年,你就可以超过至少 99 万人了(可能还更多)。

当然,坚持也不是要苦苦地坚持,有循环有成就感的坚持才是真正可以持续的。所以,一方面你要把你的坚持形成成果晒出来,让别人来给你点赞,另一方面,你还要把坚持变成一种习惯,就像吃饭喝水一样,你感觉不到太多的成本付出。只有做到这两点,你才能够真正坚持

希望我的这些话可以让你有足够的动力坚持下去。

小结

总结一下今天的内容。我分享了系统学习、举一反三、总结归纳、实践出真知和坚持不懈等几个方面的内容。

  • 在系统学习中,我给出了我学习时用的学习模板,它不但有助于你学习到技术的精髓,更能帮你提升你的学习高度。坚持几年,你一定能在某个领域成为炙手可热的佼佼者。

  • 在举一反三中,我分享了如何获得这种能力的方法。

  • 在总结和归纳中,我指出,积累的知识越多,在知识间进行联系和区辨的能力越强,总结归纳的能力越强,进而逐渐形成在更高维度上思考问题的能力。

  • 在实践出真知中,我阐明了实践的重要性,并认为,只有实践过,才能对学到的东西有更深的体会。

  • 最后,我强调,虽然学习方法很重要,但坚持不懈更为重要,并给出了怎样做才能让自己对学习这件反人类的事儿坚持不懈。

来源:《左耳听风专栏:高效学习》

SAML2单点登陆接入流程分析

2025-03-25 08:52:47

最近有个客户系统需要接入自己内部的统一认证服务,采用的是saml2.0协议,系统自身的iam充当SP角色,客户内部统一认证充当Idp服务,最后由于客户SSO流程返回数据不是标准的SAML2.0协议,没有带回请求时RelayState带的参数数据,导致iam解析SAML2.0协议异常,整个交互流程通过wireshark抓包分析,不然又要被客户diao了~。


SAML协议介绍

SAML(Security Assertion Markup Language)是一种用于在不同安全域之间交换认证和授权数据的XML标准,常用于实现单点登录(SSO)。SAML单点登录的交互流程及主要角色介绍如下。

主要角色

  1. 用户(User)

  • 试图访问服务的最终用户。

  • 服务提供者(Service Provider, SP)

    • 提供具体服务的实体,如Web应用或云服务。

    • 依赖身份提供者(IdP)进行用户认证。

  • 身份提供者(Identity Provider, IdP)

    • 负责用户认证并生成SAML断言。

    • 向服务提供者(SP)提供认证信息。

  • 用户代理(User Agent)

    • 通常是用户的浏览器,负责在IdP和SP之间传递消息。

    SAML单点登录交互流程

    saml-flow-overview.18f29125.png

    1. 用户访问SP

    • 用户通过浏览器访问SP的资源。

  • SP生成SAML请求

    • SP生成SAML认证请求(AuthnRequest),并将用户重定向到IdP。

  • 用户被重定向到IdP

    • 用户代理将SAML请求发送到IdP。

  • IdP认证用户

    • IdP验证用户身份(如通过用户名和密码)。

  • IdP生成SAML响应

    • IdP生成包含用户信息的SAML断言,并创建SAML响应(Response)。

  • 用户被重定向回SP

    • IdP将SAML响应发送回用户代理,用户代理再将其转发给SP。

  • SP验证SAML响应

    • SP验证SAML响应的有效性,并提取用户信息。

  • 用户访问资源

    • SP根据SAML断言中的信息,授权用户访问资源。

    SP 与 IdP 之间通信方式

    SP 与 IdP 之间的通信方式分为 HTTP Redirect Binding、HTTP POST Binding、HTTP Artifact Binding。每种方式在不同的阶段会用不同类型的 HTTP 与对方通信。

    HTTP Redirect Binding

    SP 通过重定向 GET 请求把 SAML Request 发送到 IdP,IdP 通过立即提交的 Form 表单以 POST 请求的方式将 SAML Response 发到 SP。

    HTTP-Redirect-Binding.0d2be36e.png

    HTTP POST Binding

    SP 通过立即提交的 Form 表单以 POST 请求的方式将 SAML Request 发到 IdP。IdP 通过立即提交的 Form 表单以 POST 请求的方式将 SAML Response 发到 SP。

    http-post-binding.b66e1cd7.png

    HTTP Artifact Binding

    SP、IdP 双方只通过浏览器交换 SAML Request、SAML Response 的索引编号,收到编号后,在后端请求对方的 Artifact Resolution Service 接口来获取真正的请求实体内容。从而避免 SAML Request、SAML Response 暴露在前端。

    HTTP-Artifact-Binding.ae27c509.png

    相关证书操作:

    # 提取idp证书
    keytool -rfc -keystore ./saml-idp.keystore -storepass xxx -exportcert -alias "idp"
    
    # 自签SP证书
    openssl req -new -x509 -days 36500 -nodes -out sp.crt -keyout sp.key
    
    将sp.key由默认PKCS#1转成PKCS#8
    openssl pkcs8 -topk8 -inform pem -nocrypt -in sp.key -outform pem -out sp.pem

    SAML单点登陆接入联调

    SAML2.0抓包分析:

    要抓取 SAML 2.0 的交互数据包,需要关注 HTTP 或 HTTPS 协议中的 SAML 请求(SAMLRequest)和 SAML 响应(SAMLResponse)。SAML 2.0 是基于 XML 的单点登录(SSO)协议,用于身份提供商(IdP)和服务提供商(SP)之间的身份验证和授权。

    1. 启动 Wireshark 并选择网络接口

    • 打开 Wireshark,选择要监听的网络接口(例如以太网或 Wi-Fi)。

    • 开始捕获数据包。

    2. 设置捕获过滤器(可选)

    如果知道 SAML 交互的 IP 地址或端口,可以设置捕获过滤器以减少捕获的数据量。例如:

    • 如果 SAML 交互是通过 HTTPS(端口 443)进行的:

    tcp port 443
    • 如果知道具体的 IP 地址:

    host <IdP_IP> or host <SP_IP>
    ip.dst == xxx && tcp.port == 443
    • 不确定数据包的方向(源或目标),可以使用 ip.srcip.dst 来分别指定源 IP 和目标 IP。

    • 如果使用的是 UDP 协议而不是 TCP,将 tcp.port 替换为 udp.port

    分析 HTTP 302 跳转数据包

    1. 找到过滤后的 HTTP 302 数据包。

    2. 展开数据包的 HTTP 层,查看以下字段:

      • Status Code: 确认状态码为 302

      • Location: 查看 Location 字段,这是跳转的目标 URL。

      • Request URI: 查看原始请求的 URI,了解是从哪个 URL 触发的跳转。

    • 过滤所有 HTTP 302 响应

    http.response.code == 302
    • 过滤特定 IP 地址的 HTTP 302 响应 如果只关心某个特定 IP 地址的 302 跳转,可以结合 IP 地址进行过滤。例如:

    ip.addr == 10.1.2.3 && http.response.code == 302

    • 过滤包含 Location 头的 HTTP 302 响应 302 跳转通常会在响应头中包含 Location 字段,指示跳转的目标 URL。可以进一步过滤包含 Location 头的 302 响应:

    http.response.code == 302 && http.location

    使用显示过滤器(基于协议字段)

     如果 iam.chegva.com 字符串出现在 HTTP 请求或响应的 URL、Host 头、或其他协议字段中,可以使用显示过滤器。

    • 过滤 HTTP 请求中包含 iam.chegva.com 的数据包

    http.request.uri contains "iam.chegva.com"
    • 过滤 HTTP 响应中包含 iam.chegva.com 的数据包

    http.response contains "iam.chegva.com"

    • 过滤 HTTP Host 头中包含 iam.chegva.com 的数据包

    http.host contains "iam.chegva.com"

    使用字节流过滤器(基于原始数据)

    如果 iam.chegva.com 字符串出现在数据包的原始字节流中(例如在 HTTPS 加密流量中),可以使用字节流过滤器。

    • 过滤包含 iam.chegva.com 字符串的所有数据包

    frame contains "iam.chegva.com"
    • 过滤 TCP 流中包含 iam.chegva.com 字符串的数据包

    tcp.payload contains "iam.chegva.com"

    3. 触发 SAML 2.0 交互

    • 在浏览器中访问使用 SAML 2.0 的服务(例如单点登录页面)。

    • 完成登录流程,确保 SAML 请求和响应被触发。

    4. 使用显示过滤器筛选 SAML 数据包

    在 Wireshark 中,使用显示过滤器来筛选 SAML 相关的数据包。SAML 请求和响应通常是通过 HTTP POST 或 HTTP Redirect 传输的。

    • 筛选 HTTP POST 请求中的 SAML 数据包

    http.request.method == "POST" and (http contains "SAMLRequest" or http contains "SAMLResponse")
    • 筛选 HTTP GET 请求中的 SAML 数据包(如果是通过 URL 重定向传输的):

    http.request.method == "GET" and (http contains "SAMLRequest" or http contains "SAMLResponse")
    • 筛选 HTTPS 流量中的 SAML 数据包 如果需要解密 HTTPS 流量以查看 SAML 内容,需要配置 Wireshark 解密 TLS 流量(见下文)。

    5. 解密 HTTPS 流量(可选)

    SAML 2.0 交互通常是通过 HTTPS 加密传输的。要解密 HTTPS 流量,需要配置 Wireshark 使用服务器的私钥或浏览器的 TLS 会话密钥。

    • 使用服务器的私钥

    1. 获取服务器的私钥文件(例如 .key.pem 文件)。

    2. 在 Wireshark 中,进入 Edit > Preferences > Protocols > TLS

    3. RSA keys list 中添加服务器的私钥文件。

    4. 重新捕获流量,Wireshark 将自动解密 HTTPS 流量。

  • 使用浏览器的 TLS 会话密钥

    1. 配置浏览器导出 TLS 会话密钥(例如,在 Chrome 中设置 SSLKEYLOGFILE 环境变量)。

    2. 在 Wireshark 中,进入 Edit > Preferences > Protocols > TLS

    3. (Pre)-Master-Secret log filename 中指定导出的密钥文件路径。

    4. 重新捕获流量,Wireshark 将自动解密 HTTPS 流量。

    6. 分析 SAML 数据包

    • 找到包含 SAMLRequestSAMLResponse 的 HTTP 数据包。

    • 展开数据包的 HTTP 层,查看 Form itemURL 中的 SAML 内容。

    • 如果需要查看 SAML 的 XML 内容,可以将 SAMLRequestSAMLResponse 的值复制出来,并使用 Base64 解码工具解码(例如在线工具或命令行工具)。

    7. 保存捕获的数据包

    • 如果需要保存捕获的数据包,可以点击 File > Save As,将捕获文件保存为 .pcap.pcapng 格式。

    解码 SAMLRequest 或 SAMLResponse

    SAML 请求和响应通常是 Base64 编码的。你可以使用以下方法解码:

    • 在线工具:将 Base64 字符串粘贴到在线解码工具中。

    • 命令行工具

    echo "<Base64_String>" | base64 --decode

    通过以上步骤,可以成功抓取并分析 SAML 2.0 的交互数据包。如果需要进一步分析 SAML 的 XML 内容,可以使用 XML 解析工具(Base64 Decode + Inflate)或浏览器插件(如 SAML-tracer)。

    RelayState在SAML2.0协议中的作用

    image.png


    参考:

    简化kubectl常用命令

    2025-03-21 00:17:33

    在k8s集群使用和运维中,每天都需要输入大量 kubectl 常用命令,其实我们可以通过定义 alias 别名和函数简化 kubectl 命令,比如使用k代替kubectl,po代替pods,svc代替services,kgpon代替kubectl get po -n [命名空间+资源名](以参数传入),并且添加kubectl命令自动补全,将这些别名和函数添加到 Shell 配置文件(如 ~/.bashrc、~/.zshrc)中,方便日常使用。以下脚本可以登陆到linux shell环境执行,结合这些别名和函数,可以大幅提高操作 Kubernetes 的效率,以后就可以轻松愉快地使用 kubectl 命令啦

    # 在 bash 中设置当前 shell 的自动补全
    source <(kubectl completion bash)
    echo "source <(kubectl completion bash)" >> ~/.bashrc
    
    # 在 zsh 中设置当前 shell 的自动补全
    # source <(kubectl completion zsh)  
    # echo '[[ $commands[kubectl] ]] && source <(kubectl completion zsh)' >> ~/.zshrc
    # zshrc添加将 .bashrc 改为 .zshrc
    
    # 添加简化命令,单引号包裹 EOF,禁止所有变量解析
    cat >> ~/.bashrc << 'EOF' 
    
    # 基础别名
    alias k='kubectl'                     # 用 k 代替 kubectl
    alias kg='kubectl get'                # 快速 get 资源
    alias kd='kubectl describe'           # 快速 describe 资源
    alias kdel='kubectl delete'           # 快速 delete 资源
    alias ka='kubectl apply -f'           # 快速 apply 文件
    alias ke='kubectl edit'               # 快速 edit 资源
    alias kex='kubectl explain'           # 快速 explain 资源
    alias kl='kubectl logs'               # 快速查看日志
    alias klf='kubectl logs -f'           # 实时查看 Pod 日志
    alias kx='kubectl exec -it'           # 快速进入 Pod 的 Shell
    alias kw='kubectl get --watch'        # 实时监控资源变化
    alias kt='kubectl top'                # 查看资源使用情况
    alias kr='kubectl rollout'            # 更新资源
    alias ks='kubectl scale'              # 资源扩缩容
    alias kcp='kubectl cp'                # 快速 cp 文件
    alias kco='kubectl config'            # 快速 config 资源
    alias kcd='kubectl cordon'            # 节点不可调度
    alias kucd='kubectl uncordon'         # 节点可调度
    alias kapi='kubectl api-resources'    # 列出支持的资源类型
    alias kcl='kubectl cluster-info'      # 显示主控节点和服务的地址
    alias kh='kubectl get --v=7'          # 显示资源请求的HTTP信息
    
    
    # 资源类型别名
    alias kgpo='kubectl get pods'         # pods
    alias kgno='kubectl get nodes'        # nodes
    alias kgns='kubectl get namespaces'   # namespaces
    alias kgcs='kubectl get cs'           # ComponentStatus
    alias kgep='kubectl get ep'           # endpoints
    alias kgsvc='kubectl get services'    # services
    alias kgdep='kubectl get deployments' # deployments
    alias kgrs='kubectl get replicasets'  # replicasets
    alias kging='kubectl get ingress'     # ingress
    alias kgcm='kubectl get configmaps'   # configmaps
    alias kgsec='kubectl get secrets'     # secrets
    alias kgsa='kubectl get sa'           # serviceaccounts
    alias kgpv='kubectl get pv'           # persistentvolumes
    alias kgpvc='kubectl get pvc'         # persistentvolumeclaims
    alias kgsc='kubectl get sc'           # storageclasses
    alias kgsts='kubectl get sts'         # StatefulSet
    alias kgds='kubectl get ds'           # DaemonSet
    alias kgev='kubectl get events'       # events
    alias kgjo='kubectl get jobs'         # jobs
    
    
    # 查看资源描述(需替换 <resource-name>)
    alias kdpo='kubectl describe pod'
    alias kdno='kubectl describe nodes'
    alias kdde='kubectl describe deployment'
    alias kdst='kubectl describe sts'
    alias kdds='kubectl describe ds'
    alias kdsv='kubectl describe svc'
    alias kdcm='kubectl describe cm'
    alias kdse='kubectl describe secrets'
    alias kdsa='kubectl describe sa'
    alias kdpv='kubectl describe pv'
    alias kdpvc='kubectl describe pvc'
    alias kdsc='kubectl describe sc'
    alias kding='kubectl describe ingress'
    alias kdjo='kubectl describe jobs'
    
    
    # 常用组合别名
    
    # 查看所有命名空间的 Pods
    alias kgpoa='kubectl get pods -A'
    
    # 查看 Pods 并显示附加信息(如 IP、节点)
    alias kgpow='kubectl get pods -owide'
    
    # 查看 Nodes 并显示附加信息(如 IP、节点)
    alias kgnow='kubectl get nodes -owide'
    
    # 资源操作:$1对应命名空间、$2对应资源名称;y → yaml,n → namespace,默认输出格式为yaml
    OUTPUT="-oyaml"
    
    # kubectl get resources
    kgpon() { kubectl get po -n "$@"; }
    kgpoy() { kgpon "$@" $OUTPUT; }
    
    kgden() { kubectl get deploy -n "$@"; }
    kgdey() { kgden "$@" $OUTPUT; }
    
    kgstn() { kubectl get sts -n "$@"; }
    kgsty() { kgstn "$@" $OUTPUT; }
    
    kgsvn() { kubectl get svc -n "$@"; }
    kgsvy() { kgsvn "$@" $OUTPUT; }
    
    kgdsn() { kubectl get ds -n "$@"; }
    kgdsy() { kgdsn "$@" $OUTPUT; }
    
    kgcmn() { kubectl get ds -n "$@"; }
    kgcmy() { kgcmn "$@" $OUTPUT; }
    
    kgsen() { kubectl get secrets -n "$@"; }
    kgsey() { kgsen "$@" $OUTPUT; }
    
    kgsan() { kubectl get sa -n "$@"; }
    kgsay() { kgsan "$@" $OUTPUT; }
    
    kgpvn() { kubectl get pv -n "$@"; }
    kgpvy() { kgpvn "$@" $OUTPUT; }
    
    kgpvcn() { kubectl get pvc -n "$@"; }
    kgpvcy() { kgpvcn "$@" $OUTPUT; }
    
    kgscn() { kubectl get sc -n "$@"; }
    kgscy() { kgscn "$@" $OUTPUT; }
    
    kgingn() { kubectl get ingress -n "$@"; }
    kgingy() { kgingn "$@" $OUTPUT; }
    
    kgevn() { kubectl get events -n "$@"; }
    kgevy() { kgevn "$@" $OUTPUT; }
    
    kgjon() { kubectl get jobs -n "$@"; }
    kgjoy() { kgjon "$@" $OUTPUT; }
    
    # kubectl describe resources
    kdpon() { kubectl describe po -n "$@"; }
    kdden() { kubectl describe deploy -n "$@"; }
    kdstn() { kubectl describe sts -n "$@"; }
    kdsvn() { kubectl describe svc -n "$@"; }
    kddsn() { kubectl describe ds -n "$@"; }
    kdcmn() { kubectl describe cm -n "$@"; }
    kdsen() { kubectl describe secrets -n "$@"; }
    kdsan() { kubectl describe sa -n "$@"; }
    kdpvn() { kubectl describe pv -n "$@"; }
    kdpvcn() { kubectl describe pvc -n "$@"; }
    kdscn() { kubectl describe sc -n "$@"; }
    kdingn() { kubectl describe ingress -n "$@"; }
    kdjon() { kubectl describe jobs -n "$@"; }
    
    # kubectl edit resources
    kepon() { kubectl edit po -n "$@"; }
    keden() { kubectl edit deploy -n "$@"; }
    kestn() { kubectl edit sts -n "$@"; }
    kesvn() { kubectl edit svc -n "$@"; }
    kedsn() { kubectl edit ds -n "$@"; }
    kecmn() { kubectl edit cm -n "$@"; }
    kesen() { kubectl edit secrets -n "$@"; }
    kesan() { kubectl edit sa -n "$@"; }
    kepvn() { kubectl edit pv -n "$@"; }
    kepvcn() { kubectl edit pvc -n "$@"; }
    kescn() { kubectl edit sc -n "$@"; }
    keingn() { kubectl edit ingress -n "$@"; }
    kejon() { kubectl edit jobs -n "$@"; }
    
    # 进入 Pod 的 Shell
    kxsh() { kubectl exec -it -n "$@" -- /bin/sh; }
    kxbs() { kubectl exec -it -n "$@" -- /bin/bash; }
    
    # 查看 Pod 的日志
    klpn() { kubectl logs -n "$@"; }
    klfpn() { klpn "$@" -f; }
    klppn() { klpn "$@" -p; }
    
    # 删除 Pod
    kdelp() { kubectl delete po -n "$@"; }
    
    # 强制删除 Pod
    krmfp() { kubectl delete po -n "$@" --force --grace-period=0; }
    
    # 上下文和命名空间切换
    alias kuse='kubectl config use-context'      # 切换集群
    alias kns='kubectl config set-context --current --namespace' # 切换命名空间
    
    # 生成 Pod 的临时调试副本(类似 docker exec)
    alias kdebug='kubectl debug -it --image=busybox'
    
    # 转发本地端口到 Pod(需替换参数)
    alias kpf='kubectl port-forward pod/<pod-name>'
    
    
    complete -o default -F __start_kubectl k
    EOF
    
    source ~/.bashrc

    使用示例:

    # 输入参数:$@对应整个输入参数,$1对应命名空间、$2对应资源名称,其余缩写命令使用方法与示例(Pods)类似
    
    # 查看所有命名空间的 Pods
    kgpoa                   → kubectl get pods -A
    kgpow -A                → kubectl get pods -owide -A
    
    # 查看指定命名空间的 Pods
    k get po -n namespace   → kubectl get pods -n namespace
    kg po -n namespace
    kgpo -n namespace
    kgpon namespace
    kgpon namespace -owide  → kubectl get pods -n namespace -owide
    
    # 查看指定命名空间的 Pod
    kgpon namespace podname → kubectl get pods -n namespace podname
    kgpoy namespace podname → kubectl get pods -n namespace podname -oyaml
    kdpon namespace podname → kubectl describe pods -n namespace podname
    kepon namespace podname → kubectl edit pods -n namespace podname
    
    # 进入 Pod 的 Shell
    kxsh namespace podname  → kubectl exec -it -n namespace podname -- /bin/sh
    kxbs namespace podname  → kubectl exec -it -n namespace podname -- /bin/bash
    
    # 查看 Pod 的日志
    klpn namespace podname  → kubectl logs -n namespace podname
    klfpn namespace podname → kubectl logs -n namespace podname -f
    klppn namespace podname → kubectl logs -n namespace podname --previous
    
    # 删除 Pod
    kdelp namespace podname → kubectl delete pods -n namespace podname
    krmfp namespace podname → kubectl delete pods -n namespace podname --force --grace-period=0

    注意事项

    • 如果别名冲突(如 k 被其他工具占用),可替换为其他名称(如 kb)。

    • 删除资源时务必确认资源名称,避免误操作。

    • 编辑系统级配置文件前建议先备份:cp ~/.bashrc{,.bak}

    • 检察语法错误:bash -n ~/.bashrc

    • 检查别名或函数是否成功定义:type kg、type kgpon


    参考:

    高效学习(二):源头、原理和知识地图

    2025-03-18 11:44:57

    有了上一篇文章中分享的那些观点,我们来看看应该怎么做。下面是我觉得比较不错的一些学习的方法,或者说对我来说最有效的学习方法。我相信,只要你和我一样,做到的话,你的学习效率一定能够提升很快。

    挑选知识和信息源

    还是我在《程序员练级攻略》中说的那样,英文对于我们来说至关重要,尤其是对于计算机知识来说。如果你觉得用百度搜中文关键词就可以找到自己想要的知识,那么你一定远远落后于这个时代了。如果你用 Google 英文关键词可以找到自己想要的知识,那么你算是能跟得上这个时代。如果你能在社区里跟社区里的大牛交流得到答案,那么你算是领先于这个时代了。

    所以,我认为你的信息源要有下面几个特质。

    • 应该是第一手资料,不是被别人理解过、消化过的二手资料。尤其对于知识性的东西来说,更是这样。应该是原汁原味的,不应该是被添油加醋的。

    • 应该是有佐证、有数据、有引用的,或是有权威人士或大公司生产系统背书的资料。应该是被时间和实践检验过的,或是小心求证过的,不是拍脑袋野路子或是道听途说出来的资料。

    • 应该是加入了一些自己的经验和思考,可以引发人深思的,是所谓信息的密集很大的文章。

    顺便说一句,我发现 Medium 上的文章质量比较高,很多文章都 Google 到了 Medium 上。

    我在《程序员练级攻略 》后期的文章中罗列了很多文章资源,有的读者很不能理解,他们觉得我多少应该导读一下或是写上一些自己的想法,而不是只是简单地罗列出来。这里请允许我辩解一下,我之所以这样做,并不是因为偷懒,我完全可以把这些信息资料全部隐藏起来,翻译也好,搬运也好,导读也好,自己消化完后再写出来。那么,我可以写出多少个专栏来?

    我觉得,只要我有时间,极客时间上的所有专栏都不用写了,我一个人就 OK 了。我可以写得又快又好,而且超出所有的人。那我可以挣到很多钱。但我不想这样,我想把我读过的好的文章推荐给大家,就像推荐书一样。那些是信息源头,已经写得非常不错了,我不用再多废话。而且那些文章底部都有很多的引用,你可以一路点过去。

    我想通过这些简单链接的方式,为我的读者打开一个全新的世界,他们可以在这个世界中自己找食吃,而不需要依赖我,这才是我想给大家带来的东西。我不知道,我的那些推荐文章,有没有让你看到了一个很广阔的世界,在那里,每天都在产生很多最新、最酷、最有营养的一手信息,而不是被我或他人消化过的二手信息。

    这里,我只想说,对于一个学习者来说,找到优质的信息源可以让你事半功倍。一方面,就像找到一本很好的武林秘籍一样,而不是被他人翻译过或消化过的,也不会有信息损失甚至有错误信息会让你走火入魔。另一方面,你需要的不只有知识和答案,更重要的是掌握学习的方法和技能。你要的是“渔”,而不是“鱼”。

    注重基础和原理

    我在很多的场合都提到过,基础知识和原理性的东西是无比重要的。这些基础知识就好像地基一样,只要足够扎实,就要可以盖出很高很高的楼。正所谓“勿在浮沙筑高台”。我说过,很多人并不是学得不够快,而他们的基础真的不行。基础不行,会影响你对事物的理解,甚至会让你不能理解为什么是这样。当你对事物的出现有不理解的东西时,通常来说,是因为你的基础知识没有跟上。

    在《程序员练级攻略 》一文中,我用了很大的篇幅给出了学习基础技术的路径。只要你努力学习那些基础知识,了解了其中的原理,就会发现这世界上的很多东西是大同的。

    举个例子,如果你学习过底层的 Socket 编程,了解多路复用和各种 I/O 模型的话(select, poll, epoll, aio, windows completion port, libevent 等),那么,对于 Node.js、Java NIO、Nginx、C++ 的 ACE 框架等这些中间件或是编程框架,你就会发现,无论表现形式是什么样的,其底层原理都是一个样的。

    无论是 JVM 还是 Node,或者是 Python 解释器里干了什么,它都无法逾越底层操作系统 API 对“物理世界”的限制。而当你了解了这个底层物理世界以后,无论那些技术玩成什么花样,它们都无法超出你的掌控(这种感觉是很爽的)。

    再举一个例子,当学了足够多的语言,并有了丰富的实践后,你开始对编程语言的各种编程范式或是控制流有了原理上的了解,这时再学一门新语言的话,你会发现自己学得飞快。

    就像我 2010 年学习 Go 语言一样,除了那些每个语言都有的 if-else、 for/while-loop、function 等东西以外,我重点在看的就是,出错处理是怎么玩的?内存管理是怎么玩的?数据封装和扩展怎么玩的?多态和泛型怎么搞的?运行时识别和反射机制是怎么玩的?并发编程怎样玩?……

    这些都是现代编程语言必需的东西,如果没有,那么这个语言的表达能力就很落后了。所以,当知道编程语言的本质和原理后,你学习一门新的语言是非常非常快的,而且可以直达其高级特性。

    最最关键的是,这些基础知识和原理性的东西和技术,都是经历过长时间的考验的,所以,这些基础技术也有很多人类历史上的智慧结晶,会给你很多启示和帮助。比如:TCP 协议的状态机,可以让你明白,如果你要设计一个异步通信协议,状态机是一件多么重要的事,还有 TCP 拥塞控制中的方式,让你知道,设计一个以响应时间来限流的中件间是什么样的。

    当学习算法和数据结构到一定程度的时候,你就会知道,算法不仅对于优化程序很重要,而且,会让你知道,该如何设计数据结构和算法来让程序变得更为健壮和优雅。

    有时候,学习就像拉弓蓄力一样,学习基础知识感觉很枯燥很不实用,工作上用不到,然而学习这些知识是为了未来可以学得更快。基础打牢,学什么都快,而学得快就会学得多,学得多,就会思考得多,对比得多,结果是学得更快……这种感觉,对于想速成的人来说,很难体会。

    这里我想再次强调一下,请一定要注重基础知识和原理上的学习!

    使用知识图

    先讲一个故事,2000 年我从昆明到上海,开始沪飘的岁月。刚到上海,找不到好工作,只能大量地学习和看书,C/C++/Java,TCP/IP,Windows 编程,Unix 编程,等等。结果呢,书太多了,根本看不过来。我想要更多地掌握知识,结果我发现以死记硬背的方式根本就是在使蛮力学习,我很难在很短的时间内学习很多的知识。

    于是我自己发明了一种叫“联想记忆法”的方法,比如,在学习 C++ 的时候,面对《C++ Primer》这种厚得不行的书,我就使用联想记忆法。

    我把 C++ 分成三部分。

    • 第一部分是 C++ 是用来解决 C 语言的问题的,那么 C 语言有什么问题呢?指针、宏、错误处理、数据拷贝…… C++ 用什么技术来解决这些问题呢?

    • 第二部分是 C++ 的面向对象特性:封装、继承、多态。封装,让我想到了构造函数、析构函数等。构造函数让我想到了初始化列表,想到了默认构造函数,想到了拷贝构造函数,想到了 new……多态,让我想到了虚函数,想到了 RTTI,RTTI 让我想到了 dynamic_cast 和 typeid 等。

    • 第三部分是 C++ 的泛型编程。我想到了 template,想到了操作符重载,想到了函数对象,想到 STL,想到数据容器,想到了 iterator,想到了通用算法,等等。

    于是,我通过“顺藤摸瓜”的方式,从知识树的主干开始做广度或是深度遍历,于是我就得到了一整棵的知识树。这种“顺藤摸瓜”的记忆方式让我记住了很多知识。最重要的是,当出现一些我不知道的知识点时,我就会往这棵知识树上挂,而这样一来,也使得我的学习更为系统和全面

    这种画知识图的方式可以让你从一个技术最重要最主干的地方出发开始遍历所有的技术细节,也就是画地图的方式。如果你不想在知识的海洋中迷路,你需要有一份地图,所以,学习并不是为了要记忆那些知识点,而是为了要找到一个知识的地图,你在这个地图上能通过关键路径找到你想要的答案

    小结

    总结一下今天的内容。首先,我强调了,挑选知识和信息源的重要性,因为优质的信息源可以让你事半功倍。其次,我认为,一定要注重基础和原理,基础打牢,学什么都快,而学得快就会学得多,学得多,就会思考得多,对比得多,结果是学得更快。

    最后,我指出,学习时一定要使用知识图,学习并不是为了要记忆那些知识点,而是为了要找到一个知识的地图,你在这个地图上能通过关键路径找到你想要的答案。我相信,只要掌握了好的方法,你能做到的话,你的学习效率一定提升很快。

    来源:《左耳听风专栏:高效学习》