3 Redis基本知识


3 Redis基本知识

redis 有 16 个数据库,默认使用第 0 个数据库。

# 切换数据库
select 5

# 当前数据库大小
dbsize

# 查看所有的 key
keys *

# 清空当前数据库
flushdb

- Redis是单线程的!

  • 明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了!所有就使用了单线程了!
  • Redis 是 C 语言写的,官方提供的数据为100000+的 QPS,完全不比同样是使用key-vale的Memecache差!

- Redis 为什么单线程还这么快?

  • 1、误区1∶高性能的服务器一定是多线程的?
  • 2、误区2∶多线程(CPU上下文会切换!)一定比单线程效率高!
    • CPU> 内存> 硬盘的速度!
  • 核心: redis 是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的,多线程(CPU上下文会切换︰耗时的操作!!!),对于内存系统来说,如果没有上下文切换效率就是最高的!多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案!

Redis 简介

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件 MQ。它支持多种类型的数据结构,如字符串 ( strings )散列 ( hashes )列表( lists )集合( sets ), 有序集合(sorted sets)
与范围查询,bitmaps , hyperloglogs 和地理空间 ( geospatial)索引半径查询。Redis内置了复制( replication ),LUA脚本 (Lua
scripting ),LRU驱动事件(LRU eviction ),事务 ( transactions )和不同级别的磁盘持久化(persistence ),并通过Redis哨兵 ( Sentinel )和自动分区(Cluster)提供高可用性( high availability )。

# 移动 key 到 3 号数据库
move age 3
# 是否存在某个 key
exists age
# 设置失效时间,设置age 10秒后失效
expire age 10
# 查看剩余失效时间
ttl age
# 返回当前 key 所存储的值的类型
type age

Redis 五大数据类型

String 字符串

# 追加字符串。如果当前 key 不存在,相当于 set key
append name "hello"

# 获取字符串长度
strlen name

###############################################

# 数字加减
# 加1
set age 10
incr age  # +1
# 减1
decr age  # -1
# 加任意值
incrby age 10
# 减任意值
decrby age 5

#############################################

# 截取字符串,双闭
getrange name 0 2  # 字符串[0,2]
# 查看全部字符串: end 置为 -1 即可
getrange name 0 -1

############################################

# 替换指定位置开始的字符串
setrange name 1 tes  # 从第1位开始,第1-3位替换成 “tes”

###########################################

# 设置过期时间
setex age 10 
# 键不存在,再设置值(在分布式锁中常常使用)
setnx age 10

###########################################

# 批量设置键值对
mset k1 v1 k2 v2 k3 v3
# 批量获取键值对
mget k1 k2 k3
# 如果当前键不存在,才设置值(原子性操作:要么同时成功,要么同时失败)
msetnx k1 v1 k2 v2

###########################################

# 对象
# 批量设置.键为 user:{id}:name
mset user:1:name allen  user:1:age 2
# 批量获取
mget user:1:name user:1:age

###########################################

# 先get再set. 如果值存在第一次输出 原值,否则输出 nil. 第二次输出 v1
getset k1 v1
get k1

###########################################

List 列表类型(双端队列)

# 放入值,默认在最左侧(后进先出)
lpush list v1
lpush list v2
# 获取所有值
lrange list 0 -1
# 放入最右侧
rpush list v3

###########################################

# 移除第一个元素
lpop list
# 移除列表最后一个元素
rpop list

###########################################

# 通过下标获取值
lindex list 0

# 获取列表长度
llen list

# 移除某几个值
lrem list 2 v

###########################################

# 截取指定范围的list. 这个 list 已经被改变了,截断了只剩下截取的元素
ltrim list 0 1

# 移动列表最后一个元素到其他列表
rpoplpush source destination

# 是否存在某个列表
exists listname

# 更新指定下标处的 value
lset listname index value

###########################################

# 插入. 在 word 前(before)/后(after) 插入 nihao
linsert mylist before "word" "nihao"

小结:

  • 他实际上是一个(双向)链表,before Node after , left , right 都可以插入值
  • 如果key不存在,创建新的链表;如果key存在,新增内容
  • 如果移除了所有值,空链表,也代表不存在!
  • 在两边插入或者改动值,效率最高! 中间元素,相对来说效率会低一点~

Set集合类型

set 中的值不能重复,而 list 中的值可以重复

# 往set中添加元素
sadd myset allen

# 查看集合元素
smembers myset

# 查看某个元素是否在集合中
sismember myset allen

# 获取set中元素个数
scard myset

# 移除某个元素
srem myset allen

# 随机抽选出指定个数的元素
srandmember myset 1

# 随机删除set中的元素
spop myset

# 将一个指定的值移动到另一个 set 中
smove myset myset2 test

###########################################

# 差、交、并
127.0.0.1:6379> sadd set a
(integer) 1
127.0.0.1:6379> sadd set b
(integer) 1
127.0.0.1:6379> sadd set c
(integer) 1
127.0.0.1:6379> sadd set2 c
(integer) 1
127.0.0.1:6379> sadd set2 d
(integer) 1
127.0.0.1:6379> sadd set2 e
(integer) 1
127.0.0.1:6379> sdiff set set2
1) "a"
2) "b"
127.0.0.1:6379> sinter set set2
1) "c"
127.0.0.1:6379> sunion set set2
1) "a"
2) "b"
3) "d"
4) "e"
5) "c"

Hash(哈希)

Map集合,key-map! 只是这个值是一个map集合! 本质和String类型没有太大区别,还是一个简单的key-vlaue !

# 添加元素、获取元素
127.0.0.1:6379> hset myhash field1 a
(integer) 1
127.0.0.1:6379> hset myhash field1 b    
(integer) 0
127.0.0.1:6379> hset myhash field2 b
(integer) 1
127.0.0.1:6379> hget myhash field2  
"b"
127.0.0.1:6379> hmset myhash f1 v1 f2 v2    # 批量设置字段值
OK
127.0.0.1:6379> hmget myhash f1 f2      # 获取多个字段值
1) "v1"
2) "v2"
127.0.0.1:6379> hgetall myhash          # 获取全部字段值
1) "field1"
2) "b"
3) "field2"
4) "b"
5) "f1"
6) "v1"
7) "f2"
8) "v2"
###########################################

# 删除某个字段
127.0.0.1:6379> hdel myhash f1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "b"
3) "field2"
4) "b"
5) "f2"
6) "v2"

###########################################

# 获取 hash 长度
127.0.0.1:6379> hlen myhash
(integer) 3

###########################################

# 判断某个字段是否存在
127.0.0.1:6379> hexists myhash f2
(integer) 1
127.0.0.1:6379> hexists myhash f1
(integer) 0

###########################################

# 只获取全部的key
127.0.0.1:6379> hkeys myhash
1) "field1"
2) "field2"
3) "f2"

# 只获取全部的value
127.0.0.1:6379> hvals myhash
1) "b"
2) "b"
3) "v2"

###########################################

# 如果不存在就创建
127.0.0.1:6379> hsetnx myhash f1 v1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "b"
3) "field2"
4) "b"
5) "f2"
6) "v2"
7) "f1"
8) "v1"


###########################################

# 增加某个字段对应的值
127.0.0.1:6379> hset myhash f3 2
(integer) 1
127.0.0.1:6379> hincrby myhash f3 3
(integer) 5


###########################################

# 存储对象
127.0.0.1:6379> hmset myhash user:name allen user:age 18
OK
127.0.0.1:6379> hgetall myhash
 1) "field1"
 2) "b"
 3) "field2"
 4) "b"
 5) "f2"
 6) "v2"
 7) "f1"
 8) "v1"
 9) "f3"
10) "5"
11) "user:name"
12) "allen"
13) "user:age"
14) "18"

  • hash变更的数据 user name age, 尤其是是用户信息之类的,经常变动的信息!
  • hash 更适合于对象的存储,String 更加适合字符串存储!

Zset(有序集合)

在set的基础上,
set k1 v1
增加了一个值 score, Zset 正是依靠 score 来进行排序。
zset k1 score1 v1

# 根据score(数字)进行排序
127.0.0.1:6379> zadd myset 1 one 2 two 5 three 2 six
(integer) 4
# 获取所有元素
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "six"
3) "two"
4) "three"

###########################################

# 排序
127.0.0.1:6379> zadd salary 1000 p1
(integer) 1
127.0.0.1:6379> zadd salary 200 p2
(integer) 1
127.0.0.1:6379> zadd salary 2080 p3
(integer) 1
# 默认从小到大排序
127.0.0.1:6379> zrangebyscore salary -inf +inf
1) "p2"
2) "p1"
3) "p3"
# 从大到小排序
127.0.0.1:6379> zrevrange salary 0 -1 withscores
1) "p3"
2) "2080"
3) "p2"
4) "200"

# 显示所有用户并且带 score
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores 
1) "p2"
2) "200"
3) "p1"
4) "1000"
5) "p3"
6) "2080"

###########################################

# 移除某个元素
127.0.0.1:6379> zrem salary p1
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "p2"
2) "p3"

###########################################

# 获取集合中的元素个数
127.0.0.1:6379> zcard salary
(integer) 2


###########################################

# 统计。获取指定区间的成员数量
127.0.0.1:6379> zadd score 1 p1 2 p2 3 p3
(integer) 3
127.0.0.1:6379> zcount score 1 2
(integer) 2

三种特殊数据类型

geospatial地理位置

# geoadd  添加经、纬度、城市
# geopos  根据城市查询经纬度
127.0.0.1:6379> geoadd china:city 116.4 39.9 beijing 121.5 31.2 shanghai 118.8 32.1 nanjing
(integer) 3
127.0.0.1:6379> geopos china:city beijing nanjing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "118.80000203847885132"
   2) "32.09999898744121793"

###########################################

# 获取距离
127.0.0.1:6379> geodist china:city beijing shanghai km
"1071.5880"

指定单位的参数 unit 必须是以下单位的其中一个:

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

# georadius 以给定的经纬度为中心, 返回半径内的所有位置元素
# count 限定数目
# withcoord 显示坐标
# withdist 显示距离
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord withdist count 2
1) 1) "nanjing"
   2) "870.1651"
   3) 1) "118.80000203847885132"
      2) "32.09999898744121793"

###########################################

# GEORADIUSBYMEMBER 可以找出位于指定范围内的元素
# 但GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的

127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "nanjing"
2) "beijing"

###########################################

# GEOHASH命令-返回一个或多个位置元素的Geohash表示
# 该命令将返回11个字符的Geohash字符串!
# 将二维的经纬度转换为一维的字符串

127.0.0.1:6379> geohash china:city beijing nanjing
1) "wx4fbxxfke0"
2) "wtsqx7es090"

Hyperloglog(基数)

基数表示集合中不重复元素的个数!

简介

  • Redis 2.8.9版本就更新了Hyperloglog数据结构!
  • Redis Hyperloglog基数统计的算法!
  • 优点:
    • 占用的内存是固定,2^64不同的元素的技术,只需要12KB内存!
    • 如果要从内存角度来比较的话 Hyperloglog 首选!
# pfadd   添加元素
# pfcount 计数,无重复
# pfmerge 合并,取并集
127.0.0.1:6379> pfadd key1 a b c d e f g
(integer) 1
127.0.0.1:6379> pfcount key1
(integer) 7
127.0.0.1:6379> pfadd key2 f g h i j k l m n
(integer) 1
127.0.0.1:6379> pfcount key2
(integer) 9
127.0.0.1:6379> pfmerge key3 key1 key2
OK
127.0.0.1:6379> pfcount key3
(integer) 14

Bitmap 位图

  • 统计用户信息,活跃,不活跃!登录、未登录!打卡,365打卡!
  • 两个状态的,都可以使用Bitmap
  • Bitmap位图,数据结构! 都是操作二进制位来进行记录,就只有0和1两个状态!
  • 365天= 365 bit, 1字节= 8bit, 46个字节左右!
# 存储对应的天数及状态
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(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> getbit sign 3
(integer) 1

# 统计状态为1的天数
127.0.0.1:6379> bitcount sign 
(integer) 4

事务

Redis事务本质︰一组命令的集合!
一个事务中的所有命令都会被序列化,在事务执行过程的中,会按照顺序执行!

  • 一次性
  • 顺序性
  • 排他性!
  • 执行一系列的命令!

——队列set set set执行—–

  • Redis事务没有隔离级别的概念!
  • 所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行! Exec
  • Redis单条命令式保存原子性的,但是事务不保证原子性!
  • redis的事务∶
    • 开启事务(multi)
    • 命令入队(….)
    • 执行事务(exec)
  • 编译型异常(代码有问题!命令有错! ),事务中所有的命令都不会被执行!
  • 运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常!
127.0.0.1:6379> multi   # 开启事务
OK
# 入队
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
# 执行事务
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) "v1"
4) OK

-------------------------------------------

# 放弃事务 discard。****事务队列中的命令都不会被执行****
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> discard
OK
127.0.0.1:6379> get k2
(nil)

-------------------------------------------

编译型异常(代码有问题!命令有错! ),事务中所有的命令都不会被执行!

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> exec
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k2
(nil)

-------------------------------------------

运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,
其他命令是可以正常执行的,错误命令抛出异常!

127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> incr k1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) OK
5) "v2"
6) "v3"

Redis 实现乐观锁

监控! ==Watch==

  • 悲观锁(效率低下)∶
    • 很悲观,认为什么时候都会出问题,无论做什么都会加锁!
  • 乐观锁(效率较高):
    • 很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
    • 获取version
    • 更新的时候比较version
# 正常执行事务
127.0.0.1:6379> set left 100
OK
127.0.0.1:6379> set taken 0
OK
127.0.0.1:6379> watch left
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby left 20
QUEUED
127.0.0.1:6379(TX)> incrby taken 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20

-------------------------------------------

# 使用多线程修改值,使用watch可以实现乐观锁

---- 线程1执行
127.0.0.1:6379> set left 100
OK
127.0.0.1:6379> set taken 0
OK
127.0.0.1:6379> watch left
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby left 20
QUEUED

---- 此时线程2执行
set left 500

---- 线程1执行事务
127.0.0.1:6379(TX)> exec
(nil)

-------------------------------------------

# 如果执行失败,就先解锁,再加锁
127.0.0.1:6379> unwatch
OK
127.0.0.1:6379> watch left
OK

Jedis

jedis —— java 操作 redis 的工具

public class JedisTemplate {
    public static void main(String[] args) {
        //1 new Jedis 对象即可
        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println(jedis.ping());

        //2 Jedis 中的方法与 Redis 中的一致
        jedis.set("k1","v1");
        System.out.println(jedis.get("k1"));
    }
}

Jedis 执行事务运行时异常:

public class JedisTemplate {
    public static void main(String[] args) {
        //1 new Jedis 对象即可
        Jedis jedis = new Jedis("127.0.0.1",6379);
        System.out.println(jedis.ping());

        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("test","allen");
        //开启事务
        Transaction multi = jedis.multi();
        String string = jsonObject.toJSONString();

        try{
            multi.set("user1",string);
            int i=1/0;
            multi.exec();
        }catch (Exception e){
            multi.discard();
            e.printStackTrace();
        }finally {
            System.out.println(jedis.get("user1"));
            jedis.close();
        }
    }
}

print

PONG
java.lang.ArithmeticException: / by zero
at com.allen.JedisTemplate.main(JedisTemplate.java:27)
null


文章作者: Hailong Gao
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Hailong Gao !
评论
  目录