Резюме:
Этот пост в блоге является четырнадцатым в «Серии реальных боевых действий системы Java Second Kill». система второго убийства». Решите такие проблемы, как «перепроданность запасов» и «повторяющиеся всплески».
содержание:
Что касается промежуточного программного обеспечения кэширования Redis, я полагаю, что все вы более или менее слышали об этом или даже боролись с этим на практике.В этой статье мы интегрируем промежуточное программное обеспечение Redis на основе SpringBoot и реализуем «Распределенную блокировку», а затем контролируем «многопоточный доступ к общим ресурсам с высокой степенью параллелизма» и, наконец, решить «безопасность параллелизма», то есть проблему «перепроданного инвентаря» или «повторяющихся всплесков»!
(1) По соглашению сначала нам нужно добавить сторонние зависимости Redis следующим образом:
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
Затем вам нужно добавить хост, сообщение порта, пароль ключа ссылки и другую информацию, где служба Redis находится в файле конфигурации application.properties, как показано ниже:
(2) Затем нам также необходимо настроить конфигурацию Bean, связанную с операционными компонентами инъекции Redis.Здесь мы в основном настраиваем компоненты операции RedisTemplate и StringRedisTemplate конфигурации инъекции и указываем стратегию сериализации их соответствующих ключей и значений:
// redis的通用化配置
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public RedisTemplate<String,Object> redisTemplate(){
RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//TODO:指定Key、Value的序列化策略
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
@Bean
public StringRedisTemplate stringRedisTemplate(){
StringRedisTemplate stringRedisTemplate=new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
return stringRedisTemplate;
}
}
(3) На данный момент можно сказать, что были сделаны достаточные приготовления, и тогда мы можем использовать это! Чтобы отличить предыдущие логические методы seckill, мы открыли новый логический метод seckill killItemV3 и приняли методы атомарной операции SETNX и EXPIRE Redis для реализации «распределенной блокировки», тем самым контролируя доступ многопоточных потоков с высокой общие ресурсы, а его полный исходный код выглядит следующим образом:
//商品秒杀核心业务逻辑的处理-redis的分布式锁
@Override
public Boolean killItemV3(Integer killId, Integer userId) throws Exception {
Boolean result=false;
if (itemKillSuccessMapper.countByKillUserId(killId,userId) <= 0){
//TODO:借助Redis的原子操作实现分布式锁-对共享操作-资源进行控制
ValueOperations valueOperations=stringRedisTemplate.opsForValue();
final String key=new StringBuffer().append(killId).append(userId).append("-RedisLock").toString();
final String value=RandomUtil.generateOrderCode();
Boolean cacheRes=valueOperations.setIfAbsent(key,value);
if (cacheRes){
stringRedisTemplate.expire(key,30, TimeUnit.SECONDS);
try {
ItemKill itemKill=itemKillMapper.selectByIdV2(killId);
if (itemKill!=null && 1==itemKill.getCanKill() && itemKill.getTotal()>0){
int res=itemKillMapper.updateKillItemV2(killId);
if (res>0){
commonRecordKillSuccessInfo(itemKill,userId);
result=true;
}
}
}catch (Exception e){
throw new Exception("还没到抢购日期、已过了抢购时间或已被抢购完毕!");
}finally {
if (value.equals(valueOperations.get(key).toString())){
stringRedisTemplate.delete(key);
}
}
}
}else{
throw new Exception("Redis-您已经抢购过该商品了!");
}
return result;
}
В приведенном выше коде мы в основном реализуем функцию «распределенной блокировки» посредством следующих операций, в том числе
(1) valueOperations.setIfAbsent(key, value);: Указывает, что если текущий ключ не существует в кеше, значение будет установлено успешно.Наоборот, если ключ уже существует в кеше, значение будет поставил неудачно! С помощью этой функции мы можем комбинировать «однозначное соответствие KillId и UserId ~ то есть один человек может получить только один продукт» в качестве ключа!
(2) Если ключ установлен, то его нужно выпустить в определенный момент времени, то есть stringRedisTemplate.expire(key, 30, TimeUnit.SECONDS); операция может помочь в реализации!
(3) Однако реальная операция «освобождения блокировки» реализуется следующим кодом, то есть для определения того, является ли текущая освобождаемая блокировка блокировкой, полученной в начале, и если да, то освободить ее! Это может быть хорошим способом избежать проблемы случайного удаления блокировок!
if (value.equals(valueOperations.get(key).toString())){
stringRedisTemplate.delete(key);
}
До сих пор распределенные блокировки, основанные на атомарных операциях Redis, использовались для управления доступом к общим ресурсам с высокой степенью параллелизма и многопоточности, чтобы решить проблемы «перепроданного инвентаря» и «повторяющихся всплесков» в сценариях всплесков. для стресс-тестирования ниже.Список пользователей стресс-теста и «общее количество мгновенных убийств» продукта такие же, как и в предыдущей главе, то есть всего=6 книг, и всего 10 пользователей.
Нажмите кнопку запуска JMeter, наблюдайте за выходной информацией консоли и таблицей базы данных item_kill и item_kill_success, вы можете видеть, что результат записи всплеска очень удовлетворительный:
То есть товар с 6 книгами в наличии ~ книгу просто убивают 6 из 10 пользователей! На самом деле, этот результат определенно является счастливым концом для нас и для пользователей!
Хотя актеры остались очень довольны своей концовкой, режиссер заметил, что в спектакле все же есть некоторые огрехи! То есть, если узел Redis выходит из строя после выполнения атомарной операции Redis SetNX и до выполнения Expire, то очень вероятно, что он навсегда попадет в дилемму «Блокировки ключа», то есть после перезапуска, потому что предыдущего ключа не существует. Он выпущен, поэтому этот ключ всегда будет существовать в кеше, то есть соответствующий пользователь не сможет убить продукт за секунды!Это действительно скрытая опасность!
Так как есть скрытые опасности, то мы должны найти способ решить их! Не волнуйтесь, мы продолжим в следующей главе!
Пополнить:
1. В настоящее время общая конструкция и кодирование этой системы seckill завершены.Полный адрес базы данных исходного кода можно скачать здесь:git ee.com/steady jack/… Помните Вилка и Звезда!!
2. Наконец, не забудьте обратить внимание на технический паблик Debug в WeChat: