NoSql
RDBMS和NoSql
传统的RDBMS
- 结构化组织
- SQL
- 数据和关系存在单独的表
- 操作远,数据定义语言
- 严格的一致性
- 基础的事务
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高扩展
大数据时代的3V:1.海量Volume,2.多样Variety,3.实时Velocity
大数据时代的3高:高并发、高可扩、高性能
Redis
Re mote Dictionary Server
干什么?
内存存储,持久化,内存时断电即失,所以持久化很重要(rdb、aof)
效率高可以用高速缓存
发布订阅系统
地图信息分析
计时器、计数器
特性:
1.多样的数据类型
2.持久化
3.集群
4.事务
Redis命令
保证有redis-benchmark
redis-server (配置文件位置)
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
常见命令
# 查看数据库
DEBSIZE
# 设置值,得到值
set name qingjiu
get name
# 得到所有的key
keys *
# 删除当前的数据库所有的值
flushdb
# redis有16数据库,默认为0,可以切换数据库,切换数据库
select 0
# 清除所有的数据库内容
FlushAll
# 是否存在某个key,存在返回1(Integer类型):EXISTS key
EXISTS name
# 移动move key databaseIndex
move name 1
# 设置过期时间EXPIRE key seconds
EXPIRE name 50000
# 获得redis的key的过期时间ttl key
ttl name
# 获得类型type name
type key
# SETEX (SET WITH EXPIRE) 设置过期时间
# SETNX (SET IF NOT EXPIRE) 不存在设置(在分布式锁中常用),存在则失败
SETEX key 30 "xxx"
SETNX key value
# 批量设置获取MSET,MGET
# MSET KEY1 VALUE1 KEY2 VALUE2...
# MGET KEY1 KEY2
MSET KEY1 VALUE1 KEY2 VALUE2
MGET KEY1 KEY2
# 对象设置东西,第一个JSON数据表示,第二的用key
MSET user:1 {name:zhangshan,age:3}
MSET user:1:name:zhangshan user:1:name:age:3
# GETSET得到在设置,GETSET KEY VALUE。
GETSET KEY VALUE
# 易错点
#MSETNX KEY1 VALUE1....如果中间有任何一个存在,所有都失败(原子操作)
Redis是单线程(6.0之后版本可以开启多线程)
主要瓶颈在于网络带宽和机器内存,所以使用单线程
核心:
redis所有数据在内存使用单线程是效率最高的,没有上下文切换
五大数据类型
key,value是区分大小写的
String(字符串)
#添加字符串,如果key不存在,相当于set key
append key "xxx"
#得到字符串长度
STRLEN key
# 自增一
incr key
# 自减一
decr key
# 步长命令,类似于i += 50:INCRBY/DECRBY key number
INCRBY key 50
DECRBY key 50
# 字符串范围 GETRANGE KEY START END,[START,END]。如果end是-1则是全部查出来
getrange key 1 5
# 替换 SETRANGE KEY OFFSET VALUE,OFFSET指的是偏移了多少。下标是OFFSET开始修改
SETRANGE key 1 xxx
List
基本上redis命令是所有list都会加L,这里下标是从0开始的
# LPUSH\RPUSH KEY VALUE1 VALUE2...(左边插入\右边插入)
lpush list value
# LRANGE KEY START STOP(STOP==-1那么是全部输出,左边下标是0.START是负数,语句就是LRANGE KEY 0 END)
lrange list 0 -1
# LPOP\RPOP KEY(左边移除、右边移除)
lpop list
# LINDEX KEY INDEX(通过下标获得值)
lindx list 0
# LLEN KEY(获得长度)
Llen list
# LREM KEY COUNT VALUE(移除COUNT数量的VALUE值,LIST中VALUE值是可以多个)
# LTRIM KEY START STOP(截取[START,STOP]的所有的值)
# RPOPLPUSH KEY OTHERKEY(RIGHT POP LEFT PUSH,右边弹出并且加入新的OTHERKEY的里面,如果OTHERKEY不在会创建)
# LSET KEY INDEX VALUE(KEY必须存在,INDEX是负数则从右边开始,左边从0开始。但是负数最大是长度负数,正数时最大为长度-1)
# LINSERT KEY BEFORE|AFTER PIVOT VALUE(在PIVOT前面或者后面加入VALUE值)
- 这个实际时链表,key不存在,创建。如果存在则添加。
-
如果移除所有值,空列表。也代表没了
-
可以变为消息队列和栈
SET
-
不可以重复
-
常见的命令
# SADD KEY MEMBER # SMEMBERS KEY (查看所有的的值) # SISMEMBER KEY MEMBER(判断MEMBER是不是在这里面,是则返回一,否则为0) # SCARD KEY(得到所有的元素个数) # SREM KEY MEMBER MEMBER1...(删除东西) # SRANDMEMBER KEY [COUNT](随机取出count个成员,count可以省略,则默认为1) # SPOP KEY [COUNT](随机弹出去一个值) # SMOVE SOURCE DESTINATION MEMBER(从源移动到目的地) # SDIFF\SINTER\SUNINON KEY KEY1 KEY...(KEY的差集、交集、并集)
hash
Map集合,key-map时候这个值是一个map集合!本质和String类型没有太大区别,一个简单的key-value值
#hset key field value [field value...] 设置值
#hmset key field value [field value...] 设置多个东西
#hget key field value [field value...] 得到值
#hmget key field value [field value...] 得到多个值
#hgetall key //得到所有field和value
#hdel key field [field...] 删除field
#hlen 长度
#hexists key field 判断是否存在
#hkeys key 得到所有key
#hvals key 得到所有value
#HINCRBY KEY FIELD INCREMENT
HINCRBY KEY FIELD -1
#HSETNX KEY FIELD VALUE 不存在可以设置,否则失败返回0
HSET user:1 name "张三";
Zset(有序集合)
# 添加
# ZADD KEY SCORE FIELD [SCORE FIELD...]
ZADD KEY 1 ONE 2 TWO 3 THREE
# 查看范围的值
# ZRANGE KEY START STOP
ZRANGE KEY 0 -1
# 排序
# ZRANGEBYSCORE KEY MIN MAX [WITHSCORES] [LIMIT OFFSET COUNT]
# ZREVRANGE KEY START STOP [WITHSCORES] 从大到小
# -INF +INF最小最大,[WITHSCORES] 是不是带上分数
ZRANGEBYSCORE KEY -INF +INF WITHSCORES 50
# 移除
# ZREM KEY MEMBER [MEMBER...]
# ZCARD KEY 获取个数
# ZCOUNT KEY MIN MAX 获得key的分数在min和max之间的
三种特殊的数据类型
geosptial
底层是zset设计的,zet命令可以使用
# geoadd key longitude latitude member [longitude latitude member...]添加
# 南北两极无法直接添加,有效的经度:-180度到180度,有效的维度在-85.05到85.05度,建议java程序一次加入
# GEOPOS KEY MEMBER [MEMBER....]
# GEODIST KEY MEMBER1 MEMBER2 [m/km/ft/mi]
# 没有指定单位默认为m,四个单位:m->米,km->千米,mi->英里,ft->英尺
# 找出指定坐标的周围元素
# GEORADIUS KEY LONGITUDE LATITUDE RADIUS m|km|ft|mi [WITHCOORD] [WITHDIST] [COUNT NUMBER]
# 查看距离,返回11个字符串。二维的字符串变为一维的字符串
# GEOHASH KEY MEMBER1 MEMBER2
Hyperloglog
基数: 不重复的元素,可以接受误差
统计访问次数:一个人可以访问很多次,但是只能算作一个人
传统方式是用set保存ID:但是高并发,有点误差。而且很多ID导致内存消耗太多了
# PFADD KEY ELEMENT [ELEMENT...] 创建
# PFCOUNT KEY 统计
# PFMERGE DESTKEY SOURCEKEY [SOURCEKEY...] 合并
Bitmaps
位存储
适用于0和1状态
# SETBIT KEY OFFSET VALUE
# GETBIT KEY OFFSET VALUE
# BITCOUNT KEY [START END] 统计打卡
事务
ACID A:原子性 C:一致性 I:隔离性 D:持久性
一组命令的集合,命令被序列化,在事务执行过程中,会按照顺序执行
一次性、顺序性、排他性!执行命令
Redis事务没有隔离级别概念
故所有命令,在事务中没有被执行!只有发起执行命令才开始执行
redis事务:
- 开启事务:MULTI
- 命令
- 执行事务(EXEC)
MULTI
[SET KEY VALUE.....]
EXEC
# DISCARD 放弃事务,事务的
编译异常:代码有错,所有命令不会执行
运行异常:其他命令继续执行
监控
悲观锁
- 认为什么时候都会出问题,都要加锁
乐观锁
- 认为什么时候都不会有问题,所以不会上锁!更新数据时回去判断是不是有人在这之间修改了数据
# watch
# 注意:这里遇到事务的时候会自动解锁
# When EXEC is called, all keys are UNWATCHed, regardless of whether the transaction was aborted or not. Also when a client connection is closed, everything gets UNWATCHed.
Jedis
Redis时官方推荐的JAVA的连接开发工具!
如何使用Jedis
导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
创建对象
public class example {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.56.10",6379);
//Jedis 所有的命令都是我们之前学习所有的命令!
System.out.println(jedis.ping());
}
}
整合Spring Boot
Jedis在springBoot 2.x被lettuce替换了
jedis:采用直连,多个线程操作是不安全的。如果需要避免不安全,就要jedis pool连接池。更像BIO模型
lettuce:采用netty,实例可以在多个线程池共享,不存在线程安全。更像NIO模型(推荐使用lettuce,因为jedis很多包没有导入)
-
导包
-
配置
-
private RedisTemplate redis;
-
上述命令就是函数,有点变化。
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
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序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
写的Util
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
Redis配置
启动时候用配置文件来启动
单位
不区分大小写
include
包含文件路径
网络
bind 127.0.0.1 #绑定端口
protect-mode yes #保护模式
port 6379 #端口
通用配置
daemonize yes #以守护进程开启运行
pidfile /var/run/redis_6379.pid #如果以后台运行那么需要指定一个pid文件
loglevel notice #日志级别,还有debug,verbose(和bug级别很像),notice(生产环境),warning
logfile "" #日志文件名
database 16
always-show-logo yes #就是启动的时候的logo要不要
快照
持久化,在规定的时间内,执行了多少的操作,那么就会持久化到文件 .rdb .aof
redis是内存数据库,没有持久化,那么断电就会消失
#如果900s内,至少一个key修改就会持久化
save 900 1
#持久化如果出错,是否继续工作
stop-writes-on-bgsave-error yes
# 是否压缩rdb文件,消耗CPU资源
rdbchecksum yes
# rdb文件检测校验
rdbchecksum yes
# 持久化目录
dir ./
复制
安全
可以设置密码
config get requirepass
config get requirepass "xxxx"
auth 123456 #登入
客户端
maxclients 10000 #最大客户端
maxmemory <bytes> #redis设置最大内存
maxmemory-policy noeviction #内存达到处理策略
###
# noeviction:不删除策略,达到最大内存限制时,如果需要更多内存,直接返回错误信息。
# allkeys-lru:所有key通用,优先删除最近最少使用的(less recently used,LRU)key.
# volatile-lru: lru的算法删除过期的key
# allkeys-random:所有key通用,随机删除一部分key.
# volatile-random:只限于设置了expire的部分,删除一部分超时的key.
# volatile-ttl:只限于设置了expire的部分,优先删除剩余时间(time to live,TTL)短的key.
###
aof配置
appendonly no #默认不开启aof模式的使用rdb方式持久化,rdb是够用的
appendfilename "" #持久化文件名字
appendfsync everysec #每秒执行一次,宕机就会少了这一秒数据
# appendfsync always(no)
持久化
RDB(Redis DataBase)
父进程fork子进程,父进程继续做原来的事情但是子进程开始把数据写入临时文件,然后替代原来的临时文件。但是最后一次宕机了,就没有。
文件是:dump.rdb
触发机制
- save规则成立
- 执行flushall命令
- 退出redis,shutdown
恢复RDB文件
# 查看文件位置,如果过在目录中有就会自动扫描加入
config get dir
优点
- 适合大规模的数据恢复
- 对数据完整性不高
缺点
- redis意外宕机了,之后的修改就没了
- fork进程,占有一部分内存
AOF(Append Only File)
将我们所有写命令记录下来,保存文件appendOnly.aof
appendonly no #是否开启
no-appendfsync-on-rewrite no #AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载(尤其是硬盘),但是可能会丢失AOF重写期间的数据;需要在负载和安全性之间进行平衡
auto-aof-rewrite-percentage 100 #重写的规则时:一百重写,过期的数据丢了
auto-aof-rewrite-min-size 64mb #文件重写触发提交之一
appendfsync everysec #同步时间,上面参数一致
aof文件出错了可以修复
redis-check-aof --fix appendonly.aof
优点
- 每次修改都同步那么就是文件完整性最好的
- 每一秒同步,可能丢失一秒的数据
- 不同步那么就是最快的
缺点
- aof文件远远大于dump文件,修复慢
- 效率慢
主从复制
将一台主节点数据复制到其他节点上。单向的。
读写分离,读的方法交给从节点,写交给主节点。最低标准一主二从
info replication # 查看信息
伪集群
修改配置
修改配置信息
- 端口
- pid名字
- dump名字
- 日志名字
redis-server kconfig/文件名
启动,默认是都是主节点
认老大
-
命令
SLAVEOF HOST PORT SLAVEOF no one #变回主节点
-
配置文件
replicaof <masterip> <masterport> masterauth <masterpassword>
细节
- 从节点只负责读,主节点只负责写
- 主节点宕机了,没有配哨兵模式的话,所有结点只有读操作。主节点回来了就可以有写操作了
- 从节点如果是命令行设置从机,那么重启不会连接回去,但是连接回去了就可以拿到主节点数据
复制细节
全量复制:从节点会发送请求sync请求,主节点就会把所有数据发过去
增量复制:之后主节点修改命令,从节点接受一部分数据就好了
二种连接
- 所有的连一个主节点
- 像链表一样 ()
哨兵模式
由于手动费时费力,故有了哨兵模式
有一堆哨兵监视所有的redis,都会给这些redis发消息,判断是不是下线。如果一个不回话,其他哨兵就会都去发这个消息,达到一定数量哨兵认为这个宕机了,就会选出一个主机
-
配置文件sentinel.conf
# sentinel monitor 名称 IP地址 端口 最后一个代表多少个哨兵认为这个下线了就是宕机了 sentinel monitor myredis 127.0.0.1 6379 1
-
启动哨兵
redis-sentinel kconfig/sentinel.conf
细节
主机回来了只能当从机器
- 优点
- 高可用性
- 主从模式升级,手动变为自动
- 主从复制,优点都有
- 缺点
- 集群达到上限,扩容十分麻烦
- 配置
配置项
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379;如果有哨兵集群,我们还需要配置每个哨兵的端口
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
#这个数字越小,完成failover所需的时间就越长,
# 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。
# 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
#3.当想要取消一个正在进行的failover所需要的时间。
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
#对于脚本的运行结果有以下规则:
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
#这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
#一个是事件的类型,
#一个是事件的描述。
#如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
#通知脚本
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是“failover”,
# <role>是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
缓存穿透和雪崩
缓存穿透
缓存没有这个数据,就会访问数据库发现也没有,返回失败。但是访问很多出现这个问题
方法一
- 布隆过滤器(hash查找值,过滤掉一些东西)
- 缓存空对象,将一个不存在的值放在缓存中
- 但是可能会导致一段时间窗口不一致
- 浪费内存
缓存穿透
比如消失为60s,但是恢复在60.01s恢复。但是访问流量过大凿开了一个洞。导致宕机
- 设置永不过期,浪费空间
- 加互斥锁,加锁保证一个过去查找数据库
雪崩
缓存集体失效
- redis多个
- 限制流量
- 数据预热,将可能数据加上到缓存。设置过期均匀一点