给hash表分片:降低锁粒度,提高锁性能
性能 提高 HASH 降低 分片 粒度
2023-06-13 09:15:56 时间
锁就像漏斗,将并发处理的多个线程变成串行化的模式,我们可以构建一个支持成千上万并发的系统,但是如果锁处理的不好会严重影响系统的性能,就像拥有多条车道的高速公路变成了单行道。
举个例子,假如我们使用go
的map
来实现一个简单的缓存,由于map
不是并发安全,所以我们还要借助sync
包的锁来保证并发安全,于是我们很容易写出下面这样的代码:
package simple_cache
import (
"sync"
)
type Cache struct {
items map[string][]byte
lock *sync.RWMutex
}
func New() *Cache {
return &Cache{
items: make(map[string][]byte, 2000),
lock: new(sync.RWMutex),
}
}
func (c *Cache) Get(key string) []byte {
// 取数据只要加读锁
c.lock.RLock()
defer c.lock.RUnlock()
return c.items[key]
}
func (c *Cache) Set(key string, data []byte) {
c.lock.Lock()
defer c.lock.Unlock()
c.items[key] = data
}
这段代码考虑到了锁其实已经算是不错了,但是每次调用set()
方法去设置缓存值的时候不仅将并发读写变成了串行化的模式,就连get()
方法也会被阻塞住。在实际生产中使用这段代码作为缓存的时候,map
中会缓存大量数据,set()
调用可能会很频繁,而且在set()
内还需要判断缓存的容量是否足够,这些都会使锁的时间变长。
然后我们不得不考虑如何优化一下锁的性能。上面代码的问题是每次set()
都锁住了整个map
,于是我们就想到能不能只锁住一部分,这样就能降低锁对性能的消耗。我们可以把原先这个大的缓存分成若干个小的分片,每个分片就是原先的一个Cache
,然后再将这些分片放入一个大的map
中,根据缓存key
值通过hash
计算后的值找到对应的分片。对上面代码改造如下:
package simple_cache
import (
"crypto/sha1"
"fmt"
"sync"
)
type Cache map[string]*ShardCache
type ShardCache struct {
items map[string][]byte
lock *sync.RWMutex
}
func NewCache() *Cache {
cache := make(Cache, 256)
for i := 0; i < 256; i++ {
cache[fmt.Sprintf("%02x", i)] = &ShardCache{
items: make(map[string][]byte, 2000),
lock: new(sync.RWMutex),
}
}
return &cache
}
func (c Cache) getShard(key string) *ShardCache {
hasher := sha1.New()
hasher.Write([]byte(key))
// 转16进制后取前两位
shardKey := fmt.Sprintf("%x", hasher.Sum(nil))[0:2]
return c[shardKey]
}
func (c Cache) Get(key string) []byte {
// 取数据只要加读锁
shard := c.getShard(key)
shard.lock.RLock()
defer shard.lock.RUnlock()
return shard.items[key]
}
func (c Cache) Set(key string, data []byte) {
shard := c.getShard(key)
shard.lock.Lock()
shard.lock.Unlock()
shard.items[key] = data
}
这里我们一共给缓存设置了256(16^2)个分片,对于任意的一个缓存key
值经过hash
后通过fmt.Sprintf("%x", hasher.Sum(nil))[0:2]
转16进制后取前两位后都能在缓存中找到对应的分片
其实像java
里面的ConcurrencyHashmap
已经是这样做的了,我们通过hash
计算数据存储的所在的分片,虽然消耗一点点计算资源但是解决了锁粒度大导致的锁性能问题,这是很值得的。
总结
- 通过对
hash
表分片,大锁拆小锁,降低锁粒度,提高高并发情况下的锁性能
相关文章
- NTP服务器(时间同步服务器)的市场需求及性能分析
- PS首选项性能怎样设置最佳?PS首选项常规设置教程
- 新机器使用前,先安装BBR加速,网站性能提高80%以上
- JQuery Tips(4)—-一些关于提高JQuery性能的Tips详解编程语言
- Oracle数据库:利用缓存提升性能(oracle缓存数据库)
- MySQL升级:提高数据库性能(mysql升级)
- 教程MySQL读写分离技术视频教程:提高性能与数据安全(mysql读写分离视频)
- hash灵活利用Redis嵌套Hash进行性能优化(redis嵌套)
- 测试MySQL数据库极限性能测试(mysql数据库极限)
- 利用Oracle触发器提高数据库性能(oracle触发器类型)
- X-NAND技术有望为QLC闪存带来SLC级别的性能
- 使用Redis队列提高系统性能与并发处理能力(redisqueue)
- 快速学习PHP MySQL部署技巧,提高网站性能与安全性(phpmysql部署)
- 深入了解Linux网络子系统,优化网络性能(linux网络子系统)
- Linux栈大小的讨论:如何调整栈空间以提高性能?(linux栈大小)
- MySQL的绑定变量:提高查询性能,减少安全风险(mysql的绑定变量)
- 利用Redis 提高App性能效率(appredis)
- Oracle内存交换分区提高性能的最佳实践(oracle内存交换分区)
- 提升Oracle写性能提高写速度的方法探索(oracle写速度慢)
- MySQL批量更新轻松提高数据库性能(c mysql 批量更新)
- 实时检测Redis运行状况保障性能(检测redis运行情况)
- MySQL索引提高数据库性能的利器(.mysql索引)
- 新一代提高Redis性能低成本实现更高效率(低redis)
- 利用压测调试提高Redis热点应用性能(压测 redis热点)
- 望提高数据库性能Oracle优化器期待助力数据库性能提升(Oracle优化器有)
- 在PHP中使用Redis提高网站性能(在php中使用redis)
- 配置Oracle SGA参数优化,提高数据库性能(oracle sga参数)
- 使用Redis优化网站性能修改缓存路径(redis 缓存路径修改)
- js的压缩及jquery压缩探讨(提高页面加载性能/保护劳动成果)
- linux下实现web数据同步的四种方式(性能比较)