什么是NoSQL
Not Only SQL 泛指非关系型数据库,传统的关系型数据库,很难应对web2.0时代(视频、音乐、个人信息),尤其是超大规模的高并发的社区。在大数据环境下发展的十分迅速。Redis
关系型数据库:表格,行列存储
很多的数据类型用户的个人信息,社交网络,地理位置,这些数据类型的存储不需要一个固定的格式,不需要多余的操作就可以横向扩展的。Map
NoSQL特点:
1.方便扩展,数据之间没有关系,很好扩展,可解耦
2.大数据量高性能(Redis 一秒写8w次,读取11w次,NoSQL的缓存是记录级,是一种细粒度的缓存,性能会比较高)
3.数据类型多样性!(不需要事先设计数据库,随取随用!)
5种常见数据类型和3种特殊数据类型 String,List,Set,Hash,Zset,geo,hyperloglog,bitmap
4.没有固定的查询语言,键值对存储,列存储,文档存储,图形数据库(社交关系),满足最终一致性CAP定理和BASE(异地多活,保证服务器不会宕机)
5.高性能、高可用,高可扩展
NoSQL四大分类
1.KV键值对: Redis、Redis+Tair、Redis+memecache
2.文档型数据库(bson格式和json一样):
MongoDB(是一个基于分布式文件存储的数据库,C++编写,主要用来处理大量的文档),是一个介入关系型数据库和非关系型数据库的中间产品,是非关系型数据库中功能最丰富,最像关系型数据库的;ConthDB;kv键值对,value为结构化数据;查询性能不高
3.列存储数据库:大数据里的 HBase,分布式文件系统;以列簇存储,将同一列数据存在一起
4.图关系数据库:不是存图形的,放的是关系,比如:朋友圈社交网络,广告推荐 Neo4j,infoGrid,最短路径寻址,N度关系查找等
Redis入门
Redis是什么?
Remote Dictionary Server ,即远程字典服务;
C语言编写的,支持网络,基于内存,可持久化的日志型Key-Value数据库,并提供多种语言的API,免费开源,结构化数据库。
Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从同步)
能做什么?
1.内存存储、持久化,内存中是断电即失,可以持久化(RDB/AOF)
2.效率高,可以用于高速缓存
3.发布订阅系统
4.地图信息分析
5.计时器,计数器(浏览量)...
特性:多样数据类型,持久化,支持集群,事务...
推荐在Linux服务器上搭建,基于Linux
Windows安装
一般不用
Linux安装
1.下载安装包可在官网下载
获得安装包 redis-6.2.4.tar.gz
2.传到我们的home目录下
[root@centos7 eric]# ls
apache-tomcat-10.0.20 jdk-18_linux-x64_bin.rpm redis-6.2.4.tar.gz
apache-tomcat-10.0.20.tar.gz jdk-8u51-linux-x64.tar.gz snakeGame.jar
3.解压redis安装包 程序一般放在opt目录下 先移动过去
[root@centos7 eric]# ls
apache-tomcat-10.0.20 jdk-18_linux-x64_bin.rpm redis-6.2.4.tar.gz
apache-tomcat-10.0.20.tar.gz jdk-8u51-linux-x64.tar.gz snakeGame.jar
[root@centos7 eric]# mv redis-6.2.4.tar.gz /opt
[root@centos7 eric]# cd /opt
[root@centos7 opt]# ls
containerd redis-6.2.4.tar.gz
[root@centos7 opt]#
tar -zxvf redis-6.2.4.tar.gz 解压
[root@centos7 opt]# tar -zxvf redis-6.2.4.tar.gz
redis-6.2.4/
redis-6.2.4/.github/
redis-6.2.4/.github/ISSUE_TEMPLATE/
redis-6.2.4/.github/ISSUE_TEMPLATE/bug_report.md
redis-6.2.4/.github/ISSUE_TEMPLATE/crash_report.md
redis-6.2.4/.github/ISSUE_TEMPLATE/feature_request.md
[root@centos7 opt]# ls
containerd redis-6.2.4 redis-6.2.4.tar.gz
[root@centos7 opt]# ll
总用量 2408
drwx--x--x. 4 root root 28 4月 23 16:05 containerd
drwxrwxr-x. 7 root root 4096 6月 1 2021 redis-6.2.4
-rw-r--r--. 1 root root 2457940 4月 28 23:03 redis-6.2.4.tar.gz
[root@centos7 opt]#
进入解压后的文件 可看到配置文件 redis.conf
[root@centos7 opt]# cd redis-6.2.4
[root@centos7 redis-6.2.4]# ls
00-RELEASENOTES COPYING MANIFESTO runtest-cluster src
BUGS deps README.md runtest-moduleapi tests
CONDUCT INSTALL redis.conf runtest-sentinel TLS.md
CONTRIBUTING Makefile runtest sentinel.conf utils
基本的环境安装
yum install gcc-c++
gcc -v 查看环境是否有问题
[root@centos7 redis-6.2.4]# gcc -v
使用内建 specs。
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
目标:x86_64-redhat-linux
配置为:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto --enable-plugin --enable-initfini-array --disable-libgcj --with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install --with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
线程模型:posix
gcc 版本 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
执行 make命令 会去把所有需要的文件配置上 需要等待一会儿 把所有东西下载完毕
可以make install 进行一个确认 也可以不执行
[root@centos7 redis-6.2.4]# make install
cd src && make install
make[1]: 进入目录“/opt/redis-6.2.4/src”
CC Makefile.dep
make[1]: 离开目录“/opt/redis-6.2.4/src”
make[1]: 进入目录“/opt/redis-6.2.4/src”
Hint: It's a good idea to run 'make test' ;)
INSTALL redis-server
INSTALL redis-benchmark
INSTALL redis-cli
make[1]: 离开目录“/opt/redis-6.2.4/src”
redis默认安装路径 /usr/local/bin
[root@centos7 usr]# ls
bin etc games include lib lib64 libexec local sbin share src tmp
[root@centos7 usr]# cd local
[root@centos7 local]# ls
bin docker etc games include lib lib64 libexec sbin share src
[root@centos7 local]# cd bin
[root@centos7 bin]# ls
redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server
[root@centos7 bin]# pwd
/usr/local/bin
将我们的redis配置文件进行拷贝 到当前目录下 , 首先建一个目录
mkdir rconfig
cp /opt/redis-6.2.4/redis.conf rconfig
[root@centos7 bin]# mkdir rconfig
[root@centos7 bin]# cp /opt/redis-6.2.4/redis.conf rconfig
[root@centos7 bin]# cd rconfig
[root@centos7 rconfig]# ls
redis.conf
[root@centos7 rconfig]#
我们之后就用 rconfig 目录下的配置文件进行启动,就算出问题了以后还可以拷贝
redis 默认不是后台启动,需要修改配置文件为后台启动
view redis.conf 配置的daemonize 后台启动 目前配置的是no 需要改为 yes
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
# When Redis is supervised by upstart or systemd, this parameter has no impact.
daemonize no
# If you run Redis from upstart or
# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
# When Redis is supervised by upstart or systemd, this parameter has no impact.
daemonize yes
wq保存退出,通过指定的配置文件启动服务 并ping 测试
[root@centos7 bin]# ls
rconfig redis-check-aof redis-cli redis-server
redis-benchmark redis-check-rdb redis-sentinel
[root@centos7 bin]# pwd
/usr/local/bin
[root@centos7 bin]# redis-server rconfig/redis.conf
使用redis 客户端进行连接 redis-cli -p 6379
[root@centos7 bin]# redis-server rconfig/redis.conf
[root@centos7 bin]# ls
rconfig redis-check-aof redis-cli redis-server
redis-benchmark redis-check-rdb redis-sentinel
[root@centos7 bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name eric
OK
127.0.0.1:6379> get name
"eric"
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379>
查看redis 进程是否开启 ps -ef | grep redis
[root@centos7 /]# ps -ef | grep redis
root 24064 1 0 23:24 ? 00:00:00 redis-server 127.0.0.1:6379
root 24130 17727 0 23:26 pts/0 00:00:00 redis-cli -p 6379
root 24505 24443 0 23:31 pts/2 00:00:00 grep --color=auto redis
[root@centos7 /]#
如何关闭redis 服务? shutdown 然后exit退出
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> shutdown
not connected> exit
再查看进程 已无连接
root 24130 17727 0 23:26 pts/0 00:00:00 redis-cli -p 6379
root 24505 24443 0 23:31 pts/2 00:00:00 grep --color=auto redis
[root@centos7 /]# ps -ef | grep redis
root 24642 24443 0 23:33 pts/2 00:00:00 grep --color=auto redis
[root@centos7 /]#
可以使用单机多redis启动集群
测试性能
redis-benchmark 压力测试工具,官方自带
命令:
-h 指定服务器主机名 默认127.0.0.1
-p 指定服务器端口 默认6379
-s 指定服务器socket
-c 指定并发连接数 默认 50
-n 指定请求书 默认10000
-d 以字节的形式指定set/get 值的数据大小 默认 3
-k 1=keep alive 0=reconnect
-r set/get/incr 使用随机key ,SADD 使用随机值
-P 通过管道传输
-q 强制退出redis, 仅显示query/sec 值
-csv 以CSV格式输出
-t 仅运行以逗号分割的测试命令列表
测试:100个并发连接 每个并发10w个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
====== SET ======
100000 requests completed in 1.03 seconds
100 parallel clients
3 bytes payload
keep alive: 1
99.900% <= 10.103 milliseconds (cumulative count 99900)
99.917% <= 24.111 milliseconds (cumulative count 99917)
100.000% <= 25.103 milliseconds (cumulative count 100000)
Summary:
throughput summary: 96805.42 requests per second
latency summary (msec):
avg min p50 p95 p99 max
0.746 0.400 0.711 0.967 1.095 24.591
100并发,每次写入3字节,10w请求1.03秒完成,只有一台服务器处理这些请求,单机性能
每秒处理96805.42个请求。
基础:
redis默认有16个数据库 配置文件中可以看到相关说明,并且通过select
[root@centos7 bin]# cd rconfig
[root@centos7 rconfig]# ls
redis.conf
[root@centos7 rconfig]# vim redis.conf
# Set the number of databases. The default database is DB 0, you can select
# a different one on a per-connection basis using SELECT where
# dbid is a number between 0 and 'databases'-1
databases 16
选择数据库,并查看大小
[root@centos7 bin]# redis-cli -p 6379
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> dbsize
(integer) 0
127.0.0.1:6379[1]> select 15
OK
127.0.0.1:6379[15]> select 16
(error) ERR DB index is out of range
127.0.0.1:6379[15]> set name eric
OK
127.0.0.1:6379[15]> dbsize
(integer) 1
127.0.0.1:6379[15]> get name
"eric"
127.0.0.1:6379[15]> keys * #查看数据库所有的key
1) "name"
127.0.0.1:6379[15]> flushdb
OK
127.0.0.1:6379[15]> keys *
(empty array)
keys * 查看数据库所有的key
清除当前数据库: flushdb
127.0.0.1:6379[15]> select 0
OK
127.0.0.1:6379> keys *
1) "name"
2) "myhash"
3) "mylist"
4) "key:__rand_int__"
5) "counter:__rand_int__"
6) "age"
清除所有的数据库:flushall
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> keys *
(empty array)
127.0.0.1:6379[3]> flushall
OK
127.0.0.1:6379[3]> keys *
(empty array)
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> keys *
(empty array)
为什么redis端口号是6379 ?
6379是手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字。
Redis 是单线程的!(6.0版本以上,对外功能支持多线程,但其实内核处理部分依然是单线程的),基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程。
Redis是C语言写的,官方提供的数据为10w+ 的QPS,完全不比Memecache差。
Redis为什么单线程还这么快?
1.误区:高性能的服务器一定是多线程的
2.误区:多线程(CPU会上下文切换)一定比单线程效率高
核心:redis将所有的数据全部放在内存中的,所以使用单线程去操作效率就是最高的(cpu上下文切换耗时),对于内存系统来说,如果没有上下文切换,效率就是最高的!多次读写都是在一个CPU上的,在内存情况下,这个就是最佳方案!
五大数据类型:String 、List 、 Set、 Hash、 Zset(有序集合)
三种特殊数据类型:geospatial(地理空间) 、 hyperloglog 、 bitmaps(范围查询)
Redis-key
查看所有的key : keys *
检查key是否已经存在 : exists +key
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name lina
OK
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
移除当前key 到指定的某个数据库中: move +key +dbNum
127.0.0.1:6379> set name lily
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name
"lily"
127.0.0.1:6379> move name 1
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "name"
127.0.0.1:6379[1]> get name
"lily"
127.0.0.1:6379[1]>
设置过期时间:expire +key +seconds
查看当前key的剩余时间:ttl +key
127.0.0.1:6379> expire name 10
(integer) 1
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> keys *
1) "age"
查看当前key存储的内容的数据类型:type +key
删除某个key-value: del +key
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> type name
string
127.0.0.1:6379> type age
string
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379>
String
追加内容:append +key +value (如果key不存在,则自动进行新增key-value操作)
计算value字符串长度:strlen +key
127.0.0.1:6379> set key1 v
OK
127.0.0.1:6379> get key1
"v"
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> exists key1
(integer) 1
127.0.0.1:6379> append key1 "hello" #append追加内容
(integer) 6
127.0.0.1:6379> get key1
"vhello"
127.0.0.1:6379> append key1 123
(integer) 9
127.0.0.1:6379> get key1
"vhello123"
127.0.0.1:6379> strlen key1 #计算字符串长度
(integer) 9
127.0.0.1:6379> append key1 ",erichaha"
(integer) 18
127.0.0.1:6379> get key1
"vhello123,erichaha"
127.0.0.1:6379> append name zhangsan #name这个key不存在,直接新建key 及value
(integer) 8
127.0.0.1:6379> keys *
1) "name"
2) "key1"
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> strlen name
(integer) 8
127.0.0.1:6379>
自增 i++ 、自减 i-- 、设置步长 i+= 。
127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views #设置浏览量初始值为 0
"0"
127.0.0.1:6379> incr views #自增1 i++
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> decr views #自减1 i--
(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> incrby views 10 #设置步长为10 ,自增10,i+=10
(integer) 12
127.0.0.1:6379> get views
"12"
127.0.0.1:6379> incr views
(integer) 13
127.0.0.1:6379> decrby views 5 #设置步长为5,自减5,i-=5
(integer) 8
127.0.0.1:6379> get views
"8"
获取字符串范围:getrange + key +startIndex +endIndex (左右包含)
127.0.0.1:6379> set key1 "hello,world"
OK
127.0.0.1:6379> getrange key1 0 3 #[0~3]范围内的字符串
"hell"
127.0.0.1:6379> getrange key1 0 -1 #获取全部字符串
"hello,world"
替换指定位置开始的字符串!setrange + key +offset + value
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 xx #从下标1开始,内容替换为xx
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
127.0.0.1:6379> setrange key2 3 xxx #从下标3开始,内容替换为xxx
(integer) 7
127.0.0.1:6379> get key2
"axxxxxg"
127.0.0.1:6379> setrange key2 1 ***********
(integer) 12
127.0.0.1:6379> get key2
"a***********"
setex(set with expire) 设置过期时间
如果不存在:setnx (set if not exist ) 不存在再设置(在分布式锁中会常常使用)
127.0.0.1:6379> setex k3 30 "hello" #设置过期时间30秒
OK
(integer) 19
127.0.0.1:6379> get k3
"hello"
127.0.0.1:6379> setnx key "redis" #如果这个key存在则创建 内容"redis"
(integer) 1
127.0.0.1:6379> keys *
1) "key2"
2) "key1"
3) "key"
127.0.0.1:6379> ttl k3
(integer) -2
127.0.0.1:6379> setnx key "mongdb" #因为这个key已经存在了所以设置不成功
(integer) 0
127.0.0.1:6379> get key
"redis"
127.0.0.1:6379>
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) "key2"
3) "key1"
4) "k1"
5) "key"
6) "k2"
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 #msetnx 是一个原子性操作,要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379>
设置对象:
set user:1 {name:zhangsan,age:18} 设置一个user:1 对象值为json字符串来保存一个对象
也可以用mset 来实现 mset user:1:name zhangsan user:1:age 18
这里的key 是: user:{id}:{field}
127.0.0.1:6379> set user:1 {name:zhangsan,age:18}
OK
127.0.0.1:6379> get user:1
"{name:zhangsan,age:18}"
127.0.0.1:6379> mset user:2:name zhangsan user:2:age 7 user:2:sex girl user:1:sex boy
OK
127.0.0.1:6379> keys *
1) "user:2:name"
2) "user:1"
3) "user:2:age"
4) "k1"
5) "key1"
6) "k2"
7) "k3"
8) "key2"
9) "user:1:sex"
10) "user:2:sex"
11) "key"
127.0.0.1:6379> get user:1:name
(nil)
127.0.0.1:6379> get user:2:name
"zhangsan"
127.0.0.1:6379> get user:2:age
"7"
127.0.0.1:6379> get user:2:sex
"girl"
127.0.0.1:6379> get user:1:sex
"boy"
127.0.0.1:6379> get user:1
"{name:zhangsan,age:18}"
getset 先get一个值再设置
127.0.0.1:6379> getset db mysql #如果不存在值,则返回nil
(nil)
127.0.0.1:6379> get db
"mysql"
127.0.0.1:6379> getset db redis #如果存在值,获取原来的值,并设置新的值
"mysql"
127.0.0.1:6379> get db
"redis"
数据结构是相同的!
String类似的使用场景:value除了是字符串还可以是数字,计数器;统计多单位的数量;粉丝数;对象缓存存储;
List 列表
在redis里面,可以把list 模拟为 栈(一个口子进出,先进后出);队列(从一边进,另外一边出);阻塞队列(可以从两边去取)。
所有的list命令都是以 l 开头的 。
LPUSH(不区分大小写):放入元素 ,将一个值或多个值放入列表的头部,从左边放进去
LRANGE :获取区间内的元素 ,0号位为最新放进去的元素,(先进后出)
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 # 获取0到1号位的元素
1) "three"
2) "two"
127.0.0.1:6379> LPUSH lll a
(integer) 1
127.0.0.1:6379> LPUSH lll b
(integer) 2
127.0.0.1:6379> LRANGE lll 0 -1
1) "b"
2) "a"
127.0.0.1:6379>
rpush :将元素放到列表的右边(尾部) lrange:获取list中的值
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"
127.0.0.1:6379>
LPOP: 移除,从左边弹出 + key +count 从左侧(头部)移除多个元素
RPOP:从右边弹出 +key +count 从右侧(尾部)移除多个元素
127.0.0.1:6379> LPOP list # 移除列表的第一个元素
"three"
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> lrange list 0 -1
1) "a"
2) "b"
3) "a"
4) "two"
5) "one"
6) "right"
127.0.0.1:6379> lpop list 2 # 从左侧弹出两个元素
1) "a"
2) "b"
127.0.0.1:6379> rpop list 2 # 从尾部弹出两个元素
1) "right"
2) "one"
127.0.0.1:6379> lrange list 0 -1
1) "a"
2) "two"
Lindex : 通过下标获取某个数据 +key +index
127.0.0.1:6379> lrange list 0 -1
1) "oneone"
2) "one"
3) "a"
4) "two"
5) "right"
6) "haha"
127.0.0.1:6379> lindex list 0 # 获取0号位上的数据
"oneone"
127.0.0.1:6379> Lindex list 1 # 获取1号位上的数据
"one"
127.0.0.1:6379>
Llen : 返回列表的长度 +key
127.0.0.1:6379> Llen list
(integer) 6
127.0.0.1:6379>
Lrem : 移除指定个数的值 +key +count +value
127.0.0.1:6379> lrem list 1 one # 移除一个one
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "oneone"
2) "a"
3) "two"
4) "right"
5) "haha"
127.0.0.1:6379> lpush list a
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "a"
2) "oneone"
3) "a"
4) "two"
5) "right"
6) "haha"
127.0.0.1:6379> lrem list 2 a # 移除两个 "a"
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "oneone"
2) "two"
3) "right"
4) "haha"
127.0.0.1:6379>
trim : 修剪 ,通过下标截断list 只要其中部分值 +key +start +end
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "oneone"
3) "two"
4) "right"
5) "haha"
127.0.0.1:6379> ltrim list 1 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "oneone"
2) "two"
127.0.0.1:6379>
rpoplpush :移除列表的最后一个元素,并将此元素 从头部放入另一个列表(或者自身)
127.0.0.1:6379> lrange list 0 -1
1) "oneone"
2) "two"
127.0.0.1:6379> Rpush list right
(integer) 3
127.0.0.1:6379> lpush list one
(integer) 4
127.0.0.1:6379> rpoplpush list mylist
"right"
127.0.0.1:6379> lrange mylist 0 -1
1) "right"
127.0.0.1:6379> lrange list 0 -1
1) "one"
2) "oneone"
3) "two"
127.0.0.1:6379> rpoplpush list list # 将自己的末尾元素弹出 从头部再放入
"two"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "oneone"
127.0.0.1:6379>
lset : 对列表某下标值 进行赋值(更新当前下标的值),如果不存在会报错
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 -1
1) "value1"
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other
(error) ERR index out of range
127.0.0.1:6379>
linsert : 将某个元素 插入到列表中某个值的前面或者后面
127.0.0.1:6379> lpush list item2
(integer) 2
127.0.0.1:6379> linsert list before "item2" haha
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "haha"
2) "item2"
3) "item"
127.0.0.1:6379> linsert list after item2 world
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "haha"
2) "item2"
3) "world"
4) "item"
127.0.0.1:6379>
小结:实际上是一个链表,Node ,在前后都可以插入值 ;如果key不存在,创建新的链表。如果移除了所有值,空链表,代表不存在!;在两边插入或者改动值,效率最高!中间元素,相对来说效率会低一点。可用作消息队列(Lpush Rpop),栈(Lpush Lpop)。
Set中的值是不能重复的(无序不重复集合,抽随机!)
命令都是s开头,
sadd +key+value 增加集合名 及value
smembers +key 查看这个集合里面有哪些元素
sismember + key + value 判断某个值是否在集合中
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset 134
(integer) 1
127.0.0.1:6379> sadd myset "134" #不允许重复,如果重复无法添加进去
(integer) 0
127.0.0.1:6379> smembers myset #查看myset集合中有哪些元素
1) "134"
2) "hello"
127.0.0.1:6379> keys *
1) "myset"
127.0.0.1:6379> sismember myset 134 #查看这个元素是否在 这个key中
(integer) 1
127.0.0.1:6379> sismember myset 1344
(integer) 0
scard + key 获取集合中元素的个数
srem +key + value 移除某个集合中的某个元素
127.0.0.1:6379> sadd myset love
(integer) 1
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> scard myset
(integer) 2
127.0.0.1:6379> sismember myset "hello"
(integer) 0
127.0.0.1:6379> smembers myset
1) "134"
2) "love"
127.0.0.1:6379>
srandmember +key 随机抽选出一个元素
srandmember +key +count 随机抽取多个数量的元素
127.0.0.1:6379> scard myset
(integer) 5
127.0.0.1:6379> smembers myset
1) "1"
2) "134"
3) "eric"
4) "love"
5) "56"
127.0.0.1:6379> srandmember myset
"56"
127.0.0.1:6379> srandmember myset # 随机抽取一个元素
"love"
127.0.0.1:6379> srandmember myset
"134"
127.0.0.1:6379> srandmember myset 2 # 随机抽取指定个数的元素
1) "eric"
2) "love"
127.0.0.1:6379> srandmember myset 2
1) "love"
2) "56"
127.0.0.1:6379> srandmember myset 3
1) "love"
2) "1"
3) "56"
127.0.0.1:6379> srandmember myset 3
1) "eric"
2) "love"
3) "1"
随机删除一些set集合中的元素
spop + key +count 可随机删除多个元素
127.0.0.1:6379> smembers myset
1) "134"
2) "love"
3) "eric"
4) "1"
5) "56"
127.0.0.1:6379> spop myset # 随机弹出一个元素
"eric"
127.0.0.1:6379> spop myset
"1"
127.0.0.1:6379> smembers myset
1) "134"
2) "love"
3) "56"
1) "134"
2) "56"
127.0.0.1:6379> spop myset 2 # 随机弹出两个元素
1) "134"
2) "56"
127.0.0.1:6379> smembers myset
1) "love"
127.0.0.1:6379>
将一个指定的值移动到另外一个set集合中 !
127.0.0.1:6379> keys *
1) "myset2"
2) "myset"
127.0.0.1:6379> smembers myset
1) "eric"
2) "world"
3) "hello"
127.0.0.1:6379> smembers myset2
1) "lala"
2) "haha"
127.0.0.1:6379> smove myset myset2 hello # 将myset集合中的hello元素移动到myset2中
(integer) 1
127.0.0.1:6379> smembers myset
1) "eric"
2) "world"
127.0.0.1:6379> smembers myset2
1) "lala"
2) "hello"
3) "haha"
127.0.0.1:6379> sadd myset lala
(integer) 1
127.0.0.1:6379> smove myset myset2 lala # 如果目标集合中已经有元素了,依然移除成功,但是
(integer) 1
127.0.0.1:6379> smembers myset # 目标集合不可重复特性依然生效
1) "eric"
2) "world"
127.0.0.1:6379> smembers myset2
1) "lala"
2) "hello"
3) "haha"
127.0.0.1:6379>
微博、B站共同关注!(交集)
数字集合类: - 差集 - 交集 - 并集
sdiff +key1 +key2 :key1集合同key2集合比较,与key2集合不同的元素有哪些 (差集)
sinter +key1 +key2 : key1集合与key2集合比较,交集有哪些元素 (交集)
sunion +key1 +key2 :key1集合与key2集合的并集,重复元素不展示 (并集)
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 # 已key1集合同key2比较,那几个元素与key2集合不同(差集)
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2 # key1集合与key2集合 的交集
1) "c"
127.0.0.1:6379> sunion key1 key2 # key1集合与key2集合 的并集(重复元素去重)并集
1) "c"
2) "b"
3) "e"
4) "a"
5) "d"
127.0.0.1:6379>
微博,用户将所有关注的人放在一个set集合中 !将它的粉丝也放在一个集合中!
共同关注,共同爱好,二度好友,推荐好友!
Hash(哈希) 本质上与String类型没太大区别,还是一个简单的key-value 结构
Map集合,key-map 这个值是一个map集合!命令是h开头
hset +key +< field +value> 设置一个map名 及里面的key-value
hget +key +< field +value> 获取一个map名里面的某个key的value
hmset +key +< field +value>+< field +value>..... 批量设置map集合 里面的key-value
hmget +key +field +field +field ...... 批量获取map集合 里面的各个字段的value
127.0.0.1:6379> hset myhash field name # 添加map的key-v
(integer) 1
127.0.0.1:6379> hget myhash field # 获取myhash 里面 field字段的值
"name"
127.0.0.1:6379> hset myhash name eric
(integer) 1
127.0.0.1:6379> hget myhash name
"eric"
127.0.0.1:6379> hset myhash age 18
(integer) 1
127.0.0.1:6379> hget myhash age
"18"
127.0.0.1:6379> hmset myhash score 18 sex girl # 批量设置hash 里面 k-v k-v
OK
127.0.0.1:6379> hmget myhash name age score sex # 批量获取hash 里面 k k k k 对应的值
1) "eric"
2) "18"
3) "18"
4) "girl"
127.0.0.1:6379>
hgetall +key 获取 hash表里面map的所有内容 k-v
127.0.0.1:6379> hmset myhash score 18 sex girl
OK
127.0.0.1:6379> hmget myhash name age score sex
1) "eric"
2) "18"
3) "18"
4) "girl"
127.0.0.1:6379> hgetall myhash # 获取所有元素内容 k-v 键值对方式展现
1) "field"
2) "name"
3) "name"
4) "eric"
5) "age"
6) "18"
7) "score"
8) "18"
9) "sex"
10) "girl"
127.0.0.1:6379>
hdel +key +field .... 删除hash表中的 map里面的一个k-v 或者多个k-v
127.0.0.1:6379> hdel myhash field
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "name"
2) "eric"
3) "age"
4) "18"
5) "score"
6) "18"
7) "sex"
8) "girl"
127.0.0.1:6379> hdel myhash name age
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "score"
2) "18"
3) "sex"
4) "girl"
127.0.0.1:6379>
hlen + key 获取哈希表的字段数量
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> hgetall myhash
1) "score"
2) "18"
3) "sex"
4) "girl"
5) "field1"
6) "hello"
7) "field2"
8) "world"
127.0.0.1:6379> hlen myhash
(integer) 4
127.0.0.1:6379>
hexists +key +field 判断hash表的map里面是否包含某个字段
hkeys +key 获取map中的所有key
hvals +key 获取map中的所有value
127.0.0.1:6379> hgetall myhash
1) "score"
2) "18"
3) "sex"
4) "girl"
5) "field1"
6) "hello"
7) "field2"
8) "world"
127.0.0.1:6379> hlen myhash # 获取长度
(integer) 4
127.0.0.1:6379> hexists myhash field1 # 是否含有某个字段
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0
127.0.0.1:6379> hkeys myhash # 展示所有的key
1) "score"
2) "sex"
3) "field1"
4) "field2"
127.0.0.1:6379> hvals myhash # 展示所有的value
1) "18"
2) "girl"
3) "hello"
4) "world"
127.0.0.1:6379>
hincrby +key +field + num 某个字段的value 自增多少
hsetnx +key +field +value 如果某个字段不存在,进行set操作,存在则不set
127.0.0.1:6379> hset myhash field3 5
(integer) 1
127.0.0.1:6379> hincrby myhash field3 1 # 自增1
(integer) 6
127.0.0.1:6379> hincrby myhash field3 -1 # 自增-1
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hehe # 如果字段不存在 set操作
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 lala # 存在 则set无效
(integer) 0
127.0.0.1:6379> hincrby myhash field3 2 # 自增2
(integer) 7
127.0.0.1:6379> hgetall myhash
1) "score"
2) "18"
3) "sex"
4) "girl"
5) "field1"
6) "hello"
7) "field2"
8) "world"
9) "field3"
10) "7"
11) "field4"
12) "hehe"
127.0.0.1:6379> hincrby myhash field3 # 自增后面必须加步长
(error) ERR wrong number of arguments for 'hincrby' command
127.0.0.1:6379>
hash变更的数据 user name age 尤其是用户信息之类的,经常变动的信息! hash更加适合于对象的存储,String更加适合字符串的存储!
hset +objectName: num +
hget +objectName:num + k 获取对象的某个属性的value
hmget +objectName:num + k +k +k ...... 批量获取对象的属性的value
127.0.0.1:6379> hset user:1 name eric age 25 score 100 # 设置对象的属性及value
(integer) 3
127.0.0.1:6379> hget user:1 name # 获取其中某个属性的值
"eric"
127.0.0.1:6379> hmget user:1 name age score # 批量获取属性的值
1) "eric"
2) "25"
3) "100"
127.0.0.1:6379>
Zset (有序集合)
在set的基础上,增加了一个值 ,中间加了一score 标识位,可用于排序
set +key +score +value
zadd + key +score +value +score +value 添加多个值并含有排序所用的数字
zrange +key + 0 -1 获取 某个zset里面的所有元素
127.0.0.1:6379> zadd myzset 1 one # 添加一个值 含有排序所用的值
(integer) 1
127.0.0.1:6379> zadd myzset 2 two
(integer) 1
127.0.0.1:6379> zadd myzset 3 three 4 four # 添加多个值
(integer) 2
127.0.0.1:6379> zrange myzset 0 -1 # 获取所有的元素
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379>
排序:
zrangebyscore + key +min +max (从负无穷 到正无穷) 最小值到最大值之间
127.0.0.1:6379> zadd salary 1500 lina
(integer) 1
127.0.0.1:6379> zadd salary 3000 lily
(integer) 1
127.0.0.1:6379> zadd salary 4500 tom
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf
1) "lina"
2) "lily"
3) "tom"
127.0.0.1:6379> zadd salary 500 oscar
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "oscar"
2) "lina"
3) "lily"
4) "tom"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 可以带上 score 一起展示
1) "oscar"
2) "500"
3) "lina"
4) "1500"
5) "lily"
6) "3000"
7) "tom"
8) "4500"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores # 指定scaore范围
1) "oscar"
2) "500"
3) "lina"
4) "1500"
127.0.0.1:6379> zadd salary 500 oscar # 会自动去重 如果score 和 元素名 一致
(integer) 0
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 升序展示所有元素及scores
1) "oscar"
2) "500"
3) "lily"
4) "3000"
5) "tom"
6) "4500"
127.0.0.1:6379> zrevrange salary 0 -1 withscores # 降序展示所有元素及scores
1) "tom"
2) "4500"
3) "lily"
4) "3000"
5) "oscar"
6) "500"
127.0.0.1:6379>
移除某元素
zrem + key + value
zcard + key 获取 有序集合中的元素个数
zrevrange +key 0 -1 从高到低降序展示
zrevrange +key 0 -1 withscores 从高到低降序展示 并展示 scores
zcount +key +min +max 计算score 在某个区间的元素的个数
zcard +key 计算集合中所有的元素的个数
127.0.0.1:6379> zrange salary 0 -1
1) "oscar"
2) "lina"
3) "lily"
4) "tom"
127.0.0.1:6379> zrem salary lina # 移除lina这个元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "oscar"
2) "lily"
3) "tom"
127.0.0.1:6379> zcard salary # 集合中所有的元素个数
(integer) 3
127.0.0.1:6379> zcount salary 100 1000 # 计算满足在此区间内的元素个数
(integer) 1
127.0.0.1:6379> zadd zset 1 hello
(integer) 1
127.0.0.1:6379> zadd zset 2 world
(integer) 1
127.0.0.1:6379> zadd zset 3 haha
(integer) 1
127.0.0.1:6379> zcount zset 1 3
(integer) 3
127.0.0.1:6379> zcount zset 1 2
(integer) 2
127.0.0.1:6379> zrevrange salary 0 -1 # 从高到低 降序展示
1) "tom"
2) "lily"
3) "oscar"
127.0.0.1:6379> zrevrange salary 0 -1 withscores # 带有score展示
1) "tom"
2) "4500"
3) "lily"
4) "3000"
5) "oscar"
6) "500"
127.0.0.1:6379> zcard salary # 查看有多少个元素
(integer) 3
set的排序版,可存储成绩、薪资,可带权重来进行判断,对不同信息进行赋值1 2 3 ,根据数据量、粉丝数等信息 进行排序展示排行榜。
三种特殊类型
geospatial 地理位置
定位、附近的人、打车距离计算?Redis 的 Geo 在Redis 3.2 版本已经有了 ,可以推算地理位置信息,两地之间的距离,附近的人
共有6个命令
GEOADD GEODIST GEOHASH GEOPOS GEORADIUS GEORADIUSBYMEMBER
geoadd 添加位置(经度,纬度,名称) 南极北极无法添加 ,一般都会下载城市数据,通过java程序一次性导入
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing # 添加key 为城市
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai # 添加经纬度 及名称
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chognqing 114.05 22.52 shengzheng
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian # 添加多个
(integer) 2
127.0.0.1:6379>
geopos 从key里面获取指定城市的经度纬度
获得当前定位:一定是一个坐标值!
127.0.0.1:6379> keys *
1) "zset"
2) "china:city"
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city chongqing
1) 1) "106.49999767541885376"
2) "29.52999957900659211"
计算两人之间的距离!直线距离
geodist :返回两个给定位置之间的距离
m 表示单位为 米 km 千米 mi 英里 ft 英尺
127.0.0.1:6379> geodist china:city beijing hangzhou #默认距离 米
"1127337.7813"
127.0.0.1:6379> geodist china:city beijing hangzhou km
"1127.3378"
127.0.0.1:6379>
我附近的人?(获得所有附近的人的地址,定位!)通过半径来查询!
georadius - 以给定的经纬度为中心,找出某一半径内的元素,且可显示距离、经纬度、符合条件的数据的数量
127.0.0.1:6379> georadius china:city 110 30 1000 km # 以此位置为中心,半径1000km内的城市
1) "chongqing"
2) "xian"
3) "shenzheng"
4) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 500 km # 此集合中,此位置半径500km内的城市
1) "chongqing"
2) "xian"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist # 显示直线距离
1) 1) "chongqing"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord # 显示符合条件的城市的经纬度
1) 1) "chongqing"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> georadius china:city 110 30 500 km withdist withcoord count 1 # 限制显示的数量为1
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
georadiusbymember 基于给定的元素来寻找周围符合条件的元素
127.0.0.1:6379> georadiusbymember china:city hangzhou 500 km
1) "hangzhou"
geohash 返回一个或多个位置元素的geohash表示
如果两个字符串越接近,距离越近
127.0.0.1:6379> geohash china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
GEO底层的实现原理其实就是Zset ! 可以使用zset命令来操作GEO
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "xian"
3) "shenzheng"
4) "hangzhou"
5) "beijing"
hyperloglog : 用来做基数统计的算法!
优点:占用内存固定12kb 如果从内存角度来比较 Hyperloglog首选
什么是基数 ?不重复的元素,拥有相等的对应的关系 的个数
A { 1,35,5,7,8,3} B{1,3,5,7,8}
例如:数据集A和B 之间对比,不重复的元素的数量,叫做基数,可以接受误差
例子:网页UV 一个人访问一个网站多次,但是还是算作一个人
传统的方式用set保存用户的id,就可以统计set中元素数量作为标准判断!这个方式如果保存大量的用户id就会比较麻烦!目的是为了计数而不是为了保存用户id;
有0.81%的错误率。
pfadd +key +elements 添加或创建数据
pfcount + key 统计基数
pfmerge +targetKey +sourceKey1 +sourceKey2 将两组元素合并到新的key中并会去重
127.0.0.1:6379> pfadd mykey a b c d e f g h i j # 创建第一组元素
(integer) 1
127.0.0.1:6379> pfcount mykey # 统计mykey中基数数量
(integer) 10
127.0.0.1:6379> pfadd mykey2 i j z x c v
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 6
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # 合并两组元素 为mykey3
OK
127.0.0.1:6379> pfcount mykey3 # 查看并集的数量,去重
(integer) 13
127.0.0.1:6379> pfadd mykey1 a b c d d d
(integer) 1
127.0.0.1:6379> pfcount mykey1 # mykey1的基数数量为4
(integer) 4
127.0.0.1:6379> pfadd mykey1 e f # 继续添加两个不重复元素
(integer) 1
127.0.0.1:6379> pfcount mykey1
(integer) 6
127.0.0.1:6379>
如果允许容错,一定可以使用Hyperloglog!
Bitmaps 位存储
统计用户信息,活跃、不活跃,登录、未登录,打卡,365打卡!只有两个状态的,都可以使用Bitmaps,都是操作二进制位来进行记录,就只有0和1两个状态!通过位运算来表示元素对应的值,365天=365bit 1字节=8bit
setbit +key +offset +value
127.0.0.1:6379> setbit sign 0 1 # 打卡记录,周一打卡
(integer) 0
127.0.0.1:6379> setbit sign 1 0 # 打卡记录,周二未打卡
(integer) 0
127.0.0.1:6379> setbit sign 2 1 # 打卡记录,周二打卡
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 2
(error) ERR bit is not an integer or out of range
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
使用bitmap 周一到周日的打卡! 周一:1 周二:0 周三:1 周四:0 周五:1 周六:0周日:1
查看某一天是否打卡
getbit +key +offset 获取信息
127.0.0.1:6379> getbit sign 3 # 查看 周四打卡情况
(integer) 0
127.0.0.1:6379> getbit sign 2 # 查看 周三打卡情况
(integer) 1
127.0.0.1:6379>
统计操作:统计打卡天数
127.0.0.1:6379> bitcount sign # 统计 值为1的数据,这周的打卡记录
(integer) 4
bitcount +key [start , end]
就是bitcount的start和end是字节(byte)数,而bitmap存储数据的时候是按位(bit)存储的,而1byte=8bit,所以bitmapcount sign 0 0,实际上是统计了bitmap的offect前8位为1的数量
127.0.0.1:6379> bitcount sign 0 0
(integer) 5
127.0.0.1:6379> bitcount sign
(integer) 6
事务(不保证原子性!没有隔离级别的概念,所有的命令在事务中并没有直接被执行,只有发起执行命令的时候才会执行)
Redis单条命令是保证原子性的,但是事务不保证原子性!
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会像队列一样,按照顺序执行!
一次性、顺序性、排他性(不允许别人干扰)执行一系列的命令!Exec
---- 队列 set set set 执行----
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> # 执行完毕
执行完毕。
放弃事务: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 v3
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>
编译型异常(命令有错!),事务中所有的命令都不会被执行!
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k v
QUEUED
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)> getset k3 # 错误命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 所有的命令都不会被标错
(nil)
运行时异常(例:1/0),事务队列中存在语法性错误,执行命令的时候,错误命令抛出异常,其他命令可以正常执行!(redis事务无原子性,mysql则是要么都执行成功,要么都执行失败)
127.0.0.1:6379> set k1 aaa
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incr k1 # 对字符串进行自增1 命令没问题 但是运行会报错
QUEUED
127.0.0.1:6379(TX)> set k2 2
QUEUED
127.0.0.1:6379(TX)> set k3 3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec
1) (error) ERR value is not an integer or out of range #执行事务 这个命令报错 其他命令成功
2) OK
3) OK
4) "3"
127.0.0.1:6379> get k2 # 获得执行后的结果
"2"
127.0.0.1:6379> get k3
"3"
Redis用乐观锁监控(Watch)
悲观锁:认为什么时候都会出问题,无论做什么都加锁
乐观锁:很乐观,认为什么时候都不会出问题,所以不会加锁!更新数据的时候去判断在此期间是否有人修改过这个数据,获取version,更新的时候比较version ,CAS的原理。
127.0.0.1:6379> set money 1000
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) 980
2) (integer) 20
127.0.0.1:6379>
再开一个客户连接redis,用于测试多线程修改值,使用watch可以当做redis的乐观锁操作!相当于mysql的获取version
[root@centos7 bin]# redis-cli -p 6379
127.0.0.1:6379> keys *
1) "out"
2) "money"
[root@centos7 bin]# ps -ef |grep redis
root 2254 1 0 16:33 ? 00:00:13 redis-server 127.0.0.1:6379
root 2265 2186 0 16:34 pts/1 00:00:00 redis-cli -p 6379
root 6668 6627 0 22:46 pts/2 00:00:00 redis-cli -p 6379
root 6690 2126 0 22:46 pts/0 00:00:00 grep --color=auto redis
127.0.0.1:6379> keys *
1) "out"
2) "money"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set money 100
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 50
QUEUED
127.0.0.1:6379(TX)> exec # 在执行之前 另外一个cli端/线程 进行了修改money
(nil)
127.0.0.1:6379> get money # 执行结果为 nil ,因为被修改了导致事务执行失败
"101"
127.0.0.1:6379>
事务执行执行,对money进行修改
127.0.0.1:6379> get money
"100"
127.0.0.1:6379> set money 101
OK
这种情况下,如果事务执行失败,需要先解锁,再重新进行监视,获取最新的值,再进行事务操作
unwatch
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 20
QUEUED
127.0.0.1:6379(TX)> exec #比对监视的值,是否有变化,有变化就会失败,没有变化就执行成功
1) (integer) 81
Jedis
使用java来操作Redis,Jedis是Redis官方推荐的java连接开发工具!使用java操作Redis中间件。(目前用springboot集成了)。
测试:
1、建立空项目,加入maven moudle
2、导入依赖
redis.clients
jedis
4.2.3
com.alibaba
fastjson
1.2.28
3、编码测试:连接、操作、断开
新建Jedis对象,可空参,可传参创建,此处指定地址及端口建立
public Jedis(String host, int port) {
this.commandObjects = new CommandObjects();
this.db = 0;
this.transaction = null;
this.isInMulti = false;
this.isInWatch = false;
this.pipeline = null;
this.dataSource = null;
this.connection = new Connection(host, port);
可能连接不上远端redis
服务器上的地址可能无法连接,需要进入redis.conf文件,先注释掉:bind 127.0.0.1 再修改保护yes 为 no protected-mode no
放行6379端口并重启防火墙
[root@centos7 rconfig]# firewall-cmd --zone=public --add-port=6379/tcp --permanent
success
[root@centos7 rconfig]# systemctl restart firewalld
本地连接测试:
public class TestPing {
public static void main(String[] args) {
//2.new Jedis 对象 此处以本地连接为例
Jedis jedis = new Jedis("127.0.0.1", 6379);
//jedis所有的命令 就是之前学的redis命令 在java里都是一个一个的方法
System.out.println(jedis.ping());
System.out.println("清空数据:"+jedis.flushDB());
System.out.println("判断值是否存在"+jedis.exists("username"));
System.out.println("新增键值对"+jedis.set("username","eric"));
System.out.println("新增键值对"+jedis.set("password","123456"));
//所有的键
Set keys = jedis.keys("*");
System.out.println(keys);
//删除
jedis.del("password");
//判断是否存在
System.out.println(jedis.exists("password"));
System.out.println("查看所存储的值的类型:"+jedis.type("username"));
System.out.println("随机返回key空间的一个:"+jedis.randomKey());
System.out.println("重命名key:"+jedis.rename("username","name"));
System.out.println("获取值:"+jedis.get("name"));
System.out.println("按索引查询:"+jedis.select(0));
System.out.println("返回当前数据库key的数目:"+jedis.dbSize());
System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
}
}
常用的API
String List Set Hash Zset Geopatial Hyperloglog Bitmap
public class TestJedis {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println("增加多个值:"+jedis.mset("key","v","k2","v2","k3","v3","k4","v4"));
System.out.println("获取多个值:"+jedis.mget("key","k2"));
System.out.println("删除多个键值对:"+jedis.del("k4","k3"));
jedis.flushDB();
//不存在再设置,分布式锁
System.out.println(jedis.setnx("key1","value"));
System.out.println(jedis.setnx("key2","value2"));
System.out.println(jedis.setnx("key3","value3"));
System.out.println(jedis.get("key2"));
//新增键值对,并设置过期时间
System.out.println(jedis.setex("k",4,"v"));
System.out.println(jedis.get("k"));
/* try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println(jedis.get("k"));
System.out.println("===========添加一个list=============");
jedis.lpush("collections","ArrayList","vector","stack","haha");
jedis.lpush("collections","tree");
System.out.println("collections的所有内容:"+jedis.lrange("collections",0,-1));
System.out.println("collections的内容:"+jedis.lrange("collections",0,3));
System.out.println("collections左边出栈:"+jedis.lpop("collections"));
System.out.println("collections截取3个:"+jedis.ltrim("collections",0,2));
System.out.println("collections的所有内容:"+jedis.lrange("collections",0,-1));
jedis.lpush("list","2","0","0","3","9","200");
System.out.println(jedis.lrange("list", 0, -1));
List sortList = jedis.sort("list");
System.out.println("排序后:"+sortList);
}
}
事务:
//事务
public class TextTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","lily");
jsonObject.put("age","18");
//开启事务
Transaction multi = jedis.multi();
String s = jsonObject.toString();
System.out.println("json串数据为:"+s);
//模拟监控
jedis.watch("s");
try {
multi.set("user",s);
multi.set("user1",s);
//模拟失败
// int i = 1/0;
multi.exec();
} catch (Exception e) {
//如果失败有异常,放弃事务
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("user"));
System.out.println(jedis.get("user1"));
//执行完毕,关闭连接
jedis.close();
}
}
}
SpringBoot整合
SpringBoot操作数据:都放在spring-data里的 jpa jdbc mongodb redis
SpringData也是和SpringBoot齐名的项目
在SpringBoot2.x之后,在依赖的底层,之前的Jedis 被替换成了lettuce
io.lettuce
lettuce-core
5.3.5.RELEASE
Jedis : 采用的直连,多个线程操作,不安全的,如果要避免不安全的,使用jedis pool连接池。更像BIO模式
lettuce : 采用netty ,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数量,像NIO模式
新增springboot类型的模块,加上web/data Redis/DevTools/Lombok/spring Configuration Processor
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate") //我们可以自己定义一个redisTemplate来替换这个默认的 此注解代表 如果没有这个template则自动生效
public RedisTemplate
// RedisConnectionFactory 是个接口,共有两个实现类 一个是 JedisConnectionFactory 源码down下来会发现 很多类其实是不存在的,并没有生效
public class JedisConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {
private final static Log log = LogFactory.getLog(JedisConnectionFactory.class);
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
JedisConverters.exceptionConverter());
private final JedisClientConfiguration clientConfiguration;
private @Nullable JedisShardInfo shardInfo;
private boolean providedShardInfo = false;
private @Nullable Pool pool;
private boolean convertPipelineAndTxResults = true;
private RedisStandaloneConfiguration standaloneConfig = new RedisStandaloneConfiguration("localhost",
Protocol.DEFAULT_PORT);
另一个实现类 LettuceConnectionFactory 类是生效的
public class LettuceConnectionFactory
implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {
private static final ExceptionTranslationStrategy EXCEPTION_TRANSLATION = new PassThroughExceptionTranslationStrategy(
LettuceConverters.exceptionConverter());
private final Log log = LogFactory.getLog(getClass());
private final LettuceClientConfiguration clientConfiguration;
private @Nullable AbstractRedisClient client;
private @Nullable LettuceConnectionProvider connectionProvider;
private @Nullable LettuceConnectionProvider reactiveConnectionProvider;
private boolean validateConnection = false;
private boolean shareNativeConnection = true;
private boolean eagerInitialization = false;
private @Nullable SharedConnection connection;
private @Nullable SharedConnection reactiveConnection;
private @Nullable LettucePool pool;
/** Synchronization monitor for the shared Connection */
1.导入依赖 2.配置连接 3.使用
org.springframework.boot
spring-boot-starter-data-redis
所以,在进行配置的时候,用的Leuce 下面的连接池
# 配置redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
# spring.redis.cluster.nodes= 配置集群节点
@SpringBootTest
class Redis02SpringbootApplicationTests {
//将自带的模板进行注入 使用
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// opsForValue() 操作字符串类似String 类型
//opsForList() 操作list类型
//opsForSet() opsForHash() opsForGeo() opsForZSet() opsForHyperLogLog
//redisTemplate.opsForList().leftPop()
//常用的方法,直接通过redisTemplate来操作,比如事务,执行,取消
//去操作关于数据库的连接 获取连接对象
/*RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
connection.flushAll();*/
redisTemplate.opsForValue().set("mykey","helloWorld");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
}
RedisTemplate 模板对象 有默认的序列化配置
public class RedisTemplate extends RedisAccessor implements RedisOperations, BeanClassLoaderAware {
private boolean enableTransactionSupport = false;
private boolean exposeConnection = false;
private boolean initialized = false;
private boolean enableDefaultSerializer = true;
private @Nullable RedisSerializer> defaultSerializer;
private @Nullable ClassLoader classLoader;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
private RedisSerializer stringSerializer = RedisSerializer.string();
如果没有自定义序列化方式,会报错
//测试没有序列化
@Test
public void test() throws JsonProcessingException {
User lily = new User("lily", 18);
//jackson帮忙序列化 变成json
String jsonLily = new ObjectMapper().writeValueAsString(lily);
System.out.println("jackson方式:"+jsonLily);
//fastJson
System.out.println("fastJson转换方式:"+JSON.toJSONString(lily));
redisTemplate.opsForValue().set("user",jsonLily);
System.out.println(redisTemplate.opsForValue().get("user"));
//{"name":"lily","age":18}
//如果不转换直接传对象进来
redisTemplate.opsForValue().set("user1",lily);
System.out.println(redisTemplate.opsForValue().get("user1"));
}
关于对象的保存,需要序列化以后才可以正常传输
默认序列化方式为JDK序列化,这个时候我们可能会使用Json来序列化,需要自己定义配置类才行
boolean defaultUsed = false;
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
如果用默认的,中文内容可能会有问题,此时可以在配置包里面加上redis配置类,参照原有的redisTemplate方式,写一个配置类,
@Configuration
public class RedisConfig {
//编写我们自己的reidsTemplate
@Bean
//@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate template = new RedisTemplate<>();
//自定义的序列化方式
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
里面的参数默认用的jdk的序列化 以Jackson 的序列化对象为例
完整自定义配置模板
//固定模板
@Configuration
public class RedisConfig {
//编写我们自己的reidsTemplate 将原有的方式改为方便使用
@Bean
//@ConditionalOnMissingBean(name = "redisTemplate")
@SuppressWarnings("all") //跳过所有检查
public RedisTemplate redisTemplate1(RedisConnectionFactory factory)
throws UnknownHostException {
//一般方便 直接使用
RedisTemplate template = new RedisTemplate();
//默认连接工厂
template.setConnectionFactory(factory);
//可以new jackson的redis序列化对象 自定义的jason序列化配置 用json去解析任意的对象
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//通过ObjectMapper 进行转译 转完之后就可以使用了
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.setKeySerializer(jackson2JsonRedisSerializer);*/
//把所有的properties set进去
template.afterPropertiesSet();
return template;
}
}
@SpringBootTest
class Redis02SpringbootApplicationTests {
//将自带的模板进行注入 使用
@Autowired
@Qualifier("redisTemplate1") //可能有定义多个模板,可通过名字指定注入
private RedisTemplate redisTemplate;
如果用原有的配置 key会出现 下面情况,当用自定义的以后,就变了,一般都不会用原生的方式
封装为一个常用工具类:RedisUtil
@Component
public final class RedisUtil {
//注入自己定义的redisTemplate
@Autowired
private RedisTemplate redisTemplate;
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据 key 获取过期时间
* @param key 键不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key 是否存在
* @param key 键
* @return true 存在 false 不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除
* @param key 可以传一个 或多个
*/
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
//重载的delete方法 里面需要传入的是集合 用集合工具类 将key 转为集合list
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 普通缓存的获取
* @param key 键
* @return value 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒)time要大于0,如果time小于等于0 将设置无限制
* @return true成功 false失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta > 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().decrement(key, -delta);
}
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
* @return
*/
public Object hget(String key,String item){
return redisTemplate.opsForHash().get(key,item);
}
/**
* 获取hashKey 对应的所有键值
* @param key 键
* @return 对应的多条记录
*/
public Map hmget(String key){
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
* @return true false
*/
public boolean hmset(String key,Map map){
try {
redisTemplate.opsForHash().putAll(key,map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true false
*/
public boolean hmset(String key,Map map,long time){
try {
redisTemplate.opsForHash().putAll(key,map);
if(time>0){
expire(key,time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表 中存入数据,如果不存在将创建
* @param key 键
* @param item 项(hash表里面的 k)
* @param value 值
* @return true成功 false失败
*/
public boolean hset(String key,String item,Object value){
try {
redisTemplate.opsForHash().put(key,item,value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表 中存入数据,如果不存在将创建
* 并设置过期时间 (如果传入有时间)
* @param key 键
* @param item 项(hash表里面的 k)
* @param value 值
* @param time 时间(秒) 注意:如果已经存在的hash表有时间,这里将会替换原有时间
* @return true false
*/
public boolean hset(String key,String item,Object value,long time){
try {
redisTemplate.opsForHash().put(key,item,value);
if(time>0){
expire(key,time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的元素
* @param key 键
* @param item 元素 可以是多个,但是不能为null
*/
public void hdel(String key,Object... item){
redisTemplate.opsForHash().delete(key,item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true存在 false 不存在
*/
public boolean hHashKey(String key,String item){
return redisTemplate.opsForHash().hasKey(key,item);
}
/**
* hash 递增 如果不存在就会创建一个并把新增后的值返回
* @param key 键
* @param item 项
* @param incr 要增加几(大于0,步长)double类型
* @return
*/
public double hincr(String key,String item,double incr){
return redisTemplate.opsForHash().increment(key,item,incr);
}
/**
*hash 递减
* @param key 键
* @param item 项
* @param decr 要减少几
* @return
*/
public double hdecr(String key,String item,double decr){
return redisTemplate.opsForHash().increment(key,item,-decr);
}
//---------------------------------------------------------
/**
* 根据key 获取Set集合中的所有值
* @param key 键
* @return Set
*/
public Set sGet(String key){
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value 从一个set中查询,是否存在
* @param key 键
* @param value 值
* @return true存在 false不存在
*/
public boolean sHasKey(String key,Object value){
try {
return redisTemplate.opsForSet().isMember(key,value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set 缓存
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key,Object... values){
try {
return redisTemplate.opsForSet().add(key,values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
* @param key 键
* @param time 时间
* @param values 值 可多个
* @return 成功个数
*/
public long sSetAndTime(String key,long time,Object... values){
try {
Long count=redisTemplate.opsForSet().add(key,values);
if(time>0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
* @param key 键
* @return
*/
public long sGetSetSize(String key){
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
* @param key 键
* @param values 值,可以是多个
* @return 移除的个数
*/
public long setRemove(String key,Object... values){
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
//----------list---------
/**
* 获取list缓存的内容
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有的值
* @return
*/
public List lGet(String key,long start,long end){
try {
return redisTemplate.opsForList().range(key,start,end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存 的长度
* @param key 键
* @return 长度
*/
public long lGetListSize(String key){
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
* @param key 键
* @param index 索引 index>=0 时,0表示头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
* @return
*/
public Object lGetIndex(String key,long index){
try {
return redisTemplate.opsForList().index(key,index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list 放入缓存
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key,Object value){
try {
redisTemplate.opsForList().rightPush(key,value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list 放入缓存,且可以设置过期时间
* @param key 键
* @param value 值
* @param time 过期时间(秒)
* @return
*/
public boolean lSet(String key,Object value,long time){
try {
redisTemplate.opsForList().rightPush(key,value);
if(time>0){
expire(key,time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list 放入缓存
* @param key 键
* @param values 值
* @return
*/
public boolean lSet(String key,List values){
try {
redisTemplate.opsForList().rightPushAll(key,values);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list 放入缓存
* @param key 键
* @param values 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key,List values,long time){
try {
redisTemplate.opsForList().rightPushAll(key,values);
if(time>0){
expire(key,time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key,long index,Object value){
try {
redisTemplate.opsForList().set(key,index,value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除 列表中多个值 为value的元素
* @param key 键
* @param count 数量
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key,long count,Object value){
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
Redis.conf详解
启动的时候一般就通过配置文件启动的。
规定了单位 ,大小写不敏感,都是一样的。
可以把多个配置文件都配置过来,包含关系
网络:
bind 127.0.0.1 绑定 ip 可*通配
protected-mode yes 保护模式
port 6379 端口设置
################################# GENERAL ###############################
#通用配置:
daemonize yes #守护进程 的方式运行,默认为no ,我们需要手动开启,不然退出,此进程就自动结束了
pidfile /var/run/redis_6379.pid #如果后台的方式运行,就需要指定一个进程文件
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产环境
# warning (only very important / critical messages are logged)
loglevel notice #日志级别 一般默认
logfile "" #日志的文件位置名,如果为空则是标准的输出不管
databases 16 # 数据库的数量 默认16个
always-show-logo no # 是否总是显示redis的logo 目前是不显式的
快照:持久化的时候会用到
持久化:在规定的时间内,执行了多少次操作会持久化到文件.rdb .aof 文件
redis是内存数据库,如果没有持久化,数据断电即失
################################ SNAPSHOTTING #########################
# 持久化规则
#3600为时间,如果3600秒内,至少一个key进行了修改,此时进行持久化操作
save 3600 1
#如果300秒内,至少100个key进行了修改,会进行持久化操作
save 300 100
#如果60秒内,至少10000个key进行了修改,会进行持久化操作
save 60 10000
stop-writes-on-bgsave-error yes #如果持久化出错了,是否继续工作 默认yes
rdbcompression yes #是否压缩 rdb 文件,此时会消耗cpu资源
rdbchecksum yes #保存rdb 文件 的时候,是否进行检查校验
# The filename where to dump the DB
dbfilename dump.rdb
dir ./ #.rdb 文件 保存的目录
主从复制:
################################# REPLICATION ##########################
安全:可设置redis的密码
######################### SECURITY #####################
# requirepass foobared redis 默认没有密码的 可以在配置文件设置 也可以通过客户端设置
# requirepass 123456
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass # 获取配置的redis密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" # 设置密码
OK
# 再次连接操作的时候就提示需要认证
127.0.0.1:6379> keys *
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456 # 进行密码登录
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379> config set requirepass '' # 取消密码恢复默认 无密码状态
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
客户端的一些限制:
############################ CLIENTS ########################
maxclients 10000 默认 连接redis最大连接数量
############ MEMORY MANAGEMENT ########
maxmemory
maxmemory-policy noeviction 内存到上限后的处理策略
volatile-lru : 对设置了过期时间的key进行LRU(默认值)移除
allkeys-lru : 删除lru算法的key
volatile-random : 随机删除即将过期key
allkey-random : 随机删除key
volatile-ttl : 删除即将过期
noeviction : 永不过期,直接返回错误
# maxmemory-policy noeviction
# LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
# algorithms (in order to save memory), so you can tune it for speed or
# accuracy. By default Redis will check five keys and pick the one that was
# used least recently, you can change the sample size using the following
# configuration directive.
############ APPEND ONLY MODE ########
aof 配置
appendonly no # 默认不开启aof ,默认使用rdb方式持久化,大部分所有情况下,rdb完全够用
appendfilename "appendonly.aof" # aof的持久化文件
# appendfsync always # 每次修改都会同步 消耗性能
appendfsync everysec # 每秒执行一次 sync 同步 ,可能会丢失1s的数据
# appendfsync no # 不执行sync ,这个时候操作系统自己同步数据,速度最快
Redis 持久化
redis是内存数据库,如果没有持久化,数据断电即失,一旦服务器进程退出,服务器中的数据库状态也会消失。所以Redis提供了持久化功能!
RDB(Redis DataBase)
在指定的时间间隔内将内存中的数据集快照写入磁盘,Snapshot快照,它恢复时是将快照文件直接读到内存里。Redis会单独创建(fork)一个子进程来尽心持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的。确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。目前一般默认的是RDB,一般情况不需要修改这个配置!
rdb 保存的文件是 dump.rdb : 生产环境一般都会对其进行备份
在配置文件中的快照配置中进行配置,可设置策略 什么时候触发RDB操作,就会生成 dump.rdb文件
[root@centos7 bin]# ls
dump.rdb redis-benchmark redis-check-rdb redis-sentinel
rconfig redis-check-aof redis-cli redis-server
[root@centos7 bin]#
触发机制:
1.save的规则满足的情况下,会自动触发rdb规则
2.执行flushall命令,也会触发rdb规则,生成dump.rdb
3.退出redis,也会产生rdb文件!
如何恢复rdb文件?
1.只需要将rdb文件放在我们redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复其中的数据!
2.查看需要存放的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 如果在这个目录下存在dump.rdb 文件,启动就会自动恢复其中的数据
127.0.0.1:6379>
几乎默认的配置就够用了,在主从复制中,rdb就是备用的,在从机上面!
也可以bgsave,这样不会造成正常主线程的写入阻塞
优点:
1、适合大规模的数据恢复
2、如果对数据的完整性要求不高
缺点:
1.需要一定的时间间隔进行操作
2.如果redis意外宕机,最后一次的修改数据就没有了
3.fork进程的时候,会占用一定的内存空间,需要考虑内存空间的问题
aof保存的是 appendonly.aof 文件
记录所有的执行命令,history ,恢复的时候就根据这个文件,全部执行一遍
也会fork一个子进程,根据内存中的数据快照,写入重建数据库状态的命令,进入临时aof文件。以日志的形式来记录每个写操作,将redis执行过的所有指令记录下来(读命令不记录),只许最佳文件但不可以改写文件,redis启动之初就会读取该文件重新构建数据,redis重启的话根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
默认不开启,如果开启需要修改为yes,开启; 默认每秒去写
默认不进行重写,保证数据的安全性
修改后,停止服务,重启
127.0.0.1:6379> shutdown
not connected> exit
[root@centos7 bin]# redis-server rconfig/redis.conf
[root@centos7 bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
重启后即可生效,会出现aof文件
[root@centos7 bin]# ls
appendonly.aof rconfig redis-check-aof redis-cli redis-server
dump.rdb redis-benchmark redis-check-rdb redis-sentinel
[root@centos7 bin]#
可进入appendonly.aof文件,查看里面都是一些命令记录,此文件也可修改,如果有问题,还有一个redis-check-aof文件可进行修复
删除rdb文件,实验,如果破坏了aof文件,redis是无法启动的,也无法连接,需要修复这个aof文件,用redis的工具redis-check-aof --fix appendonly.aof
[root@centos7 bin]# ls
appendonly.aof rconfig redis-check-aof redis-cli redis-server
dump.rdb redis-benchmark redis-check-rdb redis-sentinel
[root@centos7 bin]# rm -rf dump.rdb
[root@centos7 bin]# ls
appendonly.aof redis-benchmark redis-check-rdb redis-sentinel
rconfig redis-check-aof redis-cli redis-server
[root@centos7 bin]# vim appendonly.aof
[root@centos7 bin]# redis-server rconfig/redis.conf
[root@centos7 bin]# redis-cli -p 6379
Could not connect to Redis at 127.0.0.1:6379: Connection refused
not connected> exit
[root@centos7 bin]# ps -ef |grep redis
root 2471 2085 0 22:34 pts/1 00:00:00 grep --color=auto redis
[root@centos7 bin]#
进行修复
[root@centos7 bin]# redis-check-aof --fix appendonly.aof
0x 8b: Expected prefix '*', got: 's'
AOF analyzed: size=148, ok_up_to=139, ok_up_to_line=34, diff=9
This will shrink the AOF from 148 bytes, with 9 bytes, to 139 bytes
Continue? [y/N]: y
Successfully truncated AOF
[root@centos7 bin]# cat appendonly.aof
*2
$6
SELECT
$1
0
*3
$3
set
$1
k
$1
v
*3
$3
set
再启动就成功了
[root@centos7 bin]# redis-server rconfig/redis.conf
[root@centos7 bin]# ps -ef |grep redis
root 2529 1 0 22:40 ? 00:00:00 redis-server 127.0.0.1:6379
root 2543 2085 0 22:40 pts/1 00:00:00 grep --color=auto redis
# appendfsync always # 每次修改都会同步 消耗性能
appendfsync everysec # 每秒执行一次 sync 同步 ,可能会丢失1s的数据
# appendfsync no # 不执行sync ,这个时候操作系统自己同步数据,速度最快
如果文件大于64m,太大了,触发重写机制,就会fork一个新进程来将文件进行重写!默认的就是对文件的无限追加,文件就会越来越大!
优点:
1、每次修改都同步,文件的完整性更加好
2、每秒同步一次,可能会丢失一秒的数据
3、从不同步,效率最高的
缺点:
1、相对于数据文件来说,aof远大于rdb,修复的速度也比rdb慢
2、aof运行效率也比rdb慢,所以我们redis默认的配置就是rdb持久化
总结:
1.rdb持久化方式能够在指定的时间间隔内对数据进行快照存储
2.aof持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,aof命令以redis协议追加保存每次写的操作到文件末尾,redis还能对aof文件进行后台重写,使aof文件的体积不至于过大。也可以通过命令设置重写规则,至少文件达到多大而且是重写后达到多大后又重写
3.如果只做缓存,可以不使用任何持久化
4.可同时开启两种持久化方式:
① 这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集完善
②RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件,最好不要只使用aof,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的Bug,留作为一个万一的手段。
redis 4.0以后的新特性:RDB和AOF混合的方式:AOF文件重写的时候,先把目前内存中的数据 以快照的方式存到AOF文件中,跟RDB一样二进制类型写入,如果此时有新的命令进来,会以AOF原有的方式将修改命令追加进AOF文件中。这样即保证了数据的安全性,也保证了文件体积不会过于巨大
在Redis重启的时候,可以先加载RDB的内容,然后再重放增量AOF日志就可以完全替代之前的AOF全量文件重放,因此重启效率大幅提升。
5.性能建议:
①因为RDB文件只用作于后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1 这条规则
②如果Enable AOF,好处是在最恶劣的情况下也只会丢失不超过两秒的数据,启动脚本较简单,只load自己的AOF文件就可以了,代价是带来持续的IO,二是AOF rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的,只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的默认大小64M太小了,可以设置到5G以上,默认超过原大小100%大小重写可以改到适当的数值。
③ 如果不Enable AOF,仅靠Master-Slave Repllcation实现高可用性也可以,能省一大笔IO,也减少了rewrite时带来的系统波动。代价是如果Master/Slave 同时宕掉(断电),会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个,微博就采用这种架构
Redis发布订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统!Redis客户端可以订阅任意数量的频道。消息发布者(cli)发布消息,server中用队列存储,谁订阅了这个channel消息,则向哪个或多个cli推送消息。
消息发送者、频道、消息订阅者
命令:
PSUBSCRIBE pattern ... 订阅一个或多个符合给定模式的频道
PUBLISH channel message 将信息发送到指定的频道
PUNSUBSCRIBE pattern... 退订所有给定模式的频道
SUBSCRIBE channel... 订阅给定的一个或多个频道的信息
测试:
订阅一个频道:
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> SUBSCRIBE myEnglish # 订阅一个频道(名字)
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "myEnglish"
3) (integer) 1 # 等待推送过来的信息
启动另一个客户端,向这个频道发布消息
[root@centos7 bin]# redis-cli -p 6379
127.0.0.1:6379> PUBLISH myEnglish "hello,world!" # 发布者发布消息到频道!
(integer) 1
127.0.0.1:6379> PUBLISH myEnglish "hello!"
(integer) 1
127.0.0.1:6379>
订阅者实时收到此消息
127.0.0.1:6379> SUBSCRIBE myEnglish
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "myEnglish"
3) (integer) 1
1) "message" # 收到消息
2) "myEnglish" # 哪个频道
3) "hello,world!" # 内容
1) "message"
2) "myEnglish"
3) "hello!"
原理:
Redis使用C语言实现的,通过分析Redis源码里的 pubsub.c文件,了解发布和订阅机制的底层实现,Redis通过 PUBLISH SUBSCRIBE PSUBSCRIBE等命令实现发布和订阅功能。
通过SUBSCRIBE命令订阅某频道后,redis-server里维护了一个字典,字典的键就是一个个channel,而字典的值是一个链表,链表中保存了所有订阅这个channel的客户端。SUBSCRIBE命令的关键,就是将客户端添加到给定channel的订阅链表中。
通过PUBLISH命令向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如普通的即时聊天,群聊等功能。
使用场景:
1.实时消息系统!2.实时聊天(频道当做聊天室,将信息回显给所有人)!3.订阅关注系统
稍微复杂的场景,就需要使用消息中间件MQ
Redis主从复制
主从复制,是指将一台服务器的数据,复制到其他的Redis服务器,一起对外提供服务。前者称为主节点(master/leader),后者称为从节点(slave/follower);数据复制是单向的,只能由主节点到从节点。默认情况下,每台Redis服务器都是主节点
所以 Master以写为主,Slave以读为主。主从复制,解决读写分离问题,80%的情况下都是进行读操作!最低配 为 一 主 二从!
作用:
1、数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
2、故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余
3、负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是写少读多的场景之下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
4、高可用(集群)基石:除了上述作用外,主从复制还是哨兵和集群能够实施的基础!
一般来说,Redis运用于工程项目中,只使用一台Redis是万万不能的:
1、从结构上来说,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大
2、从容量上来说,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G,对于多典型的多读少写场景,可用一主三从的架构,真实项目中,不可能单机使用Redis
环境配置:只配置从库,不用配置主库
①照常启动服务
[root@centos7 bin]# redis-server rconfig/redis.conf
[root@centos7 bin]# ps -ef|grep redis
root 3151 1 0 14:28 ? 00:00:00 redis-server 127.0.0.1:6379
root 3157 2906 0 14:28 pts/2 00:00:00 grep --color=auto redis
[root@centos7 bin]#
②客户端连接服务端,并查看服务端的信息 info replication
[root@centos7 bin]# redis-cli -p 6379
127.0.0.1:6379> info replication # 查看当前库的信息
# Replication
role:master # 角色是 master
connected_slaves:0 # 没有子节点、没有从机
master_failover_state:no-failover
master_replid:6eba56f26a487bedf4dad25c5d9d65769068072a
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379>
先将服务停止,开始进行配置,配置出一主 二从 的redis
127.0.0.1:6379> shutdown
not connected> exit
③进入配置文件,复制三个配置文件,并逐一进行修改
[root@centos7 bin]# ls
dump.rdb redis-benchmark redis-check-rdb redis-sentinel
rconfig redis-check-aof redis-cli redis-server
[root@centos7 bin]# cd rconfig
[root@centos7 rconfig]# ls
redis.conf
[root@centos7 rconfig]# cp redis.conf redis79.conf
[root@centos7 rconfig]# cp redis.conf redis80.conf
[root@centos7 rconfig]# cp redis.conf redis81.conf
[root@centos7 rconfig]# ls
redis79.conf redis80.conf redis81.conf redis.conf
[root@centos7 rconfig]# vim redis79.conf
[root@centos7 rconfig]# vim redis80.conf
[root@centos7 rconfig]# vim redis81.conf
[root@centos7 rconfig]# tail redis81.conf
因为有多个服务所以 日志文件这里需要命名,rdb文件会重名,也需要修改
redis80.conf文件 需要修改端口为6380
# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6380
# 后台运行的pid文件需要修改为对应的名字 6380
# Note that on modern Linux systems "/run/redis.pid" is more conforming
# and should be used instead.
pidfile /var/run/redis_6380.pid
# 日志文件进行对应命名
# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile "6380.log"
# rdb文件名进行 修改
# The filename where to dump the DB
dbfilename dump6380.rdb
最后wq 保存退出!
对redis81.conf进行类似上一个文件的修改操作(端口 pid名字 log文件名字 dump.rdb名字)
④ 进行单机多集群构建,在一台服务器上配置多个
正常启动6379端口服务
[root@centos7 rconfig]# cd ..
[root@centos7 bin]# redis-server rconfig/redis79.conf
[root@centos7 bin]# ls
6379.log rconfig redis-check-aof redis-cli redis-server
dump.rdb redis-benchmark redis-check-rdb redis-sentinel
# 可看到6379.log日志文件已经创建 服务启动成功
切换到另一个窗口,启动80服务
[root@centos7 bin]# redis-server rconfig/redis80.conf
[root@centos7 bin]# ls
6379.log dump.rdb redis-benchmark redis-check-rdb redis-sentinel
6380.log rconfig redis-check-aof redis-cli redis-server
[root@centos7 bin]#
切到第三个窗口,将81服务启动
[root@centos7 bin]# redis-server rconfig/redis81.conf
[root@centos7 bin]# ls
6379.log dump.rdb redis-check-aof redis-sentinel
6380.log rconfig redis-check-rdb redis-server
6381.log redis-benchmark redis-cli
查看集群环境搭建是否成功,查看进程情况,有三个redis服务
⑤ 一主二从
默认情况下,每台Redis服务器都是主节点,所以需要进行配置!一般就只用配置从机就可以了!
目前三个服务均是主机,改为一主二从,让79为主,(80/81)为从机
命令:
SLAVEOF +ip +port 认主!
# 成为6379 的 从机
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6380> INFO replication # 此时信息变化了
# Replication
role:slave # 角色成为了 从
master_host:127.0.0.1 # 主机地址
master_port:6379 # 主机端口
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:28
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:b2bbf964560c5cb4aa451c6c960c3ba8950e2997
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:28
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:28
127.0.0.1:6380>
6379服务的信息也会变化
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:1 # 多了一台从机
slave0:ip=127.0.0.1,port=6380,state=online,offset=28,lag=1 # 第0号位从机 ,从机地址, 端口信息
master_failover_state:no-failover
master_replid:b2bbf964560c5cb4aa451c6c960c3ba8950e2997
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:28
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:28
127.0.0.1:6379>
另外一台丛机一样的操作,最后配置出两台从机
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6381> INFO replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_repl_offset:336
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:b2bbf964560c5cb4aa451c6c960c3ba8950e2997
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:336
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:323
repl_backlog_histlen:14
127.0.0.1:6381>
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=350,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=350,lag=0
master_failover_state:no-failover
master_replid:b2bbf964560c5cb4aa451c6c960c3ba8950e2997
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:350
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:350
127.0.0.1:6379>
真实主从配置,应该在配置文件中配置,这样的话是永久的,目前用的命令,暂时的
主机可以写,从机负责读!主机中的所有信息和数据,都会自动被从机保存
# 在主机里存入数据
127.0.0.1:6379> set key master
OK
127.0.0.1:6379>
#另外两台从机 可以读取到数据
127.0.0.1:6380> keys *
1) "key"
127.0.0.1:6380> get key
"master"
127.0.0.1:6380> set k v # 从机不能写!
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380>
127.0.0.1:6381> clear
127.0.0.1:6381> keys *
1) "key"
127.0.0.1:6381> get key
"master"
127.0.0.1:6381>
⑥ 目前情况下 主机断开连接,从机依然连接到主机,不会自动进行选举出主机,身份是不会变化的,只是此时没有新数据过来了,如果主机重新启动回来了,从机依然可以直接获取到主机写进去的信息
如果是使用命令行方式来配置的主从关系。断开重启会默认变为主机,从机身份会变化!只要变为从机,立刻就会从主机获取数据!!
示例:将6381断开,在此期间master进行数据写入操作。后续又将6381启动并设置为从机,发现,全量数据都有
127.0.0.1:6381> shutdown
not connected> exit
[root@centos7 bin]# ls
6379.log 6381.log dump6380.rdb dump.rdb redis-benchmark redis-check-rdb redis-sentinel
6380.log dump6379.rdb dump6381.rdb rconfig redis-check-aof redis-cli redis-server
[root@centos7 bin]# redis-server rconfig/redis81.conf
[root@centos7 bin]# redis-cli -p 6381
127.0.0.1:6381> ping
PONG
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:21ae467b71ad5b4493b253b0227c13bf198c1ce4
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6381> SLAVEOF 127.0.0.1 6379
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:2298
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:b2bbf964560c5cb4aa451c6c960c3ba8950e2997
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:2298
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2285
repl_backlog_histlen:14
127.0.0.1:6381> keys *
1) "key"
2) "country"
3) "k"
4) "haha"
127.0.0.1:6381> get k
"v"
127.0.0.1:6381>
127.0.0.1:6379> set key master
OK
127.0.0.1:6379> set k v
OK
127.0.0.1:6379> set haha lala
OK
127.0.0.1:6379> set country America
OK
127.0.0.1:6379>
[root@centos7 bin]# ps -ef |grep redis
root 3457 1 0 14:47 ? 00:00:01 redis-server 127.0.0.1:6379
root 3571 1 0 14:53 ? 00:00:01 redis-server 127.0.0.1:6380
root 3639 2906 0 14:59 pts/2 00:00:00 redis-cli -p 6379
root 3658 2819 0 15:00 pts/0 00:00:00 redis-cli -p 6380
root 4045 3214 0 15:29 pts/3 00:00:00 grep --color=auto redis
[root@centos7 bin]# ps -ef |grep redis
root 3457 1 0 14:47 ? 00:00:01 redis-server 127.0.0.1:6379
root 3571 1 0 14:53 ? 00:00:01 redis-server 127.0.0.1:6380
root 3639 2906 0 14:59 pts/2 00:00:00 redis-cli -p 6379
root 3658 2819 0 15:00 pts/0 00:00:00 redis-cli -p 6380
root 4047 1 0 15:29 ? 00:00:00 redis-server 127.0.0.1:6381
root 4053 3214 0 15:29 pts/3 00:00:00 grep --color=auto redis
[root@centos7 bin]#
复制原理:
Slave启动成功连接到master 后会发送一个sync同步命令
Master接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集的命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步。
全量复制:而slave服务在接受到数据库文件数据后,将其存盘并加载到内存中
增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步,但是只要是重新连接master,一次完全同步(全量复制)将被自动执行!我们的数据一定可以在从机中看到!
主从复制的另外一种模型:
将79设为M,80设为79的S,81设为80的S,此时像一个队列,80此时依然是一个从节点,但是如果79断掉,80就可以充当master 依然可以完成主从复制,但工作中都不会使用,此处作为了解
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_repl_offset:3082
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=3082,lag=0
master_failover_state:no-failover
master_replid:b2bbf964560c5cb4aa451c6c960c3ba8950e2997
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3082
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3082
如果没有Master,这个时候需要手动去设置Master,
命令:SLAVEOF no one
如果主机断开连接,可以使用命令,让自己变成主机!其他的节点就可以手动连接最新的主节点,即使旧的Master重新连接修复了,不会自动让位,需要全部手动配置
127.0.0.1:6380> SLAVEOF no one
OK
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=3498,lag=0
master_failover_state:no-failover
master_replid:f67123efe87e039d25e23f67d95fda67ec7138f5
master_replid2:b2bbf964560c5cb4aa451c6c960c3ba8950e2997
master_repl_offset:3498
second_repl_offset:3485
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3498
127.0.0.1:6380>
哨兵模式(自动选举老大的模式!)
上面的主从切换技术方法,需要手动的去把一台服务器切换为主服务器。效率不高还会造成一段时间内服务不可用。这不是一种推荐方式,一般都会用哨兵模式。Redis2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。原理是:哨兵通过发送命令,等待Redis服务器响应,从而监控运行多个Redis实例。
哨兵有两个作用:
1.通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
2.当哨兵检测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而一个哨兵进程对Redis服务器进行监控,可能会出问题,为此我们可用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式!
所以如果要配置哨兵,岂不就是6个进程,3个哨兵,1主2从。
假设主服务器宕机,哨兵1检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为服务器不可用,这个现象为主观下线,当后面的哨兵也检测到主服务不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover(故障转移)操作。切换成功后,通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。
自行测试:
一主二从
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=70,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=70,lag=1
master_failover_state:no-failover
master_replid:c5e8dd3c0bc700eca942c3be364875a439dbde6e
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:70
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:70
①配置哨兵配置文件,sentinel.conf (如果没有直接新建,名字不能错)
# 监控 监控名称 监控的ip 端口
sentinel monitor myredis 127.0.0.1 6379 1
~
后面的数字1,代表主机挂了,slave 投票看让谁接替成为主机,票数最多的,就会成为主机
此配置只是最简单的一点配置 不代表所有
② 启动哨兵
通过 redis-sentinel 通过文件启动
将主机宕机
sentinel 会进行failover(故障转移),重新确定一个master
如果6379又回来了
哨兵让其当了从机
主机依然是81
如果前任主机回来了,只能归并到新的主机下,当做从机
哨兵模式的优点:
1.哨兵集群,基于主从复制模式,所有的主从配置优点,全有
2.主从可以切换,故障可以转移,系统的可用性更好
3.哨兵模式就是主从模式的升级,手动→自动,更加健壮
缺点:
1.Redis不好在线扩容,集群容量一旦达到上限,在线扩容就十分麻烦
2.实现哨兵模式的配置很麻烦的,里面有很多选择
# 哨兵sentinel实例运行的端口 默认26379 如果有哨兵集群,还需要配置每个哨兵的端口
port 26379
# 哨兵sentinel的工作目录
dir /tmp
# quorum 配置多少个sentinel哨兵统一认为master主节点失连,那么这时客观上认为主节点失联
# sentinel monitor
sentinel monitor master 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis 实例客户端都需要密码
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
#sentinel auth-pass
sentinel auth-pass master MySUPER--secret-123password
# 指定多少毫秒之后,节点没有应答哨兵sentinel 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-milliseconds
sentinel down-after-milliseconds master 30000
# 在发生failover主备切换时,最多可以有多少个slave同时对新的master进行同步,这个数字越小,完成
# failover的时间越长,如果数字越大,意味着越多的slave因为replication而不可用,可以通过将这个值设
# 为1,保证每次只有一个slave处于不能处理命令请求的状态
sentinel parallel-syncs master 1
# 故障转移的超时时间failover-timeout
# 当进行failover时配置所有slaves指向新的master所需最大时间,不过即使过了这个超时,slaves依然会被
# 正确设置为指向master,但是就不按parallel-syncs所配置的规则来了
# 默认3分钟
sentinel failover-timeout master 180000
# 配置当某一时间发生时所需执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知 # 相关人员
# 通知脚本 shell编程 一般是运维配置
sentinel notification-script master /var/redis/notify.sh
Redis缓存穿透、击穿、雪崩 (服务的高可用问题)
缓存穿透:(查不到)用户想要查询一个数据,发现redis内存数据库没有,也就是没有命中缓存,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(秒杀场景),于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力。这时候相当于出现了缓存穿透。
解决方案:
1.Redis之前加一层过滤器
布隆过滤器:是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
2.缓存空对象
即使查询持久层数据库没有数据,也返回空内容,此时,redis就存了空内容。下次类似请求,就能命中缓存。当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据会从缓存中获取,保护了后端数据源。
会存在两个问题:
1、如果空值能够被缓存起来,就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键
2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口不一致,这对于需要保持一致性的业务会有影响。例:存储层有了,但是缓存层依然是没有的。
缓存击穿:(量太大,缓存过期,全砸数据库上)
缓存击穿,指一个key非常热点,在不停的扛着大并发,集中的在访问一个点(一个key),大并发集中对着一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。(微博热点,如果没抗住就会宕机)
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据瞬间压力过大。
1.设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
2.加互斥锁
分布式锁:(setnx)使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
缓存雪崩:(在某一个时间段缓存集中过期失效,Redis宕机)
产生雪崩的原因之一,比如双十一,一波商品抢购,这波商品时间比较集中的放入了缓存,假设缓存了一个小时,一个小时后,这批商品的缓存就过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
其中缓存数据集中过期不是非常致命,比较致命的是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,一般数据库也是可以抗住压力的。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,可能瞬间就把数据库压垮!
例子:双十一,会停掉一些服务,保证主要的服务可用,比如退款停掉
解决方案:
1.redis高可用:多增设几台redis,这样即使一台挂掉之后其他的还可以继续工作,就是搭建集群(异地多活)
2.限流降级:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
3.数据预热:在正式部署之前,先把可能的数据预先访问一遍,这样部分可能大量访问的数据就会加载到缓存之中,在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。