• Redis详解(一)


    常用中间件Redis详解

    1、NoSql概述

    1.1、MySQL的历史演进

    1)单机MySQL的年代

    APP----->DAL------>MySQL

    90年代,一个基本的网站访问量一般不会太大,单个数据库完全足够!

    那个时候,更多的去使用静态网页Html,服务器根本没有太大的压力

    这个时候,网站的瓶颈:

    • 数据量如果太大,一个机器放不下
    • 数据的索引(B+Tree) ,一个机器内存也放不下
    • 访问量(读写混合),一个服务器承受不了

    2)Memcached (缓存) + MySQL + 垂直拆分 (读写分离)

    读写分离

    把服务器拆分,垂直拆分

    • 因为网站百分之八十的情况都是在读,每次都要去查询数据库的话就十分的麻烦,所以我们希望减轻数据的压力,可以使用缓存来保证效率!
    • APP---->DAL----->cache(缓存)----->MySQL(拆分…出多台服务器,专门负责读或写,读写分离)

    发展过程:

    优化数据结构和索引—>文件缓存(IO)—>Memcached(当时最热门的技术)

    3)分库分表 + 水平拆分 +MySQL集群

    MySQL集群

    早些年MyISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题

    Innodb:行锁,效率增加

    慢慢的开始使用分库分表来解决写的压力(不同业务使用单独数据库管理,不同字段使用不同的表来管理)

    最近的年代

    技术爆炸,世界发生了翻天覆地的变化;(定位,音乐,热榜等数据)

    MySQL等关系数据库就不够用了,数据量很多而且变化很快!

    1.2、NoSQL

    1. 用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式生长。这时候我们就需要使用NoSQL数据库的,NoSQL可以很好的处理以上的情况

    2. 什么是NoSQL

    • Not Only SQL :不仅仅是sql,泛指非关系型数据库。随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区。随着访问量增大,暴露出来很多难以克服的问题。NoSQL在当今大数据环境下发展的十分迅速,redis。

    • redis现在是很多公司招聘的必要求的技能。是我们当下必须要掌握的一个技术!

    • 很多的数据类型如用户的个人信息,社交网络,地理位置等,这些数据类型的存储不需要一个固定的格式,不需要多余的操作就可以横向扩展的。Map

    1. NoSQL特点
    • 方便扩展(数据之间没有关系,很好扩展!)
    • 大数据量,高性能(Redis 一秒写八万次,读取11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
    • 数据类型是多样型的!(不需要事先设计数据库!随取随用!)
    1. 传统关系型数据库(RDBMS)和NoSQL区别
    • 传统的RDBMS:

      • 结构化组织
      • SQL
      • 数据和关系都存在单独的表中
      • 数据操作,数据定义
      • 严格的一致性
      • 基础的事务操作
    • NoSQL:

      • 不仅仅是数据
      • 没有固定的查询语言
      • 键值对存储,列存储,文档存储,图形数据库(社交关系)
      • 最终一致性
      • CAP定理 和BASE理论 (异地多活)
      • 高性能,高可用,高可扩展性
    1. 了解:3v +3高
    • 大数据时代的3V :主要是描述问题的

      • 海量 Volume
      • 多样 Variety
      • 实时 Velocity
    • 大数据时代的3高: 主要是对程序的要求

      • 高并发
      • 高可拓(高可扩展性:可以随时加服务拆分)
      • 高性能

    **真正在公司中的实践:NoSQL +RDBMS 一起使用才是最强的。

    1.3、阿里巴巴数据架构演进

    • 1999 第一代网站架构 Perl ,CGL ,Oracle

    • 2000 进入java时代 java,Servlet

    • 2001-2004 EJB ,DAO,DTO

    • 2005-2007 Without EJB重构 Spring+iBatis +Webx ,Antx ,底层架构:iSearch ,MQ+ESB ,数据挖掘 ,CMS

    • 2008-2009 海量数据 :Memached集群,Mysql+数据切分=Cobar,分布式存储,Hadoop,KV,CDN

    • 2010 安全、镜像 :安全,镜像,应用服务器升级,秒杀,NoSQL,SSD

    • 2011 第五代网络架构 : 敏捷开发,开放,体验

    推荐文章:阿里云的这些疯子https://www.jiqizhixin.com/articles/2018-10-18-8

    1.4、NoSQL的四大分类

    1. KV键值对:
    • 新浪:Redis
    • 美团:Redis+Tair
    • 阿里、百度:Redis +memecache
    1. 文档型数据库 :(bjson和json一样)
    • MongdDB(一般必须要掌握)
      • MongdDB是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档
      • MongdDB是一个介于关系型数据库和非关系型数据库中间的产品!MongdDB是非关系型数据库中功能最丰富、最像关系型数据库的!
    • ConthDB
    1. 列存储数据库 :
    • HBase
    • 分布式文件系统
    1. 图关系数据库
    • 不是存图形的,放的是关系,比如:朋友圈社交网络,广告推荐
    • Neo4j,InfoGrid

    2、Redis入门

    2.1、概述

    1. Redis是什么

    Redis(Remote Dictionary Server) ,即远程服务字典

    是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、key-value数据库,并提供多种语言的API。

    Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

    免费和开源!是当下最热门的NoSQL技术之一!也被人们称之为结构化数据库!

    1. Redis能干什么?
    • 内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb、aof)
    • 效率高,可以用于高速缓存
    • 发布订阅系统
    • 地图信息分析
    • 计时器、计数器(浏览量)
    1. Redis特性
    • 多样的数据类型
    • 持久化
    • 集群
    • 事务

    Redis推荐都是在Linux服务器上搭建的,我们是基于Linux学习

    2.2、安装

    Windows下安装

    1. 在github上搜索redis
    2. 下载完毕得到压缩包
    3. 解压到自己电脑上的环境目录下就可以了
    • redis-cli.exe :客户端
    • redis-server.exe :服务端
    1. 开启 双击 redis-server.exe
    2. 开启客户端redis-cli.exe 连接到服务端
    127.0.0.1:6379> ping    //测试连接
    PONG
    127.0.0.1:6379> set name xqh    //键值对存储
    OK
    127.0.0.1:6379> get name
    "xqh"
    127.0.0.1:6379>
    

    Window下使用确实简单,但是redis推荐我们使用Linux去开发使用

    Linux下安装

    2.3、测试性能

    redis-benchmark是一个官方的性能测试

    简单测试:

    #测试:100个并发连接  100000个请求
    redis-benchmark -h localhost -p 6379 -c 100 -n 100000
    

    2.4、基础的知识

    • redis默认有16个数据库,默认使用第0个
    • 可以使用select进行切换数据库 select 3
    127.0.0.1:6379[3]>flushdb  #清除当前数据库
    ok
    

    清除全部数据库的内容 flushAll

    1. Redis是单线程的!

      明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了。

    Redis是C语言写的,官方提供的数据为 100000+的QPS,完全不比同时使用key-value的Memecache差

    • 为什么Redis是单线程还这么快?

    CPU>内存>硬盘的速度要有所了解

    核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换:耗时的操作!)对于内存系统来说,如果没有上下文切换效率就是最高的,多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案。

    3、五大数据类型

    Redis是一个开源的,内存中的数据结构存储系统,它可以用作数据库、缓存和信息中间件MQ。它支持多种类型的数据结构,如 字符串,散列,列表,集合,有序集合与范围查询和地理空间索引半径查询。Reids内置了复制,LUA脚本,LRU驱动事件,事务和不同级别的磁盘持久化,并通过Redis哨兵和自动分区提供高可用性。

    3.1、Reids-Key

    127.0.0.1:6379> ping   #测试连接
    PONG
    127.0.0.1:6379> FlushALL  #清空所有数据库
    OK
    127.0.0.1:6379> keys *   #查看所有key
    (empty list or set)
    127.0.0.1:6379> set name xqh   #新建一个key
    OK
    127.0.0.1:6379> keys *
    1) "name"
    127.0.0.1:6379> set age 1
    OK
    127.0.0.1:6379> keys *
    1) "age"
    2) "name"
    127.0.0.1:6379> EXISTS name    #判断当前的key是否存在
    (integer) 1
    127.0.0.1:6379> move name 1   #移除当前的key
    (integer) 1
    127.0.0.1:6379> keys *
    1) "age"
    127.0.0.1:6379> get age
    "1"
    127.0.0.1:6379> EXPIRE age 10   #使这个key十秒后过期,单位是秒
    (integer) 1
    127.0.0.1:6379> ttl age  #查看当前key的剩余时间
    (integer) -2
    127.0.0.1:6379> get age
    (nil)
    127.0.0.1:6379> keys *
    1) "age"
    2) "name" 
    127.0.0.1:6379> type name   #查看当前key的类型
    string
    127.0.0.1:6379> type age
    string
    

    3.2、String(字符串)

    127.0.0.1:6379> set key1 v1   #设置值
    OK
    127.0.0.1:6379> keys *   #查看所有key
    1) "key1"
    127.0.0.1:6379> APPEND key1 "hello"    # 拼接字符串,如果当前字符串不存在就相当于新建一个key
    (integer) 7
    127.0.0.1:6379> get key1
    "v1hello"
    127.0.0.1:6379> STRLEN key1   # 获取字符串的长度
    (integer) 7
    127.0.0.1:6379> APPEND key1 ",xqh"
    (integer) 11
    127.0.0.1:6379> STRLEN KEY1
    (integer) 0
    127.0.0.1:6379> STRLEN key1
    (integer) 11
    127.0.0.1:6379> get key1
    "v1hello,xqh"
    127.0.0.1:6379>
    ------------------------------------------------------------------------------------------------
    127.0.0.1:6379> set views 0
    OK
    127.0.0.1:6379> get views
    "0"
    127.0.0.1:6379> incr views   #自增1 ,浏览量变为1
    (integer) 1
    127.0.0.1:6379> incr views
    (integer) 2
    127.0.0.1:6379> get views   #获得当前浏览量
    "2"
    127.0.0.1:6379> decr views   #自减1  ,浏览量-1
    (integer) 1
    127.0.0.1:6379> decr views
    (integer) 0
    127.0.0.1:6379> get views
    "0"
    127.0.0.1:6379> INCRBY views 10   #设置步长,指定增量
    (integer) 10
    127.0.0.1:6379> INCRBY views 10
    (integer) 20
    127.0.0.1:6379>DECRBY views 5
    (integer) 15
    
    
    
    ------------------------------------------------------------------------------------------------
    127.0.0.1:6379> get key1
    "hello,xqh"
    127.0.0.1:6379> GETRANGE key1 0 3   #截取字符串  【0,3】闭区间
    "hell"
    127.0.0.1:6379> GETRANGE key1 0 -1   #截取所有字符串和 get key 是一样的
    "hello,xqh"
    127.0.0.1:6379> set key2 abcdefg
    OK
    127.0.0.1:6379> SETRANGE key2 1 xx   # 从下标1开始替换为xx
    (integer) 7
    127.0.0.1:6379> get key2
    "axxdefg"
    ------------------------------------------------------------------------------------------------
    127.0.0.1:6379> setex key3 30 "hello"   #设置过期时间
    OK
    127.0.0.1:6379> ttl key3
    (integer) 24
    127.0.0.1:6379> get key3
    "hello"
    127.0.0.1:6379> setnx mykey "redis"   #不存在这个key的话才设置这个值(在分布锁中会常常使用)
    (integer) 1   #设置成功
    127.0.0.1:6379> keys *
    1) "key2"
    2) "mykey"
    3) "key1"
    127.0.0.1:6379> setnx mykey "mongdb"
    (integer) 0   #设置失败,因为已经存在mykey
    127.0.0.1:6379>
    
    ------------------------------------------------------------------------------------------------
    
    127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3   #同时设置多个值
    OK
    127.0.0.1:6379> keys *
    1) "k3"
    2) "k2"
    3) "k1"
    127.0.0.1:6379> mget k1 k2 k3   #同时获取多个值
    1) "v1"
    2) "v2"
    3) "v3" 
    127.0.0.1:6379> msetnx k1 v1 k4 v4   #设置失败,k1存在,k4不存在,设置失败,说明msetnx是一个原子性操作,要么都成功,要么都失败,不能k1创建失败,而k4创建成功
    (integer) 0
    127.0.0.1:6379> get k4
    (nil)
    127.0.0.1:6379>
    ------------------------------------------------------------------------------------------------
    127.0.0.1:6379> mset user:1:name zhangshan user:1:age 2   #对象作为值
    OK
    127.0.0.1:6379> mget user:1:name user:1:age
    1) "zhangshan"
    2) "2"
    127.0.0.1:6379>
    
    
    ------------------------------------------------------------------------------------------------
    #getset  :先获取再设置。先获取,如果不存在就返回nil并创建
    127.0.0.1:6379> getset db redis  #获取db,db不存在所以返回nil,并创建一个db:redis
    (nil)
    127.0.0.1:6379> get db
    "redis"
    127.0.0.1:6379> getset db mongdb   # 获取db,db存在先获取原来的值,再赋新的值覆盖
    "redis"
    127.0.0.1:6379> get db
    "mongdb"
    127.0.0.1:6379>
    
    
    
    ------------------------------------------------------------------------------------------------
    
    1. String类型的使用场景:value除了是我们的字符串还可以是我们的数字!
    • 计数器
    • 统计多单位的数量
    • 粉丝数
    • 对象缓存存储

    3.3、List

    1. 基本的数据类型,列表。

    在redis里面,我们可以把list玩成栈、队列、阻塞队列

    redis不区分大小写命令

    127.0.0.1:6379> ping   #测试连接
    PONG
    127.0.0.1:6379> keys *
    (empty list or set)
    127.0.0.1:6379> LPUSH list one   #将一个值或者多个值插入到列表的头部
    (integer) 1
    127.0.0.1:6379> LPUSH list two
    (integer) 2
    127.0.0.1:6379> LPUSH list three
    (integer) 3
    127.0.0.1:6379> LRANGE list 0 -1
    1) "three"
    2) "two"
    3) "one"
    127.0.0.1:6379> LRANGE list 0 1
    1) "three"
    2) "two"
    
    127.0.0.1:6379> RPUSH list righr    #将一个值或者多个值 插入到列表的尾部
    (integer) 4
    127.0.0.1:6379> LRANGE list 0 -1
    1) "three"
    2) "two"
    3) "one"
    4) "righr"
    
    
    
    127.0.0.1:6379> lrange list 0 -1
    1) "three"
    2) "two"
    3) "one"
    4) "righr"
    127.0.0.1:6379> lpop list   #移除列表list第一个元素
    "three"
    127.0.0.1:6379> lrange list 0 -1
    1) "two"
    2) "one"
    3) "righr"
    127.0.0.1:6379> rpop list   #移除列表list最后一个元素
    "righr"
    127.0.0.1:6379> lrange list 0 -1
    1) "two"
    2) "one"
    
    
    127.0.0.1:6379> lindex list 1   #通过下标获取list中某一个值
    "one"
    
    
    
    127.0.0.1:6379> llen list   #获取列表长度
    (integer) 2
    
    #移除指定元素
    127.0.0.1:6379> lrange list 0 -1
    1) "three"
    2) "two"
    3) "one"
    4) "zero"
    127.0.0.1:6379> lrem list 1 one    #移除列表中1一个one
    (integer) 1
    127.0.0.1:6379> lrange list 0 -1
    1) "three"
    2) "two"
    3) "zero"
    127.0.0.1:6379> lpush list three
    (integer) 4
    127.0.0.1:6379> lrange list 0 -1
    1) "three"
    2) "three"
    3) "two"
    4) "zero"
    127.0.0.1:6379> lrem list 2 three  #移除列表中两个three
    (integer) 2
    127.0.0.1:6379> lrange list 0 -1
    1) "two"
    2) "zero"
    
    
    
    
    
    #trim:截取,保留部分元素
    127.0.0.1:6379> rpush mylist "hello"
    (integer) 1
    127.0.0.1:6379> rpush mylist "hello1"
    (integer) 2
    127.0.0.1:6379> rpush mylist "hello2"
    (integer) 3
    127.0.0.1:6379> rpush mylist "hello3"
    (integer) 4
    127.0.0.1:6379> lrange mylist 0 -1
    1) "hello"
    2) "hello1"
    3) "hello2"
    4) "hello3"
    127.0.0.1:6379> ltrim mylist 1 2    #通过下标截取指定长度  只保留下标1到2
    OK
    127.0.0.1:6379> lrange mylist 0 -1
    1) "hello1"
    2) "hello2"
    
    
    
    
    #rpoplpush  移除列表的最后一个元素并添加到新的列表
    127.0.0.1:6379> rpush mylist "hello"
    (integer) 1
    127.0.0.1:6379> rpush mylist "hello1"
    (integer) 2
    127.0.0.1:6379> rpush mylist "hello2"
    (integer) 3
    127.0.0.1:6379> rpoplpush mylist myotherlist   #移除mylist中最后一个元素并添加到myotherlist中
    "hello2"
    127.0.0.1:6379> lrange mylist 0 -1
    1) "hello"
    2) "hello1"
    127.0.0.1:6379> lrange myotherlist 0 -1
    1) "hello2"
    
    
    
    
    
    
    127.0.0.1:6379> exists list   #列表是否存在
    (integer) 0
    127.0.0.1:6379> lpush list value1
    (integer) 1
    127.0.0.1:6379> lrange list 0 -1
    1) "value1"
    127.0.0.1:6379> lset list 0 item  # 将列表中指定下标的值替换为另一个值,更新操作。
    OK
    127.0.0.1:6379> lrange list 0 -1
    1) "item"
    127.0.0.1:6379> lset list 1 other   #列表不存在下标1,所以报错
    (error) ERR index out of range
    
    
    #linsert  将某个值插入到列表中某个值的前面或者后面
    127.0.0.1:6379> lpush mylist "hello"
    (integer) 1
    127.0.0.1:6379> lpush mylist "hello1"
    (integer) 2
    127.0.0.1:6379> linsert mylist before "hello1" "other"   #在hello1的前面插入other
    (integer) 3
    127.0.0.1:6379> lrange list 0 -1
    (empty list or set)
    127.0.0.1:6379> lrange mylist 0 -1
    1) "other"
    2) "hello1"
    3) "hello"
    127.0.0.1:6379> linsert mylist after "hello1" "other"  #在hello1的后面插入other
    (integer) 4
    127.0.0.1:6379> lrange mylist 0 -1
    1) "other"
    2) "hello1"
    3) "other"
    4) "hello"
    
    
    1. 小结
    • 它实际是一个链表 ,before ,after ,left ,right都可以插入值
    • 如果key不存在,创建新的链表
    • 如果key存在,新增内容
    • 如果移除了所有值,空链表,也代表不存在
    • 在两边插入或者改动值,效率最高!中间元素,相对来说效率会低一点

    3.4、Set(集合)

    1. set中的值是不能重复的
    127.0.0.1:6379> sadd myset "hello"   #set集合中添加元素
    (integer) 1
    127.0.0.1:6379> sadd myset "xqh"
    (integer) 1
    127.0.0.1:6379> sadd myset "love"
    (integer) 1
    127.0.0.1:6379> smembers myset  #查看指定set的所有值(无序、不重复的集合)
    1) "xqh"
    2) "love"
    3) "hello"
    127.0.0.1:6379> sismember myset hello  #判断一个元素是否在set集合中,有---1 ,没有---0
    (integer) 1
    127.0.0.1:6379> sismember myset xxx
    (integer) 0
    
    
    
    
    127.0.0.1:6379> scard myset   #获取set中值的个数
    (integer) 3
    
    
    
    127.0.0.1:6379> srem myset "hello"   #移除集合中某一个元素
    (integer) 1
    127.0.0.1:6379> smembers myset
    1) "xqh"
    2) "love"
    
    
    #随机抽出指定个数的元素
    127.0.0.1:6379> smembers myset
    1) "love"
    2) "hello"
    3) "hello3"
    4) "xqh"
    5) "hello2"
    6) "hello1"
    127.0.0.1:6379> srandmember myset
    "hello1"
    127.0.0.1:6379> srandmember myset
    "hello3"
    127.0.0.1:6379> srandmember myset
    "hello"
    127.0.0.1:6379> srandmember myset 2
    1) "hello"
    2) "hello2"
    127.0.0.1:6379> srandmember myset 2
    1) "hello1"
    2) "hello2"
    
    
    
    
    
    
    #随机删除
    127.0.0.1:6379> smembers myset
    1) "hello3"
    2) "xqh"
    3) "love"
    4) "hello2"
    5) "hello1"
    6) "hello"
    127.0.0.1:6379> spop myset  #随机删除集合中的一个元素
    "love"
    127.0.0.1:6379> smembers myset
    1) "hello3"
    2) "xqh"
    3) "hello2"
    4) "hello1"
    5) "hello"
    
    
    
    
    
    #将一个指定的值移除并且添加到另一个集合中
    
    127.0.0.1:6379> sadd myset2 "set2"
    (integer) 1
    127.0.0.1:6379> smove myset myset2 "xqh"
    (integer) 1
    127.0.0.1:6379> smembers myset2
    1) "xqh"
    2) "set2"
    
    
    
    
    #交集,并集,差集(不同)
    127.0.0.1:6379> sadd key1 a
    (integer) 1
    127.0.0.1:6379> sadd key1 b
    (integer) 1
    127.0.0.1:6379> sadd key1 c
    (integer) 1
    127.0.0.1:6379> sadd key2 c
    (integer) 1
    127.0.0.1:6379> sadd key2 d
    (integer) 1
    127.0.0.1:6379> sadd key2 e
    (integer) 1
    127.0.0.1:6379> smembers key1
    1) "c"
    2) "a"
    3) "b"
    127.0.0.1:6379> smembers key2
    1) "d"
    2) "c"
    3) "e"
    127.0.0.1:6379> sdiff key1 key2   #差集,不同的元素
    1) "a"
    2) "b"
    127.0.0.1:6379> sinter key1 key2   #交集  相同的部分  ,共同的好友可以这样实现
    1) "c"
    127.0.0.1:6379> sunion key1 key2   #并集  
    1) "b"
    2) "a"
    3) "d"
    4) "e"
    5) "c"
    

    3.5、Hash(哈希)

    1. Map集合,key-map集合 ,key- ,这时候的值是一个map集合。本质和String类型没有太大区别
    127.0.0.1:6379> hset myhash field1 xqh   #set一个具体的key-value
    (integer) 1 
    127.0.0.1:6379> hget myhash field1   # 取出值
    "xqh"
    127.0.0.1:6379> hmset myhash field1 hello field2 world  #set多个key-value
    OK
    127.0.0.1:6379> hmget myhash field1 field2   #取出多个值
    1) "hello"
    2) "world"
    127.0.0.1:6379> hgetall myhash    #查看所有key-value
    1) "field1"
    2) "hello"
    3) "field2"
    4) "world"
    
    
    
    127.0.0.1:6379> hdel myhash field1   #删除hash指定key字段,对应的value值也消失了
    (integer) 1
    127.0.0.1:6379> hgetall myhash
    1) "field2"
    2) "world"
    
    
    
    
    #获取长度
    127.0.0.1:6379> hgetall myhash
    1) "field2"
    2) "world"
    3) "field1"
    4) "hello"
    5) "field3"
    6) "xqh"
    7) "field"
    8) "xqhx"
    127.0.0.1:6379> hlen myhash   #获取哈希表的字段数量
    (integer) 4
    
    
    
    
    
    127.0.0.1:6379> hexists myhash field1  #判断指定字段是否存在
    (integer) 1
    
    
    #获取所有的key,获取所有的value
    127.0.0.1:6379> hkeys myhash
    1) "field2"
    2) "field1"
    3) "field3"
    4) "field"
    127.0.0.1:6379> hvals myhash
    1) "world"
    2) "hello"
    3) "xqh"
    4) "xqhx"
    
    
    
    
    127.0.0.1:6379> hgetall myhash
    1) "field1"
    2) "4"
    127.0.0.1:6379> hincrby myhash field1 -2   # hincrby自增,正是就增加,负数就减少
    (integer) 2
    127.0.0.1:6379> hsetnx myhash field4 hello   #如果field4不存在就添加这个元素,所以这里添加成功
    (integer) 1
    127.0.0.1:6379> hsetnx myhash field4 xqh  #因为field4已经存在了,所以这里添加失败
    (integer) 0
    
    
    
    1. hash应用
    • hash变更数据,和string类型类似,但是比string更方便。尤其是用户信息的保存,还有经常变动的信息。
    • hash更适合对象的存储,而string更适合字符串的存储

    3.6、Zset(有序集合)

    1. 在set的基础上,增加了一个值 zset k1 score1 v1 score1 2 …优先级
    127.0.0.1:6379> zadd myset 1 one  #添加一个值  (中间要加一个数字,代表优先级,从1到后面,排序越后)
    (integer) 1
    127.0.0.1:6379> zadd myset 2 two
    (integer) 1
    127.0.0.1:6379> zadd myset 3 three 4 four
    (integer) 2
    127.0.0.1:6379> zrange myset 0 -1
    1) "one"
    2) "two"
    3) "three"
    4) "four" 
    
    
    
    127.0.0.1:6379> zrange salary 0 -1    #从小到大
    1) "xiaohong"
    2) "zhangshan"
    3) "xiaolan"
    127.0.0.1:6379> zrevrange salary 0 -1  #从大到小
    1) "xiaolan"
    2) "zhangshan"
    3) "xiaohong"
    
    
    127.0.0.1:6379> zrange salary 0 -1 withscores   #升序排列显示全部的用户并带上成绩值
    1) "xiaohong"
    2) "2500"
    3) "zhangshan"
    4) "5000"
    5) "xiaolan"
    6) "20000"
    127.0.0.1:6379> zrevrange salary 0 -1 withscores    #降序排列显示全部的用户并带上成绩值
    1) "xiaolan"
    2) "20000"
    3) "zhangshan"
    4) "5000"
    5) "xiaohong"
    6) "2500"
    
    
    127.0.0.1:6379> zrangebyscore salary -inf 10000 withscores  #小于10000工资的用户升序排列
    1) "xiaohong"
    2) "2500"
    3) "zhangshan"
    4) "5000"
    
    127.0.0.1:6379> zrevrangebyscore salary +inf 3000 withscores   #大于3000工资的降序排列
    1) "xiaolan"
    2) "20000"
    3) "zhangshan"
    4) "5000"
    
    
    
    
    #删除指定元素
    127.0.0.1:6379> zrange salary 0 -1
    1) "xiaohong"
    2) "zhangshan"
    3) "xiaolan"
    127.0.0.1:6379> zrem salary xiaohong   #删除xiaohong
    (integer) 1
    127.0.0.1:6379> zrange salary 0 -1
    1) "zhangshan"
    2) "xiaolan"
    
    
    
    #获取有序集合中的个数
    127.0.0.1:6379> zcard salary
    (integer) 2
    
    
    
    
    #获取指定区间的成员数量
    127.0.0.1:6379> zrange salary 0 -1 withscores
    1) "zhangshan"
    2) "5000"
    3) "xiaochen"
    4) "10000"
    5) "xiaolan"
    6) "20000"
    127.0.0.1:6379> zcount salary 5000 20000
    (integer) 3
    
    
    
    
    
    
    • 这些只是一些常用的api,其他的遇到再去查官方文档!

    案例思路:带权重进行判断。set 排序 存储班级成绩表 ,工资表排序

    排行榜应用实现。

    4、三种特殊数据类型

    4.1、geospatial (地理位置)

    1. 朋友定位,附近的人,打车距离计算

    Redis的Geo 这个功能可以推算地理位置的信息,两地之间的距离等

    可以查询一些测试数据

    1. 6个命令
    
    ################################################################################################
    #geoadd 添加地理位置
    #规则:两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入!
    #有效的经度从-180度到180度。
    #有效的纬度从-85.05112878度到85.05112878度。
    #当坐标位置超出上述指定范围时,该命令将会返回一个错误。
    127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
    (integer) 1
    127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
    (integer) 1
    127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqi 114.05 22.52 shenzhen
    (integer) 2
    127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
    (integer) 2
    ################################################################################################
    # 获取当前定位:一定是一个坐标值
    127.0.0.1:6379> geopos china:city beijing  #获取指定城市的经度和纬度
    1) 1) "116.39999896287918091"
       2) "39.90000009167092543"
    127.0.0.1:6379> geopos china:city beijing shanghai
    1) 1) "116.39999896287918091"
       2) "39.90000009167092543"
    2) 1) "121.47000163793563843"
       2) "31.22999903975783553"
    ################################################################################################
    #两人之间的距离
    #单位
    #m 表示单位为米。
    #km 表示单位为千米。
    #mi 表示单位为英里
    #ft 表示单位为英尺。
    #如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位。
    127.0.0.1:6379> geodist china:city beijing shanghai km #查看上海到北京的直线距离,单位km
    "1067.3788"
    #################################################################################################
    #查找附近的人(需要获得所有附近的人的地址,定位!)通过半径来查询
    #获取指定数量的人
    #前提:所有数据应该都录入:china:city
    127.0.0.1:6379> georadius china:city 110 30 1000 km #以110,30这个经纬度为中心,寻找方圆1000km内的城市
    1) "chongqi"
    2) "xian"
    3) "shenzhen"
    4) "hangzhou"
    127.0.0.1:6379> georadius china:city 110 30 500 km  #以110,30这个经纬度为中心,寻找方圆500km内的城市
    1) "chongqi"
    2) "xian"
    127.0.0.1:6379> georadius china:city 110 30 500 km withdist #以110,30这个经纬度为中心,寻找方圆500km内的城市,顺带输出离中心距离(110,30)的位置
    1) 1) "chongqi"
       2) "341.9374"
    2) 1) "xian"
       2) "483.8340"
    127.0.0.1:6379> georadius china:city 110 30 500 km withcoord  #以110,30这个经纬度为中心,寻找方圆500km内的城市,顺带输出他人的定位信息(主要是经纬度)
    1) 1) "chongqi"
       2) 1) "106.49999767541885376"
          2) "29.52999957900659211"
    2) 1) "xian"
       2) 1) "108.96000176668167114"
          2) "34.25999964418929977"
    127.0.0.1:6379> georadius china:city 110 30 500 km withcoord count 1  #以110,30这个经纬度为中心,寻找方圆500km内的城市,筛选出指定数量的数据
    1) 1) "chongqi"
       2) 1) "106.49999767541885376"
          2) "29.52999957900659211"
    ################################################################################################
    #找出位于指定元素周围的其他元素
    127.0.0.1:6379> georadiusbymember china:city beijing 1000 km  #查找北京附近1000km的城市
    1) "beijing"
    2) "xian"
    #################################################################################################
    #geohash该命令将返回11个字符的geohash字符串
    #将二维的经纬度转换为一维的字符串,如果两个字符串越接近,那么则距离越近
    127.0.0.1:6379> geohash china:city beijing chongqi
    1) "wx4fbxxfke0"
    2) "wm5xzrybty0"
    #################################################################################################
    #GEO底层的实现原理其实就是zset!我们可以使用zset命令来操作geo
    127.0.0.1:6379> zrange china:city 0 -1   #查看地图中全部的元素,之前通过geoadd添加进去的
    1) "chongqi"
    2) "xian"
    3) "shenzhen"
    4) "hangzhou"
    5) "shanghai"
    6) "beijing"
    127.0.0.1:6379> zrem china:city beijing  #移除指定元素
    (integer) 1
    127.0.0.1:6379> zrange china:city 0 -1
    1) "chongqi"
    2) "xian"
    3) "shenzhen"
    4) "hangzhou"
    5) "shanghai"
    
    
    
    
    
    
    
    

    4.2、Hyperloglog

    1. 基数:不重复的元素

    A{1,3,5,7,8,7}

    B{1,3,5,7,8}

    基数(不重复的元素) =5

    1. 简介:redis 2.8.9版本就更新了Hyperloglog数据结构

    基数统计的算法!网页的UV(一个人访问一个网站多次,但是还是算作一个人)

    传统的方式,set保存用户的id ,然后就可以统计set中的元素数量作为标准判断

    这个方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是保存用户id

    优点 :占用的内存是固定,2^64 不同的元素的技术,只需要费 12kb内存,所以如果要从内存角度来比较的话 Hyperloglog 首选

    1. 测试使用
    127.0.0.1:6379> pfadd mykey a b c d e f g h i j  #创建第一组元素
    (integer) 1
    127.0.0.1:6379> pfcount mykey  #统计第一组元素数量
    (integer) 10
    127.0.0.1:6379> pfadd mykey2 i j k l m n b v
    (integer) 1
    127.0.0.1:6379> pfcount mykey2
    (integer) 8
    127.0.0.1:6379> pfmerge mykey3 mykey mykey2  #将第一组和第二组合并到第三组中,不能有重复的元素,所以一共是15个
    OK
    127.0.0.1:6379> pfcount mykey3
    (integer) 15
    
    
    
    • 允许容错的话,可以使用这个Hyperloglog。如果不允许误差就不能用这个。

    4.3、Bitmaps

    1. 位存储:

    统计用户信息,活跃、不活跃!登录、未登录!打开等,两个状态的都可以使用bitmaps

    Bitmaps 位图,数据结构 !都是操作二进制来进行记录,就只有0和1两个状态

    1. 测试
    
    #记录周一到周日的打卡情况。下标0-6代表周一至周日,0代表未打卡,1代表打卡
    127.0.0.1:6379> setbit sign 0 1
    (integer) 0
    127.0.0.1:6379> setbit sign 1 0
    (integer) 0
    127.0.0.1:6379> setbit sign 2 1
    (integer) 0
    127.0.0.1:6379> setbit sign 3 1
    (integer) 0
    127.0.0.1:6379> setbit sign 4 1
    (integer) 0
    127.0.0.1:6379> setbit sign 5 0
    (integer) 0
    127.0.0.1:6379> setbit sign 6 1
    (integer) 0
    
    #查看某一天是否打卡
    127.0.0.1:6379> getbit sign 3
    (integer) 1
    127.0.0.1:6379> getbit sign 5
    (integer) 0
    
    
    #统计打卡天数
    127.0.0.1:6379> bitcount sign   #统计这周打卡记录
    (integer) 5
    
    
    

    5、事务

    1. Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!一次性、顺序性、排他性!执行一些列的命令!
    • redis单条命令是保证原子性的,但是事务不保证原子性
    • redis事务没有隔离级别的概念!
    • 所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!
    1. redis的事务:
    • 开启事务(multi)
    • 命令入队(…)
    • 执行事务(exec)
    1. 正常执行事务:
    127.0.0.1:6379> multi  #开启事务
    OK
    # 命令入队
    127.0.0.1:6379> set k1 v1
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> get k2
    QUEUED
    127.0.0.1:6379> set k3 v3
    QUEUED
    127.0.0.1:6379> exec   #执行事务,一步一步
    1) OK
    2) OK
    3) "v2"
    4) OK
    
    
    127.0.0.1:6379> multi  #开启事务
    OK
    #命令入队
    127.0.0.1:6379> set k1 v1
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> set k4 v4  
    QUEUED
    127.0.0.1:6379> discard  #取消事务 
    OK
    127.0.0.1:6379> get k4  #事务队列中命令都不会被执行
    (nil)
    
    1. 编译型异常(代码有问题,命令有错),事务中所有的命令都不会被执行
    2. 运行时异常(1/0) ,如果事务队列中存在语法性错误,那么执行命令的时候,其他命令可以正常执行,错误命令会抛出异常
    #编译型异常(命令有错,代码有问题)
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set k1 v1
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> set k3 v3
    QUEUED
    127.0.0.1:6379> getset k3   #命令有错,缺少参数
    (error) ERR wrong number of arguments for 'getset' command
    127.0.0.1:6379> set k4 v4
    QUEUED
    127.0.0.1:6379> exec  #执行报错,事务中所有的命令都不会被执行
    (error) EXECABORT Transaction discarded because of previous errors.
    127.0.0.1:6379> get k3
    (nil)
    
    
    #运行时异常
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> set k1 "v1"
    QUEUED
    127.0.0.1:6379> incr k1  #代码没问题,只是用错了,这里字符串不能递增
    QUEUED
    127.0.0.1:6379> set k2 v2
    QUEUED
    127.0.0.1:6379> set k3 v3
    QUEUED
    127.0.0.1:6379> get k3
    QUEUED
    127.0.0.1:6379> exec  #执行不报错,错误的命令行不能执行,而其他命令行继续执行
    1) OK
    2) (error) ERR value is not an integer or out of range
    3) OK
    4) OK
    5) "v3"
    127.0.0.1:6379> get k2  #其他的命令行成功执行
    "v2"
    
    1. 监控(watch)
    • 悲观锁:认为什么时候都会出问题,无论做什么都会加锁

    • 乐观锁:认为什么时候都不会出问题,所以不会上锁,更新数据的时候去判断一下,在此期间是否有人修改过和这个数据,version

    • 获取version

    • 更新的时候比较version

    • 测试乐观锁

    
    #正常执行成功
    127.0.0.1:6379> set money 100
    OK
    127.0.0.1:6379> set out 0
    OK
    127.0.0.1:6379> watch money  #监视money对象
    OK
    127.0.0.1:6379> multi  
    OK
    127.0.0.1:6379> decrby money 20
    QUEUED
    127.0.0.1:6379> incrby out 20
    QUEUED
    127.0.0.1:6379> exec   #事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
    1) (integer) 80
    2) (integer) 20
    
    #测试多线程修改值,使用watch可以当做redis乐观锁操作!
    unwath 先解除监视,再重新监视 watch money
    

    如果修改失败,获取最新的值就行。

    6、Jedis

    我们要使用Java来操作Redis

    1. 什么是Jedis
    • 是Redis官方推荐的java连接开发工具!使用java操作Redis中间件,如果你要使用java操作redis,那么一定要对jedis十分的熟悉。
    1. 测试
    • 导入依赖
     <dependencies>
            
            <dependency>
                <groupId>redis.clientsgroupId>
                <artifactId>jedisartifactId>
                <version>4.2.3version>
            dependency>
    
            <dependency>
                <groupId>com.alibabagroupId>
                <artifactId>fastjsonartifactId>
                <version>2.0.2version>
            dependency>
    
        dependencies>
    
    
    • 编码测试
    package com.qian;
    
    import redis.clients.jedis.Jedis;
    
    public class TestPing {
        public static void main(String[] args) {
            //1.new Jedis 对象即可
            Jedis jedis =new Jedis("127.0.0.1",6379);
            //jedis所有的命令就是我们之前学习的所有命令
            System.out.println(jedis.ping());//测试连接
            
        }
    }
    //输出:pong
    
    
    1. 常用API(所有的api和前面的命令一模一样)
    package com.qian;
    
    import redis.clients.jedis.Jedis;
    
    import java.util.Set;
    
    public class TestKey {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("127.0.0.1",6379);
            System.out.println("清空数据:"+jedis.flushDB());
            System.out.println("判断某个键是否存在:"+jedis.exists("username"));
            System.out.println("新增键值对:"+jedis.set("username","xqh"));
            System.out.println("新增键值对:"+jedis.set("password","123456"));
            System.out.println("系统中所有的键如下:");
            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所存储的值的类型:"+jedis.type("username"));
            System.out.println("新增键值对:"+jedis.set("project","chinese"));
            System.out.println("随机返回key空间的一个:"+jedis.randomKey());
            System.out.println("重命名key:"+jedis.rename("username","name"));
            System.out.println("取出改后的name的值:"+jedis.get("name"));
            System.out.println("按索引查询:"+jedis.select(0));
            System.out.println("返回当前数据库中key的数目:"+jedis.dbSize());
            System.out.println("删除当前选择数据库中的所有key:"+jedis.flushDB());
            System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
    
        }
    }
    ...
      //和之前的命令一模一样,需要使用可以直接翻前面的笔记喔
    
    1. 通过Jedis再次理解事务
    package com.qian;
    
    import com.alibaba.fastjson.JSONObject;
    import com.google.gson.JsonObject;
    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.Transaction;
    
    public class TestTX {
        public static void main(String[] args) {
            Jedis jedis = new Jedis("127.0.0.1", 6379);
            jedis.flushDB();
    
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("hello","world");
            jsonObject.put("name","xqh");
            //开启事务
            Transaction multi = jedis.multi();
            String result = jsonObject.toJSONString();
            try {
                multi.set("user1", result);
                multi.set("user2", result);
                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();  //关闭连接
            }
    
    
    
        }
    }
    //代码错误,事务执行失败,get不到,输出null
    

    7、SpringBoot集成Redis

    SpringBoot操作数据

    1. 在springboot2.x之后,原来使用的jedis被替换为了lettuce
    • jedis:采用的是直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool 连接池,BIO模式

    • lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况,可以减少线程数据,更像NIO模式

    • 默认的RedisTemplate 没有过多的设置,redis对象都是需要序列化

    • 两个泛型都是 object ,object的类型,我们后使用需要强制转换

    1. 整合测试
    • 导入依赖
      <dependencies>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-webartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-configuration-processorartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    
    
    • 配置连接
    # 配置redis
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
    
    • 测试
    package com.example;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.core.RedisTemplate;
    
    @SpringBootTest
    class Redis02SpringbootApplicationTests {
    
        @Autowired
        private RedisTemplate redisTemplate;
        @Test
        void contextLoads() {
            //redisTemplate  操作不同的数据类型,api和我们的命令是一样的
            //opsForValue:操作字符串  类似String
            //opsForList :操作List  类似List
            //opsForSet
            //opsForHash()
            //opsForGeo()
            //opsForZSet()
            //opsForHyperLogLog()
    
            //除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的增删改查
    
            //获取redis的连接对象
    //        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
    //        connection.flushDb();
    //        connection.flushAll();
    
            redisTemplate.opsForValue().set("mukey","xqh");
            System.out.println(redisTemplate.opsForValue().get("mukey"));
        }
    
    }
    
    
    • 传递对象要序列化
    package com.example.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.springframework.stereotype.Component;
    
    import java.io.Serializable;
    
    @Component
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    //在企业中,我们的所有pojo都会序列化
    public class User implements Serializable {
        private String name;
        private int age;
    
    
    
    }
    
    
    //test
    @Test
        public void test() throws JsonProcessingException {
            //真实的开发一般都使用json来传递对象
            User user = new User("java", 3);
            //String jsonUser = new ObjectMapper().writeValueAsString(user);
            redisTemplate.opsForValue().set("user",user);
            Object user1 = redisTemplate.opsForValue().get("user"); //直接传递对象会报错,所有的对象传递要先序列化!
            System.out.println(user1);
    
        }
    
    • 自定义序列化配置
    @Configuration
    public class RedisConfig {
        //固定模板,企业中可以拿去直接使用
        //自定义一个RedisTemplate
        @Bean
        @SuppressWarnings("all")
        public RedisTemplate<String,Object>redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            //我们为了自己开发方便,一般直接使用
            RedisTemplate<String,Object>template=new RedisTemplate<String,Object>();
            //连接工厂
            template.setConnectionFactory(redisConnectionFactory);
            //配置具体的序列化方式
            //将任意对象解析成序列化 用json
            //json的序列化
            Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
            ObjectMapper om = new ObjectMapper();
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            //Stirng的序列化
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
            
            //key采用String的序列化方式
            template.setKeySerializer(stringRedisSerializer);
            //hash的key也采用String的序列化方式
            template.setHashKeySerializer(stringRedisSerializer);
            //value序列化方式采用jackson
            template.setValueSerializer(jackson2JsonRedisSerializer);
            //hash的value序列化方式采用jackson
            template.setHashValueSerializer(jackson2JsonRedisSerializer);
            template.afterPropertiesSet();
            
    
            
            return template;
        }
    }
    
    
    • 在企业开发中,我们大部分情况下都不会使用这种原生的方式去编写代码,而是封装成工具类RedisUtils
    package com.example.utils;
    
    import org.springframework.beans.factory.annotation.Autowired;
    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;
    
    @Component
    public class RedisUtil {
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
        public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        // =============================common============================
    
        /**
         * 指定缓存失效时间
         *
         * @param key  键
         * @param time 时间(秒)
         * @return
         */
        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 boolean isExpire(String key) {
            return getExpire(key) > 1 ? false : true;
        }
    
        /**
         * 根据key 获取过期时间
         *
         * @param key 键 不能为null
         * @return 时间(秒) 返回0代表为永久有效
         */
        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;
            }
        }
    
        /**
         * 删除缓存
         *
         * @param key 可以传一个值 或多个
         */
        @SuppressWarnings("unchecked")
        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));
                }
            }
        }
    
        // ============================String=============================
    
        /**
         * 普通缓存获取
         *
         * @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 如果time小于等于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 value 值
         * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
         * @return true成功 false 失败
         */
        public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
            try {
                if (time > 0) {
                    redisTemplate.opsForValue().set(key, value, time, timeUnit);
                } else {
                    set(key, value);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 递增
         *
         * @param key 键
         * @param delta 要增加几
         * @return
         */
        public long incr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException("递增因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, delta);
        }
    
        /**
         * 递减
         *
         * @param key 键
         * @param delta 要减少几
         * @return
         */
        public long decr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException("递减因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, -delta);
        }
    
        // ================================Map=================================
    
        /**
         * HashGet
         *
         * @param key  键 不能为null
         * @param item 项 不能为null
         * @return 值
         */
        public Object hget(String key, String item) {
            return redisTemplate.opsForHash().get(key, item);
        }
    
        /**
         * 获取hashKey对应的所有键值
         *
         * @param key 键
         * @return 对应的多个键值
         */
        public Map<Object, Object> hmget(String key) {
            return redisTemplate.opsForHash().entries(key);
        }
    
        /**
         * HashSet
         *
         * @param key 键
         * @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;
            }
        }
    
        /**
         * HashSet 并设置时间
         *
         * @param key  键
         * @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   键
         * @param item  项
         * @param value 值
         * @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   键
         * @param item  项
         * @param value 值
         * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
         * @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  键 不能为null
         * @param item 项 可以使多个 不能为null
         */
        public void hdel(String key, Object... item) {
            redisTemplate.opsForHash().delete(key, item);
        }
    
        /**
         * 判断hash表中是否有该项的值
         *
         * @param key  键 不能为null
         * @param item 项 不能为null
         * @return true 存在 false不存在
         */
        public boolean hHasKey(String key, String item) {
            return redisTemplate.opsForHash().hasKey(key, item);
        }
    
        /**
         * hash递增 如果不存在,就会创建一个 并把新增后的值返回
         *
         * @param key  键
         * @param item 项
         * @param by   要增加几(大于0)
         * @return
         */
        public double hincr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, by);
        }
    
        /**
         * hash递减
         *
         * @param key  键
         * @param item 项
         * @param by   要减少记(小于0)
         * @return
         */
        public double hdecr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, -by);
        }
    
        // ============================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   键
         * @param value 值
         * @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    键
         * @param values 值 可以是多个
         * @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    键
         * @param time   时间(秒)
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSetAndTime(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 键
         * @return
         */
        public long sGetSetSize(String key) {
            try {
                return redisTemplate.opsForSet().size(key);
            } catch (Exception e) {
               e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 移除值为value的
         *
         * @param key    键
         * @param values 值 可以是多个
         * @return 移除的个数
         */
        public long setRemove(String key, Object... values) {
            try {
                Long count = redisTemplate.opsForSet().remove(key, values);
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        // ===============================list=================================
    
        /**
         * 获取list缓存的内容
         *
         * @param key   键
         * @param start 开始
         * @param end   结束 0 到 -1代表所有值
         * @return
         */
        public List<Object> lGet(String key, long start, long end) {
            try {
                return redisTemplate.opsForList().range(key, start, end);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 获取list缓存的长度
         *
         * @param key 键
         * @return
         */
        public long lGetListSize(String key) {
            try {
                return redisTemplate.opsForList().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 通过索引 获取list中的值
         *
         * @param key   键
         * @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   键
         * @param value 值
         * @return
         */
        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   键
         * @param value 值
         * @param time  时间(秒)
         * @return
         */
        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   键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, List<Object> value) {
            try {
                redisTemplate.opsForList().rightPushAll(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 将list放入缓存
         *
         * @param key   键
         * @param value 值
         * @param time  时间(秒)
         * @return
         */
        public boolean lSet(String key, List<Object> value, long time) {
            try {
                redisTemplate.opsForList().rightPushAll(key, value);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 根据索引修改list中的某条数据
         *
         * @param key   键
         * @param index 索引
         * @param value 值
         * @return
         */
        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   键
         * @param count 移除多少个
         * @param value 值
         * @return 移除的个数
         */
        public long lRemove(String key, long count, Object value) {
            try {
                Long remove = redisTemplate.opsForList().remove(key, count, value);
                return remove;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
    }
    
    

    在我们真实的开发中,或者你们在公司,一般都可以看到一个公司自己封装RedisUtil

    测试,有了工具类后使用更加方便

      @Autowired
        private RedisUtils redisUtils;
    
    @Test
        public void test1(){
            redisUtils.set("name","xqh");
            System.out.println(redisUtils.get("name"));
        }
    
  • 相关阅读:
    Java访问修饰符private、default、protected、public的区别
    Golang 减小可执行文件大小
    想进大厂?这些TCP协议知识你应该知道
    关于编程本质那些事
    不到5分钟就被赶出来的面试,我直呼太变态了......
    最全总结 | 聊聊 Python 命令行参数化的几种方式!
    什么,api竟然有版本
    程序员养生-人体白发的机制及调养恢复
    共享股东模式:规则、优势与亮点
    延时队列DelayQueue
  • 原文地址:https://blog.csdn.net/m0_56116754/article/details/127108922