面试题答案
一键面试常见问题及解决办法
- 网络抖动
- 问题:在网络抖动情况下,客户端可能与分布式锁服务短暂失联,导致获取锁或释放锁操作出现异常,比如获取锁超时但实际上锁已获取成功,或者释放锁时网络问题导致释放失败,后续其他客户端无法获取锁。
- 解决办法:
- 重试机制:在获取锁或释放锁操作失败时,根据网络错误类型进行适当的重试。例如,使用指数退避算法,每次重试间隔时间逐渐增加,避免短时间内大量无效重试对系统造成压力。示例代码如下:
import (
"fmt"
"math/rand"
"time"
)
func acquireLockWithRetry(lockClient *LockClient, key string, retryCount int) bool {
for i := 0; i < retryCount; i++ {
success, err := lockClient.AcquireLock(key)
if err == nil && success {
return true
}
if err!= nil && isNetworkError(err) {
backoff := time.Duration(1<<i)*time.Second + time.Duration(rand.Intn(100))*time.Millisecond
time.Sleep(backoff)
}
}
return false
}
- **心跳机制**:客户端获取锁后,通过心跳机制定期向锁服务发送存活信号,以维持锁的有效性。如果锁服务长时间未收到心跳,可主动释放锁。
2. 锁的过期时间设置
- 问题:过期时间设置过短,可能导致业务未执行完锁就过期,其他客户端获取锁后造成数据不一致;设置过长,若持有锁的客户端出现故障无法主动释放锁,会造成资源长时间被占用,其他客户端无法获取锁。
- 解决办法:
- 动态调整过期时间:根据业务执行时间的统计数据,动态调整过期时间。例如,记录每次业务执行时间,计算平均执行时间并乘以一定的安全系数(如1.5)作为过期时间。
var averageExecutionTime time.Duration
var executionTimeSamples []time.Duration
func recordExecutionTime(start time.Time) {
duration := time.Since(start)
executionTimeSamples = append(executionTimeSamples, duration)
total := time.Duration(0)
for _, sample := range executionTimeSamples {
total += sample
}
averageExecutionTime = total / time.Duration(len(executionTimeSamples))
}
func getExpirationTime() time.Duration {
return averageExecutionTime * 1.5
}
- **续约机制**:客户端在持有锁期间,若发现锁的剩余过期时间较短,且业务尚未执行完毕,可以向锁服务发送续约请求,延长锁的过期时间。
3. 锁的误释放
- 问题:当多个客户端竞争锁时,可能存在某个客户端A获取锁后,由于网络延迟等原因,锁服务误判该锁已过期,将锁分配给另一个客户端B,此时客户端A完成业务后尝试释放锁,会错误地释放掉客户端B持有的锁。
- 解决办法:
- 唯一标识:在获取锁时,每个客户端生成一个唯一标识(如UUID),并将其与锁关联。释放锁时,先检查当前锁关联的标识是否与自己的标识一致,只有一致时才进行释放操作。
import (
"github.com/google/uuid"
)
func acquireLock(lockClient *LockClient, key string) (bool, string) {
uniqueID := uuid.New().String()
success, err := lockClient.AcquireLock(key, uniqueID)
if err == nil && success {
return true, uniqueID
}
return false, ""
}
func releaseLock(lockClient *LockClient, key string, uniqueID string) bool {
currentID, err := lockClient.GetLockID(key)
if err!= nil {
return false
}
if currentID == uniqueID {
return lockClient.ReleaseLock(key)
}
return false
}
- 死锁
- 问题:在分布式系统中,如果多个客户端以不同顺序获取多个锁,可能会出现死锁情况,例如客户端A获取锁1,客户端B获取锁2,然后客户端A尝试获取锁2,客户端B尝试获取锁1,形成死锁。
- 解决办法:
- 资源排序:对所有需要获取的锁进行全局排序,客户端按照固定顺序获取锁。例如,将锁的名称按照字典序排序,客户端始终先获取名称靠前的锁,再获取名称靠后的锁。
import (
"sort"
"strings"
)
func acquireLocks(lockClient *LockClient, keys []string) bool {
sort.Slice(keys, func(i, j int) bool {
return strings.Compare(keys[i], keys[j]) < 0
})
for _, key := range keys {
success, err := lockClient.AcquireLock(key)
if err!= nil ||!success {
for _, k := range keys[:len(keys) - 1] {
lockClient.ReleaseLock(k)
}
return false
}
}
return true
}
- **超时机制**:在获取锁时设置合理的超时时间,若在超时时间内未能获取到所有需要的锁,则释放已获取的锁,并重新尝试获取。