这次搭建的SpringCloud是由Eureka 、Zull、feign 、ribbon、Zull+hystricx以及zullfilter过滤器和service的搭建
功能包括:
所有微服务注册到注册中心(Eureka)中;创建Zuul,使用网关路由Zuul实现统一路由访问;创建两个service,通过feign和ribbon实现负载均衡的轮询策略;使用hystricx当服务宕机后提示无效。
一。Eureka的创建
1.创建一个module,创建一个启动类。添加如下注解:
@EnableEurekaServer
标识为注册中心,使得其它服务可以注册到这个微服务(注册中心)中
@SpringBootApplication
//服务端的启动类,可以接收别人注册进来
@EnableEurekaServer
public class EurekaserverApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaserverApplication.class, args);
}
}
2.编写properties或者yml配置文件
yml与properties转换器网址:
https://www.toyaml.com/index.html
添加eureka注册中心 配置
#这个是关键配置信息,让其它的服务知道是从哪里注册进来
eureka.client.serviceUrl.defaultZone=http://localhost:24680/eureka/
#端口
server.port=24680
#服务注册中心实例的主机名
spring.application.name= eurekaserver
#--------------------------以下是一些其它的配置信息---------------------
#服务端实例名称
eureka.instance.hostname=localhost
eureka.instace.instaceId= ${spring.application.name}:${random.int}
#是否想eureka注册中心注册自己
eureka.client.register-with-eureka=false
#fetch-registry为false表示是注册中心
eureka.client.fetch-registry=false
pom文件参考代码:
<?xml version="1.0" encoding="UTF-8"?>
4.0.0
<groupId>com.sun</groupId>
<artifactId>springbootEureka</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
debug启动后,输入默认 127.0.0.1:24680即可访问eureka主页面,显示如下:
二。service的创建
先创建一个servcie注册进注册中心(Eureka)中并运行起来。
new module,正常操作。
启动类的注解:
二者选其一
@EnableEurekaClient只适用于使用Eureka作为注册中心的场景,@EnableDiscoveryClient可以适用于其他注册中心的场景比如nacos等。
@SpringBootApplication
@ServletComponentScan
//关键的注解
@EnableDiscoveryClient
@RefreshScope //开启配置更新功能
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
2.编写properties或者yml配置文件
#前三个必备
server.port=28888
spring.application.name=springbootService
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:24680/eureka/
#---------------------------其它的配置----------------------------------
eureka.instance.prefer-ip-address=true
eureka.instance.ip-address=127.0.0.1
eureka.client.registerWithEureka=true
eureka.client.fetchRegistry=true
eureka.instance.instance-id=${eureka.instance.ip-address}:${server.port}@${random.int}
Controller:
普通的写一个方法即可,由于没有页面,因此使用RestController返回String.
(传不传参都可以)
最后成功启动service后,再Eureka的页面中显示到service已经成功注册到Eureka注册中心中。
如下图所示:
之后所有的服务均要与service中配置Eureka的内容一直,并且可能要增加,启动后的结果也是一样,都会再Eureka中呈现出来
当然service服务的请求也是可以访问的12
浏览器中输入
http://127.0.0.1:28888/service/hello
三。Zuul的创建
1.启动类
除了需要这个注解可注册到注册中心之外
@EnableDiscoveryClient
还需要这个注解
@EnableZuulProxy
启用Zuul的代理
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
@RefreshScope
public class ZuulserverApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulserverApplication.class, args);
}
}
上面一坨都是Eureka的配置 说了好多遍了 不多赘述了
中间是端口和名字
下面才是zuul的配置:
名称为sbService的路由,serviceId 是其它服务配置信息那里设置的名字,path:是当使用路由时在端口后,request路径前,需要加上的一段
例如 service的服务端口是28888 因此直接访问127.0.0.1:28888/service/hello
配置路由后,路由端口为3333,path: /sbService/**
那么就应该这么访问:
127.0.0.1:3333/sbService/service/hello
eureka:
client:
fetchRegistry: true
registerWithEureka: true
serviceUrl:
defaultZone: http://127.0.0.1:24680/eureka/
instance:
instance-id: ${eureka.instance.ip-address}:${server.port}@${random.int}
ip-address: 127.0.0.1
prefer-ip-address: true
server:
port: 33333
spring:
application:
name: hyfzuulserver
zuul:
routes:
sbService :
path : /sbService/**
serviceId : springbootservice
zuul配置完毕:
之前启动的service就按照刚才说的那样也可以访问,使用路由端口,统一了路径。
127.0.0.1:3333/sbService/service/hello
结果如下:
里面的?token=1不用管 因为我还加了过滤功能
马上介绍。
zuul不仅拥有路由功能,还可以拥有过滤安全的功能
例如我们发请求的时候需要增加一个token,没有token则直接过滤掉
那么我们在zuul项目中的启动类同级增加一个包加入zuulfilter实现类
package sun.filter;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
@Component
public class ServiceFilter extends ZuulFilter {
private static Logger log=LoggerFactory.getLogger(ServiceFilter.class);
@Override
public String filterType() {
return "pre"; // 定义filter的类型,有pre、route、post、error四种
}
@Override
public int filterOrder() {
return 0; // 定义filter的顺序,数字越小表示顺序越高,越先执行
}
@Override
public boolean shouldFilter() {
return true; // 表示是否需要执行该filter,true表示执行,false表示不执行
}
@Override
public Object run() {
// filter需要执行的具体操作
System.out.println("inner");
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getParameter("token");
System.out.println(token);
if(token==null){
log.warn("there is no request token");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("there is no request token");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
log.info("ok");
return null;
}
}
那么就搞定了,当你请求之前写的那个service的时候 如果不带token则会出现
带token
除此之外 zuul还可以和hystricx配合使用,还是跟filter一样,在zuul项目中启动类的同级下增加一个包,加入FallbackProvider的实现类:
package sun.fallbackProvider;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
@Component
public class ServiceFallbackProvider implements FallbackProvider {
@Override
// 指定熔断器功能应用于哪些路由的服务
public String getRoute() {
// 这里只针对"springbootService"服务进行熔断
// 如果需要针对所有服务熔断,则return "*"
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("route:"+route);
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "ok";
}
@Override
public void close() {
}
@Override
// 发生熔断式,返回的信息
public InputStream getBody() throws IOException {
return new ByteArrayInputStream("Sorry, the service is unavailable now.".getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
当你在service服务关闭后仍继续访问service,不会报404,而是
这就是hystricx的熔断机制
下面介绍两种轮询的方法
四。feign的创建
启动类:
基础上多加一个@EnableFeignClients注解
@SpringBootApplication
@ServletComponentScan
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
}
启动类同级建一个包:
放入ServiceFeignClient实现类
package sun.client;
import common.entity.RestfulResult;
import org.springframework.stereotype.Component;
import sun.entity.ServiceInfo;
@Component
public class ServiceFallback implements ServiceFeignClient{
@Override
public RestfulResult hello( ) {
RestfulResult result = new RestfulResult();
result.setData("服务调用失败");
return result;
}
}
和interface ServiceFeignClient
package sun.client;
import common.entity.RestfulResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import sun.entity.ServiceInfo;
@Component
@FeignClient(value = "springbootService", fallback=ServiceFallback.class) //这里的value对应调用服务的spring.applicatoin.name
public interface ServiceFeignClient {
@RequestMapping(value = "/service/hello")
RestfulResult hello();
}
controller可以另外建一个包
```java
/**
* Copyright © 2012-2014 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
*/
package sun.controller;
import common.entity.RestfulResult;
import common.utils.CommUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import sun.client.ServiceFeignClient;
import sun.entity.ServiceInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
public class ServiceController {
@Autowired
ServiceFeignClient serviceFeignClient;
// 调用:localhost:6004/consumerService?token=1
@RequestMapping("/consumerService")
public void consumerService(HttpServletRequest request, HttpServletResponse response
){
RestfulResult restfulResult = serviceFeignClient.hello();
CommUtils.printDataJason(response, restfulResult);
}
}
最后是配置:
server.port=28280
feign.hystrix.enabled=true
eureka.instance.prefer-ip-address=true
eureka.instance.ip-address=127.0.0.1
eureka.client.registerWithEureka=true
eureka.client.fetchRegistry=true
eureka.client.serviceUrl.defaultZone=http://127.0.0.1:24680/eureka/
spring.application.name=hyfSpringbootServiceFeign
eureka.instance.instance-id=${eureka.instance.ip-address}:${server.port}@${random.int}
忘记说一个小问题,到这里,因为是同一个服务内的轮询,那么至少两个service,所以我们需要copy一份之前的service,改一下端口,和到时候会打印的信息。
把这两个service都启动,feign也启动,然后连续访问feign的service会出现,轮流访问不同端口的service
结果如下;
1.
2.
五。ribbon的创建
启动类
底下加一个template
@SpringBootApplication
@ServletComponentScan
@EnableDiscoveryClient
@EnableHystrix
public class ConsumerClientApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerClientApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
}
配置文件正常配置
controller在一个包里,包和启动类同级
/**
* Copyright © 2012-2014 <a href="https://github.com/thinkgem/jeesite">JeeSite</a> All rights reserved.
*/
package sun.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import sun.entity.ServiceInfo;
@RestController
public class ServiceController {
@Autowired
RestTemplate restTemplate;
@Value("${server.port}")
String port;
// 调用:localhost:6007/consumerServiceRibbon?token=1
@RequestMapping("/consumerServiceRibbon")
@HystrixCommand(fallbackMethod="consumerServiceRibbonFallback")
public String consumerServiceRibbon(@RequestBody ServiceInfo serviceInfo){
String result = this.restTemplate.postForObject("http://springbootService/service/rest?token=1", serviceInfo, String.class);
return result;
}
public String consumerServiceRibbonFallback(@RequestBody ServiceInfo serviceInfo){
return "consumerServiceRibbon异常,端口:" + port + ",Name=" + serviceInfo.getName();
}
}
搞定
结果:
失败的同学看这里,在两个service项目那个controller那里,做两件事
1.让返回的值带上端口,这样可以清楚的显示
2.把方法的参数去掉,就留一个HttpServerlet。