Redis基础使用

Redis如何使用

redis中文网:http://www.redis.cn/

视频学习网站:狂神说

安装Redis步骤

redis的基本知识

Redis可以用作数据库缓存消息中间件
redis默认有16个数据库

使用select 切换数据库,dbsize查看数据库大小

127.0.0.1:6379> select 3	#切换数据库
OK
127.0.0.1:6379[3]> dbsize	#查看数据库大小
(integer) 0
127.0.0.1:6379[3]> 

keys * 查看所有的key

127.0.0.1:6379> keys *	#查看所有的key
1) "name2"
2) "key:__rand_int__"
3) "mylist"
4) "counter:__rand_int__"
5) "name"
6) "myhash"
127.0.0.1:6379> 

清除当前的数据库 flushdb
清除所有的数据库flushall

Redis是单线程的

Redis是基于内存操作,CPU不是Redis性能的瓶颈,Redis主要依赖于机器的内存和网络的带宽,所以使用单线程。

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

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

五大数据类型

Redis-Key

EXISTS

127.0.0.1:6379[3]> set name wangwu
OK
127.0.0.1:6379[3]> set address hujian
OK
127.0.0.1:6379[3]> keys *
1) "name"
2) "address"
127.0.0.1:6379[3]> exists name	#判断改字段是否存在,存在返回 1,不存在返回 0
(integer) 1
127.0.0.1:6379[3]> exists name2
(integer) 0

MOVE

127.0.0.1:6379[3]> move name 1	#移动数据,将name移动到数据1 中
(integer) 1
127.0.0.1:6379[3]> keys * 
1) "address"
127.0.0.1:6379[3]> select 0		#切换到 数据库1
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> get name
"wangwu"

DEL

127.0.0.1:6379[3]> keys *
1) "no2"
2) "user:1:name"
3) "user:1:age"
4) "no1"
5) "no3"
6) "user:1"

127.0.0.1:6379[3]> del no1 no2 no3		#删除字段
(integer) 3
127.0.0.1:6379[3]> keys *
1) "user:1:name"
2) "name"
3) "user:1:age"
4) "user:1"

EXPIRE、TTL

127.0.0.1:6379[3]> keys *
1) "name"
2) "address"
127.0.0.1:6379[3]> expire name 10	# expire 设置字段定时删除
(integer) 1
127.0.0.1:6379[3]> ttl name		# ttl 查看字段还有多久删除
(integer) 5
127.0.0.1:6379[3]> ttl name
(integer) 3
127.0.0.1:6379[3]> ttl name
(integer) -2
127.0.0.1:6379[3]> get name
(nil)

String(字符串)

APPEND、STRLEN

127.0.0.1:6379[3]> set code wangwu
OK
127.0.0.1:6379[3]> keys *
1) "code"

#-----------------------------------------------------------------------------------------------
127.0.0.1:6379[3]> append code " wangt to"	# 往code字段后添加字符串
(integer) 15
127.0.0.1:6379[3]> get code
"wangwu wangt to"

#-----------------------------------------------------------------------------------------------
127.0.0.1:6379[3]> strlen code	# 获得字段的长度
(integer) 15

#-----------------------------------------------------------------------------------------------
127.0.0.1:6379[3]> append word "see world"	# 当数据表中不存在该字段,则会创建一个字段
(integer) 9
127.0.0.1:6379[3]> keys *
1) "code"
2) "word"

INCR、DECR、INCRBY、DECRBY

127.0.0.1:6379[3]> set count 0	#创建一个字段
OK
127.0.0.1:6379[3]> get count
"0"
127.0.0.1:6379[3]> incr count 	#字段值+1,相当于count++
(integer) 1
127.0.0.1:6379[3]> incr count
(integer) 2
127.0.0.1:6379[3]> incr count
(integer) 3
127.0.0.1:6379[3]> decr count	#字段值-1,相当于count--
(integer) 2
127.0.0.1:6379[3]> get count
"2"

#------------------------------------------------------------------------------------------------
127.0.0.1:6379[3]> decrby count 2	#步长,一次减少n,相当于 count-=2
(integer) 0
127.0.0.1:6379[3]> incrby count 10	#步长,一次增加n,相当于 count+=10
(integer) 10
127.0.0.1:6379[3]> get count
"10"
127.0.0.1:6379[3]> incrby count 5
(integer) 15
127.0.0.1:6379[3]> get count 
"15"

GETRANGE、SETRANGE

127.0.0.1:6379[3]> set key1 hello,world
OK
127.0.0.1:6379[3]> get key1		
"hello,world"
127.0.0.1:6379[3]> getrange key1 0 5	#获取 0,5 的字段值
"hello,"
127.0.0.1:6379[3]> getrange key1 0 -1	#获取 0,-1 的字段值,等于获取全部
"hello,world"

#--------------------------------------------------------------------------
127.0.0.1:6379[3]> set key2 "wangwu,see world"
OK
127.0.0.1:6379[3]> setrange key2 7 "travle world"	#替换,从第七个位置开始替换
(integer) 19
127.0.0.1:6379[3]> get key2
"wangwu,travle world"

SETEX、SETNX

SETEX:设置过期时间
SETNX:不存在则创建,存在不创建(在分布式锁中常使用)

127.0.0.1:6379[3]> setex key3 10 "bye bye"		#设置key3 的值为bye bye ,10秒后过期
OK
127.0.0.1:6379[3]> ttl key3		#ttl查看kye3还有多久过期
(integer) 7
127.0.0.1:6379[3]> get key3
"bye bye"
127.0.0.1:6379[3]> setnx key4 "create one"		#先判断key4 是否存在,存在则创建 key4,否则创建失败
(integer) 1
127.0.0.1:6379[3]> keys *
1) "key1"
2) "key4"
3) "key2"
127.0.0.1:6379[3]> setnx key4 "create two"
(integer) 0
127.0.0.1:6379[3]> keys *
1) "key1"
2) "key4"
3) "key2"

MSET、MGET

127.0.0.1:6379[3]> keys *
(empty array)
127.0.0.1:6379[3]> mset no1 wangwu no2 lishi no3 zhangsan	# mset 一次创建多个值,k,v 对应
OK
127.0.0.1:6379[3]> keys *
1) "no2"
2) "no3"
3) "no1"
127.0.0.1:6379[3]> get no2
"lishi"
127.0.0.1:6379[3]> mget no1 no3		# mget 一次获取多个值
1) "wangwu"
2) "zhangsan"
127.0.0.1:6379[3]> msetnx no1 zhaosi no4 xiaoming		# msetnx,先判断再创建,具有原子性,都不存在,才能创建成功。 
(integer) 0
127.0.0.1:6379[3]> keys *
1) "no2"
2) "no3"
3) "no1"

#--------------------------------------------------------------------------
# 创建一个对象,方法一:在value中放入一个 json字符串
127.0.0.1:6379[3]> set user:1 {name:zhangshan,age:30}
OK
127.0.0.1:6379[3]> get user:1
"{name:zhangshan,age:30}"

#创建一个对象,方法二:巧妙设计key( user:id:filed ),可以通过key 直接获取到value
127.0.0.1:6379[3]> mset user:1:name roby user:1:age 18
OK
127.0.0.1:6379[3]> keys *
1) "user:1:name"
2) "user:1:age"
3) "user:1"
127.0.0.1:6379[3]> mget user:1:name user:1:age
1) "roby"
2) "18"

GETSET

getset:先获取再设置值

127.0.0.1:6379[3]> keys *
1) "no2"
2) "user:1:name"
3) "user:1:age"
4) "no1"
5) "no3"
6) "user:1"
127.0.0.1:6379[3]> getset name dawang	#如果值不存在,则返回nil,创建这个值
(nil)
127.0.0.1:6379[3]> get name
"dawang"
127.0.0.1:6379[3]> getset name fullPerson	#如果存在值,获取原来的值,并设置新的值
"dawang"
127.0.0.1:6379[3]> get name
"fullPerson"

List

List 可以用作堆、队列、阻塞队列。
List命令的开头 以 l 开始

  • list实际上是一个链表(before node after),在left和right都可以插入值
  • 如果key不存在,创建新的链表
  • 如果key存在,新增内容
  • 如果移除了所有值,空链表,表示不存在
  • 在两边插入值或改动值,效率最高。

LPUSH、RPUSH、LRANGE

127.0.0.1:6379[3]> lpush list1 one two 	# lpush ,从左往右添加值
(integer) 2
127.0.0.1:6379[3]> lrange list1 0 -1	# 获取list的值,0,-1获取全部
1) "two"
2) "one"
127.0.0.1:6379[3]> rpush list1 rone  	#rpush,从右往左添加值
(integer) 3
127.0.0.1:6379[3]> lrange list1 0 -1
1) "two"
2) "one"
3) "rone"
127.0.0.1:6379[3]> lpush list1 three
(integer) 4
127.0.0.1:6379[3]> lrange list1 0 -1
1) "three"
2) "two"
3) "one"
4) "rone"

LPOP、RPOP

127.0.0.1:6379[3]> lrange list1 0 -1
1) "three"
2) "two"
3) "one"
4) "rone"

127.0.0.1:6379[3]> lpop list1	#lpop,从左移除值 
"three"
127.0.0.1:6379[3]> rpop list1	#rpop,从右移除值
"rone"
127.0.0.1:6379[3]> lrange list1 0 -1
1) "two"
2) "one"

#--------------------------------------------------------------------------------------
127.0.0.1:6379> lpush list2 zhangsan lisi wangwu xiaoming roby
(integer) 5
127.0.0.1:6379> lrange list2 0 -1
1) "roby"
2) "xiaoming"
3) "wangwu"
4) "lisi"
5) "zhangsan"
127.0.0.1:6379> lpop list2 2	# lpop,从左往右移除两个值
1) "roby"
2) "xiaoming"
127.0.0.1:6379> lrange list2 0 -1
1) "wangwu"
2) "lisi"
3) "zhangsan"

LINDEX

127.0.0.1:6379> lrange list1 0 -1
1) "wangwu"
2) "lisi"
3) "zhangsan"
127.0.0.1:6379> lindex list1 2		# linde,查看下标为2
的值
"zhangsan"
127.0.0.1:6379> 

LLEN

127.0.0.1:6379> lrange l3 0 -1
1) "sex"
2) "age"
3) "name"
127.0.0.1:6379> llen l3		# 返回列表的长度
(integer) 3
127.0.0.1:6379> 

LREM

127.0.0.1:6379> lrange l4 0 -1
1) "wangwu"
2) "zhangsan"
3) "xiaoming"
4) "wangwu"
5) "lishi"
6) "wangwu"
127.0.0.1:6379> lrem l4 1 xiaoming		#移除一个指定的值
(integer) 1
127.0.0.1:6379> lrange l4 0 -1
1) "wangwu"
2) "zhangsan"
3) "wangwu"
4) "lishi"
5) "wangwu"
127.0.0.1:6379> lrem l4 2 wangwu	# 移除两个指定的值,从左往右移除
(integer) 2
127.0.0.1:6379> lrange l4 0 -1
1) "zhangsan"
2) "lishi"
3) "wangwu"

LTRIM

127.0.0.1:6379> lpush LV1 AHC0 AHC1 AHC2 AHC3
(integer) 4
127.0.0.1:6379> lrange LV1 0 -1
1) "AHC3"	#0
2) "AHC2"	#1
3) "AHC1"	#2
4) "AHC0"	#3
127.0.0.1:6379> ltrim LV1 2 3	#截取指定长度,只保留截取部分,其余的移除
OK
127.0.0.1:6379> lrange LV1 0 -1
1) "AHC1"
2) "AHC0"

RPOPLPUSH

RPOPLPUSH是一个组合命令:移除列表的最后一个元素,将他移动到新的列表中

127.0.0.1:6379> lpush LV1 AHC0 AHC1 AHC2 AHC3
(integer) 4
127.0.0.1:6379> lrange LV1 0 -1
1) "AHC3"
2) "AHC2"
3) "AHC1"
4) "AHC0"
127.0.0.1:6379> rpoplpush LV1 LV2	#移除列表的最后一个元素,将他移动到新的列表中
"AHC0"
127.0.0.1:6379> lrange LV1 0 -1	#查看原先列表
1) "AHC3"
2) "AHC2"
3) "AHC1"
127.0.0.1:6379> lrange LV2 0 -1	#查看移动列表
1) "AHC0"
127.0.0.1:6379> 

LSET

将列表中指定下标的值替换为另外一个值,更新操作

127.0.0.1:6379> exists list 	#先判断列表是否存在
(integer) 0
127.0.0.1:6379> keys *	
1) "LV2"
2) "LV1"
127.0.0.1:6379> lset list 1 hello	#如果不存在改列表,则替换失败
(error) ERR no such key
127.0.0.1:6379> lpush list heoo hello
(integer) 2
127.0.0.1:6379> lset list 0 world	#如果存在,更新当前下标的值
OK
127.0.0.1:6379> lrange list 0 -1
1) "world"
2) "heoo"
127.0.0.1:6379> lset list 2 travle	#如果该下标值不存在,则会报错
(error) ERR index out of range

LINSERT

linsert key before|after pivot element:将某个具体的value插入到列中某个元素的前面或者后面

127.0.0.1:6379> rpush code "see" "more"
(integer) 2
127.0.0.1:6379> linsert code after "more" "view"	#在more的后面添加一个元素
(integer) 3
127.0.0.1:6379> lrange code 0 -1
1) "see"
2) "more"
3) "view"
127.0.0.1:6379> lrange code 0 -1
1) "see"
2) "more"
3) "view"
127.0.0.1:6379> linsert code before "see" "wangwu" 	#在see的前面添加一个元素
(integer) 4
127.0.0.1:6379> lrange code 0 -1
1) "wangwu"
2) "see"
3) "more"
4) "view"

Set(集合)

set中的值是不能重复的、无序的。

SADD、SMEMBERS、SISMEMBER

127.0.0.1:6379> sadd myset "hello" "world" "see" "set"	#set中添加元素
(integer) 4
127.0.0.1:6379> smembers myset	#查看指定set的所有值
1) "world"
2) "hello"
3) "set"
4) "see"
127.0.0.1:6379> sismenmber myset hello	#查看某一个值是否存在set中
(error) ERR unknown command `sismenmber`, with args beginning with: `myset`, `hello`, 
127.0.0.1:6379> sismember myset hello
(integer) 1
127.0.0.1:6379> sismember myset zhangsan
(integer) 0

SCARD

127.0.0.1:6379> scard myset	#查看set集合中的元素个数
(integer) 4

SREM

127.0.0.1:6379> srem myset "set" "see"	#移除指定元素
(integer) 2
127.0.0.1:6379> smembers myset
1) "world"
2) "hello"

SRANDMEMBER

127.0.0.1:6379> smembers myset
1) "world"
2) "hello"
127.0.0.1:6379> SRANDMEMBER myset #在集合中,随机获取一个数
"world"
127.0.0.1:6379> SRANDMEMBER myset 
"hello"

SPOP

127.0.0.1:6379> smembers myset
1) "no2"
2) "no1"
3) "world"
4) "hello"
5) "wangwu"
127.0.0.1:6379> spop myset	#随机移除集合中的 一个值
"wangwu"
127.0.0.1:6379> smembers myset
1) "no2"
2) "world"
3) "hello"
4) "no1"
127.0.0.1:6379> spop myset 2	#随机移除集合中的 指定个数 个值
1) "no1"
2) "world"
127.0.0.1:6379> smembers myset
1) "no2"
2) "hello"

SMOVE

127.0.0.1:6379> sadd set2 myhello
(integer) 1
127.0.0.1:6379> keys *
1) "myset"
2) "set2"
127.0.0.1:6379> smove myset set2 hello	#将一个指定的值移动到另一个set集合中
(integer) 1
127.0.0.1:6379> smembers myset
1) "no2"
127.0.0.1:6379> smembers set2
1) "hello"
2) "myhello"

SDIFF、SINTER、SUNION

可用于微博中,用户将所有关注的人放入一个set集合中。

127.0.0.1:6379> sadd set1 "a" "b" "d" "f"
(integer) 4
127.0.0.1:6379> sadd set2 "b" "c" "e" "f"
(integer) 4
127.0.0.1:6379> smembers set1 
1) "b"
2) "f"
3) "d"
4) "a"
127.0.0.1:6379> smembers set2
1) "e"
2) "b"
3) "f"
4) "c"

#--------------------------------------------------------------------
127.0.0.1:6379> sdiff set1 set2	#差集
1) "a"
2) "d"
127.0.0.1:6379> sinter set1 set2	#交集
1) "b"
2) "f"
127.0.0.1:6379> sunion set1 set2	#并集
1) "e"
2) "b"
3) "d"
4) "a"
5) "c"
6) "f"

Hash(哈希)

Map集合,kep-map,key对应的是一个map集合,本质和String类型没有太大差别,命令以 h 开头

HSET、HGET、HMGET、HMSET、HGETALL 、HDEL

127.0.0.1:6379> hset myhash name wangwu age 18	#set一组具体的值
(integer) 2
127.0.0.1:6379> hget myhash name	#获取一个字段的值
"wangwu"
127.0.0.1:6379> hget myhash age
"18"
127.0.0.1:6379> hmset myhash address jazz phone 123425	#添加多个key-value值
OK
127.0.0.1:6379> hmget myhash name age address phone	#获取多个字段的值
1) "wangwu"
2) "18"
3) "jazz"
4) "123425"
127.0.0.1:6379> hgetall myhash	#获取全部数据
1) "name"
2) "wangwu"
3) "age"
4) "18"
5) "address"
6) "jazz"
7) "phone"
8) "123425"

#-------------------------------------------------------------------------------
127.0.0.1:6379> HDEL myhash phone 	#删除hash指定key字段,对应删除value值
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "wangwu"
3) "age"
4) "18"
5) "address"
6) "jazz"

HLEN

127.0.0.1:6379> hgetall myhash
1) "name"
2) "wangwu"
3) "age"
4) "18"
5) "address"
6) "jazz"
127.0.0.1:6379> hlen myhash	#获取hash表的字段数量
(integer) 3

HEXISTS、HKEYS、HVALS

在这里插入图片描述

HINCRBY、HDECRBY、HSETNX

在这里插入图片描述

Zset(有序集合)

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

ZADD、ZRANGE

在这里插入图片描述

ZRANGEBYSCORE、ZREVRANGE

在这里插入图片描述

ZREM、ZCARD

SS

ZCOUNT

三种特殊数据类型

geospatial

可以推算地理位置的信息,两地之间的距离。

  • 规则:两极无法直接添加,一般直接下载城市数据,通过java程序导入。
  • 有效的经度从-180度到180度
  • 有效的纬度从-85.05112878度到85.05112878度
  • 当坐标位置超出上述指定范围时,该命令将会返回一个错误

GEO底层的实现原理其实就是Zset,可以使用Zset命令来操作GEO

在这里插入图片描述

GEOADD

在这里插入图片描述

GETPOS

在这里插入图片描述

GEODIST

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

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

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

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

在这里插入图片描述

GEORADIUS

以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
在这里插入图片描述在这里插入图片描述

GEORADIUSBYMEMBER

这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点,指定成员的位置被用作查询的中心。
在这里插入图片描述

GEOHASH

由于编码解码过程中所使用的初始最小和最大坐标不同,编码的编码也不同于标准。此命令返回一个标准的Geohash。
该命令将返回11个字符的Geohash字符串
在这里插入图片描述

Hyperloglog

Hyperloglog数据结构,基数统计的算法。
基数:数组中不重复的元素。
优点:占用的内存是固定,2^64不同的元素的基数,只需要花费12KB内存。从内存角度比较Hyperloglog首选。
可用于网页的UV操作(一个人访问一个网站多次,但是还是算作一个人)
0.81%错误率!统计UV任务,可以忽略不记。
如果不允许容错,就使用set或者自定义的数据类型。

127.0.0.1:6379> pfadd key1 a b c d e f h 	#创建一元素 key1
(integer) 1
127.0.0.1:6379> PFCOUNT key1	#统计key1 元素的基数数量
(integer) 7
127.0.0.1:6379> pfadd key2 j k l u i o g jk d	#创建一元素 key2
(integer) 1
127.0.0.1:6379> pfcount key2	#统计key2 元素的基数数量
(integer) 9
127.0.0.1:6379> pfmerge key3 key1 key2	#合并两组key1 和key2 合并到 key3 ,并集
OK
127.0.0.1:6379> PFCOUNT key3
(integer) 15

Bitmaps

Bitmaps 位图,数据结构,位存储。都是操作二进制来进行记录,只有0和1两个状态。
可用于统计用户打卡和活跃程度等。

# setbit key offset value,offset和value都只能为integer类型
127.0.0.1:6379> setbit sign 0 1		#创建一个bit ,设置第 0 个状态为 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1 
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> getbit sign 1	#获取sign 第 1 个的状态
(integer) 1
127.0.0.1:6379> bitcount sign	#统计sign中状态为 1 的个数
(integer) 2

事务

Redis事务本质:一组命令的集合。一个事务中的所有命令都会被序列化,在事务的执行过程中,会按照顺序执行。
一次性、顺序性、排他性。执行一系列的命令。
Redis事务没有隔离级别的概念!
所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行。
Redis单条命令有原子性,但是事务中不保证原子性!

  • Reids的事务:
    • 开启事务(multi)
    • 命令入队(…)
    • 执行事务(exec)
    • 取消事务(discard)
# 开启事务
127.0.0.1:6379> MULTI
OK

#命令入队列
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 vb
QUEUED
127.0.0.1:6379(TX)> set kn vn
QUEUED
127.0.0.1:6379(TX)> get kn
QUEUED

#执行事务
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
4) "vn"

#-----------------------------------------------------------------
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k5 nb
QUEUED

# 取消事务
127.0.0.1:6379(TX)> DISCARD
OK

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

在这里插入图片描述

运行时异常,如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!

在这里插入图片描述

监控(watch)

悲观锁:认为每个操作都会出问题,无论做什么都会加锁!
乐观锁:认为每个操作都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据。

redis监控测试

正常执行成功:

在这里插入图片描述

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

在这里插入图片描述

Jedis

java连接开发工具,使用java操作reids的中间件。

使用Jedis操作:

  1. 导入依赖
		<dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
  1. 连接测试
public class TestPing {
	/*
	*	若连接云服务器上的redis,需要修改redis的配置文件
	*		1、将配置文件中 bind:127.0.0.1  注释掉
	*		2、将配置文件中 protected-mode 设置为 no
	*	
	*	修改后服务器可能会受到攻击
	*/
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println(jedis.ping());	//输出PONG
    }
}
  1. 测试redis命令
    public static void main(String[] args) {
        Jedis jedis = new Jedis("8.136.198.98",6379);

        String set = jedis.set("java", "javaTest");     //添加一个值
        System.out.println(set);
        String key = jedis.get("key");          //获取一个值
        System.out.println(key);

        Set<String> keys = jedis.keys("*");     //获取所有key
        System.out.println(keys.size());
        Iterator<String> iterator = keys.iterator();    //将set集合放入迭代器中
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

SpringBoot整合Redis

SpringBoot操作数据通过Spring-Data。
SpringBoot 2.x以后 ,将jedis替换为lettuce。

jedis:采用直连,多个线程操作的话,是不安全的,如果想要避免不安全,需要使用jedis pool,类似于BIO模式。


lettuce:采用netty,可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数量,类似NIO模式。

源码:
package org.springframework.boot.autoconfigure.data.redis;
在这里插入图片描述RedisTemplate中默认的序列化接口为JdkSerializationRedisSerializer,在开发中可以自定义RedisTemplate<Object,Object>类注入到容器中,替代默认的RedisTemplate。

简单搭建

  1. 导入依赖
		 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  1. 配置文件 application.properties
spring.redis.host=127.0.0.1
spring.redis.port=6379
  1. 测试类
@SpringBootTest
class RedisSpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {

        /*
        * redisTemplate,操作不同的数据类型
        *       opsForValue 操作字符串,类似 String
        *       opsForList 操作List,类似 List
        *       .....
        * */

        redisTemplate.opsForValue().set("AiOne","机器人");
        System.out.println(redisTemplate.opsForValue().get("AiOne"));

        /*
        * 获取redis连接对象
        *       RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
        *        connection.flushDb();
        *       .....
        * */
    }

}

在这里插入图片描述

Redis.config 配置文件

单位

  • 配置文件unit单位对大小写不敏感。
    在这里插入图片描述

包含
在这里插入图片描述

网络 NETWORK

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

通用 GENERAL

daemonize yes 	#以守护进程的方式运行,默认为no
pidfile /www/server/redis/redis.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 "/www/server/redis/redis.log"	#日志的文件位置
databases 16	#数据库的数量,默认16个数据库
always-show-logo yes	#是否总是显示logo

快照 SNAPSHOTTING

持久化,在规定时间内,执行多少次操作,则会持久化文件 .rdb .aof
redis是内存数据库,如果没有持久化,那么数据断点丢失!

save 900 1		#如果 900 秒内,至少有 1 个 key进行了修改,则进行持久化操作
save 300 10		#如果 300 秒内,至少有 10 个 key进行了修改,则进行持久化操作
save 60 10000	#如果 60 秒内,至少有 10000 个 key进行了修改,则进行持久化操作

stop-writes-on-bgsave-error yes		#持久化如果出错,是否继续工作
rdbcompression yes	#是否压缩.rdb文件,消耗一定的cpu资源
rdbchecksum yes		# 保存rdb文件的时候,进行错误的检查校验
dir /www/server/redis/		# rdb文件保存的目录

SECURITY 安全

redis默认没有密码。
在这里插入图片描述

限制 CLIENTS

在这里插入图片描述

APPEND ONLY 模式 aof配置

在这里插入图片描述

Redis持久化

RDB操作

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

在这里插入图片描述

RDB保存的文件是dump.rdb,都可以在配置文件中修改

在这里插入图片描述在这里插入图片描述

触发机制

  1. save的规则满足的情况下,会触发rdb规则。
  2. 执行flushall命令,也会触发我们的rdb规则。
  3. 退出redis,也会产生rdb文件。
    备份都是自动生成一个dump.rdb

恢复rdb文件

  1. 只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据。
  2. 查看需要存在的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/www/server/redis"	#如果在这个目录下存在dump.rdb文件,启动就会自动恢复其中的数据

优点:
1. 适合大规模的数据恢复。
2. 对数据的完整性要求不高。

缺点:
1. 需要一定的时间间隔进程操作,如果redis意外宕机了,最后一次修改的数据就没有了。
2. fork进程的时候,会占用一定的内存空间。

AOF操作(Append Only File)

所有的命令都记录下来,history,恢复的时候把这个文件全部在执行一遍。
日志的形式来记录每个写操作,将Redis执行过的所有指令记录下来(读操作不记录),只许追加文件但不可以改写文件,redis启动之初会 读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
AOF保存的是appendonly.aof文件
在这里插入图片描述

appendonly 默认是不开启的,需要手动开启

在这里插入图片描述如果aof文件有错误,这时候redis是启动不起来的,需要使用redis的工具redis-check-aof --fix进行修复。

优点:

  • 每一次修改都同步,文件的完整性会更好
  • 每秒同步一次,可能会丢失一秒的数据
  • 从不同步,效率最高

缺点:

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

Redis发布订阅

Redis发布订阅(pub/sub)是一种消息通信模型:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关系系统。
Redis客户端可以订阅任意数量的频道。
订阅/发布消息图:
在这里插入图片描述

下图展示了频道channel1,以及订阅这个频道的三个客户端-- client2、client5和client1之间的关系:
在这里插入图片描述当有新消息通过PUBLISH 命令发送给频道channel1 时,这个消息就会被发送给订阅它的三个客户端:
在这里插入图片描述

使用redis命令简单实现订阅

127.0.0.1:6379> SUBSCRIBE "radio channel"	#订阅一个频道
Reading messages... (press Ctrl-C to quit)	
1) "subscribe"
2) "radio channel"
3) (integer) 1
#	等待读取推送的信息
1) "message"	#频道的信息
2) "radio channel"	# 消息的具体内容
3) "first message"
1) "message"
2) "radio channel"
3) "wangwu zhangsan lishi travel ..."

127.0.0.1:6379> PUBLISH "radio channel" "first message"	
(integer) 1
127.0.0.1:6379> PUBLISH "radio channel" "wangwu zhangsan lishi travel ..."	#发布者发布消息到频道
(integer) 1

原理

Redis是使用C实现的,通过分析Redis源码里的pubsub.c文件,了解发布和订阅机制的底层实现,加深对Redis的理解。Redis通过PUBLISH、SUBSCRIBE和PSUBSCRIBE等命令实现发布和订阅功能。
通过SUBSCRIBE命令订阅某频道后,redis-server里维护了一个字典,字典的就是一个个的channel,而字典的则是一个链表链表保存了所有订阅这个channel的客户端。SUBSCRIBE命令的关键,就是将客户端添加到给定channel的订阅表中。
通过PUBLISH命令向订阅者发送消息,redis-server会使用给定频道作为,在它所维护的channel字典查找记录了订阅这个频道的所有客户端链表遍历这个链表,将消息发布给所有订阅者。
Pub/Sub从字面上理解就是发布(publish)与订阅(subscribe),在Redis中,可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。

Redis主从复制

主从复制,是指一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master/leader),后者称为从结点(slave/follwer);数据的复制是单向的,只能由主节点到从节点。 Master以写为主,slave以读为主。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

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

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

一般来说,要将Redis运用在工程项目中,只使用一台Redis是万万不能的(宕机),原因:

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

在这里插入图片描述

环境配置

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

  1. 查看redis信息
    在这里插入图片描述
  2. 配置从机,使用命令配置,暂时的。真正配置需要修改配置文件。
    在这里插入图片描述配置文件修改配置:
    在这里插入图片描述主机断开连接,从机依旧连接到主机,但是没有写操作,这个时候,主机如果回来,从机依旧可以直接获取到主机写的信息。
    如果使用命令行,来配置的主从,这个时候如果重启,就会变回主机。只要变为从机,立马就会从主机中获取值。

复制原理

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

  • 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  • 增量复制Master继续将新的所有收集到的修改命令一次传给slave,完成同步。
    但是只要是重新连接master,一次完全同步(全量复制)将被自动执行!数据一定可以在从机中看到。

哨兵模式

主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用,。这不是一种推荐的方式,更多的时候,需优先考虑哨兵模式。Redis从208开始正式提供了Sentinel(哨兵模式)架构来解决问题。
能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,他会独立运行。其原理是哨兵通过发送命令,等待Redis服务器相应从而监控多个Redis实例
在这里插入图片描述
哨兵模式:

  • 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器
  • 当哨兵监测到master宕机,会自动slave切换成master,然后通过发布订阅模式通知其他的从服务器修改配置文件,让他们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式

在这里插入图片描述假设主服务器宕机哨兵1 先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1 主观的认为主服务器不可用,这个现象称为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由随机一个哨兵发起,进行failover(故障转移)操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线

配置哨兵配置文件 sentinel.conf
在这里插入图片描述

启动哨兵
在这里插入图片描述在这里插入图片描述

优点

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

缺点

  • Redis不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦。
  • 实现哨兵模式的配置麻烦。

哨兵模式的全部配置
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

Redis缓存穿透和雪崩

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

缓存穿透(查不到)

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

解决方案

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

在这里插入图片描述
缓存空对象:
当存储层没有命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。
在这里插入图片描述这种方法存在两个问题:

  1. 如果空值能够被缓存起来,就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键
  2. 即使对空值设置了过期时间,还是会存在缓存层存储层的数据会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响。

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

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

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

解决方案

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

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

缓存雪崩

缓存雪崩是指在某一时间段,缓存集中过期失效。Redis宕机。
在这里插入图片描述

解决方案

redis高可用:
这个思想的含义是,既然redis有可能挂掉,那么就多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群

限流降级:
在缓存失效后,通过加锁或者丢弃来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值