spring-cloud 核心功能构建

spring-cloud 核心功能构建

源码参考:https://github.com/Autom-liu/spring-cloud-demo

spring cloud 基本项目依赖

父工程项目pom.xml

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.1.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Finchley.SR1</spring-cloud.version>
		<mapper.starter.version>2.0.3</mapper.starter.version>
		<mysql.version>5.1.32</mysql.version>
		<pageHelper.starter.version>1.2.5</pageHelper.starter.version>
	</properties>
	
	<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>
			<dependency>
				<groupId>tk.mybatis</groupId>
				<artifactId>mapper-spring-boot-starter</artifactId>
				<version>${mapper.starter.version}</version>
			</dependency>
			<dependency>
				<groupId>com.github.pagehelper</groupId>
				<artifactId>pagehelper-spring-boot-starter</artifactId>
				<version>${pageHelper.starter.version}</version>
			</dependency>
			<dependency>
				<groupId>mysql</groupId>
				<artifactId>mysql-connector-java</artifactId>
				<version>${mysql.version}</version>
			</dependency>
		</dependencies>
	</dependencyManagement>
	
	<dependencies>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

Eureka注册中心

添加Eureka注册中心服务

添加依赖——eureka服务端

创建新的子工程,添加如下依赖:

pom.xml:

<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

增加启动类注解

@EnableEurekaServer
@SpringBootApplication
public class EurekaServer {
	public static void main(String[] args) throws Exception {
		SpringApplication.run(EurekaServer.class, args);
	}
}

配置application.yml文件

server:
  port: 10086
spring:
  application:
    name: eureka-server
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true		# 固定ip访问,目前还没看到生效
    ip-address: 127.0.0.1 		# 固定ip访问,目前还没看到生效

注册方添加erueka客户端依赖

Erueka作为注册中心,其他往它注册的服务都是客户端,无论它是提供方还是消费方

pom.xml

<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

注册方启动类添加注解

注册方启动类需要添加服务发现注解

@EnableDiscoveryClient

调用服务写法一

调用服务的其中一种写法,就是通过DiscoveryClient类手动从注册中心获取实例列表
拿到实例,拼接URL,发起请求:

@RestController
@RequestMapping("consumer")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;
	@Autowired
	private DiscoveryClient discoverClient;
	
	@GetMapping("/{id}")
	public User queryById(@PathVariable("id") String id) {
		// 根据服务id获取实例列表
		List<ServiceInstance> instances = discoverClient.getInstances("user-service");
		// 从实例列表中拿到实例
		ServiceInstance instance = instances.get(0);
		// 构造URL
		String url = "http://"+ instance.getHost() +":"+ instance.getPort() +"/user/" + id;
		// 发起请求
		User user = restTemplate.getForObject(url, User.class);
		return user;
	}
	
}

这种方式感到很low有没有?

注册方配置erueka注册中心地址

需要注意的是一定要给每个服务命名,即spring.application.name

spring:
  application:
    name: user-consumer
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
  instance:
    prefer-ip-address: true
    ip-address: 127.0.0.1 

erueka 高级配置

高可用集群

eureka.client.service-url.defaultZone 值配置多个即可

服务注册

其实就是配置是否自己作为服务注册,默认注册自己,可以配置不注册自己

eureka.register-with-eureka: false

服务续约

注册中心和注册方维持的心跳机制,几秒告知一次健康状态

eureka.instance.lease-renewal-interval-in-seconds

开发环境小一点,生产环境默认即可,不宜太小也不宜太大

服务失效

当注册方迟迟未心跳时,配置几秒认为它不存活了

eureka.instance.lease-expiration-duration-in-seconds

服务刷新

默认情况下会缓存服务列表的数据,隔一定时间重新拉取更新,通过该参数可以修改拉取时间,在开发环境下通常设置小一点,生产环境下不需要更改 单位秒

eureka.client.registry-fetch-interval-seconds

自我保护

spring cloud注重CAP原则中的A和P,简单来说即不轻易相信服务挂了,即它会排查一切可能,只有确定100%认为它挂了它才剔除,否则只要有存活的可能,都不会放弃,哪怕它真的挂了…

开启自我保护,当服务没有正常保持心跳时,注册中心并不认为它挂了,因为存在网络延迟的可能,因此要统计次数及比例,只有超过50%(或多少的~)才可以认为它真的挂了。

这样能有效防止注册中心误判(服务挂没挂)

一般不需要关闭,非要关闭就进行如下配置为false即可:

eureka.server.enable-self-preservation

失效剔除

前面说到,注册中心不会轻易判定一个服务说挂就挂的,因此哪怕注册中心真的认为服务挂了,也要缓冲一个时间,给它最后一次机会,实在不给面子了,才会把它剔除。

因此失效剔除也是需要有时间的,配置如下:单位毫秒

eureka.server.eviction-interval-timer-in-ms

Ribbon 负载均衡器

引入相关依赖

在服务消费方引用

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

使用RibbonLoadBalancerClient动态获取实例

@RestController
@RequestMapping("consumer")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;
	@Autowired
	private RibbonLoadBalancerClient ribbonClient;
	
	@GetMapping("/{id}")
	public User queryById(@PathVariable("id") String id) {
		// 使用ribbon负载均衡器可以直接id实例
		ServiceInstance instance = ribbonClient.choose("user-service");
		String url = "http://"+ instance.getHost() +":"+ instance.getPort() +"/user/" + id;
		User user = restTemplate.getForObject(url, User.class);
		return user;
	}
	
}

调用服务写法二————使用Ribbon拦截器拦截RestTemplate

第二种写法,即使用Ribbon拦截器拦截RestTemplate,它对RestTemplate做了一次代理,省去了拼接URL麻烦,直接调用访问即可

首先要将注册到spring中的restTemplate添加注解标记

@Bean
@LoadBalanced   // 使用该注解,会对restTemplate所有请求进行一个拦截处理url
public RestTemplate restTemplate() {
	return new RestTemplate();
}

这样就可以不再通过RibbonLoadBalancerClient去获取,而是直接体现在URL上,在RestTemplate发起请求之前重写URL

@RestController
@RequestMapping("consumer")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;
	
	@GetMapping("/{id}")
	public User queryById(@PathVariable("id") String id) {
		// 将服务id直接写进url上
		String url = "http://user-service/user/" + id;
		User user = restTemplate.getForObject(url, User.class);
		return user;
	}
	
}

Ribbon 高级配置

负载均衡策略

Ribbon默认负载均衡策略是轮询的,可以通过如下配置修改负载均衡策略

[service-id].ribbon.NFLoadBalancerRuleClassName

其中service-id是服务提供方的应用名称,即spring.application.name配置的名称

负载均衡策略都在com.netflix.loadbalancer包下:

其中有如下几种策略:

  • RoundRobinRule
  • AvailabilityFilteringRule
  • WeightedResponseTimeRule
  • ZoneAvoidanceRule
  • BestAvailableRule
  • RandomRule
  • Retry

也可以自定义策略,这个上网去查了

重试机制

ribbon重试机制个人感觉有问题,没成功过,或偶尔成功过

某些人说要加入如下依赖:

<dependency>
		<groupId>org.springframework.retry</groupId>
		<artifactId>spring-retry</artifactId>
</dependency>

但另外一些人又说那是老版本的做法,这里用的是新版本的,不知道会不会问题。

但大多数主要就做如下配置即可的:

以下这些配置均是在AbstractRibbonCommand类源码中有对应指明

[service-id].ribbon.ConnectTimeOut Ribbon连接超时时间

[service-id].ribbon.ReadTimeout Ribbon读取数据超时时间

[service-id].ribbon.OkToRetryOnAllOperations 是否对所有操作都进行重试

[service-id].ribbon.MaxAutoRetriesNextServer 切换实例的重试次数

[service-id].ribbon.MaxAutoRetries 对当前实例的重试次数

但请务必先开启spring cloud重试功能:

spring.cloud.loadbalancer.retry.enabled: true

Hystrix 降级熔断

所谓熔断,即服务超出一定时长未响应及时反馈用户,避免因为阻塞占用连接。

添加Hytrix依赖

pom.xml

	<dependency>
	    <groupId>org.springframework.cloud</groupId>
	    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
	</dependency>

启动类开启Hystrix注解

@EnableHystrix

这个注解仅仅只是Hystrix的基本功能,并不通用

@EnableCircuitBreaker

这个注解包含了服务熔断,服务降级等处理,更为通用

@SpringCloudApplication

这个注解其实是@SpringBootApplication@EnableDiscoveryClient@EnableCircuitBreaker,三者合一,是spring cloud项目开发的基本注解!!

设置接口熔断回调

局部熔断回调

使用@HystrixCommandfallbackMethod属性配置,具体看如下代码:

@RestController
@RequestMapping("consumer")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;
	@Autowired
	private LoadBalancerClient ribbonClient;
	
	@GetMapping("/{id}")
	@HystrixCommand(fallbackMethod = "queryFailback")
	public Result<User> queryById(@PathVariable("id") String id) {
		ServiceInstance instance = ribbonClient.choose("user-service");
		String host = instance.getHost();
		int port = instance.getPort();
		
		String url = String.format("http://%s:%d/user/%s", host, port, id);
		System.out.println(url);
		User user = restTemplate.getForObject(url, User.class);
		return Result.success(user);
	}
	
	public Result<User> queryFailback(String id) {
		return Result.error("服务器繁忙,请稍后再试....");
	}
	
}

公共熔断回调

所谓公共熔断回调即对整个类生效,只需要在类上配置注解@DefaultPropertiesdefaultFallback属性,在需要熔断的方法上加上@HystrixCommand即可

参考代码如下:

@RestController
@RequestMapping("consumer")
@DefaultProperties(defaultFallback = "defaultFailback")
public class ConsumerController {
	
	@Autowired
	private RestTemplate restTemplate;
	@Autowired
	private LoadBalancerClient ribbonClient;
	
	@GetMapping("/{id}")
	@HystrixCommand
	public Result<User> queryById(@PathVariable("id") String id) {
		ServiceInstance instance = ribbonClient.choose("user-service");
		String host = instance.getHost();
		int port = instance.getPort();
		
		String url = String.format("http://%s:%d/user/%s", host, port, id);
		System.out.println(url);
		User user = restTemplate.getForObject(url, User.class);
		return Result.success(user);
	}
	
	public Result<User> defaultFailback() {
		return Result.error("服务器繁忙,请稍后再试试....");
	}
	
}

高级配置

以下这些配置其实都在HystrixCommandProperties类源码中有对应指出的,并不是乱来的。

全局超时时间

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds

局部超时时间

接口方法上加上如下注解:

@HystrixCommand(commandProperties = {
			@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000")
	})

熔断器请求量

熔断器会在一定的请求次数后对请求进行分析统计,并跳入下一个状态,比如统计失败次数过多,将进入熔断状态。

通过如下配置项进行配置:

circuitBreaker.requestVolumeThreshold

出错比例

前面说到达到一定的请求进行一次统计,比如统计失败次数过多,将进入熔断状态,这里的过多,其实是根据一定比例来判定的,即出错比例

可以通过如下配置项进行配置:

circuitBreaker.errorThresholdPercentage

休眠时间窗

熔断器熔断后并不是完事了,还要尝试恢复,因此会在一定的时间后从熔断状态变为半开放状态,测试当前服务集群可用性,这个时间即休眠时间窗

可用通过如下配置项进行配置:

circuitBreaker.sleepWindowInMilliseconds

通过注解的方式配置,结合局部超时时间熔断请求量休眠时间窗出错比例一起使用,最终配置的结果如下:

@HystrixCommand(commandProperties = {
			@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "6000"),
			@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
			@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
			@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"),
	})

feign

基本使用

feign基本功能即代替restTemplate写法,注意它只是远程调用的工具,封装以简化代码开发而已的,并没有改变远程调用的工作。

导入依赖

feign 已经包含了ribbon和hystrix依赖,但是为了全面,将同时导入也无所谓

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

启动类添加注解

启动类添加如下注解开启feign客户端

@EnableFeignClients

添加远程调用的服务消费端端接口

feign 远程调用其实是通过配置熟悉的spring mvc语法来构造URL,代替restTemplate方式的调用。

方式就是以接口的形式定义相关配置,如URI,返回值,参数列表等。这个接口方法的定义保持和spring mvc端的web层方法一致。

@FeignClient("user-service")
@RequestMapping("/user")
public interface UserClient {

 	@GetMapping("/{id}")
	User queryById(@PathVariable("id") String id);
}

远程调用方式三————面向接口调用

前面说到两种远程调用的方式,都是需要显式指出URL,调用restTemplate,这样写看似代码逻辑清晰,简单明了。但是大量写死的代码造成代码冗余,逻辑重复,可维护性差。因此就出现了最终的高端写法:

面向接口调用,是一个相当抽象的过程,看不出来内部使用什么实现的。方式也很简单,不需要RestTempalte,也不需要LoadBalancerClient,直接注入前面自定义的配置接口即可,具体参考如下代码:

@RestController
@RequestMapping("consumer")
public class ConsumerController {
	
	@Autowired
	private UserClient userClient;
	
	@GetMapping("/{id}")
	public Result<User> queryById(@PathVariable("id") String id) {
		User user = userClient.queryById(id);
		return Result.success(user);
	}
	
}

让你完全看不出远程调用的痕迹,这只是一种写法而已,底层仍然时想注册中心发起远程调用的。

这种写法说好听点就是减少代码冗余,避免代码重复,但其实就是装逼式写法,增加了理解难度,看得懂的人秀,看不懂的人怎么也别想不懂… _

feign 整合 Ribbon 和 Hystrix 重试和熔断

温馨提示:不建议使用,因为没有成功的。可能是我技术问题

feign 本身提供了 Ribbon 和 Hystrix,它就相当于是spring cloud家族的一个集成工具,因此导入依赖的时候不需要ribbon的相关依赖(Hystrix还是要的,因为刚出版,没有完全集成)

ribbon 和 Hystrix相关配置

大多数和原来一样的,只不过要开启feign

spring:
  application:
    name: user-consumer
  cloud:
    loadbalancer:
      retry:
        enabled: true   # 开启spring cloud 重试功能
user-service:
  ribbon:
    # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule    # 更换负载均衡策略
    ConnectTimeout: 250     #  Ribbon连接超时时间
    ReadTimeout: 1000       #  Ribbon读取数据超时时间
    OkToRetryOnAllOperations: true    # 是否对所有操作都进行重试
    MaxAutoRetriesNextServer: 100      # 切换实例的重试次数
    MaxAutoRetries: 1                 # 对当前实例的重试次数
feign:
  hystrix:
    enabled: true           # 开启feign的hystrix熔断
ribbon:
  ConnectTimeout: 500    # 连接超时时长
  ReadTimeout: 1000         # 读取超时时长
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000    # 设置hystrix的超时时间

新建failback工厂作为熔断回调

建议用工厂模式,不然不容易成功

@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {

 	@Override
	public UserClient create(Throwable cause) {
		return new UserClient() {

 			@Override
			public User queryById(String id) {
				return null;
			}
		};
	}

}

添加远程调用的服务消费端端接口

和之前一样,使用接口,但是需要在@FeignClient注解上添加多一个fallbackFactory 属性:

@FeignClient(value = “user-service”, fallbackFactory = UserClientFallbackFactory.class)

注意,feign的熔断不走hystrix的流程,因此feign配置和hystrix配置是会冲突的,因此如果要用feign的熔断功能的话,就要把原来的hystrix熔断相关的注解去掉,不然不会成功。(虽然就没成功过!)

Zuul网关

是主要的对外接口,外部必须通过网关才能访问具体的微服务,网关同时具有服务聚合的功能。

简单来说,就是Controller

基本使用

网关作为单独的项目存在,也是spring boot服务

添加依赖

<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>

启动类开启Zuul网关注解

@EnableZuulProxy

注意使用ZuulProxy而不是ZuulApplication,功能更完善

高级配置

基于服务的路由配置

所谓路由配置即URI到服务的映射关系配置

这其实是Zuul的默认配置,默认它会将所有的服务名称作为前缀到URI上去,因此即使不配置,也可照常访问。

但是有时候需求,并不要求服务名称作为URI前缀访问时,就需要指定配置了。

zuul:
  routes:
    user-service:
      path: /user-service/**
      serviceId: user-service

忽略服务

有时候并不希望有些服务对外使用,但是网关默认是全部注册上的,因此需要配置忽略的服务

zuul:
  routes:
    user-service: /user/**
  ignored-services:
    - user-consumer

局部前缀忽略

访问 zuul.routes.[service-id].path 是需要带前缀的

zuul:
  routes:
    user-service:
      path: /user/**
      serviceId: user-service
      strip-prefix: false

全局访问前缀

zuul.prefix: /api

整合Ribbon 和 Hystrix

配置其实和之前一个样,只需要加上如下配置即可

zuul.retryable

zuul:
  retryable: true   # 开启重试
ribbon:
  ConnectTimeout: 1000
  ReadTimeout: 1000
  MaxAutoRetries: 0
  MaxAutoRetriesNextServer: 100
  OkToRetryOnAllOperations: true
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 6000    # 设置hystrix的超时时间

自定义过滤器

先直接看演示

@Component
public class LoginFilter extends ZuulFilter {

	@Override
	public boolean shouldFilter() {
		return true;
	}

	@Override
	public Object run() throws ZuulException {
		RequestContext context = RequestContext.getCurrentContext();
		HttpServletRequest request = context.getRequest();
		
		String token = request.getParameter("token");
		
		if(StringUtils.isBlank(token)) {
			// 不存在,则拦截
			context.setSendZuulResponse(false);
			HttpServletResponse response = context.getResponse();
			response.setStatus(HttpStatus.FORBIDDEN.value());
			response.setContentType("text/json;charset=UTF-8");
			context.setResponse(response);
//			context.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
			
			context.setResponseBody(JSON.toJSONString(Result.error("没有权限")));
		}
		
		return null;
	}

	@Override
	public String filterType() {
		return FilterConstants.PRE_TYPE;
	}

	@Override
	public int filterOrder() {
		return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
	}

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值