SpringCloud入坑记-Ribbon初体验

在这里插入图片描述

前言

基于对Eureka的认识,可以很容易地建立一个独立的服务了,但是服务间还不能相互调用,这一节将着重解决这个问题。

创建项目

扩展商品服务

创建一个项目ms-c1-product-service,其中Eureka的配置部分和之前章节一样,接下来重点添加其他部分代码。

添加实体 Product

package com.chao.entity;

import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
    private Integer id;
    private String name;
    private double price;
}

这里给出了全部代码,为了简化其中的setter/getter,我使用了一个工具’lombok’。

关于lombok

我是在一个开源项目中接触到的lombok,这个工具在编写Java实体对象的时候能节省不少代码。通过注解的形式可以方便地自动生成setter/getter/toString等等方法,这些方法显得很冗长也没有太多技术含量。

lombok使用起来很方法,花几分钟就能上手,对于练习项目再好不过。
不过,在软件开发领域,一般遵循"约定大于配置",在项目合作中,建议不要轻易使用这个工具,否则不了解lombok的同事拿到你的代码将是厄运的开始。

在项目中使用lombok,需要做两件事情:

  1. 安装插件:如果是首次使用,请在IDE中安装lombok插件,官网(https://projectlombok.org/)包含各种常用编辑器的插件。当然,这是个一劳永逸的步骤。
  2. 添加依赖:在pom.xml中添加lombok的依赖,请注意版本号,Spring-boot已经可以管理lombok的版本。
<dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
       <scope>provided</scope>
</dependency>

添加业务模块ProductService

@Service
public class ProductService {

    private List<Product> products = Arrays.asList(new Product(0,"phone", 4999),new Product(1,"glass", 23.5), new Product(2, "clothes", 234.99));

    public Product getById(Integer id){
        return products.get(id);
    }

    public List<Product> getAll(){
        return products;
    }
}

这里模拟数据采用的是内存存储的方式(List),两个方法分别通过id拿单条数据和取出所有数据。

添加外部访问接口ProductController

@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping("/{id}")
    public Product findOne(@PathVariable Integer id){
        return productService.getById(id);
    }

    @GetMapping("/list")
    public List<Product> all(){
        return productService.getAll();
    }
    
     @GetMapping("/ping")
    public String hello(HttpServletRequest request){
        return "Hello, message from "+ request.getServerName()+ ":"+ request.getServerPort();
    }

}

代码逻辑很简单,暴露出三个Rest接口http://ip:port:product/{id},占位符{id}取值0、1、2,还有一个接口http://ip:port:product/list,获取所有商品信息,http://ip:port:product/ping可以测试连通性。

至此,商品服务的代码已经完成,项目的结果如下:
在这里插入图片描述
启动项目后,在浏览器中访问http://localhost:10001/product/1,返回如下数据:
{"id":1,"name":"glass","price":23.5}

访问http://localhost:10001/product/ping,返回数据:
Hello, message from localhost:10001

创建订单服务

新建项目模块ms-c1-order-ribbon,其中Eureka最基本的配置和之前的项目一致。

创建订单实体类Order

@Data
@AllArgsConstructor
public class Order {
    /**
     * 订单ID
     */
    private String id;
    /**
     * 订单中的商品,假设一个订单中只能包含一种商品
     */
    private Product item;
    /**
     * 订单中商品的数量
     */
    private Integer amount;
}

商品实体类Product的代码和上一个项目完全一样。

由于需要调用商品服务,利用RestTemplate来做服务间调用。

	@Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }

为什么之前的项目没有用到过?在单体应用时代,服务可以直接通过代码调用。比如@Autowired某个service,然后直接调用其中的方法。微服务时代需要服务拆分,服务间属于不同的项目,通过Restfule接口调用。

在订单服务中,需要调用商品服务OrderServiceA

@Service
public class OrderServiceA {

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 生成一个订单
     * @param id
     * @param amount
     */
    public Order create(Integer id, Integer amount){
        String url = "http://localhost:10001/product/"+id;
        Product p = restTemplate.getForObject(url, Product.class);
        return new Order(UUID.randomUUID().toString(), p, amount);
    }

    /**
     * 查看所有可以购买的商品
     */
    public List<Product> findAll(){
        String url = "http://localhost:10001/product/list";
        Product[] p = restTemplate.getForObject(url, Product[].class);
        return Arrays.asList(p);
    }
}
    /**
     * 为了测试是否可以和product-service链接
     */
    public String hello() {
        String url = "http://localhost:10001/product/ping";
        return restTemplate.getForObject(url,String.class);
    }

订单服务对外暴露Rest接口OrderAController

@RestController
@RequestMapping("/order-a")
public class OrderAController {

    @Autowired
    private OrderServiceA orderServiceA;

    @RequestMapping("/create")
    public Order add(Integer productId, Integer amount){
        return orderServiceA.create(productId, amount);
    }

    @GetMapping("/product/list")
    public List<Product> list(){
        return orderServiceA.findAll();
    }
    
     @GetMapping("/product/ping")
    public String pingProduct(){
        return orderServiceA.hello();
    }

}

order-ribbon

在浏览器中访问http://localhost:10010/order-a/product/list可以看到:
[{"id":0,"name":"phone","price":4999.0},{"id":1,"name":"glass","price":23.5},{"id":2,"name":"clothes","price":234.99}]
访问http://localhost:10010/order-a/product/ping可以看到:
Hello, message from localhost:10001
在这里插入图片描述
上面的这一系列代码不使用SpringCloud完全可以做到,那么需要优化的点在哪里呢?当然就在OrderAService,在调用其他服务的时候,地址是写死的:主机名和端口号不能灵活改变。当然你也可以将其放到某个可以配置的地方,不过这不是万全之策。

Eureka

接下来,引入Ribbon,优化之前的代码。

pom.xml中添加依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

需要包装一个RestTemplate,为了不影响之前的代码,新加一个Bean 于 CustomConfig中:

    @Bean
    @LoadBalanced
    public RestTemplate ribbonRestTemplate(){
        return new RestTemplate();
    }

注意和之前的RestTemplate唯一的不同在于加了注解@LoadBalanced,说明这个RestTemplate需要被Ribbon托管。

新建OrderBService

@Service
public class OrderBService {

    @Autowired
    private RestTemplate ribbonRestTemplate;

    /**
     * 生成一个订单
     * @param id
     * @param amount
     */
    public Order create(Integer id, Integer amount){
        String url = "http://product-service/product/"+id;
        Product p = ribbonRestTemplate.getForObject(url, Product.class);
        return new Order(UUID.randomUUID().toString(), p, amount);
    }

    /**
     * 查看所有可以购买的商品
     */
    public List<Product> findAll(){
        String url = "http://product-service/product/list";
        Product[] p = ribbonRestTemplate.getForObject(url, Product[].class);
        return Arrays.asList(p);
    }
     /**
     * 为了测试是否可以和product-service链接
     * @return
     */
    public String hello() {
        String url = "http://product-service/product/ping";
        return ribbonRestTemplate.getForObject(url,String.class);
    }
}

注意,其中只有url部分做的修改,我没有采用之前的hostname:port的方式,而是直接添加需要调用的服务名"product-service",这这值是哪里来的呢?
Eureka
ms-c1-product-service的配置文件中,有这样的配置:
spring.application.name=product-service,它就限定了我们的服务名称。

新建OrderBController

@RestController
@RequestMapping("/order-b")
public class OrderBController {

    @Autowired
    private OrderBService orderBService;

    @RequestMapping("/create")
    public Order add(Integer productId, Integer amount){
        return orderBService.create(productId, amount);
    }

    @GetMapping("/product/list")
    public List<Product> list(){
        return orderBService.findAll();
    }
    @GetMapping("/product/ping")
    public String pingProduct(){
        return orderBService.hello();
    }
}

其中的代码逻辑和OrderAController一样。

测试是否可以正常使用,在浏览器中访问http://localhost:10010/order-b/product/ping
Hello, message from 192.168.2.200:10001

得到的结果和之前的方式有一点不同,返回的是ip地址’192.168.2.200’,而不是主机名’localhost’。

在浏览器中访问http://localhost:10010/order-b/create?productId=1&amount=2得到如下结果:
[{"id":0,"name":"phone","price":4999.0},{"id":1,"name":"glass","price":23.5},{"id":2,"name":"clothes","price":234.99}]

这个接口是我们真正需要的,返回的数据和之前的一致。

Ribbon 负载均衡

上面测试可以看到,引入Ribbon以后通过服务名来做服务间调用可以避免在项目中配置大量的’主机名:端口号’信息。而这并不是Ribbon的主要作用,Ribbon提供的是一种负载均衡机制。

说起负载均衡,需要要提到Nginx,它属于最常见的负载均衡器,其他还有像F5、LVS都属于服务端负载均衡,而Ribbon属于客户端负载均衡,它赋予了应用支配HTTP与TCP的能力。

之前在做Eureka集群的时候,我创建了两个项目。在测试的时候,完全不必这样,我们可以充分利用IDE,启动多个实例。

1、在IDEA中,右上角找到启动信息-> Edit COnfigurations...
在这里插入图片描述
2、复制一个商品服务,命个名,添加端口信息-Dserver.port=xxx
在这里插入图片描述

启动以后可以在IDEA下方看到:
在这里插入图片描述
访问Eureka首页http://localhost:8761/
在这里插入图片描述

此时访问http://localhost:10010/order-a/product/ping,看到返回的信息始终为:
Hello, message from localhost:10001

访问http://localhost:10010/order-b/product/ping,会发现信息交替显示:

在这里插入图片描述
Hello, message from 192.168.2.200:10001
在这里插入图片描述
Hello, message from 192.168.2.200:10002
这里的’order-b’的接口是通过Ribbon实现了轮询的调用,实际上,并没有增加任何代码,就实现了负载均衡。

未完待续

通过对代码小小的改进,服务实现了负载均衡的能力,负载均衡的策略与原理也是比较重要的内容,下一节我将继续探究基于Ribbon的软负载均衡。

项目代码托管于Github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cj96248

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值