Dubbo3基础使用
1、Dubbo概述
现在SpringCloud Alibaba比较火,用的比较多是吧,那dubbo是不是过时的呢? 并不是的,以前有人把Dubbo和SpringCloud进行对比,其实两者是不同维度的,不能对比,dubbo就是一个rpc框架,SpringCloud是一个生态,里面包括很多组件,并且dubbo3也可以和SpringCloudAlibaba一些组件进行整合,并且我认为dubbo和SpringCloud alibaba进行整合后能发挥两个的优势,dubbo的rpc调用的性能会很好,SpringCloudAlibaba服务治理能力很强。微服务中我们经常使用feign,feign是发送http请求而dubbo是rpc,性能要好。我们可以用dubbo代替feign的http请求。并且他作为一个老牌子的分布式框架用的人还是比较多的。
1.1 什么是分布式系统?
-
《分布式系统原理与范型》定义:
- “分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”
- 分布式系统(distributed system)是建立在网络之上的软件系统。
- 简单来说:多个(不同职责)人共同来完成一件事!
- 任何一台服务器都无法满足淘宝的双十一的数据吞吐量,一定是很多台服务器公共来完成的。
-
歇后语:“三个臭皮匠赛过诸葛亮”,就是分布式系统的真实写照
1.2 服务架构的演变
1.2.1 单体架构
单体架构应该是我们最先接触到的架构实现了,在单体架构中使用经典的三层模型,即表现层,业务逻辑层和数据访问层。
单体架构只适合在应用初期,且访问量比较下的情况下使用,
优点:
- 性价比很高
- 开发速度快
- 成本低
缺点:
- 代码耦合,开发维护困难
- 无法针对不同模块进行针对性优化
- 无法水平扩展
- 单点容错率低,并发能力差
1.2.2 集群
针对单个服务器在访问量越来越大的情况越来越吃力的情况,我们可以考虑服务器的集群化处理。
集群的部署大大提高了服务的处理能力,同时利用Nginx提供的负载均衡机制,来分发请求,使用户的体验没有改变。
1.2.3 垂直拆分
上面的集群部署是可以解决一部分的服务器压力,但是随着用户访问量的增多,集群节点增加到一定阶段的时候,其实作用就已经不是太大了,因为将所有的业务都集中在一起,造成耦合度很高,这时我们可以考虑业务的拆分。来提高系统的性能。比如将原来在一个系统里面的业务拆分为用户系统,订单系统和商品系统。也就是我们讲的垂直化拆分如下:
服务垂直化拆分后是可以大大的提高整体的服务处理能力,但是也会出现很多的冗余的代码,比如用户系统要操作订单库,要操作商品库,订单系统也有可能要操作用户库和商品库等。
优点:
- 系统拆分实现了流量分担,解决了并发问题
- 可以针对不同模块进行优化
- 方便水平扩展,负载均衡,容错率提高
缺点:
- 系统间相互独立,会有很多重复开发工作,影响开发效率
1.2.4 分布式服务
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。
优点:
- 将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率
缺点:
- 系统间耦合度变高,调用关系错综复杂,难以维护
1.2.5 服务治理(SOA)
针对垂直化拆分出现的问题,这时就出现了我们经常听到的SOA(面向服务的架构).什么是SOA呢?在《微服务设计》中有这么一段描述
SOA是一种设计方法,其中包括多个服务,而服务之间通过配合最终会提供一系列功能,一个服务通常以独立的形式存在于操作系统进程中,服务之间通过网络调用,而非采用进程内调用的方式进行通信。
ESB:简单来说ESB就是一根管道,用来连接各个服务节点,为了集成不同的系统,不同协议的服务,ESB要实现消息的转发解释和路由的功能,让不同的服务进行互通
- 缺点:
- 每个供应商提供的ESB产品本身有偏差,自身实现起来比较复杂;应用服务多了之后,ESB要去集成所有服务的协议,数据转换和运维部署将变的比较困难,所有服务都是通过一根管道进行通信,直接降低了通信速度。
1.2.6 微服务化
微服务架构就是使用一套小服务来开发单体应用的方式或途径,每个服务都是基于单一业务能力构建,运行在自己的进程中,并且使用轻量级的机制进行通信,通常就是HTTP或者RPC,能够通过自动化部署机制来自动部署环境。这些服务可以使用不同的编程语言,不同的数据库存储技术,并保持最低限度的集中式管理。
运维困难: 我们这二三十个服务,二三十个服务对应二三十个服务这样部署
分布式事务: 商品、交易的等等这些调用会出现调用失败,如果失败就需要回滚,这个是不是就设计到分布式事务了,本地事务就解决不了问题
定位问题:我们需要定位那个服务出现,并且需要定位具体哪台机器出现问题
1.3 Dubbo简介
1.3.1 常见的微服务框架
- Spring Cloud、Spring Cloud Alibaba
- Dubbo
- Thrift (社区活跃度不是很好)
1.3.2 Dubbo是什么
- Dubbo是分布式服务框架,是阿里巴巴的开源项目,现交给apache进行维护
- Dubbo致力于提高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案
- 简单来说,dubbo是个服务框架,如果没有分布式的需求,是不需要用的、
1.3.3 里程碑
里程碑 | 里程碑内容 |
---|---|
2011年10月 | 阿里巴巴完成Dubbo开源 |
2012年到2014年 | Dubbo开源社区蓬勃发展,多个互联网大厂进行封装使用课 |
2014年10月 | Dubbo停止更新 |
2017年9月 | 阿里巴巴重启Dubbo的维护和更新 |
2018年2月 | Dubbo进入Apache顶级孵化项目 |
2021年3月 | Dubbo 3.x正式进入发布倒计时 |
1.4 Dubbo整体架构
1.4.1 RPC
- RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式
- RPC基本的通信原理
- 在客户端将对象进行序列化
- 底层通信框架使用netty(基于tcp协议的socket),将序列化的对象发给服务方提供方
- 服务提供方通过socket得到数据文件之后,进行反序列化,获得要操作的对象
- 对象数据操作完毕,将新的对象序列化,再通过服务提供方的socket返回给客户端
- 客户端获得序列化数据,再反序列化,得到最新的数据对象,至此,完成一次
RPC两个核心模块:通讯(socket),序列化。
节点 | 角色说明 |
---|---|
Provider | 服务的提供方 |
Consumer | 服务的消费方 |
Registry | 服务注册与发现的注册中心 |
Monitor | 监控服务的统计中心 |
Container | 服务运行容器 |
1.4.2 Dubbo架构
1.服务容器负责启动,加载,运行服务提供者;
2.服务提供者在启动时,向注册中心注册自己提供的服务;
3.服务消费者在启动时,向注册中心订阅自己所需的服务;
4.在注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者;
5.服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用;
6.服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心;
2、构建Dubbo工程
2.1 框架依赖
- Maven
- SpringBoot 2.6.11
- Dubbo 3.1.8 + zookeeper 3.4.14
2.2 搭建Zookeeper
-
解压
-
修改zk的配置文件
进入conf,将文件zoo_sample.cfg 改为zoo.cfg
-
测试zk
启动zookeeper
执行zookeeper根目录下,bin文件中的zkServer.cmd
上面的CMD窗口不要关闭,这样zookeeper就是出于运行状态了
2.3 创建工程
2.3.1 创建父工程
mdb-dubbo-ann
父工程控制版本:
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.6.11</version><relativePath/> <!-- lookup parent from repository --></parent><modules><module>dubbo-consumer</module><module>dubbo-provider</module><module>dubbo-common</module></modules><groupId>com.msb</groupId><artifactId>msb-dubbo-ann</artifactId><version>0.0.1-SNAPSHOT</version><name>msb-dubbo-ann</name><packaging>pom</packaging><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><dubbo-version>3.1.8</dubbo-version></properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>${dubbo-version}</version></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-dubbo</artifactId><version>${dubbo-version}</version></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-registry-zookeeper</artifactId><version>${dubbo-version}</version></dependency></dependencies></dependencyManagement></project>
2.3.2 创建提供者
dubbo-provider
引入依赖:
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-dubbo</artifactId>
</dependency>
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
增加配置
server:port: 8002
logging:config: classpath:logback.xml
dubbo:application:name: dubbo-providerprotocol:name: dubbo#客户端链接20880就可以访问我们的dubboport: 20883registry:address: zookeeper://127.0.0.1:2181
更改主类
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
// 因为是自动装配也可以不加这个注解
@EnableDubbo(scanBasePackages = "com.msb.dubbo.provider.service")
@SpringBootApplication
public class DubboProviderApplication {public static void main(String[] args) {SpringApplication.run(DubboProviderApplication.class);}
}
接着增加通信端口
public interface IUserService {User getUserById(Long id);
}
@Data
@AllArgsConstructor
@Builder
public class User implements Serializable {private static final long serialVersionUID = 1L;private Long id;private String name;private int age;}
增加实现类
@DubboService// 定义一个dubbo服务
public class UserServiceImpl implements IUserService {@Overridepublic User getUserById(Long id) {User user = User.builder().id(id).age(12).name("天涯").build();return user;}
2.3.3 创建客户端
引入依赖
<!--这里是dubbo和SpringBoot桥梁的整合-->
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!--每一种协议都会对应的一个jar比方:dubbo、rest、tripe 三种协议-->
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-dubbo</artifactId>
</dependency>
<!--注册中心可以是zk,nacos -->
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
更改配置
server:port: 8001
logging:config: classpath:logback.xml
dubbo:application:name: dubbo-consumerregistry:address: zookeeper://127.0.0.1:2181
更改主类
@EnableDubbo
@SpringBootApplication
public class DubboConsumeApplication {public static void main(String[] args) {SpringApplication.run(DubboConsumeApplication.class);}
}
增加调用接口
public interface IUserService {User getUserById(Long id);
}
@Data
@AllArgsConstructor
@Builder
public class User implements Serializable {private static final long serialVersionUID = 1L;private Long id;private String name;private int age;}
增加业务调用处理
@RestController
public class OrderController {@Autowiredprivate OrderService orderService;@RequestMapping("/createOrder/{userId}")public String createOrder(@PathVariable("userId") Long userId){return orderService.createOrder(userId);}
}
@Slf4j
@Service
public class OrderService {// 引用对应的dubbo服务@DubboReferenceprivate IUserService iUserService;public String createOrder(Long userId){User user = iUserService.getUserById(userId);log.info("用户用户信息:{}",user);return "创建订单成功";}
}
2.3.5 重构创建公共模块
dubbo-common 存放IUserService 和User
提供端和消费端
<dependency><groupId>com.msb</groupId><artifactId>dubbo-common</artifactId><version>0.0.1-SNAPSHOT</version>
</dependency>
2.3.6 测试
2.4 开启rest协议
如果我们的服务希望既要支持dubbo协议调用,也要能支持http调用,所以,要么仍然保留SpringMVC那一套,如果不想保留那一套,就可以开启 dubbo中的rest协议。
2.4.1 增加依赖
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-rest</artifactId>
</dependency>
2.4.2 更改配置
dubbo:application:name: dubbo-provider# 这里的协议加了s,所以可以设置多个通信协议protocols:p1:name: dubbo#客户端链接20883就可以访问我们的dubboport: 20883p2:name: rest#客户端链接20884就可以访问我们的restport: 20884
2.4.3 更改对应代码服务
@DubboService// 定义一个dubbo服务
@Path("/user")
public class UserServiceImpl implements IUserService {@GET@Path("/{userId}")@Produces(MediaType.APPLICATION_JSON)@Overridepublic User getUserById(@PathParam("userId") Long userId) {User user = User.builder().id(userId).age(12).name("天涯").build();return user;}}
2.4.4 测试
在消费端增加RestTemplate
@EnableDubbo
@SpringBootApplication
public class DubboConsumeApplication {@Beanpublic RestTemplate restTemplate(){return new RestTemplate();}public static void main(String[] args) {SpringApplication.run(DubboConsumeApplication.class);}
}
@Slf4j
@Service
public class OrderService {@DubboReferenceprivate IUserService iUserService;@AutowiredRestTemplate restTemplate;public String createOrder(Long userId){User user = restTemplate.getForObject("http://localhost:20884/user/232",User.class);log.info("用户用户信息:{}",user);return "创建订单成功";}
}
http://localhost:8001/createOrder/232
2.4.5 使用接口调用Rest
将rest协议放到common中
修改IUserService
@Path("/user")
public interface IUserService {@GET@Path("/{userId}")@Produces(MediaType.APPLICATION_JSON)User getUserById(@PathParam("userId") Long id);
}
修改consume里面内容
@Slf4j
@Service
public class OrderService {// 指定写协议@DubboReference(protocol = "rest")private IUserService iUserService;@AutowiredRestTemplate restTemplate;public String createOrder(Long userId){User user = iUserService.getUserById(userId);log.info("用户用户信息:{}",user);return "创建订单成功";}
}
如果我们不能确定是否是走的http,我们可以DispatcherServlet#service 里面打个端点,看是否进入
3、Tripe协议
3.1 HTTP1.x协议
POST /user HTTP/1.1 // 请求行
Host: www.user.com
Content-Type: application/x-www-form-urlencoded
Connection: Keep-Alive
User-agent: Mozilla/5.0. // 以上是请求头
(此处必须有一空行 | // 空行分割header和请求内容
name=world // 请求体(可选,如get请求时可选)
它会将上面字符转化为字节流,然后发送给对方,对方解析的时候会根据空格、回车、换行符进行解析
这里我们可以知道他的请求头比较大,占用空间比较多。
3.2 Dubbo协议
-
Magic - Magic High & Magic Low (16 bits)
标识协议版本号,Dubbo 协议:0xdabb
-
Req/Res (1 bit)
标识是请求或响应。请求: 1; 响应: 0。
-
2 Way (1 bit)
仅在 Req/Res 为1(请求)时才有用,标记是否期望从服务器返回值。如果需要来自服务器的返回值,则设置为1。
-
Event (1 bit)
标识是否是事件消息,例如,心跳事件。如果这是一个事件,则设置为1。
-
Serialization ID (5 bit)
标识序列化类型:比如 fastjson 的值为6。
-
Status (8 bits)
仅在 Req/Res 为0(响应)时有用,用于标识响应的状态。
- 20 - OK
- 30 - CLIENT_TIMEOUT
- 31 - SERVER_TIMEOUT
- 40 - BAD_REQUEST
- 50 - BAD_RESPONSE
- 60 - SERVICE_NOT_FOUND
- 70 - SERVICE_ERROR
- 80 - SERVER_ERROR
- 90 - CLIENT_ERROR
- 100 - SERVER_THREADPOOL_EXHAUSTED_ERROR
-
Request ID (64 bits)
标识唯一请求。类型为long。
-
Data Length (32 bits)
序列化后的内容长度(可变部分),按字节计数。int类型。
-
Variable Part
被特定的序列化类型(由序列化 ID 标识)序列化后,每个部分都是一个 byte [] 或者 byte
dubbo协议在Dubbo框架内使用还是比较舒服的,并且dubbo协议相比于http1.x协议,性能会更好,因为请求中没
有多余的无用的字节,都是必要的字节,所以dubbo协议成为了Dubbo框架中的默认协议。
但是dubbo协议一旦涉及到跨RPC框架,比如一个Dubbo服务要调用gPRC服务,就比较麻烦了,因为发一个dubbo
协议的请求给一个gPRC服务,gPRC服务只会按照gRPC的格式来解析字节流,最终肯定会解析不成功的。
dubbo协议虽好,但是不够通用,所以这就出现了Triple协议,Triple协议是基于HTTP2,没有性能问题,另外HTTP
协议非常通用,全世界都认它,兼容起来也比较简单,而且还有很多额外的功能,比如流式调用。
3.3 Triple 协议
Triple 协议是 Dubbo3 推出的主力协议。Triple 意为第三代,通过 Dubbo1.0/ Dubbo2.0 两代协议的演进,以及云原生带来的技术标准化浪潮,Dubbo3 新协议 Triple 应运而生。
3.4 项目改造
3.4.1 父工程引入依赖版本
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-triple</artifactId><version>${dubbo-version}</version>
</dependency>
3.4.2 服务提供者引入依赖
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-triple</artifactId>
</dependency>
3.4.3 引入协议
dubbo:application:name: dubbo-providerprotocols:p1:name: dubbo#客户端链接20880就可以访问我们的dubbo 20880 是默认端口port: 20883p2:name: rest#客户端链接20884就可以访问我们的restport: 20884p3:name: triport: 20885
3.4.4 消费方
引入依赖
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-triple</artifactId>
</dependency>
调用处更改协议
@Slf4j
@Service
public class OrderService {@DubboReference(protocol = "tri")private IUserService iUserService;@AutowiredRestTemplate restTemplate;public String createOrder(Long userId){User user = iUserService.getUserById(userId);log.info("用户用户信息:{}",user);return "创建订单成功";}
}
3.4.5 测试
http://localhost:8001/createOrder/232
3.5 Triple Streaming
3.5.1 使用场景
在一些大文件传输、直播等应用场景中, consumer或provider需要跟对端进行大量数据的传输,由于这些情况下的数据量是非常大的,因此是没有办法可以在一个RPC的数据包中进行传输,因此对于这些数据包我们需要对数据包进行分片之后,通过多次RPC调用进行传输,如果我们对这些已经拆分了的RPC数据包进行并行传输,那么到对端后相关的数据包是无序的,需要对接收到的数据进行排序拼接,相关的逻辑会非常复杂。但如果我们对拆分了的RPC数据包进行串行传输,那么对应的网络传输RTT与数据处理的时延会是非常大的。
为了解决以上的问题,并且为了大量数据的传输以流水线方式在consumer与provider之间传输,因此Streaming RPC的模型应运而生。
3.5.2 代码实战
-
common中引入jar
父工程:
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-common</artifactId><version>${dubbo-version}</version> </dependency>
common中
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-common</artifactId> </dependency>
-
更改服务端代码
@Path("/user") public interface IUserService {// UNARY@GET@Path("/{userId}")@Produces(MediaType.APPLICATION_JSON)User getUserById(@PathParam("userId") Long id);// SERVER_STREAM 服务端流default void sayHelloServerStream(String name, StreamObserver<String> response){}// CLIENT_STREAM / BI_STREAM 双端流default StreamObserver<String> sayHelloStream(StreamObserver<String> response){return response;}}
-
服务端流处理
@DubboService// 定义一个dubbo服务 public class UserServiceImpl implements IUserService {@Overridepublic void sayHelloServerStream(String name, StreamObserver<String> response) {response.onNext("hello 1");response.onNext("hello 2");response.onNext("hello 3");response.onNext("hello 4");response.onCompleted();} }
@Service public class OrderService {@DubboReference(protocol = "tri")private IUserService iUserService;public String createOrder(Long userId){iUserService.sayHelloServerStream("李华", new StreamObserver<String>() {@Overridepublic void onNext(String data) {System.out.println("接收到的响应数据:" + data);}@Overridepublic void onError(Throwable throwable) {System.err.println("异常处理");}@Overridepublic void onCompleted() {System.out.println("响应数据完成");}});return "创建订单成功";} }
-
双端流
@DubboService// 定义一个dubbo服务 public class UserServiceImpl implements IUserService {@Overridepublic User getUserById(Long userId) {User user = User.builder().id(userId).age(12).name("天涯").build();return user;}@Overridepublic StreamObserver<String> sayHelloStream(StreamObserver<String> response) {return new StreamObserver<String>() {@Overridepublic void onNext(String data) {// 接收到客户端发送过来的数据,进行处理,将结果返回try {Thread.sleep(3*1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("接收到客户端数据:" + data);response.onNext("服务端影响:" + data);}@Overridepublic void onError(Throwable throwable) {}@Overridepublic void onCompleted() {System.out.println("服务端处理完毕");}};} }
-
public class OrderService {@DubboReference(protocol = "tri")private IUserService iUserService;@AutowiredRestTemplate restTemplate;public String createOrder(Long userId){StreamObserver streamObserver = iUserService.sayHelloStream(new StreamObserver() {@Overridepublic void onNext(Object data) {System.out.println("接收到响应数据:" + data);}@Overridepublic void onError(Throwable throwable) {}@Overridepublic void onCompleted() {System.out.println("接收到响应数据完毕");}});streamObserver.onNext("request hello 1");streamObserver.onNext("request hello 2");streamObserver.onNext("request hello 3");streamObserver.onCompleted();return "创建订单成功";}
}
4、dubbo-admin
4.1下载
4.2 更改配置
进入application.properties配置文件所在的目录
dubbo-admin-0.5.0\dubbo-admin-server\src\main\resources\application.properties
设置zk地址
登录时候用户名秘密
更改端口
4.3 进行编译
mvn clean package
找到编译后到jar包:
dubbo-admin-0.5.0\dubbo-admin-distribution\target
4.4 启动
jar -jar xxx.jar
4.5 访问
localhost:10010
4.6 使用提供jar的注意事项
注册中心: 127.0.0.1:2181
端口号: 10010
用户名/密码: root/root
5、参数设置
5.1 版本和分组
Dubbo服务中,接口并不能唯一确定一个服务,只有接口+分组+版本号才能唯一确定一个服务。
使用场景
- 当同一个接口针对不同的业务场景、不同的使用需求或者不同的功能模块等场景,可使用服务分组来区分不同的实现方式。同时,这些不同实现所提供的服务是可并存的,也支持互相调用。
- 当接口实现需要升级又要保留原有实现的情况下,即出现不兼容升级时,我们可以使用不同版本号进行区分。
使用方式
使用 @DubboService 注解,添加 group 参数和 version 参数 本示例中使用"发布和调用" 中示例代码
代码处理
服务端,我们指定组合版本,然后启动两个服务,注意两个服务的端口不能相同
@DubboService(group = "group1",version = "1.0")// 定义一个dubbo服务
public class UserServiceImpl implements IUserService {@Overridepublic User getUserById(Long userId) {User user = User.builder().id(userId).age(12).name("天涯").build();log.info("服务获取用户信息:{}",user);return user;}
修改消费端指定版本
@DubboReference(protocol = "tri",group = "group1",version = "2.0")
private IUserService iUserService;
测试,访问只会达到对应的version=“2.0” 的版本
5.2 启动检查
启动时会在注册中心检查依赖的服务是否可用,不可用时会抛出异常
5.3 超时时间
-
由于网络或服务端不可靠,会导致调用过程中出现不确定的阻塞状态(超时)
-
为了避免超时导致客户端资源(线程)挂起耗尽,必须设置超时时间
-
在服务提供者添加如下配置:
-
返回结果
-
配置原则
dubbo推荐在Provider上尽量多配置Consumer端属性:
- 作服务的提供者,比服务使用方更清楚服务性能参数,如调用的超时时间,合理的重试次数,等等
- 在Provider配置后,Consumer不配置则会使用Provider的配置值,即Provider配置可以作消费者的缺省值。
5.4 重试次数
-
当出现失败,自动切换并重试其它服务器,dubbo重试的缺省值是2次,我们可以自行设置
-
在provider提供方配置
-
在consume配置
-
并不是所有的方法都适合设置重试次数
- 幂等方法:适合(当参数一样,无论执行多少次,结果是一样的,例如:查询,修改)
- 非幂等方法:不适合(当参数一样,执行结果不一样,例如:删除,添加)
备注: 重试是在dubbo协议下重试, tri协议下不会生效, consumer和provider同时设置重试则客户端生效
-
指定不同方法的重试,这是后我们需要引入xml
-
增加方法
-
public interface IUserService {User getUserById(@PathParam("userId") Long id);User getUserInfoById(@PathParam("userId") Long id); // 有具体实现类}
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://dubbo.apache.org/schema/dubbohttp://dubbo.apache.org/schema/dubbo/dubbo.xsd"><!-- 调用远程Producer的服务 --><dubbo:reference id="iUserService" interface="com.msb.common.service.IUserService" timeout="200" protocol="dubbo"><dubbo:method name="getUserById" retries="3"/><dubbo:method name="getUserInfoById" retries="1"/></dubbo:reference></beans>
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://dubbo.apache.org/schema/dubbohttp://dubbo.apache.org/schema/dubbo/dubbo.xsd"><!-- 调用远程Producer的服务 --><dubbo:reference id="iUserService" interface="com.msb.common.service.IUserService" timeout="200" protocol="dubbo"><dubbo:method name="getUserById" retries="3"/><dubbo:method name="getUserInfoById" retries="1"/></dubbo:reference></beans>
-
这里用Autowired注入
6、过滤器
6.1 Apache Dubbo Filter介绍
-
Apache Dubbo的Filter与Servlet的Filter功能类似
-
自定义Filter需要扩展Filter接口
-
自定义Filter可以通过@Activate注解完成默认开启
-
filter 分consumer和provider
6.2 自定义Filter
//这里group就是定义提供者还是消费者
@Activate(group = "provider")
public class MyFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {URL url = invoker.getUrl();Class<?> anInterface = invoker.getInterface();String simpleName = anInterface.getSimpleName();String serviceName = invocation.getServiceName();String methodName = invocation.getMethodName();System.out.println("url = " + url.toFullString());System.out.println("simpleName = " + simpleName);System.out.println("serviceName = " + serviceName);System.out.println("methodName = " + methodName);// 调用下一级Result result = invoker.invoke(invocation);return result;}
}
6.3 配置Filter
创建目录
然后在里面创建接口文件
文件内容:对应实现类的全路径
6.4 访问
7、上下文参数传递
7.1 使用场景
1、Dubbo系统间调用时,想传递一些通用参数,可通过Dubbo提供的扩展如Filter等实现统一的参数传递
2、Dubbo系统间调用时,想传递接口定义之外的参数,可在调用接口前使用setAttachment传递参数。
7.2 实战使用
客户端代码:
package com.msb.dubbo.consumer.filters;import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;@Activate(group = "consumer")
public class ConsumerContextFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {Result result = null;RpcContext.getClientAttachment().setAttachment("xid","1111111111111");try {//执行业务逻辑result = invoker.invoke(invocation);}finally {//清理RpcContext.getClientAttachment().clearAttachments();}return result;}
}
客户端配置
服务端代码
package com.msb.dubbo.provider.filter;import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;import java.util.Map;@Slf4j
@Activate(group = "provider")
public class ProviderContextFilter implements Filter {@Overridepublic Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {//ServerAttachment接收客户端传递过来的参数Map<String, Object> serverAttachments = RpcContext.getServerAttachment().getObjectAttachments();log.info("ContextService serverAttachments:" + JSON.toJSONString(serverAttachments));String xid = (String)serverAttachments.get("xid");log.info("获取传递数据xid:{}",xid);//执行业务逻辑Result result = invoker.invoke(invocation);return result;}}
服务端配置
8、Dubbo 工程XML配置
8.1 创建父工程
更改pom
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><modules><module>dubbo-provider</module><module>dubbo-consumer</module><module>dubbo-common</module></modules><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.0</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.msb</groupId><artifactId>msb-dubbo-xml</artifactId><version>0.0.1-SNAPSHOT</version><name>msb-dubbo-xml</name><packaging>pom</packaging><description>dubbo学习实例</description><properties><java.version>1.8</java.version><dubbo-version>3.1.8</dubbo-version></properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId><version>${dubbo-version}</version></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-dubbo</artifactId><version>${dubbo-version}</version></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-rest</artifactId><version>${dubbo-version}</version></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-triple</artifactId><version>${dubbo-version}</version></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-common</artifactId><version>${dubbo-version}</version></dependency><dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-registry-zookeeper</artifactId><version>${dubbo-version}</version></dependency></dependencies></dependencyManagement>
</project>
8.2 创建Provider
引入依赖
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-dubbo</artifactId>
</dependency>
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId>
</dependency>
application.yml配置
server:port: 8001
logging:config: classpath:logback.xml
applicationContext-dubbo.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://dubbo.apache.org/schema/dubbohttp://dubbo.apache.org/schema/dubbo/dubbo.xsd"><!-- 提供方应用信息,用于计算依赖关系,name可以随便起名,但是不能重复 --><dubbo:application name="dubbo-provider"/><!-- 使用zookeeper为注册中心,客户端使用curator --><dubbo:registry address="zookeeper://localhost:2181" client="curator"/><!--对外提供服务helloServiceAPI,服务对应的实现类是ref="HelloServiceImpl"--><dubbo:protocol name="dubbo" port="20885"/><dubbo:service id="helloServiceAPI"interface="com.msb.service.HelloServiceAPI"ref="HelloServiceImpl" retries="2"></dubbo:service></beans>
主类配置
@ImportResource(locations = {"classpath:applicationContext-dubbo.xml"})
@SpringBootApplication
public class DubboProviderApplication {public static void main(String[] args) {SpringApplication.run(DubboProviderApplication.class);}
}
服务实现
@Slf4j
public class HelloServiceImpl implements HelloServiceAPI {@Overridepublic String sayHello(String message) {log.info("接收到消息:{}",message );try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}return "生产者接收到数据:" + message;}
}
dubbo-common工程以及接口创建
public interface HelloServiceAPI {String sayHello(String message);
}
更改applicationContext-dubbo.xml
<bean id="HelloServiceImpl" class="com.msb.provider.service.impl.HelloServiceImpl"/>
8.3 创建Consumer
创建工程引入依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>com.msb</groupId><artifactId>dubbo-common</artifactId><version>0.0.2-SNAPSHOT</version>
</dependency>
<!--这里是dubbo和SpringBoot桥梁的整合-->
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-spring-boot-starter</artifactId>
</dependency>
<!--每一种协议都会对应的一个jar比方:dubbo、rest、tripe 三种协议-->
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-rpc-dubbo</artifactId>
</dependency>
<!--注册中心可以是zk,nacos -->
<dependency><groupId>org.apache.dubbo</groupId><artifactId>dubbo-registry-zookeeper</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
application.yml配置
server:port: 8002
logging:config: classpath:logback.xml
applicationContext-dubbo.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-4.3.xsdhttp://dubbo.apache.org/schema/dubbohttp://dubbo.apache.org/schema/dubbo/dubbo.xsd"><!-- 提供方应用信息,用于计算依赖关系,name可以随便起名,但是不能重复 --><dubbo:application name="dubbo-consumer" /><!-- 使用zookeeper为注册中心,客户端使用curator --><dubbo:registry address="zookeeper://localhost:2181" client="curator"/><!-- 调用远程Producer的服务 --><dubbo:reference id="helloServiceAPI" timeout="200" interface="com.msb.service.HelloServiceAPI" retries="3"/></beans>
主类创建
@ImportResource(locations = {"classpath:applicationContext-dubbo.xml"})
@SpringBootApplication
public class DubboConsumeApplication {public static void main(String[] args) {SpringApplication.run(DubboConsumeApplication.class);}
}
业务逻辑
@RestController
public class HelloController {@Autowiredprivate HelloServiceAPI helloServiceAPI;@GetMapping("/sayHello")public String sayHelle(){return helloServiceAPI.sayHello("hello everyBody");}
}
8.4 测试
http://localhost:8002/sayHello
相关文章:
Dubbo3基础使用
1、Dubbo概述 现在SpringCloud Alibaba比较火,用的比较多是吧,那dubbo是不是过时的呢? 并不是的,以前有人把Dubbo和SpringCloud进行对比,其实两者是不同维度的,不能对比,dubbo就是一个rpc框架&…...
Android 图片加载框架Glide源码详解
我们看Glide的源码从Glide类入手,使用的时候我们先调用的with方法,源码中with有3个多载的方法:下图翻译过来就是activity用FragmentActivity Applicationcontext用 with(Context)还有一个with(View)的 殊途…...
知识竞赛活动舞台搭建需要多少钱
知识竞赛活动舞台搭建的费用会根据不同的竞赛活动规模和要求而有所不同。对于小型的知识竞赛活动,如学校内部组织的知识竞赛或社区的知识竞赛活动,舞台搭建的费用往往相对较低。在这种情况下,可能只需要一些简单的装饰和道具,例如…...
07set注入级联属性和特殊字符及表达式语言
级联属性赋值(了解) 概述 级联属性赋值就是给某个对象属性的属性赋值,就是给对象关联的对象的属性赋值 Clazz班级类 public class Clazz {private String name;public Clazz() {}public Clazz(String name) {this.name name;}//set和get方法以及toString方法 }Student有cl…...
用AI在小红书做早教启蒙,2个月涨粉11.7万,获赞10万的新流量玩法
本期是赤辰第29期AI项目教程,底部准备了9月粉丝福利,可以免费领取。母婴、教育一直以来是最不缺流量的两大“真香”赛道。那么AI时代下有怎样新流量红利和玩法?接下来就给大家拆解一个在小红书上做AI绘画启蒙早教资源变现的新玩法!…...
Recommender Systems in the Era of Large Language Models (LLMs)
本文是LLM系列文章,针对《Recommender Systems in the Era of Large Language Models (LLMs)》的翻译。 大语言模型时代的推荐系统 摘要1 引言2 相关工作3 基于LLM推荐系统的深度表示学习4 预训练和微调LLM用于推荐系统5 提示LLM用于推荐系统6 未来方向6.1 幻觉缓解…...
红心向阳 百鸟朝凤
背景 最近在玩 folium 模块,基于使用过程中的一些个人体验,对 folium 进行了二次封装,开源在 GpsAndMap.在使用的过程中,发现在地图上打图标是可以进行旋转的。遇到就发现了一些有意思的玩法。 隔海的相望 下面的代码在地图 厦…...
C语言自己实现一个memcpy函数
目录 按字节拷贝实现memcpy按4字节拷贝实现memcpyTips 在 C 语言中,我们可以自己实现 memcpy 函数来实现内存数据的拷贝操作。memcpy 函数用于将指定长度的数据从源地址复制到目标地址。 按字节拷贝实现memcpy #include <stdio.h>void* my_memcpy_byte(void*…...
C#教师考勤管理系统asp.net+sqlserver
3.3.1 员工部分 1:请假管理:包括填写请假条,提交申请,查看审批,审核请假等等。 2:考勤管理:针对具体的员工考勤的统计等管理。 3:个人资料管理:进行个人信息管理…...
Nginx代理配置详解
一、什么是代理 1、正向代理(forward proxy) 正向代理,简单的说就像是一个跳板,它隐藏了真实的请求客户端(IP),服务端不知道真实的客户端是谁,客户端请求的服务都由代理服务器来代替请求。 举个例子来说…...
DAG 的深度优先搜索标记
/**\ | DAG 的深度优先搜索标记 | INIT: edge[][] 邻接矩阵 ; pre[], post[], tag 全置 0; | CALL: dfstag(i, n); pre/post: 开始 / 结束时间 \**/ int edge[V][V], pre[V], post[V], tag; void dfstag( int cur, int n) { // vertex: 0 ~ n-1 pre[cur] tag; for…...
网络存储解决方案:选择与配置
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…...
java中BigDecimal除法运算指定小数点保留位数和取舍规则
java中使用BigDecimal进行相除运算时,为了避免抛出ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result。最好指定小数点保留位数和取舍规则。 取舍规则 ROUND_CEILING: 舍位时向正无穷方向取值。即:向上取…...
车联网远程监控管理提升车辆调度效率,实现高效运营
随着智慧城市建设与物联网技术发展,车辆使用4G工业路由器网络实现车联网,并对车上视频监控、GPS定位以及温湿度传感器等信息进行数据采集和实时传输。这些数据的采集和监测将通过4G网络上传到管理平台,为车辆调度和运行效率的优化提供了有力的…...
数据治理-数据建模和设计
定义 发现、分析和确定数据需求的过程,用一种称为数据模型的精确形式表示和传递这些数据需求。过程是循环迭代的,可能包括概念、逻辑和物理模型。 常见的6种数据模型 关系模式、多维模式、面向对象模式、事实模式、时间序列模式、NoSQL模式。根据描述详…...
博客系统(升级(Spring))(四)(完)基本功能(阅读,修改,添加,删除文章)(附带项目)
博客系统 (三) 博客系统博客主页前端后端个人博客前端后端显示个人文章删除文章 修改文章前端后端提取文章修改文章 显示正文内容前端后端文章阅读量功能 添加文章前端后端 如何使用Redis项目地点: 博客系统 博客系统是干什么的? CSDN就是一…...
常用的辅助类(必会)
1.CountDownLatch package com.kuang.add;import java.util.concurrent.CountDownLatch;//计数器 减法 public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {//总数是6,必须要执行任务的时候,再使用…...
Java常用类之 String、StringBuffer、StringBuilder
Java常用类 文章目录 一、字符串相关的类1.1、String的 不可变性1.2、String不同实例化方式的对比1.3、String不同拼接操作的对比1.4、String的常用方法1.5、String类与其他结构之间的转换1.5.1、String 与基本数据类型、包装类之间的转换1.5.2、String 与char[]的转换1.5.3、…...
linux在所有文件中查找某一个字符串
linux在所有文件中查找某一个字符串 有时候我们需要在大量文件中查找某一个字符串,手工一个一个打开文件查找非常耗时,我们可以使用 find 和 xargs 两个命令来实现查找指定字符串。 命令详解 find <directory> -type f -name "*.c" |…...
WebSocket vs SSE: 实时数据推送到前端的选择与实现(详细)
Websocket和Server-Sent Events 对比推送数据给前端及各自的实现 二者对比WebSocket:Server-Sent Events (SSE):选择 WebSocket 还是 SSE: Websocket 实现使用原生 WebSocket API:使用 Netty 创建 WebSocket:总结和选择…...
Redis从入门到精通(二:数据类型)
数据存储类型介绍 Redis 数据类型(5种常用) string hash list set sorted_set/zset(应用性较低) redis 数据存储格式 redis 自身是一个 Map,其中所有的数据都是采用 key : value 的形式存储 数据类型指的是存储的数据…...
基于SSM的珠宝首饰交易平台
末尾获取源码 开发语言:Java Java开发工具:JDK1.8 后端框架:SSM 前端:采用JSP技术开发 数据库:MySQL5.7和Navicat管理工具结合 服务器:Tomcat8.5 开发软件:IDEA / Eclipse 是否Maven项目&#x…...
4款视频号数据分析平台!
很多人在做视频号的时候就会有创作参考的需求,那么你们知道视频号中有哪些数据平台?今天就和大家来分享一下 接下来就总结一下视频号数据平台有哪些?排名不分前后。 1:视频号助手(channels.weixin.qq.com)…...
【系统架构】什么是集群?为什么要使用集群架构?
什么是集群?为什么要使用集群架构? 1.什么是集群?2.为什么要使用集群?2.1 高性能2.2 价格有效性2.3 可伸缩性2.4 高可用性2.5 透明性2.6 可管理性2.7 可编程性 3.集群的常见分类3.1 负载均衡集群3.2 高可用性集群3.3 高性能计算集…...
Java手写拓扑排序和拓扑排序应用拓展案例
Java手写拓扑排序和拓扑排序应用拓展案例 1. 算法思维导图 #mermaid-svg-o8KpEXzxukfDM8c9 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-o8KpEXzxukfDM8c9 .error-icon{fill:#552222;}#mermaid-svg-o8KpEXzxukfD…...
练习:使用servlet显示试卷页面
试卷页面代码 在浏览器输入如下地址: http://localhost/examPageServlet 效果如下:...
视频监控系统/视频云存储EasyCVR接入国标GB28181设备无法播放设备录像,是什么原因?
安防视频监控平台EasyCVR支持将部署在监控现场的前端设备进行统一集中接入,可兼容多协议、多类型设备,管理员可选择任意一路或多路视频实时观看,视频画面支持单画面、多画面显示,视频窗口数量有1、4、9、16个可选,还能…...
四叶草clover配置工具:Clover Configurator for Mac
Clover Configurator是一款Mac上的工具,用于配置和优化Clover引导加载器。Clover引导加载器是一种用于启动macOS的开源引导加载器。它允许用户在启动时选择操作系统和配置启动选项。 Clover Configurator提供了一个可视化的界面,让用户可以轻松地编辑和…...
计算机网络第四章——网络层(中)
提示:待到山花烂漫时,她在丛中笑。 文章目录 需要加头加尾,其中头部最重要的就是加了IP地址和MAC地址(也就是逻辑地址和物理地址)集线器物理层设备,交换机是物理链路层的设备,如上图路由器左边就…...
时序分解 | MATLAB实现基于小波分解信号分解分量可视化
时序分解 | MATLAB实现基于小波分解信号分解分量可视化 目录 时序分解 | MATLAB实现基于小波分解信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于小波分解的分量可视化,MATLAB编程程序,用于将信号分解成不同尺度和频率的子信…...
网站开发设计工具/网站关键词排名分析
对于该教程而言,缺少了删除已存在的电影记录的功能。因此,我在这里给出删除功能的代码供大家参考学习。 另外,需要注意的是要为VS2008打上SP1服务包,不然就不能使用ADO.NET Entity Data Model功能了。附按本教程制作的MovieDataba…...
企业网站建设的好处/网站流量数据分析
【H5】 svg画扇形饼图 效果图如下: 封装代码如下: 代码内有详细注解哦! <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widt…...
公司网站流程/女教师网课入06654侵录屏
1. 域名访问失败但通过IP访问正常 发生此类型情况可能的原因如下: DNS 解析问题:域名访问失败可能是因为 DNS 解析出现了问题,导致域名无法解析成正确的 IP 地址。可以通过使用 nslookup 或 dig 命令来检查 DNS 解析是否正常。 域名解析错误…...
代码网站模板/口碑营销经典案例
第5.1节 Fcitx 输入法框架 注意 在 FreeBSD-14.0-Current 中可能会出现许多不可预料的奇怪的 bug(fcitx5 诊断信息英文乱码,输入法显示出奇怪的汉字,fcitx5-qt5 环境不能正常加载……),如果条件允许应该在 FreeBSD-Rel…...
宠物网站素材/文案发布平台
提问嘉宾: 盛国军,上海麦考林信息科技有限公司首席架构师。曾历任8848软件架构师、光芒国际磊客中国技术总监。具有10年互联网和电子商务开发经验,5年软件架构师经验,3年两千万美金投资的大型网站技术总监管理经验。 回答嘉宾&…...
电脑课做网站所需的软件/自己怎么做网站
前言: 在使用gitLab中时遇到一个问题,就是我在gitLab新建分支后,在本地切换分支不成功,遇到了这个问题,在大佬的博客的指点下,顺利解决这个问题,记录下我一步一步解决问题的过程,最后…...