SpringCloud
面试题
一、什么是微服务?
-
基于SpringBoot 提供了一套微服务解决方案,是各个微服务技术的集合体,微服务全家桶
-
微服务,目前还没有统一的标准,就是maven 开发中的一个个的小modul,使用springboot 开发的一个个小模块,专业的事情交给专业的人来做,一个模块就只做这一件事,强调的是个体。强调的是服务的大小,关注的是某一个点,也就是一个modul。
举例:一个医院有很多科室,每一个科室专门治一类病,每个科室就叫一个微服务。
-
微服务架构,微服务架构是一种架构模式 或者说是一种架构风格,他提倡将单一应用程序划分成一组小的服务,每个服务运行在自己独立的进程中,各服务之间相互配合 相互调用.各自可以进行单独的启用,销毁。自己可以拥有自己单独的数据库。
举例 :不同的科室分布在医院的不同楼层,对外显示的就是微服务架构。
二、什么是SpringCloud
基于SpringBoot 提供了一套微服务解决方案,是各个微服务技术的集合体,微服务全家桶, 完整的微服务架构。
三、为什么选择SpringCloud
功能 | Netfix/Spring Cloud | dubbo | 其他(gRP、motai等) |
---|---|---|---|
定位 | 完整的微服务框架 | 服务框架 | RPC框架,但是需要整合其他zk等 |
支持Rest | 是 Ribbon支持多种序列化选择 | 否 | 否 |
支持RPC | 否 | 是 | 是 |
服务注册与发现 | Eureka, | 是 | zookeeper /consul |
负载均衡 | 服务端zuul+客户端Ribbon | 是(客户端) | 茅台是,其他否 |
配置服务 | Netfix ,cloudconfigserver集中配置 | zookeeper | zookeeper |
高可用 | 熔断Hystrix+客户端Ribbon | 是(客户端) | |
调用监控 | zuul边缘服务,API网关 | 否 | |
典型案例 | Netfix | 无 | Sina Google |
文档丰富度 | 高 | 一般 | 一般 |
社区活跃 | 5年不维护 | 一般 | |
其他 | 与spring集成容易 |
四、Cloud 和 Boot的关系
Boot 关注的是微观,他就是一个一个的服务,Cloud 关注的是宏观
举例 : boot 就是一个个科室,而Cloud 就是把各个科室组合起来对外展示的医院,Cloud 必然依赖于Boot
~~~dubbo和cloud 的区别(组装机与品牌机)
技术维度 | cloud | dubbo |
---|---|---|
服务注册中心 | SpringCloud Netfix Eureka | zookeeper |
服务调用方式 | RestAPI | 远程调用RPC |
服务监控 | SpringBootAdmin | Dubbo-monitor |
断路器 | SpringCloud Netfix Hystrix | 不完善 |
服务网关 | SpringCloud Netfix zuul | 无 |
分布式配置 | SpringCloud Config | 无 |
服务跟踪 | SpringCloud Sleuth | 无 |
消息总线 | SpringCloud Bus | 无 |
数据流 | SpringCloud Stream | 无 |
务 | SpringCloud Task | 无 |
… | … | … |
最大区别:SpringCloud 抛弃了dubbo 的RPC通信 采用的是基于HTTP的REST方式
五、SpringCloud_Rest (一种架构模式)
有个父工程Parent 带着三个子模块module案例:
1 Praent
-
一个父工程下面有N多模块的子module
-
创建一个maven工程 和选用 maven-quickstart模板 ,Packageing一定要选择pom,将后续的公共jar抽取出来 类似一个父类
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.tydic</groupId> <artifactId>springcloud_mybook</artifactId> <version>1.0-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>1.5.9.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.0.4</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> <scope>test</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> </dependencies> </dependencyManagement> </project>
1.1 xxx–api 封装的是entity/接口/公共配置等
-
创建第一个module,SpringBoot的那个步骤
pom
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.tydic</groupId> <artifactId>book-api</artifactId> <version>0.0.1-SNAPSHOT</version> <name>book-api</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
entity:
/** * 实体类 * @author: tydic lyl * @time: 2019/11/20 */ @Data //生成get set @NoArgsConstructor //空参构造 @AllArgsConstructor//全参构造 @SuppressWarnings("") //警告强压制 @Accessors(chain=true)//显示get set chain为一个布尔值,如果为true生成的set方法返回this,为false生成的set方法是void类型。 public class book implements Serializable { private Integer id; private String name; private Double price; private String read; private String manu; private String db_source;// 因为是微服务,所以可以拥有独立数据库 }
1.2xxx–provider -8001 微服务的提供者
-
提供者跟之前一样 会有Dao sevice controller 启动类等。完成自测浏览器可查到 Json 串 即可
pom’
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.tydic</groupId> <artifactId>book-provider-8001</artifactId> <version>0.0.1-SNAPSHOT</version> <name>book-provider-8001</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency><!-- 引入自己定义的api通用包,可以使用bookEntity --> <groupId>com.tydic</groupId> <artifactId>book-api</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </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> <version>1.3.2</version> </dependency> <!-- 通用mapper --> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper-spring-boot-starter</artifactId> <version>2.0.2</version> </dependency> <!-- Druid连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.6</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!--修改后立即生效,热部署--> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> </dependency>--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies> </project>
yml
server: port: 8001 spring: application: name: book-provider-8001 datasource: #type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型 #driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包 driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/mybookshop # 数据库名称 username: root password: root dbcp2: min-idle: 5 # 数据库连接池的最小维持连接数 initial-size: 5 # 初始化连接数 max-total: 5 # 最大连接数 max-wait-millis: 200 # 等待连接获取的最大超时时间
dao:
/** * dao层控制 * @author: tydic lyl * @time: 2019/11/20 */ public interface IBookDao { /*查询所有*/ @Select("select * from goods_info") @Results( id = "findAllBookMap", value = { @Result(id = true,column = "id",property = "id"), @Result(column = "bname",property = "name"), @Result(column = "bprice",property = "price"), @Result(column = "bread",property = "read"), @Result(column = "manu",property = "manu") } ) public List<Book> findAll(); /*通过id查询*/ @Select("select * from goods_info where id=#{id};") @ResultMap("findAllBookMap") public Book findById(Integer id); /*添加*/ @Insert(" insert into goods_info(bname,bprice,bread,manu) values(#{bname},#{bprice}#{bread}#{manu});") public boolean addBook(Book book); /*更新*/ @UpdateProvider(type = BookProvider.class,method = "updateBook") //@Update("update goods_info set(bname,bprice,bread,manu) values(#{bname},#{bprice}#{bread}#{manu}); ") public boolean updateBook(Book book); /*刪除*/ @Delete("delete from goods_info where id= #{id}") public boolean deleteBook( @Param("id") Integer id); } /** * provider返回修改的动态SQL语句 * @author: tydic 雷云龙 * @time: 2019/11/20 */ public class BookProvider { public String updateBook(final Book book) { return new SQL() { { UPDATE("goods_info"); if (book.getName() != null) { SET("bname=#{name}"); } if (book.getPrice() != null) { SET("bprice=#{price}"); } if (book.getRead() != null) { SET("bread=#{read}"); } if (book.getManu() != null) { SET("manu=#{manu}"); } WHERE("id=#{id}"); } }.toString(); }
Serviceimpl
/** * Service层 * @author: tydic lyl * @time: 2019/11/20 */ @Service("bookService") public class serviceImpl implements BookService { @Autowired private IBookDao bookDao; @Override public List<Book> findAll() { return bookDao.findAll(); } @Override public Book findById(Integer id) { return bookDao.findById(id); } @Override public boolean addBook(Book book) { return bookDao.addBook(book); } @Override public boolean update(Book book) { return bookDao.updateBook(book); } @Override public boolean delete(Integer id) { return bookDao.deleteBook(id); } }
Controller
/** * controller层 * @author: tydic lyl * @time: 2019/11/20 */ @RestController public class BookController { @Autowired private BookService bookService; /** * 查询所有 */ @GetMapping(value = "/provider/Book/list") public List<Book> list() { return bookService.findAll(); } /** * 根据id 查询 */ @GetMapping(value = "/provider/Book/get/{id}") public Book get(@PathVariable Integer id) { return bookService.findById(id); } /** * 添加 */ @PostMapping(value = "/provider/Book/add") public boolean add(@RequestBody Book book) { return bookService.addBook(book); } /** * 根据id删除 */ @DeleteMapping(value = "/provider/Book/remove/{id}") public boolean remove(@PathVariable Integer id) { return bookService.delete(id); } /** * 修改 */ @PutMapping(value = "/provider/Book/update") public boolean update(@RequestBody Book book) { return bookService.update(book); } }
特别注意@MapperScan
@SpringBootApplication @MapperScan("com.tydic.dao") //spring启动时自动扫描dao下面所有接口 public class BookProvider8001Application { public static void main(String[] args) { SpringApplication.run(BookProvider8001Application.class, args); } }
当 dao接口 注入不到service时候用@MapperScan注解,spring会自动扫描dao下的所有接口
1.3xxx–consumer-dept-80 微服务的消费者
-
消费者只管消费 ,不管生产,因此没有service 等具体的操作部分
-
ConfigBean类(也就是applcationConfig.xml (@Configuration))中 使用 RestTempate 。
ConfigBean
/** * 配置bean * @author: tydic 雷云龙 * @time: 2019/11/20 */ @Configuration public class ComsumerConfig { @Bean public RestTemplate getRestTemplate() { return new RestTemplate(); } }
RestTemplate :
RestTemplate,提供了多种便捷访问远程HTTP服务的方法,
是一种便捷访问restful 服务模板类,是Spring提供的用于访问Rest客户端的模板工具集。
使用restTemplate访问restful:( url ,requestMap,ResponseBean.class)
rest请求地址,请求参数,HTTP相应后转换的类型
消费端Controller
/** * 消费者的controller层 * @author: tydic 雷云龙 * @time: 2019/11/20 */ @RestController @SuppressWarnings("unchecked") public class ConsumerController { private static final String REST_URL_PREFIX = "http://localhost:8001"; @Autowired private RestTemplate restTemplate; /*消费端的查询所有*/ @RequestMapping(value="/consumer/Book/list") public List<Book> list() { /*将路径进行拼接,拼接到生产端*/ return restTemplate.getForObject(REST_URL_PREFIX+"/provider/Book/list", List.class); } /*消费端的id查询*/ @RequestMapping(value="/consumer/Book/get/{id}") public Book get(@PathVariable("id") Integer id) { return restTemplate.getForObject(REST_URL_PREFIX+"/provider/Book/get/"+id, Book.class); } /*消费端的添加*/ @RequestMapping(value="/consumer/Book/add") public boolean add(Book book) { return restTemplate.postForObject(REST_URL_PREFIX+"/provider/Book/add", book, Boolean.class); } /*消费端的删除*/ @RequestMapping(value="/consumer/Book/remove/{id}") public boolean remove(@PathVariable("id") Integer id) { return restTemplate.postForObject(REST_URL_PREFIX+"/provider/Book/remove",id, Boolean.class); } /*消费端的修改*/ @RequestMapping(value="/consumer/Book/remove") public boolean update(Book book) { return restTemplate.postForObject(REST_URL_PREFIX+"/provider/Book/update",book, Boolean.class); } }
六、Eureka服务注册与发现
1.Eureka是什么?
- 主管服务的注册和发现。
- 只需要用服务的标识符就可以访问到服务,而不需要调用服务的配置文件了,功能类似于dubbo的注册中心,比如ZooKeeper
~~~ACID
A:原子性 C:一致性 I:独立性 D:持久性
~~~ CAP
C:强一致性 A:可用性 P:分区容错性
RDBMS (sqlServer mysql oracle)–> ACID
NOSQL(redis mongdb ) —> CAP
~~~Eureka 和zookeeper 区别
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TXnCHtgE-1576461550430)(C:\Users\lyl\AppData\Roaming\Typora\typora-user-images\1576122818197.png)]
由于分布式系统只能三进二,P必须选,
4.1 Zookeeper保证CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka保证AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
\1. Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
\2. Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
\3. 当网络稳定时,当前实例新的注册信息会被同步到其它节点中
因此, Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。
总结
Eureka作为单纯的服务注册中心来说要比zookeeper更加“专业”,因为注册服务更重要的是可用性,我们可以接受短期内达不到一致性的状况。不过Eureka目前1.X版本的实现是基于servlet的java web应用,它的极限性能肯定会受到影响。期待正在开发之中的2.X版本能够从servlet中独立出来成为单独可部署执行的服务。
2.新建Eureka服务端
pom
<!--eureka-server服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
yml
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
启动类
@EnableEurekaServer
在启动类上标注启动该新组件技术的相关注解标签(接受其他微服务注册进来)
结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DWQb196W-1576461550446)(C:\Users\雷云龙\AppData\Roaming\Typora\typora-user-images\1574300427627.png)]
3.将其他工程注册
- 服务端在70 端
修改8001pom
将eureka客户端注到工程
<!-- 将微服务provider注册进eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
修改8001yml
eureka:
client: #客户端注册进eureka服务列表内
service-url:
defaultZone: http://localhost:7001/eureka
主启动类
@EnableEurekaClient
4.服务集群配置
1.创建7001 和7002
按照7001 的pom 和 主启动类 修改 2 和3
2.修改映射配置
找到C:\Windows\System32\drivers\etc路径下的hosts文件
修改 #127.0.0.1 eureka7001.com
#127.0.0.1 eureka7002.com
#127.0.0.1 eureka7003.com
3.yml
-
7001:
server: port: 7001 eureka: instance: hostname: eureka7001.com #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己。 fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 service-url: #单机 defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址(单机)。 defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
-
7002
server: port: 7002 eureka: instance: hostname: eureka7002.com #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己。 fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 service-url: #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。 defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/
-
7003
server: port: 7003 eureka: instance: hostname: eureka7003.com #eureka服务端的实例名称 client: register-with-eureka: false #false表示不向注册中心注册自己。 fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务 service-url: #defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。 defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
-
8001
server: port: 8001 spring: application: name: book-provider datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/mybookshop username: root password: root type: com.alibaba.druid.pool.DruidDataSource dbcp2: min-idle: 5 initial-size: 5 max-total: 5 max-wait-millis: 200 eureka: client: #客户端注册进eureka服务列表内 service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: microservicecloud-dept8001 #自定义服务名称信息 prefer-ip-address: true #访问路径可以显示IP地址 info: app.name: tydic-springcloud_mybook company.name: www.tydic.com build.artifactId: $project.artifactId$ build.version: $project.version$
七、Ribbon负载均衡(进程内消费端)
1.什么是负载均衡
-
https://github.com/Netflix/ribbon/wiki/Getting-Started
-
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。
-
负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。
负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA。
常见的负载均衡有软件Nginx,LVS,硬件 F5等。
相应的在中间件,例如:dubbo和SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义。
2. 配置初步
consumer
pom:
<!-- Ribbon相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
yml
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
configbeans
/**
* 配置bean
* @author: tydic 雷云龙
* @time: 2019/11/20
*/
@Configuration
public class ComsumerConfig {
@Bean
@LoadBalanced //负载均衡
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
启动类
/**
* 启动类
* @author: tydic 雷云龙
* @time: 2019/11/20
*/
@SpringBootApplication
@EnableEurekaClient //将客户端注册进eureka
public class BookConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(BookConsumerApplication.class, args);
}
}
ConsumerController
// private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://BOOK-PROVIDER";
//此处的BOOK-PROVIDER就是 8001 中的name大写 也就是 eureka 中的provider
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0I2LiL29-1576461550447)(C:\Users\雷云龙\AppData\Roaming\Typora\typora-user-images\1574386249354.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QqszCDRT-1576461550447)(C:\Users\雷云龙\AppData\Roaming\Typora\typora-user-images\1574387172356.png)]
3. 构建Ribbon
架构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e1uGJUiT-1576461550448)(C:\Users\雷云龙\AppData\Roaming\Typora\typora-user-images\1574390873084.png)]
创建8002 8003
- pom中 几乎和8001 一样,保留自己
- 完全复制src 注意修改启动类
- yml中 几乎一样,修改port 修改 数据库 修改自定义的服务器名称 ,切记 对外暴露名要统一
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OW26WkEj-1576461550448)(C:\Users\雷云龙\AppData\Roaming\Typora\typora-user-images\1574391872952.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QOvFPYub-1576461550448)(C:\Users\雷云龙\AppData\Roaming\Typora\typora-user-images\1574391891282.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qxtMw1Y4-1576461550449)(C:\Users\雷云龙\AppData\Roaming\Typora\typora-user-images\1574391931879.png)]
4.自定义轮询算法
-
官网随机算法
-
public class RandomRule extends AbstractLoadBalancerRule { /** * Randomly choose from all living servers */ @edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE") public Server choose(ILoadBalancer lb, Object key) { if (lb == null) { return null; } Server server = null; while (server == null) { if (Thread.interrupted()) { return null; } List<Server> upList = lb.getReachableServers(); List<Server> allList = lb.getAllServers(); int serverCount = allList.size(); if (serverCount == 0) { /* * No servers. End regardless of pass, because subsequent passes * only get more restrictive. */ return null; } int index = chooseRandomInt(serverCount); server = upList.get(index); if (server == null) { /* * The only time this should happen is if the server list were * somehow trimmed. This is a transient condition. Retry after * yielding. */ Thread.yield(); continue; } if (server.isAlive()) { return (server); } // Shouldn't actually happen.. but must be transient or a bug. server = null; Thread.yield(); } return server; } protected int chooseRandomInt(int serverCount) { return ThreadLocalRandom.current().nextInt(serverCount); } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } }
-
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-op2aORSE-1576461550449)(C:\Users\雷云龙\AppData\Roaming\Typora\typora-user-images\1574403398769.png)]
5.自定义[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ic2Ih1bh-1576461550450)(C:\Users\雷云龙\AppData\Roaming\Typora\typora-user-images\1574654177607.png)]
/**
* 自定义为 5 次一轮询
* @author: tydic 雷云龙
* @time: 2019/11/22
*/
public class LylRule extends AbstractLoadBalancerRule {
//total = 0: 每个服务上面停留,想要排 5 个人 达到 5 人 ,下个窗口 这五个都在 一个 800窗口 所有index1
//index = 0: 当前使用的 用谁服务 0 1 2 = 8001 8002 8003
private int total = 0; //服务的次数,人数
private int currentindex = 0; //当前是哪个窗口提供的服务 012
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
private Server choose(ILoadBalancer lb, Object key) {
if (lb == null) { //ribbon
return null;
}
Server server = null;
while (server == null) { // 8001 8002 8003
if (Thread.interrupted()) { //线程中断
return null;
}
List<Server> allList = lb.getAllServers(); // 拿到所有的 服务 8001 8002 8003 可能存在休眠
List<Server> upList = lb.getReachableServers(); //拿到能用的服务
int serverCount = allList.size(); // 3
if (serverCount == 0) {
return null;
}
// total = 0 次
//index = 0 号
if (total < 5) {
server = upList.get(currentindex); //0号窗口8001 1号8002 2号8003
total++;
} else {
currentindex++; // 下个窗口1号继续
total = 0;
if (currentindex >= upList.size()) {
currentindex = 0;
}
}
if (server == null) { // 如果没有活着的服务,中断线程
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
}
6.启动类上
/**
* 启动类
* @author: tydic 雷云龙
* @time: 2019/11/20
*/
@SpringBootApplication
@EnableEurekaClient //将客户端注册进eureka
@RibbonClient(name ="BOOK-PROVIDER",configuration = MyRule.class)//引入自定义的均衡算法
public class BookConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(BookConsumerApplication.class, args);
}
}
八、Feign负载均衡
粗暴点说:就是注解版的 Ribbon, 面向接口。。。
1. 参照 consumer创建 consumer-feign
pom (api 的 pom 也加)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
在api中新建接口
/**
* 把 controller 写成接口形式,供Feign调用
* @author: tydic 雷云龙
* @time: 2019/11/25
*/
@FeignClient(value = "BOOK-PROVIDER")
@Repository
public interface BookFallbackService {
/**
* 查询所有
*/
@GetMapping(value = "/provider/Book/list")
public List<Book> list();
/**
* 根据id 查询
*/
//@PathVariable 路径变量
@GetMapping(value = "/provider/Book/get/{id}")
public Book get(@PathVariable Integer id) ;
/**
* 添加
*/
@Options(useGeneratedKeys = true)//添加时候id自增
@PostMapping(value = "/provider/Book/add")
public boolean add(@RequestBody Book book) ;
/**
* 根据id删除
*/
@DeleteMapping(value = "/provider/Book/remove/{id}")
public boolean remove(@PathVariable Integer id) ;
/**
* 修改
*/
@PutMapping(value = "/provider/Book/update")
public boolean update(@RequestBody Book book) ;
}
将接口注入到 Feign 的controller
/**
* Feign的controller层
* @author: tydic 雷云龙
* @time: 2019/11/25
*/
@RestController
public class ConsumerController {
@Autowired
@Qualifier("bookFallbackService")
BookFallbackService service = null;
/*消费端的查询所有*/
@RequestMapping(value="/consumer/Book/list")
public List<Book> list()
{
return this.service.list();
}
/*消费端的id查询*/
@RequestMapping(value="/consumer/Book/get/{id}")
public Book get(@PathVariable("id") Integer id)
{
return this.service.get(id);
}
/*消费端的添加*/
@RequestMapping(value="/consumer/Book/add")
public boolean add(Book book)
{
return this.service.add(book);
}
/*消费端的删除*/
@RequestMapping(value="/consumer/Book/remove/{id}")
public boolean remove(@PathVariable("id") Integer id)
{
return this.service.remove(id);
}
/*消费端的修改*/
@RequestMapping(value="/consumer/Book/remove")
public boolean update(Book book)
{
return this.service.update(book);
}
}
启动类
@EnableFeignClients (basePackages = {"com.tydic.service"})
2.后续。。。
九、Hystrix断路器
1.服务熔断
-
https://github.com/Netflix/Hystrix/wiki/How-To-Use
-
分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。 -
服务熔断相当于保险丝,可以保护服务
.创建 hystrx 8001 (8001一样 ,代替 8001)
pom
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
yml
instance:
instance-id: book-provider8001-hystrix #自定义服务名称信息
prefer-ip-address: true #访问路径可以显示IP地址
举例:数据库一共有5 条信息,当查 id = 6 时候
/**
* 根据id 查询
*/
@GetMapping(value = "/provider/Book/get/{id}")
@HystrixCommand(fallbackMethod = "processHystrix_Get")
public Book get(@PathVariable("id") Integer id)
{
Book book = this.bookService.findById(id);
if(null == book)
{
throw new RuntimeException("该ID:"+id+"没有没有对应的信息");
}
return book;
}
public Book processHystrix_Get(@PathVariable("id") Integer id)
{
return new Book().setId(id)
.setName("该ID:"+id+"没有没有对应的信息,null--@HystrixCommand")
.setDb_source("no this database in MySQL");
}
启动类:@EnableCircuitBreaker//对hystrixR熔断机制的支持
测试通过
2.服务降级
整体资源快不够了,忍痛将某些服务先关掉,待渡过难关,再开启回来。
服务降级处理是在客户端实现完成的,与服务端没有关系
后续。。。
3.实时调用监控Hystrix Dashboard
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
后续。。。。
十、 zuul路由网关
https://github.com/Netflix/zuul/wiki/Getting-Started
1、是什么?
Zuul包含了对请求的路由和过滤两个最主要的功能:
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础.
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
注意:Zuul服务最终还是会注册进Eureka
提供=代理+路由+过滤三大功能
2、新建zuul
pom
<!-- zuul路由网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
yml
server:
port: 9999
spring:
application:
name: book-zuul-gateway
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
instance:
instance-id: gateway-9999.com
prefer-ip-address: true
info:
app.name: atguigu-microcloud
company.name: www.atguigu.com
build.artifactId: $project.artifactId$
build.version: $project.version$
hosts
127.0.0.1 myzuul.com
启动类
@EnableZuulProxy
http://myzuul.com:9999/provider/Book/list
3.路由访问映射
后续。。。
x会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
后续。。。。
十、 zuul路由网关
https://github.com/Netflix/zuul/wiki/Getting-Started
1、是什么?
Zuul包含了对请求的路由和过滤两个最主要的功能:
其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础.
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。
注意:Zuul服务最终还是会注册进Eureka
提供=代理+路由+过滤三大功能
2、新建zuul
pom
<!-- zuul路由网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
yml
server:
port: 9999
spring:
application:
name: book-zuul-gateway
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
instance:
instance-id: gateway-9999.com
prefer-ip-address: true
info:
app.name: atguigu-microcloud
company.name: www.atguigu.com
build.artifactId: $project.artifactId$
build.version: $project.version$
hosts
127.0.0.1 myzuul.com
启动类
@EnableZuulProxy
http://myzuul.com:9999/provider/Book/list
3.路由访问映射
后续。。。