我的上一篇博客:
初学SpringCloud:建设EurekaServer集群(新建一个EurekaServer模块)
1、背景介绍
我的这个专栏记录我初学SpringCloud时,动手做的一个简单的微服务架构的项目。目前项目使用的是Eureka的技术,已经创建好了EurekaServer的集群,并且做好了配置(只有两台EurekaServer,也就是两个微服务模块),端口分别是7001和7002。已经创建好了一个服务提供者的EurekaClient模块,端口是8001。已经创建好了一个服务调用者模块,端口是90。服务调用者模块可以通过RestTemplate类发送HTTP请求,调用8001为端口的模块的业务逻辑服务。8001端口的模块和90端口的模块,都属于EurekaClient,目前都已经做好了注册进7001端口的EurekaServer的配置。
2、本篇博客的目的
我的上一篇博客中,创建好了EurekaServer的集群。同样是在这个专栏,我有一篇博客粗略的陈述了我对建设集群的看法。简单的来说,建设集群就是为了实现高可用。既然EurekaServer可以集群化,EurekaClient当然也是可以集群化的。本篇博客就再创建一个以8002为端口的服务提供者模块,跟8001为端口的服务提供者模块一起组成一个小集群。
3、新增一个8001为端口的服务提供者微服务模块,展示一下这个模块的文件结构。
由于是一个服务提供者的模块,是一个集群的组成部分,目的是为了提高可用性,实现高可用。因此这个模块的代码和文件结构,甚至是GetMapping注解的value值,跟8001端口模块的都是一样的。一定程度上来说,8001端口模块和这个8002端口模块,二者就几乎是一样的。
4、简单解释一下这个模块的代码
由于这个模块是服务提供者的模块,是含有业务逻辑代码的,因此它就肯定是有数据库的相关配置的。
首先就是mapper.xml文件,这个文件夹放在了resources下面,因此在application文件中是需要配置一下的。由于我使用的是MyBatis框架,因此这个文件就是将我们dao层接口的方法跟SQL语句,还有公共模块的实体类做一下关联。
再向上,就是Service层,首先是一个接口,这个接口还是两个方法。然后还有就是一个实现类,这个实现类就是需要dao层接口的注入,使用@Resource注解,直接返回的就是dao层的结果,当然需要传入必要的形参。
再向上一层,就是controller层。这一层是真正的业务逻辑层。它需要service层的注入,注入实现类。然后调用方法,得到返回结果。根据返回结果的不同,再作出相应的判断。由于我们使用的是前后端分离的设计架构,因此作出的相应的判断,就是返回一个JSON串,一个通用返回结果的类的JSON串,当然需要是一个泛型类。
最后就是我们的重要的配置文件,application文件配置一下数据库,Eureka,端口,MyBatis的mapper文件。还有是我们的POM文件,引入必要的依赖。
由于我在前面的博客中,已经详细的讲述了8001为端口的模块的代码,二者代码几乎就是一样的,这里我就不详细记录了,我直接把代码粘贴到下面来了。
mapper.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属性 强调的是我们需要映射那一个dao接口-->
<mapper namespace="com.springcloud.dao.PaymentDao">
<insert id="create" parameterType="Payment" useGeneratedKeys="true" keyProperty="id">
insert into payment(serial) values(#{serial});
</insert>
<resultMap id="BaseResultMap" type="com.springcloud.entities.Payment">
<id column="id" property="id" jdbcType="BIGINT"></id>
<id column="serial" property="serial" jdbcType="VARCHAR"></id>
</resultMap>
<select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
select * from payment where id=#{id};
</select>
</mapper>
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>cloud2023</artifactId>
<groupId>com.lanse.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-provider-payment8002</artifactId>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--下面的这个依赖是引入了自己的那个模块新建成的jar包,就是把通用的entities提出来了-->
<dependency>
<groupId>com.lanse.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<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>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--加了下面这个依赖以后,application文件就可以正常识别了-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.4</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.4</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
dao层代码
package com.springcloud.dao;
import com.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
//dao层次就是写增删改查的方法
//@Mapper
//上面的这个注解相当于 @Repository,好像是说比这个要更好
@Mapper
public interface PaymentDao {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
service层接口和实现类
package com.springcloud.service;
import com.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.springcloud.service.impl;
import com.springcloud.dao.PaymentDao;
import com.springcloud.entities.Payment;
import com.springcloud.service.PaymentService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class PaymentServiceImpl implements PaymentService {
@Resource
//@Autowired
//上面这个注解的意思,还需要再认真的看一看
//就是类似于@Autowired
private PaymentDao paymentDao;
@Override
public int create(Payment payment) {
return paymentDao.create(payment);
}
@Override
public Payment getPaymentById(Long id) {
return paymentDao.getPaymentById(id);
}
}
controller层代码
package com.springcloud.controller;
import com.springcloud.entities.CommonResult;
import com.springcloud.entities.Payment;
import com.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;
@GetMapping("/payment/get/{id}")
@ResponseBody
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
if (payment!=null){
return new CommonResult(200,"查询数据成功,port:"+serverPort,payment);
}else{
return new CommonResult(500,"查询数据是空",null);
}
}
@PostMapping("/payment/create")
public CommonResult create(@RequestBody Payment payment){
int result = paymentService.create(payment);
// log.info("插入成功"+result);
if (result>0){
return new CommonResult(200,"插入数据成功,port"+serverPort,result);
}else{
return new CommonResult(500,"插入数据失败",null);
}
}
@GetMapping("/payment/lb")
public String getPaymentLb(){
return serverPort;
}
}
5、单独看一下application文件
application文件的代码如下所示:
server:
port: 8002
spring:
application:
name: cloud-payment-service
#上面这个是微服务的名字,是不能随便改变的,尽量不要随便改变。作为EurekaClient是需要到EurekaServer去注册的
#注册的时候,就是要用到这个名字的
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Hongkong
username: root
password: 123456
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.springcloud.entities
eureka:
client:
register-with-eureka: true #这个就表示是否默认注册到EurekaServer,当然就是true啦
fetch-registry: true #这个表示是否从EurekaServer抓取已有的注册信息,默认是true。
#其实上面的这个属性单节点是无所谓的,但是集群就必须设置为true,这样才能配合ribbon使用负载均衡
service-url:
defaultZone: http://eureka7001.com:7001/eureka ,http://eureka7002.com:7002/eureka
instance:
instance-id: payment8002 #这个会使得访问EurekaServer的时候,Status一栏,就会显示它
prefer-ip-address: true #这个就是会使得访问的时候显示IP地址,更加的方便一些
# service-url:
# defaultzone: http://localhost:7001/eureka
# service-url:
# defaultZone: http://localhost:7001/eureka
首先需要配置的就是端口号,我按照事先的约定,配置了8002
然后配置了 spring.application.name 这里我想重点说一下:现在配置的是EurekaClient服务提供者的集群,它的每一个组成部分,都是要注册进EurekaServer中去的。但是呢,作为一个集群,将来肯定是以集体的面目呈现在系统中的,那么怎样才是集体呢?最直接的方式就是都有一样的名字。这就是spring.application.name属性的作用,它就像是连锁店的招牌一样,虽然店的地理位置不一样,但是招牌都是一样的,无论去哪一家店,提供的服务都是一样的。
再往下就是配置数据库了,这里我就不多说了。
再往下,就是需要配置一下mapper文件和我们公共模块的实体类。
再往下的两个属性,注册到注册中心啊,抓取注册表啊,都可以设置为ture
再外下,是一个 service-url.defaultZone属性,这个属性我们把两个注册中心都写了上去。其实是可以只写一个的,因为EurekaServer集群是会每隔一段时间互相更新服务注册表的。但是都写上去明显是更保险的。注意一下,其中的 eureka7001.com和eureka7002.com,都是EurekaServer的hostname属性的值。
再往下,有一个 instance-id属性。想象一下,即使是连锁店,即使招牌是一样的,但是每一家店总归是有编号的吧,总归是可以区分的吧,总归在总公司的名单上是可以区分的吧。这个instance-id属性,就是使得注册进EurekaServer以后,每台EurekaClient依然有唯一的id,是可以区分的。
6、我们一次启动EurekaServer的两台机器:7001端口模块,7002端口模块;再启动EurekaClient服务提供者的两台机器:8001端口模块,8002端口模块;再启动EurekaClient服务调用者(消费者)90端口模块
到此,项目的8002端口的EurekaClient服务提供者模块已经创建完毕了,并且所有的配置也是都已经到位了。
按照顺序,依次启动服务。启动后的截图如下所示:
7、访问7001端口和7002端口的EurekaServer的浏览器页面,观察注册结果
明显的,可以从注册结果中看到,它们两个是相互守望者的了,就像是有一条网线相连一样。
并且,所有的微服务模块,也都在它们两个的服务注册表上有了。
8、提醒一下现在的 90端口
由于我们现在已经将服务提供者端改造成了集群的形式,它们对外有一个统一的 招牌。这个 招牌
就是 它们application文件中的 spring.application.name属性的值。
因此,我们是需要修改 90端口的服务消费者的controller层的RestTemplate类发送的HTTP请求的内容的, 就是将原来的写死的: http://localhost:8001 修改为 http://CLOUD-PAYMENT-SERVICE 跟服务提供者模块的application文件中的spring.application.name属性的值保持一致。
9、总结
目前已经完成了集群的改造。一切都是可以正常运行的,这里我就不多展示了。
大家有没有一个疑问,当我们通过90端口的服务消费者模块,调用服务提供者模块的业务逻辑代码的时候,内部究竟会去8001端口还是8002端口呢?
这里就牵扯到一个问题了: 负载均衡。 计算机,只是一个机器,绝对不会说是随心执行指令的。即使是随机,它的内部也是有产生随机码的函数的。 负载均衡,就是说根据一定的规则,来决定究竟调用8001端口的模块还是8002端口的模块。
提前剧透一下,默认的 负载均衡原则是 轮询。Eureka内部是已经集成了的。可以在Maven里面,看到下面的依赖包含的关系。
我后面的博客会记录一下使用ZooKeeper作为服务注册中心的代码:
初学SpringCloud:使用ZooKeeper作为服务注册中心,搭建单机版ZooKeeper服务注册中心 之 在CentOS下安装JDK