Dubbogo 详解

Dubbogo 详解

简介

dubbo功能很强大的微服务开发框架,支持多种通信协议,并具有流量治理的功能。

dubbo在有了大转变,拥抱了云原生,从哪些方面可以体现呢?

  1. 推出了自己的Trip协议
  2. 修复了服务发现的级别,之前是方法级别,现在是服务级别
  3. 提供了服务发现,流量控制和XDS的结合

当然,dubbo还是那个dubbo,原来的配置都有,并且支持跨语言通信,并且方便我们开发,提供了cli的工具来使用,所以我们的通信变成了下面的样子

  • 先写IDL
  • 实现对应的服务,调用对应的接口。

dubbo有对应的示例代码:

https://github.com/apache/dubbo-go-samples

dubbo的架构和分层

我想引用官网上的图来清晰的说明,dubbo的抽象是很重要的。

dubbo中有几个重要的接口和对象

  • invoker:表示一个可调用的抽象对象,不管是从loadBalance,还是从cluster,还是dictionary,都表示的是一个invoker。
  • Invocation:调用的上下文。通过它可以调用此次调用时候的参数名字,参数类型,返回值等等一系列的数据

具体的可以参考官网:

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/architecture/code-architecture/
在这里插入图片描述

在这里插入图片描述

这是从dubbo的官网上copy的图,源码的分析按照这个图来看,很清晰,可能会有一点点的不一样,但这也没太大的问题。

在这里插入图片描述

分为服务端和客户端:

服务端:构建代理对象,接受服务端的请求。

客户端:构建代理对象,请求服务端。

这里需要处理几个问题

  1. 怎么在服务端和客户端调用的基础上,将dubbo这一套东西加上去

    每个通信协议在dubbo中都有自己的通信协议的实现,当让他们也是一个invoker,它作为baseInvoker,在此基础上增加各种各样的invoker(cluster,loadbalance,filter等)。

    对于客户端而言,也有baseInvoker,客户端而言,只有filter。

  2. go中是没有动态代理的概念的,所以,对于客户端来说,怎么生成代理对象。

    代理对象不能再运行的时候生成,那可以提早生成。

    dubbo提供IDL的工具实现了此功能,提早已经生成了好了代理对象,用来做真正的调用。

    并且生成了一个对应的ClientImpl,它是一个结构体,结构体中的属性是对应的方法,此时就可以利用GO的反射来动态的修改属性,这样在构建的时候就做到了。

服务端创建一个exporter开始

code:

type GreeterProvider struct {api.UnimplementedGreeterServer
}func (s *GreeterProvider) SayHello(ctx context.Context, in *api.HelloRequest) (*api.User, error) {logger.Infof("Dubbo3 GreeterProvider get user name = %s\n", in.Name)return &api.User{Name: "Hello " + in.Name, Id: "12345", Age: 21}, nil
}// export DUBBO_GO_CONFIG_PATH= PATH_TO_SAMPLES/direct/go-server/conf/dubbogo.yml
func main() {config.SetProviderService(&GreeterProvider{})if err := config.Load(); err != nil {panic(err)}select {}
}

将对应的实现类提供给dubbo,并且启动。

  1. 注册到dubbo中

    在整个dubbo没有启动之前,这里的注册只是放在一个map中,需要保证key是唯一。

  2. 启动

    启动分为五步

    • 初始化config
    • 初始化provider
    • 初始化consumer
    • 初始化shutdown

初始化好此provider的配置,配置叫做ServiceConfig

调用Export来做真正的服务创建操作。

会通过具体的通信协议创建对应的invoker,并且结合filter。并且将服务注册到注册中心。

总体流程是这样,现在回答几个问题:

  1. dubbo中是怎么调用到真正的provider,也就是说dubbo是怎么和provider关联的。
  2. Invoker(调用者)是怎么构建的?

问题一:

dubbo是通过反射来调用的。

原理如下:

dubbo的trip协议是grpc的基础上改进的,所以要从这一点入手,从grpc我们知道,在启动的时候要注册相应的服务,在dubbo这里,从生产的pb文件中入手
在这里插入图片描述

会将此服务描述信息告诉grpc,当调用来了之后,就会调用到对应的方法中。这里我们以_Greeter_SayHello_Handler为例,在此方法中,会构建invocation,然后在调用dubbo构建的invoker对象,就完成连接了。

在这里插入图片描述

代理对象是什么?

代理对象就是dubbo生成的invoker,在构建好invoker之后,会将它作为属性放在provider中,之后在客户端调用的时候可以通过反射来调用对应的方法。

dubbo为grpc的服务规定了接口。通过此接口就可以获取代理对象,获取服务的描述性息
在这里插入图片描述

并且官网提供了可拓展点和完整的调用说明:
https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/reference-manual/architecture/service-invocation/
在这里插入图片描述

在什么时候注册grpc服务,并且设置代理对象的?

在构建好invoker对象之后,会调用XXX_SetProxyImpl方法,设置值。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

问题 二:

invoker是dubbo中很重要的概念,表示的是一个可调用者,通过这个概念,dubbo高度抽象了服务,
在这里插入图片描述

官网:

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/advanced-features-and-usage/service/fault-tolerent-strategy/

invoker表示可用的服务,不管是loadbalance、router、director等,都是一个可用的invoker。

在抽象的invoker都有基础的实现,剩下的filter,cluster都是基于此继续操作的。我们来看invoker的构建过程

在准备好服务方的配置之后,就开始挨个启动服务了,每个服务的定义信息叫做ServiceConfig,调用Export来初始化此服务,并且注册到注册中心。

我们知道dubbo中服务的定义信息传递全都靠url,在Export中,主要有下面的几个步骤

  1. 获取注册中心,创建代理工厂(用于代理对象的创建)
  2. 构建url
  3. 通过不同的通信协议构建不同的Invoker,并且启动底层具体通信协议对应的服务器。
  4. 服务发布到注册中心

这里我们只说Invoker的构建。对应到代理里面如下:

	invoker := proxyFactory.GetInvoker(ivkURL) // 通过代理工厂创建对应的代理类exporter := extension.GetProtocol(protocolwrapper.FILTER).Export(invoker) // 从extension中获取对应的底层通信协议来构建invoker

先看代理工厂如何创建Invoker

在这里插入图片描述

基础的调用就是ProxyInvoker,等请求来了之后,通过一系列filter,会走到这里,通过反射来调用具体的方法。

不同的通信协议如何构建invoker

在这里插入图片描述

extension是一个拓展点,也是通过它逐渐的让dubbo丰满起来。

filter对应的wrapper是ProtocolFilterWrapper,通过它基于ProxyInvoker来构建最终的Invoker,最后通过最终的通信协议的来构建最终的Invoker,从而启动服务器,注册服务。

在dubbo的protocol包下面列举出了支持的通信协议

在这里插入图片描述

我们来查看dubbo3种trip的通信协议

上面已经介绍过了,主要是确定序列化类型,设置invoker代理。启动服务器

还有一个问题

最终是怎么通过反射调用的,并且何时设置的反射呢

直接去ProxyInvoker中查看invoke方法

在这里插入图片描述

这里对反射要说明一下,反射调用的时候需要确定参数的个数,结合实际,参数个数就一个或者两个,其中必须用context,剩下的要么是无参,要么就一个request对象。

查看serviceMap.Register方法可知,在注册的时候会解析此结构体中的方法,并且设置封装为Service对象

客户端创建一个Ref开始

客户端的构建思路和上面的差不多,不过多了注册中心,cluster的构建。

还是从几个问题开始

  1. 代理对象的创建和关联
  2. invoker对象的构建流程

整个流程从ReferenceConfig.Refer方法开始

代理对象的创建

按照dubbo的逻辑,对象的创建必须去proxyFactory中,但这里是整个filter,cluster构建好了之后,在基于此构建代理对象,因为这里的代理对象是最终给用户侧来调用的,所以,肯定是放在最后一步的。

最终的入口在DefaultProxyFactory.GetProxy中,此方法中会创建Proxy对象

type Proxy struct {rpc         common.RPCService // 代理还要保留origininvoke      protocol.Invoker // 生成的invoke对象,这是代表整个dubbo服务的invoke,此invoke中包含了 cluster,load等等流量治理的功能callback    interface{}attachments map[string]stringimplement   ImplementFunc // 动态函数,这是一个函数类型,会通过此函数来实现普通服务和dubbo服务的对接once        sync.Once
}

在这里插入图片描述

这里只是设置了proxy,并没有做方法绑定操作,真正的是在ReferenceConfig.Implement中实现的。他会调用到刚刚创建的proxy对象中的implement方法,从而给客户端动态绑定属性,达到调用dubbo服务的功能。这是重点,我们源码分析一下

源码:proxy.DefaultProxyImplementFunc

func DefaultProxyImplementFunc(p *Proxy, v common.RPCService) {// 开始反射操作valueOf := reflect.ValueOf(v)valueOfElem := valueOf.Elem()// 这是重点,最终会将客户端中的属性方法,都设置为此函数,然后在此函数里面做调用。// 当我们调用方法的时候,会调用到这里来,makeDubboCallProxy := func(methodName string, outs []reflect.Type) func(in []reflect.Value) []reflect.Value {return func(in []reflect.Value) []reflect.Value {var (err            errorinv            *invocation_impl.RPCInvocationinIArr         []interface{}inVArr         []reflect.Valuereply          reflect.ValuereplyEmptyFlag bool)if methodName == "Echo" {methodName = "$echo"}// 确定参数和返回值等信息if len(outs) == 2 { // return (reply, error)if outs[0].Kind() == reflect.Ptr {reply = reflect.New(outs[0].Elem())} else {reply = reflect.New(outs[0])}} else { // only return errorreplyEmptyFlag = true}start := 0end := len(in)invCtx := context.Background()// retrieve the context from the first argument if existedif end > 0 {if in[0].Type().String() == "context.Context" {if !in[0].IsNil() {// the user declared context as method's parameterinvCtx = in[0].Interface().(context.Context)}start += 1}}if end-start <= 0 {inIArr = []interface{}{}inVArr = []reflect.Value{}} else if v, ok := in[start].Interface().([]interface{}); ok && end-start == 1 {inIArr = vinVArr = []reflect.Value{in[start]}} else {inIArr = make([]interface{}, end-start)inVArr = make([]reflect.Value, end-start)index := 0for i := start; i < end; i++ {inIArr[index] = in[i].Interface()inVArr[index] = in[i]index++}}inv = invocation_impl.NewRPCInvocationWithOptions(invocation_impl.WithMethodName(methodName),invocation_impl.WithArguments(inIArr),invocation_impl.WithCallBack(p.callback), invocation_impl.WithParameterValues(inVArr))if !replyEmptyFlag {inv.SetReply(reply.Interface()) // 这里的reply就是response}for k, value := range p.attachments {inv.SetAttachment(k, value)}// add user setAttachment. It is compatibility with previous versions.atm := invCtx.Value(constant.AttachmentKey)if m, ok := atm.(map[string]string); ok {for k, value := range m {inv.SetAttachment(k, value)}} else if m2, ok2 := atm.(map[string]interface{}); ok2 {// it is support to transfer map[string]interface{}. It refers to dubbo-java 2.7.for k, value := range m2 {inv.SetAttachment(k, value)}}// 调用服务,这里用的是闭包result := p.invoke.Invoke(invCtx, inv)err = result.Error()// cause is raw user level errorcause := perrors.Cause(err)if err != nil {// if some error happened, it should be log some info in the separate file.if throwabler, ok := cause.(java_exception.Throwabler); ok {logger.Warnf("[CallProxy] invoke service throw exception: %v , stackTraceElements: %v", cause.Error(), throwabler.GetStackTrace())} else {// entire error is only for printing, do not return, because user would not want to deal with massive framework-level error messagelogger.Warnf("[CallProxy] received rpc err: %v", err)}} else {logger.Debugf("[CallProxy] received rpc result successfully: %s", result)}if len(outs) == 1 {return []reflect.Value{reflect.ValueOf(&cause).Elem()}}if len(outs) == 2 && outs[0].Kind() != reflect.Ptr {return []reflect.Value{reply.Elem(), reflect.ValueOf(&cause).Elem()}}return []reflect.Value{reply, reflect.ValueOf(&cause).Elem()}}}if err := refectAndMakeObjectFunc(valueOfElem, makeDubboCallProxy); err != nil {logger.Errorf("The type or combination type of RPCService %T must be a pointer of a struct. error is %s", v, err)return}
}

在这里插入图片描述

invoker的构建是什么样子?

对应的代码在ReferenceConfig.Refer

  1. 构建url
  2. 通过不同的通信协议构建不同的base invoker,对于trip来说就是DubboInvoker
  3. 基于此Invoker,增加filter功能
  4. 增加cluster
  5. 构建代理对象

DubboInvoker是什么样子

在这里插入图片描述

在这里插入图片描述

cluster

dubbo提供的集群容错的策略,cluster中的交互逻辑如下:

在这里插入图片描述

可以看到,整体的流程是获取所有的可用的invoker,然后通过路由,负载均衡,选择出一个invoker来做调用。

官网链接:

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/advanced-features-and-usage/service/fault-tolerent-strategy/

源码:https://github.com/apache/dubbo-go/tree/main/cluster/cluster

支持的策略如下:

理解了上面的逻辑,这里的代码比较好理解。

在这里插入图片描述

failover

快速切换,规定了重试次数,超过之后就失败。

在这里插入图片描述

failfast

只调用一次。失败就失败了

在这里插入图片描述

failsafe

在这里插入图片描述

会打印错误日志,并且返回一个空对象回去

forking

在这里插入图片描述

并行调用(默认为2),主要有一个成功,就返回,规定超时时间(1s)就返回失败

broadcast

在这里插入图片描述

广播调用,等待所有的调用结束之后才返回

available

在这里插入图片描述

zoneaware

统一地区的优先访问,其实就是做load balance 之前增加了一层筛选

adaptivesvc

自适应的cluster,需要搭配p2c load balance 使用,这个比较复杂,需要搭配filter使用。之后出文章详解。

failback

失败恢复,将失败的请求放在队列里面,重试,但没有返回值

这里需要一个机制,重试机制的实现

在Java中是放在时间轮里面实现的,go用ticker实现定时触发,一秒一次。

默认重试3次,重试队列长度为100

重试队列

在这里插入图片描述

处理过程

在这里插入图片描述

在这里插入图片描述

当失败之后,就会将调用放在一个定时任务的队列里面,每隔一秒就会将所有的任务执行一次(任务的间隔必须大于5秒),如果失败之后,通过条件决定是否要放在队列中。

loadBalance

官网:https://cn.dubbo.apache.org/zh-cn/overview/core-features/load-balance/

源码:https://github.com/apache/dubbo-go/tree/main/cluster/loadbalance

负载均衡从一堆可用的invoker中选一个出来。

consistenthashing

一致性hash,确定的入参,确定的提供者,适用于有状态请求。

但当invoker发生了变化,会重新计算hash分配。这里需要构建hash环,说到hash环就得有虚拟节点,虚拟节点是为了更好的分配平均,在这里每个物理节点会对应160个虚拟节点(默认)

在这里插入图片描述

用crc生成一个hashcode,从而生成hash环,每个方法对应一个hash环,并且hashcode发生了变化,就会重构hash环。下面的代码分为三步

hash环构建
在这里插入图片描述

每个物理节点有160个虚拟节点,并且用节点的ip+端口生成address,之后分为两次循环,外层40,内层4次,生成hashcode,并且保存虚拟节点和物理节点的映射关系。最后对生成的hash环做排序,方便之后的查找。

hash环查找

通过key生成hashcode,然后用找到第一个大于等于当前hash值的key,之后返回对应的物理节点

在这里插入图片描述

hash函数

好的函数很重要,可以让请求分配的更加的均匀。

在这里插入图片描述

这我看不懂了,看看GPT的回答吧

在这里插入图片描述

leastactive

最近最少活跃,需要配合filter继续当前节点的活跃情况,如果有多个相同情况的invoke。就通过权重来选择一个,如果还有相同的,就随机选择一个。

在这里插入图片描述

关于随机匹配,会产生一个总权重的随机值,占的比重大的为负数的概率低。

random

加权随机,按照权重做随机

在这里插入图片描述

ringhash

和服务网格相关,这里不做过多介绍

roundrobin

加权轮询,它的算法和Nginx平滑加权轮询算法,默认权重相同。

https://cn.dubbo.apache.org/zh-cn/overview/core-features/load-balance/#roundrobin
在这里插入图片描述

具体可看官网的解释,主要是为了在保证轮询的情况下,尽可能的分布均匀。

轮询算法需要保存上一次选择的节点的状态。

这里分为两步

选择节点

在这里插入图片描述

保存节点状态

在这里插入图片描述

p2c

自适应的算法。

这种算法需要结合filter来实现,之后在详细的说

dubbogo项目分层很清晰,没有像Java那样花里胡哨的代码,也没有线程池,时间轮等这些算法,好理解。


到这里就结束了。dubbo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/5531.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为云CodeArts产品体验的心得体会及想法

文章目录 前言CodeArts 的产品优势一站式软件开发生产线研发安全Built-In华为多年研发实践能力及规范外溢高质高效敏捷交付 功能特性说明体验感受问题描述完结 前言 华为云作为一家全球领先的云计算服务提供商&#xff0c;致力于为企业和个人用户提供高效、安全、可靠的云服务。…

算法与数据结构(二十一)前缀和数组差分数组

前缀和技巧适用于快速、频繁地计算一个索引区间内的元素之和。 1. 一维数组中的前缀和 先看一道例题&#xff0c;力扣第 303 题「区域和检索 - 数组不可变」&#xff0c;让你计算数组区间内元素的和&#xff0c;这是一道标准的前缀和问题&#xff1a; 题目要求你实现这样一个…

【C++初阶】---C++入门篇

文章目录 前言&#x1f31f;一、C历史介绍&#x1f31f;二、命名空间&#x1f30f;2.1.C与C对比&#x1f30f;2.2.命名空间的引入&#x1f30f;2.3.命名空间定义&#x1f30f;2.4.命名空间的使用&#x1f30f;2.5.对上述C与C对比中的第二个不同点的解释&#xff1a; &#x1f3…

TableGPT: Towards Unifying Tables, Nature Language and Commands into One GPT

论文标题&#xff1a;TableGPT: Towards Unifying Tables, Nature Language and Commands into One GPT 论文地址&#xff1a;https://github.com/ZJU-M3/TableGPT-techreport/blob/main/TableGPT_tech_report.pdf 发表机构&#xff1a;浙江大学 发表时间&#xff1a;2023 本文…

使用GGML和LangChain在CPU上运行量化的llama2

Meta AI 在本周二发布了最新一代开源大模型 Llama 2。对比于今年 2 月发布的 Llama 1&#xff0c;训练所用的 token 翻了一倍&#xff0c;已经达到了 2 万亿&#xff0c;对于使用大模型最重要的上下文长度限制&#xff0c;Llama 2 也翻了一倍。 在本文&#xff0c;我们将紧跟趋…

【NLP】使用 Keras 保存和加载深度学习模型

一、说明 训练深度学习模型是一个耗时的过程。您可以在训练期间和训练后保存模型进度。因此&#xff0c;您可以从上次中断的地方继续训练模型&#xff0c;并克服漫长的训练挑战。 在这篇博文中&#xff0c;我们将介绍如何保存模型并使用 Keras 逐步加载它。我们还将探索模型检查…

JavaSwing+MySQL的酒店管理系统

点击以下链接获取源码&#xff1a; https://download.csdn.net/download/qq_64505944/88063706?spm1001.2014.3001.5503 JDK1.8、MySQL5.7 功能&#xff1a;散客开单&#xff1a;完成散客的开单&#xff0c;可一次最多开5间相同类型的房间。 2、团体开单&#xff1a;完成团体…

【论文笔记】KDD2019 | KGAT: Knowledge Graph Attention Network for Recommendation

Abstract 为了更好的推荐&#xff0c;不仅要对user-item交互进行建模&#xff0c;还要将关系信息考虑进来 传统方法因子分解机将每个交互都当作一个独立的实例&#xff0c;但是忽略了item之间的关系&#xff08;eg&#xff1a;一部电影的导演也是另一部电影的演员&#xff09…

醉梦仙踪:二叉树狂想曲,中序遍历的华丽穿梭

本篇博客会讲解力扣“94. 二叉树的中序遍历”的解题思路&#xff0c;这是题目链接。 如何对二叉树进行中序遍历呢&#xff1f;所谓中序遍历&#xff0c;即先遍历左子树&#xff0c;接着遍历根节点&#xff0c;最后遍历右子树的一种遍历方式。具体来说&#xff0c;假设有某一种“…

htmlCSS-----背景样式

目录 前言&#xff1a; 背景样式 1.背景颜色 background-color 2.背景图片 background-image 背景的权重比较 代码示例&#xff1a; 前言&#xff1a; 很久没写文章了&#xff0c;会不会想我呢&#xff01;今天我们开始学习html和CSS的背景样式以及文字样式&#xff…

qt 5.12.6配置 msvc2015 32bit

qt 5.12.6配置 msvc2015 32bit 1.添加临时档案库2.安装 msvc20153. 配置 qmake 环境4.修改系统环境变量5.问题修改1.qt没有被正确的安装,请运行make install2.QT编译出错&#xff1a;rc不是内部或外部命令&#xff0c;也不是可运行的程序 或批处理文件。3.QT License check fai…

【Selenium+Pytest+allure报告生成自动化测试框架】附带项目源码和项目部署文档

目录 前言 【文章末尾给大家留下了大量的福利】 测试框架简介 首先管理时间 添加配置文件 conf.py config.ini 读取配置文件 记录操作日志 简单理解POM模型 简单学习元素定位 管理页面元素 封装Selenium基类 创建页面对象 简单了解Pytest pytest.ini 编写测试…

php使用PDO_sqlsrv

php拓展下载&#xff1a;Microsoft Drivers for PHP 发行说明 - PHP drivers for SQL Server | Microsoft Learn 参考文章&#xff1a;php7.3.4 pdo方式连接sqlserver 设置方法_pdo sqlserver_黑贝是条狗的博客-CSDN博客 php5.6.9安装sqlsrv扩展&#xff08;windows&#xff0…

CXL Bias Mode (1) - Bias Mode 背景与分类

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

SUSE宣布推出免费RHEL分叉以保留企业级Linux的选择权

导读在Red Hat宣布将限制AlmaLinuxOS或Rocky Linux等社区发行版对其公共仓库的访问后&#xff0c;最近Red Hat与IBM之间发生了一些争论&#xff0c;有鉴于此&#xff0c;SUSE今天宣布计划为RHEL和CentOS用户提供一个免费的替代方案。 SUSE已经开发了SUSE Linux Enterprise (SLE…

【问题记录】Ubuntu 22.04 环境下,打开 VS Code 老是访问密钥环该怎么解决?

目录 环境 问题情况 解决方法 环境 VMware Workstation 16 Pro &#xff08;版本&#xff1a;16.1.2 build-17966106&#xff09;ubuntu-22.04.2-desktop-amd64 问题情况 在Ubuntu下&#xff0c;每次运行 VS Code时&#xff0c;老是提示要输入密钥密码来解锁保存在密钥环&am…

C语言程序运行需要的两大环境《C语言进阶》

目录 程序的翻译环境和执行环境 翻译环境分为两部分&#xff0c;编译链接 第一步&#xff1a;预编译&#xff08;预处理&#xff09; 第二步&#xff0c;编译 第三步&#xff1a;汇编 关于运行环境分为四点&#xff1a; 关于链接库 程序的翻译环境和执行环境 在 ANSI C(标…

【全面解析】Windows 如何使用 SSH 密钥远程连接 Linux 服务器

创建密钥 创建 linux 服务器端的终端中执行命令 ssh-keygen&#xff0c;之后一直按Enter即可&#xff0c;这样会在将在 ~/.ssh/ 路径下生成公钥(id_rsa.pub)和私钥(id_rsa) 注意&#xff1a;也可以在 windows 端生成密钥&#xff0c;只需要保证公钥在服务器端&#xff0c;私钥…

Apache Struts2漏洞复现之s2-001漏洞复现

0x01 声明&#xff1a; 仅供学习参考使用&#xff0c;请勿用作违法用途&#xff0c;否则后果自负。 0x02 简介&#xff1a; Apache Struts 2是一个用于开发Java EE网络应用程序的开放源代码网页应用程序架构。它利用并延伸了Java ServletAPI&#xff0c;鼓励开发者采用MVC架构…

Android ObjectBox数据库的使用与详解

一、介绍 Room数据库 之前我已介绍了jetpack组件的数据库&#xff1a;Room&#xff0c;有小伙伴需要了解Room数据库可以查看这个地址&#xff1a;Android JetPack组件之Room数据库的集成与详解_android room数据库_蜗牛、Z的博客-CSDN博客 数据库的性能对设备来说很重要&#…