Redis 连接命令
#启动redis服务
redis# ./src/redis-server redis.conf
#连接redis服务
root# redis-cli -p 6379
Redis 五种基本数据类型
String
1.Redis 一共有16个数据库 默认是0号数据库
可以使用select来进行切换数据库
2.keys * :可以查看所有的key
3.flushdb :清空数据库 FLUSHALL :清空所有数据库
4.EXISTS: 查看属性是否存在
5.move key db: 移动属性指定到一个数据库 在目标数据库中存在相同的 key,那么将移动失败
del key:删除key
如图:将name属性从数据库0 移动到数据库1
- EXPRIE key seconds:设置属性过期时间 ttl key:查询剩余时间
而setex是在创建的时候设置过期时间 setex key seconds value
- type key:查看数据类型
- APPEND key value: 追加key的值 key可以不存在
- STRLEN key:查看长度
10.incr key:自增1 decr key:自减1
INCRBY key n:指定自增n(参数) DECRBY key n:指定自减n(参数)
11.GETRANGE key start end:截取字符串 SETRANGE key start value:替换字符串
#截取
127.0.0.1:6379> set key1 "hello world!"
OK
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> GETRANGE key1 0 4 #截取字符串,相当于java中的subString,下标从0开始,不会改变原有数据
"hello"
127.0.0.1:6379> get key1
"hello world!"
127.0.0.1:6379> GETRANGE key1 0 -1 #0至-1相当于 get key1,效果一致,获取整条数据
"hello world!"
#替换
127.0.0.1:6379> set key2 "hello,,,world!"
OK
127.0.0.1:6379> get key2
"hello,,,world!"
127.0.0.1:6379> SETRANGE key2 5 888 #此语句跟java中replace有点类似,下标也是从0开始,但是有区别:java中是指定替换字符,Redis中是从指定位置开始替换,替换的数据根据你所需替换的长度一致,返回值是替换后的长度
(integer) 14
127.0.0.1:6379> get key2
"hello888world!"
127.0.0.1:6379> SETRANGE key2 5 67 #该处只替换了两位
(integer) 14
127.0.0.1:6379> get key2
"hello678world!"
12.mset mget
MSETNX:是一个原子性的操作,在一定程度上保证了事务!要么都成功,要么都失败
#这里其实本质上还是字符串,但是我们讲其key巧妙的设计了一下。
##mset student:1:name student 相当于类名,1 相当于id,name 相当于属性
#如果所需数据全部这样设计,那么我们在java的业务代码中,就不需要关注太多的key
#只需要找到student类,下面哪个id,需要哪个属性即可,减少了代码的繁琐,在一定程度上可以理解为这个一个类的对象!
127.0.0.1:6379> mset student:1:name damon student:1:age 22 #新增一个key为‘student:1:name’,value为‘damon ’。。等数据
OK
127.0.0.1:6379> keys * #查看所有的key
1) "student:1:age"
2) "student:1:name"
127.0.0.1:6379> mget student:1:age student:1:name #获取数据
1) "22"
2) "damon"
##getset操作
127.0.0.1:6379> getset name1 jay1 #先get再set,先获取key,如果没有,set值进去,返回的是get的值
(nil)
127.0.0.1:6379> get name1
"jay1"
127.0.0.1:6379> getset name1 jay2 ##先获取key,如果有,set(替换)最新的值进去,返回的是get的值
"jay1"
127.0.0.1:6379> get name1 #替换成功
"jay2"
list
1.lpush(左插入) rpush(右插入) lrange()查询集合
127.0.0.1:6379> lpush list1 v1 v2 v3 v4 v5 #批量添加集合元素
(integer) 5
127.0.0.1:6379> LRANGE list1 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
5) "v1"
###这里大家有没有注意到,先进去的会到后面,也就是我们的lpush的意思是左插入,l--left
#rpush
127.0.0.1:6379> LRANGE list 0 1 #指定查询列表中的元素,从下标零开始,1结束,两个元素
1) "v3"
2) "v2"
127.0.0.1:6379> rpush list rv0 #右插入,跟lpush相反,这里添加进去元素是在尾部!
(integer) 4
127.0.0.1:6379> lrange list 0 -1 #查看集合所有元素
1) "v3"
2) "v2"
3) "v1"
4) "rv0"
- lpop(左移除) rpop(右移除)
#lpop
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
5) "v1"
127.0.0.1:6379> lpop list #从头部开始移除第一个元素
"v5"
##################
#rpop
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> rpop list
"v1"
127.0.0.1:6379> LRANGE list 0 -1 #从尾部开始移除第一个元素
1) "v4"
2) "v3"
3) "v2"
3.lindex(查询指定下标元素) llen(获取集合长度)
#lindex
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
127.0.0.1:6379> lindex list 1 #获取指定下标位置集合的元素,下标从0开始计数
"v3"
#llen
127.0.0.1:6379> llen list #获取指定集合的元素长度,相当于java中的length或者size
(integer) 3
4.lrem(根据value移除指定值)
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
127.0.0.1:6379> lrem list 1 v2 #移除集合list中的元素是v2的元素1个
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
127.0.0.1:6379> lpush list v3 v2 v2 v2
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "v2"
2) "v2"
3) "v2"
4) "v3"
127.0.0.1:6379> lrem list 10 v2 #移除集合list中元素为v2 的‘10’个,这里的参数数量,如果实际中集合元素数量不达标,不会报错,全部移除后返回成功移除后的数量值
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "v3"
5.ltrim(截取元素) rpoplpush(移除指定集合中最后一个元素到一个新的集合中)
#ltrim
127.0.0.1:6379> lpush list v1 v2 v3 v4
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "v4"
2) "v3"
3) "v2"
4) "v1"
127.0.0.1:6379> ltrim list 1 2 #通过下标截取指定的长度,这个list已经被改变了,只剩下我们所指定截取后的元素
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "v3"
2) "v2"
################
#rpoplpush
127.0.0.1:6379> lpush list v1 v2 v3 v4 v5
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
5) "v1"
127.0.0.1:6379> lpush newlist v0
(integer) 1
127.0.0.1:6379> LRANGE newlist 0 -1
1) "v0"
127.0.0.1:6379> rpoplpush list newlist #移除list集合中的最后一个元素到新的集合newlist中,返回值是移除的最后一个元素值
"v1"
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
127.0.0.1:6379> LRANGE newlist 0 -1 #确实存在该newlist集合并且有刚刚移除的元素,证明成功
1) "v1"
2) "v0"
6.lset(更新) linsert(插入)
#lset
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "v4"
3) "v3"
4) "v2"
127.0.0.1:6379> lset list 1 new #更新list集合中下标为‘1’的元素为‘newV5’
OK
127.0.0.1:6379> LRANGE list 0 -1 #查看证明更新成功
1) "v5"
2) "new"
3) "v3"
4) "v2"
##注意点:
127.0.0.1:6379> lset list1 0 vvvv #如果指定的‘集合’不存在,报错
(error) ERR no such key
127.0.0.1:6379> lset list 8 vvv #如果集合存在,但是指定的‘下标’不存在,报错
(error) ERR index out of range
########################
#linsert
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "new"
3) "v3"
4) "v2"
127.0.0.1:6379> LINSERT list after v3 insertv3 #在集合中的‘v3’元素 ‘(after)之后’ 加上一个元素
(integer) 5
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "new"
3) "v3"
4) "insertv3"
5) "v2"
127.0.0.1:6379> LINSERT list before v3 insertv3 #在集合中的‘v3’元素 ‘(before)之前’ 加上一个元素
(integer) 6
127.0.0.1:6379> LRANGE list 0 -1
1) "v5"
2) "newV5"
3) "insertv3"
4) "v3"
5) "insertv3"
6) "v2"
Set(集合)
1.sadd(添加)、smembers(查看所有元素)、sismember(判断是否存在)、scard(查看长度)、srem(移除指定元素)
#set中所有的元素都是唯一的不重复的!
127.0.0.1:6379> sadd set1 a b c d #添加set集合(可批量可单个,写法一致,不再赘述)
(integer) 4
127.0.0.1:6379> SMEMBERS set1 #查看set中所有元素
1) "a"
2) "c"
3) "b"
4) "d"
127.0.0.1:6379> SISMEMBER set1 a #判断某个值在不在set中,在返回1
(integer) 1
127.0.0.1:6379> SISMEMBER set1 r #不在返回0
(integer) 0
127.0.0.1:6379> SCARD set1 #查看集合的长度,相当于size、length
(integer) 4
127.0.0.1:6379> srem set1 a #移除set中指定的元素
(integer) 1
127.0.0.1:6379> SMEMBERS set1 #移除成功
1) "c"
2) "b"
3) "d"
2.srandmember key n:随机抽取
127.0.0.1:6379> sadd myset 1 2 3 4 5 6 7 #在set中添加7个元素
(integer) 7
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
127.0.0.1:6379> SRANDMEMBER myset 1 #随机抽取myset中1个元素返回
1) "4"
127.0.0.1:6379> SRANDMEMBER myset #不填后参数,默认抽1个值,但是下面返回不会带序号值
"3"
127.0.0.1:6379> SRANDMEMBER myset 3 #随机抽取myset中3个元素返回
1) "1"
2) "2"
3) "3"
3.spop key n:随机删除元素 smove key1 key2 val :移动一个指定元素到集合2
127.0.0.1:6379> SMEMBERS myset
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
127.0.0.1:6379> spop myset #随机删除1个元素,不指定参数值即删除1个
"2"
127.0.0.1:6379> spop myset 1 #随机删除1个元素
1) "7"
127.0.0.1:6379> spop myset 2 #随机删除2个元素
1) "3"
2) "5"
127.0.0.1:6379> SMEMBERS myset #查询删除后的结果
1) "1"
2) "4"
3) "6"
127.0.0.1:6379> smove myset myset2 1 #移动指定set中的指定元素到新的set中
(integer) 1
127.0.0.1:6379> SMEMBERS myset #查询原来的set集合
1) "4"
2) "6"
127.0.0.1:6379> SMEMBERS myset2 #查询新的set集合,如果新的set存在,即往后加,如果不存在,则自动创建set并且加入进去
1) "1"
4.sdiff :差集 sinter :交集 sunion :并集
127.0.0.1:6379> sadd myset1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> sadd myset2 3 4 5 6 7
(integer) 5
127.0.0.1:6379> SDIFF myset1 myset2 #查询指定的set之间的差集,可以是多个set
1) "1"
2) "2"
127.0.0.1:6379> SDIFF myset2 myset1 #查询指定的set之间的差集,可以是多个set
1) "6"
2) "7"
127.0.0.1:6379> SINTER myset1 myset2 #查询指定的set之间的交集,可以是多个set
1) "3"
2) "4"
3) "5"
127.0.0.1:6379> sunion myset1 myset2 #查询指定的set之间的并集,可以是多个set
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
Hash
1.hset(添加hash)、hget(查询)、hgetall(查询所有)、hdel(删除hash中指定的值)、hlen(获取hash的长度)、hexists(判断key是否存在)
127.0.0.1:6379> hset myhash name jay age 22 #添加hash,可多个
(integer) 2
127.0.0.1:6379> hget myhash name #获取hash中key是name的值
"jay"
127.0.0.1:6379> hget myhash age #获取hash中key是age的值
"22"
127.0.0.1:6379> hgetall myhash #获取hash中所有的值,包含key
1) "name"
2) "jay"
3) "age"
4) "22"
127.0.0.1:6379> hset myhash address cdc #添加
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "dingdada"
3) "age"
4) "23"
5) "address"
6) "cdc"
127.0.0.1:6379> hdel myhash name age #删除指定hash中的key(可多个),key删除后对应的value也会被删除
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "address"
2) "cdc"
127.0.0.1:6379> hlen myhash #获取指定hash的长度,相当于length、size
(integer) 1
127.0.0.1:6379> HEXISTS myhash address #判断key是否存在于指定的hash,存在返回1
(integer) 1
127.0.0.1:6379> HEXISTS myhash age #判断key是否存在于指定的hash,不存在返回0
(integer) 0
2.hkeys(获取所有key)、hvals(获取所有value)、hincrby(给值加增量)、hsetnx(存在不添加)操作
127.0.0.1:6379> hset myhash name jay age 22 high 177
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "name"
2) "jay"
3) "age"
4) "22"
5) "high"
6) "177"
127.0.0.1:6379> hkeys myhash #获取指定hash中的所有key
1) "name"
2) "age"
3) "high"
127.0.0.1:6379> hvals myhash #获取指定hash中的所有value
1) "jay"
2) "22"
3) "177"
127.0.0.1:6379> hincrby myhash age 2 #让hash中age的value指定+2(自增)
(integer) 24
127.0.0.1:6379> hincrby myhash age -1 #让hash中age的value指定-1(自减)
(integer) 23
127.0.0.1:6379> hsetnx myhash address cdc #添加不存在就新增返回新增成功的数量(只能单个增加)
(integer) 1
127.0.0.1:6379> hsetnx myhash name damon #添加存在则失败返回0
(integer) 0
127.0.0.1:6379> hgetall myhash
1) "name"
2) "jay"
3) "age"
4) "23"
5) "high"
6) "177"
7) "address"
8) "cdc"
Zset(有序集合)
1.zadd(添加)、zrange(查询)、zrangebyscore(排序小-大)、zrevrange(排序大-小)、zrangebyscore withscores(查询所有值包含key)
127.0.0.1:6379> zadd myzset 1 one 2 two 3 three #添加zset值,可多个
(integer) 3
127.0.0.1:6379> ZRANGE myzset 0 -1 #查询所有的值
1) "one"
2) "two"
3) "three"
#-inf 负无穷 +inf 正无穷
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf #将zset的值根据key来从小到大排序并输出
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> ZRANGEBYSCORE myzset 0 1 #只查询key<=1的值并且排序从小到大
1) "one"
127.0.0.1:6379> ZREVRANGE myzset 0 -1 #从大到小排序输出
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf withscores #查询指定zset的所有值,包含序号的值
1) "one"
2) "1"
3) "two"
4) "2"
5) "three"
6) "3"
2.zrem(移除元素)、zcard(查看元素个数)、zcount(查询指定区间内的元素个数)
127.0.0.1:6379> zadd myset 1 v1 2 v2 3 v3 4 v4
(integer) 4
127.0.0.1:6379> ZRANGE myset 0 -1
1) "v1"
2) "v2"
3) "v3"
4) "v4"
127.0.0.1:6379> zrem myset v3 #移除指定的元素,可多个
(integer) 1
127.0.0.1:6379> ZRANGE myset 0 -1
1) "v1"
2) "v2"
3) "v4"
127.0.0.1:6379> zcard myset #查看zset的元素个数,相当于长度,size。
(integer) 3
127.0.0.1:6379> zcount myset 0 100 #查询指定区间内的元素个数
(integer) 3
127.0.0.1:6379> zcount myset 0 2 #查询指定区间内的元素个数
(integer) 2
三大特殊数据类型
1.Bitmap:位存储
Bitmap位图,都是操作二进制位来进行记录 就只有0和1两个状态
setbit(添加) getbit(获取) bitcount(统计)
127.0.0.1:6379> setbit login 1 1 #添加周一已登陆 为1
(integer) 0
127.0.0.1:6379> setbit login 2 1
(integer) 0
127.0.0.1:6379> setbit login 3 1
(integer) 0
127.0.0.1:6379> setbit login 4 0 #添加周四已登陆 为0
(integer) 0
127.0.0.1:6379> setbit login 5 0
(integer) 0
127.0.0.1:6379> setbit login 6 1
(integer) 0
127.0.0.1:6379> setbit login 7 0
(integer) 0
127.0.0.1:6379> getbit login 1 #获取周一是否登录
(integer) 1
127.0.0.1:6379> getbit login 4 #获取周四是否登陆
(integer) 0
127.0.0.1:6379> bitcount login #统计这周登陆的天数
(integer) 4
2.Hyperloglog:基数
1:HyperLogLog是一种算法,并非redis独有
2:目的是做基数统计,故不是集合,不会保存元数据,只记录数量而不是数值。
3:耗空间极小,支持输入非常体积的数据量
4:核心是基数估算算法,主要表现为计算时内存的使用和数据合并的处理。最终数值存在一定误差
5:redis中每个hyperloglog key占用了12K的内存用于标记基数
6:pfadd命令并不会一次性分配12k内存,而是随着基数的增加而逐渐增加内存分配;而pfmerge操作则会将sourcekey合并后存储在12k大小的key中,这由hyperloglog合并操作的原理(两个hyperloglog合并时需要单独比较每个桶的值)可以很容易理解。
7:误差说明:基数估计的结果是一个带有 0.81% 标准错误(standard error)的近似值。是可接受的范围
8:Redis 对 HyperLogLog 的存储进行了优化,在计数比较小时,它的存储空间采用稀疏矩阵存储,空间占用很小,仅仅在计数慢慢变大,稀疏矩阵占用空间渐渐超过了阈值时才会一次性转变成稠密矩阵,才会占用 12k 的空间
9:HyperLogLog算法一开始就是为了大数据量的统计而发明的,所以很适合那种数据量很大,然后又没要求不能有一点误差的计算,HyperLogLog 提供不精确的去重计数方案,虽然不精确但是也不是非常不精确,标准误差是 0.81%,不过这对于页面用户访问量是没影响的,因为这种统计可能是访问量非常巨大,但是又没必要做到绝对准确,访问量对准确率要求没那么高,但是性能存储方面要求就比较高了,而HyperLogLog正好符合这种要求,不会占用太多存储空间,同时性能不错。
pfadd(添加数据集)、pfcount(统计数据集)、pfmegre(合并数据集-自动去重)
127.0.0.1:6379> pfadd dataList 1 2 3 4 5 6 7 #添加数据集
(integer) 1
127.0.0.1:6379> pfcount dataList #统计数据集中的元素
(integer) 7
127.0.0.1:6379> pfadd dataList1 4 5 6 7 8 9 10 #添加数据集
(integer) 1
127.0.0.1:6379> pfcount dataList1 #统计数据集中的元素
(integer) 7
#将dataList 和dataList1 两个数据集合并成一个新的 newdata数据集,并且自动去重
127.0.0.1:6379> pfmerge newdata dataList dataList1
OK
127.0.0.1:6379> pfcount newdata
(integer) 10
3.Geospatial:地理位置
城市经纬度查询: 经纬度查询
注意点1:两极无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
注意点2:有效的经度从-180度到180度。
注意点3:有效的纬度从-85.05112878度到85.05112878度。
注意点4:m 为米。km 为千米。mi 为英里。ft 为英尺。
geoadd(添加)、geopos(查看)、geodist(计算距离)操作
127.0.0.1:6379> geoadd city 118.8921 31.32751 nanjing 197.30794 31.79322 xxx
#当经纬度其中一个或者两个超过界限值,报错,信息如下:
(error) ERR syntax error. Try GEOADD key [x1] [y1] [name1] [x2] [y2] [name2] ...
#添加城市经纬度 语法格式: geoadd key 经度 纬度 name +++可多个添加
#添加成功后返回添加成功的数量值
127.0.0.1:6379> geoadd city 118.8921 31.32751 nanjing 117.30794 31.79322 hefei 102.82147 24.88554 kunming 91.13775 29.65262 lasa 116.23128 40.22077 beijing 106.54041 29.40268 chongqing
(integer) 6
127.0.0.1:6379> ZRANGE city 0 -1 #注意:geo的查看方式和zset的命令是一致的,
#由此可知,geo本质上还是个集合,不过Redis官方对其进行了二次封装
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
127.0.0.1:6379> geopos city nanjing #查看看指定城市的经纬度信息
1) 1) "118.89209836721420288"
2) "31.32750976275760735"
127.0.0.1:6379> geopos city nanjing beijing #查看看多个城市的经纬度信息
1) 1) "118.89209836721420288"
2) "31.32750976275760735"
2) 1) "116.23128265142440796"
2) "40.22076905438526495"
127.0.0.1:6379> geodist city nanjing beijing #计算南京到北京之间的距离,默认返回单位是m
"1017743.1413"
127.0.0.1:6379> geodist city nanjing beijing km #km 千米
"1017.7431"
127.0.0.1:6379> geodist city nanjing beijing mi #mi 英里
"632.3978"
127.0.0.1:6379> geodist city nanjing beijing ft #ft 英尺
"3339052.3010"
georadiusbymember (查找指定元素指定范围内的元素)、geohash (返回经纬度的hash值)、zrange、zrem(使用zset命令操作geo)
#查询南京 500公里范围有哪些城市
127.0.0.1:6379> georadiusbymember city nanjing 500 km
1) "hefei"
2) "nanjing"
#查询重庆 1500公里范围有哪些城市
127.0.0.1:6379> georadiusbymember city chongqing 1500 km
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
#返回北京和南京的经纬度的 hash值
127.0.0.1:6379> geohash city beijing nanjing
1) "wx4sucvncn0"
2) "wtsd1qyxfx0"
#查看所有城市name
127.0.0.1:6379> ZRANGE city 0 -1
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
#根据geo中的name删除g元素
127.0.0.1:6379> ZREM city lasa
(integer) 1
#删除成功
127.0.0.1:6379> ZRANGE city 0 -1
1) "kunming"
2) "chongqing"
3) "hefei"
4) "nanjing"
5) "beijing"
georadius(查询附近位置)
127.0.0.1:6379> ZRANGE city 0 -1 #查看城市
1) "lasa"
2) "kunming"
3) "chongqing"
4) "hefei"
5) "nanjing"
6) "beijing"
#查看指定位置的1000公里范围内有哪些城市
127.0.0.1:6379> georadius city 120 38 1000 km
1) "beijing"
2) "hefei"
3) "nanjing"
127.0.0.1:6379> georadius city 120 38 400 km #查看指定位置的400公里范围内有哪些城市
(empty array)
127.0.0.1:6379> georadius city 120 38 550 km #查看指定位置的550公里范围内有哪些城市
1) "beijing"
#查看指定位置的550公里范围内有哪些城市,withcoord指定返回城市的name
127.0.0.1:6379> georadius city 120 38 1000 km withcoord
1) 1) "beijing"
2) 1) "116.23128265142440796"
2) "40.22076905438526495"
2) 1) "hefei"
2) 1) "117.30793744325637817"
2) "31.79321915080526395"
3) 1) "nanjing"
2) 1) "118.89209836721420288"
2) "31.32750976275760735"
#查看指定位置的550公里范围内有哪些城市,withdist指定返回城市的’经纬度‘值
127.0.0.1:6379> georadius city 120 38 1000 km withcoord withdist
1) 1) "beijing"
2) "408.3496"
3) 1) "116.23128265142440796"
2) "40.22076905438526495"
2) 1) "hefei"
2) "732.6371"
3) 1) "117.30793744325637817"
2) "31.79321915080526395"
3) 1) "nanjing"
2) "749.0265"
3) 1) "118.89209836721420288"
2) "31.32750976275760735"
#查看指定位置的550公里范围内有哪些城市,withhash指定返回城市的’经纬度‘的hash值
#如果两个城市的hash值越’像‘,证明城市距离越近!
127.0.0.1:6379> georadius city 120 38 1000 km withcoord withdist withhash
1) 1) "beijing"
2) "408.3496"
3) (integer) 4069896088584598
4) 1) "116.23128265142440796"
2) "40.22076905438526495"
2) 1) "hefei"
2) "732.6371"
3) (integer) 4052763834193093
4) 1) "117.30793744325637817"
2) "31.79321915080526395"
3) 1) "nanjing"
2) "749.0265"
3) (integer) 4054278565840695
4) 1) "118.89209836721420288"
2) "31.32750976275760735"
#查看指定位置的550公里范围内有哪些城市,count num 指定返回’num‘个城市数据量
127.0.0.1:6379> georadius city 120 38 1000 km withcoord withdist withhash count 2
1) 1) "beijing"
2) "408.3496"
3) (integer) 4069896088584598
4) 1) "116.23128265142440796"
2) "40.22076905438526495"
2) 1) "hefei"
2) "732.6371"
3) (integer) 4052763834193093
4) 1) "117.30793744325637817"
2) "31.79321915080526395"
Redis 是单线程的 为什么单线程还这么快? (6.0以后是多线程)
Redis是基于内存操作的,cpu不是Redis的性能瓶颈,Redis的瓶颈是根据机器内存和网络带宽
1.误区1:高性能的服务器一定是多线程的?
2.误区2.多线程一定比单线程效率高?
核心:Redis是将所有的数据全部放在内存中的,所以单线程去操作效率是最高的,多线程(cpu上下文切换)
对于内存系统来说,如果没有上下文切换效率就是最高的
Redis不保证原子性
Redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚
multi :开启事务 exec : 提交事务
1.编译型移除(语句格式语法错误),事务内所有命令都不执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 20
QUEUED
127.0.0.1:6379> set k2 100
QUEUED
127.0.0.1:6379> incr k1
QUEUED
127.0.0.1:6379> decr k2 20 //错误的命令
(error) ERR wrong number of arguments for 'decr' command
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec //全部不会执行
(error) EXECABORT Transaction discarded because of previous errors.
2.运行时异常(1/0)命令入队时成功,执行时失败,除了失败的命令,其他命令照常执行。
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k3 str
QUEUED
127.0.0.1:6379> decr k3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) "str"
第二种情况,我们完全可以确定redis事务是不保证原子性的
redis事务不支持回滚,官方解释如下:
在事务运行期间,虽然Redis命令可能会执行失败,但是Redis仍然会执行事务中余下的其他命令,而不会执行回滚操作,你可能会觉得这种行为很奇怪。然而,这种行为也有其合理之处:
只有当被调用的Redis命令有语法错误时,这条命令才会执行失败(在将这个命令放入事务队列期间,Redis能够发现此类问题),或者对某个键执行不符合其数据类型的操作:实际上,这就意味着只有程序错误才会导致Redis命令执行失败,这种错误很有可能在程序开发期间发现,一般很少在生产环境发现。
Redis已经在系统内部进行功能简化,这样可以确保更快的运行速度,因为Redis不需要事务回滚的能力。
对于Redis事务的这种行为,有一个普遍的反对观点,那就是程序有可能会有缺陷(bug)。但是,你应当注意到:事务回滚并不能解决任何程序错误。例如,如果某个查询会将一个键的值递增2,而不是1,或者递增错误的键,那么事务回滚机制是没有办法解决这些程序问题的。请注意,没有人能解决程序员自己的错误,这种错误可能会导致Redis命令执行失败。正因为这些程序错误不大可能会进入生产环境,所以我们在开发Redis时选用更加简单和快速的方法,没有实现错误回滚的功能。
Redis如何实现乐观锁
Redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行,只要发起执行命令的时候才执行 Exec
watch(监视) multi(开启事务) exec(执行事务) discard(取消事务)
watch充当乐观锁 如果事务执行失败需要先解锁 再上锁unwatch->watch key->multi
127.0.0.1:6379> set money 100 #添加金钱100
OK
127.0.0.1:6379> set cost 0 #添加花费0
OK
127.0.0.1:6379> watch money #监控金钱
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> DECRBY money 30 #金钱-30
QUEUED
127.0.0.1:6379> incrby cost 30 #花费+30
QUEUED
127.0.0.1:6379> exec #执行事务,成功!这时候数据没有发生变动才可以成功
1) (integer) 70
2) (integer) 30
多线程测试watch
#线程1
127.0.0.1:6379> set money 100 #添加金钱100
OK
127.0.0.1:6379> set cost 0 #添加花费0
OK
127.0.0.1:6379> watch money #开启监视(乐观锁)
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> DECRBY money 20 #金钱-20
QUEUED
127.0.0.1:6379> INCRBY cost 20 #花费+20
QUEUED
#这里先不要执行,先执行线程2来修改被监视的值
127.0.0.1:6379> exec #执行报错,因为我们监视了money这个值,如果事务要对这个值进行操作前
#监视器会判断这个值是否正常,如果发生改变,事务执行失败!
(nil)
#线程2,这个在事务执行前操作执行
127.0.0.1:6379> INCRBY money 20 #金钱+20
(integer) 120
乐观锁
①当程序中可能出现并发的情况时,就需要保证在并发情况下数据的准确性,以此确保当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。
②没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
总结:乐观锁和悲观锁的区别。
悲观锁: 认为什么时候都会出问题,无论做什么都会加锁,没有执行当前步骤完成前,不让任何线程执行,十分浪费性能!一般不使用!
乐观锁: 认为什么时候都不会出问题,只有更新数据的时候去判断一下,在此期间是否有人修改过被监视的这个数据,没有的话正常执行事务,反之执行失败!
Jedis 这里使用idea远程连接云服务器redis
1.在redis.conf文件中设置redis密码:requirepss 123456
2.注释bind 127.0.0.1
3.protected-mode设置为false (默认为true)
4.使用宝塔安装的redis 需先关闭宝塔防火墙
(重启redis-server服务,进入redis后要先验证密码,用这个命令:auth 密码 ,然后ping一下看有没有配置成功)
public static void main(String[] args) {
Jedis jedis=new Jedis("8.130.18.190",6379);
jedis.auth("123456"); //设置的密码
System.out.println(jedis.ping()); //输出pong就算成功
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
#开启事务
Transaction transaction=jedis.multi();
String result=jsonObject.toJSONString();
try{
transaction.set("user1",result);
transaction.set("user2",result);
transaction.exec();
}catch (Exception e){
transaction.discard(); //失败放弃事务
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1")); //{"hello":"world"}
System.out.println(jedis.get("user2")); //{"hello":"world"}
jedis.close();
}
}
Redis实现发布订阅功能
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息。
Redis客户端可以订阅任意数量的频道!
订阅端:
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> SUBSCRIBE damon #订阅名字为 damon 的频道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "damon"
3) (integer) 1
#等待推送的信息
1) "message" #消息
2) "damon" #来自哪个频道的消息
3) "hello world" # 消息的具体内容
1) "message"
2) "damon"
3) "my name is damon"
发送端:
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> PUBLISH damon "hello world!" #发送消息到damon 频道
(integer) 1
127.0.0.1:6379> PUBLISH damon "my name is damon #发送消息到damon 频道
(integer) 1
publish:发送消息至指定频道
subscribe和psubscribe:订阅频道(可多个)
subscribe/psubscribe test1 test2 test3
punsubscribe和unsubscribe:退订
Redis中的缓存穿透、击穿和雪崩
缓存穿透:
大量请求一个不存在的key 例如:用户需要查询一个数据,但是redis中没有(比如说mysql中id=-1的数据),直接去请求MySQL,当很多用户同时请求并且都没有命中!于是都去请求了持久层的数据库,那么这样会给持久层数据库带来非常大的压力。一般出现这样的情况都不是正常用户,基本上都是恶意用户!
缓存穿透解决方案:
1.布隆过滤器
使用BitMap作为布隆过滤器,将目前所有可以访问到的资源通过简单的映射关系放入到布隆过滤器中(哈希计算),当一个请求来临的时候先进行布隆过滤器的判断,如果有那么才进行放行,否则就直接拦截
2.设置黑名单:
对redis进行实时监控,当发现redis中的命中率下降的时候进行原因的排查,配合运维人员对访问对象和访问数据进行分析查询,从而进行黑名单的设置限制服务(拒绝黑客攻击)
3.对空值缓存:
虽然数据库中没有id=-3872的用户的数据,但是在redis中对他进行缓存(key=-3872,value=null),这样当请求到达redis的时候就会直接返回一个null的值给客户端,避免了大量无法访问的数据直接打在DB上
雪崩:
redis中大量key集中过期
在某一个时间段,缓存的key大量集中同时过期了,所有的请求全部冲到持久层数据库上,导致持久层数据库挂掉!
范例:双十一零点抢购,这波商品比较集中的放在缓存,设置了失效时间为1个小时,那么到了零点,这批缓存全部失效了,而大量的请求过来时,全部冲过了缓存,冲到了持久层数据库!
雪崩解决方案:
1.Redis高可用:
搭建Redis集群,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。使用多级架构,例如:使用nginx缓存+redis缓存+其他缓存,不同层使用不同的缓存,可靠性更强
2.将失效时间分散开
通过使用自动生成随机数使得key的过期时间是随机的,防止集体过期
3.使用锁或者队列的方式
如果查不到就加上排它锁,其他请求只能进行等待
缓存击穿:
redis中一个热点key过期(大量用户访问该热点key,但是热点key过期)
缓存击穿解决方案:
- 进行预先的热门词汇的设置,进行key时长的调整
- 实时调整,监控哪些数据是热门数据,实时的调整key的过期时长
- 使用互斥锁(只有一个线程可以进行热点数据的重构)
整合SpringBoot
创建项目时勾选NoSQL中的Spring Data Redis
在SpringBoot2.x之后 原来使用的jedis被替换为了lettuce
lettuce和jedis区别:
jedis:采用的直连 多个线程操作不安全 如果想要避免不安全 使用jedis pool连接池 但会影响性能
lettuce:采用netty框架与Redis服务器连接 实例可以在多个线程中进行共享 线程是安全的
//redisTemplate 操作不同的数据类型
//opsForValue 操作字符串 String
//opsForList 操作List
//opsForSet
//opsForZset
//opsForHash
...
@SpringBootTest
class Springboot09RedisApplicationTests {
/**
* 自动装配RedisTemplate对象
*/
@Resource
private RedisTemplate redisTemplate;
@Resource
private StringRedisTemplate stringRedisTemplate;
//想把数据真正存储到Redis中 用StringRedisTemplate对象
@Test
void Test() {
RedisConnection connection=
redisTemplate.getConnectionFactory().getConnection();//获取redis连接对象
System.out.println(connection.ping());
//只有用String型的对象才能把存储的数据真正存到Redis数据库中 所以必须全为String
ValueOperations<String,String> ops = stringRedisTemplate.opsForValue();
ops.set("name","jay");
System.out.println(ops.get("name"));
}
}
传递对象需要序列化 或者是在pojo中 implements Serializable
User user=new User("jay",22);
String json=new ObjectMapper().writeValueAsString(user); //手动序列化 直接传对象需要序列化否则报错
stringRedisTemplate.opsForValue().set("user",json);
自定义redistemplate设置序列化的方式 StringRedisSerializer(String,Object)
@Configuration
@SuppressWarnings("all")
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
redisTemplate.setKeySerializer(new StringRedisSerializer()); // key的序列化类型
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
springboot操作Redis客户端实现技术切换(jedis)
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
redis.conf详解
units are case insensitive so 1GB 1Gb 1gB are all the same. #对大小写不敏感
bind 127.0.0.1 #绑定的ip
protected-mode yes #保护模式
daemoize yes #以守护进程方式运行 默认是no 需要打开
pidfile /www/server/redis/redis.pid #以后台方式运行需要指定一个pid文件
#快照 在规定的时间执行了相应的操作 会持久化到文件.rdb.aof
save 900 1 #900s 至少1个key进行了操作 我们就进行持久化
save 300 10
save 60 10000
requirepass 123456#设置密码
maxclients 10000 #设置最大客户端
maxmemory <bytes> #设置最大配置容量
maxmemory-policy noeviction #内存达到上限后处理策略
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
Redis持久化AOF和RDB(chatgpt回答)
Redis是一种基于内存的键值存储系统,它的高性能和可扩展性使得它成为了许多互联网企业缓存和存储数据的首选方案。但是,由于Redis是基于内存的,如果Redis进程意外崩溃或者服务器宕机,所有的数据都会丢失,这非常危险。
为了解决这个问题,Redis提供了两种持久化方式:RDB和AOF。
- RDB
RDB是Redis默认的持久化方式,它通过快照的方式将数据库中的数据定时写入磁盘文件中。当Redis进程需要重启的时候,它会从磁盘加载最近的RDB文件来恢复数据。RDB持久化的优点是:
- RDB持久化是高效的。在进行数据的持久化时,Redis会使用fork操作生成子进程,并在子进程中进行数据的持久化。这样,Redis主进程不需要进行磁盘I/O,不会被阻塞。
- RDB文件的大小通常比AOF文件要小得多,可以最大程度地减少磁盘占用和备份成本。
RDB持久化的缺点是:
- 因为RDB是基于快照的方式进行持久化,假如Redis进程意外崩溃,会导致最近一次快照之后的数据丢失。
- 当数据量很大时,RDB保存快照所需要的时间可能会很长,并且在检索较为频繁的情况下,可能会损失一些数据。
- AOF
AOF是Redis支持的另一种持久化方式,它通过将所有写操作追加到一个日志文件中来实现数据的持久化。当Redis重启时,会按照AOF文件中记录的顺序重新执行写操作来恢复数据。AOF持久化的优点是:
- AOF持久化是完全的,因为它按照写操作日志的方式进行持久化,所以每次写操作都会被记录下来,所有操作都可以恢复回来。
- AOF文件中存储的是Redis命令,因此具有更好的可读性,可以更加方便地查看Redis中的操作记录。
AOF持久化的缺点是:
- 对于每个写操作,都必须将其追加到AOF文件中,这会导致AOF文件的大小变得相对较大,占用存储空间。
- AOF持久化需要不断地写入到磁盘中,可能会导致磁盘的性能受到影响。此外,对于大规模写操作的情况下,AOF的性能会有所下降。
综上所述,两种持久化方式各有优缺点,Redis用户可以根据自己的需求和使用场景选择适合自己的持久化方式。
Redis集群环境搭建
复制三个配置文件 然后修改对应信息
cp redis.conf redis2.conf
cp redis.conf redis3.conf
cp redis.conf redis4.conf
修改:
1.端口 port 6380
2.pid名字 pidfile /www/server/redis/redis6380.pid
3.log文件名字 logfile "redis2.log"
4.dump.rdb名字 dbfilename dump2.rdb
修改完毕之后,启动我们三个redis服务器,我们可以通过进程查看
ps -ef|grep redis
Redis主从复制
默认情况下,每台redis服务器都是主节点,我们只用配置从机就好了 一主(6380)二从(6381 6382)
如果主机没有显示slave 可能是在主机的conf文件中配置了密码
1、将主机conf配置文件中的密码部分requirepass注掉
2、配置从机的masterauth
使用vim命令打开每个从机的配置文件例如我的从机配置文件叫redis6381.conf和redis6382.conf。 在配置文件末尾使用命令masterauth,格式:masterauth +你之前在redis.conf中设置的密码.
masterauth 123456
以上是命令配置 实际开发中一般在conf中配置
主从复制:读写分离
只有master能做写操作 slave只能做读操作
复制原理:
slave启动成功连接到master后会发送一个sync同步命令
master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据的命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步
全量复制:就是将主节点上的所有数据集合一次性地传输给从节点。当从节点启动复制,或者在执行 FLUSHALL 命令之后,就会触发全量复制。
因为全量复制需要将所有的数据传输到从节点,因此在数据量大时,需要占用较长时间和较大网络带宽
增量复制:就是将主节点上的新写命令从主节点传输给从节点,用于补充全量复制后从节点与主节点之间的差异。这样可以减少数据传输的时间和网络带宽的消耗。
由于增量复制仅将新写命令传输给从节点,因此它占用的时间和网络带宽都相对较少,是 Redis 复制中的主要方式。
只要是重新连接master,一次完全同步(全量复制)将被自动执行
哨兵模式
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费时费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑 哨兵模式 。Redis从2.8开始正式提供了Sentinel(哨兵) 架构来解决这个问题。
谋朝篡位 的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的 进程 ,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
1.配置哨兵配置文件sentinel.conf
#sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
#后面数字1 代表主机宕机 slave投票看谁接替成为主机
2.启动哨兵
redis-sentinel myConfig/sentinel.conf #和启动Redis一致
1、优点
①哨兵集群,基于主从复制模式 ,所有的主从配置优点,它全有
②主从可以切换,故障可以转移 ,系统的 可用性 就会更好
③哨兵模式就是主从模式的升级,手动到自动,更加健壮!
2、缺点
①Redis 不好在线扩容 的,集群容量一旦到达上限,在线扩容就十分麻烦!
②实现哨兵模式的配置其实是很 麻烦 的,里面有很多选择!