常用的设计模式有哪些?
常用的设计模式通常按照创建型、结构型和行为型三大类别来划分,以下是每个类别中的一些常见设计模式:
创建型(Creational Patterns):
- 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供全局访问点。
- 工厂方法模式(Factory Method Pattern):定义一个用于创建对象的接口,让子类决定实例化哪一个类。
- 抽象工厂模式(Abstract Factory Pattern):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
- 建造者模式(Builder Pattern):将复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
- 原型模式(Prototype Pattern):通过复制现有对象来创建新对象,而不是新建。
结构型(Structural Patterns):
- 适配器模式(Adapter Pattern):将一个类的接口转换成客户希望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。
- 桥接模式(Bridge Pattern):将抽象部分与实现部分分离,使它们都可以独立变化。
- 组合模式(Composite Pattern):允许你将对象组合成树形结构来表现“部分-整体”的层次结构,用户对单个对象和组合对象的使用具有一致性。
- 装饰器模式(Decorator Pattern):动态地给对象添加额外职责,同时保持类的封装性和继承性的灵活性。
- 外观模式(Facade Pattern):为子系统中的一组接口提供一个统一的高层接口,简化了外部与其交互的复杂性。
- 享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度的对象。
- 代理模式(Proxy Pattern):为其他对象提供一种代理以控制对这个对象的访问。
行为型(Behavioral Patterns):
- 责任链模式(Chain of Responsibility Pattern):多个对象处理请求,对象间形成一条链,请求会沿着这条链传递直到被处理。
- 命令模式(Command Pattern):将请求封装为一个对象,从而可以用不同的请求、队列或者日志/撤销操作来参数化其他对象。
- 解释器模式(Interpreter Pattern):定义语言的语法或表达式,并提供了解释它的方法。
- 迭代器模式(Iterator Pattern):提供一种方法顺序访问聚合对象的元素,而又不暴露其内部表示。
- 中介者模式(Mediator Pattern):定义一个中介对象来封装系列对象之间的交互关系,降低对象间的耦合度。
- 备忘录模式(Memento Pattern):在不破坏封装的前提下捕获一个对象的内部状态,以便将来恢复它。
- 观察者模式(Observer Pattern):当对象状态改变时通知所有依赖于它的对象。
- 状态模式(State Pattern):允许对象在内部状态改变时改变其行为,对象看起来像是改变了它的类。
- 策略模式(Strategy Pattern):定义了一系列算法,并分别封装起来,让它们可以相互替换,使得算法的变化独立于使用它的客户。
- 模板方法模式(Template Method Pattern):在一个抽象类中定义一个操作中的框架,而将一些步骤延迟到子类中实现。
- 访问者模式(Visitor Pattern):表示一个作用于某对象结构中各元素的操作,它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
以上是23种常见的设计模式的简要概述。每种模式都有特定的应用场景和目的,旨在提高代码的可重用性、可扩展性和可维护性。
Nginx可以做哪些事情?
Nginx 可以在多个层面上为 Web 应用和服务器提供强大的功能支持,以下是 Nginx 主要能够完成的任务:
-
Web 服务器:
- 提供静态内容服务:Nginx 能够高效地处理和分发静态资源文件,如 HTML、CSS、JavaScript、图片等。
- 大流量高并发场景:Nginx 以其非阻塞的 I/O 模型和事件驱动架构闻名,能够处理大量的并发连接而保持低内存占用。
-
反向代理:
- 分发请求:Nginx 可以作为反向代理服务器,将客户端的请求转发到后端的一组应用服务器上,实现负载均衡。
- 内容缓存:Nginx 可以缓存来自上游服务器的响应内容,减少对后端服务器的压力,并提高响应速度。
-
负载均衡:
- 轮询、权重分配、最少连接数、IP哈希等策略进行后端服务器之间的负载均衡,确保系统稳定性和高可用性。
-
SSL/TLS 加密与终止:
- 在 Nginx 层面处理 HTTPS 请求,通过 SSL/TLS 协议加密数据传输,并集中管理证书和加密套件。
-
限流与熔断:
- 使用各种模块限制客户端请求速率(如漏桶或令牌桶算法),防止 DDoS 攻击或其他异常流量导致服务过载。
- 设置熔断机制,当后端服务器压力过大时,暂时拒绝新请求以保护系统稳定性。
-
动静分离:
- 动态内容由后端服务器生成,静态内容由 Nginx 直接提供,这样可以降低后端服务器压力并提高效率。
-
URL重写与路由:
- 根据不同的 URL 规则进行重写,或者根据规则将请求转发到不同位置,实现灵活的路由功能。
-
防盗链:
- 配置 Nginx 来防止未经授权的网站引用你的资源,避免不必要的带宽消耗。
-
HTTP/2 支持:
- Nginx 支持 HTTP/2 协议,提供了多路复用、头部压缩等功能,优化了现代浏览器与服务器间的通信效率。
-
地理位置定位:
- 根据请求者的 IP 地址将流量导向不同的服务器,以实现地理相关的服务部署。
-
日志记录与分析:
- Nginx 可以生成详细的访问日志,便于统计分析用户行为及监控服务器状态。
-
邮件代理:
- Nginx 也具有 IMAP/POP3/SMTP 代理能力,尽管这一特性在实际使用中可能不如其Web服务器功能普遍。
总之,Nginx 是一款高度可配置且性能优异的网络服务器软件,它能在多种场景下发挥重要作用,无论是大型网站的前端代理,还是中小型项目的静态文件服务器,都能提供出色的性能和服务质量。
Nginx有几种负载均衡?
Nginx 提供了多种负载均衡策略,主要包括以下几种:
-
轮询(Round Robin):
- 默认的负载均衡策略,将请求按照顺序依次分配给后端服务器列表中的每个服务器。
-
加权轮询(Weighted Round Robin):
- 在轮询基础上添加权重,允许为后端服务器分配不同的处理请求的比例。权重较高的服务器将会接收到更多的请求。
-
IP哈希(ip_hash):
- 根据客户端 IP 地址进行散列运算来决定将请求转发到哪个后端服务器,确保来自同一客户端 IP 的请求被定向到同一台服务器,这有助于解决会话保持问题。
-
最少连接数(Least Connections):
- 将新的请求分发给当前连接数最少的服务器,以达到更均衡的服务器资源使用率。
-
URL哈希(url_hash 或 hash):
- 按照请求的 URL 进行哈希计算并根据结果选择后端服务器,确保相同的 URL 请求总是发送到同一个服务器,该特性需要第三方模块或特定版本支持。
-
fair(第三方):
- 第三方模块提供的策略,根据后端服务器的响应时间来进行动态调整,优先分配给之前响应时间较短的服务器。不过在 Nginx 核心中并没有直接提供这个功能,可能需要安装额外的模块才能实现。
综上所述,截至我最后更新知识库时,Nginx 自带的核心模块提供了至少四种负载均衡策略,而 fair 策略通常需要通过第三方模块来实现。不过,请注意随着技术的发展,Nginx 可能会增加更多原生支持的负载均衡算法。
Nginx的轮询里一致性哈希算法的底层原理是什么?
Nginx 的轮询(Round Robin)负载均衡策略并不直接使用一致性哈希算法。一致性哈希是一种特殊的哈希算法,用于分布式系统中将数据和请求分配到多个节点时,尽量保持在节点增减时数据分布的稳定性。
在 Nginx 的标准轮询负载均衡机制中,每个请求按照循环顺序被分发给后端服务器列表中的下一个服务器,而不涉及对客户端IP或者请求内容进行哈希计算来决定转发目标。这种简单公平的分配方式并不能解决节点添加或删除时可能导致大量请求重新映射的问题,而这正是通过一致性哈希所要解决的问题。
如果要在 Nginx 中实现类似一致性哈希的功能,通常需要借助第三方模块,例如 nginx-upstream-jhash 或者 ngx_http_consistent_hash 模块。这些模块会根据请求的某些特性(比如客户端 IP 或请求内容的一部分)生成哈希值,并基于此哈希值选择后端服务器,从而在增加或减少后端服务器时,能够更有效地维持已存在的客户端请求到服务器的映射关系,降低重新映射的成本。
Nginx如何避免缓存雪崩
在Nginx中避免缓存雪崩的策略通常与缓存失效时大量请求同时涌向后端数据库的问题相关。以下是几种在Nginx及其配合的缓存系统(如Redis)中可以采取的方法来缓解或避免缓存雪崩:
-
设置随机过期时间:
- 给缓存数据设置一个随机的过期时间,这样可以分散缓存失效的时间点,而不是让所有缓存在同一时刻失效。
-
使用多级缓存架构:
- 在应用层和Nginx之间可以设计多级缓存结构,比如先使用本地缓存(如:Memcached、FastCGI缓存),再用分布式缓存(如:Redis),当一级缓存失效时,还有下一级缓存提供保护。
-
渐进式失效策略:
- 当缓存即将到期时,不是立即删除而是标记为即将失效,并刷新缓存任务放入队列,由后台异步更新,确保新值准备好后再替换旧缓存。
-
备份和高可用性:
- 对于Redis这样的缓存服务器,采用主从复制或者集群模式,以提高缓存服务的可用性,即使某台服务器宕机也不会导致全部缓存失效。
-
限流与熔断:
- 配置Nginx的限流模块(例如limit_req)限制在单位时间内对后端服务器的请求次数,当请求超过阈值时,可以通过返回错误页面或其他降级策略来防止过多流量直接冲击数据库。
-
监控与预警:
- 建立完善的监控系统,及时发现缓存异常情况,一旦发生雪崩迹象,通过自动化运维工具快速响应和恢复。
-
缓存预热:
- 在缓存失效前或服务启动初期,主动将热点数据加载到缓存中,确保重启后短时间内不会有大量数据库查询压力。
结合以上方法,可以在Nginx及相关的缓存体系中构建一套有效的防护机制,降低缓存雪崩造成的影响。不过实际实施时需要根据具体的业务场景和技术栈进行合理调整和配置。
服务器的CUP超负荷了,如何排查是哪个项目的?
服务器CPU超负荷时,排查具体是哪个项目导致的CPU使用率过高,可以遵循以下步骤:
-
实时监控:
- 使用系统自带的工具(如Linux中的
top
、htop
或pidstat
等)实时查看CPU占用情况,并按CPU使用率排序找出消耗CPU资源最多的进程ID(PID)。
- 使用系统自带的工具(如Linux中的
-
定位进程与项目关联:
- 根据获取到的高CPU使用率的进程PID,进一步通过
ps aux | grep PID
命令查看该进程的详细信息,包括命令行参数和所在目录。通常,Java应用可以通过Java进程名或者jar包名称识别出对应的服务或项目。
- 根据获取到的高CPU使用率的进程PID,进一步通过
-
线程级排查:
- 对于Java应用,可以使用
jstack PID > thread_dump.txt
生成堆栈跟踪文件,然后分析文件内容以确定哪个线程在消耗大量CPU资源。 - 或者直接使用Arthas这样的诊断工具,通过
dashboard
、thread
等命令快速定位热点线程和方法。
- 对于Java应用,可以使用
-
日志分析:
- 查看相关项目的日志,尤其是错误日志或警告日志,查找是否有异常操作或循环逻辑导致CPU占用率飙升。
-
性能分析工具:
- 使用诸如VisualVM、JProfiler等Java性能分析工具连接到目标Java进程,通过采样分析、CPU火焰图等方式查看哪些方法调用链路消耗了大量CPU时间。
-
系统及服务配置检查:
- 检查系统层面是否存在定时任务或其他后台服务配置不当,例如:轮询间隔过短、并发处理设置不合理等。
-
业务数据分析:
- 结合业务数据波动情况,判断是否由于某个特定时间段内请求量激增或是特定功能模块被大量调用而引发的问题。
-
集群环境排查:
- 如果是在分布式环境中,需要查看整体负载均衡器的日志以及各个节点的状况,结合微服务架构找到可能压力过大的服务实例。
综合以上步骤,逐步缩小排查范围,最终锁定问题所在的项目或服务
交替打印数字和字母
问题描述
使用两个goroutine交替打印序列,一个goroutine打印数字,另外一
个goroutine打印字母,最终效果如下:
12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324
WX2526YZ2728
解题思路
问题很简单,使用channel来控制打印的进度。使用两个channel,来分别
控制数字和字母的打印序列,数字打印完成后通过channel通知字母打印,
字母打印完成后通知数字打印,然后周而复始的工作。
import ( "fmt" "strings" "sync" ) func main() { letter, number := make(chan bool), make(chan bool) wait := sync.WaitGroup{} go func() { i := 1 for { select { case <-number: fmt.Print(i) i++ fmt.Print(i) i++ letter <- true break default: break } } }() wait.Add(1) go func(wait *sync.WaitGroup) { str := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" i := 0 for { select { case <-letter: if i >= strings.Count(str, "")-1 { wait.Done() return } fmt.Print(str[i : i+1]) i++ if i >= strings.Count(str, "") { i = 0 } fmt.Print(str[i : i+1]) i++ number <- true break default: break } } }(&wait) number <- true wait.Wait() }