1.环境
<!-- RedisTemplate -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
host: 192.168.8.128
port: 6379
password: 1234
database: 0
timeout: 10000
jedis:
pool:
max-wait: -1
max-active: -1
max-idle: 20
min-idle: 10
2.Redis配置
package com.yzm.redis10.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class ObjectMapperConfig {
public static final ObjectMapper objectMapper;
private static final String PATTERN = "yyyy-MM-dd HH:mm:ss";
static {
JavaTimeModule javaTimeModule = new JavaTimeModule();
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer());
objectMapper = new ObjectMapper()
// 转换为格式化的json(控制台打印时,自动格式化规范)
//.enable(SerializationFeature.INDENT_OUTPUT)
// Include.ALWAYS 是序列化对像所有属性(默认)
// Include.NON_NULL 只有不为null的字段才被序列化,属性为NULL 不序列化
// Include.NON_EMPTY 如果为null或者 空字符串和空集合都不会被序列化
// Include.NON_DEFAULT 属性为默认值不序列化
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
// 如果是空对象的时候,不抛异常
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
// 反序列化的时候如果多了其他属性,不抛出异常
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
// 取消时间的转化格式,默认是时间戳,可以取消,同时需要设置要表现的时间格式
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.setDateFormat(new SimpleDateFormat(PATTERN))
// 对LocalDateTime序列化跟反序列化
.registerModule(javaTimeModule)
.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
// 此项必须配置,否则会报java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to XXX
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY)
;
}
static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
@Override
public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString(value.format(DateTimeFormatter.ofPattern(PATTERN)));
}
}
static class LocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext deserializationContext) throws IOException {
return LocalDateTime.parse(p.getValueAsString(), DateTimeFormatter.ofPattern(PATTERN));
}
}
}
package com.yzm.redis10.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
/**
* redisTemplate配置
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
jacksonSerializer.setObjectMapper(ObjectMapperConfig.objectMapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 使用StringRedisSerializer来序列化和反序列化redis的key,value采用json序列化
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(jacksonSerializer);
// 设置hash key 和value序列化模式
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(jacksonSerializer);
template.afterPropertiesSet();
return template;
}
}
3.模拟商品
package com.yzm.redis10.entity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Accessors(chain = true)
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
/**
* 商品名
*/
private String name;
/**
* 库存
*/
private Integer leftNum;
}
package com.yzm.redis10.service;
import com.yzm.redis10.entity.Product;
import java.util.List;
public interface ProductService {
Product getById(Integer id);
void updateById(Product product);
List<Product> list();
}
package com.yzm.redis10.service.impl;
import com.yzm.redis10.entity.Product;
import com.yzm.redis10.service.ProductService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ProductServiceImpl implements ProductService {
private static final Map<Integer, Product> productMap;
static {
productMap = new HashMap<>();
productMap.put(productMap.size() + 1, Product.builder().id(productMap.size() + 1).name("苹果").leftNum(10).build());
}
@Override
public Product getById(Integer id) {
return productMap.get(id);
}
@Override
public void updateById(Product product) {
Product update = productMap.get(product.getId());
update.setLeftNum(product.getLeftNum());
productMap.put(product.getId(), update);
}
@Override
public List<Product> list() {
return new ArrayList<>(productMap.values());
}
}
4.锁实现(推荐)
package com.yzm.redis10.redis;
import com.yzm.redis10.entity.Product;
import com.yzm.redis10.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Component
public class RedisLock {
private final StringRedisTemplate redisTemplate;
private final ProductService productService;
public RedisLock(StringRedisTemplate redisTemplate, ProductService productService) {
this.redisTemplate = redisTemplate;
this.productService = productService;
}
private static final int TIMEOUT = 4000;
private static final String LOCK_PREFIX = "secKill:";
private final AtomicInteger i = new AtomicInteger(1);
public void secKill(int productId) {
// 查询库存
Product product = productService.getById(1);
if (product.getLeftNum() < 1) {
log.error("库存不足");
return;
}
// 加锁
String uuid = UUID.randomUUID().toString();
if (!lock(LOCK_PREFIX + productId, uuid)) {
log.info("活动太火爆了,请稍后再操作");
return;
}
//秒杀逻辑
try {
product.setLeftNum(product.getLeftNum() - 1);
productService.updateById(product);
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 解锁
unlock(LOCK_PREFIX + productId, uuid);
log.info("秒杀成功" + i.getAndIncrement());
}
/**
* 加锁
*/
public boolean lock(String key, String value) {
Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofMillis(TIMEOUT));
if (ifAbsent != null && ifAbsent) {
log.info("加锁成功");
return true;
}
return false;
}
/**
* 解锁(lua脚本原子性)
*/
public void unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) " +
"else return 0 " +
"end";
List<String> keys = new ArrayList<>();
keys.add(key);
Long execute = redisTemplate.execute(RedisScript.of(script, Long.class), keys, value);
log.info("解锁成功 = " + execute);
}
}
package com.yzm.redis10.controller;
import com.yzm.redis10.entity.Product;
import com.yzm.redis10.redis.RedisLock;
import com.yzm.redis10.redis.RedisLock2;
import com.yzm.redis10.redis.RedisLock3;
import com.yzm.redis10.service.ProductService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class ProductController {
private final ProductService productService;
private final RedisLock redisLock1;
private final RedisLock2 redisLock2;
private final RedisLock3 redisLock3;
public ProductController(ProductService productService, RedisLock redisLock1,RedisLock3 redisLock3, RedisLock2 redisLock2) {
this.productService = productService;
this.redisLock1 = redisLock1;
this.redisLock2 = redisLock2;
this.redisLock3 = redisLock3;
}
@GetMapping("/list")
public List<Product> list() {
return productService.list();
}
@GetMapping("/inr")
public void inrLeft(Integer id) {
productService.updateById(Product.builder().id(id).leftNum(10).build());
}
@GetMapping("/buy")
public String buy(long time) {
for (int i = 0; i < 100; i++) {
try {
new Thread(() -> redisLock1.secKill(1)).start();
Thread.sleep(time);
} catch (Exception e) {
e.printStackTrace();
}
}
return "秒杀结束";
}
}
http://localhost:8080/list
http://localhost:8080/buy?time=50
http://localhost:8080/list
5.锁实现
package com.yzm.redis10.redis;
import com.yzm.redis10.entity.Product;
import com.yzm.redis10.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Component
public class RedisLock2 {
private final StringRedisTemplate redisTemplate;
private final ProductService productService;
public RedisLock2(StringRedisTemplate redisTemplate, ProductService productService) {
this.redisTemplate = redisTemplate;
this.productService = productService;
}
private static final int TIMEOUT = 4000;
private static final String LOCK_PREFIX = "secKill:";
private final AtomicInteger i = new AtomicInteger(1);
private static final ThreadLocal<String> local = new ThreadLocal<>();
public void secKill(int productId) {
Product product = productService.getById(1);
if (product.getLeftNum() < 1) {
log.error("库存不足");
return;
}
//加锁
String uuid = UUID.randomUUID().toString();
if (!lock(LOCK_PREFIX + productId, uuid)) {
log.info("活动太火爆了,请稍后再操作");
return;
}
//秒杀逻辑
try {
product.setLeftNum(product.getLeftNum() - 1);
productService.updateById(product);
Thread.sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
//解锁
unlock(LOCK_PREFIX + productId, uuid);
log.info("秒杀成功" + i.getAndIncrement());
}
/**
* 加锁
*/
public boolean lock(String key, String value) {
Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, value, Duration.ofMillis(TIMEOUT));
if (ifAbsent != null && ifAbsent) {
log.info("加锁成功");
local.set(value);
return true;
}
return false;
}
/**
* 解锁
*/
public void unlock(String key, String value) {
String localValue = local.get();
if (localValue.equals(value) && localValue.equals(redisTemplate.opsForValue().get(key))) {
log.info("解锁成功");
redisTemplate.delete(key);
local.remove();
}
}
}
@GetMapping("/buy2")
public String buy2(long time) {
for (int i = 0; i < 100; i++) {
try {
new Thread(() -> redisLock2.secKill(1)).start();
Thread.sleep(time);
} catch (Exception e) {
e.printStackTrace();
}
}
return "秒杀结束";
}
http://localhost:8080/buy2?time=50
6.锁实现
package com.yzm.redis10.redis;
import com.yzm.redis10.entity.Product;
import com.yzm.redis10.service.ProductService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Component
public class RedisLock3 {
private final StringRedisTemplate redisTemplate;
private final ProductService productService;
public RedisLock3(StringRedisTemplate redisTemplate, ProductService productService) {
this.redisTemplate = redisTemplate;
this.productService = productService;
}
private static final int TIMEOUT = 4000;
private static final String LOCK_PREFIX = "secKill:";
private final AtomicInteger ato = new AtomicInteger(1);
public void secKill(int productId) {
Product product = productService.getById(1);
if (product.getLeftNum() < 1) {
log.error("库存不足");
return;
}
//加锁
//value包含拥有者标识,具有唯一性,防止任何人都可以解锁
String userId = UUID.randomUUID() + ":" + System.currentTimeMillis() + TIMEOUT;
if (!lock(LOCK_PREFIX + productId, userId)) {
log.error("活动太火爆了,请稍后再操作");
return;
}
//秒杀逻辑
try {
product.setLeftNum(product.getLeftNum() - 1);
productService.updateById(product);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//解锁
unlock(LOCK_PREFIX + productId, userId);
log.info("秒杀成功" + ato.getAndIncrement());
}
/**
* 加锁
*/
public boolean lock(String key, String value) {
//没有锁,持有并加锁
Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(key, value);
if (ifAbsent != null && ifAbsent) {
log.info("加锁成功");
return true;
}
//锁已被持有,判断锁过期
synchronized (RedisLock3.class) {
String redisValue = redisTemplate.opsForValue().get(key);
if (StringUtils.hasLength(redisValue) && Long.parseLong(redisValue.split(":")[1]) < System.currentTimeMillis()) {
String oldValue = redisTemplate.opsForValue().getAndSet(key, value);
if (StringUtils.hasLength(oldValue) && oldValue.equals(redisValue)) {
log.info("锁过期,重新持有锁");
return true;
}
}
}
return false;
}
/**
* 解锁
*/
public void unlock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) " +
"else return 0 " +
"end";
List<String> keys = new ArrayList<>();
keys.add(key);
Long execute = redisTemplate.execute(RedisScript.of(script, Long.class), keys, value);
System.out.println("解锁成功 = " + execute);
}
}
@GetMapping("/buy3")
public String buy3(long time) {
for (int i = 0; i < 100; i++) {
try {
new Thread(() -> redisLock3.secKill(1)).start();
Thread.sleep(time);
} catch (Exception e) {
e.printStackTrace();
}
}
return "秒杀结束";
}
http://localhost:8080/buy3?time=50