集群原理说明
问题:微服务RPC远程服务调用最核心的是什么 答:高可用,试想你的注册中心只有一个only one, 它出故障了(不能提供和服务了),会导致整个为服务环境不可用,所以解决办法:搭建Eureka注册中心集群 ,实现负载均衡+故障容错。
集群环境的操作实现原理:互相注册,相互守望,对外是整体。
集群环境构建
一、新建cloud-eureka-server7001
更改pom文件
<dependencies>
<!--eureka-server(增加内容)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- 引入自定义的api通用包,可以使用Payment和CommonResult -->
<dependency>
<groupId>com.chenxin.springcloud</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-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>
创建YAML文件
server:
port: 7001
eureka:
instance:
hostname: localhost #eureka服务端的实例名称
client:
#false表示不向注册中心注册自己。
register-with-eureka: false
#false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
#设置与Eureka Server交互的地址查询服务和注册服务都需要依赖这个地址。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
创建主启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableEurekaServer // 标识当前服务是EurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
测试Euraka是否部署成功
二、新建cloud-eureka-server7002(略)
更改端口号和名称即可
三、创建微服务提供者支付Module模块,EurekaClient端8001入驻EurekaServer成为服务提供者
改pom(新增eureka-client坐标)
<dependencies>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
<!--mysql-connector-java-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
写YAML
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.cj.jdbc.Driver # mysql驱动包 com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/cloud?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.chenxin.springcloud.entity # 所有Entity别名类所在包
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
defaultZone: http://eureka-server:7001/eureka
创建主启动类
@SpringBootApplication
@EnableEurekaClient // 添加注解标识为EurekaClient
public class Payment8001 {
public static void main(String[] args) {
SpringApplication.run(Payment8001.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=utf8
实体Payment
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;
}
Json封装体CommonResult
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@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层
import com.chenxin.springcloud.entity.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface PaymentMapper {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
mybaits的映射文件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.chenxin.springcloud.dao.PaymentMapper">
<resultMap id="BaseResultMap" type="Payment">
<id column="id" property="id" jdbcType="BIGINT"/>
<result 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层
public interface PaymentService {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
ServiceImpl
import com.chenxin.springcloud.dao.PaymentMapper;
import com.chenxin.springcloud.entity.Payment;
import com.chenxin.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class PaymentServiceImpl implements PaymentService {
@Resource
private PaymentMapper paymentMapper;
@Override
public int create(Payment payment) {
return paymentMapper.create(payment);
}
@Override
public Payment getPaymentById(Long id) {
return paymentMapper.getPaymentById(id);
}
}
Controller
import com.chenxin.springcloud.entity.Payment;
import com.chenxin.springcloud.service.PaymentService;
import com.chenxin.springcloud.utils.CommonResult;
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,"插入数据库成功",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);
log.info("*****查询结果:{}",payment);
if (payment != null) {
return new CommonResult(200,"查询成功",payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
}
5.测试
访问http://localhost:8001/payment/get/1
三、EurekaClient端80入驻EurekaServer成为服务消费者
1.创建模块cloud-consumer-order80
改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.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.写YAML
server:
port: 80
3主启动类
import org.springframework.boot.SpringApplication;
@SpringBootApplication
public class OrderApplication80 {
public static void main(String[] args) {
SpringApplication.run(OrderApplication80.class,args);
}
}
4.实体类
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;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@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);
}
}
5.将 RestTemplate 注入到 Spring Boot 容器中
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
// @Bean相当于applicationContext.xml <bean id="" class="">
}
6.Controller
import com.chenxin.springcloud.entity.Payment;
import com.chenxin.springcloud.utils.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderController {
public static final String PAYMENT_URL = "http://localhost:8001";
@Resource
private RestTemplate restTemplate;
@GetMapping(value = "/consumer/payment/create")
public CommonResult<Payment> create(Payment payment) {
log.info("进入了客户端/消费者端的create方法...");
return restTemplate.postForObject(PAYMENT_URL + "/payment/create",payment, CommonResult.class);
}
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Integer id) {
log.info("进入了客户端/消费者端的getPayment方法...");
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id,CommonResult.class);
}
}
7.测试
直接访问服务端:http://localhost:8001/payment/get/1
通过消费者端访问:http://localhost/consumer/payment/get/1
四、新建cloud-api-commons
pom
<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>
<!-- 处理日期时间好用:https://www.cnblogs.com/wuwuyong/p/16529839.html -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.1.0</version>
</dependency>
</dependencies>
Entity & CommonResult
在新建的模块(cloud-api-commons)新建同样的包结构,并将原来的重复代码类复制过来,
如下图所示:
maven命令clean install(略)
订单80和支付8001分别改造
删除各自的原先有过的entity和utils包
各自粘贴POM内容
<!-- 引入自定义的api通用包,可以使用Payment和CommonResult -->
<dependency>
<groupId>com.chenxin.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
五、测试集群
启动7001:
启动7002:
访问:由于做了域名映射,可以访问:http://eureka7001.com:7001/
访问:由于做了域名映射,可以访问:http://eureka7002.com:7002/
1、将支付微服务8001注册到2台Eureka集群中
修改 application.yml
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
2、将支付微服务80注册到2台Eureka集群中
修改 application.yml
service-url:
#defaultZone: http://localhost:7001/eureka
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
3、测试
先要启动EurekaServer,7001/7002服务
再要启动服务提供者provider,8001
再要启动消费者,80
http://localhost/consumer/payment/get/1
4、支付微服务(8001)集群配置
新建cloud-provider-payment8002(略)
改pom(直接复制8001的pom)(略)
写YAML(直接复制8001的YAML)(略)
主启动(略)
业务类(略)
修改8001&8002的Controller
// -----------新增代码开始-----------------
@Value("${server.port}")
private String serverPort;
// -----------新增代码结束-----------------
// 以下完整代码
@RestController
@Slf4j
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@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);
log.info("*****查询结果:{}",payment);
if (payment != null) {
return new CommonResult(200,"查询成功,服务端口" + serverPort,payment);
}else{
return new CommonResult(444,"没有对应记录,查询ID: "+id,null);
}
}
}
5、测试一(启动顺序)
启动7001
启动7001
启动8001
启动8002
启动80
启动后查看集群环境是否正常:
测试二(访问资源)
访问8001:http://localhost:8001/payment/get/1
访问8002:http://localhost:8002/payment/get/1
通过消费者端访问:http://localhost/consumer/payment/get/1
6、负载均衡(解决只从一个服务器获取资源)
Bug(订单服务访问地址不能写死)
由于我们现在在注册中心已经有了服务提供者,上图中我们不使用localhost,而是使用我们注册中心的服务名,即:
将
public static final String PAYMENT_URL = "http://localhost:8001";
改为
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
修改完毕重启80服务:
@LoadBalanced注解负载均衡的能力
使用@LoadBalanced注解赋予RestTemplate负载均衡的能力
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //使用@LoadBalanced注解赋予RestTemplate负载均衡的能力,这样才可以通过RestTemplate负载均衡方式调用微服务
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
// @Bean相当于applicationContext.xml <bean id="" class="">
}
测试(修改完毕,重新启动80进行测试)
// 多次刷新会从不同的服务器上获取资源
{"code":200,"message":"查询成功,服务端口8002","data":{"id":1,"serial":"123DAF234SDFA342D"}}
{"code":200,"message":"查询成功,服务端口8001","data":{"id":1,"serial":"123DAF234SDFA342D"}}