Netflix ZuulとDisconfを組み合わせた動的ルーティングリフレッシュ
14395 ワード
会社はDisconfを使用して分散構成を管理し、Disconfはzookeeperを使用して構成ストレージを提供し、通知インタフェースを予約して構成変更を受信する.
1.Disconf更新通知エントリの構成
Disconfには 1. を使用して読み込む. 2. をリフレッシュする.
2.カスタムRouteLocatorによるルーティングテーブルのリフレッシュ機能
Zuulはデフォルトで 1.第1のステップで発行するrefreshEventは、最終的には を取得する. 2.弊社ではyamlプロファイルを使用しておりますので、解析プロファイルデータ比較ルーティングルールが変更されたかどうかを確認する必要があります.ここでは を使用しています. 3.当社は を完了する必要がある.
3.デフォルトのSimpleRouteLocatorを置き換える
上記の2つのステップが完了すると、Zuulの動的ルーティング機能は基本的にサポートされ、システムのデフォルトの
1.Disconf更新通知エントリの構成
Disconfには
IDisconfUpdatePipeline
インタフェースが用意されており、このインタフェースはDisconfコンソールに保存されているプロファイルが変更されたときにリアルタイムでメッセージを得ることができる.コードは以下の通りである.@Slf4j
@Service
@Scope("singleton")
public class CustomDisconfReloadUpdate implements IDisconfUpdatePipeline {
/**
* Store changed configuration file's path
*/
private static final ThreadLocal newConfigFilePath = new ThreadLocal<>();
@Autowired
ApplicationEventPublisher publisher;
@Autowired
RouteLocator routeLocator;
/**
* zuul refresh
*/
public void refreshRoute() {
RoutesRefreshedEvent routesRefreshedEvent = new RoutesRefreshedEvent(routeLocator);
publisher.publishEvent(routesRefreshedEvent);
}
@Override
public void reloadDisconfFile(String key, String filePath) throws Exception {
log.info("------Got reload event from disconf------");
log.info("Change key: {}, filePath: {}", key, filePath);
newConfigFilePath.set(filePath);
refreshRoute();
}
@Override
public void reloadDisconfItem(String key, Object content) throws Exception {
log.info("------Got reload event from disconf with item------");
log.info("Change key: {}, content: {}", key, content);
}
/**
* Checkout configFilePath and remove the ThreadLocal value content
* @return
*/
public static String getConfigFilePath() {
String path = newConfigFilePath.get();
newConfigFilePath.remove();
return path;
}
}
ThreadLocal
を使用して、変更するプロファイルをローカルのパスに保存し、スレッドの後続の実行時にRoutesRefreshedEvent
イベント機構を使用してZuulのルーティングテーブル2.カスタムRouteLocatorによるルーティングテーブルのリフレッシュ機能
Zuulはデフォルトで
SimpleRouteLocator
をルーティング発見エントリとして使用しているが、動的リフレッシュはサポートされていない.ZuulはRefreshableRouteLocator
インタフェースを予約して動的ルーティングテーブルのリフレッシュをサポートし、以下はカスタムクラスがルーティングリフレッシュ機能を実現することである.@Slf4j
public class CustomZuulRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
private ZuulProperties properties;
private SpringClientFactory springClientFactory;
public CustomZuulRouteLocator(String servletPath, ZuulProperties properties, SpringClientFactory springClientFactory) {
super(servletPath, properties);
this.properties = properties;
this.springClientFactory = springClientFactory;
}
@Override
public void refresh() {
doRefresh();
}
@Override
protected Map locateRoutes() {
LinkedHashMap routesMap = new LinkedHashMap<>();
// This will load the old routes which exist in cache
routesMap.putAll(super.locateRoutes());
try {
// If server can load routes from file which means the file changed, using the new config routes
Map newRouteMap = loadRoutesFromDisconf();
if(newRouteMap.size() > 0) {
log.info("New config services list: {}", Arrays.toString(newRouteMap.keySet().toArray()));
routesMap.clear();
routesMap.putAll(newRouteMap);
}
} catch (Exception e) {
// For every exception, do not break the gateway working
log.error(e.getMessage(), e);
}
LinkedHashMap values = new LinkedHashMap<>();
for (Map.Entry entry : routesMap.entrySet()) {
String path = entry.getKey();
// Prepend with slash if not already present.
if (!path.startsWith("/")) {
path = "/" + path;
}
if (StringUtils.hasText(this.properties.getPrefix())) {
path = this.properties.getPrefix() + path;
if (!path.startsWith("/")) {
path = "/" + path;
}
}
values.put(path, entry.getValue());
}
return values;
}
/**
* Read the new config file and reload new route and service config
* @return
*/
private Map loadRoutesFromDisconf() {
log.info("----load configuration----");
Map latestRoutes = new LinkedHashMap<>(16);
String configFilePath = CustomDisconfReloadUpdate.getConfigFilePath();
if(configFilePath != null) {
String newConfigContent = readFileContent(configFilePath);
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
try {
Map configResult = objectMapper.readValue(newConfigContent, Map.class);
// Store all serviceId with ribbon configuration
List allServiceIds = new ArrayList<>();
// This Node used to deal with Zuul route configuration
if(configResult.containsKey("zuul")) {
Map zuulConfig = (Map) configResult.get("zuul");
if(zuulConfig.containsKey("routes")) {
Map routes = (Map) zuulConfig.get("routes");
if(routes != null) {
for(Map.Entry tempRoute : routes.entrySet()) {
String id = tempRoute.getKey();
Map routeDetail = (Map) tempRoute.getValue();
ZuulProperties.ZuulRoute zuulRoute = generateZuulRoute(id, routeDetail);
// Got list with config serviceId
if(zuulRoute.getServiceId() != null) {
allServiceIds.add(zuulRoute.getServiceId());
}
latestRoutes.put(zuulRoute.getPath(), zuulRoute);
}
}
}
}
// deal with all serviceIds and read properties from yaml configuraiton
if(allServiceIds.size() > 0) {
allServiceIds.forEach(temp -> {
// Exist this serviceId
if(configResult.containsKey(temp)) {
Map serviceRibbonConfig = (Map) configResult.get(temp);
if(serviceRibbonConfig.containsKey("ribbon")) {
Map ribbonConfig = (Map) serviceRibbonConfig.get("ribbon");
if(ribbonConfig.containsKey("listOfServers")) {
String listOfServers = (String) ribbonConfig.get("listOfServers");
String ruleClass = (String) ribbonConfig.get("NFLoadBalancerRuleClassName");
String[] serverList = listOfServers.split(",");
dealLoadBalanceConfig(temp, ruleClass, Arrays.asList(serverList));
}
}
}
});
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
return latestRoutes;
}
/**
* Change loadbalancer's serverList configuration
*/
private void dealLoadBalanceConfig(String serviceId, String newRuleClassName, List servers) {
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) springClientFactory.getLoadBalancer(serviceId);
if(loadBalancer != null) {
// 1.Reset listOfServer content if listOfServers changed
List newServerList = new ArrayList<>();
servers.forEach(temp -> {
// remove the empty characters
temp = temp.trim();
newServerList.add(temp);
});
List oldServerList = loadBalancer.getServerListImpl().getUpdatedListOfServers();
// Judge the listOfServers changed or not
boolean serversChanged = false;
if(oldServerList != null) {
if(oldServerList.size() != newServerList.size()) {
serversChanged = true;
} else {
for(Server temp : oldServerList) {
if(!newServerList.contains(temp.getId())) {
serversChanged = true;
break;
}
}
}
} else {
serversChanged = true;
}
// listOfServers has changed
if(serversChanged) {
log.info("ServiceId: {} has changed listOfServers, new: {}", serviceId, Arrays.toString(servers.toArray()));
loadBalancer.setServerListImpl(new ServerList() {
@Override
public List getInitialListOfServers() {
return null;
}
@Override
public List getUpdatedListOfServers() {
List newServerConfigList = new ArrayList<>();
newServerList.forEach(temp -> {
Server server = new Server(temp);
newServerConfigList.add(server);
});
// Using the new config listOfServers
return newServerConfigList;
}
});
}
// Reset loadBalancer rule
if(loadBalancer.getRule() != null) {
String existRuleClassName = loadBalancer.getRule().getClass().getName();
if(!newRuleClassName.equals(existRuleClassName)) {
log.info("ServiceId: {}, Old rule class: {}, New rule class: {}", serviceId, existRuleClassName, newRuleClassName);
initializeLoadBalancerWithNewRule(newRuleClassName, loadBalancer);
}
} else {
initializeLoadBalancerWithNewRule(newRuleClassName, loadBalancer);
log.info("ServiceId: {}, Old rule class: Null, Need rule class: {}", serviceId, newRuleClassName);
}
}
}
/**
* Change loadBalancer's rule
* @param ruleClassName
* @param loadBalancer
*/
private void initializeLoadBalancerWithNewRule(String ruleClassName, DynamicServerListLoadBalancer loadBalancer) {
try {
// Create specify class instance
Class clazz = Class.forName(ruleClassName);
Constructor constructor = clazz.getConstructor();
IRule rule = (IRule) constructor.newInstance();
// Bind loadBalancer with this Rule
rule.setLoadBalancer(loadBalancer);
loadBalancer.setRule(rule);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* Generate zuul route object from configuration
* @param id
* @param routeDetail
* @return
*/
private ZuulProperties.ZuulRoute generateZuulRoute(String id, Map routeDetail) {
ZuulProperties.ZuulRoute route = new ZuulProperties.ZuulRoute();
route.setId(id);
if(routeDetail.containsKey("path")) {
route.setPath((String) routeDetail.get("path"));
}
if(routeDetail.containsKey("serviceId")) {
route.setServiceId((String) routeDetail.get("serviceId"));
}
if(routeDetail.containsKey("url")) {
route.setUrl((String) routeDetail.get("url"));
}
if(routeDetail.containsKey("stripPrefix")) {
route.setStripPrefix((Boolean) routeDetail.get("stripPrefix"));
}
return route;
}
/**
* Read config file from local file system
* @param configFile
* @return
*/
private String readFileContent(String configFile) {
try {
FileInputStream inputStream = new FileInputStream(new File(configFile));
String content = IOUtils.toString(inputStream, "UTF-8");
return content;
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return null;
}
}
refresh()
メソッドのdoRefresh()
メソッドを呼び出し、doRefresh()
は親インプリメンテーションでlocateRoutes()
を呼び出して新しいルーティングテーブルjackson-dataformat-yaml
解析Ribbon
を使用してクライアントの負荷等化を行うため、ルーティングテーブルが変化する場合、負荷等化のルールも変化する可能性があり、この部分の動的リフレッシュはDynamicServerListLoadBalancer
を修正することによって3.デフォルトのSimpleRouteLocatorを置き換える
上記の2つのステップが完了すると、Zuulの動的ルーティング機能は基本的にサポートされ、システムのデフォルトの
SimpleRouteLocator
を私たちの実装クラスに置き換える必要があります.Bean注釈を使用してカスタムクラスを有効にするだけでいいです. @Bean
public CustomZuulRouteLocator routeLocator() {
CustomZuulRouteLocator customZuulRouteLocator = new CustomZuulRouteLocator(serverProperties.getServlet().getServletPrefix(), zuulProperties, springClientFactory);
return customZuulRouteLocator;
}