Redis
1.简介
1、数据都在内存中,支持持久化,主要支持备份功能。
2、除了简单的key-value模式,还支持数据结构的存储,比如list、set、hash、zset等。
3、一般都是作为缓存数据库辅助持久化的数据库
2.Redis应用场景
1、高频次、热门访问的数据,降低数据库IO
2、分布式架构,做Session共享
3、由于持久化能力,其多样的数据结构存储特定的数据
最新的N个数据 List实现自然世界排序
排行榜,TopN 利用Zset
时效性数据,手机验证码 Exprie过期
计数器 ,秒杀 原子性
构建队列 List集合
3、Redis为什么块
1、完全基于内存,绝大多数请求存粹时内存的操作,非常快速。数据存在内存中,类似HashMap,时间复杂度时O(1)
2、数据结构简单,对数据的操作也简单,Redis中的数据结构是专门设计的。
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多线程导致的切换而消耗CPU,不用考虑各种锁的问题,不存在加锁释放锁操作,更没有死锁操作
4、采用多路I/O服用模型,非阻塞IO,其底层调用的epool方法
5、使用的底层模型不同,他们之间底层实现方式与客户端之间通信的应用协议不一样,Redis直接构建自己的VM机制,因为一般系统调用系统函数的话,会浪费一定的时间去移动和请求。
4、Redis的五大数据类型
Key相关的
keys* 查询当前库的所有键
exists 判断某个键是否存在
type查看键的类型
del 删除某个键
exprie 为键值设置过期时间,单位秒
ttl 查看还有多少秒过期 -1永不过期
dbsize 查看当前数据库key的数量
Redis 五大数据类型
String
get 查询对应的key值
set 添加键值对
append 追加到原始的末尾
strlen 获取值的长度
setnx 只有在不存在时候设置key值
incr 将key中的存储的数字值增1 只能针对数字
decr 将key中存储的数字减1
incrby/decrby<步长>
mset 同时设置一个或者多个key-value对
mget 同时获取一个或者多个value
setex <过期时间>
getset 以新换旧,设置了新值同时获得旧
List
单键多值
Redis列表是简单的字符串列表,按照插入顺序排队,可以添加一个元素到左面或者右面
底层是一个双向链表,对两端操作性能很高,通过索引下标的操作中间的节点性能会较差
lpush/rpush
从左边/右边插入一个或者多个值
lpush/rpush
从左面/右面插入一个或者多个值
lpop/rpop
从左面/右面吐出一个值
值在键在,值亡键亡
lrange
按照索引下标获取元素
lindex
获取长度
llen
set
场景是无序,但是可以去重
sadd … 将一个或者多个放入集合key当中
smembers 取出所用值
sismember 是否有值,有的话返回1 没有0
scard 返回集合个数
hash
hset 给集合中的 键赋值
hget 从集合 取出 value
zset
有序set
zadd …
zrange
5、Redis例子
第一个 完成手机验证码功能
要求:
1、输入手机号,点击发送后随机生成6位数字码,2分钟有效
2、输入验证码,点击验证,返回成功或失败
3、每个手机号每天只能发送3次验证码
public static String buildCode() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 6; i++) {
sb.append(new Random().nextInt(10));
}
return sb.toString();
}
public static String buildPhone() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 2; i++) {
sb.append(new Random().nextInt(10));
}
return sb.toString();
}
public static void sendCode(String phoneNum) {
Jedis jedis = null;
try {
String phoneCount = phoneNum + " :count";
String phoneKey = phoneNum + " :key";
jedis = new Jedis("Master", 6379);
String cn = jedis.get(phoneCount);
//今日没有发送过验证码
if (cn == null) {
jedis.setex(phoneCount, 60 * 60 * 24, "1");
String code = buildCode();
jedis.setex(phoneKey, 60 * 2, code);
System.out.println("用户 " + phoneNum + " 第一次今日发送 ");
} else {
if (Integer.parseInt(cn) < 3) {
String code = buildCode();
jedis.setex(phoneKey, 60 * 2, code);
jedis.incr(phoneCount);
System.out.println(phoneNum + "当天" + (Integer.parseInt(cn) + 1) + "次");
} else {
System.out.println("今日已经发送了三次 不能再次发送");
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedis.close();
}
}
public static void main(String[] args) {
for(int i=0;i<1000;i++){
sendCode(buildPhone());
}
}
第二个 秒杀功能
public static void secondKill(){
Jedis jedis = new Jedis();
//商品id
String prodid = "1001";
String userid = new Random().nextInt(1000)+"";
String memberKey = "sk:"+ userid +":user";
//判断是否用户已经秒杀过d了
Boolean flag = jedis.sismember(memberKey, userid);
if(flag){
//表示已经存在
System.out.println("该"+userid+"已经参与过秒杀");
}else{
String qtKey = "sk:"+prodid+":qt";
String qt = jedis.get(qtKey);
if(qt==null){
System.out.println("不存在该商品的秒杀");
}else{
//库存减1
Transaction multi = jedis.multi();
multi.decr(qtKey);
//增加秒杀
multi.sadd(memberKey,userid);
jedis.decr(qtKey);
List<Object> exec = multi.exec();
if(exec==null||exec.size()==0){
System.out.println("秒杀失败");
}else{
System.out.println("秒杀成功");
}
}
}
}
基于Lua脚本 跟分布式锁的解法,后面会有详细说明