# mycloud-demo
**Repository Path**: chen_jeff/mycloud-demo
## Basic Information
- **Project Name**: mycloud-demo
- **Description**: Spring Cloud 项目搭建
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-12-06
- **Last Updated**: 2021-12-14
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
[toc]
# MyCloud DEMO 项目构建 DEMO
## 1. 版本信息
- cloud: Hoxton.SR1
- boot: 2.2.2.RELEASE
- cloud alibaba: 2.1.0.RELEASE
- openJDK: 11
- maven: 3.6
- mysql: 8.x
## 2. 工程构建
### 2.1. 父工程构建
#### 2.1.1. `pom.xml` 引入依赖
```pom
4.0.0
ink.honp
mycloud-demo
1.0-SNAPSHOT
11
11
UTF-8
2.2.2.RELEASE
Hoxton.SR1
2.1.0.RELEASE
4.12
1.2.17
1.18.16
8.0.15
1.2.0
2.1.0
org.springframework.boot
spring-boot-dependencies
${spring.boot.version}
pom
import
org.springframework.cloud
spring-cloud-dependencies
${spring.cloud.version}
pom
import
com.alibaba.cloud
spring-cloud-alibaba-dependencies
${spring.cloud.alibaba.version}
pom
import
mysql
mysql-connector-java
${mysql.version}
com.alibaba
druid
${druid.version}
org.mybatis.spring.boot
mybatis-spring-boot-starter
${mybatis.spring.boot.version}
junit
junit
${junit.version}
log4j
log4j
${log4j.version}
org.projectlombok
lombok
${lombok.version}
true
org.springframework.boot
spring-boot-maven-plugin
true
true
```
### 2.2. 服务提供者模块 `cloud-provider-payment` 构建
#### 2.2.1. 初始化库表
SQL 语句
```sql
CREATE DATABASE IF NOT EXISTS cloud_demo DEFAULT CHARACTER SET utf8mb4 ;
use cloud_demo;
-- payment
DROP TABLE IF EXISTS payment;
CREATE TABLE payment (
`id` BIGINT (20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`serial` VARCHAR (300) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE = INNODB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ;
INSERT INTO payment (`id`, `serial`) VALUES(31, 'test001'),(32, 'test002') ;
```
#### 2.2.2. 引入依赖
`pom.xml`
```xml
mycloud-demo
ink.honp
1.0-SNAPSHOT
4.0.0
cloud-provider-payment
payment 服务提供者
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
org.mybatis.spring.boot
mybatis-spring-boot-starter
com.alibaba
druid-spring-boot-starter
1.2.8
mysql
mysql-connector-java
org.springframework.boot
spring-boot-starter-jdbc
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
```
#### 2.2.3. 添加配置
`application.yml`
```yml
server:
port: 8081
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xxxx/cloud_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: dev
password: xxxx
mybatis:
mapperLocations: classpath:/mapper/*.xml
type-aliases-package: ink.honp.cloud.payment.entities
```
#### 2.2.4. 创建启动类
`ink.honp.cloud.payment.PaymentApplication`
```java
package ink.honp.cloud.payment;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author jeff chen
* @since 2021-12-06 15:03
*/
@SpringBootApplication
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class,args);
}
}
```
_**启动,测试模块是否构建成功**_
#### 2.2.5. 创建实体和对应的 DAO 类型
1. 创建实体类 `Payment`
```java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Payment implements Serializable {
private static final long serialVersionUID = -1305247656330915904L;
private Long id;
private String serial;
}
```
2. 创建对应的数据库操作接口 `PaymentDao`
```java
package ink.honp.cloud.payment.dao;
import ink.honp.cloud.payment.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @author jeff chen
* @since 2021-12-06 15:21
*/
@Mapper
public interface PaymentDao {
int create(Payment payment);
Payment getPaymentById(@Param("id") Long id);
}
```
3. 创建对应的 `Mapper.xml` 文件
```xml
insert into payment(serial) values(#{serial});
```
#### 2.2.6. 创建实服务类
1. 创建服务接口
```java
package ink.honp.cloud.payment.service;
import ink.honp.cloud.payment.entities.Payment;
/**
* @author jeff chen
* @since 2021-12-06 15:24
*/
public interface PaymentService {
int create(Payment payment);
Payment getPaymentById(Long id);
}
```
2. 服务接口实现
```java
package ink.honp.cloud.payment.service.impl;
import ink.honp.cloud.payment.dao.PaymentDao;
import ink.honp.cloud.payment.entities.Payment;
import ink.honp.cloud.payment.service.PaymentService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author jeff chen
* @since 2021-12-06 15:24
*/
@Service
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class PaymentServiceImpl implements PaymentService {
private final PaymentDao paymentDao;
@Override
public int create(Payment payment) {
return paymentDao.create(payment);
}
@Override
public Payment getPaymentById(Long id) {
return paymentDao.getPaymentById(id);
}
}
```
#### 2.2.7. 创建 `Controller`
1. 封装统一返回数据结构
```java
package ink.honp.cloud.payment.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author jeff chen
* @since 2021-12-06 15:19
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CommonResult implements Serializable {
private static final long serialVersionUID = -6645482762631529147L;
private Integer code;
private String message;
private T data;
public CommonResult(Integer code,String message){
this(code,message,null);
}
}
```
2. 创建 Controller 适配前端调用
```java
package ink.honp.cloud.payment.controller;
import ink.honp.cloud.payment.entities.CommonResult;
import ink.honp.cloud.payment.entities.Payment;
import ink.honp.cloud.payment.service.PaymentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author jeff chen
* @since 2021-12-06 15:29
*/
@Slf4j
@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class PaymentController {
private final PaymentService paymentService;
@PostMapping(value = "/payment/create")
public CommonResult create(Payment payment){ //埋雷
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if (result>0){ //成功
return new CommonResult(200,"插入数据库成功",result);
}else {
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("*****查询结果:"+payment);
if (payment!=null){ //说明有数据,能查询成功
return new CommonResult(200,"查询成功",payment);
}else {
return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
}
}
}
```
#### 2.2.8. 测试
#### 2.2.9 项目整体结构

### 2.3 热部署 Devtools 设置
1. 添加依赖
```xml
org.springframework.boot
spring-boot-devtools
runtime
true
```
2. 添加插件
```xml
org.springframework.boot
spring-boot-maven-plugin
true
true
```
3. IDEA 设置
1. File --> Settings --> Build, Execution, Deployment

2. 双击 `Shift`, 并选择 `Registry...`

更新一下的值

4. 重启 IDEA
### 2.3 服务消费模块构建 (cloud-consumer-order)
#### 2.3.1. 引入依赖
pom.xml
```xml
mycloud-demo
ink.honp
1.0-SNAPSHOT
4.0.0
cloud-consumer-order
服务消费者
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
```
#### 2.3.2. 添加配置
application.yml
```yml
server:
port: 80
spring:
application:
name: cloud-consumer-order
```
#### 2.3.3. 编写启动类
OrderApplication
```java
package ink.honp.cloud.order;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author jeff chen
* @since 2021-12-06 16:46
*/
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
```
#### 2.3.4. 调用服务提供者的业务
1. 创建 entities
拷贝 `cloud-provider-payment` 实体类
2. 配置 `RestTemplate`, 访问远程的 rest 接口
```java
package ink.honp.cloud.order.config;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
/**
* @author jeff chen
* @since 2021-12-06 16:48
*/
@SpringBootConfiguration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
```
3. 创建 `Controller` 调用服务提供者业务
```java
package ink.honp.cloud.order.controller;
import ink.honp.cloud.order.entities.CommonResult;
import ink.honp.cloud.order.entities.Payment;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* @author jeff chen
* @since 2021-12-06 16:50
*/
@Slf4j
@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrderController {
public static final String PAYMENT_URL = "http://localhost:8081";
private final RestTemplate restTemplate;
@PostMapping("/consumer/payment/create")
public CommonResult create(Payment payment){
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class); //写操作
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult getPayment(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
}
```
#### 2.3.5. 测试
### 2.4. 项目重构 01
项目服务提供者和消费者都使用了相同的 entities, 可以将两个模块共用的实体抽取并建立一个通用的模块
1. 构建通用模块 cloud-api-commons
- pom.xml
```xml
mycloud-demo
ink.honp
1.0-SNAPSHOT
4.0.0
cloud-api-commons
API 通用模块
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
```
- 模块结构

2. 将通用模块的版本管理添加到父项目的 pom.xml 文件
3. cloud-provider-payment 和 cloud-consumer-order 添加 cloud-api-commons 依赖,将原有的entities修改为通用模块的entities
### 2.5. 构建 Eureka 服务注册中心模块
1. 模块名称 cloud-eureka-server 引入依赖
```xml
mycloud-demo
ink.honp
1.0-SNAPSHOT
4.0.0
cloud-eureka-server
Eureka 注册中心
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-actuator
ink.honp
cloud-api-commons
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
org.springframework.boot
spring-boot-starter-test
test
junit
junit
```
2. 配置
```yml
server:
port: 7001
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetchRegistry: false
service-url:
defaultZone: http://localhost:7001/eureka
```
3. 创建启动类
```java
package ink.honp.cloud.ecureka.server;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/**
* @author jeff chen
* @since 2021-12-06 17:39
*/
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
```
4. 项目结构

### 2.6. 服务提供者和服务消费者引入`Eureka`服务注册中心
1. 引入 `Eureka` 客户端依赖
```java
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
2. 配置
```yaml
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
```
3. 激活启动注解
```java
@EnableEurekaClient
```
### 2.7. OpenFeign 服务接口调用
Feign 集成了 Ribbon, 利用 Ribbon 维护了 Payment 的服务列表,并且通过轮询实现客户端的负载均衡
1. `cloud-consumer-order` 添加依赖
```xml
org.springframework.cloud
spring-cloud-starter-openfeign
```
2. 添加业务调用接口
```java
package ink.honp.cloud.order.service;
import ink.honp.cloud.commons.entities.CommonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* @author jeff chen
* @since 2021-12-06 18:11
*/
@Service
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
CommonResult getPaymentById(@PathVariable("id") Long id);
}
```
`CLOUD-PAYMENT-SERVICE`: 服务提供者的名称
3. 修改 Controller
```java
package ink.honp.cloud.order.controller;
import ink.honp.cloud.commons.entities.CommonResult;
import ink.honp.cloud.commons.entities.Payment;
import ink.honp.cloud.order.service.PaymentFeignService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* @author jeff chen
* @since 2021-12-06 16:50
*/
@Slf4j
@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OrderController {
private final PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/get/{id}")
public CommonResult getPayment(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
```
4. 添加启动注解
```java
@EnableFeignClients
```
### 2.8. OpenFeign 常用配置
#### 2.8.1. 超时配置
默认Feign客户端只等待一秒钟。时候我们需要设置Feign客户端的超时控制,也即Ribbon的超时时间,
因为Feign集成了Ribbon进行负载均衡

添加 `Ribbon` 超时配置
```yaml
ribbon:
ReadTimeout: 3000
ConnectTimeout: 3000
MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用
MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数,不包括首次调用
OkToRetryOnAllOperations: false #是否所有操作都重试
#hystrix的超时时间
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 9000
```
一般情况下 都是 `ribbon的超时时间` < `hystrix的超时时间`(因为涉及到ribbon的重试机制)
重试次数:`MaxAutoRetries` + `MaxAutoRetriesNextServer` + (`MaxAutoRetries` * `MaxAutoRetriesNextServer`)
如果在重试期间,时间超过了hystrix的超时时间,便会立即执行熔断
hystrix超时时间的计算: `(1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout` 即按照以上的配置 hystrix的超时时间应该配置为 (1+1+1)*3=9秒
---
_**当ribbon超时后且hystrix没有超时,便会采取重试机制。当`OkToRetryOnAllOperations`设置为false时,
只会对get请求进行重试。如果设置为true,便会对所有的请求进行重试,如果是put或post等写操作,如果服务器
接口没做幂等性,会产生不好的结果,所以OkToRetryOnAllOperations慎用**_
---
#### 2.8.2. OpenFeign 日志打印
日志级别
- `NONE`:默认的,不显示任何日志
- `BASIC`:仅记录请求方法、RUL、响应状态码及执行时间
- `HEADERS`:除了BASIC中定义的信息之外,还有请求和响应的头信息
- `FULL`:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
1. 配置`Feign`日志级别
```java
package ink.honp.cloud.order.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 配日 Feign 日志级别
* @author jeff chen
* @since 2021-12-07 10:28
*/
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
```
2. 需要开启日志的`Feign`客户端
```yaml
logging:
level:
ink.honp.cloud.order.service: debug
```
### 2.9. Hystrix 断路器
功能特性:
- 服务降级
- 服务熔断
- 接近实时监控
- ...
#### 2.9.1 服务降级 `Fallback`
服务大流量进来的时候,无法正常提供服务,可以将服务降级,也就是返回一个友好提示给客户端
触发降级的场景:
- 程序运行异常
- 超时
- 服务熔断
- 线程池等无法提供服务
- 人工降级
#### 2.9.2 服务熔断 `Breaker`
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
#### 2.9.3 服务限流 `Flowlimit`
实现流程控制,防止高并发造成服务不可用
#### 2.9.4 示例
##### 2.9.4.1 JMeter 压力测试配置
_Windows apache-jmeter-5.4.1.zip 测试为例_
1. 安装地址
```yaml
https://jmeter.apache.org/download_jmeter.cgi
```
2. 修改为中文显示
`~/apache-jmeter-5.4.1/bin/jmeter.properties`
```yaml
language=zh_CN
```
2. 启动 `~/apache-jmeter-5.4.1/bin/jmeter.bat`

3. 添加测试线程组


4. 添加 http 取样器

若需要添加 header 参数,需要添加 `HTTP 信息头管理器`

5. 添加监听器,监听测试结果


##### 2.9.4.2 服务端的降级配置
1. 引入依赖
```xml
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
```
2. 业务类添加测试方法
```java
```
### 2.10. Spring-cloud-gateway 网关使用
#### 2.10.1. 新建 cloud-gateway 模块
1. 添加依赖
```xml
mycloud-demo
ink.honp
1.0-SNAPSHOT
4.0.0
cloud-gateway
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
org.springframework.cloud
spring-cloud-starter-gateway
ink.honp
cloud-api-commons
org.springframework.boot
spring-boot-devtools
runtime
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
```
2. 添加配置
```yaml
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: cloud-payment-service-route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://127.0.0.1:8081 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service # lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: cloud-payment-service-route-2
#uri: http://127.0.0.1:8081
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/**
eureka:
instance:
hostname: cloud-gateway-service
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
```
3. 启动类
```java
package ink.honp.cloud.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/**
* @author jeff chen
* @since 2021-12-14 10:32
*/
@SpringBootApplication
@EnableEurekaClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
```