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的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
丰富的特性 – Redis还支持 publish/subscribe(订阅), 通知, key 过期等等特性
与其他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安装
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 5. cd src 6 ./redis-server 1. cd src 2. ./redis-cli
开起关闭命令总结
启动redis服务redis-server 启动redis客户端redis-cli -h -p (hp一般可以不写) 关闭redis服务redis-cli shutdown
设置redis远程访问
首先,要配置redis远程访问,需要明确redis.conf配置文件中三个配置项的概念和作用:
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模式,设置访问密码
./redis-server ../redis.conf
查看性能工具
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 100 -n 100000
基础知识
默认16个数据库,在图形化界面你就能看到了
切换数据库
select X
数据库大小
dbsize
清空当前数据库
flushdb
查看所有的key
keys *
清空所有的数据库
flushall
判断键是否存在
exists k
移动键,不是删除 是从一个库到另一个库
move k 2
删除键
DEL k
设置过期时间
expire k 1 1秒过期 setex k 60 v 设置值及其过期时间。如果 key 已经存在, SETEX 命令将会替换旧的值 setnx k 60 v 指定的 key 不存在时,为 key 设置指定的值。
查看剩余时间
ttl k
同时设置多个值
mset k v k v k v
获取并设置值
getset k v 若果不存在就不返回,直接设置 ,存在就返回原来的值并设置
重命名key
rename key newkey
查看key 所储存的值的类型
type k
返回 key 的数据类型,数据类型有:
-
none (key不存在)
-
string (字符串)
-
list (列表)
-
set (集合)
-
zset (有序集)
-
hash (哈希表)
性能依赖
是基于内存的,所以跟cpu没有直接的关系,一般是跟内存有关系
Redis 五大数据类型
(命令很多,我们这挑选差不多的讲,当然挑选差不多的也挺多的)
string
最常用
string 类型的值最大能存储 512MB
命令
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多亿)
命令
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多亿)。
命令
lpush v 头插 lrange k 0 2 (02是范围) 0 -1 就是获取所有的 rpush v 尾插 lrem k COUNT v count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT 。 count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值。 count = 0 : 移除表中所有与 VALUE 相等的值。 这个有点绕,一定要自己搞一遍,感觉设计的挺巧妙的 ltrim k 1 0 情况list lpop k 头删 rpop k 尾删 llen key 返回元素个数
set
Redis 的 Set 是 string 类型的无序集合。集合中最大的成员数为 232 - 1(4294967295, 每个集合可存储40多亿个成员)。
命令
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)却可以重复。
命令
zadd k score v zcard k 获取集合成员数 zrank k v 返回成员索引 zrank k 0 -1 返回所有成员 zrank k 0 1 返回0-1的成员 zrevrank key v 返回集合成员排名 zscore key v 返回集合成员分数值 ZRANGEBYSCORE name 0 1000(范围)
应用场景
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
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 客户端可以订阅任意数量的频道。
说人话就是一个频道多个客户端,只要客户端订阅了这个频道就能接收到这个频道发出的消息
命令
subscribe name 订阅频道 psubscribe name 订阅符合的频道 类似于like pubsub subcommand查看订阅与发布系统状态。 PUBLISH name "xxxx" 向频道发送消息 punsubcribe name 退订符合的频道 类似于like unsubcribe name 退订频道
Redis 事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
-
批量操作在发送 EXEC 命令前被放入队列缓存。
-
收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。
-
在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。
一个事务从开始到执行会经历以下三个阶段:
-
开始事务。
-
命令入队。
-
执行事务。
命令
multi开启事务 exec执行事务 DISCARD清空事务 WATCH key 监视 UNWATCH 放弃监视 一般不用 exec会自动解锁
举例
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开发不会出现这样的问题
当redis事务没有执行的时候别的线程改了数据,只要这个数据之前被watch了那么他就整个事务就不会执行了
java连接reids
注意事项
如果连接超时最好关掉防火墙
systemctl disable firewalld.service永久关闭防火墙 systemctl status firewalld.service查兰防火墙状态
什么是jedis
Jedis 是 Redis 官方首选的 Java 客户端开发包。就像我们连接数据库就要导入数据库连接jar包 jedis也就是类似于mysql的jar包,方法与我们的命令是一样的
导入jedis依赖
<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操作事务
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(); 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请求都是由OS先完成了再通知服务器应用去启动线程进行处理
企业级玩redis一般步骤都是重写RedisTemplate 然后封装一个工具类
1.
<?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.写配置
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
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.工具类
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举例
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
代码实现
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
var date = new Date(); var minutes = 30; date.setTime(date.getTime() + (minutes * 60 * 1000)); $.cookie("example", "foo", { expires: date });
接口的幂等性
接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用。举个最简单的例子,那就是支付,用户购买商品后支付,支付扣款成功,但是返回结果的时候网络异常,此时钱已经扣了,用户再次点击按钮,此时会进行第二次扣款,返回结果成功,用户查询余额返发现多扣钱了,流水记录也变成了两条...,这就没有保证接口的幂等性
token机制实现幂等性
token机制实现步骤: 1. 生成全局唯一的token,token放到redis或jvm内存,token会在页面跳转时获取.存放到pageScope中,支付请求提交先获取token 2. 提交后后台校验token,执行提交逻辑,提交成功同时删除token,生成新的token更新redis ,这样当第一次提交后token更新了,页面再次提交携带的token是已删除的token后台验证会失败不让提交 token特点: 要申请,一次有效性,可以限流 注意: redis要用删除操作来判断token,删除成功代表token校验通过,如果用select+delete来校验token,存在并发问题,不建议使用