Redis
NOSQL(Not Only SQL)
NoSQL,泛指非关系型的数据库。区别于关系数据库,它们不保证关系数据的ACID:原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)注意不保证不代表没有。NoSQL是一项全新的数据库革命性运动,其拥护者们提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入。
优点
易扩展,NoSQL数据库种类繁多,但是一个共同的特点都是去掉关系数据库的关系型特性。
数据之间无关系,这样就非常容易扩展。无形之间也在架构的层面上带来了可扩展的能力。
大数据量
高性能
NoSQL数据库都具有非常高的读写性能,尤其在大数据量下,同样表现优秀。这得益于它的无关系性,数据库的结构简单。
主流nosql代表
kv键值对 redis(最多)
文档型数据库 mongdb bson格式的 类似于json
redis简介
REmote DIctionary Server(Redis) 远程字典服务 是一个由 Salvatore Sanfilippo 写的 key-value 存储系统,是跨平台的非关系型数据库。Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。Redis 通常被称为数据结构服务器,因为值(value)可以是字符串(String)、哈希(Hash)、列表(list)、集合(sets)和有序集合(sorted sets)等类型。
特点
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
优势
性能极高 – Redis能读的速度是110000次/s(十万级),写的速度是81000次/s (8万级)。
mysql大概是3000-5000的量具体可以通过其他方式再去优化一点但是提升空间没多少了
丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,通过watch体现原子性,通过MULTI和EXEC指令包起来。
丰富的特性 – Redis还支持 publish/subscribe(订阅), 通知, key 过期等等特性
单线程 :避免了cpu快速切换上下文的操作,避免了时间片资源的分配所以他快
与其他key-value存储有什么不同
- Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
- Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,在磁盘格式方面他们是紧凑的以追加的方式产生的,因为他们并不需要进行随机访问。
windows安装
1.d盘根目录创建redis文件夹并解压安装包
2.配置环境变量
3.cmd 输入命令 redis-server.exe 出来图标即可 (这是开启服务端)
4.cmd开起客户端(redis-cli.exe -h 127.0.0.1 -p 6379,其实你不输-h -p也行 他默认的,如果不行你就输入)
你输入set name jack
get name
你就能得到jack
linux安装
代码语言:javascript复制1. wget http://download.redis.io/releases/redis-6.0.8.tar.gz
2. tar xzf redis-6.0.8.tar.gz
3.cd redis-6.0.8
4. make
如果报错升级gcc版本
#第一步
sudo yum install centos-release-scl
#第二步
sudo yum install devtoolset-7-gcc*
#第三步
scl enable devtoolset-7 bash
#查看gcc版本
gcc -v
make clean
make
5. cd src
6 ./redis-server
1.cd redis-6.0.8
2. cd src
3. ./redis-cli
开起关闭命令总结
代码语言:javascript复制启动redis服务redis-server
启动redis客户端redis-cli -h -p (hp一般可以不写)
关闭redis服务在redis客户端输入 shutdown
设置redis远程访问
首先,要配置redis远程访问,需要明确redis.conf(在redis的根目录下)配置文件中三个配置项的概念和作用:
bind配置的作用:用来指定允许访问的本机网卡对应的IP地址。
其中192.168.18.8是ens33网卡地址,127.0.0.1是lo回环地址。redis.conf默认是bind 127.0.0.1,即默认redis只允许本机访问。
protected-mode模式含义:一个安全保护层,目的是防止redis被互联网随意访问。假如protected-mode模式开启,当未bind特定IP地址,并且未设置访问密码的时候,只允许本地lo回环地址、unix主机名访问。
requirepass:访问密码。设置后,连接redis必须使用该密码,否则无法设置和查看数据。
结合上面三个配置项的说明,我们可以有以下几种方案实现redis外部访问:
1、不设置bind参数(注释掉所有bind行),关闭protected-mode模式,不设置访问密码。
2、不设置bind参数(注释掉所有bind行),关闭protected-mode模式,设置访问密码。
3、不设置bind参数,开启protected-mode模式,设置访问密码。
4、设置bind参数,开启protected-mode模式,不设置访问密码
5、设置bind参数,开启protected-mode模式,设置访问密码
代码语言:javascript复制systemctl status firewalld//查看防火墙状态
systemctl stop firewalld.service//关闭防火墙
systemctl disable firewalld.service//禁止防火墙开机自启
systemctl start firewalld.service
firewall-cmd --zone=public --add-port=6379/tcp --permanent 不拦截redis的6379端口
firewall-cmd --reload 重启防火墙
firewall-cmd --list-ports 列出防火墙非拦截端口
代码语言:javascript复制./redis-server ../redis.conf
端口占用了就用
netstat -anp |grep 6379 找到pid然后kill -9
如果还不行,你就看看看你的云服务器的相关配置,并且看看防火墙的端口有没有通过
查看性能工具
src下的 redis-benchmark
序号 | 选项 | 描述 | 默认值 |
---|---|---|---|
1 | -h | 指定服务器主机名 | 127.0.0.1 |
2 | -p | 指定服务器端口 | 6379 |
3 | -s | 指定服务器 socket | |
4 | -c | 指定并发连接数 | 50 |
5 | -n | 指定请求数 | 10000 |
6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
9 | -P | 通过管道传输 <numreq> 请求 | 1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
11 | --csv | 以 CSV 格式输出 | |
12 | -l(L 的小写字母) | 生成循环,永久执行测试 | |
13 | -t | 仅运行以逗号分隔的测试命令列表。 | |
14 | -I(i 的大写字母) | Idle 模式。仅打开 N 个 idle 连接并等待。 |
./redis-benchmark -c 500 -n 50000
基础知识
默认16个数据库,在图形化界面你就能看到了
切换数据库
代码语言:javascript复制select 索引号 运行完就会出现 xxx.xxx.xxx.xxx:xxx[索引号]索引号为0的数据库不显示数据库索引号
数据库大小
代码语言:javascript复制dbsize
清空当前数据库
代码语言:javascript复制flushdb
查看所有的key
代码语言:javascript复制keys *
清空所有的数据库
代码语言:javascript复制flushall
判断键是否存在
代码语言:javascript复制exists k
移动键,不是删除 是从一个库到另一个库
代码语言:javascript复制move k 2
删除键
代码语言:javascript复制 DEL k
设置过期时间
代码语言:javascript复制expire k 1 1秒过期
setex k 60 v 设置值及其过期时间。如果 key 已经存在, SETEX 命令将会替换旧的值
setnx k 60 v 指定的 key 不存在时,为 key 设置指定的值。
查看剩余时间
代码语言:javascript复制ttl k //time to live
同时设置多个值
代码语言:javascript复制mset k v k v k v
获取并设置值
代码语言:javascript复制getset k v 若果不存在就不返回,直接设置 ,存在就返回原来的值并设置
重命名key
代码语言:javascript复制rename key newkey
查看key 所储存的值的类型
代码语言:javascript复制type k
返回 key 的数据类型,数据类型有:
- none (key不存在)
- string (字符串)
- list (列表)
- set (集合)
- zset (有序集)
- hash (哈希表)
性能依赖
是基于内存的,所以跟cpu没有直接的关系,一般是跟内存有关系
Redis 五大数据类型
(命令很多,我们这挑选差不多的讲,当然挑选差不多的也挺多的)
string
最常用
string 类型的值最大能存储 512MB
命令
代码语言:javascript复制set k v
get k
append k v 追加字符串 不存在就是新建
strlen k 获取值的长度
incr k 值自增
decr k 值自减少
incrby k 1 自增1
decrby k 1 自减1
getrange k 0 2 截取0-2
hash
就是map(存对象神器,但是很多人还是用string)
Redis hash 是一个键值(key=>value)对集合。
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。
每个 hash 可以存储 232 -1 键值对(40多亿)
命令
代码语言:javascript复制hset name k v
hgetall name 获取hash表中所有的字段和值
hexists name k
hget name k 获取指定k 的值
hvals name 获取hash表中所有的值
hkeys name 获取hash表中所有的键
hlen name 返回字段数量
hsetnx name k v 没有就设置值
hscan name 0 返回所有的键值对从0 开始
list
把他当成LinkedList看
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。
列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。
命令
代码语言:javascript复制lpush name v 头插
rpush name v 尾插
lrange name 0 2 (02是范围) 0 -1 就是获取所有的
lrem name COUNT v
count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。
count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。
count = 0 : 移除表中所有与 VALUE 相等的值。
这个有点绕,一定要自己搞一遍,感觉设计的挺巧妙的
ltrim name 1 0 清空list
lpop name 头删
rpop name 尾删
llen name 返回元素个数
set
Redis 的 Set 是 string 类型的无序集合。集合中最大的成员数为 232 - 1(4294967295, 每个集合可存储40多亿个成员)。
命令
代码语言:javascript复制sadd key xxx 增加set成员
scard key 返回成员数
smembers key 返回集合中的所有成员
spop k移除并返回集合中的一个随机元素
srandmember k 返回集合中一个或者多个随机数
srem k v移除集合中一个或多个成员
set是无序唯一的所以
添加一个 string 元素到 key 对应的 set 集合中,成功返回 1,如果元素已经在集合中返回 0。
zset(sorted set)
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。
命令
代码语言:javascript复制zadd k score v
zcard k 获取集合成员数
zcount k score1 score 2 指定区间分数的成员数
zrange k score1 score2 返回有序集合指定区间内的成员
zrem k member 移除成员
命令很多。万一用的时候参考官网指令去做就完事,这里不再做过多描述
应用场景
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
String(字符串) | 二进制安全 | 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M | --- |
Hash(字典) | 键值对集合,即编程语言中的Map类型 | 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
List(列表) | 链表(双向链表) | 增删快,提供了操作某一段元素的API | 1,最新消息排行等功能(比如朋友圈的时间线) 2,消息队列 |
Set(集合) | 哈希表实现,元素不重复 | 1、添加、删除,查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 | 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐 |
Sorted Set(有序集合) | 将Set中的元素增加一个权重参数score,元素按score有序排列 | 数据插入集合时,已经进行天然排序 | 1、排行榜 2、带权重的消息队列 |
Redis 发布订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。
说人话就是一个频道多个客户端,只要客户端订阅了这个频道就能接收到这个频道发出的消息
命令
代码语言:javascript复制 subscribe name 订阅频道 这个name不需要提前创建,即写即有
psubscribe name 订阅符合的频道 类似于like
pubsub subcommand查看订阅与发布系统状态。
PUBLISH name "xxxx" 向频道发送消息
punsubcribe name 退订符合的频道 类似于like
unsubcribe name 退订频道
Redis 事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存。
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
- 开始事务。
- 命令入队。
- 执行事务。
命令
代码语言:javascript复制multi开启事务
exec执行事务
DISCARD清空事务
WATCH key 监视
UNWATCH 放弃监视 一般不用 exec会自动解锁
举例
代码语言:javascript复制redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> set b bbb
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
1) OK
2) OK
3) OK
注意:redis事务不具备原子性,也就是说不存在一个不成功就都不成功 更像是一种打包命令
原子性:不支持,不会回滚且继续执行,
隔离性:支持,事务中的命令顺序,不会被打断,先EXEC先执行,单机redis读写操作使用单进程单线程
持久性:不支持,redis 数据容易丢失
一致性:不支持,要求通过乐观锁watch来实现
跟mysql不一样的是mysql的事务是一个一个执行然后提交,但是redis是全部一起执行 ,但是你redis命令有问题那就全部不执行了,不过我们java开发不会出现这样的问题,但是如果说你的命令没有问题是运行时出了问题,那么就只不会执行那一条命令了
代码语言:javascript复制redis 127.0.0.1:7000> multi
OK
redis 127.0.0.1:7000> set a aaa
QUEUED
redis 127.0.0.1:7000> incr a
QUEUED
redis 127.0.0.1:7000> set c ccc
QUEUED
redis 127.0.0.1:7000> exec
1) (errorxxxxxx)
2) OK
3) OK
这个时候你能看的到其他的执行了,但是就第一条没有执行
watch关键字
一般用在事务开启前,一旦watch后开启一个事物只要有另一个线程修改了相关的k整个事物就会失败的。
java连接reids
什么是jedis
Jedis 是 Redis 官方首选的 Java 客户端开发包。就像我们连接数据库就要导入数据库连接jar包 jedis也就是类似于mysql的jar包,方法与我们的命令是一样的
导入jedis依赖
代码语言:javascript复制<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
2、测试连接
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.1.65");
jedis.auth("xxxx");
jedis.sadd("cc","cc");
jedis.close();
}
jedis操作事务
代码语言:javascript复制package kj08.redis.until;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class Test {
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.1.147");
jedis.auth("kj08");
jedis.flushDB();
//String watch = jedis.watch("xxx");
// System.out.println(watch);
Transaction multi = jedis.multi();
try {
multi.set("a","1");
multi.set("b","2");
multi.set("c","3");
multi.set("d","4");
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("a"));
System.out.println(jedis.get("b"));
System.out.println(jedis.get("c"));
System.out.println(jedis.get("d"));
jedis.close();
}
}
}
但是这样做还是有问题的,因为我们的正式环境中就像我们的数据库同样是需要连接池的概念,所以redis也是需要连接池的
SpringBoot整合Redis
spring2.X之后,底层原来使用的jedis被替换为了lettuce
jedis:采用直连,多个线程操作是不安全的,要避免不安全,需要使用jedis pool间接持,更像BIO模式
lettuce:采用netty,实例可以在多个线程共享,不存在线程不安全的情况,可以减少线程数量,更像NIO模式
- BIO: 同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
- NIO:同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
- AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是先完成了再通知服务器应用去启动线程进行处理
企业级玩redis一般步骤都是重写RedisTemplate 然后封装一个工具类
1.
代码语言:javascript复制<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>kj08</groupId>
<artifactId>redis-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.写配置
代码语言:javascript复制spring:
redis:
host: 192.168.1.65
port: 6379
password: kj08
pool:
max-active: 8 #连接池最大连接数(使用负值表示没有限制)
max-idle: 8 #连接池中的最大空闲连接
max-wait: -1 #连接池最大阻塞等待时间(使用负值表示没有限制)
min-idle: 0 #连接池中的最小空闲连接
timeout: 3000 #连接超时时间(毫秒)
连接密码在conf文件在尾部添加
requirepass xxxx
3.重写RedisTemplate
代码语言:javascript复制package kj08.redisdemo.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectMapper.DefaultTyping;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
//自定义Jackson序列化配置
Jackson2JsonRedisSerializer jsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, DefaultTyping.NON_FINAL);
jsonRedisSerializer.setObjectMapper(objectMapper);
//key使用String的序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
//hash的key也是用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value的key使用jackson的序列化方式
template.setValueSerializer(jsonRedisSerializer);
//hash的value也是用jackson的序列化方式
template.setHashValueSerializer(jsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
4.工具类
代码语言:javascript复制package kj08.redisdemo.until;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try{
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time,
TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0
时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count,
value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
5.controller举例
代码语言:javascript复制package kj08.redisdemo.controller;
import kj08.redisdemo.until.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class redisController {
@Autowired
private RedisUtils redisUtils;
@RequestMapping(value = "cc")
public Object hello(){
redisUtils.set("scd","ss");
return redisUtils.get("scd");
}
}
利用redis实现登录
原因 在多服务器的情况下sessionid不能保证唯一性,所以要用到缓存服务器其实token也就是sessionid的
核心 前端登录到后台后台生成有效期为30min的user 随机数:对象信息 (token)存进redis 将user 随机数返回给前端,前端存进cookie里面(也可以设置有效期),后面每次前端都在cookie里面取发给后台 后台其他的页面也要加拦截器之类的去验证token
代码实现
代码语言:javascript复制package kj08.redisdemo.Service;
import com.alibaba.fastjson.JSONObject;
import kj08.redisdemo.pojo.User;
import kj08.redisdemo.until.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
@Service
public class TokenService {
@Autowired
private RedisUtils redisUtil;
//生存保存token
public void csToken(User user){
StringBuilder token = new StringBuilder();
//加token:
token.append("user:");
token.append(new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) "-");
//加六位随机数111111-999999
token.append(new Random().nextInt((999999 - 111111 1)) 111111);
System.out.println("token=>" token.toString());
redisUtil.set(token.toString(),JSONObject.toJSONString(user),2*60*60);
}
}
如果你在linux下看reids中文是乱码的是因为他显示的是16进制的字符串 在redis-cli后面加上参数 --raw 即可
jquery存cookie
代码语言:javascript复制存tonken到cookie
document.cookie="token=" res.data.result
从cookie取tonken
getCookie: function (cname) {
var name = cname "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i )
{
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return "";
}
接口的幂等性
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性
token机制实现幂等性
代码语言:javascript复制 token机制实现步骤:
1. 生成全局唯一的token,token放到redis或jvm内存,token会在页面跳转时获取.存放到pageScope中,支付请求提交先获取token
2. 提交后后台校验token,执行提交逻辑,提交成功同时删除token,生成新的token更新redis ,这样当第一次提交后token更新了,页面再次提交携带的token是已删除的token后台验证会失败不让提交
token特点: 要申请,一次有效性,可以限流
注意: redis要用删除操作来判断token,删除成功代表token校验通过,如果用select delete来校验token,存在并发问题,不建议使用