一、SpringCloud简介
微服务
微服务化的核心就是将传统的一站式应用,根据业务拆分成一个一个的服务,彻底地去解耦合,每一个微服务提供单个业务功能也服务,一个服务做一件事,从技术角度看就是一种小而独立的处理过程,类似进程概念,能够自行单独启动或销毀,拥有自己独立的数据库。
SpringCloud与Dubbo对比
Dubbo | SpringCloud | |
---|---|---|
服务注册中心 | Zookeeper | Eureka |
服务调用方式 | RPC | RestAPI |
服务监控 | Dubbo-monitor | Spring Boot Admin |
断路器 | 不完善 | Hystrix |
服务网关 | 无 | Zuul |
分布式配置 | 无 | Spring Cloud Config |
服务跟踪 | 无 | Spring Cloud Sleuth |
消息总线 | 无 | Spring Cloud Bus |
数据流 | 无 | Spring Cloud Stream |
批量任务 | 无 | Spring Cloud Task |
最大区别:SpringCloud抛弃了Dubbo的RPC通信,采用的是基于HTTP的REST方式。 严格来说,这两种方式各有优劣,虽然从一定程度上来说,后者牺牲了服务调用的性能,但也避免了上面提到的原生RPC带来的问题。而且REST相比RPC更为灵活,服务提供方和调用方的依赖只依靠一纸契约,不存在代码级别的强依赖,这在强调快速演化的微服务环境下,显得更加合适。
一、Rest微服务构建
项目结构
microservicecloud // 父项目 |- microservicecloud-api // 存放公共接口、实体类。。 |- microservicecloud-provider-dept-8081 // 部门服务提供者 |- microservicecloud-consumer-dept-80 // 部门服务消费者
1、创建maven父项目
书写pom.xml文件
1 25 4.0.0 6 7cn.x5456 8microservicecloud 9pom 101.0-SNAPSHOT 1112 14 15microservicecloud-api 1316 23 24UTF-8 171.8 181.8 194.12 201.2.17 211.16.18 2225 73 7426 7227 33org.springframework.cloud 28spring-cloud-dependencies 29Dalston.SR1 30pom 31import 3234 40org.springframework.boot 35spring-boot-dependencies 361.5.9.RELEASE 37pom 38import 3941 45mysql 42mysql-connector-java 435.0.4 4446 50com.alibaba 47druid 481.0.31 4951 55org.mybatis.spring.boot 52mybatis-spring-boot-starter 531.3.0 5456 60ch.qos.logback 57logback-core 581.2.3 5961 66junit 62junit 63${junit.version} 64test 6567 71log4j 68log4j 69${log4j.version} 7075 94 95microservicecloud 7677 8278 81src/main/resources 79true 8083 9384 92org.apache.maven.plugins 85maven-resources-plugin 8687 9188 90$ 89
2、创建microservicecloud-api项目
和上面一样
1)书写pom.xml文件
1 25 6 10microservicecloud 7cn.x5456 81.0-SNAPSHOT 94.0.0 11 12microservicecloud-api 13 1415 2416 19org.springframework.cloud 17spring-cloud-starter-feign 1820 23org.springframework.boot 21spring-boot-starter-data-jpa 22
2)书写实体类
// 在api中这样写,有一点问题@Entity // 告诉JPA这是一个实体类(对应数据表),不是普通的javabean@Table(name = "tb_dept") // 不写这个注解,默认为这个类的小写作为名字public class Dept implements Serializable { @Id // 标识这是主键 @GeneratedValue(strategy = GenerationType.AUTO) // 根据数据库自动选则主键自增类型 private Long deptno; // 主键 private String dname; // 部门名称 private String dbSource;// 来自那个数据库,因为微服务架构可以一个服务对应一个数据库,同一个信息被存储到不同数据库
3、创建microservicecloudproviderdept8081(提供者)
1)书写pom.xml文件
1 25 6 10microservicecloud 7cn.x5456 81.0-SNAPSHOT 94.0.0 11 12microservicecloud-provider-dept-8081 1314 15 81 8216 20 21cn.x5456 17microservicecloud-api 18${project.version} 1922 25 26org.springframework.boot 23spring-boot-starter-actuator 2427 30org.springframework.cloud 28spring-cloud-starter-eureka 2931 34org.springframework.cloud 32spring-cloud-starter-config 3335 38 39 40 41 42junit 36junit 3743 47com.oracle 44ojdbc14 4510.2.0.4.0 4648 51com.alibaba 49druid 5052 55ch.qos.logback 53logback-core 5456 59org.mybatis.spring.boot 57mybatis-spring-boot-starter 5860 63org.springframework.boot 61spring-boot-starter-jetty 6264 67org.springframework.boot 65spring-boot-starter-web 6668 71 72org.springframework.boot 69spring-boot-starter-test 7073 76org.springframework 74springloaded 7577 80org.springframework.boot 78spring-boot-devtools 79
2)目录结构
DeptProvider8081_App
@SpringBootApplicationpublic class DeptProvider8001_App { public static void main(String[] args) { SpringApplication.run(DeptProvider8001_App.class, args); }}
controller
@RestControllerpublic class DeptController { @Autowired private DeptService service; @RequestMapping(value = "/dept/add", method = RequestMethod.POST) public Dept add(Dept dept){ return service.addDept(dept); } @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET) public Dept get(@PathVariable("id") Long id) { return service.findById(id); } @RequestMapping(value = "/dept/list", method = RequestMethod.GET) public Listlist(){ return service.findAll(); }}
service
@Servicepublic class DeptServiceImpl implements DeptService { @Resource private DeptRepository deptRepository; @Override public Dept addDept(Dept dept) { return deptRepository.save(dept); } @Override public Dept findById(Long id) { return deptRepository.findOne(id); } @Override public ListfindAll() { return deptRepository.findAll(); }}
repository
public interface DeptRepository extends JpaRepository{}
3)书写配置文件
spring: application: name: microservicecloud-dept datasource: url: jdbc:oracle:thin:@127.0.0.1:1521:orcl username: scott password: tiger driver-class-name: oracle.jdbc.OracleDriver jpa: hibernate: # 更新或创建数据表 ddl-auto: update # 控制台打印sql show-sql: trueserver: context-path: /DeptProvider8001_App port: 8001
4、创建microservicecloudconsumerdept80
1)书写pom.xml文件
1 25 6 10microservicecloud 7cn.x5456 81.0-SNAPSHOT 94.0.0 11 12microservicecloud-consumer-dept-80 13 1415 52 5316 20 21cn.x5456 17microservicecloud-api 18${project.version} 1922 25org.springframework.cloud 23spring-cloud-starter-eureka 2426 29org.springframework.cloud 27spring-cloud-starter-ribbon 2830 33org.springframework.cloud 31spring-cloud-starter-config 3234 37 38org.springframework.boot 35spring-boot-starter-web 3639 42org.springframework 40springloaded 4143 46org.springframework.boot 44spring-boot-devtools 4547 51com.oracle 48ojdbc14 4910.2.0.4.0 50
2)目录结构
DeptConsumer80_App
@SpringBootApplicationpublic class DeptConsumer80_App { public static void main(String[] args) { SpringApplication.run(DeptConsumer80_App.class, args); }}
书写配置类
@Configurationpublic class ConfigBean //boot -->spring applicationContext.xml --- @Configuration配置 ConfigBean = applicationContext.xml{ @Bean public RestTemplate getRestTemplate(){ return new RestTemplate(); }}
controller
@RestControllerpublic class DeptController_Consumer{ private static final String REST_URL_PREFIX = "http://localhost:8001/DeptProvider8001_App"; /** * 使用 使用restTemplate访问restful接口非常的简单粗暴无脑。 (url, requestMap, * ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。 */ @Autowired private RestTemplate restTemplate; @RequestMapping(value = "/consumer/dept/add") public boolean add(Dept dept) { return restTemplate.postForObject(REST_URL_PREFIX + "/dept/add", dept, Boolean.class); } @RequestMapping(value = "/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id) { return restTemplate.getForObject(REST_URL_PREFIX + "/dept/get/" + id, Dept.class); } @RequestMapping(value = "/consumer/dept/list") public Listlist() { return restTemplate.getForObject(REST_URL_PREFIX + "/dept/list", List.class); }}
3)配置文件
### 理论上这部分不应该有 ###spring: datasource: url: jdbc:oracle:thin:@127.0.0.1:1521:orcl username: scott password: tiger driver-class-name: oracle.jdbc.OracleDriver jpa: hibernate: # 更新或创建数据表 ddl-auto: update # 控制台打印sql show-sql: true################## server: port: 80 context-path: /DeptConsumer80_App
二、Eureka
简介
Eureka是Spring Cloud Netflix的一个子模块,也是核心模块之一。用于云端服务发现,一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。服务的注册与发现对应微服务架构来说是非常重要的,有了服务的发现与注册,只需要使用服务标识符,就可以访问到服务,而不需要修改服务调用的配置文件了。
其功能类似与dubbo的注册中心(zookeeper)。
基本架构
SpringCloud封装了Netflix公司开发的Eureka模块来实现服务的注册与发现。
Eureka采用了C-S的设计架构。Eureka Server作为服务注册功能的服务器,它是服务的注册中心。而系统中其他的微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接,这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常运行。SpringCloud的一些其他模块(Zuul)就可以通过Eureka来发现系统中的其他微服务,并执行相关的逻辑。
Eureka包含两个组件:Eureka Server和Eureka Client
Eureka Server提供服务注册服务,在各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务阶段的信息可以在界面中直观的看到。
EurekaClient是一个Java客户端,用于简化EurekaServer的交互,客户端同时也具备一个内置的、使用轮询负载算法的负载均衡器。在应用启动后,将会EurekaServer发送心跳(默认周期是30s)。如果EurekaServer在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中吧这个服务节点移除(默认30s)。
SpringCloud中三大角色
-
EurekaServer:提供服务的注册和发现
-
Server Provider服务提供方将自身服务注册到Eureka,从而使服务消费方可以找到
-
Service Consumer服务消费方从Eureka获取注册服务列表,从而可以消费服务
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)。但是当网络分区发生故障时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能网络分区发生故障),那么这个节点就会进入自我保护模式。一旦进入改模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,改Eureka节点会自动退出自我保护模式。
综上自我保护模式是一种应对网络异常的安全保护措施。他的架构哲学是宁可保留所有微服务(不管它健不健康都会保留),也不盲目注销任何微服务。使用自我保护模式,可以让Eureka集群更加健壮、稳定。
作为服务注册中心,Eureka比Zookeeper好在哪
分布式系统的CAP理论
● 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
● 可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
● 分区容错性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
Zookeeper保证的是CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟前的注册信息,但不能接受直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但是Zookeeper会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s,且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因网络问题使得zk集群失去master节点是较大概率会发生的事,虽然服务能够恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka保证的是AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作 ,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册或时如果发现连接失败,则会自动切换至其它节点,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分祌内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
-
Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
-
Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用)
-
当网络稳定时,当前实例新的注册信息会被同步到其它节点中
因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像Zookeeper那样是整个注册服务瘫痪。
项目中引入E
1、新建Eureka项目microservicecloud-eureka-7001
1)新建项目
2)目录结构
MicroservicecloudEureka7001Application
@SpringBootApplication@EnableEurekaServer // 代表eureka服务端public class MicroservicecloudEureka7001Application { public static void main(String[] args) { SpringApplication.run(MicroservicecloudEureka7001Application.class, args); }}
书写配置文件
# 端口号server.port=7001# 主机名eureka.instance.hostname=localhost# 是否向服务注册中心注册自己eureka.client.register-with-eureka=false# 是否检索服务,false表示自己就是注册中心,不需要去检索服务eureka.client.fetch-registry=false# 服务注册中心的配置内容,指定服务注册中心的位置eureka.client.service-url.defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
2、修改提供者和消费者部分配置
1)修改服务提供者与消费者的主方法
@SpringBootApplication @EnableEurekaClient // eureka客户端 public class DeptProvider8001_App { public static void main(String[] args) { SpringApplication.run(DeptProvider8001_App.class, args); } }
2)为服务提供者与消费者配置文件中添加配置
eureka: client: service-url: defaultZone: http://localhost:7001/eureka/
3)还要在restTemplate上添加负载均衡注解,客户端才可以调用
此时url可以改为提供者在Eureka中注册的名字
Eureka集群的搭建
1、再创建2个Eureka项目
2、修改系统hosts文件的映射(为了防止名字冲突)
3、书写配置文件
4、修改服务提供者的配置文件
将这个服务注册到每一个eureka上
5、成功页面
三、Ribbon
简介
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载衡算法,将Netflix的中间层服务连接在一起。 Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer (负载均衡)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。
基本架构
Ribbon在工作时分成两步
第一步先选择EurekaServer,他优先选则在同一个区域内负载较少的server
第二步在根据用户指定的策略,从server渠道的服务注册列表中选则一个地址。
Ribbon的irule组件
Ribbon默认是采用轮询的方式进行负载均衡,我们可以使用irule进行指定。
Ribbon的使用
首先要在(消费者)pom.xml文件中引入jar包
org.springframework.cloud spring-cloud-starter-eureka org.springframework.cloud spring-cloud-starter-ribbon org.springframework.cloud spring-cloud-starter-config
之前为了能够使用微服务名称来调用服务时,我们已经使用过ribbon了,没错就是这个:
Robbon默认使用的是轮询算法,总共有以下几种算法:
可以使用以下方法进行修改算法
四、
简介
Feign是一个声明式WebService客户端。使得编写Web服务客户端变得非常容易,我们只需要创建一个接口,然后在上面添加注解即可。
Feign旨在使编写JavaHttp客户端变得更容易。前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模板化的调用方法,但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服努的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在 Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它,即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon,使用它来进行负载均衡。
1、创建消费者microservicecloud-consumer-dept-feign
pom.xml导入相应依赖
org.springframework.cloud spring-cloud-starter-feign
2、在microservicecloud-api中添加接口
@FeignClient(value = "MICROSERVICECLOUD-DEPT/DeptConsumerDeptFeign_App") // 这个是我们要调用的提供者在eureka中注册的名字public interface DeptClientService{ @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET) // 要请求 提供者 的url public Dept get(@PathVariable("id") long id); @RequestMapping(value = "/dept/list", method = RequestMethod.GET) public Listlist(); @RequestMapping(value = "/dept/add", method = RequestMethod.POST) public boolean add(Dept dept);}
3、microservicecloudconsumerdeptfeign中的一些操作
DeptConsumerDeptFeign_App
@SpringBootApplication@EnableEurekaClient@EnableFeignClients(basePackages= {"cn.x5456.microservicecloud"}) // 扫描所有Feign的接口@ComponentScan("cn.x5456.springcloud") // 扫描我们调用的controllerpublic class DeptConsumerDeptFeign_App { public static void main(String[] args) { SpringApplication.run(DeptConsumerDeptFeign_App.class, args); }}
DeptController_Consumer(变得和我们面向接口编程一样了)
@RestControllerpublic class DeptController_Consumer { @Autowired private DeptClientService service; @RequestMapping(value = "/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id) { return this.service.get(id); } @RequestMapping(value = "/consumer/dept/list") public Listlist() { return this.service.list(); } @RequestMapping(value = "/consumer/dept/add") public Object add(Dept dept) { return this.service.add(dept); }}
配置文件和以前一样
五、
简介
1、概念
一般处某个服务故障异常引起,类似现实世界中的“保险丝“,当某个异常条件被触发,直接熔断整个服务,而不是一直等到此服务超时。
2、使用
1)(提供者端)导入依赖
org.springframework.cloud spring-cloud-starter-hystrix
2)在需要进行熔断处理的方法上添加注解,书写熔断方法
@RestControllerpublic class DeptController { @Autowired private DeptService service = null; @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET) //一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法 @HystrixCommand(fallbackMethod = "processHystrix_Get") public Dept get(@PathVariable("id") Long id) { Dept dept = this.service.get(id); if (null == dept) { throw new RuntimeException("该ID:" + id + "没有没有对应的信息"); } return dept; } public Dept processHystrix_Get(@PathVariable("id") Long id) { return new Dept().setDeptno(id).setDname("该ID:" + id + "没有没有对应的信息,null--@HystrixCommand") .setDb_source("no this database in MySQL"); }}
3)在主方法上添加@EnableCircuitBreaker注解
@SpringBootApplication@EnableEurekaClient //本服务启动后会自动注册进eureka服务中@EnableCircuitBreaker//对hystrixR熔断机制的支持public class DeptProvider8001_Hystrix_App{ public static void main(String[] args) { SpringApplication.run(DeptProvider8001_Hystrix_App.class, args); }}
3、问题
- 没有实现解耦的思想
- 方法膨胀(每有一个方法就要有一个对应的断路器方法)
- 服务降级
服务降级
1、概念
降级,一般是从整体负荷考虑。就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回—个缺省值。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强。
2、使用
1)在microservicecloud-api项目,根据刚刚写的DeptClientService接口新建一个实现了FallbackFactory接口的类DeptClientServiceFallbackFactory
@Component // 不要忘记添加public class DeptClientServiceFallbackFactory implements FallbackFactory{ @Override public DeptClientService create(Throwable throwable) { return new DeptClientService() { @Override public Dept get(long id) { Dept dept = new Dept(); dept.setDname("yyy"); return dept; } @Override public List list() { return null; } @Override public boolean add(Dept dept) { return false; } }; }}
2)为DeptClientService的注解中添加参数
@FeignClient(value = "MICROSERVICECLOUD-DEPT",fallbackFactory=DeptClientServiceFallbackFactory.class)
3)在消费者端的配置文件中添加下面配置:
feign: hystrix: enabled: true
4)测试
- 启动enreka,启动提供者,启动消费者
- 故意关闭服务提供者
- 再次进行访问,查看效果
六、Zuul(路由网关)
简介
Zuul包含了对请求的路由和过滤两个最主要的功能: 其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础。
Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。