单机网站的瓶颈
数据量如果太大,一个集群放不下;
数据索引(B+Tree),单表超过300万条数据就一定要建立索引,如果索引太大,一个机器内存也放不下;
访问量(读写混合),一个服务器承受不了。
如果出现以上的情况,就必须晋级。
Memcahced(缓存)+MySQL+垂直拆分(读写分离)
读写分离,一台服务器专门负责写操作,然后同步到其他服务器,其他多台服务器负责读操作,这就组成了一个集群。
使用缓存:网站80%都在读,如果每次都要访问数据库,那就比较麻烦,为了减轻服务器压力,就可以使用缓存减轻压力、保证效率。
发展过程:优化数据结构和索引——文件缓存(IO)——Memcached
分库分表+水平拆分+MySQL集群
MyISAM:表锁,转为Innodb:行锁;
将不同业务数据拆分,分为多个数据库和表,形成多个集群。
但是如今数据量大,变化量快,MySQL等关系型数据库已经不够用了。
MySQL存储一些比较大的文件、博客、图片。数据库表很大,效率就低了。
NoSQL
NoSQL(Not Only SQL)泛指非关系型数据库,这些数据类型的存储不需要一个固定格式,不需要多余的操作就可以横向扩展的。Map<Spring,Object>使用键值对来配置。
关系型数据库:表格、行、列。
解耦:
方便扩展(数据之间没有关系);
高性能读写(NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高。Redis每秒写8w次,读取11w次);
数据类型是多样型的(不需要直接设计数据库,随取随用);
传统的RDBMS和NoSQL
RDBMS
结构化组织
SQL
数据和关系都存在单独的表中
操作数据,数据定义语言
严格的一致性
基础的事务操作
...
NoSQL
不仅仅是数据
没有固定的查询语言
键值对存储、列存储、文档存储、图形存储
最终一致性
CAP定理 和 BASE理论(异地多活)
高性能、高可用、高可扩
....
3V+3高:海量Volume、多样Variety、实时Velocity;高并发、高可扩、高性能。
KV键值对
新浪:Redis
美团:Redis+Tair
阿里、百度:Redis+memecache
文档型数据库(bson格式和json一样)
MongoDB
基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档。
MongoDB是一个关系型数据库和非关系数据库的中间产物,MongoDB是非关系数据库中功能最丰富,最像关系型数据库的。
ConthDB
列存储数据库
HBase(大数据)
分布式文件系统
图关系数据库
Neo4j
InfoGrid
Redis(Remote Dictionary Server),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
功能:
内存存储、持久化
效率高,可用于高速缓存
发布订阅系统
地图信息分析
计时器、计数器(浏览量)
...
官网:Redis
中文网:redis中文官方网站
官网下载zip安装包;
解压到环境目录下;
开启Redis,双击redis-server.exe运行服务;
默认端口:6379
使用redis客户端redis-cli.exe连接redis;
ping # 测试连接 set name sep # 存入键值对 get name # 获取值
官网下载tar.gz安装包;
解压安装包,程序/opt;
tar -zxvf redis-6.2.6.tar.gz
安装基本环境;
# 安装c、c++环境 yum install gcc-c++ # 配置环境 make # 确认是否都安装了 make install
安装在目录:/usr/local/bin
安装目录中新建myconf
文件夹;
将解压包中的redis.conf
复制到安装目录;
cp /opt/redis/redis-6.2.6/redis.conf myconf
修改myconf
中的配置文件,使redis默认后台启动;
vim redis.conf i daemonize yes :wq
启动Redis
# 通过指定的配置文件,启动Redis redis-server myconf/redis.conf
客户端连接
# 客户端连接Redis redis-cli -p 6379 # 客户端操作Redis ping # 测试连接 set name sep # 存入键值对 get name # 获取值
关闭Redis服务;
shutdown # 关闭redis-server服务 exit # 退出redis-cli客户端
后面可使用单机多redis。
redis-benchmark
是Redis自带的压力测试工具。
-h:指定服务器主机,默认127.0.0.1
-p:指定服务器端口,默认6379
-s:指定服务器socket
-c:指定并发连接数,默认50
-n:指定请求数,默认1w
-d:以字节的形式指定set/get值的数据大小,默认3
...
# 测试:100个并发,10w请求 redis-benchmark -h localhost -p 6379 -c 100 -n 100000
redis默认有16个数据库,默认使用第0个数据库,可以在客户端进行操作。
select 3 # 切换到数据库3 DBSIZE # 查看当前数据库大小 flushall # 清空全部 flushdb # 清空当前库
Redis是单线程的,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了。
多线程会有CPU上下文切换,反而耗时。对于内存系统来说,如果没有上下文切换,效率就是最高的。
ping # 测试连接 move name 1 # 将键值对移到数据库1 type name # 查看当前key的类型 select 3 # 切换到数据库3 DBSIZE # 查看当前数据库大小 flushall # 清空全部 flushdb # 清空当前库
set——del、push——pop、add——remove
String使用场景:value除了可以是字符串还可以是数字。
计数器
统计多单位的数量
粉丝数
对象缓存存储
阅读数量
set name sep # 存入键值对 get name # 获取值 mset k1 v1 k2 v2 k3 v3 # 一次性设置多个键值对 msetnx k1 v1 k2 v2 k3 v3 # 键全都不存在就设置,msetnx是一个原子性操作 get k1 k2 k3 # 一次性获取多个值 del k1 # 删除键 keys * # 查看所有key exists name # 判断是否存在name这个键 append name xj # 键值对中的值追加xj,如果当前key不存在,就相当于set strlen name # 获取值的长度 incr views # 自增+1 decr views # 自减-1 incrby views 10 # 自增,设置步长10 decrby views 10 # 自增,设置步长10 getrange name 0 3 # 截取下标0-3的值 getrange name 0 -1 # 截取0-最后一个字符的值 setrange name 1 sep # 从下标1开始用sep替换原有值 expire name 10 # 设置键值对过期时间为10s ttl name # 查看键值对剩余时间 setex name 30 sep # 设置过期时间为30s的键值对 setnx name sep # 如果不存在键值对,进行设置,成功返回1,失败返回0 getset name sep # 先get键对应的值,再进行set值
在 Redis 中 list 实际上是个链表,可以实现栈(Lpush Lpop)、队列(Lpush Rpop)、阻塞队列。
所有的list命令都是以l开头的。
如果移除了所有值,空链表也代表不存在
lpush mylist one # 将一个值或多个值插入列表头部(左) rpush mylist right # 将一个值或多个值插入列表尾部(右) lrange mylist 0 1 # 获取区间中的值 lindex mylist 1 # 根据索引获取值 lpop mylist # 移除列表头部值(左) rpop mylist # 移除列表尾部值(右) lrem mylist 2 one # 移除指定值2次 ltrim mylist 1 2 # 截取指定范围的列表值 rpoplpush mylist otherlist # 移除列表的最后一个元素并且添加到另一个列表头部 lset mylist 0 item # 更改列表下指定索引的值 linsert mylist before value one # 在列表的指定值前插入one linsert mylist after value one # 在列表的指定值后插入one llen mylist # 获取列表长度
Set中的值不能重复,且无序。
所有的set命令都是以s开头的。
sadd myset hello # set集合中添加元素 smembers myset # 获取指定set中的所有值 sismember myset hello # 判断集合中是否存在某元素 scard myset # 获取set集合中元素个数 srem myset hello # 删除set集合中指定元素 spop myset # 随机删除set集合中的一个元素 srandmember myset # 随机获取set集合中的1个元素 srandmember myset 2 # 随机获取set集合中的指定个数元素 smove myset myset2 hello # 指定一个值,移动到另外一个set中 sdiff myset myset2 # 差集,myset在myset2中多的元素 sinter myset myset2 # 交集,myset与myset2的相同元素(共同好友、共同关注、共同爱好) sunion myset myset2 # 并集,myset与myset2所有不重复元素
Hash的值是一个Map集合,key-Map。
Hash可用于变更的数据,尤其是用户信息值类,经常变动的信息
所有的hash命令都是以h开头的。
hset myhash field1 xj # hash中添加元素 hget myhash field1 # 获取指定hash中的指定值 hmset myhash field1 hello field2 world # 同时添加多个值 hmget myhash field1 field2 # 同时获取指定hash中的多个值 hgetall myhash # 获取hash中的所有键值 hkeys myhash # 获取hash中的所有键 hvals myhash # 获取hash中的所有值 hlen myhash # 获取hash中的键值对数量 hdel myhash field1 # 删除hash中的指定值 hincrby myhash field1 2 # 自增+2 hsetnx myhash field1 hello # 如果不存在,则设置
有序集合,在set的基础上加了一个值,set k1 v1;zset k1 score1 v1。
所有的Zset命令都是以z开头的。
zadd myzset 1 one # 添加一个元素 zadd myzset 2 two 3 three # 添加多个元素 zrange myzset 0 -1 # 获取指定范围的值,从小到大排序 zrevrange myzset 0 -1 # 获取指定范围的值,从大到小排序 zrangebyscore myzset -inf +inf # 获取在范围内从小到大排序后的值 zrangebyscore myzset -inf +inf withscores # 获取在范围内从小到大排序后的值和score zrem myzset one # 删除指定值 zcard myzset # 获取集合中元素的个数 zcount myzset 1 2 # 获取指定区间的值的数量
可用于成绩表排序、工作排序、消息权重设置。
Redis的Gen在3.2版本就已经推出了。可以推算地理位置的信息,两地之间的信息,方圆几里的距离...
# 1、geoadd(经度、纬度、名称) geoadd china:city 116.40 39.90 beijing # 添加地理位置 # 2、geodist(m:米 km:千米 mi:英里 ft:英尺) geodist china:city beijing shanghai # 获取两地距离,默认单位m geodist china:city beijing shanghai km # 获取两地距离,指定单位km # 3、geohash geohash china:city beijing # 返回一个或多个位置元素的Geohash表示(11个字符) # 4、geopos geopos china:city beijing # 获取成员地理位置 # 5、georadius georadius china:city 110 30 500 km # 寻找指定经纬度500km半径范围内的成员 georadius china:city 110 30 500 km withdist # 寻找指定经纬度500km半径范围内的成员,附带直线距离 georadius china:city 110 30 500 km withcoord # 寻找指定经纬度500km半径范围内的成员,附带经纬度 georadius china:city 110 30 500 km count 1 # 寻找指定经纬度500km半径范围内的成员,限定只找1个 # 6、georadiusbymember georadiusbymember china:city beijing 1000 km # 查找指定成员周围1000km半径内的成员
底层实现原理是zset,也可以使用zset对应方法。
zrange china:city 0 -1 # 查看范围内的所有元素 zrem china:city beijing # 删除指定元素
基数——不重复的元素,允许有误差。
Redis 2.8.9 更新了Hyperloglog数据结构,基数统计算法。占用内存固定,2^64不同元素,只需12KB内存,但约有0.81%的错误率。
pfadd mykey a b c d e # 添加多个元素 pfcount mykey # 查找key中的不重复的元素个数 pfmerge mykey3 mykey mykey2 # 将mykey和mykey2的并集存入mykey3
如果允许容错,可使用Hyperloglog;如果不允许容错,就使用set。
统计网页的访问数UV(一个人多次访问网站,算作一次)
Bitmap位图,使用位存储,二进制来进行记录,只有0和1两个状态。
setbit sign 2 0 # 在第2位(从0开始),记录0 getbit sign 2 # 查看某一位是0还是1 bitcount sign # 统计1的个数
统计用户信息(登录、未登录)、统计打卡状态(打卡、未打卡),只有两个状态的
事务的ACID:隔离性、原子性、一致性、持久性。
Redis事务本质:一组命令的集合。一个事务的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。一次性、顺序性、排他性。
Redis单条命令保证原子性,但事务不保证原子性。
Redis事务没有隔离级别的概念。
所有命令在事务中,并没有直接被执行。只有发起执行命令的时候才会执行。
开启事务(multi)
命令入队(...)
执行事务(exec)/放弃事务(discard)
编译型异常:代码错误,所有代码都不执行。
运行时异常:语义错误,其他命令正常执行,错误命令抛出异常。
测试多线程改值,使用watch监视可以当作redis的乐观锁操作,获取当前监视对象的值version,在执行事务exec时会再次获取监视对象的值version,比对version是否相同,相同则执行事务,否则,不执行。
事务执行或放弃后,watch自动解除监视。unwatch手动解除监视。
watch money # 监视键值对 multi # 开启事务 ... exec # 执行事务 unwatch # 取消监视
悲观锁
认为什么时候都会出问题,无论做什么都会加锁。
乐观锁
认为什么时候都不会出问题,所以不会上锁。更新数据的时候区判断一下,在此期间是否有人修改过。
获取version,更新的时候比较version。
Jedis 是Redis 官方推荐的java 连接开发工具,使用java 操作Redis 的中间件。
Maven导入对应依赖
<!-- jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>4.0.0-beta3</version> </dependency> <!-- fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.78</version> </dependency>
编码测试
连接数据库
操作命令
断开连接
public class TestPing { public static void main(String[] args) { // new Jedis对象 Jedis jedis = new Jedis("127.0.0.1",6379); // Jedis 中的所有方法就是redis中的命令 System.out.println(jedis.ping()); // 测试是否连接 System.out.println(jedis.set("key1", "value1")); // 添加键 // 关闭连接 jedis.close(); } }
Transaction multi = jedis.multi(); multi.set("key","value"); multi.exec(); // 执行事务 multi.discard(); // 放弃事务
jedis.watch("key");
在SpringBoot2.x 之后,原来使用 的Jedis 被替换成了Lettuce。
Jedis:采用的是直连,多个线程操作的话不安全;如果想避免不安全,使用Jedis pool连接池,像BIO。
Lettuce:采用netty,实列可以在多个线程中共享,不存在线程不安全,可以减少线程数量,更像NIO。
redis对象都需要序列化,默认使用JDK序列化,我们需要Json序列化,重写配置类:
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String,Object>(); template.setConnectionFactory(factory); // Json 序列化配置 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); // String 的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key和hash的key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); // value和hash的value序列化方式采用Jackson template.setValueSerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
Maven导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
application.properties 配置连接
# 配置redis spring.redis.host=127.0.1 spring.redis.port=6379
测试
@Autowired private RedisTemplate redisTemplate; // opsForValue 操作字符串,类似String // opsForList opsForSet opsForHash opsForZSet opsForGeo opsForHyperLogLog redisTemplate.opsForValue().set("key","value"); redisTemplate.opsForValue().get("key"); // 除了进本的操作,常用方法可以直接同redisTemplate操作,如事务、进本的CRUD redisTemplate.exec(); // 获取redis 的连接对象 RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); connection.flushAll();
可以写一个RedisUtil 工具类,统一管理api。
redis.conf 大小写不敏感
包含:包含多个redis配置
include /path/to/local.conf include /path/to/other.conf
网络:绑定访问地址
bin 127.0.0.1 # 绑定的ip protected-mode yes # 保护模式 port 6379 # 端口设置
通用配置
daemonize yes # 以守护进程的方式运行,默认是no pidfile /var/run/redis_6379.pid # 如果以后台方式运行,需要指定pid文件 loglevel notice # 日志级别 logfile "" # 日志的文件位置名 databases 16 # 数据库的数量,默认16个 always-show-logo no # 是否总是显示logo
快照:持久化,在规定时间内执行了多少次操作,则会持久化到文件 .rdb
、.aof
redis是内存数据库,如果没有持久化,那么断电即失。
save 900 1 # 如果900s内,如果有1个以上key进行修改,那么就会持久化 top-writes-on-bgsave-error yes # 持久化出错是否继续工作 dbfilename dump.rdb # rdb默认持久化文件名 rdbcompression yes # 是否压缩rdb文件,需要销毁一些cpu资源 rdbchecksum yes # 保存rdb文件时,是否进行错误的检查 dir ./ # rdb文件保存目录
复制:主从复制
replicaof 127.0.0.1 6379 # 配置从机
安全
requirepass 123456 # vim 直接修改连接密码 config get requirepass # 客户端获取连接密码 config set requirepass "" # 客户端设置连接密码 auth 123455 # 验证密码,执行相关操作,设置密码后,必须验证密码,否则所有命令都无法使用
客户端限制
maxclients 10000 # 最大客户端数
内存管理
maxmemory <bytes> # redis配置的最大内存 maxmemory-policy noeviction # 内存达到上限后的处理策略,有6种 # 1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) # 2、allkeys-lru:删除lru算法的key # 3、volatile-random:随机删除即将过期的key # 4、allkeys-random:随机删除 # 5、volatile-ttl:删除即将过期的 # 6、noviction:永不过期,返回错误
AOF
appendonly no # 默认不开启AOF模式,默认使用RDB方式持久化 appendfilename "appendonly.aof" # aof默认的持久化文件 # appendfsync always # 每次都会 sync,消耗内存 appendfsync everysec # 每秒执行执行 sync,可能会丢失1s数据 # appendfsync no # 不执行 sync,操作系统自己同步数据 no-appendfsync-on-rewrite on # 是否打开aof文件重写 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # aof文件过大重写,默认64mb,fork一个新的进程将文件进行重写。
Redis时内存数据库,如果不将内中的数据库状态保存到磁盘,那么一旦服务进程退出,内存中的数据也将消失。
默认使用RDB方式持久化,AOF不开启,RDB方式比AOF方式更加高效,但RDB最后一次持久化出错数据可能丢失。
如果只用Redis做缓存,可不做任何的持久化操作。
按照save规则,每隔一段时间进行持久化。
rdb默认文件:dump.rdb
恢复rdb文件:只需将dump.rdb 文件放入redis启动目录,redis启动时自动会检查dump.rdb 文件。
config get dir
获取redis启动目录。
持久化触发机制
满足配置文件中save的规则,自动持久化
执行flushall命令,自动持久化
shutdown退出redis,自动持久化
优缺点
优点:
适合大规模的数据恢复;备份 dump.rdb
对数据完整性要求不高。save规则
缺点:
需要一定的时间间隔进程操作;
fork子进程进行持久化的时候,会占用一定的内存空间。
以日志的形式将所有命令都记录下来(读操作不记录),恢复时就把命令再执行一遍。
aof文件可以被人为破坏,破坏后将无法启动redis,可使用redis-check-aof
修复aof文件。
redis-check-aof --fix appendonly.aof
aof默认文件:appendonly.aof
优缺点
优点:
每一次修改都同步,文件完整性会更好;
每秒同步一次(默认),可能会丢失1s数据;
从不同步,效率最高。
缺点:
相对于数据文件,aof远远大于rbd;
aof运行效率和修复速度也比rdb慢;
fork子进程持久化时,会占用内存空间。
Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)、频道、订阅者(sub)。例如微信、微博、关注系统、群聊等功能。Redis客户端可以订阅任意数量的频道。
# 订阅端 psubscribe pattern # 订阅给定模式的频道 subscribe channel # 订阅频道 punsubscribe # 退订所有给定该模式的频道 unsubscribe # 退订所有给定的频道 # 发送端 pulish channel message # 将信息发送到指定评到 pubsub subcommand # 查看订阅与发布系统状态
发布订阅原理:
Redis 是使用C实现的,通过分析Redis 源码中的pubsub.c
文件,了解发布和订阅的底层实现,借此加深对Redis 的理解。
Redis 通过publish
、subscribe
和psubscribe
等命令实现发布和订阅功能。
同步subscribe 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个channel,而字典的值则是一个链表,链表中保存了所有订阅这个channel 的客户端。subscribe 命令的关键就是将客户端添加到给定channel 的订阅链表中。
通过publish 命令向订阅者发送消息,redis-server 会使用给定频道作为键,在它所维护的channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者、
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能从主节点到从节点。Master以写为主,Slave以读为主。默认情况下,每台Redis服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
Master Slaver1 Slaver2 Slaver3读写分离。80%的情况都在进行读操作,主机处理写请求,从机处理读请求,减缓服务器压力,架构中经常使用。
主从复制的主要作用
数据冗余:主从复制实现了数据的热别发,是持久化的一种数据冗余方式;
故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,实际上时一种服务的冗余;
负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载;
高可用基石:主从复制是哨兵和集群能够实施的基础。
一般来说,要将Redis运用于工程项目中,只用一台Redis是不可以的(宕机),原因如下:
从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力过大;
从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用于Redis存储内存,一般来说,单机Redis最大使用内存不应该超过20G。
只配从库,不用配置主库。真实的主从配置应该在配置文件中配置。
info replication # 查看当前库信息 # Replication role:master # 角色,主机master connected_slaves:0 # 从机数量 master_failover_state:no-failover master_replid:660cd6c57d24ec27fda98d4dfcdbfe28510a7bbf master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
命令行配置(不是永久的):
修改配置文件
port 6379 # 端口号 daemonize yes # 打开守护进程 pidfile /var/run/redis_6379.pid # 后台运行pid logfile "6379.log" # 日志文件命名 dbfilename dump6379.rdb # rdb文件名
通过配置文件启动多个Redis
配置从机
slaveof 127.0.0.1 6379 # 从机认主机
配置文件配置(永久的):
修改配置文件
port 6379 # 端口号 daemonize yes # 打开守护进程 pidfile /var/run/redis_6379.pid # 后台运行pid logfile "6379.log" # 日志文件命名 dbfilename dump6379.rdb # rdb文件名 replicaof 127.0.0.1 6379 # 从机配置
通过配置文件启动多个Redis
主机断开连接,从机依旧可以连接到主机,但是没有写操作;主机重新连接后,一切照旧。
从机断开连接后重连,如果是通过命令行配置从机,则从机会变回主机,需要重新配置成从机,然后一切照旧。
全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:master服务将新的所有收集到的修改命令一次传给slave,完成同步。
只要重新连接到master,就自动执行全量复制,数据一定会在从机中看到。
手动配置主机:
主机存在宕机的风险,可使用层层连接的结构,在正常情况下,Slaver1依旧只有从机的功能,并且如果Slaver1宕机,那么Master将无法增量复制给Slaver2;但当主机宕机,可让Slaver1从机变为主机,维持写操作。
如果原主机重新连接,就要重新配置
slaveof no one # 使自己变成主机
哨兵模式选举主机:
手动配置主机需要人工干预,费时费力,还会造成一段时间内服务不可用,更多时候优先考虑哨兵模式,开启一个独立的哨兵进程,监控Redis服务器(哨兵通过发送命令,等待服务器响应,从而监控多个Redis服务器),当主机宕机时,自动选举主机。
单个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统不会马上进行failover(故障转移)过程,仅仅是哨兵1主观认为主服务器不可用,这个现象被称为主观下线,当后面的哨兵也检测到主服务器不可用,并且达到一定数量时,那么哨兵之间会进行一次投票,投票的结果由一个哨兵发起,进行failover 操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程被称为客观下线。
创建哨兵配置文件sentinel.conf
vim sentinel.conf
配置哨兵配置文件
port 26379 # 端口 # sentinel monitor 主机名称 host port 1(票数) sentinel monitor myredis 127.0.0.1 6379 1
启动哨兵
redis-sentinel myconf/sentinel.conf
如果主机断开了,这个时候哨兵就会从从机中随机选择一个服务器,成为主机,原主机成为从机,原主机即使重连后依然是从机。
优点:
哨兵集群,基于主从复制模式,拥有所有主从复制的优点;
主从可以切换,故障可以转移,系统可用性更好;
从手动到自动,更加健壮。
缺点:
Redis无法在线扩容,集群容量一旦达到上限,在线扩容就会十分麻烦;
实现哨兵模式的配置很麻烦,里面有很多选择。
服务的高可用问题。
读的请求会先从Redis缓存中查询,如果缓存中没有就会从MySQL数据库中查询。
用户想要查询的数据,redis内存中都没有,也就是缓存没有命中,持久层数据库中也没有,于是本次查询失败。当缓存没有命中的用户很多时,持久层数据库的压力会很大,这就是缓存穿透。由于查不到导致的问题。
解决方案:
布隆过滤器
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而米面了底层存储系统的查询压力。
缓存空对象
如果从数据库中查询失败,那么将返回的空对象也缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据库。
问题1:缓存的空对象会大大增加存储空间;
问题2:即使对空对象设置过期时间,还是会存在缓存层和持久层,数据有一段时间窗口不一致,对需要保持一致性的业务有影响。
缓存击穿,是指一个key非常热点,再不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就会穿破缓存,直接请求数据库,就像是在一个屏障上凿开了一个洞。由于查询量太大导致的问题。
解决方案:
热点数据永不过期
从缓存层名看,没有设置过期时间,所以不会出现热点key过期后产生的问题。
加互斥锁
使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,对分布式锁的考验很大。
缓存雪崩,是指在某一个时间段,缓存集中过期失效或某个缓存服务器宕机,导致某个时间端集中创建Redis缓存。
解决方案:
Redis高可用
多增设几台redis服务器,扩大集群(异地多活)。
限流降级
在缓存失效后,通过加锁或队列来控制读数据库写缓存的线程数量。比如某个key只允许一个线程查询数据和写缓存,其他线程等待。
数据预热
数据加热的含义就是在征施部署之前,先把可能的数据预先访问一边,这样部分可能大量的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。