NoSQL,(Not Only SQL),泛指,非关系型数据库。不依赖业务逻辑的存储方式,是以 key-value 的形式存储数据的,大大增加了数据库的扩展能力!他的排名也算是比较靠前的(数据库排名);
Redis 官网
先查看 linux 中是否存在 gcc,是否需要更新;
# 查看命令 gcc --version
安装命令(通过yum来进行安装);
yum install gcc
运行解压命令;
tar -zxvf redis-6.2.6.tar.gz
解压好之后进入其目录进行编译操作;
cd redis-6.2.6 make
make install
安装完成后来到 /use/local/bin
,进入到主目录查看;
cd /usr/local/bin
redis-servere
后台启动需要一点点配置,首先来到刚解压安装编译的位置(我自己的路劲,根据需求修改即可);
cd /home/zyd/redis/redis-6.2.6
该路径下会有一个 redis.conf
的文件,将其赋值一份到其他路劲下(我这里就暂且拷贝当这个位置 /etc
路径下);
cp /home/zyd/redis/redis-6.2.6/redis.conf /etc/redis.conf
拷贝完成后,修改redis.conf
中的选项,将 daemonize
改为 yes 即可;
回到解压的根位置(带文件)启动
redis-server /etc/redis.conf
Redis 出现了一些问题,启动后无法正常的实现存到磁盘任务的问题,解决方案如下:
将配置项 stop-writes-on-bgsave-error
设置为 no
,可以在 Redis 命令行里配置,也可以在 redis.conf
配置文件里改;
启动就到这里就可以跟安装启动部分告一段落 了!
查看当前库所有 key;
keys *
判断某个 key 是否存在;
exists key
查看你的 key 是什么类型;
type key
删除指定的 key 数据;
del key
根据 value 选择非阻塞删除(仅将 keys从 keyspace 元数据中删除,真正的删除会在后续异步操作);
unlink key
10 秒钟:为给定的 key 设置过期时间;
expire key 10
查看还有多少秒过期,-1 表示永不过期,-2 表示已过期;
ttl key
命令切换数据库 例如 select 0
代表切换到0库;
select
查看当前数据库的key的数量;
dbsize
清空当前库;
flushdb
通杀全部库;
flushall
String 是 Redis 的最基本数据类型,就是一个典型的 key-value 对儿,String 是二进制安全的,这就意味着你可以存放任何数据到 Redis 的 string 中,包括序列化的对象,以及图片等等。一个字符串的最大占用内存是 512M
添加一个键值对;
set key value
查询对应的键的值;
get key
追加对应 key 的值;
append key value...
获得值得长度;
strlen key
只有在 key 不在的时候设置值;
setnx key values
将key中存放的数字进行自增1操作,如果为空,新增值为 -1;
incr key decr key
自增自减可以自定义步长;
incrby key 2 decrby key 1
设置/获取一对或者多个键值对的值;
mset k2 value2 k3 value3 k4 value4 k5 3 mget k1 k2 k3 k4 k5
只有在 key 不在的时候设置值(基于他的原子性,有一个失败,就都失败);
msetnx k2 value2 k3 value3 k4 value4 k5 3
根据给定的范围获取值(值得范围是左闭右闭),类似于 Java 中的 substring( ) 方法;
getrange k2 0 -1 获取全部 getrange k2 0 3 0-3一共四个字符
根据给定的位置开始覆盖写值;
get k2 -> value2 setrange k2 0 test get k2 -> teste2
设置键值的同时,设置过期时间(单位为秒);
setex key 5 value
设置新值的同时获得旧值;
getset key value
Redis 的 list 是单键多值,即一个键可以对应一个或者多个值。Redis 是一个简单的字符串列表,按照插入顺序排序。他的底层是一个双向链表,所以你可以在链表的头部和尾部插入元素,下面有简单示例;
从左边或者右边插入一个或多个值(并非键值对);
lpush/rpush k1 v1 v2 v3
从左边或者右边弹出一个值;特点:值在,键在,值无,键亡;
lpop/rpop key
从k1右边弹出一个值插入到左边的k2中(右弹左压);
rpoplpush k1(右弹) k2(左压)
按照索引下标获得元素;
lrange key start end lrange k5 0 3 共四位 lrange k5 0 -1 代表全部
按照索引下标从左向右开始查找对应下标的值;
lindex k5 0
在指定值得前面插入一个新值;
linsert key before lilei newvalue
从左边开始删除 n 个 value;
lrem key n vakue
将列表 key 下标为 index 的值换为 value;
lset key index value
List 的数据结构quickList
;首先,在元素比较少的时候,会使用一块连续的内存存储,这个结构是 ziplist
,称之为压缩列表。他讲所有的元素紧紧挨着一起存储,分配的是一块连续对的存储。当数据量比较多的时候,才会改成 quicklist
;
因为普通的链表需要附加指针的空间很大,会比较浪费空间。Redis 将链表个 ziplist
结合起来组成了 quicklist
,也就是使用双向指针将 ziplist
穿起来使用,解决了快速插入删除的性能问题,又不会出现太大的数据冗余!
Redis Set对外提供的功能是与 list 是类似的,特殊之处在于 Set 是可以自动排重的,也就是说,当你需要一个列表数据,又不希望出现重复数据,就可以选择使用 Set,Set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能够提供的
Redis 的 Set 是 string 类型的无序集合,它底层其实是一个 value 为 null 的 hash 表,所以添加、查找、删除的复杂度都是 o(1);
添加一个或多个值到 Set 集合中;
sadd key value1 value2...
返回该键的所有值(并不是删除);
smembers key
返回该集合的元素个数;
scard key
删除结合中的一个或多个元素;
srem key value1 value2...
从该集合中随机 弹出一个值;
spop key
从该集合中随意取出 n 个值,但是不会从集合中删除;
srandmember key n
把集合中的一个值从 source 集合移动到另一个集合destination;
smove source destination value
返回两个或多个集合的交集;
sinter key1 key2
返回两个集合或多个集合的并集;
sunion key1 key2
返回两个集合的差集元素(key1 有但是 key2 没有,也就是 key1 - key2);
sdiff key1 key2
Set 数据结构对应的是 dict 字典,字典是用哈希表实现的;而 Java 中的 HashSet 的内部实现使用的是 HashMap,只不过所有的 value 都只想同一个对象,Redis 的 Set 结构也是一样的,它的内部也使用 hash 结构,所有的 value 都指向同一个内部值;
Redis Hash 是一个键值对集合,他是一个 string 类型的 field 和 value 的映射表。类似于 Java 里的 Map<String,Object>
,所以 Hash 特别适合用于存储对象;如下图所示
如上图,key (用户 ID) + field (属性标签) 就可以操作对应的属性的数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题;
给 hash 集合中添加值(经过测试,是可以同时添加很多个属性的);
hset user1: id 1
hset user1: id 1 name zhangsan age 18
从集合中取出来值 value (记得加上冒号);
hget user1: name
批量设置值;
hmset key: field value field value...
hmset user2: id 2 name lisa age 26
查看 Hash 表 key 中,给定的域是否是存在的;
hexists key: field
查看该 key 的所有的 field (记得加上冒号) ;
hkeys key:
查看该结合的所有的 filed 的值;
hvals key:
为哈希表 key 中的域 field 的值增加量(1,0,-1);
hincrby user1: age 2
为 key 中的域 field 的值设置为 value (当且仅当域 field 不存在时才可以设置成功);
hsetnx user1: firstName lisi
Hash 类型对应的数据结构是两种:ziplist (压缩列表),hashtable (哈希表) 。当 field-value 长度较短且个数较少,使用 ziplist,否则使用 hashtable,我认为和之前的那个 List 的是类似的;
他是有序集合 Zset(sorted set),与普通的 Set 集合非常的相似,他是一个没有重复元素的字符串集合,区别在于他多了一个 “评分” ,这个用来实现了排序的功能,集合里的元素是唯一的,但是评分是可以重复的;
注意:为了方便理解和做笔记,这里将引用 set 集合中的 field 和 value 的概念,即 score = value
将一个或多个 member 元素及其 score 值加入到有序集 key 中;
zadd key score1 field score2 field...
返回有序集合中,下标在 start 和 stop 之间的元素;
zrange key start stop # WITHSCORES 携带评分一起返回 zrange key start stop withscores
返回有序集合中,评分在 min 和 max 之间的结果(从小到大就是 min,max);
zrangebyscore key min max zrangebyscore key min max withscores
从大到小就是 max,min;
zrangebyscore key max min zrangebyscore key max min withscores
给指定的集合中的元素加上一个指定的数(这里只得是加上评分);
zincrby key value field
删除指定的元素,不是根据评分删除,是根据值;
zrem key field
统计该集合中,评分区间内的元素的个数
zcount key 0 500
根据已存在的 field(不是评分),来返回对应的排位(从 0 开始)
zrank key field
SortedSet (zset) 是 Redis 提供的一个非常特别的数据结构,在结构方面和 Set 非常的相似,即 Map<String,Double>
,可以给每一个元素 value (field) 赋予一个权重(score);另外,他在内部又类似于 TreeSet ,内部元素会根据权重(score)进行排序,可以得到每个元素的名次,也可以通过 score 的范围来获取元素的列表;
zset 的底层还用了两个数据结构,hash 和 跳跃表;
hash ,hash 的作用就是关联元素的 value (field) 和权重 score,保障内部元素 value (field) 的唯一性,可以通过元素 value (score) 来找到对应的 score 的值;
跳跃表,跳跃表的目的在于给元素 value (field) 排序,根据 score 的范围来获取元素列表;什么是跳跃表?
首先到我们自己定义的配置文件的位置去,我这里是 /etc/redis.conf
第一个部分是 Units 单位,我们在 Redis 中是只支持 byte(字节) ,而不支持 bit(位),大小写不敏感;
默认情况下,他为 127.0.0.1 ,即只允许本地进行访问,在不写的情况下,允许任何地址进行访问,我这里就注释掉了;
在下面一点的位置,有一个 protected-mode ,这里我们将他设置为 no 将他关掉;这边是有点说道的,假如你开启了保护模式,且没有设置 bind ip 和 密码,那么 Redis 是只允许本机进行访问的;
以及我们可以看到他的端口设置 6379 ,那么这个就不在这里赘述啦;
通过设置 tcp 的 backlog,backlog 其实是一个连接队列,连接总和包括未完成三次握手队列和已经完成了三次握手的队列;在高并发环境下,可以设置一个高的 backlog 值来避免客户端连接问题;
linux 内核会将这个值减小到 /proc/sys/net/core/somaxconn 的值(128),所以需要确认增大 /proc/sys/net/core/somaxconn 和 /proc/net.ipv4/tcp_max_syn_backlog(128)两个值,来达到想要的结果;
超时,一个客户端维持多少秒会关闭,0 表示永远不关闭;
是否启用后台进程,通常设置为 yes,守护进程,后台启动;
存放 pid 文件的位置,每个实例都会产生一个不同的 pid 文件;
指定日志级别,Redis 总共分为四个级别,分别为:debug、verbose、notice、warning,默认为 notice;
日志文件名称
设置默认的库的数量为 16,默认的数据库为 0,可以使用 select 命令来指定连接某个库;
在命令行里面设置的密码是临时的,在 Redis 服务器重启之后,密码就还原了。需要永久的设置密码还得是在配置文件里面设置;
设置 Redis 同时可以连接多少个客户端进行连接,默认情况下为 10000 个客户端;如果达到了该限制, Redis 则会拒接新的连接请求,并反馈为 “max number of clients reached”;
建议设置,否则内存满的时候,服务器会直接宕机;
设置 Redis 的可使用内存量之后,当到达内存的使用上限的时候,Redis 将视图移除内部的数据,移除的规则可以通过 maxmemory-policy 来指定;
volatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用);
allkeys-lru:在所有集合key中,使用LRU算法移除key;
volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键;
allkeys-random:在所有集合key中,移除随机的key;
volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key;
noeviction:不进行移除。针对写操作,只是返回错误信息;
也可以设置不允许移除,那么 Redis 就会返回一些错误信息,比如 SET、LPUSH 等;
但是对于无内存申请的指令,仍然会正常响应,比如 GET 等。当然,如果你有从 Redis 的情况下,那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是不移除的情况下,就可以不考虑这个因素;
设置样本数量
订阅和发布是 Rerdis 的一种消息通信模式;Redis 客户端可以订阅任意数量的频道;
客户端进行订阅;
当在频道中发布消息的时候,消息就会发给订阅的客户端;
进入客户端之后,先进行订阅,等待消息发布即可;
subscribe channel
进入另一台客户端之后,开始发布,那么订阅了该频道的客户端就会收到该发布的消息;
publish channel helloworld
发布端,返回的数字即为订阅的客户端的数量;
Redis 提供了 Bitmaps 这个 “数据类型” 可以实现对位的操作;Bitmaps 并不是一个数据类型,实际上他只是一个字符串(key-value),但是他可以对字符串的位进行操作;Bitmaps 单独提供了一套命令,所以在 Redis 中使用 Bitmaps 和使用字符串的方法不太相同,可以把 Bitmaps 想象成一个以位为单位的数组,数组的每个数组只能存储 0 和 1,数组的下标在 Bitmaps 中叫做偏移量;
给 key 中指定偏移量的位置上赋值 0 或 1,在第一次初始化 Bitmaps 时,加入偏移量比较大时,那么整个初始化过程会比较慢,而且可能会造成 Redis 阻塞;
setbit key offset value
获取 key 中的某个偏移量的值
getbit key offset
操作如下图所示
统计集合中 1 的个数
bitcount key
将多个集合进行交集、并集等操作
bitop and/or/not result k1 k2...
我们要辩证的看待这个问题!
Bitmap 占用的空间小,Set 的占用空间较大;所以当数据量很大的时候,那么我们此时此刻来使用 Set 是不利的,会消耗很大的内存空间;但是也并不意味这 Bitmap 就是比 Set 要好,在数据量很大的情况下,有用的数据很少的时候,那么用 Bitmap 就是十分不划算的,因为其大部分的数据都是 0 ,造成了很大的数据冗余,空间浪费;
有一个场景,我们在某些情况下需要这种数据类型,比如在统计网站 PV(PageView 页面访问量),可以利用 Redis 的 incr、incrby 来进行实现;但是存在的问题,像 UV(UniqueVisitor 独立访客)、独立的 IP 数、搜索记录数等需要去重的问题,这类问题统称为基数问题;
添加指定的元素到 HyperLogLog 返回 1,则说明添加成功,返回 0,则说明添加失败;
pfadd key value
计算 HLL 的近似数;
pfcount key
合并两个 HLL 数据类型的数据集;
pfmerge
演示如下图所示
Redis 3.2 中增加了对 GEO 类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的二维坐标,在地图上就是经纬度。Redis 基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度 Hash 等常见操作;
加地理位置(经度、纬度、名称);
geoadd key longitude latitude member longitude latitude member ...
获得指定地区的坐标值;
geopos key member geopos china:city chongqing
获得两个地区的直线距离;
geodist key member1 member2 [m/km/ft/mi]
获得指定经纬度的某一半径内的元素;
georadius key longitude latitude radius [m/km/ft/mi] georadius china:city 110 30 1000 km
Jedis 是用于使用 Java 代码来操作 Redis 的一个软件(可以理解为 Redis 专用的 JDBC),我们创建一个简单的 Maven 工程测试一下相关功能。
<!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency>
public class JedisDemo1{ public static void main(String[] args){ // 创建Jedis对象(使用有参构造) Jedis jedis = new Jedis("192.168.182.128",6379); String s = jedis.ping(); System.out.println(s); } }
解决上述问题 1:配置文件中将 bind 选项直接注释掉,和 protected-mode 设置为 no,具体看上边的部分,这里不做赘述;
解决上述问题 2:在 Linux 中开启 6379 端口;
# 查看当前防火墙状态 systemctl status firewalld.service # 查看当前需要打开的端口6379的状态 firewall-cmd --zone=docker --query-port=6379/tcp firewall-cmd --query-port=6379/tcp # 如果没有打开,那么就打开,并再次查看是否开启 # firewall-cmd --add-port=6379/tcp firewall-cmd --add-port=6379/tcp --permanent # 开启后,重启服务 systemctl stop firewalld.service systemctl start firewalld.service # 2021.11.26 补充(今天看到了新的打开端口的方法,这种方法更加简洁有效,补充如下) # 1.查看防火墙状态 firewall-cmd --state # 2.开启端口 firewall-cmd --permanent --zone=public --add-port=22/tcp # 3.重启防火墙 firewall-cmd --reload # 4.查看防火墙看起的端口
在这之后,就可以进行成功连接了;我在这里搞了一个小、shell脚本,用来开启指定端口,建议放在 root 的权限下!
#!/bin/bash command=`firewall-cmd --state` if [ $command == "running" ];then firewall-cmd --permanent --zone=public --add-port=$1/tcp firewall-cmd --reload firewall-cmd --list-ports else echo "firewall has been closed" fi
执行脚本(打开 8888 端口号)
./addport.sh 8888
具体的代码操作这里不做详细描述;
<!-- 本来是用的2.6.0,后改为 2.2.1.RELEASE --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <dependencies> <!-- web的启动器 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- redis --> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.0</version> </dependency> </dependencies>
这里遇到一个坑,也是我长期没有看 SpringBoot 的结果,他的包扫描范围由他的启动类来决定,也就是说他的包扫描路径是“他的父目录下的所有子目录”,否则会因为无法扫描到包而导致无法启动
@EnableCaching @Configuration public class RedisConfig extends CachingConfigurerSupport{ @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); template.setConnectionFactory(factory); //key序列化方式 template.setKeySerializer(redisSerializer); //value序列化 template.setValueSerializer(jackson2JsonRedisSerializer); //value hashmap序列化 template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); //解决查询缓存转换异常的问题 ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 配置序列化(解决乱码的问题),过期时间600秒 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(600)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } }
@RestController @RequestMapping("/redisTest") public class RedisTestController{ @Autowired RedisTemplate redisTemplate; @GetMapping public String testRedis(){ redisTemplate.opsForValue().set("name","张三"); String name = (String) redisTemplate.opsForValue().get("name"); return name; } }
首先他将开始进行组队,按顺序组队,组队的过程是为了防止执行过程中有其他命令进行插队操作,导致我们没有达到想要的结果!
基本的操作流程如下所示:
# 开启事务操作 multi # 开始向事务队列中进行添加语句,这里称之为组队阶段 set key value ... # 如果需要中途打断组队,放弃事务操作,那么就立即使用 discard 命令进行操作。 discard # 执行阶段,开始执行队列里的语句 exec
当我们在组队时发生错误时,那我们后面执行的时候,也是会直接报错的,整个事务是失败的,相当于是一个回滚的操作;
当我们发生组队成功,但是运行失败的情况时,报错的那一条执行失败,其他的都是会执行成功的,没有类似于回滚操作;
我们都知道,在学习数据库的时候会有一个非常经典的案例,就是银行取钱结账的这个场景,这里就不在这里赘述这个场景具体是怎么样的了;有两种锁可以很好的结局这种问题;
当我们在操作数据的时候,会对数据直接进行上锁,以防止其他人对数据进行操作。等操作结束后,释放锁。等一下个人过来进行操作数据的时候,首先会判断是否满足条件,那么这就会解决我们上面阐述的事务冲突的问题;悲观锁,可以理解为是悲观的,永远认为有人会操作他的数据,所以直接上锁,这种态度被称为是一种悲观的态度,所以称之为悲观锁;
乐观锁,顾名思义,是乐观的,认为不会有人更改他的数据,所以叫做乐观锁,实际上是给数据加了一个版本号,用来做数据校验;当我们进行操作数据的时候,会拿着数据的版本号和数据库的数据的版本号进行对比,如果是一样的版本号且数据满足操作条件,就进行直接操作数据,否则不操作;
终端一:
# 用 watch 关键字来监视一个 key set blance 100 watch blance # 开启事务 multi # 在事务下进行对 blane 的操作 incrby blance 10 exec blance = 110
终端二:
# 用 watch 关键字来监视一个 key set blance 100 watch blance # 开启事务 multi # 在事务下进行对 blance 的操作 incrby blance 10 exec 操作失败返回 nil
秒杀案例,这里不做笔记和演示,只做一些原理说明;
秒杀案例的实现,靠 Redis 的乐观锁和事务进行实现,先用 Watch 来对库存进行监视,然后建立一个事务 multi 进行事务操作,再进行操作,但是这样的方法可以解决超卖的问题,但是会出现连接超时的问题;
通过数据连接池来解决了版本一中的连接超时的问题,但是同时又发现了,有库存遗留的问题;
Lua 脚本是一个小巧的脚本语言,Lua 脚本可以轻松的被 C 语言、C ++ 调用,也可以轻松的调用 C 语言等函数,Lua 没有提供强大的类库,所以只能充当一个嵌入式脚本语言,不适合作为开发独立应用程序的语言
利用 Lua 脚本完成了 Redis 实现的秒杀任务,并且解决了遗留库存的问题;
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是 Snapshot 快照中,它恢复时是将快照文件直接读到内存中;
Redis 会单独创建(fork)一个进程用来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,在用这个临时文件替换上次持久化好的文件,在整个过程中,主线程是不会进行 IO 操作的,这样就会极大的提高了 Redis 的性能;如果需要大规模的进行数据恢复,并且对于大数据恢复的完整性不是非常的敏感,那 RDB 方式要比 AOF 方式更加的高效。RDB 的缺点是最后一次持久化后的数据可能丢失(服务器宕机之前的一个备份将会丢失,因为它会存在一个持久化周期,例如 save 20 3
表示在 20 秒内如果有 3 个数据发生改变,就会触发持久化操作);
Fork 子进程的作用就是复制一个和当前进程一样的进程,新进程的所有数据(变量、环境变量、程序计数器等)数值和原进程一致,但是他是一个全新的进程,并作为原进程的子进程;
在 redis.conf 中配置文件名称,默认为 dump.rdb;该文件的默认保存路径,默认为 Redis 启动命令行所在的目录下为 dir "/myredis/";
表示的是在 3600 秒内有一个数据发生变化就会触发备份操作,其他的类同;
save:save 只管保存,其他不管,全部阻塞手动保存,不建议使用这种方法;
bgsave:Redis 会在后台一步进行快照操作,快照的同时还可以响应客户端的请求;
可以通过 lastsave 命令获取最后一次成功执行快照的时间;
执行 flushall 命令,也会产生 dump.rdb 文件,但是里面是空的,将是毫无意义的;
当 Redis 无法写入磁盘的时候,直接关掉 Redis 的写操作,推荐 yes;
对于已经存储到磁盘中的快照,可以设置是否进行压缩存储,如果是的话,Redis 会使用 LZF 算法进行压缩存储;
如果你不想消耗 CPU 来进行压缩的话,可以设置为关闭此功能;
在存储快照后,还可以让 Redis 使用 CRC64 算法进行数据校验,但是这样做会增大 10% 的性能消耗,如果希望获取到更大的性能提升,可以关闭此功能;
先通过 config get dir 查询 rdb 文件的目录,将 *.rdb 的文件拷贝到别的地方;
rdb 的恢复:
cp dump2.rdb dump.rdb;
动态停止 RDB:redis-cli config set save"" save 后给空值,表示禁用保存策略;
AOF 是什么?Append Only File,他是以日志的形式来记录每个写操作(增量保存),将 Redis 执行过的所有指令记录写下来(读操作不记录),只是追加文件不会改写文件,Redis 启动之初会读取改文件重新构建数据,换言之,Redis 重启的话就根据日志文件的内容将写指令从前到后执行一次,从而达到恢复数据的目的;
AOF 默认不开启,可以在 redis.conf 中配置文件名称,默认为 appendonly.aof ,AOF 文件的保存路径,同 RDB 的路径一致;
当他们同时开启的时候,系统会默认读取 AOF 的数据(数据不会存在丢失);
/usr/local/bin/redis-check-aof --fix appendonly.aof
来进行恢复;始终同步,每次 Redis 的写入都会立刻记入日志;性能较差,但是数据完整性较好;
每秒同步,每秒记入日志一次,如果宕机,本地的数据可能丢失;
Redis 不主动进行同步,把同步时机交给操作系统;
AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集.可以使用命令 bgrewriteaof
首先复制几个一样的配置文件出来;
cp /home/zyd/redis/redis-6.2.6/redis.conf /home/zyd/myredis/redis.conf
设置配置文件中的一些设置;
vim redis.conf daemonize yes vim redis6379.conf # 以下就是配置文件中的内容 include /home/zyd/myredis/redis.conf pidfile /var/run/redis_6379.pid port 6379 dbfilename dump6379.rdb
拷贝另外的两台 Redis 服务器的配置,并且设置好其中的配置端口;
cp redis6379.conf redis6380.conf cp redis6379.conf redis6381.conf
redis-server /home/zyd/myredis/redis6379.conf
redis-server /home/zyd/myredis/redis6380.conf
redis-server /home/zyd/myredis/redis6381.conf
这样一来,三台服务器就此就启动成功了;我们可以通过命令来查看服务器的状态;
# 连接端口号为 6379 的 redis 服务器 reids-cli -p 6379 # 查看服务器信息、状态 > info replication
redis-cli -p 6380 > slaveof 127.0.0.1 6379 redis-cli -p 6381 > slaveof 127.0.0.1 6379
下图可能会存在一些问题,他们的 IP 地址可能存在一些问题,应该是用 127.0.0.1 才行;
该模式就是我们常见的,一台主服务器下面挂着很多台从服务器;主机挂了,等主机重启后,地位不变还是主机,从机还是从机;
该模式就是主机下面只直接挂了一台从机服务器,其他的从机服务器就挂在第一台从机上,达到“薪火相传”的目的;
当一个 master 宕机之后,后面的 slave 可以立刻升为 master,其后面的 slave 不用做任何修改;
slaveof no one
反客为主的自动版,能够后台监控主机是否故障,如果故障了会根据投票数自动将从库换为主库;
将三台服务器,调整为一主二仆模式,在这里就是 6379 带着 6380、6381;
在自定义的目录下新建 sentinel.conf 文件,这个名字绝对是不能错的;
在上述一步的配置文件中写好配置内容为 sentinel monitor mymaster 127.0.0.1 6379 1
启动哨兵
vim /home/zyd/myredis/sentinel.conf # 编辑内容 sentinel monitor mymaster 127.0.0.1 1
启动哨兵
redis-sentinel /home/zyd/myredis/sentinel.conf
启动成功,如上图所示,看的到主机是 127.0.0.1 6379 ,两台从机分别又是 6380,6381;
当我们的主机宕机了,那么我们的哨兵就会根据一些规则在从机中来选出来一个新的主机当服务器;而当我们的主机重启成功的时候,他只能作为从机来使用;
由于所有的写操作都是在主机 master 上进行的操作的,然后同步更新到 slave 上,所以从 master 同步到 slave 机器上会有一定的延迟,当系统很繁忙的时候,延迟的问题会更加严重,slave 机器数量的增加也会使这个问题更加严重;
replica-priority 100
;主从复制的 Java 代码实现
private static JedisSentinelPool jedisSentinelPool=null; public static Jedis getJedisFromSentinel(){ if(jedisSentinelPool==null){ Set<String> sentinelSet=new HashSet<>(); sentinelSet.add("192.168.11.103:26379"); JedisPoolConfig jedisPoolConfig =new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(10); //最大可用连接数 jedisPoolConfig.setMaxIdle(5); //最大闲置连接数 jedisPoolConfig.setMinIdle(5); //最小闲置连接数 jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待 jedisPoolConfig.setMaxWaitMillis(2000); //等待时间 jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig); return jedisSentinelPool.getResource(); }else{ return jedisSentinelPool.getResource(); } }
Redis 集群实现了对 Redis 的水平扩容,即启动 N 个 Redis 节点,将整个数据库分布存储在这 N 个节点中,每个节点存储总数据的 1/N;
我们这里搭建6台 Redis 服务器组成集群,由于条件限制我们选择在不同的端口来启动这些服务器;
删除持久化相关的备份文件:rdb、aof;
制作 6 个实例,映射 6 个端口,分别是 6379 、6380 、6381、6389、6390、6391
在 redis.conf 中 开启 daemonize yes
cluster-enable yes 打开集群模式
cluster-config-file nodes-6379.conf 设定节点配置文件名
cluster-node-timeout 15000 设定节点失联时间
总的配置信息如下:
在 vim 编辑器中的命令行模式使用 /%s/6379/6380 命令来替换端口信息,快速配置;
启动这 6 个 Redis 服务器;
redis-server redis6379.conf
使这 6 个节点合成一个集群,在执行以下组合命令前。要保证 nodes-xxxx.conf 文件都正常的生成了;
cd /home/zyd/redis/redis-6.x.x/src/
此处应该写上 198.x.x.x 的真实的 ip 地址,不应该写 127.0.0.1
redis-cli --cluster create --cluster-replicas 1 192.168.182.128:6379 192.168.182.128:6380 192.168.182.128:6381 192.168.182.128:6389 192.168.182.128:6390 192.168.182.128:6391
以集群方式登录!
-c 采用集群策略连接,设置数据会自动切换到相应的写主机上
redis-cli -c -p 6379
在 Redis 客户端内,通过 cluster nodes 命令查看集群信息
> cluster nodes
redis cluster 如何分配 6 个节点,一个集群至少要有三个主力节点;
集群启动选项中 --cluster-replicas 1 表示我们希望为集群中的每个主力节点创建一个从节点;要实现主节点挂了从节点顶上;
分配原则尽量保证每个主数据库运行在不同的 IP 地址,每个从库和主库不在一个 IP 地址上;
什么是 slots,一个 Redis 集群包含 16384 个插槽(hash slot),数据库中的每个键都属于这 16384 个插槽的其中一个;
集群会使用 CRC16(key)% 16384 来计算键 key 属于哪个槽,其中 CRC16(key)语句用于计算 key 的 CRC16 校验和;
在 redis-cli 每次录入、查询键值,Redis 都会计算出该 key 应该送往哪个插槽(slots),如果不是客户端对应的服务器的插槽,Redis 会报错,并告知应前往的 Redis 实例地址和端口;
redis-cli 客户端提供了 -c 参数实现自动重定向;
如 redis-cli -c -p 6379 登入后,再录入、查询键值对可以自动重定向;
不在一个 slot 下的键值,是不能使用 mget,mset 等多键操作。可以通过 { } 来定义组的概念,从而使 key 中 { } 内相同的键值对放到一个 slot 中去;
CLUSTER GETKEYSINSLOTS < slot > < count > 返回 count 个 slot 槽中的键;
如果主节点下线,从节点会自动升为主节点,其中会有 15 秒超时;主节点恢复后,主节点会变为从机;
如果某一段插槽的主机从机全部挂掉了,而 cluster-require-full-coverage 为 yes,那么整个集群都会挂掉,如果该配置项为 no,那么,该插槽数据全部不能使用,也无法存储;
redis.conf 中的参数 cluster-require-full-coverage
key 对应的数据在数据源中不存在,每次针对此 key 的请求从缓存获取不到,请求都会压到数据源(也就是不断的去查数据库,从而使得数据库系统崩溃)。比如一个不存在的用户 id 获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击,就会可能直接导致数据库崩溃;
一个一定不存在的缓存以及查询不到的数据,由于缓存是命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要去存储层去查询,失去了缓存的意义;
key 对应的数据是存在的,但是在 Redis 是过期的,此时有大量的请求过来,要访问这个 key,这些请求发现缓存过期,一般都会从后端 DB 家在数据并回设到缓存中,但是此时的高并发的请求,可能会直接将后端 DB 压垮。
key 可能会在某些时间点被超高并发的访问,是一种热点数据,这个时候,就需要考虑缓存击穿的问题;
key 对应的数据是存在的,但是在 Redis 中是过期的,此时如果有大量的请求发送过来,这些请求发现缓存过期一般都会直接请求数据库数据,并加载到 Redis 中,而此时的高并发的请求可能会直接把后端的数据库系统压垮;但是和前面的缓存击穿的不同点在于,这边指的是很多 key 缓存,前者指的是一个 key;
缓存失效时,对底层系统的冲击是很可怕的,结果是很严重的;
随着业务发展的需要,远单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程、多进程并且分部在不同的机器上,这将使原单机部署情况下并发控制所策略失效,单纯的 Java API 并不能提供分布式锁的能力。为了解决这个问题就需要一种跨 JVM 的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题;
分布式锁的主流实现方案:
每一种分布式锁解决方案都有各自的优缺点:
这里,我们就基于 Redis 实现分布式锁;
使用 Redis 实现分布式锁
Redis 命令
set key value nx px 10000
EX second :设置键的过期时间为 second 秒,SET key value EX second 效果等同于 SETEX key second value;
PX millisecond:设置键的过期时间为 millisecond 毫秒。SET key value PX millisecond 效果等同于 PSETEX key millisecond value;
NX:只在键不存在是,才对键进行设置操作。SET key value NX 效果等同于 SETNX key value;
XX:只在键已经存在时,才对键进行设置操作;
设置过期时间有两种方式