0%

缓存击穿

原因

Redis中热点数据失效的瞬间,所有的请求都打到MySQL数据库上面,击穿了Redis的缓存,导致性能下降

image-20240218210704848

解决方案

  1. 设置热点数据永不过期
  2. 设置加锁队列(线程锁或分布式锁),先让一个线程去数据库查到数据,缓存到Redis中,然后其他线程再来Redis中取数据

缓存雪崩

原因

Redis中缓存的很多数据,在同一时刻失效,或Redis缓存服务器宕机,而导致请求全部打到MySQL上面,给MySQL造成巨大压力

image-20240218204959843

解决方案

  1. 将缓存数据的过期时间设为随机,避免其都在同一时刻失效
  2. Redis的高可用,集群+哨兵

缓存穿透

原因

查询的数据在数据库和缓存中都不存在,恶意请求数据,导致每次请求都要去查询数据,导致数据库压力过大或宕机

image-20240218211757292

解决方案

  1. 对请求参数进行校验(无法完全避免)
  2. 将数据库中不存在的数据也缓存到Redis中,避免每次收到数据库中进行查询
  3. 布隆过滤器

参考:

1.【趣话Redis第一弹】我是Redis,MySQL大哥被我坑惨了!

2.【阿里二面:说说你在项目中是怎么解决redis穿透、雪崩、击穿问题的?一口气说了17分钟。。】

AOP是什么

AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

利用AOP可对业务逻辑进行增强,在不改变原有逻辑的基础上,在其前后进行处理。降低了耦合性,减少了大量冗余的操作。特别适合用于大量方法都需要进行相同处理的操作。

AOP相关概念

![AOP](../images/SpringBoot AOP/32938e240b174d589359defc6a4828cf.png)

  • 切入点(Pointcut):被增强的方法。
  • 通知、增强方法(Advice):对目标方法的增强,有五种增强类型。
    • 环绕通知(@Around):内部执行方法,在方法执行的前后操作。
    • 前置通知(@Before):再发执行前执行。
    • 后置通知(@After):方法执行后执行。
    • 返回通知(@AfterReturning):方法返回之后执行。
    • 异常通知(@AfterThrowing):方法抛出异常后执行。
  • 切面(Aspect):切入点+通知(增强方法),一般是指被@Aspect修饰的类,代表着某一具体功能的AOP逻辑。

切入点的匹配方法:

表达式类型 功能
execution() 匹配方法,最全的一个
args() 匹配入参类型
@args() 匹配入参类型上的注解
@annotation() 匹配方法上的注解
within() 匹配类路径
@within() 匹配类上的注解
this() 匹配类路径,实际上AOP代理的类
target() 匹配类路径,目标类
@target() 匹配类上的注解

AOP原理

AOP的代理使用JDK动态代理和CGLIB代理来实现,默认如果目标对象是接口则使用JDK动态代理,否则使用CGLIB来生成动态代理类。

动态代理:程序在执行过程中使用JDK的反射机制,创建代理对象,并动态的指定要代理目标类。动态代理涉及的三个类:

  • InvocationHandler接口:处理器,负责调用目标方法(被代理类中的方法),并事项增强功能;通过代理类对象执行目标接口中的方法,会把方法的调用分配给调用处理器的事项类,执行实现类中的invoke()方法,我们需要把在该invoke()方法中实现调用目标类的目标方法;

  • Proxy 类:通过 JDK 的 java.lang.reflect.Proxy 类实现动态代理 ,使用其静态方法 newProxyInstance(),依据目标对象(被代理类的对象)、业务接口及调用处理器三者,自动生成一个动态代理对象。

  • Method 类:Method 是实例化的对象,有一个方法叫 invoke(),该方法在反射中就是用来执行反射对象的方法的。

编辑自定义的.service文件

/etc/systemd/system/ 目录下创建自定义的.service文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[Unit]
Description=sun-panel # 服务描述
After=syslog.target
After=network.target # 等待网络启动之后再启动此服务

[Service]
RestartSec=3s # 失败后等待 3 秒再尝试重启
Type=simple
User=root # 以指定的用户身份运行服务
Group=root
ExecStart=/root/sun-panel/sun-panel # 启动命令
WorkingDirectory=/root/sun-panel # 启动程序所在目录
Restart=always
Environment=USER=root HOME=/root/

[Install]
WantedBy=multi-user.target

注册服务

在系统中注册你的服务,这样系统每次开机都会自动启动你的服务,并且可以通过service命令来启动、停止和查看你的服务

1
systemctl enable your-service-name.service

启动服务

1
2
systemctl start your-service # 两种都可以
service your-service start

深拷贝和浅拷贝的区别

  • 浅拷贝:对于对象内的引用数据类型,浅拷贝会直接复制其对象的地址,也就是拷贝对象和原对象公用一个内部对象。
  • 深拷贝:会完全复制整个对象,包括对象所包含的内部对象,都是新创建的对象。

浅拷贝

浅拷贝的示例代码如下,我们这里实现了 Cloneable 接口,并重写了 clone() 方法。

clone() 方法的实现很简单,直接调用的是父类 Objectclone() 方法。

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
public class Address implements Cloneable{
private String name;
// 省略构造函数、Getter&Setter方法
@Override
public Address clone() {
try {
return (Address) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}

public class Person implements Cloneable {
private Address address;
// 省略构造函数、Getter&Setter方法
@Override
public Person clone() {
try {
Person person = (Person) super.clone();
return person;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}

测试:

1
2
3
4
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone();
// true
System.out.println(person1.getAddress() == person1Copy.getAddress());

从输出结构就可以看出, person1 的克隆对象和 person1 使用的仍然是同一个 Address 对象。

深拷贝

这里我们简单对 Person 类的 clone() 方法进行修改,连带着要把 Person 对象内部的 Address 对象一起复制。

1
2
3
4
5
6
7
8
9
10
@Override
public Person clone() {
try {
Person person = (Person) super.clone();
person.setAddress(person.getAddress().clone());
return person;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}

测试:

1
2
3
4
Person person1 = new Person(new Address("武汉"));
Person person1Copy = person1.clone();
// false
System.out.println(person1.getAddress() == person1Copy.getAddress());

从输出结构就可以看出,显然 person1 的克隆对象和 person1 包含的 Address 对象已经是不同的了。

那什么是引用拷贝呢? 简单来说,引用拷贝就是两个不同的引用指向同一个对象。

浅拷贝、深拷贝、引用拷贝示意图

参考:JavaGuide

两者的区别

基本类型 vs 包装类型

  • 用途:除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量。并且,包装类型可用于泛型,而基本类型不可以。
  • 存储方式:基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。
  • 占用空间:相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。
  • 默认值:成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null
  • 比较方式:对于基本数据类型来说,== 比较的是值。对于包装数据类型来说,== 比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用 equals() 方法。

为什么说是几乎所有对象实例都存在于堆中呢? 这是因为 HotSpot 虚拟机引入了 JIT 优化之后,会对对象进行逃逸分析,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换来实现栈上分配,而避免堆上分配内存

⚠️ 注意:基本数据类型存放在栈中是一个常见的误区! 基本数据类型的成员变量如果没有被 static 修饰的话(不建议这么使用,应该要使用基本数据类型对应的包装类型),就存放在堆中。

1
2
3
class BasicTypeVar{
private int x;
}
阅读全文 »

在ubuntu或debian下安装zerotier,并代理局域网所有流量

  1. 首先去zerotier官网按照其安装命令安装zerotier

    1
    curl -s https://install.zerotier.com | bash
  2. 加入zerotier虚拟局域网

    1
    zerotier-cli join <network id>
  3. 开启流量转发

    临时生效:

    1
    echo "1" >/proc/sys/net/ipv4/ip_forward

    永久生效:

    修改 /etc/sysctl.conf 文件,将 net.ipv4.ip_forward=1 注释去掉,并运行下列命令生效

    1
    sysctl -p
阅读全文 »

下载

1. 进入Redis官网

进入下载页面,找到下载按钮,右键复制下载链接

2. 下载Redis

1
wget https://github.com/redis/redis/archive/7.2.0.tar.gz -O redis.tar.gz

3. 解压压缩包

解压压缩包到opt目录

1
tar -xvf redis.tar.gz -C /opt
阅读全文 »

ubuntu server 安装时默认使用lvm进行磁盘管理,只使用磁盘空间的一半,如果要把另一半也用起来,需要如下处理:

1
2
3
4
# 利用以下方式将空间全部分给这个盘
sudo lvextend -l +100%FREE /dev/mapper/ubuntu–vg-ubuntu–lv
# 重新计算磁盘大小
sudo resize2fs /dev/mapper/ubuntu–vg-ubuntu–lv

其中/dev/mapper/ubuntu–vg-ubuntu–lv可以通过’df -h’查看

基本认识

TCP头格式

TCP 头格式

序列号:在建立连接时,由计算机生成的随机数作为初始值,通过SYN包传给接收端主机,每发送一次数据,就累加一次该「数据字节数」的大小,来解决网络包乱序的问题

确认应答号:指下次期望收到的数据序列号,发送端收到这个确认应答后可以认为在这个序号之前的数据都已经被正常接收,用来解决丢包问题

控制位

  • ACK:为1时,「确认应答」的字段变为有效,TCP规定除了最初建立连接时的SYN 包之外该位必须为1。
  • RST:为1时,表示TCP连接中出现异常,必须强制断开连接。
  • SYN:为1时,表示希望建立连接,并在其「序列号」的字段就行序列号初始值的设定。
  • FIN :为1时,表示之后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间相互交换FIN位位1的TCP段。
阅读全文 »