SpringCloud
文章目录
Eureka
eureka是一个服务注册/发现的东西,分为服务端和客户端;
Eureka单机服务端的搭建
用STS创建一个SpringBoot项目,启动器选择web模块和SpringCloud Discovery中的Eureka Server;
在启动类上加上@EnableEurekaServer
的注解,表明此应用是一个Eureka Server
package com.springcloud.EurekaServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
yml配置:
#eureka默认端口8761,这里开启web端口,建议与默认端口一致
server:
port: 8761
#为spring应用定义一个唯一名称,默认是null
#SpringCloud中的微服务,同命名的自动形成集群
spring:
application:
name: eureka-server
# eureka配置
eureka:
client:
register-with-eureka: false #是否注册本服务,默认是true
fetch-registry: false #是否发现其他服务,默认是true
此处为何将服务注册和发现都设为false呢?
默认都是true的,但是由于此处搭建的是一个单机版,也就是说,服务本身如果被服务本身发现注册的话会形成死循环;
我们在浏览器输入localhost:8761
即可进入eureka服务端可视化页面;
Eureka集群服务端的搭建
我们再多配两个配置文件,启动的时候只要根据不同配置文件启动SpringBoot实例即可;
application-eureka1.yml:
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://192.168.24.7:8761/eureka/ #表示当前服务注册到哪一个注册中心
instance:
prefer-ip-address: true #使用ip注册不使用主机名注册
application-eureka2.yml:
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://192.168.24.8:8761/eureka/ #表示当前服务注册到哪一个注册中心
instance:
prefer-ip-address: true #使用ip注册不使用主机名注册
这两个就是到哪儿注册变了一下,其他一样;
我们把项目打包,传到服务器上,分别按不同的配置文件启动jar:
java -jar -Dspring.profiles.active=eureka1 eureka.jar
;
java -jar -Dspring.profiles.active=eureka2 eureka.jar
;
然后当我们访问192.168.24.7:8761
和192.168.24.8:8761
会发现DS Replicas中有了彼此,这就代表他们俩是一个集群了;
Ribbon
ribbon主要做的是客户端的负载均衡;
服务的提供方
无论是服务的提供方还是消费方,他们都是服务,也就是说,他们都是eureka的客户端,需要到eureka注册中心注册发现;
所以我们先启动eureka的服务端,这里就以单机版为例;
用STS创建Springboot项目,启动器选择Web和SpringCloud Discovery中的Eureka Discovery Client;
没错,不需要引入任何Ribbon相关的依赖,eureka是不知道ribbon的存在的;
假设我们提供的服务是插入一个用户:
package com.springcloud.RibbonServer.pojo;
public class User {
private String name;
private Integer age;
private String remark;
private String job;
public User() {
// TODO Auto-generated constructor stub
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", remark=" + remark + ", job=" + job + "]";
}
}
controller:
package com.springcloud.RibbonServer.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springcloud.RibbonServer.pojo.User;
@RestController
public class UserController {
@RequestMapping("/insert")
public Map<String,Object> insertUser(User u){
System.out.println("正在新增用户:"+u);
Map<String,Object> result = new HashMap<String, Object>();
result.put("code",200);
result.put("message","新增用户成功");
return result;
}
}
最关键的配置文件来了:
server:
port: 8080
spring:
application:
name: ribbon-service
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/ #到这个注册中心去注册,如果有多个注册中心的话(集群),可以用逗号分隔
可以看到,我们需要到eureka服务端注册一哈;
启动应用,进入http://localhost:8761
,你会发现eureka已经将此服务注册了。
很简单吧,其实什么也不用做,就导一下Client的依赖,再在配置文件里指定一下eureka注册中心即可,其他的跟以前写代码一样;
服务的消费方
说白了就是调用我们上面写的方法,一个http请求;
一般我们会将远程调用的过程放到Service中;
按照上面的办法新建一个项目作为服务的消费方;
controller:
package com.springcloud.RibbonClient.controller;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springcloud.RibbonClient.pojo.User;
import com.springcloud.RibbonClient.service.UserService;
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/insert")
public Map<String,Object> insertUser(User u) {
Map<String,Object> result = userService.insertUser(u);
System.out.println("远程调用返回的结果:"+result);
return result;
}
}
service:
package com.springcloud.RibbonClient.service.impl;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.springcloud.RibbonClient.pojo.User;
import com.springcloud.RibbonClient.service.UserService;
@Service
public class UserServiceImpl implements UserService {
/**
* 这是Ribbon技术中的负载均衡客户端对象,封装了 从eureka server中发现的所有服务的地址列表 包括了服务的IP,端口,名称
*/
@Autowired
private LoadBalancerClient loadBalancerClient;
/**
* 这里面调用远程方法
*/
@Override
public Map<String, Object> insertUser(User u) {
// 获取一个服务实例,参数是服务名称
ServiceInstance instance = loadBalancerClient.choose("ribbon-service");
// 拼接url
StringBuilder sb = new StringBuilder();
sb.append("http://").append(instance.getHost()).append(":").append(instance.getPort()).append("/insert?name=")
.append(u.getName()).append("&age=").append(u.getAge()).append("&remark=").append(u.getRemark())
.append("&=job").append(u.getJob());
// 发送http请求
// 创建rest模板对象,用于发送http请求
RestTemplate template = new RestTemplate();
// 约束响应类型
ParameterizedTypeReference<Map<String, Object>> responseType = new ParameterizedTypeReference<Map<String, Object>>() {
};
/*
* 四个参数; 1.url 2.请求方法 3.请求实体 4.响应类型
*/
ResponseEntity<Map<String, Object>> response = template.exchange(sb.toString(), HttpMethod.GET, null,
responseType);
Map<String, Object> result = response.getBody();
return result;
}
}
配置文件:
server:
port: 8081
spring:
application:
name: ribbon-client
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
配置文件就是端口变一下,名字变一下,其他基本都一样的。
负载均衡策略
网上找了一张表,介绍了Ribbon的负载均衡策略:
策略类 | 命名 | 描述 |
---|---|---|
RandomRule | 随机策略 | 随机选择server |
RoundRobinRule | 轮询策略 | 按照顺序选择server(ribbon默认策略) |
RetryRule | 重试策略 | 在一个配置时间段内,当选择server不成功,则一直尝试选择一个可用的server |
BestAvailableRule | 最低并发策略 | 逐个考察server,如果server断路器打开,则忽略,再选择其中并发链接最低的server |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直失败并被标记为circuit tripped的server,过滤掉那些高并发链接的server(active connections超过配置的阈值) |
ResponseTimeWeightedRule | 响应时间加权重策略 | 根据server的响应时间分配权重,响应时间越长,权重越低,被选择到的概率也就越低。响应时间越短,权重越高,被选中的概率越高,这个策略很贴切,综合了各种因素,比如:网络,磁盘,io等,都直接影响响应时间 |
ZoneAvoidanceRule | 区域权重策略 | 综合判断server所在区域的性能,和server的可用性,轮询选择server并且判断一个AWS Zone的运行性能是否可用,剔除不可用的Zone中的所有server |
比如我想使用随机策略,我就可以按如下方法配置:
server:
port: 8081
spring:
application:
name: ribbon-client
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
#负载均衡配置
#最高级写你要远程调用的服务的名称
ribbon-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #使用随机策略
前缀com.netflix.loadbalancer都是一样的,不同的负载均衡策略只要根据上表改一下后缀就行了。
OpenFeign
从上面Ribbon的代码中可以看出,远程调用其他服务的代码很繁琐,OpenFeign对于Ribbon进行了进一步的封装,使得我们在调用远程服务时更加便捷;
服务的提供方
在使用Feign的过程中,我们需要一个接口来约束服务的消费方和提供方,接口如下:
package com.springcloud.FeignService.api;
/**
* 约束服务的提供者和消费者:
* 包括需要提供什么服务,允许访问什么服务,服务访问路径等;
* 注意,普通类型参数需要加上注解@RequestParam,自定义类型参数需要加上注解@RequestBody
*
*/
import java.util.Map;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import com.springcloud.FeignService.pojo.User;
public interface UserServiceApi {
@PostMapping("/insert")
Map<String,Object> insert(@RequestBody User user);
}
我们让服务提供方的controller继承这个接口,这样便受到了此接口的约束:
package com.springcloud.FeignService.controller;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.RestController;
import com.springcloud.FeignService.api.UserServiceApi;
import com.springcloud.FeignService.pojo.User;
@RestController
public class UserController implements UserServiceApi{
@Override
public Map<String, Object> insert(User user) {
Map<String,Object> result = new HashMap<String, Object>();
result.put("message", "新增用户成功");
return result;
}
}
其他地方就没什么不一样的了,配置文件也没什么不同,还是要到eureka里面注册。
服务的消费方
在服务的消费方中,我们要使用OpenFeign调用远程服务,所以需要其依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
controller:
package com.springcloud.FeignClient.controller;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import com.springcloud.FeignClient.pojo.User;
import com.springcloud.FeignClient.service.UserService;
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/insert")
public Object insert(User u) {
Map<String,Object> result = userService.insert(u);
System.out.println(result);
return result;
}
}
service:
package com.springcloud.FeignClient.service;
import org.springframework.cloud.openfeign.FeignClient;
import com.springcloud.FeignClient.api.UserServiceApi;
/**
*
* 本地服务,用于访问远程服务,和远程服务同受到UserServiceApi的约束
* 只需添加@FeignClient("远程服务名")即可
*
*/
@FeignClient("feign-service")
public interface UserService extends UserServiceApi{
}
注意我们不需要写实现类,只要添加这一个注解就够了。
为了让能扫描到@FeignClient
注解,我们还需要在启动类上添加注解@EnableFeignClients
:
package com.springcloud.FeignClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableFeignClients
public class FeignClientApplication {
public static void main(String[] args) {
SpringApplication.run(FeignClientApplication.class, args);
}
}
配置文件依旧没有什么变化。
压缩配置
数据的压缩分为两块,OpenFeign内部的压缩(服务提供方与消费方间的数据压缩)和全局的压缩(包括了浏览器与服务消费者间的数据压缩),一般我们都会对文本文件进行压缩。
配置文件如下:
server:
port: 8081
compression: #开启全局压缩
enabled: true
mime-types:
- text/html
- text/plain
- text/xml
- application/json
- application/xml
- application/javascript
min-response-size: 512
spring:
application:
name: feign-client
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
#feign开启数据压缩
feign:
compression:
request:
enabled: true #开启请求gzip压缩
mime-types:
- text/html
- text/plain
- text/xml
- application/json
- application/xml
- application/javascript
min-request-size: 512 #数据大小超过这个阈值就开启压缩,默认是2048字节
response:
enabled: true #开启响应gzip压缩
超时与负载均衡配置
先说负载均衡吧,跟ribbon的配置一模一样,因为OpenFeign底层就是调用的Ribbon:
#最上级代表服务提供方的服务名
feign-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #使用随机策略
超时配置如下:
ribbon:
ConnectTimeout: 1000 #默认连接超时时间 1秒
ReadTimeout: 1000 #默认处理超时时间 1秒 且必须大于等于连接超时时间
这个可能会出问题,因为ribbon和feign的ribbon版本冲突,所以我们可以直接这样配置:
feign:
client:
config:
default:
connect-timeout: 5000 #连接超时时间,原先默认的是1秒
read-timeout: 5000 #处理超时时间,原先默认也是1秒,注意这个值必须大于等于来能接超时时间
Hystrix
hystrix主要用来做服务的降级和熔断。
启动器:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
Ribbon下的服务降级
这里就以最简单的sleep模拟服务提供方处理超时。
ribbon默认是没有超时时间的,但是加了hystrix之后我们就可以返回一个托底数据,避免用户等待时间过长。
@Override
@HystrixCommand(fallbackMethod = "insertFallback")
public Map<String, Object> insertUser(User u) {
// 获取一个服务实例,参数是服务名称
ServiceInstance instance = loadBalancerClient.choose("ribbon-service");
// 拼接url
StringBuilder sb = new StringBuilder();
sb.append("http://").append(instance.getHost()).append(":").append(instance.getPort()).append("/insert?name=").append(u.getName()).append("&age=").append(u.getAge()).append("&remark=").append(u.getRemark()).append("&=job").append(u.getJob());
// 发送http请求
// 创建rest模板对象,用于发送http请求
RestTemplate template = new RestTemplate();
// 约束响应类型
ParameterizedTypeReference<Map<String, Object>> responseType = new ParameterizedTypeReference<Map<String, Object>>() {
};
/*
* 四个参数; 1.url 2.请求方法 3.请求实体 4.响应类型
*/
ResponseEntity<Map<String, Object>> response = template.exchange(sb.toString(), HttpMethod.GET, null,
responseType);
Map<String, Object> result = response.getBody();
return result;
}
private Map<String,Object> insertFallback(User u){
Map<String,Object> map = new HashMap<String, Object>();
map.put("message", "服务器繁忙,请稍后重试");
return map;
}
我们只要在调用远程方法的方法中添加注解@HystrixCommand(fallbackMethod = "insertFallback")
fallbackMethod里面填写服务降级后调用的本地方法名称,然后写一个本地方法即可。
为了扫描这个注解,我们还需要在启动类上添加一个注解:@EnableCircuitBreaker
;
Ribbon下的服务熔断
熔断就是说我判断这个远程服务出问题了,一段时间之内不再调用它了,直接返回托底数据,还是在@HystrixCommand
注解里面配置,具体配置如下:
/**
* HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,单位时间内多少请求发生错误开启熔断;
* HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,单位时间内,请求发生错误的百分比达到多少,开启熔断;
* HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,熔断后多少毫秒之内不再访问远程服务;
*/
@HystrixCommand(fallbackMethod = "insertFallback",commandProperties = {
@HystrixProperty(
name = HystrixPropertiesManager.CIRCUIT_BREAKER_REQUEST_VOLUME_THRESHOLD,
value = "10"),
@HystrixProperty(
name = HystrixPropertiesManager.CIRCUIT_BREAKER_ERROR_THRESHOLD_PERCENTAGE,
value = "50"),
@HystrixProperty(
name = HystrixPropertiesManager.CIRCUIT_BREAKER_SLEEP_WINDOW_IN_MILLISECONDS,
value = "4000")
})
OpenFeign下的服务降级
首先我们需要开启OpenFeign下的Hystrix:
feign:
hystrix:
enabled: true #开启hystrix降级处理
后面的事就很简单了,我们不是有这个嘛:
package com.springcloud.FeignClient.service;
import org.springframework.cloud.openfeign.FeignClient;
import com.springcloud.FeignClient.api.UserServiceApi;
import com.springcloud.FeignClient.service.impl.UserServiceImpl;
/**
*
* 本地服务,用于访问远程服务,和远程服务同受到UserServiceApi的约束
* 只需添加@FeignClient("远程服务名")即可
*
*/
@FeignClient(value = "feign-service",fallback = UserServiceImpl.class)
public interface UserService extends UserServiceApi{
}
配置fallback为该接口的实例,该接口里面重写的方法即为降级后的方法:
package com.springcloud.FeignClient.service.impl;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Service;
import com.springcloud.FeignClient.pojo.User;
import com.springcloud.FeignClient.service.UserService;
@Service
public class UserServiceImpl implements UserService{
@Override
public Map<String, Object> insert(User user) {
Map<String,Object> map = new HashMap<String, Object>();
map.put("message", "进入fallback方法啦!");
return map;
}
}
这样就算是写好了。
OpenFeign下的服务熔断
跟Ribbon不一样,feign下做熔断不用写注解,直接在yml里面配置就可以,但是配置的时候可能没有代码提示,但是不影响最终效果;
#配置hystrix熔断
hystrix:
command:
default:
execution:
timeout:
enable: true
isolation:
thread:
timeoutInMilliseconds: 4000
circuitBreaker: #熔断服务相关配置
enable: true #开启熔断服务
requestVolumeThreshold: 3 #单位时间内错误多少次开启熔断
sleepWindowInMilliSeconds: 3000 #熔断后多少毫秒不访问远程服务
errorThresholdPercentage: 20 #单位时间内错误百分比达到多少开启熔断
forceOpen: false #是否强制开启熔断,也就是熔断后再也不访问远程服务,默认就是false
forceClosed: false #是否强制关闭熔断,默认false
Config
config是SpringCloud的配置中心,用来管理配置文件。这是由于微服务项目每个服务都有一个配置文件,配置太多太杂,于是由配置中心统一管理比较方便。
远程仓库准备
我们把yml都放到远程仓库中,以后每一个配置文件都从远程获取即可。
Gitee或者GitHub都可以。
注意命名,例如feign-service-dev.yml,他就是代表dev环境的feign-service配置文件。
配置中心服务端
配置中心的服务端也作为一个eureka客户端,因此需要eureka客户端的启动器,还需要config服务端的启动器:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
在启动类上添加注解@EnableConfigServer
:
package com.springcloud.Config;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class, args);
}
}
其配置文件如下:
server:
port: 8888 #默认端口就是这个
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/muziliuri/cloud-config.git
# 如果是私有仓库的话,还需要用户名和密码
# username: 远程仓库的用户名
# password: 远程仓库的密码
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
我们可以这样测试一下:
http://localhost:8888/feign-service/default/master
这个url请求了我在gitee仓库中的feign-service.yml
配置文件,返回的json数据如下:
{
"name":"feign-service",
"profiles":[
"default"
],
"label":"master",
"version":"bd8c5da0f52510b8a1512b2ad9f0d862d6d45a86",
"state":null,
"propertySources":[
{
"name":"https://gitee.com/muziliuri/cloud-config.git/feign-service.yml",
"source":{
"server.port":8080,
"spring.application.name":"feign-service",
"eureka.client.service-url.defaultZone":"http://localhost:8761/eureka/"
}
}
]
}
如果我们需要请求feign-service-dev.yml
配置文件,相应的url应该改为:
http://localhost:8888/feign-service/dev/master
配置中心客户端
首先我们要导入客户端的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
然后我们由于是从远程仓库中获取配置文件,那么原来的配置文件就不需要了。
我们新建一个配置文件:bootstrap.yml
,必须是这个名字,这个配置文件的优先级是大于application.yml
的,我们在里面进行如下配置:
spring:
cloud:
config:
uri: http://localhost:8888/ #默认访问地址
name: feign-service #配置文件名称
profile: default #配置文件默认环境,默认就是default
label: master #访问地址默认分支,默认就是master
这样就可以了。
Gateway
网关就是相当于Nginx,微服务相当于Tomcat,我们在Gateway中统一配置进行路径转发。此处有个天坑就是Gateway版本必须与服务的提供者以及消费者保持一致,无论是SpringBoot还是SpringCloud,不然会容易出问题。
简单使用
首先是依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
要注意的是Gateway中不能有web依赖,否则会有冲突,启动不了。
配置文件:
server:
port: 9999
spring:
application:
name: gateway
cloud:
gateway:
enabled: true # 开启网关功能,默认就是开启的
discovery: # 网关发现机制配置信息
locator:
enabled: true # 开启网关发现机制
lower-case-service-id: true # 发现的服务命名是否要转化为小写
routes: #配置路由
- id: feign-client #命名随意,唯一即可
uri: lb://feign-client #lb代表loadbalance,feign-client代表要转发的服务名
predicates: # 断言机制
- Path=/test/** #将http://localhost:9999/test/**进行转发,转发至http://feign-client的ip端口/test/**
filters:
- StripPrefix=1 #过滤1个单位的前缀,就是将http://feign-client的ip端口/test/**过滤为http://feign-client的ip端口/**
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka/
routes这一段是手工配置,其实我们也可以不配,通过服务名来访问服务:
http://localhost:9999/feign-client/**