[golang 微服务] 2. RPC架构介绍以及通过RPC实现微服务

news2025/1/10 20:26:47

一.简介

在上一节简单了解了微服务定义和优缺点之后,在使用微服务框架之前,需要首先了解一下RPC架构,通过RPC可以更形象了解微服务的工作流程
  1. RPC的概念

RPC(Remote Procedure Call Protocol),是 远程过程调用的缩写,通俗的说就是 调用远处的一个函数,与之相对应的是 本地函数调用,先来看一下本地函数调用:当写下如下代码的时候:
result := Add(1,2)
传入了1,2两个参数,调用了本地代码中的一个Add函数,得到result这个返回值,这时 参数返回值代码段都在一个进程空间内,这是 本地函数调用那有没有办法,能够调用一个 跨进程 (所以叫"远程",典型的事例,这个进程部署在 另一台服务器 上)的函数呢?
这就是 RPC主要实现的功能,也是 微服务的主要功能
  1. RPC入门

使用微服务化的一个好处就是:

(1).不限定服务的提供方使用什么技术选型,能够实现公司跨团队的技术解耦

(2).每个服务都被封装成进程,彼此"独立"

(3).使用微服务可以跨进程通信

RPC协议可以实现不同语言的直接相互调用,在互联网时代, RPC已经和 IPC(进程间通信)一样成为一个不可或缺的基础构件

IPC: 进程间通信

RPC:远程进通信 —— 应用层协议(http协议同层),底层使用 TCP 实现


在golang中实现RPC非常简单,有封装好的官方库和一些第三方库提供支持,Go RPC可以利用tcphttp来传递数据,可以对要传递的数据使用多种类型编解码方式。golang官方的net/rpc库使用encoding/gob进行编解码,支持tcp或http数据传输方式,由于其他语言不支持gob编解码方式,所以使用net/rpc库实现的RPC方法没办法进行跨语言调用

golang官方还提供了net/rpc/jsonrpc库实现RPC方法,JSON RPC采用JSON进行数据编解码,因而支持跨语言调用,但目前的jsonrpc库是基于tcp协议实现的,暂时不支持使用http进行数据传输。

除了golang官方提供的rpc库,还有许多第三方库为在golang中实现RPC提供支持,大部分第三方rpc库的实现都是使用protobuf进行数据编解码,根据protobuf声明文件自动生成rpc方法定义与服务注册代码,在golang中可以很方便的进行rpc服务调用

二.net/rpc库实现远程调用

  1. 使用http作为RPC的载体实现远程调用了解

演示如何使用golang官方的 net/rpc 库实现RPC方法,使用 http 作为RPC的载体,通过 net/http 包监听客户端连接请求。http基于tcp, 多一层封包和几次握手校验性能自然比直接用tcp实现网络传输要 差一些,所以RPC微服务中一般使用的都是tcp

(1).创建RPC微服务端

新建server/main.go
package main
import (
    "fmt"
    "log"
    "net"
    "net/http"
    "net/rpc"
    "os"
)
// 定义类对象
type World struct {
}
// 绑定类方法
func (this *World) HelloWorld(req string, res *string) error {
    *res = req + " 你好!"
    return nil
    //return errors.New("未知的错误!")
}

// 绑定类方法
func (this *World) Print(req string, res *string) error {
    *res = req + " this is Print!"
    return nil
    //return errors.New("未知的错误!")
}

func main() {
    // 1. 注册RPC服务
    rpc.Register(new(World)) // 注册rpc服务
    rpc.HandleHTTP() // 采用http协议作为rpc载体
    // 2. 设置监听
    lis, err := net.Listen("tcp", "127.0.0.1:8800")
    if err != nil {
        log.Fatalln("fatal error: ", err)
    }
    fmt.Fprintf(os.Stdout, "%s", "start connection")
    // 3. 建立链接
    http.Serve(lis, nil)
}

注意:以上World结构体的方法方法必须满足Go语言的RPC规则

  • 方法只能有两个可序列化的参数,其中第二个参数是指针类型,参数的类型不能是channel(通道)、complex(复数类型)、func(函数),因为它们不能进行 序列化

  • 方法要返回一个error类型,同时必须是公开的方法

(2). 创建RPC客户端

客户端可以是 go web 也可以是一个 go应用,新建client/main.go
package main
import (
    "fmt"
    "net/rpc"
)

func main() {
    // 1. 用 rpc 链接服务器 --Dial()
    conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8800")
    if err != nil {
        fmt.Println("Dial err:", err)
        return
    }

    defer conn.Close()

    // 2. 调用远程函数
    var reply1 string // 接受返回值 --- 传出参数
    err1 := conn.Call("World.HelloWorld", "张三", &reply1)
    if err1 != nil {
        fmt.Println("Call:", err1)
        return
    }

    fmt.Println(reply1)

    var reply2 string // 接受返回值 --- 传出参数
    err2 := conn.Call("World.Print", "李四", &reply2)
    if err2 != nil {
        fmt.Println("Call:", err2)
        return
    }
    fmt.Println(reply2)
}
  1. 使用tcp作为RPC的载体实现远程调用

(1).创建RPC微服务端

新建server/main.go
package main

import (
    "fmt"
    "net"
    "net/rpc"
)

// 定义类对象
type World struct {}

// 绑定类方法
func (this *World) HelloWorld(req string, res *string) error {
    *res = req + " 你好!"
    return nil
}

func main() {
    // 1. 注册RPC服务
    err := rpc.RegisterName("hello", new(World))
    if err != nil {
        fmt.Println("注册 rpc 服务失败!", err)
        return
    }
    // 2. 设置监听
    listener, err := net.Listen("tcp", "127.0.0.1:8800")
    if err != nil {
        fmt.Println("net.Listen err:", err)
        return
    }

    defer listener.Close()
    fmt.Println("开始监听 ...")

    // 3. 建立链接
    for {
        //接收连接
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Accept() err:", err)
            return
        }
        // 4. 绑定服务
        go rpc.ServeConn(conn)
    }
}

注意:以上World结构体的方法方法必须满足Go语言的RPC规则

  • 方法只能有两个可序列化的参数,其中第二个参数是指针类型,参数的类型不能是channel(通道)、complex(复数类型)、func(函数),因为它们不能进行 序列化

  • 方法要返回一个error类型,同时必须是公开的方法

(2). 创建RPC客户端

新建client/main.go
package main

import (
    "fmt"
    "net/rpc"
)

func main() {
    // 1. 用 rpc 链接服务器 --Dial()
    conn, err := rpc.Dial("tcp", "127.0.0.1:8800")
    if err != nil {
        fmt.Println("Dial err:", err)
        return
    }

    defer conn.Close()

    // 2. 调用远程函数
    var reply string // 接受返回值 --- 传出参数
    err = conn.Call("hello.HelloWorld", "张三", &reply)
    if err != nil {
        fmt.Println("Call:", err)
        return
    }
    fmt.Println(reply)
}
说明:
首选是通过rpc.Dial拨号RPC服务,然后通过client.Call调用具体的RPC方法,在调用client.Call时,第一个参数是用点号链接的RPC服务名字和方法名字,第二和第三个参数分别定义RPC方法的两个参数

三.使用tcp作为RPC的载体实现远程调用具体案例

案例1.简单使用

1.创建一个hello微服务端,编写微服务端RPC代码,完成后启动该微服务端
2.创建一个hello客户端,编写客户端RPC代码,完成后启动该客户端,访问微服务端RPC功能,并返回相关数据

(1).创建hello微服务端

创建mirco/server/hello/main.go文件,并编写代码,代码下所示:
package main

import (
    "fmt"
    "net"
    "net/rpc"
)

//rpc服务端

//定义一个远程调用的结构体,并创建一个远程调用的函数,函数一般是放在结构体中的
type  Hello struct  {

}

/*
说明:
    1、方法只能有两个可序列化的参数,其中第二个参数是指针类型
        req 表示获取客户端传过来的数据
        res 表示给客户端返回数据
    2、方法要返回一个error类型,同时必须是公开的方法
    3、req和res的类型不能是:channel(通道)、func(函数),因为以上类型均不能进行 序列化
*/
func (this Hello) SayHello(req string, res *string) error {
    fmt.Println("请求的参数:", req)
    //设置返回的数据
    *res = "你好" + req
    return nil
}

func main()  {
    //1、 注册RPC服务
    //hello: rpc服务名称
    err1 := rpc.RegisterName("hello", new(Hello))
    if err1 != nil {
        fmt.Println(err1)
    }

    //2、监听端口
    listen, err2 := net.Listen("tcp", "127.0.0.1:8080")
    if err2 != nil {
        fmt.Println(err2)
    }

    //3、应用退出的时候关闭监听端口
    defer listen.Close()

    for {  // for 循环, 一直进行连接,每个客户端都可以连接
        fmt.Println("开始创建连接")
        //4、建立连接
        conn, err3 := listen.Accept()
        if err3 != nil {
            fmt.Println(err3)
        }
        //5、绑定服务
        rpc.ServeConn(conn)
    }
}

(2).创建hello客户端

创建mirco/client/hello/main.go文件,并编写代码,代码下所示:
package main

import (
    "fmt"
    "net/rpc"
)

//rpc服务端

func main()  {
    //1、用 rpc.Dial和rpc微服务端建立连接
    conn, err1 := rpc.Dial("tcp", "127.0.0.1:8080")
    if err1 != nil {
        fmt.Println(err1)
    }
    //2、当客户端退出的时候关闭连接
    defer conn.Close()

    //3、调用远程函数
    //微服务端返回的数据
    var reply string
    /*
        1、第一个参数: hello.SayHello,hello 表示服务名称  SayHello 方法名称
        2、第二个参数: 给服务端的req传递数据
        3、第三个参数: 需要传入地址,获取微服务端返回的数据
    */
    err2 := conn.Call("hello.SayHello", "我是客户端", &reply)
    if err2 != nil {
        fmt.Println(err2)
    }
    //4、获取微服务返回的数据
    fmt.Println(reply)
}

(3).启动微服务端,以及客户端访问

启动微服务端

启动客户端

案例2.模拟实现一个goods的微服务,增加商品 获取商品功能

1.创建一个goods微服务端,编写微服务端RPC代码,增加函数: 增加商品函数,获取商品函数,完成后启动该微服务端
2.创建一个goods客户端,编写客户端RPC代码,完成后启动该客户端,访问微服务端RPC功能,并返回相关数据

(1).创建goods微服务端

创建mirco/server/goods/main.go文件,并编写代码,代码下所示:
package main

import (
    "fmt"
    "net"
    "net/rpc"
)

// goods微服务:服务端,传入struct,增加商品,获取商品

//创建远程调用的函数,函数一般是放在结构体里面
type Goods struct{}

//AddGoods参数对应的结构体
//增加商品请求参数结构体
type AddGoodsReq struct {
    Id      int
    Title   string
    Price   float32
    Content string
}
//增加商品返回结构体
type AddGoodsRes struct {
    Success bool
    Message string
}

//GetGoods参数对应的结构体
//获取商品请求结构体
type GetGoodsReq struct {
    Id int
}
//获取商品返回结构体
type GetGoodsRes struct {
    Id      int
    Title   string
    Price   float32
    Content string
}

/*
说明:
    1、方法只能有两个可序列化的参数,其中第二个参数是指针类型
        req 表示获取客户端传过来的数据
        res 表示给客户端返回数据
    2、方法要返回一个error类型,同时必须是公开的方法
    3、req和res的类型不能是:channel(通道)、func(函数),因为以上类型均不能进行 序列化
*/

//增加商品函数
func (this Goods) AddGoods(req AddGoodsReq, res *AddGoodsRes)  error {
    //1、执行增加 模拟
    fmt.Printf("%#v\n", req)
    *res = AddGoodsRes{
        Success: true, //根据增加结果,返回状态
        Message: "增加商品成功",
    }
    return nil
}

//获取商品函数
func (this Goods) GetGoods(req GetGoodsReq, res *GetGoodsRes) error {
    //1、执行获取商品 模拟
    fmt.Printf("%#v\n", req)

    //2、返回获取的结果
    *res = GetGoodsRes{
        Id:      12,  //商品id
        Title:   "服务器获取的数据",
        Price:   24.5,
        Content: "我是服务器数据库获取的内容",
    }
    return nil
}

func main()  {
    //1、 注册RPC服务
    //goods: rpc服务名称
    err1 := rpc.RegisterName("goods", new(Goods))
    if err1 != nil {
        fmt.Println(err1)
    }

    //2、监听端口
    listen, err2 := net.Listen("tcp", "127.0.0.1:8080")
    if err2 != nil {
        fmt.Println(err2)
    }
    //3、应用退出的时候关闭监听端口
    defer listen.Close()

    for {  // for 循环, 一直进行连接,每个客户端都可以连接
        fmt.Println("准备建立连接")
        //4、建立连接
        conn, err3 := listen.Accept()
        if err3 != nil {
            fmt.Println(err3)
        }
        //5、绑定服务
        rpc.ServeConn(conn)
    }
}

(2).创建goods客户端

创建mirco/client/goods/main.go文件,并编写代码,代码下所示:
package main

import (
    "fmt"
    "net/rpc"
)

//AddGoods参数对应的结构体
//增加商品请求参数结构体
type AddGoodsReq struct {
    Id      int
    Title   string
    Price   float32
    Content string
}
//增加商品返回结构体
type AddGoodsRes struct {
    Success bool
    Message string
}

//GetGoods参数对应的结构体
//获取商品请求结构体
type GetGoodsReq struct {
    Id int
}
//获取商品返回结构体
type GetGoodsRes struct {
    Id      int
    Title   string
    Price   float32
    Content string
}

func main() {
    //1、用 rpc.Dial和rpc微服务端建立连接
    conn, err1 := rpc.Dial("tcp", "127.0.0.1:8080")
    if err1 != nil {
        fmt.Println(err1)
    }
    //2、当客户端退出的时候关闭连接
    defer conn.Close()

    //3、调用远程函数
    //微服务端返回的数据
    var reply AddGoodsRes
    /*
        1、第一个参数: goods.AddGoods,goods 表示服务名称  AddGoods 方法名称
        2、第二个参数: 给服务端的req传递数据
        3、第三个参数: 需要传入地址,获取微服务端返回的数据
    */
    err2 := conn.Call("goods.AddGoods", AddGoodsReq{
        Id:      10,
        Title:   "商品标题",
        Price:   23.5,
        Content: "商品详情",
    }, &reply)

    if err2 != nil {
        fmt.Println(err1)
    }
    //4、获取微服务返回的数据
    fmt.Println("%#v\n", reply)

    // 5、 调用远程GetGoods函数
    var goodsData GetGoodsRes
    err3 := conn.Call("goods.GetGoods", GetGoodsReq{
        Id: 12,
    }, &goodsData)
    if err3 != nil {
        fmt.Println(err3)
    }
    //6、获取微服务返回的数据
    fmt.Printf("%#v", goodsData)
}

(3).启动微服务端,以及客户端访问

启动微服务端

启动客户端

四.net/rpc/jsonrpc库以及RPC跨语言

标准库的RPC默认采用Go语言特有的 gob编码没法实现跨语言调用,golang官方还提供了 net/rpc/jsonrpc库实现RPC方法,JSON RPC采 用JSON进行数据编解码,因而 支持跨语言调用, 但目前的jsonrpc库是 基于tcp协议 实现的,暂时不支持使用http进行数据传输
  1. Linux命令之nc创建tcp服务测试数据传输

nc是 netcat的简写,是一个功能强大的网络工具,有着网络界的瑞士军刀美誉,nc命令的 主要作用如下:
  • 实现任意TCP/UDP端口的侦听,nc可以作为server以TCP或UDP方式侦听指定端口

  • 端口的扫描,nc可以作为client发起TCP或UDP连接

  • 机器之间传输文件

  • 机器之间网络测速

centos中如果找不到nc命令可以使用 yum install -y nc 安装

使用nc作为微服务server端接收客户端数据

nc -l 192.XXX.XXX.XXX 8080

nc作为微服务server端开启:

客户端请求和上面案例一致,也可以参考下面案例

上面讲解了使用 net/rpc 实现RPC的过程,但是没办法在其他语言中调用上面例子实现的RPC方法。所以接下来使用 net/rpc/jsonrpc 库实现RPC方法,此方式实现的 RPC方法支持跨语言调用
  1. 创建RPC微服务端

使用 net/rpc/jsonrpc 库实现RPC方法:
和rpc微服务端区别: 在 5. 绑定服务步骤中使用 rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
package main

import (
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

//rpc服务端

//定义一个远程调用的结构体,并创建一个远程调用的函数,函数一般是放在结构体中的
type  Hello struct  {

}

/*
说明:
    1、方法只能有两个可序列化的参数,其中第二个参数是指针类型
        req 表示获取客户端传过来的数据
        res 表示给客户端返回数据
    2、方法要返回一个error类型,同时必须是公开的方法
    3、req和res的类型不能是:channel(通道)、func(函数),因为以上类型均不能进行 序列化
*/
func (this Hello) SayHello(req string, res *string) error {
    fmt.Println("请求的参数:", req)
    //设置返回的数据
    *res = "你好" + req
    return nil
}

func main()  {
    //1、 注册RPC服务
    //hello: rpc服务名称
    err1 := rpc.RegisterName("hello", new(Hello))
    if err1 != nil {
        fmt.Println(err1)
    }

    //2、监听端口
    listen, err2 := net.Listen("tcp", "127.0.0.1:8080")
    if err2 != nil {
        fmt.Println(err2)
    }

    //3、应用退出的时候关闭监听端口
    defer listen.Close()

    for {  // for 循环, 一直进行连接,每个客户端都可以连接
        fmt.Println("开始创建连接")
        //4、建立连接
        conn, err3 := listen.Accept()
        if err3 != nil {
            fmt.Println(err3)
        }
        //5、绑定服务
        //rpc.ServeConn(conn)

        // 5. 绑定服务
        /*
           jsonrpc和默认rpc的区别:
                   以前rpc.ServeConn(conn)绑定服务
                   jsonrpc中通过rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
        */
        rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}
代码中最大的变化是用 rpc.ServeCodec函数替代了 rpc.ServeConn函数,传入的参数是针对服务端的json编解码器
  1. 创建RPC客户端

package main

import (
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

//rpc服务端

/*
把默认的rpc 改为jsonrpc
    1、rpc.Dial需要调换成net.Dial
    2、增加建立基于json编解码的rpc服务  client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
    3、conn.Call 需要改为client.Call
*/

func main()  {
    //1、用 net.Dial和rpc微服务端建立连接
    conn, err1 := net.Dial("tcp", "127.0.0.1:8080")
    if err1 != nil {
        fmt.Println(err1)
    }
    //2、当客户端退出的时候关闭连接
    defer conn.Close()

    //建立基于json编解码的rpc服务
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))


    //3、调用远程函数
    //微服务端返回的数据
    var reply string
    /*
        1、第一个参数: hello.SayHello,hello 表示服务名称  SayHello 方法名称
        2、第二个参数: 给服务端的req传递数据
        3、第三个参数: 需要传入地址,获取微服务端返回的数据
    */
    err2 := client.Call("hello.SayHello", "张三", &reply)
    if err2 != nil {
        fmt.Println(err2)
    }
    //4、获取微服务返回的数据
    fmt.Println(reply)
}
先手工调用 net.Dial函数建立TCP链接,然后基于该链接建立针对客户端的json编解码器
  1. 启动微服务端,以及客户端访问

启动微服务端

启动客户端

  1. RPC跨语言

以PHP跨语言调用RPC微服务为案例

PHP代码

<?php
class JsonRPC {
    private $conn;

    function __construct($host, $port) {
        $this->conn = fsockopen($host, $port, $errno, $errstr, 3);
        if (!$this->conn) {
            return false;
        }
    }

    public function Call($method, $params) {
        if (!$this->conn) {
            return false;
        }
        $err = fwrite($this->conn, json_encode(array(
                'method' => $method,
                'params' => array($params),
                'id' => 0,
            ))."\n");
        if ($err === false) {
            return false;
        }
        stream_set_timeout($this->conn, 0, 3000);
        $line = fgets($this->conn);
        if ($line === false) {
            return NULL;
        }
        return json_decode($line,true);
        }
    }
    $client = new JsonRPC("127.0.0.1", 8080);
    $args = "this is php aaa";
    $r = $client->Call("Hello.SayHello", $args);
    print_r($r);
?>

服务端启动和上面微服务端启动一致,php端访问,结果如下:

  1. RPC协议封装

后期使用微服务框架 GRPC Go-Micro的时候,都是使用 框架封装好的服务和客户端,接下来通过一个简单的示例演示一下 如何封装,以此来理解 封装的原理,上面的代码服务名都是写死的,不够灵活(容易写错),这里对RPC的服务端和客户端再次进行一次封装,来 屏蔽掉服务名,具体代码如下:

服务端封装

新建server/models/tools
package models

import "net/rpc"

var serverName = "HelloService"

type RPCInterface interface {
    HelloWorld(string, *string) error
}

// 调用该方法时, 需要给 i 传参, 参数应该是 实现了 HelloWorld 方法的类对象!
func RegisterService(i RPCInterface) {
    rpc.RegisterName(serverName, i)
}

封装之后的服务端实现如下:

package main

import (
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
    "server/models"
)
// 定义类对象
type World struct {}

// 绑定类方法
func (this *World) HelloWorld(req string, res *string) error {
    fmt.Println(req)
    *res = req + " 你好!"
    return nil
    //return errors.New("未知的错误!")
}

func main() {
    //注册rpc服务 维护一个hash表,key值是服务名称,value值是服务的地址
    // rpc.RegisterName("HelloService", new(World))
    models.RegisterService(new(World))
    //设置监听
    listener, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    for {
        //接收连接
        conn, err := listener.Accept()
        if err != nil {
            panic(err)
        }
        //给当前连接提供针对json格式的rpc服务
        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}

客户端封装

新建client/models/tools
package models

import (
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

var serverName = "HelloService"

type RPCClient struct {
    Client *rpc.Client
    Conn net.Conn
}

func NewRpcClient(addr string) RPCClient {
    conn, err := net.Dial("tcp", addr)
    if err != nil {
        fmt.Println("链接服务器失败")
        return RPCClient{}
    }
    //套接字和rpc服务绑定
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
    return RPCClient{Client: client, Conn: conn}
}

func (this *RPCClient) CallFunc(req string, resp *string) error {
    return this.Client.Call(serverName+".HelloWorld", req, resp)
}

封装之后客户端实现

package main

import (
    "client/models"
    "fmt"
)

func main() {
    //建立tcp连接
    client := models.NewRpcClient("127.0.0.1:8080")
    //关闭连接
    defer client.Conn.Close()

    var reply string // 接受返回值 --- 传出参数
    err := client.CallFunc("this is client", &reply)

    if err != nil {
        fmt.Println("Call:", err)
        return
    }
    fmt.Println(reply)
}

[上一节][golang 微服务] 1.单体式架构以及微服务架构介绍

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

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

相关文章

【提示学习】HPT: Hierarchy-aware Prompt Tuning for Hierarchical Text Classification

论文信息 名称内容论文标题HPT: Hierarchy-aware Prompt Tuning for Hierarchical Text Classification论文地址https://arxiv.org/abs/2204.13413研究领域NLP, 文本分类, 提示学习, 层级标签文本分类提出模型HPT(Hierarchy-aware Prompt Tuning)来源EMNLP 2022源码https://gi…

SpringBoot AOP切面编程 使用案例

参考资料 Springboot AOP实现指定敏感字段数据加密 &#xff08;数据加密篇 二&#xff09;【SpringBoot-3】切面AOP实现权限校验&#xff1a;实例演示与注解全解【小家Spring】Spring AOP中Pointcut切入点表达式最全面使用介绍AOP编程过程中的Signature接口 本篇文章核心思想…

(详解)vue中实现主题切换的三种方式

目录 一、背景 二、实现思路 方法1&#xff1a;定义全局的CSS变量 方法2&#xff1a;切换已定义好的css文件 方法3&#xff1a;切换顶级CSS类名 (需使用css处理器,如sass、less等) 一、背景 在我们开发中我们会遇到像是需要切换程序风格、主题切换啦这种应用场景。 参考大佬…

经典智能合约案例之发红包

经典智能合约案例&#xff1a;发红包 角色分析&#xff1a;发红包的人和抢红包的人 功能分析&#xff1a; 发红包&#xff1a;发红包的功能&#xff0c;可以借助构造函数实现&#xff0c;核心是将ether打入合约&#xff1b; 抢红包&#xff1a;抢红包的功能&#xff0c;抢成…

Flume系列:案例-Flume 聚合拓扑(常见的日志收集结构)

目录 Apache Hadoop生态-目录汇总-持续更新 1&#xff1a;案例需求-实现聚合拓扑结构 3&#xff1a;实现步骤&#xff1a; 2.1&#xff1a;实现flume1.conf - sink端口4141 2.2&#xff1a;实现flume2.conf- sink端口4141 2.3&#xff1a;实现flume3.conf - 监听端口4141 …

32 KVM管理系统资源-管理虚拟内存热插

文章目录 32 KVM管理系统资源-管理虚拟内存热插32.1 概述32.2 约束限制32.3 操作步骤32.3.1 配置虚拟机XML32.3.2 热插并上线内存 32 KVM管理系统资源-管理虚拟内存热插 32.1 概述 在虚拟化场景下&#xff0c;虚拟机的内存、CPU、外部设备都是软件模拟呈现的&#xff0c;因此…

深度学习进阶篇-国内预训练模型[5]:ERINE、ERNIE 3.0、ERNIE-的设计思路、模型结构、应用场景等详解

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

旧键盘打字 两数之和

&#x1f495;"不要因为别人的成功而感到沮丧&#xff0c;你的时机会来&#xff0c;只要你继续努力、坚持不懈。"&#x1f495; &#x1f43c;作者:不能再留遗憾了&#x1f43c; &#x1f386;专栏:Java学习&#x1f386; &#x1f697;本文章主要内容:使用哈希表的思…

2023年江苏省中职网络安全Web渗透测试解析(超详细)

一、竞赛时间 180分钟 共计3小时 二、竞赛阶段 1.访问地址http://靶机IP/web1,分析页面内容,获取flag值,Flag格式为flag{xxx}; 2.访问地址http://靶机IP/web2,访问登录页面。用户user01的密码为1-1000以内的数,获取用户user01的密码,将密码作为Flag进行提交,Flag格式为…

Java数据结构之第十四章、泛型进阶

补充复杂示例&#xff1a; public class MyArray<E extends Comparable<E>> { ... } 表明E必须是实现了Comparable接口的 泛型基础内容 目录 一、通配符 二、通配符上界 三、通配符下界 一、通配符 ? 用于在泛型的使用&#xff0c;即为通配符 示例&#xf…

如何使用 Python Nornir 实现基于 CLI 的网络自动化?

在现代网络环境中&#xff0c;网络自动化已成为管理和配置网络设备的重要工具。Python Nornir 是一个强大的自动化框架&#xff0c;它提供了一个简单而灵活的方式来执行网络自动化任务。本文将详细介绍如何使用 Python Nornir 实现基于 CLI 的网络自动化。 1. Python Nornir 概…

jacoco增量覆盖率平台开发

先聊聊做这个平台的意义&#xff0c;从项目管理角度来说&#xff0c;测试说项目测试完成&#xff0c;该如何证明呢&#xff1f;一般情况下我们进行验收时没什么问题就算完成了&#xff0c;但是实际上测试很多情况并没有考虑到。所以该平台可以反哺测试的测试用例&#xff0c;让…

GO的服务

1.go的安装 1.1 确认版本go version go version go1.20.4 darwin/amd64 可以看到是macos10.14版本。如果是m1 需要安装对应的版本 1.2 用vscode 进行编写go的简单例子 先进入vscode的界面&#xff0c;新建一个目录为godemo&#xff0c;里面就是go的例子的工作目录&#xff0…

计算机的大小端存储模式(计算机小白必看!)

目录 1.什么是大端小端 2.为什么会有大小端模式之分呢&#xff1f; 3.如何判断当前机器为大端字节序还是小端字节序 本文将介绍计算机存储数据时的大小端问题 1.什么是大端小端 大端&#xff08;存储&#xff09;模式&#xff0c;是指数据的低位保存在内存的高地址中&…

可视化库seaborn常用操作介绍

目录 1.seaborn 概括2.Seaborn的调色板3.单变量绘图分析4.回归分析绘图5.分类图绘制6.FacetGrid使用7.Heatmap 1.seaborn 概括 seaborn库是一个用于数据可视化的Python库&#xff0c;它建立在matplotlib之上&#xff0c;可以让你轻松地创建各种美观的图表和图形。 在seaborn中…

致敬科技工作者:我们的世界因你们而美好

在我们的日常生活中&#xff0c;科技无处不在&#xff0c;而这一切离不开科技工作者的辛勤付出。作为一名科技从业者&#xff0c;我深深地理解并感悟到&#xff0c;科技工作者们的作用是不可替代的。 二十一世纪&#xff0c;科技的发展日新月异。我们见证了第一台计算机的发明…

RT1170启动详解:Boot配置、Bootable image头的组成

文章目录 1 基础知识2 BOOT配置2.1 BOOT_CFG配置2.2 BOOT_MODE 3 Bootable image3.1 文件格式3.2 Bootable image头的组成3.3 Bootable image的生成3.4 例&#xff1a;BootROM之non-XIP加载过程3.5 例&#xff1a;bin文件分析 1 基础知识 &#xff08;1&#xff09;BootROM Bo…

地面分割--Patchwork

文章目录 1问题定义2同心区域模型3按照区域划分的平面拟合4地面点似然估计&#xff08;GLE&#xff09;总结 patchwork是一种比较优秀的地面分割方法。其过程主要分为三个部分&#xff1a;同心圆环区域(CZM:concentric Zone Model)&#xff0c;按照区域划分的平面拟合(R-GPF:re…

OpenCV基础操作(5)图像平滑、形态学转换、图像梯度

import numpy as np import cv2 as cv from matplotlib import pyplot as plt一、图像平滑 1、2D卷积 我们可以对 2D 图像实施低通滤波&#xff08;LPF&#xff09;&#xff0c;高通滤波&#xff08;HPF&#xff09;等。 LPF 帮助我们去除噪音&#xff0c;模糊图像。HPF 帮助…

【数字信号处理】Goertzl算法详解推导及双音多频(DTMF)信号检测

Geortzel算法 【要点解析】 根据卷积公式 y ( n ) = ∑ m = − ∞ ∞ x ( m )