likes
comments
collection
share

查漏补缺第十四期(滴滴实习二面)

作者站长头像
站长
· 阅读数 40

前言

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

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

项目中你是如何实现请求拦截器功能的,说说具体原理

Spring Boot项目中,可以通过编写拦截器来实现对请求的拦截和处理。拦截器是Spring框架提供的一种机制,可以在请求进入控制器之前或之后进行一些预处理或后处理操作。

  1. 创建一个实现了HandlerInterceptor接口的拦截器类。这个接口定义了三个方法:preHandlepostHandleafterCompletion,分别在请求处理之前、请求处理之后和视图渲染完成之后执行。
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 在请求处理之前执行的逻辑
        return true; // 返回true表示继续执行后续的拦截器和请求处理
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 在请求处理之后,视图渲染之前执行的逻辑
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 在整个请求完成之后执行的逻辑,可以用来做一些资源清理工作
    }
}
  1. Spring Boot配置类中注册拦截器。可以创建一个配置类,实现WebMvcConfigurer接口,并重写addInterceptors方法,在该方法中添加自定义的拦截器。
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MyInterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor())
                .addPathPatterns("/**"); // 添加拦截路径
    }
}

在上述代码中,使用addInterceptor方法将自定义的拦截器添加到InterceptorRegistry中,并通过addPathPatterns方法指定要拦截的路径。上述示例中的addPathPatterns("/**")表示拦截所有路径。

  1. 当请求到达时,拦截器会按照注册的顺序执行相应的方法。在preHandle方法中,可以编写拦截逻辑,比如权限验证、日志记录等。如果preHandle方法返回true,则继续执行后续的拦截器和请求处理;如果返回false,则请求将被中断,不再继续处理。

旁路缓存机制有了解过吗?具体解决的什么场景

旁路缓存(Cache Aside)机制是一种常见的缓存策略,用于解决数据库访问频繁的场景。它的主要目的是通过缓存数据来减少对数据库的直接访问,从而提高系统的性能和响应速度。

具体来说,旁路缓存机制解决的主要场景包括:

  1. 频繁读取操作:当系统中某些数据被频繁读取,而且这些数据在相对较长的时间内是不会发生变化的,可以将这些数据缓存在旁路缓存中。这样,当下次请求到来时,可以直接从缓存中获取数据,而无需再次查询数据库,从而提高读取性能。

  2. 减轻数据库负载:数据库是系统中最常见的瓶颈之一,频繁的数据库访问可能会对数据库服务器造成较大的负载压力。通过使用旁路缓存,可以减少对数据库的直接访问次数,从而降低数据库服务器的负载,提高系统的可扩展性和稳定性。

  3. 响应时间优化:数据库的访问通常比内存或缓存的访问慢得多。通过将常用的数据缓存在旁路缓存中,可以大大降低数据获取的延迟,从而加快系统的响应时间,提升用户体验。

  4. 并发读取保护:在某些场景下,多个请求同时读取相同的数据,如果每个请求都直接访问数据库,可能会导致并发读取的问题,增加数据库的竞争压力。通过旁路缓存,可以在第一个请求查询数据库后将数据缓存在缓存中,后续的请求可以直接从缓存获取数据,避免了并发读取的问题。

总的来说,旁路缓存机制适用于那些读取频繁、变化较少的数据场景,通过将这些数据缓存在缓存中,可以减少对数据库的直接访问,提高系统的性能、响应速度和可扩展性。但需要注意的是,旁路缓存机制也引入了缓存一致性的问题,需要在更新数据时维护缓存的一致性,以避免数据不一致的情况发生。

更新缓存失败了怎么办

重试机制:在Redis更新缓存失败后,可以进行重试操作,重新尝试更新缓存。可以使用循环结构和计数器来实现重试机制,设置最大重试次数,避免无限循环。

// 示例代码,重试更新Redis缓存
int maxRetries = 3;
int retries = 0;
boolean updateSuccess = false;

while (retries < maxRetries && !updateSuccess) {
    try {
        // 更新Redis缓存操作
        // ...
        updateSuccess = true; // 更新成功标志
    } catch (Exception e) {
        retries++; // 增加重试次数
        // 可以在重试之间添加一定的延时,避免瞬时故障导致的高并发重试
        // ...
    }
}
if (!updateSuccess) {
    // 更新Redis缓存多次仍然失败,进行日志记录或其他处理 比如MQ处理
    // ...
}

定时任务修复:如果Redis更新缓存失败,可以通过定时任务来修复缓存数据。定时任务可以周期性地扫描数据库,检查数据与缓存是否一致,如果发现不一致的情况,重新更新缓存。

// 示例代码,定时任务修复缓存数据
@Scheduled(fixedDelay = 60 * 1000) // 每隔60秒执行一次任务
public void fixCacheData() {
    // 查询数据库中的数据
    // ...
    // 更新Redis缓存
    // ...
}

需要根据具体业务场景和系统需求选择合适的处理方式。在实际应用中,可以结合日志记录、报警通知等机制,对更新缓存失败的情况进行监控和处理,确保系统的稳定性和可靠性。

如何保证缓存中的数据与数据库中的数据一致性

  • 删除缓存而不是更新缓存

当一个线程对缓存的key进行写操作的时候,如果其它线程进来读数据库的时候,读到的就是脏数据,产生了数据不一致问题。

相比较而言,删除缓存的速度比更新缓存的速度快很多,所用时间相对也少很多,读脏数据的概率也小很多

  • 先更数据,后删缓存

目前最流行的缓存读写策略cache-aside-pattern就是采用先更数据库,再删缓存的方式

在缓存中不存在对应的key,数据库又没有完成更新的时候,如果有线程进来读取数据,并写入到缓存,那么在更新成功之后,这个key就是一个脏数据。

毫无疑问,先删缓存,再更数据库,缓存中key不存在的时间的时间更长,有更大的概率会产生脏数据。

缓存不一致处理

如果不是并发特别高,对缓存依赖性很强,其实一定程序的不一致是可以接受的。

但是如果对一致性要求比较高,那就得想办法保证缓存和数据库中数据一致。

缓存和数据库数据不一致常见的两种原因:

  • 缓存key删除失败
  • 并发导致写入了脏数据

解决方案一: 消息队列保证key被删除

可以引入消息队列,把要删除的key或者删除失败的key丢尽消息队列,利用消息队列的重试机制,重试删除对应的key。

方案二: 数据库订阅+消息队列保证key被删除

可以用一个服务(比如阿里的 canal)去监听数据库的binlog,获取需要操作的数据。然后用一个公共的服务获取订阅程序传来的信息,进行缓存删除操作。

这种方式降低了对业务的侵入,但其实整个系统的复杂度是提升的,实施起来难度较大

方案三: 延时双删

还有一种情况,是在缓存不存在的时候,写入了脏数据,这种情况在先删缓存,再更数据库的缓存更新策略下发生的比较多,解决方案是延时双删。这种方式的延时时间设置需要对系统进行评估。

方案四: 设置缓存过期时间

给缓存设置一个合理的过期时间,即使发生了缓存数据不一致的问题,它也不会永远不一致下去,缓存过期的时候,自然又会恢复一致。

k和数对的最大数目

每一步操作中,你需要从数组中选出和为 k 的两个整数,并将它们移出数组。返回你可以对数组执行的最大操作数。

输入:nums = [1,2,3,4], k = 5
输出:2
解释:开始时 nums = [1,2,3,4]:
- 移出 1 和 4 ,之后 nums = [2,3]
- 移出 2 和 3 ,之后 nums = []
不再有和为 5 的数对,因此最多执行 2 次操作。
输入:nums = [3,1,3,4,3], k = 6
输出:1
解释:开始时 nums = [3,1,3,4,3]:
- 移出前两个 3 ,之后nums = [1,4,3]
不再有和为 6 的数对,因此最多执行 1 次操作。

本题可以采用双指针来解决

public class KSolution {
    public static int maxOperations(int[] nums, int k) {
        Arrays.sort(nums);
        int l = 0, r = nums.length - 1;
        int ans = 0;
        while (l < r) {
            int res = nums[l] + nums[r];
            if (res > k) {
                r--;
            } else if (res < k) {
                l++;
            } else {
                ans++;
                l++;
                r--;
            }
        }
        return ans;
    }

    public static void main(String[] args) {
        int[] nums1 = new int[]{1,2,3,4};
        System.out.println( maxOperations(nums1, 5)); // 2

        int[] nums2 = new int[]{3,1,3,4,3};
        System.out.println( maxOperations(nums2, 6)); // 1
    }

}

将数据排序,用两个指针分别指向数组的头尾;将两个指针指向的数求和;

  • 若和大于目标,则说明太大了,需要右指针左移(可以使和变小);
  • 若和小于目标,则说明太小了,需要左指针右移(可以使和变大);
  • 若和等于目标,则两个指针都往中间移动,结果+1。

循环2步骤直至左指针不在右指针的左边。

时间复杂度O(nlogn + n),空间复杂度O(1)

结束语

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

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

相关文章

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

往期设计模式相关文章

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

Kafka 专题学习

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

ElasticSearch 专题学习

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

往期并发编程内容推荐

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

博客(阅读体验较佳)

转载自:https://juejin.cn/post/7246946294920036413
评论
请登录