Redis秒杀
1.全局唯一ID
特性:
-
高可用
-
唯一性
-
高性能
-
安全性
-
递增性
全局唯一ID生成有很多方法例如:UUID、redis自增、snowflake算法、数据库自增等。
此我们失语redis自增的方式。
生成策略
具体代码实现
package com.hmdp.utils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
/**
* 全局唯一id生成器,利用Redis的自增功能保证每个ID的唯一性。
*/
@Component
public class RedisWorker {
/**
* 系统开始时间戳,用于计算时间差
*/
private static final long BEGIN_TIMESTAMP = 1640995200L; // 2022-01-01 00:00:00 的UTC时间戳
/**
* 序列号占用的位数
*/
private static final long COUNT_BITS = 32; // 序列号占用32位
private StringRedisTemplate stringRedisTemplate;
/**
* 构造函数,注入StringRedisTemplate
* @param stringRedisTemplate 用于操作Redis的模板类
*/
public RedisWorker(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 生成下一个唯一ID
* @param keyPrefix ID前缀,用于区分不同业务或类别的ID
* @return 生成的唯一ID
*/
public long nextId(String keyPrefix) {
// 1. 计算当前时间戳(秒)
long nowSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
long time = nowSecond - BEGIN_TIMESTAMP; // 时间差,用于占据高位
// 2. 获取当天序列号,基于Redis自增实现
// 根据日期生成Redis键,保证每天序列号从1开始递增
String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
Long increment = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
// 3. 将时间差和序列号拼接成最终的ID
return time << COUNT_BITS | increment; // 左移位数后与序列号进行位或操作
}
}
2.库存超卖
在高并发下,去抢购商品优惠卷时会出现超卖,我们该如何避免呢?
使用锁
我们可以使用锁的机制去实现,说到锁我们脑海里通常会出现悲观锁和乐观锁,那么这两个分别是什么呢?
-
悲观锁:悲观锁是一种在并发控制中采用的策略,它假定多线程访问数据时发生冲突的可能性非常高,因此采取一种保守的机制来防止冲突。当一个事务使用悲观锁访问某个数据时,会先锁定这些数据,确保在整个事务处理过程中,没有任何其他事务能够修改这些数据。这种做法可以有效避免并发带来的数据不一致性问题,但可能会增加系统的锁开销,并可能引起其他的性能问题,如阻塞、死锁等。
-
乐观锁:一种在并发控制中采用的策略,与悲观锁相反,它假设多线程访问数据时发生冲突的可能性较低。在读取数据时,乐观锁不会立即锁定数据,而是先进行读取操作,然后在更新数据时检查在此期间是否有其他事务修改了数据。如果检测到有其他事务进行了修改,那么更新操作就会失败,否则更新成功。 乐观锁通常通过版本号(Version)或时间戳(Timestamp)来实现。在读取数据时,会记录当前的版本号或时间戳。当事务尝试更新数据时,会再次检查版本号或时间戳是否与之前读取时的一致。如果不一致,说明数据已被其他事务修改,更新操作就会失败;如果一致,说明没有发生冲突,更新操作可以继续。 相比于悲观锁,乐观锁的优点在于减少了锁的使用,从而降低了锁带来的开销和潜在的阻塞问题,提高了并发性能。然而,在高并发且冲突频繁的情况下,乐观锁可能导致大量的重试,这可能增加系统的计算负担。
案例
具体业务代码:
@Override
@Transactional(rollbackFor = Exception.class)
public Result seckillVoucher(Long voucherId) {
// 1.获取优惠卷信息
SeckillVoucher seckillVoucher = seckillVoucherMapper.selectById(voucherId);
// 2.判断秒杀是否开启
// 2.1获取当前时间
LocalDateTime now = LocalDateTime.now();
if (seckillVoucher.getBeginTime().isAfter(now) ) {
return Result.fa