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();
}
}
}
PONG
java.lang.ArithmeticException: / by zero
at com.allen.JedisTemplate.main(JedisTemplate.java:27)
null