数据库查询缓存,减小数据源压力,提高响应速度
页面缓存:将页面的渲染结果缓存在Redis中,以减少页面生成的时间和服务器负载。
频繁计算结果缓存:将频繁计算的结果缓存在Redis中,以避免重复计算,提高性能。
分布式锁:利用Redis的原子性操作和过期时间特性,实现分布式锁,保证多个线程或进程之间的互斥操作。
消息队列:利用Redis的发布/订阅功能,实现消息队列,用于解耦和异步处理。
本地缓存也有类似应用场景,但有许多不足:
本地缓存的容量有限,无法存储大量数据。
本地缓存无法实现多个应用程序之间的共享缓存。
本地缓存的数据在应用程序重启时会丢失。
一个并发访问量比较大的key在某个时间过期,导致所有的请求直接请求到DB上。
解决方法:
1.加锁更新,⽐如请求查询A,发现缓存中没有,对A这个key加锁,同时去数据库查询数据,写⼊缓存,再返回给⽤户,这样后⾯的请求就可以从缓存中拿到数据了。
2.将过期时间组合写在value中,通过异步的⽅式不断的刷新过期时间,防⽌此类现象。
减小主库压力;提高读取性能和并发处理能力;灵活的处理数据。
强一致性:可以在写入操作后,立即读取主库数据,以保证读取到最新的数据。但会增加主库的负载。
主从同步机制:数据库主从同步会将主库的写操作同步到从库,通过设置合适的同步延迟和同步策略,可以确保从库数据的实时性和一致性。
读写分离中间件
分库:将一个数据库分成多个独立的数据库。用来解决数据库读写压力过大
垂直分库:按照业务归属不同进行拆分,例如用户,订单,商品三个业务。
水平分库:以字段为依据,按照一定的策略(hash,range等),将一个库中的数据拆分到多个库
分表:将一个数据库表分成多个独立的表,每个分表可以独立存储一部分数据,有自己的结构和索引。解决单表数据量过大问题,将数据分散到不同的表中,减少单表的数据量,提高查询和更新的效率。
分库主要关注的是将数据分布在不同的数据库实例中,以减轻单个数据库的负载,提高读写并发能力和扩展性。
而分表则是将一个大表分割成多个小表,以降低单个表的数据量,提高查询性能和写入吞吐量
主库将数据变更写入binlog
从库创建一个IO线程,读取主库binlog,并写入到中继日志(relay log)中
从库再开启一个sql线程读取relay log事件并在slave执行,来完成同步。
从库记录自己的binlog
假设有一个名为students的表,包含以下字段:id、name、age、gender、class
前:select count(*) as count from students class = “这个班级” and where age>20 and gender = “男”
后:select gender,count(*) as count from students where class = “这个班级” and age > 20 group by gender;
互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有,此时若有其他进程请求该资源,则请求进程只能等待
请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求时,该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放
不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)
循环等待条件: 若干进程间形成首尾相接循环等待资源的关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁
事务是数据执行的最小单元,不可以被分割
原子性:事务是数据处理的最小单元,事务的原子性保证事务的发生要么全部成功,要么全部失败。
隔离性:在并发事务中,各事务之间互不影响,相互独立
一致性:事务在执行前后总是由一个一致性状态转变为另一个一致性状态。
持久性:事务对数据的改变是持久的,即使中间数据库发生故障,也不会影响。
读未提交:最低的隔离级别,允许读取数据库尚未提交的数据。可能会发生幻读,脏读和不可重复读;
读已提交:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读可能发生。
可重复读:对同意字段多次读取的结果是一致的,除非是事务本身修改,可以阻止脏读和不可重复读,但幻读有可能发生。
串行化:最高的隔离级别,所有事务以此逐个执行,完全服从ACID的隔离级别。但是一般不会使用。
事务 A、B 交替执行,事务 A 读取到事务 B 未提交的数据,这就是脏读。
在一个事务范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是不可重复读。
事务 A 查询一个范围的结果集,另一个并发事务 B 往这个范围中插入 / 删除了数据,并静悄悄地提交,然后事务 A 再次查询相同的范围,两次读取得到的结果集不一样了,这就是幻读。
TCP/UDP在传输层,提高可靠的,面向连接的数据传输
@Configuration注解
@Component注解
@Service、@Repository、@Controller注解都是@Component的派生注解,分别用于声明服务层、数据访问层和控制层的Bean。
在其他类中,可以通过@Autowired或@Resource等注解来自动注入这些Bean的实例。
Spring的自动装配 byName和byType的区别?
byName:根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配。
byType:如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配;如果存在多个该类型bean,那么抛出异常,并指出不能使用byType方式进行自动装配;如果没有找到相匹配的bean,则什么事都不发生,也可以通过设置改变。
存储方式
TreeSet: 是一个有序的、不重复的集合类,它存储的是一组无序的、不重复的元素。
TreeMap: 是一个有序的、键值唯一的映射类,它存储的是键值对,并按照键的自然顺序进行排序
数据结构
元素比较
操作方法
小结
TreeSet和TreeMap都是有序的数据结构,用于存储有序的元素。
TreeSet用于存储一组无序的、不重复的元素
而TreeMap用于存储键值对,并按照键的有序性进行排序。
如果只需要存储一组有序的、不重复的元素,可以选择TreeSet;
如果需要存储一个有序的映射关系,可以选择TreeMap。
二叉搜索树特性:红黑树是一种二叉搜索树,它满足二叉搜索树的特性,即左子树上的节点值小于节点值,右子树上的节点值大于节点值。
节点颜色:红黑树的每个节点都带有颜色属性,可以是红色或黑色。
根节点和叶子节点特性:根节点和叶子节点(空节点或NIL节点)都是黑色的。
红色节点特性:父节点是红色节点的子节点必须是黑色的。
黑色节点特性:从根节点到叶子节点的每条路径上都包含相同数量的黑色节点。
最长路径不超过两倍:由于红黑树保持平衡,任何节点到其子树中叶子节点的最长路径不会超过最短路径的两倍。这使得红黑树的查找、插入和删除等操作都能在相对较快的时间内完成。
HashMap数据结构:链表数组红黑树
数据库索引结构:MySQL中的InnoDB存储引擎就使用了红黑树来维护主键和唯一索引
对于普通的索引(非唯一索引和主键索引),MySQL使用B+树实现。B+树是一种多叉树,它在节点中存储了多个键值和对应的指针,且具有以下特性:
所有数据都存储在叶子节点中,内部节点只存储键值用于导航。
叶子节点通过链表连接,可以方便地遍历和区间查询。
内部节点根据键值分割子树,实现了高效的查询。
B+树在支持范围查询和顺序遍历等操作时非常高效。它也能够保证在大部分情况下,查询的复杂度为O(log n),其中n是索引数据的大小。
对于唯一索引或主键索引(包括聚簇索引),MySQL使用B树实现。B树和B+树类似,但在叶子节点存储的是键值和对应的数据,而不是仅存储键值。B树在保证有序性的同时,提供了更快的查找速度,因为存储了数据本身。
需要注意的是,虽然红黑树可以作为索引的实现之一,但在MySQL中,并不使用红黑树作为索引结构的实现方式。红黑树在其他应用中可能有用到,但在MySQL索引中的主要实现是B树和B+树。
B+树每一层存储元素更多,深度小,查询效率高,支持范围查询
假设B+树深度为m 每个节点大小为s
B+树每个内部节点至少有m/2个孩子节点,最多有m个孩子节点。而叶子节点存储数据,不存储孩子节点指针。
第一层的叶子节点数量为1个,第二层的叶子节点数量为m,第三层的叶子节点数量为m2。因此,三层B+树的叶子节点数量为m2。
假设一个数据项的大小为D,那么每个叶子节点可以存储的数据量为S = m * D
总的数据量 = 叶子节点数量 * 每个叶子节点的数据量 = (m^2) * (m * D) = m^3 * D
所以,三层B+树可以存储的数据量为 m^3 * D。
AVL树。AVL树也是一种自平衡的二叉搜索树
AVL树通过维护相对严格的平衡性来达到自平衡的目的。它在每个节点上存储一个平衡因子(Balance factor),表示该节点的左子树高度减去右子树高度的值。平衡因子可以为-1、0或1,如果平衡因子的绝对值超过1,就表示该树不平衡,需要通过相应的旋转操作进行调整。
相比之下,红黑树维护的平衡要求相对较宽松,并且通过节点的颜色属性进行平衡调整
AVL树的优点:
AVL树的缺点:
索引碎片是指数据库索引中的数据分布不连续或不紧凑,导致查询效率下降的情况。它通常由以下原因产生:
采取以下几种解决方法
三次握手:
在建立连接时,第一次握手,客户端给服务端发送一个SYN包,表示申请连接,第二次握手服务端接收到SYN包并向客户端发送一个SYN+ACK确认包表示接收连接,第三次握手客户端收到确认包后,向服务端发送ACK确认包,服务端收到ACK报文后,三次握手建立完成。
四次挥手:
1、第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。
2、第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。
3、第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
客户端发送一个SYN+ACK确认包表示接收连接,第三次握手客户端收到确认包后,向服务端发送ACK确认包,服务端收到ACK报文后,三次握手建立完成。
四次挥手:
1、第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。
2、第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT状态。
3、第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
4、第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态