Java缓存机制

1 Java缓存

1.1 jvm内置缓存

Java中实现缓存的方式有很多,比如用static hashMap基于内存缓存的jvm内置缓存,简单不实用,保对象的有效性和周期无法控制,容易造成内存急剧上升。常用的有Oscache(主要针对jsp页面),Ehcache(主要针对数据库访问层),Jcache,Jbosscache等等很多

缺点:容易内存溢出、没有持久化(服务重启后丢失)、线程安全、多个服务器(多个jvm)之间的数据不能共享。

1.2 java操作eache

利用spring Boot搭建应用(可参考这里的缓存配置)。

访问http://localhost:8080/getUser?name=springboot2.2后查询后返回

  1. {
  2. id: 35,
  3. name: "springboot2.2",
  4. age: 99
  5. }

修改数据库中age字段后再次访问http://localhost:8080/getUser?name=springboot2.2后结果不变?代表使用了缓存,成功配置了缓存。

访问http://localhost:8080/removeCache清除缓存后,再次访问http://localhost:8080/getUser?name=springboot2.2后结果就为修改后的值。

原理?

1、当客户端请求数据时,如果服务器端配置了缓存,第一步去缓存里面查找,如果有跳4,没有则往2

2、发送jdbc请求操作数据库查询数据

3、将查询到的数据返回给缓存,并保存在缓存中

4、将从(缓存|数据库)查询到的数据返回给客户端。

好处?效率高(因为不用建立jdbc连接等)、降低了数据库的压力(不用每次请求都要去数据库查,数据库也会累啊)。

缺点?从上述也看到了,有可能产生数据不一致的情况,清除缓存可解决。

和oscache区别? ehcache 主要是对数据库访问的缓存,相同的查询语句只需查询一次数据库,从而提高了查询的速度,使用spring的AOP可以很容易实现这一功能。 oscache 主要是对页面的缓存,可以整页或者指定网页某一部分缓存,同时指定他的过期时间,这样在此时间段里面访问的数据都是一样的。

2 Redis

关系型数据库:持久、主外键、编写SQL语句、存放在硬盘。

非关系型数据库:一般用于缓存、值存放在内存(所以效率是真的高)、key-vakye形式、容易数据丢失(不过很好解决)、有点小类似jvm内置缓存(不过这个更牛嗨,因为可以多个服务器间共享数据)。

redis?可以持久化mongdb?存储json格式

2.1 Redis概述

完全开源免费、最受BSD协议、高性能的key-value费关系型数据库支持持久化、支持key-value(String)listsetzerthash等数据结构的存储、支持备份

好处?减轻数据库访问的压力。效率高?(访问内存肯定比访问硬盘快,这是常识)

应用场景?(token生成、session共享、分布式锁、验证码、自增id(订单id))

2.2 安装redis

2.2.1 windows安装redis

1、下载redis

2、解压redis-latest-windws.zip文件,将start.bat文件拷贝纸redis目录

3、编辑redis.windows.conf文件,取消requirepass的注释(前面不能有空格),空格然后添加密码比如(123456)保存。以requirepass 123456为例

4、双击start.bat运行

5、通过这个客户端工具测试一下

2.2.2 linux安装redis

1、下载redis

2、mkdir -p /usr/local/redis/bin,mkdir -p /usr/local/redis/etc

3、copy redis-3.0.0.tar.gz到用户目录比如/root

4、解压tar -zxvf redis-3.0.0.tar.gz

5、cd redis-3.0.0/后,make一下

6、进入src目录make install后就安装成功了。

7、cd cd /root/redis-3.0.0/(redis安装目录)

8、cp redis.conf /usr/local/redis/etc

9、cd src

10、cp mkreleasehdr.sh redis-benchmark redis-check-aof redis-check-dump redis-cli redis-server redis-sentinel /usr/local/redis/bin
11、修改 redis.conf文件

daemonize yes --- 修改为yes  后台启动

requirepass 123456  ----注释取消掉设置账号密码

ps aux | grep '6379'  --- 查询端口

kill -15 9886 --- 杀死重置

kill -9 9886 --- 强制杀死

service iptables stop 停止防火墙

12、cd /usr/local/redis/bin

./redis-server /usr/local/redis/etc/redis.conf启动服务

13、./redis-cli -h 127.0.0.1 -p 6379 -a "123456"  --- redis 使用账号密码连接或者windows下的客户端工具进行连接测试

PING 结果表示成功

14、停止redis

redis-cli shutdown  或者 kill redis进程的pid

15、放开一下端口,好像外部即使注释掉bind 127.0.0.1也不能访问/sbin/iptables -I INPUT -p tcp --dport 6379 -j ACCEPT

2.3 redis基本数据类型

2.3.1 字符串

  1. 127.0.0.1:6379> set name raolei
  2. OK
  3. 127.0.0.1:6379> set itboy www.itboy.com
  4. OK
  5. 127.0.0.1:6379> get name
  6. "raolei"
  7. 127.0.0.1:6379> get itboy
  8. "www.itboy.com"

常用命令:

编号

命令

描述说明

1

SET key value

此命令设置指定键的值。

2

GET key

获取指定键的值。

3

GETRANGE key start end

获取存储在键上的字符串的子字符串。

4

GETSET key value

设置键的字符串值并返回其旧值。

5

GETBIT key offset

返回在键处存储的字符串值中偏移处的位值。

6

MGET key1 [key2..]

获取所有给定键的值

7

SETBIT key offset value

存储在键上的字符串值中设置或清除偏移处的位

8

SETEX key seconds value

使用键和到期时间来设置值

9

SETNX key value

设置键的值,仅当键不存在时

10

SETRANGE key offset value

在指定偏移处开始的键处覆盖字符串的一部分

11

STRLEN key

获取存储在键中的值的长度

12

MSET key value [key value …]

为多个键分别设置它们的值

13

MSETNX key value [key value …]

为多个键分别设置它们的值,仅当键不存在时

14

PSETEX key milliseconds value

设置键的值和到期时间(以毫秒为单位)

15

INCR key

将键的整数值增加1

16

INCRBY key increment

将键的整数值按给定的数值增加

17

INCRBYFLOAT key increment

将键的浮点值按给定的数值增加

18

DECR key

将键的整数值减1

19

DECRBY key decrement

按给定数值减少键的整数值

20

APPEND key value

将指定值附加到键

2.3.2 list

  1. 127.0.0.1:6379> lpush listkey redis
  2. (integer) 1
  3. 127.0.0.1:6379> lpush listkey mysql
  4. (integer) 2
  5. 127.0.0.1:6379> lpush listkey mongdb
  6. (integer) 3
  7. 127.0.0.1:6379> lpush listkey hbase
  8. (integer) 4
  9. 127.0.0.1:6379> lrange listkey 0 10
  10. 1) "hbase"
  11. 2) "mongdb"
  12. 3) "mysql"
  13. 4) "redis"

list是简单的字符串列表,按照插入顺序排序,可在头和尾插入,最多包含2^32-1个元素(40多亿)。

常用命令:

序号

命令及描述

1

BLPOP key1 [key2 ] timeout 
移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

2

BRPOP key1 [key2 ] timeout 
移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

3

BRPOPLPUSH source destination timeout 
从列表中弹出一个值,将弹出的元素插入到另外一个列表中并返回它; 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

4

LINDEX key index 
通过索引获取列表中的元素

5

LINSERT key BEFORE|AFTER pivot value 
在列表的元素前或者后插入元素

6

LLEN key 
获取列表长度

7

LPOP key 
移出并获取列表的第一个元素

8

LPUSH key value1 [value2] 
将一个或多个值插入到列表头部

9

LPUSHX key value 
将一个值插入到已存在的列表头部

10

LRANGE key start stop 
获取列表指定范围内的元素

11

LREM key count value 
移除列表元素

12

LSET key index value 
通过索引设置列表元素的值

13

LTRIM key start stop 
对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

14

RPOP key 
移除并获取列表最后一个元素

15

RPOPLPUSH source destination 
移除列表的最后一个元素,并将该元素添加到另一个列表并返回

16

RPUSH key value1 [value2] 
在列表中添加一个或多个值

17

RPUSHX key value 
为已存在的列表添加值

2.3.3 set

的Set是string类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

哈希表实现。添加,删除,查找的复杂度都是O(1)。每个集合可存储40多亿个成员

  1. 127.0.0.1:6379> sadd setkey redis
  2. (integer) 1
  3. 127.0.0.1:6379> sadd setkey redis
  4. (integer) 0
  5. 127.0.0.1:6379> sadd setkey redis
  6. (integer) 0
  7. 127.0.0.1:6379> sadd setkey redis
  8. (integer) 0
  9. 127.0.0.1:6379> sadd setkey redis
  10. (integer) 0
  11. 127.0.0.1:6379> sadd setkey mysql
  12. (integer) 1
  13. 127.0.0.1:6379> sadd setkey mongdb
  14. (integer) 1
  15. 127.0.0.1:6379> SMEMBERS setkey
  16. 1) "mysql"
  17. 2) "mongdb"
  18. 3) "redis"

常用命令:

序号

命令及描述

1

SADD key member1 [member2] 
向集合添加一个或多个成员

2

SCARD key 
获取集合的成员数

3

SDIFF key1 [key2] 
返回给定所有集合的差集

4

SDIFFSTORE destination key1 [key2] 
返回给定所有集合的差集并存储在 destination 中

5

SINTER key1 [key2] 
返回给定所有集合的交集

6

SINTERSTORE destination key1 [key2] 
返回给定所有集合的交集并存储在 destination 中

7

SISMEMBER key member 
判断 member 元素是否是集合 key 的成员

8

SMEMBERS key 
返回集合中的所有成员

9

SMOVE source destination member 
将 member 元素从 source 集合移动到 destination 集合

10

SPOP key 
移除并返回集合中的一个随机元素

11

SRANDMEMBER key [count] 
返回集合中一个或多个随机数

12

SREM key member1 [member2] 
移除集合中一个或多个成员

13

SUNION key1 [key2] 
返回所有给定集合的并集

14

SUNIONSTORE destination key1 [key2] 
所有给定集合的并集存储在 destination 集合中

15

SSCAN key cursor [MATCH pattern] [COUNT count] 
迭代集合中的元素

2.3.4 sorted set

与set一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 232 - 1 (4294967295, 每个集合可存储40多亿个成员)。

  1. 127.0.0.1:6379> zadd zsetkey 1 redis
  2. (integer) 1
  3. 127.0.0.1:6379> zadd zsetkey 2 mysql
  4. (integer) 1
  5. 127.0.0.1:6379> zadd zsetkey 2 mongdb
  6. (integer) 1
  7. 127.0.0.1:6379> smembers setkey
  8. 1) "mysql"
  9. 2) "mongdb"
  10. 3) "redis"

常用命令:

序号

命令及描述

1

ZADD key score1 member1 [score2 member2] 
向有序集合添加一个或多个成员,或者更新已存在成员的分数

2

ZCARD key 
获取有序集合的成员数

3

ZCOUNT key min max 
计算在有序集合中指定区间分数的成员数

4

ZINCRBY key increment member 
有序集合中对指定成员的分数加上增量 increment

5

ZINTERSTORE destination numkeys key [key ...] 
计算给定的一个或多个有序集的交集并将结果集存储在新的有序集合 key 中

6

ZLEXCOUNT key min max 
在有序集合中计算指定字典区间内成员数量

7

ZRANGE key start stop [WITHSCORES] 
通过索引区间返回有序集合成指定区间内的成员

8

ZRANGEBYLEX key min max [LIMIT offset count] 
通过字典区间返回有序集合的成员

9

ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT] 
通过分数返回有序集合指定区间内的成员

10

ZRANK key member 
返回有序集合中指定成员的索引

11

ZREM key member [member ...] 
移除有序集合中的一个或多个成员

12

ZREMRANGEBYLEX key min max 
移除有序集合中给定的字典区间的所有成员

13

ZREMRANGEBYRANK key start stop 
移除有序集合中给定的排名区间的所有成员

14

ZREMRANGEBYSCORE key min max 
移除有序集合中给定的分数区间的所有成员

15

ZREVRANGE key start stop [WITHSCORES] 
返回有序集中指定区间内的成员,通过索引,分数从高到底

16

ZREVRANGEBYSCORE key max min [WITHSCORES] 
返回有序集中指定分数区间内的成员,分数从高到低排序

17

ZREVRANK key member 
返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序

18

ZSCORE key member 
返回有序集中,成员的分数值

19

ZUNIONSTORE destination numkeys key [key ...] 
计算给定的一个或多个有序集的并集,并存储在新的 key 中

20

ZSCAN key cursor [MATCH pattern] [COUNT count] 
迭代有序集合中的元素(包括元素成员和元素分值)

2.3.5 hash

是一个string类型的field和value的映射表(和map差不多,只是兼职都是字符串),hash特别适合用于存储对象。Redis 中每个 hash 可以存储 232 - 1 键值对(40多亿)。

  1. 127.0.0.1:6379> hmset hmset name "redis tutorial"
  2. OK
  3. 127.0.0.1:6379> hmset hmset age 24
  4. OK
  5. 127.0.0.1:6379> hgetall hmset
  6. 1) "name"
  7. 2) "redis tutorial"
  8. 3) "age"
  9. 4) "24"

序号

命令及描述

1

HDEL key field2 [field2] 
删除一个或多个哈希表字段

2

HEXISTS key field 
查看哈希表 key 中,指定的字段是否存在。

3

HGET key field 
获取存储在哈希表中指定字段的值。

4

HGETALL key 
获取在哈希表中指定 key 的所有字段和值

5

HINCRBY key field increment 
为哈希表 key 中的指定字段的整数值加上增量 increment 。

6

HINCRBYFLOAT key field increment 
为哈希表 key 中的指定字段的浮点数值加上增量 increment 。

7

HKEYS key 
获取所有哈希表中的字段

8

HLEN key 
获取哈希表中字段的数量

9

HMGET key field1 [field2] 
获取所有给定字段的值

10

HMSET key field1 value1 [field2 value2 ] 
同时将多个 field-value (域-值)对设置到哈希表 key 中。

11

HSET key field value 
将哈希表 key 中的字段 field 的值设为 value 。

12

HSETNX key field value 
只有在字段 field 不存在时,设置哈希表字段的值。

13

HVALS key 
获取哈希表中所有值

14

HSCAN key cursor [MATCH pattern] [COUNT count] 
迭代哈希表中的键值对。

redis怎么存放对象?通过将对象序列化为json字符串,存放为字符串形式,之后读取在反序列化为对象,这样很快很快

2.4 Spring Boot集成redis

添加依赖:

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-data-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-web</artifactId>
  8. </dependency>
  9. <!--spring2.0集成redis所需common-pool2-->
  10. <dependency>
  11. <groupId>org.apache.commons</groupId>
  12. <artifactId>commons-pool2</artifactId>
  13. <version>2.4.2</version>
  14. </dependency>

配置文件:

  1. ########################################################
  2. ###Redis (RedisConfiguration)
  3. ########################################################
  4. spring:
  5. redis:
  6. database: 0
  7. host: 192.168.245.134
  8. port: 6379
  9. password: 123456
  10. jedis:
  11. pool:
  12. max-idle: 8
  13. min-idle: 0
  14. max-active: 8
  15. max-wait: -1ms
  16. timeout: 5000ms

service:

  1. @Service
  2. public class RedisService {
  3. @Autowired
  4. private StringRedisTemplate stringRedisTemplate;
  5.  
  6. //字符串
  7. public void setStringKey(String key,String value,Long time){
  8. setObject(key,value,time);
  9. }
  10. public void setStringKey(String key,String value){
  11. setObject(key,value,null);
  12. }
  13.  
  14. //set
  15. public void setSetKey(String key,Set value){
  16. setObject(key,value,null);
  17. }
  18. //list
  19. public void setListKey(String key,List value){
  20. setObject(key,value,null);
  21. }
  22.  
  23. public String getStringKey(String key){
  24. return (String) getObject(key,new String());
  25. }
  26. public Set getSetKey(String key){
  27. return (Set) getObject(key,new HashSet<String>());
  28. }
  29. public List getListKey(String key){
  30. return (List) getObject(key,new ArrayList<String>());
  31. }
  32.  
  33.  
  34.  
  35.  
  36. public void setObject(String key,Object value,Long time){
  37. if(StringUtils.isEmpty(key)||value==null){
  38. return;
  39. }
  40. //字符串类型
  41. if(value instanceof String){
  42. String value1= (String) value;
  43. if(time!=null){
  44. stringRedisTemplate.opsForValue().set(key,value1,time,TimeUnit.SECONDS);
  45. } else{
  46. stringRedisTemplate.opsForValue().set(key,value1);
  47. }
  48. return;
  49. }
  50. //list类型
  51. else if(value instanceof List){
  52. List<String> list= (List<String>) value;
  53. for (String s:list) {
  54. stringRedisTemplate.opsForList().leftPush(key,s);
  55. }
  56. return;
  57. }
  58. //set
  59. else if(value instanceof Set){
  60. Set<String> strings= (Set<String>) value;
  61. for (String s : strings) {
  62. stringRedisTemplate.opsForSet().add(key,s);
  63. }
  64. return;
  65. }
  66. /**
  67. * .....
  68. */
  69. }
  70.  
  71. public Object getObject(String key,Object object){
  72. if(StringUtils.isEmpty(key)||object==null){
  73. return null;
  74. }
  75. else if (object instanceof String){
  76. return stringRedisTemplate.opsForValue().get(key);
  77. }
  78. else if(object instanceof List){
  79. return stringRedisTemplate.opsForList().range(key,0,stringRedisTemplate.opsForList().size(key));
  80. }
  81. else if(object instanceof Set){
  82. return stringRedisTemplate.opsForSet().members(key);
  83. }
  84. return null;
  85. }
  86. }

controller:

  1. @RestController
  2. public class IndexController {
  3. @Autowired
  4. private RedisService redisService;
  5.  
  6. @RequestMapping("/setString")
  7. public String setString(@PathParam("key") String key,
  8. @PathParam("value") String value){
  9. redisService.setStringKey(key,value);
  10. return redisService.getStringKey(key);
  11. }
  12. @RequestMapping("/setSet")
  13. public Set<String> setSet(@PathParam("key") String key,
  14. @PathParam("value") String value){
  15. HashSet<String> strings = new HashSet<>();
  16. strings.add(value);
  17. strings.add(value+"1");
  18. strings.add(value+"2");
  19. strings.add(value+"3");
  20. redisService.setSetKey(key,strings);
  21. return redisService.getSetKey(key);
  22. }
  23. @RequestMapping("/setList")
  24. public List<String> setList(@PathParam("key") String key,
  25. @PathParam("value") String value){
  26. ArrayList<String> strings = new ArrayList<>();
  27. strings.add(value);
  28. strings.add(value+"1");
  29. strings.add(value+"2");
  30. strings.add(value+"3");
  31. redisService.setListKey(key,strings);
  32. return redisService.getListKey(key);
  33. }
  34. }

访问http://localhost:8080/setSet?key=itboySet&value=123456http://localhost:8080/setString?key=itboySet&value=123456,

http://localhost:8080/setList?key=itboySet&value=123456进行测试一下是否成功!!!!!!!!

2.5 主从复制和哨兵机制理解

为什么?数据备份、读写分离、集群、高可用(宕机容错机制)。

一般情况下,Redis高可用都是一主多从,而不像其他比如Nginx多主多从。

所谓主从复制,主要是为了减轻单台服务器的压力(比如图中的master),通过多台服务器的冗余来保证高可用(单台宕机容错),实现读写分离、数据备份、集群等。

如图,其中master可读可写,但是当有客户端连接达到集群时,如果是读操作就从slave从节点中随机选择一台服务器进行响应,如果是写操作,那么操作主服务器。这就是读写分离了不是吗。。。

问题?主服务器写之后,怎么同步到从服务器?(主从复制搞定,往下看)

问题?主服务器宕机了,怎么写?通过哨兵机制,哨兵其实就是一个监听器,一直监听这主服务器,如果主服务器挂了,他就会使用投票(随机)从主服务器中选择一台服务器作为主服务器,此乃高可用。

问题?那如果整个集群挂了呢?有一个东西叫做keepalived监听器(其实就是一个shell写的重启服务的命令脚本),如果监听到某一台服务器挂了,他就会自动重启的(一般30秒内,听说可能不准确)。如果一直启动失败?那就没办法了,他只能发送一封邮件给运维人员了。

2.5.1 主从复制实现

原理:通过快照文件(类似mysql的二进制可执行文件),当master有更新时,从服务器slave会实时请求得到该快照文件,进行执行,如果网络问题?别担心过,会重试的。

过程:

1:当一个从数据库启动时,会向主数据库发送sync命令,

2:主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来

3:当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。

4:从数据库收到后,会载入快照文件并执行收到的缓存的命令。

对于redis服务器来说,和mysql有很大不同,只要设置好主从服务器之后,主服务器master可读可写,从服务器slave仅可读,不像mysql那样需要分配用户和mycat插件来控制读写分离。

配置过程:

1、准备服务器,如上图中三台服务器(192.168.245.134,192.168.245.135,192.168.245.136),选择一台为主服务器master(这台服务器什么也不用做)

2、所以redis主从复制只需要配置从服务器slave就OK,修改redis.conf配置文件,放开以下两行的注释,添加如下内容。两台从服务器都要修改。

  1. slaveof 192.168.245.134 6379
  2. #主服务器的ip和端口号
  3.  
  4. # If the master is password protected (using the "requirepass" configuration
  5. # directive below) it is possible to tell the slave to authenticate before
  6. # starting the replication synchronization process, otherwise the master will
  7. # refuse the slave request.
  8. #
  9. masterauth 123456
  10. #主服务器的认证密码

3、测试

  1. #进入master主服务器
  2. 192.168.245.134:6379> info
  3. #回车后看到如下内容即代表成功
  4. # Replication
  5. role:master
  6. connected_slaves:2 #两台从服务器
  7. slave0:ip=192.168.245.135,port=6379,state=online,offset=127,lag=1 #一些描述信息
  8. slave1:ip=192.168.245.136,port=6379,state=online,offset=127,lag=1 #一些描述信息
  9.  
  10.  
  11. #进入任何一台服务器比如135,同样info以下,看到如下内容即代表成功。
  12. # Replication
  13. role:slave #角色从服务器
  14. master_host:192.168.245.134 #主服务器ip
  15. master_port:6379 #主服务端口
  16. master_link_status:up #状态
  17. master_last_io_seconds_ago:10
  18. master_sync_in_progress:0
  19.  
  20.  
  21. #主服务器
  22. 192.168.245.134:6379> set master "192.168.245.134"
  23. OK
  24. 192.168.245.134:6379> get master
  25. "192.168.245.134"
  26. 192.168.245.134:6379>
  27. ##可读可写是吧???????
  28. #########刚刚设置的这条数据从服务器有吗??????############
  29. 192.168.245.135:6379> get master
  30. "192.168.245.134"
  31. 192.168.245.135:6379> set slave "192.168.245.135"
  32. (error) READONLY You can't write against a read only slave.
  33. 192.168.245.135:6379>
  34. ############哟呵有数据的,主从复制成功,主从间数据同步问题解决,而且不能写,读写分离也搞定了########
  35. [root@localhost bin]# ./redis-cli -h 192.168.245.136 -p 6379 -a "123456"
  36. 192.168.245.136:6379> ping
  37. PONG
  38. 192.168.245.136:6379> get master
  39. "192.168.245.134"
  40. 192.168.245.136:6379> set slave "192.168.245.136"
  41. (error) READONLY You can't write against a read only slave.
  42. 192.168.245.136:6379>
  43.  
  44. ################另一台从服务器也一样######################
  45.  

2.5.2 哨兵机制实现

原理:哨兵用于管理多个redis服务器,执行以下三个任务:

1、监控(monitoring):哨兵(sentinel)会不断检查你的master和slave是否运作正常

2、提醒(notification):当被监控的某个redis出现问题时,哨兵(sentinel)可以通过API向管理员或者其他应用程序发送通知

3、自动故障迁移(automatic failover):当一个master1不能正常工作时,哨兵会开始一次自动故障迁移操作,他将会失效master1的其中一个slave升级为新的master2,并让失效master1的其他slave的master1改为新的master2。当客户端试图连接失效的master1时,集群也会向客户端返回新的master2地址,使得集群可以使用新的master2代替失效的master1

哨兵是一个分布式系统,可以在一个架构中运行多个哨兵进程,这些进程使用流言协议(gossipprotocols)来接收关于master是否下线的消息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移以及选择哪个slave作为新的master。

每个哨兵会向其他哨兵(sentinel)、master、slave定时发送消息,来确认对方是否还活着,如果对方在指定的时间(可配置)内未响应,则暂时认为对方已挂(主观认为宕机,Subjective Down,sdown)

若哨兵群中的多数sentinel都报告某一个master没响应,系统认为该master彻底死亡(客观真正的宕机,Objective Down,oDwon),通过一定vote算法,从生下的slave节点中选择提升一台为master,然后自动修改相关配置

虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,你可以在启动一个普通 Redis 服务器时通过给定 --sentinel 选项来启动哨兵(sentinel).

实现:

这里以上面三台服务器为基础,选择192.168.245.136这台服务器为哨兵(可以选择多台,此处仅一台为例)。

注意:如果主从复制时,主服务器没有配置masterauth 123456请加上,这个坑了我很久很久,导致哨兵时一直连不上,最后查看日志才搞定,记得先将这个加到主服务器的redis.conf中,然后重启一下

1、修改redis安装目录下的sentinel.conf

  1. sentinel monitor mymaster 192.168.245.134 6379 1
  2. # 主节点名称 主机ip 端口号 选举次数(就是说当有几台sentinel认为master挂了才是真的挂了,因为这里只有一个哨兵,所以为1)
  3. sentinel down-after-milliseconds mymaster 30
  4. #就是说多少ms后没有给老子响应,老子就觉得你挂了。,默认30s,这里设置为30ms,本地测试追求实时
  5. sentinel config-epoch mymaster 1
  6. #这个数字表示在发生主从复制的时候,比如master1向master2切换时,可以同时有多少个slave能对master2执行同步(也就是复制其实),越多越好?>如果太多了那么大家都去复制了,谁来响应客户端的请求?太少?太少的话,每个都要来一遍,怕是要到天黑哦,根据实际情况吧,这里只有三台所以
  7. 为设为1.
  8.  
  9. sentinel auth-pass mymaster 123456
  10. #主服务器密码

2、启动哨兵:nohup ./redis-server ../etc/sentinel.conf --sentinel  2>1 1>nohup.log &

3、如何停止ps -aux | grep 端口号,kill -9 pid即可

4、测试?

  1. #我们首先查看master(134)的info master
  2. role:master
  3. connected_slaves:2
  4. slave0:ip=192.168.245.135,port=6379,state=online,offset=5349,lag=1
  5. slave1:ip=192.168.245.136,port=6379,state=online,offset=5349,lag=0
  6.  
  7.  
  8. #135的 slave
  9. role:slave
  10. master_host:192.168.245.134
  11. master_port:6379
  12. master_link_status:up
  13.  
  14.  
  15. #136 的 slave
  16. role:slave
  17. master_host:192.168.245.134
  18. master_port:6379
  19. master_link_status:up
  20.  
  21.  
  22. #我现在讲master停掉? master
  23. #134
  24. 192.168.245.134:6379> shutdown
  25. not connected>
  26.  
  27.  
  28. #135 的info replication
  29. 192.168.245.135:6379> info replication
  30. # Replication
  31. role:slave
  32. master_host:192.168.245.136
  33. master_port:6379
  34. master_link_status:up
  35. #看到主服务器变为136,
  36.  
  37. #136?
  38. 192.168.245.136:6379> info replication
  39. # Replication
  40. role:master
  41. connected_slaves:2
  42. slave0:ip=192.168.245.135,port=6379,state=online,offset=739,lag=0
  43. slave1:ip=192.168.245.134,port=6379,state=online,offset=739,lag=0
  44.  
  45. #基本上成功了,最后试试读写分离以及在测试一些其他的,这里不再展示
  46.  
  47.  
  48.  

2.6 数据持久化

数据持久化:就是将内存中的数据保存到硬盘,redis支持AOF和RDB两种存储方式

2.6.1 RDB存储

RDB是指在一个时间点,如果达到所配置的数据修改量,就写入一个临时文件,持久化结束后,用这个临时文件替换上次持久化的文件,达到数据恢复。(二进制文件方式

有两种保存方式:1、(阻塞)主进程直接拍快照(snapshot),然后阻塞客户端请求写入IO磁盘。2、(非阻塞)当要写入磁盘时,新建(fork)一个子进程,子进程将当前数据库快照写入磁盘,而主进程继续处理客户端请求。

每次快照持久化都是将内存数据完整写入到磁盘一次,并不 是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。

优点:使用单独子进程进行持久化,主进程不会进行任何IO操作,保证了redis的高性能

缺点:RDB需要间隔一段时间进行持久化,而且必须达到相应修改数量,所以如果持久化之间发生故障,会造成数据丢失,常用于数据要求不严谨的时候。

配置:

  1. #dbfilename:持久化数据存储在本地的文件
  2. dbfilename dump.rdb
  3. #dir:持久化数据存储在本地的路径,如果是在/redis/redis-3.0.6/src下启动的redis-cli,则数据会存储在当前src目录下
  4. dir ./
  5.  
  6. ##snapshot触发的时机,save
  7.  
  8. ##如下距离上一次持久化已经900s了,如果有大于等于1个变更,才会snapshot
  9. save 900 1
  10. ##对于此值的设置,需要谨慎,评估系统的变更操作密集程度
  11. ##可以通过“save “””来关闭snapshot功能
  12.  
  13. save 300 10 #表示间隔达到300s时如果更改了10次以上,就snapshot,如果你在10s时已经10次了,立马持久化
  14. save 60 10000 #同理间隔达到60s时如果更改了10000次以上,就snapshot,如果你在10s时已经10000次了,立马持久化
  15.  
  16. ##当snapshot时出现错误无法继续时,是否阻塞客户端“变更操作”,“错误”可能因为磁盘已满/磁盘故障/OS级别异常等
  17. stop-writes-on-bgsave-error yes
  18. ##是否启用rdb文件压缩,默认为“yes”,压缩往往意味着“额外的cpu消耗”,同时也意味这较小的文件尺寸以及较短的网络传输时间
  19. rdbcompression yes

这里我将save 300 10改为save 30 10进行测试一下(首先先停掉哨兵机制,不然宕机后他就重新选一台主的了):

改好后重启一下服务:

  1. 进行5次更改操作
  2. 192.168.245.136:6379> get name
  3. "1"
  4. 192.168.245.136:6379> set name 1
  5. OK
  6. 192.168.245.136:6379> set name 2
  7. OK
  8. 192.168.245.136:6379> set name 3
  9. OK
  10. 192.168.245.136:6379> set name 4
  11. OK
  12. 192.168.245.136:6379> set name 5
  13. OK
  14. 192.168.245.136:6379> get name
  15. "5"
  16.  
  17. #立马kill掉redis进程,一定要kill如果主动关闭服务,他是会进行snapshot的
  18. [root@localhost bin]# ps -aux | grep 6379
  19. root 1572 0.1 0.9 140840 9640 ? Ssl 02:02 0:00 ./redis-server *:6379
  20. root 1577 0.0 0.5 20160 5184 pts/0 S+ 02:02 0:00 ./redis-cli -h 192.168.245.136 -p 6379 -a 123456
  21. root 1580 0.0 0.0 112676 984 pts/1 R+ 02:03 0:00 grep --color=auto 6379
  22. [root@localhost bin]# kill -9 1572
  23.  
  24. #重启服务
  25. [root@localhost bin]# ./redis-server ../etc/redis.conf
  26. [root@localhost bin]# ./redis-cli -h 192.168.245.136 -p 6379 -a "123456"
  27. 192.168.245.136:6379> get name
  28. "1"
  29. 192.168.245.136:6379>
  30.  
  31. #可以看到我最后的是“5”,而这里是“1”,数据丢失了##########################
  32. #更改10次呢?,是成功进行持久化了的,这里不展示了,篇幅过大。
  33. #而且如果你将dump.rdb文件删除后,达到snapshot条件时,会自动创建一个新的文件,持久化其实就是将该文件备份,下次将那些持久化后的文件再放过来不就达到数据恢复了吗????????????

2.6.2 AOF存储

以日志文件方式存储,其实就是将你“操作+数据”指令格式化后追加到操作日志文件的尾部,必须append(已经写入到文件或者即将写入),才会进行数据的实际变更。“日志文件”保存了历史所有的操作过程;当 server 需要数据恢复时,可以直接 replay 此日志文件,即可还原所有的操作过程。内容是字符串,容易阅读和解析。

缺点:AOF 文件比 RDB 文件大,且恢复速度慢。

只会记录“变更操作”(例如:set/del 等),如果 server 中持续的大量变更操作,将会导致 AOF 文件非常的庞大,意味着 server 失效后,数据恢复的过程将会很长;事实上,一条数据经过多次变更,将会产生多条 AOF 记录,其实只要保存当前的状态,历史的操作记录是可以抛弃的;因为 AOF 持久化模式还伴生了“AOF rewrite”。

因为最多丢失最后一次写入文件的数据,所以很好修复,直接手工更改文件或者重新来一次即可。

修改redis.conf文件:

  1. ##此选项为aof功能的开关,默认为“no”,可以通过“yes”来开启aof功能
  2. ##只有在“yes”下,aof重写/文件同步等特性才会生效
  3. appendonly yes
  4.  
  5. ##指定aof文件名称
  6. appendfilename appendonly.aof
  7.  
  8. ##指定aof操作中文件同步策略,有三个合法值:always everysec no,默认为everysec每秒
  9. appendfsync everysec
  10. ##在aof-rewrite期间,appendfsync是否暂缓文件同步,"no"表示“不暂缓”,“yes”表示“暂缓”,默认为“no”
  11. no-appendfsync-on-rewrite no
  12.  
  13. ##aof文件rewrite触发的最小文件尺寸(mb,gb),只有大于此aof文件大于此尺寸是才会触发rewrite,默认“64mb”,建议“512mb”
  14. auto-aof-rewrite-min-size 64mb
  15.  
  16. ##相对于“上一次”rewrite,本次rewrite触发时aof文件应该增长的百分比。
  17. ##每一次rewrite之后,redis都会记录下此时“新aof”文件的大小(例如A),那么当aof文件增长到A*(1 + p)之后
  18. ##触发下一次rewrite,每一次aof记录的添加,都会检测当前aof文件的尺寸。
  19. auto-aof-rewrite-percentage 100
  20.  

重启服务后,根据你启动redis所在目录下出现appendonly.aof文件。

执行以下操作:

  1. 192.168.245.136:6379> set name 124
  2. OK
  3. 192.168.245.136:6379> set name 145
  4. OK
  5. 192.168.245.136:6379> set age 56
  6. OK
  7. 192.168.245.136:6379> del age
  8. (integer) 1
  9. 192.168.245.136:6379> get name
  10. "145"
  11.  

查看aof文件内容:

  1. [root@localhost bin]# cat appendonly.aof
  2. *2
  3. $6
  4. SELECT
  5. $1
  6. 0
  7. *3
  8. $3
  9. set
  10. $4
  11. name
  12. $3
  13. 124
  14. *3
  15. $3
  16. set
  17. $4
  18. 。。。

查询的是不会出现在里面,而且这个文件是动态的。。看起来挺好的,就是频繁的更改造成文件过大,到时候恢复起来有点太慢了,有人说那么RDB中将时间改快点要求改小点?大哥,人家是全量复制,那岂不是时间都花去复制了,还处理什么请求?这个虽然慢,可是是追加方式啊,将就了。

2.6.3 redis宕机后,值会失效吗?

不会,redis默认开启RDB存储,如果是直接关闭服务,那么会自动备份,如果是kill或者断电如果没达到RDB配置的要求则不会持久化,而且这个虽然是非阻塞的,但是毕竟全量复制啊,保证了redis的性能,但是CPU可受不了啊。因此实际情况中,最好采用AOP方式,实时而且快,但是就是容易造成文件过大,恢复困难。

2.7 redis事务

Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

multi 开启事务 exec提交事务。

序号

命令及描述

1

DISCARD 
取消事务,放弃执行事务块内的所有命令。

2

EXEC 
执行所有事务块内的命令。

3

MULTI 
标记一个事务块的开始。

4

UNWATCH 
取消 WATCH 命令对所有 key 的监视。

5

WATCH key [key ...] 
监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

2.8 发布订阅

Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。Redis 客户端可以订阅任意数量的频道

下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:

当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:

例子:

1、创建订阅频道名为 redisChatSimple

  1. 192.168.245.136:6379> subscribe redsiChatSample
  2. Reading messages... (press Ctrl-C to quit)
  3. 1) "subscribe"
  4. 2) "redsiChatSample"

2、重新开个客户端,同一频道发布消息

  1. 192.168.245.136:6379> publish redsiChatSample "gogog"
  2. (integer) 1
  3. 192.168.245.136:6379> publish redsiChatSample "gogog22"
  4. (integer) 1
  5. 192.168.245.136:6379>

3、客户端显示如下信息:

  1. 192.168.245.136:6379> subscribe redsiChatSample
  2. Reading messages... (press Ctrl-C to quit)
  3. 1) "subscribe"
  4. 2) "redsiChatSample"
  5. 3) (integer) 1
  6. 1) "message"
  7. 2) "redsiChatSample"
  8. 3) "gogog"
  9. 1) "message"
  10. 2) "redsiChatSample"
  11. 3) "gogog22"

厉害了!!!

序号

命令及描述

1

PSUBSCRIBE pattern [pattern ...] 
订阅一个或多个符合给定模式的频道。

2

PUBSUB subcommand [argument [argument ...]] 
查看订阅与发布系统状态。

3

PUBLISH channel message 
将信息发送到指定的频道。

4

PUNSUBSCRIBE [pattern [pattern ...]] 
退订所有给定模式的频道。

5

SUBSCRIBE channel [channel ...] 
订阅给定的一个或多个频道的信息。

6

UNSUBSCRIBE [channel [channel ...]] 
指退订给定的频道

原文地址:https://www.cnblogs.com/zhuyeshen/p/11429791.html