1.Redisson介绍
Redisson是Redis官方推荐的Java版的Redis客户端。它提供的功能非常多,也非常强大,此处我们只用它的分布式锁功能。
https://github.com/redisson/redisson
2.实现分布式锁
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.0</version>
</dependency>
server:
port: 8070
spring:
application:
name: dkn-provider-store
jackson:
default-property-inclusion: non_null
date-format: YYYY-MM-dd HH:mm:ss
time-zone: GMT+8
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/dkn-provider-store?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: root
logging:
level:
com.dkn: debug
org.springframework.web: trace
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
-- ----------------------------
-- Table structure for store
-- ----------------------------
DROP TABLE IF EXISTS `store`;
CREATE TABLE `store` (
`id` int(11) NOT NULL COMMENT '商品id',
`num` int(11) DEFAULT NULL COMMENT '库存数量',
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4;
-- ----------------------------
-- Records of store
-- ----------------------------
INSERT INTO `store` VALUES ('101', '100');
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
return redisson;
}
}
@Service
public class StoreServiceImpl extends ServiceImpl<StoreMapper,Store> implements StoreService {
@Autowired
RedissonClient redissonClient;
//没有任何锁
@Override
public void buy1(Integer id, Integer num) {
Store store = this.getById(id);
//模拟业务处理休息50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
store.setNum(store.getNum()-1);
this.updateById(store);
}
//同步
@Override
public synchronized void buy2(Integer id, Integer num) {
Store store = this.getById(id);
//模拟业务处理休息50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
store.setNum(store.getNum()-1);
this.updateById(store);
}
//分布式锁
@Override
public void buy3(Integer id, Integer num) {
String lockKey = "buy:product:" + id;
RLock lock = redissonClient.getLock(lockKey);
try {
boolean res = lock.tryLock(10, TimeUnit.SECONDS);
if (res) {
Store store = this.getById(id);
//模拟业务处理休息50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
store.setNum(store.getNum()-1);
this.updateById(store);
}
} catch (Exception ex) {
log.error("获取所超时!", ex);
} finally {
lock.unlock();
}
}
}
@RestController
@RequestMapping("/Store")
public class StoreController {
@Autowired
private StoreService storeService;
//模拟购买商品 没有任何锁
@GetMapping("buy1")
public AjaxResult buy1(Integer id, Integer num){
storeService.buy1(id,num);
return AjaxResult.success();
}
//模拟购买商品 synchronized本地同步锁
@GetMapping("buy2")
public AjaxResult buy2(Integer id, Integer num){
storeService.buy2(id,num);
return AjaxResult.success();
}
//模拟购买商品 redisson分布锁
@GetMapping("buy3")
public AjaxResult buy3(Integer id, Integer num){
storeService.buy3(id,num);
return AjaxResult.success();
}
}
3.测试
@SpringBootTest
@RunWith(SpringRunner.class)
public class StoreBuyTest {
@Autowired
RestTemplate restTemplate;
//测试结果100个商品,库存只减少1个,应该减少50个
@Test
public void testBuy1(){
//模拟50个用户同时购买一件物品
ThreadPoolExecutor executor=new ThreadPoolExecutor(50,50,60, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
for(int i=0;i<50;i++) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("===============================");
restTemplate.getForEntity("http://localhost:8070/Store/buy1?id=101&num=1", String.class);
}
});
}
while (true){
if(executor.getQueue().size()==0 && executor.getActiveCount()==0){
executor.shutdown();
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("end~");
}
//单个服务测试-->测试结果正确,测试结果100个商品,库存只减少50个
@Test
public void testBuy2_1(){
//模拟50个用户同时购买一件物品
ThreadPoolExecutor executor=new ThreadPoolExecutor(50,50,60, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
for(int i=0;i<50;i++) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("===============================");
restTemplate.getForEntity("http://localhost:8070/Store/buy2?id=101&num=1", String.class);
}
});
}
while (true){
if(executor.getQueue().size()==0 && executor.getActiveCount()==0){
executor.shutdown();
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("end~");
}
//启动2个服务测试,端口分别是8070,8071-->测试结果不正常,测试结果100个商品,库存只减少26个,应该减少50个
@Test
public void testBuy2_2(){
//模拟50个用户同时购买一件物品
ThreadPoolExecutor executor=new ThreadPoolExecutor(50,50,60, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
for(int i=0;i<50;i++) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("===============================");
if(RandomUtils.nextInt(0,100)<50){
restTemplate.getForEntity("http://localhost:8070/Store/buy2?id=101&num=1", String.class);
}else{
restTemplate.getForEntity("http://localhost:8071/Store/buy2?id=101&num=1", String.class);
}
}
});
}
while (true){
if(executor.getQueue().size()==0 && executor.getActiveCount()==0){
executor.shutdown();
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("end~");
}
//启动2个服务测试,端口分别是8070,8071-->测试结果正确,测试结果100个商品,库存减少50个
@Test
public void testBuy3(){
//模拟50个用户同时购买一件物品
ThreadPoolExecutor executor=new ThreadPoolExecutor(50,50,60, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
for(int i=0;i<50;i++) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("===============================");
if(RandomUtils.nextInt(0,100)<50){
restTemplate.getForEntity("http://localhost:8070/Store/buy3?id=101&num=1", String.class);
}else{
restTemplate.getForEntity("http://localhost:8071/Store/buy3?id=101&num=1", String.class);
}
}
});
}
while (true){
if(executor.getQueue().size()==0 && executor.getActiveCount()==0){
executor.shutdown();
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("end~");
}
}