最近想优化社团的品牌活动----一个在线答题系统,峰值在400人左右,所以入门Redis来优化
1. 什么是redis?
redis是基于内存来储存非关系型数据的键值对数据库。支持数据的持久化(重启加载)与多数据类型(Stirng、Hash、Set、List 、Zset)
2. 为什么要用redis
高并发:我们知道内存速度远高于硬盘(一般差3个数量级),redis做热点数据的操作,并且配合基于硬盘的数据库,可以在内存中将热点数据分流,不用每次经过硬盘数据库
高可用:redis可以实现集群与分布式
原子性:所有操作都是原子性,因为采用单线程处理,无需考虑并发
可过期:最让我眼前一亮的是,可过期,和自动删除,但属于惰性删除和随机
redis是以键值对来存储数据的,虽然Java也有对应的key-value数据结构(map集合),但如果用java的那么数据的过期时间机制需要我们自己实现,而且java的集合是在本地保存的,在多个服务器上无法保持一致性。而且redis可以是数据的持久化,并重返回内存,提供了5大数据类型,这无疑比java的集合要好很多
特点
C语言编写,基于内存操作,速度快
单线程任务机制进行工作,多线程同步
所有操作是原子性的,使用了队列,支持事务
单个key存入512M大小,key一定是字符串
应用
热点数据加速查询(热点新闻,商品)
即时信息查询(排行榜,在线人数,访问统计,点赞,浏览次数)
任务队列(秒杀,抢购)
时效性信息控制(手机验证码,限时优惠活动,登录次数)
分布式数据共享(Session分离,Token管理)
分布式锁、集群
3 安装与启动
redis官网只有Linux版本,目前最新稳定版到5.0,而Windows版已经没有更新(最近发布在16年),所以这里安装Linux版本 redis官网下载传送门
官网有安装编译介绍,对着命令行打就对了,这里做简单提示
//下载redis压缩包
$ wget http://download.redis.io/releases/redis-5.0.7.tar.gz
//解压下载的包
$ tar xzf redis-5.0.7.tar.gz
//进入解压的包
$ cd redis-5.0.7
//编译
$ make
//运行redis服务
$ ./redis-server
//打开客户端
$ ./redis-cli
4. Redis的配置文件(redis.conf)
- daemonize no:建议修改为守护进程
- port 6379
- bind 127.0.0.1:注释掉才能外部机器访问
- loglevel notice:日志记录级别
- database 16:默认16个库
- save
:多少次更新操作就将数据同步到数据文件,可以多个条件配合(持久化机制) - save 900 1
- save 300 10
- save 60 10000
- rdbcompression yes:本地存储压缩
- dbfilename dump.rdb:持久化文件名字
- dir ./:持久化文件存储地址
- masterauth
<master-password>
:主从复制的密码 - requirepass :登录密码,默认注释掉,没有密码
- maxclients 0:同一时间最大客户端连接数,默认无限制
- timeout:300 空闲客户端最大时长
启动服务端
./redis-server ./redis.conf
远程访问
redis-cli -h IP地址 -p 端口号 -a 密码
关闭
客户端运行shutdown
5. 通用命令
keys pattern ( * 任意数量的任意字符,?一个任意字符,[]一个指定字符)
type key 返回类型
del keys 返回影响条数
exists keys 返回存在的条数
expire key seconds 设置过期时间
pexpire key milliseconds 设置毫秒过期时间
expireat key timestamp 设置过期时间戳
ttl key 查看过期时间:-1永久,-2过期
pttl key 查看过期时间:返回的是毫秒
persist key:移除过期时间,永久保持
random key 随机获取一个key
rename key 改名(若改名存在的key名,则覆盖)
renamenx 不存在才改名
move key 移动key到指定数据库
move key 数据库名称 将当前key移动另个库
sort [desc limit pattern]针对集合排序不对原数据修改
select index 切换数据库
flushdb 清空当前数据库
flushall 清空所有数据库
dbsize key数量
echo message 输出到控制台日志
ping 测试连通
quit
help 命令名称
help @组名
clear 清屏
事例
set user:1 a
set user:2 b
keys user:? 将二者列出,*统配符,?单个通配符
6. Key命名规范
- key太长,降低查找效率、不短,可读性降低
- 同一命名模式:表名:主键名:主键值:字段名
- user : id : 01 : username ----- howl
- user : id : 01 : password ----- 123456
7. 数据类型
key最大512M,建议不超过1025字节
1. String
mset / mget 批量读写,缺点数据量太多时候还是要切小点来批量处理
set key value 重复设置是覆盖,String可以不管类型覆盖
setnx name key 键不存则新建,存在不覆盖返回0 // 重点分布式锁的命令,面试题,为数据库id提供生成策略,保证唯一
setex/psetex key seconds value // 重新设置会被清除覆盖
get key 获取值,不存在返回nil(查询非字符串报错)
getrange start end 截取,不包括尾
getset key value 设置指定key值,并返回旧key值;若key不存在,设置set key后返回nil
incr key 自增,返回增后的值;key不存在会先set为0,再自增,返回1
decr key 自减,返回减后的值;key不存在会先set为0,再自减,返回-1
incrby key num 自增/减num
decrby key num 自增/减num
incrbyfloat key 1.5 可以小数,前面的不行
strlen key 返回字符串长度
getbit key offset 偏移量
append key value 追加,返回总长度,没有就新键
视为数值操作的最大值
可以利用这个限制登录次数:eg一开始就设置为2^63-10,登录一次加10,到第10次就报错
long:8字节,2^63
应用场景:
- 计数器统计(投票,粉丝数)
- 保存单个字符串或JSON
- 二进制安全(存文件、图片、序列化对象)
2. Hash
存取键值对,一个Sting类型的 field 和 value 的映射表,是个K/V形式的集合,特别适合存储对象。
一个hash可以存2^32 -1 个数据(40亿),而且占用内存少。笔者称field 为字段,value为值
hash类型下的value只能存字符串,不能存储其他类型了, 即不能嵌套
形式: user:1(key) (hash)id:1 name:howl age:20
hset key field value
hsernx key filed value
hmset key field value field,value
(这里获取的是值)
hget key field
hmget key field field
hgetall key 取出所有内容,包括字段与值(先列字段,再列值)
(这里获取的是字段)
hkeys key 取出全部字段
hvals key 取出全部值
hlen key 多少个字段
hdel key field[field] 删除某个字段
hincrby user:1 age 10
hincrbyfloat user:1 age 10
hexists key field 是否存在字段
应用场景:
- 存储用户对象信息
- 如果用String存对象,如果用json,就要转换成bean对象,且修改字段要排队。比如连续修改同一个字段,要将json取出修改完存进去,然后再取出来修改存进去(CAS问题)。而Hash类型可以直接修改字段。;第二种:字段分开存,即有多少个字段,存多少条记录,条数过多内存浪费
- 抢购、激活码等
3. List
模拟栈和队列,类似于Java中的LinkedList类型,是简单的字符串列表(每个key里面可以存字符串列表),可以从头部或尾部添加元素。列表可以包含2^32-1个元素(40亿了),注重进出顺序
lpush key value1 value2 从左边添加元素,头插法咯
rpush key value1 value2 从右边添加元素
lpushx key value 从已存在的列表中插入值,若列表不存在报错
lpop key 弹出左侧第一个元素
rpop key 弹出右侧第一个元素
llen key 获取列表长度
lindex key index 通过索引获取列表中的元素
lrange key start stop 范围获取,-1标识最后一个,-2倒数第二个
lset key index value 通过索引设置列表元素的值
lrem key count value count是删除几个,可以重复的
linsert ket before/after world value 在该元素前后插入
blpop/brpop key timeout 弹出元素,没有就会阻塞直到等待超时或发现可弹出元素位置(任务队列)
rpoplpush source destination 将source右弹出的元素加入destination的左侧
rpoplpush a1 a1 可以实现循环,即右侧的元素放到左侧
应用场景:
- 对数据量大的集合集合数据测(列表数据显示(有顺序的):关注列表,粉丝列表,留言评论?分页,热点新闻(top5))
- 消息队列,而且可以确保那样还需通过ORDER BY 进行排序,订单下单流程,注册短信,邮箱等功能。需要两个队列,还是一个就可以? 一个添加消息,一个消费消息,使用rpoplpush命令。然后举例物流,从这两个列表,可以查询出快递到哪,还有多少任务未完成
4.Set
存取速度快,成员是唯一的,是String类型的无序集合。底层通过哈希实现,所以添加删除查找都是O(1),使用inset和hashtable实现。
sadd key memeber1 member2 向集合添加成员(重复添加返回0)
smembers key 获取集合的所有成员
srandmember key[count] 返回集合中的随机(抽奖),原集合内容不变
spop key [count] 随机弹出数据
scard key 获取集合成员数
sismember key member 判断 memeber 元素是否在存在
srem key member1 member2 删除集合成员
spop key[count] 随机弹出集合元素
smove source destioantion member 将member元素从source移到destinatio中
(集合运算)
sdiff key1 key 返回差集(从key1看)
sinter key1 key2 返回交集
sunion key1 key2 返回并集
sdiffstore destinatio key[key] 将集合运算结果存储到指定集合
应用场景:
- 共同关注,共同喜好(集合运算等)
- 随机信息检索,歌单推荐,新闻推荐
- 黑白名单
- 唯一性,可用于访问网站的独立IP
- 访问量,String即可
- 独立访客,Set记录不同的cookie数量
- 独立IP,Set记录不同IP
5.ZSet
String类型的不允许重复,但有序的集合。因为每个元素会关联一个double类型的分数,利用这个分数来为集合排序,而分数是可以重复的
这里注意:重复添加返回0,说明数据没有新添加,但权值是会改变的
zadd key score member [score member] 添加集合元素
zrank key member 返回指定成员的索引(排序下标)
zrange key start stop [withscore] 返回区间内的成员(低到高),后面带上了权值
zrevrange key start stop [withscore] 通过索引区间返回有序集合指定区间内的成员(高到低)
zrangebyscore key min max [withscores] [limit 0 3]
zrevrangebyscore key min max [withscores] [limit 0 3]
zremrangebyrank key start stop 移除指定排名区间所有成员
zremrangebyscore key min max 移除给定分数区间的所有成员
zrem key member[member] 移除集合中的成员
zcard key 获取有序集合的成员数量
zcount key min max 计算在有序集合中指定分数区的成员数
zinterstore destionation numkeys key key 集合操作
zincrby key increment member 权值增减(可以作为排行榜的得分来排序)
应用场景:
- 排行榜
- 将发表时间作为score来存储
- 带权的队列(扩展列表完全按序的功能)
- 涨幅跌幅
8. Java连接Redis
这里使用Jedis来连接,关系就像jdbc之于mysql,笔者使用pom.xml导入依赖
Jedis的方法名和redis的一模一样,所以熟悉redis命令即可熟悉使用Jedis
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
public static void main(String[] args) {
// 配置信息,建议放外部文件,这里为了演示
int port = 6379;
String password = "123456";
String host = "47.56.143.47";
// 连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(10); // 连接数
poolConfig.setMaxIdle(5); // 空闲数
// 获取连接,获取了连接还需要验证密码
JedisPool pool = new JedisPool(poolConfig, host, port);
Jedis jedis = pool.getResource();
jedis.auth(password);
// 二进制安全,String
jedis.set("中文", "中文");
System.out.println(jedis.get("中文"));
// List
jedis.lpush("list","list1");
jedis.rpush("list","list2");
List<String> list = jedis.lrange("list",0,-1);
for(String value : list){
System.out.println(value);
}
// Hash
jedis.hset("hset","field1","value1");
jedis.hset("hset","field2","value2");
Map<String,String> map = jedis.hgetAll("hset");
for(String key : map.keySet()){
System.out.println(map.get(key));
}
// 关闭
jedis.close();
}
9. Redis Manager
Redis之于Redis Manager 就像MySQL之于Navicat,是可视化的Redis连接管理工具。有个坑,不知道大家有没有遇到,输入命令行键盘上的两个回车作用不一样,大回车键是执行命令,右下角的回车是换行功能。
界面操作简单,一个个点击操作下就知道如何使用了