在使用 Fizz 過程當中,可能會碰到:java
等問題,下面依次介紹解決辦法,同時其它二次開發問題亦可參考。mysql
Fizz 採用 webflux 官方默認亦是最優的 http server 實現,並經過 WebFluxConfig 暴露,以方便外界進行細粒度的控制。react
不建議建立多個 http server,即便它們共享同一端口。git
webflux 默認基於 reactor-netty 實現 http server,可經過 NettyReactiveWebServerFactory 進行定製和擴展,包括 tcp、http、reactor-netty、netty、系統資源等層面,fizz 的 WebFluxConfig 含 NettyReactiveWebServerFactory bean,可修改或建立新的 NettyReactiveWebServerFactory bean 以定製 http server,下面是建立NettyReactiveWebServerFactory bean 的例子。github
@Bean
public NettyReactiveWebServerFactory nettyReactiveWebServerFactory(ServerProperties serverProperties) {
NettyReactiveWebServerFactory httpServerFactory = new NettyReactiveWebServerFactory();
httpServerFactory.setResourceFactory(null);
LoopResources lr = LoopResources.create("fizz-el", 1, Runtime.getRuntime().availableProcessors(), true);
httpServerFactory.addServerCustomizers(
httpServer -> (
httpServer.tcpConfiguration(
tcpServer -> {
return (
tcpServer
.runOn(lr, false) // 指定運行 server 的 eventloop 資源
.port(8080) // server 監聽的端口
.bootstrap(
serverBootstrap -> (
// 下面定製 netty 並調整 tcp 參數
serverBootstrap
.option(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT)
.childOption(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
)
)
);
}
)
)
);
return httpServerFactory;
}
複製代碼
netty 的默認配置適用絕大多數場景,儘可能少調整。web
對外 http 交互,可直接使用 fizz 的 FizzWebClient 或 proxyWebClient,proxyWebClient 就是一個 org.springframework.web.reactive.function.client.WebClient, FizzWebClient 也是基於 proxyWebClient,提供了與 eureka 註冊中心服務交互的便利。redis
儘可能共享 FizzWebClient 或 proxyWebClient 進行 http 操做,不建議引入 apache httpclient、feign 等 http 客戶端,即便它們是異步、響應式的,確實須要建立額外的 WebClient 時,可參考 proxyWebClientConfig 的作法,而後儘可能共享新建的 WebClient,例如:spring
private ConnectionProvider getConnectionProvider() {
return ConnectionProvider.builder("fizz-cp").maxConnections(2_000)
.pendingAcquireTimeout(Duration.ofMillis(6_000))
.maxIdleTime(Duration.ofMillis(40_000))
.build();
}
private LoopResources getLoopResources() {
LoopResources lr = LoopResources.create("fizz-wc-el", Runtime.getRuntime().availableProcessors(), true);
lr.onServer(false);
return lr;
}
public WebClient webClient() {
ConnectionProvider cp = getConnectionProvider(); // 客戶端鏈接池
LoopResources lr = getLoopResources(); // 運行客戶端的 eventloop 資源
HttpClient httpClient = HttpClient.create(cp).compress(false).tcpConfiguration(
tcpClient -> {
return tcpClient.runOn(lr, false)
// 定製客戶端底層 netty
// .bootstrap(
// bootstrap -> (
// bootstrap.channel(NioSocketChannel.class)
// )
// )
.doOnConnected(
connection -> {
connection.addHandlerLast(new ReadTimeoutHandler( 20_000, TimeUnit.MILLISECONDS))
.addHandlerLast(new WriteTimeoutHandler(20_000, TimeUnit.MILLISECONDS));
}
)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 20_000)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT);
}
);
return WebClient.builder().exchangeStrategies(ExchangeStrategies.builder().codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(-1)).build())
.clientConnector(new ReactorClientHttpConnector(httpClient)).build();
}
複製代碼
WebClient 的配置和 http server 是相似的。sql
若是須要在請求處理的流水線上加入邏輯,可經過插件機制實現,具體可參考插件章節,儘可能避免自定義 WebFilter,若是須要,應該繼承 ProxyAggrFilter:數據庫
public abstract class ProxyAggrFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
String serviceId = WebUtils.getServiceId(exchange); // 即請求的 path 是以 /proxy 開頭
if (serviceId == null) {
return chain.filter(exchange);
} else {
return doFilter(exchange, chain);
}
}
public abstract Mono<Void> doFilter(ServerWebExchange exchange, WebFilterChain chain);
}
複製代碼
實現 doFilter 方法便可,注意 filter 的執行順序,需在 fizz 的 PreFilter 和 RouteFilter 之間。
不建議在 fizz 中直接與 mysql 等傳統數據庫交互,由於它們沒有原生的異步客戶端,儘可能把數據轉移到分佈式或本地緩存中,如 redis。
對 redis/codis、mongo、kafka 等操做,應使用 spring 官方提供的響應式客戶端,注意客戶端版本要與 spring boot 版本一致, 客戶端使用可參官方文檔,至於與 fizz 的整合,包括涉及多服務端的場景等,可參考 fizz 內置的 redis 交互邏輯設計和實現,RedisReactiveConfig 及子類AggregateRedisConfig。
好比有個 biz0 redis 庫,在 fizz 中可按以下方式定義與其交互的邏輯:
在 application.yml 中加入:
biz0.redis.host: biz0 的 ip
biz0.redis.port: 6379
biz0.redis.password: 123456
biz0.redis.database: 0
複製代碼
定義對應 yml 的配置 bean,及與其交互的 redistemplate:
@Configuration
public class Biz0RedisConfig extends RedisReactiveConfig {
static final String BIZ0_REACTIVE_REDIS_PROPERTIES = "biz0ReactiveRedisProperties";
static final String BIZ0_REACTIVE_REDIS_CONNECTION_FACTORY = "biz0ReactiveRedisConnectionFactory";
static final String BIZ0_REACTIVE_REDIS_TEMPLATE = "biz0ReactiveRedisTemplate";
@ConfigurationProperties(prefix = "biz0.redis")
@Configuration(BIZ0_REACTIVE_REDIS_PROPERTIES) // 此 bean 對應上面 yml 配置
public static class biz0RedisReactiveProperties extends RedisReactiveProperties {
}
public Biz0RedisConfig(@Qualifier(BIZ0_REACTIVE_REDIS_PROPERTIES) RedisReactiveProperties properties) {
super(properties);
}
@Override
@Bean(BIZ0_REACTIVE_REDIS_CONNECTION_FACTORY)
public ReactiveRedisConnectionFactory lettuceConnectionFactory() {
return super.lettuceConnectionFactory();
}
@Override
@Bean(BIZ0_REACTIVE_REDIS_TEMPLATE) // 與 biz0 redis 交互的 template
public ReactiveStringRedisTemplate reactiveStringRedisTemplate( @Qualifier(BIZ0_REACTIVE_REDIS_CONNECTION_FACTORY) ReactiveRedisConnectionFactory factory) {
return super.reactiveStringRedisTemplate(factory);
}
}
複製代碼
RedisReactiveConfig 是 fizz 抽象的通用 redis/codis 配置,支持多服務端場景,並能在各對應客戶端間共享資源:
public abstract class RedisReactiveConfig {
protected static final Logger log = LoggerFactory.getLogger(RedisReactiveConfig.class);
// this should not be changed unless there is a truly good reason to do so
private static final int ps = Runtime.getRuntime().availableProcessors();
private static final ClientResources clientResources = DefaultClientResources.builder()
.ioThreadPoolSize(ps)
.computationThreadPoolSize(ps)
.build();
private RedisReactiveProperties redisReactiveProperties;
// 子類覆蓋並定義配置
public RedisReactiveConfig(RedisReactiveProperties properties) {
redisReactiveProperties = properties;
}
// 子類覆蓋,建立與特定 redis/codis 交互的 template
public ReactiveStringRedisTemplate reactiveStringRedisTemplate(ReactiveRedisConnectionFactory fact) {
return new ReactiveStringRedisTemplate(fact);
}
// 子類覆蓋,指定客戶端鏈接池,一般子類不用調整此邏輯
public ReactiveRedisConnectionFactory lettuceConnectionFactory() {
log.info("connect to " + redisReactiveProperties);
RedisStandaloneConfiguration rcs = new RedisStandaloneConfiguration(redisReactiveProperties.getHost(), redisReactiveProperties.getPort());
String password = redisReactiveProperties.getPassword();
if (password != null) {
rcs.setPassword(password);
}
rcs.setDatabase(redisReactiveProperties.getDatabase());
LettucePoolingClientConfiguration ccs = LettucePoolingClientConfiguration.builder()
.clientResources(clientResources)
.clientOptions(ClientOptions.builder().publishOnScheduler(true).build())
.poolConfig(new GenericObjectPoolConfig())
.build();
return new LettuceConnectionFactory(rcs, ccs);
}
}
複製代碼
做者:hongqiaowei Fizz Gateway開源地址:github.com/wehotel/fiz…
Fizz官方技術交流①羣(已滿) Fizz官方技術交流②羣(已滿) Fizz官方技術交流③羣:512164278