微服务环境搭建

  • 一、案例准备
  • 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】

Spring Security 微服务 spring微服务搭建_Spring Security 微服务

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中:

Spring Security 微服务 spring微服务搭建_spring_02

3.2 shio-common 公共模块

Spring Security 微服务 spring微服务搭建_mysql_03

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服务:

Spring Security 微服务 spring微服务搭建_spring cloud_04

启动完第二个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所支持的负载均衡的策略:

Spring Security 微服务 spring微服务搭建_mysql_05


可以通过改配置文件的方式来更改负载均衡的策略:

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 "下单成功";
    }
}