π λͺ©μ°¨
Spring Bootλ₯Ό κΈ°λ°μΌλ‘ μ΄μ»€λ¨Έμ€ μλΉμ€λ₯Ό ꡬννλ©΄μ λμμ± μ΄μκ° λ°μνλ€.
Redisμ RedissonλΌμ΄λΈλ¬λ¦¬λ₯Ό μ΄μ©ν΄ λμμ± μ΄μλ₯Ό ν΄κ²°νλ λ°©λ²μ λν΄ μμ보μ.
π λμμ± μ μ΄λ?
λμμ± μ μ΄(Concurrency Control)λ, μ¬λ¬ μ€λ λλ νλ‘μΈμ€κ° λ°μ΄ν°λ₯Ό 곡μ νλ μμ μ΄ λμμ μ€νλ λ κ·Έ μ€ν μμλ 쑰건μ μ μ΄νμ¬ λ°μ΄ν°μ 무결μ±μ μ μ§νκ³ μμμΉ λͺ»ν λμμ λ°©μ§νλ κ²μ λ§νλ€. μ¬λ¬ μμ κ°μ μνΈμμ©, μμ 곡μ , κ²½μ 쑰건 λ±μ κ΄λ¦¬νμ¬ νλ‘κ·Έλ¨μ μμ μ±κ³Ό μ±λ₯μ 보μ₯νλ λ° μ€μν μν μ νλ€.
π λμμ± μ μ΄μ λͺ©μ
- μ¬λ¬ μ¬μ©μκ° DBμ μ κ·ΌνλλΌλ λ°μ΄ν°μ μΌκ΄μ±μ 보μ₯νκ³ λ°μ΄ν°μ 무결μ±μ μ μ§
- μλ₯Ό λ§μ‘±νλ©° λ°μ΄ν°λ² μ΄μ€ μμ€ν μ μ±λ₯κ³Ό ν¨μ¨μ±μ μ μ§νλ κ²
π λμμ± μ μ΄ κΈ°λ²
λμμ± μ΄μλ₯Ό ν΄κ²°νκΈ° μν΄ λ€μν λ°©λ²μ μκ°ν΄ λ³Ό μ μλ€. λμμ± μ μ΄ κΈ°λ²μλ μ¬λ¬ κ°μ§κ° μλλ° κ·Έμ€, LockingκΈ°λ²μ μ μ©ν΄λ³΄λ €κ³ νλ€. LockingκΈ°λ²μ λ°μ΄ν°μ μ κΈ(Lock)μ μ€μ νλ©΄ λ€λ₯Έ νΈλμμ μ ν΄λΉ λ°μ΄ν°μ μ κΈμ΄ ν΄μ (UnLock)λ λκΉμ§ μ κ·Ό, μμ , μμ κ° λΆκ°λ₯νκ² ν΄λμ λ°©λ²μ΄λ€.
- synchronized
- νμ¬ μ κ·Όνκ³ μλ λ©μλμ νλμ μ€λ λλ§ μ κ·Όν μ μλλ‘ λ³΄μ₯ λ° λμνλ€. μλ°μμ μ§μνλ€.
- μ€λ λ νλμ© μμ°¨μ μΌλ‘ λ°μ΄ν°μ μ κ·Όν μ μλλ‘ ν΄μ€λ€.
- μ ν리μΌμ΄μ μ νλλ§ λμ°λ κ²½μ°λ 무κ΄νμ§λ§, μλ²κ° μ¬λ¬ λμΌ κ²½μ°μλ μ¬λ¬ κ°μ μΈμ€ν΄μ€κ° μ‘΄μ¬νλ κ²κ³Ό λμΌνκΈ° λλ¬Έμ μ€μ§μ μΈ μ΄μ νκ²½μμλ λ°μ΄ν°μ μ ν©μ±μ 보μ₯ν μ μλ€.
- Pessimistic Lock(λΉκ΄μ λ½)
- λ°μ΄ν°μ Lockμ κ±Έμ΄μ μ ν©μ±μ λ§μΆλ λ°©λ²μ΄λ€. λ°μ΄ν°λ₯Ό λ³κ²½νκΈ° μ μ λ½μ νλνλ€.
- ν λ²μμ¬λ¬ κ°μ ν μ΄λΈμ μμ νλ €κ³ νμ λ, νλμ νΈλμμ μΌλ‘ λ¬Άμ¬μκΈ° λλ¬Έμ μμ νλκ° μ€ν¨νλ©΄ λ°μ΄ν°λ² μ΄μ€λ¨μμ μ 체 μλ λ‘€λ°±μ΄ μΌμ΄λκ² λλ€.
- λμμ μ¬λ¬ μ¬μ©μκ° λ°μ΄ν°μ μ κ·Όν λ μ±λ₯ λ¬Έμ λ₯Ό λ°μμν¬ μ μλ€.
- νΉν λ°μ΄ν° λ² μ΄μ€λ λΆμ° μμ€ν μμ μ¬μ©λ κ²½μ°, λ½μ΄ λ°μν λ λ€λ₯Έ μμ μ΄ λκΈ°ν΄μΌ νλ―λ‘ μ²λ¦¬λμ΄ κ°μν μ μλ€.
- Optimistic Lock(λκ΄μ λ½)
- λΉκ΄μ λ½κ³Ό λ€λ₯΄κ² νΈλμμ μ μ‘μ§ μκΈ° λλ¬Έμ μΆ©λ κ°μ§λ₯Ό ν μ μλ€. μ±λ₯μ μΌλ‘λ λΉκ΄μ λ½λ³΄λ€ μ’λ€.
- μΆ©λμ΄ λ°μνμ¬ μμ μ λͺ»ν λΆλΆμ λν΄μλ λ‘€λ°±μ λν μ± μμ μ ν리μΌμ΄μ λ¨μμ μ§λ©°, μ ν리μΌμ΄μ μμ μλμΌλ‘ λ‘€λ°±μ ν΄μ€μΌ νλ€.
- μΆ©λμ΄ λ§μ΄ μμλκ±°λ μΆ©λμ΄ λ°μνμ λ, λΉμ©μ΄ λ§μ΄ λ€ κ²μ΄λΌκ³ νλ¨λλ κ³³μμλ μ¬μ©νμ§ μλ κ²μ΄ μ’λ€.
- μν°ν°μ @Versionμ μΆκ°ν΄μ£Όμ΄μΌ νλ€. μ‘°νμ κ°±μ μ λ²μ λΉκ΅λ₯Ό ν΅ν΄μ μ ν©μ±μ λ§μΆλ€.
- Named Lock
- λ§ κ·Έλλ‘ μ΄λ¦μ κ°μ§ Lockμ΄λ€. λ°μ΄ν°λ² μ΄μ€μ λ³λμ μ μ₯μμ μ΄λ¦μ κ°μ§ λ½μ μ μ₯νκ³ , ν΄λΉ μ΄λ¦μ λν λ½μ ν΄μ λκΈ° μ κΉμ§λ λ€λ₯Έ νΈλμμ μμ λ½μ νλν μ μλ€.
- λ³΄ν΅ λΆμ°λ½μμ μ¬μ©νλ λ°©λ²μ΄λ©°, MySQLμμλ§ μ¬μ©μ΄ κ°λ₯νλ€.
- λ½μ μ¬μ©νκΈ° μν΄ λ³λμ 컀λ₯μ νμ κ΄λ¦¬ν΄μΌ νκ³ , λ½μ κ΄λ ¨λ λΆνλ₯Ό RDSμμ λ°λλ€λ λ¨μ μ΄ μλ€.
- Lettuce
- redisλ₯Ό dependencyμ μΆκ°νλ©΄ λ³λμ λΌμ΄λΈλ¬λ¦¬ μ€μΉ μμ΄ μ¬μ©ν μ μλ€.
- μ€ν λ½ λ°©μμΌλ‘ λμμ λ§μ μ€λ λκ° λ½ νλμ λκΈ° μ€μ΄λΌλ©΄, redis μλ²μ λΆνλ₯Ό λ°μμν¬ μ μλ€.
- redisμ κ°μ΄ μ‘΄μ¬νμ§ μμΌλ©΄ μΈν νκ² νκ³ , κ°μ΄ μΈν λμλμ§ μ¬λΆλ₯Ό λ¦¬ν΄ κ°μΌλ‘ λ°μ λ½μ νλνλ λ° μ±κ³΅νλ€. λ½μ μ μ νλ μκ°μ΄ μ§§μΌλ©΄ μ 리νμ§λ§ μ€λ λκ° λ½μ μ€λ μ μ νκ³ μλ κ²½μ° CPUμ λΆλ΄μ μ€ μ μλ€.
- λ½μ λ§λ£ μκ°μ μ§μ ν μ μκΈ° λλ¬Έμ λ½ νλμ λν μ¬μλκ° νμ μλ κ²½μ°μ μ¬μ©νλ©΄ μ’λ€.
- Redisson
- λ³λμ λΌμ΄λΈλ¬λ¦¬λ₯Ό μΆκ°ν΄μΌ μ¬μ©ν μ μλ€.
- Pub/SubκΈ°λ₯μ μ¬μ©νμ¬ μ€ν λ½μ΄ redisμ μ£Όλ μμ²λ νΈλν½μ μ€μλ€.
- λ½μ λ§λ£ μκ°μ μ§μ ν μ μμ΄, λ½ νλμ μ¬μλν΄μΌ νλ κ²½μ°μ μ¬μ©νλ©΄ μ’λ€.
βοΈ μμ λ°©λ²λ€ μ€, λλ Redisson λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νμ¬ λμμ± μ μ΄λ₯Ό ꡬννλ€.
ν νλ‘μ νΈλ‘ μ΄μ»€λ¨Έμ€ μλΉμ€λ₯Ό ꡬννκ² λμλλ°, μ΄μ»€λ¨Έμ€μ νΉμ±μ μ¬λ¬ μ¬μ©μκ° λμμ ν κ°μ§ μνμ ꡬ맀νκΈ° μν μμ²μ΄ λ§μ κ²μ΄κ³ , μ¬λ¬ μ¬μ©μκ° λμμ ν κ°μ§μ μνμ λν΄ μ£Όλ¬Έμ μμ±μ μμ²νλ©΄ μλͺ»λ νΈλμμ μ΄ μμ±λ μ μλ€κ³ νλ¨νμλ€. μ΄λ¬ν λμμ± μ΄μλ₯Ό ν΄κ²°νκΈ° μν΄ λ°©λ²μ μ°Ύμ보λ μ€, λ§μΉ¨ μΈνλΌμ redisκ° κ΅¬μΆλμ΄ μμκ³ redisμ μ¬λ¬ κΈ°λ₯ μ€μ λΆμ° λ½μ μ μ©νκ² λμλ€.
π λμμ± μ μ΄ μ μ© μ
- ν μ€νΈ μλ리μ€: μ΄λΉ 100λ²μ μμ²μ νμ λ, μνμ μ¬κ³ λ 100κ° -> 0κ°, μ£Όλ¬Έμ 100κ° μμ±λμ΄μΌ νλ€.
- ν μ€νΈ κ²°κ³Ό: 3μ΄ λμ μ΄ 300λ²μ μμ²μ ν κ²°κ³Ό, μνμ μ¬κ³ λ 100κ°-> 0κ°λ‘ μ€μκ³ , μ£Όλ¬Έμ 107κ°κ° μμ±λμλ€.
- ν μ€νΈ μλ리μ€: μ΄λΉ 100λ²μ μμ²μ νμ λ, μνμ μ¬κ³ λ 1000κ° -> 900κ°, μ£Όλ¬Έμ 100κ° μμ±λμ΄μΌ νλ€.
- ν μ€νΈ κ²°κ³Ό: 3μ΄ λμ μ΄ 300λ²μ μμ²μ ν κ²°κ³Ό, μνμ μ¬κ³ λ 1000κ°-> 727κ°λ‘ μ€μκ³ , μ£Όλ¬Έμ 281κ°κ° μμ±λμλ€.
μ¬λ¬ λ²μ μμ²μ 보λμ λ, λμμ± μ΄μλ‘ μΈν΄ μ€μ΄λ μνμ μ¬κ³ μμ μ£Όλ¬Έμ μμ± κ°μκ° λ§μ§ μλλ€. λ°μ΄ν°μ μ ν©μ±μ΄ κΉ¨μ§ κ²μ΄λ€. μ΄λ₯Ό λΆμ° λ½μ μ΄μ©ν λμμ± μ μ΄λ₯Ό ν΅ν΄ ν΄κ²°ν΄ 보λλ‘ νμ.
π Redissonμ μ΄μ©ν λΆμ° λ½ κ΅¬ννκΈ°
Springμμ Redissonμ μ¬μ©νλ €λ©΄ build.gradleμ λ€μκ³Ό κ°μ΄ μμ‘΄μ±μ μΆκ°ν΄μ£Όμ΄μΌ νλ€.
dependencies {
// redisson
implementation 'org.redisson:redisson-spring-boot-starter:3.23.2'
}
RedissonClientλ₯Ό μ¬μ©νκΈ° μν΄ Redis Configλ₯Ό μ€μ ν΄ μ€λ€.
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String redisHost;
@Value("${spring.data.redis.port}")
private int redisPort;
private static final String REDISSON_HOST_PREFIX = "redis://";
@Bean
public RedissonClient redissonClient() {
RedissonClient redisson = null;
Config config = new Config();
config.useSingleServer().setAddress(REDISSON_HOST_PREFIX + redisHost + ":" + redisPort);
redisson = Redisson.create(config);
return redisson;
}
@Bean
public RedissonConnectionFactory redisConnectionFactory(RedissonClient redissonClient) {
return new RedissonConnectionFactory(redissonClient);
}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
String key(); // λ½μ μ΄λ¦
TimeUnit timeUnit() default TimeUnit.SECONDS; // λ½μ μκ° λ¨μ
long waitTime() default 5L; // λ½μ κΈ°λ€λ¦¬λ μκ°
long leaseTime() default 3L; // λ½ μλ μκ°
}
Redissonμ μ΄μ©ν λ‘μ§μ λ€μκ³Ό κ°λ€. μ΄λ Έν μ΄μ μ μΈ μ μνλλ AOPν΄λμ€μ΄λ€.
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class DistributedLockAop {
private static final String REDISSON_LOCK_PREFIX = "LOCK: ";
private static final long RETRY_DELAY = 3L; // μ¬μλ μ¬μ΄μ λκΈ° μκ°
private static final long MAX_RETRY_COUNT = 5L; // μ΅λ μ¬μλ νμ
private final RedissonClient redissonClient;
private final AopForTransaction aopForTransaction;
@Around("...")
public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
DistributedLock distributedLock = method.getAnnotation(DistributedLock.class);
String key = REDISSON_LOCK_PREFIX + CustomSpringELParser.getDynamicValue(
signature.getParameterNames(),
joinPoint.getArgs(),
distributedLock.key());
RLock rLock = redissonClient.getLock(key);
// λ½ νλ μλ λ° μ¬μλ λ‘μ§
int retryCount = 0;
while (retryCount < MAX_RETRY_COUNT) {
try { // λ½μ νλνλ €κ³ μλνκ³ , μ μλ μκ°μ΄ μ§λλ©΄ μ κΈμ ν΄μ νλ€.
boolean available = rLock.tryLock(distributedLock.waitTime(),
distributedLock.leaseTime(), distributedLock.timeUnit());
if (available) {
System.out.println("Lock acquired with key: " + key);
try {
return aopForTransaction.proceed(joinPoint);
} finally {
if (rLock.isLocked() && rLock.isHeldByCurrentThread()) {
// isLocked(): νΉμ λ½μ΄ νμ¬ Redis μλ²μ λ½ λμ΄ μλμ§ μ¬λΆλ₯Ό νμΈ
// isHeldByCurrentThread(): νΉμ λ½μ νμ¬ μ€λ λκ° λ³΄μ νκ³ μλμ§ μ¬λΆλ₯Ό νμΈ
System.out.println("Lock released with key: " + key);
rLock.unlock();
}
}
} ... {
// λ½ νλ μ€ν¨ μ λκΈ°νκ³ μ¬μλνλ λ‘μ§
}
} catch (InterruptedException e) {
log.error("DistributedLock lock interrupted");
System.out.println("DistributedLock lock interrupted");
throw new InterruptedException(e.getMessage());
}
}
throw new IllegalStateException("DistributedLock lock failed after retries");
}
}
λμμ± νκ²½μμμ λ°μ΄ν° μ ν©μ±μ 보μ₯νκΈ° μν΄ Propagation.REQUIRES_NEW μ΅μ μ μ§μ νκ³ , νΈλμμ μ»€λ° μ΄νμ λ½μ΄ ν΄μ λκ² μ²λ¦¬νλ€.
@Component
public class AopForTransaction {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Object proceed(final ProceedingJoinPoint joinPoint) throws Throwable {
return joinPoint.proceed();
}
}
λΆμ° λ½μ ꡬνν μ΄λ Έν μ΄μ μ μ μ©νμ¬ ν μ€νΈλ₯Ό μ§νν΄ λ³΄μ.
@Service
@RequiredArgsConstructor
@Component
public class OrderService {
...
@DistributedLock(key = "#orderProducts.![productId].toString()")
public OrderInfo createOrder(
Long memberId,
Long receiverId,
Long totalAmount,
PayType type,
List<OrderProduct> orderProducts
) {
...
OrderEntity saved = orderJpaRepository.save(orderEntity);
historyService.createHistory(saved.getId(), orderProducts);
return ...
}
π λμμ± μ μ΄ μ μ© ν
- ν μ€νΈ μλ리μ€: μ΄λΉ 100λ²μ μμ²μ νμ λ, μνμ μ¬κ³ λ 100κ° -> 0κ°, μ£Όλ¬Έμ 100κ° μμ±λμ΄μΌ νλ€.
- ν μ€νΈ κ²°κ³Ό: 3μ΄ λμ μ΄ 300λ²μ μμ²μ ν κ²°κ³Ό, μνμ μ¬κ³ λ 100κ°-> 0κ°λ‘ μ€μκ³ , μ£Όλ¬Έμ 100κ°κ° μμ±λμλ€.
- ν μ€νΈ μλ리μ€: μ΄λΉ 100λ²μ μμ²μ νμ λ, μ¬κ³ 1000κ° -> 900κ°, μ£Όλ¬Έμ 100κ° μμ±λμ΄μΌ νλ€.
- ν μ€νΈ κ²°κ³Ό: 3μ΄ λμ μ΄ 300λ²μ μμ²μ ν κ²°κ³Ό, μ¬κ³ λ 1000κ° -> 700κ°, μ£Όλ¬Έμ 300κ°κ° μμ±λμλ€.
μ¬κΈ°κΉμ§ λμμ± μ΄μκ° μΌμ΄λ¬μ λ Redissonμ μ¬μ©ν λΆμ° λ½μ ꡬνμ λν΄ μμ보μλ€.
π μ°Έκ³
ννλ¨ΌνΈ μ κ³ μλΉμ€νμμ λΆμ°λ½μ μ¬μ©νλ λ°©λ² - Spring Redisson
μ΄λ Έν μ΄μ κΈ°λ°μΌλ‘ λΆμ°λ½μ μ¬μ©νλ λ°©λ²μ λν΄ μκ°ν©λλ€.
helloworld.kurly.com
λ λμ€μ λΆμ° λ½(1/2) - λ λμ€λ₯Ό νμ©ν λΆμ° λ½κ³Ό μμ νκ³ λΉ λ₯Έ λ½μ ꡬν
λ λμ€λ₯Ό νμ©ν λΆμ° λ½μ λν΄ μμλ΄ λλ€. κ·Έλ¦¬κ³ μ±λ₯μ λμ΄κ³ μΌκ΄μ±μ 보μ₯νλ λ°©λ²μ λν΄ μμλ΄ λλ€.
hyperconnect.github.io
[Redis] λΆμ° λ½ (feat. Redisson)
λΆμ° λ½μ΄λ? λΆμ° λ½(Distributed Lock)μ λ€μμ μλ²(λλ νλ‘μΈμ€)κ° λμμ κ°μ μμμ μ κ·Όν λ λ°μν μ μλ λμμ± λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ μ¬μ©λλ λκΈ°ν λ©μ»€λμ¦μ λλ€. λΆμ° λ½μ ν΅
inma.tistory.com
8. Distributed locks and synchronizers
Redisson - Valkey and Redis Java client. Complete Real-Time Data Platform. Sync/Async/RxJava/Reactive API. Over 50 Valkey and Redis based Java objects and services: Set, Multimap, SortedSet, Map, L...
github.com