SpringCloud 框架
一、分布式概念
1、分布式的优点
-
处理高并发问题:
面对越来越大的业务量,一台机器的性能已经无法满足。我们需要多台机器才能满足高并发的需求。因此我 们需要垂直或水平拆分业务系统,让它变成一个分布式的架构。
-
加强系统可用性:
为了我们的系统不会因为一台机器出故障二导致整个系统不可用。所以需要利用分布式来消除单点故障,提 高系统可用性。
-
把模块拆分,使用接口通信,降低程序的耦合度
-
把项目拆分成若干个子项目,不同的团队负责不同的子项目
-
增加项目时只需增加一个子项目,调用其他子项目的接口即可
2、项目的拆分
拆分的原则:
(1)纵向拆分:按层拆分
如:
pro1——实体类(各个项目通用)
pro2——工具类
pro3——dao
pro4——service
pro5——controller
pro6——view
(2)横向拆分——按功能拆分
如:
pro1——实体类
pro2——登录
pro3——注册
…
(3)综合拆分
综合拆分是 SpringCloud 的核心思想,本文如下重点介绍:
二、综合拆分
拆分出来的项目(微服务)都有两个角色:
服务提供者(供其他服务调用);
服务消费者(调用其他服务的服务)。
服务治理:服务的启动、负载、运载等(比如运维)。
2.1 SpringCloud六大组件
1、Eureka 注册中心:
职能:提供服务者,把服务注册到Eureka中——包含服务名、IP、port;
服务消费者通过注册中心来发现、获取服务列表——调用获取到的服务。
2、Robbin:负载均衡:
职能:通过负载均衡算法调用服务提供者的服务。
负载均衡策略:轮询、随机、根据响应时间分配权重、选择并发量最小的、根据服务性能和可用性…
3、Feign:服务的通讯
职能:服务消费者通过Feign来调用服务消费者。
4、Hystrix:断路器
职能:通过服务熔断、降级来保护请求,处理压力较大的服务。
5、GateWay:路由
职能:提供路由
6、Config:配置服务
职能:提供统一的优化配置。
三、Eureka注册中心
3.1 概念
Eureka与服务提供方之间是长链接。
【说明】服务消费者不能直接调用服务!
原因:1、耦合度高:服务消费者需要储存服务提供者的IP、port,当服务提供者的IP、port发生变化后, 必须手动更新服务消费者,否则无法调用;
2、服务消费者没有负载均衡的依据;
3、当服务提供者发生变化后,服务消费者需要得知。
工作原理:
3.2 Eureka集群
3.2.1 介绍
EurekaServer:注册中心
职责:1、接收服务的注册、生成注册列表;
2、到其他注册中心获取注册信息——Eureka集群;
3、推送注册列表给服务消费者。
EurekaClient:Eureka客户端 (@EnableEurekaClient)
包含:服务提供者、服务消费者。
3.2.2 配置过程
1、新建子项目Eureka02
2、复制
3、修改application.yml文件
server:
port: 8091 #1、修改端口号
eureka:
instance:
hostname: localhost #两个eureka的hostname名一致,将自动形成通信,建立集群
client:
#2、是否向其他eureka注册————改为true
register-with-eureka: true
#3、是否不断的寻找其他eureka————改为true
fetch-registry: true
service-url:
defaultZone: http://root:root@${eureka.instance.hostname}:${server.port}/eureka/
spring:
application:
name: EUREKA02
security:
user:
name: root
password: root
四、RestTemplate接口
【作用】服务消费者调用服务提供者。 第二种方式:Feign组件。
【原理】对HTTP协议进行了封装,采用了Restful的模板。
将对象装换成JSON字符串,一定要用@RequestBody注解。
详见 Resttemplate文档。
@SpringBootApplication
@EnableEurekaClient
public class ApplyApplication {
public static void main(String[] args) {
SpringApplication.run(ApplyApplication.class, args);
}
//程序启动就将创建出bean
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
4.1 getForObject
返回值为服务提供者的返回值,服务提供者与消费者之间传递格式是JSON,两端自动转JSON。
//controller中注入RestTemplate
@Autowired
private RestTemplate restTemplate;
@GetMapping("/mv")
public String mvApplyInfo(Integer id) {
String str = restTemplate.getForObject("http://APPLYMODEL/model/del?id=" + id, String.class);
return str;
}
@GetMapping("/getInfo")
public Map getAllApplyInfo() {
List<ApplyInfo> list = restTemplate.getForObject("http://APPLYMODEL/model/getAll", List.class);
//List<ApplyInfo> list = JSON.parseObject(str, List.class);
Map<String, Object> map = new HashMap<>();
map.put("mdg", "服务器异常");
map.put("code", 0);
map.put("data", list);
map.put("count", list.size());
return map;
}
4.2 postForObject
返回值为服务提供者的返回值,服务提供者与消费者之间传递格式是JSON,两端自动转JSON。
@PostMapping("/ad")
public String addApplyInfo(ApplyInfo applyInfo) {
Integer result = restTemplate.postForObject("http://APPLYMODEL/model/add", applyInfo, Integer.class);
if (1 == result) {
return "true";
} else {
return "error";
}
}
五、Ribbon实现负载均衡
配置服务提供者的负载均衡!!!
【注意】1、Ribbin实现的是服务提供者的负载均衡(model层)、服务消费者的负载均衡需要Nginx实现;
2、负载均衡需要配置在服务消费者层,消费者获取到提供者的集群信息,消费者依据负载均衡策略决定 调用哪一个;
3、服务提供者的服务名一定要相同,服务名相同的认为是同一集群。
负载均衡主要原理: 注册中心根据负载均衡算法,给出调用建议,然后消费者根据建议进行调用。
配置步骤:
1、在服务消费者处添加 jar依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2、服务消费者中指定负载均衡算法,两种方式:
(1)创建Bean的方式 —— 服务消费者的启动类中
@SpringBootApplication
@EnableEurekaClient
public class ApplyApplication {
public static void main(String[] args) {
SpringApplication.run(ApplyApplication.class, args);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
//指定轮训的负载均衡算法————轮询
public IRule createRule(){
return new RoundRobinRule();
}
}
(2)配置文件方式(服务消费者的application.xml文件)
APPLYMODEL: #服务消费者的服务名(spring/application/name),此时也可以称作为服务消费者的集群名
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule
六、Security 安全
【作用】给Eureka注册中心设置账号密码,每一个Client(服务提供者、服务消费者)向注册中心注册必须提供账 号密码。
步骤:
1、Eureka服务中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2、在启动包下创建类,实现WebSecurityConfigurerAdapter接口。
注解方式启用Security
@EnableWebSecurity
public class WebSecurityApp extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
3、在application.xml配置文件中设置账号密码
spring:
application:
name: EUREKA01
security:
user:
name: root
password: root
4、服务提供者、服务消费者必须携代账号密码才能注册到Eureka的注册列表中
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8090/eureka,http://root:root@localhost:8091/eureka
七、分布式下的CAP定理
C:一致性,调用分布式下的集群,每一个服务提供者返回的结果都是相同的;
A:可用性,服务要高可用,搭建集群;
P:容错性,发生故障后要保证服务正常运行。
选择分析:
(1)在分布式系统中一定要保证P,允许服务出错(宕机、网络不通等),需要C和A之间进行选择;
(2)如果选择C,那么实现容错性P后,还用保持一致性,那么最终结果是服务提供者一致出错;
(3)Eureka集群选择AP,即一个服务出错后,既能保持可用性,又能保证其他程序正常运行。
八、Feign组件的使用
【原理】基于接口,使用代理方式实现服务的调用
【作用】用于服务之间的调用,简化RestTemplate操作
应用在服务消费者端。
8.1 Feign与RestTemplate区别
推荐使用Feign
都是通过对HTTP协议进行的封装
1、RestTemplate通过封装HTTP,直接发送请求,请求服务提供者。每次请求需要提供服务提供者的URL路径;
2、Feign基于接口,动态代理方式发送的请求,调用更方便;
3、RestTemplate只能调用注册到注册中心得服务,外部服务无法直接调用;
Feign可以直接通过IP方式调用外部服务;
8.2 Feign使用和相应注解
1、在消费者的pom.xml中添加jar依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2、在启动程序类上添加注解
@EnableFeignClients
3、编写接口,在接口中编写请求提供服务的方法
【注意】必须用@RequestMapping注解
@FeignClient("APPLYMODEL") //请求提供者名称
public interface ApplyFeigns {
//1、无参
@RequestMapping(value = "/model/count", method = RequestMethod.GET)
Integer getCountApply();
//2、单个参数
@RequestMapping(value = "/model/getInfoById/{id}")
ApplyInfo getApplyInfoById(@PathVariable("id") Integer id);
//3、多个参数(也可以像单个参数那样用/分开)
@RequestMapping(value = "/model/more")
ApplyInfo getMorePar(@RequestParam("applyUserId") Integer applyUserId, @RequestParam("mark") String mark);
//4、对象参
@RequestMapping(value = "/model/obj", method = RequestMethod.POST)
String getByObj(@RequestBody ApplyInfo applyInfo);
}
4、在Controller中注入接口对象,调用
@RestController
@RequestMapping("/apply")
@CrossOrigin("http://127.0.0.1:8848") //跨域
public class ApplyInfoController {
@Autowired
private ApplyFeigns applyFeigns;
//feign 1 无参
@GetMapping("/count")
public String getCount() {
return "一共" + applyFeigns.getCountApply() + "条记录";
}
//feign 2 单个参数
@GetMapping("/getInfoById")
public ApplyInfo getApplyInfoById(Integer id) {
return applyFeigns.getApplyInfoById(id);
}
//feign 多个参数
@GetMapping("/more")
public ApplyInfo getApplyInfoMoreParam(Integer applyUserId,String mark){
return applyFeigns.getMorePar(applyUserId, mark);
}
//feign 对象参
@GetMapping("/obj")
public String getByObj(ApplyInfo applyInfo){
return applyFeigns.getByObj(applyInfo);
}
}
8.3 服务降级/熔断
当消费者请求提供者时,服务者可能由于某种问题(网络延迟、宕机)不能进行正常的处理,那么Feign会调用一个“备用”的方法,这个“备用”方法返回出错信息或提示,这种情况也称为服务降级。
步骤
1、实现我们定义的Feign接口,在Feign接口中写服务降级方法。
@Component
public class ApplyFeignFallBack implements ApplyFeigns {
@Override
public Integer getCountApply() {
System.out.println("服务器异常,实行服务降级策略");
return -1;
}
@Override
public ApplyInfo getApplyInfoById(Integer id) {
return null;
}
@Override
public ApplyInfo getMorePar(Integer applyUserId, String mark) {
return null;
}
@Override
public String getByObj(ApplyInfo applyInfo) {
return null;
}
}
2、服务消费者的pom.xml中配置开启服务降级——Feign调用熔断器Hystrix实现降级的
server:
port: 8093
spring:
application:
name: APPLY
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8090/eureka,http://root:root@localhost:8091/eureka
feign:
hystrix:
enabled: true #配置使用服务降级
3、接口中指定fallback为我们的服务降级方法。
@FeignClient(value = "APPLYMODEL", fallback = ApplyFeignFallBack.class)
public interface ApplyFeigns {
@RequestMapping(value = "/model/count", method = RequestMethod.GET)
Integer getCountApply();
@RequestMapping(value = "/model/getInfoById/{id}")
ApplyInfo getApplyInfoById(@PathVariable("id") Integer id);
@RequestMapping(value = "/model/more")
ApplyInfo getMorePar(@RequestParam("applyUserId") Integer applyUserId, @RequestParam("mark") String mark);
@RequestMapping(value = "/model/obj", method = RequestMethod.POST)
String getByObj(@RequestBody ApplyInfo applyInfo);
}
4、客户端调用controller,如果发生异常或者响应超时,将服务降级/熔断。
8.3 FeignClientsFallbackFactory
如果需要统一打印引发服务降级的异常信息,那么可以使用FeignClientsFallbackFactory
1、编写FallBackFactory实现类 FeignClientsFallBackFactory.java
泛型为接口
@Component
public class ApplyFeignFallBackFactory implements FallbackFactory<ApplyFeigns> {
@Autowired
private ApplyFeignFallBack applyFeignFallBack;
@Override
public ApplyFeigns create(Throwable throwable) {
System.out.println("打印异常信息");
throwable.printStackTrace();
return applyFeignFallBack;
}
}
2、在FeignClients出处指定fallbackFactory
@FeignClient(value = "APPLYMODEL",fallbackFactory = ApplyFeignFallBackFactory.class)
public interface ApplyFeigns {
......
}
8.4 Feign实现负载均衡
Feign实现负载均衡需要整合Robbin,详见第五章。
九、Hystrix组件
【说明】8.3 当中,我们使用Feign组件并在yam文件中配置hystrix实现了服务降级功能,但那并不是原生hystrix组件实现的,是feign组件集成了hystrix组件的部分功能实现的,功能有限。这里我们将单独使用hystrix组件,感受他的强大之处。比如实现RestTemplate调用方式下的服务降级/熔断。
9.1 hystrix服务降级
依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
9.1.1 RestTemplate调用方式的服务降级
同类中定义好降级方法,调用即可。
具体步骤:
1、启动类上方添加注解: @EnableHystrix
2、同一个controller类中自定义服务降级方法
3、controller方法上方注解选取自定义降级方法
@RestController
@RequestMapping("/apply")
@CrossOrigin("http://127.0.0.1:8848")
public class ApplyInfoController {
@Autowired
private RestTemplate restTemplate;
//第3步
@GetMapping("/getInfo")
@HystrixCommand(fallbackMethod = "getInfoHystrix")
public Map getAllApplyInfo() {
List<ApplyInfo> list = restTemplate.getForObject("http://APPLYMODEL/model/getAll", List.class);
Map<String, Object> map = new HashMap<>();
map.put("mdg", "服务器异常");
map.put("code", 0);
map.put("data", list);
map.put("count", list.size());
return map;
}
//第2步
public Map getInfoHystrix() {
Map<String, String> map = new HashMap<>();
map.put("code", "1服务降级");
return map;
}
}
9.1.2 Feign使用独立的hystrix
与 9.1.1 方式相同,无需再写接口的实现类。
9.2 hystrix线程隔离
//1、Hystrix线程
@HystrixCommand(fallbackMethod = "getCount3FallBack", commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy",value = "THREAD"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
//2、信号量---了解
@HystrixCommand(fallbackMethod = "getCount3FallBack", commandProperties = {
@HystrixProperty(name = "execution.isolation.strategy",value = "SEMAPHORE"),
@HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10")
})
9.3 hystrix断路器
hystrix实现断路器,也是通过fallbackMethod指定断路后的方法实现的,但是与服务降级不同的是:
服务降级——请求会访问到服务提供者,服务提供者方法发生异常,最后走FallbackMethod;
断路器——请求不会访问到服务提供者,一段时间内达到了一定的阈值,将自动调用FallbackMethod。断路器默认每隔五秒进入一次半开状态,放行一个请求如运行成功则关闭断路状态。
代码:
//2、hystrix整合RestTemplate 实现:服务降级&断路器
@GetMapping("/getInfo")
//注意:必须有HystrixCommand注解,Hystrix面板才能监控到
@HystrixCommand(fallbackMethod = "getInfoHystrix", commandProperties = {
//线程隔离
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000"),
//断路器
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "5000")
})
public Map getAllApplyInfo() {
System.out.println("controller,当前线程基本信息:" + Thread.currentThread().getContextClassLoader());
List<ApplyInfo> list = restTemplate.getForObject("http://APPLYMODEL/model/getAll", List.class);
Map<String, Object> map = new HashMap<>();
map.put("mdg", "服务器异常");
map.put("code", 0);
map.put("data", list);
map.put("count", list.size());
return map;
}
//2、hystrix 整合 RestTemplate 服务降级 ————降级方法
public Map getInfoHystrix() {
Map<String, String> map = new HashMap<>();
map.put("code", "1服务降级");
return map;
}
9.4 hystrix控制面板/仪表盘Dashboard
hystrix控制面板实现请求的监测,可以在controller模块中配置启动,也可以单独创建子项目Model来配置。
1、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
2、启动类上添加注解
@EnableHystrixDashboard
3、启动类中配置(创建Bean)
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
4、浏览器打开dushboard主页
5、监控中心
dushboard中输入网址:http://localhost:8093/hystrix.stream 进入监控中心
访问实现服务降级的方法(具有@HystrixCommand(fallbackMethod = “M1”) 注解修饰的方法)将被检测到。
十、Config配置中心组件
【功能】对子项目中的配置文件实现统一管理
配置步骤:
1、单独创建配置中心的子项目
- 配置中心项目的pom.xml文件中引入config服务端的依赖
- 被管理的子项目的pom.xml文件中引入config客户端的依赖
<!--1、配置中心server端插件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!--2、配置中心client端插件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
2、创建启动类
@EnableConfigServer注解:表名这是一个配置中心的服务端,其他交由我统一管理的都是我的客户端。
@SpringBootApplication
@EnableConfigServer
public class ProConfig {
public static void main(String[] args) {
SpringApplication.run(ProConfig.class, args);
}
}
3、管理中心的resources根目录下创建application.yml配置文件
server:
port: 8098 #该配置中心的端口号,被管理的子项目通过这个端口号拉取他们的配置文件
spring:
application:
name: PROCONFIG
profiles:
active: native
cloud:
config:
server:
native:
search-locations: classpath:config #其他交由我管理的配置文件在config文件夹下
4、管理中心的resources文件夹下创建config文件夹,里面放入各个子项目的配置文件
与原来配置文件不同的是:以前所有子项目的配置文件都叫application.yml,现在他们可以自定义一个名字(但是必须有 -local.yml后缀),配置文件内容与之前的一致。如下:
5、被配置中心统一管理的子项目必须指定拉取配置文件地址、文件名
方法:被管理的子项目resources文件夹下创建bootstrap.yml文件。
spring:
cloud:
config:
uri: http://localhost:8098 #管理中心的端口号
name: model2 #管理中心的我的配置文件的名称
profile: local #管理中心的我的配置文件的名称后缀
【说明】application.yml文件与bootstrap.yml文件,都是SpringCloud中规定好的文件名,规范大于代码。
区别:bootstrap.yml文件的加载顺序高于application.yml文件的加载顺序。
十一、GateWay路由
【说明】路由也能实现负载均衡,提供客户端不用经过服务消费者直接调用服务提供者的方式。
1、依赖
<dependencies>
<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-gateway</artifactId>
</dependency>
<!--配置中心client端插件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
</dependencies>
2、配置文件
server:
port: 8097
spring:
application:
name: GATEWAY
cloud:
gateway:
routes:
- id: ID001 #配置文件内不能重复即可
predicates:
- Path=/model/** #model为服务提供者controller接口的第一个requestMapping
uri: lb://APPLYMODEL #服务提供者名字
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8090/eureka,http://root:root@localhost:8091/eureka
3、启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}
2、配置文件
server:
port: 8097
spring:
application:
name: GATEWAY
cloud:
gateway:
routes:
- id: ID001 #配置文件内不能重复即可
predicates:
- Path=/model/** #model为服务提供者controller接口的第一个requestMapping
uri: lb://APPLYMODEL #服务提供者名字
eureka:
client:
service-url:
defaultZone: http://root:root@localhost:8090/eureka,http://root:root@localhost:8091/eureka
3、启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class GateWayApplication {
public static void main(String[] args) {
SpringApplication.run(GateWayApplication.class, args);
}
}