最近開發一個動態路由的網關,通過nginx配置網關支持網關的高可用后,由於網關配置的路由是根據nacos服務名進行動態路由刷新,使用lb負載均衡,代碼如下:
package com.sinux.sitesupport.gw.schedule; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.sinux.sitesupport.gw.config.GateWayCustomConfig; import com.sinux.sitesupport.gw.service.DynamicUpdateRouteService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.filter.FilterDefinition; import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition; import org.springframework.cloud.gateway.route.RouteDefinition; import org.springframework.http.ResponseEntity; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; import javax.annotation.PostConstruct; import java.net.URI; import java.util.*; /** * @ClassName DynamicUpdateRouteSchedule * @Description * @Author dh * @Date 2021/10/25 19:33 * @Version 1.0 */ @Component @Slf4j public class DynamicUpdateRouteSchedule { @Value("${spring.cloud.nacos.discovery.server-addr}") private String nacosHost; @Value("${spring.application.name}") private String springServerName; @Autowired private RestTemplate restTemplate; @Autowired private DynamicUpdateRouteService dynamicUpdateRouteService; @Autowired private GateWayCustomConfig customConfig; @PostConstruct public void init() { log.info("gateway route init"); List<String> serviceNames = getNacosPublicServiceNames(); //對服務名稱組裝對應的路由規則 for (String serviceName : serviceNames) { RouteDefinition routeDefinitionHttp = generateRouteDefinitionHttp(serviceName); dynamicUpdateRouteService.addRoute(routeDefinitionHttp); if (customConfig.getWsNames().contains(serviceName)) { RouteDefinition routeDefinitionWs = generateRouteDefinitionWs(serviceName); dynamicUpdateRouteService.addRoute(routeDefinitionWs); } } } /** * 定時刷新路由 */ @Scheduled(cron = "${dynamic.update.route.cron}") public void updateRoute() { //獲取所有服務列表 List<String> serviceNames = getNacosPublicServiceNames(); //清理內存中的所有路由 dynamicUpdateRouteService.clearRoute(); //對服務組裝對應的路由規則 for (String serviceName : serviceNames) { RouteDefinition routeDefinitionHttp = generateRouteDefinitionHttp(serviceName); dynamicUpdateRouteService.addRoute(routeDefinitionHttp); if (customConfig.getWsNames().contains(serviceName)) { RouteDefinition routeDefinitionWs = generateRouteDefinitionWs(serviceName); dynamicUpdateRouteService.addRoute(routeDefinitionWs); } } dynamicUpdateRouteService.publish(); } /** * 獲取nacos上public命令空間下所有的服務名 */ public List<String> getNacosPublicServiceNames() { List<String> serviceNames = new ArrayList<>(); try { String url = "http://" + nacosHost + "/nacos/v1/ns/catalog/services?" + "pageNo=1&hasIpCount=true&withInstances=false&pageSize=10000&serviceNameParam=&groupNameParam=&namespaceId="; ResponseEntity<String> rtn = restTemplate.getForEntity(url, String.class); String body = rtn.getBody(); if (StringUtils.isNotBlank(body)) { JSONObject jsonObject = JSON.parseObject(body); JSONArray serviceList = jsonObject.getJSONArray("serviceList"); for (Object o : serviceList) { JSONObject serviceItem = (JSONObject) o; String serviceName = serviceItem.getString("name"); if (serviceName.equals(springServerName)) { continue; } serviceNames.add(serviceName); } } } catch (Exception e) { e.printStackTrace(); } return serviceNames; } /** * 根據serviceName組裝RouteDefinition 支持http * 格式見 gateway_config.bak */ public RouteDefinition generateRouteDefinitionHttp(String serviceName) { RouteDefinition routeDefinition = null; try { //http //設置路由唯一id routeDefinition = new RouteDefinition(); routeDefinition.setId(serviceName + "-http"); //設置路由的uri URI uri = UriComponentsBuilder.fromUriString("lb://" + serviceName).build().toUri(); routeDefinition.setUri(uri); //設置多個filter FilterDefinition stripPrefixFilter = new FilterDefinition(); stripPrefixFilter.setName("StripPrefix"); HashMap<String, String> stripPrefixFilterParams = new HashMap<>(); stripPrefixFilterParams.put("parts", "1"); stripPrefixFilter.setArgs(stripPrefixFilterParams); FilterDefinition addRequestHeaderFilter = new FilterDefinition(); addRequestHeaderFilter.setName("AddRequestHeader"); HashMap<String, String> addRequestHeaderFilterParams = new HashMap<>(); addRequestHeaderFilterParams.put("name", "serviceName"); addRequestHeaderFilterParams.put("value", serviceName); addRequestHeaderFilter.setArgs(addRequestHeaderFilterParams); routeDefinition.setFilters(Arrays.asList(stripPrefixFilter, addRequestHeaderFilter)); //設置多個斷言 PredicateDefinition pathPredicate = new PredicateDefinition(); pathPredicate.setName("Path"); HashMap<String, String> pathPredicateParams = new HashMap<>(); pathPredicateParams.put("pattern", "/" + serviceName + "/**"); pathPredicate.setArgs(pathPredicateParams); routeDefinition.setPredicates(Arrays.asList(pathPredicate)); routeDefinition.setOrder(1); } catch (Exception e) { e.printStackTrace(); } return routeDefinition; } }
因此有兩種策略。
1. 在nginx端配置:(參考:https://blog.csdn.net/weixin_37264997/article/details/80341911)
location / { proxy_pass http://127.0.0.1:8080/; // 代理轉發地址 // 啟用支持websocket連接 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }
2. 在網關處增添websocket (ws)的路由
/** * 根據serviceName組裝RouteDefinition 支持ws * 格式見 gateway_config.bak */ public RouteDefinition generateRouteDefinitionWs(String serviceName) { RouteDefinition routeDefinition = null; try { //http //設置路由唯一id routeDefinition = new RouteDefinition(); routeDefinition.setId(serviceName + "-ws"); //設置路由的uri URI uri = UriComponentsBuilder.fromUriString("lb:ws://" + serviceName).build().toUri(); routeDefinition.setUri(uri); //設置多個filter FilterDefinition stripPrefixFilter = new FilterDefinition(); stripPrefixFilter.setName("StripPrefix"); HashMap<String, String> stripPrefixFilterParams = new HashMap<>(); stripPrefixFilterParams.put("parts", "1"); stripPrefixFilter.setArgs(stripPrefixFilterParams); FilterDefinition addRequestHeaderFilter = new FilterDefinition(); addRequestHeaderFilter.setName("AddRequestHeader"); HashMap<String, String> addRequestHeaderFilterParams = new HashMap<>(); addRequestHeaderFilterParams.put("name", "serviceName"); addRequestHeaderFilterParams.put("value", serviceName); addRequestHeaderFilter.setArgs(addRequestHeaderFilterParams); routeDefinition.setFilters(Arrays.asList(stripPrefixFilter, addRequestHeaderFilter)); //設置多個斷言 PredicateDefinition pathPredicate = new PredicateDefinition(); pathPredicate.setName("Path"); HashMap<String, String> pathPredicateParams = new HashMap<>(); pathPredicateParams.put("pattern", "/" + serviceName + "/**"); pathPredicate.setArgs(pathPredicateParams); routeDefinition.setPredicates(Arrays.asList(pathPredicate)); routeDefinition.setOrder(2); } catch (Exception e) { e.printStackTrace(); } return routeDefinition; }
遇到的問題,總結一下