1. Dependency
ํ๋ก์ ํธ ์์กด์ฑ์ spring-cloud-starter-openfeign๋ฅผ ์ถ๊ฐํ๋ค.
implementation 'org.springframework.cloud:spring-cloud-starter-openfeign'
2. Feign client ํ์ฑํ
@EnableFeignClients ์ ๋ ธํ ์ด์ ์ ์ถ๊ฐํ์ฌ feign client๋ฅผ ํ์ฑํํ๋ค.
@SpringBootApplication ๋๋ @Configuration ํด๋์ค์ ์ถ๊ฐํ๋ฉด ๋๋ค.
@EnableFeignClients
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
3. Feign ์ธํฐํ์ด์ค ๊ฐ๋ฐ
Feign ํด๋ผ์ด์ธํธ๋ ์ธํฐํ์ด์ค์ @FeignClient๋ฅผ ๋ถ์ด๋ ๊ฒ๋ง์ผ๋ก ๋ง๋ค ์ ์๋ค.
๋ค์์ feign ํด๋ผ์ด์ธํธ์ ์์ด๋ค.
@FeignClient(name = "coupon", url = "${api-url}")
public interface CouponClient {
@GetMapping("/coupon/v1/coupons/infos")
List<Coupon> infos(@RequestParam("couponNos") List<Long> couponNos);
@GetMapping("/coupon/v1/issue/status/{memberNo}")
CouponIssueStatusInfo issueStatusInfo(@PathVariable(value = "memberNo") String memberNo,
@RequestParam(value = "couponNo") Long couponNo);
@GetMapping("/coupon/v1/issued/status/{memberNo}")
List<CouponIssueInfo> issueInfos(@PathVariable("memberNo") String memberNo,
@RequestParam("couponNos") List<Long> couponNos);
}
Spring mvc์ handler mapping ์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํด์ ํธ๋ค๋ฌ๋ฅผ ์ ์ํ๋ค.
์ฌ์ฉ๋ @FeignClient ์์ฑ์ ๋ํด ์์๋ณด์.
name
name์ ํ์ ์์ฑ์ผ๋ก ์๋น์ค ID ๋๋ ์๋น์ค์ ๋ ผ๋ฆฌ์ ์ธ ์ด๋ฆ์ด๋ค. ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ์์ ์ฌ์ฉ๋๋ค.
url
url์ feign client๋ฅผ ํตํด ํธ์ถํ url์ด๋ค. eureka, ribbon, hystrix๋ฅผ ์ฌ์ฉํ์ง ์์ ๋ ์ค์ ํ๋ค. ์๋ฅผ ๋ค์ด http://api.atoz-develop.com:8765 ์ ๊ฐ์ด ์ค์ ํ๋ค. profile ๋ณ๋ก ๋ค๋ฅธ url์ ์ฌ์ฉํ๋ ค๋ฉด ์ ์ฝ๋์ ๊ฐ์ด ์ค์ ์ผ๋ก ๋นผ๋ฉด ๋๋ค.
4. Feign Client ์ฌ์ฉ
์์ฑํ feign client๋ ๋น์ ์ฃผ์ ๋ฐ์์ ์ฌ์ฉํ ์ ์๋ค.
@Slf4j
@RequiredArgsConstructor
@Service
public class DefaultInquireCouponService implements InquireCouponService {
private final CouponClient couponClient;
@Override
public List<Coupon> getCouponInfos(String couponNumbers) {
try {
List<Coupon> couponInfoList = couponClient.infos(couponNos);
...
} catch (Exception ex) {
log.error("##getCouponInfos ERR:", ex);
}
return couponInfos;
}
}
5. Eureka(์ ๋ ์นด) ์ ์ฉ
์์์๋ @FeignClient์ url ์์ฑ์ ํธ์ถํ ๋์์ ์ง์ ์ง์ ํ๋ค.
์ด๋ฒ์๋ eureka๋ฅผ ์ฌ์ฉํด์ ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ๋ฅผ ์ ์ฉํด๋ณด์.
์ ๋ ์นด ์๋ฒ๋ ์ค๋น๋ ๊ฒ์ผ๋ก ๊ฐ์ ํ๋ค.
dependency
eureka client ์์กด์ฑ์ ์ถ๊ฐํ๋ค.
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
Eureka Client ํ์ฑํ
@EnableEurekaClient๋ฅผ ์ถ๊ฐํ์ฌ eureka client๋ฅผ ํ์ฑํํ๋ค.
@EnableFeignClients
@EnableEurekaClient
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
์ค์
๋์ค์ปค๋ฒ๋ฆฌ ์ค์ ์ ์ถ๊ฐํ๋ค.
eureka.client.serviceUrl.defaultZone์ ์ ๋ ์นด ์๋ฒ url์ ์ค์ ํ๋ฉด ๋๋ค.
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
์ถ๊ฐ๋ก ์ ๋ ์นด ์๋ฒ์ ํ์ฌ ํด๋ผ์ด์ธํธ ์๋น์ค๋ฅผ ๋ฑ๋กํ์ง ์์ผ๋ ค๋ฉด register-with-eureka๋ฅผ false๋ก ์ค์ ํ๋ค.
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://localhost:8761/eureka/
Feign Client ์์
์ด์ ์์ฑํ feign client์์ @FeignClient์ url ์์ฑ์ ์ ๊ฑฐํ๋ค.
@FeignClient(name = "coupon")
public interface CouponClient {
...
}
์๋น์ค ๊ธฐ๋ ํ ์๋ต์ด ์ ์์ ์ผ๋ก ์ค๋ฉด ์๋น์ค ๋์ค์ปค๋ฒ๋ฆฌ๊ฐ ์ ์ ์ฉ๋ ๊ฒ์ด๋ค.
์ถ๊ฐ๋ก feign client๋ ๋ค์๊ณผ ๊ฐ์ด timeout ๋ฐ ๋ก๊ทธ ๋ ๋ฒจ์ ์ค์ ํ ์ ์๋ค.
feign:
client:
config:
default:
loggerLevel: BASIC
connectTimeout: 20000
readTimeout: 20000
connectTimeout์ ์๋น์ค ์ ์ ํ์์์, readTimeout์ HTTP ์ฐ๊ฒฐ ์ฝ๊ธฐ ํ์์์ ์ค์ ์ด๋ค.
default ๋์ ํน์ ์๋น์ค์ ๋ํด ์ค์ ํ ์๋ ์๋ค.
6. hystrix ์ ์ฉ
์ต์ ๋ฒ์ ์ ์คํ๋ง ํด๋ผ์ฐ๋์์๋ feign์ hystrix ํ์ฑํ๊ฐ disable ๋์ด์๊ธฐ ๋๋ฌธ์ ํ์ฑํํ๊ธฐ ์ํด ๋ค์ ์ค์ ์ ์ถ๊ฐํด์ผ ํ๋ค.
feign:
hystrix:
enabled: true
feign client์์ ๋์ ์๋น์ค๋ก์ ์์ฒญ ์คํจ๊ฐ ์๊ณ์น๋ฅผ ๋์ผ๋ฉด ๋์ ์๋ตํ FallBackFactory ํด๋์ค๋ฅผ ์ค์ ํ ์ ์๋ค. ์ฐธ๊ณ ๋ก ์ด๋ฌํ ๋งค์ปค๋์ฆ์ Circuit breaker pattern์ด๋ผ๊ณ ํ๋ฉฐ ์ํท์ด open์ด๋ฉด ์ค๋ฅ ์ํ, close์ด๋ฉด ์ ์ ์ํ๋ฅผ ๋ํ๋ธ๋ค.
์๋์ ๊ฐ์ด FallbackFactory๋ฅผ implementsํ์ฌ ๊ตฌํํ๋ค.
@Slf4j
@Component
public class CouponClientFallbackFactory implements FallbackFactory<CouponClient> {
@Override
public CouponClient create(Throwable cause) {
return new CouponClient() {
@Override
public List<Coupon> infos(List<Long> couponNos) {
log.error("couponNos: {}", couponNos, cause);
return Collections.emptyList();
}
@Override
public CouponIssueStatusInfo issueStatusInfo(String memberNo, Long couponNo) {
log.error("memberNo: {}, couponNo: {}", memberNo, couponNo, cause);
return null;
}
@Override
public List<CouponIssueInfo> issueInfos(String memberNo, List<Long> couponNos) {
log.error("memberNo: {}, couponNos: {}", memberNo, couponNos, cause);
return Collections.emptyList();
}
};
}
}
๋ค์๊ณผ ๊ฐ์ด @FeignClient์ fallbackFactory ์์ฑ๊ฐ์ ์ถ๊ฐํ๋ค.
@FeignClient(name = "coupon", fallbackFactory = CouponClientFallbackFactory.class)
public interface CouponClient {
...
}
6-1. Hystrix ์ค์
Hystrix๋ฅผ java bean์ผ๋ก ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ ์ ์๋ค.
์ฐธ๊ณ ๋ก spring-cloud-netflix-hystrix 2.2.6.RELEASE ๊ธฐ์ค์ด๋ค.
@Bean
public Customizer<HystrixCircuitBreakerFactory> defaultConfig() {
return factory -> factory.configureDefault(id -> HystrixCommand.Setter
.withGroupKey(HystrixCommandGroupKey.Factory.asKey(id))
.andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(20000)
.withCircuitBreakerRequestVolumeThreshold(20)
.withCircuitBreakerErrorThresholdPercentage(50)
.withExecutionIsolationStrategy(HystrixCommandProperties.ExecutionIsolationStrategy.THREAD))
.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter()
.withCoreSize(50)
.withMaximumSize(300)
.withAllowMaximumSizeToDivergeFromCoreSize(true))
);
}
๊ณต์ reference: https://docs.spring.io/spring-cloud-netflix/docs/2.2.6.RELEASE/reference/html/#configuring-hystrix-circuit-breakers
๋๊ธ