redis详解(全)

article/2025/11/2 3:24:05

学前小故事

数据库和缓存保证一致性小故事

windows && linux 安装redis

redis 持久化

redis API

学前小故事

[我是redis]

你好,我是Redis,一个叫Antirez的男人把我带到了这个世界上。
在这里插入图片描述
说起我的诞生,跟关系数据库MySQL还挺有渊源的。

在我还没来到这个世界上的时候,MySQL过的很辛苦,互联网发展的越来越快,它容纳的数据也越来越多,用户请求也随之暴涨,而每一个用户请求都变成了对它的一个又一个读写操作,MySQL是苦不堪言。尤其是到“双11”、“618“这种全民购物狂欢的日子,都是MySQL受苦受难的日子。

据后来MySQL告诉我说,其实有一大半的用户请求都是读操作,而且经常都是重复查询一个东西,浪费它很多时间去进行磁盘I/O。

后来有人就琢磨,是不是可以学学CPU,给数据库也加一个缓存呢?于是我就诞生了!

出生不久,我就和MySQL成为了好朋友,我们俩常常携手出现在后端服务器中。

应用程序们从MySQL查询到的数据,在我这里登记一下,后面再需要用到的时候,就先找我要,我这里没有再找MySQL要。
在这里插入图片描述
为了方便使用,我支持好几种数据结构的存储:

* String
* Hash
* List
* Set
* SortedSet
* Bitmap

因为我把登记的数据都记录在内存中,不用去执行慢如蜗牛的I/O操作,所以找我要比找MySQL要省去了不少的时间呢。

可别小瞧这简单的一个改变,我可为MySQL减轻了不小的负担!随着程序的运行,我缓存的数据越来越多,有相当部分时间我都给它挡住了用户请求,这一下它可乐得清闲自在了!

有了我的加入,网络服务的性能提升了不少,这都归功于我为数据库挨了不少枪子儿。

[缓存过期 && 缓存淘汰]

不过很快我发现事情不妙了,我缓存的数据都是在内存中,可是就算是在服务器上,内存的空间资源还是很有限的,不能无节制的这么存下去,我得想个办法,不然吃枣药丸。

不久,我想到了一个办法:给缓存内容设置一个超时时间,具体设置多长交给应用程序们去设置,我要做的就是把过期了的内容从我里面删除掉,及时腾出空间就行了。

在这里插入图片描述
超时时间有了,我该在什么时候去干这个清理的活呢?

最简单的就是定期删除,我决定100ms就做一次,一秒钟就是10次!

我清理的时候也不能一口气把所有过期的都给删除掉,我这里面存了大量的数据,要全面扫一遍的话那不知道要花多久时间,会严重影响我接待新的客户请求的!

时间紧任务重,我只好随机选择一部分来清理,能缓解内存压力就行了。

在这里插入图片描述
就这样过了一段日子,我发现有些个键值运气比较好,每次都没有被我的随机算法选中,每次都能幸免于难,这可不行,这些长时间过期的数据一直霸占着不少的内存空间!气抖冷!

我眼里可揉不得沙子!于是在原来定期删除的基础上,又加了一招:

那些原来逃脱我随机选择算法的键值,一旦遇到查询请求,被我发现已经超期了,那我就绝不客气,立即删除。

这种方式因为是被动式触发的,不查询就不会发生,所以也叫惰性删除!

可是,还是有部分键值,既逃脱了我的随机选择算法,又一直没有被查询,导致它们一直逍遥法外!而于此同时,可以使用的内存空间却越来越少。

在这里插入图片描述
而且就算退一步讲,我能够把过期的数据都删除掉,那万一过期时间设置的很长,还没等到我去清理,内存就吃满了,一样要吃枣药丸,所以我还得想个办法。

我苦思良久,终于憋出了个大招:内存淘汰策略,这一次我要彻底解决问题!

我提供了8种策略供应用程序选择,用于我遇到内存不足时该如何决策:

* noeviction:返回错误,不会删除任何键值

* allkeys-lru:使用LRU算法删除最近最少使用的键值

* volatile-lru:使用LRU算法从设置了过期时间的键集合中删除最近最少使用的键值

* allkeys-random:从所有key随机删除

* volatile-random:从设置了过期时间的键的集合中随机删除

* volatile-ttl:从设置了过期时间的键中删除剩余时间最短的键

* volatile-lfu:从配置了过期时间的键中删除使用频率最少的键

* allkeys-lfu:从所有键中删除使用频率最少的键

有了上面几套组合拳,我再也不用担心过期数据多了把空间撑满的问题了~

[缓存穿透 && 布隆过滤器]

我的日子过的还挺舒坦,不过MySQL大哥就没我这么舒坦了,有时候遇到些烦人的请求,查询的数据不存在,MySQL就要白忙活一场!不仅如此,因为不存在,我也没法缓存啊,导致同样的请求来了每次都要去让MySQL白忙活一场。我作为缓存的价值就没得到体现啦!这就是人们常说的缓存穿透。

在这里插入图片描述
这一来二去,MySQL大哥忍不住了:“唉,兄弟,能不能帮忙想个办法,把那些明知道不会有结果的查询请求给我挡一下”

这时我想到了我的另外一个好朋友:布隆过滤器
在这里插入图片描述
我这位朋友别的本事没有,就擅长从超大的数据集中快速告诉你查找的数据存不存在(悄悄告诉你,我的这位朋友有一点不靠谱,它告诉你存在的话不能全信,其实有可能是不存在的,不过它他要是告诉你不存在的话,那就一定不存在)。

在这里插入图片描述
我把这位朋友介绍给了应用程序,不存在的数据就不必去叨扰MySQL了,轻松帮忙解决了缓存穿透的问题。

[缓存击穿 && 缓存雪崩]

这之后过了一段时间太平日子,直到那一天···

有一次,MySQL那家伙正优哉游哉的摸鱼,突然一大堆请求给他怼了过去,给他打了一个措手不及。

一阵忙活之后,MySQL怒气冲冲的找到了我,“兄弟,咋回事啊,怎么一下子来的这么猛”
在这里插入图片描述
我查看了日志,赶紧解释到:“大哥,实在不好意思,刚刚有一个热点数据到了过期时间,被我删掉了,不巧的是随后就有对这个数据的大量查询请求来了,我这里已经删了,所以请求都发到你那里来了”

“你这干的叫啥事,下次注意点啊”,MySQL大哥一脸不高兴的离开了。

这一件小事我也没怎么放在心上,随后就抛之脑后了,却没曾想几天之后竟捅了更大的篓子。

那一天,又出现了大量的网络请求发到了MySQL那边,比上一次的规模大得多,MySQL大哥一会儿功夫就给干趴下了好几次!
在这里插入图片描述
等了好半天这一波流量才算过去,MySQL才缓过神来。

“老弟,这一次又是什么原因?”,MySQL大哥累的没了力气。

“这一次比上一次更不巧,这一次是一大批数据几乎同时过了有效期,然后又发生了很多对这些数据的请求,所以比起上一次这规模更大了”

MySQL大哥听了眉头一皱,“那你倒是想个办法啊,三天两头折磨我,这谁顶得住啊?”

“其实我也很无奈,这个时间也不是我设置的,要不我去找应用程序说说,让他把缓存过期时间设置的均匀一些?至少别让大量数据集体失效”

“走,咱俩一起去”

后来,我俩去找应用程序商量了,不仅把键值的过期时间随机了一下,还设置了热点数据永不过期,这个问题缓解了不少。哦对了,我们还把这两次发生的问题分别取了个名字:缓存击穿和缓存雪崩。

我们终于又过上了舒适的日子···

数据库和缓存保证一致性小故事

一天,老板说「最近公司的用户越来越多了,但是服务器的访问速度越来越差的,阿旺帮我优化下,做好了给你画个饼!」。

程序员阿旺听到老板口中的「画饼」后就非常期待,没有任何犹豫就接下了老板给的这个任务。

阿旺登陆到了服务器,经过一番排查后,确认服务器的性能瓶颈是在数据库。

这好办,给服务器加上 Redis,让其作为数据库的缓存。

这样,在客户端请求数据时,如果能在缓存中命中数据,那就查询缓存,不用在去查询数据库,从而减轻数据库的压力,提高服务器的性能。

[先更新数据库,还是先更新缓存?]

阿旺有了这个想法后,就准备开始着手优化服务器,但是挡在在他前面的是这样的一个问题。

在这里插入图片描述

由于引入了缓存,那么在数据更新时,不仅要更新数据库,而且要更新缓存,这两个更新操作存在前后的问题:

• 先更新数据库,再更新缓存;

• 先更新缓存,再更新数据库;

阿旺没想到太多,他觉得最新的数据肯定要先更新数据库,这样才可以确保数据库里的数据是最新的,于是他就采用了「先更新数据库,再更新缓存」的方案。

阿旺经过几个夜晚的折腾,终于「优化好了服务器」,然后就直接上线了,自信心满满跑去跟老板汇报。

老板不懂技术,自然也没多虑,就让后续阿旺观察下服务器的情况,如果效果不错,就跟阿旺谈画饼的事情。

阿旺观察了好几天,发现数据库的压力大大减少了,访问速度也提高了不少,心想这事肯定成的了。

好景不长,突然老板收到一个客户的投诉,客户说他刚发起了两次更新年龄的操作,但是显示的年龄确还是第一次更新时的年龄,而第二次更新年龄并没有生效。

老板立马就找了阿旺,训斥着阿旺说:「这么简单的更新操作,都有 bug?我脸往哪儿放?你的饼还要不要了?」

听到自己准备到手的饼要没了的阿旺瞬间就慌了,立马登陆服务器排查问题,阿旺查询缓存和数据库的数据后发现了问题。

数据库的数据是客户第二次更新操作的数据,而缓存确还是第一次更新操作的数据,也就是出现了数据库和缓存的数据不一致的问题

这个问题可大了,阿旺经过一轮的分析,造成缓存和数据库的数据不一致的现象,是因为并发问题!

举个例子,比如「请求 A 」和「请求 B 」两个请求,同时更新「同一条」数据,则可能出现这样的顺序:

在这里插入图片描述

A 请求先将数据库的数据更新为 1,然后在更新缓存前,请求 B 将数据库的数据更新为 2,紧接着也把缓存更新为 2,然后 A 请求更新缓存为 1。

此时,数据库中的数据是 2,而缓存中的数据却是 1,出现了缓存和数据库中的数据不一致的现象。

[先更新缓存,再更新数据库?]

那换成「先更新缓存,再更新数据库」这个方案,还会有问题吗?

依然还是存在并发的问题,分析思路也是一样。

假设「请求 A 」和「请求 B 」两个请求,同时更新「同一条」数据,则可能出现这样的顺序:
在这里插入图片描述

A 请求先将缓存的数据更新为 1,然后在更新数据库前,B 请求来了, 将缓存的数据更新为 2,紧接着把数据库更新为 2,然后 A 请求将数据库的数据更新为 1。

此时,数据库中的数据是 1,而缓存中的数据却是 2,出现了缓存和数据库中的数据不一致的现象。

所以,无论是「先更新数据库,再更新缓存」,还是「先更新缓存,再更新数据库」,这两个方案都存在并发问题,当两个请求并发更新同一条数据的时候,可能会出现缓存和数据库中的数据不一致的现象。

[先更新数据库,还是先删除缓存?]

阿旺定位出问题后,思考了一番后,决定在更新数据时,不更新缓存,而是删除缓存中的数据。然后,到读取数据时,发现缓存中没了数据之后,再从数据库中读取数据,更新到缓存中。

阿旺想的这个策略是有名字的,是叫 Cache Aside 策略,中文是叫旁路缓存策略。

该策略又可以细分为「读策略」和「写策略」。

在这里插入图片描述

写策略的步骤:

• 更新数据库中的数据;

• 删除缓存中的数据。

读策略的步骤:

• 如果读取的数据命中了缓存,则直接返回数据;

• 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。

阿旺在想到「写策略」的时候,又陷入更深层次的思考,到底该选择哪种顺序呢?

• 先删除缓存,再更新数据库;

• 先更新数据库,再删除缓存。

阿旺这次经过上次教训,不再「想当然」的乱选方案,因为老板这次给的饼很大啊,必须把握住。

于是阿旺用并发的角度来分析,看看这两种方案哪个可以保证数据库与缓存的数据一致性。

[先删除缓存,再更新数据库]

阿旺还是以用户表的场景来分析。

假设某个用户的年龄是 20,请求 A 要更新用户年龄为 21,所以它会删除缓存中的内容。这时,另一个请求 B 要读取这个用户的年龄,它查询缓存发现未命中后,会从数据库中读取到年龄为 20,并且写入到缓存中,然后请求 A 继续更改数据库,将用户的年龄更新为 21。

在这里插入图片描述

最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库的数据不一致。

可以看到,先删除缓存,再更新数据库,在「读 + 写」并发的时候,还是会出现缓存和数据库的数据不一致的问题。

[先更新数据库,再删除缓存]

继续用「读 + 写」请求的并发的场景来分析。

假如某个用户数据在缓存中不存在,请求 A 读取数据时从数据库中查询到年龄为 20,在未写入缓存中时另一个请求 B 更新数据。它更新数据库中的年龄为 21,并且清空缓存。这时请求 A 把从数据库中读到的年龄为 20 的数据写入到缓存中。

在这里插入图片描述
最终,该用户年龄在缓存中是 20(旧值),在数据库中是 21(新值),缓存和数据库数据不一致。

从上面的理论上分析,先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高。

因为缓存的写入通常要远远快于数据库的写入,所以在实际中很难出现请求 B 已经更新了数据库并且删除了缓存,请求 A 才更新完缓存的情况。

而一旦请求 A 早于请求 B 删除缓存之前更新了缓存,那么接下来的请求就会因为缓存不命中而从数据库中重新读取数据,所以不会出现这种不一致的情况。

所以,「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的。

而且阿旺为了确保万无一失,还给缓存数据加上了「过期时间」,就算在这期间存在缓存数据不一致,有过期时间来兜底,这样也能达到最终一致。

阿旺思考到这一步后,觉得自己真的是个小天才,因为他竟然想到了个「天衣无缝」的方案,他二话不说就采用了这个方案,又经过几天的折腾,终于完成了。

他自信满满的向老板汇报,已经解决了上次客户的投诉的问题了。老板觉得阿旺这小伙子不错,这么快就解决问题了,然后让阿旺在观察几天。

事情哪有这么顺利呢?结果又没过多久,老板又收到客户的投诉了,说自己明明更新了数据,但是数据要过一段时间才生效,客户接受不了。

老板面无表情的找上阿旺,让阿旺尽快查出问题。

阿旺得知又有 Bug 就更慌了,立马就登录服务器去排查问题,查看日志后得知了原因。

「先更新数据库, 再删除缓存」其实是两个操作,前面的所有分析都是建立在这两个操作都能同时执行成功,而这次客户投诉的问题就在于,在****删除缓存(第二个操作)的时候失败了,导致缓存中的数据是旧值。

好在之前给缓存加上了过期时间,所以才会出现客户说的过一段时间才更新生效的现象,假设如果没有这个过期时间的兜底,那后续的请求读到的就会一直是缓存中的旧数据,这样问题就更大了。

所以新的问题来了,如何保证「先更新数据库 ,再删除缓存」这两个操作能执行成功?

阿旺分析出问题后,慌慌张张的向老板汇报了问题。

老板知道事情后,又给了阿旺几天来解决这个问题,画饼的事情这次没有再提了。

阿旺会用什么方式来解决这个问题呢?

老板画的饼事情,能否兑现给阿旺呢?

[如何保证两个操作都能执行成功?]

这次用户的投诉是因为在删除缓存(第二个操作)的时候失败了,导致缓存还是旧值,而数据库是最新值,造成数据库和缓存数据不一致的问题,会对敏感业务造成影响。

举个例子,来说明下。

应用要把数据 X 的值从 1 更新为 2,先成功更新了数据库,然后在 Redis 缓存中删除 X 的缓存,但是这个操作却失败了,这个时候数据库中 X 的新值为 2,Redis 中的 X 的缓存值为 1,出现了数据库和缓存数据不一致的问题。

在这里插入图片描述

那么,后续有访问数据 X 的请求,会先在 Redis 中查询,因为缓存并没有 诶删除,所以会缓存命中,但是读到的却是旧值 1。

其实不管是先操作数据库,还是先操作缓存,只要第二个操作失败都会出现数据一致的问题。

问题原因知道了,该怎么解决呢?有两种方法:

• 重试机制。

• 订阅 MySQL binlog,再操作缓存。

先来说第一种。

重试机制

我们可以引入消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。

• 如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。当然,如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。

• 如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试。

举个例子,来说明重试机制的过程。

在这里插入图片描述

订阅 MySQL binlog,再操作缓存

「先更新数据库,再删缓存」的策略的第一步是更新数据库,那么更新数据库成功,就会产生一条变更日志,记录在 binlog 里。

于是我们就可以通过订阅 binlog 日志,拿到具体要操作的数据,然后再执行缓存删除,阿里巴巴开源的 Canal 中间件就是基于这个实现的。

Canal 模拟 MySQL 主从复制的交互协议,把自己伪装成一个 MySQL 的从节点,向 MySQL 主节点发送 dump 请求,MySQL 收到请求后,就会开始推送 Binlog 给 Canal,Canal 解析 Binlog 字节流之后,转换为便于读取的结构化数据,供下游程序订阅使用。

下图是 Canal 的工作原理:
在这里插入图片描述

所以,如果要想保证「先更新数据库,再删缓存」策略第二个操作能执行成功,我们可以使用「消息队列来重试缓存的删除」,或者「订阅 MySQL binlog 再操作缓存」,这两种方法有一个共同的特点,都是采用异步操作缓存。

windows && linux 安装redis

  1. windows安装redis

    1. 下载地址

      https://github.com/MicrosoftArchive/redis/releases

    2. 安装步骤

      解压后,启动临时服务

      开启cmd新窗口,登录redis,存入键值测试

      打开redis.windows.conf,进行持久化配置、端口修改、登录密码等配置

      自定义windows服务,脱离cmd窗口限制

      安装redis图形化管理界面

      简单主从redis服务器搭建

    3. 相关截图 [解压阶段]

      1.安装包解压内容
      在这里插入图片描述
      2.文件名简要
      在这里插入图片描述

      [注]redis 支持 32 位和 64 位。这个需要根据你系统平台的实际情况选择,这里我们下载 Redis-x64-3.2.100.zip压缩包到 D 盘redis文件夹下。

      [注]redis.windows.conf是redis的配置文件,用于持久化配置,端口修改等配置。

    4. 相关截图 [部署阶段]

      1.打开cmd,进入到刚才解压到的目录,启动临时服务 || redis-server.exe redis.windows.conf
      在这里插入图片描述 [注]通过这个命令,会创建redis临时服务,不会在window Service列表出现redis服务名称和状态,此窗口关闭,服务会自动关闭。

      2.打开另一个cmd窗口,进行客户端调用 || redis-cli.exe -h 127.0.0.1 -p 6379

      在这里插入图片描述 [注]这里的set name 相当于存入 键值对为name zhangsan 的数据 get name 即从redis 中获取键为Name 的 value 值。测试非关系型数据库redis是否部署成功

      3.redis自定义windows服务安装,脱离cmd启动的局限;进入redis安装目录,安装服务 ||
      redis-server.exe --service-install redis.windows.conf --service-name redisserver1 --loglevel verbose

      在这里插入图片描述
      4.查看服务安装状况 || cmd窗口 ->win+r -> services.msc

      在这里插入图片描述 [注]
      启动服务:redis-server.exe --service-start --service-name redisserver1
      停止服务:redis-server.exe --service-stop --service-name redisserver1
      卸载服务:redis-server.exe --service-uninstall--service-name redisserver1

    5. redis桌面管理器

      链接:https://pan.baidu.com/s/1BE5SCyoF3IPetWKMhBQzwQ

      密码:d4d8
      在这里插入图片描述

      [注]Redis Desktop Manager 0.8的版本强烈建议你切换成0.9.3最后一版不收费的版本。用0.8版本进行1万条数据查询时经常卡死,或者链接超时。0.9.3却不会卡死很流畅。

      主从服务器

      将d盘下新建一个文件夹叫redis2,把redis文件夹的东西拷贝到redis2文件夹下,将
      redis-windows.conf配置文件中的ip 和端口号改一下,然后按照上面的步骤安装一个服务
      即可设置密码,redis-windows.conf配置文件中,找到 #requirepass foobared 
      #号去掉,requirepass 密码
      之后再启动服务后,要使用redis就需要输入密码了
      

      在这里插入图片描述

  2. linux安装redis

    https://redis.io/				安装包下载地址		tar -zxvf redis-5.0.8.tar.gz 	解压安装包yum install gcc- c++			(redis目录下)安装环境gcc -v							(redis目录下)		make							(redis目录下)make install					(redis目录下)cd /usr/local/bin				mkdir FeraoRedisConfig			(bin 目录下)cp /usr/redis/redis-5.0.8/redis.conf  FeraoRedisConfig	(bin 目录下)之后使用这个配置文件进行启动vim redis.conf					(FeraoRedisConfig目录下)redis默认不是后台启动的,需要修改配置文件更改daemonize yesredis-server FeraoRedisConfig/redis.conf(/usr/local/bin目录下)通过指定配置文件启动redis-cli -p 6379				(/usr/local/bin目录下)使用redis-cli进行测试连接ping							测试是否连接成功ps -ef|grep redis				新开一个窗口,查看后台进程shutdown						关闭redis连接(关闭redi服务步骤一)exit							退出(关闭redi服务步骤二)
    

基础

  1. 关系型数据库

    关系型数据库								MysqlOralce特点										数据之间有关联数据存储在硬盘上效率										操作关系型数据库非常耗时		
    

    在这里插入图片描述

  2. 非关系型数据库

    非关系型数据库						redis、hbase存储									key:value特点									数据之间没有关联关系数据存储在内存中缓存思想								从缓存中获取数据,有数据时,直接返回没有数据时,从数据库中查询,将数据放入到缓存,返回数据
    

    在这里插入图片描述
    [注]
    1.关系型数据库与非关系型数据库是互补的关系
    2.通常情况下使用关系型数据库,在适合使用NOSQL的时候使用NOSQL数据库,让NOSQL数据库对关系型数据库的不足进行弥补
    3.一般会将数据存储在关系型数据库中,在nosql数据库中备份存储关系型数据库的数据。

  3. 主流的NOSQL产品

    1. 键值(key-value)存储数据库

      相关产品Tokyo Cabinet/Tyrant ,Redis ,Voldmort , Berkeley DB典型应用内容缓存,主要用于处理大量数据的高访问负载数据模型一系列键值对优势快速查询劣势存储的数据缺少结构化
      
    2. 列存储数据库

      相关产品Cassandra, HBase ,Riak典型应用分布式的文件系统数据模型以列簇式存储,将同一列数据存在一起优势查找速度快,可扩展性强,更容易进行分布式扩展劣势功能相对局限
      
    3. 文档型数据库

      相关产品CouchDB,MongoDB典型应用Web应用(与key-value类似,Value是结构化的)数据模型一系列键值对优势数据结构要求不严格劣势数据结构要求不严格
      

Redis

  1. 基础知识

    1. 概括

      Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数
      据,50个并发执行100000个请求,读的速度是110000/秒,写的速度是81000/秒,
      且Redis通过提供多种键值数据类型来适应不同场景下的存储需求,目前为止Redis支持的
      键值数据类型,如有
      字符串类型(String)
      哈希类型(hash)
      列表类型(list)
      集合类型(set)
      有序集合类型(SortedSet
    2. 应用场景

      缓存(数据查询,短连接,新闻内容,商品内容等等)聊天室的在线好友列表任务队列。(秒杀,抢购,12306等等)应用排行榜网站访问统计数据过期处理分布式集群架构中的session分离
      
    3. 基础命令

      dbsize					当前redis库的大小select 7				切换redis库keys *					当前redis库存储的所有keyflushdb					清除当前redis库中所有数据flushall				清除全部redis库中数据config get requirepass	当前使用的密码set requirepass "123456"设置密码auth 123456				输入密码info replication		查看当前库的信息
      
    4. 细节

      1. redis默认有16个数据库(配置文件中内容),默认使用的是第02. redis是单线程的redis是基于内存操作,CPU不是redis性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽。并且redis是c语言写的,官方提供的数据未100000+的QPS,完全不比同样是使用key-value的memecache差
      
  2. 测试性能

    测试工具redis-benchmark,官方自带的性能测试工具执行位置redis所在目录,执行如上语句即可redis-benchmark命令参数-h		指定服务器主机名-p		指定服务器端口-s		指定服务器socket-c		指定并发连接数-n		指定请求数-d		以字节的形式指定set/get值的数据大小-k		1=keep alive 0=reconnect-r		set/get/incr 使用随机key,sadd使用随机值-p		通过管道传输请求-q		强制退出redis,仅显示query/sec值--csv	以csv格式输出自定义测试选项redis-server FeraoRedisConfig/redis.conf				启动redis服务redis-cli -p 6379										启动客户端redis-benchmark -h localhost -p 6379 -c 100 -n 100000	发出测试命令(bin目录下)
    

    在这里插入图片描述

  3. 配置文件(Redis.conf)

    1. 单位

      在这里插入图片描述

    2. 引入配置文件
      在这里插入图片描述

    3. 网络

      bind 127.0.0.1			绑定的ipprotected-mode yew		保护模式port 6379				端口设置					
      
    4. 通用 GENERAL

      Daemonize yes			以守护进程的方式进行,默认是no,开发者需要自己开启为yespidfile /var/run/redis_6379.pid如果以后台的方式运行,我们就需要制定一个pid文件loglevel notice			日志水平(debug、verbose、notice、warning)logfile ""				日志的文件位置名databases 16			数据库的数量,默认是16个数据库always-show-logo yes	是否总是显示Logo
      
    5. 快照

      # 持久化,在规定时间内执行了多少次操作,则会持久化到文件rdb.aof
      # redis是内存数据库,如果没有持久化,那么数据断点及失save 900 1		如果900s内,至少有一个Key进行了修改,redis进行持久化操作save 300 10		如果300s内,至少10个key进行了修改,redis进行持久化操作save 60 10000	如果60s内,至少10000个key进行了修改,redis进行进行持久化操作stop-writes-on-bgsave-error yes	持久化如果出错,是否还需要继续工作rdbcompression yes				是否压缩rdb文件,需要消耗一些cpu资源rdbchecksum yes					保有rdb文件的时候,进行错误的检查校验dir ./							文件保存的目录
      
    6. SECURITY(安全)

      requirepass 123456
      
    7. CLIENTS(限制)

      maxclients 10000		设置能连接上redis的最大客户端的数量maxmemory <bytes>		redis配置最大的内存容量maxmemory-policy noeviction内存到达上限之后的处理策略1.volatile-1ru: 	只对设置了过期时间的Key进行LRU(默认值)2.allkeys-1ru		删除1ru算法的key3.volatile-random	随机删除即将过期key4.allkeys-random	随机删除5.volatile-ttl		删除即将过期的6.noeviction		永不过期,返回错误
      
    8. APPEND ONLY MODE (aof配置)

      appendonly no		默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分庆康下,rdb够用appendfilename "appendonly.aof"持久化的文件的名字# appendfsync always每次修改都会sync,消耗性能appendfsync everysec每次执行一次sync,可能会丢失这1s的数据# appendfsync no不执行sync,这个时候操作系统自己同步数据,速度最快
      

      [注]redis启动时就通过配置文件来启动

  4. 语法(数据结构)

    字符串类型(String)存储						 	set key value获取							get  key删除  						del key 哈希类型(map格式)存储							hset key field value获取获取指定的field对应的值	hget key field获取所有的field和value	hgetall key删除							hdel key field列表类型(list,linkedlist格式)存储将元素加入列表左表			lpush key value将元素加入列表右边			rpush key value范围获取						lrange key start end删除删除列表最左边的元素,并将元素返回	lpop key删除列表最右边的元素,并将元素返回	rpop key集合类型(set)存储							sadd key value获取所有元素					smembers key删除集合中某个元素				srem key value有序集合类型(sortedset)存储							zadd key score value获取							zrange key start end删除							zrem key value-------------------------------------------------------
    通用命令查询所有键					key *获取键对应的value类型			tepe key删除指定的Key,value			del key

    [注]
    1.redis存储的是:key,value 格式的数据,其中Key都是字符串,value有5种不同的数据结构
    2.列表类型支持重复元素
    3.redis列表是简单的字符串列表,按照插入顺序排序,操作者可以添加一个元素到列表头部(左边)或者尾部(右边)
    4.集合类型不允许重复元素
    5.有序集合类型,不允许重复元素,且元素有顺序,每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序。
    在这里插入图片描述 在这里插入图片描述 在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  5. redis事务

    本质一组命令的集合。一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行。特性一次性、顺序性、排他性。注意1、redis事务没有隔离级别的概念。所有的命令在事务中,并没有直接执行,只有发起执行命令的时候才会执行(Exec2、redis单条命令是保证原子性的,但是事务不保证原子性操作开启事务(multi)命令入队(正常命令集合)执行事务(exec)所有情况正常执行事务放弃事务编译型异常(代码有问题命令出错,事务中所有的命令都不会被执行)运行时异常(IO异常,如果事务队列中存在逻辑问题,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常)
    

    在这里插入图片描述

redis 持久化

1.简单概述

2.RDB模式

3.AOF模式

4.合理使用

简单概述

轮廓

redis是内存数据库,如果不将内存中的数据库状态保存到磁盘,那么一旦服务器进程退出,服务器中的数据库状态也会消失。所以redis提供了持久化功能,且分为RDB、AOF两种方式

配置文件

redis.windows.conf文件

RDB模式

RDB模式原理

在指定的时间间隔内将内存中的数据库快照写入磁盘,再次恢复时再将快照文件直接读到内存里。

在此期间,redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何io操作的。

这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。但RDB存在缺陷是最后一次持久化的数据可能丢失。当我们安装好的redis,其默认持久化模式是RDB模式【一般情况下不需要修改】,RDB保存的文件是dump.rdb。都是在redis配置文件中快照中进行配置的。

简单可记住,RDB默认方式,不需要进行配置,默认就使用这种机制,在一定的间隔时间中,检测key的变化情况,然后持久化数据

触发机制

三种情况下会触发RDB规则

1.sava的规则满足的情况下,会自动触发rdb规则
2.执行fushall命令,也会触发我们的rdb规则
3.退出redis,也会产生rdb文件

恢复机制

只需要将rdb文件放在redis启动目录下即可,redis启动的时候回自动检测dump.rdb,恢复数据

相关配置

save 900 1 after 900 sec (15 min) if at least 1 key changed
save 300 10 after 300 sec (5 min) if at least 10 keys changed
save 60 10000 after 60 sec if at least 10000 keys changed
在这里插入图片描述

优缺点

RDB模式的优势是适合大规模的数据恢复、且对数据的完整性不高。但与之相对,不足之处是需要一定的时间间隔进程操作,如果redis意外宕机,这个最后一次修改数据就没有了,且fork进程的时候,会占用一定的内存空间。

AOF模式

AOF模式原理

【append only file】将开发者操作redis的所有命令都记录下来,恢复的时候就把文件内所有写的操作执行一遍。

它以日志的形式来记录每个写操作,将redis执行过的所有指令记录下来(读操作不记录),且只许追加文件但不可以改写文件。当redis启动之初会读取该文件重新构建数据。换句话说,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。

aof模式保存的文件名是appendonly.aof。在redis配置中,默认是不开启的,开发者需要手动配置,只需要将appendonly改为yes即可开启、再进行重启redis,就可以生效了。

如果aof文件有错位,这时候redis是启动不起来的,开发者需要修复aof文件,redis为开发者提供了一个工具,也就是bin目录下的redis-check-aof,开发者只需在redis bin目录下执行redis-check-aof --fix appendonly.aof即可
如果文件正常了,重启后就可以直接恢复了。

简单理解aof是日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据

重写规则

aof默认就是文件的无限追加,文件会越来越大,如果aof文件大于64m,redis会fork一个新的进程来将文件进行重写

相关配置

appendfsync no (关闭aof,不进行持久化)
appendonly yes (开启aof)
appendfsync always (每一次操作都进行持久化)
appendfsync everysec (每间隔一秒进行一次持久化)

在这里插入图片描述

优缺点

AOF模式的优势是每一次修改都同步时,文件的完整性会更好;每秒同步一次方式时,可能会丢失1s的数据;从不同步时,效率最高。
相对于数据文件来说,AOF文件远远大于RDB;修复的速度也比RDB慢;AOF运行效率要比RDB慢,所以redis默认的配置就是RDB持久配置

[注]
1.redis是一个内存数据库,当redis 服务器重启,获取电脑重启,数据会丢失,我们可以将redis 内存中的数据持久化保存到硬盘的文件中。
2.修改后,重启redis服务器,并制定配置文件的名称

合理使用

同时开启两种持久化方式

在这种情况下,当redis重启的时候会优先载入aof文件来恢复原始的数据,因为在通常情况下aof文件保存的数据要比rdb文件保存的数据要完整,而rdb的数据不实时。

同时使用两者时服务器重启也只会找aof文件,那要不要只使用aof呢,这里建议不要,因为rdb更适合用于备份数据库(aof在不断改变不好备份),快速重启时,而且不会有aof可能潜在的bug,留着作为一个万一的手段。

性能建议

因为rdb文件只用作后备用途,建议只在Slave上持久化rdb,而且只要15分钟备份一次就够了,只保留save 900 1 这一条规则

  1. 扩展

     ```java	1.RDB持久化方式能够在指定的时间间隔内对开发者的数据进行快照2.AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据AOF命令以redis协议追加保存每次写的操作到文件末尾,redis还能对aof文件进行后台重写,使得aof文件的体积不至于过大3.只做缓存,如果开发者希望数据在服务器运行的时候存在,开发者可以不适用任何持久化```
    
  2. reids客户端操作类:Jedis

    基础jedis是一款java操作redis的工具,使用需要引入jedis的Jar包示例
    
    	public void redisTest(){//获取连接Jedis jedis = new Jedis("127.0.0.1",6379);//输入密码jedis.auth("123456");//选择操作库jedis.select(0);	System.out.println("连接成功");System.out.println("服务正在运行: "+jedis.ping());//查看库里所有KeySet<String> keys = jedis.keys("*");for (String key : keys) {System.out.println(key);}//查看指定Key类型System.out.println(jedis.type("myhash"));System.out.println(jedis.type("mysort"));System.out.println(jedis.type("mylist"));System.out.println(jedis.type("name"));System.out.println(jedis.type("myset"));	//操作jedis.set("username","zhangsan");jedis.lpush("ferao",string);						//从左边存jedis.rpush("ferao",string,string1,string2);		//从右边存			jedis.hset("user","name","lisa");jedis.hmset("addr_lib", map);jedis.sadd("myset","java","php","c++");jedis.zadd("mysortedset",3,"lisea");//获取String username = jedis.get("username");List<String> mylist = jedis.lrange("ferao",0,-1);			String username = jedis.hget("user","name");		Set<String> myset = jedis.smembers("myset");Set<String> mysortedset = jedis.zrange("mysortedset",0,-1);//获取某类别中全部//获取hash类别中所有map中的元素Map<String,String> user = jedis.hgetAll("user");Set<String> keySet = user.KeySet();for(String key : keySet){String value= user.get(key);System.out.println(key +":" + value);}//关闭连接jedis.close();}
    

    在这里插入图片描述

Jedis常用方法

  1. 键操作

    jedis.flushDB()								清空数据boolean jedis.exists(String key)			判断某键是否存在jedis.set(String key,String value)			新增键值对(key,value)Set<String> jedis.keys("*")					获取所有keyjedis.del(String key)						删除键为key的项jedis.expire(String key,int i)				设置键为Key的项过期时间为i秒int jedis.ttl(String key)					获取键为Key的项剩余生存时间(s)jedis.persist(String key)					移除键为Key的项的生存时间限制jedis.type(String key)						查看键为Key的项所对应value的数据类型
    
  2. 字符串操作

    jedis.set(String key,String value)			增加(或覆盖)数据项jedis.setnx(String key,String value)		增加数据项(不覆盖),重复不插入jedis.setex(String key,int t,String value)	增加数据项并设置有效时间Jedis.del(String key)						删除键为Key的数据项Jedis.get(String key)						获取键为Key对应的valuejedis.append(String key,String s)			在key对应的value后面扩展字符sjedis.mset(String k1,String v1,String k2,String v2..)增加多个键值对String[] jedis.mget(String k1,String k2...)获取多个key对应的valueJedis.del(new String[]{String k1,String k2..})删除多个Key对应的数据项String jedis.getSet(String key,String value)获取key对应value并更新value
    
  3. 列表(List)操作

    jedis.lpush(String key,String v1,String v2..)添加一个list列表jedis.lpush(String key,String vn)			在key值对应的列表左插入一个元素jedis.lrange(String key,int i,int j)		获取key对应的列表,区间在[i,j]jedis.lrem(String key,int num,String val)	删除指定元素val,个数numjedis.ltrim(String key,int i,int j)			删除列表区间[i,j]之外的元素jedis.lpop(String key)						key对应的列表左出栈一个元素jedis.rpush(String key,String Vn)			key对应的列表右插入一个元素jedis.rpop(String key)						key对应的列表右出栈一个元素jedis.lset(String key,int index,String val)	修改Key对应的列表指定下标元素jedis.llen(String key)						获取key对应的列表长度jedis.lindex(String key,int index)			获取key对应的列表下标为index值jedis.sort(String key)						key对应列表内的元素从小到大排序
    
  4. 集合(Set)操作

    jedis.sadd(String key,String v1,String v2..)添加一个集合jedis.smembers(String key)					获取key对应的集合所有元素jedis.srem(String key,String val)			删除一个值为val的元素jedis.srem(String key,String v...)			删除值为v1,v2..的元素jedis.spop(String key)						随机弹出集合中一个元素jedis.scard(String key)						获取set中元素个数jedis.smove(String k1,String k2,String val)将元素val从集合k1剪贴到k2jedis.sinter(String k1,String k2)			获取集合k1和集合k2的交集jedis.sunion(String k1,String k2)			获取集合k1和集合k2的并集jedis.sdiff(String k1,String k2)			获取集合k1和集合k2的差集
    
  5. 哈希(Hash)操作

    jedis.hmet(String key,Map map)				同时将多个field-value设置到哈希表中,此命令会覆盖哈希表中已存在的字段,如果不存在创建jedis.hset(String key,String key,String value)往hash插入一个(k-v)元素jedis.hgetAll(String key)					获取hash的所有(k-v)元素jedis.hkeys(String key)						获取hash所有元素的keyjedis.hvals(String key)						获取hash所有元素的valuejedis.hincrBy(String key,String k,int i)	把哈希中k对应的元素val+=ijedis.hdecrBy(String key,String k,int i)	把hash中k对应的元素val-=ijedis.hdel(String key,String k1,String k2..)从hash中删除一个或多个元素jedis.hlen(String key)						获取hash中元素个数jedis.hexists(String key,String k1)			判断hash是否存在k1对应元素jedis.hmget(String key,String k1,String k2..)获取hash中一个或多个元素value
    
  6. 有序集合(Zsort)操作

    jedis.zadd(String key,Map map)				添加一组ZSet集合jedis.hset(String key,int score,String val)	插入一个元素(Score-Val)jedis.zrange(String key,int i,int j)		获取ZSet中下标[i,j]区间元素jedis.zrangeWithScores(String key,int i,int j)获取ZSet里sore[i,j]区间元素jedis.zscore(String key,String value)		获取ZSet里val为value元素的scorejedis.zrank(String key,String value)		获取ZSet里val为value元素的score排名jedis.zrem(String key,String value)			删除ZSet里val为value的元素jedis.zcard(String key)						获取ZSet的元素个数jedis.zcount(String key,int i,int j)		获取ZSet中score在[i,j]区间元素个数
    

redis掌握流程

nosql阿里巴巴架构演进nosql数据模型Nosql四大分类CAPBASERedis入门Redis安装(WIndows & Linux服务器)五大基本数据类型StringListSetHashZSet三大特殊数据类型geohyperloglogbitmapRedis配置详解Reids持久化RDBAOFRedis事务操作Redis实现订阅发布Redis主从复制Redis哨兵模式(现在公司中所有的集群都用哨兵模式)缓存穿透及解决方案缓存击穿及解决方案缓存雪崩及解决方案基础API之Jedis详解Springboot集成Redis操作Redis的实践分析

redis API

1.单个reids中API的测试

2.JedisPool连接redis(单机)

3.集群reids中API的测试

4.jedisCluster连接redis(集群)

单个reids中API的测试

测试key - value 的数据

/*** 单机环境Redis操作:一台Redis服务器*/
public class Standalone {private static Jedis jedis;static {jedis = new Jedis("192.168.56.180", 6379);jedis.auth("123456"); // 之前我在redis配置中配置了权限密码,这里需要设置}/*** 测试key - value 的数据* @throws InterruptedException*/@Testpublic void testKey() throws InterruptedException {System.out.println("清空数据:"+jedis.flushDB());System.out.println("判断某个键是否存在:"+jedis.exists("username"));System.out.println("新增<'username','wukong'>的键值对:"+jedis.set("username", "wukong"));System.out.println("是否存在:"+jedis.exists("name"));System.out.println("新增<'password','password'>的键值对:"+jedis.set("password", "password"));Set<String> keys = jedis.keys("*");System.out.println("系统中所有的键如下:"+keys);System.out.println("删除键password:"+jedis.del("password"));System.out.println("判断键password是否存在:"+jedis.exists("password"));System.out.println("设置键username的过期时间为5s:"+jedis.expire("username", 5));TimeUnit.SECONDS.sleep(2);System.out.println("查看键username的剩余生存时间:"+jedis.ttl("username"));System.out.println("移除键username的生存时间:"+jedis.persist("username"));System.out.println("查看键username的剩余生存时间:"+jedis.ttl("username"));System.out.println("查看键username所存储的值的类型:"+jedis.type("username"));}/**** 字符串操作* memcached和redis同样有append的操作,但是memcached有prepend的操作,redis中并没有。* @throws InterruptedException*/@Testpublic void testString() throws InterruptedException {jedis.flushDB();System.out.println("===========增加数据===========");System.out.println(jedis.set("key1","value1"));System.out.println(jedis.set("key2","value2"));System.out.println(jedis.set("key3", "value3"));System.out.println("删除键key2:"+jedis.del("key2"));System.out.println("获取键key2:"+jedis.get("key2"));System.out.println("修改key1:"+jedis.set("key1", "value1Changed"));System.out.println("获取key1的值:"+jedis.get("key1"));System.out.println("在key3后面加入值:"+jedis.append("key3", "End"));System.out.println("key3的值:"+jedis.get("key3"));System.out.println("增加多个键值对:"+jedis.mset("key01","value01","key02","value02","key03","value03"));System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03","key04"));System.out.println("删除多个键值对:"+jedis.del(new String[]{"key01","key02"}));System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));jedis.flushDB();System.out.println("===========新增键值对防止覆盖原先值==============");System.out.println(jedis.setnx("key1", "value1"));System.out.println(jedis.setnx("key2", "value2"));System.out.println(jedis.setnx("key2", "value2-new"));System.out.println(jedis.get("key1"));System.out.println(jedis.get("key2"));System.out.println("===========新增键值对并设置有效时间=============");System.out.println(jedis.setex("key3", 2, "value3"));System.out.println(jedis.get("key3"));TimeUnit.SECONDS.sleep(3);System.out.println(jedis.get("key3"));System.out.println("===========获取原值,更新为新值==========");//GETSET is an atomic set this value and return the old value command.System.out.println(jedis.getSet("key2", "key2GetSet"));System.out.println(jedis.get("key2"));System.out.println("获得key2的值的字串:"+jedis.getrange("key2", 2, 4));}/**** 整数和浮点数*/@Testpublic void testNumber() {jedis.flushDB();jedis.set("key1", "1");jedis.set("key2", "2");jedis.set("key3", "2.3");System.out.println("key1的值:"+jedis.get("key1"));System.out.println("key2的值:"+jedis.get("key2"));System.out.println("key1的值加1:"+jedis.incr("key1"));System.out.println("获取key1的值:"+jedis.get("key1"));System.out.println("key2的值减1:"+jedis.decr("key2"));System.out.println("获取key2的值:"+jedis.get("key2"));System.out.println("将key1的值加上整数5:"+jedis.incrBy("key1", 5));System.out.println("获取key1的值:"+jedis.get("key1"));System.out.println("将key2的值减去整数5:"+jedis.decrBy("key2", 5));System.out.println("获取key2的值:"+jedis.get("key2"));}/**** 列表*/@Testpublic void testList() {jedis.flushDB();System.out.println("===========添加一个list===========");jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");jedis.lpush("collections", "HashSet");jedis.lpush("collections", "TreeSet");jedis.lpush("collections", "TreeMap");System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));//-1代表倒数第一个元素,-2代表倒数第二个元素System.out.println("collections区间0-3的元素:"+jedis.lrange("collections",0,3));System.out.println("===============================");// 删除列表指定的值 ,第二个参数为删除的个数(有重复时),后add进去的值先被删,类似于出栈System.out.println("删除指定元素个数:"+jedis.lrem("collections", 2, "HashMap"));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("删除下表0-3区间之外的元素:"+jedis.ltrim("collections", 0, 3));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("collections列表出栈(左端):"+jedis.lpop("collections"));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("collections添加元素,从列表右端,与lpush相对应:"+jedis.rpush("collections", "EnumMap"));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("collections列表出栈(右端):"+jedis.rpop("collections"));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("修改collections指定下标1的内容:"+jedis.lset("collections", 1, "LinkedArrayList"));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("===============================");System.out.println("collections的长度:"+jedis.llen("collections"));System.out.println("获取collections下标为2的元素:"+jedis.lindex("collections", 2));System.out.println("===============================");jedis.lpush("sortedList", "3","6","2","0","7","4");System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0, -1));System.out.println(jedis.sort("sortedList"));System.out.println("sortedList排序后:"+jedis.lrange("sortedList", 0, -1));}/**** set集合*/@Testpublic void testSet() {jedis.flushDB();System.out.println("============向集合中添加元素============");System.out.println(jedis.sadd("eleSet", "e1","e2","e4","e3","e0","e8","e7","e5"));System.out.println(jedis.sadd("eleSet", "e6"));System.out.println(jedis.sadd("eleSet", "e6"));System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));System.out.println("删除一个元素e0:"+jedis.srem("eleSet", "e0"));System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));System.out.println("删除两个元素e7和e6:"+jedis.srem("eleSet", "e7","e6"));System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));System.out.println("eleSet中包含元素的个数:"+jedis.scard("eleSet"));System.out.println("e3是否在eleSet中:"+jedis.sismember("eleSet", "e3"));System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet", "e1"));System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet", "e5"));System.out.println("=================================");System.out.println(jedis.sadd("eleSet1", "e1","e2","e4","e3","e0","e8","e7","e5"));System.out.println(jedis.sadd("eleSet2", "e1","e2","e4","e3","e0","e8"));System.out.println("将eleSet1中删除e1并存入eleSet3中:"+jedis.smove("eleSet1", "eleSet3", "e1"));System.out.println("将eleSet1中删除e2并存入eleSet3中:"+jedis.smove("eleSet1", "eleSet3", "e2"));System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));System.out.println("eleSet3中的元素:"+jedis.smembers("eleSet3"));System.out.println("============集合运算=================");System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));System.out.println("eleSet2中的元素:"+jedis.smembers("eleSet2"));System.out.println("eleSet1和eleSet2的交集:"+jedis.sinter("eleSet1","eleSet2"));System.out.println("eleSet1和eleSet2的并集:"+jedis.sunion("eleSet1","eleSet2"));System.out.println("eleSet1和eleSet2的差集:"+jedis.sdiff("eleSet1","eleSet2"));//eleSet1中有,eleSet2中没有}/**** 散列*/@Testpublic void testHash() {jedis.flushDB();Map<String,String> map = new HashMap<String,String>();map.put("key1","value1");map.put("key2","value2");map.put("key3","value3");map.put("key4","value4");jedis.hmset("hash",map);jedis.hset("hash", "key5", "value5");System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));//return Map<String,String>System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//return Set<String>System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//return List<String>System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 6));System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 3));System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash", "key2"));System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));System.out.println("判断hash中是否存在key2:"+jedis.hexists("hash","key2"));System.out.println("判断hash中是否存在key3:"+jedis.hexists("hash","key3"));System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));System.out.println("获取hash中的值:"+jedis.hmget("hash","key3","key4"));}/*** 有序集合*/@Testpublic void testSortedSet() {jedis.flushDB();Map<String,Double> map = new HashMap<String,Double>();map.put("key2",1.2);map.put("key3",4.0);map.put("key4",5.0);map.put("key5",0.2);System.out.println(jedis.zadd("zset", 3,"key1"));System.out.println(jedis.zadd("zset",map));System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));System.out.println("zset中的所有元素:"+jedis.zrangeWithScores("zset", 0, -1));System.out.println("zset中的所有元素:"+jedis.zrangeByScore("zset", 0,100));System.out.println("zset中的所有元素:"+jedis.zrangeByScoreWithScores("zset", 0,100));System.out.println("zset中key2的分值:"+jedis.zscore("zset", "key2"));System.out.println("zset中key2的排名:"+jedis.zrank("zset", "key2"));System.out.println("删除zset中的元素key3:"+jedis.zrem("zset", "key3"));System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));System.out.println("zset中元素的个数:"+jedis.zcard("zset"));System.out.println("zset中分值在1-4之间的元素的个数:"+jedis.zcount("zset", 1, 4));System.out.println("key2的分值加上5:"+jedis.zincrby("zset", 5, "key2"));System.out.println("key3的分值加上4:"+jedis.zincrby("zset", 4, "key3"));System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));}/*** 排序*/@Testpublic void testSort(){jedis.flushDB();jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));SortingParams sortingParameters = new SortingParams();System.out.println(jedis.sort("collections",sortingParameters.alpha()));System.out.println("===============================");jedis.lpush("sortedList", "3","6","2","0","7","4");System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0, -1));System.out.println("升序:"+jedis.sort("sortedList", sortingParameters.asc()));System.out.println("升序:"+jedis.sort("sortedList", sortingParameters.desc()));System.out.println("===============================");jedis.lpush("userlist", "33");jedis.lpush("userlist", "22");jedis.lpush("userlist", "55");jedis.lpush("userlist", "11");jedis.hset("user:66", "name", "66");jedis.hset("user:55", "name", "55");jedis.hset("user:33", "name", "33");jedis.hset("user:22", "name", "79");jedis.hset("user:11", "name", "24");jedis.hset("user:11", "add", "beijing");jedis.hset("user:22", "add", "shanghai");jedis.hset("user:33", "add", "guangzhou");jedis.hset("user:55", "add", "chongqing");jedis.hset("user:66", "add", "xi'an");sortingParameters = new SortingParams();sortingParameters.get("user:*->name");sortingParameters.get("user:*->add");System.out.println(jedis.sort("userlist",sortingParameters));}
}

JedisPool连接redis(单机)

JedisPool连接redis(单机)

每次连接需要创建一个连接、执行完后就关闭,非常浪费资源,所以使用jedispool(连接池)连接

//创建连接池对象
JedisPool jedispool = new JedisPool("127.0.0.1",6379);
//从连接池中获取一个连接
Jedis jedis = jedispool.getResource();
jedis.auth("123456");
jedis.select(0);//使用jedis操作redis
jedis.flushDB();
jedis.set("unfair1","value2");
String str = jedis.get("unfair1");//使用完毕 ,关闭连接,连接池回收资源
jedis.close();
//关闭连接池
jedispool.close();

集群reids中API的测试

集群环境下Jedis操作

public class Cluster {private static JedisCluster jedis;static {// 添加集群的服务节点Set集合Set<HostAndPort> hostAndPortsSet = new HashSet<HostAndPort>();// 添加节点hostAndPortsSet.add(new HostAndPort("192.168.56.180", 7777));hostAndPortsSet.add(new HostAndPort("192.168.56.180", 8888));hostAndPortsSet.add(new HostAndPort("192.168.56.181", 7777));hostAndPortsSet.add(new HostAndPort("192.168.56.181", 8888));hostAndPortsSet.add(new HostAndPort("192.168.56.182", 7777));hostAndPortsSet.add(new HostAndPort("192.168.56.182", 8888));// Jedis连接池配置JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();// 最大空闲连接数, 默认8个jedisPoolConfig.setMaxIdle(100);// 最大连接数, 默认8个jedisPoolConfig.setMaxTotal(500);//最小空闲连接数, 默认0jedisPoolConfig.setMinIdle(0);// 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1jedisPoolConfig.setMaxWaitMillis(2000); // 设置2秒//对拿到的connection进行validateObject校验jedisPoolConfig.setTestOnBorrow(true);jedis = new JedisCluster(hostAndPortsSet, jedisPoolConfig);}/*** 测试key:value数据* 集群中flushDB、keys废弃*/@Testpublic void testKey() throws InterruptedException {//System.out.println("清空数据:"+jedis.flushDB());System.out.println("判断某个键是否存在:"+jedis.exists("username"));System.out.println("新增<'username','wukong'>的键值对:"+jedis.set("username", "xiaohai"));System.out.println("是否存在:"+jedis.exists("username"));System.out.println("新增<'password','password'>的键值对:"+jedis.set("password", "123456"));//Set<String> keys = jedis.keys("*");// System.out.println("系统中所有的键如下:"+keys);System.out.println("删除键password:"+jedis.del("password"));System.out.println("判断键password是否存在:"+jedis.exists("password"));System.out.println("设置键username的过期时间为10s:"+jedis.expire("username", 10));TimeUnit.SECONDS.sleep(2); // 线程睡眠2秒System.out.println("查看键username的剩余生存时间:"+jedis.ttl("username"));System.out.println("查看键username的剩余生存时间:"+jedis.ttl("username"));System.out.println("移除键username的生存时间:"+jedis.persist("username"));System.out.println("查看键username的剩余生存时间:"+jedis.ttl("username"));System.out.println("查看键username所存储的值的类型:"+jedis.type("username"));}/**** 字符串操作* memcached和redis同样有append的操作,但是memcached有prepend的操作,redis中并没有。* 集群中flushDB、keys、del(多个值)、mset(多个值)废弃* @throws InterruptedException*/@Testpublic void testString() throws InterruptedException {//jedis.flushDB();System.out.println("===========增加数据===========");System.out.println(jedis.set("key1","value1"));System.out.println(jedis.set("key2","value2"));System.out.println(jedis.set("key3", "value3"));System.out.println("删除键key2:"+jedis.del("key2"));System.out.println("获取键key2:"+jedis.get("key2"));System.out.println("修改key1:"+jedis.set("key1", "value1Changed"));System.out.println("获取key1的值:"+jedis.get("key1"));System.out.println("在key3后面加入值:"+jedis.append("key3", "End"));System.out.println("key3的值:"+jedis.get("key3"));//命令的时候才会去连接连接,集群中连接是对一个节点连接,不能判断多个key经过crc16算法所对应的槽在一个节点上,不支持多key获取、删除//System.out.println("增加多个键值对:"+jedis.mset("key01","value01","key02","value02"));//System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));//System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03","key04"));//System.out.println("删除多个键值对:"+jedis.del(new String[]{"key01","key02"}));//System.out.println("获取多个键值对:"+jedis.mget("key01","key02","key03"));//jedis.flushDB();System.out.println("===========新增键值对防止覆盖原先值==============");System.out.println(jedis.setnx("key1", "value1"));System.out.println(jedis.setnx("key2", "value2"));System.out.println(jedis.setnx("key2", "value2-new"));System.out.println(jedis.get("key1"));System.out.println(jedis.get("key2"));System.out.println("===========新增键值对并设置有效时间=============");System.out.println(jedis.setex("key3", 2, "value3"));System.out.println(jedis.get("key3"));TimeUnit.SECONDS.sleep(3);System.out.println(jedis.get("key3"));System.out.println("===========获取原值,更新为新值==========");//GETSET is an atomic set this value and return the old value command.System.out.println(jedis.getSet("key2", "key2GetSet"));System.out.println(jedis.get("key2"));System.out.println("获得key2的值的字串:"+jedis.getrange("key2", 2, 4)); // 相当截取字符串的第二个位置-第四个位置的字符串}/**** 整数和浮点数*/@Testpublic void testNumber() {jedis.set("key1", "1");jedis.set("key2", "2");jedis.set("key3", "2.3");System.out.println("key1的值:"+jedis.get("key1"));System.out.println("key2的值:"+jedis.get("key2"));System.out.println("key1的值加1:"+jedis.incr("key1"));System.out.println("获取key1的值:"+jedis.get("key1"));System.out.println("key2的值减1:"+jedis.decr("key2"));System.out.println("获取key2的值:"+jedis.get("key2"));System.out.println("将key1的值加上整数5:"+jedis.incrBy("key1", 5));System.out.println("获取key1的值:"+jedis.get("key1"));System.out.println("将key2的值减去整数5:"+jedis.decrBy("key2", 5));System.out.println("获取key2的值:"+jedis.get("key2"));System.out.println("key3的值:"+jedis.get("key3"));// 这里会报错,因为key3不是整数不能做计算:redis.clients.jedis.exceptions.JedisDataException: ERR value is not an integer or out of range// System.out.println("key2的值减1:"+jedis.decr("key3"));}/**** 列表*/@Testpublic void testList() {System.out.println("===========添加一个list===========");jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");jedis.lpush("collections", "HashSet"); // 叠加jedis.lpush("collections", "TreeSet"); // 叠加jedis.lpush("collections", "TreeMap"); // 叠加System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));//-1代表倒数第一个元素,-2代表倒数第二个元素System.out.println("collections区间0-3的元素:"+jedis.lrange("collections",0,3)); // 前面4个值System.out.println("===============================");// 删除列表指定的值 ,第二个参数为删除的个数(有重复时),后add进去的值先被删,类似于出栈System.out.println("删除指定元素个数:"+jedis.lrem("collections", 2, "HashMap"));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("删除下表0-3区间之外的元素:"+jedis.ltrim("collections", 0, 3));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("collections列表出栈(左端):"+jedis.lpop("collections"));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("collections添加元素,从列表右端,与lpush相对应:"+jedis.rpush("collections", "EnumMap"));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("collections列表出栈(右端):"+jedis.rpop("collections"));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("修改collections指定下标1的内容:"+jedis.lset("collections", 1, "LinkedArrayList"));System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));System.out.println("===============================");System.out.println("collections的长度:"+jedis.llen("collections"));System.out.println("获取collections下标为2的元素:"+jedis.lindex("collections", 2));System.out.println("===============================");jedis.lpush("sortedList", "3","6","2","0","7","4");System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0, -1));System.out.println(jedis.sort("sortedList"));System.out.println("sortedList排序后:"+jedis.lrange("sortedList", 0, -1));}/**** set集合*/@Testpublic void testSet() {System.out.println("============向集合中添加元素============");System.out.println(jedis.sadd("eleSet", "e1","e2","e4","e3","e0","e8","e7","e5"));System.out.println(jedis.sadd("eleSet", "e6"));System.out.println(jedis.sadd("eleSet", "e6")); // 返回0,集合中已经存在System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));System.out.println("删除一个元素e0:"+jedis.srem("eleSet", "e0"));System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));System.out.println("删除两个元素e7和e6:"+jedis.srem("eleSet", "e7","e6"));System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));System.out.println("随机的移除集合中的一个元素:"+jedis.spop("eleSet"));System.out.println("eleSet的所有元素为:"+jedis.smembers("eleSet"));System.out.println("eleSet中包含元素的个数:"+jedis.scard("eleSet"));System.out.println("e3是否在eleSet中:"+jedis.sismember("eleSet", "e3"));System.out.println("e1是否在eleSet中:"+jedis.sismember("eleSet", "e1"));System.out.println("e5是否在eleSet中:"+jedis.sismember("eleSet", "e5"));// 集群下并存会报错:redis.clients.jedis.exceptions.JedisClusterException: No way to dispatch this command to Redis Cluster because keys have different slots.// Redis集群,从key1集合与key2集合并存、交集、差集,两个键经过crc16算法可能有不同的槽。/*System.out.println("=================================");System.out.println(jedis.sadd("eleSet1", "e1","e2","e4","e3","e0","e8","e7","e5"));System.out.println(jedis.sadd("eleSet2", "e1","e2","e4","e3","e0","e8"));System.out.println("将eleSet1中删除e1并存入eleSet3中:"+jedis.smove("eleSet1", "eleSet3", "e1"));System.out.println("将eleSet1中删除e2并存入eleSet3中:"+jedis.smove("eleSet1", "eleSet3", "e2"));System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));System.out.println("eleSet3中的元素:"+jedis.smembers("eleSet3"));*//*System.out.println("============集合运算=================");System.out.println("eleSet1中的元素:"+jedis.smembers("eleSet1"));System.out.println("eleSet2中的元素:"+jedis.smembers("eleSet2"));System.out.println("eleSet1和eleSet2的交集:"+jedis.sinter("eleSet1","eleSet2"));System.out.println("eleSet1和eleSet2的并集:"+jedis.sunion("eleSet1","eleSet2"));System.out.println("eleSet1和eleSet2的差集:"+jedis.sdiff("eleSet1","eleSet2"));*/jedis.del("eleSet");jedis.del("eleSet1");jedis.del("eleSet2");jedis.del("eleSet3");}/**** 散列*/@Testpublic void testHash() {Map<String,String> map = new HashMap<String,String>();map.put("key1","value1");map.put("key2","value2");map.put("key3","value3");map.put("key4","value4");jedis.hmset("hash",map);jedis.hset("hash", "key5", "value5");System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));//return Map<String,String>System.out.println("散列hash的所有键为:"+jedis.hkeys("hash"));//return Set<String>System.out.println("散列hash的所有值为:"+jedis.hvals("hash"));//return List<String>System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 6));System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));System.out.println("将key6保存的值加上一个整数,如果key6不存在则添加key6:"+jedis.hincrBy("hash", "key6", 3));System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));System.out.println("删除一个或者多个键值对:"+jedis.hdel("hash", "key2"));System.out.println("散列hash的所有键值对为:"+jedis.hgetAll("hash"));System.out.println("散列hash中键值对的个数:"+jedis.hlen("hash"));System.out.println("判断hash中是否存在key2:"+jedis.hexists("hash","key2"));System.out.println("判断hash中是否存在key3:"+jedis.hexists("hash","key3"));System.out.println("获取hash中的值:"+jedis.hmget("hash","key3"));System.out.println("获取hash中的值:"+jedis.hmget("hash","key3","key4"));}/*** 有序集合*/@Testpublic void testSortedSet() {Map<String,Double> map = new HashMap<String,Double>();map.put("key2",1.2);map.put("key3",4.0);map.put("key4",5.0);map.put("key5",0.2);// 将一个或多个 member 元素及其 score 值加入到有序集 key 当中,如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值// score 值可以是整数值或双精度浮点数System.out.println(jedis.zadd("zset", 3,"key1"));System.out.println(jedis.zadd("zset",map));System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));System.out.println("zset中的所有元素:"+jedis.zrangeWithScores("zset", 0, -1));System.out.println("zset中的所有元素:"+jedis.zrangeByScore("zset", 0,100));System.out.println("zset中的所有元素:"+jedis.zrangeByScoreWithScores("zset", 0,100));System.out.println("zset中key2的分值:"+jedis.zscore("zset", "key2"));System.out.println("zset中key2的排名:"+jedis.zrank("zset", "key2"));System.out.println("删除zset中的元素key3:"+jedis.zrem("zset", "key3"));System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));System.out.println("zset中元素的个数:"+jedis.zcard("zset"));System.out.println("zset中分值在1-4之间的元素的个数:"+jedis.zcount("zset", 1, 4));System.out.println("key2的分值加上5:"+jedis.zincrby("zset", 5, "key2"));System.out.println("key3的分值加上4:"+jedis.zincrby("zset", 4, "key3"));System.out.println("zset中的所有元素:"+jedis.zrange("zset", 0, -1));}/*** 排序*/@Testpublic void testSort() {jedis.lpush("collections", "ArrayList", "Vector", "Stack", "HashMap", "WeakHashMap", "LinkedHashMap");System.out.println("collections的内容:"+jedis.lrange("collections", 0, -1));SortingParams sortingParameters = new SortingParams();// 当数据集中保存的是字符串值时,你可以用 ALPHA,默认是升序System.out.println("alpha排序方式:" + jedis.sort("collections",sortingParameters.alpha()));System.out.println("===============================");jedis.lpush("sortedList", "3","6","2","0","7","4");System.out.println("sortedList排序前:"+jedis.lrange("sortedList", 0, -1));System.out.println("升序:"+jedis.sort("sortedList", sortingParameters.asc()));System.out.println("降序:"+jedis.sort("sortedList", sortingParameters.desc()));System.out.println("===============================");// 集群下不支持分割表排序/*jedis.lpush("userlist", "33");jedis.lpush("userlist", "22");jedis.lpush("userlist", "55");jedis.lpush("userlist", "11");jedis.hset("user:66", "name", "66");jedis.hset("user:55", "name", "55");jedis.hset("user:33", "name", "33");jedis.hset("user:22", "name", "79");jedis.hset("user:11", "name", "24");jedis.hset("user:11", "add", "beijing");jedis.hset("user:22", "add", "shanghai");jedis.hset("user:33", "add", "guangzhou");jedis.hset("user:55", "add", "chongqing");jedis.hset("user:66", "add", "xi'an");sortingParameters = new SortingParams();// 符号 "->" 用于分割哈希表的键名(key name)和索引域(hash field),格式为 "key->field"sortingParameters.get("user:*->name");sortingParameters.get("user:*->add");System.out.println(jedis.sort("userlist",sortingParameters));*/}
}

jedisCluster连接redis(集群)

jedisCluster连接redis(集群)

jedisCluster专门用来连接redis集群[ jedisCluster在单例存在的]

@Testpublic void testJedisCluster()throws Exception{//创建jedisCluster对象,有一个参数 nodes是Set类型,Set包含若干个HostAndPort对象Set<HostAndPort> nodes = new HashSet<>();nodes.add(new HostAndPort("192.168.241.133",7001));nodes.add(new HostAndPort("192.168.241.133",7002));nodes.add(new HostAndPort("192.168.241.133",7003));nodes.add(new HostAndPort("192.168.241.133",7004));nodes.add(new HostAndPort("192.168.241.133",7005));nodes.add(new HostAndPort("192.168.241.133",7006));JedisCluster jedisCluster = new JedisCluster(nodes);//使用jedisCluster操作redisjedisCluster.set("test", "my forst jedis");String str = jedisCluster.get("test");System.out.println(str);//关闭连接池jedisCluster.close();}

http://chatgpt.dhexx.cn/article/ous9oVoF.shtml

相关文章

Redis 详解

文章目录 Redisredis 简介1. Redis 中的事务1.1 什么是事务&#xff1f;1.2 Redis 中的事务是怎么实现的&#xff1f;1.3 Redis 事务为什么不支持回滚&#xff1f;1.3.1 语法错误情况下的事务回滚状态1.3.2 类型错误情况下的事务回滚状态1.3.3 Redis事务不回滚总结 2. Redis 中…

Redis该怎么学?其实很简单,这份学习路线+资料+书单我全部贡献出来了!

前言 这绝对不是一篇水文&#xff0c;进来的兄弟们千万不要白嫖&#xff0c;真香警告⚠️。&#xff08;点赞&#xff01;&#xff01;&#xff01;&#xff09; 这篇文章很早前就打算写了&#xff0c;特地留到现在是因为我想把Redis系列的文章全部更完&#xff0c;能让需要的…

Redis入门官方文档

Redis资料 Redis官网:http://redis.io/ Redis官方文档:http://redis.io/documentation Redis教程:http://www.w3cschool.cn/redis/redis-intro.html Redis下载:http://redis.io/download redis英文文档 https://redis.io/topics/data-types redis中文文档 http://www.red…

Redis内存数据库必读的4本书

Redis从一个不为人熟知、只有少量应用的崭新数据库&#xff0c;逐渐变成了内存数据库领域的事实标准。时至今日&#xff0c;经过大量的实践应用&#xff0c;Redis简洁高效、安全稳定的特性已经深入人心。 无论是国内还是国外&#xff0c;从五百强公司到小型初创公司都在使用Re…

【好书推荐】Redis入门必备 | 《Redis实战》

一、Redis入门必备 你好&#xff0c;我是小雨青年&#xff0c;一名程序员。 今天为你推荐的书籍是《Redis实战》。 Redis作为一个内存数据库服务器&#xff0c;本书提供了大量讲解和用例。 本书的大部分用例是用Python编写的&#xff0c;希望你有一些Python语言基础。 本书…

Day768.大佬推荐的经典的Redis学习资料 -Redis 核心技术与实战

大佬推荐的经典的Redis学习资料 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于经典的Redis学习资料。 一、经典书籍 在学习 Redis 时&#xff0c;最常见的需求有三个方面。 日常使用操作&#xff1a;比如常见命令和配置&#xff0c;集群搭建等&#xff1b;关键技…

qt QSettings

qt QSettings 用法总结 用户对应用程序经常有这样的要求&#xff1a;要求它能记住它的settings&#xff0c;比如窗口大小&#xff0c;位置&#xff0c;一些别的设置&#xff0c;还有一个经常用的&#xff0c;就是recent files&#xff0c;等等这些都可以通过Qsettings来实现。 …

QT中使用QSettings保存应用程序配置信息

一、问题描述 今天遇到一个问题&#xff0c;就是想实现一个软件&#xff0c;我上一次开启软件时候配置的IP地址以及&#xff0c;打开的文件路径&#xff0c;能够在我下一次打开软件时候自动写入上一次配置的信息&#xff0c;不需要再重新设置配置了。 二、解决方法 使用QT中的…

QSettings用法(例子)

用户通常打开程序&#xff0c;希望每次打开程序都能记住跨会话设置&#xff0c;比如&#xff08;窗口的大小位置&#xff0c;选项等&#xff09;; 窗口打开如果没有记住上次打开的记录&#xff0c;每次关闭它都会重复在同样位置同样大小&#xff0c;好比如手机格式化&#xff…

Qt使用QSettings读写ini文件

作者&#xff1a;billy 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 QSettings 简介 QSettings 类提供了ini文件读写&#xff0c;注册表读写的功能 QSettings::Format有两种: QSettings::NativeFormat 在…

QSettings读写ini文件

QSettings简介 QSettings类提供了持久的独立于平台的应用程序设置。 用户通常希望应用程序能够跨会话记住它的设置(窗口大小和位置&#xff0c;选项等)。在Windows上&#xff0c;这些信息通常存储在系统注册表中&#xff0c;在macOS和iOS上则存储在属性列表文件中。在Unix系统…

Qt中使用QSettings读取配置文件注意事项

QSetting使用中的注意事项 QSetting为Windows、Linux和MacOS系统提供了统一的配置文件/注册表读取方式。 &#xff08;引用QtHelp的一句话&#xff1a;The QSettings class provides persistent platform-independent application settings.&#xff09; 但是在读取配置文件时…

QT 程序参数保存-QSettings类

背景&#xff1a; 程序需要设置参数保存在本地&#xff0c;下次一次打开时参数为上次设置的值&#xff0c;不会被重新初始化。 QSettings类 实现步骤 1 新建paraConfig类 1.1 paraConfig方法中传入文件名参数fileName 1.2 在程序目录下新建一个CFG文件夹&#xff0c;&…

QT中QSettings的使用

首先包含头文件 #include<QSettings>QSettings的使用 QSettings m_iniFile("test.ini", QSettings::IniFormat);m_iniFile.beginGroup("USERCONFIG");m_iniFile.setValue("userName", ui->Edt_user->text());m_iniFile.setValue(&q…

Qt如何用QSettings读写ini配置文件

一、ini文件的格式 ini文件的格式如下&#xff0c;主要是由节、键、值来组成。 [section1] age23 namlcm sexman[section2] age75 namelucimer sexman……………… [sectionN] // 节名 键 值 ……………… 键 值二、ini配置文件的写入 ini文件的读取和写入都是…

QSettings Class

QSettings类 QSettings类公共类型&#xff08;枚举&#xff09;公有成员函数静态成员函数函数作用这个类写文件的特征 QSettings类 QSettings类提供持久的独立于平台的应用程序设置。 头文件:#include< QSettings >qmake:QT core继承&#xff08;父&#xff09;:QObje…

Qt读写配置文件之QSettings的用法

主要是在开发中&#xff0c;将一些关键的东西写入或者读取配置文件中&#xff0c;本文主要就是使用了QSettings的setValue和value。 QSettings可以存储一系列设置。每个设置包括指定设置名称&#xff08;键&#xff09;的一个字符串和一个与该键关联的QVariant存储数据。使用se…

Qt之QSettings的使用

一.前言 软件开发中&#xff0c;有这样的需求&#xff1a;用户需要动态配置参数加载到软件界面&#xff0c;以及保存软件的一些状态值&#xff0c;这就需要将数据保存下来然后读取解析,针对这样的需求&#xff0c;可以有两种方式&#xff0c;一种就是自己定义数据格式&#xff…

QSettings 用法总结(很好用)

QSettings 用法总结&#xff08;很好用&#xff09; 用户对应用程序经常有这样的要求&#xff1a;要求它能记住它的settings&#xff0c;比如窗口大小&#xff0c;位置&#xff0c;一些别的设置&#xff0c;还有一个经常用的&#xff0c;就是recent files&#xff0c;等等这些都…