redis 利用了 IO 多路复用机制,处理客户端请求时,不会阻塞主线程。redis 单纯执行指令时(大多数指令),一个指令不到 1 微秒,如此,单核 CPU1 秒就能处理一百万个指令(大概对应几十万个请求),单线程不会成为 redis 的性能瓶颈,网络才是瓶颈

优化网络延时

首先如果使用单机部署(应用服务和 redis 在同一台机器上)的话,使用 Unix 进程间通讯来请求 redis 服务,速度比 localhost 局域网(学名:loopback)更快。

但是很多公司的业务规模不是单机部署能够支撑的,所以还是得用 TCP。

redis 客户端和服务器的通讯一般使用 TCP 长连接。如果客户端发送请求后需要等待 redis 返回结果再发送下一条指令,客户端和 redis 的多个请求就如下图:

阅读全文 »

CAS 全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。java.util.concurrent 包中的原子类就是通过 CAS 来实现的

CAS 算法涉及到 3 个操作数:

  • 需要读写的内存值 V
  • 进行比较的值 A
  • 要写入的新值 B

当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值(”比较”+”更新”,整体是一个原子操作),否则不会执行任何操作。一般情况下,”更新”是一个不断重试的操作

AtomicInteger 源码:

阅读全文 »

MQ 的本质

MQ 本质都是[一发一存一消费]

生产者先将消息投递到一个叫做[队列]的容器中,队列存储消息之后,再转发给消费者

2021-04-07-22-22-1420210407222213

上面这个图便是消息队列最原始的模型,它包含了两个关键词:队列和消息

阅读全文 »

分库分表

分库分表之扩容

分库、分表、垂直拆分和水平拆分

  • 分库:因一个数据库支持的最高并发访问数是有限的,可以将一个数据库的数据拆分到多个库中,来增加最高访问数

  • 分表:因一个表的数据量太大,用索引来查询数据都搞不定了,所以可以将一张表的数据拆分到多张表,查询时只需要查拆分后的某一张表,SQL 语句的查询性能得到提升

  • 分库分表优势:分库分表后,承受的并发增加了多倍;磁盘使用率大大降低;单表数据量减少,sql 执行效率明显提升

  • 水平拆分:把一个表的数据拆分到多个数据库,每个数据库中的表结构不变。用多个库抗更高的并发。比如订单表每个月有 500W 条数据累计,每个月都可以进行水平拆分,将上个月的数据放到另外一个数据库中

  • 垂直拆分:把一个有很多字段的表,拆分成多张表到同一个库或者多个库上面。高频访问字段放到一张表,低频访问的字段放到另外一张表。利用数据库缓存来缓存高频访问的行数据。比如一张很多字段的订单表拆分成几张表分别存不同的字段(可以有冗余字段)

分库分表的方式

阅读全文 »

对于 MySQL 层面的优化一般遵循 5 个原则:

  • 减少数据访问:设置合理的字段类型,启用压缩,通过索引访问等减少磁盘 IO

  • 返回更少的数据:只返回需要的字段和数据分页处理,减少磁盘 IO 及网络 IO

  • 减少交互次数:批量 DML 操作,函数存储等减少数据连接次数

  • 减少服务器 CPU 开销:尽量减少数据库排序操作及全表查询,减少 CPU 内存占用

  • 利用更多资源:使用表分区,可以增加并行操作,更大限度利用 CPU 资源

总结到 SQL 优化中,就如下 3 点:

  • 最大化利用索引

  • 尽可能避免全表扫描

  • 减少无效数据的查询

理解 SQL 优化原理,首先要搞清楚 SQL 执行顺序

阅读全文 »

二叉查找树

二叉查找树,Binary Search Tree[BST],也称有序二叉树(ordered binary tree),排序二叉树(sorted binary tree),是指一棵空树或具有下列性质的二叉树

1.若任意节点的左子树不为空,则左子树上所有节点的值均小于它的根节点的值

2.若任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值

3.任意节点的左右子树也分别是二叉查找树

阅读全文 »

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
左旋
void leftRotate(Tree t, TreeNode x){
TreeNode y = x.right; // 取得x节点的右儿子赋值给y

x.right = y.left; // 将y的左儿子赋值给x的右儿子
y.left.parent = x; // 将y的左儿子的父节点变更成x

y.parent = x.parent; // 将x的父节点赋值给y的父节点
if(x.parent == null){
t.root = y;
}else if(x == x.parent.left){
x.parent.left = y;
}else{
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
插入

void RBInsert(Tree t, TreeNode z){
TreeNode y = null;
TreeNode x = t.root;
while(x != null){
y = x;
if(z.value < x.value){
x = x.left;
}else{
x = x.right;
}
}

z.parent = y;

if(y == null){
t.root = z;
}else if(z.value < y.value){
y.left = z;
}else{
y.right = z;
}

z.left = null;
z.right = null;
z.color = RED;
RBInsertFixUp(t,z);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
红黑树插入后调整

void RBInsertFixUp(Tree t,Node z){
while(z.parent.color == RED){
if(z.parent == z.parent.parent.left){
TreeNode y = z.parent.parent.right;
if(y.color == RED){
z.parent.color = BLACK;
y.color = BLACK;
z.parent.parent.color = RED;
z = z.parent.parent;
}else if(z == z.parent.right){
z = z.parent;
leftRotate(t,z);
z.parent.color = BLACK;
z.parent.parent.color = RED;
rightRotate(t,z.parent.parent);
}
}else{
// 同上,调转left和right
}
}
t.root.color = BLACK;

}

finally 什么时候执行

1.finally 语句在 return 语句执行之后,return 返回之前执行的

2.finally 中的 return 语句会覆盖 try 中的 return 语句

3.finally 里的修改语句可能影响也可能不影响 try 或 catch 里 return 已经确定的返回值

static/private/final static 哪些是线程安全的

spring 是如何实现 ioc 的

spring bean 什么时候初始化,什么时候销毁的

spring lazy-init 注解

lazy-init 是 Spring 中延迟加载 bean 的属性

设置为 lazy-init=true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,而是在第一次向容器通过 getBean 索取 bean 实例时实例化的

阅读全文 »

1.第一类丢失更新

撤销一个事务的时候,把其他事务已提交的更新数据覆盖了;这是完全没有事务隔离级别造成的;如果事务 1 被提交,另一个事务被撤销,那么会连同事务 1 所作的更新也被撤销

2.第二类丢失更新

当两个或多个事务查询相同的记录,然后各自基于查询的结果更新记录时,会造成第二类更新丢失的问题;每个事务不知道其他事务的存在,最后一个事务对记录所作的更改将覆盖其他事务之前对该记录的更改

3.脏读

阅读全文 »