单体架构,是指将开发好的项目打成war包,然后发布到tomcat等容器中的应用。
微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服务共用一个最小型的集中式管理,服务可用不同的语言开发,使用不同的数据存储技术。
微服务架构的特征
每个微服务可独立运行在自己的进程中
一系列独立运行的微服务共同构建起整个系统
每个服务为独立的业务开发,一个微服务只关注某个特定的功能,例如 订单管理 用户管理等
微服务之间通过一些轻量通信机制通信。例如通过 RESTful API调用
可以使用不同的语言与数据存储技术
全自动的部署机制
Dalston.SR3中
spring-cloud-config
spring-cloud-netflix
spring-cloud-sleuth
spring-cloud-stream
spring-boot 1.5.4.RELEASE
约定优于配置
使用Spring Boot实现微服务
在正式学习Spring Cloud之前我们先使用Spring Boot实现一个微服务。
业务非常简单:
1. 商品微服务:通过商品id查询商品的服务;
2. 订单微服务:创建订单时通时,通过调用商品的微服务进行查询商品数据;
// Spring框架对RESTful方式的http请求做了封装,来简化操作
@Autowired
private RestTemplate restTemplate;
@Bean // 向Spring容器中定义RestTemplate对象
public RestTemplate restTemplate(){
return new RestTemplate();
}
RestTemplate底层默认使用的jdk的标准实现,如果我们想让RestTemplate的底层使用okhttp,非常简单:
在订单系统中要调用商品微服务中的查询接口来获取数据,在订单微服务中将url硬编码到代码中,这样显然不好,因为,运行环境一旦发生变化这个url地址将不可用。
优化硬编码问题
在SpringBoot中使用@ConfigurationProperties注解可以非常简单的将配置文件中的值映射成对象。
public class ItemProperties {
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
public class ItemProperties {
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
@Autowired
private OrderProerties orderProerties;
itcast:
item:
url: http://127.0.0.1:8081/item/
引入Spring Cloud
1. 分析硬编码的问题
通过前面5.4、5.5的实现,我们视乎已经解决了url硬编码的问题,但是我们想想:
1. 如果商品微服务的ip地址发生了变更,订单微服务中的配置文件也需要跟着修改
2. 如果商品微服务有多个,那么在订单微服务中又该如何写地址?
那应该怎么解决呢? – 通过服务注册、发现的机制来完成。
由上图可以看出:
1. 服务提供者将服务注册到注册中心
2. 服务消费者通过注册中心查找服务
3. 查找到服务后进行调用(这里就是无需硬编码url的解决方案)
4. 服务的消费者与服务注册中心保持心跳连接,一旦服务提供者的地址发生变更时,注册中心会通知服务消费者
Spring Cloud提供了多种注册中心的支持,如:Eureka、ZooKeeper等。推荐使用Eureka。
Eureka包含两个组件:Eureka Server和Eureka Client。
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就别一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。
解决响应变成xml的问题
由于我们引入了eureka server的依赖,导致破坏了之前SpringMVC默认的配置,从而导致了响应成了xml。
自我保护模式
当Eureka Server结点在短时间内丢失过多客户端时(可能发生了网络分区故障) 那么这个结点就会进入自我保护模式。一旦进入该模式,Eureka Server 就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,该Eureka Server 结点会自动退出自我保护模式
Eureka服务是一个单点服务,在生产环境就会出现单点故障,为了确保Eureka服务的高可用,我需要搭建Eureka服务的集群。
搭建Eureka集群
搭建Eureka集群非常简单,只要启动多个Eureka服务并且让这些服务之间彼此进行注册即可实现。
服务注册到Eureka集群时,可以指定多个,也可以指定一个Eureka服务(因为Eureka服务集群间彼此互联)。
指定服务的ip
指定实例的id
使用Ribbon实现负载均衡
我们思考一个问题,如果为同一个的提供者在Eureka中注册了多个服务,那么客户端该如何选择服务呢?
这时,就需要在客户端实现服务的负载均衡。
在Spring Cloud中推荐使用Ribbon来实现负载均衡。
为RestTemplate设置@LoadBalanced注解 这样,RestTemplate就具备了负载均衡的功能。
改写ItemService
public Item queryItemById(Long id){
String serviceId="itcast-microservice-item";
return this.restTemplate.getForObject("http://"+serviceId+"/item/" + id, Item.class);
}
默认负载均衡的策略为轮询 可以手动设置为随机
容错保护Hystrix
若一个单元出现故障 就很容易因依赖关系而引发故障的蔓延,最终导致整个系统的瘫痪,这样的架构相较于传统架构更加不稳定。为了解决这样的问题,产生了断路器等一系列的服务保护机制
在微服务架构中通常会有多个服务层调用,基础服务的故障可能会导致级联故障,进而造成整个系统不可用的情况,这种现象被称为服务雪崩效应。
实际测试发现:如果停止一个商品服务后 发现注册中心的服务列表里面并没有立即去除 此时 不管使用轮询算法还是随机算法 都有可能请求到宕机的这个商品服务 但是如果请求到一次就会被记录下来 接下来的重复请求都不会再请求到。当两个服务都挂掉后 当然就没有办法了 只能走fallback的方法了
第二天
Feign
虽然使用了Ribbon和Hystrix可以实现负载均衡和容错处理,但是这个编码在实现大量业务时会显得太过于冗余(如,多参数的URL拼接)。
流程分析:
1. 由于我们在入口@EnableFeignClients注解,Spring启动后会扫描标注了@FeignClient注解的接口,然后生成代理类
2. 我们在@FeignClient接口中指定了value,其实就是指定了在Eureka中的服务名称
3. 在FeignClient中的定义方法以及使用了SpringMVC的注解,Feign就会根据注解中的内容生成对应的URL,然后基于Ribbon的负载均衡去调用REST服务
我们使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过Ribbon或Feign实现服务的消费以及均衡负载;通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理。为了使得服务集群更为健壮,使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。
Spring Cloud Zuul
服务网关是微服务架构中一个不可或缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。Spring Cloud Netflix中的Zuul就担任了这样的一个角色,为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。
Config Server是一个可横向扩展、集中式的配置服务器,它用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以使用SVN存储,或者是本地文件存储。
Config Client是Config Server的客户端,用于操作存储在Config Server中的配置内容。微服务在启动时会请求Config Server获取配置文件的内容,请求到后再启动容器。
手动更新运行中的配置文件
借助与git的webhook(web钩子)实现自动更新
gogs、github等git服务器提供了web hook功能,意思是,在仓库中的资源发生更新时会通知给谁,这里的谁是一个url地址。
order中ItemService代码演变
@Service
public class ItemServiceBak {
// Spring框架对RESTful方式的http请求做了封装,来简化操作
@Autowired
private RestTemplate restTemplate;
@Value("${itcast.item.url}")
private String itcastItemUrl;
@Autowired
private OrderProerties orderProerties;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private ItemFeignClient itemFeignClient;
public Item queryItemById1(Long id){
return this.restTemplate.getForObject(this.orderProerties.getItem().getUrl()+id, Item.class);
}
public Item queryItemById2(Long id){
String serviceId="itcast-microservice-item";
List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
if(instances.isEmpty()){
return null;
}
// 为了演示,在这里只获取一个实例
ServiceInstance instance = instances.get(0);
String url = instance.getHost()+":"+instance.getPort();
return this.restTemplate.getForObject("http://"+url+"/item/" + id, Item.class);
}
@HystrixCommand(fallbackMethod = "queryItemByIdFallbackMethod") // 进行容错处理
public Item queryItemById(Long id){
String serviceId="itcast-microservice-item";
return this.restTemplate.getForObject("http://"+serviceId+"/item/" + id, Item.class);
}
public Item queryItemByIdFallbackMethod(Long id){ // 请求失败执行的方法
return new Item(id, "查询商品信息出错!", null, null, null);
}
启动注册中心 6868
启动配置服务 6688 http://localhost:6688/microservice-dev.yml
启动item服务 8081 8181 测试商品服务的负载均衡
启动order服务 8082
启动网关服务 6677 网关验证是否登录 访问是否带token
http://localhost:8081/test 测试config server
注册中心Eureka
负载均衡 Ribbon
容错保护 Hystrix
优雅Rest Feign
服务网关 spring cloud zuul
服务配置 spring cloud config