冷烛芳心
liuchang6152
不顺路了吗那祝你平安
关注数: 16
粉丝数: 2,952
发帖数: 96,473
关注贴吧数: 54
好感可以保持很久很久 但也真的说没有就没有。 各位,周末愉快
或许不是我们不够好,只是时间不凑巧而已。
时光永远静好 ,生活从来不易 早啊,各位。
落花昨夜风吹散。暗恨韶光短。 婷婷解落满天涯。且伴潺潺流水、向谁家? 书窗坐寂斜阳小。当应青丝老。 小城嫣梦已阑珊。但愿他生来世、不相关。
比起过往,前方更值得期待。 大家早啊,
永远不要放弃你真正想要的东西 等待虽难,但后悔更甚。
直壁无恩锁厌人,玄宫佳胜似迷津 不知商岭真忘汉,将谓桃源独避秦。 远树摇情低野甸,断云携恨卷秋旻。 从今要我如君勇,待伴韦郎过两尘。
竹间深路马惊嘶,独入蓬门半似迷。 劳问圃人终岁事,桔槔声里雨春畦。
只缘频燕蓬洲客,引得游人去 似迷
寞塔当选一月份最佳教练
一比二主场输给紫百合
主场对佛罗伦萨 凶多吉少
这个点在线的进来报个数 应该没人吧
Mac终端: brew command not found 解决方法 http://tieba.baidu.com/mo/q/checkurl?url=https%3A%2F%2Fblog.csdn.net%2Fweixin_43822632%2Farticle%2Fdetails%2F110472605&urlrefer=502db4df9c39b38d1790f44df228684a
二比二和副班长战平,结束两连胜
一文揭秘单线程的Redis为什么这么快? 【小宅按】Redis 作为一种 KV 缓存服务器,有着极高的性能,相对于 Memcache,Redis 支持更多种数据类型,因此在业界应用广泛。 记得刚毕业那会参加面试,面试官会问我 Redis 为什么快,由于当时技术水平有限,我只能回答出如下两点: 数据是存储在内存中的。 Redis 是单线程的。 当然,将数据存储在内存中,读取的时候不需要进行磁盘的 IO,单线程也保证了系统没有线程的上下文切换。 但这两点只是 Redis 高性能原因的很小一部分,下面从数据存储层面上为大家分析 Redis 性能为何如此高。 Redis性能如此高的原因,我总结了如下几点: 纯内存操作 单线程 高效的数据结构 合理的数据编码 其他方面的优化 在 Redis 中,常用的 5 种数据结构和应用场景如下: String:缓存、计数器、分布式锁等。 List:链表、队列、微博关注人时间轴列表等。 Hash:用户信息、Hash 表等。 Set:去重、赞、踩、共同好友等。 Zset:访问量排行榜、点击量排行榜等。 SDS Redis 是用 C 语言开发完成的,但在 Redis 字符串中,并没有使用 C 语言中的字符串,而是用一种称为 SDS(Simple Dynamic String)的结构体来保存字符串。struct sdshdr { int len; int free; char buf[];} SDS 的结构如上图: len:用于记录 buf 中已使用空间的长度。 free:buf 中空闲空间的长度。 buf[]:存储实际内容。 例如:执行命令 set key value,key 和 value 都是一个 SDS 类型的结构存储在内存中。 SDS 与 C 字符串的区别 ①常数时间内获得字符串长度 C 字符串本身不记录长度信息,每次获取长度信息都需要遍历整个字符串,复杂度为 O(n);C 字符串遍历时遇到 '\0' 时结束。 SDS 中 len 字段保存着字符串的长度,所以总能在常数时间内获取字符串长度,复杂度是 O(1)。 ②避免缓冲区溢出 假设在内存中有两个紧挨着的两个字符串,s1=“xxxxx”和 s2=“yyyyy”。 由于在内存上紧紧相连,当我们对 s1 进行扩充的时候,将 s1=“xxxxxzzzzz”后,由于没有进行相应的内存重新分配,导致 s1 把 s2 覆盖掉,导致 s2 被莫名其妙的修改。 但 SDS 的 API 对 zfc 修改时首先会检查空间是否足够,若不充足则会分配新空间,避免了缓冲区溢出问题。 ③减少字符串修改时带来的内存重新分配的次数 在 C 中,当我们频繁的对一个字符串进行修改(append 或 trim)操作的时候,需要频繁的进行内存重新分配的操作,十分影响性能。 如果不小心忘记,有可能会导致内存溢出或内存泄漏,对于 Redis 来说,本身就会很频繁的修改字符串,所以使用 C 字符串并不合适。而 SDS 实现了空间预分配和惰性空间释放两种优化策略: 空间预分配:当 SDS 的 API 对一个 SDS 修改后,并且对 SDS 空间扩充时,程序不仅会为 SDS 分配所需要的必须空间,还会分配额外的未使用空间。 分配规则如下:如果对 SDS 修改后,len 的长度小于 1M,那么程序将分配和 len 相同长度的未使用空间。举个例子,如果 len=10,重新分配后,buf 的实际长度会变为 10(已使用空间)+10(额外空间)+1(空字符)=21。如果对 SDS 修改后 len 长度大于 1M,那么程序将分配 1M 的未使用空间。 惰性空间释放:当对 SDS 进行缩短操作时,程序并不会回收多余的内存空间,而是使用 free 字段将这些字节数量记录下来不释放,后面如果需要 append 操作,则直接使用 free 中未使用的空间,减少了内存的分配。 ④二进制安全 在 Redis 中不仅可以存储 String 类型的数据,也可能存储一些二进制数据。 二进制数据并不是规则的字符串格式,其中会包含一些特殊的字符如 '\0',在 C 中遇到 '\0' 则表示字符串的结束,但在 SDS 中,标志字符串结束的是 len 属性。 字典 与 Java 中的 HashMap 类似,Redis 中的 Hash 比 Java 中的更高级一些。 Redis 本身就是 KV 服务器,除了 Redis 本身数据库之外,字典也是哈希键的底层实现。 字典的数据结构如下所示: typedef struct dict{ dictType *type; void *privdata; dictht ht[2]; int trehashidx;} typedef struct dictht{ //哈希表数组 dectEntrt **table; //哈希表大小 unsigned long size; // unsigned long sizemask; //哈希表已有节点数量 unsigned long used;} 重要的两个字段是 dictht 和 trehashidx,这两个字段与 rehash 有关,下面重点介绍 rehash。 Rehash 学过 Java 的朋友都应该知道 HashMap 是如何 rehash 的,在此处我就不过多赘述,下面介绍 Redis 中 Rehash 的过程。 由上段代码,我们可知 dict 中存储了一个 dictht 的数组,长度为 2,表明这个数据结构中实际存储着两个哈希表 ht[0] 和 ht[1],为什么要存储两张 hash 表呢? 当然是为了 Rehash,Rehash 的过程如下: 为 ht[1] 分配空间。如果是扩容操作,ht[1] 的大小为第一个大于等于 ht[0].used*2 的 2^n;如果是缩容操作,ht[1] 的大小为第一个大于等于 ht[0].used 的 2^n。 将 ht[0] 中的键值 Rehash 到 ht[1] 中。 当 ht[0] 全部迁移到 ht[1] 中后,释放 ht[0],将 ht[1] 置为 ht[0],并为 ht[1] 创建一张新表,为下次 Rehash 做准备。 渐进式 Rehash 由于 Redis 的 Rehash 操作是将 ht[0] 中的键值全部迁移到 ht[1],如果数据量小,则迁移过程很快。但如果数据量很大,一个 Hash 表中存储了几万甚至几百万几千万的键值时,迁移过程很慢并会影响到其他用户的使用。 为了避免 Rehash 对服务器性能造成影响,Redis 采用了一种渐进式 Rehash 的策略,分多次、渐进的将 ht[0] 中的数据迁移到 ht[1] 中。 前一过程如下: 为 ht[1] 分配空间,让字典同时拥有 ht[0] 和 ht[1] 两个哈希表。 字典中维护一个 rehashidx,并将它置为 0,表示 Rehash 开始。 在 Rehash 期间,每次对字典操作时,程序还顺便将 ht[0] 在 rehashidx 索引上的所有键值对 rehash 到 ht[1] 中,当 Rehash 完成后,将 rehashidx 属性+1。当全部 rehash 完成后,将 rehashidx 置为 -1,表示 rehash 完成。 注意,由于维护了两张 Hash 表,所以在 Rehash 的过程中内存会增长。另外,在 Rehash 过程中,字典会同时使用 ht[0] 和 ht[1]。 所以在删除、查找、更新时会在两张表中操作,在查询时会现在第一张表中查询,如果第一张表中没有,则会在第二张表中查询。但新增时一律会在 ht[1] 中进行,确保 ht[0] 中的数据只会减少不会增加。 跳跃表 Zset 是一个有序的链表结构,其底层的数据结构是跳跃表 skiplist,其结构如下: typedef struct zskiplistNode { //成员对象 robj *obj; //分值 double score; //后退指针 struct zskiplistNode *backward; //层 struct zskiplistLevel { struct zskiplistNode *forward;//前进指针 unsigned int span;//跨度 } level[]; } zskiplistNode; typedef struct zskiplist { //表头节点和表尾节点 struct zskiplistNode *header, *tail; //表中节点的的数量 unsigned long length; //表中层数最大的节点层数 int level; } zskiplist;前进指针:用于从表头向表尾方向遍历。 后退指针:用于从表尾向表头方向回退一个节点,和前进指针不同的是,前进指针可以一次性跳跃多个节点,后退指针每次只能后退到上一个节点。 跨度:表示当前节点和下一个节点的距离,跨度越大,两个节点中间相隔的元素越多。 在查询过程中跳跃着前进。由于存在后退指针,如果查询时超出了范围,通过后退指针回退到上一个节点后仍可以继续向前遍历。 压缩列表 压缩列表 ziplist 是为 Redis 节约内存而开发的,是列表键和字典键的底层实现之一。 当元素个数较少时,Redis 用 ziplist 来存储数据,当元素个数超过某个值时,链表键中会把 ziplist 转化为 linkedlist,字典键中会把 ziplist 转化为 hashtable。 ziplist 是由一系列特殊编码的连续内存块组成的顺序型的数据结构,ziplist 中可以包含多个 entry 节点,每个节点可以存放整数或者字符串。由于内存是连续分配的,所以遍历速度很快。 编码转化 Redis 使用对象(redisObject)来表示数据库中的键值,当我们在 Redis 中创建一个键值对时,至少创建两个对象,一个对象是用做键值对的键对象,另一个是键值对的值对象。 例如我们执行 SET MSG XXX 时,键值对的键是一个包含了字符串“MSG“的对象,键值对的值对象是包含字符串"XXX"的对象。 redisObject 的结构如下: typedef struct redisObject{ //类型 unsigned type:4; //编码 unsigned encoding:4; //指向底层数据结构的指针 void *ptr; //... }robj; 其中 type 字段记录了对象的类型,包含字符串对象、列表对象、哈希对象、集合对象、有序集合对象。 当我们执行 type key 命令时返回的结果如下:ptr 指针字段指向对象底层实现的数据结构,而这些数据结构是由 encoding 字段决定的,每种对象至少有两种数据编码:可以通过 object encoding key 来查看对象所使用的编码:细心的读者可能注意到,list、hash、zset 三个键使用的是 ziplist 压缩列表编码,这就涉及到了 Redis 底层的编码转换。 上面介绍到,ziplist 是一种结构紧凑的数据结构,当某一键值中所包含的元素较少时,会优先存储在 ziplist 中,当元素个数超过某一值后,才将 ziplist 转化为标准存储结构,而这一值是可配置的。 String 对象的编码转化 String 对象的编码可以是 int 或 raw,对于 String 类型的键值,如果我们存储的是纯数字,Redis 底层采用的是 int 类型的编码,如果其中包括非数字,则会立即转为 raw 编码: 127.0.0.1:6379> set str 1OK127.0.0.1:6379> object encoding str"int"127.0.0.1:6379> set str 1aOK127.0.0.1:6379> object encoding str"raw"127.0.0.1:6379>List 对象的编码转化 List 对象的编码可以是 ziplist 或 linkedlist,对于 List 类型的键值,当列表对象同时满足以下两个条件时,采用 ziplist 编码: 列表对象保存的所有字符串元素的长度都小于 64 字节。 列表对象保存的元素个数小于 512 个。 如果不满足这两个条件的任意一个,就会转化为 linkedlist 编码。注意:这两个条件是可以修改的,在 redis.conf 中: list-max-ziplist-entries 512list-max-ziplist-value 64 Set 类型的编码转化 Set 对象的编码可以是 intset 或 hashtable,intset 编码的结婚对象使用整数集合作为底层实现,把所有元素都保存在一个整数集合里面。 127.0.0.1:6379> sadd set 1 2 3(integer) 3127.0.0.1:6379> object encoding set"intset"127.0.0.1:6379> 如果 set 集合中保存了非整数类型的数据时,Redis 会将 intset 转化为 hashtable: 127.0.0.1:6379> sadd set 1 2 3(integer) 3127.0.0.1:6379> object encoding set"intset"127.0.0.1:6379> sadd set a(integer) 1127.0.0.1:6379> object encoding set"hashtable" 127.0.0.1:6379> 当 Set 对象同时满足以下两个条件时,对象采用 intset 编码: 保存的所有元素都是整数值(小数不行)。 Set 对象保存的所有元素个数小于 512 个。 不能满足这两个条件的任意一个,Set 都会采用 hashtable 存储。注意:第两个条件是可以修改的,在 redis.conf 中: set-max-intset-entries 512Hash 对象的编码转化 Hash 对象的编码可以是 ziplist 或 hashtable,当 Hash 以 ziplist 编码存储的时候,保存同一键值对的两个节点总是紧挨在一起,键节点在前,值节点在后:当 Hash 对象同时满足以下两个条件时,Hash 对象采用 ziplist 编码: Hash 对象保存的所有键值对的键和值的字符串长度均小于 64 字节。 Hash 对象保存的键值对数量小于 512 个。 如果不满足以上条件的任意一个,ziplist 就会转化为 hashtable 编码。注意:这两个条件是可以修改的,在 redis.conf 中: hash-max-ziplist-entries 512hash-max-ziplist-value 64Zset 对象的编码转化 Zset 对象的编码可以是 ziplist 或 zkiplist,当采用 ziplist 编码存储时,每个集合元素使用两个紧挨在一起的压缩列表来存储。 第一个节点存储元素的成员,第二个节点存储元素的分值,并且按分值大小从小到大有序排列。当 Zset 对象同时满足一下两个条件时,采用 ziplist 编码: Zset 保存的元素个数小于 128。 Zset 元素的成员长度都小于 64 字节。 如果不满足以上条件的任意一个,ziplist 就会转化为 zkiplist 编码。注意:这两个条件是可以修改的,在 redis.conf 中: zset-max-ziplist-entries 128zset-max-ziplist-value 64 思考:Zset 如何做到 O(1) 复杂度内元素并且快速进行范围操作?Zset 采用 skiplist 编码时使用 zset 结构作为底层实现,该数据结构同时包含了一个跳跃表和一个字典。 其结构如下: typedef struct zset{ zskiplist *zsl; dict *dict;} Zset 中的 dict 字典为集合创建了一个从成员到分值之间的映射,字典中的键保存了成员,字典中的值保存了成员的分值,这样定位元素时时间复杂度是 O(1)。 Zset 中的 zsl 跳跃表适合范围操作,比如 ZRANK、ZRANGE 等,程序使用 zkiplist。 另外,虽然 Zset 中使用了 dict 和 skiplist 存储数据,但这两种数据结构都会通过指针来共享相同的内存,所以没有必要担心内存的浪费。 过期数据的删除对 Redis 性能影响 当我们对某些 key 设置了 expire 时,数据到了时间会自动删除。如果一个键过期了,它会在什么时候删除呢? 下面介绍三种删除策略: 定时删除:在这是键的过期时间的同时,创建一个定时器 Timer,让定时器在键过期时间来临时立即执行对过期键的删除。 惰性删除:键过期后不管,每次读取该键时,判断该键是否过期,如果过期删除该键返回空。 定期删除:每隔一段时间对数据库中的过期键进行一次检查。 定时删除:对内存友好,对 CPU 不友好。如果过期删除的键比较多的时候,删除键这一行为会占用相当一部分 CPU 性能,会对 Redis 的吞吐量造成一定影响。 惰性删除:对 CPU 友好,内存不友好。如果很多键过期了,但在将来很长一段时间内没有很多客户端访问该键导致过期键不会被删除,占用大量内存空间。 定期删除:是定时删除和惰性删除的一种折中。每隔一段时间执行一次删除过期键的操作,并且限制删除操作执行的时长和频率。 具体的操作如下: Redis 会将每一个设置了 expire 的键存储在一个独立的字典中,以后会定时遍历这个字典来删除过期的 key。除了定时遍历外,它还会使用惰性删除策略来删除过期的 key。 Redis 默认每秒进行十次过期扫描,过期扫描不会扫描所有过期字典中的 key,而是采用了一种简单的贪心策略。 从过期字典中随机选择 20 个 key;删除这 20 个 key 中已过期的 key;如果过期 key 比例超过 1/4,那就重复步骤 1。 同时,为了保证在过期扫描期间不会出现过度循环,导致线程卡死,算法还增加了扫描时间上限,默认不会超过 25ms。 总结 总而言之,Redis 为了高性能,从各方各面都进行了优化,下次小伙伴们面试的时候,面试官问 Redis 性能为什么如此高,可不能傻傻的只说单线程和内存存储了。
今天还在上班的不是年薪过百万的 就是月薪2000的, 网上看到的,感觉有道理
有时候会羡慕会表达感情的人
两连胜了已经
意大利中前场真的没人了吗? 森西 巴神还能入选国家队?
昨天看了会儿斯佩齐亚对桑普,还有隔壁对门的比赛 这都啥啊?意甲都这样了吗?各种犯规 各种失误,比赛连贯性一点都没有,都快睡着了。
不是吧,还能这么玩???
隔壁大圣怒了! 爱听听,不听走!真霸气
听说新赛季了,打开尘封的游戏 说趁着还早打一局吧。结果更新20分钟,,打开游戏后,看故事 玉城保卫战又十多分钟,再看时间,该睡觉了
大过年的遇到这事,***糟心
希望今天有个好的结果 上班去了
名是出巡实故游,千寻往忆问何休?芳容笔墨丹青落,寂寞思卿梦里 名是出巡实故游,千寻往忆问何休? 芳容笔墨丹青落,寂寞思卿梦里头。
为什么后来者居上? “因为前者不争不抢。” “后来者不知廉耻。” “当局者来者不拒。”
好久不发帖了
那个那个,姑娘,我这有两张电影票 不知你晚上有木有时间
欧冠16强对阵出炉,C罗梅西对决。国米中签,切尔西上签
第二的切尔西和国米一个里尔,一个阿贾克斯 唯有梅西的大巴黎对阵小小罗的曼联
破吧没啥人了,无聊时扯淡的 地方都没有了
本来就是一场无关紧要的比赛,怎么还这么较真。 全主力也不一定在客场赢皇马,况且人家平局就是小组第一。 不管是米利唐的犯规还是巴雷拉的红牌,都是再正常不过的事了,怎么赛后还有这么多话题。 因为万里之外的欧洲俩俱乐部还吵的急赤白脸?都是小孩子?
谁还记得这歌??? 回应我的方式有千千万万种,你的心却像是一阵风。
早啊,系统推的屏保真好看
进了
游戏里这个勇者积分有啥用?
杀人诛心,直播吧解说 说世一卫被当猴子耍
这几天这破吧正常了,广告少了
一字谣 一弯秋水一弯凉 , 一帘月色染清霜, 一夜小楼人不寐 , 一纸薄笺离恨长。
对不起,对不起,真的对不起 我一开始只是开玩笑,也没想到你会真的当真,真的相信。我不是单身,也不在兰州。你来兰州也见不到我的。 高铁票退了吧,退不了的话票多少钱,我微信转给你。
藏头????? 一入红尘自笑痴, 两行清泪寂寥时。 三生多少前缘梦, 四海飘零半卷诗。
虞美人 故园别后胭脂瘦,谁解眉心皱。 由来好梦易成空,多少离愁别恨月明中。 半生已是疏离定,见惯红尘冷。 小楼重上错凭阑,触目伤怀依旧泪潸然。
我在脚步急促的城市之中依然一个人生活
你都如何回忆我带着笑或是很沉默
临江仙 风过枝疏林瘦,秋深白露凝霜。南飞征雁过寒塘。芨荷枯影瘦,碧水映天长。 一季繁华远去,心头暗自神伤。光阴流转鬓苍苍。蹉跎多少事,无语道沧桑。
又见西风愁不住,霜打秋芜,叶老梧桐树。 晚看黄花香驿路,雁过衰草斜阳处。 昨夜瑶琴多曲误,烈酒千盅,一醉飞云舞。 梦断高楼萦晓雾,方醒旧事心如故。
遥相问。莫相问。相问增离恨。 春日往青鸾,秋月频传信。 素笺心叠印,梨花飞红晕。 归期总来迟,怎惹相思近?
溪流几曲回肠断,今夜幽思远。 销魂桥畔语缠绵。 一捻红痕心迹、忆前欢。 清商调转兰舟唤,好住刘郎伴。 月华痴意望君安。 翠袖情歌千唱,暖孤寒。
谁把西风锁玉栏,月光一地未吹干。 临窗有怕衾怀冷,最是相思不耐寒。
故园十月已成荒,一季芳菲落未详。几缕枯藤萦小院,半轮懒日照高 故园十月已成荒,一季芳菲落未详。 几缕枯藤萦小院,半轮懒日照高墙。 桃源花别轻遗谢,暑地红消倍转凉。 我每回眸身似寄,春心到底不堪伤。
葱翠年华梦可温,檀心粉泪付何人?婵娟醉怜玲珑月,玉镜虚开倩婉 葱翠年华梦可温,檀心粉泪付何人? 婵娟醉怜玲珑月,玉镜虚开倩婉君。 缘起缘尽留何意,花开花落又一春。 人间多少纷飞事,饮尽清宵湿浥痕。
京城凝白露,日影挂疏枝。曾与花深处,何妨叶落时。它山随季老, 京城凝白露,日影挂疏枝。 曾与花深处,何妨叶落时。 它山随季老,此意共谁知。 陶令篱藩尽,空余几菊诗。
暖春双燕,他乡陌路 寒江孤影,江湖故人。 头像准备好了,有人一起挂吗
四连胜怎么没人发战报了?
陆止于此,海始于斯
好多人都提着行李箱回家了 我还得继续搬砖
这是全运会田径场?这是选美大赛吧
一场雨的时间,你没有看我 我没有看雨
首页
2
3
4
5
6
7
下一页