Redis学习笔记(一)

  • NoSql概述
    • Nosql四大分类
  • Redis入门
    • 简介
    • Windows安装
    • 基础使用
    • 问题
      • Redis4.0之前为什么是单线程
      • 单线程为什么这么快
      • Redis4.0后的多线程
        • 线程数的设置
        • 性能
    • 简单命令:Redis-KEY键(KEY)
    • 数据类型
      • 五大数据类型
        • String
          • 应用场景
        • List
          • 小结
        • Set
          • 应用场景
        • Hash(哈希)
          • 应用场景
        • Zset(有序集合)
      • 三种特殊数据类型
        • GEO(地理位置)
      • HyperLogLog (估计集合基数的数据结构)
      • Bitmaps (位图)
    • 事务
      • 前言
      • 命令
      • 异常
    • 监控(使用watch实现乐观锁)
    • Jedis

NoSql概述

在数据越来越庞大,愈来愈复杂的情况下,比如视频,评论,图片,地理位置这类信息.传统的关系型数据库会显得特别吃力,所以需要使用NoSQL(Not Only Sql).。

Nosql四大分类

KV键值对

  • Redis(c编写单线程) 新浪
  • Redis+Tair 美团
  • Redis+Memecache 阿里,百度

文档型数据库

  • MongoDB(c++编写,基于分布式文件存储的数据库,主要用来处理大量文档)

== MongoDB介于关系型数据库和非关系型数据库之间,是非关系型数据库中功能最丰富,最像关系型数据库的非关系型数据库==

  • ConthDB

列存储数据库

  • HBase
  • 分布式文件系统

图关系数据库(存储关系)
如朋友圈社交网络,广告推荐

  • Neo4j
  • InfoGrid

Redis入门

中文网:https://www.redis.net.cn/

简介

Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。–来源Redis中文网

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

是当下最热门的NoSql语言,被称为结构化数据库

Redis 与其他 key - value 缓存产品有以下三个特点:

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

作用:

  1. 内存存储,持久化
  2. 效率高,可用于高速缓存
  3. 发布订阅系统
  4. 地图信息分析
  5. 计时器,计数器(浏览量)

特性:

  1. 多样的数据类型
  2. 持久化
  3. 集群
  4. 事务

Windows安装

  1. 下载压缩包:
  2. 解压
  3. 开启服务
  4. 连接redis

基础使用

redis默认支持16个数据库,默认为0号数据库,可在conf文件中查看:

127.0.0.1:6379> select 1  #切换1号数据库
OK
127.0.0.1:6379[1]> dbsize  #查看数据库大小
(integer) 0
127.0.0.1:6379[1]> set name yeyu
OK
127.0.0.1:6379[1]> get name
"yeyu"
127.0.0.1:6379[1]> select 2  #切换2号数据库
OK
127.0.0.1:6379[2]> dbsize
(integer) 0
127.0.0.1:6379[2]> get name
(nil)
127.0.0.1:6379[2]> select 1
OK
127.0.0.1:6379[1]> get name
"yeyu"

问题

Redis4.0之前为什么是单线程

  1. Redis是基于内存操作,主要的性能瓶颈是内存或网络带宽,并不是cpu
  2. 使用单线程模式的Redis,其开发和维护更简单,因为单线程模式方便开发和调试。
  3. 即使使用单线程模型也能够并发地处理多客户端的请求,主要是因为Redis内部使用了基于epoll的多路复用。

单线程为什么这么快

多线程在单核cpu的情况下会涉及上下文切换,这是个耗时的操作,会消耗一定资源。多线程不一定就比单线程效率高。对于内存来说,如果没有上下文切换效率就是最高的。多次读写都是在一个cpu上,在内存系统中,这是个最佳的方案

  1. 基于内存操作:Redis的所有数据都存在内存中,因此所有的运算都是内存级别的,所以它的性能较高,速度较快,完全不逊于Memecache
  2. 数据结构简单:Redis的数据结构比较简单,是为Redis专门设计的,而这些简单的数据结构的查找和操作的时间复杂度都是O(1)。
  3. 多路复用和非阻塞IO:Redis使用IO多路复用功能来监听多个socket连接的客户端,这样就可以使用一个线程来处理多个情况,从而减少线程切换带来的开销,同时也避免了IO阻塞操作,从而大大提高了Redis的性能。
  4. 避免上下文切换:因为是单线程模型,因此就避免了不必要的上下文切换和多线程竞争,这就省去了多线程切换带来的时间和性能上的开销,而且单线程不会导致死锁的问题发生。

官方使用的基准测试结果表明,单线程的Redis可以达到10W/S的吞吐量。

Redis4.0后的多线程

Redis单线程的优点在于不但降低了Redis内部实现的负责性,也让所有操作都可以在无锁的情况下进行,并且不存在死锁和线程切换带来的性能以及时间上的消耗
但是其缺点也很明显**,单线程机制导致Redis的QPS(Query Per Second,每秒查询数)很难得到有效的提高**

  • Redis4.0中的多线程

此版本的多线程只能用于大数据量的异步删除,对于非删除操作的意义并不是很大。

  • Redis6.0中的多线程
    注意:默认为单线程,开启多线程需要在配置文件中进行配置

如果使用Redis多线程就可以分摊Redis同步读写IO的压力,以及充分利用多核CPU资源,并且可以有效的提升Redis的QPS。
在Redis中虽然使用了IO多路复用,并且是基于非阻塞的IO进行操作的,但是IO的读写本身是阻塞的。比如当socket中有数据时,Redis会先将数据从内核态空间拷贝到用户态空间,然后再进行相关操作,而这个拷贝过程是阻塞的,并且当数据量越大时拷贝所需要的的时间就越多,而这些操作都是基于单线程完成的。因此在Redis6.0中新增了多线程的功能来提高IO的读写性能,

它的主要实现思路是将主线程的IO读写任务拆分给一组独立的线程去执行,这样就可以使用多个socket的读写并行化了,但Redis的命令依旧是主线程串行执行的。
但是注意:Redis6.0是默认禁用多线程的,但可以通过配置文件redis.conf中的io-threads-do-reads 等于 true 来开启。但是还不够,除此之外我们还需要设置线程的数量才能正确地开启多线程的功能,同样是修改Redis的配置,例如设置 io-threads 4,表示开启4个线程。

线程数的设置

官方的建议是如果为4核CPU,那么设置线程数为2或3;如果为8核CPU,那么设置线程数为6.总之线程数一定要小于机器的CPU核数,线程数并不是越大越好。

性能

Redis的作者在2019年的RedisConf大会上提到,Redis6.0引入的多线程IO特性对性能的提升至少是一倍以上。

简单命令:Redis-KEY键(KEY)

查看redis常用命令

127.0.0.1:6379[1]> keys *  #查看所有key
1) "name"
2) "id"
127.0.0.1:6379[1]> flushdb  #清空当前数据库
OK
127.0.0.1:6379[1]> keys *
(empty list or set)
127.0.0.1:6379[1]>flushall  #清空全部
127.0.0.1:6379> exists name  #判断当前key是否存在,返回1即存在
(integer) 1
127.0.0.1:6379> move name 1  #将当前key移动到数据库1
(integer) 1
127.0.0.1:6379> expire name 10  #设置当前key过期时间
(integer) 1
127.0.0.1:6379> ttl name  #查看当前key剩余时间
(integer) 7
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type name  #查看当前key类型
string

数据类型

五大数据类型

String

127.0.0.1:6379> append name yuese  #在字符串后追加,如果key不存在,则相当于set key
(integer) 9
127.0.0.1:6379> get name
"yeyuyuese"
127.0.0.1:6379> strlen name  #获取字符串长度
(integer) 9
###########################################################
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> incr views  #浏览量,播放量使用redis自动增加
(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  #自动减一
(integer) 1
127.0.0.1:6379> incrby views 10  #自动加10
(integer) 11
127.0.0.1:6379> decrby views 5  #自动减5
(integer) 6
###########################################################
127.0.0.1:6379> set key1 yueseyeyu
OK
127.0.0.1:6379> get key1
"yueseyeyu"
127.0.0.1:6379> getrange key1 0 3  #获取字符串索引0-3的值
"yues"
127.0.0.1:6379> getrange key1 0 -1  #获取全部字符串
"yueseyeyu"
127.0.0.1:6379> setrange key1 1 bb  #替换指定位置字符
(integer) 9
127.0.0.1:6379> get key1
"ybbseyeyu"
#########################################################
# setex设置过期时间   setnx 不存在设置
127.0.0.1:6379> setex key2 30 "yeyu"  #设置过期时间
OK
127.0.0.1:6379> ttl key2
(integer) 20
127.0.0.1:6379> get key2
"yeyu"
127.0.0.1:6379> setnx key3 "yuese"  #如果key不存在则创建key
(integer) 1
127.0.0.1:6379> keys *
1) "key1"
2) "key3"
127.0.0.1:6379> setnx key3 "u"  #如果key存在,失败
(integer) 0
#########################################################
#mset mget
#msetnx  原子性
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  #原子性操作
(integer) 0
127.0.0.1:6379> get k4
(nil)
########################################################
# 创建对象: user:{id}:{filed}
127.0.0.1:6379> mset user:1:name yeyu user:1:age 18
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "yeyu"
2) "18"
#######################################################
# getset :先get再set,返回旧值设置新值,如果没有旧值返回nil
127.0.0.1:6379> getset user:2:name yuese  #
(nil)
127.0.0.1:6379> get user:2:name
"yuese"
127.0.0.1:6379> getset user:2:name yeyu
"yuese"
127.0.0.1:6379> get user:2:name
"yeyu"
应用场景
  1. 计数器
  2. 统计多单位数量
  3. 粉丝数
  4. 对象缓存存储

List

注意:所有list的命令都是l开头的

Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)
在redis中,我们可以把list玩成栈,队列,阻塞队列

127.0.0.1:6379> LPUSH LIST A  #向list头部存值
(integer) 1
127.0.0.1:6379> LPUSH LIST B
(integer) 2
127.0.0.1:6379> LPUSH LIST C
(integer) 3
127.0.0.1:6379> LRANGE LIST 0 -1 #通过区间获取具体值
1) "C"
2) "B"
3) "A"
127.0.0.1:6379> LRANGE LIST 0 1
1) "C"
2) "B"
127.0.0.1:6379> RPUSH LIST LOVE  #往list尾部添加值
(integer) 4
127.0.0.1:6379> LRANGE LIST 0 -1
1) "C"
2) "B"
3) "A"
4) "LOVE"
#######################################################
127.0.0.1:6379> LPOP LIST  #移除头部第一个元素
"C"
127.0.0.1:6379> RPOP LIST  #移除尾部第一个元素
"LOVE"
127.0.0.1:6379> LRANGE  LIST 0 -1
1) "B"
2) "A"
127.0.0.1:6379>
#######################################################
127.0.0.1:6379> Lindex LIST 1  #获取该key索引为1的值
"A"
127.0.0.1:6379> LINDEX LIST 0
"B"
127.0.0.1:6379> LLEN LIST  #获取列表长度
(integer) 2
#######################################################
127.0.0.1:6379> LPUSH LIST A  #可以有重复值
(integer) 3
127.0.0.1:6379> LRANGE LIST 0 -1
1) "A"
2) "B"
3) "A"
127.0.0.1:6379> LREM LIST 2 A    #移除两个值为A的数据
(integer) 2
127.0.0.1:6379> LRANGE LIST 0 -1
1) "B"
#######################################################
# LTRIM截取指定位置元素
127.0.0.1:6379> LPUSH LIST 1
(integer) 1
127.0.0.1:6379> LPUSH LIST 2
(integer) 2
127.0.0.1:6379> LPUSH LIST 3
(integer) 3
127.0.0.1:6379> LPUSH LIST 4
(integer) 4
127.0.0.1:6379> LTRIM LIST 1 3  #通过下标截断指定位置元素
OK
127.0.0.1:6379> LRANGE LIST 0 -1
1) "3"
2) "2"
3) "1"
#######################################################
#组合命令RPopLpush
127.0.0.1:6379> lpush list1 1
(integer) 1
127.0.0.1:6379> lpush list1 2
(integer) 2
127.0.0.1:6379> lpush list1 13
(integer) 3
127.0.0.1:6379> lrange list1 0 -1
1) "13"
2) "2"
3) "1"
127.0.0.1:6379> rpoplpush list1 list2  #移除列表最后一个元素移至新列表
"1"
127.0.0.1:6379> lrange list1 0 -1
1) "13"
2) "2"
127.0.0.1:6379> lrange list2 0 -1
1) "1"
#######################################################
# lset更新
127.0.0.1:6379> lset list5 1 1  #key值不存在则报错
(error) ERR no such key
127.0.0.1:6379> lset list2 0 love #更新列表索引为0的值
OK
127.0.0.1:6379> lrange list2 0 0
1) "love"
127.0.0.1:6379> exists list2  #判断key值是否存在
(integer) 1
#######################################################
# LInsert 插入
127.0.0.1:6379> LInsert list2 before love I  #在指定元素前插入
(integer) 2
127.0.0.1:6379> LInsert list2 after love you  #在指定元素后插入
(integer) 3
127.0.0.1:6379> lrange list2 0 -1
1) "I"
2) "love"
3) "you"
小结
  • 实际就是一个链表,有Node节点,可以通过before或者after来在node前后插入
  • 若是移除了所有值,空链表代表不存在
  • 在头部和尾部进行插入或更新操作效率最高,中间位置的元素效率就比较低

可以做消息排队
可以做消息队列,如:

  • LPush RPop : 消息队列
  • LPush LPop : 栈

Set

set值无序且唯一

127.0.0.1:6379> sadd s I  #添加set元素
(integer) 1
127.0.0.1:6379> sadd s love
(integer) 1
127.0.0.1:6379> sadd s love  #不可重复
(integer) 0
127.0.0.1:6379> sadd s you
(integer) 1
127.0.0.1:6379> SMEMBERS s  #查看set
1) "love"
2) "I"
3) "you"
127.0.0.1:6379> SISMember s me  #判断值是否存在
(integer) 0
127.0.0.1:6379> SISMember s you
(integer) 1
127.0.0.1:6379> scard s  #查看set元素个数
(integer) 3
#######################################################
# srem 移除
127.0.0.1:6379> srem s I  #移除指定元素
(integer) 1
127.0.0.1:6379> SMEMBERS s
1) "love"
2) "you"
#######################################################
# SRANDMEMBER 随机取值
127.0.0.1:6379> SRANDMEMBER s  #随机取值
"you"
127.0.0.1:6379> SRANDMEMBER s
"I"
127.0.0.1:6379> srandmember s 2  #随机取出指定个数的值
1) "I"
2) "you"
127.0.0.1:6379> srandmember s 2
1) "love"
2) "I"
#######################################################
# spop  随机移除元素
127.0.0.1:6379> smembers s
1) "love"
2) "I"
3) "you"
127.0.0.1:6379> spop s  #随机移除元素
"love"
127.0.0.1:6379> spop s
"I"
127.0.0.1:6379> smembers s
1) "you"
#######################################################
# smove 移动元素到其它set
127.0.0.1:6379> smove s s1  love  #没有元素显示0
(integer) 0
127.0.0.1:6379> smove s s1 you  #移动指定元素到另一set
(integer) 1
127.0.0.1:6379> smembers s1
1) "1"
2) "you"
#######################################################
#交集 并集 差集
127.0.0.1:6379> sdiff s1 s2  #差集
1) "b"
2) "a"
127.0.0.1:6379> sinter s1 s2  #交集
1) "c"
127.0.0.1:6379> sunion s1 s2  #并集
1) "a"
2) "c"
3) "d"
4) "e"
5) "b"
应用场景
  • 共同喜好
  • 微博共同关注

Hash(哈希)

本质还是一个key-value,其中value又是一个map集合
命令和string基本一样

127.0.0.1:6379> hset h f1 a
(integer) 1
127.0.0.1:6379> HGET h f1
"a"
127.0.0.1:6379> HMSET h f1 I f2 love f3 you  #创建多个值
OK
127.0.0.1:6379> hmget h f1 f2  #获取指定key的value
1) "I"
2) "love"
127.0.0.1:6379> hgetall h  #
1) "f1"
2) "I"
3) "f2"
4) "love"
5) "f3"
6) "you"
#######################################################
127.0.0.1:6379> hlen h  #获取指定key的字段个数
(integer) 3
127.0.0.1:6379> hkeys h  #获取所有key
1) "f1"
2) "f2"
3) "f3"
127.0.0.1:6379> HVALS h  #获取所有value
1) "I"
2) "love"
3) "you"
127.0.0.1:6379> hdel h1 s #删除hash指定key,对应value也消失
(integer) 1
#######################################################
127.0.0.1:6379> hset h f1 1
(integer) 1
127.0.0.1:6379> hincrby h f1 5  #设置指定增量
(integer) 6
127.0.0.1:6379> hgetall h
1) "f1"
2) "6"
127.0.0.1:6379> hsetnx h f2 love  #如果不存在则设置
(integer) 1
127.0.0.1:6379> hsetnx h f2 you
(integer) 0
#可以存对象
127.0.0.1:6379> hset user:1 name you
(integer) 1
127.0.0.1:6379> hget user:1 name
"you"
应用场景
  • 经常变动的数据(不想设置大量key),hash适合元对象的存储,string适合字符串的存储
  • 用户信息的储存

Zset(有序集合)

Zset和set区别在于前者在set的基础上给每个value赋予一个score,代表这个value排序权重。它的内部实现用的是"跳跃列表"的数据结构。

127.0.0.1:6379> zadd myset 1 A
(integer) 1
127.0.0.1:6379> zadd myset 2 B 3 C #添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "A"
2) "B"
3) "C"
127.0.0.1:6379> zadd myset 1 D  #重复添加已有序列自动排在下一位
(integer) 1
127.0.0.1:6379> ZRANGE myset 0 -1
1) "A"
2) "D"
3) "B"
4) "C"
#######################################################
#可用作工资排序
127.0.0.1:6379> zadd s 2000 a
(integer) 1
127.0.0.1:6379> zadd s 5999 b
(integer) 1
127.0.0.1:6379> zadd s 899 c
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE s -inf +inf  #工资排序
1) "c"
2) "a"
3) "b"
127.0.0.1:6379> ZREVRANGEBYSCORE -inf +inf withscores  #顺序排序
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> ZRANGEBYSCORE s 0 -1  #排序并显示工资
1) "c"
2) "899"
3) "a"
4) "2000"
5) "b"
6) "5999"
127.0.0.1:6379>  ZRANGEBYSCORE s -inf 2500 withscores  #指定范围排序并显示工资
1) "c"
2) "899"
3) "a"
4) "2000"
#######################################################
# ZRANGE 移除  ZCARD 获取有序集合中的个数
127.0.0.1:6379> ZREM s a  #移除集合中指定元素
(integer) 1
127.0.0.1:6379> ZRANGE s 0 -1
1) "c"
2) "b"
127.0.0.1:6379> ZCARD s  #获取有序集合中的个数
(integer) 3
#######################################################
#ZCOUNT 获取指定区间元素个数
127.0.0.1:6379> zadd s 1 I 2 love 3 you
(integer) 3
127.0.0.1:6379> ZRANGE s 0 -1
1) "I"
2) "love"
3) "you"
127.0.0.1:6379> ZCOUNT s 1 3
(integer) 3

三种特殊数据类型

GEO(地理位置)

有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。

#geoadd 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中
127.0.0.1:6379> geoadd china:city 116.41 39.91 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.43 34.50 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 120.20 30.26 hangzhou
(integer) 1
127.0.0.1:6379> geoadd china:city 108.95 34.26 xian
(integer) 1
#######################################################
#geopos 获取指定城市的精度与纬度
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.4099982380867"2) "39.909999566644508"
127.0.0.1:6379> geopos china:city shanghai xian
1) 1) "121.42999917268753"2) "34.499999717161309"
2) 1) "108.95000249147415"2) "34.2599996441893"

geodist 命令中指定单位的参数 unit 必须是以下单位的其中一个:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。

#######################################################
#geodist 获取指定两个城市的直线距离
127.0.0.1:6379> geodist china:city xian hangzhou
"1146984.2264"
127.0.0.1:6379>  geodist china:city xian hangzhou km
"1146.9842"

以给定经纬度为中心,返回某一半径内的所有元素
可以实现附近的人功能

#######################################################
#GEORADIUS 获取附近的人(城市)
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km  #获取附近1000km的城市
1) "xian"
2) "hangzhou"127.0.0.1:6379> GEORADIUS china:city 110 30 1500 km
1) "xian"
2) "hangzhou"
3) "shanghai"
4) "beijing"127.0.0.1:6379>  GEORADIUS china:city 110 30 1500 km  withdist  #显示直线距离
1) 1) "xian"2) "484.0254"
2) 1) "hangzhou"2) "981.3209"
3) 1) "shanghai"2) "1184.9674"
4) 1) "beijing"2) "1246.6750"127.0.0.1:6379> GEORADIUS china:city 120 30 1500 km withcoord  #显示经纬度
1) 1) "xian"2) 1) "108.95000249147415"2) "34.2599996441893"
2) 1) "hangzhou"2) 1) "120.20000249147415"2) "30.259999272896209"
3) 1) "shanghai"2) 1) "121.42999917268753"2) "34.499999717161309"
4) 1) "beijing"2) 1) "116.4099982380867"2) "39.909999566644508"127.0.0.1:6379>  GEORADIUS china:city 120 30 1500 km withcoord count 2  #限制返回两个值
1) 1) "hangzhou"2) 1) "120.20000249147415"2) "30.259999272896209"
2) 1) "shanghai"2) 1) "121.42999917268753"2) "34.499999717161309"

GEORADIUSBYMEMBER: 以给定昵称或者城市名称为中心,返回某一半径内的所有元素

#杭州一千公里内的城市
127.0.0.1:6379>  GEORADIUSBYMEMBER china:city hangzhou 1000 km
1) "hangzhou"
2) "shanghai"

HyperLogLog (估计集合基数的数据结构)

Bitmaps (位图)

其它命令可查看Redis命令

事务

前言

在Mysql这种关系型数据库中,事务遵循ACID

  • 原子性(atomicity)。一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
  • 一致性(consistency)。事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
  • 隔离性(isolation)。一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性(durability)。持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

事务的实质就是一组命令的集合,一个事务中的所有命令都会被序列化,在执行事务的过程中,命令按照先后顺序执行!
所有的命令在事务中不会直接执行,而是等到Exec执行命令才开始执行
而在Redis中,事务没有没有隔离级别的概念,单条命令可保证原子性,但事务不保证原子性!

命令

正常执行事务:

  • 开启事务MULTI
  • 命令入队...
  • 执行事务EXEC
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 k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) "v1"
4) OK
  • 放弃事务discard
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a
QUEUED
127.0.0.1:6379> set b b
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get a
(nil)

异常

编译型异常(代码问题或者命令有问题,所有的命令都不会被执行)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a
QUEUED
127.0.0.1:6379> set b b
QUEUED
127.0.0.1:6379> getset b
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set c c
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get a
(nil)

运行时异常(运行时异常,除了语法错误不会被执行且抛出异常后,其他的正确命令可以正常执行)

127.0.0.1:6379> set k "v"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k  #会执行失败
QUEUED
127.0.0.1:6379> set a a
QUEUED
127.0.0.1:6379> set b b
QUEUED
127.0.0.1:6379> get b
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "b"

监控(使用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  #监控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会再次获取监控对象的值,通过和之前的监控值对比查看其是否经其它线程改变,若值不同,则事务执行失败

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  #执行之前,另一线程更改money数据,事务执行失败
(nil)
127.0.0.1:6379> unwatch  #事务失败,解锁
127.0.0.1:6379> watch money  #再次money对象
127.0.0.1:6379> set money 1000
OK

Jedis

通过Jedis操作redis

  1. 导入依赖

<!--导入Jredis包--><!-- https://mvnrepository.com/artifact/redis.clients/jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.2.3</version></dependency><!--导入fastjson--><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.75</version></dependency>
  1. 编码测试
public class Test {public static void main(String[] args) {//1.新建jedis对象Jedis jedis = new Jedis("127.0.0.1",6379);//2.操作命令System.out.println(jedis.ping());//关闭连接jedis.close();}
}

所有的命令都通过jedis对象,具体命令在Redis学习笔记(一)

使用Jedis操作事务:

public class Test {public static void main(String[] args) {//1.新建jedis对象Jedis jedis = new Jedis("127.0.0.1",6379);JSONObject jsonObject = new JSONObject();jsonObject.put("Name","yeyu");jsonObject.put("age",18);//开启事务Transaction multi = jedis.multi();String result = jsonObject.toString();try {multi.set("user1",result);multi.set("user2",result);multi.exec();}catch (Exception e){multi.discard();e.printStackTrace();}finally {System.out.println(jedis.get("user1"));System.out.println(jedis.get("user2"));//关闭连接jedis.close();}}

public class Test {public static void main(String[] args) {//1.新建jedis对象Jedis jedis = new Jedis("127.0.0.1",6379);System.out.println(jedis.ping());jedis.flushAll();JSONObject jsonObject = new JSONObject();jsonObject.put("Name","yeyu");jsonObject.put("age",18);//开启事务Transaction multi = jedis.multi();String result = jsonObject.toString();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();}}
}

Redis学习笔记(一) 数据类型事务异常Jredis相关推荐

  1. Redis学习笔记1-Redis数据类型

    Redis数据类型 Redis支持5种数据类型,它们描述如下: Strings - 字符串 字符串是 Redis 最基本的数据类型.Redis 字符串是二进制安全的,也就是说,一个 Redis 字符串 ...

  2. redis完整笔记总结-数据类型-事务与锁-集群-分布式锁-常见问题(缓存穿透、击穿、雪崩)

    1. 数据类型 五大基本类型 String hash -> 类似map list set -> zset -> 基于set的有序集合 新增 bitmaps:其实就是string,主要 ...

  3. Redis学习笔记 - 数据类型与API(1)Key

    Redis学习笔记 - 数据类型与API(1)Key Key相关命令 1. 常用命令 命令 含义 时间复杂度 keys 查找所有符合给定模式 pattern 的 key O(N), N 为数据库中 k ...

  4. StackExchange.Redis学习笔记(二) Redis查询 五种数据类型的应用

    StackExchange.Redis学习笔记(二) Redis查询 五种数据类型的应用 原文: StackExchange.Redis学习笔记(二) Redis查询 五种数据类型的应用 Connec ...

  5. Redis学习笔记---Redis的事务

    Redis学习笔记-Redis的事务 1. Redis事务(弱事务)和Mysql事务对比 Atomicity(原子性):构成事务的的所有操作必须是一个逻辑单元,要么全部执行,要么全部不执行. Redi ...

  6. Redis学习笔记(B站狂神说)(自己总结方便复习)

    Redis学习笔记B站狂神说 redis: 非关系型数据库 一.NoSQL概述 1.为什么要用Nosql 1.单机Mysql的年代 思考一下,这种情况下:整个网站的瓶颈是什么? 1.数据量如果太大,一 ...

  7. Redis(学习笔记)

    Redis学习笔记 1.NoSQL数据库 1.1解决的问题 1.1.1解决CPU及内存压力 1.1.2解决IO压力 1.2NoSQL数据库概述 1.2.1什么是NoSQL数据库 1.2.2适用与不适用 ...

  8. Redis学习笔记②实战篇_黑马点评项目

    若文章内容或图片失效,请留言反馈.部分素材来自网络,若不小心影响到您的利益,请联系博主删除. 资料链接:https://pan.baidu.com/s/1189u6u4icQYHg_9_7ovWmA( ...

  9. Redis学习笔记——SpringDataRedis的使用

    与Spring集成 我需要哪些jar包? <dependency><groupId>org.springframework.data</groupId><ar ...

最新文章

  1. 易语言 基础知识一及认识句柄 局部变量
  2. opencv读取手机摄像头
  3. Swift 本地推送通知UILocalNotification
  4. NTU 课程笔记 CV6422 假设检验
  5. 软工网络15团队作业4——Alpha阶段敏捷冲刺之Scrum 冲刺博客(Day5)
  6. 你真的了解Ioc与AOP 吗?(2)
  7. Qt的安装和使用中的常见问题(详细版)
  8. 查看地区的ip段_「教程」CloudFlare 自选 IP优化网站速度
  9. linux安装curl扩展
  10. homestead修改php版本
  11. 在Java中通过线程池实现异步执行
  12. idea git push 码云: Remote: [31mYou do not have permission to push to the repository via HTTPS
  13. 学习英文必记的九种前缀与三种后缀
  14. java服务器限速下载_Java文件下载限速
  15. windows如何获取端口号
  16. 如何优雅的完成一场说来就来的APP自建
  17. 开源软件及国内发展趋势
  18. matlab中的下标都是从1开始
  19. 武大计算机学院2017年博士分数线,武汉大学高等研究院2017年博士研究生综合考核录取工作通知...
  20. 计算机硬件故障的维修方法,计算机硬件故障和维修方法分析.doc

热门文章

  1. 热血江湖服务器维护时间,热血江湖2016年10月8日停机更新维护公告 喜庆大烟花套餐国庆上架...
  2. Hive基础知识(三)--分桶表
  3. 低水平黑客也可远程攻击工业电机并造成物理破坏
  4. 控制语句-条件和分支
  5. 基于Python的指数基金量化投资-为什么量化指数基金投资
  6. 图解数据分析(5) | 核心步骤2 - 数据清洗与预处理(数据科学家入门·完结)
  7. JSHint 与 JSLint 的区别
  8. 帝国CMS7.5仿完美游戏台游戏视频网站模板
  9. 打开.py文件的方法
  10. 不登录QQ,恢复QQ聊天中的语音到电脑上,并导出为MP3