redis
文章目录
一、redis简介
1. redis简介
1.1 什么是Redis
Redis是完全开源免费的,遵守BSD协议,是-一个高性能(NOSQL)的key-value数据库,Redis是一个 开源的使用
ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库 ,并提供多种语言的APl。
1.2 NOSQL
NoSQL,泛指非关系型的数据库,NOSQL即Not-only SQL ,它可以作为关系型数据库的良好补充。随着互联网web2. 0网站的兴起,非关系型的数据库现在成了-一个极其热门的新领域,非关系数据库产品的发展非常迅速
1.2.1 NOSQL 的类别
键值(Key-Value)存储数据库
这一类数据库主要会使用到一一个哈希表,这个表中有一一个特定的键和一 一个指针指向特定的数据。
Key/value模型对于IT系统来说的优势在于简单、易部署。但是如果DBA只对部分值进行查询或更新的时候,Key/value就显得效率低下了
相关产品: Tokyo Cabinet/Tyrant. Redis. Vo1 demort. Berkeley DB
优势: 快速查询
劣势: 存储的数据缺少结构化
列存储数据库
这部分数据库通常是用来应对分布式存储的海量数据。键仍然存在,但是它们的特点是指向了多个列这些列是由列家族来安排的。
相关产品: Cassandra, HBase, Riak
优势: 查找速度快,可扩展性强,更容易进行分布式扩展
劣势: 功能相对局限
文档型数据库
文档型数据库的灵感是来自于Lotus Notes办公软件的,而且它同第一种键值存储相类似。 该类型的数据模型是版本化的文档,半结构化的文档以特定的格式存储,比如JSON。文档型数据库可以看作是键值数据库的升级版,允许之间嵌套键值。而且文档型数据库比键值数据库的查询效率更高。
相关产品: CouchDB、MongoDB
优势: 数据结构要求不严格
劣势: 查询性能不高,而且缺乏统一-的查询语法
图形(Graph)数据库
图形结构的数据库同其他行列以及刚性结构的SQL数据库不同,它是使用灵活的图形模型,并且能够扩展到多个服务器上。NoSQL数据库没有标准的询语言(SQL),因此进行数据库询需要制定数据模型。许多NoSQL数据库都有REST式的数据接口或者查询API.
相关数据库: Neo4J、InfoGrid. Infinite Gr aph
优势: 利用图结构相关算法。
劣势: 需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
1.3 redis特征
特征:
- 数据间没有必然的关联关系
- 内部采用单线程机制进行工作
- 高性能。官方提供测试数据, 50个并发执行100000个请求,读的速度是110000次/s,写的速度是81000次/s。
- 多数据类型支持
● 字符串类型 string
●列表类型 list
●散列类型. hash
●集合类型 set
●有序集合类型 sorted_ set - 持久化支持。 可以进行数据灾难恢复
2. 总结:
因此,我们总结NoSQL数据库在以下的这几种情况下比较适用:
1、数据模型比较简单;
2、需要灵活性更强的IT系统;
3、对数据库性能要求较高;
4、不需要高度的数据一致性;
5、对于给定key ,比较容易映射复杂值的环境
NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题
(高并发)。
2.1 redis 应用场景
企业级开发中:
可以用作数据库、缓存、热点数据(经常会被查询,但是不经常被修改或者删除的数据)和消息中间件等大部分功能。
1. 缓存
2. 排行榜
3. 计数器
4. 分布式会话
5. 分布式锁
6. 社交网络
7. 最新列表
8. 消息系统
二、redis install on Linux
redis is on the C++ edit ,need install gcc ,than install redis.
-
install gcc 环境
gcc的安装很简单,首先要确保root登录,其次就是Linux要能连外网
yum -y install gcc automake autoconf libtool make
-
redis 解压 and install
官网有写,解压到opt目录,tab补全redis目录名
tar zxvf redis-5.0.8.tar.gz -C /opt
4. 编译 文件
cd /opt/redis-5.0.8 && make MALLOC=libc
- 安装到指定目录
make PREFIX=/usr/local/redis install
① Docker install redis
正常
docker pull redis
service docker restart
docker images
异常:
Error response from daemon: Get https://registry-1.docker.io/v2/
原因:docker默认的源为国外官方源,下载速度较慢,需修改docker镜像源为国内。
解决:进入/etc/docker查看有没有 daemon.json,如果没有新建,有则修改。
cd /etc/docker
ls
vi daemon.json
添加如下内容:
{
"registry-mirrors":["https://pee6w651.mirror.aliyuncs.com"]
}
重启docker 服务
service docker restart
Docker start redis
docker ps #look run images
docker start [images name]
docker stop [images name]
三、启动 redis
-
启动的服务端,在redis安装的bin下面
./redis-server
-
启动客户端
在开一个连接端口 或者是后台启动redis服务器
./bin/redis-cli [-h [ip地址] -p [端口]] //默认ip本机 端口6379
碰到 -bash ./redis-cli: 没有那个文件或目录 情景
需要把 redis 的安装目录里的 redis.conf 下的 ### GENERAL ### 栏下 daemonize [no] 改成 [yes] 然后放到etc下的 myredis 下系统会默认使用该配置。具体细节 参考Linux 操作文本步骤
-
退出 redis 服务端和客户端
[CTRL+C] 客户端:shutdown
-
查看redis 状态
ps -ef | grep -i redis
1)、redis 自定义 Config
Redis默认定义了很多默认配置。但在实际开发中. - -般我们都会通过手动配置完成。
回到安装目录下找到解压文件中的reids.confRedis的配置文件位于Redis安装目录下,文件名为redis.conf
1. config redis
命令 : 解压目录下的redis.conf配置文件复制到安装文件的目录下
cp /opt/redis-5.0.5/redis.conf /usr/local/redis/
在自定义写入属性
2. redis.conf 详解
重点了解的
守护进程就是不以单独窗口打开
**1.Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程,
daemonize no
**2.指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为
6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379
**3.绑定的主机地址
bind 127.0.0.1
**8.设置数据库的数量,[默认数据库为0 ,可以使用SELECT <dbid>命令在连接 上指定数据库id
databases 16
**9.指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save <seconds> <changes>
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒( 15分钟)内有1个更改,300秒( 5分钟)内有10个更改以及60秒内有10000个更改。
**10.指定存储至本地数据库时是否压缩数据,默认为yes, Redis采用LZF (压缩算法)压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
**11.指定本地数据库文件名,默认值为dump.rdb
dbfilename dump. rdb
**12.指定本地数据库存放目录
dir ./
**15.设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH <password>命令提供密码 ,默认关闭
requirepass foobared
Redis中的内存维护策略
redis作为优秀的中间缓存件,时常会存储大量的数据,即使采取了集群部署来动态扩容,也应该即时的整理内存,维持系统性能。
在redis中有两种解决方案
① 为数据设置超时时间
设置过期时间,ttl [key] 可以查看过期时间 keys * 参考查看所有key
expire [key] [time] (以秒为单位)--这是最常用的方式
setex(String key, int seconds, String value)--字符串独有的方式
● 除了字符串自己独有设置过期时间的方法外,其他方法都需要依靠expire方法来设置时间
● 如果没有设置时间,那缓存就是永不过期
● 如果设置了过期时间,之后又想让缓存永不过期,使用persist key
② 二、采用LRU算法动态将不用的数据删除
内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,
操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。
- volatile-lru :设定超时时间的数据中,删除最不常使用的数据.
- allkeys-lru :查询所有的key中最近最不常使用的数据进行删除,这是应用最广泛的策略.
- volatile-random :在已经设定了超时的数据中随机删除.
- allkeys-random :查询所有的key,之后随机删除.
- volatile-ttl :查询全部设定超时时间的数据,之后排序,将马上将要过期的数据进行删除操作.
- noeviction :如果设置为该属性,则不会进行删除操作,如果内存溢出则报错返回.
- volatile-lfu :从所有配置了过期时间的键中驱逐使用频率最少的键
- allkeys-lfu :从所有键中驱逐使用频率最少的键
2)、自定义写入Redis
进入对应的安装目录/usr/local/redis
修改redis.conf配置文件vim redis.conf (进入命令模式通过/内容查找相应字符串)
daemonize no 修改为 daemonize yes守护进程启动
bind 127.0.01 注释掉 允许除本机外的机器访问Redis服务
requirepass设置密码设定数据库密码(保证服务安全/有些情况下 不设定密码是无法进行远程连接访问的)
Redis采用的是单进程多线程的模式。当redis.conf中选项daemonize设置成yes时 ,代表开启守护进程模式。在该模式下, redis会在后台运行,并将进程pid号写入至redis.conf选项pidfile设置的文件中,此时redis将一
直运行,除非手动ili该进程。但当daemonize选项设置成no时,当前界面将进入redis的命令行界面, exit强
制退出或者关闭连接I具(putty,xshell等)都会导致redis进程退出。服务端开发的大部分应用都是采用后台运行的模式
requirepass设置密码。因为redis速度相当快,所以一台比较好的服务器下, 一个外部用户在一秒内可以进行15W次密码尝试,这意味着你需要设定非常强大的密码来防止暴力破解。
可以通过redis 的配置文件设置密码参数,这样客户端连接到redis服务就需要密码验证,这样可以让你的
redis服务更安全
① redis 启动
服务端启动:
带上自定义的配置文件,否则会按默认的配置文件来进行
./bin/redis-server redis.conf
客户端登录: 用密码登录
./redis-cli -h host -p port -a password //-h ip地址 -p 端口 -a 密码
② redis 关闭
1. 第一种方式
(断电、非正常关闭。容易数据丢失)
1.1 查询redis进程id
PID ps -ef | grep -i redis
1.2 kill对查询的id进行强制关闭
ki1l -9 PID
2. 第二种方式
(正常关闭、数据保存)
否则数据到等到默认的系统同步时间
-
关闭redis服务器(客户端还没关). 通过客户端进行
shutdown
如果redis设置了密码,需要先在客户端通过密码登录,再进行shutdown即可关闭服务端
3)、可视化Redis Desktop Manager
redis.conf
bind 127.0.01 注释掉 允许除本机外的机器访问Redis服务
requirepass设置密码设定数据库密码(保证服务安全/有些情况下 不设定密码是无法进行远程连接访问的)
-
开启redis端口
cd /etc/sysconfig #进入目录 vi iptables #写入6379端口 -A INPUT -m state --state NEW -m tcp -p tcp --dport 6379 -j ACCEPT cd /etc/init.d #进入目录 ./iptables status #查看开启端口
-
使用软件工具连接到 6379
地址: Linux ip
四、Redis命令
Redis 命令用于在redis 服务上执行操作。要在redis 服务上执行命令需要一一个redis 客户端。
Redis 客户端在我们之前下载的的redis的安装包中。
Redis支持五种数据类型: string (字符串) , hash (哈希) , list(列表) ,
set (集合)及zset(sorted_set) :有序集合)等
1. key
①命名key
redis单个key允许存入512M大小
命名规范:
id name age
1 zhangsan 18
2 lisi 19
user:id:name
user:1:zhangsan [value]
user:2:lisi [value]
- 1.key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率;
- 2.key也不要太短,太短的话,key的可读性会降低;
- 3.在一个项目中, key最好使用统一的命名模式,例如user:123:password;
- 4.key名称区分大小写
② 常用key管理
keys * : 返回满足的所有键,可以模糊匹配比如keys abc*代表abc开头的key * 所有,?一个字符
exists [key key1..] : 是否存在指定的key,存在返回1,不存在返回0
expire [key] second :设置某个key的过期时间时间为秒
del [key] : 删除某个key
ttl [key] :查看剩余时间,当key不存在时,返回-2;存在但没有设置剩余生存时间时,返回-1, 否则,以秒为单 位,返回key的剩余生存时间。
persist [key] :取消过去时间!
PEXPIRE [key] milliseconds 修改key 的过期时间为毫秒
type [key] : 返回储存至的类型
select :选择数据库 数据库为0-15 (默认- -共16个数据库) s
设计成多个数据库实际上是为了数据库安全和备份
move [key] [dbindex] : 将当前数据中的key转移到dbindex数据库
r andomkey :随机返回- -个key
rename [key] [new key name] :重命名key
echo :打印命令
dbsize :查看数据库的key数量
info :查看数据库信息
config get *实时传储收到的请求,返回相关的配置
flushdb : 清空当前数据库
flusha1l : 清空所有数据库
③ 应用
EXPIRE key seconds EXISTS key
1、限时的优惠活动信息
2、网站数据缓存(对于- -些需要定时更新的数据 ,例如:积分排行榜)
3、手机验证码
4、限制网站访客访问频率(例如: 3秒最多访问5次)
五、Redis 数据类型
redis常用的五种数据类型
字符串类型 string 列表类型 list
散列类型 hash 集合类型 set
有序集合类型 sorted_ set (也叫zset)
1. String 类型 (java-String)
①、简介
string类型是Redis最基本的数据类型, -一个键最大能存储512MB。
string数据结构是简单的key-value类型, value其不仅是string ,也可以是数字,是包含很多种类型的特殊类型。
string类型是二进制安全的。意思是redis的string可以包含任何数据。
比如序列化的对象进行存储,比如一张图片进行二 进制存储,比如一个简单的字符串,数值等等。
②、Stirng常用命令
赋值语法:
SET [KEY_NAME] [VALUE]: (说明:多次设置name会覆盖)
命令:
SETNX [key] [value]:(not exist) 如果key1不存在 ,则设值并返回1.如果key1存在,则不设值并返回0; (解决分布式锁方案之一, 只有在key不存在时设置key的值。Setnx( SET if Not eXists )命令在指定的key不存在时,为key设置指定的值)
SETEX [key1] 10 [value] :(expired) 设置key1的值为1x,过期时间为10秒,10秒后key1清除( key也清除)
SETRANGE [key] [start] [value]: 替换指定[value]长度的字符串
取值语法:
GET [KEY_NAME] :获取key的值,如果key不存在,返回nil。如果key储存的值不是字符串类型,返回一个错误。
GETRANGE [key] [start end] : 用于获取存储在指定key中字符串的子字符串。含start 含end
GETBIT key offset :对key所储存的字符串值,获取指定偏移量上的位(bit) =======不懂
GETSET [KEY_NAME] [VALUE] : 设置指定key的值,并返回key的旧值,当key不存在时,返回nil
删值语法:
DEL [KEY_Name] : 删除指定的KEY,如果存在,返回值数字类型。
批量写:MSET [k1] [v1] [k2 [v2] ... 一次性写入多个值
批量读:MGET [k1] [k2] [k3]
自增/自减:
INCR [KEY_Name] :Incr 命令将key中储存的数字值增1。如果key不存在,先初始化为0,然后再执行INCR操作
自增: INCRBY [KEY_Name] [value]: 增量值Incrby 命令将key中储存的数字加,上指定的增量值
自减: DECR [KEY_NAME] [value] DECRBY [KEY_NAME] [value] 减值:DECR 命令将key中储存的数字减1
: (注意这些key对应的必须是数字类型字符串,否则会出错,)
字符串拼接: APPEND [KEY_NAME] [VALUE] :Append命令用于为指定的key追加至未尾,如果不存在,为其赋值
字符串长度:STRLEN [key]
③、应用场景
- 1、String通常用于保存单个字符串或JSON字符串数据
- 2、因String是二进制安全的,所以你完全可以把一个图片 文件的内容作为字符串来存储
- 3、计数器(常规key-value缓存应用。 常规计数:微博数,粉丝数)
INCR等指令本身就具有原子操作的特性,所以我们完全可以利用redis的INCR、INCRBY、 DECR、 DECRBY等指令来实现原子计数的效果。假如,在某种场景下有3个客户端同时读取了mynum的值(值为2) , 然后对其同时进行了加1的操作,那么,最后mynum的值一定是5。不少网站都利用redis的这个特性来实现业务.上的统计计数需求。
2. Hash类型 Object (Map)
①、简介
该类型非常适合于存储值对象的信息
Hash类型是String类型的fie1d和value的映射表,或者说是一个String集合。hash特别适合用于存储对象,相比较而言,将一个对象类型存储在Hash类型要存储在String类型里占用更少的内存空间,并对整个对象的存取。
可以看成具有KEY和VALUE的MAP容器,该类型非常适合于存储值对象的信息,
如:uname , upass , age等。该类型的数据仅占用很少的磁盘空间(相比于ISON)。
Redis中每个hash可以存储2的32次方-1键值对(40多亿)
②、Hash常用命令
FIELD 不能重复
赋值语法:
HSET [KEY] [FIELD] [VALUE]:为指定的KEY,设定FIELD/VALUE
HMSET [KEY] [FIELD] [VALUE] [FIELD1] [VALEUE1].... :同时将多个field-value (域-值)对设置到哈希表key中
取值语法:
HGET [KEY] [FIELD]:获取存储在HASH中的值,根据FIELD得到VALUE
HMGET [KEY] [field] [field1]:获取key所有给定字段的值
HGETALL [KEY] :返回HASH表中所有的字段和值
HKEYS [KEY] : 获取所有哈希表中的字段
HLEN [KEY] :获取哈希表中字段的数量
删除语法:
HDEL KEY [field1] [fie1d2]:删除一个或多个HASH表字段
其它语法:
HSETNX [key] [field] [value] :只有在字段field不存在时,设置哈希表字段的值
HINCRBY [key] [field] incr [ement] :为哈希表key中的指定字段的整数值加上增量increment。
HINCRBYFLOAT [key] [field] incr [ement] : 为哈希表key中的指定字段的浮点数值加。上增量incr ement。
HEXISTS [key] [field] :查看哈希表 key 中,指定的字段是否存在
③、应用场景
Hash的应用场景: (存储-一个用户信息对象数据)
1、 常用于存储一个对象
2、为什么不用string存储-个对象?
hash是最接近关系数据库结构的数据类型,可以将数据库一条记录或程序中一个对象转换成hashmap存放在redis中。
男户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2种存储方式:
第一种方式将用户ID作为查找key ,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。
第二种方法是这个用户信息对象有多少成员就存成多少个key-value对,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。
小总结:
Redis提供的Hash很好的解决了这个问题, Redis的Hash实际是内部存储的Value为-一个HashMap,并提供了直接存取这个Map成员的接口
3. list 类型 (linkList)
①、简介
List类型是一一个链表结构的集合,其主要功能有push、pop、 获取元素等。更详细的说, List类型是一-个双端链表的节后,我们可以通过相关的操作进行集合的头部或者尾部添加和删除元素, List的设计非常简单精巧,即可以作为栈,又可以作为队列,满足绝大多数的需求。
按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)一个列表最多可以包含
232- 1个元素(4294967295,每个列表超过40亿个元素)
类似AVA中的LinkedList
②、list常用命令
赋值
赋值语法:
LPUSH [key] [value1] [value2]... : 将一个或多个值插入到列表头部(从左侧添加)
RPUSH [key] [value1] [va1ue2]... : 在列表中添加一个或多个值(从右侧添加)
LPUSHX [key] [value] : 将一个值插入到已存在的列表头部。如果列表不在,操作无效
RPUSHX [key] [value] : 一个值插入已存在的列表尾部(最右边)。如果列表不在,操作无效。
取值
取值语法:
LLEN [key] :获取列表长度
LINDEX [key] [index] :通过索弓|获取列表中的元素
LRANGE [key] [start] [stop] :获取列表指定范围内的元素
描述 : 返回列表中指定区间内的元素,区间以偏移量START和END指定。偏移量就是减1
其中0表示列表的第一个元素 ,1表示列表的第二个元素,以此类推。
也可以使用负数下标,以-1表示列表的最后一个元素,-2表示列表的倒数第二个元素,以此类推。
start:(页大小页数1)
stop: (页大须数)-1
删除
删除语法:
LPOP [key] 移出并获取列表的第一个元素 (从左侧删除)
RPOP [key] 移除列表的最后一 个元素,返回值为移除的元素(从右侧删除)
BLPOP [key1] [key2] [timeout_time]: 移出并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
实例:
redis 127.0.0.1: 6379> BLPOP list1 100
在以上实例中,操作会被阻塞,如果指定的列表key list1 存在数据则会返回第一个元素 ,否则在等待100秒后会返回ni1
BRPOP [key1] [key2 ] [timeout] : 移出并获取列表的最后一个元素 ,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
LTRIM [key] [start] [stop]: 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
修改
修改语法:
LSET [key] [index] [value]:通过索引设置列表元素的值
LINSERT [key] [BEFORE/AFTER] [in list value] [value] : 在列表的元素前或者后插入元素描述:将值valuf 插入到列表key当中,位于值(list 中的值)之前或之后。
高级命令
高级语法:
RPOPLPUSH source destination : 移除列表的最后一个元表 ,并将该元素添加到另一个列表并返回
rpoplpush [key1] [key2]
示例描述:
RPOPLPUSH [key1] [key2] : key1中的最后元素移到key2的左侧 并返回
RPOPLPUSH [key1] [key2] : 循环列表,将key1中的最后元素移到最左侧 并返回
BRPOPLPUSH [key1] [key2] [timeout] : 从列表最后弹出一个值,将弹出的元素插入到另外一个列表最前面并返回它;如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
③、应用场景
项目常应用于: 1、对数据量大的集合数据删减
2、任务队列
1、对数据量大的集合数据删减
列表数据显示、关注列表、粉丝列表、留言评价等…分页、热点新闻( Top5)等
利用LRANGE还可以很方便的实现分页的功能,在博客系统中,每片博文的评论也可以存入一个单独的list中。
2、任务队列
(list通常用来实现一个消息队列,而且可以确保先后顺序,不必像MySQl那样还需要通过ORDER BY来进行排序)
任务队列介绍(生产者和消费者模式) :
在处理Web客户端发送的命令请求时,某些操作的执行时间可能会比我们预期的更长- -些,通过将待执行任务的相关信息放入队列里面,并在之后对队列进行处理,用户可以推迟执行那些需要一段时间才能能完成的操作,这种将工作交给任务处理器来执行的做法被称为任务队列( task queue )。
RPOPLPUSH source destinati on
移除列表的最后一个元素,并将该元素添加到另一个列表并返回
4.Set 类型
①、简介
Redis的Set是String类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。
Redis中集合是通过哈希表实现的, set是通过hashtable实现的
集合中最大的成员数为 2的32次方- 1 (4294967295,每个集合可存储40多亿个成员)。
类似于JAVA中的Hashtable集合
②、set常用命令
赋值语法:
SADD [key] [member1] [member2]... : 向集合添加一个或多个成员
取值语法:
SCARD [key] : 获取集合的成员数
SMEMBERS [key] : 返回集合中的所有成员
SISMEMBER [key] [member] :判断 member元素是否是集合key的成员(开发中:验证是否存在判断)
SRANDMEMBER [key] [(count)] :返回集合中一个或count个随机数
删除语法:
SREM [key] [member1] [member2]... :移除集合中一个或多个成员
SPOP [key] [count] : 移除并返回集合中的一个随机元素
SMOVE [source] [destination] [member] :将 member元素从source集合移动到destination 集合
差集语法:
SDIFF [key1] [key2]... : 返回给定所有集合的差集(左侧) 返回key1在key2的差集,返回key1中有,key2中没有的
SDIFFSTORE [destination_key] [key1] [key2] : 返回给定所有集合的差集并存储在destination中
交集语法:
SINTER [key1] [key2] : 返回给定所有集合的交集(共有数据)
SINTERSTORE [destination_key] [key1] [key2] : 返回给定所有集合的交集并存储在destination 中
并集语法:
SUNION [key1] [key2] : 返回所有给定集合的并集
SUNIONSTORE [destination_key] [key1] [key2]:所有给定集合的并集存储在destination 集合中
③、应用场景
常应用于:对两个集合间的数据[计算]进行交集、集、差集运算
1、利用集合操作,可以取不同兴趣圈子的交集,以非常方便的实现如共同关注、共同喜好、二度好友等功能。对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存储到一个新的集合中。
2、利用唯一性,可以统计访问网站的所有独立IP、存取当天[或某天]的活跃用户列表。
5. ZSet 类型
有序集合(sorted set)
①、简介
1、Redis有序集合和集合- 样也是string类型元素的集合,且不允许重复的成员。
2、不同的是每个元素都会关联一个double类型的分数。 redis正是通过分数来为集合中的成员进行从小到大的排序
3、有序集合的成员是唯- -的,但分数(score)却可以重复。
4、集合是通过哈希表实现的。集合中最大的成员数为2次方32- 1 (4294967295,每个集合可存储40多亿个成员)。Redis的ZSet是有序、 且不重复
(很多时候,我们都将redis中的有序集合叫做zsets,这是因为在redis中,有序集合相关的操作指令都是以z开头的)
②、ZSet常用命令
赋值语法:
ZADD [key] [score1] [member1] [score2 member2] : 向有序集合添加一个或多个成员,或者更新已存在成员的分数 ----分数可以重复,member1不能重复
取值语法:
ZCARD [key] : 获取有序集合的成员数
ZCOUNT [key] [min] [max] :计算在有序集合中指定区间分数的成员数 [min,max]
ZRANK [key] [member] :返回有序集合中指定成员的索引
ZRANGE [key] [start_index] [stop_index] [(WITHSCORES)] : 通过索引区间返回有序集合成指定区间内的成员(低到高)
ZREVRANGE [key] [start_index] [stop_index] [(WITHSCORES)] : 返回有序集中指定区间内的成员,通过索引,分数从高到底
ZRANGEBYSCORE [key] [min_value] [max_value] [(WITHSCORES)] [(LIMIT)] :通过分数返回有序集合指定区间内的成员 默认排序,由低到高
ZREVRANGEBYSCORE [key] [max_source] [min_source] [WITHSCORES] :返回有序集中指定分数区间内的成员,分数从高到低排序
删除语法:
DEL [key]:移除集合
ZREM [key] [member] [member ...] :移除有序集合中的一个或多个成员
ZREMRANGEBYRANK [key] [start_index] [stop_index] :移除有序集合中给定的排名区间的所有成员(第一名是0) (低到高排序)
ZREMRANGEBYSCORE [key] [min_source] [max_source] : 移除有序集合中给定的分数区间的所有成员
ZINCRBY [key] [increment_value] [member_value]: 增加memeber元素的分数increment, 返回值是更改后的分数
取值升序降序表
语法 | 取值方式 | 排序方式 |
---|---|---|
ZRANGE | 索引区间 | 升序 |
ZREVRANGE | 索引区间 | 降序 |
ZRANGEBYSCORE | 分数值区间 | 升序 |
ZREVRANGEBYSCORE | 分数值区间 | 降序 |
③、应用场景
常应用于:排行榜
销量排名,积分排名等
比如twitter,的public timeline可以以发表时间作为score来存储, 这样获取时就是自动按时间排好序的。
比如一一个存储全班同学成绩的Sorted Set ,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。
还可以用Sorted Set来做带权重的队列,比如普通消息的score为1 ,重要消息的score为2 ,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
6. HyperLogLog
①、简介
Redis在2.8.9版本添加了HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法, HyperLogLog的优点是,在输入元素的数量或者体积非常非常大
时,计算基数所需的空间总是固定的、并且是很小的。
在Redis 里面,每个HyperLogLog 键只需要花费12 KB内存,就可以计算接近2^64 个不同元素的基数。这和
计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以HyperLoglog不能像集合那样,返回输入的各个元素。
小知识:
什么是基数?
比如数据集{1, 3,5, 7, 5,7,8},那么这个数据集的基数集为 {1,3,5 ,7, 8},基数(不重复元素)为5.
基数估计就是在误差可接受的范围内,快速计算基数。
为什么需要HyperLogLog
//如果要统计1亿个数据的基数值,大约需要内存100000000/8/1024/1024 R 12M ,内存减少占用的效果显著。
//然而统计一一个对象的基数值需要12M ,如果统计10000个对象,就需要将近120G,同样不能广泛用于大数据场景。
②、ZSet常用命令
PFADD [key] [element1] [element2]... : 添加指定元素到HyperLogLog 中
PFCOUNT [key1] [key2].. : 返回给定HyperlogLog的基数估算值数量
PFMERGE destkey sourcekey [sourcekey ... :将多个 HyperlogLog 合并为一个HyperLoglog
③、应用场景
基数不大,数据量不大就用不上,会有点大材小用浪费空间
有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么
统计注册IP数
统计每日访问IP数
统计页面实时UV数
统计在线用户数
统计用户每天搜索不同词条的个数
统计真实文章阅读数
④总结
HyperLoglog是一种算法,并非redis独有
目的是做基数统计,故不是集合,不会保存元数据,只记录数量而不是数值。
耗空间极小,支持输入非常体积的数据量
核心是基数估算算法,主要表现为计算时内存的使用和数据合并的处理。最终数值存在一定误差
redis中每个hyper 1oglog key占用了12K的内存用于标记基数(官方文档)
pfadd命令并不会一次性分配12k内存,而是随着基数的增加而逐渐增加内存分配;而pfmerge操作则sourcekey合并后存储在12k大小的key中,这由hyper1og1og合并操作的原理(两个hyper1og1og合并时需要单独比较每个桶的值)
可以很容易理解。
误差说明:基数估计的结果是一个带有0.81%标准错误( standard error )的近似值。是可接受的范围
Redis对HyperLogLog 的存储进行了优化,在计数比较小时,它的存储空间采用稀疏矩阵存储,空间占用很小,仅仅
在计数慢慢变大,稀疏矩阵占用空间渐渐超过了阈值时才会一次性转变成稠密矩阵,才会占用12k 的空间
六、SpringBoot 整合redis
常用的redis客户端
Jedis :是老牌的Redis的Java实现客户端,提供了比较全面的Redis命令的支持, Redisson :实现了分布式和可扩展的Java数据结构。 Lettuce :高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentine1,管道和编码器。
优点:
Jedis :比较全面的提供了Redis的操作特性 Redisson :促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列 Lettuce :基于Netty框架的事件驱动的通信层,其方法调用是异步的。Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作
Jedis and Lettuce
jedis使直接连接redis server ,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个jedis实例增加物理连接; lettuce的连接是基于Netty的,连接实例( StatefulRedi sConnection )可以在多个线程间并发访问, StatefulRedi sConnect ion是线程安全的,所以一一个连接实例可以满足多线程环境下的并发访问,当然这也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。 Redisson实现了分布式和可扩展的Java数据结构,和Jedis相比,功能较为简单,不支持字符串操作,不支持排 序、事务、管道、分区等Redis特性。Redi sson的宗旨是促进使用者对Redis的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
总结:
优先使用Lettuce,如果需要分布式锁,分布式集合等分布式的高级特性,添加Redisson结合使用,因为 Redisson本身对字符串的操作支持很差。 在一些高并发的场景中,比如秒杀,抢票,抢购这些场景,都存在对核心资源,商品库存的争夺,控制不好,库存数 量可能被减少到负数,出现超卖的情况,或者产生唯一的一个递增ID ,由于web应用部署在多个机器上,简单的同步加锁是无法实现的,给数据库加锁的话,对于高并发,1000/s的并发,数据库可能由行锁变成表锁,性能下降会厉害。那相对而言,redis的分布式锁,相对而言,是个很好的选择,redis官方推荐使用的Redisson就提供了分布式锁和相关服务。
1)、SpringBoot 整合 Jedis
简介
Jedis :是老牌的Redis的Java实现客户端,提供了比较全面的Redis命令的支持,
我们在使用springboot搭建微服务的时候,在很多时候还是需要redis的高速缓存来缓存一一些数据 ,存储一些高频率访问的数据,如果直接使用redis的话又比较麻烦,在这里,我们使用jedis来实现redis缓存来达到高效缓存的目的
引入Jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
application.yml
spring:
redis:
port: 6379
host: 192.168.154.130
password: 1008611
jedis:
pool: #jedis连接池
max-idle: 6 #max 空闲数
max-active: 10 # max 连接数
min-idle: 2 # min 空闲数
timeout: 2000 #connect time out
编写JedisConfig
@Configuration
public class JedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.jedis.pool.max-idle}")
private int maxIdle;
@Value("${spring.redis.jedis.pool.min-idle}")
private int minIdle;
@Value("${spring.redis.jedis.pool.max-active}")
private int maxActive;
@Value("${spring.redis.timeout}")
private int timeout;
@Bean
public JedisPool jedisPool(){
JedisPoolConfig jPC = new JedisPoolConfig();
jPC.setMaxIdle(maxIdle);
jPC.setMinIdle(minIdle);
jPC.setMaxTotal(maxActive);
JedisPool jedisPool = new JedisPool(jPC,host,port,timeout,password);
return jedisPool;
}
}
Jedis案例
redis 中有什么命令,jedis中就有什么方法
①、Jedis 操作String类型
@Service
public class UserServiceimpl implements UserService {
/**
* redis 中有什么命令,jedis中就有什么方法
*/
@Autowired
private JedisPool jedisPool;
@Autowired
private JedisUtil jedisUtil;
@Override
public String getString(String key) {
Jedis jedis = jedisPool.getResource();
String val = null;
if(jedis.exists(key)){
System.out.println("query in the redis data");
val = jedis.get(key);
}else {
System.out.println("query in the mysql data");
jedis.set(key,"user data");
val = jedis.get(key);
}
jedis.close();
return val;
}
@Override
public void expireStr(String key, String value) {
Jedis jedis = jedisUtil.getJedis();
jedis.set(key,value);
jedis.expire(key,50);
jedisUtil.close(jedis);
}
}
②、Jedis 操作Hash类型
/**
* #######Hash 类型测试
* 往里面进行存储对象
* 1. 首先,进行判断redis中是否有值
* 2. 如果没有值就到mysql中去查询值 然后存储到redis中
* 3. 关闭
*/
@Override
public User selectById(int id){
String str = "user:"+id;
User u = new User();
Jedis jedis = jedisUtil.getJedis();
if(jedis.exists(str)){
System.out.println("-----------存在,从redis中查询!");
Map<String,String> map = jedis.hgetAll(str);
u.setId(map.get("id"));
u.setName(map.get("name"));
u.setAge(Integer.parseInt(map.get("age")));
}else {
//查询数据库,赋值给redis
System.out.println("-----------redis中不存在,从mysql中查询!");
u.setId(id+"");
u.setName("乔峰");
u.setAge(19);
System.out.println("-----------往redis中存储!");
Map<String,String> map =new HashMap<>();
map.put("id",u.getId());
map.put("name",u.getName());
map.put("age",u.getAge()+"");
jedis.hmset(str,map);
}
return u;
}
2)、SpringBoot2.x 中redis 使用(lettuce)
1. maven配置
<! --默认是1ettuce客户端-->
<dependency>
<groupId>org.springframework.boot </groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis依赖commons-poo1 这个依赖一定要添加-->
<dependency>
<groupId>org.apache.commons</ groupId>
<artifactId>commons-poo12</artifactId>
</dependency>
application.yml
spring:
redis:
port: 6379
password: 1008611
host: 192.168.154.130
lettuce:
poo1:
max-active: 8 #连接池最大连接数(使用负值表示没有限制)
max-idle: 8 #连接池中的最大空闲连接
min-idle: 0 #连接池中的最小空闲连接
max-wait: 1000 #连接池最大阻塞等待时间(使用负值表示没有限制)
shut down-timeout: 100 #关闭超时时间
redisConfig
如果不进行配置会字符序列化,在reids里面显示的是字符编码,提取出不会。
配置之后,在redisDesktop 工具里面不是编码其他依旧。
@Configuration
public class LettuceConfig {
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//序列化方式
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);
//template 中使用 String 类型的序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
//template on the hash key 的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
2. Lettuce 使用案例
其实是对 redisTemlate 的进一步的封装
①、lettuce 操作 String 类型
redisTemlate.opsForValue 下的方法
String 类型的方法 都在redisTemlate.opsForValue() 下面
@Service
public class UserServiceimpl implements UserService {
/**
* String data test
* Redis 有什么命令,Jedis 有什么方法
* RedisTemplate 方法----> RedisTemplate (Jedis/...)进一步的封装。
* RedisTemplate 方法 和命令 肯定不一样
* @param key
* @return
*/
@Autowired
private RedisTemplate redisTemplate;
@Override
public String getString(String key) {
String str = null;
if(redisTemplate.hasKey(key)){
System.out.println("in the redis data");
str = (String) redisTemplate.opsForValue().get(key);
}else {
System.out.println("on the mysql data");
str = "正在 study redis!";
redisTemplate.opsForValue().set(key,"正在 study redis!");
}
return str;
}
@Override
public void expireStr(String key, String value) {
if(redisTemplate.hasKey(key)){
redisTemplate.expire(key,1, TimeUnit.HOURS);
}else {
redisTemplate.opsForValue().set(key,value);
redisTemplate.expire(key,1, TimeUnit.HOURS);
}
}
}
②、Lettuce 操作 Hash 类型
Hash 类型的方法 都在redisTemlate.opsForHash() 下面
public interface KeyNameUtil{
/*用户 user 表 的 前缀,*/
String USER = "user"
}
@Override
public User selectById(String id) {
// 可以在user类里面定义一个方法 把重复的 “user” 提取出来
if(redisTemplate.opsForHash().hasKey(User.getKeyName(),id)){
System.out.println("in the reids query data!");
return (User)redisTemplate.opsForHash().get("user",id);
}else {
User u = new User();
u.setId(id);
u.setName("木兰");
u.setAge(20);
System.out.println("on the mysql query !");
// h 表名, hk id(primary key) hv value (hash (key value))
// 接口 属性,“user”将来可以换表很方便
redisTemplate.opsForHash().put(KeyNameUtil.USER,id,u);
// hash 的 field value存的是 json 字符串
return u;
}
}
③、Lettuce 操作 List 类型
List 类型的方法 都在redisTemplate.opsForList() 下面
物流案例:
@Service
public class ListQueueCacheServiceimpl implements ListQueueCacheService {
@Autowired
private RedisTemplate redisTemplate;
//把模板类注入在接口中
@Resource(name="redisTemplate")
private ListOperations<String, String> listOperations;
/**
* 1. 生成订单流程
*/
@Override
public void orderQueue(String orderId){
String key = "queue:"+orderId;
if(redisTemplate.hasKey(key)){
return ;
}
listOperations.leftPush(key,"1、商家发货(北京海淀某小区)");
listOperations.leftPush(key,"2、快递小哥取货");
listOperations.leftPush(key,"3、北京首都机场-南京禄口机场");
listOperations.leftPush(key,"4、南京禄口机场-建邺区");
listOperations.leftPush(key,"5、建邺区-千锋教育二楼");
listOperations.leftPush(key,"6、收货");
}
/**
* 2. 快递小哥发货,队列事件 带订单号参数
* 消费一个任务
*/
@Override
public String orderTouch(String orderId){
String key = "queue:"+orderId;
String keySucc = "queue:"+orderId+":succ";
return listOperations.rightPopAndLeftPush(key,keySucc);
}
/**
* 3. 快递公司、还有几项任务完成,带订单号参数
*/
@Override
public List<String> orderSelect(String orderId){
String key = "queue:"+orderId;
return listOperations.range(key,0,-1);
}
/**
* 4. 用户
* 关注:我的快递到哪里了
* 理论上还要加一个判断key存不存在
*/
@Override
public List<String> orderSelectSucc(String orderId){
String key = "queue:"+orderId+":succ";
if(!redisTemplate.hasKey(key)){
return null;
}
return listOperations.range(key,0,-1);
}
}
④、Lettuce 操作 Set 类型
Set 类型的方法 都在redisTemlate.opsForSet() 下面
⑤、Lettuce 操作 Zset 类型
ZSet 类型的方法 都在redisTemlate.opsForZSet() 下面
七、Redis其他功能
1. Redis发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。
Redis客户端可以订阅任意数量的频道。
当前用redis订阅功能并不是特别多,
Redis发布订阅(pub/sub)是一种消息通信模式: 发送者(pub)发送消息,订阅者(sub)接收消息。
Redis客户端可以订阅任意数量的频道。
下图展示了频道channell,以及订阅这个频道的三个客户端-- client2 、client5 和client1 之间的关系
当有新消息通过PUBL ISH命令发送给频道channe11 时这个消息就会被发送给订阅它的三个客户端:
订阅频道:
SUBSCRIBE channe1 [channel ...] :订阅给定的一 一个或多个频道的信息
PSUBSCRIBE pattern [pattern ...] :订阅一个或多个符合给定模式的频道。
发布频道:
PUBLISH channel message :将信息发送到指定的频道。
退订频道:
UNSUBSCRIBE [channel [channel . .CO] :指退订给定的频道。
PUNSUBSCRIBE [pattern [pattern ...] :退订所有给定模式的频道。
应用场景
这一-功能最明显的用法就是构建实时消息系统,比如普通的即时聊天,群聊等功能
1、在一个博客网站中,有10个粉丝订阅了你,当你发布新文章,就可以推送消息给粉丝们。
2、微信公众号模式微博,每个用户的粉丝都是该用户的订阅者,当用户发完微博,所有粉丝都将收到他的动态;
2. Redis多数据库
Redis下,数据库是由一个整数索引标识,而不是由一个数据库名称。默认情况下,一个客户端连接到数据库0。
redis配置文件中下面的参数来控制数据库总数:
database 16 //(从0开始1 2 3 ..15)
select [database_index] :数据库//数据库的切换
移动数据(将当前key移动另个库)
move [key]
数据库清空 :
flushdb : 清除当前数据库的所有key
flushall : 清除整个radis在数据库所有key
3. Redis事务
Redis事务可以一次执行多个命令,(按顺序地串行化执行, 执行中不会被其它命令插入,不许加塞)
简介
Redis事务可以一次执行多个命令(允许在一次 单独的步骤中执行一组命令 ),并且带有以下两个重要的保证:
批量操作在发送EXEC命令前被放入队列缓存。
收到EXEC命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
- Redis会将一个事务中的所有命令序列化,然后按顺序执行
- 执行中不会被其它命令插入,不许出现加赛行为
常用命令
MULTI
:标记-个事务快的开始。
DISCARD
:取消事务,放弃执行事务块内的所有命令。
EXEC
:执行所有事务块内的命令。
UNWATCH
:取消WATCH命令对所有key的监视。
WATCH key [key ...]
:监视一个(或多个) key, 如果在事务执行之前这个(或这些) key被其他命令所改动,那么事务将被打断。
一个事务从开始到执行会经历以下三个阶段:
开始事务。
命令入队。
执行事务。
案例:
转账:
1、输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会立即执行
2、直到输入Exec后, Redis会将之前的命令队列中的命令依次执行
127.0.0.1:6379[1]> set account:a 100
OK
127.0.0.1:6379[1]> set account:b 12
OK
127.0.0.1:6379[1]> multi
OK
127.0.0.1:6379[1]> get account:a
QUEUED
127.0.0.1:6379[1]> get account:b
QUEUED
127.0.0.1:6379[1]> decrby account:a 50
QUEUED
127.0.0.1:6379[1]> incrby account:b 50
QUEUED
127.0.0.1:6379[1]> get account:a
QUEUED
127.0.0.1:6379[1]> get account:b
QUEUED
127.0.0.1:6379[1]> exec
1) "100"
2) "12"
3) (integer) 50
4) (integer) 62
5) "50"
6) "62"
案例1:事务的错误处理
事务的错误处理:
如果执行的某个命令报出了错误, 则只有报错的命令不会被执行,而其它的命令都会执行,不会回滚。
命令没问题,语法有问题的时候
127.0.0.1:6379[1]> multi
OK
127.0.0.1:6379[1]> set aa hello
QUEUED
127.0.0.1:6379[1]> incrby aa 50
QUEUED
127.0.0.1:6379[1]> get aa
QUEUED
127.0.0.1:6379[1]> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) "hello"
命令有问题:
队列中的某个命令出现了报告错误,执行时整个的所有队列都会被取消。
127.0.0.1:6379[1]> multi
OK
127.0.0.1:6379[1]> set bb hello
QUEUED
127.0.0.1:6379[1]> get bb
QUEUED
127.0.0.1:6379[1]> asdf;
(error) ERR unknown command `asdf;`, with args beginning with:
127.0.0.1:6379[1]> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379[1]> discard
(error) ERR DISCARD without MULTI
案例2 : WATCH
开启watch监视后,在执行事务的过程中,有别的命令对该事务中的key进行修改,那么事务将全部回滚;
127.0.0.1:6379[1]> watch bb
OK
127.0.0.1:6379[1]> multi
OK
127.0.0.1:6379[1]> get bb
QUEUED
127.0.0.1:6379[1]> set bb 999
QUEUED
127.0.0.1:6379[1]> get bb
QUEUED
127.0.0.1:6379[1]> exec
(nil)
在事务的执行期间进行的修改
127.0.0.1:6379[1]> set bb 333
OK
应用场景
一组命令必须同时都执行,或者都不执行。
我们想要保证一组命令在执行的过程之中不被其它命令插入。比如 : 秒杀 (进行的一系列动作)
4. Redis数据淘汰策略Redis.conf
5. Redis持久化
什么是Redis持久化?
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
Redis提供了两种持久化方式:RDB (默认)和AOF
简介
数据存放于:
内存 : 高效、断电(关机)内存数据会秩
硬盘 : 读写速度慢于内存,断电数据不会失
Redis持久化存储支持两种方式: RDB和AOF。RDB一定时间取存储文件,AOF默认每秒去存储历史命令,
Redis是支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到硬盘来保证持久化。
RDB
rdb是Redis DataBase缩写
功能核心函数rdbSave(生成RDB文件)和rdbLoad (从文件加载内存)两个函数
RDB :是redis的默认持久化机制。
快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为
dump.rdb.
优点:快照保存数据极快、还原数据极快,适用于灾难备份
缺点:小内存机器不适合使用,RDB机制符合要求就会照快照
快照条件:
1、服务器正常关闭时. /bin/redis-cli shutldown
2、key满足一 定条件,会进行快照
vim redis. conf搜索save
:/save
save 900 1 //每900秒( 15分钟)至少1个key发生变化,产生快照
save 300 10 //每300秒( 5分钟)至少10个key发生变化,产生快照
save 60 10000 //每60秒( 1分钟)至少10000个key发生变化,产生快照
AOF
由于快照方式是在一定间隔时间做一 次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用aof持久化方式。
Append-only file:aof比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
每当执行服务器(定时)任务或者函数时flushAppendOnlyFile函数都会被调用,这个函数执行以下两个工作
aof写入保存:
WRITE :根据条件,将aof_ buf中的缓存写入到AOF文件
SAVE :根据条件,调用fsync或fdatasync函数,将AOF文件保存到磁盘中。
有三种方式如下(默认是:每秒fsync一次)
- appendonly yes //启用aof持久化方式
- #appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化
- appendfsynceverysec //每秒钟写入磁盘- 次,在性能和持久化方面做了很好的折中
- #appendfsync no //完全依赖OS ,性能最好持久化没保证
产生的问题:
aof的方式也同时带来了另一一个问题。款化文件会变的越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,实有99条都是多余的。
6. Redis换成与数据库一致性
①、实时同步
对强一致要求比较高的,应采用实时同步方案,即查询缓存查询不到再从DB查询,保存到缓存;更新缓存时,
更新数据库,再将缓存的设置过期(建议不要去更新缓存内容,直接设置缓存过期)。
@Cacheable :查询时使用,注意Long类型需转换为Sting类型,否则会抛异常
@CachePut :更新时使用,使用此注解, 一定会从DB上查询数据
@CacheEvict :删除时使用;
@Caching :组合用法
非实时同步:
实时:一方修改,另一方同步修改
非实时:一方修改,另一方向不需要同步修改
例:1个文章1分钟内被100万点击(点击数100万)
可以定一个 定时任务(凌晨2点触发定时任务) :将redis num值询出来更新mysq1
redis incr num num= 100万
mysq1 num:0;
异步队列:
消息队列:
电商网站:
注册完成--》1、邮箱发送注册成功欢迎信息 4
--》2、手机号发送(注册成功) 欢迎信息 3
--》3、将注册的信息插入到数据库 1 8秒钟
异步队列:
流量的削峰
同时执行可能需要8秒,但前面两项并不是必须要同步的,可以延后,重要的是第三项。可以先执行
异步
中间件:解决方案(一个一个解决)
②、异步队列
对于并发程度较高的,可采用异步队列的方式同步,可采用kafka等消息中间件处理消息生产和消费。
比较主流的异步队列:
activemq rabbitmq zeromq rocketmq Kafka
Kafka ---》速度快,免费,顺序存储
默认存在硬盘的时候是随机存储的,kafka是按规律顺序存储的
可靠性高:
rocketmq ---》可靠性最高(阿里巴巴给apache的)--(昂贵)
rabbitmq ---》免费开源(Pivotal)
③、使用阿里的同步工具canal
canal实现方式是模拟mysql slave 和master的同步机制,监控DB bitlog 的
日志更新来触发缓存的更新,此种方法可以解放程序员双手,减少工作量,但在
使用时有些局限性。
④、采用UDF自定义函数的方式
面对mysql的API进行编程,利用触发器进行缓存同步,但UDF主要是c/c++语
言实现,学习成本高。
总结
穿透
缓存穿透是指查询一个-定不存在的数据,于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这
将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。
解决办法:持久层查询不到就缓存空结果,查询时先判断缓存中是否exists(key) ,如果有直接返回空,没有则查询
后返回,
注意insert时需清除查询的key ,否则即便DB中有值也查询不到(当然也可以设置空缓存的过期时间)
redis key 是否存在
如果不存在
到mysql中去查询 当key有值的时候再重新给 123 赋值
写到redis中
redis.set 123 "null"
如果存在
在redis中查询
如果redis中不存在,mysql中也不存在,就形成了缓存穿透
雪崩
雪崩:缓存大量失效的时候,引发大量查询数据库。
解决办法:
用锁/分布式锁或者队列串行访问
缓存失效时间均匀命布
如果缓存集中在一段时间内失效 ,发生大量的缓存穿透,所有的查询都落在数据库上,造成了缓存雪崩。
这个没有完美解决办法,但可以分析用户行为,尽量让失效时间点均匀分布。大多数系统设计者考虑用加锁或者队列的方式保证缓存的单线程(进程)写,从而避免失效时大量的并发请求落到底层存储系统上。
1. 加锁排队.限流–限流算法.
在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
简单地来说,就是在缓存失效的时候(判断拿出来的值为空) , 不是立即去load db ,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD )去set一个mutex key ,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法。
SETNX,是「SETif Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。
2. 数据预热(最简单的方案)
可以通过缓存reload机制,预先去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的key ,设置不同
的过期时间,让缓存失效的时间点尽量均匀。
就是先操作一下进行redis的缓存,然后再次查询的时候就是查询的redis的数据
热点key
热点key:某个key访问非常频繁,当key失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃。
解决办法:
- 使用锁,单机用synchronized,lock等,分布式用分布式锁。
- 缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存。
- 在value设置-一个比过期时间t0小的过期时间值t1 ,当t1过期的时候,延长t1并做更新缓存操作。
- 设置标签缓存,标签缓存设置过期时间,标签缓存过期后,需异步地更新实际缓存
解决方案:
只用对 热点key进行 双重检测锁压测
public Users selectById(String id) {
//查询缓存
Users users = (Users)hash.get("users",id);
if(null==users){
synchronized(this){
users = (Users)hash.get("users" ,id);
if(null==users){
System.out.println("--查询数据库--");
//缓存为空,查询数据库
users=usersMapper.selectById(id);
//查询的结果存入Redis中
hash.put("users",id,users);
}
}
}
return users;
}
八、高级配置
可能的问题
一般来说, 要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:
1、从结构上,单郸Redis服务器会发生单点故障,并且一 台服务器需要处理所有的请求负载,压力较大; (容错
性)
2、从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内容容为256G ,也不能将所有内容用
作Redis存储内存,一般来说 ,单台Redis最大使用内存不应该超过20G。
问题:
1、内存容量有限====> (主从复制解决了)
2、处理能力有限====>(哨兵模式解决了,master死了就赶紧通知,从新选一个master)
3、无法高可用。(高可用:6个9,5个9)
解决:
垂直扩展
(1)增强单机硬件性能,例如:增加CPU核数如32核,…
(2) 提升单机架构性能,例如:使用Cache来减少I0次数…
水平扩展
水平扩展:只要增加服务器数量,就能线性扩充系统性能。水平扩展对系统架构设计是有要求的,难点在于:如何
在架构各层进行可水平扩展的设计,
主从复制
简介
电子商务网站上的商品,一般都是一次 上传,无数次浏览的,说专业点也就是”多读少写”。
主从复制
一个Redis服务可以有多个该服务的复制品,这个Redis服务称为Master ,其它复制称为Slaves
-
读写分离,不仅可以提高服务器的负载能力,并且可以根据读请求的规模自由增加或者减少从库的数量。
-
数据被复制成了了好几份,就算有一台机器出现故障,也可以使用其他机器的数据快速恢复。
需要注意的是:在Redis主从模式中,一台主库可以拥有多个从库,但是一个从库只能隶属于一 个主库。
Redis主从复制配置
未实验
在Redis中,要实现主从复制架构非常简单,只需要在从数据库的配置文件中加上如下命令即可:
1、主数据库不需要任务配置,创建一 个从数据库:
redis.conf(配置文件信息)
port 6380 :从服务的端口号
--slaveof 127 .0.0.1 6379 :指定主服务器
2、启动从数据库:
. /bin/redis-server ./redis.conf --port 6380 --slaveof 127.0.0.1 6379
加上slaveof参数启动另一个Redis实例作为从库 ,并且监听6380端口
3、登录到从服务器客户端: 从 只能做查询
./bin/redis-cli -p 6380 -a 1008611
变回主: slaveof no one //不是任何从
变回从: slaveofip地址 端口号
哨兵模式
简介
Redis-Sentine1 (哨兵模式)是高可用解决方案,当redis在做master-slave的高可用方案时,假如master宕机了,redis本身(以及其很多客户端)都没有实现自动进行主备切换,而redis-sentinel本身也是独立运行的进程,可以部署在其他与redis集群可通讯的机器中监控redis集群。
有了主从复制的实现之后,我们如果想从服务器进行监控,那么在redis2 .6以后提供了一一个"哨兵“机制,并在2.8版本以后功能稳定起来。
哨兵:顾名思义,就是监控Redis系统的运行状况
哨兵模式的特点
1、不时地监控r edis是否按照预期良好地运行;
2、如果发现某个redis节点运行出现状况,能够通知另外-一个进程(例如它的客户端);
3、能够进行自动切换。当一个master节点不可用时,能够选举出master的多个slave(如果有超过一个slave的话)中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。
4、哨兵为客户端提供服务发现,客户端链接哨兵,哨兵提供当前master的地址然后提供服务,如果出现切换,也就是master挂了,哨兵会提供客户端个新地址。
Redis Cluster 集群
简介
集群模式是实际使用最多的模式。
Redis Cluster是社区版推出的Redis分布式集群解决方案,主要解决Redis分布式方面的需求,比如,当遇到单机
内存.并发和流是等瓶颈的时候, Redis Cluster能起到很好的负载均衡的目的。
为什么使用redis-cluster?
为了在大流量访问下提供稳定的业务,集群化是存储的必然形态
未来的发展趋势肯定是云计算和大数据的紧密结合
只有分布式架构能满足要求
集群描述
Redis集群搭建方案:
( 1 ) Twitter开发的twemproxy
( 2 )豌豆英开发的codis
( 3 ) redis官方的redis-cluster
Redis集群搭建的方式有多种,但从redis 3.0之后版本支持redis-cluster集群,至少需要3(Master)+3(Slave)才能
**建立集群。**Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。其redis-cluster架构图如如下侧: 略
Redis Cluster集群特点
1、所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
2、节点的fail是通过集群中超过半数的节点检测失效时才生效。
3、客户端与redis节点直连,不需要中间proxy层客户端不需要连接集群所有节点连接集群中任何一一个可用节点即可。
4、redis-cluster把所有的物理节 点映射到[0-16383]slot上(不一定是平均分配) ,cluster 负责维护
5、Redis集群预分好16384个哈希槽 ,当需要在Redis 集群中放置-个key-value时,redis 先对key使用crc16算法算出一个结果,然后把结果对16384求徐数,这样每个key都会对应一个编号在0-16383之间的哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节
Redis Cluster容错
容错性,是指软件检测应用程序所运行的软件或硬件中发生的错误并从错误中恢复的能力,通常可以从系统的可靠性、可用性、可测性等几个方面来衡量。
1. 什么时候判断master不可用?
投票机制。投票过程是集群中所有master参与,如果半数以上master节点与master节点通信超时(cluster-node-
timeout),认为当前master节点挂掉.
2. 什么时候整个集群不可用(cluster_ state:fail)?
如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成集群的slot映射[0-16383]不
完整时进入fail状态.如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态.
Redis Cluster节点分配
Redis Cluster采用虚拟槽分区,所有的键根据哈希函数映射到0 ~ 16383个整数槽内,每个节点负责维护-部分
槽以及槽所印映射的键值数据。
三个主节点分别是: A,B,C三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那
么,采用哈希槽(hash slot)的方式来分配16384个slot的话,它们三个节点分别承担的slot区间是
节点A覆盖0 - 5460;
节点B覆盖5461 - 10922;
节点C覆盖10923 - 16383
Redis Cluster集群搭建
redis集群需要至少要三个master节点,我们这里搭建三个master节点,并且给每个master再搭建一个slave
节点,总共6个redis节点,这里用台机器(可以多台机器部署,修改一下ijp地址就可以了 )部署6个redis实例,
三主三从,搭建集群的步骤如下:
1、创建redis节点安装目录。
mkdir /usr/local/redis_cluster :指定目录下 创建 redis_cluster
2、在readis_cluster目录下。创建7000-7500个文件夹下。
mkdir 7000 7001 7002 7003 7004 7005
3、并将redis-conf 分别拷贝到7000-7005 文件夹下
cp /opt/reids-5.0.5/redis.conf ./7000
4、修改Redis配置文件
/usr/local/redis_cluster/7000/redis.conf
#关闭保护模式用于公网访问
protected-mode no
port 7000
#开启集群模式
cluster-enabled yes
cluster-config-file nodes -7000. conf
cluster-node-timeout 5000
#后台启动
daemonize yes
pidfile /var/run/redis. 7000.pid
logfile "7000. 1og"
############################dir /redis/data 没做这一项
#此处绑定ip可以是阿里内网ip和本地ip也可以直接注释掉该项
#bind 127.0.0.1
#用于连接主节点密码
masterauth guoweixin
#设置redis密码各个节点请保持密码一致
requirepass guoweixin
5、依次复制并修改6个redis.conf
cp ./1o00/redis.conf ./7001/ :依次进行复制
vim ./7001/redis. conf :执行 %s/[old_value]/[new_value]/g全部替换 :wq 保存并退出即可
6、依次启动6个节点
将安装的redis目录下的src复制到cluster下 ,方便启动服务端
cd /opt/redis-5.0.0 : 进入redis安装目录
cp -r ./src/ /usr/1oca1/redis_cluster/ :将src文件复制到redis_.cluster 目录中
./src/redis-server ./7000/redis.conf
./src/redis-server ./7001/redis.conf
./src/redis-server ./7002/redis.conf
./src/redis-server ./7003/redis.conf
./src/redis-server ./7004/redis.conf
./src/redis-server ./7005/redis.conf
查看进程状态
ps -ef | grep -i redis
6个独立的redis服务器
7、创建集群
Redis 5版本后通过redis-cli客户端命令来创建集群。6个服务器,是三主三从
启动客户端 集群命令 创建 密码 ip+端口
./src/redis-cli --cluster create -a 1008611 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1
# --cluster-replicas 1 表示创建每个主机有一个从机
yes #同意前面的配置
Redis Cluster集群验证
在某台机器上(或)连接集群的7001端口的节点;
./src/redis-cli -h 127.0.0.1 -c -p 7000 -a guoweixin :加参数 -C可连接到集群
redis cluster在设计的时候,就考虑到了去中心化,去中间件,也就是说,集群中的每个节点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据
基本命令
info replication 通过Cluster Nodes 命令和 Cluster Info 命令来看看集群效果
127.0.0.1:7000> info replication
#查看整个节点信息
cluster nodes
每个Redis的节点都有一一个ID值 ,此ID将被此特定redis实例永久使用,以便实例在集群上下文中具有唯- -的名
称。每个节点都会记住使用此ID的每个其他节点,而不是通过IP或端口。IP地址和端口可能会发生变化,但唯- -的
节点标识符在节点的整个生命周期内都不会改变。我们简单地称这个标识符为节点ID。
测试
127.0.0.1:7000> set dd 789
[root@localhost redis_cluster]# ./src/redis-cli -c -p 7003 -a 1008611
127.0.0.1:7003> keys *
1) "dd"
127.0.0.1:7003> get dd
Redis Cluster总结
简介:
Redis cdluster 为了保证数据的高可用性,加入了主从模式, -一个主节点对应- 一个或多个从节点,主节点提供数
据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取-一个来充当主节点,从
而保证集群不会挂掉。
集群有ABC三个主节点如果这3个节点都没有加入从节点,如果B挂掉了,我们就无法访问整个集群了。A和C的
slot也无法访问。
所以我们在集群建立的时候, -定要为每个主节点都添加了从节点比如像这样,集群包含主节点A. B、C, 以及
从节点A1. B1、C1,那么即使B挂掉系统也可以继续正确工作。
B1节点替代了B节点,所以Redis集群将会选择B1节点作为新的主节点,集群将会继续正确地提供服务。当B重
新开启后,它就会变成B1的从节点。
不过需要注意,如果节点B和B1同时挂了, Redis集群就无法继续正确地提供服务了。
Redis Cluster 关闭集群
启动集群
开启全部redis节点 redisall.sh
/usr/local/redis_cluster/src/redis-server ./7000/redis.conf
/usr/local/redis_cluster/src/redis-server ./7001/redis.conf
/usr/local/redis_cluster/src/redis-server ./7002/redis.conf
/usr/local/redis_cluster/src/redis-server ./7003/redis.conf
/usr/local/redis_cluster/src/redis-server ./7004/redis.conf
/usr/local/redis_cluster/src/redis-server ./7005/redis.conf
chmod u+x redisall.sh :执行将redisall.sh变成可执行文件
./redisall.sh :在当前目录下启动:
关闭集群
/usr/local/redis_cluster目录下编写脚本文件: vim shutdown.sh
内容如下:
/usr/local/redis_cluster/src/redis-cli -c -h 127.0.0.1 -p 7000 -a 1008611 shutdown
/usr/local/redis_cluster/src/redis-cli -c -h 127.0.0.1 -p 7001 -a 1008611 shutdown
/usr/local/redis_cluster/src/redis-cli -c -h 127.0.0.1 -p 7002 -a 1008611 shutdown
/usr/local/redis_cluster/src/redis-cli -c -h 127.0.0.1 -p 7003 -a 1008611 shutdown
/usr/local/redis_cluster/src/redis-cli -c -h 127.0.0.1 -p 7004 -a 1008611 shutdown
/usr/local/redis_cluster/src/redis-cli -c -h 127.0.0.1 -p 7005 -a 1008611 shutdown
chmod u+x shutdown.sh : 然后执行将shutdown. sh变成可执行文件
./shutdown.sh :在当前目录下启动
查看: ps aux | grep redis
官方: /usr/loca1/redis.cluster/redis-cli -a xxx -c -h 192.168.5.100 -p 8001
提示: -a访问服务端密码,-c表示集群模式,-h指定ip地址,-p指定端口号