文章目录
一、Dubbo高级特性
IDEA开启Run DashBoard面板
对着父项目右键→open in →directory path;选择父项目,此时进入到文件管理器,进入到dubbo-demo/.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>
重启IDEA,此时就可以了
在开启Run DashBoard前:
在开启Run DashBoard后:
优点:在运行多个时,管理方便。
注意:不要把配置添加的内容放在前面,反正我放了几次都消失了,不起作用,放在中间或者后面就生效了。
1.序列化协议安全
网络传输的数据必须是二进制数据,但调用方请求的出入参数都是对象。
所以需要序列化将对象转化为二进制数据,再通过网络传输;或者通过网络接收到二进制数据,再通过反序列化将二进制数据转化为对象。
序列化,即将对象转化为二进制数据;
反序列化,即将二进制数据转化为对象。
序列化也具有加密的功能,使得数据在网络的传输更加安全。
2.地址缓存
dubbo服务消费者在第一次调用时,会将服务生产者地址缓存到本地,以后再调用时则不会访问注册中心。服务生产者地址发生变化时,注册中心会通知服务消费者。
即消费者通过注册中心调用后会将地址缓存,下次调用则不需要通过注册中心;且当地址发生变化时,注册中心会通知。
当注册中心挂了,因为地址缓存,调用过的服务还能正常使用,没有调用过的则不可以。
3.超时时间和覆盖关系
超时时间:
- 服务消费者在调用服务提供者时,发生阻塞、等待的情形,此时,服务消费者会一直等待下去。
- 在某个峰值时刻,大量请求都在同时请求服务消费者,会造成线程的大量堆积,势必会造成雪崩。
- dubbo利用超时机制来解决这个问题,设置一个超时时间,在这个时间段内,无法完成服务访问,则自动断开连接。
即服务消费者在调用服务生产者可能出现线程阻塞或者等待、高并发导致雪崩的情况。
解决方法:设置超时时间,服务调用时线程阻塞或等待,还有高并发,一般将超时时间设置在服务提供者(@Service);因为生产者服务更清楚自己的服务特性。
覆盖关系:
超时设置的优先级是服务消费者>服务提供者。
4.重试机制
Dubbo提供重试机制是为了避免发生类似于网络抖动导致的请求失败;默认重试2次;
5.多版本
灰度发布(金丝雀发布):
当出现新功能时,会让一部分用户先使用新功能,用户反馈没问题时,再将所有用户迁移到新功能。
版本迁移步骤:
- 在低压力时间段,先升级一半提供者为新版本
- 再将所有消费者升级为新版本
- 最后将另一半提供者升级为新版本
设置消费者版本跟提供者版本:
消费者在@Reference注解上添加版本号;提供者在@Service注解上添加版本号。
不需要区分版本号时,将版本号指定为*
模拟新版本发布的流程:
模拟多版本发布时,提供者可以修改完原先的版本号和dubbo协议的端口号,修改一些内容;然后点击Edit Configurations,点击该服务选择复制;再运行就可以模拟新版本发布。
消费者则只需要在@Reference注解上添加新的版本号即可。
多版本、重试机制和超时时间都在@Service和@Reference中设置,例如@Service(timeout=3000,retries=2,version=“1.0.0”)
6.负载均衡
Dubbo内置负载均衡策略
- RandomLoadBalance:随机负载均衡,随机的选择一个,默认的负载均衡。
- RoundRobinLoadBalance:轮询负载均衡。
- LeastActiveLoadBalance:最少活跃调用数负载均衡,相同活跃数随机。
- ConsistentHashLoadBalance:一致性哈希负载均衡,相同参数的请求总是落在一台。
- 即随机、循环轮着来、调用量最少、相同参数的请求总是访问同一个服务。
例如选择轮询的负载均衡:
@Reference(loadbalance = "roundrobin")
Dubbo负载均衡,是服务消费者在调用服务提供者时的负载均衡。相当于前端调用后端时的负载均衡。
并且服务提供者搭建成集群时,才需要使用Dubbo的负载均衡。
按2下Shift键,选择Classes,输入类名,此时输出Balance可以选择Dubbo具体的负载均衡的类。
7.集群容错
集群调用失败时,提供多种容错方案。默认为failover重试。
幂等操作:
在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。
即一次和多次执行的结果相同,对数据等不会产生影响。
容错模式:
- Failover Cluster:失败重试。当出现失败时,重试其他服务器,默认重试2次,使用retries配置。一般用于读操作(幂等操作)。
- Failfast Cluster:快速失败,只发起一次调用,失败即刻报错。通常用于写操作(非幂等操作)。
- Failsafe Cluster:失败安全,出现异常时,直接忽略,返回一个空结果。用于日志等不重要的操作。
- Failback:失败自动恢复,后台记录失败请求,定时重发。用于非常重要的操作。
- Forking Cluster:并行调用多个服务器,只要有一个成功即返回。
- Broadcast:广播调用所有提供者,逐个调用,任意一台报错则报错。用于同步要求高的。
例如@Reference(cluster=“failfast”)
8.服务降级
服务降级,即当服务器压力剧增的情况下,根据当前业务情况及流量对一些服务和页面有策略的降级,以此来释放服务器资源以保证核心任务的正常运行。
即停掉非核心的服务,保证核心服务正常运行。
服务降级方式:
- mock=force:return null
表示消费方对该服务的方法调用都直接返回null值,不发起远程调用。用来屏蔽不可用时不重要服务对调用方的影响。 - mock=fail:return null
表示消费方对该服务的调用失败后,再返回null值,不抛异常。用来容忍不重要服务不稳定时对调用方的影响。
mock=force:return null,即让b去调用c,b没有调用并返回null。
mock=fail:return null,即让b去调用c,当b调用失败了,只能返回null。
就是说都是为了减少不重要服务的程序运行,节约资源以确保核心服务的正常运行。
例如@Reference(mock = “force:return null”)
此时访问会报500错误。
9.服务限流
限流算法:
- 漏桶算法的原理:水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
- 令牌桶算法的原理:是系统会以一个恒定的速度往桶里放令牌,而如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
漏桶与令牌桶的区别:
漏桶的天然特性决定了他不会发生突发流量,就算每秒1000个请求到来,它对后台输出的访问速率永远恒定。令牌桶则不同,因为其特性可以"预存"一定量的令牌,因此在应对突发流量的时候可以在短时间内消耗所有令牌,其突发流量处理效率会比漏桶高,但是导致后台系统的压力也会相应增多。
漏桶算法,即每秒能处理的请求数量均匀,不会因为请求数量大而加大对后台输出的访问速率。也就是说处理速率相对均匀,无法应对突发流量。
令牌桶算法,即根据生成"预存"的令牌数来确定处理请求的数量,能够处理突发流量。没令牌可取时,拒绝服务。也就是说相当于创建一个缓存区。
即漏桶算法处理请求数量相对均匀的服务;而令牌桶处理请求数量不均匀的服务。
实现:
- 并发控制:@Service(executes=8)
即服务端线程池的最大线程连接数 - 连接控制:@Service(actives=8)
即消费端占用连接的请求数不能超过8个,否则会阻塞,直到超时。
为了防止某个消费者的QPS或是所有消费者的QPS总和突然飙升而导致的重要服务的失效,系统可以对访问流量进行控制,这种对集群的保护措施称为服务限流。
QPS:每秒查询率,即每秒能够处理的流量。
10.结果缓存机制
结果缓存,用于加速热门数据的访问速度,Dubbo提供声明式缓存,以减少用户加缓存的工作量。
Dubbo提供的三种结果缓存机制:
- lru:基于最近最少使用原则删除多余缓存,保持最热的数据被缓存
- threadlocal:当前线程缓存,比如一个页面渲染,用到很多portal,每个portal都要去查用户信息,通过线程缓存,可以减少这种多余访问。
- jcache:与JSR107集成,可以桥接各种缓存实现。
lru,即选择最近最少使用的缓存删除。
threadlocal,即通过当前线程把查到的信息缓存起来,减少对数据库的访问。
jcache,即用来桥接其他缓存实现。
例如@Reference(cache = “lru”)
二、Dubbo实际项目的搭建
1.将模块的功能进行解耦
创建一个dubbo_api子模块,用来存放公共接口;将生产者的接口放在dubbo_api子模块,然后消费者和生产者都将dubbo_api子模块进行依赖注入。
创建一个持久化类子模块dubbo_pojo,引入lombok依赖,将消费者和生产者的pojo类放入持久化类子模块dubbo_pojo中。
此时dubbo_api子模块将持久化类子模块dubbo_pojo和其他公共依赖进行依赖注入。
注意:zkclient跟dubbo此时发生了冲突,可以将zkClient向上移到父项目,duboo则向下移到dubbo_api。
此时项目结构如下图:
dubbo_api,即公共接口子模块,主要功能就是存放生产者的接口服务和注入dubbo_pojo等公共类子模块。
dubbo_pojo,主要功能是存放生产者和消费者的公共pojo类。
2.dubbo_parent项目的创建及其pom.xml文件的配置
- 用重新创建一个dubbo_parent的maven项目;dependencyManagement,该标签下申明的dependencies,Maven并不会去实际下载所依赖的jar包,而是在dependencyManagement中用一个Map记录了jar的三维坐标。
即dependencyManagement内部的jar才会被进行版本管理。
此时dependencyManagement内的jar包版本号可以在properties标签内声明,而dependencyManagement内的依赖版本号则直接引用properties标签内的声明。 - 而没被dependencyManagement包含的dependencies标签会去仓库下载实际的jar包。
如果dependencies里的dependency没有声明version元素,那么maven就会去dependencyManagement里面去找artifactId和groupId的版本声明,如果有,就继承它,如果没有就会报错,必须为dependency声明一个version
如果没被dependencyManagement包含的dependencies中的dependency声明了version,则以dependency里的version为准。 - 也就是说dependencyManagement是用来管理jar包版本的。而dependencies是用来下载实际依赖的。properties用来给dependencyManagement内的依赖进行版本号的声明;然后让他们引用,从而实现了解耦,版本号信息与依赖的其他信息进行分离。
pom.xml文件:
<?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.zzx</groupId>
<artifactId>dubbo_parent</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 父项目工程设置pom -->
<packaging>pom</packaging>
<name>dubbo_parent Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<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>
<dependencies>
<!-- Dubbo 依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>${dubbo.spring.starter.version}</version>
</dependency>
<!-- zookeeper 注册中心 依赖 -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>${dubbo.registry.zookeeper.version}</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>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.user_api、user_consumer和user_provider子模块的创建
- new Module中创建项目user_api,然后引入父项目中声明版本号的dubbo和zookeeper依赖。
- new Module中创建Springboot项目user_consumer,创建方式如下:
并且引入web依赖,因为user_comsumer需要对外提供接口服务。
而且user_comsumer需要指定俩个父项目,即springboot和dubbo_parent;首先在父项目的module标签中加入user_consumer;其次需要在user_consumer中指定父项目parent,代码如下:
此时,已经完成父项目与子项目的关联。<dependencyManagement> <!-- 引入父工程 dubbo_parent --> <dependencies> <dependency> <groupId>com.zzx</groupId> <artifactId>dubbo_parent</artifactId> <version>1.0-SNAPSHOT</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
然后将公共接口引入user_consumer项目中,代码:<!-- 引入user_api公共接口 --> <dependency> <groupId>com.zzx</groupId> <artifactId>user_api</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
- new Module中创建user_provider,父项目为dubbo_parent,其本身也是一个逻辑工程,此时需要给pom文件添加
<packaging>pom</packaging>
,即标记user_provider也是父工程。
以user_provider为父工程,创建pojo、mapper和provider:
new Module,创建pojo、mapper和provider子模块,创建时父工程需要选择user_provider;
即将pojo实体类和mapper持久层接口以及provider服务提供者作为user_provider的子项目。
4.使用docker构建Mysql数据库
mobax操作linux
下载并进入到mysql客户端:
- 下载并构建mysql数据库:
docker run -d --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql:5.7
- 进入到mysql的容器中:
docker exec -it mysql /bin/bash
- 进入到mysql客户端中:
mysql -uroot -p123456
用命令行的方式创建数据库test及其user表:
- 创建database:
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));
COMMENT是给该字段进行注释的意思,但是好像不能用中文。
然后在pojo子模块中,创建user实体类。
在pom文件中引入lombok依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
实体类代码:
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
//用户id
private Long id;
//用户名字
private String name;
//用户年龄
private Integer age;
}
再将pojo工程注入到mapper工程;
5.整合Mybatis-plus配置
-
将mybatis plus和mysql依赖注入到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>
-
在provider项目中的application.properties文件中配置数据源,如下:
#Mysql 数据源配置 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://192.168.126.4:3306/test?serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456
-
额外的配置
在dubbo_parent项目中的pom文件内的dependencyManagement标签内,添加如下依赖的版本管理:<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency>
再将provider项目,指向俩个父工程user_provider和springboot;其中,user_provider如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.zzx</groupId>
<artifactId>user_provider</artifactId>
<version>1.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
最后privoder需要注入springboot和mapper项目的依赖,如下:
<!-- 引入mapper工程 -->
<dependency>
<groupId>com.zzx</groupId>
<artifactId>mapper</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
三、Dubbo项目的实现
1.编辑生产者服务
1.添加用户接口:
-
在user_api工程中引入实体类pojo工程;并创建一个package,名为api,在api包内创建一个provider的接口AddUserService,添加如下代码:
public interface AddUserService { /** * 添加用户 * @param user * @return */ int addUser(User user); }
-
在mapper工程中引入pojo工程,因为使用mybatis plus,故代码如下:
public interface UserMapper extends BaseMapper<User> { }
-
在provider工程中注入公共接口服务user_api工程
-
在provider工程启动类中,在类上添加如下注解:
@MapperScan("com.zzx.mapper")
-
在provider工程中,创建一个package名为service,在service包内创建一个实现类AddUserServiceImpl,@Service是引用dubbo包下的,代码如下:
@Service public class AddUserServiceImpl implements AddUserService { @Autowired private UserMapper userMapper; @Override public int addUser(User user) { return userMapper.insert(user); } }
-
最后还得在provider工程中的application.properties文件中配置dubbo,如下:
#Dubbo 配置 dubbo.application.name=myProvider dubbo.registry.address=zookeeper://192.168.126.4 dubbo.registry.port=2181 dubbo.protocol.name=dubbo dubbo.protocol.port=20880 dubbo.registry.timeout=30000 dubbo.scan.base-packages=com.zzx.service
2.查询用户接口:
-
在user_api工程中,在api包下创建一个接口FindUserService,代码如下:
public interface FindUserService { /** * 查询所有用户 * @return */ List<User> findUserAll(); }
-
在provider工程中,在service包下创建一个实现类FindUserServiceImpl,代码如下:
@Service
public class FindUserServiceImpl implements FindUserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findUserAll()
{
return userMapper.selectList(null);
}
}
userMapper.selectList(null);即查询条件为null,返回值封装到一个List集合中返回。
3.更新用户接口:
-
在user_api工程中,在api包下创建一个接口UpdateUserService,代码如下:
public interface UpdateUserService { //根据用户id查询用户信息 User findByUserId(Long userId); //更新操作 int updateUser(User user); }
-
在provider工程中,在service包下创建一个实现类UpdateUserServiceImpl,代码如下:
@Service public class UpdateUserServiceImpl implements UpdateUserService { @Autowired private UserMapper userMapper; /** * 根据用户id查询用户信息 * @param userId * @return */ @Override public User findByUserId(Long userId) { return userMapper.selectById(userId); } /** * 更新用户信息 * @param user * @return */ @Override public int updateUser(User user) { return userMapper.updateById(user); } }
4.删除用户接口:
- 在user_api工程中,在api包下创建一个接口DeleteUserService,代码如下:
public interface DeleteUserService {
int deleteByUserId(Long userId);
}
-
在provider工程中,在service包下创建一个实现类DeleteUserServiceImpl,代码如下:
@Service public class DeleteUserServiceImpl implements DeleteUserService { @Autowired private UserMapper userMapper; @Override public int deleteByUserId(Long userId) { return userMapper.deleteById(userId); } }
2.编辑消费者服务
Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。它与 JSP,Velocity,FreeMaker 等模板引擎类似,也可以轻易地与 Spring MVC 等 Web 框架集成。与其它模板引擎相比,Thymeleaf 最大的特点是,即使不启动 Web 应用,也可以直接在浏览器中打开并正确显示模板页面 。
1.集成Thymeleaf
在user_consumer工程中:
-
配置视图解析器:
springboot很多配置都有默认配置,而默认页面映射路径为:classpath:/templates/*.html
静态文件路径为:classpath:/static/ -
在pom文件中引入thymeleaf依赖,如下:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <version>2.7.1</version> </dependency>
-
在resource中的application.properties中配置thymeleaf,如下:
#thymeleaf配置 spring.thymeleaf.mode=HTML5 spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.servlet.content-type=text/html #开发时关闭缓存,否则无法看到实时页面 spring.thymeleaf.cache=false
-
在resource下的templates中编写一个简单的Html5页面index.html用以呈现页面并交互,如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> <a href="/adduser">添加用户</a> <a href="#">查询用户</a> </body> </html>
-
在controller包下编写一个控制层类,用以浏览器映射访问,且PageController是用以页面跳转,如下:
@Controller public class PageController { @GetMapping("/{page}") public String showIndex(@PathVariable String page) { return page; } }
2.添加用户
-
编写一个简单的adduser.html页面,用以添加用户,代码如下:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/> <head> <meta charset="UTF-8"> <title>添加用户</title> </head> <body> <form action="/user/addUser" method="post"> 用户姓名:<input type="text" name="name" placeholder="请输入名字"/><br/> 用户年龄:<input type="text" name="age" placeholder="请输入年龄"/><br/> <input type="submit" value="添加"/> </form> </body> </html>
-
编写一个简单的ok.html页面,用以操作成功后返回到index.html页面,代码如下:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/> <head> <meta charset="UTF-8"> <title>成功页面</title> </head> <body> 操作成功请<a href="/index">返回</a> </body> </html>
-
需要在该工程下,实现接口UserService、实现类UserServiceImpl和用户控制层类UserController;
UserService 代码:
public interface UserService { /** * 添加用户 * @param user * @return */ int addUser(User user); }
UserServiceImpl代码:
@Service public class UserServiceImpl implements UserService { //远程调用添加服务 @Reference private AddUserService addUserService; @Override public int addUser(User user) { return addUserService.addUser(user); } }
UserController代码:
@Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping("addUser") public String addUser(User user) { int i = userService.addUser(user); if(i>0) return "redirect:/ok"; return null; } }
-
在user_consumer工程中,还需要引入user_api工程。
-
最后需要在user_consumer工程的application.properties中配置dubbo,如下:
#Dubbo 配置 dubbo.application.name=myConsumer dubbo.registry.address=zookeeper://192.168.126.4 dubbo.registry.port=2181 dubbo.protocol.name=dubbo dubbo.protocol.port=20881 dubbo.registry.timeout=30000 dubbo.scan.base-packages=com.zzx.service
3.查询用户
业务流程,就是从用户请求后,浏览器将地址映射到Controller层与之匹配的方法上,方法内部通过接口引用调用其实现类,实现类调用生产者的接口,也就是公共服务类,公共服务类接口调用dubbo远程调用到的实现类,实现类又去调用mapper,但是crud是使用mybatis plus完成数据库操作的。
-
消费者工程的接口、实现类和控制层的代码实现:
控制层:@GetMapping("findUser") public String findUser(Model model) { //查询所有用户 List<User> users = userService.findUserAll(); //返回值不为空时 if(users!=null) { model.addAttribute("users", users); return "showUser"; } return null; }
实现类:
//远程调用查询服务 @Reference private FindUserService findUserService; @Override public List<User> findUserAll() { return findUserService.findUserAll(); }
接口:
/** * 查询全部用户 * @return */ List<User> findUserAll();
-
最后编写一个showUser,用以展示查询到的用户及其操作,
用thymeleaf引擎动态实现表格,th:就是thymeleaf的标记。${}是获取从后台传递的数据,@{}则是用以动态解析链接地址。
代码如下:<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <link rel="shortcut icon" href="../resources/favicon.ico" th:href="@{/static/favicon.ico}"/> <head> <meta charset="UTF-8"> <title>显示用户</title> </head> <body> <table border="1" align="center"> <tr> <th>用户id</th> <th>用户姓名</th> <th>用户年龄</th> <th>操作</th> </tr> <tr th:each="user:${users}"> <td th:text="${user.id}"></td> <td th:text="${user.name}"></td> <td th:text="${user.age}"></td> <td> <a th:href="@{/user/findUserByUserId(userid=${user.id})}">修改用户</a> <a th:href="@{/user/deleteUser(userid=${user.id})}">删除用户</a> </td> </tr> </table> </body>
4.更新用户
-
更新操作分为俩步,第一步由用户id去查询用户信息,用Model存储用户信息并返回到更新页面进行呈现。
第二步,用户填修改后提交,会将用户信息一同返回,根据用户id修改用户其他信息以完成更新操作。 -
消费者服务的接口、实现类和控制层代码:
接口:User findUserByUserId(Long userId); /** * 更新用户 * @param user * @return */ int updateUser(User user);
实现类:
//远程调用更新服务 @Reference private UpdateUserService updateUserService; /** * 根据用户id查询用户信息(预更新) * @param userId * @return */ @Override public User findUserByUserId(Long userId) { return updateUserService.findByUserId(userId); } /** * 更新用户 * @param user * @return */ @Override public int updateUser(User user) { return updateUserService.updateUser(user); }
控制层:
@GetMapping("findUserByUserId") public String findUserByUserId(Long userId,Model model) { User user = userService.findUserByUserId(userId); if(user != null) { model.addAttribute("user",user); return "updateUser"; } System.out.println("失败"); return null; } /** * 用户更新操作 * @param user * @return */ @PostMapping("updateUser") public String updateUser(User user) { int count = userService.updateUser(user); if(count>0) return "redirect:/user/findUser"; return null; }
5.删除用户
根据用户id删除用户信息。
总结:
1.Run DashBoard是一个项目的运行管理,使用它来管理项目的运行会更方便。
2.Dubbo的10个特性:
- 序列化协议安全,即使用序列化在网络间的传输会安全;
- 地址缓存,即消费者服务第一次调用生产者服务时,会将调用地址进行缓存,即使zookeeper注册中心挂了也不会影响调用这个生产者服务。
- 超时时间和覆盖关系,即消费者服务在调用生产者服务时,可能会出现线程阻塞或者等待,还有高并发的情况;此时需要设置超时时间。
覆盖关系,即消费者服务设置的超时时间的优先级比生产者服务设置的优先级高。 - 重试机制,即当调用失败后重试,可以设置重试的次数。
- 多版本,即在消费者服务设置版本号,则远程调用时会去找对应版本号的生产者服务。(可用于灰度发布)
- 负载均衡,即生产者服务的集群分摊对消费者服务的调用,以减缓高并发的压力,dubbo中有4种策略。
- 集群容错,即消费者服务对生产者服务的集群调用的容错机制,当调用集群中的某个生产者服务失败时的处理策略,dubbo有6种集群容错的策略。
- 服务降级,即为了保证核心业务的正常运行,牺牲用户体验,也就是把非核心业务停掉。
- 服务限流,即在服务消费者设置占用连接数,以及在服务生产者设置线程池最大连接数,以防止QPS飙升导致重要服务的失效。
- 结果缓存机制,即dubbo提供声明式结果缓存,以减少用户加缓存的工作量,用以缓存常用的数据,减少对数据库的访问,dubbo提供3种结果缓存策略。
即消费者服务调用生产者服务时,可以使用到的高级特性。
3.将项目大致分为3个,一个公共接口工程、一个消费者服务工程和一个生产者工程。生产者工程又包含生产者服务工程、实体类工程和处理数据库的mapper工程。
公共接口工程也就是要注册到注册中心的接口服务;即生产者服务工程的接口,只是解耦到公共接口工程去了,因为消费者服务工程调用时需要引入该接口的整个项目。
消费者服务工程的作用有实现页面跳转及其呈现,还有业务的处理;然后就是调用生产者服务,即与数据库的交互,消费者服务不会直接操作。
生产者服务工程,主要作用是与数据库打交道,所以mapper和pojo类都直接与生产者服务工程放在同级。
4.消费者与生产者的工作流程:
- zookeeper的工作流程
将生产者服务注册到zookeeper注册中心中,消费者服务去订阅zookeeper注册中心,zookeeper注册中心向消费者服务发布通知,消费者服务根据通知找到在zookeeper注册中心注册过的生产者服务。 - 由消费者服务进行页面跳转,用户在页面进行业务请求,消费者服务通过Controller将地址请求映射到与@RequestMapping参数匹配的具体的方法上,具体的方法内部又会通过springIOC注入的接口调用接口的实现类。
从接口的实现类中又会去调用公共接口服务的接口,通过公共接口服务的接口再去远程调用公共接口服务接口的实现类。
公共接口服务接口远程调用的实现类又会去调用继承 封装实体类的mybatis
plus的mapper接口,通过mapper接口,去调用mybatis plus的方法,完成对数据库的操作。
简而言之,即消费者呈现页面,业务调用消费者服务;消费者服务调用公共接口服务的接口,该接口调用通过远程调用的实现类,也就是生产者服务中的实现类;该实现类又去调用已经继承封装实体类的mybatis plus的mapper接口;再由mapper接口调用mybatis plus方法完成对数据库的操作。
公共接口服务是被注入到生产者与消费者中,消费者通过该服务去远程调用生产者。虽然@Reference注解在公共接口服务的接口上面,但是它并不是通过远程调用的。因为远程调用只能调用到注册到zookeeper上服务,而公共接口服务连bubbo配置以及注解都没有,不可能注册到zookeeper上。他只是调用通过dubbo远程调用到的实现类而已。