p1-3 企业架构演进
单机Mysql --> 多Mysql --> 主从复制,读写分离 --> 加入缓存 --> Mysql集群(数据分散在多个主从数据库集群中) --> NoSQL(解决各种爆发式增长的,不同的数据如定位,图片等,对MySQL数据库的压力)
p4-6 NoSQL
- 什么是NoSQL?
NoSQL是非关系型数据库(Mysql属于关系型数据库,按照表格存储数据,行,列),用来存储个人信息,社交网络,地理位置等不需要固定格式的数据。 比如使用Map<String, Object>数据类型。 - NoSQL特点
解耦,方便扩展,大数据高性能,数据类型多样
NoSQL四大存储数据方式及其对应的技术
- KV键值对
Redis属于这一种存储方式 - 列
HBase - 文档型数据库(BSON,和JSON一样)
MongoDB是基于分布式文件存储的数据库,是基于非关系型数据库和关系型数据库的中间产品,是非关系型数据库中功能最丰富最像关系型数据库的! - 图形数据库(社交关系)
存储图关系。Neo4j
对比:
其中Redis 、 HBase 、 MongoDB 、 Neo4j 是比较重要的。
p7 Redis
Redis (Remove Dictionary Server) 远程字典服务。免费开源的最热门NoSQL技术之一,支持持久化(rdb和aof),支持网络,Key-Value数据库,结构化数据库。
p9 安装Redis
可以直接通过docker安装,直接拉取redis镜像。docker的教程也可以看狂神的视频非常详细。
启动docker后用docker pull redis就可以拉取redis镜像,这里拉取镜像需要VPN并且可能会因为网络出现问题,多试几次或者使用阿里云镜像加速服务。
启动操作redis容器有多种方法:
首先需要在终端启动redis容器,执行redis-server启动命令,并打开redis持久化配置
docker run -d --name redis -p 6379:6379 redis-server --appendonly yes
通过上面的一步就以及开启了一个容器,内部的redis-server服务也开启了。
关于狂神视频中的redis配置文件如何在docker中配置可以看这篇博客:docker安装redis并以配置文件方式启动。注意创建/usr/local/docker/redis.conf需要用sudo获取权限。用挂载的配置文件启动的命令如下:
docker run -p 6379:6379 --name myredis -v /usr/local/docker/redis.conf:/etc/redis/redis.conf -v /usr/local/docker/data:/data -d redis redis-server /etc/redis/redis.conf --appendonly yes
参数说明:
- -d :后台运行
- –name myredis:服务名
- -p 6379:6379 : 将容器6379端口映射到主机6379端口(前面是主机后面是容器)
- redis-server /etc/redis/redis.conf --appendonly yes:在容器中使用/etc/redis/redis.conf 文件启动redis-server,并打开redis aof持久化配置
1 .在外部控制,不进入redis容器
你可以使用docker exec命令来进入容器,连接开启redis-cli,容器id可以通过docker ps查看;
docker exec -ti 你的容器id redis-cli # 后面也可以加-p xxxx 来连接对应的端口
也可以用 docker exec -it 你的容器id [COMMEND] 后面加入你想要输入的命令来操作redis。
2 .直接进入容器内部操作
可以通过可视化的docker界面选择运行中的容器进入,或者输入以下代码行进入。
docker exec -it 容器id /bin/sh
进入容器之后cd到目标的目录:
cd /
cd usr/local/bin
ls
接下来就可以尽情使用了
p10 测试redis
也有两种方法:
1.在外部调用命令
docker exec -it 94255e06d918 redis-benchmark -h localhost -p 6379 -c 100 -n 100000
其中redis-benchmark的参数(其他参数自行查阅手册):
- -h 主机地址
- -p 端口号
- -c 并发连接数
- -n 请求总数
2.进入容器内部调用(注意需要cd到目标目录)
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
p11
打开redis.conf,由于我们是挂载的redis.conf,直接看本机位置的redis.conf就可以了。
Redis基础知识
- redis默认有16个数据库,可以在redis.conf中修改。默认使用第0个数据库,可以在redis-cli中使用select [index]切换数据库。
- 查看数据库大小:dbsize
- 查看所有key: keys *
- 清空当前/所有数据库:flushdb/flushall
Redis是单线程的!
因为redis是基于内存操作的,其瓶颈不在于CPU而是内存。
为什么Redis是单线程的还那么快?
首先多线程不一定比单线程效率高,多线程涉及CPU上下文切换。
其次Redis是把所有数据放在内存中的!!!,内存系统没有上下文切换效率是最高的,所以单线程CPU多次读写都是在一个CPU上的,这是最快的。
p12 Redis其他基本命令
- 判断key是否存在,存在返回1,否则返回0:exists name
- 移动key-value,把name移动到1号数据库:move name 1
- 设置过期时间,设置name10秒过期:expire name 10,一般用于设置热点信息等。
- 查看过期时间:ttl name,返回值为正数则表示剩余秒数,返回值为-1表示没有过期时间,返回值为-2表示已经过期。
- 查看当前key的类型:type name
在实战中使用redis可以做单点登录。
其他命令在官网查看帮助文档
p13 String字符串类型详解
String相关命令:
- 上面使用的set和get命令就是字符串类型的设置和读取。
- 删除:del key [key]
- 追加(动态修改),在原有key对应的String后面添加字符串,如果当前key不存在则set,返回字符串长度:append name zww
- 获取字符串长度:strlen name
- 加1/减1 :incr views / decr views
- 加减指定数:incrby views 10 / decrby views 10
- 获取子字符串,若后面两个参数为0和3,则会取出包含0和3位置下的四个字符,若为0和-1表示所有字符:getrange key1 0 3 / getrange key1 0 -1
- 替换指定位置开始的字符串:setrange key1 6 zww
- 创建并设置过期时间:setex key2 30 "hello"
- 不存在则创建,存在则不做任何事并返回0,经常在分布式锁中使用 :setnx name lll
- 批量设置/获取:mset key1 v1 key2 v2 key3 v3 / mget key1 key2 key3
- 批量不存在再设置,如果有一个失败则全部失败(原子性操作):msetnx key1 v1 key2 v2 key3 v3
- 先get再set,不存在则返回null,如果存在则返回原来的值,并设置新的值,可以用来更新(类似于CAS中的比较并修改的操作):getset db redis
设置对象的用法(设置一个user1,其name=zhangsan,age=30):
方法一:设置user:1表示user1对象,其值为一个json字符串。
set user:1 {name:zhangsan,age:3}
方法二:用mset,为每个属性(域)user:{id}:{filed},设置一个key-value。
mset user:1:name zhangsan user:1:age:3
适用hash存储对象会更加合适,在下面会讲解到。
p14 List列表数据类型详解
所有list命令都是l开头的,可以存在重复的值。本质上是一个链表,如果链表中没有值则为空链表,当作不存在。
基本操作:
- 插入列表头部:lpush list one
- 插入列表尾部:rpush list right
- 获取区间的值:lrange list 0 -1 / lrange list 0 1
- 移除:lpop list one / rpop list right
- 通过下标获取某个值:lindex list 1
- 长度:llen list
- 移除指定的值,移除最后一个加入的one,可以同时移除多个:lrem list 1 one
- 截断(修建):ltrim list 1 2
- 更新指定下标的值,如果list不存在或者这个下标不存在则报错:lset list 1 one
- 插入到指定元素的前面或后面:linsert list one before/after xhp
组合操作:
- 移除list1最右边的数把它push到list2最左边,如果没有list2会先建立一个空的list2:rpoplpush list1 list2
可以用来做消息队列
p15 Set集合数据类型详解
set集合无序且不出现重复。
基本操作:
- 添加元素,返回1添加成功,返回0表示已经存在,添加失败:sadd myset xhp
- 获取所有元素:smembers myset
- 查询元素是否存在:sismember myset xhp
- 查询元素个数:scard myset
- 移除元素:srem myset xhp
- 随机抽选,后面可以加上个数:srandmember myset 2
- 随机移除:spop myset
- 移动元素:smove myset1 myset2 xhp
- 差集:sdiff key1 key2
- 交集:sinter key1 key2 # 可以用来做共同关注,推荐好友
- 并集:sunion key1 key2
p16 Hash哈希数据类型详解
本质上和String差不多,只是同时存下了key-vaule。
基本操作:
- 添加/获取/批量:hset myhash field1 xhp / hget myhash field1 / hmset / hmget
- 获取所有值:hgetall myhash
- 删除指定:hdel myhash field1
- 获取长度:hlen myhash
- 是否存在:hexist myhash field1
- 获得所有的field:hkeys myhash
- 获得所有value:hvals myhash
- 增加/减少:hincrby myhash field1 2 / hdecrby myhash field1 2
- 不存在则创建,存在则不做任何事并返回0 :hsetnx myhash field2 zww
hash可以用来存储变更的数据,尤其是用户信息的保存。
hash更适合存储对象,对应了对象的属性和值,比上面介绍的String存储对象要更加适用。
p17 Zset有序集合数据类型详解
Zset是有序集合,在set的基础上增加了一个优先级值score,1表示优先级最大。
基本操作:
- 添加:zadd myset 1 one 2 two 3 three
- 查询升序/降序:zrange / zrevrange myset 0 -1
- 根据score查询升序/降序:zrangebyscore / zrevrangebyscore key min max [withscores] [limit offset count]
其中min max表示查询的范围,withscores表示是否需要带优先级值输出。
比如我们把薪水的值作为优先级值存在zset中:
zrangebyscore salary -inf +inf withscores # 从小到大输出的所有元素,并输出score
- 移除元素:zrem salary zhangsan
- 个数:zcard salary
- 统计区间个数:zcount salary min max
Zset存储成绩表,工资表,普通消息-重要消息,排行榜应用,取Top N。
剩下的API查询官方文档即可。
p18 geospatial地理位置数据类型
经常应用在朋友定位,附近的人,打车距离计算。
geospatial通过经度纬度来定位,其底层是通过Zset实现的。
-
geoadd key longitude latitude member [longitude latitude member …]
往key中添加经度longitude,维度latitude 的成员member
比如:geoadd china:city 116.40 39.90 beijing
-
geopos key member [member …]
获取指定城市的经度和纬度 -
geodist key member1 member2 [m|km|ft|mi]
获取两地的距离,其中单位默认为米m,可以指定单位千米km,英里ft,英尺mi。 -
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
在key中根据当前位置和半径查找
其中longitude latitude为当前位置的经纬度;
radius为查找半径,m|km|ft|mi为单位;
[WITHCOORD] [WITHDIST] [WITHHASH]分别表示附带查询目标的经纬度、距离、哈希值;
[COUNT count]选出前几个;
[ASC|DESC]结果按照升序还是降序排列; -
georadiusbymember key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
在key中根据指定member和半径查找 -
geohash 基本不使用
由于geospatial底层是用Zset实现的,所以可以用Zset的所有操作,比如移除zrem,查询zrange等。
p19 Hyperloglog数据类型
hyperloglog是一个用于基数统计的数据结构,优点是内存非常小,但是会有一个小误差。比如网页的UV(User Views)一个人多次访问只能算一个人,而且允许一定的误差,可以用这个数据结构。
什么是基数?
基数是集合中不重复元素的个数。
基本操作:
- pfadd key element [element …] : 添加元素
- pfcount key [key …] : 查询数量
- pfmerge destkey sourcekey [sourcekey …] : 并集操作,把sourcekey中的元素都加到destkey中,destkey可以是空集合。
p20 Bitmap位图数据结构
Bitmap是一种位存储结构,都是操作二进制位来记录。
应用如下:统计用户信息,是否活跃,是否登录,每日打卡。比如一年打开情况365bit就可以存储了,365bits约等于46字节,非常省内存。
- setbit key offset value : 在key的下标offset的设置value值,只能设置0或1
- getbit key offset : 获取下标offset的值
- bitcount key [start end] : 统计区间1的个数
p21 Redis基本事务
事务的本质就是一组命令的集合,一个事务中的所有命令会被序列化,按顺序执行。
事务的执行具有一次性、顺序性、排他性。
关系型数据库支持事务的 ACID 原则:原子性、一致性、持久性、隔离性。
而Redis作为非关系型数据库其单条命令是保证原子性的,但是Redis事务是不保证原子性,并且没有隔离的概念。
Redis事务的操作:
- 开启事务(multi)
- 命令入队(…放入正常的命令…)
- 执行事务(exec)
- 放弃、取消事务(discard)
编译型异常,即一条命令本身有错误,那么事务中的所有命令都不会执行。
运行时异常,即出现类似1/0或者访问数组下标不存在这一类错误,只有出错的命令不执行,其他命令还是会执行成功,从这一点可以看出Redis事务是不保证原子性的。
p22 Redis实现乐观锁(面试常问)
Redis使用Watch监控来实现乐观锁
监视/加锁对象:watch key
解锁对象:unwatch
在开启事务之前watch需要监视的对象,如果在事务执行完成之前,有其他线程修改了监视的对象,则会执行失败,返回nil。
如果事务执行失败则需要先解锁,再重新加锁,再次执行。
p23-24 使用Jedis来操作Redis和事务
Jedis是java操作Redis的中间件(jar包)。
1、导入对应的依赖
<dependencies>
<!--导入Jedis包-->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!--fastjson-->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
2、编码测试
- 连接数据库:
public class TestPing {
public static void main(String[] args) {
// 1 new Jedis对象,传入主机地址和端口号即可
Jedis jedis = new Jedis("127.0.0.1", 6379);
// Jedis的所有命令都在这个对象里,Jedis的命令和之前学习的Redis命令完全一致
System.out.println(jedis.ping());
}
}
- 操作命令
- 断开连接 :jedis.close()
- 事务操作:
public class TestTX {
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", "xhp");
String result = jsonObject.toString();
// 开启事务
Transaction multi = jedis.multi();
try {
multi.set("user1", result);
multi.set("user2", result);
int i = 1/0; //java代码执行异常,抛出异常
multi.incrBy("user1",10); //这一句命令错误,但是不会有任何异常,正确的命令还会执行
multi.set("user3", result);
//执行事务
multi.exec();
} catch (Exception e) {
//捕获异常,放弃事务
multi.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
System.out.println(jedis.get("user3"));
//最终都要关闭
jedis.close();
}
}
}
p25 SpringBoot集成Redis
项目勾选默认三个开发工具、Spring Web和Spring Data Redis。
SpringBoot在2.2.x之后用lettuce替代Jedis来操作Redis。
Jedis和lettuce的区别:
Jedis:直接连接Redis数据库,多个线程操作不安全,需要用Jedis pool连接池来解决这个问题。(类似于BIO的模式)
lettuce:底层采用netty,实例可以在多个线程中共享,不存在线程不安全的情况。(类似NIO模式)
整合测试过程
1.导入依赖
2.配置连接
SpringBoot 所有配置类都有一个自动配置类,Redis的自动配置类为 RedisAutoConfiguration,对应的配置文件叫RedisProperties,其源码如下:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class) // 绑定了一个叫RedisProperties的配置文件
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // redisTemplate不存在时才会创建新的RedisTemplate,这意味着我们可以自己手动编写redisTemplate来进行覆盖
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// Redis对象都是要进行序列化的,序列化在 new RedisTemplate 中进行
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean #String时Redis的常用类型,单独提出来一个Bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
我们可以参照RedisProperties.class源码在application.properties中以spring.redis为前缀就可以对其进行配置了。比如:
#配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=0
spring.redis.lettuce.pool.max-active=8 #记得使用lettuce不要用jedis
3.测试!
首先在测试类中注入RedisTemplate,然后就可以使用redisTemplate中封装好的方法来进行Redis操作。(在之后的实际使用过程中,我们通常会自己编写RedisTemplate做一些增强的操作来使用,而不是使用默认的RedisTemplate)
class Redis02SpringbootApplicationTests {
//注入RedisTemplate
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate 操作不同的数据类型有不同的opsFor方法,其中的api指令是相同的
// opsForValue ->String
// opsForList ->List
// opsForSet
// opsForZSet
// opsForHash
// opsForHyperLogLog
// opsForGeo
// 还有Redis事务操作 discard mutil exec等
// // 获取Redis连接对象,进行Redis数据库的清空操作
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
redisTemplate.opsForValue().set("name","xhp");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
Redis对象的序列化
如果保存对象的情况需要实现对象的序列化,不然会报错。
p26 自定义RedisTemplate
1.创建RedisConfig.java类,内部编写自己的RedisTemplate,代码和所给的默认RedisTemplate一样,把@ConditionalOnMissingBean去掉即可,在中间添加上自己需要配置的代码。
2.比如配置具体的序列化方式。
下面的例子可以解决使用默认jdk序列化存储对象产生的乱码情况。
@Configuration
public class RedisConfig {
//编写自己的redisTemplate
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//自己配置序列化方式
//json序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key采用String序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value采用json序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value采用json序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet(); //把前面的序列化方式都set进Properties
return template;
}
}
在视频中不需要编写@Qualifier注解,因为默认的RedisTemplate编写了@ConditionalOnMissingBean注解。
3.企业级开发中会进一步封装一个RedisUtils类,内部有各种常用的操作可以自己使用。相当于自己的轮子,网上也有很多RedisUtils的现成代码可以提供参考。
p27 Redis配置文件解析
redis.conf 文件就是docker中挂载使用的文件,在启动redis时就需要调用这个配置文件。
这一部分狂神对配置文件里面的内容进行了讲解,具体如果需要修改使用的话再去翻看。
p28 Redis持久化 - RDB和AOF(面试工作重点!!!!!)
Redis是内存数据库,需要进行持久化操作。
RDB : Redis DataBase
- 工作原理:在指定时间间隔中,把内存中的数据集快照(Snapshot)写入磁盘中,恢复时把快照文件直接读取到内存中。
- 工作过程:Redis单独创建(fork)一个子进程来进行持久化操作,把持久化数据写入一个临时文件,持久化结束时用这个临时文件替换上次的持久化文件。
- 默认使用RDB作为持久化方式,它比AOF更高效,但是可能会在两次持久化之间丢失数据。
- RDB保存的持久化文件是dump.rdb
- RDB一般在主从复制中用来作为备用。
RDB触发持久化保存机制:
- save命令规则。可以在redis.conf中设置save规则,满足时会自动触发RDB。
- 执行flushall命令。
- 退出redis。
RDB恢复:只要把dump.rdb文件放在redis启动目录(/usr/local/bin)即可,redis启动时会自动检查dump.rdb中的数据。
优点:
- 适合大规模。因为fork了一个子进程,效率很高。
- 对数据完整性不高。因为最后一次持久化数据可能会丢失
缺点:
- 占用内存。子进程需要占用额外内存。
- 需要时间间隔,数据会丢失。
AOF : Append Only File
- 工作原理:把所有命令全部记录在文件中,恢复时把所有命令再执行一遍。
- 工作过程:fork子进程以日志形式记录所有写操作(读操作不记录),只允许在之前的基础上追加文件,也是 append only file 的由来。
- 默认不开启,需要手动修改配置文件开启(将appendonly改为 yes),两个都开启的方式下,优先使用AOF。
- AOF保存的持久化文件是appendonly.aof,如果这个文件被破坏修改,是启动不起来的,可以使用redis-check-aof --fix appendonly.aof进行修复。
- AOF是文件的无限追加,所以有一个重写机制来保证aof文件不会过大,默认64m时进行重写。
优点:可以每次修改都进行同步,安全性更高,但实际使用是每秒同步一次。
缺点:恢复速度慢,aof文件所占内存会很大。
p30 Redis发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者发布pub消息,订阅者接受sub消息。
完成发布订阅需要消息发布者、频道、消息接受者;
频道一般用消息队列来实现。
命令:
订阅端先订阅频道,然后就会监听等待读取信息;发送端发布消息到频道;订阅端就会接受到三条信息,分别是消息类型,频道和消息内容。
底层原理:维护一个字典,字典的键就是频道,字典的值是一个链表,链表内部存储所有订阅者的客户端。发布者发布消息使用给定的频道查找对应的订阅着链表进行遍历,并将消息发布给所有订阅者。
在实际场景中可以用来:
- 实时消息系统
- 实时群聊天室
- 发布订阅关注系统
在更复杂的场景中使用消息中间件(MQ)去完成更专业的功能。
p31 Redis主从复制(重点!!!)
主从复制,读写分离:将主服务器复制到几个从服务器上,主服务器负责写操作,其他从服务器负责读操作。用来解决服务器压力,架构中经常使用。因为80%的情况都是读操作。最低配置为一主二从。
docker伪集群搭建
多次启动镜像,并映射到不同的端口号即可,这里为了分开不同的服务器还需要为每个redis库分配不同的ip地址,并且设置bind 0.0.0.0来让所有IP都能通信。
docker network create --subnet=172.60.0.0/16 mynetwork #在这里为了方便实验,单独创建一个bridge网桥,分配172.60.0网段
docker run -d --name redis-master --net=mynetwork -p 6380:6379 --ip 172.60.0.2 -v /usr/local/docker/redis.conf:/etc/redis/redis.conf -v /usr/local/docker/data:/data redis redis-server /etc/redis/redis.conf --bind 0.0.0.0
docker run -d --name redis-slave1 --net=mynetwork -p 6381:6379 --ip 172.60.0.3 -v /usr/local/docker/redis.conf:/etc/redis/redis.conf -v /usr/local/docker/data:/data redis redis-server /etc/redis/redis.conf --bind 0.0.0.0
docker run -d --name redis-slave2 --net=mynetwork -p 6382:6379 --ip 172.60.0.4 -v /usr/local/docker/redis.conf:/etc/redis/redis.conf -v /usr/local/docker/data:/data redis redis-server /etc/redis/redis.conf --bind 0.0.0.0
查看当前库的信息(是主库还是从库等):info replication
默认情况下启动的redis库都是主节点。我们只需要配置从节点,标记他的主节点即可。
slaveof host port
# 这里我们分别在不同的从库上输入主库的ip地址和端口号即完成主从复制
slaveof 172.60.0.2 6379
之后就可以用info replication查看信息,并且进行set get操作:
主机可以写,但是从机只能进行读操作
测试
- 主机断开宕机了,那么主机就不能再写数据,但是从机依然可以运行读工作。如果主机之后恢复连接,不需要额外的操作,仍可以进行主从复制。
- 从机断开,再重新启动,那么这时候就不再有主从关系,这个从机又变成自己的主机了,如果手动再设置为一个从机,则可以立即获得目标主机的所有值。这是通过一个同步操作,在第一次连接时主机启动一个进程,将所有数据都同步到slave中(全量复制)。
可以通过 slaveof no one 来让从机变回主机。
p33 哨兵模式(面试工作重点!!!!!)
作用:在主机故障宕机时,从机可以根据投票数自动将从库转换为主库,不再需要人工干预。
原理:哨兵是一个独立的进程,其原理是通过发送命令等待redis服务器响应,如果没有响应则说明服务器发生故障,进行相应的操作。 为了应对哨兵故障的可能,还使用了多哨兵模式,哨兵之间相互监控。
操作过程:主服务器宕机后,哨兵1检测到,则这时主服务器主观下线,哨兵1通知其他哨兵检查,如果多个哨兵都认为主服务器下线,则一个哨兵发起故障转移(failover)投票,选出投票最高的从机作为新主机,这个过程为客观下线。
配置开启哨兵模式
-
创建配置文件sentinel.conf,写入对应配置。(网上有很多配置文件可以参考)最重要的命令如下:
-
在usr/local/bin目录下使用上面配置的文件开启哨兵进程
如果主机断开,从机晋升为主机之后,主机回来了,那么主机就会被降级为从机。
优点:
- 所有主从复制的优点他都有。
- 可以在故障模式下完成自动的转移,系统可用性高。
缺点:
- Redis不能在线扩容
- 哨兵模式的配置实现比较复杂
p35 缓存穿透、击穿和雪崩(面试高频,工作常用!!!!!!!!!!!)
一、缓存穿透(查不到)
概念:
用户查询数据在缓存中没有找到,在MySQL中也没有找到,那么本次查询失败。如果同时有很多用户发送这样的请求,则缓存没有起到为MySQL分担压力的作用,最后导致数据库崩溃,这就叫缓存穿透。
解决方案:
布隆过滤器:是一种数据结构,通过算法将无效请求直接丢弃。内部以hash的形式存储查询参数,在控制层先校验。
缓存空对象:如果没有命中则把空对象存储在缓存中,并设置过期时间。
存在两个问题:1.需要额外的空间存储空对象。2.空对象有过期时间,在过期时间内导致数据不一致问题。
二、缓存击穿(量太大,缓存一个key过期)
概念:
大并发请求在一个热点数据上,这个热点数据由于有过期时间,在失效恢复时间间隔的一瞬间,大并发直接请求在底层数据库上,导致服务器宕机。
解决方案:
热点数据不过期
加分布式互斥锁
三、缓存雪崩(很多key过期失效)
概念:
在一个时间段内,很多key失效,但是这些key还是有很多请求访问,这时候MySQL服务器压力又会过大,导致宕机。
解决方案:
redis高可用:使用多个redis集群,异地多活。
限流降级:在缓存失效之后,加锁或者队列来限制请求线程的数量,或者停掉一些不重要的服务。
数据预热:在即将发生大访问并发之前,手动把预测的热点数据放入缓存中,并根据情况设置不同的过期时间来符合预期要求。