学习redis时记录的笔记

nosql数据库
技术的分类
1.解决功能性的问题:java,jsp,RDBMS,Tomcat,HTM,Linux,JDBC,SVN
2.解决扩展性的问题:Struts,Spring,SpringMvc,Hibernate,Mybatis
3。解决性能问题:NOSQL,java线程,Handoop,Nginx,MQ,ElasticSearch
客户端-》负载均衡-》多个服务器(文档服务器,数据服务器)-》数据库
为了解决内存,和io问题
我们会长生一个session
解决session的存放问题:
1.存在Cookie中,不安全
2.存放在文件服务器或者数据库里,造成大量的io效率问题
3.session赋值session数据冗余
,节点越多浪费越大
4.读取数据库,完全在内存中,速度快,数据结构简单(nosql)数据库

什么时候用到redis?
1.存储用户的数据,将session数据存到redis中,redis是内存,速度快,不存在cookie中
因为有安全问题,
2.当数据库很大的时候,为了加快IO速度,我们会对数据库进行,水平分隔,垂直分隔,读写分离等
但是这样会破坏代码的业务逻辑,我们可以通过redis作为频繁访问的缓存,下次访问的时候直接访问redis,速度快。、
用专门的数据库,对特定的数据进行处理:文档数据库,缓存数据库,列式数据库
nosql数据库进行缓存操作,减少io的读操作

onsql(not only sql)不仅仅是sql,泛指非关系型数据库。键值对
关系数据库:mysql,oracle
以key-value模式存储,大大增加了数据库的扩展能力
不遵循sql标准
事务的特性:原子性,一致性,隔离性,持久性
不支持ACID,原子性,一致性,隔离性,持久性
远超于sql的性能。
Soql使用的场景:
1)对数据高并发的读写(秒杀商城)
2)海量数据的读写
3)对数据提高可扩展性的
Nosql不使用场景
1)需要事务支持
2)基于sql的结构化查询存储,处理赋值的关系需要即席查询
(用不着sql的和用来sql也不行的情况,请考虑用nosql)


Memcache
很早出现的Nosql数据库,
数据都在内存中,一般不持久化(关机了就不存在了)
支持简单的key-value模式,支持类型单一,只字符串
一般是作为缓存数据库辅助持久化的数据库

Redis
几乎覆盖了Memcache的绝大部分功能
数据都在内存中,支持持久化,主要用作数据备份恢复(存在硬盘中,电脑关机不消失)
除了支持简单的key-value模式,还支持多种数据结构的存储
,比如list,set,hash,zset等
一般是作为缓存数据库辅助持久化的数据库

MongoDB
高性能,开源,模式自由(schema free)的文档型数据库,
数据都在内存中,如果内存不足,把不常用的数据保存到硬盘
虽然是key-value模式。但是对value(尤其是json)提供了丰富的查询功能。
支持二进制数据及大型对象
可以根据数据的提点 替代RDBMS,称为独立的数据库,或者配合RDBMS,存储特点的数据。

存储方式:行式存储,列式存储。

hbase是hadoop项目中的数据库,它用于需要对大量的数据进行随机,实时的读写操作的场景中

图关系型数据库
Neo4j
主要应用:社会关系,公共交通网络,地图及网络拓普
例如好友推荐

Redis 是完全开源的,遵守 BSD 协议,是一个高性能的 key-value 数据库。

Redis 与其他 key - value 缓存产品有以下三个特点:

Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
Redis支持数据的备份,即master-slave模式的数据备份。
性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
Redis与其他key-value存储有什么不同?
Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。
Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。

Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存
,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
安装

windos版本:
下载地址:https://github.com/tporadowski/redis/releases。

Redis 支持 32 位和 64 位。这个需要根据你系统平台的实际情况选择,
这里我们下载 Redis-x64-xxx.zip压缩包到 C 盘,解压后,将文件夹重新命名为 redis。
一般不采用

linux安装:
下载地址:http://redis.io/download,下载最新稳定版本。

Redis是单线程+多路IO复用技术
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)
串行 vs 多线程+锁(memcached) vs 单线程+多路IO复用(Redis)
(与Memcache三点不同: 支持多数据类型,支持持久化,单线程+多路IO复用)

set 添加键值对 (如果对已经有的key,会覆盖)
get取值

---------linux安装redis-------
1)去官网下载文件
2)上传到linux /opt目录
3)执行make编译
drwxr-xr-x. 9 root root 4096 7月 18 2020 vmware-tools-distrib
[root@anyu100 opt]# tar -zvxf redis-6.2.1.tar.gz
redis-6.2.1/

make[1]: 离开目录“/opt/redis-6.2.1/src”
4)执行make install 安装
[root@anyu100 redis-6.2.1]# make install
cd src && make install
make[1]: 进入目录“/opt/redis-6.2.1/src”
CC Makefile.dep
make[1]: 离开目录“/opt/redis-6.2.1/src”
make[1]: 进入目录“/opt/redis-6.2.1/src”

Hint: It’s a good idea to run ‘make test’ 😉
5)安装目录:/usr/local/bin
查看默认安装目录:
redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何
redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲
redis-check-dump:修复有问题的dump.rdb文件
redis-sentinel:Redis集群使用
redis-server:Redis服务器启动命令
redis-cli:客户端,操作入口
--------开启redis-----
1)前台启动
redis-server
[root@anyu100 bin]# redis-server
8892:C 07 Jan 2022 19:09:06.199 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
8892:C 07 Jan 2022 19:09:06.199 # Redis version=6.2.1, bits=64, commit=00000000, modified=0, pid=8892, just started
不建议使用,关闭界面后redis关闭
2)后台启动
进入 /opt/redis-6.2.1 目录复制文件redis-conf 到/opt/redis(建议放在)/usr/local目录下
修改配置文件redis-conf
将daemonize no改成yes
进入/usr/local/bin目录使用命令
redis-server /opt/redis/redis.conf
查看进程
[root@anyu100 bin]# ps -ef |grep redis 在 本机上
anyu 8423 8373 0 11:40 pts/0 00:00:00 redis-server *:6379
root 12499 9165 0 12:16 pts/0 00:00:00 grep --color=auto redis
使用命令:redis-cli

远程连接
[root@anyu100 bin]# ps -ef|grep redis
root 3646 1 0 12:36 ? 00:00:00 redis-server 127.0.0.1:6379
root 3652 3263 0 12:36 pts/0 00:00:00 grep --color=auto redis

[root@anyu100 bin]# redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
表示连接成功

关闭:
1)使用客户端连接命令
redis -cli
后 shutdown
127.0.0.1:6379> shutdown
not connected>
not connected> exit
[root@anyu100 bin]# ps -ef|grep redis
root 4015 3263 0 12:40 pts/0 00:00:00 grep --color=auto redis
2)单实例关闭:redis-cli shutdown
3)杀死进程 kill -9 pid

常用命令
keys *查看当前库所有key (匹配:keys *1)
exists key判断某个key是否存在
type key 查看你的key是什么类型
del key 删除指定的key数据
unlink key 根据value选择非阻塞删除
仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。
expire key 10 10秒钟:为给定的key设置过期时间
ttl key 查看还有多少秒过期,-1表示永不过期,-2表示已过期

select命令切换数据库 redis默认有16个库
dbsize查看当前数据库的key的数量
flushdb清空当前库
flushall通杀全部库

常用命令

字符串string****
set 添加键值对

*NX:当数据库中key不存在时,可以将key-value添加数据库
*XX:当数据库中key存在时,可以将key-value添加数据库,与NX参数互斥
*EX:key的超时秒数
*PX:key的超时毫秒数,与EX互斥

get 查询对应键值
append 将给定的 追加到原值的末尾 成功后显示长度
strlen 获得值的长度
setnx 只有在 key 不存在时 设置 key 的值

incr
将 key 中储存的数字值增1
只能对数字值操作,如果为空,新增值为1
decr
将 key 中储存的数字值减1
只能对数字值操作,如果为空,新增值为-1
incrby / decrby <步长>将 key 中储存的数字值增减。自定义步长。

mset …
同时设置一个或多个 key-value对
mget …
同时获取一个或多个 value
msetnx …
同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
redis是原子性的,只要有一个失败就都失败。
getrange <起始位置><结束位置>
获得值的范围,类似java中的substring,前包,后包
setrange <起始位置>
用 覆写所储存的字符串值,从<起始位置>开始(索引从0开始)。

setex <过期时间>
设置键值的同时,设置过期时间,单位秒。
getset
以新换旧,设置了新值同时获得旧值。

String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,
内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配.

内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。
当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。
需要注意的是字符串最大长度为512M

redis列表list*
单键多值
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。

常用命令
lpush/rpush … 从左边/右边插入一个或多个值。
lpop/rpop 从左边/右边吐出一个值。值在键在,值光键亡。
头插法和尾插法
rpoplpush 从列表右边吐出一个值,插到列表左边。

lrange
按照索引下标获得元素(从左到右)
lrange mylist 0 -1 0左边第一个,-1右边第一个,(0-1表示获取所有)
lindex 按照索引下标获得元素(从左到右)
llen 获得列表长度

linsert before 在的前面插入插入值
before之前 after之后
lrem 从左边删除n个value(从左到右)
lset将列表key下标为index的值替换成value

List的数据结构为快速链表quickList。
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。
它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
当数据量比较多的时候才会改成quicklist。
因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,
结构上还需要两个额外的指针prev和next。

Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。
这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

88redis集合 set****
Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据
,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,
这个也是list所不能提供的。
Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。
一个算法,随着数据的增加,执行时间的长短,如果是O(1),数据增加,查找数据的时间不变

sadd …
将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
smembers 取出该集合的所有值。
sismember 判断集合是否为含有该值,有1,没有0
scard返回该集合的元素个数。
srem … 删除集合中的某个元素。
spop 随机从该集合中吐出一个值。
srandmember 随机从该集合中取出n个值。不会从集合中删除 。
smove value把集合中一个值从一个集合移动到另一个集合
sinter 返回两个集合的交集元素。
sunion 返回两个集合的并集元素。
sdiff 返回两个集合的差集元素(key1中的,不包含key2中的)

3.4.3. 数据结构
Set数据结构是dict字典,字典是用哈希表实现的。
Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,
它的内部也使用hash结构,所有的value都指向同一个内部值。

redis 哈希hash*******

Redis hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
类似Java里面的Map<String,Object>

hset 给集合中的 键赋值
hget 从集合取出 value
hmset … 批量设置hash的值
hexists查看哈希表 key 中,给定域 field 是否存在。
hkeys 列出该hash集合的所有field
hvals 列出该hash集合的所有value
hincrby 为哈希表 key 中的域 field 的值加上增量 1 -1
hsetnx 将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .

zrank 返回该值在集合中的排名,从0开始。

数据结构
Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。

有序集合*****
Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。
不同之处是有序集合的每个成员都关联了一个评分(score),这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。
集合的成员是唯一的,但是评分可以是重复了 。
因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。
访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
zadd …
将一个或多个 member 元素及其 score 值加入到有序集 key 当中。
zrange [WITHSCORES]
返回有序集 key 中,下标在 之间的元素
带WITHSCORES,可以让分数一起和值返回到结果集。
zrangebyscore key minmax [withscores] [limit offset count]
返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员
。有序集成员按 score 值递增(从小到大)次序排列。
zrevrangebyscore key maxmin [withscores] [limit offset count]
同上,改为从大到小排列。
zincrby 为元素的score加上增量
zrem 删除该集合下,指定值的元素
zcount 统计该集合,分数区间内的元素个数
SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,
可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,
可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
zset底层使用了两个数据结构
(1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
(2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

三次握手,四次挥手****

tcp:三次握手,四次挥手
握手之前主动打开连接的客户端结束CLOSED阶段,被动打开的服务器端也结束CLOSED阶段,并进入LISTEN阶段。随后开始“三次握手”:

(1)首先客户端向服务器端发送一段TCP报文,其中:

标记位为SYN,表示“请求建立新连接”;
序号为Seq=X(X一般为1);
随后客户端进入SYN-SENT阶段。
(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文,其中:

标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);
序号为Seq=y;
确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。
(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中:

标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);
序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;
确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;
随后客户端进入ESTABLISHED阶段。
服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。

在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。

此后客户端和服务器端进行正常的数据传输。这就是“三次握手”的过程。

(1)首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:

标记位为FIN,表示“请求释放连接“;
序号为Seq=U;
随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。
注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。

(2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:

标记位为ACK,表示“接收到客户端发送的释放连接的请求”;
序号为Seq=V;
确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;
随后服务器端开始准备释放服务器端到客户端方向上的连接。
客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段

前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了

(3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:

标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。
序号为Seq=W;
确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。
随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。

(4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:

标记位为ACK,表示“接收到服务器准备好释放连接的信号”。
序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。
确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。
随后客户端开始在TIME-WAIT阶段等待2MSL

为什么要客户端要等待2MSL呢?见后文。

服务器端收到从客户端发出的TCP报文之后结束LAST-ACK阶段,进入CLOSED阶段。由此正式确认关闭服务器端到客户端方向上的连接。

客户端等待完2MSL之后,结束TIME-WAIT阶段,进入CLOSED阶段,由此完成“四次挥手”。

后“两次挥手”既让客户端知道了服务器端准备好释放连接了,也让服务器端知道了客户端了解了自己准备好释放连接了。
于是,可以确认关闭服务器端到客户端方向上的连接了,由此完成“四次挥手”。

与“三次挥手”一样,在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的
,这样做保证了TCP报文传输的连贯性,一旦出现某一方发出的TCP报文丢失,便无法继续"挥手",以此确保了"四次挥手"的顺利完成。

****读懂redis.conf配置
主要:
1)# IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES

JUST COMMENT OUT THE FOLLOWING LINE.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

#bind 127.0.0.1 -::1

默认情况bind=127.0.0.1只能接受本机的访问请求
不写的情况下,无限制接受任何ip地址的访问
2)
如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应
将本机访问保护模式设置no

even if no authentication is configured, nor a specific set of interfaces

are explicitly listed using the “bind” directive.

protected-mode no
3)端口号,默认 6379

**发布和订阅
5. Redis的发布和订阅
5.1. 什么是发布和订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。
1、 打开一个客户端订阅channel1
SUBSCRIBE channel1
打开另一个客户端,给channel1发布消息hello
publish channel1 hello
返回的1是订阅者数量
3、打开第一个客户端可以看到发送的消息
注:发布的消息没有持久化,如果在订阅的客户端收不到hello,只能收到订阅后发布的消息

***8新数据类型
6.1. Bitmaps
6.1.1. 简介
现代计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位, 例如“abc”字符串是由3个字节组成,
但实际在计算机存储时将其用二进制表示, “abc”分别对应的ASCII码分别是97、 98、 99, 对应的二进制分别是01100001、
01100010和01100011
合理地使用操作位能够有效地提高内存使用率和开发效率。
Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:
(1) Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。
(2) Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。
1、setbit
(1)格式
setbit设置Bitmaps中某个偏移量的值(0或1)

*offset:偏移量从0开始

(2)实例
每个独立用户是否访问过网站存放在Bitmaps中, 将访问的用户记做1, 没有访问的用户记做0, 用偏移量作为用户的id。
设置键的第offset个位的值(从0算起) , 假设现在有20个用户,userid=1, 6, 11, 15, 19的用户对网站进行了访问, 那么当前Bitmaps初始化结果如图

unique:users:20201106代表2020-11-06这天的独立访问用户的Bitmaps

注:
很多应用的用户id以一个指定数字(例如10000) 开头, 直接将用户id和Bitmaps的偏移量对应势必会造成一定的浪费, 通常的做法是每次做setbit操作时将用户id减去这个指定数字。
在第一次初始化Bitmaps时, 假如偏移量非常大, 那么整个初始化过程执行会比较慢, 可能会造成Redis的阻塞。

2、getbit
(1)格式
getbit获取Bitmaps中某个偏移量的值

获取键的第offset位的值(从0开始算)

(2)实例
获取id=8的用户是否在2020-11-06这天访问过, 返回0说明没有访问过:

注:因为100根本不存在,所以也是返回0

3、bitcount
统计字符串被设置为1的bit数。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,start、end 是指bit组的字节的下标数,二者皆包含。
(1)格式
bitcount[start end] 统计字符串从start字节到end字节比特值为1的数量

(2)实例
计算2022-11-06这天的独立访问用户数量

start和end代表起始和结束字节数, 下面操作计算用户id在第1个字节到第3个字节之间的独立访问用户数, 对应的用户id是11, 15, 19。

举例: K1 【01000001 01000000 00000000 00100001】,对应【0,1,2,3】
bitcount K1 1 2 : 统计下标1、2字节组中bit=1的个数,即01000000 00000000
–》bitcount K1 1 2   --》1

bitcount K1 1 3 : 统计下标1、2字节组中bit=1的个数,即01000000 00000000 00100001
–》bitcount K1 1 3  --》3

bitcount K1 0 -2 : 统计下标0到下标倒数第2,字节组中bit=1的个数,即01000001 01000000 00000000
–》bitcount K1 0 -2  --》3

注意:redis的setbit设置或清除的是bit位置,而bitcount计算的是byte位置。

4、bitop
(1)格式
bitop and(or/not/xor) [key…]

bitop是一个复合操作, 它可以做多个Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(异或) 操作并将结果保存在destkey中。

(2)实例
2020-11-04 日访问网站的userid=1,2,5,9。
setbit unique:users:20201104 1 1
setbit unique:users:20201104 2 1
setbit unique:users:20201104 5 1
setbit unique:users:20201104 9 1

2020-11-03 日访问网站的userid=0,1,4,9。
setbit unique:users:20201103 0 1
setbit unique:users:20201103 1 1
setbit unique:users:20201103 4 1
setbit unique:users:20201103 9 1

计算出两天都访问过网站的用户数量
bitop and unique:users:and:20201104_03
unique:users:20201103unique:users:20201104
6.1.3. Bitmaps与set对比
假设网站有1亿用户, 每天独立访问的用户有5千万, 如果每天用集合类型和Bitmaps分别存储活跃用户可以得到表
set和Bitmaps存储一天活跃用户对比
数据
类型 每个用户id占用空间 需要存储的用户量 全部内存量
集合
类型 64位 50000000 64位50000000 = 400MB
Bitmaps 1位 100000000 1位
100000000 = 12.5MB

很明显, 这种情况下使用Bitmaps能节省很多的内存空间, 尤其是随着时间推移节省的内存还是非常可观的
set和Bitmaps存储独立用户空间对比
数据类型 一天 一个月 一年
集合类型 400MB 12GB 144GB
Bitmaps 12.5MB 375MB 4.5GB

但Bitmaps并不是万金油, 假如该网站每天的独立访问用户很少, 例如只有10万(大量的僵尸用户) ,
那么两者的对比如下表所示, 很显然, 这时候使用Bitmaps就不太合适了, 因为基本上大部分位都是0。

***************** HyperLogLog************
用于计算访问量,计算基数,去除重复,优点是占用内存很少,计算大量数据。
在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现。

但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?
这种求集合中不重复元素个数的问题称为基数问题。
解决基数问题有很多种方案:
(1)数据存储在MySQL表中,使用distinct count计算不重复个数
(2)使用Redis提供的hash、set、bitmaps等数据结构来处理
以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。
能否能够降低一定的精度来平衡存储空间?Redis推出了HyperLogLog
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,
计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时
,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,
返回输入的各个元素。

什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

1、pfadd
(1)格式
pfadd < element> [element …] 添加指定元素到 HyperLogLog 中
(2)
将所有元素添加到指定HyperLogLog数据结构中。如果执行命令后HLL估计的近似基数发生变化,则返回1,否则返回0。

2、pfcount
(1)格式
pfcount [key …] 计算HLL的近似基数,可以计算多个HLL,比如用HLL存储每天的UV
,计算一周的UV可以使用7天的UV合并计算即可
3、pfmerge
(1)格式
pfmerge [sourcekey …] 将一个或多个HLL合并后的结果存储在另一个HLL中,
比如每月活跃用户可以使用每天的活跃用户来合并计算可得

Geospatial**
Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

6.3.2. 命令
1、geoadd
(1)格式
geoadd< longitude> [longitude latitude member…] 添加地理位置(经度,纬度,名称)
(2)实例
geoadd china:city 121.47 31.23 shanghai
geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 beijing
两极无法直接添加,一般会下载城市数据,直接通过 Java 程序一次性导入。
有效的经度从 -180 度到 180 度。有效的纬度从 -85.05112878 度到 85.05112878 度。
当坐标位置超出指定范围时,该命令将会返回一个错误。
已经添加的数据,是无法再次往里面添加的。
2、geopos

(1)格式
geopos [member…] 获得指定地区的坐标值
3、geodist
(1)格式
geodist [m|km|ft|mi ] 获取两个位置之间的直线距离
(2)
单位:
m 表示单位为米[默认值]。
km 表示单位为千米。
mi 表示单位为英里。
ft 表示单位为英尺。
如果用户没有显式地指定单位参数, 那么 GEODIST 默认使用米作为单位
4、georadius
(1)格式
georadius< longitude>radius m|km|ft|mi 以给定的经纬度为中心,找出某一半径内的元素
经度 纬度 距离 单位

java程序连接redis
1)关闭防火墙,或开放端口,不然报错,连接超时
Caused by: java.net.SocketTimeoutException: connect timed out
防火墙关闭后:
Connected to the target VM, address: ‘127.0.0.1:1411’, transport: ‘socket’
PONG
表示连接成功
查看防火墙状态:
firewall-cmd --state
running
打开端口:
firewall-cmd --permanent --add-port=6379/tcp
success

端口已经打开:

[root@anyu100 ~]# firewall-cmd --list-ports
3306/tcp 6379/tcp 8080/tcp
连接成功:
Disconnected from the target VM, address: ‘127.0.0.1:1513’, transport: ‘socket’
PONG
2)redis.conf中注释掉bind 127.0.0.1 ,然后 protected-mode no
取消只能本机登录,关掉保护机制

jedis需要的jar包:

redis.clients
jedis
3.2.0

测试:
Jedis jedis = new Jedis(“192.168.137.3”,6379);

String pong = jedis.ping();
System.out.println(“连接成功:”+pong);
jedis.close();

new一个jedis对象,然后进行操作。
操作:
对于key
jedis.set(“k1”, “v1”);//setkey
Set keys = jedis.keys(“*”);//keys *
jedis.exists(“k1”)//判断key是否存在
keys.size()//keys的大小
jedis.ttl(“k1”)//查看key的过期时间
jedis.get(“k1”))//获取key的值
对于String
jedis.mset(“str1”,“v1”,“str2”,“v2”,“str3”,“v3”);//批量插入key
jedis.flushDB();//清空当前库
jedis.flushAll();//通杀所有库
jedis.del(“key1”);//删除key
Boolean key1 = jedis.exists(“key1”);//判断key是否存在
String key11 = jedis.type(“key1”);//判断key的类型
jedis.unlink(“key1”,“key2”);// 根据value选择非阻塞删除
jedis.expire(“key1”,2);//设置key的过期时间
jedis.select(3);//切换库


String
jedis.setnx(“key1”,“v3”); //智能设置不存在的key
jedis.append(“key1”,“append v3”);//追加
jedis.strlen(“key1”)//查看key的长度
jedis.incr(“key2”);//key的值加1
jedis.decr(“key2”);//key的值减1
jedis.incrBy(“key2”,10);//key的值增加自定义步长
jedis.decrBy(“key2”,10);//key的值减少自定义步长
mset …
同时设置一个或多个 key-value对
jedis.mset(“key4”,“10”,“key5”,“6”);
mget …
同时获取一个或多个 value
System.out.println(jedis.mget(“key4”,“key5”));
msetnx …
jedis.msetnx(“key4”,“10”,“key5”,“6”);
jedis.set(“key6”,“i am key5”);
System.out.println( jedis.getrange(“key6”,1,5));//获取指定长度的value
setrange <起始位置>
用 覆写所储存的字符串值,从<起始位置>开始(索引从0开始)。

setex <过期时间>
设置键值的同时,设置过期时间,单位秒。
getset
以新换旧,设置了新值同时获得旧值。

list**
双向链表:
-1右边第一个,(0-1表示获取所有)
jedis.lpush(“list1”,“list one”,“list two”,“list three”);
List list1 = jedis.lrange(“list1”, 0, -1);
for(String list:list1){
System.out.println(list);
}
结果:
list three
list two
list one
链表从左往右插入list one在第三
jedis.rpush(“list1”,“list one”,“list two”,“list three”);
List list1 = jedis.lrange(“list1”, 0, -1);
for(String list:list1){
System.out.println(list);
}
从左往右插 list one排第一
lrange 从左往右读,根据索引值
jedis.lpop(“list1”);//从左吐出一个值。
jedis.rpop(“list1”);//从右吐出一个值。
String lindex = jedis.lindex(“list1”, 0);//根据索引值下标获得元素
Long llen = jedis.llen(“list1”);//获得list的长度
jedis.lrem(“list1”,2,“list one”);//
lrem
* 根据参数 count 的值,移除列表中与参数 value 相等的元素。
count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
count = 0 : 移除表中所有与 value 相等的值。
List list4 = jedis.lrange(“list1”, 0, -1);//将列表key下标为index的值替换成value
底层是双向链表,数据结构是快速链表,元素较少的时候是压缩链表。
***set是一个无序集合
jedis.sadd(“set1”,“set one”,“set two”,“set three”);//添加,将一个或多个 member 元素加入到集合 key 中,已经存在的 member 元素将被忽略
Set smembers = jedis.smembers(“set1”);//取出该集合的所有值。
for(String smember:smembers){
System.out.println(smember);
}

   Boolean aBoolean = jedis.sismember("set1", "set one");
    System.out.println(aBoolean);

//sismember 判断集合是否为含有该值,有1,没有0
System.out.println(jedis.scard(“set1”));//scard返回该集合的元素个数。
Set smembers2 = jedis.smembers(“set1”);//
srem … 删除集合中的某个元素。
jedis.spop(“set1”);//spop 随机从该集合中吐出一个值。
srandmember 随机从该集合中取出n个值。不会从集合中删除 。
smove value把集合中一个值从一个集合移动到另一个集合
sinter 返回两个集合的交集元素。
sunion 返回两个集合的并集元素。
sdiff 返回两个集合的差集元素(key1中的,不包含key2中的)

*hash
Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。
当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。
Redis hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
类似Java里面的Map<String,Object>
jedis.hset(“user”,“name”,“zhangsan”);//键赋值
String hget = jedis.hget(“user”, “name”);
//批量设值
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put(“name”,“lisi”);
hashMap.put(“age”,“11”);
jedis.hmset(“user2”,hashMap);
String hget1 = jedis.hget(“user2”, “name”);//批量设值
System.out.println(hget1);

    Boolean hexists = jedis.hexists("user", "name");
    System.out.println(hexists);//判断哈希表中,filed是否存在
hkeys <key>列出该hash集合的所有field     System.out.println(jedis.hkeys("user2"));

hvals 列出该hash集合的所有value
hincrby 为哈希表 key 中的域 field 的值加上增量 1 -1
hsetnx 将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .

*****有序set
加入了一个叫评分的东西
Jedis jedis = new Jedis(“192.168.200.130”,6379);
jedis.flushDB();
jedis.zadd(“zset1”,1,“zset one”);//给有序集合添加成员
//zrange [WITHSCORES]
//返回有序集 key 中,下标在 之间的元素
Set zset1 = jedis.zrange(“zset1”, 0, 100);//读取分数0-100之间在zset1的member

    for (String zest2:zset1){
        System.out.println(zest2);
    }

zrevrangebyscore key maxmin [withscores] [limit offset count]
同上,改为从大到小排列。
zincrby 为元素的score加上增量
zrem 删除该集合下,指定值的元素
zcount 统计该集合,分数区间内的元素个数
zrank 返回该值在集合中的排名,从0开始。
zset底层使用了两个数据结构
(1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
(2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。


String用于处理简单的数据
hash适合存储对象
Zset适合做排行榜

**整合springboot
1、 在pom.xml文件中引入redis相关依赖

org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 2.6.0 2、 application.properties配置redis配置

#Redis服务器地址
spring.redis.host=192.168.140.136
#Redis服务器连接端口
spring.redis.port=6379
#Redis数据库索引(默认为0)
spring.redis.database= 0
#连接超时时间(毫秒)
spring.redis.timeout=1800000
#连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=20
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=5
#连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0

3、 添加redis配置类

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    jackson2JsonRedisSerializer.setObjectMapper(om);
    template.setConnectionFactory(factory);

//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisSerializer<String> redisSerializer = new StringRedisSerializer();
    Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}

4、测试一下
RedisTestController中添加测试方法
@RestController
@RequestMapping(“/redisTest”)
public class RedisTestController {
@Autowired
private RedisTemplate redisTemplate;

@GetMapping
public String testRedis() {
    //设置值到redis
    redisTemplate.opsForValue().set("name","lucy");
    //从redis获取值
    String name = (String)redisTemplate.opsForValue().get("name");
    return name;
}

}

面试:
Redis是什么?
Redis 是 C 语言开发的一个开源的(遵从 BSD 协议)高性能非关系型(NoSQL)的(key-value)键值对数据库。可以用作数据库、缓存、消息中间件等。
2.2 Redis 的存储结构有哪些?
String,字符串,是 redis 的最基本的类型,一个 key 对应一个 value。是二进制安全的,最大能存储 512MB。
String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配.

Hash,散列,是一个键值(key=>value)对集合。string 类型的 field 和value 的映射表,特别适合用于存储对象。每个 hash 可以存储 232 -1 键值对(40 多亿)
Hash类型对应的数据结构是两种:ziplist(压缩列表),hashtable(哈希表)。当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。

List,列表,是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列边或者尾部(右边)。最多可存储 232 - 1 元素(4294967295, 每个列表可存储 40 亿)
它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。List的数据结构为快速链表quickList。
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。
它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
当数据量比较多的时候才会改成quicklist。
因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。

Set,集合, 是 string 类型的无序集合,最大的成员数为 232 -1(4294967295, 每个集合可存储 40 多亿个成员)。

Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。
Set数据结构是dict字典,字典是用哈希表实现的。
Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

Sorted set,有序集合,和 set 一样也是 string 类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个 double 类型的分数。
SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java的数据结构Map<String, Double>,可以给每一个元素value赋予一个权重score,另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
zset底层使用了两个数据结构
(1)hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
(2)跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。

redis 正是通过分数来为集合中的成员进行从小到大的排序。zset 的成员是唯一的,但分数(score)却可以重复。

2.3 Redis 的优点?
1 因为是纯内存操作,Redis 的性能非常出色,每秒可以处理超过 10 万次读写操作,是已知性能最快的 Key-Value 数据库。Redis 支持事务 、持久化
2、单线程操作,避免了频繁的上下文切换。
3、采用了非阻塞 I/O 多路复用机制。I/O 多路复用就是只有单个线程,通过跟踪每个 I/O 流的状态,来管理多个 I/O 流。

2.4 为什么要用 Redis
高性能:
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。将该用户访问的数据存在数缓存中,
这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快。如果数据库中的对应数据改变的之后,
同步改变缓存中相应的数据即可!

高并发:
直接操作缓存能够承受的请求是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,
这样用户的一部分请求会直接到缓存这里而不用经过数据库。

2.5 redis的持久化
Redis 提供了两种持久化的方式,分别是 RDB(Redis DataBase)和 AOF(Append Only File)。
RDB,简而言之,就是在不同的时间点,将 redis 存储的数据生成快照并存储到磁盘等介质上。
AOF,则是换了一个角度来实现持久化,那就是将 redis 执行过的所有写指令记录下来,在下次 redis 重新启动时,只要把这些写指令从前到后再重复执行一遍,
就可以实现数据恢复了。
RDB 和 AOF 两种方式也可以同时使用,在这种情况下,如果 redis 重启的话,则会优先采用 AOF 方式来进行数据恢复,这是因为 AOF 方式的数据恢复完整度更高。
2.6 Redis 的缺点
2.6.1 缓存和数据库双写一致性问题
一致性的问题很常见,因为加入了缓存之后,请求是先从 redis中查询,如果 redis 中存在数据就不会走数据库了,
如果不能保证缓存跟数据库的一致性就会导致请求获取到的数据不是最新的数据。
解决方案:
1、编写删除缓存的接口,在更新数据库的同时,调用删除缓存
的接口删除缓存中的数据。这么做会有耦合高以及调用接口失败的情况。
2、消息队列:ActiveMQ,消息通知。
2.6.2缓存的并发竞争问题
并发竞争,指的是同时有多个子系统去 set 同一个 key 值。
解决方案:
1、最简单的方式就是准备一个分布式锁,大家去抢锁,抢到
锁就做 set 操作即可
2.6.3缓存雪崩问题
缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波
请求,结果请求都怼到数据库上,从而导致数据库连接异常。
解决方案:
1、给缓存的失效时间,加上一个随机值,避免集体失效。
2、使用互斥锁,但是该方案吞吐量明显下降了。
3、搭建 redis 集群。
2.6.4缓存击穿问题
缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。
解决方案:
1、利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,
再去请求数据库。没得到锁,则休眠一段时间重试
2、采用异步更新策略,无论 key 是否取到值,都直接返回,
value 值中维护一个缓存失效时间,缓存如果过期,异步起一个线程
去读数据库,更新缓存。

2.7 Redis 集群
2.7.1主从复制
主从复制原理
从服务器连接主服务器,发送 SYNC 命令。主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成RDB 文件并使用缓冲区记录此后执行的所有写命令。
主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令。从服务器收到快照文件后丢弃所有旧数据,载入收到的快照。
主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令。
从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令(从服务器初始化完成)。
主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令(从服务器初始化完成后的操作)。

优点
支持主从复制,主机会自动将数据同步到从机,可以进行读写分离。为了分载 Master 的读操作压力,Slave 服务器可以为客户端提供只读操作的服务,
写服务仍然必须由 Master 来完成Slave同样可以接受其它 Slaves 的连接和同步请求,这样可以有效的分载 Master 的同步压力Master Server 是以非阻塞的方式为
Slaves 提供服务。所以在Master-Slave 同步期间,客户端仍然可以提交查询或修改请求。Slave Server 同样是以非阻塞的方式完成数据同步。
在同步期间,如果有客户端提交查询请求,Redis 则返回同步之前的数据。

缺点
Redis 不具备自动容错和恢复功能,主机从机的宕机都会导致前端部分读写请求失败,需要等待机器重启或者手动切换前端的 IP才能恢复。
主机宕机,宕机前有部分数据未能及时同步到从机,切换 IP后还会引入数据不一致的问题,降低了系统的可用性。Redis 较难支持在线扩容,
在集群容量达到上限时在线扩容会变得很复杂。

2.7.2 哨兵模式
当主服务器中断服务后,可以将一个从服务器升级为主服务器,以便继续提供服务,但是这个过程需要人工手动来操作。
为此,Redis2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能。哨兵的作用就是监控 Redis 系统的运行状况,它的功能包括以下两个。
1、监控主服务器和从服务器是否正常运行。
2、主服务器出现故障时自动将从服务器转换为主服务器。

哨兵的工作方式
每个 Sentinel (哨兵)进程以每秒钟一次的频率向整个集群中的 Master 主服务器,Slave 从务器以及其他 Sentinel(哨兵)进程发送一个 PING 命令。
如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值,
则这个实例会被Sentinel(哨兵)进程标记为主观下线(SDOWN)。如果一个 Master 主服务器被标记为主观下线(SDOWN),
则正在监视这个 Master 主服务器的所有 Sentinel(哨兵)进程要以每秒一次的频率确认 Master 主服务器的确进入了主观下线状态。
当有足够数量的 Sentinel(哨兵)进程(大于等于配置文件指定的值)在指定的时间范围内确认 Master 主服务器进入了主观下线状态(SDOWN),
则 Master 主服务器会被标记为客观下线(ODOWN)。
在一般情况下, 每个 Sentinel(哨兵)进程会以每 10 秒一169 / 196次的频率向集群中的所有Master主服务器,Slave从服务器发送 INFO命令。
当 Master 主服务器被 Sentinel(哨兵)进程标记为客观下线(ODOWN)时,Sentinel(哨兵)进程向下线的 Master 主服务器的所有 Slave
从服务器发送 INFO 命令的频率会从 10 秒一次改为每秒一次。若没有足够数量的 Sentinel(哨兵)进程同意 Master 主服务器下线, Master
主服务器的客观下线状态就会被移除。若 Master主服务器重新向 Sentinel (哨兵)进程发送 PING 命令返回有效回复,Master 主服务器的主观下线状态就会被移除。
优点
哨兵模式是基于主从模式的,所有主从的优点,哨兵模式都具有。主从可以自动切换,系统更健壮,可用性更高。

sentinel,哨兵主要有以下功能:
1、集群监控:负责监控redis master和slave进程是否正常工作
2、消息通知:如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
3、故障转移:如果master node挂掉了,会自动转移到新的master地址。
4、配置中心:如果故障转移发送了,通知client客户端新的master地址。

哨兵用于实现redis’集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。
故障转移时,判断一个master node是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举
即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的。
哨兵通常需要三个实例,来保证自己的健壮性。
哨兵加redis主从的部署架构,是不保证数据零丢失的,只能保证redis集群的高可用性。
对于哨兵+redis主从这种复杂的部署架构,尽量在测试环境和生成环境都进行充足的测试和演练。

缺点
Redis 较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂。
2.7.3 Redis-Cluster 集群

redis cluster是一种服务端sharding技术,采用slot(槽)的概念,一共分为16384个槽,将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行。
方案说明:
通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)的数据,默认分配了16384个槽位。
每片数据分片会存储在多个互为主从的节点上
数据写入先写主节点,再同步到从节点(支持配置为阻塞同步)
同一分片多个节点间的数据不保持强一致性。
读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点
扩容时需要吧旧节点的数据迁移一部分到新节点。
在redis cluster 架构下,每个redis要开放两个端口,比如一个是6379,另外一个就是加1W的端口号,比如16389
16379端口号是用来进行节点间通信的,也就是cluster bus通信,用来进行故障检测,配置更新,故障转移授权。cluster bus用了另外一种二进制的协议,
gossip协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。
优点:无中心架构,支持动态扩容,对业务透明
具备sentinel的监控和自动failover(故障转移)能力
客户端不需要连接集群的所有节点,连接集群的任何一个可用节点即可。
高性能,客户端直连redis服务,免去了代理的损耗。
缺点:运维复杂,数据迁移需要人工干预。
只能使用0号库
不支持批量操作
分布式逻辑和模块耦合。

主从复制的核心原理
通过执行slaveof命令或者设置slaveof选项,让一个服务器去复制另外一个服务器的数据。主数据库可以进行读写操作,当写操作导致数据变化时,
会自动将数据同步给从服务器,而从数据库一般是只读的。并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能用有一个主数据库。
全量复制:
1、主节点通过bgsave命令fork子进程进行rdb持久化,该过程非常消耗cpu,内存,硬盘io
2、主节点通过网络将rdb文件发送给从节点,对主节点的带框都会带来很大的消耗
3、从节点情况清空老数据,载入新rdb文件的过程是阻塞的,无法响应客户端的命令;如果从节点执行不grewiteof,也会带来额外的消耗。
部分复制:
1、复制偏移量:执行复制的双方,主从节点,分别会维护一个复制偏移量offset。
2、复制积压缓冲区:主节点内部维护了一个固定长度的,先进先出fifo队列,作为复制积压缓冲区,当从节点offset的差距过大超过缓冲区,将无法部分复制。
3、服务器运行:每个redis节点,都有其运行id,运行id由节点在启动时自动生成,主节点会将自己的运行id发送给从节点,从节点就会将主节点的id存起来,从节点redis断开重连
的时候,据说根据id判断同步的进度。

redis 的哨兵模式基本已经可以实现高可用,读写分离,但是在这种模式下每台 redis 服务器都存储相同的数据,很浪费内存,所以在redis3.0 上加入了 cluster 模式,
实现的 redis 的分布式存储,也就是说每台 redis 节点上存储不同的内容。Redis-Cluster 采用无中心结构,它的特点如下:

所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。节点的fail是通过集群中超过半数的节点检测失效时才生效。
客户端与 redis 节点直连,不需要中间代理层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

工作方式
在 redis 的每一个节点上,都有这么两个东西,一个是插槽(slot),它的取值范围是:0-16383。还有一个就是 cluster,可以理解为是一个集群管理的插件。
当我们的存取的 key 到达的时候,redis会根据 crc16 的算法得出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,
通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。为了保证高可用,redis-cluster 集群引入了主从模式,
一个主节点对应一个或者多个从节点,当主节点宕机的时候,就会启用从节点。当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与A 通信超时,
那么认为主节点 A 宕机了。如果主节点 A 和它的从节点A1 都宕机了,那么该集群就无法再提供服务了。
2.8 Redis的分布式锁
Redis 官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
1>安全特性:互斥访问,即永远只有一个 client 能拿到锁
2>避免死锁:最终 client 都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的 client crash 了或者出现了网络分区
3>容错性:只要大部分 Redis 节点存活就可以正常提供服务
Redis实现分布式锁
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。
当且仅当 key 不存在,将 key 的值设为 value。 若给定的 key 已经存在,则 SETNX 不做任何动作
SETNX 是『SET if Not eXists』(如果不存在,则 SET)的简写。
返回值:设置成功,返回 1 。设置失败,返回 0 。

使用SETNX完成同步锁的流程及事项如下:
使用SETNX命令获取锁,若返回0(key已存在,锁已存在)则获取失败,反之获取成功
为了防止获取锁后程序出现异常,导致其他线程/进程调用SETNX命令总是返回0而进入死锁状态,需要为该key设置一个“合理”的过期时间
释放锁,使用DEL命令将锁数据删除

新数据类型:
Bitmaps
现代计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位, 例如“abc”字符串是由3个字节组成, 但实际在计算机存储时将其用二进制表示,
“abc”分别对应的ASCII码分别是97、 98、 99, 对应的二进制分别是01100001、 01100010和01100011

理地使用操作位能够有效地提高内存使用率和开发效率。
Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:
(1) Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。
(2) Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组,
数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。
每个独立用户是否访问过网站存放在Bitmaps中, 将访问的用户记做1, 没有访问的用户记做0, 用偏移量作为用户的id。
设置键的第offset个位的值(从0算起) , 假设现在有20个用户,userid=1, 6, 11, 15, 19的用户对网站进行了访问,
计算出两天都访问过网站的用户数量
主要用于存储用户的访问量,计算某天的访问数量,计算出任意一天都访问过网站的用户数量(例如月活跃就是类似这种) , 可以使用or求并集
这种情况下使用Bitmaps能节省很多的内存空间, 尤其是随着时间推移节省的内存还是非常可观的
使用bitmaps对比set就是在存储上节省空间,对于大量用户的统计节省空间可观。

HyperLogLog(基数统计)
在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现。
但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。
解决基数问题有很多种方案:
(1)数据存储在MySQL表中,使用distinct count计算不重复个数
(2)使用Redis提供的hash、set、bitmaps等数据结构来处理
以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。
能否能够降低一定的精度来平衡存储空间?Redis推出了HyperLogLog
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
该类型主要用于基数去重,在庞大的数据量上很有用。

Geospatial
Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。
redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

Redis 回收使用的是什么算法? LRU 算法(最少使用概预算算法)
Redis 支持的 Java 客户端都有哪些?官方推荐用哪个? Redisson、Jedis、lettuce 等等,官方推荐使用 Redisson。
Jedis 与 Redisson 对比有什么优缺点? Jedis 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持; Redisson 实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,功能较为简单,不支 持字符串操作,
不支持排序、事务、管道、分区等 Redis 特性。Redisson 的宗旨是促进使 用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
Redis 事务相关的命令有哪几个? MULTI、EXEC、DISCARD、WATCH

Redis 如何做内存优化? 尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小,
所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的 web 系统中有一个 用户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的 key,
而是应该把这个用户 的所有信息存储到一张散列表里面(就是少用key)

原子没有roleback操作,他是操作要么一起执行,要么都不执行,他是支持原子性的,
如果失败了,不会丢失数据,有rdb,和aof,
redis是单线程的,他有watch操作。
1、事务开始
multi命令的执行,标识着一个事务的开始。multi命令会将客户端的flags属性中打开redis_multi标识来完成的。
2、命令入队
当一个客户端切换到事务状态之后,服务器会根据这个客户端发送的命令来执行不同的操作。如果客户端发送的命令为
multi,exec,watch,discard中的一个,立即执行这个命令,否则会将命令放入一个事务队列里面,然后向客户端返回queued回复

如果客户端发送的命令为exec,discard,exec,watch,multi四个命令中的其中一个,name服务器会立即执行这个命令。
如果客户端发送的是四个命令以外的命令,那么服务器并不会执行这个命令。
首先检查此命令的格式是否正确,如果不正确,服务器,服务器会在客户端状态(Redisclient)的flags属性关闭
redis_multi表示,并且就返回错误信息给客户端。
如果正确,将这个命令放入一个事务队列里面,然后向客户端返回queued回复
事务队列是按照fifo的方式保存入队的命令。

事务执行
客户端发送exec命令,服务器执行exec命令逻辑。
如果客户端状态的flags属性不包含redis_multli标识,或者包含redis——dirty_cas或者redis_dirty_exec标识,那么直接取消事务的执行。
否则客户端处于事务状态(flags有redis——multi标识),服务器会遍历事务队列,然后执行事务队列中的所有命令,最后将返回结果全部返回给客户端。
redis不支持事务回滚机制,但是它会检查每一个事务中的命令是否错误。
redis事务不支持检查哪些程序员自己逻辑错误。例如string类型的数据库键执行对hashmap类型操作。
watch命令是一个乐观锁,可以为redis事务提供check-and-set(cas)行为。监控一个或多个键,一旦其中一个键被修改(或删除),
之后的事务就不会执行,监控一直持续到exec命令执行。
multi命令用于开启一个事务,他总是返回OK。multi执行之后,客户端可以继续向服务端发送任意多条命令。
这些命令不会立即执行,而是被放到一个队列中,当exec命令被调用时,所有队列中的命令才会执行。
exec:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断的时候,返回空值null。
通过调用discard,客户端可以清空队列,并放弃执行事务,并且客户端会从事务状态中退出。
unwatch命令可以取消watch对所有key的监控。

docker run --name kibana -e ELASTICSEARCH_HOSTS=http://http://43.142.141.29:9200 -p 5601:5601 \ -d kibana:7.4.2

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值