分布式文件存储与数据缓存 Redis高可用分布式实践(上)

一、Reids概述

1.1 为什么要使用NoSQL

单机Mysql的美好年代

在90年代,一个网站的访问量一般都不大,用单个数据库完全可以轻松应付。在那个时候,更多的都是静态网页,动态交互类型的网站不多。

遇到问题:

随着用户数的增长,Tomcat和数据库之间竞争资源,单机性能不足以支撑业务。

 Tomcat与数据库分开部署

Tomcat和数据库分别独占服务器资源,显著提高两者各自性能。

新的问题:

随着用户数的增长,并发读写数据库成为瓶颈。

引入本地缓存和分布式缓存

通过缓存能把绝大多数请求在读写数据库前拦截掉,大大降低数据库压力。其中涉及的技术包括:使用memcached作为本地缓存,使用Redis作为分布式缓存。

注意:

缓存抗住了大部分的访问请求,随着用户数的增长,并发压力主要落在单机的Tomcat上,响应逐渐变慢。

引入反向代理实现负载均衡

在多台服务器上分别部署Tomcat,使用反向代理软件(Nginx)把请求均匀分发到每个Tomcat中。

新的挑战:

反向代理使应用服务器可支持的并发量大大增加,但并发量的增长也意味着更多请求穿透到数据库,单机的数据库最终成为瓶颈。

数据库读写分离

由于数据库的写入压力增加,Memcached只能缓解数据库的读取压力。读写集中在一个数据库上让数据库不堪重负,大部分网站开始使用主从复制技术来达到读写分离,以提高读写性能和读库的可扩展性。Mysql的master-slave模式成为这个时候的网站标配了.

新的挑战:

业务逐渐变多,不同业务之间的访问量差距较大,不同业务直接竞争数据库,相互影响性能。读取数据从从库读取,写数据时写到主库。

数据库按业务分库

把不同业务的数据保存到不同的数据库中,使业务之间的资源竞争降低,对于访问量大的业务,可以部署更多的服务器来支撑。

为什么用NoSQL

用户的个人信息,社交网络,地理位置,用户生成的数据和用户操作日志已经成倍的增加。我们如果要对这些用户数据进行挖掘,那SQL数据库已经不适合这些应用了, NoSQL数据库的发展也却能很好的处理这些大的数据。

 1.2 什么是NoSQL

 NoSQL(NoSQL = Not Only SQL),意即“不仅仅是SQL”,泛指非关系型的数据库。随着互联网web2.0网站的兴起,传统的关系数据库在应付特别是超大规模和高并发类型纯动态网站已经显得力不从心,暴露了很多难以克服的问题。

结构化数据和非结构化数据

  • 结构化数据指的是由二维表结构来逻辑表达和实现的数据,严格遵循数据格式与长度规范,也称作为行数据。
  • 非结构化数据,指的是数据结构不规则或不完整,没有任何预定义的数据模型,不方便用二维逻辑表来表现的数据,例如办公文档(Word)、文本、图片、HTML、各类报表、视频音频等。

 NoSQL的四大分类

 KV型NoSql(代表----Redis)

KV型NoSql顾名思义就是以键值对形式存储的非关系型数据库,是最简单、最容易理解也是大家最熟悉的一种NoSql,因此比较快地带过。

特点:

  • 数据基于内存,读写效率高
  • KV型数据,时间复杂度为O(1),查询速度快

注意:

KV型NoSql最大的优点就是高性能,利用Redis自带的BenchMark做基准测试,TPS可达到10万的级别,性能非常强劲。

列式NoSql(代表----HBase)

列式NoSql,大数据时代最具代表性的技术之一了,以HBase为代表。

关系行数据库数据:

注意:

看到每行有name、phone、address三个字段,这是行式存储的方式,且可以观察id = 2的这条数据,即使phone字段没有,它也是占空间的。

列式数据库数据 :

注意:

  • 查询时只有指定的列会被读取,不会读取所有列
  • 列数据被组织到一起,一次磁盘IO可以将一列数据一次性读取到内存中
文档型NoSql(代表----MongoDB)

什么是文档型NoSql呢,文档型NoSql指的是将半结构化数据存储为文档的一种NoSql,文档型NoSql通常以JSON或者XML格式存储数据。

注意:

关系型数据库是按部就班地每个字段一列存,在MongDB里面就是一个JSON字符串存储。

 搜索型NoSql(代表----ElasticSearch)

 传统关系型数据库主要通过索引来达到快速查询的目的,但是在全文搜索的场景下,索引是无能为力的,like查询一来无法满足所有模糊匹配需求,二来使用限制太大且使用不当容易造成慢查询,搜索型NoSql的诞生正是为了解决关系型数据库全文搜索能力较弱的问题,ElasticSearch是搜索型NoSql的代表产品。

 1.3 关系型数据库和非关系型数据及其区别

关系型数据库

关系型数据库最典型的数据结构是表,由二维表及其之间的联系所组成的一个数据组织 优点:

  • 易于维护:都是使用表结构,格式一致;
  • 使用方便:SQL语言通用,可用于复杂查询;
  • 复杂操作:支持SQL,可用于一个表以及多个表之间非常复杂的查询。 缺点:
  • 读写性能比较差,尤其是海量数据的高效率读写;
  • 固定的表结构,灵活度稍欠;

非关系型数据库 

优点:

  • 格式灵活:存储数据的格式可以是key,value形式、文档形式、图片形式等等,文档形式、图片形式等等,使用灵活,应用场景广泛,而关系型数据库则只支持基础类型。
  • 速度快:nosql可以使用硬盘或者随机存储器作为载体,而关系型数据库只能使用硬盘;
  • 高扩展性;
  • 成本低:nosql数据库部署简单,基本都是开源软件。

缺点:

  • 不提供sql支持,学习和使用成本较高;

  • 无事务处理;

  • 数据结构相对复杂,复杂查询方面稍欠。

1.4 当下NoSQL经典应用

 当下应用是SQL和NoSQL一起使用

淘宝商品信息如何存放 

商品基本信息

名称、价格、出厂信息、生产厂商,商家信息等, 关系型数据库就可以解决。

注意:

注意,淘宝内部用的Mysql是里面的大牛自己改造过的。

商品描述、详情、评论

 多文件信息描述类,IO读写性能变差不能使用Mysql数据库,使用MongDB。

商品的图片

 图片放在分布式文件系统中:

  1. 淘宝自己的TFS
  2. Google的GFS
  3. Hadoop的HDFS
  4. 阿里云的OSS
商品关键字 

 搜索引擎 elasticsearch 或者 ISerach

商品热门的波段信息

内存数据库 Redis Tair Memache

遇到的问题:

  1. 数据类型太多
  2. 数据源繁多
  3. 数据要改造

发现问题 

难点:

  • 数据类型多样性
  • 数据源多样性和变化重构
  • 数据源改造而数据服务平台不需要大面积重构

解决问题 

UDSL统一数据服务平台

 UDSL热点缓存设计

 1.5 什么是Redis

 Redis是一个使用ANSI C编写的开源、包含多种数据结构、支持网络、基于内存、可选持久性的键值对分布式缓存数据库。

特性

  • 基于内存运行,性能高效
  • 支持分布式,理论上可以无限扩展
  • key-value存储系统
  • 开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API

谁在用Redis

  • Github

  • 京东

  • 微博

  • 阿里巴巴

  • 百度

  • 美团

  • 搜狐

二、Redis安装

2.1 Linux下安装Redis

下载地址

Redis官方网址:Redis

下载Redis安装包源文件

 鼠标右键选择复制连接地址,在linux下使用wget下载安装包(或者直接下载到win,再上传到Linux)

wget https://github.com/redis/redis/archive/6.2.6.tar.gz

 将安装包解压到/usr/local目录下

安装C语言编译环境GCC

#查看是否安装了gcc
gcc --version


#安装gcc
yum install -y gcc

编译Redis

在redis-6.2.6文件下执行编译命令,将redis源码编译成可执行的文件

[root@localhost redis-6.2.6]# make

安装Redis

在redis-6.2.6目录下执行

[root@localhost redis-6.2.6]# make install

打开src文件发现绿色的命令文件 

注意:

redis-benchmark:Redis自带的基准性能测试工具

redis-check-aof:对有问题的 AOF 文件进行修复,AOF和RDB文件后面会说明

redis-check-rdb:对有问题的 RDB文件进行修复

redis-sentinel:Redis集群使用

redis-cli:客户端

redis-server:服务器启动

服务启动 

前台启动:/usr/local/redis-2.6.2/src下执行

./redis-server

 后台启动

修改redis.conf文件,在resid-6.2.6根目录下

daemonize  yes   #由no改为yes

在src下启动服务

[root@localhost src]# ./redis-server ../redis.conf

进入redis数据库

[root@localhost src]# ./redis-cli

2.2 Docker安装Redis

 下载最新Redis镜像

docker pull redis

注意:

可以用docker pull redis命令下载最新版本的Redis镜像,也可 以用“docker pull redis:标签”命令下载指定版本的Redis。

启动Redis容器 

docker run -itd --name myFirstRedis -p 6379:6379
redis:latest

观察Redis启动效果

docker logs myFirstRedis

注意:

如果直接在Linux等环境上启动Redis服务器,就能直接看到启动后的效果。

查看Redis的版本

先确保myFirstRedis容器处于Up状态。进入容器的命令行交互窗口。

docker exec -it myFirstRedis /bin/bash
redis-server --version

Redis服务器和客户端

Redis是基于键值对存储的NoSQL数据库,其中的数据是存储在 Redis服务器里的。和传统的MySQL数据库服务器相似,一个Redis服务器可以同多个客户端创建连接。

docker exec -it myFirstRedis /bin/bash
redis-cli

2.3 基础知识

默认16数据库

Redis是一个字典结构的存储服务器,一个Redis实例提供了多个用来存储数据的字典,客户端可以指定将数据存储在哪个字典中。

这与在一个关系数据库实例中可以创建多个数据库类似(如下图所示),所以可以将其中的每个字典都理解成一个独立的数据库。

 Redis默认支持16个数据库,可以通过调整Redis的配置文件redis/redis.conf中的databases来修改这一个值,设置完毕后重启Redis便完成配置。

Redis 使用的到底是多线程还是单线程?

因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了。

IO多路复用技术

redis 采用网络IO多路复用技术来保证在多连接的时候, 系统的高吞吐量。

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。

大白话解释

假设你是一个机场的空管, 你需要管理到你机场的所有的航线, 包括进港,出港, 有些航班需要放到停机坪等待,有些航班需要去登机口接乘客。最简单的做法,就是你去招一大批空管员,然后每人盯一架飞机, 从进港,接客,排位,出港,航线监控,直至交接给下一个空港,全程监控。

遇到的问题:

  • 很快你就发现空管塔里面聚集起来一大票的空管员,交通稍微繁忙一点,新的空管员就已经挤不进来了。
  • 空管员之间需要协调,屋子里面就1, 2个人的时候还好,几十号人以后 ,基本上就成菜市场了。
  • 空管员经常需要更新一些公用的东西,比如起飞显示屏,比如下一个小时后的出港排期,最后你会很惊奇的发现,每个人的时间最后都花在了抢这些资源上。

 切换数据库

select number

示例: 

# 默认使用 0 号数据库
redis 127.0.0.1:6379> SET db_number 0 
OK
# 使用 1 号数据库
redis 127.0.0.1:6379> select 1   
OK
127.0.0.1:6379[1]>

为当前数据库添加数据 

127.0.0.1:6379> set k3 30
OK

清空当前库

Redis Flushdb 命令用于清空当前数据库中的所有 key。

127.0.0.1:6379>flushdb

通杀全部库

Redis Flushall 命令用于清空整个 Redis 服务器的数据(删除所有数据库的所有 key )。

redis 127.0.0.1:6379> FLUSHALL 

为什么默认端口6379?

意大利的一位广告女郎名字叫Merz全名Alessia Merz。

6379 = Merz

 三、Redis数据类型

3.1 key键类型

keys

查看当前库中所有的key 。

keys *

有3个通配符 *, ? ,[]

  • *: 通配任意多个字符
  • ?: 通配单个字符
  • []: 通配括号内的某1个字符

 但是在实际生产环境中不建议使用该命令。新版本也进行了替代:

[root@localhost src]# ./redis-cli --scan "*"
"k1"

exists

判断某个key是否存在,返回1表示存在,0不存在。

exists key

示例:

#查看k1是否存在,如果存在返回1
127.0.0.1:6379> exists k1
(integer) 1
 
# 查看k1 k2 k3是否存在,如果k1 k2存在,k3不存在,则返回2
127.0.0.1:6379> exists k1 k2 k3
(integer) 2 

注意:

可以设置多个key,只返回存在的个数,但不返回哪一个存在/不存在。

type

查看当前key 所储存的值的类型。返回当前key所储存的值的类型,如string 、list等。

type key

示例:

127.0.0.1:6379> type k1
string

del

删除已存在的key,不存在的 key 会被忽略。

del key

示例:

可以设置多个key,返回删除成功的个数。

#  删除k1,如果成功返回1,失败返回0
del k1
# 删除k1 k2 k3,如果k1 k2存在,k3不存在,则返回2
del k1 k2 k3

expire

给key设置time秒的过期时间。设置成功返回 1 。 当 key 不存在返回 0。

expire key time

示例:

# 给k1设置30秒后删除
127.0.0.1:6379> expire k1 30
(integer) 1

应用场景是手机验证码 

ttl

以秒为单位返回 key 的剩余过期时间。

ttl key

示例:

127.0.0.1:6379> ttl k1
(integer) 25

注意:

当 key 不存在时,返回 -2 。 当 key 存在但没有设置剩余生存时间时,返回 -1 。 否则,以秒为单位,返回 key 的剩余生存时间。

persist

移除给定 key 的过期时间,使得 key 永不过期。

persist key

示例:

persist k1

3.2 String类型

简介

String是Redis最基本的类型,一个key对应一个value。String是二进制安全的,意味着String可以包含任何数据,比如序列化对象或者一张图片。String最多可以放512M的数据。

set

用于设置给定 key 的值。如果 key 已经存储其他值, set 就重写旧值,且无视类型。

set key value

示例:

127.0.0.1:6379> set k1 v1
OK

get

用于获取指定 key 的值。如果 key 不存在,返回 nil 。

get key

示例:

127.0.0.1:6379> get k1
"v1"

append

将给定的value追加到key原值末尾。

append key value

示例:

127.0.0.1:6379> append k2 "aaa"
(integer) 5
127.0.0.1:6379> get k2
"20aaa"

注意:

  • 如果 key 已经存在并且是一个字符串, append 命令将 value 追加到 key 原来的值的末尾。
  • 如果 key 不存在, append 就简单地将给定 key 设为 value ,就像执行 set key value 一样。

strlen

获取指定 key 所储存的字符串值的长度。当 key 储存的不是字符串值时,返回一个错误。

127.0.0.1:6379> strlen k2
(integer) 5

setex

给指定的 key 设置值及time 秒的过期时间。如果 key 已经存在, setex命令将会替换旧的值,并设置过期时间。

setex key time value

 示例:

#向Redis中设置一个k1的键值对并且10秒后过期
127.0.0.1:6379> setex k1 10 v1
OK

应用场景是短信验证码 

setnx

只有在key不存在时设置key的值

setnx key value

示例:

127.0.0.1:6379> setnx k1 v1
(integer) 0
127.0.0.1:6379> setnx k4 v4
(integer) 1

getrange

获取指定区间范围内的值,类似between........and 的关系

getrange key start end

示例:

127.0.0.1:6379> set k5 abcd123xxx
OK
127.0.0.1:6379> getrange k5 2 4
"cd1"

setrange

获取指定区间范围内的值,类似between........and 的关系

setrange key offset value

示例:

127.0.0.1:6379> set k6 abcd1234
OK
127.0.0.1:6379> setrange k6 1 xxx
(integer) 8
127.0.0.1:6379> get k6
"axxx1234"

incr

将 key 中储存的数字值增一。

incr key

示例:

#因为Redis中不存在k1,所以先初始化为0,再递增,值为1
127.0.0.1:6379> incr k1
(integer) 1
# incr k1 存在k1,递增后k1的值为2
127.0.0.1:6379> incr k1
(integer) 2
# 如果value不是数字就会报错
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> INCR k2
(error) ERR value is not an integer or out of range

注意:

  • 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 incr 操作。
  • 如字符串类型的值不能表示为数字、或者是其他类型,那么返回一个错误。

decr

将 key 中储存的数字值减一。

decr key

示例:

127.0.0.1:6379> decr k1
(integer) 1
127.0.0.1:6379> decr k1
(integer) 0
127.0.0.1:6379> decr k1
(integer) -1
127.0.0.1:6379> decr k1
(integer) -2
#如果 
set k2 v2 
decr k2 因为k2不为数值,Redis返回一个错误

注意:

  • 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 decr 操作。
  • 如字符串类型的值不能表示为数字、或者是其他类型,那么返回一个错误。

incrby/decrby key step

将key存储的数字值按照step进行增减。

127.0.0.1:6379> incrby k1 10
(integer) 20

注意:

  • 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 incrby/decrby 命令。
  • 如字符串类型的值不能表示为数字、或者是其他类型,那么返回一个错误。

mset

同时设置一个或多个 key-value 。

mset key1 value1 key2 value2

 示例:

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK

mget

返回所有(一个或多个)给定 key 的值。

mget  key1 key2

示例:

127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"

注意:

如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。

getset

将给定key值设为value,并返回key的旧值(old value),简单一句话(先get然后立即set)。

getset key value

示例:

127.0.0.1:6379> getset k1 wcc
"v1"
127.0.0.1:6379> get k1
"wcc"

使用场景

value 除了是字符串以外还可以是数字。

  • 计数器
  • 统计多单位的数量
  • 粉丝数
  • 对象缓存存储
  • 分布式锁

3.3 List类型

简介

List是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。底层是一个双向链表,对两段操作性能极高,通过索引操作中间的节点性能较差。

lpush/rpush

从左边(头部)/右边(尾部)插入一个或多个值。

lpush/rpush key1 value1  value2 value3……

示例:

#从左边放入v1 v2 v3
127.0.0.1:6379> lpush k1 v1 v2 v3 
(integer) 3

#从右边放入v4 v5 v6
127.0.0.1:6379> rpush k1 v4 v5 v6
(integer) 6

lrange

返回key列表中的start和end之间的元素(包含start和end)。 其中 0 表示列表的第一个元素,-1表示最后一个元素。

lrange key start end

示例:

127.0.0.1:6379> lrange k1 0 -1
1) "v3"
2) "v2"
3) "v1"
127.0.0.1:6379> 

lpop/rpop

移除并返回第一个值或最后一个值。

lpop/rpop  key

示例:

127.0.0.1:6379> lpop k1
"v3"
127.0.0.1:6379> rpop k1
"v1"

注意:

值在键在,值光键亡。

lindex

获取列表index位置的值(从左开始)。

lindex key index

 示例:

127.0.0.1:6379> lindex k1 0
"v2"

llen

获取列表长度。

llen key

示例:

127.0.0.1:6379> llen k1
(integer) 1

lrem

从左边开始删除与value相同的count个元素。

lrem key count value

示例:

#从左边开始删除k1列表中2个v2元素
127.0.0.1:6379> lrange k1 0 -1
1) "v7"
2) "v6"
3) "v5"
4) "v4"
5) "v3"
6) "v2"
7) "v1"
8) "v2"
127.0.0.1:6379> lrem k1  2 v2
(integer) 2
127.0.0.1:6379> lrange k1 0 -1
1) "v7"
2) "v6"
3) "v5"
4) "v4"
5) "v3"
6) "v1"

linsert

在列表中value值的前边/后边插入一个new value值(从左开始)。

linsert key before/after value newvalue

示例:

# 在v1前面插入一个v8
127.0.0.1:6379> linsert k1 before v1 v8
(integer) 7
127.0.0.1:6379> lrange k1 0 -1
1) "v7"
2) "v6"
3) "v5"
4) "v4"
5) "v3"
6) "v8"
7) "v1"

lset

将索引为index的值设置为value

lset key index value

示例:

#将索引为0的值改为aaa
127.0.0.1:6379> lset k1 0 aaa
OK
127.0.0.1:6379> lrange k1 0 -1
1) "aaa"
2) "v6"
3) "v5"
4) "v4"
5) "v3"
6) "v8"
7) "v1"

使用场景

  • 消息队列
  • 排行榜
  • 最新列表

 3.4 Set类型

简介

与List类似是一个列表功能,但Set是自动排重的,当需要存储一个列表数据,又不希望出现重复数据时,Set是一个很好的选择。

Set是String类型的无序集合,它底层其实是一个value为null的hash表,所以添加、删除、查找的时间复杂度都是O(1)。

sadd

将一个或多个元素添加到集合key中,已经存在的元素将被忽略。

sadd key value1 value2……

示例:

#向集合中添加值,最终只有v1 v2 v3 v4 v5 v6
127.0.0.1:6379> sadd k1 v1 v2 v2 v3 v4 v5 v6

smembers

取出该集合的所有元素。

smembers key

示例:

127.0.0.1:6379> smembers k1
1) "v2"
2) "v1"
3) "v3"

sismember

判断集合key中是否含有value元素,如有返回1,否则返回0。

sismember key value

示例:

127.0.0.1:6379> sismember k1 v1
(integer) 1
127.0.0.1:6379> 

scard

返回该集合的元素个数。

scard key

示例:

127.0.0.1:6379> scard k1
(integer) 3
127.0.0.1:6379> 

srem

删除集合中的一个或多个成员元素,不存在的成员元素会被忽略。

srem key value1 value2……

示例:

# 删除v1 v2
srem k1 v1 v2

spop

随机删除集合中一个元素并返回该元素。

spop key

示例:

spop k1 随机删除一个元素,并返回

srandmember

随机取出集合中count个元素,但不会删除。

srandmember key count

示例:

#随机取出集合中的2个元素
srandmember k1 2 

smove

将value元素从sourcekey集合移动到destinationkey集合中。

smove sourcekey destinationkey value

示例:

smove k1 k2 v5 将元素v5从集合k1中移动到集合k2

注意:

如果 sourcekey集合不存在或不包含指定的 value元素,则 smove 命令不执行任何操作,仅返回 0 。

sinter

返回两个集合的交集元素。

sinter key1 key2

sunion

返回两个集合的并集元素。

sunion key1 key2

sdiff

返回两个集合的差集元素(key1中的,不包含key2)

sdiff key1 key2

使用场景

  • 黑白名单
  • 随机展示
  • 好友
  • 关注人
  • 粉丝
  • 感兴趣的人集合

 3.4 Hash类型

简介

Hash是一个键值对的集合。Hash 是一个 String 类型的 field(字段) 和 value(值) 的映射表,hash 特别适合用于存储对象。

  • Hash存储结构优化

    • 如果field数量较少,存储结构优化为类数组结构
    • 如果field数量较多,存储结构使用HashMap结构

hset

给key集合中的field赋值value。相当于是为实体类的属性赋值,此时的属性就相当于是key,属性的值相当于是value。

hset key field value

示例:

#为实体类user的属性name赋值为zhangsan
127.0.0.1:6379> hset user name zhangsan

注意:

  • 如果哈希表不存在,一个新的哈希表被创建并进行 HSET 操作。
  • 如果字段已经存在于哈希表中,旧值将被重写。

hget

从key哈希中,取出field字段的值。相当于是获取实体类哪个属性的值。

hget key field

示例:

#获取实体类user的name属性
127.0.0.1:6379> hget user name
"zhangsan"

hmset

批量设置哈希的字段及值。

hmset key field1 value1 field2 value2……

示例:

#为user实体类设置name、age、addres属性并赋值
127.0.0.1:6379> hmset user name zhangsan age 19 address shandong
OK

hexists

判断指定key中是否存在某个属性。

hexists key field

示例:

#判断user实体类中是否存在name属性
127.0.0.1:6379> hexists user name
(integer) 1

注意:

如果哈希表含有给定字段,返回 1 。 如果哈希表不含有给定字段,或 key 不存在,返回 0 。

hkeys

获取该哈希中所有的field。获取该实体类的属性。

#获取user实体类的全部属性
127.0.0.1:6379> hkeys user
1) "name"
2) "age"
3) "address"

hvals key

获取该哈希中所有的value。获取该实体类的属性值。

hvals key

示例:

127.0.0.1:6379> hvals user
1) "zhangsan"
2) "19"
3) "shandong"

hincrby

为哈希表key中的field字段的值加上增量increment。

hincrby key field increment

示例:

#为实体类user的属性age增1
127.0.0.1:6379> hincrby user age 1
(integer) 20

hdel

删除哈希表 key 中的一个或多个指定字段,不存在的字段将被忽略。

hdel key field1 field2……

示例:

#删除user实体类的age和address属性
127.0.0.1:6379> hdel user age address
(integer) 2

hsetnx

给key哈希表中不存在的的字段赋值 。

hsetnx key field value

示例:

#为user中不存在的属性age赋值,因为age属性不存在所以可以赋值。
127.0.0.1:6379> hset user age 10
(integer) 1

#因为name属性不存在所以无法为该属性赋值
127.0.0.1:6379> hset user name zhangsan
(integer) 0

注意:

  • 如果哈希表不存在,一个新的哈希表被创建并进行 hsetnx 操作。
  • 如果字段已经存在于哈希表中,操作无效。
  • 如果 key 不存在,一个新哈希表被创建并执行 hsetnx 命令。

使用场景

  • 购物车
  • 存储对象

 3.5 Zset

简介

Zset与Set非常相似,是一个没有重复元素的String集合。不同之处是Zset的每个元素都关联了一个分数(score),这个分数被用来按照从低分到高分的方式排序集合中的元素。集合的元素是唯一的,但分数可以重复。-

注意:

因为元素是有序的,所以可以根据分数(score)或者次序(position)来获取一个范围内的元素。

zadd

将一个或多个元素(value)及分数(score)加入到有序集key中。

zadd key score1 value1 score2 value2……

示例:

#java分数是100,c++分数200,c分数300
127.0.0.1:6379> zadd k1 100 java 200 c++ 300 C
(integer) 3

注意:

  • 如果某个元素已经是有序集的元素,那么更新这个元素的分数值,并通过重新插入这个元素,来保证该元素在正确的位置上。
  • 分数值可以是整数值或双精度浮点数。
  • 如果有序集合 key 不存在,则创建一个空的有序集并执行 zadd 操作。

zrange

返回key集合中的索引start和索引end之间的元素(包含start和end)。

zrange key start end [withscores]

示例:

#返回集合中全部的元素
127.0.0.1:6379> zrange k1 0 -1
1) "java"
2) "c++"
3) "C"


#返回集合中的元素和分数
127.0.0.1:6379> zrange k1 0 -1 withscores
1) "java"
2) "100"
3) "c++"
4) "200"
5) "C"
6) "300"

注意:

  • 其中元素的位置按分数值递增(从小到大)来排序。 其中 0 表示列表的第一个元素,-1表示最后一个元素。
  • withscores是可选参数,是否返回分数。

 zrangebyscore

返回key集合中的分数minscore 和分数maxscore 之间的元素(包含minscore 和maxscore )。其中元素的位置按分数值递增(从小到大)来排序。

zrangebyscore key  minscore maxscore [withscores]

示例:

127.0.0.1:6379> zrangebyscore k1 100  300 [withscores]
1) "java"
2) "c++"
3) "C"

zincrby

为元素value的score加上increment的值。

zincrby key increment value

示例:

#给Java加50
127.0.0.1:6379> zincrby k1 50 java 
"150"

zrem

删除该集合下value的元素。

zrem k1 php 删除php

zcount

统计该集合在minscore 到maxscore分数区间中元素的个数。

 zcount key minscore maxscore

示例:

zcount k1 100 300 统计100分到300分中间元素的个数

zrank

返回value在集合中的排名,从0开始。

zrank key value

示例:

zrank k1 c++ 返回c++排名

使用场景

  • 延时队列
  • 排行榜
  • 限流

 3.6 itmaps类型(较难理解)

简介

在计算机中,用二进制(位)作为存储信息的基本单位,1个字节等于8位。

例如 "abc" 字符串是由 3 个字节组成,计算机存储时使用其二进制表示,"abc"分别对应的ASCII码是97、98、99,对应的二进制是01100001、01100010、01100011,在内存中表示如下:

合理地使用位能够有效地提高内存使用率和开发效率。

Redis提供了Bitmaps这个 “数据结构” 可以实现对位的操作:

setbit

设置Bitmaps中某个偏移量的值。

setbit key offset value

示例:

redis中bitmaps可以用来统计用户信息,eg:活跃天数、打卡天数、登录天数

bitmaps位图,都是操作二进制来进行记录,就只有0和1两个状态

统计zhangsan4月份考勤情况,第二个数字代表的是天,第三个数字代表是否迟到:1未迟到0迟到
127.0.0.1:6379> bitcount zhangsan
(integer) 0
127.0.0.1:6379> setbit zhangsan:4 1 1
(integer) 1
127.0.0.1:6379> setbit zhangsan:4 2 1
(integer) 1
127.0.0.1:6379> setbit zhangsan:4 3 0
(integer) 0
127.0.0.1:6379> setbit zhangsan:4 4 1
(integer) 1

注意 最后一个数字是偏移量的值,倒数第二个叫偏移量。

getbit

获取Bitmaps中某个偏移量的值。

getbit key offset

示例:

获取key的offset 的值。

127.0.0.1:6379> getbit zhangsan 3
(integer) 0

bitcount

统计字符串被设置为1的bit数量。一般情况下,给定的整个字符串都会被进行统计,可以选择通过额外的start和end参数,指定字节组范围内进行统计(包括start和end),0表示第一个元素,-1表示最后一个元素。

bitcount key [start end]

示例:

bitcount sign 获取整个字符串被设置为1的bit数量,结果为3

如:当前存在一个key为k1的bitmaps存储着[00000001,00000001,00000010,00000011],分别对应[1,1,2,3]。

setbit num 7 1  
setbit num 15 1
setbit num 22 1
setbit num 30 1
setbit num 31 1

bitcount num 1 2 统计索引1、2两个字节组中bit=1的数量,即统计00000001,00000010中bit=1的数量,结果为2
bitcount num 1 3 统计索引1、2、3三个字节组中bit=1的数量,即统计00000001,00000010,00000011中bit=1的数量,结果为4
bitcount num 0 -1 统计所有的字节组中bit=1的数量,结果为5

setbit设置或获取的是bit(位)的位置,bitcount计算的是byte(字节)位置。

bitop

将多个bitmaps通过求交集/并集方式合并成一个新的bitmaps。

bitop and/or destkey sourcekey1 sourcekey2……

示例:

bitop and k3 k1 k2 通过求交集将k1 k2合并成k3
bitop or k3 k1 k2  通过求并集将k1 k2合并成k3

使用场景

  • 活跃天数
  • 打卡天数
  • 登录天数
  • 用户签到
  • 统计活跃用户
  • 统计用户是否在线
  • 实现布隆过滤器

 3.7 Geospatia

简介

GEO,Geographic,地理信息的缩写。该类型就是元素的二维坐标,在地图上就是经纬度。Redis基于该类型,提供了经纬度设置、查询、范围查询、距离查询、经纬度Hash等常见操作。

geoadd

用于存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。

geoadd key longitude latitude member

示例:

# 将北京的经纬度和名称添加到china
geoadd china 116.405285 39.904989 beijing 
# 将成都和上海的经纬度、名称添加到china
geoadd china 104.065735 30.659462 chengdu 121.472644 31.231706 shanghai 

geopos

从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。

geopos key member [member ……]

示例:

#返回china中名称为shanghai和beijing的经纬度
geopos china shanghai beijing 

geodist

用于返回两个给定位置之间的距离。

geodist key member1 member2 [m|km|ft|mi]

参数说明:

  • m :米,默认单位。
  • km :千米。
  • mi :英里。
  • ft :英尺。
# 返回shanghai和beijing之间的距离,结果1067597.9668,单位米
geodist chinacity shanghai beijing 
# 返回shanghai和chengdu之间的距离,结果1660.0198,单位是千米
geodist chinacity shanghai chengdu km 

georadius

以给定的经纬度(longitude latitude)为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离(radius )的所有位置元素。

georadius key longitude latitude radius m|km|ft|mi 

示例:

#获取经纬度110 30为中心,在china内1200公里范围内的所有元素。
georadius china 110 30 1200 km 

使用场景

  • 附近的电影院

  • 附近的好友

  • 离最近的火锅店

前提是将这些商铺的坐标放到redis 

3.8 HyperLogLog

简介

在我们做站点流量统计的时候一般会统计页面UV(独立访客:unique visitor)和PV(即页面浏览量:page view)。redis HyperLogLog是用来做基数统计的算法,HyperLogLog的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且使很小的。

什么是基数

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

pfadd

将所有元素参数添加到 Hyperloglog 数据结构中。

pfadd key element1 element2……

示例:

如果至少有个元素被添加返回 1, 否则返回 0。

pfadd book1 uid1 uid2 uid3

注意:

添加元素到HyperLogLog中,如果内部有变动返回1,没有返回0。

pfcount

计算Hyperloglog 近似基数,可以计算多个Hyperloglog ,统计基数总数。

pfcount key1 key2……

示例:

pfcount book1       #计算book1的基数,结果为3
pfadd book2 uid3 uid4   #添加两个元素到book2中
pfcount book1 book2    #统计两个key的基数总数,结果为5

pfmerge

将一个或多个Hyperloglog(sourcekey1) 合并成一个Hyperloglog (destkey )。

pfmerge destkey sourcekey1 sourcekey2……

示例:

比如每月活跃用户可用每天活跃用户合并后计算。

#将book1和book2合并成book,结果为5
pfmerge book book1 book2 

使用场景

基数不大,数据量不大就用不上,会有点大材小用浪费空间,有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么,和bitmap相比,属于两种特定统计情况,简单来说,HyperLogLog 去重比 bitmaps 方便很多,一般可以bitmap和hyperloglog配合使用,bitmap标识哪些用户活跃。

  • 网站PV统计
  • 网站UV统计
  • 统计访问量(IP数)
  • 统计在线用户数
  • 统计每天搜索不同词条的个数
  • 统计文章真实阅读数

四、Redis可视化工具:Redis Destktop Manager(带时间)

 4.1 下载Redis Desktop Manager

官网RESP.app (formerly Redis Desktop Manager) - GUI for Redis ® available on Windows, macOS, iPad and Linux.

 或者使用文章配套的安装包。

4.2 连接Redis服务

关闭防火墙

systemctl stop firewalld.service
在redis配置文件中关闭保护模式
protected-mode no

开启远程访问

redis默认只允许本地访问,要使redis可以远程访问可以修改redis.conf。

注释掉bind 127.0.0.1 可以使所有的ip访问redi 

 重启redis服务

#查看redis端口
lsof -i:6379

#杀死redis进程
kill -9 PID

#重启reids服务
[root@localhost src]# ./redis-server ../redis.conf

- 出现16个库代表连接成功

 五、Java整合Redis

5.1 Jedis操作

什么是Jedis

Jedis是Redis官方推荐的Java连接开发工具。

创建maven工程引入maven依赖
<dependency>
   <groupId>redis.clients</groupId>
   <artifactId>jedis</artifactId>
   <version>3.6.0</version>
</dependency>
<dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.12</version>
   <scope>test</scope>
</dependency>
Jedis连接到redis并操作redis
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.List;
import java.util.Set;

public class TestJedis {

    Jedis jedis;


    //1.创建连接实例
    @Before
    public void init(){
        /*
         * 第一个参数:redis服务的ip
         * 第二个参数:redis服务的端口
         */
        jedis = new Jedis("192.168.66.100",6379);
    }

    //2.Jedis-API:String
    @Test
    public void stringTest(){
        //设置一个键值对
        jedis.set("k1","v1");
        //通过key获取value
        String k1 = jedis.get("k1");
        System.out.println(k1);
        //对某一个key自增
        Long ires = jedis.incr("k2");
    }

    //2.Jedis-API:Keys
    @Test
    public void keysTest(){
        //返回所有的key
        Set<String> keys = jedis.keys("*");
        //返回该key剩余过期时间
        Long time = jedis.ttl("k1");
    }

    //3.Jedis-API:List
    @Test
    public void listTest(){
        //向list中添加数据
        jedis.lpush("list1","v1","v2","v3");
        //返回list全部数据
        List<String> list = jedis.lrange("list1",0,-1 );
    }

    //4.Jedis-API:Set
    @Test
    public void setTest(){
        //向set中添加数据
        jedis.sadd("set1" ,"v1","v2","v2","v3");
        //查看该集合中有多少个元素
        jedis.smembers("set1");
    }

    //4.Jedis-API:Hash
    @Test
    public void hashTest(){
        //设置一个hash
        jedis.hset("user","age","25");
        //获取该key的所有value
        jedis.hvals("user");
    }

    //4.Jedis-API:Zset
    @Test
    public void zsetTest(){
        //向zset中添加一条数据
        jedis.zadd("zset1",100,"java");
       //获取所有的值
        jedis.zrange("zset1",0,-1);
    }

    //5.Jedis-API:Bitmaps
    @Test
    public void bitmapsTest(){
        //将b1偏移量为0的位设置为1
        jedis.setbit("b1",0, "1");
        //获取b1偏移量为0的位
        jedis.getbit("b1",0);
    }

    //6.Jedis-API:Geospatia
    @Test
    public void geospatiaTest(){
        //添加一条地理信息数据
        jedis.geoadd("chinacity",130,110,"beijing");
    }

    //7.Jedis-API:Hyperloglog
    @Test
    public void hyperloglogTest(){
        //将所有元素参数添加到 Hyperloglog 数据结构中。
        jedis.pfadd("book","c++","java","php");
    }
    
    @After
    public void close(){
        //1.关闭连接实例
        jedis.close();

    }
}

注意:

其实jedis中的方法基本同redis命令一致。

 5.2 Spring-Data-Reids

简介

Spring-Data-Redis是spring大家族的一部分,通过简单的配置访问Redis服务,对Reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了Redis各种操作、异常处理及序列化,支持发布订阅。

RedisTemplate介绍

Spring封装了RedisTemplate对象来进行对Redis的各种操作,它支持所有的Redis原生的api。

org.springframework.data.redis.core
Class RedisTemplate<K,V>

注意:

  • K:模板中的Redis key的类型,模板中的Redis key的类型(通常为String)如:RedisTemplate<String, Object>。
  • V:模板中的Redis value的类型

RedisTemplate中定义了对5种数据结构操作 

redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set

StringRedisTemplate与RedisTemplate

  • 两者的关系是StringRedisTemplate继承RedisTemplate。

  • 两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。

  • SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。

    StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。

    RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。

 创建springboot项目并添加依赖

 <dependencies>
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
     </dependency>
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-web</artifactId>
     </dependency>
     <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <optional>true</optional>
     </dependency>
     <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>io.projectreactor</groupId>
       <artifactId>reactor-test</artifactId>
       <scope>test</scope>
     </dependency>
   </dependencies>

在application.properties中配置

#Redis服务器连接地址
spring.redis.host=192.168.56.31
#Redis服务器连接端口
spring.redis.port=6379
#连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
#连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
#连接池中的最大空闲连接
spring.redis.pool.max-idle=8
#连接池中的最小空闲连接
spring.redis.pool.min-idle=0
#连接超时时间(毫秒)
spring.redis.timeout=30000

自定义序列化

RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。该配置是将其序列化的方式改为json序列化。 

package com.zj.redis.conf;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * 自定义序列化方式
 */
@Configuration
public class RedisConfig {
   @Bean
   public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
     RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
     redisTemplate.setKeySerializer(new StringRedisSerializer());
     redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
     redisTemplate.setHashKeySerializer(new StringRedisSerializer());
     redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
     redisTemplate.setConnectionFactory(redisConnectionFactory);
     return redisTemplate;
   }
}

没有配置自定义序列化之前:

 配置自定义序列化之后:注意此时是json类型

package com.zj.redis;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.util.Set;

@SpringBootTest
class RedisApplicationTests {

    @Resource
    private RedisTemplate redisTemplate;


    /*
    * 使用RedisTemplate实例操作redis
    * */
    //操作字符串
    @Test
    void contextLoads() {
        //添加数据(key是name,value是zhangsan)
        redisTemplate.opsForValue().set("name", "zhangsan");
        //获取数据
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

    //操作list
    @Test
    void contextLoads2(){
        //添加数据
        redisTemplate.opsForList().rightPush("age",23);
        redisTemplate.opsForList().rightPush("age",24);
        redisTemplate.opsForList().rightPush("age",25);
        //获取最右边的数据
        System.out.println(redisTemplate.opsForList().rightPop("age"));
    }

    //操作Hash
    @Test
    void contextLoads3(){
        //添加
        redisTemplate.opsForHash().put("user","name","张三");
        redisTemplate.opsForHash().put("user","age",12);
        redisTemplate.opsForHash().put("user","address","临沂市");
        //获取
        System.out.println(redisTemplate.opsForHash().get("user", "name"));
    }

    //操作set
    @Test
    void contextLoads4(){
        //添加
        redisTemplate.opsForSet().add("verse","两岸猿声啼不住,轻舟已过万重山。","莫愁前路无知己,天下谁人不识君。",
                                                   "大鹏一日同风起,扶摇直上九万里。");
        //获取
        Set verse = redisTemplate.opsForSet().members("verse");
        System.out.println(verse);
        //获取元素长度
        System.out.println(redisTemplate.opsForSet().size("verse"));

    }

    //操作ZSet
    @Test
    void contextLoads5(){
        //添加元素
        redisTemplate.opsForZSet().add("programmer","Dennis MacAlistair Ritchie",100);
        redisTemplate.opsForZSet().add("programmer","Linus Benedict Torvalds",99);
        redisTemplate.opsForZSet().add("programmer","Bjarne Stroustrup ",98);
        redisTemplate.opsForZSet().add("programmer","Brain Wlison Kernighan",97);
        //获取元素
        Set programmer = redisTemplate.opsForZSet().range("programmer", 0, -1);
        System.out.println(programmer);
        //通过分数获取元素
        Set programmer1 = redisTemplate.opsForZSet().rangeByScore("programmer", 90, 100);
        System.out.println(programmer1);
    }

}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张小猿ε٩(๑> ₃ <)۶ з

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值