新的一周又来了,今天分享的是训练营的朋友在阿里的一面,看了一下面试的内容,感觉挺简单的,你做一下试试:
Redis 数据消失的原因
Redis 中的数据如果既没有设置过期时间也没有被显式删除但仍然消失了,可能是因为配置了持久化策略(如RDB快照或AOF日志),在重启后数据未正确加载;
或者执行了清空命令如FLUSHDB
或FLUSHALL
。此外,当Redis达到其配置的最大内存限制时,会依据设定的驱逐策略(eviction policy)自动移除部分键值对以释放内存空间。
还有可能是由于主从复制过程中发生故障切换,导致某些尚未同步的数据丢失。
Redis 的数据结构
字符串(String)、哈希表(Hash)、列表(List)、集合(Set)以及有序集合(Sorted Set)。
Redis Geo Hash 的使用和实现
是GeoHash算法,
它可以将经纬度坐标编码成一个字符串,从而简化了位置数据的管理和检索。
在内部,Redis 使用有序集合(zset)来保存每个地点的位置信息,其中元素的分数代表了该地点的GeoHash编码值。这样就可以高效地进行范围查找和最近邻搜索。
ZSET 的存储方式
ZSET 允许为每个成员关联一个浮点数分值,并按此分值升序排列。为了保证高效的插入、删除和查找操作。
Redis 在内部采用了跳跃表(skiplist)与哈希表相结合的方式来组织数据。跳跃表确保了快速的顺序访问,而哈希表提供了O(1)复杂度的随机访问能力。
RocketMQ 如何保障高性能
RocketMQ 设计了一系列机制以确保高吞吐量和低延迟的消息传递服务。
其中包括异步刷盘减少I/O阻塞,批量发送消息降低网络开销,零拷贝技术提升文件读取效率,以及精心优化的文件索引管理和多线程处理模型。
这些特性共同作用,使RocketMQ能够在大规模分布式环境中保持卓越性能的同时维持较高的可靠性和可用性。
批量提交的好处
批量提交指的是将多个独立的操作合并成一次请求发送给服务器。
- 这种方式可以显著减少客户端与服务端之间的交互次数,进而降低了通信成本和整体延迟。
- 由于减少了每次请求的上下文切换和资源分配过程,系统能够更加高效地利用计算资源。
- 批量处理还促进了资源共享,因为多个任务可以在单次调用中得到统一处理,减少了并发控制带来的额外负担。
固态硬盘和机械硬盘的区别
固态硬盘(SSD)和机械硬盘(HDD)之间最明显的差异在于它们的工作原理。
SSD 采用闪存颗粒作为存储介质,无任何移动部件,因此具有更快的速度、更低的功耗、更好的抗震性和更长的使用寿命。
HDD 利用旋转磁碟片和机械臂来进行数据读写,虽然单位容量成本较低且适合长时间存储大量数据,但在性能上通常逊色于SSD,尤其是在随机读写方面。
随机读和顺序读
随机读是指按照不连续的地址序列访问数据块,这种模式下每次读取都需要重新定位磁头(针对HDD),所以会导致较高的延迟。
顺序读是指沿着预先确定的路径连续读取一系列数据块,这种方式对于HDD来说更为有利,因为它不需要频繁调整磁头位置,而对于SSD而言,两种读取模式之间的性能差距相对较小,因为它们不受物理位置的影响。
MQ 消息如何保障不丢失
持久化存储、确认机制以及高可用部署方案。
- 持久化意味着消息会被写入磁盘,即使发生故障也能恢复;
- 确认机制要求消费者成功处理完消息后才向MQ发送确认信号;
- 高可用部署则是指通过集群化配置来增强系统的冗余度,避免单点故障造成的服务中断。
注册中心挂了怎么办
- 应用程序应当具备一定的容错能力,比如容忍短暂的服务发现失败,并依赖本地缓存继续运行直到连接恢复正常。
- 应该建立健康检查和重试逻辑,以便及时感知到注册中心的状态变化并在其恢复后迅速重新连接。
- 采用分布式部署策略,确保注册中心自身也是高可用的,比如通过负载均衡器或多活数据中心等方式提高系统的稳定性和可靠性。
编程题:LRU算法
- 结合双向链表(由Go标准库中的
container/list
包提供)和哈希表,以确保O(1)的时间复杂度用于获取和插入操作。 - LRU缓存遵循“最近最少使用”的原则,即当缓存满时,移除最近最少使用的项。
package main
import (
"container/list"
"fmt"
)
// LRUCache defines a cache with Least Recently Used eviction policy.
type LRUCache struct {
capacity int // Cache capacity
cache map[int]*list.Element // Map to store key and its corresponding list element
lru *list.List // Doubly linked list to maintain order of usage
}
// Entry represents an entry in the cache.
type Entry struct {
key int // Key for the cache entry
value int // Value associated with the key
}
// Constructor initializes a new LRUCache instance.
func Constructor(capacity int) LRUCache {
return LRUCache{
capacity: capacity,
cache: make(map[int]*list.Element),
lru: list.New(),
}
}
// Get retrieves the value of the key if the key exists in the cache, otherwise returns -1.
func (cache *LRUCache) Get(key int) int {
if elem, ok := cache.cache[key]; ok {
cache.lru.MoveToFront(elem) // Move the accessed element to the front as it's most recently used
return elem.Value.(Entry).value
}
return -1 // Return -1 if the key is not found
}
// Put inserts or updates the value of the key. If the number of keys exceeds the capacity, it evicts the least recently used item.
func (cache *LRUCache) Put(key int, value int) {
if elem, ok := cache.cache[key]; ok {
cache.lru.MoveToFront(elem) // Update existing entry and move it to the front
elem.Value = Entry{key, value}
} else {
if cache.lru.Len() >= cache.capacity {
// Remove the oldest item from the cache when at capacity
old := cache.lru.Back()
if old != nil {
cache.lru.Remove(old)
delete(cache.cache, old.Value.(Entry).key)
}
}
// Add new entry to the front of the list
elem := cache.lru.PushFront(Entry{key, value})
cache.cache[key] = elem
}
}
func main() {
cache := Constructor(2 /* capacity */)
cache.Put(1, 1)
cache.Put(2, 2)
fmt.Println(cache.Get(1)) // returns 1
cache.Put(3, 3) // evicts key 2
fmt.Println(cache.Get(2)) // returns -1 (not found)
cache.Put(4, 4) // evicts key 1
fmt.Println(cache.Get(1)) // returns -1 (not found)
fmt.Println(cache.Get(3)) // returns 3
fmt.Println(cache.Get(4)) // returns 4
}
欢迎关注 ❤
我们搞了一个免费的面试真题共享群,互通有无,一起刷题进步。
没准能让你能刷到自己意向公司的最新面试题呢。
感兴趣的朋友们可以加我微信:wangzhongyang1993,备注:sf面试群。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。