差異處
這裏顯示兩個版本的差異處。
java:apache_camel:throttler:helloworld [2019/03/17 21:56] tony [Library Info] |
java:apache_camel:throttler:helloworld [2023/06/25 09:48] |
||
---|---|---|---|
行 1: | 行 1: | ||
- | {{tag>camel}} | ||
- | ====== Camel - Throttler Hello World ====== | ||
- | ===== Introduction ===== | ||
- | 在設計web應用程式時,有時會需要一個節流器,去幫我控制單位時間內能處理的請求數量,以避免過載;又或者是要根據不同使用者所買的授權,去控制單位時間內能呼叫的API次數等。Camel提供了Throttler,讓我們能輕鬆透過設定,去達到這些效果。\\ | ||
- | \\ | ||
- | 我將透過HTTP GET請求/events/{id}做為範例,說明如何使用Throttler。首先介紹這個範例中的兩個RouteBuilder。 | ||
- | ===== RestRouteBuilder ===== | ||
- | REST核心設定集中在這個builder中,它負責宣告用什麼port與component去建立REST服務: | ||
- | <code java> | ||
- | package org.tonylin.practice.camel.rest; | ||
- | import org.apache.camel.builder.RouteBuilder; | ||
- | import org.apache.camel.model.rest.RestBindingMode; | ||
- | |||
- | public class RestRouteBuilder extends RouteBuilder { | ||
- | |||
- | @Override | ||
- | public void configure() throws Exception { | ||
- | restConfiguration().component("netty4-http").port(8080).bindingMode(RestBindingMode.auto).endpointProperty("ssl", "false"); | ||
- | } | ||
- | } | ||
- | </code> | ||
- | (我以http當範例,如果對https用法有興趣,可以參考[[java:apache_camel:rest_with_netty_http_and_ssl|這篇]]) | ||
- | ===== ThrottlerRouteBuilder ===== | ||
- | 接下來是今天的主角,我先列出程式碼內容,後面再針對重點configure做說明: | ||
- | <code java> | ||
- | package org.tonylin.practice.camel.throttler; | ||
- | |||
- | import static com.google.common.base.Preconditions.checkState; | ||
- | |||
- | import org.apache.camel.Exchange; | ||
- | import org.apache.camel.Processor; | ||
- | import org.apache.camel.builder.RouteBuilder; | ||
- | import org.apache.camel.processor.ThrottlerRejectedExecutionException; | ||
- | import org.slf4j.Logger; | ||
- | import org.slf4j.LoggerFactory; | ||
- | |||
- | public class ThrottlerRouteBuilder extends RouteBuilder { | ||
- | private static Logger logger = LoggerFactory.getLogger(ThrottlerRouteBuilder.class); | ||
- | private final static String GET_EVENTS = "GET_EVENTS"; | ||
- | |||
- | private Object eventHandler; | ||
- | |||
- | private int limit = 2; | ||
- | private int period = 200; | ||
- | |||
- | public ThrottlerRouteBuilder(Object eventHandler) { | ||
- | this.eventHandler = eventHandler; | ||
- | |||
- | } | ||
- | |||
- | public void setLimit(int limit) { | ||
- | this.limit = limit; | ||
- | } | ||
- | |||
- | public void setPeriod(int period) { | ||
- | this.period = period; | ||
- | } | ||
- | |||
- | @Override | ||
- | public void configure() throws Exception { | ||
- | checkState(eventHandler!=null, "Can't find eventHandler"); | ||
- | |||
- | onException(ThrottlerRejectedExecutionException.class) | ||
- | .process(new Processor() { | ||
- | @Override | ||
- | public void process(Exchange exchange) throws Exception { | ||
- | logger.debug("handle ThrottlerRejectedExecutionException"); | ||
- | exchange.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, "503"); | ||
- | } | ||
- | }) | ||
- | .handled(true); | ||
- | |||
- | rest("/events/{id}").get().route().id(GET_EVENTS) | ||
- | .throttle(limit) | ||
- | .timePeriodMillis(period) | ||
- | .rejectExecution(true) | ||
- | .bean(eventHandler).endRest(); | ||
- | } | ||
- | } | ||
- | </code> | ||
- | 我首要說明的是throttler的configure: | ||
- | <code java> | ||
- | rest("/events/{id}").get().route().id(GET_EVENTS) | ||
- | .throttle(limit) | ||
- | .timePeriodMillis(period) | ||
- | .rejectExecution(true) | ||
- | .bean(eventHandler).endRest(); | ||
- | </code> | ||
- | 除了HTTP GET的宣告外,這些程式碼代表著以下意義: | ||
- | * throttle(limit): 限制的存取次數。 | ||
- | * timePeriodMillis(period): 限制存取次數的單位時間,預設是1000ms。 | ||
- | * rejectExecution(true): 當超過此限制時,是否要reject請求,預設為false。假如沒reject,後續超過限制的請求會block至單位時間後執行。 | ||
- | * bean(eventHandler): 請求的處理者。 | ||
- | 在我設定rejectExecution為true後,我發現camel會拋出ThrottlerRejectedExecutionException,且client會block住;因此這個設定必須與camel的errorHandler一同使用,這是我的使用範例: | ||
- | <code java> | ||
- | onException(ThrottlerRejectedExecutionException.class) | ||
- | .process(new Processor() { | ||
- | @Override | ||
- | public void process(Exchange exchange) throws Exception { | ||
- | logger.debug("handle ThrottlerRejectedExecutionException"); | ||
- | exchange.getOut().setHeader(Exchange.HTTP_RESPONSE_CODE, "503"); | ||
- | } | ||
- | }) | ||
- | .handled(true); | ||
- | </code> | ||
- | 我宣告當發生ThrottlerRejectedExecutionException例外時,會將回應給client的response code設為503以代表server過載。除此之外,別忘記把handled設為true,代表例外已被處理。 | ||
- | ===== Unit Test ===== | ||
- | 最後我以單元測試來展示效果,包含testOverload與testThrottlePeriod兩個測試;而throttler的limit為2,period為200ms,testcase會在後面做說明: | ||
- | <code java> | ||
- | package org.tonylin.practice.camel.throttler; | ||
- | |||
- | import java.util.ArrayList; | ||
- | import java.util.List; | ||
- | |||
- | import org.apache.camel.RoutesBuilder; | ||
- | import org.apache.camel.test.junit4.CamelTestSupport; | ||
- | import org.apache.http.HttpResponse; | ||
- | import org.apache.http.client.HttpClient; | ||
- | import org.apache.http.client.methods.HttpGet; | ||
- | import org.apache.http.impl.client.HttpClientBuilder; | ||
- | import org.junit.Test; | ||
- | import org.tonylin.practice.camel.rest.RestHandler; | ||
- | import org.tonylin.practice.camel.rest.RestRouteBuilder; | ||
- | |||
- | public class ThrottlerRouteBuilderTest extends CamelTestSupport { | ||
- | |||
- | private RestHandler hander = new RestHandler(); | ||
- | private static final int limit = 2; | ||
- | private static final int period = 200; | ||
- | |||
- | private HttpClient client = HttpClientBuilder.create().build(); | ||
- | private HttpGet httpGet = new HttpGet("http://localhost:8080/events/123"); | ||
- | |||
- | @Override | ||
- | protected RoutesBuilder[] createRouteBuilders() throws Exception { | ||
- | ThrottlerRouteBuilder throttlerRouteBuilder = new ThrottlerRouteBuilder(hander); | ||
- | throttlerRouteBuilder.setLimit(limit); | ||
- | throttlerRouteBuilder.setPeriod(period); | ||
- | |||
- | return new RoutesBuilder[] { | ||
- | new RestRouteBuilder(), | ||
- | throttlerRouteBuilder | ||
- | }; | ||
- | } | ||
- | |||
- | private List<HttpResponse> batchRequest(int times) throws Exception { | ||
- | List<HttpResponse> responses = new ArrayList<HttpResponse>(); | ||
- | for( int i = 0 ; i < times ; i++ ) { | ||
- | responses.add(client.execute(httpGet)); | ||
- | } | ||
- | return responses; | ||
- | } | ||
- | |||
- | @Test | ||
- | public void testOverload() throws Exception { | ||
- | // skip | ||
- | } | ||
- | |||
- | @Test | ||
- | public void testThrottlePeriod() throws Exception { | ||
- | // skip | ||
- | } | ||
- | } | ||
- | </code> | ||
- | 測試中使用的RestHandler,負責收集請求的event id,用以確認請求內容是否正確: | ||
- | <code java> | ||
- | public class RestHandler { | ||
- | private static Logger logger = LoggerFactory.getLogger(RestHandler.class); | ||
- | private List<String> requestIds = new ArrayList<String>(); | ||
- | |||
- | @Handler | ||
- | public void handle(Exchange exchange) { | ||
- | String requestId = exchange.getIn().getHeader("id", String.class); | ||
- | logger.debug("Request id: {}", requestId); | ||
- | requestIds.add(requestId); | ||
- | } | ||
- | |||
- | public List<String> getRequestIds(){ | ||
- | return requestIds; | ||
- | } | ||
- | } | ||
- | </code> | ||
- | 針對testOverload測試,是用來確認throttler單位時間內的請求是否有效;因此測試中,連續做了3次請求,最後會去確認這三次的請求結果是否正確: | ||
- | <code java> | ||
- | @Test | ||
- | public void testOverload() throws Exception { | ||
- | // when request 3 times | ||
- | List<HttpResponse> responses = batchRequest(3); | ||
- | |||
- | // then | ||
- | assertEquals(2, hander.getRequestIds().size()); | ||
- | assertEquals(200, responses.get(0).getStatusLine().getStatusCode()); | ||
- | assertEquals(200, responses.get(1).getStatusLine().getStatusCode()); | ||
- | assertEquals(503, responses.get(2).getStatusLine().getStatusCode()); | ||
- | } | ||
- | </code> | ||
- | 而testThrottlePeriod測試,用以確認throttler單位時間是否有作用;因此測試中,會先發3次請求,再等待此單位時間後,再發3次請求。最後確認請求結果: | ||
- | <code java> | ||
- | @Test | ||
- | public void testThrottlePeriod() throws Exception { | ||
- | // when | ||
- | List<HttpResponse> responses = batchRequest(3); | ||
- | Thread.sleep(period+1); | ||
- | |||
- | responses.addAll(batchRequest(3)); | ||
- | |||
- | // then | ||
- | assertEquals(2, hander.getRequestIds().size()); | ||
- | assertEquals(200, responses.get(0).getStatusLine().getStatusCode()); | ||
- | assertEquals(200, responses.get(1).getStatusLine().getStatusCode()); | ||
- | assertEquals(503, responses.get(2).getStatusLine().getStatusCode()); | ||
- | assertEquals(200, responses.get(3).getStatusLine().getStatusCode()); | ||
- | assertEquals(200, responses.get(4).getStatusLine().getStatusCode()); | ||
- | assertEquals(503, responses.get(5).getStatusLine().getStatusCode()); | ||
- | } | ||
- | </code> | ||
- | 透過這兩個測試範例,我們可以簡單地了解throttler的用法。 | ||
- | ===== Library Info (Gradle Config) ===== | ||
- | 以下是我在寫這篇文章時,所使用的libraries版本: | ||
- | <code> | ||
- | ext { | ||
- | camelVersion='2.23.1' | ||
- | nettyAllVersion='4.1.34.Final' | ||
- | guavaVersion='27.1-jre' | ||
- | log4jVersion='1.2.17' | ||
- | slf4jVersion='1.7.26' | ||
- | httpClientVersion='4.5.7' | ||
- | } | ||
- | |||
- | dependencies { | ||
- | compile group: 'org.apache.camel', name: 'camel-core', version: "$camelVersion" | ||
- | compile group: 'org.apache.camel', name: 'camel-netty4-http', version: "$camelVersion" | ||
- | compile group: 'org.apache.camel', name: 'camel-http-common', version: "$camelVersion" | ||
- | compile group: 'org.apache.camel', name: 'camel-netty4', version: "$camelVersion" | ||
- | compile group: 'io.netty', name: 'netty-all', version: "$nettyAllVersion" | ||
- | compile group: 'com.google.guava', name: 'guava', version: "$guavaVersion" | ||
- | compile group: 'log4j', name: 'log4j', version: "$log4jVersion" | ||
- | compile group: 'org.slf4j', name: 'slf4j-api', version: "$slf4jVersion" | ||
- | runtime group: 'org.slf4j', name: 'slf4j-log4j12', version: "$slf4jVersion" | ||
- | testCompile group: 'org.apache.camel', name: 'camel-test', version: "$camelVersion" | ||
- | testCompile group: 'org.apache.httpcomponents', name: 'httpclient', version: "$httpClientVersion" | ||
- | testCompile 'junit:junit:4.12' | ||
- | } | ||
- | </code> | ||
- | ===== Reference ===== | ||
- | * [[http://camel.apache.org/throttler.html|Camel - Throttler]] | ||
- | |||
- | ===== ===== | ||
- | ---- | ||
- | \\ | ||
- | ~~DISQUS~~ |