温馨提示:距离2026年结束还剩556天,剩余约为152.33%
Java基础数据类型:
包装类 全部占8个字节。 数据库有哪些约束: 主键约束、外键约束、唯一约束、非空约束、默认约束。
有list、set、map三种:
ThreadLocal通过在每个线程中维护一个ThreadLocalMap对象来实现线程隔离。ThreadLocalMap以ThreadLocal对象作为键,线程私有的变量副本作为值。每个线程都有自己的ThreadLocalMap,线程可以通过 ThreadLocal的 get()和set()方法来获取和设置自己线程的 ThreadLocal 变量的值。
由于ThreadLocal的生命周期和线程的生命周期绑定,使用完ThreadLocal后,需要调用 remove()方法进行清理,避免内存泄漏。
解决方法
在使用完ThreadLocal后,在合适的地方调用remove()方法清理资源,可以使用try-finally语句块确保清理操作的执行,或者使用ThreadLocal的initialValue()方法设置初始值,这样在线程结束后会自动清理。
// 设置初始化方法
private static ThreadLocal<Object> threadLocal = ThreadLocal.withInitial(()-> {
return "初始化值,类型要与泛型相同";
});
// 使用完ThreadLocal后,调用remove()方法清理
threadLocal.remove();
如果多个线程共享了同一个ThreadLocal变量,可能会导致数据错乱或不确定的结果。每个线程应持有自己的ThreadLocal变量实例。
解决方法: 对于需要在多个线程之间共享变量的情况,应该创建多个ThreadLocal实例,每个线程持有自己的实例。
在使用线程池时,需要特别小心 ThreadLocal 的使用,避免由于线程的重用而导致 ThreadLocal数据的混乱。
解决方法: 使用线程池时,应避免使用ThreadLocal变量或者在使用前后显式清理ThreadLocal变量,确保每次任务执行时 ThreadLocal的状态是干净的。
总的来说,使用ThreadLocal时需要注意其生命周期、清理和共享的问题,合理使用并及时清理ThreadLocal,可以避免潜在的问题发生。
ThreadLocal是一种 Java技术,它允许在多线程环境中维护线程私有的变量副本。底层实现会使用一个类似于 Map的结构来存储每个线程的变量副本。ThreadLocal并不是强引用或弱引用,它使用弱引用作为键来维护各个线程的变量副本,但变量本身由线程强引用。在使用ThreadLocal时,可能会出现内存泄漏的问题。如果线程结束了,但ThreadLocal中的变量没有被手动清理,那么该变量会一直存在于 ThreadLocal的 Map 中,导致内存泄漏。解决这个问题的常见方式是在使用完ThreadLocal后调用remove()方法将变量从ThreadLocal中移除,或者使用Java8中的ThreadLocal的InitialValue方法来提供默认值。另外,也可以使用ThreadLocal的弱引用方式来解决内存泄漏问题,例如使用InheritableThreadLocal。
数据类型 | 底层数据结构 | 使用场景 |
---|---|---|
String | 普通字符串,由单个char组成的集合。类型有embStr, int, raw | 用于缓存、计数器、分布式锁等场景 |
Set | 有序数组:存储的数据都为整数且数目不超过512个 散列数组:数据较大,满足不了有序数组后 | 可以进行集合运算,例如并集、交集、差集等操作常用于标签、好友列表等场景 |
Zset | 压缩列表6以后为Dict即HashTable:单个数据(字符串或其他)小于64字节,个数小于512个跳表:当数据过大,不满足以上2个条件后 | 可以按照分数排序,也可以进行范围查询。常用于排行榜、计分系统等场景 |
List | 压缩列表zipList: 列表中保存的单个数据(字符串或其他)小于64字节,列表中数据个数小于512个。双向循环链表: 当数据过大,不满足以上2个条件后。 | 可以进行队列和栈的操作常用于消息队列、任务队列等场景 |
Hash | 压缩列表zipList: 列表中保存的单个数据(字符串或其他)小于64字节,列表中数据个数小于512个。散列表: 当数据过大,不满足以上2个条件后。 | 用于存储对象,例如用户信息、商品信息等 |
读未提交、读已提交、可重复读和串行化 使用@Transactional注解的isolation属性来控制事务的隔离级别
对于发送消息的场景,正常情况没有问题,直接发送即可:
如果是非正常情况就需要特殊处理了,一般会有三种非正常情况需要处理: ● 第一种情况,消息发送到交换机(exchange),但是没有队列与交换机绑定,消息会丢失。
● 第二种情况,在消息的发送后进行确认,如果发送失败需要将消息持久化,例如:发送的交换机不存在的情况。
● 第三种情况,由于网络、MQ服务宕机等原因导致消息没有发送到MQ服务器。
第一种情况: 对于消息只是到了交换机,并没有到达队列,这种情况记录日志即可,因为我们也不确定哪个队列需要这个消息。 配置如下(nacos中的shared-spring-rabbitmq.yml文件):
package com.sl.mq.config;
import cn.hutool.core.util.StrUtil;
import com.sl.transport.common.constant.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
@Slf4j
@Configuration
public class MessageConfig implements ApplicationContextAware {
/**
* 发送者回执 没有路由到队列的情况
*
* @param applicationContext 应用上下文
* @throws BeansException 异常
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
// 获取RabbitTemplate
RabbitTemplate rabbitTemplate = applicationContext.getBean(RabbitTemplate.class);
// 设置ReturnCallback
rabbitTemplate.setReturnsCallback(message -> {
if (StrUtil.contains(message.getExchange(), Constants.MQ.DELAYED_KEYWORD)) {
//延迟消息没有发到队列是正常情况,无需记录日志
return;
}
// 投递失败,记录日志
log.error("消息没有投递到队列,应答码:{},原因:{},交换机:{},路由键:{},消息:{}",
message.getReplyCode(), message.getReplyText(), message.getExchange(), message.getRoutingKey(), message.getMessage());
});
}
}
第二种情况: 在配文件中开启配置publisher-confirm-type,即可在发送消息时添加回调方法:
在代码中进行处理,将消息数据持久化到数据库中,后续通过xxl-job进行处理,将消息进行重新发送。
同样,如果出现异常情况也是将消息持久化:
第三种情况: 将发送消息的代码进行try{}catch{}处理,如果出现异常会通过Spring-retry机制进重试,最多重试3次,如果依然失败就将消息数据进行持久化:
设置重试:
最终的落库操作:
xxl-job任务,主要负责从数据库中查询出错误消息数据然后进行重试:
package com.sl.mq.job;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sl.mq.entity.FailMsgEntity;
import com.sl.mq.service.FailMsgService;
import com.sl.mq.service.MQService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* 失败消息的处理任务
*/
@Slf4j
@Component
@ConditionalOnBean({MQService.class, FailMsgService.class})
public class FailMsgJob {
@Resource
private FailMsgService failMsgService;
@Resource
private MQService mqService;
@XxlJob("failMsgJob")
public void execute() {
//查询失败的数据,每次最多处理100条错误消息
LambdaQueryWrapper<FailMsgEntity> queryWrapper = new LambdaQueryWrapper<FailMsgEntity>()
.orderByAsc(FailMsgEntity::getCreated)
.last("limit 100");
List<FailMsgEntity> failMsgEntityList = this.failMsgService.list(queryWrapper);
if (CollUtil.isEmpty(failMsgEntityList)) {
return;
}
for (FailMsgEntity failMsgEntity : failMsgEntityList) {
try {
//发送消息
this.mqService.sendMsg(failMsgEntity.getExchange(), failMsgEntity.getRoutingKey(), failMsgEntity.getMsg());
//删除数据
this.failMsgService.removeById(failMsgEntity.getId());
} catch (Exception e) {
log.error("处理错误消息失败, failMsgEntity = {}", failMsgEntity);
}
}
}
}
xxl-job中的任务调度:
声明(创建)交换机(durable为true)、 队列(durable为true) 发送消息时指定持久化(MessageDeliveryMode.PERSISTENT)
交换机与队列都不能自动删除
共有3种确认模式:默认使用auto
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: none|auto|manual
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true # 开启消费者失败重试
initial-interval: 1000 # 初识的失败等待时长为1秒
multiplier: 3 # 失败的等待时长倍数,下次等待时长 = multiplier * last-interval
max-attempts: 4 # 最大重试次数
stateless: true # true无状态;false有状态。如果业务中包含事务,这里改为false
重试次数耗尽,如果消息依然失败,则需要有MessageRecover。有3种不同的实现
@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){
return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error");
}
重试次数耗尽时,使用第一种RejectAndDoNotRequeueRecover,则消息会进入死信,由运维人员来处理。