-
微服务定义:
微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法,每个服务运行在自己的进程中,服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动和部署机制独立部署。这些服务共用一个最小型的集中式的管理,服务可用不同的语言开发,使用不同的数据存储技术;
-
微服务的特性:
- 每个微服务可独立运行在自己的进程里
- 一系列独立运行的微服务共同构建起整个系统
- 每个服务为独立的业务开发,一个微服务只关注某个特定的功能,例如订单管理、用户管理等
- 可使用不同的语言与存储技术(契合项目情况与团队实力)
- 微服务之间通过轻量的通信机制进行通信,例如通过REST API进行调用;
- 全自动的部署机制
Spring Cloud 就是微服务系统架构的一站式解决方案,简单地说它就是微服务框架其中的一种,在平时我们构建微服务的过程中需要做如 服务发现注册 、配置中心 、消息总线 、负载均衡 、断路器 、数据监控 等操作,而 Spring Cloud 为我们提供了一套简易的编程模型,使我们能在 Spring Boot 的基础上轻松地实现微服务项目的构建。
消费者与提供者服务A调用服务B,服务B调用服务C,那么服务B是什么角色?
服务调用关系- 服务提供者:暴露接口给其他微服务调用
- 服务消费者:调用其他微服务提供的接口
提供者和消费者角色其实是相对的,一个服务可以同时是服务提供者和服务消费者
Eureka注册中心在微服务架构中往往会有一个注册中心,每个微服务都会向注册中心去注册自己的地址及端口信息,注册中心维护着服务名称与服务实例的对应关系。每个微服务都会定时从注册中心获取服务列表,同时汇报自己的运行情况,这样当有的服务需要调用其他服务时,就可以从自己获取到的服务列表中获取实例地址进行调用,Eureka实现了这套服务注册与发现机制。
负载均衡之 RibbonRibbon是Netflix发布的负载均衡器。属于SpringCloud组件之一,用于实现客户端负载均衡功能,运行在消费者端,其工作原理就是 Consumer 端获取到了所有的服务列表之后,在其内部使用负载均衡算法,进行对多个系统的调用。。
ribbon默认采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载会在项目启动时创建,降低第一次访问的耗时,项目一启动,把所有服务全加载进来,把服务列表缓存在内存当中,后面再需要用的话就直接用了。这个可以通过更改配置来修改ribbon的默认加载策略。
Nginx 和 Ribbon 的对比提到 负载均衡 就不得不提到 Nignx 了,而和 Ribbon 不同的是,Nignx是一种集中式的负载均衡器。
何为集中式呢?简单理解就是 将所有请求都集中起来,然后再进行负载均衡,在 Nginx 中请求是先进入负载均衡器,而在 Ribbon 中是先在客户端进行负载均衡才进行请求的。
Ribbon 的几种负载均衡算法负载均衡,不管 Nginx 还是 Ribbon 都需要其算法的支持,如果我没记错的话 Nginx 使用的是 轮询和加权轮询算法。而在 Ribbon 中有更多的负载均衡调度算法,其默认是使用的 RoundRobinRule 轮询策略。
- RoundRobinRule:轮询策略。Ribbon 默认采用的策略。若经过一轮轮询没有找到可用的 provider,其最多轮询 10 轮。若最终还没有找到,则返回 null。
- RandomRule: 随机策略,从所有可用的 provider 中随机选择一个。
- RetryRule: 重试策略。先按照 RoundRobinRule 策略获取 provider,若获取失败,则在指定的时限内重试。默认的时限为 500 毫秒。
总结
-
Ribbon负载均衡规则
- 规则接口是IRule
- 默认实现是ZoneAvoidanceRule,根据zone选择服务列表,然后轮询
-
负载均衡自定义方式
- 代码方式:配置灵活,但修改时需要重新打包发布
- 配置方式:直观,方便,无需重新打包发布,但是无法做全局配置
- 饥饿加载
- 开启饥饿加载
- 指定饥饿加载的微服务名称
我们再java代码里面发http请求,Spring提供一个工具类,RestTemplate,在配置类里面或者启动类用@Bean注册RestTemplate,如果发get请求,则用restTemplate.getForObject(第一个String类型参数为需要调用项目的url也就是ip地址和端口以及请求参数等,第二个参数为返回值类型是对象还是json),如果是post请求,则用restTemplate.postForObject(),如下代码,这样用起来很不方便。后面介绍Feign远程调用,更加方便。
@Autowired
private RestTemplate restTemplate;
// 这里是提供者A的ip地址,但是如果使用了 Eureka 那么就应该是提供者A的名称
private static final String SERVICE_PROVIDER_A = "http://localhost:8081";
@PostMapping("/judge")
public boolean judge(@RequestBody Request request) {
String url = SERVICE_PROVIDER_A + "/service1";
// 是不是太麻烦了???每次都要 url、请求、返回类型的
return restTemplate.postForObject(url, request, Boolean.class);
}
OpenFeign 也是运行在消费者端的,使用 Ribbon 进行负载均衡,所以 OpenFeign 直接内置了 Ribbon。
@FeignClient(name = "ecp.msg.svc", url = "${feign.url.ecp-msg-svc:}")
@Api(tags = "消息发送服务")
public interface EcpMsgSendRecordService {
@ApiOperation("根据条件搜索消息发送记录")
@PostMapping("/msg/sendRecord/search")
public ApiResult> searchByCondition(@RequestBody EcpMsgSendRecordSearchDTO msgSendRecordSearchDTO);
@ApiOperation("发送消息")
@PostMapping("/msg/send")
public ApiResult sendMsg(@RequestBody @Valid SendMsgInfoVo msg);
@ApiOperation("更新消息的业务状态(例如:问卷调查,未填写->已填写)")
@PostMapping("/msg/updateMsgStatus")
public ApiResult updateMsgStatus(@RequestBody @Valid UpdateMsgParam updateMsgParam);
@ApiOperation("修改消息体发送结果记录数")
@PostMapping("/msg/updateSendSuccessCount")
public ApiResult updateSendSuccessCount(@RequestBody List successCountInfo);
}
在导入了 Open Feign 之后我们就可以远程调用服务,更加方便的编写 Consumer 端代码。
Hystrix之熔断和降级官方是这么说的:在分布式环境中,不可避免地会有许多服务依赖项中的某些失败。Hystrix是一个库,可通过添加等待时间容限和容错逻辑来帮助您控制这些分布式服务之间的交互。Hystrix通过隔离服务之间的访问点,停止服务之间的级联故障并提供后备选项来实现此目的,所有这些都可以提高系统的整体弹性。
总体来说 Hystrix 就是一个能进行 熔断 和 降级 的库,通过使用它能提高整个系统的弹性。
那么什么是 熔断和降级 呢?例如,此时我们整个微服务系统是这样的。服务A调用了服务B,服务B再调用了服务C,但是因为某些原因,服务C顶不住了,这个时候大量请求会在服务C阻塞。
服务C阻塞了还好,毕竟只是一个系统崩溃了。但是请注意这个时候因为服务C不能返回响应,那么服务B调用服务C的的请求就会阻塞,同理服务B阻塞了,那么服务A也会阻塞崩溃。
注意,为什么阻塞会崩溃。因为这些请求会消耗占用系统的线程、IO 等资源,消耗完你这个系统服务器不就崩了么。
像这种情况,就叫 服务雪崩。
所谓 熔断 就是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过 断路器 直接将此请求链路断开。
也就是我们上面服务B调用服务C在指定时间窗内,调用的失败率到达了一定的值,那么 Hystrix 则会自动将 服务B与C 之间的请求都断了,以免导致服务雪崩现象。
其实这里所讲的 熔断 就是指的 Hystrix 中的 断路器模式 ,你可以使用简单的 @HystrixCommand 注解来标注某个方法,这样 Hystrix 就会使用 断路器 来“包装”这个方法,每当调用时间超过指定时间时(默认为1000ms),断路器将会中断对这个方法的调用。
也可以对这个注解的很多属性进行设置,比如设置超时时间,像这样。
@HystrixCommand(
commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1200")}
)
public List getXxxx() {
// ...省略代码逻辑
}
服务降级是为了更好的用户体验,当一个方法调用异常时,通过执行另一种代码逻辑来给用户友好的回复。这也就对应着 Hystrix 的 后备处理 模式。可以通过设置 fallbackMethod 来给一个方法设置备用的代码逻辑。比如这个时候有一个热点新闻出现了,我们会推荐给用户查看详情,然后用户会通过id去查询新闻的详情,但是因为这条新闻太火了,大量用户同时访问可能会导致系统崩溃,那么我们就进行 服务降级 ,一些请求会做一些降级处理比如当前人数太多请稍后查看等等。
// 指定了后备方法调用
@HystrixCommand(fallbackMethod = "getHystrixNews")
@GetMapping("/get/news")
public News getNews(@PathVariable("id") int id) {
// 调用新闻系统的获取新闻api 代码逻辑省略
}
//
public News getHystrixNews(@PathVariable("id") int id) {
// 做服务降级
// 返回当前人数太多,请稍后查看
}
Gateway网关
我们知道 服务提供者是 消费者通过 Eureka Server 进行访问的,即 Eureka Server 是 服务提供者的统一入口。那么整个应用中存在那么多 消费者需要用户进行调用,这个时候用户该怎样访问这些 消费者工程呢?当然可以像之前那样直接访问这些工程。但这种方式没有统一的消费者工程调用入口,不便于访问与管理,而网关就是这样的一个对于 消费者 的统一入口。
Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。
Spring Cloud Gateway 可以看做是一个 Zuul 1.x 的升级版和代替品,比 Zuul 2 更早的使用 Netty 实现异步 IO,从而实现了一个简单、比 Zuul 1.x 更高效的、与 Spring Cloud 紧密配合的 API 网关。
相关概念-
Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由;
-
Predicate(断言):指的是Java 8 的 Function Predicate。 输入类型是Spring框架中的ServerWebExchange。 这使开发人员可以匹配HTTP请求中的所有内容,例如请求头或请求参数。如果请求与断言相匹配,则进行路由;
-
Filter(过滤器):指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前后对请求进行修改。
- 在application.yml中进行配置:
server:
port: 9201
service-url:
user-service: http://localhost:8201
spring:
cloud:
gateway:
routes:
- id: path_route #路由的ID
uri: ${service-url.user-service}/user/{id} #匹配后路由地址
predicates: # 断言,路径相匹配的进行路由
- Path=/user/{id}
-
启动eureka-server,user-service和api-gateway服务,并调用该地址测试:http://localhost:9201/user/1
-
发现该请求被路由到了user-service的该路径上:http://localhost:8201/user/1
Spring Cloud Config 为分布式系统中的外部化配置提供服务器和客户端支持。使用 Config 服务器,可以在中心位置管理所有环境中应用程序的外部属性。
简单来说,Spring Cloud Config 就是能将各个 应用/系统/模块 的配置文件存放到 统一的地方然后进行管理(Git 或者 SVN)。
我们的应用只有启动的时候才会进行配置文件的加载,那么 Spring Cloud Config 就暴露出一个接口给启动应用来获取它所想要的配置文件,应用获取到配置文件然后再进行它的初始化工作。就如下图。
如果我在应用运行时去更改远程配置仓库(Git)中的对应配置文件,那么依赖于这个配置文件的已启动的应用会不会进行其相应配置的更改呢?
答案是不会的,一般会使用 Bus 消息总线 + Spring Cloud Config 进行配置的动态刷新。
Spring Cloud Bus用于将服务和服务实例与分布式消息系统链接在一起的事件总线。在集群中传播状态更改很有用(例如配置更改事件)。
可以简单理解为 Spring Cloud Bus 的作用就是管理和广播分布式系统中的消息,也就是消息引擎系统中的广播模式。作为 消息总线 的 Spring Cloud Bus 可以做很多事而不仅仅是客户端的配置刷新功能。
拥有了 Spring Cloud Bus 之后,只需要创建一个简单的请求,并且加上 @ResfreshScope 注解就能进行配置的动态修改了,如下图。



