>> '<搭建说明>:'
> '使用的组件包括: Eureka、Ribbon、Config、Zuul、Hystrix 完成一个用户信息管理小服务。'
> '后端框架包括:注册中心Eureka、配置中心 Config API网关Zuul、客户端负载均衡Ribbon、断路器Hystrix;'
> '同时后端包含两个业务服务,一个是用户服务sc-user-server,一个是数据服务sc-data-server'
>> 技术方案实现流程图间 图-1
> 1)'用户从浏览器发起请求',经过浏览器,请求达到Nginx,
> 2)打开前端界面,由前端发起请求后台数据,
> 3)当请求达到Nginx后,'Nginx对网关层进行负载',因为网关也需要做'HA'(HA是什么?底部有注脚定义),
> 4)此时网关收到请求后会根据请求路径进行'动态路由',
> 5)根据'服务名发现'是 UserService中的服务,则从Ribbon中选择一台UsrService的实例进行调用
> 6)由UserServer返回数据,如果'此时UserService需要使用第三方DataService返回数据',
> 7)'则跟Zuul一样,选择一台DataService的实例进行调用',返回数据到前台即可渲染页面
> 8)流程结束🔚
-HA(Highly Available)(双机集群(HA)系统简称),高可用性集群,是保证业务连续性的有效解决方案,
-一般有两个或两个以上的节点,且分为活动节点及备用节点。
工程名 | 端口 | 描述 |
---|---|---|
server | N/A | 父工程 |
config-server | 9090 | 配置中心 |
eureka-server | 8761 | 注册中心 |
zuul-server | 7777 | API GateWay 网关 |
hystrix-dashboard | 9099 | 仪表盘& Turbine(风机)聚合Hystrix 断路器 -整合整个集群下的监控状态 |
common | N/A | 公共基础包,方便后台服务引用 |
user-server | 9091 | 用户服务,对用户数据的操作 |
data-server | 8099 | API 数据服务,提供基础的数据 |
<!-父工程-server->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
> config-server |9090| 配置中心:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
server:
port: 9090
spring:
cloud:
config:
server:
git:
uri: #配置文件远程地址URL
#username:
#password:
search-paths: SC-CONFIG
application:
name: sc-configserver
/*** @description : 配置中心 */
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);}}
> eureka-server | 8761| 注册中心:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
server:
port: 8761
spring:
application:
name: sc-eurekaserver
eureka:
client:
registerWithEureka: false
fetchRegistry: false
/*** eureka server*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);}}
> zuul-server | 7777| API GateWay 网关:
>> <Zuul中的Fallback机制:>
> 在微服务应用本身发生问题后,Zuul提供类一个Fallback机制,可以在出现问题时候进行统一处理,
> 需要实现FallbackProvider接口,然后定义自己需要的错误码和错误信息即可。
> -: 实现FallbackProvider接口,加上相应的处理逻辑,在Zuul-Fallback.java 该类中。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>cn.springcloud.book</groupId>
<artifactId>common</artifactId>
<version>${parent.version}</version>
</dependency>
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
instance:
prefer-ip-address: true
management:
security:
enabled: false
endpoints:
web:
exposure:
include: hystrix.stream
feign:
hystrix:
enabled: true
ribbon:
ConnectTimeout: 6000
ReadTimeout: 6000
MaxAutoRetries: 0 #对第一次请求的服务的重试次数
MaxAutoRetriesNextServer: 0 #要重试的下一个服务的最大数量(不包括第一个服务)
OkToRetryOnAllOperations: false
zuul:
ribbonIsolationStrategy: THREAD
threadPool:
useSeparateThreadPools: true
threadPoolKeyPrefix: zuulgateway
max:
host:
max-per-route-connections: 200
max-total-connections: 500
host:
socket-timeout-millis: 5000
connect-timeout-millis: 10000
hystrix:
threadpool:
default:
coreSize: 20
maximumSize: 50
maxQueueSize: -1
allowMaximumSizeToDivergeFromCoreSize: true
command:
default:
execution:
timeout:
enabled: false
isolation:
thread:
interruptOnTimeout: false
timeoutInMilliseconds: 15000
spring:
application:
name: sc-zuul-server
server:
port: 7777
> 从网关正确的访问http://localhost:7777/sc-user/service/getContextUserId 这个地址,此时发现报错:
> 这是自定义类一个异常,没有传用户信息,因为这里在网关做了拦截,
> 如果请求头里没有x-customs-user 则鉴权不通过,
> 代码如下:AuthFilter
/*** 鉴权filter*/
public class AuthFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
@Override
public boolean shouldFilter() {// 判断是否需要进行处理return true;}
@Override
public Object run() {
RequestContext rc = RequestContext.getCurrentContext();
authUser(rc);return null;}
@Override
public String filterType() {return "pre";}
@Override
public int filterOrder() {return 0;}
private static Map<String, String> httpRequestToMap(HttpServletRequest request) {
Enumeration<String> headerNames = request.getHeaderNames();
Map<String, String> headers = new HashMap<>();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, request.getHeader(headerName));
}return headers;}
public static void authUser(RequestContext ctx) {
HttpServletRequest request = ctx.getRequest();
Map<String, String> header = httpRequestToMap(request);
String userId = header.get(User.CONTEXT_KEY_USERID);
if(StringUtils.isEmpty(userId)) {
try {
BaseException BaseException = new
BaseException(CommonError.AUTH_EMPTY_ERROR.getCode(),CommonError.
AUTH_EMPTY_ERROR.getCodeEn(),CommonError.
AUTH_EMPTY_ERROR.getMessage(),1L);
BaseExceptionBody errorBody = new BaseExceptionBody(BaseException);
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.setResponseBody(JSONObject.toJSON(errorBody).toString());
} catch (Exception e) {logger.error("println message error",e);}
}else {
for (Map.Entry<String, String> entry : header.entrySet()) {
ctx.addZuulRequestHeader(entry.getKey(), entry.getValue());}}}
@Component
public class ZuulFallback implements FallbackProvider{
@Override
public String getRoute() {
return "*"; //可以配置指定的路由,值为serviceId,如sc-user-service}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR;}
@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();}
@Override
public void close() {}
@Override
public InputStream getBody() throws IOException {
//定义自己的错误信息
return new ByteArrayInputStream(("microservice error").getBytes()); }
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers; }
@Override
public int getRawStatusCode() throws IOException {
// TODO Auto-generated method stub
return HttpStatus.INTERNAL_SERVER_ERROR.value();}};}}
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@EnableCircuitBreaker
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class, args);}
@Bean
public AuthFilter preRequestFilter() {return new AuthFilter();}}
>hystrix-dashboard | 9099| 仪表盘& Turbine(风机)聚合Hystrix 断路器 -整合整个集群下的监控状态:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
instance:
prefer-ip-address: true
management:
security:
enabled: false
endpoints:
web:
exposure:
include: hystrix.stream
turbine:
appConfig: sc-user-service,sc-zuul-service,sc-data-service
clusterNameExpression: "'default'"
server:
port: 9099
spring:
cloud:
config:
label: master
uri: http://localhost:9090
name: config-info
profile: dev
application:
name: sc-hystrix-dashboard
@SpringBootApplication
@EnableDiscoveryClient
@EnableTurbine
@EnableHystrixDashboard
public class HystrixDashboardTurbineApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardTurbineApplication.class, args);}}
> common | N/A| 公共基础包,方便后台服务引用:
>> '公共包(对象,拦截器,工具类等)'
> 框架一般会有一些值对象,拦截器,分页对象,权限等这些基础数据,
> 并且其它服务都是需要共有的能力的,所以会抽出这部分对象放入公共包里,
> 供其它服务引用。建立common的工程.
> 如 user-service 和 data-service 都会用到它里面一些组件和类。
> 该包里存有/存放的类或东西可以有如下几部分:
> 1)'用户上下文对象传递',
> 1-1: 如用户对象,从Zuul网关一直到后面的微服务都需要用户对象
> 1-2: 后面的微服务需要获取到用户ID后开展非一些业务操作,比如:
> 1-2-1:在 Zuul获取到用户信息,存入Header头,后台服务进入方法前,'获取到Header进行组装User用户对象',
> 1-2-1:'后台服务通过UserContextHolder获取'。
> 1-2-2:在后台服务之间相互调用时,增加拦截器,获取当前用户然后转换成Header放入请求头,
> 1-2-3:被'调用服务拦截器后解析到Header放入上下文',服务通过UserContextHolder获取。
> 1-3:具体实现步骤:
> 1-3-1:一呢,在公共SDK里定义用户对象User。
> 1-3-2:二呢,增加三个拦截器。
> 1-3-3:FeignUserContextInterceptor类中,在使用Feign进行服务间调用时会拦截到请求,
> 并将用户属性放到Header里,代码在类内。
> RestTemplateUserContextInterceptor类,在使用使用RestTemplate进行服务之间调用时,会拦截到请求
> 并将用户属性放到Header里,该类中有具体实现步骤。
> UserContexttInterceptor类,进入controller控制器时,会拦截到请求,
> 从Header头解析出用户对象存入上下文中,方便服务里使用,具体实现代码查看该类。
> 1-3-4:三呢,增加UserContextHolder类。
> 1-3-5: Hystrix并发策略,查看SpringCloudHystrixConcurrencyStrategy类具体实现。
> 此类使用了 'ThreadLocal 对象'保存用户信息,由于在线线程池隔离的模式下,会导致前后线程传递对象丢失,
> 该类中,使用自定义并发策略 HystrixConcurrencyStrategy解决此问题,具体方法看该类的实现。
> 1-3-6:在配置类中注册三个拦截器和并发策略,代码看CommonConfiguration.java 类。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
cn.springcloud.book.common.config.CommonConfiguration
/*** 用户对象 **/
public class User implements Serializable {
private static final long serialVersionUID = -4083327605430665846L;
public final static String CONTEXT_KEY_USERID = "x-customs-user";
/** * 用户ID*/
private String userId;
private String userName;
public String getUserName() {return userName;}
public void setUserName(String userName) {this.userName = userName;}
public String getUserId() {return userId;}
public void setUserId(String userId) {this.userId = userId;}
public User() {}
public User(Map<String, String> headers) {
userId = headers.get(CONTEXT_KEY_USERID);}
/*** 将user对象转换成为http对象头* @return http头键值对*/
public Map<String, String> toHttpHeaders() {
Map<String, String> headers = new HashMap<>();
headers.put(CONTEXT_KEY_USERID,userId);
return headers;}}
public class HttpConvertUtil {
/*** convert the httpServletRequest headers to headers map
* @param request* @return*/
public static Map<String, String> httpRequestToMap(HttpServletRequest request) {
Enumeration<String> headerNames = request.getHeaderNames();
Map<String, String> headers = new HashMap<>();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, request.getHeader(headerName));
}return headers; }}
public class ExceptionUtil {
/*** 异常枚举转类型换为英文code
* @param error 异常枚举
* @return 大驼峰编码*/
public static String errorToCodeEN(Enum<?> error) {
String errorName = error.name().toLowerCase();
String[] sp = errorName.split("_");
StringBuffer code = new StringBuffer();
for (String s : sp) {
code.append(StringUtils.capitalize(s));
}return code.toString();}}
public class AuthUtil {
public static boolean authUser(User user, HttpServletResponse respone)
throws Exception{
if(StringUtils.isEmpty(user.getUserId())) {
return false;}else {return true;}}}
> 观察UserController类中的三个Controller层类提供接口,并且顺序运行:
> eureka-server(服务器)、zuul-server(服务器) data-service(服务) user-service(服务)
> 成功后浏览器访问http://localhost:9091/getContextUserId,
> 发现页面空白,控制台打印"the user is null,please access from gateway or chexk usr info."
> 说明拦截器起到作用,对于/没有用户信息这样不合法对请求进行了拦截。
> 代码:(UserContextInterceptor.java -定义的公共基础包中) 类中所示。
public class UserContextInterceptor implements HandlerInterceptor {
private static final Logger log =
LoggerFactory.getLogger(UserContextInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse respone, Object arg2) throws Exception {
User user = new User(HttpConvertUtil.httpRequestToMap(request));
if(StringUtils.isEmpty(user.getUserId()) && StringUtils.isEmpty(user.getUserName())) {
log.error("the user is null, please access from gateway or check user info");
return false;}
UserContextHolder.set(user);return true;}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse respone,
Object arg2, ModelAndView arg3)
throws Exception {// DOING NOTHING}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse
respone, Object arg2, Exception arg3)
throws Exception { UserContextHolder.shutdown();}}
/**
* RestTemplate传递用户上下文*/
public class RestTemplateUserContextInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution)
throws IOException {
User user = UserContextHolder.currentUser();
Map<String, String> headers = user.toHttpHeaders();
for (Map.Entry<String, String> header : headers.entrySet()) {
request.getHeaders().add(header.getKey(), header.getValue());}
// 调用
return execution.execute(request, body);}}
/*** Feign传递用户上下文 */
public class FeignUserContextInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// User user = UserContextHolder.currentUser();
ServletRequestAttributes attributes = (ServletRequestAttributes)
RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
String values = request.getHeader(name);
template.header(name, values);}}}}
/** * 通用异常信息 */
public enum CommonError {
/*** 1001, "用户信息为空" */
AUTH_EMPTY_ERROR(10001, "the user is null, please check");
private Integer code;
private String message;
CommonError(Integer code, String message) {
this.code = code;this.message = message;}
public Integer getCode() {return code;}
public void setCode(Integer code) {
this.code = code;}
public String getMessage() {return message;}
public void setMessage(String message) {this.message = message;}
public String getCodeEn() {return ExceptionUtil.errorToCodeEN(this);}}
public class BaseExceptionBody implements Serializable {
/*** serialVersionUID*/
private static final long serialVersionUID = -1270478894426234738L;
/*** 相关业务ID */
private Long businessId;
/*** 异常编码:数字*/
private Integer code;
/*** 异常编码:英文短语*/
private String codeEN;
/*** 异常信息*/
private String businessMessage;
/** * 异常类型*/
private String exceptionType;
public BaseExceptionBody(BaseException exception) {
this.businessId = exception.getBusinessId();
this.code = exception.getCode();
this.codeEN = exception.getCodeEN();
this.businessMessage = exception.getMessage();
this.exceptionType = exception.getClass().getName();}}
public class BaseException extends RuntimeException {
private static final long serialVersionUID = 1796731834836398434L;
private Long businessId;
private Integer code;
private String codeEN;
private String businessMessage;
/** * 基础异常* * @param code 异常编码:数字
* @param codeEN 异常编码:英文短语 * @param message 异常信息
* @param businessId 相关业务ID*/
public BaseException(Integer code, String codeEN, String message, Long businessId)
{ this(code, codeEN, message, businessId, null);}
public BaseException(Integer code, String codeEN, String message,
Long businessId, Throwable t) {
super(message, t);
this.businessId = businessId;
this.code = code;
this.codeEN = codeEN;
this.businessMessage = message;}}
/*** 用户上下文*/
public class UserContextHolder {
public static ThreadLocal<User> context = new ThreadLocal<User>();
public static User currentUser() {return context.get();}
public static void set(User user) {context.set(user);}
public static void shutdown() {context.remove();}}
/*** Spring上下文管理工具 */
@Component
public class SpringContextManager implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
SpringContextManager.applicationContext = applicationContext;}
/*** 获取上下文* * @return Spring上下文 */
public static ApplicationContext getApplicationContext() {
return applicationContext;}
/** * 获取Spring配置 * @param key 配置名称 * @return 配置值*/
public static String getProperties(String key) {
return applicationContext.getEnvironment().getProperty(key);}
/** * 获取Spring配置<br> * 没有配置时,返回默认值* @param key 配置名称
* @param defaultValue 默认值 * @return 配置值*/
public static String getProperties(String key, String defaultValue) {
return applicationContext.getEnvironment().getProperty(key, defaultValue);}}
public class SpringCloudHystrixConcurrencyStrategy extends HystrixConcurrencyStrategy {
private HystrixConcurrencyStrategy delegateHystrixConcurrencyStrategy;
@Override
public <T> Callable<T> wrapCallable(Callable<T> callable) {
return new HystrixThreadCallable<>(callable,
RequestContextHolder.getRequestAttributes(),
HystrixThreadLocal.threadLocal.get()); }
public SpringCloudHystrixConcurrencyStrategy() {init();}
private void init() {
try {
this.delegateHystrixConcurrencyStrategy =
HystrixPlugins.getInstance().getConcurrencyStrategy();
if (this.delegateHystrixConcurrencyStrategy instanceof
SpringCloudHystrixConcurrencyStrategy) { return;}
HystrixCommandExecutionHook commandExecutionHook =
HystrixPlugins.getInstance().getCommandExecutionHook();
HystrixEventNotifier eventNotifier =
HystrixPlugins.getInstance().getEventNotifier();
HystrixMetricsPublisher metricsPublisher =
HystrixPlugins.getInstance().getMetricsPublisher();
HystrixPropertiesStrategy propertiesStrategy =
HystrixPlugins.getInstance().getPropertiesStrategy();
HystrixPlugins.reset();
HystrixPlugins.getInstance().registerConcurrencyStrategy(this);
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);}
catch (Exception e) {throw e;}}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixProperty<Integer> corePoolSize,
HystrixProperty<Integer> maximumPoolSize,
HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
return this.delegateHystrixConcurrencyStrategy.getThreadPool(
threadPoolKey, corePoolSize, maximumPoolSize,
keepAliveTime, unit, workQueue);}
@Override
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
HystrixThreadPoolProperties threadPoolProperties) {
return this.delegateHystrixConcurrencyStrategy.getThreadPool(
threadPoolKey, threadPoolProperties); }
@Override
public BlockingQueue<Runnable> getBlockingQueue(int maxQueueSize) {
return this.delegateHystrixConcurrencyStrategy.getBlockingQueue(maxQueueSize);}
@Override
public <T> HystrixRequestVariable<T> getRequestVariable(
HystrixRequestVariableLifecycle<T> rv) {
return this.delegateHystrixConcurrencyStrategy.getRequestVariable(rv);}}
public class HystrixThreadLocal {
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();}
public class HystrixThreadCallable<S> implements Callable<S>{
private final RequestAttributes requestAttributes;
private final Callable<S> delegate;
private String params;
public HystrixThreadCallable(Callable<S> callable, RequestAttributes
requestAttributes,String params) {
this.delegate = callable;
this.requestAttributes = requestAttributes;
this.params = params; }
@Override
public S call() throws Exception {
try { RequestContextHolder.setRequestAttributes(requestAttributes);
HystrixThreadLocal.threadLocal.set(params);
return delegate.call(); } finally {
RequestContextHolder.resetRequestAttributes();
HystrixThreadLocal.threadLocal.remove();} } }
@Configuration
@EnableWebMvc
public class CommonConfiguration extends WebMvcConfigurerAdapter{
/*** 请求拦截器*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserContextInterceptor());}
/*** 创建Feign请求拦截器,在发送请求前设置认证的用户上下文信息*/
@Bean
@ConditionalOnClass(Feign.class)
public FeignUserContextInterceptor feignTokenInterceptor() {
return new FeignUserContextInterceptor();}
/*** RestTemplate拦截器 * @return */
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.getInterceptors().add(new RestTemplateUserContextInterceptor());
return restTemplate;}
@Bean
public SpringCloudHystrixConcurrencyStrategy springCloudHystrixConcurrencyStrategy()
{return new SpringCloudHystrixConcurrencyStrategy();}}
> user-server | 9091| 用户服务,对用户数据的操作:
<dependency>
<groupId>cn.springcloud.book</groupId>
<artifactId>common</artifactId>
<version>${parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
instance:
prefer-ip-address: true
management:
security:
enabled: false
endpoints:
web:
exposure:
include: hystrix.stream
feign:
hystrix:
enabled: true
ribbon:
ConnectTimeout: 6000
ReadTimeout: 6000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 0
hystrix:
command:
default:
execution:
timeout:
isolation:
thread:
timeoutInMilliseconds: 15000
server:
port: 9091
spring:
cloud:
config:
label: master
uri: http://localhost:9090
name: config-info
profile: dev
application:
name: sc-user-service
public interface IUserService {
public String getDefaultUser();
public String getContextUserId();
public List<String> getProviderData();}
@Component
public class UserService implements IUserService{
@Autowired
private DataService dataService;
@Autowired
private RestTemplate restTemplate;
@Override
public String getDefaultUser() {return dataService.getDefaultUser();}
@Override
public String getContextUserId() {return dataService.getContextUserId();}
@Override
public List<String> getProviderData() {
List<String> result = restTemplate.getForObject
("http://sc-data-service/getProviderData", List.class);
return result;}}
@Component
public class UserClientFallback implements DataService{
@Override
public String getDefaultUser() {return new String("get getDefaultUser failed");}
@Override
public String getContextUserId() {return new String("get getContextUserId failed");}}
/*** feign调用数据服务*/
@FeignClient(name = "sc-data-service", fallback=UserClientFallback.class)
public interface DataService {
@RequestMapping(value = "/getDefaultUser", method = RequestMethod.GET)
public String getDefaultUser();
@RequestMapping(value = "/getContextUserId", method = RequestMethod.GET)
public String getContextUserId();}
@RestController
public class UserController {
@Autowired
private IUserService userService;
/*** 获取配置文件中系统默认用户* @return*/
@GetMapping("/getDefaultUser")
public String getDefaultUser(){
return userService.getDefaultUser();}
/*** 获取上下文用户* @return */
@GetMapping("/getContextUserId")
public String getContextUserId(){
return userService.getContextUserId();}
/*** 获取供应商数据*/
@GetMapping("/getProviderData")
public List<String> getProviderData(){
return userService.getProviderData();}}
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@EnableCircuitBreaker
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args); }}
> data-server | 8099| API 数据服务,提供基础的数据:
<dependency>
<groupId>cn.springcloud.book</groupId>
<artifactId>common</artifactId>
<version>${parent.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
spring:
application:
name: sc-data-service
eureka:
client:
serviceUrl:
defaultZone: http://${eureka.host:127.0.0.1}:${eureka.port:8761}/eureka/
instance:
prefer-ip-address: true
management:
security:
enabled: false
endpoints:
web:
exposure:
include: hystrix.stream
server:
port: 8099
spring:
cloud:
config:
label: master
uri: http://localhost:9090
name: config-info
profile: dev
application:
name: sc-data-service
/*** @description : 配置信息*/
@Component
@ConfigurationProperties(prefix = "远程服务路径")
public class DataConfig {
private String defaultUser;
public String getDefaultUser() {return defaultUser;}
public void setDefaultUser(String defaultUser) {
this.defaultUser = defaultUser;}}
@RestController
public class DataController {
@Autowired
private DataConfig dataConfig;
@GetMapping("/getContextUserId")
public String getContextUserId(){
return UserContextHolder.currentUser().getUserId();}
@GetMapping("/getDefaultUser")
public String getDefaultUser(){
return dataConfig.getDefaultUser();}
@GetMapping("/getProviderData")
public List<String> getProviderData(){
List<String> provider = new ArrayList<String>();
provider.add("Beijing Company");
provider.add("Shanghai Company");
provider.add("Shenzhen Company");
return provider;}}
/*** 数据服务* */
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class DataServiceApplication {
public static void main(String[] args) {
SpringApplication.run(DataServiceApplication.class, args);}}
=====================================================================================================
最后,给点进来的’码’同志,提供一份参考配置生产环境,简单模版供大家参考:
1) Eureka 服务端配置参考:
server:
port: 8761
spring:
appliction:
name: eurcka-server
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:8761/eureka, http://127.0.0.1:8761/eureka
instance:
prefer-ip-address: true
server: enable-self-preser
vation: false
eviction-interval-timer-in-ms: 30000
Eureka属性描述表格服务端:
2) Eureka 客户端配置参考:
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:8761/eureka, http://127.0.0.2:eureka/
instance:
prefer-ip-address: true
3) Ribbon 配置参考:
Ribbon的配置,一般都配置全局的,也可以配置为单个服务的,这里列了个全局的。
–对于是否重试,在真实项目中观察,由于幂等性或网络不稳定等原因导致易出问题,默认都不进行重试,
如果对于一些查询比较多的服务可以开启重试,根据具体项目来定义,
具体的配置可以看源码的 DefaultClientConfigImpl类.
ribbon:
ConnectTimeout: 2000 #全局请求连接的超时时间,默认2秒
ReadTimeout: 5000 #全局请求的超时时间,默认5秒
MaxAutoRetries: 0 #对当前实例的重试次数
MaxAutoRetriesNextServer: 0 #切换下一个实例重试次数
OkToRetryOnAllOperations: false #对所有操作请求都进行重试
4) Hystrix 配置参考:
Hystrix断路器的配置,源代码类 HystrixCommandProperties
通常全局请求连接超时时间推荐设置为10秒,如果请求需要的时间过长则根据你的请求进行修改或
单独时间,如果要设置线程池大小,可以看网关的配置如下:
hystrix.command.default.exection.isonlation.thread.timeoutlnMilliseconds
hystreix:
command:
default:
execution:
isolation:
thread:
timeoutlnMilliseconds: 10000 #全局请求连接的超时时间默认为1秒
5) Zuul 网关 配置参考:
zuul:
ribbonlsolationStrategy: THREAD
threadPool:
userSeparateThreadPools: true
threadPoolKeyPrefix: zuulgateway
host:
max-per-route-connections: 50
max-total-connections: 300
socket-timeout-millis: 5000
connect-timeout-millis: 5000
hystrix:
threadpool:
default:
coreSize: 20
maximunmSize: 50
allowMaximumSizeToDivergeFromCoreSize: true
command:
default:
execution:
isolation:
thread:
timeoutlnMilliseconds: 10000
Zuul的属性描述表: