Redis 学习笔记

13 篇文章 0 订阅

写在前面


第一次学redis是因为毕业找工作,大概学习了下,看了江南一点雨的教学视频。然后也没怎么用过,毕业近一年了,现在的公司因为业务情况,也没有用到,所以我基本上是没有在接触过,如今工作不咋忙,把之前看的在笔记整理整理,在系统的学习下。笔记大都是教程的笔记,另有部分地方补充,生活加油  ^_^。2021.03.14


Redis 是我们在互联网应用中使用最广泛的一个 NoSQL 数据库,基于 C 开发的键值对存储数据库,

Redis 这个名字是 Remote Dictionary Service 字母缩写。 很多人想到 Redis,就想到缓存。

但实际上 Redis 除了缓存之外,还有许多更加丰富的使用场景。比如 分布式锁,限流。

特点: 支持数据持久化 支持多种不同的数据结构类型之间的映射 支持主从模式的数据备份 自带了发布订阅系统

Redis 五种基本数据类型

String 是 Redis 里边最最简单的一种数据结构。在 Redis 中,所以的 key 都是字符串,但是,不同的 key 对应的 value 则具备不同的数据结构,我们所说的五种不同的数据类型,主要是指 value 的数据类 型不同。

Redis 中的字符串是动态字符串,内部是可以修改的,像 Java 中的 StringBuffer,它采用分配冗余空间 的方式来减少内存的频繁分配。在 Redis 内部结构中,一般实际分配的内存会大于需要的内存,当字符 串小于1M 的时候,扩容都是在现有的空间基础上加倍,扩容每次扩 1M 空间,最大 512M。

set给一个key赋值
append当key存在时,则直接在对应的value后面加值,否者就创建新的键值对
decr可以实现对value减1的操作,如果value不是数字,报错,如果value不存在,则会给一个默认的值为0,在默认的基础上减1
decrby和decr类似,但是可以自己设置步长,该命令的第二个参数就是步长
getget用来获取一个key和value
getrange可以用来返回 key 对应的 value 的子串,这有点类似于 Java 里边的 substring。这个命令第 二个和第三个参数就是截取的起始和终止位置,其中,-1 表示最后一个字符串,-2 表示倒数第二个字符 串,以此类推.
getset获取并更新某一个 key。
incr给某一个 key 的 value 自增。
incrby给某一个 key 的 value 自增,同时还可以设置步长。
incrbyfloat和 incrby 类似,但是自增的步长可以设置为浮点数。
mget 和 mset批量获取和批量存储
ttl查看 key 的有效期
setex在给 key 设置 value 的同时,还设置过期时间
psetex和 setex 类似,只不过这里的时间单位是毫秒
setnx 默认情况下, set 命令会覆盖已经存在的 key,setnx 则不会。
setrange覆盖一个已经存在的 key 的value
msetx批量设置
stlen

查看字符串长度

[root@liruilong redis-5.0.7]# redis-cli 
127.0.0.1:6379> append name liruilong
(integer) 9
127.0.0.1:6379> append name liruilonglovesy
(integer) 24
127.0.0.1:6379> get name
"liruilongliruilonglovesy"
127.0.0.1:6379> decr keydeck
(integer) -1
127.0.0.1:6379> set k3 34
OK
127.0.0.1:6379> decr  k3
(integer) 33
127.0.0.1:6379> decrby  k3 4
(integer) 29
127.0.0.1:6379> get k3
"29"
127.0.0.1:6379> GETRANGE name -3 -1
"esy"
127.0.0.1:6379> GETRANGE name -2 -1
"sy"
127.0.0.1:6379> GETSET k3  100
"29"
127.0.0.1:6379> get k3
"100"
127.0.0.1:6379> INCR k3
(integer) 101
127.0.0.1:6379> INCRBY k3 45
(integer) 146
127.0.0.1:6379> INCRBYFLOAT K3 23.34
"23.34"
127.0.0.1:6379> INCRBYFLOAT k3 23.34
"169.34"
127.0.0.1:6379> mget k3 name K3
1) "169.34"
2) "liruilongliruilonglovesy"
3) "23.34"
127.0.0.1:6379> SET KK1 23 KK2 34 KK3 45
(error) ERR syntax error
127.0.0.1:6379> MSET KK1 23 KK2 34 KK3 45
OK
127.0.0.1:6379> MGET KK1 KK2 KK3
1) "23"
2) "34"
3) "45"
127.0.0.1:6379> TTL name
(integer) -1
127.0.0.1:6379> SETEX name 5 
(error) ERR wrong number of arguments for 'setex' command
127.0.0.1:6379> SETEX names 5 456 
OK
127.0.0.1:6379> get names
(nil)
127.0.0.1:6379> SETEX names 10 456 
OK
127.0.0.1:6379> get names
"456"
127.0.0.1:6379> get names
"456"
127.0.0.1:6379> get names
(nil)
127.0.0.1:6379> ttl names
(integer) -2
127.0.0.1:6379> SETNX name liruilong
(integer) 0
127.0.0.1:6379> get name
"liruilongliruilonglovesy"
127.0.0.1:6379> SETNX named liruilong
(integer) 1
127.0.0.1:6379> get named
"liruilong"
127.0.0.1:6379> MSETNX name 23 wer 234
(integer) 0
127.0.0.1:6379> get name
"liruilongliruilonglovesy"
127.0.0.1:6379> get wer
(nil)
127.0.0.1:6379> SETRANGE name -1 sunyue
(error) ERR offset is out of range
127.0.0.1:6379> SETRANGE name 0 sunyue
(integer) 24
127.0.0.1:6379> get name
"sunyueongliruilonglovesy"
127.0.0.1:6379> 

BIT 命令

在 Redis 中,字符串都是以二进制的方式来存储的。例如 set k1 a,a 对应的 ASCII 码是 97,97 转为 二进制是 01100001,BIT 相关的命令就是对二进制进行操作的。

getbitkey 对应的 value 在 offset 处的 bit 值。
setbit修改 key 对应的 value 在 offset 处的 bit 值
bitcount统计二进制数据中 1 的个数。
127.0.0.1:6379> set k1 a
OK
127.0.0.1:6379> GETBIT k1 1
(integer) 1
127.0.0.1:6379> GETBIT k1 2
(integer) 1
127.0.0.1:6379> GETBIT k1 3
(integer) 0
127.0.0.1:6379> GETBIT k1 4
(integer) 0
127.0.0.1:6379> GETBIT k1 5
(integer) 0
127.0.0.1:6379> GETBIT k1 6
(integer) 0
127.0.0.1:6379> GETBIT k1 7
(integer) 1
127.0.0.1:6379> GETBIT k1 8
(integer) 0
127.0.0.1:6379> GETBIT k1 9
(integer) 0
127.0.0.1:6379> GETBIT k1 0
(integer) 0
127.0.0.1:6379> BITCOUNT k1
(integer) 3
127.0.0.1:6379> BITCOUNT k1  0 3
(integer) 3
127.0.0.1:6379> setbit   user 100 1
(integer) 0
127.0.0.1:6379> 

List

Redis中的列表:一个链表可以有序的存储多个字符串,Lpush和Rpush分别用于将元素推入列表的左端和右端,即头和尾

lpushlpush 表示将 value 的值从左到右依次插入表头位置。
lrange返回列表指定区间内的元素。
rpush这个和 lpush 功能基本类似,不同的是 rpush 是从右往左依次插入表头位置。
rpop移除并返回列表的尾元素。
lpop移除并返回列表的头元素。
lindex返回列表中,下标为 index 的元素。
ltrimltrim 可以对一个列表进行修剪。
blpop、brpop阻塞式的弹出,相当于 lpop ,rpop的阻塞版 最后一位为阻塞时间,设置为0时,为永久阻塞。
127.0.0.1:6379> lpush num 1 2 3 4
(integer) 4
127.0.0.1:6379> LRANGE num 0 2
1) "4"
2) "3"
3) "2"
127.0.0.1:6379> rpush num 5 6 7 8
(integer) 8
127.0.0.1:6379> LRANGE num 0 2
1) "4"
2) "3"
3) "2"0
127.0.0.1:6379> LRANGE num  4 7
1) "5"
2) "6"
3) "7"
4) "8"
127.0.0.1:6379> LRANGE num  0 7
1) "4"
2) "3"
3) "2"
4) "1"
5) "5"
6) "6"
7) "7"
8) "8"
127.0.0.1:6379> RPOP num
"8"
127.0.0.1:6379> 
127.0.0.1:6379> LPOP num
"4"
127.0.0.1:6379> LINDEX num 0
"3"
127.0.0.1:6379> LINDEX num 1
"2"
127.0.0.1:6379> LINDEX num -1
"7"
127.0.0.1:6379> ltrim num 3 4
OK
127.0.0.1:6379> LRANGE num 0 2
1) "5"
2) "6"
127.0.0.1:6379> LRANGE num 0 4
1) "5"
2) "6"
127.0.0.1:6379> blpop num 60
1) "num"
2) "5"
127.0.0.1:6379> blpop num 60
1) "num"
2) "6"
127.0.0.1:6379> blpop num 60

(nil)
(60.07s)
127.0.0.1:6379> 

Set

Redis中的集合:Redis集合和链表都可以存储多个字符串,他们之间的不同之处在于,列表可以存储多个相同的字符串,而集合则通过使用散列表来保证自己存储的每个字符串都是各不相同的

sadd添加元素到一个 key 中
smembers获取一个 key 下的所有元素
srem移除指定的元素
sismemeber返回某一个成员是否在集合中
scard返回集合的数量
srandmember随机返回一个元素
spop随机返回并且出栈一个元素。
smove把一个元素从一个集合移到另一个集合中去。
sdiff返回两个集合的差集。
sinter返回两个集合的交集。
sdiffstore这个类似于 sdiff ,不同的是,计算出来的结果会保存在一个新的集合中。
sinterstore类似于 sinter,只是将计算出来的交集保存到一个新的集合中。
sunion求并集。
sunionstore求并集并且将结果保存到新的集合中。
127.0.0.1:6379> sadd set 1
(integer) 1
127.0.0.1:6379> sadd set 2
(integer) 1
127.0.0.1:6379> sadd set 2 3 4 5
(integer) 3
127.0.0.1:6379> SMEMBERS set
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> SREM set 6
(integer) 0
127.0.0.1:6379> SMEMBERS set
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> SREM set 5
(integer) 1
127.0.0.1:6379> SMEMBERS set
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> SISMEMBER 4
(error) ERR wrong number of arguments for 'sismember' command
127.0.0.1:6379> SISMEMBER  set 4
(integer) 1
127.0.0.1:6379> SCARD set
(integer) 4
127.0.0.1:6379> SRANDMEMBER set
"3"
127.0.0.1:6379> SRANDMEMBER set
"4"
127.0.0.1:6379> SRANDMEMBER set 4
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> SRANDMEMBER set 3
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> SPOP set 
"2"
127.0.0.1:6379> SMEMBERS set
1) "1"
2) "3"
3) "4"
127.0.0.1:6379> SMOVE set set1 3 
(integer) 1
127.0.0.1:6379> SCARD set1
(integer) 1
127.0.0.1:6379> sdiff set set1
1) "1"
2) "4"
127.0.0.1:6379> SINTER set set1
(empty list or set)
127.0.0.1:6379> SDIFFSTORE set set1 set2
(integer) 1
127.0.0.1:6379> SMEMBERS set2
(empty list or set)
127.0.0.1:6379> SUNION set set1
1) "3"
127.0.0.1:6379> 

Hash

在 hash 结构中,key 是一个字符串,value 则是一个 key/value 键值对。Redis散列可以让用户将多个键值对存储到一个Redis键里,是无序的。

hset添加值。
hget获取值
hmset批量设置
hmget批量获取
hdel删除一个指定的建
 hsetnx默认情况下,如果 key 和 field 相同,会覆盖掉已有的 value,hsetnx 则不会。
hvals获取所有的 value
hkeys获取所有的 key
hgetall同时获取所有的 key 和 value
hexists返回 field 是否存在
hincrby给指定的 value 自增
hincrbyfloat可以自增一个浮点数
hlen返回 某一个 key 中 value 的数量
hstrlen返回某一个 key 中的某一个 field 的字符串长度
[root@liruilong ~]# redis-cli 
127.0.0.1:6379> hset name liruilong
(error) ERR wrong number of arguments for 'hset' command
127.0.0.1:6379> hset k1  name liruilong
(integer) 1
127.0.0.1:6379> hget k1 name
"liruilong"
127.0.0.1:6379> HMSET k1 age 26 ax 男
OK
127.0.0.1:6379> HMGET 看
(error) ERR wrong number of arguments for 'hmget' command
127.0.0.1:6379> HMGET k1
(error) ERR wrong number of arguments for 'hmget' command
127.0.0.1:6379> HMGET k1 name age
1) "liruilong"
2) "26"
127.0.0.1:6379> hdel k1 ax
(integer) 1
127.0.0.1:6379> HSETNX k1 name 234
(integer) 0
127.0.0.1:6379> HMGET k1 name age
1) "liruilong"
2) "26"
127.0.0.1:6379> HVALS k1
1) "liruilong"
2) "26"
127.0.0.1:6379> HKEYS k1
1) "name"
2) "age"
127.0.0.1:6379> HGETALL k1
1) "name"
2) "liruilong"
3) "age"
4) "26"
127.0.0.1:6379> HEXISTS k1 name
(integer) 1
127.0.0.1:6379> HEXISTS k1 names
(integer) 0
127.0.0.1:6379> HSET k1 num 23
(integer) 1
127.0.0.1:6379> HINCRBY k1 num
(error) ERR wrong number of arguments for 'hincrby' command
127.0.0.1:6379> HINCRBY k1 num 1
(integer) 24
127.0.0.1:6379> HINCRBY k1 num 0.5
(error) ERR value is not an integer or out of range
127.0.0.1:6379> HINCRBYfloat k1 num 0.5
"24.5"
127.0.0.1:6379> HLEN k1
(integer) 3
127.0.0.1:6379> HSTRLEN k1 name
(integer) 9
127.0.0.1:6379> 

ZSet

Redis中的有序集合:和散列一样,用于存储键值对,有序集合的键被称为成员,每个成员各不相同,有序集合的值被称为分值,分值必须为浮点数有序集合是Redis里唯一一个即可以根据成员访问元素,又可以根据分值的排序来访问元素结构

zadd将指定的元素添加到有序集合中
zscore 返回 member 的 score 值
zrangezrange 返回集合中的一组元素
zrevrange返回一组元素,但是是倒序
zcard返回元素个数
zcount返回 score 在某一个区间内的元素
zrangebyscore按照 score 的范围返回元素
zrank返回元素的排名(从小到大
zrevrank返回元素排名(从大到小
zincrbyscore 自增
zrem弹出一个元素
zinterstore给两个集合求交集
zlexcount计算有序集合中成员数量
zrangebylex返回指定区间内的成员
127.0.0.1:6379> ZADD k2 60 v1
(integer) 1
127.0.0.1:6379> ZSCORE k2 v1
"60"
127.0.0.1:6379> ZRANGE k1
(error) ERR wrong number of arguments for 'zrange' command
127.0.0.1:6379> ZADD k2 70 v2
(integer) 1
127.0.0.1:6379> ZADD k2 100 v5
(integer) 1
127.0.0.1:6379> ZRANGE k2 0 3
1) "v1"
2) "v2"
3) "v5"
127.0.0.1:6379> ZRANGE k2 0 2
1) "v1"
2) "v2"
3) "v5"
127.0.0.1:6379> ZRANGE k2 0 1
1) "v1"
2) "v2"
127.0.0.1:6379> ZRANGE k2 0 1 withscores
1) "v1"
2) "60"
3) "v2"
4) "70"
127.0.0.1:6379> ZCARD k2
(integer) 3
127.0.0.1:6379> ZCOUNT k2 0 2
(integer) 0
127.0.0.1:6379> ZCOUNT k2 0 1
(integer) 0
127.0.0.1:6379> ZCOUNT k2 60 90
(integer) 2
127.0.0.1:6379> ZCOUNT k2 (60 (90
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE k2 60 80
1) "v1"
2) "v2"
127.0.0.1:6379> ZRANK k2 
(error) ERR wrong number of arguments for 'zrank' command
127.0.0.1:6379> ZRANK k2 v2
(integer) 1
127.0.0.1:6379> ZRANK k2 v5
(integer) 2
127.0.0.1:6379> ZRANK k2 v5 v6
(error) ERR wrong number of arguments for 'zrank' command
127.0.0.1:6379> ZREVRANGE k2 v5
(error) ERR wrong number of arguments for 'zrevrange' command
127.0.0.1:6379> ZREVRANGE k2 v3
(error) ERR wrong number of arguments for 'zrevrange' command
127.0.0.1:6379> ZREVRANK k2 v5
(integer) 0
127.0.0.1:6379> ZREM k2 v5
(integer) 1
127.0.0.1:6379> ZLEXCOUNT k2 - +
(integer) 2
127.0.0.1:6379> ZLEXCOUNT k2 [v1 v]2
(error) ERR min or max not valid string range item
127.0.0.1:6379> ZLEXCOUNT k2 [v1 v2]
(error) ERR min or max not valid string range item
127.0.0.1:6379> ZRANGEBYLEX k2 - +
1) "v1"
2) "v2"
127.0.0.1:6379> 

Key的一些操作

del删除一个 key/value
dump序列化给定的 key
exists判断一个 key 是否存在
ttl查看一个 key 的有效期
expire给一个 key 设置有效期,如果 key 在过期之前被重新 set 了,则过期时间会失效。
persist移除一个 key 的过期时间
keys *查看所有的 key
pttl

和 ttl 一样,只不过这里返回的是毫秒

mget

获取多个key值
127.0.0.1:6379> del k1
(integer) 1
127.0.0.1:6379> dump k2
"\x0c\x19\x19\x00\x00\x00\x15\x00\x00\x00\x04\x00\x00\x02v1\x04\xfe<\x03\x02v2\x04\xfeF\xff\a\x00\xc9\xff\xb6\xe1j\x92\x00\x8a"
127.0.0.1:6379> EXISTS k1
(integer) 0
127.0.0.1:6379> ttl k2
(integer) -1
127.0.0.1:6379> EXPIRE k2 5555
(integer) 1
127.0.0.1:6379> PERSIST k2
(integer) 1
127.0.0.1:6379> KEYS *
1) "k2"
127.0.0.1:6379> EXPIRE k2 5555
(integer) 1
127.0.0.1:6379> ttl k2
(integer) 5552
127.0.0.1:6379> ttl k2
(integer) 5550
127.0.0.1:6379> ttl k2
(integer) 5549
127.0.0.1:6379> PERSIST k2
(integer) 1
127.0.0.1:6379> ttl k2
(integer) -1
##set命令,存数据,给变量赋值(x:99)【给单个变量赋值】
127.0.0.1:6379> set x 99
OK
##mset命令,存数据,给变量赋值(i:77),(j:88),(k:99)【给多个变量同时赋值】
127.0.0.1:6379> mset i 77 j 88 k 99
OK
##get命令,取数据,获取变量i的值【获取单个变量的值】
127.0.0.1:6379> get i
"77"
##mget命令,取数据,获取多个变量j,k,x的值【获取多个变量的值】
127.0.0.1:6379> mget j k x
1) "88"
2) "99"
3) "99"
##keys命令,显示所有的变量名【* 代表所有】
127.0.0.1:6379> keys *
1) "x"
2) "j"
3) "school"
4) "i"
5) "k"
##keys命令,显示变量名为一个字符的变量【? 代表单个字符】
127.0.0.1:6379> keys ?
1) "x"
2) "j"
3) "i"
4) "k"
##keys命令,显示变量名为六个字符的变量【? 代表单个字符】
127.0.0.1:6379> keys ??????
1) "school"
##keys命令,显示age的变量名,不存在,即为空
127.0.0.1:6379> keys age
(empty list or set)
##keys命令,显示school的变量名,存在
127.0.0.1:6379> keys school
1) "school"
##type命令,查看变量i的类型【string 为字符类型】
127.0.0.1:6379> type i
string
##set命令,存数据,给z赋值(z:10)【给单个变量赋值】
127.0.0.1:6379> set z 10
OK
##type命令,查看变量z的类型【string 为字符类型】
127.0.0.1:6379> type z
string
##lpush命令,存数据,给变量赋值(hostname:pc99,pc88)【列表类型】
127.0.0.1:6379> lpush hostname pc99 pc88
(integer) 2
##lrange命令,取数据,查看变量hostname下标为0到1的值
127.0.0.1:6379> lrange hostname 0 1
1) "pc88"
2) "pc99"
##type命令,查看变量hostname的类型【list 为列表类型】
127.0.0.1:6379> type hostname
list
##exists命令,检查变量是否存在,重复给一个变量赋值,会覆盖上一次变量的值
##返回值为1,代表变量存在;返回值为0,则代表变量不存在
127.0.0.1:6379> exists hostname
(integer) 1

 

 

  • 四种数据类型(list/set/zset/hash),在第一次使用时,如果容器不存在,就自动创建一个
  • 四种数据类型(list/set/zset/hash),如果里边没有元素了,那么立即删除容器,释放内存。

 Redis客户端

  • Jedis api 在线网址:http://tool.oschina.net/uploads/apidocs/redis/clients/jedis/Jedis.html
  • redisson 官网地址:https://redisson.org/
    • redisson git项目地址:https://github.com/redisson/redisson
  • lettuce 官网地址:https://lettuce.io/
    • lettuce git项目地址:https://github.com/lettuce-io/lettuce-core

概念:

  • Jedis:是Redis的Java实现客户端,提供了比较全面的Redis命令的支持,
  • Redisson:实现了分布式和可扩展的Java数据结构。
  • Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。

优点:

  • Jedis:比较全面的提供了Redis的操作特性
  • Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列
  • Lettuce:主要在一些分布式缓存框架上使用比较多

可伸缩:

  • Jedis:使用阻塞的I/O,且其方法调用都是同步的,程序流需要等到sockets处理完I/O才能执行,不支持异步。Jedis客户端实例不是线程安全的,所以需要通过连接池来使用Jedis。
  • Redisson:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Redisson的API是线程安全的,所以可以操作单个Redisson连接来完成各种操作
  • Lettuce:基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作

 Jedis 

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
    <type>jar</type>
    <scope>compile</scope>
 </dependency

对于 Jedis 而言,一旦连接上 Redis 服务端,剩下的操作都很容易了。 在 Jedis 中,由于方法的 API 和 Redis 的命令高度一致,所以,Jedis 中的方法见名知意,直接使用即 可。 

 public static void main(String[] args) {
        Jedis jedis = new Jedis("39.97.241.18");
       jedis.auth("foobared");
        System.out.println(jedis.ping());
    }

连接池 :在实际应用中,Jedis 实例我们一般都是通过连接池来获取,由于 Jedis 对象不是线城安全的,所以,当 我们使用 Jedis 对象时,从连接池获取 Jedis,使用完成之后,再还给连接池

  @Test
    void jedisPool() {
        JedisPool jedisPool = new JedisPool("10.0.16.83", 6379);
        Jedis jedis = jedisPool.getResource();
        System.out.println("连接是否成功:"+jedis.ping());
        jedis.close();
    }

工具类:

package com.liruilong.redis.demo;

import redis.clients.jedis.Jedis;

/**
 * @author Liruilong
 * @Date 2020/9/5 17:56
 * @Description: 函数接口,
 * 传递一个Redis对象,类似于 Runnable -> Thread
 * 类似策略模式,CallWithJedis可以看着为策略模板,
 */
@FunctionalInterface
public interface CallWithJedis {
    void call(Jedis jedis);

}
package demo;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Objects;

/**
 * @author Liruilong
 * @Date 2020/9/5 17:49
 * @Description: 线程安全的Redis池(单例)
 */

/**
 * @author Liruilong
 * @Date 2021/3/14 17:49
 * @Description: 线程安全的Redis池(单例)
 */
public final class Redis {
    private  volatile JedisPool pool;
    private static   volatile Redis redis = null;
    private  Redis() {
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        //连接池最大空闲数
        config.setMaxIdle(300);
        //最大连接数
        config.setMaxTotal(1000);
        //连接等待时间,-1 表示没限制
        config.setMaxWaitMillis(30000);
        //空闲时检查有效性
        config.setTestOnBorrow(true);
        // 地址,端口,超时时间,密码
        this.pool = new JedisPool(config,"39.97.241.18",6379,30000,"foobared");
    }
    public void execute(CallWithJedis callWithJedis){
        try (Jedis jedis = pool.getResource()){
            callWithJedis.call(jedis);
        }
    }

    public static Redis builder() {
        if (Objects.isNull(redis)) {
            synchronized (Redis.class) {
                if (Objects.isNull(redis)) {
                    return new Redis();
                }
            }
        }
         return  redis;
    }

}

Lettuce 

Lettuce 和 Jedis 的一个比较:
1. Jedis 在实现的过程中是直接连接 Redis 的,在多个线程之间共享一个 Jedis 实例,这是线城不安全的,如果想在多线程场景下使用 Jedis,就得使用连接池,这样,每个线城都有自己的 Jedis 实 例。

2. Lettuce 基于目前很火的 Netty NIO 框架来构建,所以克服了 Jedis 中线程不安全的问题,Lettuce 支持同步、异步 以及 响应式调用,多个线程可以共享一个连接实例。 使用 Lettuce,首先创建一个普通的 Maven 项目,添加 Lettuce 依赖:

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>5.2.2.RELEASE</version>
 </dependency>

public static void main(String[] args) {
        RedisClient redisClient = RedisClient.create(RedisURI.builder()
                .withHost("39.97.241.18")
                .withPort(6379)
                .withPassword("foobared").withTimeout(Duration.of(10,ChronoUnit.SECONDS)).build());
        StatefulRedisConnection<String, String> connect = redisClient.connect();
        RedisCommands<String, String> sync = connect.sync();
        sync.set("name","liruilong");
        System.out.println(sync.get("name"));


    }

Redis场景

Redis 做分布式锁

分布式锁也算是 Redis 比较常见的使用场景。
问题场景:
例如一个简单的用户操作,一个线城去修改用户的状态,首先从数据库中读出用户的状态,然后 在内存中进行修改,修改完成后,再存回去。在单线程中,这个操作没有问题,但是在多线程 中,由于读取、修改、存 这是三个操作,不是原子操作,所以在多线程中,这样会出问题。对于这种问题,我们可以使用分布式锁来限制程序的并发执行

基本用法

分布式锁实现的思路很简单,就是进来一个线城先占位,当别的线城进来操作时,发现已经有人占位 了,就会放弃或者稍后再试。为了防止业务代码在执行的时候抛出异常,我们给每一个锁添加了一个超时时间,超时之后,锁会被自 动释放,但是这也带来了一个新的问题:

public static void main(String[] args) {

        Redis redis = Redis.builder();
        redis.execute(red -> {
            Long setnx = red.setnx("Key", "V1");
            // 没有人占位
            if (setnx == 1) {
                // 设置过期时间
                red.expire("Key", 5);
                red.set("name", "liruilong");
                System.out.println(red.get("name"));
                red.del("Key");
                System.out.println("没人占位");
            } else {
                // 有人占位, 停止、暂缓 操作
                System.out.println("有人占位, 停止、暂缓 操作");
            }
        });
    }

这样改造之后,还有一个问题,就是在获取锁和设置过期时间之间如果如果服务器突然挂掉了,这个时 候锁被占用,无法及时得到释放,也会造成死锁,因为获取锁和设置过期时间是两个操作,不具备原子性
为了解决这个问题,从 Redis2.8 开始,setnx 和 expire 可以通过一个命令一起来执行了,我们对上述 代码再做改进:

package com.liruilong.redis.demo;


import redis.clients.jedis.params.SetParams;

import java.util.Objects;

/**
 * @author Liruilong
 * @Date 2020/9/5 14:57
 * @Description:
 */
public class Myredis {

    public static void main(String[] args) {

        Redis redis = Redis.builder();
        redis.execute(red -> {
           String set= red.set("Key","V1",new SetParams().nx().ex(5));
            // 没有人占位
            if (Objects.nonNull(set) && "OK".equals(set)) {
                red.set("name", "liruilong");
                System.out.println(red.get("name"));
                red.del("Key");
                System.out.println("没人占位");
            } else {
                // 有人占位, 停止、暂缓 操作
                System.out.println("有人占位, 停止、暂缓 操作");
            }
        });
    }
}

解决超时问题

如果要执行的业务非常耗时,可能会出现紊乱。举个例子:第 一个线程首先获取到锁,然后开始执行业务代码,但是业务代码比较耗时,执行了 8 秒,这样,会在第 一个线程的任务还未执行成功锁就会被释放了,此时第二个线程会获取到锁开始执行,在第二个线程刚 执行了 3 秒,第一个线程也执行完了,此时第一个线程会释放锁,但是注意,它释放的第二个线程的 锁,释放之后,第三个线程进来。

对于这个问题,我们可以从两个角度入手:

  • 尽量避免在获取锁之后,执行耗时操作。 可以在锁上面做文章,将锁的 value 设置为一个随机字符串,每次释放锁的时候,都去比较随机 字符串是否一致,如果一致,再去释放,否则,不释放。
  • 对于第二种方案,由于释放锁的时候,
    • 要去查看锁的 value,
    • 第二个比较 value 的值是否正确,
    • 第三步 释放锁,

有三个步骤,很明显三个步骤不具备原子性,为了解决这个问题,我们得引入 Lua 脚本

  • Lua 脚本的优势: 使用方便,Redis 中内置了对 Lua 脚本的支持。 Lua 脚本可以在 Redis 服务端原子的执行多个 Redis 命令。 由于网络在很大程度上会影响到 Redis 性能,而使用 Lua 脚本可以让多个命令一次执行,可以有 效解决网络给 Redis 带来的性能问题。 在 Redis 中,使用 Lua 脚本大致上两种思路:
  • . 提前在 Redis 服务端写好 Lua 脚本,然后在 Java 客户端去调用脚本(推荐)。
  • . 可以直接在 Java 端去写 Lua 脚本,写好之后,需要执行时,每次将脚本发送到 Redis 上去执行
package demo;

import redis.clients.jedis.params.SetParams;

import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;

/**
 * @author Liruilong
 * @Date 2020/9/16 13:54
 * @Description:
 */
public class LuaTest {
    public static void main(String[] args) {
        Redis redis = Redis.builder();
        for (int i = 0; i < 2; i++) {
            int finalI = i;
            redis.execute(o -> {
                String string = UUID.randomUUID().toString();
                //获取锁
                String key = o.set("Key", string, new SetParams().nx().ex(5));
                // 判断是否拿到锁
                if (Objects.nonNull(key) && "OK".equals(key)) {
                    // 业务模块
                    o.set("name", "liruilong");
                    System.out.println("第"+ finalI +"次调用"+o.get("name"));
                    // 释放锁,sh1校验和,
                  //编写脚本方式  o.eval("lua脚本");
                    o.evalsha("b8059ba43af6ffe8bed3db65bac35d452f8115d8", Arrays.asList("Key"), Arrays.asList(string));
                } else {
                    // 没拿到锁有人占位, 停止、暂缓 操作
                    System.out.println("第"+ finalI +"次调用"+"没拿到锁有人占位, 停止、暂缓 操作");
                }
            });
        }
    }
    /*Lua脚本
    if redis.call("get",KEYS[1]==ARGV[1]) then
        retuen redis.call("del",KEYS[1])
    else
        return 0
    end
    cat lua/releasewherevalueequal.lua | redis-cli -a javaboy script load --pipe
     */
}

  lua脚本,script load 这个命令会在 Redis 服务器中缓存 Lua 脚本,并返回脚本内容的 SHA1 校验和,然后在 Java 端调用时,传入 SHA1 校验和作为参数,这样 Redis 服务端就知道执行哪个脚本了。

感觉这个有问题,lua脚本有问题,锁没有被释放,但是思路没问题,以后在研究 

Redis 做消息队列;

我们平时说到消息队列,一般都是指 RabbitMQ、RocketMQ、ActiveMQ 以及大数据里边的 Kafka, 这些是我们比较常见的消息中间件,也是非常专业的消息中间件,作为专业的中间件,它里边提供了许 多功能。
 但是,当我们需要使用消息中间件的时候,并非每次都需要非常专业的消息中间件,假如我们只有一个 消息队列,只有一个消费者,那就没有必要去使用上面这些专业的消息中间件,这种情况我们可以直接 使用 Redis 来做消息队列。

Redis 的消息队列不是特别专业,他没有很多高级特性,适用简单的场景,如果对于消息可靠性有着极 高的追求,那么不适合使用 Redis 做消息队列。 

消息队列

Redis 做消息队列,使用它里边的 List 数据结构就可以实现,我们可以使用 lpush/rpush 操作来实现入 队,然后使用 lpop/rpop 来实现出队。
在客户端(例如 Java 端),我们会维护一个死循环来不停的从队列中读取消息,并处理,如果队列中 有消息,则直接获取到,如果没有消息,就会陷入死循环,直到下一次有消息进入,这种死循环会造成 大量的资源浪费,这个时候,我们可以使用之前讲的 blpop/brpop 

package demo.redis_msglist;

import demo.Redis;


public class msgList {

    public static void   producer(){
        Redis.builder().execute( redis ->{
            for (int i = 0; i < 2; i++) {
                Long ajax = redis.lpush("producer", "ajax" + i);
                System.out.println("生产消息数:"+ajax);
            }
        });
    }

    public static  void  consumer(){
        Redis.builder().execute( redis ->{
            for (int i = 0; i < 3; i++) {
                System.out.println("消费消息"+redis.rpop("producer"));
            }
        });
    }

    public static void main(String[] args) {
        producer();
        consumer();
    }

}

这里为了演示,这样写,但是实际情况我们会使用死循环不停的去  redis.rpop("producer") 获取消息,这里有一个问题,就是当没有消息时,会一直死循环下去。会出现多次无意义的从连接池里获取连接。解决办法是使用brpop和blpop实现阻塞读取(重要)。当没有消息可供消费是,进入阻塞,一直保持连接,不会一直循环。

brpop命令可以接收多个键,其完整的命令格式为 BRPOP key [key ...] timeout,如:brpop key1 0。意义是同时检测多个键,如果所有键都没有元素则阻塞,如果其中一个有元素则从该键中弹出该元素(会按照key的顺序进行读取,可以实现具有优先级的队列)

package demo.redis_msglist;

import demo.Redis;


public class msgListBlpop {
    public static void   producer(){
        Redis.builder().execute( redis ->{
            for (int i = 0; i < 2; i++) {
                Long ajax = redis.lpush("producer", "ajax" + i);
                System.out.println("生产消息数:"+ajax);
            }
        });
    }
    public static  void  consumer(){
        Redis.builder().execute( redis ->{
            System.out.println("消费消息:");
            for (int i = 0; i < 3; i++) {
                System.out.println(redis.brpop(5,"producer")+ " ");
            }
        });
    }
    public static void main(String[] args) {
        producer();
        consumer();
    }
}

延迟消息队列

延迟队列可以通过 zset 来实现,因为 zset 中有一个 score,我们可以把时间作为 score,将 value 存到 redis 中,然后通过轮询的方式,去不断的读取消息出来。
首先,如果消息是一个字符串,直接发送即可,如果是一个对象,则需要对对象进行序列化,这里我们 使用 JSON 来实现序列化和反序列化  

package com.liruilong.redis.demo.mq;

/**
 * @author Liruilong
 * @Date 2020/9/16 14:07
 * @Description:
 */
public class MqDO {
    private String id;
    private Object data;

    public String getId() {
        return id;
    }

    public MqDO setId(String id) {
        this.id = id;
        return this;
    }

    public Object getData() {
        return data;
    }

    public MqDO setData(Object data) {
        this.data = data;
        return this;
    }

    @Override
    public String toString() {
        return "MqDO{" +
                "id='" + id + '\'' +
                ", data=" + data +
                '}';
    }
}

 消息的生产消费

package demo.mq;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import demo.Redis;
import redis.clients.jedis.Jedis;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author Liruilong
 * @Date 2020/9/16 14:10
 * @Description:
 */
public class Mq {
    private Jedis jedis;
    private String queuy;

    public Mq(Jedis jedis, String queuy) {
        this.jedis = jedis;
        this.queuy = queuy;
    }

    /**
     * <per>
     * <p>消息入队</p>
     * <per/>
     * @param data
     * @return void
     * @throws
     * @Description : TODO
     * @author Liruilong
     * @Date 2020/9/16 14:17
     **/
    public void queue(Object data) {
        MqDO mqDO = new MqDO().setId(UUID.randomUUID().toString()).setData(data);
        try {
            //对象序列化
            String s = new ObjectMapper().writeValueAsString(mqDO);
            System.out.println(LocalDateTime.now().toString()+ s);
            jedis.zadd(queuy, System.currentTimeMillis() + 5000, s);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    /**
     * <per>
     * <p>消息出队消费</p>
     * <per/>
     * @param
     * @return void
     * @throws
     * @Description : TODO
     * @author Liruilong
     * @Date 2020/9/16 14:32
     **/
    public void loop() {
        while (!Thread.interrupted()) {
            // 读取score 在当前时间和0之间的时间
            Set<String> zrange = jedis.zrangeByScore(queuy, 0, System.currentTimeMillis(), 0, 1);
            if (zrange.isEmpty()) {
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    break;
                }
                continue;
            }
            String next = zrange.iterator().next();
            // 多线程情况下的处理,如果还在的话,且弹出成功
            if (jedis.zrem(queuy, next) > 0) {
                try {
                    MqDO mqDO = new ObjectMapper().readValue(next, MqDO.class);
                    System.out.println(LocalDateTime.now().toString()+mqDO+"被消费了");
                } catch (JsonProcessingException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /*
     * @return
     * @Description 测试
     * @author Liruilong
     * @date  2021/3/15  21:54
     **/
    public static void main(String[] args) {
        new Redis().execute(redis -> {
            //消息队列
            Mq mq = new Mq(redis, "LiRuilong");
            // 生产消息
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    mq.queue("生产消息-" + i);
                }
            }).start();
            // 消费消息
            Thread thread = new Thread(() -> mq.loop());
            thread.start();

            //模拟结束任务。
            try {
                TimeUnit.MILLISECONDS.sleep(7000);
                thread.interrupt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        });
    }
}

测试

 /*
     * @return
     * @Description 测试
     * @author Liruilong
     * @date  2021/3/15  21:54
     **/
    public static void main(String[] args) {
        new Redis().execute(redis -> {
            //消息队列
            Mq mq = new Mq(redis, "LiRuilong");
            // 生产消息
            new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    mq.queue("生产消息-" + i);
                }
            }).start();
            // 消费消息
            Thread thread = new Thread(() -> mq.loop());
            thread.start();

            //模拟结束任务。
            try {
                TimeUnit.MILLISECONDS.sleep(7000);
                thread.interrupt();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


        });
    }

Bit 操作

实例:

用户一年的签到记录,如果你用 string 类型来存储,那你需要 365 个 key/value,操作起来麻烦。通过 位图可以有效的简化这个操作。 它的统计很简单: 01111000111 每天的记录占一个位,365 天就是 365 个位,大概 46 个字节,这样可以有效的节省存储空间,如果有 一天想要统计用户一共签到了多少天,统计 1 的个数即可。 对于位图的操作,可以直接操作对应的字符串(get/set),可以直接操作位(getbit/setbit).,类似于bitMap的思想,有时也称位域

零存整取 :例如存储一个 Java 字符串:

顺序和二进制计算的顺序是相反的。

整存零取 存一个字符串进去,但是通过位操作获取字符串。

签到记录: 01111000111 1 表示签到的天,0 表示没签到,统计总的签到天数: 可以使用 bitcount

bitcount 中,可以统计的起始位置,但是注意,这个起始位置是指字符的起始位置而不是 bit 的起始位 置。 除了 bitcount 之外,还有一个 bitpos。bitpos 可以用来统计在指定范围内出现的第一个 1 或者 0 的位 置,这个命令中的起始和结束位置都是字符索引,不是 bit 索引,一定要注意。

127.0.0.1:6379> get k1
"R"
127.0.0.1:6379> bitcount k1
(integer) 3
127.0.0.1:6379> bitcount name
(integer) 42
127.0.0.1:6379> 

Bit 批处理:在 Redis 3.2 之后,新加了一个功能叫做 bitfield,可以对 bit 进行批量操作。

表示获取 name 中的位,从 0 开始获取,获取 4 个位,返回一个无符号数字。  BITFIELD name get u4 0

  • u 表示无符号数字
  • i 表示有符号数字,有符号的话,第一个符号就表示符号位,1 表示是一个负数。

bitfiled 也可以一次执行多个操作。

127.0.0.1:6379> bitfield name get u4 0 get u5 6 get i2 7
1) (integer) 6
2) (integer) 19
3) (integer) 0
127.0.0.1:6379> 

用无符号的 98 转成的 8 位二进制数字,代替从第 8 位开始接下来的 8 位数字。

127.0.0.1:6379> bitfield name set u8 8 98
1) (integer) 105
127.0.0.1:6379> 

INCRBY: 对置顶范围进行自增操作,自增操作可能会出现溢出,既可能是向上溢出,也可能是向下溢出。Redis 中对于溢出的处理方案是折返。8 位无符号数 255 加 1 溢出变为 0;8 位有符号数 127,加 1 变为 - 128. 也可以修改默认的溢出策略,可以改为 fail ,表示执行失败。

HyperLogLog

一般我们评估一个网站的访问量,有几个主要的参数:

  • pv,Page View,网页的浏览量
  • uv,User View,访问的用户

一般来说,pv 或者 uv 的统计,可以自己来做,也可以借助一些第三方的工具,比如 cnzz,友盟 等。如果自己实现,pv 比较简单,可以直接通过 Redis 计数器就能实现。但是 uv 就不一样,uv 涉及到另外一个问题,去重。我们首先需要在前端给每一个用户生成一个唯一 id,无论是登录用户还是未登录用户,都要有一个唯一id,这个 id 伴随着请求一起到达后端,在后端我们通过 set 集合中的 sadd 命令来存储这个 id,最后通过 scard 统计集合大小,进而得出 uv 数据

如果是千万级别的 UV,需要的存储空间就非常惊人。而且,像 UV 统计这种,一般也不需要特别精确800w 的 uv 和 803w 的 uv,其实差别不大。所以,我们要介绍今天的主角---HyperLogLog

Redis 中提供的 HyperLogLog 就是专门用来解决这个问题的,HyperLogLog 提供了一套不怎么精确但是够用的去重方案,会有误差,官方给出的误差数据是 0.81%,这个精确度,统计 UV 够用了。

HyperLogLog 主要提供了两个命令:pfadd 和 pfcount。

  • pfadd 用来添加记录,类似于 sadd ,添加过程中,重复的记录会自动去重。
  • pfcount 则用来统计数据。
  • pfmerge ,合并多个统计结果,在合并的过程中,会自动 去重多个集合中重复的元素。
127.0.0.1:6379> pfadd uv u0 u1 u2 u3 u4
(integer) 1
127.0.0.1:6379> pfcount uv
(integer) 5
127.0.0.1:6379> 

数据量少的时候看不出来误差。

在 Java 中,我们多添加几个元素:

package demo;

public class Uvcontent {

    public static void main(String[] args) {
        Redis.builder().execute( redis ->{
            for (int i = 0; i <1000 ; i++) {
                redis.pfadd("Uv","uv"+i);
            }
            System.out.println(redis.pfcount("Uv"));

        });
    }
}

布隆过滤器

我们用 HyperLogLog 来估计一个数,有偏差但是也够用。HyperLogLog 没有判断是否包含的方法,例如 pfexists 、pfcontains 等。没有这样的方法存在,但是我们有这样的业务需求。

例如刷今日头条,推送的内容有相似的,但是没有重复的。这就涉及到如何在推送的时候去重?

解决方案很多,例如将用户的浏览历史记录下来,然后每次推送时去比较该条消息是否已经给用户推送

了。但是这种方式效率极低,不推荐。解决这个问题,就要靠我们今天要说的布隆过滤器

Bloom Filter 介绍

Bloom Filter 专门用来解决我们上面所说的去重问题的,使用 Bloom Filter 不会像使用缓存那么浪费空间。当然,他也存在一个小小问题,就是不太精确。Bloom Filter 相当于是一个不太精确的 set 集合,我们可以利用它里边的 contains 方法去判断某一个对象是否存在,但是需要注意,这个判断不是特别精确。一般来说,通过 contains 判断某个值不存在,那就一定不存在,但是判断某个值存在的话,则他可能不存在。

以今日头条为例,假设我们将用户的浏览记录用 B 表示,A 表示用户没有浏览的新闻,现在要给用户推

送消息,先去 B 里边判断这条消息是否已经推送过,如果判断结果说没推送过(B 里边没有这条记

录),那就一定没有推送过。如果判断结果说有推送过(B 里边也有可能没有这条消息),这个时候该

条消息就不会推送给用户,导致用户错过该条消息,当然这是概率极低的。

Bloom Filter 原理

每一个布隆过滤器,在 Redis 中都对应了一个大型的位数组以及几个不同的 hash 函数。

所谓的 add 操作是这样的: 首先根据几个不同的 hash 函数给元素进行 hash 运算一个整数索引值,拿到这个索引值之后,对位数 组的长度进行取模运算,得到一个位置,每一个 hash 函数都会得到一个位置,将位数组中对应的位置 设置位 1 ,这样就完成了添加操作。

当判断元素是否粗存在时,依然先对元素进行 hash 运算,将运算的结果和位数组取模,然后去对应的 位置查看是否有相应的数据,如果有,表示元素可能存在(因为这个有数据的地方也可能是其他元素存 进来的),如果没有表示元素一定不存在。

Bloom Filter 中,误判的概率和位数组的大小有很大关系,位数组越大,误判概率越小,当然占用的存 储空间越大;位数组越小,误判概率越大,当然占用的存储空间就小

Bloom Filter 安装

https://oss.redislabs.com/redisbloom/Quick_Start/

这里给大家介绍两种安装方式:

1. Docker:

docker run -p 6379:6379 --name redis-redisbloom redislabs/rebloom:latest

2. 自己编译安装:

cd redis-5.0.7
git clone https://github.com/RedisBloom/RedisBloom.git
cd RedisBloom/
make
cd ..
redis-server redis.conf --loadmodule ./RedisBloom/redisbloom.so

安装完成后,执行 bf.add 命令,测试安装是否成功。

每次启动时都输入 redis-server redis.conf --loadmodule ./RedisBloom/redisbloom.so 比较

麻烦,我们可以将要加载的模块在 redis.conf 中提前配置好。

 

 

 

 

 

 

 

 

 

 

redis安装:四种方式获取一个 Redis:

  • 1. 直接编译安装(推荐使用)
wget http://download.redis.io/releases/redis-5.0.7.tar.gz
tar -zxvf redis-5.0.7.tar.gz
cd redis-5.0.7/
make
make install
  • 2. 使用 Docker
    • docker run --name javaboy-redis -d -p 6379:6379 redis --requirepass 123
      redis-cli -a 123
      docker exec -it javaboy-redis redis-cli -a 123
      
  • 3. 也可以直接安装
    • yum install redis
  • 4. 还有一个在线体验的方式,通过在线体验,可以直接使用 Redis 的功能
    • http://try.redis.io/
[root@liruilong etc]# vim redis
redis.conf           redis-sentinel.conf  
[root@liruilong etc]# vim redis.conf 
[root@liruilong etc]# 

直接编译安装

###查询源码安装软件gcc是否安装,没有则安装gcc
[root@vpc50 ~]# rpm -q gcc || yum -y install gcc
##解压redis源码包
[root@vpc50 ~]# tar -zxvf redis-4.0.8.tar.gz
##进入到源码解压目录下,编译并且安装
[root@vpc50 ~]# cd redis-4.0.8/
[root@vpc50 redis-4.0.8]# make && make install
##redis-双Tab键,redis使用命令出现,安装成功
[root@vpc50 redis-4.0.8]# redis-【双Tab键】
redis-benchmark redis-check-rdb redis-sentinel
redis-check-aof redis-cli redis-server                     

初始配置

配置服务运行参数 ./utils/install_server.sh #执行源码目录下的初始化脚本 

  • 端口 6379
  • 主配置文件 /etc/redis/6379.conf
  • 日志文件 /var/log/redis_6379.log
  • 数据库目录 /var/lib/redis/6379
  • 服务启动程序 /usr/local/bin/redis-server
  • 命令行连接命令 /usr/local/bin/redis-cli
###查看源码目录下的内容
1.2.3 管理服务
[root@vpc50 redis-4.0.8]# ls
00-RELEASENOTES INSTALL runtest tests
BUGS Makefile runtest-cluster utils
CONTRIBUTING MANIFESTO runtest-sentinel
COPYING README.md sentinel.conf
deps redis.conf src
###运行初始化脚本,一路回车即可
[root@vpc50 redis-4.0.8]# ./utils/install_server.sh
Welcome to the redis service installer
This script will help you easily set up a running redis server
##选择redis端口号:【6379】 回车确认
Please select the redis port for this instance: [6379]
Selecting default: 6379
##选择redis主配置文件:【/etc/redis/6379.conf】 回车确认
Please select the redis config file name [/etc/redis/6379.conf]
Selected default - /etc/redis/6379.conf
##选择redis日志文件:【/var/log/redis_6379.log】 回车确认
Please select the redis log file name [/var/log/redis_6379.log]
Selected default - /var/log/redis_6379.log
##选择redis数据库目录:【/var/lib/redis/6379】 回车确认
Please select the data directory for this instance [/var/lib/redis/6379]
Selected default - /var/lib/redis/6379
##选择redis启动程序:【/usr/local/bin/redis-server】 回车确认
Please select the redis executable path [/usr/local/bin/redis-server]
##以上选择的配置
Selected config:
Port : 6379
Config file : /etc/redis/6379.conf
Log file : /var/log/redis_6379.log
Data dir : /var/lib/redis/6379
Executable : /usr/local/bin/redis-server
Cli Executable : /usr/local/bin/redis-cli
##确认,则选择回车即可;否则按 Ctrl + C 重新配置
Is this ok? Then press ENTER to go on or Ctrl-C to abort.
Copied /tmp/6379.conf => /etc/init.d/redis_6379
Installing service...
Successfully added to chkconfig!
Successfully added to runlevels 345!
Starting Redis server...
Installation successful! #安装成功

##查看redis的启动端口号
[root@vpc50 redis-4.0.8]# ss -ntulp | grep redis
tcp LISTEN 0 128 127.0.0.1:6379 *:*
users:(("redis-server",pid=4585,fd=6))

管理服务

  •  ps -C redis-server  //查看进程
  •  netstat -utnlp I grep :6379  //查看端口
  •  /etc/init.d/redis 6379 start  //启动服务
  •  /etc/init.d/redis-6379 stop   //停止服务
################### 管理redis服务,在vpc50上操作 #####################
##查看redis的进程号
[root@vpc50 redis-4.0.8]# ps -C redis-server
PID TTY TIME CMD
4585 ? 00:00:00 redis-server
##通过脚本的方式停止redis的服务
[root@vpc50 redis-4.0.8]# /etc/init.d/redis_6379 stop
Stopping ...
Redis stopped
##再次查看redis的进程号,消失,服务处于停止状态
[root@vpc50 redis-4.0.8]# ps -C redis-server
PID TTY TIME CMD
##重新开启redis服务
[root@vpc50 redis-4.0.8]# /etc/init.d/redis_6379 start
Starting Redis server...
##查看redis的进程号,进程号存在,服务处于开启状态
[root@vpc50 redis-4.0.8]# ps -C redis-server
PID TTY TIME CMD
4625 ? 00:00:00 redis-server

安装完成后,启动 Redis:

 daemonize yes  : 表示以守护进程的方式启动Redis

[root@liruilong redis-5.0.7]# vim  redis.conf 
[root@liruilong redis-5.0.7]# redis-server redis.conf 
27655:C 23 Nov 2020 10:42:26.236 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
27655:C 23 Nov 2020 10:42:26.236 # Redis version=5.0.7, bits=64, commit=00000000, modified=0, pid=27655, just started
27655:C 23 Nov 2020 10:42:26.236 # Configuration loaded
[root@liruilong redis-5.0.7]# red
red                        redis-benchmark            redis-check-rdb            redis-sentinel             
redhat_lsb_trigger.x86_64  redis-check-aof            redis-cli                  redis-server               
[root@liruilong redis-5.0.7]# red
red                        redis-benchmark            redis-check-rdb            redis-sentinel             
redhat_lsb_trigger.x86_64  redis-check-aof            redis-cli                  redis-server               
[root@liruilong redis-5.0.7]# redis-cli 
127.0.0.1:6379> 

开启远程连接 

Redis 默认是不支持远程连接的,需要手动开启
一共修改两个地方:

  • 1. 注释掉 bind: 127.0.0.1
  • 2. 开启密码校验,去掉 requirepass 的注释 改完之后,保存退出,启动 Redis

配置文件解析

 

© 2018-2020 liruilong@aliyun.com,All rights reserved.
 本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
 

  • 0
    点赞
  • 0
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页

打赏作者

山河已无恙

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值