1、redis学习笔记
1、NoSql概述
- 用户的·信息,用户的数据,用户的日志,出现大量的爆发,这时候就需要使用NoSql的数据库了
- NoSql的意思是不仅仅是sql,很多的数据类型不需要固定的数据模式,不需要多余的操作就可以进行横向的扩展的
2、NoSql的特点:
- 方便扩展(数据库·之间没有关系,可以很好的进行扩展)
- 大数据量高性能(Redis一秒写8万次,读取11万次,NoSQL的缓存记录,是一种细粒度的缓存,性能比较高)
- 数据类型是多样的(不需要事先设计数据库,随取随用)+
- 传统的数据库和Nosql的区别
传统的数据库:
- 结构话组织
- SQL
- 数据库关系存在于单独的表中
- 操作,数据定义语言
- 严格一致性
- 基础的事务
NoSQL:
- 不仅仅是数据库
- 没有特定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性
- CAP定理和BASE理论
-
3v和三高
大数据时代的3V,主要描述的问题:
- 海量Volume
- 多样 Variety
- 实时Velocity
大数据时代的三高:
- 高并发
- 高可用
- 高性能
真正的企业的实践,关系型数据库和非关系型数据库中配合使用
3、Nosql的分类
KV键值对:
- redis
文档型数据库:
- MongDB
- MongoDB是一个基于分布式存储的数据库,使用c++进行编写,主要用于存储大量的文章
- MongoDB是一个介于关系型数据库,和非关系型的数据库中间的产品,MongDB是非关系型数据库中功能最丰富的,最像关系型数据库的
- ConthDB
列存储的数据库
- HBase
- 分布式文件存储
图形关系数据库:
- 她存的是关系,不是图片,Noo4j.InfoGrid
4、Redis
即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。redis 会周期性的将更新的数据写入到磁中,或者将操作的命令追加到记录文件中,并且在这个基础之上实现maseter-slave主从同步,免费开源,当下最热门的技术
- 内存存储,持久化,内存是断电即失的,所以说持久化很重要
- 效率高,可以进行高速缓存
- 发布订阅系统
- 地图信息分析
- 计时器、计数器
特性
- 多样性的数据类型
- 持久化
- 集群
- 事务Redis
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UvHhF1LU-1633416353573)(C:\Users\17451\Desktop\学习笔记\新建文本文档.assets\image-20211001171908668.png)]
redis有十六个数据库默认使用的是第零个数据库!可以使用select进行数据库的切换: select 3
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DWajErkd-1633416353587)(C:\Users\17451\Desktop\学习笔记\新建文本文档.assets\image-20211001174242669.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vuG8n5Yi-1633416353591)(C:\Users\17451\Desktop\学习笔记\新建文本文档.assets\image-20211001174348516.png)]
清空当前的数据库: flush db,清空全部的数据库:FLUSHALL
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mzrzshtW-1633416353595)(C:\Users\17451\Desktop\学习笔记\新建文本文档.assets\image-20211001174645839.png)]
Redis是单线程的
-
redis是很快的,redis是基于内存进行操作的,CPU不是redis的使用性能的瓶颈,Redis的瓶颈是机器的瓶颈和网络的带宽
-
redis是c语言写的,高性能的服务器不一定是多线程的,多线程不一定比单线程效率高,cpu会进行上下文切换
-
redis是将所有的数据放在内存当中的,所以说使用单线程操作效率就是最高的,多线程(CPU会上下文进行切换,消耗一定的时间),对于内存系统来说,如果没有上下文的切换,效率就是最高的,多次读写都是在CPU中
常用的命令:
5、Redis的五种数据类型
1、String
keys *:查看当前数据库中所有的键
exists key:查看当前键值对是否存在,
move key: 移除键值对
expire key s:设置key过期时间
ttl key:查看key过期的剩余时间
set key:设置键
get key:获取到键对应的值
type key: 查看当前key对应的类型
append key "hello": 增加对应key内容,如果key不存在,就相当于set一个key
incr key: 增加1
decr key: 减去1
incrby key 10:设置步长为10,每次减去10
getrange key 0,3:截取字符串
2、List集合
在redis中,我们可以把list集合看成栈,队列,阻塞队列,redis不区分大小写命令,list的命令都是以L开头的
lpush list one :将一个之或者多个值,插入到列表头部
rpush list righrP: 将一个或者多个值,插入到列表尾部
lrangelist 0 1:获取指定区间内list中的值
Lpop:移除左边的第一个元素
Rpop:移除右边的第一个元素
Lindex:获取到指定下标的值,通过下标获取到值
Lrem:移除指定的值
ltrim list 1 2:通过下标截取指定的长度的值,返回指定的长度
rpopLpush:移除列表中的最后一个元素,添加一个新的元素
exit list:查看集合中是否有值
lset:将列表中指定下标的值替换成另一个值,更新操作
linsert list before :"sa" "asdd": 在qq前面插入asdd
- list它实际上是一个链表,如果key不存在,创建新的链表,如果key存在,新增内容,如果移除了集合中的数据,空链表,也代表着不存在,在两边插入或者改动值,效率最高,中间元素,效率低,可以使用当做消息队列,但是rabbitMQ,似乎要比他做的要更好一点
3、Hash(哈希)
Map集合,key-map! 这时候的值是一个map集合,本质和string没有太大的区别,还是一个简单的key-vlue
hset myhash key value:向其中存入数据
hget myhash key :通过键得到值,值是一个key-vlue的形式
hgetall myhash:获取到hash中的全部的数据
hdel myhash key:删除键值对
hlen myhash: 获取到hash表中的字段的数量
hexists myhash field:判断hash中指定字段是否存在
hkeys myhash: # 只获得所有的key
hvals myhash:只获得所有的value
可以使用hash去存储用户的信息
4、Zset(有序集合)
在set的基础上增加了一个值,用来进行排序,可以做排行榜
zadd myset 1 one: 增加了一个标识,用来进行排序,
zrangebyscore salary -inf +inf:查询出集合中所有的数据
zrange salary 0 -1:查询出集合中所有的数据
zrem salary 0 1 :移除集合中的数据
zcard salary :获取有序集合中的数量
5、Set集合
setrange key 1,xx:设置替换指定位置开始的字符串
setex(set with exxpire) 设置过期时间
setnx(set if not exist) 不存在设置
mset k1 v1 k2 v1 :批量设置键值对
mget k1 k2 k3:同时获取到多个值
msetnx k1 v1 k2 v2: 批量设置多个值,是一个原子性的操作,要么全部成功,要么全部失败
##对象
set user: 1 {name:kangkang,age:1}
###############
getset: 先get然后在set
6、redis的3中特殊的类型
geospatial 地理位置
朋友的定位,附近的人,打车距离的计算,可以精测的推算出来地理位置信息,两地之间的距离,一般下载城市数据,通过java进行导入
key:值(纬度,经度)
getadd:添加地理位置
getpos:获得当前定位,坐标值
geodist:返回两个给定位置的距离
Hyperloglog数据结构
-
Redis Hyperloglog: 技术统计算法,一个人访问多次,但是还是算作一个人
-
传统的方式,set保存用户的id,然后就可以统计set中的元素的数量作为标准判断,这种方式会保存大量的用户id,十分的麻烦
pfadd:创建添加一组基元素
pfcount:统计一组基元素的数量
pfmerge:合并两组基元素,取并集
Bitmaps位存储
统计用户的信息,活跃,和不活跃,使用01这种标识来进行记录,只有两个状态的都可以是用Bitmaps的,还可以使用bitmaps来记录仪周一到周日的打卡,
7、Redis中的事务
Redis中事务的本质:一组命令一起执行,一组命令都需要进行序列化,在事务执行的过程中,会按照顺序进行执行,一次性,顺序性,和排他性,执行一些列的命令
redis的事务没有隔离级别的概念,所有的命令在事务中,并未被直接被执行,只有发起执行命令的时候才会被执行
Redis单条命令是保证原子性的,但是事务是不保证原子性的
redis的事务:
- 开启事务(multi)
- 命令入队
- 执行事务(exec)
- 放弃事务(跟执行事务选一个)DISCARD,事务中的命令都不会被执行
事务在执行完成之后的话,下次再进行命令操作的时候,需要重新的开启事务
编译时异常,代码有问题,命令有错误的时候,事务中的所有的命令都不会被执行
运行时异常,如果事务队列中存在语法性问题(给空值加一),执行命令的时候其他的命令是可以正常执行的,错误命令抛出异常
package com.hema;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TestTs {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
// 开启事务
Transaction multi = jedis.multi();
JSONObject jsonObject = new JSONObject();
jsonObject.put("aa","asdd");
jsonObject.put("bb","adassdd");
String s = jsonObject.toJSONString();
jedis.watch(s);
try {
multi.set("json",s);
multi.set("json2",s);
// 执行命令
multi.exec();//如果成功,执行事务
} catch (Exception e) {
multi.discard();//放弃事务
e.printStackTrace();
} finally {
System.out.println(jedis.get("json"));
System.out.println(jedis.get("json2"));
jedis.close();//关闭连接
}
}
}
监控!
悲观锁:
- 很悲观,什么时候都会出现问题,无论什么时候都会加锁
乐观锁:
- 很乐观,认为什么时候都不会出现问题,所以都不会上锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据
- 获取version
- 更新的时候去对比version
watch:加锁
unWatch:解锁
如果发现事务执行失败,就先解锁,然后重新枷锁,获取到最新的值,然后再去对比监视的值是否发生了变化,如果没有发生变化,就可以执行成功,如果发生了变化就执行失败
8、Jedis
Jedis是Redis官方推荐的java连接开发工具,使用java操作Redis的中间件,如果使用java操作redis,一定要对redis十分的熟悉才可以
测试
- 导入依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
- 测试连接
package com.hema;
import redis.clients.jedis.Jedis;
public class Testping {
public static void main(String[] args) {
//1.new 一个jedis的对象
Jedis jedis = new Jedis("127.0.0.1",6379);
// 2.jedis所有的命令都是跟redis的命令相对应的
String ping = jedis.ping();
System.out.println(ping);
}
}
jedis常用的API
对键的操作:
package com.hema;
import redis.clients.jedis.Jedis;
import java.util.Set;
public class TestKey {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println("清空当前数据库中的数据"+jedis.flushDB());
System.out.println("清空所有数据库中的数据"+jedis.flushAll());
System.out.println("判断是否存在某个值1"+jedis.exists("name"));
System.out.println("新增<'name','kaka'>键值对"+jedis.set("username","kuang"));
System.out.println("新增<'password','password'>键值对"+jedis.set("password","kuang"));
System.out.println("当前数据库中的所有键如下");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("删除password"+jedis.del("username"));
System.out.println("判断键是否存在"+jedis.exists("username"));
System.out.println("查看password所存储值的类型"+jedis.type("password"));
System.out.println("随机返回key中的一个"+jedis.randomKey());
System.out.println("重新命名key"+jedis.rename("password","pass"));
System.out.println("取出修改后的名字"+jedis.get("pass"));
System.out.println("按照索引进行查询"+jedis.select(0));
System.out.println("返回当前数据库中key的数目"+jedis.dbSize());
}
}
- String
package com.hema;
import redis.clients.jedis.Jedis;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class TestKey {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.flushDB());//清空数据库
//添加数据
System.out.println(jedis.set("key1","value1"));
System.out.println(jedis.set("key2","value2"));
System.out.println(jedis.set("key3","value3"));
System.out.println("删除键key2"+jedis.del("key2"));
System.out.println("获取到key2"+jedis.get("key2"));
System.out.println("修改key1"+jedis.set("key1","kangkang"));
System.out.println("获取到修改后的值"+jedis.get("key1"));
System.out.println("在key3后面添加值"+jedis.append("key3","end"));
System.out.println("获取到key3修改后的值"+jedis.get("key3"));
System.out.println("增加多个键值对"+jedis.mset("key01","value01","key02","value02"));
System.out.println("获取到多个键值对"+jedis.mget("key01","key02"));
System.out.println("删除多个键值对"+jedis.del("key01","key02"));
// 分布式锁
System.out.println("如果不存在就设置新增键,防止对原来的键进行覆盖"+jedis.setnx("kk","sad"));
System.out.println("获取到新增的键"+jedis.get("kk"));
System.out.println("===============新增键并且设置有效的时间======================");
System.out.println(jedis.setex("key3",2,"value23"));
System.out.println("获取到设置有效时间的值"+jedis.get("key3"));
try {
//设置线程休眠时间为三秒
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("重新获取到值"+jedis.get("key3"));
System.out.println("获取到原值,更新值"+jedis.getSet("key2","dasasasasasasas"));
System.out.println(jedis.get("key2"));
System.out.println("截取key2的字符串"+jedis.getrange("key2",2,4));
}
}
- list
package com.hema;
import redis.clients.jedis.Jedis;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class TestKey {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.flushDB());//清空数据库
jedis.lpush("collections","ArrayList","vector","stack","hash");
jedis.lpush("collections","hashset");
jedis.lpush("collections","TreeMap");
jedis.lpush("collections","TreeSet");
System.out.println("collections中的内容"+jedis.lrange("collections",0,-1));//-1代表倒数第一个元素,-2代表倒数第二个元素
System.out.println("collections中的0-3内容"+jedis.lrange("collections",0,3));
//删除列表中指定的值,第二个参数Wie删除的个数(有重复的时候),后添加进去的先被删除
System.out.println("删除指定元素的个数"+jedis.lrem("collections",2,"hash"));
System.out.println("查询集合中的内容"+jedis.lrange("collections",0,-1));
System.out.println("删除下标为0-3区间之外的元素"+jedis.ltrim("collections",0,3));
System.out.println("查看当前集合中所有的元素"+jedis.lrange("collections",0,-1));
System.out.println("collections列表出栈"+jedis.lpop("collections"));
System.out.println("collections中所有的元素"+jedis.lrange("collections",0,-1));
System.out.println("collections添加元素,从列表右端"+jedis.lpush("collections","ds"));
System.out.println("collections添加元素,从列表左端"+jedis.rpush("collections","ds"));
System.out.println("collections集合中所有的元素"+jedis.lrange("collections",0,-1));
System.out.println("collections右边出栈"+jedis.rpop("collections"));
System.out.println("collections全部内容"+jedis.lrange("collections",0,-1));
System.out.println("修改下标为一的元素的内容"+jedis.lset("collections",1,"qwqwqwq"));
System.out.println("collections全部内容"+jedis.lrange("collections",0,-1));
System.out.println("获取到collections的长度"+jedis.llen("collections"));
System.out.println("获取到collections下标为2的元素"+jedis.lindex("collections",2));
jedis.lpush("sortedList","1","10","4","9","3");
System.out.println("排序前"+jedis.lrange("sortedList",0,-1));
System.out.println("排序"+jedis.sort("sortedList"));
System.out.println("排序后"+jedis.lrange("sortedList",0,-1));
}
}
- hash
package com.hema;
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class TestKey {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.flushDB());//清空数据库
Map<String,String> map = new HashMap<>();
map.put("key1","value1");
map.put("key2","value2");
map.put("key3","value3");
map.put("key4","value4");
// 向redis中添加元素1
jedis.hmset("hash",map);
// 向redis中添加key为5,value为value5的元素
jedis.hset("hash","key5","value5");
System.out.println("所有的键值对为"+jedis.hgetAll("hash"));
System.out.println("所有的键值为"+jedis.hkeys("hash"));
System.out.println("所有的值为"+jedis.hvals("hash"));
System.out.println("判断是否存在key6,不存在就添加"+jedis.hsetnx("hash","key6","value6"));
System.out.println("所有的键值对"+jedis.hgetAll("hash"));
System.out.println("删除一个或者多个键值对"+jedis.hdel("hash","key2"));
System.out.println("所有的键值对"+jedis.hgetAll("hash"));
System.out.println("所有的键值对的个数"+jedis.hlen("hash"));
System.out.println("判断是否存在key2"+jedis.hexists("hash","key2"));
System.out.println("获取到hash中的值"+jedis.hmget("hash","key3"));
}
}
- set
package com.hema;
import redis.clients.jedis.Jedis;
import java.util.Set;
import java.util.concurrent.TimeUnit;
public class TestKey {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
System.out.println(jedis.flushDB());//清空数据库
System.out.println("=======集合中添加元素======");
System.out.println(jedis.sadd("eleset","q1","q2","q3","q4","q5","q6"));
System.out.println(jedis.sadd("eleset","q8"));
System.out.println("eleset的全部元素为"+jedis.smembers("eleset"));
System.out.println("删除e0元素"+jedis.srem("eleset","q0"));
System.out.println("删除多个元素元素"+jedis.srem("eleset","q5","q4"));
System.out.println("elest的所有的元素W为"+jedis.smembers("eleset"));
System.out.println("随机移除集合中的一个元素"+jedis.spop("eleset"));
System.out.println("随机移除集合中的一个元素"+jedis.spop("eleset"));
System.out.println("全部元素"+jedis.smembers("eleset"));
System.out.println("eleset中元素的个数"+jedis.scard("eleset"));
System.out.println("eleset中是否存在e2"+jedis.sismember("eleset","q2"));
System.out.println(jedis.sadd("eleset1","q1","q2","q3","q4","q5","q6"));
System.out.println(jedis.sadd("eleset2","q1","q2","q3"));
System.out.println("将eleset1这种删除e1并存入eleset3中"+jedis.smove("eleset1","eleset3","q1"));
System.out.println("eleset3中的元素"+jedis.smembers("eleset3"));
System.out.println("=======集合中的运算=========");
System.out.println("eleset1和eleset2的交际"+jedis.sinter("eleset1","eleset2"));
System.out.println("eleset1和eleset2的并集"+jedis.sunion("eleset1","eleset2"));
System.out.println("eleset1和eleset2的差集"+jedis.sdiff("eleset1","eleset2"));
jedis.sinterstore("eleset4","eleset1","eleset2");//取交集然后存进eleset4中
System.out.println("eleset4中的元素"+jedis.smembers("eleset4"));
}
}
- zset
9、SpringBoot整合redis
SpringBoot操作数据:Spring-data,在spring2.x之后,原来使用的jedis被替换成了lettuce
- jedies:
jedies:采用的是直连的,多个线程操作的话,是不安全的,如果想要1避免不安全的,使用jedis pool 连接池
- lettuce:
采用的是netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以肩上线程的数据,更像Nio模式
-
package com.hema.redis02springboot; 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 RedisTemplate redisTemplate; @Test void contextLoads() { //opsForList:操作list集合 // opsforvalue:操作字符串 // opsforset:操作set集合 // opsforhash:操作hash // opsforZset:操作zset //除了基本的操作,我们常用的方法都可以直接使用RedisTemplate操作,比如事务,和最基本的crud操作 // // RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();//操作数据库对象 // connection.flushAll();//删除所有数据库中的内容 // connection.flushDb();//删除当前数据库中的内容 redisTemplate.opsForValue().set("key","杨小花"); System.out.println(redisTemplate.opsForValue().get("key")); } }
真实的开发中,我们会使用Json进行传值,如果要使用到对象的时候,我们可以将对象继承serializable接口进行序列化。在开发中,我们所有的pojo都会被序列化,公司会一般会自己进行封装
自定义序列化:
package org.magic.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
//自定义Jackson序列化配置
Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, DefaultTyping.NON_FINAL);
jsonRedisSerializer.setObjectMapper(objectMapper);
//key使用String的序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
//hash的key也是用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value的key使用jackson的序列化方式
template.setValueSerializer(jsonRedisSerializer);
//hash的value也是用jackson的序列化方式
template.setHashValueSerializer(jsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
10、Redis.config详解
- 启动的时候,通过配置文件进行启动
单位,对大小写是不敏感的
包含,可以包含其他的配置文件
网络
bind 127.0.0.1 #绑定的ip地址
port 6379 #端口号
日志
# 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 #打印的日志等级
# Specify the log file name. Also 'stdout' can be used to force
# Redis to log on the standard output.
logfile stdout#日志输出的文件名
databases 16 #默认有16个数据库
快照
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件中,redis是1内存数据库,如果没有持久化,就会数据断电即失
# Save the DB on disk:
#
# save <seconds> <changes>
#
# Will save the DB if both the given number of seconds and the given
# number of write operations against the DB occurred.
#
# In the example below the behaviour will be to save:
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
#
# Note: you can disable saving at all commenting all the "save" lines.
#
# It is also possible to remove all the previously configured save
# points by adding a save directive with a single empty string argument
# like in the following example:
#
# save ""
save 900 1 #如果900s内,如果至少有一个key进行了修改。我们将对其进行持久化操作
save 300 10 #如果300s内,如果至少有10个key进行了修改。我们将对其进行持久化操作
save 60 10000 #如果60s内,如果至少有10000个key进行了修改。我们将对其进行持久化操作
stop-writes-on-bgsave-error yes#持久化失败后,是否要继续工作
rdbcompression yes#是否压缩rdb文件,需要消耗cpu的资源
rdbchecksum yes#保存rdb文件的时候,进行错误的检查校验,如果出错,就进行修复
dir ./ #rdb文件保存的目录
REPLICATION 主从复制的配置文件
SECURITY安全
可以在这里设置redis的密码,默认是没有密码的
requirepass foobared
LIMITS限制
maxclients 10000 #设置连接redis最大客户端数量
maxheap <bytes> #设置redis配置ide最大内存容量
maxmemory-policy volatile-lru #当内存达到上限之后的处理的策略,比如移除一些过期的key,报错。。。。。
APPEND ONLY MODE aof配置
appendonly no #默认是不开起的,redis默认使用的是rdb进行持久化
appendfilename appendonly.aof #aof的持久化文件名字
# appendfsync always #每次修改都会进行写入
appendfsync everysec#每秒都同步sync
# appendfsync no#不同步执行sync,操作系统自己同步数据,速度最快
11、RDB操作
在指定的时间间隔内将内存中的数据以快照的形式写入到磁盘中,也就是以拍快照的的形式,他恢复时,是将快照文件直接读取到内存中,Redis会单独创建一个(fork)子进程来进行持久化,主进程不进行任何的io操作,这就确保了极高的性能,如果需要进行大规模的数据恢复,使用RDB的方式比AOF方式要更加的高效,RDB的缺点是最后一次持久化的数据可能丢失掉,redis默认使用的是RDB进行操作
rdb保存文件是 dump.rdb
AOF保存的是 appendonly.aof
触发的机制
- save 的规则满足的时候,会自动的触发rdb的规则(生成rdb文件)
- 执行flushall命令的时候,也会触发我们的rdb规则
- 退出redis,也会产生rdb规则
备份完成之后,就会生成一个dump.rdb文件
如果恢复rdb文件
- 只要将rdb文件放在redis的启动目录中就可以了,redis就会自动检查dump.rdb恢复其中的数据!
优点:
- 适合大规模的恢复数据
- 对数据的完整性要求不高
缺点:
- 需要一定的时间间隔进程操作,,如果redis意外宕机了,最后一次修改的数据就没有了
- 使用fork进程的时候,会占用一定的内容空间
12、AOF操作
将我们的所有的命令都记录下来,回复的时候就把这个文件全部在执行一遍
以日志的形式来记录每个写的操作,将redis的执行的过程中所有的指令记录下来,只许追加文件,但是不可以改写文件,redis启动之初,会读取改文件重新的构建数据,redis重启的时候就会根据日志文件的内容,将命令从前到后执行一次,来完成数据的恢复工作
AOF保存的是appendonly.aof文件,,默认是不开启的,我们需要在配置文件中进行更改配置信息,修改完文件之后,需要进行重启生效
如果AOF文件有错位,需要重新修复这个文件,redis提供的一个工具,redis-check-aof --fix,要么全部丢弃,要么只丢弃错误的命令
优点:
- 每次修改都会同步,文件完整性更好
- 每秒同步一次数据,可能会丢失一秒的数据
- redis会记录文件的大小,如果大小超过64mb,触发重写机制,fork就会创建一个新的进程将文件进行重写
缺点:
- 相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
- Aof的运行效率也要比rdb慢。我们redis默认的配置就是rdb的持久化
- 只做缓存,如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化
- 同时开启两种持久化方式
- 在这种情况下,当redis重启的时候,会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF保存的数据集要比RDB文件保存的数据集要完整
- RDB的数据不实时,同时使用两者时,服务器重启也只会找到AOF文件,但是RDB更加适合于备份数据库,快速启动,AOF在不断的变化不好备份
- 性能建议:
- 因为RDB文件只用作后备用途,只建议在slave从节点上面持久化RDB文件,只需要15分钟备份一次就可以
- 如果开启了AOF,好处就是在最恶劣的情况下,也只是丢失不超过两秒的数据,启动脚本load自己的AOF文件就可以了,代价就是带来了持续的io操作,二 AOF rewrite的最后将rewrite过程中产生的新数据写到新的文件造成阻塞几乎是不可避免的,只要硬盘许可,应该尽量减少·AOF rewrite的频率,可以将允许重写的基础大小提升。
- 还可以不开启AOF,仅仅靠Master-Slave主从复制也可以实现高可用性,可以省去很大一部分的io操作,代价就是Master/Slave同时宕机的时候,就会丢失很多的数据
13、Redis发布订阅
Redis发布订阅是一种消息通信模式,发送者发送消息,订阅者接收消息
Redis客户端可以订阅任意数量的频道
下图描述的是频道和消息订阅者之间的关系:频道就是一个字典
当有新的消息通过PUBLISH命令发送给channel1的时候,消息就会推送给平台的订阅者
命令
优点:
- 实施消息系统!
- 实时聊天室(频道当做聊天室,将信息回西安各所有人即可)
- 订阅关注
- 稍微复杂的场景就会使用消息中间件来处理,因为人家是专业的
14、Redis主从复制
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器,前者称为主节点(master/leader),后者称为从节点(slave/follower);数据复制是单向的,只能由主节点到从节点,Master以写为主,Slave以读为主,默认情况下,每台Redis服务器都是主节点,并且一个主节点可以有多个从节点(或者没有从节点),但是一个从节点只能有一个主节点,主从复制的作用主要包括:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余的方式
- 故障恢复:当主节点出现问题的时候,可以由从节点提供服务,快速的故障恢复,实际上是一种服务的冗余
- 负载均衡:在主从复制的基础之上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,(即写Redis数据的时候应该应用连接主节点,读Redis数据的时候应该应用连接从节点),分担服务器的负载,尤其是读多写少的情况下,通过多个从节点分担负载,可以大大的提高Redis服务器的并发量
- 高可用基石,除了上述作用之外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis是高可用的基础
一般来说,要将Redis运用到工程中,只使用一台Redis是不可能的
-
从结构上来看,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力过大
-
从容量来看,单个Redis服务器内存容量有限,就算是一台Redis的内存为256G,也不能将所有的内存用作Redis的存储内存,一般情况下,Redis的最大内存不应该超过20G,一般情况下,电商网站上的商品,都是一次性上传的,无数次的进行浏览的,就是读多写少
主从复制,读写分离,80%的情况下都是在进行读操作,减缓服务器的压力,架构中经常使用。
环境配置
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>
复制三个配置文件,然后修改对应的信息(windows下的redis3.0之后才有集群)
- 端口 :进程占用的端口号
- pid名字(windows找不到) 记录了进程的id,文件带有锁,可以防止程序的多次启动
- log文件名字,明确文件的位置
- dump.rdb的名字 持久化文件的位置
如果使用命令行Slaveof 主机的地址,主机的端口号 时候,配置是暂时的,永久的话需要使用配置文件进行配置,主机慈爱可以写,从机不能写,只能读,主机中所有的信息和数据,都会自动的被从机进行保存,主机断开连接,从机依旧连接到主机,但是没有写操作,这个时候,如果主机回来了,从机依旧可以直接获取到主机写的信息,如果是使用命令行的形式进行配置的集群,从机宕机后恢复,会变成主机,只要变成从机,就会立刻从主机中获取到值
复制原理
slave 启动成功连接到master之后会发送一个sync同步命令
Master接到命令,启动后台进程,同时手机所有用于修改数据集的命令,在后天执行完毕之后,master将整个文件传输到slave,并且完成一次同步
全量复制:Slave服务在接受到数据库的文件得到时候,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传递给slave,完成同步
但是只要重新连接master,一次完全同步(全量复制)将会被自动执行,顾名思义就是在连接之后增加的数据-·
哨兵模式
如果主机断开连接了,我们可以使用SLAVEOF no one ,让自己便成为主机,手动的进行命名新的主机,如果主机master修复了,那就需要进行重新配置了
哨兵模式
主从切换的技术的方法是:当服务器宕机的时候,需要手动大的把一台从服务器切换称为主服务器,需要进行人工的干预,费时费力,还会造成一段时间内的服务不可用,我们可以使用哨兵进行呢解决这个问题,他可以后台监控主机是否故障,如果故障了,根据投票数自动的将从库,转变为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,他作为进程,会独立的进行运行,原理就是哨兵发送命令,等待Redis服务器的响应,从而监控运行的多个Redis的实例
哨兵的作用:
- 通过发送命令,让redis服务器返回监控其运行的状态,包括主服务器和从服务器
- 当哨兵监测到master宕机的时候,会自动的将slave切换称为master,然后通过发布订阅通知其他的服务器,修改配置文件,让他们切换主机
一个哨兵对redis进行监控的时候,可能会出现问题,因此,我们可以使用多个哨兵进行监控,各个哨兵之间还可以互相监控,形成多哨兵模式
假设主服务器宕机,哨兵1先检测到这个情况后,系统并不会马上进行选举failover过程。仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主管下线,当后面的哨兵也检测到主服务器不可用的时候,并且数量达到一定的值是,哨兵之间会进行一次投票,投票的结果由一个哨兵进行发起,切换成功之后,就会通过发布订阅的模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线
配置哨兵的文件sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1
#sentinel monitor 被监控的名称 host port 1
后面的数字1,代表主机挂了之后,slave投票看看让谁接替成为新的主机,票数最多的,就会成为主机,一代表的是只要一个哨兵认为主机挂掉了就开始进行选举,最低通过的票数为一,说白了,就是一个哨兵认为主机宕机了,该主机就会被认定为宕机
- 如果主机回来了,只能归并到新的master下当做从机
优点:
- 哨兵集群,基于主从复制的模式,所有主从复制的优点,他都有
- 主从可以进行切换,故障可以进行转移,系统的可用性就会更好
- 哨兵模式就是主从模式的升级,更加健壮
缺点:
- 、redis不容易在线扩容,集群一旦到达上限,在线扩容就会很麻烦
15、Redis的缓存穿透和雪崩
Redis的缓存的使用,极大的提高了应用程序的性能和效率,特别是数据查询方便,同时,也带来的一些问题
缓存穿透
缓存穿透,用户想要查询一个数据,发现redis内存数据库中没有这个数据,也就是缓存没有命中,于是向持久层数据库进行查询,发现也没有,于是查询失败,当用户很多没有缓存命中的时候,都去查询了持久层的数据库,会给数据库造成很大的压力,出现了缓存的穿透
解决方法
1.布隆过滤器:
布隆过滤器是一种数据结构,对所有可能查询的参数以hash的形式进行存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询的压力
2.缓存空对象:
- 如果空值能够被缓存起来,就意味着缓存需要更多的空间存储更多的键
- 即使对空值设置了过期的时间,还是会存在缓存层和存储层有一段时间不一致的问题,
缓存击穿
这里要注意和缓存穿透的区别,缓存击穿,就是一个key十分的热点,在不断的扛着大量的并发,大并发集中对着一个点进行访问,当这个key在失效的瞬间,持续的大量的并发就穿破缓存,直接请求数据库,这二类数据一般是热点数据,由于缓存过期,同时访问数据库来查询最新的数据,并且会回写缓存,导致数据库1压力瞬间增大
解决方法
1.设置热点数据永不过期
从缓存层面上来看,不设置过期时间就不会出现该问题
2.加互斥锁
分布式锁:使用分布式锁,保证对于每一个key同时只有一个线程去查询后端服务其他的线程没有获取到分布式锁的权限,因此只需要等待,将压力转移到了分布式锁
缓存雪崩
缓存雪崩,就是在某一时间端内,缓存集中过期失效,Redis宕机
产生雪崩的原因之一,就是同一时间内,扛着并发的大量的key在同一时间内都过期了,就会直接去访问数据库
解决方案
1.redis的高可用:
多设置几台redis,搭建集群
2.限流降级
在缓存失效后,可以通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程去查询数据和写缓存,其他的线程等待
3.数据预热
数据加热的我含义是在正式部署的时候,将数据先预先访问一下,将数据加载到redis缓存中,设置不同的过期的时间,让缓存失效的时间点尽量均匀一点