本案例提供2020版本的最佳实践方案,使用我认为最容易学习的组件,可能很多组件有很多替代方案,在这里不依依讲述。本案例使用的组件如下:
本案例总体架构如下:

本案例包括2个模块,一个服务提供者provider 、服务消费者consumer。

在父模块(pom)添加依赖如下:
<properties>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-boot.version>2.4.4</spring-boot.version>
<spring-cloud.version>2020.0.2</spring-cloud.version>
<spring-cloud-alibaba.version>2020.0.RC1</spring-cloud-alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- spring boot 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring cloud 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring cloud alibaba 依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
服务提供者provider
在provider的pom文件引入依赖:
<!--springcloud依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置文件application.yml:
server:
#端口配置
port: 8762
#上下文
servlet:
context-path: /springbootdemo
#数据源配置
spring:
application:
name: provider
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
在config包下创建ServerConfig,实现一个getIp方法用来获取当前服务的IP地址和端口号。
@Configuration
public class ServerConfig implements ApplicationListener<WebServerInitializedEvent> {
private int serverPort;
//获取内置tomcat端口号
@Override
public void onApplicationEvent(WebServerInitializedEvent event) {
this.serverPort = event.getWebServer().getPort();
}
// 获取项目IP
public String getIp() {
InetAddress address = null;
try {
address = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
if(this.serverPort == 0){ // 获取外置tomcat端口号
this.serverPort = getTomcatPort();
}
return "http://"+address.getHostAddress() +":"+this.serverPort;
}
public int getTomcatPort() {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
try {
QueryExp protocol = Query.match(Query.attr("protocol"), Query.value("HTTP/1.1"));
ObjectName name = new ObjectName("*:type=Connector,*");
Set<ObjectName> objectNames = beanServer.queryNames(name, protocol);
for (ObjectName objectName : objectNames) {
String catalina = objectName.getDomain();
if ("Catalina".equals(catalina)) {
return Integer.parseInt(objectName.getKeyProperty("port"));
}
}
} catch (MalformedObjectNameException e) {
e.printStackTrace();
}
return 0;
}
}
在controller包下存在有以下测试接口。
@RestController
@RequestMapping("goods")
public class ItemsController {
@Resource
private GoodsService goodsService;
@Autowired
private ServerConfig serverConfig;
@GetMapping("query/{catalog}")
public Map<String,Object> queryAllGoodsListByCatalogName(@PathVariable("catalog") String catalogName){
Map<String, Object> result = new HashMap<String, Object>();
//执行这个接口的时候,请问大家,程序如何获得当前服务的ip地址和端口号。
try {
List<Goods> goodsList = goodsService.queryAllGoodsListByCatalog(catalogName);
result.put("code", 200);
result.put("msg", "查询所有商品列表成功!");
result.put("data", goodsList);
//用来测试负载均衡时,Consumer可以查询调用那个provider的服务。
result.put("host", serverConfig.getIp());
return result;
} catch (Exception ex) {
ex.printStackTrace();
result.put("code", 400);
result.put("msg", "程序出现错误!");
return result;
}
}
}
在provider启动类添加@EnableDiscoveryClient
@RestController
@EnableDiscoveryClient
@SpringBootApplication
@MapperScan("com.oracle.springbootdemo.mapper")
public class MallServerApplication {
public static void main(String[] args) {
SpringApplication.run(MallServerApplication.class, args);
//输出sa-token的基本配置
System.out.println("启动成功:sa-token配置如下:" + SaManager.getConfig());
}
}
服务消费者consumer
在pom文件引入以下依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
需要注意的是引入openfeign,必须要引入loadbalancer,否则无法启动。
配置文件:
server:
port: 8763
spring:
application:
name: consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
在consumer工程的启动类开启FeignClient的功能:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}
在repository包下写一个FeignClient,去调用provider服务的接口:
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(value = "provider" )
public interface ProviderClient {
@GetMapping("/springbootdemo/items/catalog")
String queryItemsListByCatalogName(@RequestParam(value = "catalogName", defaultValue = "all", required = true) String catalogName);
}
在controller包下写一个接口,让consumer去调用provider服务的接口:
@RestController
public class ConsumerController {
@Autowired
ProviderClient providerClient;
@GetMapping("/items/catalog")
public String itemsCatalogList(@RequestParam(value = "catalogName",required = true,defaultValue = "all") String catalogName){return providerClient.queryItemsListByCatalogName(catalogName);}
}
服务调用:
启动两个工程,在nacos页面查看,可见2个服务都已经注册成功:

在浏览器上输入http://localhost:8763/items/catalog?catalogName=食品,浏览器返回响应:
[{"version":0,"createTime":"2020-04-16 00:01:54","modifyTime":null,"flag":true,"id":97,"name":"康师傅红烧牛肉面","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_33.jpg","number":500,"catalog":"食品","city":"杭州","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":1,"freePost":false},{"version":0,"createTime":"2020-11-08 11:40:05","modifyTime":null,"flag":true,"id":102,"name":"统一老谭酸菜面","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_34.jpg","number":500,"catalog":"食品","city":"长沙","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":1,"freePost":false},{"version":0,"createTime":"2020-11-09 15:37:12","modifyTime":null,"flag":true,"id":103,"name":"康师傅西红柿牛腩面","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_33.jpg","number":500,"catalog":"食品","city":"杭州","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":1,"freePost":false},{"version":0,"createTime":"2020-11-09 16:26:40","modifyTime":null,"flag":true,"id":104,"name":"白象方便面","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_35.jpg","number":500,"catalog":"食品","city":"郑州","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":1,"freePost":false},{"version":0,"createTime":"2020-11-17 17:19:56","modifyTime":null,"flag":true,"id":105,"name":"韩国火鸡方便面","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_36.jpg","number":500,"catalog":"食品","city":"沈阳","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":1,"freePost":false},{"version":0,"createTime":"2020-11-17 17:19:58","modifyTime":null,"flag":true,"id":106,"name":"农心辛拉面","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_37.jpg","number":500,"catalog":"食品","city":"沈阳","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":1,"freePost":false},{"version":0,"createTime":"2020-11-17 17:19:59","modifyTime":null,"flag":true,"id":107,"name":"太阳牌锅巴","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_38.jpg","number":500,"catalog":"食品","city":"西安","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":1,"freePost":false},{"version":0,"createTime":"2021-02-05 09:25:41","modifyTime":"2021-02-05 09:25:41","flag":true,"id":111,"name":"诸葛卧龙手工锅巴","price":10,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_42.jpg","number":0,"catalog":"食品","city":"武汉","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-02-05 10:34:32","modifyTime":"2021-02-05 10:34:32","flag":true,"id":112,"name":"百吉猫手工锅巴","price":12,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_43.jpg","number":0,"catalog":"食品","city":"西安","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-02-05 10:34:34","modifyTime":"2021-02-05 10:34:34","flag":true,"id":113,"name":"北京牌方便面","price":1,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_44.jpg","number":0,"catalog":"食品","city":"武汉","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-03-25 11:25:35","modifyTime":"2021-03-25 11:25:35","flag":true,"id":114,"name":"老襄阳手工锅巴","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_45.jpg","number":0,"catalog":"食品","city":"杭州","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-03-25 11:27:32","modifyTime":"2021-03-25 11:27:32","flag":true,"id":115,"name":"蒙牛纯牛奶","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_46.jpg","number":0,"catalog":"食品","city":"杭州","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-03-25 11:27:32","modifyTime":"2021-03-25 11:27:32","flag":true,"id":116,"name":"夏进纯牛奶","price":3,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_47.jpg","number":0,"catalog":"食品","city":"杭州","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-03-25 11:32:29","modifyTime":"2021-03-25 11:32:29","flag":true,"id":117,"name":"完达山纯牛奶","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_48.jpg","number":0,"catalog":"食品","city":"杭州","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-03-25 11:33:29","modifyTime":"2021-03-25 11:33:29","flag":true,"id":118,"name":"君乐宝纯牛奶","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_49.jpg","number":0,"catalog":"食品","city":"石家庄","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-04-01 14:28:20","modifyTime":"2021-04-01 14:28:20","flag":true,"id":119,"name":"伊利纯牛奶","price":4,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_50.jpg","number":0,"catalog":"食品","city":"杭州","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-04-01 14:28:55","modifyTime":"2021-04-01 14:28:55","flag":true,"id":120,"name":"澳大利进口纯牛奶","price":12,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_51.jpg","number":0,"catalog":"食品","city":"杭州","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-04-01 14:33:50","modifyTime":"2021-04-01 14:33:50","flag":true,"id":121,"name":"徐福记沙琪玛","price":16,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_52.jpg","number":0,"catalog":"食品","city":"杭州","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-04-01 14:35:21","modifyTime":"2021-04-01 14:35:21","flag":true,"id":122,"name":"三只松鼠沙琪玛","price":15,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_53.jpg","number":0,"catalog":"食品","city":"南京","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false},{"version":0,"createTime":"2021-04-06 20:09:07","modifyTime":"2021-04-06 20:09:07","flag":true,"id":123,"name":"稻香村沙琪玛","price":20,"pic":"http://114.67.88.176:8080/examdemo/images/api/goodList/pics_small/good_54.jpg","number":0,"catalog":"食品","city":"北京","property":null,"province":null,"shopId":0,"shopName":null,"discount":0.0,"buyCount":0,"status":0,"freePost":false}]
测试负载均衡:
其实feign使用了spring cloud loadbanlancer作为负载均衡器。 可以通过修改provider的端口,再在本地启动一个新的provider服务,那么本地有2个provider 服务,端口分别为8761 和8762。在浏览器上多次调用http://localhost:8763/items/catalog?catalogName=食品 接口,可以获得不同provider返回的数据。
idea2022.3 运行启动多个springboot实例配置如下:
