一、Dubbo概念
1.1 什么是分布式系统

单机架构
一个系统业务量很小的时候所有的代码都放在一个项目中就好了,然后这个项目部署在一台服务器上,整个项目所有的服务都由这台服务器提供。
缺点:
- 服务性能存在瓶颈
 - 代码量庞大,系统臃肿,牵一发动全身
 - 单点故障问题
 
集群架构
单机处理到达瓶颈的时候,你就把单机复制几份,这样就构成了一个集群。

集群存在的问题:
当你的业务发展到一定程度的时候,你会发现一个问题无论怎么增加节点,貌似整个集群性能的提升效果并不明显了。这时候,你就需要使用分布式架构了。
什么是分布式

分布式架构就是将一个完整的系统,按照业务功能,拆分成一个个独立的子系统,在分布式结构中,每个子系统就被称为“服务”。这些子系统能够独立运行在web容器中,它们之间通过RPC方式通信。
分布式的优势:
- 系统之间的耦合度大大降低,可以独立开发、独立部署、独立测试,系统与系统之间的边界非常明确,排错也变得相当容易,开发效率大大提升。
 - 系统之间的耦合度降低,从而系统更易于扩展。我们可以针对性地扩展某些服务。
 - 服务的复用性更高。比如,当我们将用户系统作为单独的服务后,该公司所有的产品都可以使用该系统作为用户系统,无需重复开发。
 
三者区别

总结:
将一套系统拆分成不同子系统部署在不同服务器上(这叫分布式),然后部署多个相同的子系统在不同的服务器上(这叫集群)。
集群:多个人在一起作同样的事 。
分布式 :多个人在一起作不同的事 。
1.2 什么是RPC

RPC(Remote Procedure Call)远程过程调用,它是一种通过网络从远程计算机程序上请求服务。RPC是一种技术思想而非一种规范或协议。


大白话理解就是:RPC让你用别人家的东西就像自己家的一样。
RPC两个作用:
- 屏蔽远程调用跟本地调用的区别,让我们感觉就是调用项目内的方法
 - 隐藏底层网络通信的复杂性,让我们更加专注业务逻辑。
 
常见 RPC 技术和框架:
- 阿里的 Dubbo/Dubbox、Google gRPC、Spring Cloud。
 
1.3 Dubbo简介

Dubbo是什么
Apache Dubbo是一款高性能、轻量级的开源服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
Dubbo能做什么
- 透明化的远程方法调用,就像调用本地方法一样调用远程方法,只需简单配置,没有任何API侵入。
 - 软负载均衡及容错机制,可在内网替代F5等硬件负载均衡器,降低成本,减少单点。
 - 服务自动注册与发现,不再需要写死服务提供方地址,注册中心基于接口名查询服务提供者的IP地址,并且能够平滑添加或删除服务提供者,就是将服务注册到zookeeper。
 

注意:
Dubbo采用全Spring配置方式,透明化接入应用,对应用没有任何API侵入,只需用Spring加载Dubbo的配置即可。
Dubbo支持的协议
协议是两个网络实体进行通信的基础,数据在网络上从一个实体传输到另一个实体,以字节流的形式传递到对端。在这个字节流的世界里,如果没有协议,就无法将这个一维的字节流重塑成为二维或者多维的数据结构以及领域对象。

Dubbo支持的协议
- Dubbo协议
 - Hessian协议
 - HTTP协议
 - RMI协议
 - WebService协议
 - Memcached协议
 - Redis协议
 
推荐:
使用Dubbo协议。
1.4 Dubbo核心组件

注册中心Registry
在Dubbo微服务体系中,注册中心是其核心组件之一。Dubbo通过注册中心实现了分布式环境中各服务之间的注册与发现,是各个分布式节点之间的纽带。

其主要作用如下:
- 动态加入:一个服务提供者通过注册中心可以动态地把自己暴露给其他消费者,无须消费者逐个去更新配置文件。
 - 动态发现:一个消费者可以动态地感知新的配置、路由规则和新的服务提供者,无须重启服务使之生效。
 - 动态调整:注册中心支持参数的动态调整,新参数自动更新到所有相关服务节点。
 - 统一配置:避免了本地配置导致每个服务的配置不一致问题。
 
常见得注册发现服务
常见的注册中心有zookeeper 、eureka、consul、etcd。

服务提供者Provider
服务的提供方
服务消费者Consumer
调用远程服务的服务消费方,包租婆就是生产者,中介就是注册中心,租客就是消费者。

监控中心Monitor
主要负责监控统计调用次数和调用时间等。
Dubbo 工作流程

二、Dubbo配置开发环境
2.1 Docker安装zookeeper

下载Zookeeper镜像
docker pull zookeeper:3.5.9
 
启动运行容器
docker run --name zk -d -p 2181:2181 zookeeper:3.5.9
 
参数:
- --name zk:镜像名称zk
 - -d:守护进程运行
 - -p:映射端口号
 
进入容器
docker exec -it zk /bin/bash
 
参数:
- exec:在运行的容器中执行命令
 - -it:交互式
 
2.2 Docker安装Dubbo-Admin管理平台

Dubbo-admin管理平台,图形化的服务管理页面,安装时需要指定注册中心地址,即可从注册中心中获取到所有的提供者/消费者进行配置管理。
下载Dubbo-Admin镜像
docker pull docker.io/apache/dubbo-admin
 
启动运行容器
docker run -d \
--name dubbo-admin \
-p 9600:8080 \
-e admin.registry.address=zookeeper://192.168.66.100:2181 \
-e admin.config-center=zookeeper://192.168.66.100:2181 \
-e admin.metadata-report.address=zookeeper://192.168.66.100:2181 \
--restart=always \
docker.io/apache/dubbo-admin
 
参数:
- --restart:always 容器退出时总是重启
 - admin.registry.address:注册中心
 - admin.config-center:配置中心
 - admin.metadata-report.address:元数据中心
 
浏览器访问可视化界面
浏览器输入http://192.168.66.100:9600,用户名root 密码 root

需要注意的是需要将docker下的zookeeper和dubbo-admin服务都开启。
三、Dubbo入门案例
3.1 需求介绍
单体架构

分布式架构

订单服务
功能如下:
- 创建订单
 - 根据用户id查询订单详情
 用户服务
- 创建订单
 - 根据用户id查询订单详情
 
3.2 配置开发环境
1.创建Maven项目
通过创建多模块项目实现不同的模块。
2.设置JDK版本
    <profiles>
        <profile>
            <id>jdk-1.8</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <jdk>1.8</jdk>
            </activation>
            <properties>
                <maven.compiler.source>1.8</maven.compiler.source>
                <maven.compiler.target>1.8</maven.compiler.target>
                <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
            </properties>
        </profile>
    </profiles>
 
maven默认的jdk是1.5版本
3.设置项目Dubbo-demo为父项目
<packaging>pom</packaging>
 
只需要将项目的打包方式设置为pom即可。此时父项目的src可删除。
4.修改字符编码
File->Settings->Editor->File Encodings

5.配置忽略描述文件
File->Editor->File Types->Ignore Files and Folders
*.md;*.gitignore;.mvn;.idea;
6.设置aliyun阿里云maven本地仓库镜像
    <repositories>
        <repository>
            <id>alimaven</id>
            <name>aliyun maven</name>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories> 
3.3 生产者工程配置
1.创建服务消费者SpringBoot项目模块
2.为生产者项目指定父项目
    <parent>
        <groupId>com.zj</groupId>
        <artifactId>Dubbo-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent> 
修改子模块的父项目。
3. 指定父项目的子模块
加入下面配置
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent> 
4.父项目加入生产者模块
在pom.xml文件中加入
    <modules>
        <module>dubbo-producer</module>
    </modules> 
5.在生产者项目引入依赖
        <!-- 整合dubbo -->
        <dependency>
            <groupId>io.dubbo.springboot</groupId>
            <artifactId>spring-boot-starter-dubbo</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!-- zookeeper客户端 -->
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.7</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency> 
3.4 生产者代码编写
1.创建订单实体类
package com.zj.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
 * 订单模型
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order implements Serializable {
  // 订单id
  private Long id;
  // 用户id
  private Long userId;
  // 订单总价格
  private Double prict;
  // 收货人手机号
  private String mobile;
  // 收货人地址
  private String address;
  // 支付类型 1:微信 2:支付宝
  private Integer pay_method;
} 
网络中对象的传递是字节流因此需要实现序列化接口。
2.编写订单接口
package com.zj.service;
import com.zj.pojo.CommonResult;
import com.zj.pojo.Order;
public interface IOrderService {
  //创建订单
  CommonResult<Order> createOrders(Order order);
  //根据用户id查询订单详情
  CommonResult<Order> findByUserId(Long userId);
} 
3.创建统一返回结果集实体类
/**
 * 统一返回结果集
 * @param <T>
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> implements Serializable {
   // 返回结果编码
   private Integer code;
   // 返回结果描述
   private String message;
   // 数据
   private T data;
   private CommonResult(Integer code,String message){
     this(code,message,null);
   }
}
 
4.编写订单业务实现类
package com.zj.service;
import com.alibaba.dubbo.config.annotation.Service;
import com.zj.pojo.CommonResult;
import com.zj.pojo.Order;
@Service
public class OrderServiceImpl implements IOrderService {
    /**
     * 创建订单
     * @param order
     */
    @Override
    public CommonResult<Order> createOrders(Order order) {
       //TODO 模拟数据库操作
        CommonResult<Order> result = new CommonResult<Order>();
        result.setCode(200);
        result.setMessage("订单创建成功!");
        result.setData(null);
        return result;
    }
    /**
     * 根据用户id查询订单
     * @param userId
     * @return
     */
    @Override
    public CommonResult<Order> findByUserId(Long userId) {
        //TODO 模拟数据库操作
        CommonResult<Order> commonResult = new CommonResult<Order>();
        // 返回结果编码
        commonResult.setCode(200);
        // 返回结果描述信息
        commonResult.setMessage("查询成功");
        // 返回结果集
        Order order = new Order();
        order.setId(1L);
        order.setUserId(1L);
        order.setPrict(121.1);
        order.setMobile("18588888888");
        order.setAddress("北京市海淀区中关村");
        order.setPay_method(1);
        commonResult.setData(order);
        return commonResult;
    }
}
 
注意:
@Service注解的作用:将这个类提供的方法对外发布,将访问该方法的地址IP,端口路径注册到注册中心zookeeper
该注解不是spring的注解而是dubbo的注解:
com.alibaba.dubbo.config.annotation.Service;
5.生产者编写配置文件
#配置项目名称
spring.dubbo.application.name = order-producer
#配置注册中心(告诉项目注册中心在哪)
spring.dubbo.registry.address=zookeeper://192.168.66.100
spring.dubbo.registry.port=2181
#指定Dubbo使用的协议和端口号
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20880
#指定注册到zookeeper上的超时时间
spring.dubbo.registry.timeout=10000
#配置Dubbo包扫描
spring.dubbo.scan=com.zj.service
 
6.启动生产者项目

3.5消费者工程配置
1.创建服务消费者SpringBoot项目模块
2.为消费者项目指定父项目
    <parent>
        <groupId>com.zj</groupId>
        <artifactId>dubbo-demo</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent> 
3. 指定父项目的子模块
    <modules>
        <module>dubbo-producer</module>
        <module>dubbo-consumer</module>
    </modules> 
4.在消费者模块引入依赖
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 整合dubbo -->
        <dependency>
            <groupId>io.dubbo.springboot</groupId>
            <artifactId>spring-boot-starter-dubbo</artifactId>
            <version>1.0.0</version>
        </dependency>
        <!-- zookeeper客户端 -->
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.7</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>> 
因为消费者需要调用生产者的接口.
3.6 消费者代码编码
1.编写用户实体类
/**
 * 用户模型
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
  
  //用户id
  private Long id;
  // 用户名字
  private String name;
}
 
2.编写用户接口
/**
 * 用户接口
 */
public interface IUserService {
  //根据用户id查询订单详情
  CommonResult<Order> findByUserId(Long id);
}
 
3.编写用户接口实现类
package com.zj.service;
import com.alibaba.dubbo.config.annotation.Reference;
import com.zj.pojo.CommonResult;
import com.zj.pojo.Order;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements IUserService {
  //引入订单服务(远程调用)
  @Reference  //从zookeeper注册中心获取访问IOrderService的url,通过rpc协议远程调用,将结果封装为代理对象赋给变量
  private IOrderService iOrderService;
  /**
   * 根据用户id查询订单
   * @param id 用户id
   * @return
   */
  @Override
  public CommonResult<Order> findByUserId(Long id) {
       return iOrderService.findByUserId(id);
   }
}
 
这里的service注解是spring注解因为需要controller调用service层.
4.编写用户控制层
package com.zj.controller;
import com.zj.pojo.CommonResult;
import com.zj.pojo.Order;
import com.zj.service.IUserService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
 * 用户控制层
 */
@RestController
public class UserController {
  
  @Resource
  private IUserService iUserService;
  /**
   * 根据用户ID查询用户订单详情
   * @param userId 用户id
   * @return
   */
  @GetMapping("findByUserId")
  public CommonResult<Order> findByUserId(Long userId){
    return iUserService.findByUserId(userId);
   }
  
}
 
5.加入Dubbo配置
#配置项目名称
spring.dubbo.application.name = order-consumer
#配置注册中心(告诉项目注册中心在哪)
spring.dubbo.registry.address=zookeeper://192.168.66.100
spring.dubbo.registry.port=2181
#指定Dubbo使用的协议和端口号
spring.dubbo.protocol.name=dubbo
spring.dubbo.protocol.port=20881
#指定注册到zookeeper上的超时时间
spring.dubbo.registry.timeout=10000
#配置Dubbo包扫描
spring.dubbo.scan=com.zj.service
#端口
server.port=8081
 
6.访问localhost:8081/find/1


3.7 idea开启Dashboard面板
普通的Run面板

Run Dashboard面板

修改配置文件
在.idea/workspace.xml 文件中添加
 <component name="RunDashboard">
 <option name="ruleStates">
  <list>
   <RuleState>
    <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
   </RuleState>
   <RuleState>
    <option name="name" value="StatusDashboardGroupingRule" />
   </RuleState>
  </list>
 </option>
 <option name="configurationTypes">
 <set>
  <option value="SpringBootApplicationConfigurationType" />
 </set>
</option>
</component>
 

四.Dubbo高级特性
 4.1 序列化协议安全
 
 
为什么需要序列化
网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。

总结:
序列化就是将对象转换成二进制数据的过程,而反序列就是反过来将二进制转换为对象的过程。
序列化反序列过程

流程:
不妨借用个例子帮助你理解,比如发快递,我们要发一个需要自行组装的物件。发件人发之前,会把物件拆开装箱,这就好比序列化;这时候快递员来了,不能磕碰呀,那就要打包,这就好比将序列化后的数据进行编码,封装成一个固定格式的协议;过了两天,收件人收到包裹了,就会拆箱将物件拼接好,这就好比是协议解码和反序列化。
4.2 地址缓存

地址缓存
(面试)注册中心挂了,服务是否可以正常访问?
答案:
可以.因为dubbo服务消费者在
第一次调用时,会将服务提供方地址缓存到本地,以后在调用则不会访问注册中心。服务提供者地址发生变化时,注册中心会通服务消费者。
4.3 超时时间和覆盖关系

超时机制

问题:
- 服务消费者在调用服务提供者的时候发生了阻塞、等待的情形,这个时候,服务消费者会一直等待下去。
 - 在某个峰值时刻,大呈的请求都在同时请求服务消费者,会造成线程的大呈堆积,势必会造成雪崩。
 - dubbo利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
 
配置超时时间
生产者端设置超时时间(建议)
使用timeout属性配置超时时间,默认值1000,单位毫秒。
@Service(timeout = 3000) //当前服务3秒超时
public class OrderServiceImpl implements IOrderService {} 
消费端设置超时时间(不建议)
@Reference(timeout = 2000)// 远程注入
private IOrderService iOrderService;
 
4.4重试机制

超时问题:
如果出现网络抖动,则会出现请求失败。
如何解决
Dubbo提供重试机制来避免类似问题的发生。
重试机制配置
@Service(timeout = 3000,retries = 2)
 
注意:
Dubbo在调用服务不成功时,默认会重试2次。
4.5 多版本灰度发布

Dubbo提供多版本的配置,方便我们做服务的灰度发布,或者是解决不兼容的问题。
灰度发布(金丝雀发布):
当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。就是王者的体验服!!

版本迁移步骤
- 在低压力时间段,先升级一半提供者为新版本
 - 再将所有消费者升级为新版本
 - 然后将剩下的一半提供者升级为新版本
 
老版本服务提供者配置
@Service(version = "1.0.0") 
新版本服务提供者配置
@Service(version = "2.0.0") 
新版本服务消费者配置
@Reference(version = "2.0.0")
private IOrderService iOrderService;// 订单服务
 
如果不需要区分版本,可以按照以下的方式配置 :
@Reference(version = "*")
private IOrderService iOrderService;// 订单服务
 
4.6 负载均衡

Dubbo是一个分布式服务框架,能避免单点故障和支持服务的横向扩容。一个服务通常会部署多个实例。

问题:
如果服务B的请求量很大导致服务器宕机,则订单服务生产者会出现单点故障。如何从多个服务 Provider 组成的集群中挑选出一个进行调用,就涉及到一个负载均衡的策略。
Dubbo内置负载均衡策略

如果增加服务,搭建服务的集群的话,消费者E到底调用的是哪一个服务器的服务呢?Dubbo为解决该问题提供了一个方案,也就是负载均衡.
- RandomLoadBalance:随机负载均衡,随机的选择一个,默认负载均衡。
 - RoundRobinLoadBalance:轮询负载均衡。
 - LeastActiveLoadBalance:最少活跃调用数,相同活跃数的随机。
 - ConsistentHashLoadBalance:一致性哈希负载均衡,相同参数的请求总是落在同一台机器上。
 
负载均衡策略配置
生产者服务
@Service(timeout = 3000,retries = 3,loadbalance = "roundrobin")
 
消费者服务
@Reference(timeout = 2000,loadbalance = "roundrobin")
private IOrderService iOrderService;
 
参数:
- random:随机负载均衡
 - leastactive:最少活跃调用数,相同活跃数的随机
 - roundrobin:轮询负载均衡
 - consistenthash:一致性哈希负载均衡
 
4.7 集群容错

集群容错模式
Dubbo框架为服务集群容错提供了一系列好的解决方案,在此称为dubbo服务集群容错模式。

容错模式
- Failover Cluster:失败重试。默认值。当出现失败,重试其它服务器,默认重试2次,使用retries配置。一般用于读操作
 - Failfast Cluster : 快速失败,只发起一次调用,失败立即报错。通常用于写操作。
 - Failsafe Cluster : 失败安全,出现异常时,直接忽略。返回一个空结果。日志不重要操作。
 - Failback Cluster : 失败自动恢复,后台记录失败请求,定时重发。非常重要的操作,发送请求到成功未知。
 - Forking Cluster:并行调用多个服务器,只要有一个成功即返回。
 - Broadcast Cluster:广播调用所有提供者,逐个调用,任意一台报错则报错。 同步要求高的可以使用这个模式。
 
集群容错配置
在消费者服务配置
@Reference(cluster = "failover")
private IOrderService iOrderService;
 
4.8 服务降级

什么是服务降级
服务降级,当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,关闭除了核心业务之外的服务以此释放服务器资源以保证核心任务的正常运行。

两种场景:
- 当下游的服务因为某种原因响应过慢,下游服务主动停掉一些不太重要的业务,释放出服务器资源,增加响应速度!
 - 当下游的服务因为某种原因不可用,上游主动调用本地的一些降级逻辑,避免卡顿,迅速返回给用户!
 
为什么需要降级
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。
服务降级方式
第一种:mock = "force:return null"
@Reference(timeout = 2000,mock = "force:return null")
private IOrderService iOrderService; 
含义:
表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不重要服务不可用时对调用方的影响。
第二种 :mock = "mock=fail:return null"
  @Reference(timeout = 2000,mock = "mock=fail:return null") 
  private IOrderService iOrderService; 
含义:
表示消费方对该服务的方法调用在失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
4.9 服务限流

生活中的限流
春运,一项全人类历史上最大规模的迁移活动,抢火车票一直是每年跨年以后的热点话题。例如,通过一系列复杂的验证手段来实现用户的限流。

限流算法
漏桶算法

原理:
漏桶算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
令牌桶算法

原理:
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
漏桶 vs 令牌桶的区别
漏桶的天然特性决定了它不会发生突发流量,就算每秒1000个请求到来,那么它对后台服务输出的访问速率永远恒定。而令牌桶则不同,其特性可以“预存”一定量的令牌,因此在应对突发流量的时候可以在短时间消耗所有令牌,其突发流量处理效率会比漏桶高,但是导向后台系统的压力也会相应增多。
服务限流实现
为了防止某个消费者的QPS或是所有消费者的QPS总和突然飙升而导致的重要服务的失效,系统可以对访问流量进行控制,这种对集群的保护措施称为服务限流。
并发控制
服务生产者添加如下注解
@Service(executes = 10)
 
注意:
服务端并发执行(或占用线程池线程数)不能超过10个
连接控制
@Service(actives= 10)
 
注意:
占用连接的请求的数不能超过10个。
4.10 结果缓存

结果缓存,用于加速热门数据的访问速度,Dubbo提供声明式缓 存,以减少用户加缓存的工作量。

Dubbo提供了三种结果缓存机制:
- lru: 基于最近最少使用原则删除多余缓存,保持最热的数据被缓存。
 - threadlocal: 当前线程缓存,比如一个页面渲染,用到很多portal,每个portal都要去查用户信 息,通过线程缓存,可以减少这种多余访问。
 - jcache: 与JSR107集成,可以桥接各种缓存实现。
 
配置缓存
@Reference(cache="lru")
 
五、Dubbo实战
5.1 项目介绍
需求
完成用户表的CRUD操作。
技术架构

项目结构设计
本项目采用maven分模块开发方式,即对整个项目拆分为几个maven工程,每个maven工程存放特定的一类代码。

解释:
- user_api:公共接口
 - user_consumer:服务消费者
 - user_provider:服务生产者
 
项目目的
了解分布式项目的构建方式。
5.2 构建dubbo_parent工程

 
修改pom文件,因为是父项目,所有将项目的打包类型设置为pom
<packaging>pom</packaging> 
设置项目依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zj</groupId>
    <artifactId>dubbo_parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--如果是maven项目的花,在创建子项目的时候会自动在父项目指定子项目
    如果是springboot项目的话需要手动指定子项目-->
    <modules>
        <module>user_api</module>
        <module>user_provider</module>
    </modules>
    <packaging>pom</packaging>
    <properties>
        <dubbo.spring.starter.version>2.7.6</dubbo.spring.starter.version>
        <dubbo.registry.zookeeper.version>2.7.6</dubbo.registry.zookeeper.version>
        <mybatisplus.spring.starter.version>3.5.0</mybatisplus.spring.starter.version>
        <mysql.connector.version>5.1.49</mysql.connector.version>
    </properties>
    <dependencyManagement>
<!-- dependencyManagement 这个标签的作用就是给定版本的,所以不会下载的,都是从仓库获取依赖,但是你是第一次,仓库没有这些依赖,所以先下载之后在将这个标签注解解-->
        <dependencies>
            <!-- Dubbo 依赖 -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-spring-boot-starter</artifactId>
                <version>2.7.6</version>
            </dependency>
            <!-- zookeeper 注册中心 依赖 -->
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo-registry-zookeeper</artifactId>
                <version>2.7.6</version>
            </dependency>
            <!-- Mybatis plus 依赖 -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatisplus.spring.starter.version}</version>
            </dependency>
            <!--MySQL 数据库依赖 -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.connector.version}</version>
            </dependency>
            <!--lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.22</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!--设置JDK版本-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project> 
5.3 构建user_api工程
该工程存放的是公共的接口

 设置依赖,在父项目中已经指定了版本因此不需要再子项目设置版本啦。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>dubbo_parent</artifactId>
        <groupId>com.zj</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>user_api</artifactId>
    <dependencies>
        <!-- Dubbo 依赖 -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
        </dependency>
        <!-- zookeeper 注册中心 依赖 -->
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-registry-zookeeper</artifactId>
        </dependency>
    </dependencies>
</project> 
父项目指定子项目
    <!--如果是maven项目的花,在创建子项目的时候会自动在父项目指定子项目
    如果是springboot项目的话需要手动指定子项目-->
    <modules>
        <module>user_api</module>
    </modules> 
5.4 构建user_consumer工程

 
引入dubbo_parent父工程
    <!--引入父工程-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.zj</groupId>
                <artifactId>dubbo_parent</artifactId>
                <version>1.0-SNAPSHOT</version>
                <type>pom</type>
            </dependency>
        </dependencies>
    </dependencyManagement> 
因为在该项目中默认的父工程是:spring-boot-starter-parent,但是项目还有一个父工程是dubbo_parent,当一个项目出现两个以及以上的父项目的时候使用dependencyManagement标签引入其他父项目。
5.5 构建user_provider工程

 
因为该项目是个逻辑工程因此也需要i指定项目的打包方式为pom,并删除src
<packaging>pom</packaging> 
5.5.1 创建pojo,mapper,provider工程

 
 

5.6 构建实体类
5.6.1 Docker构建Mysql数据库
#创建并启动数据库MySQL
docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
 
参数:
-d:后台运行
--name:数据库名称
-p :端口映射
-e : 设置数据库密码
#如果数据库已经在之前创建好了,只需要启动数据库即可。
ocker start 43(容器ID)
#通过命令行操作mysql
docker exec -it mysql /bin/bash
#登录到mysql容器
mysql -uroot -p123456
 
5.6.2 创建数据库test
create database test;
#选择test数据库
use test;
#创建用户表
CREATE TABLE user
(
   id BIGINT(20) NOT NULL COMMENT '主键ID',
   name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
   age INT(11) NULL DEFAULT NULL COMMENT '年龄',
  PRIMARY KEY (id)
);
#查看全部表
show tabloes; 
注意分号
5.6.3 在pojo项目中创建用户实体类
pojo项目引入lombok依赖
    <dependencies>
        <dependency>
            <groupId>com.zj</groupId>
            <artifactId>pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies> 
package com.zj.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
    // 用户id
    private Long id;
    // 用户名字
    private String name;
    // 用户年纪
    private Integer age;
} 
mapper工程引入pojo工程
        <dependency>
            <groupId>com.zj</groupId>
            <artifactId>pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency> 
5.7 整合Mybaits-plus配置
5.7.1 修改mapper工程pom文件
    <!-- Mybatis plus 依赖 -->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
    </dependency>
    <!--MySQL 数据库依赖 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency> 
在父工程中定义过版本了。
5.7.2 在mapper工程中定义好接口UserMapper
package com.zj;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zj.pojo.User;
/*持久层*/
public interface UserMapper extends BaseMapper<User> {
    
}
 
在provider项目引入mapper项目
    <dependencies>
        <dependency>
            <groupId>com.zj</groupId>
            <artifactId>mapper</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies> 
5.7.3 在provider项目中配置
该项目依赖两个父项目,分别是user_provider和springboot
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
    </parent>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <artifactId>user_provider</artifactId>
                <groupId>com.zj</groupId>
                <version>1.0-SNAPSHOT</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement> 
在该项目中引入springboot核心依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency> 
在该项目中创建启动类providerApplication,添加 @MapperScan 注解,扫描 Mapper 文件夹
package com.zj;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.zj.mapper")
public class providerApplication {
    public static void main(String[] args) {
        SpringApplication.run(providerApplication.class);
    }
}
 
创建配置文件application.properties并配置数据源
################ 配置MySQL数据源 ##############
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.66.100:3306/test?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
 
5.8 创建添加用户接口
5.8.1 在user_api工程引入pojo工程
        <dependency>
            <groupId>com.zj</groupId>
            <artifactId>pojo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency> 
5.8.2 在user_api项目中创建添加用户接口
package com.zj.api;
import com.zj.pojo.User;
public interface addUserService {
    int addUser(User user);
} 
5.8.3 在provider工程中引入user_api工程并实现user_api中的接口
        <dependency>
            <groupId>com.zj</groupId>
            <artifactId>user_api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency> 
package com.zj.service;
import com.zj.mapper.UserMapper;
import com.zj.api.AddUserService;
import com.zj.pojo.User;
import org.apache.dubbo.config.annotation.Service;
import javax.annotation.Resource;
/*将服务信息注册到zookeeper*/
@Service(timeout = 50000)
public class AddUserServiceImpl implements AddUserService {
    @Resource
    private UserMapper userMapper;
    @Override
    public int addUser(User user) {
        return userMapper.insert(user);
    }
}
 
5.8.4 在provider工程中配置Dubbo服务并启动zookeeper容器
################ Dubbo 配置 ####################
#服务的名称
dubbo.application.name=Provider
#  注册中心地址(单机)
dubbo.registry.address=zookeeper://192.168.66.100:2181
#  注册中心地址(集群)
#dubbo.registry.address=zookeeper://192.168.233.130:2181?backup=192.168.233.130:2182,192.168.233.130:2183
dubbo.registry.timeout=50000
#协议
dubbo.protocol.name=dubbo
#dubbo服务端口
dubbo.protocol.port=20880
#包扫描
dubbo.scan.base-packages=com.zj.provider.service 
启动provider项目打开即可。

5.9 创建查询用户接口
5..9.1在user_api项目中创建查询全部用户接口
package com.zj.api;
import com.zj.pojo.User;
import java.util.List;
public interface FindUserService {
    List<User> findAll();
} 
5.9.2在 provider中实现查询用户业务接口
package com.zj.service;
import com.zj.api.FindUserService;
import com.zj.mapper.UserMapper;
import com.zj.pojo.User;
import org.apache.dubbo.config.annotation.Service;
import javax.annotation.Resource;
import java.util.List;
@Service
public class FindUserServiceImpl implements FindUserService {
    @Resource
    private UserMapper userMapper;
    
    @Override
    public List<User> findAll() {
        return userMapper.selectList(null);
    }
} 
启动项目,查看dubbo控制台。

5.10 创建更新用户接口
5.10.1 在 user_api 项目中添加更新用户业务接口
package com.zj.api;
import com.zj.pojo.User;
public interface UpdateUserService {
    /*先查*/
    User preUpdateUser(Integer userId);
    /*再更新*/
    void updateUser(User user);
} 
5.10.2 在 provider 中实现更新用户业务接口
package com.zj.service;
import com.zj.api.UpdateUserService;
import com.zj.mapper.UserMapper;
import com.zj.pojo.User;
import org.apache.dubbo.config.annotation.Service;
import javax.annotation.Resource;
@Service
public class UpdateUserServiceImpl implements UpdateUserService {
    
    @Resource
    private UserMapper userMapper;
    
    @Override
    public User preUpdateUser(Integer userId) {
        return userMapper.selectById(userId);
    }
    @Override
    public void updateUser(User user) {
         userMapper.updateById(user);
    }
}
 
启动项目。

5.11 创建删除用户接口
5.11.1 在user_api项目中添加删除用户业务接口
package com.zj.api;
public interface DeleteUserService {
    int  deleteUser(Long userId);
} 
5.11.2 在provider中实现删除用户业务接口
package com.zj.service;
import com.zj.api.DeleteUserService;
import com.zj.mapper.UserMapper;
import org.apache.dubbo.config.annotation.Service;
import javax.annotation.Resource;
@Service
public class DeleteUserServiceImpl implements DeleteUserService {
    @Resource
    private UserMapper userMapper;
    
    @Override
    public int deleteUser(Long userId) {
        return userMapper.deleteById(userId);
    }
}
 
启动项目。

5.12 user_consumer项目集成Thymeleaf
5.12.1 user_consumer工程pom文件中添加thymeleaf依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency> 
5.12.2 配置视图解析器
默认
spring-boot很多配置都有默认配置,比如默认页面映射路径为
classpath:/templates/*.html  
同样静态文件路径为
classpath:/static/
 
自定义
在application.properties(或者application.yml)中可以配置thymeleaf模板解析器属性.就像使用springMVC的JSP解析器配置一样。
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html 
#开发时关闭缓存,不然没法看到实时页面
spring.thymeleaf.cache=false
 
5.12.3 编写index.html首页
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<a th:href="@{/addUser}">添加用户</a><br>
<a th:href="@{/user/getUser}">查询用户</a>
</body>
</html> 
5.12.4 创建页面跳转 Controller
package com.zj.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class PageController {
   /**
   * 完成页面跳转
   */
   @GetMapping("/{page}")
   public String showPage(@PathVariable String page){
     return page;
   }
   /*忽略图标*/
    @GetMapping("/favicon.ico")
    @ResponseBody
    public String favicon(){
        return " ";
    }
}
 
启动user_cnsumer项目。

5.13 用户添加业务消费者实现
5.13.1 user_consumer项目配置文件中添加Dubbo相关的配置信息
################ Dubbo 配置 ####################
#服务的名称
dubbo.application.name=Consumer
#  注册中心地址(单机)
dubbo.registry.address=zookeeper://192.168.66.100:2181
#  注册中心地址(集群)
#dubbo.registry.address=zookeeper://192.168.233.130:2181?backup=192.168.233.130:2182,192.168.233.130:2183
dubbo.registry.timeout=50000
#协议
dubbo.protocol.name=dubbo
#dubbo服务端口
dubbo.protocol.port=20881
#包扫描
dubbo.scan.base-packages=com.zj.service 
5.13.2 编写adduser.html页面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>添加用户</title>
</head>
<body>
<form th:action="@{/user/addUser}" method="post">
    用户姓名:<input type="text" name="name"/><br/>
    用户年龄:<input type="text" name="age"/><br/>
    <input type="submit" value="OK"/>
</form>
</body>
</html> 
5.13.3 user-consumer项目添加 user_api依赖
        <dependency>
            <groupId>com.zj</groupId>
            <artifactId>user_api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency> 
5.13.4 user_consumer项目编写用户添加接口和实现类
package com.zj.service;
import com.zj.pojo.User;
public interface UserService {
    void addUser(User user);
} 
package com.zj.service.Impl;
import com.alibaba.dubbo.config.annotation.Reference;
import com.zj.api.AddUserService;
import com.zj.pojo.User;
import com.zj.service.UserService;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
    //远程调用生产者接口
    @Reference
    private AddUserService addUserService;
    @Override
    public void addUser(User user) {
      addUserService.addUser(user);
    }
}
 
5.13.5创建控制器
package com.zj.controller;
import com.zj.pojo.User;
import com.zj.service.UserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.annotation.Resource;
/**
 * 处理用户操作控制器
 */
@Controller
@RequestMapping("/user")
public class UserController {
  @Resource
  private UserService userService;
  /**
   * 处理添加用户请求
   */
  @RequestMapping("/addUser")
  public String addUser(User user){
    this.userService.addUser(user);
    return "redirect:/ok";
   }
} 
运行项目即可。

查看数据库是否存在该数据。

查看、修改、删除和添加同理不再赘述。


















