반응형

엔디비아, 그리고 역발상의 투자

최근 주식 시장에서 '엔디비아'라는 이름을 한 번쯤은 들어봤을 거예요. 엔디비아는 인공지능, 게이밍, 자동차 등 다양한 분야에서 사용되는 그래픽 처리 장치(GPU)를 제조하는 선도 기업이죠. 하지만 주식 시장에서는 항상 승자만 있는 것이 아닙니다. 때로는 주가가 하락할 때도 있고, 이런 상황에서도 기회를 찾아 수익을 내는 방법이 있어요. 그중 하나가 바로 '인버스 주식'입니다. 엔디비아 주식이 하락할 것이라 예상한다면, 인버스 도전은 어떨까요? 잠깐! 인버스가 뭔지 모른다고요? 걱정 마세요, 지금부터 하나하나 알아보도록 해요.

 

인버스 주식이란?

1. 인버스 주식의 개념

인버스 주식이란, 특정 주식이나 지수의 가치가 하락할 때 오히려 가치가 상승하는 주식을 의미해요. 즉, 주가 하락을 예측하고 그 상황에서 이익을 얻고자 할 때 선택할 수 있는 투자 방법입니다. 마치 주식 시장의 역발상이라고 할 수 있죠.

2. 엔디비아 주식과 인버스

엔디비아 주식은 기술 주식 중에서도 특히 변동성이 큰 편에 속합니다. 따라서 엔디비아 주식에 인버스로 투자한다는 것은, 엔디비아 주식의 가격 하락을 예상하고 그 상황에서 수익을 내겠다는 전략이에요.

3. 인버스 투자의 리스크

하지만 인버스 투자는 높은 수익을 기대할 수 있는 만큼, 리스크도 크다는 것을 명심해야 해요. 주가가 예상과 반대로 상승한다면, 손실을 볼 수도 있습니다. 따라서 인버스 투자는 시장을 잘 분석하고, 리스크 관리를 철저히 해야 하는 전략이랍니다.

 

인버스 투자, 도전해볼까?

엔디비아 주식에 인버스로 투자하는 것은, 시장의 변동성을 이용해 수익을 내려는 하나의 전략입니다. 하지만 이 방법은 고수익과 고위험을 동반하기 때문에, 투자 결정을 내리기 전에 충분한 정보 수집과 분석이 필요해요. 중고등학생 여러분도 주식 시장의 다양한 면모를 이해하고, 신중하게 투자하는 방법에 대해 고민해보는 좋은 기회가 될 것입니다. 주식 시장의 파도를 타는 것은 쉽지 않지만, 이 과정에서 배우는 지식과 경험은 여러분의 미래에 큰 자산이 될 거예요. 인버스 투자, 정말 도전해볼 만하지 않나요?

반응형
반응형

초콜릿, 달콤한 비밀의 시작

초콜릿은 많은 사람들의 사랑을 받는 달콤한 간식입니다. 하지만 최근에 초콜릿 가격이 오르고 있다는 소식이 들려오면서, 우리의 작은 행복에도 먹구름이 드리워졌어요. 왜 그럴까요? 초콜릿 가격 상승의 비밀을 파헤쳐 보면서, 우리가 평소 잘 알지 못했던 초콜릿의 세계에 대해 더 깊이 들여다보는 시간을 가져봅시다.

 

 

초콜릿 가격 상승의 다양한 요인들

  1. 원재료 비용의 상승: 초콜릿의 주원료인 카카오 가격이 오르고 있어요. 카카오는 열대 지방에서만 자라는데, 기후 변화로 인해 생산량이 감소하고 있거든요. 이로 인해 카카오 가격이 상승하면서, 초콜릿 생산 비용도 올라가고 있는 것이죠.
  2. 운송 비용의 증가: 전 세계적인 팬데믹 상황과 연료 가격의 상승으로 인해 운송 비용이 크게 증가했어요. 초콜릿은 생산지에서 우리의 손에 도달하기까지 여러 나라를 거쳐야 하는데, 이 운송 과정에서 발생하는 비용 증가가 초콜릿 가격 상승으로 이어지고 있답니다.
  3. 수요의 증가: 사람들이 집에서 보내는 시간이 많아지면서, 달콤한 간식에 대한 수요가 증가했어요. 특히, 고품질의 프리미엄 초콜릿에 대한 수요가 높아지고 있는데, 이런 트렌드도 가격 상승에 한몫하고 있죠.
  4. 환율 변동: 세계 시장에서의 환율 변동도 초콜릿 가격에 영향을 미치고 있어요. 대부분의 카카오는 해외에서 수입되기 때문에, 환율이 높아지면 수입 비용도 증가하게 되고, 이는 곧 초콜릿 가격 상승으로 이어지게 되죠.

 

초콜릿 가격 상승, 우리가 할 수 있는 것은?

초콜릿 가격이 오르는 것은 여러 복합적인 요인들이 얽혀 있기 때문에, 단순한 문제가 아니에요. 하지만 우리는 이 상황을 더 잘 이해함으로써, 합리적인 소비 결정을 내릴 수 있어요. 예를 들어, 지속 가능한 방식으로 재배된 카카오를 사용한 초콜릿을 구매하는 것은 환경에도 도움이 되고, 장기적으로 가격 안정에도 기여할 수 있답니다.

초콜릿 가격 상승은 우리에게 달콤한 간식 이상의 것을 생각해보게 만들어요. 이는 우리가 살고 있는 세계의 변화에

대응하는 방법에 대해 깊이 고민해 볼 기회를 제공하는 것이기도 합니다. 지구 온난화와 환경 보호의 중요성, 지속 가능한 소비의 필요성 등 우리가 평소 잘 생각하지 않았던 큰 문제들과도 연결되어 있죠. 초콜릿 한 조각에서 시작된 작은 호기심이 우리가 살아가는 세상을 바라보는 넓은 시각으로 이어질 수 있습니다.

또한, 초콜릿 소비를 통해 우리는 더 많은 정보를 요구하고, 선택하는 소비자로서의 역할을 할 수 있습니다. 예를 들어, 카카오 농부들에게 공정한 대가를 지불하고, 환경을 보호하는 방식으로 생산된 초콜릿을 선택함으로써, 우리는 더 나은 세상을 만드는 데 기여할 수 있어요. 이는 단지 가격 상승을 넘어서, 우리의 선택이 어떻게 세계에 긍정적인 영향을 미칠 수 있는지를 보여주는 좋은 예시가 됩니다.

초콜릿 가격 상승에 대해 알아보면서, 우리는 더 넓은 세계와 연결되어 있음을 깨닫게 됩니다. 우리의 작은 선택 하나하나가 어떻게 큰 변화를 만들어낼 수 있는지, 그리고 우리가 소비자로서 어떻게 더 책임감 있는 결정을 내릴 수 있는지에 대해 생각해 볼 수 있는 기회가 되었으면 합니다. 초콜릿 가격 상승이 단지 달콤한 간식이 조금 더 비싸지는 것 이상의 의미를 가지게 되는 순간, 우리는 더 나은 소비자, 더 나은 지구 시민으로 한 걸음 더 나아갈 수 있을 거예요.

반응형
반응형
plugins {
	id 'org.springframework.boot' version '2.7.5'
}

...
	implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
	implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

 

spring:
  cloud:
    gateway:
      routes:
      - id: test
        uri: http://httpbin.org
        predicates:
        - Path=/get
        filters:
        - TokenRelay=
  security:
    oauth2:
      client:
        registration:
          login-client:
            provider: uaa
            client-id: login-client
            client-secret: secret
            authorization-grant-type: authorization_code
            scope: openid,profile,email,resource.read
            redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
        provider:
          uaa:
            authorization-uri: http://localhost:8090/uaa/oauth/authorize
            token-uri: http://localhost:8090/uaa/oauth/token
            user-info-uri: http://localhost:8090/uaa/userinfo
            user-name-attribute: sub
            jwk-set-uri: http://localhost:8090/uaa/token_keys

 

IDP

https://github.com/spring-cloud-samples/sample-gateway-oauth2login

 

수정사항

uaa-server/build.gradle

 

installUrl = 'https://www-us.apache.org/dist/tomcat/tomcat-8/v8.5.43/bin/apache-tomcat-8.5.43.zip'

=> installUrl = 'http://archive.apache.org/dist/tomcat/tomcat-8/v8.5.43/bin/apache-tomcat-8.5.43.zip'

 

uaa-server/uaa.yml

 

redirect-uri: http://localhost:8080/login/oauth2/code/login-client

=> 자신 redirect uri로 설정

 

 

참고

https://www.baeldung.com/spring-cloud-gateway-oauth2

https://jainkku.tistory.com/m/90

 

https://cloud.spring.io/spring-cloud-static/spring-cloud-security/2.2.2.RELEASE/reference/html/#_client_token_relay_in_spring_cloud_gateway

https://github.com/spring-cloud-samples/sample-gateway-oauth2login

 

반응형
반응형

일반적으로 경량화 API Gateway 제품들은 단일 포트로 요청을 받아 Gateway 기능을 수행한다. 하지만 일부 제품에서는 다중 포트를 열어 특정 포트마다 특정 엔드포인트로 라우팅하는 기능도 제공한다.

 

Spring Cloude Gateway를 이용하여 API Gateway를 구현할 때 위와 같이 다중 포트를 열 수 있는지 리서치를 해보았다.

 

Spring Cloud Gateway에는 Spring Boot 및 Spring Webflux에서 제공하는 Netty 라이브러리가 반드시 필요하다. 당연하게도 Netty 자체에서는 다중 포트를 열어 각각의 Server 구성이 가능하다.

import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;

public class MultiAddressApplication {
	public static void main(String[] args) {
		HttpServer httpServer = HttpServer.create();
		DisposableServer server1 = httpServer
				.host("localhost") 
				.port(8080)        
				.bindNow();

		DisposableServer server2 = httpServer
				.host("0.0.0.0") 
				.port(8081)      
				.bindNow();

		Mono.when(server1.onDispose(), server2.onDispose())
				.block();
	}
}

 

하지만 Spring Cloud Gateway 내부 로직에서 사용하는 Webflux는 단일 포트만으로 처리할 수 있는 것으로 판단된다. 

 

아래는 해당 기능을 요청한 이슈들이지만 결국 block 되었다.

https://github.com/reactor/reactor-netty/issues/67

https://github.com/spring-projects/spring-boot/issues/12035

 

 

 

 

 

 

반응형

'Spring > Spring Cloud' 카테고리의 다른 글

[Spring Cloud Gateway] 라우팅 동적반영(refresh)  (0) 2024.03.17
[Spring Cloud] Gateway + OAuth2 Client  (0) 2022.11.21
Spring Cloud Gateway  (0) 2022.10.24
반응형

공식문서 : link

getting started : link

 

1. 개요

Spring Cloud Gateway는 API Gateway 기능을 구현할 수 있는 라이브러리로 대표적인 기능으로는 routing, filtering 있다.

@SpringBootApplication
public class DemogatewayApplication {
	@Bean
	public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
		return builder.routes()
			.route("path_route", r -> r.path("/get")
				.uri("http://httpbin.org"))
			.route("host_route", r -> r.host("*.myhost.org")
				.uri("http://httpbin.org"))
			.route("rewrite_route", r -> r.host("*.rewrite.org")
				.filters(f -> f.rewritePath("/foo/(?<segment>.*)", "/${segment}"))
				.uri("http://httpbin.org"))
			.route("hystrix_route", r -> r.host("*.hystrix.org")
				.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
				.uri("http://httpbin.org"))
			.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
				.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
				.uri("http://httpbin.org"))
			.route("limit_route", r -> r
				.host("*.limited.org").and().path("/anything/**")
				.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
				.uri("http://httpbin.org"))
			.build();
	}
}

 

 

2. 설정 클래스

- org.springframework.cloud.gateway.route.RouteDefinition  : 라우터 IO

- org.springframework.cloud.gateway.route.Route : RouteDefinitionRouteLocator에서 RouteDefinition을 Route로 변경

- org.springframework.cloud.gateway.route.Route.AsyncBuilder : Route Builder

- org.springframework.cloud.gateway.route.Route.Builder : Route Builder

 

3. 중요 클래스

org.springframework.cloud.gateway.route.CachingRouteLocator

- Route를 캐싱하는 클래스

- RefreshRoutesEvent 이벤트를 받아 RouteLocator에서 라우터를 다시 읽어 캐시 갱신

- 요청이 들어올 경우 RoutePredicateHandlerMapping.lookupRoute에서 CachingRouteLocator.getRoutes를 호출하여 캐싱된 Route 리스트 Get

 

org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping

- 클라이언트 요청에 대해 알맞은 라우팅을 lookup하는 클래스

 

org.springframework.cloud.gateway.handler.FilteringWebHandler

- 글로벌 필터와 라우터에 등록되어 있는 필터를 합쳐 필터를 수행한다.

 

org.springframework.cloud.gateway.filter.NettyRoutingFilter

- 실제 라우팅 URI에 요청하는 필터

 

org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint, GatewayControllerEndpoint

- 실제 actuate 요청을 받는 컨트롤러

-  5. actuator 설정 참조

 

4. 요청 흐름도

0. Client Request

 

1. reactor.netty.http.server.HttpServer.HttpServerHandle.onStateChange

 

2. org.springframework.http.server.reactive.ReactorHttpHandlerAdapter.apply

 

3. org.springframework.web.server.adapter.HttpWebHandlerAdapter.handle

 

4. org.springframework.web.reactive.DispatcherHandler.handle

 

5. org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping.getHandlerInternal

 

6. org.springframework.cloud.gateway.handler.FilteringWebHandler.handle

 

7. org.springframework.cloud.gateway.handler.FilteringWebHandler.DefaultGatewayFilterChain.filter

 

 

5. actuator 설정

- pom.xml

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

 

- application.yml

management:
  endpoints:
    web:
      exposure:
        include: 
          - gateway
  endpoint:
    gateway:
      enabled: true

 

- endpoints

  • GET /actuator/gateway/globalfilters: Displays the list of global filters applied to the routes.
  • GET /actuator/gateway/routefilters: Displays the list of GatewayFilter factories applied to a particular route.
  • POST /actuator/gateway/refresh: Clears the routes cache.
  • GET /actuator/gateway/routes: Displays the list of routes defined in the gateway.GET /actuator/gateway/routes/{id}: 특정 route 설정 정보
  • GET /actuator/gateway/routes/{id} : Displays information about a particular route.
  • POST /actuator/gateway/routes/{id}: Add a new route to the gateway.
  • DELETE /actuator/gateway/routes/{id}: Remove an existing route from the gateway.

 

 

참고

https://blog.jungbin.kim/spring/2021/02/27/spring-cloud-gateway-route-locator.html

https://dlsrb6342.github.io/2019/05/14/spring-cloud-gateway-%EA%B5%AC%EC%A1%B0/

DB를 이용한 동적반영

https://medium.com/bliblidotcom-techblog/spring-cloud-gateway-dynamic-routes-from-database-dc938c6665de

반응형
반응형

@RestController는 @ResponseBody를 디폴트로 포함하므로 생략 가능.

반응형

'Spring' 카테고리의 다른 글

[Spring] SQLException to Spring DataAccessException 변환  (0) 2022.05.24
반응형

이슈

rewriteBatchedStatements=true

고객사에서 위와 같은 mysql 옵션을 추가하자 다음과 같은 오류가 발생하였다.

java.lang.NullPointerException
at com.mysql.cj.ClientPreparedQuery.computeMaxParameterSetSizeAndBatchSize(ClientPreparedQuery.java:66)

 

오류가 발생한 코드는 Batch JDBC 코드를 작성할 때 자주 사용하는 보일러 플레이트 코드로 executeBatch()가 실행될 때 오류가 발생하였다.

Blob blob = ...
try (Connection connection = DBUtil.getInstance().getConnection();
         PreparedStatement preparedStatement = connection.prepareStatement(query)) {
        connection.setAutoCommit(false);
        for (TransactionBatch batch : batches) {
            try {                   
                preparedStatement.setString(1, batch.getDeviceID());
                preparedStatement.setBlob(2, blob);
                preparedStatement.addBatch();
            } catch (Exception e) {
               e.printStackTrace();
            }
        }

        preparedStatement.executeBatch();
        blob.free();

 

해결

연구소에서 재현을 위해 rewriteBatchedStatements=true 옵션을 추가했는데 동일한 현상이 발생하지 않았고 추후 확인해보니 고객사와 mysql-connector-java 버전에 차이가 있었다.

연구소는 버전이 5.1.38, 고객사는 8.0.19였다. 버전을 고객사와 동일하게 맞춘 후 동일한 현상이 재현되었다.

구글링을 해보니 setBlob() 또는 setBinaryStream()을 사용할 때 문제가 발생하며 해당 메서드 대신 setBytes()를 사용해야 했다. setBytes()를 사용하면 오류가 발생하지 않는다.

이유

이유를 좀 더 찾아보자.

 

2015년 6월에 스택오버플로우에서 해당 관련 글이 올라왔다. (computeMaxParameterSetSizeAndBatchSize가 PreparedStatement에 있는 것으로 보아 버전이 달라보인다.). 해당 답변에서는 rewrite를 하기 위해 사이즈를 재 계산해야 하지만 input stream에서는 할 수 없다라는 의미처럼 보이지만, 정확히 이해가 되지 않는다.

 

 

2017년 3월에 mysql 커뮤니티에 버그 리포트(#85317)로 해당 관련 글이 올라왔다. mysql 개발자가 해당 글을 당일 확인하고 버그로 판단하고 해결되기까지...........무려 5년이 걸렸다.

 

5년 뒤인 8.0.29 버전에 이슈가 패치되었다고 한다. => 링크

  • When the connection property rewriteBatchedStatements was set to true, inserting a BLOB using a prepared statement and executeBatch() resulted in a NullPointerException. (Bug #85317, Bug #25672958)

setBlob()을 사용한 기존 코드를 유지한 채 8.0.29 버전으로 수행하니 더 이상 오류가 발생하지 않았다.

 

8.0.19와 8.0.29로 코드를 비교해서 어떠한 점이 패치되었는지 판단하려 하였으나, 많은 부분이 변경 되어 확인하기가 어려웠다.

 

8.0.19 버전에서 NPE가 발생한 소스는 다음과 같다.

   @Override
    protected long[] computeMaxParameterSetSizeAndBatchSize(int numBatchedArgs) {
        long sizeOfEntireBatch = 0;
        long maxSizeOfParameterSet = 0;

        for (int i = 0; i < numBatchedArgs; i++) {
            ClientPreparedQueryBindings qBindings = (ClientPreparedQueryBindings) this.batchedArgs.get(i);

            BindValue[] bindValues = qBindings.getBindValues();

            long sizeOfParameterSet = 0;

            for (int j = 0; j < bindValues.length; j++) {
                if (!bindValues[j].isNull()) {

                    if (bindValues[j].isStream()) {
                        long streamLength = bindValues[j].getStreamLength();

                        if (streamLength != -1) {
                            sizeOfParameterSet += streamLength * 2; 
                        } else {
                            int paramLength = qBindings.getBindValues()[j].getByteValue().length; // NPE 발생
                            sizeOfParameterSet += paramLength;
                        }
                        
                        ....

 

getByteValue()를 하는 중 배열에 들어 있는 값이 null이라 발생하지 않았을 까 싶다..

 

 

 

반응형
반응형

swagger-ui는 아래 예제처럼 url 이라는 쿼리 파라미터로 외부 리소스의 API 스펙을 임포트하여 사용할 수 있다.

https://petstore.swagger.io/?url=https://darosh.github.io/openapi-directory-lite/specs/github.com/v3.json

 

3.3.8 이후 버전부터는 XSS 보안 이슈로 인하여 해당 옵션을 일반적으로 사용할 수 없도록 변경하였다.

해당 보안 이슈는 다음을 참고한다.

 

사용해야 한다면 재량적으로 쿼리 파라미터에 queryConfigEnabled=true을 주어 사용할 수 있다.

해당 패치 노트는 다음을 참고한다.

 

필자는 swagger-ui를 v3.36.1을 사용하고 있었으며 이후 springdoc 1.6.9 버전으로 마이그레이션을 진행했다.

springdoc 1.6.9 버전은 swagger-ui 4.11.1 버전을 포함하고 있었기 때문에 위와 같은 문제를 직면했다.

 

필자는 특이하게도 springdoc을 이용하여 기본 url인 localhost:8080/swagger-ui/index.html을 사용하지 않고, 특별한 방식으로 swagger-ui를 import해서 사용했다.

관련하여 다음을 참고한다.

@Configuration
@SpringBootApplication
@EnableTransactionManagement
public class App implements WebMvcConfigurer {

  ...

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/tester/**")
        .addResourceLocations("classpath:/META-INF/resources/static/",
            "classpath:/META-INF/resources/webjars/swagger-ui/4.11.1/");
  }

 

따라서 swagger를 사용할 때는 localhost:8080/tester/index.html를 사용했고 url 쿼리 파라미터를 위해 queryConfigEnabled를 사용하였으나 동작하지 않았다.

 

혹시나 하여 기본적으로 제공하는 springdoc의 localhost:8080/swagger-ui/index.html에서도 동작하지 않았다.

 

이후, springdoc에서 제공하는 설정값을 아래와 같이 세팅하여 기동하니 queryConfigEnabled를 주지 않아도 url 옵션이 동작하였다.

springdoc:
  swagger-ui:
    query-config-enabled: true

 

 

반응형

+ Recent posts