网站浏览器兼容性,宁波网络推广优化,网站建设培训教程 新手入门到精通,创意产品缓存与数据库双写不一致小概率事件
//线程1 写数据库stock 5 ---------------》更新缓存
//线程2 写数据库stock 4 -----》更新缓存
//线程1 ------》写数据库stock 10 -----》删除缓存
//线程2 ---------------------------------------------------------------------…缓存与数据库双写不一致小概率事件
//线程1 写数据库stock 5 ---------------》更新缓存
//线程2 写数据库stock 4 -----》更新缓存
//线程1 ------》写数据库stock 10 -----》删除缓存
//线程2 -----------------------------------------------------------------------------------------------》写数据库stock 9 -----》删除缓存
//线程3 -------------------------------------------------》查缓存空----》查数据库stock 10------------------------------------------------------------------------》写缓存
使用 redisson 的 RReadWriteLock让修改 stock 的地方串行执行源码还是使用 lua 脚本实现。
开发规范与性能优化
key名设计
可读性和可管理性: 以业务名(或数据库名)为前缀(防止key冲突)用冒号分隔比如业务名:表名:id简洁性: 控制key的长度不要包含特殊字符
vlaue设计原则
拒绝bigkey 字符串类型它的big体现在单个value值很大一般认为超过10KB就是bigkey非字符串类型哈希、列表、集合、有序集合它们的big体现在元素个数太多。一般来说string类型控制在10KB以内hash、list、set、zset元素个数不要超过5000。非字符串的bigkey不要使用del删除使用hscan、sscan、zscan方式渐进式删除同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期会触发del操作造成阻塞bigkey的危害 导致redis阻塞网络拥塞过期删除 默认异步删除
bigkey的产生
一般来说bigkey的产生都是由于程序设计不当或者对于数据规模预料不清楚造成的来看几个例子
(1) 社交类粉丝列表如果某些明星或者大v不精心设计下必是bigkey。
(2) 统计类例如按天存储某项功能或者网站的用户集合除非没几个人用否则必是bigkey。
(3) 缓存类将数据从数据库load出来序列化放到Redis里这个方式非常常用但有两个地方需要注意第一是不是有必要把所有字段都缓存第二有没有相关关联的数据有的同学为了图方便把相关数据都存一个key下产生bigkey。
如何优化bigkey
拆 big list list1、list2、…listNbig hash可以讲数据分段存储比如一个大的key假设存了1百万的用户数据可以拆分成200个key每个key下面存放5000个用户数据如果bigkey不可避免也要思考一下要不要每次把所有元素都取出来(例如有时候仅仅需要hmget而不是hgetall) 选择适合的数据类型控制key的生命周期
命令使用
hgetall、lrange、smembers、zrange、sinter等并非不能使用但是需要明确N的值。有遍历的需求可以使用hscan、sscan、zscan代替。禁止线上使用keys、flushall、flushdb等通过redis的rename机制禁掉命令或者使用scan的方式渐进式处理。redis的多数据库较弱使用数字进行区分很多客户端支持较差同时多业务用多数据库实际还是单线程处理会有干扰。使用批量操作提高效率 原生命令例如mget、mset。非原生命令可以使用pipeline提高效率。但要注意控制一次批量操作的元素个数(例如500以内实际也和元素字节数有关)。原生命令是原子操作pipeline是非原子操作。 Redis事务功能较弱不建议过多使用可以用lua替代
客户端使用
避免多个应用使用一个Redis实例使用带有连接池的数据库可以有效控制连接同时提高效率高并发下建议客户端添加熔断功能(例如sentinel、hystrix)设置合理的密码如有必要可以使用SSL加密访问redis的三种清除策略 被动删除当读/写一个已经过期的key时会触发惰性删除策略直接删除掉这个过期key主动删除由于惰性删除策略无法保证冷数据被及时删掉所以Redis会定期(默认每100ms)主动淘汰一批已过期的key这里的一批只是部分过期key所以可能会出现部分key已经过期但还没有被清理掉的情况导致内存并没有被释放当前已用内存超过maxmemory限定时触发主动清理策略八种淘汰策略 针对设置了过期时间的key做处理 volatile-ttl在筛选时会针对设置了过期时间的键值对根据过期时间的先后进行删除越早过期的越先被删除volatile-random就像它的名称一样在设置了过期时间的键值对中进行随机删除。volatile-lru会使用 LRU 算法筛选设置了过期时间的键值对删除。volatile-lfu会使用 LFU 算法筛选设置了过期时间的键值对删除。 针对所有的key做处理 allkeys-random从所有键值对中随机选择并删除数据。allkeys-lru使用 LRU 算法在所有数据中进行筛选删除。allkeys-lfu使用 LFU 算法在所有数据中进行筛选删除。 不处理 noeviction不会剔除任何数据拒绝所有写入操作并返回客户端错误信息(error) OOM command not allowed when used memory此时Redis只响应读操作
LRU 算法Least Recently Used最近最少使用
淘汰很久没被访问过的数据以最近一次访问时间作为参考。
LFU 算法Least Frequently Used最不经常使用
淘汰最近一段时间被访问次数最少的数据以次数作为参考。 maxmemory-policy(默认是noeviction)推荐使用volatile-lru 当Redis运行在主从模式时只有主结点才会执行过期删除策略然后把删除操作”del key”同步到从结点删除数据。 使用带有连接池的数据库可以有效控制连接同时提高效率标准使用方式
连接池的最佳性能是maxTotal maxIdle这样就避免连接池伸缩带来的性能干扰。但是如果并发量不大或者maxTotal设置过高会导致不必要的连接资源浪费。计算资源池大小 一次命令时间borrow|return resource Jedis执行命令(含网络) 的平均耗时约为1ms一个连接的QPS大约是1000业务期望的QPS是50000理论上需要的资源池大小是50000 / 1000 50个。但事实上这是个理论值还要考虑到要比理论值预留一些资源通常来讲maxTotal可以比理论值大一些。 可以给redis连接池做预热 JedisPoolConfig jedisPoolConfig new JedisPoolConfig();JedisPool jedisPool;{System.out.println(初始化连接池);//资源池中最大连接数jedisPoolConfig.setMaxTotal(10);//资源池允许最大空闲的连接数jedisPoolConfig.setMaxIdle(10);//资源池确保最少空闲的连接数jedisPoolConfig.setMinIdle(2);//向资源池借用连接时是否做连接有效性检测(ping)无效连接会被移除。业务量很大时候建议设置为false(多一次ping的开销)jedisPoolConfig.setTestOnBorrow(true);jedisPool new JedisPool(jedisPoolConfig, 127.0.0.1, 6379, 3000, null);//连接池预热示例代码ListJedis minIdleJedisList new ArrayListJedis(jedisPoolConfig.getMinIdle());for (int i 0; i jedisPoolConfig.getMinIdle(); i) {Jedis jedis null;try {jedis jedisPool.getResource();jedis.clientSetname(client: i);minIdleJedisList.add(jedis);jedis.ping();} catch (Exception e) {e.printStackTrace();} finally {//注意这里不能马上close将连接还回连接池否则最后连接池里只会建立1个连接。。//jedis.close();}}//统一将预热的连接还回连接池for (int i 0; i jedisPoolConfig.getMinIdle(); i) {Jedis jedis null;try {jedis minIdleJedisList.get(i);//将连接归还回连接池jedis.close();System.out.println(连接 jedis.clientGetname() 归还成功);} catch (Exception e) {e.printStackTrace();} finally {}}}GetMapping(pool)public String pool() {Jedis jedis null;try {jedis jedisPool.getResource();//具体的命令String set jedis.set(pool:redis:, 1);System.out.println(使用连接 jedis.clientGetname() 执行 结果: set);} catch (Exception e) {e.printStackTrace();} finally {//注意这里不是关闭连接在JedisPool模式下Jedis会被归还给资源池。if (jedis ! null)jedis.close();}return success;}慢查询日志slowlog
config get slow* #查询有关慢日志的配置信息
config set slowlog-log-slower-than 20000 #设置慢日志使时间阈值,单位微秒此处为20毫秒即超过20毫秒的操作都会记录下来生产环境建议设置1000也就是1ms这样理论上redis并发至少达到1000如果要求单机并发达到1万以上这个值可以设置为100
config set slowlog-max-len 1024 #设置慢日志记录保存数量如果保存数量已满会删除最早的记录最新的记录追加进来。记录慢查询日志时Redis会对长命令做截断操作并不会占用大量内存建议设置稍大些防止丢失日志
config rewrite #将服务器当前所使用的配置保存到redis.conf
slowlog len #获取慢查询日志列表的当前长度
slowlog get 5 #获取最新的5条慢查询日志。慢查询日志由四个属性组成标识ID发生时间戳命令耗时执行命令和参数
slowlog reset #重置慢查询日志布隆过滤器
布隆过滤器就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得比较均匀。
布隆过滤器的实现原理
0000000000000000000k1111k2111k3111
对 k1 进行多个 hash 计算获得数组索引值add
对 k2 进行多个 hash 计算获得数组索引值add
对 k3 进行多个 hash 计算获得数组索引值exists此时出现了 hash 碰撞误判 k3 存在。
适用于数据命中不高、 数据相对固定、 实时性低通常是数据集较大 的应用场景 代码维护较为复杂 但是缓存空间占用很少。根据预计元素、误差率生成数组的长度。不能修改只能重建。
布隆过滤器的代码示例
GetMapping(bloom)
public String bloom() {Config config new Config();ListString nodes Arrays.asList(redis://192.168.139.135:8001,redis://192.168.139.136:8002,redis://192.168.139.137:8003,redis://192.168.139.135:8004 ,redis://192.168.139.136:8005,redis://192.168.139.137:8006);//集群配置config.useClusterServers().setNodeAddresses(nodes);config.useClusterServers().setPassword(yes);RedissonClient redisson Redisson.create(config);RBloomFilterString nameFilter redisson.getBloomFilter(nameFilter);//初始化布隆过滤器预计元素为100000000L,误差率为3%,根据这两个参数会计算出底层的bit数组大小nameFilter.tryInit(100000000L,0.03);String[] nameList { a, b, c, d, e, f, g, h, i, j, k};String[] checkList { a1, b2, c3, d, e4, f5, g6, h7, i8, j, k};for (String name : nameList) {nameFilter.add(name);}//判断下面号码是否在布隆过滤器中for (String name : checkList) {boolean rs nameFilter.contains(name);System.out.println(name 检查结果 rs);}return success;
}