Skip to content

Commit

Permalink
refactor : 비관적 락에서 redis 분산락으로 성능 개선
Browse files Browse the repository at this point in the history
  • Loading branch information
Sangyong-Jeon committed May 9, 2023
1 parent 300dd64 commit ba92e2e
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 11 deletions.
1 change: 1 addition & 0 deletions build.gradle
Expand Up @@ -30,6 +30,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'io.springfox:springfox-boot-starter:3.0.0'
implementation 'org.redisson:redisson-spring-boot-starter:3.17.0'
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"
compileOnly 'org.projectlombok:lombok'
Expand Down
Expand Up @@ -22,7 +22,7 @@ class CouponPersistenceAdapter implements LoadCouponPort, CreateCouponPort, Upda

@Override
public Coupon loadCoupon(Long couponId) {
CouponJpaEntity couponJpaEntity = couponRepository.findLockById(couponId)
CouponJpaEntity couponJpaEntity = couponRepository.findById(couponId)
.orElseThrow(CouponNotExistException::new);
return couponMapper.mapToDomainEntity(couponJpaEntity);
}
Expand Down
Expand Up @@ -11,9 +11,14 @@
import com.hexagonal.coupon.common.exception.DuplicateCouponException;
import com.hexagonal.coupon.domain.MemberCoupon;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.concurrent.TimeUnit;

@Service
@Transactional
@RequiredArgsConstructor
Expand All @@ -23,20 +28,38 @@ class CreateMemberCouponService implements CreateMemberCouponUseCase {
private final CreateMemberCouponPort createMemberCouponPort;
private final UpdateCouponStatePort updateCouponStatePort;
private final FindCouponOfMemberPort findCouponOfMemberPort;
private final RedissonClient redissonClient;

@Value("${redis.lock.coupon}")
private String couponLockName;

@Override
public CreateMemberCouponResponse createMemberCoupon(CreateMemberCouponCommand command) {
if (isNotStock(command.getCouponId())) {
throw new CouponNotRemainException();
}
RLock lock = redissonClient.getLock(couponLockName);

if (isDuplicateCoupon(command)) {
throw new DuplicateCouponException();
}
try {
if (!lock.tryLock(10, 3, TimeUnit.SECONDS)) {
throw new RuntimeException("Lock 획득 실패");
}

updateCouponStatePort.decreaseRemainQuantity(command.getCouponId());
MemberCoupon memberCoupon = createMemberCouponPort.createMemberCoupon(command.getMemberId(), command.getCouponId());
return new CreateMemberCouponResponse(memberCoupon.getId(), command.getCouponId(), memberCoupon.getCreateDateTime());
if (isNotStock(command.getCouponId())) {
throw new CouponNotRemainException();
}

if (isDuplicateCoupon(command)) {
throw new DuplicateCouponException();
}

updateCouponStatePort.decreaseRemainQuantity(command.getCouponId());
MemberCoupon memberCoupon = createMemberCouponPort.createMemberCoupon(command.getMemberId(), command.getCouponId());
return new CreateMemberCouponResponse(memberCoupon.getId(), command.getCouponId(), memberCoupon.getCreateDateTime());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (lock != null && lock.isLocked()) {
lock.unlock();
}
}
}

private boolean isNotStock(Long couponId) {
Expand Down
9 changes: 8 additions & 1 deletion src/main/resources/application.yml
Expand Up @@ -13,7 +13,14 @@ spring:
properties:
hibernate:
format_sql: true
open-in-view: false
redis:
host: localhost
port: 6379
logging:
level:
org:
hibernate.SQL: debug
hibernate.SQL: debug
redis:
lock:
coupon: COUPON

0 comments on commit ba92e2e

Please sign in to comment.