1. 版本如何确定
2. 准备项目
2.1 创建父工程
2.1.1 页面操作
2.1.2 父工程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">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zmj.springcloud</groupId>
<artifactId>cloud2022</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<!-- 统一管理jar包版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.18.24</lombok.version>
<mysql.version>5.1.47</mysql.version>
<druid.version>1.1.22</druid.version>
<mybatis.spring.boot.version>2.3.0</mybatis.spring.boot.version>
</properties>
<!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version -->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.12.RELEASE</version>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.2 新建模块
2.2.1 新建模块操作
比如新建一个支付模块
2.2.2 模块的pom.xml添加依赖
<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.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>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.2.3 模块配置文件application.yml
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3306/mytest?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.zmj.springcloud.entity
2.2.4 主启动类
@SpringBootApplication
@MapperScan("com.zmj.springcloud.dao")
public class CloudProviderPayment {
public static void main(String[] args) {
SpringApplication.run(CloudProviderPayment.class,args);
}
}
2.2.5 实体类对应数据库表
package com.zmj.springcloud.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
private Long id;
private String serial;
}
2.2.6 响应结果类封装JSON
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseResult<T> {
private Integer code;
private String message;
private T data;
public ResponseResult(Integer code,String message) {
this(code,message,null);
}
}
2.2.7 DAO层
package com.zmj.springcloud.dao;
import com.zmj.springcloud.entity.Payment;
import org.apache.ibatis.annotations.Param;
public interface PaymentDao {
int add(Payment payment);
Payment getPaymentById(@Param("id") Long id);
}
2.2.8 mapper/PaymentMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zmj.springcloud.dao.PaymentDao">
<insert id="add" parameterType="com.zmj.springcloud.entity.Payment" useGeneratedKeys="true" keyProperty="id">
insert into payment(serials) values (#{serials})
</insert>
<resultMap id="BaseResultMap" type="com.zmj.springcloud.entity.Payment">
<id column="id" property="id" jdbcType="BIGINT"></id>
<id column="serials" property="serials" jdbcType="VARCHAR"></id>
</resultMap>
<select id="getPaymentById" resultMap="BaseResultMap">
select * from Payment where id = #{id}
</select>
</mapper>
2.2.9 Service层
@Service
public class PaymentServiceImpl implements PaymentService {
@Resource
private PaymentDao paymentDao;
@Override
public int save(Payment payment) {
return paymentDao.add(payment);
}
@Override
public Payment getPaymentById(Long id) {
return paymentDao.getPaymentById(id);
}
}
2.2.10 Controller层
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@PostMapping("/payment/create")
public ResponseResult create(@RequestBody Payment payment) {
int result = paymentService.save(payment);
log.info("插入结果:"+result);
if (result > 0) {
return new ResponseResult(200,"保存成功",result);
}
return new ResponseResult(444,"保存失败",null);
}
@GetMapping("/payment/{id}")
public ResponseResult getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
log.info("端口:"+serverPort+",查询结果:"+payment);
if (payment != null) {
return new ResponseResult(200,"查询成功",payment);
}
return new ResponseResult(445,"没有对应的查询结果,查询ID="+id,null);
}
}
2.2.11 再新建一个消费者模块
其他模块类似,比如再创建一个cloud-consumer-order模块,作为客户端调用上面的支付模块
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.zmj.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
</dependencies>
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
@RestController
public class OrderController {
public static final String PAYMENT_URL = "http://localhost:8001";
@Resource
private RestTemplate restTemplate;
@PostMapping("/consumer/payment/create")
public ResponseResult<Payment> create(@RequestBody Payment payment) {
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment, ResponseResult.class);
}
@GetMapping("/consumer/payment/{id}")
public ResponseResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL+"/payment/"+id,ResponseResult.class);
}
}
2.2.12 再新建一个公共模块
比如抽取上面两个模块里面的entity
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
2.2.13 启动所有模块
按Alt + 8 可以调出所有的Services,然后右键启动,就可以将全部模块启动
http://localhost/consumer/payment/1
结果:
{"code":200,"message":"查询成功","data":{"id":1,"serials":"abcd"}}
http://127.0.0.1/consumer/payment/create
请求JSON:
{
"serials": "sxy"
}
响应JSON:
{
"code": 200,
"message": "保存成功",
"data": 1
}
3. Eureka服务
3.1 Eureka概述
服务治理
Spring Cloud封装了Netflix公司开发的Eureka模块来实现服务治理。
在传统的RPC远程调用框架中,管理每个服务与服务之前依赖关系比较复杂,管理比较复杂,所以需要适用服务治理,管理服务与服务之间的依赖关系,可以服务调用、负载均衡、容错等,实现服务发现与注册。
服务注册与发现
Eureka采用了CS设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。而系统中的其他微服务,适用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中的各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前服务器自己的信息,比如服务通讯地址等以别名方式注册到注册中心上。另一方(消费者或服务提供者)以该别名的方式去注册中心上获取实际的服务通讯地址,然后再实现本地RPC调用。
RPC远程调用的核心设计思想在于注册中心。因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。在任何RPC远程框架中,都会有一个注册中心用于存放服务地址相关信息(接口地址)。
Eureka包含两个组件,Eureka Server和Eureka Client。
Eureka Server提供服务注册服务。各个微服务节点通过配置启动后,会在Eureka Server中进行注册。这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息。服务节点的信息可在界面中直观的看到。
Eureka Client通过注册中心进行访问,是一个Java客户端,用于简化Eureka Server
的交互。客户端同时也具备一个内置的、使用轮询负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将把这个服务节点从服务注册表中移除(默认为90秒)。
3.2 单机Eureka构建
3.2.1 新建Eureka模块
@SpringBootApplication
@EnableEurekaServer
public class CloudEurekaServer {
public static void main(String[] args) {
SpringApplication.run(CloudEurekaServer.class,args);
}
}
application.yml
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名字
client:
register-with-eureka: false #表识不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
<artifactId>cloud-eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>com.zmj.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
3.2.2 测试发现还没有服务注册进来
3.2.3 注册客户端
1) 比如,注册cloud-provider-payment 添加到pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2) 添加到application.yml
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
3) 启动类,加入@EnableEurekaClient注解
@SpringBootApplication
@MapperScan("com.zmj.springcloud.dao")
@EnableEurekaClient
public class CloudProviderPayment {
public static void main(String[] args) {
SpringApplication.run(CloudProviderPayment.class,args);
}
}
4) 再次查看注册成功
5) 再把cloud-consumer-order按照上面的方式注册进来
3.3 集群Eureka构建
3.3.1 再次新建一个Eureka服务并互相注册
7001服务application.yml
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名字
client:
register-with-eureka: false #表识不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7002.com:7002/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
7002服务application.yml
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名字
client:
register-with-eureka: false #表识不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
3.3.2 再次新建支付模块
新建支付模块端口为8002,其他内容与8001一样
3.3.3 支付模块和订单模块并修改application.yml
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
3.3.4 订单模块修改OrderController和ApplicationContextConfig
@RestController
public class OrderController {
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Resource
private RestTemplate restTemplate;
@PostMapping("/consumer/payment/create")
public ResponseResult<Payment> create(@RequestBody Payment payment) {
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment, ResponseResult.class);
}
@GetMapping("/consumer/payment/{id}")
public ResponseResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL+"/payment/"+id,ResponseResult.class);
}
}
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //赋予RestTemplate负载均衡的能力,否则会报错。
// I/O error on GET request for "http://CLOUD-PAYMENT-SERVICE/payment/1": CLOUD-PAYMENT-SERVICE;
// nested exception is java.net.UnknownHostException: CLOUD-PAYMENT-SERVICE
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
3.3.5 测试结果
结果会发现,两个支付模块服务实现了负载均衡
3.4 actuator微服务信息完善
3.4.1 服务名称修改和IP显示
从上面的注册页面显示可以看到,支付模块服务名称过长,或者我们想定义成自己想要的;另外也想看到具体的IP
在对应的模块下application.yml里面的 eureka节点下加入
eureka:
instance:
instance-id: payment8001
prefer-ip-address: true #访问路径有IP地址提示
eureka:
instance:
instance-id: payment8002
prefer-ip-address: true #访问路径有IP地址提示
http://123.1.1.226:8002/actuator/health
3.5 服务发现Discovery
对于注册进eureka里面的微服务,可以通过服务发现来获得该服务的信息
在两个支付模块下修改
@SpringBootApplication
@MapperScan("com.zmj.springcloud.dao")
@EnableEurekaClient
@EnableDiscoveryClient //开启服务发现功能
public class CloudProviderPayment {
public static void main(String[] args) {
SpringApplication.run(CloudProviderPayment.class,args);
}
}
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@Resource
private DiscoveryClient discoveryClient;
@PostMapping("/payment/create")
public ResponseResult create(@RequestBody Payment payment) {
int result = paymentService.save(payment);
log.info("插入结果:"+result);
if (result > 0) {
return new ResponseResult(200,"保存成功",result);
}
return new ResponseResult(444,"保存失败",null);
}
@GetMapping("/payment/{id}")
public ResponseResult getPaymentById(@PathVariable("id") Long id) {
Payment payment = paymentService.getPaymentById(id);
log.info("端口:"+serverPort+",查询结果:"+payment);
if (payment != null) {
return new ResponseResult(200,"查询成功",payment);
}
return new ResponseResult(445,"没有对应的查询结果,查询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;
}
}
http://localhost:8002/payment/discovery
3.6 Eureka自我保护
某时刻某一个微服务不可用了,Eureka不会立刻清理,依旧会对该微服务的信息进行保存,属于CAP里面的AP分支
4.Zookeeper服务注册与发现
4.1 新建提供者模块
4.1.1 新建支付模块
新建一个Maven支付模块cloud-provider-payment3
<dependencies>
<dependency>
<artifactId>cloud2022</artifactId>
<groupId>com.zmj.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 8004
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: 192.168.1.200:2181
@SpringBootApplication
@EnableDiscoveryClient //开启服务发现功能
public class CloudProviderPayment {
public static void main(String[] args) {
SpringApplication.run(CloudProviderPayment.class,args);
}
}
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/zk")
public String paymentzk(){
return "springcloud with zookeeper:"+serverPort+"\t"+ UUID.randomUUID().toString();
}
}
4.1.2 启动此模块
4.1.3 查看ZK服务注册
详情请参照: 好好聊聊Docker 23. Docker安装Zookeeper
docker ps
# CONTAINER ID IMAGE NAMES
# 8c89711ae723 zookeeper:3.5.7
docker exec -it 8c89711ae723 /bin/bash
cd bin
./zkCli.sh -server 127.0.0.1:2181
#查看注册服务
ls /services
[cloud-provider-payment]
http://localhost:8004/payment/zk
4.2 新建消费者模块
4.2.1 新建订单模块
新建一个Maven订单模块cloud-consumerzk-order
<dependencies>
<dependency>
<artifactId>cloud2022</artifactId>
<groupId>com.zmj.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 80
spring:
application:
name: cloud-consumer-order
cloud:
zookeeper:
connect-string: 192.168.1.200:2181
@SpringBootApplication
@EnableDiscoveryClient //开启服务发现客户端
public class CloudConsumerZKOrder {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerZKOrder.class,args);
}
}
@Configuration
public class ApplicationContextConfig {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
@RestController
@Slf4j
public class OrderZKController {
public static final String INVOME_URL = "http://cloud-provider-payment";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/zk")
public String payment (){
String result = restTemplate.getForObject(INVOME_URL+"/payment/zk",String.class);
return result;
}
}
4.2.2 启动此模块
4.2.3 查看ZK服务注册
ls /services
#[cloud-consumer-order, cloud-provider-payment]
http://localhost/consumer/payment/zk
5. Consul服务注册与发现
5.1 Consul手册
What is Consul? | Consul | HashiCorp Developer
5.2 安装Consul
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install consul
consul --version
#Consul v1.15.2
#Revision 5e08e229
#Build Date 2023-03-30T17:51:19Z
#Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
5.3 启动Consul
#使用开发模式启动
consul agent -dev -client 0.0.0.0 -u
5.4 新建提供者模块
新建Maven支付模块cloud-providerconsul-payment
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.zmj.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 8006
spring:
application:
name: consul-provider-payment
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
@SpringBootApplication
@EnableDiscoveryClient
public class CloudProviderconsulPayment {
public static void main(String[] args) {
SpringApplication.run(CloudProviderconsulPayment.class,args);
}
}
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/consul")
public String paymentConsul() {
return "springcloud with consul: " + serverPort + "\t" + UUID.randomUUID().toString();
}
}
5.4 新建消费者模块
新建Maven订单模块cloud-consumerconsul-order
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.zmj.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 80
spring:
application:
name: consul-consumer-order
cloud:
consul:
host: localhost
port: 8500
discovery:
service-name: ${spring.application.name}
@SpringBootApplication
@EnableDiscoveryClient
public class CloudConsumerconsulOrder {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerconsulOrder.class,args);
}
}
@Configuration
public class ApplicationContextConfig {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
@RestController
@Slf4j
public class OrderConsulController {
public static final String INVOME_URL = "http://consul-provider-payment";
@Resource
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/consul")
public String payment (){
return restTemplate.getForObject(INVOME_URL+"/payment/consul",String.class);
}
}
6.Ribbon负载均衡服务调用
6.1 概述
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer (简称LB) 后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现白定义的负载均衡算法。
7.OpenFeign服务接口调用
7.1 概述
Feign是一个声明式的web服务客户端,让编写web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可。
Feign旨在使编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一Feign汪解即可),即可完成对服务提供方的接口绑定,简化了使用Sprin cloud Ribbon时,自动封装服务调用客户端的开发量。Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用
7.2 新建消费者模块
新建Maven订单模块cloud-consumer-feign-order
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.zmj.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka
@SpringBootApplication
@EnableFeignClients
public class CloudConsumerFeignOrder {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerFeignOrder.class,args);
}
}
下面这段对照提供者(上面截图的支付模块)来写,调用其实也是调用的提供者对应位置
@Service
@FeignClient(value = "CLOUD-PAYMENT-SERVICE") //支付模块注册服务名称
public interface PaymentFeignService {
@GetMapping(value = "/payment/{id}")
ResponseResult getPaymentById(@PathVariable("id") Long id);
}
@RestController
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService;
@GetMapping(value = "/consumer/payment/get/{id}")
public ResponseResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
7.3 启动模块查看效果
http://localhost/consumer/payment/get/1
Feign自带负载均衡配置项,两个支付模块交替查询
7.4 OpenFeign超时控制
在两个提供者(支付模块)中延长处理时间
再次访问,会看到报错,超时。因为openFeign默认超时时间为一秒
http://localhost/consumer/payment/get/1
YML文件里需要开启OpenFeign客户端超时控制
在消费者(订单模块)application.yml中追加
#设置Feign客户端超时时间(OpenFeign默认支持Ribbon)
ribbon:
#建立连接所用的时间(毫秒)
ReadTimeout: 6000
#建立连接后从服务器读取可用资源所用的时间(毫秒)
ConnectTimeout: 6000
再次访问,不再报错
7.5 OpenFeign日志打印功能
7.5.1 日志级别
NONE: 默认的,不显示任何日志
BASIC: 仅记录请求方法、URL、响应状态码及执行时间
HEADERS: 除了 BASIC 中定义的信息之外,还有请求和响应的头信息
FULL: 除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据
7.5.2 配置日志Bean
import feign.Logger;
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
application.yml
#追加日志级别设置
logging:
level:
com.zmj.springcloud.service.PaymentFeignService: debug
8.Gateway新一代网关
8.1 概述
Route(路由)是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由。
Predicate(断言)参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
Filter(过滤)指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gatewayeb Handler.
Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter在“pre”类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等在“post”类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量监控等有着非常重要的作用。
8.2 新建模块
新建网关模块cloud-gateway-gateway
8.2.1 添加依赖
<dependencies>
<!--新增gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.zmj.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
8.2.2 application.yml
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/** #断言,路径相匹配的进行路由
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
8.2.3 主启动类
@SpringBootApplication
@EnableEurekaClient
public class CloudGatewayMain {
public static void main(String[] args) {
SpringApplication.run( CloudGatewayMain.class,args);
}
}
8.2.4 启动服务
http://localhost:9527/payment/1
8.3 通过微服务名实现动态路由
默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
application.yml
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/** #断言,路径相匹配的进行路由
- id: payment_routh2
#uri: http://localhost:8001
uri: lb://cloud-payment-service
predicates:
- Path=/payment/discovery/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
8.4 断言
application.yml的predicates部分
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/** #断言,路径相匹配的进行路由
#如果时间未到,则报错org.springframework.web.server.ResponseStatusException: 404 NOT_FOUND
- After=2023-06-26T22:58:35.253+08:00[Asia/Shanghai]
- Cookie=username,zmj #必须有cookie存在username=zmj
- Header=X-Request-Id, \d+ #请求头要有X-Request-Id。并且值为正整数
- id: payment_routh2
#uri: http://localhost:8001
uri: lb://cloud-payment-service
predicates:
- Path=/payment/discovery/** #断言,路径相匹配的进行路由
8.4.1 测试After
8.4.2 测试Cookie
注掉Cooie设置再测
8.4.3 测试Header
打开Header,注掉Cooie设置再测
8.5 Filter
8.5.1 自定义过滤器
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("*********come in MyLogGateWayFilter: "+new Date());
String username = exchange.getRequest().getQueryParams().getFirst("username");
if(StringUtils.isEmpty(username)){
log.info("*****用户名为Null 非法用户");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
8.5.2 启动服务
http://localhost:9527/payment/discovery?username=z3
9. SpringCloud Alibaba
9.1官网
https://github.com/alibaba/spring-cloud-alibaba
Spring Cloud Alibaba Reference Documentation
https://github.com/alibaba/spring-cloud-alibaba/blob/2.2.x/README-zh.md
9.2 官网文档
https://nacos.io/zh-cn/index.html
9.3 下载并安装
参照:在Linux系统上安装软件 4. Linux安装Nacos
9.4 Nacos作为服务注册中心
新建模块
9.4.1 父工程增加依赖
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
9.4.2 新建提供者
cloudalibaba-provider-payment
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
application.yml
server:
port: 9912
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: http://192.168.1.133:8848/
management:
endpoints:
web:
exposure:
include: '*'
@EnableDiscoveryClient
@SpringBootApplication
public class CloudAlibabaProviderPayment {
public static void main(String[] args) {
SpringApplication.run(CloudAlibabaProviderPayment.class,args);
}
}
@RestController
public class PaymentController
{
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id)
{
return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
}
}
9.4.3 启动服务并测试
9.4.4 再新建一个提供者模块
cloudalibaba-provider-payment2,端口9911
9.4.5 新建消费者
cloudalibaba-consumer-nacos-order,端口83
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.zmj.springcloud</groupId>
<artifactId>cloud-api-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
application.yml
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: http://192.168.1.133:8848/
service-url:
nacos-user-service: http://nacos-payment-provider
@EnableDiscoveryClient
@SpringBootApplication
public class CloudalibabaConsumerNacosOrder {
public static void main(String[] args) {
SpringApplication.run(CloudalibabaConsumerNacosOrder.class,args);
}
}
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
@RestController
@Slf4j
public class OrderNacosController {
@Resource
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}")
private String serverURL;
@GetMapping(value = "/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Long id) {
return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
}
}
http://localhost:83/consumer/payment/nacos/1
负载均衡机制
9.5 Nacos作为服务配置中心
9.5.1 基础配置
9.5.1.1 新建模块
cloudalibaba-config-nacos-client,端口3377
<dependencies>
<!--nacos-config-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--nacos-discovery-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般基础配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
bootstrap.yml
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: http://192.168.1.133:8848/ #服务注册中心地址
config:
server-addr: http://192.168.1.133:8848/ #配置中心地址
file-extension: yml #指定yaml格式的配置
application.yml
spring:
profiles:
active: dev
bootstrap.yml的优先级大于application.yml
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain {
public static void main(String[] args) {
SpringApplication.run(NacosConfigClientMain.class, args);
}
}
package com.zmj.springcloud.alibaba.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope //支持Nacos配置的动态刷新
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
9.5.1.2 Nacos配置文件中dataId命名规则
${spring.application.name}-${spring.profiles.active).${spring.cloud.nacos.config.file-extension}
得到这次要配置的文件名称:nacos-config-client-dev.yml
9.5.1.3 新建Nacos命名空间
http://192.168.1.133:8848/nacos/
P107不断更新中。。。。
文章评论