springcloud笔记(一) Eurake和Ribbon,RestTemplate 的学习与测试案例

2022-05-09 10:04:51 浏览数 (1)

目录

  • 1 快速入门
    • 1.1 搭建和配置一个服务提供者
    • 1.2 搭建和配置一个服务消费者
    • 1.3 以上总结
  • 2 服务注册中心
    • 2.1什么是服务注册?
    • 2.2 什么是服务发现?
    • 2.3 需求
    • 2.4 Eureka 是什么
    • 2.5搭建和配置一个Eureka服务
    • 2.6向 Eureka 服务注册中心注册服务
    • 2.7Ribbon 是什么?
    • 2.8 消费端集成eurake
    • 2.9 eurake 服务集群
      • 2.9.1 为什么要有集群
      • 2.9.2 搭建集群
      • 2.9.3 将提供者注册到集群里面
      • 2.9.4 将客户端也使用集群
      • 2.9.5 Eureka 服务注册中心自我保护机制
      • 2.9.6关于自我保护常用几个配置如下:
  • 3客户端负载均衡 Ribbon
    • 3.1 Ribbon 实现客户端负载均衡
    • 3.2Ribbon 负载均衡策略
  • 4 RestTemplate类
    • 4.1 RestTemplate 的 GET 请求
      • 4.1.1 第一种:getForEntity
      • 4.1.2 第一种:getForObject()
    • 4.2 RestTemplate 的 POST 请求
      • 4.2.1 restTemplate.postForObject()
      • 4.2.2 restTemplate.postForEntity()
    • 4.3RestTemplate 的 PUT 请求:
      • 4.3.1restTemplate.put()
    • 4.4RestTemplate 的 DELETE 请求:
      • 4.4.1restTemplate.delete();

1 快速入门

1.1 搭建和配置一个服务提供者

1 创建一个空项目 2 里面创建一个springboot的model

启动项目

以上就搭建了一个服务提供者的项目

1.2 搭建和配置一个服务消费者

代码语言:javascript复制
@Configuration
public class Config {
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder){
        return builder.build();
    }
}



@RestController
public class ConsumerController {

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(value="/cloud/hello")
    public String helloController() {
        return restTemplate.getForEntity("http://localhost:8080/hello",
                String.class).getBody();
    }
}

启动两个项目 用消费端进行访问

1.3 以上总结

以上只是有两个项目,一个项目A 调用B项目,在A项目里面写死B项目的地址进行调用;

还不是springcloud的项目;

我们需要使用服务注册中心和服务发现,就是springcloud项目了;

2 服务注册中心

2.1什么是服务注册?

服务注册:将服务所在主机、端口、版本号、通信协议等信息登记到注册中心上;

2.2 什么是服务发现?

服务发现:服务消费者向注册中心请求已经登记的服务列表,然后得到某个服务的主机、端口、版本号、通信协议等信息,从而实现对具体服务的调用;

2.3 需求

以上的入门案例,我们就需要改为springcloud项目,就需要有一个注册中心;

springcloud框架,里面有一个组件就是服务注册组件,Eureka;

2.4 Eureka 是什么

Eureka 由两个组件 组成:Eureka 服务端和 Eureka 客户端。

其他的微服务项目,要通过Eureka 客户端去连接Eureka 服务端;

2.5搭建和配置一个Eureka服务

1创建一个springboot项目; 2 添加eureka的服务端的依赖;

因为这个依赖是springcloud的,所以,先加入找springcloud的地址的依赖,就是仓库的依赖

代码语言:javascript复制
 <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

之后加入 springcloud的依赖

代码语言:javascript复制
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

之后导入eureka的依赖

代码语言:javascript复制
      <!--Spring Cloud 的 eureka-server 起步依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

3 在 Spring Boot 的入口类上添加一个@EnableEurekaServer 注解,用于 开启 Eureka 注册中心服务端

4 在 application.properties 文件中配置 Eureka 服务注册中心信息:

代码语言:javascript复制
#内嵌定时 tomcat 的端口
server.port=8761
#设置该服务注册中心的 hostname
eureka.instance.hostname=localhost
#由于我们目前创建的应用是一个服务注册中心,而不是普通的应用,默认情况下,这个应用会向注
#册中心(也是它自己)注册它自己,设置为 false 表示禁止这种自己向自己注册的默认行为
eureka.client.register-with-eureka=false
#表示不去检索其他的服务,因为服务注册中心本身的职责就是维护服务实例,它不需要去检索其他
#服务
eureka.client.fetch-registry=false
#指定服务注册中心的位置
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka

提示: springboot和springcloud的版本一定要对应 当前使用的springboot 的版本是

代码语言:javascript复制
 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

5 启动项目,浏览器访问 版本一样才可以启动

2.6向 Eureka 服务注册中心注册服务

1 首先将提供者姓名变为springcloud项目,加入eurake的依赖

代码语言:javascript复制
      <!--SpringCloud 集成 eureka 客户端的起步依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
代码语言:javascript复制
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
 </dependencyManagement>


    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

2 激活 Eureka 中的 EnableEurekaClient 功能: 在 Spring Boot 的入口函数处,通过添加@EnableEurekaClient 注解来表明自 己是一个 eureka 客户端,让我的服务提供者可以连接 eureka 注册中心;

3 提供者的配置文件里面进行配置

代码语言:javascript复制
#配置当前服务的名称,一般就是当前项目的名称
spring.application.name=01-service-provider
#这个项目要往哪个  服务里面进行注册,配置一下那个服务的地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

4、启动服务提供者 SpringBoot 程序的 main 方法运行; 5、启动运行之后,通过在浏览器地址栏访问我们之前搭建好的 eureka 注册中 心,就可以看到有一个服务已经注册成功了;

2.7Ribbon 是什么?

Ribbon 在 Eureka 客户端服务发现的基础上,实现了对服务实例的选择策略, 从而实现对服务的负载均衡消费;

2.8 消费端集成eurake

消费端集成eurake之后,消费端会从注册中心拿到服务列表,拿到具体的服务端的地址,之后使用ribbon去调用服务端;

1、在该消费者项目中添加 eureka 的依赖,因为服务消费者从注册中心获取服 务,需要连接 eureka,所以需要 eureka 客户端的支持;

代码语言:javascript复制
!--SpringCloud 集成 eureka 客户端的起步依赖--> <dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

2、激活 Eureka 中的 EnableEurekaClient 功能: 在 Spring Boot 的入口函数处,通过添加@EnableEurekaClient 注解来表明自 己是一个 eureka 客户端,让我的服务消费者可以使用 eureka 注册中心;

3、配置服务的名称和注册中心的地址:

代码语言:javascript复制
spring.application.name=03-springcloud-web-consumer
eureka.client.service-url.defaultZone=http://localhost:8761/eureka

4、前面我介绍了服务的发现由 eureka 客户端实现,而服务的真正调用由 ribbon 实现,所以我们需要在调用服务提供者时使用 ribbon 来调用:

我们看之前是咋调用的,写了一个RestTemplate配置类,在controller层使用这个直接调用服务端的ip地址进行调用服务;

现在我们要使用ribbon进行调用,也就是直接使用注册中心里面的服务名称调用,不用ip了

5、完成上面的步骤后,我们就可以启动消费者的 SpringBoot 程序,main 方法 运行; 6、启动成功之后,通过在浏览器地址栏访问我们的消费者,看是否可以正常调 用远程服务提供者提供的服务;

2.9 eurake 服务集群

2.9.1 为什么要有集群

由于注册中心 eureka 本身也是一个服务,如果它只有 一个节点,那么它有可能发生故障,这样我们就不能注册与查询服务了,所以我们需要一个高可用的服务注册中心,这就需要通过注册中心集群来解决。

Eureka Server 的高可用实际上就是将自己作为服务向其他服务注册中心注册 自己,这样就会形成一组互相注册的服务注册中心,进而实现服务清单的互相同步,往注册中心 A 上注册的服务,可以被复制同步到注册中心 B 上,所以从任何一台注册中心上都能查询到已经注册的服务,从而达到高可用的效果。

2.9.2 搭建集群

一个项目,使用多个配置文件启动,只要启动这个项目的时候,使用的是不同的配置文件,就相当于启动了多个项目。

我们知道,Eureka 注册中心高可用集群就是各个注册中心相互注册,所以:

1.在 8761 的配置文件中,让它的 service-url 指向 8762,在 8762 的配置文件中让它的 service-url 指向 8761

2.由于 8761 和 8762 互相指向对方,实际上我们构建了一个双节点的服务注册 中心集群 eureka.client.service-url.defaultZone=http://eureka8762:8762/eureka/ eureka.client.service-url.defaultZone=http://eureka8761:8761/eureka/

3 然后在本地 hosts 文件配置:C:WindowsSystem32driversetchosts 127.0.0.1 eureka8761 127.0.0.1 eureka8762

4 运行时,在运行配置项目 Program Arguments 中配置: –spring.profiles.active=eureka8761

–spring.profiles.active=eureka8762

分别启动两个eurace的服务注册之后

浏览器访问

2.9.3 将提供者注册到集群里面

启动这个提供者项目,浏览器访问

2.9.4 将客户端也使用集群

2.9.5 Eureka 服务注册中心自我保护机制

自我保护机制是 Eureka 注册中心的重要特性,当 Eureka 注册中心进入自我保 护模式时,在 Eureka Server 首页会输出如下警告信息:

,自我保护模式是一种应对网络异常的安全保护措施,它的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留),也不盲目注 销任何健康的微服务,使用自我保护模式,可以让 Eureka 集群更加的健壮、稳定。

当然也可以使用配置项:eureka.server.enable-self-preservation = false 禁用自我 保护模式。

2.9.6关于自我保护常用几个配置如下:

服务器端配置: #测试时关闭自我保护机制,保证不可用服务及时踢出 eureka.server.enable-self-preservation=false 客户端: #每间隔 2s,向服务端发送一次心跳,证明自己依然"存活" eureka.instance.lease-renewal-interval-in-seconds=2 #告诉服务端,如果我 10s 之内没有给你发心跳,就代表我故障了,将我踢出掉 eureka.instance.lease-expiration-duration-in-seconds=10

3客户端负载均衡 Ribbon

Ribbon就是一个jar包 springcloud里面已经集成了Ribbon,直接使用注解就可以使用;

主要功能是提供客户端的软件负载均衡;

在 Spring Cloud 中,Ribbon 主要与 RestTemplate 对象配合起来使用,Ribbon会自动化配置 RestTemplate 对象,通过@LoadBalanced 开启 RestTemplate对象调用时的负载均衡。

3.1 Ribbon 实现客户端负载均衡

由于 Spring Cloud Ribbon 的封装, 我们在微服务架构中使用客户端负载均衡 调用非常简单, 只需要如下两步:

1、启动多个服务提供者实例并注册到一个服务注册中心或是服务注册中心集群。

两个服务提供者的名称要一样,就是配置文件里面写的要一样

2、服务消费者通过被@LoadBalanced 注解修饰过的 RestTemplate 来调用服务提供者。这样,我们就可以实现服务提供者的高可用以及服务消费者的负载均衡调用。

同一个服务名称,不同的地址,之后就需要使用负载均衡了;

以上浏览器访问,就实现了负载均衡

3.2Ribbon 负载均衡策略

Ribbon 的负载均衡策略是由 IRule 接口定义, 该接口由如下实现:

上面有那么多策略,我想要在我的项目里面改了默认的,我如何做?

在配置文件里面,重新重写一个方法就可以了

代码语言:javascript复制
 /**
     * 覆盖掉原来ribbon默认的轮询负载均衡策略
     *
     *
     * @return
     */
    @Bean
    public IRule iRule() {
        //return new RandomRule(); //采用随机的负载均衡策略
        return new RetryRule(); //采用重试的负载均衡策略
    }

4 RestTemplate类

从以上的学习步骤,我们只是在客户端使用了这个类,写一个这个RestTemplate的配置类,在controller注入他,使用他调用服务提供者;

在日常操作中,基于 Rest 的方式通常是四种情况,它们分表是: GET 请求 --查询数据 POST 请求 –添加数据 PUT 请求 – 修改数据 DELETE 请求 –删除数据

4.1 RestTemplate 的 GET 请求

Get 请求可以有两种方式:

4.1.1 第一种:getForEntity

1 服务提供者提供的方法非返回值是String

代码语言:javascript复制
 @RequestMapping(value="/cloud/hello")
    public String helloController() {
//        return restTemplate.getForEntity("http://localhost:8080/hello",
//                String.class).getBody();

//        使用ribbon
        ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://01-SERVICE-PROVIDER/hello",
                String.class);
        int statusCodeValue = responseEntity.getStatusCodeValue();
        HttpStatus httpStatus = responseEntity.getStatusCode();
        HttpHeaders httpHeaders = responseEntity.getHeaders();
        String body = responseEntity.getBody();

        System.out.println(statusCodeValue);
        System.out.println(httpStatus);
        System.out.println(httpHeaders);
        System.out.println(body);
        return restTemplate.getForEntity("http://01-SERVICE-PROVIDER/hello",
                String.class).getBody();
    }
代码语言:javascript复制
以上代码:
getForEntity 方法第一个参数为要调用的服务的地址,即服务提供者提供的
http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/hello 接口地址,注
意这里是通过服务名调用而不是服务地址,如果改为服务地址就无法使用
Ribbon 实现客户端负载均衡了。
getForEntity 方法第二个参数 String.class 表示希望返回的 body 类型是 String
类型,如果希望返回一个对象,也是可以的,比如 User 对象;

2 服务提供者提供的方法的返回值是对象,比如user

我们找到服务提供者的项目,重新写一个接口,这个接口的返回值是对象

提供者项目里面

代码语言:javascript复制
 @RequestMapping(value = "/getuser", method = RequestMethod.GET)
    public User getuser() {

        User user = new User();
        user.setId(1);
        user.setUsername("jing");
        return user;
    }

消费者里面调用这个方法

代码语言:javascript复制
    @RequestMapping(value="/getuser")
    public User getuser() {
//        return restTemplate.getForEntity("http://localhost:8080/hello",
//                String.class).getBody();

//        使用ribbon
        ResponseEntity<User> responseEntity = restTemplate.getForEntity("http://01-SERVICE-PROVIDER/getuser",
                User.class);
        int statusCodeValue = responseEntity.getStatusCodeValue();
        HttpStatus httpStatus = responseEntity.getStatusCode();
        HttpHeaders httpHeaders = responseEntity.getHeaders();
        User body = responseEntity.getBody();

        System.out.println(statusCodeValue);
        System.out.println(httpStatus);
        System.out.println(httpHeaders);
        System.out.println(body);
        return restTemplate.getForEntity("http://01-SERVICE-PROVIDER/getuser",
                User.class).getBody();
    }

3 根据条件查询

我们可以根据map 和数组的方式传参,传的方法如下

我们需要在提供者项目里面写一个方法,这个方法是需要接受参数

代码语言:javascript复制
 @RequestMapping("/service/getUser")
    public User getUser(@RequestParam("id") Integer id,
                        @RequestParam("name") String name
                       ) {

        //进行业务处理(省略)
        System.out.println("服务提供者1。。。。。。。");

        User user = new User();
        user.setId(id);
        user.setUsername(name);
        return user;
    }

消费端的代码

代码语言:javascript复制
@RequestMapping("/web/getUser")
    public User getUser () {

        //逻辑判断(省略)

        String[] strArray = {"105", "张无忌"};

        Map<String, Object> paramMap = new ConcurrentHashMap<>();
        paramMap.put("id", 1028);
        paramMap.put("name", "张翠山");




        User body1 = restTemplate.getForObject("http://01-SERVICE-PROVIDER/service/getUser?id={0}&name={1}}", User.class, strArray);


        User body2 = restTemplate.getForObject("http://01-SERVICE-PROVIDER/service/getUser?id={id}&name={name}", User.class, paramMap);


        System.out.println(body1.getId()   "--"   body1.getUsername() );

        System.out.println(body2.getId()   "--"   body2.getUsername());

        return restTemplate.getForEntity("http://01-SERVICE-PROVIDER/service/getUser?id={id}&name={name}", User.class, paramMap).getBody();
    }

4.1.2 第一种:getForObject()

这个方法仅仅是拿到具体的数据 当你不需要返回响应中的其他信息,只需要 body 体信息的时候,可以 使用这个更方便;

和上一个里面的方法一样

4.2 RestTemplate 的 POST 请求

就是新增数据

我们在提供者项目里面,写代码

代码语言:javascript复制
//  新增数据
    @PostMapping("/service/addUser")
    public User addUser(@RequestParam("id") Integer id,
                        @RequestParam("name") String name) {

        //进行业务处理(省略)
        System.out.println("服务提供者1。。。。。。。");

        User user = new User();
        user.setId(id);
        user.setUsername(name);

        //将user对象插入数据库(暂时省略)
        return user;
    }

以下是客户端里面使用的方法进行调用提供者

4.2.1 restTemplate.postForObject()

代码语言:javascript复制
    /**
     * 消费者调用 -addUser方法
     *
     * @return
     */
    @RequestMapping("/web/addUser")
    public User addUser () {

        //逻辑判断(省略)

        String[] strArray = {"105", "张无忌"};

        Map<String, Object> paramMap = new ConcurrentHashMap<>();
        paramMap.put("id", 1028);
        paramMap.put("name", "张翠山");



        //要传的表单信息,参数数据(很坑人的)
        MultiValueMap<String, Object> dataMap = new LinkedMultiValueMap<String, Object>();
        dataMap.add("id", "1028");
        dataMap.add("name", "张翠山");


        //调用SpringCloud服务提供者提供的服务
        ResponseEntity<User> responseEntity = restTemplate.postForEntity("http://01-SERVICE-PROVIDER/service/addUser", dataMap, User.class);

        User body1 = restTemplate.postForObject("http://01-SERVICE-PROVIDER/service/addUser", dataMap, User.class);

        int statusCodeValue = responseEntity.getStatusCodeValue();
        HttpStatus httpStatus = responseEntity.getStatusCode();
        HttpHeaders httpHeaders = responseEntity.getHeaders();
        User body2 = responseEntity.getBody();
        System.out.println(body2.getId()   "--"   body2.getUsername());

        System.out.println(statusCodeValue);
        System.out.println(httpStatus);
        System.out.println(httpHeaders);

        System.out.println(body1.getId()   "--"   body1.getUsername());

        return restTemplate.postForEntity("http://01-SERVICE-PROVIDER/service/addUser", dataMap, User.class).getBody();
    }

4.2.2 restTemplate.postForEntity()

代码语言:javascript复制
    /**
     * 消费者调用 -addUser方法
     *
     * @return
     */
    @RequestMapping("/web/addUser")
    public User addUser () {

        //逻辑判断(省略)

        String[] strArray = {"105", "张无忌"};

        Map<String, Object> paramMap = new ConcurrentHashMap<>();
        paramMap.put("id", 1028);
        paramMap.put("name", "张翠山");



        //要传的表单信息,参数数据(很坑人的)
        MultiValueMap<String, Object> dataMap = new LinkedMultiValueMap<String, Object>();
        dataMap.add("id", "1028");
        dataMap.add("name", "张翠山");


        //调用SpringCloud服务提供者提供的服务
        ResponseEntity<User> responseEntity = restTemplate.postForEntity("http://01-SERVICE-PROVIDER/service/addUser", dataMap, User.class);

        User body1 = restTemplate.postForObject("http://01-SERVICE-PROVIDER/service/addUser", dataMap, User.class);

        int statusCodeValue = responseEntity.getStatusCodeValue();
        HttpStatus httpStatus = responseEntity.getStatusCode();
        HttpHeaders httpHeaders = responseEntity.getHeaders();
        User body2 = responseEntity.getBody();
        System.out.println(body2.getId()   "--"   body2.getUsername());

        System.out.println(statusCodeValue);
        System.out.println(httpStatus);
        System.out.println(httpHeaders);

        System.out.println(body1.getId()   "--"   body1.getUsername());

        return restTemplate.postForEntity("http://01-SERVICE-PROVIDER/service/addUser", dataMap, User.class).getBody();
    }

4.3RestTemplate 的 PUT 请求:

对数据进行修改

4.3.1restTemplate.put()

代码语言:javascript复制
 /**
     * 消费者调用 -updateUser方法
     *
     * @return
     */
    @RequestMapping("/web/updateUser")
    public String updateUser () {

        //逻辑判断(省略)

        String[] strArray = {"105", "张无忌", "13600000000"};

        Map<String, Object> paramMap = new ConcurrentHashMap<>();
        paramMap.put("id", 1028);
        paramMap.put("name", "张翠山");
        paramMap.put("phone", "13700000000");


        //要传的表单信息,参数数据(很坑人的)
        MultiValueMap<String, Object> dataMap = new LinkedMultiValueMap<String, Object>();
        dataMap.add("id", "1028");
        dataMap.add("name", "张翠山");
        dataMap.add("phone", "13700000000");

        //调用SpringCloud服务提供者提供的服务
        restTemplate.put("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/updateUser", dataMap);

        restTemplate.put("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/updateUser", dataMap);

        return "success";
    }

4.4RestTemplate 的 DELETE 请求:

对数据进行删除

4.4.1restTemplate.delete();

代码语言:javascript复制
  /**
     * 消费者调用 -deleteUser方法
     *
     * @return
     */
    @RequestMapping("/web/deleteUser")
    public String deleteUser () {
        //逻辑判断(省略)

        String[] strArray = {"105", "张无忌", "13600000000"};

        Map<String, Object> paramMap = new ConcurrentHashMap<>();
        paramMap.put("id", 1028);
        paramMap.put("name", "张翠山");
        paramMap.put("phone", "13700000000");

        //调用SpringCloud服务提供者提供的服务
        restTemplate.delete("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/deleteUser?id={0}&name={1}&phone={2}", strArray);

        restTemplate.delete("http://01-SPRINGCLOUD-SERVICE-PROVIDER/service/deleteUser?id={id}&name={name}&phone={phone}", paramMap);

        return "success";
    }

0 人点赞