Redis
如何开启、关闭、连接
linux常规命令开关
yum安装gcc-c++环境
yum install gcc-c++
自动配置文件
make
确认是否全部安装,没安装的再次安装
make install
启动
cd src
./redis-server
可根据配置文件启动
/www/server/redis/redis.conf
宝塔Linux面板命令开关
redis安装目录
/www/server/redis
启动
/etc/init.d/redis start
停止
/etc/init.d/redis stop
redis配置文件目录
/www/server/redis/redis.conf
连接
/www/server/redis/src/redis-cli
redis-benchmark 压力测试
cd src
./redis-benchmark [options]


基础知识
redis一共有16个数据库,默认使用第1个
select [index] #切换数据库
flushdb #清空当前数据库
flushall #清空所有数据库
keys * #查看当前数据库的所有KV
为什么redis是单线程还快
Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。
1.redis是基于内存的,内存的读写速度非常快
2.redis是单线程的,省去了很多上下文切换线程的时间
3.redis使用多路复用技术,可以处理并发的连接。非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。epoll中的读、写、关闭、连接都转化成了事件,然后利用epoll的多路复用特性,绝不在io上浪费一点时间。
https://zhuanlan.zhihu.com/p/58038188
五大数据类型
Redis-key
127.0.0.1:6379> flushall #清除所有数据库的缓存
OK
127.0.0.1:6379> flushdb #清除当前数据库的缓存
OK
127.0.0.1:6379> set name pang #设置key-value为name-pang
OK
127.0.0.1:6379> keys * #查询所以的key
1) "name"
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> get name #获取key为name的value
"pang"
127.0.0.1:6379> expire name 10 #设置name的过期时间为10s
(integer) 1
127.0.0.1:6379> ttl name #查看剩余的有效时间
(integer) 3
127.0.0.1:6379> ttl name #过期的话剩余时间为-2
(integer) -2
127.0.0.1:6379> exists name age #查看name和age是否存在
(integer) 1 #存在数量为1
127.0.0.1:6379> exists name #查看name是否存在
(integer) 0 #不存在
127.0.0.1:6379> move age 1 #将age这个数据移动到1号数据库里
(integer) 1
127.0.0.1:6379> select 1 #选择数据库1
OK
127.0.0.1:6379[1]> keys * #查看所以key
1) "age"
String类型
127.0.0.1:6379> set name pang #设置key-value为name-pang
OK
127.0.0.1:6379> type name #查看类型
string
127.0.0.1:6379> append key1 +hello #拼接字符串
(integer) 12 #长度为12
127.0.0.1:6379> get key1
"value1+hello"
127.0.0.1:6379> strlen key1 #查看字符串长度
(integer) 12
127.0.0.1:6379> set views 0 #设置访问量
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #自增
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views #自减
(integer) 1
127.0.0.1:6379> incrby views 10 #自增步长为10
(integer) 11
127.0.0.1:6379> decrby views 5 #自减步长为5
(integer) 6
127.0.0.1:6379> set name1 "hello,pang"
OK
127.0.0.1:6379> getrange name1 0 5 #查看下标0到5的值
"hello,"
127.0.0.1:6379> getrange name1 0 -1 #查看全部
"hello,pang"
127.0.0.1:6379> setrange name1 5 haha #将name1的下标5之后的值覆盖为haha [5,~
(integer) 10
127.0.0.1:6379> get name1
"hellohahag"
127.0.0.1:6379> setex name1 10 zheng #name1存在设置失效时间为10s并且值为zheng
OK
127.0.0.1:6379> ttl name1
(integer) 5
127.0.0.1:6379> ttl name1
(integer) 3
127.0.0.1:6379> ttl name1
(integer) -2
127.0.0.1:6379> setnx name1 pang #如果不存在则设置name1的值为pang
(integer) 1
127.0.0.1:6379> get name1
"pang"
127.0.0.1:6379> mset name2 pang age2 18 #设置多值
OK
127.0.0.1:6379> mget name2 age2 #获取多值
1) "pang"
2) "18"
127.0.0.1:6379> msetnx name3 hahah3 age3 didi3 #不存在设置多值
(integer) 1
127.0.0.1:6379> keys *
1) "name3"
2) "key1"
3) "age3"
4) "name1"
5) "views"
6) "age2"
7) "name2"
8) "name"
127.0.0.1:6379> set user:1 {name:pang,age:18} #存储对象
OK
127.0.0.1:6379> get user:1
"{name:pang,age:18}"
127.0.0.1:6379> getset name1 yang #先取旧值再设置新值
"pang"
使用场景
- 计数器 incrby
- 统计多单位的数量 uid:43241:follow 0 有人关注就inrc 取关就decr
- 粉丝数
- 对象缓存
List(列表)
127.0.0.1:6379> lpush mylist 1 2 3 4 #lpush:往左边插入
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> lrange mylist 0 2
1) "4"
2) "3"
3) "2"
127.0.0.1:6379> rpush mylist 5 #rpush:往右边插入
(integer) 5
127.0.0.1:6379> rpush mylist 6
(integer) 6
127.0.0.1:6379> lrange mylist 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "5"
6) "6"
127.0.0.1:6379> lpop mylist 1 #lpop:删除left前1个数据
1) "4"
127.0.0.1:6379> rpop mylist 2 #rpop:删除right前2个数据
1) "6"
2) "5"
127.0.0.1:6379> lrange mylist 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> lindex mylist 0 #lindex:查看左边下标为0的数据
"3"
127.0.0.1:6379> lindex mylist 1
"2"
127.0.0.1:6379> llen mylist #llen:查看长度
(integer) 3
127.0.0.1:6379> lpush mylist 1 1 1 1 1
(integer) 8
127.0.0.1:6379> lrange mylist 0 -1
1) "1"
2) "1"
3) "1"
4) "1"
5) "1"
6) "3"
7) "2"
8) "1"
127.0.0.1:6379> lrem mylist 3 1 #lrem:删除3个1
(integer) 3
127.0.0.1:6379> rpush list2 one two three four five six
(integer) 6
127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
6) "six"
127.0.0.1:6379> ltrim list2 1 2 #ltrim:截取成下标1和小标2的数据
OK
127.0.0.1:6379> lrange list2 0 -1
1) "two"
2) "three"
127.0.0.1:6379> linsert list2 after "three" "four" #linsert:在three的after添加four
(integer) 3
127.0.0.1:6379> lrange list2 0 -1
1) "two"
2) "three"
3) "four"
127.0.0.1:6379> linsert list2 before "two" "one" #在two的before添加one
(integer) 4
小结:
- 实际上是一个链表,before after ,left right 都可以插入
- 如果key不存在创建新链表
- 如果key存在则新增内容
- 在两边插入或改动效率最高!
可用于消息队列(lpush rpop),栈(lpush lpop)
Set(集合)
无序不重复
127.0.0.1:6379> sadd myset "i" "love" "yang" #sadd:添加元素
(integer) 3
127.0.0.1:6379> sismember myset love #sismember:查看是否是集合内元素
(integer) 1
127.0.0.1:6379> sismember myset pang
(integer) 0
127.0.0.1:6379> smembers myset #smembers:查看集合所有元素
1) "love"
2) "i"
3) "yang"
127.0.0.1:6379> scard myset #scard:查看集合元素个数
(integer) 3
127.0.0.1:6379> srem myset i #srem:删除集合元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 2
127.0.0.1:6379> sadd myset pang wang li zhang
(integer) 4
127.0.0.1:6379> smembers myset
1) "love"
2) "li"
3) "pang"
4) "zhang"
5) "wang"
6) "yang"
127.0.0.1:6379> srandmember myset 1 #srandmember:随机取1个值
1) "zhang"
127.0.0.1:6379> srandmember myset 1
1) "pang"
127.0.0.1:6379> srandmember myset 1
1) "li"
127.0.0.1:6379> spop myset 1 #spop:随机弹出1个值
1) "love"
127.0.0.1:6379> smembers myset
1) "li"
2) "pang"
3) "zhang"
4) "wang"
5) "yang"
127.0.0.1:6379> smove myset myset2 li #smove:移动元素去其他集合
(integer) 1
127.0.0.1:6379> smembers myset2
1) "li"
127.0.0.1:6379> sadd myset2 pang tao lv wang
(integer) 4
127.0.0.1:6379> sdiff myset myset2 #sdiff:差集
1) "zhang"
2) "yang"
127.0.0.1:6379> sinter myset myset2 #sinter:交集
1) "pang"
2) "wang"
127.0.0.1:6379> sunion myset myset2 #sunion:并集
1) "lv"
2) "li"
3) "tao"
4) "pang"
5) "wang"
6) "zhang"
7) "yang"
用法:
- 共同关注,共同好友,六度分割理论
Hash (哈希)
127.0.0.1:6379> hset myhash field1 value1 field2 value2 #hset:设值
(integer) 2
127.0.0.1:6379> hget myhash field1 #hget:取值
"value1"
127.0.0.1:6379> hget myhash field2
"value2"
127.0.0.1:6379> hgetall myhash #hgetall:取所有的值
1) "field1"
2) "value1"
3) "field2"
4) "value2"
127.0.0.1:6379> hdel myhash field1 #hdel:删除值
(integer) 1
127.0.0.1:6379> hlen myhash #hlen:查看个数
(integer) 1
127.0.0.1:6379> hexists myhash field1 #hexists:是否存在
(integer) 0
127.0.0.1:6379> hexists myhash field2
(integer) 1
127.0.0.1:6379> hkeys myhash #hkeys:查看所以keys
1) "field2"
2) "field3"
3) "field4"
127.0.0.1:6379> hsetnx myhash field0 10 #hsetex,hsetnx:存在或不存在则设值
(integer) 1
127.0.0.1:6379> hincrby myhash field0 10 #hincrby:自增
(integer) 20
#########
127.0.0.1:6379> hset user:1 name zhangsan age 18 #存入对象user:1
(integer) 2
127.0.0.1:6379> hgetall user:1
1) "name"
2) "zhangsan"
3) "age"
4) "18"
hash更适合储存对象
Zset(有序集合)
类似于set只是在set的key-value之间加了一个数量 key-score-value
127.0.0.1:6379> zadd salary 2500 zhangsan #zadd:添加值
(integer) 1
127.0.0.1:6379> zadd salary 1000 lisi
(integer) 1
127.0.0.1:6379> zadd salary 2000 wangwu
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf #zrangebyscore:升序排序
1) "lisi"
2) "wangwu"
3) "zhangsan"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #带上score [withscores]
1) "lisi"
2) "1000"
3) "wangwu"
4) "2000"
5) "zhangsan"
6) "2500"
127.0.0.1:6379> zcard salary #zcard:查看个数
(integer) 3
127.0.0.1:6379> zrevrange salary 0 -1 withscores #zrevrange:降序
1) "zhangsan"
2) "2500"
3) "wangwu"
4) "2000"
5) "lisi"
6) "1000"
127.0.0.1:6379> zcount salary 1000 2500 #在score在[1000,2500]区间的数据个数
(integer) 3
127.0.0.1:6379> zcount salary 1001 2499
(integer) 1
使用:
- 储存成绩表啊,工资表啊
- 排行榜
三大特殊数据类型
geospatial地理位置
geoadd
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
geopos
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
geodist 两地距离
127.0.0.1:6379> geodist china:city beijing shanghai km
"1067.3788"
georadius 以经纬度为中心搜索范围内的位置
127.0.0.1:6379> georadius china:city 115 39 1000 km #经纬115 39为中心1000km内的位置
1) "beijing"
127.0.0.1:6379> georadius china:city 115 39 1000 km withdist #显示到中心点区域和距离
1) 1) "beijing"
2) "156.4529"
127.0.0.1:6379> georadius china:city 115 39 1000 km withcoord #显示范围内的城市信息
1) 1) "beijing"
2) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> georadius china:city 115 39 1000 km withcoord count 1 #显示范围内的城市信息限定数量1个
1) 1) "beijing"
2) 1) "116.39999896287918091"
2) "39.90000009167092543"
georadiusbymember 显示以北京为中心10000 km内的城市名字
127.0.0.1:6379> georadiusbymember china:city beijing 10000 km
1) "hangzhou"
2) "shanghai"
3) "beijing"
底层基于zset
127.0.0.1:6379> zrange china:city 0 -1
1) "hangzhou"
2) "shanghai"
3) "beijing"
127.0.0.1:6379> zrem china:city beijing #移除元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "hangzhou"
2) "shanghai"
Hyperloglog
简介
Redis2.8.9更新的Hyperloglog数据结构
技术统计算法
优点:占用内存是固定的,2^64不同的元素,只需占12kb。比起用set存储,大大节省内存。
不过有0.81% 的错误率
使用
127.0.0.1:6379> pfadd k1 9 8 4 9 7 3 3 5 4 #添加k1
(integer) 1
127.0.0.1:6379> pfadd k2 1 5 1 5 0 6 7 1 4 6 6 #添加k2
(integer) 1
127.0.0.1:6379> pfcount k1 #查询k1,注意不可重复
(integer) 6
127.0.0.1:6379> pfcount k2 #查询k2,注意不可重复
(integer) 6
127.0.0.1:6379> pfmerge k3 k1 k2 #k1和k2的并集,存于k3
OK
127.0.0.1:6379> pfcount k3
(integer) 9
bitmaps
优点:在储存方面使用0或者1二进制位,可用于上班打卡这类只存在两种结果的事情上
127.0.0.1:6379> setbit daka 0 1 #设置打卡1代表打卡0代表没打卡
(integer) 0
127.0.0.1:6379> setbit daka 1 0 #bit位1的位置为0
(integer) 1
127.0.0.1:6379> setbit daka 2 0 #bit位2的位置为0
(integer) 0
127.0.0.1:6379> setbit daka 3 0 #...
(integer) 0
127.0.0.1:6379> setbit daka 4 1
(integer) 0
127.0.0.1:6379> setbit daka 5 1
(integer) 0
127.0.0.1:6379> setbit daka 6 1
(integer) 0
127.0.0.1:6379> bitcount daka [start end]#统计打卡天数,可加其实和结束
(integer) 4
redis的setbit设置或清除的是bit位置,而bitcount计算的是byte位置
start 和 end 参数的设置,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位。
https://www.cnblogs.com/song27/p/12329554.html
事务
redis的事务不保证原子性,所谓原子性就是要成功都成功,一个失败全部失败。redis的事务类似于队列,先进的先出,后进的后出!
redis事务流程
- 开启事务 multi
- 命令入队 .....
- 执行事务 exec
执行事务
127.0.0.1:6379> multi #开启事务
OK
#命令入队
127.0.0.1:6379(TX)> set key1 value1
QUEUED
127.0.0.1:6379(TX)> set key2 value2
QUEUED
127.0.0.1:6379(TX)> get key1
QUEUED
127.0.0.1:6379(TX)> get key2
QUEUED
127.0.0.1:6379(TX)> exec #执行事务
1) OK #set key1 value1
2) OK #set key2 value2
3) "value1" #get key1
4) "value2" #get key2
放弃事务
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379(TX)> set key1 value1#设值
QUEUED
127.0.0.1:6379(TX)> discard #放弃事务
OK
127.0.0.1:6379> get key1 #无法查到值
(nil)
编译时异常(错误命令)
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379(TX)> set key1 value1 #设值
QUEUED
127.0.0.1:6379(TX)> getxxx hahaha #输入错误命令,触发编译时异常
(error) ERR unknown command `getxxx`, with args beginning with: `hahaha`,
127.0.0.1:6379(TX)> exec #执行事务
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get key1 #发现无法取到key1的值,说明事务内发生编译时异常,则全部命令都不会执行
(nil)
运行时异常
127.0.0.1:6379> set name "pxm" #设值
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379(TX)> set key1 value1 #设值key1
QUEUED
127.0.0.1:6379(TX)> incr name #制造运行时异常,因为字符串非数字不能自增
QUEUED
127.0.0.1:6379(TX)> set key2 value2 #设值key2
QUEUED
127.0.0.1:6379(TX)> exec #执行事务
1) OK #set key1 value1执行成功
2) (error) ERR value is not an integer or out of range #incr name执行失败
3) OK #set key2 value2执行成功
127.0.0.1:6379> mget key1 key2 #获取key1 key2成功,事务内说明运行时异常发生只会影响此异常命令
1) "value1"
2) "value2"
监控
乐观锁:什么时候出现问题什么时候加锁 携带版本号进行比较
悲观锁:无论什么时候都会加锁,例如java里的sychoinized
使用watch进行监控:
正常情况:
127.0.0.1:6379> set inmoney 1000 #设置进账1000
OK
127.0.0.1:6379> set outmoney 1000 #设置出账1000
OK
127.0.0.1:6379> watch inmoney #监控入账
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379(TX)> incrby inmoney 200 #进账增200
QUEUED
127.0.0.1:6379(TX)> decrby outmoney 200#出账减200
QUEUED
127.0.0.1:6379(TX)> exec #执行事务(成功)
1) (integer) 1200
2) (integer) 800
监控中被他线程修改,使用watch当做乐观锁:
127.0.0.1:6379> mget inmoney outmoney
1) "1200"
2) "800"
127.0.0.1:6379> watch inmoney #使用watch监控,当做乐观锁
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby inmoney 200
QUEUED
127.0.0.1:6379(TX)> decrby outmoney 200
QUEUED
127.0.0.1:6379(TX)> exec #由于监控期间他线程修改了inmoney的值,导致触发乐观锁,事务失败
(nil)
解决方法:
127.0.0.1:6379> unwatch #使用unwatch 解绑所有的监控
OK
127.0.0.1:6379> watch inmoney #再次使用watch绑定,确保监控的为最新的值
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby inmoney 200
QUEUED
127.0.0.1:6379(TX)> decrby outmoney 200
QUEUED
127.0.0.1:6379(TX)> exec #执行事务时,先对比监控的值是否发生改变,如果没有则执行成功,改变了则失败,则再解绑重绑
1) (integer) 2600
2) (integer) 400
jedis
使用java操作redis的中间件
-
导入依赖
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.51</version> </dependency> -
使用
Jedis jedis = new Jedis("47.99.171.23", 6379);//连接远程需要打开安全组防火墙和修改配置文件 jedis.flushDB(); ....远程redis连接:https://blog.csdn.net/weixin_44502804/article/details/90044682
Spring-boot整合
springboot操作数据:spring-data jpa jdbc redis!
springdata也是和springboot其名的项目
为啥springboot2.x后使用lettuce而舍弃jedis
jedis:采用直连技术,多线程操作是不安全的,需要使用jedis pool。更像BIO模式
lettuce:采用netty,实例可以多个线程中共享,不存在线程安全问题,更像NIO模式
redis自动配置类源码分析:
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")//如果没有redisTemplate的类才执行,说明我们可以自定义
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();// 泛型为object使用的时候需要转向
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
整合测试
1 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2 配置连接测试
配置redis

@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("myKey","myValue");
System.out.println(redisTemplate.opsForValue().get("myKey"));
}
}
重写自动配置类
由于redis的自动配置类太low了,另外在传json数据时需要序列化,所以我们自定义自动配置类
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
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);
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;
}
}
重写工具类
抽取常用方法
代码参考「redis-study」
Redis.conf配置文件
INCLUDES 包含
# include /path/to/local.conf
# include /path/to/other.conf
可以导入其他配置文件
NETWORK 网络
bind 127.0.0.1
protected-mode no
port 6379
GENERAL
daemonize yes #是否以守护进程的方式运行,默认no,我们要开启yes,即后台运行
pidfile /www/server/redis/redis.pid
#如果指定了pid文件,Redis会在启动时指定的地方写入pid文件
#并在退出时删除它。
#当服务器运行非守护时,如果没有pid文件,则不创建pid文件
#在配置中指定。当服务器被守护时,pid文件
#被使用,即使没有指定,默认为"/var/run/redis.pid"。
#创建一个pid文件是最好的努力:如果Redis不能创建它
#没有坏的事情发生,服务器将正常启动和运行。
logfile "/www/server/redis/redis.log"
databases 16
always-show-logo yes #就是启动时像汉堡一样的东西哈哈
SNAPSHOTTING 快照
持久化,在规定的时间内,执行多少次操作的话,则会持久化到文件
没有持久化的话,数据断点即失
save 900 1 #900s 至少一个key修改了则持久化操作
save 300 10 #...
save 60 10000 #...
stop-writes-on-bgsave-error yes #持久化出错是否继续工作
rdbcompression yes#是否压缩rdb
rdbchecksum yes #是否检查rdb
dbfilename dump.rdb #rdb文件名
dir /www/server/redis/ #储存位置
REPLICATION 复制
replicaof <masterip> <masterport> #配置主机的地址和端口
masterauth <master-password> #主机若有密码配置密码
SECURITY 安全
设置密码可以在配置文件里设置,也可以直接命令行设置
config get requirepass #查看密码
config set requirepass#设置密码
auth xxxxx #输入密码
CLIENTS 客户端
# maxclients 10000 #默认最大连接1w台
MEMORY MANAGEMENT 内存管理
# MAXMEMORY POLICY 内存达到上限的解决策略
noeviction #不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。(默认值)
allkeys-lru #所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
volatile-lru #只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
allkeys-random #所有key通用; 随机删除一部分 key。
volatile-random #只限于设置了 expire 的部分; 随机删除一部分 key。
volatile-ttl #只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。
APPEND ONLY MODE aof配置
appendonly no #默认不开启aof模式,默认使用rdb方式持久化
appendfilename "appendonly.aof" #aof文件名
# appendfsync always #每次修改都会sync
appendfsync everysec #每秒都sync,可能会丢失1s的数据
# appendfsync no #不执行sync,这个时候操作系统自己同步,速度最快
redis持久化
RDB(Redis DataBase)
RDB其实就是把数据以快照的形式保存在磁盘上。什么是快照呢,你可以理解成把当前时刻的数据拍成一张照片保存下来。
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
既然RDB机制是通过把某个时刻的所有数据生成一个快照来保存,那么就应该有一种触发机制,是实现这个过程。对于RDB来说,提供了2种机制:save、bgsave(默认)我们分别来看一下
- save

该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止,执行完成时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。
- bgsave(默认)
执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体流程如下。具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令

在配置文件可以修改save参数,save 900 1 (900s 至少一个key修改了则持久化操作),则会生成dump.rdb文件用于持久化。
127.0.0.1:6379> config get dir #rdb文件放在这就可以直接读取恢复,进行持久化
1) "dir"
2) "/www/server/redis"
rdb的优缺点
- 优点
- 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作
- RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快
- 缺点
- RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,如果redis宕机了,最后一次的修改数据就没了。
- fork会消耗额外内存
AOF(Append Only File)
全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,redis会将每一个收到的写命令都通过write函数追加到文件中。通俗的理解就是日志记录。
appendonly no #默认不开启aof模式,默认使用rdb方式持久化
appendfilename "appendonly.aof" #aof文件名
# appendfsync always #每次修改都会sync
appendfsync everysec #每秒都sync,可能会丢失1s的数据
# appendfsync no #不执行sync,这个时候操作系统自己同步,速度最快

通俗的说,就是每写一条set命令,就会追加到appendonly.aof文件中,下次启动时再次执行只写命令从而做到持久化操作。
数据修复
redis-check-aof --fix appendonly.aof
可以用于不小心输入了flushall删掉了所有缓存文件,然后打开appendonly.aof删掉保存的flushall命令后执行恢复即可。
AOP重写
aof持久化策略是将Redis执行的命令追加到一个文件之中,当Redis重启时会加载这个文件依次重新执行命令,以达到数据恢复的目的。但是随着Redis的运行,aof文件之中会存在很多冗余的命令,以及无效的命令(例如,先SET一个键,在将这个键删除),这样会导致aof文件过于庞大,还原数据所需要的时间也会相应的增大,因此在满足一定条件的情况下,需要重写aof文件,将冗余的命令进行合并,同时将无效的命令进行删除。
AOF的方式也同时带来了另一个问题。持久化文件会变的越来越大。为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。将内存中的数据以命令的方式保存到临时文件中,同时会fork出一条新进程来将文件重写

重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式(因为可能会有很多无效命令例如,先SET一个键,在将这个键删除)重写了一个新的aof文件,这点和快照有点类似。
重写配置:
no-appendfsync-on-rewrite no #默认关闭
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb #旧文件达到64mb时重写
AOF优缺点
优点
- AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,最多丢失1秒钟的数据。
- AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写
缺点:
- 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
- 进行数据恢复的时候,没有恢复一模一样的数据出来。
AOF和RDB比较

[图片上传失败...(image-4c3d8d-1630630196405)]
redis订阅与发布
redis发布订阅(pub/sub)是一种消息通信模式。

测试
订阅端:
127.0.0.1:6379> SUBSCRIBE channel1 #订阅channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
1) "message"
2) "channel1"
3) "hello world"
1) "message"
2) "channel1"
3) "hello redis"
^C
127.0.0.1:6379> UNSUBSCRIBE channel1 #退订channel1
1) "unsubscribe"
2) "channel1"
3) (integer) 0
发送端:
127.0.0.1:6379> PUBLISH channel1 "hello world" #在channel1发送消息
(integer) 1
127.0.0.1:6379> PUBLISH channel1 "hello redis"
(integer) 1
redis主从复制
未修改配置文件或者命令的机子会自动默认为master,修改文件的方法redis配置中已经阐述,命令修改配置如下
注意:一台linux上模拟1主2从复制时候需要修改配置文件的一下参数:
- port xxxx
- pidfile /www/server/redis/redisxx.pid
- logfile "/www/server/redis/redisxx.log"
- dbfilename dumpxx.rdb
Slave0:
[root@pang ~]# /www/server/redis/src/redis-server /root/myconfig/redis80.conf #指定配置文件启动服务
[root@pang ~]# /www/server/redis/src/redis-cli -p 6380
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #修改本机127.0.0.1的端口6379为次slave的master
OK
127.0.0.1:6380> info replication #查看复制的配置
# Replication
role:slave #此机为slave
master_host:127.0.0.1 #master主机为本机
master_port:6379 #主机端口为6379
...
Slave1
[root@pang ~]# /www/server/redis/src/redis-server /root/myconfig/redis81.conf
[root@pang ~]# /www/server/redis/src/redis-cli -p 6381
127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
127.0.0.1:6381> info replication #查看复制的配置
# Replication
role:slave #此机为slave
master_host:127.0.0.1 #master主机为本机
master_port:6379 #主机端口为6379
...
master:
[root@pang src]# /www/server/redis/src/redis-server /root/myconfig/redis79.conf
[root@pang src]# /www/server/redis/src/redis-cli -p 6379
127.0.0.1:6379> info replication #查看复制配置
# Replication
role:master #角色为master
connected_slaves:2 #账下的丛集数2
slave0:ip=127.0.0.1,port=6381,state=wait_bgsave,offset=0,lag=0 #slave0信息
slave1:ip=127.0.0.1,port=6380,state=wait_bgsave,offset=0,lag=0 #slave1信息
master_failover_state:no-failover
...
细节
master可以写,slave只能读。
主机断开连接,从机依旧在绑定着主机,主机重新连接再写入操作,从机依旧可以获取主机上的信息
命令绑定主机master是一次性的,关闭连接了就复原了,如果每次开启服务都绑定master则需要在配置文件里修改replication下的参数。
复制原理
slave启动连接到master会发送一个sync的同步命令
master接到命令后,启动后台存盘进程,同时收集所有修改数据的命令,在后台进程执行之后,master将传送整个数据文件到slave,并完成一次同步。
全量复制:slave服务器在接收到数据库文件数据之后,将其存盘并加载到内存中。
增量复制:master继续将新的所有的收集到的修改数据命令依次传给slave,完成同步。
但是 只要重新连接master,一次完全同步(全量复制)将别自动执行,数据肯定可以在slave中看到。
毛毛虫式主从复制

如果master宕机,下面的slave遍可以谋朝篡位:
12.0.0.1:6381> slaveof no one #此时6381端口的slave谋朝篡位编程master
即使79端口的master恢复连接也无法再次成为master
当然真正开发当中不使用普通的主从复制模式,而是使用以下的哨兵模式。
redis哨兵模式(自动选举master)
概述
当我们主服务器宕机后,需要手动把一台从服务器切换成中服务器,需要人工干预,费事费力,还会造成一段时间内服务器不可用。所以说会优先使用哨兵模式,通过sentinel(哨兵)来解决问题。
谋朝篡位的自动版本,能够根据后台监控主机故障,如果主机故障了,会根据投票数自动将从机转换为主机。
哨兵模式是一种特殊的模式,首先redis提供了哨兵的命令,哨兵为一个独立的进程,独立运行。原理是哨兵通过发生命令等待redis服务器响应,从而监控运行的多个redis实力。

以防哨兵死亡,通常使用多个哨兵进行监控。

假设主服务器宕机,哨兵1会检测这个结果,系统不会马上进行failover(故障转移),仅仅是哨兵1主观的认为服务器不可以,此现象称为主观下线,当后面的服务器也检测到主服务器不可用时,并且数量达到一定值时,那么哨兵之间会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。之后通过发布订阅模式,让各个哨兵切换监控新的主机,称为客观下线。
测试
#配置端口 默认为26379
port 26379
#以守护进程模式启动
daemonize yes
#日志文件名
logfile "sentinel_26379.log"
#存放备份文件以及日志等文件的目录
dir "/opt/redis/data"
#监控的IP 端口号 名称 sentinel通过投票后认为mater宕机的数量,此处为至少2个,此配置最重要,其他可以没有这个必须有
#mymaster为<master-name>随便取
sentinel monitor mymaster 127.0.0.1 6379 2
#当在Redis实例中开启了requirepass foobared授权密码,这样所有连接Redis实例的客户端都要提供密码
#设置哨兵sentinel连接主从的密码,注意必须为主从设置一样的验证密码
# sentinel auth-pass <master-name> <password>
#30秒ping不通主节点的信息,主观认为master宕机
sentinel down-after-milliseconds mymaster 30000
#故障转移后重新主从复制,1表示串行,>1并行
sentinel parallel-syncs mymaster 1
#故障转移开始,三分钟内没有完成,则认为转移失败
sentinel failover-timeout mymaster 180000
[root@pang ~]# redis-sentinel myconfig/sentinel.conf # 指定配置文件开启哨兵
当监视的master节点断开,这时候会投票选举一个slave成为新的master。当原先的master重新连接时,会自动把它分配到新的master下当做slave。
哨兵模式的优缺点
优点:
1、哨兵集群,基于主从复制模式,所有的主从配置优点,它都有
2、主从可以切换,故障可以转移,高可用性的系统
3、哨兵模式就是主从模式的升级,手动到自动,更加健壮
缺点:
1、Redis不好在线扩容的,集群容量一旦到达上限,在线扩容就十分麻烦
2、哨兵模式的配置繁琐
redis缓存穿透、击穿与雪崩
正常的缓存流程

穿透
高并发访问缓存和数据库都没有的数据,导致宕机。

解决方法:
- 布隆过滤器。拦截不符合要求的请求
- 缓存空对象,把空的对象写进redis,避免直接访问数据库。
击穿(高并发访问单个key)
单个热点key瞬间失效,大量请求直接访问数据库,虽数数据库将此key写回缓存时间很短,但时间再短,还是有成千上百万请求在这几毫秒间直接访问数据库,从而导致宕机。

解决方法:
-
使用分布式锁,在请求数据库这一步上锁,就只有一个线程可以抢到锁访问数据库,同时让其他线程睡几毫秒,
待数据库写回数据到redis,其他线程就可以直接从缓存里拿数据了。
雪崩
大量的redis缓存在同一时间全部失效(或redis宕机),直接访问数据库导致宕机
例如双11为了防止过多请求直接打到数据库上,提前将访问量大的数据写到redis缓存中,并设置3小时的失效时间,当3小时过后的一瞬间,大量的数据(key)访问数据库,导致数据库响应不及时宕机。

解决方法:
- 随机设置缓存失效时间,不让他们同一时间失效
- redis集群部署
- 用不让key失效,或者通过定时任务在key失效前再次加载key进redis缓存
拓展
布隆过滤器
布隆过滤器会把一个数据通过hash算法,把它所对应的bit数组下标存入1。比如你好算出三个hash分别是357,则下标357则存入1。查询时通过下标357就可能知道此数据为你好,为什么用可能,因为不同的数据完全可以拥有相同的hash值。所以布隆过滤器不便于删除操作,因为可能会误删相同hash的其他数据。

这里为什么是通过3个hash不是5个不是10个以下慢慢做出解释。
首先布隆过滤器存在一个很大的缺点就是会误判!误判的原因很简单,因为不同的数据会算出不同的hash,对应下标也相同。比如你好和hello算出的hash都是2,在查询hello是否存在的时候需查询下标2,发现下标2有数据(因为为1不为0),则判断hello是存在的。其实下标2存入的是你好不是hello,这时就发生了误判。

源码解析:
public void test2() {
int size = 100000;//预计存入10w个数据
double fpp = 0.03;//误判率为3%
int count = 0;
BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);
for (int i = 0; i < size; i++) {
filter.put(i);
}
for (int i = 0; i < size + 10000; i++) {
if (!filter.test(i)) {
System.out.println(i + "==》不在filter里");
count++;
}
}
System.out.println("误判个数=》" + (10000 - count));//误判个数=》286
}
那是不是误判率越低越好呢?
误判率0.01和0.03对比发现:
误判率越低,所需要的hash函数就越多,带来的性能消耗也会越大,所以并非误判率越低越好。

redis如何使用布隆过滤器防止穿透:
Config config = new Config();//配置文件
config.useSingleServer().setAddress("127.0.0.1");//设置服务器地址
config.useSingleServer().setPassword("123455");//密码
RedissonClient redissonClient = Redisson.create(config);//创建redission
//通过redission创建布隆过滤器,起名为myFilter
RBloomFilter<Object> myFilter = redissonClient.getBloomFilter("myFilter");
// 设置过滤样本数量,以及误判率
myFilter.tryInit(1000000L, 0.03);
//添加数据进过滤器
myFilter.add("10086");
//返回为true
System.out.println(myFilter.contains(10086));