用equals
不同,第一次有误差,第二次会舍入。
用HashMap或HashSet去重,用List排序。Java中没有一种数据类型是既去重又排序的,可以用两个数据类型结合处理该问题。
Java 反射是一种机制,允许程序在运行时检查和操作类、接口、字段、方法等程序结构的信息。反射使得程序可以在运行时获取类的成员和方法的信息,创建对象、调用方法等,而不需要在编译时明确地知道这些信息。
Java 反射的原理基于以下几个关键的类和接口:
1.1. Class 类:Java 中的每个类都有一个与之对应的 Class
对象,该对象包含了有关类的结构和方法的信息。通过 Class.forName("ClassName")
可以获取对应类的 Class
对象。
1.2. Constructor、Method、Field 等类:这些类代表了类的构造函数、方法和字段。可以通过 Class
对象的方法来获取特定构造函数、方法和字段的信息。
1.3. java.lang.reflect 包:该包中包含了与反射相关的类和接口,如 Constructor
、Method
、Field
等。
优点:
缺点:
总之,Java 反射是一项强大而灵活的功能,能够在运行时获取和操作类的信息,但在使用时需要权衡其带来的性能开销和安全性问题。在不得不使用反射时,应该尽量减少其使用范围,以确保代码的可维护性和性能。
字符串在创建之后是不可修改的,同内容的字符串在字符串池中只存在一份。
频繁的拼接、截取操作字符串性能差,可以使用StringBuffer或StringBuilder类实现对字符串的高效操作。
使用字符数组:
可以将字符串转换为字符数组,然后通过反向遍历字符数组来构建翻转后的字符串。
可以通过调用 reverse() 方法来翻转 StringBuilder 或 StringBuffer 对象中的内容。
4.1、继承Thread类,重写run方法;new一个线程并调用start。
4.2、实现Runnable接口,重写run方法;new一个runnable对象和线程对象(runnable对象作为参数),调用start。
基本数据类型和对应的包装类之间进行自动转换的语言特性。
6.1、什么是事务?
事务是指对数据库的一系列操作,这些操作要么全部同时成功,要么
全部同时失败。
事务是对数据库操作的基本单位。
事务的特性:原子性,一致性,隔离性,持久性。
6.2、什么是分布式事务?
分布式事务是指在分布式架构当中,由多个微服务的子事务共同组成的事务。
6.3、如何解决分布式事务的问题?
首先要知道分布式系统的基础理论,CAP原理
P是分区容错性,A是可用性,C是一致性。在分布式系统中,三者不可能全部
同时满足。一般P是必须要满足的,所以对于A和C要进行权衡和取舍。例如
Zookeeper强调的是一致性,即满足CP,Eureka强调可用性,即满足AP。
基于此理论进一步还有一个BASE理论,就是说既然无法满足强一致性,那么就
要让分布式系统中的各个节点根据自身特点采用适当的方式达到最终一致性。
BA 基本可用
S 软状态
E 最终一致性
要解决分布式事务的问题,也要基于这样的理论进行架构选型和实现。
比如银行的系统更应该强调一致性,而电商平台应该更多地考虑可用性。
分布式事务的解决方案有:
XA,2PC, 3PC,Seata框架的AT模式,TCC模式,SAGA,可靠消息最终一致性
最大努力通知
6.4、Seate框架的AT模式的原理和实现?
Seata框架的AT模式具体也分两个阶段:第一个阶段,执行各个分支的事务;
第二个阶段,控制全局事务提交或回滚。
过程涉及到的角色有事务的协调者,事务的管理者,资源管理器
协调者负责与各个微服务之间通讯,产生XID,传递事务状态。
管理者负责发起一个全局事务,提交一个全局事务。
资源管理器负责向TC注册分支事务,处理本地分支事务,
上报分支事务状态,接收全局事务指令。
工作过程是这样的:第一阶段由事务的管理者发起全局事务,并向事务协调者申请一个XID,
然后管理者所在的服务A启动RM,XID传过来,执行分支事务,RM把执行的情况上报
给TC,服务A调服务B,并把XID传过去,B的RM执行分支事务,并给TC上报情况,
TC把分支事务的执行情况发送给TM。其他服务如C\D以此类推。
第二阶段,TM根据所有分支的情况决定提交或回滚,并通知TC,TC把指令发送给涉及到的
所有微服务的RM,RM执行分支事务的提交或回滚。
堆又分为伊甸园区、S0/S1、老年代。
java8采用的是分代回收的思想,是一套组合拳。
JVM给对象增加了一个年龄计数器,没经过一次GC仍然存活的对象,年龄+1.
当对象产生时先进入伊甸园区,经过一次GC没有被回收的,会进入S0,再经过一次GC进入S1,
这时采用的回收算法是复制算法。
经过数次(默认15)仍然存活则会进入老年代区。
GC分为minorGC和fullGC,老年代GC采用的是标记整理算法。
有些对象非常大,当大于设置值PretenureSizeThreshold的时候,直接进入老年代,避免复制过程太耗时。
堆又分为伊甸园区、S0/S1、老年代。
元空间保存的有静态变量、常量、类的元信息。
栈是线程私有的,堆和元空间是线程共享的。
栈中保存有程序计数器、本地方法栈、虚拟机栈。
虚拟机栈的栈帧是对应某个方法的。
栈帧中有局部变量表、操作数栈、动态链接和方法出口。
悲观锁:
悲观锁假设在整个数据访问过程中,其他线程会试图修改数据,因此在访问数据之前,会将数据进行加锁,确保其他线程无法修改数据直到当前线程释放锁。悲观锁通常使用数据库的锁机制实现,如数据库的行级锁或表级锁。它可以防止脏读、不可重复读和幻读等并发问题,但会导致较高的竞争和开销。
在 Java 中,使用 synchronized 关键字或 ReentrantLock 类可以实现悲观锁。
乐观锁:
乐观锁假设在数据访问过程中,不会有其他线程修改数据,因此不会在访问数据时加锁。相反,它在更新数据之前,会检查数据是否被其他线程修改过。如果没有修改,就进行更新操作,否则需要处理冲突。乐观锁通常使用版本号或时间戳等机制来检测数据是否被修改过。
在 Java 中,常用的乐观锁实现方式是使用 Atomic 类,如 AtomicInteger、AtomicLong,以及一些乐观锁算法,如 CAS(Compare and Swap)算法。
Java 提供了几种悲观锁和乐观锁的实现方式:
悲观锁:
使用 synchronized 关键字:通过在方法或代码块上加锁,实现线程同步。
使用 ReentrantLock 类:提供了更灵活的锁控制,支持公平锁和非公平锁,以及可中断的锁等特性。
乐观锁:
使用 Atomic 类:如 AtomicInteger、AtomicLong 等,提供了原子操作和乐观锁的机制。
使用版本号或时间戳:在数据中增加一个版本号或时间戳,更新数据时检查版本号或时间戳是否一致,来实现乐观锁。
悲观锁适用于对数据的更新操作较频繁,且并发冲突较多的情况,而乐观锁适用于读操作较多,冲突较少的情况。选择使用哪种锁取决于具体的业务场景和性能需求。
选择合适的垃圾回收器: 根据应用的性能需求和硬件配置,选择合适的垃圾回收器。例如,对于吞吐量优先的场景,可以选择 Parallel GC 或 G1 GC。对于低延迟的场景,可以考虑使用 ZGC 或 Shenandoah GC。
适当调整堆大小: 设置合理的堆大小可以减少 GC 的频率。如果堆过小,会导致频繁的垃圾回收;如果堆过大,会增加 GC 暂停的时间。通过调整 -Xmx(最大堆大小)和 -Xms(初始堆大小)参数来优化堆的大小。
避免过多的对象创建: 减少不必要的对象创建可以降低垃圾回收的压力。尽量使用对象池、缓存等方式复用对象,减少临时对象的产生。
减少长时间存活的对象: 长时间存活的对象容易进入老年代,导致老年代的垃圾回收。通过合理的业务逻辑设计,尽量减少对象的长时间存活。
合理设置 GC 参数: 根据应用的需求,调整 GC 相关的参数。例如,设置合适的新生代和老年代比例,调整垃圾回收的线程数等。
减少 Full GC 的频率: Full GC 通常会引起较长的暂停时间。避免频繁触发 Full GC,可以通过调整参数、避免内存泄漏等方式来实现。
监控和调优: 使用工具如 Java VisualVM、GCEasy 等来监控垃圾回收的情况,分析 GC 日志以找到性能瓶颈,并针对性地调整 JVM 参数。
HashMap的底层数据结构是哈希表,它允许我们使用键值对的形式来存储和获取数据。哈希表中的每个元素由一个键和一个值组成,它们被存储在一个数组中。当发生哈希冲突时,HashMap会将冲突的键值对存储在一个链表中。
HashMap的底层数组的大小是16。当发生哈希冲突时,HashMap会将冲突的键值对存储在一个链表中。JDK7和JDK8版中HashMap实现是大有不同的。JDK7采用的是数组+链表的形式,当链表长度大于8的时候,链表就会转为红黑树,提高了他的效率。JDK8采用的是数组+链表+红黑树的结构,当链表长度大于等于8,并且数组长度大于等于64时,链表才需要转换成成红黑树 。
HashMap的底层数组在以下情况下会扩容:当HashMap中的元素数量超过阈值(threshold)时,就会进行扩容。阈值是由初始容量(initial capacity)和负载因子(load factor)计算出来的。默认情况下,初始容量为16,负载因子为0.75。因此,当HashMap中的元素数量超过16 * 0.75 = 12时,就会进行扩容。扩容时,HashMap会创建一个新的数组,长度为原数组的2倍,并将原数组中的所有元素复制到新数组中 。
红黑树的查询时间复杂度为O(logN),链表为O(N)。
需要注意的是,HashMap
是非线程安全的,如果在多线程环境中使用,可能需要考虑使用 ConcurrentHashMap
或者手动进行同步操作来保证线程安全。
ConcurrentHashMap
是 Java 中的一个并发安全的哈希映射实现,它通过一系列的并发控制技术来保证在多线程环境下的线程安全性。以下是 ConcurrentHashMap
如何保证线程安全的几个主要机制:
a. 分段锁(Segmented Locks):
ConcurrentHashMap
内部使用一组分段锁,将整个哈希表分成多个段(segments)。每个段拥有自己的锁,可以独立地进行并发操作。这种分段锁的设计允许多个线程同时在不同段上进行操作,从而提高并发性能。
b. Hash Entry 数组:
ConcurrentHashMap
内部的数据结构也是数组,每个数组元素是一个 Hash Entry
,表示一个键值对。每个 Hash Entry
都拥有自己的锁。这样,在进行查找、插入和删除等操作时,只需要锁住特定的 Hash Entry
,而不是整个哈希表,从而减小了锁的粒度。
c. CAS(Compare-And-Swap)操作:
ConcurrentHashMap
在并发操作时使用了 CAS 操作来更新数据,而不是使用传统的锁机制。CAS 是一种原子操作,用于比较当前值和期望值,如果相等则进行更新,否则不执行操作。这允许多个线程同时尝试更新数据,而只有一个线程能够成功,其他线程需要重试。
d. 红黑树:
类似于 HashMap
,ConcurrentHashMap
也使用链表和红黑树来处理哈希冲突。当链表中的元素数量达到一定阈值时,会将链表转换为红黑树,以提高查找效率。
总之,ConcurrentHashMap
通过分段锁、Hash Entry 数组、CAS 操作等多种技术来保证在多线程环境下的线程安全性。这使得多个线程可以同时访问和操作映射,而不会发生竞态条件等并发问题。需要注意,虽然 ConcurrentHashMap
提供了较高的并发性能,但在使用时还是需要根据实际需求仔细选择适当的并发容器。
控制反转(Inversion of Control,IoC)、依赖注入(Dependency Injection,DI)和AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架的几个核心概念,用于实现松耦合的设计,提高代码的可维护性和可测试性。
控制反转(IoC):
控制反转指的是将对象的创建和管理从应用程序代码中反转到容器(通常是框架)中。传统上,应用程序代码负责创建和管理对象,而在IoC中,框架负责创建、管理和注入对象,从而反转了控制权。这意味着应用程序不再负责对象的创建和生命周期管理,而是通过配置或注解告诉框架需要哪些对象,框架会在需要时创建和管理这些对象。
依赖注入(DI):
依赖注入是IoC的具体实现方式,它指的是将一个对象的依赖关系从它自己的代码中解耦出来,并通过注入(提供)这些依赖关系。通常,一个对象需要其他对象来完成其功能,而在依赖注入中,这些依赖的对象将被注入到被依赖的对象中,而不是由被依赖的对象自己创建或管理这些依赖。
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,用于分离横切关注点(如日志记录、事务管理、安全性等)与核心业务逻辑。
以下是实现AOP的基本步骤和关键概念:
定义切面(Aspect)、切点(Pointcut)、通知(Advice):
配置AOP: 在Spring中,你可以使用XML配置或基于注解的方式来定义切面、切点、通知以及与目标对象的关联。Spring Boot推荐使用注解方式来配置AOP,避免繁琐的XML配置,使配置更加简洁和方便。
启动Spring容器: 在应用程序启动时,需要启动Spring容器以实例化和管理切面、目标对象等。Spring容器会创建代理对象,将切面逻辑织入到目标对象的方法调用中。
执行AOP: 当目标对象的方法被调用时,代理对象会检查切点表达式,并根据切点匹配的条件决定是否应用切面的逻辑。如果切点匹配,切面的通知会被执行。
下面是示例代码:
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect//@Aspect 注解标识这是一个切面类
@Component
public class LoggingAspect {
//@Before 和 @After 注解表示前置通知和后置通知。
@Before("execution(* com.example.service.*.*(..))")
public void beforeMethodExecution() {
System.out.println("Method is about to be executed.");
}
//切点表达式 execution(* com.example.service.*.*(..)) 指定了切点
@After("execution(* com.example.service.*.*(..))")
public void afterMethodExecution() {
System.out.println("Method has been executed.");
}
}
SpringBoot中可以使用创建一个配置类,内部实例化一个交给Spring管理的Bean,类名是ThreadPoolTaskExecutor 。
ThreadPoolTaskExecutor可以配置线程池的相关参数,如线程数等。
在实际的并发编程中,异步操作可以使用线程池来实现。当一个任务需要以异步方式执行时,可以将该任务提交给线程池,线程池会分配一个线程来执行该任务,从而避免了阻塞主线程或其他任务。
@Async注解告诉Spring该方法应该以异步方式执行。要启用异步支持,需要在启动类添加@EnableAsync注解。
@Transactional注解开启事务。
控制器、处理器映射器、处理器适配器、视图解析器、视图、模型。
Spring内置了一个Tomcat,这是一个Web容器,其底层使用了Servlet,它为Spring提供了线程池。
SpringBoot提供了简单的线程池配置方法,我们可以在yml文件中做出如下配置:
server.tomcat.max-threads=100 # 最大线程数
server.tomcat.min-spare-threads=10 # 最小空闲线程数
server.tomcat.accept-count=200 # 允许的最大连接数
是的,Spring MVC 提供了对 Cookie 和 Session 的支持,使您能够在 Web 应用程序中管理用户会话和状态。默认状态下的Cookie和Session值为null。下面我将简要介绍如何使用 Cookie 和 Session 在 Spring MVC 中进行会话管理。
使用 Cookie:
Cookies 是存储在用户浏览器中的小型文本数据,用于在客户端和服务器之间传递信息。在 Spring MVC 中,您可以使用 javax.servlet.http.Cookie
类来操作和管理 Cookie。
设置和获取 Cookie:
在 Spring MVC 控制器方法中,您可以使用 HttpServletResponse
对象的 addCookie
方法来设置 Cookie。使用 HttpServletRequest
对象的 getCookies
方法来获取客户端发送的所有 Cookie。
使用 Session:
Session 是在服务器端存储用户数据的一种机制,用于跟踪用户会话状态。在 Spring MVC 中,您可以使用 使用 HttpServletRequest
对象的 getSession
方法来获取或创建 HttpSession 对象,用此对象来操作和管理会话。
获取或创建 Session:
在 Spring MVC 控制器方法中,您可以使用 HttpServletRequest
对象的 getSession
方法来获取或创建 HttpSession 对象。
此对象有方法setAttribute和getAttribute可以设置和获取数据。
@Controller返回的是视图,而@RestController是RESTful风格,返回的是实际数据。
RESTful(Representational State Transfer)是一种软件架构风格,用于设计网络应用程序的通信方式。它在分布式系统中提供了一种资源管理和交互的方式,强调资源的状态以及通过 HTTP 方法进行操作。RESTful 风格的设计原则旨在使系统更加简单、可伸缩、可维护和易于理解。
以下是一些 RESTful 风格的关键原则和特征:
基于资源(Resources):在 REST 中,一切都被视为资源,每个资源都有一个唯一的标识符(URI)。资源可以是任何事物,如用户、文章、订单等。URI 用于定位资源,通过 HTTP 方法对资源进行操作。
无状态通信(Stateless Communication):每个请求从客户端到服务器都必须包含足够的信息,以便服务器能够理解并处理请求,而不依赖于之前的请求。服务器不会存储客户端状态,所有必要的信息都包含在请求中。(也就是说提倡不使用Cookie和Session)
使用HTTP方法:RESTful 风格使用标准的 HTTP 方法(GET、POST、PUT、DELETE 等)来对资源进行操作。每个方法都有特定的含义,例如 GET 用于获取资源,POST 用于创建资源,PUT 用于更新资源,DELETE 用于删除资源。
统一接口:RESTful 接口应该尽量保持简单和一致。资源的交互通过标准的 HTTP 方法和状态码进行,而不需要定义复杂的自定义操作。
资源的自我描述性:服务器在响应中应该提供足够的信息,使客户端能够理解资源的属性和如何与之交互。通常使用 JSON 或 XML 格式的数据来描述资源。
超媒体作为应用状态的引擎(HATEOAS):HATEOAS 是 RESTful 风格的一个重要概念,它表示在资源表示中提供相关资源的链接,使客户端能够根据链接进一步导航和交互,而无需预先了解所有的端点。
总之,RESTful 风格是一种通过 URI、HTTP 方法和资源表示来设计和组织网络应用程序的方式。它强调简单性、可伸缩性和可用性,并适用于构建 Web API 和分布式系统。
如果不记得设计原则的名字,只需要记住核心思想是复用和解耦。为了做到这个,我们需要面向接口编程,且尽量做到类和接口的精简单一,对扩展开放,对修改关闭,用组合代替继承。
下面是一些常见的设计原则:
开闭原则 (对修改关闭,对扩展开放)
里氏替换原则(子类尽量不重写父类的方法)(例如鸵鸟不会飞,不要重写鸟的飞方法,而是继承动物类)
依赖倒置原则(面向接口编程,而非面向实现编程)(例如shopping,传参Shop shop而非具体的shop)
单一职责原则(类的职责要单一,否则应拆分)(例如辅导员负责xx,班主任负责xxx)
接口隔离原则 (设计接口要精简单一)
迪米特原则 (通过中介,降低类之间耦合度)(要适度)
合成复用原则 (优先使用聚合/组合,避免使用继承)
开闭原则就是:对扩展开放,对修改关闭。
开闭原则的要点包括:
开放性(Open for Extension):允许在不修改已有代码的情况下引入新的功能或行为。
封闭性(Closed for Modification):已有的代码应该稳定,不应因引入新功能而需要频繁修改。
当谈到开闭原则时,一个重要的考虑是如何通过接口和多态性来实现。在 Go 中,由于它的接口和类型系统,可以很自然地应用开闭原则。以下是一个使用 Go 语言实现开闭原则的示例:
假设我们有一个图形绘制应用,需要绘制不同类型的图形。我们将定义一个 Shape
接口,并让不同的图形类型实现这个接口。
package main
import "fmt"
// Shape 是图形的接口
type Shape interface {
Draw()
}
// Rectangle 是矩形类型
type Rectangle struct{}
// Draw 实现了 Shape 接口的 Draw 方法
func (r Rectangle) Draw() {
fmt.Println("Drawing a rectangle")
}
// Circle 是圆形类型
type Circle struct{}
// Draw 实现了 Shape 接口的 Draw 方法
func (c Circle) Draw() {
fmt.Println("Drawing a circle")
}
func main() {
shapes := []Shape{Rectangle{}, Circle{}}
for _, shape := range shapes {
shape.Draw()
}
}
在这个示例中,我们定义了 Shape
接口,以及 Rectangle
和 Circle
两个类型,它们分别实现了 Shape
接口的 Draw
方法。在 main
函数中,我们可以轻松地使用不同类型的图形,而无需修改现有的代码。
如果要添加新的图形类型,只需创建一个新的类型并实现 Shape
接口的 Draw
方法即可,这符合开闭原则的要求,不需要修改现有的代码。
在这种情况下,你可以考虑使用组合模式(Composite Pattern)来实现任务处理模块的选择和顺序调用。组合模式允许你将对象组织成树状结构,以表示部分-整体的层次关系,从而能够灵活地组合和调用不同模块。
在组合模式中,通常会有两种基本类型的对象:叶子对象和容器对象。在你的情况下,CPU和GPU处理模块可以看作是叶子对象,而包含多个处理模块组合的任务可以看作是容器对象。
以下是如何使用组合模式来实现这种情况的一个简化示例:
// 抽象基类,代表处理模块
abstract class ProcessingModule {
abstract void process();
}
// 叶子对象,具体的CPU处理模块
class CPU extends ProcessingModule {
@Override
void process() {
System.out.println("CPU processing");
}
}
// 叶子对象,具体的GPU处理模块
class GPU extends ProcessingModule {
@Override
void process() {
System.out.println("GPU processing");
}
}
// 容器对象,任务,可能包含多个处理模块
class Task extends ProcessingModule {
private List<ProcessingModule> modules = new ArrayList<>();
void addModule(ProcessingModule module) {
modules.add(module);
}
@Override
void process() {
for (ProcessingModule module : modules) {
module.process();
}
}
}
通过上述代码,你可以:
ProcessingModule
类。Task
容器对象,它可以包含多个不同的处理模块。process
方法,实现模块的顺序调用。这种设计模式允许你动态地组合和调用不同的处理模块,同时也方便你对任务中的模块进行管理和扩展。
HashMap 使用键的比较来确定键值对的位置。如果所有的键比较都返回 1,那么 HashMap 将无法正确地定位键值对,可能导致无法正确地插入、查找或删除数据,可能会引发错误或异常。
在某些情况下,遍历集合时执行 remove
操作并在之后使用 break
可能会导致 ConcurrentModificationException
异常。这是因为当你在遍历集合时,通过 Iterator
或者增强型 for
循环进行遍历时,Java 检测到集合的结构发生变化(比如添加或删除元素),就会抛出该异常。
下面是一个可能导致异常的例子:
List<String> myList = new ArrayList<>();
myList.add("A");
myList.add("B");
myList.add("C");
for (String element : myList) {
myList.remove(element); // 这里执行 remove 操作
break; // 然后立即退出循环
}
在这个例子中,当执行 myList.remove(element)
时,集合的结构发生了变化。然后使用 break
立即退出循环,这可能会导致 ConcurrentModificationException
异常。
为了避免这种情况,你可以考虑使用迭代器(Iterator)的方式来进行安全的遍历和修改操作:
Iterator<String> iterator = myList.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
iterator.remove(); // 使用迭代器的 remove 方法
break;
}
使用迭代器的 remove
方法会避免 ConcurrentModificationException
异常,因为迭代器自身会维护正确的集合状态。
总之,在遍历集合时修改集合的结构是一个敏感的操作,需要小心处理。使用迭代器的 remove
方法可以避免异常情况。
https的实现,要依靠非对称加密技术,所以先讲一下非对称加密的特点。
操作系统内置了一组根证书颁发机构(CA)的公钥,这些根证书可以用于验证其他证书的合法性。
客户端拿到服务端给的加密数据后,使用CA的公钥验证服务端的证书的合法性。
CA证书中包含了服务器的公钥、颁发机构的信息、过期日期等信息。
拿到CA证书,客户端会使用服务端提供的公钥加密一个随机数密钥,发送给服务端。
服务端用私钥解密,此时这个随机数密钥是绝对安全的,可作为对称加密的密钥使用并传输数据。
数据位置:
数据大小限制:
缓存:
使用场景:
总之,GET和POST请求在数据传递、数据大小限制、缓存、幂等性等方面存在明显的区别,开发人员应根据具体的使用场景和需求选择合适的请求方法。
遇到过,通过设置中间件过滤器解决跨域问题。在过滤器中可以配置放行规则。
配置规则也可以放到Nginx中。
IP地址是一种用于唯一标识网络上设备的数字标识,用于在网络中路由和传递数据。
DNS是域名服务器,可以用于将人类可读的域名转换为对应的IP地址。
TCP是可靠传输,UDP是不可靠传输。
TCP(Transmission Control Protocol)是一种常用的传输层协议,用于在网络上建立可靠的连接和传输数据。TCP的连接建立和终止过程分别是三次握手和四次挥手。
三次握手(Three-Way Handshake):
客户端向服务器发送连接请求(SYN): 客户端向服务器发送一个标志位为SYN(同步)的TCP数据包,表明客户端希望建立连接。同时,客户端会选择一个初始的序列号。
服务器向客户端回复确认连接请求(SYN + ACK): 服务器收到客户端的请求后,会回复一个标志位为SYN和ACK(确认)的TCP数据包,表示接受连接请求。服务器也会选择一个初始的序列号,并把客户端的序列号加1作为确认序列号。
客户端向服务器回复确认连接(ACK): 客户端收到服务器的回复后,会发送一个标志位为ACK的TCP数据包,表示连接已经建立。客户端的确认序列号是服务器的初始序列号加1。
此时,连接已经建立,双方可以开始传输数据。
四次挥手(Four-Way Handshake):
客户端向服务器发送关闭连接请求(FIN): 当客户端完成数据传输后,会发送一个标志位为FIN的TCP数据包,表示希望关闭连接。
服务器向客户端回复确认关闭请求(ACK): 服务器收到客户端的关闭请求后,会发送一个标志位为ACK的TCP数据包,表示已经接受关闭请求。
服务器向客户端发送关闭连接请求(FIN): 当服务器完成数据传输后,会发送一个标志位为FIN的TCP数据包,表示希望关闭连接。
客户端向服务器回复确认关闭请求(ACK): 客户端收到服务器的关闭请求后,会发送一个标志位为ACK的TCP数据包,表示已经接受关闭请求。
此时,连接已经彻底关闭,双方都无法再进行数据传输。
总的来说,三次握手用于建立连接,确保双方都愿意进行数据传输,而四次挥手则用于终止连接,双方都确认数据传输已经结束。
DDoS(Distributed Denial of Service)攻击是一种恶意行为,旨在通过向目标服务器发送大量的请求或流量,超过其处理能力限制,从而导致服务器无法正常响应合法用户的请求,进而使服务不可用。
缓解方法:
IP封堵: 检测到恶意IP地址后,可以将其临时或永久地封锁,从而减少攻击的影响。
云托管服务: 一些云服务提供商提供DDoS防护服务,可以自动应对DDoS攻击,确保应用的可用性。
SQL注入是一种恶意攻击,通过向应用程序的输入字段插入恶意的SQL代码,从而绕过应用程序的验证和过滤,访问、修改或删除数据库中的数据,甚至获取敏感信息。SQL注入通常发生在没有充分验证和转义用户输入的情况下,使攻击者能够执行不受限制的数据库操作。
防范SQL注入攻击的关键在于确保用户输入的数据不会被误解为SQL代码。以下是一些防范措施:
使用参数化查询(Prepared Statements): 参数化查询是使用预定义的参数来替代用户输入,数据库会自动进行参数化处理,从而避免将用户输入直接嵌入到SQL语句中。
使用ORM框架: 使用对象关系映射(ORM)框架可以帮助将数据库操作与用户输入分开,从而减少SQL注入的风险。
输入验证和过滤: 对用户输入进行验证和过滤,确保输入符合预期格式和类型。例如,如果期望输入是数字,就验证输入是否为合法的数字。
转义特殊字符: 对用户输入的数据进行适当的转义,以确保特殊字符不被误解为SQL代码。数据库提供的转义函数可以用于这个目的。
最小权限原则: 限制数据库用户的权限,确保应用程序只能执行必要的数据库操作,减少攻击者可以利用的机会。
避免动态拼接SQL语句: 避免在代码中直接拼接用户输入的数据来构建SQL语句,这容易被攻击者利用。而是使用参数化查询或ORM框架。
错误信息保护: 不要将详细的错误信息暴露给用户,因为攻击者可能会利用这些信息来进行攻击。
安全更新和维护: 及时更新数据库和应用程序的补丁,以修复已知的漏洞和安全问题。
总的来说,防范SQL注入攻击需要从编码、验证、过滤、权限控制等多个层面进行,确保用户输入的数据不会被误解为恶意的SQL代码。
CDN(Content Delivery Network)是一种分布式网络架构,用于在全球范围内分发内容(如网页、图像、视频、文件等),以提高访问速度、减少延迟和减轻服务器负载。CDN的目标是将内容就近分发给用户,从而提供更快的加载时间和更好的用户体验。
主键是表中的一个字段,用于唯一标识数据。
主键的作用是确保每条记录都能被唯一标识。
主键上默认会创建一个聚合索引。
外键是表中的一个字段,用于建立与其他表的联系。
聚合索引是指叶子节点中既存储了索引也存储数据的数据结构。一个表中只能有一个聚合索引。数据库会默认为主键创建聚合索引。
联合索引是一种索引类型,它基于多个表字段创建。这些字段的顺序很重要,因为查询时需要按照联合索引中字段的顺序进行检索。
使用B+树。B+树的树高比较低,磁盘读写代价低,且叶子节点形成有序链表,更适合区间查询。
1、MySQL会自动给主键建立索引。
2、有索引的表,数据实际上是存在索引表里的,是排序的。所以查找更快。
3、索引表是分页的,每个page大小是16kb
4、页目录,存的是每页的第一个索引和那个页的指针。
5、这种结构叫做B+树,要会算每页存多少条数据,假设每条数据占1kb,则每页能存16条。
目录也是16kb,161024个字节,目录的每条假设占14个字节,则能存1170个目录条。
那么B+树如果有两层,则总共能存161170个数据。三层1611701170个数据。
创建索引是为了提高数据库查询性能,但并不是所有列都适合创建索引,因为索引也会带来一些开销。以下是一些适合和不适合创建索引的列的指导原则:
适合创建索引的列:
不适合创建索引的列:
总之,创建索引需要根据实际查询需求、数据操作频率和列的特性来权衡。过多或不恰当地创建索引可能会导致索引维护开销过大,反而影响性能。最好根据具体的应用场景和查询模式来选择哪些列适合创建索引。
创建索引。
使用合适的数据类型。
尽量使用全值匹配查询,避免范围查询和模糊查询。
分页查询优化。
使用explain关键字分析查询效率。
如:
explain select * from tb_index where a=5
查到的结果重点看type字段
type显示查询使用了何种类型,效率const > eq_ref > ref > range > index > all
const : 代表查询索引字段,并且表中最多只有一行匹配,只有主键查询只匹配一行才会是const
eq_ref: 搜索时使用primary key 或 unique 类型的索引
ref : 根据索引查找一个或多个值,非唯一索引
range : 对索引列进行范围查找
index : 全索引表扫描
all : 全表扫描,通常没有用到建索引的列
事务就是针对数据库的一组操作,这些操作要么全部成功,要么全部失败。
事务有四个特性:原子性、一致性、隔离性、持久性。
事务的特性(ACID):
原子性(Atomicity): 原子性表示事务中的操作被视为一个不可分割的单元,要么全部执行成功,要么全部回滚(失败)。如果在事务中的任何操作失败,整个事务会被回滚到起始状态,不会留下部分修改。
一致性(Consistency): 一致性确保在事务开始和结束时,数据库从一个一致状态转变为另一个一致状态。换句话说,事务的执行不会破坏数据库的完整性和约束,无论事务成功还是失败。
隔离性(Isolation): 隔离性确保并发执行的事务之间相互隔离,即一个事务的操作不会被其他事务看到,直到事务提交。这可以防止并发执行导致的数据不一致问题。
持久性(Durability): 持久性指的是一旦事务提交,其所做的修改将会永久保存在数据库中,即使系统发生故障。即使系统崩溃,事务提交后的修改也不应该丢失。
在并发条件下,多个事务对数据库的操作可能会产生一些并发问题,具体有:
脏读(Dirty Read):指一个事务读取了另一个事务未提交的数据。
幻读(Phantom Read):指一个事务在多次查询中,第二次查询结果包含了第一次查询中未出现的数据,或者第二次查询结果中的数据行数比第一次查询结果多。
不可重复读(Non-repeatable Read):指在一个事务内,多次读取同一数据,由于其他事务的修改,导致本次读取的结果与上一次读取的结果不一致。
幻读涉及范围查询,即查询条件导致的行数变化,多为插入或删除操作引起。
不可重复读涉及单行数据的变化,多为更新操作引起。
幻读强调的是数据集合的变化,不可重复读强调的是特定数据的变化。
MySQL 支持四种隔离级别,分别是:读未提交(READ UNCOMMITTED) 读提交 (READ COMMITTED) 可重复读 (REPEATABLE READ) 串行化 (SERIALIZABLE)。这四种隔离级别从上往下,隔离强度逐渐增强。其中,读未提交和读已提交是最低的隔离级别,而串行化是最高的隔离级别。
MySQL和DB2 默认的隔离级别是可重复读,Oracle默认的隔离级别是读已提交。
用于保证某列或某组列的值在表中唯一的一种约束。唯一约束确保在指定的列中没有重复的值,即每个值只能出现一次,从而避免了数据的重复性和冲突。
分库分表是数据库性能优化和扩展的一种重要策略,特别在处理大规模数据和高并发的情况下。分库分表将数据库按照一定的规则拆分成多个独立的数据库实例和表,以减轻单一数据库的负担,提高系统的吞吐量和性能。
以下是一些步骤和考虑因素,帮助你进行有效的分库分表:
分析数据访问模式: 首先要了解应用的数据访问模式,包括哪些表经常被查询、更新、插入等。根据访问模式,将热点数据和冷数据进行划分,可以避免出现过多的访问竞争。
选择分库还是分表: 分库是将不同的数据表拆分到不同的数据库中,分表是将一个表的数据拆分到多个小表中。选择要分的库还是表要根据具体情况来决定。
选择拆分规则: 拆分规则是决定如何将数据分布到不同的库或表中的规则。可以根据业务逻辑、数据属性、地理位置等因素来选择合适的拆分规则,例如按用户ID范围、按地区、按时间等。
应用层适配: 分库分表后,应用程序需要适应新的数据库结构。这可能需要重写查询、更新和插入逻辑,以确保数据正确地路由到相应的库和表。
处理分布式事务: 在分库分表架构中,处理分布式事务变得更加复杂。你可能需要使用分布式事务处理框架,或者根据具体情况使用分布式锁来保证一致性。
监控和维护: 在分库分表后,监控和维护变得更加重要。你需要实时监控数据库实例的性能,及时进行优化和扩展。
备份和恢复: 分库分表后的备份和恢复策略也需要重新规划。你可能需要定期备份每个库和表,并确保能够迅速恢复。
数据迁移: 数据迁移是将现有的数据从单一的数据库迁移到分库分表的数据库结构中。这需要仔细规划和测试,以避免数据丢失或不一致。
测试和评估: 在进行分库分表之前,务必进行充分的测试和评估,确保新的架构能够满足性能和可用性需求。
总之,分库分表是一项复杂的任务,需要仔细规划和执行。在进行分库分表之前,建议详细了解应用的数据特点和访问模式,以及选择适合的拆分策略和工具。同时,确保进行充分的测试,以确保新的架构能够达到预期的性能和可扩展性。
如果是整数,可以使用tinyint/smallint/mediumint/int/bigint 存储,他们占用的空间分别是1、2、3、4、8个字节
可以设置长度,但没必要,因为不影响他们占的字节数。
他们能存储的数据范围要看有符号和无符号的,然后按规则计算出来,以int为例
//int占4个字节,也就是8*4=32个位,如果是有符号,则取值范围是-2^31到2^31-1,如果是无符号,则取值范围是0~2^32
如果是小数,则需要使用float或double类型存储,这两个是近似浮点数,有舍入误差。他们分别占4和8个字节。
float取值范围:约为-3.402823466E+38到-1.175494351E-38,以及1.175494351E-38到3.402823466E+38
double取值范围:约为-1.7976931348623157E+308到-2.2250738585072014E-308,以及2.2250738585072014E-308到1.7976931348623157E+308
如果需要存储精确小数,则需要使用decimal。设置的长度和小数位数例如40 、18表示总位数可以是40位,小数位数可以是18位。
varchar255
这表示VARCHAR列的最大长度为255个字符。这并不意味着每次存储都会占用255个字节的存储空间。实际存储的大小取决于存储的字符串长度。如果你存储一个长度为10的字符串,实际上只会占用10个字节的存储空间(加上一些额外的开销)。VARCHAR的长度设置为255仅表示这个列最多可以存储255个字符的字符串。
一般情况下,UTF-8 编码是最常用的字符编码,它支持多种语言,包括英文、中文、日文等。在 UTF-8 编码下,一个字符的存储需要占用不同数量的字节,通常情况下:
基本 ASCII 字符(如英文字母和数字)占用 1 个字节。
大多数其他字符占用 2 到 3 个字节。
String、Hash、List、Set、SortedSet
RDB和AOF
RDB是定期将内存快照写入磁盘。
AOF是记录对Redis的写操作命令日志。
AOF文件更大,RDB可能会丢失部分数据。
可以使用两种持久化方案混合使用的方式。
缓存穿透、缓存击穿和缓存雪崩是与缓存相关的三种常见问题,它们都可能导致缓存系统性能下降或出现异常情况。以下是对这三个问题的解释以及如何避免的介绍:
缓存穿透(Cache Miss and Database Miss):
缓存穿透是指一个查询无法从缓存中获取到数据,因此不断查询数据库,这可能是由于查询的数据根本不存在于数据库中,但是由于缓存不命中的原因,每次都会进行数据库查询。这可能会导致数据库的负载增加。
避免方法:
缓存击穿(Cache Miss and Hotspot):
缓存击穿是指一个热点数据(经常被查询的数据)失效,导致每次查询都必须从数据库加载数据,这会导致数据库压力增大,降低系统性能。
避免方法:
缓存雪崩(Massive Cache Expiry):
缓存雪崩是指在某个时间点,缓存中大量的数据同时失效,导致大量的请求都直接访问数据库,从而造成数据库压力骤增。
避免方法:
综合来说,避免缓存穿透、击穿和雪崩需要综合考虑缓存策略、数据预加载、合理设置缓存过期时间等。不同的应用场景可能需要不同的缓存方案来保证系统的稳定性和性能。
Redis 是一个单线程的键值存储系统,这意味着在大多数情况下,Redis 在同一时间只会处理一个命令。然而,这并不意味着 Redis 不能实现并发。尽管 Redis 使用单线程来处理命令,但它通过多路复用技术(I/O Multiplexing)来实现高并发性能。在大部分情况下,Redis不会产生并发问题。
使用SETNX方法。