Redis部署-集群

news2024/11/23 11:33:48

目录

集群

数据分片算法

哈希求余

一致性哈希算法

哈希槽分区算法

redis集群搭建

1.创建目录和配置.

2.将上述redis节点.构建成集群

3.使用客户端连接集群

集群模式下的故障转移流程

1.故障判定

2.故障迁移

集群扩容


集群

广义上的集群,只要是多个机器,构成了分布式系统,都可以称为是一个"集群".前面的主从模式和哨兵模式也可以称为是广义的集群.

狭义的集群,redis提供的集群模式,在这个集群模式之下,主要是姐姐,存储空间不足的问题.

redis集群基本概念

上述的哨兵模式,虽然提高了系统的可用性,但是真正用来存储数据的还是master和slave节点,所有的数据都需要存储在单个的master和slave节点中.

如果数据量很大,超出了master/slave所在机器的物理内存,就可能出现严重问题了.

那么如何获得更大的空间,加机器即可!!!

redis集群就是在上述思路下,引入多组master/slave存储数据全集的一部分,从而构成一个更大的整体,称为是redis集群.

假定整个数据全集是1TB,此时就可以引入三组master/slave,每一组master/slave只需要存储整个数据全集的三分之一即可.

其中每一组主从节点保存的是同样的数据,占数据全集的三分之一.

每个从节点都是对应主节点的备份,当主节点挂了,对应的slave会补位成主节点.

每组主从节点都可以称为是一个分片(sharding).

如果全量数据进一步增加,只要在增加更多的分片即可.


数据分片算法

Redis cluster的核⼼思路是⽤多组机器来存数据的每个部分.那么接下来的核⼼问题就是,给定⼀个数据(⼀个具体的key),那么这个数据应该存储在哪个分⽚上?读取的时候⼜应该去哪个分⽚读取?
围绕这个问题,业界有三种⽐较主流的实现⽅式.

哈希求余

借助了哈希表的基本思想,借助hash函数,把一个key映射到整数,在针对数组的长度,求余,就可以得到一个数组的下标.

比如有三个分片,编号为0,1,2.

此时就可以针对要插入的数据key计算hash值(比如使用MD5计算hash值),在把这个hash值余上分片的个数,就得到了一个编号,此时就可以把这个数据放到对应的下标对应分片中了.

md5是一个计算hash值的算法.它能够针对一个字符串里面的内容进行一系列的数学演算,最终得到一个整数.

它是一个非常广泛使用的hash算法.特点:

  • 1.md5计算结果是定长的,无论输入的原字符串有多长,最终算出的结果就是固定长度.
  • 2.md5计算结果是分散的,两个源字符串,哪怕只有一个地方不同,算出来的md5值也会差别很大.
  • 3.md5计算结果是不可逆的.字符串->md5值是很容易得到,而根据md5值还原出原始字符串是很困难的,理论上是不可行的.

如果计算出hash(key)%3==0,此时这个key就要存储在0号分片中,后续查询key的时候,也是同样的算法.

数据搬运

一旦服务器集群需要扩容,就需要更高的成本了.分片的主要目的是为了提高存储能力,分片越多,能存的数据也就越多,但是成本也就更高.

如果随着业务的增长,原先的三个分片已经不够用了,那么此时就要"扩容",引入更多的分片.

引入新的分片的后,hash(key)%N中的N就变了,加入这里新引入一个分片,N就从3变为了4.

当hash函数和key都不变的情况下,如果N变了,整体的分片结果仍然会改变.

如果发现某个数据,在扩容之后,不应该存储在当前的分片中了,就需要重新进行分配这个数据,这个过程就叫做数据搬运.

从上图可以看出,一共20个数据,经过扩容之后,只有3个数据不需要搬运,17个数据需要搬运!!!

由此我们知道采用哈希求余算法需要搬运的数据的比例是很高的.如果在生产环境上扩容,开销是极大的.所以我们往往不能直接在生产环境上操作上述过程,只能通过替换的方式来实现扩容.但是替换也就意味着依赖的机器更多了,成本更高,操作步骤也非常复杂!!!


一致性哈希算法

在hash求余这种操作下,当前的key属于哪个分片,是交替的.

102->0,103->1,104->2,105->0......,交替出现,就导致数据搬运的成本很大.

在一致性hash算法中,把交替出现,改进成了连续出现.降低了数据搬运的开销,能够高效扩容.

一致性hash算法过程

1.把0->2^31-1这个数据空间,映射到一个圆环上,数据按照顺时针方向增长.

2.假设当前存在三个分片,就把分片放到圆环的某个位置上.

3.假定有一个key,计算得到hash值H,就从H所在位置,顺时针往下找,找到的第一个分片,即为该key所从属的分片.

这就相当于,N个分片的位置,把整个圆环分成了N个管辖空间,key的hash值落在某个区间内,就归对应的区间管理.


在这种情况下,如果扩容一个分片,原有分片在环上的位置不动,只要在环上新安排一个分片位置即可.

此时,只要把0号分片上的部分数据,搬运到3号分片上即可,1号分片和2号分片管理的区间上的数据都是不变的.

虽然搬运的成本低了,但是这几个分片上的数据量,就可能步均匀了,就造成了数据倾斜的问题!!!


哈希槽分区算法

此种算法是redis真正采用的分片算法.

为了解决搬运成本高和数据分配不均匀的问题,reids cluster引入了哈希槽算法.

hash_slot = crc16(key) % 16384

其中crc也是一种hash算法.

相当于把整个哈希值,映射到16384个槽位上,也就是[0,16384].

然后把这些槽位均匀的分配给每个分片,每个分片的节点都需要记录自己持有哪些分片.

这种算法,本质就是把一致性hash和哈希求余两种方式结合一下.

假设现在有三个分片,一种可能的分配方式:

0号分片:[0,5461],共5462个槽位;

1号分片:[5462,10923],共5462个槽位.

2号分片:[10924,16383],共5460个槽位.

虽然不是严格意义的均匀,但是差异非常小,此时这三个分片上的数据就是比较均匀的了.

上述只是一种可能的分片方式,实际上分片是非常灵活的,每个分片持有的槽位号,可以是连续的,也可以是不连续的.

此处,每个分片都会使用位图这样的数据结构,来表示出当前持有的槽位.16384个bit位(2KB),用每一位的0或者1来区分这个分片是否持有这个槽位.

如果需要扩容,比如新增一个3号分片,就可以针对原有的槽位进行重新分配.

比如可以把之前每个分片持有的槽位,各拿出一点,分给新的分片.

• 0号分⽚:[0,4095],共4096个槽位
• 1号分⽚:[5462,9557],共4096个槽位
• 2号分⽚:[10924,15019],共4096个槽位
• 3号分⽚:[4096,5461]+[9558,10923]+[15019,16383],共4096个槽位.

注意,我们在使用redis集群分片的时候,不需要手动指定哪些槽位分配给某个分片,只需要告诉redis某个分片应该持有多少个槽位即可,redis会自动完成后续的槽位分配,以及key对应的搬运工作.

关于哈希槽分区算法的两个问题

redis集群是最多有16384个分片吗???

其实不然,如果一个分片上只有一个槽位,这对于集群的数据均匀是难以保证的.而且16384个分片这么大规模的集群,本身的可用性是一个大问题.

实际上redis的作者建议分片的数目不应该超过1000.

为什么是16384个槽位???

节点之间通过⼼跳包通信.⼼跳包中包含了该节点持有哪些slots.这个是使⽤位图这样的数据结构
表⽰的.表⽰16384(16k)个slots,需要的位图⼤⼩是2KB.如果给定的slots数更多了,⽐如65536个了,此时就需要消耗更多的空间,8KB位图表⽰了.8KB,对于内存来说不算什么,但是在频繁的⽹络⼼跳包中,还是⼀个不⼩的开销的.

另⼀⽅⾯,Redis集群⼀般不建议超过1000个分⽚.所以16k对于最⼤1000个分⽚来说是⾜够⽤
的,同时也会使对应的槽位配置位图体积不⾄于很⼤.

总结来说,就是这些个槽位基本上是够用的,同时占用的网络带宽也不是很大.


redis集群搭建

在这里由于只有一台云服务器,所以也是基于docker搭建.

实际工作中,一般是通过主机的方式,来搭建集群.

在搭建之前,一定要把之前启动的redis容器,给停止掉!!!在redis-data目录和redis-sentinel目录下分别执行docker-compose down命令.

在这里我们创建出11个redis节点,其中9个用于集群的搭建,2个用于集群的扩容.


1.创建目录和配置.

创建redis-cluster目录,内部创建两个文件.

在linux上以.sh为后缀结尾的文件,称为是shell脚本.shell脚本里可以批量化执行命令,并且还能加入条件,循环,函数等机制,来完成更加复杂的工作.

generate.sh内容

for port in $(seq 1 9); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.10${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done
# 注意 cluster-announce-ip 的值有变化.
for port in $(seq 10 11); \
do \
mkdir -p redis${port}/
touch redis${port}/redis.conf
cat << EOF > redis${port}/redis.conf
port 6379
bind 0.0.0.0
protected-mode no
appendonly yes
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.30.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
EOF
done

for port in $(seq 1 9);表明这是一个基于范围的循环.类似于java的for each.

seq也是一个命令,seq 1 9表示生成1-9闭区间内的数据.

\是续行符,把下一行的内容和当前行,合并成一行.shell默认情况下,要求把所有的代码都写到一行里,使用续行符来换行.

对于for来说,用do和done表示代码块的开始和结束,shell中{}用来表示变量了,不表示代码块.

shell中拼接字符是直接写到一起,而不需要使用+.

因此上述第一个循环就表示,创建9个目录,在这些目录下创建一个文件,将内容写到文件中去.

这些内容只有在配置集群的ip的时候是不一致的,

cluster-announce-ip 172.30.0.10${port},会生成101-109的ip.

经过上述两个循环,就会得到11个目录,每个目录里都有一个配置文件,配置文件中ip地址各不相同.

cluster-enabled yes表示开启集群
cluster-config-file nodes.conf//不需要手动写,redis自动生成,后续启动节点之后,会配置一些redis集群信息,写入到此文件中.
cluster-node-timeout 5000//多个节点保持联络的心跳包的超时时间
cluster-announce-ip 172.30.0.10${port}//该redis节点所在主机的ip,当前是使用docker容器模拟的主机,此处写的应该是docker容器的ip.
cluster-announce-port 6379//redis节点自身绑定的端口(容器内的端口),属于是业务端口.
cluster-announce-bus-port 16379//该redis节点的管理端口.

一个服务器,可以绑定多个端口号.

业务端口是用来完成业务数据通信的,响应redis客户端的请求.

管理端口:为了完成一些管理上的任务来进行通信的端口,如果某个分片的redis主节点挂了,就需要从节点成为主节点,此过程就需要管理端口来完成对应的操作.

完成上述操作之后,使用bash命令执行shell脚本.


docker-compose.yml的编写

version: '3.7'
networks:
  mynet:
    ipam:
      config:
        - subnet: 172.30.0.0/24
services:
  redis1:
    image: 'redis:5.0.9'
    container_name: redis1
    restart: always
    volumes:
      - ./redis1/:/etc/redis/
    ports:
      - 6371:6379
      - 16371:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.101
  redis2:
    image: 'redis:5.0.9'
    container_name: redis2
    restart: always
    volumes:
      - ./redis2/:/etc/redis/
    ports:
      - 6372:6379
      - 16372:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.102
  redis3:
    image: 'redis:5.0.9'
    container_name: redis3
    restart: always
    volumes:
      - ./redis3/:/etc/redis/
    ports:
      - 6373:6379
      - 16373:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.103
  redis4:
    image: 'redis:5.0.9'
    container_name: redis4
    restart: always
    volumes:
      - ./redis4/:/etc/redis/
    ports:
      - 6374:6379
      - 16374:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.104
  redis5:
    image: 'redis:5.0.9'
    container_name: redis5
    restart: always
    volumes:
      - ./redis5/:/etc/redis/
    ports:
      - 6375:6379
      - 16375:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.105
  redis6:
    image: 'redis:5.0.9'
    container_name: redis6
    restart: always
    volumes:
      - ./redis6/:/etc/redis/
    ports:
      - 6376:6379
      - 16376:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.106
  redis7:
    image: 'redis:5.0.9'
    container_name: redis7
    restart: always
    volumes:
      - ./redis7/:/etc/redis/
    ports:
      - 6377:6379
      - 16377:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.107
  redis8:
    image: 'redis:5.0.9'
    container_name: redis8
    restart: always
    volumes:
      - ./redis8/:/etc/redis/
    ports:
      - 6378:6379
      - 16378:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.108
  redis9:
    image: 'redis:5.0.9'
    container_name: redis9
    restart: always
    volumes:
      - ./redis9/:/etc/redis/
    ports:
      - 6379:6379
      - 16379:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.109
  redis10:
    image: 'redis:5.0.9'
    container_name: redis10
    restart: always
    volumes:
      - ./redis10/:/etc/redis/
    ports:
      - 6380:6379
      - 16380:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.110
  redis11
    image: 'redis:5.0.9'
    container_name: redis11
    restart: always
    volumes:
      - ./redis11:/etc/redis/
    ports:
      - 6381:6379
      - 16381:16379
    command:
      redis-server /etc/redis/redis.conf
    networks:
      mynet:
        ipv4_address: 172.30.0.111

此处为了后续创建静态ip,要先手动创建出网络,同时给这个网络也分配ip.

创建完配置文件之后,启动容器.


2.将上述redis节点.构建成集群

redis-cli --cluster create 172.30.0.101:6379 172.30.0.102:6379 172.30.0.103:6379 172.30.0.104:6379 172.30.0.105:6379 172.30.0.106:6379 172.30.0.107:6379 172.30.0.108:6379 172.30.0.109:6379 --cluster-replicas 2

--cluster create表示建立集群,后面填写每个节点的ip和端口.

--cluster-replicas 2表示每个主节点需要2个从节点备份.

redis在构建集群的时候,谁是主节点谁是从节点,哪些节点是一个分片不是固定的.

执行命令.

输入yes.

集群构造完毕.


3.使用客户端连接集群

从101-109九个节点,现在是一个整体,使用客户都安连上任意一个节点,都是在操作整个集群,本质上都是等价的.

使用cluster nodes命令查看当前集群的信息.

使用集群来存储数据.

设置成集群模式之后,当前数据就要分片存储了,k1这个key通过hash计算之后,得到slot为12706,属于103这个分片,所以就报错了.

我们可以在启动redis客户端的时候,加上-c选项,此时客户端如果发现当前的key的操作不在当前分片上,就能够自动的重定向到对应的分片主机.

请求转发给了103这个节点,进一步完成了数据存储的操作.

使用集群之后,之前学过的操作多个key的命令有时候就不能正常使用了,此时如果key分布在多个分片上,就有可能出现问题.


如果集群中,有节点挂了怎么办?

如果挂了的是从节点,没有多大影响.

如果挂了的是主节点,因为只有主节点才能处理写操作(如果在从节点上尝试写操作,此时就会自动的被重定向到指定的主节点上),此时集群做的工作就和哨兵做的类似了,集群会自动的把该主节点旗下的从节点,选拔一个出来,晋升为主节点.

我们先使用docker stop redis1命令停掉redis1.

在连上一个客户端查看集群信息.

可以看出,106成了新的主节点,并且105成了106的从节点.

然后我们在使用docker start redis1恢复redis1节点.

再次查看集群信息.

101成了从节点,从属于106.

通过上述过程,我们可以看出,集群机制具有故障转移的机制.


集群模式下的故障转移流程

1.故障判定

集群中的所有节点, 都会周期性的使⽤⼼跳包进⾏通信.
1. 节点 A 给 节点 B 发送 ping 包, B 就会给 A 返回⼀个 pong 包. ping 和 pong 除了 message type
属性之外, 其他部分都是⼀样的. 这⾥包含了集群的配置信息(该节点的id, 该节点从属于哪个分⽚,
是主节点还是从节点, 从属于谁, 持有哪些 slots 的位图...).
2. 每个节点, 每秒钟, 都会给⼀些随机的节点发起 ping 包, ⽽不是全发⼀遍. 这样设定是为了避免在节点很多的时候, ⼼跳包也⾮常多(⽐如有 9 个节点, 如果全发, 就是 9 * 8 有 72 组⼼跳了, ⽽且这是按照 N^2 这样的级别增⻓的).
3. 当节点 A 给节点 B 发起 ping 包, B 不能如期回应的时候, 此时 A 就会尝试重置和 B 的 tcp 连接, 看能否连接成功. 如果仍然连接失败, A 就会把 B 设为 PFAIL 状态(相当于主观下线).
4. A 判定 B 为 PFAIL 之后, 会通过 redis 内置的 Gossip 协议, 和其他节点进⾏沟通, 向其他节点确认 B 的状态. (每个节点都会维护⼀个⾃⼰的 "下线列表", 由于视⻆不同, 每个节点的下线列表也不⼀定相同).
5. 此时 A 发现其他很多节点, 也认为 B 为 PFAIL, 并且数⽬超过总集群个数的⼀半, 那么 A 就会把 B 标记成 FAIL (相当于客观下线), 并且把这个消息同步给其他节点(其他节点收到之后, 也会把 B 标记成FAIL).
⾄此, B 就彻底被判定为故障节点了.

2.故障迁移

上述例⼦中, B 故障, 并且 A 把 B FAIL 的消息告知集群中的其他节点.
如果 B 是从节点, 那么不需要进⾏故障迁移.
如果 B 是主节点, 那么就会由 B 的从节点 (⽐如 C 和 D) 触发故障迁移了.
所谓故障迁移, 就是指把从节点提拔成主节点, 继续给整个 redis 集群提供⽀持.
具体流程如下:
1. 从节点判定⾃⼰是否具有参选资格. 如果从节点和主节点已经太久没通信(此时认为从节点的数据和主节点差异太⼤了), 时间超过阈值, 就失去竞选资格.
2. 具有资格的节点, ⽐如 C 和 D, 就会先休眠⼀定时间. 休眠时间 = 500ms 基础时间 + [0, 500ms] 随机时间 + 排名 * 1000ms. offset 的值越⼤, 则排名越靠前(越⼩).
3. ⽐如 C 的休眠时间到了, C 就会给其他所有集群中的节点, 进⾏拉票操作. 但是只有主节点才有投票资格.
4. 主节点就会把⾃⼰的票投给 C (每个主节点只有 1 票). 当 C 收到的票数超过主节点数⽬的⼀半, C 就会晋升成主节点. (C ⾃⼰负责执⾏ slaveof no one, 并且让 D 执⾏ slaveof C).
5. 同时, C 还会把⾃⼰成为主节点的消息, 同步给其他集群的节点. ⼤家也都会更新⾃⼰保存的集群结构信息.
上述选举的过程, 称为 Raft 算法, 是⼀种在分布式系统中⼴泛使⽤的算法. 在随机休眠时间的加持下, 基本上就是谁先唤醒, 谁就能竞选成功.

注意和哨兵的区别,哨兵实现出leader,leader负责找一个从节点升级成主节点.而集群是直接投票选出新的主节点.


集群扩容

101-109九个主机,构成了3主6从结构的集群.

现在将110和111两个节点也加入到集群当中,以110为主节点,111为从节点,同时数据分片从3变为4.

1.新的主节点110加入到集群中

redis-cli --cluster add-node 172.30.0.110:6379 172.30.0.101:6379

add-node后的第一组地址是新节点的地址,第二组地址是集群中任意节点的地址,代表整个集群.

此时通过cluster nodes命令查看到110已经成为主节点了,但是还有槽位分配给它.

2.重新分配slots

把之前三组的master上面的槽位各自分出一些来,给到新的主节点.

redis-cli --cluster reshard 172.30.0.101:6379

reshard后的地址是集群中任意节点的地址,reshard代表重新切分的意思.

执行此命令之后,会进入交互式操作,redis会提示用户输入以下内容:

1).多少个slots要进行reshard?

此处我们填写4096.

2).哪个节点接收这些slots?

此处我们填写172.30.0.110这个节点的集群节点的id,上方会有打印,直接粘贴即可.

3).这些slots从哪些节点搬运过来?

此处我们填写all,意思是每个主节点都分一些槽位过来.

也可以手动指定,从某一个或者某几个节点来移动slots,输入以done结尾.

当输入all之后,给出的搬运计划还没有真正开始,当输入yes之后,搬运才真正开始.

此时不仅仅是slots的重新划分,也会把slots上对应的数据,也搬运到新的主机上,这是比较重量的操作!!!

注意,在搬运key的过程中,对于哪些不需要搬运的key,客户端进行访问的时候是没有问题的,但是对于需要搬运的key,进行访问可能会出现短暂的访问错误(因为key的位置发生了变化),随着搬运完成,这样的错误也就自然恢复了.

搬运完成后,就可以看到它的槽位信息了.

3.给新的主节点添加从节点

光有主节点了,此时扩容的⽬标已经初步达成.但是为了保证集群可⽤性,还需要给这个新的主节点添加,从节点,保证该主节点宕机之后,有从节点能够顶上.

redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave --cluster-master-id [172.30.0.110节点的nodeid]

从节点添加完毕!!!


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

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

相关文章

HarmonyOS架构及关键技术整理

技术解析&#xff1a;鸿蒙系统的底层优势 鸿蒙系统采用了先进的微内核设计&#xff0c;这是一种全新的系统架构&#xff0c;能够更好地适应现代智能设备的多样性和互联性。微内核通过最小化系统的核心功能&#xff0c;提高了系统的安全性和可定制性。此外&#xff0c;鸿蒙系统…

Day51力扣打卡

打卡记录 Plus and Multiply&#xff08;模拟&#xff09; 链接 要满足 a x b ∗ y n a^x b * y n axb∗yn 的关系&#xff0c;可以枚举满足 b ∗ y n − a x b * y n - a ^ x b∗yn−ax 的可余条件。 t int(input()) for _ in range(t):n, a, b map(int, input().…

Demystifying DeFi MEV Activities in Flashbots Bundle

目录 笔记后续的研究方向摘要引言贡献 Demystifying DeFi MEV Activities in Flashbots Bundle CCS 2023 笔记 本文介绍了对 Flashbots 捆绑包中的去中心化金融 &#xff08;DeFi&#xff09; 矿工可提取价值 &#xff08;MEV&#xff09; 活动的研究。作者开发了ActLifter&am…

无效的源发行版:18

启动项目出现报错&#xff1a;无效的源发行版&#xff1a;18 大概率是项目的jdk版本不一致造成的 我的项目是Gradle构建&#xff0c;主要检查这几方面 ①Gradle配置的JVM ②build.gradle文件的java版本 sourceCompatibility : 编译Java文件的jdk版本&#xff0c;涉及到具体…

【WPF.NET开发】创建简单WPF应用

本文内容 先决条件什么是 WPF&#xff1f;配置 IDE创建项目设计用户界面 (UI)调试并测试应用程序 通过本文你将熟悉在使用 Visual Studio 开发应用程序时可使用的许多工具、对话框和设计器。 你将创建“Hello, World”应用程序、设计 UI、添加代码并调试错误。在此期间&#…

车联网架构设计(二)_消息缓存

在上一篇博客车联网架构设计(一)_消息平台的搭建-CSDN博客中&#xff0c;我介绍了车联网平台需要实现的一些功能&#xff0c;并介绍了如何用EMQXHAPROXY来搭建一个MQTT消息平台。车联网平台的应用需要消费车辆发布的消息&#xff0c;同时也会下发消息给车辆&#xff0c;以实现车…

【Hive】——数据仓库

1.1 数仓概念 数据仓库&#xff08;data warehouse&#xff09;&#xff1a;是一个用于存储&#xff0c;分析&#xff0c;报告的数据系统 目的&#xff1a;是构建面向分析的集成化数据环境&#xff0c;分析结果为企业提供决策支持 特点&#xff1a; 数据仓库本身不产生任何数据…

robotFramwork 中如何禁用或跳过其中某个 testcase

在 Robot Framework 中&#xff0c;你可以通过添加一个特殊的标签&#xff08;tag&#xff09;来禁用某个测试用例。这个标签是 robot:skip。 robotframework *** Settings *** Test Setup Open Application*** Test Cases *** My Test Case[Tags] robot:skipDo Some…

Linux 环境下,jdbc连接mysql问题

1. 下载MySQL的JDBC驱动&#xff1a; 从MySQL官网下载最新的MySQL Connector/J&#xff0c;并将其解压到某个目录&#xff0c;比如/usr/local/mysql/。 2. 将JDBC驱动添加到类路径&#xff1a; 将JDBC驱动添加到类路径&#xff0c;可以使用以下命令&#xff1a; export CLA…

分布式系统硬件资源池原理和接入实践

一、硬件资源池理念产生背景 随着智能设备的发展和普及&#xff0c;越来越多的智能设备已经深入人们的生活&#xff0c;比如手机&#xff0c;PC&#xff0c;平板&#xff0c;各类穿戴设备等。当前单个设备的外设资源已经非常丰富&#xff0c;硬件能力也很强大。像我们的手机、…

什么是数字化工厂?

数字化工厂通常需要资金和技术支持&#xff0c;对大企业来说更容易投入建设。 中小企业难道就毫无机会了吗&#xff1f; 当然不是。中小企业也能够实现数字化工厂&#xff0c;只是可能需要采取不同的策略。虽然中小企业可能面临资源有限的挑战&#xff0c;但通过渐进式的方式和…

win10与 vm虚拟机win7共享文件夹创建

1:在win10&#xff08;主机&#xff09;电脑先随意共享一个文件夹 2&#xff1a;在win10&#xff08;主机&#xff09;上创建一个网络映射 右键此电脑选择映射网络驱动器 成功后会多出这个网络位置 3&#xff1a;win7虚拟机设置 在虚拟机中点击计算机右键添加一个网络位置

11月榜单丨飞瓜数据B站UP主排行榜(哔哩哔哩平台)发布!

飞瓜轻数发布2023年11月飞瓜数据UP主排行榜&#xff08;B站平台&#xff09;&#xff0c;通过充电数、涨粉数、成长指数、带货数据等维度来体现UP主账号成长的情况&#xff0c;为用户提供B站号综合价值的数据参考&#xff0c;根据UP主成长情况用户能够快速找到运营能力强的B站U…

《WebGIS快速开发教程》第5版“惊喜”更新啦

我的书籍《WebGIS快速开发教程》第5版&#xff0c;经过忙碌的编写&#xff0c;终于发布啦&#xff01; 先给大家看看新书的封面&#xff1a; 这次的封面我们经过了全新的设计&#xff0c;不同于以往的任何一个版本。从封面就可以看出第5版肯定有不小的更新。 那么我们话不多说…

【musl-pwn】msul-pwn 刷题记录 -- musl libc 1.2.2

前言 本文不分析 musl libc 相关源码&#xff0c;仅仅为刷题记录&#xff0c;请读者自行学习相关知识&#xff08;看看源码就行了&#xff0c;代码量也不大&#xff09; starCTF2022_babynote 保护&#xff1a;保护全开 程序与漏洞分析&#xff1a; 程序实现了一个菜单堆&…

SL4010升压恒压控制器芯片 2.5V启动 最大10A电流 支持300W大功率

SL4010是一款升压恒压控制器芯片&#xff0c;它具有2.5V启动、最大10A电流、支持300W大功率等特点。该芯片采用先进的控制技术&#xff0c;能够实现高效的电能转换&#xff0c;同时保持稳定的输出电压和电流。 SL4010芯片的主要功能是将输入的直流电压升高到所需的电压&#xf…

mysql中year函数有什么用

YEAR()函数用于提取日期或日期时间值中的年份。可以用于提取DATE、DATETIME或TIMESTAMP列中的年份。 SELECT YEAR(date_column) FROM table;# 提取字符串中的数据SELECT YEAR(2023-07-19) FROM table_name;

Spring-Boot---配置文件

文章目录 配置文件的作用配置文件的格式PropertiesProperties基本语法读取Properties配置文件 ymlyml基本语法读取yml配置文件 Properties VS Yml 配置文件的作用 整个项目中所有重要的数据都是在配置文件中配置的&#xff0c;具有非常重要的作用。比如&#xff1a; 数据库的…

[UIM]论文解读:subword Regularization: Multiple Subword Candidates

文章目录 一、完整代码二、论文解读2.1 介绍2.2 NMT2.3 Unigram language model2.4 subword 抽样2.5 效果 三、整体总结 论文&#xff1a;Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates 作者&#xff1a;Taku Kudo 时…