当前位置 : 主页 > 编程语言 > java >

Redis数据结构实战演练,看看微博、微信、购物车、抽奖小程序是如何使用的?

来源:互联网 收集:自由互联 发布时间:2023-02-04
大家都知道,现在只要是个系统就会有缓存的存在,而且几乎所有的系统都离不开Redis,可见Redis在现在系统的重要性。 所以,今天我们就来聊一下Redis,当然主要聊聊Redis在不同业务场

大家都知道,现在只要是个系统就会有缓存的存在,而且几乎所有的系统都离不开Redis,可见Redis在现在系统的重要性。

所以,今天我们就来聊一下Redis,当然主要聊聊Redis在不同业务场景下的使用。

接下来,我们先从缓存的世界开始,一步步揭开Redis的神秘面纱。

1. 缓存发展史&缓存分类

1.1 大型网站中缓存的使用

file 访问量越大,响应力越差,用户体验越差

引入缓存、示意图如下:

file

高性能 :

假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。

这样有什么好处呢? 那就是保证用户下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。

不过,要保持数据库和缓存中的数据的一致性。 如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!

高并发:

一般像 MySQL 这类的数据库的 QPS 大概都在 1w 左右(4 核 8g) ,但是使用 Redis 缓存之后很容易达到 10w+,甚至最高能达到 30w+(就单机 redis 的情况,redis 集群的话会更高)。

QPS(Query Per Second):服务器每秒可以执行的查询次数;

所以,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高的系统整体的并发。

1.2 常见缓存的分类

分布式缓存

分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用的信息。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。

具有缓存功能的中间件:Redis、Memcache、Tair(阿里 、美团)等等

1.3 分布式缓存选型方案对比

Memcache和Redis区别

共同点 :

  • 都是基于内存的数据库,一般都用来当做缓存使用。
  • 都有过期策略。
  • 两者的性能都非常高。
  • 区别 :

  • Redis 支持更丰富的数据类型(支持更复杂的应用场景)。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
  • Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。
  • Redis 有灾难恢复机制。 因为可以把缓存中的数据持久化到磁盘上。
  • Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 Redis 目前是原生支持 cluster 模式的.
  • Memcached 是多线程,非阻塞 IO 复用的网络模型;Redis 使用单线程的多路 IO 复用模型。 (Redis 6.0 引入了多线程 IO )
  • 相信看了上面的对比之后,我们已经没有什么理由可以选择使用 Memcached 来作为自己项目的分布式缓存了。

    2. Redis概述&安装配置

    2.1 概述

    官网:https://redis.io

    中文官网地址:http://www.redis.cn

    file 简单来说 Redis 就是一个使用 C 语言开发的数据库,不过与传统数据库不同的是 Redis 的数据是存在内存中的 ,也就是它是内存数据库,所以读写速度非常快,因此 Redis 被广泛应用于缓存方向。

    另外,Redis 除了做缓存之外,Redis 也经常用来做分布式锁,甚至是消息队列。

    Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。

    Redis应用场景

    • 缓存使用,减轻DB压力
    • DB使用,用于临时存储数据(字典表,购买记录)
    • 解决分布式场景下Session分离问题(登录信息)
    • 任务队列(秒杀、抢红包等等) 乐观锁
    • 应用排行榜 zset
    • 签到 bitmap
    • 分布式锁
    • 冷热数据交换

    2.3 安装&配置

    官网:https://redis.io/download

    file Redis没有官方的windows版本,所以建议在linux系统上去运行

    选择下载稳定版本、不稳定版本可以尝鲜、但是不推荐在生产环境中使用

    安装

    第一步:安装 C 语言需要的 GCC 环境

    yum install -y gcc-c++ yum install -y wget

    第二步:下载并解压缩 Redis 源码压缩包

    # 下载 wget https://download.redis.io/releases/redis-6.2.4.tar.gz mkdir /usr/local/redis tar -zxvf redis-6.2.4.tar.gz -C /usr/local/redis

    file

    第三步:编译 Redis 源码,进入 redis-6.2.4 目录,执行编译命令,进行安装

    cd /usr/local/redis/redis-6.2.4/src make && make install

    执行完毕后安装成功!

    启动

    前端启动
    • 启动命令: redis-server ,直接运行 bin/redis-server 将以前端模式启动
    • 关闭命令: ctrl+c
    • 启动缺点:客户端窗口关闭则 redis-server 程序结束,不推荐使用此方法
    • 启动图例:

    file

    后端启动(守护进程启动)
    • 第一步:拷贝 redis-6.2.4/redis.conf 配置文件到 Redis 安装目录的 bin 目录
    cp redis.conf /usr/local/redis
    • 第二步:修改 redis.conf
    vim redis.conf
    • 第三步:修改 redis.conf

    (1)修改daemonize no ---> daemonize yes,目的是为了让redis启动在linux后台运行

    file (2)修改redis的工作目录:(名称随意)

    file

    • 第四步:启动服务
    .redis-server redis.conf

    file

    查看进程

    file

    • 后端启动的关闭方式
    .redis-cli shutdown

    命令说明

    redis-server :启动 redis 服务

    redis-cli :进入 redis 命令客户端

    redis-benchmark : 性能测试的工具

    redis-check-aof : aof 文件进行检查的工具

    redis-check-dump : rdb 文件进行检查的工具

    redis-sentinel : 启动哨兵监控服务

    Redis命令行客户端

    • 命令格式
    .redis-cli -h 127.0.0.1 -p 6379
    • 参数说明
    -h:redis服务器的ip地址 -p:redis实例的端口号
    • 默认方式:如果不指定主机和端口也可以 默认主机地址是127.0.0.1 默认端口是6379
    .redis-cli

    2.4 ui

    命令行已经足够强大,尤其是高版本,强大到怀疑人生

    但是!它并不友好,业界有很多ui可供使用,典型的:Another Redis Desktop Manager

    1)开源

    源码地址:https://gitee.com/qishibo/AnotherRedisDesktopManager

    编译包下载:https://github.com/qishibo/AnotherRedisDesktopManager/releases

    2)支持多平台

    Windows

    Linux

    Mac

    3)基本使用

    创建连接:

    file

    主页监控:

    file

    基本操作:

    file

    命令行:

    file

    3. 数据类型选择&应用场景

    file

    Redis的Key的设计

    1、key名设计

    可读性和可管理性

    以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如 业务名:表名:id

    file 简洁性

    保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,例如:

    file 不要包含特殊字符

    反例:包含空格、换行、单双引号以及其他转义字符

    string字符串类型

  • 介绍 :string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的,但是 Redis 并没有使用 C 的字符串表示,而是自己构建了一种 简单动态字符串(simple dynamic string,SDS)。相比于 C 的原生字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
  • 常用命令: set,get,strlen,exists,decr,incr,setex 等等。
  • 应用场景 :一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等等。
    • 单值缓存

      SET <font color='red'>key </font> <font color='blue'>value</font>

      GET <font color='red'>key </font>


    • 对象缓存

      MSET user:1:name zimu user:1:balance 1888

      MGET user:1:name user:1:balance

    file

    • 分布式锁(「SET if Not eXists」)

      SETNX <font color='red'>product:10001</font> true // 返回1代表获取锁成功

      SETNX <font color='red'>product:10001</font> false // 返回0代表获取锁失败

      .......执行业务操作

      DEL <font color='red'>product:10001</font> // 执行完业务 释放锁

      SET <font color='red'>product:10001</font> true ex 10 nx // 防止程序意外终止导致死锁


    • 计数器

      INCR article:readcount:101

    file

    hash类型(散列表)

    file

  • 介绍 :hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过,Redis 的 hash 做了更多优化。另外,hash 是一个 string 类型的 field 和 value 的映射表,特别适合用于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以 hash 数据结构来存储用户信息,商品信息等等。
  • 常用命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。
  • 应用场景: 系统中对象数据的存储。
    • 对象缓存

      HMSET user {userId}:username zhangfei {userId}:password 123456

      HMSET user 1:username zhangfei 1:password 123456

      HMGET user 1:username 1:password

    • 电商购物车

    file

    • 购物车操作

      1)添加商品 ---> hset cart:1001 10088 1

    • 增加数量 ---> hincrby cart:1001 10088 1
    • 3) 商品总数 ---> hlen cart:1001

      4) 删除商品---> hdel cart:1001 10088

      5)获取购物车所有商品---> hgetall cart:1001

    优点:

    1)同类数据归类整合储存,方便数据管理

    2)相比String操作消耗内存和cpu更小

    3)相比String储存 更节省空间

    缺点:

    1)过期功能不能使用在field上,只能用在key上

    2)Redis集群架构下不适合大规模使用

    file

    list列表类型

  • 介绍 :list 即是 链表。链表是一种非常常见的数据结构,特点是易于数据元素的插入和删除并且且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 LinkedList,但是 C 语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为一个 双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
  • 常用命令: rpush,lpop,lpush,rpop,lrange、llen 等。
  • 应用场景: 发布与订阅或者说消息队列、慢查询。
  • file

    • 常用数据结构

      Stack(栈)= LPUSH(左边放) + LPOP(左边取) --> FILO

      Quece(队列)= LPUSH(左边放) + RPOP右边取)

      BLocking MQ(阻塞队列)= LPUSH(左边放) + BRPOP(右边阻塞取:没有数据就阻塞!)

    • 微博、朋友圈、公众号等,关注的文章列表展示

    file

    子慕老师关注了北京本地宝 ,京城美味君等公众号,这些订阅号发布消息时,通过推或拉的方式把消息LPUSH放入redis中属于小明的list中。其中key为msg:{小明_ID}。当小明要获取大V们发的消息时,使用LRANGE 命令从队列中获取指定个数的订阅号信息

    1)京城美味君发动态,消息ID为10001

    LPUSH msg:{zimu-ID} 10001

    2)北京本地宝发动态,消息ID为10002

    LPUSH msg:{zimu-ID} 10002

    3)查看最新订阅号消息

    LRANGE msg:{zimu-ID} 0 4

    set集合类型

  • 介绍 : set 类似于 Java 中的 HashSet 。Redis 中的 set 类型是一种无序集合,集合中的元素没有先后顺序。当你需要存储一个列表数据,又不希望出现重复数据时,set 是一个很好的选择,并且 set 提供了判断某个成员是否在一个 set 集合内的重要接口,这个也是 list 所不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。比如:你可以将一个用户所有的关注人存在一个集合中,将其所有粉丝存在一个集合。Redis 可以非常方便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
  • 常用命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion 等。
  • 应用场景: 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景
    • 微信抽奖小程序

    file 1)点击 参与抽奖 加入集合

    SADD key {userID}

    2)查看排行榜

    SMEMBERS key

    3)抽取count名中奖者

    SRANDMEMBER key [count] / SPOP key [count]

    • 集合操作实现微博、微信关注模型

    file 首先了解一下set的集合操作,假如有三个集合

    file

    交集为:SINTER set1 set2 set3 ==> { c }

    并集为:SUNION set1 set2 set3 ==> { a,b,c,d,e }

    差集为:SDIFF set1 set2 set3 ==> { a }

    差集计算方式:set1 - (set2并set3) = {a、b、c} - {b、c、d、e} = {a} 只保留a中单独存在的元素

    共同关注A的人:可以用交集来实现 我可能认识的人:可以使用差集来实现,把我关注的人求差集

    sortedset有序集合类型

  • 介绍: 和 set 相比,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。

  • 常用命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。

  • 应用场景: 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。

  • file

    • Zset集合操作实现排行榜

    file

  • 点击新闻,为其分值+1
  • ZINCRBY hotNews:20210707 1 iphone13或有日落金玫瑰金

    2)展示当日排行前10

    ZREVRANGE hotNews:20210707 0 ,9 WITHSCORES

    bitmap位图 类型

  • 介绍 : bitmap 存储的是连续的二进制数字(0 和 1),通过 bitmap, 只需要一个 bit 位来表示某个元素对应的值或者状态,key 就是对应元素本身 。我们知道 8 个 bit 可以组成一个 byte,所以 bitmap 本身会极大的节省储存空间。
  • file 2. 常用命令: setbit 、getbit 、bitcount、bitop

  • 应用场景: 适合需要保存状态信息(比如是否签到、是否登录...)并需要进一步对这些信息进行分析的场景。比如用户签到情况、活跃用户情况、用户行为统计(比如是否点赞过某个视频)。
  • # SETBIT 会返回之前位的值(默认是 0)这里会生成 7 个位 127.0.0.1:6379> setbit mykey 7 1 (integer) 0 127.0.0.1:6379> setbit mykey 7 0 (integer) 1 127.0.0.1:6379> getbit mykey 7 (integer) 0 127.0.0.1:6379> setbit mykey 6 1 (integer) 0 127.0.0.1:6379> setbit mykey 8 1 (integer) 0 # 通过 bitcount 统计被被设置为 1 的位的数量。 127.0.0.1:6379> bitcount mykey (integer) 2Copy to clipboardErrorCopied

    针对上面提到的一些场景,这里进行进一步说明。

    使用场景一:用户行为分析 很多网站为了分析你的喜好,需要研究你点赞过的内容。

    # 记录你喜欢过 001 号小姐姐 127.0.0.1:6379> setbit beauty_girl_001 uid 1

    使用场景二:统计活跃用户

    面试题:现在系统有亿级的活跃用户,为了增强用户粘性,该如何实现签到、日活统计?

    使用时间作为 key,然后用户 ID 为 offset,如果当日活跃过就设置为 1

    那么我该如果计算某几天/月/年的活跃用户呢(暂且约定,统计时间内只有有一天在线就称为活跃),有请下一个 redis 的命令

    # 对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。 # BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种参数 BITOP operation destkey key [key ...]

    初始化数据:

    127.0.0.1:6379> setbit 20210308 1 1 (integer) 0 127.0.0.1:6379> setbit 20210308 2 1 (integer) 0 127.0.0.1:6379> setbit 20210309 1 1 (integer) 0

    统计 20210308~20210309 总活跃用户数: 1

    127.0.0.1:6379> bitop and desk1 20210308 20210309 (integer) 1 127.0.0.1:6379> bitcount desk1 (integer) 1

    统计 20210308~20210309 在线活跃用户数: 2

    127.0.0.1:6379> bitop or desk2 20210308 20210309 (integer) 1 127.0.0.1:6379> bitcount desk2 (integer) 2

    geo地理位置类型

    概述

    Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作

    应用场景:附近的人、摇一摇、附近的车、附近银行站点查询

    file

    环境要求

  • redis版本需要3.2及以上
  • 如果使用jedis操作redis,需要jedis版本为2.9及以上
  • 如果使用spring data redis操作redis,需要spring data redis版本为1.8.0及以上
  • redis GEO常用命令

    Tips: 在学习geo命令时会使用到经纬度坐标信息,可以在百度地图的拾取坐标系统中获取测试坐标信息,网址:http://api.map.baidu.com/lbsapi/getpoint/index.html

    1. geoadd命令

    为了进行地理位置相关操作, 我们首先需要将具体的地理位置记录起来, 这一点可以通过执行 geoadd 命令来完成, 该命令的基本格式如下:

    GEOADD location-set longitude latitude name [longitude latitude name ...]

    此命令用于添加位置信息到集合中

    以下代码展示了如何通过 GEOADD 命令, 将武汉、襄阳、宜昌、枝江、咸宁等数个湖北省的市添加到位置集合 hubeiCities 集合里面

    此处添加武汉的坐标信息到hubeiCities集合中

    geoadd hubeiCities 114.32538 30.534535 wuhan

    此处添加襄阳、枝江、咸宁的坐标信息到hubeiCities集合中

    geoadd hubeiCities 112.161882 32.064505 xiangyang 111.305197 30.708127 yichang 111.583717 30.463363 zhijiang 114.295174 29.885892 xianning

    2. geopos命令

    此命令用于根据输入的位置名称获取位置的坐标信息,基本语法如下

    GEOPOS location-set name [name ...]

    案例:查询襄阳市的位置信息

    geopos hubeiCities xiangyang --结果如下【1为经度 2为纬度】 1) "112.16188341379165649" 2) "32.06450528704699821"

    也可以一次查询多个位置的经纬度

    geopos hubeiCities xiangyang wuhan --襄阳的经纬度 1) 1) "112.16188341379165649" 2) "32.06450528704699821" --武汉的经纬度 2) 1) "114.32538002729415894" 2) "30.53453492166421057"

    3. geodist命令

    此命令用于计算两个位置之间的距离,基本语法如下:

    GEODIST location-set location-x location-y [unit]

    可选参数 unit 用于指定计算距离时的单位, 它的值可以是以下单位的其中一个:

    m 表示单位为米。 km 表示单位为千米。 mi 表示单位为英里。 ft 表示单位为英尺。

    案例:分别以默认距离单位和指定距离单位计算襄阳和武汉的距离

    --不指定距离单位 127.0.0.1:6381> geodist hubeiCities xiangyang wuhan "266889.7642" --指定距离单位km 127.0.0.1:6381> geodist hubeiCities xiangyang wuhan km "266.8898"

    4. georadius命令和georadiusbymember命令

    这两个命令都可以用于获取指定范围内的元素,也即查找特定范围之内的其他存在的地点。比如找出地点A范围200米之内的所有地点,找出地点B范围50公里之内的所有地点等等。

    这两个命令的作用一样, 只是指定中心点的方式不同: georadius 使用用户给定的经纬度作为计算范围时的中心点, 而 georadiusbymember 则使用储存在位置集合里面的某个地点作为中心点。

    以下是这两个命令的基本语法

    GEORADIUS location-set longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count] GEORADIUSBYMEMBER location-set location radius m|km|ft|mi [WITHCOORD] [WITHDIST] [ASC|DESC] [COUNT count]

    这两个命令的各个参数的意义如下:

    m|km|ft|mi 指定的是计算范围时的单位;

    如果给定了WITHCOORD,那么在返回匹配的位置时会将位置的经纬度一并返回;

    如果给定了WITHDIST , 那么在返回匹配的位置时会将位置与中心点之间的距离一并返回;

    在默认情况下, GEORADIUS 和 GEORADIUSBYMEMBER 的结果是未排序的, ASC 可以让查找结果根据距离从近到远排序, 而 DESC 则可以让查找结果根据从远到近排序;

    COUNT参数用于指定要返回的结果数量。

    下面通过案例分别演示georadius命令和georadiusbymember命令

    GEORADIUS案例: 在hubeiCities位置集合中查找距离经纬度为112.927076 28.235653(长沙)500km以内的位置信息,查找结果中应包含不超过5个位置的坐标信息,距离信息,并按距离由近到远排序。 查询代码如下:

    127.0.0.1:6381> georadius hubeiCities 112.927076 28.235653 500 km withcoord withdist asc count 5 -- 咸宁 距离目标位置226.67公里 1) 1) "xianning" 2) "226.6716" 3) 1) "114.29517298936843872" 2) "29.88589217282589772" -- 枝江 距离目标位置279.91公里 2) 1) "zhijiang" 2) "279.9154" 3) 1) "111.58371716737747192" 2) "30.46336248623112652" -- 武汉 距离目标位置289.38公里 3) 1) "wuhan" 2) "289.3798" 3) 1) "114.32538002729415894" 2) "30.53453492166421057" -- 宜昌 距离目标位置316.68公里 4) 1) "yichang" 2) "316.6777" 3) 1) "111.30519658327102661" 2) "30.70812783498269738" -- 襄阳 距离目标位置432.18公里 5) 1) "xiangyang" 2) "432.1767" 3) 1) "112.16188341379165649" 2) "32.06450528704699821"

    GEORADIUSBYMEMBER案例: 在hubeiCities位置集合中查找距离襄阳200km以内的位置信息【这里指定的目标位置只能是hubeiCities中存在的位置,而不能指定位置坐标】,查找结果中应包含不超过2个位置的坐标信息,距离信息,并按距离由远到近排序。 查询代码如下:

    127.0.0.1:6381> georadiusbymember hubeiCities xiangyang 200 km withcoord withdist desc count 2 -- 枝江 距襄阳186.38km 1) 1) "zhijiang" 2) "186.3784" 3) 1) "111.58371716737747192" 2) "30.46336248623112652" -- 宜昌 距襄阳171.40km 2) 1) "yichang" 2) "171.3950" 3) 1) "111.30519658327102661" 2) "30.70812783498269738"

    好了,今天就先唠到这里,真是越来越体会到了 码字不易的深刻内涵,一不小心,码了6000+字,有点累了

    大家如果觉得有帮助,就请给个 点赞、 评论、 转发,顺手来个一键三连,哈哈哈

    你们的支持是我最大的动力。

    加油 打工人!!!

    往日文章,大家自行饮用。

    往期干货:

    本文由传智教育博学谷发布。

    如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。

    转载请注明出处!

    上一篇:为什么大家都说 SELECT * 效率低?
    下一篇:没有了
    网友评论