SpringCloud
2021-05-07 15:20:21 36 举报
AI智能生成
SpringCloud基本知识以及对比dubbo+zookeeper,springcloud五大组件代码测试,都已上传码云:https://gitee.com/xiaoyeya/springcloud
作者其他创作
大纲/内容
聊聊微服务
微服务的定义
微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,去高耦合
每一个微服务提供单个业务功能的服务,一个服务做一件事情
这些服务使用不同的编程语言书写,以及不同数据存储技术,并保持最低限度的集中式管理。
简而言之,就是组件服务,最早出现在Unix的设计理念中
微服务的优缺点
优点
每个服务足够内聚,足够小,代码容易理解
微服务是松耦合的,是有功能意义的服务
微服务能使用不同的语言开发
微服务允许容易且灵活的方式集成自动部署
微服务只是业务逻辑的代码,不会和HTML,CSS,或其他的界面混合
每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一的数据库;
缺点
开发人员要处理分布式系统的复杂性
多服务运维难度,随着服务的增加,运维压力增大
系统部署依赖问题
服务间通信成本问题
数据一致性问题
微服务的技术栈
服务开发
SpringBoot
Spring
SpringMVC
服务注册与发现
Eureka
Zookeeper
服务调用
RPC
RestTemplate
服务熔断器
Hystrix
负载均衡
Ribbon
Nginx
服务接口调用
Feign
消息队列
Kafka
RocketMQ
RabbitMQ
ActiveMQ
服务配置中心管理
SpringCloudConfig
服务路由(API网关)
Zuul
服务监控
Metrics
DashBoard
服务部署
Docker
个人推荐使用SpringCloud作为开发技术栈
选型依据
整体解决方案和框架成熟度
社区热度高,十分活跃
正在快速发展,可维护性高
当前各大IT公司用的微服务架构有那些
阿里:dubbo+HFS
京东:JFS
新浪:Motan
当当网:DubboX
SpringCloud入门
官方概要图
SpringCloud和SpringBoot的关系
SpringBoot专注于开发单个个体微服务
SpringCloud是关注全局的微服务协调整理治理框架
SpringBoot可以离开SpringCloud独立使用,开发项目
但SpringCloud离不开SpringBoot,属于依赖关系
Dubbo 和 SpringCloud对比
Dubbo
服务注册中心:Zookeeper
服务调用方式:RPC
服务监控:Dubbo-monitor
断路器:不完善
服务网关:无
分布式配置:无
服务跟踪:无
消息总栈:无
数据流:无
批量任务:无
SpringCloud
服务注册中心:Spring Cloud Netfilx Eureka
服务调用方式:RestTemplate
服务监控:Spring Boot Admin
断路器:Spring Cloud Netfilx Hystrix
服务网关:Spring Cloud Netfilx Zuul
分布式配置:Spring Cloud Config
服务跟踪:Spring Cloud Sleuth
消息总栈:Spring Cloud Bus
数据流:Spring Cloud Stream
批量任务:Spring Cloud Task
最大的区别是Spring Cloud 抛弃了Dubbo的RPC通信,采用的是基于HTTP的REST方式
SpringCloud版本选择
大版本说明
实际开发版本关系
五大神兽
服务注册中心:Spring Cloud Netfilx Eureka
什么是Eureka
Eureka 是 Netflix 的一个子模块,也是核心模块之一
Eureka 是一个基于 Rest 的服务,用于定位服务,以实现云端中间层服务发现和故障转移
Eureka 采用了 C-S 的设计架构。Eureka Server 作为服务注册功能的服务器
Eureka三大角色
Eureka Server:提供服务的注册与发现
Service Provider:服务生产方,将自身服务注册到Eureka中,从而使服务消费方能狗找到
Service Consumer:服务消费方,从Eureka中获取注册服务列表,从而找到消费服务
构建一个简单的分布式项目
Eureka Server
导入pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
编写配置文件
server:
port: 7001
#Eureka配置
eureka:
instance:
hostname: eureka7001 #eureka服务端实例名称
client:
register-with-eureka: false #是否向注册中心注册自己
fetch-registry: false #是否提取注册表
service-url:
defaultZone: http://localhost:7001/eureka/
编写启动类
package com.xuhu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author 小也
* @create 2021/5/4 16:45
*/
@SpringBootApplication
@EnableEurekaServer //启动eureka服务端的注解
public class EurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServer_7001.class,args);
}
}
启动服务,打开浏览器,输入localhost:7001进入eureka后台界面
Service Provider
把实体类放在单独的module中
导入lombok依赖
编写pojo类
package com.xuhu.springcloud.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* @author 小也
* @create 2021/5/2 11:08
*/
@Data
@NoArgsConstructor
@Accessors(chain = true)
public class Dept implements Serializable { //网络传输对象一定要实现序列化
private Long deptno;
private String dname;
private String db_source;
public Dept(String dname) {
this.dname = dname;
}
}
数据库建表DDL
CREATE TABLE `dept` (
`deptno` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`dname` varchar(255) DEFAULT '',
`db_source` varchar(255) DEFAULT DATABASE(),
PRIMARY KEY (`deptno`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
导入pom依赖
<!--我们首先要拿到我们的实体类-->
<dependency>
<groupId>org.xuhu</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--数据库-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!--eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--测试单元-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!--日志门面-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--test-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--jetty-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
编写mvc文件
mapper接口
package com.xuhu.springcloud.dao;
import com.xuhu.springcloud.pojo.Dept;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author 小也
* @create 2021/5/3 22:21
*/
@Mapper
@Repository
public interface DeptDao {
/**
* 添加部分
* @param dept
* @return
* 这里一定要写@Param("dept"),mybatis3没有做自动匹配,是个大坑
*/
public boolean addDept(@Param("dept") Dept dept);
/**
* 根据id查询
* @param id
* @return
*/
public Dept queryById(@Param("id") Long id);
/**
* 查询所有部门
* @return
*/
public List<Dept> queryAll();
}
mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC
"-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xuhu.springcloud.dao.DeptDao">
<insert id="addDept" parameterType="com.xuhu.springcloud.pojo.Dept">
insert into dept (dname,db_source) values (#{dept.dname},DATABASE())
</insert>
<select id="queryById" resultType="com.xuhu.springcloud.pojo.Dept" parameterType="Long">
select * from dept where deptno = #{id}
</select>
<select id="queryAll" resultType="com.xuhu.springcloud.pojo.Dept">
select * from dept
</select>
</mapper>
service接口
package com.xuhu.springcloud.service;
import com.xuhu.springcloud.pojo.Dept;
import java.util.List;
/**
* @author 小也
* @create 2021/5/3 22:45
*/
public interface DeptService {
/**
* 添加部门
* @param dept
* @return
*/
public boolean addDept(Dept dept);
/**
* 根据id查询
* @param id
* @return
*/
public Dept queryById(Long id);
/**
* 查询所有部门
* @return
*/
public List<Dept> queryAll();
}
service实现类
package com.xuhu.springcloud.service;
import com.xuhu.springcloud.dao.DeptDao;
import com.xuhu.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author 小也
* @create 2021/5/3 22:46
*/
@Service
public class DeptServiceImpl implements DeptService{
@Autowired
private DeptDao deptDao;
@Override
public boolean addDept(Dept dept) {
return deptDao.addDept(dept);
}
@Override
public Dept queryById(Long id) {
return deptDao.queryById(id);
}
@Override
public List<Dept> queryAll() {
return deptDao.queryAll();
}
}
controller视图跳转
package com.xuhu.springcloud.controller;
import com.xuhu.springcloud.pojo.Dept;
import com.xuhu.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author 小也
* @create 2021/5/3 22:47
*/
@RestController
@RequestMapping("/provider/dept")
public class DeptProviderController {
@Autowired
private DeptService deptService;
@Autowired
private DiscoveryClient client;
@PostMapping("/add")
public boolean addDept(@RequestBody Dept dept){
return deptService.addDept(dept);
}
@GetMapping("/getById/{id}")
public Dept getById(@PathVariable("id") Long id){
return deptService.queryById(id);
}
@GetMapping("/getAll")
public List<Dept> getAll(){
return deptService.queryAll();
}
/**
* 返回所有服务
* @return
*/
@GetMapping("/discovery")
public Object discovery(){
return client.getServices();
}
}
编写配置文件
# 端口配置
server:
port: 8001
#mybatis配置
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
#spring配置
spring:
application:
name: springcloud-provider-dept #当前服务名
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
username: root
password: root
#eureka配置
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
instance:
instance-id: springcloud-provider-dept-8001 #修改eureka上的默认描述信息
prefer-ip-address: true #开启服务ip追踪
#远程调用信息完善
info:
author: xuhu
编写启动类
package com.xuhu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author 小也
* @create 2021/5/3 23:02
*/
@SpringBootApplication
@EnableEurekaClient //开启Eureka客户端
@EnableDiscoveryClient //开启服务发现
public class DeptProvider_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProvider_8001.class,args);
}
}
Service Consumer
导入pom依赖
<dependency>
<groupId>org.xuhu</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
编写config配置,注册restTemplate
package com.xuhu.springcloud.config;
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;
/**
* @author 小也
* @create 2021/5/4 10:49
*/
@Configuration
public class ConfigBean { //@Configuration就相当于ApplicationContext.xml
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
编写controller视图跳转
package com.xuhu.springcloud.controller;
import com.xuhu.springcloud.pojo.Dept;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import java.util.List;
/**
* @author 小也
* @create 2021/5/4 10:09
*/
@RestController
@RequestMapping("/consumer/dept")
public class DeptConsumerController {
// private static final String REST_URL_PREFIX = "http://localhost:8001/provider";
//SPRINGCLOUD-PROVIDER-DEPT是来自我们eureka的instance-id,忽略端口号
private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT/provider";
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/add")
public boolean add(Dept dept){
return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add",dept,boolean.class);
}
@RequestMapping("/getById/{id}")
public Dept getById(@PathVariable("id") Long id){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/getById/" + id, Dept.class);
}
@RequestMapping("/getAll")
public List<Dept> getAll(){
return restTemplate.getForObject(REST_URL_PREFIX + "/dept/getAll", List.class);
}
}
编写配置文件
server:
port: 80
#eureka配置
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
编写启动类
package com.xuhu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author 小也
* @create 2021/5/4 11:01
*/
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class,args);
}
}
我们现在直接访问localhost/consumer/dept/getAll就可以通过restTemplate调用到provider的方法
搭建eureka集群
新建module把Eureka Server中的文件复制两次,端口号分别改成7001、7002、7003
更改系统host文件,因为我们的eureka服务端不能重名,否则无法搭建集群
host文件路径:C:\Windows\System32\drivers\etc\hosts
添加三个映射
127.0.0.1 eureka7001
127.0.0.1 eureka7002
127.0.0.1 eureka7003
127.0.0.1 eureka7002
127.0.0.1 eureka7003
修改这三个eurekaServer的配置文件
第一个eurekaServer
server:
port: 7001
#Eureka配置
eureka:
instance:
hostname: eureka7001 #eureka服务端实例名称
client:
register-with-eureka: false #是否向注册中心注册自己
fetch-registry: false #是否提取注册表
service-url:
defaultZone: http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
第二个eurekaServer
server:
port: 7002
#eureka配置
eureka:
instance:
hostname: eureka7002 #eureka服务器实例名称
client:
register-with-eureka: false #是否向注册中心注册自己
fetch-registry: false #是否提取服注册表
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7003:7003/eureka/
第三个eurekaServer
server:
port: 7003
#eureka配置
eureka:
instance:
hostname: eureka7003 #eureka服务端实例名称
client:
register-with-eureka: false #是否向注册中心注册自己
fetch-registry: false #是否提取注册表
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/
修改复制出来的两个module的主启动类不要重名
最后把这三个服务启动起来访问eureka7001:7001或者eureka7002:7002或者eureka7003:7003
如图就表示成功了
C(Consistency)A(Availability)P(Partition tolerance)理论
一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求
CA:单点集群,满足一致性,可用性的系统,通常可扩展性较差
CP:满足一致性,分区容错的系统,通常性能不是特别高
AP:满足可用性,分区容错的系统,通常可能对一致性要求低一些
Zookeeper 保证的是 CP —> 满足一致性,分区容错的系统,通常性能不是特别高
Eureka 保证的是 AP —> 满足可用性,分区容错的系统,通常可能对一致性要求低一些
负载均衡(在客户端实现)
实现负载均衡的前提是有多个服务提供者供我们使用
新建两个module,把我们之前写的Provider里的文件复制到新的module中
配置文件中端口分别修改成8001、8002、8003
配置文件中的eureka.instance.instance-id最后面的端口号也对应修改成对应的端口号
数据库也新建两个库,除了名字不同,表结构均一致,最后一个db_source字段对应当前数据库名,方便我们观察到底调用的是哪个服务
Netflix Ribbon
Consumer添加pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
修改config配置类
package com.xuhu.springcloud.config;
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;
/**
* @author 小也
* @create 2021/5/4 10:49
*/
@Configuration
public class ConfigBean { //@Configuration就相当于ApplicationContext.xml
@Bean
@LoadBalanced //默认轮询算法,算法可以自定义
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
启动8001、8002、8003端口服务提供服务,7001端口提供注册中心,80端口请求服务(要是电脑内存大于等于16G,可以把7002端口、7003端口的注册中心都启动起来,我的电脑只有8G,开多了会很吃力,就开这5个也能达到我们想要的效果)
多次访问localhost/consumer/dept/getAll会发现我们查询出来的数据db_source这一列会在db01、bd02、db03中轮流切换,这是因为我们采用了轮询算法,自带的还有随机算法,并且可以自定义算法
自定义Ribbon算法
自定义算法类继承AbstractLoadBalancerRule,该包不要和主启动类所在的包同级,要跟启动类所在包同级
根据自带的轮询以及随机算法,我们可以仿照写出自定义负载均衡算法
主启动类配置我们的自定义算法
@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "MICROSERVICECLOUD-DEPT",configuration=MySelfRule.class)
public class DeptConsumer80_App {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer80_App.class, args);
}
}
Feign(其也是基于Ribbon,只是将调用方式RestTemplete 更改成Service 接口)
新建module,把之前80端口module的内容复制过来
添加pom依赖
<dependency>
<groupId>org.xuhu</groupId>
<artifactId>springcloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
修改我们的实体类module
创建service,我们使用feign是基于接口实现的,不再是restTemplate了
package com.xuhu.springcloud.service;
import com.xuhu.springcloud.pojo.Dept;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
/**
* @author 小也
* @create 2021/5/6 10:41
*/
@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT")
@Component
public interface DeptFeignClientService {
@PostMapping("/provider/dept/add")
public boolean addDept(@RequestBody Dept dept);
@GetMapping("/provider/dept/getById/{id}")
public Dept getById(@PathVariable("id") Long id);
@GetMapping("/provider/dept/getAll")
public List<Dept> getAll();
}
修改我们新的带feign的module的controller视图跳转
package com.xuhu.springcloud.controller;
import com.xuhu.springcloud.pojo.Dept;
import com.xuhu.springcloud.service.DeptFeignClientService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author 小也
* @create 2021/5/4 10:09
*/
@RestController
@RequestMapping("/consumer/dept")
public class DeptConsumerController {
@Autowired
private DeptFeignClientService service; //我们在实体module刚定义的service接口
@RequestMapping("/add")
public boolean add(Dept dept){
return service.addDept(dept);
}
@RequestMapping("/getById/{id}")
public Dept getById(@PathVariable("id") Long id){
return service.getById(id);
}
@RequestMapping("/getAll")
public List<Dept> getAll(){
return service.getAll();
}
}
修改我们新的带feign的主启动类
package com.xuhu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
/**
* @author 小也
* @create 2021/5/6 10:45
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages = {"com.xuhu.springcloud.service"})
@ComponentScans({@ComponentScan("com.xuhu.springcloud.service")})
public class DeptConsumerFeign_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumerFeign_80.class,args);
}
}
启动8001、8002、8003端口服务提供服务,7001端口提供注册中心,新80端口请求服务,feign默认就是轮询算法
断路器:Netflix Hystrix
分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免失败!
熔断机制是对应服务雪崩的一种微服务链路保护机制
当链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息
代码测试
@HystrixCommand(fallbackMethod = "")注解实现方法熔断
新建module,复制8001端口服务端的内容
添加pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
修改controller视图
package com.xuhu.springcloud.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.xuhu.springcloud.pojo.Dept;
import com.xuhu.springcloud.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author 小也
* @create 2021/5/3 22:47
*/
@RestController
@RequestMapping("/provider/dept")
public class DeptProviderController {
@Autowired
private DeptService deptService;
@Autowired
private DiscoveryClient client;
@PostMapping("/add")
public boolean addDept(@RequestBody Dept dept){
return deptService.addDept(dept);
}
@GetMapping("/getById/{id}")
//服务异常回调hystrixGetById函数
@HystrixCommand(fallbackMethod = "hystrixGetById")
public Dept getById(@PathVariable("id") Long id){
Dept dept = deptService.queryById(id);
if(dept==null){
throw new RuntimeException();
}
return dept;
}
public Dept hystrixGetById(@PathVariable("id") Long id){
return new Dept().setDeptno(id).setDname("没有该数据").setDb_source("没有该数据");
}
@GetMapping("/getAll")
public List<Dept> getAll(){
return deptService.queryAll();
}
@GetMapping("/discovery")
public Object discovery(){
return client.getServices();
}
}
修改主启动类,添加开启熔断机制的注解
package com.xuhu.springcloud;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
/**
* @author 小也
* @create 2021/5/3 23:02
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient //开启服务发现
@EnableCircuitBreaker //开启hystrix熔断机制
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class,args);
}
}
继承FallbackFactory抽象工厂类的类实现服务降级
新建module,复制8001端口服务端的内容
添加pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
在实体类module添加熔断处理类(实现FallbackFactory,重写create方法,实现并返回返回实体类module的接口)
package com.xuhu.springcloud.service;
import com.xuhu.springcloud.pojo.Dept;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @author 小也
* @create 2021/5/6 13:40
*/
@Component
public class DeptFeignClientFailfactory implements FallbackFactory {
@Override
public DeptFeignClientService create(Throwable throwable) {
return new DeptFeignClientService() {
@Override
public boolean addDept(Dept dept) {
return false;
}
@Override
public Dept getById(Long id) {
return new Dept()
.setDeptno(id)
.setDname("服务器压力过大,该服务暂时不可用")
.setDb_source("服务器压力过大,该服务暂时不可用");
}
@Override
public List<Dept> getAll() {
ArrayList<Dept> depts = new ArrayList<>();
depts.add(new Dept()
.setDeptno(0L)
.setDname("服务器压力过大,该服务暂时不可用")
.setDb_source("服务器压力过大,该服务暂时不可用"));
return depts;
}
};
}
}
修改主启动类,添加开启熔断机制的注解
package com.xuhu.springcloud;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
/**
* @author 小也
* @create 2021/5/3 23:02
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient //开启服务发现
@EnableCircuitBreaker //开启hystrix熔断机制
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class,args);
}
}
服务未出现问题时
正常情况
服务出现问题
服务降级,但不会爆出错误页面,比较友善
服务熔断和服务降级的区别
服务熔断
服务端
某个服务超时或异常,引起熔断~,类似于保险丝(自我熔断)
服务降级
客户端
从整体网站请求负载考虑,当某个服务熔断或者关闭之后,服务将不再被调用,此时在客户端,我们可以准备一个 FallBackFactory ,返回一个默认的值(缺省值)。会导致整体的服务下降,但是好歹能用,比直接挂掉强。
hystrix--dashboard仪表盘
随时监控服务调用情况,快速掌握现在服务运行情况
代码测试
新建module
引入pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
配置端口(我这里是9001)
新建主启动类,注解开启hystrix-dashboard仪表盘
package com.xuhu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
/**
* @author 小也
* @create 2021/5/6 14:12
*/
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboard_9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboard_9001.class,args);
}
}
在8001服务端主启动类注册ServletRegistoryBean
package com.xuhu.springcloud;
import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
/**
* @author 小也
* @create 2021/5/3 23:02
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient //开启服务发现
@EnableCircuitBreaker //开启hystrix熔断机制
public class DeptProviderHystrix_8001 {
public static void main(String[] args) {
SpringApplication.run(DeptProviderHystrix_8001.class,args);
}
@Bean
public ServletRegistrationBean hystrixMetricsStreamServlet(){
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new HystrixMetricsStreamServlet());
servletRegistrationBean.addUrlMappings("/actuators/hystrix.stream"); //路径固定
return servletRegistrationBean;
}
}
开启8001、9001准备测试
浏览器输入localhost:9001/hystrix进入dashboard主页
按照提示输入服务进程对应地址等信息,开始监控
刚开始会显示Loading
我们新开窗口请求我们带熔断的接口
dashboard连接成功,开始监控
服务网关:Netflix Zuul
Zull包含了对请求的路由和过滤
路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础
滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础
Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他服务的消息
代码测试
新建module
导入pom依赖
<!--因为zuul路由网关要被注册到eureka中,所以需要导入eureka依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
编写application.yml配置文件
server:
port: 9527 #端口号
spring:
application:
name: springcloud-zuul #微服务名称
eureka:
instance:
instance-id: springcloud-zuul-9527 #eureka中的实例id
prefer-ip-address: true #开启ip追踪
client:
service-url: #注册到eureka集群
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
zuul:
ignored-services: "*" #忽略所有请求
prefix: "/xuhu" #请求必须携带/xuhu前缀
routes:
mydept.serviceId: springcloud-provider-dept #注册中心的微服务名称
mydept.path: /springcloud/** #通过/springcloud/**调用远程服务,如果没有ignored-services: "*"的话,
也是可以通过微服务名称进行远程调用的
编写主启动类
package com.xuhu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
* @author 小也
* @create 2021/5/6 14:50
*/
@SpringBootApplication
@EnableZuulProxy //开启zuul路由网关
public class ZuulApplication_9527 {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication_9527.class,args);
}
}
启动80、7001、8001、9527开始测试
打开eureka注册中心,发现zuul已经成功注册进来了
此时http://localhost:8001/provider/dept/getById/1可以正常使用
此时通过zuul路由网关访问http://localhost:9527/xuhu/springcloud/provider/dept/getById/1,也能正常使用了
但是脱去前缀就无法访问,http://localhost:9527/springcloud/provider/dept/getById/1
使用微服务名称访问接口也是不能继续用了,http://localhost:9527/xuhu/springcloud-provider-dept/provider/dept/getById/1
只要我们把zuul微服务的端口改成80,那用户访问就是域名加前缀加接口名了,接近现实情况
分布式配置:Spring Cloud Config
spring cloud config 分为服务端和客户端两部分
服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密,解密信息等访问接口
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息
spring cloud config 分布式配置中心的作用
集中式管理配置文件
不同环境,不同配置,动态化的配置更新,分环境部署,比如 /dev /test /prod /beta /release
运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
当配置发生变动时,服务不需要重启,即可感知到配置的变化,并应用新的配置
代码测试
首先编写服务端,连接git远程仓库
远程创建仓库,上传两个配置文件
eureka配置文件
spring:
profiles:
active: dev
---
server:
port: 7001
#spring配置
spring:
profiles: dev
application:
name: springcloud-config-eureka
#Eureka配置
eureka:
instance:
hostname: eureka7001 #eureka服务端实例名称
client:
register-with-eureka: false #是否向注册中心注册自己
fetch-registry: false #是否提取注册表
service-url:
defaultZone: http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
---
server:
port: 7001
#spring配置
spring:
profiles: test
application:
name: springcloud-config-eureka
#Eureka配置
eureka:
instance:
hostname: eureka7001 #eureka服务端实例名称
client:
register-with-eureka: false #是否向注册中心注册自己
fetch-registry: false #是否提取注册表
service-url:
defaultZone: http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
---
服务提供者配置文件
spring:
profiles:
active: dev
---
# 端口配置
server:
port: 8001
#mybatis配置
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
#spring配置
spring:
profiles: dev
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
username: root
password: root
#eureka配置
eureka:
client:
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
instance:
instance-id: springcloud-provider-dept-8001 #修改eureka上的默认描述信息
prefer-ip-address: true #开启服务ip追踪
#远程调用信息完善
info:
author: xuhu
---
# 端口配置
server:
port: 8001
#mybatis配置
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
#spring配置
spring:
profiles: test
application:
name: springcloud-provider-dept
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC&useSSL=false
username: root
password: root
#eureka配置
eureka:
client:
service-url:
defaultZone: http://eureka7001:7001/eureka/,http://eureka7002:7002/eureka/,http://eureka7003:7003/eureka/
instance:
instance-id: springcloud-provider-dept-8001 #修改eureka上的默认描述信息
prefer-ip-address: true #开启服务ip追踪
#远程调用信息完善
info:
author: xuhu
新建config服务端
新建module
导入pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
编写配置文件
server:
port: 3344
spring:
application:
name: springcloud-config-3344
cloud:
config:
server:
git:
uri: https://gitee.com/xiaoyeya/springcloud-config.git # gitee的https连接,不要使用ssh连接
新建主启动类
package com.xuhu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.config.server.EnableConfigServer;
/**
* @author 小也
* @create 2021/5/7 13:26
*/
@SpringBootApplication
@EnableConfigServer //开启远程配置
public class SpringCloudConfig_3344 {
public static void main(String[] args) {
SpringApplication.run(SpringCloudConfig_3344.class,args);
}
}
编写客户端,从服务端拿取配置信息
把eureka的配置替换成远程配置
新建module
复制之前的7001端口服务内容
添加远程配置依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
编写配置文件
application.yml
spring:
application:
name: springcloud-config-eureka-7001
bootstrap.yml(项目根配置文件,和application.yml同级)
spring:
cloud:
config:
name: config-eureka
label: master
profile: dev
uri: http://localhost:3344 # 这里写的是我们springcloud-config的服务端
主启动类添加开启远程配置注解
package com.xuhu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author 小也
* @create 2021/5/4 16:45
*/
@SpringBootApplication
@EnableEurekaServer //启动eureka服务端的注解
public class ConfigEurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(ConfigEurekaServer_7001.class,args);
}
}
把服务提供者的配置替换成远程配置
新建module
复制之前的8001端口服务内容
添加远程配置依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
<version>2.1.1.RELEASE</version>
</dependency>
编写配置文件
application.yml
spring:
application:
name: springcloud-config-eureka-8001 # 这里服务名要和之前在实体类module中的feign配置的一样
要不然feign无法通过服务名从eureka中调用,后面的端口号无所谓
bootstrap.yml(项目根配置文件,和application.yml同级)
spring:
cloud:
config:
name: config-provider-dept
label: master
profile: dev
uri: http://localhost:3344 # 这里写的是我们springcloud-config的服务端
主启动类添加开启远程配置注解
package com.xuhu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author 小也
* @create 2021/5/4 16:45
*/
@SpringBootApplication
@EnableEurekaServer //启动eureka服务端的注解
public class ConfigEurekaServer_7001 {
public static void main(String[] args) {
SpringApplication.run(ConfigEurekaServer_7001.class,args);
}
}
开始测试
我们启动3344,7001,8001,80这四个端口服务
我们一定要先启动334端口服务,从远程拿到配置信息,不然我们的服务会出问题
比如我先启动了eureka,发现eureka的端口竟然是8080
比如我先启动了eureka,发现eureka的端口竟然是8080
访问http://localhost/consumer/dept/getAll
远程修改连接的数据库,从db02改成db03,那么我们查出的数据也应该改变
0 条评论
下一页