赞
踩
历程:单机mysql(x) -->x(读写分离) -->x + memcache缓存(发展历程:优化数据结构和索引–>文件缓存(IO)–>memcache缓存)–>mysql集群
原因:mysql关系型数据库很难存放一些特殊数据,比如个人信息、社交网络、地理位置等,这些数据不需要固定的格式来存储。
什么是nosql?
nosql = not only sql(不仅仅sql)。泛指非关系型数据库,随着web 2.0的到来,传统的关系型数据库很难支撑下去,尤其出现超大规模的高并发的社区论坛!nosql在当今大数据环境下发展迅速,redis是发展最快的,是当下我们必须要掌握的技术!
1、结构化组织
2、sql
3、数据和关系都存在单独的表中
4、严格的一致性
5、基础的事务
…
1、不仅仅是数据
2、没有固定的查询语言
3、键值对存储、列存储、文档存储、图形数据库(社交关系)
4、最终一致性
5、CAD定理和BASE
6、高性能、高可用、高可扩
…
3V:多样(Variety)、海量(Volume)、实时(Velocity)
3高:高性能、高并发、高可扩
redis、memcache、tair
HBase、分布式文件系统
MongoDB、ConthDB
存的是关系,而不是图形。
Neo4j、inoGrid
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
1、内存存储、持久化(aof、rdb)
2、高速缓存、效率高
3、发布订阅
4、地图信息分析
5、计时器、计数器
…
1、多样的数据类型
2、持久化
3、集群
4、事务
下载链接:redis安装包下载
1、双击redis-server.exe打开redis服务端,最小化
2、双击redis-cli.exe打开redis客户端,输入ping回车,显示pong即连接成功。
key-value键值对存储,存值set key value,取值get key
1、下载压缩包linux版压缩包下载
2、使用xftp传输到linux(我是使用阿里云的服务器)
3、程序一般放在/opt,移动过去并解压压缩包
移动过去 mv redis-7.0.8.tar.gz /opt
切换目录cd /opt
解压 tar -zxvf redis-7.0.8.tar.gz
4、进到redis目录,安装基本环境 yum install gcc-c++
5、在当前目录 make
, 完毕后可以再确认一下 make install
(可有可无)
6、安装的redis在/usr/local/bin,如图所示:
7、为了安全,拷贝一份redis.conf,用备用的redis.conf,出问题了能复原
8、redis默认不是后台运行,修改配置文件,让他可以在后台运行,找到daemonize no,把他改成yes
9、启动redis-server,通过刚刚修改的配置文件启动 redis-server pconfig/redis.conf
10、启动redis-cli,测试能不能连通 redis-cli -p 6379
11、查看redis进程
开另一个终端 ps -ef|grep redis
12、关闭redis shutdown
, exit
回到命令行
select
用来切换数据库keys *
用來查看当前数据库的所有keyDBSIZE
用来查看当前数据库的大小flushdb
用来清空当前数据库flushall
用来清空所有数据库exists
用来判断key是否存在move
用来移除key
后面的1代表当前数据库
expire
用来设置key的过期时间ttl
用来查询key的剩余时间如上图
type
用来查看key的类型摘要:官网介绍的redis
翻译:Redis是一个开源(BSD许可)的内存数据结构存储,用作数据库、缓存、消息代理和流引擎。Redis提供了字符串(string)、哈希(hash)、列表(list)、集合(set)、带范围查询的排序集合(zset)、位图、超日志、地理空间索引和流等数据结构。Redis具有内置复制、Lua脚本、LRU逐出、事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供高可用性。
append
给字符串加长如果key不存在,就新建一个
strlen
用来计算字符串的长度incr
加1,decr
减1, incrby
按步长加, decrby
按步长减getrange
用来截取字符串,和java的substring一样
当end是-1时,返回整个字符串
setrange
用来替换指定区间字符串,和java的replace一样setex
创建一个过期时间的值, setnx
当key不存在时创建,否则不创建mset
批量写, mget
批量读msetnx
有原子性,同时成功或同时失败mset加:
写json对象, mget
读json对象getset
先读再写lpush
从左边添加, rpush
从右边添加,index从左到右lrange
读list的值如上图,从index0开始读,0到-1表示读全部(和getrange一样)
lpop
从左边移出, rpop
从右边移出(命令 key count)count 表示数量
llen
计算list的长度lindex
读指定index的值lrem
移出指定个数的valueltrim
截取指定区间的值意思是只要index 3到4的值
rpoplpush
弹出最右边的一个元素,加到一个新的list中lset
替换指定index的值(前提要存在,否则报错)exists
判断list是否存在(和string一样)linsert
从list的某个元素(value)的前面或者后面插入值温馨提示:图中before和after后面的2是value,而不是index。
可以用作消息队列、栈等。
sadd
加一个元素(不是原子性操作)smembers
查看set的所有元素如上图
sismember
判断set是否包含指定元素srem
删除指定一个或多个元素scard
返回set的元素个数srandmember
抽取set的指定个数的元素spop
随机删除一个或多个元素smove
将一个元素从一个set移动到另一个setsdiff
多个集合的差集,以第一个对比sinter
集合间的交集sunion
集合间的并集hset
设置key的值,可以同时设置多个fieldhget
读取key的一个field的值hmset
同时设置key的多个field,hmget
读取多个field值hgetall
读取key的所有field-valuehexists
判断key的field是否存在hdel
删除key的若干个field的field-valuehlen
返回key的field-value的对数hkeys
返回key的所有field值hvals
返回key的所有value值hincrby
加指定值(可以是正数,也可以是负数)hsetnx
不存在就写,存在就不写zadd
value前加个score,标记以便排序zrangebyscore
对zset按区间内score升序,加withscores表示同时读取score
zrevrange
降序,withscores表示是否包含scorezcard
返回zset的元素个数zrem
移除指定元素zcount
返回区间的元素个数(按score值)geoadd
key 经度 纬度member(添加经纬度)温馨提示:有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。
geopos
key member(返回元素的经纬度)geodist
返回两个给定位置之间的距离如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit 必须是以下单位的其中一个:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
GEODIST 默认使用米作为单位。
georadius
key 经纬度 半径 单位【可选withdist 距离、withchrood 经纬度、count 数量、asc/desc 按距离排序】georadiusbymember
找出指定元素周围的元素geohash
返回元素的11位字符串,hash表示Zset
, 可以用zset命令操作geopfadd
添加元素(不重复)pfcount
计算数量pfmerge
返回并集setbit
写入某人一周的登录状态getbit
查看某人星期四有没有登录bitcount
计算登录的天数【可选(index区间)】redis单条命令具有原子性,redis事务不保证原子性
redis事务不具有隔离性的概念
redis事务具有一次性、顺序性、排他性
一次性:事务执行的时候命令一次性被执行
排他性:事务在执行时不能被其他事务干扰
顺序性:事务中的命令被序列化,按照入队顺序执行
redis事务流程
1、开启事务(multi)
2、命令入队
3、执行事务(exec)
乐观锁:认为任何时候都不会出问题,不加锁。更新数据的时候判断下是否被修改过。(redis中的watch)
悲观锁:认为任何时候都会出问题,加锁,性能十分低。
如图所示,money没有被修改过,事务能正常执行。
启动另一个redis客户端2,在客户端1事务还没执行时。修改money的值,客户端1事务提交不成功。客户端1会解锁,拿到最新的值后再次加锁,执行事务。直至事务执行成功!
客户端1
客户端2
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.3</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.0</version>
</dependency>
运行本机redis-server
System.out.println("包含k1吗?---" + jedis.exists("k1")); System.out.println("set键值对(k1, v1)---" + jedis.set("k1", "v1")); System.out.println("k1的值=" + jedis.get("k1")); System.out.println("批量创建键值对(k2, v2; k3, v3)" + jedis.mset("k2", "v2", "k3", "v3")); System.out.println("读取k1、k2、k3的值:" + jedis.mget("k1", "k2", "k3")); System.out.println("查看k1,index:0-1:" + jedis.getrange("k1", 0, 1)); System.out.println("查看整个k1:" + jedis.getrange("k1", 0, -1)); System.out.println("更新k1的值为v6---" + jedis.setrange("k1", 0, "v6")); System.out.println("读取k5,更新为v5---" + jedis.getSet("k5", "v5")); System.out.println("读取k5,更新为v6---" + jedis.getSet("k5", "v6")); System.out.println("写入k8,过期时间为10s---" + jedis.setex("k8", 10, "v8")); System.out.println("查看k8的剩余时间:" + jedis.ttl("k8")); System.out.println("设置k1的过期时间---" + jedis.expire("k1", 10)); System.out.println("如果k9不存在,写入v9---" + jedis.setnx("k9", "v9")); System.out.println("如果k9不存在,写入v9---" + jedis.setnx("k9", "v9")); System.out.println("设置age:20---" + jedis.set("age", "20")); System.out.println("age+1:---" + jedis.incr("age")); System.out.println("age+10:" + jedis.incrBy("age", 10)); System.out.println("age-1:" + jedis.decr("age")); System.out.println("age-5:" + jedis.decrBy("age", 5)); System.out.println("" + jedis.append("k5", ",hello")); System.out.println("k5的长度---" + jedis.strlen("k5")); System.out.println("删除k1、k2---" + jedis.del("k1", "k2")); System.out.println("查看所有的key:" + jedis.keys("*")); System.out.println("清空数据库---" + jedis.flushDB()); System.out.println("查看所有的key:" + jedis.keys("*"));
System.out.println("清空数据库:" + jedis.flushDB()); System.out.println("从左边加:" + jedis.lpush("list1","5", "8", "1", "8", "1")); System.out.println("从右边加:" + jedis.rpush("list1", "10", "4")); System.out.println("list1是否存在:" + jedis.exists("list1")); System.out.println("查看list1的元素:" + jedis.lrange("list1", 0, -1)); System.out.println("从左边移出1个:" + jedis.lpop("list1")); System.out.println("从右边移出1个(redis6.2以下版本不支持取多个):" + jedis.rpop("list1")); System.out.println("将最右边一个移出,添加到list2:" + jedis.rpoplpush("list1", "list2")); System.out.println("查看list1的元素个数:" + jedis.llen("list1")); System.out.println("查看list2的元素:" + jedis.lrange("list2", 0, -1)); System.out.println("读取list1的index=2的值:" + jedis.lindex("list1", 2)); System.out.println("移出指定个数的元素值:" + jedis.lrem("list1", 2, "1")); System.out.println("截取1 - 2的元素:" + jedis.ltrim("list1", 1, 2)); System.out.println("将index值为1的元素值替换成10:" + jedis.lset("list1", 1, "10")); System.out.println("将15插在index 0的前面:()" + jedis.linsert("list1", ListPosition.BEFORE, "0", "15")); System.out.println("将51插在index 0的后面:" + jedis.linsert("list1", ListPosition.AFTER, "0", "51")); System.out.println("查看list1的元素:" + jedis.lrange("list1", 0, -1)); System.out.println("查看所有的key:" + jedis.keys("*")); System.out.println("清空数据库:" + jedis.flushDB()); System.out.println("查看所有的key:" + jedis.keys("*"));
System.out.println("清空数据库:" + jedis.flushDB()); System.out.println("添加值:" + jedis.sadd("set1", "1", "2", "3", "4", "5")); System.out.println("移出值:" + jedis.srem("set1", "2")); System.out.println("查看set1:" + jedis.smembers("set1")); System.out.println("返回2个随机成员:" + jedis.srandmember("set1", 2)); System.out.println("返回set1的元素个数:" + jedis.scard("set1")); System.out.println("判断5在不在set1:" + jedis.sismember("set1", "5")); System.out.println("随机删除2个成员:" + jedis.spop("set1", 2)); System.out.println("将一个member从set1移到set2:" + jedis.smove("set1", "set2", "3")); System.out.println("查看set2:" + jedis.smembers("set2")); System.out.println("添加值到set3:" + jedis.sadd("set3", "3", "4", "5", "6", "7")); System.out.println("求set1和set3的差集:" + jedis.sdiff("set1", "set3")); System.out.println("查看set1:" + jedis.smembers("set1")); System.out.println("求set1和set3的交集:" + jedis.sinter("set1", "set3")); System.out.println("求set1和set3的并集:" + jedis.sunion("set1", "set3")); System.out.println("清空数据库:" + jedis.flushDB());
System.out.println("清空数据库:" + jedis.flushDB()); HashMap<String, String> map = new HashMap<>(); map.put("k1", "v1"); map.put("k2", "v2"); map.put("k3", "v3"); System.out.println("添加值:" + jedis.hset("hash1", "name", "zhangsan")); System.out.println("添加值:" + jedis.hset("hash1", "age", "24")); System.out.println("读取值:" + jedis.hget("hash1", "age")); System.out.println("读取值:" + jedis.hmset("hash1", map)); System.out.println("查看所有的key-value:" + jedis.hgetAll("hash1")); System.out.println("判断field是否存在:" + jedis.hexists("hash1", "name")); System.out.println("查看hash的键值对数:" + jedis.hlen("hash1")); System.out.println("查看hash的field的长度:" + jedis.hstrlen("hash1", "name")); System.out.println("删除key的若干个field的field-value:" + jedis.hdel("hash1", "name", "sex")); System.out.println("查看所有的key-value:" + jedis.hgetAll("hash1")); System.out.println("返回key的所有field值:" + jedis.hkeys("hash1")); System.out.println("返回key的所有value值:" + jedis.hvals("hash1")); System.out.println("加指定值(可以是正数,也可以是负数):" + jedis.hincrBy("hash1", "age", 10)); System.out.println("不存在就写,存在就不写(密码):" + jedis.hsetnx("hash1", "pwd", "123")); System.out.println("不存在就写,存在就不写(密码):" + jedis.hsetnx("hash1", "pwd", "123")); System.out.println("查看所有的key-value:" + jedis.hgetAll("hash1")); System.out.println("清空数据库:" + jedis.flushDB());
System.out.println("清空数据库:" + jedis.flushDB());
System.out.println("添加值:" + jedis.zadd("zset1", 500, "z1"));
System.out.println("添加值:" + jedis.zadd("zset1", 1000, "z2"));
System.out.println("添加值:" + jedis.zadd("zset1", 700, "z3"));
System.out.println("添加值:" + jedis.zadd("zset1", 2000, "z4"));
System.out.println("添加值:" + jedis.zadd("zset1", 1500, "z5"));
System.out.println("添加值:" + jedis.zadd("zset1", 800, "z6"));
System.out.println("查看所有的值:" + jedis.zrange("zset1", 0, -1));
System.out.println("移出值:" + jedis.zrem("zset1", "z1", "z4"));
System.out.println("查看所有的值:" + jedis.zrange("zset1", 0, -1));
System.out.println("降序:" + jedis.zrevrangeWithScores("zset1", 0, -1));
System.out.println("返回zset1的元素个数:" + jedis.zcard("zset1"));
System.out.println("升序(含scores):" + jedis.zrangeByScoreWithScores("zset1", "-inf", "+inf", 0, 5));
System.out.println("升序:" + jedis.zrangeByScore("zset1", "-inf", "+inf", 0, 5));
System.out.println("清空数据库:" + jedis.flushDB());
System.out.println("清空数据库:" + jedis.flushDB());
System.out.println("添加值:" + jedis.geoadd("geo1", 110.55, 23.55, "beijing"));
System.out.println("添加值:" + jedis.geoadd("geo1", 130.55, 28.55, "xian"));
System.out.println("添加值:" + jedis.geoadd("geo1", 115.55, 25.16, "guangzhou"));
System.out.println("查看值:" + jedis.geopos("geo1", "guangzhou"));
System.out.println("两个位置的距离:" + jedis.geodist("geo1", "beijing", "guangzhou", GeoUnit.KM));
System.out.println("以guangzhou为圆心,求与其他位置的距离:" + jedis.georadiusByMember("geo1", "guangzhou", 1000, GeoUnit.KM));
System.out.println("以某个位置为圆心,求与其他位置的距离:" + jedis.georadius("geo1", 105.0, 25, 1000, GeoUnit.KM));
System.out.println("清空数据库:" + jedis.flushDB());
System.out.println("清空数据库:" + jedis.flushDB());
System.out.println("添加数据:" + jedis.pfadd("key1", "1", "2", "3", "4"));
System.out.println("添加数据:" + jedis.pfadd("key2", "3", "4", "5", "6"));
System.out.println("key1、key2的元素个数是:(不重复)" + jedis.pfcount("key1", "key2"));
System.out.println("key1和key2的并集是:" + jedis.pfmerge("key3", "key1", "key2"));
System.out.println("key3的元素个数是:" + jedis.pfcount("key3"));
System.out.println("清空数据库:" + jedis.flushDB());
System.out.println("清空数据库:" + jedis.flushDB());
System.out.println("星期一:" + jedis.setbit("key1", 0, true));
System.out.println("星期二:" + jedis.setbit("key1", 1, false));
System.out.println("星期三:" + jedis.setbit("key1", 2, true));
System.out.println("星期四:" + jedis.setbit("key1", 3, true));
System.out.println("星期五:" + jedis.setbit("key1", 4, false));
System.out.println("星期六:" + jedis.setbit("key1", 5, true));
System.out.println("星期日:" + jedis.setbit("key1", 6, true));
System.out.println("星期四:" + jedis.getbit("key1", 3));
System.out.println("这星期登录了多少天:" + jedis.bitcount("key1"));
System.out.println("清空数据库:" + jedis.flushDB());
jedis.close();
springboot操作数据:spring-data redis jpa mongodb jdbc
springboot 2.x及以上版本已经将lettuce替换成jedis
jedis:采用直连,多线程下不安全,使用 jedis pool连接池又会造成性能低的问题。
lettuce:采用 netty,实例可被多个线程共享,线程安全, 性能高。
springboot配置类都会有一个自动配置类,而自动配置类都会绑定
一个.properties文件。找到文件,了解底层是如何实现的,方便自
定义template。
如下图所示:3.0.2的springboot版本已经不推荐以前的写法了。
正确写法如下图:许多配置自己查看并测试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring.data.redis.host=127.0.0.1
spring.data.redis.port=6379
// redisTemplate.opsForValue();操作字符串
// redisTemplate.opsForList();操作list
// redisTemplate.opsForSet();操作set
// redisTemplate.opsForHash();操作hash
// redisTemplate.opsForZSet();操作zset
// redisTemplate.opsForGeo();操作geo
// redisTemplate.opsForHyperLogLog();操作hyperloglog
redisTemplate.opsForValue().set("name", "zhangsan");
System.out.println(redisTemplate.opsForValue().get("name"));
测试完成
使用redis存储java对象时,如果java对象没有序列化,会报错,如
下图。
虽然可以在User类实现序列化接口, 也可以用fastjson进行序列化
,但是这种方式不推荐,每个对象都要实现接口或者使用jackson序
列化。推荐自定义template。
redis默认使用jdk序列化
如图所示:key需要转义。
1、自己写一个config类,并注解为配置类。
@Bean @ConditionalOnSingleCandidate(RedisConnectionFactory.class) @SuppressWarnings("all") public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<String, Object>(); template.setConnectionFactory(factory); //jackson的序列化配置 Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper); //String的序列化 StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); //String的key采用string序列化 template.setKeySerializer(stringRedisSerializer); //Hash的key采用string序列化 template.setHashKeySerializer(stringRedisSerializer); //String的value采用jackson序列化 template.setValueSerializer(serializer); //Hash的value采用jackson序列化 template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; }
2、注入redisTemplate时注意导入这个自定义template。
代码太长,百度就有。
1k => 1000 bytes
1kb => 1024 bytes
1m => 1000000 bytes
1mb => 10241024 bytes
1g => 1000000000 bytes
1gb => 10241024*1024 bytes
units are case insensitive so 1GB 1Gb 1gB are all the same.
redis对单位不敏感,即不区分大小写。
include /path/to/local.conf
include /path/to/other.conf
include /path/to/fragments/*.conf
可以包含上述配置文件
bind 127.0.0.1 -::1
绑定本机网卡ipprotected-mode yes
保护模式 开启port 6379
连接端口, 默认6379daemonize yes
守护进程方式运行,即后台运行pidfile /var/run/redis_6379.pid
和守护进程关联,进程文件loglevel notice
日志等级logfile ""
日志文件, “”标志性输出databases 16
默认16个数据库always-show-logo no
logo总是显示save 3600 1 300 100 60 10000
持久化规则redis是内存数据库, 内存是断电即逝的。需要持久化。
3600s内发生至少1个修改,写入到文件。
300s内发生至少100个修改,写入到文件。
60s内发生至少10000个修改,写入到文件。
stop-writes-on-bgsave-error yes
发送错误是否继续写入rdbcompression yes
rdb文件是否压缩rdbchecksum yes
rdb是否检查校验dbfilename dump.rdb
rdb文件名字maxclients 10000
默认最大10000个客户端连接maxmemory-policy noeviction
内存不足的应对策略(5种)requirepass magebyte
设置密码,默认没密码appendonly no
是否开启aofappendfilename "appendonly.aof"
aof文件名字appendfsync everysec
每一秒写入aof文件,并完成磁盘同步appendfsync always
总是写入aof文件,并完成磁盘同步appendfsync no
写入aof文件,不等待磁盘同步no-appendfsync-on-rewrite
aof重写sentinel monitor host port 1
监控master主节点在指定的时间间隔内,将内存的数据集快照写入磁盘rdb文件,恢复时将rdb文件读到内存。
rdb文件位置:
rdb文件名默认dump.rdb, 可以在 redis.conf 配置文件内修改。
快照触发机制:
1、save time count, 当time内修改了count次,就会生成一个rdb文件。
2、执行 flushall 也会生成一个rdb文件。
3、退出redis也会生成一个rdb文件。
优点:1、适合大规模数据的恢复!
2、数据完整性不高的可以使用,高效率。
缺点:1、需要间隔一定时间操作,redis突然宕机就会失去最后一次修改的数据。
2、folk 进程需要占用内存空间。
以日志的记录每个 写指令 ,将redis所有的指令记录下来,读操作不记录,只许追加文件,不许改写文件。redis启动时会读取该文件,重构数据。(aof的文件是默认是appendonly.aof, 可以改配
置文件)修改配置文件appendonly yes, 重启redis即可生效。
当aof文件有埙坏,redis就跑不起来。此时需要使用redis提供的修复文件, redis-check-aof --fix ,执行该指令即可修复,redis就可以跑起来了,但错误的数据被丢弃了!
优点: 1、数据的完成性高一些,可以设置redis每个修改都执行写入aof文件!
2、设置不同步aof,效率非常高!
缺点:1、aof数据文件远远大于rdb文件,修复速度也远远低于rdb文件!
2、aof运行效率要比rdb文件慢, 所有redis默认使用rdb进行持久化!
3、将同步到aof文件配置成 everysec ,可能会丢失1s的数据!
4、持续io,降低效率!
aof重写规则
当aof文件大于64mb, 就会再folk一个进程来重写!
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis 发布订阅(pub/sub)实现了消息系统,发送者(在redis术语中称为发布者)在接收者(订阅者)接收消息时发送消息。传送消息的链路称为信道。在Redis中,客户端可以订阅任意数量的信道。
1、基于事件的系统中,Pub/Sub是目前广泛使用的通信模型,它采用事件作为基本的通信机制,提供大规模系统所要求的松散耦合的交互模式:订阅者(如客户端)以事件订阅的方式表达出它有兴趣接收的一个事件或一类事件;发布者(如服务器)可将订阅者感兴趣的事件随时通知相关订阅者。
2、 消息发布者,即publish客户端,无需独占链接,你可以在publish消息的同时,使用同一个redis-client链接进行其他操作(例如:INCR等)
3、 消息订阅者,即subscribe客户端,需要独占链接,即进行subscribe期间,redis-client无法穿插其他操作,此时client以阻塞的方式等待“publish端”的消息;这一点很好理解,因此subscribe端需要使用单独的链接,甚至需要在额外的线程中使用。
PUBLISH channel message #将信息发送到指定的频道。
SUBSCRIBE channel [channel …] #订阅给定的一个或多个频道的
信息。
UNSUBSCRIBE [channel [channel …]] #退订给定的频道。
PSUBSCRIBE pattern [pattern …] #订阅一个或多个符合给定模式
的频道。根据模式来订阅,可以订阅许多频道。
PUNSUBSCRIBE [pattern [pattern …]] #退订所有给定模式的频道。
PUBSUB subcommand [argument [argument …]] #查看订阅与发布系统状态。
发送端:
接收端:
1、实时消息系统。
2、实时聊天。
3、订阅、关注…
1、Redis使用异步复制,2.8版本开始,从服务器会以每秒一次的频率向主服务器报告复制流(replication stream)的处理进度。
2、一个主服务器可以有多个从服务器。
3、不仅主服务器可以有从服务器,从服务器也可以有自己的从服务器,多个从服务器之间可以构成一个图状结构。
4、复制功能不会阻塞主服务器:即使有一个或多个从服务器正在进行初次同步, 主服务器也可以继续处理命令请求。
5、复制功能也不会阻塞从服务器:只要在 redis.conf 文件中进行了相应的设置, 即使从服务器正在进行初次同步, 服务器也可以使用旧版本的数据集来处理命令查询。
6、在从服务器删除旧版本数据集并载入新版本数据集的那段时间内,连接请求会被阻塞。
7、还可以配置从服务器,让它在与主服务器之间的连接断开时,向客户端发送一个错误。
8、复制功能可以单纯地用于数据冗余(data redundancy),也可以通过让多个从服务器处理只读命令请求来提升扩展(scalability): 比如说,繁重的SORT命令可以交给附属节点去运行。
9)可以通过复制功能来让主服务器免于执行持久化操作:只要关闭主服务器的持久化功能,然后由从服务器去执行持久化操作即可。
1、数据冗余:实现数据的热备份,是持久化之外的另一种方式。
2、故障恢复:当主节点(master)出现问题时,可以由从节点(slave)来提供服务,实现了快速恢复故障,也就是服务冗余。
3、读写分离:master主要是写,slave主要是读,可以提高服务器的负载能力。同时可以根据需求的变化,添加从节点的数量。
4、负载均衡:配合读写分离,有主节点提供写服务,从节点提供读服务,分担服务器负载,尤其在写少读多的情况下,通过多个从节点分担读负载,可以大大提高redis服务器的并发量和负载。
5、高可用的基石:主从复制是哨兵和集群能够实施的基础,因此我们可以说主从复制是高可用的基石。
修改端口port、pid文件、rdb文件、log文件
查看主从信息(命令:info replication)
6380主从信息
6379主从信息
主机写入k1 v1
从机读取k1成功
从机不能写入
1、将主机shutdown,从机虽然写不了数据,但仍然能读到数据。
2、将从机shutdown,再连接,从机成为了主机,因为是用命令设
置主从关系的。重启就会恢复。通常是在配置文件设置主从关系。
3、从机重新认6379作主机后,扔能访问到k1,是全量复制。
4、主机此时写入k5 v5, 从机仍能读到k5, 是增量复制。
1、6380认6379作master, 而6381认6380作master。此时6380仍然是从节点,依旧不能执行写操作。
2、此时6379宕机了, 从节点可以自己当master,命令slaveof no one
,此时就算之前的主节点恢复了也没有了从节点,需要重新手动配置。
主从切换技术:当主机宕机后,需要手动把一台从(slave)服务器切换为主服务器,这就需要人工干预,费时费力,还回造成一段时间内服务不可用,所以推荐哨兵架构(Sentinel)来解决这个问题。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵模式有两个作用:
1、通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器当哨兵监测到Redis主机宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他服务器,修改配置文件,让他们换主机。
2、当一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此可以使用哨兵进行监控, 各个哨兵之间还会进行监控,这就形成了多哨兵模式。
假设主服务器宕机,哨兵1先检测到结果,但是系统并不会马上进行failover过程,仅仅是哨兵1主观认为主服务器不可以用,这个现象称为主观下线,当后面的哨兵也检测到主服务器不可用,并且数量达到一定时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover故障转移操作。操作转移成功后。就会发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这一过程称为客观下线。
sentinel monitor mysentinel 127.0.0.1 6379 1
sentinel monitor (被监控的名称) host port 1 (1是监测到主机宕机后,会投出1票)
将6379关掉
如上图所示:选举了6380为master。
当主机重新连接时,只能被抓去当6380的从机了。。。
优点:
1.哨兵集群,基于主从复制模式,所以有主从配置的所有优点。
2.主从可以切换,故障可以转移,系统可用性就更好。
3.哨兵模式是主从模式的升级,手动到自动,更完善。
缺点:
1.Redis不好在线扩容,集群容量一旦达到上限,扩容麻烦。
2.实现配置麻烦。
通常流程是:一个请求过来,先查询是否在缓存当中,如果缓存中存在,则直接返回。如果缓存中不存在对应的数据,则检索数据库,如果数据库中存在对应的数据,则更新缓存并返回结果。如果数据库中也不存在对应的数据,则返回空或错误。
缓存穿透(cache penetration)是用户访问的数据既不在缓存当中,也不在数据库中。出于容错的考虑,如果从底层数据库查询不到数据,则不写入缓存。这就导致每次请求都会到底层数据库进行查询,缓存也失去了意义。当高并发或有人利用不存在的Key频繁攻击时,数据库的压力骤增,甚至崩溃,这就是缓存穿透问题。
分析业务请求,如果是正常业务请求时发生缓存穿透现象,可针对相应的业务数据,在数据库查询不存在时,将其缓存为空值(null)或 默认值。需要注意的是,针对空值的缓存失效时间不宜过长,一般设置为5分钟之内。当数据库被写入或更新该key的新数据时,缓存必须同时被刷新,避免数据不一致。
在业务请求的入口处进行数据合法性校验,检查请求参数是否合理、是否包含非法值、是否恶意请求等,提前有效阻断非法请求。比如,根据年龄查询时,请求的年龄为-10岁,这显然是不合法的请求参数,直接在参数校验时进行判断返回。
在写入数据时,使用布隆过滤器进行标记(相当于设置白名单),业务请求发现缓存中无对应数据时,可先通过查询布隆过滤器判断数据是否在白名单内,如果不在白名单内,则直接返回空或失败。
当发生异常情况时,实时监控访问的对象和数据,分析用户行为,针对故意请求、爬虫或攻击者,进行特定用户的限制;
缓存击穿和缓存雪崩很类似,只不过是缓存击穿是一个热点key失效,而缓存雪崩是大量热点key失效。因此,可以将缓存击穿看作是缓存雪崩的一个子集。
在使用缓存时,通常会对缓存设置过期时间,一方面目的是保持缓存与数据库数据的一致性,另一方面是减少冷缓存占用过多的内存空间。但当缓存中大量热点缓存采用了相同的实效时间,就会导致缓存在某一个时刻同时实效,请求全部转发到数据库,从而导致数据库压力骤增,甚至宕机。从而形成一系列的连锁反应,造成系统崩溃等情况,这就是缓存雪崩(Cache Avalanche)。
除了缓存过期,由于某些原因导致缓存服务宕机、挂掉或不响应也会发生缓存雪崩。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。