目录
一、redis是什么?
二、redis的特性
三、reidis使用场景
四、redis的启动方式
五 、redis的API使用
1 . 通用命令
a 、通用命令
b、数据结构和内部编码
c、单线程
2 . Redis API的理解和使用
a . 字符串
b . hash
c . list
d . set(集合)
e . zset(有序集合)
六、java客户端(jedis)使用redis
1 . 开源框架; 2 . 多种数据结构; 3. 基于键值对的存储服务系统; 4. 高性能、功能丰富。
1. 速度快:
最关键的就是直接存储在计算机内存中; 第二使用的线程模型是单线程模型; 第三redis使用c语言编写(50000行左右)。
2. 持久化:
数据虽然都是存储在内存中的,但是redis会异步的将数据存储到硬盘中。
操作系统也有这种特性,在计算机蓝屏是会将内存数据dump到硬盘,下次开机时从硬盘重新读取到内存。
3. 支持多种数据结构:
基于键值对的存储,key使用string保存,value有5种基础的数据结构:(1)String;(2)有序列链表List;(3)无序列集合set;(4)无序散列表;(5)有序排序集合zset
4. 支持多种编程语言:
redis协议为简单的tcp协议,所以他支持非常多的客户端语言
5. 功能丰富:
注册、订阅实现消息队列; 支持事物; lua脚本; pipeline
6. 简单:
这里的简单从片面上说最初的代码长度才23000行,现在的不止了;当然它的简单还指它不依赖其他外部库; 同时它的线程模型都是单线程,所以无论服务端还是客户端都方便开发。
7. 主从复制:
redis提供了主服务器和从服务器的模式,主从服务器间进行同步数据。
8. 高可用,分布式:
redis2.8 之后提供了redis-sentinel 支持高可用;
redis3.0之后提供了redis-cluster 支持分布式;
1 . 简单的消息队列 (利用redis 的注册和订阅服务)
2 . 分布式锁, session共享池, 计数器(利用redis单线程的唯一性);
3 . 缓存中间件(适配服务与数据库之间的访问量);
1 . 安装redis 参考官方文档
$ wget http://download.redis.io/releases/redis-5.0.0.tar.gz $ tar xzf redis-5.0.0.tar.gz $ cd redis-5.0.0 $ make
2 . redis src下的可执行文件:
3 . 启动方式
(a) 直接启动redis-server , 采用默认配置(直接就是redis-server);
(b) 动态参数配置,可指配端口号等(如 redis-server --port 6380);
(c) 采用配置文件启动,也是最常用的启动方式(生产环境下推荐,如 redis-server conf/redis6380.conf, 即用同目录下的conf下的redis6380.conf配置启动redis服务);
4 . 连接方式
(1) redis-cli (直接连接)
(2)redis-cli -h 127.0.0.1 -p 6380 (连接本地6380端口的redis)
5. 关闭redis
(1) redis-cli -a 密码 -h 127.0.0.1 -p 6379 shutdown
① keys : 得到redis中所有的key, 一般结合正则表达式来使用,即 keys 【pattern】, 如keys * 得到所有的key,keys he? 得到以he开头的第三个字符任意的三位字符的key,它的时间复杂度为O(n),所以线上环境不推荐使用。
② dbsize :得到redis中key的个数。它的时间复杂度为O(1),原因是redis中维护了这个计数器,不是遍历得到的。
③ exists 【key】:判断key在redis中是否存在,存在为1,不存在为0,时间复杂度为O(1)
④ del 【key ...】 : 删除key,删除成功为1,key不存在为0,时间复杂度为O(1)
⑤ expire 【key】【seconds】:设置key的过期时间(秒级别),时间复杂度为O(1);
ttl 【key】:查看这个key剩余多久的过期时间,不过期的key返回 -1,key不存在为 -2,时间复杂度为O(1);
persist 【key】 : 清除这个key的过期时间,这个key就不过期,时间复杂度为O(1);
⑥ type 【key】:返回这个key的类型,有string,set, zset, list,hash,null(key不存在时),时间复杂度为O(1)
⑦ info memory : 查看redis 占用内存等信息。
这里上两张图,图一是redis的数据结构和内部编码结构,通过这个图我们知道在外看来hash结构在内部实际有两种内部编码格式,一个是hashtable,另一个是ziplist。那这个的好处是什么?学过java的可以联想面向接口编程,对内扩展,对外统一原则,什么时候用什么内部编码就相当于到底选择用时间换空间还是用空间换时间,因为不同的内部编码会有不同的空间存储方式和不同的时间效率。
图二是redis内部的一个数据结构叫redisObject,这里举出了几个关键的属性,第一个属性就是数据类型,第二个是编码方式。
redis在处理客户端的命令是采用的是单线程模型,用一个线程处理多个客户端的请求命令。
那为什么redis的处理速度很快呢? 其实也就是它的存储介质是内存(最关键),第二是它采用的是非堵塞式的处理请求,第三因为是单线程所以避免了线程间的数据切换和竞争。
因为单线程,所以如果单条命令处理时间过长的话会造成堵塞,因此redis要避免长命令,如keys, flushall等。
① 结构和命令
key value 结构,key都是字符串,value有上图说的5种类型,虽说value存的是字符串(实际存储的都是二进制),但是value可以存储多种,如字符串(world),数字(1, 2.2), 序列化数据(如json数据)等。
get, set:专门针对字符串的命令,得到和设置key的值。
incr, decr,incrby,decrby:第一个是自增1(incr count),第二个为自减1(decr count),第三个是自增自定义的数(incrby count 2),第四个是自减自定义的数(decrby count 2)
setnx:key不存在则设置值为value,存在则失败。(n为null的意思,setnx可以理解为为add操作)
set key value xx : key存在则设置value覆盖原来的值,不存在则失败。(set key value xx 可以理解为update操作)
mget key1 key2 key3 :批量得到多个key的值,原子操作,O(n),效率比多次获取快了 n-1 次网络时间。
mset key1 value1 key2 value2 key3 value3 :批量设置多个值,O(n)。
② 内部编码
String 内部编码使用以下三种类型:
i):int ---》 8个字节的长整形
ii): embstr ----》 小于等于39个字节的字符串
iii):raw ----》 大于39个字节的字符串
Redis 根据String 长度自动决定编码类型
查看内部编码类型可用 object encoding key 来查看
③ 快速实战
记录网站每个用户页面的访问量:incr userid:pageView ---->(key用userid + 页面名称的字符串来记录)
缓存视频网站的热门视频信息: set vid vedioInfo (key用vid表示,值是视频的二进制序列化文件)
④ 查漏补缺
getset key value: 设置key为value,返回oldvalue。
append key value: 追加value
strlen key : 计算key的长度,O(1),和dbsize类似,有专门存长度的属性。
incrbyfloat key value: 给浮点数自增自定义的数(incrbyfloat count 1.2)
getrange key a b: 得到key中value的第a个到第b个字符,从0开始算。
setrange key position newValue : 将position位置的value用newValue设置。
① 特点,结构
相当于Mapmap,在一个中还有一个map,一个hash也可以理解成数据库一张表的一行数据,一行数据有很多属性和值,但是每个hash中的属性可以不同。
② 命令
所有命令以h开头
hget,hset,hdel:和string的操作一致,如 hget key field ,hset key field value,hdel key field
hexists,hlen :判断是否存在字段和判断hash有多少个字段, 如hexists key field, hlen key 时间复杂度为O(n) (这里不知道怎么获取hash中某个字段的长度)
hmget,hmset : 一次返回 / 设置一个hash中的多个field的值 , 如hmget key field1 field2 field3, hmset key field1 value1 field2 value2 field3 value3 ,时间复杂度 O(n)
hincrby key field count : 给key中field增加count值, 这个count可以是负数(一个命令代替了string的incr,decr,incrby,decrby)
hgetall key : 返回key的所有field和value, O(n),在hash field和value很多的时候要注意,可以用hmget代替
hvals key,hkeys key : 得到key的所有value值, 得到所有key的field值。
③ 快速实战
记录每个用户个人主页的访问量: hincrby user:1 pageView 1 (这个和上面直接用string记录count不一样的是这是用一个user的一个pageView字段来记录访问量,key为user:1, field为页面名,每次添加量为1)
④ 查漏补缺
hsetnx key field, hincrbyfloat key field float : 用法和string用法一致。
① 特点
有序(遍历的时候按照插入顺序打出),可以重复,左右两边都可以插入弹出。
这里放一张list内部编码为linkedlist 结构的图
② 命令API (从增删改查4个方面来讲述)
增加
rpush key value1 value2 value3 :从右边插入value,最后结果就是 value1,value2, value3, 时间复杂度为O(n)
lpush key value1 value2 value3 : 从左边插入value,最后结果是value3,value2, value1,时间复杂度为O(n)
linsert key before | after value newValue :在value before | after 插入newValue,时间复杂度为O(n)
删除
lpop key : 弹出左边的第一个值,O(1)
rpop key : 弹出右边的第一个值,O(1)
lrem key count value : 删除key中count数量的value值,count>0 时从左到右删,count<0 时从右到左删count绝对值的数,=0 时删除所有value值。
ltrim key start end : 保留 [start, end] 的值,O(n),适合对大表的修建,比如每次保留90%
查找
lrange key start end : 查找 [start, end] 范围内的数, 时间复杂度O(n),-1为最后一位数,[0, -1] 即查找所有的值
lindex key index : 查找下标为index的值,时间复杂度O(n), lindex key -2 , 即查找倒数第二个数
llen key : 获取list的长度, 时间复杂度为O(1)
修改
lset key index newValue : 修改下标为index的值为newValue,时间复杂度为O(n)
③ 快速实战
时间轴微博展示: 微博关注的人发了新微博展示在时间轴最前面(用列表的lpush),并且得到范围内的数据做分页(用range)
④ 查漏补缺
blpop,brpop key timeout:阻塞弹出,等待 timeout时间去弹出,超出timeout则失败,timeout=0为永不等待。
① 特点
无序,不重复,支持集合间的操作
② 集合内API
sadd key element : 新增元素,element 可以为多个, 时间复杂度和添加的元素相关
srem key element: 删除元素,element可以为多个,时间复杂度和添加的元素相关
scard key : 得到key中的元素个数,O(1)
sismember key element :判断element是否在集合中。
srandmember key count : 随机得到集合中count个数的值,就是随机查询,不删除。
smembers key : 得到集合中的所有元素, 返回结果无序,小心,集合中的数据太大时会造成redis堵塞。在生产环境可以用sscan代替。
spop key : 随机弹出一个元素 。 实战中可以用来当做抽奖系统选中奖用户,所有用户是一个集合,随机弹出中奖用户。
③ 集合间API
sdiff set1 set2 : 差集,set1 - set2
sinner set1 set2 : 交集,set1 set2 相同元素的集合
sunion set1 set2 : 并集,set1 + set2
sdiffstore | sinnerstore | sunionstore newSet set1 set2 :将set1 set2 的集合在进行交,并,差之后存储到一个newSet的集合。
④ 实战
抽象池中选中奖用户(spop)。
微博或者qq的共同好友,共同关注等(sinner).
① 特点,结构和命令
用分值(store)来进行排序。
② 重要API
命令基本都以 ‘Z’ 开头
zadd key score element: 添加元素,score element 可以有多对,O(logN)
zrem key element : 删除元素, score element 可以有多对
zscore key element :得到element的score
zincrby key count element : 给element的score增加count值(count可以为负)
zcard key :得到集合中的个数。 O(1)
zrank | zrevrank key element: 得到element在集合中的排名,前者按照分值从小到大排名,后者按照分值从大到小排名。
zrange key start end [withscores] : 按照排名(从小到大递增)得到范围内的元素(后面的withscores表示分值是不是一起打印) , 把zrange 换成zrevrange 则是按照从大到小递减来排名取值。
zrangebyscore start end [withscores] :按照分值排序(从小到大)得到分值在start到end内的元素。
zcount key scoreStart scoreEnd : 按照分值得到start到end之间的元素个数。 O(logN + m) ==> N为集合中的元素个数,m为start到end间的个数
zremrangebyrank key start end : 按照排名排序删除排名在start到end之间的值。
zremrangebystore key start end : 按照分数排序删除分值在start到end之间的值。
③ 快速实战
可以应用于排行榜,音乐排行榜,点赞数榜等。最核心的是zset中的score保存的内容,可以为音乐听歌次数,时间戳用于按时间排序等。
redis-cli 是redis的官方客户端,jedis是java调用redis的客户端(本质还是tcp连接,用的是socket,对外是jedis对象)
这里有两种使用jedis的方法。
1 . jedis直连的方式,每个连接jedis都是一个tcp连接。 2 . 连接池的方式,通过连接池管理jedis
下面给出简单的使用方法。
使用前先引入jedis的maven依赖
redis.clients
jedis
2.9.0
package com.utils;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class JedisUtil {
private static ThreadLocal jedisPoolLocal = new ThreadLocal<>();
private static JedisPool jedisPool;
static {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
jedisPool = new JedisPool(poolConfig, "你的ip", 6380);
jedisPoolLocal.set(jedisPool);
}
/**
* 使用jedis访问redis得到数据,适用于长期,少量的连接
* @param key
* @return
*/
public static String getByJedis(String key) {
Jedis jedis = null;
String result = null;
try {
jedis = new Jedis("你的ip", 6380);
result = jedis.get(key);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
return result;
}
}
/**
* 通过连接池得到jedis,jedis获取value,对于资源的管理更加合理
* @param key
* @return
*/
public static String getByJedisPool (String key) {
Jedis jedis = null;
String result = null;
try {
jedis = jedisPoolLocal.get().getResource();
result = jedis.get(key);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
// close操作先判断是否存在连接池,存在的话代表jedis是从pool里获取的,则归还jedis,否则关闭连接。
jedis.close();
}
return result;
}
}
public static ThreadLocal getJedisPoolLocal () {
jedisPoolLocal.set(jedisPool);
return jedisPoolLocal;
}
}
这里插播一条(很重要):因为我是连接阿里云的服务器(ip 为A),在客户端(ip为B)连接的时候要把阿里云的保护组开放,而且要把redis的redis.conf的bind属性注释掉(bind 指的是你redis服务器本身接受请求的ip即B,但是经过测试发现bind写成B时,redis服务启动不了,写成本地服务器的ip 即A时,启动了B连不上,当时想到了可不可以绑定两个,我测试了好像不行,但是redis的示例配置redis.conf说是可以的,我最后是把bind属性注释掉或者写成bind 0.0.0.0就能正常启动并且B能连上A的redis,写的有问题的话多请大佬指教)。当时写了bind 0.0.0.0 ,然后第二天就收到阿里云的紧急警告。。
因为这个服务器就是自己学习用的,也就没什么处理这个警告,不过还是查了下阿里有建议的修复方案。
接下来的 小工具,持久化 请看最详细的redis学习日记(二):redis的小工具及持久化