当前位置: 首页 > news >正文

Redis详解--基本篇

本篇将介绍:

  • 我们为什么要使用NoSql数据库,
  • NoSql数据库的基本概述,
  • Redis的简单信息,
  • Redis常用的数据类型操作,
  • Redis的配置文件,
  • 关于Redis简单的发布及订阅。

    我们为什么要使用NoSql数据库?

        当我们开发一个项目,我们除了要掌握功能性、扩展性的技术,我们还要掌握解决性能问题的技术——这项需求方案,目的旨在:

解决项目开发完成之后,受众用户可能会有很多,随着用户量的不断增加的场景。

Nginx负载均衡和Session问题

Nginx负载均衡

        随着用户访问量的不断增加,原本一对一的服务模式不复存在。

        就像一家奶茶店,抠门的店长只请了一个服务员,在工作日有十个客户排队的话服务员还能应付,但是在休息日,店里涌入了100个客户,这时店员就会手忙脚乱,这时服务员可能会出错,效率降低,在节假日,店里挤了1000个客户,这时候服务员就会直接崩溃,客户将全部跑光。

        当抠门的店长发现生意越来越好,客户对服务员的数量需求越来越大的时候,店长决定多请几个服务员了,为了管理这些服务员,店长还请了领班,领班不管奶茶口味,也不招人,他的工作只是公平分配任务给服务员,高峰期让服务员全部到岗,低峰期让一些服务员放假,只保留基本的服务员框架。

        这件事换到计算机场景,就是所谓的突破性能瓶颈,单台服务器的 CPU、内存、带宽、磁盘 I/O 都有上限。请求一旦超过这个上限,系统响应时间飙升,甚至直接宕机。为解决这个问题,我们采用 Nginx 负载均衡,把“一位店员”扩展成“多位店员”,通过Nginx进行管理。

  1. 横向扩容:把同样的业务代码部署到多台服务器,形成“店员团队”。
  2. 统一入口:所有用户请求先到 Nginx,再由它按策略分发给不同的服务器。
  3. 动态伸缩高峰期多开几台服务器,低谷期关掉几台,节省成本。
  4. 高可用:某台服务器挂了,Nginx 自动把它踢出队列,用户无感知。

Session问题

        继续刚才的例子:

        当一个顾客第一次到店时,领班给他办一张实体会员卡,并把卡号、积分等信息只存在1号店员的收银机里。

        顾客第二次来,被领班领到 3 号店员——3 号店员收银机里没有这张卡的记录,只能让顾客重新办卡、积分清零。

这就是 Session 问题:卡(Session)只在某一台收银机(服务器)里,换台机器就认不出来。

这件事换到计算机场景,就是所谓的Session问题:

        当Nginx负载均衡将用户的访问压力分摊到不同的web服务器上时,每台服务器各自存一份 Session,如果用户前后的请求被Nginx分到不同服务器,就会读到两份不一样的 Session——数据瞬间清零,这就是 Session 问题。

        解决办法就是将用户第一次访问的信息发送给nosql(缓存数据库),然后实现所有web服务器对它的共享

按刚才的例子来说,就是把会员卡信息放到所有店员都能查到的总机(Redis);
无论顾客被分到哪位店员,只要报出卡号,店员都能瞬间查到同一份资料,会员卡永远有效。

IO压力

        继续上面的例子,解决了session问题,我们发现各台 Web 服务器虽然都能共享同一份 session 数据,但是效率还是很慢的,因为项目数据的日积月累,数据库里面的数据也越来越庞大,每一次的用户访问行为,Web服务器都要去缓存数据库查找一次,当用户访问量的增大,这消耗了大量的资源,这就是IO压力,针对这个问题,我们会把频繁查询的数据放入缓存,以后我们从缓存中取出数据即可,减少对数据库的访问压力。

        如果还没明白的话,我依然搬出那个奶茶店的例子,一看就懂。

        就像在服务员在听到进店的顾客报出他的会员卡号的时候,他就去翻一本厚厚的会员登录账本,顾客每一次刷卡,服务员都要去账本找,随着顾客和顾客的需求越来越多,店长发现了这个问题,他想了一个办法,解决了IO压力,

        他把“最常被查的会员资料”先影印一份,贴在收银台前面的小黑板(缓存 Redis)上。以后顾客有要刷卡点单之类的需求,服务员先看小黑板,黑板上记录着积分和余额之类的数据(session),非常方便不需要再翻账本,只有黑板上没有的资料,才会去翻账本,翻完会补写到黑板,以供下一次方便取用。此时,IO压力骤降。

NoSql数据库的基本概述

什么是nosql数据库

        NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。 NoSQL 不依赖业务 逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。

nosql数据库有以下特点:(牺牲严格规矩,换来极限速度)

不遵循SQL标准。 不支持ACID(原子性,一致性,隔离性,持久性)。 远超于SQL的性能。

nosql的适用场景有:

对数据高并发的读写。 海量数据的读写。 对数据高可扩展性的。

但是,nosql的不适用场景如下:

需要事务支持。

基于sql的结构化查询存储,处理复杂的关系,需要sql查询。

常见的NoSql数据库

        以下的特点分三方面来讲:数据放哪和持久化策略、数据结构、典型用途

Memcached

  • 纯内存不持久化
  • 简单 key-value 缓存
  • 典型用途:给持久化数据库做高速缓存

Redis

  • 内存为主支持 RDB/AOF 持久化
  • key-value + 列表、集合、哈希、有序集合等数据结构
  • 典型用途:缓存、计数器、消息队列、分布式锁等

MongoDB

  • 文档型(类 JSON/BSON),schema-free
  • 内存+磁盘混合存储,自动落盘
  • 典型用途:内容管理、用户资料、日志、商品目录

我们可以通过此链接查询最新的数据库排名:http:// https://db-engines.com/en/ranking

Redis的简单信息

        Redis的全称为 Remote Dictionary Server,意为:远程字典服务。

类型为:开源 key-value 存储系统

支持的数据类型有:String、list、set、zset、hash

特点为:

所有类型支持原子性读写集合运算排序等操作
数据常驻内存,保证高吞吐
周期性 RDB 快照或追加 AOF 日志实现持久化
支持主从复制(master-slave)

Redis的应用场景

应用场景核心数据结构实现思路与说明
获取最新数据List通过List实现按自然时间排序的数据
排行榜Zset (有序集合)使用 ZADD 添加成员及其分数(Score),自动排序。通过 ZRANGE 等命令获取排名。
时效性数据(所有类型)使用 EXPIRE为任何类型的 Key 设置过期时间,到期自动删除,适用于验证码、会话等。
计数器/秒杀String使用 INCR/DECR 等原子性操作命令,在高并发下安全地实现计数、库存扣减等。
去重Set (集合)利用 Set 内元素唯一的特性,使用 SADD 添加元素,天然实现去重。常用于过滤重复内容。
构建队列List使用 LPUSH 生产消息,RPOP 或 BRPOP 消费消息,实现简单的先进先出(FIFO)队列。
发布订阅Pub/Sub使用 SUBSCRIBE 订阅频道,PUBLISH 向频道发布消息,实现一对多的实时消息广播。

Redis的默认端口号为什么是6379?有兴趣可以参考下面这篇博文

https://blog.csdn.net/corleone_4ever/article/details/104120007

Redis安装在Linux操作系统

        为什么Redis要安装在Linux操作系统上?因为Linux 本身就是 Redis 的开发、测试和默认部署环境,官方只给它打 Linux 版安装包和性能优化补丁;换别的系统,要么得自己编译、要么缺内核特性(如 epoll),速度也慢一截。

这里省略Redis的安装流程,默认的安装目录在/usr/local/bin

[root@noy001 redis-6.2.1]# cd /usr/local/bin
[root@noy001 bin]# ll
总用量 18844
-rwxr-xr-x. 1 root root 4833352 10月  1 18:13 redis-benchmark
lrwxrwxrwx. 1 root root      12 10月  1 18:13 redis-check-aof -> redis-server
lrwxrwxrwx. 1 root root      12 10月  1 18:13 redis-check-rdb -> redis-server
-rwxr-xr-x. 1 root root 5003368 10月  1 18:13 redis-cli
lrwxrwxrwx. 1 root root      12 10月  1 18:13 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 9450208 10月  1 18:13 redis-server

了解这几个文件的用途:

redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何

redis-check-aof:修复有问题的AOF文件,rdb和aof后面再讲

redis-check-dump:修复有问题的dump.rdb文件

redis-sentinel:Redis集群使用

redis-server:Redis服务器启动命令

redis-cli:客户端,操作入口

Redis启动方案

Redis前台启动(不推荐)

        

        前台启动,命令行窗口不能关闭,否则服务器停止。

[root@noy001 bin]# redis-server

        将出现以下界面:

Redis前台启动(推荐)

[root@noy001 bin]# cd /opt/redis-6.2.1/
[root@noy001 bin]# cp redis.conf /usr/local/bin
[root@noy001 bin]# cd /usr/local/bin
[root@noy001 bin]# ll

这时bin目录就会有redis.conf文件了

编辑此文件,将后台启动设置daemonize no改成yes

 [root@noy001 bin]# vim redis.conf

接下来启动redis

[root@noy001 bin]# redis-server redis.conf
[root@noy001 bin]# ps -ef | grep redis  # 查看redis的服务进程
root      7616     1  0 18:32 ?        00:00:00 redis-server 127.0.0.1:6379
root      7631  2427  0 18:32 pts/0    00:00:00 grep --color=auto redis

使用客户端连接redis  并且ping一下看看连上没有

[root@noy001 bin]# redis-cli  # 使用redis自带客户端连接redis
127.0.0.1:6379> ping         # 查看是否ping通redis服务器
PONG

关闭redis服务

[root@noy001 bin]# redis-cli shutdown  # 关闭redis服务
[root@noy001 bin]# ps -ef | grep redis
root      7667  2427  0 18:37 pts/0    00:00:00 grep --color=auto redis

如果reids是多实例,,也可以指定端口关闭:

[root@noy001 bin]# redis-cli -p 6379 shutdown

在防火墙开放redis端口号6379的远程访问权限,这样就可以与外部的应用进行连接

[root@noy001 bin]# firewall-cmd --permanent --add-port=6379/tcp
success
[root@noy001 bin]# firewall-cmd --reload
success
[root@noy001 bin]# firewall-cmd --list-ports
3306/tcp 8080/tcp 6379/tcp

redis常用数据类型操作

redis基于key(键)的操作

127.0.0.1:6379> keys *            # 查看当前数据库中所有的key值
(empty array)
127.0.0.1:6379> set k1 eric       # 设置key值 值是string类型
OK
127.0.0.1:6379> set k2 james
OK
127.0.0.1:6379> set k3 kobe
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> type k2           # 查看指定key的数据类型
string
127.0.0.1:6379> exists k1         # 判断指定的key是否存在 存在就为1 不存在就为0
(integer) 1
127.0.0.1:6379> exists k4
(integer) 0
127.0.0.1:6379> del k3            # 删除指定的key值
(integer) 1
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
127.0.0.1:6379> expire k1 10      # 给key设置过期时间
(integer) 1
127.0.0.1:6379> ttl k1            # 查看指定key值的存活时期 不存在就为负数
(integer) 6
127.0.0.1:6379> ttl k1
(integer) -2
127.0.0.1:6379> select 0          # 切换到指定的数据库
OK
127.0.0.1:6379> dbsize            # 查看当前数据库的key的数量
(integer) 1
127.0.0.1:6379> flushdb           # 清空当前数据库
OK
127.0.0.1:6379> flushall          # 清空所有的数据库
OK

redis中string的操作

        Redis 的 String 类型是一种 key-value 结构,是Redis最基本的类型,一个 key 映射一个 value,value 可以是任何二进制数据,最大容量 512 MB,且完全二进制安全。

127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set k1 eric        # 给key设置String类型的值
OK
127.0.0.1:6379> set k2 kobe
OK
127.0.0.1:6379> get k1             # 根据key获取对象的value值
"eric"
127.0.0.1:6379> get k2
"kobe"
127.0.0.1:6379> set k1 james       # 如果给一个已经存在的key设置值就是覆盖原来的值
OK
127.0.0.1:6379> get k1
"james"
127.0.0.1:6379> append k1 123      # 在指定key的值后面追加内容
(integer) 8                        # 追加之后字符串的长度
127.0.0.1:6379> get k1
"james123"
127.0.0.1:6379> strlen k1          # 获取key的长度
(integer) 8
127.0.0.1:6379> setnx k1 miller    # 当key不存在时value才会设置成功,否则设置不成功
(integer) 0                        # 0表示没有设置成功
127.0.0.1:6379> get k1             # k1的值没有发生变化,因为key已经存在
"james123"
127.0.0.1:6379> setnx k3 curry     # k3的值设置成功,因为之前不存在key3这个键
(integer) 1                        # 1表示设置成功
127.0.0.1:6379> get k3
"curry"
127.0.0.1:6379> set num1 100
OK
127.0.0.1:6379> incr num1          # incr命令操作的值必须是数字类型;incr:对key自增1
(integer) 101
127.0.0.1:6379> get num1
"101"
127.0.0.1:6379> decr num1          # 对key进行自减操作
(integer) 100
127.0.0.1:6379> get num1
"100"
127.0.0.1:6379> incrby num1 5      # 对key进行增加,指定增加5
(integer) 105
127.0.0.1:6379> decrby num1 10     # 对key进行递减,指定减去10
(integer) 95127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3      # 批量设置k-v
OK
127.0.0.1:6379> mget k1 k2 k3               # 批量获取值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v11 k5 v5         # 只有所有的key都不存在才会设置,任意一个key存在都不会设置值
(integer) 0
127.0.0.1:6379> msetnx k4 v4 k5 v5
(integer) 1
127.0.0.1:6379> mget k4 k5
1) "v4"
2) "v5"
127.0.0.1:6379> set name LebronJames
OK
127.0.0.1:6379> getrange name 0 6           # 根据指定区间获取字符串的值 索引值从0开始
"LebronJ"
127.0.0.1:6379> getrange name 0 5
"Lebron"
127.0.0.1:6379> setrange name 0 kobe        # 从指定索引值开始替换值
(integer) 11
127.0.0.1:6379> get name
"kobeonJames"
127.0.0.1:6379> setex k6 20 v6              # 设置key的值的同时,也设置过期时间
OK
127.0.0.1:6379> ttl k6
(integer) 16
127.0.0.1:6379> get name
"kobeonJames"
127.0.0.1:6379> getset name green           # 使用新值替换旧值,但是返回旧值
"kobeonJames"
127.0.0.1:6379> get name
"green"

        需要注意的是:incr命令是原子操作。也就是指不会被线程调度机制打断的操作。这种操作一旦开始,就一直运行到结束,中间不会有任何线程上下文切换。

在单线程中, 能够在单条指令中完成的操作都可以认为是"原子操作"。

而,Java中的i++不属于原子性操作

因为Java中的自增经历了以下三步:

  1. 第一步:取值 i = 0;
  2. 第二步:自加操作 i++;
  3. 第三步:将自加的操作结果赋予给i。

这里有一个经典的问题:

i = 0,两个线程对i分别进行i++100次。值是多少?(2-200)

最好的情况下两个线程互不打扰,一个线程结束另一个线程继续对i的操作,最后返回的值是200

而最坏的情况下,线程A和B几乎总是同时读到了相同的值

它们都读到 i=0,都算出 1,都写回 1两次操作只加了1次

它们又都读到 i=1,都算出 2,都写回 2又两次操作只加了1次

... 如此重复。

        因为 i++ 不是一步到位的操作,它被拆成了“读、改、写”三步。两个线程的这三步会随机交织在一起,导致某些增加操作被覆盖和丢失。

redis中list的操作

        List列表是单键多值的列表。Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素 到列表的头部(左边)或者尾部(右边)。它的底层实际是个双向链表,对两端的操作性能很高,但是 通过索引下标操作中间的节点性能会较差。

127.0.0.1:6379> lpush k1 v1 v2 v3      # 从列表的左边追加元素 键是k1 值是v1 v2 v3
(integer) 3
127.0.0.1:6379> lrange k1 0 -1       # 查询列表中的所有元素
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> rpush k1 v4 v5 v6    # 从列表的右边追加元素 键是k1 值是v4 v5 v6
(integer) 6
127.0.0.1:6379> lrange k1 0 -1
1) "v3"
2) "v2"
3) "v1"
4) "v4"
5) "v5"
6) "v6"
127.0.0.1:6379> lpop k1              # 从左边弹出一个值并返回
"v3"
127.0.0.1:6379> rpop k1              # 从右边弹出一个值并返回
"v6"
127.0.0.1:6379> lpush k2 s1 s2 s3    # 创建k2列表 并追加值
(integer) 3
127.0.0.1:6379> lrange k2 0 -1
1) "s3"
2) "s2"
3) "s1"
127.0.0.1:6379> lrange k1 0 -1
1) "v2"
2) "v1"
3) "v4"
4) "v5"
127.0.0.1:6379> rpoplpush k1 k2     # 将k1列表最右边的值取出来,追加到k2列表的最左边
"v5"
127.0.0.1:6379> lrange k1 0 -1     # k1最右边的元素没有了
1) "v2"
2) "v1"
3) "v4"
127.0.0.1:6379> lrange k2 0 -1     # 查看k2集合,k2元素的最左边追加了元素v5
1) "v5"
2) "s3"
3) "s2"
4) "s1"
127.0.0.1:6379> lindex k2 2        # 根据指定下标获取元素
"s2"
127.0.0.1:6379> llen k2            # 获取指定列表的长度
(integer) 4
127.0.0.1:6379> linsert k2 before s2 v2  # 在k2列表中的s2元素前面追加元素v2
(integer) 5
127.0.0.1:6379> lrange k2 0 -1
1) "v5"
2) "s3"
3) "v2"
4) "s2"
5) "s1"
127.0.0.1:6379> lrem k2 1 s2       # 从左边删除指定个数的元素s2
(integer) 1
127.0.0.1:6379> lrange k2 0 -1
1) "v5"
2) "s3"
3) "v2"
4) "s1"
127.0.0.1:6379> lset k2 2 s2       # 将指定索引位置上的元素替换(将k2列表中2号索引位置上的元素替换成s2)
OK
127.0.0.1:6379> lrange k2 0 -1
1) "v5"
2) "s3"
3) "s2"
4) "s1"

list的数据结构

        Redis 的 List 在元素少时一整块连续内存的 ziplist 紧凑存放

        元素多时切成若干段 ziplist,再用双向指针把这些段串成 quicklist,既省掉普通链表的每个节点 16 B prev/next 指针开销,又保留 O(1) 的插入删除效率。

redis中的set操作

        Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动去重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,

并且set提供了判断某个成员是否在一个set集合内的命令,这个也是list所不能提供的。 Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是 O(1)。(随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变)

127.0.0.1:6379> sadd s1 v1 v2 v3          # 向s1中添加元素 v1 v2 v3
(integer) 3
127.0.0.1:6379> smembers s1               # 查询集合中的所有元素
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> sadd s1 v3                # 添加重复元素,未成功
(integer) 0
127.0.0.1:6379> smembers s1
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> sismember s1 v1           # 判断集合是否存在某元素:存在返回1,不存在返回0
(integer) 1
127.0.0.1:6379> sismember s1 v7
(integer) 0
127.0.0.1:6379> scard s1                  # 返回集合中元素的个数
(integer) 3
127.0.0.1:6379> srem s1 v1 v2             # 删除集合中指定的元素
(integer) 2
127.0.0.1:6379> smembers s1
1) "v3"# 以下演示需要 s1 先恢复为 v2 v3 v5,假设已恢复
127.0.0.1:6379> spop s1                   # 随机弹出并移除集合中的一个元素
"v5"
127.0.0.1:6379> smembers s1
1) "v2"
2) "v3"# 再次恢复 s1 为 v2 v3 v5
127.0.0.1:6379> srandmember s1 2          # 随机查看2个元素,不移除
1) "v2"
2) "v5"
127.0.0.1:6379> smembers s1
1) "v2"
2) "v3"
3) "v5"127.0.0.1:6379> smove s1 s2 v5            # 将s1中的v5移动到s2
(integer) 1
127.0.0.1:6379> smembers s1
1) "v2"
2) "v3"
127.0.0.1:6379> smembers s2
1) "v1"
2) "v2"
3) "v3"
4) "v5"127.0.0.1:6379> sinter s1 s2              # 取s1与s2的交集
1) "v2"
2) "v3"
127.0.0.1:6379> sunion s1 s2              # 取s1与s2的并集
1) "v1"
2) "v2"
3) "v3"
4) "v5"
127.0.0.1:6379> sdiff s2 s1               # 取s2相对于s1的差集
1) "v1"
2) "v5"

set的数据结构

Set数据结构是dict字典,字典是用哈希表实现的。

Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

redis中的hash操作

        Redis hash 是一个键值对集合。Redis hash是一个string类型的field和value的映射表,hash特别适合 用于存储对象。类似Java里面的Map。

根据这个需求做出解决方案:

需求:在redis里面保存一个用户对象( {id:1001,username:"eric",age=23} )。

方案1: 将user对象转换成一个json字符串,使用string来存储这个json字符串。

但是缺点是每次修改这个对象里面的数据,都需要把这个字符串转换成java对象修改,修改之后再转换成json字符串,比较麻烦,不适合频繁修改数据的场景。

方案2:通过key和对象属性拼接的方式存储数据:

 key              value    
user:id           1001
user:username     eric
user:age          23

缺点:存储的数据分散,如果对象的属性过多,在redis中的key也会非常的多

方案3:通过redis中的hash类型的数据保存

        通过 key(用户ID) + field(属性标签) 就可以操作对应属性数据了,既不需要重复存储数据,也不会带来序列化和并发修改控制的问题。

hash的常用命令

127.0.0.1:6379> hset user id 1001                # 向hash中存储数据:key=user, field=id, value=1001
(integer) 1
127.0.0.1:6379> hget user id                    # 获取指定field的值
"1001"
127.0.0.1:6379> hmset user username eric age 23 # 批量添加field-value
OK
127.0.0.1:6379> hmget user username age         # 批量获取field对应的value
1) "eric"
2) "23"
127.0.0.1:6379> hexists user username           # 判断field是否存在:存在返回1,不存在返回0
(integer) 1
127.0.0.1:6379> hkeys user                      # 获取所有field
1) "id"
2) "username"
3) "age"
127.0.0.1:6379> hvals user                      # 获取所有value
1) "1001"
2) "eric"
3) "23"
127.0.0.1:6379> hdel user age                   # 删除指定field及其value
(integer) 1
127.0.0.1:6379> hget user age
(nil)
127.0.0.1:6379> hincrby user id 1               # 对field的值自增1
(integer) 1002
127.0.0.1:6379> hincrby user id -1              # 对field的值自减1
(integer) 1001
127.0.0.1:6379> hsetnx user age 25              # field不存在时设置成功,存在则失败
(integer) 1
127.0.0.1:6379> hsetnx user username james
(integer) 0
127.0.0.1:6379> hget user age
"25"
127.0.0.1:6379> hget user username
"eric"

hash的数据结构

        Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。

redis中 的zset操作

        Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集 合中的成员。集合的成员是唯一的,但是评分可以是重复了。

        因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

127.0.0.1:6379> zadd user 99 kobe 88 durant 82 curry   # 添加元素到zset集合中
(integer) 3
127.0.0.1:6379> zrange user 0 -1                     # 升序查看所有成员
1) "curry"
2) "durant"
3) "kobe"
127.0.0.1:6379> zrange user 0 -1 withscores          # 升序查看所有成员及其分数
1) "curry"
2) "82"
3) "durant"
4) "88"
5) "kobe"
6) "99"
127.0.0.1:6379> zrangebyscore user 80 89             # 升序查看 80-89 分区间成员
1) "curry"
2) "durant"
127.0.0.1:6379> zrangebyscore user 80 89 withscores  # 同上,含分数
1) "curry"
2) "82"
3) "durant"
4) "88"
127.0.0.1:6379> zrevrangebyscore user 100 1          # 降序查看所有成员
1) "kobe"
2) "durant"
3) "curry"
127.0.0.1:6379> zincrby user 12 kobe                 # 给 kobe 增加 12 分
"111"
127.0.0.1:6379> zrange user 0 -1 withscores
1) "curry"
2) "82"
3) "durant"
4) "88"
5) "kobe"
6) "111"
127.0.0.1:6379> zrem user kobe                       # 删除 kobe
(integer) 1
127.0.0.1:6379> zrange user 0 -1
1) "curry"
2) "durant"
127.0.0.1:6379> zcount user 0 100                    # 统计 0-100 分区间的成员数
(integer) 2
127.0.0.1:6379> zrank user durant                    # 查看 durant 的排名(从 0 开始)
(integer) 1
127.0.0.1:6379> zrank user curry                     # 查看 curry 的排名
(integer) 0

zset的数据结构

zset底层使用了两个数据结构:

(1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素 value找到相应的score值。

(2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

跳跃表是什么?

        有序集合在生活中比较常见,例如根据成绩对学生排名,根据得分对玩家排名等。对于有序集合的底层实现,可以用数组、平衡树、链表等。数组不便元素的插入、删除;平衡树或红黑树虽然效率高但结构复杂;链表查询需要遍历所有效率低。Redis采用的是跳跃表。跳跃表效率堪比红黑树,实现远比红黑树简单。

        跳跃表像多层楼梯最底层把所有节点排好序;上层隔几根柱子建“快速通道”,查询时先走高层大步跳遇到比目标大的节点就降到下一层继续细找,直到最底层锁定位置,增删节点时同步维护这些层即可。

redis配置文件

        redis.conf 就是 Redis 的启动脚本:一行一个指令,告诉它监听哪个端口、能占多大内存、多久把数据落盘、设什么密码,改完重启或 CONFIG REWRITE 就生效。

这里我的建议是尽量不要管它,有什么要改的多看看说明或者博客。

redis的发布和订阅

        Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。 Redis 客户端可以订阅任意数量的频道。

完成redis的发布和订阅需要两个步骤:

客户端(订阅者)需要订阅频道。

当发送者(生产者)给这个频道发布消息后,消息就会发送给订阅的客户端。

具体实现

        打开一个客户端1订阅channel1

[root@noy001 bin]# redis-cli
127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1

        打开客户端2,发送消息hello

[root@noy001 bin]# redis-cli
127.0.0.1:6379> publish channel1 hello
(integer) 1

        再次查看客户端1

http://www.dtcms.com/a/348816.html

相关文章:

  • Linux 的 TCP 网络编程常用API
  • 网络流量分析——使用捕获和显示过滤器查询网络流量
  • 每天自动备份oracle
  • 关于熵减 - 力学单位和来源
  • 安装gitlab
  • C++ AOV 拓扑排序
  • pyecharts可视化图表-scatter:从入门到精通
  • 2020/12 JLPT听力原文 问题二 5番
  • 【网络运维】Shell 脚本编程:case 条件语句
  • 【大语言模型 18】Vision Transformer革命解析:图像理解的范式突破与架构创新
  • VsCode使用SFTP连接Linux
  • 油雾干扰下误报率↓76%!陌讯动态感知算法在卸油作业安全识别中的实战突破
  • Java:HashSet的使用
  • 【MySQL】CRUD基础详解
  • 基于 Redis + JWT 的跨系统身份共享方案
  • HTTP数据之旅:一个网络请求的完整冒险
  • Unity的Cursor.lockState
  • 油雾环境下漏检率↓79%!陌讯多模态检测算法在加油站智能巡检的落地实践
  • VMware Workstation 不可恢复错误:(vcpu-0)
  • 强反射场景漏检率↓89%!陌讯动态感知算法在护目镜合规检测的实战解析
  • 二叉树学习笔记
  • MyBatis 和 MyBatis-Plus对比
  • 【GEE+Python 实战】用 Sentinel-2 监测 2024 年研究区 NDVI 变化(附完整源码与避坑指南)
  • 深入解析十大经典排序算法原理与实现
  • 理想汽车智驾方案介绍 2|MindVLA 方案详解
  • Java 编译器的世界:前端、JIT 与 AOT 的秘密:详解 Java 的编译过程与编译器生态
  • 秦始皇在位时的重要贡献
  • 室联人形机器人:家政服务任务结构化、技术要点、深入应用FPGA的控制系统框架设计(整合版A)
  • Redis 启动的三种方式:从基础到实战配置指南
  • WSL-linux部署IndexTTS 记录(含本地 CUDA/cuDNN 编译依赖说明)