余声-个人博客


  • 首页

  • 分类

  • 归档

  • 标签

👌说说Spring常用的注解

发表于 2025-03-02 | 更新于 2025-06-22 | 分类于 Spring
字数统计 | 阅读时长

👌说说Spring常用的注解

口语化回答

好的,面试官,比较常用的就是Component,将类放到容器管理,autowired,resource,来装配 bean。还有就是 configuration 和 bean 进行配合,装载 bean 进入容器。以及一些扩展的注解比如 aop 的 aspect 切面,事务相关的 transsactional。以上。

题目解析

基本不会考,这个没啥意义,不排除小公司或应届会问到这个问题。

面试得分点

随便搞下面的 3~4 个注解说出来即可。

题目详细答案

核心注解

@Component:将一个类标记为 Spring 组件类,表示这个类的实例将由 Spring 容器管理。

@Autowired:自动注入依赖对象。可以用于构造器、字段、Setter 方法或其他任意方法。

@Qualifier:在有多个候选 Bean 时,指定要注入的 Bean。通常与@Autowired一起使用。

1
2
3
4
5
6
@Component
public class MyComponent {
@Autowired
@Qualifier("specificBean")
private Dependency dependency;
}

@Value:注入外部化的值,例如配置文件中的属性值、系统属性或环境变量。

1
2
3
4
5
@Component
public class MyComponent {
@Value("${my.property}")
private String myProperty;
}

@Configuration:标记一个类为 Spring 配置类,该类可以包含一个或多个@Bean方法。

1
2
3
4
5
6
7
@Configuration
public class AppConfig {
@Bean
public MyComponent myComponent() {
return new MyComponent();
}
}

@Bean:定义一个方法,返回一个要注册为 Spring 容器管理的 Bean。

1
2
3
4
@Bean
public MyComponent myComponent() {
return new MyComponent();
}

@ComponentScan:指定要扫描的包,以查找带有@Component、@Service、@Repository和@Controller注解的类,并将它们注册为 Spring Bean。

1
2
3
4
5
@Configuration
@ComponentScan(basePackages = "com.example")
public class AppConfig {
// 配置类内容
}

@Primary:标记一个 Bean 为首选 Bean,当有多个候选 Bean 时,Spring 会优先注入带有@Primary注解的 Bean。

1
2
3
4
5
@Component
@Primary
public class PrimaryBean implements Dependency {
// 实现
}

@Scope:指定 Bean 的作用域,例如单例(singleton)、原型(prototype)等。

1
2
3
4
5
@Component
@Scope("prototype")
public class PrototypeBean {
// 实现
}

特定用途注解

@Service:标记一个类为服务层组件,实际上是@Component的特殊化。

1
2
3
4
@Service
public class MyService {
// 服务逻辑
}

@Repository:标记一个类为数据访问层组件,实际上是@Component的特殊化,并且支持持久化异常转换。

1
2
3
4
@Repository
public class MyRepository {
// 数据访问逻辑
}

@Controller:标记一个类为 Spring MVC 控制器,实际上是@Component的特殊化

1
2
3
4
@Controller
public class MyController {
// 控制器逻辑
}

@RestController:标记一个类为 RESTful Web 服务的控制器,等同于@Controller和@ResponseBody的组合。

1
2
3
4
5
6
7
@RestController
public class MyRestController {
@GetMapping("/hello")
public String hello() {
return "Hello, World!";
}
}

AOP(面向切面编程)相关注解

@Aspect:标记一个类为切面类。

1
2
3
4
5
@Aspect
@Component
public class MyAspect {
// 切面逻辑
}

@Before:定义一个方法,在目标方法执行之前执行。

1
2
3
4
5
6
7
8
@Aspect
@Component
public class MyAspect {
@Before("execution(* com.example.service.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
// 前置逻辑
}
}

@After:定义一个方法,在目标方法执行之后执行。

1
2
3
4
5
6
7
8
@Aspect
@Component
public class MyAspect {
@After("execution(* com.example.service.*.*(..))")
public void afterMethod(JoinPoint joinPoint) {
// 后置逻辑
}
}

@Around:定义一个方法,包围目标方法的执行

1
2
3
4
5
6
7
8
9
@Aspect
@Component
public class MyAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 环绕逻辑
return joinPoint.proceed();
}
}

事务管理相关注解

@Transactional:标记一个方法或类,表示该方法或类中的所有方法都需要事务管理。

1
2
3
4
5
6
7
@Service
public class MyService {
@Transactional
public void performTransaction() {
// 事务逻辑
}
}

MyBatis拦截器实现分表

发表于 2025-02-22 | 更新于 2025-06-22 | 分类于 分表
字数统计 | 阅读时长

👌MyBatis拦截器实现分表

背景

所负责的业务中有一个 xx 系统,其中的 xx 核心表增长速度非常快,每日增长 200W 的数据量,业务特性主要是查询最近一周的数据,那么随着数据量越来越多,查询性能疯狂下降。这是典型的冷热数据场景,于是要开始进行冷热分离的操作。

常见的两种方案,一种是删除/归档旧数据,一种是数据分表。

方案选择

归档/删除旧数据

这种方式最常见的发难就是将冷数据移动到归档表或者直接进行删除,从而减少表的大小。

实现思路非常简单,一般写一个定时任务不断的跑就可以了。

缺点:

1、数据删除会影响数据库性能,引发慢sql,多张表并行删除,数据库压力会更大。

2、频繁删除数据,会产生数据库碎片,影响数据库性能,引发慢SQL。

有一定的风险,选择另一种分表的方案。

分表

业务特性是查最近的一周的数据,按日期的水平拆分是合理的。每周生成一张新表,同时此表只放本周的数据,这样表内数据量得到了控制,不会很大。

分表方案选型

分别常见的主要考虑2种分表方案:Sharding-JDBC、利用Mybatis自带的拦截器特性。

经过对比后,决定采用Mybatis拦截器来实现分表。

1、JAVA生态中很常用的分表框架是Sharding-JDBC,虽然功能强大,但需要一定的接入成本,并且很多功能暂时用不上。

2、系统本身已经在使用Mybatis了,只需要添加一个mybaits拦截器,把SQL表名替换为新的周期表就可以了,没有接入新框架的成本,开发成本也不高。

分表具体实现思路

1、通过拦截器获取表名

String tableName = TemplateMatchService.matchTableName(boundSql.getSql().trim());

2、根据数据日期进行计算后缀

3、替换原有 sql,最终执行。

String rebuildSql = boundSql.getSql().replace(shardingProperty.getTableName(), shardingTable);

metaStatementHandler.setValue(ORIGIN_BOUND_SQL, rebuildSql);

Linux中断机制是什么?

发表于 2025-02-20 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌Linux中断机制是什么?

Linux中断机制是操作系统处理硬件和软件事件的一种重要方式。中断可以使操作系统及时响应各种事件,如硬件设备的输入输出操作、定时器事件等。

中断的基本概念

1.中断类型

  • 硬件中断:由硬件设备(如键盘、网卡、硬盘等)发出的信号,引起CPU中断当前执行的程序,转而执行中断处理程序。
  • 软件中断:由软件指令(如系统调用、异常等)引发的中断。

2.中断向量表

中断向量表(Interrupt Vector Table, IVT)是一个包含中断服务程序地址的表。当中断发生时,CPU通过中断向量表找到相应的中断服务程序并执行。

中断处理流程

  1. 中断发生:硬件设备或软件事件发出中断信号。
  2. 中断响应:CPU停止当前执行的程序,保存当前的上下文(如程序计数器、寄存器等),并根据中断向量表找到对应的中断服务程序。
  3. 中断处理:执行中断服务程序,处理中断事件。
  4. 中断返回:中断处理完成后,恢复保存的上下文,继续执行被中断的程序。

中断处理的关键组件

1.中断控制器

中断控制器(如PIC、APIC)负责管理和分配中断请求。它可以屏蔽不需要的中断,并确定中断的优先级。

2.中断处理程序

中断处理程序(Interrupt Service Routine, ISR)是处理特定中断事件的函数。它通常分为顶半部(Top Half)和底半部(Bottom Half):

  • 顶半部:快速响应中断,执行紧急的处理任务。
  • 底半部:延迟执行较长时间的任务,以减少顶半部的执行时间,避免阻塞其他中断。

3.中断上下文

中断上下文是指中断发生时CPU的状态,包括程序计数器、寄存器等。保存和恢复中断上下文是中断处理的关键步骤。

中断处理的实现

1.注册中断处理程序

在Linux内核中,可以使用request_irq函数注册中断处理程序:

1
2
3
#include <linux/interrupt.h>

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

2.中断处理程序示例

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
30
31
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>

static irqreturn_t my_interrupt_handler(int irq, void *dev_id) {
printk(KERN_INFO "Interrupt occurred!\n");
// 中断处理逻辑
return IRQ_HANDLED;
}

static int __init my_module_init(void) {
int irq = 1; // 假设中断号为1
int result = request_irq(irq, my_interrupt_handler, IRQF_SHARED, "my_interrupt", NULL);
if (result) {
printk(KERN_ERR "Failed to request IRQ\n");
return result;
}
return 0;
}

static void __exit my_module_exit(void) {
int irq = 1; // 假设中断号为1
free_irq(irq, NULL);
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A simple interrupt handler module");
MODULE_AUTHOR("Your Name");

3.顶半部和底半部

  • 顶半部:快速处理中断的关键部分。
  • 底半部:使用任务队列、工作队列或软中断机制延迟处理较长时间的任务。

中断的优先级和屏蔽

中断控制器可以设置中断的优先级和屏蔽某些中断,以确保重要的中断优先得到处理。APIC(Advanced Programmable Interrupt Controller)提供了更高级的中断管理功能,如多级中断优先级和分布式中断处理。

中断的嵌套和重入

中断处理程序应尽量短小精悍,以减少中断嵌套的层数。中断处理程序应避免使用可能导致阻塞的操作,如长时间的I/O操作和锁定操作。

原文: https://www.yuque.com/jsyun/vwxwc6

Linux查看文件磁盘占用情况

发表于 2025-02-20 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌Linux查看文件磁盘占用情况?

1.du命令

du(disk usage)命令用于估算文件和目录的磁盘使用情况。

基本用法

1
du [选项] [文件或目录]

常用选项

  • -h:以人类可读的格式显示(例如,K、M、G)。
  • -s:显示总计(不显示子目录的详细信息)。
  • -a:包括所有文件和目录。
  • -c:显示总计。
  • --max-depth=N:限制显示的目录层级深度。

示例

  • 查看当前目录下每个文件和子目录的磁盘使用情况:
1
du -h
  • 查看指定目录的总磁盘使用情况:
1
du -sh /path/to/directory
  • 查看指定目录及其子目录的磁盘使用情况,限制深度为1:
1
du -h --max-depth=1 /path/to/directory

2.df命令

df(disk free)命令用于查看文件系统的磁盘使用情况。

基本用法

1
df [选项] [文件或目录]

常用选项

  • -h:以人类可读的格式显示(例如,K、M、G)。
  • -T:显示文件系统类型。
  • -i:显示inode使用情况。

示例

  • 查看所有挂载的文件系统的磁盘使用情况:
1
df -h
  • 查看特定目录所在的文件系统的磁盘使用情况:
1
df -h /path/to/directory

3.ncdu工具

ncdu(NCurses Disk Usage)是一个基于ncurses的磁盘使用分析工具,提供了交互式界面。

安装

在Debian/Ubuntu系统上:

1
sudo apt-get install ncdu

在CentOS/RHEL系统上:

1
sudo yum install ncdu

使用

1
ncdu /path/to/directory

4.ls命令

ls命令也可以显示文件的大小,但不如du详细。

示例

  • 以人类可读的格式显示文件大小:
1
ls -lh
  • 显示目录的总大小:
1
ls -lhS

5.find命令结合du

可以使用find命令查找特定条件的文件,然后结合du命令查看它们的磁盘使用情况。

示例

  • 查找大于100MB的文件并显示其磁盘使用情况:
1
find /path/to/directory -type f -size +100M -execdu -h {} +

Linux死锁是什么?

发表于 2025-02-20 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌Linux死锁是什么?

在Linux操作系统中,死锁(Deadlock)是指两个或多个进程在等待彼此释放资源,从而导致它们都无法继续执行的情况。这是一种常见的并发问题,尤其在多线程或多进程环境中。

死锁的四个必要条件

死锁的发生需要满足以下四个条件(也称为Coffman条件):

  1. 互斥条件(Mutual Exclusion):至少有一个资源是非共享的,即一次只能被一个进程使用。
  2. 占有并等待条件(Hold and Wait):一个进程已经持有了至少一个资源,并且正在等待获取其他被其他进程持有的资源。
  3. 不剥夺条件(No Preemption):资源不能被强制从进程中剥夺,只有持有资源的进程可以主动释放资源。
  4. 环路等待条件(Circular Wait):存在一个进程链,使得每个进程都在等待链中下一个进程所持有的资源。

死锁的检测和预防

为了处理死锁问题,可以采取以下几种策略:

1.预防死锁

预防死锁的方法包括破坏上述四个条件之一:

  • 破坏互斥条件:尽可能减少对非共享资源的使用。
  • 破坏占有并等待条件:在进程开始时一次性分配所有需要的资源,或者要求进程在请求资源前释放所有持有的资源。
  • 破坏不剥夺条件:允许操作系统强制剥夺某些资源。
  • 破坏环路等待条件:对资源进行排序,并要求进程按顺序请求资源。

2.避免死锁

避免死锁的方法主要是通过资源分配策略来确保系统永远不会进入死锁状态。常用的方法包括银行家算法(Banker’s Algorithm),它通过模拟资源分配情况来判断是否会导致死锁。

3.检测和恢复

如果无法预防或避免死锁,可以通过检测和恢复来处理死锁:

  • 死锁检测:定期检查系统中是否存在死锁。可以使用资源分配图(Resource Allocation Graph)来检测环路,从而识别死锁。
  • 死锁恢复:一旦检测到死锁,可以通过以下方式恢复:
    • 终止进程:强制终止一个或多个进程以打破死锁。
    • 资源剥夺:强制从某些进程中剥夺资源并重新分配。

linux 内存有问题怎么查看

发表于 2025-02-20 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌linux 内存有问题怎么查看?

如果怀疑内存有问题,可以通过以下步骤进行检查和诊断:

1. 使用free命令查看内存使用情况

free命令可以显示系统的内存使用情况,包括总内存、已使用内存、空闲内存和缓存等信息。

1
free -h

输出示例:

1
2
3
total        used        free      shared  buff/cache   available
Mem: 7.8G 3.2G 1.5G 200M 3.1G 4.1G
Swap: 2.0G 0.0K 2.0G

2. 使用top或htop命令查看内存使用情况

top和htop命令可以实时显示系统的资源使用情况,包括内存使用情况。

  • top命令:
1
top
  • htop命令(需要安装):
1
2
sudo apt-get install htop
htop

3. 使用vmstat命令查看内存和系统性能

vmstat命令可以报告虚拟内存、进程、CPU 活动等信息。

1
vmstat 1 5

输出示例:

1
2
3
4
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 1576828 72464 2192160 0 0 8 11 64 150 1 0 99 0 0
0 0 0 1576820 72464 2192160 0 0 0 0 59 144 0 0 100 0 0

4. 检查内存使用情况的日志

查看系统日志,如/var/log/syslog或/var/log/messages,看看是否有与内存相关的错误或警告。

1
2
sudo grep -i memory /var/log/syslog
sudo grep -i memory /var/log/messages

linux的中断机制用来干什么

发表于 2025-02-20 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌linux的中断机制用来干什么?

Linux的中断机制用于处理各种硬件和软件事件,使操作系统能够及时响应并处理这些事件。中断机制在操作系统中起着至关重要的作用。

1.硬件设备的事件处理

硬件设备通过中断机制通知操作系统它们的状态变化或需要处理的事件。例如:

  • 键盘输入:当键盘按键被按下时,键盘控制器会发出中断信号,通知CPU读取按键值。
  • 网络数据接收:网卡接收到数据包时,会触发中断,通知操作系统处理接收到的数据。
  • 硬盘I/O完成:硬盘完成读写操作后,会发出中断信号,通知操作系统该操作已完成,可以继续处理数据。

2.定时器和时钟管理

操作系统使用定时器中断来管理系统时钟和时间片轮转。例如:

  • 系统时钟更新:定时器会定期触发中断,操作系统通过这些中断更新系统时钟。
  • 时间片轮转:在多任务操作系统中,定时器中断用于实现时间片轮转调度,确保各个进程能够公平地获得CPU时间。

3.系统调用和异常处理

软件中断(如系统调用和异常)允许用户空间程序与内核进行交互,并处理异常情况。例如:

  • 系统调用:用户程序通过触发软件中断进入内核态,执行系统调用以请求操作系统服务(如文件操作、进程管理等)。
  • 异常处理:当程序发生异常(如除零错误、非法内存访问等)时,CPU会触发异常中断,操作系统捕获并处理这些异常。

4.中断驱动的I/O操作

中断机制使得设备驱动程序可以实现高效的I/O操作。例如:

  • 非阻塞I/O:通过中断通知,设备驱动程序可以在设备准备好时处理I/O操作,而无需进程持续轮询设备状态,从而提高系统效率。

5.电源管理

中断机制在电源管理中也扮演重要角色。例如:

  • 电池状态变化:当电池电量低或充电完成时,电源管理芯片会触发中断,通知操作系统采取相应的措施(如节能模式或提示用户)。

6.多核处理器的中断分配

在多核处理器系统中,中断控制器(如APIC)可以将中断分配到不同的CPU核,以实现负载均衡和提高并行处理能力。

原文: https://www.yuque.com/jsyun/vwxwc6

优惠券秒杀

发表于 2025-02-16 | 更新于 2025-06-22 | 分类于 场景题
字数统计 | 阅读时长

一、前置校验(redis的lua脚本实现)

1、检查库从是否充足
2、用户是否下单过该优惠券(使用Redis中的set类型来缓存下单该优惠券的用户id集合,并且要保证数据及时更新同步,即 在检验资格通过后需要向set中添加用户id)
Lua脚本需要的ARGV参数列表中有两个待定参数,分别是优惠券id 以及 用户id

二、处理业务

1、资格检验通过,则需要保证该有效订单被阻塞队列拿到,后续阻塞式执行成功,所以将“凭证”(封装好用户id、券id、订单id的订单实例)传入阻塞队列,等待异步线程阻塞式读取处理下单业务;

如果不让你用看门狗,你会用什么方案来解决重复支付问题?

发表于 2025-02-16 | 更新于 2025-06-22 | 分类于 场景题
字数统计 | 阅读时长

👌如果不让你用看门狗,你会用什么方案来解决重复支付问题?

1. 幂等性令牌(Idempotency Token)

原理:在每次支付请求中,客户端生成一个唯一的幂等性令牌,并将其发送给服务器。服务器在处理支付请求时,会检查这个令牌是否已经被使用过。如果令牌已经被使用,服务器会拒绝重复的支付请求。

实现步骤:

  1. 客户端生成一个唯一的幂等性令牌(如 UUID)。
  2. 客户端将令牌和支付请求一起发送给服务器。
  3. 服务器检查令牌是否已经存在于数据库中。
  4. 如果令牌不存在,服务器处理支付请求并将令牌存储在数据库中。
  5. 如果令牌已经存在,服务器拒绝支付请求或者返回之前的处理结果。

示例:

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
public class PaymentService {

private PaymentRepository paymentRepository;

public PaymentService(PaymentRepository paymentRepository) {
this.paymentRepository = paymentRepository;
}

public void processPayment(PaymentRequest request) throws DuplicatePaymentException {
String antiReplayToken = request.getAntiReplayToken();

if (paymentRepository.existsByAntiReplayToken(antiReplayToken)) {
throw new DuplicatePaymentException("Duplicate payment detected for anti-replay token: " + antiReplayToken);
}

// 处理支付逻辑
Payment payment = new Payment();
payment.setAntiReplayToken(antiReplayToken);
payment.setAmount(request.getAmount());
payment.setStatus("processing");

paymentRepository.save(payment);

// 完成支付后更新状态
payment.setStatus("completed");
paymentRepository.save(payment);
}
}

2. 数据库唯一约束(不推荐)

原理:在数据库中为每个支付请求生成一个唯一的交易 ID,并在数据库中设置唯一约束(Unique Constraint)来防止重复记录。

实现步骤:

  1. 在支付请求中生成一个唯一的交易 ID。
  2. 在数据库中保存支付记录时,使用交易 ID 作为唯一键。
  3. 如果尝试插入重复的交易 ID,数据库会抛出唯一约束异常,从而防止重复支付。

示例代码(伪代码):

1
2
3
4
5
6
7
8
9
CREATE TABLE payments (
id SERIAL PRIMARY KEY,
transaction_id VARCHAR(255) UNIQUE NOT NULL,
amount DECIMAL(10, 2),
status VARCHAR(50)
);

-- 插入支付记录时
INSERT INTO payments (transaction_id, amount, status) VALUES ('unique_transaction_id', 100.00, 'pending');

3. 分布式锁

原理:使用分布式锁来确保同一时间只有一个支付请求能够被处理。可以使用 Redis、ZooKeeper 等工具实现分布式锁。

实现步骤:

  1. 在处理支付请求前,尝试获取分布式锁。
  2. 如果获取锁成功,处理支付请求。
  3. 如果获取锁失败,拒绝支付请求或等待重试。
  4. 支付处理完成后,释放分布式锁。

示例:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

@Autowired
private RedissonClient redissonClient;

@Autowired
private PaymentRepository paymentRepository;

public void processPayment(PaymentRequest request) throws DuplicatePaymentException {
String transactionId = request.getTransactionId();
RLock lock = redissonClient.getLock("payment_lock_" + transactionId);

try {
if (lock.tryLock(10, 10, TimeUnit.SECONDS)) {
if (paymentRepository.existsByTransactionId(transactionId)) {
throw new DuplicatePaymentException("Duplicate payment detected for transaction ID: " + transactionId);
}

// 处理支付逻辑
Payment payment = new Payment();
payment.setTransactionId(transactionId);
payment.setAmount(request.getAmount());
payment.setStatus("processing");

paymentRepository.save(payment);

// 完成支付后更新状态
payment.setStatus("completed");
paymentRepository.save(payment);
} else {
throw new RuntimeException("Could not acquire lock for transaction ID: " + transactionId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Lock acquisition interrupted", e);
} finally {
lock.unlock();
}
}
}

4. 事务和锁机制

原理:使用数据库事务和锁机制来确保支付操作的原子性和一致性。

实现步骤:

  1. 在数据库中为支付操作创建一个事务。
  2. 在事务中获取支付记录的锁,防止其他事务同时修改相同的记录。
  3. 执行支付操作并提交事务。
  4. 如果在支付操作中发生冲突,回滚事务并重试。

5. 状态机

原理:使用状态机来管理支付请求的状态,确保每个支付请求只能从一个状态转移到另一个状态,避免重复处理。

实现步骤:

  1. 定义支付请求的状态(如pending、processing、completed、failed)。
  2. 在处理支付请求时,根据当前状态进行状态转移。
  3. 使用数据库事务确保状态转移的原子性。

总结

以上方法都可以有效地防止重复支付,具体选择哪种方案取决于系统的架构和需求。幂等性令牌和数据库唯一约束是比较常见且易于实现的方法,而分布式锁和状态机适用于更复杂的分布式系统。无论选择哪种方案,都需要确保支付操作的幂等性和一致性。

原文: https://www.yuque.com/jingdianjichi/xyxdsi/cf41s0446a6gokgr

看门狗机制可以防止重复支付?

发表于 2025-02-16 | 更新于 2025-06-22 | 分类于 场景题
字数统计 | 阅读时长

👌看门狗机制可以防止重复支付?

看门狗机制(Watchdog Mechanism)并不是直接用于防止重复支付的常见技术,但它可以在一定程度上帮助监控和管理支付流程,确保支付操作的可靠性和安全性。

看门狗机制的基本原理

看门狗机制通常是一个定期检查系统状态或操作完成情况的过程。如果系统在规定时间内没有响应或完成特定操作,看门狗机制会触发预定义的恢复或补救措施。它类似于一种“守护进程”,确保系统在正常运行。

在支付系统中的应用

在支付系统中,看门狗机制可以用于监控支付请求的处理状态,确保支付请求在合理的时间内完成,并防止由于网络延迟或系统故障导致的重复支付问题。

实现步骤

  1. 支付请求的状态管理:
    • 每个支付请求都有一个唯一的标识符(如交易ID)。
    • 支付请求在数据库中有明确的状态(如pending、processing、completed、failed)。
  2. 看门狗机制的监控:
    • 看门狗进程定期检查支付请求的状态,确保所有支付请求在合理的时间内从pending转移到completed或failed状态。
    • 如果发现某个支付请求长时间停留在pending或processing状态,看门狗机制会触发相应的处理逻辑。
  3. 处理逻辑:
    • 如果支付请求长时间处于pending状态,看门狗机制可以重试支付请求或将其标记为failed,以防止重复支付。
    • 如果支付请求长时间处于processing状态,看门狗机制可以检查支付处理是否卡住,并尝试重启处理流程或进行人工干预。

通过看门狗机制,可以监控支付请求的处理状态,确保支付操作在合理时间内完成,防止由于网络延迟或系统故障导致的重复支付问题。尽管看门狗机制不是直接防止重复支付的工具,但它可以作为一种辅助措施,确保支付系统的可靠性和安全性。

原文: https://www.yuque.com/jingdianjichi/xyxdsi/getdtvffwg1agghx

锁库存如果不用redis 还能怎么实现?

发表于 2025-02-16 | 更新于 2025-06-22 | 分类于 场景题
字数统计 | 阅读时长

👌锁库存如果不用redis 还能怎么实现?

锁库存是电商系统中常见的需求,特别是在高并发场景下,需要确保库存的准确性和一致性。记住 Redis 是一种常见的解决方案,如果面试官非问这种,就答下面这些方案。

1. 数据库乐观锁

乐观锁是一种不使用数据库锁机制的锁定方式。它假设冲突很少发生,因此在更新数据时进行冲突检测。

实现方法:

  1. 添加版本号: 在库存表中添加一个version字段,用于记录数据的版本。
  2. 更新时检查版本号: 在更新库存时,通过version字段来确保数据没有被其他事务修改。

示例:

1
2
3
UPDATE inventory
SET stock = stock - 1, version = version + 1
WHERE product_id = ? AND version = ?;

如果更新成功,说明库存锁定成功;否则,说明有并发修改,需要重试。

2. 数据库悲观锁(不推荐)

悲观锁是一种锁定机制,确保在事务完成之前,其他事务无法访问被锁定的数据。

实现方法:

  1. 使用**SELECT ... FOR UPDATE**: 在事务中使用SELECT ... FOR UPDATE语句锁定行,直到事务提交或回滚。

示例 SQL:

1
2
3
4
5
6
7
8
9
10
11
12
13
BEGIN;

SELECT stock
FROM inventory
WHERE product_id = ?
FOR UPDATE;

-- 检查库存并更新
UPDATE inventory
SET stock = stock - 1
WHERE product_id = ?;

COMMIT;

悲观锁适用于冲突较多的场景,但可能会导致数据库锁争用,影响性能。

3. 分布式锁

分布式锁用于在分布式系统中确保资源的独占访问。可以使用 Zookeeper、Etcd 等工具实现分布式锁。

实现方法:

  1. 使用 Zookeeper: 利用 Zookeeper 的临时节点和顺序节点特性,实现分布式锁。

示例使用 Curator 框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CuratorFramework client = CuratorFrameworkFactory.newClient("zkServer:2181", new ExponentialBackoffRetry(1000, 3));
client.start();

InterProcessMutex lock = new InterProcessMutex(client, "/inventory_lock");
try {
if (lock.acquire(10, TimeUnit.SECONDS)) {
try {
// 处理库存
} finally {
lock.release();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
client.close();
}

4. 基于消息队列的异步处理(可用)

通过消息队列,将库存操作异步化,避免并发冲突。

实现方法:

  1. 消息队列: 使用 Kafka、RabbitMQ 等消息队列,将库存操作请求发送到队列中。
  2. 消费者处理: 消费者从队列中读取消息,顺序处理库存操作。

示例:

1
2
3
4
5
6
7
8
// 生产者
rabbitTemplate.convertAndSend("inventoryQueue", new InventoryMessage(productId, quantity));

// 消费者
@RabbitListener(queues = "inventoryQueue")
public void handleInventory(InventoryMessage message) {
// 处理库存
}

5. 数据库事务(不要用不要用)

通过数据库事务,确保库存操作的原子性和一致性。

实现方法:

  1. 事务管理: 在业务逻辑中使用数据库事务,确保库存操作的原子性。

示例代码(使用 Spring 事务管理):

1
2
3
4
5
6
7
8
9
10
11
@Transactional
public void updateInventory(Long productId, int quantity) {
// 检查库存
int stock = inventoryMapper.getStock(productId);
if (stock >= quantity) {
// 更新库存
inventoryMapper.updateStock(productId, stock - quantity);
} else {
throw new InsufficientStockException();
}
}

总结

锁库存的实现方式有很多种,选择合适的方案需要根据具体的业务需求和技术栈来决定。乐观锁和悲观锁适用于单体应用和简单的分布式场景;分布式锁适用于复杂的分布式系统;基于消息队列的异步处理适用于高并发、高吞吐量的场景;数据库事务适用于需要确保操作原子性的场景。每种方案都有其优缺点,需要综合考虑性能、复杂度和一致性要求。

原文: https://www.yuque.com/jingdianjichi/xyxdsi/og4as3nog7ss4mse

1000w条数据插入mysql如何设计?

发表于 2025-02-15 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌1000w条数据插入mysql如何设计?

1. 使用批量插入(Bulk Insert)

批量插入可以显著减少 SQL 语句的开销和网络传输的负担。可以使用INSERT INTO ... VALUES语句一次插入多条记录。

示例:

1
2
3
4
5
INSERT INTO your_table (column1, column2, column3) VALUES
('value1', 'value2', 'value3'),
('value4', 'value5', 'value6'),
...
('valueN1', 'valueN2', 'valueN3');

2. 禁用或延迟索引和约束(不到万不得已,别搞,不推荐)

在插入大量数据之前,可以暂时禁用或延迟索引和外键约束,以减少插入过程中的开销。插入完成后再重新启用索引和约束。

禁用外键检查:

1
SET foreign_key_checks =0;

禁用唯一检查:

1
SET unique_checks =0;

3. 调整 MySQL 配置

确保 MySQL 配置足够支持大批量数据插入。

增大缓冲区大小:

1
2
3
[mysqld]
innodb_buffer_pool_size = 1G # 根据你的内存大小调整
innodb_log_buffer_size = 64M

调整事务日志大小:

1
2
3
[mysqld]
innodb_log_file_size = 512M
innodb_log_files_in_group = 2

4. 使用事务

将大量插入操作分批放入事务中,可以减少每次插入的提交开销。如果数据量非常大,可以每 1000 或 10000 条记录提交一次。换成代码使用就是 transactiontemplate来手动控制事务提交

示例:

1
2
3
4
5
6
7
8
9
START TRANSACTION;

INSERT INTO your_table (column1, column2, column3) VALUES
('value1', 'value2', 'value3'),
('value4', 'value5', 'value6'),
...
('value1000', 'value1001', 'value1002');

COMMIT;

5. 使用分区表

如果数据量非常大且有分区需求,可以考虑使用 MySQL 的分区表功能,将数据按某个字段(如日期、ID 等)进行分区,提升查询和插入性能。

6. 数据导入工具

对于非常大的数据集,可以使用 MySQL 提供的工具如mysqlimport或者第三方工具如mydumper/myloader进行数据导入,这些工具通常针对大数据集做了优化。

redis滑动窗口限流怎么做的

发表于 2025-02-15 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌Redis滑动窗口限流怎么做的说一下?

Redis的滑动窗口限流是一种基于时间窗口的限流策略,用于控制在特定时间范围内的请求数量。其基本思想是将时间划分为多个小的子窗口,通过记录每个子窗口内的请求数量来实现限流。与固定窗口限流相比,滑动窗口限流可以更平滑地限制请求速率,减少突发流量对系统的影响。

基本概念

  1. 时间窗口:滑动窗口限流将时间划分为多个小的子窗口。例如,要限制每秒最多10个请求,可以将1秒划分为10个子窗口,每个子窗口100毫秒。
  2. 记录请求时间戳:使用Redis的有序集合(Sorted Set)记录请求的时间戳。
  3. 计算当前窗口内的请求数量:每次有新请求到来时,移除过期的时间戳,计算当前窗口内的请求数量。
  4. 判断是否限流:如果当前窗口内的请求数量超过限流阈值,则拒绝请求;否则,允许请求并记录时间戳。

实现步骤

  1. 确定时间窗口和子窗口大小。
  2. 使用Redis有序集合记录请求时间戳。
  3. 计算当前窗口内的请求数量。
  4. 判断是否限流。

示例

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

import java.util.List;

public class SlidingWindowRateLimiter {
private Jedis jedis;
private String key;
private int windowSizeInMillis;
private int limit;

public SlidingWindowRateLimiter(Jedis jedis, String key, int windowSizeInMillis, int limit) {
this.jedis = jedis;
this.key = key;
this.windowSizeInMillis = windowSizeInMillis;
this.limit = limit;
}

public boolean isAllowed() {
long currentTimeMillis = System.currentTimeMillis();
long windowStart = currentTimeMillis - windowSizeInMillis;

Transaction transaction = jedis.multi();
transaction.zremrangeByScore(key, 0, windowStart); // 移除过期的时间戳
transaction.zcard(key); // 获取当前窗口内的请求数量
transaction.zadd(key, currentTimeMillis, String.valueOf(currentTimeMillis)); // 添加当前请求的时间戳
transaction.expire(key, windowSizeInMillis / 1000 * 2); // 设置过期时间
List<Object> results = transaction.exec();

long currentCount = (Long) results.get(1); // 获取当前窗口内的请求数量

return currentCount <= limit;
}

public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
SlidingWindowRateLimiter limiter = new SlidingWindowRateLimiter(jedis, "rate_limiter_key", 1000, 10);

if (limiter.isAllowed()) {
System.out.println("请求被允许");
} else {
System.out.println("请求被拒绝");
}

jedis.close();
}
}

详细解释

  1. 初始化:
    • SlidingWindowRateLimiter类的构造函数接受jedis(Jedis客户端实例)、key(Redis键)、windowSizeInMillis(时间窗口大小,以毫秒为单位)和limit(限流阈值)。
  2. isAllowed 方法:
    • 获取当前时间戳(毫秒)。
    • 计算窗口开始时间(当前时间减去窗口大小)。
    • 使用Redis事务(Transaction)执行以下操作:
      • zremrangeByScore:移除有序集合中所有时间戳小于窗口开始时间的元素(即过期的时间戳)。
      • zcard:获取当前窗口内的请求数量。
      • zadd:添加当前请求的时间戳到有序集合中。
      • expire:设置Redis键的过期时间,防止长期不活跃时占用Redis内存。
    • 执行事务并获取结果。
    • 检查当前窗口内的请求数量是否超过限流阈值,决定是否允许请求。
  3. main 方法:
    • 创建Jedis客户端实例并连接到Redis服务器。
    • 创建SlidingWindowRateLimiter实例并调用isAllowed方法来检查请求是否被允许。
    • 关闭Jedis客户端。

优点

  • 平滑限流:滑动窗口限流相比固定窗口限流更加平滑,能够更好地应对突发流量。
  • 精确控制:可以精确控制特定时间窗口内的请求数量。

缺点

  • 复杂度较高:实现相对复杂,需要精确管理时间戳和窗口。
  • 性能开销:频繁的Redis操作可能会带来一定的性能开销。

for循环调数据库有什么风险

发表于 2025-02-15 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌for循环调数据库有什么风险

1. 性能问题

风险:

  • 高延迟:每次数据库调用都涉及网络通信和数据库处理时间,频繁调用会导致整体延迟增加。
  • 低效率:大量的小批量操作比单次大批量操作效率低,增加了数据库的负载。

应对措施:

  • 批量操作:尽量将多个操作合并为一个批量操作,如使用批量插入、批量更新等。
  • 缓存:在内存中缓存数据,减少数据库访问次数。
  • 异步处理:使用异步处理或消息队列,将数据库操作分散到后台任务中执行。

2. 资源消耗

风险:

  • 连接耗尽:频繁的数据库连接和断开会消耗大量的数据库连接资源,可能导致连接池耗尽。
  • 内存和CPU负载:大量的数据库操作会增加数据库服务器的内存和CPU负载,影响其他操作的性能。

应对措施:

  • 连接池优化:使用连接池并适当配置连接池参数,以减少连接创建和销毁的开销。
  • 合并请求:将多个查询合并为一个查询,减少数据库连接次数。

3. 事务处理复杂性

风险:

  • 事务管理困难:在for循环中处理事务可能导致事务管理复杂化,增加了出错的可能性。
  • 锁争用:长时间持有锁或频繁锁定资源可能导致锁争用,影响系统性能。

应对措施:

  • 事务分段:将大事务分解为多个小事务,减少单个事务的持有时间。
  • 乐观锁和悲观锁:根据具体情况选择合适的锁策略,避免不必要的锁争用。

4. 数据一致性

风险:

  • 部分失败:for循环中的某些操作可能失败,导致数据不一致。
  • 并发问题:多个并发操作可能导致数据竞态条件,影响数据一致性。

应对措施:

  • 重试机制:实现重试机制,确保操作在失败时能重新执行。
  • 并发控制:使用合适的并发控制策略,如乐观锁、悲观锁或分布式锁,确保数据一致性。

5. 可维护性和可读性

风险:

  • 代码复杂:在for循环中嵌入大量的数据库操作代码,会使代码变得复杂且难以维护。
  • 错误处理困难:错误处理逻辑复杂,增加了代码的维护成本。

应对措施:

  • 抽象和封装:将数据库操作抽象和封装到独立的方法或类中,提高代码的可读性和可维护性。
  • 日志和监控:添加日志记录和监控,方便排查问题。

mysql中已经存在的表,加索引,会不会产生锁表

发表于 2025-02-15 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌mysql中已经存在的表,加索引,会不会产生锁表?

在线DDL添加索引

在MySQL 5.5及更早版本,InnoDB不支持在线DDL操作,添加索引会锁定表,阻止任何读写操作,直到索引创建完成。

在MySQL 5.6及以上版本中,InnoDB存储引擎支持在线添加索引,这意味着在大多数情况下,表仍然可以被读取和写入。只有在索引创建的初始和最终阶段会短暂锁定表。

使用方法

要在线添加索引,你可以使用标准的ALTER TABLE语句。MySQL会自动决定是否可以在线完成操作。

1
ALTERTABLE table_name ADD INDEX index_name (column_name);

行为

  • 初始和最终阶段锁定:在索引创建的开始和结束阶段,表会被短暂锁定。
  • 中间阶段无锁定:在索引创建的中间阶段,表可以继续进行读写操作。

使用Percona Toolkit的pt-online-schema-change(主流)

对于一些复杂的DDL操作或者在不支持在线DDL的环境中,可以使用Percona Toolkit的pt-online-schema-change工具进行在线模式变更。这是一个强大的工具,可以在不中断服务的情况下进行DDL操作。

1
pt-online-schema-change --alter "ADD INDEX idx_last_name (last_name)" D=database_name,t=employees --execute

注意事项

  1. 性能影响:虽然在线DDL操作不会完全锁定表,但仍然会对性能产生一定影响,特别是在高负载的环境中。
  2. 空间需求:在线添加索引可能需要额外的磁盘空间,因为MySQL会创建一个临时表来构建新的索引。
  3. 版本要求:确保你的MySQL版本在5.6及以上,并且使用InnoDB存储引擎,以支持在线DDL操作。

redis什么情况下会变慢

发表于 2025-02-15 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌redis什么情况会变慢?

1. 内存不足

原因

  • Redis 是一个内存数据库,当数据量超过可用内存时,系统可能会开始交换内存到磁盘,这会显著降低性能。

解决方法

  • 确保系统有足够的内存。
  • 使用 Redis 的内存淘汰策略(如 LRU、LFU)来管理内存。
  • 考虑使用 Redis 集群来分散内存负载。

2. 大量慢查询

原因

  • 慢查询会阻塞 Redis 的事件循环,导致其他请求变慢。

解决方法

  • 使用SLOWLOG命令监控和优化慢查询。
  • 尽量避免在 Redis 中执行复杂的查询操作。

3. 网络延迟

原因

  • 网络延迟会增加请求和响应之间的时间,导致整体性能下降。

解决方法

  • 确保 Redis 服务器和客户端之间的网络连接稳定。
  • 使用本地缓存减少网络请求。

4. 大批量操作

原因

  • 大批量的操作如MSET、MGET、LRANGE等会占用大量的 CPU 和内存资源。

解决方法

  • 将大批量操作拆分成小批量操作。
  • 使用管道(pipeline)来批量发送命令,减少网络往返次数。

5. 持久化操作

原因

  • Redis 提供 RDB 和 AOF 两种持久化方式,持久化操作会占用 CPU 和 I/O 资源,影响性能。

解决方法

  • 调整 RDB 和 AOF 的配置参数,减少持久化操作的频率。
  • 使用异步持久化操作,避免阻塞主线程。

6. 大键值对(Big Keys)

原因

  • 存储和操作大键值对(如大字符串、大列表、大集合等)会占用大量内存和 CPU 资源。

解决方法

  • 尽量避免存储大键值对。
  • 将大键值对拆分成多个小键值对。

7. CPU 瓶颈

原因

  • Redis 是单线程的,CPU 使用率高时会影响性能。

解决方法

  • 优化 Redis 的配置和使用,减少 CPU 负载。
  • 使用 Redis 集群或分片(sharding)来分散负载。

8. 不合理的配置

原因

  • 不合理的配置参数(如最大客户端连接数、最大内存限制等)会影响 Redis 的性能。

解决方法

  • 根据实际需求调整 Redis 的配置参数。
  • 参考 Redis 官方文档和最佳实践进行配置。

9. 客户端连接过多

原因

  • 过多的客户端连接会占用 Redis 的文件描述符资源,影响性能。

解决方法

  • 限制客户端连接数。
  • 使用连接池来管理客户端连接。

10. 数据结构选择不当

原因

  • 不合理的数据结构选择会影响 Redis 的性能。

解决方法

  • 根据实际需求选择合适的数据结构。
  • 参考 Redis 官方文档了解各数据结构的性能特性。

原文: https://www.yuque.com/jingdianjichi/xyxdsi/wruq3nw7xph7ascq

jedis与redisson对比有什么优缺点

发表于 2025-02-12 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

jedis与redisson对比有什么优缺点?

口语化回答

好的,面试官。jedis 是一个轻量级的 redis 客户端,比较容易集成和使用。redisson 是后面的升级版框架,在分布式方面增加了很多的处理。最大的区别在于线程安全和分布式上面,jedis 需要一些操作来保证安全性,redission 则天然支持,内部封装了很多处理。还有就是集群方面 jedis 如果使用集群非常的麻烦,需要配置很多东西,redisson 则非常容易,天然支持。redisson 还提供了很多现成的功能,分布式锁,限流等等这些,不需要再从头编写。如果业务比较简单,jedis 就够了。复杂的话大型项目还是建议使用 redisson。以上。

题目解析

这道题真不常考,也可以说几乎不考。不是重点,大家了解一下即可。别到时候人家说这两个名词,不知道是什么就行。

面试得分点

线程安全,集群支持,api 功能扩展

题目详细答案

Jedis和Redisson是两种常用的Java Redis客户端。Jedis是一个轻量级的Redis客户端,易于集成和使用。Redisson是一个在Redis的基础上实现的Java驻内存数据网格。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。

二者相比主要是以下的优点和缺点:

Jedis

优点

直观的API:提供了直接且简单的API,便于操作Redis的各种数据结构和命令。

性能高:由于其轻量级特性,Jedis在单线程操作中性能较高。

广泛使用:Jedis是较早的Java Redis客户端之一,有着广泛的社区支持和文档资源。

缺点

线程安全性:Jedis实例不是线程安全的,需要通过连接池(JedisPool)来管理连接,增加了复杂性。

功能有限:Jedis主要提供了对Redis命令的直接封装,缺乏高级特性,如分布式锁、限流器等。

集群支持:虽然Jedis支持Redis集群,但配置和使用相对复杂,且在某些场景下性能不如Redisson。

Redisson

优点

线程安全:Redisson的所有对象都是线程安全的,简化了多线程环境下的使用。

高级特性:提供了许多高级特性,如分布式锁、分布式集合、分布式队列、分布式缓存、限流器等,适合复杂的分布式系统。

易用性:Redisson的API设计更加面向对象,提供了丰富的分布式数据结构和并发工具,使开发更加简便。

集群支持:Redisson对Redis集群的支持更加友好和高效,配置和使用相对简单。

缺点

重量级:Redisson的功能丰富,但也带来了较大的依赖包和内存占用,相比Jedis更为重量级。

性能开销:由于提供了许多高级特性,Redisson在某些场景下的性能可能不如Jedis。

学习曲线:Redisson的API和功能较多,学习和掌握所有特性需要一定的时间。

选择建议

1、 如果你的应用场景比较简单,只需要基本的Redis操作,并且对性能有较高要求,Jedis是一个不错的选择。

2、复杂分布式系统:如果你的应用需要使用Redis的高级特性,如分布式锁、限流器、分布式集合等,或者需要在多线程环境中使用Redis,Redisson会更合适。

3、 集群支持:如果需要使用Redis集群,Redisson的配置和使用相对简单、性能较好,更加推荐使用。

redis key的过期时间和永久有效分别怎么设置

发表于 2025-02-11 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

redis key的过期时间和永久有效分别怎么设置?

在Redis中,你可以使用多种命令来设置键的过期时间或将键设置为永久有效。

  1. 使用EXPIRE命令,EXPIRE命令用于设置键的过期时间,以秒为单位。
1
2
3
EXPIRE key seconds
例如:
EXPIRE mykey 60 # 设置 mykey 的过期时间为60秒
  1. 使用PEXPIRE命令,PEXPIRE命令用于设置键的过期时间,以毫秒为单位。
1
2
3
PEXPIRE key milliseconds
例如:
PEXPIRE mykey 60000 # 设置 mykey 的过期时间为60000毫秒(即60秒)

以上的两个区别就是秒级和毫秒级的区别。

  1. 使用EXPIREAT命令,EXPIREAT命令用于设置键的过期时间为指定的 Unix 时间戳,以秒为单位。
1
2
3
EXPIREAT key timestamp
例如:
EXPIREAT mykey 1672531199 # 设置 mykey 的过期时间为指定的 Unix 时间戳
  1. 使用PEXPIREAT命令,PEXPIREAT命令用于设置键的过期时间为指定的 Unix 时间戳,以毫秒为单位。
1
2
3
PEXPIREAT key milliseconds-timestamp
例如:
PEXPIREAT mykey 1672531199000 # 设置 mykey 的过期时间为指定的 Unix 时间戳(毫秒)
  1. 使用SET命令带选项,SET命令可以在设置键值的同时指定过期时间。
1
2
3
4
5
SET key value EX seconds
SET key value PX milliseconds
例如:
SET mykey "value" EX 60 # 设置 mykey 的值为 "value" 并使其在60秒后过期
SET mykey "value" PX 60000 # 设置 mykey 的值为 "value" 并使其在60000毫秒(即60秒)后过期

设置键为永久有效

  1. 使用PERSIST命令,PERSIST命令用于移除键的过期时间,使其变为永久有效。
1
2
3
PERSIST key
例如:
PERSIST mykey # 移除 mykey 的过期时间,使其变为永久有效

redis 回收进程如何工作

发表于 2025-02-11 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌redis回收进程如何工作的?

题目详细答案

Redis的回收进程负责定期扫描和删除过期键。服务器运行时自动执行。主要就是定期回收和惰性回收,其实也可以看之前的 redis 过期策略。来进行合二为一,其实二者答案差不多,就是内容有一定区别。

定期回收任务

Redis回收进程的核心是一个定期任务,这个任务在Redis的事件循环中执行。

事件循环:Redis使用一个单线程的事件循环模型来处理所有的客户端请求和后台任务。

定期任务:在事件循环中,Redis会定期触发一个函数来执行过期键的检查和删除操作。这个函数默认每100毫秒运行一次。

实现步骤如下:

  1. 选择数据库:每次执行定期任务时,Redis会遍历所有的数据库(默认16个)。
  2. 随机抽样:对于每个数据库,Redis会随机选择一部分带有过期时间的键进行检查(默认20个)。
  3. 检查过期键:检查这些键是否已经过期,如果过期则删除。
  4. 重复检查:如果发现超过一定比例的键是过期的(默认25%),则继续进行更多次的检查和删除,直到过期键的比例下降到合理范围内。

惰性回收

惰性回收不是一个独立的进程,而是每次客户端访问键时触发的检查机制。它的工作原理如下:

  1. 访问键:每当客户端访问一个键时,Redis会检查该键是否设置了过期时间。
  2. 检查过期:如果该键已经过期,Redis会立即删除该键,并返回空结果或相应的错误信息。

redis 如何实现延迟队列

发表于 2025-02-11 | 更新于 2025-06-22 | 分类于 面试
字数统计 | 阅读时长

👌redis如何实现延时队列

题目详细答案

可以使用有序集合(Sorted Set)来实现延时队列。有序集合中的每个元素有一个关联的分数,可以用来表示任务的执行时间戳。具体的步骤如下,非常简单

添加任务到延时队列

将任务添加到有序集合中,使用任务的执行时间作为分数(score)。

1
2
3
4
5
6
7
8
// 示例代码:添加任务到延时队列
String queueName = "delay_queue";
String taskId="task_1";
long delay=5000; // 延迟时间(毫秒)
long executionTime= System.currentTimeMillis() + delay;
Jedis jedis = newJedis("localhost");
jedis.zadd(queueName, executionTime, taskId);
jedis.close();

轮询延时队列并执行任务

定期检查有序集合中的任务,找到那些执行时间已经到达或超过当前时间的任务,并执行这些任务。

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
// 示例代码:轮询延时队列并执行任务
String queueName = "delay_queue";
Jedis jedis=new Jedis("localhost");
while (true) {
long currentTime= System.currentTimeMillis();
Set<Tuple> tasks = jedis.zrangeByScoreWithScores(queueName, 0, currentTime, 0, 1);

if (tasks.isEmpty()) {
// 没有任务需要执行,休眠一段时间
Thread.sleep(1000);
continue;
}

for (Tuple task : tasks) {
StringtaskId= task.getElement();
// 执行任务
executeTask(taskId);

// 从队列中移除已执行的任务
jedis.zrem(queueName, taskId);
}
}

jedis.close();
private static void executeTask(String taskId) {
// 实现任务执行逻辑
System.out.println("Executing task: " + taskId);
}
<i class="fa fa-angle-left"></i>1…678…12<i class="fa fa-angle-right"></i>

232 日志
18 分类
28 标签
GitHub
© 2025 javayun
由 Hexo 强力驱动
主题 - NexT.Gemini