Dubbo学习笔记

Dubbo学习手册

RPC基础知识

软件架构

单一应用架构

当网站流量很小、应用规模很小时,只需要一个应用,将所有功能都部署在一起,以减少部署服务器数量和成本。

此时,用于简化增删改查的数据访问框架 ORM 是关键

这种结构的应用适合小型系统,或者公司的内部系统,用户少、请求量低,对请求的处理时间没有太高的要求。

将所有的功能都部署到一个服务器上,简单易用,开发难度低。

缺点:

  1. 性能扩展困难
  2. 不利于多人同时开发
  3. 不利于升级维护
  4. 整个系统的空间占用比较大

分布式服务架构

当应用越来越多,应用与应用之间存在大量的交互,此时可以将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的 分布式服务框架 RPC 是关键。

分布式系统将服务作为独立的应用,实现服务的共享和重用。

分布式系统

什么是分布式系统

分布式系统是若干独立计算机(服务器)的集合,这些计算机对于用户来说就像单个相关系统。分布式系统(Distributed System)是建立在网络之上的服务器端的一种结构。

分布式系统中的计算机可以使用不同的操作系统,可以运行不同应用程序提供的服务,将服务分散部署到多个计算机服务器上。

RPC

RPC(Remote Produce Call)远程过程调用,是一种进程间通信方式,是一种技术思想,而不是技术规范。

它允许程序调用另一个地址空间(网络上的另一台机器)的过程或函数,而不用开发人员显式编码这个调用的细节。调用本地方法和调用远程方法一样。

RPC的实现方式可以不同,如:Java的 RMI,Spring的远程调用等。

RPC的概念是在上世纪80年代由 Brue Jay Nelson 提出的。使用 RPC可以将本地的调用扩展到远程调用(分布式系统的其他服务器)。

RPC的特点:

  1. 简单:使用简单,使得建立分布式应用更容易
  2. 高效:调用过程十分清晰,效率高
  3. 通用:作为进程间通讯的方式,有着通用的规则

RPC基本原理

RPC调用过程:

  1. 因为调用方 Client 要使用右侧 Server的功能,故发起对该功能的请求调用
  2. Client Stub是 RPC中定义的存根,作为 Client的助手,把要调用的方法参数进行序列化,把方法名称和其他数据包装起来
  3. 通过网络 Socket,把方法调用的细节内容发送给右侧的 Server
  4. Server端通过 Socket接收请求的方法名称,参数等数据,传给 Server Stub
  5. Server端获取到的数据由 Server Stub处理,调用 Server真正的方法,处理业务
  6. Server方法处理完业务,把结果对象 Object交给 Server Stub,助手把 Object进行序列化,转化为二进制数据
  7. Server Stub将二进制数据交给网络处理程序
  8. 通过网络将二进制数据发送给 Client
  9. Client接收数据,将给 Client Stub
  10. Client Stub将数据反序列化为 Java对象,作为远程方法的调用结果

注意:

  • RPC通信是基于 TCP / UDP 的
  • 序列化方式(XML / JSON / 二进制)

Dubbo介绍

Dubbo概述

Apache Dubbo 是一款高性能、轻量级的开源 Java RPC框架。

Dubbo是阿里巴巴服务化治理方案的核心框架,致力于提高性能和透明化的 RPC远程服务调用方案、服务治理方案。

它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,服务自动注册和发现

  • 远程通讯:提供对多种基于长链接的 NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交互方式
  • 集群容错:提供基于接口方法的透明远程过程调用,包括多协议支持,软负载均衡,失败容错,地址路由,动态配置等集群支持
  • 自动发现:基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器

Dubbo的核心部件:

  • Remoting:网络通信框架,实现了 sync-over-async 和 request-response 消息机制
  • RPC:一个远程过程调用的抽象,支持负载均衡、容灾和集群功能
  • Registry:服务目录框架用于服务的注册和服务事件的发布与订阅

其他的 RPC框架:

  • 新浪微博的 Motan
  • 当当的 DubboX
  • 谷歌的 GRPC

Dubbo的基本架构

  • Provider(服务提供者):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务
  • Consumer(服务消费者):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务。它从提供者地址列表中,基于软负载均衡算法,选择一台提供者进行调用。
  • Registry(注册中心):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长链接推送变更数据给消费者
  • Monitor(监控中心):服务消费者和提供者,在内存中累计调用次数和调用事件,定时发送一次统计数据到监控中心

调用关系说明:

  1. 服务容器负责启动,加载,运行服务提供者
  2. 服务提供者在启动时,向注册中心注册自己提供的服务
  3. 服务消费者在启动时,向注册中心订阅自己所需的服务
  4. 注册中心返回服务提供者的地址列表给消费者,如果有变更,注册中心将基于长链接推送变更数据给消费者
  5. 服务消费者从提供者地址列表中基于软负载均衡算法,选择一台提供者进行调用,如果失败再做挑选
  6. 服务消费者和提供者,在内存中统计调用次数和调用时间,定时每分钟发送到监控中心

Dubbo的底层实现是动态代理,由 Dubbo框架创建远程服务(接口)对象的代理对象,通过代理对象调用远程方法。

Dubbo支持的协议

Dubbo一共支持 8 种协议:

  • dubbo
  • hessian
  • rmi
  • http
  • webservice
  • thrift
  • memcached
  • redis

Dubbo 官方推荐使用 dubbo协议,默认端口号为 20880

使用 dubbo协议,在 Spring配置文件中需加入:

<dubbo:protocal name="dubbo" port="20880"/>

dubbo协议

dubbo协议的特点:

  • 采用单一长连接和异步通讯,适合小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况
  • 底层网络通信默认使用 Netty,性能优秀
  • 不适合传送大数据量的服务,比如:传文件、传视频等

长连接和短连接

长连接:指在一个连接上可以连续发送多个数据包,在连接保持的情况下,如果没有数据包发送,需要双方同时发送检测包。长连接多用于操作频繁,点对点的通讯,而且连接数不能太多的情况。例如:数据库的连接用长连接。

短连接:指双方有信息交互时就建立一个连接,数据发送完以后,就断开连接,即每次连接只完成一项业务的发送。像 Web网站的 HTTP服务一般都使用短连接,更省资源。

Dubbo直连方式调用

点对点的直连项目:服务消费者直接访问提供者,没有注册中心,因此,消费者必须指定服务提供者的访问地址(URL)

实现目标

用户访问网站 ------> 网站访问服务 -----> 生成者提供并暴露服务

具体实现

orderservice-provider

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.2</version>
</dependency>

<plugins>
    <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
        </configuration>
    </plugin>
</plugins>
// 创建订单实体类 Order
public class Order implements Serializable {
    
    private static final long serialVersionUID = 1L;
    // 订单 id
    private String id;
    // 商品名称
    private String goodName;
    // 商品单价
    private float price;
    // 购买数量
    private Integer amount;
    // setters and getters, toString()
}
// 创建订单服务接口 OrderService
public interface OrderService {
    // 用户的id,商品名称,单价,数量
    public Order createOrder(Integer userId, String goodName, float price, Integer amount);
}
// 创建接口实现类 OrderServiceImpl
public class OrderServiceImpl implements OrderService {
    
    @Override
    public Order createOrder(Integer userId, String goodName, float price, Integer amount) {
        Order order = new Order();
        String orderId = UUID.randomUUID().toString().replaceAll("-", "");
        order.setId(orderId);
        order.setGoodName(goodName);
        order.setPrice(price);
        order.setAmount(amount);
        return order;
    }
}
<!-- 创建dubbo配置文件 orderservice-provider.xml -->
<!-- 明确服务的名称,使用唯一值,服务的名称是该项目/模块的名称 -->
<dubbo:application name="ch01-link-userservice-provider"/>
<!-- 访问服务的协议名称、端口 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 
	interface:接口的全限定名
 	ref:接口的实现 bean的 id
	registry:直连方式,不使用注册中心。N/A:不使用注册中心
-->
<dubbo:service interface="com.bjpowernode.service.OrderService" ref="orderService" registry="N/A"/>
<!-- 声明订单实现bean -->
<bean id="orderService" class="com.bjpowernode.service.impl.OrderServiceImpl"/>
// 测试配置文件
public class OrderApplication {
    public static void main(String[] args) throws IOException {
        String configLocation = "orderservice-provider.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(configLocation);
        ((ClassPathXmlApplicationContext)ac).start();
        // 阻塞操作,应用一直运行
        System.in.read();
    }
}

orderservice-consumer

<!-- pom.xml -->
<dependency>
    <groupId>com.bjpowernode</groupId>
    <artifactId>link-orderservice-provider</artifactId>
    <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.2</version>
</dependency>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
// 创建购买商品的接口
public interface ShopService {
    // 用户的id,商品名称,单价,数量
    public Order buyGoods(Integer userId, String goodName, float price, Integer amount);
}
// 创建购买接口的实现类
public class ShopServiceImpl implements ShopService {
    // 使用远程服务接口
    private OrderService orderService;
    
    public void setOrderService(OrderService orderService) {
        this..orderService = orderService;
    }
    
    @Override
    public void buyGoods(Integer userId, String goodName, float price, Integer amount) {
        // 调用远程方法创建订单
        Order order = orderService.createOrder(userId, goodName, price, amount);
        return order;
    }
}
<!-- 创建 dubbo配置文件 -->
<!-- 服务名称,使用唯一值,服务的名称是 dubbo内部使用标识服务的 -->
<dubbo:application name="ch02-link-consumer" />
<!-- 
	引用远程服务接口
 	id:远程接口的代理对象名称
	interface:远程接口的全限定名称
	url:访问服务提供者的地址
-->
<dubbo:reference id="remoteOrderService" interface="com.bjpowernode.service.OrderService" url="dubbo://localhost:20880" registry="N/A" />

<bean id="shopService" class="com.bjpowernode.service.impl.ShopServiceImpl">
	<property name="orderService" ref="remoteOrderService" />
</bean>
// 执行消费者
public class ConsumeApplication {
    public static void main(String[] args) throws IOException {
        String configLocation = "shop-consumer.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContex(configLocation);
        ((ClassPathXmlApplicationContext) ctx)).start();
        
        ShopService service = (ShopService) ac.getBean("shopService");
        Order order = service.buyGoods(1, "手机", 2000, 2);
        System.out.println("order:" + order);
    }
}

接口抽取和远程调用

接口抽取

关键词:

  • 分包:将服务接口、服务模型、服务异常等均放在公共包中
  • 粒度:服务接口尽可能大粒度,每个服务方法应代表一个功能,而不是某功能的一个步骤,否则将面临分布式事务问题,Dubbo暂未提供分布式事务支持。服务接口应以业务场景为单位划分,并对相近业务做抽象,防止接口数量爆炸。接口应有明确的语义以便于后期维护。
  • 版本:每个接口都应定义版本号,为后续不兼容升级提供可能,如:<dubbo:service interface="com.xxx.XxxService"version="1.0"/>。建议使用两位版本号,要变更服务版本时,先升级一半提供者为新版本,再将消费者全部升级为新版本,然后将剩下的一半提供者升级为新版本。

实现目标

公共接口包

// 新建 Address实体类
public class Address implements Serializable {
    private static final long serialVersionUID=1L;
    // 收件人姓名
    private String name;
    // 城市
    private String city;
    // 街道
    private String street;
    // 邮编
    private String zipcode;
    // 手机号
    private String mobile;
    // 是否使用此地址
    private Boolean use;
    //setters and getters, toString
}
// 新建 UserInfoService接口
public interface UserInfoService {
    List<Address> queryAddress(Integer userId);
}

用户信息服务提供者

<!-- pom.xml -->
<dependency>
    <groupId>com.bjpowernode</groupId>
    <artifactId>node-shop-interface</artifactId>
    <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.2</version>
</dependency>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
// 创建 UserInfoServiceImpl实现类
public class UserInfoServiceImpl implements UserInfoService {
    @Override
    public List<Address> queryAddress(Integer userId) {
        List<Address> adds = new ArrayList<>();
        if(userId == 1) {
            Address address = new Address();
            address.setName("张三");
            address.setCity("北京");
            address.setStreet("凉水河二街");
            address.setUse(true);
            
            adds.add(address);
        } else if(userId == 2) {
            Address address = new Address();
            address.setName("赵明");
            address.setCity("北京");
            address.setStreet("西城区西单10号");
            address.setUse(true);
            // 其他属性赋值
            adds.add(address);
        }
        return adds;
    }
}
<!-- dubbo配置文件 -->
<!-- 服务的名称,使用唯一值,服务名称是dubbo内部使用标识服务的 -->
<dubbo:application name="ch04-link-userservice-provider-1" />
<!-- 访问服务的协议名称,端口 -->
<dubbo:protocol name="dubbo" port="20880" />
<!-- 
	暴露的服务,消费者调用此接口的方法
	interface:接口的全限定名称
	ref:接口的实现类对象id
-->
<dubbo:service interface="com.bjpowernode.service.UserInfoService" ref="userService" registry="N/A" />
<!-- 服务接口的实现对象:功能的真正实现者 -->
<bean id="userService" class="com.bjpowernode.service.impl.UserInfoServiceImpl" />
<!-- web.xml注册 Spring监听器 -->
<context-param>
	<param-name>contextConfigLocation</param-name>
    <param-value>classpath:userservice-provider.xml</param-value>
</context-param>
<!-- Spring监听器 -->
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

服务消费者

<!-- index.jsp -->
<script type="text/javascript">
	var URL_PREFIX="${pagaContext.request.contextPath}";
    function buyGoods(userId, name, price, amount) {
        window.location.href = URL)PREFIX + "/shop/buy?userId=" + userId 
            + "&name=" + name 
            + "&price=" + price 
            + "&amount" + amount;
    }
    function showAddress(userId) {
        window.location.href = URL_PREFIX + "/shop/address?userId=" + userId;
    }
</script>

<div style="margin-left:400px">
    <p>
        商品列表
    </p>
    <table border="1" cellpadding="1" cellspacing="1" width="60%">
        <tr>
            <td>iphone</td>
            <td>5000</td>
            <td>20</td>
            <td><a href="javascript:void(0) onClick='buyGoods(1, iphone, 5000, 1)'"></a>购买</td>
        </tr>
    </table>
</div>
<div>
    <p>
        我的收件地址
    </p>
    <a href="javascript:void(0)" onclick="showAddress(2)">我的收件地址</a>
</div>
<!-- pom.xml -->
<dependency>
    <groupId>com.bjpowernode</groupId>
    <artifactId>nod-shop-interface</artifactId>
    <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>
<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>dubbo</artifactId>
    <version>2.6.2</version>
</dependency>
<build>
    <plugins>
        <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
// 创建商品信息实体类 Goods
public class Goods {
    private String name;
    private Float price;
    private Integer amount;
    // setters and getters
}
// 创建接口 ShopService
public interface ShopService {
    public Order createOrder(Integer userId, Goods goods);
    public List<Address> showAddress(Integer userId);
}
// 创建接口实现类 ShopServiceImpl
public class ShopServiceImpl implements ShopService {
    // 远程接口作为属性
    private OrderService orderService;
    private UserInfoService userInfoService;
    
    public void setOrderService(OrderServcie orderService) {
        this.orderService = orderService;
    }
    
    public void setUserInfoServcie(UserInfoService userInfoService) {
        this.userInfoService = userInfoService;
    }
    
    @Override
    public Order createOrder(Integer userId, Goods goods) {
        Order order = orderService.createOrder(userId, goods.getName(), goods.getPrice.floatValue(), goods.getAmount());
        return order;
    }
    
    @Override
    public List<Address> showAddress(Integer userId) {
        List<Address> addresses = userInfoService.queryAddress(userId);
        return addresses;
    }
}
// 创建类 ShopController
@Controller
@RequestMapping("/shop")
public class ShopController {
    @Autowired
    private ShopService shopService;
    
    @RequestMapping("/buy")
    public ModelAndView buyGoods(Integer userId, Goods goods) {
        ModelAndView mv = new ModelAndView();
        Order order = shopService.createOrder(userId, goods);
        mv.addObject("order", order);
        mv.setViewName("view-order");
        return mv;
    }
    
    @RequestMapping("/addresses")
    public ModelAndView showListAddress(Integer userId) {
        ModelAndView mv = new ModelAndView();
        List<Address> addresses = shopService.showAddress(userId);
        mv.addObject("addresses", addresses);
        mv.setViewName("view-address");
        return mv;
    }
}
<!-- view-order.jsp -->
显示订单信息:<br/>
<div align="center" style="margin-left:400px">
    view-order.jsp<br/>
    <h5>
        您的订单已经生成
    </h5>
    <h5>
        订单号:${order.id}
    </h5>
    <h5>
        商品:${order.goodName}
    </h5>
</div>
<!-- view-address.jsp -->
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
显示地址信息<br/>
<div style="margin-left:400px">
    view-address.jsp<br/>
    <c:forEach items="${addresses}" var="address">
    	<h5>
            收件人:${address.name}
        </h5>
        <h5>
            收件地址:${address.city}${address.street}
        </h5>
        <h5>
            联系电话:${address.mobile}
        </h5>
        <h5>
            邮编:${address.zipcode}
        </h5>
        <h5>
            默认地址:${address.use}
        </h5>
    </c:forEach>
</div>
<!-- Spring配置文件 -->
<!-- 声明组件扫描器 -->
<context:component:scan base-package="com.bjpowernode.controllers" />
<!-- 声明视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="prefix" value="/WEB-INF/jsp/" />
    <property name="suffix" value=".jsp" />
</bean>
<!-- 声明注解驱动 -->
<mvc:annnotation-driven />
<!-- dubbo配置文件 -->
<dubbo:application name="ch05-link-consumer" />

<dubbo:reference id="remoteUserService" interface="com.bjpowernode.service.UserInfoService" url="dubbo://localhost:20880" registry="N/A" check="false" />

<dubbo:reference id="remoteOrderService" interface="com.bjpowernode.service.OrderService" url="dubbo://localhost:20881" registry="N/A" check="false" />

<bean id="shopServcie" class="com.bjpowernode.service.impl.ShopServiceImpl">
	<property name="orderService" ref="remoteOrderService" />
    <property name="userInfoService" ref="remoteUserService" />
</bean>
<!-- web.xml -->
<servlet>
	<servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    	<param-name>contextConfigLocation</param-name>
        <param-value>classpath:dispatcherServlet.xml,classpath:dubbo-consume.xml</param-value>
        <load-on-startuip>1</load-on-startuip>
    </init-param>
</servlet>
<servlet-mapping>
	<servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Dubbo常用标签

Dubbo中常用标签分为三个类别:

  • 公用标签
  • 服务提供者标签
  • 服务消费者标签
公用标签

<dubbo:application />

配置应用信息:<dubbo:application name=“服务的名称”/>

<dubbo:registry />

配置注册中心:<dubbo:registry address=“ip:port” protocol=“协议”/>

服务提供者标签

<dubbo:protocol />

配置访问服务提供者的协议信息:<dubbo:protocol name=“协议名称”port=“端口号”/>

<dubbo:service />

配置暴露的服务:<dubbo:service interface=“服务接口名”ref=“服务实现对象 bean”/>

服务消费者标签

<dubbo:reference />

配置服务消费者引用远程服务:<dubbo:reference id=“服务引用的 bean的 id”interface=“服务接口名”/>

Dubbo常用配置

关闭检查 check

dubbo在缺省状态下启动时会检查依赖的服务是否可用,不可用时会抛出异常,组织 Spring初始化,以便于上线时及早发现问题。默认为 check=true,通过设置 check=false关闭检查。

比如,测试时出现了循环依赖,必须有一方先启动。

请求重试 retries

远程服务调用重试次数,不包括第一次调用,默认是 2 次。

<dubbo:reference retries=“5”/>

超时时间 timeout

由于网络或服务端不可靠,会导致调用出现一种不确定的中间状态(超时)。为了避免导致客户端资源耗尽(线程挂起),必须设置超时时间。

timeout = 调用远程服务超时时间(毫秒值)

<!--  消费者端 -->
<dubbo:reference interface="com.xxx.XxxService" timeout="2000" />
<!--  生成者端 -->
<dubbo:server interface="com.xxx.XxxService" timeout="2000" />

版本号 version

每个接口都应定义版本号,为后续不兼容升级提供可能。当一个接口有不同的实现类,为了区分不同的接口实现使用 version。特别是项目需要把早期接口的实现全部换位新的实现类的情况。

可以用版本号从早期的接口实现过渡到新的接口实现,版本号不同的服务相互间不引用。

Dubbo注册中心

注册中心概述

对于提供者,它需要发布服务,而且由于应用系统的复杂性,服务数量、类型不断膨胀;对于消费者,它只关心自己如何获取到需要的服务,而面对复杂的应用系统,需要管理大量的服务调用。

而且,对于服务提供方和服务消费方来说,他们还有可能兼具这两种角色,即既需要提供服务,又需要消费服务。通过将服务统一管理起来,可以有效地优化内部应用对服务发布 / 使用的流程和管理。服务注册中心可以通过特定协议来完成服务对外的统一。

Dubbo提供如下可供选择的注册中心:

  • Multicast注册中心:组播方式
  • Redis注册中心:使用 Redis作为注册中心
  • Simple注册中心:就是 dubbo的一个服务,提供查找服务的功能
  • ZooKeeper注册中心:使用 Zookeeper作为注册中心

ZooKeeper介绍

Zookeeper是 Apache Hadoop的子项目,是一个高性能的、分布式的、开放源码的树形的目录服务,支持变更推送,适合作为 dubbo服务的注册中心,工业强度高,可用于生成环境。

Zookeeper就像是 Windows的资源管理器,包含了所有的文件。服务提供者和服务消费者都在注册中心中登记。注册中心使用“心跳”机制更新提供者的状态,会自动删除不能使用的提供者。

ZooKeeper安装

ZooKeeper下载

官网下载地址

Windows平台安装 ZooKeeper

1)解压下载的文件

2)修改 ZooKeeper-3.6.2/conf/目录下的配置文件

​ 复制 zoo-sample.cfg 改名为 zoo.cfg

​ 文件内容:

  • tickTime:心跳时间,毫秒值。ZooKeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是说每个 tickTime时间就会发送一个心跳,表明存活状态。
  • dataDir:数据目录,可以是任意目录。存储 ZooKeeper的快照文件, pid文件。默认为 /tmp/zookeeper,建议改为安装目录下的新建文件夹 data
  • clientPort:客户端连接 zookeeper的端口,即 zookeeper对外的服务端口,默认为 2181

Linux平台安装 ZooKeeper

Linux平台安装 ZooKeeper需要先安装好 jdk,并配置好 JAVA_HOME

1)解压文件到指定目录下

1614566613101

2)配置文件

​ 在 zookeeper的 conf目录下,将 zoo_sample.cfg 改名为 zoo.cfg

3)类似Windows,修改配置文件

4)切换到 bin目录下启动

5)切换到 bin目录下关闭

使用 ZooKeeper

<!-- 在生产者和消费者处修改 pom.xml -->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>4.1.0</version>
</dependency>

提供者:

<!-- 修改 dubbo配置文件 -->
<dubbo:application name="ch07-zk-userservice-provider"/>
<!-- 创建注册中心 zookeeper -->
<dubbo:registry address="zookeeper://localhost:2181"/>
<!-- 创建访问的协议 -->
<dubbo:protocol name="dubbo" port="20881" />
<dubbo:service interface="com.bjpowernode.service.OrderService" ref="orderService" />
<bean id="orderService" class="com.bjpowernode.service.impl.OrderServiceImpl"/>

消费者:

<!-- 修改 dubbo配置文件 -->
<dubbo:application name="ch08-zk-consumer"/>
<dubbo:registry address="zookeeper://localhost:2181"/>
<dubbo:reference interface="com.bjpowernode.service.OrderService" id="orderService"/>

注册中心的高可用

高可用通常用来描述一个系统经过专门的设计,从而减少不能提供服务的时间,而保持其服务的高度可用性。

ZooKeeper是高可用的、健壮的。ZooKeeper宕机后,正在运行中的 dubbo服务仍然可以正常访问。

健壮性:

  • 监控中心宕机后不影响使用,只是丢失部分采样数据
  • 注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
  • 服务提供者无状态,任意一台宕机后,不影响使用
  • 服务提供者全部宕机后,消费者应用将无法正常使用,并会无限次重连等待服务提供者回复

Dubbo监控中心

什么是监控中心

dubbo的使用,其实只需要有注册中心、消费者、提供者这三者即可,但是并不能看到有哪些消费则会和提供者。为了更好的调试、发现问题、解决问题,引入了 dubbo-admin。通过 dubbo-admin可以对消费者和提供者进行管理,在 dubbo应用部署做动态的调整,服务的管理。

dubbo-admin:图形化的服务管理页面,安装时需要指定注册中心地址,即从注册中心获取到所有的提供者、消费者进行配置管理。

dubbo-monitor-simple:简单的监控中心

发布配置中心

1)下载监控中心

2)可以导入到maven中并进行编译为war包,后发布到tomcat中

3)修改 dubbo-admin下 WEB-INF/dobbo-properties文件,内容如下

dubbo.registry.address=zookeeper://127.0.0.1:2181
dubbo.admin.root.password=root
dubbo.admin.guest.password=guest

4)运行dubbo-admin

​ 注意:应先启动注册中心,在启动服务者和消费者,再打开dubbo-admin

Dubbo负载均衡

集群

集群是一种服务器结构,把一组多个计算机,包括硬件和软件组织再一个网络中,相互连接起来共同完成某个工作。对用户来说使用的是同一个计算机。集群对用户来说是透明的。

负载均衡

负载均衡是以集群为前提的,将负载(工作任务)进行平衡、分摊到多个操作单元上进行执行。

对于网络应用而言,并不是一开始就要负载均衡,而是当网络应用的访问量不断增长,单个处理单元无法满足负载需求时,网络应用流量将要出现瓶颈时,负载均衡才会起到作用。一般通过一个或者多个前端负载均衡器,将工作负载分发到后端的一组服务器上,从而达到整个系统的高性能和高可用性。

负载均衡有两方面的含义:首先,单个重负载的工作分配到堕胎服务器做并行处理,每个服务器处理结束后将结果汇总,返回给用户,系统处理能力将大幅度提高,这就是集群(cluster)技术带来的优势。其次,大量的并发访问或数据流量分担到多台服务器分别处理,减少用户等待响应的时间,每个访问分配给不同的服务器处理。

负载策略

Dubbo提供了多种均衡策略,缺省为 random随机调用

  • Random LoadBalance随机,按权重设置随机概率,在一个截面上碰撞的概率高,但调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重
  • RoundRbin LoadBalance轮循,按公约后的权重设置轮循比率,。存在满的提供者累积请求问题,如:第二胎机器很慢但是没宕机,当请求调到第二台时就卡在那里,久而久之,所有请求都开在调到第二台上了。
  • LeastActive LoadBalance最少活跃调用数,相同活跃数的随机,活跃数指调用前后技术差。使慢的提供者收到更少的请求,因为越慢的提供者的调用前后计数差会越大。
  • ConsistentHash LoadBalance一致性 Hash,相同参数的请求总是发送到同一提供者。缺省制度一第一个参数 Hash

配置方式

<dubbo:service interface="com.xxx.XxxService" loadbalance="roundrobin"/>

或者

<dubbo:reference interface="com.xxx.XxxService" loadbalance="roundrobin"/>

  • 随机:loadbalance =“round”
  • 轮循:loadbalance =“roundrobin”
  • 最少活跃:loadbalance =“leastactive”
  • 一致性 Hash:loadbalance =“consistenthash”
posted @   小么VinVin  阅读(172)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗
点击右上角即可分享
微信分享提示