Spring Boot与Spring Security:
使用JWT:
存储和管理令牌:
安全性:
日志和监控:
JWT登录流程:
JWT结构包括三部分:
头(Header):它通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,如HMAC SHA256或RSA。
有效载荷(Payload):包含声明,这是关于实体(通常是用户)以及其他一些元数据的语句。这些声明被称为“claims”。
签名(Signature):为了获得签名部分,您必须获取编码的header,编码的payload,一个秘密,然后使用header中指定的算法进行签名。
摘要算法:JWT支持多种摘要算法,但最常见的算法是:
其中,HS256使用共享密钥(客户端和服务器都知道的),而RS256使用私钥/公钥对,只有服务器知道私钥。这使得RS256更适合于公开的、可扩展的环境,因为只有生成JWT的服务器才能验证和接受令牌。
添加iText库依赖:
pom.xml中添加iText的依赖。不同版本的iText可能有所不同,所以要确保选择一个适合的版本。创建PDF文档:
Document类来创建一个新的PDF文档。PdfWriter.getInstance()方法将这个Document对象与一个文件输出流关联起来。开始编写内容:
document.open()document.add()方法添加内容。iText提供了多种元素,如Paragraph, Chapter, Section, List, PdfPTable等,你可以根据需要加入。格式化内容:
BaseFont和Font类来创建和应用不同的字体和样式。创建表格:
PdfPTable类来创建表格。添加图片或公司标志:
Image类来添加图片到文档。页眉和页脚:
HeaderFooter类或事件处理来添加页眉和页脚。这样可以为每一页自动添加页码、日期、公司标志等信息。完成文档:
document.close()来关闭并保存文档。提供给用户:
客户端(Client):发起RPC请求的部分。客户端包含代表远程过程的存根(stub),它提供与本地过程相同的接口。
服务器(Server):接受RPC请求并执行服务的部分。服务器同样包含一个存根,负责接受请求、解码参数、执行请求并返回结果。
传输层:RPC需要一种通信方式来在客户端和服务器之间传输数据。这通常通过网络完成,例如使用TCP/IP或UDP。
消息格式/序列化:由于网络传输层通常只能传输字节流,RPC需要将数据(如过程参数和返回值)转换为这种格式。这个转换过程叫做序列化(将数据转换为字节流)和反序列化(将字节流转回原始数据)。
请求与响应:客户端发起的是请求,服务器返回的是响应。每个请求都与一个响应匹配。
服务注册与发现:在某些RPC系统中(如gRPC、Apache Thrift等),服务器可以注册其提供的服务,并且客户端可以发现这些服务。这可以使得客户端和服务器的连接更加动态和灵活。
错误处理:如果远程调用中发生错误(如网络问题、服务不可用等),RPC框架应该能够捕获并处理这些错误。
身份验证和授权:为了确保只有合法的客户端可以访问服务,RPC系统可能会包含身份验证和授权机制。
负载均衡:在多个服务器实例中,RPC系统可能会提供负载均衡功能,使得客户端的请求可以均匀地分配到不同的服务器。
(1)选择CP还是AP取决于你的系统需求:
(2)ZooKeeper 是一个CP系统。当网络分区发生时,为了维护一致性,ZooKeeper可能会牺牲可用性。这意味着在某些情况下,ZooKeeper可能不会响应客户端的请求,以确保数据的一致性。
(1)在RPC(远程过程调用)中,序列化的主要作用是将数据或对象转化为可传输的格式,使其能够在网络上进行传输,从而实现不同节点或服务之间的通信。
具体作用如下:
数据交换:通过序列化,客户端可以将请求参数转化为字节流,在网络上发送到服务器;服务器接收到字节流后,再通过反序列化恢复为原始的请求参数。
保证数据完整性:序列化过程中可以将数据结构完整地转化为字节流,确保数据在传输过程中不丢失任何信息。
兼容性:有些序列化协议(如Protocol Buffers, Avro等)提供了版本控制和兼容性管理,使得数据格式可以随着时间演进而不影响已有的客户端和服务器之间的通信。
(2)Serializable的原理
Serializable 是Java中的一个标记性接口,用于指示一个类的对象可以被序列化。当一个类实现了Serializable接口时,Java的对象序列化机制可以将其转换为字节流,反之也可以从字节流中重构对象。
(1) 跳表(SkipList)主要被用于有序集合(Sorted Sets, zset)的实现。它能实现快速查询、插入和删除操作。它是由多层链表组成,每一层都是一个有序链表。层级之间通过上下指针相互连接,底层是完整数据链表。
(2)查询过程和效率
查询效率为O(log n),因为每一次跳跃都大致减少了一半的搜索空间。
(3)与二叉树和红黑树的比较
平衡问题:二叉查找树(如红黑树)需要进行平衡操作,而跳表通过随机化的方法来确保层级结构的均衡,避免了复杂的旋转操作。
范围查询:红黑树不支持范围查询,而跳表的底层是完整的有序链表,所以很容易进行范围查询。
简单性和实现难度:跳表的结构和算法相对简单,而红黑树的实现相对复杂。
(4)与B+树的区别
结构:跳表是基于链表的,而B+树是基于树的结构。
存储方式:在B+树中,所有的值都在叶子节点,并且叶子节点形成了一个有序链表。而在跳表中,每一个节点可能出现在多个层级上。
查询效率:B+树和跳表的查询效率都是O(log n),但在实际应用中,由于B+树更适合磁盘存储和I/O操作,它通常用于数据库索引;而跳表因为其结构简单,经常用于内存数据结构,如Redis。
扩展性:跳表更容易进行水平扩展。
(5)为什么Redis选择跳表而不是红黑树?
Redis使用跳表来实现其有序集合数据类型(Sorted Set)。跳表相比红黑树来说,更容易实现,理解和维护。同时,跳表更容易支持范围查询和有序数据的快速插入/删除。红黑树的范围查找并不直接得到,需要进行额外的遍历操作。
要实现整数的自增功能,可以使用INCR命令。如果键不存在,Redis会将它设置为0,然后执行自增操作。
Redis集群提供了一种方式,允许多个Redis节点协同工作,以此来提供更高的可用性和性能。其核心特点和概念包括:
集群模式不仅提高了Redis的性能(通过分片)和高可用性(通过主从复制和故障转移),而且还允许Redis部署规模更大、容纳更多数据。
Bean 的生命周期指的是 Bean 在 Spring(IoC)中从创建到销毁的整个过程。Bean 的生命周期主要包含以下 5 个流程:
(1)实例化:为 Bean 分配内存空间;
(2)设置属性:将当前类依赖的 Bean 属性,进行注入和装配;
(3)初始化:
(4)使用 Bean:在程序中使用 Bean 对象;
(5)销毁 Bean:将 Bean 对象进行销毁操作。
InitializingBean接口的afterPropertiesSet方法。@PostConstruct注解标记一个方法作为bean的初始化方法。init-method属性来指定bean的初始化方法。避免使用OFFSET方法: 传统的使用LIMIT和OFFSET的方法在处理大量数据时可能会很慢,因为它跳过了OFFSET之前的所有行,这在数据很多时会非常慢。
使用主键进行分页: 在大多数情况下,基于ID的分页查询效率更高。例如,假设上次的最后一个ID是1000,下次查询可以从ID>1000开始,然后使用LIMIT取下一页的数量。
SELECT * FROM table WHERE id > 1000 ORDER BY id ASC LIMIT 10; 使用覆盖索引: 确保查询只使用索引中的列,这样MySQL可以只扫描索引,而不必查询实际的数据行。这称为“覆盖索引”。
避免SELECT *: 只选择需要的列,而不是使用SELECT *,这样可以减少数据传输量。
缓存结果: 如果结果集不经常更改,可以考虑缓存分页结果,以减少对数据库的重复查询。
预估总数: 对于某些应用,不需要知道精确的总页数或总行数,可以考虑预估这个数字,以避免计算成本高的COUNT查询。
调整innodb_buffer_pool_size配置: 确保InnoDB缓冲池足够大,以缓存常用的数据和索引。
使用分区: 如果表非常大,可以考虑使用分区,这样查询可以只扫描感兴趣的分区,而不是整个表。
考虑使用摘要: 对于大量数据的分页显示,可能不需要显示每一行的所有数据。可以考虑只获取和显示摘要或预览,当用户需要查看详细信息时,再进行单独的查询。
减少每页显示的行数: 当处理大量数据时,考虑减少每页的行数,这样可以减少查询和渲染的时间。
(1) Hash 表
哈希算法有个 Hash 冲突 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 链地址法。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 HashMap 就是通过链地址法来解决哈希冲突的。不过,JDK1.8 以后HashMap为了减少链表过长的时候搜索时间过长引入了红黑树。
为了减少 Hash 冲突的发生,一个好的哈希函数应该“均匀地”将数据分布在整个可能的哈希值集合中。既然哈希表这么快,为什么 MySQL 没有使用其作为索引的数据结构呢? 主要是因为 Hash 索引不支持顺序和范围查询。假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。并且,每次 IO 只能取一个。
(2) B 树& B+树
B 树也称 B-树,全称为 多路平衡查找树 ,B+ 树是 B 树的一种变体。B 树和 B+树中的 B 是 Balanced (平衡)的意思。
目前大部分数据库系统及文件系统都采用 B-Tree 或其变种 B+Tree 作为索引结构。
B 树& B+树两者有何异同呢?
读未提交 (READ UNCOMMITTED)
读提交 (READ COMMITTED)
可重复读 (REPEATABLE READ)
串行化 (SERIALIZABLE)
多版本并发控制 (MVCC): InnoDB使用MVCC来实现可重复读。简单来说,对于每行数据,InnoDB既保存数据的当前值,也保存该行的之前版本的值。当一个事务读取一行数据时,它会读取该行数据在该事务开始时的版本,而不是最新版本。这就确保了事务在其生命周期内看到的数据是一致的。
Undo日志: 当一行数据被修改时,InnoDB会在Undo日志中保存该行的原始数据。如果其他事务需要读取这行数据的早期版本,它可以在Undo日志中找到。
Read View: 当事务开始时,InnoDB会为它创建一个“Read View”。Read View确定了该事务可以看到哪些行的哪个版本。基本上,事务只能看到在它开始之前就已经存在的行版本。
Next-Key Locking: InnoDB使用一种称为“Next-Key Locking”的方法,这种方法在读取行时为行和行的间隙加锁,从而防止其他事务插入新的行。
一致性非锁定读: 在可重复读隔离级别下,InnoDB默认进行一致性非锁定读。这意味着读操作不会设置任何锁,而是直接返回行的早期版本,如果可用的话。
快照读和当前读: 在可重复读隔离级别下,普通的SELECT(不带FOR UPDATE或LOCK IN SHARE MODE)是快照读,它返回数据的早期版本。而带FOR UPDATE或LOCK IN SHARE MODE的SELECT则是当前读,它返回数据的最新版本,并设置相应的锁。
脏读 (Dirty Read): 当一个事务读取了另一个事务尚未提交的数据,称为脏读。如果这个写事务在之后发生了回滚,那么读事务读取到的信息将是不准确的。
不可重复读 (Non-Repeatable Read): 在一个事务内,多次读取同一数据时,中间的某次读取返回了不同的数据,这种现象称为不可重复读。这通常是由于在两次读取之间,另一个事务修改了这些数据。
幻读 (Phantom Read): 在一个事务内,多次执行相同的查询,第一次查询返回了一些行,而第二次查询返回了不同的行,这称为幻读。这通常是因为在两次查询之间,另一个事务插入或删除了一些行。
写-写冲突: 当两个事务同时试图修改同一数据时,可能会发生写-写冲突。大多数数据库管理系统采用锁机制来避免这种冲突,确保每次只有一个事务可以写入数据。
读-写冲突: 当一个事务正在读取数据,而另一个事务试图同时写入相同的数据,就会发生读-写冲突。这可能会导致等待,直到写事务完成,或者读事务可能需要重新读取数据。
死锁 (Deadlock): 当两个或多个事务互相等待对方释放资源时,会发生死锁。例如,事务A等待事务B释放一个资源,而事务B同时等待事务A释放另一个资源。这些事务都进入了一个等待状态,无法前进。
资源竞争: 当多个事务都尝试访问和使用相同的资源(如数据、锁或内存)时,它们可能会相互干扰,导致系统的整体性能下降。
为了解决上述问题,数据库管理系统提供了事务隔离级别,以控制事务之间的交互方式。通过选择合适的隔离级别,可以在数据一致性和系统性能之间取得平衡
隐式类型转换: 当字段的数据类型和查询条件中的数据类型不匹配时,MySQL可能不会使用索引。例如,如果一个字段是字符型,但在WHERE子句中与数字进行比较,这将导致索引失效。
使用函数: 在列上使用函数会导致索引失效。例如,使用UPPER()函数来进行字符串比较。
使用OR: 当在WHERE子句中使用OR连接两个不同的列,如果其中一个列没有索引,那么整个查询可能不会使用索引。
非最左前缀: 对于联合索引,如果查询没有使用联合索引的最左部分,则索引可能不会被使用。例如,对于(a,b,c)的联合索引,如果查询只涉及b和c而不涉及a,则索引可能不会被使用。
LIKE的不正确使用: 当使用LIKE操作符时,如果搜索字符串的开头是通配符(如%abc),则索引将不会被使用。但如果模式是固定的开始(如abc%),则会使用索引。
使用!=或<>: 使用这些操作符可能会导致全表扫描,从而不使用索引。
索引列的计算: 在WHERE子句中对索引列进行计算,例如indexed_column / 2 = 100,这将导致索引失效。
NULL检查: 使用IS NULL或IS NOT NULL操作符可能导致索引不被使用。
索引未被统计或统计信息过时: 如果MySQL查询优化器认为全表扫描比使用索引更快,或者索引统计信息不是最新的,它可能选择不使用索引。
使用JOIN: 如果在JOIN操作中不正确使用索引列,可能导致索引失效
CHAR: 用于存储定长字符串。即使存储的字符串长度小于声明的长度,它也会占用为其分配的全部空间。例如,CHAR(10)用于存储长度为10的字符串。
VARCHAR: 用于存储变长字符串。只占用实际需要的空间加上一个或两个额外字节。例如,VARCHAR(10)可以存储最长为10个字符的字符串,但只占用实际字符数加上1个或2个字节的空间。
TINYTEXT: 可变长度文本字段,最大长度为255个字符。
TEXT: 可变长度文本字段,最大长度为65,535字符。
MEDIUMTEXT: 可变长度文本字段,最大长度为16,777,215字符。
LONGTEXT: 可变长度文本字段,最大长度为4,294,967,295字符。
ENUM: 用于存储预定义值列表中的一个值。在表创建时,会为列定义一个值列表,每个列值必须来自该列表。例如,ENUM('RED', 'BLUE', 'GREEN')。
SET: 类似于ENUM,但可以存储多个值。例如,SET('RED', 'BLUE', 'GREEN')可以存储如"RED,BLUE"这样的组合值
MySQL 中并发事务的控制方式无非就两种:锁 和 MVCC。锁可以看作是悲观控制的模式,多版本并发控制(MVCC,Multiversion concurrency control)可以看作是乐观控制的模式。锁 控制方式下会通过锁来显示控制共享资源而不是通过调度手段,MySQL 中主要是通过 读写锁 来实现并发控制。
MVCC 是多版本并发控制方法,即对一份数据会存储多个版本,通过事务的可见性来保证事务能看到自己应该看到的版本。通常会有一个全局的版本分配器来为每一行数据设置版本号,版本号是唯一的。
MVCC 在 MySQL 中实现所依赖的手段主要是: 隐藏字段、read view、undo log。
来源和类型:
synchronized是Java的关键字,属于JVM层面的锁。通过监视器锁(monitor)来实现,每个对象都有监视器锁。Lock是Java类库提供的锁机制。例如ReentrantLock。手动性:
synchronized的锁定和释放是隐式的,当线程进入synchronized修饰的方法或代码块时,锁定,当退出时释放。Lock需要手动锁定和释放。如果忘记释放,可能导致死锁。可中断性:
synchronized是不可中断的。Lock提供了一个可以响应中断的锁获取操作,lockInterruptibly()。公平性:
synchronized是非公平锁。Lock可以设置为公平锁或非公平锁。例如,ReentrantLock在构造时可以指定是否为公平锁。绑定多个条件:
synchronized没有此功能。Lock可以与多个Condition对象绑定,实现多路通知功能。性能和效率:
synchronized的性能通常低于Lock,但在近年的Java版本中,这两者的性能差异已经缩小。Lock提供了更高的灵活性,因此在某些复杂的场景下可能更有优势。synchronized和Lock都可以用于多线程的同步,但Lock提供了更高的灵活性和更细粒度的控制结构:
数组 + 链表结构: HashMap 的基础是一个数组结构(Node)。每个数组的元素或称为桶 (bucket) 都保存了一个链表。
在 Java 8 之后,对于长链表还可能使用红黑树来有效地管理哈希冲突,并保持其操作的效率
解决哈希冲突的办法:
链地址法 (Chaining): 这是 HashMap 中使用的方法。在数组的同一索引位置上,所有具有相同哈希码或数组索引的元素都存储在一个链表中。如果在某个索引位置上发生了哈希冲突,那么新的键值对就被添加到那个位置的链表的末尾。
开放地址法 (Open Addressing): 当冲突发生时,查找数组中的下一个空位置并将键值对放置在那里。这种方法的一个变种是双散列,它使用两个哈希函数来确定元素的位置。
再哈希法 (Rehashing): 使用另一个哈希函数来确定键值对的位置。
线性探测 (Linear Probing): 当哈希冲突发生时,线性地查找数组中的下一个空位置。
二次探测 (Quadratic Probing): 当哈希冲突发生时,以二次的方式查找数组中的下一个空位置。
三次握手(Three-way Handshake):建立连接时,TCP使用三次握手机制来确保双方都准备好进行通信。
确认应答(Acknowledgements):当接收到数据时,接收方会发送一个确认消息(ACK)回到发送方。发送方在发送数据后会启动一个计时器,等待ACK的到来。
超时重传:如果发送方在预定时间内没有收到ACK,它会假设数据包已丢失,并重新发送该数据包。
序列号:每个发送出去的字节都有一个序列号。接收方使用这些序列号来重新组装数据流并检测丢失的数据包。
滑动窗口:TCP使用滑动窗口协议来控制在一个给定的时间内发送者可以发送多少数据,以及何时需要确认。这不仅可以处理丢包,还可以进行流量控制,防止接收方被发送方的数据淹没。
错误检测:TCP头部有一个校验和字段,用于检查数据是否在传输过程中被损坏。
拥塞控制:如果网络出现拥塞,TCP会减少其发送数据的速率,避免更严重的网络问题。这是通过观察丢失的数据包来实现的,因为丢失的数据包通常是网络拥塞的一个标志。
四次挥手(Four-way Handshake):当连接的一方完成其发送任务后,它可以请求关闭连接,这需要一个四步过程来确保双方都完成了数据传输。
继承、重写和向上转型。 只有满足这3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
JVM的垃圾回收机制是指在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行。垃圾回收的过程主要包括以下几个步骤:
判定垃圾回收的对象。回收垃圾之前,首先要找到需要被当作垃圾而回收的对象。JVM分为五个区域——程序计数器、虚拟机栈、本地方法栈、堆、方法区。我们知道程序计数器与栈均是线程私有的,其生命周期取决于线程的生命周期。
标记存活对象。可达性算法是为了标记存活的对象,知道哪些是可回收对象。
垃圾回收算法进行回收。常见的几种垃圾回收算法有标记清除、复制算法、标记整理和分代收集算法。
压缩内存空间。在进行完垃圾回收之后,可能会出现内存空间不连续的情况,需要进行内存压缩。
(1)HTTP与HTTPS的区别
安全性:
端口:
性能:
证书:
(2) HTTP请求结构
一个HTTP请求主要包含以下部分:
(3) 请求头的作用
请求头在HTTP请求中扮演了重要的角色,它为服务器提供了关于客户端请求的一些信息。以下是请求头的一些常见用途:
Content-Type头部,客户端可以告诉服务器发送的数据是什么格式,如application/json或text/html。Content-Length头部,指示请求或响应体的大小。Authorization头部用于包含凭据,通常用于API认证。Cache-Control和其他相关的头部可以控制如何缓存响应内容。User-Agent头部描述了发出请求的客户端类型,如浏览器或其他客户端应用。Accept头部,客户端可以告诉服务器它希望收到哪种类型的响应。Cookie头部可以包含服务器设置的任何cookie,它们在每个请求中发送回服务器。Origin头部表示请求来自哪个源,与CORS(跨来源资源共享)策略相关。(1)二叉树 (Binary Tree)
(2)平衡二叉树 (Balanced Binary Tree)
(3)全二叉树 (Complete Binary Tree)
(4)红黑树 (Red-Black Tree)
(5)B树
(6)B+树
区别和总结:
(1) 使用哈希函数:对每条数据记录计算哈希值。如果你的数据记录是字符串或其他数据类型,可以使用像MD5或SHA-1这样的哈希函数。计算哈希的目的是为了将大数据记录转化为较小的唯一标识符(哈希值),这样处理起来更加高效。
(2)外部排序:
(3)使用哈希表进行优化:
注意:哈希函数可能会出现冲突,即不同的数据记录可能会有相同的哈希值。因此,当发现两条记录的哈希值相同时,应该进一步检查原始记录以确定是否确实重复。