๊ฐ์
์ต๊ทผ ์ ๋ฌด ํ๋ก์ ํธ์์ ํน์ (์๊ธ์ด ๋ถ๊ฐ๋๋) ๋ก์ง์ ๋ํด ์๋ณ ์ฌ์ฉ๋์ ์ ํํ๋ ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋์ด์ผ ํ์ต๋๋ค. ์ด์ ๊ด๋ จํ์ฌ ์ฒ๋ฆฌ์จ ์ ํ ๊ธฐ์ ์ ์์๋ณด์๋๋ฐ Bucket4j, Guava, RateLimitj, Resilience4j ๋ฑ ๋ค์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๋ ๊ฑธ ์๊ฒ ๋์๊ณ , ํ๋ก์ ํธ ํ๊ฒฝ์ธ Spring Boot 2.7.x, MariaDB(nosql & in-momory๋ถ์ฌ), Java 11์ ์๋ง์ Bucket4j๋ฅผ ์ ํํ๊ฒ ๋์์ต๋๋ค.
๋ค์์ ๊ณ ๋ คํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ํน์ง๊ณผ ๊ทธ ์ ํ ์ด์ ์ ๋๋ค.
- Guava: ๋ค์ํ ํต์ฌ Java ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ ๊ณตํ์ง๋ง, ๋จ์ํ Rate Limiting ๊ธฐ๋ฅ๋ง์ ์ํด ์ฌ์ฉํ๊ธฐ์๋ ๋ค์ ๋ฌด๊ฑฐ์ด ๋๋์ด๋ค.
- Resilience4j: ์ํท ๋ธ๋ ์ด์ปค๋ฅผ ์ ๊ณตํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก Rate Limiter ๊ธฐ๋ฅ๋ ํฌํจ๋์ด ์์ง๋ง, ๋ง์ฐฌ๊ฐ์ง๋ก ๋จ์ํ Rate Limiting ๊ธฐ๋ฅ๋ง์ ์ํด ์ฌ์ฉํ๊ธฐ์๋ ๋ค์ ๋ฌด๊ฑฐ์ด ๋๋์ด๋ค.
- RateLimitj: ์ฌ๋ผ์ด๋ฉ ์๋์ฐ ์๊ณ ๋ฆฌ์ฆ์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ Rate Limiter ๊ตฌํ์ฒด์ด๋ค. ๊ทธ๋ฌ๋ ๊ณต์ ๋ฌธ์์ ๋ ์ด์ ์ ๋ฐ์ดํธ๊ฐ ์์ ๊ฒ์ด๋ผ๊ณ ๋ช ์๋์ด ์๊ณ , Bucket4j๋ฅผ ๋์ฒด์ ๋ก ๊ถ์ฅํ๊ณ ์๋ค.
- Bucket4j: ํ ํฐ ๋ฒํท ์๊ณ ๋ฆฌ์ฆ์ ๊ธฐ๋ฐ์ผ๋ก ํ๋ฉฐ, lock-freeํ ๊ตฌํ์ผ๋ก ๋ฉํฐ ์ค๋ ๋ฉ ํ๊ฒฝ์์์ ํ์ฅ์ฑ์ด ์ฐ์ํ๋ฉฐ, Rate Limiting๋ง์ ๋ชฉ์ ์ผ๋ก ์ ๊ณต๋๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
๋ฐ๋ผ์ ์ด ๊ธ์์๋ Bucket4j๋ฅผ ์ฌ์ฉํ์ฌ ํธ๋ํฝ ์ ํ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ ๋ฆฌํฉ๋๋ค.
ํ ํฐ ๋ฒํท ์๊ณ ๋ฆฌ์ฆ ์ด๋?
ํ ํฐ ๋ฒํท ์๊ณ ๋ฆฌ์ฆ(Token Bucket Algorithm)์ ๋คํธ์ํฌ ํธ๋ํฝ ๋ฐ ๋ฐ์ดํฐ ์ ์ก ์๋๋ฅผ ์ ์ดํ๋ ๋ฐ ๋๋ฆฌ ์ฌ์ฉ๋๋ ๊ธฐ๋ฒ์ ๋๋ค. ๊ณ ์ ๋ ์๋๋ก ํ ํฐ์ด ๋ฒํท์ ์ฑ์์ง๋ฉฐ, ๊ฐ ์์ฒญ์ ๋ฒํค์์ ํ๋ ์ด์์ ํ ํฐ์ ์๋นํฉ๋๋ค. ๋ฒํท์ ์ถฉ๋ถํ ํ ํฐ์ด ์๋ ๊ฒฝ์ฐ, ์์ฒญ์ ๋๊ธฐํ๊ฑฐ๋ ๊ฑฐ๋ถ๋๋ฉฐ ์ด ๋ฐฉ์์ ํธ๋ํฝ์ ์ ์ฐํ๊ฒ ์ ์ดํ๊ณ ํผํฌ ์๊ฐ์ ๋ํ ๋ถํ๋ฅผ ๊ด๋ฆฌํ๋๋ฐ ํจ๊ณผ์ ์ ๋๋ค. ์ฃผ์ ํน์ง๊ณผ ๋์ ์๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ํ ํฐ์ ์์ฑ๊ณผ ์๋น: ๋ฒํท์๋ ์ผ์ ํ ์๋๋ก ํ ํฐ์ด ์ถ๊ฐ๋๋ค. ์๋ฅผ ๋ค์ด, 1๋ถ์ 100๊ฐ์ ํ ํฐ์ด ์์ฑ๋ ์ ์์ผ๋ฉฐ ์์ฒญ์ด ๋ค์ด์ฌ ๋๋ง๋ค ๋ฒํท์์ ํ ํฐ์ ํ๋ ๋๋ ๊ทธ ์ด์ ์๋นํ๋ค.
- ์์ฒญ ์ ํ: ๋ฒํท์ ํ ํฐ์ด ์ถฉ๋ถํ์ง ์์ผ๋ฉด ์๋ก์ด ์์ฒญ์ ์ฒ๋ฆฌ๋์ง ์๊ณ ๋ฒ๋ ค์ง๋ค.
- ํ ํฐ์ ์ฌ์ถฉ์ : ์ผ์ ์๊ฐ์ด ์ง๋๋ฉด ํ ํฐ์ด ๋ค์ ์์ฑ๋์ด ๋ฒํท์ ์ถ๊ฐ๋๋ค. ์๋ฅผ ๋ค์ด, ์ด๊ธฐ์ 120๊ฐ์ ํ ํฐ์ผ๋ก ์์ํ์ฌ 1๋ถ์ 100๊ฐ์ฉ ์ฌ์ถฉ์ ๋๋ ๋ฐฉ์์ด๋ค.
Bucket4j ์ ์ฉ
Bucket4j๋ ๋ก์ปฌ ๋ฉ๋ชจ๋ฆฌ ์ธ์๋, JDBC(MySQL, PostgreSQL, Oracle, MSSQL)์ Redis์ ๊ฐ์ด ๋ค์ํ ๋ถ์ฐ ํ๊ฒฝ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ง์ํฉ๋๋ค.
์๋๋ MariaDB์ Redis์ Bucket4j๋ฅผ ์ ์ฉํ ์์ ์ฝ๋๋ค์ ๋๋ค. ๋ก์ปฌ ๋ฉ๋ชจ๋ฆฌ(์บ์)๋ฅผ ์ฌ์ฉํ ์ฝ๋์ ๋ํด์๋ ์ธํฐ๋ท์ ๋ค์ํ ์์ ๊ฐ ์์ผ๋ฏ๋ก, ํ์ํ๋ค๋ฉด ํด๋น ์๋ฃ๋ค์ ์ฐธ์กฐํ์๋ฉด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค. ์ค์ ์๋น์ค ํ๊ฒฝ์์๋ ๋๊ฐ ์ฌ๋ฌ ์ธ์คํด์ค๊ฐ ๋์์ ์ด์๋๋ฏ๋ก, ์ด์ฒ๋ผ ๋ถ์ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ Bucket4j๋ฅผ ์ ์ฉํ๋ ๊ฒ์ด ํ์ํฉ๋๋ค.
- Bucket4j๋ฅผ MariaDB์ ์ ์ฉํ ์์ : Bucket4j-mariadb
- Bucket4j๋ฅผ Redis์ ์ ์ฉํ ์์ : Bucket4j-redis
Bucket4j-mariadb
1. table ์์ฑ
๋จผ์ ์๋์ ๊ฐ์ด 'bucket'์ด๋ผ๋ ์ด๋ฆ์ ํ ์ด๋ธ์ ์์ฑํฉ๋๋ค. ๊ณต์ ๋ฌธ์์์๋ ํ ์ด๋ธ ์ด๋ฆ์ 'buckets'๋ก ์ง์ ํ์์ง๋ง, ์ค์ ๋ก BucketTableSetting.getDefault() ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ํ ์ด๋ธ ์ด๋ฆ์ด 'bucket'์ผ๋ก ๋ฐํ๋๋ฏ๋ก, ํ ์ด๋ธ ์ด๋ฆ์ 'bucket'์ผ๋ก ์ค์ ํ๋ ๊ฒ์ด ํธ๋ฆฌํฉ๋๋ค.
create table bucket
(
id bigint not null
primary key,
state blob null
);
2. dependency
'bucket4j-mysql' ์์กด์ฑ์ ์ถ๊ฐํฉ๋๋ค.
dependencies {
...
// bucket4j
implementation 'com.bucket4j:bucket4j-mysql:8.7.0'
}
3. APIRateLimiter
@Slf4j
@Component
public class APIRateLimiter {
// ๋ฒํท์ ์ฉ๋์ ์ค์ , ์ด๋ ๋ฒํท์ ๋ด๊ธธ ์ ์๋ ํ ํฐ์ ์ต๋ ์๋ฅผ ์๋ฏธ
private static final int CAPACITY = 3;
// ํ ํฐ์ด ์ผ๋ง๋ ๋น ๋ฅด๊ฒ ์ฌ์ถฉ์ ๋ ์ง ์ค์ , ์ด๋ ์ง์ ๋ ์๊ฐ ๋์ ๋ฒํท์ ์ถ๊ฐ๋ ํ ํฐ์ ์๋ฅผ ์๋ฏธ
private static final int REFILL_AMOUNT = 3;
// ํ ํฐ์ด ์ฌ์ถฉ์ ๋๋ ๋น๋๋ฅผ ์ค์
private static final Duration REFILL_DURATION = Duration.ofSeconds(5);
// MySQLSelectForUpdateBasedProxyManager ๊ฐ์ฒด๋ฅผ ์์ฑ, ์ด ๊ฐ์ฒด๋ ๋ฒํท์ ์์ฑ ๋ฐ ๊ด๋ฆฌ๋ฅผ ๋ด๋น
private final MySQLSelectForUpdateBasedProxyManager<Long> proxyManager;
// ๋์ผํ API ํค์ ๋ํ ์์ฒญ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด ๋ฒํท์ ์ฌ์ฌ์ฉํ๊ธฐ ์ํด ๋ฒํท์ ์ ์ฅํ๋ ๋งต์ ์์ฑ
private final ConcurrentMap<String, BucketProxy> buckets = new ConcurrentHashMap<>();
/**
* APIRateLimiter ์์ฑ์.
*
* @param dataSource ๋ฐ์ดํฐ ์์ค
*/
public APIRateLimiter(DataSource dataSource) {
// ๋ฒํท ํ
์ด๋ธ ์ค์ ์ ๊ฐ์ ธ์ด
var tableSettings = BucketTableSettings.getDefault();
// SQL ํ๋ก์ ์ค์ ์ ์์ฑ
var sqlProxyConfiguration = SQLProxyConfiguration.builder()
.withTableSettings(tableSettings)
.build(dataSource);
// SQL ํ๋ก์ ์ค์ ์ ์ด์ฉํด MySQLSelectForUpdateBasedProxyManager ๊ฐ์ฒด๋ฅผ ์์ฑ
proxyManager = new MySQLSelectForUpdateBasedProxyManager<>(sqlProxyConfiguration);
}
/**
* API ํค์ ํด๋นํ๋ ๋ฒํท์ ๊ฐ์ ธ์ค๊ฑฐ๋, ์์ ๊ฒฝ์ฐ ์๋ก ์์ฑํ๋ ๋ฉ์๋.
*
* @param apiKey API ํค
* @return ํด๋น API ํค์ ๋์ํ๋ ๋ฒํท
*/
private BucketProxy getOrCreateBucket(String apiKey) {
return buckets.computeIfAbsent(apiKey, key -> {
// API ํค์ ํด์ ์ฝ๋๋ฅผ ๋ฒํท ID๋ก ์ฌ์ฉ
Long bucketId = (long) key.hashCode();
// ๋ฒํท ์ค์ ์ ์์ฑ
var bucketConfiguration = createBucketConfiguration();
// ๋ฒํท ID์ ๋ฒํท ์ค์ ์ ์ด์ฉํด ๋ฒํท์ ์์ฑํ๊ณ , ์ด๋ฅผ ๋ฐํ
return proxyManager.builder().build(bucketId, bucketConfiguration);
});
}
/**
* ๋ฒํท ์ค์ ์ ์์ฑํ๋ ๋ฉ์๋.
*
* @return ์์ฑ๋ ๋ฒํท ์ค์
*/
private BucketConfiguration createBucketConfiguration() {
return BucketConfiguration.builder()
// ๋ฒํท์ ๋ํ ์ ํ(์ฉ๋๊ณผ ์ฌ์ถฉ์ ์๋)์ ์ค์
.addLimit(Bandwidth.builder().capacity(CAPACITY).refillIntervally(REFILL_AMOUNT, REFILL_DURATION).build())
.build();
}
/**
* API ํค์ ํด๋นํ๋ ๋ฒํท์์ ํ ํฐ์ ์๋นํ๋ ค๊ณ ์๋ํ๋ ๋ฉ์๋.
*
* @param apiKey API ํค
* @return ํ ํฐ ์๋น ์ฑ๊ณต ์ฌ๋ถ
*/
public boolean tryConsume(String apiKey) {
// API ํค์ ํด๋นํ๋ ๋ฒํท์ ๊ฐ์ ธ์ด
BucketProxy bucket = getOrCreateBucket(apiKey);
// ๋ฒํท์์ ํ ํฐ์ ์๋นํ๋ ค๊ณ ์๋ํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ
boolean consumed = bucket.tryConsume(1);
log.info("API Key: {}, Consumed: {}, Time: {}", apiKey, consumed, LocalDateTime.now());
return consumed;
}
}
๋ง์ฝ ์ฌ๊ธฐ์ ์ปค์คํ ํ ํ ์ด๋ธ ํน์ ์ปฌ๋ผ์ ์ง์ ํ๊ฑฐ๋, ์คํค๋ง๋ฅผ ์ง์ ํ๊ณ ์ถ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ ์ํ๋ฉด ๋ฉ๋๋ค.
var tableSettings = BucketTableSettings.customSettings("test.bucket","id", "state");
Bucket4j-redis
Bucket4j๋ฅผ Redis์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ MariaDB์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ๊ณผ ๊ฑฐ์ ๋์ผํฉ๋๋ค. Bucket4j๋ Redis์ Redisson, Jedis, Lettuce๋ฅผ ๋ชจ๋ ์ง์ํ๋ฉฐ, ์๋ ์์ ์์๋ Lettuce๋ฅผ ์ฌ์ฉํ์์ต๋๋ค.
1. dependency
์์กด์ฑ์ ์ถ๊ฐํฉ๋๋ค.
dependencies {
...
// bucket4j
implementation 'com.bucket4j:bucket4j-redis:8.7.0'
}
2. APIRateLimiter
@Slf4j
@Component
public class APIRateLimiter {
// ๋ฒํท์ ์ฉ๋์ ์ค์ , ์ด๋ ๋ฒํท์ ๋ด๊ธธ ์ ์๋ ํ ํฐ์ ์ต๋ ์๋ฅผ ์๋ฏธ
private static final int CAPACITY = 3;
// ํ ํฐ์ด ์ผ๋ง๋ ๋น ๋ฅด๊ฒ ์ฌ์ถฉ์ ๋ ์ง ์ค์ , ์ด๋ ์ง์ ๋ ์๊ฐ ๋์ ๋ฒํท์ ์ถ๊ฐ๋ ํ ํฐ์ ์๋ฅผ ์๋ฏธ
private static final int REFILL_AMOUNT = 3;
// ํ ํฐ์ด ์ฌ์ถฉ์ ๋๋ ๋น๋๋ฅผ ์ค์
private static final Duration REFILL_DURATION = Duration.ofSeconds(5);
// LettuceBasedProxyManager ๊ฐ์ฒด๋ฅผ ์์ฑ, ์ด ๊ฐ์ฒด๋ ๋ฒํท์ ์์ฑ ๋ฐ ๊ด๋ฆฌ๋ฅผ ๋ด๋น
private final LettuceBasedProxyManager<String> proxyManager;
// ๋์ผํ API ํค์ ๋ํ ์์ฒญ์ ์ฒ๋ฆฌํ๊ธฐ ์ํด ๋ฒํท์ ์ฌ์ฌ์ฉํ๊ธฐ ์ํด ๋ฒํท์ ์ ์ฅํ๋ ๋งต์ ์์ฑ
private final ConcurrentMap<String, Bucket> buckets = new ConcurrentHashMap<>();
/**
* APIRateLimiter ์์ฑ์.
*
* @param redisClient Redis ํด๋ผ์ด์ธํธ
*/
public APIRateLimiter(RedisClient redisClient) {
// Redis ์ฐ๊ฒฐ์ ์์ฑ
StatefulRedisConnection<String, byte[]> connection = redisClient.connect(RedisCodec.of(StringCodec.UTF8, ByteArrayCodec.INSTANCE));
// Redis ์ฐ๊ฒฐ์ ์ด์ฉํด LettuceBasedProxyManager ๊ฐ์ฒด๋ฅผ ์์ฑ
this.proxyManager = LettuceBasedProxyManager.builderFor(connection)
.withExpirationStrategy(ExpirationAfterWriteStrategy.basedOnTimeForRefillingBucketUpToMax(Duration.ofSeconds(100)))
.build();
}
/**
* API ํค์ ํด๋นํ๋ ๋ฒํท์ ๊ฐ์ ธ์ค๊ฑฐ๋, ์์ ๊ฒฝ์ฐ ์๋ก ์์ฑํ๋ ๋ฉ์๋.
*
* @param apiKey API ํค
* @return ํด๋น API ํค์ ๋์ํ๋ ๋ฒํท
*/
private Bucket getOrCreateBucket(String apiKey) {
return buckets.computeIfAbsent(apiKey, key -> {
// ๋ฒํท ์ค์ ์ ์์ฑ
BucketConfiguration configuration = createBucketConfiguration();
// ๋ฒํท ID์ ๋ฒํท ์ค์ ์ ์ด์ฉํด ๋ฒํท์ ์์ฑํ๊ณ , ์ด๋ฅผ ๋ฐํ
return proxyManager.builder().build(key, configuration);
});
}
/**
* ๋ฒํท ์ค์ ์ ์์ฑํ๋ ๋ฉ์๋.
*
* @return ์์ฑ๋ ๋ฒํท ์ค์
*/
private BucketConfiguration createBucketConfiguration() {
return BucketConfiguration.builder()
// ๋ฒํท์ ๋ํ ์ ํ(์ฉ๋๊ณผ ์ฌ์ถฉ์ ์๋)์ ์ค์
.addLimit(Bandwidth.simple(CAPACITY, REFILL_DURATION).withInitialTokens(REFILL_AMOUNT))
.build();
}
/**
* API ํค์ ํด๋นํ๋ ๋ฒํท์์ ํ ํฐ์ ์๋นํ๋ ค๊ณ ์๋ํ๋ ๋ฉ์๋.
*
* @param apiKey API ํค
* @return ํ ํฐ ์๋น ์ฑ๊ณต ์ฌ๋ถ
*/
public boolean tryConsume(String apiKey) {
// API ํค์ ํด๋นํ๋ ๋ฒํท์ ๊ฐ์ ธ์ด
Bucket bucket = getOrCreateBucket(apiKey);
// ๋ฒํท์์ ํ ํฐ์ ์๋นํ๋ ค๊ณ ์๋ํ๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํ
boolean consumed = bucket.tryConsume(1);
log.info("API Key: {}, Consumed: {}, Time: {}", apiKey, consumed, LocalDateTime.now());
return consumed;
}
}
ํ ์คํธ
์๋์ ๊ฐ์ด AOP(Aspect-Oriented Programming)๋ฅผ ์ ์ฉํ์ฌ ์ฝ๋์ ๊ฐ๋ ์ฑ์ ๋์ด๊ณ ์ค๋ณต์ ์ ๊ฑฐํ ์ ์์ต๋๋ค.
RateLimit
๋จผ์ , ๊ฐ ๋ฉ์๋์ ์ ์ฉํ ์ฌ์ฉ์ ์ ์ ์ ๋ ธํ ์ด์ ์ธ RateLimit์ ์์ฑํฉ๋๋ค. ์ด ์ ๋ ธํ ์ด์ ์ API ํค๋ฅผ ์ง์ ํ๋ 'key'๋ผ๋ ์์ฑ์ ๊ฐ์ง๋๋ค.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimit {
String key();
}
RateLimitingAspect
๋ค์์ผ๋ก, RateLimit ์ ๋ ธํ ์ด์ ์ ์ฒ๋ฆฌํ Aspect ํด๋์ค์ธ RateLimitingAspect๋ฅผ ์์ฑํฉ๋๋ค. ์ด ํด๋์ค๋ APIRateLimiter ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ API ํค์ ํด๋นํ๋ ๋ฒํท์์ ํ ํฐ์ ์๋นํ๋ ค๊ณ ์๋ํฉ๋๋ค. ํ ํฐ์ด ์ถฉ๋ถํ๋ฉด ์์ฒญ์ด ์ฑ๊ณต์ ์ผ๋ก ์ฒ๋ฆฌ๋๊ณ , ๊ทธ๋ ์ง ์์ผ๋ฉด ์์ธ๊ฐ ๋ฐ์ํฉ๋๋ค.
@Aspect
@Component
public class RateLimitingAspect {
private final APIRateLimiter apiRateLimiter;
@Autowired
public RateLimitingAspect(APIRateLimiter apiRateLimiter) {
this.apiRateLimiter = apiRateLimiter;
}
@Around("@annotation(rateLimit)")
public Object rateLimit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
if (apiRateLimiter.tryConsume(rateLimit.key())) {
return joinPoint.proceed();
} else {
throw new RuntimeException("Rate limit exceeded for key: " + rateLimit.key());
}
}
}
RateLimitingController
@RestController
@Slf4j
public class RateLimitingController {
@Autowired
private RateLimitingService rateLimitingService;
@GetMapping("/test")
public String test() {
return rateLimitingService.run();
}
}
RateLimitingService
@Service
public class RateLimitingService {
@RateLimit(key = "someUniqueKey")
public String run() {
return "์์ฒญ ์ฑ๊ณต";
}
}
์ ๋ฆฌ
Bucket4j ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ฉํ๋ฉด, API ํธ์ถ์ ์ฒ๋ฆฌ์จ์ ์์ฝ๊ฒ ์ ํํ ์ ์์ต๋๋ค. ๋ํ ๋ค์ํ ๊ณณ์์ ์ ์ฉํ๊ณ ๋ถ๊ฐ ๊ธฐ๋ฅ์ ๋ฃ์ ์ ์๋๋ฐ, Baeldung์ Bucket4j ๊ฐ์ด๋๋ฅผ ๋ณด๋ฉด ์ธํฐ์ ํฐ๋ฅผ ์ด์ฉํ์ฌ ์ฒ๋ฆฌ์จ์ ์ ํํ ์๋ ์๊ณ , ์๋์ฒ๋ผ ์ฌ์ฉ์์ ์๊ธ์ ์ ๋ฐ๋ผ API ํธ์ถ ์ ํ์ ๋ค๋ฅด๊ฒ ์ ์ฉํ๋ ๊ฒ๋ ๊ฐ๋ฅํฉ๋๋ค.
enum PricingPlan {
// ํ์๊ฐ์ 20๋ฒ ์ฌ์ฉ ๊ฐ๋ฅ
FREE {
Bandwidth getLimit() {
return Bandwidth.classic(20, Refill.intervally(20, Duration.ofHours(1)));
}
},
// ํ์๊ฐ์ 40๋ฒ ์ฌ์ฉ ๊ฐ๋ฅ
BASIC {
Bandwidth getLimit() {
return Bandwidth.classic(40, Refill.intervally(40, Duration.ofHours(1)));
}
},
// ํ์๊ฐ์ 100๋ฒ ์ฌ์ฉ ๊ฐ๋ฅ
PROFESSIONAL {
Bandwidth getLimit() {
return Bandwidth.classic(100, Refill.intervally(100, Duration.ofHours(1)));
}
};
//..
}
Bucket4j๋ ์ด์ ๊ฐ์ด ๋ค์ํ ํ๊ฒฝ์์ API ํธ์ถ ์ ํ์ ๊ตฌํํ๋ ๋ฐ ์ ์ฉํ๊ฒ ์ฌ์ฉ๋ ์ ์์ต๋๋ค. ๋ฐ๋ผ์, ์๋น์ค์ ์ฑ๋ฅ์ ํฅ์์ํค๊ณ ์๋ฒ์ ๋ถํ๋ฅผ ๊ด๋ฆฌํ๋ ๋ฐ ํ์ํ ๊ธฐ๋ฅ์ ์ฝ๊ณ ๋น ๋ฅด๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค.
'BackEnd๐ฑ > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
ThreadPoolExecutor ๋์์ ๊ดํ ์คํด (0) | 2024.03.11 |
---|---|
MySQL ๋ฒ์ ์ ๋ฐ๋ฅธ @Transactional(readOnly=true)์ ๋์ ๊ณผ์ (4) | 2024.01.24 |
Hibernate์ @Formula๋ฅผ ์ด์ฉํ ์ฐ๊ด ๊ด๊ณ ์ํฐํฐ ์ง๊ณ (0) | 2023.12.20 |
ShedLock์ผ๋ก ๋ค์ค ์ธ์คํด์ค ํ๊ฒฝ์์ ๋จ์ผ ์ค์ผ์ค๋ฌ ๋์ ๋ณด์ฅํ๊ธฐ (2) | 2023.11.24 |
default method๋ก JpaRepository ์ข ๋ ์ฐ์ํ๊ฒ ์จ๋ณด๊ธฐ (0) | 2023.11.16 |
@Scheduled ์ฌ์ฉํ ๋ ์ค๋ ๋ ์ค์ (0) | 2023.11.09 |
๋๊ธ