SpringCloud从入门到精通——微服务注册中心

目录

  • 一、远程调用概念引入
    • 1. 什么是远程调用
    • 2. 如何远程调用接口
    • 3. HttpClient使用调用天气预报接口
      • 3.1 HttpClient基本介绍
      • 3.2 Maven依赖
      • 3.3 HttpClient工具类
      • 3.4 测试调用天气预报接口
      • 3.5 提供天气预报接口
  • 二、什么是服务治理
    • 1.1 微服务框架中名词
    • 1.2 微服务框架中常用名词
  • 三、使用HttpClient实现RPC
    • 1. Maven依赖
    • 2. demo-member-producer
      • 2.1 MemberService
      • 2.2 AppMember:
      • 2.3 application.yml:
    • 3. demo-order-consumer
      • 3.1 OrderToMemberService
      • 3.2 HttpClientUtils
      • 3.3 AppOrder
      • 3.4 application.yml
    • 4. 访问
  • 四、常用注册中心
  • 五、Eureka服务注册中心
    • 1. Eureka基本介绍
    • 2. Eureka环境构建
      • 2.1 EurekaServer端
        • 2.1.1 Maven依赖
        • 2.1.2 配置文件
        • 2.1.3 启动类
      • 2.2 EurekaClient端
        • 2.2.1 Maven
        • 2.2.2 配置文件
        • 2.2.3 启动类
      • 2.3 服务发现
        • 2.3.1 Maven
        • 2.3.2 配置文件
        • 2.3.3 启动类
        • 2.2.4 接口
        • 2.2.5 工具类
      • 2.4 Eureka 常用配置解析
    • 3. 效果展示
      • 3.1 启动EurekaServer端
      • 3.2 启动EurekaClient端
      • 3.3 启动服务发现
      • 3.4 接口测试
  • 六、Nacos服务注册中心
    • 1. Nacos基本介绍
      • 1.1 什么是 Nacos
    • 2. NacosServer端环境搭建
      • 2.1 Nacos2.0启动常见错误
    • 3. 手动注册服务与发现
    • 4. NacosClient环境搭建
      • 4.1 Maven依赖
      • 4.2 配置文件
      • 4.3 接口
      • 4.4 启动项目
    • 5. NacosClient实现服务发现
      • 5.1 Maven依赖
      • 5.2 配置文件
      • 5.3 接口
      • 5.4 工具类
      • 5.5 启动类
      • 5.3 启动项目
      • 5.4 接口测试
  • 七、Resttemplate
    • 1. 接口
    • 2. 启动类
  • 八、本地负载均衡算法
    • 1. 轮询算法
    • 2. 随机算法
    • 3. 故障转移算法
    • 4. 权重算法

一、远程调用概念引入

1. 什么是远程调用

RPC是远程过程调用(Remote Procedure Call)的缩写形式。SAP系统RPC调用的原理其实很简单,有一些类似于三层构架的C/S系统,第三方的客户程序通过接口调用SAP内部的标准或自定义函数,获得函数返回的数据进行处理后显示或打印。

例如我们平台需要获取到天气预报,就可以直接调用中国天气预报接口。
接口名称:http://wthrcdn.etouch.cn/weather_mini?city=北京
返回接口信息:
SpringCloud从入门到精通——微服务注册中心_第1张图片

2. 如何远程调用接口

  1. HttpClient org.apache.httpcomponents
  2. OkHttp 对原生HttpURLConnection 封装(JDK本身自带)
  3. EasyHttp 基于OkHttp的网络请求框架

3. HttpClient使用调用天气预报接口

3.1 HttpClient基本介绍

实现了所有 HTTP 的方法(GET、POST、PUT、HEAD、DELETE、HEAD、OPTIONS 等)
支持 HTTPS 协议
支持代理服务器(Nginx等)等
支持自动(跳转)转向

3.2 Maven依赖

    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpclientartifactId>
            <version>4.5.5version>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.66version>
        dependency>
    dependencies>

3.3 HttpClient工具类

package com.demo.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: JYC
 * @Title: HttpClientUtils
 * @Description: TODO
 * @Date: 2022/4/20 23:10
 */
public class HttpClientUtils {


    private static final CloseableHttpClient httpClient;
    public static final String CHARSET = "UTF-8";
    private static final Log log = LogFactory.getLog(HttpClientUtils.class);

    // 采用静态代码块,初始化超时时间配置,再根据配置生成默认httpClient对象
    static {
        RequestConfig config = RequestConfig.custom().setConnectTimeout(60000).setSocketTimeout(15000).build();
        httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
    }

    public static String doGet(String url, Map<String, String> params) {
        return doGet(url, params, CHARSET);
    }


    public static String doPost(String url, Map<String, String> params) throws IOException {
        return doPost(url, params, CHARSET);
    }

    /**
     * HTTP Get 获取内容
     *
     * @param url     请求的url地址 ?之前的地址
     * @param params  请求的参数
     * @param charset 编码格式
     * @return 页面内容
     */
    public static String doGet(String url, Map<String, String> params, String charset) {
        try {
            if (params != null && !params.isEmpty()) {
                List<NameValuePair> pairs = new ArrayList<NameValuePair>(params.size());
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    String value = entry.getValue();
                    if (value != null) {
                        pairs.add(new BasicNameValuePair(entry.getKey(), value));
                    }
                }
                // 将请求参数和url进行拼接
                url += "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs, charset));
            }
            HttpGet httpGet = new HttpGet(url);
            CloseableHttpResponse response = httpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                httpGet.abort();
                throw new RuntimeException("HttpClient,error status code :" + statusCode);
            }
            HttpEntity entity = response.getEntity();
            String result = null;
            if (entity != null) {
                result = EntityUtils.toString(entity, "utf-8");
            }
            EntityUtils.consume(entity);
            response.close();
            return result;
        } catch (Exception e) {
            log.error("请求服务器端出错:" + e);
            return null;
        }

    }

    /**
     * HTTP Post 获取内容
     *
     * @param url     请求的url地址 ?之前的地址
     * @param params  请求的参数
     * @param charset 编码格式
     * @return 页面内容
     * @throws IOException
     */
    public static String doPost(String url, Map<String, String> params, String charset)
            throws IOException {
        List<NameValuePair> pairs = null;
        if (params != null && !params.isEmpty()) {
            pairs = new ArrayList<NameValuePair>(params.size());
            for (Map.Entry<String, String> entry : params.entrySet()) {
                String value = entry.getValue();
                if (value != null) {
                    pairs.add(new BasicNameValuePair(entry.getKey(), value));
                }
            }
        }
        HttpPost httpPost = new HttpPost(url);
        if (pairs != null && pairs.size() > 0) {
            httpPost.setEntity(new UrlEncodedFormEntity(pairs, CHARSET));
        }
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                httpPost.abort();
                throw new RuntimeException("HttpClient,error status code :" + statusCode);
            }
            HttpEntity entity = response.getEntity();
            String result = null;
            if (entity != null) {
                result = EntityUtils.toString(entity, "utf-8");
            }
            EntityUtils.consume(entity);
            return result;
        } catch (ParseException e) {
            log.error("请求服务器端出错:" + e);
            return null;
        } finally {
            if (response != null)
                response.close();
        }
    }
}

3.4 测试调用天气预报接口

public class HttpClientUtilsTest {
    public static void main(String[] args) {
        String url = "http://wthrcdn.etouch.cn/weather_mini";
        HashMap<String, String> params = new HashMap<String, String>();
        params.put("city", "北京");
        String result = HttpClientUtils.doGet(url, params);
        System.out.println("result:" + result);
    }
}

SpringCloud从入门到精通——微服务注册中心_第2张图片

3.5 提供天气预报接口

package com.demo.controller;

import com.alibaba.fastjson.JSONObject;
import com.demo.utils.HttpClientUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

/**
 * @Author: JYC
 * @Title: WeatherController
 * @Description: TODO
 * @Date: 2022/4/21 9:37
 */
@RestController
public class WeatherController {

    @RequestMapping("/getWeather")
    public Object getWeather(String city) {
        String url = "http://wthrcdn.etouch.cn/weather_mini";
        HashMap<String, String> params = new HashMap<String, String>();
        params.put("city", city);
        String jsonStr = HttpClientUtils.doGet(url, params);
        // 转化成json
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
        // 获取今天天气
        return jsonObject.getJSONObject("data").getJSONArray("forecast");
    }
}

启动类:

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author: JYC
 * @Title: App
 * @Description: TODO
 * @Date: 2022/4/21 9:47
 */
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class);
    }
}

在这里插入图片描述


二、什么是服务治理

SpringCloud从入门到精通——微服务注册中心_第3张图片

在RPC远程调用过程中,服务与服务之间依赖关系非常大,服务Url地址管理非常复杂,所以这时候需要对我们服务的url实现治理,通过服务治理可以实现服务注册与发现、负载均衡、容错等。

1.1 微服务框架中名词

生产者----提供接口
消费者----调用生产者提供的接口

1.2 微服务框架中常用名词

生产者:提供接口
消费者:调用生产者提供的接口
服务的注册:当我们服务启动时会将服务的ip和端口注册存放在注册中心上
容器:存放服务的接口IP和端口号码
服务的发现:消费者如果调用接口时根据服务名称去服务注册中心查找该对应服务接口地址,在本地实现RPC远程调用

三、使用HttpClient实现RPC

demo-order-consumer----订单服务 消费者 调用会员服务的接口
demo-member-producer—会员服务 生产者 提供接口

SpringCloud从入门到精通——微服务注册中心_第4张图片

1. Maven依赖

    <modules>
        <module>demo-member-producermodule>
        <module>demo-order-consumermodule>
    modules>

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <version>2.4.1version>
        dependency>
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpclientartifactId>
            <version>4.5.5version>
        dependency>
    dependencies>

2. demo-member-producer

2.1 MemberService

package com.demo.service;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: JYC
 * @Title: MemberService
 * @Description: TODO
 * @Date: 2022/4/21 13:57
 */
@RestController
public class MemberService {
    /**
     * 会员服务提供接口
     * @return
     */
    @RequestMapping("/getMember")
    public String getMember() {
        return "我是会员服务接口";
    }
}

2.2 AppMember:

package com.demo.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author: JYC
 * @Title: com.demo.service.AppMember
 * @Description: TODO
 * @Date: 2022/4/21 13:59
 */
@SpringBootApplication
public class AppMember {
    public static void main(String[] args) {
        SpringApplication.run(AppMember.class);
    }
}

2.3 application.yml:

server:
  port: 8080

SpringCloud从入门到精通——微服务注册中心_第5张图片

3. demo-order-consumer

3.1 OrderToMemberService

package com.demo.service;

import com.demo.utils.HttpClientUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */
@RestController
public class OrderToMemberService {
    /**
     * 订单服务,调用 会员服务接口
     */
    @RequestMapping("/orderToMember")
    public String OrderToMember() {
        // HttpClient 工具类 实现RPC远程调用
        String memberUrl = "http://192.168.66.1:8080/getMember";
        return HttpClientUtils.doGet(memberUrl, null);
    }
}

3.2 HttpClientUtils

package com.demo.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */

public class HttpClientUtils {

    private static final CloseableHttpClient httpClient;
    public static final String CHARSET = "UTF-8";
    private static final Log log = LogFactory.getLog(HttpClientUtils.class);

    // 采用静态代码块,初始化超时时间配置,再根据配置生成默认httpClient对象
    static {
        RequestConfig config = RequestConfig.custom().setConnectTimeout(60000).setSocketTimeout(15000).build();
        httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
    }

    public static String doGet(String url, Map<String, String> params) {
        return doGet(url, params, CHARSET);
    }


    public static String doPost(String url, Map<String, String> params) throws IOException {
        return doPost(url, params, CHARSET);
    }

    /**
     * HTTP Get 获取内容
     *
     * @param url     请求的url地址 ?之前的地址
     * @param params  请求的参数
     * @param charset 编码格式
     * @return 页面内容
     */
    public static String doGet(String url, Map<String, String> params, String charset) {
        try {
            if (params != null && !params.isEmpty()) {
                List<NameValuePair> pairs = new ArrayList<NameValuePair>(params.size());
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    String value = entry.getValue();
                    if (value != null) {
                        pairs.add(new BasicNameValuePair(entry.getKey(), value));
                    }
                }
                // 将请求参数和url进行拼接
                url += "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs, charset));
            }
            HttpGet httpGet = new HttpGet(url);
            CloseableHttpResponse response = httpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                httpGet.abort();
                throw new RuntimeException("HttpClient,error status code :" + statusCode);
            }
            HttpEntity entity = response.getEntity();
            String result = null;
            if (entity != null) {
                result = EntityUtils.toString(entity, "utf-8");
            }
            EntityUtils.consume(entity);
            response.close();
            return result;
        } catch (Exception e) {
            log.error("请求服务器端出错:" + e);
            return null;
        }

    }

    /**
     * HTTP Post 获取内容
     *
     * @param url     请求的url地址 ?之前的地址
     * @param params  请求的参数
     * @param charset 编码格式
     * @return 页面内容
     * @throws IOException
     */
    public static String doPost(String url, Map<String, String> params, String charset)
            throws IOException {
        List<NameValuePair> pairs = null;
        if (params != null && !params.isEmpty()) {
            pairs = new ArrayList<NameValuePair>(params.size());
            for (Map.Entry<String, String> entry : params.entrySet()) {
                String value = entry.getValue();
                if (value != null) {
                    pairs.add(new BasicNameValuePair(entry.getKey(), value));
                }
            }
        }
        HttpPost httpPost = new HttpPost(url);
        if (pairs != null && pairs.size() > 0) {
            httpPost.setEntity(new UrlEncodedFormEntity(pairs, CHARSET));
        }
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                httpPost.abort();
                throw new RuntimeException("HttpClient,error status code :" + statusCode);
            }
            HttpEntity entity = response.getEntity();
            String result = null;
            if (entity != null) {
                result = EntityUtils.toString(entity, "utf-8");
            }
            EntityUtils.consume(entity);
            return result;
        } catch (ParseException e) {
            log.error("请求服务器端出错:" + e);
            return null;
        } finally {
            if (response != null)
                response.close();
        }
    }
}

3.3 AppOrder

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author: JYC
 * @Title: AppOrder
 * @Description: TODO
 * @Date: 2022/4/21 14:10
 */
@SpringBootApplication
public class AppOrder {
    public static void main(String[] args) {
        SpringApplication.run(AppOrder.class);
    }
}

3.4 application.yml

server:
  port: 8070

SpringCloud从入门到精通——微服务注册中心_第6张图片

4. 访问

http://locahost:8070/orderToMember
SpringCloud从入门到精通——微服务注册中心_第7张图片

四、常用注册中心

Eureka、Zookeeper、Consule、Nacos、Redis 等。

五、Eureka服务注册中心

1. Eureka基本介绍

Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。
SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。

Eureka包含两个组件:Eureka Server和Eureka Client
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器。

在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

2. Eureka环境构建

2.1 EurekaServer端

2.1.1 Maven依赖
   <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
        dependency>
    dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>Finchley.RC2version>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>
    <repositories>
        <repository>
            <id>spring-milestonesid>
            <name>Spring Milestonesname>
            <url>https://repo.spring.io/milestoneurl>
            <snapshots>
                <enabled>falseenabled>
            snapshots>
        repository>
    repositories>
2.1.2 配置文件
server:
  port: 9090 #服务注册中心端口号
spring:
  application:
    name: spring-cloud-eureka-server
eureka:
  instance:
    hostname: 127.0.0.1 #服务注册中心IP地址
  client:
    registerWithEureka: false #是否向服务注册中心注册自己
    fetchRegistry: false #是否检索服务
    serviceUrl: #服务注册中心的配置内容,指定服务注册中心的位置
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
      
2.1.3 启动类
package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

/**
 * @Author: JYC
 * @Title: AppEurekaServer
 * @Description: TODO
 * @Date: 2022/4/21 18:46
 */
@SpringBootApplication
@EnableEurekaServer
public class AppEurekaServer {
    public static void main(String[] args) {
        SpringApplication.run(AppEurekaServer.class);
    }
}

2.2 EurekaClient端

2.2.1 Maven
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
    dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>Finchley.RC2version>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>

    <repositories>
        <repository>
            <id>spring-milestonesid>
            <name>Spring Milestonesname>
            <url>https://repo.spring.io/milestoneurl>
            <snapshots>
                <enabled>falseenabled>
            snapshots>
        repository>
    repositories>

2.2.2 配置文件
server:
  port: 8080
spring:
  application:
    name: demo-member   #服务名称 在注册中心展示服务名称
eureka:
  client:
    service-url: # 服务注册中心地址
      defaultZone: http://127.0.0.1:9090/eureka/

2.2.3 启动类
package com.demo.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

/**
 * @Author: JYC
 * @Title: com.demo.service.AppMember
 * @Description: TODO
 * @Date: 2022/4/21 13:59
 */
@SpringBootApplication
@EnableEurekaClient
public class AppMember {
    public static void main(String[] args) {
        SpringApplication.run(AppMember.class);
    }
}

2.3 服务发现

2.3.1 Maven
<dependencies>
        <dependency>
            <groupId>org.apache.httpcomponentsgroupId>
            <artifactId>httpclientartifactId>
            <version>4.5.5version>
        dependency>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
        dependency>
    dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-dependenciesartifactId>
                <version>Finchley.RC2version>
                <type>pomtype>
                <scope>importscope>
            dependency>
        dependencies>
    dependencyManagement>

    <repositories>
        <repository>
            <id>spring-milestonesid>
            <name>Spring Milestonesname>
            <url>https://repo.spring.io/milestoneurl>
            <snapshots>
                <enabled>falseenabled>
            snapshots>
        repository>
    repositories>
2.3.2 配置文件
server:
  port: 8070
spring:
  application:
    name: demo-order   #服务名称 在注册中心展示服务名称
eureka:
  client:
    service-url: # 服务注册中心地址
      defaultZone: http://127.0.0.1:9090/eureka/
2.3.3 启动类
package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @Author: JYC
 * @Title: AppOrder
 * @Description: TODO
 * @Date: 2022/4/21 14:10
 */
@SpringBootApplication
@EnableDiscoveryClient
public class AppOrder {
    public static void main(String[] args) {
        SpringApplication.run(AppOrder.class);
    }
}

2.2.4 接口
package com.demo.service;

import com.demo.utils.HttpClientUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */
@RestController
public class OrderToMemberService {
    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 订单服务,调用 会员服务接口
     */
    @RequestMapping("/orderToMember")
    public String OrderToMember() {
        // HttpClient 工具类 实现RPC远程调用
//        String memberUrl = "http://192.168.66.1:8080/getMember";
        /**
         * 根据服务名称,从注册中心获取会员的接口地址
         */
        List<ServiceInstance> instances = discoveryClient.getInstances("demo-member");
        ServiceInstance serviceInstance = instances.get(0);
        // 会员服务的ip和端口
        String memberUrl = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/" + "getMember";
        return "订单服务调用会员服务:" + HttpClientUtils.doGet(memberUrl, null);
    }
}

2.2.5 工具类
package com.demo.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */

public class HttpClientUtils {

    private static final CloseableHttpClient httpClient;
    public static final String CHARSET = "UTF-8";
    private static final Log log = LogFactory.getLog(HttpClientUtils.class);

    // 采用静态代码块,初始化超时时间配置,再根据配置生成默认httpClient对象
    static {
        RequestConfig config = RequestConfig.custom().setConnectTimeout(60000).setSocketTimeout(15000).build();
        httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
    }

    public static String doGet(String url, Map<String, String> params) {
        return doGet(url, params, CHARSET);
    }


    public static String doPost(String url, Map<String, String> params) throws IOException {
        return doPost(url, params, CHARSET);
    }

    /**
     * HTTP Get 获取内容
     *
     * @param url     请求的url地址 ?之前的地址
     * @param params  请求的参数
     * @param charset 编码格式
     * @return 页面内容
     */
    public static String doGet(String url, Map<String, String> params, String charset) {
        try {
            if (params != null && !params.isEmpty()) {
                List<NameValuePair> pairs = new ArrayList<NameValuePair>(params.size());
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    String value = entry.getValue();
                    if (value != null) {
                        pairs.add(new BasicNameValuePair(entry.getKey(), value));
                    }
                }
                // 将请求参数和url进行拼接
                url += "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs, charset));
            }
            HttpGet httpGet = new HttpGet(url);
            CloseableHttpResponse response = httpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                httpGet.abort();
                throw new RuntimeException("HttpClient,error status code :" + statusCode);
            }
            HttpEntity entity = response.getEntity();
            String result = null;
            if (entity != null) {
                result = EntityUtils.toString(entity, "utf-8");
            }
            EntityUtils.consume(entity);
            response.close();
            return result;
        } catch (Exception e) {
            log.error("请求服务器端出错:" + e);
            return null;
        }

    }

    /**
     * HTTP Post 获取内容
     *
     * @param url     请求的url地址 ?之前的地址
     * @param params  请求的参数
     * @param charset 编码格式
     * @return 页面内容
     * @throws IOException
     */
    public static String doPost(String url, Map<String, String> params, String charset)
            throws IOException {
        List<NameValuePair> pairs = null;
        if (params != null && !params.isEmpty()) {
            pairs = new ArrayList<NameValuePair>(params.size());
            for (Map.Entry<String, String> entry : params.entrySet()) {
                String value = entry.getValue();
                if (value != null) {
                    pairs.add(new BasicNameValuePair(entry.getKey(), value));
                }
            }
        }
        HttpPost httpPost = new HttpPost(url);
        if (pairs != null && pairs.size() > 0) {
            httpPost.setEntity(new UrlEncodedFormEntity(pairs, CHARSET));
        }
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                httpPost.abort();
                throw new RuntimeException("HttpClient,error status code :" + statusCode);
            }
            HttpEntity entity = response.getEntity();
            String result = null;
            if (entity != null) {
                result = EntityUtils.toString(entity, "utf-8");
            }
            EntityUtils.consume(entity);
            return result;
        } catch (ParseException e) {
            log.error("请求服务器端出错:" + e);
            return null;
        } finally {
            if (response != null)
                response.close();
        }
    }
}

2.4 Eureka 常用配置解析

Eureka 常用配置解析(点击跳转)

3. 效果展示

3.1 启动EurekaServer端

SpringCloud从入门到精通——微服务注册中心_第8张图片
浏览器访问:http://127.0.0.1:9090/
出现如下界面表示服务端启动成功
SpringCloud从入门到精通——微服务注册中心_第9张图片

3.2 启动EurekaClient端

SpringCloud从入门到精通——微服务注册中心_第10张图片
浏览器访问:http://127.0.0.1:9090/
在界面中能看到"DEMO-MEMBER"表示启动成功
SpringCloud从入门到精通——微服务注册中心_第11张图片

3.3 启动服务发现

SpringCloud从入门到精通——微服务注册中心_第12张图片
浏览器访问:http://127.0.0.1:9090/
在界面中能看到"DEMO-ORDER"表示启动成功
SpringCloud从入门到精通——微服务注册中心_第13张图片

3.4 接口测试

在浏览器中输入:http://127.0.0.1:8070/orderToMember
SpringCloud从入门到精通——微服务注册中心_第14张图片

六、Nacos服务注册中心

1. Nacos基本介绍

Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。

Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。 Nacos 是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。

*由阿里巴巴推出,Java语言编写
Nacos官网: https://nacos.io/zh-cn/docs/what-is-nacos.html

1.1 什么是 Nacos

服务(Service)是 Nacos 世界的一等公民。Nacos 支持几乎所有主流类型的“服务”的发现、配置和管理:
Kubernetes Service
gRPC & Dubbo RPC Service
Spring Cloud RESTful Service

Nacos 的关键特性包括:
服务发现和服务健康监测

Nacos 支持基于 DNS 和基于 RPC 的服务发现。服务提供者使用 原生SDK、OpenAPI、或一个独立的Agent TODO注册 Service 后,服务消费者可以使用DNS TODO 或HTTP&API查找和发现服务。

Nacos 提供对服务的实时的健康检查,阻止向不健康的主机或服务实例发送请求。Nacos 支持传输层 (PING 或 TCP)和应用层 (如 HTTP、MySQL、用户自定义)的健康检查。 对于复杂的云环境和网络拓扑环境中(如 VPC、边缘网络等)服务的健康检查,Nacos 提供了 agent 上报模式和服务端主动检测2种健康检查模式。Nacos 还提供了统一的健康检查仪表盘,帮助您根据健康状态管理服务的可用性及流量。

动态配置服务
动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。
动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。
配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。
Nacos 提供了一个简洁易用的UI (控制台样例 Demo) 帮助您管理所有的服务和应用的配置。Nacos 还提供包括配置版本跟踪、金丝雀发布、一键回滚配置以及客户端配置更新状态跟踪在内的一系列开箱即用的配置管理特性,帮助您更安全地在生产环境中管理配置变更和降低配置变更带来的风险。

动态 DNS 服务
动态 DNS 服务支持权重路由,让您更容易地实现中间层负载均衡、更灵活的路由策略、流量控制以及数据中心内网的简单DNS解析服务。动态DNS服务还能让您更容易地实现以 DNS 协议为基础的服务发现,以帮助您消除耦合到厂商私有服务发现 API 上的风险。
Nacos 提供了一些简单的 DNS APIs TODO 帮助您管理服务的关联域名和可用的 IP:PORT 列表.

服务及其元数据管理
Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略、服务的 SLA 以及最首要的 metrics 统计数据。

2. NacosServer端环境搭建

服务注册中心 如何设计

  1. 服务注册中心 web页面 管理服务注册信息内容
  2. 服务注册中心Api接口 服务注册
  3. 提供jar 能够被客户端支持 实现服务注册。

Nacos 下载地址:https://github.com/alibaba/nacos/releases

进入到:nacos-server-2.0.3\nacos\bin startup.cmd 启动即可。

访问:127.0.0.1:8848/nacos
默认账户密码:nacos/nacos

2.1 Nacos2.0启动常见错误

  1. 默认Nacos是集群方式启动,初学者建议先改成单机版本。
    修改:D:\path\cloud\nacos\bin startup.cmd 改成:set MODE=“standalone”
    在双击启动: startup.cmd

3. 手动注册服务与发现

1.实现服务注册
发送post请求:
‘http://127.0.0.1:8848/nacos/v1/ns/instance?serviceName=mayikt-member&ip=20.18.7.10&port=8080’

2.实现服务发现
http://127.0.0.1:8848/nacos/v1/ns/instance/list?serviceName=mayikt-member

详细步骤操作:https://nacos.io/zh-cn/docs/quick-start.html

注意:发送请求类型是为Post类型

一直刷新观察控制台,大概要等到30s才会把实例剔除(大概15s设置不健康)
该知识点设计服务续约问题

4. NacosClient环境搭建

4.1 Maven依赖

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.0.0.RELEASEversion>
parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>com.alibabagroupId>
        <artifactId>fastjsonartifactId>
        <version>1.2.66version>
    dependency>
    <dependency>
        <groupId>org.apache.httpcomponentsgroupId>
        <artifactId>httpclientartifactId>
        <version>4.5.5version>
    dependency>
<dependency>
    <groupId>org.springframework.cloudgroupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
    <version>0.2.2.RELEASEversion>
dependency>

dependencies>

4.2 配置文件

server:
  port: 8080
spring:
  application:
    name: demo-member  #服务名称 在 注册中心展示服务名称 --
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # nacos服务注册中心Server端 地址

4.3 接口

package com.demo.service;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: JYC
 * @Title: MemberService
 * @Description: TODO
 * @Date: 2022/4/21 13:57
 */
@RestController
public class MemberService {
    @Value("${server.port}")
    private String serverPort;
    /**
     * 会员服务提供接口
     * @return
     */
    @RequestMapping("/getMember")
    public String getMember() {
        return "我是会员服务接口...端口:" + serverPort;
    }
}

4.4 启动项目

package com.demo.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author: JYC
 * @Title: com.demo.service.AppMember
 * @Description: TODO
 * @Date: 2022/4/21 13:59
 */
@SpringBootApplication
public class AppMember {
    public static void main(String[] args) {
        SpringApplication.run(AppMember.class);
    }
}


5. NacosClient实现服务发现

5.1 Maven依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
            <version>0.2.2.RELEASEversion>
        dependency>
    dependencies>

5.2 配置文件

server:
  port: 8070
spring:
  application:
    name: demo-order   #服务名称 在注册中心展示服务名称
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 # nacos服务注册中心Server端 地址

5.3 接口

package com.demo.service;

import com.demo.utils.HttpClientUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */
@RestController
public class OrderToMemberService {
    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 订单服务,调用 会员服务接口
     */
    @RequestMapping("/orderToMember")
    public String OrderToMember() {
        // HttpClient 工具类 实现RPC远程调用
//        String memberUrl = "http://192.168.66.1:8080/getMember";
        /**
         * 根据服务名称,从注册中心获取会员的接口地址
         */
        List<ServiceInstance> instances = discoveryClient.getInstances("demo-member");
        ServiceInstance serviceInstance = instances.get(0);
        // 会员服务的ip和端口
        String memberUrl = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/" + "getMember";
        return "订单服务调用会员服务:" + HttpClientUtils.doGet(memberUrl, null);
    }
}

5.4 工具类

package com.demo.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */

public class HttpClientUtils {

    private static final CloseableHttpClient httpClient;
    public static final String CHARSET = "UTF-8";
    private static final Log log = LogFactory.getLog(HttpClientUtils.class);

    // 采用静态代码块,初始化超时时间配置,再根据配置生成默认httpClient对象
    static {
        RequestConfig config = RequestConfig.custom().setConnectTimeout(60000).setSocketTimeout(15000).build();
        httpClient = HttpClientBuilder.create().setDefaultRequestConfig(config).build();
    }

    public static String doGet(String url, Map<String, String> params) {
        return doGet(url, params, CHARSET);
    }


    public static String doPost(String url, Map<String, String> params) throws IOException {
        return doPost(url, params, CHARSET);
    }

    /**
     * HTTP Get 获取内容
     *
     * @param url     请求的url地址 ?之前的地址
     * @param params  请求的参数
     * @param charset 编码格式
     * @return 页面内容
     */
    public static String doGet(String url, Map<String, String> params, String charset) {
        try {
            if (params != null && !params.isEmpty()) {
                List<NameValuePair> pairs = new ArrayList<NameValuePair>(params.size());
                for (Map.Entry<String, String> entry : params.entrySet()) {
                    String value = entry.getValue();
                    if (value != null) {
                        pairs.add(new BasicNameValuePair(entry.getKey(), value));
                    }
                }
                // 将请求参数和url进行拼接
                url += "?" + EntityUtils.toString(new UrlEncodedFormEntity(pairs, charset));
            }
            HttpGet httpGet = new HttpGet(url);
            CloseableHttpResponse response = httpClient.execute(httpGet);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                httpGet.abort();
                throw new RuntimeException("HttpClient,error status code :" + statusCode);
            }
            HttpEntity entity = response.getEntity();
            String result = null;
            if (entity != null) {
                result = EntityUtils.toString(entity, "utf-8");
            }
            EntityUtils.consume(entity);
            response.close();
            return result;
        } catch (Exception e) {
            log.error("请求服务器端出错:" + e);
            return null;
        }

    }

    /**
     * HTTP Post 获取内容
     *
     * @param url     请求的url地址 ?之前的地址
     * @param params  请求的参数
     * @param charset 编码格式
     * @return 页面内容
     * @throws IOException
     */
    public static String doPost(String url, Map<String, String> params, String charset)
            throws IOException {
        List<NameValuePair> pairs = null;
        if (params != null && !params.isEmpty()) {
            pairs = new ArrayList<NameValuePair>(params.size());
            for (Map.Entry<String, String> entry : params.entrySet()) {
                String value = entry.getValue();
                if (value != null) {
                    pairs.add(new BasicNameValuePair(entry.getKey(), value));
                }
            }
        }
        HttpPost httpPost = new HttpPost(url);
        if (pairs != null && pairs.size() > 0) {
            httpPost.setEntity(new UrlEncodedFormEntity(pairs, CHARSET));
        }
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpPost);
            int statusCode = response.getStatusLine().getStatusCode();
            if (statusCode != 200) {
                httpPost.abort();
                throw new RuntimeException("HttpClient,error status code :" + statusCode);
            }
            HttpEntity entity = response.getEntity();
            String result = null;
            if (entity != null) {
                result = EntityUtils.toString(entity, "utf-8");
            }
            EntityUtils.consume(entity);
            return result;
        } catch (ParseException e) {
            log.error("请求服务器端出错:" + e);
            return null;
        } finally {
            if (response != null)
                response.close();
        }
    }
}

5.5 启动类

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author: JYC
 * @Title: AppOrder
 * @Description: TODO
 * @Date: 2022/4/21 14:10
 */
@SpringBootApplication
public class AppOrder {
    public static void main(String[] args) {
        SpringApplication.run(AppOrder.class);
    }
}

5.3 启动项目

SpringCloud从入门到精通——微服务注册中心_第15张图片

5.4 接口测试

在Nacos的服务管理界面中看到刚刚启动的两个服务,表示两个服务均启动成功。
SpringCloud从入门到精通——微服务注册中心_第16张图片
访问接口:http://127.0.0.1:8070/orderToMember
SpringCloud从入门到精通——微服务注册中心_第17张图片

七、Resttemplate

RestTemplate 是从 Spring3.0 开始支持的一个 HTTP 请求工具,它提供了常见的REST请求方案的模版,例如 GET 请求、POST 请求、PUT 请求、DELETE 请求以及一些通用的请求执行方法 exchange 以及 execute。RestTemplate 继承自 InterceptingHttpAccessor 并且实现了 RestOperations 接口,其中 RestOperations 接口定义了基本的 RESTful 操作,这些操作在 RestTemplate 中都得到了实现。接下来我们就来看看这些操作方法的使用。
底层是基于HttpClient封装的

1. 接口

package com.demo.service;

import com.demo.utils.HttpClientUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */
@RestController
public class OrderToMemberService {
    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 订单服务,调用 会员服务接口
     */
    @RequestMapping("/orderToMember")
    public String OrderToMember() {
        // HttpClient 工具类 实现RPC远程调用
//        String memberUrl = "http://192.168.66.1:8080/getMember";
        /**
         * 根据服务名称,从注册中心获取会员的接口地址
         */
        List<ServiceInstance> instances = discoveryClient.getInstances("demo-member");
        ServiceInstance serviceInstance = instances.get(0);
        // 会员服务的ip和端口
        String memberUrl = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/" + "getMember";
        ResponseEntity<String> response = restTemplate.getForEntity(memberUrl, String.class);
        return "订单服务调用会员服务:" + response.getBody();
    }
}

2. 启动类

package com.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @Author: JYC
 * @Title: AppOrder
 * @Description: TODO
 * @Date: 2022/4/21 14:10
 */
@SpringBootApplication
public class AppOrder {
    public static void main(String[] args) {
        SpringApplication.run(AppOrder.class);
    }

    /**
     * 将restTemplate注入到spring ioc容器
     * @return
     */
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

SpringCloud从入门到精通——微服务注册中心_第18张图片
SpringCloud从入门到精通——微服务注册中心_第19张图片

八、本地负载均衡算法

1. 轮询算法

package com.demo.loadbalance;

import org.springframework.cloud.client.ServiceInstance;

public interface LoadBalance {
    /**
     * 负载均衡算法:给我多个地址,负载均衡会取出一个地址返回使用
     * @param serviceId
     * @return
     */
    ServiceInstance getInstances(String serviceId);
}

package com.demo.loadbalance;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author: JYC
 * @Title: RoundLoadBalance
 * @Description: TODO
 * @Date: 2022/4/22 11:23
 */
@Component
public class RoundLoadBalance implements LoadBalance {
    @Autowired
    private DiscoveryClient discoveryClient;
    private AtomicInteger atomicCount = new AtomicInteger(0);

    @Override
    public ServiceInstance getInstances(String serviceId) {
        // 1. 根据服务的名称,获取该服务集群地址列表
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
        // 2. 判断是否为null
        if (instances == null || instances.size() == 0) {
            return  null;
        }
        // 3. 使用负载均衡算法
        int index = atomicCount.incrementAndGet();
        return instances.get(index);
    }
}

package com.demo.service;

import com.demo.loadbalance.RoundLoadBalance;
import com.demo.utils.HttpClientUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */
@RestController
public class OrderToMemberService {
    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private RoundLoadBalance roundLoadBalance;

    /**
     * 订单服务,调用 会员服务接口
     */
    @RequestMapping("/orderToMember")
    public String OrderToMember() {
        // HttpClient 工具类 实现RPC远程调用
//        String memberUrl = "http://192.168.66.1:8080/getMember";
        /**
         * 根据服务名称,从注册中心获取会员的接口地址
         */
//        List instances = discoveryClient.getInstances("demo-member");
//        ServiceInstance serviceInstance = instances.get(0);
        ServiceInstance serviceInstance = roundLoadBalance.getInstances("demo-member");
        // 会员服务的ip和端口
        String memberUrl = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/" + "getMember";
        return "订单服务调用会员服务:" + HttpClientUtils.doGet(memberUrl, null);
    }
}

2. 随机算法

package com.demo.loadbalance;

import org.springframework.cloud.client.ServiceInstance;

public interface LoadBalance {
    /**
     * 负载均衡算法:给我多个地址,负载均衡会取出一个地址返回使用
     * @param serviceId
     * @return
     */
    ServiceInstance getInstances(String serviceId);
}

package com.demo.loadbalance;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Random;

/**
 * @Author: JYC
 * @Title: RandomLoadBalance
 * @Description: TODO
 * @Date: 2022/4/22 11:37
 */
@Component
public class RandomLoadBalance implements LoadBalance{
    @Autowired
    private DiscoveryClient discoveryClient;


    @Override
    public ServiceInstance getInstances(String serviceId) {
        // 1. 根据服务的名称,获取该服务集群地址列表
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
        // 2. 判断是否为null
        if (instances == null || instances.size() == 0) {
            return  null;
        }
        // 生成随机数范围
        Random random = new Random();
        int index = random.nextInt(instances.size());
        return instances.get(index);
    }
}

package com.demo.service;

import com.demo.loadbalance.RandomLoadBalance;
import com.demo.loadbalance.RoundLoadBalance;
import com.demo.utils.HttpClientUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */
@RestController
public class OrderToMemberService {
    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private RoundLoadBalance roundLoadBalance;
    @Autowired
    private RandomLoadBalance randomLoadBalance;

    /**
     * 订单服务,调用 会员服务接口
     */
    @RequestMapping("/orderToMember")
    public String OrderToMember() {
        // HttpClient 工具类 实现RPC远程调用
//        String memberUrl = "http://192.168.66.1:8080/getMember";
        /**
         * 根据服务名称,从注册中心获取会员的接口地址
         */
//        List instances = discoveryClient.getInstances("demo-member");
//        ServiceInstance serviceInstance = instances.get(0);
//        ServiceInstance serviceInstance = roundLoadBalance.getInstances("demo-member");
        ServiceInstance serviceInstance = randomLoadBalance.getInstances("demo-member");
        // 会员服务的ip和端口
        String memberUrl = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/" + "getMember";
        return "订单服务调用会员服务:" + HttpClientUtils.doGet(memberUrl, null);
    }
}

3. 故障转移算法

package com.demo.service;

import com.demo.loadbalance.RandomLoadBalance;
import com.demo.loadbalance.RoundLoadBalance;
import com.demo.utils.HttpClientUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */
@RestController
@Slf4j
public class OrderToMemberService {
    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 订单服务,调用 会员服务接口
     */
    @RequestMapping("/orderToMember")
    public String OrderToMember() {
        /**
         * 根据服务名称,从注册中心获取会员的接口地址
         */
        List<ServiceInstance> instances = discoveryClient.getInstances("demo-member");
        for (int i = 0; i < instances.size(); i++) {
            try {
                ServiceInstance serviceInstance = instances.get(i);
                // 会员服务的ip和端口
                String memberUrl = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/" + "getMember";
            }catch (Exception e) {
                log.error("[rpc远程调用发生了故障,开始故障转移,切换下一个地址调用 e:{}]", e);
            }
        }
        return "fail";
    }
}

4. 权重算法

package com.demo;

import com.demo.loadbalance.LoadBalance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author: JYC
 * @Title: WeightLoadBalance
 * @Description: TODO
 * @Date: 2022/4/22 14:38
 */
@Component
public class WeightLoadBalance implements LoadBalance {
    @Autowired
    private DiscoveryClient discoveryClient;
    private AtomicInteger countAtomicInteger = new AtomicInteger(0);

    @Override
    public ServiceInstance getInstances(String serviceId) {
        // 1.根据服务Id名称,获取该接口多个实例
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceId);
        if (instances == null) {
            return null;
        }

        List<ServiceInstance> newInstances = new ArrayList<>();
        // 循环遍历该服务名称,对应的多个实例
        instances.forEach((service) -> {
            // 获取该服务实例对应的权重比例
            Double weight = Double.parseDouble(service.getMetadata().get("nacos.weight"));
            for (int i = 0; i < weight; i++) {
                newInstances.add(service);
            }
        });
        // 线程安全
        return newInstances.get(countAtomicInteger.incrementAndGet() % newInstances.size());
    }
}

package com.demo.service;

import com.demo.WeightLoadBalance;
import com.demo.loadbalance.RandomLoadBalance;
import com.demo.loadbalance.RoundLoadBalance;
import com.demo.utils.HttpClientUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Author: JYC
 * @Title: OrderToMemberService
 * @Description: TODO
 * @Date: 2022/4/21 14:04
 */
@RestController
@Slf4j
public class OrderToMemberService {
    @Autowired
    private DiscoveryClient discoveryClient;
    @Autowired
    private WeightLoadBalance weightLoadBalance;

    /**
     * 订单服务,调用 会员服务接口
     */
    @RequestMapping("/orderToMember")
    public String OrderToMember() {
        /**
         * 根据服务名称,从注册中心获取会员的接口地址
         */
//        List instances = discoveryClient.getInstances("demo-member");
//        ServiceInstance serviceInstance = instances.get(0);
        ServiceInstance serviceInstance = weightLoadBalance.getInstances("demo-member");
        // 会员服务的ip和端口
        String memberUrl = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/" + "getMember";
        return "订单服务调用会员服务:" + HttpClientUtils.doGet(memberUrl, null);
    }
}

SpringCloud从入门到精通——微服务注册中心_第20张图片

你可能感兴趣的:(SpringCloud,SpringCloud,微服务注册中心,Eureka,Nacos)