1.NoSql的特点
- 方便扩展,数据之间没有关系型数据库的三大范式
- 存取数据很块,因为redis是直接加载在内存,没有和磁盘交互
- 数据类型也很多,而且不用事先设计
大数据时代:高并发,高可扩,高性能
2.Redis(远程字典服务) 结构化数据库
它能干嘛
-
内存存储,持久化,内存断电即失,所以需要持久化(adb,rof)
-
效率高,缓存
-
地图信息分析
-
消息队列
-
计时器,浏览量 …
Windows和linux下都可以安装,官网可以下载,它的安装一点也不难,新手不要怕
基本环境安装
yum insatll gcc-c++ gcc -v查看一下安装
安装完环境 make 会编译环境 make insatll查看一下
一定要记住redis安装目录
可以设置为可以后台运行,到时候可以ps -ef|grep redis可以看得到执行目录
进入redis.conf配置
修改完:wq!保存,退出重启redis
redis-server xxxx/redis.conf
redis-cli -p 6379 端口启动
基本命令
set key
get key
keys *
ping 连接成功会反应pong
查看redis状态
ps -ef|grep redis
losf -i:6379
关闭redis
127.0.0.1:6379> shutdown
再exit
性能测试工具
redis-benchmark -h localhost -p 6379 -c 100 -n 100000 (100个并发连接,每个10万个请求)
知识小驿站
-
redis默认16数据库,而我们一开始用的是第0个,我们可以用select num进行切换 dbsize数据库大小
-
keys * 查看所有key flushAll 清空全部数据库 flushdb 清空当前数据库
-
redis是单线程的,它基于内存,跟cpu没多大关系,主要是机器的内存,和网络速率问题,单线程也没有多线程麻烦,就使用单线程了
-
C语言大佬写的
开心一刻
为什么redis端口默认是6379
redis的作者看电视节目的MERZ明星,然后那明星经常说一些梗来逗大家开心,然后作者后来命名又觉得这个名字体现了技术,造梗很厉害有技术价值的意思,由于是数字端口,然后就想到了手机九宫格对应的6379
redis单线程为什么还这么快
首先多线程的话一定会有上下文的切换,从用户态到内核态的调用执行,而这过程耗时,而且redis不仅存放在内存,可以直接基于内存的存储,使用单线程又不用切换,多次读写也只是在一个cpu上,同一块内存,无须耗费其它时间
讲个故事
打个比方,现在有个工人(CPU)要雕刻玉佛。
工厂有个大仓库,里面放道很多玉石的材料。工人从仓库里取出来玉,进行加工。但仓库太大了,远离他的雕刻的机器和设备,至少100里地。每雕刻一块玉就来回跑太累,特别费专时间。
于是老板给他放了一张大桌子,先放一批玉过来,都雕刻完了再送回仓属库。
这个桌子就是内存
那么从桌子到磁盘的距离肯定小于机器到桌子再到磁盘的距离,也小于机器直接到仓库的距离,
所以cpu的磁盘io时间花的比内存久,多线程都是cpu在调度,
3.五大数据类型
先来点基本的命令
set name lichu
set age 11 EXIST age //返回1,说明存在
get "age"
move name 1 //移动到数据库1
EXPIRE name 10 //10秒后过期 ttl name//查看过期时间
type key //查看key类型
1.String类型
append key value //追加字符串,如果key不存在,那就set key
strlen key//字符串长度
set num 1
incr num//加一 decr num//减一 都是类型变为Integer
incrby key 10//增量10
decrby key 10//减小10
getrange key 0 3//从字符串的0到3得位置 0 -1 全部
setrange key 1 xx //在key字符串的1位置开始替换 xx 比如abc 替换为axx
====================================================
setex key 30 "hello" //设置过期时间
setnx mykey "redis" //不存在才能设置成功返回1,存在返回失败0
meset key1 value1 key2 value2 //批量设置key,非原子性操作,能成功的就成功,不成功的不设值
mesetnx key1 value1 key2 value2 //批量设置key,原子性操作一个不成功,全都不成功
meget key1 key2 //可以取得多个key的值
SET user:1 {name:zhangsan,age:3} //可以设置一个json字符串
getset db redis //先get出key的值,再设置,第一次get为空 这个就有点像cas了,比较并交换
2.List类型
类似一个双端队列,左右都可以存取值
所有list命令基本都是l开头
LPUSH list two
LPUSH list three
LRange list 0 -1 //顺序是three在前,因为后来居上 获取全部list里的元素
LRange list 0 1 //获取0,1下标的两个元素
RPUSH list four //从右边加值,所以,four在最底下
LPOP list //左
RPOP list //右移除第一个值
//集合里面可以存在重复值
lindex list 1 //通过下标获取集合的值
llen list //相当于list.size()
lrem list 1 one // 移除指定个数的一个值
ltrim list 1 2 //截取出下标1开始的2个元素,返回这个集合,其它没截取的就丢弃了
RPOPLPUSH list newlist //移除列表最后一个元素,并且添加上给第一个
lset list 0 item //更新当前下标的值,前提得该集合这位置存在一个值
linsert list before "world" "hello" //在world前面加一个值hello
linsert list after "world" "hello"
3.Set类型
sadd set "hello" //添加值
SMEMBERS set //查看set内容,遍历
SISMEMBER set hello //set.contains(key)
Srem set hello //移除元素
scard set //size()方法
sandMember set //随机set一个元素出来
spop set //随机删除一个key
smove set set2 "hello" //将set中得值移动到set2中
//共同关注,也感兴趣,二度好友,推荐好友(共同好友很多就推荐了)
SDIFF set set2 //差集,去掉set中和set2相同得,剩下得set就是set和set2得差集
SINTER set set2 //交集
SUNION set set2 //并集
4.Hash类型
map集合!! Map<key,Map<field,value>>
hset hash field value //设值
hget hash field //取值
hmset hash field value field2 value2 //同时设置多个值
hmget hash field field2 //获取对应得值
hgetall hash //获取所有得键值对
hdel hash field //删除指定得键
hlen hash //长度
HEXISTS hash field//判断key是否存在
hkeys hash //显示所有key
hincrby hash field 1 //field加一,如果是-1,就是减1
5. ZSet类型
在set得基础上追加了一个值 set k1 v1 | zset k1 age v1
zadd zset 1 one //增加
zadd zset 1 one 2 two //多个
zrange zset 0 -1
zrangebyscore 字段 -inf +inf //范围
ZREVERRANGE 字段 -inf +inf value
4.Redis事务
redis事务不保证原子性,单条命令原子性,没有隔离级别的概念
multi
命令入队
set k1 vi
exec
完成事务
multi
命令入队
set k1 vi
discard //放弃事务,命令撤回
编译型异常(代码有问题,命令有错),都不会执行,而且直接失败
multi
set k1 v1
seta k2 v2
exec
这时候get k1会发现没值
运行时异常,某个地方报错,其它地方仍然会执行,错误的地方抛出异常
multi
set k1 v1
incr k2
exec
这时候get k1 仍然有值,但是incr 会报错
1.监控
悲观锁与乐观锁(概念可以去看我的其它文章有介绍)
set money 100
set out 0
watch money //监视
multi
Decrby money 20
incrby out 20
exec
这样出来效果很正常一个80一个20
如果是多线程下
一个线程
watch money //监视
multi
Decrby money 20
incrby out 20
另一个线程
set money 100
get money
这时第一个线程exec会提交事务失败回滚,类似乐观锁,watch先获取了值,比较发现值不是了所以会失败
watch完,要记得unwatch,要再次watch获取最新的版本值,CAS无锁原理
5.Jedis
Redis官方推荐java连接开发工具,他是个中间件
记得打开服务,如果是远程redis,那要修改配置redis.conf
设置保护域关掉,然后bind本地也关闭,记得再弄一个密码,现在远程连接需要一个密码
本地的话,不需要这些,因为redis默认绑定本地
//测试一下
Jedis redis=new Jedis("127.0.0.1",6379);
//如果是远程可以点进他源码自行参考测试方式
伪代码:sout(jedis.ping());
五大类型等对应API都在java中有对应的方法
其它的类型也是一样的,都有对应的方法,这里就不一一列举,因为随便可以查的到
6.SpringBoot整合
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
在springboot 2.x之后底层redis已经不用jedis了,因为直连,多个线程是不安全的,使用jedis pool 连接池如BIO
改用了lettuce:采用netty,实例可以在多个线程之间共享,不存在线程不安全的情况,可以减少线程数据,更像NIO模式
里面有个RedisAutoConfig,里面有个方法是redistemplate,一旦我们配置,自动配置就失效了
测试一下
//application.properties
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
//其实可以去网上收索需要配置额外的可以再加,也可以去看他的源码里面到底有啥属性可以配
@Autowired
RedisTemplate redistemplate;
@Test
void contextLoads(){
redistemplate.opsForValue().set("mykey","myValue");
sout(redistemplate.opsForValue().get("mykey"));
}
但是如果你存入中文字符串,会发现keys * 里面不是中文值
默认是jdk序列化,会让中文转义
所以这时候,我们一般都会用自己的RedisConfig类来统一管理redis配置
来重新加入config的测试
user类,注意序列化
package com.example.pojo;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
import java.io.Serializable;
@Component
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
/**
* name : asd
* age : 1
*/
private String name;
private int age;
}
//Test
@Test
void test() {
// RedisConnection connection=redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
//真实开发一般用json
User user = new User("章北海", 18);
try {
String json=new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",json);
System.out.println(redisTemplate.opsForValue().get("user"));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
直接传递对象报错,没有序列化
配置具体的序列化方式
package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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;
import java.net.UnknownHostException;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<String , Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om=new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
1.utils工具类
/**
* Redis工具类,使用之前请确保RedisTemplate成功注入
*
* @author ye17186
* @version 2019/2/22 10:48
*/
public class RedisUtils {
private RedisUtils() {
}
@SuppressWarnings("unchecked")
private static RedisTemplate<String, Object> redisTemplate = SpringUtils
.getBean("redisTemplate", RedisTemplate.class);
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public static boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public static boolean expire(final String key, final long timeout, final TimeUnit unit) {
Boolean ret = redisTemplate.expire(key, timeout, unit);
return ret != null && ret;
}
/**
* 删除单个key
*
* @param key 键
* @return true=删除成功;false=删除失败
*/
public static boolean del(final String key) {
Boolean ret = redisTemplate.delete(key);
return ret != null && ret;
}
/**
* 删除多个key
*
* @param keys 键集合
* @return 成功删除的个数
*/
public static long del(final Collection<String> keys) {
Long ret = redisTemplate.delete(keys);
return ret == null ? 0 : ret;
}
/**
* 存入普通对象
*
* @param key Redis键
* @param value 值
*/
public static void set(final String key, final Object value) {
redisTemplate.opsForValue().set(key, value, 1, TimeUnit.MINUTES);
}
// 存储普通对象操作
/**
* 存入普通对象
*
* @param key 键
* @param value 值
* @param timeout 有效期,单位秒
*/
public static void set(final String key, final Object value, final long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
/**
* 获取普通对象
*
* @param key 键
* @return 对象
*/
public static Object get(final String key) {
return redisTemplate.opsForValue().get(key);
}
// 存储Hash操作
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public static void hPut(final String key, final String hKey, final Object value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 往Hash中存入多个数据
*
* @param key Redis键
* @param values Hash键值对
*/
public static void hPutAll(final String key, final Map<String, Object> values) {
redisTemplate.opsForHash().putAll(key, values);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public static Object hGet(final String key, final String hKey) {
return redisTemplate.opsForHash().get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public static List<Object> hMultiGet(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
// 存储Set相关操作
/**
* 往Set中存入数据
*
* @param key Redis键
* @param values 值
* @return 存入的个数
*/
public static long sSet(final String key, final Object... values) {
Long count = redisTemplate.opsForSet().add(key, values);
return count == null ? 0 : count;
}
/**
* 删除Set中的数据
*
* @param key Redis键
* @param values 值
* @return 移除的个数
*/
public static long sDel(final String key, final Object... values) {
Long count = redisTemplate.opsForSet().remove(key, values);
return count == null ? 0 : count;
}
// 存储List相关操作
/**
* 往List中存入数据
*
* @param key Redis键
* @param value 数据
* @return 存入的个数
*/
public static long lPush(final String key, final Object value) {
Long count = redisTemplate.opsForList().rightPush(key, value);
return count == null ? 0 : count;
}
/**
* 往List中存入多个数据
*
* @param key Redis键
* @param values 多个数据
* @return 存入的个数
*/
public static long lPushAll(final String key, final Collection<Object> values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
return count == null ? 0 : count;
}
/**
* 往List中存入多个数据
*
* @param key Redis键
* @param values 多个数据
* @return 存入的个数
*/
public static long lPushAll(final String key, final Object... values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
return count == null ? 0 : count;
}
/**
* 从List中获取begin到end之间的元素
*
* @param key Redis键
* @param start 开始位置
* @param end 结束位置(start=0,end=-1表示获取全部元素)
* @return List对象
*/
public static List<Object> lGet(final String key, final int start, final int end) {
return redisTemplate.opsForList().range(key, start, end);
}
}
c long lPushAll(final String key, final Collection<Object> values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
return count == null ? 0 : count;
}
/**
* 往List中存入多个数据
*
* @param key Redis键
* @param values 多个数据
* @return 存入的个数
*/
public static long lPushAll(final String key, final Object... values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
return count == null ? 0 : count;
}
/**
* 从List中获取begin到end之间的元素
*
* @param key Redis键
* @param start 开始位置
* @param end 结束位置(start=0,end=-1表示获取全部元素)
* @return List对象
*/
public static List<Object> lGet(final String key, final int start, final int end) {
return redisTemplate.opsForList().range(key, start, end);
}
}
后续跟上redis进阶,关注期待哦
转载:https://blog.csdn.net/jiohfgj/article/details/106318464