基於springcloud服務灰度釋出(二)
阿新 • • 發佈:2018-12-11
灰度鏈路測試
文章目錄
1測試介面實現
當所有服務接入灰jar後,想要測試一下鏈路效果,並不是很方便;所以
在所有服務接入依賴後,預先設定鏈路測試介面方,以便測試整體鏈路效果是否達到預期
/** * 路由鏈路測試 * * @author xie */ @RestController @RequestMapping("/gray/test") public class GrayRouteTestController { private Logger logger = LoggerFactory.getLogger(GrayRouteTestController.class); @Resource private EurekaDiscoveryClient discoveryClient; @Resource() private ApplicationInfoManager applicationInfoManager; @Resource private RestTemplate restTemplate; /** * @param nodes 需要請求的順序服務資訊 * @return */ @RequestMapping(value = "linksTest", method = RequestMethod.POST) public TestResult nodeTest(@RequestBody List<String> nodes) { InstanceInfo startNodeServer = applicationInfoManager.getInfo(); String currentServiceId = startNodeServer.getAppName().toLowerCase(); if (!nodes.get(0).equals(currentServiceId)) { nodes.add(0,currentServiceId); } //處理鏈路節點資訊 RequestNode requestNode = convert2RequestNode(nodes); List<ResultNode> resultNodes = internalNodeTest(requestNode); ResultNode startNode = new ResultNode(); startNode.setServiceId(startNodeServer.getAppName().toLowerCase()); startNode.setHost(startNodeServer.getHostName()); startNode.setPort(startNodeServer.getPort()); String serverStatus = startNodeServer.getMetadata().get(Constant.INSTANCE_STATUS); startNode.setInstanceStatus(serverStatus); resultNodes.add(startNode); Comparator<ResultNode> tComparator = Collections.reverseOrder(); resultNodes.sort(tComparator); TestResult testResult = new TestResult(); ResultNode resultNode = resultNodes.get(resultNodes.size() - 1); testResult.setSuccess(resultNode.isSuccess()); testResult.setMessage(resultNode.getMessage()); testResult.setNodeList(resultNodes); testResult.setTotal(nodes.size()); testResult.setRoute2Gray(GrayUtils.isGray()); testResult.setFailureCount(resultNode.isSuccess() ? 0 : nodes.size() - (resultNodes.size() - 1)); return testResult; } @RequestMapping(value = "internalNodeTest", method = RequestMethod.POST) public List<ResultNode> internalNodeTest(@RequestBody RequestNode requestNode) { //獲取下一請求服務節點資訊 RequestNode next = requestNode.getNext(); if (next == null) { logger.info("request is end"); List<ResultNode> list = new ArrayList<>(); return list; } GrayUtils.currentSelectServer.set(new Server(next.getServiceId())); try { ResultNode nextNodeServer = new ResultNode(); nextNodeServer.setIndex(next.getIndex()); nextNodeServer.setServiceId(next.getServiceId()); List<ResultNode> resultNodes = new ArrayList<>(); //處理服務請求url String serverUrl = getServerUrl(next.getServiceId()); if (StringUtils.isEmpty(serverUrl)) { nextNodeServer.setMessage("無" + next.getServiceId() + "服務資訊"); nextNodeServer.setSuccess(false); resultNodes.add(nextNodeServer); return resultNodes; } HttpHeaders requestHeaders = new HttpHeaders(); //傳遞灰度標籤 requestHeaders.add(Constant.ROUTE_TO_GRAY, String.valueOf(GrayUtils.isGray())); HttpEntity<RequestNode> requestEntity = new HttpEntity<>(next, requestHeaders); try { ResponseEntity<String> response = restTemplate.postForEntity(serverUrl, requestEntity, String.class); //下一服務節點處理成功,處理返回結果 resultNodes = JSON.parseArray(response.getBody(), ResultNode.class); Server testServer = GrayUtils.getTestServer(); nextNodeServer.setHost(testServer.getHost()); nextNodeServer.setPort(testServer.getPort()); if (testServer instanceof DiscoveryEnabledServer) { DiscoveryEnabledServer enabledServer = (DiscoveryEnabledServer) testServer; InstanceInfo instanceInfo = enabledServer.getInstanceInfo(); String serverStatus = instanceInfo.getMetadata().get(Constant.INSTANCE_STATUS); nextNodeServer.setInstanceStatus(serverStatus); nextNodeServer.setIndex(next.getIndex()); } } catch (Exception ex) { //下一服務節點請求失敗 logger.warn("links test failure:{}", ex.getMessage()); nextNodeServer.setMessage("[route_to_gray:" + GrayUtils.isGray() + "]" + ex.getMessage() + serverUrl); nextNodeServer.setSuccess(false); resultNodes.add(nextNodeServer); } resultNodes.add(nextNodeServer); return resultNodes; } finally { GrayUtils.removeTestServer(); } } private RequestNode convert2RequestNode(List<String> nodes) { RequestNode nextNode = null; for (int i = nodes.size() - 1; i >= 0; i--) { if (nextNode == null) { nextNode = new RequestNode(nodes.get(i), i); } else { RequestNode tNextNode = new RequestNode(nodes.get(i), i); tNextNode.setNext(nextNode); nextNode = tNextNode; } } return nextNode; } /** * 處理請求url * @param nextNodeId * @return */ private String getServerUrl(String nextNodeId) { List<ServiceInstance> instances = discoveryClient.getInstances(nextNodeId); if (instances.isEmpty()) { return null; } ServiceInstance serviceInstance = instances.get(0); String contextPath = serviceInstance.getMetadata().get(CONTEXT_PATH); String temContextPath = contextPath == null ? "/" : contextPath; String url = "http://" + nextNodeId.toLowerCase() + temContextPath + "/gray/test/internalNodeTest"; return url; } }
2 繞過測度介面攔截
通常服務介面訪問都需要登入,未登入訪問的介面將會攔截
結果實際,通常訪問攔截的實現要麼要filter實現,要麼在HandlerInterceptor(mvc專案)
為了讓測試介面不被攔截住,那麼得想辦法繞過這些攔截;在不修改業務程式碼的情況下,繞過
攔截,代理是常用的做法;結果spring代理特點,可以輕鬆實現
2.1 建立filter 或HandlerInterceptor 代理
public class InterceptorBeanProcessor implements BeanPostProcessor { protected final Logger logger = LoggerFactory.getLogger(getClass()); private static final Map<String, Object> baseTypes = new HashMap<>(); private Enhancer enhancer = new Enhancer(); private boolean filterClassExist; private boolean interceptorClassExist; private GrayProperties grayProperties; public InterceptorBeanProcessor(GrayProperties grayProperties){ this.grayProperties = grayProperties; } static { baseTypes.put("int", 0); baseTypes.put("short", 0); baseTypes.put("long", 0L); baseTypes.put("double", 0d); baseTypes.put("float", 0f); baseTypes.put("boolean", false); baseTypes.put("char", (char) 0); baseTypes.put("byte", Byte.valueOf((byte) 0)); } @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { //建立filter 的代理 Object filterProxy = createFilterProxy(bean); if(bean.equals(filterProxy)){ //建立Interceptor 代理 return createInterceptorProxy(bean); }else { return filterProxy; } } private Object createFilterProxy(Object bean){ if(!filterClassExist()) { return bean; } if (bean instanceof Filter) { Class<?> clazz = bean.getClass(); try { return cglibProxy(clazz, new FilterMethodInterceptor(bean,grayProperties)); } catch (Throwable e) { logger.warn("create {} filter proxy failure :{}", clazz.getName(), e.getMessage()); return bean; } } return bean; } private Object createInterceptorProxy(Object bean){ if(!interceptorClassExist()) { return bean; } if (bean instanceof HandlerInterceptor) { Class<?> clazz = bean.getClass(); try { return cglibProxy(clazz, new HandlerInterceptorMethodInterceptor(bean,grayProperties)); } catch (Throwable e) { logger.warn("create {} interceptor proxy failure:{}", clazz.getName(), e.getMessage()); return bean; } } return bean; } /** * 如果不是servlet容器,filter 可能不存在 * @return */ private boolean filterClassExist() { if(filterClassExist){ return true; } try { Class clzz = Filter.class; filterClassExist = true; }catch (Throwable e){ filterClassExist = false; } return false; } private boolean interceptorClassExist() { if(interceptorClassExist){ return true; } try { Class clzz = HandlerInterceptor.class; interceptorClassExist = true; }catch ( Throwable e){ interceptorClassExist = false; } return false; } /** * final 方法沒法建立代理(cglib代理為實現灰的子類,故無法覆蓋其final方法) * * @param clazz * @return */ private Object cglibProxy(Class clazz, Callback callback) { enhancer.setSuperclass(clazz); enhancer.setCallback(callback); Constructor<?>[] constructors = clazz.getConstructors(); Parameter[] parameters = constructors[0].getParameters(); Object[] arguments = new Object[parameters.length]; Class[] paramsTypes = new Class[parameters.length]; for (int i = 0; i < arguments.length; i++) { Class<?> type = parameters[i].getType(); arguments[i] = baseTypes.get(type.getSimpleName().toLowerCase()); paramsTypes[i] = type; } return enhancer.create(paramsTypes, arguments); } }
2.2 處理是否跳過攔截
public class InterceptorAdapter implements Callback { protected final Logger logger = LoggerFactory.getLogger(getClass()); private final String FILTER_METHOD="doFilter"; private final String INTERCEPTOR_METHOD="preHandle"; PathMatcher pathMatcher = new AntPathMatcher(); private Object target; private GrayProperties grayProperties; public InterceptorAdapter(Object target, GrayProperties grayProperties) { this.target = target; this.grayProperties = grayProperties; } /** * 如果是測度路徑,跳過當前filter * @param proxy * @param method * @param args * @return * @throws InvocationTargetException * @throws IllegalAccessException * @throws IOException * @throws ServletException */ protected Object doFilter(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException, IOException, ServletException { //是否為doFilter 方法 if (FILTER_METHOD.equals(method.getName())) { HttpServletRequest servletRequest = (HttpServletRequest) args[0]; ServletResponse servletResponse = (ServletResponse) args[1]; FilterChain filterChain = (FilterChain) args[2]; String servletPath = servletRequest.getServletPath(); //符合跳過規則,執行一個filter if (skipCurrentPath(servletPath)) { logger.debug("[{}] is gray test path ,skip filter of {}",servletPath,target.getClass().getSimpleName()); filterChain.doFilter(servletRequest, servletResponse); } else { method.invoke(target, args); } } else { method.setAccessible(true); return method.invoke(target, args); } return null; } private boolean skipCurrentPath(String servletPath){ Set<String> skipPaths = grayProperties.getSkipPaths(); for(String pattern :skipPaths){ if(pathMatcher.match(pattern,servletPath)){ return true; } } return false; } /** * 如果是測試路徑,跳過當前interceptor * @param proxy * @param method * @param args * @return * @throws Exception */ protected Object preHandle(Object proxy, Method method, Object[] args) throws Exception { //是否為preHandle 方法 if (INTERCEPTOR_METHOD.equals(method.getName())) { HttpServletRequest servletRequest = (HttpServletRequest) args[0]; String servletPath = servletRequest.getServletPath(); //符合跳過規則,不執當前代理邏輯 if (skipCurrentPath(servletPath)) { logger.debug("[{}] is gray test path ,skip interceptor of {}", servletPath, target.getClass().getSimpleName()); return true; } else { return method.invoke(target, args); } } else { method.setAccessible(true); return method.invoke(target, args); } } /** * 如果是測試鏈路,跳過該過濾器 * @param proxy * @param method * @param args * @param methodProxy * @return * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { return doFilter(proxy,method,args); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return doFilter(proxy,method,args); } }