Redis学习笔记

文章目录

Redis

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。

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

它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

NoSQL

Redis:nosql(非关系型数据库)。

NoSQL演变史参考:https://www.cnblogs.com/lukelook/p/11135209.html

NoSQL 概述
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,
泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。

(例如谷歌或Facebook每天为他们的用户收集万亿比特的数据)。这些类型的数据存储不需要固定的模式,无需多余操作就可以横向扩展。

MySQL的扩展性瓶颈:MySQL数据库也经常存储一些大文本字段,导致数据库表非常的大,在做数据库恢复的时候就导致非常的慢,不容易快速恢复数据库。比如1000万4KB大小的文本就接近40GB的大小,如果能把这些数据从MySQL省去,MySQL将变得非常的小。关系数据库很强大,但是它并不能很好的应付所有的应用场景。MySQL的扩展性差(需要复杂的技术来实现),大数据下IO压力大,表结构更改困难,正是当前使用MySQL的开发人员面临的问题。

NoSQL代表:MongDB、 Redis、Memcache

NoSQL特点:解耦!

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

  2. 大数据量高性能(redis一秒读11完次,一秒写8完次,NoSQL是缓存记录级。是一种细粒度的缓存,性能比较高)

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

  4. 传统的RDBMS和NoSQL

    传统的RDBMS
    -结构化组织
    -SQL
    -数据和关系都存在单独的表中
    -操作数据,数据定义语言
    -严格的一致性
    -...
    
    NoSQL
    -不仅仅是数据
    -没有固定的查询语言
    -键值对存储,列存储,文档存储,图数据库
    -最终一致性
    -CAP定理和BASE理论(异地多活)
    -高性能,高可用,高扩展
    -...
    

NoSQL的四大分类

KV键值对:

  • 新浪:Redis
  • 美团:Redis+Tair
  • 阿里,百度:Redis + memcache

文档型数据库(bson格式 和json一样)

  • MongoDB
    • MongoDB是一个基于分布式文件存储的数据库,C++编写,主要用于处理大量的文档。
    • MongoDB是一个基于关系型数据库和非关系型数据库中间的产品。MongoDB是非关系型数据库中最丰富,最像关系型数据库的。
  • ConthDB

列存储数据库

  • HBase
  • 分布式文件系统

图关系数据库

  • 放的是关系,不是图,比如朋友圈社交网络
  • Neo4j,infoGrid
分类 Examples举例 典型应用场景 数据模型 优点 缺点
键值(key-value)[3] Tokyo Cabinet/Tyrant, Redis, Voldemort, Oracle BDB 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等。[3] Key 指向 Value 的键值对,通常用hash table来实现[3] 查找速度快 数据无结构化,通常只被当作字符串或者二进制数据[3]
列存储数据库[3] Cassandra, HBase, Riak 分布式的文件系统 以列簇式存储,将同一列数据存在一起 查找速度快,可扩展性强,更容易进行分布式扩展 功能相对局限
文档型数据库[3] CouchDB, MongoDb Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) Key-Value对应的键值对,Value为结构化数据 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 查询性能不高,而且缺乏统一的查询语法。
图形(Graph)数据库[3] Neo4J, InfoGrid, Infinite Graph 社交网络,推荐系统等。专注于构建关系图谱 图结构 利用图结构相关算法。比如最短路径寻址,N度关系查找等 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群方案。[3]

Redis入门

概述

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

redis与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

免费,开源,是当下最热门的NoSQL技术之一,也被称之为结构化数据库。

Redis能干什么:

  1. 内存存储,持久化,内存中是断电即失的,所以持久化非常重要(RDB,AOF)。
  2. 效率高,可以用于高速缓存。
  3. 发布订阅系统。
  4. 地图信息分析。
  5. 计时器,计数器(浏览量)。

特点:

  1. 支持多种语言
  2. 持久化
  3. 集群
  4. 事务

Redis中文网:https://www.redis.net.cn/

Windows安装

  1. 下载安装包(github上)
  2. 解压压缩包
  3. 开启Redis,直接双击服务运行即可
  4. 使用Redis客户端来连接服务端(ping 测试连接,存值:set name zr,取值:get name)

Windows下使用非常简单,但是Redis推荐我们使用Redis去开发使用。

Linux安装

  1. 下载安装包(官网下载)

  2. 解压Redis的安装包到指定包

  3. 进入解压后的文件,可以看到Redis的配置文件(redis.conf)

  4. 环境安装

    [root@zhourui redis-6.0.9]# yum install gcc-c++
    
    [root@zhourui redis-6.0.9]# make
    
    make install
    

    再次make

Redis学习笔记

make install

Redis学习笔记

  1. Redis的默认安装路径:/usr/local/bin

Redis学习笔记

  1. 将redis的配置文件(redis.conf)复制到当前目录下

    [root@zhourui bin]# mkdir zconfig
    
    [root@zhourui bin]# cp /www/server/redis/redis-6.0.9/redis.conf zconfig/
    
    
  2. redis默认不是后台启动的,修改配置文件将 no 改为 yes

    Redis学习笔记

  3. 启动Redis服务,通过指定的配置文件启动服务

    [root@zhourui bin]# redis-server zconfig/redis.conf 
    
    
  4. 启动客户端redis-cli

    [root@zhourui bin]# redis-cli -p 6379
    127.0.0.1:6379> ping  #测试连接
    PONG
    127.0.0.1:6379> set name zr  #存值
    OK
    127.0.0.1:6379> get name  #取值
    "zr"
    127.0.0.1:6379> keys *  #查看所有的key
    1) "name"
    127.0.0.1:6379> 
    
    
  5. 查看redis的进程是否开启

    Redis学习笔记

  6. 关闭redis服务,在连接的客户端输入 shutdown

    Redis学习笔记

  7. 再次查看进程是否存在,ps -ef|grep redis

  8. 单机多redis修改端口即可

性能测试

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

官方自带的性能测试工具。

redis-benchmark 命令参数!

redis 性能测试工具可选参数如下所示:

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

测试:启动redis后新开一个窗口,执行

# 测试:100个并发连接,100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

Redis学习笔记

Redis学习笔记

基础的知识

redis默认有16个数据库(从 redis.conf 中可以看到)。

默认使用的是第 0 个。

可以使用 select 进行切换。

flushdb:清空当前数据库,flushall:清空所有的数据库,dbsize :数据库的大小,keys * :查看所有的key。

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 zhou
OK
127.0.0.1:6379[3]> dbsize  #数据库的大小
(integer) 1
127.0.0.1:6379[3]> select 6
OK
127.0.0.1:6379[6]> dbsize
(integer) 0
127.0.0.1:6379[6]> get name
(nil)
127.0.0.1:6379[6]> select 3
OK
127.0.0.1:6379[3]> get name
"zhou"
127.0.0.1:6379[3]> flushdb  #清空数据库
OK
127.0.0.1:6379[3]> get name
(nil)
127.0.0.1:6379> flushall  #清空所有的数据库
OK
127.0.0.1:6379[3]> 

Redis是单线程的!

官方表示,Redis是基于内存操作的,CPU不是Redis的性能瓶颈,Redis的性能瓶颈是根据机器的内存和网络的带宽,既然可以使用单线程来实现,就使用单线程了。

Redis是C语言写的,官方提供的数据100000+的QPS,这个完全不比同样是使用 key-value 的Memcache差!

Redis是单线程为什么还这么快?

误区1:认为高性能的服务器一定是多线程的.

误区2:多线程(cpu会上下文切换)一定比单线程效率高。

核心:Redis是将所有的数据放在内存中的,所以使用单线程去操作效率就是最高的,多线程(cpu会上下文切换,耗时的操作),对于内存系统来说没有上下文的切换效率就是最高的,多次读写都是在一个CPU上的,在内存情况下,这个就是最佳方案。

五大数据类型

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库缓存消息中间件。 它支持多种类型的数据结构,如 字符串(strings)散列(hashes)列表(lists)集合(sets)有序集合(sorted sets) 与范围查询, bitmapshyperloglogs地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication)LUA脚本(Lua scripting)LRU驱动事件(LRU eviction)事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

Redis-Key

127.0.0.1:6379> exists name  #判断键是否存在
(integer) 1
127.0.0.1:6379> move name 1  #移除1数据库的name键
(integer) 1
127.0.0.1:6379> set name zzrr
OK
127.0.0.1:6379> keys *  #查看所有的key
1) "name"
2) "age"
127.0.0.1:6379> expire name 10  #设置key过期时间  单位是秒
(integer) 1
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> ttl name  #查看当前key的剩余时间
(integer) 6
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> type name  #查看当前key的类型
string
127.0.0.1:6379> type age
string
127.0.0.1:6379> 

String(字符串)

###############################################################################
127.0.0.1:6379> set name zr  #设置值
OK
127.0.0.1:6379> get name
"zr"
127.0.0.1:6379> keys *  #查看所有的key
1) "name"
127.0.0.1:6379> append name "hello"  #追加字符串,如果当前的key不存在,就相当于 set key
(integer) 7
127.0.0.1:6379> get name
"zrhello"
127.0.0.1:6379> strlen name  #查看字符串的长度
(integer) 7
127.0.0.1:6379> 

###############################################################################
# 增量
127.0.0.1:6379> set views 0  #初始值为0
OK
127.0.0.1:6379> get views  
"0"
127.0.0.1:6379> incr views  #自增1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views  
"2"
127.0.0.1:6379> 
127.0.0.1:6379> decr views  #自减1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incrby views 10  #设置步长,指定增量
(integer) 11
127.0.0.1:6379> decrby views 10  #设置步长,指定减量
(integer) 1
127.0.0.1:6379> 

###############################################################################
# 字符串范围 range
127.0.0.1:6379> flushdb  #清空数据库
OK
127.0.0.1:6379> set name zhour  #设置值
OK
127.0.0.1:6379> get name  #获取值
"zhour"
127.0.0.1:6379> getrange name 0 3  #截取字符串 [0,3]
"zhou"
127.0.0.1:6379> getrange name 0 -1  #获得全部的字符串,和getkey是一样的
"zhour"
127.0.0.1:6379> 

# 替换
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx  #替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"

###############################################################################
# setex (set with expire)  设置过期时间
# setnx (set if not exist) 不存在再设置  在分布式锁中常使用

127.0.0.1:6379> setex key3 30 "hello"   #设置key3的值为hello,30秒后过期
OK
127.0.0.1:6379> ttl key3  #查看还有多长时间过期
(integer) 25
127.0.0.1:6379> setnx key4 "redis"  #如果key4不存在,就创建key4
(integer) 1
127.0.0.1:6379> setnx key4 "MongoDB"  #如果key4存在,就创建失败
(integer) 0
127.0.0.1:6379> get key4
"redis"
127.0.0.1:6379> 

###############################################################################
# mset
# mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3  #同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "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
127.0.0.1:6379> get k4
(nil)

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

# user:{id}:{filed},这样设计在redis中是可以的
127.0.0.1:6379> mset user:1:name zhour user:1:age 3
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhour"
2) "3"
127.0.0.1:6379> 

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

String 类型的使用场景:value除了是字符串还可以是数字。

  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储

List(列表)

基本的数据类型,列表。

在redis中,可以把list玩出成,栈,队列,阻塞队列。

所有的 list 命令都是以 l 开头的。Redis不区分大小写命令。

###############################################################################
# lpush
127.0.0.1:6379> lpush list one  #将一个值或者多个值插入到列表的头部(左)
(integer) 1
127.0.0.1:6379> lpush list two  
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1  #获取list的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1  #获取区间内具体的值
1) "three"
2) "two"
127.0.0.1:6379> rpush list right  #将一个值或者多个值插入到列表的尾部(右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> 

###############################################################################
# lpop
# rpop
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list  #移除列表的第一个元素
"three"
127.0.0.1:6379> rpop list  #移除列表的最后一个元素
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> 

###############################################################################
# lindex
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> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> llen list  #获取列表的长度
(integer) 2
127.0.0.1:6379> 

###############################################################################
# lrem 移除指定的值
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "four"
3) "three"
4) "two"
5) "one"
127.0.0.1:6379> lrem list 1 one  #移除list集合中指定个数的value,精确匹配
(integer) 1
127.0.0.1:6379> lrem list 1 four
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "four"
2) "three"
3) "two"
127.0.0.1:6379> lpush list four
(integer) 4
127.0.0.1:6379> lrem list 2 four  #移除list中的两个four
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"

###############################################################################
# ltrim 袖箭
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist hello1
(integer) 2
127.0.0.1:6379> rpush mylist hello12
(integer) 3
127.0.0.1:6379> rpush mylist hello13
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2  #通过下标截取指定的长度,只剩下截取的元素
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
2) "hello12"
127.0.0.1:6379> 

###############################################################################
# rpoplpush  移除列表的最后一个元素,并将它添加到一个新的列表中
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist hello1
(integer) 2
127.0.0.1:6379> rpush mylist hello2
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist  #移除列表的最后一个元素,并将它添加到一个新的列表中
"hello2"
127.0.0.1:6379> lrange mylist 0 -1  #查看原来的列表
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1  #查看新的列表
1) "hello2"
127.0.0.1:6379> 

###############################################################################
# lset  将列表中指定下标的值替换为另外一个值
127.0.0.1:6379> exists list  #判断这个列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item  #如果不存在列表,去更新就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset list 0 item  #如果存在,会更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other  #不存在这个下标,会报错
(error) ERR index out of range

###############################################################################
# linsert  将某个具体的value插入到某个元素的前面或者后面
127.0.0.1:6379> rpush mylist hello
(integer) 1
127.0.0.1:6379> rpush mylist world
(integer) 2
127.0.0.1:6379> linsert mylist before world other
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> linsert mylist after  world new
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"

list:实际上是一个链表,before Node after,left,right都可以插入值。

  • 如果key不存在,创建新的列表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,代表不存在
  • 在两边插入或者改动值效率最高,中间元素,相对来说,效率低一点

消息队列(Lpush,Rpop),栈(Lpush,Lpop)。

Set(集合)

set中的值是不能重复的!

###############################################################################
# 存值,取值
127.0.0.1:6379> sadd myset hello  #set中存值
(integer) 1
127.0.0.1:6379> sadd myset hellozr
(integer) 1
127.0.0.1:6379> sadd myset hellozhou
(integer) 1
127.0.0.1:6379> smembers myset  #查看指定set的所有值
1) "hellozr"
2) "hello"
3) "hellozhou"
127.0.0.1:6379> sismember myset hello  #判断某一个元素是否在set中,存在返回 1
(integer) 1
127.0.0.1:6379> sismember myset world  #不存在这个元素会返回 0
(integer) 0
127.0.0.1:6379> 

###############################################################################
127.0.0.1:6379> scard myset  #获取set中元素的个数
(integer) 3

###############################################################################
# srem
127.0.0.1:6379> srem myset hello  # 移除set中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 2
127.0.0.1:6379> smembers myset  #查看set中的元素
1) "hellozr"
2) "hellozhou"
127.0.0.1:6379> 

###############################################################################
# set 无序不重复集合 
127.0.0.1:6379> sadd myset zhourr
(integer) 1
127.0.0.1:6379> smembers myset
1) "zhourr"
2) "hellozr"
3) "hellozhou"
127.0.0.1:6379> srandmember myset  #随机抽选出一个元素
"hellozr"
127.0.0.1:6379> srandmember myset
"hellozhou"
127.0.0.1:6379> srandmember myset
"hellozr"
127.0.0.1:6379> srandmember myset
"hellozr"
127.0.0.1:6379> 

###############################################################################
# 删除指定的key  随机删除key
127.0.0.1:6379> smembers myset
1) "zhourr"
2) "hellozr"
3) "hellozhou"
127.0.0.1:6379> spop myset  #随机移除元素
"hellozr"
127.0.0.1:6379> spop myset
"hellozhou"
127.0.0.1:6379> smembers myset
1) "zhourr"
127.0.0.1:6379> 

###############################################################################
# 将一个指定的值移动到另外一个set集合中
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset world
(integer) 1
127.0.0.1:6379> sadd myset zhour
(integer) 1
127.0.0.1:6379> sadd myset2 set2
(integer) 1
127.0.0.1:6379> smove myset myset2 zhour   # 将一个指定的值移动到另外一个set集合中
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "hello"
2) "world"
127.0.0.1:6379> SMEMBERS myset2
1) "zhour"
2) "set2"
127.0.0.1:6379> 

###############################################################################
# 共同关注(交集)
# SDIFF 差集
# SINTER 交集
# SUNION 并集
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> SDIFF key1 key2  # 差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2  # 交集 共同好友
1) "c"
127.0.0.1:6379> SUNION key1 key2  # 并集
1) "a"
2) "b"
3) "c"
4) "e"
5) "d"
127.0.0.1:6379> 

可以将 A 的所有关注放在一个集合中,将它的粉丝放在另一个集合中!

共同好友,共同爱好,二度好友,推荐好友!(六度分割理论)

Hash(哈希)

Map集合,key-map集合,value变成了map集合!本质和String类型没有太大的区别,还是一个简单的key-value。

127.0.0.1:6379> hset myhash field1 zhourr  # set一个具体的 key value
(integer) 1
127.0.0.1:6379> hget myhash field1  # 获取一个字段值
"zhourr"
127.0.0.1:6379> hmset myhash field1 hello field2 world  # set 多个 key value
OK
127.0.0.1:6379> hmget myhash field1 field2  # 获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash  # 获取全部的数据 key value
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> 

###############################################################################

127.0.0.1:6379> hdel myhash field1  # 删除hash指定的key字段,对应的value值也被删除
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
127.0.0.1:6379> 

###############################################################################
# hlen 
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> hlen myhash  # 获取hash 表的字段数量
(integer) 2
127.0.0.1:6379> 

###############################################################################
# 判断 hash 中指定的key是否存在
127.0.0.1:6379> HEXISTS myhash field1  #判断 hash 中指定的key是否存在
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0

###############################################################################
#只获得所有的field
#只获得所有的value
127.0.0.1:6379> hkeys myhash  #只获得所有的field
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myhash  #只获得所有的value
1) "world"
2) "hello"
127.0.0.1:6379> 

###############################################################################
# hincrby
127.0.0.1:6379> hset myhash field3 5  # 指定增量
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 2  
(integer) 7
127.0.0.1:6379> HINCRBY myhash field3 -3
(integer) 4
127.0.0.1:6379> hsetnx myhash field4 hello  # 如果不存在,则可以设置
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 world  # 如果存在,则不可用设置
(integer) 0
127.0.0.1:6379> 

hash 变更的数据 user name age ,尤其是用户信息的保存,或经常变动的信息。hash更适合对象的存储,string更适合字符串的存储。

Zset(有序集合)

在set的基础上增加了一个值,set k1 v1 , zset k1 score1 v1.

###############################################################################
127.0.0.1:6379> zadd myset 1 one  #添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three  # 添加多个值
(integer) 2
127.0.0.1:6379> zrange myset 0 -1  # 获取所有的值
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> 

###############################################################################
# 排序
127.0.0.1:6379> zadd salary 2500 xiaohong  # 添加三个用户
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 4000 zhour
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf  #显示全部用户,从负无穷到正无穷排序
1) "xiaohong"
2) "zhour"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1  # 从大到小排序
1) "zhangsan"
2) "zhour"
3) "xiaohong"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores  # 从小到大显示,并附带成绩
1) "xiaohong"
2) "2500"
3) "zhour"
4) "4000"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 4000 withscores  # 4000以下的员工的排序
1) "xiaohong"
2) "2500"
3) "zhour"
4) "4000"

127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf  # 从大到小排序
1) "zhangsan"
2) "zhour"
3) "xiaohong"

###############################################################################
# 移除元素
127.0.0.1:6379> zrange salary 0 -1
1) "xiaohong"
2) "zhour"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong  # 移除元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "zhour"
2) "zhangsan"
127.0.0.1:6379> zcard salary  # 获取有序集合中的个数
(integer) 2
127.0.0.1:6379> 


###############################################################################
# 获取指定区间的成员数量
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 zhour
(integer) 2
127.0.0.1:6379> zcount myset 1 3  # 获取指定区间的成员数量
(integer) 3


set 排序 :班级成绩表,工资表,带权重判断数据重要性,排行榜取top N。

其它更多操作,查看官方文档!!!

三种特殊数据类型

Geospatial(地理位置)

朋友的位置,附件的人,打车距离计算。

Redis的geo在3.2版本就推出了!这个功能可以推算地理位置信息,两地之间的距离,附近的人。

在线地理位置信息:http://www.jsons.cn/lngcode/

相关命令

geoadd:

# 添加地理位置
# 两级无法直接添加,我们一般会下载城市数据,通过java程序一次性导入!
# 将指定的地理空间位置(经度、纬度、名称)添加到指定的key中
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqin 114.05 22.52 shengzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.34 hangzhou 108.96 34.26 xian
(integer) 2
127.0.0.1:6379> 

  • 有效的经度从-180度到180度。
  • 有效的纬度从-85.05112878度到85.05112878度。

当坐标位置超出上述指定范围时,该命令将会返回一个错误。

**geopos:**获得当前的定位,一定是一个坐标值

# 获取指定城市的经度和纬度
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing chongqin
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"
127.0.0.1:6379> 

deodist:

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

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

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

  • m 表示单位为米。
  • km 表示单位为千米。
  • mi 表示单位为英里。
  • ft 表示单位为英尺。

如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。

127.0.0.1:6379> geodist china:city beijing shanghai  # 北京到上海的直线距离 单位 米
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km  # 北京到上海的直线距离 单位 千米
"1067.3788"
127.0.0.1:6379> geodist china:city beijing chongqin km  # 北京到重庆的直线距离 单位 千米
"1464.0708"
127.0.0.1:6379> 

**georadius:**以给定的经度纬度为中心,找出某一半径内的元素。

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

127.0.0.1:6379> georadius china:city 110 30 1000 km  # 获取以110,30为中心,1000km为半径内的城市
1) "chongqin"
2) "xian"
3) "shengzheng"
4) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 500 km  # 获取以110,30为中心,500km为半径内的城市
1) "chongqin"
2) "xian"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist  # 显示到中心点的距离
1) 1) "chongqin"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord  # 显示范围内城市的位置信息
1) 1) "chongqin"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 1  # 筛选出1个结果
1) 1) "chongqin"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> 

GEORADIUSBYMEMBER:

127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km  #指定位置范围内的其它位置
1) "beijing"
2) "xian"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379> 

**geohash:**返回一个或多个位置元素的 Geohash 表示。

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

# 将二维的经纬度转化为一维的字符串(如果两个字符串越接近则距离越接近)
127.0.0.1:6379> geohash china:city beijing chongqin
1) "wx4fbxxfke0"
2) "wm5xzrybty0"

geo的底层实现原理其实是 zset!我们可以使用zset来操作geo。

127.0.0.1:6379> zrange china:city 0 -1  #查看全部的元素
1) "chongqin"
2) "xian"
3) "shengzheng"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city chongqin  #删除元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "xian"
2) "shengzheng"
3) "hangzhou"
4) "shanghai"
5) "beijing"
127.0.0.1:6379> 

Hyperloglog

基数:不重复元素的个数。

Redis在2.8.9版本就更新了hyperloglog数据结构。

Redis hyperloglog 基数统计的算法。

优点:占用的内存是固定的,2^64不同元素的基数,只需要12kb的内存!从内存的角度来比较的话,hyperloglog是首选。

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

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

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

0.81%的错误率,统计UA任务,可以忽略不计的。

127.0.0.1:6379> pfadd myket a b c d e f g h i j  # 存一组值
(integer) 1
127.0.0.1:6379> pfcount myket  # 统计一组元素的基数数量
(integer) 10
127.0.0.1:6379> pfadd myket2 i j z x c v b n m
(integer) 1
127.0.0.1:6379> pfcount myket2
(integer) 9
127.0.0.1:6379> pfmerge mykey3 myket myket2  # 合并两组 并集
OK
127.0.0.1:6379> pfcount mykey3  # 查看合并后的数量
(integer) 15
127.0.0.1:6379> 

如果允许容错,就可以使用hyperloglog。

Bitmap

位存储。

统计用户信息,活跃,不活跃!登录,未登录 !打卡!两个状态的都可以使用Bitmap。

Bitmap 位图,数据结构!都是操作二进制位来记录,就只有 0 和 1 两个状态。

# 使用 bitmaps 记录周一到周日的打卡,1为打卡,0为未打卡
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 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
127.0.0.1:6379> 

查看某一天是否有打卡

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

统计打卡的天数

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

事务

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

Redis事务没有隔离级别的概念,所有的命令在事务中并没有被直接执行,只有发起执行命令的时候才会被执行。

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

一次性,顺序性,排它性!执行一系列的命令。

==========队列 set set set 执行=================

Redis 事务:

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

正常执行事务!

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> 

放弃事务!

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 k4 v4
QUEUED
127.0.0.1:6379> DISCARD  # 取消事务  事务队列中的命令都不会被执行
OK
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> 


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

127.0.0.1:6379> multi
OK
127.0.0.1:6379> 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> get k5
(nil)
127.0.0.1:6379> 

运行时异常!(如果事务队列中存在语法性的错误,那么执行的时候,其它命令是可以正常执行的,错误命令会抛出异常)这里的事务就无法保证原子性!!!

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> 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> get k3
QUEUED
127.0.0.1:6379> exec  # 第一条命令报错了,但是其它的命令执行成功了
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> 

监控!Watch

乐观锁:很乐观,认为什么时候都不会出问题,所以无论做什么都不会加锁。更新数据的时候去判断一下,在此期间是否有人修改过这个数据,version。获取version,更新的时候比较version。

悲观锁:很悲观,认为什么时候都会出问题,所以无论做什么都加锁。

Redis的监视测试:

# 正常执行成功
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money   # 监视money
OK
127.0.0.1:6379> multi  # 事务正常结束,数据期间没有发生变动
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379> 

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

127.0.0.1:6379> watch money  # 监视money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10 
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec  # 执行之前,另外一个线程修改了money值,就会导致事务执行失败
(nil)
127.0.0.1:6379> 

如果事务执行失败,就先解锁 unwatch ,再watch money,再开启事务执行后面的操作。

Jedis

使用Java来操作redis。

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

测试:

  1. 导入对应的依赖
<!--    导入 jedis的包-->
    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>

    <!--    fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.74</version>
        </dependency>
    </dependencies>
  1. 编码测试

    • 连接数据库

    • 操作命令

    • 断开连接

      package com.zr;
      
      import redis.clients.jedis.Jedis;
      
      public class TestPing {
          public static void main(String[] args) {
              // new jedis对象
              Jedis jedis = new Jedis("39.105.48.232",6379);
              System.out.println(jedis.ping());
          }
      }
      

输出:

Redis学习笔记

常用API

package com.zr;

import redis.clients.jedis.Jedis;

import java.util.Set;

public class TestKey {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);

        System.out.println("清空数据:"+jedis.flushDB());
        System.out.println("判断某个键是否存在:"+jedis.exists("username"));
        System.out.println("新增键值对:"+jedis.set("username","zr"));
        System.out.println("新增键值对:"+jedis.set("password","813794474"));
        System.out.println("系统中的键如下:");
        Set<String> keys = jedis.keys("*");
        System.out.println(keys);

        System.out.println("删除键"+jedis.del("password"));
        System.out.println("判断password键是否存在:"+jedis.exists("password"));
        System.out.println("判断username的类型:"+jedis.type("username"));
        System.out.println("随机返回:"+jedis.randomKey());
        System.out.println("随机返回:"+jedis.rename("username","name"));
        System.out.println("取出值:"+jedis.get("name"));
        System.out.println("索引查询:"+jedis.select(0));
        System.out.println("删除当前数据库:"+jedis.flushDB());
        System.out.println("返回当前数据库的key数量:"+jedis.dbSize());
        System.out.println("删除所有:"+jedis.flushAll());

    }
}

String

package com.zr;

import redis.clients.jedis.Jedis;

import java.util.Set;
import java.util.concurrent.TimeUnit;

public class TestString {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);

        System.out.println("清空数据:"+jedis.flushDB());

        System.out.println("=======增加数据========");
        System.out.println(jedis.set("k1","v1"));
        System.out.println(jedis.set("k2","v2"));
        System.out.println(jedis.set("k3","v3"));
        System.out.println("删除:"+jedis.del("k2"));
        System.out.println("取值:"+jedis.get("k2"));
        System.out.println("修改k1:"+jedis.set("k1","v111"));
        System.out.println("k3后增加:"+jedis.append("k3","zhour"));
        System.out.println("k3:"+jedis.get("k3"));
        System.out.println("增加多个:"+jedis.mset("k4","v4","k5","v5","k6","v6"));
        System.out.println("获取多个:"+jedis.mget("k4","k5","k6"));

        jedis.flushDB();
        System.out.println("=======新增键值防止覆盖======");
        System.out.println(jedis.setnx("k1","v1"));
        System.out.println(jedis.setnx("k2","v2"));
        System.out.println(jedis.setnx("k3","v3"));

        System.out.println("=======新增键并设置有效时间======");
        System.out.println(jedis.setex("k3",6,"v3"));
        System.out.println(jedis.get("k3"));
        try{
            TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(jedis.get("k3"));

        System.out.println("=======获取原值,更新为新值=======");
        System.out.println(jedis.getSet("k2","k222gdsg"));
        System.out.println(jedis.get("k2"));
        System.out.println("截取:"+jedis.getrange("k2",2,5));
    }
}

list

package com.zr;

import redis.clients.jedis.Jedis;

public class TestList {
    public static void main(String[] args) {

        Jedis jedis = new Jedis("127.0.0.1",6379);
        jedis.flushDB();
        System.out.println("=========增加list=====");
        jedis.lpush("collection","Aeeaylist","Vector","Stack","HashMap","WeakHashMap","LinkHashMap");
        jedis.lpush("collection","HashSet");
        jedis.lpush("collection","TreeSet");
        System.out.println("collection:"+jedis.lrange("collection",0,-1));
        System.out.println("collection中0-3区间:"+jedis.lrange("collection",0,3));
        System.out.println("=========================");
        System.out.println("删除:"+jedis.lrem("collection",2,"HashMap"));
        System.out.println("collection:"+jedis.lrange("collection",0,-1));
        System.out.println("删除指定区间:"+jedis.ltrim("collection",0,2));
        System.out.println("collection:"+jedis.lrange("collection",0,-1));

        System.out.println("出栈 左端"+jedis.lpop("collection"));
        System.out.println("出栈 右端"+jedis.rpop("collection"));
        System.out.println("右端添加元素"+jedis.rpush("collection","right"));
        System.out.println("左端添加元素"+jedis.lpush("collection","left"));
        System.out.println("修改指定下标:"+jedis.lset("collection",1,"LinkHashMap"));
        System.out.println("========长度==========");
        System.out.println("collection:"+jedis.lrange("collection",0,-1));
        System.out.println(jedis.llen("collection"));

    }
}

Set,Hash,Zset例子参考 五大基本数据类型!!!!!

事务!

package com.zr;

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestTX {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);

        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("name","zhour");
        //开启事务
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();
        //jedis.watch(result);

        try {
            multi.set("user1",result);
            multi.set("user2",result);
            //int i = 1/0;  //代码异常,执行失败
            multi.exec();  //执行事务
        } catch (Exception e) {
            multi.discard();  //放弃事务
            e.printStackTrace();
        }finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();  //关闭连接
        }
    }
}

SpringBoot整合

SpringBoot 操作数据:spring-data :jpa jdbc redis等!

SpringData也是和SpringBoot齐名的项目。

说明:在spring2.X之后,jedis被替换为了 lettuce!

jedis:采用的是直连,多个线程操作的话,是不安全的,如果想避免不安全,就要使用jedis pool连接池!更像 BIO 模式

lettuce:采用netty,实例可以在多个线程*享,不存在线程不安全的情况!更像 NIO 模式

源码解析:

@Bean
@ConditionalOnMissingBean(
    name = {"redisTemplate"}  //不存在才生效,我们可以自己定义一个来替换这个默认的
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    //默认的RedisTemplate没有过多的设置,redis对象都是需要序列化的
    //两个泛型都是Object的类型,后面使用需要强制转换
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

@Bean
@ConditionalOnMissingBean  //由于String是redis中最常使用的一个方法,所以单独提出来了一个bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

整合测试

  1. 导入依赖

    <!--操作redis-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  2. 配置连接

    # springboot的所有配置类,都有一个自动配置类
    # 自动配置类都会绑定一个 properties的配置文件 RedisAutoConfiguration
    
    #配置redis
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    spring.red
    
  3. 测试

    package com.zr;
    
    @SpringBootTest
    class Redis02SpringbootApplicationTests {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Test
        void contextLoads() {
            //opsForValue 操作字符串的。类似string
            //opsForList  操作list
            //opsForSet
            //opsForHash
            //opsForZSet
            //opsForGeo
    
            //除了基本的操作,其它的方法都可以使用redisTemplat来操作,比如事务,和基本的CRUD
    
            //获取redis的连接对象
            // RedisConnection connection= redisTemplate.getConnectionFactory().getConnection();
            // connection.flushDb();
            // connection.flushAll();
    
            redisTemplate.opsForValue().set("mykey","zhour");
            System.out.println(redisTemplate.opsForValue().get("mykey"));
        }
    }
    

序列化配置(再点进去可以看到默认的是jdk序列化,我们可以使用json序列化)

Redis学习笔记

测试:

User

package com.zr.config.pojo;

@Component
@Data
@AllArgsConstructor
@NoArgsConstructor
//在企业开发中,所有的pojo都会序列化
public class User implements Serializable {
    private String name;
    private Integer age;
}
@Test
void test() throws JsonProcessingException {
    //真实开发一般使用json来传递对象
    User user = new User("周周", 8);
    // String jsonUser = new ObjectMapper().writeValueAsString(user);
    // redisTemplate.opsForValue().set("user",jsonUser);

    redisTemplate.opsForValue().set("user",user);  //这里直接传递对象,会报错,需要将对象序列化
    System.out.println(redisTemplate.opsForValue().get("user"));
}

编写自己的RedisTemplate

package com.zr.config;

@Configuration
public class RedisConfig {
    //编写我们自己的redisTemplate
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String,Object>();
        template.setConnectionFactory(factory);
        //json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //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;
    }
}

测试

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

@Test
void test() throws JsonProcessingException {
    //真实开发一般使用json来传递对象
    User user = new User("周周", 8);
    // String jsonUser = new ObjectMapper().writeValueAsString(user);
    // redisTemplate.opsForValue().set("user",jsonUser);

    redisTemplate.opsForValue().set("user",user);  //这里直接传递对象,会报错,需要将对象序列化
    System.out.println(redisTemplate.opsForValue().get("user"));
}

可以将redis的所有操作封装成一个工具类,类似以前的jdbcUtils。

所有的redis操作十分简单,重要的是我们要理解redis的思和每一种数据结构的具体应用场景!!

Redis.conf详解

启动的时候,就是通过配置文件来启动的!

Redis学习笔记

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

包含:include,可以包含其它配置文件

Redis学习笔记

网络:NETWORK

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

通用:GENERAL

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

# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)  #生产环境使用
# warning (only very important / critical messages are logged)
loglevel notice

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

databases 16 # 默认有16个数据库

always-show-logo yes  # 是否总是显示log

快照

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

redis是内存数据库,如果没有持久化,数据则断电即失。

# 900秒内,如果至少有一个key进行了修改,我们就进行持久化操作
save 900 1
# 300秒内,如果至少有10个key进行了修改,我们就进行持久化操作
save 300 10
# 60秒内,如果至少有10000个key进行了修改,我们就进行持久化操作
save 60 10000
# 可自己定义设置

stop-writes-on-bgsave-error yes  # 持久化出错了是否还需要继续工作
rdbcompression yes  # 是否压缩rdb文件,需要消耗一些cpu资源
rdbchecksum yes  # 保存rdb文件的时候,进行错误校验
dir ./  # rdb文件保存的目录

REPLICATION:复制,与主从复制相关,主从复制中解释。

SECURITY:安全

可以在这里设置redis的密码,默认是没有密码的,

config get requirepass  # 获取密码
config set requirepass "123456"  # 设置密码, config set requirepass "" 不设置密码

config get requirepass  # 此时显示没有权限

auth 123456  # 密码登录

config get requirepass  # 现在可以获取到设置的密码

限制:CLIENTS

# maxclients 10000  # 设置能连接上redis客户端的最大连接数量
# maxmemory <bytes>  # 最大内存容量
# maxmemory-policy noeviction  # 内存达到上限之后的处理策略
redis.conf中的默认的过期策略是 volatile-lru

maxmemory-policy 六种方式
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值) 
2、allkeys-lru : 删除lru算法的key   
3、volatile-random:随机删除即将过期key   
4、allkeys-random:随机删除   
5、volatile-ttl : 删除即将过期的   
6、noeviction : 永不过期,返回错误

APPEND ONLY MODEl:aof配置

appendonly no  # 默认是不开启aof模式的,默认是使用rdb的方式持久化的,在大部分情况下,rdb完全够用
appendfilename "appendonly.aof"  # 持久化的文件的名字

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

Redis持久化

Redis是内存数据库,如果不将内存中的数据库持久化到磁盘中,那么一旦服务终止,数据就会消失,所以Redis提供了持久化的功能!

RDB(Redis DataBase)

在指定的时间间隔内,将内存中的数据集体写入磁盘。也就是Snapshot快照,恢复时将快照文件直接读到内存中。

Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入一个临时文件中,待持久化过程都结束,再用这个临时文件替换上次持久化好的文件,整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能。如果需要进行大规模的数据恢复,且对恢复数据的完整性不是非常敏感,则RDB的方式比AOF的方式更加高效。RDB的缺点是最后一次持久的数据可能丢失。我们默认的就是RDB,一般不需要更改。

一般生产环境会将dump.rdb文件备份!!主从复制中,rdb备用,在从机上面。

rdb保存的文件是:dump.rdb,在配置文件快照中进行配置的。

Redis学习笔记

触发机制(生成dump.rdb)

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

备份就会自动生成一个dump.rdb。

如何恢复rdb文件

只需要将dump.rdb文件放在redis的启动目录下就可以,redis启动的时候会自动检查dump.rdb文件。

查看需要存放的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"  # 如果在这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据

优点:

  1. 适合大规模的数据恢复!dump.rdb
  2. 对数据线完整性的要求不高

缺点:

  1. 需要一定的时间间隔进行操作!意外宕机,最后一次的数据会丢失。
  2. fork进程的时候,会占用一定的进程空间!

AOF(Append Only File)

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

以日志的形式去记录每一个写操作,将redis执行的所有命令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动会读取该文件重构数据库。也就是,redis启动会根据日志文件的内容将写指令从前到后执行一次以完成数据恢复的工作。

aof保存的是 appendonly.aof文件。

Redis学习笔记

默认是不开启的,使用需要手动配置!只需要将appendonly no改为yes开启即可!

重启redis就可以生效了!

如果 appendonly.aof 文件有错误,redis是启动不起来的,我们需要修复这个aof文件。

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

修复成功后,重启即可!

重写规则

默认的是文件的无限追加,会越来越大。

Redis学习笔记

如果aof文件大于64mb,就会fork一个进程来将文件进行重写。

优点和缺点:

appendonly no  # 默认是不开启aof模式的,默认是使用rdb的方式持久化的,在大部分情况下,rdb完全够用
appendfilename "appendonly.aof"  # 持久化的文件的名字

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

优点:

  1. 每一次修改都同步,文件的完整性会更好!
  2. 默认的是每秒同步一次,可能会丢失一秒的数据
  3. 从不同步,效率是最高的

缺点:

  1. 相对于数据文件来说,aof远大于rdb,修复的速度也比rdb慢
  2. aof的运行效率也要比rdb慢,所以redis的默认配置是使用rdb持久化

Redis发布订阅

Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。公众号,微博等关注系统!

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

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系

Redis学习笔记

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

Redis学习笔记

Redis 发布订阅命令

下表列出了 redis 发布订阅常用命令:

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

测试:

订阅端

127.0.0.1:6379> SUBSCRIBE zhour  # 订阅一个频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "zhour"
3) (integer) 1
# 等待接收推送的信息
1) "message"  #消息
2) "zhour"    #哪个频道的消息
3) "hello,zhour" #消息的具体内容

1) "message"
2) "zhour"
3) "hello,redis"

发送端

127.0.0.1:6379> PUBLISH zhour "hello,zhour"  # 发布消息到频道
(integer) 1
127.0.0.1:6379> PUBLISH zhour "hello,redis"  # 发布消息到频道
(integer) 1
127.0.0.1:6379> 

Redis是使用C实现的,通过分析redis源码里的pubsub.c文件,了解发布和订阅机制的底层实现,可加深对redis的理解。

通过subscribe命令订阅某频道后,redis-server中维护了一个字典,字典的键就是一个个频道,而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端,subscribe命令的关键,就是将客户端添加到给的的channel的订阅链表中。

通过publish命令向订阅者发送消息,redis-server会使用指定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历所有的链表,将消息发给所有的订阅者。

Pub/Sub从字面来理解就是发布(publish)与订阅(subscribe),在redis中,可以设定对某一个key值进行消息发布及消息订阅,当一个key上进行了消息发布后,所有订阅它的客户端都会收到相应的信息,这一功能的最明显的用法就是实时消息系统,即时聊天,群聊等功能。

稍微复杂的场景可以使用消息中间件MQ来做。

Redis主从复制

主从复制,是指将一台redis服务器上的数据,复制到其它redis服务器上,前者称为主节点(master/leader),后者称为从节点(slave/follower);数据复制是单向的,只能从主节点到从节点,Master以写为主,Slave以读为主。

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

主从复制的作用主要包括:

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

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

  1. 从结构上,单个redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大。
  2. 从容量上,单个redis服务器内存容量有限,就算一套服务器内存容量256g,也不能将所有的内存用作redis的内存,一般来说,单台redis最大使用内存不超过20g。

电商网站上的商品,一般都是一次上传,无数次浏览的,也就是”多读少写“。

环境配置

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

127.0.0.1:6379> info replication  #查看当前库的信息
# Replication
role:master  # 角色
connected_slaves:0  # 美哟从机
master_replid:df9e0065e32fd82d44fc257454f2101cecd2aa10
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> 

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

  1. 端口号
  2. pid 名字
  3. 日志名字
  4. dump.rdb 名字

修改完后启动三个redis服务,查看进程信息

Redis学习笔记

一主二从

默认情况下,每台redis服务器都是主节点,我们只用配置从机。

认老大!一主(79)二从(80,81)

从机中进行配置 SLAVEOF (80,81都这样配置)

127.0.0.1:6380> SLAVEOF 127.0.0.1 6379  # SLAVEOF host  6379 
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:4
master_sync_in_progress:0
slave_repl_offset:0
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:d07d76bbf57965ec5eb6dfa2696c7fb50a1dab70
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:6380> 

在主机中查看

127.0.0.1:6379> info replication
# Replication
role:master # 主机
connected_slaves:2  #连个从机
slave0:ip=127.0.0.1,port=6380,state=online,offset=294,lag=0  # 从机的信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=294,lag=0  # 从机的信息
master_replid:d07d76bbf57965ec5eb6dfa2696c7fb50a1dab70
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:294
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:294
127.0.0.1:6379> 

真实的主从配置应该在配置文件中配置,才会是永久的,这里使用的是命令,就是暂时的。

# 配置文件中配置
# replicaof <masterip> <masterport>

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

主机

127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> 

从机

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.
127.0.0.1:6380> 

测试:主机断开连接,从机依旧是连接主机的,但是没有写操作,这个时候,如果主机回来了,从机依然可以获取主机写进去的信息。

如果是使用命令行配置的从机,重启后就会变回主机。只要变为从机,就会立即从主机中获取值!

复制原理

Slave启动成功连接到Master后会发送一个sync同步命令。

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

全量复制:slave在接收到数据后,将其存盘并加载到内存中。

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

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

层层链路

6379—6380—6381,三个节点依次连接,此时6380依旧是从节点。

这时候也可以完成主从复制!

这种情况下,如果6379挂掉,就需要手动配置一个主节点,使用 slaveof no one 使自己成为主节点。其它节点就可以手动连接到这个主节点(手动)。如果6379恢复了,就需要重新连接配置。

哨兵模式

自动选举老大的模式!

主从切换技术的方法是:当主服务器宕机后,需要手动把一台服务器切换为主服务器,这就需要人工干预,费时费力,而且还会造成一段时间的服务不可用,这种方式在实际中不太可取,所以,就有了哨兵模式,Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。

手动设置主节点的自动版, 能够监控后台主机是否故障,如果故障了可以根据投票数选举一个从节点变为主节点。

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

一个哨兵对服务监控可能会出现问题,对此,我们可以使用多个哨兵进行监控,各个哨兵之间也会互相监控,这样就会形成多哨兵的模式。

Redis学习笔记

哨兵进程的工作方式

  1. 每个Sentinel(哨兵)进程以每秒钟一次的频率向整个集群中的Master主服务器,Slave从服务器以及其他Sentinel(哨兵)进程发送一个 PING 命令。
  2. 如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值,则这个实例会被 Sentinel(哨兵)进程标记为主观下线SDOWN)。
  3. 如果一个Master主服务器被标记为主观下线(SDOWN),则正在监视这个Master主服务器的所有Sentinel(哨兵)进程要以每秒一次的频率确认Master主服务器的确进入了主观下线状态
  4. 有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认Master主服务器进入了主观下线状态(SDOWN), 则Master主服务器会被标记为客观下线(ODOWN)
  5. 在一般情况下, 每个Sentinel(哨兵)进程会以每 10 秒一次的频率向集群中的所有Master主服务器、Slave从服务器发送 INFO 命令。
  6. 当Master主服务器被 Sentinel(哨兵)进程标记为**客观下线(ODOWN)**时,Sentinel(哨兵)进程向下线的 Master主服务器的所有 Slave从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。
  7. 若没有足够数量的 Sentinel(哨兵)进程同意 Master主服务器下线, Master主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel(哨兵)进程发送 PING 命令返回有效回复,Master主服务器的主观下线状态就会被移除。

测试:

我们目前的模式是一主二从。

配置失败配置文件

# sentinel monitor 被监控的名称(名称自己设定)  host port  达到多少票数后开始选举(即客观下线)
sentinel monitor myredis 127.0.0.1 6379 1

启动哨兵

[root@zhourui bin]# redis-sentinel zconfig/sentinel.conf 
1971005:X 06 Jan 2021 20:41:13.713 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1971005:X 06 Jan 2021 20:41:13.713 # Redis version=6.0.9, bits=64, commit=00000000, modified=0, pid=1971005, just started
1971005:X 06 Jan 2021 20:41:13.713 # Configuration loaded
                _._                                                  
           _.-``__ ''-._                                             
      _.-``    `.  `_.  ''-._           Redis 6.0.9 (00000000/0) 64 bit
  .-`` .-```.  ```\/    _.,_ ''-._                                   
 (    '      ,       .-`  | `,    )     Running in sentinel mode
 |`-._`-...-` __...-.``-._|'` _.-'|     Port: 26379
 |    `-._   `._    /     _.-'    |     PID: 1971005
  `-._    `-._  `-./  _.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |           http://redis.io        
  `-._    `-._`-.__.-'_.-'    _.-'                                   
 |`-._`-._    `-.__.-'    _.-'_.-'|                                  
 |    `-._`-._        _.-'_.-'    |                                  
  `-._    `-._`-.__.-'_.-'    _.-'                                   
      `-._    `-.__.-'    _.-'                                       
          `-._        _.-'                                           
              `-.__.-'                                               

1971005:X 06 Jan 2021 20:41:13.714 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
1971005:X 06 Jan 2021 20:41:13.717 # Sentinel ID is bc602d0d0bcb457a46c117f1b87970ec13b67f73
1971005:X 06 Jan 2021 20:41:13.717 # +monitor master myredis 127.0.0.1 6379 quorum 1
1971005:X 06 Jan 2021 20:41:13.717 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:41:13.719 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379

# 主机挂掉后重新选取主机的日志,新选举的主节点是 6381
1971005:X 06 Jan 2021 20:44:12.650 # +sdown master myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:12.650 # +odown master myredis 127.0.0.1 6379 #quorum 1/1
1971005:X 06 Jan 2021 20:44:12.650 # +new-epoch 1
1971005:X 06 Jan 2021 20:44:12.650 # +try-failover master myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:12.653 # +vote-for-leader bc602d0d0bcb457a46c117f1b87970ec13b67f73 1
1971005:X 06 Jan 2021 20:44:12.653 # +elected-leader master myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:12.653 # +failover-state-select-slave master myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:12.719 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:12.719 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:12.785 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:13.054 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:13.054 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:13.110 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:14.082 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:14.082 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:14.153 # +failover-end master myredis 127.0.0.1 6379
1971005:X 06 Jan 2021 20:44:14.153 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6381
1971005:X 06 Jan 2021 20:44:14.154 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381
1971005:X 06 Jan 2021 20:44:14.154 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
1971005:X 06 Jan 2021 20:44:44.157 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381

如果主机此时回来了,只能归并到新的主机下当作从机,这就是哨兵模式的规则!

哨兵模式

优点:

  1. 哨兵模式,基于主从复制模式,所有的主从配置的优点,它都有
  2. 主从可以切换,故障可以转移,系统的可用性就会更好
  3. 哨兵模式就是主从模式的升级,从手动到自动,更加健壮

缺点:

  1. Redis不好在线扩容,集群容量一旦达到上线,在线扩容就十分困难
  2. 哨兵模式的配置比较繁琐,里面由很多的配置

哨兵模式的全部配置:

# 哨兵sentinel实例的端口  默认 26379 哨兵集群需配置每个哨兵的端口
port 26379

# 哨兵的工作目录
dir /tmp

#哨兵sentinel监控 redis 主节点的 ip port
#master-name 自己命名主节点的名字
#quorum 配置多少个sentinel统一认为master节点失联,就客观认为主节点失联了
#sentinel moniter <host> <port> <quorum>
sentinel monitor 127.0.0.1 6379 1

#当redis中开启了requirepass foobared授权密码 这样所有连接redis实例的客户端都需要提供密码
#设置哨兵连接主从的密码 注意必须为主从设置一样的密码
#sentinel auth-pass <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

#设置多少秒之后,主节点没有应答哨兵,此时,哨兵主观认为主节点下线 默认是30秒
#sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000

#这个配置指定了在发生failover主设备切换时最对哦可以有多少个slave同时对新的master进行同步
这个数字越小,完成failover的时间越长
但是如果这个数字越大,就意味着,有很多的slave因为replication而不可用
可将这个值设为1,可保证每次只有一个slave处于不能处理命令请求的状态
#sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1

#故障转移超时时间,默认3分钟
#sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000

#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件给管理员
#sentinel notification-script <master-name> <script-name>
sentinal notification-script mymaster /var/redis/notify.sh

#客户端重新配置主节点参数脚本
#当一个master因为failover而发生改变时,这个脚本会被调用,通知相关客户端关于master地址已经发生改变的信息
#sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh


Redis缓存穿透和雪崩

服务的高可用问题!

Redis缓存的使用,极大地提高了应用程序的效率和性能,特别是数据查询方面,但同时,它也带来了一些问题,其中,最紧要的问题,就是数据一致性的问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。

另外的一些典型问题就是,缓存穿透,缓存雪崩和缓存击穿,目前,业界都有比较流行的解决方案。

缓存穿透(查不到)

缓存穿透:用户想要查询一个数据,发现redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库中查找,发现也没有,于是查询失败。当用户很多的时候,缓存都没有命中(秒杀),于是都去请求了持久层数据库,这会给持久层数据库造成很大的压力,也就是出现了缓存穿透!

解决方案:

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

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

但是这种方法会存在以下问题:

如果空值能被缓存起来,这就意味着缓存需要更多的空间来存储空值的键。

即使对空值设置了过期时间,还是会存在缓存层和存储层的数据有一段时间不一致,这对需要保证一致性的业务会有影响。

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

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

当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力增大。

解决方案:

设置热点数据永不过期:

从缓存层面来看,没有设置过期时间,所以不会出现热点key过期产生的问题。

加互斥锁:

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

缓存雪崩

缓存雪崩:是指在某一个时间段,缓存集中过期失效,redis宕机。

比如,双十一时,将一批商品集中放入了缓存,假设缓存一小时,这批商品的缓存过期,那么对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力峰波,于是所有的请求都会到达存储层,存储层的调用量会暴增,造成数据库挂掉的情况。

其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网,因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的,无非是对数据产生周期性的压力而已,而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间把数据库压垮。

解决方案

redis高可用

这个思想的含义是,既然redis有可能挂掉,那就多设几台redis,这样一台挂掉后其它的还可以继续工作,其实就是搭建集群。(异地多活)

限流降级

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

数据预热

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

上一篇:创建admin用户


下一篇:Node.js是一个事件驱动I/O服务端JavaScript环境