先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Golang全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注go)
正文
- 在服务器开启时一次性初始化到服务器本地缓存
- 采用Guava Cache,Guava Cache用于存储频繁使用的少量数据,支持高并发访问
- 也可以使用JDK的CurrentHashMap,需要自行实现
3、热门职位
数据特点:频繁变化,不必时时同步;但一定要有数据,不能为空
方案:
- 数据从服务层读取(dubbo),然后放到本地缓存中(Guava),如果出现超时或读取为空,则返回原
- 来本地缓存的数据。
注意:不同的客户端看到的数据有可能不一样
4、数据回填
从Dubbo中读取数据时,先读取Redis集群的缓存,如果缓存命中则直接返回。如果缓存不命中则返回本地缓存,不能直接读取数据库。用异步的形式从数据库刷入到缓存中。
5、热点策略
对于热点数据我们采用本地缓存策略,而不采用服务熔断策略,因为首页数据可以不准确,但不能不响应。
高并发脏读
先更新数据库,在更新缓存
先删除缓存,在更新数据库
先更新数据库,再删除缓存(推荐)
Redis底层结构和缓存原理
本章学习目标:
- 掌握Redis五种基本数据类型的用法和常见命令的使用
- 了解bitmap、geo、stream的使用
- 理解Redis底层数据结构(Hash、跳跃表、quicklist)
- 了解RedisDB和RedisObject
- 理解LRU算法
- 理解Redis缓存淘汰策略
- 能够较正确的应用Redis缓存淘汰策略
Redis数据类型和应用场景
Redis是一个Key-Value的存储系统,使用ANSI C语言编写。key的类型是字符串。value的数据类型有:常用的:string字符串类型、list列表类型、set集合类型、sortedset(zset)有序集合类型、hash类型。不常见的:bitmap位图类型、geo地理位置类型。Redis5.0新增一种:stream类型
注意:Redis中命令是忽略大小写,(set SET),key是不忽略大小写的 (NAME name)
Redis中key的设计
- 用:分割
- 把表名转换为key前缀, 比如: user:
- 第二段放置主键值
- 第三段放置列名
比如:用户表user, 转换为redis的key-value存储
username 的 key: user:9:username
{userid:9,username:zhangf}
email的key user:9:email
表示明确:看key知道意思
不易被覆盖
数据类型及命令手册
参考:https://blog.csdn.net/qq_36581961/article/details/113248387
Redis客户端访问
Java程序访问Redis
采用jedis API进行访问即可
关闭RedisServer端的防火墙
systemctl stop firewalld(默认)
systemctl disable firewalld.service(设置开启不启动)
新建maven项目后导入Jedis包
redis.clients jedis 2.9.0测试类
@Test
public void testConn(){
//与Redis建立连接 IP+port
Jedis redis = new Jedis(“192.168.127.128”, 6379);
//在Redis中写字符串 key value
redis.set(“jedis:name:1”,“jd-zhangfei”);
//获得Redis中字符串的值
System.out.println(redis.get(“jedis:name:1”));
//在Redis中写list
redis.lpush(“jedis:list:1”,“1”,“2”,“3”,“4”,“5”);
//获得list的长度
System.out.println(redis.llen(“jedis:list:1”));
}
Spring访问Redis
新建项目,添加依赖
org.springframework spring-beans 5.2.5.RELEASE org.springframework spring-core 5.2.5.RELEASE org.springframework spring-context 5.2.5.RELEASE org.springframework spring-test 5.2.5.RELEASE junit junit 4.12 test org.springframework.data spring-data-redis 1.0.3.RELEASE添加Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
classpath:redis.properties
添加properties文件
redis.pool.maxActive=100
redis.pool.maxIdle=50
redis.pool.maxWait=1000
redis.pool.testOnBorrow=true
redis.timeout=50000
redis.server=192.168.72.128
redis.port=6379
编写测试用例
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
import java.io.Serializable;
@ContextConfiguration({ “classpath:redis.xml” })
public class RedisTest extends AbstractJUnit4SpringContextTests {
@Autowired
private RedisTemplate<Serializable, Serializable> rt;
@Test
public void testConn() {
rt.opsForValue().set(“name”,“zhangfei”);
System.out.println(rt.opsForValue().get(“name”));
}
}
SpringBoot访问Redis
新建项目,导入依赖
org.springframework.boot spring-boot-starter-data-redis添加配置文件application.yml
spring:
redis:
host: 192.168.72.128
port: 6379
jedis:
pool:
min-idle: 0
max-idle: 8
max-active: 80
max-wait: 30000
timeout: 3000
添加配置类RedisConfig
package com.lagou.sbr.cache;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Autowired
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
** 添加RedisController**
package com.lagou.sbr.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping(value = “/redis”)
public class RedisController {
@Autowired
RedisTemplate redisTemplate;
@GetMapping(“/put”)
public String put(@RequestParam(required = true) String key,
@RequestParam(required = true) String value) {
//设置过期时间为20秒
redisTemplate.opsForValue().set(key,value,20, TimeUnit.SECONDS);
return “Success”;
}
@GetMapping(“/get”)
public String get(@RequestParam(required = true) String key){
return (String) redisTemplate.opsForValue().get(key);
}
}
修改Application并运行
package com.lagou.sbr;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class SpringbootRedisApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootRedisApplication.class, args);
}
}
底层数据结构
参考(未更新完整):https://blog.csdn.net/qq_36581961/category_10691600.html
缓存过期和淘汰策略
Redis性能高:
官方数据
读:110000次/s
写:81000次/s
长期使用,key会不断增加,Redis作为缓存使用,物理内存也会满
内存与硬盘交换(swap) 虚拟内存 ,频繁IO 性能急剧下降
maxmemory
不设置的场景
Redis的key是固定的,不会增加
Redis作为DB使用,保证数据的完整性,不能淘汰 , 可以做集群,横向扩展
缓存淘汰策略:禁止驱逐 (默认)
设置的场景
Redis是作为缓存使用,不断增加Key
maxmemory : 默认为0 不限制
问题:达到物理内存后性能急剧下架,甚至崩溃;内存与硬盘交换(swap) 虚拟内存 ,频繁IO 性能急剧下降
设置多少?与业务有关
1个Redis实例,保证系统运行 1 G ,剩下的就都可以设置Redis;物理内存的3/4
slaver : 留出一定的内存
在redis.conf中
maxmemory 1024mb
命令: 获得maxmemory数
CONFIG GET maxmemory
设置maxmemory后,当趋近maxmemory时,通过缓存淘汰策略,从内存中删除对象
不设置maxmemory 无最大内存限制 maxmemory-policy noeviction (禁止驱逐) 不淘汰
设置maxmemory maxmemory-policy 要配置
expire数据结构
在Redis中可以使用expire命令设置一个键的存活时间(ttl: time to live),过了这段时间,该键就会自动被删除
expire的使用
expire命令的使用方法如下:expire key ttl(单位秒)
127.0.0.1:6379> expire name 2 #2秒失效
(integer) 1
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> set name zhangfei
OK
127.0.0.1:6379> ttl name #永久有效
(integer) -1
127.0.0.1:6379> expire name 30 #30秒失效
(integer) 1
127.0.0.1:6379> ttl name #还有24秒失效
(integer) 24
127.0.0.1:6379> ttl name #失效
(integer) -2
expire原理
typedef struct redisDb {
dict *dict; – key Value
dict *expires; – key ttl
dict *blocking_keys;
dict *ready_keys;
dict *watched_keys;
int id;
} redisDb;
上面的代码是Redis 中关于数据库的结构体定义,这个结构体定义中除了 id 以外都是指向字典的指针,其中我们只看 dict 和 expires
dict 用来维护一个 Redis 数据库中包含的所有 Key-Value 键值对,expires则用于维护一个 Redis 数据库中设置了失效时间的键(即key与失效时间的映射)
当我们使用 expire命令设置一个key的失效时间时,Redis 首先到 dict 这个字典表中查找要设置的key是否存在,如果存在就将这个key和失效时间添加到 expires 这个字典表。当我们使用 setex命令向系统插入数据时,Redis 首先将 Key 和 Value 添加到 dict 这个字典表中,然后将 Key 和失效时间添加到 expires 这个字典表中。
简单地总结来说就是,设置了失效时间的key和具体的失效时间全部都维护在 expires 这个字典表中。
删除策略
Redis的数据删除有定时删除
、惰性删除
和主动删除
三种方式。Redis目前采用惰性删除
+主动删除
的方式。
定时删除
在设置键的过期时间的同时,创建一个定时器,让定时器在键的过期时间来临时,立即执行对键的删除操作。
创建一个定时器,当key设置有过期时间 且过期时间到达 由定时器任务立即对键的删除
- 优点:节约内存 到时删除 快速释放不必要的内存
- 缺点:CPU压力很大 无论CPU此时的负载多高 均占用CPU 会影响redis服务器相应时间和指令吞吐量
总结:以时间换空间
惰性删除
数据到达过期时间 不做处理 等下次访问该数据时
- 如果过期 删除
- 如果未过期 返回数据
- 优点:节约CPU性能 发现必须删除才删除
- 缺点:内存压力大 出现长期占用内存数据
总结:用存储空间换处理器性能 以空间换时间
调用expireIfNeeded函数,该函数的意义是:读取数据之前先检查一下它有没有失效,如果失效了就删除它。
int expireIfNeeded(redisDb *db, robj *key) {
//获取主键的失效时间 get当前时间-创建时间>ttl
long long when = getExpire(db,key);
//假如失效时间为负数,说明该主键未设置失效时间(失效时间默认为-1),直接返回0
if (when < 0) return 0;
//假如Redis服务器正在从RDB文件中加载数据,暂时不进行失效主键的删除,直接返回0
if (server.loading) return 0;
//如果以上条件都不满足,就将主键的失效时间与当前时间进行对比,如果发现指定的主键
//还未失效就直接返回0
if (mstime() <= when) return 0;
//如果发现主键确实已经失效了,那么首先更新关于失效主键的统计个数,然后将该主键失
//效的信息进行广播,最后将该主键从数据库中删除
server.stat_expiredkeys++;
propagateExpire(db,key);
return dbDelete(db,key);
}
定期删除(主动删除)
周期性轮询redis库中的时效性数据 采用随机抽取的策略 利用过期数据占比的方式控制删除频度
- 特点:CPU性能占用设置有峰值 检测频度可以自定义设置
- 特点:内存占用率不是很大 ,长期占用内存的冷数据会被持续清理
逐出算法
数据逐出的相关配置
- 最大可使用配置
占用物理内存的比例 默认值为0 表示不限制 生产环境按照需求设定 通常设置在50%以上
maxmemory
- 每次选取待删除的个数
选取数据时并不会全库扫描 导致严重的性能消耗 降低读写性能 因此采用随机获取数据的方式作为待检测删除数据
maxmemory-samples
- 删除策略
达到最大内存之后 对被挑出来的数据进行删除的策略
maxmemory-policy
通信协议及事件处理机制
本章学习目标:
- 知道Redis的请求响应模式
- 理解请求数据格式(RESP)
- 描述命令处理流程
- 知道Redis的响应格式
- 掌握4种IO多路复用模式(epoll)
- 理解aeEventLoop
通信协议
Redis是单进程单线程的。应用系统和Redis通过Redis协议(RESP)进行交互。
请求响应模式
Redis协议位于TCP层之上,即客户端和Redis实例保持双工的连接。
串行的请求响应模式(ping-pong)
串行化是最简单模式,客户端与服务器端建立长连接
连接通过心跳机制检测(ping-pong)
ack应答
客户端发送请求,服务端响应,客户端收到响应后,再发起第二个请求,服务器端再响应。
telnet和redis-cli 发出的命令 都属于该种模式
特点:
- 有问有答
- 耗时在网络传输命令
- 性能较低
双工的请求响应模式(pipeline)
批量请求,批量响应。请求响应交叉进行,不会混淆(TCP双工)
- pipeline的作用是将一批命令进行打包,然后发送给服务器,服务器执行完按顺序打包返回。
- 通过pipeline,一次pipeline(n条命令)=一次网络时间 + n次命令时间
原子化的批量请求响应模式(事务)
Redis可以利用事务机制批量执行命令。后面会详细讲解。
发布订阅模式(pub/sub)
发布订阅模式是:一个客户端触发,多个客户端被动接收,通过服务器中转。后面会详细讲解。
脚本化的批量执行(lua)
客户端向服务器端提交一个lua脚本,服务器端执行该脚本。后面会详细讲解。
请求数据格式
Redis客户端与服务器交互采用序列化协议
(RESP)。请求以字符串数组的形式来表示要执行命令的参数。Redis使用命令特有(command-specific)数据类型作为回复。
Redis通信协议的主要特点有:
- 客户端和服务器通过 TCP 连接来进行数据交互, 服务器默认的端口号为 6379 。
- 客户端和服务器发送的命令或数据一律以 \r\n (CRLF)结尾。
在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。简单,高效,易读。
内联格式
可以使用telnet给Redis发送命令,首字符为Redis命令名的字符,格式为 str1 str2 str3…
[root@localhost bin]# telnet 127.0.0.1 6379
Trying 127.0.0.1…
Connected to 127.0.0.1.
Escape character is ‘^]’.
ping
+PONG
exists name
:1
规范格式(redis-cli)
1、间隔符号,在Linux下是\r\n,在Windows下是\n
2、简单字符串 Simple Strings, 以 "+“加号 开头
3、错误 Errors, 以”-"减号 开头
4、整数型 Integer, 以 “:” 冒号开头
5、大字符串类型 Bulk Strings, 以 "$"美元符号开头,长度限制512M
6、数组类型 Arrays,以 "*"星号开头
用SET命令来举例说明RESP协议的格式。
redis> SET mykey Hello
“OK”
实际发送的请求数据:
*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$5\r\nHello\r\n
*3
$3
SET
$5
mykey
$5
Hello
实际收到的响应数据:
+OK\r\n
命令处理流程
整个流程包括:服务器启动监听、接收命令请求并解析、执行命令请求、返回命令回复等。
Server启动时监听socket
- 启动调用 initServer方法
- 创建eventLoop(事件机制)
- 注册时间事件处理器
- 注册文件事件(socket)处理器
- 监听 socket 建立连接
建立Client
- redis-cli建立socket
- redis-server为每个连接(socket)创建一个 Client 对象
- 创建文件事件监听socket
- 指定事件处理函数
读取socket数据到输入缓冲区
从client中读取客户端的查询缓冲区内容。
解析获取命令
- 将输入缓冲区中的数据解析成对应的命令
- 判断是单条命令还是多条命令并调用相应的解析器解析
执行命令
解析成功后调用processCommand 方法执行命令,如下图:
大致分三个部分:
- 调用 lookupCommand 方法获得对应的 redisCommand
- 检测当前 Redis 是否可以执行该命令
- 调用 call 方法真正执行命令
协议响应格式
- 状态回复
对于状态,回复的第一个字节是“+”OK
- 错误回复
对于错误,回复的第一个字节是“ - ”
- -ERR unknown command ‘foobar’
- -WRONGTYPE Operation against a key holding the wrong kind of value
- 整数回复
对于整数,回复的第一个字节是“:”":6"
- 批量回复
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
器解析
执行命令
解析成功后调用processCommand 方法执行命令,如下图:
大致分三个部分:
- 调用 lookupCommand 方法获得对应的 redisCommand
- 检测当前 Redis 是否可以执行该命令
- 调用 call 方法真正执行命令
协议响应格式
- 状态回复
对于状态,回复的第一个字节是“+”OK
- 错误回复
对于错误,回复的第一个字节是“ - ”
- -ERR unknown command ‘foobar’
- -WRONGTYPE Operation against a key holding the wrong kind of value
- 整数回复
对于整数,回复的第一个字节是“:”":6"
- 批量回复
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Go)
[外链图片转存中…(img-DVKnVhmH-1713467498240)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!