90年代基本的访问量都不太大,单个数据库完全够用,而且更多使用的是静态网页,服务器没有过多的压力。
可能面临的问题
发展过程:优化数据结构和索引–>文件缓存(IO)–>Memcached
MyISAM:表锁 十分影响效率,高并发会有问题
Innodb:行锁
之后使用分库分表来解决写的压力
为什么要用NoSQL?
用户的个人信息,社交网络,地理位置,用户自己产生的数据,用户日志等爆发式增长,这时就需要使用NoSQL数据库。
什么是NoSQL?
Not Only Sql 泛指非关系型数据库
关系型数据库:表格,行和列
用户的个人信息,社交网络,地理位置,用户自己产生的数据,用户日志等,数据存储不需要固定的格式,不需要过多的操作就可以横向扩展。
特点
传统的RDBMS和NoSQL的区别:
RDBMS 关系型数据库
NoSQL 非关系型数据库
不仅仅是数据
没有固定的查询语言
键值对存储,列存储,文档存储,图形数据库
最终一致性
CAP定理和BASE
高性能 高可用 高可扩
KV键值对 Redis
文档型数据库 MongDB 是一个基于分布式文件存储的数据库,C++编写的,主要用来处理大量的文档,MongoDB是一个介于关系型数据库和非关系型数据库的中间产品,是非关系型数据库中功能最丰富的,最像关系型数据库的非关系型数据库
列存储数据库 HBase 分布式文件系统
图形关系数据库 不是存图形,而是存关系的 比如社交网络、广告推荐 Neo4j
是什么?
Remote Dictionary Server (远程字典服务),是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
能干什么?
特性
Linux安装
官网下载压缩包
https://redis.io/download/#redis-downloads
xftp上传到Linux的opt目录下
解压缩
[root@summer opt]# tar -zxvf redis-5.0.14.tar.gz
基本环境安装
[root@summer redis-5.0.14]# yum install gcc-c++
[root@summer redis-5.0.14]# make
[root@summer redis-5.0.14]# make install
redis默认安装路径 /usr/local/bin
将redis配置文件拷贝到 当前目录
[root@summer bin]# mkdir config
[root@summer bin]# ls
chardetect easy_install-3.6 libmcrypt-config redis-cli
cloud-id easy_install-3.8 mcrypt redis-sentinel
cloud-init jsondiff mdecrypt redis-server
cloud-init-per jsonpatch redis-benchmark
config jsonpointer redis-check-aof
easy_install jsonschema redis-check-rdb
[root@summer bin]# cp /opt/redis-5.0.14/redis.conf config
[root@summer bin]# cd config
[root@summer config]# ls
redis.conf
redis默认不是后台启动的,需要修改配置文件
daemonize yes
启动redis服务
# 通过指定的配置文件启动服务
[root@summer bin]# redis-server config/redis.conf
2740:C 25 Jun 2022 15:07:49.548 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2740:C 25 Jun 2022 15:07:49.548 # Redis version=5.0.14, bits=64, commit=00000000, modified=0, pid=2740, just started
2740:C 25 Jun 2022 15:07:49.548 # Configuration loaded
测试
# 使用redis客户端进行连接
[root@summer bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name summer
OK
127.0.0.1:6379> get name
"summer"
127.0.0.1:6379> keys * # 查看所有的key
1) "name"
127.0.0.1:6379>
查看redis的进程
[root@summer ~]# ps -aux|grep redis
root 2741 0.0 0.1 159636 3324 ? Ssl 15:07 0:00 redis-server 127.0.0.1:6379
root 2808 0.0 0.0 24860 1580 pts/0 S+ 15:08 0:00 redis-cli -p 6379
root 2989 0.0 0.0 112812 980 pts/1 S+ 15:11 0:00 grep --color=auto redis
关闭redis服务
127.0.0.1:6379> shutdown # 关闭redis
not connected> exit # 退出
[root@summer bin]#
再次查看进程
[root@summer ~]# ps -aux|grep redis
root 3096 0.0 0.0 112812 976 pts/1 S+ 15:13 0:00 grep --color=auto redis
redis-benchmark 是一个压力测试工具
测试
# 100个并发连接 100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
# 需要保证 redis-server 服务是开启的
[root@summer bin]# redis-benchmark -h localhost -p 6379 -c 100 -n 100000
====== PING_INLINE ======
100000 requests completed in 1.34 seconds # 100000个请求
100 parallel clients # 100个客户端
3 bytes payload # 每次写入三个字节
keep alive: 1 # 只有一台服务器来处理这些请求
94.86% <= 1 milliseconds
99.33% <= 2 milliseconds
100.00% <= 2 milliseconds # 每个请求在3毫秒内处理完成
74515.65 requests per second # 每秒处理多少请求
====== PING_BULK ======
100000 requests completed in 1.35 seconds
100 parallel clients
3 bytes payload
keep alive: 1
94.24% <= 1 milliseconds
99.34% <= 2 milliseconds
100.00% <= 2 milliseconds
74349.44 requests per second
====== SET ======
100000 requests completed in 1.34 seconds
100 parallel clients
3 bytes payload
keep alive: 1
95.24% <= 1 milliseconds
99.38% <= 2 milliseconds
99.95% <= 3 milliseconds
100.00% <= 3 milliseconds
74682.60 requests per second
====== GET ======
100000 requests completed in 1.34 seconds
100 parallel clients
3 bytes payload
keep alive: 1
94.29% <= 1 milliseconds
99.29% <= 2 milliseconds
100.00% <= 2 milliseconds
74626.87 requests per second
redis默认有16个数据库
databases 16 # 默认使用的是第0个数据库
127.0.0.1:6379> select 3 # 切换到第三个数据库
OK
127.0.0.1:6379[3]> dbsize # 查看数据库的大小
(integer) 0
127.0.0.1:6379[3]> set name summer
OK
127.0.0.1:6379[3]> dbsize
(integer) 1
127.0.0.1:6379[3]> keys * # 查看数据库的所有key
1) "name"
127.0.0.1:6379[3]> flushdb # 清空当前数据库 flushall 清空所有数据库
OK
127.0.0.1:6379[3]> keys *
(empty list or set)
为什么Redis使用单线程?
Redis是基于内存操作的,不受限于CPU,受限于内存和网络带宽。既然可以使用单线程,就使用单线程
为什么单线程还这么快?
误区1:高性能的服务器一定是多线程的 不一定!!!
误区2:多线程一定比单线程效率高 不一定!!!
核心:redis是将所有的数据全部放在内存中的,所以使用单线程操作就是最高效的,多线程可能需要CPU上下文切换,是耗时操作,对于内存来说,没有上下文切换效率就是最高的。
127.0.0.1:6379> keys * # 查看所有的key
1) "name"
127.0.0.1:6379> exists name # 查看关键字name是否存在
(integer) 1
127.0.0.1:6379> exists age # 查看关键字key是否存在
(integer) 0
127.0.0.1:6379> move name 1 # 移动关键字name到另一个数据库
127.0.0.1:6379> del name # 删除关键字及其value
127.0.0.1:6379> expire name 10 # 设置key的过期时间 10s后name就不存在了
(integer) 1
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> type name # 查询key对应的value的类型
string
127.0.0.1:6379> type age
string
# apend
# strlen
127.0.0.1:6379> append k1 hello # 字符串追加 key不存在,相当于set
(integer) 7
127.0.0.1:6379> get k1
"v1hello"
127.0.0.1:6379> strlen k1 # 字符串长度
(integer) 7
127.0.0.1:6379> append k1 ,summer
(integer) 14
127.0.0.1:6379> get k1
"v1hello,summer"
127.0.0.1:6379>
# incr 自增一
# decr 自减一
# incrby 自增 步长
# decrby 自减 步长
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views # 自增一
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incr views # 自增一
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views # 自减一
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incrby views 10 # 自增10 设置步长为10
(integer) 11
127.0.0.1:6379> get views
"11"
127.0.0.1:6379> decrby views 5 # 自减10 设置步长为5
(integer) 6
127.0.0.1:6379> get views
"6"
# getrange 截取
# setrange 替换
# setex 存活时间
# setnx key不存在就创建,存在就不创建
127.0.0.1:6379> set key hello,redis
OK
127.0.0.1:6379> getrange key 0 3 # 截取字符串 [0,3]
"hell"
127.0.0.1:6379> getrange key 0 -1 # 截取全部字符串
"hello,redis"
127.0.0.1:6379> set key1 abcdefg
OK
127.0.0.1:6379> get key1
"abcdefg"
127.0.0.1:6379> setrange key1 1 zzzz # 替换字符串
(integer) 7
127.0.0.1:6379> get key1
"azzzzfg"
127.0.0.1:6379> setex key2 30 hello # 设置存活时间 30秒后key2就会消失
127.0.0.1:6379> setnx key3 redis # 如果key3不存在,创建key3
(integer) 1
127.0.0.1:6379> setnx key3 mongoDB # 如果key3存在,创建失败
(integer) 0
127.0.0.1:6379> get key3
"redis"
# mset 一次设置多个key
# mget 一次读取多个key的值
# msetnx 只有当所有的key都不存在时,才能创建成功
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 一次设置多个key
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k3"
3) "k1"
127.0.0.1:6379> mget k1 k2 k3 # 一次读取多个key
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 # k1存在,但是k4不存在,同样创建不成功,只有当都不存在的时候才能创建成功
(integer) 0
# 对象
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 24
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "24"
# getset 先get再set
127.0.0.1:6379> getset db redis # 如果不存在值,就直接set
(nil)
127.0.0.1:6379> getset db redis # 如果存在值,就可以get
"redis"
127.0.0.1:6379> getset db mongdb # 先get原来的值,后set新值
"redis"
127.0.0.1:6379> getset db mongdb
"mongdb"
所有的list命令都是l开头
# lpush 将值插入列表头部 也就是插到左边 类似链表的头插法
# rpush 将值插入列表尾部 也就是插到右边
# lrange
# linsert 将某个元素插入到某个元素的前面或者后面
127.0.0.1:6379> lpush list one # one
(integer) 1
127.0.0.1:6379> lpush list two # two one
(integer) 2
127.0.0.1:6379> lpush list three # three two one
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
127.0.0.1:6379> rpush list zore
(integer) 4 # 头 尾
127.0.0.1:6379> lrange list 0 -1 # three two one zore
1) "three"
2) "two"
3) "one"
4) "zore"
# linsert 将某个元素插入到某个元素的前面或者后面
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "world"
127.0.0.1:6379> linsert list before world nice
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "nice"
3) "world"
127.0.0.1:6379> linsert list after hello a
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "a"
3) "nice"
4) "world"
# lpop 从左边弹出元素
# rpop 从右边弹出元素 three two one zore
# lrem 移除指定个数的value, lrem key count value
# ltrim 通过下标截取指定的长度
127.0.0.1:6379> lpop list
"three"
127.0.0.1:6379> rpop list
"zore"
# lrem 移除指定个数的value, lrem key count value
127.0.0.1:6379> lrem list 1 one
(integer) 1
# ltrim 通过下标截取指定的长度
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "zore"
127.0.0.1:6379> ltrim list 1 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
# 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 value2
(error) ERR index out of range
# lindex 通过下标获取某个索引的值
# llen 获取列表的长度
127.0.0.1:6379> lindex list 0 # 通过下标获取某个索引的值
"two"
127.0.0.1:6379> lindex list 1
"one"
127.0.0.1:6379>
# llen 获取列表的长度
127.0.0.1:6379> llen list
(integer) 2
# rpoplpush 将当前列表的最后一个元素弹出,将其压入另一个列表的头部
# list: four three two one
# list1:c b a
# list: four three two
# list1:one c b a
127.0.0.1:6379> rpoplpush list list1
"one"
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"
127.0.0.1:6379> lrange list1 0 -1
1) "one"
2) "c"
3) "b"
4) "a"
set的值是不能重复的,有顺序的
# sadd 在set集合中添加值
# srem 移除set集合中的元素
# spop 随机删除set集合中的几个元素
# smove 将一个set集合中的元素移动到另一个set集合中
# smembers 查看set集合的元素
# sismembers 判断是否存在某值
# scard 获取set集合中的元素个数
# srandmember 随机获取集合中的几个元素
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> smembers myset
1) "world"
2) "hello"
127.0.0.1:6379> sismember myset hello
(integer) 1
127.0.0.1:6379> sismember myset summer
(integer) 0
127.0.0.1:6379> scard myset
(integer) 2
127.0.0.1:6379> srem myset world
(integer) 1
127.0.0.1:6379> smembers myset
1) "redis"
2) "summer"
3) "world"
4) "hello"
5) "mongdb"
127.0.0.1:6379> srandmember myset
"world"
127.0.0.1:6379> srandmember myset
"summer"
127.0.0.1:6379> srandmember myset 3
1) "mongdb"
2) "redis"
3) "world"
127.0.0.1:6379> spop myset
"mongdb"
127.0.0.1:6379> spop myset
"world"
127.0.0.1:6379> smembers myset
1) "mongdb"
2) "summer"
3) "redis"
4) "world"
5) "hello"
127.0.0.1:6379> sadd set1 set1
(integer) 1
127.0.0.1:6379> smove myset set1 redis
(integer) 1
127.0.0.1:6379> smembers myset
1) "mongdb"
2) "summer"
3) "world"
4) "hello"
127.0.0.1:6379> smembers set1
1) "redis"
2) "set1"
# sdiffer k1 k2 k1与k2的差集
# sinter k1 k2 k1与k2的交集
# sunion k1 k2 k1与k2的并集
127.0.0.1:6379> sadd k1 a b c d
(integer) 4
127.0.0.1:6379> sadd k2 c d e f
(integer) 4
127.0.0.1:6379> sdiff k1 k2 # 差集
1) "a"
2) "b"
127.0.0.1:6379> sinter k1 k2 # 交集
1) "d"
2) "c"
127.0.0.1:6379> sunion k1 k2 # 并集
1) "e"
2) "b"
3) "c"
4) "d"
5) "f"
6) "a"
应用在一些应用程序的 共同关注、粉丝、推荐好友等等
Map 集合 Key-Map Key-< Key,Value>
# hset 存值
# hmset 一次存多个值
# hsetnx 不存在才创建成功
# hdel 删除指定的key和value
# hincrby 自增步长
# hget 取值
# hmget 一次取多个值
# hgetall 取出所有的键值对
# hlen 获取键值对的个数
# hexists 判断是否存在指定的字段
# hkeys 获取所有的key
# hvals 获取所有的value
127.0.0.1:6379> hset myhash field1 hello
(integer) 1
127.0.0.1:6379> hget myhash field1
"hello"
127.0.0.1:6379> hset myhash field2 world
(integer) 1
127.0.0.1:6379> hget myhash field2
"world"
127.0.0.1:6379> hmset myhash field3 hello field4 redis
OK
127.0.0.1:6379> hget myhash field3
"hello"
127.0.0.1:6379> hget myhash field4
"redis"
127.0.0.1:6379> hmget myhash field3 field4
1) "hello"
2) "redis"
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
5) "field3"
6) "hello"
7) "field4"
8) "redis"
127.0.0.1:6379> hdel myhash field3 field4 # 删除指定的键值对
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hlen myhash # 获取键值对的个数
(integer) 2
127.0.0.1:6379> hexists myhash field1 # 判断是否存在指定字段
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0
127.0.0.1:6379> hkeys myhash # 获取所有的key
1) "field1"
2) "field2"
127.0.0.1:6379> hvals myhash # 获取所有的value
1) "hello"
2) "world"
127.0.0.1:6379> hset myhash field3 2
(integer) 1
127.0.0.1:6379> hincrby myhash field3 5 # 自增 5
(integer) 7
127.0.0.1:6379> hincrby nyhash field3 -4 # 自增 -4
(integer) -4
127.0.0.1:6379> hsetnx myhash field4 hello # 不存在创建成功
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world # 存在创建失败
(integer) 0
应用:用户信息保存 hash更适合对象类型的数据存储
127.0.0.1:6379> hset user1 name zhangsan
(integer) 1
127.0.0.1:6379> hset user1 age 15
(integer) 1
# zadd 添加值
# zrem 删除值
# zrange 查看值
# zrangebyscore key min max 升序
# zrevrangebyscore key max min 降序
# zcard 获取元素个数
# zcount 获取某个范围内的元素个数
127.0.0.1:6379> zadd myset 1 hello 2 world 3 summer 4 redis
(integer) 4
127.0.0.1:6379> zrange myset 0 -1
1) "hello"
2) "world"
3) "summer"
4) "redis"
127.0.0.1:6379> zadd salary 2500 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 3000 lisi
(integer) 1
127.0.0.1:6379> zadd salary 3500 wangwu
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf # -∞ +∞ 从小到大排序
1) "zhangsan"
2) "lisi"
3) "wangwu"
127.0.0.1:6379> zrangebyscore salary 1 3000 withscores # 升序排列socre在1~3000的键值对,并附带成绩
1) "zhangsan"
2) "2500"
3) "lisi"
4) "3000"
127.0.0.1:6379> zrevrangebyscore salary 5000 1
1) "wangwu"
2) "3500"
3) "lisi"
4) "3000"
5) "zhangsan"
6) "2500"
127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "wangwu"
2) "3500"
3) "lisi"
4) "3000"
5) "zhangsan"
6) "2500"
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "lisi"
3) "wangwu"
127.0.0.1:6379> zrem salary lisi # 移除lisi
(integer) 1
127.0.0.1:6379> zcard salary # 统计有序集合中元素的数量
(integer) 2
127.0.0.1:6379> zcount salary 2500 3000 # 统计某个范围内的元素数量
(integer) 2
应用:存储班级成绩表,工资表排序,排行榜应用
定位、附近的人、打车距离计算
# geoadd key 经度 纬度 名称
# geopos key 名称 获取指定名称的经度纬度
# geodist key 名称 名称 两个地点的直线距离 可以指定单位 km m 单位 m km mi(英里) ft(英尺)
# georadius key 经度 维度 半径 单位 搜索以某一经纬点为圆心的,固定长度为半径的经纬点
# georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
# georadiusbymember key 名称 半径 单位 搜索以某一个成员城市为中心的,固定长度位半径的经纬点
# geohash key 名称 将二维的经纬度 返回一个11位的hash值
127.0.0.1:6379> geoadd china:city 126.642464 45.756967 haerbin
(integer) 1
127.0.0.1:6379> geoadd china:city 117.129063 36.194968 taian
(integer) 1
127.0.0.1:6379> geoadd china:city 116.405285 39.904989 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 118.767413 32.041544 nanjing
(integer) 1
127.0.0.1:6379> geoadd china:city 113.280637 23.125178 guangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 114.085947 22.547 shenzhen
(integer) 1
127.0.0.1:6379> geoadd china:city 87.617733 43.792818 xinjiang
(integer) 1
127.0.0.1:6379> geoadd china:city 102.712251 25.040609 yunnan
(integer) 1
127.0.0.1:6379> geoadd china:city 111.670801 40.818311 neimenggu
(integer) 1
127.0.0.1:6379> geopos china:city taian
1) 1) "117.12906092405319214"
2) "36.19496775641126618"
127.0.0.1:6379> geopos china:city neimenggu
1) 1) "111.67080312967300415"
2) "40.81831182465131036"
127.0.0.1:6379> geodist china:city taian haerbin km
"1328.1061"
127.0.0.1:6379> geodist china:city shenzhen haerbin km
"2820.3995"
127.0.0.1:6379> georadius china:city 117.12906092405319214 36.19496775641126618 1000 km
1) "taian"
2) "nanjing"
3) "beijing"
4) "neimenggu"
127.0.0.1:6379> georadius china:city 117.12906092405319214 36.19496775641126618 1300 km withdist
1) 1) "neimenggu"
2) "699.7992"
2) 1) "taian"
2) "0.0000"
3) 1) "nanjing"
2) "485.9545"
4) 1) "beijing"
2) "417.4882"
127.0.0.1:6379> georadius china:city 117.12906092405319214 36.19496775641126618 1300 km withcoord
1) 1) "neimenggu"
2) 1) "111.67080312967300415"
2) "40.81831182465131036"
2) 1) "taian"
2) 1) "117.12906092405319214"
2) "36.19496775641126618"
3) 1) "nanjing"
2) 1) "118.76741319894790649"
2) "32.04154324806454923"
4) 1) "beijing"
2) 1) "116.40528291463851929"
2) "39.9049884229125027"
127.0.0.1:6379> georadiusbymember china:city taian 1500 km
1) "neimenggu"
2) "taian"
3) "nanjing"
4) "beijing"
5) "haerbin"
127.0.0.1:6379> geohash china:city taian
1) "ww7mcm4fbz0"
geo的实现原理就是Zset
127.0.0.1:6379> zrange china:city 0 -1
1) "xinjiang"
2) "yunnan"
3) "neimenggu"
4) "shenzhen"
5) "guangzhou"
6) "taian"
7) "nanjing"
8) "beijing"
9) "haerbin"
127.0.0.1:6379> zrem china:city guangzhou
(integer) 1
基数:就是集合中不重复元素的个数 有一定的错误率
应用场景:统计访问网站的用户数 每个人只能统计一次
传统方式是使用set集合,保存用户的id,然后统计集合中元素的个数,这个方式如果保存大量的用户id比较麻烦,目的是为了计数,而不需要保存用户的id
优点:占用的内存是固定的
# pfadd 添加元素
# pfcount 统计元素数量
# pfmerge 合并
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 mykey1 a b c d e f g h i j a b c d e f
(integer) 1
127.0.0.1:6379> pfcount mykey1
(integer) 10
127.0.0.1:6379> pfmerge mykey2 mykey mykey1 # 将mykey和mykey1合并为mykey2
OK
127.0.0.1:6379> pfcount mykey2
(integer) 10
位存储
可以统计用户信息,活跃、不活跃、登录、未登录、打卡、未打卡,只要是两个状态的都可以使用Bitmaps
# setbit
# getbit
# bitcount key 统计 1 的个数 即打卡的天数
# 模拟一周打卡的场景 0~6 代表周一 -- 周日 0 代表未打卡 1 代表打卡
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
# 查看某天是否打卡
127.0.0.1:6379> getbit sign 5
(integer) 1
127.0.0.1:6379> getbit sign 4
(integer) 0
127.0.0.1:6379> bitcount sign
(integer) 5
事务本质:一组命令的集合 按照顺序执行
一次性、顺序性、排他性
Redis单条命令是保证原子性的,但是事务是不保证原子性的
Redis也没有隔离性,所有的命令在事务中并没有直接执行,只有发起执行命令的时候才会执行 exec
Redis事务:
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 入队,并没有直接执行
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec # 发起执行命令之后,才顺序执行
1) OK
2) OK
3) "v2"
4) OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard # 放弃事务 之前队列中的命令都不会执行
OK
127.0.0.1:6379> get k4
(nil)
编译型异常 命令有错,事务中的所有命令都不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3 # 此处命令使用操作,缺少参数
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec # 执行事务报错 所有命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
运行时异常 如果事务队列中存在语法问题,执行命令时,其他命令是正常执行的,错误命令抛出异常
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 # 此处字符串自增,错误
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range # 执行错误
2) OK # 其余命令正常执行
3) OK
悲观锁:很悲观,认为一定会出问题,遇到什么情况都会加锁
乐观锁:很乐观,认为一定不会出问题,什么情况都不会加锁,在数据更新的时候会将数据查出来,查出version字段,更新的时候where语句会加上之前查到的version字段,这样只要中途有所改动,此更新就不会成功
# 正常执行成功
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> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec # 事务正常结束,期间数据没有改动,成功执行,事务成功执行后,对money的监控也随之取消
1) (integer) 80
2) (integer) 20
再开启一个窗口 模拟多线程
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
# 此时先不要执行事务,模拟另一个线程将money进行修改
# 此时另一个线程对money进行了修改
127.0.0.1:6379> incrby money 1000
(integer) 1080
# 然后执行刚才的事务,发现执行失败,原因是之前已经监视着money,另外一个线程对money进行了修改,所以不具备更新条件,事务执行失败
127.0.0.1:6379> exec
(nil)
# 事务执行失败后,就先解锁,再次加锁
127.0.0.1:6379> unwatch money
什么是Jedis?
是Redis官方推荐的java连接工具,使用java操作Redis中间件。
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.78version>
dependency>
这里注意如果要连接远程,必须在阿里云开放端口6379,修改redis的配置文件 将bind 127.0.0.1 注释掉 修改protected-mode no 添加requirepass123456,之后重启redis服务和客户端
如果连接不成功,查看防火墙是否开启,如果是开启的话,开放6379端口
public class TestPing {
public static void main(String[] args) {
Jedis jedis = new Jedis("远程ip",6379);
jedis.auth("123456"); // 修改配置文件的密码
String ping = jedis.ping();
System.out.println(ping);
jedis.close();
}
}
public class TestTA {
public static void main(String[] args) {
Jedis jedis = new Jedis("远程ip",6379); // 连接redis
jedis.auth("123456");
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("name","zhangsan");
jsonObject.put("age","26");
String s = jsonObject.toJSONString();
Transaction multi = jedis.multi(); // 开启事务
try {
multi.set("k1",s);
multi.set("k2","hello redis");
multi.exec(); // 执行事务
}catch (Exception e){
multi.discard(); // 放弃事务,相当于回滚
}finally {
jedis.close(); // 关闭连接
System.out.println(jedis.get("k1"));
System.out.println(jedis.get("k2"));
}
}
}
在SpringBoot2.x之后,原来使用的jedis被替换成lettuce
jedis:采用的是直连,多个线程操作的时候是不安全的,如果想要避免不安全,使用jedis pool连接池
lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况,可以减少线程数量
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") // 可以自己定义一个redisTemplate来替换这个默认的
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
// 默认的RedisTemplate 没有过多的设置,redis对象都是需要序列化的
// 两个泛型都是object,之后使用需要强制转换为
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean // String类型最常使用的类型,所以单独提出一个bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
# 配置redis
spring.redis.host=远程ip
spring.redis.port=6379
spring.redis.password=123456
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
void contextLoads() {
// opsForValue 类似String
// opsForGeo() 操作地图
// opsForList() 操作列表
// opsForHash() 操作hash
// opsForSet() 操作set集合
// opsForHyperLogLog()
// opsForZSet()
// 获取redis连接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
redisTemplate.opsForValue().set("k1","v1");
System.out.println(redisTemplate.opsForValue().get("k1"));
}
}
定义一个User类,先不进行序列化
package com.hua.pojo;
import org.springframework.stereotype.Component;
@Component
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
存储json字符串
@Test
public void test() throws JsonProcessingException {
User user = new User("小明",23);
String jsonUser = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",jsonUser);
System.out.println(redisTemplate.opsForValue().get("user"));
}
// 输出 json字符串 {"name":"小明","age":23}
存储对象
@Test
public void test() throws JsonProcessingException {
User user = new User("小明",23);
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
// 出错,org.springframework.data.redis.serializer.SerializationException: 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.hua.pojo.User]
将User类进行序列化就可以成功,所以在企业中所有的实体类都需要序列化 User{name=‘小明’, age=23}
如果在get user时,名字还是乱码,则在启动的时候 redis-cli -a 123456 --raw -a 后是密码
默认的序列化为JdkSerializationRedisSerializer jdk序列化,想要自己的序列化就必须编写自己的redisTemplate
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
// 配置自己的序列化方式
// 1. Jackson的序列化配置
Jackson2JsonRedisSerializer<Object> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jsonRedisSerializer.setObjectMapper(mapper);
// 2. String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
// hash的key采用String的序列化方式
redisTemplate.setHashKeySerializer(stringRedisSerializer);
// value采用Jackson的序列化方式
redisTemplate.setValueSerializer(jsonRedisSerializer);
// hash的value采用Jackson的序列化方式
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
工作中,会使用一些自定义的redis工具类,工具类中封装了一些操作方法,直接调用即可
redis.conf
# include /path/to/local.conf
# include /path/to/other.conf
bind 127.0.0.1 # 绑定的ip
protected-mode yes # 保护模式
port 6379 # 端口设置
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 # 数据库的数量 默认是16个
always-show-logo yes # 是否显示log
在规定的时间内,执行了多少操作,则会持久化到文件 .rdb .aof redis是内存数据库,没有持久化,数据断点即失
save 900 1 # 900s内至少有一个key进行修改 就进行持久化操作
save 300 10 # 300s内至少有10个key进行修改 就进行持久化操作
save 60 10000 # 60s内至少有10000个key进行修改 就进行持久化操作
stop-writes-on-bgsave-error yes # 持久化是否继续工作
rdbcompression yes # 是否压缩 rdb文件 需要消耗 cpu资源
rdbchecksum yes # 保存rbd文件的时候进行错误的校验
dir ./ # rdb文件保存的目录
主从复制 REPLICATION
安全 SECURITY
# requirepass foobared 密码默认为空的
127.0.0.1:6379> config set requirepass 123456 # 设置密码
# 设置密码后所有命令没有权限了
127.0.0.1:6379>auth 123456 # 认证密码
# maxclients 10000 设置客户端最大连接数量
# maxmemory 最大的内存容量
# maxmemory-policy noeviction 内存到达上限时的处理策略
# 六种处理策略
1、volatile-lru: 只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru: 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random: 随机删除
5、volatile-ttl: 删除即将过期的
6、noeviction: 永不过期,返回错误
appendonly no # 默认不开启 默认使用rdb方式持久化的,大部分情况rdb足够了
appendfilename "appendonly.aof" # aof持久化文件的名字
# appendfsync always # 每次修改都会sync 速度慢
appendfsync everysec # 每秒执行一次sync 可能丢失1s的数据
# appendfsync no # 不执行sync os自己同步数据,速度最快
Redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,一旦服务器进程退出,服务器中的数据库状态也会消失,所以必须要有持久化操作。
RDB保存的文件 dump.rdb
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是快照,恢复时将快照文件读到内存中,Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化操作结束后,再用这个临时文件替换上一次的持久化文件,整个过程主进程不会进行任何IO操作,这就确保了极高的性能。如果要进行大规模数据的恢复,且对数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加高效。RDB的缺点就是最后一次持久化后的数据可能丢失。默认使用的就是RDB,一般情况下不会进行修改。
触发机制
备份就会自动生成dump.rdb
如何恢复rdb文件?
只需将dump.rdb文件放在redis的启动目录下,redis启动的时候会自动检查dumo.rdb文件,恢复数据
127.0.0.1:6379> config get dir # 查看dump.rdb文件的默认存放位置,只要该位置有该文件,就会有数据
dir
/usr/local/bin
优点:
缺点:
AOF保存的文件是 appendonly.aof
以日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但是不可以改写文件,redis启动的时候就会读取该文件重新构建数据,也就是当redis重启的时候会根据日志文件的内容将写指令从前到后执行一次。
appendonly no # 默认不开启 需要手动开启 yes
appendfilename "appendonly.aof" # 存储文件名
# appendfsync always
appendfsync everysec # 每秒写
# appendfsync no
重启redis就可以生效,会在redis的运行界面生成一个appendonly.aof的文件
如果aof文件大于64m,此时就会fork一个新的进程会文件进行重写
如果appendonly.aof文件有问题,是连接不上redis服务端的,这时可以用redis运行目录的redis-check-aof 进行修复,错误的数据可能会被删除
redis-check-aof --fix appendonly.aof # 这样就可以进行修复
优点
缺点
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息 例如微信公众号,微博
Redis客户端可以订阅任意数量的频道
测试:
127.0.0.1:6379> subscribe hhhhh # 订阅频道 hhhhh
Reading messages... (press Ctrl-C to quit) # 此时就在等待频道推送消息
1) "subscribe"
2) "hhhhh"
3) (integer) 1
127.0.0.1:6379> publish hhhhh hello-redis # 发布者向频道推送消息 hello-redis
(integer) 1
1) "message" # 消息
2) "hhhhh" # 来自那个频道
3) "hello-redis" # 消息的具体内容
原理:
Redis是通过C实现的,通过publish、subscribe、psubscribe(订阅多个频道)等命令实现发布和订阅。
redis-server里维护了一个字典,字典的键就是一个个的频道,字典的值是一个链表,链表中保存了所有订阅该频道的用户,subscribe的关键就是将客户端添加到指定频道的订阅用户链表中。
通过publish命令向订阅者发送消息,redis-server会使用给定的频道作为键,在字典中找到该键对应的用户链表,遍历该链表,将消息发布给所有订阅者。
使用场景:
实时消息系统,实时聊天系统,订阅关注系统。
概念
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器,前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主。
默认情况下,每台redis服务器都是主节点,且一个主节点可以有多个从节点(或者没有从节点),但是一个从节点只能有一个主节点。
作用:
一般Redis运用在工程项目中,只使用一台Redis是不可能的,原因如下:
环境配置
只配置从库,不用配置主库
127.0.0.1:6379> info replication # 查看当前库的信息
# Replication
role:master # 角色
connected_slaves:0 # 从机个数
master_replid:9499da347d88ead93325b6a1aba3ab4545dbc48f
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
复制三个配置文件(redis79.conf,redis80.conf,redis81.conf),修改对应的信息,端口号,pid名字,日志名字,dump.rdb文件名
[root@summer1245 config]# ps -aux|grep redis
root 16377 0.0 0.3 165776 3288 ? Rsl 14:28 0:00 redis-server 127.0.0.1:6379
root 16388 0.0 0.1 24860 1556 pts/1 S+ 14:28 0:00 redis-cli -p 6379
root 16402 0.0 0.3 165776 3288 ? Ssl 14:28 0:00 redis-server 127.0.0.1:6380
root 16414 0.0 0.1 24860 1556 pts/2 S+ 14:29 0:00 redis-cli -p 6380
root 16433 0.0 0.3 159632 3296 ? Ssl 14:29 0:00 redis-server 127.0.0.1:6381
root 16443 0.0 0.1 24860 1568 pts/3 S+ 14:29 0:00 redis-cli -p 6381
一主二从
默认情况下每一个redis服务器都是一个主节点,一般情况下只配置从机
一主(79)二从(80、81)
在两个从机上运行命令
127.0.0.1:6380> slaveof 127.0.0.1 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=84,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=84,lag=1
master_replid:54b0726996e93d8df0781612cc24226d147acfb4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:84
从机的库信息
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:6
master_sync_in_progress:0
slave_repl_offset:14
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:54b0726996e93d8df0781612cc24226d147acfb4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:84
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:54b0726996e93d8df0781612cc24226d147acfb4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:84
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:71
repl_backlog_histlen:14
真实的主从配置应该在配置文件中配置,这样的配置就能是永久的,这里使用命令进行配置,只能是暂时的
在从机的配置文件中的aplication配置区域,修改以下信息
replicaof <masterip> <masterport> # 配置所属主机的ip和port
masterauth <master-password> # 如果主机有密码,则需添加该项
细节说明:
# 从机写会报错
(error) READONLY You can't write against a read only replica.
当主机宕机,默认情况下从机的主机不会进行改变,此时会丧失写的能力;当主机重新上线,从机还是会读取到主机写下的新内容。
当从机宕机后重启,如果是命令行方式创建的集群,则此时本来的从机会默认成为主机,不会读取到主机写入的内容,当重新配置为从机之后,此时又会读取到主机写的内容。
复制原理
从机启动成功连接到主机后会发送一个sync同步命令,主机接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,主机将传送整个数据文件到从机,并完成一次完全同步。
全量复制:将主机的整个数据文件传送到从机,并同步
增量复制:主机继续将新的所有收集到的修改命令一次传给从机,完成同步
但是只要是重新连接主机,一次完全同步(全量复制)将会自动执行
另一种主从配置方式 层层链路
127.0.0.1:6381> slaveof 127.0.0.1 6380 # 修改6381端口的从机的主机为 6380的
# 此时6380仍然是一个从机 无法进行写入
如果主机宕机,这时候能不能重新任命一个主机?
salveof no one # 主机宕机之后,在某一个从机执行整个命令,该从机会变为主机,其他的从机可以手动连接到新的主机,如果原来的主机重新上线,也不会成为集群的主机了,篡位成功!
在两台服务器上搭建一主而从,一台服务器的6379端口为主,6380为从,另一台服务器的6381为从
主机需要修改绑定,关闭保护模式,设置密码
从机需要配置主机的ip和端口号,访问密码
此处的6379和6380是一台服务器上的
6379配置(主)
# bind 127.0.0.1 注掉
protected-mode no 保护模式关闭
requirepass 123456 设置密码
6380配置(从) 复制一份6379的配置 修改端口号,pid名字,日志名字,dump.rdb文件名
replicaof 主机所在服务器ip(或者写127.0.0.1) 6379
masterauth 123456
6381配置(从)
port 6381
pidfile /var/run/redis_6381.pid
logfile "6381.log"
replicaof 主机所在的服务器ip 6379
masterauth 123456
如果出现以下问题,代表主从复制失败。
master_link_status:down
如果master_link_status:up 则成功
down大概有以下几种情况,具体情况查看日志文件
1、主机protected-mode配置成了yes开启该参数后,redis只会本地进行访问,拒绝外部访问,所以需要配成protected-mode no
2、如果在主中设置了密码,从中应该配置密码
3、bind ip地址 表示可以那些服务器可以访问,设置为 0.0.0.0 或者不绑定 表示都可以访问,如果是127.0.0.1 表示本地访问
4、端口,防火墙问题
#防火墙添加6379端口:firewall-cmd --permanent --zone=public --add-port=6379/tcp
#重启防火墙:firewall-cmd --reload
#查看默认防火墙状态(关闭后显示notrunning,开启后显示running):firewall-cmd --state
另外如果是云服务器,则需要修改安全组
概述
主从切换技术的方法就是:当主服务器宕机之后,需要手动把一台从服务器切换为主服务器,需要人工干预,费时费力而且会有段时间服务不可用,因此更多时候采用哨兵模式。
哨兵模式能够监控主机是否故障,如果故障了根据投票数自动将从库转为主库。
哨兵模式是一种特殊的模式,Redis提供了哨兵的命令,哨兵是一个独立的进程,独立运行,原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵作用:
当一个哨兵进程对Redis服务器进行监控可能会出现问题,因此可以使用多个哨兵进行监控,各个哨兵之间互相监控,形成多哨兵模式。
假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用时,并且数量达到一定值时,哨兵之间会进行一次投票,投票的结果由一个哨兵发起,进行failover(故障转移)操作,切换成功之后,就会通过发布订阅模式,让各个哨兵把自己监控的服务器实现切换主机,这个过程为客观下线。
测试:
一主而从
# 1. 配置哨兵配置文件 sentinel.conf
# 被监控的名称 host port 主机宕机,进行投票,票数最多的从机为主机
sentinel monitor myredis 127.0.0.1 6379 1
# 2. 启动哨兵
[root@summer1245 bin]# redis-sentinel config/sentinel.conf
20861:X 30 Jun 2022 15:59:43.337 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
20861:X 30 Jun 2022 15:59:43.337 # Redis version=5.0.14, bits=64, commit=00000000, modified=0, pid=20861, just started
20861:X 30 Jun 2022 15:59:43.337 # Configuration loaded
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 5.0.14 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 20861
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
20861:X 30 Jun 2022 15:59:43.339 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
20861:X 30 Jun 2022 15:59:43.340 # Sentinel ID is 21c0c45d55765b158b6d239fe1a2ac23847b004a
20861:X 30 Jun 2022 15:59:43.340 # +monitor master myredis 127.0.0.1 6379 quorum 1
20861:X 30 Jun 2022 15:59:43.341 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
20861:X 30 Jun 2022 15:59:43.342 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
# 哨兵日志
20861:X 30 Jun 2022 16:01:29.696 # +sdown master myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:29.696 # +odown master myredis 127.0.0.1 6379 #quorum 1/1
20861:X 30 Jun 2022 16:01:29.696 # +new-epoch 1
20861:X 30 Jun 2022 16:01:29.696 # +try-failover master myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:29.698 # +vote-for-leader 21c0c45d55765b158b6d239fe1a2ac23847b004a 1
20861:X 30 Jun 2022 16:01:29.698 # +elected-leader master myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:29.698 # +failover-state-select-slave master myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:29.764 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:29.764 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:29.822 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:30.610 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:30.610 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:30.691 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:31.623 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:31.623 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:31.678 # +failover-end master myredis 127.0.0.1 6379
20861:X 30 Jun 2022 16:01:31.678 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6381
20861:X 30 Jun 2022 16:01:31.679 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381
20861:X 30 Jun 2022 16:01:31.679 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
20861:X 30 Jun 2022 16:02:01.721 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
如果主机宕机之后,此时就会从从机中根据投票算法选举出一个从机转为主机,此处选举了6381的从机,如果原主机重新上线,则只能是新主机的从机。
优点:
缺点:
根据上述场景,一台服务器上是一主一从,另一台服务器上是一从,进行以下配置
三个配置文件 全部注释bind,关掉保护模式,修改对应的端口,文件名,设置密码,这里密码都是123456,设置为一样的
从服务器需要配置主服务器的ip和端口号,访问密码
哨兵配置文件
sentinel monitor myredis 主机ip 6379 1
sentinel auth-pass myredis 123456
另外如果开启了防火墙,一定要打开对应的端口号,云服务器还要设置安全组
哨兵模式的全配置
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379 如果有哨兵集群,需要配置每个哨兵的端口
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
# sentinel auth-pass
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越多的slave因为replication而不可用。可以通过将这个值设为1来保证每次只有一个slave 处于不能处理命令请求的状态。
# sentinel parallel-syncs
sentinel parallel-syncs mymaster 1
# 故障转移的超时时间failover-timeout可以用在以下这些方面:
# 1. 同一个sentinel对同一个master两次failover之间的间隔时间。
# 2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
# 3.当想要取消一个正在进行的failover所需要的时间。
# 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
# 配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
# 对于脚本的运行结果有以下规则:
# 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
# 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
# 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
# 一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
# 通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
# 通知脚本
# shell编程
# sentinel notification-script
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
# 以下参数将会在调用脚本时传给脚本:
#
# 目前总是“failover”,
# 是“leader”或者“observer”中的一个。
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 一般都是由运维来配置!
缓存中查不到
Redis缓存的使用,极大地提升了应用程序地性能和效率,特别是数据查询方面。同时也会带来一些问题,最严重的就是数据的一致性问题,如果对数据地一致性要求过高,则不能使用缓存。另外还有缓存穿透,缓存雪崩和缓存击穿等问题。
1一、缓存穿透
用户如果想要查询某个数据,发现Redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库查询,发现也没有,于是本次查询失败。当用户很多时,缓存都没有名中,于是都去请求了持久层数据库。这会给持久层数据库造成很多的压力,这时候就叫做缓存穿透。
解决方案
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免对底层数据库的查询压力。
当存储层不命中后,即使返回空对象也能将其缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据库。
但是此种方法会有问题:
如果空值能够被缓存起来,这就意味着缓存需要更多的空间来存储键。
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口不一致,这对于高一致性的业务会有影响。
二、缓存击穿
查的太多,缓存过期
一个key非常热点,在不停的有高并发,大并发地集中访问该点,当这个key失效的瞬间,大并发就会穿破缓存直接请求数据库,就像是在一堵墙上钻了一个洞。
当某个key在过期地瞬间,有大量地请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且写回缓存,会导致数据库瞬间压力过大。
解决方案
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。
分布式锁,保证每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,只需等待。这种方式将高并发的压力转移到了分布式锁。
三、缓存雪崩
缓存雪崩是指某个时间段,缓存集体过期失效,Redis宕机。
集中过期不是最致命的,最致命的是缓存服务器的某个节点宕机或者断网,很可能将数据库直接压垮
解决方案
多设几台Redis服务器,搭建集群
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程查询数据和写缓存,其他线程等待
在正式部署之前,把可能的数据先预先访问一遍,这样部分可能会大量访问的数据就会加载到缓存中,在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,然缓存失效的时间点尽量均匀。