redis总结

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]

参数列表.png
压测结果.png
基础知识

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的中间件

  1. 导入依赖

    <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>
    
  2. 使用

    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

image.png
@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
save流程图

该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止,执行完成时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。

  • bgsave(默认)

执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体流程如下。具体操作是Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束。阻塞只发生在fork阶段,一般时间很短。基本上 Redis 内部所有的RDB操作都是采用 bgsave 命令

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的优缺点

  • 优点
    1. 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作
    2. RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快
  • 缺点
    1. RDB快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,如果redis宕机了,最后一次的修改数据就没了。
    2. 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,这个时候操作系统自己同步,速度最快

AOP运行原理

通俗的说,就是每写一条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文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式(因为可能会有很多无效命令例如,先SET一个键,在将这个键删除)重写了一个新的aof文件,这点和快照有点类似。

重写配置:

no-appendfsync-on-rewrite no #默认关闭

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb #旧文件达到64mb时重写

AOF优缺点

优点

  1. AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,最多丢失1秒钟的数据。
  2. AOF日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写

缺点:

  1. 对于同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
  2. 进行数据恢复的时候,没有恢复一模一样的数据出来。

AOF和RDB比较

AOF和RDB比较

[图片上传失败...(image-4c3d8d-1630630196405)]

redis订阅与发布

redis发布订阅(pub/sub)是一种消息通信模式。

redis发布订阅

测试

订阅端:

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实力

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缓存穿透、击穿与雪崩

正常的缓存流程

正常的缓存流程

穿透

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

穿透

解决方法:

  1. 布隆过滤器。拦截不符合要求的请求
  2. 缓存空对象,把空的对象写进redis,避免直接访问数据库。

击穿(高并发访问单个key)

单个热点key瞬间失效,大量请求直接访问数据库,虽数数据库将此key写回缓存时间很短,但时间再短,还是有成千上百万请求在这几毫秒间直接访问数据库,从而导致宕机。

击穿

解决方法:

  1. 使用分布式锁,在请求数据库这一步上锁,就只有一个线程可以抢到锁访问数据库,同时让其他线程睡几毫秒,

    待数据库写回数据到redis,其他线程就可以直接从缓存里拿数据了。

雪崩

大量的redis缓存在同一时间全部失效(或redis宕机),直接访问数据库导致宕机

例如双11为了防止过多请求直接打到数据库上,提前将访问量大的数据写到redis缓存中,并设置3小时的失效时间,当3小时过后的一瞬间,大量的数据(key)访问数据库,导致数据库响应不及时宕机。

雪崩

解决方法:

  1. 随机设置缓存失效时间,不让他们同一时间失效
  2. redis集群部署
  3. 用不让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函数就越多,带来的性能消耗也会越大,所以并非误判率越低越好。

image.png

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));
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • [TOC] 一、Redis 基础常问 1.1、Redis 有哪些数据结构 基础:字符串String、字典Hash、...
    w1992wishes阅读 4,037评论 0 1
  • Redis 为什么这么快? 很多人只知道是 K/V NoSQl 内存数据库,单线程……这都是没有全面理解 Redi...
    大漠硝烟阅读 1,643评论 0 0
  • Redis为什么速度快 1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashM...
    亖狼何需装羴阅读 4,302评论 0 0
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 11,593评论 0 4
  • 公元:2019年11月28日19时42分农历:二零一九年 十一月 初三日 戌时干支:己亥乙亥己巳甲戌当月节气:立冬...
    石放阅读 11,914评论 0 2

友情链接更多精彩内容