Redis学习

为什么要用Nosql?

1、单机MySQL的年代

在这里插入图片描述90年代,一个基本的网站访问量不会太大,单个数据库完全足够
呢个时候服务器更多的是使用静态的html,服务器根本没有太大压力
思考一下,这种情况下:整个网站的瓶颈是什么?
1、数据量如果太大,一个机器放不下
2、数据的索引(B+tree),一个机器内存也放不下, 超过三百万就必须建立索引了
3、访问量(读写混合),一个服务器承受不了。

只要你出现以上三种情况之一,那么你就必须要晋级。

2、Memcached(缓存)+MySQL+垂直拆分(读写分离)

网站80%的情况都是在读,每次都去查询数据库的画话就十分麻烦!所以说我们希望减轻数据的压力,我们可以用缓存来保证效率!
发展过程:优化数据结构和索引–>文件缓存(IO)–>Memcached(当时最热门的技术!)
在这里插入图片描述

3、分库分表+水平拆分+MySQL集群

本质:数据库(读,写)
早些年MyISAM:表锁,十分影响效率!高并发下就会出现严重的锁问题。
转战Innodb:行锁
慢慢的就开始使用分库分表来解决写的压力!MySQL在那个年代推出了表分区!这个并没有多少公司使用!
MySQL的集群,很好的满足了那个年代的所有需求!
在这里插入图片描述

4、如今最近的年代

2010-2020十年之间,世界已经发生了翻天覆地的变化;(定位,也是一种数据,音乐热榜!)
MySQL等关系型数据库就不够用了!数据量很多,变化很快!
MySQL有的使用它来存储一些比较大的文件,博客,图片!数据表很大,效率就低了!如果有一种数据库来专门处理这种数据,MySQL压力就变得十分小(研究如何处理这些问题!)大数据的IO压力下,表几乎没法更改。

目前一个基本的互联网项目

在这里插入图片描述

为什么要用NoSQL!

用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长!
这个时候我们就需要NoSQL数据库,NoSQL可以很好的解决这个问题

什么是NoSQL

NoSQL = Not Only SQL

关系型数据库:表格,行,列
泛指非关系型数据库,随着web2.0互联网诞生!传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展十分迅速,redis是发展最快的,而且是我们当下必须要掌握的一个技术。
很多的数据类型用户的个人信息,社交网络,地理位置。这些数据类型的存储不需要一个固定的格式!不需要的多余的操作就可以进行横向扩展!
Map<String,Object>使用键值对来控制。

NoSQL特点

1、方便扩展(数据之间没有关系,很好扩展)
2、大数据量高性能(Redis一秒可以写八万次,一秒可以读十一万,NoSQL缓存记录级,是一种细粒度的缓存,性能会比较高!)
3、数据类型是多样型的(不需要事先设计数据库!随取随用!如果是数据量比较大的表,很多人就无法设计了)
4、传统的RDBMS和NoSQL

传统的RDNMS
-结构化组织
-SQL
-数据和关系都存在单独的表中
-操作定义,数据定义语言
-严格的一致性
-基础的事务
-…

NoSQL
-不仅仅是数据
-没有固定的查询语言
-键值对存储,列存储,文档存储,图形数据库(社交关系)
-最终一致性
-CAP定理和BASE(异地多活)
-高性能,高可扩展,高可用

真正的在公司实践:NoSQL+RDBMS一起使用才是最强的.

NoSQL四大分类

键值对存储

  • 新浪:redis
  • 美团:redis+Tair
  • 阿里,百度:redis+memcache

文档型数据库(bson格式和json一样)

  • MongoDB(一般必须要掌握)
    MongoDB是一个基于分布式文件存储的数据库,c++编写,主要用来处理大量的文档
    MongoDB是一个介于关系型数据库和非关系型数据库中间品!MongoDB是非关系型数据库中功能最丰富,最像关系型数据库的。

  • CouchDB(国外的一款数据库)

列存储数据库

  • HBase
  • 分布式文件系统

图关系数据库

  • 他不是存图形,而是存关系,比如朋友圈,社交网络,广告推荐等等。
  • Neo4j InfoGrid
    在这里插入图片描述

Redis是什么

Redis (Remote Dictionary Server)远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

Redis能干什么

1、内存存储,持久化,内存是断电即失,所以说持久化很重要(rdb,aof)
2、效率高可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器,计数器(浏览量!)
6、…

Redis特性

1、多样的数据
2、持久化
3、集群
4、事务
5、…

测试性能

redis-benchmark是一个压力测试工具!
官方自带的性能测试工具!
redis-benchmark命令参数:
在这里插入图片描述

#测试 100个并发 100000个请求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000

在这里插入图片描述
测试结果分析
在这里插入图片描述

Redis基础知识

  • Redis默认有16个数据库。
    在这里插入图片描述
  • 默认使用第0个,可以使用select进行切换
select 3

在这里插入图片描述

  • 查询所有key
keys *

在这里插入图片描述

  • 清除当前数据库
flushdb
  • 清除所有数据库
flushall
  • Redis是单线程

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

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

1.redis是基于内存的,内存的读写速度非常快;
2.redis是单线程的,省去了很多上下文切换线程的时间;
3.redis使用多路复用技术,可以处理并发的连接;

目前,多路复用主要有三种技术:select,poll,epoll。它们出现的顺序是按时间先后的,越排后的技术改正了之前技术的缺点。epoll是最新的也是目前最好的多路复用技术。

  • select:
    1.会修改传入的参数,对于多个调用的函数来说非常不友好;
    2.要是sock(io流出现了数据),select只能轮询这去找数据,对于大量的sock来说开销很大;
    3.不是线程安全的,很恐怖;
    4.只能监视1024个连接;
  • poll:
    1.还不是线程安全的…
    2.去掉了1024个连接的限制;
    3.不修改传入的参数了;
  • epoll:
    1.线程安全了;
    2.epoll不仅能告诉你sock有数据,还能告诉你哪个sock有数据,不用轮询了;
    3.however,只支持linux系统;

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

五大数据类型

Redis-key

127.0.0.1:6379> FLUSHALL  #清空所有
OK
127.0.0.1:6379> keys * #查看所有key
(empty list or set)
127.0.0.1:6379> clear
127.0.0.1:6379> set name zhaoli #设置key
OK
127.0.0.1:6379> get name #获取key对应的value
"zhaoli"
127.0.0.1:6379> keys * 
1) "name"
127.0.0.1:6379> EXISTS name #判断当前key是否存在 存在返回1,不存在返回0
(integer) 1
127.0.0.1:6379> EXISTS name1
(integer) 0
127.0.0.1:6379> move name 1 #将当前数据移动到指定库中
(integer) 1
127.0.0.1:6379> select 1 #切换到1号库
OK
127.0.0.1:6379[1]> keys *
1) "name"
127.0.0.1:6379[1]> get name
"zhaoli"
127.0.0.1:6379[1]> del name #删除指定的key
(integer) 1
127.0.0.1:6379[1]> keys *
(empty list or set)
127.0.0.1:6379[1]> set name zhaoli
OK
127.0.0.1:6379[1]> EXPIRE name 10 #设置过期时间
(integer) 1
127.0.0.1:6379[1]> ttl name #查看当前key剩余时间
(integer) 6
127.0.0.1:6379[1]> ttl name
(integer) 4
127.0.0.1:6379[1]> ttl name
(integer) 3
127.0.0.1:6379[1]> ttl name
(integer) 3
127.0.0.1:6379[1]> ttl name
(integer) 1
127.0.0.1:6379[1]> ttl name
(integer) -2
127.0.0.1:6379[1]> set name zhaoli
OK
127.0.0.1:6379[1]> set age 19
OK
127.0.0.1:6379[1]> type name #查看当前key类型
string
127.0.0.1:6379[1]> type age
string

不知道的命令可以去官网查看:https://redis.io/commands

  • String
###########################################################
127.0.0.1:6379[1]> set key1 v1
OK
127.0.0.1:6379[1]> APPEND key1 hello    #追加字符串
(integer) 7
127.0.0.1:6379[1]> get key1
"v1hello"
127.0.0.1:6379[1]> STRLEN key1        #获取字符串长度
(integer) 7
127.0.0.1:6379[1]> APPEND key zhaoli   #如果当前key不存在,则相当于set key
(integer) 6
127.0.0.1:6379[1]> keys *
1) "key1"
2) "key"
3) "age"
4) "name"
############################################################
127.0.0.1:6379[1]> set views 0
OK
127.0.0.1:6379[1]> INCR views  #自增一
(integer) 1
127.0.0.1:6379[1]> get views
"1"
127.0.0.1:6379[1]> DECR views   #自减一
(integer) 0
127.0.0.1:6379[1]> get views
"0"
127.0.0.1:6379[1]> INCRBY views 10  #自增  步长为10
(integer) 10
127.0.0.1:6379[1]> get views   
"10"
127.0.0.1:6379[1]> DECRBY views 5    #自减  步长为5
(integer) 5
127.0.0.1:6379[1]> get views
"5"
############################################################
字符串范围range
127.0.0.1:6379[1]> set key1 hello,redis
OK
127.0.0.1:6379[1]> STRLEN key1        #获取字符串长度
(integer) 11
127.0.0.1:6379[1]> get key1
"hello,redis"
127.0.0.1:6379[1]> GETRANGE key1 0 3    #截取字符串[0,3]
"hell"
127.0.0.1:6379[1]> GETRANGE key1 0 -1      #截取全部字符串
"hello,redis"
############################################################
替换
127.0.0.1:6379[1]> set key2 abcdefg
OK
127.0.0.1:6379[1]> SETRANGE key2 2 xxx
(integer) 7
127.0.0.1:6379[1]> get key2
"abxxxfg"
############################################################
setex(set with expire) #设置过期时间
setnx(set if not exist) #如果不存在再设置(在分布式锁中会常常使用)
127.0.0.1:6379[1]> setex key1 30 hello  #设置key1的值为hello,30秒后过期
OK
127.0.0.1:6379[1]> ttl key1
(integer) 22
127.0.0.1:6379[1]> set mykey zhaoli
OK
127.0.0.1:6379[1]> setnx mykey xiaocong   #如果mykey存在,创建失败
(integer) 0
127.0.0.1:6379[1]> keys *
1) "mykey"
############################################################
批量获取和设值
127.0.0.1:6379[1]> mset k1 v1 k2 v2 k3 v3        #同时设置多个值
OK
127.0.0.1:6379[1]> keys *
1) "k3"
2) "k2"
3) "k1"
127.0.0.1:6379[1]> mget k1 k2 k3          #同时获取多个值	
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379[1]> msetnx k1 v2 k4 v4    #msetnx 是一个原子性的操作,要么一起成功,要么一起失败。
(integer) 0
############################################################
#对象 
set user:1 {name:zhangsan,age:3}   #设置一个user:1 对象,值为一个json字符串来保存一个对象!
这里的key是一个巧妙的设计,对象:{id}{field}
127.0.0.1:6379[1]> mset user:1:name zhangsan user:1:age 19
OK
127.0.0.1:6379[1]> mget user:1:name user:1:age
1) "zhangsan"
2) "19"
###############################################################
getset        #先get后set
127.0.0.1:6379[1]> getset key1 redis   #如果不存在值,则返回nil
(nil)
127.0.0.1:6379[1]> get key1
"redis"
127.0.0.1:6379[1]> getset key1 mongodb   #如果存在值,返回原来的值,将值设置为新值
"redis"
127.0.0.1:6379[1]> get key1
"mongodb"
  • List(redis中list可以玩成栈,队列,阻塞队列)
#############################################################
LPUSH
RPUSH
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       #获取list'中的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 1       #获取list中指定范围的值
1) "three"
2) "two"
127.0.0.1:6379> RPUSH list right          #将一个值或多个值插入到列表右部
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"

############################################################
LPOP
RPOP
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
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"
############################################################
LINDEX
127.0.0.1:6379> LINDEX list 0        #通过下标获取list中某一个值
"two"
127.0.0.1:6379> LINDEX list 1
"one"
#############################################################
LLEN
127.0.0.1:6379> LLEN list          #返回list长度
(integer) 2
##############################################################
LREM          #移除指定值!
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "two"
3) "one"
4) "one"
127.0.0.1:6379> LREM list 2 two  #移除list集合中指定个数的value,精确匹配
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
2) "one"
127.0.0.1:6379> LREM list 1 one
(integer) 1
127.0.0.1:6379> LRANGE list 0 -1
1) "one"
############################################################
127.0.0.1:6379> RPUSH list hello
(integer) 1
127.0.0.1:6379> RPUSH list hello1
(integer) 2
127.0.0.1:6379> RPUSH list hello12
(integer) 3
127.0.0.1:6379> RPUSH list hello13
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "hello1"
3) "hello12"
4) "hello13"
127.0.0.1:6379> LTRIM list 1 2       #截取list指定区间
OK
127.0.0.1:6379> LRANGE list 0 -1
1) "hello1"
2) "hello12"
############################################################
RPOPLPUSH      #将最后一个元素弹出并添加进新的list
127.0.0.1:6379> LRANGE list 0 -1
1) "hello1"
2) "hello12"
127.0.0.1:6379> RPOPLPUSH list list
"hello12"
127.0.0.1:6379> LRANGE list 0 -1
1) "hello12"
2) "hello1"
############################################################
LSET       #将列表中指定下标元素替换为新的值,更新操作
127.0.0.1:6379> LPUSH list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
127.0.0.1:6379> LSET list 0 items
OK
127.0.0.1:6379> LRANGE list 0 0
1) "items"
127.0.0.1:6379> LSET list 1 items
(error) ERR index out of range
############################################################
LINSERT       #在指定元素前面或后面插入元素
127.0.0.1:6379> RPUSH list hello
(integer) 1
127.0.0.1:6379> RPUSH list world
(integer) 2
127.0.0.1:6379> LINSERT list before world other
(integer) 3
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> LINSERT list after world new
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"

栈 (LPUSH,LPOP) 队列(LPUSH,RPOP)

  • Set(set中值不能重复)
127.0.0.1:6379> SADD myset hello      # set集合中添加值
(integer) 1
127.0.0.1:6379> SADD myset world
(integer) 1
127.0.0.1:6379> SADD myset zhaoli
(integer) 1
127.0.0.1:6379> SMEMBERS myset      #查看值定set中的所有值
1) "world"
2) "zhaoli"
3) "hello"
127.0.0.1:6379> SISMEMBER myset hello  #判断set中是否存在某一个值
(integer) 1
############################################################	
127.0.0.1:6379> SCARD myset   #获取set中元素个数
(integer) 3
############################################################
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "zhaoli"
3) "hello"
127.0.0.1:6379> SREM myset world       #移除set中指定元素
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "zhaoli"
2) "hello"
############################################################
127.0.0.1:6379> SMEMBERS myset
1) "a2"
2) "zhaoli"
3) "hello"
4) "a4"
5) "a1"
6) "a3"
127.0.0.1:6379> SRANDMEMBER myset      #随机抽选出一个元素
"zhaoli"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset 3   #随机抽选出指定个数元素
1) "a1"
2) "a2"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset 3
1) "a4"
2) "a1"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset 33   #超出总的元素个数则显示全部
1) "zhaoli"
2) "a4"
3) "a1"
4) "a3"
5) "a2"
6) "hello"
###########################################################
127.0.0.1:6379> SMEMBERS myset
1) "zhaoli"
2) "a4"
3) "a1"
4) "a3"
5) "hello"
6) "a2"
127.0.0.1:6379> SPOP myset   #随机删除一个元素
"a3"
127.0.0.1:6379> SPOP myset
"a1"
127.0.0.1:6379> SMEMBERS myset
1) "zhaoli"
2) "a4"
3) "hello"
4) "a2"
127.0.0.1:6379> SPOP myset 2  #随机删除指定个数的元素
1) "zhaoli"
2) "a2"
###############################################################
SMOVE  #将一个指定元素移动到另一个set
127.0.0.1:6379> SADD myset hello
(integer) 1
127.0.0.1:6379> SADD myset world
(integer) 1
127.0.0.1:6379> SADD myset zhaoli
(integer) 1
127.0.0.1:6379> SADD set2 set2
(integer) 1
127.0.0.1:6379> SMOVE myset set2 zhaoli
(integer) 1
127.0.0.1:6379> SMEMBERS myset 
1) "world"
2) "hello"
127.0.0.1:6379> SMEMBERS set2
1) "set2"
2) "zhaoli"
############################################################
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 f
(integer) 1
127.0.0.1:6379> SDIFF key1 key2  #差集
1) "a"
2) "b"
127.0.0.1:6379> SINTER key1 key2  #交集 共同好友可以通过交集实现
1) "c"
127.0.0.1:6379> SUNION key1 key2  #并集
1) "c"
2) "a"
3) "b"
4) "f"
5) "d"

微博,A用户将所有的关注的人放在一个set中!,将他的粉丝也放在一个集合中!
共同关注,共同爱好,二度好友,推荐好友(六度分割理论!!+)

  • Hash
    map集合,key-map这时候的值是一个map集合!本质和String没有太大区别,还是一个简单的key-value
127.0.0.1:6379> hset myhash k1 v1       #	set一个具体的key-value
(integer) 1
127.0.0.1:6379> hget myhash k1
"v1"
127.0.0.1:6379> hmset myhash k2 v2 k3 v3    #set多个key-vaue
OK
127.0.0.1:6379> hmget k1 k2 k3
1) (nil)
2) (nil)
127.0.0.1:6379> hmget myhash k1 k2 k3   #获取多个key-value
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> hgetall myhash   #获取全部数据
1) "k1"
2) "v1"
3) "k2"
4) "v2"
5) "k3"
6) "v3"
##########################################################
127.0.0.1:6379> hdel myhash k1 k2   #删除指定key的值
(integer) 2
127.0.0.1:6379> hgetall myhash
1) "k3"
2) "v3"
##########################################################
127.0.0.1:6379> hlen myhash     #获取hash表的字段数量
(integer) 1
##########################################################
127.0.0.1:6379> HEXISTS myhash k3   #判断hash表中指定字段是否存在
(integer) 1
127.0.0.1:6379> HEXISTS myhash k1
(integer) 0
127.0.0.1:6379> hkeys myhash   #获取全部key
1) "k3"
127.0.0.1:6379> hvals myhash  #获取全部值
1) "v3"
##########################################################
127.0.0.1:6379> hset myhash k1 1
(integer) 1
127.0.0.1:6379> HINCRBY myhash k1 2      #自增步长为2
(integer) 3
127.0.0.1:6379> HINCRBY myhash k1 -1
(integer) 2
127.0.0.1:6379> HSETNX myhash k2 hello   #如果不存在则可以设置
(integer) 1
127.0.0.1:6379> HSETNX myhash k2 world   #如果存在则不能设置
(integer) 0

hash更适合对象的存储,String更适合字符串的存储

  • Zset(有序集合)
    在set基础上,增加了一个值,set k1 v1 zset k1 score1 v1
127.0.0.1:6379> ZADD myset 1 one      #zset 添加一个值
(integer) 1
127.0.0.1:6379> ZADD myset 2 two 3 three      #zset添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
############################################################
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf     #显示全部用户 从小到大
1) "zahngsan"
2) "xiaocong"
3) "zhaoli"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores  #显示全部用户并且附带分数
1) "zahngsan"
2) "500"
3) "xiaocong"
4) "1500"
5) "zhaoli"
6) "2500"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 1500 withscores #显示工资小于1500元的用户的生序排列
1) "zahngsan"
2) "500"
3) "xiaocong"
4) "1500"
127.0.0.1:6379> ZREVRANGE salary 0 -1 withscores     #显示全部用户并且附带分数降序排列
1) "zhaoli"
2) "2500"
3) "xiaocong"
4) "1500"
5) "zahngsan"
6) "500"
##########################################################################
127.0.0.1:6379> ZREM salary zhaoli     #移除指定元素
(integer) 1
##########################################################################
127.0.0.1:6379> zcard salary       #获取有序集合元素个数
(integer) 3
#############################################################
127.0.0.1:6379> ZCOUNT myset 1 2       #获取指定区间成员数量
(integer) 2
##############################################################

应用案例:set排序 存储班级成绩排序表,工资排序表
普通消息 0 ,重要消息 1 带权重
网站热搜排行榜,取Top N

三种特殊数据类型

  • geospatial(地理位置)
    Redis的Geo在Redis3.2版本就推出了!这个功能可以推算地理位置信息,两地之间的距离,方圆几里的人。
    朋友的定位,附近的人,打车位置?
    在线经纬度查询:http://www.jsons.cn/lngcode/
    在这里插入图片描述

有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。

127.0.0.1:6379> GEOADD china:city 116.40 39.90 beijing
(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 chongqing
(integer) 1
127.0.0.1:6379> GEOADD china:city 120.15 30.28 hangzhou
(integer) 1
127.0.0.1:6379> GEOADD china:city 125.14 42.92 xian
(integer) 
##############################################################
#获取指定位置经纬度  GEOPOS key members
127.0.0.1:6379> GEOPOS china:city beijing chongqing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"
###########################################################
#查看两地之间的距离
127.0.0.1:6379> GEODIST china:city beijing shanghai
"1067378.7564"
127.0.0.1:6379> GEODIST china:city beijing shanghai km
"1067.3788"
############################################################
#获取以给定的经纬度为中心,找出某一半径范围内的元素
#显示以110 30 为中心半径1000km的元素以及他到中心点的距离
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist
1) 1) "chongqing"
   2) "341.9374"
2) 1) "hangzhou"
   2) "976.4868"
#显示以110 30 为中心半径1000km的元素以及他的经纬度
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withcoord
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
#显示以110 30 为中心半径1000km的元素以及他到中心点的距离,显示数量为1
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord count 1
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
#显示以110 30 为中心半径1000km的元素以及他到中心点的距离,显示数量为2
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord count 2
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "hangzhou"
   2) "976.4868"
   3) 1) "120.15000075101852417"
      2) "30.2800007575645509"
#显示以110 30 为中心半径1000km的元素以及他到中心点的距离,显示数量为3
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km withdist withcoord count 3
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "hangzhou"
   2) "976.4868"
   3) 1) "120.15000075101852417"
      2) "30.2800007575645509"
#################################################################
#查询指定位置周围固定距离的元素
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km 
1) "beijing"
2) "xian"
############################################################
#Geohash该命令将返回11个字符的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) "hangzhou"
3) "shanghai"
4) "beijing"
5) "xian"
127.0.0.1:6379> ZREM china:city beijing
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "hangzhou"
3) "shanghai"
4) "xian"
  • hyperloglog

什么是基数?

A{1,3.4,5,4}
B{1,3,4,5}
基数(不重复元素个数) = 4 可以接受误差!

简介

Redis2.8.9版本更新了Hyperloglog数据结构
Redis Hyperloglog 基数统计的算法!
优点:占用的内存是固定的,2^64不同元素的基数,只需要费12KB的内存。如果从内存角度比较的话,Hyperloglog首选!
网页的UV(一个人访问一个网站多次,但还算作一个人)
传统的方式,set保存用户id,然后统计set中元素数量作为标准判断
这个方式如果存在大量用户,就会占用大量内存,就会比较麻烦!我们的目的是基数,而不是保存用户id。

0.81%错误率,UV统计是可以不记的

127.0.0.1:6379> PFADD mykey a b c d e      #创建第一组元素mykey
(integer) 1
127.0.0.1:6379> PFADD mykey2 e f j l n l    #创建第二组元素mykey2 
(integer) 1
127.0.0.1:6379> PFCOUNT mykey      #统计mykey元素数量
(integer) 5
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2  #合并两组mykey mykey2 ==》mykey3 并集
OK
127.0.0.1:6379> PFCOUNT mykey3 #查看并集数量
(integer) 9

如果允许容错,那么一定可以使用Hyperloglog
如何不允许容错,使用set或自己的数据类型。

  • bitmap

位存储

统计用户信息,活跃 不活跃!登录,未登录!打卡,两个状态的,都可以使用Bitmap
Bitmap 位图,数据结构!都是操作二进制位进行记录,就只有1和0两个状态。
365天 = 365 bit 1字节 = 8 bit 46个字节左右

测试

使用Bitmaps来记录一周的打卡记录

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 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

获取打卡记录

127.0.0.1:6379> GETBIT sign 3
(integer) 0
127.0.0.1:6379> GETBIT sign 4
(integer) 1

获取打卡次数

127.0.0.1:6379> BITCOUNT sign
(integer) 4

事务

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

-----队列 set set set 执行-------

Redis事务没有隔离级别的概念
所有命令在事务中,并没有直接执行,只有发起执行命令才会执行!exec
Redis单条命令保证原子性,但是事务不保证原子性
Redis事务:

  • 开启事务(multi)
  • 命令入队
  • 执行事务(exec)

正常事务执行

127.0.0.1:6379> MULTI  #开启事务
OK
127.0.0.1:6379> set k1 v1 #命令入队
QUEUED
127.0.0.1:6379> set k1 v2  #命令入队
QUEUED
127.0.0.1:6379> get k1  #命令入队
QUEUED
127.0.0.1:6379> set k2 v2  #命令入队
QUEUED
127.0.0.1:6379> EXEC  #执行事务
1) OK
2) OK
3) "v2"
4) OK

取消执行

127.0.0.1:6379> MULTI  #开启事务
OK
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD  #取消事务
OK
127.0.0.1:6379> get k4
(nil)

编译时异常,命令有错!事务中所有的命令都不会被执行

127.0.0.1:6379> MULTI 
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> GETSET k2
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

运行时异常,如果对列中某条语句存在运行时异常,那么这条语句会执行失败,抛出异常,其他语句会执行失败。

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> INCR k1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
4) "v2"

监控!Watch

悲观锁

  • 认为什么时候都会出现问题,无论做什么都会加锁

乐观锁

  • 认为什么时候都不会出错,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据,

  • 获取version!

  • 更新的时候比较version。

redis监视测试

正常执行成功!

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> WATCH money  #监视money对象
OK
127.0.0.1:6379> MULTI  #事务正常结束,数据期间没有发生变动,这时候就正常执行成功!
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
127.0.0.1:6379> EXEC
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当做redis乐观锁操作
线程一:

127.0.0.1:6379> watch money   #监视money对象
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> EXEC   #执行之前,另一个线程对值进行了修改,就会导致事务执行失败
(nil)

线程二(在线程一事务执行之前执行)

127.0.0.1:6379> set money 1000
OK

如果失败,获取最新值再次执行

127.0.0.1:6379> UNWATCH      #先解锁
OK
127.0.0.1:6379> WATCH money     #获取新值
OK
127.0.0.1:6379> MULTI  #再次执行
OK
127.0.0.1:6379> DECRBY money 10
QUEUED
127.0.0.1:6379> INCRBY out 10
QUEUED
127.0.0.1:6379> EXEC     #比较获取的值与当前值是否相同
1) (integer) 990
2) (integer) 30

Jedis

我们要使用java来操作redis

Jedis是官方推荐的java连接开发工具!使用java来操作redis中间件

测试

  • 导入对应的依赖
<!--导入jedis-->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.3.0</version>
        </dependency>
        <!--导入fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>
package cn.smallcosmos;

import redis.clients.jedis.Jedis;

/**
 * @Date 2020/5/14 下午3:02
 * @Created by zhaoli
 */
public class test {
    public static void main(String[] args) {
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);
        //测试连接
        System.out.println(jedis.ping());

    }
}

常用API跟上面相同
事务操作

package cn.smallcosmos;

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 * @Date 2020/5/14 下午3:02
 * @Created by zhaoli
 */
public class test {
    public static void main(String[] args) {
        //连接redis
        Jedis jedis = new Jedis("127.0.0.1",6379);

        JSONObject jsonObject = new JSONObject();

        jsonObject.put("hello","world");
        jsonObject.put("name","zhaoli");

        String jsonString = jsonObject.toJSONString();
        jedis.flushDB();
        //开启事务
        Transaction multi = jedis.multi();
        try {
            multi.set("user1",jsonString);
            int i  = 1/0;
            multi.set("user2",jsonString);
            multi.exec();  //执行事务
        } catch (Exception e) {
            multi.discard();  //放弃事务
            e.printStackTrace();
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();
        }

    }
}
<think>好的,我现在需要帮助用户学习Redis,从入门到精通。首先,用户已经提供了一些参考资料,包括引用1、2、3,我需要结合这些内容来组织回答。 首先,我应该确定用户的需求是什么。用户想要学习Redis的相关知识,从入门到精通。所以回答需要结构清晰,分阶段指导,并且提供学习资料和相关问题。 根据用户提供的引用,引用2提到Redis是内存数据库/缓存/消息队列,所以需要先介绍Redis的基本概念和应用场景。引用1和引用3涉及环境搭建和配置,以及一些参考资料链接,比如优快云的博客和另一篇博客。这些可以作为学习资料的一部分推荐给用户。 接下来,我需要按照系统级指令的要求,确保数学公式正确使用,但Redis相关的内容可能较少涉及数学公式,所以可能不需要太多这方面的处理。但如果有性能分析或算法部分,可能需要使用行内或独立公式,例如时间复杂度O(n)之类的,可以写成$O(n)$。 然后,用户要求生成相关问题,放在最后。需要确保问题与Redis相关,覆盖入门、进阶、配置优化、应用场景、高可用方案以及高级数据结构等方面。 回答的结构应该分为几个部分:入门阶段、进阶阶段、精通阶段,每个阶段推荐资料和重点内容。同时,结合用户提供的引用,例如环境搭建部分可以参考引用1和引用2,HyperLogLog部分可以参考引用3中的链接。 还需要注意引用标识的正确添加,例如在提到环境搭建时引用[^1][^2],在HyperLogLog部分引用[^3]。 最后,检查是否符合所有系统级指令:正确使用LaTeX(如果有的话),中文回答,生成相关问题,引用标识自然添加。同时,确保回答真实可靠,结构清晰,帮助用户逐步学习。 现在开始组织内容: 入门阶段需要介绍Redis的基本概念、安装配置和基础命令。引用2提到Redis是内存数据库,可以作为缓存和消息队列,这部分需要强调。引用1的环境搭建步骤需要整理,比如复制配置文件到/etc/redis,修改配置并启动。可以推荐官方文档和用户提供的优快云博客作为资料。 进阶阶段包括持久化机制、主从复制、哨兵模式、集群部署,以及数据类型的使用场景。需要结合引用中的信息,比如环境配置可能涉及持久化的设置,引用3中的HyperLogLog属于高级数据结构,可以放在进阶或精通阶段。 精通阶段涉及源码分析、性能调优、高可用方案(如Redis Cluster)、高级数据结构(HyperLogLog、Streams)。引用3中的HyperLogLog部分可以作为参考资料。 相关问题需要涵盖各个阶段的内容,例如如何选择数据结构、持久化机制的区别、集群部署步骤、性能优化方法、高可用方案比较,以及HyperLogLog的应用,这些问题可以帮助用户深入学习。 最后,确保所有引用正确对应,例如引用对应环境配置,引用对应Redis定义,引用[^3]对应HyperLogLog的参考链接。</think>### Redis学习路径:从入门到精通 #### 一、**入门阶段** 1. **核心概念** Redis是一个基于内存的键值存储系统,支持多种数据结构(字符串、哈希、列表等),常用于缓存、消息队列和实时数据分析。 关键术语:键值对、持久化(RDB/AOF)、单线程模型、数据类型(如`String`, `Hash`, `List`)。 2. **环境搭建** - **安装步骤**: ```bash # 下载源码编译安装 wget http://download.redis.io/releases/redis-6.2.6.tar.gz tar xzf redis-6.2.6.tar.gz cd redis-6.2.6 make make install ``` - **配置启动**: 复制配置文件到`/etc/redis`目录,修改`redis.conf`(如设置`daemonize yes`后台运行),启动命令:`redis-server /etc/redis/redis.conf`[^1]。 3. **基础操作** 学习`SET/GET`命令、数据类型操作(如`HSET/HGET`)、过期时间设置(`EXPIRE`)等。 **推荐资料**: - [Redis官方文档](https://redis.io/documentation) - [《Redis设计与实现》](http://redisbook.com/)(入门章节) --- #### 二、**进阶阶段** 1. **核心机制** - **持久化**: RDB(快照)通过`SAVE`或`BGSAVE`生成数据快照;AOF(追加日志)记录写操作,提供更高数据安全性。 配置示例: ```bash # redis.conf appendonly yes # 启用AOF appendfsync everysec # 每秒同步 ``` - **高可用**: 主从复制(`replicaof`配置)、哨兵模式(故障自动切换)、Redis Cluster(分布式分片)。 2. **应用场景** - **缓存**:缓存雪崩/穿透/击穿解决方案(如布隆过滤器)。 - **消息队列**:使用`List`实现队列,或`Streams`支持消费者组。 - **实时统计**:利用`HyperLogLog`估算基数(如UV统计)[^3]。 3. **推荐资料**: - [Redis实战](https://book.douban.com/subject/26612779/)(应用案例详解) - [Redis进阶笔记](https://zhangc233.github.io/2021/05/02/Redis/)(含`HyperLogLog`解析) --- #### 三、**精通阶段** 1. **源码与性能优化** - 阅读Redis源码(如事件循环`ae.c`、数据结构`dict.c`)。 - 性能调优:内存碎片整理(`MEMORY PURGE`)、慢查询分析(`SLOWLOG`)、网络瓶颈排查。 2. **高级特性** - **Lua脚本**:原子化执行复杂操作。 - **模块化扩展**:自定义数据类型(如[RedisJSON](https://redis.io/docs/stack/json/))。 - **流处理**:`Streams`支持消息持久化和消费者组。 3. **推荐资料**: - [Redis源码剖析](https://github.com/huangz1990/redis-3.0-annotated)(注释版源码) - [Redis深度历险](https://book.douban.com/subject/30386804/)(原理与优化) --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值