我们现在处于什么时代?大数据时代!
大数据时代一般的数据库进行分析处理了!2006年 Hadoop出现
1、单机MySQL时代
90年代,一个基本的网站访问量一般不会太大,单个数据库完全足够!
那个时候,更多的去使用静态网页HTML,服务器根本没有太大的压力!
思考一下,这种情况下:整个网站的瓶颈是什么?
数据量如果太大,一台机器放不下了!
数据的索引 (B+tree),一个机器内存也放不下
访问量(读写混合),一个服务器承受不了
只要你开始出现以上的三种情况之一,那么你就必须要晋级!
2、Memcached(缓存)+Mysql+垂直拆分(读写分离)
网站80%的情况都在读,每次都要去查询数据库的话就十分麻烦!所以说我们希望减轻数据库的压力,我们可以使用缓存来保证效率!
发展过程:优化数据结构和索引——》文件缓存(IO)——》Memcached(当时最热门的技术!)
3、分库分表+水平拆分+Mysql集群
技术和业务在发展的同时,对人的要求也越来越高!
本质:数据库(读写)
早些年MYISAM:表锁(100万 去查张三的密码会将整个用户表锁起来,剩下的进程由于表被锁了,只能等待释放锁才能访问),十分影响效率!在高并发下就会出现严重的锁问题!
转战INNODB:行锁
慢慢的就开始使用分库分表来解决写的压力!Mysql在那个年代推出了表分区,但这个并没有多少公司使用!
Mysql的集群,就很好的满足了那个年代的需求!
4、如今的年代
Mysql等关系型数据库就不够用了!数据量很多,变化很快!
Mysql有的人使用它来存储一些比较大的文件,博客,图片!数据库表很大,效率就低了!如果有一种数据库来专门处理这种数据,mysql的压力就会变的十分小!(研究如何处理这些问题)大数据的IO压力下,表几乎没法更改 1亿 加一列
灰度发布
目前一个基本的互联网项目
为什么要用Nosql?
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
这个时候我们就需要使用Nosql数据库,Nosql就可以很好的处理以上情况!
Nosql
NoSQL = Not Only SQL(不仅仅是SQL)
关系型数据库:表格:行、列
泛指非关系型数据库,随着web2.0互联网的诞生!传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题,Mysql解决不了,于是Nosql出来了,NoSQL在当今大数据环境下发展十分迅速!Redis是发展最快的,是我们当下必须要掌握的技术!
很多的数据类型:用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式!不需要多余的操作就可以横向扩展的!Map
NoSQL特点
解耦!
方便扩展(数据之间没有关系,很好扩展!)
大数据量,高性能(Redis 一秒可以写8万次,读取11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高!)
数据类型是多样型的(不需要事先设计数据库!随取随用!如果数据库量十分大的表,很多人就无法设计了!)
传统的RDBMS和NoSQL
传统的RDBMS - 结构化组织 - SQL - 数据和关系存在单独的表中 row column - 操作数据,数据库定义语言 - 严格的一致性 - 基础的事务 - ... NoSQL - 不仅仅是数据库 - 没有固定的查询语言 - 键值对存储,列存储,文档存储,图形数据库(社交关系) - 最终一致性 - CAP定理和BASE (异地多活) 初级架构师! - 高性能、高可用、高可扩 - ...
了解:3V+3高
大数据时代的3V:主要是描述问题的
海量(Volume) 多样(Variety) 实时(Velocity)
大数据时代的3高:主要是对程序的要求 高并发 高可扩(随时可水平拆分,机器不够了,可以扩展机器来解决) 高性能(保证用户体验和性能)
真正的公司中的实践:NoSQL + REDMS一起使用才是最强的,阿里巴巴的架构演进!
思考问题:淘宝网站上这么多的东西难道都是在一个数据库中的吗?
1、商品的基本信息 名称、价格、商家信息 关系型数据库就可以解决——》MySQL(组件化、热插拔)/Oracle(淘宝早些年就去IOE了!-王坚 推荐文章:阿里云的这群疯子) 淘宝内部的MySQL 不是大家用的MySQL 2、商品的描述、评论(文字比较多) 文档型数据库,Redis/MongoDB 3、图片 分布式文件系统 FastDFS - 淘宝自己的 TFS - Goole的 GFS - Hadoop的 HDFS - 阿里云的 OSS 4、商品的关键字(搜索) - 搜索引擎 solr elasticsearch - 淘宝 ISerach 多隆 5、商品的热门的波段信息 - 内存数据库 - Redis Tair Memecache 6、商品的交易,外部的支付接口 - 三方应用
要知道,一个简单的网页背后的技术一定不是大家所想的那么简单!
大型互联网应用问题:
数据类型太多了
数据源繁多,经常重构
数据要改造,大面积改造?
解决问题:
USDL(统一数据服务平台)
这里以上都是NoSQL入门概述,不仅能够提高大家的知识,还可以帮助大家了解大厂的工作内容!
KV键值对:
新浪:Redis
美团:Redis + Tair
阿里、百度:Redis + Memecache
文档型数据库(bson格式和json一样):
BSON(/ˈbiːsən/)是一种计算机数据交换格式,主要被用作MongoDB数据库中的数据存储和网络传输格式。它是一种二进制表示形式,能用来表示简单数据结构、关联数组(MongoDB中称为“对象”或“文档”)以及MongoDB中的各种数据类型。BSON之名缘于JSON,含义为Binary JSON(二进制JSON)。
MongoDB(一般必须要掌握)
MongoDB是一个基于分布式文件存储的数据库,C++编写的,主要用来处理大量的文档!
MongoDB是一个介于关系型数据库和非关系型数据库中间的产品!MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的。
CounthDB
列存储数据库:
HBase
分布式文件系统
图形关系数据库:
他不是存图形的,放的是一些关系,比如:朋友圈社交网络,广告推荐!
Neo4j,InfoGrid
四者对比:
官网教程:Redis 教程_redis教程
Redis是什么?
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
免费和开源!是当下最热门的NoSQL技术之一!也被称为结构化数据库
Redis能干嘛?
内存存储、持久化,内存是断电即失的,所以持久化很重要(RDB、AOF)
效率高,可以用于高速缓存
发布、订阅系统
地图信息分析
计时器、计数器(浏览量!incr decre)
......
特性
多样的数据类型
持久化
集群
事务
......
学习需要用到的东西
Redis官网:Redis
Redis中文网:Redis中文网
Redis是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
在官网上下载即可!
注意:Windows在GitHub上下载(停更很久了!)
Redis推荐都是在Linux服务器上搭建的,我们是基于Linux学习!
Windows安装
下载安装包:https://github.com/dmajkic/redis/tags
解压到自己电脑上的环境目录即可
开启Redis,双击运行 redis-server 即可(Redis默认端口是6379)
使用Redis客户端 redis-cli 连接Redis(ping之后返回:PONG即表示连接成功)
记住一句话,Windows下使用确实简单,但是Redis推荐使用Linux去开发使用
下载安装包 redis-7.0.7.tar.gz:Download | Redis
然后通过xftp上传到home目录下
[root@xiaoshu ~]# cd / [root@xiaoshu /]# ls bin boot dev etc home lib lib64 lost+found media mnt opt patch proc root run sbin srv sys tmp usr var www [root@xiaoshu /]# cd home [root@xiaoshu home]# ls springboot www xiaoshu [root@xiaoshu home]# cd xiaoshu/ [root@xiaoshu xiaoshu]# ls redis-7.0.7.tar.gz
解压redis安装包!程序一般放到 /opt 目录下
[root@xiaoshu xiaoshu]# mv redis-7.0.7.tar.gz /opt [root@xiaoshu xiaoshu]# cd / [root@xiaoshu /]# ls bin boot dev etc home lib lib64 lost+found media mnt opt patch proc root run sbin srv sys tmp usr var www [root@xiaoshu /]# cd opt [root@xiaoshu opt]# ls containerd jdk-19.0.1 jdk-19_linux-x64_bin.tar.gz redis-7.0.7.tar.gz threatbook [root@xiaoshu opt]# tar -zxvf redis-7.0.7.tar.gz ... [root@xiaoshu opt]# ls containerd jdk-19.0.1 jdk-19_linux-x64_bin.tar.gz redis-7.0.7 redis-7.0.7.tar.gz threatbook [root@xiaoshu opt]#
进入解压后的文件,可以看到redis的配置文件
[root@xiaoshu opt]# ls containerd jdk-19.0.1 jdk-19_linux-x64_bin.tar.gz redis-7.0.7 redis-7.0.7.tar.gz threatbook [root@xiaoshu opt]# cd redis-7.0.7/ [root@xiaoshu redis-7.0.7]# ls 00-RELEASENOTES CODE_OF_CONDUCT.md COPYING INSTALL MANIFESTO redis.conf runtest-cluster runtest-sentinel sentinel.conf tests utils BUGS CONTRIBUTING.md deps Makefile README.md runtest runtest-moduleapi SECURITY.md src TLS.md [root@xiaoshu redis-7.0.7]#
基本的环境安装
[root@xiaoshu redis-7.0.7]# yum install gcc-c++ #安装gcc-c++ Last metadata expiration check: 0:03:09 ago on Tue 03 Jan 2023 11:44:58 AM CST. Package gcc-c++-10.2.1-3.3.al8.x86_64 is already installed. #提示已经安装过了 Dependencies resolved. Nothing to do. Complete! [root@xiaoshu redis-7.0.7]# gcc -v #查看gcc版本 Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/10/lto-wrapper Target: x86_64-redhat-linux Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --disable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --with-isl --enable-gnu-indirect-function --enable-cet --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 10.2.1 20200825 (Alibaba 10.2.1-3 2.32) (GCC) #显示没问题 [root@xiaoshu redis-7.0.7]# make #将所有需要的文件都配上,是在线下载需要一点时间 ... Hint: It's a good idea to run 'make test' ;) make[1]: Leaving directory '/opt/redis-7.0.7/src' [root@xiaoshu redis-7.0.7]# make #再 make 一次就比较快了 cd src && make all make[1]: Entering directory '/opt/redis-7.0.7/src' CC Makefile.dep Hint: It's a good idea to run 'make test' ;) make[1]: Leaving directory '/opt/redis-7.0.7/src' [root@xiaoshu redis-7.0.7]# make install #执行 make install 之后所有东西就安装好了 cd src && make install make[1]: Entering directory '/opt/redis-7.0.7/src' Hint: It's a good idea to run 'make test' ;) INSTALL redis-server INSTALL redis-benchmark INSTALL redis-cli make[1]: Leaving directory '/opt/redis-7.0.7/src' [root@xiaoshu redis-7.0.7]#
redis 的默认安装目录 /usr/local/bin
[root@xiaoshu bin]# cd /usr [root@xiaoshu usr]# ls bin games include java lib lib64 libexec local sbin share src tmp [root@xiaoshu usr]# cd local [root@xiaoshu local]# ls aegis bin btjdk bttomcat cloudmonitor curl etc games include lib lib64 libexec libiconv libsodium man nghttp2 openssl rpcgen sbin share src zend [root@xiaoshu local]# cd bin [root@xiaoshu bin]# ls libmcrypt-config mcrypt mdecrypt redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server [root@xiaoshu bin]#
出现上面的结果说明安装就完成了!
将redis配置文件复制到/usr/local/bin
目录下
[root@xiaoshu bin]# ls libmcrypt-config mcrypt mdecrypt redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server [root@xiaoshu bin]# cp /opt/redis-7.0.7/r redis.conf runtest runtest-cluster runtest-moduleapi runtest-sentinel [root@xiaoshu bin]# mkdir config [root@xiaoshu bin]# ls config libmcrypt-config mcrypt mdecrypt redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server [root@xiaoshu bin]# cp /opt/redis-7.0.7/redis.conf config/ [root@xiaoshu bin]# cd config/ [root@xiaoshu config]# ls redis.conf #以后就用这个配置文件,原来的在我们的解压目录下(不去动它) [root@xiaoshu config]#
redis默认不是后台启动的,修改配置文件
[root@xiaoshu config]# vim redis.conf ... # 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 #默认后台启动为 no ;按i改为 yes ,按Esc,:wq 即可 ... :wq [root@xiaoshu config]#
启动redis服务,以及使用redis-cli进行连接测试
[root@xiaoshu bin]# ls config libmcrypt-config mcrypt mdecrypt redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server [root@xiaoshu bin]# redis-server config/redis.conf #启动redis服务,格式:运行服务名称 通过哪个配置文件来运行 [root@xiaoshu bin]# ls config libmcrypt-config mcrypt mdecrypt redis-benchmark redis-check-aof redis-check-rdb redis-cli redis-sentinel redis-server [root@xiaoshu bin]# redis-cli -p 6379 #使用redis客户端进行连接,格式:运行服务名称 -p 端口号 127.0.0.1:6379> ping PONG #连接成功 127.0.0.1:6379> set name xiaoshu OK 127.0.0.1:6379> get name "xiaoshu" 127.0.0.1:6379> keys * #查看所有的key 1) "name" 127.0.0.1:6379>
查看redis的进程是否开启
重新开启一个会话
[root@xiaoshu ~]# ps -ef|grep redis root 446662 1 0 13:40 ? 00:00:00 redis-server 127.0.0.1:6379 root 446731 444222 0 13:41 pts/0 00:00:00 redis-cli -p 6379 root 447033 446965 0 13:50 pts/1 00:00:00 grep --color=auto redis [root@xiaoshu ~]#
如何关闭redis服务?shutdown
[root@xiaoshu bin]# redis-cli -p 6379 127.0.0.1:6379> ping PONG 127.0.0.1:6379> set name xiaoshu OK 127.0.0.1:6379> get name "xiaoshu" 127.0.0.1:6379> key * (error) ERR unknown command 'key', with args beginning with: '*' 127.0.0.1:6379> keys * 1) "name" 127.0.0.1:6379> shutdown #关闭redis not connected> exit #退出 [root@xiaoshu bin]#
再次查看进程是否存在
后面会使用单机多redis启动集群测试!
redis-benchmark 是一个压力测试工具!
官方自带的性能测试工具!
Redis 性能测试是通过同时执行多个命令实现的。
语法
redis 性能测试的基本命令如下:
redis-benchmark [option] [option value]
注意:该命令是在 redis 的目录下执行的,而不是 redis 客户端的内部指令。
redis 性能测试工具可选参数如下所示:
序号 | 选项 | 描述 | 默认值 |
---|---|---|---|
1 | -h | 指定服务器主机名 | 127.0.0.1 |
2 | -p | 指定服务器端口 | 6379 |
3 | -s | 指定服务器 socket | |
4 | -c | 指定并发连接数 | 50 |
5 | -n | 指定请求数 | 10000 |
6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
9 | -P | 通过管道传输 |
1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
11 | --csv | 以 CSV 格式输出 | |
12 | *-l*(L 的小写字母) | 生成循环,永久执行测试 | |
13 | -t | 仅运行以逗号分隔的测试命令列表。 | |
14 | *-I*(i 的大写字母) | Idle 模式。仅打开 N 个 idle 连接并等待。 |
我们来简单测试一下:
#测试:100个并发连接 每一个并发 10 0000个请求 [root@xiaoshu bin]# redis-benchmark -h localhost -p 6379 -c 100 -n 100000 ====== PING_INLINE ====== 100000 requests completed in 1.23 seconds #对我们的10万个请求进行写入测试,用了 1.23s 100 parallel clients #100个并发客户端 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.191 milliseconds (cumulative count 1) 50.000% <= 0.727 milliseconds (cumulative count 50033) 75.000% <= 0.935 milliseconds (cumulative count 75336) 87.500% <= 1.151 milliseconds (cumulative count 87786) 93.750% <= 1.343 milliseconds (cumulative count 93795) 96.875% <= 1.471 milliseconds (cumulative count 96913) 98.438% <= 1.567 milliseconds (cumulative count 98476) 99.219% <= 1.679 milliseconds (cumulative count 99231) 99.609% <= 1.887 milliseconds (cumulative count 99613) 99.805% <= 2.279 milliseconds (cumulative count 99805) 99.902% <= 11.495 milliseconds (cumulative count 99903) 99.951% <= 43.903 milliseconds (cumulative count 99953) 99.976% <= 44.159 milliseconds (cumulative count 99978) 99.988% <= 44.351 milliseconds (cumulative count 99989) 99.994% <= 44.415 milliseconds (cumulative count 99994) 99.997% <= 44.447 milliseconds (cumulative count 99997) 99.998% <= 44.479 milliseconds (cumulative count 100000) 100.000% <= 44.479 milliseconds (cumulative count 100000) Cumulative distribution of latencies: 0.000% <= 0.103 milliseconds (cumulative count 0) 0.002% <= 0.207 milliseconds (cumulative count 2) 0.019% <= 0.303 milliseconds (cumulative count 19) 0.347% <= 0.407 milliseconds (cumulative count 347) 8.458% <= 0.503 milliseconds (cumulative count 8458) 30.487% <= 0.607 milliseconds (cumulative count 30487) 46.705% <= 0.703 milliseconds (cumulative count 46705) 60.936% <= 0.807 milliseconds (cumulative count 60936) 72.060% <= 0.903 milliseconds (cumulative count 72060) 80.955% <= 1.007 milliseconds (cumulative count 80955) 85.840% <= 1.103 milliseconds (cumulative count 85840) 89.756% <= 1.207 milliseconds (cumulative count 89756) 92.645% <= 1.303 milliseconds (cumulative count 92645) 95.441% <= 1.407 milliseconds (cumulative count 95441) 97.508% <= 1.503 milliseconds (cumulative count 97508) 98.837% <= 1.607 milliseconds (cumulative count 98837) 99.306% <= 1.703 milliseconds (cumulative count 99306) 99.518% <= 1.807 milliseconds (cumulative count 99518) 99.630% <= 1.903 milliseconds (cumulative count 99630) 99.709% <= 2.007 milliseconds (cumulative count 99709) 99.746% <= 2.103 milliseconds (cumulative count 99746) 99.839% <= 3.103 milliseconds (cumulative count 99839) 99.875% <= 11.103 milliseconds (cumulative count 99875) 99.932% <= 12.103 milliseconds (cumulative count 99932) 99.975% <= 44.127 milliseconds (cumulative count 99975) 100.000% <= 45.119 milliseconds (cumulative count 100000) Summary: throughput summary: 81499.59 requests per second #每秒处理的请求数 latency summary (msec): avg min p50 p95 p99 max 0.836 0.184 0.727 1.391 1.631 44.479 ...
Redis是单线程的(重要)
明白redis是很快的,官方表示,Redis是基于内存操作的,CPU不是redis的性能瓶颈,redis的瓶颈是根据机器的内存和网络带宽决定,既然可以使用单线程来实现,就使用单线程了!所以就使用单线程了!
redis是C语言写的,官方提供的数据为 10 0000+ 的QPS,说明这个完全不比同样是使用 key-value的 Memecache 差!
redis为什么单线程还这么快?
高性能的服务器不一定是多线程的
多线程(CPU上下文切换:会消耗一定的资源——>耗时操作)不一定比单线程效率高!
核心:redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高;多线程(CPU上下文切换:会消耗一定的资源——>耗时操作);对于内存系统来说,如果没有上下文切换,效率就是最高的!多次读写都是在一个CPU上的,在内存情况下。这个就是最佳的方案!
Redis数据库
redis默认有16个数据库,查看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 SELECTwhere # dbid is a number between 0 and 'databases'-1 databases 16
默认使用的是第0个;可以使用select进行切换数据库!
127.0.0.1:6379> select 3 #切换数据库 OK 127.0.0.1:6379[3]> dbsize #查看数据库大小 (integer) 0 127.0.0.1:6379[3]> set name xiaoyu OK 127.0.0.1:6379[3]> dbsize (integer) 1 127.0.0.1:6379[3]> get name "xiaoyu" 127.0.0.1:6379[3]> flushdb #清空当前数据库 OK 127.0.0.1:6379[3]> dbsize (integer) 0 127.0.0.1:6379[3]> select 0 OK 127.0.0.1:6379> dbsize (integer) 5 127.0.0.1:6379> keys * #查看数据库所有的key 1) "mylist" 2) "name" 3) "key:__rand_int__" 4) "counter:__rand_int__" 5) "myhash" 127.0.0.1:6379> get name "xiaoshu" 127.0.0.1:6379> select 3 #切换数据库 OK 127.0.0.1:6379[3]> dbsize (integer) 0 127.0.0.1:6379[3]> flushall #清空全部数据库内容 OK 127.0.0.1:6379[3]> select 0 OK 127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379>
清空当前数据库内容:flushdb
清空全部数据库内容:flushall
思考:为什么redis是6379?粉丝效应(了解一下即可)
官网文档
REmote DIctionary Server(Redis) 是一个开源(BSD许可),内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。它支持字符串、哈希表、列表、集合、有序集合,位图,hyperloglogs等数据类型。内置复制、Lua脚本、LRU收回、事务以及不同级别磁盘持久化功能,同时通过Redis Sentinel提供高可用,通过Redis Cluster提供自动分区。
它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(set) 和 有序集合Zset(Sorted Set)等类型。
String
List
Set
Hash
Zset(Sorted Set)
127.0.0.1:6379[3]> flushall OK 127.0.0.1:6379[3]> select 0 OK 127.0.0.1:6379> dbsize (integer) 0 127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> set name xiaoshu OK 127.0.0.1:6379> set age 22 OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> exists name #判断key是否存在 (integer) 1 #返回 1 表示存在 127.0.0.1:6379> exists name1 (integer) 0 #返回 0 表示不存在 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 xiaoshu OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> get name "xiaoshu" 127.0.0.1:6379> expire name 10 #设置key的过期时间为 10s (integer) 1 127.0.0.1:6379> ttl name (integer) 0 127.0.0.1:6379> ttl name (integer) -2 #返回-2表示就过期了 127.0.0.1:6379> ttl name (integer) -2 127.0.0.1:6379> get name #重新查看key (nil) #返回空,nil就表示空 127.0.0.1:6379>
单点登录:就可以设置一些过期时间
127.0.0.1:6379> keys * 1) "age" 127.0.0.1:6379> set name xiaoshu OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> type name #查看当前key的类型 string 127.0.0.1:6379> type age string 127.0.0.1:6379>
后面如果遇到不会的命令,查看官方文档!
字符串基本操作
127.0.0.1:6379> keys * 1) "age" 127.0.0.1:6379> set name xiaoshu OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> type name string 127.0.0.1:6379> type age string 127.0.0.1:6379> append name ",hello" #在原来string类型值的基础上,追加一个 ",hello";如果key不存在,就相当于set key (integer) 13 #返回值的长度 127.0.0.1:6379> get name "xiaoshu,hello" 127.0.0.1:6379> strlen name #获取key的长度 (integer) 13 127.0.0.1:6379>
自增:i++
步长:i+=10
127.0.0.1:6379> set views 0 #初始浏览量为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> decr views #自减1 (integer) 0 127.0.0.1:6379> get views "0" 127.0.0.1:6379> incrby views 10 #可以设置步长,指定增量 (integer) 10 127.0.0.1:6379> get views "10" 127.0.0.1:6379> decrby views 5 #可以设置步长,指定减量 (integer) 5 127.0.0.1:6379> get views "5" 127.0.0.1:6379>
字符串范围 range
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> set name xiaoshu OK 127.0.0.1:6379> getrange name 4 6 #截取下标从[4,6]的字符串 "shu" 127.0.0.1:6379> getrange name 0 -1 #获取全部的字符串 和 get key 是一样的 "xiaoshu" 127.0.0.1:6379> setrange name 4 hai #替换指定位置开始的字符串 (integer) 7 127.0.0.1:6379> get name "xiaohai" 127.0.0.1:6379> setrange name 4 yu (integer) 7 127.0.0.1:6379> get name "xiaoyui" 127.0.0.1:6379>
setex:set with expire 设置过期时间
setnx:set if not exist 如果不存在设置(在分布式锁中会常常使用)
127.0.0.1:6379> keys * 1) "name" 127.0.0.1:6379> get name "xiaoshu" 127.0.0.1:6379> setex name 30 "xiaoshu" #设置key为name的值"xiaoshu",30s后过期 OK 127.0.0.1:6379> ttl name (integer) 22 127.0.0.1:6379> get name "xiaoshu" 127.0.0.1:6379> setnx mykey "redis" #如果mykey不存在,创建mykey (integer) 1 #成功返回 1 127.0.0.1:6379> keys * 1) "mykey" 127.0.0.1:6379> ttl name (integer) -2 127.0.0.1:6379> setnx mykey "MongoDB" #如果mykey存在,创建mykey就会失败 (integer) 0 #失败返回 0 127.0.0.1:6379> get mykey "redis" 127.0.0.1:6379>
mset:批量设置值
mget:批量获取值
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> mset name xiaoshu age 22 OK 127.0.0.1:6379> keys * 1) "age" 2) "name" 127.0.0.1:6379> mget name age 1) "xiaoshu" 2) "22" 127.0.0.1:6379> msetnx age 22 name xiaoshu #存在设置不成功 (integer) 0 127.0.0.1:6379> msetnx age 22 name1 xiaoyu #存在一个就会不成功;原子性:要么一起成功,要么一起失败! (integer) 0 127.0.0.1:6379> get name1 (nil) 127.0.0.1:6379>
对象
#这里的key是一个巧妙的设计:user:{id}:{filed},如此设计在redis中是完全可以的! 127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2 OK 127.0.0.1:6379> mget user:1:name user:1:age 1) "zhangsan" 2) "2" 127.0.0.1:6379> keys * 1) "age" 2) "name" 3) "user:1:age" 4) "user:1:name" 127.0.0.1:6379> set user:2 {name:xiaoyu,age:3} #设置一个user:2对象 值为 json 字符串来保存一个对象! OK 127.0.0.1:6379> keys * 1) "age" 2) "user:2" 3) "name" 4) "user:1:name" 5) "user:1:age" 127.0.0.1:6379> get user:2:name (nil) 127.0.0.1:6379> get user:2 "{name:xiaoyu,age:3}" 127.0.0.1:6379>
getset 先get再set
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> getset db redis #如果不存在值,返回nil;并且会创建key为db,值为redis的键值对 (nil) 127.0.0.1:6379> keys * 1) "db" 127.0.0.1:6379> getset db redis #如果存在值,返回当前值 "redis" 127.0.0.1:6379> getset db mongodb #如果存在值,获取原来的值,并设置新的值(可以用来做一些更新的操作) "redis" 127.0.0.1:6379> get db "mongodb" 127.0.0.1:6379>
数据结构是相通的!
String类似的使用场景:value除了是字符串还可以是数字!
计数器
统计多单位的数量
粉丝数
对象缓存存储
基本的数据类型,列表
在redis里面,我们可以把list玩成,栈、队列、阻塞队列!redis不区分大小写命令
lpush:将一个值或者多个值,插入到列表的头部(lpush:leftpush,从左边放入)
rpush:将一个值或者多个值,插入到列表的尾部(rpush:rightpush,从右边放入)
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> lpush list one #将一个值或者多个值,插入到列表的头部(lpush:leftpush,从左边放入) (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> rpush list four #将一个值或者多个值,插入到列表的尾部(rpush:rightpush,从右边放入) (integer) 4 127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "four" 127.0.0.1:6379>
lpop:移除列表的第一个元素
rpop:移除列表的最后一个元素
127.0.0.1:6379> lrange list 0 -1 1) "three" 2) "two" 3) "one" 4) "four" 127.0.0.1:6379> lpop list "three" 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "one" 3) "four" 127.0.0.1:6379> rpop list "four" 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "one" 127.0.0.1:6379>
Lindex:获取List的中的元素,通过index获取(从0开始的)
127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "one" 127.0.0.1:6379> lindex list 1 #获取list的中的元素,通过index获取(从0开始的) "one" 127.0.0.1:6379> lindex list 0 "two" 127.0.0.1:6379>
Llen 返回List的长度
127.0.0.1:6379> keys * 1) "list" 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "one" 127.0.0.1:6379> Llen list (integer) 2 127.0.0.1:6379>
Lrem:移除指定个数的值!取关 uid
格式:Lrem key count element 分析:count 需要移除的数量 element 需要移除指定元素的值
127.0.0.1:6379> keys * 1) "list" 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "one" 127.0.0.1:6379> Llen list (integer) 2 127.0.0.1:6379> lpush list two (integer) 3 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "two" 3) "one" 127.0.0.1:6379> Lrem list 1 one (integer) 1 127.0.0.1:6379> lrange list 0 -1 1) "two" 2) "two" 127.0.0.1:6379> Lrem list 2 two (integer) 2 127.0.0.1:6379> lrange list 0 -1 (empty array) 127.0.0.1:6379>
trim 修剪;list 截断
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> rpush list xiaoshu1 (integer) 1 127.0.0.1:6379> rpush list xiaoshu2 (integer) 2 127.0.0.1:6379> rpush list xiaoshu3 (integer) 3 127.0.0.1:6379> rpush list xiaoshu4 (integer) 4 127.0.0.1:6379> lrange list 0 -1 1) "xiaoshu1" 2) "xiaoshu2" 3) "xiaoshu3" 4) "xiaoshu4" 127.0.0.1:6379> Ltrim list 1 2 #通过下标截取指定的长度,这个list已经被改变了,只剩下被截取的元素! OK 127.0.0.1:6379> lrange list 0 -1 1) "xiaoshu2" 2) "xiaoshu3" 127.0.0.1:6379>
rpoplpush 移除列表的最后一个元素,并且将它放到另外一个列表中
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> rpush list xiaoshu1 (integer) 1 127.0.0.1:6379> rpush list xiaoshu2 (integer) 2 127.0.0.1:6379> rpush list xiaoshu3 (integer) 3 127.0.0.1:6379> rpoplpush list mylist # "xiaoshu3" 127.0.0.1:6379> keys * 1) "mylist" 2) "list" 127.0.0.1:6379> lrange mylist 0 -1 1) "xiaoshu3" 127.0.0.1:6379> lrange list 0 -1 1) "xiaoshu1" 2) "xiaoshu2" 127.0.0.1:6379>
lset key index element 类似于一个更新的操作
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 xiaoshu #当前列表不存在,就会设置值不成功 (error) ERR no such key 127.0.0.1:6379> lpush list xiaoyu (integer) 1 127.0.0.1:6379> lpush list xiaoshu (integer) 2 127.0.0.1:6379> lrange list 0 -1 1) "xiaoshu" 2) "xiaoyu" 127.0.0.1:6379> lrange list 0 0 1) "xiaoshu" 127.0.0.1:6379> lrange list 1 1 1) "xiaoyu" 127.0.0.1:6379> lset list 0 xiaoyu #将list列表中下标(指定)为0的元素的值换成 xiaoyu OK 127.0.0.1:6379> lrange list 0 -1 1) "xiaoyu" 2) "xiaoyu" 127.0.0.1:6379> lset list 2 xiaoshu #如果列表的下标超出了,再使用lset更新值也会报错 (error) ERR index out of range 127.0.0.1:6379>
Linsert key before|after pivot element 将某一个具体的值插入到某个元素的前面或者后面
分析:pivot 指的是需要往哪个元素的前后插入值
127.0.0.1:6379> lrange list 0 -1 1) "xiaoyu" 2) "xiaoyu" 127.0.0.1:6379> Linsert list before xiaoyu xiaoshu (integer) 3 127.0.0.1:6379> lrange list 0 -1 1) "xiaoshu" 2) "xiaoyu" 3) "xiaoyu" 127.0.0.1:6379> Linsert list after xiaoyu xiaolan (integer) 4 127.0.0.1:6379> lrange list 0 -1 1) "xiaoshu" 2) "xiaoyu" 3) "xiaolan" 4) "xiaoyu" 127.0.0.1:6379>
小结
它实际上是一个链表,before Node after;left、right 都可以插入值
如果key不存在,创建新的链表
如果key存在,新增内容
如果移除了所有值,空链表,也代表不存在
在两边插入或者改动值,效率最高!中间元素,相对来说效率会低一点
消息排队!消息队列 Lpush Rpop,栈 Lpush Lpop
set中的值是不能重复的!无序的!
127.0.0.1:6379> keys * 1) "list" 127.0.0.1:6379> sadd myset xiaoshu #向myset集合中添加一个元素:xiaoshu (integer) 1 127.0.0.1:6379> keys * 1) "list" 2) "myset" 127.0.0.1:6379> Sadd myset xiaoyu (integer) 1 127.0.0.1:6379> Sadd myset xiaohua (integer) 1 127.0.0.1:6379> Smembers myset #查看myset集合中所有元素 1) "xiaoyu" 2) "xiaoshu" 3) "xiaohua" 127.0.0.1:6379> Sismember myset xiaoshu #判断某个元素是否存在myset集合中 (integer) 1 #存在返回1 127.0.0.1:6379> Sismember myset xiaolan (integer) 0 #不存在返回0 127.0.0.1:6379> keys * 1) "list" 2) "myset" 127.0.0.1:6379> Smembers myset 1) "xiaoyu" 2) "xiaoshu" 3) "xiaohua" 127.0.0.1:6379> Sadd myset xiaoshu #如果添加已经存在的值,则会添加不成功! (integer) 0 127.0.0.1:6379> Sadd myset xiaolan (integer) 1 127.0.0.1:6379> Smembers myset 1) "xiaolan" 2) "xiaoyu" 3) "xiaoshu" 4) "xiaohua" 127.0.0.1:6379> Srem myset xiaoshu #移除myset集合中的某个具体的元素 (integer) 1 127.0.0.1:6379> Smembers myset 1) "xiaolan" 2) "xiaoyu" 3) "xiaohua" 127.0.0.1:6379>
set 无序不重复集合。抽随机!
127.0.0.1:6379> Smembers myset 1) "xiaolan" 2) "xiaoyu" 3) "xiaohua" 127.0.0.1:6379> SRANDMEMBER myset #随机返回一个myset集合中的元素 "xiaoyu" 127.0.0.1:6379> SRANDMEMBER myset 2 #随机返回两个myset集合中的元素 1) "xiaoyu" 2) "xiaolan" 127.0.0.1:6379> SRANDMEMBER myset 2 1) "xiaoyu" 2) "xiaohua" 127.0.0.1:6379>
随机删除set中的元素
127.0.0.1:6379> SMEMBERS myset 1) "xiaohua" 127.0.0.1:6379> SADD myset xiaoshu1 (integer) 1 127.0.0.1:6379> SADD myset xiaoshu2 (integer) 1 127.0.0.1:6379> SADD myset xiaoshu3 (integer) 1 127.0.0.1:6379> SADD myset xiaoshu4 (integer) 1 127.0.0.1:6379> SADD myset xiaoshu5 (integer) 1 127.0.0.1:6379> SMEMBERS myset #通过下面可以看出set是一个无序集合 1) "xiaoshu3" 2) "xiaohua" 3) "xiaoshu2" 4) "xiaoshu5" 5) "xiaoshu1" 6) "xiaoshu4" 127.0.0.1:6379> SPOP myset #随机删除set中的元素 "xiaoshu3" 127.0.0.1:6379> SPOP myset #随机删除set中的元素 "xiaoshu1" 127.0.0.1:6379> SMEMBERS myset 1) "xiaoshu5" 2) "xiaohua" 3) "xiaoshu2" 4) "xiaoshu4" 127.0.0.1:6379> SPOP myset 2 #可指定随机删除几个set中的元素 1) "xiaohua" 2) "xiaoshu4" 127.0.0.1:6379> SMEMBERS myset 1) "xiaoshu5" 2) "xiaoshu2" 127.0.0.1:6379>
SMOVE:将一个指定的值,移动到另外一个set集合中!
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> SADD myset1 xiaoshu (integer) 1 127.0.0.1:6379> SADD myset1 xiaoyu (integer) 1 127.0.0.1:6379> SADD myset1 xiaolan (integer) 1 127.0.0.1:6379> SMEMBERS myset1 1) "xiaolan" 2) "xiaoyu" 3) "xiaoshu" 127.0.0.1:6379> SADD myset2 xiaoming (integer) 1 127.0.0.1:6379> SADD myset2 xiaozhang (integer) 1 127.0.0.1:6379> SADD myset2 xiaoqian (integer) 1 127.0.0.1:6379> SMEMBERS myset2 1) "xiaoqian" 2) "xiaozhang" 3) "xiaoming" 127.0.0.1:6379> SMOVE myset1 myset2 xiaoshu 将myset1中的一个成员xiaoshu从源(myset1)移动到目标(myset2)下 (integer) 1 127.0.0.1:6379> SMEMBERS myset1 1) "xiaolan" 2) "xiaoyu" 127.0.0.1:6379> SMEMBERS myset2 1) "xiaoshu" 2) "xiaoqian" 3) "xiaozhang" 4) "xiaoming" 127.0.0.1:6379>
微博,B站里面常常会发现有 共同关注!(在数学中叫:并集)
数学集合类:
差集
并集
交集
127.0.0.1:6379> SMEMBERS myset1 1) "xiaolan" 2) "xiaoyu" 127.0.0.1:6379> SMEMBERS myset2 1) "xiaoshu" 2) "xiaoqian" 3) "xiaozhang" 4) "xiaoming" 127.0.0.1:6379> SADD myset1 xiaoshu (integer) 1 127.0.0.1:6379> SMEMBERS myset1 1) "xiaoshu" 2) "xiaolan" 3) "xiaoyu" 127.0.0.1:6379> SMEMBERS myset2 1) "xiaoshu" 2) "xiaoqian" 3) "xiaozhang" 4) "xiaoming" 127.0.0.1:6379> SDIFF myset1 myset2 #差集:以myset1为参照,找出myset1中存在而myset2中不存在的值 1) "xiaoyu" 2) "xiaolan" 127.0.0.1:6379> SINTER myset1 myset2 #交集:找出myset1和myset2中都有值 共同好友就可以这样子实现! 1) "xiaoshu" 127.0.0.1:6379> SUNION myset1 myset2 #并集:将myset1和myset2中的值都放在一起 1) "xiaoqian" 2) "xiaolan" 3) "xiaoming" 4) "xiaoshu" 5) "xiaoyu" 6) "xiaozhang" 127.0.0.1:6379>
之前学的都是key-value键值对;现在key-Map集合!这时候这个hash的值是一个map
本质和String类型没有太大的区别,还是一个简单的key-value!
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> hset myhash name xiaoshu #设置一个myhash里面的元素为键值对: (integer) 1 127.0.0.1:6379> hget myhash name #获取myhash中key为name的值 "xiaoshu" 127.0.0.1:6379> hmset myhash age 22 sex man #批量设置值 OK 127.0.0.1:6379> hmget myhash name age sex #批量获取值 1) "xiaoshu" 2) "22" 3) "man" 127.0.0.1:6379> hset myhash name xiaoyu #重新给name赋值,会覆盖原来的! (integer) 0 #这里返回的是0,我也不知道为什么返回0,下面获取到的是新的值,以下面的实际内容为准! 127.0.0.1:6379> hgetall myhash #获取全部的k-v 1) "name" #key 2) "xiaoyu" #value 3) "age" 4) "22" 5) "sex" 6) "man" 127.0.0.1:6379> hmget myhash name age sex 1) "xiaoyu" 2) "22" 3) "man" 127.0.0.1:6379> hset myhash name xiaolan age 20 sex woman (integer) 0 127.0.0.1:6379> hmget myhash name age sex 1) "xiaolan" 2) "20" 3) "woman" 127.0.0.1:6379>
HDEL 删除hash指定的key字段!对应的value值也就没有了
127.0.0.1:6379> keys * 1) "myhash" 127.0.0.1:6379> hgetall myhash 1) "name" 2) "xiaolan" 3) "age" 4) "20" 5) "sex" 6) "woman" 127.0.0.1:6379> HDEL myhash sex #删除hash指定的key字段!对应的value值也就没有了 (integer) 1 127.0.0.1:6379> hgetall myhash 1) "name" 2) "xiaolan" 3) "age" 4) "20" 127.0.0.1:6379>
HLEN 获取hash表的字段数量
HEXISTS 判断哈希表中是否存在某个键
127.0.0.1:6379> hgetall myhash 1) "name" 2) "xiaolan" 3) "age" 4) "20" 127.0.0.1:6379> HLEN myhash #获取hash表的字段数量 (integer) 2 127.0.0.1:6379> HEXISTS myhash name #判断hash中的指定字段是否存在 (integer) 1 127.0.0.1:6379>
只获取hash中所有keys
只获取hash中所有values
127.0.0.1:6379> HGETALL myhash 1) "name" 2) "xiaolan" 3) "age" 4) "20" 127.0.0.1:6379> HKEYS myhash 1) "name" 2) "age" 127.0.0.1:6379> HVALS myhash 1) "xiaolan" 2) "20" 127.0.0.1:6379>
指定增量:HINCRBY myhash age 2
指定减量:HINCRBY myhash age -2
127.0.0.1:6379> HGETALL myhash 1) "name" 2) "xiaolan" 3) "age" 4) "20" 127.0.0.1:6379> HINCRBY myhash age 2 #指定增量 (integer) 22 127.0.0.1:6379> HGETALL myhash 1) "name" 2) "xiaolan" 3) "age" 4) "22" 127.0.0.1:6379> HINCRBY myhash age -2 #指定减量 (integer) 20 127.0.0.1:6379> HGETALL myhash 1) "name" 2) "xiaolan" 3) "age" 4) "20" 127.0.0.1:6379> HSETNX myhash name xiaolan #如果存在则会设置不成功 (integer) 0 127.0.0.1:6379> HSETNX myhash sex woman #如果不存在才会成功新增一个field-value (integer) 1 127.0.0.1:6379> HGETALL myhash 1) "name" 2) "xiaolan" 3) "age" 4) "20" 5) "sex" 6) "woman" 127.0.0.1:6379>
hash可以存一些变更的数据,尤其是用户信息之类的,经常变动的信息!hash更适合于对象的存储,String更加适合字符串存储!
127.0.0.1:6379> hset user:1 name xiaoshu (integer) 1 127.0.0.1:6379> hget user:1 name "xiaoshu" 127.0.0.1:6379>
在set的基础上增加了一个值;有序集合最大的作用就是排序!
set k1 v1 zset k1 score1 v1
127.0.0.1:6379> ZADD myzset 1 one #向myzset中新增一个one,它的优先级是1,优先级越小排的位置越靠前 (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 #获取myzset中所有的元素 1) "one" 2) "two" 3) "three" 4) "four" 127.0.0.1:6379> ZADD myzset 2 five #向myzset中新增一个five,它的优先级是2 (integer) 1 127.0.0.1:6379> Zrange myzset 0 -1 1) "one" 2) "five" 3) "two" 4) "three" 5) "four" 127.0.0.1:6379>
排序如何实现?
ZRANGEBYSCORE 通过ZRANGEBYSCORE进行排序
127.0.0.1:6379> ZADD salary 5000 xiaoming #添加三个用户 (integer) 1 127.0.0.1:6379> ZADD salary 2500 xiaoshu (integer) 1 127.0.0.1:6379> ZADD salary 2500 zhangsan (integer) 1 127.0.0.1:6379> 127.0.0.1:6379> Zrange salary 0 -1 #显示全部用户 1) "xiaoshu" 2) "zhangsan" 3) "xiaoming" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf #通过ZRANGEBYSCORE(5000)进行排序,-inf:表示负无穷,+inf:表示正无穷,从小到大 1) "xiaoshu" 2) "zhangsan" 3) "xiaoming" 127.0.0.1:6379> ZADD salary 7000 xiaolan (integer) 1 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf 1) "xiaoshu" 2) "zhangsan" 3) "xiaoming" 4) "xiaolan" 127.0.0.1:6379> Zrange salary 0 -1 1) "xiaoshu" 2) "zhangsan" 3) "xiaoming" 4) "xiaolan" # ZRANGEBYSCORE key min max 127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores #显示所有用户,并且带着scores一起打印出来 1) "xiaoshu" #name 2) "2500" #value 3) "zhangsan" 4) "2500" 5) "xiaoming" 6) "5000" 7) "xiaolan" 8) "7000" 127.0.0.1:6379> ZRANGEBYSCORE salary -inf 5000 withscores #只打印5000及5000以内的数据,升序排列 1) "xiaoshu" 2) "2500" 3) "zhangsan" 4) "2500" 5) "xiaoming" 6) "5000" 127.0.0.1:6379> ZREVRANGE salary 0 -1 withscores #降序排列,从大到小 1) "xiaolan" 2) "7000" 3) "xiaoming" 4) "5000" 5) "zhangsan" 6) "2500" 7) "xiaoshu" 8) "2500" 127.0.0.1:6379> Zrange salary 0 -1 withscores #升序排列,从小到大 1) "xiaoshu" 2) "2500" 3) "zhangsan" 4) "2500" 5) "xiaoming" 6) "5000" 7) "xiaolan" 8) "7000" 127.0.0.1:6379>
zrem 移除zset中的元素
Zcard 查看zset中有几个元素
127.0.0.1:6379> zrange myzset 0 -1 1) "one" 2) "five" 3) "two" 4) "three" 5) "four" 127.0.0.1:6379> ZRANGEBYSCORE myzset -inf +inf withscores 1) "one" 2) "1" 3) "five" 4) "2" 5) "two" 6) "2" 7) "three" 8) "3" 9) "four" 10) "4" 127.0.0.1:6379> Zrem myzset five #移除某个元素 (integer) 1 127.0.0.1:6379> zrange myzset 0 -1 1) "one" 2) "two" 3) "three" 4) "four" 127.0.0.1:6379> Zcard myzset #查看zset中有几个元素 (integer) 4 127.0.0.1:6379> ZREVRANGE myzset 0 -1 withscores #降序排列,从大到小 1) "four" 2) "4" 3) "three" 4) "3" 5) "two" 6) "2" 7) "one" 8) "1" 127.0.0.1:6379>
ZCOUNT 获取指定区间(闭区间)中的成员数量
127.0.0.1:6379> ZADD zset 1 xiaoshu 2 xiaoyu 3 xiaohua 4 xiaoming (integer) 4 127.0.0.1:6379> Zrange zset 0 -1 withscores 1) "xiaoshu" 2) "1" 3) "xiaoyu" 4) "2" 5) "xiaohua" 6) "3" 7) "xiaoming" 8) "4" 127.0.0.1:6379> ZCOUNT zset 1 3 #获取指定区间[1,3]中的成员数量 (integer) 3 127.0.0.1:6379>
其他有需要去查看官方文档!
案例思路:
set 排序 存储班级成绩表,工资表排序!
普通消息 1,重要消息 2,带权重进行判断!
排行榜应用实现,取Top N测试!
朋友的定位,附近的人,打车距离的计算!
Redis的Geo!这个功能可以推算地理位置的信息,两地之间的距离,方圆几里的人!
可以查询一些测试数据:经纬度查询定位 拾取坐标系统 经纬度查询地图
Redis 地理位置(geo) 命令
命令 | 描述 |
---|---|
Redis GEOHASH 命令 | 返回一个或多个位置元素的 Geohash 表示 |
Redis GEOPOS 命令 | 从key里返回所有给定位置元素的位置(经度和纬度) |
Redis GEODIST 命令 | 返回两个给定位置之间的距离 |
Redis GEORADIUS 命令 | 以给定的经纬度为中心, 找出某一半径内的元素 |
Redis GEOADD 命令 | 将指定的地理空间位置(纬度、经度、名称)添加到指定的key中 |
Redis GEORADIUSBYMEMBER 命令 | 找出位于指定范围内的元素,中心点是由给定的位置元素决定 |
geoadd 添加地理位置 经度 纬度 名称
官网:Redis GEOADD 命令_将指定的地理空间位置(纬度、经度、名称)添加到指定的key中
规则:两极(南北极)无法直接添加,我们一般会下载城市数据,直接通过java程序通过加载配置文件的方式一次性全部导入!
有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。
当坐标位置超出上述指定范围时,该命令将会返回一个错误。
格式:geoadd key 经度 纬度 名称
注意:官网说的是 纬度、经度、名称 ,按照官网输入会有问题;因为命令行中的提示前面是经度,后面是纬度,所以我使用 经度 纬度 名称 就没问题!
127.0.0.1:6379> keys * (empty array) 127.0.0.1:6379> geoadd china:city 121.48 31.23 shanghai (integer) 1 127.0.0.1:6379> geoadd china:city 113.27 23.23 guangzhou 114.06 22.54 shenzhen 120.21 30.25 hangzhou 102.83 24.88 kunming (integer) 4 127.0.0.1:6379> keys * 1) "china:city" 127.0.0.1:6379>
geopos 从key里返回所有给定位置元素的位置(经度和纬度)
获取当前定位:一定是一个坐标值!
127.0.0.1:6379> geopos china:city shanghai 1) 1) "121.48000091314315796" 2) "31.22999903975783553" 127.0.0.1:6379> geopos china:city guangzhou shenzhen 1) 1) "113.27000051736831665" 2) "23.23000083189281639" 2) 1) "114.06000226736068726" 2) "22.53999903789756587" 127.0.0.1:6379>
geodist 返回两个给定位置之间的距离(直线距离)
如果两个位置之间的其中一个不存在, 那么命令返回空值。
指定单位的参数 unit 必须是以下单位的其中一个:
m 表示单位为米。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
127.0.0.1:6379> geodist china:city shanghai kunming #查看上海到昆明的直线距离,默认返回m "1958842.7198" 127.0.0.1:6379> geodist china:city shanghai kunming km #查看上海到昆明的直线距离,默认返回km "1958.8427" 127.0.0.1:6379> geodist china:city shanghai guangzhou km "1203.6997" 127.0.0.1:6379>
georadius 以给定的经纬度为中心, 找出某一半径内的元素
我附近的人?(获取所有附近的人的地址,定位!)通过半径查询!
withxxx参数:
WITHDIST
: 在返回位置元素的同时, 将位置元素与中心之间的距离也一并返回。 距离的单位和用户给定的范围单位保持一致。
WITHCOORD
: 将位置元素的经度和维度也一并返回。
WITHHASH
: 以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大。
命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:
ASC
: 根据中心的位置, 按照从近到远的方式返回位置元素。
DESC
: 根据中心的位置, 按照从远到近的方式返回位置元素。
以下基于china:city查询,若要使查询结果更精确就得将所有数据录入其中!
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km #查询以经纬度(110,30)中心,半径为1000km附近的城市! 1) "kunming" 2) "shenzhen" 3) "guangzhou" 4) "hangzhou" 127.0.0.1:6379> GEORADIUS china:city 110 30 500 km #查询以经纬度(110,30)中心,半径为500km附近的城市! (empty array) #返回空说明没有 127.0.0.1:6379> GEORADIUS china:city 121 31 500 km #查询以经纬度(121,31)中心,半径为500km附近的城市! 1) "hangzhou" 2) "shanghai" 127.0.0.1:6379> GEORADIUS china:city 121 31 500 km withcoord #查询以经纬度(121,31)中心,半径为500km附近的城市!并返回经纬度 1) 1) "hangzhou" 2) 1) "120.21000176668167114" 2) "30.24999979792261939" 2) 1) "shanghai" 2) 1) "121.48000091314315796" 2) "31.22999903975783553" 127.0.0.1:6379> GEORADIUS china:city 121 31 500 km withdist 1) 1) "hangzhou" 2) "112.5875" #将位置元素与中心之间的距离也一并返回 2) 1) "shanghai" 2) "52.3797" #将位置元素与中心之间的距离也一并返回 127.0.0.1:6379> GEORADIUS china:city 121 31 500 km withdist withcoord count 1 #只查一个 1) 1) "shanghai" 2) "52.3797" #将位置元素与中心之间的距离也一并返回 3) 1) "121.48000091314315796" 2) "31.22999903975783553" 127.0.0.1:6379> GEORADIUS china:city 121 31 500 km withdist withcoord count 2 1) 1) "shanghai" 2) "52.3797" 3) 1) "121.48000091314315796" 2) "31.22999903975783553" 2) 1) "hangzhou" 2) "112.5875" 3) 1) "120.21000176668167114" 2) "30.24999979792261939" 127.0.0.1:6379> GEORADIUS china:city 121 31 500 km withdist withcoord count 3 1) 1) "shanghai" 2) "52.3797" 3) 1) "121.48000091314315796" 2) "31.22999903975783553" 2) 1) "hangzhou" 2) "112.5875" 3) 1) "120.21000176668167114" 2) "30.24999979792261939" 127.0.0.1:6379>
GEORADIUSBYMEMBER 找出位于指定范围内的元素,中心点是由给定的位置元素决定
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 1000 km 1) "hangzhou" 2) "shanghai" 127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 10 km 1) "shanghai" 127.0.0.1:6379>
GEOHASH 返回一个或多个位置元素的 Geohash 表示;该命令将返回11个字符的Geohash字符串,如果两个字符串越相似,则距离越近!
127.0.0.1:6379> geohash china:city shanghai 1) "wtw3sm5pcj0" #将二维的经纬度转换为一维的字符串!说白了,就是将经纬度用一个hash值来表示了! 127.0.0.1:6379>
geo 底层的实现原理就是Zset!我们可以使用Zset命令来操作geo!
127.0.0.1:6379> zrange china:city 0 -1 #查看所有元素 1) "kunming" 2) "shenzhen" 3) "guangzhou" 4) "hangzhou" 5) "shanghai" 127.0.0.1:6379> zrem china:city kunming #移除指定元素 (integer) 1 127.0.0.1:6379> zrange china:city 0 -1 1) "shenzhen" 2) "guangzhou" 3) "hangzhou" 4) "shanghai" 127.0.0.1:6379>
什么是基数?
基数:不重复的元素,可以接受误差!
A = {1,3,5,7,8,7} 基数为:5
B = {1,3,5,7,8} 基数为:5
简介
Redis 2.8.9 版本就更新了 Hyperloglog 数据结构了!
Hyperloglog 是用来作基数统计的算法!
优点:
占用的内存是固定的,2^64不同的元素的基数,只需要12kb内存。如果要从内存角度来比较的话Hyperloglog就是首选!
大概只有0.81%的错误率!统计UV任务,可以忽略不计!
网页的UV(一个人访问一个网站多次,还是算作一个人!)
传统的方式:set 保存用户的id,然后就可以统计set中的元素数量作为标准判断!
这种方式如果保存大量的用户id,就会比较麻烦!我们的目的是为了计数,而不是为了保存用户id
127.0.0.1:6379> PFADD myset a b c d e f g h i j #创建第一组元素 myset (integer) 1 127.0.0.1:6379> PFCOUNT myset #统计 myset 中的基数数量(重复的只算一个) (integer) 10 127.0.0.1:6379> PFADD myset2 i j x s n g (integer) 1 127.0.0.1:6379> PFCOUNT myset2 (integer) 6 127.0.0.1:6379> PFMERGE myset3 myset myset2 将myset 和 myset2 中的数据合并到 myset3 OK 127.0.0.1:6379> PFCOUNT myset3 #查看并集 myset3 中的基数数量 (integer) 13 127.0.0.1:6379>
如果允许容错,那么一定可以使用Hyperloglog!
如果不允许容错,那就使用 set 或者自己 的数据类型即可!
位存储
14亿人 统计疫情感染人数:01010100 未感染:0 感染:1
统计用户信息,活跃,不活跃!登录,未登录!只有两个状态的都可以使用 Bitmap
Bitmap 位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态!
打卡:365天 = 365 bit 1字节 = 8bit 46个字节左右!
测试
使用bitmap来记录 周一(0)到周日(6)的打卡!
127.0.0.1:6379> setbit sign 0 1 #key:sign 设置周一(0)为打卡状态(1) (integer) 0 127.0.0.1:6379> setbit sign 1 0 #key:sign 设置周二(1)为未打卡状态(0) (integer) 0 127.0.0.1:6379> setbit sign 2 0 (integer) 0 127.0.0.1:6379> setbit sign 3 1 (integer) 0 127.0.0.1:6379> setbit sign 4 1 (integer) 0 127.0.0.1:6379> setbit sign 5 0 (integer) 0 127.0.0.1:6379> setbit sign 6 0 #key:sign 设置周日(6)为未打卡状态(0) (integer) 0 127.0.0.1:6379>
查看某天是否打卡
127.0.0.1:6379> getbit sign 5 #查看周六是否打卡 (integer) 0 127.0.0.1:6379> getbit sign 4 #查看周五是否打卡 (integer) 1 127.0.0.1:6379>
统计打卡天数
127.0.0.1:6379> bitcount sign #查看 周一(0)到周日(6)的打卡天数,后面不带参数就默认查看全部 (integer) 3 127.0.0.1:6379> bitcount sign 0 4 #查看 周一(0)到周五(4)的打卡天数,后面带了参数就查看那个区间的打卡天数 (integer) 3 127.0.0.1:6379>
Redis 事务的本质:一组命令的集合!一个事务中所有的命令都会被序列化,在事务执行过程中,会按照顺序执行!
----------- 队列 set set set 执行 --------------
一次性、顺序性、排他性!执行一系列的命令!
Redis单条命令是保证原子性的,但是redis的事务不保证原子性!
Redis事务没有隔离级别的概念!所有的命令在事务中,并没有直接被执行!只有发起执行命令的时候才会执行!就没有幻读、脏读等问题!
Redis 事务命令
下表列出了 redis 事务的相关命令:
序号 | 命令及描述 |
---|---|
1 | DISCARD 取消事务,放弃执行事务块内的所有命令。 |
2 | EXEC 执行所有事务块内的命令。 |
3 | MULTI 标记一个事务块的开始。 |
4 | UNWATCH 取消 WATCH 命令对所有 key 的监视。 |
5 | [WATCH key key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。 |
锁: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>
放弃事务
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 #放弃事务之后就退出了当前事务,可以看到端口号后面的(TX)已经不见了! OK 127.0.0.1:6379> get k4 (nil) 127.0.0.1:6379> get k2 (nil) 127.0.0.1:6379> get k1 (nil) 127.0.0.1:6379>
编译型异常(代码有问题!命令有错!),事务中所有的命令都不会被执行!
127.0.0.1:6379> multi OK 127.0.0.1:6379(TX)> set k1 v1 QUEUED 127.0.0.1:6379(TX)> set k2 v2 QUEUED 127.0.0.1:6379(TX)> set k3 v3 QUEUED 127.0.0.1:6379(TX)> getset k3 #命令错误 (error) ERR wrong number of arguments for 'getset' command 127.0.0.1:6379(TX)> 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) 127.0.0.1:6379>
运行时异常(1/0),如果事务队列中存在语法性错误,其他命令是可以正常执行的!错误命令抛出异常!
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)> set k3 v3 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) "v3" 127.0.0.1:6379> get k2 "v2" 127.0.0.1:6379> get k3 "v3" 127.0.0.1:6379>
监视 Watch (面试常问)
悲观锁:
很悲观,认为什么时候都会出问题,无论做什么都会加锁!
乐观锁:
很乐观,认为什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据(mysql里面用 version 进行监视)
Redis 监控测试
1、正常执行成功
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 对象,此时的值是100 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 #在没有其他线程修改原来值的情况下,执行exec,此时的money还是100,就会执行成功! 1) (integer) 80 2) (integer) 20 127.0.0.1:6379>
2、多线程的情况下修改值,使用 watch 可以当做redis的乐观锁操作!
在一个redis-cli的客户端1进行事务操作
127.0.0.1:6379> keys * 1) "money" 2) "out" 127.0.0.1:6379> get money "80" 127.0.0.1:6379> get out "20" 127.0.0.1:6379> watch money #监视 money,它此时的值是 80 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)>
这时从另外一个redis-cli的客户端2对money进行操作
127.0.0.1:6379> keys * 1) "money" 2) "out" 127.0.0.1:6379> get money "80" 127.0.0.1:6379> get out "20" 127.0.0.1:6379> set money 1000 #将money充值到1000 OK 127.0.0.1:6379>
然后重新回到客户端1进行执行事务操作
127.0.0.1:6379> keys * 1) "money" 2) "out" 127.0.0.1:6379> watch money #监视 money,它此时的值是 80 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 #执行事务之前,另外一个客户端2修改了我们的值变成了1000,而监视的值是80;二者不一样就会执行失败! (nil) #返回nil,表示执行事务失败了 127.0.0.1:6379> unwatch #如果事务执行失败,需要继续执行,则需要解除当前监视,再进行重新监视新值 OK 127.0.0.1:6379> watch money #启动监视,现在money的值是1000了 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 127.0.0.1:6379>
如果修改失败,获取最新的值就行!
我们要使用Java来操作Redis
什么是 Jedis ?
Jedis 是 Redis 官方推荐的 java 连接开发工具!使用Java 操作Redis中间件!如果你要使用java操作redis,那么一定要对Jedis十分的熟悉!
新建一个Java_Redis空项目,在里面再建一个redis-01-jedis模块
设置JDK版本
设置字节码版本
在pom文件中导入jedis、fastjson的包
redis.clients jedis 4.3.1 com.alibaba fastjson 2.0.22
编码测试
打开本地redis并连接成功
连接数据库
package com.shu; import redis.clients.jedis.Jedis; public class TestPing { public static void main(String[] args) { //1、new Jedis 对象即可 Jedis jedis = new Jedis("127.0.0.1",6379); //创建连接 //Jedis 所有的命令就是我们之前学习的所有指令!之前的指令就是现在的方法! System.out.println(jedis.ping()); jedis.close(); //关闭连接 } }
操作命令
常用的API命令和之前学习的一样
断开连接
连接阿里云redis
阿里云安全组配置
宝塔防火墙开放端口
修改redis.conf文件内容,用 wq! 可强制保存并退出
注释这句话
将yes改为no
服务器防火墙开放端口6379(如果在宝塔面板已经打开,下面会返回yes,就不用此处的命令操作了)
firewall-cmd --query-port=6379/tcp #查看端口6379是否开启 #如果返回yes则代表已开启 firewall-cmd --zone=public --add-port=6379/tcp --permanent #开启6379端口 firewall-cmd --reload #重载防火墙 firewall-cmd --query-port=6379/tcp #再次查看端口6379是否开启 firewall-cmd --list-ports #查看已经开启的端口,应该会返回3306/tcp 6379/tcp
重启redis-server
[root@xiaoshu bin]# ps -ef|grep redis #查看进程状态 root 447380 1 0 Jan03 ? 00:04:35 redis-server 127.0.0.1:6379 root 517881 517787 0 17:16 pts/0 00:00:00 grep --color=auto redis [root@xiaoshu bin]# kill 447380 #杀死进程:kill pid [root@xiaoshu bin]# ps -ef|grep redis root 517943 517787 0 17:20 pts/0 00:00:00 grep --color=auto redis [root@xiaoshu bin]# redis-server config/redis.conf #重启服务 [root@xiaoshu bin]# ps -ef|grep redis #查看进程状态 root 517947 1 0 17:20 ? 00:00:00 redis-server 127.0.0.1:6379 root 517953 517787 0 17:20 pts/0 00:00:00 grep --color=auto redis [root@xiaoshu bin]# redis-cli #测试连接 127.0.0.1:6379> ping PONG 127.0.0.1:6379>
连接成功