博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Cloud 核心组件——注册中心
阅读量:5090 次
发布时间:2019-06-13

本文共 8417 字,大约阅读时间需要 28 分钟。

1. 什么是微服务的注册中心

注册中心:服务管理,核心是有个服务注册表,心跳机制动态维护。

为什么要用?

微服务应用和机器越来越多,调用方需要知道接口的网络地址,如果靠配置文件的方式去控制网络地址,对于动态新增机器,维护带来很大问题。

主流的注册中心:Zookeeper、Eureka、Consul、ETCD 等。

 

服务提供者 Provider:启动的时候向注册中心上报自己的网络信息。

服务消费者 Consumer:启动的时候向注册中心上报自己的网络信息,拉取 Provider 的相关网络信息。

 

2. 分布式应用知识CAP理论知识

CAP定理:指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition Tolerance(分区容错性),三者不可同时获得。

一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(所有节点在同一时间的数据完全一致,越多节点,数据同步越耗时)

可用性(A):负载过大后,集群整体是否还能响应客户端的读写请求。(服务一直可用,而且是正常响应时间)

分区容错性(P):分区容忍性,就是高可用性,一个节点崩了,并不影响其它的节点。(100个节点,挂了几个,不影响服务,越多机器越好)

CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡。

原因:

CA 满足的情况下,P 不能满足的原因:数据同步(C)需要时间,也要正常的时间内响应(A),那么机器数量就要少,所以P就不满足。

CP 满足的情况下,A 不能满足的原因:数据同步(C)需要时间,,机器数量也多(P),但是同步数据需要时间,所以不能再正常时间内响应,所以A就不满足。

AP 满足的情况下,C不能满足的原因:机器数量也多(P),正常的时间内响应(A),那么数据就不能及时同步到其他节点,所以C不满足。

注册中心选择:

Zookeeper:CP 设计,保证了一致性,集群搭建的时候,某个节点失效,则会进行选举行的 leader,或者半数以上节点不可用,则无法提供服务,因此可用性没法满足。

Eureka:AP 原则,无主从节点,一个节点挂了,自动切换其他节点可以使用,去中心化。

结论:

分布式系统中P,肯定要满足,所以只能在 C 和 A 中二选一。没有最好的选择,最好的选择是根据业务场景来进行架构设计,如果要求一致性,则选择 Zookeeper,如金融行业;如果要去可用性,则 Eureka,如电商系统。

 

Eureka 原理图:

Spring Cloud 体系官方地址:

参考:

 

3. 使用 IDEA 搭建 Eureka 服务中心 Server 端并启动

官方文档:

第一步:创建项目

和创建普通的 Spring Boot 项目是一样的,只是需要选择下图所示依赖

第二步:添加注解 @EnableEurekaServer

@SpringBootApplication@EnableEurekaServerpublic class EurekaServerApplication {    public static void main(String[] args) {        SpringApplication.run(EurekaServerApplication.class, args);    }}

第三步:增加配置 application.yml(其实可以使用 application.properties,但官网上使用 yml,这里我们照抄官网)

server:  port: 8761eureka:  instance:    hostname: localhost  client:    registerWithEureka: false    fetchRegistry: false    serviceUrl:      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

第四步:访问注册中心页面

使用  访问注册中心页面,这个地址按照配置文件中的来,这里注意下,按照配置文件说的,访问地址为:http://localhost:8761/eureka/

但不同版本,可能不一样,我所使用的版本是 Greenwich,它就不能添加 /eureka/ 否则会报 404 错误

 

Eureka 管理后台出现一串红色字体:EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY'RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

这个是警告,说明有服务上线率低。

关闭检查方法:Eureka 服务端配置文件加入

server:  enable-self-preservation: false

注意:自我保护模式禁止关闭,默认是开启状态 true

 

4. 创建商品服务,并将服务注册到注册中心

第一步:创建一个 Spring Boot 应用,增加服务注册和发现依赖

第二步:模拟商品信息,存储在内存中

第三步:开发商品列表接口,商品详情接口

第四步:配置文件加入注册中心地址

server:  port: 8771#指定注册中心地址eureka:  client:    serviceUrl:      defaultZone: http://localhost:8761/eureka/#服务的名称spring:  application:    name: product-service

我们可以用当前项目启动多个实例,参考教程:

启动后,我们可以登录下访问注册中心页面,看到如下效果:

为什么只加一个注册中心地址,就可以注册?

官网解释:By having spring-cloud-starter-netflix-eureka-client on the classpath, your application automatically registers with the Eureka Server.(也就是说,这样 Jar 包在类路径上,就可以自动识别)

 

5.常用的服务间的调用方式

RPC:远程过程调用,像调用本地服务(方法)一样调用服务器的服务。支持同步、异步调用。客户端和服务器之间建立 TCP 连接,可以一次建立一个,也可以多个调用复用一次链接。PRC 数据包小。

Rest:Http 请求,支持多种协议和功能。开发方便成本低。Http 数据包大。类似 HttpClient,URLConnection。

 

6.订单服务调用商品服务获取商品信息

第一步:创建 order_service 项目

注意:调用方需要引入 Ribbon 依赖

第二步:使用 Ribbon(类似 HTTPClient,URLConnection)

启动类增加注解

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

第三步:开发伪下单接口

第四步:根据名称进行调用商品,获取商品详情

注意:红字标识的就是上面商品服务的 spring.application.name

@Servicepublic class ProductOrderServiceImpl implements ProductOrderService {    @Autowired    private RestTemplate restTemplate;    @Override    public ProductOrder save(int userId, int productId) {        Object obj = restTemplate.getForObject("http://product-service/api/v1/product/find?id="+productId, Object.class);        System.out.println(obj);        ProductOrder productOrder = new ProductOrder();        productOrder.setCreateTime(new Date());        productOrder.setUserId(userId);        productOrder.setTradeNo(UUID.randomUUID().toString());        return productOrder;    }}

当我们用请求多次访问时,可以从控制台看到端口号的不同,说明这是从不同端口的应用返回的数据

商品服务的 Controller 如下:

@RestController@RequestMapping("/api/v1/product")public class ProductController {    @Value("${server.port}")    private String port;    @Autowired    private ProductService productService;    /**     * 获取所有商品列表     * @return     */    @RequestMapping("list")    public Object list(){        return productService.listProduct();    }    /**     * 根据id查找商品详情     * @param id     * @return     */    @RequestMapping("find")    public Object findById(int id){        Product product = productService.findById(id);        Product result = new Product();        BeanUtils.copyProperties(product,result);        result.setName( result.getName() + " data from port="+port );        return result;    }}

我们还可以通过另一种调用方式进行调用

//调用方式二ServiceInstance instance = loadBalancer.choose("product-service");String url = String.format("http://%s:%s/api/v1/product/find?id="+productId, instance.getHost(),instance.getPort());RestTemplate restTemplate = new RestTemplate();Object obj = restTemplate.getForObject(url, Object.class);//Map
productMap = restTemplate.getForObject(url, Map.class);

调用原理:

1)首先从注册中心获取 Provider 的列表

2)通过一定的策略选择其中一个节点

3)再返回给 restTemplate 调用

我们也可以自定义负载均衡策略,官网说明:

server:  port: 8781#指定注册中心地址eureka:  client:    serviceUrl:      defaultZone: http://localhost:8761/eureka/#服务的名称spring:  application:    name: order-service#自定义负载均衡策略product-service:  ribbon:    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

策略选择:

1)如果每个机器配置一样,则建议不修改策略 (推荐)

2)如果部分机器配置强,则可以改为 WeightedResponseTimeRule

 

7.使用 Feign 改造订单服务 

Feign: 伪 RPC 客户端(本质还是用http)

官方文档:

第一步:加入依赖

org.springframework.cloud
spring-cloud-starter-openfeign

第二步:启动类增加 @EnableFeignClients

@SpringBootApplication@EnableFeignClientspublic class OrderServiceApplication {    public static void main(String[] args) {        SpringApplication.run(OrderServiceApplication.class, args);    }}

第三步:增加一个接口并使用注解 @FeignClient(name="product-service")

package com.jwen.order_service.service;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;/** * 商品服务客户端 */@FeignClient(name = "product-service")public interface ProductClient {    @GetMapping("/api/v1/product/find")    String findById(@RequestParam(value = "id") int id);}

注意点:

1)服务名和 Http 方法必须对应

2)使用 RequestBody,应该使用 @PostMapping

3)多个参数的时候,通过 @RequestParam(value = "id") int id 方式调用

第四步:更改调用方式编码

package com.jwen.order_service.service.impl;import com.fasterxml.jackson.databind.JsonNode;import com.jwen.order_service.domain.ProductOrder;import com.jwen.order_service.service.ProductClient;import com.jwen.order_service.service.ProductOrderService;import com.jwen.order_service.utils.JsonUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.Date;import java.util.UUID;@Servicepublic class ProductOrderServiceImpl implements ProductOrderService {    @Autowired    private ProductClient productClient;    @Override    public ProductOrder save(int userId, int productId) {        String response = productClient.findById(productId);        JsonNode jsonNode = JsonUtils.str2JsonNode(response);        ProductOrder productOrder = new ProductOrder();        productOrder.setCreateTime(new Date());        productOrder.setUserId(userId);        productOrder.setTradeNo(UUID.randomUUID().toString());        productOrder.setProductName(jsonNode.get("name").toString());        productOrder.setPrice(Integer.parseInt(jsonNode.get("price").toString()));        return productOrder;    }}
JsonUtils 工具类的代码
package com.jwen.order_service.utils;import com.fasterxml.jackson.databind.JsonNode;import com.fasterxml.jackson.databind.ObjectMapper;import java.io.IOException;/** * json工具类 */public class JsonUtils {    private static final ObjectMapper objectMappper = new ObjectMapper();    /**     * json字符串转JsonNode对象的方法     */    public static JsonNode str2JsonNode(String str){        try {            return  objectMappper.readTree(str);        } catch (IOException e) {            return null;        }    }}

Ribbon 和 Feign 两个之间,应该选择 Feign。Feign 默认集成了 Ribbon。写起来更加思路清晰和方便。采用注解方式进行配置,配置熔断等方式方便

超时配置

#修改调用超时时间feign:  client:    config:      default:        connectTimeout: 5000        readTimeout: 5000

模拟接口响应慢,线程睡眠新的方式

try {    TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {    e.printStackTrace();}

 

转载于:https://www.cnblogs.com/jwen1994/p/11408511.html

你可能感兴趣的文章
python split()函数多个分隔符用法
查看>>
读书笔记--Spring Integration in Action 目录
查看>>
Linux console text color
查看>>
ES6 Promise对象then方法链式调用
查看>>
LiveNVR RTSP流媒体服器软件通过按需直播降低企业服务带宽 - RTSP网页无插件直播...
查看>>
css正常文档布局和元素可见性代码
查看>>
JS --实用小方法收集
查看>>
python-数据类型
查看>>
ASP.NET EF实体主外键关系
查看>>
【python之路15】深浅拷贝及函数
查看>>
Python操作RabbitMQ
查看>>
HDUOJ----4509湫湫系列故事——减肥记II
查看>>
linux下mysql函数的详细案列
查看>>
【2019.7.24】数颜色 / 聪明的可可 / 奖章分发
查看>>
深度学习基础网络 ResNet
查看>>
js(事件) d3
查看>>
算法学习-带分数
查看>>
Pomodairo,番茄工作法-应用篇
查看>>
XML 之 与Json或String的相互转换
查看>>
Android 之 权限 uses-permission 设置
查看>>