这一节开始,我们将通过代码逐步解决我们上一节中关于分布式和集群的几个问题!实际的编码会让我们对SpringCloud、微服务、分布式、集群了解的更深刻。
一、父子项目
接下来就要开始做 springcloud 项目了。 springcloud 比较特别,它由多个微服务组成, 所谓的微服务,就是 springboot,。
所以可以说 springcloud 由多个 springboot 项目组成, 而这些 springboot 之间又是围绕一个共同目的而存在的。
所以,为了便于组织这些 springboot 项目,我们会采用 maven 父子-聚合 项目的方式来开发。下面 我们来看一下如何使用IDEA创建maven父子聚合项目。
通过 maven 可以创建父子-聚合项目。 所谓的父子项目,即有一个父项目,有多个子项目。
这些子项目,在业务逻辑上,都归纳在这个父项目下,并且一般来说,都会有重复的jar包共享。
所以常用的做法会把重复的 jar 包都放在父项目下进行依赖,那么子项目就无需再去依赖这些重复的 jar 包了。
1、新建一个父maven项目
这里我们给它命名位父maven项目。
2、修改pom文件
idea 自动生成的 pom.xml 有一大堆东西,很多都用不着。 修改为如下的内容。
2.1. 默认是 jar, 修改为pom。 这样才可以作为父项目存在。
2.2. 增加 hutool jar 和 junit 包的依赖,用于后来子项目里观察对其的调用。
pom文件代码如下:
4.0.0
com.topmap
parentMavenProject
1.0-SNAPSHOT
parentMavenProject
parentMavenProject
pom
junit
junit
4.11
test
cn.hutool
hutool-all
4.3.1
3、创建一个子项目
所谓的子项目,其实是maven module.
右键点击 parentMavenProject->New->Module.
这样我们就在父maven项目下面新建了子maven项目。
子项目的pom文件中多了一个
父maven项目多了一个
下面我们来测试一下子项目是否已经跟父项目取得关联(这里在子项目中使用父项目依赖的jar包进行测试)
在 childMavenProject 下新建 TestHutool类,并运行。
这里我们在子项目中使用了父项目中调价的依赖中的方法,并且运行成功了!这说明子项目,能够使用 父项目中的 jar 包了。
4、父子项目整体架构
这就是我们的整体目录结构,可以发现 childMavenProject 是位于 parentMavenProject下面的。 所以如果将来有 childMavenProject1, childMavenProject2, childMavenProject3 也会放在这么一个目录下,就方便管理了。
二、服务注册中心
下面我们根据上面新建父子项目的流程,再新建父项目---springcloud,子项目---eureka-server,这里eureka-server就是服务注册中心。
上面的父子项目创建好了之后把父项目springcloud的 src 目录删了,因为父项目里用不到,看一下父项目的pom:
4.0.0
com.topmap
springcloud
0.0.1-SNAPSHOT
springcloud
springcloud
pom
org.springframework.boot
spring-boot-starter-parent
2.0.3.RELEASE
UTF-8
UTF-8
1.8
Finchley.RELEASE
org.springframework.boot
spring-boot-starter-test
test
cn.hutool
hutool-all
4.3.1
org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import
这个pom里有几点重要信息:
1. 依赖 springboot 版本是 2.0.3
2. 有基于 hutool 的依赖, hutool 是一个工具类,用起来很方便
3. springcloud 用的版本是 Finchley
再新建子项目 eureka-server
再来看一下子项目的pom文件:
springcloud
com.topmap
0.0.1-SNAPSHOT
4.0.0
eureka-server
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
这里添加了springcloud的spring-cloud-starter-netflix-eureka-server jar 包,就是用来做服务注册的一个jar包。
然后我们新建一个EurekaServer 启动类。
EurekaServer ,它扮演的角色是注册中心,用于注册各种微服务,以便于其他微服务找到和访问。
EurekaServer 本身就是个 Springboot 微服务, 所以它有 @SpringBootApplication 注解。
@EnableEurekaServer 表示这是个 EurekaServer 。
启动的时候,端口号没有在配置文件里,而是直接放在代码里,这么做是为了提示同学这个端口号是否被占用了,否则有时候端口号被占用了,老是启动不了,搞得自己晕头转向的。
NetUtil 是 Hutool 的工具,在父项目的 pom.xml 里已经依赖了。
最后我们来配置yml文件,提供 eureka 的相关信息。
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
spring:
application:
name: eureka-server
来解释一下上面的信息:
hostname: localhost 表示主机名称。
registerWithEureka:false. 表示是否注册到服务器。 因为它本身就是服务器,所以就无需把自己注册到服务器了。
fetchRegistry: false. 表示是否获取服务器的注册信息,和上面同理,这里也设置为 false。
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 自己作为服务器,公布出来的地址。 比如后续某个微服务要把自己注册到 eureka server, 那么就要使用这个地址: http://localhost:8761/eureka/
name: eurka-server 表示这个微服务本身的名称是 eureka-server
最后启动EurekaServerApplication:
然后访问:http://127.0.0.1:8761/
这就是注册中心的管理界面,主要看 :Instances currently registered with Eureka, 可以发现信息是:No instances available。
这表示 暂时还没有微服务注册进来。
下面我们会进行微服务的注册!
三、注册数据微服务
创建子项目 product-data-service。
修改 pom.xml 为如下:
spring-cloud-starter-netflix-eureka-client 表示这是个 eureka 客户端。
spring-boot-starter-web: 表示这是个web服务,会提供控制层
springcloud
com.topmap
1.0-SNAPSHOT
4.0.0
product-data-service
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-web
然后我们编写product的实体类:
package com.topmap.pojo;
/**
* @Author: MaHuadong
* @Date: 2019/12/30 15:22
* @Version 1.0
*/
public class Product {
private int id;
private String name;
private int price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Product(){
}
public Product(int id,String name,int price){
super();
this.id=id;
this.name = name;
this.price = price;
}
}
再新建service:
service类提供一个 Product 集合。
package com.topmap.service;
import com.topmap.pojo.Product;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: MaHuadong
* @Date: 2019/12/30 15:39
* @Version 1.0
*/
@Service
public class ProductService {
@Value("${server.port}")
String port;
public List listProducts(){
List ps = new ArrayList<>();
ps.add(new Product(1,"product a from port:"+port, 50));
ps.add(new Product(2,"product b from port:"+port, 150));
ps.add(new Product(3,"product c from port:"+port, 250));
return ps;
}
}
需要注意的是,这里把 端口号 放进了产品信息里。 这个数据服务会做成集群,那么访问者为了分辨到底是从哪个数据微服务取的数据,就需要提供个端口号,才能意识到是从不同的微服务得到的数据。
再新建Controller:
控制类,把 Product 集合转换成 json 数组。
下面我们再来写这个子项目product-data-service的启动类:
启动类, 考虑到要做集群。 所以让用户自己输入端口,推荐 8001,8002,8003.
但是每次测试都要输入端口号又很麻烦,所以做了个 Future类,如果5秒不输入,那么就默认使用 8001端口。
package com.topmap;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.NetUtil;
import cn.hutool.core.util.NumberUtil;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import java.util.Scanner;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @Author: MaHuadong
* @Date: 2019/12/30 15:54
* @Version 1.0
*/
@SpringBootApplication
//代表这是一个Eureka客户端
@EnableEurekaClient
public class ProductDataServiceApplication {
public static void main(String[] args){
int port=0;
int defaultPort=8001;
Future future= ThreadUtil.execAsync(() ->{
int p=0;
System.out.println("请于5秒钟内输入端口号, 推荐 8001 、 8002 或者 8003,超过5秒将默认使用 " + defaultPort);
Scanner scanner=new Scanner(System.in);
while(true){
String strPort=scanner.nextLine();
if (!NumberUtil.isInteger(strPort)){
System.err.println("只能是数字");
continue;
}else {
p = Convert.toInt(strPort);
scanner.close();
break;
}
}
return p;
});
try {
port=future.get(5, TimeUnit.SECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException e){
port = defaultPort;
}
if (!NetUtil.isUsableLocalPort(port)){
System.err.printf("端口%d被占用了,无法启动%n", port );
System.exit(1);
}
new SpringApplicationBuilder(ProductDataServiceApplication.class).properties("server.port=" + port).run(args);
}
}
最后我们来配置子项目product-data-service的配置文件yml:
# server:
# port: 因为会启动多个 product-data-service, 所以端口号由用户自动设置,推荐 8001,8002,8003
spring:
application:
# 微服务的名称
name: product-data-service
eureka:
client:
serviceUrl:
# 注册中心的地址
defaultZone: http://localhost:8761/eureka/
好了,我们现在父子项目都写好了,即微服务的注册中心和客户端应用都写好了。
现在再来看一下http://localhost:8761/中的Instances currently registered with Eureka:
仍然还未有微服务进行注册,因为我们的微服务 product-data-service还未启动,下面我们进行启动!
启动两次 ProductDataServiceApplication, 分别输入 8001和8002.
可以在注册中心 http://127.0.0.1:8761/ 看到, product-data-service 这个微服务,有两个实例,分别是8001和8002端口。
在上面给了两行红色警示,下面我们设置一下去掉它:
把注册中心重新启动一下或者在配置里把自我保护模式关了
|
这样就不会再有那个红色提醒了。
可以如此访问: http://127.0.0.1:8001/products ,http://127.0.0.1:8002/products,并看到如图所示的数据。
但是这种方式是通过 http 协议 访问微服务本身,和注册中心没有关系,也观察不到集群的效果,接下来我们就会讲如何用微服务,访问另一个微服务。
这一节就先到这儿!