传统的数据库: - 结构话组织 - SQL - 数据库关系存在于单独的表中 - 操作,数据定义语言 - 严格一致性 - 基础的事务 NoSQL: - 不仅仅是数据库 - 没有特定的查询语言 - 键值对存储,列存储,文档存储,图形数据库 - 最终一致性 - CAP定理和BASE理论 -
3v和三高
大数据时代的3V,主要描述的问题:
大数据时代的三高:
真正的企业的实践,关系型数据库和非关系型数据库中配合使用
KV键值对:
文档型数据库:
列存储的数据库
图形关系数据库:
即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。redis 会周期性的将更新的数据写入到磁中,或者将操作的命令追加到记录文件中,并且在这个基础之上实现maseter-slave主从同步,免费开源,当下最热门的技术
特性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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中
常用的命令:
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:截取字符串
在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
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去存储用户的信息
在set的基础上增加了一个值,用来进行排序,可以做排行榜
zadd myset 1 one: 增加了一个标识,用来进行排序, zrangebyscore salary -inf +inf:查询出集合中所有的数据 zrange salary 0 -1:查询出集合中所有的数据 zrem salary 0 1 :移除集合中的数据 zcard salary :获取有序集合中的数量
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
geospatial 地理位置
朋友的定位,附近的人,打车距离的计算,可以精测的推算出来地理位置信息,两地之间的距离,一般下载城市数据,通过java进行导入
key:值(纬度,经度)
getadd:添加地理位置 getpos:获得当前定位,坐标值 geodist:返回两个给定位置的距离
Hyperloglog数据结构
Redis Hyperloglog: 技术统计算法,一个人访问多次,但是还是算作一个人
传统的方式,set保存用户的id,然后就可以统计set中的元素的数量作为标准判断,这种方式会保存大量的用户id,十分的麻烦
pfadd:创建添加一组基元素 pfcount:统计一组基元素的数量 pfmerge:合并两组基元素,取并集
Bitmaps位存储
统计用户的信息,活跃,和不活跃,使用01这种标识来进行记录,只有两个状态的都可以是用Bitmaps的,还可以使用bitmaps来记录仪周一到周日的打卡,
Redis中事务的本质:一组命令一起执行,一组命令都需要进行序列化,在事务执行的过程中,会按照顺序进行执行,一次性,顺序性,和排他性,执行一些列的命令
redis的事务没有隔离级别的概念,所有的命令在事务中,并未被直接被执行,只有发起执行命令的时候才会被执行
Redis单条命令是保证原子性的,但是事务是不保证原子性的
redis的事务:
事务在执行完成之后的话,下次再进行命令操作的时候,需要重新的开启事务
编译时异常,代码有问题,命令有错误的时候,事务中的所有的命令都不会被执行
运行时异常,如果事务队列中存在语法性问题(给空值加一),执行命令的时候其他的命令是可以正常执行的,错误命令抛出异常
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();//关闭连接 } } }
监控!
悲观锁:
乐观锁:
watch:加锁 unWatch:解锁
如果发现事务执行失败,就先解锁,然后重新枷锁,获取到最新的值,然后再去对比监视的值是否发生了变化,如果没有发生变化,就可以执行成功,如果发生了变化就执行失败
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); } }
对键的操作:
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()); } }
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)); } }
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)); } }
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")); } }
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")); } }
SpringBoot操作数据:Spring-data,在spring2.x之后,原来使用的jedis被替换成了lettuce
jedies:采用的是直连的,多个线程操作的话,是不安全的,如果想要1避免不安全的,使用jedis pool 连接池
采用的是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; } }
单位,对大小写是不敏感的
包含,可以包含其他的配置文件
网络
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,操作系统自己同步数据,速度最快
在指定的时间间隔内将内存中的数据以快照的形式写入到磁盘中,也就是以拍快照的的形式,他恢复时,是将快照文件直接读取到内存中,Redis会单独创建一个(fork)子进程来进行持久化,主进程不进行任何的io操作,这就确保了极高的性能,如果需要进行大规模的数据恢复,使用RDB的方式比AOF方式要更加的高效,RDB的缺点是最后一次持久化的数据可能丢失掉,redis默认使用的是RDB进行操作
rdb保存文件是 dump.rdb
AOF保存的是 appendonly.aof
触发的机制
备份完成之后,就会生成一个dump.rdb文件
如果恢复rdb文件
优点:
缺点:
将我们的所有的命令都记录下来,回复的时候就把这个文件全部在执行一遍
以日志的形式来记录每个写的操作,将redis的执行的过程中所有的指令记录下来,只许追加文件,但是不可以改写文件,redis启动之初,会读取改文件重新的构建数据,redis重启的时候就会根据日志文件的内容,将命令从前到后执行一次,来完成数据的恢复工作
AOF保存的是appendonly.aof文件,,默认是不开启的,我们需要在配置文件中进行更改配置信息,修改完文件之后,需要进行重启生效
如果AOF文件有错位,需要重新修复这个文件,redis提供的一个工具,redis-check-aof --fix,要么全部丢弃,要么只丢弃错误的命令
优点:
缺点:
Redis发布订阅是一种消息通信模式,发送者发送消息,订阅者接收消息
Redis客户端可以订阅任意数量的频道
下图描述的是频道和消息订阅者之间的关系:频道就是一个字典
当有新的消息通过PUBLISH命令发送给channel1的时候,消息就会推送给平台的订阅者
命令
优点:
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器,前者称为主节点(master/leader),后者称为从节点(slave/follower);数据复制是单向的,只能由主节点到从节点,Master以写为主,Slave以读为主,默认情况下,每台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之后才有集群)
如果使用命令行Slaveof 主机的地址,主机的端口号 时候,配置是暂时的,永久的话需要使用配置文件进行配置,主机慈爱可以写,从机不能写,只能读,主机中所有的信息和数据,都会自动的被从机进行保存,主机断开连接,从机依旧连接到主机,但是没有写操作,这个时候,如果主机回来了,从机依旧可以直接获取到主机写的信息,如果是使用命令行的形式进行配置的集群,从机宕机后恢复,会变成主机,只要变成从机,就会立刻从主机中获取到值
复制原理
slave 启动成功连接到master之后会发送一个sync同步命令
Master接到命令,启动后台进程,同时手机所有用于修改数据集的命令,在后天执行完毕之后,master将整个文件传输到slave,并且完成一次同步
全量复制:Slave服务在接受到数据库的文件得到时候,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传递给slave,完成同步
但是只要重新连接master,一次完全同步(全量复制)将会被自动执行,顾名思义就是在连接之后增加的数据-·
哨兵模式
如果主机断开连接了,我们可以使用SLAVEOF no one ,让自己便成为主机,手动的进行命名新的主机,如果主机master修复了,那就需要进行重新配置了
哨兵模式
主从切换的技术的方法是:当服务器宕机的时候,需要手动大的把一台从服务器切换称为主服务器,需要进行人工的干预,费时费力,还会造成一段时间内的服务不可用,我们可以使用哨兵进行呢解决这个问题,他可以后台监控主机是否故障,如果故障了,根据投票数自动的将从库,转变为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,他作为进程,会独立的进行运行,原理就是哨兵发送命令,等待Redis服务器的响应,从而监控运行的多个Redis的实例
哨兵的作用:
一个哨兵对redis进行监控的时候,可能会出现问题,因此,我们可以使用多个哨兵进行监控,各个哨兵之间还可以互相监控,形成多哨兵模式
假设主服务器宕机,哨兵1先检测到这个情况后,系统并不会马上进行选举failover过程。仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主管下线,当后面的哨兵也检测到主服务器不可用的时候,并且数量达到一定的值是,哨兵之间会进行一次投票,投票的结果由一个哨兵进行发起,切换成功之后,就会通过发布订阅的模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线
配置哨兵的文件sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1 #sentinel monitor 被监控的名称 host port 1
后面的数字1,代表主机挂了之后,slave投票看看让谁接替成为新的主机,票数最多的,就会成为主机,一代表的是只要一个哨兵认为主机挂掉了就开始进行选举,最低通过的票数为一,说白了,就是一个哨兵认为主机宕机了,该主机就会被认定为宕机
优点:
缺点:
Redis的缓存的使用,极大的提高了应用程序的性能和效率,特别是数据查询方便,同时,也带来的一些问题
缓存穿透
缓存穿透,用户想要查询一个数据,发现redis内存数据库中没有这个数据,也就是缓存没有命中,于是向持久层数据库进行查询,发现也没有,于是查询失败,当用户很多没有缓存命中的时候,都去查询了持久层的数据库,会给数据库造成很大的压力,出现了缓存的穿透
解决方法
1.布隆过滤器:
布隆过滤器是一种数据结构,对所有可能查询的参数以hash的形式进行存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询的压力
2.缓存空对象:
缓存击穿
这里要注意和缓存穿透的区别,缓存击穿,就是一个key十分的热点,在不断的扛着大量的并发,大并发集中对着一个点进行访问,当这个key在失效的瞬间,持续的大量的并发就穿破缓存,直接请求数据库,这二类数据一般是热点数据,由于缓存过期,同时访问数据库来查询最新的数据,并且会回写缓存,导致数据库1压力瞬间增大
解决方法
1.设置热点数据永不过期
从缓存层面上来看,不设置过期时间就不会出现该问题
2.加互斥锁
分布式锁:使用分布式锁,保证对于每一个key同时只有一个线程去查询后端服务其他的线程没有获取到分布式锁的权限,因此只需要等待,将压力转移到了分布式锁
缓存雪崩
缓存雪崩,就是在某一时间端内,缓存集中过期失效,Redis宕机
产生雪崩的原因之一,就是同一时间内,扛着并发的大量的key在同一时间内都过期了,就会直接去访问数据库
解决方案
1.redis的高可用:
多设置几台redis,搭建集群
2.限流降级
在缓存失效后,可以通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程去查询数据和写缓存,其他的线程等待
3.数据预热
数据加热的我含义是在正式部署的时候,将数据先预先访问一下,将数据加载到redis缓存中,设置不同的过期的时间,让缓存失效的时间点尽量均匀一点