速配
更新: 2/1/2025 字数: 0 字 时长: 0 分钟
项目地址
前端 https://github.com/lihuibear/friend-frontend
后端 https://github.com/lihuibear/friend-backed
后端面试题
在项目中如何利用Redis实现分布式Session?Redis的主要优势是什么?
本项目中,我使用Redis分布式Session来代替Tomcat本地的Session存储,能够在分布式多机场景下保证获取登录用户信息的一致性。用Redis实现分布式Session的优点是非常简单方便,只需要引入Redis和 spring-session-data-redis 依赖,然后在配置文件中指定Redis的地址和 session的store-type为redis,即可自动生效,不用自己额外编码。
- :Redis首先是一个开源的基于内存K/V存储中间件。基于内存存储所以读写的性能非常的高。
- :String、list、hash、set、zset
- :支持AOF和RDB两种持久化方式。
- :Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行
- :主机会自动将数据同步到从机,可以进行读写分离
- :RedisSentinel提供了高可用性解决方案,可以监控Redis主服务器和从服务器,并在主服务器失败时自动进行故障转移。
- :RedisCluster提供了分布式存储解决方案,可以自动分割数据到多个节点,提高系统的扩展性和可用性。
- :Redis支持在服务器端执行Lua脚本,这使得可以在执行复杂逻辑时减少网络往返次数。
- :Redis有多种语言的客户端库,使得在各种编程环境中都可以方便地使用Redis。
- :Redis是开源的,这意味着它可以免费使用,并且有活跃的社区支持。
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.6.2</version>
</dependency>
#session 失效时间 (1天)
session:
timeout: 86400
store-type: redis
#redis配置
redis:
host: localhost
port: 6379
database: 0
在Redis中,使用Hash代替String存储用户信息的好处是什么?Hash与String 存储方式有何区别?
Redis的Hash结构采用key/value键值对的形式存储数据,使用Redis的Hash来存储用户信息后,能够很方便地对用户每个属性进行独立的更新和查询操作,而不是更新和返回整个JSON字符串,性能会更高。 举个例子,你想要获取用户的昵称(就4个字符串),但是用户的简介有100KB的大小。如果用Hash结构,可以只获取昵称,网络传输的内容大小就很小;而如果用String结构整体存储,网络传输数据时会把所有的用户信息都返回出来,增加传输开销。 此外,相比于直接在Spring Boot 中使用String 类型存储用户信息,使用Hash结构不用额外存储序列化对象信息,可以一定程度上节省内存。
在大多数情况下,Hash 是存储用户信息的更好选择,因为它提供了更高的灵活性、更低的内存占用和更好的性能。而 String 更适合存储简单的键值对数据。
结构化存储:Hash 是一种结构化的数据类型,适合存储对象(如用户信息)
节省内存: Redis 对 Hash 进行了优化,当字段数量较少时,Hash 会使用更紧凑的存储方式(ziplist),从而减少内存占用。
如果使用 String 存储用户信息,每个字段都需要一个独立的键,这会增加键的数量和内存开销。
高效的部分更新:
使用 Hash 可以单独更新某个字段,而不需要读取和写入整个对象。
如果使用 String 存储用户信息,更新某个字段时需要先读取整个对象,修改后再写回,增加了网络和计算开销。
更好的可读性和管理:
Hash 将用户信息作为一个整体存储,键的数量更少,便于管理和维护。
使用 String 存储时,键的数量会随着用户信息的字段数量增加而增加,管理起来更复杂。
请解释一下Java8 StreamAPI和Lambda表达式的作用,以及在项目中如何应用它们来简化集合处理?
StreamAPI和Lambda表达式是Java8提供的语法糖,它们的作用是使集合处理更加简洁、易读和高效。 Lambda表达式是一种匿名函数,允许你以更紧凑的方式传递代码块,简化代码的编写,比如:
// 使用 Lambda 表达式过滤以 "A" 开头的名字
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A"))
.collect(Collectors.toList());
StreamAPI是一种流式操作集合的方法,提供了丰富的集合处理操作,比如过滤、映射、排序等,还支持延迟加载和并行处理,简化代码、并提高编码效率。 上述代码示例同样也是StreamAPI的应用。 在本项目中,匹配相似用户时,将存储用户和匹配度的List转化为Stream流,用sorted方法进行编辑距离由小到大排序,用limit方法取流的前N项,用collect终结操作将处理好的流转化为集合List。还有使用StreamAPI的map方法对用户列表中的每个用户信息进行脱敏、使用ParallelStream实现并发流等。
为什么在Redis中需要自定义序列化器?如何实现自定义序列化器?
由于Spring Boot Data Redis默认使用JDK序列化器,会将存储到Redis的键值对转化为字节数组,不利于在Redis可视化工具中阅读、并且不利于跨语言兼容,所以需要指定序列化器。 所以我通过新建RedisTemplateConfig配置类来创建自定义的RedisTemplateBean,并且通过redisTemplate.setKeySerializer(RedisSerializer.string())指定了Redis Key的序列化方式。
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(RedisSerializer.string());
return redisTemplate;
}
}
在解决首页加载过慢的问题中,你使用了Spring Scheduler定时任务和分布式锁,请解释一下定时任务的执行原理和此处分布式锁的作用。
项目使用SpringScheduler实现定时任务,我将每个任务定义为独立的Job类,并且给实际需要定时执行的方法增加@Scheduled注解来开启定时任务。
在@Scheduled注解中,我使用crontab表达式来定义执行定时任务的时间周期,Spring Scheduler会根据这些定义,在时机到达时开启独立的线程来执行任务。
在分布式场景下,可能有多个服务器实例同时执行同一个定时任务,导致并发问题或重复执行,所以用分布式锁来保证定时任务执行的唯一性。当定时任务要执行时,先去抢锁,只有抢到锁的服务器实例才会执行定时任务。
编辑距离算法是什么,它在你实现的用户匹配功能中起到了什么作用?请解释一下编辑距离算法的实现原理。
编辑距离算法是一种用于度量两个字符串之间的相似度或差异性的算法,常用于字符串相似度比较、拼写检查等场景。 在用户匹配功能中,我使用编辑距离算法来计算用户输入的搜索关键词与已有用户信息的匹配程度,并按照相似度进行排序,从而实现最相似用户的推荐。
比较字符,字符串相似度比较,ab -->b 1 abc --> b 2
前端面试题
在你的项目中,为什么选择了全局通用的Layout组件而不是局部组件?
代码复用与维护性:
全局 Layout 组件 可以在整个项目中复用,避免了重复代码。
如果需要修改布局(例如调整头部、底部或侧边栏),只需修改全局 Layout 组件,所有页面都会同步更新,维护成本低。
如果使用局部组件,每个页面都需要单独维护布局代码,增加了开发和维护的工作量。
一致性:
全局 Layout 组件可以确保整个项目的布局风格一致,避免不同页面出现样式或结构差异。对用户体验来说,一致的布局有助于用户快速熟悉系统,降低学习成本。
简化页面开发:
使用全局 Layout 组件后,页面开发者只需关注页面自身的业务逻辑,无需重复编写布局代码。页面代码更加简洁,可读性更高。