微服务环境搭建
- 一、案例准备
- 1、技术选型
- 2、模块设计
- 3、模块开发
- 3.1 spring-cloud-demo1 父模块
- 3.2 shio-common 公共模块
- 3.3 shop-product 商品模块(主要是商品信息的操作)
- 3.4 shop-order 商品订单模块
- 二、Nacos
- 使用nacos
- 三、实现服务调用的负载均衡
- 3.1搭建集群
- 3.2负载均衡操作
- 3.2.1第一种:生成随机数(不推荐)
- 3.2.2第二种:基于Ribbon实现负载均衡(也不推荐)
- 3.2.3第三种:基于openFeign实现服务调用
一、案例准备
本次模拟电商项目中的商品、订单为案例
1、技术选型
maven:3.5、
数据库:MySql5.6以上、
持久层:Mybatis-plus
其他:SpringCloud Alibaba技术栈
2、模块设计
spring-cloud-demo1 父工程 (jar版本的管理)
shop-common 公共模块【实体类】 《实体类,公共依赖,工具类》
shop-product 商品微服务 【端口:8080~8089】
shop-order 订单微服务【端口:8090~8099】
3、模块开发
注:在每个模块中导入依赖jar包时,都要考虑清楚jar包在模块中的传递关系,不要跨界。比如数据库驱动包,并不是每个模块都需要,并且如果模块中有该依赖,而没有连接数据库,就会报错
3.1 spring-cloud-demo1 父模块
创建一个maven项目,因为父模块中不用写代码,直接将src文件夹给删了
pom.xml 主要进行jar版本的管理
<!-- 引入springboot的父依赖-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.2.RELEASE</version>
</parent>
<!-- 定义版本号-->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- spingcloud的版本号-->
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<!-- springcloud-alibaba的版本号-->
<spring-cloud-alibaba.version>2.2.5.RELEASE</spring-cloud-alibaba.version>
</properties>
<!-- dependencyManagement标签中的依赖不会传递给子项目,如果子项目需要,需要在项目中声明-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
每创建一个子项目,都会自动添加到父工程的pom中:
3.2 shio-common 公共模块
pom.xml
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
3.3 shop-product 商品模块(主要是商品信息的操作)
实现一个根据商品id查找商品信息的功能
1、pom.xml
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>shop-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2、dao层 ProductDao
public interface ProductDao extends BaseMapper<Product> {
}
3、service层
3.1ProductService
public interface ProductService {
Product findById(Integer pid);
}
3.2ProductServiceImpl
@Service
public class ProductServiceimpl implements ProductService {
@Resource
private ProductDao productDao;
/* *
* 根据产品id查找商品
* @param pid
* @return com.fy.entity.Product
* @date 2021/5/24 19:23
*/
public Product findById(Integer pid){
Product product = productDao.selectById(pid);
return product;
}
}
4、Controller层 ProductController.java
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
private ProductService productServiceimpl;
@GetMapping("/ById/{pid}")
public Product selectById(@PathVariable Integer pid){
return productServiceimpl.findById(pid);
}
}
5、application.properties
server.port=8081
#给该微服务起名字
spring.application.name=shop-product
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springcloud?serverTimezone=Asia/Shanghai&characterEncoding=utf8
spring.datasource.password=root
spring.datasource.username=root
mybatis-plus.mapper-locations=classpath:mapper/*.xml
logging.level.com.fy.dao=debug
3.4 shop-order 商品订单模块
实现一个下订单的功能,传递商品编号和订单的商品数量,根据商品编号,调用shop-product模块
1、pom.xml
<dependencies>
<dependency>
<groupId>org.example</groupId>
<artifactId>shop-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2、dao层 OrderDao
public interface OrderDao extends BaseMapper<Order> {
}
3、service层
3.1OrderService
public interface OrderService {
Boolean insertInfo(Order order);
}
3.2OrderServiceImpl
@Service
public class OrderServiceImpl implements OrderService {
@Resource
private OrderDao orderDao;
@Override
public Boolean insertInfo(Order order) {
return orderDao.insert(order)>0;
}
}
4、application.properties
server.port=8091
spring.application.name=shop-order
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/springcloud?serverTimezone=Asia/Shanghai&characterEncoding=utf8
spring.datasource.password=root
spring.datasource.username=root
mybatis-plus.mapper-locations=classpath:mapper/*.xml
logging.level.com.fy.dao=debug
5、controller层 OrderController.java
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderServiceimpl;
@Resource
private RestTemplate restTemplate;
@GetMapping("/inserInfo")
public String insertInfo(Integer pid,Integer num){
Order order = new Order();
// oid为自增,不用设置
order.setUid(1);//用户id
order.setPid(pid);//商品id
order.setNumber(num);//订单数量
order.setUsername("fengyun");
/* 现在需要根据pid找到商品名字和商品价格,需要用到shop-product服务中的方法
* 现在项目是在一起的,但是实际应用中,项目是在不同的服务器上运行的,需要远程连接,如何连接?
* ①基于http协议,Restful风格访问,用来访问微服务
* ②基于TCP协议 RPC 适合用来访问soa项目(阿里的)
*
* 我们使用第一种,spring提供了工具类,RestTemplate
* */
Product product = restTemplate.getForObject("http://localhost:8081/product/ById/" + pid, Product.class);
if (product!=null){
order.setPname(product.getPname());
order.setPprice(product.getPprice());
}else {
throw new RuntimeException("商品已下架");
}
orderServiceimpl.insertInfo(order);
return "下单成功";
}
}
6、启动类
@SpringBootApplication
@MapperScan(basePackages = {"com.fy.dao"})
@EnableFeignClients
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class,args);
}
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
发现问题:
在controller中,在调用shop-product模块时,我们直接写的是目标模块的ip地址,用的硬编码,当提供服务的模块ip改变了,就要调用者手动再改ip,明显
不合适。如果能够让调用者根据一个名字或者标识去调用目标模块,就很好。
解决以上问题,我们使用nacos
二、Nacos
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理的平台。是Spring Cloud Alibaba 组件之一,负责服务注册发现和服务配置。
简单说,nacos就是一个注册中心,用来管理注册上来的各个微服务。
使用nacos
1、下载nacos
https://nacos.io/zh-cn/docs/quick-start.html
1、其中1.3.2以下的默认不开启集群,以上的默认即可配置集群。(不是说它下载下来就有集群,还需要自己去打开多个nacos,不过不用手动打开集群禁止)
2、解压就可用
启动:bin文件夹下的startup.cmd
3、nacos可以修改端口号 默认为88487
修改端口号:conf文件夹下的application.properties
4、访问nacos
可以通过浏览器访问nacos:
http://localhost:8848/nacos
2、导入nacos的依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3、修改application.properties
将nacos的服务路径配置到服务中,每一个微服务都要进行配置,common模块就不用了
spring.cloud.nacos.discovery.server-addr=localhost:8848
4、修改OrderController
4.1注入nacos的方法调用对象
@Autowired
private DiscoveryClient discoveryClient;
4.2 使用discoveryClient来获取nacos中注册的服务
// getInstances()中写要获取的服务名,该名字是在每个服务的配置文件中配置的
List<ServiceInstance> instances = discoveryClient.getInstances("shop-product");
//现在就一个shop-product服务,后边要进行集群的搭建
ServiceInstance instance = instances.get(0);
//获取服务的ip+端口号
URI uri = instance.getUri();
//调用目标服务
Product product = restTemplate.getForObject(uri+"/product/ById/" + pid, Product.class);
三、实现服务调用的负载均衡
3.1搭建集群
使用shop-product模块来搭建集群。我们在一台电脑上模拟,再打开一个shop-product服务:
启动完第二个shop-product项目,就会注册到nacos中,我们现在就要让我们的请求去分摊到这两个shop-product项目上,三种方法:
3.2负载均衡操作
3.2.1第一种:生成随机数(不推荐)
从nacos中再根据服务名shop-product取服务,现在就可以取出来两个,开启更多的服务,取出来的会更过,所以我们可以写一个随机数,随机选择一个服务:
//手动实现负载均衡
//生成一个随机数,用来随机拿出instances中的值
int index=(int)(Math.random())*instances.size();
ServiceInstance instance = instances.get(index);
URI uri = instance.getUri();//获取服务的ip+端口号
//调用目标服务
Product product = restTemplate.getForObject(uri+"/product/ById/" + pid, Product.class);
3.2.2第二种:基于Ribbon实现负载均衡(也不推荐)
1、什么是Ribbon?
是 Netflix 发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中, nacos一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功
能,Ribbon利用从nacos中读 取到的服务信息,在调用服务节点提供的服务时,会合理(策略)的进行负载。 在SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自
动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务。
2、Ribbon的主要作用
(1)服务调用
基于Ribbon实现服务调用, 是通过拉取到的所有服务列表组成(服务名-请求路径的)映射关系。借助 RestTemplate 最终进行调用
(2)负载均衡
当有多个服务提供者时,Ribbon可以根据负载均衡的算法自动的选择需要调用的服务地址
(3)实现负载均衡
Ribbon是Spring Cloud的一个组件, 它可以让我们使用一个注解就能轻松的搞定负载均衡
第一步:在RestTemplate的生成方法上添加@LoadBalanced注解
@Bean
@LoadBalanced //负载均衡,默认使用轮询策略
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
第二步:更改调用shop-product处的方法 OrderController类中
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderServiceimpl;
@Resource
private RestTemplate restTemplate;
@GetMapping("/inserInfo")
public String insertInfo(Integer pid,Integer num){
Order order = new Order();
// oid为自增,不用设置
order.setUid(1);//用户id
order.setPid(pid);//商品id
order.setNumber(num);//订单数量
order.setUsername("fengyun");
// 使用ribbon实现负载均衡
Product product = restTemplate.getForObject("http://shop-product/product/ById/" + pid, Product.class);
if (product!=null){
order.setPname(product.getPname());
order.setPprice(product.getPprice());
}else {
throw new RuntimeException("商品已下架");
}
orderServiceimpl.insertInfo(order);
return "下单成功";
}
}
注
我们看getForObject方法中的请求路径的组成:http://+微服务的名称+业务路径,最大的改变就是不再获取服务的ip和port,直接以微服务的名称,ribbon会根据一定的策略,选择服务名相同的服务中的一个。
ribbon所支持的负载均衡的策略:
可以通过改配置文件的方式来更改负载均衡的策略:
shop-product: # 这里使用服务的名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #使用的的负载均衡策略
3.2.3第三种:基于openFeign实现服务调用
在使用ribbon做负载均衡时,其实已经很好了。但是有一个不舒服的地方,我们平常调用业务,都是调用方法名,穿参,执行,上边的写法不太符合我们的习惯,而且代码可读性差。此时openFeign就出现了。
1、什么是openFeign
Feign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务一样简单, 只需要创建一个接口并添加一个注解即可。
Nacos很好的兼容了Feign, Feign默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。
2、openFeign的使用
Ⅰ、加入openFeign的依赖
<!--feign的jar文件-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
Ⅱ、在主启动类上加入开启feign的注解
@SpringBootApplication
@MapperScan(basePackages = {"com.fy.dao"})
@EnableFeignClients
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class,args);
}
@Bean
@LoadBalanced //负载均衡,默认使用轮询策略
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
Ⅲ、创建openFeign的接口
接口内容就是要调用的微服务的controller层,不要方法体。
@FeignClient("shop-product")
public interface ProductFeign {
@GetMapping("/product/ById/{pid}")
public Product selectById(@PathVariable Integer pid);
}
@FeignClient()注解中写服务名
Ⅳ、改OrderController
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderServiceimpl;
@Autowired
private ProductFeign productFeign;
@GetMapping("/inserInfo")
public String insertInfo(Integer pid,Integer num){
Order order = new Order();
// oid为自增,不用设置
order.setUid(1);//用户id
order.setPid(pid);//商品id
order.setNumber(num);//订单数量
order.setUsername("fengyun");
Product product=productFeign.selectById(pid);
if (product!=null){
order.setPname(product.getPname());
order.setPprice(product.getPprice());
}else {
throw new RuntimeException("商品已下架");
}
orderServiceimpl.insertInfo(order);
return "下单成功";
}
}