Redis 概述
redis 介绍
Redis(Re mote Di ctionary S erver ),即远程字典服务。是一个开源的使用ANSI C语言 编写、支持网络、可基于内存 亦可持久化 的日志型、Key-Value数据库 ,并提供多种语言的API。[百度百科]
官网介绍
Redis是一个开源(BSD许可),内存数据结构存储,用作数据库,缓存和消息代理。 Redis提供数据结构,例如字符串,哈希列表,集合,排序集,带有范围查询,位图,超级目录,地理空间索引和流。 Redis拥有内置复制,Lua Scripting,LRU驱逐,事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis Cluster自动分区提供高可用性
redis 用途
内存存储,持久化,持久化机制(rdb,aof)
高效,用于高速缓存
发布订阅系统
地图信息分析
计量器,计数器
。。。
redis redis 默认16个db 使用第0个
端口6379 是因为作者的偶像的9键名字。
redis 是单线程的
redis是很快的,官方介绍,Redis基于内存操作,CPU不是Redis的性能瓶颈,Redis的瓶颈瑟吉欧根据机器的内存和网络带宽,既然可以单线程实现,就使用单线程了。
Redis是C语言写的。挂房提供数据:100000+ 的QPS, 和Memecache不相上下。
为什么Redis是单线程还这么快?
单线程和多线程认识误区:
高性能服务器不 一定是多线程。
多线程(CPU是上下文切换)一定不比 多线程高
速度:CPU>内存>硬盘。 cpu切换非常耗时间。
reids将所有数据存放内存中。所以单线程去操作效率就是最高的,多线程(CPU上下文切换消耗时间),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都在同一个CPU上的,在内存情况下,这就是最佳方案。
Redis -Benchmark 性能测试 1 redis-benchmark [option] [option value]
参数如下:
序号
选项
描述
1
-h
指定服务器主机名
127.0.0.1
2
-p
指定服务器端口
6379
3
-s
指定服务器 socket
4
-c
指定并发连接数
50
5
-n
指定请求数
10000
6
-d
以字节的形式指定 SET/GET 值的数据大小
2
7
-k
1=keep alive 0=reconnect
1
8
-r
SET/GET/INCR 使用随机 key, SADD 使用随机值
9
-P
通过管道传输 请求
1
10
-q
强制退出 redis。仅显示 query/sec 值
11
–csv
以 CSV 格式输出
12
***-l*(L 的小写字母)**
生成循环,永久执行测试
13
-t
仅运行以逗号分隔的测试命令列表。
14
***-I*(i 的大写字母)**
Idle 模式。仅打开 N 个 idle 连接并等待。
安装 Docker 1 2 3 4 5 docker run -d --name redis -p 6379:6379 --restart unless-stopped -v /home/redis/data:/data -v /home/redis/conf/redis.conf:/etc/redis/redis.conf -d redis:buster redis-server /etc/redis/redis.conf -------- 自启 /usr/local/bin/redis-server /usr/soft/redis/redis-6.2.5/etc/redis.conf
Redis Key命令 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 select num: 切换第num个数据库 DBSIZE: 查看dbSize keys * : 查看keys flushdb :清空 flushAll : 清空所有 move key (1 数据库): 移除key 一般不用??? ------------------- set key val : 插入 mset 批量添加 ------------------- mget 批量获取 expire key time: time后失效 setex key time val : time后失效 setinx : 不存在设置 setnx : 不存在创建 ttl key : 实时查看过期时间 type kay : 查看 key的类型 Exists key : 是否存在 ------------------- i++ decr/ incr key : 减一/加一 decrby/ incrby key num: 减/加 num -------------------- msetnx: 原子性 批量添加 getset key val: 不存在返回null 存在 返回旧 插入新 ==CAS==
基本类型 String
append key : 追加Str strlen key : 返回长度 getRange key star end : 截取长度【star , end】 setRange key star end “str” : 替换[s到n]字符串
List
lpush key val : 头部从左近入
Rpush : 尾部右插入
Lpop key : 移出左侧
rPop : 右侧移出
lRange key a b 获取第[a,b]数据
lindex key val 获取某一个值
llen : 长度
trim a,b: 截取 剩下 [a,b] 的数据
rpoplPush souce tar : 右侧移除最后一个 左侧添加一个
Set
sadd key val : 添加
smembers key : 查看
sismember key val 查看是否存在set中
srem 移除
srandmember key num随机查询num个数据
sdiff fox king # 查看fox集合的差集
sinter fox king # 查看两个集合的交集
sunion fox king # 查看两个集合的并集
srem fox one #移除set集合中指定的元素
spop fox 随机删除
smove fox king 1 # 讲一个指定的值、移动到另外一个set集合中
hash
hash是一个String类型的field(字段)和value(值)的映射表,hash特别适合于存储对象。
Redis中每个Hash可以存储232-1键值对(40多亿)
hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息!hash更适于对象是存储,String更加适合字符串!!
hset name keys vals 添加
hget name key
hgetall name 查看
–
hkeys name 查看key
hvals name 查看val
hlen name 数量
hexists name key 是否存在
hdel name key 删除 name的 key段
hincrby name key num #指定增量 加num
hsetnx name key val #若不存在、则可以设置、反之不可用设置
ZSet(sorted set:有序集合)
zadd name ks vs #添加值
zcard fox #获取有序集合的成员数
zrange 获取值
zrangebyscore name -inf +inf # 查看全部用户,并从小到大排序
特殊类型 geospatial
getadd 添加地理位置 规则:南北两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
参数 key 值(纬度、经度、名称) (-180度到180度)
纬度: -85.05112878度到85.05112878度 geoadd china:city 116.40 39.90 beijing
geopos china:city hangzhou beijing # 获取指定的城市的经度 和 维度!
geodist china:city hangzhou beijing km geodist 返回两个给定位置之间的距离
GEORADIUS china:city 110 30 1000 km # 以110,30这个经纬度为中心,寻找方圆1000km内的城市 GEORADIUS china:city 110 30 500 km withdist # 显示到中间位置的直线距离 半径500km GEORADIUS china:city 110 30 500 km withcoord # 显示到中心距离半径500km的城市 + 经纬度信息 GEORADIUS china:city 110 30 500 km withcoord withdist count 3 # 筛选出指定结果 GEORADIUSBYMEMBER china:city beijing 1000 km # 找出位于指定范围内的元素,中心点是由给定的位置元素决定
hyperloglog 位图
基数
基数(一个集合中不重复的元素) 统计疫情感染人数:存身份标识码 优点:占用的内存是固定的,2^64不同的元素的技术,只需要废12kb内存。如果要从内存角度来比较的话Hyperloglog首选! 如果允许容错,那么一定可以使用Hyperloglog 0.81的错误率 如果不允许容错,就使用 set 或 自己的数据类型 即可
pfadd mykey a b c d e f g h i j # 创建第一组元素 mykey
PFCOUNT mykey # 统计mykey元素的基数数量
PFMERGE mykey3 mykey mykey2 # 合并两组 mykey mykey2=>mykey3并集
bitmap 位图
位存储 用于 打卡 登录与否 这类 0/1 状态切换的类型 1字节 = 8位
setbit name a b 输入第a条数据为b(0/1)
get name x 获取第x天的数据
bitcount name 查看 数据
事务
Redis事务本质:一组命令的集合!一个事务中所有命令都会被序列化,在事务执行过程中,会按照顺序执行!一次性、顺序性、排他性!执行一系列的命令! Redis事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!Exec
Redis单条命令是保证原子性的,但是事务不保证原子性!
Redis的事务: 一个事务从开始到执行会经历以下三个阶段:
开启事务(MULTI) 命令入队() 执行事务(EXEC)
MULTI 标记一个事务块的开始。
EXEC 执行所有事务块内的命令。
DISCARD 取消事务,放弃执行事务块内的所有命令。
UNWATCH 取消 WATCH 命令对所有 key 的监视。
WATCH key [key …] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
事务错误异常
编译型异常(代码有问题、命令有错)、事务中所有的命令都不会被执行
运行时异常(1/0),如果事务队列中存在语法性、那么执行命令的时候、其他命令是可以正常执行的、错误命令抛出异常!
乐观锁
watch 监控
悲观锁:
乐观锁:
认为不会出问题,只有需要的时候才会上锁。更新数据的时候回去判断一下,在此期间是否数据被修改。
获取version
更新时比较version
watch 监视 unwatch 解锁 提交失败 所有都失败
1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379>watch money OK 127.0.0.1:6379>mu1ti OK 127.0.0.1:6379>DECRBY money 10 QUEUED 127.0.0.1:6379>INCRBY out 10 QUEUED 127.0.0.1;6379> exec (ni1)
jedis 我们要使用java来操作Redis
什么是Jedis? 是Redis官方推荐的java连接开发工具!使用java操作Redis的中间件!如果要使用java操作Redis、一定要对Jedis十分的熟悉!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependencies > <dependency > <groupId > redis.clients</groupId > <artifactId > jedis</artifactId > <version > 3.4.1</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > fastjson</artifactId > <version > 1.2.75</version > </dependency > </dependencies >
string 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public static void main (String[] args) { Jedis jedis = new Jedis ("8.*.*.76" , 6379 ); System.out.println("清空数据:" + jedis.flushDB()); System.out.println("判断某个键是否存在:" + jedis.exists("username" )); System.out.println("新增<'username','username'>的键值对:" + jedis.set("username" , "username" )); System.out.println("新增<'password','password'>的键值对:" + jedis.set("password" , "password" )); System.out.println("系统中所有的键如下:" ); Set<String> keys = jedis.keys("*" ); System.out.println(keys); System.out.println("删除键password:" + jedis.del("password" )); System.out.println("判断键password是否存在:" + jedis.exists("password" )); System.out.println("查看username所存储的值的类型:" + jedis.type("username" )); System.out.println("随机返回key空间的一个:" + jedis.randomKey()); System.out.println("重命名key:" + jedis.rename("username" , "name" )); System.out.println("取出改后的name:" + jedis.get("name" )); System.out.println("按索引查询:" + jedis.select(0 )); System.out.println("删除当前选择数据库中的所有key:" + jedis.flushDB()); System.out.println("返回当前数据库中的key的数目:" + jedis.dbSize()); System.out.println("删除所有数据库中的所有key:" + jedis.flushAll()); }
list 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public static void main (String[] args) { Jedis jedis = new Jedis ("8.*.*.*" , 6379 ); jedis.flushDB(); System.out.println("==========增加数据==========" ); 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" ,"valueChanged" )); System.out.println("获取key1的值:" + 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" , "key03" , "value03" )); System.out.println("获取多个键值对:" + jedis.mget("key01" , "key02" , "key03" )); System.out.println("删除多个键值对:" + jedis.del("key01" , "key02" )); System.out.println("获取多个键值对:" + jedis.mget("key01" , "key02" ,"key03" )); jedis.flushDB(); System.out.println("=============新增键值对防止覆盖原先值=================" ); System.out.println(jedis.setnx("key1" , "value1" )); System.out.println(jedis.setnx("key2" , "value2" )); System.out.println(jedis.setnx("key2" , "value2-new" )); System.out.println(jedis.get("key1" )); System.out.println(jedis.get("key2" )); System.out.println("新增键值对并设置有效时间" ); System.out.println(jedis.setex("key3" , 2 , "value3" )); 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("================获取原值,更新为新值==============" ); System.out.println(jedis.getSet("key2" , "key2GetSet" )); System.out.println(jedis.get("key2" )); System.out.println("获得key2的值的字串:" + jedis.getrange("key2" , 2 , 4 )); }
set 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public static void main (String[] args) { Jedis jedis = new Jedis ("*.*.*.*" , 6379 ); jedis.flushDB(); System.out.println("==============向集合中添加元素(不重复)==============" ); System.out.println(jedis.sadd("eleSet" , "e1" , "e2" , "e3" , "e4" , "e5" , "e6" , "e7" )); System.out.println(jedis.sadd("eleSet" , "e8" )); System.out.println(jedis.sadd("eleSet" , "e8" )); System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet" )); System.out.println("删除一个元素e1:" + jedis.srem("eleSet" , "e1" )); System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet" )); System.out.println("删除两个元素e7和e4:" + jedis.srem("eleSet" , "e7" , "e4" )); System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet" )); System.out.println("随机的移除集合中的一个元素:" + jedis.spop("eleSet" )); System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet" )); System.out.println("eleSet中包含元素的个数:" + jedis.scard("eleSet" )); System.out.println("e3是否在eleSet中:" + jedis.sismember("eleSet" , "e3" )); System.out.println("===========================================" ); System.out.println(jedis.sadd("eleSet1" , "e1" , "e2" , "e3" , "e4" , "e5" , "e6" , "e7" )); System.out.println(jedis.sadd("eleSet2" , "e1" , "e2" , "e3" , "e4" , "e5" )); System.out.println("将eleSet1中删除e1并存入eleSet3中:" + jedis.smove("eleSet1" , "eleSet3" , "e1" )); System.out.println("将eleSet1中删除e2并存入eleSet3中:" + jedis.smove("eleSet1" , "eleSet3" , "e2" )); System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1" )); System.out.println("eleSet3中的元素:" + jedis.smembers("eleSet3" )); System.out.println("==================集合运算=================" ); System.out.println("eleSet1中的元素:" + jedis.smembers("eleSet1" )); System.out.println("eleSet2中的元素:" + jedis.smembers("eleSet2" )); 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" )); System.out.println("eleSet1和eleSet2的交集个数为:" +jedis.sinterstore("eleSet4" , "eleSet1" , "eleSet2" )); System.out.println("把eleSet1和eleSet2的交集存储到eleSet4中、eleSet4的元素为:" +jedis.smembers("eleSet4" )); }
hash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public static void main (String[] args) { Jedis jedis = new Jedis ("*.*.*.*" , 6379 ); jedis.flushDB(); HashMap<String, String> map = new HashMap <>(); map.put("key1" , "value1" ); map.put("key2" , "value2" ); map.put("key3" , "value3" ); map.put("key4" , "value4" ); System.out.println("=========添加名称为hash(key)的hash元素============" ); jedis.hmset("hash" , map); System.out.println("===向名称为hash的hash中添加key为key5,value为value5的元素====" ); jedis.hset("hash" , "key5" , "value5" ); System.out.println("散列hash的所有键值对为:" +jedis.hgetAll("hash" )); System.out.println("散列hash的所有键为:" +jedis.hkeys("hash" )); System.out.println("散列hash的所有值为:" +jedis.hvals("hash" )); System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:" +jedis.hincrBy("hash" ,"key6" ,6 )); System.out.println("散列hash的所有键值对为:" +jedis.hgetAll("hash" )); System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:" +jedis.hincrBy("hash" ,"key6" ,3 )); System.out.println("散列hash的所有键值对为:" +jedis.hgetAll("hash" )); System.out.println("删除一个或者多个键值对:" +jedis.hdel("hash" ,"key2" )); System.out.println("散列hash的所有键值对为:" +jedis.hgetAll("hash" )); System.out.println("散列hash中键值对的个数:" +jedis.hlen("hash" )); System.out.println("判断hash中是否存在key2:" +jedis.hexists("hash" ,"key2" )); System.out.println("判断hash中是否存在key3:" +jedis.hexists("hash" ,"key3" )); System.out.println("获取hash中的key3的值:" +jedis.hmget("hash" ,"key3" )); System.out.println("获取hash中的key3、key4的值:" +jedis.hmget("hash" ,"key3" ,"key4" )); }
事务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public static void main (String[] args) { Jedis jedis = new Jedis ("服务器IP地址" , 6379 ); jedis.auth("密码" ); jedis.flushDB(); JSONObject jsonObject = new JSONObject (); jsonObject.put("hello" , "world" ); jsonObject.put("name" , "fox" ); Transaction multi = jedis.multi(); String string = jsonObject.toJSONString(); try { multi.set("user1" , string); multi.set("user2" , string); 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 整合
在springboot2.X之后,原来使用的jedis被替换为了lettuce
jedis:采用的直接连接,多个线程操作的话,会不安全,如果要避免不安全,使用jedis Pool连接池 类似BIO模式。
lettuce:采用nett ,实例可以再多个线程中共享,不存在线程不安全的情况!可以减少线程数据,更像NIO模式。
1 2 3 4 5 6 spring.redis.host =8.131.86.76 spring.redis.port =6379 spring.redis.password =密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @SpringBootTest class Redis02SpringbootApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void contextLoads () { redisTemplate.opsForValue().set("mykey" , "fox" ); System.out.println(redisTemplate.opsForValue().get("mykey" )); } }
序列化问题
SpringBoot Redis Template 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate (RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> template = new RedisTemplate <String, Object>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer <>(Object.class); ObjectMapper om = new ObjectMapper (); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer (); template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setValueSerializer(jackson2JsonRedisSerializer); template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
实体需要序列化
redis.conf 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 # Redis configuration file example. # # Note that in order to read the configuration file, Redis must be # started with the file path as first argument: # 开始启动时必须如下指定配置文件 # ./redis-server /path/to/redis.conf # # Note on units: when memory size is needed, it is possible to specify # it in the usual form of 1k 5GB 4M and so forth: # # 存储单位如下所示 # 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1mb => 1024*1024 bytes # 1g => 1000000000 bytes # 1gb => 1024*1024*1024 bytes ################################## INCLUDES ################################### # 如果需要使用多配置文件配置redis,请用include(包含) # # include /path/to/local.conf # include /path/to/other.conf ################################## MODULES ##################################### # modules手动设置加载模块(当服务无法自动加载时设置) # # loadmodule /path/to/my_module.so # loadmodule /path/to/other_module.so ################################## NETWORK (网络) ##################################### # Examples: # # bind 192.168.1.100 10.0.0.1 # bind 127.0.0.1::1 # # 设置绑定的ip bind 127.0.0.1 # 保护模式:不允许外部网络连接redis服务 protected-mode yes # 设置端口号 port 6379 # TCP listen() backlog. # # TCP 连接数,此参数确定了TCP连接中已完成队列(完成三次握手之后)的长度 tcp-backlog 511 # Unix socket. # # 通信协议设置,本机通信使用此协议不适用tcp协议可大大提升性能 # unixsocket /tmp/redis.sock # unixsocketperm 700 # TCP keepalive. # 定期检测cli连接是否存活 tcp-keepalive 300 ################################# GENERAL ##################################### # 是否守护进程运行(后台运行) daemonize yes # 是否通过upstart和systemd管理Redis守护进程 supervised no # 以后台进程方式运行redis,则需要指定pid 文件 pidfile /var/run/redis_6379.pid # 日志级别 # 可选项有: # debug(记录大量日志信息,适用于开发、测试阶段); # verbose(较多日志信息); # notice(适量日志信息,使用于生产环境); # warning(仅有部分重要、关键信息才会被记录)。 loglevel debug # 日志文件的位置 logfile "" # 数据库的个数 databases 16 # 是否显示logo always-show-logo yes ################################ SNAPSHOTTING(快照) ################################ # Save the DB on disk: # # 持久化操作设置 900秒内触发一次请求进行持久化,300秒内触发10次请求进行持久化操作,60s内触发10000次请求进行持久化操作 save 900 1 save 300 10 save 60 10000 # 如果 bgsave 存储快照失败,那么 redis 将阻止数据继续写入 (false:不会停止对外服务) stop-writes-on-bgsave-error false # 使用压缩rdb文件 yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间 rdbcompression yes # 是否校验rdb文件,更有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗 rdbchecksum yes # dbfilename的文件名 dbfilename dump.rdb # dbfilename文件的存放位置 dir ./ ################################# REPLICATION(复制) ################################# # replicaof(复制) 即slaveof 设置主结点的ip和端口 # replicaof <masterip > <masterport > # 集群节点访问密码 # masterauth(主) <master-password > # 从结点断开后是否仍然提供数据 replica-serve-stale-data yes # 设置从节点是否只读 replica-read-only yes # 是或否创建新进程进行磁盘同步设置 repl-diskless-sync no # master节点创建子进程前等待的时间 repl-diskless-sync-delay 5 # replicas发送PING到master的间隔,默认值为10秒。 # repl-ping-replica-period 10 # # repl-timeout 60 # repl-disable-tcp-nodelay no # # repl-backlog-size 1mb # # repl-backlog-ttl 3600 # replica-priority 100 # # min-replicas-to-write 3 # min-replicas-max-lag 10 # # replica-announce-ip 5.5.5.5 # replica-announce-port 1234 ################################## SECURITY(安全) ################################### # 设置连接时密码 # requirepass 一定要有密码 ################################### CLIENTS(客户端) #################################### # 最大连接数 # maxclients 10000 ############################## MEMORY MANAGEMENT(内存管理) ################################ # redis配置的最大内存容量 # maxmemory <bytes > # 内存达到上限的处理策略 # maxmemory-policy noeviction # ---------------------------------6种策略---------------------------------------------- 1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 2、allkeys-lru : 删除lru算法的key 3、volatile-random:随机删除即将过期key 4、allkeys-random:随机删除 5、volatile-ttl : 删除即将过期的 6、noeviction : 永不过期,返回错误 # 处理策略设置的采样值 # 清理时会根据用户配置的maxmemory-policy来做适当的清理(一般是LRU或TTL),这里的LRU或TTL策略并不是针对redis的所有key,而是以配置文件中的maxmemory-samples个key作为样本池进行抽样清理。 # 如果增加,会提高LRU或TTL的精准度,redis作者测试的结果是当这个配置为10时已经非常接近全量LRU的精准度了 # maxmemory-samples 5 # 是否开启 replica 最大内存限制 # replica-ignore-maxmemory yes ############################# LAZY FREEING #################################### # 惰性删除或延迟释放 lazyfree-lazy-eviction no lazyfree-lazy-expire no lazyfree-lazy-server-del no replica-lazy-flush no ############################## APPEND ONLY MODE(aof) ############################### # 是否使用AOF持久化方式 默认使用RDB持久化 aof大部分没有rdb效率高 appendonly no # appendfilename的文件名 appendfilename "appendonly.aof" # 持久化策略 # appendfsync always appendfsync everysec # 每秒 会丢失当前一秒的数据 # appendfsync no # 持久化时(RDB的save | aof重写)是否可以运用Appendfsync,用默认no即可,保证数据安全性 no-appendfsync-on-rewrite no # 设置重写的基准值 auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 指定当发生AOF文件末尾截断时,加载文件还是报错退出 aof-load-truncated yes # 开启混合持久化,更快的AOF重写和启动时数据恢复 aof-use-rdb-preamble yes ################################ REDIS CLUSTER ############################### # 是否开启集群 # cluster-enabled yes # 集群结点信息文件 # cluster-config-file nodes-6379.conf # 等待节点回复的时限 # cluster-node-timeout 15000 # 结点重连规则参数 # cluster-replica-validity-factor 10 # # cluster-migration-barrier 1 # # cluster-require-full-coverage yes # # cluster-replica-no-failover no
设置密码 1 2 3 4 5 进入redis config get requirepass config set requirepass 123456 auth "密码" save
Redis 持久化 RDB (redis Datebase) 一般RDB 放在丛机上备份使用一般默认用这个
使用dump.rdb 保存
如果rdb文件有问题可以check redis-check-rdb
rdb 就是一个快照的概念
在redis操作过程中 fork出一个子进程去写一个临时的rdb文件 写完后 替换原来的快照文件 子进程结束 流程如图
执行bgsave命令,Redis父进程判断当前是否存在正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回。
父进程执行fork操作创建子进程,fork操作过程中父进程会阻塞,通过info stats命令查看latest_fork_usec选项,可以获取最近一个fork操作的耗时,单位为微秒。
父进程fork完成后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,可以继续响应其他命令。
子进程创建RDB文件,根据父进程内存生成临时快照文件,完成后对原有文件进行原子替换。执行lastsave命令可以获取最后一次生成RDB的时间,对应info统计的rdb_last_save_time选项。
进程发送信号给父进程表示完成,父进程更新统计信息,具体见info Persistence下的rdb_*相关选项。
触发机制
save 规则满足
手动执行 flushall
AOF(append only file) 追加文件 使用日志的方式 去存储 每一次写操作
如果aof文件有问题可以自动修复 redis-check-aof –fix
缺点:相对于数据文件aof大于rdb 读取速度也慢
aof 效率比rdb慢
aof重写: 如果文件太大 会新fork一个进程去重写
redis 发布订阅 redis 发布订阅(pub/sub)是一种消息通信模式:发布者发送消息,订阅者接收消息。
redis 主从复制 概念 主从复制: 是指将一台redis服务器的数据复制到其他的Redis服务器.前者称为主节点(master/leader), 后者称为从节点(slave/follower); 数据的复制是单向的, 只能由主节点到从节点. 所以,主节点写为主,从节点读为主
默认情况 , 每台redis 服务器都是主节点;而且一个主节点可以有0或多个从节点.
主从复制的作用:
数据冗余: 主节点复制实现了数据的热备份,是持久化之外的数据冗余方式.
故障恢复: 主节点出问题 从节点仍然可以提供服务,实现快速的故障恢复; –服务冗余
负载均衡:从主从复制的基础上 加上读写分离, 由主节点写 从节点提供读的服务; 分担服务器压力,尤其是读多写少的情况
高可用的基础: 除了上述作用,主从复制还是 哨兵和集群都能够实施的基础, 因此说主从复制是redis高可用的基础
主从复制, 读写分离; 架构中很常用
一般来说 redis 不会只有一个节点:
从结构上,单个redis 易发生故障且不易处理
单个redis服务器内存容量优先, 不可能将一台服务器的全部内存作为reids内存 单台redis内存超过20g就应该考虑主从复制了
主机写入自动写到从机 从机不能写 会error
从机重连会自动同步主机数据(全量复制)
Slave 启动连接到master 会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕后,master将传送整个数据文件到slave,完成同步
全量复制: slave在接收到数据后,将其存盘并加载到内存
增量复制: master 将新的所有收集的修改命令依次传给slave,完成同步
环境配置
redis默认自己是主库.所以只配置从库就行 配置了replicaof配置启动就是从机
一主二从 1 2 > slaveof [老大的ip] [老大的端口] > slaveod no one
主从复制的几种模式 主连从1连从2: 从2也会被同步
0060-redis主从复制的几种模型_登峰小蚁的博客-CSDN博客_主从复制模型
实际情况的主从安排有很多种 之后遇到在处理 [todo]
哨兵模式 (自动选举老大(主节点))
概述
主从切换技术需要人工干预,费是费力 效率低,更多我们会使用哨兵模式来做. Redis2.8开始正式提供了Sentinel(哨兵)架构解决问题.
检测主机状态,自动选举主机.
哨兵模式是一种特殊的模式, 除了redis提供的哨兵命令 redis-sentinel
, 哨兵是一个独立的进程,会独立运行.通过哨兵发送命令.等待redis服务器响应,从而监视多个redis的情况
哨兵的作用:
通过发送命令,让redis 服务器返回运行状态, 包括主服务器和从服务器.
当哨兵检测到master宕机, 会自动将slave切换为master, 通过发布订阅 通知其他的从机服务器,修改配置文件, 让他们切换主机.
一个人哨兵进程对redis服务器进行监控. 可能会出现问题. 所以 我们可以使用多个哨兵进行监控. 每个哨兵之间还会进行监控,这就是多哨兵模式
执行方式:假设主服务器宕机,哨兵1先检测到结果,系统不会立刻进行failover过程,仅仅是哨兵1主管认为主服务器不可用.这个被称为主观下线 .当后面的哨兵也检测到主服务器不可用时,并且到达一定数量.那么哨兵就会进行一次投票,投出一个哨兵发起failover[故障转移]操作.切换成功后,进行发布订阅模式,让哨兵自己监控的从服务器实现切换主机,这个过程为客观下线
投票算法(todo)
配置 1 2 3 4 5 6 7 protected-mode no sentinel monitor [myMasterName] [ip] [port] 2 sentinel auth-pass mymaster 123456
1 2 ./redis-sentinel ../sentinel.conf
哨兵模式的其他配置项
配置项
参数类型
作用
port
整数
启动哨兵进程端口 默认26379
dir
文件夹目录
哨兵进程服务临时文件夹,默认为/tmp,要保证有可写入的权限
sentinel down-after-milliseconds
[服务名称] [毫秒数(整数)]
指定哨兵在监控Redis服务时,当Redis服务在一个默认毫秒数内都无法回答时,单个哨兵认为的主观下线时间,默认为30000(30秒)
sentinel parallel-syncs
[服务名称] [服务器数(整数)]
指定可以有多少个Redis服务同步新的主机,一般而言,这个数字越小同步时间越长,而越大,则对网络资源要求越高
sentinel failover-timeout
[服务名称] [毫秒数(整数)]
指定故障切换允许的毫秒数,超过这个时间,就认为故障切换失败,默认为3分钟
sentinel notification-script
[服务名称] [脚本路径]
指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,比较常用
sentinel monitor
[master-name(字母A-z、数字0-9 “.-_”)] [ip] [redis-port] [quorum]
哨兵sentinel监控的redis主节点的 ip port quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
sentinel auth-pass
[master-name] [password]设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
sentinel client-reconfig-script
[master-name] [script-path]
客户端重新配置主节点参数脚本 一般都是由运维来配置!
sentinel down-after-milliseconds配置项只是一个哨兵在超过规定时间依旧没有得到响应后,会自己认为主机不可用。对于其他哨兵而言,并不是这样认为。哨兵会记录这个消息,当拥有认为主观下线的哨兵达到sentinel monitor所配置的数量时,就会发起一次投票,进行failover,此时哨兵会重写Redis的哨兵配置文件,以适应新场景的需要。
详细解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000 sentinel notification-script mymaster /var/redis/notify.sh sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
缺点:
配置麻烦
redis 扩容会更麻烦
redis 缓存穿透和雪崩 缓存穿透在` 简单说就是用户访问缓存数据(例redis) 但是缓存中不存在 就需要去查询数据库. 流程上正常,如果恶意攻击,或者数据量太大,数据库就会出问题(缓存就没有意义了)
解决方案
布隆过滤器 加到缓存层
对控制层数据进行检测 不符合情况直接返回.
缓存空对象
第一次访问缓存不存在.查询数据库如果不存在会在缓存层添加一个空对象
占用空间
缓存击穿 举例 微博服务器宕机
和缓存穿透性质差不多, 但是是某一个热点key在扛着大量的访问流量,这个热点key’突然失效了,就会直接打到数据库,大量访问以及数据会写导致宕机.就像在 一个围墙上击穿出现了个洞.比如数据过期
缓存击穿 是热点缓存数据失效. 缓存穿透 是因为空数据 查不到
解决方案
热点数据不过期
占空间 redis会清理
分布式锁
保证对每个key同时只有一个线程去查询后端数据库,其他线程没有获得锁的权限.因此只需要等待.将压力转移到了分布式锁上面.
缓存雪崩 缓存数据 大批集体失效.过期或者.缓存服务器宕机,直接打到后端数据库 会有问题
双十一: 停掉一些服务保证高可用
解决方案:
redis 高可用 多搞几个 (异地多活)
限流降级
预热 先加载数据
文章资料 官网:https://www.redis.io/
中文官网:http://www.redis.cn/
todo 布隆过滤器 布隆(Bloom Filter)过滤器——全面讲解,建议收藏_李子捌的博客-CSDN博客_布隆过滤器