SpringCloud
一,微服务架构
1 架构演变
单体架构 =》垂直应用架构 =》SOA架构 =》 微服务架构
2 微服务架构:
微服务架构和SOA架构很明显的一个区别就是服务拆分粒度的不同,但是对于拉勾的架构发展来说,我们所看到的SOA阶段其实服务拆分粒度相对来说已经⽐较细了(超前哦!),所以上述拉勾SOA到拉勾微服务,从服务拆分上来说变化并不⼤,只是引入了相对完整的新⼀代Spring Cloud微服务技术。⾃然,上述我们看到的都是拉勾架构演变的阶段结果,每⼀个阶段其实都经历了很多变化,拉勾的服务拆分其实也是⾛过了从粗到细,并⾮绝对的⼀步到位。举个拉勾案例来说明SOA和微服务拆分粒度不同我们在SOA架构的初期,“简历投递模块”和“⼈才搜索模块”都有简历内容展示的需求,只不过说可能略有区别,刚开始在两个模块中各维护了⼀套简历查询和展示的代码;后期我们将服务更细粒度拆分,拆分出简历基础服务,那么不同模块调用这个基础服务即可。
3 微服务架构优缺点
优点
-
微服务很小,便于特定业务功能的聚焦 A B C D
-
微服务很小,每个微服务都可以被一个小团队单独实施(开发、测试、部署上线、运维),团队合作一定程度解耦,便于实施敏捷开发
-
微服务很小,便于重用和模块之间的组装
-
微服务很独立,那么不同的微服务可以使用不同的语言开发,松耦合
-
微服务架构下,我们更容易引入新技术
-
微服务架构下,我们可以更好的实现DevOps开发运维⼀体化;
缺点
-
微服务架构下,分布式复杂难以管理,当服务数量增加,管理将越加复杂;
-
微服务架构下,分布式链路跟踪难等;
4 一些概念
4.1 服务注册与服务发现
4.2 负载均衡
4.3 熔断
4.4 链路追踪
4.5 API网关(API GATEWAY)
二,SpringCloud概述
1 是什么
Spring Cloud是一系列框架的有序集合。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。Spring并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
Spring Cloud是一系列框架的有序集合(Spring Cloud是一个规范) 开发服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等 利用Spring Boot的开发便利性简化了微服务架构的开发(自动装配)
2 解决了什么问题
Spring Cloud 规范及实现意图要解决的问题其实就是微服务架构实施过程中存在的一些问题,比如微服务架构中的服务注册发现问题、网络问题(比如熔断场景)、统一认证安全授权问题、负载均衡问题、链路追踪等问题。
3 SpringCloud架构
3.1 核心组件
3.2 体系结构
Spring Cloud中的各组件协同工作,才能够支持一个完整的微服务架构。比如
-
注册中心负责服务的注册与发现,很好将各服务连接起来
-
API网关负责转发所有外来的请求
-
断路器负责监控服务之间的调用情况,连续多次失败进行熔断保护
-
配置中心提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息
4 SpringCloud与Dubbo对比
Dubbo是阿⾥巴巴公司开源的⼀个⾼性能优秀的服务框架,基于RPC调用,对于⽬前使⽤率较⾼的Spring Cloud Netflix来说,它是基于HTTP的,所以效率上没有Dubbo⾼,但问题在于Dubbo体系的组件不全,不能够提供⼀站式解决⽅案,⽐如服务注册与发现需要借助于Zookeeper等实现,⽽Spring Cloud Netflix则是真正的提供了⼀站式服务化解决⽅案,且有Spring⼤家族背景。前些年,Dubbo使⽤率⾼于SpringCloud,但⽬前Spring Cloud在服务化/微服务解决⽅案中已经有了⾮ 常好的发展趋势。
5 SpringCloud与Springboot的关系
Spring Cloud 只是利用了Spring Boot 的特点,让我们能够快速的实现微服务组件开发,否则不使⽤Spring Boot的话,我们在使⽤Spring Cloud时,每一个组件的相关Jar包都需要我们自己导入配置以及需要开发⼈员考虑兼容性等各种情况。所以Spring Boot是我们快速把Spring Cloud微服务技术应用起来的一种方式。
三,SpringCloud案例准备
1 案例说明
本部分我们按照普通⽅式模拟一个微服务之间的调用(后续我们将⼀步步使用Spring Cloud的组件对案例进行改造)。 拉勾App有这样一个功能:“面试直通车”,当求职⽤户开启了⾯试直通⻋之后,会根据企业客户的招聘岗位需求进行双向匹配。其中有⼀个操作是:为企业⽤户开启⼀个定时任务,根据企业录入的⽤⼈条件,每⽇匹配⼀定数量的应聘者“投递”到企业的资源池中去,那么系统在将匹配到的应聘者投递到资源池的时候需要先检查:此时应聘者默认简历的状态(公开/隐藏),如果此时默认简历的状态已经被应聘者设置为“隐藏”,那么不再执行“投递”操作。 “⾃动投递功能”在“⾃动投递微服务”中,“简历状态查询功能”在“简历微服务”中,那么就涉及到“⾃动投递微服务”调用“简历微服务”查询简历。在这种场景下,“自动投递微服务”就是一个服务消费者,“简历微服务”就是⼀个服务提供者。
2 数据库准备
/* Navicat Premium Data Transfer Source Server : localhost Source Server Type : MySQL Source Server Version : 50717 Source Host : 127.0.0.1:3306 Source Schema : lagou Target Server Type : MySQL Target Server Version : 50717 File Encoding : 65001 Date: 19/04/2020 17:49:15 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for r_resume -- ---------------------------- DROP TABLE IF EXISTS `r_resume`; CREATE TABLE `r_resume` ( `id` int(11) NOT NULL AUTO_INCREMENT, `sex` varchar(10) DEFAULT NULL COMMENT '性别', `birthday` varchar(30) DEFAULT NULL COMMENT '出生日期', `work_year` varchar(100) DEFAULT NULL COMMENT '工作年限', `phone` varchar(20) DEFAULT NULL COMMENT '手机号码', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `status` varchar(80) DEFAULT NULL COMMENT '目前状态', `resumeName` varchar(500) DEFAULT NULL COMMENT '简历名称', `name` varchar(40) DEFAULT NULL, `createTime` datetime DEFAULT NULL COMMENT '创建日期', `headPic` varchar(100) DEFAULT NULL COMMENT '头像', `isDel` int(2) DEFAULT NULL COMMENT '是否删除 默认值0-未删除 1-已删除', `updateTime` datetime DEFAULT NULL COMMENT '简历更新时间', `userId` int(11) DEFAULT NULL COMMENT '用户ID', `isDefault` int(2) DEFAULT NULL COMMENT '是否为默认简历 0-默认 1-非默认', `highestEducation` varchar(20) DEFAULT '' COMMENT '最高学历', `deliverNearByConfirm` int(2) DEFAULT '0' COMMENT '投递附件简历确认 0-需要确认 1-不需要确认', `refuseCount` int(11) NOT NULL DEFAULT '0' COMMENT '简历被拒绝次数', `markCanInterviewCount` int(11) NOT NULL DEFAULT '0' COMMENT '被标记为可面试次数', `haveNoticeInterCount` int(11) NOT NULL DEFAULT '0' COMMENT '已通知面试次数', `oneWord` varchar(100) DEFAULT '' COMMENT '一句话介绍自己', `liveCity` varchar(100) DEFAULT '' COMMENT '居住城市', `resumeScore` int(3) DEFAULT NULL COMMENT '简历得分', `userIdentity` int(1) DEFAULT '0' COMMENT '用户身份1-学生 2-工人', `isOpenResume` int(1) DEFAULT '3' COMMENT '人才搜索-开放简历 0-关闭,1-打开,2-简历未达到投放标准被动关闭 3-从未设置过开放简历', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2195388 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of r_resume -- ---------------------------- BEGIN; INSERT INTO `r_resume` VALUES (2195320, '女', '1990', '2年', '199999999', 'test@testtest01.com', '我目前已离职,可快速到岗', '稻壳儿的简历', 'wps', '2015-04-24 13:40:14', 'images/myresume/default_headpic.png', 0, '2015-04-24 13:40:14', 1545132, 1, '本科', 0, 0, 0, 0, '', '广州', 15, 0, 3); INSERT INTO `r_resume` VALUES (2195321, '女', '1990', '2年', '199999999', 'test@testtest01.com', '我目前已离职,可快速到岗', '稻壳儿的简历', 'wps', '2015-04-24 14:17:54', 'images/myresume/default_headpic.png', 0, '2015-04-24 14:20:35', 1545133, 1, '本科', 0, 0, 0, 0, '', '广州', 65, 0, 3); INSERT INTO `r_resume` VALUES (2195322, '女', '1990', '2年', '199999999', 'test@testtest01.com', '我目前已离职,可快速到岗', '稻壳儿的简历', 'wps', '2015-04-24 14:42:45', 'images/myresume/default_headpic.png', 0, '2015-04-24 14:43:34', 1545135, 1, '本科', 0, 0, 0, 0, '', '广州', 65, 0, 3); INSERT INTO `r_resume` VALUES (2195374, '女', '1990', '1年', '199999999', 'test@testtest01.com', '我目前正在职,正考虑换个新环境', '稻壳儿', 'wps', '2015-06-04 17:53:37', 'images/myresume/default_headpic.png', 0, '2015-06-04 17:53:39', 1545523, 1, '本科', 0, 0, 0, 0, '', '广州', 65, 0, 3); INSERT INTO `r_resume` VALUES (2195375, '女', '1990', '1年', '199999999', 'test@testtest01.com', '我目前正在职,正考虑换个新环境', '稻壳儿', 'wps', '2015-06-04 18:11:06', 'images/myresume/default_headpic.png', 0, '2015-06-04 18:11:07', 1545524, 1, '本科', 0, 0, 0, 0, '', '广州', 65, 0, 3); INSERT INTO `r_resume` VALUES (2195376, '女', '1990', '1年', '199999999', 'test@testtest01.com', '我目前正在职,正考虑换个新环境', '稻壳儿', 'wps', '2015-06-04 18:12:19', 'images/myresume/default_headpic.png', 0, '2015-06-04 18:12:19', 1545525, 1, '本科', 0, 0, 0, 0, '', '广州', 65, 0, 3); INSERT INTO `r_resume` VALUES (2195377, '女', '1990', '1年', '199999999', 'test@testtest01.com', '我目前正在职,正考虑换个新环境', '稻壳儿', 'wps', '2015-06-04 18:13:28', 'images/myresume/default_headpic.png', 0, '2015-06-04 18:13:28', 1545526, 1, '本科', 0, 0, 0, 0, '', '广州', 65, 0, 3); INSERT INTO `r_resume` VALUES (2195378, '女', '1990', '1年', '199999999', 'test@testtest01.com', '我目前正在职,正考虑换个新环境', '稻壳儿', 'wps', '2015-06-04 18:15:16', 'images/myresume/default_headpic.png', 0, '2015-06-04 18:15:16', 1545527, 1, '本科', 0, 0, 0, 0, '', '广州', 65, 0, 3); INSERT INTO `r_resume` VALUES (2195379, '女', '1990', '1年', '199999999', 'test@testtest01.com', '我目前正在职,正考虑换个新环境', '稻壳儿', 'wps', '2015-06-04 18:23:06', 'images/myresume/default_headpic.png', 0, '2015-06-04 18:23:06', 1545528, 1, '本科', 0, 0, 0, 0, '', '广州', 65, 0, 3); INSERT INTO `r_resume` VALUES (2195380, '女', '1990', '1年', '199999999', 'test@testtest01.com', '我目前正在职,正考虑换个新环境', '稻壳儿', 'wps', '2015-06-04 18:23:38', 'images/myresume/default_headpic.png', 0, '2015-06-04 18:23:39', 1545529, 1, '本科', 0, 0, 0, 0, '', '广州', 65, 0, 3); INSERT INTO `r_resume` VALUES (2195381, '女', '1990', '1年', '199999999', 'test@testtest01.com', '我目前正在职,正考虑换个新环境', '稻壳儿', 'wps', '2015-06-04 18:27:33', 'images/myresume/default_headpic.png', 0, '2015-06-04 18:27:33', 1545530, 1, '本科', 0, 0, 0, 0, '', '广州', 65, 0, 3); COMMIT; SET FOREIGN_KEY_CHECKS = 1;
3 工程环境准备
3.1 创建父工程
my-lagou-sc
<?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> <packaging>pom</packaging> <groupId>com.limeng.sc</groupId> <artifactId>my-lagou-sc</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>my-lagou-common-service</module> <module>my-lagou-resume-8095</module> <module>my-lagou-resume-8096</module> <module>my-lagou-autodeliever-8097</module> <module>my-lagou-autodeliever-8098</module> <module>my-lagou-eureka-server-8761</module> <module>my-lagou-eureka-server-8762</module> <module>my-lagou-hystrix-dashboard-8100</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencyManagement> <dependencies> <!--spring cloud依赖管理,引入了Spring Cloud的版本--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Greenwich.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <!-- springboot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </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> <!-- Actuator可以帮助你监控和管理Spring Boot应用--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!-- Feign依赖 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--引入Jaxb,开始--> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.2.11</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.2.11</version> </dependency> <dependency> <groupId>org.glassfish.jaxb</groupId> <artifactId>jaxb-runtime</artifactId> <version>2.2.10-b140310.1920</version> </dependency> <dependency> <groupId>javax.activation</groupId> <artifactId>activation</artifactId> <version>1.1.1</version> </dependency> <!--引入Jaxb,结束--> <!-- junit测试 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <!--编译插件--> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>11</source> <target>11</target> <encoding>utf-8</encoding> </configuration> </plugin> <!--打包插件--> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3.2 创建common工程
my-lagou-common-service
<?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"> <parent> <artifactId>my-lagou-sc</artifactId> <groupId>com.limeng.sc</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>my-lagou-common-service</artifactId> <dependencies> <!--Spring Data Jpa--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies> <!--排除该工程,不生成jar包 --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <skip>true</skip> </configuration> </plugin> </plugins> </build> </project>
创建pojo Resume类
package com.limeng.sc.common.pojo; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="r_resume") public class Resume { @Id /** * 生成策略经常使用的两种: * GenerationType.IDENTITY:依赖数据库中主键自增功能 Mysql * GenerationType.SEQUENCE:依靠序列来产生主键 Oracle */ @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 主键 private String sex; // 性别 //.... //set/get方法 }
3.3 创建 简历子工程 my-lagou-resume
使用springboot + spring jpa
<?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"> <parent> <artifactId>my-lagou-sc</artifactId> <groupId>com.limeng.sc</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>my-lagou-resume</artifactId> <dependencies> <dependency> <groupId>com.limeng.sc</groupId> <artifactId>my-lagou-common-service</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--eureka client 客户端依赖引入--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> </dependencies> </project>
注意:
1,连接mysql url 要跟 serverTimezone=UTC, 配置文件是application.yml ,也可以是bootstrap.yml(优先级更高)
2,打jar包的时候要打父工程jar包,既 my-lagou-sc -> maven -> Lifecycle ->package/install
server: port: 8095 spring: application: name: my-lagou-resume datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/lagou?serverTimezone=UTC username: root password: root jpa: database: MySQL show-sql: true hibernate: naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #避免将驼峰命名转换为下划线命名 #注册到Eureka服务中心 eureka: client: service-url: # 注册到集群,就把多个Eurekaserver地址使用逗号连接起来即可;注册到单实例(非集群模式),那就写一个就ok defaultZone: http://LagouCloudEurekaServerA:8761/eureka,http://LagouCloudEurekaServerB:8762/eureka instance: prefer-ip-address: true #服务实例中显示ip,而不是显示主机名(兼容老的eureka版本) # 实例名称: 192.168.1.103:lagou-service-resume:8080,我们可以自定义它 instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@ metadata-map: m1: n1 m2: n2
启动类
要有扫描包注解,要扫描数据库的东西 @EntityScan("com.limeng.sc.common.pojo") 否则JPA相关报错
Caused by: java.lang.IllegalArgumentException: Not a managed type: class com.limeng.sc.common.pojo.Resume
package com.limeng.sc.resume; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.domain.EntityScan; @SpringBootApplication @EnableDiscoveryClient // 开启服务发现,让Eureka能发现这个服务 @EntityScan("com.limeng.sc.common.pojo") public class ResumeApplication { public static void main(String[] args) { SpringApplication.run(ResumeApplication.class,args); } }
dao:
package com.limeng.sc.resume.dao; import com.limeng.sc.common.pojo.Resume; import org.springframework.data.jpa.repository.JpaRepository; public interface ResumeDao extends JpaRepository<Resume,Long> { }
service :
package com.limeng.sc.resume.service; import com.limeng.sc.common.pojo.Resume; import com.limeng.sc.resume.dao.ResumeDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Example; import org.springframework.stereotype.Service; import java.util.Optional; @Service public class ResumeService { @Autowired private ResumeDao resumeDao; public Resume getDefaultResumeByUserId(long userId){ Resume resume = new Resume(); resume.setUserId(userId); resume.setIsDefault(1); Example<Resume> example = Example.of(resume); Optional<Resume> optional = resumeDao.findOne(example); return optional.get(); } }
controller:
package com.limeng.sc.resume.controller; import com.limeng.sc.resume.service.ResumeService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/resume") public class ResumeController { @Autowired private ResumeService resumeService; @Value("${server.port}") private Integer port; @GetMapping(value="/isOpenResume/{userId}") public Integer getDefaultResumeState(@PathVariable long userId){ // return resumeService.getDefaultResumeByUserId(userId).getIsOpenResume(); return port; } }
3.4 创建auto-deliver子工程
引入maven
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-commons</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--熔断器Hystrix--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> </dependencies>
配置yaml
server: port: 8098 eureka: client: service-url: # 注册到集群,就把多个Eurekaserver地址使用逗号连接起来即可;注册到单实例(非集群模式),那就写一个就ok defaultZone: http://LagouCloudEurekaServerA:8761/eureka,http://LagouCloudEurekaServerB:8762/eureka instance: prefer-ip-address: true #服务实例中显示ip,而不是显示主机名(兼容老的eureka版本) # 实例名称: 192.168.1.103:lagou-service-resume:8080,我们可以自定义它 instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}:@project.version@ metadata-map: k1: v1 k2: v2 k3: v3 spring: application: name: my-lagou-autodelievr #针对的被调用方微服务名称,不加就是全局生效 my-lagou-resume: ribbon: #请求连接超时时间 ConnectTimeout: 2000 #请求处理超时时间 ##########################################Feign超时时长设置 ReadTimeout: 5000 #对所有操作都进行重试 OkToRetryOnAllOperations: true ####根据如上配置,当访问到故障请求的时候,它会再尝试访问一次当前实例(次数由MaxAutoRetries配置), ####如果不行,就换一个实例进行访问,如果还不行,再换一次实例访问(更换次数由MaxAutoRetriesNextServer配置), ####如果依然不行,返回失败信息。 MaxAutoRetries: 0 #对当前选中实例重试次数,不包括第一次调用 MaxAutoRetriesNextServer: 0 #切换实例的重试次数 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #负载策略调整 logging: level: com.limeng.sc.autodeliever.service.ResumeFeignService: debug #配置消费者调用远程接口的权限定路径 # 开启Feign的熔断功能 feign: hystrix: enabled: true compression: request: enabled: true # 开启请求压缩 mime-types: text/html,application/xml,application/json # 设置压缩的数据类型,此处也是默认值 min-request-size: 2048 # 设置触发压缩的大小下限,此处也是默认值 response: enabled: true # 开启响应压缩 hystrix: command: default: execution: isolation: thread: ##########################################Hystrix的超时时长设置 timeoutInMilliseconds: 2000
远程调用my-lagou-resume的API,使用Spring的 RestTemplate
RestTemplate有下列方法:
<T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables)//get 得到Entity <T> ResponseEntity<T> postForEntity(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables) <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) //get <T> T postForObject(String url, @Nullable Object request, Class<T> responseType, Object... uriVariables)
使用:
package com.limeng.sc.autodeliever.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; @RestController @RequestMapping(value="/autoDeliver") public class AutoDeliverController { @Autowired private RestTemplate restTemplate; /* //普通方法调用 @GetMapping(value="/checkState/{userId}") public Integer checkState(@PathVariable long userId){ return restTemplate.getForObject("http://localhost:8095/resume/isOpenResume/"+userId, Integer.class); }*/ @Autowired private DiscoveryClient discoveryClient; /* //Eureka调用 @GetMapping(value="/checkState/{userId}") public Integer checkState(@PathVariable long userId){ List<ServiceInstance> instances = discoveryClient.getInstances("my-lagou-resume"); ServiceInstance instance = instances.get(0); String host = instance.getHost(); int port = instance.getPort(); String url = "http://"+host+":"+port+"/resume/isOpenResume/"+userId; System.out.println("==========>>>自动投递服务调用简历查看服务。。。。。。"); return restTemplate.getForObject(url,Integer.class); }*/ @GetMapping(value="/checkState/{userId}") public Integer checkState(@PathVariable long userId){ Integer result = restTemplate.getForObject("http://my-lagou-resume/resume/isOpenResume/"+userId, Integer.class); System.out.println(result); return result; } @Autowired private ResumeFeignService resumeFeignService; @GetMapping(value="/checkStateFeign/{userId}") public Integer checkStateFeign(@PathVariable long userId){ Integer result = resumeFeignService.isOpenResume(userId); System.out.println(result); return result; } }
启动类:
package com.limeng.sc.autodeliever; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.web.client.RestTemplate; @SpringBootApplication @EnableDiscoveryClient // 开启服务发现(Eureka,Nacos) //@EnableCircuitBreaker // 开启熔断器功能 /** @SpringCloudApplication包含@SpringBootApplication,@EnableDiscoveryClient,@EnableCircuitBreaker */ @EnableFeignClients // 开启Feign, Feign支持熔断器功能 public class DelieverApplication8097 { public static void main(String[] args) { SpringApplication.run(DelieverApplication8097.class,args); } @LoadBalanced//Ribbon负载均衡 @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
4 存在问题
我们在自动投递微服务中使用RestTemplate调用简历微服务的简历状态接口时(Restful API 接口)。 在微服务分布式集群环境下会存在什么问题呢?怎么解决? 存在的问题:
-
在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护。
-
服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡。
-
在服务消费者中,不清楚服务提供者的状态。
-
服务消费者调用服务提供者时候,如果出现故障能否及时发现不向用户抛出异常信息?
-
RestTemplate这种请求调用方式是否还有优化空间?能不能类似于Dubbo那样玩?
-
这么多的微服务统一认证如何实现?
-
配置文件每次都修改好多个很麻烦!? ....
上述分析出的问题,其实就是微服务架构中必然面临的一些问题:
-
服务管理:自动注册与发现、状态监管
-
服务负载均衡
-
熔断,服务降级
-
远程过程调用(RPC)
-
网关拦截、路由转发
-
统一认证
-
集中式配置管理,配置信息实时变动更新
四,第一代SpringCloud核心组件
1 Eureka服务注册中心
1.1 关于服务注册中心
对于任何一个微服务,原则上都应存在或者支持多个提供者(比如简历微服务部署多个实例),这是由微服务的分布式属性决定的。 更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和分布往往是动态变化的
1.2 主流服务中心对比
Zookeeper Zookeeper它是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应 ⽤中经常遇到的⼀些数据管理问题,如:统⼀命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。简单来说zookeeper本质=存储+监听通知。 znode Zookeeper 用来做服务注册中心,主要是因为它具有节点变更通知功能,只要客户端监听相关服务节点,服务节点的所有变更,都能及时的通知到监听客户端,这样作为调用方只要使用Zookeeper 的客户端就能实现服务节点的订阅和变更通知功能了,非常方便。另外,Zookeeper可用性也可以,因为只要半数以上的选举节点存活,整个集群就是可用的。集群最小要有3个节点。 Eureka 由Netflix开源,并被Pivatal集成到SpringCloud体系中,它是基于 RestfulAPI 风格开发的服务注册与发现组件。 Consul Consul是由HashiCorp基于Go语⾔开发的⽀持多数据中⼼分布式⾼可⽤的服务发布和注册服务软件, 采⽤Raft算法保证服务的⼀致性,且⽀持健康检查。 Nacos Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。简单来说 Nacos就是 注册中心 + 配置中心的组合,帮助我们解决微服务开发必会涉及到的服务注册 与发现,服务配置,服务管理等问题。Nacos 是 Spring Cloud Alibaba 核心组件之一,负责服务注册与发现,还有配置。
组件名 | 语言 | CAP | 对外暴露接口 |
---|---|---|---|
Eureka | Java | AP(自我保护机制,保证可用) | HTTP |
Consul | Go | CP | HTTP/DNS |
Zookeeper | Java | CP | 客户端 |
Nacos | Java | 支持AP/CP切换 | HTTP |
P:分区容错性(一定的要满足的) C:数据一致性 A:高可用 CAP不可能同时满足三个,要么是AP,要么是CP
1.3 服务注册中心组件 Eureka
renew(续约)是Eureka client向server端的,相当于向server发送心跳,报告我是活着的!
Eureka 包含两个组件:Eureka Server 和 Eureka Client,Eureka Client是⼀个Java客户端,由于简化与Eureka Server的交互;Eureka Server提供服务发现的能力,各个微服务启动时,会通过Eureka Client向Eureka Server 进