文章目录
redis(Remote Dictionary Server)
简介
C语言开发的开源高性能键值对(key-value)数据库(性能高的主要原因)。
特征:
- 数据间没有必然的关联关系
- 内部采用单线程机制进行工作
- 高性能。50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s。
- 多数据类型支持
- 字符串类型:string
- 列表类型:list
- 散列类型:hash
- 集合类型:set
- 有序集合类型:sorted_set
- 持久化支持。可以进行数据灾难恢复(比如断电)。
应用:
- 为热点数据加速查询(主要场景),如热点商品、热点新闻、热点资讯、推广类等高访问量信息等。
- 任务队列,如秒杀、抢购、购票排队等(redis作为缓存);运营平台监控到的突发高频访问数据:突发时政要闻,被吃瓜群众围观(redis作为缓存);高频复杂的统计数据:在线人数、投票(redis作为缓存)。
- 即时信息查询,如各位排行榜、各类网站访问统计、公交到站信息、在线人数信息(聊天室、网站)、设备信号等。
- 时效性信息控制,如验证码控制、投票控制等。
- 分布式数据共享,如分布式集群架构中的
session
分离 - 消息队列
- 分布式锁
- 附加功能(系统功能优化或升级):单服务器升级集群、session管理、token管理
下载
Linux版(适用于企业级开发):
- redis高级开始使用
- 4.0版本挺好
Windows版本(适合学习):
-
用于入门学习
-
3.2版本吧
下载后解压即可
-
说明:
-
redis-server.exe是服务器,先双击启动
-
redis-cli.exe是用于操作的客户端,后双击启动(装逼:D:;cd Environments;cd r+按下Tab键提醒redis-x64-3.2;redis-cli;启动客户端成功)
ps:dir对应
Linux
ls命令 -
redis-check-aof.exe用于持久化用的
-
redis-benchmark.exe是用于性能测试的
-
-
端口号:6379(默认)
命令行操作
-
增加(修改)
set key value
如:
set name lalala
-
查询
get key
如果不存在,返回空(nil),如
get name
-
清除屏幕信息命令
clear
(windows)不同操作系统不一样
-
查看帮助信息
help 命令名称
查看命令使用方式
help @组名
获取组中所有命令信息名称
ps:此命令可直接输入也可help+空格,然后用Tab键进行选择。其中get key
和set key value
命令就属于@string
组中。
-
退出
quit
exit
esc键
数据类型(常用)
-
string
字符串,如果字符串以整数的形式展示,可以作为数字操作使用。
数据库中热点数据key的命名约定:表名:主键名:主键的值:字段名,如:
order:id:372637247:name
-
基本操作
增(修改):
set key value
;多个数据:mset key1 value1 key2 value2 ...
获取:
get key
;多个数据:get key1 key2 ...
删除:
del key
,(integer)1代表运行结果成功,(integer)0代表运行结果失败,适用于string的所有操作获取数据字符串长度:
strlen key
追加信息到原来的信息后部(如果原始信息存在就追加,否则就新建):
append key value
ps:(integer)+数字指运行结果是否成功,还是表示运行结果值(1个2个3个),具体要看给出的指令;数据的最大存储量:512MB;
-
扩展操作
需求:大型企业应用中,使用多张表存储同类型数据,但是对应的主键id必须保证统一性,不能重复(主要问题在这)。Oracle数据库具有sequence设定,可以解决此问题,但mysql数据库并不具有类似的机制,求解决办法。
解决方法:
1)设置数据增加指定范围的值
incr key //如果是数字字符串,使得加1 incrby key increment //如果是数字字符串,使得增加指定值(整数) incrbyfloat key increment //如果是数字字符串,使得增加指定小数
2)设置数据减少指定范围的值
decr key //如果是数字字符串,使得减1 decrby key increment //如果是数字字符串,使得减少指定值(整数)
ps:按数字进行操作的数据,如果原始数据不能转换成数字,或超出redis数字上限(java的long类型的最大值),将报错。
用此方法,redis可以用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性,适用于所有数据库,且支持数据库集群。
redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无序考虑并发带来的数据影响。
需求:投票,每四个小时只能投一票(不能一直不间断投啊)
解决方案:
设置数据的生命周期:
setex key seconds value psetex key milliseconds value //一个是秒,一个是毫秒
需求:主页高频访问信息显示控制,例如新浪微博大V(应该是用户吧)主页显示粉丝数与微博数量
解决方案:
1)在redis中为大V用户设定用户信息,以用户主键和属性值作为key,后台设定定时刷新策略即可,如:
set user:id:372637247:fans 122383 set user:id:372637247:blogs 666
2)在redis中以json存储大V用户信息,定时刷新(也可hash类型),如:
set user:id:372637247 {id:372637247,blogs:666,fans:122383}
-
-
hash(存储的数据有点像是对象的格式)
数据少时,用的数组存储,多就类似于
HashMap
的方式-
基本操作
增加(修改)数据
hset key field value
;多个数据:hmset key field1 value1 field2 value2...
获取数据
hget key field
;多个数据:hmget key field1 field2...
hgetall key
(获取全部字段,如果字段过多,遍历会使得效率很低,有可能成为数据访问瓶颈)删除数据
hdel key field1 field2...
获取哈希表中字段(
field
)的数量hlen key
获取哈希表中是否存在指定的字段
hexists key field
-
扩展操作
获取哈希表中所有字段名(
field
)或字段值hkeys key
hvals key
设置指定字段的数字增加指定值(上面已经提过类似的)
hincrby key field increment
hincrbyfloat key field increment
ps:hash类型下的value只能存储字符串;每个hash可以存储2^32-1个键值对
需求:电商网站购物车设计与实现(仅分析购物车的redis存储模型:添加、浏览、更改数量、删除、清空)
解决方案:
第一步(存储,还没有起到加速作用):以客户id作为key,为每一位客户创建一个hash类型存储对应的购物车信息;将商品编号作为field,购买数量作为value进行存储;添加商品:追加全新的field与field;浏览:遍历hash;更改数量:redis的增减命令或直接设置value值;删除商品:删除field;清空:删除key
第二步:购物车每条商品记录保存成两条field;field1用于保存购买数量:命名:
商品id:nums
,保存数据:数字
;field2用于保存购物车中显示的信息,包含文字描述、图片地址、所属商家信息等:命名:商品id:info
,保存数据:json
。示例如下:hmset 003 g01:nums 100 g01:info {...}
第三步:将field2做成一个独立的公共hash,这样会导致公共的商品信息频繁更改,这样解决:
hsetnx key field value
(如果key的field有值,就什么都不做,没值就添加),如:hsetnx 003 g01:nums 400
(其值还是100),所以公共信息就不会频繁更改了。需求:抢购移动、联通、电信的30、50、100元的手机充值卡,每一种抢购上限为1000张
解决方案:
商家(移动等)id作为key,参与抢购的充值卡id作为field、相应的数量作为value,抢购时使用降值(
hincrby+负数
,因为另一种降值,hash不提供)方式控制充值卡数量。示例:hmset p01 c30 1000 c50 1000 c100 1000 hincrby p01 c50 -1
ps:string存储对象(json)与hash存储对象:前者强调整体,以读为主;后者更新比较灵活
-
-
list
保存多个数据,底层使用双向链表存储
-
基本操作
增加(修改)数据
lpush key value1 value2...
(从左边进)rpush key value1 value2...
(从右边进)获取数据
lrange key start stop
(开始和结束位置;lrange key 0 -1
:可以查看全部内容,因为-1代表倒数第一个元素)lindex key index
llen key
获取并移除数据
lpop key
(从左边出)rpop key
(从右边出)ps:list中保存的数据都是string类型的,最多2^32-1个元素。
list可以对数据进行分页操作,通常第1页的信息来自list(提高效率),第2页及后面的信息通过数据库的形式加载(妙啊)。
-
扩展操作
规定时间内获取并移除数据
blpop key1 key2... timeout
(从左边出,在timeout
的时间内一直等着,直到时间结束)brpop key1 key2... timeout
(从右边)需求:朋友圈点赞,要求按照点赞顺序显示点赞好友信息,如果取消点赞,移除对应好友信息。
解决方案:增加用
rpush
就好,移除用lrem key count value
(从左边remove,即删除),如:rpush list1 a b c d e
;lrem list1 1 d
(count代表移除多少个,因为有可能有重复元素,value代表移除谁)。需求:微博中个人用户的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最近关注的粉丝列在前面。
解决方案:用list
-
-
set
与hash存储结构完全相同,仅存储hash中的field,不存储值(nil),不允许重复。
-
基本操作
增加数据
sadd key member1 member2...
获取全部数据
smembers key
删除数据
srem key member1 member2...
获取集合大小
scard key
判断集合中是否包含指定数据
sismember key member
-
扩展操作
集合的交、并、差集
sinter key1 key2... sunion key1 key2... sdiff key1 key2...
求出两个集合的交、并、差集后存储到指定集合中
sinterstore destination key1 key2... sunionstore destination key1 key2... sdiffstore destination key1 key2...
将数据从原来的集合移到另一个集合中
smove soure destination member
需求:app首次使用时会设置3项兴趣,后期为了增加用户的活跃度,必须让用户对其他类别的信息逐渐产生兴趣,增加客户留存度
解决方案:1)系统先分析出客户没有关注的各个分类的最热点信息条目并组织成set集合。2)随机挑选出其中部分信息。3)配合用户关注分类中的热点信息组织成展示的信息集合。
随机获取集合中指定数量的数据
srandmember key count
随机获取集合中的某个数据并将该数据移除集合
spop key [count]
ps:set应用于同类信息的关联搜索,一度关联搜索,二度关联搜索:显示共同关注(一度);显示共同好友(一度);从用户A出发,获取到好友用户B的好友信息列表(一度);从用户A出发,获取到好友用户B的购物清单列表(二度)。
可以用于屏蔽爬虫用户(黑名单),对于安全性更高的应用访问,可以设定可访问的用户群体(白名单)。
-
-
sorted_set
有序(根据自身特征)的set(没错,就是上方的set),在set的基础上添加可排序字段。
-
基本操作
增加数据
zadd key score1 member1 score2 member2...
获取全部数据
zrange key start stop [withscores] //zrange scores 0 -1(升序,不显示排序字段) //zrange scores 0 -1 withscores(升序,显示排序字段) zrevrange key start stop [withscores]//反序,后面的同上
删除数据
zrem key member1 member2...
按条件获取数据
zrangebyscore key min max [withscores][limit]//查询min、max范围之内的数据,limit限制数据条数,如zrangebyscore length 50 99 limit 0 3 withscores,查询范围之内的3条数据,是的,没错,前3条数据。 zrevrangebyscore key max min [withscores]//反序
ps:limit后面跟的是offset和count:作用于查询结果,表示开始位置和数据总数
按条件删除数据
zremrangebyrank key start stop//删除两个索引内的数据,包含边界,两边都包含 zremrangebyscore key min max//删除范围之内的数据
获取集合大小
zcard key zcount key min max//范围之内的数据总数
集合交、并操作(并存储)
zinterstore destination numkeys key1 key2...//numkeys指的是要操作的集合个数,若numkeys为3,后面的key就要有3个,求交之后相同数据的排序字段的值会加在一块 zunionsstore destination numkeys key1 key2...
-
扩展操作
需求:各类资源网站TOP10
解决方案:sorted_set排序即可
获取数据对应的索引(排名)
zrank key member//由小到大的索引 zerevrank key member//由大到小的索引
score值获取与修改
zscore key member//获取对应的score值 zincrby key increment member//增加指定值
ps:score,即排序字段可以是整数也可以是小数(双精度double值,可能会丢失精度:0.5->2-1、0.125->2-3,但是0.3这样的数字无法精准描述,俺之前看到的资料说,这是会有四舍五入的),有范围。
需求:视频VIP,当VIP体验到期之后如何有效管理此类信息
解决方案:将处理时间记录为score值,利用排序功能区分处理的先后顺序;记录下一个要处理的时间,到期后处理对应任务,移除redis中的记录,并记录下一个要处理的时间;当新任务加入时,判定并更新当前下一个要处理的任务时间;为提升性能,通常又会按 1小时内、1天内、1周内分成若干个sorted_set。示例:
zadd ts 1539849 uid:001 zadd ts 1539945 uid:002 zrange ts 0 -1 withscores//根据排序结果001用户先到期
获取当前系统时间
time
需求:当任务或消息待处理,形成了任务或消息队列时,对于高优先级的任务要保障对其有限处理,如何实现权重管理
解决方案:采用score记录权重即可。示例:
zadd tasks 4 order:id:005 zadd tasks 9 order:id:345//排序后先执行该任务,执行完后移除 zrem tasks order:id:345
-
ps:数据类型指的是key-value的value部分的类型,key只能是字符串
通用命令
key通用命令
-
key是一个字符串
-
基本操作
-
删除指定key
del key
-
获取key是否存在
exists key
-
获取key的类型
type key
-
-
扩展操作
-
为key设置有效期
expire key seconds pexpire key milliseconds//此命令设置毫秒时间 expireat key timestamp//使用时间戳,通常在Linux系统中使用时间戳 pexpireat key milliseconds-timestamp
-
获取key的有效时间(还剩多少时间)
ttl key//还剩多少秒,超过设定的时间,返回-2,永久的数据返回-1 pttl key//配套毫秒
-
切换key从时效性转换为永久性
persist key
-
查询key
keys pattern
keys *//查看所有的key key yyh*//查询所有以yyh开头的 key *yyh//查询所有以yyh结尾的 key ??yyh//查询长度为5,以yyh结尾的 key yyh?//查询长度为4,以yyh开头的 key y[yz]h//查询以y开头,以h结尾,中间包含一个字母:y或z的
规则:*匹配任意数量的任意符号 ?匹配一个任意符号 []匹配一个指定符号
-
给key改名
rename key newkey//如果newkey已经存在,那么原来的key的value将覆盖newkey的value(string类型的) renamenx key newkey//解决上面会覆盖的问题,如果newkey已经存在,更改将会失败
-
对所有key排序(用于排序list、set、sorted_set里面的元素)
sort key [desc]//list、set、sorted_set的key名称,不会更改原数据的位置
-
其他key通用操作
help @generic
//看key的全指令(generic组)
-
数据库通用命令
redis为每个服务提供有16个数据库,编号从0到15;每个数据库之间的数据相互独立。
-
切换数据库
select index
//index是0~15,默认操作的是0号 -
数据移动
move key db//移动key到几号数据库,如果目标库有该key,则移动失败
-
数据清除
dbsize//当前库有多少个key flushdb//清除当前数据库的key flushall//清除所有数据库的key
-
其他操作
quit//退出 ping//测试服务器是否连通,连通返回PONG,没有连通则不会有反应,如果是ping abc,则控制台会打印"abc" echo message//相当于system.out.println("message")
jedis
简介
java操作redis的工具,java还有其他的连接redis的方法:spring data redis(这个好)、lettuce。
jedis对应的方法名与redis里面的指令完全相同。
demo
-
依赖
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
-
代码
@Test public void helloJedis(){ //连接redis Jedis jedis = new Jedis("127.0.0.1", 6379); //操作redis jedis.set("message","helloJedis"); String message = jedis.get("message"); System.out.println(message); //关闭连接 jedis.close(); }
Jedis工具类
public class JedisUtils {
private static JedisPool jp = null;
private static String host;
private static int port;
private static int maxTotal;
private static int maxIdle;
static {
ResourceBundle rb = ResourceBundle.getBundle("redis");//redis配置文件名
host = rb.getString("redis.host");
port = Integer.parseInt(rb.getString("redis.port"));
maxTotal = Integer.parseInt(rb.getString("redis.maxTotal"));
maxIdle = Integer.parseInt(rb.getString("redis.maxIdle"));
//JedisPool类是jedis提供的连接池,构造方法的参数有:GenericObjectPoolConfig poolConfig,String host,int port
//poolConfig:连接池配置对象,host:redis地址,port:redis端口
JedisPoolConfig jpc = new JedisPoolConfig();
jpc.setMaxTotal(maxTotal);//按照连接池思想,设置连接池最大连接数
jpc.setMaxIdle(maxIdle);//设置活动连接数
jp = new JedisPool(jpc,host,port);
}
public static Jedis getJedis(){
return jp.getResource();
}
}
redis.host = 127.0.0.1
redis.port = 6379
#按照连接池思想,设置连接池最大连接数
redis.maxTotal = 30
#设置活动连接数
redis.maxIdle = 10
高级
Linux下安装redis
-
下载
wget http://download.redis.io/releases/redis-4.0.0.tar.gz
-
解压(在/usr/local/src目录下)
tar -xvf redis-4.0.0.tar.gz
-
安装
cd redis-4.0.0
make install
-
报错
开始报127错误,解决如下:
yum install cpp yum install binutils yum install glibc yum install glibc-kernheaders yum install glibc-common yum install glibc-devel yum install gcc yum install make
然后报c里面的错:
In file included from adlist.c:34:0: zmalloc.h:50:31: 致命错误:jemalloc/jemalloc.h:没有那个文件或目录
-
启动
在
/usr/local/src/redis-4.0.0/src
目录下执行:redis-server
redis-cli
-
指定端口启动
redis-server --port 6380
redis-cli -p 6380
或者redis-cli -h 127.0.0.1 -p 6380
–port和-p应该是一样的
-
指定配置文件启动
cat redis.conf | grep -v "#" | grep -v "^$"//第二部分是查看的内容去掉注释,第三部分是去掉注释之后会有空白,将空白也去掉,然后进行查看 cat redis.conf | grep -v "#" | grep -v "^$" > redis-6379.conf//将输出的内容新生成一个文件
配置项:
daemonize no
:为no时,控制台会打印日志信息,改成yes之后以守护线程启动,就会在后台启动logfile ""
:后台启动时的日志文件名(不含位置)dir ./
:对应日志文件生成的位置修改配置文件如下:
port 6379 daemonize yes logfile "6379.log" dir /usr/local/src/redis-4.0.0/data
启动:
redis-server redis-6379.conf ps -ef | grep redis//查看启动进程,要启动多个只需要多复制几个配置文件,修改里面的端口和日志文件名就行了,如下:
redis-server conf/redis-6380.conf //在redis-4.0.0下专门建一个文件夹保存配置文件
持久化(用于灾难性恢复)
将内存中的数据与硬盘之间做了一个关联(看来redis确实适合用来做缓存)。
- 将当前数据状态(快照)进行保存,存储数据结果,关注点在数据。redis叫
RDB
- 把这个数据的操作过程记录下来(比如ctrl+z),就是日志形式,关注点在于数据的操作过程。redis叫
AOF
RDB的指令及配置
命令:save
手动执行一次保存操作,生成的数据保存在之前设定的data
目录中,是dump.rdb
文件。当kill掉redis的进程之后,再次启动时redis保存的数据还在(没有执行save,再次启动就没有数据了)
配置:dbfilename dump.rdb
设置保存的文件名,默认值为dump.rdb
,通常设置为dump-端口号.rdb
配置:dir
不仅对应日志文件生成的位置,也对应.rdb文件的路径,通常设置的目录名称为data
配置:rdbcompression yes
设置save时保存的文件是否压缩,默认为yes
,采用LZF压缩,如果设置为no
,可以节省cpu运行时间,但会使存储的文件变得巨大。
配置:rdbchecksum yes
(不用看)
设置是否进行RDB文件格式校验(万一该文件损坏了,所以校验),该校验过程在写文件和读文件过程均进行,默认为yes
,如果设置为no
,可以节约读写过程约10%时间消耗,但是存在数据损坏的风险。
注意:因为redis是单线程(执行的时候像从栈里弹出去一个个的方法),save
指令会阻塞当前redis服务器,直到当前RDB
过程完成为止,如果save
的时间过长,会影响效率,线上环境不建议使用save
。解决办法如下:
命令bgsave
手动启动后台保存操作,但不是立即执行(原理是调用fork函数生成一个子进程)。因此对于RDB
可以放弃save
命令的使用。
配置:stop-writes-on-bgsave-error yes
(了解)
bgsave
的专用配置:如果后台存储过程中出现错误,是否停止保存,默认yes
。
配置:save second changes
满足限定时间范围内key的变化达到指定值即进行持久化,second
:时间范围,changes
:key的变化(包括增加、删除和更新)指定值,比如:save 300 10
。(这样的配置方式执行的是bgsave
)
方式 | save命令 | bgsave命令 | save配置(同bgsave命令) |
---|---|---|---|
读写 | 同步 | 异步 | |
阻塞客户端指令 | 是 | 否 | |
额外内存消耗 | 否 | 是 | |
启动新进程 | 否 | 是 |
ps:RDB特殊启动形式:全量复制:请看主从复制;服务器运行过程中重启:debug reload
;关闭服务器时指定保存数据:shutdown save
。rdb的缺点:因为基于快照,所以数据量大;无法实时(时时刻刻)持久化;有子进程会牺牲性能;redis的众多版本中RDB的文件格式没有统一。
AOF的指令及配置
以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到数据恢复的目的。主要解决了实时持久化的问题,目前是主流方式。
AOF的三种策略:
always(每次):每次写入操作(即get操作不算)均同步到AOF文件中,数据0误差,性能较低,不建议使用
everysec(每秒):每秒将缓冲区的指令同步到AOF文件中,数据准确性较高,性能较高,默认配置,同时也建议使用,在系统突然宕机的情况下丢失1秒内的数据
no(系统控制):由操作系统控制每次同步到AOF文件的周期,整体过程不可控
配置:appendonly yes/no
是否开启AOF持久化,默认关闭
配置:appendfsync always/everysec/no
AOF策略
配置:appendfilename filename
AOF持久化文件名,默认appendonly.aof
,建议改为appendonly-端口号.aof
配置:dir
同RDB
概念:AOF重写
-
将对同一个数据的若干条命令执行结果转化为最终结果对应的命令进行记录,好处是:降低磁盘占用;提高持久化效率;提高数据恢复效率(即用时减少,因为命令少了嘛)
-
重写规则:1.进程内已超时的数据不再写入文件;2.使用进程内数据直接生成AOF文件,只保留数据的最终写入命令(包括增删改,查当然不用管);
-
重写方式:
-
手动重写:
命令:
bgrewriteaof
//原理和bgsave非常像(会有子进程) -
自动重写(激活的也是上面的命令)
配置:
auto-aof-rewrite-min-size size
//自动重写触发的文件大小(aof_current_size>size
),默认值不同版本不一样:32M、64Mps:对比参数为:
aof_current_size
//当前大小(info Persistence
获取该信息,在客户端输入)配置:
auto-aof-rewrite-percentage percentage
//自动重写触发的百分比((aof_current_size-aof_base_size)/aof_base_size>=percentage
)ps:对比参数:
aof_base_size
//基础大小(info Persistence
获取该信息,在客户端输入)
-
对比
持久化方式 | RDB | AOF |
---|---|---|
占用空间 | 小(数据级,可以压缩) | 大(命令级,可以重写) |
存储速度 | 慢 | 快 |
恢复速度 | 快 | 慢 |
安全性 | 会丢失数据 | 以策略决定(可以零误差) |
资源消耗 | 高/重量级 | 低/轻量级 |
启动优先级 | 低 | 高 |
如何选择:
- 如果对数据非常敏感,就AOF
- 数据呈现阶段有效性,可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),就RDB
- 灾难恢复用RDB
- 可以同时开启两者,重启时,redis会优先使用AOF来恢复数据。
持久化应用场景
- 抢购,如限购类、限量发放优惠券、激活码等业务的数据存储设计
- 最新消息展示(临时的任务,存储量不大)
- 具有操作先后顺序的数据控制
- 白名单与黑名单设定的服务控制
- 按次结算的服务控制
redis的事务
-
基本操作
-
开启事务
multi
设定开启位置,后续所有命令均加入到事务中
-
执行事务
exec
设定结束位置,同时执行事务,与
multi
成对出现 -
取消事务
discard
终止当前事务的定义,在
multi
之后,exec
之前 -
注意:
如果中途的命令存在语法错误,所有命令均不会执行,包括正确的命令;
如果中途的命令语法正确,执行结果错误(例如对string类型的name进行lpush),那么能够正确执行的命令会执行,执行结果错误的命令视为无效
-
回滚(这个有点扯)
操作之前拷贝一份受影响的数据,然后利用拷贝的数据恢复所有被修改的项
-
-
监视锁(像互斥锁)
-
对key添加监视锁,在执行
exec
之前如果key发生了变化,终止事务执行watch key1 key2...
-
取消对所有key的监视
unwatch
示例:
127.0.0.1:6379> keys * 1) "name" 127.0.0.1:6379> set age 30 OK 127.0.0.1:6379> get name "lalala" 127.0.0.1:6379> watch name OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> set sex 1 QUEUED 127.0.0.1:6379> exec (nil)
在
exec
之前另开一个会话客户端,然后exec
就会出现上面的结果(取消执行):127.0.0.1:6379> set name hahaha OK
-
-
分布式锁(解决最后一件商品的超卖问题,即被多个用户购买。好像还是互斥锁。)
setnx lock-key value
利用
setnx
命令的返回值特征,有值则返回设置失败,无值则返回设置成功;设置成功的,拥有控制权,进行下一步业务操作,设置失败的,不具有控制权,排队等待;操作完毕之后通过del
删掉即释放锁。示例:
set num 10 setnx lock-num 1 incrby num -1 del lock-num
如果在过程中有人也要执行
setnx lock-num 1
,那么这个操作会返回(integer) 0
,这就是锁[doge]。ps:
expire lock-key second
或者pexpire lock-key millisecond
为锁设置时效性,如果该用户一直不释放锁,应该让系统(时效性)来释放。锁时间推荐:1.持有锁的最长操作时间xxx毫秒2.测试百万次最长执行时间对应命令的最大耗时和百万次网络延迟平均耗时3.最大耗时x120%+平均网络延迟x110%;如果业务最大耗时<<网络平均延迟(也可能反过来),通常为老哥数量级,取其中单个耗时较长(业务最大耗时或者网络平均延迟)即可。
删除策略
redis是一种内存级数据库,所有数据均放在内存中。
TTL
获取数据的状态:XX(具有时效性的数据)、-1(永久的数据)、-2(已经过期或被删除或未定义的数据)
时效性命令:setex、expire、expireat、pexpire、pexpireat
过期数据的删除策略(目标是在内存占用和cpu占用之间寻找一种平衡,顾此失彼可能会造成:redis性能下降、服务器宕机、内存泄漏):
-
定时删除(拿时间换空间)
创建一个定时器,当key过期时,由定时器立马执行对键的删除操作。优点:省内存,到时就删除,缺点:cpu压力大,占用cpu,影响redis服务器响应时间和命令吞吐量
-
惰性删除(拿内存空间换cpu性能,空间换时间)
数据到达过期时间,不做处理,等下次访问数据时删除。优点:节约cpu性能,缺点:内存压力增大,出现长期占用内存的数据
-
定期删除(折中方案,随机抽查,重点删除)
对每个数据库(0到15)轮询,每次执行250ms/server.hz时间,对某个数据库检测时,随机挑W个key检测,如果key过期,则删除key(若删除的key>Wx25%,对剩下的再挑W个,然后进行同样的操作,循环;若删除的key<=Wx25%,则检测下一个数据库)。
特点:cpu占用有峰值;内存压力不大(长期占用内存的数据会被持续清理)
ps:W=ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP属性值(在配置文件配置),参数
current_db
用于记录执行到了哪一个数据库。
逐出算法
如果内存不满足新加入数据的最低存储要求,redis会临时删除一些数据为当前命令清理存储空间(如果不成功,则反复执行,所有尝试完毕后,还是不能满足要求,将出现错误信息),清理的策略称为逐出算法。
-
相关配置
-
最大可使用内存
maxmemory
占用物理内存的比例,默认为0,表示不限制。
-
每次选取待删除数据个数
maxmemory-samples
采用随机获取数据的方式作为待检测删除数据。
-
删除策略
maxmemory-policy
对被挑选出来的数据进行删除的策略,选项如下:
检测易失数据(可能会过期的数据:db[i].expires):
volatile-lru//挑选最近最少使用(get)的数据淘汰,建议设置 volatile-lfu//挑选最近使用次数最少的数据淘汰 volatile-ttl//挑选将要过期的数据淘汰 volatile-random//任意选择数据淘汰
检测全库数据(所有数据db[i].dict):
allkeys-lru//挑选最近最少使用(get)的数据淘汰 allkeys-lfu//挑选最近使用次数最少的数据淘汰 allkeys-random//任意选择数据淘汰
放弃数据逐出
no-enviction//放弃逐出数据(4.0版本默认),可以引发OOM错误
示例:
maxmemory-policy volatile-lru
逐出策略配置的依据:
info//这会输出监控信息,查询缓存hit(命中)和miss(丢失)的次数,根据需求调优redis配置
-
配置文件redis.conf
-
服务器基础配置
-
服务器以守护进程的方式运行
略
-
绑定主机地址(绑定后只能通过这个ip访问)
bind 127.0.0.1
-
服务器端口号
port 6379
-
设置数据库数量
databases 16
//就是那个0到15,默认值就是16 -
设置服务器以指定日志记录级别
loglevel debug/verbose/notice/warning
默认值是verbose
ps:开发设置为
verbose
,生产环境(线上)设置为notice
(简化日志输出,降低写日志IO的频度) -
日志文件名
logfile 端口号.log
-
设置同一时间最大客户端连接数,默认无限制。达到上限时,redis会关闭新的连接
maxclients 0
-
客户端闲置等待最大时长,超时直接关闭连接。关闭该功能:设置为0
timeout 300
//300秒 -
导入并加载指定配置文件信息,用于快速创建公共配置较多的redis实例配置文件(多个redis服务器),便于维护
include /path/server-端口号.conf
-
高级数据类型
Bitmaps(按位操作)
-
基础操作
-
获取指定key对应偏移量上的bit值
getbit key offset
-
设置指定key对应偏移量上的bit值,0或1
setbit key offset value
ps:每一位bit默认为0。
-
-
扩展操作
-
对指定key进行交、并、非、异或操作,并将结果保存到destKey中
bitop op destKey key1 key2...
//and:交;or:并;not:非;xor:异或 -
统计指定key中1的数量
bitcount key [start end]
-
HyperLogLog
基数(不重复的元素的个数),此数据类型用于统计基数
-
基本操作
-
添加数据
pfadd key element
-
统计基数
pfcount key
-
合并数据
pfmerge destkey key1 key2...
ps:此数据类型里面添加的数据无法读出,它只统计基数;此类型的核心是一个基数估算算法,最终数值存在一定误差,基数估计的结果是一个带有0.81%标准错误的近似值;消耗空间极小,每个此类型的key占用12K(上限值)的内存;合并操作后占的存储空间一定是12K。
-
GEO
点与点之间的关联操作,即地理位置计算
-
基本操作
-
添加坐标
geoadd key longitude latitude member
//经度和纬度,member是指坐标的名称 -
获取坐标
geopos key member
-
计算坐标点距离
geodist key member1 member2 [unit]
//unit指单位,如:m表示米(默认值)示例:
geoadd geos 1 1 a geoadd geos 2 2 b geodist geos a b km//km表示千米
-
根据坐标求范围内的坐标
georadius key longitude latitude radius m/km/ft/mi [withcoord] [withdist] [withhash] [count count]
//后面的参数了解就行 -
根据坐标名称求范围内坐标
georadiusbymember key member radius m/km/ft/mi [withcoord] [withdist] [withhash] [count count]
-
获取坐标hash值
geohash key member
-
主从复制
高并发、高性能、高可用。
架构跟mongodb集群很像:master是主节点(提供数据,可读写),slave是从节点(接收数据,只能读),核心工作是master的数据同步到slave中。
这样可以大大提高redis服务器的并发量与数据吞吐量,当master出现问题时,由slave实现快速的故障恢复。
这里出现了持久化之外的数据备份方式:数据热备份。
基于主从复制,构建哨兵与集群,实现redis的高可用方案
建立master与slave之间的连接
-
设置master的地址和端口,保存master信息
在slave客户端发送:
slaveof masterip masterport
,这样会保存master的ip和端口根据保存的信息创建连接master的socket
其他方式:1)命令:
redis-server -slaveof masterip masterport
2)配置:slaveof masterip masterport
-
建立socket连接(从此步开始后面的可以忽略)
-
在slave客户端周期性发送
ping
,master响应pong
-
身份验证
在master配置文件设置密码:
requirepass password
;其他方式:命令:config set requirepass password
;config get requirepass
在slave客户端命令设置密码:
auth password
,master验证授权;其他方式:配置:masterauth password
。因此在启动客户端时要给出密码:
redis-cli -a password
-
在slave客户端发送自己的端口给master:
repconf listening-port 端口号
,master会保存slave的端口号
此时slave有master的地址与端口,master有slave的端口,连接就好了
ps:断开主从,在从客户端发送:slaveof no one
数据同步(主–>从)
-
先执行全量同步
从客户端发送:
psync2
master执行bgsave,生成
RDB
文件,通过socket发送给slave,slave接收之后便会先清空,然后恢复RDB
文件数据 -
后执行部分同步
master将新来的命令发给slave,slave执行重写(bgrewriteaof),同步部分数据。
ps:复制缓冲区大小设定不合理,可能导致数据溢出。如进行全量同步的时候周期太长,到部分同步的时候发现数据已经存在丢失的情况,必须进行第二次全量同步,使得slave进入死循环。解决方法:在master中执行repl-backlog-size 1mb
(默认大小1mb)。
在同步时关闭对外服务:配置slave-serve-stale-data yes/no
slave的数量过多时,调整结构:由一主多从结构变为树状结构,中间节点既是master,也是slave,因此深度越深的slave数据同步的延迟越大。
命令传播(到这里主从复制流程就完全结束了)
当master中的数据被修改后,需要让主从同步到一致。
master将接收到的修改命令发送给slave,slave接收到命令后执行命令。
-
断网
-
网络闪断闪连
忽略
-
短时间网络中断
部分同步
-
长时间网络中断
全量同步
-
心跳机制
进入命令传播阶段,master和slave间需要进行信息交换,使用心跳机制进行维护,实现双方保持在线。
-
master方的心跳
- 命令:
ping
- 周期:由
repl-ping-slave-period
决定,默认10秒 - 作用判断slave是否在线
- 查询:
info replication
,获取slave最后一次连接时间间隔(lag为0或1视为正常)
- 命令:
-
slave方心跳
- 命令:
replconf ack {offset}
- 周期:1秒
- 作用:判断master是否在线,并汇报自己的复制偏移量,获取最新的数据变更命令。
- 命令:
-
注意
-
当slave多数掉线或者延迟过高时,master将拒绝所有信息同步操作(保障数据稳定)
min-slave-to-write 2 min-slave-max-lag 10
当slave数量少于2个,或者slave的连接延迟时长大于等于10时,强制关闭master写功能,停止数据同步。slave数量和延迟都是由slave发送
replconf ack
命令做确认。
-
哨兵模式(仲裁、选举模式)
哨兵就是对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master。哨兵也是一台redis服务器,只是不存储数据。(像仲裁节点,mongodb表示这个我熟)
配置哨兵
-
配置1+2的主从结构(1个master,2个slave)
-
配置三个哨兵(配置相同,端口不同)
主要在于
sentinel.conf
:port 26379 //2+6379 dir /tmp //哨兵工作信息存储目录 sentinel monitor mymaster 127.0.0.1 6379 2 //mymaster为自定义的主节点名,2(通常为哨兵的数量的一半加1,所以哨兵数量通常为单数,mongodb表示我熟)表示由两个哨兵认为mymaster挂了,它就挂了 sentinel down-after-milliseconds mymaster 30000 //多长时间没响应,认定它挂了,30秒 sentinel parallel-syncs mymaster 1 //此值越小服务器压力越小,速度越慢 sentinel failover-timeout mymaster 180000 //在进行同步的时候,多长时间同步完成算有效,没完成就算超时,180秒
-
启动哨兵(哨兵启动之后,配置文件会发生变化,与上方的配置不太一样)
redis-sentinel sentinel-端口号.conf
-
启动顺序
启动时,先master,后slave,最后哨兵
cluster集群(不需要哨兵,自带主从切换,即master挂了的时候)
集群可以解决的问题:redis提供的OPS(读写效率,每秒的操作命令数)可以达到10万/秒,当前业务已经达到10万/秒;单机的内存容量256G,业务需求1T。
作用:分散单台服务器的访问压力,实现负载均衡;分散单台服务器的存储压力,实现可扩展性;降低单台服务器宕机带来的业务灾难。
架构
将多个主从结构(若干台计算机)连接在一起,对外提供更大的访问带宽和存储空间,使其对外呈现单机的服务效果。
搭建
-
配置节点(3主3从)
配置文件,从6501到6506,如:
port 6501 dir "/redis-4.0.0/data" dbfilename "dump-6501.rdb" cluster-enabled yes cluster-config-file "cluster-6501.conf" cluster-node-timeout 5000
接着挨个启动,如:
redis-server conf/redis-6501.conf
创建集群
redis-cli --cluster create ip1:port1 ip2:port2..ip6:port6 --cluster-replicas n
一个master对应n个slave,由最后的参数n决定;匹配顺序为第一个master与前n个slave分为一组,以此类推。
如:
redis-cli --cluster create 127.0.0.1:6501 127.0.0.1:6502 127.0.0.1:6503 127.0.0.1:6504 127.0.0.1:6505 127.0.0.1:6506 --cluster-replicas 1
当在6501(master)操作的时候可能会遇到:
(error) MOVED 5798 127.0.0.1:6502
类似错误,说这个槽在6502。原因是:分槽(slot)类似于hash位置的计算,所以有这样的状况。解决办法:在启动时这样:redis-cli -c -p 6501
(cluster模式启动),操作的时候就会自动跳转到6502
cluster配置
-
是否启用cluster集群模式,成为cluster节点
cluster-enabled yes/no
-
cluster配置文件名,该文件自动生成
cluster-config-file filename
-
节点服务响应超时时间,用于判断该节点是否下线或切换为从节点
cluster-node-timeout milliseconds
-
master连接的slave最小数量
cluster-migration-barrier min_slave_number
cluster节点操作命令
-
查看集群节点信息
cluster nodes
-
添加master到当前集群中,连接时可以指定任意现有节点地址与端口
redis-cli --cluster add-node newip:port nowip:port
添加之后分槽方式1(不创建新槽)
redis-cli --cluster reshard newip:port --cluster-from master-id1,master-id2,master-idn --cluster-to newip-master-id --cluster-slots 槽数量
指定得到的槽数量(正常情况下是:16384/算上新加之后的总的master数量)之后,这些槽将平均从指定的master们处获取。
添加之后分槽方式2(从有槽的master中分配指定数量到另一个master中,常用于清空master的槽)
redis-cli --cluster reshard newip:port --cluster-from master-id --cluster-to newip-master-id --cluster-slots 槽数量 --cluster-yes
-
添加slave
redis-cli --cluster add-node newip:port masterip:port --cluster-slave --cluster-master-id masterid
-
删除节点,如果删除的节点是master,必须保证其中没有槽(slot)
redis-cli --cluster del-node ip:port del-id
ps:masterid和del-id可以通过cluster nodes查看,第一列就是(最后一列是槽);nowip:port输入集群中的任一个即可。