文章目录
一、Eureka-注册中心
1.创建父工程
创建一个父工程,在父工程指定SpringCloud的版本,并且将packaging修改为pom。
#父类模块
<packaging>pom</packaging>
<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>
2.创建eureka server
创建eureka的server,创建SpringBoot工程(maven工程),自己添加启动类,导入依赖(c),在启动类添加注解,编写yml文件。
2.1导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
2.2启动类添加注解
/**
* @author Zouch
* @date 2020/10/29 9:29
* @description
*/
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
2.3编写yml配置文件
server:
port: 8761 #端口号
eureka:
instance:
hostname: localhost #localhost
client:
#当前服务是单机版
register-with-eureka: false
fetch-registry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
3.创建eureka client
3.1创建maven工程,修改为SpringBoot,参考上面2.1
3.2导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
3.3启动类添加注解
/**
* @author Zouch
* @date 2020/10/29 9:56
* @description
*/
@SpringBootApplication
@EnableEurekaClient
public class CustomerApplication {
public static void main(String[] args) {
SpringApplication.run(CustomerApplication.class,args);
}
}
3.4编写配置文件
#指定eureka服务地址
eureka:
client:
service-url: #点进去就能看到
defaultZone: http://localhost:8761/eureka
spring:
application:
name: CUSTOMER
启动,浏览器可以看到已经注册到eureka。
4.测试
4.1创建search模块注册到eureka
同3.3
4.2使用EurekaClient 的对象去获取服务信息
4.3使用RestTemplate去调用
/**
* @author Zouch
* @date 2020/10/29 10:19
* @description
*/
@RestController
public class CustomerController {
//启动类要bean new一个RestTemplate
@Autowired
private RestTemplate restTemplate;
@Autowired
private EurekaClient eurekaClient;
@RequestMapping("/customer")
public String customer(){
//1.通过eurekaclient 获取到search服务的信息
InstanceInfo search = eurekaClient.getNextServerFromEureka("SEARCH", false);
//2.获取到访问地址
String url = search.getHomePageUrl();
System.out.println(url);
//3.通过restTemplate访问
String result = restTemplate.getForObject(url+"/search",String.class);
//4.返回
return result;
}
}
5.Eureka的安全
实现Eureka认证。
1.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2.编写配置类
/**
* @author Zouch
* @date 2020/10/29 10:42
* @description
*/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//忽略掉路径
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
3.编写配置文件
spring:
security:
user:
name: root
password: root
4.其他服务向eureka注册需要添加用户名和密码
#指定eureka服务地址
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8761/eureka ##root:root@ 用户名和密码
spring:
application:
name: SEARCH
server:
port: 8081
6.Eureka的高可用
如果程序正在运行,突然eureka宕机了。
1.如果调用方访问过一次被调用方,则不会影响到功能。
2.如果没有访问过,就会造成当前功能不可用。
搭建Eureka高可用
-
搭建多台eureka
采取了复制的方式,直接文件夹复制项目。删除iml和target文件,再给父工程添加一个module -
让服务注册到多台eureka
eureka: client: service-url: defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
-
让多台eureka相互通讯
server: port: 8761 #端口号 eureka: instance: hostname: localhost #localhost client: register-with-eureka: true #注册到eureka fetch-registry: true #从eureka拉取信息 serviceUrl: defaultZone: http://root:root@localhost:8762/eureka/
7.Eureka的细节
1.EurekaClient启动时,将自己的信息注册到EurekaServer上,EurekaServer会存储EurekaClient的信息。
2.当EurekaClient调用服务时,本地无注册信息的缓存时,会去EurekaServer获取注册信息。
3.EurekaClient会通过心跳的方式去和EurekaServer进行连接。(默认30s会发送一次心跳请求,如果超过了90s还没有发送心跳信息的话,EurekaServer就默认认为你宕机了,将当前EurekaClient从注册表中移除)
#指定eureka服务地址
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
instance:
lease-renewal-interval-in-seconds: 30 #心跳间隔
lease-expiration-duration-in-seconds: 90 #多久没发生会认为你宕机
4.EurekaClient会每隔30s 去EurekaServer更新本地注册表缓存信息。
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
registry-fetch-interval-seconds: 30 #多久去更新一下本地注册表信息
5.Eureka自我保护机制,统计15分钟内,一个服务的心跳发送比例低于85%,EurekaServer就会开启自我保护机制
- 不会从EurekaServer去移除长时间没有收到心跳的服务。
- EurekaServer还是可以正常提供服务。
- 网络比较稳定时,EurekaServer才会将自己的信息被其他节点同步过去。
eureka:
instance:
hostname: localhost #localhost
server:
enable-self-preservation: true #开启自我保护机制
client:
register-with-eureka: true #注册到eureka
fetch-registry: true #从eureka拉取信息
serviceUrl:
defaultZone: http://root:root@localhost:8762/eureka/
6.CAP定理 (之能满足其二,且P分区容错性必须要有)
- C -一致性
- A -可用性
- P -分区容错性
如果选择了CP、可能导致系统在一定时间内不可用,如果同步的数据大,造成的损失很大。
Eureka就是一个AP的效果,高可用的集群,Eureka集群无中心,即使宕机几个也不会导致系统不可用,不需要选举新的master、也会导致一定时间内数据不一致。
二、Ribbon-服务间负载均衡
Ribbon是客户端负载均衡;Dubbon采用的是服务端负载均衡。
1.使用Ribbon
1.启动两个search模块(服务模块)
详情见https://blog.csdn.net/woshizouqi/article/details/109360000
2.在客户端模块(调用方)中加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-ribbon</artifactId>
</dependency>
3.配置整合RestTemplate和Ribbon
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
4.在客户端去访问
@RequestMapping("/customer")
public String customer(){
String result=restTemplate.getForObject("http://SEARCH/search",String.class);
return result;
}
2.Ribbin配置负载均衡策略
2.1负载均衡策略
- RandomRule() :随机策略;
- RoundRobbinRule() :轮询策略;
- WeightedResponseTimeRule() :默认会采用轮询的策略,后续根据响应时间,自动分配权重。
- BestAvailableRule():根据被调用方并发数最小的去分配
2.2采用注解形式
@Bean
public IRule rule(){
return new RandomRule();
}
3.3配置文件去指定策略(推荐,可以实现单个服务的负载均衡)
#指定具体服务的负载均衡策略
SEARCH: #编写服务名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule #策略类
三、Feign-服务调用
1.使用
1.导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.添加注解
启动类添加@EnableFeignClients
3.创建接口和目标模块做映射
/**
* @author Zouch
* @date 2020/10/29 15:48
* @description
*/
@FeignClient("SEARCH") //目标服务名
public interface SearchClient {
//value ->服务访问路径
@RequestMapping(value = "search", method = RequestMethod.GET)
public String search();
}
4.测试
/**
* @author Zouch
* @date 2020/10/29 10:19
* @description
*/
@RestController
public class CustomerController {
@Autowired
private SearchClient searchClient;
@RequestMapping("/customer")
public String customer(){
String result = searchClient.search();
return result;
}
}
2.feign的参数传递方式
2.1注意事项
- 如果传递的参数比较复杂。默认会采用POST的请求方式
- 传递单个参数时,推荐使用@PathVariable,如果传递的参数个数比较多,这里也可以采用@RequestParam,不要省略value属性。
- 传递对象信息,统一采用json,@RequestBody。
3.Feign的fallback和fallbackfactory
3.1新建一个类实现Client所有方法
/**
* @author Zouch
* @date 2020/10/29 16:23
* @description
*/
@Component
public class SearchFallback implements SearchClient{
@Override
public String search() {
return "服务调用失败";
}
}
3.2修改@FeignClient
@FeignClient(value = "SEARCH",fallback = SearchFallback.class) //目标服务名
3.3添加配置
#开启fallback
feign:
hystrix:
enabled: true
光使用fallback看不到服务方的错误信息,可以使用fallbackFactory
3.4创建一个类实现FallbackFactory<>接口,泛型内类型是Client接口类,注意加@Component注解
/**
* @author Zouch
* @date 2020/10/29 16:33
* @description
*/
@Component
public class SearchFallbacklFactory implements FallbackFactory<SearchClient> {
//一定要注意注入,然后调用 return
@Autowired
SearchFallback searchFallback;
@Override
public SearchClient create(Throwable throwable) {
throwable.printStackTrace();
return searchFallback;
}
}
3.5修改Client接口的注解
@FeignClient(value = "SEARCH",fallbackFactory = SearchFallbacklFactory.class) //目标服务名
这样就能在客户端也能打印错误日志!
四、Hystrix-服务容错和降级
1.Hystrix功能
Hystrix主要是为了解决服务雪崩问题
- 降级机制:当你的服务出现超时或者异常等,可以执行一个降级方法,返回一个托底数据。
- 隔离:提供了一个Hystrix线程池,信号量,和tomcat的线程池相互隔离。
- 熔断:当你服务的失败率达到一定阈值,自动触发降级。
- 缓存:请求缓存的功能。
2.降级机制实现
2.1导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.2启动类添加一个注解
//Hystric熔断
@EnableCircuitBreaker
2.3针对某一个方法去编写他的降级方法
//getStudent的降级方法 方法的描述要和接口一致
public Student getStudentFallBack(@RequestParam String id) {
return new Student("fallback","1","1");
}
2.4在接口上添加注解
@RequestMapping("/get")
@HystrixCommand(fallbackMethod = "getStudentFallBack")
public Student getStudent(@RequestParam String id){
int i=1/0;
Student student = searchClient.searchStudent("2");
return student;
}
3.线程隔离
如果使用Tomcat的线程池去处理用户的请求,使用当前线程去执行其他服务的功能,如果某一个服务出现故障,导致Tomcat的线程大量的堆积,无法处理其他业务。
github.com/Netflix/Hystrix/Wiki/Configuration
- Hystrix的线程池(默认),接受用户请求采用tomcat的线程池,调用其他服务时,采用Hystrix的线程池。
- 信号量,使用的还是Tomcat的线程池,帮助我们去管理Tomcat的线程池。
3.1Hystrix的线程池的配置
3.2Hystrix的信号量的配置
4.断路器
在调用指定服务时,如果说这个服务的失败率达到你输入的一个阈值,将断路器从closed状态,转变为open状态,指定服务时无法被访问的,如果你访问就直接走fallback方法,在一定的时间内,open状态会再次转变为half open状态,允许一个请求发送到我的指定服务,如果成功,转变为closed,失败则再次转为open,然后循环到half open ,直到断路器转回到closed状态。
配置断路器监控界面
- 导入依赖
- 在启动类添加注解
6.请求缓存
6.1创建Service调用client
/**
* @author Zouch
* @date 2020/11/2 17:07
* @description
*/
@Service
public class CustomerService {
@Autowired
SearchClient searchClient;
@CacheResult
@HystrixCommand(commandKey = "findById")
public Student findById(@CacheKey String id){
return searchClient.searchStudent(id);
}
@CacheRemove(commandKey = "findById")
public void clearFindById(@CacheKey String id ){
System.out.println("findById缓冲"+id+"被清空");
}
}
6.2使用请求缓存的注解
@CacheResult 缓存当前方法的返回结果(必须和HystrixCommand配合使用)
@CacheRemove 帮助我们清除某一个缓存信息(基于commandKey)
@CacheKey 指定哪个方法参数作为缓存的key
6.3编写Filter,去构建HystrixRequestContext
/**
* @author Zouch
* @date 2020/11/2 17:13
* @description
*/
@WebFilter("/*")
public class HystrixRequestContextInitFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HystrixRequestContext.initializeContext();
filterChain.doFilter(servletRequest,servletResponse);
}
}
//!!注意要在启动类添加 @ServletComponentScan("com.zouch.filter")
五、Zuul-服务网关
客户端维护大量的ip和端口信息,直接访问指定服务
认证和授权操作,需要每一个模块都添加认证和授权的操作
项目迭代,服务拆分,客户端需要进行大量的变化
统一把安全性校验都放在zuul中
1.使用Zuul
5.1创建maven项目,修改为springboot
new module ->
5.2导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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-zuul</artifactId>
</dependency>
5.3添加注解
@EnableEurekaClient
@EnableZuulProxy
5.4编写配置文件
#指定eureka服务地址
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8761/eureka,http://root:root@localhost:8762/eureka
spring:
application:
name: ZUUL
server:
port: 8083
5.5测试从Zuul调用
http://localhost/customer/customer # http://localhost/服务名/接口地址
2.Zuul监控配置
2.1导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
2.2增加配置
#查看Zuul的监控界面(开发时配置为*,上生产不配置)
management:
endpoints:
web:
exposure:
include: "*"
2.3输入网址查看
http://localhost/actuator/routes http://localhost/服务名/路径
{
- /search/**: “search”,
- /zuul/**: “zuul”,
- /customer/**: “customer”,
- /unknown/**: “unknown”
}
3.忽略服务配置
zuul:
#基于服务名忽略服务,监控界面无法查看到 如果忽略全部服务 “*”
ignored-services: eureka
#路径含有search的都会被拦截,监控页面可以查看到,但访问时会404(被拦截)
ignored-patterns: /**/search/**
4.自定义服务配置
zuul:
#指定自定义服务(方式一,key(服务名):value(路径))
# routes:
# search: /ss/**
# customer: /cc/**
#指定自定义服务(方式二)
routes:
kehu: #自定义名称
path: /ccc/** #映射的路径
serviceId: customer #服务名称
#还有很多属性可以定义
5.灰度发布
5.1添加一个配置类
@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
return new PatternServiceRouteMapper(
"(?<name>^.+)-(?<version>v.+$)",
"${version}/${name}");
}
//服务名-v几
// /v几/路径
5.2准备一个服务,提供两个版本
version: v1
spring:
application:
name: CUSTOMER-${version}
5.3修改Zuul的配置
6.Zuul的过滤器执行流程
客户端请求发送到Zuul服务上,首先通过PreFilter链,如果正常放行,会把请求转发给RoutingFilter,请求转发到一个指定的服务,在服务响应结果后,再次走PostFilter过滤连,最终将响应结果交给客户端。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hq3kZ3Oj-1604556691347)(C:\Users\zouch\AppData\Roaming\Typora\typora-user-images\image-20201105112855763.png)]
6.1实现过滤器
创建pojo类实现ZuulFilter
/**
* @author Zouch
* @date 2020/11/5 11:37
* @description
*/
@Component
public class ZuulFilterTest extends ZuulFilter {
指定过滤器类型
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
指定执行顺序
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
配置是否启用
@Override
public boolean shouldFilter() {
//开启过滤器
return true;
}
指定过滤器具体业务代码
@Override
public Object run() throws ZuulException {
System.out.println("过滤器执行!!!!!");
return null;
}
6.2Filter实现Token校验
1.准备访问路径,请求参数传递token
http://localhost/customer/customer?token=122
2.创建AuthenticationFilter
/**
* @author Zouch
* @date 2020/11/5 12:01
* @description
*/
@Component
public class AuthenticationFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return FilterConstants.PRE_DECORATION_FILTER_ORDER-2;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//获取request对象
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
//获取token参数
String token = request.getParameter("token");
if (token==null||"123".equals(token)){
//token校验失败,直接响应数据
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
}
//这个return几乎没有意义,不用管
return null;
}
}
3.在run方法编写代码
@Override
public Object run() throws ZuulException {
//获取request对象
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
//获取token参数
String token = request.getParameter("token");
if (token==null||"123".equals(token)){
//token校验失败,直接响应数据
currentContext.setSendZuulResponse(false);
currentContext.setResponseStatusCode(HttpStatus.SC_UNAUTHORIZED);
}
//这个return几乎没有意义,不用管
return null;
}
4.测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IyIuHBQe-1604556691353)(C:\Users\zouch\AppData\Roaming\Typora\typora-user-images\image-20201105131306911.png)]
7.Zuul降级
1创建pojo类 ,实现FallBackProvider接口
@Component
public class ZuulFallBack implements FallbackProvider {
2.重写方法
/**
* @author Zouch
* @date 2020/11/5 13:16
* @description Zuul这边处理降级,目的是可以处理客户端没有实现降级的服务
*/
@Component
public class ZuulFallBack implements FallbackProvider {
@Override
public String getRoute() {
return "*"; //代表全部出现问题的方法,都走这个降级方法
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
System.out.println("降级的服务"+route);
System.out.println("降级的原因"+cause);
return new ClientHttpResponse(){
@Override
public HttpHeaders getHeaders() {
//指定响应头信息
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
@Override
public InputStream getBody() throws IOException {
//给用户响应的信息
String msg = "当前服务" + route + "出现问题";
return new ByteArrayInputStream(msg.getBytes());
}
@Override
public HttpStatus getStatusCode() throws IOException {
//指定具体的httpstatus
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
//返回的状态码
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
//指定错误信息
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
}
};
}
}
3.测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y2YfTttS-1604556691357)(C:\Users\zouch\AppData\Roaming\Typora\typora-user-images\image-20201105133018194.png)]
8.Zuul动态路由
1.创建过滤器
/**
* @author Zouch
* @date 2020/11/5 13:35
* @description 从redis获取服务名实现动态路由,好处是修改时不需要重启项目,动态修改
*/
@Component
public class DynamicRoutingFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
//执行顺序放到pre过滤器后面
return FilterConstants.PRE_DECORATION_FILTER_ORDER+ 2;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//1.拿到request对象
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//2.获取参数,rediskey
String redisKey = request.getParameter("redisKey");
//3.直接判断
if (redisKey !=null&&redisKey.equalsIgnoreCase("customer")){
requestContext.put(FilterConstants.SERVICE_ID_KEY,"/customer");
} else if (redisKey !=null&&redisKey.equalsIgnoreCase("search")){
requestContext.put(FilterConstants.SERVICE_ID_KEY,"/customer");
}
return null;
}
}
2.run方法编写
@Override
public Object run() throws ZuulException {
//1.拿到request对象
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//2.获取参数,rediskey
String redisKey = request.getParameter("redisKey");
//3.直接判断
if (redisKey !=null&&redisKey.equalsIgnoreCase("customer")){
requestContext.put(FilterConstants.SERVICE_ID_KEY,"/customer");
} else if (redisKey !=null&&redisKey.equalsIgnoreCase("search")){
requestContext.put(FilterConstants.SERVICE_ID_KEY,"/customer");
}
return null;
}
未完待续