以下学习内容笔记全部来源于B站教学视频:【狂神说Java】Redis最新超详细版教程通俗易懂,非常感谢来自狂神老师的教学系列,附B站学习视频链接:https://www.bilibili.com/video/av840034966
单机mysql时代:
90年代,一个基本的网站访问量不会太大,单个数据库足够!
更多的使用静态网页html
网站瓶颈:
缓存时代:
memcached(缓存)+mysql+读写分离(垂直拆分)
网站百分之80情况都是在读,每次都要去数据库查询太麻烦,减轻数据库的压力,可以使用缓存来保证效率(Cache)-------解决读的问题
发展过程:优化Mysql底层结构和索引–>文件缓存(IO操作)—>Memcached 当时最热门的技术!
分库分表+水平拆分+mysql集群:
技术和业务发展同时,对技术要求也越来越高
本质: 数据库(读、写)
早些年 MyISAM:表锁,影响效率,高并发下出现严重的锁问题
早些年Innodb:行锁
慢慢地使用分库分表来解决写的压力!并没有多少公司使用
Mysql集群,很好满足了个别需求
最近的年代:
2010-2020 技术发展迅速(定位、音乐、热榜)
Mysql等关系型数据库就不够用了,数据量很多,变化很快
目前一个基本的互联网项目:
为什么要用Nosql
这时候可以使用Nosql数据库处理以上的情况!
NoSQL
NoSQL=not only sql
SQL:表格,行,列
泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!
NoSQL在当今大数据时代发展迅速,REDIS发展是最快的,、
NoSQL特点
方便扩展(数据之间没有关系,很好扩展,没有耦合性)
大数据量下的高性能(redis一秒可以写8W次,可以读取11w次,NoSQL的缓存级别,是一种细粒度的缓存,性能会比较高!)
多类型(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人无法设计)
传统RDBM和NoSQL:
结构化组织
SQL
数据和关系都存在单独的表中
数据操作语言,数据定义语言,
严格的一致性
基础的事务
…
不仅仅是数据
没有固定的查询语言
键值对存储,列存储,文档存储,图形数据库(社交关系)
最终一致性
CAP定理、 BASE (异地多活)初级架构师!
基本的高性能、高可用、高可扩展
…
扩展:大数据的3v和3高
描述问题:海量、多样、实时
程序要求:高并发、高可用、高性能
KV键值对
文档型数据库(bson格式json)
MongoDB(一般必须掌握)
基于分布式文件存储的数据库,C++编写,处理大量的文档!
是一个介于关系型数据库和非关系型数据库中间的产品,是非关系型数据库中功能最丰富的,最像关系型数据库的!
ConthDB
列存储数据库
图形关系数据库
不是放图片的,是放关系的:朋友圈社交网络、广告推荐!
Ne04j, infoGrid
NoSQL的对比
redis–Remote Dictionary Server
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。免费且开源,当前最热门的NoSql之一
能干嘛?
内存存储、持久化,内存中是断电即失去,所以说持久化很重要(rdb/aof)
效率高、可以用于高速缓存
发布订阅系统
地图信息分析
计时器、计数器(浏览量)
…
特性
学习中需要用到的东西
下载安装包 https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
解压到特定文件夹
默认端口:6379
测试连接:
windows下使用确实简单,但是推荐使用linux去开发使用
redis-6.0.6.tar.gz
并进入
tar -zxvf redis-6.0.6.tar.gz
配置文件:redis.conf
yum install gcc-c++
gcc -v
make
(需要一点时间)报错,原因分析:gcc版本过低,升级一下
# 升级gcc版本 $ yum -y install centos-release-scl $ yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils $ scl enable devtoolset-9 bash # 升级后查看下版本 $ gcc -v
重新执行make
make install
(也可以不用执行,确认一下安装)所有东西已安装好了
Redis默认安装路径:/usr/local/bin
复制Redis配置文件到当前目录下
# 创建配置文件文件夹并复制配置文件 $ mkdir hconfig $ cp /opt/redis-6.0.6/redis.conf hconfig
之后就使用这个配置文件进行启动
Redis默认不是后台启动的,需要修改配置文件
# 修改daemonize为yes $ vim redis.conf
启动Redis服务
通过指定的配置文件启动服务
redis-server hconfig/redis.conf
测试连接
使用Redis客户端进行连接
# 连接 $ redis-cli -p 6379 127.0.0.1:6379> ping PONG 127.0.0.1:6379> set name yunmx # 设置一个key OK 127.0.0.1:6379> get name # 读取一个key "yunmx" 127.0.0.1:6379> keys * # 查询所有的key 1) "name" 127.0.0.1:6379>
查看Redis的进程是否开启
ps -ef |grep redis
关闭redis服务
shutdown
127.0.0.1:6379> ping PONG 127.0.0.1:6379> shutdown not connected> exit
官方自带的性能测试工具—>
redis-benchmark
命令参数:
简单测试:
# 测试100个并发连接,每个并发20个请求 $ redis-benchmark -h localhost -p 6379 -c 100 -n 20
查看分析:
====== PING_INLINE ====== 100000 requests completed in 1.46 seconds # 100000次ping请求在1.46s内完成 100 parallel clients # 100的并发量 3 bytes payload # 3字节数据写入 keep alive: 1 # 保证一台服务器测试(单机性能) host configuration "save": 900 1 300 10 60 10000 host configuration "appendonly": no multi-thread: no 0.00% <= 0.5 milliseconds # 0.5毫秒内完成百分之0的请求 1.37% <= 0.6 milliseconds 11.42% <= 0.7 milliseconds 23.22% <= 0.8 milliseconds 35.29% <= 0.9 milliseconds 47.60% <= 1.0 milliseconds 60.01% <= 1.1 milliseconds 72.40% <= 1.2 milliseconds 84.82% <= 1.3 milliseconds 94.78% <= 1.4 milliseconds 97.65% <= 1.5 milliseconds 98.50% <= 1.6 milliseconds 98.95% <= 1.7 milliseconds 99.28% <= 1.8 milliseconds 99.51% <= 1.9 milliseconds 99.67% <= 2 milliseconds 99.99% <= 3 milliseconds 100.00% <= 3 milliseconds # 3毫秒内完成100%的请求(100000) 68587.11 requests per second # 平均每秒完成的请求数
Redis默认数据库数量有16个,redis.conf配置文件可查看,默认使用的数据库为0
# 使用select 命令进行切换 [root@yunmx bin]# redis-cli -p 6379 127.0.0.1:6379> select 1 OK 127.0.0.1:6379[1]> select 2 OK 127.0.0.1:6379[2]> select 16 (error) ERR DB index is out of range 127.0.0.1:6379[2]> select 15 OK 127.0.0.1:6379[15]>
# DBSIZE 查看数据库大小 127.0.0.1:6379> set name hejie OK 127.0.0.1:6379> DBSIZE (integer) 1 127.0.0.1:6379> SET AGE NAN OK 127.0.0.1:6379> DBSIZE (integer) 2 127.0.0.1:6379>
# 使用keys * 127.0.0.1:6379> keys * 1) "AGE" 2) "name" 127.0.0.1:6379>
# 清空当前数据库flushdb 127.0.0.1:6379> keys * 1) "AGE" 2) "name" 127.0.0.1:6379> flushdb OK 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> # 清空所有数据库flush 127.0.0.1:6379> select 0 OK 127.0.0.1:6379> set name hejie OK 127.0.0.1:6379> set key1 hejie1 OK 127.0.0.1:6379> set key2 hejie2 OK 127.0.0.1:6379> keys * 1) "key1" 2) "key2" 3) "name" 127.0.0.1:6379> select 1 OK 127.0.0.1:6379[1]> set name yunmx OK 127.0.0.1:6379[1]> set key1 yunmx1 OK 127.0.0.1:6379[1]> set key2 yunmx2 OK 127.0.0.1:6379[1]> keys * 1) "key1" 2) "key2" 3) "name" 127.0.0.1:6379[1]> flushall OK 127.0.0.1:6379[1]> keys * (empty array) 127.0.0.1:6379[1]> select 0 OK 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379>
# 使用exists 判断 127.0.0.1:6379> set name hejie OK 127.0.0.1:6379> set age 1 OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> exists age (integer) 1 # 存在 127.0.0.1:6379> exists name (integer) 1 127.0.0.1:6379> exists key1 (integer) 0 # 不存在 127.0.0.1:6379>
# move移动一个key 127.0.0.1:6379> set key1 hejie OK 127.0.0.1:6379> set key2 hejie1 OK 127.0.0.1:6379> set key3 hejie2 OK 127.0.0.1:6379> move key1 1 # 将key1移动给数据库1 (integer) 1 127.0.0.1:6379> select 1 OK 127.0.0.1:6379[1]> keys * 1) "key1" 127.0.0.1:6379[1]>
# EXPIRE 设置一个key的过期时间,单位时间:s # TTL 查看一个key的过期时间 127.0.0.1:6379> keys * 1) "key3" 2) "key2" 127.0.0.1:6379> get key3 "hejie2" 127.0.0.1:6379> EXPIRE key3 10 # 设置key3 10秒后过期 (integer) 1 127.0.0.1:6379> ttl key3 (integer) -2 # 表示已经过期 127.0.0.1:6379> ttl key3 (integer) -2 127.0.0.1:6379> keys * 1) "key2" 127.0.0.1:6379>
单线程为什么这么快?
C语言写的,官方表示每秒的QPS是100000+,基于内存操作,Redis的瓶颈和服务器的内存和网络带宽有关,既然可以用单线程实现,就用单线程喽。
误区:
高性能服务器一定是多线程的?
多线程一定比单线程效率高?
核心:Redis是将所有数据放在内存中的,所以说使用单线程去操作效率就是最高的,因为多线程会产生CPU上下文切换,消耗时间的操作,对于内存系统来说,没有上下文切换,效率就是最高的,多次读写都是在一个CPU上的
五大数据类型
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
使用场景:value除了是字符串还可以是我们的数字
### 常见用法 127.0.0.1:6379> set kye1 yunmx # 设置一个key OK 127.0.0.1:6379> get key1 # 获取一个key的值 (nil) 127.0.0.1:6379> keys * # 查看当前库的所有key,输出key名 1) "kye1" 127.0.0.1:6379> EXISTS kye1 # 判断key是否存在 (integer) 1 127.0.0.1:6379> EXISTS key1 (integer) 0 127.0.0.1:6379> APPEND kye1 "helloworld" # 向key追加内容,如果key不存在,就设置一个key (integer) 15 127.0.0.1:6379> get kye1 "yunmxhelloworld" 127.0.0.1:6379> STRLEN kye1 # 获取一个key的长度 (integer) 15 127.0.0.1:6379> ### 自增长用法 127.0.0.1:6379> set count 0 OK 127.0.0.1:6379> get count "0" 127.0.0.1:6379> incr count # 自增1 (integer) 1 127.0.0.1:6379> get count "1" 127.0.0.1:6379> incr count (integer) 2 127.0.0.1:6379> get count "2" 127.0.0.1:6379> decr count # 自减1 (integer) 1 127.0.0.1:6379> get count "1" 127.0.0.1:6379> decr count (integer) 0 127.0.0.1:6379> get count "0" 127.0.0.1:6379> incrby count 5 # 自增;步长5 (integer) 5 127.0.0.1:6379> get count "5" 127.0.0.1:6379> decrby count 5 # 自减;步长5 (integer) 0 127.0.0.1:6379> get count "0" ### 获取字符串范围 127.0.0.1:6379> set key1 yunmxhejie # 设置一个key OK 127.0.0.1:6379> getrange key1 0 4 # 获取从标0到4范围的字符串 "yunmx" 127.0.0.1:6379> getrange key1 0 -1 # 获取全部的字符串 "yunmxhejie" 127.0.0.1:6379> ### 替换 127.0.0.1:6379> set key1 yunmx123456 # 设置一个key OK 127.0.0.1:6379> SETRANGE key1 0 hejie # 从下标为0的位置开始替换 (integer) 11 127.0.0.1:6379> get key1 "hejie123456" 127.0.0.1:6379> ### 设置过期时间 127.0.0.1:6379> setex key1 10 yunmx # 设置一个key,过期时间为10秒 OK 127.0.0.1:6379> ttl key1 # 查看key的过期时间 (integer) 7 127.0.0.1:6379> ttl key1 # 返回-2表示已经过期 (integer) -2 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> ### 不存在设置(分布式锁中会经常用到) 127.0.0.1:6379> setnx key2 redis # setnx:不存在key值则创建 (integer) 1 127.0.0.1:6379> setnx key2 mysql # setnx:存在key值则创建失败 (integer) 0 127.0.0.1:6379> keys * 1) "key2" 127.0.0.1:6379> ### 批量设置和获取 127.0.0.1:6379> mset key1 1 key2 2 key3 3 key4 4 # 同时设置多个值 OK 127.0.0.1:6379> keys * 1) "key1" 2) "key4" 3) "key3" 4) "key2" 127.0.0.1:6379> mget key1 key2 key3 key4 # 同时获取多个值 1) "1" 2) "2" 3) "3" 4) "4" 127.0.0.1:6379> msetnx key5 5 key4 4 # 原子性操作:要么一起成功,要么一起失败 (integer) 0 127.0.0.1:6379> get key5 (nil) 127.0.0.1:6379> ### 对象 127.0.0.1:6379> set user:1 {name:yunmx,age:25} # 普通设置对象:key加json字符串 OK 127.0.0.1:6379> get user:1 "{name:yunmx,age:25}" 127.0.0.1:6379> mset user:1:name yunmx user:1:age 25 # 巧妙设计对象;如此设计,是完全可以的 OK 127.0.0.1:6379> mget user:1:name user:1:age 1) "yunmx" 2) "25" ### 组合命令 127.0.0.1:6379> getset db mysql # 如果不存在,则设置 (nil) 127.0.0.1:6379> get db "mysql" 127.0.0.1:6379> getset db MongoDB # 如果存在,返回当前值,再设置新的值 "mysql" 127.0.0.1:6379> get db "MongoDB" 127.0.0.1:6379>
基本的数据类型
基本的数据类型,列表
在redis里面,list可以完成栈、队列、阻塞队列!
用法:
消息排队、消息队列(lpush Rpop) 栈(lpush lpop)
### 基础命令 127.0.0.1:6379> LPUSH key1 1 # 将一个值或者多个值添加到头部 (integer) 1 127.0.0.1:6379> LPUSH key1 2 (integer) 2 127.0.0.1:6379> LPUSH key1 3 (integer) 3 127.0.0.1:6379> LRANGE key1 0 -1 1) "3" 2) "2" 3) "1" 127.0.0.1:6379> LRANGE key1 0 1 1) "3" 2) "2" 127.0.0.1:6379> rpush key1 5 # 将一个值添加到尾部 (integer) 4 127.0.0.1:6379> LRANGE key1 0 -1 1) "3" 2) "2" 3) "1" 4) "5" ### 移除元素 127.0.0.1:6379> LRANGE key1 0 -1 1) "5" 2) "4" 3) "3" 4) "2" 5) "1" 127.0.0.1:6379> LPOP key1 # 移除列表的第一个元素 "5" 127.0.0.1:6379> rPOP key1 # 移除列表的最后一个元素 "1" 127.0.0.1:6379> LRANGE key1 0 -1 1) "4" 2) "3" 3) "2" ### 获取元素、列表长度 127.0.0.1:6379> LRANGE key1 0 -1 1) "4" 2) "3" 3) "2" 127.0.0.1:6379> LINDEX key1 0 # 获取列表中的第一个元素 "4" 127.0.0.1:6379> LINDEX key1 1 "3" 127.0.0.1:6379> LINDEX key1 2 "2" 127.0.0.1:6379> LINDEX key1 3 (nil) 127.0.0.1:6379> LLEN key1 # 获取列表长度 (integer) 3 ### 移除指定的值 127.0.0.1:6379> LRANGE key1 0 -1 1) "2" 2) "4" 3) "4" 4) "3" 5) "2" 127.0.0.1:6379> LREM key1 1 2 # 移除一个2 (integer) 1 127.0.0.1:6379> LRANGE key1 0 -1 1) "4" 2) "4" 3) "3" 4) "2" 127.0.0.1:6379> LREM key1 2 4 # 移除2个4 (integer) 2 127.0.0.1:6379> LRANGE key1 0 -1 1) "3" 2) "2" ### 截取 127.0.0.1:6379> LRANGE key1 0 -1 1) "15" 2) "14" 3) "13" 4) "12" 5) "11" 6) "10" 127.0.0.1:6379> LTRIM key1 0 3 # 通过下标截取指定的长度,list已经被改变了 OK 127.0.0.1:6379> LRANGE key1 0 -1 1) "15" 2) "14" 3) "13" 4) "12" ### 组合命令:rpoppush 127.0.0.1:6379> rpush list1 1 2 3 4 5 (integer) 5 127.0.0.1:6379> LRANGE list1 0 -1 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 127.0.0.1:6379> rpoplpush list1 list2 # 移除列表中的最后一个元素,并加入到新的列表中 "5" 127.0.0.1:6379> LRANGE list1 0 -1 1) "1" 2) "2" 3) "3" 4) "4" 127.0.0.1:6379> LRANGE list2 0 -1 1) "5" 127.0.0.1:6379> ### 元素更新 127.0.0.1:6379> EXISTS list1 # 判断列表是否存在 (integer) 0 127.0.0.1:6379> lset list1 0 yunmx # 不存在报错 (error) ERR no such key 127.0.0.1:6379> lpush lista1 1 2 3 4 (integer) 4 127.0.0.1:6379> LRANGE list1 0 0 (empty array) 127.0.0.1:6379> LRANGE list1 0 -1 (empty array) 127.0.0.1:6379> LRANGE lista1 0 0 1) "4" 127.0.0.1:6379> lset lista1 0 yunmx # 存在修改当前下标的值 OK 127.0.0.1:6379> LRANGE lista1 0 0 1) "yunmx" ### 元素插入 127.0.0.1:6379> LRANGE lista1 0 -1 1) "yunmx" 2) "3" 3) "2" 4) "1" 127.0.0.1:6379> LINSERT lista1 before yunmx hejie # 插入当前元素的前面 (integer) 5 127.0.0.1:6379> LINSERT lista1 after yunmx hejie1 # 插入当前元素的后面 (integer) 6 127.0.0.1:6379> LRANGE lista1 0 -1 1) "hejie" 2) "yunmx" 3) "hejie1" 4) "3" 5) "2" 6) "1"
set中的值不能重复d ,是无序的
### 基础用法 127.0.0.1:6379> sadd set1 yunmx # 往集合里面存值 (integer) 1 127.0.0.1:6379> sadd set1 hejie1 (integer) 1 127.0.0.1:6379> sadd set1 hejie01 (integer) 1 127.0.0.1:6379> sadd set1 hejie02 (integer) 1 127.0.0.1:6379> sadd set1 hejie02 (integer) 0 127.0.0.1:6379> SMEMBERS set1 # 查询集合里面所有的元素 1) "yunmx" 2) "hejie02" 3) "hejie01" 4) "hejie1" 127.0.0.1:6379> SISMEMBER set1 hejie # 判断元素是否存在 (integer) 0 127.0.0.1:6379> SISMEMBER set1 hejie5 (integer) 0 127.0.0.1:6379> SISMEMBER set1 hejie1 # 存在返回1 (integer) 1 ### 获取集合中的元素的个数 127.0.0.1:6379> SCARD set1 (integer) 4 127.0.0.1:6379> sadd set1 hejie09 (integer) 1 127.0.0.1:6379> SCARD set1 (integer) 5 ### 移除set集合中的指定元素 127.0.0.1:6379> SREM set1 yunmx # 移除set集合中的:yunmx (integer) 1 127.0.0.1:6379> SCARD set1 (integer) 4 127.0.0.1:6379> SMEMBERS set1 1) "hejie01" 2) "hejie09" 3) "hejie1" 4) "hejie02" ### 抽随机用法(无序不重复) 127.0.0.1:6379> SRANDMEMBER set1 # 随机抽选出一个元素 "hejie02" 127.0.0.1:6379> SRANDMEMBER set1 "hejie09" 127.0.0.1:6379> SRANDMEMBER set1 "hejie02" 127.0.0.1:6379> SRANDMEMBER set1 "hejie09" 127.0.0.1:6379> SRANDMEMBER set1 "hejie01" 127.0.0.1:6379> SRANDMEMBER set1 2 # 随机抽选出指定的数量元素 1) "hejie1" 2) "hejie01" 127.0.0.1:6379> SRANDMEMBER set1 2 1) "hejie09" 2) "hejie02" 127.0.0.1:6379> SRANDMEMBER set1 2 1) "hejie1" 2) "hejie01" ### 随机移除元素 127.0.0.1:6379> SMEMBERS set1 1) "hejie01" 2) "hejie09" 3) "hejie1" 4) "hejie02" 127.0.0.1:6379> spop set1 # 随机移除一个元素 "hejie02" 127.0.0.1:6379> spop set1 "hejie1" 127.0.0.1:6379> SMEMBERS set1 1) "hejie01" 2) "hejie09" ### 指定的值移动另一个集合中 127.0.0.1:6379> SMEMBERS set1 1) "1" 2) "2" 3) "3" 127.0.0.1:6379> SMEMBERS set2 1) "er" 2) "san" 3) "yi" 127.0.0.1:6379> smove set1 set2 1 # 将set1中的元素移动到set2,需要移动的值为set1中的1 (integer) 1 127.0.0.1:6379> SMEMBERS set1 1) "2" 2) "3" 127.0.0.1:6379> SMEMBERS set2 1) "1" 2) "er" 3) "san" 4) "yi" ### 共同关注:B站,微博(并集) # 数字集合:差集、交集、并集 127.0.0.1:6379> sadd set1 1 2 3 4 5 6 (integer) 6 127.0.0.1:6379> sadd set2 4 5 6 7 8 9 (integer) 6 127.0.0.1:6379> 127.0.0.1:6379> SDIFF set1 set2 # 差集:set1中的元素不存在于set2 1) "1" 2) "2" 3) "3" 127.0.0.1:6379> SINTER set1 set2 # 交集 1) "4" 2) "5" 3) "6" 127.0.0.1:6379> SDIFF set2 set1 1) "7" 2) "8" 3) "9" 127.0.0.1:6379> SUNION set1 set2 # 并集 1) "1" 2) "2" 3) "3" 4) "4" 5) "5" 6) "6" 7) "7" 8) "8" 9) "9"
Map集合,key-Map集合,本质和string没有太大区别,还是一个简单的key-vlaue
应用
### 基础用法 127.0.0.1:6379> hset hash1 name yunmx # 设置一个key-vlaue (integer) 1 127.0.0.1:6379> hget hash1 name # 获取一个字段值 "yunmx" 127.0.0.1:6379> hmset hash1 name hejie age 28 # 设置多个 OK 127.0.0.1:6379> hgetall hash1 # 获取所有元素 1) "name" 2) "hejie" 3) "age" 4) "28" 127.0.0.1:6379> hget hash1 age # 获取一个字段值 "28" ### 删除一个值 127.0.0.1:6379> HDEL hash1 name # 删除指定的key字段 (integer) 1 127.0.0.1:6379> hgetall hash1 1) "age" 2) "28" ### 获取hash所有的长度(键值对) 127.0.0.1:6379> HLEN hash1 # 获取hash中的键值对数量 (integer) 1 127.0.0.1:6379> hset hash1 age 18 (integer) 0 127.0.0.1:6379> hset hash1 age1 19 (integer) 1 127.0.0.1:6379> hset hash1 name hejie (integer) 1 127.0.0.1:6379> HLEN hash1 (integer) 3 ### 判断hash中字段是否存在 127.0.0.1:6379> HGETALL hash1 1) "age" 2) "18" 3) "age1" 4) "19" 5) "name" 6) "hejie" 127.0.0.1:6379> HEXISTS hash1 age # 判断哈希中是否存在age字段 (integer) 1 127.0.0.1:6379> HEXISTS hash1 yunmx (integer) 0 ### 只获取所有的字段或值 127.0.0.1:6379> hkeys hash1 1) "age" 2) "age1" 3) "name" 127.0.0.1:6379> HVALS hash1 1) "18" 2) "19" 3) "hejie" ### 指定自增 127.0.0.1:6379> HINCRBY hash1 age 1 # 自增1 (integer) 19 127.0.0.1:6379> HINCRBY hash1 age -1 (integer) 18 127.0.0.1:6379> hsetnx hash1 age 1 # 如果存在,不设置 (integer) 0 127.0.0.1:6379> hsetnx hash1 age2 1 # 不存在,则添加新的键值对 (integer) 1
在set的基础上增加了一个值,多了一个计数位
场景思路:
### 基础用法 127.0.0.1:6379> zadd myset 1 one # 添加一个值 (integer) 1 127.0.0.1:6379> zadd myset 2 two (integer) 1 127.0.0.1:6379> zadd myset 3 three (integer) 1 127.0.0.1:6379> zadd myset 4 four 5 five # 添加多个值 (integer) 2 127.0.0.1:6379> ZRANGE myset 0 -1 # 查看所有的值 1) "one" 2) "two" 3) "three" 4) "four" 5) "five" ### 排序如何实现 # 用法:从最小到最大 # ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] 127.0.0.1:6379> zadd salary 12000 yunmx # 添加4个用户 (integer) 1 127.0.0.1:6379> zadd salary 11000 hejie (integer) 1 127.0.0.1:6379> zadd salary 10000 hejie01 (integer) 1 127.0.0.1:6379> zadd salary 9000 yunmx01 (integer) 1 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # -inf +inf 负无穷到正无穷 从小到大 1) "yunmx01" 2) "hejie01" 3) "hejie" 4) "yunmx" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # withscores表示带一些其他参数说明 1) "yunmx01" 2) "9000" 3) "hejie01" 4) "10000" 5) "hejie" 6) "11000" 7) "yunmx" 8) "12000" ### 移除元素 127.0.0.1:6379> ZRANGE salary 0 -1 # 查看所有的元素 1) "yunmx01" 2) "hejie01" 3) "hejie" 4) "yunmx" 127.0.0.1:6379> zrem salary yunmx01 # 移除特定的元素 (integer) 1 127.0.0.1:6379> ZRANGE salary 0 -1 1) "hejie01" 2) "hejie" 3) "yunmx" ### 获取有序集合中的个数 127.0.0.1:6379> ZRANGE salary 0 -1 1) "hejie01" 2) "hejie" 3) "yunmx" 127.0.0.1:6379> ZCARD salary (integer) 3 ### 按区间计算 127.0.0.1:6379> ZRANGE salary 0 -1 1) "hejie01" 2) "hejie" 3) "yunmx" 127.0.0.1:6379> zcount salary 1000 9000 # 查看1000到9000之间的数量 (integer) 0 127.0.0.1:6379> zcount salary 1000 10000 # 查看1000到1-000之间的数量值 (integer) 1
城市经纬度查询:http://www.jsons.cn/lngcode/
GEO实现的底层原理其实就是Zset!可以使用Zset来操作GEO!
只有6个命令
### 添加位置 # geoadd key longitude(经度) latitude(维度) member [longitude latitude member ...] 可添加多个 # 参数:key 值(经度 维度 名称) 127.0.0.1:6379> geoadd china:city 104.065735 30.659462 chengdu (integer) 1 127.0.0.1:6379> geoadd china:city 106.504962 29.533155 chongqing (integer) 1 ### 获取当前定位经纬度 127.0.0.1:6379> GEOPOS china:city chengdu # 获取成都的经纬度 1) 1) "104.06573742628097534" 2) "30.65946118872339099" 127.0.0.1:6379> GEOPOS china:city chongqing # 获取重庆的经纬度 1) 1) "106.50495976209640503" 2) "29.53315530684997015" ### 计算两个地方的距离 #单位:m/km/mi/ft 127.0.0.1:6379> geodist china:city chengdu chongqing m # 计算出成都到重庆的直线距离 单位m "266056.2971" 127.0.0.1:6379> geodist china:city chengdu chongqing km # 计算出成都到重庆的直线距离 单位km "266.0563" ### 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。 # GEORADIUS: 127.0.0.1:6379> GEORADIUS china:city 103 30 1 km # 以103 30这个点为中心,以1km为半径 (empty array) 127.0.0.1:6379> GEORADIUS china:city 103 30 2 km (empty array) 127.0.0.1:6379> GEORADIUS china:city 103 30 20 km (empty array) 127.0.0.1:6379> GEORADIUS china:city 103 30 200 km 1) "chengdu-qingyangqu" 2) "chengdu" 3) "chengdu-jinjiangqu" 127.0.0.1:6379> GEORADIUS china:city 103 30 20000 km 1) "chengdu-qingyangqu" 2) "chongqing" 3) "chengdu" 4) "chengdu-jinjiangqu" 127.0.0.1:6379> GEORADIUS china:city 103 30 20000 km withcoord # 附带显示经纬度 1) 1) "chengdu-qingyangqu" 2) 1) "104.06151026487350464" 2) "30.67387107851423167" 2) 1) "chongqing" 2) 1) "106.50495976209640503" 2) "29.53315530684997015" 3) 1) "chengdu" 2) 1) "104.06573742628097534" 2) "30.65946118872339099" 4) 1) "chengdu-jinjiangqu" 2) 1) "104.06573742628097534" 2) "30.65946118872339099" ### 找出位于指定范围内的元素,中心点是由给定的位置元素决定 # GEORADIUSBYMEMBER 127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 10 km # chengdu城市方圆10km的坐标 1) "chengdu" 2) "chengdu-jinjiangqu" 3) "chengdu-qingyangqu" 127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 100 km 1) "chengdu" 2) "chengdu-jinjiangqu" 3) "chengdu-qingyangqu" 127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 10 km 1) "chengdu" 2) "chengdu-jinjiangqu" 3) "chengdu-qingyangqu" 127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 1000 km 1) "chengdu-qingyangqu" 2) "chongqing" 3) "chengdu" 4) "chengdu-jinjiangqu" 127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 1000 km withcoord # 附带坐标信息 1) 1) "chengdu-qingyangqu" 2) 1) "104.06151026487350464" 2) "30.67387107851423167" 2) 1) "chongqing" 2) 1) "106.50495976209640503" 2) "29.53315530684997015" 3) 1) "chengdu" 2) 1) "104.06573742628097534" 2) "30.65946118872339099" 4) 1) "chengdu-jinjiangqu" 2) 1) "104.06573742628097534" 2) "30.65946118872339099" ### Redis GEOHASH 命令 - 返回一个或多个位置元素的 Geohash 表示 不常用
基数:不重复的元素个数,可以接受误差
A(1 2 3 45 5 78)
B(3 4 6 7 8 9 0 9 78 )
基数统计的算法
网页的UV(一个人访问一个网站,但是还是算做一个)
传统的方式:set保存用户的ID,然后就可以统计set中的元素数量作为标准判断,如果保存大量的用户ID,就会比较麻烦,我们的目的是为了计数而不是保存ID
占用内存是固定的,2^64不同的元素技术,只需要废12KB内存
0.81%的错误率!统计UV时候,可以忽略不计
127.0.0.1:6379> PFADD key1 a 1 2 3 4 b c 6 7 9 # 设置一个HyperLOGLOG (integer) 1 127.0.0.1:6379> PFCOUNT key1 # 查看里面不重复的数量个数 (integer) 10 127.0.0.1:6379> PFADD key2 a a 1 2 3 4 b c 6 7 9 10 10 101 10 10 8 6 (integer) 1 127.0.0.1:6379> PFCOUNT key2 (integer) 13 127.0.0.1:6379> PFMERGE key3 key1 key2 # 合并两个key的元素数量 OK 127.0.0.1:6379> PFCOUNT key3 # 查看不重复的元素数量 (integer) 13
位存储0和1
2个状态的都可以使用此数据类型
127.0.0.1:6379> SETBIT key 0 1 # 使用Bitmaps记录一周的打卡 (integer) 0 127.0.0.1:6379> SETBIT key 1 0 (integer) 0 127.0.0.1:6379> SETBIT key 2 0 (integer) 0 127.0.0.1:6379> SETBIT key 3 1 (integer) 0 ## 查看某一天是否有打卡 127.0.0.1:6379> getbit key 3 (integer) 1 127.0.0.1:6379> getbit key 2 (integer) 0 ### 统计操作 # 统计打开天数 127.0.0.1:6379> BITCOUNT key (integer) 2
事务可以一次执行多个命令, 并且带有以下两个重要的保证:
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,事务执行中,按照顺序执行
Redis单条命令保证原子性,但是事务不保证原子性!
---入队--- command1 command2 command2 ---执行---
三个阶段:
### 正常执行 127.0.0.1:6379> MULTI # 开启事务 OK 127.0.0.1:6379> set key1 1 # 命令入队 QUEUED 127.0.0.1:6379> set key2 2 QUEUED 127.0.0.1:6379> set key3 3 QUEUED 127.0.0.1:6379> get key1 QUEUED 127.0.0.1:6379> get key2 QUEUED 127.0.0.1:6379> exec # 事务执行,按照顺序执行 1) OK 2) OK 3) OK 4) "1" 5) "2" ### 放弃事务 127.0.0.1:6379> MULTI # 开启事务 OK 127.0.0.1:6379> set key1 1 QUEUED 127.0.0.1:6379> set key2 2 QUEUED 127.0.0.1:6379> set key3 3 QUEUED 127.0.0.1:6379> DISCARD # 取消事务 OK 127.0.0.1:6379> exec (error) ERR EXEC without MULTI 127.0.0.1:6379> get key1 # 放弃事务取不到相关的值 (nil) ### 事务错误一:命令有错,事务中所有的命令都不会被执行! 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set key1 1 QUEUED 127.0.0.1:6379> set key2 2 QUEUED 127.0.0.1:6379> getset key3 # 错误的命令,直接报错 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379> set key3 3 QUEUED 127.0.0.1:6379> exec (error) EXECABORT Transaction discarded because of previous errors. # 执行事务报错 127.0.0.1:6379> get key1 # 结果是所有的命令都不会执行 (nil) ### 事务错误二:运行异常,如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> set key1 "yunmx" QUEUED 127.0.0.1:6379> incr key1 QUEUED 127.0.0.1:6379> set key2 2 QUEUED 127.0.0.1:6379> set key3 3 QUEUED 127.0.0.1:6379> get key3 QUEUED 127.0.0.1:6379> exec 1) OK 2) (error) ERR value is not an integer or out of range # 代码结构没问题,抛出异常 3) OK 4) OK 5) "3" 127.0.0.1:6379> get key2 "2"
监控:watch
悲观锁:很悲观;认为任何时候都会出现问题,无论做什么都加锁,影响性能
乐观锁:很乐观,任务任何时候都不会出现问题,所以不会上锁,更新数据的时候去判断一下,在此期间,是否有人修改过这个数据,mysql用的version字段!
### Redis的测试监视 127.0.0.1:6379> set mymoney 1000 OK 127.0.0.1:6379> set myout 0 OK 127.0.0.1:6379> WATCH mymoney # 监视 OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> DECRby mymoney 20 QUEUED 127.0.0.1:6379> INCRBY myout 20 QUEUED 127.0.0.1:6379> exec # 事务正常结束,数据期间没有发生变动,正常执行了 1) (integer) 980 2) (integer) 20 127.0.0.1:6379> ### 使用watch当乐观锁,测试多线程修改值后事务提交失败 # 第一个进程开启事务 127.0.0.1:6379> watch mymoney OK 127.0.0.1:6379> MULTI OK 127.0.0.1:6379> DECRBY mymoney 10 QUEUED 127.0.0.1:6379> INCRBY myout 10 QUEUED 127.0.0.1:6379> exec # 未提交前执行第二个事务进行数据的修改 (nil) # 事务提交后返回修改失败, 127.0.0.1:6379> # 第二个进程修改我们的值 127.0.0.1:6379> get mymoney "980" 127.0.0.1:6379> set mymoney 20 OK 127.0.0.1:6379> get mymoney "20" 127.0.0.1:6379> set mymoney 1000 OK 127.0.0.1:6379> get mymoney "1000" ### 可以使用unwatch解锁,再次使用watch获取最新的值,再去执行事务
启动时候就是通过配置文件来启动的
配置文件对大小写不敏感
包含其他配置文件
网络
bind 127.0.0.1 # 绑定的ip protected-mode yes # 保护模式 port 6379 # 端口设置
GENERAL通用配置
daemonize yes # 已守护进程方式运行,默认是no,我们需要自己开启 pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要指定一个pid文件 # 日志(4个级别) # 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 # 是否总是显示logo 可以不显示,默认显示
快照SNAPSHOTTING
# 规则 save 900 1 # 如果900秒内,至少有一个key进行修改,我们就进行持久化操作 save 300 10 save 60 10000 # 我们可以设置自己的一个规则 stop-writes-on-bgsave-error yes # 持久化出现错误的时候,是否继续进行工作,默认是yes rdbcompression yes # 是否压缩rdb文件,压缩就会消耗CPU资源 rdbchecksum yes # 是否校验rdb文件,出错会进行修复 dir ./ # rdb文件保存的目录
快照
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件,.rdb .aof,如果没有持久化,那么数据就会断电及失
主从复制 REPLICATION
安全SECURITY
# requirepass foobared 默认没有密码 # 可以通过修改配置文件来设置密码 # 比如: requirepass 123456 # 验证 127.0.0.1:6379> ping (error) NOAUTH Authentication required. 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> ping PONG 127.0.0.1:6379> # 也可以通过命令进行密码设置 127.0.0.1:6379> config get requirepass 1) "requirepass" 2) "123456" 127.0.0.1:6379> config set requirepass "110" OK 127.0.0.1:6379> ping (error) NOAUTH Authentication required. 127.0.0.1:6379> auth 110 OK 127.0.0.1:6379> ping PONG 127.0.0.1:6379>
客户端CLIENTS
# 客户端的一些限制 maxclients 10000 # 设置能连接上redis的客户端数量限制 maxmemory <bytes> # redis配置的最大内存容量 maxmemory-policy noeviction # 内存到达上限后的处理策略 # 移除一些过期的key # 报错 # ... 1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 2、allkeys-lru : 删除lru算法的key 3、volatile-random:随机删除即将过期key 4、allkeys-random:随机删除 5、volatile-ttl : 删除即将过期的 6、noeviction : 永不过期,返回错误
APPEND ONLY MODE模式 aof配置
appendonly no # 默认不开启aof模式,默认使用的是rdb方式持久化的 appendfilename "appendonly.aof" # 持久化的文件的名字 # appendfsync always # 每次修改值都会写入,速度比较慢 appendfsync everysec # 每秒执行一次sync,可能会丢失1s的数据! # appendfsync no # 不同步,这个时候操作系统自己同步数据,速度最快!
Redis是内存数据库,如果不将内存中的数据库状态保存到硬盘中,那么一旦服务器进程退出,服务器中的数据状态也会消失,所以Redis提供了持久化功能
RDB
在指定的时间间隔内,将内存中的数据集体快照写入磁盘中,也就是进行Snapshot快照,它恢复时是将快照文件直接读到内存里。
操作
单独创建(fork)一个子进程来进行持久化,会将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程不进行任何IO操作。这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高校。RDB的缺点是嘴鸥一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改此配置!
rdb保存的文件是dump.rdb
# 触发机制 1.save的规则满足的情况下 2.执行了FLUSHALL命令 3.退出redis也会触发
如何恢复rdb文件
# 查看存放的位置 127.0.0.1:6379> config get dir 1) "dir" 2) "/usr/local/bin"
优缺点
优点
- 适合大规模的数据恢复
- 如果你对数据的完整性要求不高
缺点
- 需要一定的时间间隔进行操作!如果redis意外宕机,最后一次修改的数据就没有了
- fork进程的时候,会占用一定的内存空间
将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部执行一遍
AOF
以日志的形式来记录每个操作,将redis执行过的所有指令记录下来(读操作不会记录),只许追加文件但不可改写文件,redis启动之后会读取文件重新构建数据,换言之,redis重启的话根据日志文件的内容将写指令从前到 后执行一次以完成数据的恢复工作
aof保存的文件是appendonly.aof文件
# 破坏aof文件后,尝试连接 [root@yunmx bin]# redis-cli Could not connect to Redis at 127.0.0.1:6379: Connection refused # 使用检测文件自动修复这个AOF文件:redis-check-aof [root@yunmx bin]# redis-check-aof --fix appendonly.aof 0x 62: Expected \r\n, got: 6173 AOF analyzed: size=122, ok_up_to=23, diff=99 This will shrink the AOF from 122 bytes, with 99 bytes, to 23 bytes Continue? [y/N]: y Successfully truncated AOF
优缺点
优点
- 每次修改都同步,文件完整性更好,
- 每秒同步一次,这样会丢失一秒的数据
- 从不同步,效率最高的!
缺点
- 相对于数据文件来说,aof远远大于rdb,修复的速度比rdb慢
- aof运行效率要比rdb慢,会进行IO操作
扩展:
RBD
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储。
AOF
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。
只做缓存
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.。
同时开启两种持久化方式
性能建议
订阅、发布消息图:
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息
Redis客户端可以订阅任意数量的频道
第一个是:消息发送者 第二个是:频道 第三个是:消息订阅者
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
Redis 发布订阅命令
下表列出了 redis 发布订阅常用命令:
- PSUBSCRIBE pattern [pattern …]
订阅一个或多个符合给定模式的频道。- PUBSUB subcommand [argument [argument …]]
查看订阅与发布系统状态。- PUBLISH channel message
将信息发送到指定的频道。- PUNSUBSCRIBE [pattern [pattern …]]
退订所有给定模式的频道。- SUBSCRIBE channel [channel …]
订阅给定的一个或多个频道的信息。- UNSUBSCRIBE [channel [channel …]]
指退订给定的频道。
# 测试 # 订阅一个频道 127.0.0.1:6379> SUBSCRIBE yunmx # 订阅一个频道到yunmx Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "yunmx" 3) (integer) 1 # 新窗口发送一个消息:发布者 [root@yunmx ~]# redis-cli 127.0.0.1:6379> PUBLISH yunmx test # 发布者发布消息到指定名称的频道 (integer) 1 # 接收到的消息 127.0.0.1:6379> SUBSCRIBE yunmx Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "yunmx" 3) (integer) 1 1) "message" # 接收的消息 2) "yunmx" # 哪个频道 3) "test" # 消息内容
Redis发布订阅的原理
使用C实现的,通过分析Redis源码里面的pubsub.c文件,了解发布和订阅机制的底层实现逻辑,加深对redis的理解。(俺也不懂C,俺也不是开发,咋整)
通过PUBLISH/SUBSCRIBE/PSUBSCRIBE等命令实现发布订阅功能
- SUBSCRIBE:通过此命令订阅频道以后,redis-server里维护一个字典,字典的键就是一个个channel(频道),而字典的值则是一个链表,链表中保存了所有订阅这个频道的客户端。-----就是将客户端添加到给定channel的订阅链表中
- PUBLISH:向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中查询了记录了订阅这个频道所有的客户端连接,遍历整个链表,将消息发送给所有订阅者
- PUB、SUB:就是发布、订阅,在redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值进行了消息发布以后,所有订阅它的客户端都会收到相应的消息,这一功能明显的用法就是用作实时消息系统,比如普通的即时聊天、群聊等功能
主从复制
是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。master以写为主,slave以读为主。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
一般来说,要将redis运用于生产项目中,只使用一台redis是万万不能的
主从复制,读写分离!80%的情况都是在进行读操作!减缓服务器的压力,架构中经常使用!一主二 从!
测试场景
环境配置
只配置从库,不配置主库!
# 启动一个redis,查看信息 127.0.0.1:6379> info replication # Replication role:master # 角色 connected_slaves:0 # 从机数量 master_replid:fa0e795a3e369ed7e46a40ee8818a51255ab6df3 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
复制一个配置文件,修改一下端口号、日志保存的文件名、rdb文件名、pid文件 ,在启动2个redis
[root@yunmx bin]# redis-server redis-conf/redis.conf1 # 6380 6038:C 12 Dec 2021 13:06:38.166 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 6038:C 12 Dec 2021 13:06:38.166 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=6038, just started 6038:C 12 Dec 2021 13:06:38.166 # Configuration loaded [root@yunmx bin]# redis-server redis-conf/redis.conf2 # 6381 6045:C 12 Dec 2021 13:06:39.640 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 6045:C 12 Dec 2021 13:06:39.641 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=6045, just started 6045:C 12 Dec 2021 13:06:39.641 # Configuration loaded
未配置主从之前,三个节点都是主节点
认老大
一主(79)二从(80,81)
# 6380配置 127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 # 认本机的6379端口服务的redis做老大 OK 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:8 master_sync_in_progress:0 slave_repl_offset:0 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:55c3146a93d180ad5aa89e4d64ff894a451e77e5 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 # 查看主机的信息 127.0.0.1:6379> info replication # Replication role:master connected_slaves:2 slave0:ip=127.0.0.1,port=6380,state=online,offset=168,lag=0 # 从机的信息 slave1:ip=127.0.0.1,port=6381,state=online,offset=168,lag=0 master_replid:55c3146a93d180ad5aa89e4d64ff894a451e77e5 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:168 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:168
真实的主从配置应该在配置文件中配置,这样的话是永久的,上述使用的是命令,只是暂时的
replicaof <masterip> <masterport> # 主机的IP地址和端口号 masterauth <master-password> # 如果设定有密码,配置密码即可
主机可以设置值,从机不能写,主机中所有信息和数据,从机都会保存
# 主机设置一个key 127.0.0.1:6379> set key1 yunmx OK 127.0.0.1:6379> # 从机中也会有,从机无法设置key 127.0.0.1:6380> keys * 1) "key1" 127.0.0.1:6380> get key1 "yunmx" 127.0.0.1:6380> set key2 yunmx2 (error) READONLY You can't write against a read only replica. 127.0.0.1:6380>
老大宕机后,从机还是从机,只是会显示主机状态不正常;主机恢复,从机依旧可以直接获取到主机写入的信息,保证了一定的高可用性
如果使用的是我们命令行配置的主从,如果从机宕机后,从机就会脱离主从了,需要再次命令行配置从机,变成从机以后,就能获取到key的值了
# 停掉6379主机服务,查看从机集群状态 127.0.0.1:6380> info replication # Replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:down master_last_io_seconds_ago:-1 master_sync_in_progress:0 slave_repl_offset:1065 master_link_down_since_seconds:10 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:55c3146a93d180ad5aa89e4d64ff894a451e77e5 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:1065 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:1065 # 恢复6379主机服务,验证是否还会同步数据 [root@yunmx ~]# redis-cli -p 6379 127.0.0.1:6379> set key2 yunmx2 # 恢复主节点,设置一个key OK 127.0.0.1:6379> # 从节点读取主机恢复后设置的key 127.0.0.1:6380> get key2 # 能够正常读取 "yunmx2" # 停止从机服务,主机设定一个key,在恢复从机,进行验证 127.0.0.1:6379> set key3 yunmx3 OK 127.0.0.1:6380> get key3 # 从机无法获得key3的数据 (nil) 127.0.0.1:6381> get key3 "yunmx3"
Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令后,确定后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步
全量复制:slave服务将接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:Mster继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要重新连接master,一次完全同步将被自动执行!我们的数据一定可以在从机中看到!
上一个M链接下一个S!
可以完成主从复制!
如果没有79,这个时候能不能选择一个老大出来呢,这个时候需要手动去配置!
谋朝篡位:slaveof no one
使自己变成主机!如果老大回来了,也是需要手动配置
自动版选老大的模式
主从切换技术的方式是:当主服务器宕机后,需要手动把一台服务器切换为主服务,这就需要人工干预,费时费力,还会造成一段时间内服务不能使用。这不是一种推荐的方式,更多的是我们考虑哨兵模式,Redis从2.8开始正式提供Sentinel(哨兵)架构来解决这个问题。
能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,是一个独立的进程,作为进程,它会独立运行。原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
哨兵模式的作用:
一个哨兵进程对redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样就形成了多哨兵模式
使用哨兵模式,至少都会启动6个进程
假设主服务宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务不可用,这个现场叫主观下线。当后面的哨兵也检测到主服务不可用,并且数量达到一定值后,那么哨兵就会进行一次投票,投票的结果由一个哨兵发起,进行failover故障转移操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务实现切换主机,这个过程被称为客观下线
我们目前测试的架构是一主二从
配置哨兵模式的配置文件
# 新建配置文件并编辑以下内容 sentinel monitor myredis 127.0.0.1 6379 1# 语法:sentinel monitor 被监控的名称 host port 1(1代表主机宕机后,从机投票让谁来接替成为主机,)
启动哨兵
[root@yunmx bin]# redis-sentinel redis-conf/sentinel.conf # 启动一个哨兵 12680:X 12 Dec 2021 15:13:42.570 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo 12680:X 12 Dec 2021 15:13:42.570 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=12680, just started 12680:X 12 Dec 2021 15:13:42.570 # Configuration loaded _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 6.0.6 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in sentinel mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 26379 | `-._ `._ / _.-' | PID: 12680 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-' 12680:X 12 Dec 2021 15:13:42.571 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128. 12680:X 12 Dec 2021 15:13:42.575 # Sentinel ID is ea7ccf0119a4cf2873cf3bb108da5c7af86d36bd 12680:X 12 Dec 2021 15:13:42.575 # +monitor master myredis 127.0.0.1 6379 quorum 1 12680:X 12 Dec 2021 15:13:42.575 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:13:42.580 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
手动宕机测试
# 关掉主机 127.0.0.1:6379> SHUTDOWN not connected> # 哨兵监控的一些信息 12680:X 12 Dec 2021 15:16:49.174 # +failover-state-select-slave master myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:16:49.241 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:16:49.241 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:16:49.324 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:16:50.181 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:16:50.181 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:16:50.237 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:16:51.182 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:16:51.182 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:16:51.234 # +failover-end master myredis 127.0.0.1 6379 12680:X 12 Dec 2021 15:16:51.234 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6381 # 哨兵显示主机自动切换到了6381 12680:X 12 Dec 2021 15:16:51.234 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381 12680:X 12 Dec 2021 15:16:51.234 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381 12680:X 12 Dec 2021 15:17:21.244 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381 # 检查6381主机的信息 127.0.0.1:6381> info replication # Replication role:master # 变成了master connected_slaves:1 slave0:ip=127.0.0.1,port=6380,state=online,offset=21944,lag=0 master_replid:a4719b795f6c088ed1a11408a2bc52cc48ece215 master_replid2:367d9493cb151b433bd535ad9e49603d1fa35013 master_repl_offset:21944 second_repl_offset:11812 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:21944
如果master宕机以后,这个时候就会从从机中随机选择一个服务器(有一个自己的投票算法)作为主机
主机恢复
# 重新开启之前宕机的主机。观察哨兵的反应 12680:X 12 Dec 2021 15:23:41.461 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381 # 查看先前主机的信息 127.0.0.1:6379> info replication # Replication role:slave # 变成从机 master_host:127.0.0.1 master_port:6381 master_link_status:up master_last_io_seconds_ago:1 master_sync_in_progress:0 slave_repl_offset:41645 slave_priority:100 slave_read_only:1 connected_slaves:0 master_replid:a4719b795f6c088ed1a11408a2bc52cc48ece215 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:41645 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:39205 repl_backlog_histlen:2441 # 只能归并到新的主机下,当作从机,这就是哨兵模式的规则!
优缺点
哨兵集群,基于主从复制模式,所有的主从配置优点它多有
主从可以切换,故障可以转移,系统的可用性会更好
就是主从模式的升级,手动到自动,更加健壮!
不好在线扩容,集群容量一旦达到上线,在线扩容就十分麻烦!
实现哨兵模式的配置其实很麻烦的,里面有很多选择!
哨兵模式的全部配置
<后续进行详细学习>
# Example sentinel.conf # port <sentinel-port> # The port that this sentinel instance will run on # sentinel实例运行的端口 port 26379 # 哨兵进程运行的端口号 # sentinel announce-ip <ip> # sentinel announce-port <port> # # The above two configuration directives are useful in environments where, # because of NAT, Sentinel is reachable from outside via a non-local address. # # When announce-ip is provided, the Sentinel will claim the specified IP address # in HELLO messages used to gossip its presence, instead of auto-detecting the # local address as it usually does. # # Similarly when announce-port is provided and is valid and non-zero, Sentinel # will announce the specified TCP port. # # The two options don't need to be used together, if only announce-ip is # provided, the Sentinel will announce the specified IP and the server port # as specified by the "port" option. If only announce-port is provided, the # Sentinel will announce the auto-detected local IP and the specified port. # # Example: # # sentinel announce-ip 1.2.3.4 # dir <working-directory> # Every long running process should have a well-defined working directory. # For Redis Sentinel to chdir to /tmp at startup is the simplest thing # for the process to don't interferer with administrative tasks such as # unmounting filesystems. dir /tmp # sentinel monitor <master-name> <ip> <redis-port> <quorum> # master-name : master Redis Server名称 # ip : master Redis Server的IP地址 # redis-port : master Redis Server的端口号 # quorum : 主实例判断为失效至少需要 quorum 个 Sentinel 进程的同意,只要同意 Sentinel 的数量不达标,自动failover就不会执行 # # Tells Sentinel to monitor this master, and to consider it in O_DOWN # (Objectively Down) state only if at least <quorum> sentinels agree. # # Note that whatever is the ODOWN quorum, a Sentinel will require to # be elected by the majority of the known Sentinels in order to # start a failover, so no failover can be performed in minority. # # Slaves are auto-discovered, so you don't need to specify slaves in # any way. Sentinel itself will rewrite this configuration file adding # the slaves using additional configuration options. # Also note that the configuration file is rewritten when a # slave is promoted to master. # # Note: master name should not include special characters or spaces. # The valid charset is A-z 0-9 and the three characters ".-_". # sentinel monitor mymaster 127.0.0.1 6379 2 # sentinel auth-pass <master-name> <password> # # Set the password to use to authenticate with the master and slaves. # Useful if there is a password set in the Redis instances to monitor. # # Note that the master password is also used for slaves, so it is not # possible to set a different password in masters and slaves instances # if you want to be able to monitor these instances with Sentinel. # # However you can have Redis instances without the authentication enabled # mixed with Redis instances requiring the authentication (as long as the # password set is the same for all the instances requiring the password) as # the AUTH command will have no effect in Redis instances with authentication # switched off. # # Example: # # sentinel auth-pass mymaster MySUPER--secret-0123passw0rd # sentinel down-after-milliseconds <master-name> <milliseconds> # # Number of milliseconds the master (or any attached slave or sentinel) should # be unreachable (as in, not acceptable reply to PING, continuously, for the # specified period) in order to consider it in S_DOWN state (Subjectively # Down). # 选项指定了 Sentinel 认为Redis实例已经失效所需的毫秒数。当实例超过该时间没有返回PING,或者直接返回错误, 那么 Sentinel 将这个实例标记为主观下线(subjectively down,简称 SDOWN ) # # Default is 30 seconds. sentinel down-after-milliseconds mymaster 30000 # sentinel parallel-syncs <master-name> <numslaves> # # How many slaves we can reconfigure to point to the new slave simultaneously # during the failover. Use a low number if you use the slaves to serve query # to avoid that all the slaves will be unreachable at about the same # time while performing the synchronization with the master. # 选项指定了在执行故障转移时, 最多可以有多少个从Redis实例在同步新的主实例, 在从Redis实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长。 sentinel parallel-syncs mymaster 1 # sentinel failover-timeout <master-name> <milliseconds> # # Specifies the failover timeout in milliseconds. It is used in many ways: # # - The time needed to re-start a failover after a previous failover was # already tried against the same master by a given Sentinel, is two # times the failover timeout. # # - The time needed for a slave replicating to a wrong master according # to a Sentinel current configuration, to be forced to replicate # with the right master, is exactly the failover timeout (counting since # the moment a Sentinel detected the misconfiguration). # # - The time needed to cancel a failover that is already in progress but # did not produced any configuration change (SLAVEOF NO ONE yet not # acknowledged by the promoted slave). # # - The maximum time a failover in progress waits for all the slaves to be # reconfigured as slaves of the new master. However even after this time # the slaves will be reconfigured by the Sentinels anyway, but not with # the exact parallel-syncs progression as specified. # 如果在该时间(ms)内未能完成failover操作,则认为该failover失败 # # Default is 3 minutes. sentinel failover-timeout mymaster 180000 # SCRIPTS EXECUTION # # sentinel notification-script and sentinel reconfig-script are used in order # to configure scripts that are called to notify the system administrator # or to reconfigure clients after a failover. The scripts are executed # with the following rules for error handling: # # If script exits with "1" the execution is retried later (up to a maximum # number of times currently set to 10). # # If script exits with "2" (or an higher value) the script execution is # not retried. # # If script terminates because it receives a signal the behavior is the same # as exit code 1. # # A script has a maximum running time of 60 seconds. After this limit is # reached the script is terminated with a SIGKILL and the execution retried. # NOTIFICATION SCRIPT # # sentinel notification-script <master-name> <script-path> # # Call the specified notification script for any sentinel event that is # generated in the WARNING level (for instance -sdown, -odown, and so forth). # This script should notify the system administrator via email, SMS, or any # other messaging system, that there is something wrong with the monitored # Redis systems. # # The script is called with just two arguments: the first is the event type # and the second the event description. # # The script must exist and be executable in order for sentinel to start if # this option is provided. # 指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,但是很常用。 # # Example: # # sentinel notification-script mymaster /var/redis/notify.sh # CLIENTS RECONFIGURATION SCRIPT # # sentinel client-reconfig-script <master-name> <script-path> # # When the master changed because of a failover a script can be called in # order to perform application-specific tasks to notify the clients that the # configuration has changed and the master is at a different address. # # The following arguments are passed to the script: # # <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port> # # <state> is currently always "failover" # <role> is either "leader" or "observer" # # The arguments from-ip, from-port, to-ip, to-port are used to communicate # the old address of the master and the new address of the elected slave # (now a master). # # This script should be resistant to multiple invocations. # # Example: # # sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
服务的高可用问题!
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时也会带来一些问题,其中,最要害的就是数据的一致性问题,从严格意义上来讲,这个问题无解,如果对数据一致性要求较高的,那么就不能使用缓存。还有一些典型的问题就是:缓存穿透、缓存雪崩、缓存击穿。目前,业界也都有比较流行的解决方案。
查不到导致的
概念
用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
在这里插入图片描述
解决方案
是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
当存储层不命中时候,即使返回的空对象也将其存储起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端数据源
缓存空对象的2个问题:
量太大,缓存过期!
概念
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
缓存击穿是指一个key非常热点,在不停的扛着大并发, 大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就会穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞
当某个key在过期的瞬间,会大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大
解决方案
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
分布式锁:保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的 权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因为对分布式锁的考验很大
概念
是指某一个时间段,缓存集中过期失效,redis宕机
解决方案
redis集群,保证服务的高可用(异地多活)
在缓存失效后,通过加锁、队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程查看数据和写缓存,其他线程等待
在正式部署之前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀