//创建Jedis对象,连接Redis服务
//创建Jedis对象,连接Redis服务
Jedis jedis = new Jedis("192.168.10.100", 6379);
设置密码
Jedis.auth();
// 指定数据库 0
jedis.select(1);
使用ping命令是测试看是否连接成功
String result = jedis.ping(); System.out.println(result);
// 返回PONG
添加数据
Jedis.set("use","")
//
获取数据
Jedis.get
//释放资源
if(jedis==null)
{
jedis.close()
}
通过Redis连接池获取连接对象并且操作服务器
// 初始化redis客户端连接池 JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), “192.168.10.100”, 6379, 10000, “root”);
封装jedisUtil对外提供连接对象获取方法
通过**@Value**的注解
@Configuration public class RedisConfig { //服务器地址 @Value(" s p r i n g . r e d i s . h o s t " ) p r i v a t e S t r i n g h o s t ; / / 端 口 @ V a l u e ( " {spring.redis.host}") private String host; //端口 @Value(" spring.redis.host")privateStringhost;//端口@Value("{spring.redis.port}") private int port; //密码 @Value(" s p r i n g . r e d i s . p a s s w o r d " ) p r i v a t e S t r i n g p a s s w o r d ; / / 超 时 时 间 @ V a l u e ( " {spring.redis.password}") private String password; //超时时间 @Value(" spring.redis.password")privateStringpassword;//超时时间@Value("{spring.redis.timeout}") private String timeout; //最大连接数 @Value(" s p r i n g . r e d i s . j e d i s . p o o l . m a x − a c t i v e " ) p r i v a t e i n t m a x T o t a l ; / / 最 大 连 接 阻 塞 等 待 时 间 @ V a l u e ( " {spring.redis.jedis.pool.max-active}") private int maxTotal; //最大连接阻塞等待时间 @Value(" spring.redis.jedis.pool.max−active")privateintmaxTotal;//最大连接阻塞等待时间@Value("{spring.redis.jedis.pool.max-wait}") private String maxWaitMillis; //最大空闲连接 @Value(" s p r i n g . r e d i s . j e d i s . p o o l . m a x − i d l e " ) p r i v a t e i n t m a x I d l e ; / / 最 小 空 闲 连 接 @ V a l u e ( " {spring.redis.jedis.pool.max-idle}") private int maxIdle; //最小空闲连接 @Value(" spring.redis.jedis.pool.max−idle")privateintmaxIdle;//最小空闲连接@Value("{spring.redis.jedis.pool.min-idle}") private int minIdle;
@Bean public JedisPool redisPoolFactory(){ JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); //注意值的转变 jedisPoolConfig.setMaxWaitMillis(Long.parseLong(maxWaitMillis.substring(0,maxW aitMillis.length()-2))); //注意属性名 jedisPoolConfig.setMaxTotal(maxTotal); jedisPoolConfig.setMaxIdle(maxIdle); jedisPoolConfig.setMinIdle(minIdle); JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, Integer.parseInt(timeout.substring(0, timeout.length() - 2)), password); return jedisPool; } }
Redis操作五大基本的数据类型
连接与释放
@Autowired
private JedisPool JedisPool
private Jedis Jedis=null
//初始化jedis对象的实例
jedisPool.getResource();
jedis=jedisPool.getResource();
// Redis中以层级关系、目录形式存储数据 @Test public void testdir(){ 0 jedis.set(“user:01”, “user_zhangsan”);
System.out.println(jedis.get("user:01"));
}
redis操作byte
J
Jedis 是一个优秀的基于 Java 语言的 Redis 客户端,但是,其不足也很明显: Jedis 在实现上是直 接连接 Redis-Server,在多个线程间共享一个 Jedis 实例时是线程不安全的,如果想要在多线程场景 下使用 Jedis ,需要使用连接池,每个线程都使用自己的 Jedis 实例,当连接数量增多时,会消耗较 多的物理资源。
Lettuce 则完全克服了其线程不安全的缺点: Lettuce 是基于 Netty 的连接 (StatefulRedisConnection),
Lettuce 是一个可伸缩的线程安全的 Redis 客户端,支持同步、异步和响应式模式。多个线程可以共 享一个连接实例,而不必担心多线程并发问题。它基于优秀 Netty NIO 框架构建,支持 Redis 的高级功 能,如 Sentinel,集群,流水线,自动重新连接和 Redis 数据模型。
默认情况下的模板 RedisTemplate<Object, Object>,默认序列化使用的是 JdkSerializationRedisSerializer ,存储二进制字节码。这时需要自定义模板,当自定义模板后又 想存储 String 字符串
要把 domain object 做为 key-value 对保存在 redis 中,就必须要解决对象的序列化问题。Spring Data Redis给我们提供了一些现成的方案: JdkSerializationRedisSerializer 使用JDK提供的序列化功能。 优点是反序列化时不需要提供 类型信息(class),但缺点是序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗 Redis 服 务器的大量内存。
Jackson2JsonRedisSerializer 使用 Jackson 库将对象序列化为JSON字符串。优点是速度快,序 列化后的字符串短小精悍。但缺点也非常致命,那就是此类的构造函数中有一个类型参数,必须提供要 序列化对象的类型信息(.class对象)。通过查看源代码,发现其只在反序列化过程中用到了类型信息。 GenericJackson2JsonRedisSerializer 通用型序列化,这种序列化方式不用自己手动指定对象的 Class。
7.7. 操作string
@Configuration public class RedisConfig
缓存击穿
它的定义是 高并发的请求下,某个热门的key 突然过期了 导致大量的请求未找到缓存数据 进而全部去访问DB请求数据 导致了DB压力瞬间加大
解决方案:缓存击穿的情况下一般不容易造成DB的宕机.只会造成数据库的周期性压力.
缓存击穿的解决方案 Redis的数据不设置过期时间,然后在缓存的对象上添加一个属性标识过期时间,每次获取到数据时候,校验对象中的过期时间属性,如果数据即将过期 .就异步发起一个线程主动的更新缓存中数据 但是这种方案可能会导致拿到过期的值,就看业务能否可以接受
如果要求数据必须是新的数据,则最好的方案是为热点数据设置一个永不过期,再加上一个互斥锁来保证缓存的单线程写
缓存穿透:
缓存穿透是 恶意请求去查询缓存和数据库中都不存在的数据 比如通过id 去查询商品信息 id一般都是大于0, 但攻击者故意传id=-1去查询数据 由于缓存不命中则会从数据库中去获取数据库 , 这样导致每个请求都去访问数据库 造成缓存穿透
利用互斥锁 缓存失效的时候 先去获得锁 再去请求数据库 没有得到锁,则休眠一段时间后重试
采用异步更新策略,无论key是否取得值 都直接返回,value值中维护一个缓存失效时间,缓存如果过期了 就异步起一个线程去读取数据库,更新缓存,需要做缓存预热(项目启动前,先加一个缓存)操作.
提供一个能拦截机制 利用布隆过滤器 内部维护一个合法有效的key 不合法的数数据立刻返回
如果从数据库查到对象为空 也放入缓存,只是设定的缓存过期时间较短,比如设置时间为60秒
缓存雪崩
定义就是 如果大量缓存一段时间内集中过期了,这个时候会发生大量的缓存击穿现象 所有的请求都落在DB上 由于查询的数据量巨大 引起DB压力过大甚至是DB宕机
可能会被问到布隆过滤器是什么 怎么用呢
在解决Redis的缓存击穿 互斥锁是怎样具体保证缓存的单线程写
代码
package com.macro.mall.portal.service.impl;
import com.macro.mall.common.utils.JsonUtils;
import com.macro.mall.common.utils.RedisUtil;
import com.macro.mall.model.PmsProduct;
import com.macro.mall.portal.dao.PmsProductDao;
import com.macro.mall.portal.service.PmsProductService;
import com.mysql.cj.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import javax.annotation.Resource;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 商品管理Service实现类
* Created by macro on 2018/4/26.
*/
@Service
public class PmsProductServiceImpl implements PmsProductService {
private static final Logger LOGGER = LoggerFactory.getLogger(PmsProductServiceImpl.class);
@Autowired
private PmsProductDao pmsProductDao;
@Autowired
private RedisUtil redisUtil;
@Resource
private RedisTemplate<String, Object> redisTemplate;
private static final String KEY_PREFIX = "sku:info:id:";
private static final String KEY = "sku:id:";
//锁名称前缀
public static final String LOCK_PREFIX = "redis:lock:id:";
/**
* 查询商品详情
*
* @param id
* @return
*/
@Override
public PmsProduct list(Long id,String ip) {
System.out.println("当前" + ip + "正在访问");
//商品详情的key
String key = KEY_PREFIX;
String lockKey = LOCK_PREFIX + id;
String infoKey = KEY + id;
// 创建布隆过滤器对象,防止缓存击穿
/* BloomFilter<Integer> filter = BloomFilter.create(
Funnels.integerFunnel(),
15000,
0.01);
//添加数据到布隆过滤器
String ids = id.toString();
filter.putAll(Integer.valueOf(ids));*/
//查询缓存中是否存在
if (redisUtil.hasKey(key)) {
//查询缓存
return (PmsProduct) redisUtil.get(key);
} else {
System.out.println("ip为" + ip + "在缓存中没有查到,申请分布式锁" + lockKey);
//设置分布式锁,防止缓存击穿
String token = UUID.randomUUID().toString();
//redisTemplate设置分布式锁
//boolean lock = redisUtil.lock(lockKey);
Jedis jedis = new Jedis("localhost", 6379);
//防止删除别人的锁
String lock = jedis.set(lockKey, token, "nx", "px", 10 * 1000);
if (null != lock && "OK".equals(lock)) {
//设置成功,有权在锁有效时间内查询数据库
PmsProduct list = pmsProductDao.list(id);
//防止某用户抢不到锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list != null) {
BoundHashOperations<String, Object, Object> stringObjectObjectBoundHashOperations = redisTemplate.boundHashOps(key);
//放入redis
stringObjectObjectBoundHashOperations.put(infoKey, JsonUtils.serialize(list));
Random r = new Random();
//设置key的随机过期时间,防止缓存雪崩
stringObjectObjectBoundHashOperations.expire(r.nextInt(123), TimeUnit.SECONDS);
return list;
} else {
//防止缓存穿透,设置为null的key以及过期时间
BoundHashOperations<String, Object, Object> hashOperations = redisTemplate.boundHashOps(key);
hashOperations.put(key, "");
hashOperations.expire(60 * 3, TimeUnit.SECONDS);
}
//判断是否是删除自己的锁
String localToken = jedis.get(lockKey);
if (null != localToken && token.equals(localToken)) {
String script = "if redis.call('get',KEYS[1] == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Long eval = (Long) jedis.eval(script, Integer.parseInt(lockKey),localToken);
if (eval != null && eval != 0) {
//使用lua脚本在高并发情况下,获取key的同时删除key
jedis.del(lockKey);
}
System.out.println("ip" + ip + "申请分布式锁成功,并已删除");
}
//redisTemplate释放分布式锁
//redisUtil.deleteLock(key);
} else {
System.out.println("ip为" + ip + "申请分布式锁失败" + lockKey);
//设置自旋,重新访问
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//直接return,防止产生未执行线程
return list(id, ip);
}
}
return null;
}
}
/**
* 获得锁
*
* @param lock
* @return
*/
public boolean lock(String lock) {
return (boolean) redisTemplate.execute((RedisCallback) connection -> {
//获取时间毫秒值
long expireAt = System.currentTimeMillis() + LOCK_EXPIRE + 1;
//获取锁
Boolean acquire = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
if (acquire) {
return true;
} else {
byte[] bytes = connection.get(lock.getBytes());
//非空判断
if (Objects.nonNull(bytes) && bytes.length > 0) {
long expireTime = Long.parseLong(new String(bytes));
// 如果锁已经过期
if (expireTime < System.currentTimeMillis()) {
// 重新加锁,防止死锁
byte[] set = connection.getSet(lock.getBytes(), String.valueOf(System.currentTimeMillis() + LOCK_EXPIRE + 1).getBytes());
return Long.parseLong(new String(set)) < System.currentTimeMillis();
}
}
}
return false;
});
}
/**
* 删除锁
*
* @param key
*/
public void deleteLock(String key) {
redisTemplate.delete(key);
}
Redis主要有哪些功能
哨兵(Sentinel)和复制(Replication) 是为了确保原始服务器的完整备份
Sentinel可以管理多个Redis服务 它提供了监控,提醒以及自动的故障转移功能
Replication 则是负责让一个Redis服务器可以装配多个备份的服务器>Redis也是运用这两个功能来保证Redis高可用的\
事务 很多情况下我需要一次执行布置一个命令 而且需要它同时成功或者失败.Redis对事务的支持也是源于这部分的需求 即支持一次性按顺序执行多个命令的能力 且保证其原子性(多个指令不支持)
LuA脚本 在事务的的基础上 如果我们需要在服务器端一次性的执行更复杂的操作(包含一些逻辑判断),lua就可以派上用场了
持久化 Redis持久化是指Redis把内存中的数据写入到硬盘中,在Redis重启后加载这些数据 从而最大限度的降低缓存失去带来的影响
集群 单台服务器的资源总是有限的,CPU资源和Io资源我们可以通过主从复制 来进行读写分离 把一部分cpu和Io的压力转移到服务器上 这也有点类似于mysql 数据的主从同步(复制) 在Redis官方公布分布式方案之前 有twemproxy和codis这两种方案,这两种方案总体上来说 都是依赖proxy来进行分布式的
Redis 支持哪几种数据类型(String最大存储量为512M,其余的都是2^32-1)
String :最基本的数据域类型,二进制安全的字符串 最大存储量为512M
List:按照添加顺序保持顺序的字符串列表
Set: 无序无重复的字符串集合
sorted sets 已经排序的字符串集合.
hash: key-value对的一种集合
Redis是单进程单线程的
,Redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销
Nginx:多进程单线程模型
Memcached:单进程多线程
Redis为什么是单线程的
如果多线程处理就会涉及到锁,而且多线程处理会涉及到线程切换而消耗CPU .由于CPU不是Redis 的瓶颈 ,它的瓶颈最有可能是时机器内存或者网络带宽.单线程无法发挥多核CPU性能,不过可以通过单机开多个Redis实例来解决
使用Redis的优势
速度快 因为数据存在内存中,类似于HashMap ,hashmap的优势就是查找和操作时间复杂度都是o(l)
支持丰富的数据类型: 五大数据类型 String list hash set sorted sets
支持事务 操作都是原子性 所谓的原子性就是对数据的更改要模式全部执行 要么是全部不执行
丰富的特性 可用于缓存 消息 按key设置过期时间 过期时间会自动删除
Redis 单点吞吐量
单点的tps达到8万/秒, Qps达到10万/秒
QPS: 应用系统每秒最大接受的用户访问量
Tps 每秒钟最大能处理的请求数目
Redis相比memcached有哪些优势
memcached所有的值均是简单的字符串,Redis作为其替代者,支持更为丰富的数据类型
Redis的速度比memcached快的很多
Redis可以持久化其数据
Redis支持数据的备份,也是是master-slave模式的备份
Redis有几种数据淘汰策略
Redis中 ,允许用户设置最大使用内存大小server.maxmemory,当Redis内存数据集大小上升到一定大小的时候 就会实行数据的淘汰策略
volatile-lru 从设置过期的数据集中挑选最近最少使用的淘汰
volatie-ttr 从设置过期的数据集中挑选将要过期的数据淘汰
volatie-random :从已设置过期的数据集中任意挑选数据淘汰
allekeyl-lru:从数据集中任意最近最少使用的数据淘汰
noenviction: 禁止淘汰数据
Redis 淘汰数据的时候还会同步到aof
Redis 集群方案应该做什么 都有哪些方案
1 twemproxy
2codis 目前用到的最多集群方案 基本和twemproxy一致的效果 但它支持在节点数量改变情况下,旧节点数据可恢复到新hash节点
3Redis cluster 3.0自带的集 特点在于它的分布式算法不是一致性hash,而是hash槽的概念 以及自身节点设置从节点
Redis读写分离模型
通过增加Slave DB的数量 读的性能可以线性增长 为了避免MasterDB的单点故障 集群一般都会采用两台Master DB做双机热备 整个集群的读写的可用性都非常高
读写分离架构的缺陷在于 不管是master还是slave 每个节点都必须保存完整的数据 如果在数据量 很大的情况下 集群的扩展能力还是受限于单个结点的存储能力 而且对于Write-intensive类型的应用 读写架构并不合适
Redis 数据分片模型**
为了解决读写分离模型的缺陷 可以将数据分片模型用起来
可以将每个节点看曾独立的master,然后通过业务实现数据分片
结合上面的Redis读写分离和数据分片模型 可以将每个master设计成一个master和多个slave组成的模型
Redis提供了两种持久化方式
RDB
AOF
2.RDB 默认开启,会按照配置的指定时间将内存中的数据快照到磁盘中,创建一个dump.rdb文 件,Redis启动时再恢复到内存中。 Redis会单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然 后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文 件,然后子进程退出,内存释放。
需要注意的是,每次快照持久化都会将主进程的数据库数据复制一遍,导致内存开销加倍, 若此时内存不足,则会阻塞服务器运行,直到复制结束释放内存;都会将内存数据完整写入 磁盘一次,所以如果数据量大的话,而且写操作频繁,必然会引起大量的磁盘I/O操作,严 重影响性能,并且后一次持久化后的数据可能会丢失; 3.AOF 以日志的形式记录每个写操作(读操作不记录),只需追加文件但不可以改写文件,Redis 启动时会根据日志从头到尾全部执行一遍以完成数据的恢复工作。包括lushDB也会执行。 主要有两种方式触发:有写操作就写、每秒定时写(也会丢数据)。 因为AOF采用追加的方式,所以文件会越来越大,针对这个问题,新增了重写机制,就是当 日志文件大到一定程度的时候,会fork出一条新进程来遍历进程内存中的数据,每条记录对 应一条set语句,写到临时文件中,然后再替换到旧的日志文件(类似rdb的操作方式)。默 认触发是当aof文件大小是上次重写后大小的一倍且文件大于64M时触发。
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。一般情况下,只要使用默认 开启的RDB即可,因为相对于AOF,RDB便于进行数据库备份,并且恢复数据集的速度也要 快很多。
开启持久化缓存机制,对性能会有一定的影响,特别是当设置的内存满了的时候,更是下降 到几百reqs/s。所以如果只是用来做缓存的话,可以关掉持久化
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复 原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进 行后台重写,使得AOF文件的体积不至于过大.
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式. 你也可以同时开启两种持久化方式, 在这种情况下, 当Redis重启的时候会优先载入AOF文件来 恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整. 重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始。
Redis常见性能问题和解决方案
Master最好不要做任何持久化工作 如RDB内存快照和AoF日志文件
如果数据比较重要 某个Slave开启AoF备份数据,策略设置为每秒同步一次
为了主从复制的书读和连接的稳定性 Master和Slave了最好在同一个局域网内
就尽量避免在压力很大的主库上增加从库
主从复制不要图状结构 用单项链表结构更为稳定 5.主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <Slave3… 这样的结构方便解决单点故障问题,实现Slave对Master的替换。如果Master挂了, 可以立刻启用Slave1做Master,其他不变
Redis 支持的java客户端都有哪些
Redisson jedis lettuce 等 官方推荐Redissom
Redis支持的Java客户端都有哪些?官方推荐用哪个? Redisson、Jedis、lettuce等等,官方推荐使用Redisson。
Redis哈希槽的概念?
Redis集群没有使用一致性hash,而是引入了哈希槽的概念,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384的值,决定将一个key放到哪个桶中。
Redis集群最大节点个数是多少?
Redis集群预分好16384个桶(哈希槽)
Redis集群的主从复制模型是怎样的?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主 从复制模型,每个节点都会有N-1个复制品. Redis集群会有写操作丢失吗?为什么? 欢迎关注公众号【码农突围】
Redis并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操 作。
Redis集群之间是如何复制的?
异步复制
Redis如何做内存优化?
尽可能使用散列表(hashes),散列表(是说散列表里面存储的数少)使用的内存非常小, 所以你应该尽可能的将你的数据模型抽象到一个散列表里面。比如你的web系统中有一个用 户对象,不要为这个用户的名称,姓氏,邮箱,密码设置单独的key,而是应该把这个用户的 所有信息存储到一张散列表里面. Redis回收进程如何工作的?
一个客户端运行了新的命令,添加了新的数据。 Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
Redis回收使用的是LRU算法
Redis有哪些适合的场景
Session 共享(单点登录)
页面缓存
队列
排行榜/计数器
发布/订阅