Spring Cloud(2020版) 学习笔记(一)
- 1.Boot和Cloud版本选型
- 2.Cloud组件停更说明
- 3.父工程Project空间新建
- 4.支付模块构建
- 5.消费模块构建
- 6. EurekaServer集群服务端
- 7.多个支付服务集群注册进Eureka集群
- 8.订单服务注册eureka
- 9.服务发现Discovery
- 10. Eureka自我保护理论知识
- 11.怎么禁止自我保护
- 12. 支付服务注册进zookeeper
- 13.订单服务注册zookeeper
- 14. Consul简介
- 15. 安装并运行Consul
- 16 .服务提供者注册进Consul
- 17. 订单服务注册consul
- 18.三个注册中心异同点
- 19.Ribbon入门介绍
- 20. Ribbon的负载均衡和Rest调用
- 21. Ribbon默认自带的负载规则
- 22.Ribbon负载规则替换
- 23.Ribbon默认负载轮询算法原理
- 24.RoundRobinRule源码分析
- 25.Ribbon之手写轮询算法
- 26.OpenFeign是什么
- 27.OpenFeign服务调用
- 28. OpenFeign超时控制
- 29.OpenFeign日志增强
1.Boot和Cloud版本选型
-
Spring Boot 2.X 版
- 源码地址
- Spring Boot 2 的新特性
- 通过上面官网发现,Boot官方强烈建议你升级到2.X以上版本
-
Spring Cloud H版
-
Spring Boot 与 Spring Cloud 兼容性查看
-
接下来开发用到的组件版本
- Cloud - Hoxton.SR1
- Boot - 2.2.2.RELEASE
- Cloud Alibaba - 2.1.0.RELEASE
- Java - Java 8
- Maven - 3.5及以上
- MySQL - 5.7及以上
2.Cloud组件停更说明
-
停更引发的“升级惨案”
- 停更不停用
- 被动修复bugs
- 不再接受合并请求
- 不再发布新版本
-
Cloud升级
-
服务注册中心
- × Eureka
- √ Zookeeper
- √ Consul
- √ Nacos
-
服务调用
- √ Ribbon
- √ LoadBalancer
-
服务调用2
- × Feign
- √ OpenFeign
-
服务降级
- × Hystrix
- √ resilience4j
- √ sentienl
-
服务网关
- × Zuul
- ! Zuul2
- √ gateway
-
服务配置
- × Config
- √ Nacos
-
服务总线
- × Bus
- √ Nacos
-
-
Eureka停用,可以使用zk作为服务注册中心
-
服务调用,Ribbon准备停更,代替为LoadBalance
-
Feign改为OpenFeign
-
Hystrix停更,改为resilence4j,或者阿里巴巴的sentienl
-
Zuul改为gateway
-
服务配置Config改为 Nacos
-
服务总线Bus改为Nacos
3.父工程Project空间新建
创建微服务cloud整体聚合父工程Project,有8个关键步骤:
-
New Project - maven工程 - create from archetype: maven-archetype-site
-
聚合总父工程名字
-
Maven选版本
-
工程名字
-
字符编码 - Settings - File encoding
-
注解生效激活 - Settings - Annotation Processors
-
Java编译版本选8
-
File Type过滤 - Settings - File Type,可根据个人习惯设置
- 父工程pom配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>cloud-provider-payment</module>
<module>cloud-consumer-order</module>
<module>cloud-api-commons</module>
</modules>
<!-- 统一管理 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>5.1.47</mysql.version>
<druid.version>1.1.16</druid.version>
<mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
</properties>
<!-- 子块基础之后,提供作用:锁定版本 + 子module不用写 groupId 和 version -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</dependency>
<!-- 下面三个基本是微服务架构的标配 -->
<!--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 阿里巴巴-->
<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>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!-- druid-->
<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>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<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>
</project>
4.支付模块构建
创建微服务模块:
- 建Module
- 改POM
- 写YML
- 主启动
- 业务类
-
建名为cloud-provider-payment和cloud-api-commons公共Maven工程
-
改POM
cloud-provider-payment的pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment</artifactId>
<dependencies>
<!--包含了sleuth+zipkin-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-zipkin</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.chenyc</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-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
cloud-api-commons公共工程的pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud2020</artifactId>
<groupId>com.chenyc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-api-commons</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<!-- 当别人通过pom依赖Project A的时候,D不会被传递依赖进来-->
<!-- <optional>true</optional>-->
</dependency>
<!-- 这个是新添加的,之前没用到,后面会用到。关于这个hutool 是个功能强大的工具包,官网:https://hutool.cn/ -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
</project>
- 写YML
server:
port: 8001
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.x.xxx:3306/xxxxxx_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: xxxxxx
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
- 主启动
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author chenyc
* @create 2021-03-29 16:01
*/
@SpringBootApplication
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
- 业务类
- SQL:
CREATE TABLE `payment`(
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`serial` varchar(200) DEFAULT '',
PRIMARY KEY (id)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
- Entities:
在公共工程cloud-api-commons创建实体类Payment和CommonResult:
package com.chenyc.springcloud.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author chenyc
* @create 2021-03-29 16:08
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code,String message){
this(code,message,null);
}
}
JSON封装体CommonResult:
package com.chenyc.springcloud;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author chenyc
* @create 2021-03-29 16:08
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code,String message){
this(code,message,null);
}
}
- DAO:
接口PaymentDao:
package com.chenyc.springcloud.dao;
import com.chenyc.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper // 是ibatis下面的注解 //@Repositoty有时候会有问题
public interface PaymentDao {
int create(Payment payment);
Payment getPaymentById(@Param("id") Long id);
}
MyBatis映射文件PaymentMapper.xml,路径:resources/mapper/PaymentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.chenyc.springcloud.dao.PaymentDao">
<resultMap id="BaseResultMap" type="com.chenyc.springcloud.entities.Payment">
<id column="id" property="id" jdbcType="BIGINT"/>
<id column="serial" property="serial" jdbcType="VARCHAR"/>
</resultMap>
<insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
insert into payment(serial) values (#{serial})
</insert>
<select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
select * from payment where id = #{id}
</select>
</mapper>
Service:
接口PaymentService
package com.chenyc.springcloud.service;
import com.chenyc.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;
public interface PaymentService
{
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
实现类
package com.chenyc.springcloud.service.impl;
import com.chenyc.springcloud.dao.PaymentDao;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class PaymentServiceImpl implements PaymentService
{
@Resource
PaymentDao paymentDao;
public int create(Payment payment)
{
return paymentDao.create(payment);
}
public Payment getPaymentById(Long id)
{
return paymentDao.getPaymentById(id);
}
}
Controller:
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@Slf4j
public class PaymentController{
@Resource
private PaymentService paymentService;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort: ",result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort: ",payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
}
- 测试
- Postman - http://localhost:8001/payment/create
- http://localhost:8001/payment/get/1
- 热部署配置devtools,根据自己需要选择性开启
代码改动后希望自动生效
- 添加到具体模块pom文件中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
- 添加plugin到父工程的pom文件中:上面也已经添加好了
<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>
- Enabling automatic build
File -> Settings(New Project Settings->Settings for New Projects) ->Complier
下面项勾选
- Automatically show first error in editor
- Display notification on build completion
- Build project automatically
- Compile independent modules in parallel
- Update the value of
键入Ctrl + Shift + Alt + / ,打开Registry,勾选:
compiler.automake.allow.when.app.running
actionSystem.assertFocusAccessFromEdt
重启IDEA,热部署只允许在开发阶段使用
5.消费模块构建
- 建Module
创建名为cloud-consumer-order的maven工程。
- 改POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order</artifactId>
<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-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.chenyc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
- 写YML
server:
port: 80
- 启动类
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author chenyc
* @create 2021-03-29 19:55
*/
@SpringBootApplication
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain.class, args);
}
}
- 业务代码
controller
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
/**
* @author chenyc
* @create 2021-03-30 10:34
*/
@RestController
@RequestMapping("/consumer")
public class OrderController {
//远程调用的 地址
public static final String PAYMENY_URL = "http://localhost:8001";
@Autowired
private RestTemplate restTemplate;
@PostMapping("/payment/create")
public CommonResult<Payment> create (@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENY_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENY_URL + "/payment/get/" + id,//请求地址
CommonResult.class);//返回类型
}
}
RestTemplate:
package com.chenyc.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author chenyc
* @create 2021-03-30 10:33
*/
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
- 测试
6. EurekaServer集群服务端
- 找到C:\Windows\System32\drivers\etc路径下的hosts文件,修改映射配置添加进hosts文件
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
1.单击Windows键,选择运行,输入CMD,回车。
2.在命令提示符中输入"ipconfig /displaydns"命令回车即可。
3.再次输入"ipconfig /flushdns"命令回车即可刷新dns,使hosts文件立即生效。
4. 勾选允许并行
- 创建名为cloud-eureka-server的Maven工程
<!-- eureka新旧版本 -->
<!-- 以前的老版本(2018)-->
<dependency>
<groupid>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- 现在新版本(2020.2)--><!-- 我们使用最新的 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 修改pom.xml、
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring-cloud2020</artifactId>
<groupId>com.chenyc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server</artifactId>
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.chenyc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</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-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
- 修改application.yml,单机版eureka将defaultZone配置成自己,启动一个即可
spring:
profiles:
active: eureka7002
---
server:
port: 7001
spring:
profiles: eureka7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 单机版本
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群版本,机器配置不行,只启动2台eureka,组成集群
defaultZone: http://eureka7002.com:7002/eureka/
---
server:
port: 7002
spring:
profiles: eureka7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 单机版本
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群版本,机器配置不行,只启动2台eureka,组成集群
defaultZone: http://eureka7001.com:7001/eureka/
- 主启动
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author chenyc
* @create 2021-03-30 13:20
*/
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain {
public static void main(String[] args) {
SpringApplication.run(EurekaMain.class,args);
}
}
- 测试
修改端口号启动两个eureka,组成集群,机器配置有限只启动两个,配好的可以启动3个或者更多
spring:
profiles:
active: eureka7001
spring:
profiles:
active: eureka7002
7.多个支付服务集群注册进Eureka集群
- 改POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment</artifactId>
<dependencies>
<!--包含了sleuth+zipkin-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-zipkin</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.chenyc</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-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
- 修改YML
spring:
profiles:
active: payment8002
---
server:
port: 8001
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: 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
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
server:
port: 8002
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8002
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
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
- 修改启动类
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 16:01
*/
@SpringBootApplication
@EnableEurekaClient
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
- 修改controller增加serverPort
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@Slf4j
public class PaymentController{
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;//添加serverPort
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort: "+serverPort,result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
}
- 启动两个端口号分别为8001和8002的服务,查看eureka
spring:
profiles:
active: payment8001
spring:
profiles:
active: payment8002
8.订单服务注册eureka
- 修改pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order</artifactId>
<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-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.chenyc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
- 修改yaml
server:
port: 80
spring:
application:
name: cloud-consumer-order
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
- 修改启动类
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 19:55
*/
@SpringBootApplication
@EnableEurekaClient//<--- 添加该标签
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain.class, args);
}
}
- 修改controller的PAYMENY_URL,变为支付服务的注册服务名,实现负载均衡
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
/**
* @author chenyc
* @create 2021-03-30 10:34
*/
@RestController
@RequestMapping("/consumer")
public class OrderController {
//远程调用的 地址
// public static final String PAYMENY_URL = "http://localhost:8001";
public static final String PAYMENY_URL = "http://CLOUD-PROVIDER-PAYMENT";
@Autowired
private RestTemplate restTemplate;
@PostMapping("/payment/create")
public CommonResult<Payment> create (@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENY_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENY_URL + "/payment/get/" + id,//请求地址
CommonResult.class);//返回类型
}
}
- 修改RestTemplate
package com.chenyc.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author chenyc
* @create 2021-03-30 10:33
*/
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced//使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
- 启动消费者服务,查看euraka
- postman测试,第一次请求到8001服务,第二次请求到8002,以此类推循环,实现了负载均衡
- actuator微服务信息完善
修改cloud-provider-payment的yml文件添加以下信息
- instance-id 服务名称修改(也就是将IP地址,换成可读性高的名字)
- prefer-ip-address可显示的IP地址
eureka:
...
instance:
instance-id: payment8001 #添加此处
prefer-ip-address: true #添加此处
eureka:
...
instance:
instance-id: payment8002 #添加此处
prefer-ip-address: true #添加此处
完整信息
spring:
profiles:
active: payment8002
---
server:
port: 8001
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: 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 #添加此处
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
server:
port: 8002
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8002
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: payment8002 #添加此处
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
9.服务发现Discovery
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
- 修改cloud-provider-payment的Controller
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@Slf4j
public class PaymentController{
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;//添加serverPort
@Autowired
DiscoveryClient discoveryClient;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort: "+serverPort,result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("*****element: "+element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
}
- 修改主启动类
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 16:01
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient//添加该注解
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
- 测试,启动eureka和cloud-provider-payment
请求:http://localhost:8001/payment/discovery
10. Eureka自我保护理论知识
- 概述
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护。一旦进入保护模式,Eureka Server将会尝试保护其服务注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务。
如果在Eureka Server的首页看到以下这段提示,则说明Eureka进入了保护模式:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THANTHRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUSTTO BE SAFE
- 导致原因
一句话:某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存。
属于CAP里面的AP分支。
- 为什么会产生Eureka自我保护机制?
为了EurekaClient可以正常运行,防止与EurekaServer网络不通情况下,EurekaServer不会立刻将EurekaClient服务剔除
- 什么是自我保护模式?
默认情况下,如果EurekaServer在一定时间内没有接收到某个微服务实例的心跳,EurekaServer将会注销该实例(默认90秒)。但是当网络分区故障发生(延时、卡顿、拥挤)时,微服务与EurekaServer之间无法正常通信,以上行为可能变得非常危险了——因为微服务本身其实是健康的,此时本不应该注销这个微服务。Eureka通过“自我保护模式”来解决这个问题——当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。
自我保护机制∶默认情况下EurekaClient定时向EurekaServer端发送心跳包,如果Eureka在server端在一定时间内(默认90秒)没有收到EurekaClient发送心跳包,便会直接从服务注册列表中剔除该服务,但是在短时间( 90秒中)内丢失了大量的服务实例心跳,这时候Eurekaserver会开启自我保护机制,不会剔除该服务(该现象可能出现在如果网络不通但是EurekaClient为出现宕机,此时如果换做别的注册中心如果一定时间内没有收到心跳会将剔除该服务,这样就出现了严重失误,因为客户端还能正常发送心跳,只是网络延迟问题,而保护机制是为了解决此问题而产生的)。
在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。
它的设计哲学就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。一句话讲解:好死不如赖活着。
综上,自我保护模式是一种应对网络异常的安全保护措施。它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何健康的微服务。使用自我保护模式,可以让Eureka集群更加的健壮、稳定。
11.怎么禁止自我保护
-
在eurekaServer关闭自我保护,默认自我保护机制是开启的
-
先要关闭自我保护 enable-self-preservation: false
-
eviction-interval-timer-in-ms 启用主动失效,并且每次主动失效检测间隔为3s
-
spring:
profiles:
active: eureka7002
---
server:
port: 7001
spring:
profiles: eureka7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 单机版本
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群版本,机器配置不行,只启动2台eureka,组成集群
defaultZone: http://eureka7002.com:7002/eureka/
server:
enable-self-preservation: false # 设为false,关闭自我保护
#清理间隔(单位毫秒,默认是0,不清理)启用主动失效,并且每次主动失效检测间隔为3s
eviction-interval-timer-in-ms: 3000
---
server:
port: 7002
spring:
profiles: eureka7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 单机版本
# defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
# 集群版本,机器配置不行,只启动2台eureka,组成集群
defaultZone: http://eureka7001.com:7001/eureka/
server:
enable-self-preservation: false # 设为false,关闭自我保护
#清理间隔(单位毫秒,默认是0,不清理)启用主动失效,并且每次主动失效检测间隔为3s
eviction-interval-timer-in-ms: 3000
Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认值为0,默认情况不删除实例)进行检查,如果发现实例在在一定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到心跳,则会注销此实例。
- 客户端修改
eureka.instance.lease-renewal-interval-in-seconds 客户端每隔这个时间会主动心跳一次,默认值为30s,更新自己的状态。Eureka Server收到心跳后,会通知集群里的其它Eureka Server更新此实例的状态。
eureka.instance.lease-expiration-duration-in-seconds 设置服务过期时间配置,超过这个时间没有接收到心跳EurekaServer就会将这个实例剔除,一般是lease-renewal-interval-in-seconds的3倍,默认90s。
Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认值为0,默认情况不删除实例)进行检查,如果发现实例在在一定时间(此值由lease-expiration-duration-in-seconds定义,默认值为90s)内没有收到心跳,则会注销此实例。经过测试设置4s上报一次心跳,12s内无跳就让注册中心剔除服务比较合理
消费端为了能快速响应服务请求会从Eureka Server拉取服务地址清单后缓存到本地(后面会增量获取),fetch-registry要设置为true,定期的更新客户端的服务清单时间 registry-fetch-interval-seconds: 8(默认30s),这是消费端的设置。
修改yaml文件
spring:
profiles:
active: payment8002
---
server:
port: 8001
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8001
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
server:
port: 8002
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8002
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8002 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
- Eureka停更说明
停更说明
12. 支付服务注册进zookeeper
- zookeeper是一个分布式协调工具,可以实现注册中心功能
- 修改pom文件,注释掉euraka相关依赖,加入zookeeper相关依赖,spring-cloud-starter-zookeeper-discovery自带zookeeper3.5.3,我安装的是3.4.9,需要排除依赖,不然会依赖冲突
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment</artifactId>
<dependencies>
<!--eureka-client-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
<!-- </dependency>-->
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.chenyc</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-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3 防止与3.4.9起冲突-->
<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>
</dependencies>
</project>
- 修改启动类
注释掉euraka相关,不然启动会报错
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
//import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 16:01
*/
@SpringBootApplication
//@EnableEurekaClient
@EnableDiscoveryClient//添加该注解
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
- 修改yaml,新增8003端口
spring:
profiles:
active: payment8003
---
server:
port: 8001
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8001
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
server:
port: 8002
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8002
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8002 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
# 8003测试用zookeeper作为注册中心
server:
port: 8003
spring:
profiles: payment8003
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: xxx.xxx.xxx.xxx:2181
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
- 修改controller
增加方法/payment/zk
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
@RestController
@Slf4j
public class PaymentController{
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;//添加serverPort
@Autowired
DiscoveryClient discoveryClient;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort: "+serverPort,result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("*****element: "+element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
@RequestMapping(value = "/payment/zk")
public String paymentzk()
{
return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
}
}
- 查看zookeeper
./zkCli.sh -server xxx.xxx.xxx.xxx:2181
ls /services
get /services/cloud-provider-payment/ac34b7b2-c23a-4c79-97d7-b56f4e3774ae
- 测试
get /services/cloud-provider-payment/ac34b7b2-c23a-4c79-97d7-b56f4e3774ae json格式化
{
"name": "cloud-provider-payment",
"id": "ac34b7b2-c23a-4c79-97d7-b56f4e3774ae",
"address": "windows10.microdone.cn",
"port": 8003,
"sslPort": null,
"payload": {
"@class": "org.springframework.cloud.zookeeper.discovery.ZookeeperInstance",
"id": "application-1",
"name": "cloud-provider-payment",
"metadata": { }
},
"registrationTimeUTC": 1617346045209,
"serviceType": "DYNAMIC",
"uriSpec": {
"parts": [
{
"value": "scheme",
"variable": true
},
{
"value": "://",
"variable": false
},
{
"value": "address",
"variable": true
},
{
"value": ":",
"variable": false
},
{
"value": "port",
"variable": true
}
]
}
}
ZooKeeper的服务节点是临时节点,关闭服务就删除,没有保护机制
13.订单服务注册zookeeper
- 修改pom文件
注释掉euraka相关配置,添加zookeeper相关配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order</artifactId>
<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-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.chenyc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-netflix-eureka-client</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>
</dependencies>
</project>
- 修改启动类
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
//import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 19:55
*/
@SpringBootApplication
//@EnableEurekaClient//<--- Enable服务注册添加该标签
@EnableDiscoveryClient//<--- zookeeper服务注册添加该标签
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain.class, args);
}
}
- 修改yaml文件
spring:
profiles:
active: orderZookeeper
---
#将order服务注册到eureka的配置
server:
port: 80
spring:
application:
name: cloud-consumer-order
profiles: orderEureka
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
---
#将order服务注册到zookeeper的配置
server:
port: 80
spring:
application:
name: cloud-consumer-order
profiles: orderZookeeper
cloud:
zookeeper:
connect-string: xxx.xxx.xxx.xxx:2181
- 修改controller,增加/payment/zk方法
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
/**
* @author chenyc
* @create 2021-03-30 10:34
*/
@RestController
@RequestMapping("/consumer")
public class OrderController {
//远程调用的 地址
// public static final String PAYMENY_URL = "http://localhost:8001";
//euraka远程调用的地址
// public static final String PAYMENY_URL = "http://CLOUD-PROVIDER-PAYMENT";
//zookeeper远程调用的地址
public static final String PAYMENY_URL = "http://cloud-provider-payment";
@Autowired
private RestTemplate restTemplate;
@PostMapping("/payment/create")
public CommonResult<Payment> create (@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENY_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENY_URL + "/payment/get/" + id,//请求地址
CommonResult.class);//返回类型
}
@GetMapping(value = "/payment/zk")
public String paymentInfo()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/zk",String.class);
return result;
}
}
- 启动服务查看zookeeper
[zk: localhost:2181(CONNECTED) 0] ls /
[services, zookeeper]
[zk: localhost:2181(CONNECTED) 1] ls /services
[cloud-consumer-order, cloud-provider-payment]
[zk: localhost:2181(CONNECTED) 2]
- 测试
14. Consul简介
Consul官网
Consul下载地址
Consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp 公司用Go语言开发。
提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服务网格,总之Consul提供了一种完整的服务网格解决方案。
它具有很多优点。包括:基于raft协议,比较简洁;支持健康检查,同时支持HTTP和DNS协议支持跨数据中心的WAN集群提供图形界面跨平台,支持Linux、Mac、Windows
能干嘛?
- 服务发现 - 提供HTTP和DNS两种发现方式。
- 健康监测 - 支持多种方式,HTTP、TCP、Docker、Shell脚本定制化
- KV存储 - Key、Value的存储方式
- 多数据中心 - Consul支持多数据中心
- 可视化Web界面
15. 安装并运行Consul
windows版解压缩后,运行consul.exe,打开cmd
C:\consul_1.9.4_windows_amd64>consul -v
Consul v1.9.4
Revision 10bb6cb3b
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
- 开发模式启动consul agent -dev
C:\consul_1.9.4_windows_amd64>consul agent -dev
浏览器输入 - http://localhost:8500/ - 打开Consul控制页。
16 .服务提供者注册进Consul
- 修改pom,注释euraka和zookeeper相关配置,添加consul配置
spring:
profiles:
active: payment8004
---
# 8001和8002测试用eureka作为注册中心
server:
port: 8001
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8001
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
server:
port: 8002
spring:
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
profiles: payment8002
eureka:
client:
#表示是否将自己注册进Eurekaserver默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
# Eureka Server每8秒拉取服务地址清单后缓存到本地
registry-fetch-interval-seconds: 8
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
instance:
instance-id: payment8002 #添加此处
prefer-ip-address: true #添加此处
#心跳检测与续约时间
#开发时没置小些,保证服务关闭后注册中心能即使剔除服务
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
# 8003测试用zookeeper作为注册中心
server:
port: 8003
spring:
profiles: payment8003
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 172.18.8.112:2181
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
---
# 8004测试用consul作为注册中心
server:
port: 8004
spring:
profiles: payment8004
application:
name: cloud-provider-payment
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver # mysql驱动包
url: jdbc:mysql://172.18.8.117:3306/new_assets_db?useAffectedRows=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=CTT&zeroDateTimeBehavior=convertToNull
username: root
password: 7cz.jXR3mycw
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenyc.springcloud.entities # 所有Entity别名类所在包
- 启动类
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
//import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 16:01
*/
@SpringBootApplication
//@EnableEurekaClient
@EnableDiscoveryClient//添加该注解
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
- controller,增加/payment/consul方法
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
@RestController
@Slf4j
public class PaymentController{
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;//添加serverPort
@Autowired
DiscoveryClient discoveryClient;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort: "+serverPort,result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("*****element: "+element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
@RequestMapping(value = "/payment/zk")
public String paymentzk()
{
return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
}
@RequestMapping(value = "/payment/consul")
public String paymentConsul()
{
return "springcloud with consul: "+serverPort+"\t "+ UUID.randomUUID().toString();
}
}
- 启动测试
17. 订单服务注册consul
- 修改pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order</artifactId>
<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-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.chenyc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-netflix-eureka-client</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>-->
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
</dependencies>
</project>
- 修改主启动类
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
//import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 19:55
*/
@SpringBootApplication
//@EnableEurekaClient//<--- Enable服务注册添加该标签
@EnableDiscoveryClient//<--- zookeeper服务注册添加该标签
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain.class, args);
}
}
- 修改yaml文件
spring:
profiles:
active: orderZookeeper
---
#将order服务注册到eureka的配置
server:
port: 80
spring:
application:
name: cloud-consumer-order
profiles: orderEureka
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
---
#将order服务注册到zookeeper的配置
server:
port: 80
spring:
application:
name: cloud-consumer-order
profiles: orderZookeeper
cloud:
zookeeper:
connect-string: 172.18.8.112:2181
---
#将order服务注册到consul的配置
server:
port: 80
spring:
application:
name: cloud-consumer-order
profiles: orderConsul
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${spring.application.name}
- 修改controller
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
/**
* @author chenyc
* @create 2021-03-30 10:34
*/
@RestController
@RequestMapping("/consumer")
public class OrderController {
//远程调用的 地址
// public static final String PAYMENY_URL = "http://localhost:8001";
//euraka远程调用的地址
// public static final String PAYMENY_URL = "http://CLOUD-PROVIDER-PAYMENT";
//zookeeper和consul远程调用的地址
public static final String PAYMENY_URL = "http://cloud-provider-payment";
@Autowired
private RestTemplate restTemplate;
@PostMapping("/payment/create")
public CommonResult<Payment> create (@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENY_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENY_URL + "/payment/get/" + id,//请求地址
CommonResult.class);//返回类型
}
@GetMapping(value = "/payment/zk")
public String paymentInfo()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/zk",String.class);
return result;
}
@GetMapping(value = "/payment/consul")
public String paymentInfoConsul()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/consul",String.class);
return result;
}
}
- 启动测试
consul控制台显示3个服务,但是列表中没有,应该和我下的最新版本有关系,我下的1.9.4,但是不影响服务调用
18.三个注册中心异同点
CAP:
-
C:Consistency (强一致性)
-
A:Availability (可用性)
-
P:Partition tolerance (分区容错性)
最多只能同时较好的满足两个。
CAP理论的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求。
因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:
- CA - 单点集群,满足—致性,可用性的系统,通常在可扩展性上不太强大。
- CP - 满足一致性,分区容忍必的系统,通常性能不是特别高。
- AP - 满足可用性,分区容忍性的系统,通常可能对一致性要求低一些。
AP架构(Eureka)
当网络分区出现后,为了保证可用性,系统B可以返回旧值,保证系统的可用性。
结论:违背了一致性C的要求,只满足可用性和分区容错,即AP
CP架构(ZooKeeper/Consul)
当网络分区出现后,为了保证一致性,就必须拒接请求,否则无法保证一致性。
结论:违背了可用性A的要求,只满足一致性和分区容错,即CP。
CP 与 AP 对立同一的矛盾关系。
19.Ribbon入门介绍
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。
简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
Github - Ribbon
Ribbon目前也进入维护模式。
Ribbon未来可能被Spring Cloud LoadBalacer替代。
- LB负载均衡(Load Balance)是什么
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)。
常见的负载均衡有软件Nginx,LVS,硬件F5等。
- Ribbon本地负载均衡客户端VS Nginx服务端负载均衡区别
Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的。
Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。
- 集中式LB
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方;
- 进程内LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
一句话
负载均衡 + RestTemplate调用
20. Ribbon的负载均衡和Rest调用
spring-cloud-starter-netflix-eureka-client自带了spring-cloud-starter-ribbon引用。
- 修改pom,注释zookeeper和consul注册中心,恢复euraka
支付服务pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment</artifactId>
<dependencies>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.chenyc</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-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- <!– SpringBoot整合zookeeper客户端 –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>-->
<!-- <!–先排除自带的zookeeper3.5.3 防止与3.4.9起冲突–>-->
<!-- <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.12</version>-->
<!-- </dependency>-->
<!-- <!–SpringCloud consul-server –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-consul-discovery</artifactId>-->
<!-- </dependency>-->
</dependencies>
</project>
订单服务pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order</artifactId>
<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-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.chenyc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>-->
<!-- <!–SpringCloud consul-server –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-consul-discovery</artifactId>-->
<!-- </dependency>-->
</dependencies>
</project>
- 修改启动类
支付服务
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 16:01
*/
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient//添加该注解
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class, args);
}
}
订单服务
package com.chenyc.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author chenyc
* @create 2021-03-29 19:55
*/
@SpringBootApplication
@EnableEurekaClient//<--- Enable服务注册添加该标签
@EnableDiscoveryClient//<--- zookeeper服务注册添加该标签
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain.class, args);
}
}
21. Ribbon默认自带的负载规则
lRule:根据特定算法中从服务列表中选取一个要访问的服务
- RoundRobinRule 轮询
- RandomRule 随机
- RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重
- WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server的可用性选择服务器
22.Ribbon负载规则替换
- 修改cloud-consumer-order
- 注意配置细节
官方文档明确给出了警告:
这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,
否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
- 新建package - com.lun.myrule
- 在com.lun.myrule下新建MySelfRule规则类
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();
}
}
- 主启动类添加@RibbonClient
import com.lun.myrule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
@SpringBootApplication
@EnableEurekaClient
//添加到此处
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE", configuration = MySelfRule.class)
public class OrderMain80
{
public static void main( String[] args ){
SpringApplication.run(OrderMain80.class, args);
}
}
- 修改controller
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
/**
* @author chenyc
* @create 2021-03-30 10:34
*/
@RestController
@RequestMapping("/consumer")
public class OrderController {
//远程调用的 地址
// public static final String PAYMENY_URL = "http://localhost:8001";
//euraka远程调用的地址
public static final String PAYMENY_URL = "http://CLOUD-PROVIDER-PAYMENT";
//zookeeper和consul远程调用的地址
// public static final String PAYMENY_URL = "http://cloud-provider-payment";
@Autowired
private RestTemplate restTemplate;
@PostMapping("/payment/create")
public CommonResult<Payment> create (@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENY_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENY_URL + "/payment/get/" + id,//请求地址
CommonResult.class);//返回类型
}
@GetMapping(value = "/payment/zk")
public String paymentInfo()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/zk",String.class);
return result;
}
@GetMapping(value = "/payment/consul")
public String paymentInfoConsul()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/consul",String.class);
return result;
}
@GetMapping("/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id)
{
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENY_URL+"/payment/get/"+id,CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();//getForObject()
}else{
return new CommonResult<>(444,"操作失败");
}
}
}
- 测试
实现随机访问
23.Ribbon默认负载轮询算法原理
默认负载轮训算法: rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。
List instances = discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”);
如:
List [0] instances = 127.0.0.1:8002
List [1] instances = 127.0.0.1:8001
8001+ 8002组合成为集群,它们共计2台机器,集群总数为2,按照轮询算法原理:
当总请求数为1时:1%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
当总请求数位2时:2%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002
当总请求数位3时:3%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
当总请求数位4时:4%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002
如此类推…
24.RoundRobinRule源码分析
public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
//重点关注这方法
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
package com.netflix.loadbalancer;
import com.netflix.client.config.IClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* The most well known and basic load balancing strategy, i.e. Round Robin Rule.
*
* @author stonse
* @author Nikos Michalakis <nikos@netflix.com>
*
*/
public class RoundRobinRule extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
setLoadBalancer(lb);
}
//重点关注这方法。
public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}
Server server = null;
int count = 0;
while (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
/**
* Inspired by the implementation of {@link AtomicInteger#incrementAndGet()}.
*
* @param modulo The modulo to bound the value of the counter.
* @return The next value.
*/
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;//求余法
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
25.Ribbon之手写轮询算法
- 修改支付服务的controller,增加方法/payment/lb
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
@RestController
@Slf4j
public class PaymentController{
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;//添加serverPort
@Autowired
DiscoveryClient discoveryClient;
@PostMapping(value = "/payment/create")
public CommonResult create(@RequestBody Payment payment)
{
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if(result > 0)
{
return new CommonResult(200,"插入数据库成功,serverPort: "+serverPort,result);
}else{
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id)
{
Payment payment = paymentService.getPaymentById(id);
if(payment != null)
{
return new CommonResult(200,"查询成功,serverPort: "+serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
@GetMapping(value = "/payment/discovery")
public Object discovery()
{
List<String> services = discoveryClient.getServices();
for (String element : services) {
log.info("*****element: "+element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances) {
log.info(instance.getServiceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
return this.discoveryClient;
}
@RequestMapping(value = "/payment/zk")
public String paymentzk()
{
return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
}
@RequestMapping(value = "/payment/consul")
public String paymentConsul()
{
return "springcloud with consul: "+serverPort+"\t "+ UUID.randomUUID().toString();
}
@GetMapping(value = "/payment/lb")
public String getPaymentLB() {
return serverPort;//返回服务接口
}
}
- 订单微服务改造,注释@LoadBalanced,OrderMain去掉注解@RibbonClient
package com.chenyc.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author chenyc
* @create 2021-03-30 10:33
*/
@Configuration
public class ApplicationContextConfig {
@Bean
// @LoadBalanced//使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
package com.chenyc.springcloud;
import com.chenyc.myrule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
/**
* @author chenyc
* @create 2021-03-29 19:55
*/
@SpringBootApplication
@EnableEurekaClient//<--- Enable服务注册添加该标签
@EnableDiscoveryClient//<--- zookeeper服务注册添加该标签
//添加到此处
//@RibbonClient(name = "CLOUD-PROVIDER-PAYMENT", configuration = MySelfRule.class)
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain.class, args);
}
}
- LoadBalancer
package com.chenyc.springcloud.lb;
import org.springframework.cloud.client.ServiceInstance;
import java.util.List;
public interface LoadBalancer
{
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
- 创建MyLB类
package com.chenyc.springcloud.lb;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
*/
@Component//需要跟主启动类同包,或者在其子孙包下。
public class MyLB implements LoadBalancer
{
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement()
{
int current;
int next;
do {
current = this.atomicInteger.get();
next = current >= 2147483647 ? 0 : current + 1;
}while(!this.atomicInteger.compareAndSet(current,next));
System.out.println("*****第几次访问,次数next: "+next);
return next;
}
//负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标 ,每次服务重启动后rest接口计数从1开始。
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances)
{
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
- 修改controller
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.lb.LoadBalancer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
/**
* @author chenyc
* @create 2021-03-30 10:34
*/
@RestController
@RequestMapping("/consumer")
public class OrderController {
//远程调用的 地址
// public static final String PAYMENY_URL = "http://localhost:8001";
//euraka远程调用的地址
public static final String PAYMENY_URL = "http://CLOUD-PROVIDER-PAYMENT";
//zookeeper和consul远程调用的地址
// public static final String PAYMENY_URL = "http://cloud-provider-payment";
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@PostMapping("/payment/create")
public CommonResult<Payment> create (@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENY_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENY_URL + "/payment/get/" + id,//请求地址
CommonResult.class);//返回类型
}
@GetMapping(value = "/payment/zk")
public String paymentInfo()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/zk",String.class);
return result;
}
@GetMapping(value = "/payment/consul")
public String paymentInfoConsul()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/consul",String.class);
return result;
}
@GetMapping("/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id)
{
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENY_URL+"/payment/get/"+id,CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();//getForObject()
}else{
return new CommonResult<>(444,"操作失败");
}
}
@GetMapping(value = "/payment/lb")
public String getPaymentLB()
{
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-PAYMENT");
if(instances == null || instances.size() <= 0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}
}
- 测试 不停地刷新http://localhost/consumer/payment/lb,可以看到8001/8002交替出现。
26.OpenFeign是什么
- Feign能干什么
Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
- Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
- Feign和OpenFeign两者区别
Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@Feignclient可以解析SpringMVc的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
27.OpenFeign服务调用
- 修改pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.chenyc</groupId>
<artifactId>spring-cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consumer-order</artifactId>
<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-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.chenyc</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>-->
<!-- <!–SpringCloud consul-server –>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-consul-discovery</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
- yaml未修改
- 主启动
package com.chenyc.springcloud;
import com.chenyc.myrule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* @author chenyc
* @create 2021-03-29 19:55
*/
@SpringBootApplication
@EnableEurekaClient//<--- Enable服务注册添加该标签
@EnableDiscoveryClient//<--- zookeeper服务注册添加该标签
//添加到此处
//@RibbonClient(name = "CLOUD-PROVIDER-PAYMENT", configuration = MySelfRule.class)
@EnableFeignClients
public class OrderMain {
public static void main(String[] args) {
SpringApplication.run(OrderMain.class, args);
}
}
- 业务类
package com.chenyc.springcloud.openFeign;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author chenyc
* @create 2021-04-04 15:40
*/
@Component
@FeignClient(value = "CLOUD-PROVIDER-PAYMENT")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id);
}
- Controller
package com.chenyc.springcloud.controller;
import com.chenyc.springcloud.entities.CommonResult;
import com.chenyc.springcloud.entities.Payment;
import com.chenyc.springcloud.lb.LoadBalancer;
import com.chenyc.springcloud.openFeign.PaymentFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.net.URI;
import java.util.List;
/**
* @author chenyc
* @create 2021-03-30 10:34
*/
@RestController
@RequestMapping("/consumer")
public class OrderController {
//远程调用的 地址
// public static final String PAYMENY_URL = "http://localhost:8001";
//euraka远程调用的地址
public static final String PAYMENY_URL = "http://CLOUD-PROVIDER-PAYMENT";
//zookeeper和consul远程调用的地址
// public static final String PAYMENY_URL = "http://cloud-provider-payment";
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@Autowired
private RestTemplate restTemplate;
@Autowired
PaymentFeignService paymentFeignService;
@PostMapping("/payment/create")
public CommonResult<Payment> create (@RequestBody Payment payment){
return restTemplate.postForObject(PAYMENY_URL + "/payment/create", payment, CommonResult.class);
}
@GetMapping("/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENY_URL + "/payment/get/" + id,//请求地址
CommonResult.class);//返回类型
}
@GetMapping(value = "/payment/zk")
public String paymentInfo()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/zk",String.class);
return result;
}
@GetMapping(value = "/payment/consul")
public String paymentInfoConsul()
{
String result = restTemplate.getForObject(PAYMENY_URL+"/payment/consul",String.class);
return result;
}
@GetMapping("/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id)
{
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENY_URL+"/payment/get/"+id,CommonResult.class);
if(entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();//getForObject()
}else{
return new CommonResult<>(444,"操作失败");
}
}
@GetMapping(value = "/payment/lb")
public String getPaymentLB()
{
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PROVIDER-PAYMENT");
if(instances == null || instances.size() <= 0){
return null;
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}
@GetMapping(value = "/payment/openFeign/get/{id}")
public CommonResult<Payment> openFeign(@PathVariable("id") Long id)
{
return paymentFeignService.getPaymentById(id);
}
}
6.测试
http://localhost/consumer/payment/openFeign/get/1
Feign自带负载均衡配置项
28. OpenFeign超时控制
OpenFeign默认等待1秒钟,超过后报错
YML文件里需要开启OpenFeign客户端超时控制
#设置feign客户端超时时间(OpenFeign默认支持ribbon)(单位:毫秒)
ribbon:
#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
29.OpenFeign日志增强
- 日志打印功能
Feign提供了日志打印功能,我们可以通过配置来调整日恙级别,从而了解Feign 中 Http请求的细节。
说白了就是对Feign接口的调用情况进行监控和输出
- 日志级别
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
- FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据。
配置日志bean
package com.chenyc.springcloud.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig
{
@Bean
Logger.Level feignLoggerLevel()
{
return Logger.Level.FULL;
}
}
YML文件里需要开启日志的Feign客户端
logging:
level:
# feign日志以什么级别监控哪个接口
com.chenyc.springcloud.openFeign.PaymentFeignService: debug