redis的5种数据类型:
1.1string 字符串(可以为整形、浮点型和字符串,统称为元素)
1.2list 列表(实现队列,元素不唯一,先入先出原则)
1.3set 集合(各不相同的元素)
1.4hash hash散列值(hash的key必须是唯一的)
1.5sort set 有序集合
1.String类型
1.1使用场景:
场景一:“某某综艺”,启动海选投票,只能通过微信投票,每个微信号每4个小时只能投1票。
场景二:电商商家开启热门商品推荐,热门商品不能一直处于热门期,每种商品热门期维持3天,3天后自动取消热门
场景三:新闻网站会出现热点新闻,热点新闻最大的特征是对时效性,如何自动控制热点新闻的时效性
1.2数据操作成功与否的反馈
1.2.1表示运行结果是否成功
(integer)0 –> false 失败
(integer)1 –> true 成功
1.2.2表示运行结果值
(integer)3 –> 3 3个
(integer)1 –> 1 1个
数据未获取到
(nil)等同于null
1.3String类型操作实践
字符串类型是redis中最简单的数据类型,它存储的值可以是字符串,其最大字符串长度支持到512M。基于此类型,可以实现博客的字数统计,将日志不断追加到指定key,实现一个分布式自增id,实现一个博客的的点赞操作等
incr/incrby
当存储的字符串是整数时,redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值。如果num不存在,则自动会创建,如果存在自动+1。
INCR key
127.0.0.1:6379> set num 1
(integer) 1
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> keys *
1) "num"
127.0.0.1:6379> incr num
127.0.0.1:6379>
INCRBY key increment可以指定增长数据
127.0.0.1:6379> incrby num 2
(integer) 5
127.0.0.1:6379> incrby num 2
(integer) 7
127.0.0.1:6379> incrby num 2
(integer) 9
127.0.0.1:6379>
decr/decrby
当存储的字符串是整数时,redis提供了一个实用的命令DECR,其作用是让当前键值递减,并返回递增后的值。如果num不存在,则自动会创建,如果存在自动-1。
DECR key 按照默认步长(默认为1)进行递减
DECRBY key decrement 按照指定步长进行递减
append
向尾部追加值。如果键不存在则创建该键,其值为写的value,即相当于SET key value。返回值是追加后字符串的总长度。
语法:APPEND key value
strlen
字符串长度,返回数据的长度,如果键不存在则返回0。注意,如果键值为空串,返回也是0。
语法:STRLEN key
127.0.0.1:6379> get test
"123abc"
127.0.0.1:6379> strlen test
(integer) 6
127.0.0.1:6379> strlen tnt
(integer) 0
127.0.0.1:6379> set tnt ""
OK
127.0.0.1:6379> strlen tnt
(integer) 0
127.0.0.1:6379> exists tnt
(integer) 1
127.0.0.1:6379>
mset/mget
同时设置/获取多个键值
语法:MSET key value [key value …]
MGET key [key …]
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> mset a 1 b 2 c 3
OK
127.0.0.1:6379> mget a b c
1) "1"
2) "2"
3) "3"
127.0.0.1:6379>
- 博客的字数统计如何实现?(strlen)
- 如何将审计日志不断追加到指定key?(append)
- 你如何实现一个分布式自增id?(incr-雪花算法)
- 如何实现一个博客的的点赞操作?(incr,decr)
2.string应用案例demo
1.创建工程
创建maven父工程,例如03-sca-redis,并在此工程下创建两个子工程,一个为sca-jedis,一个为sca-tempate,例如:
1.2在sca-jedis添加项目依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
1.3在sca-template添加项目依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
分布式id
业务描述
在分布式系统中,数据量将越来越大时,就需要对数据进行分表操作,但是,分表后,每个表中的数据都会按自己的节奏进行自增,很有可能出现ID冲突。这时就需要一个单独的机制来负责生成唯一ID,生成出来的ID也可以叫做 分布式ID,这里我们借助redis实现一个简易的分布式id进行实现,当然还有一些第三方的系统,可以帮你生成这样的id,可以自己进行拓展学习.
关键代码实现
package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.time.zone.ZoneOffsetTransitionRule;
/**
* 需求:生成一个分布递增的id
* 多张表基于这个方法中生成的id作为主键id值(分布式环境不会采用数据库
* 表中自带的自增策略-auto_increment)
*/
public class IdGenerator {
//虚拟机IP地址
private static final String IP ="192.168.126.129";
//redis端口号
private static final int port =6379;
public static Long getId(){
Jedis jedis = new Jedis(IP,port);
//jedis.auth("123456");//数据库密码没有密码就不用写
Long id = jedis.incr("id");
jedis.close();//关闭连接
return id;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(){
@Override
public void run() {
Long id = IdGenerator.getId();
System.out.println("id = "+id);
}
}.start();
}
}
}
2.hash hash散列值(hash的key必须是唯一的)
String类型存储的困惑
对象类数据的存储如果具有较为频繁的更新需求,操作会显得笨重,存容易,改麻烦。
为了区别与Redis中的键值对的称呼,hash中的键成为field,而key特征Redis的键。
2.1hash类型
新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
需要的内存结构:一个存储空间保存多少个键值对数据
hash类型:底层使用哈希表结构实现数据存储
2.2hash类型数据操作的注意事项
hash类型下的value只能存储字符串,不允许存储其他类型数据,不存在嵌套现象。如果数据未获取到,对应的值为(nil)
每个hash可以存储232-1个键值对
hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计不是为了存储大量对象的,切记不可滥用,更不可以将hash作为对象列表使用
hgetall操作可以获取全部属性,如果内部key过多,遍历整体数据效率就会很低,有可能成为数据访问瓶颈。
2.3Hash语法实践
hset/hget
HSET key field value
HGET key field
HMSET key field value [field value…]
HMGET key field [field]
HGETALL key
127.0.0.1:6379> hset user username chenchen
(integer) 1
127.0.0.1:6379> hget user username
"chenchen"
127.0.0.1:6379> hset user username chen
(integer) 0
127.0.0.1:6379> keys user
1) "user"
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
127.0.0.1:6379>
127.0.0.1:6379> hset user age 18
(integer) 1
127.0.0.1:6379> hset user address "xi'an"
(integer) 1
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
3) "age"
4) "18"
3) "address"
4) "xi'an"
127.0.0.1:6379>
HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0。
hincrby
127.0.0.1:6379> hdecrby article total 1 #执行会出错
127.0.0.1:6379> hincrby article total -1 #没有hdecrby自减命令
(integer) 1
127.0.0.1:6379> hget article total #获取值
hmset/hmget
HMSET和HMGET设置和获取对象属性
127.0.0.1:6379> hmset person username tony age 18
OK
127.0.0.1:6379> hmget person age username
1) "18"
2) "tony"
127.0.0.1:6379> hgetall person
1) "username"
2) "tony"
3) "age"
4) "18"
127.0.0.1:6379>
hexists
属性是否存在
127.0.0.1:6379> hexists killer
(error) ERR wrong number of arguments for 'hexists' command
127.0.0.1:6379> hexists killer a
(integer) 0
127.0.0.1:6379> hexists user username
(integer) 1
127.0.0.1:6379> hexists person age
(integer) 1
127.0.0.1:6379>
hdel
127.0.0.1:6379> hdel user age
(integer) 1
127.0.0.1:6379> hgetall user
1) "username"
2) "chen"
127.0.0.1:6379> hgetall person
1) "username"
2) "tony"
3) "age"
4) "18"
127.0.0.1:6379>
hkeys/hvals
只获取字段名HKEYS或字段值HVALS
127.0.0.1:6379> hkeys person
1) "username"
2) "age"
127.0.0.1:6379> hvals person
1) "tony"
2) "18"
2.3.8 hlen
元素个数
127.0.0.1:6379> hlen user
(integer) 1
127.0.0.1:6379> hlen person
(integer) 2
127.0.0.1:6379>
小节面试分析
- 发布一篇博客需要写内存吗?(需要,hmset)
- 浏览博客内容会怎么做?(hmget)
- 如何判定一篇博客是否存在?(hexists)
- 删除一篇博客如何实现?(hdel)
- 分布式系统中你登录成功以后是如何存储用户信息的?(hmset)
hash应用场景
单点登陆
业务描述
在分布式系统中,通过会有多个服务,我们登录了一个服务以后,再访问其它服务时,不想再登录,就需要有一套单独的认证系统,我们通常会称之为单点登录系统,在这套系统中提供一个认证服务器,服务完成用户身份认证,在一些中小型分布式系统中中,我们通常会借助redis存储用户的认证信息
关键代码实现
package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.Map;
import java.util.UUID;
/**
* SSO(单点登录系统) 案例演示:
* 1)访问资源(假如没有登录,要提示先登录,如何判定是否登录了)
* 2)执行登录(登录成功,存储用户登录信息)
* 3)访问资源(已登录)
* 解决方案
* 1)方案1:SpringSecurity+jwt+oauth2 (并发比较大)
* 2)方案2:SpringSecurity+redis+oauth2 (中小型并发)
*
*/
public class SSODemo02 {
/**认证中心的登录设计*/
static String doLogin(String username,String password){
//1.执行用户身份校验
if(!"jack".equals(username))//将来这个jack来自数据库
throw new IllegalArgumentException("这个用户不存在");
//2.用户存在并且密码正确,表示用户是系统的合法用户
if(!"123456".equals(password))
throw new IllegalArgumentException("密码不正确");
//3.将合法用户信息存储到redis
Jedis jedis=new Jedis("192.168.126.129", 6379);
String token=UUID.randomUUID().toString();
jedis.hset(token,"username",username);
jedis.hset(token, "status", "1");
//....
jedis.expire(token, 2);
jedis.close();
return token;//token
}
/**获取资源服务中的资源*/
static Object doGetResource(String token){
//1.检查用户是否已经登录
if(token==null||"".equals(token))
throw new IllegalArgumentException("请先登录");
Jedis jedis=new Jedis("192.168.126.129", 6379);
Map<String,String> map=jedis.hgetAll(token);
jedis.close();
//2.假如没有登录,则提示先登录
System.out.println("map="+map);
if(map.isEmpty())
throw new RuntimeException("登录超时或token无效,请重新登录");
//3.已登录则可以访问资源
System.out.println("继续访问资源");
//.....
return "the resource of user";
}
//假设这里的main方法为客户端
public static void main(String[] args) throws InterruptedException {
String token=null;
//第一次访问资源
// doGetResource(token);
//执行登录操作(将来认证要在认证中心实现)
token=doLogin("jack", "123456");
//第二次访问资源
Thread.sleep(2000);
doGetResource(token);
}
}
购物车简易实现Demo
业务描述
简易购物车业务设计如图所示:
基础指令操作,例如:
1)向购物车添加商品
hset cart:101 2001 1
hset cart:101 2002 1
hset cart:101 2003 2
2)查看购物车商品
hgetall cart:101
3)删除购物车商品
hdel cart:101 2003
4)改变购物车某个商品的购买数量
hincrby cart:101 2002 2
购物车简易代码Demo
package com.jt.redis;
import redis.clients.jedis.Jedis;
import java.util.Map;
/**
* 购物车系统简易实现
* 需求:
* 1)向购物车添加商品
* 2)修改购物车商品数量
* 3)查看购物车商品
* 4)清空购车商品信息
* 存储设计:
* 1)mysql (优势是持久性,读写速度会差一些)
* 2)redis (优势是性能,持久性相对MySQL会差一下,
* 假如性能要求高于数据的存储安全,可以存储到redis)
*/
public class CartDemo01 {
static final String IP="192.168.126.129";
static final int PORT=6379;
/**
* 向购物车添加商品
* @param userId
* @param productId
* @param num
*/
public static void doAddCart(String userId,
String productId,
int num){
//1.创建jedis对象
Jedis jedis=new Jedis(IP,PORT);
//2.基于hash结构存储商品信息
//jedis.hset()
jedis.hincrBy("cart:"+userId, productId, num);
//3.释放资源
jedis.close();
}
/**
* 查看购物车商品信息
* @param userId
*/
public static Map<String, String> doViewCart(
String userId){
//1.创建jedis对象
Jedis jedis=new Jedis(IP,PORT);
//2.查看购物车信息
Map<String, String> cart =
jedis.hgetAll("cart:" + userId);
//3.释放资源
jedis.close();
//4.返回购物车商品信息
return cart;
}
/**
* 删除购车某个商品
* @param userId
* @param productId
*/
public static void doClearCart(String userId,
String... productId){
//1.创建jedis对象
Jedis jedis=new Jedis(IP,PORT);
//2.清除指定商品
jedis.hdel("cart:"+userId, productId);
//3.释放资源
jedis.close();
}
public static void main(String[] args){
String userId="1001";
//1.购买商品时,将商品信息添加到购物车
doAddCart(userId,"201",1);
doAddCart(userId,"202",1);
doAddCart(userId,"203",1);
//2.修改购车商品的数量
doAddCart(userId,"202",1);
doAddCart(userId,"203",2);
//3.查看购物车商品
Map<String, String> cart =
doViewCart(userId);
System.out.println(cart);
//4.清空购物车
doClearCart(userId, "201","202","203");
cart = doViewCart(userId);
System.out.println(cart);
}
}