鏈€杩戝湪github涓婄湅浜唖oul缃戝叧鐨勮璁★紝绐佺劧灏辨潵浜嗗叴瓒e噯澶囪嚜宸变粠闆跺紑濮嬪啓涓€涓珮鎬ц兘鐨勭綉鍏炽€傜粡杩囦袱鍛ㄦ椂闂寸殑寮€鍙戯紝鎴戠殑缃戝叧ship-gate鏍稿績鍔熻兘鍩烘湰閮藉凡瀹屾垚锛屾渶澶х殑缂洪櫡灏辨槸鍓嶇鍔熷簳澶樊娌℃湁绠$悊鍚庡彴馃槫銆?/p>浜屻"/>

寰掓墜鎾镐簡涓€涓狝PI缃戝叧锛岀悊瑙f洿閫忓交浜嗭紝浠g爜宸蹭笂浼爂ithub锛岃嚜鍙杶

Posted 鍗犲皬鐙肩殑鍗氬

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了寰掓墜鎾镐簡涓€涓狝PI缃戝叧锛岀悊瑙f洿閫忓交浜嗭紝浠g爜宸蹭笂浼爂ithub锛岃嚜鍙杶相关的知识,希望对你有一定的参考价值。

鍥炲鈥?66鈥滆幏鍙栭潰璇曞疂鍏?/span>

涓€銆佽儗鏅?span class="mq-12">

鏈€杩戝湪github涓婄湅浜唖oul缃戝叧鐨勮璁★紝绐佺劧灏辨潵浜嗗叴瓒e噯澶囪嚜宸变粠闆跺紑濮嬪啓涓€涓珮鎬ц兘鐨勭綉鍏炽€傜粡杩囦袱鍛ㄦ椂闂寸殑寮€鍙戯紝鎴戠殑缃戝叧ship-gate鏍稿績鍔熻兘鍩烘湰閮藉凡瀹屾垚锛屾渶澶х殑缂洪櫡灏辨槸鍓嶇鍔熷簳澶樊娌℃湁绠$悊鍚庡彴馃槫銆?/p>

浜屻€佽璁?span class="mq-16">

2.1 鎶€鏈€夊瀷

缃戝叧鏄墍鏈夎姹傜殑鍏ュ彛锛屾墍浠ヨ姹傛湁寰堥珮鐨勫悶鍚愰噺锛屼负浜嗗疄鐜拌繖鐐瑰彲浠ヤ娇鐢ㄨ姹傚紓姝ュ寲鏉ヨВ鍐炽€傜洰鍓嶄竴鑸湁浠ヤ笅涓ょ鏂规锛?/p>

  • Tomcat/Jetty+NIO+Servlet3

Servlet3宸茬粡鏀寔寮傛锛岃繖绉嶆柟妗堜娇鐢ㄦ瘮杈冨锛屼含涓滐紝鏈夎禐鍜孼uul锛岄兘鐢ㄧ殑鏄繖绉嶆柟妗堛€?/p>

  • Netty+NIO

Netty涓洪珮骞跺彂鑰岀敓锛岀洰鍓嶅敮鍝佷細鐨勭綉鍏充娇鐢ㄨ繖涓瓥鐣ワ紝鍦ㄥ敮鍝佷細鐨勬妧鏈枃绔犱腑鍦ㄧ浉鍚岀殑鎯呭喌涓婲etty鏄瘡绉?0w+鐨勫悶鍚愰噺锛孴omcat鏄?3w+,鍙互鐪嬪嚭鏄湁涓€瀹氱殑宸窛鐨勶紝浣嗘槸Netty闇€瑕佽嚜宸卞鐞咹TTP鍗忚锛岃繖涓€鍧楁瘮杈冮夯鐑︺€?/p>

鍚庨潰鍙戠幇Soul缃戝叧鏄熀浜嶴pring WebFlux锛堝簳灞侼etty锛夌殑锛屼笉鐢ㄥお鍏冲績HTTP鍗忚鐨勫鐞嗭紝浜庢槸鍐冲畾涔熺敤Spring WebFlux銆?/p>

缃戝叧鐨勭浜屼釜鐗圭偣鏄叿澶囧彲鎵╁睍鎬э紝姣斿Netflix Zuul鏈塸reFilters锛宲ostFilters绛夊湪涓嶅悓鐨勯樁娈垫柟渚垮鐞嗕笉鍚岀殑涓氬姟锛屽熀浜庤矗浠婚摼妯″紡灏嗚姹傝繘琛岄摼寮忓鐞嗗嵆鍙疄鐜般€?/p>

鍦ㄥ井鏈嶅姟鏋舵瀯涓嬶紝鏈嶅姟閮戒細杩涜澶氬疄渚嬮儴缃叉潵淇濊瘉楂樺彲鐢紝璇锋眰鍒拌揪缃戝叧鏃讹紝缃戝叧闇€瑕佹牴鎹甎RL鎵惧埌鎵€鏈夊彲鐢ㄧ殑瀹炰緥锛岃繖鏃跺氨闇€瑕佹湇鍔℃敞鍐屽拰鍙戠幇鍔熻兘锛屽嵆娉ㄥ唽涓績銆?/p>

鐜板湪娴佽鐨勬敞鍐屼腑蹇冩湁Apache鐨刏ookeeper鍜岄樋閲岀殑Nacos涓ょ锛坈onsul鏈夌偣灏忎紬锛夛紝鍥犱负涔嬪墠鍐橰PC妗嗘灦鏃跺凡缁忕敤杩囦簡Zookeeper锛屾墍浠ヨ繖娆″氨閫夋嫨浜哊acos銆?/p>

2.2 闇€姹傛竻鍗?span class="mq-33">

棣栧厛瑕佹槑纭洰鏍囷紝鍗冲紑鍙戜竴涓叿澶囧摢浜涚壒鎬х殑缃戝叧锛屾€荤粨涓嬪悗濡備笅锛?/p>

鑷畾涔夎矾鐢辫鍒?/strong>

鍙熀浜巚ersion鐨勮矾鐢辫鍒欒缃紝璺敱瀵硅薄鍖呮嫭DEFAUL,HEADER鍜孮UERY涓夌锛屽尮閰嶆柟寮忓寘鎷?銆乺egex銆乴ike涓夌銆?/p>

璺ㄨ瑷€

HTTP鍗忚澶╃敓璺ㄨ瑷€

楂樻€ц兘

Netty鏈韩灏辨槸涓€娆鹃珮鎬ц兘鐨勯€氫俊妗嗘灦锛屽悓鏃秙erver灏嗕竴浜涜矾鐢辫鍒欑瓑鏁版嵁缂撳瓨鍒癑VM鍐呭瓨閬垮厤璇锋眰admin鏈嶅姟銆?/p>

楂樺彲鐢?/strong>

鏀寔闆嗙兢妯″紡闃叉鍗曡妭鐐规晠闅滐紝鏃犵姸鎬併€?/p>

鐏板害鍙戝竷

鐏板害鍙戝竷锛堝張鍚嶉噾涓濋泙鍙戝竷锛夋槸鎸囧湪榛戜笌鐧戒箣闂达紝鑳藉骞虫粦杩囨浮鐨勪竴绉嶅彂甯冩柟寮忋€傚湪鍏朵笂鍙互杩涜A/B testing锛屽嵆璁╀竴閮ㄥ垎鐢ㄦ埛缁х画鐢ㄤ骇鍝佺壒鎬锛屼竴閮ㄥ垎鐢ㄦ埛寮€濮嬬敤浜у搧鐗规€锛屽鏋滅敤鎴峰B娌℃湁浠€涔堝弽瀵规剰瑙侊紝閭d箞閫愭鎵╁ぇ鑼冨洿锛屾妸鎵€鏈夌敤鎴烽兘杩佺Щ鍒癇涓婇潰鏉ャ€傞€氳繃鐗规€т竴鍙互瀹炵幇銆?/p>

鎺ュ彛閴存潈

鍩轰簬璐d换閾炬ā寮忥紝鐢ㄦ埛寮€鍙戣嚜宸辩殑閴存潈鎻掍欢鍗冲彲銆?/p>

璐熻浇鍧囪 

鏀寔澶氱璐熻浇鍧囪 绠楁硶锛屽闅忔満锛岃疆璇紝鍔犳潈杞绛夈€傚埄鐢⊿PI鏈哄埗鍙互鏍规嵁閰嶇疆杩涜鍔ㄦ€佸姞杞姐€?/p>

2.3 鏋舵瀯璁捐

鍦ㄥ弬鑰冧簡涓€浜涗紭绉€鐨勭綉鍏砕uul,Spring Cloud Gateway,Soul鍚庯紝灏嗛」鐩垝鍒嗕负浠ヤ笅鍑犱釜妯″潡銆?/p>

寰掓墜鎾镐簡涓€涓狝PI缃戝叧锛岀悊瑙f洿閫忓交浜嗭紝浠g爜宸蹭笂浼爂ithub锛岃嚜鍙杶

瀹冧滑涔嬮棿鐨勫叧绯诲鍥撅細

寰掓墜鎾镐簡涓€涓狝PI缃戝叧锛岀悊瑙f洿閫忓交浜嗭紝浠g爜宸蹭笂浼爂ithub锛岃嚜鍙杶
缃戝叧璁捐

娉ㄦ剰锛?/strong> 杩欏紶鍥句笌瀹為檯瀹炵幇鏈夌偣鍑哄叆锛孨acos push鍒版湰鍦扮紦瀛樼殑閭d釜鐜妭娌℃湁瀹炵幇锛岀洰鍓嶅彧鏈塻hip-sever瀹氭椂杞pull鐨勮繃绋嬨€俿hip-admin浠嶯acos鑾峰彇娉ㄥ唽鏈嶅姟淇℃伅鐨勮繃绋嬶紝涔熸敼鎴愪簡ServiceA鍚姩鏃朵富鍔ㄥ彂鐢烪TTP璇锋眰閫氱煡ship-admin銆?/p>

2.4 琛ㄧ粨鏋勮璁?span class="mq-70">

寰掓墜鎾镐簡涓€涓狝PI缃戝叧锛岀悊瑙f洿閫忓交浜嗭紝浠g爜宸蹭笂浼爂ithub锛岃嚜鍙杶

涓夈€佺紪鐮?span class="mq-75">

3.1 ship-client-spring-boot-starter

棣栧厛鍒涘缓涓€涓猻pring-boot-starter鍛藉悕涓簊hip-client-spring-boot-starter锛屼笉鐭ラ亾濡備綍鑷畾涔塻tarter鐨勫彲浠ョ湅鎴戜互鍓嶅啓鐨勩€婂紑鍙戣嚜宸辩殑starter銆嬨€?/p>

鍏舵牳蹇冪被 AutoRegisterListener 灏辨槸鍦ㄩ」鐩惎鍔ㄦ椂鍋氫簡涓や欢浜嬶細

1.灏嗘湇鍔′俊鎭敞鍐屽埌Nacos娉ㄥ唽涓績

2.閫氱煡ship-admin鏈嶅姟涓婄嚎浜嗗苟娉ㄥ唽涓嬬嚎hook銆?/p>

浠g爜濡備笅锛?/p>

* Created by 2YSP on 2020/12/21
*/
public class AutoRegisterListener implements ApplicationListener<ContextRefreshedEvent{

   private final static Logger LOGGER = LoggerFactory.getLogger(AutoRegisterListener.class);

   private volatile AtomicBoolean registered = new AtomicBoolean(false);

   private final ClientConfigProperties properties;

   @NacosInjected
   private NamingService namingService;

   @Autowired
   private RequestMappingHandlerMapping handlerMapping;

   private final ExecutorService pool;

   /**
* url list to ignore
*/

   private static List<String> ignoreUrlList = new LinkedList<>();

   static {
       ignoreUrlList.add("/error");
   }

   public AutoRegisterListener(ClientConfigProperties properties) {
       if (!check(properties)) {
           LOGGER.error("client config port,contextPath,appName adminUrl and version can't be empty!");
           throw new ShipException("client config port,contextPath,appName adminUrl and version can't be empty!");
       }
       this.properties = properties;
       pool = new ThreadPoolExecutor(140, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
   }

   /**
* check the ClientConfigProperties
*
@param properties
@return
*/

   private boolean check(ClientConfigProperties properties) {
       if (properties.getPort() == null| properties.getContextPath() == null
              | properties.getVersion() == null| properties.getAppName() == null
              | properties.getAdminUrl() == null) {
           return false;
       }
       return true;
   }


   @Override
   public void onApplicationEvent(ContextRefreshedEvent event) {
       if (!registered.compareAndSet(falsetrue)) {
           return;
       }
       doRegister();
       registerShutDownHook();
   }

   /**
* send unregister request to admin when jvm shutdown
*/

   private void registerShutDownHook() {
       final String url = "http://" + properties.getAdminUrl() + AdminConstants.UNREGISTER_PATH;
       final UnregisterAppDTO unregisterAppDTO = new UnregisterAppDTO();
       unregisterAppDTO.setAppName(properties.getAppName());
       unregisterAppDTO.setVersion(properties.getVersion());
       unregisterAppDTO.setIp(IpUtil.getLocalIpAddress());
       unregisterAppDTO.setPort(properties.getPort());
       Runtime.getRuntime().addShutdownHook(new Thread(() -> {
           OkhttpTool.doPost(url, unregisterAppDTO);
           LOGGER.info("[{}:{}] unregister from ship-admin success!", unregisterAppDTO.getAppName(), unregisterAppDTO.getVersion());
       }));
   }

   /**
* register all interface info to register center
*/

   private void doRegister() {
       Instance instance = new Instance();
       instance.setIp(IpUtil.getLocalIpAddress());
       instance.setPort(properties.getPort());
       instance.setEphemeral(true);
       Map<String, String> metadataMap = new HashMap<>();
       metadataMap.put("version", properties.getVersion());
       metadataMap.put("appName", properties.getAppName());
       instance.setMetadata(metadataMap);
       try {
           namingService.registerInstance(properties.getAppName(), NacosConstants.APP_GROUP_NAME, instance);
       } catch (NacosException e) {
           LOGGER.error("register to nacos fail", e);
           throw new ShipException(e.getErrCode(), e.getErrMsg());
       }
       LOGGER.info("register interface info to nacos success!");
       // send register request to ship-admin
       String url = "http://" + properties.getAdminUrl() + AdminConstants.REGISTER_PATH;
       RegisterAppDTO registerAppDTO = buildRegisterAppDTO(instance);
       OkhttpTool.doPost(url, registerAppDTO);
       LOGGER.info("register to ship-admin success!");
   }


   private RegisterAppDTO buildRegisterAppDTO(Instance instance) {
       RegisterAppDTO registerAppDTO = new RegisterAppDTO();
       registerAppDTO.setAppName(properties.getAppName());
       registerAppDTO.setContextPath(properties.getContextPath());
       registerAppDTO.setIp(instance.getIp());
       registerAppDTO.setPort(instance.getPort());
       registerAppDTO.setVersion(properties.getVersion());
       return registerAppDTO;
   }
}

3.2 ship-server

ship-sever椤圭洰涓昏鍖呮嫭浜嗕袱涓儴鍒嗗唴瀹癸紝 1.璇锋眰鍔ㄦ€佽矾鐢辩殑涓绘祦绋?2.鏈湴缂撳瓨鏁版嵁鍜宻hip-admin鍙妌acos鍚屾锛岃繖閮ㄥ垎鍦ㄥ悗闈?.3鍐嶈銆?/p>

ship-server瀹炵幇鍔ㄦ€佽矾鐢辩殑鍘熺悊鏄埄鐢╓ebFilter鎷︽埅璇锋眰锛岀劧鍚庡皢璇锋眰鏁欑粰plugin chain鍘婚摼寮忓鐞嗐€?/p>

PluginFilter鏍规嵁URL瑙f瀽鍑篴ppName锛岀劧鍚庡皢鍚敤鐨刾lugin缁勮鎴恜lugin chain銆?/p>

public class PluginFilter implements WebFilter {

   private ServerConfigProperties properties;

   public PluginFilter(ServerConfigProperties properties) {
       this.properties = properties;
   }

   @Override
   public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
       String appName = parseAppName(exchange);
       if (CollectionUtils.isEmpty(ServiceCache.getAllInstances(appName))) {
           throw new ShipException(ShipExceptionEnum.SERVICE_NOT_FIND);
       }
       PluginChain pluginChain = new PluginChain(properties, appName);
       pluginChain.addPlugin(new DynamicRoutePlugin(properties));
       pluginChain.addPlugin(new AuthPlugin(properties));
       return pluginChain.execute(exchange, pluginChain);
   }

   private String parseAppName(ServerWebExchange exchange) {
       RequestPath path = exchange.getRequest().getPath();
       String appName = path.value().split("/")[1];
       return appName;
   }
}```

PluginChain缁ф壙浜咥bstractShipPlugin骞舵寔鏈夋墍鏈夎鎵ц鐨勬彃浠躲€?br>
```java
@Author: Ship
@Description:
@Date: Created in 2020/12/25
*/
public class PluginChain extends AbstractShipPlugin {
   /**
* the pos point to current plugin
*/

   private int pos;
   /**
* the plugins of chain
*/

   private List<ShipPlugin> plugins;

   private final String appName;

   public PluginChain(ServerConfigProperties properties, String appName) {
       super(properties);
       this.appName = appName;
   }

   /**
* add enabled plugin to chain
*
@param shipPlugin
*/

   public void addPlugin(ShipPlugin shipPlugin) {
       if (plugins == null) {
           plugins = new ArrayList<>();
       }
       if (!PluginCache.isEnabled(appName, shipPlugin.name())) {
           return;
       }
       plugins.add(shipPlugin);
       // order by the plugin's order
       plugins.sort(Comparator.comparing(ShipPlugin::order));
   }

   @Override
   public Integer order() {
       return null;
   }

   @Override
   public String name() {
       return null;
   }

   @Override
   public Mono<Void> execute(ServerWebExchange exchange, PluginChain pluginChain) {
       if (pos == plugins.size()) {
           return exchange.getResponse().setComplete();
       }
       return pluginChain.plugins.get(pos++).execute(exchange, pluginChain);
   }

   public String getAppName() {
       return appName;
   }

}

AbstractShipPlugin瀹炵幇浜哠hipPlugin鎺ュ彛锛屽苟鎸佹湁ServerConfigProperties閰嶇疆瀵硅薄銆?/p>

public abstract class AbstractShipPlugin implements ShipPlugin {

   protected ServerConfigProperties properties;

   public AbstractShipPlugin(ServerConfigProperties properties) {
       this.properties = properties;
   }
}```

ShipPlugin鎺ュ彛瀹氫箟浜嗘墍鏈夋彃浠跺繀椤诲疄鐜扮殑涓変釜鏂规硶order(),name()鍜宔xecute()銆?br>
```java
public interface ShipPlugin {
   /**
* lower values have higher priority
*
@return
*/

   Integer order();

   /**
* return current plugin name
*
@return
*/

   String name();

   Mono<Void> execute(ServerWebExchange exchange,PluginChain pluginChain);

}```

DynamicRoutePlugin缁ф壙浜嗘娊璞$被AbstractShipPlugin锛屽寘鍚簡鍔ㄦ€佽矾鐢辩殑涓昏涓氬姟閫昏緫銆?br>
```java
@Author: Ship
@Description:
@Date: Created in 2020/12/25
*/
public class DynamicRoutePlugin extends AbstractShipPlugin {

   private final static Logger LOGGER = LoggerFactory.getLogger(DynamicRoutePlugin.class);

   private static WebClient webClient;

   private static final Gson gson = new GsonBuilder().create();

   static {
       HttpClient httpClient = HttpClient.create()
               .tcpConfiguration(client ->
                       client.doOnConnected(conn ->
                               conn.addHandlerLast(new ReadTimeoutHandler(3))
                                       .addHandlerLast(new WriteTimeoutHandler(3)))
                               .option(ChannelOption.TCP_NODELAY, true)
               );
       webClient = WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient))
               .build();
   }

   public DynamicRoutePlugin(ServerConfigProperties properties) {
       super(properties);
   }

   @Override
   public Integer order() {
       return ShipPluginEnum.DYNAMIC_ROUTE.getOrder();
   }

   @Override
   public String name() {
       return ShipPluginEnum.DYNAMIC_ROUTE.getName();
   }

   @Override
   public Mono<Void> execute(ServerWebExchange exchange, PluginChain pluginChain) {
       String appName = pluginChain.getAppName();
       ServiceInstance serviceInstance = chooseInstance(appName, exchange.getRequest());
//        LOGGER.info("selected instance is [{}]", gson.toJson(serviceInstance));
       // request service
       String url = buildUrl(exchange, serviceInstance);
       return forward(exchange, url);
   }

   /**
* forward request to backend service
*
@param exchange
@param url
@return
*/

   private Mono<Void> forward(ServerWebExchange exchange, String url) {
       ServerHttpRequest request = exchange.getRequest();
       ServerHttpResponse response = exchange.getResponse();
       HttpMethod method = request.getMethod();

       WebClient.RequestBodySpec requestBodySpec = webClient.method(method).uri(url).headers((headers) -> {
           headers.addAll(request.getHeaders());
       });

       WebClient.RequestHeadersSpec<?> reqHeadersSpec;
       if (requireHttpBody(method)) {
           reqHeadersSpec = requestBodySpec.body(BodyInserters.fromDataBuffers(request.getBody()));
       } else {
           reqHeadersSpec = requestBodySpec;
       }
       // nio->callback->nio
       return reqHeadersSpec.exchange().timeout(Duration.ofMillis(properties.getTimeOutMillis()))
               .onErrorResume(ex -> {
                   return Mono.defer(() -> {
                       String errorResultJson = "";
                       if (ex instanceof TimeoutException) {
                           errorResultJson = "{"code":5001,"message":"network timeout"}";
                       } else {
                           errorResultJson = "{"code":5000,"message":"system error"}";
                       }
                       return ShipResponseUtil.doResponse(exchange, errorResultJson);
                   }).then(Mono.empty());
               }).flatMap(backendResponse -> {
                   response.setStatusCode(backendResponse.statusCode());
                   response.getHeaders().putAll(backendResponse.headers().asHttpHeaders());
                   return response.writeWith(backendResponse.bodyToFlux(DataBuffer.class));
               });
   }

   /**
* weather the http method need http body
*
@param method
@return
*/

   private boolean requireHttpBody(HttpMethod method) {
       if (method.equals(HttpMethod.POST)| method.equals(HttpMethod.PUT)| method.equals(HttpMethod.PATCH)) {
           return true;
       }
       return false;
   }

   private String buildUrl(ServerWebExchange exchange, ServiceInstance serviceInstance) {
       ServerHttpRequest request = exchange.getRequest();
       String query = request.getURI().getQuery();
       String path = request.getPath().value().replaceFirst("/" + serviceInstance.getAppName(), "");
       String url = "http://" + serviceInstance.getIp() + ":" + serviceInstance.getPort() + path;
       if (!StringUtils.isEmpty(query)) {
           url = url + "?" + query;
       }
       return url;
   }


   /**
* choose an ServiceInstance according to route rule config and load balancing algorithm
*
@param appName
@param request
@return
*/

   private ServiceInstance chooseInstance(String appName, ServerHttpRequest request) {
       List<ServiceInstance> serviceInstances = ServiceCache.getAllInstances(appName);
       if (CollectionUtils.isEmpty(serviceInstances)) {
           LOGGER.error("service instance of {} not find", appName);
           throw new ShipException(ShipExceptionEnum.SERVICE_NOT_FIND);
       }
       String version = matchAppVersion(appName, request);
       if (StringUtils.isEmpty(version)) {
           throw new ShipException("match app version error");
       }
       // filter serviceInstances by version
       List<ServiceInstance> instances = serviceInstances.stream().filter(i -> i.getVersion().equals(version)).collect(Collectors.toList());
       //Select an instance based on the load balancing algorithm
       LoadBalance loadBalance = LoadBalanceFactory.getInstance(properties.getLoadBalance(), appName, version);
       ServiceInstance serviceInstance = loadBalance.chooseOne(instances);
       return serviceInstance;
   }


   private String matchAppVersion(String appName, ServerHttpRequest request) {
       List<AppRuleDTO> rules = RouteRuleCache.getRules(appName);
       rules.sort(Comparator.comparing(AppRuleDTO::getPriority).reversed());
       for (AppRuleDTO rule : rules) {
           if (match(rule, request)) {
               return rule.getVersion();
           }
       }
       return null;
   }


   private boolean match(AppRuleDTO rule, ServerHttpRequest request) {
       String matchObject = rule.getMatchObject();
       String matchKey = rule.getMatchKey();
       String matchRule = rule.getMatchRule();
       Byte matchMethod = rule.getMatchMethod();
       if (MatchObjectEnum.DEFAULT.getCode().equals(matchObject)) {
           return true;
       } else if (MatchObjectEnum.QUERY.getCode().equals(matchObject)) {
           String param = request.getQueryParams().getFirst(matchKey);
           if (!StringUtils.isEmpty(param)) {
               return StringTools.match(param, matchMethod, matchRule);
           }
       } else if (MatchObjectEnum.HEADER.getCode().equals(matchObject)) {
           HttpHeaders headers = request.getHeaders();
           String headerValue = headers.getFirst(matchKey);
           if (!StringUtils.isEmpty(headerValue)) {
               return StringTools.match(headerValue, matchMethod, matchRule);
           }
       }
       return false;
   }

}

3.3 鏁版嵁鍚屾

app鏁版嵁鍚屾

涓€鑸湪绾跨殑瀹炰緥鏉冮噸鍜屾彃浠跺垪琛ㄩ兘鏄湪绠$悊鐣岄潰閰嶇疆锛岀劧鍚庡姩鎬佺敓鏁堢殑锛屾墍浠ラ渶瑕乻hip-admin瀹氭椂鏇存柊瀹炰緥鐨勬潈閲嶅拰鎻掍欢淇℃伅鍒版敞鍐屼腑蹇冦€?/p>

瀵瑰簲浠g爜ship-admin鐨凬acosSyncListener

@Author: Ship
@Description:
@Date: Created in 2020/12/30
*/
@Configuration
public class NacosSyncListener implements ApplicationListener<ContextRefreshedEvent{

   private static final Logger LOGGER = LoggerFactory.getLogger(NacosSyncListener.class);

   private static ScheduledThreadPoolExecutor scheduledPool = new ScheduledThreadPoolExecutor(1,
           new ShipThreadFactory("nacos-sync"true).create());

   @NacosInjected
   private NamingService namingService;

   @Value("${nacos.discovery.server-addr}")
   private String baseUrl;

   @Resource
   private AppService appService;

   @Override
   public void onApplicationEvent(ContextRefreshedEvent event) {
       if (event.getApplicationContext().getParent() != null) {
           return;
       }
       String url = "http://" + baseUrl + NacosConstants.INSTANCE_UPDATE_PATH;
       scheduledPool.scheduleWithFixedDelay(new NacosSyncTask(namingService, url, appService), 030L, TimeUnit.SECONDS);
   }

   class NacosSyncTask implements Runnable {

       private NamingService namingService;

       private String url;

       private AppService appService;

       private Gson gson = new GsonBuilder().create();

       public NacosSyncTask(NamingService namingService, String url, AppService appService) {
           this.namingService = namingService;
           this.url = url;
           this.appService = appService;
       }

       /**
* Regular update weight,enabled plugins to nacos instance
*/

       @Override
       public void run() {
           try {
               // get all app names
               ListView<String> services = namingService.getServicesOfServer(1, Integer.MAX_VALUE, NacosConstants.APP_GROUP_NAME);
               if (CollectionUtils.isEmpty(services.getData())) {
                   return;
               }
               List<String> appNames = services.getData();
               List<AppInfoDTO> appInfos = appService.getAppInfos(appNames);
               for (AppInfoDTO appInfo : appInfos) {
                   if (CollectionUtils.isEmpty(appInfo.getInstances())) {
                       continue;
                   }
                   for (ServiceInstance instance : appInfo.getInstances()) {
                       Map<String, Object> queryMap = buildQueryMap(appInfo, instance);
                       String resp = OkhttpTool.doPut(url, queryMap, "");
                       LOGGER.debug("response :{}", resp);
                   }
               }

           } catch (Exception e) {
               LOGGER.error("nacos sync task error", e);
           }
       }

       private Map<String, Object> buildQueryMap(AppInfoDTO appInfo, ServiceInstance instance) {
           Map<String, Object> map = new HashMap<>();
           map.put("serviceName", appInfo.getAppName());
           map.put("groupName", NacosConstants.APP_GROUP_NAME);
           map.put("ip", instance.getIp());
           map.put("port", instance.getPort());
           map.put("weight", instance.getWeight().doubleValue());
           NacosMetadata metadata = new NacosMetadata();
           metadata.setAppName(appInfo.getAppName());
           metadata.setVersion(instance.getVersion());
           metadata.setPlugins(String.join(",", appInfo.getEnabledPlugins()));
           map.put("metadata", StringTools.urlEncode(gson.toJson(metadata)));
           map.put("ephemeral"true);
           return map;
       }
   }
}

ship-server鍐嶅畾鏃朵粠Nacos鎷夊彇app鏁版嵁鏇存柊鍒版湰鍦癕ap缂撳瓨銆?/p>

@Author: Ship
@Description: sync data to local cache
@Date: Created in 2020/12/25
*/
@Configuration
public class DataSyncTaskListener implements ApplicationListener<ContextRefreshedEvent{

   private static ScheduledThreadPoolExecutor scheduledPool = new ScheduledThreadPoolExecutor(1,
           new ShipThreadFactory("service-sync"true).create());

   @NacosInjected
   private NamingService namingService;

   @Autowired
   private ServerConfigProperties properties;

   @Override
   public void onApplicationEvent(ContextRefreshedEvent event) {
       if (event.getApplicationContext().getParent() != null) {
           return;
       }
       scheduledPool.scheduleWithFixedDelay(new DataSyncTask(namingService)
               , 0L, properties.getCacheRefreshInterval(), TimeUnit.SECONDS);
       WebsocketSyncCacheServer websocketSyncCacheServer = new WebsocketSyncCacheServer(properties.getWebSocketPort());
       websocketSyncCacheServer.start();
   }


   class DataSyncTask implements Runnable {

       private NamingService namingService;

       public DataSyncTask(NamingService namingService) {
           this.namingService = namingService;
       }

       @Override
       public void run() {
           try {
               // get all app names
               ListView<String> services = namingService.getServicesOfServer(1, Integer.MAX_VALUE, NacosConstants.APP_GROUP_NAME);
               if (CollectionUtils.isEmpty(services.getData())) {
                   return;
               }
               List<String> appNames = services.getData();
               // get all instances
               for (String appName : appNames) {
                   List<Instance> instanceList = namingService.getAllInstances(appName, NacosConstants.APP_GROUP_NAME);
                   if (CollectionUtils.isEmpty(instanceList)) {
                       continue;
                   }
                   ServiceCache.add(appName, buildServiceInstances(instanceList));
                   List<String> pluginNames = getEnabledPlugins(instanceList);
                   PluginCache.add(appName, pluginNames);
               }
               ServiceCache.removeExpired(appNames);
               PluginCache.removeExpired(appNames);

           } catch (NacosException e) {
               e.printStackTrace();
           }
       }

       private List<String> getEnabledPlugins(List<Instance> instanceList) {
           Instance instance = instanceList.get(0);
           Map<String, String> metadata = instance.getMetadata();
           // plugins: DynamicRoute,Auth
           String plugins = metadata.getOrDefault("plugins", ShipPluginEnum.DYNAMIC_ROUTE.getName());
           return Arrays.stream(plugins.split(",")).collect(Collectors.toList());
       }

       private List<ServiceInstance> buildServiceInstances(List<Instance> instanceList) {
           List<ServiceInstance> list = new LinkedList<>();
           instanceList.forEach(instance -> {
               Map<String, String> metadata = instance.getMetadata();
               ServiceInstance serviceInstance = new ServiceInstance();
               serviceInstance.setAppName(metadata.get("appName"));
               serviceInstance.setIp(instance.getIp());
               serviceInstance.setPort(instance.getPort());
               serviceInstance.setVersion(metadata.get("version"));
               serviceInstance.setWeight((int) instance.getWeight());
               list.add(serviceInstance);
           });
           return list;
       }
   }
}

璺敱瑙勫垯鏁版嵁鍚屾

鍚屾椂锛屽鏋滅敤鎴峰湪绠$悊鍚庡彴鏇存柊浜嗚矾鐢辫鍒欙紝ship-admin闇€瑕佹帹閫佽鍒欐暟鎹埌ship-server锛岃繖閲屽弬鑰冧簡soul缃戝叧鐨勫仛娉曞埄鐢╳ebsocket鍦ㄧ涓€娆″缓绔嬭繛鎺ュ悗杩涜鍏ㄩ噺鍚屾锛屾鍚庤矾鐢辫鍒欏彂鐢熷彉鏇村氨鍙綔澧為噺鍚屾銆?/p>

鏈嶅姟绔疻ebsocketSyncCacheServer锛?/p>

@Author: Ship
@Description:
@Date: Created in 2020/12/28
*/
public class WebsocketSyncCacheServer extends WebSocketServer {

   private final static Logger LOGGER = LoggerFactory.getLogger(WebsocketSyncCacheServer.class);

   private Gson gson = new GsonBuilder().create();

   private MessageHandler messageHandler;

   public WebsocketSyncCacheServer(Integer port) {
       super(new InetSocketAddress(port));
       this.messageHandler = new MessageHandler();
   }


   @Override
   public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
       LOGGER.info("server is open");
   }

   @Override
   public void onClose(WebSocket webSocket, int i, String s, boolean b) {
       LOGGER.info("websocket server close...");
   }

   @Override
   public void onMessage(WebSocket webSocket, String message) {
       LOGGER.info("websocket server receive message: [{}]", message);
       this.messageHandler.handler(message);
   }

   @Override
   public void onError(WebSocket webSocket, Exception e) {

   }

   @Override
   public void onStart() {
       LOGGER.info("websocket server start...");
   }


   class MessageHandler {

       public void handler(String message) {
           RouteRuleOperationDTO operationDTO = gson.fromJson(message, RouteRuleOperationDTO.class);
           if (CollectionUtils.isEmpty(operationDTO.getRuleList())) {
               return;
           }
           Map<String, List<AppRuleDTO>> map = operationDTO.getRuleList()
                   .stream().collect(Collectors.groupingBy(AppRuleDTO::getAppName));
           if (OperationTypeEnum.INSERT.getCode().equals(operationDTO.getOperationType())
                  | OperationTypeEnum.UPDATE.getCode().equals(operationDTO.getOperationType())) {
               RouteRuleCache.add(map);
           } else if (OperationTypeEnum.DELETE.getCode().equals(operationDTO.getOperationType())) {
               RouteRuleCache.remove(map);
           }
       }
   }
}

瀹㈡埛绔疻ebsocketSyncCacheClient锛?/p>

@Author: Ship
@Description:
@Date: Created in 2020/12/28
*/
@Component
public class WebsocketSyncCacheClient {

   private final static Logger LOGGER = LoggerFactory.getLogger(WebsocketSyncCacheClient.class);

   private WebSocketClient client;

   private RuleService ruleService;

   private Gson gson = new GsonBuilder().create();

   public WebsocketSyncCacheClient(@Value("${ship.server-web-socket-url}") String serverWebSocketUrl,
RuleService ruleService) 
{
       if (StringUtils.isEmpty(serverWebSocketUrl)) {
           throw new ShipException(ShipExceptionEnum.CONFIG_ERROR);
       }
       this.ruleService = ruleService;
       ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1,
               new ShipThreadFactory("websocket-connect"true).create());
       try {
           client = new WebSocketClient(new URI(serverWebSocketUrl)) {
               @Override
               public void onOpen(ServerHandshake serverHandshake) {
                   LOGGER.info("client is open");
                   List<AppRuleDTO> list = ruleService.getEnabledRule();
                   String msg = gson.toJson(new RouteRuleOperationDTO(OperationTypeEnum.INSERT, list));
                   send(msg);
               }

               @Override
               public void onMessage(String s) {
               }

               @Override
               public void onClose(int i, String s, boolean b) {
               }

               @Override
               public void onError(Exception e) {
                   LOGGER.error("websocket client error", e);
               }
           };

           client.connectBlocking();
           //浣跨敤璋冨害绾跨▼姹犺繘琛屾柇绾块噸杩烇紝30绉掕繘琛屼竴娆?/span>
           executor.scheduleAtFixedRate(() -> {
               if (client != null && client.isClosed()) {
                   try {
                       client.reconnectBlocking();
                   } catch (InterruptedException e) {
                       LOGGER.error("reconnect server fail", e);
                   }
               }
           }, 1030, TimeUnit.SECONDS);

       } catch (Exception e) {
           LOGGER.error("websocket sync cache exception", e);
           throw new ShipException(e.getMessage());
       }
   }

   public <T> void send(T t) {
       while (!client.getReadyState().equals(ReadyState.OPEN)) {
           LOGGER.debug("connecting ...please wait");
       }
       client.send(gson.toJson(t));
   }
}

鍥涖€佹祴璇?span class="mq-862">

4.1鍔ㄦ€佽矾鐢辨祴璇?span class="mq-865">

1銆佹湰鍦板惎鍔╪acos ,sh startup.sh -m standalone

2銆佸惎鍔╯hip-admin

3銆佹湰鍦板惎鍔ㄤ袱涓猻hip-example瀹炰緥銆?/p>

瀹炰緥1閰嶇疆锛?/p>

ship:
 http:
   app-name: order
   version: gray_1.0
   context-path: /order
   port: 8081
   admin-url: 127.0.0.1:9001

 server:
 port: 8081

 nacos:
 discovery:
   server-addr: 127.0.0.1:8848

瀹炰緥2閰嶇疆锛?/p>

ship:
 http:
   app-name: order
   version: prod_1.0
   context-path: /order
   port: 8082
   admin-url: 127.0.0.1:9001

 server:
 port: 8082

 nacos:
 discovery:
   server-addr: 127.0.0.1:8848

4銆佸湪鏁版嵁搴撴坊鍔犺矾鐢辫鍒欓厤缃紝璇ヨ鍒欒〃绀哄綋http header 涓殑name=ship鏃惰姹傝矾鐢卞埌gray_1.0鐗堟湰鐨勮妭鐐广€?/p>

寰掓墜鎾镐簡涓€涓狝PI缃戝叧锛岀悊瑙f洿閫忓交浜嗭紝浠g爜宸蹭笂浼爂ithub锛岃嚜鍙杶

5銆佸惎鍔╯hip-server,鐪嬪埌浠ヤ笅鏃ュ織鏃跺垯鍙互杩涜娴嬭瘯浜嗐€?/p>

2021-01-02 19:57:09.159  INFO 30413 --- [SocketWorker-29] cn.sp.sync.WebsocketSyncCacheServer      : websocket server receive message:
 [{"operationType":"INSERT","ruleList":[{"id":1,"appId":5,"appName":"order","version":"gray_1.0","matchObject":"HEADER","matchKey":"name","matchMethod":1,"matchRule":"ship","priority":50}]}]

6銆佺敤Postman璇锋眰http://localhost:9000/order/user/add,POST鏂瑰紡锛宧eader璁剧疆name=ship锛屽彲浠ョ湅鍒板彧鏈夊疄渚?鏈夋棩蹇楁樉绀恒€?/p>

==========add user,version:gray_1.0

4.2鎬ц兘鍘嬫祴

鍘嬫祴鐜锛?/p>

  • MacBook Pro 13鑻卞
  • 澶勭悊鍣?2.3 GHz 鍥涙牳Intel Core i7
  • 鍐呭瓨 16 GB 3733 MHz LPDDR4X
  • 鍚庣鑺傜偣涓暟涓€涓?
  • 鍘嬫祴宸ュ叿锛歸rk
  • 鍘嬫祴缁撴灉锛?0涓嚎绋嬶紝500涓繛鎺ユ暟锛屽悶鍚愰噺澶ф姣忕9400涓姹傘€?
寰掓墜鎾镐簡涓€涓狝PI缃戝叧锛岀悊瑙f洿閫忓交浜嗭紝浠g爜宸蹭笂浼爂ithub锛岃嚜鍙杶
鍘嬫祴缁撴灉

浜斻€佹€荤粨

鍗冮噷涔嬭濮嬩簬瓒充笅锛屽紑濮嬩互涓哄啓涓€涓綉鍏充細寰堥毦锛屼絾褰撲綘瀹為檯寮€濮嬭鍔ㄦ椂灏变細鍙戠幇鍏跺疄娌¢偅涔堥毦锛屾墍浠ヨ繄鍑虹涓€姝ュ緢閲嶈銆傝繃绋嬩腑涔熼亣鍒颁簡寰堝闂锛岃繕鍦╣ithub涓婄粰soul鍜宯acos杩欎袱涓紑婧愰」鐩彁浜嗕袱涓猧ssue锛屽悗鏉ュ彂鐜版槸鑷繁鐨勯棶棰橈紝灏村艾馃槄銆?/p>

鏈枃浠g爜宸插叏閮ㄤ笂浼犲埌 https://github.com/2YSP/ship-gate


鏉ユ簮 | https://www.cnblogs.com/2YSP/p/14223892.html



  
    
    
  
鏈€杩戦潰璇旴AT锛屾暣鐞嗕竴浠介潰璇曡祫鏂?/span> 銆?span class="mq-946">Java闈㈣瘯BAT閫氬叧鎵嬪唽銆?/span> 锛岃鐩栦簡Java鏍稿績鎶€鏈€丣VM銆丣ava骞跺彂銆丼SM銆佸井鏈嶅姟銆佹暟鎹簱銆佹暟鎹粨鏋勭瓑绛夈€?/span>
鏄庡ぉ瑙?锝★渐蠅锝ワ健)锞夆櫋

以上是关于寰掓墜鎾镐簡涓€涓狝PI缃戝叧锛岀悊瑙f洿閫忓交浜嗭紝浠g爜宸蹭笂浼爂ithub锛岃嚜鍙杶的主要内容,如果未能解决你的问题,请参考以下文章

鎬庝箞鐢ˋPI缃戝叧鏋勫缓寰湇鍔★綔鏋舵瀯

鏈€鎶€鏈?| API缃戝叧鏈嶅姟鐔旀柇闄嶇骇瀹炶返

API缃戝叧Kong

API璁捐寰楀ソ锛屼笅鐝笅寰楁棭