目录
1.reids的简单介绍
2.redis的使用场景
3. 单例模式安装redis
4.springBoot整合redis
A.redis单机版
B.redis sentinel 版
C.redis cluster 版
https://redis.io/ redis的官网,引用官方的简单介绍
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps,hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster
简单的翻译下 :
redis是一个开放代码(BSD许可),内存数据结构存储,用作数据库、缓存和消息代理。支持字符串、哈希、列表、集合、有序集合,Redis具有内置的复制、Lua脚本、LRU收回、事务和不同级别的磁盘持久性,并通过Redis Sentinel和Redis群集的自动分区提供高可用性
redis 的命令 是 atomic operations ,也就是说具有原子性
reids可持久化,将数据持续化,两种方式,一种设置一段时间后将数据转储到硬盘,另一种方式追加每一条命令到日志
Redis also supports trivial-to-setup master-slave asynchronous replication, with very fast non-blocking first synchronization, auto-reconnection with partial resynchronization on net split
reids还支持简单的主从异步复制设置,注意一下,这里没有说redis支持分布式集群
transactions 事物
pub/sub 发布、订阅
lua scripting lua 脚本语言
keys with a limited time-to -live 键的有限存活时间
LRU eviction of keys 最近最少使用回收键的策略
Automatic failover 自动切换,即集群的部署方式,利用的redis sentinel 哨兵模式
本人的主机是debian,于是就直接安装redis,因为目的是为了巩固和学习,所以选择redis最新稳定版本5.0.3
另外说一句就是debian9默认的gnome桌面环境配置的软件还挺多的,用熟了还挺好的,开发完全足够了,起码没有windows系统那样时不时的弹出一个广告。
wget http://download.redis.io/releases/redis-5.0.3.tar.gz
解压后,直接make make test 没有问题后sudo make install
启动 redis redis-server 直接在terminal启动,出来下面的效果,退出当前命令,redis-server就关闭了
默认情况下 reids是不允许远程连接的
修改redis.conf 中的配置
注释掉 bind 127.0.0.1
并将 protected-mode 设为no
后台运行 daemonize 设为 yes
启动 redis 执行命令redis-server redis.conf
使用Redis Desktop Manager进行远程连接
也可以直接在linux上运行客户端vi
redis-cli
然后ping 出现PONG即说明redis正常启动并工作了
设置RedisConnectionFactory,spring 认为单机版无需制定poolConfig 指定连接地址等信息即可
@Bean
public RedisConnectionFactory JedisConnectionFactory() {
//reids单例模式,这个没有set方法注入到JedisConnectionFactory所以只能根据构造方法
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
//地址 端口 密码 数据库选择 spring认为这才是最基本的
redisStandaloneConfiguration.setHostName("127.0.0.1");
redisStandaloneConfiguration.setPort(6379);
//redisStandaloneConfiguration.setPassword(password);若有密码提供密码
redisStandaloneConfiguration.setDatabase(1);
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
return jedisConnectionFactory;
}
初始化redisTemplate ,开启transaction,并封装Util类
/**
* 实例化 RedisTemplate 对象
*
* @return
*/
@Bean
public RedisTemplate functionDomainRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
return redisTemplate;
}
/**
* 设置数据存入 redis 的序列化方式,并开启事务
*
* @param redisTemplate
* @param factory
*/
private void initDomainRedisTemplate(RedisTemplate redisTemplate, RedisConnectionFactory factory) {
// 如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to
// String!
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
// 开启事务
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.setConnectionFactory(factory);
}
/**
* 注入封装RedisTemplate @Title: redisUtil @return RedisUtil @autor lpl @date
* 2017年12月21日 @throws
*/
@Bean(name = "redisUtil")
public RedisUtil redisUtil(RedisTemplate redisTemplate) {
RedisUtil redisUtil = new RedisUtil();
redisUtil.setRedisTemplate(redisTemplate);
return redisUtil;
}
具体的RedisUtils类
package com.songhq.data.redis;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
public class RedisUtil {
private RedisTemplate redisTemplate;
public void setRedisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
//=============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
* @return
*/
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(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 by 要增加几(大于0)
* @return
*/
public long incr(String key, long delta){
if(delta<0){
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param by 要减少几(小于0)
* @return
*/
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
* @return 值
*/
public Object hget(String key,String item){
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map
至此,单机版的redis配置完成,接下来可以使用RedisUtil来访问redis.
package com.songhq.data.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.songhq.data.bean.User;
import com.songhq.data.redis.RedisUtil;
@RestController
@RequestMapping("/redis")
public class RedisController {
@Autowired
private RedisUtil redisUtil;
@RequestMapping("/set/{key}/{value}")
public String set(@PathVariable("key")String key ,@PathVariable("value")String value) {
redisUtil.set(key, value);
return "key is "+ key +"and value is "+value;
}
@RequestMapping("/get/{key}")
public String get(@PathVariable("key")String key ) {
Object object = redisUtil.get(key);
return "key is "+ key +"and value is "+object.toString();
}
@RequestMapping("/setuser/{key}")
public User setUser(@PathVariable("key")String key ) {
User user = new User();
user.setName(key);
user.setSex("female");
user.setAge("26");
redisUtil.set(key, user);
return user;
}
@RequestMapping("/getuser/{key}")
public User getUser(@PathVariable("key")String key ) {
User user =(User) redisUtil.get(key);
return user;
}
//判断一个key是否存在
public boolean exist(String key) {
return redisTemplate.hasKey(key);
}
//set一个值,仅当key不存在的
public Boolean setnx(String key ,Object value) {
return redisTemplate.opsForValue().setIfAbsent(key, value);
}
}
一个redis的简单应用,应用场景并发下的数量控制,奖品抢购,v1和v2是简单实现抢购的两种方案,其中v1是并发不安全的,
v2是并发安全的
/**
*
* redis应用在数量控制器的业务场景
* 第一个 购物节抢优惠名额有限
* 第二个 就是促销活动数量有限
* 第三个 就是抢红包,红包的个数有限
*
* 计数器版本1
* incr 获取增加量 或者 decr 获取减少量
*
* get(key) (判断) (问题1 两个同时get(key) 不存在,都设置初始值)
*
* key不存在的话,就初始化
*
* 存在的话,get(key)得到当前的数量
*
*
* 当前数量+ 要增加的数量 程序代码判断是否是超过数量(判断)(两个客户端 同时判断都没超过总素,都进行加数量的操作,会导致结果超出设定值)
*
* 如果没有则增加 并持久化到mysql等数据库
* 如果超过则结束
*
*
*
* 版本2
*
* 获取将要增加的数量
*
* get(key)不存在 设置 值 setnx
*
* 再get(key)得到当前数量
*
* incrby 当前数量+增加的数量 并会返回增加后的数量
*
* (用incrby 返回的结果进行判断)如果没有超过则成功
* 如果超过 则结束
*
*
*
* 抽奖限额
* @param key
* @return
*/
@RequestMapping("/counter/v1")
public String counter( ) {
//抽奖限额100
int limitAmount = 100;
//每个用户的中奖额为1
int decrAmount = 1;
//记录奖品数量的key
String key = "v1";
//如果不存v1这个键
if(!redisUtil.exist(key)) {
//进行初始化,只剩下5件商品了
redisUtil.set(key, limitAmount-95);
}
//获取当前量
Object object = redisUtil.get(key);
int currentAmount = Integer.valueOf(object.toString());
if(currentAmount -decrAmount < 0) {
//奖品完了
System.out.println("很抱歉,奖品被抢完了");
return "很抱歉,奖品被抢完了";
}else {
//中奖成功,奖奖品数量减1
redisUtil.decr(key, decrAmount);
System.out.println("恭喜你中奖了");
return "恭喜你中奖了";
}
}
@RequestMapping("/counter/v2")
public String counter2( ) {
//抽奖限额100
int limitAmount = 100;
//每个用户的中奖额为1
int decrAmount = 1;
//记录奖品数量的key
String key = "v2";
//如果不存v1这个键
if(!redisUtil.exist(key)) {
//进行初始化,只剩下5件商品了
redisUtil.setnx(key, limitAmount-95);
}
//获取当前量
Object object = redisUtil.get(key);
int currentAmount = Integer.valueOf(object.toString());
long result = redisUtil.decr(key, decrAmount);
if(result < 0) {
//奖品完了
System.out.println("很抱歉,奖品被抢完了");
return "很抱歉,奖品被抢完了";
}else {
System.out.println("恭喜你中奖了");
return "恭喜你中奖了";
}
}
测试 liunx平台 使用并发测试工具ab
ab -c 100 -n 200 http://localhost:9005/redis/counter/v1
ab -c 100 -n 200 http://localhost:9005/redis/counter/v2
-c是并发输 -n 是访问量
测试结果,v2实现了数量控制,只有5人中奖,v1存在并发问题,不止5人中奖
注意并发测试下比较耗内存,有可能默认的tomcat java参数不够使用,会导致tomcat无法正常响应
启动程序时加大数配置 -Xms1024M -Xmx1024M -XX:PermSize=256M -XX:MaxNewSize=256M -XX:MaxPermSize=256M
在debug试图下可以看到tomcat启动了100多个线程,这也验证了tomcat是一个请求对应一个线程的模式。
1.介绍和搭建 redis sentinel
官方介绍
Redis Sentinel provides high availability for Redis. In practical terms this means that using Sentinel you can create a Redis deployment that resists without human intervention to certain kind of failures.
Redis Sentinel also provides other collateral tasks such as monitoring, notifications and acts as a configuration provider for clients.
简单的说redis sentinel 可以自动抵抗某种失败,也就是某个redis server宕机,而且附属的功能有监视 、通知和作为客户端的配置,原文讲的比较清楚
2.首先搭建Replication
cp redis.conf redis-master.conf
cp redis.conf redis-slave.conf
mater的配置不用改,这里只需改redis-slave的配置
修改port 的端口为 6380
增加 replicaof 127.0.0.1 6379
注释掉master特有的配置 export XMODIFIERS=@im=fctix 设置只读
先后启动master 和slave 简单测试一下在master上面set值,在slave上可以获取该值,默认情况下slave只读
3.搭建sentinel 至少需要三台
port 5000
sentinel myid a5740c4b73fe8ca091e3612a222538dc19509fc5
sentinel deny-scripts-reconfig yes
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
daemonize yes
protected-mode no
# Generated by CONFIG REWRITE
dir "/usr/local/my/redis"
maxclients 4064
sentinel failover-timeout mymaster 60000
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0
sentinel known-replica mymaster 127.0.0.1 6380
sentinel known-sentinel mymaster 127.0.0.1 5002 46a90470ce2c585845d9a75abd3a3211f029bdad
sentinel known-sentinel mymaster 127.0.0.1 5001 64488234fa6525fe9a2f04d8f610c738ae725db3
sentinel current-epoch 0
redis-sentinel sentinel-5000.conf 启动这台,接下来改一下端口号,启动5001和5002这两台
netstat -tnulp|grep redis 查看redis相关进程的端口情况,所有端口启动成功
redis-cli -p 5000
连接到5000的sentinel,并查看master的信息
sentinel master mymaster
获取master
SENTINEL get-master-addr-by-name mymaster
出现6379 ,测试高可用,睡眠6379,再次运行SENTINEL get-master-addr-by-name mymaster 出现6380,即master为6380了,
即重新选举了,一个简单的sentinel就搭建成功了
redis-cli -p 6379 DEBUG sleep 30
4.springBoot的设置
@Bean
public JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大空闲数
jedisPoolConfig.setMaxIdle(maxIdle);
// 连接池的最大数据库连接数
jedisPoolConfig.setMaxTotal(maxTotal);
// 最大建立连接等待时间
jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
// 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
// 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
// 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
// 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
jedisPoolConfig.setTestOnBorrow(testOnBorrow);
// 在空闲时检查有效性, 默认false
jedisPoolConfig.setTestWhileIdle(testWhileIdle);
return jedisPoolConfig;
}
/**
* redis sentinel版配置
* @param jedisPoolConfig
* @return
*/
@Bean
public RedisConnectionFactory JedisConnectionFactory(JedisPoolConfig jedisPoolConfig ) {
//使用RedisSentinelConfiguration进行配置
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration();
redisSentinelConfiguration.setDatabase(0);
redisSentinelConfiguration.setMaster("mymaster");
ArrayList arrayList = new ArrayList<>();
RedisNode redisNode1 = new RedisNode("127.0.0.1", 5000);
RedisNode redisNode2 = new RedisNode("127.0.0.1", 5001);
RedisNode redisNode3 = new RedisNode("127.0.0.1", 5002);
arrayList.add(redisNode1);
arrayList.add(redisNode2);
arrayList.add(redisNode3);
redisSentinelConfiguration.setSentinels(arrayList);
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisSentinelConfiguration,jedisPoolConfig);
return jedisConnectionFactory;
}
其他的不用变,测试访问可行,sentinel的搭建和配置就完成了。
1.redis cluster 的搭建,参见官方教程 https://redis.io/topics/cluster-tutorial,
开启集群的配置 如下 ,为增加集群的可靠性,为每个集群节点增加一个slave,这里搭6台; 端口分别是 7000
7001,7002,7003,7004,7005
port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
分别启动上面的relids-server
redis 5版本后就继承集群的命令,不用像redis 4那样依赖rubby 执行gem install redis 然后 "\" 表示换行
./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005
redis 5版本后的直接执行
redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \
--cluster-replicas 1
注意这里值得一提的是,根据提示,到这里基本上redis集权就搭建好了
但是如果远程连接的话,会出现问题,原因是这里使用的127.0.0.1,要想远程也能连接的话,需将127.0.0.1更改为本机的在互联网上的ip地址,注意这是个坑
我们测试一下搭建的集群
redis-cli -c -p 7000登入到7000的server上,执行get zhangsan 发现出现redirected 到7001 的server上,说明集群搭建成功
-> Redirected to slot [10454] located at 47.100.212.206:7001
(nil)
再使用命令查看redis cluster的状态
47.100.212.206:7001> cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:6
cluster_my_epoch:2
cluster_stats_messages_ping_sent:734362
cluster_stats_messages_pong_sent:731393
cluster_stats_messages_meet_sent:2
cluster_stats_messages_sent:1465757
cluster_stats_messages_ping_received:731389
cluster_stats_messages_pong_received:734364
cluster_stats_messages_meet_received:4
cluster_stats_messages_received:1465757
47.100.212.206:7001> cluster nodes
4ee8576b9e307c704a512ffae789696b08dc14d7 47.100.212.206:7002@17002 master - 0 1553581104308 3 connected 10923-16383
7b039631155681cc6b614a32fc72cf1544de5952 47.100.212.206:7003@17003 slave e565d66cde9f312d59e95e4be38eab9e4265063f 0 1553581103574 4 connected
e565d66cde9f312d59e95e4be38eab9e4265063f 47.100.212.206:7000@17000 master - 0 1553581103000 1 connected 0-5460
45319ce6f0a9d07d4e6eda626a4b9f95bfb02873 47.100.212.206:7004@17004 slave 2f9dbf4b14c11584b75feb3330390499c5b4e32d 0 1553581103273 5 connected
2f9dbf4b14c11584b75feb3330390499c5b4e32d 172.19.146.101:7001@17001 myself,master - 0 1553581103000 2 connected 5461-10922
84b09336b2334a68f470c318959ce88ee5ea5944 47.100.212.206:7005@17005 slave 4ee8576b9e307c704a512ffae789696b08dc14d7 0 1553581104527 6 connected
2,springBoot整合reids集群
其它的配置都不变,只需更改RedisConnectionFactory 这个连接工厂的生成方式
/**
* redis集群版配置
* @param jedisPoolConfig 配置有问题,没有通过
* @return
*/
@Bean
public RedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig ) {
HashSet hashSet = new HashSet();
hashSet.add("47.100.212.206:7000");
hashSet.add("47.100.212.206:7001");
hashSet.add("47.100.212.206:7002");
hashSet.add("47.100.212.206:7003");
hashSet.add("47.100.212.206:7004");
hashSet.add("47.100.212.206:7005");
RedisClusterConfiguration clusterConf = new RedisClusterConfiguration(hashSet);
clusterConf.setMaxRedirects(3);
JedisConnectionFactory factory = new JedisConnectionFactory(clusterConf,jedisPoolConfig);
return factory;
}
到这里,springBoot整合完毕。