SpringCloud是一系列框架的有序集合。它基于SpringBoot的便利性融合了一整套实现微服务的框架并提供了服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等组件。
SpringCloud的基础功能:
服务治理: Spring Cloud Eureka
客户端负载均衡: Spring Cloud Ribbon
服务容错保护: Spring Cloud Hystrix
声明式服务调用: Spring Cloud FeignAPI
网关服务:Spring Cloud Zuul
高级功能:
消息总线: Spring Cloud Bus
消息驱动的微服务: Spring Cloud Stream
分布式服务跟踪: Spring Cloud Sleuth
4.0.0
com.yin
spring-cloud-example
pom
1.0-SNAPSHOT
eureka
provider01
provider02
consumer01
feign
hystrix_dashboard
zuul
1.8
Greenwich.SR1
2.1.3.RELEASE
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-dependencies
${springboot.version}
pom
import
4.0.0
com.yin
eureka
0.0.1-SNAPSHOT
eureka
Demo project for Spring Boot
com.yin
spring-cloud-example
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
1.4.2.RELEASE
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
#服务器端口
server:
port: 10000
#工程名(在注册中心的页面看到的名字)
spring:
application:
name: eureka
eureka:
instance:
#服务注册中心实例的主机名
hostname: localhost
server:
#关闭自我保护机制,防止失效的服务也被一直访问 (开发环境)
enable-self-preservation: false
#该配置可以修改检查失效服务的时间,每隔5s检查失效服务,默认该配置是 60s (开发环境)
eviction-interval-timer-in-ms: 5000
client:
#是否检索服务, #是否需要将自己注册到注册中心,
#因为该工程自己就是服务注册中心,所以无需注册。如果是多个服务注册中心集群模式,则另当别论
fetch-registry: false
#是否注册到eureka
register-with-eureka: false
#服务器暴露注册的地址
service-url:
defaultZone: http://localhost:10000/eureka/
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
由于eureka 启动的端口是10000,故输入http://localhost:10000/ 查看启动情况
4.0.0
com.yin
spring-cloud-example
1.0-SNAPSHOT
com.yin
provider01
0.0.1-SNAPSHOT
provider01
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.projectlombok
lombok
1.16.10
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
为了后面测试负载均衡,这里需要提供两个服务提供者,端口号分别是8001和8002
#端口
server:
port: 8001
#应用名
spring:
application:
name: hello-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10000/eureka/
register-with-eureka: true
fetch-registry: true
#healthcheck:
#enabled: true #开启自定义健康检查
instance:
#eureka服务器在接收到最后一个心跳之后等待的时间,然后才能从列表中删除此实例 默认90s
lease-expiration-duration-in-seconds: 10
#eureka客户端需要向eureka服务器发送心跳的频率 默认30s
lease-renewal-interval-in-seconds: 1
# ip地址
#ip-address: 固定ip地址
#instance-id: ${spring.cloud.client.ip-address}:${server.port}
#preferIpAddress: true
@SpringBootApplication
@EnableDiscoveryClient
@EnableEurekaClient
public class Provider01Application {
public static void main(String[] args) {
SpringApplication.run(Provider01Application.class, args);
}
}
1、服务提供者01的controller
@RestController
public class ProviderController {
@RequestMapping("/test")
public String test(@RequestParam("id") Integer id) {
if (id % 2 != 0) {
throw new RuntimeException("运行错误");
}
return "provider01 数据提供";
}
@PostMapping("/getUser")
public UserVo getUser(@RequestBody UserVo userVo) {
userVo.setUserName(userVo.getUserName()+"provider01的处理");
return userVo;
}
}
2、服务提供者02的controller
@RestController
public class ProviderController {
@RequestMapping("/test")
public String test(@RequestParam("id")Integer id) {
return "provider02 数据提供";
}
@PostMapping("/getUser")
public UserVo getUser(@RequestBody UserVo userVo) {
userVo.setUserName(userVo.getUserName()+"provider02的处理");
return userVo;
}
}
3、UserVo 类
@Data
public class UserVo {
private Long userId;
private String userName;
}
4.0.0
com.yin
spring-cloud-example
1.0-SNAPSHOT
com.yin
consumer01
0.0.1-SNAPSHOT
consumer01
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-ribbon
1.4.2.RELEASE
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
server:
port: 8083
#工程名(在注册中心的页面看到的名字)
spring:
application:
name: consumer-demo
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10000/eureka/
register-with-eureka: true
fetch-registry: true
instance:
#服务注册中心实例的主机名
hostname: localhost
server:
#关闭自我保护
enableSelfPreservation: false
# 续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)
eviction-interval-timer-in-ms: 1000
logging:
level:
org:
springframework:
cloud: debug
com:
netflix: debug
添加@EnableDiscoveryClient 和 @EnableEurekaClient注解,引入RestTemplate
@SpringBootApplication
@EnableDiscoveryClient
@EnableEurekaClient
public class Consumer01Application {
public static void main(String[] args) {
SpringApplication.run(Consumer01Application.class, args);
}
@Bean
@LoadBalanced
public RestTemplate template(){
return new RestTemplate();
}
}
@RestController
public class CustomerController {
@Resource
private RestTemplate restTemplate;
@GetMapping("index")
public Object getIndex(@RequestParam("id") Integer id) {
HashMap paramMap = new HashMap<>();
paramMap.put("id", id);
return restTemplate.getForObject("http://hello-server/test"+ "?id={id}", String.class,paramMap);
}
}
在页面上输入http://localhost:8083/index?id=2 交替出现
但如果输入id=1时就会有一半的机会
4.0.0
com.yin
spring-cloud-example
1.0-SNAPSHOT
com.yin
feign
0.0.1-SNAPSHOT
feign
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-web
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.boot
spring-boot-starter-actuator
org.projectlombok
lombok
1.16.10
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
#端口
server:
port: 8084
#工程名(在注册中心的页面看到的名字)
spring:
application:
name: consumer-feign
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10000/eureka/
register-with-eureka: true
fetch-registry: true
instance:
#服务注册中心实例的主机名
hostname: localhost
server:
#关闭自我保护
enableSelfPreservation: false
# 续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)
eviction-interval-timer-in-ms: 1000
#开启hystrix 指标
feign:
hystrix:
enabled: true
#用于dashboard监听
management:
endpoints:
web:
exposure:
include: "*"
logging:
level:
org:
springframework:
cloud: debug
com:
netflix: debug
@SpringBootApplication
@EnableFeignClients
@EnableHystrix
@EnableCircuitBreaker
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
@RestController
public class FeignController {
@Resource
private HelloService helloService;
@GetMapping("/test")
public String test(@RequestParam("id") Integer id){
return helloService.test(id);
}
@GetMapping("/getUser")
public UserVo getUser(){
UserVo userVo = new UserVo();
userVo.setUserId(1L);
userVo.setUserName("userName");
return helloService.getUser(userVo);
}
}
2、远程调用的接口
@Component
@FeignClient(name = "hello-server", fallback = HelloFeedbackServiceImpl.class)
public interface HelloService {
@RequestMapping(value = "/test", method = RequestMethod.GET)
public String test(@RequestParam("id") Integer id);
@RequestMapping(value = "/getUser", method = RequestMethod.POST)
public UserVo getUser(@RequestBody UserVo userVo);
}
3、调用失败,异常处理的类
@Component
public class HelloFeedbackServiceImpl implements HelloService {
@Override
public String test(Integer id) {
return "请求失败"+id;
}
@Override
public UserVo getUser(UserVo userVo) {
return null;
}
}
4、请求拦截、处理可用于请求日志打印,请求封装
@Slf4j
public class FeignConfig {
@Bean
public RequestInterceptor getRequestInterceptor(){
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate requestTemplate) {
/** 设置请求头信息 **/
// requestTemplate.header("Content-Type", "application/json");
// 可以做日志记录
Request.Body body1 = requestTemplate.requestBody();
String body = body1.length() == 0 ? "" : new String(body1.asBytes());
System.out.println("日志记录:" + body);
}
};
}
}
输入http://localhost:8084/test?id=1 交替出现
这里需要注意的是feign这个服务中application.yml的
#用于dashboard监听
management:
endpoints:
web:
exposure:
include: "*"
pom.xml 中的,都是为hystrix的监听提供服务的
org.springframework.boot
spring-boot-starter-actuator
4.0.0
com.yin
spring-cloud-example
1.0-SNAPSHOT
com.yin
hystrix_dashboard
0.0.1-SNAPSHOT
hystrix_dashboard
Demo project for Spring Boot
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
#端口
server:
port: 8085
#工程名(在注册中心的页面看到的名字)
spring:
application:
name: consumer-dashboard
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10000/eureka/
register-with-eureka: true
fetch-registry: true
instance:
#服务注册中心实例的主机名
hostname: localhost
server:
#关闭自我保护
enableSelfPreservation: false
# 续期时间,即扫描失效服务的间隔时间(缺省为60*1000ms)
eviction-interval-timer-in-ms: 1000
logging:
level:
org:
springframework:
cloud: debug
com:
netflix: debug
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
在浏览器中输入http://localhost:8085/hystrix
在输入框输入我们要监听feign服务的熔断情况
http://localhost:8084/actuator/hystrix.stream
然后多次请求http://localhost:8084/test?id=1,输入页面显示feign接口熔断信息
4.0.0
com.yin
spring-cloud-example
1.0-SNAPSHOT
com.yin
zuul
0.0.1-SNAPSHOT
zuul
Demo project for Spring Boot
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-zuul
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-starter-web
com.google.code.gson
gson
2.8.5
org.springframework.boot
spring-boot-starter-test
test
org.junit.vintage
junit-vintage-engine
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
org.springframework.boot
spring-boot-maven-plugin
server:
port: 8086
spring:
application:
name: zuul-demo
eureka:
client:
service-url:
defaultZone: http://localhost:10000/eureka
instance:
#eureka服务器在接收到最后一个心跳之后等待的时间,然后才能从列表中删除此实例 默认90s(开发环境)
lease-expiration-duration-in-seconds: 10
#eureka客户端需要向eureka服务器发送心跳的频率 默认30s (开发环境)
lease-renewal-interval-in-seconds: 1
####################zuul#################
zuul:
host:
# http的超时时间
connect-timeout-millis: 2000
# socket超时时间
socket-timeout-millis: 1000
## http连接池大小
max-total-connections: 200
## 每个host最大连接数
max-per-route-connections: 20
##信号量模式
ribbon-isolation-strategy: semaphore
##最大信号量
semaphore:
max-semaphores: 100
##路由代理
routes:
route1:
path: /special_port/**
url: http://localhost:8001
route2:
path: /all_port/**
serviceId: hello-server
custom:
zuul:
token-filter:
noAuthenticationRoutes:
- /getUser
- /logIn
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
@Component
@ConfigurationProperties("custom.zuul.token-filter")
public class TokenConfigurationBean {
public List getNoAuthenticationRoutes() {
return noAuthenticationRoutes;
}
public void setNoAuthenticationRoutes(List noAuthenticationRoutes) {
this.noAuthenticationRoutes = noAuthenticationRoutes;
}
private List noAuthenticationRoutes;
}
@Component
public class TokenValidataFilter extends ZuulFilter {
@Resource
TokenConfigurationBean tokenConfigurationBean;
// filterType:过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。
// pre:可以在请求被路由之前调用
// route:在路由请求时候被调用
// post:在route和error过滤器之后被调用
// error:处理请求时发生错误时被调用
@Override
public String filterType() {
return "pre";
}
//设置过滤的顺序之所以设置为6,是因为PreDecorationFilter 过滤器为5,需要用到
//是因为PreDecorationFilter 中设置proxy
@Override
public int filterOrder() {
return 6
}
//判断是否需要过滤
@Override
public boolean shouldFilter() {
//过滤我们的url,如果说是相应请求比如登录:不做任何校验直接放行
RequestContext ctx = RequestContext.getCurrentContext();
return !tokenConfigurationBean.getNoAuthenticationRoutes().contains(ctx.get("requestURI"));
}
@Override
public Object run() {
//校验token
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest req = ctx.getRequest();
String token = req.getHeader("Authorization");
if(token==null || !"123".equals(token)){
RequestContext.getCurrentContext().setResponseStatusCode(HttpStatus.SC_FORBIDDEN);
ReflectionUtils.rethrowRuntimeException(new ZuulException("无访问权限",HttpStatus.SC_FORBIDDEN,"token校验不通过"));
}
return null;
}
}
@Component
public class SendErrorRestFilter extends SendErrorFilter {
protected static final Logger logger = LoggerFactory.getLogger(SendErrorRestFilter.class);
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
Throwable throwable = findCauseException(context.getThrowable());
//获取到状态码
String status = String.valueOf(context.getResponseStatusCode());
HashMap responseParam = new HashMap<>();
responseParam.put("code","异常码"+status);
responseParam.put("errorMessage",throwable.getMessage());
//记录日志
logger.warn("有个异常:",context.getThrowable());
Gson gson = new Gson();
context.setResponseBody(gson.toJson(responseParam));
context.getResponse().setContentType("text/html;charset=UTF-8");
context.remove("throwable");
return null;
}
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 0;
}
// 找出最初始的异常
Throwable findCauseException(Throwable throwable) {
while (throwable.getCause() != null) {
throwable = throwable.getCause();
}
return throwable;
}
}