K8s组件:etcd安装、使用及原理(Linux)

news2024/12/21 15:48:39

K8s组件:etcd安装、使用及原理(Linux)

1 介绍及安装

1.1 介绍

分布式系统架构中对一致性要求很高,etcd就满足了分布式系统中的一致性要求。

  • 实现了分布式一致性键值对存储的中间件,支持跨平台,有活跃的社区。
  • etcd 是基于go实现的一个分布式键值对存储(类比Redis),设计用来可靠而快速的保存关键数据并提供访问。通过分布式锁,leader选举和写屏障(write barriers) 来实现可靠的分布式协作。etcd集群是为高可用,持久性数据存储和检索而准备。
  • etcd完整的cluster(集群)至少需要3台,这样才能选出一个master和两个node
  • etcd目前占用2379和2380两个端口
  • 2379:提供HTTP API服务,和etcdctl交互;
  • 2380:集群中节点间通讯;
  • 具有强一致性,常用于注册中心(配置共享和服务发现)
  • 现目前是分布式和云原生下的基础组件,如:k8s等

CAP理论:Consistency一致性、Availability可用性、partition tolerance分区容错性【CP、AP】

  • 系统架构:
    在这里插入图片描述

应用场景:

  1. 键值对存储
  2. 服务注册与发现
  3. 消息发布与订阅
  4. 分布式锁

1.2 Raft协议

参考:https://juejin.cn/post/7035179267918938119#heading-3

1.2.1 基本概念

①名词解释

Raft协议一共包含3种角色:

  • Leader(领袖):领袖由群众投票选举得出,每次选举,只能选出一名领袖;
  • Candidate(候选人):当没有领袖时,某些群众可以成为候选人,然后去竞争领袖的位置;
  • Follower(群众):这个很好理解,就不解释了。

然后在进行选举过程中,还有几个重要的概念:

  1. Leader Election(领导人选举):简称选举,就是从候选人中选出领袖;
  2. Term(任期):它其实是个单独递增的连续数字,每一次任期就会重新发起一次领导人选举;
  3. Election Timeout(选举超时):就是一个超时时间,当群众超时未收到领袖的心跳时,会重新进行选举。
②角色转换

这幅图是领袖、候选人和群众的角色切换图,我先简单总结一下:
在这里插入图片描述

  • 群众 -> 候选人:当开始选举,或者“选举超时”时
  • 候选人 -> 候选人:当“选举超时”,或者开始新的“任期”
  • 候选人 -> 领袖:获取大多数投票时
  • 候选人 -> 群众:其它节点成为领袖,或者开始新的“任期”
  • 领袖 -> 群众:发现自己的任期ID比其它节点分任期ID小时,会自动放弃领袖位置

备注:后面会针对每一种情况,详细进行讲解。

1.2.2 选举

①领导人选举

为了便于后续的讲解,我画了一副简图,“选举定时器”其实就是每个节点的“超时时间”。

在这里插入图片描述

成为候选人:每个节点都有自己的“超时时间”,因为是随机的,区间值为150~300ms,所以出现相同随机时间的概率比较小,因为节点B最先超时,这时它就成为候选人。

在这里插入图片描述

选举领导人:候选人B开始发起投票,群众A和C返回投票,当候选人B获取大部分选票后,选举成功,候选人B成为领袖。

在这里插入图片描述

心跳探测:为了时刻宣誓自己的领导人地位,领袖B需要时刻向群众发起心跳,当群众A和C收到领袖B的心跳后,群众A和C的“超时时间”会重置为0,然后重新计数,依次反复。

这里需要说明一下,领袖广播心跳的周期必须要短于“选举定时器”的超时时间,否则群众会频繁成为候选者,也就会出现频繁发生选举,切换Leader的情况。
在这里插入图片描述

②领袖挂掉的情况

当领袖B挂掉,群众A和C会的“选举定时器”会一直运行,当群众A先超时时,会成为候选人,然后后续流程和“领导人选举”流程一样,即通知投票 -> 接收投票 -> 成为领袖 -> 心跳探测。

在这里插入图片描述

③出现多个候选者情况

当出现多个候选者A和D时,两个候选者会同时发起投票,如果票数不同,最先得到大部分投票的节点会成为领袖;如果获取的票数相同,会重新发起新一轮的投票。

在这里插入图片描述

当C成为新的候选者,此时的任期Term为5,发起新一轮的投票,其它节点发起投票后,会更新自己的任期值,最后选择新的领袖为C节点。
在这里插入图片描述

1.2.3 日志复制

①复制状态机

复制状态机的基本思想是一个分布式的状态机,系统由多个复制单元组成,每个复制单元均是一个状态机,它的状态保存在操作日志中。如下图所示,服务器上的一致性模块负责接收外部命令,然后追加到自己的操作日志中,它与其他服务器上的一致性模块进行通信,以保证每一个服务器上的操作日志最终都以相同的顺序包含相同的指令。一旦指令被正确复制,那么每一个服务器的状态机都将按照操作日志的顺序来处理它们,然后将输出结果返回给客户端。

②数据同步流程

数据同步流程,借鉴了“复制状态机”的思想,都是先“提交”,再“应用”。当Client发起数据更新请求,请求会先到领袖节点C,节点C会更新日志数据,然后通知群众节点也更新日志,当群众节点更新日志成功后,会返回成功通知给领袖C,至此完成了“提交”操作;当领袖C收到通知后,会更新本地数据,并通知群众也更新本地数据,同时会返回成功通知给Client,至此完成了“应用”操作,如果后续Client又有新的数据更新操作,会重复上述流程。

在这里插入图片描述

③日志原理

日志的是以条目(Entry)的方式顺序组织在一起的,日志中包含index、term、type和data等字段。index随日志条目的递增而递增,term是生成该条目的leader当时处于的term。type是etcd定义的字段,目前有两个类型,一个是EntryNormal正常的日志,EntryConfChange是etcd本身配置变化的日志。data是日志的内容。

在这里插入图片描述
内存中的日志操作,主要是由一个raftLog类型的对象完成的,以下是raftLog的源码。可以看到,里面有两个存储位置,一个是storage是保存已经持久化过的日志条目。unstable是保存的尚未持久化的日志条目。

type raftLog struct {
     // storage contains all stable entries since the last snapshot.
     //这里还是一个内存存储,保存了从上一个snapshot起,已经持久化了的日志条目。
     storage Storage
 
     // unstable contains all unstable entries and snapshot.
     // they will be saved into storage.
     // 保存了尚未持久化的日志条目或快照。
     unstable unstable
 
     // committed is the highest log position that is known to be in
     // stable storage on a quorum of nodes.
     //指示当前已经确认的被半数以上节点同步过的最新日志index
     committed uint64
     // applied is the highest log position that the application has
     // been instructed to apply to its state machine.
     // Invariant: applied <= committed
     //指示已经作用到状态机中的最新日志条目的index
     applied uint64
 
     logger Logger

持久化日志: WAL和snapshot。下图显示持久化的Storage接口定义和storage结构中字段的定义。它实际上就是包含一个WAL来保存日志条目,一个Snapshotter负责保存日志快照的。

在这里插入图片描述
WAL是一种追加的方式将日志条目一条一条顺序存放在文件中。存放在WAL的记录都是walpb.Record形式的结构。Type代表数据的类型,Crc是生成的Crc校验字段。Data是真正的数据。

参考:https://mp.weixin.qq.com/s/o_g5z77VZbImgTqjNBSktA

1.2.4 脑裂情况(网络问题,出现多个master)

当网络问题导致脑裂,出现双Leader情况时,每个网络可以理解为一个独立的网络,因为原先的Leader独自在一个区,所以向他提交的数据不可能被复制到大多数节点上,所以数据永远都不会提交,这个可以在第4幅图中提现出来(SET 3没有提交)。

在这里插入图片描述
当网络恢复之后,旧的Leader发现集群中的新Leader的Term比自己大,则自动降级为Follower,并从新Leader处同步数据达成集群数据一致

在这里插入图片描述
脑裂情况其实只是异常情况的一种,当Leader通知Follower更新日志、Leader提交更新时,都存在各种异常情况导致的问题,这个我就不再详述了,具体可以参考《云原生分布式存储基石-etcd深入解析》书中的“1.4.3 异常情况”这一章,里面讲述的比较清楚。

1.3 安装

  1. 下载压缩包(尽量选择在~目录下下载)
  • 通过github直接下载,然后ftp上传到linux。Github地址:https://github.com/etcd-io/etcd/releases,根据自己linux版本下载
  • 如果github访问过慢的话,可以通过华为镜像网站下载:https://mirrors.huaweicloud.com/etcd/
  • curl直接下载;curl -O https://github.com/etcd-io/etcd/releases/download/v3.4.24/etcd-v3.4.24-linux-amd64.tar.gz

PS:curl默认不支持Https,命令#curl -V(V大写)查看Protocols项有没有https ,如果没有就要用命令:# yum install openssl-devel 装SSL

  1. 解压缩
tar -zxvf etcd-v3.4.6-linux-amd64.tar.gz
  1. 配置环境变量

将文件夹中etcd和etcdctl两个文件添加可执行文件路径到环境变量PATH中。

  • etcd是服务端,etcdctl是运维人员操作的控制端,一般只需要装etcd,现在是学习就都装在同一台机器。
  • PS:用echo $PATH查看自己的环境变量路径
# 移动可执行文件位置
mv etcd /usr/local/bin
mv etcdctl /usr/local/bin
# 修改profile文件,
vim /etc/profile
# 在文件最后加入变量,因为etcd默认使用V2版本,我们需要V3版本的API。
export ETCDCTL_API=3
# 使环境变量生效
source /etc/profile

在这里插入图片描述
4. 查看版本信息

etcdctl version

在这里插入图片描述

  1. 创建etcd配置文件,一定需要确认用户对数据目录etcd有读写权限,否则服务可能无法正确启动
[root@Cent0S7 ~]# mkdir -p /var/lib/etcd/
[root@Cent0S7 ~]# cat <<EOF | sudo tee /etc/etcd.conf
#节点名称
ETCD_NAME=$(hostname -s)
#数据存放位置
ETCD_DATA_DIR=/var/lib/etcd/
EOF

注意:5以后的操作根据自己需求可选

  1. 创建开机启动文件
[root@Cent0S7 ~]# cat <<EOF | sudo tee /etc/systemd/system/etcd.service
 
[Unit]
Description=Etcd Server
Documentation=https://github.com/coreos/etcd
After=network.target
 
[Service]
User=root
Type=notify
#这个文件特别关键,etcd使用的环境变量都需要通过环境变量文件读取
EnvironmentFile=-/etc/etcd.conf
ExecStart=/usr/local/bin/etcd
Restart=on-failure
RestartSec=10s
LimitNOFILE=40000
 
[Install]
WantedBy=multi-user.target
EOF 
  1. 重新加载配置 & 开机启动 & 启动etcd
[root@Cent0S7 ~]# systemctl daemon-reload && systemctl enable etcd && systemctl start etcd

开机启动,设置状态enabled:

[root@Cent0S7 ~]# systemctl list-unit-files etcd.service
UNIT FILE    STATE 
etcd.service enabled
 
1 unit files listed.

查看etcd状态:

[root@Cent0S7 ~]# systemctl show etcd.service
Type=notify
Restart=on-failure
NotifyAccess=main
RestartUSec=10s
TimeoutStartUSec=1min 30s
TimeoutStopUSec=1min 30s
WatchdogUSec=0
WatchdogTimestamp=Sun 2020-11-29 22:44:07 CST
WatchdogTimestampMonotonic=9160693425
------  剩余内容 (略)  -------
  1. 查看端口是否启动
[root@Cent0S7 ~]# netstat -an |grep 2379
tcp        0      0 127.0.0.1:2379          0.0.0.0:*               LISTEN    
tcp        0      0 127.0.0.1:53156         127.0.0.1:2379          ESTABLISHED
tcp        0      0 127.0.0.1:2379          127.0.0.1:53156         ESTABLISHED

CentOS默认没有装netstat,需要 # yum install -y net-tools 自己装

2 使用

2.1 put(新增、修改)

# 设置值 etcdctl put KEY VALUE
etcdctl put myKey "this is etcd"

# 取值 etcdctl get KEY
etcdctl get myKey

在这里插入图片描述

2.2 get(查询)

# 1. 根据key值查询
 etcdctl get name1
# 2. 返回结果不显示key,只显示value
etcdctl get --print-value-only name1
# 3. 按key前缀查找
etcdctl get --prefix name
# 4. 按key的字节排序查找
etcdctl get --from-key name2
# 5. 查询所有key
etcdctl get --from-key ""

2.3 del(删除)

# 1. 删除指定key
etcdctl del name11
# 2. 删除指定前缀的key
etcdctl del --prev-kv --prefix name
# 3. 删除所有key
 etcdctl del --prefix ""

3 etcd+grpc实现服务注册与发现(windows)

项目结构:
在这里插入图片描述

3.1 本地docker启动etcd

本地安装好docker环境,或者直接下载docker-desktop

  • 搭建教程参考:https://editor.csdn.net/md/?articleId=130749488
  1. 拉取镜像
docker pull bitnami/etcd
  1. 运行
docker run -d --name Etcd-server --publish 2379:2379 --env ALLOW_NONE_AUTHENTICATION=yes --env ETCD_ADVERTISE_CLIENT_URLS=http://localhost:2379 bitnami/etcd:latest

在这里插入图片描述

3.2 编写proto文件

server.proto

syntax = "proto3";
option go_package = ".;rpc";
message Empty {

}

message HelloResponse {
    string hello = 1;
}

message RegisterRequest {
    string name = 1;
    string password = 2;
}

message RegisterResponse {
    string uid = 1;
}

service Server {
    rpc Hello(Empty) returns(HelloResponse);
    rpc Register(RegisterRequest) returns(RegisterResponse);
}

通过脚本,生成对应的go代码

生成之前,先在client和server目录下分别创建好rpc目录

gen.sh:

echo "生成rpc server代码"

# 输出目录
OUT=../server/rpc

# protoc脚本及参数
protoc \
--go_out=${OUT} \
--go-grpc_out=${OUT} \
--go-grpc_opt=require_unimplemented_servers=false \
server.proto

echo "生成rpc client代码"

OUT=../client/rpc
protoc \
--go_out=${OUT} \
--go-grpc_out=${OUT} \
--go-grpc_opt=require_unimplemented_servers=false \
server.proto
# 也可以直接在终端执行命令
# 生成server下相关的
protoc --go_out=../server/rpc --go-grpc_out=../server/rpc --go-grpc_opt=require_unimplemented_servers=false server.proto

# client相关的
protoc --go_out=../client/rpc --go-grpc_out=../client/rpc --go-grpc_opt=require_unimplemented_servers=false server.proto

在这里插入图片描述

3.3 server端

①etcd.go:

package main

import (
	"context"
	"fmt"
	clientv3 "go.etcd.io/etcd/client/v3"
	"go.etcd.io/etcd/client/v3/naming/endpoints"
	"log"
)

const etcdUrl = "http://localhost:2379"
const serviceName = "chihuo/server"
const ttl = 10

var etcdClient *clientv3.Client

func etcdRegister(addr string) error {
	log.Printf("etcdRegister %s\b", addr)
	etcdClient, err := clientv3.NewFromURL(etcdUrl)

	if err != nil {
		return err
	}

	em, err := endpoints.NewManager(etcdClient, serviceName)
	if err != nil {
		return err
	}

	lease, _ := etcdClient.Grant(context.TODO(), ttl)

	err = em.AddEndpoint(context.TODO(), fmt.Sprintf("%s/%s", serviceName, addr), endpoints.Endpoint{Addr: addr}, clientv3.WithLease(lease.ID))
	if err != nil {
		return err
	}
	//etcdClient.KeepAlive(context.TODO(), lease.ID)
	alive, err := etcdClient.KeepAlive(context.TODO(), lease.ID)
	if err != nil {
		return err
	}

	go func() {
		for {
			<-alive
			fmt.Println("etcd server keep alive")
		}
	}()

	return nil
}

func etcdUnRegister(addr string) error {
	log.Printf("etcdUnRegister %s\b", addr)
	if etcdClient != nil {
		em, err := endpoints.NewManager(etcdClient, serviceName)
		if err != nil {
			return err
		}
		err = em.DeleteEndpoint(context.TODO(), fmt.Sprintf("%s/%s", serviceName, addr))
		if err != nil {
			return err
		}
		return err
	}

	return nil
}

②server.go:

package main

import (
	"context"
	"fmt"
	"go_code/demo01/study/etcd-grpc/server/rpc"
)

type Server struct {
}

// server.proto文件中 服务提供的方法
// rpc Hello(Empty) returns(HelloResponse);
func (s Server) Hello(ctx context.Context, request *rpc.Empty) (*rpc.HelloResponse, error) {
	//server.proto定义的HelloResponse中只有一个string参数
	resp := rpc.HelloResponse{Hello: "hello client."}
	return &resp, nil
}

/*
server.proto文件中定义的格式,因此设置resp.uid

	message RegisterResponse {
	  string uid = 1;
	}
*/
func (s Server) Register(ctx context.Context, request *rpc.RegisterRequest) (*rpc.RegisterResponse, error) {
	resp := rpc.RegisterResponse{}
	resp.Uid = fmt.Sprintf("%s.%s", request.GetName(), request.GetPassword())
	return &resp, nil
}

③main.go:

package main

import (
	"context"
	"flag"
	"fmt"
	"go_code/demo01/study/etcd-grpc/server/rpc"
	"google.golang.org/grpc"
	"log"
	"net"
	"os"
	"os/signal"
	"syscall"
)

func main() {
	var port int
	flag.IntVar(&port, "port", 8001, "port")
	flag.Parse()
	addr := fmt.Sprintf("localhost:%d", port)

	//关闭信号处理
	ch := make(chan os.Signal, 1)
	signal.Notify(ch, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL, syscall.SIGHUP, syscall.SIGQUIT)
	go func() {
		//开启协程从ch管道中读取,如果有服务停止,则注销etcd中的服务
		s := <-ch
		//处理etcd中服务的注销流程
		etcdUnRegister(addr)
		if i, ok := s.(syscall.Signal); ok {
			os.Exit(int(i))
		} else {
			os.Exit(0)
		}
	}()

	//注册服务
	err := etcdRegister(addr)

	if err != nil {
		panic(err)

	}
	lis, err := net.Listen("tcp", addr)

	if err != nil {
		panic(err)
	}

	grpcServer := grpc.NewServer(grpc.UnaryInterceptor(UnaryInterceptor()))

	rpc.RegisterServerServer(grpcServer, Server{})

	log.Printf("service start port %d\n", port)
	if err := grpcServer.Serve(lis); err != nil {
		panic(err)
	}
}

func UnaryInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
		log.Printf("call %s\n", info.FullMethod)
		resp, err = handler(ctx, req)
		return resp, err
	}
}

3.4 client端

client.go:

package main

import (
	"context"
	"fmt"
	clientv3 "go.etcd.io/etcd/client/v3"
	"go.etcd.io/etcd/client/v3/naming/resolver"
	rpc2 "go_code/demo01/study/etcd-grpc/client/rpc"
	"google.golang.org/grpc/balancer/roundrobin"
	"google.golang.org/grpc/credentials/insecure"
	"log"
	"time"

	"google.golang.org/grpc"
)

const etcdUrl = "http://localhost:2379"
const serviceName = "chihuo/server"

func main() {
	//bd := &ChihuoBuilder{addrs: map[string][]string{"/api": []string{"localhost:8001", "localhost:8002", "localhost:8003"}}}
	//resolver.Register(bd)
	//获取etcd客户端
	etcdClient, err := clientv3.NewFromURL(etcdUrl)
	if err != nil {
		panic(err)
	}
	etcdResolver, err := resolver.NewBuilder(etcdClient)

	//通过grpc与服务建立连接
	conn, err := grpc.Dial(fmt.Sprintf("etcd:///%s", serviceName), grpc.WithResolvers(etcdResolver), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, roundrobin.Name)))

	if err != nil {
		fmt.Printf("err: %v", err)
		return
	}

	//rpc2 "go_code/demo01/study/etcd-grpc/client/rpc"
	//通过连接conn获取ServerClient(与服务器相互通信)
	ServerClient := rpc2.NewServerClient(conn)

	for {
		//通过客户端发起远程调用【请求服务端的Hello方法】,接受服务器的返回结果
		helloRespone, err := ServerClient.Hello(context.Background(), &rpc2.Empty{})
		if err != nil {
			fmt.Printf("err: %v", err)
			return
		}

		log.Println(helloRespone, err)
		time.Sleep(500 * time.Millisecond)
	}

}

3.5 测试效果

  1. 启动三个server,并向etcd注册服务
//1. 进入server/main.go所在目录
go run . --port 8081
go run . --port 8082
go run . --port 8083

在这里插入图片描述

可以,看到我们已经启动了三个服务端,并且已经向etcd注册成功

  1. 启动一个client端,通过etcd拉取服务
    在这里插入图片描述
  2. 观察三个server的打印,可以发现,client端的请求时负载均衡的,每个server都有可能被访问到

在这里插入图片描述

  1. 我们停止server3,发现client的请求被均衡的分发到server1、server2

表明server3已经从etcd中被踢出【服务上线下线】

  • 由于请求过快,因此,可能效果不明显,大家可以将时间打印的更细致一点

进入docker部署的etcd内部,查询所有的key:
在这里插入图片描述

4 ETCD-V3版本变动

4.1 watch机制

etcdv2中的键被废除以后,为了能够跟踪key的变化,使用了事件机制进行跟踪,维护键的状态,来防止被删除掉的键还能恢复和watch到,但是有一个滑动窗口的大小限制,那么如果要获取1000个时间之前的键就获取不到了。因此etcdv2中通过watch来同步数据不是那么可靠,断开连接一段时间后就会导致有可能中间的键的改动获取不到了。在etcdv3中支持get和watch键的任意的历史版本记录。

另外,v2中的watch本质上还是建立很多HTTP连接,每一个watch建立一个tcp套接字连接,当watch的客户端过多的时候会大大消耗服务器的资源,如果有数千个客户端watch数千个key,那么etcd v2的服务端的socket和内存资源会很快被耗尽。v3版本中的watch可以进行连接复用,多个客户端可以共用相同的TCP连接,大大减轻了服务器的压力。

总结一下,其实这里主要进行2点优化:

  • 实时监听key的更新:解决v2中途key的数据更新,客服端不会感知的问题;
  • 多路复用:这个可以想到select和epool模型,就是一个客户之前需要建立多个TCP连接,现在只需要建立一个即可。

etcd-V3版本:可以跟踪的key个数不受限制了,消耗资源更少了

参考:
https://www.cnblogs.com/wutou/p/14056868.htm

https://blog.csdn.net/weixin_34067980/article/details/92961304

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

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

相关文章

交互式标注工具-Paddlelabel

PaddleLabel 是基于飞桨 PaddlePaddle 各个套件功能提供的配套标注工具。目前支持对分类、检测、分割、OCR 四种常见的计算机视觉任务数据集进行标注和管理&#xff0c;除基础的手动标注功能外也支持深度学习辅助标注&#xff0c;可以有效地提升标注效率。重点是free free free…

玩转代码|那些实用的JavaScript单行代码,帮你轻松工作

目录 数组去重 从url获取参数并转为对象 检查对象是否为空 反转字符串 生成随机十六进制 检查当前选项卡是否在后台 检测元素是否处于焦点 检查设备类型 文字复制到剪贴板 获取选定的文本 查询某天是否为工作日 转换华氏/摄氏 两日期之间相差的天数 将 RGB 转换为…

并发-编程之JMMvolatile详解

并发三大特性&#xff1a;可见性、原子性、有序性 并发都是数据【多线程对一个变量进行连续加1】 线程A和B都对count进行连续加1&#xff0c;因为count不是原子性&#xff0c;如果再执行countcount1之前cpu执行权被抢占&#xff0c;就会阻塞住&#xff0c;这时候线程B完成count…

基于Java车库智能管理平台设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

Java | 使用切面AOP拦截并修改Controller接口请求参数

关注common wx&#xff1a; CodingTechWork 引言 在开发过程中&#xff0c;会有一些需求将controller层的一些方法入参进行全量转换&#xff0c;最容易想到的可能是在调用下层service方法时&#xff0c;调用公共的方法进行入参转换&#xff0c;这时带来的唯一问题就是代码不雅…

TCP的粘包、拆包、解决方案以及Go语言实现

什么是粘包&#xff0c;拆包&#xff1f; TCP的粘包和拆包问题往往出现在基于TCP协议的通讯中&#xff0c;比如RPC框架在使用TCP进行数据传输时&#xff0c;由于TCP是基于字节流的协议&#xff0c;而不是基于消息的协议&#xff0c;可能会出现粘包&#xff08;多个消息粘在一起…

基于matlab创建基于颜色特征的图像检索系统(附源码)

一、前言 此示例说明如何使用自定义的功能包工作流创建基于内容的图像检索 &#xff08;CBIR&#xff09; 系统。 基于内容的图像检索 &#xff08;CBIR&#xff09; 系统用于查找在视觉上与查询图像相似的图像。CBIR系统的应用可以在许多领域找到&#xff0c;例如基于网络的…

记账APP:小哈记账4——记账首页页面的制作(1)

项目介绍&#xff1a; 小哈记账是一款用于记账APP&#xff0c;基于Android Studio开发工具&#xff0c;采用Java语言进行开发&#xff0c;同时使用litepal和阿里云数据库进行数据的增删查改&#xff0c;以图标的形式在App的界面上显示。App可以清晰显示收支情况&#xff0c;并以…

2. MongoDB分片集群架构实战-----MongoDB分片集群和多文档事务详解

分布式技术MongoDB 1. 分片简介2. MongoDB分片集群架构3. 环境搭建3.1 分片集群搭建3.2 使用mtools搭建分片集群 4 使用分片集群5. 分片策略5.1 什么是chunk5.2 分片算法5.3 哈希分片5.4 分片标签5.4 分片键(ShardKey)的选择5.5 分片键(ShardKey)的约束 6. 数据均衡6.1 均衡的方…

【JAVAWEB】JavaScript基础知识

目录 1.认识JavaScript 1.1JavaScript是什么 1.2JavaScript和html,css的关系 1.3JavaScript的组成 2.JavaScript的前置知识 2.1JavaScript的书写形式 行内式 内嵌式 外部式 2.2注释 2.3输入输出 3.JavaScript的语法知识 3.1变量的使用 创建变量 使用变量 3.2动态…

echarts柱状图查找数据

controller层&#xff1a; /*** 查询最近一周每天的行为识别总人数* return*/ApiOperation("查询最近一周每天的行为识别总人数") // RequiresPermissions("zhgd:aialarmdata:selectShu")GetMapping("/selectShu")public List<List> se…

UE5 Motion Warping功能学习

MotionWarping&#xff08;运动扭曲&#xff09;可对角色根运动进行修改&#xff0c;从而让角色根运动动画结束时准确停在某一点&#xff0c;如图&#xff1a; 此外UE5还提供移动步幅、转向的Warping功能&#xff08;防滑步&#xff09;&#xff0c;之前写过一个简单的介绍可…

9.2 IO多路复用select函数

目录 I/O多路复用模型 多路复用的实现方式 select函数 fd_set结构体 I/O多路复用模型 多路复用的实现方式 select函数 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);poll函数 int poll(struct pollfd *fds, nfds_…

Git 安装 配置初始化

1. Git 下载 1.1 git 官网下载 Git 官网 https://git-scm.com/download/win 根据自己的电脑系统下载对应的版本 1.2 百度网盘下载 百度网盘&#xff1a;https://pan.baidu.com/s/17Thcov7VKfIc_hINcQimrw 提取码&#xff1a;13142. 安装git 2.1 双击安装包 2.2 点击next …

新应用爆发,开启5G的鱼龙之变

鱼龙变化&#xff0c;是中国自古以来就有的吉祥寓意与美好期盼。早在商代出土的玉雕中&#xff0c;就出现了由鱼化为龙的形象。晋代民间的歌谣中&#xff0c;已经有了“东海大鱼化为龙”的说法。在此之后&#xff0c;“鱼跃龙门”成为了考试中第的代名词&#xff0c;也被引申为…

台灯选用什么类型好?分享好用的护眼台灯

建议是选择护眼台灯比较好一点&#xff0c;不管是大人还是孩子使用&#xff0c;都会比普通台灯护眼一些。主要是普通台灯在交流电状态下正常工作的时候&#xff0c;每秒钟会发生闪烁100次左右&#xff0c;这样的频率人眼是看不出来的。但是在台灯下时间一长&#xff0c;极其容易…

sonarqube安装指南

sonarQube安装的指导文章说起来有很多&#xff0c;其他步骤本文不再赘述&#xff0c;可以参考其他文章。 在这里讲一下对JDK版本的事情。 SonarQube Server对系统的JDK、和研发写代码所使用的JDK版本有要求&#xff0c;要求大版本统一&#xff0c;比如大家基本上都在用JDK8&a…

景联文科技高质量教育GPT题库:引领教育行业的技术革命

ChatGPT拉开了大语言题库和生成式AI产业蓬勃发展的序幕。全世界教育科技公司扎堆接入GPT-4&#xff0c;涵盖美国、欧洲、日韩、中东和北非地区等。大语言题库在教育领域中势必将获得更加广阔的应用前景和丰富的应用场景。 杭州景联文科技是AI基础数据行业的头部企业&#xff0c…

实用技巧之拼接

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于拼接的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.什么时候会用到拼接 二.使用什么方式进…

React V6实现父子组件双向绑定传值

功能背景 之前在写vue的时候用到一个很好用的东西&#xff0c;比如控制一个dialog的显示隐藏&#xff0c;那么可以由父组件控制它显示&#xff0c;子组件&#xff08;即这个dialog&#xff09;自己可以关闭自己&#xff0c;那么他们之间只维护一个visible的状态&#xff0c;就需…