查漏补缺第十期(网易实习一面)

350 阅读16分钟

前言

目前正在出一个查漏补缺专题系列教程, 篇幅会较多, 喜欢的话,给个关注❤️ ~

本专题主要以Java语言为主, 好了, 废话不多说直接开整吧~

Session过期怎么处理

处理会话过期可以依赖于底层的Servlet容器(例如Tomcat)或使用Spring Session框架来管理会话。

以下是两种处理会话过期的常见方法:

  • 使用Servlet容器的会话过期配置:Servlet容器提供了一些配置选项来管理会话过期。你可以在application.properties(或application.yml)文件中添加以下配置:
# 设置会话过期时间为30分钟(单位:秒)
server.servlet.session.timeout=1800

上述配置将会话过期时间设置为30分钟。可以根据需要进行调整。

  • 使用Spring Session框架:Spring Session是一个用于管理会话的框架,它提供了对会话存储的抽象和灵活的配置选项。你可以将Spring Session集成到Spring Boot项目中,以更高级的方式管理会话过期。

添加Spring Session依赖:在pom.xml文件中添加Spring Session的依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-session</artifactId>
</dependency>

配置会话过期时间:在application.properties(或application.yml)文件中添加以下配置:

# 设置会话过期时间为30分钟(单位:秒)
server.servlet.session.timeout=1800

启用Spring Session:在启动类上添加@EnableRedisHttpSession注解,以启用Spring Session并指定会话存储方式(如Redis):

import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

@SpringBootApplication
@EnableRedisHttpSession
public class YourApplication {
    // ...
}

上述配置示例中使用了Redis作为会话存储方式。你可以根据需要选择其他支持的存储方式。

无论你选择哪种方法,一旦会话过期,你可以在控制器或其他相关组件中处理会话过期的情况。例如,你可以通过捕获SessionDestroyedEvent事件来执行一些特定的逻辑:

import org.springframework.context.ApplicationListener;
import org.springframework.security.web.session.HttpSessionDestroyedEvent;

public class SessionDestroyedListener implements ApplicationListener<HttpSessionDestroyedEvent> {

    @Override
    public void onApplicationEvent(HttpSessionDestroyedEvent event) {
        // 处理会话过期的逻辑
    }
}

上述代码是一个会话销毁事件的监听器示例,你可以在onApplicationEvent方法中编写处理过期会话的逻辑。

  • 也可以利用iframe自动刷新空页面来保持session的续期

  • 也可以存到数据库中比如Mysql

如果你想将会话数据存储到MySQL数据库中,可以使用Spring Session框架结合JDBC来实现。

下面是使用Spring Boot和Spring Session将会话数据存储到MySQL数据库的步骤:

  1. 添加依赖:在pom.xml文件中添加Spring SessionMySQL数据库的依赖项:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-session</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
  1. 配置数据库连接:在application.properties(或application.yml)文件中配置MySQL数据库连接信息:
spring.datasource.url=jdbc:mysql://localhost:3306/your_database_name
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
  1. 创建会话实体
import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class SessionEntity {

    @Id
    private String sessionId;
    private byte[] sessionData;

    // getters and setters
}
  1. 创建会话存储库:创建一个继承自JpaRepository的接口,用于访问和操作会话实体。
import org.springframework.data.jpa.repository.JpaRepository;

public interface SessionRepository extends JpaRepository<SessionEntity, String> {
}
  1. 配置会话存储:创建一个配置类,配置会话存储的方式为JDBC,并指定会话实体和存储库。
import org.springframework.context.annotation.Configuration;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.session.jdbc.config.annotation.web.http.JdbcHttpSessionConfiguration;

@Configuration
@EnableJdbcHttpSession
public class SessionConfig extends JdbcHttpSessionConfiguration {

    public SessionConfig() {
        super();
    }
}

redis过期时间怎么设置,redis怎么续期

Redis中,你可以使用EXPIRE命令来设置键的过期时间,并使用TTL命令来获取键的剩余过期时间。要续期(延长)键的过期时间,你可以使用EXPIREAT命令或PEXPIRE命令。

下面是一些常见的Redis命令示例来设置和续期键的过期时间:

  1. 设置键的过期时间(以秒为单位):

    EXPIRE key_name seconds
    

    例如,将名为mykey的键的过期时间设置为60秒:

    EXPIRE mykey 60
    
  2. 获取键的剩余过期时间(以秒为单位):

    TTL key_name
    

    例如,获取名为mykey的键的剩余过期时间:

    TTL mykey
    

    如果键已经过期或不存在,TTL命令将返回-2或-1。

  3. 续期(延长)键的过期时间:

    • EXPIREAT命令:以Unix时间戳作为参数来设置键的过期时间。

      EXPIREAT key_name timestamp
      

      例如,将名为mykey的键的过期时间延长到Unix时间戳为1735689600的日期和时间:

      EXPIREAT mykey 1735689600
      
    • PEXPIRE命令:以毫秒为单位设置键的过期时间。

      PEXPIRE key_name milliseconds
      

      例如,将名为mykey的键的过期时间延长500毫秒:

      PEXPIRE mykey 500
      

rabbitmq可以多个消费者订阅一个消费者吗,实现步骤

RabbitMQ中,多个消费者可以同时订阅一个消费者(也称为工作队列模式或任务队列模式)。这种模式下,多个消费者会从同一个队列中接收消息,并按照一定的策略来共享和处理这些消息。

下面是实现多个消费者订阅一个消费者的步骤:

  1. 创建一个消息队列:首先,在RabbitMQ中创建一个消息队列,作为消息的缓冲区。

  2. 发布消息:将消息发布到消息队列中。这可以通过RabbitMQ的生产者(发布者)完成。

  3. 创建多个消费者:创建多个消费者应用程序,它们将订阅同一个消息队列。

  4. 消费消息:在每个消费者应用程序中,设置消息接收逻辑,使其从消息队列中接收并处理消息。消费者可以使用RabbitMQ的消费者(订阅者)来实现。

以下是一个简单的Java代码示例,展示了如何使用RabbitMQJava客户端实现多个消费者订阅一个消费者:

import com.rabbitmq.client.*;

public class Consumer {

    private static final String QUEUE_NAME = "my_queue";

    public static void main(String[] args) throws Exception {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        
        // 创建连接
        Connection connection = factory.newConnection();
        
        // 创建通道
        Channel channel = connection.createChannel();
        
        // 声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        
        // 设置每个消费者同时只能处理一条消息
        channel.basicQos(1);
        
        // 创建多个消费者
        for (int i = 0; i < 3; i++) {
            // 创建消费者实例
            String consumerName = "Consumer " + (i + 1);
            Consumer consumer = new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    String message = new String(body, "UTF-8");
                    System.out.println(consumerName + " received: " + message);
                    
                    // 模拟耗时操作
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                    // 手动发送消息确认信号
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            };
            
            // 启动消费者
            channel.basicConsume(QUEUE_NAME, false, consumer);
        }
    }
}

在上述代码中,我们创建了3个消费者,并且它们都订阅了同一个名为my_queue的消息队列。每个消费者在接收到消息后会进行一些处理,并手动发送消息确认信号(basicAck)来告知RabbitMQ该消息已被处理。注意,通过调整basicQos方法的参数,我们限制了每个消费者同时处理的消息数量为1,以实现公平分发。

通过运行多个这样的消费者应用程序,它们将同时从队列中接收消息并处理,实现多个消费者订阅一个消费者的效果。

rabbitmq遇到重复下单怎么处理

当使用RabbitMQ处理重复下单问题时,可以采取以下几种策略:

  1. 去重处理:在消费者端进行去重处理,以确保同一订单只被处理一次。可以维护一个订单ID的集合,记录已经处理过的订单ID,当新的订单到达时,先检查该订单ID是否已存在于集合中,如果存在则不处理,否则进行处理并将订单ID加入到集合中。

  2. 幂等性处理:确保消费者处理逻辑具备幂等性,即使同一消息被消费多次,最终的结果保持一致。这样即使发生重复消息,重复的处理操作也不会引起实际业务上的影响。

    例如,在处理订单下单消息时,可以在数据库中添加唯一约束或使用订单号作为主键,当重复的订单号到达时,数据库操作将失败,但不会产生实际的影响。

  3. 消息去重:在消息发布端进行去重处理,以避免发送重复的消息到队列中。可以在发送消息前,先查询数据库或缓存中是否已存在相同的订单,如果存在则不发送消息,否则发送消息到队列。

    这种方法可以减少不必要的重复消息,但仍然需要保证消费者的处理逻辑具备幂等性。

  4. 消息超时处理:在消息发送后设置一定的超时时间,在消费者端处理消息时,检查订单是否已经处理过。如果订单已经处理过,则忽略该消息;如果订单未处理,但超过了设定的超时时间,则重新处理该消息。

这种方法可以解决消息在网络传输过程中的重复问题,但仍然需要保证消费者的处理逻辑具备幂等性。

需要根据具体的业务场景和需求选择适合的处理策略。通常结合多种策略可以提高处理重复下单问题的可靠性和效率。

当处理重复下单问题时,以下是一个示例代码,结合了幂等性处理和消息超时处理:

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeoutException;

public class OrderConsumer {

    private static final String QUEUE_NAME = "order_queue";
    private static final String EXCHANGE_NAME = "order_exchange";
    private static final String ROUTING_KEY = "order";

    private static Set<String> processedOrders = new HashSet<>();

    public static void main(String[] args) throws IOException, TimeoutException {
        // 创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");

        // 创建连接
        Connection connection = factory.newConnection();

        // 创建通道
        Channel channel = connection.createChannel();

        // 声明交换机和队列
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT, true);
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY);

        // 设置每个消费者同时只能处理一条消息
        channel.basicQos(1);

        // 创建消费者实例
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String message = new String(body, "UTF-8");
                String orderId = properties.getMessageId();

                if (processedOrders.contains(orderId)) {
                    System.out.println("Order already processed: " + orderId);
                    // 手动发送消息确认信号
                    channel.basicAck(envelope.getDeliveryTag(), false);
                } else {
                    // 模拟订单处理耗时
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // 处理订单逻辑
                    System.out.println("Processing order: " + message);

                    // 标记订单已处理
                    processedOrders.add(orderId);

                    // 手动发送消息确认信号
                    channel.basicAck(envelope.getDeliveryTag(), false);
                }
            }
        };

        // 启动消费者
        channel.basicConsume(QUEUE_NAME, false, consumer);
    }
}

在上述代码中,我们创建了一个订单消费者,它会从名为order_queue的队列中接收订单消息。消费者在处理订单之前,会检查订单是否已经处理过,如果已处理则忽略该消息,否则进行订单处理,并将订单ID加入到已处理订单的集合中。

这样,即使同一个订单消息被重复发送到队列中,只有第一次会被处理,后续的重复消息会被忽略。

redis数据类型,zset实现原理

Redis中,有多种数据类型可供使用,其中之一是有序集合(Sorted Set),也称为有序列表(Sorted List),其实现原理如下:

有序集合是一个包含了成员(member)和分数(score)的数据结构,其中每个成员都关联着一个分数,用于进行排序。有序集合在Redis内部使用了一种称为跳跃表(Skip List)的数据结构来实现。

跳跃表是一种基于链表的数据结构,可以提供快速的插入、删除和查找操作,同时保持元素有序。它通过维护多层级的指针来实现高效的查找和范围查询。

当在Redis中创建一个有序集合(ZSET)时,Redis会为每个成员分配一个唯一的标识符,同时根据成员的分数构建跳跃表的结构。每个跳跃表节点都包含一个成员和分数,以及指向其他节点的指针。

通过使用跳跃表,有序集合能够在O(log(N))的时间复杂度内执行插入、删除和查找操作。它允许按照成员的分数进行排序,并支持根据分数范围进行范围查询,例如获取分数在指定范围内的成员列表。

有序集合还提供了一些其他的操作,例如根据成员获取其分数、根据成员进行排名(从小到大或从大到小)、计算成员之间的分数差值等。

由于有序集合的实现使用了跳跃表,这使得它成为Redis中非常高效和灵活的数据类型,适用于许多场景,例如排行榜、计数器、范围查询等。

总结起来,有序集合通过使用跳跃表作为底层数据结构,实现了成员有序、快速的插入、删除和查找操作。这使得有序集合成为Redis中强大且高效的数据类型之一。

redis淘汰策略

Redis采用了多种淘汰策略(Eviction Policy)来管理内存,以确保在内存使用达到限制时仍能保持数据的可用性。以下是Redis中常见的淘汰策略:

  1. LRU(Least Recently Used):选择最近最少使用的键进行淘汰。即当内存不足时,优先淘汰最近最少访问的键。

  2. LFU(Least Frequently Used):选择最不经常使用的键进行淘汰。即当内存不足时,优先淘汰被访问次数最少的键。

  3. Random(随机):随机选择要淘汰的键。这种策略是完全随机的,没有根据访问模式或其他指标进行考虑。

  4. LRU-TTL(Least Recently Used with Time to Live):在最近最少使用的键中,优先淘汰剩余生存时间(TTL)较短的键。这种策略适用于有设置过期时间的键。

  5. Allkeys-LRU:在所有键中采用LRU策略进行淘汰,包括设置过期时间和未设置过期时间的键。

  6. Volatile-LRU:在设置了过期时间的键中采用LRU策略进行淘汰,优先淘汰具有较短剩余生存时间(TTL)的键。

  7. Volatile-TTL:在设置了过期时间的键中,优先淘汰具有较短剩余生存时间(TTL)的键。

Redis的淘汰策略可以通过配置文件(redis.conf)中的maxmemory-policy选项进行设置。默认的淘汰策略是volatile-lru,即优先淘汰具有较短剩余生存时间(TTL)的键。

需要注意的是,淘汰策略仅在Redis的内存使用达到maxmemory配置限制时才会触发。如果Redis的内存使用未超过限制,即使设置了淘汰策略,也不会主动淘汰数据。

通过选择适合应用场景的淘汰策略,可以在保持数据可用性的前提下,合理管理Redis的内存使用,避免内存溢出问题。

redis如果一个key特别大,如果要删除掉会有什么问题,比如删除一个特别大的 ZSet,怎么删

当要删除一个特别大的数据结构(如一个特别大的ZSet)时,可能会面临以下几个问题:

  1. 删除操作可能会导致Redis在删除过程中出现阻塞,从而影响其他操作的性能。当执行删除操作时,Redis会一次性将整个数据结构加载到内存中,然后进行删除操作。如果数据结构非常大,可能会占用大量的内存和CPU资源,并导致Redis在删除期间变得不可响应。

  2. 删除操作可能会导致Redis的内存占用过高。当删除一个特别大的ZSet时,Redis需要将整个ZSet的数据加载到内存中进行删除操作,这可能导致Redis的内存占用达到或接近内存限制。如果内存不足,可能会触发Redis的淘汰策略,导致其他键被淘汰。

针对以上问题,可以考虑采取以下措施来删除一个特别大的ZSet

  1. 分批删除:将删除操作分批进行,每次删除一部分数据。可以使用ZSCAN命令遍历ZSet,并使用ZREM命令逐个删除ZSet的成员。这样可以减少单次删除操作的数据量,减轻Redis的负担,并降低对其他操作的影响。

    ZSCAN key cursor [MATCH pattern] [COUNT count]
    ZREM key member [member ...]
    

    注意,分批删除可能需要多次执行,直到将整个ZSet删除完毕。

  2. 使用后台线程删除:如果对实时性要求不高,可以将删除操作放入后台线程进行处理,以减少对Redis主线程的影响。可以使用RedisLua脚本或编写自定义的后台任务来异步删除特别大的ZSet

    例如,可以编写一个异步任务,使用ZSCAN命令遍历ZSet,并使用ZREM命令逐个删除ZSet的成员。然后将该任务放入一个异步任务队列中,由后台线程逐个执行删除操作。

  3. 谨慎处理删除过程中的阻塞情况:如果删除操作无法避免对Redis主线程的阻塞,可以在删除期间暂时停止对Redis的其他操作,以避免阻塞影响其他请求的性能。

结束语

大家可以针对自己薄弱的地方进行复习, 然后多总结,形成自己的理解,不要去背~

本着把自己知道的都告诉大家,如果本文对您有所帮助,点赞+关注鼓励一下呗~

相关文章

项目源码(源码已更新 欢迎star⭐️)

往期设计模式相关文章

设计模式项目源码(源码已更新 欢迎star⭐️)

Kafka 专题学习

项目源码(源码已更新 欢迎star⭐️)

ElasticSearch 专题学习

项目源码(源码已更新 欢迎star⭐️)

往期并发编程内容推荐

推荐 SpringBoot & SpringCloud (源码已更新 欢迎star⭐️)

博客(阅读体验较佳)