20240222 (목) 대용량 트래픽 프로젝트 - 동시성 제어 프로젝트 7일차

2024. 2. 22. 16:30TIL

오늘 공부한 내용

Redisson을 이용한 Lock 구현

 

기존에 사용하던 Lettuce는 락을 점유할때 까지 레디스에 요청을 계속 보내는 형식의 SpinLock 형태이다.

요청을 끊임없이 계속해서 한다는 것은 결국 CPU resource를 끊임없이 사용한다는 의미다.

이렇게 CPU를 계속 낭비하게 되면,, 다른 일을 해야하는 스레드들이 CPU에서 작업할 수 있는 기회는 줄어들게 된다

결국 프로그램의 퍼포먼스는 안좋아 지고, 컴퓨터 시스템 전체적으로도 응답성이 나빠지게 된다

그런 대안점으로 사용하려하는것이 Redisson을 이용한 Pub-sub기반 Lock

이 Lock을 설명해보자면

  • Pub-sub 기반으로 Lock 구현 제공
  • Pub-Sub 방식이란, 채널을 하나 만들고, 락을 점유중인 스레드가, 락을 해제했음을, 대기중인 스레드에게 알려주면 대기중인 스레드가 락 점유를 시도하는 방식입니다.
  • 이 방식은, Lettuce와 다르게 대부분 별도의 Retry 방식을 작성하지 않아도 됩니다

이렇게 된다.

package com.a03.concurrencycontrolproject.common.redis.service

import com.a03.concurrencycontrolproject.domain.ticket.dto.CreateTicketRequest
import com.a03.concurrencycontrolproject.domain.ticket.service.TicketService
import org.redisson.api.RedissonClient
import org.springframework.stereotype.Component
import java.util.concurrent.TimeUnit

@Component
class RedissonLockService(
    private val redissonClient: RedissonClient,
    private val ticketService: TicketService
) {
    fun createTicket(userid: Long, request: CreateTicketRequest) {
        //key 로 Lock 객체 가져옴
        val lock = redissonClient.getLock(request.goodsId.toString())

        try {
            //획득시도 시간, 락 점유 시간
            val available =lock.tryLock(5, 1, TimeUnit.SECONDS)

            if (!available) {
                println("lock 획득 실패")
                return
            }
            ticketService.createTicket(userid, request)
//        } catch (e: InterruptedException) {
//            throw RuntimeException(e)
        } finally {
            lock.unlock()
        }
    }
}

위는 실제 Redisson을 이용한 pub-sub방식의 Lock 코드

 

이후에는 새로만든 Lock을 aop로 적용하는 것도 해보았다.

package com.a03.concurrencycontrolproject.common.aop.redisson.eventlock

import com.a03.concurrencycontrolproject.common.aop.ConcurrencyControlDto
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.RedissonClient
import org.springframework.stereotype.Component
import java.util.concurrent.TimeUnit

@Aspect
@Component
class EventLockAspect(
    private val redissonClient: RedissonClient,
) {


    @Around("@annotation(com.a03.concurrencycontrolproject.common.aop.redisson.eventlock.EventLock) && args(..,request)")
    fun run(joinPoint: ProceedingJoinPoint, request: ConcurrencyControlDto) {
        val methodSignature = joinPoint.signature as MethodSignature
        val annotation = methodSignature.method.getAnnotation(EventLock::class.java)

        val name = annotation?.name ?: ""
        val lock = redissonClient.getLock(generateKey(name, request.targetId))
        try {
            //획득시도 시간, 락 점유 시간
            val available = lock.tryLock(5, 1, TimeUnit.SECONDS)

            if (!available) {
                println("lock 획득 실패")
                return
            }
            joinPoint.proceed()
        } finally {
            lock.unlock()
        }
    }

    private fun generateKey(name: String, id: Long): String {
        return "$name $id";
    }
}

오늘은 코드 마무리도 했고 발표준비도 했으니 조금 쉬엄쉬엄했다...