Skip to content

Commit

Permalink
Refactor : 배타적락 -> redisson 분산락으로 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
Kim-Dong-Jun99 committed Jan 25, 2024
1 parent 87accab commit 5adcddf
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 38 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:

- name: Build with Gradle
run: |
./gradlew build
./gradlew bootJar
- name: web docker build and push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
Expand Down
9 changes: 8 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,19 @@ dependencies {
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.2', 'io.jsonwebtoken:jjwt-jackson:0.11.2'
// kafka
implementation 'org.springframework.kafka:spring-kafka'
// redisson
implementation 'org.redisson:redisson-spring-boot-starter:3.26.0'
// lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

// mysql
runtimeOnly 'com.mysql:mysql-connector-j'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.tenten.tentenstomp.domain.trip.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.tenten.tentenstomp.domain.trip.entity.TripItem;
Expand All @@ -10,16 +9,11 @@
import java.util.List;
import java.util.Optional;

import static jakarta.persistence.LockModeType.PESSIMISTIC_WRITE;

public interface TripItemRepository extends JpaRepository<TripItem, Long> {
@Lock(PESSIMISTIC_WRITE)
@Query("SELECT ti FROM TripItem ti JOIN FETCH ti.tourItem WHERE ti.trip.encryptedId = :tripId AND ti.visitDate = :visitDate ORDER BY ti.seqNum ASC")
List<TripItem> findTripItemByTripIdAndVisitDate(@Param("tripId") String tripId, @Param("visitDate") LocalDate visitDate);
@Lock(PESSIMISTIC_WRITE)
@Query("SELECT ti FROM TripItem ti JOIN FETCH ti.trip WHERE ti.id = :tripItemId")
Optional<TripItem> findTripItemForUpdate(@Param("tripItemId") Long tripItemId);
@Lock(PESSIMISTIC_WRITE)
@Query("SELECT ti FROM TripItem ti JOIN FETCH ti.trip WHERE ti.id = :tripItemId")
Optional<TripItem> findTripItemForDelete(@Param("tripItemId") Long tripItemId);
@Query("SELECT CAST(COALESCE(SUM(ti.price),0) as long) FROM TripItem ti WHERE ti.trip.encryptedId = :tripId AND ti.visitDate = :visitDate")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
package org.tenten.tentenstomp.domain.trip.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.tenten.tentenstomp.domain.trip.entity.Trip;

import java.util.Optional;

import static jakarta.persistence.LockModeType.PESSIMISTIC_WRITE;

public interface TripRepository extends JpaRepository<Trip, Long> {

Optional<Trip> findByEncryptedId(String encryptedId);

@Lock(PESSIMISTIC_WRITE)
@Query("SELECT t FROM Trip t LEFT OUTER JOIN FETCH t.tripItems WHERE t.encryptedId = :tripId")
Optional<Trip> findTripForUpdate(@Param("tripId") String tripId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.tenten.tentenstomp.domain.trip.repository.MessageProxyRepository;
import org.tenten.tentenstomp.domain.trip.repository.TripItemRepository;
import org.tenten.tentenstomp.domain.trip.repository.TripRepository;
import org.tenten.tentenstomp.global.common.annotation.WithRedissonLock;
import org.tenten.tentenstomp.global.component.PathComponent;
import org.tenten.tentenstomp.global.component.dto.response.TripPathCalculationResult;
import org.tenten.tentenstomp.global.exception.GlobalException;
Expand Down Expand Up @@ -42,7 +43,7 @@ public class TripItemService {
private final KafkaProducer kafkaProducer;
private final PathComponent pathComponent;
private final MessageProxyRepository messageProxyRepository;

@WithRedissonLock(paramClassType = TripItemPriceUpdateMsg.class)
@Transactional
public void updateTripItemPrice(String tripItemId, TripItemPriceUpdateMsg priceUpdateMsg) {
Optional<TripItem> optionalTripItem = tripItemRepository.findTripItemForUpdate(Long.parseLong(tripItemId));
Expand Down Expand Up @@ -106,7 +107,7 @@ private void setTripItemSeqNums(List<TripItem> pastDateTripItems, List<TripItem>
updateSeqNum(newPastDateTripItems);
updateSeqNum(newDateTripItems);
}

@WithRedissonLock(paramClassType = TripItemVisitDateUpdateMsg.class)
@Transactional
public void updateTripItemVisitDate(String tripItemId, TripItemVisitDateUpdateMsg visitDateUpdateMsg) {
Optional<TripItem> optionalTripItem = tripItemRepository.findTripItemForUpdate(Long.parseLong(tripItemId));
Expand Down Expand Up @@ -167,7 +168,7 @@ private void updateTripPathPrices(Trip trip, TripPathCalculationResult pastDateT
tripPathPriceMap.put(newDate.toString(), newDateTripPath.pathPriceSum());
trip.updateTripPathPriceMap(tripPathPriceMap);
}

@WithRedissonLock(paramClassType = TripItemDeleteMsg.class)
@Transactional
public void deleteTripItem(String tripItemId, TripItemDeleteMsg tripItemDeleteMsg) {
Optional<TripItem> optionalTripItem = tripItemRepository.findTripItemForDelete(Long.parseLong(tripItemId));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.tenten.tentenstomp.domain.trip.repository.MessageProxyRepository;
import org.tenten.tentenstomp.domain.trip.repository.TripItemRepository;
import org.tenten.tentenstomp.domain.trip.repository.TripRepository;
import org.tenten.tentenstomp.global.common.annotation.WithRedissonLock;
import org.tenten.tentenstomp.global.component.PathComponent;
import org.tenten.tentenstomp.global.component.dto.response.TripPathCalculationResult;
import org.tenten.tentenstomp.global.exception.GlobalException;
Expand Down Expand Up @@ -112,7 +113,7 @@ public void enterMember(String tripId) {
);
}


@WithRedissonLock
@Transactional
public void updateTrip(String tripId, TripUpdateMsg tripUpdateMsg) {
Trip trip = tripRepository.findTripForUpdate(tripId).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 여정이 없다.", NOT_FOUND));
Expand Down Expand Up @@ -140,7 +141,7 @@ public void updateTrip(String tripId, TripUpdateMsg tripUpdateMsg) {
kafkaProducer.sendAndSaveToRedis(tripInfoMsg, tripBudgetMsg);

}

@WithRedissonLock
@Transactional
public void addTripItem(String tripId, TripItemAddMsg tripItemAddMsg) {
Trip trip = tripRepository.findTripForUpdate(tripId).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 여정이 없습니다 " + tripId, NOT_FOUND));
Expand Down Expand Up @@ -177,7 +178,7 @@ public void updateBudgetAndItemsAndPath(Trip trip, List<TripItem> tripItems, Str
kafkaProducer.sendAndSaveToRedis(tripBudgetMsg, tripItemMsg, tripPathMsg);
}


@WithRedissonLock
@Transactional
public void updateTripItemOrder(String tripId, TripItemOrderUpdateMsg orderUpdateMsg) {
Trip trip = tripRepository.findTripForUpdate(tripId).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 여정이 없습니다 " + tripId, NOT_FOUND));
Expand All @@ -200,7 +201,7 @@ public void getPathAndItems(String tripId, PathAndItemRequestMsg pathAndItemRequ
kafkaProducer.send(TRIP_ITEM, messageProxyRepository.getTripItemMsg(trip.getEncryptedId(), pathAndItemRequestMsg.visitDate()));
kafkaProducer.send(PATH, messageProxyRepository.getTripPathMsg(trip.getEncryptedId(), pathAndItemRequestMsg.visitDate()));
}

@WithRedissonLock
@Transactional
public void updateTripBudget(String tripId, TripBudgetUpdateMsg tripBudgetUpdateMsg) {
Trip trip = tripRepository.findTripForUpdate(tripId).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 여정이 없습니다 " + tripId, NOT_FOUND));
Expand All @@ -210,7 +211,7 @@ public void updateTripBudget(String tripId, TripBudgetUpdateMsg tripBudgetUpdate
TripBudgetMsg tripBudgetMsg = TripBudgetMsg.fromEntity(trip);
kafkaProducer.sendAndSaveToRedis(tripBudgetMsg, tripInfoMsg);
}

@WithRedissonLock
@Transactional
public void updateTripTransportation(String tripId, TripTransportationUpdateMsg tripTransportationUpdateMsg) {
Trip trip = tripRepository.findTripForUpdate(tripId).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 여정이 없습니다 " + tripId, NOT_FOUND));
Expand All @@ -222,7 +223,7 @@ public void updateTripTransportation(String tripId, TripTransportationUpdateMsg
updateBudgetAndItemsAndPath(trip, tripItems, visitDate);

}

@WithRedissonLock
@Transactional
public TripItemAddResponse addTripItemFromMainPage(String tripId, TripItemAddRequest tripItemAddRequest) {
Trip trip = tripRepository.findTripForUpdate(tripId).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 여정이 없습니다 " + tripId, NOT_FOUND));
Expand Down Expand Up @@ -258,6 +259,7 @@ public void updateCursor(String tripId, CursorUpdateMsg cursorUpdateMsg) {
TripCursorMsg tripCursorMsg = new TripCursorMsg(tripId, cursorUpdateMsg.visitDate(), memberId, member.getNickname(), cursorUpdateMsg.x(), cursorUpdateMsg.y());
kafkaProducer.sendAndSaveToRedis(tripCursorMsg);
}
@WithRedissonLock
@Transactional
public void updateTripDate(String tripId, LocalDate startDate, LocalDate endDate) {
Trip trip = tripRepository.findTripForUpdate(tripId).orElseThrow(() -> new GlobalException("해당 아이디로 존재하는 여정이 없다.", NOT_FOUND));
Expand Down
85 changes: 82 additions & 3 deletions src/main/java/org/tenten/tentenstomp/global/aspect/UtilAspect.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,28 @@
package org.tenten.tentenstomp.global.aspect;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.tenten.tentenstomp.global.common.annotation.WithRedissonLock;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

import static org.tenten.tentenstomp.global.common.constant.ErrorMsgConstant.*;

@Component
@Aspect
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class UtilAspect {

private final RedissonClient redissonClient;
@Around("@annotation(org.tenten.tentenstomp.global.common.annotation.GetExecutionTime)")
public Object getExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Expand All @@ -19,4 +31,71 @@ public Object getExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {

return proceed;
}

@Around("@annotation(org.tenten.tentenstomp.global.common.annotation.WithRedissonLock)")
public Object withRedissonLock(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();

WithRedissonLock withRedissonLock = method.getAnnotation(WithRedissonLock.class);
String tripId = getTripId(
joinPoint.getArgs(),
withRedissonLock.paramClassType(),
withRedissonLock.identifier(),
signature.getParameterNames()
);

RLock lock = redissonClient.getLock(tripId);

long waitTime = withRedissonLock.waitTime();
long leaseTime = withRedissonLock.leaseTime();
TimeUnit timeUnit = withRedissonLock.timeUnit();
try {
boolean available = lock.tryLock(waitTime, leaseTime, timeUnit);
if (!available) {
throw new RuntimeException(REDISSON_TIME_OUT);
}
log.info("redisson lock acquired " + tripId + " thread " + Thread.currentThread().getId());
return joinPoint.proceed(joinPoint.getArgs());
} finally {
lock.unlock();
}
}

private String getTripId(Object[] args, Class<?> paramClassType, String identifier, String[] parameterNames) {
String tripId;
if (paramClassType.equals(Object.class)) {
tripId = getFromPrimitive(parameterNames, args, identifier);
} else {
tripId = getFromObject(args, paramClassType, identifier);
}
return tripId;
}

private String getFromObject(Object[] args, Class<?> paramClassType, String identifier) {
for (Object object : args) {
if (object.getClass().equals(paramClassType)) {
Class<?> aClass = object.getClass();

Object result;
try {
result = aClass.getMethod(identifier).invoke(object);
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw new RuntimeException(e);
}
return result.toString();
}
}
throw new RuntimeException(INVALID_OBJECT_TYPE);
}

private String getFromPrimitive(String[] parameterNames, Object[] args, String identifier) {
for (int i = 0; i < parameterNames.length; i++) {
if (parameterNames[i].equals(identifier)) {
return String.valueOf(args[i]);
}
}
throw new RuntimeException(INVALID_PARAM);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.tenten.tentenstomp.global.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.TimeUnit.SECONDS;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WithRedissonLock {
String identifier() default "tripId";
Class<?> paramClassType() default Object.class;

long waitTime() default 25L;

long leaseTime() default 25L;
TimeUnit timeUnit() default SECONDS;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@
public class ErrorMsgConstant {
public static final String NOT_FOUND_TRIP = "해당 아이디로 존재하는 여정이 없다. ";
public static final String NOT_FOUND_TRIP_ITEM = "해당 아이디로 존재하는 여정 아이템이 없다. ";
public static final String REDISSON_TIME_OUT = "Redisson Time Out";
public static final String INVALID_PARAM = "파라미터에 tripId가 없습니다.";
public static final String INVALID_OBJECT_TYPE = "파라미터 클래스 타입 중 tripId() 메소드를 가진 클래스가 없습니다.";
}
Loading

0 comments on commit 5adcddf

Please sign in to comment.