SpringCloud
1 技术架构
2 版本
33添加父工程
3.1 父工程依赖
<!--添加在项目的版本下面 父工程的pom-->
<packaging>pom</packaging>
<!-- 统一管理jar包版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>8.0.22</mysql.version>
<druid.version>1.1.16</druid.version>
<mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
</properties>
<!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version -->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>
dependencyManagement:一般出现在父工程,在父工程指定版本,子工程的依赖版本可以不用设置,直接会在父类找,只声明依赖,并不引入依赖。
4 cloud_privader_payment8001 服务提供者
创建一个服务提供者,有自己的springboot完整的一套,配置文件、实体类、controller,service、dao、mapper配置文件一全套。
创建一个消费者,只有配置文件、实体类、controller,消费者的实体类是和服务提供者的实体类一样。
4.1 子项目依赖
<dependencies>
<!--包含了sleuth+zipkin-->
<!--刚开始还用不到这两个依赖-->
<!--
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
4.2、编写配置文件
第一个模块的配置文件
server:
port: 8002
spring:
application:
name: cloud_payment_service #这个微服务名称很重要,后面会用到。
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作
# driver-class-name: org.gjt.mm.mysql.Driver #mysql驱动包
url: jdbc:mysql://47.93.49.117:3306/db2020?serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entity #所有Entity别名类所在的包
4.3、服务者的Controller
import com.atguigu.springcloud.entity.CommentResult;
import com.atguigu.springcloud.entity.Payment;
import com.atguigu.springcloud.service.PaymentServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@Slf4j //打印日志
public class PaymentController {
@Resource private PaymentServiceImpl paymentService;
@RequestMapping(value = "/payment/create")
public CommentResult createPayment(@RequestBody Payment payment){
int result = paymentService.create(payment);
log.info("*****插入结果"+result);
if(result > 0){
return new CommentResult(200,"插入数据成功!",result);
}else {
return new CommentResult(4444,"插入数据失败!",null);
}
}
//Restful风格
@GetMapping(value = "/payment/get/{id}")
public CommentResult getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("*****查询结果"+payment);
if(payment==null){
return new CommentResult(4444,"数据查询失败!",null);
}else {
return new CommentResult(200,"数据查询成功!",payment+"kk");
}
}
}
4.4、编写启动类
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
这样配置启动没有任何错误
//Restful风格
@GetMapping(value = "/payment/get/{id}")
public CommentResult getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("*****查询结果"+payment);
if(payment==null){
return new CommentResult(200,"数据查询成功!",payment);
}else {
return new CommentResult(4444,"数据查询失败!",null);
}
}
4.5、热部署 Devtools
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
2、添加配置插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
3、开启自动编译权限
4、ctrl+shift+Alt +/
5、重启idea
这样修改后台代码就不用重启服务器了
5、cloud_consmer_order80 消费者
5.1、RestTemplate
远程访问的封装。传统情况下在java代码里访问restful服务,一般使用Apache的HttpClient。不过此种方法使用起来太过繁琐。spring提供了一种简单便捷的模板类来进行操作,这就是RestTemplate。
@RequestBody
@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);GET方式无请求体,所以使用 @RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
注:一个请求,只有一个RequestBody;一个请求,可以有多个RequestParam。
注:当同时使用@RequestParam()和@RequestBody时,@RequestParam()指定的参数可以是普通元素、 数组、集合、对象等等(即:当,@RequestBody 与@RequestParam()可以同时使用时,原SpringMVC接收 参数的机制不变,只不过RequestBody 接收的是请求体里面的数据;而RequestParam接收的是key-value 里面的参数,所以它会被切面进行处理从而可以用普通元素、数组、集合、对象等接收)。 即:如果参数时放在请求体中,application/json传入后台的话,那么后台要用@RequestBody才能接收到; 如果不是放在请求体中的话,那么后台接收前台传过来的参数时,要用@RequestParam来接收,或 则形参前 什么也不写也能接收。
5.2、依赖
<dependencies>
<!--包含了sleuth+zipkin-->
<!-- <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency><!– 引入自己定义的api通用包,可以使用Payment支付Entity –>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
5.3、配置文件
server:
port: 80
5.4、编写ApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
//把RestTemplate加进来
@Configuration
public class ApplicationContext {
@Bean
public RestTemplate getRestemplate(){
return new RestTemplate();
}
}
5.5、编写消费者的Controller
import com.atguigu.entity.CommentResult;
import com.atguigu.entity.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderController {
public static final String payment_url = "http://localhost:8002";
@Resource private RestTemplate restTemplate;
@GetMapping("/consumer/payment/create")
public CommentResult<Payment> create(Payment payment){
return restTemplate.postForObject(payment_url+"/payment/create/",payment,CommentResult.class);
}
@GetMapping("/consumer/payment/get")
public CommentResult<Payment> getPaymeny(Long id){
return restTemplate.getForObject(payment_url+"/payment/get/"+id,CommentResult.class);
}
}
5.6、让idea出现RunDashboard
打开idea的.idea文件的workspace
修改找到,没有就直接添加进去
<component name="RunDashboard">
<option name="configurationTypes">
<set>
<option value="SpringBootApplicationConfigurationType" />
</set>
</option>
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
<option name="contentProportion" value="0.13436294" />
</component>
6、项目重构
新建一个项目,放重复的代码!比如在服务提供者和消费者 中的实体类是一样的,我们就可以把这种放在一个项目中。
6.1、新建一个字子项目cloud_api_common
6.1.1、依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--工具依赖-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
将实体类复制到cloud_api_common中
6.1.2加载
都显示成功之后就可以了
6.1.3、导入
将这个工具项目导入到其他子项目
在pom.xm查看这个项目的版本项目名等信息
然后将依赖添加到其他子项目
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.example</groupId>
<artifactId>cloud_api_common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
7、服务注册中心
7.1、Eureka服务注册中心(停更)
7.1.1、服务治理
7.1.2、注册与发现
7.1.3、Eureka的两个组件
7.1.3.1、Eureka Server
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。 Eureka Server本身也是一个服务,默认情况下会自动注册到Eureka注册中心。 如果搭建单机版的Eureka Server注册中心,则需要配置取消Eureka Server的自动注册逻辑。毕竟当前服务注册到当前服务代表的注册中心中是一个说不通的逻辑。 Eureka Server通过Register、Get、Renew等接口提供服务的注册、发现和心跳检测等服务。
7.1.1.3.2Eureka Client
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。 Eureka Client分为两个角色,分别是:Application Service(Service Provider)和Application Client(Service Consumer)
7.1.4、单机Eureka的构建步骤
1、构建新的项目
cloud_eureka_server7001
2、导入依赖
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.example</groupId>
<artifactId>cloud_api_common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
3、书写配置
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#集群指向其它eureka
#defaultZone: http://eureka7002.com:7002/eureka/
#单机就是7001自己
defaultZone: http://localhost:7001/eureka/
#server:
#关闭自我保护机制,保证不可用服务被及时踢除
#enable-self-preservation: false
#eviction-interval-timer-in-ms: 2000
4、在启动类上配置Eureka的服务中心
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
/*配置 Eureka的服务中心*/
@EnableEurekaServer
public class Cloud_Eureka_Server {
public static void main(String[] args) {
SpringApplication.run(Cloud_Eureka_Server.class,args);
}
}
5、测试
7.1.4.1、Cloud_provider_payment8001注册进EurekaServer
EurekaClient端cloud-provider-payment8001将注册进EurekaServer成为服务提供者provider
1、修改Cloud_provider_payment8001
修改一:在启动类加@EnableEurekaClient
@SpringBootApplication
/*添加Eureka客户端*/
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
修改二:添加依赖,Eureka客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
修改三:修改配置文件,添加Eureka相关
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机版
defaultZone: http://localhost:7001/eureka
# 集群版
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001
#访问路径可以显示IP地址
prefer-ip-address: true
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
#lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
#lease-expiration-duration-in-seconds: 2
2、测试
7.1.4.2、cloud_consumer_order80消费者注册进Eureka
EurekaClient端cloud-consumer-order80将注册进EurekaServer成为服务消费者consumer。
1、修改cloud_order_server
修改一:在启动类加@EnableEurekaClient
@SpringBootApplication
/*添加Eureka客户端*/
@EnableEurekaClient
public class Order80 {
public static void main(String[] args) {
SpringApplication.run(Order80.class,args);
}
}
修改二:添加依赖,Eureka客户端依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
修改三:修改配置文件,添加Eureka相关
spring:
application:
name: cloud_order_service #这个微服务名称很重要,在Eureka中显示的就是这个微服务名字
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机
defaultZone: http://localhost:7001/eureka
# 集群
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
2、测试
7.1.5、Eureka集群
微服务RPC远程调用的核心
高可用
7.1.5.1、修改Eureka,创建集群
每一个注册中心的服务都是相互指向。
1、修改cloud_eureka_sever7001
修改配置文件
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #自己的hostname
#hostname: localhost #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#集群指向其它eureka
defaultZone: http://eureka7002.com:7002/eureka/ #在7001服务中指向其他集群
#单机就是7001自己
#defaultZone: http://localhost:7001/eureka/
#server:
#关闭自我保护机制,保证不可用服务被及时踢除
#enable-self-preservation: false
#eviction-interval-timer-in-ms: 2000
2、创建cloud_eureka_server7002
依赖、启动类和7001一样
配置文件不同
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #自己的hostname
#hostname: localhost #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#集群指向其它eureka
defaultZone: http://eureka7001.com:7001/eureka/
#单机就是7001自己
#defaultZone: http://localhost:7001/eureka/
#server:
#关闭自我保护机制,保证不可用服务被及时踢除
#enable-self-preservation: false
#eviction-interval-timer-in-ms: 2000
3、修改host文件模拟两台主机
########springcloud#######
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
4、测试
7.1.5.1、将服务提供者和消费者注册进Eureka集群
两个文件只用修改配置文件即可
cloud_provider_payment8001
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机版
#defaultZone: http://localhost:7001/eureka
# 集群版
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #此时应该用集群
instance:
instance-id: payment8001
#访问路径可以显示IP地址
prefer-ip-address: true
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
#lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
#lease-expiration-duration-in-seconds: 2
cloud_consumer_order80
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机
#defaultZone: http://localhost:7001/eureka
# 集群
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
测试
服务启动顺序:先启动Eureka集群,再启动服务提供者8001,最后启动消费者80
7.1.6、服务提供者集群Cloud_provider_payment8001
改错 把配置值文件的微服务名称的下划线都改成横杠
1、参考8001创建8002
将8002的配置文件的服务端口改成8002,其他的完全复制。
2、修改消费者cloud_consumer_order80
-
服务提供者集群,所以调用服务提供者的时候需要知道 public static final String payment_url = "http://cloud-payment-service";
-
@RestController @Slf4j public class OrderController { /*多个提供者微服务要用服务名 * 服务提供者集群的:用微服务名 * * 单个 微服务就localhost:端口号 * */ public static final String payment_url = "http://cloud-payment-service"; @Resource private RestTemplate restTemplate; @RequestMapping("/consumer/payment/create") public CommonResult<Payment> create(Payment payment){ return restTemplate.postForObject(payment_url+"/payment/create/",payment,CommonResult.class); } @RequestMapping("/consumer/payment/get") public CommonResult<Payment> getPaymeny(Long id){ return restTemplate.getForObject(payment_url+"/payment/get/"+id,CommonResult.class); } } -
修改RestTemplate,在Application配置类中
-
负载均衡
@Configuration public class ApplicationContext { @Bean @LoadBalanced //负载均衡 public RestTemplate getRestemplate(){ return new RestTemplate(); } }
7.1.7、actuator 完成主机名称的修改
1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2、修改8001、8002配置文件
eureka:
#在eureka下加上主机名和ip地址
instance:
instance-id: payment8001
#访问路径可以显示IP地址
prefer-ip-address: true
7.1.8、服务发现 Discovery
1、修改服务 提供者的Controller
/*
* 服务发现
* */
@Resource
private DiscoveryClient discoveryClient;
/*
* 输出服务提供者的详细信息
* */
@RequestMapping("/payment/discovery")
public Object discovery(){
/*查询出所有的服务,包括服务提供者和消费者*/
List<String> services = discoveryClient.getServices();
for (String el : services){
log.info("*****element:"+el);
}
/*查询出指定服务的实例,也就是集群的小服务*/
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances){
log.info("*****instence"+instance);
}
return discoveryClient;
}
2、修改8001的启动类
添加一个注解
/*服务发现*/
@EnableDiscoveryClient
3、8002同理
4、测试
7.1.9、Eureka的自我保护机制
某时刻微服务不能用了,Eureka不会立刻清理,依旧会对微服务进行信息保存。
怎么禁止自我保护模式
在Eureka服务端7001
默认是true,开启的自我保护机制。
#server:
#关闭自我保护机制,保证不可用服务被及时踢除
#enable-self-preservation: false
#eviction-interval-timer-in-ms: 2000
在Eureka客户端8001
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
#lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
#lease-expiration-duration-in-seconds: 2
7.2、zookeeper注册中心
7.2.1、docker安装
[root@cbbgs ~]# docker pull zookeeper #拉取Zookeeper的docker镜像
Using default tag: latest
latest: Pulling from library/zookeeper
a076a628af6f: Already exists
943d8acaac04: Pull complete
b9998d19c116: Pull complete
6f3bacbd30c2: Pull complete
5bb61178ce00: Pull complete
b2cff6714614: Pull complete
fb3ca86b025f: Pull complete
c4f5b9c9ba49: Pull complete
Digest: sha256:74747e50e76474074b997525046869bbe194ed73f719d880ceac71f4a2e8a2e6
Status: Downloaded newer image for zookeeper:latest
docker.io/library/zookeeper:latest
[root@cbbgs ~]# docker images #查看docker镜像
REPOSITORY TAG IMAGE ID CREATED SIZE
homemall-h latest 8addc602c1ff 3 days ago 681MB
registry.cn-hangzhou.aliyuncs.com/cbbgs/homemall-h latest 8addc602c1ff 3 days ago 681MB
homemall latest c3dd8526a983 3 days ago 724MB
registry.cn-hangzhou.aliyuncs.com/cbbgs/homemall latest c3dd8526a983 3 days ago 724MB
redis latest 621ceef7494a 7 days ago 104MB
zookeeper latest 28ffb774bc32 7 days ago 253MB
mysql latest d4c3cafb11d5 8 days ago 545MB
java 8 d23bdf5b1b1b 4 years ago 643MB
[root@cbbgs ~]# docker run -d -p 2181:2181 zookeeper #运行zookeeper容器
3d6121d4f2c11891cda5e4bd619e99d1ce6a9b527f8c7527aa3c162914efc5be
[root@cbbgs ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3d6121d4f2c1 zookeeper "/docker-entrypoint.…" 5 seconds ago Up 3 seconds 2888/tcp, 3888/tcp, 0.0.0.0:2181->2181/tcp, 8080/tcp dazzling_leakey
8137393ac557 redis "docker-entrypoint.s…" 2 days ago Up 2 days 0.0.0.0:6379->6379/tcp silly_hoover
dca567a50338 homemall-h "java -jar /app.jar …" 3 days ago Up 3 days 0.0.0.0:8088->8088/tcp objective_kalam
81cda9cbf07b homemall "java -jar /app.jar …" 3 days ago Up 3 days 0.0.0.0:8084->80/tcp trusting_jennings
eb893c71f0c3 mysql "docker-entrypoint.s…" 4 days ago Up 4 days 0.0.0.0:3306->3306/tcp, 33060/tcp mysql8
[root@cbbgs ~]# docker exec -it 3d6121d4f2c1 /bin/bash # 进入zookeeper
root@3d6121d4f2c1:/apache-zookeeper-3.6.2-bin# cd bin
root@3d6121d4f2c1:/apache-zookeeper-3.6.2-bin/bin# ls
README.txt zkEnv.cmd zkServer.sh zkTxnLogToolkit.sh
zkCleanup.sh zkEnv.sh zkSnapShotToolkit.cmd
zkCli.cmd zkServer-initialize.sh zkSnapShotToolkit.sh
zkCli.sh zkServer.cmd zkTxnLogToolkit.cmd
root@3d6121d4f2c1:/apache-zookeeper-3.6.2-bin/bin# ./zkCli.sh #运行Zookeeper
Connecting to localhost:2181
可以idea远程连接zookeeper
7.2.2、springcloud集成zookeeper
1、新建一个项目 cloud-privider-payment8003
依赖
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.example</groupId>
<artifactId>cloud_api_common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
配置文件
#8003表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 8003
#服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 47.93.49.117:2181
编写controller
@RestController
@Slf4j
public class PaymentController
{
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/payment/zk")
public String paymentzk()
{
return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
}
}
在启动类上加上服务发现的注解
@SpringBootApplication
//该注解用于向使用consul或者zookeeper作为注册中心时注册服务
@EnableDiscoveryClient
public class Cloud_Privider_Payment8003
{
public static void main(String[] args) {
SpringApplication.run(Cloud_Privider_Payment8003.class, args);
}
}
测试
就可以看到这些
7.2.3、消费者注册进Zookeeper
和Eureka的差不多,最多配置文件不一样,其他的都一样。
1、创建cloud_consumer_orderzk80
2、导入依赖
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、配置文件
server:
port: 80
spring:
application:
name: cloud-consumer-order
cloud:
#注册到zookeeper地址
zookeeper:
connect-string: 47.93.49.117:2181
4、编写启动类加上发现服务的注解
@SpringBootApplication
@EnableDiscoveryClient
public class Cloud_Consumer_Orderzk80 {
public static void main(String[] args) {
SpringApplication.run(Cloud_Consumer_Orderzk80.class,args);
}
}
5、编写ResTemplate配置类
@Configuration
public class Application {
@Bean
@LoadBalanced
public RestTemplate getResTeplate(){
return new RestTemplate();
}
}
6、业务类的编写(只写controller演示)
@RestController
public class PaymentController {
public static final String URL="http://cloud-provider-payment";
@Resource
public RestTemplate restTemplate;
@RequestMapping("/consumer/payment/zk")
public Object zk(){
String forObject = restTemplate.getForObject(URL + "/payment/zk/", String.class);
return forObject;
}
}
7、测试
依次启动8003和zk80
zookeeper多了一个我们刚刚自注册的微服务
访问服务提供者和消费者都没问题
7.3、Consul注册中心
consul是google开源的一个使用go语言开发的服务发现、配置管理中心服务。内置了服务注册与发现框 架、分布一致性协议实现、健康检查、Key/Value存储、多数据中心方案,不再需要依赖其他工具(比如ZooKeeper等)。服务部署简单,只有一个可运行的二进制的包。每个节点都需要运行agent,他有两种运行模式server和client。每个数据中心官方建议需要3或5个server节点以保证数据安全,同时保证server-leader的选举能够正确的进行。
7.3.1、Docker安装Consul
[root@cbbgs ~]# docker pull consul #拉取Consul镜像
Using default tag: latest
latest: Pulling from library/consul
801bfaa63ef2: Pull complete
365f82ba1261: Pull complete
9e4e74a5b5f7: Pull complete
45ef039d7e5d: Pull complete
10a29f03702b: Pull complete
a0cc116ba6cd: Pull complete
Digest: sha256:ee446bdd2e35a9cac8fcfe77fff7dc6f97045eb0cafb8eb283dce7832fc8a45e
Status: Downloaded newer image for consul:latest
docker.io/library/consul:latest
[root@cbbgs ~]# docker images #查看拉取的镜像
REPOSITORY TAG IMAGE ID CREATED SIZE
registry.cn-hangzhou.aliyuncs.com/cbbgs/homemall-h latest 8addc602c1ff 3 days ago 681MB
homemall-h latest 8addc602c1ff 3 days ago 681MB
homemall latest c3dd8526a983 4 days ago 724MB
registry.cn-hangzhou.aliyuncs.com/cbbgs/homemall latest c3dd8526a983 4 days ago 724MB
redis latest 621ceef7494a 8 days ago 104MB
zookeeper latest 28ffb774bc32 8 days ago 253MB
mysql latest d4c3cafb11d5 9 days ago 545MB
consul latest 2823bc69f80f 5 weeks ago 120MB
java 8 d23bdf5b1b1b 4 years ago 643MB
[root@cbbgs ~]# docker run -d -p 8500:8500 consul #运行consul容器
01b50d91518c24469ee3462b4b979ede2db5ed1485b9a9d7deb8f512d3515339
[root@cbbgs ~]# docker ps #查看容器
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
01b50d91518c consul "docker-entrypoint.s…" 5 seconds ago Up 4 seconds 8300-8302/tcp, 8301-8302/udp, 8600/tcp, 8600/udp, 0.0.0.0:8500->8500/tcp sad_swanson
3d6121d4f2c1 zookeeper "/docker-entrypoint.…" 5 hours ago Up 5 hours 2888/tcp, 3888/tcp, 0.0.0.0:2181->2181/tcp, 8080/tcp dazzling_leakey
8137393ac557 redis "docker-entrypoint.s…" 2 days ago Up 2 days 0.0.0.0:6379->6379/tcp silly_hoover
dca567a50338 homemall-h "java -jar /app.jar …" 3 days ago Up 3 days 0.0.0.0:8088->8088/tcp objective_kalam
81cda9cbf07b homemall "java -jar /app.jar …" 4 days ago Up 4 days 0.0.0.0:8084->80/tcp trusting_jennings
eb893c71f0c3 mysql "docker-entrypoint.s…" 4 days ago Up 4 days 0.0.0.0:3306->3306/tcp, 33060/tcp mysql8
7.3.2、服务提供者注册进consul
还是一样的,我们模仿前面的服务提供者进行,就是配置文件和依赖不一样
1、创建服务提供者cloud_providerconsul_payment8006
2、导入依赖
<dependencies>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
3、书写配置文件
###consul服务端口号
server:
port: 8006
spring:
application:
name: consul-provider-payment
####consul注册中心地址
cloud:
consul:
host: 47.93.49.117
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
heartbeat:
enabled: true
4、书写启动类
@SpringBootApplication
@EnableDiscoveryClient
public class Cloud_prividerconsul_payment8006 {
public static void main(String[] args) {
SpringApplication.run(Cloud_prividerconsul_payment8006.class,args);
}
}
5、书写业务类(controller)
@RestController
@Slf4j
public class PaymentController
{
@Value("${server.port}")
private String serverPort;
@RequestMapping(value = "/payment/consul")
public String paymentConsul()
{
return "springcloud with consul: "+serverPort+"\t "+ UUID.randomUUID().toString();
}
}
6、测试
7.3.3、消费者注册进Consul
1、创建cloud_consumerconsul_order80
2、导入依赖
<dependencies>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、书写配置文件
###consul服务端口号
server:
port: 80
spring:
application:
name: cloud-consumer-order
####consul注册中心地址
cloud:
consul:
host: 47.93.49.117
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
heartbeat:
enabled: true
4、启动类
@SpringBootApplication
@EnableDiscoveryClient //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class Cloud_Consumerconsul_Order90
{
public static void main(String[] args) {
SpringApplication.run(Cloud_Consumerconsul_Order90.class, args);
}
}
5、配置类
@Configuration
public class ApplicationContextConfig
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
6、业务类(controller)
@RestController
@Slf4j
public class OrderConsulController
{
public static final String INVOKE_URL = "http://consul-provider-payment";
@Resource
private RestTemplate restTemplate;
@GetMapping(value = "/consumer/payment/consul")
public String paymentInfo()
{
String result = restTemplate.getForObject(INVOKE_URL+"/payment/consul",String.class);
return result;
}
}
7、测试
刚开始测试的时候报错,No instances available for consul-provider-payment,还有consul面板爆红
成这样
这是因为没有开启心跳
开启之后就正常了
7.4、三个注册中心的异同点
详细文章:www.cnblogs.com/keiyoumi520…
CAP原理
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
8、Ribbon 负载均衡服务调用(进入维护模式)
负载均衡
英文名称为Load Balance,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行,例如FTP服务器、Web服务器、企业核心应用服务器和其它主要任务服务器等,从而协同完成工作任务。
负载均衡主要分为软件负载和硬件负载,在微服务盛行的现在,软件负载在微服务里成为主流,netflix的ribbon就是其中之一
Ribbon是一个基于HTTP和TCP的客户端负载均衡器,当我们将Ribbon和Eureka一起使用时,Ribbon会到Eureka注册中心去获取服务端列表,然后进行轮询访问以到达负载均衡的作用,客户端负载均衡也需要心跳机制去维护服务端清单的有效性,当然这个过程需要配合服务注册中心一起完成。
进程内负载均衡:Ribbon
集中式负载均衡:Nginx
Ribbon负载均衡和Rest调用
Ribbon 在工作时分成两步:
- 第一步先选择 EurekaServer,它优先选择在同一个区域内负载较少的server
- 第二步再根据用户指定的策略,在从server 取到的服务注册列表中选择一个地址
新版eureka引入了ribbon,所以不用自己引入也可以使用负载均衡
RestTemplate使用:
官网:docs.spring.io/spring-fram…
getForObject 方法 / getForEntity方法
Ribbon默认自带的负载规则IRule
-
com.netflix.loadbalancer.RoundRobinRule 轮询
-
com.netflix.loadbalancer.RandomRule 随机
-
com.netflix.loadbalancer.RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务
-
WeightResponseTimRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
-
BestAvaliableRule 会像过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
-
AvaliabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例
-
ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能喝server的可用性选择服务器
-
策略类 命名 说明 RandomRule 随机策略 随机选择 Server RoundRobinRule 轮训策略 按顺序循环选择 Server RetryRule 重试策略 在一个配置时问段内当选择 Server 不成功,则一直尝试选择一个可用的 Server BestAvailableRule 最低并发策略 逐个考察 Server,如果 Server 断路器打开,则忽略,再选择其中并发连接最低的 Server AvailabilityFilteringRule 可用过滤策略 过滤掉一直连接失败并被标记为 circuit tripped的 Server,过滤掉那些高并发连接的 Server(active connections 超过配置的网值)ResponseTimeWeightedRule 响应时间加权策略 根据 Server 的响应时间分配权重。响应时间越长,权重越低,被选择到的概率就越低;响应时间越短,权重越高,被选择到的概率就越高。这个策略很贴切,综合了各种因素,如:网络、磁盘、IO等,这些因素直接影响着响应时间 ZoneAvoidanceRule 区域权衡策略 综合判断 Server 所在区域的性能和 Server 的可用性轮询选择 Server,并且判定一个 AWS Zone 的运行性能是否可用,剔除不可用的 Zone 中的所有 Server
替换默认的规则轮询,改为随机
1、特别提示,替换的规则不能和主启动类放在一个包下面,应该分开放
@Configuration
public class Myrule {
@Bean
public IRule myRule()
{
return new RandomRule();//定义为随机
}
}
2、在主启动类添加注解@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration= Myrule.class)
9、Openfeign服务的调用
Openfeign是什么
Openfign服务调用
调用总览
1、用Openfign代替以前的Ribbon+RestTeplate的消费者服务,创建项目cloud_consumerfeign_order80
2、依赖
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.example</groupId>
<artifactId>cloud_api_common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、配置文件
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
4、主启动类,.加上注解@EnableFeignClints
@SpringBootApplication
@EnableFeignClients //集成有负载均衡
public class Cloud_ConsumerFeign_order80 {
public static void main(String[] args) {
SpringApplication.run(Cloud_ConsumerFeign_order80.class,args);
}
}
5、创建OpenFignService,用上注解@FeignClint(value="微服务名字")
@Component
@FeignClient(value = "cloud-payment-service")
public interface OpenFeignServiece {
/*opfeign调用的是服务提供者*/
@RequestMapping(value = "/consumer/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id);
}
6、业务类调用创建的OpenFignService
@RestController
public class PaymentOPemFeignController {
@Resource private OpenFeignServiece openFeignServiece;
/*Controller调用的是自己创建的OpenFeignServiece*/
@RequestMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id) {
return openFeignServiece.getPaymentById(id);
}
}
7、测试
OpenFign的超时控制
在服务调用端配置yml,主要就是配置ribbon的链接超时时间和读取超时时间,单位为毫秒
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
日志增强
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
logging:
level:
# feign日志以什么级别监控哪个接口
com.atguigu.springcloud.service.PaymentFeignService: debug
10、Hystrix断路器(停更)
分布式系统面临的问题 复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。
服务雪崩
- 多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
- 对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
Hystrix是什么?
- Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
- “断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
Hystrix能干什么
服务降级、服务熔断、服务限流、接近实时的监控。。。。 官网资料 github.com/Netflix/Hys…
服务降级
概念:服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此释放服务器资源以保证核心任务的正常运行。(温馨提示,兜底的操作)
- 服务接口拒绝服务:页面能访问,但是添加删除提示服务器繁忙。页面内容也可在Varnish或CDN内获取。
- 页面拒绝服务:页面提示由于服务繁忙此服务暂停。跳转到varnish或nginx的一个静态页面。
- 延迟持久化:页面访问照常,但是涉及记录变更,会提示稍晚能看到结果,将数据记录到异步队列或log,服务恢复后执行。
- 随机拒绝服务:服务接口随机拒绝服务,让用户重试,目前较少有人采用。因为用户体验不佳。
那些情况会发生降级
程序运行异常、超时、服务熔断触发服务降级、线程池/信号量打满也会导致服务降级
服务熔断
如果某个目标服务调用慢或者有大量超时,此时,熔断该服务的调用,对于后续调用请求,不在继续调用目标服务,直接返回,快速释放资源。如果目标服务情况好转则恢复调用。(直接拉闸)
服务限流
限流模式主要是提前对各个类型的请求设置最高的QPS阈值,若高于设置的阈值则对该请求直接返回,不再调用后续资源。秒杀操作,有序排队。
Hystrix服务提供者构建
1、构建新的服务提供者cloud_provider_hystrix_payment8001
2、依赖
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--<dependency><!– 引入自己定义的api通用包,可以使用Payment支付Entity –>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencies>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--<dependency><!– 引入自己定义的api通用包,可以使用Payment支付Entity –>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、配置文件 ,我们用单机的Eureka测试,不用集群的测试
server:
port: 8001
spring:
application:
name: cloud-provider-hystrix-payment
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
4、配置启动类
@SpringBootApplication
@EnableEurekaClient
public class Cloud_Provider_Hystrix_Payment8001 {
public static void main(String[] args) {
SpringApplication.run(Cloud_Provider_Hystrix_Payment8001.class,args);
}
}
5、service
@Service
public class PaymentService {
/*模拟正常情况*/
public String paymentInfo_OK(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}
/*模拟异常情况*/
public String paymentInfo_TimeOut(Integer id)
{
//int age = 10/0;
try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): ";
}
}
6、controller
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@RequestMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_OK(id);
log.info("*****result: "+result);
return result;
}
@RequestMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = paymentService.paymentInfo_TimeOut(id);
log.info("*****result: "+result);
return result;
}
}
7、测试
我们以上面的项目来模拟熔断降级!!!
开启两万个线程来访问8001,同时两万个请求来访问延迟服务,会正常的那个服务造成卡顿,那么整个服务都陷入卡顿。(转圈圈)
这样8001自测都通过不了。我们继续恶化情况,加入服务消费者。
Hystrix服务消费者构建
1、创建一个服务的消费者cloud_feign_hystrix_order80
2、依赖
<dependencies>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!--eureka client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- <!– 引入自己定义的api通用包,可以使用Payment支付Entity –>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>-->
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3、配置文件
server:
port: 80
spring:
application:
name: cloud-consumer-feign-hystrix-order
eureka:
client:
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
4、启动类
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80
{
public static void main(String[] args)
{
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
5、service
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService
{
@RequestMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@RequestMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
6、controller
@RestController
@Slf4j
public class OrderHystirxController
{
@Resource
private PaymentHystrixService paymentHystrixService;
@RequestMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id)
{
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@RequestMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
}
7、测试
我们两万个多线程扑向8001造成这种结果
怎么解决
服务降级服务提供侧fallback
超时
1、8001在service上添加 @HystrixCommand和兜底的方法
我们把加载正常的时间设成3秒,但是我们把 paymentInfo_TimeOut方法故意设成延迟五秒加载 那么paymentInfo_TimeOut方法一定出错,我们就一定会执行我们兜底的方法paymentInfo_TimeOutHandler 我们测试看是不是执行的兜底的方法,如果是则说明服务降级成功
@Service
public class PaymentService {
/*模拟正常情况*/
public String paymentInfo_OK(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"O(∩_∩)O哈哈~";
}
/*模拟异常情况*/
/*三秒以内走正常情况,我们让他失败*/
/*
* 我们把加载正常的时间设成3秒,但是我们把 paymentInfo_TimeOut方法故意设成延迟五秒加载
* 那么paymentInfo_TimeOut方法一定出错,我们就一定会执行我们兜底的方法paymentInfo_TimeOutHandler
* 我们测试看是不是执行的兜底的方法,如果是则说明服务降级成功
* */
@HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="3000")
})
public String paymentInfo_TimeOut(Integer id)
{
//int age = 10/0;
try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
return "线程池: "+Thread.currentThread().getName()+" id: "+id+"\t"+"O(∩_∩)O哈哈~"+" 耗时(秒): ";
}
/*,上面的方法出事了兜底的方法*/
public String paymentInfo_TimeOutHandler(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" 8001系统繁忙或者运行报错,请稍后再试,id: "+id+"\t"+"o(╥﹏╥)o";
}
}
2、主启动类修改,加注解**@EnableCircuitBreaker** //回路
3、测试
结果运行的就是我们兜底的 方法
我们再来测试一下,在service的paymentInfo_TimeOut方法里面加一个除0错误,注释掉延迟的,看会不会报错
结果显而易见,只要程序出现问题就会服务降级,执行我们的兜底方法。
服务降级服务消费者fallback
80微服务消费者也可以更好的保护自己,比如服务生产者加载的时间是三秒,但是消费者端需要两秒就加载出来,我们也可以对客户端进行降级保护。
1、配置文件修改
#加上
feign:
hystrix:
enabled: true
2、启动类加上注解@EnableHystrix
3、controller一样的修改
@RestController
@Slf4j
public class OrderHystirxController
{
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")
})
//@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
{
// int age = 10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
{
return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
}
}
3、测试
我们让服务提供者健康,让消费者延迟
全局服务降级DefaultProperties
在降级的页面加上注解
1、类上@DefaultProperties(defaultFallback = "paymentTimeOutFallbackMethodAll")
方法上@HystrixCommand
有一个全局的兜底方法
通配降级服务FeignFallback
我们的全局fallback和业务逻辑混在一起,不方便解耦,我们只需要为feigin客户端定义的接口添加一个服务降级处理的实现类即可实现解耦 。
@Component
public class PaymentFallbackService implements PaymentHystrixService
{
@Override
public String paymentInfo_OK(Integer id)
{
return "-----PaymentFallbackService fall back-paymentInfo_OK ,o(╥﹏╥)o";
}
@Override
public String paymentInfo_TimeOut(Integer id)
{
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut ,o(╥﹏╥)o";
}
}
修改接口PaymentHystrixService即可
@Component
//fallback实现类
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService
{
@RequestMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@RequestMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
Hystrix之服务器熔断
修改8001的service
添加一个方法
//=====服务熔断================================================
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
if(id < 0)
{
throw new RuntimeException("******id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id)
{
return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~ id: " +id;
}
修改controller
//====服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id)
{
String result = paymentService.paymentCircuitBreaker(id);
log.info("****result: "+result);
return result;
}
测试输入http://localhost:8001/payment/circuit/1为正确
http://localhost:8001/payment/circuit/-1为错误,当多次错误,在输入做正确的请求还是会错,只有等正确访问慢慢上升,才会正确。
1.熔断关闭状态(Closed)
服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。
2.熔断开启状态(Open)
在固定时间内(Hystrix默认是10秒),接口调用出错比率达到一个阈值(Hystrix默认为50%),会进入熔断开 启状态。
进入熔断状态后, 后续对该服务接口的调用不再经过网络,直接****执行本地的fallback方法****。
3.半熔断状态(Half-Open)
在进入熔断开启状态一段时间之后(Hystrix默认是5秒),熔断器会进入半熔断状态。
所谓半熔断就是尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。
如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断开启 状态。
断路器打开之后
到了后面我们用sentienil代替hystrix!