redis
12. 三种特殊的数据类型
12.1 GEO地理位置
简介:
redis的GEO特性在redis3.2版本中推出,这个功能可以将用户给定的地理位置信息存储起来,并对这些信息进行操作。来实现诸如附近位置,摇一摇这类依赖于地理位置信息的功能。GEO的数据类型为Zset。
GEO的数据结构总共有六个常用命令:geoadd ,geopos ,geodist , georadius, georadiusbymember , gethash
官方文档:https://www.redis.net.cn/order/3685.html
命令:
geoadd
将指定的地理空间位置(经度,纬度,名称)添加到指定的key中。这些数据会存储到有序集合Zset中,目的是为了方便使用georadius,或者georadiusbymember命令对数据进行半径查询等操作。
geoadd china:city 121.48 31.40 shanghai 113.88 22.55 shenzhen 120.21 30.20 hangzhou
注意点:该命令采用标准格式的参数xy,所以经度必须在纬度之前。有效的经度 -180~180度,有效的纬度 -85.05~85.05
geopos
从key里面返回所有给定位置元素的位置(经度和纬度)
geopos key member[member...]
geopos china:city shanghai hangzhou
geodist
返回两个给定位置之间的距离,指定的参数unit必须是以下中的一种。m 米 ,km 千米, mi 英里,ft 英尺,默认是m。
geodist key member1 member2 [unit]
geodist china:city shanghai hangzhou km
georadius
以给定的经纬度为中心,找出某一半径内的元素。
范围的单位:m米,km千米 ,mi英里 ,ft英尺
georadius key longitude(经度) latitude(纬度) radius m|km|ft|mi [withcoord] [withdist][withhash][asc|desc][count count]
withdist:返回位置元素的同时,将位置元素与中心之间的距离也一并返回。距离的单位与用户给定范围单位保持一致。
withcoord:将位置元素的经纬度也返回。
withhash:以 52 位有符号整数的形式, 返回位置元素经过原始 geohash 编码的有序集合分值。 这个选项主要用于底层应用或者调试, 实际中的作用并不大
asc: 根据中心的位置, 按照从近到远的方式返回位置元素。
desc:根据中心的位置, 按照从远到近的方式返回位置元素。
count:获取前n个匹配的元素,可以减少带宽当数据量大时。
举例:
georadius china:city 120 30 1500 km withdist
georadiusbymember
找出位于指定范围内的元素,中心点是由给定的位置元素决定的。
georadiusbymember key member radius m|km|ft|mi [withcoord][withdist][withhash][asc|desc][count count]
举例:
georadiusbymember china:city shanghai 1500 km
geohash
返回一个或者多个位置元素的geohash表示。
redis使用geohash将二维经纬度转换为一维字符串,字符串越长表示位置更精确,两个字符串越相似表示距离越近。
geohash key member[member...]
举例:
geohash china:city shanghai hangzhou
zrem
geo没有提供删除成员的方法,但是因为geo的底层是zset,所有可以用zrem命令实现对地理位置信息的删除。
zrem china:city shanghai
:移除元素
zrange china:city 0 -1
:查看所有元素
命令演示
12.2 HyperLogLog
简介
redis在2.8.9版本添加了HyperLogLog结构。
redis HyperLogLog是用来做基数统计的算法。HyperLogLog的优点是在输入元素的数量和体积非常大时,计算基数所需的空间总是固定的,并且很小。
基数:比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
在redis里面,每个HyperLogLog键只需要花费12kb内存,就可以计算接近2^64个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
HyperLogLog则是一种算法,提供了不精确的去重基数方案。
举个栗子:假如我要统计网页的UV(浏览用户数量,一天内同一个用户多次访问只能算一次),传统的 解决方案是使用Set来保存用户id,然后统计Set中的元素数量来获取页面UV。但这种方案只能承载少量 用户,一旦用户数量大起来就需要消耗大量的空间来存储用户id。我的目的是统计用户数量而不是保存 用户,这简直是个吃力不讨好的方案!而使用Redis的HyperLogLog最多需要12k就可以统计大量的用户 数,尽管它大概有0.81%的错误率,但对于统计UV这种不需要很精确的数据是可以忽略不计的
基本命令
pfadd key element[element...]
:添加指定元素到HyperLogLog
pfcount key[key...]
:返回给定HyperLogLog的基数估计值。
pfmerge destkey sourcekey [sourcekey...]
:将多个HyperLogLog合并为一个HyperLogLog,并集计算。
12.3 BitMap
简介
在开发中,可能会遇到这种情况:需要统计用户的某些信息,如活跃或不活跃,登录或者不登录;又如 需要记录用户一年的打卡情况,打卡了是1, 没有打卡是0,如果使用普通的 key/value存储,则要记录 365条记录,如果用户量很大,需要的空间也会很大,所以 Redis 提供了 Bitmap 位图这种数据结构, Bitmap 就是通过操作二进制位来进行记录,即为 0 和 1;如果要记录 365 天的打卡情况,使用 Bitmap 表示的形式大概如下:0101000111000111…,这样有什么好处呢?当然就是节约内存 了,365 天相当于 365 bit,又 1 字节 = 8 bit , 所以相当于使用 46 个字节即可。
BitMap 就是通过一个 bit 位来表示某个元素对应的值或者状态, 其中的 key 就是对应元素本身,实际上 底层也是通过对字符串的操作来实现。Redis 从 2.2 版本之后新增了setbit, getbit, bitcount 等几个 bitmap 相关命令。
基本命令
setbit key n value
:设置key的第n位的值value是0或1,n是从0开始的。getbit key n
:获取第n位的值。如果没有设置返回0bitcount key[start,end]
:统计key上值为1的个数。
13. redis事务
理论
redis事务的概念:
redis事务的本质是一组命令的集合。事务支持一次执行多个命令,一个事务中所有的命令都会被序列化。在事务执行过程中,会按照顺序串行化执行对列中 的命令,其他客户端提交的命令请求不会插入到事务执行的命令序列中。
总结的说:redis事务就是一次性,顺序性,排他性的执行一个对列中的一系列命令。
redis事务没有隔离级别的概念:
批量操作在发送exec命令前被放入对列缓存,并不会被实际执行。
redis不保证原子性:
redis中,单条命令是原子性执行的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。
redis事务的三个阶段:
开始事务
命令入队
执行事务
redis事务的相关命令
watch key1 key2...
:监视一个或者多个key,如果在事务执行之前,被监视的key被其他命令改动,则事务被打断(类似乐观锁)unwatch
:取消watch对所有key的监控。multi
:标记一个事务块的开始(queued)exec
:执行所有事务块的命令(一旦执行exec后,之前加的监控锁都会被取消掉)discard
:取消事务,放弃事务块中所有 的命令
乐观锁和悲观锁?
- 悲观锁(Pessimistic Lock),顾名思义就是很悲观,每次拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿到这个数据就会block知道它拿到锁。传统的关系型数据库里面就用到了很多这种锁机制,比如行锁,表锁等,读锁写锁等,都是在操作之前先上锁。
- 乐观锁(Optimistic Lock)顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制,乐观锁适用于多读的应用类型,这样可以提高吞吐量,乐观锁策略:提交版本必须大于记录当前版本才能执行更新。
实践
-
正常执行
-
放弃事务
-
如果事务对列中存在命令性错误(类似java编译性错误),执行exec命令时,所有命令都不会执行。
-
如果在事务对列中存在语法性错误(类似于java的1/0运行时异常),则执行exec命令时,其他正确命令会被执行,错误命令抛出异常。
-
watch命令的演示(watch 用来监控key在事务执行的前后是否变化)–事务成功执行
-
使用watch–事务执行失败
由于在事务执行过程中,watch监视的值出现了变化,因此导致了错误,这时要放弃监视,然后重来。
**注意:**一旦exec执行事务后,无论事务执行成功还是失败,watch对变量的监控都被取消。因此当事务执行失败后,需要重新执行watch对变量进行监控,并开启新的事务进行操作。
watch指令类似于乐观锁,事务提交时,如果watch监控的多个key中任何key的值被其他客户端更改,则使用exec执行事务时,事务对列不会被执行,同时返回Nullmulti-bulk 通知调用者事务执行失败。
14. springboot整合redis
步骤:
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
-
在application.properties中配置
spring.redis.host=115.29.232.195 spring.redis.port=6379 spring.redis.database=0 spring.redis.timeout=50000
-
测试
@SpringBootTest class SpringbootRedisApplicationTests { @Autowired private RedisTemplate redisTemplate; @Test void contextLoads() { /* * opsForList:操作list,类似string * opsForGeo 操作geo * opsForSet * ....... * 和redis命令行一样。 * */ /*获取redis的连接对象*/ // RedisConnection connection = redisTemplate.getConnectionFactory().getConnection(); // connection.flushDb(); // connection.flushAll(); redisTemplate.opsForValue().set("mykey","kuangshen"); System.out.println(redisTemplate.opsForValue().get("mykey")); redisTemplate.opsForSet().add("k5","v9"); Set k5 = redisTemplate.opsForSet().members("k5"); System.out.println(k5); } }
redis要做的配置
- 阿里云设置redis的安全组:端口6379
-
修改redis.conf
(在/usr/local/bin/myredisconfig/redis.conf)
bind 127.0.0.1 改为 bind 0.0.0.0(注释掉也行)
protected-mode yes 改为 protected-mode no: (即该配置项表示是否开启保护模式,默认是开启,开启后Redis只会本地进行访问,拒绝外部访问)
daemonize no 改为 daemonize yes
注意:修改后要重新登录redis
-
关于防火墙的设置
rpm -qa|grep firewalld;rpm -qa|grep firewall-config
:检查系统是否安装了firewalld和firewall-config.CentOS中系统默认安装firewalld, firewall-config要自己安装yum -y update firewalld
:将firewalld更新为最新版yum -y install firewall-config
:安装firewall-configsystemctl start firewalld
:启动firewalld服务systemctl status firewalld
:查看firewalld的状态systemctl stop firewalld
:停止firewalld服务systemctl enable firewalld
:恢复开机自动启动firewalld服务推荐参考文章:CentOS7中firewalld的安装与使用详解 https://blog.csdn.net/solaraceboy/article/details/78342360
-
检查阿里云的防火墙是否开放端口号
firewall-cmd --query-port=6379/tcp
如果是 yes,则就是开放的。如果是no
则永久开放6379端口:
firewall-cmd --zone=public --add-port=6379/tcp --permanent
重新加载:
firewall-cmd --reload
然后再查看端口是否开启:
firewall-cmd --query-port=6379/tcp
-
查看redis服务进程
ps -ef | grep redis
-
如果上面的方法都试过了但还是不行,试试重启服务器
推荐一个好用的redis客户端工具
Another Redis Desktop Manager
下载地址:https://github.com/qishibo/AnotherRedisDesktopManager、
可以用它来测试和管理redis
redis的序列化配置
- 通过源码可知,springboot自动帮我们在容器中生成了一个redisTemplate和一个StringRedisTemplate,但是这个redisTemplate的泛型是<ooject,object>,写代码不方便,需要类型转换的代码,并且没有设置数据存在redis时,key及value的序列化方式。因此要自定义一个配置类。
- 为什么要序列化:https://www.jianshu.com/p/cc5a29b06b3d
package com.kuang.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;
/**
* Created with Intellij IDEA
* Description:
* user: CoderChen
* Date: 2021-06-06
* Time: 14:05
*/
@Configuration
public class redisConfig {
/*编写自己的redisTemplate----固定模板*/
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
/*为了自己开发方便,一般直接使用<String,object>*/
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);
/*string序列化配置*/
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
/*配置具体的序列化方式*/
/*key采用string的序列化方式*/
template.setKeySerializer(stringRedisSerializer);
/*hash的key采用string的序列化方式*/
template.setHashKeySerializer(stringRedisSerializer);
/*value序列化方式采用jackson*/
template.setValueSerializer(Jackson2JsonRedisSerializer);
/*hash的value序列化方式采用jackson*/
template.setHashValueSerializer(Jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
redis的工具类
- 直接使用redisTemplate操作redis时,需要很多代码,因此直接封装一个redisUtils,这样写代码方便一点。这个redisUtils交给spring容器实例化,使用时直接注解注入。
package com.kuang.utils;
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;
/**
* Created with Intellij IDEA
* Description:
* user: CoderChen
* Date: 2021-06-06
* Time: 14:52
*/
/*在真实开发中,经常使用*/
@Component
public final class RedisUtil {
@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;
}
}
}
测试代码
package com.kuang;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kuang.pojo.User;
import com.kuang.utils.RedisUtil;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Set;
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
@Qualifier("redisTemplate")/*避免与源码重合,跳转到自定义redisTempalte*/
private RedisTemplate redisTemplate;
@Autowired
private RedisUtil redisUtil;
@Test
void contextLoads() {
/*
* opsForList:操作list,类似string
* opsForGeo 操作geo
* opsForSet
* .......
* 和redis命令行一样。
* */
/*获取redis的连接对象*/
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
redisTemplate.opsForValue().set("mykey","kuangshen");
System.out.println(redisTemplate.opsForValue().get("mykey"));
redisTemplate.opsForSet().add("k5","v9");
Set k5 = redisTemplate.opsForSet().members("k5");
System.out.println(k5);
}
@Test
public void test() throws JsonProcessingException {
/*真实开发一般使用json传递数据*/
User user = new User("kuangshen", 3);
// String jsonUser = new ObjectMapper().writeValueAsString(user);
// redisTemplate.opsForValue().set("user", jsonUser);
redisTemplate.opsForValue().set("user", user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
@Test
public void test1() {
redisUtil.set("username", "coderchen");
System.out.println(redisUtil.get("username"));
}
}
15. conf配置文件分析
熟悉基本配置
- redis的配置文件的位置是/usr/local/redis/redis-6.06 redis.conf,但我们平时将它拷贝到/usr/local/bin/myredisconfig redis.conf 来进行操作和修改配置文件。确保初始文件的安全。
-
在redis中查看基本的配置
config get *
:获取全部的配置,可以查看配置是否修改成功。 -
Units单位
配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit
对大小写不敏感
-
includes包含
和spring配置文件相似,可以通过includes包含,redis.conf作为总文件,可以包含其他文件。(读一下前面的英文说明即知)
-
network网络配置
bind 0.0.0.0 :绑定的主机地址
protected-mode no:保护模式,默认为yes, 默认是开启,开启后Redis只会本地进行访问,拒绝外部访问.需要设置为yes
port 6379 :默认端口
-
general通用
daemonize yes:默认情况下,redis不作为守护进程运行,需要开启的话,改为yes
supervised no:可以通过upstart和systemd管理redis守护进程。
pidfile /var/run/redis_6379.pid:以后台进程的方式运行redis,则需要指定pid文件。
loglevel notice:日志级别。可选项有:
debug:记录大量日志信息,适用于开发,测试阶段
verbose:较多日志信息
notice:适量日志信息,适用于生产环境
warning:仅有部分重要,关键信息才会被记录
logfile “”:日志文件的位置,当指定为空字符串时,为标准输出。
databases 16:设置数据库的数目,默认数据库时DB 0
always-show-logo yes:是否总是显示logo
-
snapshopting快照
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BF2Vf7Dn-1623067788419)(https://cdn.jsdelivr.net/gh/chenshuai-coder/CDN/img/20210607103041.png)]
save 900 1:900秒(15分钟)内至少一个key值改变(则进行数据库保存–持久化)
save 300 10:300秒(5分钟内)至少10个key值改变(则进行数据库保存–持久化
save 60 10000: 60秒(1分钟)内至少10000个key值改变(则进行数据库保存–持久化)stop-writes-on-bgsave-error yes:持久化出现错误后是否依然进行工作
rdbcompression yes:使用压缩rdb文件 yes:压缩 但是需要一些cpu 的消耗 no:不压缩,需要更多的磁盘空间。
rdbchecksum yes:是否校验rdb文件,更有利于文件的容错性,但是在保存rdb文件的时 候,会有大概10%的性能损耗
dbfilename dump.rdb:dbfilenamerdb文件名称
dir ./: dir 数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
-
security安全
获得和设置密码:(需要先进入redis)
config get requirepass
config set requirepass "123456"
然后每次登录redis时,都要使用auth验证:
auth password
注意:如果服务器使用redis不设置密码,信息可能会收到攻击导致丢失。
在conf中设置密码,将requirepass 这个注释去掉,后面的字段改成对应的密码。
-
clients客户端
maxclients 10000:设置能连上redis的最大客户端连接数量
-
memory management 内存管理
maxmemory :redis配置的最大内存容量
maxmemory-policy noeviction:maxmemory-policy 内存达到上限的处理策略
volatile-lru:利用LRU算法移除设置过过期时间的key。
volatile-random:随机移除设置过过期时间的key。
volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
allkeys-lru:利用LRU算法移除任何key。
allkeys-random:随机移除任何key。
noeviction:不移除任何key,只是返回一个写错误
-
append only mode 只追加模式
常见的配置介绍
- redis默认不是以守护进程的方式进行,可以通过该配置项修改,使用yes启动守护进程。
daemonize no —> daemonize yes
- 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指 定
pidfile /var/run/redis.pid
- 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认 端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字
port 6379
- 绑定的主机地址
bind 127.0.0.1
- 当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 300
- 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为 verbose
loglevel verbose
- 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方 式为标准输出,则日志将会发送给/dev/null
logfile stdout
- 设置数据库的数量,默认数据库为0,可以使用SELECT 命令在连接上指定数据库id
databases 16
- 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
save
Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更 改
- 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时 间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes
- 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
- 指定本地数据库存放目录
dir ./
- 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master 进行数据同步
slaveof
- 当master服务设置了密码保护时,slav服务连接master的密码
masterauth
- 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过AUTH 命令提供密码, 默认关闭
requirepass foobared
- 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可 以打开的最大文件描述符数,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时, Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxclients 128
- 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝 试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作, 但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory
- 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不 开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来 同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no
- 指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof
- 指定更新日志条件,共有3个可选值:
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec
- 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将 访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中
vm-enabled no
- 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享
vm-swap-file /tmp/redis.swap
- 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据 都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有 value都存在于磁盘。默认值为0
vm-max-memory 0
- Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多 个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page 大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用 默认值
vm-page-size 32
- 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中 的,,在磁盘上每8个pages将消耗1byte的内存
vm-pages 134217728
- 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都 是串行的,可能会造成比较长时间的延迟。默认值为4
vm-max-threads 4
- 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启
glueoutputbuf yes
- 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法
hash-max-zipmap-entries 64
hash-max-zipmap-value 512
- 指定是否激活重置哈希,默认为开启
activerehashing yes
- 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各 个实例又拥有自己的特定配置文件
include /path/to/local.conf