- 数据量急剧增加,单个数据库装不下庞大的数据
- 数据的索引太大,一个机器的内存也放不下
- 访问量太大,一台服务器承受不住。(需要大量的读写混合)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-djD3BO9d-1666190632545)(images/image-20211015122718484.png)]
读写分离就是: 将一个大的数据库拆分成专门读和写的数据库
优化数据库的数据结构和索引
(难度大)文件缓存
,通过IO流获取比每次都访问数据库效率略高,但是流量爆炸式增长时候,IO流也承受不了MemCache
,当时最热门的技术,通过在数据库和数据库访问层之间加上一层缓存,第一次访问时查询数据库,将结果保存到缓存,后续的查询先检查缓存,若有直接拿去使用,效率显著提升。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KgFSjOGe-1666190632546)(images/image-20211015131300452.png)]
虽然利用mysql
集群可以解决大量数据存储的问题,但是如今各式各样的数据出现(用户定位数据、图片数据等),大数据时代关系数据库无法满足要求,因此Nosql
数据库解决了这些问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jTTL1qCX-1666190632546)(images/image-20211015133656560.png)]
Nosql
关系型数据库无法存储个人信息、社交网络、地理位置等各式各样的数据,这时候就需要我们使用非关系型数据库Nosql
Nosql
= Not Only Sql
(不仅仅是 sql
)
关系数据库
- 列+行,同一个表下数据的结构是一样的。
- 结构化组织
- SQL
- 数据和关系都存在单独的表中 row col
- 操作,数据定义语言
- 严格的一致性
- 基础的事务
非关系型数据库
- 数据存储没有固定的格式、并且可以进行横向扩展
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库(社交关系)
- 最终一致性
- CAP定理和BASE
- 高性能,高可用,高扩展
velume
variety
velocity
实际上的公司实践是:Nosql+RDNMS一起使用才是最合适的。
Redis(Remote Dictionary Server )
,即远程字典服务。是一个开源的使用ANSI
和C语言
编写、支持网络、可基于内存亦可持久化的日志型、Key-Value
数据库,并提供多种语言的API
。与memcached
一样,**为了保证效率,数据都是缓存在内存中。**区别的是redis
会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave
(主从)同步。
redis
会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件
cd /usr/local
mkdir redis
下载
cd /usr/local/redis
wget http://download.redis.io/releases/redis-3.2.10.tar.gz
解压
tar -xzvf redis-3.2.10.tar.gz
解压后
安装redis
注意:这里使用
gcc
对redis进行编译 生成redis-server
等文件。如果没有安装redis需要先在服务器安装gcc,不然是没有redis-server
文件的–无法启动和关闭。yum -y install gcc gcc-c++
进入redis-3.2.10
目录,执行编译
cd redis-3.2.10
sudo make && make install
注意:
redis.conf
是一个非常重要的配置文件
cd redis-3.2.10
vim redis.conf
在配置文件61行左右(行数在右下角),注释掉172.0.0.1(默认redis是只能内网127.0.0.1访问,如果想外网访问需要修改绑定的地址)
# bind 127.0.0.1
设置redis可以一直在后台运行,以守护进程方式运行,即关闭SSH工具程序也在运行。
将 daemonize no 改成 daemonize yes(在128行左右)
**注意:**守护进程一旦开启,想要
关闭redis
就相当困难了,使用kill -9 port
依然无法杀死redis进程
,因为每次杀死进程后又会重新开启redis
。所以 先不开启 守护进程方式
如果不小心开启了:修改后,
重新编译redis即可
daemonize no # 关闭
daemonize yes # 开启
开启远程访问,大概在80行左右
注意:protected-mode 是3.2 之后加入的新特性,是为了禁止公网访问redis cache,加强redis安全的。
protected-mode no
密码设置,将”#requirepass foobared“ 取掉注释改成 requirepass 123456(或者其它你需要的密码)(在480行左右)
requirepass 123456
最后保存退出
进入redis-3.2.10
目录,启动redis
cd redis-3.2.10
# 启动 redis
redis-server redis.conf
# 查看是否启动成功
ps aux | grep redis
启动脚本startRedis.sh
############### startRedis.sh
#!/bin/sh
# start redis
redis-server /usr/local/redis/redis-3.2.10/redis.conf # redis.conf 的路径
echo "redis started"
############### end
vim startRedis.sh
chmod +x ./startRedis.sh
./startRedis.sh
出现 redis-server *: 6379
即启动成功
如果出现了redis-server 127.0.0.1: 6379
需要查看redis.conf
是否配置正确
首先登陆阿里云控制台; 控制台–>云服务器ECS–>安全与网络–>安全组–>配置规则
出方向和入方向都要配置
点击快速添加
服务器测试连接
进入redis-3.2.10
目录,测试连接
redis-cli -a 123456 # 如果没有设置密码: redis-cli
or
redis-cli
# 查看密码
config get requirepass
# 退出
exit
退出redis
service redis stop
显示结果,服务器本地连接成功
本地远程测试连接
下载RDM管理工具:https://redisdesktop.com/
设置连接信息
名字: 任意取
密码:
redis
密码 地址: 服务器地址
点击测试连接
如果测试失败
请排查以下原因
配置文件
redis.conf
配置错误,是否开启远程连接
6379
端口是否开发密码配置错误
# 查看redis密码 redis-cli config get requirepass
要是显示为空,重新配置
redis.conf
文件中密码:requirepass 12345
或者临时性配置密码
redis-cli config set requirepass 123456
redis shell
中关闭redis
redis-cli -a 123456 # 如果设置了密码,要带上密码,否者会出现没有权限的警告
127.0.0.1:6379> shutdown
127.0.0.1:6379> exit
如果出现问题,应该是直接使用redis-cli
进入的shell
127.0.0.1:6379> shutdown
NOAUTH Authentication required.
解决方法
auth 123456
redis
redis-cli -a 123456 shutdown # 设计密码直接关闭redis
redis-cli shutdown # 没有设置密码,关闭redis
ps aux | grep redis # 查看 进程 ID
root 19935 0.1 0.2 37252 4176 ? Sl 14:49 0:00 redis-server *:6379
root 20050 0.0 0.0 14436 1004 pts/0 S+ 15:01 0:00 grep --color=auto redis
kill -9 19935 # 杀死 redis 进程
使用redis-benchmark
自带工具测试,redis
的工具都放在/usr/local/bin
中
root@iZ2ze3e3cxev6ehgdt3qpdZ:/usr/local/bin# ls
chardetect cloud-init-per jsonpatch redis-benchmark redis-cli
cloud-id docker-compose jsonpointer redis-check-aof redis-sentinel
cloud-init jsondiff jsonschema redis-check-rdb redis-server
root@iZ2ze3e3cxev6ehgdt3qpdZ:/usr/local/bin# redis-benchmark -h localhost -p 6379 -c 100 -n 10000
====== PING_INLINE ======
10000 requests completed in 0.18 seconds - 10000条请求
100 parallel clients - 100个并发数据
3 bytes payload - 所需空间 3 btyes
keep alive: 1 - 单机
76.71% <= 1 milliseconds - 76.71%用了1毫秒
99.96% <= 2 milliseconds
100.00% <= 2 milliseconds
56497.18 requests per second - 每秒可以处理56497.18条数据
Redis
是单线程的,Redis
是基于内存操作,CPU
不是Redis
的瓶颈,而是机器内存和网络带宽。
Redis
将所有数据全部放在了内存操作中,省略了CPU
上下文切换的耗时,对于系统而言没有上下文切换效率是最好的。,在内存存储数据情况下,单线程就是最佳的方案。
多线程存在CPU
的上下文切换
Redis中有16个数据库,默认使用第0个,可以使用select n切换数据库,dbsize可以查看当前数据库的大小,与key数量相关。
127.0.0.1:6379> config get databases # 查看redis中的数据库信息
1) "databases"
2) "16"
127.0.0.1:6379> dbsize # 默认是0号数据库, 查看0号数据库的大小
(integer) 0
127.0.0.1:6379> select 4 # 切换到4号数据库
OK
127.0.0.1:6379[4]> set name test # 设置 name 键值对
OK
127.0.0.1:6379[4]> select 8
OK
127.0.0.1:6379[8]> get name # 不同数据库之间 数据是不能互通的,并且dbsize 是根据库中key的个数。
(nil)
127.0.0.1:6379[8]> select 4
OK
127.0.0.1:6379[4]> get name
"test"
127.0.0.1:6379[4]> keys * # # 查看当前数据库中所有的key。
1) "name"
config get databases
: 查看redis中的数据库信息
dbsize
: 默认是0号数据库, 查看0号数据库的大小
select 4
:切换到4号数据库
keys *
:查看当前数据库中所有的key。
flushdb
:清空当前数据库中的键值对。
flushall
:清空所有数据库的键值对。
set name test
: 设置键值对
get name
: 得到键值对
在redis中无论什么数据类型,在数据库中都是以key-value形式保存,通过进行对Redis-key的操作,来完成对数据库中数据的操作。
set key value
:创建键值对
get key
: 得到键值对
exists key
:判断键是否存在
del key
:删除键值对
move key db
:将键值对移动到指定数据库
expire key second
:设置键值对的过期时间
type key
:查看value的数据类型
ttl age
: 查看剩余过期时间ttl key - 如果返回 -1 , 表示key没有设置过过期时间 - 如果返回 -2 , 表示key设置过过期时间,并且已经过期 - 如果返回 剩余时间 , 表示key还没有过期
key * redis:4>set age 20 # 创建键值对 age
"OK"
redis:4>get age # 得到键值对 age
"20"
redis:1>exists name # 判断键name是否存在
"0"
redis:1>exists age # 判断键age是否存在
"1"
redis:1>del age # 删除键值对
"1"
redis:1>type name # 查询将键值对的类型
string
redis:1>expire age 15 # 删除键值对
"1"
redis:1>ttl age # 查看剩余过期时间
"5"
redis:1>ttl age
"2"
redis:1>ttl age
"1"
redis:1>ttl age
"-2"
redis:1>get age
null
redis:1>keys * # 查询该数据所有的键值对
语法:
APPEND key value
redis:0>set msg hello
"OK"
redis:0>get msg
"hello"
redis:0>append msg ",world" # 向msg后追加 信息
"11"
redis:0>get msg
"hello,world"
语法:
DECR/INCR key
redis:0> set age 20
OK
127.0.0.1:6379> incr age # age++
(integer) 21
127.0.0.1:6379> decr age # age--
(integer) 20
按指定的步长对数值进行加减
语法:
INCRBY/DECRBY key
redis:0> set age 20
OK
redis:0>incrby age 2 # age+2
"22"
redis:0>decrby age 10 # age-10
" 12"
语法:
INCRBYFLOAT key
redis:0>set age 20
"OK"
redis:0>incrbyfloat age 1.1
"21.1"
语法:
strlen key
redis:0>strlen age
"4"
redis:0>
语法:
GETRANGE key start end
redis:0>set name asdfghj
"OK"
redis:0>getrange name 1 4
"sdfg"
语法:
SETRAGNE key offset value
redis:0>get name
"asdfghj"
redis:0>setrange name 2 *
"7"
redis:0>get name
"as*fghj"
redis:0>
语法:
getset key value
redis:0>get name
"as*fghj"
redis:0>getset name san
"as*fghj"
redis:0>get name
"san"
redis:0>getset msg 123 # 如果 getset 的是不存在的key , 返回null
null
语法:
setnx key value
redis:0>flushall
"OK"
redis:0>setnx msg 123 # 仅对不存在的键值对进行 set
"1"
redis:0>setnx msg 123 # 如果键值对存在, 返回失败
"0"
语法:
msetnx key1 value1 key2 value2..
redis:0>msetnx k1 v1 k2 v2
"1"
redis:0>keys *
1) "k2"
2) "k1"
3) "msg"
语法:
MSET key1 key2 ...
redis:0>mget k1 k2
1) "v1"
2) "v2"
redis:0>
语法:
MSET key:{id1}:{filed1} value1 key:{id2}:{filed2} value2
redis:0>mset user:1:name san user:2:age 20
"OK"
redis:0>mget user:1:name user:2:age
1) "san"
2) "20"
redis:0>
Redis
列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232 - 1
个元素 (4294967295
, 每个列表超过40
亿个元素)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ymG0fkbY-1666190632547)(images/image-20211015202052785.png)]
Redis中的list是可以双端操作的,相当于双端队列,操作左边Lxxx,操作右边Rxxx,
语法:
lpush/rpush key value1 value2...
redis:0>lpush mylist v1 v2
"2"
redis:0>rpush mylist v3
"3"
redis:0>lrange mylist 0 -1
1) "v2"
2) "v1"
3) "v3"
语法:
lrange key start end
redis:0>lrange mylist 1 2 # 获取 从1到2位数据
1) "v1"
2) "t2"
redis:0>lrange mylist 0 -1 # 获取整个list
1) "t1"
2) "v1"
3) "t2"
redis:0>
语法:
linsert key before/after pivot value
redis:0>linsert mylist before t1 m1 # 在t1前插入 m1
"4"
redis:0>linsert mylist after t1 m2 # 在 t1 后插入 m2
"5"
redis:0>lrange mylist 0 -1
1) "m1"
2) "t1"
3) "m2"
4) "v1"
5) "t2"
redis:0>
语法:
llen key
redis:0>llen mylist
"5"
redis:0>
语法:
lindex mylist index
redis:0>lindex mylist 2
"m2"
redis:0>
语法:
lset key index value
redis:0>lset mylist 2 u1
"OK"
redis:0>lrange mylist 0 -1
1) "m1"
2) "t1"
3) "u1"
4) "v1"
5) "t2"
redis:0>
语法:
lpop/rpop mylist
redis:0>lpop mylist
"v4"
redis:0>lrange mylist 0 -1
1) "v2"
2) "t1"
3) "v1"
4) "t2"
5) "v3"
语法:
ltrim key start end
redis:0>lrange mylist 0 -1
1) "v2"
2) "t1"
3) "v1"
4) "t2"
5) "v3"
redis:0>ltrim mylist 1 3
"OK"
redis:0>lrange mylist 0 -1
1) "t1"
2) "v1"
3) "t2"
语法:
lrem key count value
redis:0>lrange mylist 0 -1
1) "t3"
2) "t2"
3) "m1"
4) "t1"
5) "u1"
6) "v1"
7) "t2"
redis:0>lrem mylist 1 t2 # 删除mylist 中的一个 t2 , 从头部开始
"1"
redis:0>lrange mylist 0 -1
1) "t3"
2) "m1"
3) "t1"
4) "u1"
5) "v1"
6) "t2"
redis:0>
redis:0>lrange mylist 0 -1
1) "u1"
2) "t3"
3) "m1"
4) "t1"
5) "u1"
6) "v1"
7) "t2"
redis:0>lrem mylist -1 u1 # 删除mylist 中的一个 t2 , 从尾部部开始
"1"
redis:0>lrange mylist 0 -1
1) "u1"
2) "t3"
3) "m1"
4) "t1"
5) "v1"
6) "t2"
redis:0>
注意:
cout > 0
, 从头部开始数cout < 0
, 从尾部开始数
list
实际上是一个链表,before Node after
,left
,right
都可以插入值- 如果
key
不存在,则创建新的链表- 如果
key
存在,新增内容- 如果移除了所有值,空链表,也代表不存在
- 在两边插入或者改动值,效率最高!修改中间元素,效率相对较低
消息排队!消息队列(Lpush Rpop),栈(Lpush Lpop)
Redis
的Set
是string
类型的无序集合。集合成员是唯一的,这就意味着set集合中不能出现重复的数据。
Redis
中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)
。
语法:
sdd key member member2...
redis:0>sadd myset v1
"1"
语法:
smembers key
redis:0>smembers myset
1) "v3"
2) "v2"
3) "v1"
redis:0>
语法:
scrad key
redis:0>scard myset
"3"
语法:
sismember key member
redis:0>sismember myset v4
"0"
redis:0>sismember myset v1
"1"
redis:0>
语法:
smove key newkey value
redis:0>smove myset newset v2
"1"
redis:0>smembers myset
1) "v3"
2) "v1"
redis:0>smembers newset
1) "v2"
语法:
srem key member1 member2...
redis:0>smembers myset
1) "v5"
2) "v1"
3) "v3"
4) "v4"
5) "v7"
6) "v6"
redis:0>srem myset v7 v6
"2"
redis:0>smembers myset
1) "v3"
2) "v4"
3) "v1"
4) "v5"
redis:0>
语法:
sdiff key1 key2 ...
redis:0>smembers myset
1) "v3"
2) "t1"
3) "v4"
4) "v1"
5) "v5"
6) "t3"
7) "t2"
redis:0>smembers newset
1) "t5"
2) "t4"
3) "v2"
4) "t1"
5) "t6"
redis:0>sdiff myset newset
1) "v5"
2) "v1"
3) "v3"
4) "v4"
5) "t2"
6) "t3"
语法:
sinter key1 key2 ...
redis:0>smembers myset
1) "v3"
2) "t1"
3) "v4"
4) "v1"
5) "v5"
6) "t3"
7) "t2"
redis:0>smembers newset
1) "t5"
2) "t4"
3) "v2"
4) "t1"
5) "t6"
redis:0>sinter myset newset
1) "t1"
语法:
sunion key1 key2 ...
redis:0>smembers myset
1) "v3"
2) "t1"
3) "v4"
4) "v1"
5) "v5"
6) "t3"
7) "t2"
redis:0>smembers newset
1) "t5"
2) "t4"
3) "v2"
4) "t1"
5) "t6"
redis:0>sunion myset newset
1) "v1"
2) "v5"
3) "t2"
4) "t3"
5) "t6"
6) "v3"
7) "t5"
8) "t4"
9) "v2"
10) "t1"
11) "v4"
Redis hash
是一个string
类型的field
和value
的映射表,hash
特别适合用于存储对象。
Set
就是一种简化的Hash
,只变动key
,而value
使用默认值填充。可以将一个Hash
表作为一个对象进行存储,表中存放对象的信息。
语法:
hset key field value
redis:0>hset myhash name san
"1"
redis:0>hset myhash name dan
"0"
redis:0>
语法:
hmset key field1 value1 field2 value2
redis:0>hmset myhash age 18 sex 1
"OK"
redis:0>hgetall myhash
1) "name"
2) "dan"
3) "age"
4) "18"
5) "sex"
6) "1"
redis:0>
语法:
hgetall key
redis:0>hgetall myhash
1) "name"
2) "dan"
3) "age"
4) "18"
5) "sex"
6) "1"
redis:0>
语法:
hget key field
redis:0>hget myhash name
"dan"
redis:0>
语法:
hkeys key
redis:0>hkeys myhash
1) "name"
2) "age"
3) "sex"
redis:0>
语法:
hexists key field
redis:0>hexists myhash name
"1"
redis:0>hexists myhash name1
"0"
redis:0>
语法:
hvals key
redis:0>hvals myhash
1) "dan"
2) "18"
3) "1"
redis:0>
语法:
hlen key
redis:0>hlen myhash
"3"
redis:0>
语法:
hdel key field1 field2...
redis:0>hdel myhash age sex
"2"
redis:0>hkeys myhash
1) "name"
redis:0>
语法:
hincrby key field n
redis:0>hincrby myhash age 4
"16"
redis:0>hget myhash age
"16"
redis:0>
语法:
hincrbyfloat key field n
redis:0>hincrbyfloat myhash age 2.5
"18.5"
redis:0>
Hash
变更的数据user name age
,尤其是用户信息之类的,经常变动的信息!Hash
更适合于对象的存储,Sring
更加适合字符串存储!
不同的是每个元素都会关联一个double
类型的分数(score
)。redis
正是通过分数来为集合中的成员进行从小到大的排序。
score
相同:按字典顺序排序
语法:
zadd key score member1 score2 member2...
redis:0>zadd myzset 68 java 98 c # 向 zset中添加 java 68分,c98 分
"2"
redis:0>
语法:
zcard key
redis:0>zcard myzset
"2"
redis:0>
语法:
zcount key min max
127.0.0.1:6379> ZCOUNT myzset 0 1 # 获取score在 [0,1]区间的成员数量
(integer) 1
语法:
zincrby key n member
redis:0>zincrby myzset 10 java
"78"
redis:0>
语法:
zscore key member
redis:0>zscore myzset java
"78"
redis:0>
语法:
zrange key 0 -1
redis:0>zrange myzset 0 2 # 获得 1 - 3 区间的成员
1) "java"
2) "c++"
3) "c"
redis:0>zrange myzset 0 -1 # 获得所有 成员
1) "java"
2) "c++"
3) "c"
4) "php"
redis:0>
语法:
zrangebylex key min max
redis:0>zrangebylex myzset - +
1) "java"
2) "c++"
3) "c"
4) "php"
redis:0>
语法:
zrangebylex key - + limit n m
redis:0>zrangebylex myzset - + limit 0 2 # 查询第一条到第二条数据
1) "java"
2) "c++"
redis:0>zrangebylex myzset - + limit 2 3 # 查询第2条到第3条数据
1) "c"
2) "php"
redis:0>
127.0.0.1:6379> ZRANGEBYLEX testset - + LIMIT 3 3 # 显示 3,4,5条记录
"apple"
"back"
"java"
127.0.0.1:6379> ZRANGEBYLEX testset (- [apple # 显示 (-,apple] 区间内的成员
"abc"
"add"
"amaze"
"apple"
127.0.0.1:6379> ZRANGEBYLEX testset [apple [java # 显示 [apple,java]字典区间的成员
"apple"
"back"
"java"
语法:
zrangebyscore key min max
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 10 # 返回score在 [1,10]之间的的成员
"m1"
"m3"
"m2"
127.0.0.1:6379> ZRANGEBYSCORE myzset 1 5
"m1"
"m3"
语法:
zlexcount key - +
redis:0>zlexcount myzset - +
"4"
redis:0>
127.0.0.1:6379> ZLEXCOUNT testset [apple [java
(integer) 3
语法:
zrem key field min max
redis:0>zrem myzset c
"1"
redis:0>
27.0.0.1:6379> ZREMRANGEBYLEX testset [apple [java # 移除字典区间[apple,java]中的所有成员
(integer) 3
127.0.0.1:6379> ZREMRANGEBYRANK testset 0 1 # 移除排名0~1的所有成员
(integer) 2
127.0.0.1:6379> ZREMRANGEBYSCORE myzset 0 3 # 移除score在 [0,3]的成员
(integer) 2
语法:
zrevrange key min max
redis:0>zrevrange myzset 0 -1
1) "php"
2) "c++"
3) "java"
语法:
zrevrangebyscore key max min
redis:0>zrevrangebyscore myzset 100 1
redis:0>zrevrangebyscore myzset 100 1
1) "php"
2) "c++"
3) "java"
127.0.0.1:6379> ZREVRANGEBYLEX testset [java (add # 按字典倒序 返回集合中(add,java]字典区间的成员
"java"
"back"
"apple"
"amaze
使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用
geoadd key longitud(经度) latitude(纬度) member [..]
将具体经纬度的坐标存入一个有序集合geopos key member [member..]
获取集合中的一个/多个成员坐标geodist key member1 member2 [unit]
返回两个给定位置之间的距离。默认以米作为单位。georadius key longitude latitude radius m|km|mi|ft [WITHCOORD][WITHDIST] [WITHHASH] [COUNT count]
以给定的经纬度为中心, 返回集合包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。GEORADIUSBYMEMBER key member radius...
功能与GEORADIUS相同,只是中心位置不是具体的经纬度,而是使用结合中已有的成员作为中心点。geohash key member1 [member2..]
返回一个或多个位置元素的Geohash表示。使用Geohash位置52点整数编码。
Redis HyperLogLog
是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
Hyperloglog
其底层使用string
数据类型。
基数就是数据集中不重复的元素的个数,
(还可以使用set集合来统计不重复元素的个数,因为set集合元素是不重复的)
网页的访问量(UV):一个用户多次访问,也只能算作一个人。
传统实现,存储用户的id
,然后每次进行比较。当用户变多之后这种方式及其浪费空间,而我们的目的只是计数,()
Hyperloglog就能帮助我们利用最小的空间完成。
PFADD key element1 [elememt2..]
添加指定元素到 HyperLogLog中PFCOUNT key [key]
返回给定 HyperLogLog 的基数估算值。PFMERGE destkey sourcekey [sourcekey..]
将多个 HyperLogLog 合并为一个 HyperLogLog
代码示例
redis:0>pfadd test a d f g h j k l # 添加元素到hyperloglog中
"1"
redis:0>type test # 查看类型
"string"
redis:0>pfcount test # 计算基数
"8"
redis:0>pfadd test2 q s f r y h y u i
"1"
redis:0>pfcount test2
"8"
redis:0>pfmerge test3 test test2 # 合并 test 和 test2 为 test3
"OK"
redis:0>pfcount test3
"14"
redis:0>
使用位存储,信息状态只有 0 和 1
Bitmap是一串连续的2进制数字(0或1),每一位所在的位置为偏移(offset),在bitmap上可执行AND,OR,XOR,NOT以及其它位操作。
应用场景: 签到统计、状态统计
setbit key offset value
为指定key的offset位设置值getbit key offset
获取offset位的值bitcount key [start end]
统计字符串被设置为1的bit数,也可以指定统计范围按字节bitop operration destkey key[key..]
对一个或多个保存二进制位的字符串 key 进行位元操作,并将结果保存到 destkey 上。BITPOS key bit [start] [end]
返回字符串里面第一个被设置为1或者0的bit位。start和end只能按字节,不能按位
代码示例
redis:0>setbit sign 0 1 # 星期一 正常
"0"
redis:0>setbit sign 1 1 # 星期二 正常
"0"
redis:0>setbit sign 2 0 # 星期三 缺勤
"0"
redis:0>setbit sign 3 1 # 星期四 正常
"0"
redis:0>setbit sign 4 0 # 星期五 缺勤
"0"
redis:0>setbit sign 5 1 # 星期六 正常
"0"
redis:0>setbit sign 6 0 # 星期天 缺勤
"0"
redis:0>type sign # bitMap 的数据类型为 String
"string"
redis:0>getbit sign 5 # 得到某一天的情况
"1"
redis:0>bitcount sign # 统计一个星期之内的考勤情况
"4"
redis:0>
事务本质: 一组命令的集合,事务中每条命令都会被序列化,执行过程中按顺序执行,不允许其他命令进行干扰。
事务具有: 一次性 、顺序性、排他性
redis事务特点:
Redis
的单条命令是保证原子性的,但是redis
事务不能保证原子性Redis
事务没有隔离级别的概念
multi
开启事务exec
执行事务注: 所以事务中的命令在加入时都没有被执行,直到提交时才会开始执行(Exec
)一次性完成。
redis:0>multi # 开启事务
"OK"
redis:0>set k1 v1 # 入队列
"QUEUED"
redis:0>set k2 v2 # 入队列
"QUEUED"
redis:0>get k2 # 入队列
"QUEUED"
redis:0>keys * #
"QUEUED"
redis:0>exec # 执行事务
1) "OK"
2) "OK"
3) "OK"
4) "OK"
5) "OK"
6) "v2"
7) "OK"
8) 1) "k2"
2) "k1"
9) "OK"
redis:0>multi
"OK"
redis:0>set h1 v1
"QUEUED"
redis:0>discard # discard 取消事务
"OK"
redis:0>exec
"ERR EXEC without MULTI"
redis:0>get h1
null
事务可能遇到错误
- 代码语法错误(编译时异常)所有的命令都不执行,如:入队命令为错误命令
- 代码逻辑错误 (运行时异常)**其他命令可以正常执行 ** , 因此redis不保证原子性
redis:0>multi
"OK"
redis:0>set h1 v1
"QUEUED"
redis:0>e r #### 错误语法
"ERR unknown command 'e'"
redis:0>exec ### 执行失败
"EXECABORT Transaction discarded because of previous errors."
代码语法错误,所有的命令都不执行
redis:0>multi
"OK"
redis:0>incr k1 # 给String ++ 逻辑错误
"QUEUED"
redis:0>set h1 v1
"QUEUED"
redis:0>exec
1) "OK"
2) "ERR value is not an integer or out of range"
3) "OK"
4) "OK"
5) "OK"
redis:0>
代码逻辑错误,其他命令可以正常执行, 说明Redis事务不保证原子性
锁的目的:
实现并发控制: 保证在并发情况下数据的准确性
如何实现并发控制
实现并发控制的主要手段分为
乐观并发控制
和悲观并发控制
两种。
在修改数据时,为了避免其他人修改,直接对数据进行加锁,以防止数据被其他人修改。这种先锁定,再修改数据的方式就是悲观锁。
悲观锁特点
悲观锁,具有强烈的独占
和排他
特性。
之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。
Java
里面的同步 synchronized 关键字的实现。synchronized
共享锁(S锁、读锁),加了共享锁只能读,不能写,只能加共享锁
排他锁(X锁、写锁), 加了排他锁不能在加其他锁,不能读,不能写
悲观并行控制,即悲观锁,采用先上锁再访问 的原则。为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。
乐观锁
是相对悲观锁
而言的,乐观锁
假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RhuRl4d4-1666190632547)(images/image-20211018145039189.png)]
乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。(乐观锁没有使用数据库本身的一些锁,去实现乐观锁)
Java
中java.util.concurrent.atomic
包下面的原子变量使用了乐观锁的一种 CAS 实现方式。version
字段,表示数据被修改的次数。当数据被修改时,version
值会 +1
。当线程 A
要更新数据时,在读取数据的同时也会读取 version
值,在提交更新时,若刚才读取到的 version
值与当前数据库中的 version
值相等时才更新,否则重试更新操作,直到更新成功。· 乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。
使用watch key
监控指定数据,相当于乐观锁加锁。
对需要上锁的key添加watch key
即可
> watch key
redis:0>set money 100 # 账户的钱
"OK"
redis:0>set out 0 # 开销的钱
"OK"
redis:0>watch money # 对账户key加锁
"OK"
redis:0>multi # 创建事务
"OK"
redis:0>decrby money 20
"QUEUED"
redis:0>incrby out 20
"QUEUED"
redis:0>exec # 执行
1) "OK"
2) "80"
3) "OK"
4) "20"
5) "OK"
redis:0>
线程1,事务先不执行
redis:0>get money
"170"
redis:0>watch money
"OK"
redis:0>multi
"OK"
redis:0>decrby money 50
"QUEUED"
redis:0>incrby out 50
"QUEUED"
redis:0> #### ===> 先不执行事务
线程2,事务修改money的值
redis:0>incrby money 100 # 充值
"270"
redis:0>
线程1, 执行失败
redis:0>exec
redis:0>get money # 执行失败, money还是 线程2修改的值
"270"
redis:0>
unwatch key
Jedis
是以前java
操作redis
的方法,使用Java
来操作Redis
,Jedis
是Redis
官方推荐使用的Java
连接redis
的客户端。已经过时,现在使用springboot来连接redis
导入依赖
<dependencies>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>3.2.0version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.70version>
dependency>
dependencies>
编写测试连接类
public class JedisTest {
public static void main(String[] args) {
// 创建 jedis对象 连接
Jedis jedis = new Jedis("8.140.106.149",6379);
// 认证密码, 如果没有设置密码,则不需要认证
jedis.auth("123456");
System.out.println(jedis.ping());
}
}
注:
连接成功得到
jedis
对象后,java
操作redis
和在终端上是一模一样的
public class JedisTest {
public static void main(String[] args) {
// 创建 jedis对象 连接
Jedis jedis = new Jedis("8.140.106.149",6379);
// 认证密码
jedis.auth("123456");
//测试连接
System.out.println(jedis.ping());
//清空默认redis默认数据库
jedis.flushDB();
// 创建需要添加的内容, 需要转化为json
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","word");
jsonObject.put("name","san");
String result = jsonObject.toJSONString();
//开启事务
Transaction multi = jedis.multi();
try{
multi.set("user1",result);
multi.set("user2",result);
multi.exec();
}catch (Exception e){ //如果出现异常,放弃事务
multi.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); // 关闭连接
}
}
}
jedis
: 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool
连接池! 更像 BIO
模式**(BIO,同步阻塞IO模式,数据的读写必须在一个线程完成之后才能执行**)lettuce
: 采用netty
,实例可以再多个线程中进行共享,不存在线程不安全的情况!可以减少线程数据了,更像 NIO
模式**(NIO,同步非阻塞IO模式,NIO使用单线程或者只是用少量的多线程,多链接使共用一个线程。)**说明: 在 SpringBoot2.x 之后,原来使用的jedis 被替换为了 lettuce?
https://www.cnblogs.com/xycq/articles/13885013.html
我们在学习SpringBoot
自动配置的原理时,整合一个组件并进行配置一定会有一个自动配置类xxxAutoConfiguration,并且在spring.factories
中也一定能找到这个类的完全限定名。Redis
也不例外。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-euBcPiAD-1666190632547)(images/image-20211019101340388.png)]
ctrl+f
搜索Redis,再按ctrl+鼠标右键即可[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUjEqT98-1666190632548)(images/image-20211019101434730.png)]
RedisAutoConfiguration
自动配置类// proxyBeanMethods = true 或不写,是Full模式
// proxyBeanMethods = false 是lite模式
// 不带@Configuration的类叫Lite配置类
// 1. Full模式下,通过方法调用指向的仍旧是原来的Bean
// 2. lite模式下,Spring 5.2.0+的版本,建议你的配置类均采用Lite模式去做,即显示设置proxyBeanMethods = false。Spring Boot在2.2.0版本(依赖于Spring 5.2.0)起就把它的所有的自动配置类的此属性改为了false,即@Configuration(proxyBeanMethods = false),提高Spring启动速度
@Configuration(
proxyBeanMethods = false
)
// 某个class位于类路径上,才会实例化一个Bean
@ConditionalOnClass({RedisOperations.class})
// @EnableConfigurationProperties注解的作用是:使使用 @ConfigurationProperties 注解的类生效。
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
// ConditionalOnMissingBean : 当这个bean(RedisTemplate)不存在的时候,下面的RedisTemplate就会生效,也就是如果你自己写了一个 redisTemplate 那么这个 redisTemplate 就会失效。
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
// String 类型比较常用,所以单独定义一个StringRedisTemplate
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
引入了redis
的配置文件RedisProperties
,可以发现大对数配置参数都在其中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r7YDPNQe-1666190632548)(images/image-20211019101923144.png)]
public interface RedisConnectionFactory extends PersistenceExceptionTranslator {
RedisConnection getConnection();
RedisClusterConnection getClusterConnection();
boolean getConvertPipelineAndTxResults();
RedisSentinelConnection getSentinelConnection();
}
实现该接口有jedis
方法和lettuce
方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RsZeK0l0-1666190632548)(images/image-20211019105301158.png)]
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
spring.application.name=jedis-springboot
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123456
@SpringBootTest
class JedisSpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate 操作不同的数据类型,api和我们的指令是一样的
// opsForValue 操作字符串 类似String
// opsForList 操作List 类似List
// opsForSet
// opsForHash
// opsForZSet
// opsForGeo
// opsForHyperLog
// 除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD
// 获取连接对象
//RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//connection.flushDb();
//connection.flushAll();
redisTemplate.opsForValue().set("name","san");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}
// 控制台输出
san
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-njsGTTeO-1666190632548)(images/image-20211019121225744.png)]
此时我们回到Redis
查看数据时候,惊奇发现全是乱码,可是程序中可以正常输出。这时候就关系到存储对象的序列化问题,在网络中传输的对象也是一样需要序列化,否者就全是乱码。
RedisTemplate
内部的序列化配置是这样的 ,默认的序列化器是采用JDK序列化器
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56qjInUU-1666190632549)(images/image-20211019121515257.png)]
后续我们定制RedisTemplate
就可以对其进行修改。
RedisSerializer
提供了多种序列化方案:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DJa3Oxmn-1666190632549)(images/image-20211019121625467.png)]
RedisTemplete
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 我们为了自己开发方便,一般直接使用
RedisTemplate<String, Object> template = new RedisTemplate<String,
Object>();
template.setConnectionFactory(factory);
// Json序列化配置
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);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.10.0version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-annotationsartifactId>
<version>2.10.0version>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-coreartifactId>
<version>2.10.0version>
dependency>
@SpringBootTest
class JedisSpringbootApplicationTests {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("name","san");
System.out.println(redisTemplate.opsForValue().get("name"));
}
}