网站开发算是研发支出吗,有哪些网站可以做海报设计知乎,免费代理服务器国外,北京建设工程交易协会网站一、redis持久化
1、RDB快照#xff08;snapshot#xff09;
redis配置RDB存储模式#xff0c;修改redis.conf文件如下配置#xff1a;
# 在300s内有100个或者以上的key被修改就会把redis中的数据持久化到dump.rdb文件中
# save 300 100# 配置数据存放目录#xff08;现…一、redis持久化
1、RDB快照snapshot
redis配置RDB存储模式修改redis.conf文件如下配置
# 在300s内有100个或者以上的key被修改就会把redis中的数据持久化到dump.rdb文件中
# save 300 100# 配置数据存放目录现在指定的是redis目录下的data文件夹
dir ./data# 配置rdb文件存储文件名
dbfilename dump.rdb 也可以在连接redis时手动输入save或者bgsave命令都会将所有redis内存快照生成到一个新的dump.rdb文件并覆盖原来的rdb文件
bgsave写时复制COW机制
主线程在执行redis命令在内存中生成副本的同时由主线程fork出来的子进程开始读取redis内存中的数据并且写到rdb文件中fork出来的bgsave子进程和主线程互不影响redis后台生成rdb文件是采用bgsave方式
save和bgsave
save是和主线程同步执行bgsave是异步执行save会阻塞其它命令bgsave在主线程fork时会有短暂的阻塞save不会额外消耗内存bgsave不会阻塞客户端命令 命令 save bgsave IO类型 同步 异步 是否阻塞redis其它命令 是 否(在生成子进程执行调用fork函数时会有短暂阻塞) 复杂度 O(n) O(n) 优点 不会消耗额外内存 不阻塞客户端命令 缺点 阻塞客户端命令 需要fork子进程消耗内存
2、AOFappend-only file
rdb的缺点也非常明显不是实时持久化数据到rdb文件如果系统宕机redis会丢失最近时间的数据。AOF持久化是将修改的每一条指令报存到appendonly.aof文件中和mysql的redolog一样先写os cache操作系统缓存每隔一段时间sync到磁盘
开启aof
修改redis.conf文件如下配置
# 开启aof
appendonly yes
# aof文件名
appendfilename appendonly.aof# aof三种策略
# 每次有新命令追加到 AOF 文件时就执行一次 fsync 非常慢也非常安全。
# appendfsync always
# 每秒 fsync 一次足够快并且在故障时只会丢失 1 秒钟的数据。
# appendfsync everysec
# 从不 fsync 将数据交给操作系统来处理。更快也更不安全的选择。
# appendfsync no
执行set gaorufeng 123 ex 100 命令生成的aof文件如下星号后面的数字代表命令有多少个参数$号后面的数字代表这个参数有几个字符如果执行带过期时间的set命令aof文件里记录的是并不是执行的原始命令而是记录key过期的时间戳
*5
$3
SET
$9
gaorufeng
$3
123
$4
PXAT
$13
1693895615978
AOF重写
比如一条自增命令在生成aof文件就会生成多条指令incr count命令的aof文件如下
*2
$4
incr
$5
count
*2
$4
incr
$5
count
*2
$4
incr
$5
count
*2
$4
incr
$5
count
重写的作用旨在减少指令的存储可以配置aof让redis重写修改redis.conf文件如下配置
# 当前文件是之前文件大小的两倍设置100自动触发重写
auto-aof-rewrite-percentage 100
# aof文件至少要达到64M才会自动重写文件太小恢复速度本来就很快重写的意义不大
auto-aof-rewrite-min-size 64mb redis客户端执行bgrewriteaof手动重写aof此时命令如下
*3
$3
SET
$5
count
$1
4
aof方式存储数据完整rdb存储数据文件小故启动redis时当选rdb恢复内存数据二进制恢复数据快 命令 RDB AOF 启动优先级 低 高 体积 小 大 恢复速度 快 慢 数据安全性 容易丢数据 根据策略决定
3、混合持久化
# 开启混合持久化
aof-use-rdb-preamble yes
在开启aof的前提下aof重写前内存做rdb快照处理并且将rdb快照内容和增量的aof修改内存数据的命令存在一起
Redis数据备份策略
写crontab定时调度脚本每小时都copy一份rdb或aof的备份到一个目录中去仅仅保留最近48小时的备份每天都保留一份当日的数据备份到一个目录中去可以保留最近1个月的备份每次copy备份的时候都把太旧的备份给删了每天晚上将当前机器上的备份复制一份到其他机器上以防机器损坏
二、redis主从架构 redis主从架构搭建配置从节点步骤
1、进入到redis目录下创建数据存储目录
mkdir data
cd data
mkdir 6379
mkdir 6380
mkdir 63812、copy不同配置文件
cp -r redis.conf redis.conf.6379
cp -r redis.conf redis.conf.6380
cp -r redis.conf redis.conf.63813、6379为主节点其余为从节点修改配置文件
vim redis.conf.6379
在6379配置文件中修改如下
port 6379
daemonize yes
protected-mode no
pidfile /var/run/redis_6379.pid
logfile 6379.log
dir /usr/local/redis/redis-6.2.7/data/6379
# 需要注释掉bind
# bind 127.0.0.1bind绑定的是自己机器网卡的ip如果有多块网卡可以配多个ip代表允许客户端通过机器的哪些网卡ip去访问内网一般可以不配置bind注释掉即可4、配置从节点6380
vim redis.conf.6380
在6380配置文件中修改如下
port 6380
daemonize yes
protected-mode no
pidfile /var/run/redis_6380.pid
logfile 6380.log
dir /usr/local/redis/redis-6.2.7/data/6380
# 从本机6379的redis实例复制数据Redis 5.0之前使用slaveof
replicaof 192.168.146.128 6379
# 配置从节点只读
replica-read-only yes5、配置从节点6381
vim redis.conf.6381
在6381配置文件中修改如下
port 6381
daemonize yes
protected-mode no
pidfile /var/run/redis_6381.pid
logfile 6381.log
dir /usr/local/redis/redis-6.2.7/data/6381
# 从本机6379的redis实例复制数据Redis 5.0之前使用slaveof
replicaof 192.168.146.128 6379
# 配置从节点只读
replica-read-only yes6、先启动主节点6379后启动从节点6380、6381
src/redis-server redis.conf.6379
src/redis-server redis.conf.6380
src/redis-server redis.conf.63817、连接从节点查看是否能获取主节点数据
src/redis-cli -p 6379
src/redis-cli -p 6381
Redis主从工作原理
如果你为master配置了一个slave不管这个slave是否是第一次连接上Master它都会发送一个PSYNC命令给master请求复制数据。
master收到PSYNC命令后会在后台进行数据持久化通过bgsave生成最新的rdb快照文件持久化期间master会继续接收客户端的请求它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以后master会把这份rdb文件数据集发送给slaveslave会把接收到的数据进行持久化生成rdb然后再加载到内存中。然后master再将之前缓存在内存中的命令发送给slave。
当master与slave之间的连接由于某些原因而断开时slave能够自动重连Master如果master收到了多个slave并发连接请求它只会进行一次持久化而不是一个连接一次然后再把这一份持久化的数据发送给多个并发连接的slave。
主从复制(全量复制)流程图 数据部分复制
当master和slave断开重连后一般都会对整份数据进行复制。但从redis2.8版本开始redis改用可以支持部分数据复制的命令PSYNC去master同步数据slave与master能够在网络连接断开重连后只进行部分数据复制(断点续传)。
master会在其内存中创建一个复制数据用的缓存队列缓存最近一段时间的数据master和它所有的slave都维护了复制的数据下标offset和master的进程id因此当网络连接断开后slave会请求master继续进行未完成的复制从所记录的数据下标开始。如果master进程id变化了或者从节点数据下标offset太旧已经不在master的缓存队列里了那么将会进行一次全量数据的复制。 主从复制(部分复制断点续传)流程图 如果有很多从节点为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过大)可以做如下架构让部分从节点与从节点(与主节点同步)同步数据 Jedis连接代码示例
dependencygroupIdredis.clients/groupIdartifactIdjedis/artifactIdversion2.9.0/version
/dependency
public class JedisSingleTest {public static void main(String[] args) {JedisPoolConfig jedisPoolConfig new JedisPoolConfig();jedisPoolConfig.setMaxTotal(20);jedisPoolConfig.setMaxIdle(10);jedisPoolConfig.setMinIdle(5);// timeout这里既是连接超时又是读写超时从Jedis 2.8开始有区分connectionTimeout和soTimeout的构造函数JedisPool jedisPool new JedisPool(jedisPoolConfig, 192.168.146.128, 6379, 3000, null);Jedis jedis null;try {//从redis连接池里拿出一个连接执行命令jedis jedisPool.getResource();System.out.println(jedis.set(single, gao));System.out.println(jedis.get(single));//管道示例//管道的命令执行方式cat redis.txt | redis-cli -h 127.0.0.1 -a password - p 6379 --pipePipeline pl jedis.pipelined();for (int i 0; i 10; i) {pl.incr(pipelineKey);pl.set(gao i, gao);}ListObject results pl.syncAndReturnAll();System.out.println(results);//lua脚本模拟一个商品减库存的原子操作//lua脚本命令执行方式redis-cli --eval /tmp/test.lua , 10jedis.set(product_count_10016, 15); //初始化商品10016的库存String script local count redis.call(get, KEYS[1]) local a tonumber(count) local b tonumber(ARGV[1]) if a b then redis.call(set, KEYS[1], a-b) return 1 end return 0 ;Object obj jedis.eval(script, Arrays.asList(product_count_10016), Arrays.asList(10));System.out.println(obj);} catch (Exception e) {e.printStackTrace();} finally {//注意这里不是关闭连接在JedisPool模式下Jedis会被归还给资源池。if (jedis ! null)jedis.close();}}
}
管道Pipeline
客户端可以一次性发送多个请求而不用等待服务器的响应待所有命令都发送完后再一次性读取服务的响应这样可以极大的降低多条命令执行的网络传输开销管道执行多条命令的网络开销实际上只相当于一次命令执行的网络开销。需要注意到是用pipeline方式打包命令发送redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多缓存消耗内存也越多。所以并不是打包的命令越多越好。
pipeline中发送的每个command都会被server立即执行如果执行失败将会在此后的响应中得到信息也就是pipeline并不是表达“所有command都一起成功”的语义管道中前面命令失败后面命令不会有影响继续执行。
三、redis哨兵高可用架构 sentinel哨兵是特殊的redis服务不提供读写服务主要用来监控redis实例节点。
哨兵架构下client端第一次从哨兵找出redis的主节点后续就直接访问redis的主节点不会每次都通过sentinel代理访问redis的主节点当redis的主节点发生变化哨兵会第一时间感知到并且将新的redis主节点通知给client端(这里面redis的client端一般都实现了订阅功能订阅sentinel发布的节点变动消息) 基于主从架构的redis哨兵架构搭建步骤
1、复制一份sentinel.conf文件
cp sentinel.conf sentinel-26379.conf2、将相关配置修改为如下值
port 26379
daemonize yes
pidfile /var/run/redis-sentinel-26379.pid
logfile 26379.log
dir /usr/local/redis-5.0.3/data
# sentinel monitor master-redis-name master-redis-ip master-redis-port quorum
# quorum是一个数字指明当有多少个sentinel认为一个master失效时(值一般为sentinel总数/2 1)master才算真正失效
sentinel monitor mymaster 192.168.146.128 6379 2 # mymaster这个名字随便取客户端访问时会用到3、启动sentinel哨兵实例
src/redis-sentinel sentinel-26379.conf4、查看sentinel的info信息
src/redis-cli -p 26379
127.0.0.1:26379info
可以看到Sentinel的info里已经识别出了redis的主从5、可以自己再配置两个sentinel端口26380和26381注意上述配置文件里的对应数字都要修改
sentinel集群都启动完毕后会将哨兵集群的元数据信息写入所有sentinel的配置文件里去(追加在文件的最下面)我们查看下如下配置文件sentinel-26379.conf如下所示
sentinel known-replica mymaster 192.168.146.128 6380 #代表redis主节点的从节点信息
sentinel known-replica mymaster 192.168.146.128 6381 #代表redis主节点的从节点信息
sentinel known-sentinel mymaster 192.168.146.128 26381 c3c89b0c5d1b81f5e9487ae17260c8fbfe477c2e #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.146.128 26380 b3d47687e6b2d6d569b0c6a53a1b92be5c442c5c #代表感知到的其它哨兵节点
当redis主节点如果挂了哨兵集群会重新选举出新的redis主节点同时会修改所有sentinel节点配置文件的集群元数据信息比如6379的redis如果挂了假设选举出的新主节点是6380则sentinel文件里的集群元数据信息会变成如下所示
sentinel known-replica mymaster 192.168.146.128 6380 #代表主节点的从节点信息
sentinel known-replica mymaster 192.168.146.128 6379 #代表主节点的从节点信息
sentinel known-sentinel mymaster 192.168.146.128 26381 c3c89b0c5d1b81f5e9487ae17260c8fbfe477c2e #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.146.128 26379 af83ca977cadbe5842472fea925c85906c26e1e0 #代表感知到的其它哨兵节点
同时还会修改sentinel文件里之前配置的mymaster对应的6379端口改为6380
sentinel monitor mymaster 192.168.146.128 6381 2 当6379的redis实例再次启动时哨兵集群根据集群元数据信息就可以将6379端口的redis节点作为从节点加入集群
哨兵的Jedis连接代码
public class JedisSentinelTest {public static void main(String[] args) throws IOException {JedisPoolConfig config new JedisPoolConfig();config.setMaxTotal(20);config.setMaxIdle(10);config.setMinIdle(5);String masterName mymaster;SetString sentinels new HashSetString();sentinels.add(new HostAndPort(192.168.0.60,26379).toString());sentinels.add(new HostAndPort(192.168.0.60,26380).toString());sentinels.add(new HostAndPort(192.168.0.60,26381).toString());//JedisSentinelPool其实本质跟JedisPool类似都是与redis主节点建立的连接池//JedisSentinelPool并不是说与sentinel建立的连接池而是通过sentinel发现redis主节点并与其建立连接JedisSentinelPool jedisSentinelPool new JedisSentinelPool(masterName, sentinels, config, 3000, null);Jedis jedis null;try {jedis jedisSentinelPool.getResource();System.out.println(jedis.set(sentinel, gao));System.out.println(jedis.get(sentinel));} catch (Exception e) {e.printStackTrace();} finally {//注意这里不是关闭连接在JedisPool模式下Jedis会被归还给资源池。if (jedis ! null)jedis.close();}}
}
1、引入相关依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId
/dependency
springboot项目核心配置
server:port: 8080spring:redis:database: 0timeout: 3000sentinel: #哨兵模式master: mymaster #主服务器所在集群名称nodes: 192.168.146.128:26379,192.168.146.128:26380,192.168.146.128:26381lettuce:pool:max-idle: 50min-idle: 10max-active: 100max-wait: 1000
访问代码
RestController
public class IndexController {private static final Logger logger LoggerFactory.getLogger(IndexController.class);Autowiredprivate StringRedisTemplate stringRedisTemplate;/*** 测试节点挂了哨兵重新选举新的master节点客户端是否能动态感知到* 新的master选举出来后哨兵会把消息发布出去客户端实际上是实现了一个消息监听机制* 当哨兵把新master的消息发布出去客户端会立马感知到新master的信息从而动态切换访问的masterip** throws InterruptedException*/RequestMapping(/test_sentinel)public void testSentinel() throws InterruptedException {int i 1;while (true){try {stringRedisTemplate.opsForValue().set(gaoi, i);System.out.println(设置key gao i);i;Thread.sleep(1000);}catch (Exception e){logger.error(错误, e);}}}
}
StringRedisTemplate与RedisTemplate详解
spring 封装了 RedisTemplate 对象来进行对redis的各种操作它支持所有的 redis 原生的 api。在RedisTemplate中提供了几个常用的接口方法的使用分别是:
private ValueOperationsK, V valueOps;
private HashOperationsK, V hashOps;
private ListOperationsK, V listOps;
private SetOperationsK, V setOps;
private ZSetOperationsK, V zSetOps;
RedisTemplate中定义了对5种数据结构操作
redisTemplate.opsForValue();//操作字符串
redisTemplate.opsForHash();//操作hash
redisTemplate.opsForList();//操作list
redisTemplate.opsForSet();//操作set
redisTemplate.opsForZSet();//操作有序set
StringRedisTemplate继承自RedisTemplate也一样拥有上面这些操作。
StringRedisTemplate默认采用的是String的序列化策略保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略保存的key和value都是采用此策略序列化保存的。
Redis客户端命令对应的RedisTemplate中的方法列表 String类型结构 Redis RedisTemplate rt set key value rt.opsForValue().set(key,value) get key rt.opsForValue().get(key) del key rt.delete(key) strlen key rt.opsForValue().size(key) getset key value rt.opsForValue().getAndSet(key,value) getrange key start end rt.opsForValue().get(key,start,end) append key value rt.opsForValue().append(key,value) Hash结构 hmset key field1 value1 field2 value2... rt.opsForHash().putAll(key,map) //map是一个集合对象 hset key field value rt.opsForHash().put(key,field,value) hexists key field rt.opsForHash().hasKey(key,field) hgetall key rt.opsForHash().entries(key) //返回Map对象 hvals key rt.opsForHash().values(key) //返回List对象 hkeys key rt.opsForHash().keys(key) //返回List对象 hmget key field1 field2... rt.opsForHash().multiGet(key,keyList) hsetnx key field value rt.opsForHash().putIfAbsent(key,field,value hdel key field1 field2 rt.opsForHash().delete(key,field1,field2) hget key field rt.opsForHash().get(key,field) List结构 lpush list node1 node2 node3... rt.opsForList().leftPush(list,node) rt.opsForList().leftPushAll(list,list) //list是集合对象 rpush list node1 node2 node3... rt.opsForList().rightPush(list,node) rt.opsForList().rightPushAll(list,list) //list是集合对象 lindex key index rt.opsForList().index(list, index) llen key rt.opsForList().size(key) lpop key rt.opsForList().leftPop(key) rpop key rt.opsForList().rightPop(key) lpushx list node rt.opsForList().leftPushIfPresent(list,node) rpushx list node rt.opsForList().rightPushIfPresent(list,node) lrange list start end rt.opsForList().range(list,start,end) lrem list count value rt.opsForList().remove(list,count,value) lset key index value rt.opsForList().set(list,index,value) Set结构 sadd key member1 member2... rt.boundSetOps(key).add(member1,member2,...) rt.opsForSet().add(key, set) //set是一个集合对象 scard key rt.opsForSet().size(key) sidff key1 key2 rt.opsForSet().difference(key1,key2) //返回一个集合对象 sinter key1 key2 rt.opsForSet().intersect(key1,key2)//同上 sunion key1 key2 rt.opsForSet().union(key1,key2)//同上 sdiffstore des key1 key2 rt.opsForSet().differenceAndStore(key1,key2,des) sinter des key1 key2 rt.opsForSet().intersectAndStore(key1,key2,des) sunionstore des key1 key2 rt.opsForSet().unionAndStore(key1,key2,des) sismember key member rt.opsForSet().isMember(key,member) smembers key rt.opsForSet().members(key) spop key rt.opsForSet().pop(key) srandmember key count rt.opsForSet().randomMember(key,count) srem key member1 member2... rt.opsForSet().remove(key,member1,member2,...)