Redis学习

Redis特点

解耦

  1. 方便扩展
  2. 大数据量高性能(Redis 一秒写8万,读11万数据)
  3. 数据类型多样型
  4. 高性能,高可用,高扩展
  5. 最终一致性

大数据时代的3V+3高

  1. 海量,多样,实时
  2. 高并发,高可扩,高性能

真正在公司中的实践,nosql+rdbms一起使用才是最强的

技术没有高低之分,就看你如何使用(提升内功,思维的提高)

Redis入门

  • redis是什么

    • Redis是Remote Dictionary Server(远程数据服务)的缩写,由意大利人Antirez(Salvatore Sanfilippo)开发的一款内存高速缓存数据库,它使用C语言编写,其数据模型为key-value,并支持丰富的数据结构(类型),如stringlisthashsetsorted sort。可持久化,保证数据安全。
    • 是一个开源的舒勇ANSI C语言编写,支持网络,可以基于内存持节话得日执行,key-value数据库,并提供多种语言得API
  • redis性能

    • 下面是官方的bench-mark数据: [1]

      ​ 测试完成了50个并发执行100000个请求。

      ​ 设置和获取的值是一个256字节字符串。

      ​ Linux box是运行Linux 2.6,这是X3320 Xeon 2.5 ghz。

      ​ 文本执行使用loopback接口(127.0.0.1)。

      ​ 结果:读的速度是110000次/s,写的速度是81000次/s 。

  • redis能干什么

    • 内存存储,持久化,内存中是断电即失,所以说持久化很重要
    • 效率高,可以用于告诉缓存
    • 发布订阅系统
    • 地图信息分析
    • 计时器,计数器(浏览量)
  • redis特性

    • 多样得数据类型
    • 持久化
    • 集群
    • 事务
  • 学习中要用到的东西

    • 本网站(hahahaha):https://shuo-codinghan.gitee.io/hsblog/
    • redis官网:https://redis.io/
    • 中文网:https://www.redis.net.cn/
    • window在github上下载(停止更新很久了(不建议在window上学习)

安装Redis

  • 下载安装包

在这里插入图片描述

  • 使用tar命令解压

在这里插入图片描述

  • 解压完毕

在这里插入图片描述

  • 我们可以看到上面的图片中有redis.conf配置文件,我们可以对这个文件进行拷贝,以免出错,这里我已经进行了拷贝

在这里插入图片描述

  • 安装Redis需要的环境,按照顺序执行下面的命令即可
yum install gcc-c++

在这里插入图片描述

在使用make命令对其进行配置生效(需要点时间)

在这里插入图片描述

  • redis不是默认启动的,所以我们要修改配置文件,daemonize设置为yes

在这里插入图片描述

  • 启动redis,进入redis下面的src目录(使用下面的命令,需要指定配置文件)
./redis-server 你redis.conf配置文件的目录

在这里插入图片描述

  • 连接redis客户端
./redis-cli -p redis的端口号(默认6379)

在这里插入图片描述

  • 测试连接

在这里插入图片描述

基本命令

  • 查看进程命令
ps -ef|grep redis

在这里插入图片描述

  • 关闭连接(连接redis客户端使用下面命令关闭连接,之后在exit)
shutdown

再次使用ps查看进程就看不到了

测试性能

  • redis-benchmark是一个压力测试工具,官方自带的
  • redis-benchmark命令参数

在这里插入图片描述

  • 我们来简单测试一下

测试100个并发连接 100000请求

./redis-benchmark -h localhost -p 6379 -c 100 -n 100000

在这里插入图片描述

  • 如何查看分析呢

在这里插入图片描述

基础知识

  • redis默认有十六个数据库

在这里插入图片描述

  • 默认使用的是第0个
  • 可以使用select来切换数据库

在这里插入图片描述

在这里插入图片描述

查看数据库所有的key

keys *

清空数据库和清空全部数据库

flushdb
flushall
  • Redis是单线程的

明白redis是很快的,官方表示,redis是基于内存操作,cpu不是redis性能瓶颈,redis的平静是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了,所以就是用的但相乘

redis是C语言写的,官方提供的数据为100000+的QPS 完全比比使用key-value的Memeache差

redis为什么单线程还这么快?

  1. 误区1:性能搞得服务器一定是多线程的?
  2. 误区2:多线程一定比单线程效率高

核心:redis是将所有的数据放在内存中的,所以说使用单线程效率就是最高的,对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个cpu上的,在内存情况下,这个就是最高的.

五大数据类型

  • 官网文档

在这里插入图片描述

  • Redis-Key
exists key //key是否存在,存在则返回1
move key 1 //移除数据库1代表当前数据库
expire key 秒数 //设置过期时间成功返回1
ttl key //查看当前key省多少时间过期
tepy key //查看当前key的类型

后面如果有什么不会的命令可以去官方去查看

https://www.redis.net.cn/order/

String(字符串)

90%的java程序员使用redis都只会String类型

####################################################################
127.0.0.1:6379> FLUSHALL
OK
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> set key1 h1 #设置值
OK
127.0.0.1:6379> get key1 #获得值
"h1"
127.0.0.1:6379> EXISTS key1 #判断key是否存在
(integer) 1
127.0.0.1:6379> APPEND key1 "hello" # 追加字符串,如果当前key不存在就相当于set key
(integer) 7
127.0.0.1:6379> get key1
"h1hello"
127.0.0.1:6379> APPEND key1 hanshuo111
(integer) 17
127.0.0.1:6379> get key1
"h1hellohanshuo111"
127.0.0.1:6379> 
127.0.0.1:6379> STRLEN key1 #获取字符串的长度
(integer) 17
127.0.0.1:6379> 
####################################################################
# 相当于Java中的i++
# 步长 i+=
127.0.0.1:6379> set views 0 # 初始为0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views # 自增1
(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 # 自减1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incrby views 10 # 可以设置步长,指定增量
(integer) 11
127.0.0.1:6379> decrby views 10 # 指定减量
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> 
####################################################################
#字符串范围range
127.0.0.1:6379> set key1 "hello,hanshuo"#设置一个key1的值
OK
127.0.0.1:6379> get key1
"hello,hanshuo"
127.0.0.1:6379> getrange key1 0 3 #获取0到3长度的字符串
"hell"
127.0.0.1:6379> getrange key1 0 -1 #获取全部的字符串长度
"hello,hanshuo"
127.0.0.1:6379> 

#替换
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx #位置1的字符被替换为xx
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
127.0.0.1:6379> 
####################################################################
# setex (set with expire)设置过期时间
# setnx (set if not expire)不存在,再设置(在分布式锁中会常常用到)

127.0.0.1:6379> setex key3 30 "hello" #设置key3值为hello 30秒后过期
OK
127.0.0.1:6379> ttl key3
(integer) 25
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> setnx mykey "redis" #如果mykey不存在,创建mykey
(integer) 1
127.0.0.1:6379> keys *
1) "mykey"
2) "key1"
3) "key2"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey "redis2" #如果mykey存在,创建失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> 
####################################################################
mset
mget

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k3"
3) "k2"
127.0.0.1:6379> mget k1 k2 k3 # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 # msetnx 是一个原子性的操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get key4
(nil)
127.0.0.1:6379>

#对象
set user:1 {name:zhangsan,age:3} #设置一个user:1 对象 值为json字符串来保存一个对象

#这里的key是一个巧妙的设计: user:{id}:{filed},如此设计在Redis中是完全OK了

127.0.0.1:6379> mset user:1:name zhansan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhansan"
2) "2"
127.0.0.1:6379> 
####################################################################
getset #先get再set

127.0.0.1:6379> getset db redis #如果不存在时,返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mysql # 如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mysql"
127.0.0.1:6379> 

数据结构是相通的!

String类似的使用场景:value除了是我们的字符还可以是我们的数字

  • 计数器
  • 统计多单位数量 uid:4654651531:follow 0
  • 粉丝数量
  • 对象缓存

List(列表)

  • 基本的数据类型,列表
  • 再redis里面,我们可以玩成,栈,队列,阻塞队列
  • 所有的list都是用L开头的,Redis不区分大小写命令
#######################################################################
127.0.0.1:6379> LPUSH list one # 将一个值或者多个值,插入到列表头 (左)
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1 #获取list所有值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1 #根据区间获取list具体的值
1) "three"
2) "two"
127.0.0.1:6379> 
127.0.0.1:6379> RPUSH list right # 将一个值或者多个值,插入到列表尾部 (右)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> 
#######################################################################
#LPOP
#RPOP
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> LPOP list #移除列表的第一个元素
"three"
127.0.0.1:6379> RPOP list #移除列表的最后一个元素
"right"
#######################################################################
#Lindex
127.0.0.1:6379> lindex list 0 #通过下标获取某一个值
"two"
127.0.0.1:6379> 
#######################################################################
#LLEN
127.0.0.1:6379> llen list #获取list长度
(integer) 2
127.0.0.1:6379> 
#######################################################################
#LREM 移除指定的值
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "two"
4) "one"
127.0.0.1:6379> LREM list 1 one #移除一个one
(integer) 1
127.0.0.1:6379> LREM list 2 two #移除两个two
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
127.0.0.1:6379> 
#######################################################################
#Ltrim 修剪,截断
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "hello1"
(integer) 2
127.0.0.1:6379> RPUSH mylist "hello2"
(integer) 3
127.0.0.1:6379> RPUSH mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 # 通过下标,截取指定的长度,这个list已经被改变了,阶段了只剩下截取的元素
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello1"
2) "hello2"
127.0.0.1:6379> 
#######################################################################
#rpoplpush 移除列表的最后一个元素,并移动到新的列表中

127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "hello1"
(integer) 2
127.0.0.1:6379> RPUSH mylist "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist #移除列表的最后一个元素,并移动到新的列表中
"hello2"
127.0.0.1:6379> lrange mylist 0 -1 # 查看原来的列表
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1 #查看目标列表中,确实存在值
1) "hello2"
127.0.0.1:6379> 
#######################################################################
#lset 将列表中指定下标的值替换为另外一个值,相当于跟新操作
127.0.0.1:6379> EXISTS list #判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item #如果不存在列表我们去更新就会报错
(error) ERR no such key
127.0.0.1:6379> LPUSH list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 item #更新当前下标的值
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other #如果不存在就会报错
(error) ERR index out of range
127.0.0.1:6379> 
#######################################################################
#linsert 将某一个具体的value插入到列表中某个元素的前面或者后面
127.0.0.1:6379> Rpush mylist hello
(integer) 1
127.0.0.1:6379> Rpush mylist world
(integer) 2
127.0.0.1:6379> linsert mylist before world other
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> linsert mylist after world new 
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"
127.0.0.1:6379> 

小结

  • 他实际上是一个链表,before Node after ,left right都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在
  • 在两边插入或者改动值,效率最高,中间元素,相对来说效率会低一点。

可以做,消息排队,消息队列( Lpush Rpop) 栈(Lpush Lpop)

Set(集合)

  • set中的值是不能重复的
#######################################################################
127.0.0.1:6379> sadd myset hello #set集合添加元素
(integer) 1
127.0.0.1:6379> sadd myset hanshuo
(integer) 1
127.0.0.1:6379> sadd myset lovehanshuo
(integer) 1
127.0.0.1:6379> smembers myset #查看set集合中的元素
1) "hanshuo"
2) "lovehanshuo"
3) "hello"
127.0.0.1:6379> sismember myset hello #判断set中的指定元素是否存在 有就返回1,则返回0
(integer) 1
127.0.0.1:6379> sismember myset world
(integer) 0
127.0.0.1:6379> 
#######################################################################
127.0.0.1:6379> scard myset#获取set集合中的内容元素个数
(integer) 4
127.0.0.1:6379> 
#######################################################################
#rem
127.0.0.1:6379> srem myset hello #移除set中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "hanshuo"
2) "lovehanshuo"
3) "helloworld"
127.0.0.1:6379> 
#######################################################################
set 无序不重复,抽随机
127.0.0.1:6379> SRANDMEMBER myset #随机抽选出一个元素
"lovehanshuo"
127.0.0.1:6379> SRANDMEMBER myset 1#随机抽选出指定个数的元素
1) "helloworld"
127.0.0.1:6379> 
#######################################################################
删除指定的key,随机删除一个key
127.0.0.1:6379> SMEMBERS myset
1) "hanshuo"
2) "lovehanshuo"
3) "helloworld"
127.0.0.1:6379> spop myset#随机移除一个元素
"helloworld"
127.0.0.1:6379> spop myset
"lovehanshuo"
127.0.0.1:6379> SMEMBERS myset
1) "hanshuo"
127.0.0.1:6379> 
#######################################################################
将一个指定的值移动到另外一个set中

127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> sadd myset hanshuo
(integer) 1
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 hanshuo#将一个指定的值移动到另外一个set中
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
127.0.0.1:6379> SMEMBERS myset2
1) "hanshuo"
2) "set2"
127.0.0.1:6379> 
#######################################################################
微博上面的共同关注的做法(数学中的并集)
数字集合类
- 差集
- 交集
- 并集
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2#差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2#交集   共同好友就可以这样实现
1) "c"
127.0.0.1:6379> SUNION key1 key2#并集
1) "a"
2) "b"
3) "c"
4) "e"
5) "d"
127.0.0.1:6379> 
#######################################################################
  • 微博,A用户将所有关注的人放在一个set集合中!将他的粉丝亦可以放在一个集合中
  • 共同关注就可以实现,共同爱好,二度好友,推荐好友(六度分隔理论:你如果认识六个人就可以认识全世界所有人)

Hash(哈希)

  • Map集合,key-Map!这时候值是一个map集合,本质和String类型没有太大区别,还是一个简单的key-value
  • set myhash field hanshuo
127.0.0.1:6379> hset myhash field1 hanshuo #set 一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash field1
"hanshuo"
127.0.0.1:6379> hmset myhash field1 hello filed2 world #set 多个key-value
OK
127.0.0.1:6379> hmget myhash field1 filed2#获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash#获取全部字段值
1) "field1" #key
2) "hello"  #value
3) "filed2"
4) "world"
127.0.0.1:6379> Killed
########################################################################
127.0.0.1:6379> hdel myhash field1 #删除hash指定的key字段,对应的value值也就消失了
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "filed2"
2) "world"
127.0.0.1:6379> 
########################################################################
hlen
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> hgetall myhash
1) "filed2"
2) "world"
3) "field1"
4) "hello"
5) "field2"
6) "world"
127.0.0.1:6379> hlen myhash #获取hash表的字段数量
(integer) 3
127.0.0.1:6379> 
########################################################################
127.0.0.1:6379> HEXISTS myhash field1 # 判断hash指定字段是否存在
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0
127.0.0.1:6379> 
########################################################################
只获得所有的field
只获取所有的value
127.0.0.1:6379> hkeys myhash#只获得所有的field
1) "filed2"
2) "field1"
3) "field2"
127.0.0.1:6379> hvals myhash#只获取所有的value
1) "world"
2) "hello"
3) "world"
127.0.0.1:6379> 
########################################################################
incr decr
127.0.0.1:6379> hset myhash field3 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 1#指定一个增量
(integer) 6
127.0.0.1:6379> HINCRBY myhash field3 -1#指定一个减量
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hello#不存在就创建
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world#存在了就不能创建
(integer) 0
127.0.0.1:6379> 

hash变更的数据 user name age,尤其是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更加适合字符串的存储

Zset(有序集合)

  • 在set基础上,增加了一个值,set k1 v1 zset k1 score1 v1
########################################################################
127.0.0.1:6379> zadd myset 1 one #添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> ZRange myset 0 -1 #查看
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> 
########################################################################
排序的实现
127.0.0.1:6379> zadd salary 2500 xiaohong#添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 hanshuo
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf#显示全部用户,从小到大排序
1) "hanshuo"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf
1) "hanshuo"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores #从小到大排序并显示value
1) "hanshuo"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores #显示工资小于2500人的升序排序
1) "hanshuo"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> 

127.0.0.1:6379> ZREVRANGE salary 0 -1 #从大到小排序
1) "zhangsan"
2) "hanshuo"
127.0.0.1:6379> 

########################################################################
移除元素
127.0.0.1:6379> zrange salary 0 -1
1) "hanshuo"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong#移除一个元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "hanshuo"
2) "zhangsan"
127.0.0.1:6379> 
########################################################################
127.0.0.1:6379> zcard salary#获取有序集合中的个数
(integer) 2
127.0.0.1:6379> 
########################################################################
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 hanshuo
(integer) 2
127.0.0.1:6379> zcount myset 1 3#获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
127.0.0.1:6379> 

其余的一些API,通过我们的学习,剩下的如果工作有需要,这个时候可以去查查看官方文档

  • 案例思路
    • set:排序 存储班级成绩表,工资表排序!
    • 普通消息,1,重要消息 2,带权重进行判断
    • 排行榜应用实现,取 Top N 测试

三种特殊数据类型

为什么其他教程中都不喜欢讲这些,这些在生活中或开发中,都有十分多的应用场景,学习了,就是多一个思路!

技多不压身!!!

geospatial(地理位置)

  • 朋友的定位,附近的人,打车距离计算
  • Resis的Geo,这个功能可以推算地理位置信息,两地之间,方圆几里的人!
    • 可以查询一些测试数据:https://jingweidu.bmcx.com/

只有六个命令

getadd

#添加地理位置
#getadd 南极和北极无法直接添加,我们一般会下载城市数据一次性导入
#参数key 值(维度,经度,名称)
#有效的经度从-180到180
#有效的维度从-85.05112878到85.05112878
#当坐标位置超出上述指定范围时,该命令将会返回一个错误
127.0.0.1:6379> geoadd China:city 116.23 40.22 beijing
(integer) 1
127.0.0.1:6379> geoadd China:city 121.48 31.40 shanghai
(integer) 1
127.0.0.1:6379> geoadd China:city 106.54 29.40 chongqing
(integer) 1
127.0.0.1:6379> geoadd China:city 113.88 22.55 shenzhen
(integer) 1
127.0.0.1:6379> geoadd China:city 120.21 30.20 hangzhou
(integer) 1
127.0.0.1:6379> geoadd China:city 108.93 34.23 xian
(integer) 1
127.0.0.1:6379> 

getpos

127.0.0.1:6379> GEOPOS China:city beijing#获取指定的经度和维度
1) 1) "116.23000055551528931"
   2) "40.2200010338739844"
127.0.0.1:6379> GEOPOS China:city beijing chongqing#获取多个指定的经度和维度
1) 1) "116.23000055551528931"
   2) "40.2200010338739844"
2) 1) "106.54000014066696167"
   2) "29.39999880018641676"
127.0.0.1:6379> 

geodist

两人之间的距离

单位:

  • m表示米
  • km表示千米
  • mi表示为英里
  • ft表示英尺
127.0.0.1:6379> geodist China:city beijing shanghai
"1088785.4302"
127.0.0.1:6379> geodist China:city beijing shanghai km#查询北京到上海的距离
"1088.7854"
127.0.0.1:6379> 

georadius以给定的经度纬度为中心,找出某一个半径内的距离

附近的人(获得所有附近的人的地址,定位)通过半径来查询

获得指定数量的人,500

所有数据应该都录入:China:city中,结果会更精确

127.0.0.1:6379> GEORADIUS China:city 110 30 1000 km#以110 30 这个经纬度为中心,寻找方圆1000km以内的城市
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS China:city 110 30 500 km
1) "chongqing"
2) "xian"
127.0.0.1:6379> GEORADIUS China:city 110 30 500 km withdist#显示到中间的位置
1) 1) "chongqing"
   2) "340.8679"
2) 1) "xian"
   2) "481.1540"
127.0.0.1:6379> GEORADIUS China:city 110 30 500 km withcoord#显示他人的定位信息
1) 1) "chongqing"
   2) 1) "106.54000014066696167"
      2) "29.39999880018641676"
2) 1) "xian"
   2) 1) "108.92999857664108276"
      2) "34.23000121926852302"
127.0.0.1:6379> GEORADIUS China:city 110 30 1000 km withdist withcoord count 1#筛选出指结果
1) 1) "chongqing"
   2) "340.8679"
   3) 1) "106.54000014066696167"
      2) "29.39999880018641676"
127.0.0.1:6379> 

georadiusbymember

#找出位于指定元素周围其他元素
127.0.0.1:6379> GEORADIUSBYMEMBER China:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379> 

geohash 返回一个或多个位置元素的geohash表示

该命令将返回11个字符的Geohash字符串

#将二维的经纬度转换为一维的字符串,如果两个字符串更像则两地更接近
127.0.0.1:6379> GEOHASH China:city beijing chongqing
1) "wx4sucu47r0"
2) "wm5z22h53v0"
127.0.0.1:6379> 

GEO底层的实现原理就是Zset!我们可以使用Zset命令操作GEO

127.0.0.1:6379> ZRANGE China:city 0 -1#查看地图中全部的元素
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> Zrem China:city beijing#移除指定元素
(integer) 1
127.0.0.1:6379> ZRANGE China:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
127.0.0.1:6379> 

Hyperloglog

什么是基数

A{1,3,5,7,8,7}

B{1,3,5,7,8}

基数:不重复的元素=5,可以接受误差

简介

Redis2.8.9版本就更新了hyperloglog数据结构

Redis Hyperloglog技术统计的算法

优点:占用的内存是固定的,2^64不同的元素的计数,只需要使用12kb内存,如果要村内存角度来比较的话Hyperloglog首选

网页的UV(一个人访问一个网站多次,但是还是算一个人)

传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断

这个方式如果保存大量的用户id就会比较麻烦,我们的目的就是为了计数,而不是保存用户id

0.81%错误率!统计UV任务,可以忽略不记!

127.0.0.1:6379> PFadd mykey a b c d e f g h i j#创建第一组元素
(integer) 1
127.0.0.1:6379> pfcount mykey#统计基数数量
(integer) 10
127.0.0.1:6379> PFadd mykey2 i j z x c v b n m#创建第二组元素
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2#合并两组 mykey mykey2 -> mykey3 并集
OK
127.0.0.1:6379> pfcount mykey3 #查看数量
(integer) 15
127.0.0.1:6379> 

如果允许容错,那么一定使用Hyperloglog

如果不允许容错,就是用set

Bitmap

位存储

只有个两个状态的,都可以使用bitmap

Bitmap位图,数据结构,都是操作二进制位来进行记录,就是有01两个转台

测试

周一:1 周二 :0…

在这里插入图片描述

查看某一天是否有打卡

127.0.0.1:6379> getbit sign 3
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
127.0.0.1:6379

统计这周的打卡记录操作

127.0.0.1:6379> bitcount sign
(integer) 4
127.0.0.1:6379> 

事务

Redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行

一次性,顺序性,排他性,执行一些列的命令

-----队列 set set set 执行-----

Redis事务没有隔离级别的概念

所有的命令在事务中,并灭有直接被执行!只有发起执行命令的时候才会执行!Exec

Redis单条命令式保存原子性的,但是事务不保证原子性

redis事务

  • 开启事务(multi)
  • 命令入队(…)
  • 执行事务(exec)

正常执行事务

127.0.0.1:6379> multi#开启驶入
OK
#命令入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec#执行事务
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379> 

放弃事务

127.0.0.1:6379> multi#开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> DISCARD#放弃事务
OK
127.0.0.1:6379> get k4#事务中的命令都不会被执行
(nil)
127.0.0.1:6379> 

编译型异常(代码有问题,命令错误),食物中所有的命令都不会被执行

127.0.0.1:6379> MULTI 
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> getset k3 #错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> 

运行时异常

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range#虽然第一条命令成功了,但是依旧正常执行成功
2) OK
3) "v2"
127.0.0.1:6379> 

监控!Watch(面试常问)

悲观锁

  • 很悲观,认为什么时候都会出问题,无论什么时候都会加锁

乐观锁

  • 很乐观,认为什么时候都不会出问题,所以不会上锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据

redis监测测试

正常执行成功

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 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
127.0.0.1:6379> 

测试多线程修改值,使用watch当作redis的乐观锁操作

127.0.0.1:6379> watch money #监视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)> 
127.0.0.1:6379(TX)>  exec #执行之前,另一个线程,修改了我们要操作的值,执行之后,操作失败
(nil)
127.0.0.1:6379> 
#解锁,如果发现事务失败,就使用unwatch先解锁
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379>
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 1
QUEUED
127.0.0.1:6379(TX)> incrby money 1
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 999
2) (integer) 1000
127.0.0.1:6379> 

如果修改失败,获取最新的值

Jedis

我们要使用Java来操作Redis

什么是jedis是Redis官方推荐的Java连接开发工具,使用Java操作Redis中间件,如果你要使用Java操作redis,那么一定要对jedis十分熟悉

测试

创建maven项目导入maven包

  1. maven仓库地址:https://mvnrepository.com/
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
	<groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
	<version>3.2.0</version>
</dependency>
<!--fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

编码测试:

连接Redis

在这里插入图片描述

输出

在这里插入图片描述

APi的使用

在这里插入图片描述

public static void main(String[] args) {
        //new Jedis 对象即可
        Jedis jedis = new Jedis("0.0.0.0",6379);
        //jedis 所有的命令就是我们之前学习的所有命令
        System.out.println(jedis.ping());
        try{
            
        }catch (Exception e){
            
        }finally {
            jedis.close();
        }
    }

RedisTemplate模板

package com.kuang.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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
  @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工具类

package com.kuang.utils;

import org.springframework.beans.factory.annotation.Autowired;
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
    private RedisTemplate<String, Object> redisTemplate;

    // =============================common============================
    /**
     * 指定缓存失效时间
     * @param key  键
     * @param time 时间(秒)
     */
    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)
     */
    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)
     */
    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
     */
    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 对应多个键值
     */
    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)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }


    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     * @param key 键
     */
    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 键
     */
    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代表所有值
     */
    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 键
     */
    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倒数第二个元素,依次类推
     */
    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 值
     */
    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  时间(秒)
     */
    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;
        }

    }

}

Redis.conf详解

  • 启动的时候,就是通过这个文件来启动的

单位

在这里插入图片描述

1,配置文件unit单位,对大小写不敏感

包含include

在这里插入图片描述

网络

bind 127.0.0.1 -::1 #绑定的ip
protected-mode yes#保护模式
port 6379#端口

通用general

daemonize yes#以守护进程的方式运行,默认是no我们需要自己开启为yes
pidfile /var/run/redis_6379.pid #如果以后台的方式运行,我们就需要指定一个pid文件

#日志
# Specify the server verbosity level.
# 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 ""#日志的文件名
databases 16#数据库的数量,默认十六个
always-show-logo no#是否总是显示logo

快照

持久化,在规定时间内,执行了多少次操作,则会吃就好的.rdb.aof

redis是内存数据库,如果没有持久化,那么数据断电及失

#如果900内,如果至少有一个1 key进行了修改,我们就进行持久化操作
save 900 1
#如果300内,如果至少有一个10 key进行了修改,我们就进行持久化操作
save 300 10
#如果60内,如果至少有一个10000 key进行了修改,我们就进行持久化操作
save 60 10000
#我们之后学习持久化,会定义这个持久化
stop-writes-on-bgsave-error yes # 如果持久化出错了是否继续工作
rdbcompression yes#是否压缩rdb文件,需要消耗一些cpu资源
rdbchecksum yes#保存rdb文件的时候,检查校验
dir ./#rdb文件保存的目录

Security安全

可以通过命令设置命令

config set requirepass "密码"
auth "密码"#登录

限制

maxclients 10000#设置最大客户端的数量
maxmemory <bytes>#最大的内存容量
maxmemory-policy noeviction#内存到达上限的处理策略
		#移除一些过期的key
		#报错
		#……

在这里插入图片描述

APPEND ONLY MODE模式 aof配置

在这里插入图片描述

Redis持久化

RDB(Redis DataBases)

  • 面试和go你工作,持久化都是重点
  • Redis是内存数据库,如果不讲内存中的数据库状态保存到磁盘中,那么服务器进程退出,服务器中的数据状态也会小时,所以Redis提供了持久化功能

什么是RDB

在这里插入图片描述

在这里插入图片描述

我们默认的就是rdb,一般情况加不需要修改这个配置

rdb保存的文件是,dump.rdb

在这里插入图片描述

触发机制

1.save的规则满足的情况下,会自动触发rdb规则

2.执行flushall命令,也会去触发我们的rdb规则

3.退出redis,也会产生rdb文件

如果回复rdb文件

  1. 只需要讲rdb文件放在我们redis启动目录就可以,redis启动时就会检测rdb文件
  2. 查看我们需要存放的位置config get dir,如果在这个目录下存在dump.rdb文件,启动就hi自动恢复数据

优缺点

  • 优点
    • 适合大规模的数据恢复!
    • 对数据的完整性不高!
  • 缺点
    • 需要一定时间间隔进程操作,如果redis意外宕机,最后一次修改数据就没有了
    • fork进程的时候,会占用一定的内存空间

AOF(Append Only File)

将我们所有的命令都记录下好,history,回复的时候就把这个文件全部都执行一遍

是什么

在这里插入图片描述

在这里插入图片描述

append

在这里插入图片描述

appendonly 默认是不开启的,我们需要手动开启!我们只需要讲appendonly改为yes就开启了aof

重启我们的redis就可以生效了,会生成文件

如果这个aof文件有错误,这个时候我们的Redis是启动不起来的,我们需要修复相和歌aof文件

redis给我们提供了一个工具 redis-check-aof --fix appendonly.aof

在这里插入图片描述

重启即可恢复

优点和缺点

  • 优点
    • 每一次修改都同步,文件的完整性会更加好
    • 每秒同步一次,可能会丢失一秒的数据
    • 从不同步,效率最高
  • 缺点
    • 相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
    • 运行效率也要比rdb慢,所以redis默认的配置就是rdb

扩展

在这里插入图片描述

在这里插入图片描述

Redis发布订阅

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

命令

在这里插入图片描述

  • 使用场景
    • 实时消息系统
    • 实时聊天
    • 订阅,关注系统都是可以的
  • 稍微复杂的场景,我们就会使用消息中间件来做MQ

Redis主从复制

在这里插入图片描述

在这里插入图片描述

主从复制,读写分离!80%情况下都是进行读操作,减缓服务器压力,架构中经常使用 一主一从

在公司中,主从复制就是必须要使用的

环境配置

只配置从库,不用高配置主库

127.0.0.1:6379> info replication#查看当前库的信息
# Replication
role:master#角色 master
connected_slaves:0#没有从机
master_failover_state:no-failover
master_replid:d7d34516269339c67cb548c3a721f5127c1eb2cd
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
127.0.0.1:6379> 

复制三个配置文件,然后修改对应信息

  1. 端口号
  2. pid名字
  3. log文件名字
  4. dump.rdb名字

修改完毕之后启动三个服务,可以通过进程信息查看

在这里插入图片描述

一主二从

默认情况下,每台都是主节点,我们一般情况下只用配置从机就好了

info replication#查看信息
slaveof 127.0.0.1 6379#在80服务中使用,认6379端口的redis为主机

#在6379中查看信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2 #有两个从机了
slave0:ip=127.0.0.1,port=6380,state=online,offset=140,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=140,lag=1
master_failover_state:no-failover
master_replid:511ba726375231fddf422c1a97bf347a69bbc61b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:140
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:140
127.0.0.1:6379> 

真实的配置应该在配置文件中配置,这样的话才是永久的,我们上面的配置是使用命令配置的是暂时的

第一个是配置主机的IP和端口,第二个是主机的密码,如果没有密码就不用配置,这样配置之后,这台redis每次启动之后都是从机

在这里插入图片描述

细节了解

主机可以写,从机负责读,主机中的所有信息和数据,都自动会被从机保存,从机写就报错

  • 主机断开连接,从机依旧连接到主机的,但是没有写的操作了,这个时候,主机回来了,从机依旧可以直接连接主机
  • 如果使用命令行配置的主从,这个时候从机重启了,从机会自动变成主机,只要变回了从机,立马就会从机中获取值

复制原理

在这里插入图片描述

层层连接

上一个主节点,连接下一个从节点

如果没有主节点,这个时候能不能选出来一个主节点呢,手动

如果主机断开了,使用命令重新配置一个从节点为主机!其他的节点就可以手动连接到主节点,如果这个时候原来的主节点回来了,需要重新配置

127.0.0.1:6380> slaveof no one#使此从节点变为主节点
OK
127.0.0.1:6380> 

哨兵模式(自动选举主节点的模式)

上面我们讲的操作的自动化模式

概述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 配置哨兵文件serninel.conf
sentinel monitor mymaster 127.0.0.1 6379 1#表示监控那个主机,1代表主机挂了,slave投票,票数最多的,就会成为主机

如果master节点断开了,这个时候就会从从机中随机选择一个服务器(这里面有一个算法)

如果master节点会来了,只能作为新主节点的从节点,这就是哨兵模式

优点和缺点

  • 优点
    • 哨兵集群,基于主从复制模式,所有的主从配置优化,他全有
    • 主从可以切花,故障可以转移,系统的可用性就会更好
    • 烧饼模式就是主从模式的升级,手动到自动,更加健壮
  • 缺点
    • Redis不好在线扩容,达到上限之后,十分麻烦
    • 实现哨兵模式的配置其实很麻烦,里面有很多配置,如果有哨兵集群,配置文件很多

Redis缓存穿透和雪崩(面试高频,工作常用)

在这里我们不会分析区分详细的底层,服务的高可用问题

缓存穿透(查不到导致)

概念

在这里插入图片描述

解决方案

布隆过滤器

在这里插入图片描述

缓存空对象

在这里插入图片描述

在这里插入图片描述

缓存击穿(量太大,缓存过期)

概念

在这里插入图片描述

解决方案

在这里插入图片描述

缓存雪崩

概念

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

解决方案

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coding_Bryce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值