DON’T PANIC!

学思践悟,了妄唯真,保持真诚,分享价值

Linux 架构蓝图简谈

笔记内容来源于《How Linux Works》CH01 Linux系统中的抽象级别和层数 Linux操作系统有三个主要的层级,它们分别是Hardware、Linux Kernel、User Processes三层,具体包含的内容可以看下图。 Hardware层和User Processes层都很容易去理解它,这里重点讲解Linux Kernel层。内核是操作系统核心,内核是在内存中用来告诉CPU要做什么事情的软件,内核可以管理硬件并且作为Hardware和Running Program的接口。 进程是运行中的程序,是由内核管理的程序。Kernel和User Processes之间重要的区别,Kernel运行在Kernel Mode,User Processes运行在User Mode。 代码运行在Kernel Mode可以完全无限制的执行任何CPU instruction和引用any memory address。这是非常强大但也是充满危险的特权,内核程序很容易崩溃整个系统,因为只有内核程序可以访问内核空间。 在用户模式下,执行代码无法直接access hardware或reference meory,在用户模式下运行的代码必须委托给系统API(system call)才能访问硬件或内存。因为这种隔离,用户模式下的崩溃总是可恢复的,并且计算机上大多数代码运行在用户模式下。 Hardware | 理解主内存 在计算机系统所有硬件中,Main Memory可能是最重要的。它用来存储运行中的内核和进程,CPU也只能操作在内存上,CPU读取指令和数据从内存中,然后写数据返回给内存。 在内存中,state术语经常被描述为当前进程已经完成或正在执行等状态,而不是描述为比特的排列顺序,因为进程通常都是有数百万比特组成。

May 21, 2023

使用OpenSSL命令生成RSA密钥

基于OPENSSL工具,生成2048比特长度的RSA密钥对 1 2 3 openssl genrsa -out private_key.pem 2048 openssl pkcs8 -topk8 -inform PEM -in private_key.pem -outform PEM -nocrypt -out private_key_pkcs8.pem openssl rsa -in private_key.pem -pubout -out public_key.pem 私钥和公钥的关系 根据上图可以得到一些结论 私钥 = 模数 + 公钥指数 + 私钥指数;公钥 = 模数 + 公钥指数 知道私钥后可以推导出公钥 使用OpenSSL命令查看公钥和私钥信息 你会发现,公钥和私钥的模数相同,公钥指数也相同,唯独私钥指数不同,因为公钥没有。

May 21, 2023

为什么 Redis 比 MySQL 更快

单线程模型 Single-threaded read/write 使用多线程通常会带来更大的吞吐率和更短的响应时间,然而,使用多线程并不一定比单线程程序快。CPU在一个时间片中只执行一个线程,当内核线程切换时,它需要保存线程A的执行环境,然后加载线程B的执行环境。线程执行上下文切换的开销并不低,如果没有必要,应该尽量减少。而且,使用多线程往往会使设计更加复杂,要求我们在访问共享数据时更加小心–对数据加锁对Redis来说不是一个划算的选择。 多线程的坏处 上下文切换 访问共享数据时对数据加锁 绝大多数的Redis请求都是纯粹的内存操作,这个过程非常快,CPU并不是性能瓶颈,即使是单线程实例,单个实例也可以每秒处理数万个数据请求。对于更高的并发性要求,Redis还支持集群部署,即在一台或多台服务器上启动多个实例,以分担访问压力。 从产品定位上,Redis不是一个CPU密集性应用,单线程足以满足计算需求。 多路复用模型 IO Multiplexing 强烈推荐阅读原文 在类Unix系统中,内核使用File Descriptor,也即FD去标识一个文件或者一个I/O资源(网络套接字),这些FD被特定的进程所访问。使用不同的I/O模型去访问FD,所访问的模式也不同,常用包含五种I/O模型: 阻塞式I/O 非阻塞式I/O I/O复用 信号I/O 异步I/O 对于I/O多路复用,Linux提供了select/poll/epoll这三个系统调用,进程可以使用这些进程调用,从内核中监听多个文件描述符。 select/poll 使用线性结构存储进程监听的FD集合。即把文件描述符集合拷贝到内核中,让内核检查是否有事件产生,检查的方式很粗暴,通过遍历的方式去遍历文件描述符集合,当检查到有事件产生时,标记可读可写,接下来再把整个文件描述符集合拷贝到用户态中,然后用户态还需要通过遍历的方式找到可读可写的FD。 select使用BitsMap(最大监听1024个FD)、poll使用链表(突破了select的限制)。 epoll 使用红黑树监听待检测的FD。红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn)。通过对这棵黑红树的管理,不需要像 select/poll 在每次操作时都传入整个FD集合,减少了内核和用户空间大量的数据拷贝和内存分配。 结论: 我们每次向Redis请求,都会产生一个FD,如何支持数以万计的并发请求。这个问题可以转化为,Redis服务进程如何监听这么多的FD,答案是使用多路复用epoll来提升链接处理能力。这点与MySQL不同,MySQL是一个连接分配一个线程的模式。

May 20, 2023

Linux 进程间的通信方式 | 信号

Linux进程间通信方式简要 在Linux中,使用进程之间通过多种方式进行通信,包括管道、信号、共享内存、消息队列、套接字等。进程间通信实现不同进程之间的数据共享和协作,更好完成各自任务。 管道 - pipe 比如ls | grep 123 中,就将ls这个进程的输出,作为grep的输入进行传递。 信号 - signal 一个进程,可以向另一个进程发送信号,并且另一个进程可以去处理这个发送来的信号。 共享内存 - shard memory 共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。 消息队列 - message queues 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 信号量 - semaphores 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。 套接字 - socket 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。例如访问远端的MySQL服务器,需要注意,如果MySQL服务在本地,则套接字使用的unix套接字,而非tcp套接字。 列出所有的信号类型 可能会有疑问,为什么是kill,因为在linux当中,大部分信号都是将进程给杀死,所以干脆就统一称为kill了。64种死法。 1 2 kill -l HUP INT QUIT ILL TRAP ABRT EMT FPE KILL BUS SEGV SYS PIPE ALRM TERM URG STOP TSTP CONT CHLD TTIN TTOU IO XCPU XFSZ VTALRM PROF WINCH INFO USR1 USR2 如何终止一个进程 1 2 px -aux | grep xxx # 通过进程名等获取进程pid kill -9 111 # 给pid为111的进程发送sigkill信号 在程序开发过程中,可以让程序捕捉并处理kill进程发送来的信号。 但是有个约定俗成的约定,kill -9信号,也就是sigkill信号是不可以被捕捉并处理的,即程序接收到kill -9信号,则必须得死。...

May 20, 2023

MySQL 分库分表的方案

1️⃣ 用户请求量太大 因为单服务器TPS、内存、IO都是有限的。 ✅ 解决方法是分散请求到多个服务器上。(划分服务) 2️⃣ 单个数据库太大 单个数据库处理能力有限;单库所在服务器上磁盘空间不足;单库上操作的IO瓶颈。 ✅ 解决方法是切分成更多更小的库。(划分库) 3️⃣ 单个数据表太大 CRUD都成问题,索引膨胀,查询超时。 ✅ 解决方法是切分成多个数据集更小的表。(划分表)

May 20, 2023

Golang 互斥锁与读写锁

临界区 (Critical section) 在并发编程中,对共享资源的并发访问会导致意外或错误的行为,因此需要对访问共享资源的程序部分进行保护,以避免并发访问。这个被保护的部分就是关键部分或关键区域。它不能被一个以上的进程同时执行。通常情况下,关键部分访问共享资源,如数据结构、外围设备或网络连接,在多个并发访问的情况下,这些资源将无法正常运行。 如下图,在互斥(mutex)的情况下,一个线程在需要访问共享资源时,通过使用锁的技术封锁一个关键部分,而其他线程必须等待轮到自己进入该部分。这可以防止两个或更多的线程共享同一内存空间并想访问一个共同的资源时发生冲突。 互斥锁 互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个goroutine可以访问共享资源。 Go语言中使用sync包的Mutex类型来实现互斥锁。 使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁;当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。 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 package main import ( "fmt" "sync" ) var num int64 var wg sync.WaitGroup var mux sync.Mutex func main() { wg.Add(2) go add() go add() wg.Wait() fmt.Println(num) // output: 10000 } func add() { for idx := 0; idx < 5000; idx++ { mux....

May 20, 2023

分布式高可用 | 限流、熔断、降级

在分布式系统中,限流、熔断和降级,这三者都是流量超过时,通过一定的方式去保护系统。而高可用的意思是在系统出现故障或异常情况时仍然能够保持稳定的运行状态。 通常我们的分布式系统如下图所示。 Service A 是主调,Service B 是被调。他们各自分别都有两个实例,即两个副本,为实现分布式高可用。 限流:一般是在被调生效,即图中的绿色框框所处的位置 熔断:一般是在主调生效,也有部分熔断设计在被调生效,如图中红色块所处的位置。 降级:一般从用户侧观察,是否享有的服务变差了。限流和熔断都会带来降级。 限流 - Rate Limiting 限流是从系统的流量入口考虑,从进入的流量上进行限制,达到保护系统的作用。是指需要限制并发/请求量的场景(如秒杀等),通过限流保护服务免受雪崩之灾。 主流的限流限流算法有计数器、令牌桶、漏桶。 🔺 计数器 多少次每秒/每分/每天,根据作用范围的不同,计数器两类 单机计数器即 Local Counter(每个机器都有自己的计数器) 全局计数器也就是分布式计数器即 Global Counter(所有机器公有一个计数器阈值) 🔺 令牌桶 令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。 令牌桶算法是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的算法。 通常,令牌桶算法用来控制发送到网络上的数据的数目,并允许突发数据的发送。 🔺 漏桶 漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。 限流与压测 通常限流需要配合压测来做,不然也不知道系统的负载极限在哪里。初期做法是通过对单一服务的压测,或全链路压测得到一个阈值(静态)。 该处做法就是简单,缺点是随着业务复杂度的上升,静态阈值将会不能代表服务的真实服务能力,所以一般在活动或者大促前,去组织一批服务压测。更好的做法是全链路压测日常化,然后随时调整这个阈值。实现阈值从静态到动态的转变。 熔断 - Circuit Breaker Mechanism 如图所示,熔断器首先是关闭的,当主调失败到达了一定阈值后,此时熔断器打开(主调的请求不会再到被调了,压根就不会发出请求),过了一个"冷却时间"后,熔断器此时会处于一个半开的状态,会有请求到被调。 如果请求成功就关闭熔断器(后端服务恢复),否则就继续打开熔断器。熔断强调的是服务之间的调用能实现自我恢复的状态。 例如你的 A 服务里面的一个功能依赖 B 服务,这时候 B 服务出问题了,返回的很慢。这种情况可能会因为这么一个功能而拖慢了 A 服务里面的所有功能,因此我们这时候就需要熔断! 即当发现 A 要调用这 B 时就直接返回错误(或者返回其他默认值),就不去请求B了。 🔺 主调熔断 主调熔断是熔断的主要场景,在微服务中,通过结合服务注册和发现组件来进行使用的。主调通过服务发现组件来获取被调的实例,如果主调的失败达到了一个阈值,服务发现组件会熔断被调的实例,也就是不再会去请求被调。 🔺 被调熔断 被调熔断,被调实例获取当前的实例负载情况,如果 cpu/内存/io 等超过了一定的阈值,那么就会触发熔断,将抛弃进来的请求。此时熔断和限流比较像,一个是基于负载情况,一个是根据配置的阈值情况。 降级 降级也就是服务降级,当我们的服务器压力剧增为了保证核心功能的可用性 ,而选择性的降低一些功能的可用性,或者直接关闭该功能。这就是典型的丢车保帅了。当服务暂时不可用或者影响到核心流程时,需要待高峰或者问题解决后再打开。...

May 20, 2023

为什么后台系统中需要Metrics组件

Metrics,谷歌翻译就是度量的意思。当我们需要为某个系统某个服务做监控、做统计,就需要用到Metrics。基本上每一个服务、应用都需要做一个监控系统,这需要尽量以少量的代码,实现统计某类数据的功能。 例如对于图片压缩服务来说 每秒钟的请求数是多少? 平均每个请求处理的时间? 请求处理的最长耗时? 等待处理的请求队列长度? 例如对于缓存服务来说 缓存的命中率? 平均查询缓存的时间? 五种常用的Metrics类型 以 Java 为例,目前最为流行的 metrics 库是来自 Coda Hale 的 dropwizard/metrics,该库被广泛地应用于各个知名的开源项目中。例如 Hadoop,Kafka,Spark,JStorm 中。 Meter Meter可以mark事件发生的次数,并且通过getCount方法返回事件发生的次数。Meter对象提供四种速率,分别是表示最近一分钟、最近五分钟、最近一个季度和整个Meter生命周期的平均速率。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 Meter meter = new Meter(); long initCount = meter.getCount(); assertThat (initCount, equalTo(0L)); meter.mark(); assertThat (meter.getCount(), equalTo(1L)); meter.mark(20); assertThat (meter.getCount(), equalTo(21L)); double meanRate = meter.getMeanRate(); double oneMinate = meter.getOneMinuteRate(); double fiveMinate = meter.getFiveMinuteRate(); double fifteenMinRate = meter....

May 19, 2023

DNS的工作流程

工作流程 首先查询root域名服务器,并返回相应的顶级域名服务器地址。 【得到.com顶级域名服务器】 查询顶级域名服务器,返回一个权威域名服务器地址。【得到亚马逊域名服务器】 查询权威域名服务器地址,最终得到查询域名对应的IP地址,并且返回给DNS解析器。【得到地址】 客户端从DNS解释器中获取域名对应的IP地址,然后再去请求该IP地址上的服务。 关于DNS的要点 DNS默认端口为53端口,主要用于域名解析。 DNS推荐使用的是UDP协议进行通讯,因为相对于TCP而言,UDP的包更小,连接也更少。 DNS resolver(也称为递归解析器)是一种服务器,旨在从浏览器和其他应用程序上接受DNS查询。 DNS解析器存储着临时DNS记录(IPv4的A记录和Ipv6的AAAA记录等),根据TTL生存时间保存。 如何设计更新处理查询数以亿级数据的系统 最好的方式是,不要处理数以亿级的数据。对数据进行分区。 例如DNS的系统设计,首先分根域名服务器,用于查询顶级域名服务器,顶级域名服务器用于查询权威域名服务器,最终权威域名服务器再去查询,具体的域名相对应的地址。我们更新也不过是更新权威服务器而已。

May 19, 2023

数据库索引最左匹配原则

在联合索引时,一定会碰到并用到最左匹配原则。所谓最左匹配原则,就是你的SQL语句中用到了联合索引中的最左边的索引,那么这条SQL语句就可以利用这个联合索引去进行匹配。不仅仅MySQL支持联合索引的最左匹配原则,MongoDB数据库也支持。 值得注意的是,当遇到范围查询时候,例如>,<,between,like,索引就会停止匹配。 组合索引 - composite index 最左匹配原则 - leftmost prefix principle 案例说明 例如对(a,b,c,d)字段建立索引,使用where如下,就是可以匹配索引的。 1 2 3 a = 1 a = 1 AND b = 2 b = 1 AND a = 1 # MySQL优化器会自动调整a和b的顺序,保持与索引顺序一致。 但是如果执行如下,那么将无法命中匹配索引。 1 2 b = 2 # b有序的前提是a有序,如果a无法确定,那么在B+树里肯定也是无序的,所以不能命中匹配索引。 a = 1 AND b = 2 AND c > 3 AND d = 4 # a和b和c可以命中索引,但是d因为遇到了范围查询,故无法命中索引

May 19, 2023