综合141-160
141. 什么是 AOP?
展开 中等 VIP 后端 Spring
AOP定义: AOP(Aspect-Oriented Programming)面向切面编程,是一种编程范式,通过预编译方式和运行期动态代理实现程序功能的统一维护。
核心概念:
1. 切面(Aspect)
- 横切关注点的模块化
- 包含通知和切点的定义
2. 连接点(Join Point)
- 程序执行过程中能够应用通知的所有点
- Spring AOP中只支持方法执行连接点
3. 切点(Pointcut)
- 匹配连接点的断言
- 定义在哪些连接点上应用通知
4. 通知(Advice)
- 在特定连接点执行的代码
- 五种类型:前 置、后置、返回、异常、环绕
5. 目标对象(Target Object)
- 被一个或多个切面所通知的对象
6. 织入(Weaving)
- 将切面与目标对象链接,创建代理对象的过程
Spring AOP实现:
注解方式:
@Aspect
@Component
public class LoggingAspect {
// 切点定义
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 前置通知
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
log.info("Method {} called with args: {}",
joinPoint.getSignature().getName(),
Arrays.toString(joinPoint.getArgs()));
}
// 后置通知
@After("serviceLayer()")
public void logAfter(JoinPoint joinPoint) {
log.info("Method {} execution completed",
joinPoint.getSignature().getName());
}
// 返回通知
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
log.info("Method {} returned: {}",
joinPoint.getSignature().getName(), result);
}
// 异常通知
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
log.error("Method {} threw exception: {}",
joinPoint.getSignature().getName(), ex.getMessage());
}
// 环绕通知
@Around("serviceLayer()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
log.info("Method {} executed in {} ms",
joinPoint.getSignature().getName(),
endTime - startTime);
return result;
} catch (Exception e) {
log.error("Method {} execution failed",
joinPoint.getSignature().getName(), e);
throw e;
}
}
}
切点表达式:
// 执行方法切点
@Pointcut("execution(public * com.example.service.*.*(..))")
// 注解切点
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
// 类型切点
@Pointcut("within(com.example.service.*)")
// Bean名称切点
@Pointcut("bean(*Service)")
// 组合切点
@Pointcut("serviceLayer() && @annotation(org.springframework.cache.annotation.Cacheable)")
实际应用场景:
1. 日志记录
@Aspect
@Component
public class AuditAspect {
@Around("@annotation(Auditable)")
public Object auditMethod(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
// 记录操作日志
AuditLog auditLog = AuditLog.builder()
.operation(className + "." + methodName)
.startTime(LocalDateTime.now())
.build();
try {
Object result = joinPoint.proceed();
auditLog.setStatus("SUCCESS");
return result;
} catch (Exception e) {
auditLog.setStatus("FAILED");
auditLog.setErrorMessage(e.getMessage());
throw e;
} finally {
auditLog.setEndTime(LocalDateTime.now());
auditLogService.save(auditLog);
}
}
}
2. 权限控制
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(requiresRole)")
public void checkPermission(JoinPoint joinPoint, RequiresRole requiresRole) {
String[] roles = requiresRole.value();
String currentUser = SecurityContextHolder.getContext()
.getAuthentication()
.getName();
if (!userService.hasAnyRole(currentUser, roles)) {
throw new AccessDeniedException("Access denied for user: " + currentUser);
}
}
}
// 使用示例
@Service
public class UserService {
@RequiresRole({"ADMIN", "USER_MANAGER"})
public void deleteUser(Long userId) {
// 删除用户逻辑
}
}
3. 缓存管理
@Aspect
@Component
public class CacheAspect {
@Around("@annotation(cacheable)")
public Object cache(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
String key = generateCacheKey(joinPoint, cacheable.key());
// 尝试从缓存获取
Object cached = redisTemplate.opsForValue().get(key);
if (cached != null) {
return cached;
}
// 执行方法并缓存结果
Object result = joinPoint.proceed();
redisTemplate.opsForValue().set(key, result,
Duration.ofSeconds(cacheable.expire()));
return result;
}
}
AOP优势:
- 减少代码重复
- 提高模块化程度
- 易于维护和扩展
- 关注点分离
142. 什么是服务降级?
展开 中等 VIP Spring Cloud 微服务 后端 服务降级
服务降级定义: 服务降级是指在系统压力剧增或部分服务不可用时,主动关闭部分非核心功能或返回简化结果,以保证核心功能正常运行的一种保护机制。
降级策略:
1. 功能降级
- 关闭非核心功能
- 简化业务逻辑
- 返回默认值或缓存数据
2. 性能降级
- 降低数据精度
- 减少查询范围
- 简化计算逻辑
3. 容量降级
- 限制并发请求数
- 丢弃部分请求
- 延迟处理非紧急任务
实现方式:
1. Hystrix实现
@Component
public class UserService {
@HystrixCommand(
fallbackMethod = "getUserFallback",
commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50")
}
)
public User getUser(Long userId) {
// 调用远程服务
return userClient.getUser(userId);
}
// 降级方法
public User getUserFallback(Long userId) {
return User.builder()
.id(userId)
.name("默认用户")
.email("default@example.com")
.build();
}
// 降级原因
public User getUserFallback(Long userId, Throwable ex) {
log.error("Get user failed, fallback triggered", ex);
return getUserFallback(userId);
}
}
2. Sentinel实现
@Service
public class OrderService {
@SentinelResource(
value = "getOrder",
fallback = "getOrderFallback",
blockHandler = "getOrderBlocked"
)
public Order getOrder(Long orderId) {
return orderClient.getOrder(orderId);
}
// 降级方法(异常降级)
public Order getOrderFallback(Long orderId, Throwable ex) {
log.warn("Get order fallback triggered for orderId: {}", orderId, ex);
return Order.builder()
.id(orderId)
.status("UNKNOWN")
.build();
}
// 限流降级
public Order getOrderBlocked(Long orderId, BlockException ex) {
log.warn("Get order blocked for orderId: {}", orderId);
return Order.builder()
.id(orderId)
.status("BUSY")
.message("系统繁忙,请稍后重试")
.build();
}
}
3. 自定义降级实现
@Component
public class DegradationManager {
private final Map<String, Boolean> degradationFlags = new ConcurrentHashMap<>();
private final RedisTemplate<String, Object> redisTemplate;
// 检查是否需要降级
public boolean shouldDegrade(String service) {
// 本地缓存检查
Boolean localFlag = degradationFlags.get(service);
if (localFlag != null) {
return localFlag;
}
// Redis配置检查
Boolean redisFlag = (Boolean) redisTemplate.opsForValue()
.get("degradation:" + service);
if (redisFlag != null) {
degradationFlags.put(service, redisFlag);
return redisFlag;
}
return false;
}
// 动态设置降级状态
public void setDegradation(String service, boolean degrade) {
degradationFlags.put(service, degrade);
redisTemplate.opsForValue().set("degradation:" + service, degrade);
}
}
@Service
public class ProductService {
@Autowired
private DegradationManager degradationManager;
public ProductDetails getProductDetails(Long productId) {
if (degradationManager.shouldDegrade("product-detail")) {
// 降级:返回基本信息
return getBasicProductInfo(productId);
}
// 正常:返回完整信息
return getFullProductDetails(productId);
}
private ProductDetails getBasicProductInfo(Long productId) {
// 从缓存或数据库获取基本信息
Product product = productRepository.findById(productId);
return ProductDetails.builder()
.id(product.getId())
.name(product.getName())
.price(product.getPrice())
.build();
}
private ProductDetails getFullProductDetails(Long productId) {
// 获取完整信息(可能调用多个服务)
Product product = productRepository.findById(productId);
List<Review> reviews = reviewService.getReviews(productId);
List<Recommendation> recommendations = recommendationService.getRecommendations(productId);
return ProductDetails.builder()
.id(product.getId())
.name(product.getName())
.price(product.getPrice())
.description(product.getDescription())
.reviews(reviews)
.recommendations(recommendations)
.build();
}
}
降级级别:
1. 页面降级
@Controller
public class HomeController {
@GetMapping("/")
public String home(Model model) {
if (degradationManager.shouldDegrade("homepage")) {
// 返回简化页面
return "home-simple";
}
// 返回完整页面
loadRecommendations(model);
loadPersonalizedContent(model);
return "home-full";
}
}
2. 接口降级
@RestController
public class ApiController {
@GetMapping("/api/search")
public SearchResult search(@RequestParam String keyword) {
if (degradationManager.shouldDegrade("search")) {
// 降级:返回缓存结果
return getCachedSearchResult(keyword);
}
// 正常:实时搜索
return searchService.search(keyword);
}
}
3. 数据降级
@Service
public class ReportService {
public Report generateReport(String type, Date startDate, Date endDate) {
if (degradationManager.shouldDegrade("report")) {
// 降级:减少数据精度
return generateSimplifiedReport(type, startDate, endDate);
}
return generateDetailedReport(type, startDate, endDate);
}
private Report generateSimplifiedReport(String type, Date startDate, Date endDate) {
// 按天聚合而不是按小时
return reportRepository.findDailyReport(type, startDate, endDate);
}
}
降级监控:
@Component
public class DegradationMonitor {
@EventListener
public void onDegradationTriggered(DegradationEvent event) {
// 记录降级事件
log.warn("Degradation triggered for service: {}, reason: {}",
event.getService(), event.getReason());
// 发送告警
alertService.sendAlert("Service degradation", event);
// 记录指标
meterRegistry.counter("degradation.triggered",
"service", event.getService())
.increment();
}
@Scheduled(fixedRate = 30000)
public void checkSystemHealth() {
double cpuUsage = systemMetrics.getCpuUsage();
double memoryUsage = systemMetrics.getMemoryUsage();
if (cpuUsage > 80 || memoryUsage > 85) {
// 自动触发降级
degradationManager.setDegradation("non-essential", true);
} else if (cpuUsage < 60 && memoryUsage < 70) {
// 恢复服务
degradationManager.setDegradation("non-essential", false);
}
}
}
最佳实践:
- 明确核心和非核心功能
- 设计优雅的降级策略
- 建立自动化降级机制
- 监控降级效果
- 及时恢复服务
143. Synchronized 和 ReentrantLock 有什么区别?
展开 中等 Java并发 Java
基本对比:
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 锁类型 | 内置锁(监视器锁) | 显式锁 |
| 使用方式 | 关键字 | API调用 |
| 释放方式 | 自动释放 | 手动释放 |
| 可中断性 | 不可中断 | 可中断 |
| 超时获取 | 不支持 | 支持 |
| 公平性 | 非公平 | 可选公平/非公平 |
| 条件变量 | 单一条件(wait/notify) | 多条件变量 |
| 性能 | JVM优化,较好 | 用户态实现 |
使用示例对比:
synchronized使用:
public class SynchronizedExample {
private final Object lock = new Object();
private int count = 0;
// 方法同步
public synchronized void increment() {
count++;
}
// 代码块同步
public void decrement() {
synchronized (lock) {
count--;
}
}
// 静态方法同步
public static synchronized void staticMethod() {
// 锁的是类对象
}
public synchronized void waitExample() throws InterruptedException {
while (condition) {
wait(); // 等待条件
}
// 执行逻辑
notifyAll(); // 唤醒所有等待线程
}
}
ReentrantLock使用:
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private int count = 0;
// 基本加锁
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock(); // 必须在finally中释放
}
}
// 可中断锁
public void interruptibleIncrement() throws InterruptedException {
lock.lockInterruptibly();
try {
count++;
} finally {
lock.unlock();
}
}
// 尝试获取锁
public boolean tryIncrement() {
if (lock.tryLock()) {
try {
count++;
return true;
} finally {
lock.unlock();
}
}
return false;
}
// 超时获取锁
public boolean timeoutIncrement(long timeout, TimeUnit unit) {
try {
if (lock.tryLock(timeout, unit)) {
try {
count++;
return true;
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return false;
}
// 条件变量使用
public void conditionExample() throws InterruptedException {
lock.lock();
try {
while (someCondition) {
condition.await(); // 等待条件
}
// 执行逻辑
condition.signalAll(); // 唤醒所有等待线程
} finally {
lock.unlock();
}
}
}
高级特性对比:
1. 公平性
// ReentrantLock支持公平锁
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
ReentrantLock unfairLock = new ReentrantLock(false); // 非公平锁
// synchronized总是非公平的
public synchronized void method() {
// 非公平锁
}
2. 多条件变量
public class MultiConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Object[] buffer = new Object[10];
private int count, putIndex, takeIndex;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == buffer.length) {
notFull.await(); // 等待不满条件
}
buffer[putIndex] = x;
if (++putIndex == buffer.length) putIndex = 0;
++count;
notEmpty.signal(); // 通知不空条件
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 等待不空条件
}
Object x = buffer[takeIndex];
if (++takeIndex == buffer.length) takeIndex = 0;
--count;
notFull.signal(); // 通知不满条件
return x;
} finally {
lock.unlock();
}
}
}
3. 锁状态查询
public class LockInfoExample {
private final ReentrantLock lock = new ReentrantLock();
public void showLockInfo() {
System.out.println("Hold count: " + lock.getHoldCount());
System.out.println("Queue length: " + lock.getQueueLength());
System.out.println("Is fair: " + lock.isFair());
System.out.println("Is locked: " + lock.isLocked());
System.out.println("Is held by current thread: " + lock.isHeldByCurrentThread());
}
}
性能对比:
1. JVM优化
// synchronized享受JVM层面的优化
// - 偏向锁:大多数情况下锁不存在竞争
// - 轻量级锁:少量竞争时使用CAS
// - 重量级锁:激烈竞争时才升级
public synchronized void optimizedByJVM() {
// JVM会根据竞争情况自动优化
}
2. 用户态实现
// ReentrantLock是Java层面实现
// - 基于AQS(AbstractQueuedSynchronizer)
// - 更多功能但开销稍大
public void reentrantLockMethod() {
lock.lock();
try {
// 用 户态实现,功能更丰富
} finally {
lock.unlock();
}
}
选择建议:
使用synchronized的场景:
- 简单的同步需求
- 不需要超时或中断
- 代码简洁性要求高
- JVM自动优化场景
使用ReentrantLock的场景:
- 需要可中断的锁获取
- 需要超时的锁获取
- 需要公平锁
- 需要多个条件变量
- 需要锁状态查询
实际应用示例:
@Service
public class OrderService {
private final Map<String, ReentrantLock> orderLocks = new ConcurrentHashMap<>();
// 使用ReentrantLock实现订单级别的锁
public void processOrder(String orderId) {
ReentrantLock orderLock = orderLocks.computeIfAbsent(orderId, k -> new ReentrantLock());
try {
// 尝试获取锁,最多等待5秒
if (orderLock.tryLock(5, TimeUnit.SECONDS)) {
try {
// 处理订单
handleOrder(orderId);
} finally {
orderLock.unlock();
}
} else {
throw new OrderProcessingException("Order is being processed by another thread");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new OrderProcessingException("Order processing interrupted");
}
}
// 使用synchronized实现简单的计数
private final Object countLock = new Object();
private int processedCount = 0;
public void incrementProcessedCount() {
synchronized (countLock) {
processedCount++;
}
}
}
144. Redis 的 Lua 脚本功能是什么?如何使用?
展开 中等 VIP 后端 Redis
Lua脚本简介: Redis的Lua脚本功能允许在Redis服务器端执行Lua脚本,确保多个Redis操作的原子性,减少网络往返次数,提高性能。
核心特性:
- 原子性:脚本执行期间不会被其他命令打断
- 服务器端执行:减少网络开销
- 可复用:脚本可以被缓存和重复使用
- 灵活性:支持复杂的业务逻辑
基本使用:
1. EVAL命令
# 基本语法
EVAL script numkeys key [key ...] arg [arg ...]
# 简单示例
EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 mykey myvalue
# 复杂示例:原子性增加并获取值
EVAL "
local current = redis.call('get', KEYS[1])
if current == false then
current = 0
else
current = tonumber(current)
end
current = current + tonumber(ARGV[1])
redis.call('set', KEYS[1], current)
return current
" 1 counter 5
2. EVALSHA命令
# 加载脚本获取SHA1
SCRIPT LOAD "return redis.call('get', KEYS[1])"
# 返回: "6b1bf486c81ceb7edf3c093f4c48582e38c0e791"
# 使用SHA1执行脚本
EVALSHA 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 1 mykey
Java客户端使用:
1. Jedis实现
@Component
public class RedisLuaService {
@Autowired
private JedisPool jedisPool;
// 分布式锁脚本
private static final String LOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
// 限流脚本
private static final String RATE_LIMIT_SCRIPT =
"local key = KEYS[1] " +
"local limit = tonumber(ARGV[1]) " +
"local window = tonumber(ARGV[2]) " +
"local current = redis.call('get', key) " +
"if current == false then " +
" redis.call('setex', key, window, 1) " +
" return 1 " +
"elseif tonumber(current) < limit then " +
" return redis.call('incr', key) " +
"else " +
" return 0 " +
"end";
public boolean releaseLock(String lockKey, String lockValue) {
try (Jedis jedis = jedisPool.getResource()) {
Object result = jedis.eval(LOCK_SCRIPT,
Collections.singletonList(lockKey),
Collections.singletonList(lockValue));
return "1".equals(result.toString());
}
}
public boolean isAllowed(String key, int limit, int windowSeconds) {
try (Jedis jedis = jedisPool.getResource()) {
Object result = jedis.eval(RATE_LIMIT_SCRIPT,
Collections.singletonList(key),
Arrays.asList(String.valueOf(limit), String.valueOf(windowSeconds)));
return !"0".equals(result.toString());
}
}
}
2. RedisTemplate实现
@Service
public class RedisScriptService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 原子性增减库存脚 本
private static final String STOCK_SCRIPT =
"local stock = redis.call('get', KEYS[1]) " +
"if stock == false then " +
" return {-1, 'Stock not found'} " +
"end " +
"stock = tonumber(stock) " +
"local quantity = tonumber(ARGV[1]) " +
"if stock >= quantity then " +
" redis.call('decrby', KEYS[1], quantity) " +
" return {stock - quantity, 'Success'} " +
"else " +
" return {stock, 'Insufficient stock'} " +
"end";
// 批量操作脚本
private static final String BATCH_SET_SCRIPT =
"for i=1,#KEYS do " +
" redis.call('set', KEYS[i], ARGV[i]) " +
"end " +
"return #KEYS";
public StockResult decreaseStock(String productId, int quantity) {
DefaultRedisScript<List> script = new DefaultRedisScript<>();
script.setScriptText(STOCK_SCRIPT);
script.setResultType(List.class);
List<Object> result = redisTemplate.execute(script,
Collections.singletonList(productId),
String.valueOf(quantity));
if (result != null && result.size() == 2) {
return new StockResult(
Integer.parseInt(result.get(0).toString()),
result.get(1).toString()
);
}
return new StockResult(-1, "Script execution failed");
}
public int batchSet(Map<String, String> keyValues) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(BATCH_SET_SCRIPT);
script.setResultType(Long.class);
List<String> keys = new ArrayList<>(keyValues.keySet());
List<String> values = new ArrayList<>(keyValues.values());
Long result = redisTemplate.execute(script, keys, values.toArray());
return result != null ? result.intValue() : 0;
}
}
实际应用场景:
1. 分布式锁
@Component
public class DistributedLock {
// 获取锁脚本
private static final String ACQUIRE_LOCK_SCRIPT =
"if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +
" redis.call('expire', KEYS[1], ARGV[2]) " +
" return 1 " +
"else " +
" return 0 " +
"end";
// 释放锁脚本
private static final String RELEASE_LOCK_SCRIPT =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
public boolean acquireLock(String lockKey, String lockValue, int expireSeconds) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(ACQUIRE_LOCK_SCRIPT);
script.setResultType(Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList(lockKey),
lockValue, String.valueOf(expireSeconds));
return Long.valueOf(1L).equals(result);
}
public boolean releaseLock(String lockKey, String lockValue) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(RELEASE_LOCK_SCRIPT);
script.setResultType(Long.class);
Long result = redisTemplate.execute(script,
Collections.singletonList(lockKey),
lockValue);
return Long.valueOf(1L).equals(result);
}
}
2. 滑动 窗口限流
@Component
public class SlidingWindowRateLimit {
private static final String SLIDING_WINDOW_SCRIPT =
"local key = KEYS[1] " +
"local window = tonumber(ARGV[1]) " +
"local limit = tonumber(ARGV[2]) " +
"local now = tonumber(ARGV[3]) " +
"-- 清理过期数据 " +
"redis.call('zremrangebyscore', key, 0, now - window * 1000) " +
"-- 获取当前窗口内的请求数 " +
"local current = redis.call('zcard', key) " +
"if current < limit then " +
" -- 添加当前请求 " +
" redis.call('zadd', key, now, now) " +
" redis.call('expire', key, window) " +
" return {1, limit - current - 1} " +
"else " +
" return {0, 0} " +
"end";
public RateLimitResult checkLimit(String key, int windowSeconds, int limit) {
DefaultRedisScript<List> script = new DefaultRedisScript<>();
script.setScriptText(SLIDING_WINDOW_SCRIPT);
script.setResultType(List.class);
long now = System.currentTimeMillis();
List<Object> result = redisTemplate.execute(script,
Collections.singletonList(key),
String.valueOf(windowSeconds),
String.valueOf(limit),
String.valueOf(now));
if (result != null && result.size() == 2) {
boolean allowed = "1".equals(result.get(0).toString());
int remaining = Integer.parseInt(result.get(1).toString());
return new RateLimitResult(allowed, remaining);
}
return new RateLimitResult(false, 0);
}
}
3. 排行榜操作
@Component
public class LeaderboardService {
// 更新排行榜脚本
private static final String UPDATE_LEADERBOARD_SCRIPT =
"local key = KEYS[1] " +
"local member = ARGV[1] " +
"local score = tonumber(ARGV[2]) " +
"local maxSize = tonumber(ARGV[3]) " +
"-- 添加或更新成员分数 " +
"redis.call('zadd', key, score, member) " +
"-- 获取排行榜大小 " +
"local size = redis.call('zcard', key) " +
"-- 如果超过最大大小,移除最低分数的成员 " +
"if size > maxSize then " +
" redis.call('zremrangebyrank', key, 0, size - maxSize - 1) " +
"end " +
"-- 返回成员当前排名(从1开始) " +
"return redis.call('zrevrank', key, member) + 1";
public int updateScore(String leaderboard, String member, double score, int maxSize) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setScriptText(UPDATE_LEADERBOARD_SCRIPT);
script.setResultType(Long.class);
Long rank = redisTemplate.execute(script,
Collections.singletonList(leaderboard),
member, String.valueOf(score), String.valueOf(maxSize));
return rank != null ? rank.intValue() : -1;
}
}
脚本管理:
@Component
public class ScriptManager {
private final Map<String, String> scriptShas = new ConcurrentHashMap<>();
@PostConstruct
public void loadScripts() {
// 预加载脚本
loadScript("rate_limit", RATE_LIMIT_SCRIPT);
loadScript("distributed_lock", LOCK_SCRIPT);
}
private void loadScript(String name, String script) {
try (Jedis jedis = jedisPool.getResource()) {
String sha = jedis.scriptLoad(script);
scriptShas.put(name, sha);
log.info("Loaded script '{}' with SHA: {}", name, sha);
}
}
public Object executeScript(String scriptName, List<String> keys, List<String> args) {
String sha = scriptShas.get(scriptName);
if (sha == null) {
throw new IllegalArgumentException("Script not found: " + scriptName);
}
try (Jedis jedis = jedisPool.getResource()) {
return jedis.evalsha(sha, keys, args);
}
}
}
最佳实践:
- 预加载常用脚本提高性能
- 合理使用KEYS和ARGV参数
- 注意脚本的原子性特性
- 避免在脚本中执行耗时操作
- 处理脚本执行异常情况
145. HTTP 2.0 和 3.0 有什么区别?
展开 中等 VIP 网络
主要区别对比:
| 特性 | HTTP/2.0 | HTTP/3.0 |
|---|---|---|
| 传输协议 | TCP | QUIC (UDP) |
| 连接建立 | TCP握手 + TLS握手 | QUIC握手(合并) |
| 队头阻塞 | 应用层解决,传输层仍存在 | 完全解决 |
| 连接迁移 | 不支持 | 支持 |
| 0-RTT | TLS 1.3支持 | 原生支持 |
| 拥塞控制 | TCP内核实现 | 用户空间实现 |
HTTP/2.0特性回顾:
1. 多路复用
// HTTP/2在单个TCP连接上多路复用
// 但TCP层面仍可能有队头阻塞
连接: TCP Connection
├── Stream 1: GET /api/users
├── Stream 3: GET /api/orders
├── Stream 5: POST /api/products
└── Stream 7: GET /api/stats
2. 头部压缩(HPACK)
# HTTP/2 HPACK压缩
:method: GET
:path: /api/users
:authority: api.example.com
# 重复头部使用索引引用
HTTP/3.0革新特性:
1. QUIC协议基础
HTTP/3 over QUIC over UDP
应用层: HTTP/3
传输层: QUIC
网络层: UDP
2. 连接建立优化
# HTTP/2 (TCP + TLS)
客户端 -> 服务器: SYN
服务器 -> 客户端: SYN-ACK
客户端 -> 服务器: ACK
客户端 -> 服务器: TLS Client Hello
服务器 -> 客户端: TLS Server Hello + Certificate
... (更多TLS握手)
总计: 2-3 RTT
# HTTP/3 (QUIC)
客户端 -> 服务器: QUIC Initial Packet (包含加密握手)
服务器 -> 客户端: QUIC Response
客户端可立即发送应用数据
总计: 1 RTT (0-RTT 重连)
3. 真正解决队头阻塞
// HTTP/2: TCP层面仍 有队头阻塞
TCP丢包影响所有Stream ❌
// HTTP/3: 独立的Stream
Stream 1: 正常传输 ✅
Stream 2: 丢包重传 🔄
Stream 3: 正常传输 ✅
Stream 4: 正常传输 ✅
// Stream间相互独立
实际部署差异:
HTTP/2配置(Nginx):
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# HTTP/2 push
location / {
http2_push /css/style.css;
http2_push /js/app.js;
}
}
HTTP/3配置(Nginx):
server {
listen 443 ssl http2; # HTTP/2 回退
listen 443 http3 reuseport; # HTTP/3
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_protocols TLSv1.3; # HTTP/3需要TLS 1.3
# 添加Alt-Svc头告知客户端支持HTTP/3
add_header Alt-Svc 'h3=":443"; ma=86400';
}
连接迁移特性:
// HTTP/3支持连 接迁移
// 用户从WiFi切换到移动网络
原连接ID: Connection-ID-12345
新IP地址: 192.168.1.100 -> 10.0.0.50
连接状态: 保持不变 ✅
// HTTP/2需要重新建立连接
原TCP连接: WiFi IP + Port
新连接: 移动网络 IP + Port
需要: 重新握手 ❌
0-RTT连接恢复:
// HTTP/3 0-RTT恢复
客户端缓存: 服务器配置 + 会话票据
重连时: 立即发送应用数据
服务器: 立即处理请求
// 示例时序
T0: 客户端发送0-RTT数据 + 握手
T1: 服务器响应数据 + 握手确认
// 无需等待握手完成
性能优势对比:
延迟比较:
首次连接建立:
HTTP/2: 2-3 RTT
HTTP/3: 1 RTT
重连:
HTTP/2: 2-3 RTT
HTTP/3: 0 RTT
弱网络环境:
HTTP/2: 受TCP队头阻塞影响严重
HTTP/3: 独立Stream,影响较小
Java客户端支持:
HTTP/2客户端:
// Java 11+ HttpClient
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
HTTP/3客户端(实验性):
// 使用支持QUIC的客户端库
// 例如:quiche-java, netty-quic等
QuicClient client = QuicClient.newBuilder()
.serverName("example.com")
.build();
QuicConnection connection = client.connect()
.join();
QuicStream stream = connection.createStream()
.join();
// 发送HTTP/3请求
stream.writeData(httpRequestBytes);
浏览器支持检测:
// 检测HTTP/3支持
if ('serviceWorker' in navigator) {
// 现代浏览器可能支持HTTP/3
fetch('/api/test', {
// 浏览器自动协商最佳协议版本
}).then(response => {
console.log('Protocol:', response.headers.get('alt-svc'));
});
}
// 检查连接信息(Chrome DevTools)
// Network -> 连接ID列可以看到h3/h2标识
部署注意事项:
HTTP/3部署挑战:
# 1. 防火墙配置
# 需要开放UDP 443端口
iptables -A INPUT -p udp --dport 443 -j ACCEPT
# 2. 负载均衡器支持
# 需要支持QUIC的负载均衡器
# 如:Cloudflare, AWS ALB等
# 3. CDN支持
# 需要CDN提供商支持HTTP/3
# 如:Cloudflare, AWS CloudFront等
渐进式升级策略:
// 服务端支持多协议
server {
listen 443 ssl http2; # HTTP/2
listen 443 http3 reuseport; # HTTP/3
# 协议协商
add_header Alt-Svc 'h3=":443"; ma=86400, h2=":443"; ma=86400';
}
// 客户端自动降级
HTTP/3尝试 -> HTTP/2回退 -> HTTP/1.1兜底
监控和调试:
// 监控HTTP版本分布
const protocolMetrics = {
'http/1.1': 0,
'http/2': 0,
'http/3': 0
};
// Chrome DevTools
// Network面板 -> Protocol列
// 可看到每个请求使用的协议版本
// 服务器日志
log_format http3 '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'protocol=$server_protocol';
未来发展:
- HTTP/3生态逐步成熟
- QUIC协议不断优化
- 更多服务端和客户端支持
- 网络基础设施逐步升级