四、配置文件介绍
4.1 Units单位
配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit
大小写不敏感
4.2 INCLUDES包含
类似jsp中的include,多实例的情况可以把公用的配置文件提取出来
4.3 网络相关配置
4.3.1 bind
默认情况bind=127.0.0.1只能接受本机的访问请求
不写的情况下,无限制接受任何ip地址的访问
生产环境肯定要写你应用服务器的地址;服务器是需要远程访问的,所以需要将其注释掉
如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应
保存配置,停止服务,重启启动查看进程,不再是本机访问了。
4.3.2 protected-mode
将本机访问保护模式设置 no
4.3.3 port
端口号,默认 6379
4.3.4 timeout
一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭
4.3.5 tcp-keepalive
对访问客户端的一种心跳检测,每个n秒检测一次。
单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60
4.4 GENERAL通用
4.4.1 daemonize
是否为后台进程,设置为yes
守护进程,后台启动
4.4.2 pidfile
存放pid文件的位置,每个实例会产生一个不同的pid文件
4.4.3 loglevel
指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice
四个级别根据使用阶段来选择,生产环境选择notice 或者warning
4.5 SECURITY安全
4.5.1 设置密码
访问密码的查看、设置和取消
在命令中设置密码,只是临时的。重启redis服务器,密码就还原了。
永久设置,需要再配置文件中进行设置。
4.6 LIMITS限制
4.6.1 maxclients
设置redis同时可以与多少个客户端进行连接。
默认情况下为10000个客户端。
如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
4.6.2 maxmemory
建议必须设置,否则,将内存占满,造成服务器宕机
设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。
如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。
4.6.3 maxmemory-policy
volatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用)
allkeys-lru:在所有集合key中,使用LRU算法移除key
volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
allkeys-random:在所有集合key中,移除随机的key
volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
noeviction:不进行移除。针对写操作,只是返回错误信息
五、Redis的发布和订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。
客户端可以订阅频道如下图
当给这个频道发布消息后,消息就会发送给订阅的客户端
5.1 测试
打开一个客户端订阅channel1
SUBSCRIBE channel1
打开另一个客户端,给channel1发布消息hello
返回的1是订阅者数量
打开第一个客户端可以看到发送的消息
注:发布的消息没有持久化,如果在订阅的客户端收不到hello,只能收到订阅后发布的消息
六、Redis新数据类型
6.1 Bitmaps
现代计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位, 例如“abc”字符串是由3个字节组成, 但实际在计算机存储时将其用二进制表示, “abc”分别对应的ASCII码分别是97、 98、 99, 对应的二进制分别是01100001、 01100010和01100011,如下图
合理地使用操作位能够有效地提高内存使用率和开发效率。
Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:
(1) Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。
(2) Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。
6.1.1 命令
setbit
(1)格式
setbit设置Bitmaps中某个偏移量的值(0或1)
*offset:偏移量从0开始
(2)实例
每个独立用户是否访问过网站存放在Bitmaps中, 将访问的用户记做1, 没有访问的用户记做0, 用偏移量作为用户的id。
设置键的第offset个位的值(从0算起) , 假设现在有20个用户,userid=1, 6, 11, 15, 19的用户对网站进行了访问, 那么当前Bitmaps初始化结果如图
unique:users:20201106代表2020-11-06这天的独立访问用户的Bitmaps
注:
很多应用的用户id以一个指定数字(例如10000) 开头, 直接将用户id和Bitmaps的偏移量对应势必会造成一定的浪费, 通常的做法是每次做setbit操作时将用户id减去这个指定数字。
在第一次初始化Bitmaps时, 假如偏移量非常大, 那么整个初始化过程执行会比较慢, 可能会造成Redis的阻塞。
2、getbit
(1)格式
getbit<key><offset>获取Bitmaps中某个偏移量的值
获取键的第offset位的值(从0开始算)
(2)实例
获取id=8的用户是否在2020-11-06这天访问过, 返回0说明没有访问过:
注:因为100根本不存在,所以也是返回0
3、bitcount
统计字符串被设置为1的bit数。一般情况下,给定的整个字符串都会被进行计数,通过指定额外的 start 或 end 参数,可以让计数只在特定的位上进行。start 和 end 参数的设置,都可以使用负数值:比如 -1 表示最后一个位,而 -2 表示倒数第二个位,start、end 是指bit组的字节的下标数,二者皆包含。
(1)格式
bitcount<key>[start end] 统计字符串从start字节到end字节比特值为1的数量
(2)实例
计算2022-11-06这天的独立访问用户数量
start和end代表起始和结束字节数, 下面操作计算用户id在第1个字节到第3个字节之间的独立访问用户数, 对应的用户id是11, 15, 19。
6.2 HyperLogLog
在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现。
但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。
解决基数问题有很多种方案:
(1)数据存储在MySQL表中,使用distinct count计算不重复个数
(2)使用Redis提供的hash、set、bitmaps等数据结构来处理
以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。
什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
6.2.1 命令
1、pfadd
(1)格式
pfadd <key>< element> [element ...] 添加指定元素到 HyperLogLog 中
(2)实例
将所有元素添加到指定HyperLogLog数据结构中。如果执行命令后HLL估计的近似基数发生变化,则返回1,否则返回0。
2、pfcount
(1)格式
pfcount<key> [key ...] 计算HLL的近似基数,可以计算多个HLL,比如用HLL存储每天的UV,计算一周的UV可以使用7天的UV合并计算即可
(2)实例
3、pfmerge
(1)格式
pfmerge<destkey><sourcekey> [sourcekey ...] 将一个或多个HLL合并后的结果存储在另一个HLL中,比如每月活跃用户可以使用每天的活跃用户来合并计算可得
七、Jedis测试
7.1 导包
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.3.0</version> </dependency>
7.2 各种测试
package com.caq.jedis; import org.junit.Test; import redis.clients.jedis.Jedis; import java.util.List; import java.util.Set; public class JedisDemo1 { public static void main(String[] args) { //创建jedis对象 Jedis jedis = new Jedis("172.20.10.9", 6379); //测试 String ping = jedis.ping(); System.out.println(ping); } // 操作zset @Test public void demo05() { Jedis jedis = new Jedis("172.20.10.9", 6379); jedis.zadd("china",100d,"shanghai"); Set<String> china = jedis.zrange("china", 0, -1); System.out.println(china); jedis.close(); } // 操作hash @Test public void demo04() { Jedis jedis = new Jedis("172.20.10.9", 6379); jedis.hset("demo04", "age", "20"); String hget = jedis.hget("demo04", "age"); System.out.println(hget); jedis.close(); } // 操作set @Test public void demo03() { Jedis jedis = new Jedis("172.20.10.9", 6379); jedis.sadd("demo03", "lucy"); jedis.sadd("demo03", "monkey"); Set<String> name = jedis.smembers("demo03"); System.out.println(name); jedis.close(); } //操作list @Test public void demo02() { Jedis jedis = new Jedis("172.20.10.9", 6379); jedis.lpush("demo02", "lucy", "jack", "tom"); List<String> key1 = jedis.lrange("demo02", 0, -1); System.out.println(key1); jedis.close(); } //操作key @Test public void demo1() { //创建redis对象 Jedis jedis = new Jedis("172.20.10.9", 6379); // Set<String> keys = jedis.keys("*"); // keys.forEach(x-> System.out.println(x)); // 添加 // jedis.set("name","jack"); // String name = jedis.get("name"); // 添加多个key jedis.mset("k1", "v1", "k2", "v2"); List<String> mget = jedis.mget("k1", "k2"); System.out.println(mget); jedis.close(); } }
7.3 连接Redis注意事项
禁用Linux的防火墙:Linux(CentOS7)里执行命令
systemctl stop/disable firewalld.service
redis.conf中注释掉bind 127.0.0.1 ,然后 protected-mode no
7.4 手机验证码实例
要求:
1、输入手机号,点击发送后随机生成6位数字码,2分钟有效
2、输入验证码,点击验证,返回成功或失败
3、每个手机号每天只能输入3次
package com.caq.jedis; import redis.clients.jedis.Jedis; import java.util.Random; public class PhoneCode { public static void main(String[] args) { //模拟验证码发送 // verifyCode("342352132"); //校验 getRedisCode("342352132", "833731"); } //1 生成6位数字验证码 public static String getCode() { Random random = new Random(); String code = ""; for (int i = 0; i < 6; i++) { int rand = random.nextInt(10); code += rand; } return code; } //2 每个手机每天只能发送三次,验证码放到redis中,设置超时时间 public static void verifyCode(String phone) { // 连接redis Jedis jedis = new Jedis("10.216.54.73", 6379); // 拼接key // 手机发送次数key String countKey = "VerityCode" + phone + ":count"; // 验证码key String codeKey = "VerityCode" + phone + ":code"; // 每个手机每天只能发送三次 String count = jedis.get(countKey); if (count == null) { // 没有发送次数,第一次发送 // 设置发送次数是1 jedis.setex(countKey, 24 * 60 * 60, "1"); } else if (Integer.parseInt(count) <= 2) { // 发送次数+1 jedis.incr(countKey); } else if (Integer.parseInt(count) > 2) { // 发送三次,不能再发送 System.out.println("今天发送次数已经超过三次"); jedis.close(); return; } //发送验证码放到redis里面 String vcode = getCode(); jedis.setex(codeKey, 120, vcode); jedis.close(); } // 3 验证码校验 public static void getRedisCode(String phone, String code) { //从redis获取验证码 Jedis jedis = new Jedis("10.216.54.73", 6379); //验证码key String codeKey = "VerityCode" + phone + ":code"; String redisCode = jedis.get(codeKey); //判断 if (redisCode.equals(code)) { System.out.println("success"); } else { System.out.println("fail"); } jedis.close(); } }
八、 Redis与Spring Boot整合
8.1 引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- spring2.X集成redis所需common-pool2--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.1</version> </dependency>
8.2 配置文件
spring: redis: #Redis服务器地址 host: 10.216.54.73 #Redis服务器连接端口 port: 6379 #Redis数据库索引(默认为0) database: 0 #连接超时时间(毫秒) timeout: 1800000 lettuce: pool: #连接池最大连接数(使用负值表示没有限制) max-active: 20 #最大阻塞等待时间(负数表示没限制) max-wait: -1 #最大阻塞等待时间(负数表示没限制) max-idle: 5 #连接池中的最小空闲连接 min-idle: 0
8.3 测试
package com.caq.boot.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; 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.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @EnableCaching @Configuration public class RedisConfig extends CachingConfigurerSupport { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); RedisSerializer<String> redisSerializer = new StringRedisSerializer(); 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); template.setConnectionFactory(factory); //key序列化方式 template.setKeySerializer(redisSerializer); //value序列化 template.setValueSerializer(jackson2JsonRedisSerializer); //value hashmap序列化 template.setHashValueSerializer(jackson2JsonRedisSerializer); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory factory) { RedisSerializer<String> redisSerializer = new StringRedisSerializer(); 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); // 配置序列化(解决乱码的问题),过期时间600秒 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(600)) .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)) .disableCachingNullValues(); RedisCacheManager cacheManager = RedisCacheManager.builder(factory) .cacheDefaults(config) .build(); return cacheManager; } }
RedisTestController中添加测试方法
package com.caq.boot.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/redisTest") public class RedisTestController { @Autowired private RedisTemplate redisTemplate; @GetMapping public String testRedis(){ //设置值到redis redisTemplate.opsForValue().set("name","lucy"); // 从redis获取值 String name = (String) redisTemplate.opsForValue().get("name"); return name; } }


































