Redis Redis是什么?
Redis是什么?
Redis(Re mote Di ctionary S erver ),即远程字典服务 ,是一个开源的使用ANSI C语言 编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库 ,并提供多种语言的API。称之为结构化数据库.
Redis能干什么?
Redis能干什么?
1.内存存储\持久化,内存中是断电即使,持久化很重要(rdb\aof)
2.效率高,用于高速缓存
3.发布订阅系统
4.地图信息分析
5.计时器\计数器
Redis特性
Redis特性
1.多样的数据类型
2.持久化
3.集群
4.事务
Redis推荐是在Linux服务器上搭建的
redis默认端口号6379
测试连接ping 返回pong
set name xxx
get name
Linux安装步骤 1.解压:tar -zxvf redis-6.2.6.tar.gz
2.进入到指定刚才解压的目录里面:cd redis-3.0.0
3.使用 make命令:make
4.接下来将redis的的安装到指定的目录里面(下面的那个redis的的不需要提前创建的)
使用PREFIX = / usr / local / src / redis安装至指定目录里面.
make install PREFIX=/usr/local/src/redis
Linux启动步骤: 在/usr/local/bin的目录下新建目录xconfig,然后在原redis目录(/home/xiaode/redis-6.2.6/)中复制redis.conf到xconfig下,命令:cp /home/xiaode/redis-6.2.6/redis.conf usr/local/bin/xconfig。
启动Redis服务,命令:./redis-server xconfig/redis.conf
确定服务开启,看进程,命令:ps -ef|grep redis
启动Redis客户端,命令:./redis-cli -p 6379
关闭服务在客户端中输入,命令:shutdown
退出客户端,命令:exit
1、首先保证本机能ping通linux的ip地址;如果不能请执行命令关闭防火墙
2、配置文件根目录使用vim redis.conf 打开 配置文件;确认在redis.conf配置文件中设置了requirepass 密码(找不到使用 / 查找)
1 #requirepass foobared``更改为``requirepass 123456
3、保证redis.conf中的 bind 127.0.0.1 被注释掉了
4、最终确认密码是否生效 ./redis-cli 登陆上客户端后输入auth 123456 回车;会返回OK
修改后台启动设置daemonize no改成yes redis-benchmark是一个压力测试工具 命令:redis-benchmark -h localhost -p 6379 -c 100 -n 100000 (100并发,100000连接)
Redis基础知识
Redis基础知识
默认有16个数据库,切换数据库,命令:select 2
查看数据库大小,命令:dbsize
查看所有的key,命令:keys *
清空数据库,命令:flushdb
问题:(error) MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.
这是由于强制停止redis快照,不能持久化引起的,运行info命令查看redis快照的状态,连接redis后运行 config set stop-writes-on-bgsave-error no 命令即可
清空所有数据库,命令:flushall
为什么redis默认端口是6379:粉丝效应
6379在是手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字。
Alessia Merz 是一位意大利舞女、女演员。 Redis 作者 Antirez 早年看电视节目,觉得 Merz 在节目中的一些话愚蠢可笑,Antirez 喜欢造“梗”用于平时和朋友们交流,于是造了一个词 “MERZ”,形容愚蠢,与 “stupid” 含义相同。MERZ长期以来被Redis作者antirez及其朋友当作愚蠢的代名词。
后来 Antirez 重新定义了 “MERZ” ,形容”具有很高的技术价值,包含技艺、耐心和劳动,但仍然保持简单本质“。
到了给 Redis 选择一个数字作为默认端口号时,Antirez 没有多想,把 “MERZ” 在手机键盘上对应的数字 6379 拿来用了。
为什么mysql默认端口是3306
3306是作者女儿的名字的九宫格打法,然后redis的6379也是,是作者喜欢的明星的名字的九宫格位置打法.
Redis是单线程的 ,基于内存操作,CPU不是Redis的性能瓶颈,瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程。
Redis为什么单线程还这么快? Redis是C语言写的,官方数据位100000+的QPS,完全不必同样使用key-value的Memecache差!
误区1:高性能的服务器一定是多线程的
误区2:多线程(CPU上下文切换)一定比单线程效率高
核心:redis将所有的数据全部放在内存中,所以使用单线程去操作效率最高,多线程会耗时操作,对于内存系统来说,没有CPU上下文切换,效率最高。
Redis五大基本数据类型
Redis五大基本数据类型
Redis-Key: 判断key是否存在,命令:exists key,存在返回1否则0
移除key,命令:move name 1(当前数据库)
设置过期时间,命令: expire key10(秒)
查看过期剩余时间,命令:ttl key
查看当前类型,命令:type key
String(字符串): 给value追加字符串,如果key不存在就会set,命令:append key xxx
查看value字符串长度,命令:strlen key1
自增1,命令:incr key
自减1,命令:decr key
步长增,命令:incrby key 10
步长减,命令:decrby key 10
value的字符串截取,命令:getrange key 0(start) 3 (end) [0-3]
0到 -1截取全部的字符串
value的字符串替换,命令:setrange key 0(从第几位开始替换位置) xx(替换值)
set的同时设置过期时间,命令:setex key 30 value
key已过期(不存在)则设置,未过期(存在)不设置,命令:(m)setnx key vlue
批量设置key,命令:mset k1 v1 k2 v2
批处理,msetnx key1 value1 key2 value2,要么一起成功,要么一起失败
对象:创建对象 1.set user:1 {name:zhangsan,age:23}
get user:1
2.mset user:1:name zhangsan user:1:age 23
mget user:1:name user:1:age
组合命令: getset user:1:name “lisi”,先获得后设置,不存在放回nil
string的应用:计数器,统计多单位的数量 ,粉丝数,对象缓存存储
List:基本数据类型,列表
list实际上是一个链表,值可以重复
消息队列(Lpush,Rpop),栈(Lpush,Lpop)
在redis里,我们可以把list玩成栈,队列,阻塞队列。
所有list命令都是以L开头的。
头部设置list值,命令:lpush list xxx
尾部设置list值,命令:rpush list xxx
通过区间获取值,命令:lrange list 0 -1
头部移除list值,命令:lpop list x(个数)
尾部移除list值,命令:rpop list x(个数)
获取单个值,命令:lindex list x(下标)
查看长度,命令:llen list
移除指定的值,命令:lrem list x(个数) 值,lrem list 2 b
截取,list会被改变,命令:ltrim list start end(从0开始)
组合命令,从旧list尾部移除,到新list头部添加
rpoplpush 当前list 新的list
可以更新list的值,前提是list存在元素,命令:lset list 0(下标) xxx
判断一个list是否存在,命令:exists list
linsert list before/after list值 插入值,before头部,after尾部
Set(集合)
set中的值不可以重复,无序的
设置set值,命令:sadd set xxx
查看set值,命令:smembers set
判断是否存在指定元素,命令:sismember set xxx
查看set的长度,命令:scard set
移除set的值,命令:srem set xxx
随机选取n个值,不写x默认一个,命令:srandmember set (x)
随机移除元素,命令:spop set x(个数)
将一个set的指定的值(kkk)移到新的set,命令:smove set set2 kkk
差集:sdiff set set2
交集:sinter set set2
并集:sunion set set2
set的应用:微博A所有关注的人放在set集合中,将它的粉丝也放在一个集合中,共同关注,共同爱好,六度分隔理论,二度好友
Hash(哈希map集合) 设置hashmap值,命令:hset hash key value
获得hashmap值,命令:hget hash key
批量设置,命令:hmset hash k1 v1 k2 v2
批量获取,命令:hmget hash k1 k2
获取所有的数据,命令:hgetall hash
删除指定的key,对应的value值也没了,命令:hdel hash key
获取hash的长度,命令:hlen hash
判断hash中指定字段是否存在,命令:hexists hash key
获取所有的key名,命令:hkeys hash
增加,命令:hincrby hash key value
减少,命令:hdecrby hash key value
存在不设置,不存在设置,命令:hsetnx hash key value
hash的应用:变更的数据,user的name和age,更适合对象的存储,string更适合存字符串。
Zset(有序集合) 在set的基础上,增加了一个值score
设置zset的值,命令:zadd zset 1(score) one
遍历zset的值,命令:zrange zset 0 -1
从小到大排序,命令:zrangebyscore zset -inf(负无穷) +inf(正无穷) withscores
范围:-inf(负无穷) +inf(正无穷),可以随便设置(-inf-2500)
移除元素,命令:命令:zrem zset key
查看zset的长度,命令:zcard zset
从大到小排序,命令:zrevrange zset 0 -1
获取区间的数量,命令:zcount zset 1 2
zset的应用:set排序,存储班级成绩,工资排序。排行榜,带权重的判断。
Redis三大特殊数据类型
Redis三大特殊数据类型
geospatial地理位置:在redis3.2版本推出,可以推算地理位置的信息,两地之间的距离,方圆几里的人 朋友的定位、附近的人、打车距离计算
添加地理位置,命令:geoadd china:city 112.98(经度) 28.11(纬度) changsha (两级无法添加,我们一般会下载城市数据,通过java程序一次性导入)
geoadd china:city 112.98 28.11 changsha
geoadd china:city 121.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing
geoadd china:city 114.08 22.54 shenzhen
geoadd china:city 120.15 30.28 hangzhou
geoadd china:city 125.14 42.92 xian
获取指定地理位置,命令:geopos china:city changsha
返回两个位置之间的距离,命令:geodist china:city changsha shanghai km(长沙到上海的 距离km)
单位:m米,km千米,mi英里,ft英尺
附近的人,通过半径来搜索,获取所有附近人的地址 。
以给定的经纬度为中心,找出某一半径内的元素,命令: georadius china:city 110 30 1000 km withdist(直线距离) withcoord(经纬度) count n(限制查出的长度)
(以当前位置(经度110,纬度30)为原点,半径为1000km搜索城市)
所有查询的城市必须被录入
以给定城市的经纬度为中心,找出其半径内的元素,命令: georadiusbymember china:city changsha 1000 km withdist(直线距离) withcoord(经纬度) count n(限制查出的长度)
geohash:该命令返回11个字符的Geohash字符串!转成一维字符串,字符串越接近就相隔越近。
Geo底层实现原理就是Zset,可以用zset命令操作geo
删除地理位置:zrem china:city changsha
Hyperloglog
什么是是基数?1,3,5,7
{1,3,5,7,8,7}
基数=5
基数:不重复的元素的个数
简介
Redis2.8.9版本更新了Hyperloglog数据结构
基数统计的算法,网页的UV页面访问量,一个人访问多次还是算一个人。
传统方式:set保存用户id,相同用户id则覆盖,用元素个数统计访问量。弊端:保持大量的用户id,就比较麻烦,目的是为了计数而不是保护用户id。
Hyperloglog优点:占用内存是固定的,2^64不同的元素计数,只占用12kb!
0.81%错误率,统计UV,可以忽略不计。
添加,命令: pfadd mkey a b c d e f g h i j
统计个数,命令:pfcount mkey
合并(重复计一次)到新key,命令:pfmerge mkey3 mkey mkey2
容许容错,可以使用
Bitmaps
位存储
统计疫情感染人数,没感染为0,感染为1
统计用户信息,不活跃为0,活跃为1
两个状态的都可以使用bitmaps
位图、数据结构,操作二进制进行记录,只有0和1
365天=365bit 1字节=8bit 46个字节左右!
设置值,命令:setbit sign 0(位置) 0/1(值)
打卡为1,未打卡为0
周一 0 周二 1 周三 0 周四 0 周五1…
setbit sign 0 0 setbit sign 1 1 setbit sign 2 0 setbit sign 3 0 setbit sign 4 1
查看某一天是否打卡getbit sign 3(周四)
统计打卡的天数,命令:bitcount sign (start end)
事务 MYSQL事务的四大特性(ACID):原子性、隔离性、一致性、持久性
Redis单条命令保证原子性,Redis事务不保证原子性,Redis事务没有隔离级别的概念。
所有命令在事务中并没有被执行,只有发起执行命令的时候才被执行。
事务本质:一组命令的集合!一个事务中所有命令都会被序列化,按照顺序执行,有排他性。
redis事务:
正常执行事务
开启事务,命令:multi
执行事务,命令:exec
1 2 3 4 5 6 7 8 9 10 11 12 13 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k1 a QUEUED 127.0.0.1:6379(TX)> set k2 b QUEUED 127.0.0.1:6379(TX)> set k3 c QUEUED 127.0.0.1:6379(TX)> exec 1) OK 2) OK 3) OK
撤销事务,命令:discard,事务队列中命令都不会执行
编译型异常(命令有错),事务中所有命令都不会执行
1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> getset k3 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379(TX)> set k5 a QUEUED 127.0.0.1:6379(TX)> exec (error) EXECABORT Transaction discarded because of previous errors. 127.0.0.1:6379> get k5 (nil)
运行时异常(1/0),事务中存在语法错误,其他命令是可以正常执行的,错误命令会抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k "a" QUEUED 127.0.0.1:6379(TX)> incr k QUEUED 127.0.0.1:6379(TX)> set k2 bbb QUEUED 127.0.0.1:6379(TX)> get k2 QUEUED 127.0.0.1:6379(TX)> exec 1) OK 2) (error) ERR value is not an integer or out of range 3) OK 4) "bbb"
监控!Watch(乐观锁)面试常问 悲观锁:认为什么时候都会出问题,无论做什么都会加锁
乐观锁:认为什么时候都不会出问题,不会上锁,更新数据时判断一下,在此期间是否有人修改这个数据。、
获取version
更新的时候比较version
Redis监视测试
1.正常执行成功
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 127.0.0.1:6379> set money 100 (总共100元) OK 127.0.0.1:6379> set out 0 (花了0元) OK 127.0.0.1:6379> watch money (监视money) OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> decrby money 20 QUEUED 127.0.0.1:6379(TX)> incrby out 20 QUEUED 127.0.0.1:6379(TX)> exec 1) (integer) 80 2) (integer) 20
2.使用watch充当乐观锁,有其他客户端突然插入,更新了数据
客户端A
1 2 3 4 5 6 7 8 9 127.0.0.1:6379> watch money OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> decrby money 10 QUEUED 127.0.0.1:6379(TX)> incrby out 10 QUEUED 127.0.0.1:6379(TX)>
客户端B突然执行,加了1000元
1 2 3 4 5 [xiaode@localhost bin]$ redis-cli 127.0.0.1:6379> get money "80" 127.0.0.1:6379> set money 1000 OK
客户端A监控,自动加锁,结果执行失败!
1 2 3 4 5 6 7 8 9 10 127.0.0.1:6379> watch money OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> decrby money 10 QUEUED 127.0.0.1:6379(TX)> incrby out 10 QUEUED 127.0.0.1:6379(TX)> exec (nil)
可以使用unwatch解锁,重新watch拿到新值进行操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 127.0.0.1:6379> watch money OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> decrby money 10 QUEUED 127.0.0.1:6379(TX)> incrby out 10 QUEUED 127.0.0.1:6379(TX)> exec (nil) 127.0.0.1:6379> unwatch OK 127.0.0.1:6379> watch money OK 127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> decrby money 10 QUEUED 127.0.0.1:6379(TX)> incrby out 10 QUEUED 127.0.0.1:6379(TX)> exec 1) (integer) 990 2) (integer) 30 127.0.0.1:6379>
如果发现事务执行失败,就先解锁,再重新Watch
Jedis
什么是Jedis?java操作redis的中间件
导入对应依赖
1 2 3 4 5 6 <!-- jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.7.0</version> </dependency>
连接redis数据库
小坑:
1.修改redis.conf的IP绑定和安全模式改为no
bind 192.168.93.129 -::1
protected-mode no
2.注意linux的防火墙要关闭
查看防火墙状态:firewall-cmd –state
停止防火墙:systemctl stop firewalld.service
禁止防火墙开机启动:systemctl disable firewalld.service
1 2 3 4 5 6 7 8 9 import redis.clients.jedis.Jedis; public class TestPing { public static void main(String[] args) { Jedis jedis = new Jedis("192.168.93.129", 6379); System.out.println(jedis.ping()); } }
操作命令
常用API
1、创建 jedis对象
2、键操作:
3、字符串操作:
4、整数和浮点数操作:
5、列表(List)操作:
6、集合(Set)操作:
7、哈希(Hash)操作:
8、有序集合(Zsort)操作:
9、排序操作:
断开连接
jedis.close()
Jedis操作事务 jedis.multi();
SpringBoot整合Redis jedis:采用直连,多线程操作是不安全的,使用jedis pool连接池,像BIO模式。
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全,像NIIO模式。
1.导入jar
1 2 3 4 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2.配置连接
1 2 springboot.redis.host=192.168.93.129 springboot.redis.port=6379
3.操作
1 2 3 4 5 @Autowired private RedisTemplate redisTemplate; RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); connection.flushDb(); redisTemplate.opsForValue().set("name","张三"); System.out.println(redisTemplate.opsForValue().get("name")); connection.close();
关于对象的保存,需要序列化
RedisConfig类
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 package com.oydq.springbootredis.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; 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 public RedisTemplate redisTemplate1(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }
RedisUtil类
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 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 package com.oydq.springbootredis.util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; @Component public final class RedisUtil { @Autowired @Qualifier("redisTemplate1") private RedisTemplate<String, Object> redisTemplate; // =============================common============================ /** * 指定缓存失效时间 * @param key 键 * @param time 时间(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } 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(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) * @return 134 */ 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) * @return */ 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 * @return 值 */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * @param key 键 * @return 对应的多个键值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * @param key 键 * @param map 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map<String, Object> 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<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } 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) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } 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) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * @param key 键 * @param item 项 * @param by 要减少记(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= /** * 根据key获取Set中的所有值 * * @param key 键 * @return */ public Set<Object> 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, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) expire(key, time); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 获取set缓存的长度 * @param key 键 * @return */ 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代表所有值 * @return */ public List<Object> 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 键 * @return */ 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倒数第二个元素,依次类推 * @return */ 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 值 * @return */ 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 时间(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * @param key 键 * @param value 值 * @return */ public boolean lSet(String key, List<Object> 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<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) expire(key, time); 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; } } }
测试类
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 package com.oydq.springbootredis; import com.alibaba.fastjson.JSON; import com.fasterxml.jackson.core.JsonProcessingException; import com.oydq.springbootredis.pojo.User; import com.oydq.springbootredis.util.RedisUtil; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.core.RedisTemplate; @SpringBootTest class SpringbootRedisApplicationTests { @Autowired private RedisUtil redis; @Test void a() throws JsonProcessingException { //真实开发一般使用json对象来传递对象,序列化 User user = new User(1, "张三", 18); // String jsonUser = new ObjectMapper().writeValueAsString(user); String json= JSON.toJSONString(user); redis.set("user",user); redis.set("abc","haha"); System.out.println(redis.get("user")); System.out.println(redis.get("abc")); } }
Redis.conf详解 单位:大小写不敏感
单位:大小写不敏感
1 2 3 4 5 6 7 8 9 # 1k => 1000 bytes # 1kb => 1024 bytes # 1m => 1000000 bytes # 1mb => 1024*1024 bytes # 1g => 1000000000 bytes # 1gb => 1024*1024*1024 bytes # # units are case insensitive so 1GB 1Gb 1gB are all the same.
包含别的配置文件
包含别的配置文件
1 2 # include /path/to/local.conf # include /path/to/other.conf
网络
网络
1 2 3 4 5 6 7 8 9 10 bind 192.168.1.100 10.0.0.1 # listens on two specific IPv4 addresses # bind 127.0.0.1 ::1 # listens on loopback IPv4 and IPv6 # bind * -::* # like the default, all available interfaces 安全模式 protected-mode no 端口 port 6379
通用GENERAL
通用GENERAL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 以守护进程的方式,默认是no,我们需要自己开启yes daemonize yes 配置文件的pid文件,以后台方式运行 pidfile /var/run/redis_6379.pid 日志级别 # 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 日志文件名 logfile "" 默认16个数据库 databases 16 是否显示logo always-show-logo no
SNAPSHOTTING快照
SNAPSHOTTING快照:持久化,在规定时间内,执行了多少操作,会被持久化到文件.rda .aof
redis是内存数据库,如果没有持久化,那么断电即失
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 在3600s内,如果至少有1个key进行了修改,我们进行持久化操作 # save 3600 1 在300s内,如果至少有100个key进行了修改,我们进行持久化操作 # save 300 100 在60s内,如果至少有10000个key进行了修改,我们进行持久化操作 # save 60 10000 持久化出错是否继续工作 stop-writes-on-bgsave-error no 是否压缩rdb文件,需要消耗一些cpu资源 rdbcompression yes 保持rdb文件时,进行错误校验 rdbchecksum yes rdb文件保持的目录 dir ./
REPLICATION复制,后面讲主从复制
REPLICATION复制,后面讲主从复制
SECURITY安全
SECURITY安全
1 2 设置密码 requirepass foobared xxx
CLIENTS 客户端限制
CLIENTS 客户端限制
1 2 能连接的最大客户端数量 maxclients 10000
MEMORY MANAGEMENT内存
MEMORY MANAGEMENT内存
1 2 3 4 5 6 7 8 9 10 11 12 设置最大的内存容量 maxmemory <bytes> 内存达到上限的处理策略 maxmemory-policy noeviction 1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 2、allkeys-lru : 删除lru算法的key 3、volatile-random:随机删除即将过期key 4、allkeys-random:随机删除 5、volatile-ttl : 删除即将过期的 6、noeviction : 永不过期,返回错误
APPEND ONLY MODEAOF的配置
APPEND ONLY MODE AOF的配置:默认不开启aof持久化,默认rdb,一般rdb就够用了。
1 2 3 4 5 6 7 8 9 10 开启aof appendonly no 持久化的文件的名字 appendfilename "appendonly.aof" # appendfsync always 每次修改都同步,消耗性能 appendfsync everysec 每秒执行一次,可能会丢失这1s的数据 # appendfsync no 不同步,操作系统自己同步数据,速度最快
Redis持久化 RDB(Redis Data Base)
什么是RDB?
在指定时间间隔里将内存中的数据集快照写入磁盘,恢复时,将快照文件直接读到内存。
在主从复制中,rdb是备用的,在从机上。
rdb的缺点是最后一次持久化的数据可能丢失。
redis默认是RDB,一般不需要修改这个配置。
RDB保存文件是dump.rdb
自动生成dump.rdb文件,触发机制
1.满足配置里save规则,自动触发
save 3600 1
2.执行了flushall,自动触发
3.退出redis,自动触发
恢复RDB文件
1.只需要把rdb文件放到redis的启动目录即可。
2.查看需要存在的位置:config get 目录(该目录存在drump.rdb)
优点:如果对数据完整性要求不高,适合大规模的数据恢复。
缺点:需要一定的时间间隔进程操作,如果redis意外宕机,最后一次修改就没了。fork进程的时候,会占用一定内容空间。
有时候在生产环境,会对rdb文件进行备份。
AOF(Append Only File)
将我们的所有命令都记录下来,history,恢复的时候把这个文件全部再执行一遍。
AOF保存的是appendonly.aof,默认无限添加。
redis.conf的==APPEND ONLY MODE==
默认不开启的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 开启aof appendonly no 持久化的文件的名字 appendfilename "appendonly.aof" # appendfsync always #每次修改都同步,消耗性能 appendfsync everysec #每秒执行一次,可能会丢失这1s的数据 # appendfsync no #不同步,操作系统自己同步数据,速度最快 no-appendfsync-on-rewrite no #是否重写 auto-aof-rewrite-percentage 100 #重写寄存值 auto-aof-rewrite-min-size 64mb #重新的最大存储值 如果aof大于64m,fork一个新的进程将我们的文件进行重写。
开启:appendonly yes
重启redis服务即可生效
如果aof文件有错位,这时候redis启动不起来,我们需要修复这个aof,redis给我们提供了redis-check-aof修复工具。
命令:redis-check-aof –fix appendonly.aof
优点:每一次修改都同步,文件完整性更加好,每秒同步一次,可能会丢失1s数据。
缺点:相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢,aof运行效率比rbd慢。
发布订阅 底层是字典。
Redis发布订阅是一种消息通信模式 ,发送者发送消息,订阅者接受消息。
Redis客户端可以订阅任意数量的频道。
订阅:subscribe 频道名称
发生:publish 频道名称
订阅者
1 2 3 4 5 6 7 8 9 10 11 12 13 127.0.0.1:6379> subscribe aaa #订阅一个频道 Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "aaa" 3) (integer) 1 #当前推送的消息 1) "message" 2) "aaa" 3) "hello" 1) "message" 2) "aaa" 3) "redis"
发布者
1 2 3 4 5 6 #发生订阅信息 127.0.0.1:6379> publish aaa "hello" (integer) 1 127.0.0.1:6379> publish aaa "redis" (integer) 1
使用场景:实时消息系统,实时聊天,订阅关注系统。稍微复杂就会使用消息中间件来做MQ。
Redis主从复制 默认情况下,每台redis服务器都是主节点。
指一台redis服务器的数据,复制到其他的Redis服务器,前者为主节点,后者为从节点,数据复制是单向的,只能从主节点复制到从节点。
主从 复制,读写分离。80%的情况下都是读操作,减轻服务器压力,架构中经常用!一主二从。
作用:1.数据冗余,2.故障恢复,3.负载均衡,4.高可用(集群)基石
防止宕机,内存有限,至少配三个redis。单台redis服务器最大使用内存不应该超过20G
环境配置 只配置从库,不配置主库。
查看主从复制信息,命令:info replication
1 2 3 4 5 6 7 8 9 10 11 12 13 127.0.0.1:6379> info replication # Replication role:master #角色 connected_slaves:0 #没有从机 master_failover_state:no-failover master_replid:9c302ef04b1b0341f83b564227c5158b744bce2b master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0
一个配置文件打开一个redis服务器。
从机需要改配置,PID名,端口号,日志文件名,rdb文件名/aof文件名
一般只需要配置从机即可。
认老大!一主(6379)二从(6380,6381)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 127.0.0.1:6380> info replication # Replication role:slave #从机 master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:2 master_sync_in_progress:0 slave_read_repl_offset:14 slave_repl_offset:14 slave_priority:100 slave_read_only:1 replica_announced:1 connected_slaves:0 master_failover_state:no-failover master_replid:1f658b85b3194f10ae56993c8f3e4eb8f91544c3 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:14 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:14
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 127.0.0.1:6381> info replication # Replication role:slave #从机 master_host:127.0.0.1 master_port:6379 master_link_status:up master_last_io_seconds_ago:3 master_sync_in_progress:0 slave_read_repl_offset:84 slave_repl_offset:84 slave_priority:100 slave_read_only:1 replica_announced:1 connected_slaves:0 master_failover_state:no-failover master_replid:1f658b85b3194f10ae56993c8f3e4eb8f91544c3 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:84 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:85 repl_backlog_histlen:0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 127.0.0.1:6379> info replication # Replication role:master #主机 connected_slaves:2 #两台从机 slave0:ip=127.0.0.1,port=6380,state=online,offset=238,lag=1 slave1:ip=127.0.0.1,port=6381,state=online,offset=238,lag=1 master_failover_state:no-failover master_replid:1f658b85b3194f10ae56993c8f3e4eb8f91544c3 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:238 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:238
真实的主从配置应该在配置中配置,就是永久的,命令是暂时的。
主机可以写,从机只能读。
1 2 27.0.0.1:6381> set name a (error) READONLY You can't write against a read only replica.
复制原理
复制原理:slave启动成功连接到master后会发一个sync命令,master接到命令,启动后台存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕后,master将传送整个数据文件到slave,完成一次完全同步。
全量复制 :slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制 :master继续将新的所有收集到的修改命令依次传给slave,完成同步,但是只要是重新连接master,一次完整同步(全量复制)将被自动执行。
1.主机断开连接,从机依旧连接主机,但是没有写操作,主机如回来了,从机依旧可以从主机读取值。
2.从机断开连接,如果使用命令行配置的,重新启动就会变回主机。再变到从机,又可以拿到原主机的值。
层层链路
上个从节点做下个主节点
M-S(M)-S
主机宕机了,从机自己当老大:
哨兵模式(Sentinel)
自动选取老大模式
主从切换技术:主机宕机后,需要手动把一台服务器切换为主机,需要人工干预,费时费力,还会造成一段时间服务不可用,这不是一种推荐方法,所以引入了哨兵模式架构来解决。
能够后台自动监控主机是否故障,如果故障,则根据投票数自动从从机切换为主机。
哨兵模式是一种特殊的模式,首先redis提供哨兵命令,哨兵是一个独立的进程,原理:哨兵通过发送命令,等redis服务器响应,从而监控运行的多个redis实例。
一个哨兵对redis服务进行监控,可能会出现问题,这时候我们可以使用多哨兵进行监控,各哨兵之间互相监控,形成多哨兵模式。
多个哨兵发现主机不响应,通过投票选出从机转化成主机。
1.新建配置哨兵配置文件,sentinel.conf
1 2 3 sentinel monitor myredis 127.0.0.1 6379 1 myredis:被监控的主机名称,随意 "1"的含义:一个哨兵检测到主机不响应则认定主机宕机,判定为客观下线。
2.启动哨兵
1 redis-sentinel xconfig/sentinel.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 7752:X 23 Oct 2021 13:57:53.037 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 7752:X 23 Oct 2021 13:57:53.037 # Redis version=6.2.6, bits=64, commit=00000000, modified=0, pid=7752, just started 7752:X 23 Oct 2021 13:57:53.037 # Configuration loaded 7752:X 23 Oct 2021 13:57:53.037 * Increased maximum number of open files to 10032 (it was originally set to 1024). 7752:X 23 Oct 2021 13:57:53.037 * monotonic clock: POSIX clock_gettime _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 6.2.6 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in sentinel mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 26379 | `-._ `._ / _.-' | PID: 7752 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | https://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 7752:X 23 Oct 2021 13:57:53.038 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 7752:X 23 Oct 2021 13:57:53.040 # Sentinel ID is 28f4f524e90d820fb95436d9dfd9b5effb9dc050 7752:X 23 Oct 2021 13:57:53.040 # +monitor master myredis 127.0.0.1 6379 quorum 1 7752:X 23 Oct 2021 13:57:53.040 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379 7752:X 23 Oct 2021 13:57:53.042 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
如果主机宕机后,哨兵从从机里随机选择一个转成主机。
哨兵日志显示哪一个从机转成了主机:
1 2 7752:X 23 Oct 2021 14:01:55.948 # +failover-end master myredis 127.0.0.1 6379 #主机客观下线 7752:X 23 Oct 2021 14:01:55.948 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6381 #转换从机做主机
原主机如果在这之后连接上了,则当新主机的从机。
优点:
1.哨兵集群,基于主从复制,所有的主从配置优点,他都有
2.主从可以切换,故障可以转移,系统的可用性会更好
3.哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点:
1.redis不好在线扩容的,集群容量一旦到达上限,在线扩容就很麻烦
2.实现哨兵模式的配置其实很麻烦,里面有很多选择
哨兵模式的全部配置:
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 # Example sentinel.conf # 哨兵sentinel实例运行的端口 默认26379 port 26379 # 哨兵sentinel的工作目录 dir /tmp # 哨兵sentinel监控的redis主节点的 ip port # master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。 # quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了 # sentinel monitor <master-name> <ip> <redis-port> <quorum> sentinel monitor mymaster 127.0.0.1 6379 2 # 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码 # 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码 # sentinel auth-pass <master-name> <password> sentinel auth-pass mymaster MySUPER--secret-0123passw0rd # 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒 # sentinel down-after-milliseconds <master-name> <milliseconds> sentinel down-after-milliseconds mymaster 30000 # 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步, 这个数字越小,完成failover所需的时间就越长, 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。 # sentinel parallel-syncs <master-name> <numslaves> sentinel parallel-syncs mymaster 1 # 故障转移的超时时间 failover-timeout 可以用在以下这些方面: #1. 同一个sentinel对同一个master两次failover之间的间隔时间。 #2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。 #3.当想要取消一个正在进行的failover所需要的时间。 #4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了 # 默认三分钟 # sentinel failover-timeout <master-name> <milliseconds> sentinel failover-timeout mymaster 180000 # SCRIPTS EXECUTION #配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。 #对于脚本的运行结果有以下规则: #若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10 #若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。 #如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。 #一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。 #通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本, 这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数, 一个是事件的类型, 一个是事件的描述。 如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。 #通知脚本 # sentinel notification-script <master-name> <script-path> sentinel notification-script mymaster /var/redis/notify.sh # 客户端重新配置主节点参数脚本 # 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。 # 以下参数将会在调用脚本时传给脚本: # <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port> # 目前<state>总是“failover”, # <role>是“leader”或者“observer”中的一个。 # 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的 # 这个脚本应该是通用的,能被多次调用,不是针对性的。 # sentinel client-reconfig-script <master-name> <script-path> sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
Redis缓存穿透,击穿和雪崩 缓存穿透
缓存穿透:用户查询一个数据,发现redis内存数据库中没有,于是去持久层数据库查询,发现也没有。直接频繁的查询持久层数据库,给数据库产生很大的压力,相当于出现了缓存穿透。
解决:
1.布隆过滤器:是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的压力。
2.缓存空对象:则返回空对象也缓存起来,设置一个过期时间,保护后段数据源。
存在问题:空值缓存需要更多的空间存储。即使设置了过期时间,也可能出现缓存层和存储层不同步。
缓存击穿 :点
缓存击穿:一个key为热点,大并发集中对着一个key访问,当key失效了,大并发直接访问持久层数据库,凿开了一个大洞。
解决:
1.设置key永不过期,但是会浪费空间
2.加互斥锁,但对分布式锁考验很大
缓存雪崩 :面
缓存雪崩:某一个时间段,缓存集体过期失效,大并发直接访问持久层数据库。redis宕机!
双十一:停掉一些服务,保证主要的服务可用。
解决:
1.redis高可用:既然redis可能 挂掉,就多设置几台redis服务器,异地多活。
2.限流降级(springcloud):缓存失效后,通过加锁或队列来控制数据库写缓存的线程数量,比如一个key只允许一个线程查询数据和写缓存,其他线程等待。
3.数据预热:正式部署前,先把可能的数据预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动加载缓存不同的key,设置不同的过期时间,让缓存失效时间点尽量均匀。