Redis 是一个高性能的key-value数据库,非关系型数据库。
NoSQL,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在处理web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,出现了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,特别是大数据应用难题。摘自百度百科
- 单机mysql的年代:
90年代,一个基本的网站访问量一般不会太大,单个数据库完全足够!那个时候,更多的去使用静态网页html,服务器根本没有太大的压力。
瓶颈:
- Memcached(缓存)+ MySQL + 垂直拆分(读写分离)
网站80%的情况都是在读,每次都要去查询数据库的话就十分麻烦!所以说我们希望减轻数据库的压力,我们可以使用缓存来保证效率!
发展过程:优化数据结构和索引 --> 文件缓存(IO)–> Memcached(当时最热门的技术)
- 分库分表 + 水平拆分 + MySQL集群
技术和业务在发展的同时,对人的要求也越来越高!
本质:数据库(读,写)
早些年MyISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题
转战lnnodb:行锁
慢慢的就开始使用分库分表来解决写的压力!MySQL 在那个年代推出了表分区!这个并没有多少公司使用!
MySQL的集群,很好满足哪个年代的所有需求!
- 如今的年代
2010-2020 十年之间,世界已经发生了翻天覆地的变化;(定位,也是一种数据,音乐,热榜!)
MSQL 等关系型数据库就不够用了!数据量很多,变化很快~
MySQL有的使用它来存储一些比较大的文件,博客,图片!数据库表很大,效率就低了!如果有一种数据库来专门处理这种数据,MySQL压力就变得十分小(研究如何处理这些问题!)大数据的IO压力下,表几乎没法更大!
目前一个基本的互联网项目!
为什么要用NoSQL
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
这时候我们就需要使用NOSQL数据库的,Nosql 可以很好的处理以上的情况!
NOSQL= Not Only SQL(不仅仅是SQL)
关系型数据库:表格,行,列
泛指非关系型数据库的,随着veb2.0互联网的诞生!传统的关系型数据库很难应对web2.0时代!尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题,NOSQL在当今大数据环境下发展的十分迅速,Redis是发展最快的,而且是我们当下必须要掌握的一个技术! NoSQL特点 解耦! 了解:3v+3高 大数据时代的3V:主要是描述问题的 大数据时代的3高:主要是对程序的要求 真正在公司中的实践:NOSQL + RDBMS 一起使用才是最强的! 思考问题:这么多东西难道都是在一个数据库中的吗? 技术急不得,越是慢慢学 ,才能越扎实! 没有什么是加一层解决不了的! 要知道,一个简单地网页背后的技术一定不是大家所想的那么简单! 解决问题: 这里以上都是NoSQL入门概述,不仅能够提高大家的知识,还可以帮助大家了解大厂的工作内容! KV键值对: 新浪:Redis 美团:Redis + Tair 阿里、百度:Redis + memecache 文档型数据库(bson格式 和json一样): 列存储数据库: 图形关系数据库: 四者对比 敬畏之心可以使人进步! Redis是什么? Redis(Remote Dictionary Server ),即远程字典服务。 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。 redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。 Redis 能干嘛? 1、内存存储、持久化,内存中是断电即失、所以说持久化很重要 (rdb、aof) 特性 1、多样的数据类型 学习中需要用到的东西 官网:http://www.redis.cn 下载地址:http://www.redis.cn/download.html 注意:Windows版本在Github上下载(停更很久了),Redis推荐在Linux系统上搭建。 参考官网"下载页面"下方的文档:http://www.redis.cn/download.html 进入要保存redis源程序的路径,使用下边的命令下载redis: 扩展:建议使用homebrew安装redis,见后边笔记。 解压Redis的安装包!程序一般放在 Redis的默认安装路径 笔记摘自: 注意:使用homebrew安装的软件,会安装在homebrew的根目录下(一般是/opt/homebrew)。所以redis的相关文件也在其中。本案例中,redis的配置文件在/opt/homebrew/etc/redis.conf 使用homebrew安装redis。 可执行程序,在 相关的配置文件,在 扩展:opt有可选的意思,用来安装附加软件包。是用户级的程序目录,安装到/opt目录下的程序,它所有的数据、库文件等等都是放在同个目录下面。 在 1.Redis默认不是后台启动的,需要修改配置文件。(**注意:**homebrew下载的redis,使用brew命令启动,默认是以守护线程的方式运行) protected模式,会导致远程连接失败。在学习阶段可将protected模式关闭,方便远程连接。 2.关闭protected模式,以使用redis可视化工具redisinsight连接虚拟机中的redis服务器。 配置文件详细介绍,见后边的笔记。 启动Redis服务 测试: redis-benchmark 是一个压力测试工具!官方自带的性能测试工具! 命令参数: 测试: redis默认有16个数据库。 默认使用的是第0个(0-15)。 Redis是单线程的 明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了! Redis 为什么单线程还这么快? 核心:redis 是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上,在内存情况下,这个就是最佳的方案! 官网文档 全段翻译: incr命令,数字值增一(increase,增加) decr命令,数字值减一(decrease,减少) incrby命令,增加指定的数量 decrby命令,减少指定的数量 value除了可以是字符串还可以是数字! 总结:Hash更适合对象信息的保存(经常变动),String更适合字符串存储。 List列表,基本的数据类型,有序可重复。在redis里面,我们可以把list当作栈、队列、阻塞队列使用。 所有的list命令都是L开头的。(命令不区分大小写) 将List看作是一个横着放的容器,添加元素时从左边放入,取出时从右边取 Lpush命令,L - list,push - 推进,往list中添加元素(同时创建list)。L也可以看作是left,即从左边推进(添加)元素(一个或多个元素)。 Rpush命令,R - right,push - 推进,往list的右边推进(添加)元素 Lrange命令,L - list,range - 范围,根据索引获取list中某段范围内的元素。用法与前边的getrange类似。 应用场景:消息排队,消息队列 (Lpush Rpop),栈( Lpush Lpop)等。 set集合。set中的值是不能重复的(无序不可重复)。 微博,A用户将所有关注的人放在一个set集合中!将它的粉丝也放在一个集合中! Hash是一个 string类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。(field - value映射可以看作是键值对key - value) 与String中的incrby相似,但又不同。这里增减都是使用一个命令 总结:Hash更适合对象信息的保存(经常变动),String更适合字符串存储。 Zset - 有序集合。在set的基础上增加了一个值。可用于排序。 思路:set排序,存储班级成绩表,工资表排序。 普通消息,1,重要消息,2,带权重进行判断。 排行榜应用实现,取Top N测试。 geospatial,地理位置。geo可用于推算地理位置的信息,两地之间的距离,方圆几里的人。 应用场景:朋友的定位,附近的人,打车距离计算。 redis hyperloglog 基数统计的算法。redis2.8.9版本开始支持。 优点:占用的内存是固定,2^64不同的元素的基数,只需要12KB内存,如果要从内存角度来比较的话hyperloglog 是首选。 A{1,3,5,7,9}、B{1,3,5,9} 基数(不重复的元素) = 4,可以接受误差 应用场景:网页的UV(一个人访问一个网站多次,但是还是算作一个人)。传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为标准判断。这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数而不是保存用户id。 0.81%错误率,统计UV任务,可以忽略不计的。 bitmaps,位存储。 应用场景:统计疫情感染人数:0 0 0 1。统计用户信息,活跃,不活跃。登录,未登录。两个状态的,都可以使用Bitmaps Redis事务本质:一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行过程中会按照顺序执行。一次性、顺序性、排他性。 Redis事务没有隔离级别的概念。 所有的命令在事务中,并没有直接被执行。只有发起执行命令的时候才会执行,Exec 注意:Redis单条命令是保证原子性的(要么都执行要么都不执行),但是事务不保证原子性。 Redis的事务: 锁:redis可以实现乐观锁,watch 正常执行事务: discard,放弃事务 编译型异常(代码有问题,redis命令拼写错误,能在编译时检测到),事务中所有的命令都不会被执行 运行时异常,如果事务队列中存在语法性错误(redis命令的拼写没有错误,使用上有错误,只有在运行时才会发现),那么执行命令的时候,其它命令是可以正常执行的。 监控,watch。面试常问 悲观锁:认为什么时候都会出问题,无论做什么都会加锁 乐观锁: 认为什么时候都不会出问题,不会上锁。更新数据的时候去判断一下,在此期间是否有人修改过这个数据 获取version 更新的时候比较version Redis监听案例: 正常执行: 测试多线程修改值,使用watch可以当作redis的乐观锁操作。 Jedis,使用Java来操作Redis。 Jedis是官方推荐的java连接开发工具,使用java操作redis中间件。 1、导入依赖 2、编码测试 jedis -> lettuce SpringBoot操作数据:springData,jpa、jdbc、mongodb、redis SpringBoot是和SpringData齐名的项目 说明:在SpringBoot 2.x之后,原来使用的jedis被替换为了lettuce。 jedis:采用直连,多个线程操作的情况是不安全的,如果要避免需使用jedis pool连接池,更像BIO模式。 lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况。可以减少线程数据,更像是NIO模式。 SpringBoot整合: 源码分析 配置: 问题:出现乱码。 原因是默认序列化配置导致,默认序列化方式是JDK序列化,会对字符串进行转义。要解决这个问题,需要自定义配置类,使用redis提供的序列化实现(有string、json等)。 自定义配置类,替换默认的redisTemplate。类RedisConfig中,返回bean,名为myRedisTemplate。 指定使用我们自定义的myRedisTemplate 自定义redis工具类,简化redisTemplate的使用。 建议:启动redis时使用我们自定义的配置文件(多了解一下redis配置) 配置后台启动、关闭protected模式,见前边的笔记。 通用的配置文件,可使用include包含 redis是内存数据库,如果不进行持久化操作,那么数据断电就丢失。 持久化,在规定时间内执行了多少次操作,会持久化到文件.rdb.aof 可以设置密码,默认没有 限制客户端 append only模式,aof RDB 做全量持久化,AOF 做增量持久化。Redis 4.0 之后推出 RDB-AOF 混合持久化模式,作为默认配置来使用: 在指定时间间隔内将内存中的数据集快照写入磁盘,即snapshot快照。它恢复时是将快照文件直接读到内存中。 redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,持久化过程都结束了再这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作,确保了极高性能。如果要进行大规模数据恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加高效。RDB的缺点是最后一次持久化后的数据可能会丢失。redis默认就是RDB,一般不需要修改其配置。 rdb保存的文件是dump.rdb,在配置文件中的“快照”部分进行配置: 触发rdb规则,生成dump.rdb文件 只需要将rdb文件放在redis启动目录下就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据 查看需要存放的位(rdb文件与aof文件等等): 优点: 缺点: AOF,append only file 将所有的命令都记录下来,恢复的时候就把这个文件全部再执行一遍。默认文件无限追加。 以日志的形式来记录每个写操作,将redis执行过的所有指令记录下来(读操作不记录),只会追加文件不会改写文件(前边redis指令),redis启动会读取该文件,根据日志文件的内容将指令从前到后执行一次以完成数据的恢复,重新构建数据。 配置文件: 重写规则: 优点: 缺点: Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统。 Redis客户端可以订阅任意数量的频道。 订阅/发布消息图:消息发送者、频道、消息订阅者 下图展示了频道channel1,以及订阅这个频道的三个客户端 – client2、client5和client1之间的关系: 当有新消息通过publish命令发送给频道channel1时,这个消息就会被发送给订阅它的三个客户端: 订阅端: 发送端: Redis是使用C实现的,通过分析 Redis 源码里的 pubsub.c 文件,了解发布和订阅机制的底层实现,籍此加深对 Redis 的理解。 1、实时消息系统 2、实时聊天(频道当作聊天室,将消息回显给所有人) 3、订阅、关注系统都可以 稍微复杂的场景可以使用消息中间件来实现。 主从复制,是指将一台Redis服务器的数据,复制到其他Redis服务器。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据的复制是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主。 默认情况下,每台Redis服务器都是主节点。一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。 一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的(宕机),原因如下: 电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是“多读少写”。对于这种场景,可以使用下边的架构: 主从复制,读写分离。80%的情况下都是在进行读操作,减缓服务器的压力,架构中经常使用,一主二从。 只配置从库,不配置主库。默认情况下,每台Redis服务器都是主节点,只需要配置从机即可。 使用命令的方式配置主从机,是临时的,重启失效(恢复为主机)。在配置文件中进行配置才是永久生效。 只需要配置从机,使用命令slaveof指认主机即可。 不需要配置。 主机可以写,从机不能写只能读。主机中所有信息和数据,都会自动被从机保存。 主机断开连接(宕机、网络原因等),依然可以从“从机”读数据,但是没有写操作。与主机恢复连接后,依然可以获取到主机后边写入的数据。 也可以组成链式结构。主 - 从 - 从 - 从 配置命令也是使用前边的“slaveof”,只不过全部指向主机改成链式。 若主机宕机了,要在剩下的从机中选择一个作为主机。可使用命令“slaveof no one”进行配置。 原主机恢复后,不会主动恢复之前的关系,不过可以使用“slaveof”命令配置为从机。 前边,主从切换需要人工干预,当“主”服务器宕机后,需要手动把一台“从”服务器切换为“主”服务器,费时费力还会造成一段时间内服务不可用。哨兵模式,Redis自2.8开始正式提供Sentinel(哨兵)架构,能够在后台监控主机是否故障,如果发生故障则通过自动选举把其中一台“从”服务器转换为“主”服务器。 哨兵模式是一种特殊的模式,Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。 作用: 然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。(除了监控各个服务器之外,哨兵之间也会互相监控) 假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为“主观下线”。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为“客观下线”。 我们目前的状态是一主二从。 1、配置哨兵配置文件sentinel.conf。vim sentinel.conf 后边参数“1”,表示多少个主节点检测到主节点有问题就进行故障转移。其实就是有多少个哨兵(sentinel)认为这个主节点主观下线才算真正的下线,然后进行故障转移,让其客观下线 2、启动哨兵 如果master节点断开了,就会从从机中随机选择一个服务器。 如果主机回来了,只能归并到新的主机下,当作从机。 优点: 缺点: 哨兵模式的全部配置: Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题就是数据的一致性问题,从严格意义上来讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。另外的一些典型问题就是缓存穿透、缓存雪崩、缓存击穿。目前,业界也都有比较流行的解决方案。 缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(例如秒杀),于是都去请求持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。 布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力。 当存储层不命中后,即使返回的是空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。 但是这种方法存在两个问题: 这里需要注意和缓存穿透的区别,缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。 当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大。 设置热点数据永不过期。从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题。 分布式锁:使用分布式锁,保证对每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。 缓存雪崩,是指在某一个时间段,缓存集中过期失效。Redis宕机。 产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入来缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会到达存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。 其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。 这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活) 这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。 数据预热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。 课程学习自:遇见狂神说
很多的数据类型:用户的个人信息、社交网络、地理位置等。这些数据类型的存储不需要一个固定的格式!不需要多余的操作就可以橫向扩展的!Map
1、方便扩展(数据之间没有关系,很好扩展!)
2、大数据量高性能(Redis 一秒写8万次,读取11万,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!
3、数据类型是多样型的!(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人就无法设计了!)
4、传统 RDBMS 和 NoSQL传统的 RDBMS(Relational Database Management System,关系数据库管理系统)
- 结构化组织
- SOL
- 数据和关系都存在单独的表中(row,col)
- 操作操作,数据定义语言
- 严格的一致性
- 基础的事务
- ......
Nosql
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性,
- CAP定理和BASE(异地多活) 初级架构师!(狂神理念:只要学不死,就往死里学!)
- 高性能,高可用,高可扩
- ......
技术没有高低之分,就看你如何去使用!(提升内功,思维的提高!)
阿里巴巴演进分析
开源才是技术的王道!
任何一家互联网的公司,都不可能只是简简单单让用户能用就好了!
大量公司做的都是相同的业务;(竞品协议)
随着这样的竞争,业务是越来越完善,然后对于开发者的要求也是越来越高!# 1、商品的基本信息
名称、价格、商家信息
关系型数据库就可以解决了! MySQL / oracle(淘宝早年就去IOE了!一 王坚。推荐文章:阿里云的这群疯子)
淘宝内部的 MySQL 不是大家用的 MySQL
# 2、商品的描述、评论(文字比较多)
文档型数据库中,MongoDB
# 3、图片
分布式文件系统 FastDFS
- 淘宝自己的 TFS
- Google的 GFS
- Hadoop HDFS
- 阿里云的 OSS
# 4、商品的关键字 (搜索)
- 搜素引擎 solr elasticsearch
- Isearch:多隆(多去了解一下这些技术大佬!)
所有的牛人都有一段艰苦奋斗的岁月!但是你只要像SB一样的去坚持,终将牛逼!
# 5、热门商品的波动信息
- 内存数据库
- Redis Tair、Memache...
# 6、商品的交易,外部的支付接口
- 三方应用
大型互联网应用问题:
NoSQL的四大分类
Redis入门
概述
免费和开源!是当下最热门的 NoSQL技术之一!也被人们称之为结构化数据库!
2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量!)
6、…
2、持久化
3、集群
4、事务
5、…
MacOS/Linux安装
wget http://download.redis.io/releases/redis-6.0.6.tar.gz
解压
/opt
下暂时申请root权限(输入当前用户的密码):
sudo su
复制到opt目录(Mac OS下载的文件一般保存在~/Downloads目录下):
cp ~/Downloads/redis-6.2.4.tar.gz redis-6.2.4.tar.gz
解压:
tar -zxvf redis-6.2.4.tar.gz
进入解压后的文件目录:
cd redis-6.2.4
编译&安装
编译redis(进入解压后的文件目录后):
make
确认是否编译成功:
make install
若编译失败,则需要安装make命令相关的依赖,gcc-c++。Mac OS自带无需担心。
/usr/local/bin
(用户自行编译安装时默认的可执行程序的安装位置)homebrew安装
# 命令安装
brew install redis
# 初次启动
brew services start redis
# 连接redis
redis-cli
> set name nazimei
OK
> ping
PONG
# 关闭redis服务器,并退出
> shutdown
not connected> quit
配置
/usr/local/bin
/bin
/opt
/etc/redis.conf
/usr/local/bin
下新建一个myconf文件夹,专门保存程序运行所需要的配置文件。将opt下的redis默认配置文件拷贝一份到myconf文件夹中,初始配置文件redis.conf,一般在/opt/redis-版本号/redis.conf
。//创建myconf文件夹,并将配置文件redis.conf备份到该文件夹下:
mkdir /usr/local/bin/myconf && cp /opt/redis-6.2.4/redis.conf /usr/local/bin/myconf
//homebrew安装的redis,备份配置文件为 myredis.conf
qsdbl@macbook ~ % cd /opt/homebrew/etc
qsdbl@macbook etc % sudo cp redis.conf myredis.conf
//备份文件夹可以在任意路径下创建,但是建议放在/usr/local/bin下,跟可执行程序放一起。
后台启动
//使用vim编辑器打开配置文件:
vim redis.conf
输入英文冒号:进入底线命令模式
输入命令set nu回车显示行号
输入命令/daemonize回车定位到字符daemonize(作为守护线程运行)
按i键,进入编辑模式。将后边的no更改为yes
按ESC键退出编辑模式,输入英文冒号:进入底线命令模式
输入wq回车,保存并退出vim编辑器
//默认的配置文件,不做修改:
/opt/redis-6.2.4/redis.conf
//修改 拷贝到myconf的配置文件(自定义):
/usr/local/bin/myconf/redis.conf
protected
1.修改配置文件
找到 protected-mode yes,修改为protected-mode no
2.临时关闭。启动时添加选项--protected-mode no
redis-server --protected-mode no
3.若不生效,可两个都试试(或注释掉配置)
启动&关闭
#会默认调用/usr/local/bin路径下的redis-server程序(默认安装路径),后边指定配置文件(前边配置后台方式运行的默认配置文件,这种启动方式不会打印出redis的logo)
#若不指定配置文件,则会使用默认的/opt/redis-6.2.4/redis.conf
#启动:使用/usr/local/bin/myconf下的配置文件启动
redis-server /usr/local/bin/myconf/redis.conf
#homebrew安装的redis,启动:
redis-server /opt/homebrew/etc/myredis.conf
#或:(使用brew启动,使用默认配置文件。若要自定义配置文件,使用上边的命令启动)
brew services start redis
#关闭(只能通过下边命令关闭,否则homebrew会自动启动)
brew services stop redis
#关闭:后台运行的服务,可通过redis客户端连接后使用命令shutdown关闭服务或系统命令关闭服务
#使用内置的客户端与Redis进行交互(会默认调用/usr/local/bin路径下的redis-cli程序),后边指定已启动的Redis服务的端口号,默认为6379(以配置文件中的为准)
redis-cli -p 6379
命令ping,用于测试连接
命令set key名 value值,用于设置key/value键值对
命令get key名,用于获取value值
命令keys *,用于查询所有的key
命令shutdown,用于关闭Redis服务(服务端程序)
命令exit,用于退出内置的redis客户端
#命令ps查看启动的redis服务进程:
ps -ef | grep redis
#字段含义如下:
UID PID PPID C STIME TTY TIME CMD
UID:程序被该 UID 所拥有
PID:就是这个程序的 ID
PPID:则是其上级父程序的ID
C:CPU使用的资源百分比
STIME:系统启动时间
TTY:登入者的终端机位置
TIME:使用掉的CPU时间。
CMD:所下达的是什么指令
#除了前边的命令shutdown可以关闭redis服务,还可以使用kill命令结束程序
kill -9 程序PID
测试性能
#测试本地redis服务(默认6379端口),100个并发,10万条请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
#可简写为:
redis-benchmark -c 100 -n 100000
#若内存不够,可能会报“Error from server: MISCONF Redis is configured to save RDB snapshots, but it is...“类似的错误。
#启动redis服务(配置文件我拷贝到/usr/local/bin下的myconf文件夹下了,与前边有点出入)
qingshuaidebaoluo@qingshuaidebaoluoMac-mini ~ % redis-server /usr/local/bin/myconf/redis.conf
#开始测试
qingshuaidebaoluo@qingshuaidebaoluoMac-mini ~ % redis-benchmark -c 100 -n 100000
====== PING_INLINE ====== #ping操作
100000 requests completed in 0.50 seconds #在0.50秒内完成100000个请求
100 parallel clients #100个并行客户端
3 bytes payload #3字节有效负载
keep alive: 1
host configuration "save": 3600 1 300 100 60 10000
host configuration "appendonly": no
multi-thread: no
Latency by percentile distribution:
0.000% <= 0.111 milliseconds (cumulative count 1)
50.000% <= 0.255 milliseconds (cumulative count 57343) #0.255毫秒完成50%
...
====== SET ====== #set操作
100000 requests completed in 0.47 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 3600 1 300 100 60 10000
host configuration "appendonly": no
multi-thread: no
Latency by percentile distribution:
0.000% <= 0.087 milliseconds (cumulative count 1)
50.000% <= 0.255 milliseconds (cumulative count 59445)
...
====== GET ====== #get操作
100000 requests completed in 0.46 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 3600 1 300 100 60 10000
host configuration "appendonly": no
multi-thread: no
Latency by percentile distribution:
0.000% <= 0.119 milliseconds (cumulative count 1)
50.000% <= 0.247 milliseconds (cumulative count 51945)
...
基础的知识
#打开配置文件
vim /usr/local/bin/myconf/redis.conf
输入英文冒号:进入底线命令模式
输入命令set nu回车显示行号
输入命令/databases回车定位到字符databases(/跟字符,搜索命令)
可以看到默认有16个数据库
qingshuaidebaoluo@qingshuaidebaoluoMac-mini ~ % redis-cli
127.0.0.1:6379> ping
PONG
#查看数据库大小(数据量)
127.0.0.1:6379> dbsize
(integer) 6
127.0.0.1:6379> keys *
1) "passwd"
2) "myhash"
3) "key:__rand_int__"
4) "mylist"
5) "name"
6) "counter:__rand_int__"
#切换数据库
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 2
OK
127.0.0.1:6379[2]> select 15
OK
#16个数据库,0到15,所以切换到16时报错
127.0.0.1:6379[15]> select 16
(error) ERR DB index is out of range
#查看数据库大小
127.0.0.1:6379[15]> dbsize
(integer) 0
127.0.0.1:6379[15]> keys *
(empty array)
127.0.0.1:6379[15]>
Redis 是C 语言写的,官方提供的数据为 100000+ 的QPS(每秒查询率),完全不比同样是使用key-vale的Memecache差!
五大数据类型
Redis 是一个开源( BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件MQ。它支持多种类型的数据结构,如 字符串(strings),散列(hashes),列表 (lists),集合(sets),有序集合 ( sorted sets)与范围查询,bitmaps, hyperloglogs 和 地理空间( geospatial)索引半径查询。Redis 内置了 复制 (replication),LUA脚本 ( Luascripting), LRU驱动事件( LRUeviction),事务(transactions) 和不同级别的磁盘持久化(persistence),并通过 Redis哨兵(Sentinel)和自动分区 ( Cluster)提供高可用性 (high availability )。
Redis Key
键值对&数据库
#启动redis,并指定配置文件:
[root@localhost bin]# redis-server myconf/redis.conf
#打开内置的客户端:
[root@localhost bin]# redis-cli
#查看所有的key
127.0.0.1:6379> keys *
1) "key:__rand_int__"
2) "myhash"
3) "counter:__rand_int__"
4) "mylist"
5) "name"
#清空当前数据库中的所有 key
127.0.0.1:6379> flushdb
OK
#清空所有数据库中的 key
127.0.0.1:6379> flushall
OK
#查看所有的key
127.0.0.1:6379> keys *
(empty array)
#添加一个key/value键值对
127.0.0.1:6379> set name qsdbl
OK
#添加一个key/value键值对
127.0.0.1:6379> set passwd 123
OK
#查看所有的key
127.0.0.1:6379> keys *
1) "passwd"
2) "name"
#移动key为name的键值对到数据库1(默认有16个数据库,0-15,默认进入数据库0)
127.0.0.1:6379> move name 1
(integer) 1
#查看所有的key
127.0.0.1:6379> keys *
1) "passwd"
#切换到数据库1
127.0.0.1:6379> select 1
OK
#查看所有的key
127.0.0.1:6379[1]> keys *
1) "name"
过期时间
#查看是否存在key为name的键值对,1-存在,0-不存在
127.0.0.1:6379[1]> exists name
(integer) 1
#给key为name的键值对设置过期时间(单位秒)
127.0.0.1:6379[1]> expire name 5
(integer) 1
#查看key为name的键值的过期时间(单位秒)
127.0.0.1:6379[1]> ttl name
(integer) 3
#查看所有的key
127.0.0.1:6379[1]> keys *
1) "name"
#查看所有的key(过期时间到后,自动删除)
127.0.0.1:6379[1]> keys *
(empty array)
#添加一个key/value键值对
127.0.0.1:6379[1]> set passwd 123345
OK
#查看过期时间,-1表示没有设置过期时间
127.0.0.1:6379[1]> ttl passwd
(integer) -1
#设置过期时间为30秒
127.0.0.1:6379> expire passwd 30
(integer) 1
127.0.0.1:6379> ttl passwd
(integer) 27
#移除过期时间
127.0.0.1:6379> persist passwd
(integer) 1
#移除成功
127.0.0.1:6379> ttl passwd
(integer) -1
类型&删除
#查看类型
127.0.0.1:6379[1]> type passwd
string
#删除key为passwd的键值对
127.0.0.1:6379[1]> del passwd
(integer) 1
#查看所有的key(空,说明删除成功)
127.0.0.1:6379[1]> keys *
(empty array)
String
追加&长度
#查看redis服务是否已启动
qingshuaidebaoluo@qingshuaidebaoluoMac-mini ~ % ps -ef | grep redis
501 1358 1289 0 9:55下午 ttys000 0:00.00 grep redis
#redis服务还没启动,启动redis(使用myconf文件夹下的配置文件)
qingshuaidebaoluo@qingshuaidebaoluoMac-mini ~ % redis-server /usr/local/bin/myconf/redis.conf
#再次查看redis服务是否已启动
qingshuaidebaoluo@qingshuaidebaoluoMac-mini ~ % ps -ef | grep redis
501 1373 1 0 9:55下午 ?? 0:00.02 redis-server 127.0.0.1:6379
501 1376 1289 0 9:55下午 ttys000 0:00.00 grep redis
#打开内置的客户端:
qingshuaidebaoluo@qingshuaidebaoluoMac-mini ~ % redis-cli
#测试连接
127.0.0.1:6379> ping
PONG
#获取(查看)所有key
127.0.0.1:6379> keys *
1) "mylist"
2) "myhash"
3) "passwd"
4) "key:__rand_int__"
5) "counter:__rand_int__"
6) "name"
#查看key为name的value值
127.0.0.1:6379> get name
"qsdbl"
#往key为name的value后边追加字符“-01”,返回值为value的长度
127.0.0.1:6379> append name "-01"
(integer) 8
#再次查看key为name的value值
127.0.0.1:6379> get name
"qsdbl-01"
#判断是否存在key为name的键值对,0-不存在,1-存在
127.0.0.1:6379> exists name
(integer) 1
#查看key为name的value值的长度(string length ==> strlen)
127.0.0.1:6379> strlen name
(integer) 8
数值增减
#添加一个键值对。key为num,value为0
127.0.0.1:6379> set num 0
OK
#增一
127.0.0.1:6379> incr num
(integer) 1
127.0.0.1:6379> get num
"1"
#减一
127.0.0.1:6379> decr num
(integer) 0
127.0.0.1:6379> get num
"0"
#增5
127.0.0.1:6379> incrby num 5
(integer) 5
127.0.0.1:6379> get num
"5"
#减5
127.0.0.1:6379> decrby num 5
(integer) 0
127.0.0.1:6379> get num
"0"
范围
#查看key为name的键值对的value
127.0.0.1:6379> get name
"qsdbl-01"
#获取key为name的键值对的value值中索引在1到4之间的字符(包括前后索引的字符)
127.0.0.1:6379> getrange name 1 4
"sdbl"
#后一个索引值为-1,则获取到最后一个字符。例如:0 -1,获取所有字符(从0开始到最后)
127.0.0.1:6379> getrange name 0 -1
"qsdbl-01"
#substr命令与getrange命令作用相同
127.0.0.1:6379> substr name 1 4
"sdbl"
127.0.0.1:6379> substr name 0 -1
"qsdbl-01"
#替换指定位置开始的字符串(不是插入,而是替换掉。例如下边的例子,字符zeroOne长度为7,则从索引6开始后边的7个字符替换为zeroOne,没有则新增)
127.0.0.1:6379> setrange name 6 zeroOne
(integer) 13
127.0.0.1:6379> get name
"qsdbl-zeroOne"
127.0.0.1:6379> setrange name 6 0000
(integer) 13
127.0.0.1:6379> get name
"qsdbl-0000One"
新建相关
#查看所有key
127.0.0.1:6379> keys *
1) "mylist"
2) "myhash"
3) "passwd"
4) "num"
5) "key1"
6) "key:__rand_int__"
7) "counter:__rand_int__"
8) "name"
#创建键值对,key为key01,value为hello。同时设置过期时间为10秒
127.0.0.1:6379> setex key01 10 "hello"
OK
127.0.0.1:6379> ttl key01
(integer) 6
127.0.0.1:6379> get key01
"hello"
#10秒后,key为key01的键值对被删掉了
127.0.0.1:6379> ttl key01
(integer) -2
127.0.0.1:6379> get key01
(nil)
#如果不存在,则创建键值对。key为num,value为6666。由前边keys命令执行结果可知,已存在key为num的键值对,所以创建失败,返回0
127.0.0.1:6379> setnx num 6666
(integer) 0
#创建成功,返回1
127.0.0.1:6379> setnx num02 6666
(integer) 1
127.0.0.1:6379> get num
"0"
127.0.0.1:6379> get num02
"6666"
#切换到数据库1(默认16个,0-15)
127.0.0.1:6379> select 1
OK
#同时创建三对键值对
127.0.0.1:6379[1]> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379[1]> keys *
1) "k2"
2) "k1"
3) "k3"
#如果不存在,则创建键值对。原子性操作,要么全部创建,要么全部不创建。
127.0.0.1:6379[1]> msetnx k1 v1 k4 v4
(integer) 0
#因为已经存在k1所以全部都不创建,k4没有被创建
127.0.0.1:6379[1]> keys *
1) "k2"
2) "k1"
3) "k3"
#如果不存在值,则返回nil
127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> get db
"redis"
#如果存在值,则返回原来的值并设置新的值
127.0.0.1:6379> getset db mongodb
"redis"
127.0.0.1:6379> get db
"mongodb"
应用场景
案例
#一般用法:使用set、get命令保存对象
#user:001,作为key,user:后边为用户id。value中保存对象各属性值
127.0.0.1:6379[1]> set user:001 {name:zhansan,age:3}
OK
127.0.0.1:6379[1]> get user:001
"{name:zhansan,age:3}"
#使用mset,同时设置多个键值对。下边的例子,同时保存了用户名、年龄、性别。
#第一对键值对:
#key为user:002:name,用:分隔,前边的为类名,中间的为id,后边的为属性名
#value为zhansan
127.0.0.1:6379[1]> mset user:002:name zhansan user:002:age 18 user:002:sex man
OK
127.0.0.1:6379[1]> mget user:002:name user:002:age user:002:sex
1) "zhansan"
2) "18"
3) "man"
List
基本命令
#切换到数据库2(默认16个,0-15)
127.0.0.1:6379[1]> select 2
OK
#查看当前数据库中所有的键值对(为空)
127.0.0.1:6379[2]> keys *
(empty array)
#创建list,名为list1,往list1中添加元素1
127.0.0.1:6379[2]> Lpush list1 1
(integer) 1
#往list1中添加元素2
127.0.0.1:6379[2]> Lpush list1 2
(integer) 2
#往list1中添加元素3
127.0.0.1:6379[2]> Lpush list1 3
(integer) 3
#根据索引获取list中某段范围内的元素。索引0到-1,表示获取该list中的全部元素
#我们发现取出的元素的顺序是3、2、1,而我们前边往list中添加元素时的顺序是1、2、3,这是因为list左进右出,见前边的解释。
127.0.0.1:6379[2]> Lrange list1 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379[2]> Lrange list1 0 1
1) "3"
2) "2"
#往list的右边推进(添加)元素
127.0.0.1:6379[2]> Rpush list1 4
(integer) 4
#取出全部的元素
#可以发现,取出的元素的顺序为3、2、1、4。元素4,添加到了最右边。
127.0.0.1:6379[2]> Lrange list1 0 -1
1) "3"
2) "2"
3) "1"
4) "4"
#获取最后一个元素(最右边的)
127.0.0.1:6379[2]> lrange list1 3 3
1) "4"
移除
#查看名为list1的list中的所有元素
127.0.0.1:6379[2]> lrange list1 0 -1
1) "3"
2) "2"
3) "1"
4) "4"
#移除左边的第一个元素
127.0.0.1:6379[2]> Lpop list1
"3"
#查看所有元素,可以发现最左边的元素3已被移除
127.0.0.1:6379[2]> lrange list1 0 -1
1) "2"
2) "1"
3) "4"
#移除右边的第一个元素
127.0.0.1:6379[2]> Rpop list1
"4"
#查看所有元素,可以发现最右边的元素4已被移除
127.0.0.1:6379[2]> lrange list1 0 -1
1) "2"
2) "1"
#移除多个元素。从list1的左边开始,移除两个元素
127.0.0.1:6379[2]> Lpop list1 2
1) "2"
2) "1"
#查看所有元素,可以发现仅剩的两个元素已被移除,list1为空
127.0.0.1:6379[2]> lrange list1 0 -1
(empty array)
127.0.0.1:6379[2]> keys *
(empty array)
#新建mylist,同时往其中添加元素one two three four two one(list中可以有重复的元素)
127.0.0.1:6379[2]> Lpush mylist one two three four two one
(integer) 6
#查看所有元素(左进右出)
127.0.0.1:6379[2]> Lrange mylist 0 -1
1) "one"
2) "two"
3) "four"
4) "three"
5) "two"
6) "one"
#从左边开始 移除mylist中的一个 one元素(从左边开始)
127.0.0.1:6379[2]> Lrem mylist 1 one
(integer) 1
#查看mylist中的所有元素,可以发现最左边的one已被移除
127.0.0.1:6379[2]> Lrange mylist 0 -1
1) "two"
2) "four"
3) "three"
4) "two"
5) "one"
#从左边开始 移除mylist中的两个 two元素
127.0.0.1:6379[2]> Lrem mylist 2 two
(integer) 2
#查看所有元素,一左一右的两个two均已被移除
127.0.0.1:6379[2]> Lrange mylist 0 -1
1) "four"
2) "three"
3) "one"
#清空当前数据库
127.0.0.1:6379[2]> flushdb
OK
127.0.0.1:6379[2]> keys *
(empty array)
#新建mylist,同时往其中添加元素0 1 2 3 4
127.0.0.1:6379[2]> Lpush mylist 0 1 2 3 4
(integer) 5
#查看mylist中的所有元素
127.0.0.1:6379[2]> Lrange mylist 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "0"
#截取保留mylist中索引1到2的元素,修剪掉其他元素
127.0.0.1:6379[2]> Ltrim mylist 1 2
OK
#左进右出,索引从左边开始。故mylist[0]=4,mylist[1]=3,mylist[2]=2,...
#截取了元素3、2,修剪了其他元素
127.0.0.1:6379[2]> Lrange mylist 0 -1
1) "3"
2) "2"
#新建列表mylist
127.0.0.1:6379> lpush mylist 1 2 3 4 5
(integer) 5
#查看全部的元素(左进右出)
127.0.0.1:6379> lrange mylist 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
#将mylist中最后一个元素移除,并将其移到mylist02中
127.0.0.1:6379> rpoplpush mylist mylist02
"1"
#查看mylist中全部的元素
127.0.0.1:6379> lrange mylist 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
#查看mylist02中全部的元素
127.0.0.1:6379> lrange mylist02 0 -1
1) "1"
添加
#查看mylist02中全部的元素
127.0.0.1:6379> lrange mylist02 0 -1
1) "1"
#存在名为mylist02的list
127.0.0.1:6379> exists mylist02
(integer) 1
#设置索引为0的元素为one(将one放到mylist02中索引为0的位置)
127.0.0.1:6379> lset mylist02 0 one
OK
#查看mylist02中全部的元素
127.0.0.1:6379> lrange mylist02 0 -1
1) "one"
#不存在名为mylist03的list
127.0.0.1:6379> exists mylist03
(integer) 0
#无法使用lset命令向其中添加元素
127.0.0.1:6379> lset mylist03 0 one
(error) ERR no such key
#创建名为mylist的list,并往其中添加元素0 1 2 3 4
127.0.0.1:6379> Lpush mylist 0 1 2 3 4
(integer) 5
#查看所有元素
127.0.0.1:6379> Lrange mylist 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "0"
#插入元素。往元素“1”的前边(before)插入元素“1.5”
127.0.0.1:6379> Linsert mylist Before 1 1.5
(integer) 6
#查看所有元素(左进右出,索引从左开始。所以mylist[0]=4,...,mylist[4]=0,元素4是在前边,元素0在最后)
127.0.0.1:6379> Lrange mylist 0 -1
1) "4"
2) "3"
3) "2"
4) "1.5"
5) "1"
6) "0"
#插入元素。往元素“1”的后边(after)插入元素“0.5”
127.0.0.1:6379> Linsert mylist After 1 0.5
(integer) 7
#查看所有元素
127.0.0.1:6379> Lrange mylist 0 -1
1) "4"
2) "3"
3) "2"
4) "1.5"
5) "1"
6) "0.5"
7) "0"
索引&长度
#新建list1,同时往其中添加元素1到10
127.0.0.1:6379[2]> lpush list1 1 2 3 4 5 6 7 8 9 10
(integer) 10
#获取索引为5的元素
127.0.0.1:6379[2]> Lindex list1 5
"5"
#别忘了,list是左进右出,索引从左开始。即list[0]=10,list[1]=9,...,list[5]=5,...
127.0.0.1:6379[2]> Lrange list1 0 -1
1) "10"
2) "9"
3) "8"
4) "7"
5) "6"
6) "5"
7) "4"
8) "3"
9) "2"
10) "1"
#获取list的长度(数据量)
127.0.0.1:6379[2]> Llen list1
(integer) 10
小结
Set
基本命令
#往名为myset的set集合中添加元素1314,若不存在集合myset则创建再往其中添加元素
127.0.0.1:6379> sadd myset 1314
(in teger) 1
127.0.0.1:6379> sadd myset 6666
(integer) 1
#查看名为myset的set集合中的所有元素
127.0.0.1:6379> smembers myset
1) "1314"
2) "6666"
#查看名为myset的set集合中 是否存在元素1314。1-存在,0-不存在
127.0.0.1:6379> sIsMember myset 1314
(integer) 1
#查看名为myset的set集合中 是否存在元素520
127.0.0.1:6379> sIsMember myset 520
(integer) 0
#获取set集合中的元素个数
127.0.0.1:6379> scard myset
(integer) 2
移除
#移除集合中的元素1314
127.0.0.1:6379> srem myset 1314
(integer) 1
#查看myset集合中的所有元素
127.0.0.1:6379> smembers myset
1) "6666"
#往myset集合中添加元素520与1314
127.0.0.1:6379> sadd myset 520 1314
(integer) 1
#查看myset集合中的所有元素
127.0.0.1:6379> smembers myset
1) "520"
2) "1314"
3) "6666"
#随机弹出(删除)一个元素
127.0.0.1:6379> spop myset
"1314"
#查看myset集合中的所有元素
127.0.0.1:6379> smembers myset
1) "520"
2) "6666"
#随机弹出(删除)一个元素
127.0.0.1:6379> spop myset
"520"
#清空当前数据库所有的数据
127.0.0.1:6379> flushdb
OK
#创建myset集合并往其中添加元素1 2 3 4
127.0.0.1:6379> sadd myset 1 2 3 4
(integer) 4
#创建myset2集合并往其中添加元素5 6 7 8
127.0.0.1:6379> sadd myset2 5 6 7 8
(integer) 4
#将myset集合中的元素4 移动到myset2集合中
127.0.0.1:6379> smove myset myset2 4
(integer) 1
#查看myset集合中的所有元素
127.0.0.1:6379> smembers myset
1) "1"
2) "2"
3) "3"
#查看myset2集合中的所有元素
127.0.0.1:6379> smembers myset2
1) "4"
2) "5"
3) "6"
4) "7"
5) "8"
获取
#往myset集合中添加元素 520与1314
127.0.0.1:6379> sadd myset 520 1314
(integer) 1
#随机获取集合中的一个元素
127.0.0.1:6379> srandmember myset
"520"
127.0.0.1:6379> srandmember myset
"6666"
#随机获取集合中的两个元素
127.0.0.1:6379> srandmember myset 2
1) "1314"
2) "520"
127.0.0.1:6379> srandmember myset 2
1) "1314"
2) "520"
127.0.0.1:6379> srandmember myset 2
1) "1314"
2) "6666"
#清空当前数据库所有的数据
127.0.0.1:6379> flushdb
OK
#创建集合myset1与myset2
127.0.0.1:6379> sadd myset1 1 2 3 4
(integer) 4
127.0.0.1:6379> sadd myset2 3 4 5 6
(integer) 4
#求两集合的 差集(前者为基础)
127.0.0.1:6379> sDiff myset1 myset2
1) "1"
2) "2"
127.0.0.1:6379> sDiff myset2 myset1
1) "5"
2) "6"
#求两集合的 交集(应用场景:查看共同好友)
127.0.0.1:6379> sInter myset1 myset2
1) "3"
2) "4"
#求两集合的 并集
127.0.0.1:6379> sUnion myset1 myset2
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
应用场景
共同关注,共同爱好,二度好友,推荐好友!(六度分割理论)
Hash(哈希)
创建
#清空所有数据库
127.0.0.1:6379> flushall
OK
#查看当前数据库的所有数据
127.0.0.1:6379> keys *
(empty array)
#创建一个Hash,名为myhash1。并往其中添加键值对,key为key1,value为value1
127.0.0.1:6379> hset myhash1 key1 value1
(integer) 1
#创建一个Hash,名为myhash2。并往其中添加两个键值对
127.0.0.1:6379> hset myhash2 key1 value1 key2 value2
(integer) 2
#创建一个Hash,并往其中添加多个键值对,该命令已废弃,建议使用hset。
127.0.0.1:6379> hmset myhash3 key1 value1 key2 value2 key3 value3
OK
127.0.0.1:6379> hgetall myhash1
1) "key1"
2) "value1"
#键值对不存在则新建(返回1,创建成功)
127.0.0.1:6379> hsetnx myhash1 key3 value3
(integer) 1
#键值对不存在则新建(返回0,创建失败)
127.0.0.1:6379> hsetnx myhash1 key3 value3
(integer) 0
127.0.0.1:6379> hgetall myhash1
1) "key1"
2) "value1"
3) "key3"
4) "value3"
获取
#获取名为myhash1的Hash中的,field为key1的value值
127.0.0.1:6379> hget myhash1 key1
"value1"
#获取名为myhash2的Hash中的,field为key1、key2的value值
127.0.0.1:6379> hmget myhash2 key1 key2
1) "value1"
2) "value2"
#获取名为myhash2的Hash中的所有键值对,field与value
127.0.0.1:6379> hgetall myhash2
1) "key1"
2) "value1"
3) "key2"
4) "value2"
127.0.0.1:6379> hset myhash f1 v1 f2 v2 f3 v3
(integer) 3
127.0.0.1:6379> hgetall myhash
1) "f1"
2) "v1"
3) "f2"
4) "v2"
5) "f3"
6) "v3"
#获取名为myhash的Hash的长度(多少对键值对)
127.0.0.1:6379> hlen myhash
(integer) 3
#获取所有的field(即键值对的键 - key)
127.0.0.1:6379> hkeys myhash
1) "f1"
2) "f2"
3) "f3"
#获取所有的value(即键值对的值 - value)
127.0.0.1:6379> hvals myhash
1) "v1"
2) "v2"
删除
#删除 名为myhash2的Hash中的 field为key1的键值对
127.0.0.1:6379> hdel myhash2 key1
(integer) 1
#获取名为myhash2的Hash中的所有键值对
127.0.0.1:6379> hgetall myhash2
1) "key2"
2) "value2"
数值增减
127.0.0.1:6379> hset myhash f5 5
(integer) 1
#增加指定的数量
127.0.0.1:6379> hincrby myhash f5 1
(integer) 6
127.0.0.1:6379> hget myhash f5
"6"
#减去指定的数量
127.0.0.1:6379> hincrby myhash f5 -1
(integer) 5
案例
#使用hset创建一个名为user:1的Hash(以"类名:id"的方式命名)保存对象信息,将对象的属性以键值对的形式保存在Hash中
127.0.0.1:6379> hset user:1 name lily age 18 email 1135637451@qq.com
(integer) 3
127.0.0.1:6379> hset user:2 name pete age 16 email [email protected]
(integer) 3
#获取名为user:1的Hash的所有键值对
127.0.0.1:6379> hgetall user:1
1) "name"
2) "lily"
3) "age"
4) "18"
5) "email"
6) "[email protected]"
#获取名为user:1的Hash中 键为name的值
127.0.0.1:6379> hget user:1 name
"lily"
127.0.0.1:6379> hgetall user:2
1) "name"
2) "pete"
3) "age"
4) "16"
5) "email"
6) "[email protected]"
#对比 String中的案例:
#使用mset,同时设置多个键值对。下边的例子,同时保存了用户名、年龄、性别。
#第一对键值对:
#key为user:002:name,用:分隔,前边的为类名,中间的为id,后边的为属性名
#value为zhansan
127.0.0.1:6379[1]> mset user:002:name zhansan user:002:age 18 user:002:sex man
OK
127.0.0.1:6379[1]> mget user:002:name user:002:age user:002:sex
1) "zhansan"
2) "18"
3) "man"
Zset
set k1 v1
zset k1 score1 v1
//score - 成绩、分值
创建
#创建名为myset的zset,添加一个值,key为myset,value为one,score为1
127.0.0.1:6379> zadd myzset 1 one
(integer) 1
#添加多个值
127.0.0.1:6379> zadd myzset 2 two 3 three
(integer) 2
获取
获取值
#获取全部值
127.0.0.1:6379> zrange myzset 0 -1
1) "one"
2) "two"
3) "three"
获取值&排序
#根据score的值,从小到大排序,区间是无穷小到无穷大即所有数据
> zrangebyscore myset -inf +inf
1) "one"
2) "two"
3) "tree"
> zrangebyscore myset 2 7
1) "two"
2) "tree"
#错误示例,从小到大排序,区间写成了大到小
> zrangebyscore myset +inf -inf
(empty array)
#根据score的值,从大到小排序
> zrevrangebyscore myset 7 2
1) "tree"
2) "two"
#应用场景:通过升序排序(小到大),获取名为myset的zset中小于2的数据
127.0.0.1:6379> zrangebyscore myset -inf 2
1) "one"
2) "two"
#并带上对应的score
127.0.0.1:6379> zrangebyscore myset -inf 2 withscores
1) "one"
2) "1"
3) "two"
4) "2"
获取数量
#清空数据库
> flushdb
OK
#查看名为myset的zset中的元素个数。myset不存在了,也不会报错而是返回0
> zcard myset
(integer) 0
#查看所有的key(myset已被清除)
> keys *
(empty array)
#新增一个zset,名为myset,添加6个元素(值),score和元素相同
> zadd myset 1 1 3 3 7 7 9 9 13 13 77 77
(integer) 6
#查看myset中的所有元素
> zrange myset 0 -1
1) "1"
2) "3"
3) "7"
4) "9"
5) "13"
6) "77"
#查看指定区间的元素数量
> zcount myset 3 9
(integer) 3
#查看指定区间的元素数量
> zcount myset 3 79
(integer) 5
#获取集合中的元素个数
> zcard myset
(integer) 6
#对集合myset进行升序排序,获取score在3到79之间的元素
> zrangebyscore myset 3 79
1) "3"
2) "7"
3) "9"
4) "13"
5) "77"
#包括区间边界3和79
删除
#移除指定的元素(zset,有序不重复集合)
> zrem myset 77 9
(integer) 2
#查看所有元素,77与9已被删除
> zrange myset 0 -1
1) "1"
2) "3"
3) "7"
4) "13"
应用场景
三种特殊数据类型
geospatial
创建
#geoadd
#规则:两级无法直接添加,一般是下载城市数据,直接通过java程序一次性导入
#参数: key 值(经度、纬度、名称)
#经纬度查询:https://jingweidu.bmcx.com/
#longitude 经度、 latitude 纬度、 member 成员
#创建一个名为 china:city 的geospatial,并添加一个值(经度、纬度、名称)
> geoadd china:city 116.40 39.90 beijin
(integer) 1
#添加第二个值
> geoadd china:city 121.47 31.23 shanghai
(integer) 1
#一次添加多个值
> geoadd china:city 106.50 29.53 chongqin 114.05 22.52 shengzheng 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 4
获取
#:输入地名(前边设置的value,值),返回
#获取“名称”(前边添加值时,一个值包括“经度、纬度、名称”)为“beijin”的值的经纬度
> geopos china:city beijin
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
#获取xian的经纬度
> geopos china:city beijin xian
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
# m 表示单位为米。
# km 表示单位为千米。
# mi 表示单位为英里。
# ft 表示单位为英尺。
#查看 beijin 与 beijin 之间的距离,单位为 km
> geodist china:city beijin shanghai km
"1067.3788"
#获取 以给定的经纬度(110 30)为中心,1000km内的元素
> georadius china:city 110 30 1000 km
1) "chongqin"
2) "xian"
3) "shengzheng"
4) "hangzhou"
#限定数量
> georadius china:city 110 30 1000 km count 2
1) "chongqin"
2) "xian"
#返回的结果中 带上距离(距离指定的经纬度)
> georadius china:city 110 30 500 km withdist
1) 1) "chongqin"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
#带上经纬度
> georadius china:city 110 30 500 km withcoord
1) 1) "chongqin"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
#带上经纬度 和 距离
> georadius china:city 110 30 500 km withcoord withdist
1) 1) "chongqin"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
#以 beijin 为中心,获取半径为1000km的范围的其它元素
> georadiusbymember china:city beijin 1000 km
1) "beijin"
2) "xian"
#以 shanghai 为中心,获取半径为500km的范围的其它元素
> georadiusbymember china:city shanghai 500 km
1) "hangzhou"
2) "shanghai"
#获取 beijin 与 chongqin 的位置信息的哈希表示
> geohash china:city beijin chongqin
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
#获取 beijin 与 chongqin 的位置信息(经纬度)
> geopos china:city beijin chongqin
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
删除
#使用type命令查看其属于5大数据类型中的哪一种,即可以使用对应的命令来操作
> type china:city
zset
#查看所有元素
> zrange china:city 0 -1
1) "chongqin"
2) "xian"
3) "shengzheng"
4) "hangzhou"
5) "shanghai"
6) "beijin"
#移除指定元素
> zrem china:city beijin
(integer) 1
> zrange china:city 0 -1
1) "chongqin"
2) "xian"
3) "shengzheng"
4) "hangzhou"
5) "shanghai"
hyperloglog
#创建第一组元素 mykey
> PFadd mykey a b c d e f g
(integer) 1
#统计第一组元素mykey中的 基数 数量
> PFcount mykey
(integer) 7
#创建第二组元素 mykey2
> PFadd mykey2 d e f a
(integer) 1
> PFcount mykey2
(integer) 4
#创建第三组元素 mykey3,通过合并mykey与mykey2
> PFmerge mykey3 mykey mykey2
OK
> PFcount mykey3
(integer) 7
#创建第四组元素 mykey4,与mykey元素基本相同,不同点在于元素a重复了几个
> PFadd mykey4 a a a b c d e f g
(integer) 1
#查看mykey4中的基数数量,与mykey是一致的(去重)
> PFcount mykey4
(integer) 7
bitmaps
#案例:使用bitmaps来记录周一到周日的打卡情况。
#1代表已打卡,0代表未打卡
#- 周一:1,周二:1。。。
#创建一个名为daka的bitmaps
#连续往其中添加值(模拟每日打卡)
> setbit daka 0 1
(integer) 0
> setbit daka 1 1
(integer) 0
> setbit daka 2 0
(integer) 0
> setbit daka 3 1
(integer) 0
> setbit daka 4 0
(integer) 0
> setbit daka 5 0
(integer) 0
> setbit daka 6 1
(integer) 0
#查看某一天是否打卡
#查看daka中offset为0(代表星期一)的值,查看星期一的打卡情况(0 - 未打卡,1-已打卡)
> getbit daka 0
(integer) 1
#查看星期三的打卡情况
> getbit daka 2
(integer) 0
#统计操作,统计打卡的天数
> bitcount daka
(integer) 4
事务
---- 队列 set set set 执行 ----
执行事务
#开启事务
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 k1
QUEUED
#执行事务(开始执行事务中的命令)
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v1"
放弃事务
#开启事务
127.0.0.1:6379> multi
OK
#命令入队
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)> discard
OK
#由于事务未执行,所以k5没有创建成功,获取不到对应的值(v5)
127.0.0.1:6379> get k5
(nil)
编译型异常
#清空数据库
127.0.0.1:6379> flushdb
OK
#开启事务
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
#使用了错误的redis命令,getmyset 应该为 get
127.0.0.1:6379(TX)> getmyset k2
(error) ERR unknown command `getmyset`, with args beginning with: `k2`,
127.0.0.1:6379(TX)> set k3 v3
QUEUED
#执行事务
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
#尝试获取k1、k3的值。均没有获取到相关值,说明事务中所有的命令都没有被执行。
127.0.0.1:6379> get k1
(nil)
127.0.0.1:6379> get k3
(nil)
运行时异常
#清空数据库
127.0.0.1:6379> flushdb
OK
#设置一个键值对,k1/v1,值“v1”是字符串
127.0.0.1:6379> set k1 v1
OK
#开启事务
127.0.0.1:6379> multi
OK
#命令入队
127.0.0.1:6379(TX)> set k2 v2
QUEUED
#对字符串“v1”执行命令“incr”,数字值增一。显然会报错
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
#执行事务
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "v2"
5) "v3"
#获取k3的值,可以正常获取到“v3”。说明执行事务的过程中,其它命令是不受命令“incr k1”的影响,可以正常执行
#redis事务中,不保证原子性
127.0.0.1:6379> get k3
"v3"
#添加两个key/value,用于记录 总金额 和 支出金额
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
#监听money
127.0.0.1:6379> watch 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
#事务操作过程中监听money,期间没有发生变动,事务能正常执行结束
#事务结束后,结束监听
#模拟进程1:
#查看 money 与 out 的值
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> get out
"20"
#监听money(只有在事务结束后,才会结束监听)
#此时money的值为80
127.0.0.1:6379> watch money
OK
#开启事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 200
QUEUED
127.0.0.1:6379(TX)> incrby out 200
QUEUED
#模拟进程2:
#进程1中监听money,和开启了事务(还未执行)。进程2中修改了所监听的money
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> get out
"20"
#money被修改为了88
127.0.0.1:6379> incrby money 8
(integer) 88
#模拟进程1:
#执行事务
#因为在 监听money 到 money解除监听(事务结束) 之间,被监听的money值发生了改变(由80变成了88)
#所以事务中的命令均不会被执行,并退出了事务(同时监听也解除)
127.0.0.1:6379(TX)> exec
(nil)
127.0.0.1:6379>
#也可以使用命令 unwatch 解除监听
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> unwatch
OK
Jedis
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.60version>
dependency>
案例
@SpringBootTest
class MyTest {
@Test
public void test() throws SQLException {
//1、连接数据库
Jedis jedis = new Jedis("192.168.23.131", 6379);
//2、操作命令
//jedis 命令 对应 方法名
System.out.println("ping测试:"+jedis.ping());//ping 测试
jedis.set("name","小黑");//创建key
jedis.set("age","16");
System.out.println("当前数据库所有key的数量:"+jedis.dbSize());//查看 当前数据库 所有key的数量
Set<String> keys = jedis.keys("*");
Iterator<String> iterator = keys.iterator();
System.out.print("所有的key = ");
String str = "[";
while(iterator.hasNext()){
String next = iterator.next();
str += next+"="+jedis.get(next)+",";
}
if(str.length() > 1){
System.out.println(str.substring(0,str.length()-1)+"]");
}else{
System.out.println(str+"]");
}
// System.out.println(jedis.flushDB());//删除 当前数据库 中的所有key
// System.out.println(jedis.flushAll());//删除 所有数据库 中的所有key
//3、断开连接
if(jedis!=null){
jedis.close();//释放连接
}
// jedis.shutdown();//关闭redis服务器
System.out.println("ping测试:"+jedis.ping());//ping 测试。调用close()后,还是能使用jedis对象?
}
}
ping测试:PONG
当前数据库所有key的数量:2
所有的key = [name=小黑,age=16]
ping测试:PONG
事务操作
@SpringBootTest
class MyTest {
@Test
public void test() throws SQLException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("sex","男");
jsonObject.put("addr","北京朝阳");
Jedis jedis = new Jedis("192.168.23.131", 6379);
//开启事务
Transaction multi = jedis.multi();
String userInfo = jsonObject.toJSONString();
try {
multi.set("user1",userInfo);
multi.set("user2",userInfo);
int i = 1/0;//代码抛出异常,事务执行失败
multi.exec();//执行事务
}catch(Exception e){
multi.discard();//放弃事务
e.printStackTrace();
}finally{
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close();//关闭连接
}
}
}
//运行结果:
java.lang.ArithmeticException: / by zero
at com.qsdbl.nazox_demo.MyTest.test(MyTest.java:39)
...
null
null
//将错误代码”int i = 1/0;“移除后,运行结果为:
{"sex":"男","addr":"北京朝阳"}
{"sex":"男","addr":"北京朝阳"}
SpringBoot整合
导入依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
配置连接
//maven的autoconfigure包下的spring.factories中找到redis的auto配置类,使用注解”@EnableConfigurationProperties“绑定的文件就是redis的配置文件,通过配置文件我们可以了解到redis哪些参数可配置
//RedisAutoConfiguration
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}//我们可以自己定义一个redisTemplate来替换这个默认的
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的RedisTemplate没有过多的设置,redis对象都是需要序列化的
//两个泛型都是Object,Object的类型,需要强制转换成
#application.properties文件
#SpringBoot 所有的配置类都有一个自动配置类 RedisAutoConfiguration
#自动配置类都会绑定一个 properties 配置文件 RedisProperties
#maven的autoconfigure包下的spring.factories中找到redis的auto配置类,使用注解”@EnableConfigurationProperties“绑定的文件就是redis的配置文件,通过配置文件我们可以了解到redis哪些参数可配置
#配置redis
spring.redis.host=192.168.23.131
spring.redis.port=6379
测试
@SpringBootTest
class MyTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void test() throws SQLException {
//redisTemplate 操作不同的数据类型
//ops,operations,操作
//opsForValue 操作字符串
//opsForList 操作List
//opsForSet
//opsForHash
//。。。
//redisTemplate.opsForValue().set("name","小黑");
//除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务、基本的CRUD
//获取redis的连接对象
//RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//connection.flushDb();//清空当前数据库的所有key
//示例:
redisTemplate.opsForValue().set("age","18");
redisTemplate.opsForValue().set("addr","北京朝阳zhaoyan");
System.out.println(redisTemplate.opsForValue().get("age"));
System.out.println(redisTemplate.opsForValue().get("addr"));
}
}
//在服务器中,所有命令行查看刚刚创建的key/value
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04addr"
2) "\xac\xed\x00\x05t\x00\x03age"
127.0.0.1:6379> get addr
(nil)
127.0.0.1:6379> get age
(nil)
问题
自定义配置类
package com.qsdbl.nazox_demo.configuration;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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;
/**
* @description 配置 redis
* @author 轻率的保罗
* @since 2022/4/12
*/
@Configuration
public class RedisConfig {
//编写配置类 redisTemplate,参考RedisAutoConfiguration的内部类RedisTemplate
//bean的标识为方法名 myRedisTemplate
@Bean
public RedisTemplate<String, Object> myRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
//为了开发方便,直接使用
使用&测试
@SpringBootTest
class MyTest {
@Autowired
@Qualifier("myRedisTemplate")//指定使用我们自定义的配置类
private RedisTemplate redisTemplate;
@Test
public void test() throws SQLException {
redisTemplate.opsForValue().set("age","18");
redisTemplate.opsForValue().set("addr","北京朝阳zhaoyan");
System.out.println(redisTemplate.opsForValue().get("age"));
System.out.println(redisTemplate.opsForValue().get("addr"));
StoreMat storeMat = new StoreMat();
storeMat.setMat_name("钢笔");
storeMat.setMat_price("15");
redisTemplate.opsForValue().set("mat",storeMat);
System.out.println(redisTemplate.opsForValue().get("mat"));
}
}
//运行结果:
18
北京朝阳zhaoyan
StoreMat(mat_code=null, mat_name=钢笔, mat_size=null, mat_unit=null, mat_price=15, mat_desc=null, mat_id=null, type_id=null, typeCode=null, typeName=null, add_userid=null, add_date=null, modify_userid=null, modify_date=null, tenant_id=null, in_num=null, in_money=null, out_num=null, out_money=null, local_code=null)
//redis-cli中查看的key:
127.0.0.1:6379> keys *
1) "age"
2) "mat"
3) "addr"
定义工具类
package com.qsdbl.nazox_demo.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @author: 轻率的保罗
* @since: 2022-04-20 20:13
* @Description: redis工具类
*/
@Component
public class RedisUtil {
@Autowired
@Qualifier("myRedisTemplate")
private RedisTemplate<String, Object> redisTemplate;
/**
* 指定缓存失效时间
* @param key 键
* @param time 过期时间(秒)
* @return true - 成功;false - 失败
*/
public boolean expire(String key, long time){
try {
if(time>0){
redisTemplate.expire(key,time, TimeUnit.SECONDS);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 根据key获取过期时间
* @param key 键
* @return 过期时间
*/
public long getExpire(String key){
return redisTemplate.getExpire(key,TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key
* @return true - 成功;false - 失败
*/
public boolean hasKey(String key){
try {
return redisTemplate.hasKey(key);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 删除key
* @param key key名,不定参数(没有或一个或多个)
*/
public void del(String... key){
if(key != null && key.length > 0){
if(key.length == 1){
redisTemplate.delete(key[0]);
}else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key){
return key == null ? null :redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true - 成功;false - 失败
*/
public boolean set(String key, Object value){
try {
redisTemplate.opsForValue().set(key,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入,并设置过期时间
* @param key 键
* @param value 值
* @param time 过期时间(秒),time要大于0,如果小于等于0将设置无限期
* @return true - 成功;false - 失败
*/
public boolean set(String key, Object value, long time){
try {
if(time > 0){
redisTemplate.opsForValue().set(key,value,time,TimeUnit.SECONDS);
}else {
set(key, value);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 增加指定的数量
* @param key 键
* @param num 数量(要增加几,大于0)
* @return 增加后的值
*/
public long incrby(String key, long num){
if(num < 0){
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key,num);
}
/**
* 减少指定的数量
* @param key 键
* @param num 数量(要减少几,大于0)
* @return 减少后的值
*/
public long decrby(String key, long num){
if(num < 0){
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().decrement(key,num);
}
//hash适合用来保存对象:
/**
* 获取Hash中的某个键值对
* @param key 键(hash的名),不能为null
* @param item 项(该hash中的哪一项,键名),不能为null
* @return 值
*/
public Object hget(String key, String item){
return redisTemplate.opsForHash().get(key,item);
}
/**
* 获取Hash中的所有键值
* @param key 键(hash的名),不能为null
* @return 所有键值
*/
public Map<Object, Object> hmget(String key){
return redisTemplate.opsForHash().entries(key);
}
/**
* 创建一个Hash,并往其中添加多个键值对
* @param key 键(hash的名),不能为null
* @param map 多个键值对
* @return true - 成功;false - 失败
*/
public boolean hmset(String key, Map<String, Object> map){
try {
redisTemplate.opsForHash().putAll(key,map);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 创建一个Hash,并往其中添加多个键值对,并设置过期时间
* @param key 键(hash的名),不能为null
* @param map 多个键值对
* @param time 过期时间
* @return true - 成功;false - 失败
*/
public boolean hmset(String key, Map<String, Object> map, long time){
try {
redisTemplate.opsForHash().putAll(key,map);
if(time>0){
expire(key, time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 向一个hash表中放入数据,如果不存在则创建
* @param key 键(hash的名)
* @param item 项(hash中一个键值对的键)
* @param value 值(hash中一个键值对的值)
* @return true - 成功;false - 失败
*/
public boolean hset(String key,String item, Object value){
try {
redisTemplate.opsForHash().put(key,item,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 向一个hash表中放入数据,如果不存在则创建
* @param key 键(hash的名)
* @param item 项(hash中一个键值对的键)
* @param value 值(hash中一个键值对的值)
* @param time 过期时间(秒,当前所设置的键值对),若已存在则会替换原来的过期时间
* @return true - 成功;false - 失败
*/
public boolean hset(String key,String item, Object value, long time){
try {
redisTemplate.opsForHash().put(key,item,value);
if(time > 0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 删除hash中的值
* @param key 键(hash的名)
* @param item 项(可以多个,不能为null)
*/
public void hdel(String key, Object... item){
redisTemplate.opsForHash().delete(key,item);
}
/**
* 判断hash中是否有该项(键值对)
* @param key 键
* @param item 项
* @return true - 有;false - 没有
*/
public boolean hHasKey(String key, String item){
return redisTemplate.opsForHash().hasKey(key,item);
}
/**
* hash中,某键值对的值 增加 指定的数量,不存在就会创建
* @param key 键(hash的名)
* @param item 项(hash中一个键值对的键)
* @param num 数量(大于0)
* @return 增加后的值
*/
public double hincrby(String key, String item, double num){
return redisTemplate.opsForHash().increment(key,item,num);
}
/**
* hash中,某键值对的值 减少 指定的数量,不存在就会创建
* @param key 键(hash的名)
* @param item 项(hash中一个键值对的键)
* @param num 数量(大于0)
* @return 减少后的值
*/
public double hdecrby(String key, String item, double num){
return redisTemplate.opsForHash().increment(key,item,-num);
}
// set集合,无序不重复
/**
* 根据key获取set中的所有值
* @param key 键
* @return
*/
public Set<Object> sGet(String key){
try {
return redisTemplate.opsForSet().members(key);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
* @param key 键(set集合的名)
* @param value 值(set集合的元素)
* @return true - 存在;false - 不存在
*/
public boolean sHasKey(String key, Object value){
try {
return redisTemplate.opsForSet().isMember(key,value);
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set中
* @param key 键(set集合的名)
* @param values 值(set集合的元素。不定参数)
* @return 成功的个数
*/
public long sSet(String key,Object... values){
try {
return redisTemplate.opsForSet().add(key,values);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
* 将数据放入set中,并设置过期时间
* @param key 键(set集合的名)
* @param time 过期时间
* @param values 值(set集合的元素。不定参数)
* @return 成功的个数
*/
public long sSet(String key,long time,Object... values){
try {
Long count = redisTemplate.opsForSet().add(key,values);
if (time>0){
expire(key,time);
}
return count;
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
* 获取set的长度
* @param key 键(set集合的名)
* @return set的长度
*/
public long sGetSetSize(String key){
try {
return redisTemplate.opsForSet().size(key);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
* 移除set中值为value的元素
* @param key 键(set集合的名)
* @param values 值(set中的元素)
* @return 移除的个数
*/
public long setRemove(String key, Object... values){
try {
return redisTemplate.opsForSet().remove(key,values);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
// list集合,有序可重复。一般当作栈、队列、阻塞队列使用
/**
* 获取list的长度
* @param key 键(list集合的名)
* @return 长度
*/
public long lGetListSize(String key){
try {
return redisTemplate.opsForList().size(key);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键(list集合的名)
* @param index 索引。index >= 0时,0 - 表头,1 - 第二个元素,依次类推。index < 0时,-1 - 表尾,-2 - 倒数第二个元素,依次类推。
* @return 值
*/
public Object lGetIndex(String key, long index){
try {
return redisTemplate.opsForList().index(key,index);
}catch (Exception e){
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
* @param key 键(list集合的名)
* @param value 值
* @return true - 成功;false - 失败
*/
public boolean lSet(String key, Object value){
try {
redisTemplate.opsForList().rightPush(key,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存,并设置过期时间
* @param key 键(list集合的名)
* @param value 值
* @param time 过期时间
* @return true - 成功;false - 失败
*/
public boolean lSet(String key, Object value, long time){
try {
redisTemplate.opsForList().rightPush(key,value);
if(time > 0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键(list集合的名)
* @param value 值(集合)
* @return true - 成功;false - 失败
*/
public boolean lSet(String key, List<Object> value){
try {
redisTemplate.opsForList().rightPush(key,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存,并设置过期时间
* @param key 键(list集合的名)
* @param value 值(集合)
* @param time 过期时间
* @return true - 成功;false - 失败
*/
public boolean lSet(String key, List<Object> value, long time){
try {
redisTemplate.opsForList().rightPush(key,value);
if(time > 0){
expire(key,time);
}
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某个元素
* @param key 键(list集合的名)
* @param index 索引
* @param value 值(替换掉旧元素)
* @return true - 成功;false - 失败
*/
public boolean lUpdateIndex(String key, long index, Object value){
try {
redisTemplate.opsForList().set(key,index,value);
return true;
}catch (Exception e){
e.printStackTrace();
return false;
}
}
/**
* 移除N个 值为value的元素
* @param key 键(list集合的名)
* @param count 移除的数量
* @param value 值
* @return 实际移除的数量
*/
public long lRemove(String key, long count, Object value){
try {
return redisTemplate.opsForList().remove(key,count,value);
}catch (Exception e){
e.printStackTrace();
return 0;
}
}
}
使用&测试
@SpringBootTest
class MyTest {
@Autowired
private RedisUtil rUtil;
@Test
public void test() throws SQLException {
rUtil.set("slogn","永远相信美好的事情即将发生!");
System.out.println(rUtil.get("slogn"));
}
}
//运行结果:
永远相信美好的事情即将发生!
Redis.conf详解
# 使用homebrew安装的redis,配置文件为/opt/homebrew/etc/redis.conf。
# 自定义配置文件myredis.conf
cd /opt/homebrew/etc
sudo cp redis.conf myredis.conf
# 编辑配置文件
sudo vim myredis.conf
# 使用自定义的配置文件启动redis服务
redis-server /opt/homebrew/etc/myredis.conf
# 打开redis客户端,连接redis服务器
redis-cli
单位
# 配置文件unit单位,对大小写不敏感
# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.
包含
# include /path/to/local.conf
# include /path/to/other.conf
网络
# 绑定的ip(默认只允许本机连接)
bind 127.0.0.1 ::1
#端口设置
port 6379
通用
# 开启保护模式
protected-mode yes
# 守护线程方式运行
daemonize yes
# 如果以后台的方式(守护线程)运行,就需要指定一个pid文件
pidfile /var/run/redis_6379.pid
# 日志级别:默认notice,适度冗长,生产环境使用
# 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
# 日志文件路径:
# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile ""
#数据库数量
# Set the number of databases. The default database is DB 0, you can sele ct
# a different one on a per-connection basis using SELECT
快照
# Save the DB to disk.
#
# save
安全
# 启动redis客户端,连接redis服务器
qsdbl@macbook etc % redis-cli
127.0.0.1:6379> ping
PONG
# 查看密码
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
# 设置密码
127.0.0.1:6379> config set requirepass "123456"
OK
# 注意:config set requirepass "",即可去掉密码验证
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> exit
# 重新登录,需要完成验证才能访问redis
qsdbl@macbook etc % redis-cli
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
# 输入密码,完成验证
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379>
# 注意:重启失效
限制
# 最大连接数量
maxclients 10000
# redis配置最大的内存容量
maxmemory <bytes>
# 内存达到上限后的处理策略
maxmemory-policy noeviction
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
aof
# 默认不开启,默认使用rdb方式持久化。
appendonly no
# 持久化的文件名
appendfilename "appendonly.aof"
# 每次修改都会同步,消耗性能
# appendfsync always
# 每秒执行一次sync,可能会丢失这一秒的数据
appendfsync everysec
# 不执行同步,这时候操作系统自己同步数据,速度最快
# appendfsync no
Redis持久化
RDB操作
# 快照文件名
dbfilename dump.rdb
触发机制
如何恢复?
# 通过下边的命令查看 或 查看配置文件(见上边conf详解中的快照)
127.0.0.1:6379> config get dir
1) "dir"
2) "/opt/homebrew/var/db/redis"
小结
AOF操作
配置
#默认不开启,需要手动配置。只需更改为yes即可(重启生效。默认是RDB-AOF混合持久化模式)
appendonly no
#aof日志文件名
appendfilename "appendonly.aof"
#通过命令config get dir查看日志文件保存位置。homebrew安装的redis,保存该文件的地方应该是:/opt/homebrew/var/db/redis
#默认每秒记录一次日志文件
# appendfsync always
appendfsync everysec
# appendfsync no
#若日志文件受损,redis服务器启动时会报错并提示使用redis-check-aof命令来修复.aof文件
#把有语法错误的操作记录删掉,尽可能保留数据
redis-check-aof --fix appendonly.aof文件(注意路径)
#重写配置
#如果aof文件大于(配置文件中的)64M,就会fork一个新的进程来重写日志文件。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
#RDB-AOF 混合持久化模式,默认开启
#即AOF和RDB同时开启,先加载RDB再加载AOF
aof-use-rdb-preamble yes
小结
扩展
Redis发布订阅
命令
命令
描述
PSUBSCRIBE pattern [pattern …]
订阅一个或多个符合给定模式的频道
PUBSUB subcommand [argument [argument …]]
查看订阅与发布系统状态
PUBLISH channel message
将信息发送到指定的频道
PUNSUBSCRIBE [pattern [pattern …]]
退订所有给定模式的频道
SUBSCRIBE channel [channel …]
订阅给定的一个或多个频道的信息
UNSUBSCRIBE [channel [channel …]]
退订给定的频道
测试
qsdbl@macbook ~ % redis-cli
# 订阅一个频道 qsdbl
127.0.0.1:6379> SUBSCRIBE qsdbl
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "qsdbl"
3) (integer) 1
# 等待读取到推送的消息
1) "message" #消息
2) "qsdbl" #频道
3) "hello" # 发送的内容
1) "message"
2) "qsdbl"
3) "hello world"
qsdbl@macbook ~ % redis-cli
# 发布者发布消息到频道 qsdbl
127.0.0.1:6379> PUBLISH qsdbl hello
(integer) 1
127.0.0.1:6379> publish qsdbl "hello world"
(integer) 1
原理
Redis 通过 PUBLISH、 SUBSCRIBE 和 PSUBSCRIBE 等命令实现发布和订阅功能。
通过 SUBSCRIBE 命令订阅某频道后,redis-server 里维护了一个字典,字典的键就是一个个 channel,而字典的值则是一个链表,链表中保存了所有订阅这个 channel 的客户端。SUBSCRIBE 命令的关键,就是将客户端添加到给定 channel 的订阅链表中。
通过 PUBLISH 命令向订阅者发送消息,redis-server 会使用给定的频道作为键,在它所维护的 channel 字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。使用场景
Redis主从复制
概念
作用
配置
# 查看当前库的信息(replication - 复制)
127.0.0.1:6379> info replication
# Replication
role:master #角色 master - 主机
connected_slaves:0 #没有从机
master_failover_state:no-failover
# 命令:
#(在从机中进行如下配置)指认主机
> slaveof 地址 端口号
# 配置文件:
# replicaof
案例
从机
-- 配置文件为 myredis02.conf ,运行在6380端口
-- 使用以下命令启动:redis-server /opt/homebrew/etc/myredis02.conf
qsdbl@macbook homebrew % redis-cli -p 6380
127.0.0.1:6380> ping
PONG
# 查看本机信息
127.0.0.1:6380> info replication
# Replication
role:master #角色默认为“主机”
connected_slaves:0
master_failover_state:no-failover
master_replid:8a7d6685effb30244d55aed169f767689df7f61a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
# 指认主机(将本机配置为从机)
127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
# 查看本机信息
127.0.0.1:6380> info replication
# Replication
role:slave #角色为“从机”,说明配置成功
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:4
master_sync_in_progress:0
slave_read_repl_offset:14
slave_repl_offset:14
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:0c8a6ae83e787193fbbc161150f92a2d47f126a3
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
主机
-- 配置文件为 myredis.conf ,运行在6379端口
-- 使用以下命令启动:redis-server /opt/homebrew/etc/myredis.conf
qsdbl@macbook homebrew % redis-cli -p 6379
127.0.0.1:6379> ping
PONG
# 查看本机信息
127.0.0.1:6379> info replication
# Replication
role:master # 主机
connected_slaves:0 # 当前连接的从机数量
master_failover_state:no-failover
master_replid:8fac5ae804e75ba1fe75db2681e20279e689606b
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
# 查看本机信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1 # 当前连接的从机数量
slave0:ip=127.0.0.1,port=6380,state=online,offset=294,lag=0
master_failover_state:no-failover
master_replid:0c8a6ae83e787193fbbc161150f92a2d47f126a3
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:294
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:294
注意
# 从机
127.0.0.1:6380> set k2 v2
(error) READONLY You can't write against a read only replica.
复制原理
扩展
哨兵模式
概念
测试
# sentinel monitor 被监控的名称 host port 1
sentinel monitor myredis 127.0.0.1 6379 1
# 如果之前是给redis加过密码的话一定要记得在配置文件里加上sentinel auth-pass
redis-sentinel sentinel.conf
小结
全部配置
# Example sentinel.conf
# 哨兵sentinel实例运行的端口 默认26379
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# 哨兵sentinel监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。
# quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentinel monitor
Redis缓存穿透和雪崩
缓存穿透(查不到)
解决方案1-布隆过滤器
解决方案2-缓存空对象
缓存击穿(量太大,缓存过期)
解决方案1-设置永不过期
解决方案2-互斥锁
缓存雪崩
解决方案1-redis高可用
解决方案2-限流降级
解决方案3-数据预热