---恢复内容开始---
分布式项目架构
根据业务需求进行拆分成N个子系统,多个子系统相互协作才能完成业务流程子系统之间通讯使用RPC远程通讯技术。
优点:
1.把模块拆分,使用接口通信,降低模块之间的耦合度。
2.把项目拆分成若干个子项目,不同的团队负责不同的子项目。
3.增加功能时只需要再增加一个子项目,调用其它系统的接口就可以。
4.可以灵活的进行分布式部署。
有优点就有缺点,缺点如下:
1.系统之间交互需要使用远程通信,接口开发增加工作量。
2.各个模块有一些通用的业务逻辑无法共用。
为了解决上面分布式架构的缺点,我们引入了soa架构,SOA:Service Oriented Architecture面向服务的架构。也就是把工程拆分成服务层、表现层两个工程。服务层中包含业务逻辑,只需要对外提供服务即可。表现层只需要处理和页面的交互,业务逻辑都是调用服务层的服务来实现。
rpc远程调用框架
几种比较典型的RPC的实现和调用框架。
(1)RMI实现,利用java.rmi包实现,基于Java远程方法协议(Java Remote Method Protocol)
和java的原生序列化。
(2)Hessian,是一个轻量级的remoting
onhttp工具,使用简单的方法提供了RMI的功能。 基于HTTP协议,采用二进制编解码。
(3)thrift是一种可伸缩的跨语言服务的软件框架。thrift允许你定义一个描述文件,描述数据类型和服务接口。依据该文件,编译器方便地生成RPC客户端和服务器通信代码。
(4)SpringCloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。
(4) Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。
1.服务的注册与发现(Eureka )(注册中心)
Eureka环境搭建:
1.pom文件如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--eureka server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
<!-- spring boot test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RC1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
2.配置application.yml
port: 8888
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
3.配置Springboot的启动文件
package com.nswi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
*
*@author jianghaojie
*Date:2019年3月14日
*
*/
@SpringBootApplication
@EnableEurekaServer
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
4.打开eureka可以看见生产者service-menmber与service-order都同时注入到eureka中
查看service-menber
查看service-order
生产者与消费者将IP和端口号注入eureka,消费者在eureka中拿到生产者的IP与端口号,在通过httpclient请求对生产者进行调用
使用ribbon实现负载均衡
启动两个会员服务工程,端口号分别为8762、8763,订单服务 使用负载均衡策略轮训到会员服务接口。
但是外面一般采用fengn
服务消费者(Feign)
什么是Feign
Feign是一个声明式的伪Http客户端,它使得写Http客户端变得更简单。使用Feign,只需要创建一个接口并注解。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。Feign支持可插拔的编码器和解码器。Feign默认集成了Ribbon,并和Eureka结合,默认实现了负载均衡的效果。
简而言之:
- Feign 采用的是基于接口的注解
- Feign 整合了ribbon
Maven依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties>
<dependencies> <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.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
<repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> |
application.yml配置
eureka: client: serviceUrl: defaultZone: http://localhost:8888/eureka/ server: port: 8765 spring: application: name: service-order-feign |
service层如下
/**
*
*/
package com.nswi.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import com.nswi.fallbake.MenberFallbake;
/**
*
*@author jianghaojie
*Date:2019年3月15日
* feign
*/
@FeignClient(value ="service-member" )
public interface FengnService {
@RequestMapping("/getall")
public List<String > feigngetall();
}control层如下
/**
*
*/
package com.nswi.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.nswi.service.FengnService;
/**
*
*@author jianghaojie
*Date:2019年3月15日
*
*/
@RestController
public class FengnControlller {
@Autowired
private FengnService service;
@RequestMapping(value="getall")
public List<String > feigngetall2(){
return service.feigngetall();
}
}
application的启动如下
/**
*
*/
package com.nswi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
/**
*
*@author jianghaojie
*Date:2019年3月15日
*
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class FeignApp {
public static void main(String[] args) {
SpringApplication.run(FeignApp.class, args);
}
}
同样他也能fengn作负载均衡。采用轮训机制
效果如下
4.路由网关zuul
Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能, 类似于nginx转发。
Maven依赖
创建工程service-zuul
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath /> <!-- lookup parent from repository --> </parent>
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</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> <scope>test</scope> </dependency> </dependencies>
<dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RC1</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
<repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> |
application.yml配置
eureka: client: serviceUrl: defaultZone: http://localhost:8888/eureka/ server: port: 8769 spring: application: name: service-zuul zuul: routes: api-a: path: /api-member/** service-id: service-member api-b: path: /api-order/** service-id: service-order |
/api-order/**这个是以/api-order/拦截开头的后面任何地址
application的启动项如下
/**
*
*/
package com.nswi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
*
*@author jianghaojie
*Date:2019年3月15日
*
*/
@EnableZuulProxy
@EnableEurekaClient
@SpringBootApplication
public class ZuulAPP {
public static void main(String[] args) {
SpringApplication.run(ZuulAPP.class, args);
}
}
发送请求http://127.0.0.1:8769/api-member/getMemberAll
转发到http://127.0.0.1:8762/getMemberAll
开启网关 @EnableZuulProxy
服务过滤
@Component public class MyFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(MyFilter.class);
@Override public String filterType() { return "pre"; }
@Override public int filterOrder() { return 0; }
public boolean shouldFilter() { return true; }
public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); log.info(String.format("%s >>> %s", request.getMethod(), request.getRequestURL().toString())); Object accessToken = request.getParameter("token"); if (accessToken != null) { return null; } log.warn("token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); try { ctx.getResponse().getWriter().write("token is empty"); } catch (Exception e) { } return null;
} } |
消费者的controller层测试配置如下
/**
*
*/
package com.nswi.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.nswi.service.OrderService;
/**
*
*@author jianghaojie
*Date:2019年3月14日
*
*/
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping("/getOrederUserall")
public List<String > getOrederUserall() {
System.out.println("订单开始调用任务");
return orderService.getOrder();
}
@RequestMapping("/getorderapi")
public String getOrderapi() {
return "this is order service";
}
}
生产者conroller层配置如下(主要是增加了加黑的配置方便区别和学习)
/**
*
*/
package com.nswi.concroller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
*@author jianghaojie
*Date:2019年3月14日
*
*/
@RestController
public class MemberController {
@Value("${server.port}")
private String serverport;
@RequestMapping(value="/getall")
public List<String> getall() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
List<String >list= new ArrayList<>();
list.add("zhangfei");
list.add("lisi");
list.add("wangwu");
list.add(serverport);
return list;
}
@RequestMapping("/getmemberApi")
public String getmemberApi() {
return "this is vip service";
}
}
断路器(Hystrix)
为什么需要 Hystrix?
在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用(RPC)。为了保证其高可用,单个服务又必须集群部署。由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务累计,导致服务瘫痪,甚至导致服务“雪崩”。为了解决这个问题,就出现断路器模型。
Hystrix 是一个帮助解决分布式系统交互时超时处理和容错的类库, 它同样拥有保护系统的能力.什么是服务雪崩
分布式系统中经常会出现某个基础服务不可用造成整个系统不可用的情况, 这种现象被称为服务雪崩效应. 为了应对服务雪崩, 一种常见的做法是手动服务降级. 而Hystrix的出现,给我们提供了另一种选择.
服务雪崩应对策略
针对造成服务雪崩的不同原因, 可以使用不同的应对策略:
- 流量控制
- 改进缓存模式
- 服务自动扩容
- 服务调用者降级服务
流量控制 的具体措施包括:
- 网关限流
- 用户交互限流
- 关闭重试
服务雪崩解决办法
1.服务雪崩的原因
(1)某几个机器故障:例如机器的硬驱动引起的错误,或者一些特定的机器上出现一些的bug(如,内存中断或者死锁)。
(2)服务器负载发生变化:某些时候服务会因为用户行为造成请求无法及时处理从而导致雪崩,例如阿里的双十一活动,若没有提前增加机器预估流量则会造服务器压力会骤然增大二挂掉。
(3)人为因素:比如代码中的路径在某个时候出现bug
2.解决或缓解服务雪崩的方案
一般情况对于服务依赖的保护主要有3中解决方案:
(1)熔断模式:这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾。放到我们的系统中,如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
(2)隔离模式:这种模式就像对系统请求按类型划分成一个个小岛的一样,当某个小岛被火少光了,不会影响到其他的小岛。例如可以对不同类型的请求使用线程池来资源隔离,每种类型的请求互不影响,如果一种类型的请求线程资源耗尽,则对后续的该类型请求直接返回,不再调用后续资源。这种模式使用场景非常多,例如将一个服务拆开,对于重要的服务使用单独服务器来部署,再或者公司最近推广的多中心。
(3)限流模式:上述的熔断模式和隔离模式都属于出错后的容错处理机制,而限流模式则可以称为预防模式。限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。这种模式不能解决服务依赖的问题,只能解决系统整体资源分配问题,因为没有被限流的请求依然有可能造成雪崩效应。
3.熔断设计
在熔断的设计主要参考了hystrix的做法。其中最重要的是三个模块:熔断请求判断算法、熔断恢复机制、熔断报警
(1)熔断请求判断机制算法:使用无锁循环队列计数,每个熔断器默认维护10个bucket,每1秒一个bucket,每个blucket记录请求的成功、失败、超时、拒绝的状态,默认错误超过50%且10秒内超过20个请求进行中断拦截。
(2)熔断恢复:对于被熔断的请求,每隔5s允许部分请求通过,若请求都是健康的(RT<250ms)则对请求健康恢复。
(3)熔断报警:对于熔断的请求打日志,异常请求超过某些设定则报警
4.隔离设计
隔离的方式一般使用两种
(1)线程池隔离模式:使用一个线程池来存储当前的请求,线程池对请求作处理,设置任务返回处理超时时间,堆积的请求堆积入线程池队列。这种方式需要为每个依赖的服务申请线程池,有一定的资源消耗,好处是可以应对突发流量(流量洪峰来临时,处理不完可将数据存储到线程池队里慢慢处理)
(2)信号量隔离模式:使用一个原子计数器(或信号量)来记录当前有多少个线程在运行,请求来先判断计数器的数值,若超过设置的最大线程个数则丢弃改类型的新请求,若不超过则执行计数操作请求来计数器+1,请求返回计数器-1。这种方式是严格的控制线程且立即返回模式,无法应对突发流量(流量洪峰来临时,处理的线程超过数量,其他的请求会直接返回,不继续去请求依赖的服务)
5 超时机制设计
超时分两种,一种是请求的等待超时,一种是请求运行超时。
等待超时:在任务入队列时设置任务入队列时间,并判断队头的任务入队列时间是否大于超时时间,超过则丢弃任务。
运行超时:直接可使用线程池提供的get方法
什么是熔断机制
熔断机制,就是下游服务出现问题后,为保证整个系统正常运行下去,而提供一种降级服务的机制,通过返回缓存数据或者既定数据,避免出现系统整体雪崩效应。在springcloud中,该功能可通过配置的方式加入到项目中。
Hystrix作用
1.断路器机制
断路器很好理解, 当Hystrix Command请求后端服务失败数量超过一定比例(默认50%), 断路器会切换到开路状态(Open). 这时所有请求会直接失败而不会发送到后端服务. 断路器保持在开路状态一段时间后(默认5秒), 自动切换到半开路状态(HALF-OPEN). 这时会判断下一次请求的返回情况, 如果请求成功, 断路器切回闭路状态(CLOSED), 否则重新切换到开路状态(OPEN). Hystrix的断路器就像我们家庭电路中的保险丝, 一旦后端服务不可用, 断路器会直接切断请求链, 避免发送大量无效请求影响系统吞吐量, 并且断路器有自我检测并恢复的能力.
2.Fallback
Fallback相当于是降级操作. 对于查询操作, 我们可以实现一个fallback方法, 当请求后端服务出现异常的时候, 可以使用fallback方法返回的值. fallback方法的返回值一般是设置的默认值或者来自缓存.
3.资源隔离
在Hystrix中, 主要通过线程池来实现资源隔离. 通常在使用的时候我们会根据调用的远程服务划分出多个线程池. 例如调用产品服务的Command放入A线程池, 调用账户服务的Command放入B线程池. 这样做的主要优点是运行环境被隔离开了. 这样就算调用服务的代码存在bug或者由于其他原因导致自己所在线程池被耗尽时, 不会对系统的其他服务造成影响. 但是带来的代价就是维护多个线程池会对系统带来额外的性能开销. 如果是对性能有严格要求而且确信自己调用服务的客户端代码不会出问题的话, 可以使用Hystrix的信号模式(Semaphores)来隔离资源.
服务的降级
什么是服务降级
所有的RPC技术里面服务降级是一个最为重要的话题,所谓的降级指的是当服务的提供方不可使用的时候,程序不会出现异常,而会出现本地的操作调
service-order工程新增Maven依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> |
下面使用feign演示熔断机制
service层如下:
加入的东西我加粗重点
/**
*
*/
package com.nswi.service;
import java.util.List;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import com.nswi.fallbake.MenberFallbake;
/**
*
*@author jianghaojie
*Date:2019年3月15日
* feign
*/
@FeignClient(value ="service-member" ,fallback=MenberFallbake.class)
public interface FengnService {
@RequestMapping("/getall")
public List<String > feigngetall();
}
controller层如下
/**
*
*/
package com.nswi.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.nswi.service.FengnService;
/**
*
*@author jianghaojie
*Date:2019年3月15日
*
*/
@RestController
public class FengnControlller {
@Autowired
private FengnService service;
@RequestMapping(value="getall")
public List<String > feigngetall2(){
return service.feigngetall();
}
}
关键的fallbake如下也就是出现高并发服务器出现不能立即响应的情况的解决方案
/**
*
*/
package com.nswi.fallbake;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Component;
import com.nswi.service.FengnService;
/**
*
*@author jianghaojie
*Date:2019年3月15日
*
*/
@Component
public class MenberFallbake implements FengnService {
@Override
public List<String> feigngetall() {
List<String> List = new ArrayList<>();
List.add("服务发生异常");
return List;
}
}
application.yml的配置如下(黑色为重点)
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8888/eureka/
server:
port: 8765
spring:
application:
name: service-order-feign
feign:
hystrix:
enabled: true
###超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 2000
这个是hystrix超时时间的配置,加黑的为重点
下面是启动项不变
/**
*
*/
package com.nswi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.feign.EnableFeignClients;
/**
*
*@author jianghaojie
*Date:2019年3月15日
*
*/
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class FeignApp {
public static void main(String[] args) {
SpringApplication.run(FeignApp.class, args);
}
}在member(生产者)里面用thread。sleep的故意延迟响应方面查看熔断机制
/**
*
*/
package com.nswi.concroller;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
*@author jianghaojie
*Date:2019年3月14日
*
*/
@RestController
public class MemberController {
@Value("${server.port}")
private String serverport;
@RequestMapping(value="/getall")
public List<String> getall() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
List<String >list= new ArrayList<>();
list.add("zhangfei");
list.add("lisi");
list.add("wangwu");
list.add(serverport);
return list;
}
@RequestMapping("/getmemberApi")
public String getmemberApi() {
return "this is vip service";
}
}
就会产生如下的这样的效果,但是不影响其他线程,如果其他响应正常的话,也能做出反应,这就是熔断机制