文章目录
Redis
远程字典服务,redis,C语言编写,基于内存的可持久化kv数据库,支持多种语言api
作用
:内存中的数据是断电即失的,还是要考虑持久化的(RDB、AOF),redis可用于高速缓存,也可以当作简单消息队列,也可做一些地图信息分析,计时器、计数器
特性
:多样的数据类型、集群、持久化、事务
安装(linux)
# 官方下载安装包
https://redis.io/download
# 上次linux服务器解压
#进入redis目录
yum install gcc-c++
make
make install
# /usr/local/bin目录下默认会按安装一些redis的命令
# 当前目录新建文件夹my_redis_conf,把redis.cof拷贝到新建文件夹内
# 修改配置
vim redis.conf
修改 daemonize 为 yes(后台启动)
# 启动redis
redis-server /usr/local/soft/redis-6.2.6/my_redis_cofig/redis.conf
# 测试连接
redis-cli -p 6379
输入ping
出现pong则没有问题
# 关闭redis,在redis-cli输入如下命令
127.0.0.1:6379> SHUTDOWN
not connected> exit
性能测试
redis-benchmark
,默认该命令在/usr/local/bin下
简单测试
# 测试100个并发连接 100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
# 测试完后如何分析输出的数据?
====== SET ======
100000 requests completed in 1.28 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 3600 1 300 100 60 10000
host configuration "appendonly": no
multi-thread: no
Latency by percentile distribution:
0.000% <= 0.183 milliseconds (cumulative count 1)
......
......
Summary:
throughput summary: 78003.12 requests per second
latency summary (msec):
avg min p50 p95 p99 max
0.653 0.176 0.631 0.775 0.975 4.263
这是其中一部分的输出数据,可以看到10万请求花费了1.28s,100个并发的客户端,每次写入了三个字节,只有一台服务器来处理,每秒处理78003.12请求
基础知识
#redis的默认数据库有16个,可以进入redis.conf,可发下如下命令,默认的使用的第0个
databases 16
# 可在redis-cli进行切换。比如切换到第三个,不同的数据库数据是不通的
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]>
# 查看数据库所有的key
127.0.0.1:6379[3]> keys *
# 清除当前数据库数据
127.0.0.1:6379[3]> FLUSHDB
# 清除所有数据库数据
127.0.0.1:6379> FLUSHALL
注意,redis是单线程
的,redis是基于内存,cpu并不是一个瓶颈,redis的瓶颈是服务器的内存和网络带宽,所以在能使用单线程的情况不需要使用多线程,redis是C语言写的,而是redis官网提供数据为100000+的QPS(每秒查询率),性能不比Memecache差,该链接讲解了大致的区别redis和memcached的区别和使用场景_北北的博客-CSDN博客_redis和memcached的区别和使用场景
那问题来了,为什么单线程还这么快?
首先要明确一点,多线程不一定比单线程快,redis是将所有的数据放到内存的,所以使用单线程操作的效率是最高的,多线程会导致cpu上下文切换的操作耗时间,而对于依赖内存的系统,没有上下文切换效率就是最高的
五大数据类型
string、List、Set、Hash、Zset
redis中文官方命令大全链接Redis命令中心(Redis commands) – Redis中国用户组(CRUG)
Redis-key相关
127.0.0.1:6379> KEYS *
(empty array)
127.0.0.1:6379> set name xiaoyoupei # 设一个key为name,value为xiaoyoupei
OK
127.0.0.1:6379> KEYS * # 查看所有的key
1) "name"
127.0.0.1:6379> set age 21
OK
127.0.0.1:6379> KEYS *
1) "name"
2) "age"
127.0.0.1:6379> EXISTS name # 判断key是否存在,返回1表示存在,0表示不存在
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
127.0.0.1:6379> EXISTS age
(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> set name xiaoyoupei
OK
127.0.0.1:6379> KEYS *
1) "name"
2) "age"
127.0.0.1:6379> get name # 通过某个key获取key所对应的value
"xiaoyoupei"
127.0.0.1:6379> EXPIRE name 10 # 设置某个key的过期时间(自动清除时间),单位是秒
(integer) 1
127.0.0.1:6379> KEYS *
1) "name"
2) "age"
127.0.0.1:6379> ttl name # 查看某个key剩余的过期时间,返回为-2表示已经清除,-1表示没有设置过期时间
(integer) -2
127.0.0.1:6379> set name xiaoyoupei
OK
127.0.0.1:6379> TYPE name # 查看某个key属于什么类型
string
127.0.0.1:6379> TYPE age
string
String
##### 追加、长度 #####
127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> get key1
"v1"
127.0.0.1:6379> KEYS * # name,age是上文遗留
1) "name"
2) "age"
3) "key1"
127.0.0.1:6379> EXISTS key1
(integer) 1
127.0.0.1:6379> APPEND key1 HelloWorld # 追加,如果key不存在就相当于set key
(integer) 12
127.0.0.1:6379> get key1
"v1HelloWorld"
127.0.0.1:6379> STRLEN key1 # 获取字符串的长度
(integer) 12
127.0.0.1:6379> APPEND key1 ',youpei'
(integer) 19
127.0.0.1:6379> get key1
"v1HelloWorld,youpei"
127.0.0.1:6379> STRLEN key1
(integer) 19
##### 自增、自减、步长 #####
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
(integer) 1
127.0.0.1:6379> get views
"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
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> DECR views
(integer) 0
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> DECR views
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> INCRBY views 10 # 增加的步长为10
(integer) 9
127.0.0.1:6379> DECRBY views 5 # 减少的步长为5
(integer) 4
##### 通过下标截取、替换 #####
127.0.0.1:6379> set key1 hello,xiaoyoupei
OK
127.0.0.1:6379> get key1
"hello,xiaoyoupei"
127.0.0.1:6379> GETRANGE key1 0 4 # 通过下标截取(左右都是闭区间)
"hello"
127.0.0.1:6379> GETRANGE key1 0 -1 # 获取全部字符串,同get
"hello,xiaoyoupei"
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> SETRANGE key2 1 x # 替换指定位置的字符串
(integer) 7
127.0.0.1:6379> get key2
"axcdefg"
127.0.0.1:6379> SETRANGE key2 2 ii
(integer) 7
127.0.0.1:6379> get key2
"axiiefg"
##### #####
# setex (set with expire) # 设置过期时间
# setnx (set if not exist) # 不存在就修改,存在就不改变
127.0.0.1:6379> SETEX key3 30 hello # 设置key3的值并且30秒后过期
OK
127.0.0.1:6379> ttl key3
(integer) 25
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> ttl key3
(integer) 8
127.0.0.1:6379> SETNX mykey redis # 如果mykey不存在,创建mykey,存在就创建失败
(integer) 1
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> keys *
1) "mykey"
2) "key2"
3) "key1"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx mykey mongodb
(integer) 0
127.0.0.1:6379> get mykey
"redis"
##### 多值操作 #####
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> keys *
(empty array)
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 # 如果不存在就创建,但此操作结束后却发现没有k4,因为此操作是原子性的,要么一起成功,要么都失败
(integer) 0
127.0.0.1:6379> keys *
1) "k3"
2) "k2"
3) "k1"
##### 对象 #####
# set user:1 {name:xiao,age:21}
# user:{id}:{filed},redis支持这样的设计
127.0.0.1:6379> MSET user:1:name xiao user:1:age 21
OK
127.0.0.1:6379> MGET user:1:name user:1:age
1) "xiao"
2) "21"
##### 先get后set #####
127.0.0.1:6379> GETSET db redis # 如果存在值,返回值;不存在就返回nil
(nil)
127.0.0.1:6379> GET db
"redis"
127.0.0.1:6379> GETSET db mongodb
"redis"
127.0.0.1:6379> GET db
"mongodb"
List
列表,redis可将list作为栈、队列、阻塞队列,list可重复
链表,在每个节点的前面后面都可以插入,链表左右也可以插入值
##### lpush、rpush #####
127.0.0.1:6379> keys *
(empty array)
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> LRANGE list 0 0
1) "three"
127.0.0.1:6379> RPUSH list right # 将一个值或者多个值,插入列表的尾部(右部)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
上述所图所示
##### 移除列表的值lpop、rpop;通过下边获取lindex #####
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> LPOP list # 移除头部一个(左部)
1) "three"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> RPOP list # 移除尾部一个(右部)
"right"
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> LINDEX list 1 # 获取下标为1的值
"one"
127.0.0.1:6379> LINDEX list 0
"two"
##### LLEN:长度,LREM:移除 #####
127.0.0.1:6379> FLUSHDB
OK
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> LLEN list
(integer) 3
127.0.0.1:6379> lpush list three # 这就表示列表可重复
(integer) 4
127.0.0.1:6379> LREM list 1 one # 移除指定数目的value,这里是移除1个one
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> LREM list 2 three # 移除指定数目的value,这里是移除2个three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
##### LTRIM:截取 #####
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> keys *
(empty array)
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> FLUSHDB
OK
127.0.0.1:6379> keys *
(empty array)
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
"hello2" # 移除mylist尾部一个元素将其添加到myotherlist,如果myotherlist不存在会自动创建
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "hello1"
127.0.0.1:6379> LRANGE myotherlist 0 -1
1) "hello2"
##### LSET:修改列表指定下标的值 #####
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> EXISTS list
(integer) 0
127.0.0.1:6379> LSET list 0 item # 不存在列表更新就会报错
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
127.0.0.1:6379> LSET list 0 item # 更新列表的下标值
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"
##### LINSERT:某个具体值的插入列表中 #####
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> RPUSH list 'hello'
(integer) 1
127.0.0.1:6379> RPUSH list 'world'
(integer) 2
127.0.0.1:6379> LINSERT list before world xiao # 将xiao插入到world之前
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "xiao"
3) "world"
127.0.0.1:6379> LINSERT list after world youpei # 将youpei插入到world之后
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "xiao"
3) "world"
4) "youpei"
Set
set集合,无序且不能重复
##### SADD:set添加元素,SMEMBERS:查看全部元素,SISMEMBER:判断元素是否存在 #####
127.0.0.1:6379> SADD myset hello # set中添加元素
(integer) 1
127.0.0.1:6379> SADD myset xiaoyoupei
(integer) 1
127.0.0.1:6379> SADD myset liyan
(integer) 1
127.0.0.1:6379> SMEMBERS myset # 查看指定set的所有值
1) "liyan"
2) "xiaoyoupei"
3) "hello"
127.0.0.1:6379> SADD myset liyan # 相同值添加失败
(integer) 0
127.0.0.1:6379> SMEMBERS myset
1) "liyan"
2) "xiaoyoupei"
3) "hello"
127.0.0.1:6379> SISMEMBER myset liyan # 判断某个元素是不是在set中,返回为1是在,返回为0是不在
(integer) 1
127.0.0.1:6379> SISMEMBER myset liyan1
(integer) 0
##### scard:统计个数,srem:移除set中的某个元素 #####
127.0.0.1:6379> SCARD myset # 获取set集合中的个数
(integer) 3
127.0.0.1:6379> SREM myset hello # 移除set中的某个元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "liyan"
2) "xiaoyoupei"
##### SRANDMEMBER:随机抽选,SPOP:随机移除元素 #####
127.0.0.1:6379> SRANDMEMBER myset # 随机抽选
"xiaoyoupei"
127.0.0.1:6379> SRANDMEMBER myset
"liyan"
127.0.0.1:6379> SRANDMEMBER myset 2 # 随机抽选指定个数的元素
1) "liyan"
2) "xiaoyoupei"
127.0.0.1:6379> SPOP myset # 随机移除某个元素
"xiaoyoupei"
127.0.0.1:6379> SPOP myset
"liyan"
127.0.0.1:6379> SPOP myset
(nil)
127.0.0.1:6379> SMEMBERS myset
(empty array)
##### smove:将一个集合的某个元素移动到另外一个集合 #####
127.0.0.1:6379> SMOVE myset myset2 xiao
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "xiao"
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> SDIFF key1 key2 # 差集
1) "b"
2) "a"
127.0.0.1:6379> SINTER key1 key2 # 交集
1) "c"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "b"
2) "a"
3) "c"
4) "e"
5) "d"
Hash
map集合,key-Map,可以存储经常变更的数据
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> HSET myhash field1 xiaoyoupei # set一个具体的kv
(integer) 1
127.0.0.1:6379> HGET myhash field1
"xiaoyoupei"
127.0.0.1:6379> HMSET myhash field1 hello field2 word # set多个kv
OK
127.0.0.1:6379> HMGET myhash field1 field2 # 获取多个字段值
1) "hello"
2) "word"
127.0.0.1:6379> HGETALL myhash # 获取全部的数据
1) "field1"
2) "hello"
3) "field2"
4) "word"
127.0.0.1:6379> HDEL myhash field1 # 删除hash的指定的key字段
(integer) 1
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "word"
127.0.0.1:6379> HLEN myhash
(integer) 1
127.0.0.1:6379> HMSET myhash field1 hello field2 word
OK
127.0.0.1:6379> HGETALL myhash
1) "field2"
2) "word"
3) "field1"
4) "hello"
127.0.0.1:6379> HLEN myhash # 获取hash表的key值数量
(integer) 2
127.0.0.1:6379> HEXISTS myhash field1 # 判断hash中的指定字段是否存在
(integer) 1
127.0.0.1:6379> HEXISTS myhash field3
(integer) 0
127.0.0.1:6379> HKEYS myhash # 获取hash中所有key
1) "field2"
2) "field1"
127.0.0.1:6379> HVALS myhash # 获取hash中所有value
1) "word"
2) "hello"
##################################
127.0.0.1:6379> HSET hash field1 5
(integer) 1
127.0.0.1:6379> HINCRBY hash field1 1 # 自增1
(integer) 6
127.0.0.1:6379> HGETALL hash
1) "field1"
2) "6"
127.0.0.1:6379> HINCRBY hash field1 -1
(integer) 5
127.0.0.1:6379> HGETALL hash
1) "field1"
2) "5"
127.0.0.1:6379> HSET NX hash field4 hello # 如果不存在可以设置
(integer) 1
127.0.0.1:6379> HSETNX hash field4 world
(integer) 0
Zset
有序集合
127.0.0.1:6379> ZADD myset 1 one 2 two 3 three # 添加多个值
(integer) 3
127.0.0.1:6379> ZRANGE myset 0 -1 # 下标查看集合元素
1) "one"
2) "two"
3) "three"
# 排序实现
127.0.0.1:6379> ZADD salary 10000 xiao
(integer) 1
127.0.0.1:6379> ZADD salary 8000 youpei
(integer) 1
127.0.0.1:6379> ZADD salary 1000 can
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示用户,从小到大
1) "can"
2) "youpei"
3) "xiao"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores# 显示全部用户和工资,从小到大
1) "can"
2) "1000"
3) "youpei"
4) "8000"
5) "xiao"
6) "10000"
127.0.0.1:6379> ZREVRANGE salary 0 -1# 从大到小排序
1) "xiao"
2) "youpei"
3) "can"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 8000 withscores # 显示工资小于8000(闭区间)的用户和工资
1) "can"
2) "1000"
3) "youpei"
4) "8000"
127.0.0.1:6379> zrange salary 0 -1
1) "can"
2) "youpei"
3) "xiao"
127.0.0.1:6379> zrem salary can # 移除某个元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "youpei"
2) "xiao"
127.0.0.1:6379> ZCARD salary # 获取有序集合的个数
(integer) 2
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> ZADD myset 2 world 3 xiaoyoupei
(integer) 2
127.0.0.1:6379> ZCOUNT myset 1 3 # 统计数量
(integer) 3
三大特殊数据类型
geospatial地理位置
# geoadd添加地理位置,南北极无法添加
# 参数 key 经纬度
127.0.0.1:6379> GEOADD china:city 118.99868 32.69124 tianchang
(integer) 1
127.0.0.1:6379> GEOADD china:city 118.767413 32.041544 nanjing
(integer) 1
127.0.0.1:6379> GEOADD china:city 121.472644 31.231706 shanghai
(integer) 1
127.0.0.1:6379> GEOADD china:city 120.153576 30.287459 hangzhou
(integer) 1
127.0.0.1:6379> GEOADD china:city 116.405285 39.904989 beijing
(integer) 1
# GEOPOS 获取指定的城市的经度和纬度
127.0.0.1:6379> GEOPOS china:city beijing tianchang
1) 1) "116.40528291463851929"
2) "39.9049884229125027"
2) 1) "118.99867862462997437"
2) "32.69124044090514758"
# GEODIST 两地之前的距离,km为单位
127.0.0.1:6379> GEODIST china:city tianchang nanjing km
"75.4588"
# 获取给定位置为圆心,某一半径内的圈内元素
# 110 30是经纬度,1000km是半径
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km
1) "hangzhou"
2) "nanjing"
3) "tianchang"
# withdist 与给定位置距离 withcoord 列出经纬度 count 限制统计次数
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord count 1
1) 1) "nanjing"
2) "865.7012"
3) 1) "118.76741319894790649"
2) "32.04154324806454923"
# 找出指定元素周围的其他元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city tianchang 1000 km
1) "nanjing"
2) "tianchang"
3) "beijing"
4) "hangzhou"
5) "shanghai"
# 将二维经纬度转换为一维11位的hash值
127.0.0.1:6379> GEOHASH china:city tianchang nanjing
1) "wtu9gzk0q00"
2) "wtsqq8gz920"
GEO底层实际上就是一个Zset,所以可以使用Zset命令操作GEO
# 查看全部元素
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "hangzhou"
2) "shanghai"
3) "nanjing"
4) "tianchang"
5) "beijing"
# 删除某个元素
127.0.0.1:6379> ZREM china:city hangzhou
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "shanghai"
2) "nanjing"
3) "tianchang"
4) "beijing"
Hyperloglog
引入基数(不重复的元素)
hyperloglog——>基数统计的算法
设想网页的uv量(指的是一个用户访问网站多次还是记为一位,不像pv)
传统的方式,用的是set,统计set中元素数量,但是占用太多内存
我们的存储是为了计数而不是保存某个冗长的用户id
优点
占用内存固定12kb,hyperloglog成为首选,但会有0.81%错误率
,但我们可以忽略不计
# 添加元素
127.0.0.1:6379> PFADD mykey xiao you pei
(integer) 1
# 统计个数
127.0.0.1:6379> PFCOUNT mykey
(integer) 3
127.0.0.1:6379> PFADD mykey xiao li yan
(integer) 1
# 不会重复,因此是5个元素
127.0.0.1:6379> PFCOUNT mykey
(integer) 5
127.0.0.1:6379> PFADD mykey2 zhang san xiao
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 3
# mykey mykey2合并为mykey3
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2
OK
127.0.0.1:6379> PFCOUNT mykey3
(integer) 7
Bitmap
引入位存储
# 位存储,形象理解记录周一到周日打卡,1为打卡,0为非
127.0.0.1:6379> SETBIT sign 0 0
(integer) 0
127.0.0.1:6379> SETBIT sign 1 0
(integer) 0
127.0.0.1:6379> SETBIT sign 2 0
(integer) 0
127.0.0.1:6379> SETBIT sign 3 0
(integer) 0
127.0.0.1:6379> SETBIT sign 4 1
(integer) 0
127.0.0.1:6379> SETBIT sign 5 1
(integer) 0
127.0.0.1:6379> SETBIT sign 6 1
(integer) 0
# 查看某一天是否打卡
127.0.0.1:6379> GETBIT sign 0
(integer) 0
127.0.0.1:6379> GETBIT sign 3
(integer) 0
127.0.0.1:6379> GETBIT sign 6
(integer) 1
# 统计0-6打卡次数
127.0.0.1:6379> BITCOUNT sign 0 6
(integer) 3
事务操作
redis单条命令保证原子性,但redis中事务没有原子性
redis事务的本质是一组命令的集合,有其一次性、顺序性、排他性,没有隔离级别的概念
所有的命令在事务中,并没有直接被执行,只有发起执行命令才会执行(想到了spark)
redis事务:
开启事务:MULTI
命令入队:具体命令
执行事务:EXEC
# 正常执行 测试案例
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 k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> EXEC
1) OK
2) OK
3) "v2"
4) OK
# 放弃事务 测试案例
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 k4 v4
QUEUED
127.0.0.1:6379(TX)> DISCARD # 取消事务
OK
127.0.0.1:6379> get k4 # 事务中的命令都不会执行
(nil)
编译时异常,命令有错,事务中所有命令都不会执行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> set k1 v2
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> GETSET k3 # 错误命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> EXEC # 执行事务报错,所有命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k4
(nil)
运行时异常,如果事务队列中存在语法性,那么在执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常
127.0.0.1:6379> FLUSHDB
OK
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> MULTI
OK
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)> get k2
QUEUED
127.0.0.1:6379(TX)> EXEC
1) (error) ERR value is not an integer or out of range
2) OK
3) "v2"
实现乐观锁
悲观锁:无论做什么都会加锁,影响性能
乐观锁:不会上锁,更新数据的时候判断在此期间是否有人修改过数据,(类似mysql加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(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20
# 失败执行 监控成功
# 第一个执行事务时候被第二个修改,watch监控直接表示事务必会失败
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379(TX)> DECRBY money 10 # 第一个
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379(TX)> exec # 由于watch监控,所以此处执行必会失败
(nil)
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000 # 第二个
OK
# 那后面事务如何执行,关锁再上锁
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(TX)> DECRBY money 1
QUEUED
127.0.0.1:6379(TX)> INCRBY out 1
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 999
2) (integer) 21
Jedis
官方推荐java连接redis的开发工具
导入依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
package com.xyp.redis;
import redis.clients.jedis.Jedis;
/**
* Description:
*
* @Author: xiaoyoupei
* @CreateTime: 2021/12/1 20:24
*/
public class TestPing {
public static void main(String[] args) {
//创建对象
Jedis jedis = new Jedis("172.16.6.14",6379);
//测试是否连接成功
System.out.println(jedis.ping());
}
}
注意,这里可能有些配置没改,无法连接,尤其是在服务器上,需要修改
1、需要注释
# bind 127.0.0.1 -::1
2、改为no
protected-mode no
常用api
事务
package com.xyp.redis;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
/**
* Description:
*
* @Author: xiaoyoupei
* @CreateTime: 2021/12/1 20:55
*/
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("172.16.6.14", 6379);
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", "xiao");
jsonObject.put("age", 21);
jedis.flushDB();
//开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try {
multi.set("user1", result);
// int i = 1 / 0; //打开注释就可事务失败
//执行事务
multi.exec();
} catch (Exception e) {
//失败抛弃事务
multi.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
jedis.close();
}
}
}