nosql讲解,非关系型数据库
阿里巴巴架构演进
nosql数据模型
Nosql四大分类
CAP
BASE
Redis入门
五大基本类型
三种特殊数据类型
Redis配置文件如何读取
Redis持久化
Redis的一些事务操作
Redis实现订阅发布
Redis主从复制
缓存穿透,以及解决方式
缓存雪崩,以及雪崩方式,微服务一环套一环,可能会导致雪崩
基础API之jedis详情
SpringBoot集成Redis操作
Redis的实践分析
大数据时代,一般的数据库无法进行分析处理了Hadoop
单机SQL时代,更多使用静态网页
出现这三种情况,就需要升级,但是如果没有这么大的数据量,应该从单机开始
Memcached缓存+MySQL+垂直拆分(读写分离)
网站百80%的操作都是在读,减轻数据库的压力可以使用缓存提高效率,缓存使用什么技术都没所谓
分库分表+水平拆分+M有SQL集群
使用缓存解决了读的问题,用分集群的方式解决读的问题,数据存在不同的库中
早些年,MySAM使用 表锁,读取一行要将整个表锁起来,十分影响效率,高并发下会出现严重问题
后来转为innodb 行锁
慢慢的就开始使用分库分表来解决写的压力,将表拆分,不同的业务使用不同表,存放在不同的数据库中,不同的业务使用单独的数据库,结合微服务,mysql推出了表分区,但是很少公司使用
Mysql的集群已经满足了那个年代的需求
最近的年代
定位也是一种数据,Mysql的关系型数据库已经不够用了,数据量多,变化快,
非关系型数据库
放在缓存中一段时间之后再进行持久化的操作,来保证效率和安全
存储很大的文件,会导致数据表很大效率会变低, 如果有一种专门的数据库来处理这种数据Mysql压力就会变得十分小,研究如何处理这些问题
如果数据量变大,要在对数据库进行更改,增加一行是很难的
用户先访问企业防火墙,到负载均衡的主机,到App服务器,到mysql实例,然后是独立功能的服务器
用户的个人信息、社交网络、地理位置、用户自己生产的数据、用户日志等等数据爆发式的生长,
NoSQL=Not Only SQL 不仅仅是SQL
泛指非关系型数据库,Web2.0的诞生,传统的关系型数据库很难对付Web2.0时代,尤其是超大规模的高并发的社区,
NoSQL在大数据环境下发展十分迅速
要存储的数据不是固定格式,以键值对来控制,Map< String , Object >,数据之间没有关系就很好扩展,Java现在要做面向接口编程也是为了解耦,
使用Redis也是为了高性能,官方的数据读取速度每秒11w次,写8w次
数据类型是多样性的,不需要事先设计数据库,不需要设计键值对,随取随用
传统的RDBMS和NoSQL
传统的RDBMS
NoSQL
大数据时代的3V和3高
大数据时代的3V,主要是描述问题的
大数据时代的3高,主要是对程序的要求
真正在公司中实践一定是:NoSQL+RDBMS一起使用才是最好的
阿里巴巴框架演进
敏捷开发
极限编程
开放,提升网站的开放性,吸引第三方开发者加入网站的建设
体验,网站并发压力快速增长,用户对体验提出了更高的要求
使用了各种数据库,这么多种类型的数据库导致数据架构非常复杂,要简化架构,增加一层就行,像是jdbc一样,
商品中的信息存在不同的数据库中
商品的基本信息
名称、价格、商家信息:
商家的描述、评论(文字比较多)
图片
商品的关键字
搜索引擎
商品热门的波段信息
商品的交易,外部支付接口
大型互联网应用问题:
阿里的解决方案
统一数据服务层UDSL,在网站应用集群和底层数据源之间,构建一层代理,统一数据层
模型数据映射
统一的查询和更新API
设计了一套统一的DSL,提供了统一的增删改查的API,开发速度问题是解决了,但是性能还是问题
网站数据庞大,只能缓存热点数据,解决方案,开发热点缓存平台,提供UDSL作为缓存系统
以上都是NoSQL入门概述
KV键值对
文档型数据库(Bson格式和Json格式)
列存储数据库
图形关系数据库
Redis (Remote Dictionary Server),远程字典服务
开源、使用C语言编写,支持网络、基于内存可持久化的日志型,Key-Value数据库,提供多种语言的API,可以用多种语言调用 ,NoSQL技术之一,也被称之为结构化数据库之一
读的速度是11w,写的速度是8w
Redis能干嘛
特性
常用网站
安装Redis
brew install redis
//安装redis
brew info redis
//查看软件详细信息,以来关系,注意事项等
brew list redis
安装包所在的位置
启动Redis
redis-server
启动之后不要关闭
连接测试
redis-cli
默认端口是6379
基本操作
Redis推荐使用Linux开发
好吧这里加入了Linux的知识,要开始学习Linux
退出Redis
redis-cli shutdown
netstat -lnpt |grep 6379
//查看6379=端口占用情况,netstat CentOS不自带,需要另外安装
yum install -y net-tools
kill -9 [PID]
//结束对应PID进程
ln -s /usr/local/redis/bin/redis-cli /usr/bin/redis
//创建链接,使之可以直接使用/bin之中的命令,这创建应该回到~目录进行创建,称为命令软链接
cp /usr/local/redis-5.0.3/redis.conf /usr/local/redis/bin/
//复制操作
make install PREFIX=/usr/local/redis
//安装到指定目录
tar -zxvf redis-5.0.3.tar.gz
//解压
wget http://download.redis.io/releases/redis-5.0.3.tar.gz
//通过链接下载
不同版本的redis有不同的操作,选择高版本的redis,基本就只是解压,安装,选择配置文件启动
make install
make uninstall
在/usr/local/bin 下有很多工具
benchmark压力测试工具,官方自带的性能测试工具
测试100个并发,每个并发100000个请求
redis-benchmark -p 6379 -c 100 -n 10000
不加-h 就默认本机
解读测试数据
基础知识
Redis是单线程的,Redis是基于内存操作的,CPU不是Redis的瓶颈,根据机器的内存和网络带宽的,可以用单线程实现
每秒10w+的QPS,完全不比使用key-value的Memecache差
单线程Redis
Redis是内存中的数据结构存储系统,可以作用数据库、缓存、消息中间MQ
支持多种数据类型
Redis内置了主从复制replication、LUAscripting脚本,LRU 驱动事件,transactions事务,和不同级别的磁盘持久化persistence
通过Redis哨兵Sentinel,和自动分区Cluster,提供高可用性high availability
Redis
redis-server &
#后台启动
redis-server /etc/local/bin/redis.conf
#指定文件启动
-p
#指定端口启动
append 追加value
追加不存在的key会set key
strlen 查看value长度
incr xc 自增1,可以用作增加浏览量increase
decr xc 递减1
incrby 能设置自增量的自增 incrby xc 12 decrby xc 12
getrange 截取范围,下标从0开始 getrange xc 0 2 取xc第一到第三的数 getrange xc 0 -1 全部
setrange 修改范围的值 setrange xc 1 22 123456变122456
setex (set wth expire) 设置key时顺便设置生存时间 setex xc aaa 30
setnx ( set if not exist )参数不存在就设置,在分布式锁经常使用,如果存在就创建失败.
setex xc aa
mset 批量设置,空格间隔 mset xc1 1 xc2 2 xc3 3
mget mget xc1 xc2 xc3
msetnx 原子性操作,批量设置,要么都成功或失败,nx如果存在就创建失败
对象操作
设置一个对象,值为json字符串来保存一个对象
user:{id}:{filed}
getset 先get再set,规则就按getset来,无论key存不存在,都按getset来
数据结构是相同的
String类似的使用场景:value除了是外面的字符串还可以是我们的数字
在Redis中可以将List作为、栈、队列、阻塞队列
消息排队、消息队列、堆、栈
集合中的值不能重复,set是无需不重复原则
string和list的元素都是value,set中是member
map集合,key - map 本质和string类型没有太大区别,还是一个简单的key - value,像是增加了一层
做用户信息保存,比用string类型好点,存储经常变动的信息,hash更适合存储对象,
在set的基础上增加了一个值
可以作为一个排行榜功能,进场刷新,或者任务等级排序之类的,都可以做
可以推算地理位置的信息,两地之间的距离,方圆几公里之内的人
需要注意:
查看官网,一共有六个相关命令
纬度经度,member名称,geoadd可以一次添加多个
可以使用geopos读取地理位置
geodist,输出两地距离,加上unit单位,设置输出距离单位
m、km、mi 英里、ft 英尺
我附近的人功能,通过半径来查询,获取附近的人的定位地址
georadius命令来实现,longitude纬度、latitude经度、radius半径 单位,输入查询位置的经纬度就能从key集合中找到在半径范围的元素
后面跟了几个参数,withdist、withcoord、withhash、asc、desc、count
Withdist:返回元素位置的同时,把与中心之间相差的距离一同返回
withcoord : 将元素经纬度一同返回
是一种概率数据结构,计数唯一事物,从技术上讲估计一个集合的基数,通常计数唯一项需要使用成比例的内存,因为需要记录使用过的元素,以免多次记录,但是hyperloglog的算法可以用内存换精度,虽然有误差,但是误差小于1%,算法的神奇之处在于只需要很小的内存,最大也不超过12k,类似集合的功能,能记录2^64的计数,从内存的角度来说,hyperloglog是首选
能用在网页的UV
统计用户信息,活跃与不活跃,登录未登录只有两种状态的数据,可以使用BitMaps
可以用作打卡功能实现,到达一定数目之后进行统计,判断预期数目与统计得出的数目是否达到预期
setbit 中的offset是偏移量,可以看作下标,value只能是0或1
getbit
bitcount 统计key offset 为1的个数 bitcount sign
bitpos 查看 key offset 为0或1的位置,并且可以设置range
bitop 对一个或多个保存二进制位的字符串key进行位元操作,将结果保存在deskkey上
and、or、not、xor
除了not之外,其他都能加入多个key进行运算
Redis事务的本质是一组命令的集合,一个事务中所有命令都被序列化,在事务执行过程中,会按照顺序执行!
一次性、顺序性、排他性,一次执行多个指令,其他客户端提交的命令请求不会插入到事务执行命令序列中
单条命令是原子性执行的,但事务不保证原子性,且没有回滚,事务中任意命令执行失败,其余命令仍会被执行
乐观锁应该适用于读多写少的情况,悲观锁应该适用于写多读少的情况
官方推荐的操作Redis的中间件,SpringBoot已经有整合RedisTemplate
redis.clients
jedis
3.3.0
测试
package com.xc;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
/**
* 星晨
*/
public class TestPing {
public static void main(String[] args) {
//1.new Jedis对象即可
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());//连接成功打印PONG
jedis.flushDB();//清空当前数据
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","xingchen");
//开启事务
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();//关闭连接
}
}
}
SpringBoot操作数据:是封装在Spring-data中的,jpa、jdbc、mongodb、redis
在SpringBoot2.x以后与原来使用的jedis被替换成来看lettuce,底层已经不使用jedis了
org.springframework.boot
spring-boot-starter-data-redis
SpringBoot所有配置类,都会有一个自动配置类
自动配置类都会绑定一个properties配置文件
RedisAutoConfiguation
启动配置类中有一个RedisProperties配置类
里面有很多以前缀spring.redis开头的配置,可以在application中配置
如host、password、、配置
RedisAutoConfiguation中封装了两个Bean
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate
没有过多的设置,Redis的对象都是需要序列化的
两个泛型都是object,后面使用需要强制转换
靠自己重写config来替换这个template
StringRedisTamplate
@ConditionalOnMissingBean(name = "redisTemplate")
//重写一个redisTemplate就能替换掉这个bean
org.springframework.boot
spring-boot-starter-data-redis
# 配置Redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
package com.xc;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class Redis02SpringBootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//redisTemplate 操作不同的数据类型,api和我们的指令是一样的
//opsForValue 操作字符串 类似String
//opsForList 操作List 类似List
//opsForSet
//opsForHash
//opsForZSet
//opsForGeo
//opsForHyperLogLog
//除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD
//获取redis的连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
redisTemplate.opsForValue().set("mykey","xingchen");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
}
package com.xc.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time, TimeUnit timeUnit) {
try {
if (time > 0) {
redisTemplate.expire(key, time, timeUnit);
}
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((Collection) 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 hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map 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 map, long time, TimeUnit timeUnit) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time, timeUnit);
}
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, TimeUnit timeUnit) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time, timeUnit);
}
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 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, TimeUnit timeUnit, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time, timeUnit);
}
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 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,TimeUnit timeUnit) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time,timeUnit);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List 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 value, long time,TimeUnit timeUnit) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time,timeUnit);
}
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;
}
}
// ===============================HyperLogLog=================================
public long pfadd(String key, String value) {
return redisTemplate.opsForHyperLogLog().add(key, value);
}
public long pfcount(String key) {
return redisTemplate.opsForHyperLogLog().size(key);
}
public void pfremove(String key) {
redisTemplate.opsForHyperLogLog().delete(key);
}
public void pfmerge(String key1, String key2) {
redisTemplate.opsForHyperLogLog().union(key1, key2);
}
}
持久化RDB、AOF,重点
Redis是内存数据库,断电即失去,只要是内存数据库就一定会有持久化操作
在指定的时间 间隔内将内存中的数据集快照写入到磁盘中,Snapshot快照,恢复时将快照文件直接读到内存中
整个过程主进程不进行任何io操作,保证了性能,如果进行大规模数据恢复,RDB和AOP都可以进行数据恢复,RDB数据恢复完整性不敏感,RDB更加高效,缺点时最后一次持久化后的数据可能丢失,默认使用的就是RDB,一般情况不需要修改这个配置
RDB保存的文件是dump.rdb
AOF保存的文件是appendonly.aof
配置快照在snapshots配置区域下
通过Redis config get dir 获取
config get dir
触发机制
备份会自动生成dump.rdb文件
如何恢复备份文件
只要将rdb文件放在redis规定的目录,redis启动时会自动检查dump.rdb文件恢复数据
查看位置,config get dir
在生产环境中最好对dump.rdb文件进行备份
RDB优缺点
优点:
缺点:
将所执行的所有命令都记录下来,处读操作以外,恢复时重新执行一次,如果是大数据就需要写很久
aof默认是文件无限追加,大小会不断扩张
在主从复制中,rdb是备用的,在从机上使用,aof一般不使用
配置文件Append Only Modo区块中设置
appendonly no
#默认关闭appendonly 手动设置yes开启
appendfilename "appendonly.aof"
#默认名字
# appendfsync always
appendfsync everysec
# appendfsync no
#每次都进行修改
#每秒钟都进行修改
#不进行修改
no-appendfsync-on-rewrite no
#是否进行重写
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
#percentage重写百分比
#重写时文件最小的体积
#一般保持默认,一般只需要开启
这些配置也能在连接redis后在redis中通过config set 进行更改
与RDB类似的触发机制,也能生成配置文件
进行了一些操作,如list在同一个key上覆盖值操作,aof是一同操作的,把之前的值进行了覆盖,但是保存的并不是最新的值,而是把全部进行的操作保存了下来,lpush lpop,当从aof文件中恢复数据时,不管最新的值是什么都重新的进行一遍操作,这样在时间上和效率上并不是最优的,但是能保证在每次的操作能进行备份,保证数据不丢失,如果出于绝对的安全考虑可以开启aof
人为测试aof文件损坏,aof文件是根据文件的大小进行比对,判断文件是否损坏,使用
haoyun@HAOYUN ~ % redis-check-aof --fix /usr/local/var/db/redis/appendonly.aof
AOF analyzed: size=23, ok_up_to=23, diff=0
AOF is valid
损坏的aof会导致redis无法打开
这个修复真垃圾,给我数据删没了,删除规律数据不好修复,但是加入明显没有逻辑的错误,还是能修复
redis-check-rdb 能修复rdb文件
优缺点
优点:
缺点
rdb持久化方式能够在指定的时间间隔内对数据进行快照存储
aof持久化方式记录每次对服务器写的操作,服务器重启时,重新执行命令来恢复原始数据,追加在文件末尾,能对aof文件进行重写,避免体积过大
如果只做缓存不需要使用任何持久化
同时开启两种持计划方式
性能建议
rdb文件只做后背用途,建议只在slave上持久化rdb文件,15分钟备份一次,使用save 900 1 规则
使用aof,即便在最恶劣的环境下也不会丢失超过2秒的数据
一个Master有多个slave,将一台redis服务器数据,复制到其他的redis服务器,前者称为主节点(masterleader),后者称为从节点(slave、follower),数据是单向的,只能从主节点到从节点,Master以写为主,Slave以读为主
默认情况下,每台redis服务器都是主节点,一个Master可以有多少Slave或没有从节点,一个从节点只能有一个主节点
主从复制作用包括:
不能只使用一台redis的原因:
通常的电商网站都是一次上传吗,无数次浏览,读多写少 ,主从复制,读写分离,80%的情况都在进行读操作,起码一主二从
需要配置的config选项
Redis replication实现
查看当前服务
ps -ef|grep redis
默认情况下,每台Redis服务器都是主节点;我们一般情况下只用配置从机就好了
认老大,一主(6379)、二从(6380、6381)
真实的主从配置要在配置文件中配置,在redis-cli中配置的是暂时的
配置在redis.conf文件中replication区块下
replicaof
replicaof 127.0.0.1 6379
配置文件设置好,启动时就不用重新设置
根据读写分离的原则,主机只能写,从机只能读
slave 会自动write master中的数据,但是不能往slave中写数据
当master宕机时让slave变为master
slaveof no one
#让自己变为主机
这种设置是手动的,使用哨兵模式将自动选取master
此时master恢复后使用slaveof no one 的主机也还会继续当master,要重新作为slave只能重新配置
概述切换技术的方法是,当master服务器宕机后,需要人工切换,费事,更多时候选择优先考虑是哨兵模式,redis2.8 开始正确提供sentinel(哨兵 )
能够监控后台的主机是否故障,根据投票自动将从库专为主库
哨兵模式是一种特殊模式,哨兵是一个独立的进程,作为进程独立运行,原理是哨兵通过发送命令,等待redis服务器响应,从而监控多个redis实例
像每台发送信息确定主机是否存活,优点类似于springcloud的心跳检测
这种图成为单机哨兵,当单个哨兵也宕机也会有风险,创建多个哨兵是个不错的选择,称为多哨兵模式
当哨兵模式检测到master宕机,会自动将slave切换成master,通过发布订阅模式通知其他的从服务器,修改配置文件,让他们切换主机
1、配置sentinel.conf
sentinel monitor 被监控的名称 主机地址 端口 1
sentinel monitor mymaster 127.0.0.1 6379 1
后面的这个数字1,代表主机挂了,slave(从机)投票看让谁接替成为主机,票数最多的就会成为主机。
2、启动哨兵
然后启动就行
默认端口为26379,默认pid为69427
当master 79 宕机,sentinel选举了81为newmaster
master节点断开,这时候从slave中选择一个座位master,其中有投票算法,自行了解
当79重新启动后,是以80作为master的slave role存在
主机宕机之后哨兵会重新在从机中选出一个从机做为新主机,如果旧主机重新启动回来了,只能归并到新的主机下,当作从机,这就是哨兵模式的规则。
优点
缺点
都是服务的三高问题
面试高频,工作常用
redis缓存的使用极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时,它也带来了一些问题,数据一致性问题,严格意义上来讲,问题无解,对一致性要求极高,不推荐使用缓存
布隆过滤器、缓存空对象
用户查询一个数据,redis数据库中没有,也就是缓存没命中,于是向持久层数据库查询,发现也没有,于是查询失败,用户很多的时候,缓存都没有命中,都请求持久层数据库,给持久层数据库造成巨大压力,称为缓存穿透
在直达持久层的路径上加上过滤器、或者缓存中专门增加一个为空的请求
例子微博服务器热搜,巨大访问量访问同一个key
一个key非常热点,不停扛着大并发,集中对一个点进行访问,当个key失效的瞬间,持续大并发导致穿破缓存,直接请求数据库
某个key在过期的瞬间,大量的访问会同时访问数据库来查询最新的数据,并且回写缓存,导致数据库瞬间压力过大
在某一个时间段,缓存集中过期失效,redis宕机
产生雪崩的原因之一,设置缓存的存活时间较短,大并发访问时刚好都过期,直接访问了数据库,对数据库而言,会产生周期性压力波峰,暴增时数据库可能会宕机
双十一时会停掉一些服务,保证主要的一些服务可用,springcloud中说明过