redis的简单与集群搭建
wptr33 2024-12-31 15:02 19 浏览
Redis是什么?
是开源免费用c语言编写的单线程高性能的(key-value形式)内存数据库,基于内存运行并支持持久化的nosql数据库
作用
主要用来做缓存,单不仅仅是做缓存,比如:redis的计数器生成分布式的唯一主键,redis实现分布式锁、队列、会话缓存等等。
redis数据类型及api操作(http://redisdoc.com/)
key
keys * 查询所有的键
scan 0 match * count 1 查询所有键从下角标0开始,返回1个键数据的结果集
exists key 判断某个key是否存在
move key db 当前库就没有了,到指定的库中去了
expire key 为给定的key设置过期时间
ttl key 查看还有多少时间过期 -1表示永不过期 -2表示已过期
type key 查看key是什么类型
1.string
一般使用场景:单值缓存、对象缓存、分布式锁、计数器、websession共享、分布式全局序列号
string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M
set key value 设置key value
get key 查看当前key的值
del key 删除key
append key value 如果key存在,则在指定的key末尾添加,如果key存在则类似set
strlen key 返回此key的长度
以下几个命令只有在key值为数字的时候才能正常操作
incr key 为执定key的值加一
decr key 为指定key的值减一
incrby key 数值 为指定key的值增加数值
decrby key 数值 为指定key的值减数值
getrange key 0(开始位置) -1(结束位置) 获取指定区间范围内的值,类似between......and的关系 (0 -1)表示全部
setrange key 1(开始位置,从哪里开始设置) 具体值 设置(替换)指定区间范围内的值
setex 键 秒值 真实值 设置带过期时间的key,动态设置。
setnx key value 只有在 key 不存在时设置 key 的值。
mset key1 value key2 value 同时设置一个或多个 key-value 对。
mget key1 key 2 获取所有(一个或多个)给定 key 的值。
msetnx key1 value key2 value 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
getset key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
2.list
一般使用场景:公众号消息流、
它是一个字符串链表,left、right都可以插入添加;如果键不存在,创建新的链表;如果键已存在,新增内容;如果值全移除,对应的键也就消失了。链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。它的底层实际是个链表
lpush key value1 value2 将一个或多个值加入到列表头部
rpush key value1 value2 将一个或多个值加入到列表底部
lrange key start end 获取列表指定范围的元素 (0 -1)表示全部
lpop key 移出并获取列表第一个元素
rpop key 移出并获取列表最后一个元素
lindex key index 通过索引获取列表中的元素
llen 获取列表长度
lrem key 0(数量) 值,表示删除全部给定的值。零个就是全部值 从left往right删除指定数量个值等于指定值的元素,返回的值为实际删除的数量
ltrim key start(从哪里开始截) end(结束位置) 截取指定索引区间的元素,格式是ltrim list的key 起始索引 结束索引
3.set
一般使用场景:微信抽奖程序、微信点赞、收藏标签、关注模型
Redis的Set是string类型的无序,不能重复的集合。
sadd key value1 value 2 向集合中添加一个或多个成员
smembers key 返回集合中所有成员
sismembers key member 判断member元素是否是集合key的成员
scard key 获取集合里面的元素个数
srem key value 删除集合中指定元素
srandmember key 数值 从set集合里面随机取出指定数值个元素 如果超过最大数量就全部取出,
spop key 随机移出并返回集合中某个元素
smove key1 key2 value(key1中某个值) 作用是将key1中执定的值移除 加入到key2集合中
sdiff key1 key2 在第一个set里面而不在后面任何一个set里面的项(差集)
sinter key1 key2 在第一个set和第二个set中都有的 (交集)
sunion key1 key2 两个集合所有元素(并集)
4.hash
一般使用场景:对象缓存、电商购物车、
Redis hash 是一个键值对集合。Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
kv模式不变,但v是一个键值对
类似Java里面的Map
优点:同类数据归类整合存储,方便数据管理;相对于string消耗的内存与cpu更小;相比于string更省存储空间
缺点:过期功能不能用于field(里层)只能作用于key(外层)上;redis集群架构下不适合大规模使用
hset key (key value) 向hash表中添加一个元素
hget key key 向hash表中获取一个元素
hmset key key1 value1 key2 value2 key3 value3 向集合中添加一个或多个元素
hmget key key1 key2 key3 向集合中获取一个或多个元素
hgetall key 获取在hash列表中指定key的所有字段和值
hdel key key1 key2 删除一个或多个hash字段
hlen key 获取hash表中字段数量
hexits key key 查看hash表中,指定key(字段)是否存在
hkeys key 获取指定hash表中所有key(字段)
hvals key 获取指定hash表中所有value(值)
hincrdy key key1 数量(整数) 执定hash表中某个字段加 数量 ,和incr一个意思
hincrdyfloat key key1 数量(浮点数,小数) 执定hash表中某个字段加 数量 ,和incr一个意思
hsetnx key key1 value1 与hset作用一样,区别是不存在赋值,存在了无效。
5.zset
一般使用场景:排行榜
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
zadd key score 值 score 值 向集合中添加一个或多个成员
zrange key 0 -1 表示所有 返回指定集合中所有value
zrange key 0 -1 withscores 返回指定集合中所有value和score
zrangebyscore key 开始score 结束score 返回指定score间的值
zrem key score某个对应值(value),可以是多个值 删除元素
zcard key 获取集合中元素个数
zcount key 开始score 结束score 获取分数区间内元素个数
zrank key vlaue 获取value在zset中的下标位置(根据score排序)
zscore key value 按照值获得对应的分数
conf文件的注意点
1.bind可以使用0.0.0.0或者机器的IP地址
2.protected-mode在使用bind或者设置了密码时是无效的
3.protected-mode默认是开启的,不能被外网访问的
4.当redis以守护进程方式运行时,redis默认会把pid写入redis.pid文件,可以使用pidfile指定(只适用于linux版本,window不适用)
5.logfile指定日志文件的位置
6.databases是数据库的数目,默认是16
redis的单线程和多性能
redis是单线程吗:redis的单线程只要是指redis的网络io和键值对读写是由一个线程来完成的,这也是redis对外提供键值存储服务的主要流程。但是redis的其他功能,如持久化、异步删除、数据同步等都是由额外的线程执行的。
redis单线程为什么还能这么快:因为它所有的数据都在内存中,所有的运算都是内存级别的运算,并且单线程避免了线程切换的损耗问题。正是由于redis是单线程所以要小心使用redis指令,对于那些耗时的指令(如keys),需要谨慎使用,不小心会导致redis卡顿
redis单线程如何处理多个并发客户端的连接:redis的io多路复用,redis利用epoll来实现io多路复用,将连接信息和事件放到队列总,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
持久化RDB(丢失数据较多)
- 原理:redis会单独创建(fork)一个与当前线程一样的子线程来进行持久化,这子线程的所有数据都和原线程一模一样,会先将数据希尔到一个临时的文件中,等到持久化结束了再用这个临时文件替换上次持久化好的文件,整个过程中,主线程不进行任何的io操作,这就确保了极高的性能。
- 持久化文件存放的位置:由dbfilename指定
- 什么时候fork子进程,或者什么时候触发rdb持久化机制:
shutdow时,没有开启aof会触发
配置文件中默认的快照配置
执行命令save或者bgsave,save是主线程操作会阻塞,save只管保存,其余不管,bgsave是新fork线程后台操作
执行flushall命令(清空所有数据),无意义
- 怎么开启rdb持久化
配置文件中配置save命令:save 900 1;表示900秒由一条数据更新会触发rdb的持久化机制
可以写多个save条件
当配置主从复制集群时,是关闭不了rdb的
持久化aof(丢失数据较少)
- 原理:将redis的操作日志以追加的方式写入文件,读操作是不记录的
- 怎么开启aof:配置文件中appendonly 设置为yes;命令行:config set appendonly yes,然后还是需要修改配置文件
- 持久化文件在哪里:配置文件中 appendfilename指定
- 触发机制(配置文件中appendfsync指定):no表示等操作系统进行数据缓存同步到磁盘(快,没保障);always同步持久化,每次发送数据变更时,立即记录到磁盘(慢,安全);everysec表示美妙同步一次(默认值,快,可能会丢失一秒以内的数据)
- aof文件中日志解析:*2代表是2组命令;$6,$3代表是这个命令有6个,3个字符组成(命令长度);
- aof的重写机制:当aof增长到一定大小时候redis可以调用bgreweiteaof命令对日志文件进行重写;当文件大小的增长率大于配置项时自动开启重写,是配置文件中auto-aof-rewrite-percentage指定表示超过源文件大小的百分之多少,案例:50;当文件大小大于配置项的大小时自动开启重写,是配置文件中的auto-aof-rewrite-min-size指定,案例:64mb
rdb与aof是可以同时开启时,默认使用aof持久化,所以切换rdb与aof时,最好通过命令行的方式启动aof,这样不会有数据丢失
混合持久化
- 4.0版本的混合持久化是默认关闭的,可以通过配置文件中的aof-use-rdb-preamble来控制,yes代表开启,no表示禁用,5.0之后默认开启
- 混合持久化是通过bgrewriteaof完成的,当开启混合持久化时,fork的子线程先将共享的内存副本全量的以rdb的方式写入aof文件,然后再将重写缓存区的增量命令以aof 的方式写入文件,完成后通知主线程更新统计信息,将新的aof文件替换旧的aof文件
- 优点:加载速度快,同时结合aof 的方式保存,数据丢失少
- 缺点:兼容性差,一旦开启混合持久化,在4.0版本之前都不识别该aof文件,阅读性差。
缓存穿透问题
缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,并且出于容错考虑, 如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
可能造成原因:
1.业务代码自身问题2.恶意攻击。爬虫等等
危害
对底层数据源压力过大,有些底层数据源不具备高并发性。 例如mysql一般来说单台能够扛1000-QPS就已经很不错了
解决方案
1.缓存空对象
缺点:可能会缓存大量空对象,当缓存过期时仍然会有缓存穿透的问题
public class NullValueResultDO implements Serializable{
private static final long serialVersionUID = -6550539547145486005L;
}
public class UserManager {
UserDAO userDAO;
LocalCache localCache;
public UserDO getUser(String userNick) {
Object object = localCache.get(userNick);
if(object != null) {
if(object instanceof NullValueResultDO) {
return null;
}
return (UserDO)object;
} else {
User user = userDAO.getUser(userNick);
if(user != null) {
localCache.put(userNick,user);
} else {
localCache.put(userNick, new NullValueResultDO());
}
return user;
}
}
}
2.布隆过滤器
缺点:添加数据时,布隆过滤器也需要添加数据,但是不能删除数据,所以要定时维护更新布隆过滤里面的数据
1)、Google布隆过滤器的缺点
基于JVM内存的一种布隆过滤器重启即失效本地内存无法用在分布式场景不支持大数据量存储
2)、Redis布隆过滤器
可扩展性Bloom过滤器:一旦Bloom过滤器达到容量,就会在其上创建一个新的过滤器不存在重启即失效或者定时任务维护的成本:基于Google实现的布隆过滤器需要启动之后初始化布隆过滤器缺点:需要网络IO,性能比Google布隆过滤器低
缓存击穿.热点key重建缓存问题
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
我们知道,使用缓存,如果获取不到,才会去数据库里获取。但是如果是热点 key,访问量非常的大,数据库在重建缓存的时候,会出现很多线程同时重建的情况。因为高并发导致的大量热点的 key 在重建还没完成的时候,不断被重建缓存的过程,由于大量线程都去做重建缓存工作,导致服务器拖慢的情况。
解决方案
1.互斥锁
第一次获取缓存的时候,加一个锁,然后查询数据库,接着是重建缓存。这个时候,另外一个请求又过来获取缓存,发现有个锁,这个时候就去等待,之后都是一次等待的过程,直到重建完成以后,锁解除后再次获取缓存命中。
public String getKey(String key){
String value = redis.get(key);
if(value == null){
String mutexKey = "mutex:key:"+key; //设置互斥锁的key
if(redis.set(mutexKey,"1","ex 180","nx")){ //给这个key上一把锁,ex表示只有一个线程能执行,过期时间为180秒
value = db.get(key);
redis.set(key,value);
redis.delete(mutexKety);
}else{
// 其他的线程休息100毫秒后重试
Thread.sleep(100);
getKey(key);
}
}
return value;
}
互斥锁的优点是思路非常简单,具有一致性,但是互斥锁也有一定的问题,就是大量线程在等待的问题。存在死锁的可能性。
缓存雪崩问题
缓存雪崩是指机器宕机或在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
4:如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
redis的集群
- 主从架构
原理:若为master配置了一个slave,不管这个slave是否是第一次连接上master,它都会发送psync命令给master请求复制数据。master收到命令后,会在后台进行数据持久化通过bgsave上次最新的rdb快照文件,持久化时,master会继续接受客户端请求,他会把可能修改的数据及的请求缓存在内存中。当持久化进行完毕之后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后在加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。
当master与slave之间的连接由于某些原因断开时,slave能过自动连接master,如果master收到了多个salve并发连接请求,他只会进行一次持久化,而不是一个连接一次,然后再问吧这份持久化数据发送给多个并发连接的slave。
解决主从风暴的问题方法:配置阶梯式主从架构
配置从节点实例,复制conf文件:
port 6389
pidfile /var/run/redis_6380.pid #把pid进程号写入pidfile配置文件
logfile "6380.log"
dir /usr/local/redis-5.0.3/data/6389 #指定数据存放目录
#配置主从复制
replicaof 127.0.0.1:6379 #从本机的6379redis实例复制数据,redis5.0之前用slaveof
replica-read-only yes #配置从节点只读
#启动从节点
redis-server redis.conf
#连接从节点
redis-cli -p 6380
#测试在6379实例上写数据,6380实例是否能及时同步数据
同步骤配置下一个从节点
- 哨兵模式的主从架构
原理:哨兵是特殊的redis服务,不提供读写服务,主要来健康redis实例的节点,哨兵架构下的client端第一次从哨兵中找出redis的主节点,后续就直接访问redis的主节点,不是每次都通过哨兵进行代理访问redis的主节点,当redis的主节点变化时,哨兵会第一时间感知到,并且将新的redis节点通知给client端(这里的redis的client端一般都实现了订阅功能,订阅哨兵发布的节点变动消息)
配置搭建:
复制一份sentinel.conf文件
修改相关配置:
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "26379.log"
dir "/usr/local/redis-5.0.3/data"
#sentinel monitor <master-name> <ip> <redis-port> <quorum>
#quorum是数字,指明当前有多少sentinel认为master失效时(一般是 总数/2+1),master才算真正失效
sentinel monitor mymaster 192.168.0.60:6379 2
注释掉bind IP那行代码
protected-mode no 关闭自我保护模式,开启只有本机可以访问
启动哨兵实例
redis-sentinel redis-sentinel-26379.conf
查看sentinel的info信息
redis-cli -p 26379
info
可以看到sentinel的info里已经识别出redis的主从
同步骤可以配置其余的哨兵
- redis高可用集群cluster(最少需要三个主节点)
原理:redis cluster将所有数据划分为16384个槽位,每个节点负责其中一部分槽位,槽位的信息存储在每个节点中。
当集群的客户端连接集群时,他会得到一份集群的槽位配置信息并将其缓存在客户端本地,这样当客户端要查找某个key时,可以直接定位目标节点,同时因为信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
跳转重定向:当客户端向一个错误的节点发出指令,该节点会发现指令大key所在的槽位并不归自己管理,这时她会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连接这个节点获取数据,客户端收到指令后处理跳转到正确的节点上操作,还会同步更新纠正本地的槽位映射表缓存,后续所有可以将使用新的槽位表映射表。
配置:
daemonize yes 后台启动
protect-mode no 关闭保护模式,开启的话本机才可以访问redis
注释掉bind
redis集群至少需要三个master节点,我们搭建三个master并且给每个master搭建一个slave节点
在第一台机器的/usr/local下创建文件夹redis-cluster,然后在其下面分别创建2个文件夹
mkdir -p /usr/local/redis-cluster
mkdir 8001 8004
把之前的redis.conf配置文件copy到8001下,修改内容:
daemonize yes
port 8001
dir /usr/local/redis-cluster/8001/ (指定数据文件位置,必须要指定不同的目录位置,不然会丢失数据)
cluster-enabled yes 启动集群模式
cluster-config-file nodes-8001.conf集群节点信息文件
cluster-node-timeout 5000
protected-mode no
appendonly yes
如果要设置密码需要如下配置
requirepass mima redis的访问密码
masterauth mima 集群的访问密码,与上面一致
上述步骤配置其他节点,端口号不同
#启动从节点
redis-server redis.conf
构建集群命令(最后的数字是一个主节点对应多少个从节点的意思),只需构建一次,服务器重启不需要重启构建
redis5.0使用/usr/local/bin/redis-cli --cluster create 192.168.0.104:7000 192.168.0.104:7001 192.168.0.104:7002 192.168.0.104:7003 192.168.0.104:7004 192.168.0.104:7005 --cluster-replicas 1
相关推荐
- Linux高性能服务器设计
-
C10K和C10M计算机领域的很多技术都是需求推动的,上世纪90年代,由于互联网的飞速发展,网络服务器无法支撑快速增长的用户规模。1999年,DanKegel提出了著名的C10问题:一台服务器上同时...
- 独立游戏开发者常犯的十大错误
-
...
- 学C了一头雾水该咋办?
-
学C了一头雾水该怎么办?最简单的方法就是你再学一遍呗。俗话说熟能生巧,铁杵也能磨成针。但是一味的为学而学,这个好像没什么卵用。为什么学了还是一头雾水,重点就在这,找出为什么会这个样子?1、概念理解不深...
- C++基础语法梳理:inline 内联函数!虚函数可以是内联函数吗?
-
上节我们分析了C++基础语法的const,static以及this指针,那么这节内容我们来看一下inline内联函数吧!inline内联函数...
- C语言实战小游戏:井字棋(三子棋)大战!文内含有源码
-
井字棋是黑白棋的一种。井字棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙、三子旗等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。但是,有很多时...
- C++语言到底是不是C语言的超集之一
-
C与C++两个关系亲密的编程语言,它们本质上是两中语言,只是C++语言设计时要求尽可能的兼容C语言特性,因此C语言中99%以上的功能都可以使用C++完成。本文探讨那些存在于C语言中的特性,但是在C++...
- 在C++中,如何避免出现Bug?
-
C++中的主要问题之一是存在大量行为未定义或对程序员来说意外的构造。我们在使用静态分析器检查各种项目时经常会遇到这些问题。但正如我们所知,最佳做法是在编译阶段尽早检测错误。让我们来看看现代C++中的一...
- ESL-通过事件控制FreeSWITCH
-
通过事件提供的最底层控制机制,允许我们有效地利用工具箱,适时选择使用其中的单个工具。FreeSWITCH是一个核心交换与混合矩阵,它周围有几十个模块提供各种功能特性。我们完全控制了所有的即时信息,这些...
- 物理老师教你学C++语言(中篇)
-
一、条件语句与实验判断...
- C语言入门指南
-
当然!以下是关于C语言入门编程的基础介绍和入门建议,希望能帮你顺利起步:C语言入门指南...
- C++选择结构,让程序自动进行决策
-
什么是选择结构?正常的程序都是从上至下顺序执行,这就是顺序结构...
- C++特性使用建议
-
1.引用参数使用引用替代指针且所有不变的引用参数必须加上const。在C语言中,如果函数需要修改变量的值,参数必须为指针,如...
- C++程序员学习Zig指南(中篇)
-
1.复合数据类型结构体与方法的对比C++类:...
- 研一自学C++啃得动吗?
-
研一自学C++啃得动吗?在开始前我有一些资料,是我根据网友给的问题精心整理了一份「C++的资料从专业入门到高级教程」,点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!个人...
- C++关键字介绍
-
下表列出了C++中的常用关键字,这些关键字不能作为变量名或其他标识符名称。1、autoC++11的auto用于表示变量的自动类型推断。即在声明变量的时候,根据变量初始值的类型自动为此变量选择匹配的...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mysql max (33)
- vba instr (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)