【注:本笔记参照Redis6公开课程所做笔记,仅学习使用】
一、NoSQL数据库 1.1 NoSQL是什么- NoSQL,Not Only SQL,泛指非关系型的数据库
- NoSQL不依赖业务逻辑方式存储,而以简单的key-value模式存储,因此大大的增加了数据库的扩展能力
- 不遵循SQL标准,不支持ACID,远超于SQL的性能
- 对数据高并发的读写
- 海量数据的读写
- 对数据高可扩展性的
-
需要事务支持的时候不适用
-
基于sql的结构化查询存储,处理复杂的关系,需要即席查询时不适用
-
用不着sql的和用了sql也不行的情况,请考虑用NoSQL
Redis就是典型的NoSQL数据库
- Redis是一个开源的key-value存储系统
- 和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set – 有序集合)和hash(哈希类型)
- 这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的
- 在此基础上,Redis支持各种不同方式的排序
- 与memcached一样,为了保证效率,数据都是缓存在内存中
- Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件
- 实现了master-slave(主从)同步
- 配合关系型数据库做高速缓存
- 多样的数据结构存储持久化数据
- Redis安装在linux环境下,还需要C语言环境
- 首先在linux的目录下解压下载的安装包
- Redis默认将redis安装在目录/usr/local/bin下
#安装c语言环境 yum install gcc # 解压安装包 tar -zxvf redis-6.2.1.tar.gz # 进入目录中 cd redis-6.2.1 # 用make命令编译文件,编译成c文件 make # 安装redis make install # 进入目录中看是否有redis相关文件 cd /usr/local/bin ll2.4 Redis启动 2.4.1 备份redis.conf
拷贝一份redis.conf到其他目录下
# 进入redis文件中,找到redis.conf文件 cd /opt/redis-redis-6.2.1 cp redis.conf /etc/redis.conf2.4.2 更改daemonize配置
进入备份的redis.conf文件,将 daemonize no 改成 daemonize yes
vi redis.conf2.4.3 启动redis
# 进入redis目录 cd /usr/local/bin # 将redis启动,格式:redis-server + 备份的redis.conf的位置 redis-server /etc/redis.conf # 启动之后查看进程 ps -ef | grep redis2.4.4 通过客户端连接redis
redis-cli # 使用ping命令,出现PONG表示正常联通状态 ping2.4.5 redis的关闭
redis-cli shutdown2.5 Redis使用
- 端口号默认是6379
- 默认16个数据库,类似数组下标从0开始,初始默认使用0号库
- 使用命令select来切换数据库。如:select 8
- 统一密码管理,所有库同样密码
- dbsize查看当前数据库的key的数量
- flushdb清空当前库
- flushall通杀全部库
- redis支持多数据类型,支持持久化
- redis底层是单线程+多路IO复用技术
- 每个人给需求到黄牛,黄牛没买到票之前,123号做自己的事情。黄牛买到1号人的票之后,让1号取票,23号继续做自己的事情
- 保证CPU一直都在运行
# 查询库中的所有key keys * # 查询key是否存在,存在返回1,不存在返回0 # 以下例子是查询k1是否存在 exists k1 # 查询key的类型 # 以下例子是查询k1的数据类型 type k1 # 删除key del k3 # 根据value选择非阻塞删除key【将元数据删除,真实数据会慢慢删除】 unlink k3 # expire key 10 10秒钟:为给定的key设置过期时间 expire k1 10 #ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期 ttl k1 # select命令切换数据库 select 15 # dbsize查看当前数据库的key的数量 dbsize # flushdb清空当前库 flushdb # flushall通杀全部库 flushall3.2 string类型 3.2.1 string类型特点
- String是Redis最基本的类型,一个key对应一个value
- String类型是二进制安全的,意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象
- String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M
# set:添加key和value set k1 value # 注:如果key重复了,后来的value会覆盖之前的value # get:根据key查询value get k1 # append:将给定的value追加到原值的末尾 append k1 abc # strlen:获得值的长度 strlen k1 # setnx:只有在key不存在时才设置key的值 setnx k3 500 # incr:将key中储存的数字值增1。只能对数字值操作,如果为空,新增值为1 incr k3 # decr:将key中储存的数字值减1。只能对数字值操作,如果为空,新增值为-1 decr k3 # incrby/decrby+key+步长:将key中储存的数字值增减自定义步长 incrby k3 10 # mset3.2.3 string数据结构... :设置多个key/value mset k1 v1 k2 v2 k3 v3 # mget ...:得到多个值 mget k1 k2 k3 # msetnx ...:同时设置一个或多个key-value对,当且仅当所有给定key都不存在【原子性,有一个出错了整体就出错】 # getrange <起始位置><结束位置>:取value中第几号到第几号位置的值 getrange k1 0 3 # 假设k1的value是lucymary,则执行结果为lucy # setrange <起始位置> :将value中的第几号位置的值开始后面几位设置成新的value setrange k1 3 abcd # 假设k1的value是lucymary,则执行结果是luc abc ry # setex <过期时间> :设置kv的时候,就设置过期时间,单位是秒 setex yanzhengma 10 abcd # getset :以新值换旧值,设置了新值同时获得旧值 getset k1 v100
- string的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。
- 可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配
- 单键多值
- Redis列表是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边)
- 它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差
# lpush/rpush3.3.3 List数据结构:从左边/右边插入一个或多个值 lpush k1 v1 v2 v3 # 头插 rpush k1 v4 v5 v6 # 尾插 # lpop/rpop :从左边/右边弹出一个值,值在键在,值光键亡 lpop k1 rpop k1 # rpoplpush :从key1列表右边吐出一个值,插到key2列表左边 rpoplpush k1 k2 # lrange :按照索引下标获得元素(从左到右),取所有值是从0~-1 lrange k1 0 -1 # lindex :按照索引下标获得元素(从左到右) lindex k1 3 # llen :获得列表长度 llen k1 # linsert before/after :在 的后面插入 插入值 linsert k1 before "v1" "new v1" # 在v1前面添加新的值new v1 # lrem :从左边删除n个value(从左到右) lrem k1 2 v1 # lset :将列表key下标为index的值替换成value lset k1 1 "new"
- List的数据结构为快速链表quickList
- 首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表,它将所有的元素紧挨着一起存储,分配的是一块连续的内存
- 当数据量比较多的时候才会改成quicklist
- 因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next
- Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余
- Set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择
- Set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的
- Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)
# sadd3.4.3 Set数据结构:将一个或多个member元素加入到集合key中,已经存在的member元素将被忽略 sadd k1 v1 v2 v3 # smembers :取出该集合的所有值 smembers k1 # sismember :判断集合key是否为含有该value值,有1,没有0 sismember k1 v2 # scard :返回该集合的元素个数 scard k1 # srem :删除集合中的某个元素 srem k1 v1 v2 # spop :随机从该集合中弹出一个值 spop k1 # srandmember :随机从该集合中取出n个值,不会从集合中删除 srandmember k2 2 # smove value:把集合中一个值从一个集合移动到另一个集合 smove k1 k2 v3 # 把k1的v3值移动到k2 # sinter :返回两个集合的交集元素 sinter k1 k2 # sunion :返回两个集合的并集元素 sunion k2 k3 # sdiff :返回两个集合的差集元素(key1中的,不包含key2中的) sdiff k1 k2
- Set数据结构是dict字典,字典是用哈希表实现的
- Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象
- Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值
- Hash是一个键值对集合
- Hash是一个string类型的field和value的映射表,hash特别适合用于存储对象,类似Java里面的Map
- 用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储
# hset3.5.3 Hash数据结构:给key集合中的field键赋值value hset user:1001 id 1 hset user:1001 name zhangsan # hget :从key1集合field取出value hget user:1001 id # hmset :批量设置hash的值 hmset user:1002 id 2 name lisi age 30 # hexists :查看哈希表key中,给定域field是否存在 hexists user:1002 id # hkeys :列出该hash集合的所有field hkeys user:1002 # hvals :列出该hash集合的所有value hvals user:1002 # hincrby :为哈希表key中的域field的值加上增量1,-1 hincrby user:1002 age 2 # hsetnx :将哈希表key中的域field的值设置为value,当且仅当域field不存在 hsetnx user:1002 gender 1
- Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)
- 当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable
- Redis有序集合Zset与普通集合set非常相似,是一个没有重复元素的字符串集合
- 不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员
- 集合的成员是唯一的,但是评分可以是重复的
- 因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素
- 访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表
# zadd3.6.3 Zset数据结构:将一个或多个member元素及其score值加入到有序集key当中 zadd topn 200 java 300 c++ 400 mysql 500 php # zrange [WITHSCORES]:返回有序集key中,下标在 之间的元素【从小到大排序】,带WITHSCORES,可以让分数一起和值返回到结果集 zrange topn 0 -1 zrange topn 0 -1 withscores # zrangebyscore key min max [withscores] [limit offset count]:返回有序集key中,所有score值介于min和max之间(包括等于min或max)的成员。有序集成员按score值从小到大次序排列。带WITHSCORES,可以让分数一起和值返回到结果集 zrangebyscore topn 300 500 # zrevrangebyscore key max min [withscores] [limit offset count]:同上,只是排序方式变成分数从大到小排 zrevrangebyscore topn 400 100 withscores # zincrby :为元素的score加上增量 zincrby topn 50 java # zrem: 删除该集合下,指定值的元素 zrem topn php # zcount :统计该集合,分数区间内的元素个数 zcount topn 200 300 # zrank :返回该值在集合中的排名,从0开始 zrank topn mysql
- SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map
,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表 - Zset底层使用了两个数据结构(1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。(2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表
- Redis发布订阅(pub/sub)是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息
- Redis客户端可以订阅任意数量的频道
-
客户端可以订阅频道
-
当给这个频道发布消息后,消息就会发送给订阅的客户端
-
打开两个客户端,分别进入/usr/local/bin/redis-cli
-
使用第一个客户端,订阅channel1
subscribe channel1
-
使用第二个客户端,向channel1发布一条消息“world”
publish channel1 world
-
客户端1收到了这个world消息
- Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作
- 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量
# setbit:设置Bitmaps中某个偏移量的值(0或1),偏移量从0算起 setbit users:20220711 1 1 # 将第1的位置替换成1 setbit users:20220711 6 1 # 将第6的位置替换成1 setbit users:20220711 11 1 # 将第11的位置替换成1 setbit users:20220711 15 1 # 将第15的位置替换成1 setbit users:20220711 19 1 # 将第19的位置替换成1
# getbit5.2 HyperLogLog 5.2.1 HyperLogLog类型特点:获取Bitmaps中某个偏移量的值 getbit users:20220711 1 # bitcount [start end]:统计字符串被设置为1的bit数,start和end是指定范围内统计 # start 和 end 参数的设置,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,start、end 是指bit组的字节的下标数,二者皆包含 bitcount user:20220711 # bitop and(or/not/xor) [key…]:bitop是一个复合操作,它可以做多个Bitmaps的and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在destkey中 bitop and unique:users:and:20201104_03 unique:users:20201103 unique:users:20201104
- 主要可以解决求集合中不重复元素个数的问题,也叫基数问题
- 比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8},基数(不重复元素)为5
- 在Redis里面,每个HyperLogLog键只需要花费12KB内存,就可以计算接近2^64个不同元素的基数
# pfadd5.3 Geospatial 5.3.1 Geospatial类型特点< element> [element ...]:添加指定元素到HyperLogLog中 pfadd program "java" # pfcount [key ...]:计算HLL的近似基数,可以计算多个HLL,比如用HLL存储每天的UV,计算一周的UV可以使用7天的UV合并计算即可 pfcount program # pfmerge [sourcekey ...]:将一个或多个HLL合并后的结果存储在另一个HLL中,比如每月活跃用户可以使用每天的活跃用户来合并计算可得 pfmerge knew k1 program # 将看k1和program合并后放入knew里
- GEO,Geographic,地理信息的缩写。该类型,就是元素的二维坐标,在地图上就是经纬度
- Redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作
# geoadd六、Jedis 6.1 使用Jedis连接Redis[longitude latitude member...]:添加地理位置(经度,纬度,名称) # 有效的经度从 -180 度到 180 度。有效的纬度从 -85.05112878 度到 85.05112878 度。两极不可添加 geoadd china:city 121.47 31.23 shanghai # geopos [member...]:获得指定地区的坐标值 geopos china:city shanghai # geodist [m|km|ft|mi ]:获取两个位置之间的直线距离,最后添加单位,默认为米 geodist china:city shanghai shenzhen km # georadius < longitude> radius m|km|ft|mi:以给定的经纬度为中心,找出某一半径内的元素 georadius china:city 110 30 1000 km
1. 导入Jedis依赖
redis.clients jedis 3.2.0
2. 创建工程后,编写Java代码
package com.atguigu.Jedis;
import redis.clients.jedis.Jedis;
public class JedisDemo {
public static void main(String[] args) {
Jedis jedis = new Jedis("172.16.248.201", 6379);
System.out.println(jedis.ping());
jedis.close();
}
}
注意1:new Jedis时,传入参数port为Redis的主机IP,后面参数是端口号
注意2:如果报错,将redis.conf文件里面的bind 127.0.0.1一行注释掉,并将protected-mode一行值改为no,并重启redis服务
注意3:如果上述操作之后还报错,则将linux防火墙关闭即可
参考以下CSDN:摘用其他博主的文章
6.3 案例:Jedis实现验证码操作package com.atguigu.phonecode;
import redis.clients.jedis.Jedis;
import java.util.Random;
import java.util.Scanner;
public class PhoneCode {
public static void main(String[] args) {
// 模拟验证码发送
verifyCode("18736544020");
Scanner sc = new Scanner(System.in);
System.out.println("请输入验证码:");
String scCode = sc.next();
getRedisCode(scCode, "18736544020");
}
// 验证码校验
public static void getRedisCode(String code, String phone){
// 连接redis
Jedis jedis = new Jedis("172.16.248.201", 6379);
// 验证码key
String codeKey = "VerifyCode" + phone + ":code";
String redisCode = jedis.get(codeKey);
// 判断
if(redisCode.equals(code)){
System.out.println("成功");
}else{
System.out.println("失败");
}
jedis.close();
}
// 每个手机一天只能发送三次,验证码放到redis中,设置过期时间120s
public static void verifyCode(String phone){
// 连接redis
Jedis jedis = new Jedis("172.16.248.201", 6379);
// 拼接key
// 手机发送次数
String countKey = "VerifyCode" + phone + ":count";
// 验证码key
String codeKey = "VerifyCode" + phone + ":code";
// 每个手机每天只能发送三次
String count = jedis.get(countKey);
if(count == null){
// 没有发送次数,第一次发送
// 设置发送次数是1
jedis.setex(countKey, 24*60*60, "1");
} else if(Integer.parseInt(count) <= 2){
// 发送次数+1
jedis.incr(countKey);
} else if(Integer.parseInt(count) > 2){
// 发送三次,不能再发送
System.out.println("今天已发送三次,不可以再次发送!");
jedis.close();
}
// 发送的验证码放到redis中
String vcode = getCode();
jedis.setex(codeKey, 120, vcode);
jedis.close();
}
// 生成6位验证码
public static String getCode(){
Random random = new Random();
String codes = "";
for (int i = 0; i < 6; i++) {
int rand = random.nextInt(10);
codes += rand;
}
return codes;
}
}
七、Redis整合SpringBoot
7.1 导入依赖
7.2 修改配置文件application.propertiesorg.springframework.boot spring-boot-starter-data-redis
将以下内容添加到配置文件中
#Redis服务器地址 spring.redis.host=192.168.140.136 #Redis服务器连接端口 spring.redis.port=6379 #Redis数据库索引(默认为0) spring.redis.database= 0 #连接超时时间(毫秒) spring.redis.timeout=1800000 #连接池最大连接数(使用负值表示没有限制) spring.redis.lettuce.pool.max-active=20 #最大阻塞等待时间(负数表示没限制) spring.redis.lettuce.pool.max-wait=-1 #连接池中的最大空闲连接 spring.redis.lettuce.pool.max-idle=5 #连接池中的最小空闲连接 spring.redis.lettuce.pool.min-idle=07.3 添加Redis配置类
package com.atguigu.redis_springboot.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
RedisTemplate template = new RedisTemplate<>();
RedisSerializer 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 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;
}
}
7.4 编写Controller
package com.atguigu.redis_springboot.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("redisTest")
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;
@GetMapping
public String testRedis(){
// 设置值到redis
redisTemplate.opsForValue().set("name", "lucy");
// 从redis获取值
String name = (String)redisTemplate.opsForValue().get("name");
return name;
}
}
7.5 启动SpringBoot
启动SpringBoot项目,在浏览器端访问http://localhost:8080/redisTest,即可。
八、Redis事务 8.1 Redis事务- Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行
- 事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
- Redis事务的主要作用就是串联多个命令防止别的命令插队
- 从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行
- 组队的过程中可以通过discard来放弃组队
- Multi进入组队阶段,Exec进入执行阶段,Discard取消
- 组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消
- 如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚
- 悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁
- 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
- 乐观锁(Optimistic Lock),顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制
- 乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的
- 在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些))key 被其他命令所改动,那么事务将被打断
- 取消WATCH命令对所有key的监视。如果在执行WATCH命令之后,exec命令或discard命令先被执行了的话,那么就不需要再执行unwatch了
- 单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。 - 没有隔离级别的概念
队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行 - 不保证原子性
事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
将数据写入硬盘,就叫持久化。
9.1 RDB 9.1.1 RDB是什么在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里
9.1.2 RDB的备份原理- Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件
- 整个过程中,主进程不进行任何IO操作,这就确保了极高的性能
- 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效
- RDB的缺点是最后一次持久化后的数据可能丢失
- Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但其是一个全新的进程,并作为原进程的子进程
- 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程
- 适合大规模的数据恢复
- 对数据完整性和一致性要求不高更适合使用
- 节省磁盘空间
- 恢复速度快
- Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
- 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
- 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改
以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
十、Redis主从复制 10.1 什么是主从复制主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主
10.2 主从复制的作用- 读写分离,性能扩展
- 容灾快速恢复
# 查看三台主机运行情况,打印主从复制相关信息 info replication # 在从机上,使用如下命令,可以将其加入主服务器上 slaveof 主机IP 主机端口号
- 主机更新数据的时候,从机的数据也随着更新
- 主机可以进行写错做,但从机不能执行写操作
- 当主服务器shutdown,从服务器不会上位
- 当从服务器shutdown之后又启动,不会继续成为之前的从服务器,只能重新slaveof
- 当从服务器连接上主服务器,从服务器向主服务器发送数据同步消息【从服务器发起】
- 主服务器接到从服务器发送的同步消息后,把主服务器数据进行持久化,放到rdb文件中,再将rdb文件发送到服务器,从服务器拿到rdb文件进行读取【主服务器发起】
- 每次主服务器进行写操作之后,和从服务器进行数据同步【主服务器发起】



