Redis详细教程

Redis

nosql

Nosql概述:
为什么要用Nosql
  1. 单机MySQL的年代!

    app —> dal —>Mysql

  2. Memcached(缓存)+ MySql + 垂直拆分(读写分离)

    优化数据结构和索引 --> 文件缓存(IO)–> Memcached(当时最热门的技术!)

  3. 分库分表 + 水平拆分 + MySQL集群

    本质:数据库(读、写)

    早些年MyISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题

    转战Innodb : 行锁

    慢慢就开始了分库分表来缓解压力!

什么是Nosql

NoSQL = Not Only Sql (不仅仅是sql)

泛指非关系型数据库

NoSql特点:

解耦!

  1. 方便扩展!(数据之间没有关系,很好扩展!)

  2. 大数据量高性能!(Redis 一秒写8万次,读取11万次)

  3. 数据类型是多样型的!(不需要实现设计数据库!随取随用!)

  4. 传统RDBMS 和 NoSQL

    传统的 RDBMS
    - 结构化组织
    - SQL
    - 数据和关系都存在单独的表中
    - 操作数据,数据定义语言
    - 严格的一致性
    - 基础的事务
    - .......
    
    NoSql
    - 不仅仅是数据
    -  没有固定的查询语言
    -  键值对存储,列存储,文档储存,图形化存储
    -  最终一致性
    -  CAP定理BASE
    -  高性能、高可用、高可扩展
    -  .......
    

阿里巴巴框架演进

nosql 数据模型

KV键值对:
  • 新浪:Redis
  • 美团:Redis + Tair
  • 阿里、百度:Redis + memecache
文档型数据库:
  • MongoDB
    • MonoDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
    • MonoDB是一个介于关系型数据库和非关系型数据库中中间的产品 (MonoDB是非关系型数据库功能最丰富的,最想关系型数据库)
  • ConthDB
列存储:
  • HBase
  • 分布式文件系统
图形化数据库:
  • 他不是存图形的,放的是关系,比如:朋友圈社交网络、广告推荐!
  • Neo4J、InfoGrid

Nosql 四大分类

CAP

BASE

Redis入门

Redis是什么!

Redis(Remote Dictionary Server ),即远程字典服务

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

免费和开源!是当下最热门的NoSql 技术之一!也被人们称为架构化数据库

Redis能干什么!
  1. 内存储存、持久化、内存中的数据是断电及失、所以说持久化很重要!(rdb、aof)
  2. 效率高、可以用于告诉缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器、计数器、(数据浏览量)
  6. ……
Redis特性!
  1. 持久化
  2. 多样化数据库
  3. 集群
  4. 事务
  5. ……

安装Redis

Windows安装

windows :下载地址

下载完成解压

image-20210312190659183

启动Redis服务器:

双击运行服务 redis-server.exe

image-20210312191028923

运行成功

再次运行redis客户端 启动 redis-cli.exe

127.0.0.16379> ping  ----> 测试是否连接成功
PONG 
127.0.0.16379> set name changan  -----> 设置 key value
OK
127.0.0.16379> get name  ------> 用 key 去寻找 value
"changan"

Linux安装

官网下载:地址

下载完成之后导入到 Liunx 的 opt 目录下面

执行 tar -zxvf redis... 进行解压

解压完成只要需要安装c++ 环境 yum -y install gcc-c++

安装完成执行 make

image-20210313130749997

执行完成之后在执行一遍

然后执行 make install

image-20210313130858601

redis 的默认路径在 usr/local/bin目录下面

image-20210313131120947

移动 redis.config 到 本目录下

image-20210313131349900

redis默认不是后台启动,我们需要修改配置文件

找到这个修改为 yes

image-20210313131611230

启动redis 服务

image-20210313131826979

测试连接

image-20210313132042196

查看redis服务信息

image-20210313132339207

关闭redis

127.0.0.16379> shutdown   --->关闭redis
not connected> exit   ---> 退出

redis性能测试

序号选项描述默认值
1-h指定服务器主机名127.0.0.1
2-p指定服务器端口6379
3-s指定服务器 socket
4-c指定并发连接数50
5-n指定请求数10000
6-d以字节的形式指定 SET/GET 值的数据大小2
7-k1=keep alive 0=reconnect1
8-rSET/GET/INCR 使用随机 key, SADD 使用随机值
9-P通过管道传输 请求1
10-q强制退出 redis。仅显示 query/sec 值
11–csv以 CSV 格式输出
12-l生成循环,永久执行测试
13-t仅运行以逗号分隔的测试命令列表。
14-IIdle 模式。仅打开 N 个 idle 连接并等待。

测试 使用 redis-benchmark

测试 :100个并发连接, 100000 请求

redis-benchmark -h 127.0.0.0 -p 6379 -c 100 -n 100000

====== PING_INLINE ======                                                   
  100000 requests completed in 2.31 seconds    ----> 对我们10w请求进行测试
  100 parallel clients    -----> 100个并发的客服端
  3 bytes payload    ----> 每次写入3个字节
  keep alive: 1   ---> 只有一台服务器来处理这些请求,单机性能
  host configuration "save"3600 1 300 100 60 10000
  host configuration "appendonly": no
  multi-thread: no
      
====== SET ======                                                   
  100000 requests completed in 2.31 seconds
  100 parallel clients
  3 bytes payload
  keep alive: 1
  host configuration "save"3600 1 300 100 60 10000
  host configuration "appendonly": no
  multi-thread: no

Latency by percentile distribution:
0.000% <= 0.935 milliseconds (cumulative count 1)
50.000% <= 1.599 milliseconds (cumulative count 50402)
75.000% <= 1.895 milliseconds (cumulative count 75422)
87.500% <= 2.039 milliseconds (cumulative count 87552)
93.750% <= 2.143 milliseconds (cumulative count 93953)
96.875% <= 2.487 milliseconds (cumulative count 96893)
98.438% <= 3.607 milliseconds (cumulative count 98439)
99.219% <= 6.535 milliseconds (cumulative count 99219)
99.609% <= 12.039 milliseconds (cumulative count 99613)
99.805% <= 12.583 milliseconds (cumulative count 99805)
99.902% <= 13.031 milliseconds (cumulative count 99903)
99.951% <= 13.479 milliseconds (cumulative count 99952)
99.976% <= 13.775 milliseconds (cumulative count 99976)
99.988% <= 13.927 milliseconds (cumulative count 99988)
99.994% <= 14.007 milliseconds (cumulative count 99994)
99.997% <= 14.039 milliseconds (cumulative count 99997)
99.998% <= 14.071 milliseconds (cumulative count 99999)
99.999% <= 14.087 milliseconds (cumulative count 100000)
100.000% <= 14.087 milliseconds (cumulative count 100000)

基础知识

image-20210318154701720

Redis默认数据库

Redis默认有16个数据库,默认使用第0个

  • select 2 ----> 更换数据库
  • dbsize -----> 查看数据库大小
  • keys * ------> 查看数据库所有的key
  • flushdb --------> 清空当前数据库
  • flushall ------> 清空所有库
[root@VM-0-5-centos changanconfig]# redis-cli -p 6379
127.0.0.16379> ping
PONG
127.0.0.16379> select 2   ---->  更换数据库
OK
127.0.0.16379[2]> dbsize  -----> 查看数据库大小
(integer) 0
127.0.0.16379[2]> 

Redis数据类型

五种基本数据类型
  • Redis-key
  • String
  • List
  • Set
  • Hash
  • Zset
Redis-key

keys * 查看当前库所有的key

exists name 判断namekey 是否存在

move name 1 移除 name

expire name 10 设置name 10秒钟之后过期

ttl name 查看过期时间

type name 查看当前 key 的类型

String

append 追加字符串

127.0.0.16379> keys *
1) "name"
2) "age"
127.0.0.16379> get name
"changan"
127.0.0.16379> append name love    
(integer) 11
127.0.0.16379> get name
"changanlove"
127.0.0.16379> exists name
(integer) 1
    

incr views 给 views 加1

decr views 给 views 减1

incrby views 给 views 加10

decrby views 给 views 减10

127.0.0.16379> set views 0
OK
127.0.0.16379> get views
"0"
127.0.0.16379> incr views
(integer) 1
127.0.0.16379> decr views
(integer) 0
127.0.0.16379> incrby views 10
(integer) 10
127.0.0.16379> decrby views 10
    

getrange 范围查询数据

127.0.0.16379> getrange key1 0 3
"hell"
127.0.0.16379> getrange key1 0 -1
"hello,word"
127.0.0.16379> getrange key1 5 -1
",word"
127.0.0.16379> getrange key1 5 0
""
127.0.0.16379> getrange key1 1 -1
"ello,word"
127.0.0.16379> getrange key1 0 -1
    

setrange 范围修改

127.0.0.16379> setrange key1 1 xx
(integer) 10
127.0.0.16379> keys *
1) "key1"
127.0.0.16379> get key1
"hxxlo,word"
    

常在分布式锁使用

127.0.0.1:6379> setex key2 30 "hello"   ---- > 设置过期时间
OK
127.0.0.1:6379> ttl key2                ---- > 查看剩余时间
(integer) 28
127.0.0.1:6379> setnx mykey "redis"     ---- > 如果 mykey 不存在,创建 mykey 返回1 
(integer) 1
127.0.0.1:6379> keys *   
1) "key2"
2) "key1"
3) "mykey"
127.0.0.1:6379> ttl key2 
(integer) -2
127.0.0.1:6379> setnx mykey "MongDB"    ---- > 如果 mykey 存在,就创建失败 返回0
(integer) 0
127.0.0.1:6379> get mykey
"redis"

批量获取值和设置值

mset 批量设置

127.0.0.16379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.16379> keys *
1) "k3"
2) "k2"
3) "k1"	
    

mget 同时获取多个值

127.0.0.16379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
    

msetnx 设置多个值 如果一个值设置失败,那么全部失败 (原子性)

127.0.0.16379> msetnx k1 v1 k5 v5
(integer) 0
    

getset 先获取在设置

127.0.0.16379> getset db redis   --> 不存在返回nil并创建
(nil)
127.0.0.16379> get db
"redis"
127.0.0.16379> getset db mongDb  --> 如果存在,返回当前的值,并设置新的值
"redis"
127.0.0.16379> get db
"mongDb"
    
应用场景
  • 计数器
  • 统计多单位数量
  • 粉丝数
  • 对象缓存存储
List

基本数据类型,列表

lpush 储存 《左存储》

rpush 储存 《右存储》

lrange 读取

127.0.0.16379> lpush list one  ---> 给左push
(integer) 1
127.0.0.16379> lpush list two
(integer) 2
127.0.0.16379> lpush list three
(integer) 3
127.0.0.16379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.16379> lrange list 0 1
1) "three"
2) "two"
    
#####################################################################
    
127.0.0.16379> rpush list yss
(integer) 4
127.0.0.16379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "yss"

    

lpop : 左移除

rpop : 右移除

127.0.0.16379> lpop list ----> 移除list第一个元素
"three"
127.0.0.16379> rpop list ----> 移除list最后一个元素
"yss"
127.0.0.16379> lrange list 0 -1
1) "two"
2) "one"

lindex 通过下标获取值

127.0.0.1:6379> lindex list 0
"two"
127.0.0.1:6379> lindex list 1
"one"

llen 获取list的长度

127.0.0.16379> llen list
(integer) 4

lrem 删除 ‘精确匹配’

127.0.0.16379> lrange list 0 -1
1) "two"
2) "one"
3) "asdfa"
4) "9889"
5) "9889"
127.0.0.16379> lrem list 1 asdfa  ----- > 删除 1个 asdfa
(integer) 1
127.0.0.16379> lrange list 0 -1
1) "two"
2) "one"
3) "9889"
4) "9889"
127.0.0.16379> rpush list 9889
(integer) 4
127.0.0.16379> lrange list 0 -1
1) "two"
2) "one"
3) "9889"
4) "9889"
127.0.0.16379> lrem list 2 9889   ------ > 删除 29889
(integer) 2
127.0.0.16379> lrange list 0 -1
1) "two"
2) "one"

ltrim 修剪

127.0.0.16379> rpush mylist "hello"
(integer) 1
127.0.0.16379> rpush mylist "hello1"
(integer) 2
127.0.0.16379> rpush mylist "hello2"
(integer) 3
127.0.0.16379> rpush mylist "hello3"
(integer) 4
127.0.0.16379> ltrim mylist 1 2   ----- > 通过下标截取指定长度
OK
127.0.0.16379> lrange mylist 0 -1
1) "hello1"
2) "hello2"

rpoplpush 移除列表的最后一个元素 并移动到新的列表中

127.0.0.16379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
3) "hello3"
127.0.0.16379> rpoplpush mylist myotherlist
"hello3"
127.0.0.16379> lrange mylist 0 -1
1) "hello1"
2) "hello2"
127.0.0.16379> lrange myotherlist 0 -1
1) "hello3"

lset 将列表中指定下标的值替换为另外一个

127.0.0.16379> exists list   --- >  判断是否有这个集合
(integer) 0
    
127.0.0.16379> lset list 0 item   ----- > 给list的第0个下标添加 item
(error) ERR no such key    ---> 报错,添加失败 没有这个list
    
127.0.0.16379> lpush list value1  ------ > 创建 list
(integer) 1
127.0.0.16379> lrange list 0 -1
1) "value1"
127.0.0.16379> lset list 0 item   ----- > 再次用 lset 覆盖 刚创建那个list的第0个下标
OK
127.0.0.16379> lrange list 0 -1
1) "item"
127.0.0.16379> lset list 1 item  -----> 如果给list添加值 这个下标不存在那么就会报错
(error) ERR index out of range

linsert 将某一个具体的值插入列表某个元素的前后

参数:``before左、前after` 右、后

127.0.0.16379> rpush mylist "hello"
(integer) 1
127.0.0.16379> rpush mylist "word"
(integer) 2

// before
127.0.0.16379> linsert mylist before "word" "other"  
(integer) 3
127.0.0.16379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "word"

// after
127.0.0.16379> linsert mylist after "word" "king"
(integer) 4
127.0.0.16379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "word"
4) "king"

小结
  • set实际是一个链表,before Node after,left ,right 都可以插入值

  • 如果key 不存在,创建新的链表

  • 如果key 存在,新增内容

  • 如果移除了所有的值,空链表,也代表不存在

  • 在两边插入或改动值,效率最高!中间元素,相对来说效率会第一点

    可以用来 消息排队! 消息队列(Lpush Rpop) 左进右出 ,栈(Lpush Lpop)左进左出

Set

set中的值不能重复

sadd

smembers

sismember

scard

127.0.0.16379> sadd myset hello   ----> 给集合添加值
(integer) 1
127.0.0.16379> sadd myset changan  
(integer) 1
127.0.0.16379> sadd myset king
(integer) 1
127.0.0.16379> SMEMBERS myset    -----> 查看当前集合元素
1) "king"
2) "changan"
3) "hello"
127.0.0.16379> SISMEMBER myset hello   -----> 查看集合是否有hello  有的话返回1 没有就是0
(integer) 1
127.0.0.16379> SISMEMBER myset word
(integer) 0
127.0.0.16379> scard myset   ------> 获取当前集合的个数
(integer) 3
    
127.0.0.16379> srem myset hello   -----> 删除集合指定的值
(integer) 1
127.0.0.16379> SMEMBERS myset
1) "king"
2) "changan"


SRANDMEMBER 随机从集合中筛选一个数据

127.0.0.16379> SRANDMEMBER myset
"king"
127.0.0.16379> SRANDMEMBER myset
"king"
127.0.0.16379> SRANDMEMBER myset
"king"
127.0.0.16379> SRANDMEMBER myset
"king"
127.0.0.16379> SRANDMEMBER myset
"changan"

spop 随机删除一个元素

127.0.0.16379> SMEMBERS myset
1) "king"
2) "changan"
127.0.0.16379> spop myset
"king"
127.0.0.16379> SMEMBERS myset
1) "changan"

smove将一个指定的值移动到另外一个set集合中

127.0.0.16379> sadd myset hello
(integer) 1
127.0.0.16379> sadd myset word
(integer) 1
127.0.0.16379> sadd myset changanking
(integer) 1
127.0.0.16379> sadd myset king
(integer) 1
127.0.0.16379> sadd myset2 set2
(integer) 1
127.0.0.16379> smove myset myset2 changanking    
(integer) 1
127.0.0.16379> SMEMBERS myset
1) "word"
2) "hello"
3) "changan"
4) "king"
127.0.0.16379> SMEMBERS myset2
1) "changanking"
2) "set2"

SDIFF 查看2个集合的差集

SINTER 查看2个集合的交集 共同好友就可以实现

SUNION 查看2个集合的

127.0.0.16379> sadd key1 a
(integer) 1
127.0.0.16379> sadd key1 b
(integer) 1
127.0.0.16379> sadd key1 c
(integer) 1
127.0.0.16379> sadd key2 c
(integer) 1
127.0.0.16379> sadd key2 d
(integer) 1
127.0.0.16379> sadd key2 e
(integer) 1
127.0.0.16379> SDIFF key1 key2
1) "a"
2) "b"
127.0.0.16379> SINTER key1 key2
1) "c"
127.0.0.16379> SUNION key1 key2
1) "c"
2) "b"
3) "a"
4) "d"
5) "e"

应用场景

微博 ,A用户将所有的关注的人放在一个set集合中,将他的粉丝也放在一个集合中

共同关注,共同爱好,二度好友,推荐好友!

Hash

Map集合,Key–Value

127.0.0.16379> hset map k changan   ---->  给map存一个k-v
(integer) 1
127.0.0.16379> hget map k           ---->  通过k取值
"changan"
127.0.0.16379> hmset map y king u anuyn  ---->  给map存多个值
OK 
127.0.0.16379> hmget map y u           ---->  获取map多个值
1) "king"
2) "anuyn"
127.0.0.16379> hgetall map              ------> 查看map所有的kv
1) "k"
2) "changan"
3) "y"
4) "king"
5) "u"
6) "anuyn"

hdel 删除指定的kv

127.0.0.16379> hdel map u     -----> 删除指定的 k
(integer) 1
127.0.0.16379> hgetall map
1) "k"
2) "changan"
3) "y"
4) "king"

hlen 查询集合有几组kv

127.0.0.16379> hlen map
(integer) 2

HEXISTS 查看集合的k是否存在

127.0.0.16379> HEXISTS map k
(integer) 1
127.0.0.16379> HEXISTS map u
(integer) 0

hkeys 查看所有的key

hvals 查看所有的value

127.0.0.16379> hkeys map
1) "k"
2) "y"
127.0.0.16379> hvals map
1) "changan"
2) "king"

HINCRBY 指定增量

hsetnx 创建一个集合 如果这个集合没有数据 就放入数据 返回1 如果有数据 那么返回0

127.0.0.16379> hset myhash field3 5    ----- >>> 指定增量
(integer) 1
127.0.0.16379> HINCRBY myhash field3 1   ------>>> 给数据加一
(integer) 6
127.0.0.16379> HINCRBY myhash field3 -1  ------>>>> 给数据减一
(integer) 5
127.0.0.16379> hsetnx myhash field4 hello  -------> 创建一个集合 如果存在则不能设置 返回1
(integer) 1
127.0.0.16379> hsetnx myhash field4 world   ---->> 创建一个集合 如果存在则不能设置 返回0
(integer) 0

hash可以用于存储对象

127.0.0.16379> hset user:1 name changan
(integer) 1
127.0.0.16379> hget user:1 name
"changan"
127.0.0.16379> hset user:1 age 23
(integer) 1
127.0.0.16379> hget user:1 age
"23"
应用场景

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

Zset(有序集合)

在set的基础上加了一个值

api:
127.0.0.16379> zadd myset 1 one     -----> 创建一个值
(integer) 1
127.0.0.16379> zadd myset 2 two 3 three   ----->  创建2个值
(integer) 2
127.0.0.16379> zrange myset 0 -1   ------> 获取myset 里的数据
1) "one"
2) "two"
3) "three"

zrangebyscore : 从低到高 进行排序

zrevrange : 从高到低 进行排序

127.0.0.16379> zadd salary 5000 xiaohong
(integer) 1
127.0.0.16379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.16379> zadd salary 500 zhangsan
(integer) 1
127.0.0.16379> zrangebyscore salary -inf +inf   ---- > 通过集合salary 的 钱数排序 -inf 负无穷  +inf 正无穷
    从低到高
1) "zhangsan"
2) "xiaoming"
3) "xiaohong"
    
127.0.0.16379> zrevrange salary 0 -1
1) "xiaoming"
2) "zhangsan"

127.0.0.16379> zrangebyscore salary -inf +inf withscores  -->> 查询出来所有数据 WITHSCORES会返回元素和其分数
1) "zhangsan"
2) "500"
3) "xiaoming"
4) "2500"
5) "xiaohong"
6) "5000"
127.0.0.16379> zrangebyscore salary -inf 2500 withscores
1) "zhangsan"
2) "500"
3) "xiaoming"
4) "2500"

zrem 移除

127.0.0.16379> zrange salary 0 -1
1) "zhangsan"
2) "xiaoming"
3) "xiaohong"
127.0.0.16379> zrem salary xiaohong
(integer) 1
    

zcard 获取集合中的个数

127.0.0.16379> zcard salary
(integer) 2
    

zcount 获取指定区间的成员数量

127.0.0.16379> zadd myset 1 "hello" 2 "word" 3 "changan"
(integer) 3
127.0.0.16379> zcount myset 1 2
(integer) 2
127.0.0.16379> zcount myset 1 3
(integer) 3
    
使用场景

set 排序 存储班级成绩,工资排序

普通消息 1、重要消息 2 带权重进行判断

排行榜应用实现 取 Top N测试

三种特殊数据类型
geospatial 地理位置

Redis 的 Geo 可以去实现 推算2地之间的距离,推算地理位置信息 … !

测试数据网站:http://www.toolzl.com/tools/gps.html

image-20210415171238885

Geoadd 添加地理位置
2极地区无法直接添加,我们一般会下载城市数据,用Java程序导入

将指定的地理空间位置(纬度、经度、名称)添加到指定的key

  • 有效经度为-180至180度。
  • 有效纬度为-85.05112878至85.05112878度。
127.0.0.16379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.16379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.16379> geoadd china:city 106.50 29.53 chongqi 114.05 22.52 shenzhen
(integer) 2
127.0.0.16379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian 
(integer) 2

geopos 查询地理位置

127.0.0.16379> geopos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.16379> geopos china:city xian
1) 1) "108.96000176668167114"
   2) "34.25999964418929977"
    

geodist

返回两个给定位置之间的距离。

如果两个位置之间的其中一个不存在, 那么命令返回空值。

指定单位的参数 unit 必须是以下单位的其中一个:

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。
127.0.0.16379> geodist china:city beijing xian km   北京到西安的直线距离
"910.0565"
127.0.0.16379> geodist china:city beijing shanghai km  北京带上海的直线距离
"1067.3788"

附近的人

半径搜索

127.0.0.16379> georadius china:city 110 30 1000 km  以 11030的地理坐标 查询 1000km 内的城市
1) "chongqi"
2) "xian"
3) "shenzhen"
4) "hangzhou"
127.0.0.16379> georadius china:city 110 30 500 km 以 11030的地理坐标 查询 500km 内的城市
1) "chongqi"
2) "xian"

127.0.0.16379> georadius china:city 110 30 500 km withdist   到中心的直线距离
1) 1) "chongqi"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
    
127.0.0.16379> georadius china:city 110 30 500 km withcoord   经纬度
1) 1) "chongqi"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"

        
127.0.0.16379> georadius china:city 110 30 500 km withcoord count 1  查询指定数量的
1) 1) "chongqi"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"

GEORADIUSBYMEMBER

找出位于指定元素周围的其他元素

127.0.0.16379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.16379> GEORADIUSBYMEMBER china:city beijing 500 km
1) "beijing"

删除、查看

127.0.0.16379> zrange china:city 0 -1   查看所有
1) "chongqi"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.16379> zrem china:city beijing  删除
(integer) 1
127.0.0.16379> zrange china:city 0 -1
1) "chongqi"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
Hyperloglog 基数统计

什么是基数!

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

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

基数 (不重复的元素)= 5,可以接收误差!

简介

pfadd :添加数据

pfcount :查询数据

pfmerge :合并数据

127.0.0.16379> pfadd mykey a b c d e f g h i j k
(integer) 1
127.0.0.16379> pfcount mykey
(integer) 11
127.0.0.16379> pfadd mykey2 b c d t y o p
(integer) 1
127.0.0.16379> pfcount mykey2
(integer) 7
127.0.0.16379> pfmerge mykey3 mykey mykey2
OK
127.0.0.16379> pfcount mykey3
(integer) 14
Bitmaps(位图) 位存储

只要是2位数的 数据结构就可以用

如:登录、未登录 活跃、不活跃

设置或者清空key的value(字符串)在offset处的bit值。

那个位置的bit要么被设置,要么被清空,这个由value(只能是0或者1)来决定。当key不存在的时候,就创建一个新的字符串value

测试

测试打卡记录 在2021年4月15日没有打卡等

setbit 存值

getbit 取值

bitcount 查询

127.0.0.16379> setbit sign 20210415 0 
(integer) 0
127.0.0.16379> setbit sign 20210416 1
(integer) 0
127.0.0.16379> setbit sign 20210414 0
(integer) 0
127.0.0.16379> setbit sign 20210413 1
(integer) 0
127.0.0.16379> setbit sign 20210412 1
(integer) 0
127.0.0.16379> setbit sign 20210417 1
(integer) 0
127.0.0.16379> getbit sign 20210416
(integer) 1
127.0.0.16379> getbit sign 20210415
(integer) 0
127.0.0.16379> bitcount sign
(integer) 4

事务

Redis 事务本质:一组命令的集合!

一个事务中所有的命令都会被序列化,在事务执行过程中,会按照顺序执行!

一次性、序列型、非它性、

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

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

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

Redis的事务:

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

测试:

127.0.0.16379> multi     // > 开启事务
OK
127.0.0.16379(TX)> set k1 1    // 命令入队
QUEUED
127.0.0.16379(TX)> set k1 2
QUEUED
127.0.0.16379(TX)> set k1 3
QUEUED
127.0.0.16379(TX)> set k2 2
QUEUED
127.0.0.16379(TX)> set k3 4
QUEUED
127.0.0.16379(TX)> set k4 6
QUEUED
127.0.0.16379(TX)> get k4
QUEUED
127.0.0.16379(TX)> set k7 9
QUEUED
127.0.0.16379(TX)> exec      //  执行事务
1) OK
2) OK
3) OK
4) OK
5) OK
6) OK
7) "6"
8) OK

放弃事务:

127.0.0.16379> multi    // 开启事务
OK
127.0.0.16379(TX)> set key7 7
QUEUED
127.0.0.16379(TX)> discard    // 取消事务
OK
127.0.0.16379> get key7
(nil)

编译型异常: 命令错误

127.0.0.16379> multi
OK
127.0.0.16379(TX)> set k1 2
QUEUED
127.0.0.16379(TX)> set k2 4
QUEUED
127.0.0.16379(TX)> set k4 5
QUEUED
127.0.0.16379(TX)> getset k7 8
QUEUED
127.0.0.16379(TX)> getset k9       //错误语法
(error) ERR wrong number of arguments for 'getset' command
127.0.0.16379(TX)> set k8 9
QUEUED
127.0.0.16379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors. 
    /*提示语法错误,所有命令都不执行 **/
127.0.0.16379> get k4
(nil)

运行时异常:如果命令中存在错误,不会影响其他命令执行

127.0.0.16379> set k1 7
OK
127.0.0.16379> set k2 "j9"
OK
127.0.0.16379> multi
OK
127.0.0.16379(TX)> incr k2    // 会执行失败
QUEUED
127.0.0.16379(TX)> set k3 3
QUEUED
127.0.0.16379(TX)> set k4 5
QUEUED
127.0.0.16379(TX)> get k4
QUEUED
127.0.0.16379(TX)> exec
1) (error) ERR value is not an integer or out of range    
    // 虽然第一条命令执行失败了后面的命令会继续执行
2) OK
3) OK
4) "5"
127.0.0.16379> get k3
"3"
127.0.0.16379> get k4
"5"

监控

悲观锁:

认为什么时候都会出现问题,无论做什么都会加锁

乐观锁:

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

  • 获取 version

  • 更新的时候比较 version

数据测试:

单线程

127.0.0.16379> set money 100    // 设置金钱是100
OK
127.0.0.16379> set out 0        // 设置金钱是0
OK
127.0.0.16379> watch money      // 给money上锁
OK
127.0.0.16379> multi     // 开启事务
OK
127.0.0.16379(TX)> decrby money 20    // money金钱减 20
QUEUED
127.0.0.16379(TX)> incrby out 20      // out 加 220
QUEUED
127.0.0.16379(TX)> exec
1) (integer) 80
2) (integer) 20
 

多线程

使用 watch 来当做Redis的乐观锁

// 线程 1 
127.0.0.16379> watch money      // 开启监控
OK
127.0.0.16379> multi            // 开启事务
OK
127.0.0.16379(TX)> decrby money 10    // 金币减10
QUEUED
127.0.0.16379(TX)> incrby out 10      // 金币加10
QUEUED
    
    // 线程 2
    127.0.0.16379> incrby money 1000   // 线程2 给这个用户 充值 1000
	(integer) 1080
    
// 线程 1    
127.0.0.16379(TX)> exec  // 线程 1 继续执行 然后就会执行失败
(nil)

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

解决办法:

127.0.0.16379> unwatch    // 放弃旧的锁
OK
127.0.0.16379> watch money   // 加新锁
OK
127.0.0.16379> multi    // 开启事务
OK
127.0.0.16379(TX)> decrby money 100
QUEUED
127.0.0.16379(TX)> incrby out 100
QUEUED
127.0.0.16379(TX)> exec
1) (integer) 1000
2) (integer) 120

Jedis

什么是Jedis

Jedis是Redis 推荐使用的Java开发工具!使用Java操作中间件!

测试
1、导入依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.5.2</version>
</dependency>

<!-- fastjson-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.76</version>
</dependency>

2、编码测试
连接数据库
public static void main(String[] args) {
        Jedis jedis = new Jedis("XXXXXXX"6379); // ip地址及端口号
        jedis.auth("---"); //redis 密码
        System.out.println(jedis.ping());
}

本地访问 直接 Jedis jedis = new Jedis("127.0.0.1",6379);

输出 PONG 连接成功

操作命令

事务:

RedisApi api = new RedisApi();
Jedis jedis = api.connection();
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello""word");
jsonObject.put("name""changan");

Transaction multi = jedis.multi();
String toString = jsonObject.toJSONString();
try {
    multi.set("user1",toString);        //注意事务执行的话要用事务 multi.set 而不是jedis.set
    multi.set("user2",toString);
    // int i = 1/0;
    multi.exec();
}catch (Exception e){
    multi.discard();
    e.printStackTrace();
}finally {
    System.out.println(jedis.get("user1"));
    System.out.println(jedis.get("user2"));
}
断开连接

jedis.close()

SpringBoot整合

说明:在SpringBoot2.x 之后 Jedis 被替换成为了 lettuce

区别:

  • Jedis : 采用直连方式 、是不安全的,要避免安全隐患要采用 Jedis的pool连接池管理! 像BIO
  • lettuce : 采用netty 实例可以在多个进程中共享、不存在线程不安全问题 像NIO
整合
  1. 添加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 配置Redis

    spring:
      redis:
        host: 服务器地址
        port: 6379
        password: redis密码
    
  3. 测试

    @Resource
    private RedisTemplate redisTemplate;
    
    @Test
    void contextLoads() {
        RedisConnection connection = Objects.requireNonNull(redisTemplate.getConnectionFactory()).getConnection();
        
        System.out.println(connection.ping());    // 测试链接是否成功
        redisTemplate.opsForValue().set("key""changan");   //  操纵字符串 set 一个值
        Object key = redisTemplate.opsForValue().get("key");  // 获取值
        System.out.println(key);
    }
    
    • redisTemplate.opsForValue() 操作字符串

    • redisTemplate.opsForList() 操作list

    • connection.close();    // 关闭链接
      connection.flushAll(); // 清空所有数据库的所有 key
      connection.flushDb();  // 清空
      
测试
@Test
public void UserTest() throws JsonProcessingException {
    RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
    connection.flushDb();
    User user = new User("长安"23);
    String s = new ObjectMapper().writeValueAsString(user);   // 转化为Json 数据
    redisTemplate.opsForValue().set("user",s);
    System.out.println(redisTemplate.opsForValue().get("user"));
}

// 输出结果  {"name":"长安","age":23}

如果我们直接传对象没有序列化 ,会报错

@Test
public void UserTest() throws JsonProcessingException {
    RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
    connection.flushDb();
    User user = new User("长安"23);
    redisTemplate.opsForValue().set("user",user);
    System.out.println(redisTemplate.opsForValue().get("user"));
}

//Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer; nested exception is java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [com.changan.model.User]

如果要传递对象要序列化

public class User implements Serializable {

    private String name;
    private int age;

}

// 结果  User(name=长安, age=23)

编写一个自己的 Redis 序列化

@Configuration
public class RedisConfig {

    // 配置自己的Redis  固定模板
    @Bean
    public RedisTemplate<StringObject> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 我们为了自己开发方便,一般直接使用<String,Object>
        RedisTemplate<StringObject> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // Json序列化配置
        Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jsonRedisSerializer.setObjectMapper(objectMapper);

        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key 采用String 方式序列化
        template.setKeySerializer(stringRedisSerializer);
        // hash 的key 采用String 方式序列化
        template.setHashKeySerializer(stringRedisSerializer);
        // Value 采用Json 序列化
        template.setValueSerializer(jsonRedisSerializer);
        // hash 的 Value 采用Json 序列化
        template.setHashValueSerializer(jsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }

}

Redis.conf详解

单位

image-20210419092419685

包含 INCLUDES

image-20210420092826846

这里包括一个或多个其他配置文件。这很有用,如果你
有一个标准的模板,去所有的Redis服务器,但也需要
自定义一些服务器设置。包括文件可以包括
其他文件,所以明智地使用这个。
注意选项“include”不会被命令“CONFIG REWRITE”重写
从admin或Redis哨兵。因为Redis总是使用最后处理的
line作为配置指令的值,你最好放包含
在这个文件的开头,以避免在运行时覆盖配置更改。
如果你有兴趣使用include覆盖配置
options,最好使用include作为最后一行。
include /path/to/local.conf
include /path/to/other.conf
网络 NETWORK

image-20210420093312397

bind 0.0.0.0 对所有人开放 可以指定单个或多个ip

指定多个ip 访问 ip 用空格 隔开

image-20210420093502139

protected-mode yes 开启受保护模式

prot 6379 默认端口号

通用 GENERAL

daemonize yes 以守护进程的方式运行,默认是 no 我们需要自己设置为yes

pidfile /www/server/redis/redis.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

#指定服务器详细级别。
#这个可以是:
# debug(大量信息,对开发/测试有用)
# verbose(很多很少有用的信息,但不像调试级别那样混乱)
# notice(有点冗长,可能是在生产中需要的内容)
# warning(只记录非常重要/关键的消息)

logfile "/www/server/redis/redis.log" 日志的文件位置

# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT <dbid> where
# dbid is a number between 0 and 'databases'-1
databases 16
    

#设置数据库个数。默认数据库为“DB 0”,可选择
#在每个连接上使用SELECT where
# dbid是一个介于0'databases'-1之间的数字

databases 默认的数据库数量 16 个

# By default Redis shows an ASCII art logo only when started to log to the
# standard output and if the standard output is a TTY. Basically this means
# that normally a logo is displayed only in interactive sessions.
#
# However it is possible to force the pre-4.0 behavior and always show a
# ASCII art logo in startup logs by setting the following option to yes.
always-show-logo yes

always-show-logo yes 是否显示log 默认为开启

快照 SNAPSHOTTING

image-20210420095513960

持久化数据 因为 Redis是内存数据库 如果断电等因素 会失去数据 所以我们需要在一定时间里 持久化数据

// 在 900s 内 有 1个key进行了操作 那么将会持久化一下
save 900 1
// 在 300s 内 有 10个key进行了操作 那么将会持久化一下
save 300 10
// 在 60s 内 有 1w个key进行了操作 那么将会持久化一下
save 60 10000

image-20210420095830031

stop-writes-on-bgsave-error yes 持久化 出错了是否继续工作 默认继续

# Compress string objects using LZF when dump .rdb databases?
# For default that's set to 'yes' as it's almost always a win.
# If you want to save some CPU in the saving child set it to 'no' but
# the dataset will likely be bigger if you have compressible values or keys.
rdbcompression yes

rdbcompression yes 是否压缩 rdb文件

默认压缩 压缩会消耗cpu资源

# Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
# This makes the format more resistant to corruption but there is a performance
# hit to pay (around 10%) when saving and loading RDB files, so you can disable it
# for maximum performances.
#
# RDB files created with checksum disabled have a checksum of zero that will
# tell the loading code to skip the check.
rdbchecksum yes

rdbchecksum yes 保存 rdb 文件时进行错误的校验

# The working directory.
#
# The DB will be written inside this directory, with the filename specified
# above using the 'dbfilename' configuration directive.
#
# The Append Only File will also be created inside this directory.
#
# Note that you must specify a directory here, not a file name.
dir /www/server/redis/

rdb 文件保存的目录

复制 REPLICATION
安全 SECURITY
# Require clients to issue AUTH <PASSWORD> before processing any other
# commands.  This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#
# requirepass foobared

requirepass xxxxx 设置redis 登录密码

#要求客户端在处理任何其他密码之前发出AUTH
#命令。这在您不信任的环境中可能很有用
#其他可以访问运行redis-server的主机。
#这个应该被注释掉,以便向后兼容,因为大多数
#人们不需要认证(例如,他们运行自己的服务器)
#警告:由于Redis是相当快的外部用户可以尝试
# 150k密码每秒对一个好的盒子。这意味着你应该这么做
#使用一个非常强的密码,否则它会很容易被破解
限制 CLIENTS

maxclients 10000 默认有 1w 个用户可以同时连接redis 服务器

maxmemory <bytes> redis 配置最大的内存容量

maxmemory-policy noeviction 内存到达上限的处理策略 6种

**1、volatile-lru:**只对设置了过期时间的key进行LRU(默认值)

2、allkeys-lru : 删除lru算法的key

**3、volatile-random:**随机删除即将过期key

**4、allkeys-random:**随机删除

5、volatile-ttl : 删除即将过期的

6、noeviction : 永不过期,返回错误

aof配置 APPEND ONLY MODE
基本配置

appendonly no 默认不开启 默认使用rdb持久化方式

appendfilename "appendonly.aof" 持久化文件的名字

# appendfsync always    // 每修改一个key都会执行 sync,消耗性能
appendfsync everysec    // 每一秒执行一次 sync,可能会丢失这1s的数据
# appendfsync no        // 不执行 sync,这个时候操作系统会自己同步数据速度是最快的
详细配置

Redis持久化

因为Redis是内存数据库,如果不把数据保存到磁盘中,那么如果机器出现断电等,数据就会丢失,

所以Redis提供了持久化功能

RDB

RDB (Redis DataBase)

什么是RDB

image-20210420173941820

就是在指定时间里把内存的数据存储到磁盘中

Rdb 默认保存的文件叫做 dump.rdb

image-20210420174644255

触发机制

image-20210420175238907

  1. save 的规则满足的情况下,会自动触发rdb规则
  2. 执行flushdb 命令,也会触发rdb文件规则
  3. 退出Redis ,也会产生rdb文件

备份就会生成一个 dump.rdb文件

如何回复rdb文件
  1. 只需要将rdb文件放到Redis启动目录下就可以,Redis启动的时候自动检查dump.rdb回复其数据
  2. 可以用 config get dir 查询我们需要将文件放在那个目录下
优点

适合大规模数据恢复

对数据的完成性数据不高可以使用

缺点

需要一定的时间间隔进行操作 ,如果在最后一次操作的时候当机了 那么最后修改的数据就没了

fork 一条进程的时候会占用一定的内存空间

AOF

AOF (Append Only File)

Aof是什么

将我们的所有命令记录下来,恢复的时候就把这个文件全部再执行一遍

AOf

以日志的形式记录每一个写的操作,将Redis执行过程中的所有命令指令记录下来(读操作不记录),只许追加文件但不可以改写文件,Redis启动之初会读取文件重新构建数据

aof 保存的文件是 appendonly.aof

image-20210422090840407

默认是没有开启的 我们需要的话需要手动开启 把 appendonly no 改为 appendonly yes

如果Aof文件受损了Redis启动不起来 我们可以通过 redis-check-aof --fix 来修复aof文件

image-20210422091808551

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

Redis发布与订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息 ------ > 微信 、微博的 关注系统

Redis 客户端可以订阅任意数量的频道

订阅/发布消息图:

  • 消息发布者
  • 频道
  • 消息订阅者

image-20210423090614975

下图是频道 channel1 , 以及订阅这个频道的三个客户端 ----- cilent2,cilent5 和 cilent1 之间的关系

image-20210423091450391

当有消息通过Publish 命令发送给频道 channel1 时,这个消息就会被它发送给订阅它的三个客户端

image-20210423091506037

命令

redis 发布订阅常用命令:

序号命令及描述
1PSUBSCRIBE pattern [pattern …] 订阅一个或多个符合给定模式的频道。
2[PUBSUB subcommand argument [argument …]] 查看订阅与发布系统状态。
3PUBLISH channel message 将信息发送到指定的频道。
4[PUNSUBSCRIBE pattern [pattern …]] 退订所有给定模式的频道。
5SUBSCRIBE channel channel …] 订阅给定的一个或多个频道的信息。
6[UNSUBSCRIBE channel [channel …]] 指退订给定的频道。

订阅信息:

127.0.0.16379> subscribe king     // 订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "king"
3) (integer) 1
    // 等待推送的信息
1) "message"   // 消息
2) "king"      // 来自于那个频道
3) "hello,yss"  // 来自哪个频道的内容
1) "message"
2) "king"
3) "\xe6\x88\x91\xe7\x88\xb1\xe4\xbd\xa0"

推送信息:

127.0.0.16379> publish king "hello,yss"  // 发布信息到指定的频道
(integer) 1
127.0.0.16379> publish king "我爱你"
(integer) 1

Redis主从复制

概念

一般来说,要将 Redis用于工程项目中,只使用一台 Redist是万万不能的,原因如下:

1、从结构上,单个 Redist服务器会发生单点故障,井且一台服务器需要处理所有的请求负載,压力较大

2、从容量上,单个 Redis服务器内存容量有限就算一台 Redis服务器内存容量为266,也不能将所有内存用作 Redis?存储内存一般来说,单台 Redist最大使用内存不应该超过206

电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。
对于这种场景,我们可以使如下这种架构主从复制,息指将一台Reds3服的数据,复制他的Reds眼努,前者称为主节点 master/leade,后者称为从节点( slave/ follower);数据的复制是单向的,只能由主节点到从节点。 Master以写为主,Save以读为主。

认情况下,每台 Redish服务器都是主节点:且ー个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点
主从复制的作用主要包括:

  1. 数据元余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现可题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写 Redisa效据时应用连接主节点,读Reds数据时应用连接从节点),分担服务器负载;尤具是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis服务器的并发量
  4. 高可用(集群)基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Reds高可用的基础

环境配置

只配置从库,不配置主库!

查看当前库的信息

127.0.0.16379> info replication
# Replication
role:master   // 角色
connected_slaves:0  // 连接的丛机 
master_failover_state:no-failover
master_replid:c52bb664e09e9e76e9c995fdd15215578e38250f
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

image-20210424193553158

复制3个Redis.conf

然后修改配置文件:

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

配置成功

启动3个redis

image-20210427152706030

一主二从

默认情况Redis每一个服务都是主节点

一般只配置从机

一主 (6310)二从 (6320,6330)

查看Redis当前库信息

6310的信息

image-20210427153212906

6320的信息

image-20210427153233963

6330的信息

image-20210427153312486

配置

命令slaveof 127.0.0.1 6310

63206310 是老大

6320的信息

image-20210427153811589

同样给 6330 配置一下

6310的信息

127.0.0.1:6310> info replication
# Replication
role:master     // 主机标识
connected_slaves:1   // 他有一个从机
slave0:ip=127.0.0.1  // 从机地址
,port=6320  // 从机端口
,state=online // 从机状态 
,offset=252,lag=1
master_failover_state:no-failover
master_replid:1c382c2d63dcacaabab6ce01942c24c1b1d3fbb1
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:252
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:252

2个都配置完成

image-20210427154330727

细节

主机可以写,从机不可以写只能读!主机中的所有数据都会被从机保存

主机可以读写 :

image-20210427154957290

从机只能读不能写不然会报错:

image-20210427155036738

测试

1、主机宕机了 从机状态

127.0.0.1:6310> SHUTDOWN
not connected> exit
[root@King bin]# redis-cli -p 6310
Could not connect to Redis at 127.0.0.1:6310: Connection refused
not connected> ping
Could not connect to Redis at 127.0.0.1:6310: Connection refused

主机关闭

image-20210427160524806

image-20210427160413157

主机宕机不会影响从机的读取操作

2、主机从新工作

[root@King bin]# redis-server KingConfig/redis-10.conf
[root@King bin]# redis-cli -p 6310
127.0.0.1:6310> ping
PONG
127.0.0.1:6310> set k2 v2
OK
127.0.0.1:6310> get k2
"v2"

从机状态

image-20210427160810556

也可以查询值

127.0.0.1:6320> get k2
"v2"

测试从机断开 :如果从机是使用命令连接的主机 那么从机断开之后 重新连接 他就会变为自己的主机,这样是取不到主机更新数据,但是可以取到以前的数据,我们重新让它成为从机 那么他还是可以读取到数据

复制原理

Slave启动成功连接到 master 后会发送一个sync命令

Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后, master将传送整个数据文件到 slave,井完成一次完全同步

全量复制:而 slave服务在接收到数据库文件数据后,将其存盘井加载到内存中

增量复制:Master继续将新的所有收集到的修改命令依次传给 slave,完成同步

但是只要是重新连接 master,一次完全同步(全量复制)将被自动执行

主机宕机

如果主机宕机了,从机想要变成主机 我们可以通过命令 slaveof no noe 让自己变成主机!其他节点就可以手动连接,这个时候主机连接了 还想要 这个主机当老大 那么我们需要手动配置

哨兵模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式

哨兵模式概述

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

1

Redis哨兵

这里的哨兵有两个作用

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

Redis配置哨兵模式

配置3个哨兵和1主2从的Redis服务器来演示这个过程。

服务类型是否是主服务器IP地址端口
Redis192.168.11.1286379
Redis192.168.11.1296379
Redis192.168.11.1306379
Sentinel-192.168.11.12826379
Sentinel-192.168.11.12926379
Sentinel-192.168.11.13026379

2

多哨兵监控Redis

首先配置Redis的主从服务器,修改redis.conf文件如下

# 使得Redis服务器可以跨网络访问
bind 0.0.0.0
# 设置密码
requirepass "123456"
# 指定主服务器,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
slaveof 192.168.11.128 6379
# 主服务器密码,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
masterauth 123456

上述内容主要是配置Redis服务器,从服务器比主服务器多一个slaveof的配置和密码。

配置3个哨兵,每个哨兵的配置都是一样的。在Redis安装目录下有一个sentinel.conf文件,copy一份进行修改

# 禁止保护模式
protected-mode no
# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,192.168.11.128代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
sentinel monitor mymaster 192.168.11.128 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456

上述关闭了保护模式,便于测试。

有了上述的修改,我们可以进入Redis的安装目录的src目录,通过下面的命令启动服务器和哨兵

# 启动Redis服务器进程
./redis-server ../redis.conf
# 启动哨兵进程
./redis-sentinel ../sentinel.conf

注意启动的顺序。首先是主机(192.168.11.128)的Redis服务进程,然后启动从机的服务进程,最后启动3个哨兵的服务进程。

哨兵模式的其他配置项

配置项参数类型作用
port整数启动哨兵进程端口
dir文件夹目录哨兵进程服务临时文件夹,默认为/tmp,要保证有可写入的权限
sentinel down-after-milliseconds<服务名称><毫秒数(整数)>指定哨兵在监控Redis服务时,当Redis服务在一个默认毫秒数内都无法回答时,单个哨兵认为的主观下线时间,默认为30000(30秒)
sentinel parallel-syncs<服务名称><服务器数(整数)>指定可以有多少个Redis服务同步新的主机,一般而言,这个数字越小同步时间越长,而越大,则对网络资源要求越高
sentinel failover-timeout<服务名称><毫秒数(整数)>指定故障切换允许的毫秒数,超过这个时间,就认为故障切换失败,默认为3分钟
sentinel notification-script<服务名称><脚本路径>指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,比较常用

sentinel down-after-milliseconds配置项只是一个哨兵在超过规定时间依旧没有得到响应后,会自己认为主机不可用。对于其他哨兵而言,并不是这样认为。哨兵会记录这个消息,当拥有认为主观下线的哨兵达到sentinel monitor所配置的数量时,就会发起一次投票,进行failover,此时哨兵会重写Redis的哨兵配置文件,以适应新场景的需要。

哨兵配置

配置哨兵配置文件 sentinel.conf

image-20210427221721022

sentinel monitor 被监控的名字自定义 ip地址 端口号 1

sentinel monitor myredis 127.0.0.1 6310 1

后面这个1代表 如果主机宕机了 slave 投票看让谁接替成为主机,票数最多的,就会成为主机

redis-sentinel 服务来启动 哨兵

image-20210427222948307

这个时候如果主机宕机了

image-20210427223236066

哨兵就会去检测 主机是否还有呼吸 如果检查没有了 那么他就是从从机里面选一个来作为主机

选举 6320 来做为主机

image-20210427223441603

如果 6310 回来 那么 他就会作为 现任老大的从机

image-20210427223921295

6320 主机

image-20210427224011068

哨兵集群配置

# Example sentinel.conf

# port <sentinel-port> 哨兵的默认端口 默认是 26379 
port 8001

# 守护进程模式
daemonize yes

# 指明日志文件名
logfile "./sentinel1.log"

# 工作路径,sentinel一般指定/tmp比较简单
dir ./

# 哨兵监控这个master,在至少quorum个哨兵实例都认为master down后把master标记为odown
# (objective down客观down;相对应的存在sdown,subjective down,主观down)状态。
# slaves是自动发现,所以你没必要明确指定slaves。
sentinel monitor MyMaster 127.0.0.1 7001 1

# master或slave多长时间(默认30秒)不能使用后标记为s_down状态。
sentinel down-after-milliseconds MyMaster 1500

# 若sentinel在该配置值内未能完成failover操作(即故障时master/slave自动切换),则认为本次failover失败。
sentinel failover-timeout TestMaster 10000

# 设置master和slaves验证密码
sentinel auth-pass TestMaster testmaster123

sentinel config-epoch TestMaster 15
#除了当前哨兵, 还有哪些在监控这个master的哨兵
sentinel known-sentinel TestMaster 127.0.0.1 8002 0aca3a57038e2907c8a07be2b3c0d15171e44da5
sentinel known-sentinel TestMaster 127.0.0.1 8003 ac1ef015411583d4b9f3d81cee830060b2f29862

Redis缓存穿透和雪崩

缓存穿透 (查不到)

概念

缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。

这里需要注意和缓存击穿的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。

解决方案
布隆过滤器

布隆过滤器是一种数据结构,垃圾网站和正常网站加起来全世界据统计也有几十亿个。网警要过滤这些垃圾网站,总不能到数据库里面一个一个去比较吧,这就可以使用布隆过滤器。假设我们存储一亿个垃圾网站地址。

可以先有一亿个二进制比特,然后网警用八个不同的随机数产生器(F1,F2, …,F8) 产生八个信息指纹(f1, f2, …, f8)。接下来用一个随机数产生器 G 把这八个信息指纹映射到 1 到1亿中的八个自然数 g1, g2, …,g8。最后把这八个位置的二进制全部设置为一。过程如下:

3

有一天网警查到了一个可疑的网站,想判断一下是否是XX网站,首先将可疑网站通过哈希映射到1亿个比特数组上的8个点。如果8个点的其中有一个点不为1,则可以判断该元素一定不存在集合中。

那这个布隆过滤器是如何解决redis中的缓存穿透呢?很简单首先也是对所有可能查询的参数以hash形式存储,当用户想要查询的时候,使用布隆过滤器发现不在集合中,就直接丢弃,不再对持久层查询。

1

这个形式很简单。

缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源;

2

但是这种方法会存在两个问题:

如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键;即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

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

描述:

缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。

解决方案:
  1. 设置热点数据永远不过期
  2. 接口限流与熔断,降级

重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,当接口中的某些 服务 不可用时候,进行熔断,失败快速返回机制。

  1. 布隆过滤器

bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。布隆过滤器的关键就在于hash算法和容器大小

  1. 加互斥锁,互斥锁参考代码如下:

3

说明:

​ 1)缓存中有数据,直接走上述代码13行后就返回结果了

​ 2)缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。

​ 3)当然这是简化处理,理论上如果能根据key值加锁就更好了,就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据,上面代码明显做不到这点。

缓存雪崩

概念

缓存雪崩是指,缓存层出现了错误,不能正常工作了。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

4

解决方案

(1)redis高可用

这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。

(2)限流降级

这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

(3)数据预热

数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

实战项目 (很小的,就简单测试一下)

https://gitee.com/PoseidonKing/projects

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值