Redis入门

Nosql

Nosql = Not Only SQL

泛指非关系型数据库的,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0是滴啊,尤其是超大规模的高并发的社区,暴露出很多难以克服的问题,Nosql在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是当下必须要掌握的一门技术!很多数据类型用户的个人信息,社交网络,地理位置,这些数据类型的存储不需要一个固定的格式。<!--more-->

Nosql

  • 不仅仅是数据

  • 没有固定的查询语言

  • 键值对存储,列存储,文档存储,图形数据库(社交关系)

  • 最终一致性

  • CAP定理和BASE

  • 高性能,高可用,高可拓

四大分类

KV键值对

  • 新浪:Redis

  • 美团:Redis + Tair

  • BA : Redis + memecache

文档型数据库

  • MongoDB

    • MongoDB是一个基于分布式文件存储的数据库,c++编写,主要用来处理大量文档

    • MongoDB是一个介于关系型数据库和非关系型数据中中间的产物,MongoDB是非关系型数据库中功能丰富,最像关系型数据库

  • ConthDB

列存储数据库

  • Hbase

  • 分布式文件系统

图关系数据库

  • 不是存放图形,存放的是关系,比如:朋友圈社交网络,广告推荐

  • Neo4j,InfoGrid

Redis入门

概述

Redis能干吗

  1. 内存存储,持久化,内存是断电即失,所以说持久化很重要(rdb,aof)

  2. 效率高,可以用于高速缓存

  3. 发布订阅系统

  4. 地图信息分析

  5. 计时器,计数器(微信,微博浏览量)

  6. ....

Linux安装

  1. 下载安装包 https://redis.io/

  2. 解压Redis安装包到opt文件夹下

 tar -zxvf redis-6.2.5.tar.gz

 

图1 解压redis到opt目录下

 

  1. 基本环境的安装

 yum install gcc-c++
 
 gcc -v // 检查版本确定安装成功

 

图2 检验yum install gcc-c++成功

 

 make
 
 make install

图3 检验make成功

 

 

  1. redis的默认安装路径是/usr/local/bin

图4 redis默认安装路径

 

 

将redis配置文件复制到当前目录下(这里在当前目录下创建一个目录用于存储配置文件)

图5 复制redis.conf所在位置

 

 

修改配置文件,将daemonize no修改为daemonize yes

图6 设置daemonize yes

 

 

  1. 开启redis服务

图7 redis服务开启

 

 

redis-benchmark

redis-benchmark是一个压力测试工具

官方自带的性能测试工具

图8 redis-benchmark命令参数

 

 

  1. 开启redis服务

  2. 进入redis 的目录下

redis-benchmark命令是在 redis 的目录下执行的,而不是 redis 客户端的内部指令。

 cd /usr/local/bin
  1. 执行命令

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

图9 redis-benchmark压力测试结果

 

 

 Summary:
  throughput summary: 76045.62 requests per second
  latency summary (msec):
          avg       min       p50       p95       p99       max
         0.736     0.264     0.711     1.055     1.423     3.087
 ====== INCR ======                                                  
   100000 requests completed in 1.28 seconds # 对于100000个请求进行写入测试
   100 parallel clients # 100个客户端
   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.287 milliseconds (cumulative count 1)
 50.000% <= 0.687 milliseconds (cumulative count 51545)
 75.000% <= 0.783 milliseconds (cumulative count 76210)
 87.500% <= 0.871 milliseconds (cumulative count 87931)
 93.750% <= 0.959 milliseconds (cumulative count 93881)
 96.875% <= 1.079 milliseconds (cumulative count 96882)
 98.438% <= 1.223 milliseconds (cumulative count 98475)
 99.219% <= 1.343 milliseconds (cumulative count 99252)
 99.609% <= 1.463 milliseconds (cumulative count 99616)
 99.805% <= 1.703 milliseconds (cumulative count 99805)
 99.902% <= 1.975 milliseconds (cumulative count 99903)
 99.951% <= 2.207 milliseconds (cumulative count 99952)
 99.976% <= 2.383 milliseconds (cumulative count 99976)
 99.988% <= 2.519 milliseconds (cumulative count 99988)
 99.994% <= 2.591 milliseconds (cumulative count 99995)
 99.997% <= 2.615 milliseconds (cumulative count 99997)
 99.998% <= 2.639 milliseconds (cumulative count 99999)
 99.999% <= 2.647 milliseconds (cumulative count 100000)
 100.000% <= 2.647 milliseconds (cumulative count 100000)
 
 Cumulative distribution of latencies:
 0.000% <= 0.103 milliseconds (cumulative count 0)
 0.002% <= 0.303 milliseconds (cumulative count 2)
 0.074% <= 0.407 milliseconds (cumulative count 74)
 5.577% <= 0.503 milliseconds (cumulative count 5577)
 26.117% <= 0.607 milliseconds (cumulative count 26117)
 56.678% <= 0.703 milliseconds (cumulative count 56678)
 80.060% <= 0.807 milliseconds (cumulative count 80060)
 90.774% <= 0.903 milliseconds (cumulative count 90774)
 95.379% <= 1.007 milliseconds (cumulative count 95379)
 97.271% <= 1.103 milliseconds (cumulative count 97271)
 98.351% <= 1.207 milliseconds (cumulative count 98351)
 99.009% <= 1.303 milliseconds (cumulative count 99009)
 99.493% <= 1.407 milliseconds (cumulative count 99493)
 99.672% <= 1.503 milliseconds (cumulative count 99672)
 99.758% <= 1.607 milliseconds (cumulative count 99758)
 99.805% <= 1.703 milliseconds (cumulative count 99805)
 99.846% <= 1.807 milliseconds (cumulative count 99846)
 99.888% <= 1.903 milliseconds (cumulative count 99888)
 99.909% <= 2.007 milliseconds (cumulative count 99909)
 99.933% <= 2.103 milliseconds (cumulative count 99933)
 100.000% <= 3.103 milliseconds (cumulative count 100000)

基础知识

数据库

redis有16个数据库,默认使用第0个,可以使用select进行切换

图10 redis.conf中显示redis数据库数量

 

 

 127.0.0.1:6379> select 2 # 切换数据库
 OK
 127.0.0.1:6379> dbsize #查看数据库大小
 (integer) 5
 127.0.0.1:6379> flushdb # 清除当前数据库
 127.0.0.1:6379> flushall # 清除所有数据库
 127.0.0.1:6379> clear # 清屏

官网可以查看命令 https://redis.io/commands

Redis-Key

 127.0.0.1:6379> keys * # 查看所有的键值
 1) "counter:__rand_int__"
 2) "myhash"
 3) "mylist"
 4) "name"
 5) "key:__rand_int__"
 127.0.0.1:6379> set name ccy # 存储键值对
 OK
 127.0.0.1:6379> get name # 获取键值对
 "ccy"
 127.0.0.1:6379> type name # 获取数据类型
 string
 127.0.0.1:6379> EXISTS name # 查看键值对是否存在
 (integer) 1
 127.0.0.1:6379> EXISTS myhash name
 (integer) 2
 127.0.0.1:6379> move name 1 # 移动key到某个数据库
 127.0.0.1:6379> EXPIRE name 10 # 设置有效时间
 (integer) 1
 127.0.0.1:6379> ttl name # 显示剩余时间,当返回-2时代表时间到了
 (integer) 2
 127.0.0.1:6379> ttl name
 (integer) -2
 127.0.0.1:6379> get name
 (nil)
 127.0.0.1:6379> DEL name #删除某个键值
 (integer) 0

五大数据类型

String

127.0.0.1:6379> get name
"ccy"
127.0.0.1:6379> APPEND name "ccy" # 追加字符串如果key不存在就相当于set key
(integer) 6
127.0.0.1:6379> STRLEN name # 获取字符串长度
(integer) 6
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views # 增加1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> decr views # 减少1
(integer) 0
127.0.0.1:6379> INCRBY views 10 # 设置增长量
(integer) 10
127.0.0.1:6379> DECRBY views 5 # 设置减少量
(integer) 5
127.0.0.1:6379> get views
"5"
127.0.0.1:6379> GETRANGE name 0 2 # 截取部分字符串
"ccy"
127.0.0.1:6379> GETRANGE name 0 -1 # 获取全部字符串
"ccyccy"
127.0.0.1:6379> SETRANGE name 3 zfs # 替换字符串
(integer) 6
127.0.0.1:6379> get name
"ccyzfs"
127.0.0.1:6379> setex key 60 "hello" # 设置过期时间
OK
127.0.0.1:6379> setnx mykey # 不存在设置(在分布式锁中常常使用)
(integer) 0
127.0.0.1:6379> setnx mykey "redis"
(integer) 1
127.0.0.1:6379> setnx mykey "jiuzhe" # 覆盖失败
(integer) 0
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 批量设置值
OK
127.0.0.1:6379> keys *
1) "myhash"
2) "views"
3) "mylist"
4) "name"
5) "key:__rand_int__"
6) "k1"
7) "k2"
8) "counter:__rand_int__"
9) "age"
10) "k3"
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
set user:1 {name:zhangsan.age:18} # 设置一个user:1 对象值维json字符来保存对象 user:{id}:{filed}
127.0.0.1:6379> mset user:1:name ccy user:1:age 21
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "ccy"
2) "21"
127.0.0.1:6379> getset key "value" #先get后set
(nil)
127.0.0.1:6379> get key
"value"
127.0.0.1:6379> getset key "v1"
"value"
127.0.0.1:6379> get key
"v1"

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

  • 计数器

  • 统计多单位数量

  • 粉丝数

  • 对象缓存存储

List

所有List命令都是以 ‘l’ 开头的

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 2 # 获取列表的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one
127.0.0.1:6379> RPUSH list zero # 将一个值或是多个值从列表的尾部插入
(integer) 7
127.0.0.1:6379> LRANGE list 0 -1
1) "six"
2) "five"
3) "four"
4) "three"
5) "two"
6) "one"
7) "zero"
127.0.0.1:6379> LPOP list # 将一个值或多个值从头部移除
"six"
127.0.0.1:6379> RPOP list # 将一个值或多个值从尾部移除
"zero"
127.0.0.1:6379> LINDEX list 0 # 通过下标获得list中的某一个值
"five"
127.0.0.1:6379> LLEN list # 获取list长度
(integer) 5
#下面的实例list头部添加了"five"元素
127.0.0.1:6379> LRANGE list 0 -1
1) "five"
2) "five"
3) "four"
4) "three"
5) "two"
6) "one"
127.0.0.1:6379> LREM list 1 one # 移除list集合中指定个数的value
(integer) 1
127.0.0.1:6379> LREM list 2 five
(integer) 2
#
127.0.0.1:6379> LTRIM list 0 2 # 通过下标截取移除指定的长度
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "five"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> RPOPLPUSH list otherlist # 从尾部弹出元素移动到另一个list
"one"
#
127.0.0.1:6379> LRANGE otherlist 0 -1
1) "one
127.0.0.1:6379> LSET otherlist 0 two # 如果存在则会更新当前下标的值,如果不存在则会报错
OK
127.0.0.1:6379> LRANGE otherlist 0 -1
1) "two"
#
127.0.0.1:6379> LRANGE otherlist 0 -1
1) "two"
2) "one"
127.0.0.1:6379> LINSERT otherlist before one 1.5 # 插入元素
(integer) 3
127.0.0.1:6379> LRANGE otherlist 0 -1
1) "two"
2) "1.5"
3) "one
  • 如果key不存在创建新的链表

  • 如果key存在,新增内容

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

  • 在两边插入或者改动值效率最高,中间元素效率稍微低一点

应用场景:消息队列(Lpush Rpop),栈(Lpush Lpop)

Set

set命令基本是以's'开头

127.0.0.1:6379> SADD myset one # 添加set元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset # 查看所有set元素
1) "two"
2) "four"
3) "three"
4) "one"
127.0.0.1:6379> SISMEMBER myset one # 查看set中存在元素
(integer) 1
127.0.0.1:6379> SISMEMBER myset five
(integer) 0
127.0.0.1:6379> SCARD myset # 统计set中元素个数
(integer) 4
127.0.0.1:6379> SREM myset one three # 移除set中元素
(integer) 2
127.0.0.1:6379> SRANDMEMBER myset # 随机抽选任意个数元素
"four"
127.0.0.1:6379> SRANDMEMBER myset
"two
127.0.0.1:6379> SRANDMEMBER myset 2
1) "two"
2) "four"
127.0.0.1:6379> SPOP myset 1 # 随机移除元素
1) "four"
127.0.0.1:6379> SMOVE myset myset1 five # 移动指定元素
(integer) 1
# 数字集合类
- 差集 SDIFF
- 并集 SUNION
- 交集 SINTER
127.0.0.1:6379> SMEMBERS myset
1) "four"
2) "three"
3) "one"
4) "two"
127.0.0.1:6379> SMEMBERS myset1
1) "five"
2) "two"
3) "one"
127.0.0.1:6379> SDIFF myset myset1
1) "three"
2) "four"
127.0.0.1:6379> SUNION myset myset1
1) "one"
2) "five"
3) "three"
4) "two"
5) "four"
127.0.0.1:6379> SINTER myset myset1
1) "two"
2) "one"

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

Hash

Map集合,key-map

127.0.0.1:6379[1]> HSET myhash field ccy # 存储字段-value
(integer) 1
127.0.0.1:6379[1]> HGET myhash field # 获得对应字段的value
"ccy"
127.0.0.1:6379[1]> HMSET myhash field1 hello field2 world # 批量存储
OK
127.0.0.1:6379[1]> HMGET myhash field1 field2 # 批量获取
1) "hello"
2) "world"
127.0.0.1:6379[1]> HGETALL myhash # 获得hash表中全部的数据包括字段和value
1) "field"
2) "ccy"
3) "field1"
4) "hello"
5) "field2"
6) "world
127.0.0.1:6379[1]> HDEL myhash field # 删除字段同时也删除了对应的value
(integer) 1
127.0.0.1:6379[1]> HGETALL myhash
1) "field1"
2) "hello"
3) "field2"
4) "world
127.0.0.1:6379[1]> HLEN myhash # 获取hash表的字段数量
(integer) 2
127.0.0.1:6379[1]> HEXISTS myhash field1 # 判断hash表中在指定字段
(integer) 1
127.0.0.1:6379[1]> HEXISTS myhash field3
(integer) 0
127.0.0.1:6379[1]> HKEYS myhash # 获取hash表中的字段
1) "field1"
2) "field2"
127.0.0.1:6379[1]> HVALS myhash # 获取hash表中的值
1) "hello"
2) "world"
127.0.0.1:6379[1]> HSET myhash field3 6
(integer) 1
127.0.0.1:6379[1]> HINCRBY myhash field3 1 # 设置增量可以为负数
(integer) 7

hash存储变更的数据,尤其是用户信息之类的,经常变动的数据,hash适用于对象的存储

Zset

有序集合,在set的基础上增加了一个值

127.0.0.1:6379[1]> ZADD myset 1 one # 添加一个score的set元素
(integer) 1
127.0.0.1:6379[1]> ZADD myset 2 two
(integer) 1
127.0.0.1:6379[1]> ZADD myset 4 four 3 three
(integer) 2
127.0.0.1:6379[1]> ZRANGE myset 0 -1 # 按照socre升序排列
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379[1]> ZRANGEBYSCORE myset -inf +inf # 按照score升序排序 inf代表无穷
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379[1]> ZREVRANGE myset 0 -1 # 按照socre降序排列
1) "four"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379[1]> ZCARD myset # 获取元素数量
(integer) 4
127.0.0.1:6379[1]> ZCOUNT myset 1 4 # 获得指定区间的数量
(integer) 4

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

三种特殊数据类型

Geospatial

数据类型是zset。地理位置,朋友的定位,附近的人,打车距离计算

图11 Geospatial方法

 

 

GEOADD 添加经度纬度

127.0.0.1:6379[2]> GEOADD china:city 112.551 37.893 beijing
(integer) 1
127.0.0.1:6379[2]> GEOADD china:city 121.445 31.213 shanghai
(integer) 1
127.0.0.1:6379[2]> GEOADD china:city 108.969 34.285 xian
(integer) 1

GEODIST 返回由排序集表示的地理空间索引中两个成员之间的距离

127.0.0.1:6379[2]> GEODIST china:city beijing shanghai
"1101632.0359"
127.0.0.1:6379[2]> GEODIST china:city beijing shanghai km
"1101.6320"

GEOHASH 返回一份位置或者多个位置元素用11位的hash表示

127.0.0.1:6379[2]> GEOHASH china:city beijing
1) "ww8p94jnsr0"

GEOPOS 返回由key处的排序集表示的地理空间索引的所有指定成员的位置(经度、纬度)

127.0.0.1:6379[2]> GEOPOS china:city beijing xian
1) 1) "112.55100220441818237"
2) "37.89300027354286016"
2) 1) "108.96899789571762085"
2) "34.28499959898385896

GEORADIUS 以给定的经纬度为中心,找出某半径内的元素

127.0.0.1:6379[2]> GEORADIUS china:city 110 30 500 km
1) "xian"
127.0.0.1:6379[2]> GEORADIUS china:city 110 30 500 km withcoord
1) 1) "xian"
2) 1) "108.96899789571762085"
2) "34.28499959898385896"
127.0.0.1:6379[2]> GEORADIUS china:city 110 30 500 km withdist
1) 1) "xian"
2) "486.3850"

GEORADIUSBYMEMBER 指定成员的位置用作查询的中心

127.0.0.1:6379[2]> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian

GEOSEARCH 支持在矩形区域内搜索代替GEORADIUS,功能比GEORADIUS更强大

127.0.0.1:6379[2]> GEOSEARCH china:city FROMLONLAT 110 37 BYBOX 1000 1000 km ASC
1) "beijing"
2) "xian"
127.0.0.1:6379[2]> GEOSEARCH china:city FROMLONLAT 110 37 BYRADIUS 5000 km ASC
1) "beijing"
2) "xian"
3) "shanghai"

此命令类似于GEOSEARCH,但将结果存储在目标键中。

127.0.0.1:6379[2]> GEOSEARCHSTORE key china:city FROMLONLAT 110 37 BYBOX 1000 1000 km ASC
(integer) 2

Hyperloglog

基数不重复的元素

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

B1,3,5,7,8} 基数 5

Hyperloglog 基数统计算法 优点:占用的内存固定且小;缺点:有出错率

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

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

127.0.0.1:6379[2]> PFADD hyperloglog a b c d e f g h i j # 添加元素
(integer) 1
127.0.0.1:6379[2]> PFCOUNT hyperloglog # 计数
(integer) 10
127.0.0.1:6379[2]> PFADD hyperloglog1 i j z x c v b n m
(integer) 1
127.0.0.1:6379[2]> PFCOUNT hyperloglog1
(integer) 9
127.0.0.1:6379[2]> PFMERGE hyperloglog3 hyperloglog hyperloglog1 # 合并基数
OK
127.0.0.1:6379[2]> PFCOUNT hyperloglog3
(integer) 15

Bitmap

位存储 位图 Bitmap和Hyperloglog都是一种数据结构

统计用户信息,活跃,不活跃,登录,未登录,打卡

业务场景:打卡

################################# 按位存储,0代表未打卡,1代表打卡,若想得到打卡天数只需要记录1的个数即可
127.0.0.1:6379[2]> SETBIT sign 0 1 # 按位存储
(integer) 0
127.0.0.1:6379[2]> SETBIT sign 1 0
(integer) 0
127.0.0.1:6379[2]> SETBIT sign 2 0
(integer) 0
127.0.0.1:6379[2]> SETBIT sign 3 1
(integer) 0
127.0.0.1:6379[2]> SETBIT sign 4 1
(integer) 0
127.0.0.1:6379[2]> SETBIT sign 5 0
(integer) 0
127.0.0.1:6379[2]> SETBIT sign 6 0
(integer) 0
127.0.0.1:6379[2]> GETBIT sign 2 # 查询周三有没有打卡
(integer) 0
127.0.0.1:6379[2]> GETBIT sign 3 # 查询周四有没有打卡
(integer) 1
127.0.0.1:6379[2]> BITCOUNT sign # 统计打卡情况
(integer) 3

Redis事务

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

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

  • 开启事务(MULTI)

  • 命令入队

  • 执行事务(EXEC)

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

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

127.0.0.1:6379[3]> MULTI # 开启事务
OK
127.0.0.1:6379[3](TX)> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379[3](TX)> set k2 v2
QUEUED
127.0.0.1:6379[3](TX)> set k3 v3
QUEUED
127.0.0.1:6379[3](TX)> get k1
QUEUED
127.0.0.1:6379[3](TX)> get k2
QUEUED
127.0.0.1:6379[3](TX)> get k3
QUEUED
127.0.0.1:6379[3](TX)> set k4 v4
QUEUED
127.0.0.1:6379[3](TX)> EXEC # 执行事务
1) OK
2) OK
3) OK
4) "v1"
5) "v2"
6) "v3"
7) OK
#####################################
127.0.0.1:6379[3]> MULTI
OK
127.0.0.1:6379[3](TX)> set k5 v5
QUEUED
127.0.0.1:6379[3](TX)> DISCARD # 取消事务
OK
127.0.0.1:6379[3]> get k5 # 事务队列中的命令都不会被执行
(nil)
#####################################
编译型异常(命令有问题),事务中所有的命令都不会被执行
127.0.0.1:6379[3]> MULTI # 开启事务
OK
127.0.0.1:6379[3](TX)> set k1 v1
QUEUED
127.0.0.1:6379[3](TX)> set k2 v2
QUEUED
127.0.0.1:6379[3](TX)> set k3 v3
QUEUED
127.0.0.1:6379[3](TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command # 编译型错误
127.0.0.1:6379[3](TX)> set k4 v4
QUEUED
127.0.0.1:6379[3](TX)> EXEC # 执行事务
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379[3]> get k4 # 验证
(nil)
#####################################
运行时异常(出现类似1/0的错误),如果事务队列中存在语法性,那么执行命令的时候,其他命令可以正常执行
127.0.0.1:6379[3]> set k1 v1
OK
127.0.0.1:6379[3]> type k1
string
127.0.0.1:6379[3]> MULTI
OK
127.0.0.1:6379[3](TX)> INCR k1
QUEUED
127.0.0.1:6379[3](TX)> set k2 v2
QUEUED
127.0.0.1:6379[3](TX)> EXEC
1) (error) ERR value is not an integer or out of range
2) OK
127.0.0.1:6379[3]> get k2
"v2"

Redis监视测试

127.0.0.1:6379[3]> set money 100
OK
127.0.0.1:6379[3]> set out 0
OK
127.0.0.1:6379[3]> watch money // 监控键值'money'
OK
127.0.0.1:6379[3]> MULTI // 开启事务
OK
127.0.0.1:6379[3](TX)> DECRBY money 20
QUEUED
127.0.0.1:6379[3](TX)> INCRBY out 20
QUEUED
127.0.0.1:6379[3](TX)> EXEC
1) (integer) 80
2) (integer) 20

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

# 第一个线程执行事务
127.0.0.1:6379[3]> WATCH money
OK
127.0.0.1:6379[3]> MULTI
OK
127.0.0.1:6379[3](TX)> DECRBY money 10
QUEUED
127.0.0.1:6379[3](TX)> INCRBY out 10
QUEUED
# 第二个线程执行事务
127.0.0.1:6379[3]> MULTI
OK
127.0.0.1:6379[3](TX)> set money 1000 // 修改此时watch的值
QUEUED
127.0.0.1:6379[3](TX)> EXEC
1) OK
# 第一个线程继续执行事务
127.0.0.1:6379[3](TX)> EXEC // 执行事务失败
(nil)
127.0.0.1:6379[3]> UNWATCH // 取消监视
OK
127.0.0.1:6379[3]> WATCH money // 重新监视
OK
127.0.0.1:6379[3]> MULTI
OK
127.0.0.1:6379[3](TX)> DECRBY money 10
QUEUED
127.0.0.1:6379[3](TX)> INCRBY out 10
QUEUED
127.0.0.1:6379[3](TX)> EXEC
1) (integer) 990
2) (integer) 30

Jedis

Jedis是Redis官方推荐的java连接工具

  1. 导入测试

<dependencies>
<!-- 导入jedis的maven依赖-->
<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.62</version>
</dependency>
</dependencies>
  1. 开启本地redis

图12 开启本地redis服务

 

 

public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println(jedis.ping()); // 测试结果为PONG表示连接成功
}
}

SpringBoot整合

  1. 导入maven依赖

  2. 配置文件

spring.redis.host=127.0.0.1
spring.redis.port=6379
  1. 启动redis

  2. 执行测试用例

@Autowired
private RedisTemplate redisTemplate;

@Test
void contextLoads() {
redisTemplate.opsForValue().set("k1", "value1");
redisTemplate.opsForValue().set("k2", "value2");
System.out.println(redisTemplate.opsForValue().get("k1"));
System.out.println(redisTemplate.opsForValue().get("k2"));
}

序列化,不执行序列化redis键值对在redis不是以key表示

  1. 创建序列化对象

@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements Serializable {
private String name;
private int age;
}
  1. 覆盖RedisAutoConfiguration

@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate1(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.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String序列化
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;
}
}
  1. 测试用例

@Autowired
@Qualifier("redisTemplate1")
private RedisTemplate redisTemplate;

@Test
public void test() throws JsonProcessingException {
// 真实的开发一般使用Json传递对象
User user = new User("ccy", 3);
// String str = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user", user);
System.out.println(redisTemplate.opsForValue().get("user"));
}

Redis.conf详解

网络

bind 127.0.0.1 -::1 # 绑定ip

protected-mode yes # 保护模式

port 6379 # 端口号

通用

daemonize yes # 以守护进程的方式进行,默认是no,我们需要手动开启

pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要执行一个pid

loglevel notice # 日志级别

logfile "" # 日志的文件位置名

databases 16 # 默认的数据库数量

always-show-logo no # 是否总是显示logo

快照

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

redis是内存数据库,如果没有数据库,那么数据断电即失

# 如果在3600s内,如果有1个 key进行了修改,就进行持久化操作
save 3600 1
# 如果在300s内,如果有100个 key进行了修改,就进行持久化操作
save 300 100
# 如果在60s内,如果有10000个 key进行了修改,就进行持久化操作
save 60 10000

stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作

rdbcompression yes # 是否压缩rdb文件

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

dir ./ # rdb 保存的目录

REPLICATION 主从复制

SECURITY 安全

# requirepass foobared redis密码
127.0.0.1:6379> PING
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123" # 设置密码
OK
# 重新启动
127.0.0.1:6379> config get requirepass # 密码限制,指令失效
(error) NOAUTH Authentication required.
127.0.0.1:6379> AUTH 123 # 密码验证
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123"

CLIENTS 客户端限制

maxclients 10000 # 设置能连接上redis的最大客户端数量

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

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

APPEND ONLY MODE AOF

appendonly no # 默认aof不开启

appendfilename "appendonly.aof" # 持久化文件名称

# appendfsync always # 每次修改都会 sync,消耗性能
appendfsync everysec # 每秒执行一次,可能会丢失ls的数据
# appendfsync no # 不执行 sync 这个时候操作系统会自动同步数据速度很快

Redis持久化

redis是内存数据库,断电即失,如果不将内存中的数据库状态保存在磁盘里,那么一旦服务器进程退出,服务器中的数据库状态也会消失,所以redis提供了持久化操作

RDB

图13 RDB

 

 

触发机制

  1. save保存快照,会自动触发rdb规则

127.0.0.1:6379> save
OK
  1. 执行flushall命令,也会触发rdb规则

127.0.0.1:6379> flushall
OK
  1. 退出redis,也会产生rdb规则

127.0.0.1:6379> SHUTDOWN
not connected> exit
  1. 满足redis.confi文件

127.0.0.1:6379> set k1 v1 # 满足本例中设定的60s修改5个数据
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
127.0.0.1:6379> set k4 v4
OK
127.0.0.1:6379> set k5 v5
OK

恢复rdb文件

  1. 只需要将rdb文件放在redis启动目录就可以,redis启动时会自动检查rdb恢复其中的数据

  2. 查看启动目录

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 如果在这个目录下存在rdb文件那么就会在redis启动的时候恢复数据,默认就在这个路径中

优点

  1. 适合大规模的数据恢复

  2. 对数据的完整性要求不高

缺点

  1. 需要一定的时间间隔操作,如果redis意外宕机了,最后一个持久化后的数据就没有了

  2. fork进程的时候,会占用内存空间

演示

  1. 修改redis.conf文件

图14 修改redis.conf文件示例

 

 

  1. 清除rdb文件

[root@iZ2ze25611z7hnx7plpz2nZ bin]# rm -rf dump.rdb
  1. 触发rdb(参考触发机制)

AOF

图15 AOF

 

 

优点

  1. 每一次修改都同步,文件的完整性

  2. 每秒同步一次,可能会丢失一秒的数据

  3. 从不同步,效率最高

缺点

  1. 相对于数据文件来说,aof远远大于rdb,修复速度也比rdb慢

  2. aof的运行效率也比rdb慢,所以redis默认的配置就是rdb持久化

演示

  1. 修改redis.conf文件

图16 修改AOF配置

 

 

  1. 重启redis

127.0.0.1:6379> SHUTDOWN
not connected> exit
[root@iZ2ze25611z7hnx7plpz2nZ bin]# ps -ef|grep redis
root 12070 11929 0 15:31 pts/0 00:00:00 grep --color=auto redis
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis.conf
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p6379
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
  1. 重启redis,bin目录下会出现appendonly.aof

图17 AOF文件以及文件内容

 

 

  1. 修改appendonly.aof检测redis-check-aof作用

# 修改appendonly.aof文件以后redis服务会无法启动
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p6379
Unrecognized option or bad number of args for: '-p6379'
# 使用redis-check-aof修复被修改的文件
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis.conf
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p 6379

扩展

  • rdb持久化方式能够在指定的时间间隔内对数据进行快照存储

  • aof持久化方式记录每次对服务器写的操作,服务器重启时,重新执行命令来恢复原始数据,追加在文件末尾,能对aof文件进行重写,避免体积过大

  • 如果只做缓存不需要使用任何持久化

  • 同时开启两种持计划方式

    • 重启时优先载入aof文件来恢复数据

    • 只会找aof文件,但是推荐只使用rdb用于备份,能快速重启,并且不会有aof可能潜在的bug

  • 性能建议

    • rdb文件只做后备用途,建议只在save上持久化rdb文件,15分钟备份一次,使用save 900 1 规则

    • 使用aof,即便在最恶劣的环境下也不会丢失超过2秒的数据

      • 代价:持续的io

      • rewrite 过程中产生的新数据写到新文件造成的阻塞不可避免,尽量减少rewrite的频率

    • 不使用aof,也可以通过Master-Slave Replication 实现高可用性也可以,能省去一大笔io,减少rewrite带来的系统波动

      • 代价:如果Master-Slave 同时倒掉,会丢失十几分钟的数据,启动脚本也要比较Master-Slave中的rdb文件,选择最新的文件,载入新的,微博就是这种架构

Redis发布订阅

redis发布订阅(pub/sub)是一种消息通道模式:发送者(pub)发送信息,订阅者(sub)接受信息。redis客户端可以订阅任意数量的频道

图18 Redis发布订阅

 

 

原理

图19 Redis发布订阅原理

 

 

方法

图20 订阅发布方法

 

 

应用场景

  1. 实时消息系统

  2. 事实聊天(频道当作聊天室,将信息回显给所有人即可)

  3. 订阅,关注系统都是可以的

演示

  1. 订阅端

Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ccy"
3) (integer) 1
  1. 发送端

127.0.0.1:6379> PUBLISH ccy "hello,ccy"
(integer) 1
# 此时订阅端另一个redis进程
127.0.0.1:6379> SUBSCRIBE ccy
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ccy"
3) (integer) 1
1) "message"
2) "ccy"
3) "hello,ccy"

Redis主从复制

一个Master有多个slave,将一台redis服务器数据,复制到其他的redis服务器,前者称为主节点(masterleader),后者称为从节点(slave、follower),数据是单向的,只能从主节点到从节点,Master以写为主,Slave以读为主

默认情况下,每台redis服务器都是主节点,一个Master可以有多少Slave或没有从节点,一个从节点只能有一个主节点

主从复制作用

  • 数据冗余

    • 实现了数据的热备份,是持久化之外的一种数据冗余方式

  • 故障恢复

    • 主节点出现问题,从节点可以提供服务,实现快速的故障恢复,实际上是一种服务的冗余

  • 负载均衡

    • 在主从复制的基础上,配合读写分离,主节点提供写服务,从节点提供读服务,写redis数据时连接主节点,读redis数据连接从节点,分担服务器负载,尤其在写少读多的场景下通过,多个从节点分担负载,可以提高redis性能

  • 高可用(集群)基石

    • 哨兵、集群,能够实施的基础,主从复制时高可用的基础

不能只使用一台redis的原因

  • 从结构上讲,单个redis服务器会发生单点故障,一台服务器需要处理所有请求,压力大

  • 从容量上讲,单个redis服务器内存容量有限,并且不能完全使用全部的内存,单台redis的最大内存不应该超过20g压力过大

图21 主从复制

 

 

演示

本例中使用三个redis服务器

  1. 配置文件

  • daemonize:yes

  • port:6379/6380/6381

  • pidfile

  • logfile

  • dbfilename

  1. 启动redis服务

# 第一台服务器
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis79.conf
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p 6379
# 第二台服务器
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis80.conf
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p 6380
# 第三台服务器
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis81.conf
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p 6381

启动服务后,每个服务器都是独立存在的都是master节点

127.0.0.1:6379> info replication # 查看配置信息
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:f75fe68da13dbd2aa94c7673ea0891315f5dbae8
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
  1. 一主二从(一般都是从机连接主机)

# 第二台服务器
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
# 第三台服务器
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379
OK

连接以后主机和从机配置信息发生改变

# 第一台服务器(主机)
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=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=140,lag=0
master_failover_state:no-failover
master_replid:11c40313ea11a9e81f6e83faef8e5081a0ebd409
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:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_repl_offset:0
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:11c40313ea11a9e81f6e83faef8e5081a0ebd409
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0

注意

  • 真实的主从配置要在配置文件中配置,在redis-cli中配置的是暂时的,配置在redis.conf文件中replication区块下

  • 配置文件设置好,启动时就不用重新设置

  • 根据读写分离的原则,从机只能读

# 第一台服务器(主机)
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 v1
OK
# 第二台服务器(从机)
127.0.0.1:6380> get k1
"v1"
127.0.0.1:6380> set k2 v2 # 从机无法写
(error) READONLY You can't write against a read only replica.
  • 在没配置哨兵的情况下,当master崩溃了,slave还是slave

  • 在没有设置哨兵的情况下,主机崩溃了,从机仍然能读取

  • 从机崩溃,主机继续写入数据,从机恢复,只要变为从机就会立马从主机中获取值

复制原理

  • slave启动成功连接到master后会发送一个sync同步命令

  • master接到命令,启动后台的存盘进程,同时收集所接收到的用于修改数据集命令,后台执行完毕之后,master将传送整个数据文件到slave,并完成一次同步,成为增量复制

  • 全量复制

    • slave服务在接受到数据库文件数据后,将其存盘并加载到内存中

  • 增量复制

    • master继续将新的所有收集到的修改命令依次传给slave,完成同步

  • 只要重新连接master,一次完全同步(全量复制)将被自动执行,数据一定能在从机中看到

结构

图22 主从复制结构

 

 

链式结构的slave81 点master设置为slave 80 ,实现的功能也是一样,当master 79 崩溃,slave 80也不会变成master

哨兵模式

概述切换技术的方法是,当master服务器宕机后,需要人工切换,费事,更多时候选择优先考虑是哨兵模式,redis2.8 开始正确提供sentinel(哨兵 )

能够监控后台的主机是否故障,根据投票自动将从库专为主库

哨兵模式是一种特殊模式,哨兵是一个独立的进程,作为进程独立运行,原理是哨兵通过发送命令,等待redis服务器响应,从而监控多个redis实例

当主机宕机后手动配置从机变为主机

slaveof no one

此时master恢复后使用slaveof no one 的主机也还会继续当master,要重新作为slave只能重新配置

单哨兵模式

图23 单哨兵模式

 

 

当哨兵模式检测到master宕机,会自动将slave切换成master,通过发布订阅模式通知其他的从服务器,修改配置文件,让他切换主机

多哨兵模式

假设master宕机,sentinel先检测到这个结果,系统并不会马上进行failover(故障切换、失效备援)这个现象称为主观下线,当后面的哨兵也检测到主服务器不可用,sentinel之间会发起一次投票,投票的结果由随机一个sentinel发起,进行failover操作,得到sentinel票数多的slave能成功切换为master,切换成功后,通过发布订阅模式,让各个哨兵把自己监控的服务器实现切换主机,这个过程称为客观下线

图24 多哨兵模式

 

 

演示

  1. 配置sentinel.conf文件

[root@iZ2ze25611z7hnx7plpz2nZ kconfig]# vim sentinel.conf

sentinel monitor myredis 127.0.0.1 6380 1 # 编写配置信息
  1. 运行配置文件

# 此时处于配置文件的目录
[root@iZ2ze25611z7hnx7plpz2nZ kconfig]# cd .. # 退回到上一级因为上一级存在启动文件
[root@iZ2ze25611z7hnx7plpz2nZ bin]# ls
6379.log cloud-id dump6381.rdb jsondiff pcre-config redis-check-rdb
6380.log cloud-init dump.rdb jsonpatch pcregrep redis-cli
6381.log cloud-init-per easy_install jsonpointer pcretest redis-sentinel
appendonly.aof dump6379.rdb easy_install-3.6 jsonschema redis-benchmark redis-server
chardetect dump6380.rdb easy_install-3.8 kconfig redis-check-aof
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-sentinel kconfig/sentinel.conf # 根据配置文件启动哨兵模式
4314:X 23 Aug 2021 10:02:46.060 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
4314:X 23 Aug 2021 10:02:46.060 # Redis version=6.2.5, bits=64, commit=00000000, modified=0, pid=4314, just started
4314:X 23 Aug 2021 10:02:46.060 # Configuration loaded
4314:X 23 Aug 2021 10:02:46.061 * monotonic clock: POSIX clock_gettime
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.2.5 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 4314
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | https://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'

4314:X 23 Aug 2021 10:02:46.061 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
4314:X 23 Aug 2021 10:02:46.072 # Sentinel ID is 2089f9ac541a0710f19390a54331aceffbdf93d5
4314:X 23 Aug 2021 10:02:46.072 # +monitor master myredis 127.0.0.1 6379 quorum 1
4314:X 23 Aug 2021 10:02:46.073 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.165 # +sdown master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.165 # +odown master myredis 127.0.0.1 6379 #quorum 1/1
4314:X 23 Aug 2021 10:04:32.165 # +new-epoch 1
4314:X 23 Aug 2021 10:04:32.165 # +try-failover master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.170 # +vote-for-leader 2089f9ac541a0710f19390a54331aceffbdf93d5 1
4314:X 23 Aug 2021 10:04:32.170 # +elected-leader master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.170 # +failover-state-select-slave master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.271 # +selected-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.271 * +failover-state-send-slaveof-noone slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.329 * +failover-state-wait-promotion slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.811 # +promoted-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.811 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.888 # +failover-end master myredis 127.0.0.1 6379
4314:X 23 Aug 2021 10:04:32.888 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6380
4314:X 23 Aug 2021 10:04:32.888 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
4314:X 23 Aug 2021 10:04:33.183 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6380
  1. 作用演示

# 第一台服务器(主机)关机,此时从机失去主机
127.0.0.1:6379> SHUTDOWN
not connected> exit
# 哨兵模式启动,自动投票选出6380端口为主机
4314:X 23 Aug 2021 10:05:02.949 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
4314:X 23 Aug 2021 10:20:50.173 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
4314:X 23 Aug 2021 10:21:00.097 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6380
# 此时第二台服务器(从机)变为主机,从机为第三台服务器
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=60671,lag=1
master_failover_state:no-failover
master_replid:b5fa57ab460ed278d5b56ac381e48a25c83d77bb
master_replid2:11c40313ea11a9e81f6e83faef8e5081a0ebd409
master_repl_offset:60803
second_repl_offset:9411
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:60803
# 重新开启原先的主机也就是第一台服务器,无法恢复为主机而是作为从机连接在现在的主机上
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-server kconfig/redis79.conf
[root@iZ2ze25611z7hnx7plpz2nZ bin]# redis-cli -p 6379
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:75800
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:b5fa57ab460ed278d5b56ac381e48a25c83d77bb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:75800
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:74826
repl_backlog_histlen:975
# 第一台服务器重新启动后,主机(第二台服务器)多了一个从机
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6381,state=online,offset=182056,lag=0
slave1:ip=127.0.0.1,port=6379,state=online,offset=182056,lag=0
master_failover_state:no-failover
master_replid:b5fa57ab460ed278d5b56ac381e48a25c83d77bb
master_replid2:11c40313ea11a9e81f6e83faef8e5081a0ebd409
master_repl_offset:182188
second_repl_offset:9411
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:182188

优点

  • 基于集群,基于主从复制,所有的主从配置的优点,它全有

  • 主从可以切换,故障可以切换,系统的可用性提高

  • 哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点

  • redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦

  • 哨兵模式需要很多配置,多哨兵,多端口配置复杂,一般由运维来配置

缓存穿透、击穿、雪崩

缓存穿透

用户查询一个数据,redis数据库中没有,也就是缓存没命中,于是向持久层数据库查询,发现也没有,于是查询失败,用户很多的时候,缓存都没有命中,都请求持久层数据库,给持久层数据库造成巨大压力,称为缓存穿透

解决方法

  1. 布隆过滤器

  • 布隆过滤器是一种数据结构,对所有可能的查询参数以hash形式存储,在控制层进行校验,不符合则丢弃,从而避免了对底层存储系统查询压力

图25 布隆过滤器

 

 

  1. 缓存空对象

当持久化层不命中后,将返回的空对象存储起来,同时设置一个过期时间,之后再访问这个数据就从缓存中获取,保护持久层数据源

图26 缓存空对象

 

 

问题

  • 存储空的key也需要空间

  • 对空值设置了过期时间,还会存在缓存层和存储层的数据有一段时间窗口不一致,对于需要保持一致性的业务会有影响

缓存击穿

例子:微博服务器热搜,巨大访问量访问同一个key

一个key非常热点,不停扛着大并发,集中对一个点进行访问,当个key失效的瞬间,持续大并发导致穿破缓存,直接请求数据库

某个key在过期的瞬间,大量的访问会同时访问数据库来查询最新的数据,并且回写缓存,导致数据库瞬间压力过大

解决方法

  • 设置热点数据不过期,缺点:一直缓存也会浪费空间

  • 加互斥锁

    • 分布式锁:使用分布式锁,保证对于每个key同时只有一个线程查询后端服务,其他线程没有获得分布式锁的权限,只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大

缓存雪崩

在某一个时间段,缓存集中过期失效比如redis宕机

产生雪崩的原因之一,设置缓存的存活时间较短,大并发访问时刚好都过期,直接访问了数据库,对数据库而言,会产生周期性压力波峰,暴增时数据库可能会宕机

图27 缓存雪崩

 

 

解决方法

  • 增加集群中服务器数量

    • 异地多活

  • 限流降级

    • 缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,对某个key只允许一个线程查询数据和写缓存,其他线程等待

  • 数据预热

    • 正式部署之前,把可能的数据提前访问一遍,可能大量访问的数据就会加载到缓存中,加载不同的key,设置不同的过期时间,让缓存时间尽量均匀

    •  

__EOF__

本文作者双双
本文链接https://www.cnblogs.com/ccywmbc/p/16609570.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   雙雙  阅读(347)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示