基础概念
IP
IP 地址(Internet Protocol Address)是数字地址,用于识别在互联网上相互连接的计算机或网络设备。
它是互联网通信的基础,所有通过互联网传输的数据都要依靠 IP 地址来传输。
IP 地址分为 IPv4 和 IPv6 两种形式,
IPv4 地址通常用点分十进制表示,例如 192.168.0.1
IPv6 地址则由 8 组 16 位的十六进制数字表示,例如 2001:0db8:85a3:0000:0000:8a2e:0370:7334。
OSI 模型
OSI 模型是开放式系统互连参考模型(Open Systems Interconnection Reference Model)的简称,
它是由国际标准化组织(ISO)制定的一种网络通信的分层结构模型。
该模型将网络通信分为七层,每一层都定义了特定的协议和功能,以提供网络通信的全面架构。
OSI 模型的七层分别是:
物理层(Physical Layer):负责数据的物理传输,包括电缆、光纤、无线电等传输介质及其物理连接。
数据链路层(Data Link Layer):提供有可靠的数据传输服务,将原始比特流转换为有意义的数据帧,实现数据检错、纠错等。
网络层(Network Layer):管理不同网络之间的数据传输,将数据包路由到目的地址,实现服务质量控制和流量调度等功能。
传输层(Transport Layer):负责数据的端到端传输,提供可靠的数据传输和流量控制,实现数据分段和重组。
会话层(Session Layer):管理网络中的会话连接,实现进程间通信以及数据同步等服务。
表示层(Presentation Layer):处理数据的格式转换、数据压缩、加密解密等,将应用层数据转换成网络标准格式。
应用层(Application Layer):为用户提供应用服务,包括文件传输、电子邮件、远程登录等,是用户接口和网络的通信接口。
OSI 模型的分层结构可以使系统的设计和使用更加简单和系统化,并且使各层之间的功能清晰明确,提高了系统的可维护性和可扩展性。
TCP and UDP
OSI 和 TCP/UDP 都是网络通信的协议范式,但它们的角色和定位不同。
OSI 模型是一种概念性的框架,用来描述网络通信的各个层次,从而使不同的协议能够在特定层次上相互协作。
而 TCP/UDP 则是 OSI 模型中两个很重要的协议,它们分别负责 OSI 模型的传输层。
TCP (Transmission Control Protocol)是 OSI 模型中第四层(传输层)的协议之一。
TCP 提供了可靠的数据传输和流量控制服务,在数据传输过程中保证数据的完整性、可靠性和有序性。
TCP 协议的特点是面向连接、有序、可靠。它适用于对网络延迟要求较高,对数据准确性要求较高的应用。
UDP (User Datagram Protocol)是 OSI 模型中第四层(传输层)的协议之一。
UDP 与 TCP 不同之处在于它是一种无连接协议,通信双方在传输数据之前不需要建立连接。
UDP 在数据传输过程中不保证数据的完整性、可靠性和有序性。UDP 协议的特点是无连接、不可靠、简单、高效。 它适用于对网络延迟要求较低,对数据准确性要求不高的应用。
在实际网络通信中,TCP 和 UDP 都有各自的应用场景。
例如,在文件传输、电子邮件等应用场景中,TCP 协议的可靠性和保证数据完整性是非常重要的。
而在视频流传输、音频流传输、在线游戏等应用场景中,UDP 由于传输效率高,可以更好地满足实时性要求。
- TCP 的
三次握手和四次挥手
TCP 的三次握手和四次挥手 是 TCP 协议用于建立和关闭连接时的具体过程。
简单来说,三次握手是客户端和服务器建立连接,四次挥手是关闭连接。
三次握手是指:
客户端向服务器发送 SYN (同步请求)包。
服务器收到 SYN 包后,向客户端发送 SYN/ACK(同步应答)包。
客户端收到 SYN/ACK 包后,向服务器发送 ACK(确认)包。
这时,TCP 连接建立成功。
四次挥手是指:
客户端发送 FIN (结束)包。
服务器收到 FIN 包后,向客户端发送 ACK 包,表明已收到关闭请求。
服务器处理完客户端的数据后,发送 FIN 包给客户端,表示数据处理完成,可以关闭连接。
客户端收到 FIN 包后,向服务器发送 ACK 包,表明自己也准备关闭连接。 这时,TCP 连接关闭完成。
需要注意的是,TCP 连接的建立和关闭都是需要经过三次握手和四次挥手的过程,这样可以保证连接的可靠性和完整性。 同时,在实际应用中还需要考虑网络延迟、丢包等因素,以确保 TCP 连接的稳定性和高效性。

DNS
DNS(域名系统)是一种将域名解析为 IP 地址的协议。
通过 DNS,用户可以用更易于记忆的域名来表示互联网上的各种网络资源,如网站、邮件服务器等。
DNS 这个服务是由一个全球分布的、自组织的 DNS 网络共同提供的。
在进行 DNS 查询时,用户会向本地 DNS 服务器发送请求,如果该 DNS 服务器缓存了相应的解析记录,则直接将其返回给用户;
如果未缓存,则向根域名服务器发起查询,根域名服务器返回该域名所在的顶级域名服务器,再向其发起查询,
直到最终找到对应的 DNS 服务器并返回 IP 地址。
这个过程被称为递归查询。
DNS 的作用非常重要,因为它使得用户可以通过更加友好的域名来访问互联网上的资源,而无需记忆复杂的数字 IP 地址。
同时,DNS也可以帮助网络管理员对网络流量进行管理和控制,以提高网络性能和安全性。
负载均衡(Load Balancing)
负载均衡(Load Balancing)是一种将工作负载分配到多个计算资源上的方法,以提高系统的可扩展性、可用性和性能。
当用户的请求量增加时,负载均衡器可以动态地将请求分配到多个服务器上,避免了单个服务器过载,从而保证系统的稳定性和性能。
常用的负载均衡技术包括:
硬件负载均衡:使用专门的负载均衡硬件设备,能够提供高性能和可靠性,但价格较贵。
软件负载均衡:使用软件实现负载均衡,可以运行在普通服务器上,成本较低,但性能和可靠性可能受到限制。
DNS 负载均衡:通过 DNS 服务器将请求分发到不同的 IP 地址,达到负载均衡的效果。
内容分发网络(CDN)技术:将静态内容缓存到全球各地的 CDN 边缘节点,让用户可以从离自己最近的节点获取内容,降低了带宽消耗和响应时间。
负载均衡技术对于高流量、高并发的应用场景非常重要,如电商网站、互联网金融、在线游戏等。
集群(Clustering)
集群(Clustering)是指将多台计算机连接到一起,以共同完成某个任务或提供某种服务的计算机系统。
其目的是通过分发工作负载,提高系统的可用性、可靠性和性能。
在集群系统中,多台计算机通过网络连接实现相互通信和协作,可以共享数据和资源,同时也可以相互备份和故障转移,从而实现了高可用性和容错性。
常见的集群系统有以下几种类型:
高性能计算集群:用于科学计算、大数据处理等需要高性能计算能力的应用场景。
负载均衡集群:用于解决网络服务的高并发和负载均衡问题。
数据库集群:用于提高数据库的可用性、可靠性和性能。
Web 应用服务器集群:用于提供高可用性的 Web 服务,如电商网站、社交媒体等。
存储集群:用于提供分布式存储服务。
集群系统需要满足以下特点:
可扩展性:可以动态地增加或删除计算节点来扩展或缩减集群系统的规模。
可靠性:集群系统应该能够容纳节点的故障,并自动地进行故障恢复。
性能:集群系统应该能够提供高性能的计算、存储和网络带宽。
管理和维护:集群系统应该具备易于管理和维护的特点,包括安全管理、监控和日志记录等。
Caching 缓存
Caching(缓存)是指在计算机系统中,将访问频度高的数据临时存储在快速访问的存储设备中,以提高数据访问的速度和响应性能的技术。
常见的缓存技术有内存缓存、磁盘缓存、页面缓存、CDN缓存等。
通过缓存技术,计算机系统可以减少对后台数据源的访问,从而提高系统的性能和吞吐量。
例如,在一个网站中,可以将经常访问的页面或图片缓存在 CDN 上,以减少用户的等待时间和网络带宽的消耗。
缓存技术的优点如下:
加快数据访问速度,提高系统响应性能。
减少对数据源的访问次数,降低系统负载和延迟。
提高系统可扩展性和可靠性。
减少对带宽的消耗。
改善用户体验,提高用户满意度。
缓存技术的缺点如下:
缓存数据可能过期或失效,需要进行缓存管理和维护。
缓存数据与源数据可能不一致,需要进行缓存一致性处理。
缓存可能消耗大量的存储空间和内存资源。
缓存可能增加系统复杂度,需要进行缓存策略设计和实现。
因此,在进行缓存设计和实现时,需要平衡缓存和数据源之间的复杂度和性能,并根据实际情况选择合适的缓存技术和策略。
缓存更新策略
缓存更新策略(1) : Read/Write Through – 并发 脏数据
读
如果没有命中缓存 都会更新缓存
写
缓存更新策略(2) : Cache Aside – 避免脏数据
写 只更新数据库 然后删除缓存
读 更新缓存
Content Delivery Network (CDN) 内容分发网络
Content Delivery Network(CDN),中文名为内容分发网络,是一种收费的云服务,专门为通过互联网传输高质量内容的客户提供加速、可靠的内容分发系统。
CDN 是由多个服务器组成的分布式系统,在全球各地有多个服务器节点,可以迅速从最近的服务器向用户提供内容并达到降低网络延迟和增加传输速度的效果。
CDN 通常是由以下三部分构成的:
源站:源站就是存储原始数据的服务器,一般为运营该网站的服务器。
边缘节点:在 CDN 中拥有大量分布式节点群,这些节点储存网站的部分数据,比如图片、视频等。这些节点位于全球各地,可以提供更快的数据传输和更高的可靠性。
负载均衡器:负载均衡器负责将用户请求传输到最近的边缘节点并将用户的请求加速处理。
CDN 的优点有:
可以缩短加载时间,提高用户访问速度,提升用户的体验。
具有高可用性,由于 CDN 分布在不同的网络节点,一旦某个节点出现故障,其它节点可以很快提供备用服务,保证用户访问不受影响。
可以减轻网站源站的负担。
可以加强数据的安全性和保密性。
可以削减网络带宽消耗,从而节省成本。
可以提高防御DDoS攻击的能力。
CDN 的缺点有:
需要付费使用。
无法加速动态数据的传输,对于实时数据缓存也不适用。
需要改变 DNS 记录,会对网站的稳定性造成影响。
总的来说,CDN 是提高网站访问速度和质量的一种有效的方式,尤其对于访问量和带宽大的网站,它尤其有用处。
Proxy 代理
代理(Proxy)是指利用一个代替另一个执行某个操作或获取信息的行为。
在计算机网络中,代理是一种常用的技术,可以为客户端提供访问资源的方式。
代理服务器(Proxy Server)在网络中起到了连接 Internet 和局域网的桥梁作用, 它可以代替客户端向 Internet 内的 Web 服务器请求资源,并将请求得到的信息转发回客户端, 同时也可以缓存请求的资源,从而减少客户端和互联网之间的网络带宽,提高访问速度。
代理服务器的种类有很多,常见的包括:
匿名代理(Anonymous Proxy):可以隐藏端口的代理。
透明代理(Transparent Proxy):可以隐藏用户IP地址的代理。
反向代理(Reverse Proxy):可以隐藏服务器端口的代理。
代理服务器的主要作用有以下几个方面:
访问互联网:代理服务器可以代替客户端向 Internet 内的 Web 服务器请求资源。
加速访问:代理服务器可以缓存请求的资源,减少客户端和互联网之间的网络带宽,提高访问速度。
隐藏 IP 地址:代理服务器可以隐藏客户端的 IP 地址,提高个人隐私。
访问受限资源:代理服务器可以突破一些地理或政策限制,访问一些受限的资源。
安全控制:代理服务器可以对客户端请求进行安全控制,保护客户端免受一些安全威胁。
虽然代理服务器在一定程度上增加了网络访问的安全性和方便性,但也有一些缺点,比如增加了单点故障的风险,代理服务器也可能会成为攻击者的攻击目标。 因此,在使用代理服务器时需要注意安全问题。
Availability 可用性
在计算机领域中,可用性(Availability)指的是在一定时间内,系统或应用程序可以正常运行、可访问和可用的程度,
在 IT 服务管理中是指 IT 系统或应用程序随时处于可以使用状态,可以满足业务需要。
可用性是评估系统和应用程序性能的重要指标之一。
高可用性意味着系统或应用程序的稳定性比较好,发生故障的概率较低,故障恢复时间也比较短, 从而减小了业务停滞和数据丢失的风险,提高了业务连续性和用户满意度。
常见的提高可用性的措施包括:
多种技术手段,如使用负载均衡、集群、冗余备份、监控、自动故障处理等技术实现高可用性。
非核心业务的容错处理,通过数据备份、数据灾备、数据冗余等方式保证主要数据不会因为故障而丢失,降低业务维护成本。
快速响应,及时发现和解决问题,减小业务影响,避免加重故障造成的影响。
定期维护和升级服务器和应用程序,加强监控和灾难恢复等机制,减少故障发生的可能性。
综上所述,可用性是 IT 系统或应用程序的重要性能指标之一,提高可用性可以减小故障带来的风险,提高业务连续性和用户满意度。
Scalability 可扩展性
在计算机领域中,可扩展性(Scalability)指的是系统或应用程序能够适应业务规模和需求增长而不需要对系统或应用程序进行重新设计或重新实现的能力。
也就是说,当应用程序或系统因业务需求变化需要扩展时,可扩展性能够保证系统或应用程序能够在系统硬件或软件系统资源进行适应性扩展。
可扩展性是衡量系统或应用程序性能的重要指标之一。
具有良好的可扩展性的系统或应用程序能够适应不断变化的业务规模和需求,满足不断增长的用户需求,从而提高了系统或应用程序的生命周期和价值, 是大型企业和互联网公司所追求的核心技术之一。
常见的提高可扩展性的措施包括:
使用分布式架构,通过增加服务器、分片技术、分布式缓存等方式提高系统/应用程序的处理能力。
采用异步处理和消息队列技术,减少请求阻塞和等待,提高系统并发处理能力和资源利用率。
调优数据库和缓存,缓解系统瓶颈,在处理高并发请求的场景下减小数据库和缓存的负载压力。
选择可扩展的开发框架和工具,提高代码可维护性和扩展性,并降低代码重构,可以为系统提供快速迭代和快速调整的能力。
综上所述,可扩展性是系统或应用程序的重要性能指标之一,提高可扩展性能够帮助企业和互联网公司适应不断变化的业务规模和需求,是关键的技术优势。
Storage 存储
在计算机领域中,存储(Storage)通常指的是存储设备或存储介质,用于存储数据和程序。存储可以分为内部存储和外部存储两种类型。
- 1)内部存储:包括计算机的主存储器(RAM)和硬盘(Hard Disk Drive,HDD)。
主存储器是计算机中用于存储正在运行的程序和数据的临时存储设备。它的特点是速度非常快,但存储容量有限且电源关闭后数据会丢失。
硬盘是计算机中的永久性存储设备,可以长期保存数据,但读写速度相对较慢。
2)外部存储:包括磁盘阵列(RAID)、网络存储(NAS)、存储区域网络(SAN)、云存储等。
它们的特点包括:适合大量数据的存储、可扩展性强、数据备份等。
现代计算机中,存储技术不断发展和进步,越来越多的新存储技术的出现,例如:固态硬盘(Solid State Drive,SSD)、闪存(Flash)、光盘(CD-ROM、DVD-ROM、Blu-ray Disc)等。
存储容量的提升、存取速度的加快等技术的不断发展,对各行各业的计算机应用产生了深远的影响,促进了大数据、云计算、人工智能等新兴应用的发展。
API网关
API网关是一种服务器,用于将来自客户端的API请求转发到后端服务。
它位于客户端和后端服务之间,并将所有请求路由到相应的服务、负责鉴权等安全方面,并提供其他API功能。
作为目前微服务架构标准组件,API网关具有如下几个优点:
1.流量控制:API网关可以做流量控制,限制一段时间内对某个微服务的请求次数,避免网络拥堵和微服务雪崩效应;
2.服务发现:API网关可以结合注册中心来实现自动的服务发现和负载均衡,将请求分配到多个实例之间,提高服务的可用性;
3.安全认证:API网关可以完成请求授权和认证,保障API服务的安全性,避免非法请求进行攻击;
4.API统一管理:API网关可以实现多个API的管理,当修改、升级或下线某个API时,只需调整API网关的配置即可,不需要修改客户端或后端服务的代码。
API网关一般有多种开源实现,例如Nginx、Kong、Zuul等,也有很多云服务提供商提供托管服务,例如AWS API Gateway,阿里云API网关,Google Cloud API网关等。
常见的API 技术 (REST、GraphQL、gRPC、SOAP、WebSocket)
常见的API技术包括以下几种:
REST API:基于HTTP协议实现的一种Web API,使用HTTP请求方法对Web资源进行操作,通常返回JSON或XML格式的响应数据。
SOAP API:基于XML协议和SOAP(Simple Object Access Protocol)协议实现的Web服务,
通常使用WSDL(Web Services Description Language)描述SOAP API的接口信息,需要使用SOAP客户端进行访问。
GraphQL API:由Facebook推出的一种用于API创建与查询的语言和运行时环境,具有数据类型验证、请求记忆、查询深度限制等特性,
支持多个查询同时访问。
WebSocket API:使用WebSocket协议实现的一种实时通信API,支持双向通信,可以实现客户端与服务器之间的实时数据传输,
通常使用JSON或二进制数据格式传输数据。
gRPC API:由Google推出的一种高性能、开源的RPC(Remote Procedure Call)框架,使用Protocol Buffers作为数据序列化协议,
支持多种编程语言,包括C++、Java、Go、Python等。
不同API技术有着各自的优缺点和适用场景,开发者需要根据具体需求选择最为合适的技术。
服务器和客户端双向通信的技术 (长轮询、WebSockets、服务器发送事件 (SSE))
长轮询、WebSockets和服务器发送事件(SSE)都是实现服务器和客户端双向通信的技术,但它们实现的方式和适用场景略有不同。
长轮询(Long Polling):长轮询是一种传统的实现服务器和客户端双向通信的方式。
在长轮询中,客户端发送一个HTTP请求到服务器,
服务器返回一个保持连接打开的响应,直到有新的数据接收时才返回响应。
这种方式虽然实现了双向通信,但是会造成服务器负载和带宽浪费。适用于实时性不高、数据量较小的场景。
WebSockets:WebSockets是一种新型的实现服务器和客户端双向通信的技术,它基于TCP协议,实现了真正的双向通信。
WebSockets在建立连接时,会与服务器进行握手,之后可以在双方之间传输数据。
这种方式实现了高效、实时的双向通信,适用于实时性要求较高、数据量较大的场景。
服务器发送事件(SSE):服务器发送事件也是一种实现服务器和客户端双向通信的技术,它通过HTTP请求与服务器建立连接,服务器可以向客户端发送事件。
服务器发送事件不同于WebSockets,它是服务器主动向客户端推送数据,而不是由客户端发起请求。
这种方式适用于实时性要求不高,而且数据量较小的场景。
总的来说,长轮询、WebSockets和服务器发送事件各有优缺点,需要根据具体场景进行选择。
如果需要高效、实时的双向通信,可以选择WebSockets;
如果数据量较小,而且实时性不高,可以选择服务器发送事件;
如果需要兼顾实时性和数据量,可以选择长轮询。
熔断器 Circuit breaker
熔断器(Circuit Breaker)是一种在分布式系统中使用的模式,用于避免故障的扩散,从而提高系统的稳定性和可用性。
熔断器的作用类似于家庭电路中的保险丝,当电路中出现问题时,保险丝会自动熔断,从而避免电路的过载和短路。
同样地,在分布式系统中,当某个服务出现故障时,熔断器将该服务与系统隔离,从而避免故障的扩散,同时熔断器也会提供一定的反馈和重试机制,让系统更加健壮和稳定。
熔断器通常实现为一个状态机,其中包括三种状态:关闭状态、打开状态和半开状态。
当系统正常时,熔断器处于关闭状态,所有请求都会正常处理。
当某个服务出现故障,达到设定的阈值后,熔断器将进入打开状态,所有请求将被拒绝,
直到设定的时间过去后,熔断器将进入半开状态,允许一些请求进行处理,检查服务是否恢复正常。
如果服务恢复正常,熔断器将回到关闭状态,否则将继续保持打开状态,并定期检查服务是否已经修复。
熔断器可以帮助分布式系统更好地处理故障和异常情况,提高系统的健壮性和可靠性,从而使分布式系统更加稳定和可用。
服务发现 Service Discovery
服务发现(Service Discovery)是指在分布式系统中动态地发现和识别可用的服务实例的过程。
在现代的微服务架构中,每个服务通常都有多个实例和不同版本,并且这些实例可能会动态地增加或者减少。服务发现旨在解决以下问题:
服务的位置:服务发现使得客户端可以发现某个服务实例的位置。
服务的负载均衡:当某个服务有多个实例时,服务发现可以自动地实现负载均衡,从而平衡不同的服务实例之间的请求流量。
高可用性:服务发现可以监控服务实例的健康状况,当某个实例出现故障时,自动地将请求转移到其他可用的实例。
通常,服务发现使用一些独立的组件来实现,它们提供一些标准的API和协议来实现服务的注册和发现。其中比较流行的服务发现组件包括:
Consul:由HashiCorp公司开发的服务发现和配置管理工具。
ZooKeeper:由Apache基金会开发的一个分布式协调服务。
Eureka:由Netflix公司开发的服务发现工具。
Kubernetes:一种流行的容器编排工具,也支持服务发现和负载均衡功能。
服务发现是现代微服务架构中非常重要的一环,它不仅可以提高系统的可用性和可扩展性,还可以降低系统的复杂度和维护成本。
灾难恢复(Disaster Recovery,DR)
灾难恢复(Disaster Recovery,DR)是指在发生灾难性事件导致系统或应用不可用时,通过恢复数据、应用和系统,使业务能够在较短时间内恢复正常运行的能力。
其主要目的是保证业务的连续性和可用性,最小化灾难对业务造成的影响和损失。
DR 可以包括多个层面的实践,例如:
数据备份,将数据备份到本地或远程位置,以防数据中心或服务器出现故障导致数据丢失。
虚拟化,通过虚拟化技术在备份服务器上运行灾难时的主服务器。
高可用性(HA),在多个数据中心或服务器上部署多个实例以增加可用性。
测试和演练,通过模拟灾难来测试和验证灾难恢复计划的可靠性和有效性。
自动化,使用自动化工具确保灾难应对过程的实施正确性和一致性。
DR 是企业信息化中非常重要的一环,它不仅可以帮助组织应对各种意外事故,保障业务的连续运营,也是业务安全和成功的必要条件之一。 因此,组织需要制定和实施完整的灾难恢复计划以应对可能发生的灾难事件。
虚拟机(VM)和容器
虚拟机(VM)和容器都是计算机技术中常用的虚拟化技术,它们的作用是在物理服务器上创建多个隔离的运行环境,
使得在一个物理服务器上可以同时运行多个应用程序或服务,从而提高硬件资源利用率。
虚拟机是一种完全的虚拟化技术,它可以在一台物理计算机上运行多个虚拟机,每个虚拟机都是一台独立的计算机,包括独立的操作系统、硬件资源等。 虚拟机使用虚拟化软件创建,并与底层的物理服务器隔离。
虚拟机可以在不同的操作系统平台之间移植,并且支持各类应用,因此被广泛应用于企业中的服务器虚拟化、开发、测试等场景。
容器是一种轻量级的虚拟化技术,它利用宿主机操作系统内核来创建多个运行环境,每个容器都是一个独立的运行环境,包括应用程序、依赖项和运行时库等。 与虚拟机不同的是,容器之间共享相同的操作系统内核,因此比虚拟机启动更快、消耗的资源更少。 容器通常用于应用打包、快速部署和微服务架构,支持不同的应用程序和语言。
虚拟机和容器各有自己的优缺点
虚拟机提供了更高的隔离性和安全性,可以运行多种不同的操作系统和应用程序,但需要占用更多的资源和启动时间较长。
容器启动速度快、占用资源较少,但容器之间的隔离性较弱,安全性不如虚拟机。
根据应用场景的不同,可以选择使用虚拟机、容器或混合使用两种技术。
####
SSL、TLS、mTLS 通信安全协议
SSL(Secure Sockets Layer)和TLS(Transport Layer Security)是常用的通信安全协议,用于网络通信的加密、认证和数据完整性保护。
SSL 是一种早期的协议,后来被 TLS 所取代,TLS 的版本从 1.0 至 1.3 不断更新完善。
传统的 SSL/TLS 是单向认证,只需要服务端提供证书,客户端验证服务器证书的有效性即可。
SSL/TLS 使用公钥和私钥进行加密通信,其中,公钥用于加密发送方的数据,而私钥用于解密接收方的数据。
协议还包括数字证书和证书信任链,用于验证通信双方的身份和建立可信连接。
SSL/TLS 通信协议广泛应用于 Web 等 Internet 应用中,可以使用 HTTP 协议(HTTPS)或其他应用层协议进行通信。
mTLS(Mutual TLS)是一种基于证书的双向认证机制,也称为双向 SSL。
mTLS 在服务端和客户端之间建立双向信任关系,客户端需要提供自己的证书,服务端验证客户端证书的有效性。
这种机制可以提高通讯的安全性,进一步防止中间人攻击和伪装攻击等。
mTLS 将双向认证机制应用于 SSL/TLS 通信中,客户端与服务端之间需要提供证书进行身份认证。
mTLS 广泛应用于微服务、容器化应用、API 网关等场景,可以提高应用之间的安全通信能力。
单点登录 (SSO)
单点登录(SSO) 是一种身份验证机制,允许用户只需进行一次登录就可以访问多个应用程序或系统
它提供了用户合并其登录凭据并只需登录一次即可访问多个应用程序或系统的便利性和安全性。
在 SSO 中,用户只需一次性提供其身份验证凭据,例如用户名和密码,然后这些凭据被保存在一个安全的位置中,称为信任服务器或者认证服务器。
其他应用程序或系统之间的通信会与此认证服务器进行验证,从而允许用户访问多个应用程序而无需重新进行身份验证。
这种方式可以有效地减轻用户的密码管理负担,减少他们的时间和精力。
还可以提高安全性,因为用户只需在安全的认证服务器上登录一次,而不是多次登录多个应用程序,避免了密码泄露风险。
SSO 通常通过使用标准身份验证协议(如OAuth、OpenID Connect 或 SAML)来实现。
这些协议允许应用程序之间易于通信和协调,从而提供了一个简便、安全和容易维护的 SSO 方案。
使用Python实现SSO的一种可能方案:
使用Flask或Django等Web框架搭建认证服务器(称为Identity Provider或IDP)。IDP将负责对用户进行身份验证并颁发身份验证令牌(ID token)。
将所有需要SSO的应用程序(称为Service Provider或SP)与IDP集成,使它们可以与IDP通信并根据需要验证来自IDP的ID token。可以使用现有的Python包,如flask_sso、py_sso等来实现这些。
当用户尝试访问SP时,SP将请求重定向到IDP,并带有一个唯一标识符作为参数,以便IDP将用户与请求的应用程序关联起来。
如果用户尚未进行身份验证,则IDP将引导用户进行身份验证,并颁发一个ID token以供SP验证。
IDP向SP返回一个断言(Assertion),其中包含用户身份的信息和时间戳。SP可以使用此信息来验证用户身份并允许用户访问资源。
在此过程中,使用的身份验证协议通常是OpenID Connect(OIDC),它建立在OAuth2之上,并提供身份验证令牌,用户属性和作用域等附加功能。
以上是一个基本的SSO方案,实际上需要更多的细节和安全措施来保护用户数据和避免安全漏洞。
OAuth 和 OpenID 连接 (OIDC)
OAuth和OpenID Connect(OIDC)都是Web身份验证协议,用于允许用户使用其身份验证来访问其他应用程序或系统。
-
OAuth是一种授权协议,允许用户授权第三方应用程序来访问其受保护的资源,而无需提供其用户名和密码。例如,一个用户可以使用他们的Google账户来登录和授权其他应用程序访问他们的日历数据,而无需向第三方应用程序透露他们的Google账户密码。
-
OIDC则建立在OAuth之上,同时提供了标准的身份验证方式。OIDC不仅允许用户授权第三方应用程序访问其资源,还提供了身份验证令牌(ID Token), 用于验证用户已经通过身份验证并授权给应用程序。
与OAuth一样,OIDC还可用于实现单点登录(SSO),使用户只需一次身份验证即可访问多个应用程序。
OIDC还提供了用户属性和域(Scope)的概念,允许应用程序请求和获得用户的基本资料。
总之,OAuth和OIDC都是Web身份验证协议,用于允许用户使用其身份验证来访问其他应用程序或系统。
OAuth主要用于授权和访问受保护的资源
而OIDC则建立在OAuth之上,同时提供了标准的身份验证方式。
这两种协议都可以用于实现单点登录。
速率限制 Rate Limiting
速率限制(Rate Limiting),是指限制一个服务在指定时间窗口内接受的请求量,防止恶意攻击、滥用服务以及保持服务的稳定性。
速率限制是Web应用程序中最重要的安全保障之一,可以在保护应用程序防止DDoS攻击、爬虫调用等方面发挥重要作用。
速率限制可以通过多种方式实现,包括:
请求计数器:记录每个IP地址或用户的请求次数,并限制在指定时间窗口内的请求量,当计数器超过限制时,则返回错误信息或暂时禁用该IP地址或用户的访问
令牌桶算法:在令牌桶中,令牌以固定速率生成,并放入桶中。每个请求需要从桶中获取令牌,如果令牌不足,则请求被拒绝。
这种方法可以保证请求的速率不超过固定速率,同时允许额外的请求在某个突发时间段内。
漏桶算法:漏桶算法中,请求从一个固定大小的桶中流出,与令牌桶相反,桶的大小不会改变。如果桶为空,则请求被拒绝。
漏桶算法可以稳定地限制请求的速率,但不允许额外的请求在某个突发时间段内。
在实践中,速率限制通常需要根据面向的场景和应用程序的规模、复杂度等因素进行不同的调整和优化。
CSP 并发机制
CSP并发机制 是一种基于通信的并发编程模型,主要应用于并发控制、进程通信和并发逻辑的实现等方面。
其中CSP是Communicating Sequential Processes的缩写,即通信顺序进程模型。
在CSP并发机制中,所有的并发任务都是由独立的进程组成,它们之间通过进程间通信(IPC)来传递数据与状态,从而实现进程间的协同工作。
其中,CSP的核心概念是通道(Channel),即一个连接发送者和接收者的缓冲区,可以用于进程间的数据交换。
CSP并发机制的主要特点包括:
显式的进程间通信:通过定义通道来实现进程间的通信,可以避免线程同步带来的复杂度和竞争条件。
避免竞争条件:各个并发任务通过使用不同的通道来避免竞争条件,从而实现更高效和稳定的并发控制。
强制顺序:CSP模型使用通道传输数据的方式,赋予数据以强制的传输顺序,避免了并发带来的不确定性。
应用范围广:CSP并发机制适用于多种场景,包括网络编程、操作系统内核、分布式系统、并发逻辑等。
总的来说,CSP并发机制是一种高效、稳定和可靠的编程模型,为实现大规模并发应用提供了一种更好的思路。
Go语言的逻辑处理器(Logical Processor)
Go语言的逻辑处理器(Logical Processor)是指一组可用于进行并发调度的硬件线程,Go程序可以使用逻辑处理器来进行对goroutine的调度和执行。
Go的调度器将goroutine分配到绑定的逻辑处理器上运行,从而实现了并发执行。
Go的调度器是基于m:n模型实现的,即将m个goroutine映射到n个OS线程上执行。
调度器根据goroutine的特性实现了一些高效的优化调度算法,其中最重要的优化有:
Goroutine的调度是非抢占式的:
即一个goroutine在没有显式地调用runtime.Goexit()或者发生IO阻塞等情况时,不会自动让出CPU。
因此,在开发中需要注意避免一个goroutine长时间的运行。
执行态、阻塞态的转换是通过channel实现的:
Goroutine阻塞在一个channel上时,调度器会将该goroutine的执行状态设置为阻塞态,并将该goroutine所占用的处理器对其他goroutine开放。
当channel有数据可读或可写后,该goroutine重新进入就绪态等待去执行。
通过本地运行队列和全局运行队列快速地进行goroutine的调度和切换。
总之,Go的调度器在并发和并行处理上有着出色的性能表现,使用逻辑处理器将goroutine映射到不同的线程完成任务,能够在多个核心上高效利用计算资源,从而实现并发处理。
Python 中 internal check
在 Python 中,主线程执行 internal check 的时候,主要是执行一些系统级的操作,例如:
垃圾回收:Python 通过垃圾回收器来回收不再使用的内存,主线程会在一定周期内执行垃圾回收,以保证 Python 程序不会占用过多的内存资源。
I/O 调度:Python 通过 select 或 epoll 等机制来进行 I/O 调度和事件处理,主线程会不断地检查有没有新的 I/O 事件需要处理,以保证程序的正常运行。
键盘信号处理:在 Linux 或者 Unix 系统下,主线程会检查是否收到了键盘信号(比如 Ctrl-C),并对信号进行相应的处理,例如终止程序的运行等。
定时器处理:主线程会处理定时器事件,例如在定时器到期时触发回调函数等。
总的来说,主线程执行 internal check 的时候,主要是执行系统级别的操作,以保证 Python 程序的正常运行和内存占用的合理控制。
闭包和装饰器
闭包和装饰器都是 Python 中的高级特性,下面简单介绍一下它们的概念和用法。
闭包
闭包是指一个函数可以访问另一个函数内部的变量,并且该内部变量可以在外部函数执行完毕后仍然被访问和使用。
简单来说,闭包就是一个函数和与其相关的引用环境组合而成的实体。
闭包的作用:
可以用于封装模块,可以避免污染全局变量空间;
可以保护函数内部的变量不被修改;
可以实现装饰器,详见下面的装饰器部分。
以下是一个简单的闭包示例:
def outer(x):
def inner(y):
return x + y
return inner
f = outer(10) # f是一个闭包,x的值为10
print(f(20)) # 输出30,相当于调用inner(20)
装饰器
装饰器是一个函数,它可以在不改变原函数代码的前提下增加函数的功能。 我们可以把装饰器理解为一个“装饰”函数,它接受一个函数作为参数, 返回一个函数对象,使得原函数的功能得到了增强。
装饰器最常用的应用场景是在函数执行前后执行一些预处理或后续操作,例如打印日志、计时和缓存等。
以下是一个简单的装饰器示例:
import time
def time_it(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print('function {} takes {} seconds'.format(func.__name__, end - start))
return result
return wrapper
@time_it
def my_func(x):
return x**2
print(my_func(10)) # 输出100,同时输出函数执行时间
注意事项:
通过 @ 装饰器形式可以为函数添加装饰器;
装饰器需要返回一个函数对象,这个函数对象需要和原函数具有一样的输入输出;
可以为装饰器添加参数,例如 @decorator(args),此时需要在装饰器函数再套一层函数进行处理。
总结:
闭包和装饰器是 Python 中常用的高级特性;
闭包可以用于封装模块和实现装饰器;
装饰器可以用于增强函数的功能,例如计时和缓存等;
装饰器需要返回一个函数对象,具有和原函数一样的输入输出。
什么是MD5加密,有什么特点?
MD5是一种广泛使用的哈希函数,可将任意长度的消息输入并输出固定长度的“指纹”,通常为128位。
它是“消息摘要算法”(Message Digest Algorithm)的缩写。
MD5算法的核心思想是将输入的消息分成小块,对每个块进行处理,最后合并结果,产生一个128位(16字节)的哈希值。
在应用中,常常用MD5算法来校验数据完整性,判断文件是否被篡改过等。MD5算法通常不可逆,也就是说,无法根据输出的哈希值推导出输入的原始消息。
MD5算法的特点是:
不可逆性:无法根据输出的哈希值推导出输入的原始消息。这种不可逆性可以保证数据的安全性,防止数据被篡改。
唯一性:不同的输入消息产生的哈希值是唯一的,可以用于数据校验。
执行效率高:MD5算法计算速度快,适合对大量数据进行哈希计算。
完整性保护:MD5算法可用于密码存储和传输保护,防止密码的明文存储和传输被攻击者获取并利用。
总之,MD5算法是一种常用的哈希算法,具有不可逆性、唯一性、执行效率高、完整性保护等特点,被广泛应用于数据安全领域。
但是,由于MD5算法已经被攻击者成功攻击,因此在一些安全领域已经不再建议使用MD5算法,而是采用更加安全的哈希算法,如SHA-3算法。
对称加密和非对称加密是两种常用的加密方式
对称加密是指发送和接收双方使用相同的密钥进行加密和解密的过程。
在对称加密中,加密和解密使用的是同一把密钥,该密钥需要发送方和接收方都知道。
这种加密方式的优点是加解密速度快,但缺点是密钥需要在双方之间传递,传输安全性难以保证。
假设发送方想要加密一个字符串“hello”,使用AES对称加密算法,并将密钥设置为“1234”。
加密过程如下:
将字符串“hello”转换为二进制流01001000 01100101 01101100 01101100 01101111
使用密钥“1234”对二进制流进行加密,得到密文10110110 11001001 11101111 11001110 00111110
发送方向接收方传输密文10110110 11001001 11101111 11001110 00111110
接收方收到密文后,使用密钥“1234”进行解密,
解密过程如下:
使用密钥“1234”对密文进行解密,得到二进制流01001000 01100101 01101100 01101100 01101111
将二进制流转换为字符串“hello”
因此,接收方成功解密了发送方发送的数据。
非对称加密是指加密和解密使用的密钥不同,发送方使用接收方的公钥进行加密,接收方使用自己的私钥进行解密。
该加密方式的优点是安全性高,但缺点是运算速度慢。
假设Alice想要向Bob发送一个加密消息。
Bob生成一对公私钥,其中公钥用于加密,私钥用于解密。
Bob将公钥发送给Alice,然后Alice使用Bob的公钥将消息加密,并将加密后的消息发送回Bob。
Bob再使用自己的私钥将消息解密。
具体操作如下:
Bob生成一对公私钥
Bob将公钥发送给Alice
Alice使用Bob的公钥将消息“hello”加密
Alice向Bob发送加密后的消息
Bob使用自己的私钥将消息解密,得到原始消息“hello”
在这个例子中,加密和解密过程使用了不同的密钥,加密使用了Bob的公钥,而解密使用了Bob的私钥,从而保证了数据的安全性。
在实际应用中,为了兼顾安全性和速度,通常采用混合加密,将对称加密和非对称加密方式结合起来使用。 发送数据时,首先使用非对称加密方式传输对称加密密钥,然后使用对称加密方式加密数据传输,接收方使用非对称加密密钥解密对称加密密钥,然后使用对称加密方式解密数据。
总之,对称加密和非对称加密各有优缺点,选择何种加密方式应该根据具体的应用场景来确定。
架构与设计模式
N-tier architecture,也称为多层架构
N-tier architecture是一种软件架构形式,其中应用程序被分成多个相互关联但相互独立的层(层数取决于应用程序的需求)。
它主要被用于复杂的企业应用程序中,由于其可伸缩性和可维护性而备受青睐。
N-tier架构通常由三个核心层组成:
表示层(Presentation Layer):这是应用程序和用户之间的接口层,负责处理用户请求并向用户展示数据和结果。通常使用web应用程序处理请求和响应。
业务逻辑层(Business Logic Layer):这是应用程序的中间层,负责控制数据访问和处理业务逻辑,以满足用户需求。它通常由计算机处理大量的业务流程,并且可以通过网络传输数据,与数据库进行交互。
数据访问层(Data Access Layer):这是N-tier架构中的底层,该层负责处理与数据库的通信和数据访问。数据访问层主要处理数据库的查询和更新,然后将结果返回给业务逻辑层。
此外,N-tier架构还可以添加其他层,如安全层、日志层、缓存层等,以增强应用程序的性能和安全。
N-tier架构使得应用程序可以分离出多个功能,并且使得每个层在满足特定需求时变得更容易维护和修改。
此外,N-tier架构还提供了灵活性和可扩展性,可以在不中断业务过程的情况下随着业务增长进行扩展。
Message Brokers,又称为消息中间件
Message Brokers 是一种用于构建分布式系统的软件组件。
它的作用是在分布式系统中传递消息,从而解耦系统的各个部分,使它们之间的通信更加高效和可靠。
消息代理的基本工作原理是:
生产者/发送者将消息发送到代理,然后代理将消息路由到一个或多个消费者/接收者。
消息代理可以指定消息的持久性、优先级、路由规则等属性,以便更好地处理消息。
消息代理有很多实现方式,
包括企业级消息队列(如 Apache ActiveMQ、RabbitMQ 和 IBM MQ)、
分布式日志记录系统(如 Apache Kafka)、
HTTP 队列系统(如 Apache Camel)、
WebSocket、AMQP(高级消息队列协议)等。
消息代理具有以下优点:
解耦系统:消息代理可以将系统中的不同部分解耦,从而使系统部分之间的通讯更加高效和可靠。
异步通信:消息代理可以让发送方和接收方实现异步通信,从而减少应用程序的延迟和等待时间。
可扩展性:由于消息代理作为中间层处理消息,因此可以在不影响系统其它部分的情况下,无限扩展系统。
容错性:消息代理可以通过处理丢失或错误的消息使整个系统更加容错。
可靠性:由于消息代理支持持久消息,因此可以确保消息的成功传递,从而增加了整个系统的可靠性
消息队列(Message Queues)
消息队列(Message Queues)是一种为了解决分布式系统中不同模块之间通信的问题而设计的一种软件。
消息队列中的消息按照时间顺序排列,并按照先进先出的原则进行处理。
消息队列的基本流程:
生产者向消息队列中添加消息。
消息队列保存消息,并将其标记为等待处理状态。
消费者从消息队列中读取消息,并将其标记为已处理状态。
消息队列检查消息的状态,并将已处理的消息从队列中删除。
消息队列可以用于在不同的进程或不同的系统之间发送和接收消息。
一些常用的消息队列有 RabbitMQ、ActiveMQ、ZeroMQ、Kafka 等。
消息队列的优点:
解耦合:消息队列可以有效地解耦合不同组件或服务之间的耦合关系。
异步处理:能够支持异步消息处理,减轻高并发下服务的压力以及处理响应时间。
缓存:能够有效地缓存来自消息队列的数据,提高系统读取消息的效率。
可靠性:通常消息队列支持数据持久化,使得即使在出错或者电源掉电等情况下数据也能被有效地保存。
扩展性:消息队列可以非常容易地通过横向扩展来提升系统的复杂性和可用性。
优先级队列的实现
在 RabbitMQ 中,队列本身不支持优先级,但是可以通过在消息中添加属性来实现优先级。
下面就是一个基于 Python 的 RabbitMQ 实现优先级队列的示例:
首先,需要安装 pika 包,这是一个用于 RabbitMQ 的 Python 客户端库。
pip install pika
接下来,我们需要创建一个 RabbitMQ 连接:
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
在 RabbitMQ 中,一个消息可以有多个属性(metadata)。我们可以将优先级作为一个消息的属性,然后在消费者中对消息进行排序和处理。
下面是发送消息的代码:
channel.basic_publish(exchange='my_exchange',
routing_key='my_queue',
body='Hello, World!',
properties=pika.BasicProperties(priority=1))
在这个例子中,我们将优先级设置为 1。如果需要设置更高的优先级,只需要将 priority 的值设置为更大的整数。
接下来,我们需要编写一个消费者来处理消息。消费者需要使用 basic_qos 方法来告诉 RabbitMQ 只发送一定数量的消息给它,以确保消费者不会收到太多的消息而无法处理。
channel.basic_qos(prefetch_count=1)
def callback(ch, method, properties, body):
print("Received message: %r" % body)
ch.basic_ack(delivery_tag = method.delivery_tag)
channel.basic_consume(queue='my_queue', on_message_callback=callback, auto_ack=False)
channel.start_consuming()
在消费者中,我们使用 basic_qos 方法设置只接收一条消息。
在 callback 函数中,我们对消息进行处理。在处理完成后,我们使用 basic_ack 方法告诉 RabbitMQ 已经处理完了该消息,可以将它从队列中删除了。
通过在消息上设置优先级属性以及使用 basic_qos 方法来控制最大消息数量,我们就可以实现一个基于 RabbitMQ 的优先级队列了
消息队列(Message Queues)和消息代理(Message Brokers)
消息队列(Message Queues)和消息代理(Message Brokers)是两个相关但不同的概念。
消息队列是一种基础设施,用于在发送方和接收方之间传递消息并缓存消息。
消息队列通常负责处理消息的发送、接收和缓存,并在消费者不可用时保留消息。
消息代理则是更高级别的服务,它提供了消息路由和分发等高级功能。
消息代理不仅可以缓存和传递消息,还可以根据订阅者的特定需求路由消息。
此外,消息代理还可以提供发现服务,即使客户端不知道发送者或接收者的确切位置,也可以路由消息。
简而言之,消息队列通常只负责传输和缓存消息,而消息代理则是一种更高级别的服务,它提供了路由、分发和发现等高级功能。
一些常见的消息队列包括 RabbitMQ、Apache Kafka 和 ActiveMQ;
一些常见的消息代理包括 RabbitMQ、Apache Kafka 和 Apache Pulsar
发布-订阅(Publish-Subscribe,简称Pub/Sub)模式
发布-订阅(Publish-Subscribe,简称Pub/Sub)是一种消息传递模式,
用于将信息从一个发布者(发布消息的组件)传递给许多订阅者(订阅消息的组件),以实现信息的广播和分发。
在发布-订阅模式中,发布者将消息发送到一个或多个主题(Topics),而订阅者则在一个或多个主题上订阅消息。
发布者不知道哪些订阅者会收到消息,而订阅者也不知道消息的来源。
这种松耦合的通信机制使得发布-订阅模式非常适用于大规模分布式系统,例如 IoT 系统、流式处理等。
发布-订阅模式有两种实现方式:基于主题的订阅和内容过滤订阅。
基于主题的订阅 是最简单和最常用的订阅方式,订阅者只需指定感兴趣的主题即可接收到该主题下的所有消息。
内容过滤订阅 则需要订阅者进行更复杂的订阅配置,例如使用 SQL 表达式来订阅感兴趣的消息内容。
常见的发布-订阅系统包括 Apache Kafka、RabbitMQ 和 Redis Pub/Sub 等
企业服务总线 (ESB)
企业服务总线(Enterprise Service Bus,ESB) 是一种软件集成架构,通常被用于连接不同的应用程序间进行通信,也被称为企业级集成架构。
ESB通过标准化的消息传递协议和格式来连接任何数量的应用程序,使得它们能够相互通信和交换数据。
ESB并不是一种特定的软件或技术,而是一种整体架构和方法,通常由以下几个组件构成:
消息传递:基于标准消息传递协议和格式进行消息传递。
消息路由:路由器可以根据消息的内容、源和目标应用程序的信息来选择合适的路由。
数据转换:ESB可以对消息进行格式化、转换或转码,确保不同应用程序之间的兼容性。
应用集成:个别应用程序通过ESB实现统一的数据交换和在业务流程中的通信,从而实现多系统间无缝衔接。
监控与管理:ESB提供了管理和监控控制台,可以检测消息的流量、延迟和错误,也可以对ESB配置和管理进行监视和管理。
最主要的作用是简化与整合不同的应用程序,使企业内部各系统之间互相通讯变得更加轻松,提高了企业的业务响应速度和管理能力,减少错误,并降低了维护成本。
整体式应用程序 和 微服务 Monoliths and Microservices 架构
整体式应用程序(Monoliths)和微服务(Microservices)是两种不同的应用程序架构方式。
-
整体式应用程序是指将应用程序构建为一个单一、整体的单元。通常,整体式应用程序由一个大型的代码库和数据库组成,通过一些内部的代码结构和框架来实现不同功能的分离。
整体式应用程序容易理解和构建,但它们的维护和扩展可能会变得困难,因为功能之间的耦合性较高,无法进行灵活的更新和变更。
-
微服务是指将应用程序划分为较小的、松耦合的、独立运行的部分。每个微服务都负责一个特定领域的业务逻辑,并使用轻量级通信机制来与其他微服务进行交互。
微服务的优点是可以通过更好的隔离性、故障转移和满足特定需求的专业性提高应用程序的可扩展性和可维护性。
但微服务架构可能会增加应用程序的复杂性,需要一些额外的管理和监控。
总的来说,整体式应用程序更适合于小规模应用,而微服务则更适合于大型复杂的应用程序,需要团队具备较高的技术水平。
具体的架构选择取决于应用程序的功能需求、团队的技术水平和可管理的复杂性,以及可扩展性和可维护性的目标。
事件驱动架构 (EDA) 架构
事件驱动架构(Event-Driven Architecture,EDA)是一种应用程序架构方式,其基本思想是应用程序的各个组件通过事件来进行通信和协作。
在EDA中,应用程序中的各个组件(例如微服务)都被设计为能够发布和订阅事件的实体。 事件可能是应用程序内部组件之间的消息,也可能是来自外部系统的通知。
当一个组件发生某些行为操作(例如读取、创建、更新、删除等)时,它会发布一个事件,告诉其他组件该行为的发生。 其他组件可以订阅这些事件来进行相应的逻辑处理。
EDA的优点在于:
增强了应用程序的松散耦合性和可扩展性,通过事件通信的方式,各个组件之间不需要直接通信,从而减少了组件之间的耦合性。
增强了应用程序的可观察性和可追踪性,因为每个事件都被记录下来,可以查询和分析这些事件,从而对应用程序进行监测和故障排除。
支持异步处理,组件之间的通信可以以异步的方式进行,即使一个组件挂了,整个系统仍然可以正常运行。
EDA的缺点在于:
可能增加复杂性,需要额外的开发工作来实现事件的发布和订阅机制。
不适合所有类型的应用程序,因为EDA需要在应用程序内部实现事件发布和订阅机制,这可能会导致性能问题,特别是大规模应用程序。
基于 Python 和 RabbitMQ 实现 EDA 架构的示例
EDA 架构(Event-Driven Architecture,事件驱动架构)是一种基于事件驱动的架构模式,广泛应用于分布式系统中。
RabbitMQ 是一种流行的消息代理,在实现 EDA 架构时也经常被使用。
下面是一个基于 Python 和 RabbitMQ 实现 EDA 架构的示例:
首先,我们需要安装 RabbitMQ 和 pika 包:
sudo apt-get install rabbitmq-server
pip install pika
接下来,我们需要编写一个事件发生器(event producer),它将事件发布到 RabbitMQ 中:
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='exchanges', exchange_type='direct')
while True:
message = input('Enter a new message: ')
channel.basic_publish(exchange='exchanges',
routing_key='incoming',
body=message)
connection.close()
在这个例子中,我们创建一个直连型(direct)交换器,并使用 basic_publish 方法将消息发布到 RabbitMQ 中。用户在控制台输入一条消息,程序就会将该消息发布到 incoming 队列中。这个程序可以一直运行,以便用户随时发布新的消息。
下面是一个事件处理器(event consumer),它将订阅来自 incoming 队列的消息,并且将它们发送到 outgoing 队列中:
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
channel.exchange_declare(exchange='exchanges', exchange_type='direct')
result = channel.queue_declare('', exclusive=True)
queue_name = result.method.queue
channel.queue_bind(exchange='exchanges', queue=queue_name, routing_key='incoming')
def callback(ch, method, properties, body):
print('Processing incoming message: %r' % body)
processed_message = 'Processed: %r' % body
channel.basic_publish(exchange='exchanges', routing_key='outgoing', body=processed_message)
channel.basic_consume(queue=queue_name, on_message_callback=callback, auto_ack=True)
print('Waiting for incoming messages...')
channel.start_consuming()
在这个例子中,我们使用了 basic_consume 方法来订阅 incoming 队列中的消息,并将它们发送到 outgoing 队列中。
对于每个收到的消息,我们都会在控制台输出该消息的内容,并且将处理后的消息发布到 outgoing 队列中。
这个程序可以一直运行,以便及时处理新收到的消息。
通过这个简单的示例,我们可以看到 RabbitMQ 可以轻松地实现 EDA 架构。我们只需要创建一个交换器,并将事件发生器发布到其中,然后在事件处理器中订阅该交换器并处理收到的事件即可。
事件溯源(Event Sourcing) 模式
事件溯源(Event Sourcing)是一种设计模式,它将应用程序状态的变化表示为一系列不可变的事件流,每个事件记录了发生什么以及何时发生。
通过这种方式,事件溯源可以提供完整的历史记录和可追溯性,也可以用于通过重演事件流来恢复应用程序状态。
在事件溯源中,应用程序的状态不是直接被更新和保存的,而是通过发布事件的方式来记录状态的变化。
例如,一个电商网站的订单状态可能从“待发货”到“已发货”,每次状态变化都会发布一个对应的事件,这些事件被记录到一个不可变的事件流中。
这个事件流可以被用来跟踪订单状态的历史记录,也可以被用来重构应用程序状态,例如,恢复一个被误删除的订单状态。
事件溯源的优点在于:
提供了完整的历史记录和可追溯性,可以轻松地检查应用程序状态的演变。
支持事件驱动的设计,通过事件来驱动应用程序的行为和状态变化。
支持事务性操作,因为事件是不可变的,可以防止在状态更新过程中发生问题。
可以用于支持 CQRS(Command and Query Responsibility Segregation)模式,即将应用程序中的写和读分离,从而提高可伸缩性和扩展性。
事件溯源的缺点在于:
可能需要额外的存储空间来保存事件流。
可能需要额外的编程复杂性来处理事件流。
对于某些应用程序来说,由于需要记录所有状态变化,可能会增加性能负担。
命令和查询责任分离(CQRS) 模式
命令和查询责任分离(CQRS)是一种设计模式,通过将系统中的读和写操作分离,来提高系统的性能、可伸缩性和可维护性。
将数据修改看作是“命令”,而查询数据看作是“查询”,这两个操作分别由不同的代码路径执行。
在CQRS模式中
写操作和读操作的数据模型可以不同
写操作只更新状态,而不返回任何东西
读操作则提供查询服务,它们的结果可能是缓存数据或者基于实时数据的查询结果。
这样,写操作和读操作就可以优化和扩展,从而提高系统的性能。
总之,CQRS模式是一种强调将写操作和读操作分离的设计模式,能够提高系统的性能、可伸缩性和可维护性
发布订阅模式 和 EDA架构
-
发布订阅模式是指生产者 将消息 发布到特定的主题(Topic) 消费者 从相同的主题中订阅消息
这种模式可以使得消息的发送者和接收者解耦,从而提高系统的可扩展性。
-
EDA架构是建立在发布订阅模式的基础上将事件(Event)作为消息的载体 通过事件通知 实现消息传递和处理的异步模型
可以说,EDA架构是在发布订阅模式的基础上,增加了事件的概念和使用。
EDA架构的应用中,事件是系统中的重要元素,各个组件之间需要根据事件的触发和处理来实现系统的业务逻辑。
发布订阅模式则更为通用,它可以应用于各种类型的消息传递场景中,不依赖于具体业务逻辑。
综上所述,发布订阅模式是EDA架构的一种具体实现方式,而EDA架构是建立在发布订阅模式的基础上,增加了事件的概念和使用,用于实现异步消息传递和处理的分布式架构。
常见的做服务治理的工具主要包括:
Spring Cloud:Spring Cloud是一套基于Spring Boot的微服务开发框架,提供了服务注册和发现、服务负载均衡、服务路由、断路器、分布式配置等多种功能,可以轻松实现微服务化开发和服务治理。
Kubernetes:Kubernetes是一个容器编排平台,主要用于容器集群的管理,可以对服务进行部署、扩展、升级和管理等操作。Kubernetes提供了服务发现、负载均衡、自动伸缩等功能,可以在微服务架构中实现服务治理。
Consul:Consul是一个服务发现和配置管理工具,可以实现服务注册和发现、健康检查、负载均衡、DNS解析等功能。Consul还提供了分布式一致性协议,确保服务注册和发现的可靠性。
ZooKeeper:ZooKeeper是一个分布式协调服务框架,可以用于服务发现、配置管理、分布式锁管理等功能。ZooKeeper提供了数据节点的监控和通知机制,可以保证服务的高可用和可靠性。
etcd: etcd是一个高可用的分布式键值存储系统,最初由CoreOS公司开发,现被CNCF接管维护。etcd的设计目的是为了支持分布式系统中的服务发现、配置共享等用例。
etcd以高可用、一致性、可靠性和安全性为核心特性,支持多数据中心、数据加密、访问控制等特性。
etcd的工作原理是基于Raft协议的分布式一致性算法,实现了数据的强一致性。etcd存储键值对,并支持对这些键值对的事务型操作。
etcd还提供了分布式锁、观察者模式等机制,支持leader选举、副本复制、读写分离等功能。
etcd可以被用于服务注册与发现、动态配置管理、分布式锁、分布式任务调度等场景
etcd的使用方式主要包括:
启动etcd服务:通过命令行参数来配置并启动etcd实例。
Put操作:向etcd中存入键值对。
Get操作:从etcd中获取指定键的值。
Watch操作:监视etcd中某个键值的变化。
Delete操作:从etcd中删除指定键的值。
etcd可以被集成到微服务架构中,通过etcd实现服务注册与发现、配置共享、分布式锁等功能,可以提高微服务的稳定性和可靠性。etcd也被广泛应用于容器编排、分布式系统等领域。
使用方式主要包括:
服务注册和发现:微服务架构中,服务需要实时注册到服务注册中心,并通过服务发现机制进行查询和调用。可以使用Consul、ZooKeeper等注册中心来完成。
负载均衡:通过负载均衡算法,将服务请求分配到不同的服务实例上,以提高服务的可用性和性能。可以使用Ribbon、Nginx等负载均衡器来完成。
服务熔断:当一个服务的访问量超过其负载能力时,会出现服务崩溃的情况。为此,可以引入熔断机制,当服务出现异常时,熔断器会快速响应,避免服务的连锁反应。可以使用Hystrix、Resilience4j等熔断器来完成。
服务路由:通过服务路由机制,将服务的访问路径进行控制和管理,以便实现服务请求的转发和分发。可以使用Zuul、Spring Cloud Gateway等网关来完成。
服务监控:通过对服务进行监控和统计,提高服务的可用性和性能。可以使用Spring Boot Actuator、Prometheus等监控组件来完成。
Mysql 数据库
隔离级别和锁
RR和RC是关系型数据库的两种不同的事务隔离级别,主要区别在于数据的读取是否受到已提交的事务所修改的影响。具体来说,区别如下:
RR隔离级别下,事务读取的数据是从一个静态的时间点快照中获取的,即当前事务开始时数据库的状态。
这意味着,其他已提交的事务对数据做的修改对当前事务是不可见的。
因此,RR设计了更多的冲突检测机制。
在 RR 隔离级别下,读取操作和修改操作都会加锁。
RC隔离级别下,事务读取的数据是最新的,即其他已提交的事务对数据做的修改对当前事务是可见的。
这可以提高数据库的并发性,但可能会导致数据不一致的情况。
在 RC 隔离级别下,读取操作会加共享锁,允许并发读取,但不允许并发更新或删除。
乐观锁是一种并发控制机制,它不同于传统的加锁机制,它假设并发修改的概率非常小,因此在读取时不进行加锁,而是在提交修改时进行验证。
如果其他事务已经修改了相应的数据,则当前事务修改会失败。
乐观锁通常用于对小规模数据进行修改,因为乐观锁在并发修改较为频繁的情况下可能会导致性能下降。
在RR和RC隔离级别下都可以使用乐观锁,但要求应用程序需要具备相关的检测和冲突处理能力。
CAS(Compare and Swap)机制
乐观锁 在RR和RC模式下的使用
以一个模拟银行转账的例子来说明 乐观锁 在RR和RC模式下的使用。
假设有两个账户,账户A和账户B,每个账户有500元。
现在同时有两个人操作转账,一个人从A账户向B账户转账100元,另一个人从B账户向A账户转账200元。
在这种并发情况下,如果使用传统的加锁机制,转账操作之间必须相互等待,降低了并发性能。
但是,使用乐观锁机制可以允许并发执行转账操作,并在提交时进行检查。
在RR隔离级别下,每个转账操作都会创建一个事务,读取和修改操作都会加锁。
在进行转账操作时,首先要读取账户A和B的余额,并计算转账后的余额。
然后,再次读取账户A和B的余额,并与之前读取的余额进行比较。
如果不同,则说明其他的事务已经修改过余额,转账操作失败;
否则,更新账户余额并提交事务。
伪代码如下:
START TRANSACTION;
SELECT balance FROM account WHERE id = A FOR UPDATE; -- 加锁读取账户A的余额
SELECT balance FROM account WHERE id = B FOR UPDATE; -- 加锁读取账户B的余额
SET new_balance_of_A = balance_of_A - 100; -- 转账操作,计算A的新余额
SET new_balance_of_B = balance_of_B + 100; -- 转账操作,计算B的新余额
SELECT balance FROM account WHERE id = A FOR UPDATE; -- 再次加锁读取账户A的余额
SELECT balance FROM account WHERE id = B FOR UPDATE; -- 再次加锁读取账户B的余额
IF balance_of_A = new_balance_of_A AND balance_of_B = new_balance_of_B THEN -- 比较新旧余额
UPDATE account SET balance = new_balance_of_A WHERE id = A; -- 更新账户A余额
UPDATE account SET balance = new_balance_of_B WHERE id = B; -- 更新账户B余额
COMMIT;
ELSE
ROLLBACK; -- 其他事务已经修改过余额,转账操作失败
END IF;
END TRANSACTION;
在RC隔离级别下,读取操作不会加锁,需要注意其他已提交事务对数据做的修改对当前事务是可见的。
在进行转账操作时,需要首先读取账户A和B的余额,并计算转账后的余额。然后,更新账户余额并提交事务。
如果其他已提交事务对数据做的修改与之相冲突,则当前事务提交时会失败。
伪代码如下:
START TRANSACTION;
SELECT balance FROM account WHERE id = A; -- 读取账户A的余额
SELECT balance FROM account WHERE id = B; -- 读取账户B的余额
SET new_balance_of_A = balance_of_A - 100; -- 转账操作,计算A的新余额
SET new_balance_of_B = balance_of_B + 100; -- 转账操作,计算B的新余额
UPDATE account SET balance = new_balance_of_A WHERE id = A AND balance = balance_of_A; -- 更新账户A余额
IF ROW_COUNT() = 1 THEN -- 如果更新成功,则说明没有冲突,更新B账户余额
UPDATE account SET balance = new_balance_of_B WHERE id = B AND balance = balance_of_B;
IF ROW_COUNT() = 1 THEN -- 如果更新成功,则提交事务
COMMIT;
ELSE
ROLLBACK; -- 在更新B账户时发现冲突,回滚事务
END IF;
ELSE
ROLLBACK; -- 在更新A账户时发现冲突,回滚事务
END IF;
END TRANSACTION;
需要注意的是,在以上实例中,乐观锁都需要应用程序进行协调和处理,因此乐观锁需要更多的开发工作量和维护成本。
乐观锁在RC(Read Committed)隔离级别下可以使用CAS(Compare and Swap)机制实现。
CAS是一种无锁机制,可以在不加锁的情况下进行原子操作,如果操作成功则将修改提交,否则重试。
在使用CAS实现乐观锁时,需要注意以下几点:
需要对读写操作进行分离,分别处理读和写两种情况;
在进行写操作时,需要首先读取数据,并将版本号(或时间戳等)一起保存起来,在提交修改时检查版本号是否仍然相同,如果不同则说明其他事务已经修改过数据,当前操作失败,需要重试;
在进行读操作时,也需要读取版本号,并根据版本号是否发生变化决定是否重新读取数据。
以下是一个模拟银行转账乐观锁在RC模式下的实现示例:
1. 开启一个事务,先读取账户A和账户B的余额和版本号;
2. 计算转账后A和B的余额,并更新版本号;
3. 使用compare and swap机制将新的余额和版本号提交到数据库;
4. 如果提交成功,则提交事务;否则,回滚事务;
伪代码如下(以Java为例):
int retryCount = 0;
while (retryCount < maxRetryCount) {
try {
// 开启事务
conn.setAutoCommit(false);
// 先读取账户A和B的余额和版本号
int balanceA = getBalance(conn, "A");
int balanceB = getBalance(conn, "B");
int versionA = getVersion(conn, "A");
int versionB = getVersion(conn, "B");
// 计算转账后A和B的余额,并更新版本号
int newBalanceA = balanceA - 100;
int newBalanceB = balanceB + 100;
int newVersionA = versionA + 1;
int newVersionB = versionB + 1;
// 提交修改
if (compareAndSwap(conn, "A", balanceA, newBalanceA, versionA, newVersionA) &&
compareAndSwap(conn, "B", balanceB, newBalanceB, versionB, newVersionB)) {
conn.commit();
return;
} else {
// 版本号不一致,重试
conn.rollback();
retryCount++;
}
} catch (SQLException e) {
conn.rollback();
throw new RuntimeException("Transaction failed.", e);
}
}
throw new RuntimeException("Transaction retries exceeded.");
其中getBalance和getVersion方法用于读取账户余额和版本号,compareAndSwap方法用于提交修改。需要注意的是,以上示例中的版本号只是一种实现方式,也可以使用时间戳等来实现。
- 乐观锁在
RR(Repeatable Read)隔离级别下可以使用版本号或时间戳等机制实现。
RR模式下,事务读取的数据一旦被另一个事务修改,就会抛出异常,因此,乐观锁无法像在RC模式下那样采用CAS机制实现。
retry_count = 0
max_retry_count = 3
while retry_count < max_retry_count:
try:
# 开启事务
conn.begin()
# 读取账户A和B的余额和版本号
balance_a, version_a = get_balance_and_version(conn, "A")
balance_b, version_b = get_balance_and_version(conn, "B")
# 计算转账后A和B的余额,并更新版本号
new_balance_a, new_balance_b = balance_a - 100, balance_b + 100
new_version_a, new_version_b = version_a + 1, version_b + 1
# 提交修改
update_balance_and_version(conn, "A", new_balance_a, new_version_a)
update_balance_and_version(conn, "B", new_balance_b, new_version_b)
conn.commit()
break
except Exception as e:
conn.rollback()
retry_count += 1
if retry_count >= max_retry_count:
raise Exception("Transaction failed after %d retries" % max_retry_count)
else:
continue
其中get_balance_and_version方法用于读取账户余额和版本号,update_balance_and_version方法用于提交修改,整个过程在一个while循环中,直到提交成功或超过最大重试次数。
Mysql 读写分离
前提条件: (数据的读写比例不均衡)
Mysql 读写分离: 不对数据分片,而是使用 多个具有相同数据的 MySQL 实例来分担大量的查询请求,这种方法通常称为“读写分离”
主库负责执行应用程序发来的所有数据更新请求,然后异步将数据变更实时同步到所有的从库中去,
主库和所有从库中的数据是完全一样的。
利用读写分离可以提升 MySQL 并发
读写分离实现:
-
1 部署一主多从多个 MySQL 实例,并让它们之间保持数据实时同步。
-
2 分离应用程序对数据库的读写请求,分别发送给从库和主库
分离应用程序的 读写请求方法 有下面这三种: 纯手工方式: 修改应用程序的 DAO 层代码,定义读写两个数据源,指定每一个数据库请求的数据源。 组件方式(推荐): 也可以使用像 Sharding-JDBC 这种集成在应用中的第三方组件来实现,这些组件集成在你的应用程序内, 代理应用程序的所有数据库请求,自动把请求路由到对应数据库实例上。 代理方式: 在应用程序和数据库实例之间部署一组数据库代理实例,比如说 Atlas 或者 MaxScale
读写分离的数据基础:
无论是复制还是备份恢复,依赖的都是全量备份和 Binlog
快照 + 操作日志
全量备份 相当于备份那一时刻的一个数据快照
Binlog 则记录了每次数据更新的变化,也就是操作日志
解决Mysql 海量数据查找
存储系统性能问题,其实就是查找快慢的问题
-
查找的时间复杂度
1.1 查找算法 1.2 存储数据的数据结构 一般都由数据库完成 比如 Mysql InnoDB , 存储结构 B+ 树 查找算法 树的查找 时间复杂度 O(logN) -
数据总量
所以一般情况下, 我们可以优化的,就是 数据总量
解决海量数据导致存储系统慢问题的思想:
分片(Shard) 把大数据拆分成 N 个小块
拆开之后,每个分片里的数据就没那么多了,然后让查找尽量落在某一个分片上,
来提升查找性能
-
分片的方案
1) 首选的方案是归档历史订单 (Sharding Key – 订单完成时间) – 按时间分表
2) 分库分表
数据量大,就分表 (查询慢的问题) (分表的依据,取决于业务如何访问数据,让查询尽量落到一个分片中) 并发高,就分库 (应对高并发)分库分表的缺点: 会极大地限制数据库的查询能力,之前很简单的查询,分库分表之后,可能就没法实现
分库分表一定是,数据量和并发大到所有招数都不好使了,我们才拿出来的最后一招
-
分表示例
MySQL大量数据的存储,可以采用分表的方式进行管理。
常见的分表方式包括按照时间、地区、业务类型等分类进行分表,下面是按照时间进行分表的示例:
创建原始表
我们首先创建一个原始表,用于存储数据。例如,我们创建一个表student记录学生的信息:
CREATE TABLE student (
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT(3) NOT NULL,
grade INT(3) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
创建分表
接下来,我们按照日期进行分表,例如将2019年的数据存储在student_2019表中,2018年的数据存储在student_2018表中,以此类推。我们可以通过在表名中添加日期后缀的方式进行分表,例如:
CREATE TABLE student_2019 (
id INT(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
age INT(3) NOT NULL,
grade INT(3) NOT NULL,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
数据迁移
我们将原始表中2019年的数据迁移到student_2019表中。可以通过insert into select的方式实现:
INSERT INTO student_2019(name, age, grade, create_time)
SELECT name, age, grade, create_time
FROM student
WHERE create_time >= '2019-01-01 00:00:00' AND create_time <= '2019-12-31 23:59:59';
查询数据
我们可以通过UNION ALL的方式查询所有分表中的数据:
SELECT * FROM student_2019
UNION ALL
SELECT * FROM student_2018
...
以上是按照时间进行分表的示例,其他分表方式类似。需要注意的是,在分表时要考虑到表的大小、索引等因素对查询性能的影响,并选择合适的分表方式。
- 分库示例
MySQL分库是一种将数据按照一定的规则拆分到不同的数据库中来进行存储的方式,以提高数据库的扩展性和性能。
下面以按照用户ID拆分为例,介绍如何进行MySQL分库。
创建多个数据库
根据需求和拆分规则,创建多个相同结构的数据库,每个数据库对应一部分数据。
例如,可以创建user_db_1、user_db_2、user_db_3等多个数据库。
配置分片规则
需要在应用服务中配置分片规则,以决定将数据存储到哪个数据库中。
例如,可以根据用户ID的取模结果来选择数据存储到哪个数据库中。
编写分库程序
根据分片规则,编写程序来实现数据的插入、查询、更新和删除操作。
程序需要根据分片规则来选择对应的数据库进行操作。
示例代码:
配置分片规则:
public class UserDao {
private static final int DB_NUM = 3;
private static final String URL_PATTERN = "jdbc:mysql://localhost:3306/user_db_%d";
public Connection getConnection(long userId) throws SQLException {
int dbIndex = (int) (userId % DB_NUM) + 1;
String url = String.format(URL_PATTERN, dbIndex);
return DriverManager.getConnection(url, "user", "password");
}
}
对于每个数据库,需要创建相同的user表,用于存储用户信息。
被拆分后的用户数据会直接存储在相应的数据库中,用户操作则需要访问多个数据库来完成。
需要注意的是,分库会导致数据复杂度增加,需要额外的开发和运维成本来维护。
而且,分库也不是适用于所有场景,选择是否采用分库需要根据具体需求进行综合考虑。
MySQL的分片(Sharding)、分表和分库
MySQL的分片(Sharding)、分表和分库是一些常见的数据库分割技术,它们的目的都是为了提高MySQL数据库的性能和可扩展性。 虽然它们的概念类似,但各有不同的应用场景和策略。
分片
分片是将一张大表的数据水平分散存储到多个数据库或服务器(节点)上的技术。
通过分片,可以将一个单一查询的负载平均分摊到不同的数据库或服务器上,从而提高数据库的并发能力和可用性。
分表
分表是将一个大表按照某种规则拆分成多个小表进行存储,可以提高单张表的查询效率和写入性能。
例如按照时间、地区、业务类型等分类进行分表。分表有以下几种方式:
按照时间分表,将数据按照时间戳或日期拆分成多个表,例如按年份、月份进行分表;
按照地区分表,将数据按照地理位置拆分成多个表,例如按照国家、省份、城市进行分表;
按照业务类型分表,将数据按照业务类型拆分成多个表,例如按订单、用户、商品进行分表。
分库
分库是将大量数据按照一定规则拆分到不同的物理数据库中的技术。
每个数据库拥有自己的独立用户、表和索引,相互之间没有数据交集。
通过分库,可以将大型数据库的数据拆分到多个物理服务器上,实现负载均衡和性能提升。
综上所述,分片、分表和分库旨在提高MySQL数据库的性能和扩展性,但它们的应用场景和策略不同,需要根据具体情况进行选择和实现。可以采用单独的分片、分表和分库技术,也可以联合使用它们来优化MySQL数据库的性能。
总的来说,分表和分片都是数据拆分的方式,但是分表是在单个数据库中进行,而分片是将数据库分散存储在多个节点上。选择哪种方式,需要根据具体情况进行考虑。
跨多系统的多份数据实时同步
原始数据 ==> Canal ==> MQ ==> 业务A
==> 业务B
==> 业务C
索引优化
索引是MySQL数据库中的一种优化技术,可以提高查询效率和数据检索的速度。
索引的作用是为了加速查询操作,也就是说在MySQL查询过程中,如果使用了索引,那么查询时只需要在索引中查找,不必扫描全表,可以大幅度提高查询效率。
MySQL索引优化的方式有以下几种:
联合索引:将多列列值合成一个索引,可以最大化地利用索引的优势进行查询,提高查询效率。如:
CREATE INDEX idx_name_age ON student(name, age);
前缀索引:对于较长的字符串列,可以只取其一部分作为索引而不是全部,可以缩小索引的大小,提高查询效率。如:
CREATE INDEX idx_prefix_name ON student(name(20));
压缩索引:对于含有很多重复值的列,可以使用压缩索引来减少索引的大小,提高查询效率。如:
CREATE INDEX idx_name ON student(name) WITH PARSER my_compress_parser;
列表分区(Partitioning):将表按照某一列的值分成多个分区,可以将数据分散存储,降低查询成本,提高查询效率。例如按年份分区:
CREATE TABLE school(
id INT,
name VARCHAR(50),
year INT,
PRIMARY KEY(id, year)
)
PARTITION BY RANGE(year)(
PARTITION p0 VALUES LESS THAN(2010),
PARTITION p1 VALUES LESS THAN(2020),
PARTITION p2 VALUES LESS THAN(2030)
);
索引覆盖:尽可能让查询语句通过索引完成,避免全表扫描,以提高查询效率。如:
SELECT id, name FROM student WHERE age = 18;
此时,如果age列上已经建有索引,则查询语句可以直接通过索引获得数据,不必扫描整个表。
- mysql 如何创建
压缩索引
MySQL支持多种类型的索引,其中包括压缩索引。
压缩索引可以减小索引文件的大小,加速查询并减少磁盘I/O操作。
创建MySQL压缩索引的方法如下:
索引压缩是 MySQL 5.6 引入的一个日志结构改进,通过使用空间压缩减少 InnoDB 存储索引的磁盘空间,从而提高性能。
以下是 MySQL 5.6 中使用索引压缩的示例:
创建一个表并在其上创建一个索引:
CREATE TABLE mytable (
id INT PRIMARY KEY,
name VARCHAR(50),
age INT,
address VARCHAR(100)
) ENGINE=InnoDB;
CREATE INDEX idx_age ON mytable(age);
插入大量的数据以便进行测试:
INSERT INTO mytable (id, name, age, address)
VALUES (1, 'Tom', 25, 'Shanghai'), (2, 'Jerry', 30, 'Beijing'),
(3, 'Mike', 25, 'New York'), (4, 'Peter', 20, 'Sydney'),
(5, 'Mary', 35, 'New York'), (6, 'John', 40, 'Singapore'),
(7, 'David', 23, 'Tokyo'), (8, 'Sara', 28, 'Paris');
...
检查索引占用的磁盘空间:
SELECT table_name, index_name, ROUND(index_length/1024/1024,2) AS index_size
FROM information_schema.TABLES WHERE table_schema = 'mydatabase'
AND table_name = 'mytable' AND index_name = 'idx_age';
输出结果可能类似于:
+------------+-----------+-----------+
| table_name | index_name | index_size |
+------------+-----------+-----------+
| mytable | idx_age | 0.02 |
+------------+-----------+-----------+
可以看到,这个索引占用了 0.02 MB 的磁盘空间。
在 create table 或 alter table 语句中使用 row_format=compressed 选项开启索引压缩。例如:
CREATE TABLE mytable_compressed (
id INT PRIMARY KEY,
name VARCHAR(50),
age INT,
address VARCHAR(100),
INDEX idx_age(age)
) ENGINE=InnoDB ROW_FORMAT=COMPRESSED;
将数据从原始表导入到压缩表:
INSERT INTO mytable_compressed SELECT * FROM mytable;
检查压缩表索引占用的磁盘空间:
SELECT table_name, index_name, ROUND(index_length/1024/1024,2) AS index_size
FROM information_schema.TABLES WHERE table_schema = 'mydatabase'
AND table_name = 'mytable_compressed' AND index_name = 'idx_age';
输出结果可能类似于:
+---------------------+-----------+-----------+
| table_name | index_name | index_size |
+---------------------+------------+-----------+
| mytable_compressed | idx_age | 0.01 |
+---------------------+-----------+-----------+
可以看到,压缩的索引占用的磁盘空间仅为原始索引的一半左右,这将大大降低存储和 IO 成本,提高查询性能。的实际情况进行测试和调整。
MySQL 实例、节点和库
在MySQL数据库架构中,实例、节点和库是有紧密关系的。下面我们分别介绍一下它们之间的关系。
- 实例
在MySQL中,一个实例指的是一个运行于某个主机上的MySQL服务进程。
在一个实例中,可以包含多个数据库,每个数据库又可以包含多个数据表。
实例由配置文件和数据文件组成,每个实例有自己独立的端口号和配置参数。
多个实例之间是相互独立的,不能共享数据和配置参数。
- 节点
节点是指MySQL数据库复制机制中的概念,它通常指的是主节点和从节点。
主节点负责接收客户端请求并进行写操作,而从节点则接收主节点的变更日志,并进行同步复制,提供读操作的支持。
节点之间通过二进制日志文件进行数据同步和复制,保证节点之间数据的一致性。
- 库
库是指MySQL中的一个数据库,它可以包含多个表和存储过程等。
库的概念是用来区分不同的业务模块或者数据的一部分,以便更好的管理数据。
在MySQL实例中,可以通过创建多个库来分离不同业务模块或者数据,从而提高管理和维护效率。
综上所述,MySQL的实例是指运行于某个主机上的一个MySQL服务进程,节点是指数据库复制机制中的概念,用于实现数据的同步和复制,库是指MySQL中的一个数据库,用于区分不同业务模块或数据。它们之间的关系是:一个实例可以包含多个库和多个节点,每个节点可以是一个主节点或从节点,相互之间通过复制机制同步数据,保证数据的一致性。
MySQL 的 EXPLAIN type, ref 结果
在 EXPLAIN 结果中,type 表示 MySQL 使用了哪种类型的访问方式来处理查询。
下面是 type 类型的种类以及举例说明:
system system 类型只有一行数据,是const类型的特例。当查询的表只有一行记录时,使用 system 类型。例如:
EXPLAIN SELECT * FROM users WHERE id=1;
const const 类型是最优秀的类型,常用的场景是将常量与主键进行比较。例如:
EXPLAIN SELECT * FROM users WHERE id=1;
eq_ref eq_ref 类型表示使用了连接查询时,被连接的每个表至多只有一条匹配记录,也称为等值连接。例如:
EXPLAIN SELECT orders.*, customers.* FROM orders JOIN customers ON orders.customer_id = customers.id WHERE orders.id = 100;
ref ref 类型表示使用了非唯一性索引或者唯一性索引的前缀部分来扫描某个范围的行,找到匹配的行记录,例如:
EXPLAIN SELECT * FROM users WHERE age > 30;
range range 类型表示使用索引扫描时指定了范围查找。例如:
EXPLAIN SELECT * FROM users WHERE age BETWEEN 30 AND 40;
index index 类型表示从全表扫描中选择了使用了索引来查询。例如:
EXPLAIN SELECT * FROM users WHERE last_name = 'Smith';
ALL ALL 类型表示全表扫描,没有使用索引。例如:
EXPLAIN SELECT * FROM users;
需要注意的是,尽量避免使用 type 类型为 ALL 的查询,因为这种方式对于大量数据的表来说开销很大,对性能影响最大。 优化 SQL 查询语句以及增加索引都是优化查询时比较有效的方式。
在 explain 语句中,ref 是指用于连接表的索引列。ref 的种类可以分为以下几种:
const:表示使用常量值与索引列的匹配,通常出现在对主键或唯一索引的查询中。
例如,对于以下查询:
EXPLAIN SELECT * FROM mytable WHERE id = 1;
如果 id 是一个主键或唯一索引列,则 ref 的值为 const。
eq_ref:表示使用相等的连接,通常出现在使用联合索引或者多列主键时,其中一个索引列只有一条记录与之匹配的查询中。
例如,对于以下查询:
EXPLAIN SELECT * FROM mytable WHERE col1 = 1 AND col2 = 'abc';
如果 (col1, col2) 是联合索引或者是多列主键,则 ref 的值为 eq_ref。
ref:表示使用非唯一性索引或者唯一性索引的非唯一性前缀与索引列相匹配。
例如,对于以下查询:
EXPLAIN SELECT * FROM mytable WHERE col1 = 1;
如果 col1 是一个非唯一性索引列,则 ref 的值为 ref。
range:表示使用索引进行范围扫描,例如使用类似于 BETWEEN 或者 >、<、<=、>= 的操作符查询非唯一性索引。
例如,对于以下查询:
EXPLAIN SELECT * FROM mytable WHERE col1 > 1 AND col1 < 10;
如果 col1 是一个非唯一性索引列,则 ref 的值为 range。
index:表示查询将扫描整个索引,常常出现在使用 LIKE 操作符进行模糊查询时。
例如,对于以下查询:
EXPLAIN SELECT * FROM mytable WHERE col1 LIKE 'abc%';
如果 col1 是一个非唯一性索引列,则 ref 的值为 index。
NULL:表示没有被使用。
例如,对于以下查询:
EXPLAIN SELECT * FROM mytable WHERE col1 = col2;
如果没有任何索引列被使用,则 ref 的值为 NULL。
ref 的种类反映了查询中连接表的索引列的使用情况。在优化查询时,需要仔细观察 explain 的输出结果,根据 ref 的种类进行相应的索引优化。
100GB的数据量 mysql 表 如何高效的删除字段 增加字段 以及删除表
删除字段:
步骤一:使用 ALTER TABLE 命令,删除字段。例如,我们要删除表中的 column_to_delete 字段:
ALTER TABLE table_name DROP COLUMN column_to_delete;
步骤二:如果表中的数据量很大,可以使用 pt-online-schema-change 工具来删除字段,这将减少表的 downtime。以下是使用 pt-online-schema-change 实现删除字段的示例命令:
pt-online-schema-change --alter "DROP COLUMN column_to_delete" D=database_name,t=table_name
增加字段:
步骤一:使用 ALTER TABLE 命令,添加字段。例如,我们要在表中添加名为 new_column 的字段:
ALTER TABLE table_name ADD COLUMN new_column DATA_TYPE;
注意:DATA_TYPE 是指添加字段的数据类型,可以是 INT、VARCHAR、DATETIME 等。
步骤二:如果表中的数据量很大,可以使用 pt-online-schema-change 工具来添加字段,这将减少表的 downtime。
pt-online-schema-change --alter "ADD COLUMN new_column DATA_TYPE" D=database_name,t=table_name
删除表:
步骤一:使用 DROP TABLE 命令,删除表。例如,我们要删除名为 table_name 的表:
DROP TABLE table_name;
步骤二:如果需要备份数据,可以使用 mysqldump 工具将数据导出到文件中:
mysqldump -u USERNAME -p DATABASE_NAME TABLE_NAME > backup.sql
步骤三:确认该操作无误之后,才能执行删除操作,因为这个操作不可撤销。下面是使用 pt-online-schema-change 工具删除表的示例命令:
pt-online-schema-change --alter "DROP TABLE table_name" D=database_name,t=table_name
注意:--alter 参数可以删除字段或删除表。
在 MySQL 中,可以对不同类型的字段进行索引,包括整型、字符串、日期等等。不同字段类型做索引的效率和优缺点如下:
整型 - 整型类型是最常用的用于索引的类型,可以通过 B-tree 索引快速定位数据。
查询时可以充分利用整型的自然排序,能够快速定位数据,并进行排序。
整型类型的缺点是,对于长整型的数据会占据更多的存储空间,尤其是在需要建立联合索引的情况下。
字符串 - 需要注意的是,字符串类型的字段做索引,效率相对较低。
因为字符串类型的数据是按照 ASCII 值排序的,所以要对字符串类型的索引进行正确的排序。
对于长字符串类型的数据,建立索引会占据更多的存储空间。此外,字符串类型的索引使用 LIKE 等操作的效率也相对较慢。
日期 - 对于日期类型的字段,可以使用 B-tree 索引来加速查询。
由于日期类型的数据是按照自然排序的,可以利用这个特性来加速查询。
此外,MySQL 还提供了一些特殊的日期函数,如 DATE_FORMAT、DATE_ADD 等,可以方便地对日期类型的数据进行处理。
综上所述,整型类型是最高效的索引类型,可用于对数据进行快速的定位和排序。字符串类型相对较慢,但在需要搜索特定字符的时候,非常有用。日期类型的字段也可以加速查询,并方便地进行基于日期的操作。
BTree 中不同节点储存的数据格式
CREATE TABLE `demo` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) NOT NULL,
`b` int(11) NOT NULL,
`c` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `demo_a_IDX` (`a`,`b`,`c`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='测试表';
表数据:
id a b c
1 1 2 test1
2 1 3 test2
3 2 3 test3
4 2 2 test4
5 3 3 test5
B+ 树是 MySQL 常用的索引结构之一,它的节点类型有叶子节点和内部节点。下面是不同节点所存储的数据示例:
叶子节点
叶子节点主要存储索引键的值和行指针,如果创建的是聚簇索引的话,行指针就是整张表的行数据,否则就是对应索引键的值除外。
那么叶子节点的存储格式如下:
a b c id
1 2 test1 1
1 3 test2 2
2 3 test3 3
2 2 test4 4
3 3 test5 5
叶子节点存储了索引列 name 对应的所有值,以及对应行数据的行指针 id。
内部节点
内部节点存储的是索引键值以及指向下一级的子节点的指针。
通常情况下,内部节点比叶子节点更大,因为它存储了子节点的指针。
那么内部节点的存储格式如下:
a b c sub_node_ptr
1 2 test1 子节点指针
2 3 test3 子节点指针
3 3 test5
a b c
1 2 test1
1 3 test2
2 3 test3
2 2 test4
3 3 test5
在 demo_a_IDX 索引的 B+ 树中
第一个内部节点存储的是 a 的分割值,指向 a = 1 的子节点
第二个内部节点存储的是 b 的分割值,指向 b = 3、c = 'test3' 的叶子节点
最后一个节点是一个叶子节点,没有子节点指针。
内部节点存储了索引列 name 和 age 对应的所有值,以及指向下一级的子节点的指针sub_node_ptr。
综上所述,B+ 树节点中存储的数据格式是可以根据不同的数据结构和字段类型而变化的,但是其本质都是存储索引列的值和相应的指针。
Mysql 联合索引以及索引命中
CREATE TABLE `demo` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) NOT NULL,
`b` int(11) NOT NULL,
`c` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `demo_a_IDX` (`a`,`b`,`c`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='测试表';
联合索引
demo_a_IDX的树结构和存储结构如下:
树结构:联合索引 demo_a_IDX 对应的是一个 B+ 树,其中树的 根节点 存储空索引(或者称为“哨兵节点”),所有非空节点都是内节点或叶子节点。
对于 内节点,每个节点中会存储若干个索引键值和对应的子节点指针,用于 快速定位到目标节点。
对于 叶子节点,每个节点中会存储若干个索引键值和对应的数据行指针,用于 定位和访问数据行。
存储结构:由于使用了 InnoDB 引擎,并且使用了 BTREE 索引类型,因此存储结构是按照 索引键值的排序规则,将对应数据行的主键值存储在一个有序结构中。
具体来说,联合索引 demo_a_IDX 的存储结构如下:
索引中的每个节点 都会存储节点对应的 最左边索引键值 和 最右边索引键值 以及其对应的 子节点或数据行指针。
如果索引中包含多个字段,那么按照顺序考虑每个字段的值来排序。
即按 a 字段的值排序,如果多个行的 a 字段的值相同,则按照 b 字段的值排序,如果多个行的 a 字段和 b 字段的值都相同,则按照 c 字段的值排序。
索引中存储的是对应数据行的主键值,而不是实际的数据行。这样可以加快访问速度,并且可以占用更少的磁盘空间。
举个例子,假设有如下一些数据:
id a b c
1 1 10 ‘foo’
2 1 20 ‘bar’
3 2 10 ‘baz’
4 3 10 ‘qux’
5 3 30 ‘quux’
那么索引 demo_a_IDX 精简的存储结构如下:
a b c 主键 id
1 10 ‘foo’ 1
1 20 ‘bar’ 2
2 10 ‘baz’ 3
3 10 ‘qux’ 4
3 30 ‘quux’ 5
从上表可以看出,联合索引 demo_a_IDX 的存储结构中,按照索引键值排序,将对应数据行的主键值存储在一起。 这样当使用联合索引进行查询时,MySQL 可以快速定位到对应主键值的位置,并通过主键索引或者回表操作获取到对应数据行的详细内容。
当某个查询涉及到 a、b、c 字段的时候,以下情况会命中
联合索引 demo_a_IDX:
- 查询条件
包含联合索引中索引的全部字段,例如下面的查询:
SELECT * FROM demo WHERE a = 1 AND b = 2 AND c = 'hello';
这个查询条件正好包含了联合索引中的所有字段,因此可以直接利用索引定位行。
- 查询条件
包含联合索引中的左前缀,例如下面的查询:
SELECT * FROM demo WHERE a = 1 AND b = 2;
-- 联合索引的左前缀即包含了索引的左边(从左往右)的一些字段,这些字段可以定位到较少数量的行。
-- 因此,这个查询中可以利用到联合索引,查询引擎会按照左前缀定位到符合条件的行,然后再判断是否满足其他条件。
- 查询条件
包含联合索引的左递归子集,例如下面的查询:
SELECT * FROM demo WHERE a = 1 AND b = 2 AND c LIKE 'hell%';
-- 联合索引中的左递归子集 是指连续的、从左往右的若干字段组成的集合,这些字段可以定位到明确的行区间。
-- 这个查询中,a=1、b=2 可以定位到一个明确的行区间,然后再根据 c LIKE 'hell%' 的过滤条件过滤掉不符合要求的行。
以下情况不会命中索引:
- 查询条件中的字段
不在索引的左前缀或者左递归子集中,例如下面的查询:
SELECT * FROM demo WHERE b = 2 AND c = 'hello';
-- 这个查询条件中包含了 b、c 两个字段,但 a 字段不在其中,因此这个查询不能利用 demo_a_IDX 索引,而需要进行全表扫描(或者利用其他适合的索引)。
- 查询条件中
包含了联合索引的左前缀,但是没有包含索引的所有列,例如下面的查询:
SELECT * FROM demo WHERE a = 1 AND b = 2 AND c LIKE '%world%';
-- 联合索引的左前缀可以定位到一个行区间,但由于查询条件中包含了 c LIKE '%world%',这个子句使用了通配符 %,
-- 因此不能利用索引快速定位到符合条件的行,需要进行全表扫描 (或者利用其他适合的索引)。
注意:在使用 LIKE 子句时,最好使用 like 'prefix%' 形式,这样可以利用索引的左递归子集;
同时不建议在 LIKE 子句中使用通配符 % 作为搜索模式的开头,这会导致索引失效,进而需要进行全表扫描。
B-tree 和 B+ tree
B-tree 和 B+ tree 都是常用于数据库索引加速的数据结构,它们的区别主要体现在以下方面:
-
叶子节点是否存储数据B-tree 中,每个节点都可以存储数据,包括叶子节点和非叶子节点, B+ tree 只在叶子节点存储数据,而非叶子节点仅用于索引。 -
叶子节点是否有指针B-tree 中,叶子节点和非叶子节点都有指向下一个节点的指针 B+ tree 中,非叶子节点只有指向子节点的指针,而叶子节点则通过链表相连。 -
内部节点的存储方式B-tree 中,节点的所有键值都在节点中 B+ tree 中,内部节点只存储键值,不存储数据。 -
数据查找方式B-tree 中,由于节点既包含键值和数据,因此查找到一个节点时,可以直接获取数据; B+ tree 中,只有叶子节点存储数据,因此必须沿着叶子节点链表依次访问,才能获取全部数据。
综上所述,B+ tree 相较于 B-tree 更适合于存储海量数据的场景
B+ tree 只在叶子节点存储数据,因此在插入、更新和删除节点时,仅需维护叶子节点的链表,
B-tree 中,则需要考虑内部节点和叶子节点的数据更新,因此 B+ tree 更加高效。
MySQL 默认使用 B+ tree 来实现普通索引和唯一索引,但是在某些情况下也可以使用 B- tree 来实现唯一索引。
B- tree 是一种多路平衡搜索树,它比 B+ tree 更适合用于磁盘文件系统之类的非易失性存储媒介。B- tree 的特点是节点中可以存储更多的键值,因此整棵树的高度会更小,磁盘访问次数也会更少。
而且,B- tree 中的所有节点都是等价的,可以存储数据,因此避免了 B+ tree 需要不断调整叶子节点的情况。
针对唯一索引,MySQL 也可以使用 B- tree 实现。使用 B- tree 实现唯一索引的优点在于,对于访问磁盘次数较高的场景,可以通过 B- tree 减少磁盘访问次数,提高查询性能。
但是,使用 B- tree 实现唯一索引存在一些缺点:
B- tree 节点中可以存储数据,因此节点中需要存储更多的信息,导致每个节点大小可能会很大,从而影响内存控制能力,进而影响性能。
在数据插入时,由于 B- tree 的平衡性,每次插入会很有可能导致整棵树都需要调整,从而影响写入性能。
总的来说,B- tree 适用于需要低延迟、高吞吐量的场景。但是,对于如今的大规模数据存储和高并发访问的需求,很多情况下 B+ tree 仍然是性能更好的选择。