微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

2021-06-29

Redis概述

Redis简介

  • Redis:Remote Dictionary Server(远程字典服务器)
  • Redis是当前比较热门的NOsql系统之一,它是一个开源的使用ANSI C语言编写的key-value存储系统(区别于MysqL的二维表格的形式存储。)。和Memcache类似,但很大程度补偿了Memcache的不足。和Memcache一样,Redis数据都是缓存在计算机内存中,不同的是,Memcache只能将数据缓存到内存中,无法自动定期写入硬盘,这就表示,一断电或重启,内存清空,数据丢失。所以Memcache的应用场景适用于缓存无需持久化的数据。而Redis不同的是它会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,实现数据的持久化。

Redis特点

  • Redis是一款内存型非关系型数据库,速度快;官方测试Redis读取的速度是110000次/s,写的速度是81000次/s
  • 支持多种数据结构:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合);以及三种特殊类型
  • 支持过期时间,支持事务,消息订阅
  • redis 单条命令是保证原子性的,但是整个事务不保证原子性 没有事务隔离级别的概念
  • 持久化,主从复制(集群)

Redis应用场景

  • 数据缓存,提高性能
  • 会话缓存,保存web会话信息
  • 排行榜/计数器
  • 消息队列

Redis的安装

  • 首先上传Redis压缩包到自定义文件夹,并解压到对应文件

    tar -zvxf redis-6.0.8.tar.gz 
    
  • 注意:redis6的编译需要gcc9版本,centos的gcc是4.8版本,因此需要更新成gcc9版本

    #首先可以查看gcc 的版本
    gcc -v
    #升级到gcc 9
    yum -y install centos-release-scl
    yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
    scl enable devtoolset-9 bash
    #需要注意的是scl命令启用只是临时的,退出shell或重启就会恢复原系统gcc版本。
    #如果要长期使用gcc 9.3的话,执行以下命令,这样退出shell重新打开就是新版的gcc了
    echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
    
  • 进入到Redis的解压目录,执行make命令

    [root@localhost redis-6.0.8]# make
    cd src && make all
    make[1]: 进入目录“/home/programs/Redis/redis-6.0.8/src”
        CC Makefile.dep
    make[1]: 离开目录“/home/programs/Redis/redis-6.0.8/src”
    make[1]: 进入目录“/home/programs/Redis/redis-6.0.8/src”
    ......
    
    Hint: It's a good idea to run 'make test' ;)
    
    make[1]: 离开目录“/home/programs/Redis/redis-6.0.8/src”
    [root@localhost redis-6.0.8]# 
    
  • make命令执行完后同样在解压目录下执行make install命令

    [root@localhost redis-6.0.8]# make install
    cd src && make install
    make[1]: 进入目录“/home/programs/Redis/redis-6.0.8/src”
        CC Makefile.dep
    make[1]: 离开目录“/home/programs/Redis/redis-6.0.8/src”
    make[1]: 进入目录“/home/programs/Redis/redis-6.0.8/src”
    
    Hint: It's a good idea to run 'make test' ;)
    
        INSTALL install
        INSTALL install
        INSTALL install
        INSTALL install
        INSTALL install
    make[1]: 离开目录“/home/programs/Redis/redis-6.0.8/src”
    [root@localhost redis-6.0.8]# 
    
  • 复制redis.conf文件到指定目录,这样解压目录下的redis.conf文件就不会被影响

    cp redis.conf /usr/local/bin/myconfig
    
  • 修改redis.conf文件,让redis以守护线程的方式启动(即后台运行)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bocEFrat-1624963559710)(Redis.assets/image-20200925171450022.png)]

  • 在/usr/local/bin目录下指定配置文件启动服务端 redis-server

    redis-server myconfig/redis.conf
    
  • 在/usr/local/bin目录下启动客户端

    redis-cli -p 6379
    
  • 测试

    127.0.0.1:6379> ping
    PONG
    127.0.0.1:6379> set "name" "zs"
    OK
    127.0.0.1:6379> get "name"
    
  • 重新开启一个连接查看redis的进程是否启动

    [root@localhost bin]# ps -ef|grep redis
    root      37678      1  0 16:50 ?        00:00:03 redis-server 127.0.0.1:6379
    root      71203  11881  0 17:21 pts/0    00:00:00 redis-cli -p 6379
    root      71703  69756  0 17:22 pts/2    00:00:00 grep --color=auto redis
    
  • 关闭redis服务

    127.0.0.1:6379> shutdown
    not connected> exit
    
  • 再次查看进程

    [root@localhost bin]# ps -ef|grep redis
    root      77478  69756  0 17:24 pts/2    00:00:00 grep --color=auto redis
    
  • docker部署的redis进入bash命令

    • 首先进入redis的安装目录下

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b7rYmPMS-1624963559714)(Redis.assets/image-20201125155539958.png)]

    • 进入bash命令行

      docker exec -it redis redis-cli
      #进入后如果提示
      127.0.0.1:6379> (error) NOAUTH Authentication required
      127.0.0.1:6379>  auth "yourpassword"  #输入你的密码即可
      127.0.0.1:6379> ping
      127.0.0.1:6379> pong
      
  • 使用RedisDesktopManager连接Redis

    • 修改redis.conf文件

      #修改bind   原来:  bind  127.0.0.1   代表本地回环地址,访问redis服务只能通过本机的客户端连接,而无法通过远程连接
      #修改为:将此行注释或者讲ip改成0.0.0.0   这样就能接受所有来自于可用网络接口的连接
      bind 0.0.0.0
      #修改protected mode  保护模式
      protected-mode no
      #补充 redis.conf 中的daemonize 配置
      
       daemonize yes #代表开启守护进程模式。此时是单进程多线程的模式,redis将在后台运行,并将pid写入redis.conf--pidfile文件中,此时redis将一直运行,除非手动kill
      
       daemonize no #当前界面将进入redis的命令行界面,exit强制退出或者关闭连接工具(xshell等)都会导致redis进程退出
       #修改配置文件后需要重启redis!!!
       #注:启动redis  一定要指定配置文件,否则配置文件不生效的
      
    • 连接

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wk7nys2K-1624963559716)(Redis.assets/image-20200925174508772.png)]

    • 注意:安装完后,记得在防火墙配置端口

Redis基础知识

  • Redis是单进程单线程(redis6版本采用了多线程)

    • 采用 I/O 多路复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗)
    • 多线程处理可能涉及到锁,多线程处理会涉及到线程切换而消耗cpu
    • 单进程不存在线程安全问题
    • 缺点:无法发挥多核cpu性能,不过可以通过在单机开多个Redis实例来完善
  • Redis认有16个数据库,类似数组下表从零开始,初始认使用零号库

    #切换到下标为1的数据库
    127.0.0.1:6379> select 1
    OK
    127.0.0.1:6379[1]> set name zs #往下标为1的库中设值
    OK
    127.0.0.1:6379[1]> keys *  #查看下标为1的库下所有的key
    1) "name"
    127.0.0.1:6379[1]> get name
    "zs"
    
  • 常用基本命令

    127.0.0.1:6379[1]> dbsize #查看当前库的key数量
    (integer) 1
    127.0.0.1:6379[1]> flushdb  #清空当前库
    OK
    127.0.0.1:6379[1]> flushall #清空所有库
    OK
    
  • key常用命令

    #该命令用于在 key 存在时删除 key
    del key
    #序列化给定 key ,并返回被序列化的值
    dump key 
    #检查给定 key 是否存在
    exists key 
    #为给定 key 设置过期时间,以秒计 
    expire key seconds
    #expireat 的作用和 expire 类似,都用于为 key 设置过期时间。 不同在于 expireat 命令接受的时间参数是 unix 时间戳(unix timestamp) 
    expireat key timestamp 
    #设置 key 的过期时间以毫秒计 
    pexpire key milliseconds 
    #设置 key 过期时间的时间戳(unix timestamp) 以毫秒计 
    pexpireat key milliseconds-timestamp 
    #查找所有符合给定模式( pattern)的 key 
    keys pattern 
    #将当前数据库的 key 移动到给定的数据库 db 当中 
    move key db 
    #移除 key 的过期时间,key 将持久保持 
    persist key 
    #以毫秒为单位返回 key 的剩余的过期时间 
    pttl key 
    #以秒为单位,返回给定 key 的剩余生存时间(ttl, time to live) 
    ttl key 
    #从当前数据库随机返回一个 key 
    randomkey 
    #修改 key 的名称 
    rename key newkey 
    #仅当 newkey 不存在时,将 key 改名为 newkey
    renamenx key newkey 
    #返回 key 所储存的值的类型
    type key 
    

Redis的五大基本数据类型

String(字符串类型)

  • 字符串类型是编程语言和应用程序中最常见和最有用的数据类型,也是Redis的基本数据类型之一,事实上,Redis中所有键都必须是字符串。

    127.0.0.1:6379> set name zs  #设置一个值
    OK
    127.0.0.1:6379> get name  # 取值
    "zs"
    127.0.0.1:6379> getrange name 0 1  #取出key中下标 0~1的字符 闭区间 []
    "zs"
    127.0.0.1:6379> set email [email protected]
    OK
    127.0.0.1:6379> getrange email 2 5 #取出key中下标2~5的字符
    "3233"
    127.0.0.1:6379> set name 1234
    OK
    127.0.0.1:6379> setrange name 2 x #将下标为2的字符替换成x
    (integer) 4
    127.0.0.1:6379> get name
    "12x4"
    127.0.0.1:6379> del name  #删除一个键
    (integer) 1
    127.0.0.1:6379> get name
    (nil)
    127.0.0.1:6379> exists name #判断一个键是否存在
    (integer) 0
    127.0.0.1:6379> exists email
    (integer) 1
    127.0.0.1:6379> set name lisi
    OK
    127.0.0.1:6379> expire name 30 #设置一个键的过期时间 单位秒
    (integer) 1
    127.0.0.1:6379> ttl name#查看一个键还有多久过期
    (integer) 27  #27秒
    127.0.0.1:6379> set name wer
    OK
    127.0.0.1:6379> getset name abc #将给定 key 的值设为 value ,并返回 key 的旧值(old value)
    "wer"
    127.0.0.1:6379> strlen name #返回key所存储值的长度
    (integer) 3
    127.0.0.1:6379> setex age 10 18 #将值 value 关联到 key ,并将 key 的过期时间设为 seconds (以秒为单位)
    OK
    127.0.0.1:6379> setnx age 22 #只有在 key 不存在时设置 key 的值
    (integer) 1
    127.0.0.1:6379> setnx name ttt  #key存在时 设置失败
    (integer) 0
    127.0.0.1:6379> mset k1 vi k2 v2 k3 v3 #批量设置键值
    OK
    127.0.0.1:6379> mget k1 k2 k3 #批量取值
    1) "vi"
    2) "v2"
    3) "v3"
    127.0.0.1:6379> msetnx k4 v4 k5 v5 k6 v6 #批量设置多个不存在的KV对
    (integer) 1
    127.0.0.1:6379> psetex oo 100 abc #与setex 命令相似,但它以毫秒为单位设置 key 的生存时间
    OK
    127.0.0.1:6379> set age 1
    OK
    127.0.0.1:6379> get age
    "1"
    127.0.0.1:6379> incr age #自增一
    (integer) 2
    127.0.0.1:6379> incr age
    (integer) 3
    127.0.0.1:6379> incrby age 3 #设置自增的增量为3
    (integer) 6
    127.0.0.1:6379> incrby age 3
    (integer) 9
    127.0.0.1:6379> incrbyfloat age 0.5 #设置自增的增量为浮点类型 0.5
    "9.5"
    127.0.0.1:6379> incrbyfloat age 0.5
    "10"
    127.0.0.1:6379> set class 10
    OK
    127.0.0.1:6379> decr class #自减一
    (integer) 9
    127.0.0.1:6379> decr class
    (integer) 8
    127.0.0.1:6379> decrby class 2 #设置自减的减量为2
    (integer) 6
    127.0.0.1:6379> decrby class 2
    (integer) 4
    127.0.0.1:6379> set dept it
    OK
    127.0.0.1:6379> get dept
    "it"
    127.0.0.1:6379> append dept info #如果 key 已经存在并且是一个字符串, append 命令将指定的 value 追加到该 key 原来值(value)的末尾
    (integer) 6
    127.0.0.1:6379> get dept
    "itinfo"
    
  • String类型的应用场景:计数器,如微博的评论数、点赞数、分享数,抖音作品的收藏数,京东商品的销售量、评价数等。

List(列表类型)

  • 列表是应用我只是应该程序开发中非常有用的数据类型之一,列表能存在一组对象,因此它也可以被用于栈或者队列,在Redis中,与键相关的联的值可以是字符串组成的列表,Redis中的列表更像是数据结构中的双向链表。

    127.0.0.1:6379> lpush name v1 v2 v3 v4  #往列中添加4个值
    (integer) 4
    127.0.0.1:6379> llen name #查看列表长度
    (integer) 4
    127.0.0.1:6379> lrange name  0 -1 #查看列表中所有的值
    1) "v4"
    2) "v3"
    3) "v2"
    4) "v1"
    127.0.0.1:6379> lpop name #移除最后进的一个元素 先进后出,后进先出
    "v4"
    127.0.0.1:6379> lrange name  0 -1
    1) "v3"
    2) "v2"
    3) "v1"
    127.0.0.1:6379> rpop name #移除最早进的一个元素
    "v1"
    127.0.0.1:6379> lrange name  0 -1
    1) "v3"
    2) "v2"
    127.0.0.1:6379> lpush name v1 v2 v3 v4 v5 v6
    (integer) 6
    127.0.0.1:6379> lrange name 0 -1
    1) "v6" 
    2) "v5"
    3) "v4"
    4) "v3"
    5) "v2"
    6) "v1"
    127.0.0.1:6379> lindex name 2 #取出下标为2的元素
    "v4"
    127.0.0.1:6379> lindex name 1 #取出下标为1的元素
    "v5"
    127.0.0.1:6379> lindex name 0 #取出下标为0的元素,最后进的下标为0
    "v6"
    127.0.0.1:6379> lrem name 2 v3 #移除列表中的元素 语法 lrem key count value
    #count不同则代表不同的含义,详情见https://blog.csdn.net/qq_41384743/article/details/98211366
    (integer) 1
    127.0.0.1:6379> lrange name 0 -1
    1) "v6"
    2) "v5"
    3) "v4"
    4) "v2"
    5) "v1"
    127.0.0.1:6379> lrange name 0 -1
    1) "v6"
    2) "v5"
    3) "v4"
    127.0.0.1:6379> lset name 0 v1 #根据索引设置元素的值  存在就更新 不存在就报错
    OK
    127.0.0.1:6379> lrange name 0 -1
    1) "v1"
    2) "v5"
    3) "v4"
    127.0.0.1:6379> ltrim name 1 2 #截取下标从1 ~ 2的元素,类似于subString截取字符串
    OK
    127.0.0.1:6379> lrange name 0 -1
    1) "v5"
    2) "v4"
    127.0.0.1:6379> rpush name k1 #在尾部追加一个元素
    (integer) 3
    127.0.0.1:6379> lrange name 0 -1
    1) "v5"
    2) "v4"
    3) "k1"
    127.0.0.1:6379> lrange name 0 -1
    1) "v6"
    2) "v6"
    3) "v5"
    4) "v4"
    5) "k1"
    127.0.0.1:6379> linsert name before v5 a1 #在v5前插入值 a1
    (integer) 6
    127.0.0.1:6379> lrange name 0 -1
    1) "v6"
    2) "v6"
    3) "a1"
    4) "v5"
    5) "v4"
    6) "k1"
    

Set(集合)

  • 集合类型是由唯一,无序对象组成的集合(collection).它经常用于测试某个成员是集合中,重复项删除和集合运算(求并,交,差集),Redis的值对象可以是字符串集合。

    127.0.0.1:6379> sadd name v1 v2 v3 v4 v5 v6 #往集合中添加值
    (integer) 6
    127.0.0.1:6379> scard name #获取集合中元素的数量
    (integer) 6
    127.0.0.1:6379> smembers name #查看集合中所有的成员
    1) "v3"
    2) "v4"
    3) "v1"
    4) "v6"
    5) "v2"
    6) "v5"
    127.0.0.1:6379> sadd name1 v5 v6 v7 #新建一个集合
    (integer) 3
    127.0.0.1:6379> sdiff name name1 #取name和name1的差集(就是指前面那个集合有后面那个集合没有的元素)
    1) "v2"
    2) "v3"
    3) "v1"
    4) "v4"
    127.0.0.1:6379> sdiff name1 name
    1) "v7"
    127.0.0.1:6379> Smembers name
    1) "v6"
    2) "v4"
    127.0.0.1:6379> Smembers name1
    1) "v6"
    2) "v7"
    3) "v5"
    127.0.0.1:6379> sinter name name1 #取两个集合的交集
    1) "v6"
    127.0.0.1:6379> sunion name name1 #取两个集合的并集
    1) "v6"
    2) "v7"
    3) "v4"
    4) "v5"
    127.0.0.1:6379> SISMember name v2 #判断某个元素是否存在集合中
    (integer) 1
    127.0.0.1:6379> srem name v1 v2 #移除一个或多个元素
    (integer) 2
    127.0.0.1:6379> Smembers name
    1) "v6"
    2) "v3"
    3) "v4"
    4) "v5"
    127.0.0.1:6379> srem name v3 #移除指定元素
    (integer) 1
    127.0.0.1:6379> Smembers name
    1) "v6"
    2) "v4"
    3) "v5"
    127.0.0.1:6379> srandmember name #随机一个元素
    "v5"
    127.0.0.1:6379> srandmember name 2 #随机出多个元素
    1) "v6"
    2) "v4"
    127.0.0.1:6379> spop name #随机删除一个
    "v5"
    127.0.0.1:6379> Smembers name
    1) "v6"
    2) "v4"
    127.0.0.1:6379> smove name1 name v7 #将集合name1中的元素v7移动到name集合中
    (integer) 1
    127.0.0.1:6379> Smembers name1
    1) "v6"
    2) "v5"
    127.0.0.1:6379> Smembers name
    1) "v6"
    2) "v7"
    3) "v4"
    
  • Set集合应用场景:粉丝列表,好友,感兴趣的人集合,共同关注

ZSet(sortset)有序集合

  • 有序集合是一个类似于set但是更复杂的数据类型,单词sorted意为着这种集合中的每个元素都有一个可用于排序的权重,并且我们可以按顺序从集合中得到元素在某些需要一个保持数据有序的场景中,使用这种原生的序的特性是很方便的。

    127.0.0.1:6379> zadd myzset 1 one 2 two 3 three 4 four #往集合中添加值
    (integer) 4
    127.0.0.1:6379> zrange myzset 0 -1 #查看集合中所有的值
    1) "one"
    2) "two"
    3) "three"
    4) "four"
    127.0.0.1:6379> zrange myzset 0 -1 withscores #加上withscores就可以显示出一一对应关系
    1) "one"
    2) "1"
    3) "two"
    4) "2"
    5) "three"
    6) "3"
    7) "four"
    8) "4"
    127.0.0.1:6379> zcard myzset #获取集合长度
    (integer) 4
    127.0.0.1:6379> zrangebyscore myzset -inf +inf #从小到大排序
    1) "one"
    2) "two"
    3) "three"
    4) "four"
    127.0.0.1:6379> zrevrange myzset 0 -1 #从大到小排序
    1) "four"
    2) "three"
    3) "two"
    4) "one"
    127.0.0.1:6379> zrem myzset four #移除指定元素
    (integer) 1
    127.0.0.1:6379> zrevrange myzset 0 -1
    1) "three"
    2) "two"
    3) "one"
    127.0.0.1:6379> zcount myzset 0 2 #获取指定区间的元素个数
    (integer) 2
    
  • Zset 应用场景:1.工资表排序; 2.成绩表排序;3.带权重进行判断;4.排行榜;

Hash(哈希)

  • 哈希表示字段和值之间的映射关系,与JAVA中的Map类似,Redis数据集本身就可以看做一个哈希,其中字符串类型的键关联到如字符串和列表之类的数据对象,而Reidis的数据对象也可以再次使用哈希,其字段和值必须是字符串。

    127.0.0.1:6379> hset name k1 v1 k2 v2 k3 v3 k4 v4 k5 v5 k6 v6 #设置KV对
    (integer) 6
    127.0.0.1:6379> hgetall name #取除所有的键值对
     1) "k1"
     2) "v1"
     3) "k2"
     4) "v2"
     5) "k3"
     6) "v3"
     7) "k4"
     8) "v4"
     9) "k5"
    10) "v5"
    11) "k6"
    12) "v6"
    127.0.0.1:6379> hget name k1 #获取一个键下的值
    "v1"
    127.0.0.1:6379> hexists name k2 #判断是否有这个键
    (integer) 1
    127.0.0.1:6379> hdel name  k5 v5 #删除一个键值对
    (integer) 1
    127.0.0.1:6379> hkeys name #获取哈希表中的所有的key
    1) "k1"
    2) "k2"
    3) "k3"
    127.0.0.1:6379> hvals name #获取哈希表中的所有的value
    1) "v1"
    2) "v2"
    3) "v3"
    127.0.0.1:6379> hlen name #获取哈希表中字段的数量
    (integer) 3
    
  • Hash的应用场景:对象信息的保存,经常变更的数据

三种特殊数据类型

Geospatial 地理位置

  • 应用场景:朋友的定位,附近的人,打车距离计算。

  • geo底层其实就是Zset,可以使用Zset命令来操作geo

    #geoadd key 经度 纬度 城市名   注意:两极无法直接添加,一般会下载城市数据,通过java程序一次性导入
    127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing #添加一个城市经纬度记录
    (integer) 1
    127.0.0.1:6379> geopos china:city beijing #获取一个城市的经纬度,注意:必须得存在你这个key中
    1) 1) "116.39999896287918091"
       2) "39.90000009167092543"
    127.0.0.1:6379> geodist china:city beijing shanghai km #获取两个地点之间的距离 可指定单位
    "1068.7817"
    127.0.0.1:6379> georadius china:city 121 31 1100 km  [withdist withcoord count 1] #获取以121,31经纬度为中心,寻找方圆1100km的城市,withdist 显示距离 withcoord显示坐标 count 限制数量
    1) "changsha"
    2) "shanghai"
    3) "beijing"
    127.0.0.1:6379> geohash china:city hangzhou shanghai # 将二维坐标转换为一维字符串 ,两个字符串越接近,距离越近
    1) "wtmknuxtmb0"
    2) "wtw3s77j9j0"
    127.0.0.1:6379> georadiusbyMember china:city shanghai 200 km #获取以上海为中心半径为200km周围的城市,注意:只获取到key中存在的
    1) "hangzhou"
    2) "shanghai"
    

Hyperloglog 基数

  • 基数:基数就是集合中不重复的元素

  • 应用场景:可以统计网页的访问次数

    127.0.0.1:6379> pfadd name java html sql #添加元素到key中
    (integer) 1
    127.0.0.1:6379> pfcount name #统计数量
    (integer) 3
    127.0.0.1:6379> pfadd age 18
    (integer) 1
    127.0.0.1:6379> pfmerge age name #合并,将name中的元素合并到age中
    OK
    127.0.0.1:6379> pfcount name
    (integer) 3
    127.0.0.1:6379> pfcount age
    (integer) 4
    

Bitmap 位图

  • Bitmap:只有两个状态 非0即1

  • 应用场景:可以统计用户信息: 活跃 不活跃 登录登录 打卡 未打卡 签到 未签到

    #使用bitmap记录周一到周日的打卡情况0~6代表周一到周日 后面的那个1或0代表打卡和没打卡
    127.0.0.1:6379> setbit week 0 1
    (integer) 0
    127.0.0.1:6379> setbit week 1 1
    (integer) 0
    127.0.0.1:6379> setbit week 2 1
    (integer) 0
    127.0.0.1:6379> setbit week 3 0
    (integer) 0
    127.0.0.1:6379> setbit week 4 1
    (integer) 0
    127.0.0.1:6379> setbit week 5 1
    (integer) 0
    127.0.0.1:6379> setbit week 6 0
    (integer) 0
    127.0.0.1:6379> getbit week 1 #查看周二是否打卡
    (integer) 1
    127.0.0.1:6379> getbit week 6 #查看周日是否打卡
    (integer) 0
    127.0.0.1:6379> bitcount week #统计一周打卡的数量
    (integer) 5
    

事务

  • 可以一次执行多个命令,本质是一组命令的集合。一个事务中的所有命令都会序列化,按顺序地串行化执行而不会被其它命令插入,不许加塞

  • Redis事务的特性:一次性,顺序性,排他性

  • redis的单条命令是保证原子性的,但是Redis的事务不保证原子性,没有隔离级别的概念

  • 所有的命令在事务中,并没有直接被执行,而是当我们发起执行命令的时候才会执行

  • redis事务的三个阶段:

    • 开启事务:multi
    • 命令入队
    • 执行事务:exec
  • 放弃事务:discard 类似于回滚

  • 正常执行一段事务

    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> get k1
    QUEUED
    127.0.0.1:6379> exec  #执行事务
    1) OK
    2) OK
    3) OK
    4) "v1"
    
    #测试回滚事务
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> mset k1 v1 k2 v2 k4 v4
    QUEUED
    127.0.0.1:6379> discard  #取消事务,类似于回滚事务
    OK
    127.0.0.1:6379> get k4 #事务中的命令都不会执行
    (nil)
    
    
  • 编译型异常(代码有问题,命令有错),事务中命令都不会执行

    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 k1  #所有命令都不会执行
    (nil) 
    
    
  • 运行时异常(类似于1/0) 如果事务队列中存在语法性错误,只有错的不执行会抛出异常,其他命令正常执行

    127.0.0.1:6379> set k1 v1  
    OK
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> incr k1  #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 k2
    QUEUED
    127.0.0.1:6379> exec
    1) (error) ERR value is not an integer or out of range  # k1是字符串不能自增,其他正常执行
    2) OK
    3) OK
    4) "v2"
    
    

监控

  • 悲观锁:认为什么时候都会出问题,无论做什么都会加锁,会影响性能

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

  • watch 监视

    #正常执行
    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 #监视这个对象
    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
    
    
  • 多线程测试,类似于乐观锁的操作

    #线程一
    127.0.0.1:6379> watch money #监视对象
    OK
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> decrby money 10
    QUEUED
    127.0.0.1:6379> incrby out 10 #此时并未执行事务,这时线程二对数据进行修改
    QUEUED
    
    #线程二
    127.0.0.1:6379> get money
    "80"
    127.0.0.1:6379> set money 1000
    OK
     
    #此时线程一再去执行时 
    127.0.0.1:6379> exec #执行失败
    (nil)
    
    #此时可以先放弃监视,然后重新监视,再开启事务重新操作
    127.0.0.1:6379> unwatch
    OK
    127.0.0.1:6379> watch money
    OK
    127.0.0.1:6379> multi
    OK
    127.0.0.1:6379> decrby money 200
    QUEUED
    127.0.0.1:6379> incrby out 200
    QUEUED
    127.0.0.1:6379> exec  #比对监视的值是否发生变化,如果没变化就执行成功,反之失败
    1) (integer) 800
    2) (integer) 220
    

Jedis

  • Jedis是Redis官方推荐的Java连接开发工具

  • 基本使用

    • 导入相关依赖

      <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
      <dependency>
          <groupId>redis.clients</groupId>
          <artifactId>jedis</artifactId>
          <version>3.3.0</version>
      </dependency>
      
      <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>fastjson</artifactId>
          <version>1.2.73</version>
      </dependency>
      
    • 测试

      import redis.clients.jedis.Jedis;
       
      public class JedisDemo {
          public static void main(String[] args) {
              //连接Redis 服务,注意连接远程的要保证远程的开启了,本地的不用
              Jedis jedis = new Jedis("192.168.2.130",6379);
              // 如果 Redis 服务设置来密码,需要下面这行,没有就不需要
              // jedis.auth("123456"); 
              System.out.println("连接成功");
              //查看服务是否运行
              System.out.println("服务正在运行: "+jedis.ping());
              //所有的API就是对应的命令...自行测试
          }
      }
      
      //Jedis配合事务测试
      import com.alibaba.fastjson.JSONObject;
      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.Transaction;
      
      public class JedisDemo {
          public static void main(String[] args) {
              Jedis jedis = new Jedis("192.168.2.130",6379);
              System.out.println(jedis.ping());
              JSONObject jsonObject = new JSONObject();
              jsonObject.put("hello","world");
              jsonObject.put("nihao","shijie");
              //开启事务
              Transaction transaction = jedis.multi();
              String result = jsonObject.toJSONString();
              try{
                  transaction.set("str1",result);
                  transaction.set("str2",result);
                  int i = 1/0; //模拟异常事务
              }catch (Exception e){
                  transaction.discard();  //放弃事务
                  e.printstacktrace();
              }finally {
                  System.out.println(jedis.get("str1"));
                  System.out.println(jedis.get("str2"));
                  jedis.close();  //关闭连接
              }
          }
      }
      

SpringBoot整合Redis

  • 创建SpringBoot项目,引入相关依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  • 编写主配置文件

    spring.redis.host=192.168.2.130
    spring.redis.port=6379
    
  • 测试

    @Autowired
    private Redistemplate redistemplate; //注入Redistemplate
    @Test
    void contextLoads() {
        //获得连接
        redistemplate.getConnectionFactory();
        //set一个字段
        redistemplate.opsForValue().set("name","zs");
    	//注意:当我们传递对象的使用都需要将对象序列化
        //实体类对象直接实现Serializable接口,其他的可以通过下列方式,将user对象序列化
        //String u = new ObjectMapper().writeValueAsstring(user);
        System.out.println(redistemplate.opsForValue().get("name"));
        //其余的API与命令形式类似...自行测试
        //企业一般会对原生的API进行封装,封装成一个RedisUtils或RedisTools
    }
    
  • 自定义Redistemplate,开发中使用自定义的Redistemplate

    package com.hr.springbootredis.config;
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.Redistemplate;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    @Configuration
    public class RedisConfig {
    
        // 这是我给大家写好的一个固定模板,大家在企业中,拿去就可以直接使用!
        // 自己定义了一个 Redistemplate
        @Bean
        @SuppressWarnings("all")
        public Redistemplate<String, Object> redistemplate(RedisConnectionFactory
                                                           factory) {
            // 我们为了自己开发方便,一般直接使用 <String, Object>
            Redistemplate<String, Object> template = new Redistemplate<String,
            Object>();
            template.setConnectionFactory(factory);
            // Json序列化配置
            Jackson2JsonRedisSerializer 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);
            // String 的序列化
            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;
         }
    }
    
    

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐