SpringCloud 全家桶基本介绍及使用
1.微服务生态关系:
分为:SpringCloud Netflix,SpringCloud Apache,SpringCloud Alibaba
Netflix有:Eureka(注册中心)、Feign(服务调用、远程调用)、ribbon(负载均衡及重试)、Hystrix(熔断及降级)、Zuul(网关),Config(配置中心)
SpringCloud Apache官方:Service Registry(服务注册) Service Discovery(服务发现)、OpenFeign或者Restemplate(远程服务调用)、LoadBalance(负载均衡)、Circuit Breaker(熔断降级)、Gateway(网关)
SpringCloud Alibaba: 很火,后续单独做介绍
Netflix很多组件都不在维护,SpringBoot新版本官方逐渐把Netflix的组件移除,目前仅仅保留了Eureka,但是很多老项目还使用旧的组件,而且springcloud 组件很多思想都是相通的,还是有学习的必要。
2.单体应用和微服务
单体应用:把所有的功能及模块整合到一起,打成一个jar或者war包,部署到服务器启动应用
优点:适用于简单的项目,开发,测试及部署都简单
缺点:不适用于大型项目,随着项目不断发展功能越来越多,单体应用劣势就显示出来。
- 复杂度高:代码量巨大,多达百万或者千万,添加一个小功能可能会造成其他隐患。
- 技术债务:不坏不修复,不敢修复
- 持续部署困难:编译时间长,部署时间长,改一个小功能,全部部署,会导致无关的功能暂停使用
- 可靠性差:一个小的bug,造成整个应用无法崩溃无法使用
- 扩展受限:不利于扩展
- 阻碍创新:不利于引进新的技术。传统应用springmvc 改为springboot需要大量改动。
微服务:把整个系统按照业务拆分,拆分成一个个小服务,就是微服务,所有的微服务组成一个整体。分久必合合久必分
微服务优点
- 独立部署。不依赖其他服务,耦合性低,不用管其他服务的部署对自己的影响。
- 易于开发和维护:关注特定业务,所以业务清晰,代码量少,模块变的易开发、易理解、易维护。
- 启动块:功能少,代码少,所以启动快,有需要停机维护的服务,不会长时间暂停服务。
- 局部修改容易:只需要部署 相应的服务即可,适合敏捷开发。
- 技术栈不受限:java,node.js等
- 按需伸缩:某个服务受限,可以按需增加内存,cpu等。
- 职责专一。专门团队负责专门业务,有利于团队分工。
- 代码复用。不需要重复写。底层实现通过接口方式提供。
- 便于团队协作:每个团队只需要提供API就行,定义好API后,可以并行开发。
微服务缺点
- 分布式固有的复杂性:容错(某个服务宕机),网络延时,调用关系、分布式事务等,都会带来复杂。
- 分布式事务的挑战:每个服务有自己的数据库,有点在于不同服务可以选择适合自身业务的数据库。订单用MySQL,评论用Mongodb等。目前最理想解决方案是:柔性事务的最终一致性。
- 接口调整成本高:改一个接口,调用方都要改。
- 测试难度提升:一个接口改变,所有调用方都得测。自动化测试就变的重要了。API文档的管理也尤为重要。推荐:yapi。
- 运维要求高:需要维护 几十 上百个服务。监控变的复杂。并且还要关注多个集群,不像原来单体,一个应用正常运行即可。
- 重复工作:比如java的工具类可以在共享common.jar中,但在多语言下行不通,C++无法直接用java的jar包。
3.Eureka
注册中心,作于服务的自动注册与发现。分为两部分:Eureka Server和Eureka Client.微服务上线会将自己的IP,端口port,服务名servername提交到注册中心,注册中心维护者一份注册列表。服务的消费者会从注册中心拉取服务注册列表,获取具体的调用路由再去调用服务提供者。为了避免每次都访问注册中心,client会定时去server拉取注册表信息到缓存到client本地。
- 客户需要每30秒发送一次心跳来续租,3个30s没有续租,(默认90s续租到期)eureka服务端会在注册列表将这个实例删除。
- 客户端会定期从eureka 拉取注册列表进行增量更新,默认30s一次。
- 发送心跳续租、拉取注册列表和续租到期时间都可以自己设置。
- eureka提供的restful url: 可以查看eureka 的信息
Eureka集群搭建
修改host文件:
进入目录 C:\Windows\System32\drivers\etc,
在host文件上添加
127.0.0.1 euk1.com
127.0.0.1 euk2.com
新建springboot项目
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
在主启动类添加 @EnableEurekaServer 注解
@EnableEurekaServer
@SpringBootApplication
public class CloudEurekaApplication {
public static void main(String[] args) {
SpringApplication.run(CloudEurekaApplication.class, args);
}
}
搭建集群需要两个以上服务,euk1.com 和euk2.com配置文件分别为:
#是否将自己注册到其他Eureka Server,默认为true 需要
spring.application.name=EurekaServer
#web端口,服务是由这个端口处理rest请求的
server.port=7901
eureka.client.register-with-eureka=true
#是否从eureka server获取注册信息, 需要
eureka.client.fetch-registry=true
#设置服务注册中心的URL,用于client和server端交流
#此节点应向其他节点发起请求
eureka.client.serviceUrl.defaultZone=http://euk2.com:7902/eureka/
#主机名,必填
eureka.instance.hostname=euk1.com
management.endpoint.shutdown.enabled=true
#是否将自己注册到其他Eureka Server,默认为true 需要
spring.application.name=EurekaServer
#web端口,服务是由这个端口处理rest请求的
server.port=7902
eureka.client.register-with-eureka=true
#是否从eureka server获取注册信息, 需要
eureka.client.fetch-registry=true
#设置服务注册中心的URL,用于client和server端交流
#此节点应向其他节点发起请求
eureka.client.serviceUrl.defaultZone=http://euk1.com:7901/eureka/
#主机名,必填
eureka.instance.hostname=euk2.com
management.endpoint.shutdown.enabled=true
euk1和euk2的服务名都是EurekaServer,表示他们都是同一个服务,端口分别问7901和7902,他们相互注册和拉取信息
启动项目,浏览器访问:localhost:7901
eureka server集群搭建成功。
eureka提供的restful url: 可以查看eureka 的信息
http://localhost:7901/eureka/apps
http://localhost:7901/eureka/status
DiscoveryClient接口:是springcloud 服务注册发现的顶级接口
自我保护机制:当Server在短时间内丢失过多客户端时,那么Server会进入自我保护模式,会保护注册表中的微服务不被注销掉。当网络故障恢复后,退出自我保护模式。
客户端每分钟续约数量小于客户端总数的85%时会触发保护机制
Eureka Client:
客户端将信息上报给服务端
引入依赖client
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
将自己的健康信息上报给server,加入健康检查监控依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置文件:
server.port=8081
spring.application.name=consumer
#向注册中心注册url
eureka.client.serviceUrl.defaultZone=http://euk1.com:7901/eureka/
#将自己的健康信息上报给server,状态up或者down
eureka.client.healthcheck.enabled=true
management.endpoints.web.exposure.include=*
client 更改健康状态up,down,并将状态信息上报给eureka server. 如果为down则不再调用此服务
import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator;
import org.springframework.stereotype.Service;
@Service
public class HealthService implements HealthIndicator{
private Boolean flag = true;
public void setFlag(Boolean flag) {
this.flag = flag;
}
public Boolean getFlag() {
return flag;
}
@Override
public Health health() {
// TODO Auto-generated method stub
if(flag) {
return Health.up().build();
}
return Health.down().build();
}
}
4.Actuator
健康检查,监控应用
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
默认只暴露health 和 info 两个端口,可以通过配置暴露所有的监控端点:
#健康检查,暴露所有的监控端点
management.endpoints.web.exposure.include=*
访问:http://localhost:7901/actuator 查看所有的监控端点
查看具体的监控端点:
例如:http://localhost:7901/actuator/health
5.RestTemplate+LoadBalance
实现远程调用和负载均衡
用于远程调用,发送http请求调用其它微服务接口
RestTemplate 整合LoadBalance
package com.example.demo;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class BeanConfig {
@Bean
//开启负载均衡,整合LoadBalance,整合后RestTemplate 只能通过服务名来调用服务,不能通过ip+端口调用服务
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
实体类People
package com.example.demo;
public class People {
private String name;
private Integer age;
private String tel;
private String addr;
public People() {
}
public People(String name,Integer age,String tel,String addr) {
super();
this.name = name;
this.age = age;
this.tel = tel;
this.addr = addr;
}
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 getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
RestTeamplate 发送get 请求,post 表单请求 或者是post json 请求
package com.example.demo;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@RestController
public class ControllerDemo01 {
private static final Logger LOGGER = LoggerFactory.getLogger(ControllerDemo01.class);
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient; //服务注册发现顶级接口
@Autowired
private LoadBalancerClient loadBalancerClient; //LoadBalance 顶级接口
@RequestMapping("/test1")
public String test1() {
List<ServiceInstance> list = discoveryClient.getInstances("PROVIDER");
LOGGER.info(list.toString());
ServiceInstance serviceInstance = list.get(0);
String url = "http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/getPort";
return restTemplate.getForObject(url, String.class);
}
@RequestMapping("/test2")
public String test2() {
String url = "http://PROVIDER/getPort";
return restTemplate.getForObject(url, String.class);
}
@RequestMapping("/test3")
public int test3() {
ServiceInstance instance = loadBalancerClient.choose("PROVIDER");
return instance.getPort();
}
@RequestMapping("/v/test4")
public String test4() {
return "test4";
}
/**
* 发送get请求,通过服务名调用,从map获取请求参数
* @return
*/
@RequestMapping("/getPeopleObject1")
public People getPeopleObject1() {
Map<String, Object> map = new HashMap<>();
map.put("name", "小王");
map.put("age", 15);
map.put("tel", "138888888");
map.put("addr", "中国");
String url = "http://PROVIDER/getPeopleObject?name={name}&age={age}&tel={tel}&addr={addr}";
People people = restTemplate.getForObject(url, People.class, map);
return people;
}
@RequestMapping("/getPeopleObject2")
public People getPeopleObject2() {
String url = "http://PROVIDER/getPeopleObject?name={1}&age={2}&tel={3}&addr={4}";
People people = restTemplate.getForObject(url, People.class, "小红",23,"1272272927","美国");
return people;
}
/**
* 发送post form 表单请求
* @return
*/
@RequestMapping("/getPeopleObject3")
public People postPeopleObject3() {
LinkedMultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("name", "小王");
map.add("age", "15");
map.add("tel", "138888888");
map.add("addr", "中国");
String url = "http://PROVIDER/getPeopleObject2";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>(map, headers);
People people = restTemplate.postForObject(url, request, People.class);
return people;
}
/**
* 发送post json 请求
* @return
* @throws JsonProcessingException
*/
@RequestMapping("/getPeopleObject4")
public People postPeopleObject4() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
People people = new People("json", 20, "272292", "北京");
String jsonContent = objectMapper.writeValueAsString(people);
String url = "http://PROVIDER/getPeopleObject3";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> request = new HttpEntity<String>(jsonContent, headers);
People people1 = restTemplate.postForObject(url, request, People.class);
return people1;
}
}
6.OpenFeign
OpenFeign 默认实现了ribbon,可以实现远程调用及负载均衡
引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
主启动类加上@EnableFeignClients注解
在主启动类加@EnableFeignClients注解开启对@FeignClient注解类扫描加载处理,生成代理对象注入到ioc容器。扫描所有@FeignClient注解的类,并将这些信息注入Spring IoC容器中。当定义的Feign接口中的方法被调用时,通过JDK的代理方式,来生成具体的RequestTemplate来远程调用。
@FeignClient(服务名)+@RequestMapping(url路径) http://服务名/url 来远程访问。如果有参数一定要加上@RequestParam,如果发送的是post json 请求,加上@RequestBody
FeignController
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.example.demo.service.FeignService;
@RestController
public class FeignController {
@Autowired
private FeignService feignService;
@RequestMapping("/feignTest")
public String feignTest() {
return feignService.feignTest();
}
@RequestMapping("/feignTest2")
public String feignTest2() {
return feignService.feignTest2("王五","20");
}
@RequestMapping("/feignTest3")
public String feignTest3() {
People people = new People("赵六",22,"29742","美国");
return feignService.feignTest3(people);
}
@RequestMapping("/feignTest4")
public String feignTest4() {
return feignService.feignTest4("天天",30,"12494242","台湾");
}
@RequestMapping("/feignTest5")
public String feignTest5() {
System.out.println("发起请求");
return feignService.feignTest5();
}
}
FeignService
package com.example.demo.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.example.demo.People;
@FeignClient(name="PROVIDER",fallback = FeignServiceFallback.class)
public interface FeignService {
@RequestMapping("/feignTest")
public String feignTest();
@RequestMapping("/feignTest2")
public String feignTest2(@RequestParam String name, @RequestParam String age);
@RequestMapping("/feignTest3")
public String feignTest3(@RequestBody People people);
@RequestMapping("/feignTest4")
public String feignTest4(@RequestParam String name,@RequestParam Integer age,@RequestParam String tel,@RequestParam String addr);
@RequestMapping("/feignTest5")
public String feignTest5();
}
Feign 连接超时、请求超时重试配置
package com.example.demo;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.Request;
import feign.Retryer;
@Configuration
public class FeignConfig {
//连接超时
private static int connectTimeoutMillis = 1000;
//请求超时
private static int readTimeoutMillis = 2000;
@Bean
public Request.Options options(){
return new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS, readTimeoutMillis, TimeUnit.MILLISECONDS, true);
}
//重试
@Bean
public Retryer feignRetryer() {
return new Retryer.Default(100, 1000, 4);
}
}
7.Hystrix
熔断降级。比喻:保险丝过载熔断。熔断请求不再处理,执行降级方法
默认情况:
触发条件:最少20个请求,失败达到50%
每当20个请求中,有50%失败时,熔断器就会打开,此时在调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否吧熔断器关闭,或者继续打开。达到熔断之后,那么后面它就直接不去调该微服务
作用:
- 为系统提供保护机制。在依赖的服务出现高延迟或失败时,为系统提供保护和控制。
- 防止雪崩。
- 快速失败:Fail Fast。同时能快速恢复。侧重点是:(不去真正的请求服务,发生异常再返回),而是直接失败。
- 监控:Hystrix可以实时监控运行指标和配置的变化,提供近实时的监控、报警、运维控制。
- 回退机制:fallback,当请求失败、超时、被拒绝,或当断路器被打开时,执行回退逻辑。回退逻辑我们自定义,提供优雅的服务降级。
- 自我修复:断路器打开一段时间后,会自动进入“半开”状态,可以进行打开,关闭,半开状态的转换。
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
主启动类添加@EnableCircuitBreaker,旧版本是@EnableHystrixBreaker
RestTemplate + Hystrix
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
@RestController
public class HystrixController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/hystrixTest1")
@HystrixCommand(fallbackMethod = "back")
public String hystrixTest1() {
String url = "http://PROVIDER/getPort";
return restTemplate.getForObject(url, String.class);
}
private String back() {
System.out.println("降级方法!!!");
return "降级方法";
}
}
OpenFeign+hystrix
配置文件:
#Feign整合Hystrix
feign.circuitbreaker.enabled=true
#旧版
#feign.hystrix.enabled=true
package com.example.demo.service;
import org.springframework.stereotype.Component;
import com.example.demo.People;
@Component
public class FeignServiceFallback implements FeignService {
public String feignTest() {
return "feignTest 降级";
}
@Override
public String feignTest2(String name, String age) {
// TODO Auto-generated method stub
return null;
}
@Override
public String feignTest3(People people) {
// TODO Auto-generated method stub
return null;
}
@Override
public String feignTest4(String name, Integer age, String tel, String addr) {
// TODO Auto-generated method stub
return null;
}
@Override
public String feignTest5() {
// TODO Auto-generated method stub
return null;
}
}
8.HystrixDashboard
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>
spring-cloud-starter-netflix-hystrix-dashboard
</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
主启动类
@EnableHystrixDashboard
暴露所有监控端点 management.endpoints.web.exposure.include=*
允许被hystrix-dashboard 监控 hystrix.dashboard.proxy-stream-allow-list=*
访问: http://localhost:8081/hystrix 图形化界面
页面输入: localhost:8081/actuator/hystrix.stream,健康上报
访问方法,图像化界面监控
9.Sleuth + zipkin
Sleuth 链路追踪
如果能跟踪每个请求,中间请求经过哪些微服务,请求耗时,网络延迟,业务逻辑耗时等。我们就能更好地分析系统瓶颈、解决系统问题。因此链路跟踪很重要。
span(跨度),基本工作单元。一次链路调用,创建一个span
trace(跟踪),一组共享“root span”的span组成的树状结构 称为 trace,trace也有一个64位ID,trace中所有span共享一个trace id。类似于一颗 span 树
zipkin:图形化界面,Sleuth将日志发送到zipkin,zipkin收集日志以图形化的方式将信息展示出来
添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
配置文件
#sleuth+zipkin
#设置采样率
spring.sleuth.sampler.rate=1
spring.zipkin.base-url=http://127.0.0.1:9411/
访问localhost:9411
10.SpringCloud Admin
admin serve和admin client
新建项目admin server
添加依赖
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-server</artifactId>
</dependency>
主启动类 @EnableAdminServer
启动admin server
admin client:
添加依赖:
<!-- Admin 服务 -->
<dependency>
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置文件
#spring cloud admin
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
spring.boot.admin.client.url=http://localhost:8080
浏览器访问admin
http://localhost:8080/
11.SpringCloud gateway
网关
待更新。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。