redis干货
Posted 小明爱java编程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis干货相关的知识,希望对你有一定的参考价值。
redis
一、NOSQL
NOSQL: no only sql(不仅仅SQL)
泛指非关系型数据库
NoSQL特点:
1.方便扩展(数据之间没有关系,很好扩展)
2.大数据量高性能(Redis一秒写8万次,读取1万,NoSQL的缓存记录级,时一种细粒度的的缓存,性能会比较高)
3.数据类型时多样型的(不需要事先设计数据库,随取随用,如果时数据量十分大的表,很多人就无法设计了)
4.传统RDBMS 和 NoSQL
RDBMS
-
结构化组织
-
SQL
-
数据和关系都存在单独的表中
-
数据操作,数据定义语言
-
严格的一致性
- 基础的事务
NoSQL
-
不仅仅是数据
-
没有固定的查询语言
-
键值对存储,列存储,文档存储,图形数据库(社交关系)
-
最终一致性
-
CAP定理和BASE(异地多活)初级架构师
- 高性能,高可用,高可扩
了解 3V + 3高
- 3V
1.海量Volume
2.多样Variety
3实时Velocity
- 3高
1.高可用
2.高可扩
3.高性能
二、阿里巴巴实践分析理解数据架构演进
1.商品的基本信息:
名称、价格、商家信息:
关系型数据库就可以解决:mysql / Oracle (淘宝早年就去IOE了--王坚 --阿里云这群疯子)
(IOE: IBM小型机 --Oracle数据库 --EMC存储设备)
2.商品的描述、评论(文字比较多)
文档型数据库中,MongDB
3.图片
分布式文件系统 FastDFS
-
淘宝自己的 TFS
-
Google的 GFS
-
hadoop HDFS
- 阿里云的 oss
4.商品的关键字 (搜索)
-
搜素引擎 solr elasticsearch
- ISearch:多隆
三、NoSQL四大分类
KV键值对:
-
\\新浪:Redis
-
美团:Redis + Tair
- 阿里、百度:Redis+memcache
文档型数据库:(bson格式和json一样)
- MongoDB(一般必须掌握)
1.MongDB是一个基于分布式文件存储的数据库,C++编写,主要用于处理大量文档
2.MongDB是一个介于关系型数据库和非关系型数据库中中间的产品,MongoDB是非关系型数据库中功能最丰富,最像关系型数据库
3.ConthDB
列存储数据库:
-
HBase
- 分布式文件系统
图关系数据库:
-
他不是存图形,放的是关系,比如:朋友圈社交网络、网络推荐
- Neo4J, InfoGrid
四、Redis的概述
Redis的概述(Remote Dictionary Server)即远程字典服务
是单线程的多路IO复用
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、key-Value数据库,并提供多种语言的API
1.周期性把更新的数据写入磁盘或者把修改操作写入追加的记录文件
2.实现master-slaver(主从)同步
3.免费和开源,是当下最热门的NoSQL技术之一
4.结构化数据库
Redis能干嘛?
1.内存存储,持久化(rdb,aof)
2.效率高,可以用于高速缓存
3.发布订阅系统
4.地图信息分析
5.计时器、计数器(浏览量)
特征:
1.多样的数据类型
2.持久化
3.集群
4.事务
Redis推荐都是基于Linux搭建
五、Window版本Redis
版本:3.2.100
默认端口:6379
官方文档:http://redis.cn/topics/introduction
六、Linux安装Redis
Linux安装Redis
1.基本的环境配置:
(1)
yum install gcc-c++
(2)
# make
(3)
make install
2.redis默认安装路径:/usr/local/bin
3.redis配置文件:
(1)在/usr/local/bin/ 下新建文件夹kconfig
(2)将redis配置文件redis.conf,复制到/usr/local/bin/kconfig
(3)redis默认不是后台启动,需要修改配置文件,改为后台启动
4.启动redis服务:
(1)启动redis服务端
redis-server kconfig/redis.conf
(2)使用客户端连接端口号6379
redis-cli -p 6379
5.查看进程:
ps -ef |grep redis[服务名]
6.关闭redis服务:
shutdown
not connected> exit
#
七、redis性能测试
测试:100个并发连接 10 0000请求
$ redis-benchmark -h localhost -p 6379 -c 100 -n 100000
八、基础知识
1.redis默认有16个数据库,默认使用的是第0个
(1)切换数据库:
127.0.0.1:6379> select 3
(2)数据库大小:
127.0.0.1:6379> DBSIZE
(3)查看所有key:
127.0.0.1:6379> keys *
(4)清除当前数据库
127.0.0.1:6379> flushdb
(5)清除全部数据内容
127.0.0.1:6379> flushall
(6)删除指定key数据
127.0.0.1:6379> del key
(7)根据value选择非阻塞删除
127.0.0.1:6379> unline key
(8)为给定的key设置过期时间
# 10秒钟
127.0.0.1:6379> expire key 10
2.Redis是单线程
Redis为什么单线程这么快?
1.误区1:高性能的服务器一定是多线程的?
2.误区2:多线程(CPU上下文会切换)一定比单线程效率高
核心:redis是所有的数据全部放在内存中的,所以说使用单线程去操作效率是最高的,多线程会切换CPU上下文切换,对于内存系统来说,没有上下文切换是效率最高的,多次读写都是在单个CPU上操作的
九、基本命令
设置key过期时间:
127.0.0.16379> EXPIRE name 10
查看key的剩余时间:
127.0.0.16379> ttl name[key]
查看key的类型
127.0.0.16379> type name
Redis命令帮助文档:https://redis.io/commands
十、五大数据类型
String
String二进制安全的,意味着Redis中的String可以包含任何数据,比如jpg或序列化对象,字符串value最大可以是512M
判断存在:
127.0.0.1:6379> exist key
追加:(key值不存在,相当于set)
127.0.0.1:6379> append key1 "hello"
获取长度:
127.0.0.1:6379> strlen key1
自增1:
127.0.0.1:6379> incr key 自减1:
127.0.0.1:6379> dect key
指定增量:
127.0.0.1:6379> incrby key 10
指定减量:
127.0.0.1:6379> decrby key 10
获取字符串指定范围:
127.0.0.1:6379> GETRANGE key 0 3 #[0,3]
替换指定位置开始的字符串:
127.0.0.1:6379> SETRANGE key 1 xx
设置过期时间:
#setex(set with expire) # 设置过期时间
127.0.0.1:6379> setex key 30 "hello"
#setnx(set if not exist) # 不存在设置(分布式锁中常常使用)
127.0.0.1:6379> setnx key "redis"
批量set、get
#mset
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
#msetnx
127.0.0.1:6379> msetnx k1 v1 k4 v4 #原子性,要么全成功,要么全失败
#对象
127.0.0.1:6379> set user:1name:zhangsan,zge:3
#user:id:filer
getset #先get然后再set
127.0.0.1:6379> getset db redis
1.#如果不存在值,则返回nil
2.#如果存在值,则返回原来的值
List
List的数据结构为快速链表quickList
首先在列表元素较少的情况请下会使用一块连续的内存储存,这个结构是ziplist,即是压缩列表
它将所有的元素紧挨着一起储存,分配的是一块连续的内存
当数据量比较多的时候才会改成quickList
因为普通的链表需要的附加指针空间太大,会比较浪费空间,比如这个列表里存的是int类型的数据,结构上还需要两个额外的指针prev和next
在redis里面,我们可以把list当成是对栈、队列、阻塞队列的操作
所有list命令都是用l开头的
# LPUSH
127.0.0.1:6379> LPUSH list one # 将一个值或多个值,插入到列表头部(左)
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
############################################################
# LRANGE
127.0.0.1:6379> LRANGE list 0 -1 # 获取list中所有值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1 # 通过区间获取具体的值
1) "three"
2) "two"
127.0.0.1:6379> LRANGE list 0 2
1) "three"
2) "two"
3) "one"
############################################################
# RPUSH、LPOP、RPOP
127.0.0.1:6379> RPUSH list right # 将一个值或多个值,插入到列表尾部(右)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> LPOP list # 移除list的第一个元素
"three"
127.0.0.1:6379> RPOP list
"right"
127.0.0.1:6379> LRANGE list 0 -1 # 移除list的最后一个元素
1) "two"
2) "one"
127.0.0.1:6379>
############################################################
# LINDEX、LPUSH、LLEN
127.0.0.1:6379> lindex list 1 # 通过下标获得list 中的某一个值
"one"
127.0.0.1:6379> lindex list 0
"two"
127.0.0.1:6379>
127.0.0.1:6379> Lpush list one
(integer) 1
127.0.0.1:6379> Lpush list two
(integer) 2
127.0.0.1:6379> Lpush list three
(integer) 3
127.0.0.1:6379> Llen list # 获取list长度
(integer) 3
127.0.0.1:6379> Lpush list thre
(integer) 4
127.0.0.1:6379> LRANGE list 1 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 -1
1) "thre"
2) "three"
3) "two"
4) "one"
############################################################
# LREM
127.0.0.1:6379> Lrem list 1 thre # 移除列表列表中一个元素为thre
(integer) 1
127.0.0.1:6379> Lpush list three
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> Lrem list 2 three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
trim 修剪;list截断
# LTRIM
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> Rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> Rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 # 截取下标指定的长度,其他被截断
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello1"
2) "hello2"
############################################################
RPOPLPUSH
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist # 移除最后一个元素并将它移入新的列表中
"hello2"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "hello1"
127.0.0.1:6379> LRANGE myotherlist 0 -1
1) "hello2"
############################################################
# LSET
127.0.0.1:6379> exists list
(integer) 0
127.0.0.1:6379> lset list 0 item # 如果不存在列表我们去更新就会报错
(error) ERR no such key
127.0.0.1:6379> LPUSH list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
127.0.0.1:6379> LRANGE list 0 1
1) "value1"
127.0.0.1:6379> LSET list 0 item # 如果存在,更新当前下标的值
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"
127.0.0.1:6379> LSET list 1 other # 如果不存在则会报错
(error) ERR index out of range
############################################################
# LINSERT
Linsert # 将某个具体的value插入到列表中某个元素的前面或者后面!
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "world"
(integer) 2
127.0.0.1:6379> LINSERT mylist before "world" "other"
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> LINSERT mylist after "world" "new"
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"
总结:
list实际是一个链表,before Node after , left , right都可以插入值
如果key不存在,创建新的链表
如果key存在,新增内容
如果移除了所有值,空链表,也代表不存在
在两边插入或者改动值,效率最高,中间元素,相对来说效率会低一点
消息队列 | LpushRpop , 栈 | LpushLpop
Set
Set数据结构是dict字典,字典使用哈希表实现的
Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象
Redis的set结构也是一样,它的内部也使用了hash结构,所有的value都指向同一个内部值
# SADD、SMEMBERS、SISMEMBER、SCARD
127.0.0.1:6379> sadd myset "hello" # set集合添加元素
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> SMEMBERS myset # 查看指定set所有值
1) "world"
2) "kuangshen"
3) "hello"
127.0.0.1:6379> SISMEMBER myset "hello" # 判断某一个值是不是在集合set中
(integer) 1
127.0.0.1:6379> SISMEMBER myset "happy"
(integer) 0
127.0.0.1:6379> scard myset # 获取集合set中元素的个数
(integer) 3
127.0.0.1:6379>
############################################################
# SRANDMEMBER
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "kuangshen"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset # 随机抽选出一个元素
"hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset
"world"
127.0.0.1:6379> SRANDMEMBER myset
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset 2 随机抽选出指定个数的元素
1) "kuangshen"
2) "world"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "kuangshen"
2) "hello"
############################################################
# SPOP
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "kuangshen"
3) "hello"
127.0.0.1:6379> spop myset # 随机删除一些set集合中的元素
"world"
127.0.0.1:6379> spop myset
"kuangshen"
127.0.0.1:6379> SMEMBERS myset
1) "hello"
############################################################
# SMOVE
将一个指定的值,移动到另一个set集合
127.0.0.1:6379> clear
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset2 "kuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "kuangshen"
127.0.0.1:6379> SMOVE myset myset2 "hello" # 将一个指定的值,移动到另一个set集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
127.0.0.1:6379> SMEMBERS myset2
1) "kuangshen"
2) "hello"
############################################################
# SDIFF、SINTER、SUNION
B站、微博共同关注(并集)
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 a
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> SDIFF key1 key2 # 差集 左 - 右
1) "c"
2) "b"
127.0.0.1:6379> SDIFF key2 key1
1) "e"
2) "d"
127.0.0.1:6379> SINTER key1 key2 # 交集
1) "a"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "a"
2) "c"
3) "b"
4) "e"
5) "d"
Hash
Map集合,key-map 这个值是一个Map,本质和Sring没有太大区别,还是一个简单的key-value
Hash类型对应的数据结构有两种:ziplist(压缩列表)、hashtable(哈希表),当field-value长度交短且个数较少时,使用ziplist,否则使用hashtable
set myhash field kuangshen
# HSET、HGET、HMSET、HMGET、HGETALL
127.0.0.1:6379> hset myhash field1 kuangshen # set 一个具体key-value
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获取一个字段值
"kuangshen"
127.0.0.1:6379> hmset myhash field1 hello field2 world # set 多个key-value
OK
127.0.0.1:6379> hmget myhash field1 field2 # 获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash # 获取全部的数据
1) "field1"
2) "hello"
3) "field2"
4) "world"
############################################################
# HDEL
127.0.0.1:6379> hset myhash field1 hello
(integer) 1
127.0.0.1:6379> hset myhash field2 world
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myhash field1 # 删除hash指定ley字段,对应的value也就消失了
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
############################################################
# hlen
127.0.0.1:6379> hlen myhash # 获取hash表的字段数量
(integer) 1
############################################################
# HEXISTS
127.0.0.1:6379> HEXISTS myhash field1 #判断hash 中指定的字段是否存在
(integer) 0
127.0.0.1:6379> HEXISTS myhash field2
(integer) 1
############################################################
# HKEYS 、HVALS
只获得所有field,只获得所有value
127.0.0.1:6379> hkeys myhash # 只获得所有field
1) "field2"
127.0.0.1:6379> hvals myhash # 只获得所有value
1) "world"
############################################################
# HINCRBY、HSETNX
127.0.0.1:6379> hset myhash field3 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 1 # 指定增量
(integer) 6
127.0.0.1:6379> HINCRBY myhash field3 -1
(integer) 5
127.0.0.1:6379> HSETNX myhash field4 hello # 如果value不存在则可以设置
(integer) 1
127.0.0.1:6379> HSETNX myhash field4 world # 如果value存在则不允许设置
(integer) 0
hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息,hash更适合于对象的存储,String更适合字符串存储
Zset
SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java数据结构Map<String, Double>,可以给每一个元素赋予权重score,另一方面它有类似于TreeSet,内部的元素会安装score权重进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
查找使用的是跳跃链表
在set的基础上,增加了一个值,增加了一个值,set k1 v1 , zset k1 score1 v1
# ZADD、ZRANGE
127.0.0.1:6379> zadd myset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
############################################################
# ZRANGEBYSCORE
127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhagsan
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1 # 显示全部用户,从小到大
1) "zhangsan"
2) "xiaohong"
3) "xiaoming"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示全部用户 从小到大
1) "kuangshen"
2) "xiaohong"
3) "zhagsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 从小到大显示全部用户并且附带成绩
1) "kuangshen"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhagsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores
1) "kuangshen"
2) "500"
3) "xiaohong"
4) "2500"
############################################################
# ZREVRANGE、ZREVRANGEBYSCORE
127.0.0.1:6379> zadd salary 2500 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 1800 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 1500 zhangsan
(integer) 1
127.0.0.1:6379> ZREVRANGE salary 0 -1 # 显示全部用户,从大到小
1) "xiaoming"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf # 从大到小显示
1) "xiaoming"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf withscores # 从大到小显示全部用户并且附带成绩
1) "xiaoming"
2) "2500"
3) "xiaohong"
4) "1800"
5) "zhangsan"
6) "1500"
############################################################
# ZCOUNT
127.0.0.1:6379> zcount salary 1500 3000 # 读取指定区间的用户数量
(integer) 3
总结:
- ZRANGE 后面只能跟 0 -1
- ZRANGEBYSCORE后面只能跟 范围,例如: -inf +inf
十一、三大特殊数据类型
Geospatial地理位置
八命令:
# geoadd 添加地理位置
#规则 两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入
#参数 key 值(经度 纬度 名称)
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shengzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379>
############################################################
#geopos
127.0.0.1:6379> GEOPOS china:city beijing # 获取地理位置坐标
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> GEOPOS china:city shanghai
1) 1) "121.47000163793563843"
2) "31.22999903975783553"
# GEODIST
两人之间的距离
单位:
- m表示单位为米
- km表示单位为千米
- mi表示单位为英里
- ft表示单位为英尺
# GEODIST
127.0.0.1:6379> GEODIST china:city beijing shanghai # 北京到上海的直径距离
"1067378.7564"
127.0.0.1:6379> GEODIST china:city beijing shanghai km
"1067.3788"
127.0.0.1:6379> GEODIST china:city beijing chongqing km
"1464.0708"
# GEORADIUS [WITHCOORD] [WITHDIST]
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km # 获取处在某个具体坐标指定半径内的地理位置
1) "chongqing"
2) "xian"
3) "shengzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqing"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist
1) 1) "chongqing"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
# GEOHASH
该命令返回11个字符的Geohash字符串
# 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,name则距离越近
127.0.0.1:6379> GEOHASH china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
GEO 底层的实现原理其实就是Zset,我们可以使用Zset命令来操作geo
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shengzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> ZREM china:city beijing
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shengzhen"
4) "hangzhou"
5) "shanghai"
Hyperloglog基数统计
Redis2.8.9版本就更新了Hyperloglog数据结构
Redis Hyperloglog基数统计的算法
优点:占用的内存是固定,计算2^64不同的元素的技术,只需要费12KB内存,如果要从内存角度来比较,Hyperloglog首选
网页的UV
传统方式:set保存用户的id,然后统计set中的元素数量作为标准判断
0.81%错误率,统计UV任务,可以忽略不计
# PFADD、PFCOUNT、PFMERGE
测试
127.0.0.1:6379> pfadd mykey a b c d e f g h i # 创建第一组元素 key
(integer) 1
127.0.0.1:6379> pfadd mykey2 i j z x c v b n m
(integer) 1
127.0.0.1:6379> pfcount mykey2 # 统计mykey2元素的基数数量
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # 合并两组 mykey mykey2 => mykey3 并集
OK
127.0.0.1:6379> pfcount mykey3 # 统计并集中的元素数量
(integer) 15
要使用Hyperloglog,前提是允许容错!
Bitmaps位图场景
位存储
- 统计用户信息,活跃,未活跃,登录,未登录,打卡,365打卡,两个状态的,都可以使用Bitmapes!
Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态
- 合理地使用操作位能够有效地提高内存使用率和开发效率
(1)Bitmaps本身不是一种数据类型,实际上它就是字符串(key-value),但是它可以对字符串的位进行操作
(2)Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储1和0,数组的下标在Bitmaps中叫做偏移量
365天=365bit 1字节=8bit 46个字节左右
测试
# SETBIT、GETBIT
127.0.0.1:6379> setbit sign 0 1 # 设置第一天打卡
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> getbit sign 3 # 查看第三天是否有打卡
(integer) 1
# bitop
(1)格式
bitop and(or/not/xor) <destkey> [key....]
bitop是一个复合操作,它可以做多个Bitmaps的and(交集)、or(并集)、not(非)。
xor(异或)操作并将结果保存在destkey中
- 注:
很多用户的id以一个指定的id开头,直接将用户的id和Bitmaps的偏移量对应势必会造成一定的浪费,通常的做法是每次做setbit操作时将用户id减去这个指定数字。
在第一次初始化Bitmaps时,假如偏移量非常大,那么整个初始化过程执行会比较慢,可能会造成Redis阻塞。
十二、Redis实现乐观锁
监控 Watch(面试常问!)
悲观锁:
- 很悲观,认为什么时候都会出问题,无论做什么都会加锁
- 传统的关系数据库里边就用到这种锁机制,比如行锁、表锁等,读锁、写锁等,都是在做操作之前先上锁
- 实现方法:在sql后面加上 for update或者for update nowait
乐观锁:
- 很乐观,认为什么时候都不会出现问题,所以不会上锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据,
- 获取version
- 更新的时候比较数据库的version
- 乐观锁于多读的应用类型,这样可以提高吞吐量,Redis就是利用这种check-and-set机制实现事务的。
Redis监视测试
两种之间的差别就是,在并发执行的时候,悲观锁会阻塞,而乐观锁会不执行
执行成功:
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 监视 money 对象
OK
127.0.0.1:6379> multi # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
OK
127.0.0.1:6379(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
执行失败:
客户端1:
127.0.0.1:6379> watch money # 监视money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379(TX)> exec # 执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失败
(nil)
127.0.0.1:6379> get money
"1000"
127.0.0.1:6379>
客户端2:
[fangyupeng@hadoop102 ~]$ redis-cli -p 6379
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK
原因:watch到脏数据,需要unwatch后再watch
注意:实现乐观锁开启监视必须在开启事务之前!
十三、Redis基本事务操作
Redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行
一次性、顺序性、排他性,执行一些列的命令
Redis事务的主要作用串联多个命令,防止别的命令插队
- 单独的隔离操作,事务执行过程中不会被客户端发送来的命令请求所打断
- Redis事务没有隔离级别的概念
所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会被执行,exec
- Redis单挑命令是保证原子性的,但是事务不保证原子性
redis事务:
- 开启事务(Multi)
- 命令入队(....)
- 执行事务(exec)
正常执行事务
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 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
放弃事务
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379> get k4
(nil)
编译型异常(代码有问题,命令有错),事务中所有的命令都不会执行
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 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3 # 错误的命令
(error) ERR wrong number of arguments for getset command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> exec # 所有的命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5
(nil)
运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令可以正常执行,错误命令抛出异常
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> incr k1 # 失败
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec # 事务可以执行
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
127.0.0.1:6379> get k1
"v1"
实现秒杀
- 使用乐观锁(库存遗留问题)
- 使用 lua(解决库存遗留问题)
库存遗留问题:第一个用户成功秒杀时,并发时的其他用户都秒杀失败,就算存在库存,lua作为嵌入式语言,可以使得整个事务具有原子性,不会被其他命令插队,不存在版本号不同问题
十四、通过Jedis操作Redis
依赖:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.6.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.2</version>
</dependency>
- testList
package com.zhkucst;
import redis.clients.jedis.Jedis;
public class TestList
public static void main(String[] args)
Jedis jedis = new Jedis("192.168.10.102",6379);
jedis.flushDB();
System.out.println("======添加一个List======");
jedis.lpush("collections","ArrayList", "Vector","Stack", "HashMap","WeakHashMap","LinkedHashMap");
jedis.lpush("collections","HashSet");
jedis.lpush("collections","TreeSet");
jedis.lpush("collections","TressMap");
System.out.println("collection的内容:" + jedis.lrange("collections",0, -1));
System.out.println("============================");
System.out.println("删除指定元素个数:" + jedis.lrem("collections",2, "HashMao"));
System.out.println("删除下表0-3区间之外的元素:" + jedis.ltrim("collections",0,3));
System.out.println("collections的内容:" + jedis.lrange("collections",0, -1));
System.out.println("collections列表出栈(左端):" + jedis.lpop("collections"));
System.out.println("collections的内容:" + jedis.lrange("collections",0, -1));
System.out.println("collections列表出栈(右端 ):" + jedis.rpop("collections"));
System.out.println("collections的内容:" + jedis.lrange("collections",0, -1));
System.out.println("修改collections列表指定下标1的内容:" + jedis.lset("collections",1,"UpdateHashSet"));
System.out.println("collections的内容:" + jedis.lrange("collections",0, -1));
System.out.println("============================");
System.out.println("collections的长度:" + jedis.llen("collections"));
System.out.println("获取collections下标为2的元素:" + jedis.lindex("collections",2));
System.out.println("============================");
jedis.lpush("sortedList", "3","6","2","8","7","4");
System.out.println("sortedList排序前:" + jedis.lrange("sortedList", 0, -1));
System.out.println(jedis.sort("sortedList"));
System.out.println("sortedList排序后:" + jedis.lrange("sortedList", 0, -1));
- testSet
package com.zhkucst;
import redis.clients.jedis.Jedis;
public class TestSet
public static void main(String[] args)
Jedis jedis = new Jedis("192.168.10.102", 6379);
jedis.flushDB();
System.out.println("======向集合中添加元素(不重复)======");
System.out.println(jedis.sadd("eleSet","e1","e2","e3","e4","e0","e8","e7","e5"));
System.out.println(jedis.sadd("eleSet","e6"));
System.out.println(jedis.sadd("eleSet","e6"));
System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
System.out.println("删除一个元素e0:" + jedis.srem("eleSet","e0"));
System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
System.out.println("删除两个元素e7,e6:" + jedis.srem("eleSet","e7", "e6"));
System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
System.out.println("随机地移除集合中的一个元素:" + jedis.spop("eleSet"));
System.out.println("随机地移除集合中的一个元素:" + jedis.spop("eleSet"));
System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
System.out.println("eleSet集合中包含的元素个数:" + jedis.scard("eleSet"));
System.out.println("e3是否在eleSet集合中:" + jedis.sismember("eleSet","e3"));
System.out.println("e1是否在eleSet集合中:" + jedis.sismember("eleSet","e1"));
System.out.println("e5是否在eleSet集合中:" + jedis.sismember("eleSet","e5"));
System.out.println("============================");
System.out.println("eleSet1:" + jedis.sadd("eleSet1","e1","e2","e4","e3","e0","e8","e7","e5"));
System.out.println("eleSet2:" + jedis.sadd("eleSet2","e1","e2","e4","e3","e0","e8"));
System.out.println("eleSet1的元素:" + jedis.smembers("eleSet1"));
System.out.println("eleSet2的元素:" + jedis.smembers("eleSet2"));
System.out.println("将eleSet1中删除e1并存入eleSet3:" + jedis.smove("eleSet1","eleSet3","e1"));
System.out.println("将eleSet1中删除e2并存入eleSet3:" + jedis.smove("eleSet1","eleSet3","e2"));
System.out.println("eleSet1的元素:" + jedis.smembers("eleSet1"));
System.out.println("eleSet3的元素:" + jedis.smembers("eleSet3"));
System.out.println("======集合运算======");
System.out.println("eleSet1的元素:" + jedis.smembers("eleSet1"));
System.out.println("eleSet2的元素:" + jedis.smembers("eleSet2"));
System.out.println("eleSet1与eleSet2的交集:" + jedis.sinter("eleSet1", "eleSet2"));
System.out.println("eleSet1与eleSet2的并集:" + jedis.sunion("eleSet1","eleSet2"));
System.out.println("eleSet1与eleSet2的差集:" + jedis.sdiff("eleSet1","eleSet2"));
- testHash
package com.zhkucst;
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.Map;
public class TestHash
public static void main(String[] args)
Jedis jedis = new Jedis("192.168.10.102", 6379);
jedis.flushDB();
Map<String,String> map = new HashMap<>();
map.put("k1","v1");
map.put("k2","v2");
map.put("k3","v3");
map.put("k4","v4");
jedis.hmset("hash",map);
jedis.hset("hash","key5","value5");
System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
System.out.println("散列hash的所有键:" + jedis.keys("hash"));
System.out.println("散列hash的所有值:" + jedis.hvals("hash"));
System.out.println("将k6保存的值加上一个整数,如果k6不存在则添加k6:" + jedis.hincrBy("hash","key6",6));
System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
System.out.println("将k6保存的值加上一个整数,如果k6不存在则添加k6:" + jedis.hincrBy("hash","key6",3));
System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
System.out.println("删除一个或多个键值对:" + jedis.hdel("hash","k2"));
System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
System.out.println("散列表hash中键值对个数:" + jedis.hlen("hash"));
System.out.println("判断hash中是否存在k2:" + jedis.hexists("hash","k2"));
System.out.println("判断hash中是否存在k3:" + jedis.hexists("hash","k3"));
System.out.println("获取hash中k3的值:" + jedis.hmget("hash","k2"));
System.out.println("获取hash中k2、k3的值:" + jedis.hmget("hash","k2","k3"));
十五、通过Jedis操作事务
TestTX.jar
package com.zhkucst;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class TextTX
public static void main(String[] args)
Jedis jedis = new Jedis("192.168.10.102",6379);
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","zhangsan");
jedis.flushDB();
System.out.println("开启事务...");
Transaction multi = jedis.multi();
//jedis.watch();//开启监视
// .....
//jedis.unwatch();关闭监视
try
String result = jsonObject.toJSONString();
multi.set("user1",result);
multi.set("user2",result);
int i = 1/0;
multi.exec();
catch (Exception exception)
multi.discard();
exception.printStackTrace();//放弃事务
finally
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();//关闭连接
十六、自定义RedisTemplate
通过自动配置类源码:
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.data.redis;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
/**
* @link EnableAutoConfiguration Auto-configuration for Spring Datas Redis support.
*
* @author Dave Syer
* @author Andy Wilkinson
* @author Christian Dupuis
* @author Christoph Strobl
* @author Phillip Webb
* @author Eddú Meléndez
* @author Stephane Nicoll
* @author Marco Aust
* @author Mark Paluch
* @since 1.0.0
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import( LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class )
public class RedisAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
可知,Redis可以自定义配置类,来实现序列化
- 通过接口 public interface RedisSerializer<T> 的实现类分析,有几种序列化方式
- pojo类实现接口Serializable以实现能够序列化对象
- 自定义RedisTemplate
package com.zhkucst.conf;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig
@Bean
//抑制所有警告
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
//一般使用<String, Object>
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//json序列化设置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value采用json的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value采用json序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
//其他未自定义的属性通过该方法配置默认的PropertiesSet
template.afterPropertiesSet();
return template;
- 测试
@Test
public void test() throws JsonProcessingException
User user = new User("张三",23);
//String jsonUser = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user", user);
System.out.println(redisTemplate.opsForValue().get("user"));
十七、Redis配置文件详解
单位
1.配置文件unit单位对大小写不敏感,只支持byte,不支持bit
包含
好比我们学习Spring、Import、include
网络
bind 127.0.0.1 # 绑定的id
protected-mode yes # 保护模式
port 6379 # 端口设置
通用GENERAL
daemonize yes # 以守护进程的方式进行,默认是no,我们需要自己开启为yes
pidfile /var/run/redis_6379.pid #如果以后台的方式运行,我们需要指定一个pid文件
# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产模式
# warning (only very important / critical messages are logged)
loglevel notice # 日志文件的通知等级
logfile "/usr/local/bin/klog" # 日志的文件位置名
databases 16 # 数据库的数量, 默认是16个数据库
always-show-logo no # 是否总显示logo
快照
持久化,在规定时间内,执行了多少次操作,则会持久化到文件,rdb.aof
redis是内存数据库,如果没有持久化,那么数据数据断电及失
# 如果900s内,如果至少有1 key进行了修改,我们及进行持久化操作
save 900 1
# 如果300s内,如果至少有10 key进行了修改,我们及进行持久化操作
save 300 10
# 如果60s内,如果至少有10000 key进行了修改,我们及进行持久化操作
save 60 10000
stop-writes-on-bgsave-error yes # 持久化如果出错,是否需要继续工作
rdbcompression yes # 是否压缩rdb文件,需要消耗一些cpu
rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验
#dir ./ # rdb 文件保存的目录,默认为启动服务时的当前路径,最好改成绝对路径
dir /usr/local/bin/krdb
REPLICATION复制,主从复制
详见十九篇:Redis主从复制
SECURITY 安全
可以在这里设置redis的密码,默认是没有密码
# root
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass # 获取redis的密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" # 设置redis的密码
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
# localhost
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456 # 使用密码进行登录
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
限制CLIENTS
maxclients 10000MEMORY MANAGEMENT 内存处理策略
maxmemory <bytes> # redis配置最大内存容量
maxmemory-policy noeviction # 内存到达上限后的处理策略
1、volatile-lru -> Evict using approximated LRU(Least Recently Used), only keys with an expire set.
# 只对设置了过期时间的key 进行LRU(默认值)
2、allkeys-lru -> Evict any key using approximated LRU.
# 删除LRU算法的key
3、volatile-lfu -> Evict using approximated LFU(Least Frequently Used), only keys with an expire set.
# 只对设置了过期时间的key 进行LFU
4、allkeys-lfu -> Evict any key using approximated LFU.
# 删除LFU算法的key
5、volatile-random -> Remove a random key having an expire set.
# 随机删除即将过期的key
6、allkeys-random -> Remove a random key, any key.
# 随机删除
7、volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# 删除即将过期的
8、noeviction -> Dont evict anything, just return an error on write operations.
# 永不过期,返回错误
# LRU means Least Recently Used
# LFU means Least Frequently Used
APPEND ONLY MODE aof模式
appendonly no # 默认是不开启aof模式,默认是是rdb方式持久化,在大部分所有的情况下,rdb够用
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改都会sync,消耗性能
appendfsync everysec # 每秒执行 sync, 可能会丢失1s的数据
# appendfsync no # 不执行 sync, 这个时候操作系统自己同步数据,速度更快
十八、持久化-RDB操作
什么是RDB?
查找文件名:root@hadoop102 bin# find / -name dump.rdb
查找配置文件中的配置,命令:config get dir
save 和 bgsave
save:save时只管保存,其他不管,全部阻塞,手动保存,不建议
bgsave:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求
生成RDB的方式:
- 满足save规则,会自动触发rdb规则
- FLUSHALL命令,触发rdb规则
- shutdown,退出redis,产生rdb文件
恢复RDB文件
- 步骤
- 只需将rdb文件放在redis启动目录,即配置文件中指定的dir路径:
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 如果在该目录下存在dump.rdb,启动redis会自动恢复其中的数据,放到内存中
- redis启动后会自动检查demp.rdb恢复其中的数据
- 优点
- 适合大规模的数据恢复
- 对数据的完整性要求不高
- 节省磁盘空间
- 恢复速度快
- 缺点
- 需要一定的时间间隔进程操作,如果redis意外死机了,这个最后一次修改的数据就没有了。(因为rdb持久化操作是先保存在了临时文件中,这是宕机那临时文件也就不能替换原来的rdb文件,导致最后一次持久化操作数据丢失)
- fork进程的时候,会占用一定的内容空间,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
- 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
其他配置
- stop-writes-on-bgsave-error
当Redis无法写入磁盘的话,直接关掉Redis的写操作,推荐yes
- rdbcompression压缩文件
对于存储到磁盘中的快照,可以设置是否进行压缩存储,如果是的话,redis会采用LZF算法进行压缩,默认是yes
- rdbchecksum检查完整性
在存储快照后,还可以让redis使用CRC64算法来进行数据校验
十九、持久化-AOF操作
什么是AOF?(Append Only File)
aof保存的是 appendonly.aof文件
默认是不开启的,我们需要手动配置,我们只需将 appendonly 改为 yes 就开启了 aof
如果这个aof文件有错误,这时候redis是启动不起来的,我们需要修复这个aof文件
redis给我们提供了一个工具 redis-check-aof
appendonly no # 默认是不开启aof模式,默认使用rdb方式持久化,在大部分情况下,有rdb就够了
appendfilename "appendonly.aof" # 持久化的文件名
appendfsync always # 每次修改都会sync ,消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失1s的数据
appendfsync no # 不执行sync,这个时候操作系统自己同步数据,速度更快
AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)
AOF和RDB所在的目录在同一个位置
开启AOF进行备份时要先关闭redis或开启后重启redis,配置才会生效
重启redis便会加载aof文件进行恢复,文件损坏会启动失败
修复AOF文件
redis-check-aof --fix krdb/appendonly.aof
重写规则说明
aof默认就是文件的无限追加,文件会越来越大
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb # 如果aof文件大于64m,太大了,fork一个新的进程来将我们的文件进行重写
优点和缺点
- 优点
- 每一次修改都同步,文件的完整性会更加好
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步,效率最高
- 缺点
- 相对于数据文件来说,aof远远大于rdb,恢复的速度比rdb慢
- aof运行效率要比rdb慢,所以我们redis默认的配置就是rdb持久化
二十、Redis主从复制
概念
主从复制,是指将一台redis服务器的数据,复制到其他Redis服务器,前者称为主节点(master/leader),后者称为从节点(slaver/follower),数据的复制是单向的,只能从主节点到从节点,Master以写为主,Slaver以读为主。
默认情况下,每套Redis服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
三种实现方式:
- 一主二仆
- 薪火相传
- 反客为主
主从复制的作用主要包括:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式;
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余;
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,从节点提供度服务(即写Redis数据是应用连接主节点,读Redis数据是应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上
以上是关于redis干货的主要内容,如果未能解决你的问题,请参考以下文章
干货分享,值得收藏:搞懂这些redis知识点,还怕干不过面试官?
虹科干货 | 虹科Redis企业版数据库的延迟如此之小,proxy功不可没!