Redis
1.Nosql
- 单机mysql
- 缓存机制
- 分库分表+水平拆分+mysql集群:本质上是数据库的读写
- MyISAM:表锁,效率低
- Innodb:行锁
特点
解耦!
1.方便扩展
2.大数据量高性能
3.数据类型是多样型的(不需要设计数据库,随取随用)
4.传统RDBMS和NoSQL
- 传统的RDBMS
- 结构化组织
- 数据和关系存在单独的表中
- 严格的一致性
- 基础的事务
- Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高可扩
- 3V+3高
- 海量Volume
- 多样Variety
- 实时Velocity
- 高并发
- 高可用
- 高性能
四大分类
KV键值对:
- Redis、Tair、memacache
文档型数据库(bson和json一样):
- mongoDB:基于分布式文件存储的数据库,用来处理大量的文档
- mongoDB是一个介于关系型数据库和非关系型数据库之间的产品。
- ConthDB
列存储数据库:
- HBase
- 分布式文件系统
图关系数据库:
-
存放的是关系(如朋友圈社交网络)
-
Neo4j,InfoGrid
2.Redis入门
概述
Redis(Remote Dictionary Server):远程字典服务
作用:
- 内存存储,持久化,内存是断电即失,持久化(rdb、aof)
- 效率高,可用用于高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器
特性:
- 多样的数据类型
- 持久化
- 集群
- 事务
配置:
- https://redis.io/
3.Windows安装
github地址
开启redis,开启服务
使用客户端连接redis
redis-benchmark性能测试
4.基础知识
端口号:6379
127.0.0.1:6379> select 3 //切换数据库
OK
127.0.0.1:6379[3]> DBSIZE //db大小
(integer) 0
127.0.0.1:6379[3]> keys * //查询所有的db数
1) "name"
127.0.0.1:6379[3]> flushdb //清空当前数据库
OK
127.0.0.1:6379[3]> FLUSHALL //清空所有数据库
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
redis是单线程的
- redis是基于内存操作的,CPU不是redis的性能瓶颈,是根据机器的内存和网络带宽
单线程为什么这么快?
- redis是c语言写的,官方的数据是10万+的QPS
- 将所有的数据放在内存中的,多线程(CPU会上下文切换:耗时),低于内存系统来说,没有上下文切换效率就是最高的,多次读写都是在一个CPU上,在内存情况下单线程就是最好的。
5.五大数据类型
Redis-key
127.0.0.1:6379> set name liuxiang //设置key
OK
127.0.0.1:6379> EXISTS name //判断是否存在
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
127.0.0.1:6379> move name 1 //移除
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> get age
"1"
127.0.0.1:6379> EXPIRE age 10 //设置key过期时间 单位s
(integer) 1
127.0.0.1:6379> ttl age //查看当前key的剩余时间
(integer) 7
127.0.0.1:6379> ttl age
(integer) 0
127.0.0.1:6379> get age
(nil)
127.0.0.1:6379> type name //查看当前key的类型
string
127.0.0.1:6379> type age
string
String(字符串)
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> get v1
(nil)
127.0.0.1:6379> get key1
"v1"
127.0.0.1:6379> EXISTS key1
(integer) 1
127.0.0.1:6379> APPEND key1 "hello" //追加字符串 当前key不存在相当于set一个key
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 //获取字符串的长度
(integer) 16
127.0.0.1:6379> APPEND key1 ",liuxiang"
(integer) 16
127.0.0.1:6379> get key1
"v1hello,liuxiang"
=====================================================================
127.0.0.1:6379> set views 0 //设置浏览量
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views //增加1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views //减少1
(integer) 1
127.0.0.1:6379> incrby views 10 //自增 设置步长
(integer) 11
127.0.0.1:6379> decrby views 5 //自减 设置步长
(integer) 6
============================================================
127.0.0.1:6379> set key1 "hello liuxaing" //设置key1值
OK
127.0.0.1:6379> get key1
"hello liuxaing"
127.0.0.1:6379> getrange key1 0 3 //截取字符串【0,3】
"hell"
127.0.0.1:6379> getrange key1 0 -1 //获取全部的字符串
"hello liuxaing"
==============================================================
127.0.0.1:6379> set key1 abcdefg
OK
127.0.0.1:6379> get key1
"abcdefg"
127.0.0.1:6379> setrange key1 1 xx //替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key1
"axxdefg"
===========================================================
127.0.0.1:6379> setex key1 30 "hello" //设置key1值 30s后过期
OK
127.0.0.1:6379> ttl key1
(integer) 26
127.0.0.1:6379> get key1
"hello"
127.0.0.1:6379> setnx mykey "redis" //设置过期时间
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
127.0.0.1:6379> setnx mykey "mongdb" //不存在再设置(在分布式锁常用)如果mykey存在 创建失败
(integer) 0
127.0.0.1:6379> keys *
1) "mykey"
127.0.0.1:6379> get mykey
"redis"
=================================================================
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 //同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3 //同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 //原子性操作 k4创建失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
==================================================================
//key的设计:user:{id}:{filed}
127.0.0.1:6379> mset user:1:name liuxiang user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "liuxiang"
2) "2"
==================================================================
//先get后set
127.0.0.1:6379> getset db redis //如果不存在值则返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb //如果存在值 获取原来的值 并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"
List
基本的数据类型,列表
127.0.0.1:6379> lpush list one #左插值
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 #通过区间获取具体的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379> rpush list right #右插值
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list #左弹值
"three"
127.0.0.1:6379> rpop list #右弹值
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0 #通过下表获得list中的某个值
"two"
127.0.0.1:6379> llen list #获得list的长度
(integer) 2
127.0.0.1:6379> lrem list 1 one #移除list集合中指定个数的value
(integer) 1
=====================================================================
127.0.0.1:6379> rpush list "hello"
(integer) 1
127.0.0.1:6379> rpush list "hello1"
(integer) 2
127.0.0.1:6379> rpush list "hello2"
(integer) 3
127.0.0.1:6379> ltrim list 1 2 #通过下表截取指定的长度
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello1"
2) "hello2"
=====================================================================
127.0.0.1:6379> rpush list "hello"
(integer) 1
127.0.0.1:6379> rpush list "hello1"
(integer) 2
127.0.0.1:6379> rpush list "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush list otherlist #移除列表的最后一个元素移动到新的列表中
"hello2"
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange otherlist 0 -1
1) "hello2"
=====================================================================
127.0.0.1:6379> lpush list 1
(integer) 1
127.0.0.1:6379> lset list 0 okk #替换当前索引位置的值,若无值会报错
OK
127.0.0.1:6379> lrange list 0 0
1) "okk"
127.0.0.1:6379> lpush list "hello"
(integer) 2
127.0.0.1:6379> linsert list before "okk" other #在指定字符位置前插入值
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "other"
3) "okk"
Set集合
127.0.0.1:6379> sadd myset "hello" #添加值
(integer) 1
127.0.0.1:6379> smembers myset #查set中的所有值
1) "hello"
127.0.0.1:6379> sismember myset "hello" #判断某个值是不是在set集合中
(integer) 1
127.0.0.1:6379> srem myset "hello" #移除set中的指定值
(integer) 1
127.0.0.1:6379> scard myset #查询set集合中值的个数
(integer) 0
127.0.0.1:6379> srandmember myset #随机抽取一个值
"hello"
127.0.0.1:6379> spop myset #随机删除set集合中的一个值
"hello"
====================================================================
127.0.0.1:6379> sdiff k1 k2 #差集
1) "b"
127.0.0.1:6379> sinter k1 k2 #交集
1) "a"
2) "c"
127.0.0.1:6379> sunion k1 k2 #并集
1) "a"
2) "b"
3) "c"
4) "e"
Hash map集合
key-map形式
127.0.0.1:6379> hset myhash field liuxiang #set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash field
"liuxiang"
127.0.0.1:6379> hmset myhash field hello field1 world #set多个key-value
OK
127.0.0.1:6379> hmget myhash field field1 #获取多个值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash #获取全部的数据
1) "field"
2) "hello"
3) "field1"
4) "world"
127.0.0.1:6379> hdel myhash field1 #删除指定字段及value值
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field"
2) "hello"
127.0.0.1:6379> hlen myhash #获取hash表的字段数量
(integer) 1
127.0.0.1:6379> hexists myhash field #判断是否存在
(integer) 1
127.0.0.1:6379> hkeys myhash #获取所有字段
1) "field"
127.0.0.1:6379> hvals myhash #获取所有值
1) "hello"
127.0.0.1:6379> hincrby myhash field1 1 #自增
(integer) 2
127.0.0.1:6379> hsetnx myhash field hello #如果存在则不能设置
(integer) 0
Zset有序集合
127.0.0.1:6379> zadd salary 2500 xiaohong #添加用户
(integer) 1
127.0.0.1:6379> zadd salary 3000 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 500 xiaocong
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf inf #显示全部用户从小到大
1) "xiaocong"
2) "xiaohong"
3) "xiaoming"
127.0.0.1:6379> zrangebyscore salary -inf inf withscores #显示用户并带成绩
1) "xiaocong"
2) "500"
3) "xiaohong"
4) "2500"
5) "xiaoming"
6) "3000"
127.0.0.1:6379> zrem salary xiaohong #移除有序集合中的key
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "xiaocong"
2) "xiaoming"
127.0.0.1:6379> zcard salary #获取有序集合中的个数
(integer) 2
6.Geospatial地理位置
基于Zset实现!
#geoadd 添加地理位置 参数: 纬度 经度 名称
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijin
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqi 114.05 22.52 shenzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.96 34.26 xian
(integer) 1
#geopos 获取指定地点的经纬度
127.0.0.1:6379> geopos china:city beijin chongqi
1) 1) "116.39999896287918"
2) "39.900000091670925"
2) 1) "106.49999767541885"
2) "29.529999579006592"
#geodist 获取两地的直线距离
127.0.0.1:6379> geodist china:city hangzhou shenzhen
"1052108.2563"
# georadius 获取指定经纬度半径范围内的地点
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqi"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord
1) 1) "chongqi"
2) 1) "106.49999767541885"
2) "29.529999579006592"
2) 1) "xian"
2) 1) "108.96000176668167"
2) "34.2599996441893"
#georadiusbymember 获取指定地方周围半径的城市
127.0.0.1:6379> georadiusbymember china:city shenzhen 500 km
1) "shenzhen"
7.Hyperloglog
传统的方式,set保存用户的id,可用统计set中的元素数量作为判断标准,这个方式如果保存大量的用户id就会比较麻烦。
Redis Hyperloglog基数统计法。
127.0.0.1:6379> pfadd mykey a b c d e f g h i j k
(integer) 1
127.0.0.1:6379> pfcount mykey #统计mykey元素的基数数量
(integer) 11
127.0.0.1:6379> pfadd mykey2 i z x g h o l
(integer) 1
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 #合并两组
OK
127.0.0.1:6379> pfcount mykey3 #查看并集的数量
(integer) 15
8.Bitmaps
两个状态的均可用,如打卡和登录等!
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0
127.0.0.1:6379> bitcount sign #统计打卡记录
(integer) 4
9.事务操作
redis单条命令保持原子性,但事务不保证原子性!没有隔离性
事务本质:一组命令的集合!
所有命令在事务中并没有直接执行,只有发起执行命令的时候才会执行!
redis事务:
- 开启事务:multi
- 命令入队
- 执行事务:exec
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discrad #取消事务
OK
127.0.0.1:6379> exec #取消的事务不会被执行
1) OK
2) OK
3) "v1"
4) OK
编译型错误:代码错误 ;运行时异常:语法错误
10.redis乐观锁
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #监视
OK
127.0.0.1:6379> multi #事务正常结束,数据期间没有发生变动这个时候正常启动事务
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
=================================================================
# 测试多线程修改值,执行前另外一个线程修改了值,事务exec执行失败
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec
(nil)
==================================================================
# 执行失败,先解锁,再监控再做事务操作
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec
1) (integer) 980
2) (integer) 40
11.Jedis
Jedis是Redis官方推荐的Java连接开发工具,使用java操作redis中间件。
1.导入对应的依赖
<dependencies>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.8.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
</dependencies>
2.编码测试
- 连接数据库
- 操作
- 断开连接
操作事务:
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","liuxiang");
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try {
multi.set("user1",result);
multi.set("user2",result);
int i = 1/0; //运行时异常
multi.exec(); //执行事务
} catch (Exception e) {
multi.discard(); //放弃事务
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();
}
}
}
12.SpringBoot整合
原来的Jedis被替换为了lettuce!
jedis:采用的直连,多个线程操作不安全,避免不安全可用使用jedis pool线程池
lettuce:采用netty,实例可用在多个线程中共享,不存在线程不安全!
1.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2.配置
#配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
3.连接测试
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//opsForValue() 操作字符串 类似String
//opsForList() 操作list集合
//opsForSet() 操作set集合
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
redisTemplate.opsForValue().set("mykey","liuxiang");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
自定义RedisTemplate模板
@Configuration
public class RedisConfig {
//编写自己的redisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//序列化配置
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);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用string的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key采用string的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value采用jackson2JsonRedisSerializer
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash 的value采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
13.Redis配置文件详解
单位
大小写不敏感
包含
如spring中的import,include,将其他配置文件加进来
网络
bind 127.0.0.1 //绑定的ip
protected-mode yes //保护模式
port 6379 //端口设置
通用
windows不支持!
NOT SUPPORTED ON WINDOWS daemonize no //默认是no 以守护进程的方式运行
NOT SUPPORTED ON WINDOWS pidfile /var/run/redis.pid //如果以后台的方式运行需要指定一个pid文件
//日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" //日志的输出文件名
databases 16 //数据库的数量 ,默认16个
快照SNAPSHOTTING
在规定的时间内执行多少次操作则会持久化到文件.rdb .aof(持久化文件)
redis是内存数据库,如果没有持久化那么数据断电即失!
save 900 1 //如果900s内,如果有一个key修改就进行持久化操作
save 300 10//如果300s内,如果有十个key修改就进行持久化操作
save 60 10000//如果900s内,如果有一万个key修改就进行持久化操作
stop-writes-on-bgsave-error yes //持久化出错是否还需要继续工作
rdbcompression yes //是否压缩rdb文件,需要消耗一些CPU资源
rdbchecksum yes //保存edb文件时候进行错误的校验
dbfilename dump.rdb //rdb文件保存的目录
主从复制REPLICATION
在主从复制一节当中!
安全
设置密码:
config set requirepass "123456" #设置密码
auth 123456 #登录
ping
config get requirepass #获得密码
requirepass foobared
限制 客户端
maxclients 10000 #设置连接上redis的最大客户端数量
maxmemory <bytes> #最大的内存容量
maxmemory-policy noeviction #内存到达上限的处理策略
1.volatile-lru 只对设置了过期时间的key进行lru
2.allkeys-lru 删除lru算法的key
3.volatile-random 随机删除过期的key
4.allkeys-random 随机删除
5.volatile-ttl 删除即将过期的
6.noeviction 永不过期 返回错误
APPEND ONLY MODE
appendonly no #默认不开启aof模式,默认使用rdb方式持久化(大部分情况够用)
appendfilename "appendonly.aof" #持久化文件
# appendfsync always 每次修改都会sync 消耗性能
appendfsync everysec #每秒执行一次sync 可能会丢失数据
# appendfsync no 不执行同步,操作系统自己同步数据
14.redis持久化
redis是内存数据库,不保存就会断电即失!
RDB(Redis DateBase)
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入一个临时文件中,等持久化过程结束后替换上次持久化的文件。
进行大规模的数据恢复RDB比AOF更加高效,因为不进行IO操作
缺点:最后一次持久化的数据可能会丢失,默认是RDB
rdb保存的文件是dump.rdb
127.0.0.1:6379> config get dir
1) "dir"
2) "D:\\Environment\\Redis-x64-3.2.100" #如果在这个目录下存在dump.rdb文件,启动就自动恢复数据
优点:
1、适合大规模的数据恢复
2、对数据的完整性要求不高
缺点:
1、需要一定的时间间隔进程操作,redis意外宕机了最后一次数据就没有
2、fork进程的时候会占用一定的空间
AOF(Append Only File)
aof文件大于64M,fork新进程重写文件!将所有命令都记录下来。
默认不开启:
appendonly no #改为yes就开启
appendfilename "appendonly.aof" #持久化的文件名
appendfsync always #每次修改都会sync 消耗性能
appendsync everysec #每秒执行一次sync
appendsync no #不执行sync,操作系统自己同步数据
如果aof文件有错误,redis是启动不起来,利用
redis-check-aof --fix appendonly.aof
修复文件
缺点:
1、相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
2、aof运行效率慢!有IO操作!
扩展:
- 只做缓存可以不适用任何持久化
- 同时开启两种持久化方式,优先载入AOF,因为数据更完整
15.Redis订阅发布
Redis发布订阅(pub/sub)是一种消息通信模式:发送者pub发送消息,订阅者sub接收消息
Redis客户端可以订阅任意数量的频道。
SUBSCRIBE liuxiang #订阅一个频道
PUBLISH liuxiang "hello,man" #发布者发布消息到频道
原理:
使用C实现
16.Redis主从复制
概念:
主从复制指将一台redis服务器的数据复制到其他redis服务器(主机到从机)
主机以写为主,从机以读为主
主从复制的作用:
- 数据冗余:实现了数据的热备份,是持久化之外的另一种数据冗余方式
- 故障恢复:当主节点出现问题时由从节点提供服务,实现快速的故障恢复
- 负载均衡:在主从复制的基础上配合读写分离,可以由主节点提供写服务,从节点提供读服务,分担服务器负载。
- 高可用基石:主从复制是哨兵和集群能够实施的基础
主从复制:复制配置文件修改pid、dump文件、log日志以及端口即可!
基本的要求:一主二从,默认情况下每台redis服务器都是主节点
配置从机即可(命令):
SLAVEOF 127.0.0.1 6379
如果重启后会变成主机,再次变成从机还是会获取所有信息!
在配置文件中永久配置:
Redis最大使用内存不应该超过20G
127.0.0.1:6379> info replication #查看当前库的信息
# Replication
role:master #主机
connected_slaves:0 #从机数
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379>
复制原理:
- slave启动成功连接到master后会发送一个sync同步命令
- master接到命令启动后台的存盘进程同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到slave,并完成一次同步
- 全量复制:slave服务接收到数据库文件后存盘并加载到内存中
- 增量复制:master继续将新的修改命令依次传给slave,完成同步
层层递进模式:
中间的服务器既当从节点又当主节点,当主节点崩了,此时变成主节点
slaveof no one #自己成为主节点
17.哨兵模式
哨兵是一个独立的进程,独立运行,原理是哨兵通过发送命令等待redis服务器响应,从而监控运行的多个redis实例!
两个作用:
- 通过发送命令让redis服务器返回监控其运行状态包括主服务器和从服务器
- 当哨兵监测到master宕机,自动切换slaver到master,通过发布订阅模式通知其他从服务器修改配置文件
多哨兵模式:除了监控redis服务器,哨兵之间也互相监控
1.配置哨兵配置文件sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1 #1代表主机宕机投票选举主机
2.启动哨兵
redis-sentinel kconfig/sentinel.conf
主机宕机,临时选举变成主机,如果主机恢复,只能继续变回从机!
缺点:
1.扩容麻烦
2.实现哨兵模式的配置复杂
哨兵模式的配置
端口:26379,如果有哨兵集群,需要配置多个端口
18.缓存穿透和雪崩
缓存穿透(查不到)
用户想要查询一个数据,发现redis内存数据库没有。也就是缓存没有命中,于是向持久层数据库查询,发现也没有,查询失败。当用户很多的时候,缓存都没有命中,于是都去持久层数据库查询,带来很大压力,就会出现缓存穿透。
解决:
1.布隆过滤器:
一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免对底层存储系统的查询压力。
2.缓存空对象
当存储器不命中后,即使返回的空对象也缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护后端数据源。
存在问题:
- 如果空值被缓存,无意义
- 对空值设置过期时间,还是会存在不一致
缓存击穿(量太大)
指一个key非常热点,在不停的扛着大并发,集中对一个点进行访问,当缓存失效(缓存过期)的瞬间,持续的大并发穿破缓存直接请求数据库。
解决:
- 设置缓存不过期
- 加互斥锁:
- 分布式锁:保证每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限
缓存雪崩
指在某一个时间段,缓存集中过期失效,redis宕机!
解决方案:
- redis高可用:多增加redis服务器
- 限流降级:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某一个key只允许一个线程查询数据和写缓存
- 数据预热:在正式部署之前,先把可能的数据先访问一遍,设置不同的过期时间,让缓存失效的时间点尽量均匀。