总体概览
--此文章的功能为我就职于北京登云美业网络科技有限公司时开发。
<<为老东家打个小广告 : "云美来(专业的SAAS美业服务商)"为美业商家提供数字化和自动化的经营工具>>
描述
由于现阶段微服务项目越来越多,在公司实际业务场景中会面临的下述一系列的问题:
1)(多版本)服务(近实时)切换(升降级)。
2)(多版本)服务无缝(相对来说)升级和回滚。
3)(多版本)服务灰度发布。
4)人力资源、硬件资源、软件资源等浪费。
#此次在应用层加入”版本切换”(利用分布式配置中心 apollo 近实时生效功能)功能来解决上述一系列问题。
#版本切换针对的是”客户端”与”应用层”,”应用层”与”服务层”,并且提供客户端动态以 HTTP 形式调用应用层版本服务,以及应用层以 HTTP 形式动态调用服务层版本服务的功能。
#在第一版"微服务版本切换初始设计思路(第一版)"的功能上进行大面积优化。
注:此次功能的开发只针对单个环境,暂未解决多环境切换功能,后续加入。
下述只包括此次项目涉及的内容,更多 apollo 内容需参考
”https://github.com/ctripcorp/apollo/wiki”.
架构基本流程
1)APP、H5、PC、小程序为四个客户端终端。
2)nginx 统一转发这四个客户端的请求到应用层。
3)每个客户端对应一个应用层类型,每个类型应用会多机部署。
4.1)应用层的主要作用为:服务下沉、服务升降级、服务灰度发布、服务版本切换、服务转发,也可接入链路追踪功能来监控调用服务的情况,当然要尽可能的把无效链接拦截到应用层。
4.2)应用层需要调用服务层/微服务的服务接口,但是应用层并不具有微服务性质,在这里可以简单地说,即不接入微服务框架 SpringCloud 相关内容。现阶段,只接入 Apollo 配置中心,配置服务接口地址,通常是以 HTTP 或 HTTPS 的形式调用,且逐渐适配 HTTP 的各种属性等。服务接口需要通过服务网关接入,应用层可以直接通过网关调用服务接口,也可以在应用层和网关中间接入内网 SLB,内网 SLB 来代理服务层的网关,应用层直接调用内网 SLB 即可。
5)服务层包括网关和各个服务模块。
5.1)网关用来做服务负载,以及各个模块的公共处理。
5.2)其中多租户相关模块主要是来适配其他接入项目的用户、租户、应用、菜单、按钮、接口等权限功能,最主要的是做到权限统一管理以及数据隔离。
5.3)其他非多租户模块,比如商品模块、订单模块等,为服务层的公共模块,也就是基础设施层,为以后的业务及服务扩展做好了铺垫,在应用层的业务复杂度到一定程度后,应用层的服务会逐渐落地到服务层的各个模块中,作为公共服务对外提供功能。
6)网关层、服务层以及 apollo 分布式配置中心需要接入 eureka 分布式注册中心来监听服务是否正常。
7)DB 包括了 Redis、MongoDB 以及通过 maxscale 接入的 MySQL 主从。
8)定时任务集成 elasticjob 分布式调度中心,elasticjob 需要集成 zookeeper 分布式协调中心来保证 elasticjob 的高可用。
9)可以发现,应用层-网关层-服务层,三者都接入了 DB 信息,维护各自的数据库,处理各自的模块信息。
版本配置(namespace)流程
1)client_version_reflect
客户端与应用层版本的对应关系。
2)client_version_url
应用层版本与调用此应用层版本的所有链接。
3)server_version_reflect
应用层版本与服务层版本的对应关系。
4)server_version_url
服务层版本与调用此服务层版本的正式环境所有链接。
5)server_version_url_mock
服务层版本与调用此服务层版本的模拟数据环境所有链接。
6)domain_url
域名配置集合。
7)request_template
请求模板集合。
版本配置(namespace)关系示例
#更多解释参考”版本属性配置”章节
版本属性(namespace)配置
#下述各个 namespace 配置关系参考”版本配置关系示例”
client_version_reflect
(客户端与应用层版本的对应关系)
client_version_url
(应用层版本与调用此应用层版本的所有链接)
server_version_reflect
(应用层版本与服务层版本的对应关系)
server_version_url
(服务层版本与调用此服务层版本的正式环境所有链接)
server_version_url_mock
(服务层版本与调用此服务层版本的模拟数据环境所有链接)
domain_url
(域名配置集合)
request_template
(请求模板集合)
基本配置
application
1)在 apollo 新建创建的每个项目都会生成默认的 namespace 为 application,在这个 application 中只需要修改过端口属性 server.port 和项目名称属性 spring.application.name 即可。
2)端口属性 server.port 修改内容:
apollo:namespace 为 application 的 server.port 属性。
实际项目:文件为”resources/bin/start-环境标识。sh”中。
3)项目名称属性 spring.application.name 修改内容:
apollo:在 apollo 创建项目的名称。
实际项目:实际项目名称、resources/bin/start-环境标识。sh、resources/application-环境标识。yml、pom.xml。
application_info
这里配置自定义属性,比如在项目中调用的第三方微信、支付宝链接。
但是不能配置 Redis、MySQL 这类属性。它们需要在 apollo 新建单独的 namespace,因为它们相对于项目来说是独立存在的。
项目启动初始化流程
1)项目首次启动。
2)启动系统配置检测,强制检测spring.profiles.active是否设置环境标识值,值分别为dev本地开发/uat本地测试/fat线上测试/prod生产。
3)启动apollo监听器,把apollo中涉及版本切换的所有namespace加入监听器。
4)把所有namespace的配置一次性全部同步到相应的MAP中。
5)当配置修改时,apollo监听器实时更新相应的MAP。
6)客户端在调用应用层链接时,传递客户端标识与版本号给予应用层过滤器。
7)应用层过滤器接收到之后在”client_version_reflect”中找到应用层版本号。
8)通过应用层版本号和调用链接在”client_version_url”中找到调用应用层真正的链接并重定向到controller控制层。
9)Controller控制层调用Service服务层。
10)之后Service服务层通过应用层版本号在”server_version_reflect”中找到服务层的版本号。
11)如果是调用正式服务,那么通过服务层的版本号在”server_version_url”中找到调用服务层的链接直接调用。如果是调用模拟服务数据,那么通过服务层的版本号在”server_version_url_mock”中找到调用模拟服务层的链接直接调用。
为什么使用 apollo 做版本切换
为什么使用 apollo。不使用 Redis、MySQL 等。
1)可视化,apollo 配置数据结构清晰。
2)支持多种常用数据结构配置。
3)可直接交由开发人员动态管理。如果引入 Redis、MySQL 这类数据库的话,那么初始化数据还需通过 DBA 管理,后期配置很局限。
4)可近实时生效。
UML
调用
DEMO入口
DEMO入口案例
@Slf4j
@WebFilter(filterName = DemoConstants.Filter.Common.filterName, urlPatterns = DemoConstants.Filter.Common.urlPatterns)
public class BaseDemoFilter extends BaseFilter implements Filter {
@Autowired
private HandlerExceptionResolver handlerExceptionResolver;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.warn("Filter...........................init");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {
log.info("-----------------------start-doFilter-----------------------");
LocalHttpServletRequestWrapper httpServletRequest = null;
HttpServletResponse httpServletResponse = null;
try {
httpServletRequest = new LocalHttpServletRequestWrapper((HttpServletRequest) request);
httpServletResponse = (HttpServletResponse)response;
//如特殊情况,第三方链接调用并不会传递clientHeaderName和clientHeaderVersion,可通过下述MAP来设置固定值.
//但是如果HttpHeaders中是有值的,还是以HttpHeaders中的值为准.
//TODO 不用的话要删除下面三行.
Map<String, String> temporaryHeaderMap = httpServletRequest.getTemporaryHeaderMap();
temporaryHeaderMap.put(BaseConstants.CLIENT_HEADER_NAME,"ios");
temporaryHeaderMap.put(BaseConstants.CLIENT_HEADER_VERSION,"1.0.1");
//获取实际跳转链接
String clientUrl = initForwardUrl(httpServletRequest,DemoConstants.Flag.VALUE);
//跳转之前的不同版本过滤器处理
DemoFilterManager.doFilterVersion(httpServletRequest,httpServletResponse);
//执行客户端跳转,不能用重定向,因为要传参
httpServletRequest.getRequestDispatcher(clientUrl).forward(httpServletRequest,httpServletResponse);
}catch (Exception e){
log.error("{}过滤异常{}",DemoConstants.Filter.Common.filterName,e);
handlerExceptionResolver.resolveException(httpServletRequest,httpServletResponse,null,e);
}
log.info("-----------------------end-doFilter-----------------------");
}
@Override
public void destroy() {
log.warn("Filter...........................destroy");
}
/**
* 初始化全局(终端-应用层-服务层)header头信息
* @param headers
*/
@Override
public void initGlobalHeader(Set<String> headers) {
headers.add(DemoConstants.Common.GLOBAL_HEADER_ACCESS_TOKEN);
}
}
@Slf4j
public abstract class BaseFilter {
private static final ThreadLocal<Map<String, String[]>> MAP_ARRAY_THREAD_LOCAL = LocalContext.getMapArrayThreadLocal();
private static final ThreadLocal<Map<String, String>> HTTP_HEADER_THREAD_LOCAL = LocalContext.getHttpHeaderThreadLocal();
protected static final Map<String, String> CLIENT_VERSION_REFLECT = ApolloConfigManagerResult.getClientVersionReflect();
protected static final Map<String, Map<String,ApolloClientVersionUrlBean>> CLIENT_VERSION_URL = ApolloConfigManagerResult.getClientVersionUrl();
/**
* 获取实际跳转链接(支持重写)
* @param httpServletRequest
* @return
*/
protected String initForwardUrl(final LocalHttpServletRequestWrapper httpServletRequest,final String model){
String clientUrl;
try {
String requestUri = httpServletRequest.getRequestURI();
String method = httpServletRequest.getMethod().toLowerCase();
String clientHeaderName = httpServletRequest.getHeader(BaseConstants.CLIENT_HEADER_NAME);
String clientHeaderVersion = httpServletRequest.getHeader(BaseConstants.CLIENT_HEADER_VERSION);
if(StringUtils.isBlank(clientHeaderName)){
clientHeaderName = httpServletRequest.getTemporaryHeaderMap().get(BaseConstants.CLIENT_HEADER_NAME);
}
if(StringUtils.isBlank(clientHeaderVersion)){
clientHeaderVersion = httpServletRequest.getTemporaryHeaderMap().get(BaseConstants.CLIENT_HEADER_VERSION);
}
log.info(LoggerConstants.INFO.INFO3,requestUri,method,clientHeaderName,clientHeaderVersion);
final String prefixUrl = new StringBuilder().append(BaseConstants.SLASH).append(BaseConstants.API).append(BaseConstants.SLASH).append(model).toString();
int indexOfNum = requestUri.indexOf(prefixUrl);
boolean clientFlag = (indexOfNum == 0 && StringUtils.isNotBlank(clientHeaderName) && StringUtils.isNotBlank(clientHeaderVersion));
if(clientFlag){
String clientVersionValue = clientHeaderName + BaseConstants.LINE + clientHeaderVersion;
String applicationVersionValue = CLIENT_VERSION_REFLECT.get(clientVersionValue);
Map<String,ApolloClientVersionUrlBean> apolloClientVersionUrlBeanMap = CLIENT_VERSION_URL.get(applicationVersionValue);
if(null == apolloClientVersionUrlBeanMap){
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION34 + requestUri);
}
String linkMethod = requestUri + BaseConstants.LINE + method;
ApolloClientVersionUrlBean apolloClientVersionUrlBean = apolloClientVersionUrlBeanMap.get(linkMethod);
if(null == apolloClientVersionUrlBean){
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION34 + requestUri);
}
String clientUrlVersion = apolloClientVersionUrlBean.getVersion();
String newRequestUri = requestUri.substring(prefixUrl.length(),requestUri.length());
clientUrl = new StringBuilder().append(BaseConstants.SLASH).append(model).append(BaseConstants.SLASH).append(clientUrlVersion).append(newRequestUri).toString();
//获取参数MAP
Map<String, String[]> parameterMap = new HashMap<>(httpServletRequest.getParameterMap());
parameterMap.put(BaseConstants.CLIENT_URL_VERSION,new String[]{clientUrlVersion});
parameterMap.put(BaseConstants.CLIENT_URL,new String[]{clientUrl});
parameterMap.put(BaseConstants.APPLICATION_VERSION,new String[]{applicationVersionValue});
parameterMap.put(BaseConstants.CLIENT_VERSION,new String[]{clientVersionValue});
//0正常流程,1模拟数据
parameterMap.put(BaseConstants.IS_MOCK,new String[]{String.valueOf(apolloClientVersionUrlBean.getIsMock())});
httpServletRequest.setParameterMap(parameterMap);
MAP_ARRAY_THREAD_LOCAL.set(parameterMap);
//处理全局和自定义头信息
this.initHeaderInfo(httpServletRequest);
}else{
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION36 + requestUri);
}
}catch (Exception e){
log.error(LoggerConstants.EXCEPTION.EXCEPTION35,e);
throw new ServiceException(ExceptionCode.EXCEPTION_10005.getCode(),ExceptionCode.EXCEPTION_10005.getMsg());
}
return clientUrl;
}
/**
* 处理全局和自定义头信息
* @param httpServletRequest
*/
private void initHeaderInfo(final LocalHttpServletRequestWrapper httpServletRequest){
//获取全局(终端-应用层-服务层)头信息MAP
Map<String,String> globalHeaderMap = new HashMap<>();
//先遍历全局HttpHeaders属性
HttpHeadersExtend.HttpHeadersEnum.getHttpHeadersMap().keySet().forEach(httpHeadersKey -> {
String httpHeadersValue = httpServletRequest.getHeader(httpHeadersKey);
if(StringUtils.isNotBlank(httpHeadersValue)){
globalHeaderMap.put(httpHeadersKey,httpHeadersValue);
}
});
//再遍历不同项目的自定义HttpHeaders属性
Set<String> globalHeaderSet = new HashSet<>();
//初始化全局(终端-应用层-服务层)header头信息
this.initGlobalHeader(globalHeaderSet);
globalHeaderSet.forEach(httpHeadersKey -> {
String httpHeadersValue = httpServletRequest.getHeader(httpHeadersKey);
if(StringUtils.isNotBlank(httpHeadersValue)){
globalHeaderMap.put(httpHeadersKey,httpHeadersValue);
}
});
//设置全局(终端-应用层-服务层)头信息MAP
httpServletRequest.setHeaderMap(globalHeaderMap);
HTTP_HEADER_THREAD_LOCAL.set(httpServletRequest.getHeaderMap());
log.info(LoggerConstants.INFO.INFO6,globalHeaderMap);
}
/**
* 初始化全局(终端-应用层-服务层)header头信息
* @param headers
*/
protected abstract void initGlobalHeader(Set<String> headers);
}
public class DemoTestServiceImpl implements IDemoTestService {
/**
* 通用HTTP调用入口
* @param dto
* @return
* @throws ServiceException
*/
@Override
public String testXX(DemoTestDTO dto) throws ServiceException {
//String param = JSONObject.toJSONString(dto);
String param = String.valueOf(System.currentTimeMillis());
/**
* 参数一:请求URL的code值,对应apollo(server_version_url/server_version_url_mock)中的code值,必传.
* 参数二:请求URL的参数,object类型,建议固定类型,比如json字符串或字节流,可为空,不传"".
* 参数三:请求URL的响应类型,与接收类型保持一致,可为空,不传默认String.
*/
String response = RequestInvokeService.httpInvoke(ApolloConfigDemoConstants.ServerVersionUrl.send,param,String.class);
return response;
}
/**
* 自定义HTTP请求类型入口
* TODO 此时apoll(request_template)中配置的connectTimeout、readTimeout、headerName、headerValue、contentType不生效.
* @param vo
* @return
* @throws ServiceException
*/
@Override
public String testXX2(DemoTestVO vo) throws ServiceException {
String param = JSONObject.toJSONString(vo);
RequestInvokeCustomerEntity requestInvokeCustomerEntity = new RequestInvokeCustomerEntity();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
//httpHeaders.addAll();
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setConnectTimeout(100);
simpleClientHttpRequestFactory.setReadTimeout(100);
//simpleClientHttpRequestFactory.setXX();
//方式一:自定义HttpHeaders和SimpleClientHttpRequestFactory
requestInvokeCustomerEntity.setHttpHeaders(httpHeaders);
requestInvokeCustomerEntity.setSimpleClientHttpRequestFactory(simpleClientHttpRequestFactory);
RequestInvokeCustomerService requestInvokeCustomerService1 = new RequestInvokeCustomerService(requestInvokeCustomerEntity);
/**
* 参数一:请求URL的code值,对应apollo(server_version_url/server_version_url_mock)中的code值,必传.
* 参数二:请求URL的参数,object类型,建议固定类型,比如json字符串或字节流,可为空,不传"".
* 参数三:请求URL的响应类型,与接收类型保持一致,可为空,不传默认String.
*/
return requestInvokeCustomerService1.httpInvoke(ApolloConfigDemoConstants.ServerVersionUrl.send2,param,String.class);
}
第一步
第一步案例
/**
* @Description: 调用的主方法
* TODO 可扩展到HTTP、HTTPS、RPC、MQ等.
*/
public abstract class BaseRequestContext {
/**
* 调用方法入口,各个参数根据调用类型自定义拼装
* TODO 不可以设置成public
* @param url
* @param param
* @param clazz
* @param <T>
* @return
*/
protected abstract <T> T process(String url,String param,Class<T> clazz);
}
public class LoadBalancerHttpRequestContext extends BaseLoadBalancerHttpRequestContext {
/**
* 自定义请求封装实体对象
*/
private RequestInvokeCustomerEntity requestInvokeCustomerEntity;
/**
* 默认配置请求实例化
*/
public LoadBalancerHttpRequestContext() {
}
/**
* 自定义配置请求实例化
* @param requestInvokeCustomerEntity
*/
public LoadBalancerHttpRequestContext(RequestInvokeCustomerEntity requestInvokeCustomerEntity) {
this.requestInvokeCustomerEntity = requestInvokeCustomerEntity;
}
/**
* 调用前处理内容
* TODO 此方法块中的内容在HttpRequestRestTemplate中有体现
* @param httpRequestInvokeEntity
* @param httpResponseInvokeEntity
*/
@Override
public void before(AssembleHttpRequestInvokeEntity httpRequestInvokeEntity,
AssembleHttpResponseInvokeEntity httpResponseInvokeEntity) {
HttpRequestProcessBeforeContext httpRequestProcessBeforeContext;
if(null != this.getRequestInvokeCustomerEntity()){
//第一部分:调用前处理内容-处理HTTP请求自定义属性部分
httpRequestProcessBeforeContext = new HttpRequestProcessBeforeContext(httpRequestInvokeEntity,httpResponseInvokeEntity,this.getRequestInvokeCustomerEntity());
httpRequestProcessBeforeContext.beforeOfCustomer();
}
else{
//第二部分:处理HTTP请求apollo配置属性部分,即使不是自定义也要声明出来HttpHeaders和SimpleClientHttpRequestFactory
httpRequestProcessBeforeContext = new HttpRequestProcessBeforeContext(httpRequestInvokeEntity,httpResponseInvokeEntity);
httpRequestProcessBeforeContext.beforeOfNormal();
}
//第三部分:处理HTTP请求的全局Header部分
httpRequestProcessBeforeContext.beforeOfGlobalHeader();
}
/**
* 调用过程入口
* @param httpRequestInvokeEntity
* @param httpResponseInvokeEntity
* @param <T>
* @return
*/
@Override
public <T> T invoke(AssembleHttpRequestInvokeEntity httpRequestInvokeEntity,
AssembleHttpResponseInvokeEntity httpResponseInvokeEntity) {
ApolloRequestTemplateLoadbalanceBean templateBean = httpRequestInvokeEntity.getTemplateBean();
HttpRequestEntity httpRequestEntity = httpRequestInvokeEntity.getHttpRequestEntity();
HttpResponseEntity<T> httpResponseEntity = httpResponseInvokeEntity.getHttpResponseEntity();
String requestType = templateBean.getRequestType();
//这里使用状态模式来处理
HttpRequestInvokeStateContext invokeStateContext = new HttpRequestInvokeStateContext();
if(BaseConstants.RequestType.LOADBALANCE == BaseConstants.RequestType.get(requestType.toLowerCase())){
String loadBalanceType = templateBean.getLoadBalance();
if(LoadBalancerType.RANDOM.name().equals(loadBalanceType.toUpperCase())){
invokeStateContext.setInvokeState(new HttpLoadBalancerRandomInvokeState());
}
}else if(BaseConstants.RequestType.HTTP == BaseConstants.RequestType.get(requestType.toLowerCase())){
invokeStateContext.setInvokeState(new HttpInvokeState());
}else if(BaseConstants.RequestType.HTTPS == BaseConstants.RequestType.get(requestType.toLowerCase())){
invokeStateContext.setInvokeState(new HttpsInvokeState());
}
return invokeStateContext.handler(httpRequestEntity,httpResponseEntity);
}
/**
* 调用后处理内容
* @param httpRequestInvokeEntity
* @param httpResponseInvokeEntity
*/
@Override
public void after(AssembleHttpRequestInvokeEntity httpRequestInvokeEntity,
AssembleHttpResponseInvokeEntity httpResponseInvokeEntity) {
}
/**
* 自定义请求封装实体对象
* @return
*/
private RequestInvokeCustomerEntity getRequestInvokeCustomerEntity() {
return this.requestInvokeCustomerEntity;
}
}
public class HttpRequestProcessBeforeContext {
//组装http请求相关对象
private AssembleHttpRequestInvokeEntity httpRequestInvokeEntity;
//组装http响应相关对象
private AssembleHttpResponseInvokeEntity httpResponseInvokeEntity;
//自定义请求封装实体对象
private RequestInvokeCustomerEntity requestInvokeCustomerEntity;
public HttpRequestProcessBeforeContext(AssembleHttpRequestInvokeEntity httpRequestInvokeEntity,
AssembleHttpResponseInvokeEntity httpResponseInvokeEntity) {
this.httpRequestInvokeEntity = httpRequestInvokeEntity;
this.httpResponseInvokeEntity = httpResponseInvokeEntity;
}
public HttpRequestProcessBeforeContext(AssembleHttpRequestInvokeEntity httpRequestInvokeEntity,
AssembleHttpResponseInvokeEntity httpResponseInvokeEntity,
RequestInvokeCustomerEntity requestInvokeCustomerEntity) {
this(httpRequestInvokeEntity,httpResponseInvokeEntity);
this.requestInvokeCustomerEntity = requestInvokeCustomerEntity;
}
/**
* 第一部分:调用前处理内容-处理HTTP请求自定义属性部分
*/
public void beforeOfCustomer(){
HttpRequestEntity httpRequestEntity = this.getHttpRequestInvokeEntity().getHttpRequestEntity();
if(null != this.getRequestInvokeCustomerEntity().getHttpHeaders()){
httpRequestEntity.setHttpHeaders(this.getRequestInvokeCustomerEntity().getHttpHeaders());
}
if(null != this.getRequestInvokeCustomerEntity().getSimpleClientHttpRequestFactory()){
httpRequestEntity.setSimpleClientHttpRequestFactory(this.getRequestInvokeCustomerEntity().getSimpleClientHttpRequestFactory());
}
String url = httpRequestEntity.getUrl();
Map<String,String> restfulMap = this.getRequestInvokeCustomerEntity().getRestful_map();
if(url.contains(ApolloRelationConfigConstants.RESTFUL_LINK0)){
String restfulLink0 = restfulMap.get(ApolloRelationConfigConstants.RESTFUL_LINK0);
if(StringUtils.isNotBlank(restfulLink0)){
url = url.replace(ApolloRelationConfigConstants.RESTFUL_LINK0,restfulLink0);
httpRequestEntity.setUrl(url);
}else{
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION60 + url);
}
}
if(url.contains(ApolloRelationConfigConstants.RESTFUL_LINK1)){
String restfulLink1 = restfulMap.get(ApolloRelationConfigConstants.RESTFUL_LINK1);
if(StringUtils.isNotBlank(restfulLink1)){
url = httpRequestEntity.getUrl().replace(ApolloRelationConfigConstants.RESTFUL_LINK1,restfulLink1);
httpRequestEntity.setUrl(url);
}else{
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION60 + url);
}
}
}
/**
* 第二部分:处理HTTP请求apollo配置属性部分,即使不是自定义也要声明出来HttpHeaders和SimpleClientHttpRequestFactory
*/
public void beforeOfNormal(){
HttpRequestEntity httpRequestEntity = this.getHttpRequestInvokeEntity().getHttpRequestEntity();
ApolloRequestTemplateLoadbalanceBean templateBean = this.getHttpRequestInvokeEntity().getTemplateBean();
HttpHeaders httpHeaders = new HttpHeaders();
if(StringUtils.isNotBlank(templateBean.getContentType())){
httpHeaders.setContentType(MediaType.valueOf(templateBean.getContentType()));
}else{
httpHeaders.setContentType(MediaType.valueOf(httpRequestEntity.getContentType()));
}
if(StringUtils.isBlank(templateBean.getHeaderName()) || StringUtils.isBlank(templateBean.getHeaderValue())){
httpHeaders.add(httpRequestEntity.getHeaderName(), httpRequestEntity.getHeaderValue());
}else{
httpHeaders.add(templateBean.getHeaderName(), templateBean.getHeaderValue());
}
httpRequestEntity.setHttpHeaders(httpHeaders);
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
if(null != templateBean.getConnectTimeout()){
simpleClientHttpRequestFactory.setConnectTimeout(templateBean.getConnectTimeout());
}else{
simpleClientHttpRequestFactory.setConnectTimeout(httpRequestEntity.getConnectTimeout());
}
if(null != templateBean.getReadTimeout()){
simpleClientHttpRequestFactory.setReadTimeout(templateBean.getReadTimeout());
}else{
simpleClientHttpRequestFactory.setReadTimeout(httpRequestEntity.getReadTimeout());
}
httpRequestEntity.setSimpleClientHttpRequestFactory(simpleClientHttpRequestFactory);
}
/**
* 第三部分:处理HTTP请求的全局Header部分
*/
public void beforeOfGlobalHeader(){
HttpRequestEntity httpRequestEntity = this.getHttpRequestInvokeEntity().getHttpRequestEntity();
HttpHeaders httpHeaders = httpRequestEntity.getHttpHeaders();
Map<String, String> headerMap = LocalContext.getHttpHeaderThreadLocal().get();
headerMap.keySet().forEach(headerKey -> {
httpHeaders.add(headerKey,headerMap.get(headerKey));
});
}
private AssembleHttpRequestInvokeEntity getHttpRequestInvokeEntity() {
return this.httpRequestInvokeEntity;
}
private AssembleHttpResponseInvokeEntity getHttpResponseInvokeEntity() {
return this.httpResponseInvokeEntity;
}
private RequestInvokeCustomerEntity getRequestInvokeCustomerEntity() {
return this.requestInvokeCustomerEntity;
}
}
public abstract class BaseLoadBalancerHttpRequestContext extends BaseRequestContext{
protected final Map<String, String> serverVersionReflectMap = ApolloConfigManagerResult.getServerVersionReflect();
protected final Map<String, Map<String, ApolloServerVersionUrlBean>> serverVersionUrlMap = ApolloConfigManagerResult.getServerVersionUrl();
protected final Map<String, Map<String, ApolloServerVersionUrlMockBean>> serverVersionUrlMockMap = ApolloConfigManagerResult.getServerVersionUrlMock();
protected final Map<String, String> domainUrlMap = ApolloConfigManagerResult.getDomainUrl();
protected final Map<String, JSONObject> requestTemplateMap = ApolloConfigManagerResult.getRequestTemplate();
/**
* 调用方法入口,各个参数根据调用类型自定义拼装
* TODO 可以设置成public,其中的方法根据不同的类型自定义扩展方法
* @param url
* @param param
* @param clazz
* @param <T>
* @return
*/
@Override
final public <T> T process(String url,Object param,Class<T> clazz){
AssembleHttpRequestInvokeEntity httpRequestInvokeEntity = assembleRequest(url,param);
AssembleHttpResponseInvokeEntity httpResponseInvokeEntity = assembleResponse(clazz);
before(httpRequestInvokeEntity,httpResponseInvokeEntity);
T response = invoke(httpRequestInvokeEntity,httpResponseInvokeEntity);
after(httpRequestInvokeEntity,httpResponseInvokeEntity);
return response;
}
abstract protected void before(AssembleHttpRequestInvokeEntity httpRequestInvokeEntity, AssembleHttpResponseInvokeEntity httpResponseInvokeEntity);
abstract protected <T> T invoke(AssembleHttpRequestInvokeEntity httpRequestInvokeEntity,AssembleHttpResponseInvokeEntity httpResponseInvokeEntity);
abstract protected void after(AssembleHttpRequestInvokeEntity httpRequestInvokeEntity,AssembleHttpResponseInvokeEntity httpResponseInvokeEntity);
/**
* 组装AssembleHttpRequestInvokeEntity对象
* @param url
* @param param
* @return
*/
private AssembleHttpRequestInvokeEntity assembleRequest(String url, Object param){
//这里要检验URL格式
if(StringUtils.isBlank(url)){
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION1);
}
Map<String,Object> invokeInfoMap = getInvokeInfo();
String applicationVersion = (String) invokeInfoMap.get(BaseConstants.APPLICATION_VERSION);
String serviceVersion = this.serverVersionReflectMap.get(applicationVersion);
if(StringUtils.isBlank(serviceVersion)){
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION3);
}
String domain;
String requestTemplate;
String method;
String link;
int isMock = Integer.parseInt(String.valueOf(invokeInfoMap.get(BaseConstants.IS_MOCK)));
//正常流程
if(isMock == 0){
Map<String, ApolloServerVersionUrlBean> beanMap = this.serverVersionUrlMap.get(serviceVersion);
if(null == beanMap){
throw new IllegalArgumentException(isMock + LoggerConstants.EXCEPTION.EXCEPTION4);
}
ApolloServerVersionUrlBean apolloServerVersionUrlBean = beanMap.get(url);
if(null == apolloServerVersionUrlBean || null == apolloServerVersionUrlBean.getDomain()){
throw new IllegalArgumentException(isMock + LoggerConstants.EXCEPTION.EXCEPTION5);
}
domain = this.domainUrlMap.get(apolloServerVersionUrlBean.getDomain());
requestTemplate = apolloServerVersionUrlBean.getRequestTemplate();
method = apolloServerVersionUrlBean.getMethod().toUpperCase();
link = apolloServerVersionUrlBean.getLink();
}
//模拟流程
else if(isMock == 1){
Map<String, ApolloServerVersionUrlMockBean> beanMap = this.serverVersionUrlMockMap.get(serviceVersion);
if(null == beanMap){
throw new IllegalArgumentException(isMock + LoggerConstants.EXCEPTION.EXCEPTION4);
}
ApolloServerVersionUrlMockBean apolloServerVersionUrlMockBean = beanMap.get(url);
if(null == apolloServerVersionUrlMockBean || null == apolloServerVersionUrlMockBean.getDomain()){
throw new IllegalArgumentException(isMock + LoggerConstants.EXCEPTION.EXCEPTION5);
}
domain = this.domainUrlMap.get(apolloServerVersionUrlMockBean.getDomain());
requestTemplate = apolloServerVersionUrlMockBean.getRequestTemplate();
method = apolloServerVersionUrlMockBean.getMethod().toUpperCase();
link = apolloServerVersionUrlMockBean.getLink();
}else{
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION6);
}
if(StringUtils.isBlank(domain)){
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION7);
}
if(StringUtils.isBlank(requestTemplate)){
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION8);
}
JSONObject jsonObject = this.requestTemplateMap.get(requestTemplate);
ApolloRequestTemplateLoadbalanceBean templateBean = jsonObject.toJavaObject(ApolloRequestTemplateLoadbalanceBean.class);
String requestType = templateBean.getRequestType();
if(StringUtils.isBlank(requestType)){
//如果不加默认负载均衡
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION9);
}
HttpRequestEntity httpRequestEntity = new HttpRequestEntity();
httpRequestEntity.setHttpMethod(HttpMethod.resolve(method));
httpRequestEntity.setParam(param);
if(BaseConstants.RequestType.LOADBALANCE == BaseConstants.RequestType.get(requestType.toLowerCase())){
httpRequestEntity.setConnectList(Arrays.asList(domain.split(BaseConstants.COMMA)));
httpRequestEntity.setUrl(link);
String loadBalanceType = templateBean.getLoadBalance();
if(StringUtils.isBlank(loadBalanceType) || LoadBalancerType.RANDOM.name().equals(loadBalanceType.toUpperCase())){
templateBean.setLoadBalance(LoadBalancerType.RANDOM.name());
}else{
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION10);
}
}else if(BaseConstants.RequestType.HTTP == BaseConstants.RequestType.get(requestType.toLowerCase())){
if(domain.contains(BaseConstants.COMMA)){
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION11);
}
httpRequestEntity.setUrl(domain + link);
}else if(BaseConstants.RequestType.HTTPS == BaseConstants.RequestType.get(requestType.toLowerCase())){
if(domain.contains(BaseConstants.COMMA)){
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION11);
}
httpRequestEntity.setUrl(domain + link);
}else{
throw new IllegalArgumentException(LoggerConstants.EXCEPTION.EXCEPTION12);
}
AssembleHttpRequestInvokeEntity assembleHttpRequestInvokeEntity = new AssembleHttpRequestInvokeEntity();
assembleHttpRequestInvokeEntity.setTemplateBean(templateBean);
assembleHttpRequestInvokeEntity.setHttpRequestEntity(httpRequestEntity);
return assembleHttpRequestInvokeEntity;
}
private <T> AssembleHttpResponseInvokeEntity assembleResponse(Class<T> clazz){
AssembleHttpResponseInvokeEntity assembleHttpResponseInvokeEntity = new AssembleHttpResponseInvokeEntity();
HttpResponseEntity httpResponseEntity = new HttpResponseEntity();
if(null == clazz){
Class<String> stringClass = String.class;
httpResponseEntity.setType(stringClass);
}else{
httpResponseEntity.setType(clazz);
}
assembleHttpResponseInvokeEntity.setHttpResponseEntity(httpResponseEntity);
return assembleHttpResponseInvokeEntity;
}
//这个是否和controller的对应一致
private Map<String,Object> getInvokeInfo(){
Map<String, String[]> threadLocalMap = LocalContext.getMapArrayThreadLocal().get();
Map<String,Object> invokeInfoMap = new HashMap<>();
invokeInfoMap.put(BaseConstants.APPLICATION_VERSION,threadLocalMap.get(BaseConstants.APPLICATION_VERSION)[0]);
invokeInfoMap.put(BaseConstants.IS_MOCK,threadLocalMap.get(BaseConstants.IS_MOCK)[0]);
return invokeInfoMap;
}
}
第二步
#衔接第一步中的”LoadBalancerHttpRequestContext”
第二步案例
/**
* @Description: http相关调用状态的上下文
*/
public class HttpRequestInvokeStateContext {
private AbstractHttpRequestInvokeState invokeState;
private AbstractHttpRequestInvokeState getInvokeState() {
return invokeState;
}
public final void setInvokeState(AbstractHttpRequestInvokeState invokeState){
this.invokeState = invokeState;
this.invokeState.setContext(this);
}
public <T> T handler(HttpRequestEntity httpRequestEntity, HttpResponseEntity<T> httpResponseEntity) {
return this.getInvokeState().handler(httpRequestEntity,httpResponseEntity);
}
}
/**
* @Description: http相关调用状态
*/
public abstract class AbstractHttpRequestInvokeState {
protected HttpRequestInvokeStateContext context;
public void setContext(HttpRequestInvokeStateContext context){
this.context = context;
}
abstract protected <T> T handler(HttpRequestEntity httpRequestEntity,HttpResponseEntity<T> httpResponseEntity);
}
/**
* @Description: http相关调用:负载均衡-随机负载http/https状态
*/
public class HttpLoadBalancerRandomInvokeState extends AbstractHttpRequestInvokeState{
@Override
public <T> T handler(HttpRequestEntity httpRequestEntity, HttpResponseEntity<T> httpResponseEntity) {
LoadBalancerEntity loadBalancerEntity = new LoadBalancerEntity();
loadBalancerEntity.setHttpRequestEntity(httpRequestEntity);
//负载均衡配置
ILoadBalancer loadBalancer = LoadBalancerDirector.getRandomLoadBalancer(loadBalancerEntity);
return LoadBalancerRequestInvoke.getInstance().invoke(loadBalancer,httpRequestEntity,httpResponseEntity);
}
}
第三步
#衔接第二步中的”HttpRequestUnusualMethodImpl”
第三步案例
/**
* @Description: 各种http请求方法处理
*/
public class HttpRequestMethodImpl implements IHttpRequestMethod {
@Override
public <T> T post(HttpRequestEntity httpRequestEntity, HttpResponseEntity<T> httpResponseEntity) {
T val = null;
try {
HttpRequestRestTemplate httpRequestRestTemplate = new HttpRequestRestTemplate(httpRequestEntity);
RestTemplate restTemplate = httpRequestRestTemplate.getRestTemplate();
HttpEntity httpEntity = httpRequestRestTemplate.getHttpEntity();
val = restTemplate.exchange(httpRequestEntity.getUrl(), HttpMethod.POST,httpEntity, httpResponseEntity.getType()).getBody();
} catch (Exception e) {
e.printStackTrace();
}
return val;
}
@Override
public <T> T get(HttpRequestEntity httpRequestEntity, HttpResponseEntity<T> httpResponseEntity) {
T val = null;
try {
HttpRequestRestTemplate httpRequestRestTemplate = new HttpRequestRestTemplate(httpRequestEntity);
RestTemplate restTemplate = httpRequestRestTemplate.getRestTemplate();
HttpEntity httpEntity = httpRequestRestTemplate.getHttpEntity();
val = restTemplate.exchange(httpRequestEntity.getUrl(), HttpMethod.GET,httpEntity, httpResponseEntity.getType()).getBody();
} catch (Exception e) {
e.printStackTrace();
}
return val;
}
@Override
public <T> T put(HttpRequestEntity httpRequestEntity, HttpResponseEntity<T> httpResponseEntity) {
T val = null;
try {
HttpRequestRestTemplate httpRequestRestTemplate = new HttpRequestRestTemplate(httpRequestEntity);
RestTemplate restTemplate = httpRequestRestTemplate.getRestTemplate();
HttpEntity httpEntity = httpRequestRestTemplate.getHttpEntity();
val = restTemplate.exchange(httpRequestEntity.getUrl(), HttpMethod.PUT,httpEntity, httpResponseEntity.getType()).getBody();
} catch (Exception e) {
e.printStackTrace();
}
return val;
}
@Override
public <T> T delete(HttpRequestEntity httpRequestEntity, HttpResponseEntity<T> httpResponseEntity) {
T val = null;
try {
HttpRequestRestTemplate httpRequestRestTemplate = new HttpRequestRestTemplate(httpRequestEntity);
RestTemplate restTemplate = httpRequestRestTemplate.getRestTemplate();
HttpEntity httpEntity = httpRequestRestTemplate.getHttpEntity();
val = restTemplate.exchange(httpRequestEntity.getUrl(), HttpMethod.DELETE,httpEntity, httpResponseEntity.getType()).getBody();
} catch (Exception e) {
e.printStackTrace();
}
return val;
}
}
/**
* @Description: 组装请求对象
*/
public class HttpRequestRestTemplate {
private RestTemplate restTemplate;
private HttpHeaders httpHeaders;
private SimpleClientHttpRequestFactory simpleClientHttpRequestFactory;
private HttpRequestEntity httpRequestEntity;
public HttpRequestRestTemplate(HttpRequestEntity httpRequestEntity) {
this.httpRequestEntity = httpRequestEntity;
//如果自定义HttpHeaders不为空,那么在apollo(request_template)中配置的headerName、headerValue、contentType将失效
httpHeaders = httpRequestEntity.getHttpHeaders();
if(null == httpHeaders){
this.httpHeaders = new HttpHeaders();
this.httpHeaders.setContentType(MediaType.valueOf(httpRequestEntity.getContentType()));
this.httpHeaders.add(httpRequestEntity.getHeaderName(), httpRequestEntity.getHeaderValue());
}
//如果自定义simpleClientHttpRequestFactory不为空,那么在apollo(request_template)中配置的connectTimeout、readTimeout将失效
this.simpleClientHttpRequestFactory = httpRequestEntity.getSimpleClientHttpRequestFactory();
if(null == this.simpleClientHttpRequestFactory){
this.simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
this.simpleClientHttpRequestFactory.setConnectTimeout(httpRequestEntity.getConnectTimeout());
this.simpleClientHttpRequestFactory.setReadTimeout(httpRequestEntity.getReadTimeout());
}
this.restTemplate = new RestTemplate(this.simpleClientHttpRequestFactory);
}
public RestTemplate getRestTemplate() {
return this.restTemplate;
}
public HttpEntity getHttpEntity(){
return new HttpEntity<>(this.httpRequestEntity.getParam(), this.httpHeaders);
}
}
Apollo 版本切换与监听
#只是以 XXMock 对象做了个例子,其他示例同理。
APOLLO 案例
/**
* @Description: 继承这个类的对象过多,要进行抽离
*/
public class BaseApolloConfigManager {
/**
* 1、因为这个类针对于不同的apollo-namespace都会实例化一次,所以这个MAP定义在这里并不会引起原子性问题.
* 2、这里的MAP没有针对于固定泛型的设置,因为不同的apollo-namespace解析对象不一样.
*/
private Map map;
/**
* 监听apollo更新config配置的内存MAP存储数据
* @param propertyName
* @param value
*/
public void updateConfig(String propertyName, String value) {
try {
if(StringUtils.isNotBlank(value)){
map.put(propertyName, value.toLowerCase());
}
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 监听apollo删除config配置的内存MAP存储数据
* @param propertyName
*/
public void deleteConfig(String propertyName) {
try {
map.remove(propertyName);
}catch (Exception e){
e.printStackTrace();
}
}
final protected void setMap(Map map) {
this.map = map;
}
}
public class ApolloServerVersionUrlMockManager extends BaseApolloConfigManager {
private final Map<String, Map<String, ApolloServerVersionUrlMockBean>> map = ApolloConfigManagerResult.getServerVersionUrlMock();
public ApolloServerVersionUrlMockManager() {
super.setMap(map);
}
/**
* 监听apollo更新config配置的内存MAP存储数据(注意这里子MAP的主键是code)
* @param propertyName
* @param newValue
*/
@Override
public void updateConfig(String propertyName, String newValue) {
try {
if(StringUtils.isNotBlank(newValue)){
List<ApolloServerVersionUrlMockBean> urlList = JSONObject.parseArray(newValue,ApolloServerVersionUrlMockBean.class);
Map<String,ApolloServerVersionUrlMockBean> valueMap = new HashMap<>();
for (ApolloServerVersionUrlMockBean jsonObject : urlList) {
valueMap.put(jsonObject.getCode(),jsonObject);
}
map.put(propertyName.toLowerCase(),valueMap);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
public class ApolloServerVersionUrlMockConfig extends ApolloRelationVersionConfig {
public ApolloServerVersionUrlMockConfig() {
super(new ApolloServerVersionUrlMockManager());
}
}
public class ApolloRelationVersionConfig extends BaseApolloRelationVersionConfig {
protected final BaseApolloConfigManager apolloConfigManager;
public ApolloRelationVersionConfig(BaseApolloConfigManager apolloConfigManager) {
this.apolloConfigManager = apolloConfigManager;
}
@Override
public void initConfigData(Config config) {
Set<String> propertyNames = config.getPropertyNames();
propertyNames.forEach((propertyName) -> {
String value = config.getProperty(propertyName,"");
this.apolloConfigManager.updateConfig(propertyName,value);
});
}
@Override
public void addVersionConfig(ConfigChange configChange) {
String propertyName = configChange.getPropertyName();
String value = configChange.getNewValue();
this.apolloConfigManager.updateConfig(propertyName,value);
}
@Override
public void updateVersionConfig(ConfigChange configChange) {
String propertyName = configChange.getPropertyName();
String value = configChange.getNewValue();
this.apolloConfigManager.updateConfig(propertyName,value);
}
@Override
public void deleteVersionConfig(ConfigChange configChange) {
String propertyName = configChange.getPropertyName();
this.apolloConfigManager.deleteConfig(propertyName);
}
}
/**
* @Description: apollo监听
*/
public abstract class BaseApolloRelationVersionConfig {
/**
* 在项目启动初始化时把config对象加入apollo的监听器
* @param config
*/
final void addChangeListener(Config config){
ConfigChangeListener configChangeListener = (changeEvent) -> {
for(String key : changeEvent.changedKeys()){
ConfigChange configChange = changeEvent.getChange(key);
switch (configChange.getChangeType()){
case ADDED:
addVersionConfig(configChange);
break;
case MODIFIED:
updateVersionConfig(configChange);
break;
case DELETED:
deleteVersionConfig(configChange);
break;
default:
break;
}
}
};
config.addChangeListener(configChangeListener);
}
final public void init(Config config){
initConfigData(config);
addChangeListener(config);
}
protected abstract void initConfigData(Config config);
protected abstract void addVersionConfig(ConfigChange configChange);
protected abstract void updateVersionConfig(ConfigChange configChange);
protected abstract void deleteVersionConfig(ConfigChange configChange);
}
HTTP
#衔接上下两张图中的”HttpRequestUnusualMethodImpl”
HTTP 案例
/**
* @Description: http请求实体对象(统一使用此对象而不是父类)
* 针对于IHttpRequestUnusualMethod接口中的不常用请求方法
* 在此类中加入不常用的属性值,配合IHttpRequestUnusualMethod中不常用请求方法来使用
*/
public class HttpRequestEntity extends BaseHttpRequestEntity{
}
/**
* @Description: http请求实体对象
* 如果扩展过多,那么可以直接继承此类
*/
public class BaseHttpRequestEntity extends BaseEntity {
/**
* http或https
* 默认不用设置
*/
private HttpFlag httpFlag = HttpFlag.HTTP;
/**
* 多个IP:PORT
* TODO 只用于负载均衡算法
* 写法(不加http前缀默认http,写域名端口默认解析80端口):
* https://test1.com,https://test2.com
* http://test.com,https://test2.com
* test.com,test2.com
* https://10.10.8.10:8080,10.10.8.11:8080
* http://10.10.8.10:8080,http://10.10.8.10:8180
* 10.10.8.10:8080,http://10.10.8.10:8080
*/
private List<String> connectList = Lists.newArrayList();
/**
* 请求URL
* TODO 如果用于负载均衡,那么传入子链接,负载均衡后拼成完整链接。比如/api/forward
* TODO 如果不用于负载均衡,那么直接传入完整链接。比如https://test.com/api/forward
*/
private String url = "";
/**
* 请求方法:
* GET,
* HEAD,
* POST,
* PUT,
* PATCH,
* DELETE,
* OPTIONS,
* TRACE;
*/
private HttpMethod httpMethod = HttpMethod.GET;
/**
* 请求参数(json)
*/
private String param = "";
/**
* 连接超时
* set方法中有判断限制
*/
private Integer connectTimeout = -1;
/**
* 读超时
* set方法中有判断限制
*/
private Integer readTimeout = -1;
/**
* http请求头接收数据类型KEY
* TODO 如果上述header不为空,那么headerName和headerValue不会set到HttpHeaders中
*/
private String headerName = HttpHeaders.ACCEPT;
/**
* http请求头接收数据类型VALUE
*/
private String headerValue = MediaType.APPLICATION_JSON.toString();
/**
* http实体头Content-type
*/
private String contentType = MediaType.APPLICATION_JSON_UTF8.toString();
public HttpFlag getHttpFlag() {
return httpFlag;
}
public void setHttpFlag(HttpFlag httpFlag) {
if(null != httpFlag){
this.httpFlag = httpFlag;
}
}
public List<String> getConnectList() {
return connectList;
}
public void setConnectList(List<String> connectList) {
if(!connectList.isEmpty()){
this.connectList = connectList;
}
}
public HttpMethod getHttpMethod() {
return httpMethod;
}
public void setHttpMethod(HttpMethod httpMethod) {
if(null != httpMethod){
this.httpMethod = httpMethod;
}
}
public String getParam() {
return param;
}
public void setParam(String param) {
if(StringUtils.isNotBlank(param)){
this.param = param;
}
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
if(StringUtils.isNotBlank(url)){
this.url = url;
}
}
public Integer getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(Integer connectTimeout) {
if(null != connectTimeout && connectTimeout > 0){
this.connectTimeout = connectTimeout;
}
}
public Integer getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(Integer readTimeout) {
if(null != readTimeout && readTimeout > 0){
this.readTimeout = readTimeout;
}
}
public String getHeaderName() {
return headerName;
}
public void setHeaderName(String headerName) {
if(StringUtils.isNotBlank(headerName)){
this.headerName = headerName;
}
}
public String getHeaderValue() {
return headerValue;
}
public void setHeaderValue(String headerValue) {
if(StringUtils.isNotBlank(headerValue)){
this.headerValue = headerValue;
}
}
public String getContentType() {
return contentType;
}
public void setContentType(String contentType) {
if(StringUtils.isNotBlank(contentType)){
this.contentType = contentType;
}
}
}
/**
* @Description: http响应实体对象(统一使用此对象而不是父类)
* 针对于IHttpRequestUnusualMethod接口中的不常用请求方法
* 在此类中加入不常用的属性值,配合IHttpRequestUnusualMethod中不常用请求方法来使用
*/
public class HttpResponseEntity<T> extends BaseHttpResponseEntity<T> {
}
/**
* @Description: http响应实体对象
*/
public class BaseHttpResponseEntity<T> extends BaseEntity {
/**
* 响应对象类型
*/
private Class<T> type;
public Class<T> getType() {
return type;
}
public void setType(Class<T> type) {
this.type = type;
}
}
负载均衡
负载均衡案例
public final class LoadBalancerDirector {
private static final BaseLoadBalancerBuilder RANDOM = new LoadBalancerRandomBuilder();
private static final BaseLoadBalancerBuilder ROUND_ROBIN = new LoadBalancerRoundRobinBuilder();
private LoadBalancerDirector(){}
public static ILoadBalancer getRandomLoadBalancer(LoadBalancerEntity loadBalancerEntity){
return RANDOM.getILoadBalancer(loadBalancerEntity);
}
public static ILoadBalancer getRoundRobinLoadBalancer(LoadBalancerEntity loadBalancerEntity){
return ROUND_ROBIN.getILoadBalancer(loadBalancerEntity);
}
}
public class LoadBalancerRandomBuilder extends AbstractLoadBalancerBuilder{
public LoadBalancerRandomBuilder() {
super(new LoadBalancerRandomProduct());
}
@Override
public ILoadBalancer getILoadBalancer(LoadBalancerEntity loadBalancerEntity) {
//设置super.loadBalancerProduct.setIfPing(Boolean.TRUE);开启ping
return super.getILoadBalancer(loadBalancerEntity);
}
}
public abstract class BaseLoadBalancerBuilder {
protected abstract ILoadBalancer getILoadBalancer(LoadBalancerEntity loadBalancerEntity);
}
/**
* @Description: 随机负载均衡
*/
public class LoadBalancerRandomProduct extends AbstractLoadBalancerProduct {
/**
* 由于不同的负载均衡有不同的处理机制,且会在前面根据应用实时情况切换负载机制.
* 所以虽然是局部对象,也不把下述相同的内容单独抽出来
* @param loadBalancerEntity
* @return
*/
@Override
public IRule getRule(LoadBalancerEntity loadBalancerEntity) {
RandomRule rule = new RandomRule();
IClientConfig clientConfig = loadBalancerEntity.getClientConfig();
if(clientConfig != null){
rule.initWithNiwsConfig(clientConfig);
}
ILoadBalancer loadBalancer = loadBalancerEntity.getLoadBalancer();
if(loadBalancer != null){
rule.setLoadBalancer(loadBalancer);
}
return rule;
}
}
/**
* @Description: 获取负载均衡对象ILoadBalancer
*/
public abstract class AbstractLoadBalancerBuilder extends BaseLoadBalancerBuilder {
protected final AbstractLoadBalancerProduct loadBalancerProduct;
public AbstractLoadBalancerBuilder(AbstractLoadBalancerProduct loadBalancerProduct) {
this.loadBalancerProduct = loadBalancerProduct;
}
@Override
protected ILoadBalancer getILoadBalancer(LoadBalancerEntity loadBalancerEntity) {
ILoadBalancer loadBalancer = null;
try {
IRule rule = loadBalancerProduct.getRule(loadBalancerEntity);
List<Server> serverList = loadBalancerProduct.getServerList(loadBalancerEntity.getHttpRequestEntity().getConnectList());
//默认false,不开启,在子类中设置super.loadBalancerProduct.setIfPing(Boolean.TRUE);开启ping
boolean ifPing = loadBalancerProduct.ifPing();
if(!serverList.isEmpty() && !ifPing){
loadBalancer = LoadBalancerBuilder.newBuilder().withRule(rule).buildFixedServerListLoadBalancer(serverList);
}else{
loadBalancerProduct.getPingServerList(serverList);
if(!serverList.isEmpty()){
loadBalancer = LoadBalancerBuilder.newBuilder().withRule(rule).buildFixedServerListLoadBalancer(serverList);
}else{
throw new IllegalArgumentException("..............................");
}
}
}catch (Exception e){
e.printStackTrace();
}
return loadBalancer;
}
}
public abstract class BaseLoadBalancerProduct {
protected abstract IRule getRule(LoadBalancerEntity loadBalancerEntity);
protected abstract boolean getPing(Server server);
protected boolean ifPing(){
return false;
}
final protected List<Server> getServerList(List<String> connectList){
return LoadBalancerUtil.getLoadBalancerListServer(connectList);
}
}
public abstract class AbstractLoadBalancerProduct extends BaseLoadBalancerProduct {
private boolean ifPing = false;
final public void getPingServerList(List<Server> serverList){
if(ifPing() & !serverList.isEmpty()){
serverList.listIterator().forEachRemaining(server -> {
if(!getPing(server)){
serverList.remove(server);
}
});
}
}
@Override
public boolean getPing(Server server) {
PingUrl pingUrl = new PingUrl();
return pingUrl.isAlive(server);
}
@Override
public boolean ifPing() {
return this.ifPing;
}
public void setIfPing(boolean ifPing){
this.ifPing = ifPing;
}
}
调用方式
public static final String demoTestController = Version.V1_0_0 + Common.demoTestController;
@Slf4j
@Controller(DemoConstants.Controller.V1_0_0.demoTestController)
public class DemoTestController extends BaseDemoVersionController {
@Resource(name = DemoConstants.Service.V1_0_0.demoTestService)
private IDemoTestService demoTestService;
/**
* 通用HTTP调用入口
* TODO 此时apoll(request_template)中配置的connectTimeout、readTimeout、headerName、headerValue、contentType生效.
* TODO 如果VO对象和DTO对象很相似,那么可以在各个层次中只使用一个实体对象,在建立实体对象的时候把后缀VO或DTO去掉即可.
* @param vo
* @return
* @throws ServiceException
*/
@GetMapping(DemoConstants.Url.SEND)
@ResponseBody
public ResponseEntity<String> send(DemoTestVO vo) throws ServiceException {
//初始化VO对象
super.init(vo);
//VO与DTO转换
DemoTestDTO dto = new DemoTestDTO();
super.voConvertDto(vo,dto);
return new ResponseEntity<>(demoTestService.testXX(dto));
}
/**
* 自定义HTTP请求类型入口
* TODO 此时apoll(request_template)中配置的connectTimeout、readTimeout、headerName、headerValue、contentType不生效.
* TODO 如果VO对象和DTO对象很相似,那么可以在各个层次中只使用一个实体对象,在建立实体对象的时候把后缀VO或DTO去掉即可.
* @param httpServletRequest 自定义request对象,可以获取到所有的参数MAP和头MAP
* @param vo 接收请求参数
* @return
* @throws ServiceException
*/
@GetMapping(DemoConstants.Url.SEND2)
@ResponseBody
public ResponseEntity<String> send2(LocalHttpServletRequestWrapper httpServletRequest, DemoTestVO vo) throws ServiceException {
//初始化VO对象
super.init(vo);
log.info("--------------send2-ParameterMap---------{}",httpServletRequest.getParameterMap());
log.info("--------------send2-HeaderNamesMap---------{}",httpServletRequest.getHeaderNamesMap());
return new ResponseEntity<>(demoTestService.testXX2(vo));
}
}
public static final String baseDemoVersionController = Version.V1_0_0 + Common.baseDemoVersionController;
public static final String BASE_URL_1_0_0 = BaseConstants.SLASH + Flag.VALUE + BaseConstants.SLASH + Version.V1_0_0;
@Controller(DemoConstants.Controller.V1_0_0.baseDemoVersionController)
@RequestMapping(DemoConstants.Url.BASE_URL_1_0_0)
public class BaseDemoVersionController extends BaseDemoController {
}
public static final String demoTestService = Version.V1_0_0 + Common.demoTestService;
@Service(DemoConstants.Service.V1_0_0.demoTestService)
public class DemoTestServiceImpl implements IDemoTestService {
/**
* 通用HTTP调用入口
* @param dto
* @return
* @throws ServiceException
*/
@Override
public String testXX(DemoTestDTO dto) throws ServiceException {
String param = JSONObject.toJSONString(dto);
/**
* 参数一:请求URL的code值,对应apollo(server_version_url/server_version_url_mock)中的code值,必传.
* 参数二:请求URL的参数,object类型,建议固定类型,比如json字符串或字节流,可为空,不传"".
* 参数三:请求URL的响应类型,与接收类型保持一致,可为空,不传默认String.
*/
String response = RequestInvokeService.httpInvoke(ApolloConfigDemoConstants.ServerVersionUrl.testurl,param,String.class);
return response;
}
/**
* 自定义HTTP请求类型入口
* TODO 此时apoll(request_template)中配置的connectTimeout、readTimeout、headerName、headerValue、contentType不生效.
* @param vo
* @return
* @throws ServiceException
*/
@Override
public String testXX2(DemoTestVO vo) throws ServiceException {
String param = JSONObject.toJSONString(vo);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.valueOf(MediaType.APPLICATION_JSON_UTF8.toString()));
//httpHeaders.addAll();
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
//simpleClientHttpRequestFactory.setXX();
//方式一:自定义HttpHeaders和SimpleClientHttpRequestFactory
RequestInvokeCustomerService requestInvokeCustomerService1 = new RequestInvokeCustomerService(httpHeaders,simpleClientHttpRequestFactory);
/**
* 参数一:请求URL的code值,对应apollo(server_version_url/server_version_url_mock)中的code值,必传.
* 参数二:请求URL的参数,object类型,建议固定类型,比如json字符串或字节流,可为空,不传"".
* 参数三:请求URL的响应类型,与接收类型保持一致,可为空,不传默认String.
*/
return requestInvokeCustomerService1.httpInvoke(ApolloConfigDemoConstants.ServerVersionUrl.testurl,param,String.class);
//方式二:自定义HttpHeaders
//RequestInvokeCustomerService requestInvokeCustomerService2 = new RequestInvokeCustomerService(httpHeaders);
/**
* 参数一:请求URL的code值,对应apollo(server_version_url/server_version_url_mock)中的code值,必传.
* 参数二:请求URL的参数,object类型,建议固定类型,比如json字符串或字节流,可为空,不传"".
* 参数三:请求URL的响应类型,与接收类型保持一致,可为空,不传默认String.
*/
//return requestInvokeCustomerService2.httpInvoke(ApolloConfigDemoConstants.ServerVersionUrl.testurl,param,String.class);
//方式三:自定义SimpleClientHttpRequestFactory
//RequestInvokeCustomerService requestInvokeCustomerService3 = new RequestInvokeCustomerService(simpleClientHttpRequestFactory);
/**
* 参数一:请求URL的code值,对应apollo(server_version_url/server_version_url_mock)中的code值,必传.
* 参数二:请求URL的参数,object类型,建议固定类型,比如json字符串或字节流,可为空,不传"".
* 参数三:请求URL的响应类型,与接收类型保持一致,可为空,不传默认String.
*/
//return requestInvokeCustomerService3.httpInvoke(ApolloConfigDemoConstants.ServerVersionUrl.testurl,param,String.class);
}
}
/**
* @Description: 各种类型的通用调用入口
* TODO 现在只支持HTTP调用,如果以后要扩展RPC、MQ等,那么只扩展下述方法即可,调用方式和参数等都不用变.
*/
public final class RequestInvokeService {
private final static BaseLoadBalancerHttpRequestContext BALANCER_HTTP_REQUEST_CONTEXT = new LoadBalancerHttpRequestContext();
private RequestInvokeService(){}
/**
* HTTP调用通用入口
* @param url
* @param param
* @param clazz
* @param <T>
* @return
*/
public final static <T> T httpInvoke(String url,Object param,Class<T> clazz){
return BALANCER_HTTP_REQUEST_CONTEXT.process(url, param, clazz);
}
}
/**
* @Description: HTTP自定义请求参数调用入口
*/
public final class RequestInvokeCustomerService {
private BaseLoadBalancerHttpRequestContext BALANCER_HTTP_REQUEST_CONTEXT;
private RequestInvokeCustomerService(){}
/**
* 自定义配置请求实例化
* @param httpHeaders 自定义头信息
*/
public RequestInvokeCustomerService(HttpHeaders httpHeaders) {
BALANCER_HTTP_REQUEST_CONTEXT = new LoadBalancerHttpRequestContext(httpHeaders,null);
}
/**
* 自定义配置请求实例化
* @param simpleClientHttpRequestFactory 自定义请求工厂
*/
public RequestInvokeCustomerService(SimpleClientHttpRequestFactory simpleClientHttpRequestFactory) {
BALANCER_HTTP_REQUEST_CONTEXT = new LoadBalancerHttpRequestContext(null,simpleClientHttpRequestFactory);
}
/**
* 自定义配置请求实例化
* @param httpHeaders 自定义头信息
* @param simpleClientHttpRequestFactory 自定义请求工厂
*/
public RequestInvokeCustomerService(HttpHeaders httpHeaders,SimpleClientHttpRequestFactory simpleClientHttpRequestFactory) {
BALANCER_HTTP_REQUEST_CONTEXT = new LoadBalancerHttpRequestContext(httpHeaders,simpleClientHttpRequestFactory);
}
/**
* 调用入口
* @param url
* @param param
* @param clazz
* @param <T>
* @return
*/
public final <T> T httpInvoke(String url,Object param,Class<T> clazz){
return BALANCER_HTTP_REQUEST_CONTEXT.process(url, param, clazz);
}
}
环境说明
1)DEV
Development environment
开发环境,用于开发者调试使用
2)FAT
Feature Acceptance Test environment
功能验收测试环境,用于软件测试者测试使用
测试环境,相当于 alpha 环境(功能测试).
3)UAT
User Acceptance Test environment
用户验收测试环境,用于生产环境下的软件测试者测试使用
集成环境,相当于 beta 环境(回归测试)
4)PRO
Production environment
生产环境
#无论是项目配置、项目编译、项目运行、脚本配置、apollo 配置等,所有的环境都必须按照上方的内容保持一致。
思考点列表
1)外网到内网的安全问题。
2)如何通过数据模拟工具来适配不同的业务流程。
3)如何降低应用层和服务层的联调时间。
4)传输数据的安全处理。
5)确认哪些接口通过应用层接入,哪些接口通过服务层接入,哪些接口需要做特殊处理,特殊处理在哪做及怎么做的问题。
6)接口版本的定义。每个版本引入父类 controller,统一继承,统一声明。
7)错误码的定义。由于应用层要接入很多服务层,还包括第三方的,各个服务层的响应码定义规则都不一样,那么只要服务层响应码不是 200,那么应用层统一用比如 10001 接收,并最终响应给客户端。
8)controller 中定义的每个方法,参数尽可能的使用 VO 来接收,如果内部逻辑处理比较复杂,那么需要进一步的转成 DTO 对象。
9)URL 定义要遵循 RESTFul 规范。
10)controller 中定义的每个方法,参数对象中的字段需要做自定义校验处理,这里引入@Valid 和@ValidGroup 来适配各种字段校验处理。
11)可以利用 swagger 和数据模拟工具结合使用来测试接口的稳定性。
12)在引入多版本后,每个 controller、service 等类名是一样的,只是路径以及注入 Spring 时候的名称是不一样的。所有的名称统一定义和管理。
13)apollo 配置中心以模板的请示来动态配置 HTTP 和负载均衡相关属性。
14)apollo 中每个 namespace 的属性配置要针对一个实体,每个 namespace 尽可能把公共属性抽离出来作为父类实体,子类实体用来扩展不同的 namespace。
15)过滤器没有使用责任链模式,因为服务版本过多,会导致过滤器性能下降,而且调试也会很困难,所以直接把所有版本的过滤器放入 MAP 中,用版本号过滤相应的过滤器即可,不同版本过滤的公共方法抽离出即可。
16)为了对 HttpServletRequest 中的请求参数进行后续处理,对 HttpServletRequestWrapper 进行了重写,在过滤器中的 ServletRequest/HttpServletRequest 对象转为
HttpServletRequestWrapper 的子类 LocalHttpServletRequestWrapper 对象。
17)在此版本控制功能中,可以发现应用层中的同一个服务,只有版本号不一样,其实请求链接路径是一样的,在处理的过程中,让客户端把版本号放在 HTTP 请求头中,通过 apollo 中配置的版本映射找到真正的调用链接,把版本号和链接重新拼装,重定向到真正的版本服务即可。
这样做有几个好处,一是 apollo 可以统一管理,并且是动态配置,实时生效,二是确保 URL 请求的相对安全,三是确保服务链接的唯一性,变化的只有版本号。
18)在此版本控制功能中,可以发现服务层的配置中是没有版本号的,因为在应用层中调用的服务的规范并不能确保是一致的,所以直接配置真正的调用链接即可,内网间的调用可根据业务的划分,”忽略”部分的安全处理,如果要调用第三方链接,遵循第三方的调用配置,也是直接调用即可。
19)要利用过滤器和拦截器的一些特性,把无效链接的一些错误行为与参数拦截到执行业务之前,降低垃圾信息给应用层及后续调用服务的影响。
20)个别的服务需要在调用之前 ping 一下。
21)在应用层需要加入网络攻击的处理,这也可以放到 nginx 中通过 LUA 等脚本来处理。
22)关于流量控制,可以在应用层加入 sentinel,sentinel 可以从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
23)加入 SQL 攻击处理。
24)防跨站脚本攻击。
25)针对不同的中间件,设置不同的日志级别输出方式。
26)在 client_version_url 中配置了 isMock 字段,来控制应用层调用服务层的数据是否是模拟数据,0 正常调用服务,1 调用模拟接口平台。
这个 isMock 字段没有配置在 server_version_url 或 server_version_url_mock 中,起初的规划是 server_version_url 或 server_version_url_mock 中配置,在应用层中维护两套代码,一套是正常调用服务的,另一套是调用模拟接口平台,后来屏蔽了这套方案,因为无论是正常调用服务,还是调用模拟接口平台,那么二者的数据流转和业务逻辑应该是一样的才对,虽然在服务层没开发完成时,调用模拟接口平台,但最终二者需要不断磨合才能达到最终的数据流转和业务逻辑的一致性。
27)在 client_version_url/server_version_url/server_version_url_mock 配置的链接中,不同的环境需要配置不同的域名以及相关 http 参数,起初设计是把域名和链接配置在一起,http 参数也一样,但是在之后的对接中发现一些问题,
一是环境过多的话,那么配置的所有链接都要改一遍,工作量太大。
二是开发人员相对来说不需要关注最终的域名配置和 HTTP 参数(比如不用的应用负载配置值不一样,不用太纠结这个精确值,配置一个范围值即可,一般来说,波动不会太大的)。
最终,是把域名和 HTTP 参数配置在不同的 namespace,二者作为模板,在调用的不同 URL 直接配置相应的模板 KEY 即可,这样无论环境怎么切换,只需改这两个模板即可,而且模板数毕竟很少,在大多 URL 中都是共用的。
28)apollo 的版本切换功能和服务调用功能可打成 jar 包提供给各个应用层直接使用。
29)应用层调用服务层的入口,基类为 BaseRequestContext。虽然现在只支持 HTTP/HTTPS,但是这里是为以后适配 RPC、MQ 留出了可扩展口。比如现在的 HTTP 和 HTTPS 调用 BaseLoadBalancerHttpRequestContext 基类,在以后的 RPC、MQ 也可以像类似方式进行扩展。当此类调用类型多了之后,加入不同的适配器在应用层选择性调用即可。
30)在 BaseLoadBalancerHttpRequestContext 实现的 procecss 方法中,发现组装了 AssembleHttpRequestInvokeEntity 和 AssembleHttpResponseInvokeEntity 对象,由于 LoadBalancerHttpRequestContext 在 invoke 方法调用中,需要根据调用类型 HTTP/HTTPS/LOADBALANCER 判断调用的方式,并且需要传入 HttpRequestEntity 和 HttpResponseEntity,所以在 AssembleHttpRequestInvokeEntity 和 AssembleHttpResponseInvokeEntity 重新组装/组合了相关请求和响应对象,做到对象统一处理,且不会出现过多的冗余对象逻辑。
31)在 ApolloRelationConfigContext 执行了 apollo 的初始化操作,为防止多次初始化问题,加入全局布尔值变量来处理。
32)BaseApolloConfigManager 配置了监听 apollo 的公共方法,子类重写,可以发现不同的 apollo-namespace 来处理各自监听的 MAP 对象,而不是把所有的 MAP 对象都交给父类 BaseApolloConfigManager 来处理。
33)可以发现在组装负载均衡对象中用到了建造者模式,每个负载均衡策略均由一个 build 对象和一个 product 对象,在 LoadBalancerRandomBuilder 中可以设置是否开启调用服务时的是否 ping 策略。
34)HttpRequestUnusualMethodImpl 和 HttpRequestMethodImpl 为调用 HTTP 不同方法的实现类,HttpRequestUnusualMethodImpl 为不常用方法的实现,HttpRequestMethodImpl 为常用方法的实现,HttpRequestRestTemplate 组装了 HttpRequestEntity 和 HttpResponseEntity 的对象,可以发现通过这一个对象的组装,减轻了大量 HTTP 方法的重写。
35)在开发的过程中,发现并不是所有代码都要写成动态的,如果这样的话,那么容易陷入设计误区。