【建议收藏】Redis超详细入门教程大杂烩

179 篇文章 1 订阅
165 篇文章 3 订阅

写在前边

  • Redis入门的整合篇。本篇也算是把2021年redis留下来的坑填上去,重新整合了一翻,点击这里,回顾我的2020与2021~一名大二后台练习生

NoSQL

NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。
NoSQL 不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。

  • 不遵循SQL标准。
  • 不支持ACID。
  • 远超于SQL的性能。

适用场景

  • 对数据高并发的读写
  • 海量数据的读写
  • 对数据高可扩展性的

不适用场景

  • 需要事务支持
  • 基于sql的结构化查询存储,处理复杂的关系,需要即席查询。
  • (用不着sql的和用了sql也不行的情况,请考虑用NoSql)

概述

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

  • redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

用途

1、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量!)

特性

1、多样的数据类型
2、持久化
3、集群
4、事务

历史发展

一开始数据量很少,只需要单表处理读和写

90年代,一个基本的网站访问量一般不会太大,单个数据库完全足够!
那个时候,更多的去使用静态网页 Html ~ 服务器根本没有太大的压力!
思考一下,这种情况下:整个网站的瓶颈是什么?
1、数据量如果太大、一个机器放不下了!
2、数据的索引 (B+ Tree),一个机器内存也放不下
3、访问量(读写混合),一个服务器承受不了

Memcached(缓存)+ MySQL + 垂直拆分 (读写分离)

可以一台服务器负责写,如何同步给前台服务器去读?

网站80%的情况都是在读,每次都要去查询数据库的话就十分的麻烦!所以说我们希望减轻数据的压力,我们可以使用缓存来保证效率!

分库分表,MySQL集群 + 水平拆分

早些年MyISAM: 表锁,十分影响效率!高并发下就会出现严重的锁问题
转战Innodb:行锁
慢慢的就开始使用分库分表来解决写的压力! MySQL 在哪个年代推出了表分区!这个并没有多少公司使用!
MySQL 的集群,很好满足那个年代的所有需求!

Linux安装

1、官网下载安装包!
2、解压Redis的安装包到你要的目录!

3、基本环境安装

4、进入解压后的目录

4、进入src目录

  • 此处如果有问题的话可以自行搜索相关安装教程,根据自己需求下载window版或者linux版

Redis运行

先设置daemonize no改成yes,开启后台启动

protected-mode 设置成no

找到# requirepass foobared

  • 一定要设置密码阿,之前被黑客通过redis黑进服务器了,植入了挖抗病毒麻了🙊

要注意是在启动server的时候选择conf!!!!!

./redis-server ../redis.conf

./redis-cli -p 端口号

进入后 auth 你设置的密码

Redis关闭与退出

  • 然后exit

性能分析

redis-benchmark 是一个压力测试工具!
官方自带的性能测试工具!
redis-benchmark 命令参数!


我们来简单测试下:

如何查看这些分析呢?

基础知识

默认16个数据库,可以用select切换

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

命令大全:

EXPIRE key10

ttl key

type key

move name 1(1表示当前数据库)

大杂烩

127.0.0.1:6379> keys * # 查看所有的key(empty list or set)127.0.0.1:6379> set name kuangshen # set keyOK127.0.0.1:6379> keys *1) "name"127.0.0.1:6379> set age 1OK127.0.0.1:6379> keys *1) "age"2) "name"127.0.0.1:6379> EXISTS name # 判断当前的key是否存在(integer) 1127.0.0.1:6379> EXISTS name1(integer) 0127.0.0.1:6379> move name 1 # 移除当前的key(integer) 1127.0.0.1:6379> keys *1) "age"127.0.0.1:6379> set name qinjiangOK127.0.0.1:6379> keys *1) "age"2) "name"127.0.0.1:6379> clear127.0.0.1:6379> keys *1) "age"2) "name"127.0.0.1:6379> get name"qinjiang"127.0.0.1:6379> EXPIRE name 10 # 设置key的过期时间,单位是秒(integer) 1127.0.0.1:6379> ttl name # 查看当前key的剩余时间(integer) 4127.0.0.1:6379> ttl name(integer) 3127.0.0.1:6379> ttl name(integer) 2127.0.0.1:6379> ttl name(integer) 1127.0.0.1:6379> ttl name(integer) -2127.0.0.1:6379> get name(nil)127.0.0.1:6379> type name # 查看当前key的一个类型!string127.0.0.1:6379> type agestring

String

appen和strlen

127.0.0.1:6379> set key1 v1 # 设置值OK127.0.0.1:6379> get key1 # 获得值"v1"127.0.0.1:6379> keys * # 获得所有的key1) "key1"127.0.0.1:6379> EXISTS key1 # 判断某一个key是否存在(integer) 1127.0.0.1:6379> APPEND key1 "hello" # 追加字符串,如果当前key不存在,就相当于setkey(integer) 7127.0.0.1:6379> get key1"v1hello"127.0.0.1:6379> STRLEN key1 # 获取字符串的长度!(integer) 7127.0.0.1:6379> APPEND key1 ",kaungshen"(integer) 17127.0.0.1:6379> STRLEN key1(integer) 17127.0.0.1:6379> get key1"v1hello,kaungshen"

incr和decr 自增和自减

# i++# 步长 i+=127.0.0.1:6379> set views 0 # 初始浏览量为0OK127.0.0.1:6379> get views"0"127.0.0.1:6379> incr views # 自增1 浏览量变为1(integer) 1127.0.0.1:6379> incr views(integer) 2127.0.0.1:6379> get views"2"127.0.0.1:6379> decr views # 自减1 浏览量-1(integer) 1127.0.0.1:6379> decr views(integer) 0127.0.0.1:6379> decr views(integer) -1127.0.0.1:6379> get views"-1"127.0.0.1:6379> INCRBY views 10 # 可以设置步长,指定增量!(integer) 9127.0.0.1:6379> INCRBY views 10(integer) 19127.0.0.1:6379> DECRBY views 5

getRange字符串范围 setRange替换指定位置开始的字符串

# 字符串范围 range127.0.0.1:6379> set key1 "hello,kuangshen" # 设置 key1 的值OK127.0.0.1:6379> get key1"hello,kuangshen"127.0.0.1:6379> GETRANGE key1 0 3 # 截取字符串 [0,3]"hell"127.0.0.1:6379> GETRANGE key1 0 -1 # 获取全部的字符串 和 get key是一样的"hello,kuangshen"# 替换!127.0.0.1:6379> set key2 abcdefgOK127.0.0.1:6379> get key2"abcdefg"127.0.0.1:6379> SETRANGE key2 1 xx # 替换指定位置开始的字符串!(integer) 7127.0.0.1:6379> get key2"axxdefg"

设置过期时间setex setnx(不存在才设置,存在时会失败)

# setex (set with expire) # 设置过期时间# setnx (set if not exist) # 不存在时再设置(在分布式锁中会常常使用!)127.0.0.1:6379> setex key3 30 "hello" # 设置key3的值为 hello,30秒后过期OK127.0.0.1:6379> ttl key3(integer) 26127.0.0.1:6379> get key3"hello"127.0.0.1:6379> setnx mykey "redis" # 如果mykey 不存在,创建mykey(integer) 1127.0.0.1:6379> keys *1) "key2"2) "mykey"3) "key1"127.0.0.1:6379> ttl key3(integer) -2127.0.0.1:6379> setnx mykey "MongoDB" # 如果mykey存在,创建失败!(integer) 0127.0.0.1:6379> get mykey

mset/get 同时设置/获取多个值

msetmget127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同时设置多个值OK127.0.0.1:6379> keys *1) "k1"2) "k2"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) 0127.0.0.1:6379> get k4(nil)

对象 set user:1:name xxx user:1:age xxx

# 对象set user:1 {name:zhangsan,age:3} # 设置一个user:1 对象 值为 json字符来保存一个对象!# 这里的key是一个巧妙的设计: user:{id}:{filed} , 如此设计在Redis中是完全OK了!127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2OK127.0.0.1:6379> mget user:1:name user:1:age1) "zhangsan"2) "2"

get同时set

getset # 先get然后在set127.0.0.1:6379> getset db redis # 如果不存在值,则返回 nil(nil)127.0.0.1:6379> get db"redis127.0.0.1:6379> getset db mongodb # 如果存在值,获取原来的值,并设置新的值"redis"127.0.0.1:6379> get db"mongodb"

使用场景


String类似的使用场景:value除了是我们的字符串还可以是我们的数字!
计数器
统计多单位的数量
粉丝数
对象缓存存储!

Hash(跟String类似的其实)

Map集合,key-map! 时候这个值是一个map集合! 本质和String类型没有太大区别,还是一个简单的 key-vlaue!
hash存储经常变更的数据 user name age,尤其是是用户信息之类的,经常变动的信息! hash 更适合于对象的存储,String更加适合字符串存储!

hset/get

hdel(类似String的del)

hkeys/hvals(只获得所有key/val)

同样可以指定增量,可以hsetnx

127.0.0.1:6379> hset myhash field1 kuangshen # set一个具体 key-vlaue(integer) 1127.0.0.1:6379> hget myhash field1 # 获取一个字段值"kuangshen"127.0.0.1:6379> hmset myhash field1 hello field2 world # set多个 key-vlaueOK127.0.0.1:6379> hmget myhash field1 field2 # 获取多个字段值1) "hello"2) "world"127.0.0.1:6379> hgetall myhash # 获取全部的数据,1) "field1"2) "hello"3) "field2"4) "world"127.0.0.1:6379> hdel myhash field1 # 删除hash指定key字段!对应的value值也就消失了!(integer) 1127.0.0.1:6379> hgetall myhash1) "field2"2) "world"##########################################################################hlen127.0.0.1:6379> hmset myhash field1 hello field2 worldOK127.0.0.1:6379> HGETALL myhash1) "field2"2) "world"3) "field1"4) "hello"127.0.0.1:6379> hlen myhash # 获取hash表的字段数量!(integer) 2##########################################################################127.0.0.1:6379> HEXISTS myhash field1 # 判断hash中指定字段是否存在!(integer) 1127.0.0.1:6379> HEXISTS myhash field3(integer) 0########################################################################### 只获得所有field# 只获得所有value127.0.0.1:6379> hkeys myhash # 只获得所有field1) "field2"2) "field1"127.0.0.1:6379> hvals myhash # 只获得所有value1) "world"2) "hello"##########################################################################incr decr127.0.0.1:6379> hset myhash field3 5 #指定增量!(integer) 1127.0.0.1:6379> HINCRBY myhash field3 1(integer) 6127.0.0.1:6379> HINCRBY myhash field3 -1(integer) 5127.0.0.1:6379> hsetnx myhash field4 hello # 如果不存在则可以设置(integer) 1127.0.0.1:6379> hsetnx myhash field4 world # 如果存在则不能设置(integer) 0

List

  • 底层是一个双向链表,对于两段的插入删除效率比较高,而对于中间索引值的插入和更改就相对慢了,因为查询慢

Lpush和Rpush LRange 查看区间值(0 -1就会查所有)

127.0.0.1:6379> LPUSH list one # 将一个值或者多个值,插入到列表头部 (左)(integer) 1127.0.0.1:6379> LPUSH list two(integer) 2127.0.0.1:6379> LPUSH list three(integer) 3127.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 righr # 将一个值或者多个值,插入到列表尾部 (右)(integer) 4127.0.0.1:6379> LRANGE list 0 -11) "three"2) "two"3) "one"4) "righr"

Lpop和Rpop

LPOPRPOP127.0.0.1:6379> LRANGE list 0 -11) "three"2) "two"3) "one"4) "righr"127.0.0.1:6379> Lpop list # 移除list的第一个元素"three"127.0.0.1:6379> Rpop list # 移除list的最后一个元素"righr"127.0.0.1:6379> LRANGE list 0 -11) "two"2) "one"

Lindex(通过下标获得 list 中的某一个值)

Lindex127.0.0.1:6379> LRANGE list 0 -11) "two"2) "one"127.0.0.1:6379> lindex list 1 # 通过下标获得 list 中的某一个值!"one"127.0.0.1:6379> lindex list 0"two"

Llen 获取长度

Llen127.0.0.1:6379> Lpush list one(integer) 1127.0.0.1:6379> Lpush list twobilibili:狂神说Java(integer) 2127.0.0.1:6379> Lpush list three(integer) 3127.0.0.1:6379> Llen list # 返回列表的长度(integer) 3

Lrem 移除count个指定value的值

移除指定的值!取关 uidLrem127.0.0.1:6379> LRANGE list 0 -11) "three"2) "three"3) "two"4) "one"127.0.0.1:6379> lrem list 1 one # 移除list集合中指定个数的value,精确匹配(integer) 1127.0.0.1:6379> LRANGE list 0 -11) "three"2) "three"3) "two"127.0.0.1:6379> lrem list 1 three(integer) 1127.0.0.1:6379> LRANGE list 0 -11) "three"2) "two"127.0.0.1:6379> Lpush list three(integer) 3127.0.0.1:6379> lrem list 2 three(integer) 2127.0.0.1:6379> LRANGE list 0 -11) "two"

trim 截取

trim 修剪。; list 截断!127.0.0.1:6379> keys *(empty list or set)127.0.0.1:6379> Rpush mylist "hello"(integer) 1127.0.0.1:6379> Rpush mylist "hello1"(integer) 2127.0.0.1:6379> Rpush mylist "hello2"(integer) 3127.0.0.1:6379> Rpush mylist "hello3"(integer) 4127.0.0.1:6379> ltrim mylist 1 2 # 通过下标截取指定的长度,这个list已经被改变了,截断了,只剩下截取的元素!OK127.0.0.1:6379> LRANGE mylist 0 -11) "hello1"2) "hello2"

rpoplpush 移除指定的值,移动到新的list中

rpoplpush # 移除列表的最后一个元素,将他移动到新的列表中!127.0.0.1:6379> rpush mylist "hello"(integer) 1127.0.0.1:6379> rpush mylist "hello1"(integer) 2127.0.0.1:6379> rpush mylist "hello2"(integer) 3127.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"

Lset 修改指定下标的值

lset #将列表中指定下标的值替换为另外一个值,更新操作127.0.0.1:6379> EXISTS list # 判断这个列表是否存在(integer) 0127.0.0.1:6379> lset list 0 item # 如果不存在列表我们去更新就会报错(error) ERR no such key127.0.0.1:6379> lpush list value1(integer) 1127.0.0.1:6379> LRANGE list 0 01) "value1"127.0.0.1:6379> lset list 0 item # 如果存在,更新当前下标的值OK127.0.0.1:6379> LRANGE list 0 01) "item"127.0.0.1:6379> lset list 1 other # 如果不存在,则会报错!(error) ERR index out of range

Insert 在指定value前边或者后边插入值

linsert # 将某个具体的value插入到列把你中某个元素的前面或者后面!127.0.0.1:6379> Rpush mylist "hello"(integer) 1127.0.0.1:6379> Rpush mylist "world"(integer) 2127.0.0.1:6379> LINSERT mylist before "world" "other"(integer) 3127.0.0.1:6379> LRANGE mylist 0 -11) "hello"2) "other"3) "world"127.0.0.1:6379> LINSERT mylist after world new(integer) 4127.0.0.1:6379> LRANGE mylist 0 -11) "hello"2) "other"3) "world"4) "new"

Set

成员相关

Sadd 添加成员

Smember 查看所有成员

SIsMember 查看是否是成员之一

Scard 获取元素个数

srem 删除指定value

SrandMember 随机获取成员(不会pop)

Spop 随机pop出来

大杂烩

127.0.0.1:6379> sadd myset "hello" # set集合中添加匀速(integer) 1127.0.0.1:6379> sadd myset "kuangshen"(integer) 1127.0.0.1:6379> sadd myset "lovekuangshen"(integer) 1127.0.0.1:6379> SMEMBERS myset # 查看指定set的所有值1) "hello"2) "lovekuangshen"3) "kuangshen"127.0.0.1:6379> SISMEMBER myset hello # 判断某一个值是不是在set集合中!(integer) 1127.0.0.1:6379> SISMEMBER myset world(integer) 0##########################################################################127.0.0.1:6379> scard myset # 获取set集合中的内容元素个数!(integer) 4##########################################################################rem127.0.0.1:6379> srem myset hello # 移除set集合中的指定元素(integer) 1127.0.0.1:6379> scard myset(integer) 3127.0.0.1:6379> SMEMBERS myset1) "lovekuangshen2"2) "lovekuangshen"3) "kuangshen"##########################################################################set 无序不重复集合。抽随机!127.0.0.1:6379> SMEMBERS myset1) "lovekuangshen2"2) "lovekuangshen"3) "kuangshen"127.0.0.1:6379> SRANDMEMBER myset # 随机抽选出一个元素"kuangshen"127.0.0.1:6379> SRANDMEMBER myset"kuangshen"127.0.0.1:6379> SRANDMEMBER myset"kuangshen"127.0.0.1:6379> SRANDMEMBER myset"kuangshen"127.0.0.1:6379> SRANDMEMBER myset 2 # 随机抽选出指定个数的元素1) "lovekuangshen"2) "lovekuangshen2"127.0.0.1:6379> SRANDMEMBER myset 21) "lovekuangshen"2) "lovekuangshen2"127.0.0.1:6379> SRANDMEMBER myset # 随机抽选出一个元素"lovekuangshen2"##########################################################################删除定的key,随机删除key!127.0.0.1:6379> SMEMBERS myset1) "lovekuangshen2"2) "lovekuangshen"3) "kuangshen"127.0.0.1:6379> spop myset # 随机删除一些set集合中的元素!"lovekuangshen2"127.0.0.1:6379> spop myset"lovekuangshen"127.0.0.1:6379> SMEMBERS myset1) "kuangshen"##########################################################################将一个指定的值,移动到另外一个set集合!127.0.0.1:6379> sadd myset "hello"(integer) 1127.0.0.1:6379> sadd myset "world"(integer) 1127.0.0.1:6379> sadd myset "kuangshen"(integer) 1127.0.0.1:6379> sadd myset2 "set2"(integer) 1127.0.0.1:6379> smove myset myset2 "kuangshen" # 将一个指定的值,移动到另外一个set集合!(integer) 1127.0.0.1:6379> SMEMBERS myset1) "world"2) "hello"127.0.0.1:6379> SMEMBERS myset21) "kuangshen"2) "set2"##########################################################################微博,B站,共同关注!(并集)数字集合类:- 差集 SDIFF- 交集- 并集127.0.0.1:6379> SDIFF key1 key2 # 差集1) "b"2) "a"127.0.0.1:6379> SINTER key1 key2 # 交集 共同好友就可以这样实现1) "c"127.0.0.1:6379> SUNION key1 key2 # 并集1) "b"2) "c"3) "e"4) "a"5) "d"

ZSet(有序集合)

在set的基础上,增加了一个值

127.0.0.1:6379> zadd myset 1 one # 添加一个值(integer) 1127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值(integer) 2127.0.0.1:6379> ZRANGE myset 0 -11) "one"2) "two"3) "three"##########################################################################排序如何实现127.0.0.1:6379> zadd salary 2500 xiaohong # 添加三个用户(integer) 1127.0.0.1:6379> zadd salary 5000 zhangsan(integer) 1127.0.0.1:6379> zadd salary 500 kaungshen(integer) 1# ZRANGEBYSCORE key min max127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示全部的用户 从小到大!1) "kaungshen"2) "xiaohong"3) "zhangsan"127.0.0.1:6379> ZREVRANGE salary 0 -1 # 从大到进行排序!1) "zhangsan"2) "kaungshen"127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 显示全部的用户并且附带成绩 1)"kaungshen"2) "500"3) "xiaohong"4) "2500"5) "zhangsan"6) "5000"127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 显示工资小于2500员工的升序排序!1) "kaungshen"2) "500"3) "xiaohong"4) "2500"########################################################################### 移除rem中的元素127.0.0.1:6379> zrange salary 0 -11) "kaungshen"2) "xiaohong"3) "zhangsan"127.0.0.1:6379> zrem salary xiaohong # 移除有序集合中的指定元素(integer) 1127.0.0.1:6379> zrange salary 0 -11) "kaungshen"2) "zhangsan"127.0.0.1:6379> zcard salary # 获取有序集合中的个数(integer) 2##########################################################################127.0.0.1:6379> zadd myset 1 hello(integer) 1127.0.0.1:6379> zadd myset 2 world 3 kuangshen(integer) 2127.0.0.1:6379> zcount myset 1 3 # 获取指定区间的成员数量!(integer) 3127.0.0.1:6379> zcount myset 1 2(integer) 2

Redis.conf配置

Units单位

配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit
大小写不敏感

INCLUDES包含

类似jsp中的include,多实例的情况可以把公用的配置文件提取出来

让外网连接

可以设置bind 为对应主机的ip

  • 若想让所有ip都能访问,注释掉这一行就好了,即不写的情况下,无限制接受任何ip地址的访问

同时protected-mode 也得设置成no

Port

端口号,默认 6379

tcp-backlog

设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列+ 已经完成三次握手队列。
在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。
注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn的值(128),所以需要确认增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)两个值来达到想要的效果

timeout

一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭。

tcp-keepalive

对访问客户端的一种心跳检测,每个n秒检测一次。
单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60

pidfile

存放pid文件的位置,每个实例会产生一个不同的pid文件

loglevel

指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice
四个级别根据使用阶段来选择,生产环境选择notice 或者warning

databases 16

设定库的数量默认16,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id

Limt限制

maxclients

Ø 设置redis同时可以与多少个客户端进行连接。
Ø 默认情况下为10000个客户端。
Ø 如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。

maxmemory

Ø 建议必须设置,否则,将内存占满,造成服务器宕机
Ø 设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。
Ø 如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
Ø 但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。

maxmemory-policy

Ø volatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用)
Ø allkeys-lru:在所有集合key中,使用LRU算法移除key
Ø volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
Ø allkeys-random:在所有集合key中,移除随机的key
Ø volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
Ø noeviction:不进行移除。针对写操作,只是返回错误信息

maxmemory-samples

Ø 设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。
Ø 一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。


daemonize

Redis 默认不是以守护进程的方式运行,可以通过该配置项修改,使用 yes 启用守护进程

持久化配置

rdb

aof

设置密码

通过命令行修改(不推荐,重启就没了)

修改redis.conf

vim进入后,直接输入/requirepass 找到被注释掉的那一栏,自行设置密码
密码设置之后,当你退出再次连上redis的时候,就需要输入密码了,不然是无法操作的。这里有两种方式输入密码,一是连接的时候直接输入密码,而是连接上之后再输入密码

连接时输入密码

连接后输入密码

限制client(一般不用管)

SpringBoot整合

SpringBoot 操作数据:spring-data jpa jdbc mongodb redis!
说明: 在 SpringBoot2.x 之后,原来使用的jedis 被替换为了 lettuce
jedis : 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool 连接池! 更像 BIO 模式
lettuce : 采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO 模式

自定义RedisTemplate

package com.example.redis.conf;import com.faster

Redis工具类

package com.example.redis.utils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import java.util.Collection;import java.util.List;import java.util.Map;import java.util.Set;import java.util.concurrent.TimeUnit;//在真实开发中,经常使用@Componentpublic final class RedisUtil { @Autowired private RedisTemplate<String, Object> redisTemplate; // =============================common============================ /**  * 指定缓存失效时间  *  * @param key 键  * @param time 时间(秒)  */ public boolean expire(String key, long time) {  try {   if (time > 0) {    redisTemplate.expire(key, time, TimeUnit.SECONDS);   }   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 根据key 获取过期时间  *  * @param key 键 不能为null  * @return 时间(秒) 返回0代表为永久有效  */ public long getExpire(String key) {  return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /**  * 判断key是否存在  *  * @param key 键  * @return true 存在 false不存在  */ public boolean hasKey(String key) {  try {   return redisTemplate.hasKey(key);  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 删除缓存  *  * @param key 可以传一个值 或多个  */ @SuppressWarnings("unchecked") public void del(String... key) {  if (key != null && key.length > 0) {   if (key.length == 1) {    redisTemplate.delete(key[0]);   } else {    redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));   }  } }// ============================String============================= /**  * 普通缓存获取  *  * @param key 键  * @return 值  */ public Object get(String key) {  return key == null ? null : redisTemplate.opsForValue().get(key); } /**  * 普通缓存放入  *  * @param key 键  * @param value 值  * @return true成功 false失败  */ public boolean set(String key, Object value) {  try {   redisTemplate.opsForValue().set(key, value);   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 普通缓存放入并设置时间  *  * @param key 键  * @param value 值  * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期  * @return true成功 false 失败  */ public boolean set(String key, Object value, long time) {  try {   if (time > 0) {    redisTemplate.opsForValue().set(key, value, time,      TimeUnit.SECONDS);   } else {    set(key, value);   }   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 递增  *  * @param key 键  * @param delta 要增加几(大于0)  */ public long incr(String key, long delta) {  if (delta < 0) {   throw new RuntimeException("递增因子必须大于0");  }  return redisTemplate.opsForValue().increment(key, delta); } /**  * 递减  *  * @param key 键  * @param delta 要减少几(小于0)  */ public long decr(String key, long delta) {  if (delta < 0) {   throw new RuntimeException("递减因子必须大于0");  }  return redisTemplate.opsForValue().increment(key, -delta); }// ================================Map================================= /**  * HashGet  *  * @param key 键 不能为null  * @param item 项 不能为null  */ public Object hget(String key, String item) {  return redisTemplate.opsForHash().get(key, item); } /**  * 获取hashKey对应的所有键值  *  * @param key 键  * @return 对应的多个键值  */ public Map<Object, Object> hmget(String key) {  return redisTemplate.opsForHash().entries(key); } /**  * HashSet  *  * @param key 键  * @param map 对应多个键值  */ public boolean hmset(String key, Map<String, Object> map) {  try {   redisTemplate.opsForHash().putAll(key, map);   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * HashSet 并设置时间  *  * @param key 键  * @param map 对应多个键值  * @param time 时间(秒)  * @return true成功 false失败  */ public boolean hmset(String key, Map<String, Object> map, long time) {  try {   redisTemplate.opsForHash().putAll(key, map);   if (time > 0) {    expire(key, time);   }   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 向一张hash表中放入数据,如果不存在将创建  *  * @param key 键  * @param item 项  * @param value 值  * @return true 成功 false失败  */ public boolean hset(String key, String item, Object value) {  try {   redisTemplate.opsForHash().put(key, item, value);   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 向一张hash表中放入数据,如果不存在将创建  *  * @param key 键  * @param item 项  * @param value 值  * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间  * @return true 成功 false失败  */ public boolean hset(String key, String item, Object value, long time) {  try {   redisTemplate.opsForHash().put(key, item, value);   if (time > 0) {    expire(key, time);   }   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 删除hash表中的值  *  * @param key 键 不能为null  * @param item 项 可以使多个 不能为null  */ public void hdel(String key, Object... item) {  redisTemplate.opsForHash().delete(key, item); } /**  * 判断hash表中是否有该项的值  *  * @param key 键 不能为null  * @param item 项 不能为null  * @return true 存在 false不存在  */ public boolean hHasKey(String key, String item) {  return redisTemplate.opsForHash().hasKey(key, item); } /**  * hash递增 如果不存在,就会创建一个 并把新增后的值返回  *  * @param key 键  * @param item 项  * @param by 要增加几(大于0)  */ public double hincr(String key, String item, double by) {  return redisTemplate.opsForHash().increment(key, item, by); } /**  * hash递减  *  * @param key 键  * @param item 项  * @param by 要减少记(小于0)  */ public double hdecr(String key, String item, double by) {  return redisTemplate.opsForHash().increment(key, item, -by); }// ============================set============================= /**  * 根据key获取Set中的所有值  *  * @param key 键  */ public Set<Object> sGet(String key) {  try {   return redisTemplate.opsForSet().members(key);  } catch (Exception e) {   e.printStackTrace();   return null;  } } /**  * 根据value从一个set中查询,是否存在  *  * @param key 键  * @param value 值  * @return true 存在 false不存在  */ public boolean sHasKey(String key, Object value) {  try {   return redisTemplate.opsForSet().isMember(key, value);  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 将数据放入set缓存  *  * @param key 键  * @param values 值 可以是多个  * @return 成功个数  */ public long sSet(String key, Object... values) {  try {   return redisTemplate.opsForSet().add(key, values);  } catch (Exception e) {   e.printStackTrace();   return 0;  } } /**  * 将set数据放入缓存  *  * @param key 键  * @param time 时间(秒)  * @param values 值 可以是多个  * @return 成功个数  */ public long sSetAndTime(String key, long time, Object... values) {  try {   Long count = redisTemplate.opsForSet().add(key, values);   if (time > 0) {    expire(key, time);   }   return count;  } catch (Exception e) {   e.printStackTrace();   return 0;  } } /**  * 获取set缓存的长度  *  * @param key 键  */ public long sGetSetSize(String key) {  try {   return redisTemplate.opsForSet().size(key);  } catch (Exception e) {   e.printStackTrace();   return 0;  } } /**  * 移除值为value的  *  * @param key 键  * @param values 值 可以是多个  * @return 移除的个数  */ public long setRemove(String key, Object... values) {  try {   Long count = redisTemplate.opsForSet().remove(key, values);   return count;  } catch (Exception e) {   e.printStackTrace();   return 0;  } }// ===============================list================================= /**  * 获取list缓存的内容  *  * @param key 键  * @param start 开始  * @param end 结束 0 到 -1代表所有值  */ public List<Object> lGet(String key, long start, long end) {  try {   return redisTemplate.opsForList().range(key, start, end);  } catch (Exception e) {   e.printStackTrace();   return null;  } } /**  * 获取list缓存的长度  *  * @param key 键  */ public long lGetListSize(String key) {  try {   return redisTemplate.opsForList().size(key);  } catch (Exception e) {   e.printStackTrace();   return 0;  } } /**  * 通过索引 获取list中的值  *  * @param key 键  * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0  *    时,-1,表尾,-2倒数第二个元素,依次类推  */ public Object lGetIndex(String key, long index) {  try {   return redisTemplate.opsForList().index(key, index);  } catch (Exception e) {   e.printStackTrace();   return null;  } } /**  * 将list放入缓存  *  * @param key 键  * @param value 值  */ public boolean lSet(String key, Object value) {  try {   redisTemplate.opsForList().rightPush(key, value);   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 将list放入缓存  *  * @param key 键  * @param value 值  * @param time 时间(秒)  */ public boolean lSet(String key, Object value, long time) {  try {   redisTemplate.opsForList().rightPush(key, value);   if (time > 0) {    expire(key, time);   }   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 将list放入缓存  *  * @param key 键  * @param value 值  * @return  */ public boolean lSet(String key, List<Object> value) {  try {   redisTemplate.opsForList().rightPushAll(key, value);   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 将list放入缓存  *  * @param key 键  * @param value 值  * @param time 时间(秒)  * @return  */ public boolean lSet(String key, List<Object> value, long time) {  try {   redisTemplate.opsForList().rightPushAll(key, value);   if (time > 0) {    expire(key, time);   }   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 根据索引修改list中的某条数据  *  * @param key 键  * @param index 索引  * @param value 值  * @return  */ public boolean lUpdateIndex(String key, long index, Object value) {  try {   redisTemplate.opsForList().set(key, index, value);   return true;  } catch (Exception e) {   e.printStackTrace();   return false;  } } /**  * 移除N个值为value  *  * @param key 键  * @param count 移除多少个  * @param value 值  * @return 移除的个数  */  public long lRemove (String key,long count, Object value){   try {    Long remove = redisTemplate.opsForList().remove(key, count,value);    return remove;   } catch (Exception e) {    e.printStackTrace();    return 0;   }  } }

Redis持久化

Redis 是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以 Redis 提供了持久化功能!

rdb (Redis DataBase)


在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的!!!!
这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,RDB方式要比AOF方式更加的高效。

流程图

相关配置回顾

save

保存的文件是dump.rdb

stop-writes-on-bgsave-error


当Redis无法写入磁盘的话,直接关掉Redis的写操作。

rdbcompression 压缩文件

  • 如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。

rdbchecksum 检查完整性

  • 但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能

redis备份

然后删除掉dump.rdb 模拟一下数据丢失的实际场景
关闭redis服务器
接下来要怎么恢复原来的数据呢,我们只需要 cp dump2.rdb dump.rdb
启动Redis,备份数据会直接加载

  • 简单说就是先备份一下rdb文件 , 然后需要重新加载的时候再改回原来的默认名字就好了 , redis就会自动重新加载

优点

  • 适合大规模的数据恢复
  • 对数据完整性和一致性要求不高更适合使用
  • 节省磁盘空间
  • 恢复速度快

缺点

最后一次持久化后的数据可能丢失。

AOF(Append Only File)(流程还没看)

日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

同步频率设置

appendfsync always

appendfsync everysec

appendfsync no

重写


重写只关心最终的结果,不关心你的过程,把两条语句压缩成一条了

auto-aof-rewrite-percentage:

auto-aof-rewrite-min-size:

例如:文件达到70MB开始重写,降到50MB,下次什么时候开始重写?100MB
系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,
如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。

缺点

  • 比起RDB占用更多的磁盘空间。
  • 恢复备份速度要慢。
  • 每次读写都同步的话,有一定的性能压力。
  • 存在个别Bug,造成恢复不能。

建议

官方推荐两个都启用。

  • 建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段

如果对数据不敏感,可以单独用RDB。
如果只是做纯内存缓存,可以都不用。

发布与订阅(原理狂神待看,菜鸟命令)

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。

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

  1. 打开一个客户端订阅channel1

SUBSCRIBE channel1

  1. 打开另一个客户端,给channel1发布消息hello


返回的1是订阅者数量

  1. 打开第一个客户端可以看到发送的消息


注:发布的消息没有持久化,如果在订阅的客户端收不到hello,只能收到订阅后发布的消息

订阅

127.0.0.1:6379> SUBSCRIBE kuangshenshuo # 订阅一个频道 kuangshenshuoReading messages... (press Ctrl-C to quit)1) "subscribe"2) "kuangshenshuo"3) (integer) 1# 等待读取推送的信息1) "message" # 消息2) "kuangshenshuo" # 那个频道的消息3) "hello,kuangshen" # 消息的具体内容1) "message"2) "kuangshenshuo"3) "hello,redis"

发送端

127.0.0.1:6379> PUBLISH kuangshenshuo "hello,kuangshen" # 发布者发布消息到频道!(integer) 1127.0.0.1:6379> PUBLISH kuangshenshuo "hello,redis" # 发布者发布消息到频道!(integer) 1

使用场景:


1、实时消息系统
2、实时聊天!(频道当做聊天室,将信息回显给所有人即可!)
3、订阅,关注系统都是可以的!
稍微复杂的场景我们就会使用 ---> 消息中间件MQ

Redis事务

定义

Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
Redis事务的主要作用就是串联多个命令防止别的命令插队。

Multi、Exec、discard?(SpringBoot怎么实现)

从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。

 //增加乐观锁 jedis.watch(qtkey); //3.判断库存 String qtkeystr = jedis.get(qtkey); if(qtkeystr==null || "".equals(qtkeystr.trim())) {  System.out.println("未初始化库存");  jedis.close();  return false ; } int qt = Integer.parseInt(qtkeystr); if(qt<=0) {  System.err.println("已经秒光");  jedis.close();  return false; } //增加事务 Transaction multi = jedis.multi(); //4.减少库存 //jedis.decr(qtkey); multi.decr(qtkey); //5.加人 //jedis.sadd(usrkey, uid); multi.sadd(usrkey, uid); //执行事务 List<Object> list = multi.exec(); //判断事务提交是否失败 if(list==null || list.size()==0) {  System.out.println("秒杀失败");  jedis.close();  return false; } System.err.println("秒杀成功"); jedis.close();

错误处理

组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消。
 

***与MySQL的区别!!!

如果执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。

总结

在组队期间发生错误,就会回滚。

  • 而在执行阶段发生错误了,则不会回滚。

悲观锁

乐观锁

对比

秒杀库存变成负数问题

  • 乐观锁和悲观锁都能解决,因为在每次购买下单之前,都会先去检查一下
    • 乐观锁会去检查版本,发现版本号不一样了,直接就失败了!(那肯定不会出现负数的情况)
    • 而悲观锁呢? 悲观锁是等待上一个人执行完了,再来操作.这样可以保证不会出现负数的情况,同时也能够继续进行购买,不会说就此失败了!

乐观锁引发的库存遗留问题

解决--Lua脚本

将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。
LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。
利用lua脚本淘汰用户,解决超卖问题。
redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题

watch

在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

事务三特性

  • 单独的隔离操作
  • 没有隔离级别的概念
  • 不保证原子性

主从复制

主(写)从(读)

**从机只能读,不能写

一主二仆(附步骤)

拷贝多个redis.conf文件include(写绝对路径) ??

新建redis_7001.conf 修改conf

#引入我们原来的redis.conf文件(根据目录引入)include ../redis.conf#在引入的基础上,修改必要的选项pidfile /var/run/redis_7001.pidport 7001dbfilename dump7001.rdbappendonly nodaemonize yes

同理创建另外两个conf,修改相应的pid文件,port,dufilename名字

#引入我们原来的redis.conf文件(根据目录引入)include ../redis.conf#在引入的基础上,修改必要的选项pidfile /var/run/redis_7002.pidport 7002dbfilename dump7002.rdbappendonly nodaemonize yes
#引入我们原来的redis.conf文件(根据目录引入)include ../redis.conf#在引入的基础上,修改必要的选项pidfile /var/run/redis_7003.pidport 7003dbfilename dump7003.rdbappendonly nodaemonize yes

查看三台主机运行情况

info replication

slaveof ip地址 端口

从机挂掉后,重启没办法恢复成xx的从机

但挂掉期间主服务的操作,重启后还是可以看到的

薪火相传

  • 上一个Slave可以是下一个slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了链条中下一个的master, 可以有效减轻master的写压力,去中心化降低风险

中途变更转向:会清除之前的数据,重新建立拷贝最新的
风险是一旦某个slave宕机,后面的slave都没法备份
主机挂了,从机还是从机,无法写数据了

反客为主

主服务器挂掉后,从服务器只需要用 slaveof no one 就可以上位,变成主服务器了!

复制原理

  • 当从服务器连接上主服务器后,从服务器会主动向主服务器请求数据同步的消息
  • 主服务器接受到后,就会进行持久化操作,然后把rdb文件发送给从服务器,从服务器读取rdb文件以达到同步的效果
  • 每次主服务器进行了写操作后,也会跟从服务器进行数据同步

全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步

哨兵模式

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

自定义的/myredis目录下新建sentinel.conf文件,名字绝不能错

配置哨兵,填写内容

sentinel monitor mymaster 127.0.0.1 6379 1

启动哨兵

执行redis-sentinel /myredis/sentinel.conf

当主机挂掉,从机选举中产生新的主机

(大概10秒左右可以看到哨兵窗口日志,切换了新的主机)
哪个从机会被选举为主机呢?根据优先级别:slave-priority
原主机重启后会变为从机

复制延时

由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

故障恢复


优先级在redis.conf中默认:slave-priority 100,值越小优先级越高

  • 偏移量是指获得原主机数据最全的

每个redis实例启动后都会随机生成一个40位的runid

集群(无中心化)

***好处

主从模式,薪火相传模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息。

代理

无中心化


Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N

  • Redis 集群通过分区(partition)来提供一定程度的可用性(availability):即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。

搭载集群

配置文件

include ../redis.confpidfile /var/run/redis_7001.pidport 7001dbfilename dump7001.rdbcluster-enabled yescluster-config-file nodes-7001.confcluster-node-timeout 15000

快速替换的小技巧

:%s/7001/7002g

帝皇侠合体

  • redis-cli --cluster create --cluster-replicas 1 10.0.8.13:7001 10.0.8.13:7002 10.0.8.13:7003 10.0.8.13:7004 10.0.8.13:7005 10.0.8.13:7006
  • 注意如果是阿里云或者腾讯云的,这里得用内网ip!!!!

--replicas 1 采用最简单的方式配置集群,一台主机,一台从机,正好三组。

输入yes

成功界面

2223凄凄切切凄凄切切前驱群群群群群晕晕晕晕晕00

普通方式登录

  • 可能直接进入读主机,存储数据时,会出现MOVED重定向操作。所以,应该以集群方式登录。

-c 采用集群策略连接

  • ./redis-cli -c -p 7001

查看节点信息

  • CLUSTER NODES

分配原则

一个集群至少要有三个主节点。
选项--cluster-replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。

slot

一个 Redis 集群包含16384 个插槽(hash slot),数据库中的每个键都属于这16384 个插槽的其中一个,
集群使用公式 CRC16(key) % 16384

  • 来计算键key 属于哪个槽,其中CRC16(key) 语句用于计算键key 的CRC16 校验和。

集群中的每个节点负责处理一部分插槽。举个例子,如果一个集群可以有主节点,其中:

  • 节点 A 负责处理0号至5460号插槽。
  • 节点 B 负责处理5461号至10922号插槽。
  • 节点 C 负责处理10923号至16383号插槽。
  • 不过这里如果计算出来的值一样(并不会像哈希一样出现冲突),而是让一个插槽放多个数据就好了

不在一个slot下的键值,是不能使用mget,mset等多键操作。

  • 可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。

计算重定向(无中心化的实现方式)

  • 通过计算,然后去重定向到相应的节点上,这样就可以实现各个节点能够互相连通,自然而然每个节点都能作为入口了,因为他们可以重定向到别的节点,互相连通互相访问!

主机挂掉了???

  • 此时7003 fail掉了,此处还有待进一步验证,现在服务器上挂着几个项目不敢乱搭,寒假再拿另外一个服务器来试试。

JRedis集群开发

即使连接的不是主机,集群会自动切换主机存储。主机写,从机读。
无中心化主从集群。无论从哪台主机写的数据,其他主机上都能读到数据。

public class JedisClusterTest { public static void main(String[] args) {  Set<HostAndPort>set =new HashSet<HostAndPort>();  set.add(new HostAndPort("192.168.31.211",6379));  JedisCluster jedisCluster=new JedisCluster(set);  jedisCluster.set("k1", "v1");  System.out.println(jedisCluster.get("k1")); }}

另一种版本

 public static void main(String[] args) {  Set<HostAndPort> jedisClusterNodes = new HashSet<HostAndPort>();  //Jedis Cluster will attempt to discover cluster nodes automatically  jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6371));  jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6372));  jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6373));  jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6374));  jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6375));  jedisClusterNodes.add(new HostAndPort("127.0.0.1", 6376));  JedisCluster jc = new JedisCluster(jedisClusterNodes);  jc.set("foo", "bar");  St
ring value = jc.get("foo");  System.out.println(" ===> " + value); }

 

小伙伴们有兴趣想了解内容和更多相关学习资料的请点赞收藏+评论转发+关注我,后面会有很多干货。
我有一些面试题、架构、设计类资料可以说是程序员面试必备!所有资料都整理到网盘了,需要的话欢迎下载!私信我回复【07】即可免费获取

 

原文出处:www.shaoqun.com/a/1424715.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值