安装并配置-Kubernetes-集群 (安装并配置mysql的实验总结)
日常开发中,基于自然支持散布式锁,大家在线上散布式名目中都经常使用过Redis锁。本文重要针对日常开发中加锁环节中某些意外场景启动解说与剖析。本文解说示例代码都在名目目录下
RedisLockTest
类中。
一、义务超时,锁曾经过时
这个意外场景说瞎话出现概率很低,大局部状况下加锁时义务口头都会很快,锁还没到期,义务自己就会删除锁。除非说义务调用第三方接口不稳固造成超时、数据库查问突然变得十分慢就或许会发生这个意外场景。
那怎样处置这个意外嘞?大局部人或许都会回答减少一个定时义务,在定时义务内检测锁快过时时,启动续期操作。OK,这么做如同是可以处置这个意外,那么博主在这里给出自己的见地。
1.1先说一个暴论:假设预想到有这类意外发生,为什么不在加锁时,就把加锁过时期间设置大一点
不论所续期还是增大加锁时长,都会造成一个疑问,其余线程会迟迟失掉不到锁,不时被阻塞。那结果都一样,为什么不间接增大加锁期间?
1.2间接不设置过时期间,义务不口头完,不监禁锁
假设在加锁时就不设置过时期间的话,通常上如同是可以处置这个疑问,义务不口头完,锁就不会监禁。然而作为程序员,总觉得哪里怪怪的,义务不口头完,锁就不会监禁!
这个打算程序反常的状况下,可以满足咱们的要求,然而一旦出现意外将造成锁不可监禁的结果,也就是说只需咱们处置这个锁在意外场景下不可监禁的疑问,这个打算还是OK的。博主这里间接给出打算:
在不设置过时期间的加锁操作成功时,给一个自动过时期间比如三十秒,同时启动一个定时义务,给咱们的锁启动智能续期,每隔
自动过时期间/3
秒后口头一次性续期操作,出现锁残余时长小于
自动过时期间/2
就从新赋值过时时长为三十秒。这样的话,可以保障锁必定由义务口头完能力监禁,当程序意外出现时,依然能保障锁会在三十秒内监禁。
![Kubernetes](https://tyye.cn/zb_users/theme/tpure/style/images/lazyload.png)
1.3设置过时期间,义务不口头完,不监禁锁
这个打算实质上与打算二的处置打算相反,还是启动定时义务启动续期操作,流程这里不做多余讲述。须要留意的就是加锁指定过时期间会比拟合乎咱们的客观认知。实践上他的底层逻辑跟打算二相反,无非就是定时义务口头距离,锁残余时长续期判别要依据过时期间来计算。
「综合来看:打算三会最适合,合乎咱们的客观认知,跟咱们之前对Redis的经常使用逻辑较为相近。」
二、线程B加锁口头中未监禁锁,线程A监禁了线程B的锁
这里回到这个意外场景自身,咱们可以给每个线程设置恳求ID,加锁成功将恳求ID设置为加锁key的对应value,线程监禁锁时须要判别以后线程的恳求ID与加锁key的对应value能否相反,相反则可以监禁锁,不相反则不准许监禁。
三、线程加锁成功后继续放开加锁
处置形式有两种,一是修正方法内的加锁逻辑,不要加同一把锁,修正方法a内的加锁key称号。二是针对加锁逻辑做修正,成功可重入性。
这里便捷引见如何成功可重入性,给每个线程设置恳求ID,加锁成功将恳求ID设置为加锁key的对应value,针对同一个线程的重复加锁,判别以后线程已存在恳求ID的状况下,恳求ID间接与加锁key的对应value相比拟,相反则间接前往加锁成功。
四、代码通常
4.1加锁智能续期通常
设置锁过时期间为10秒,而后该义务口头15秒,代码如下:
@Slf4j@SpringBootTest@RunWith(SpringRunner.class)publicclassRedisLockTest{@AutowiredprivateRedisLockredisLock;@Test@TestpublicvoidredisLockNeNewTest(){Stringkey="test";try{log.info("---放开加锁");if(redisLock.lock(key,10)){//模拟义务口头15秒log.info("---加锁成功");Thread.sleep(15000);log.info("---口头终了");}}catch(Exceptione){log.error(e.getMessage(),e);}finally{redisLock.unLock(key);}}}
口头如下:
可以看出就算义务口头超越过时期间也能经过智能续期让代码反常口头。
4.2多线程下其余线程不可独特放开到同一把锁通常
启动两个线程,线程A先加锁,线程B后桎梏
@TestpublicvoidredisLockReleaseSelfTest()throwsIOException{newThread(()->{Stringkey="test";try{log.info("---放开加锁");if(redisLock.lock(key,10)){//模拟义务口头15秒log.info("---加锁成功");Thread.sleep(15000);log.info("---口头终了");}else{log.info("---加锁失败");}}catch(Exceptione){log.error(e.getMessage(),e);}finally{redisLock.unLock(key);}},"thread-A").start();newThread(()->{Stringkey="test";try{Thread.sleep(100L);log.info("---放开加锁");if(redisLock.lock(key,10)){//模拟义务口头15秒log.info("---加锁成功");Thread.sleep(15000);log.info("---口头终了");}else{log.info("---加锁失败");}}catch(Exceptione){log.error(e.getMessage(),e);}finally{redisLock.unLock(key);}},"thread-B").start();System.in.read();}
结果如下:
可以看到,线程A先放开到锁,线程B后放开锁,结果线程B放开加锁失败。
4.3锁得可重入性通常
以后线程加锁成功后,在线程口头中继续放开同一把锁,代码如下:
@TestpublicvoidredisLockReEntryTest(){Stringkey="test";try{log.info("---放开加锁");if(redisLock.lock(key,10)){//模拟义务口头15秒log.info("---加锁第一次性成功");if(redisLock.lock(key,10)){//模拟义务口头15秒log.info("---加锁第二次成功");Thread.sleep(15000);log.info("---加锁第二次口头终了");}else{log.info("---加锁第二次失败");}Thread.sleep(15000);log.info("---加锁第一次性口头终了");}else{log.info("---加锁第一次性失败");}}catch(Exceptione){log.error(e.getMessage(),e);}finally{redisLock.unLock(key);}}
结果如下:
4.4加锁逻辑解说
间接贴出本文最外围RedisLock类所有代码:
@Slf4j@ComponentpublicclassRedisLock{@AutowiredpublicRedisTemplateredisTemplate;/***自动锁过时期间20秒*/publicstaticfinalIntegerDEFAULT_TIME_OUT=30;/***保留线程id-ThreadLocal*/privateThreadLocal<String>stringThreadLocal=newThreadLocal<>();/***保留定时义务(watch-dog)-ThreadLocal*/privateThreadLocal<ExecutorService>executorServiceThreadLocal=newThreadLocal<>();/***加锁,不指定过时期间**@paramkeykey称号*@returnboolean*/publicbooleanlock(Stringkey){returnlock(key,null);}/***加锁**@paramkeykey称号*@paramtimeout过时期间*@returnboolean*/publicbooleanlock(Stringkey,Integertimeout){IntegertimeoutTmp;if(timeout==null){timeoutTmp=DEFAULT_TIME_OUT;}else{timeoutTmp=timeout;}StringnanoId;if(stringThreadLocal.get()!=null){nanoId=stringThreadLocal.get();}else{nanoId=IdUtil.nanoId();stringThreadLocal.set(nanoId);}RedisScript<Long>redisScript=newDefaultRedisScript<>(buildLuaLockScript(),Long.class);Longexecute=(Long)redisTemplate.execute(redisScript,Collections.singletonList(key),nanoId,timeoutTmp);booleanflag=execute!=null&&execute==1;if(flag){ScheduledExecutorServicescheduledExecutorService=Executors.newSingleThreadScheduledExecutor();executorServiceThreadLocal.set(scheduledExecutorService);scheduledExecutorService.scheduleWithFixedDelay(()->{RedisScript<Long>renewRedisScript=newDefaultRedisScript<>(buildLuaRenewScript(),Long.class);Longresult=(Long)redisTemplate.execute(renewRedisScript,Collections.singletonList(key),nanoId,timeoutTmp);if(result!=null&&result==2){ThreadUtil.shutdownAndAwtTermination(scheduledExecutorService);}},0,timeoutTmp/3,TimeUnit.SECONDS);}returnflag;}/***监禁锁**@paramkeykey称号*@returnboolean*/publicbooleanunLock(finalStringkey){StringnanoId=stringThreadLocal.get();RedisScript<Long>redisScript=newDefaultRedisScript<>(buildLuaUnLockScript(),Long.class);Longexecute=(Long)redisTemplate.execute(redisScript,Collections.singletonList(key),nanoId);booleanflag=execute!=null&&execute==1;if(flag){if(executorServiceThreadLocal.get()!=null){ThreadUtil.shutdownAndAwaitTermination(executorServiceThreadLocal.get());}}returnflag;}privateStringbuildLuaLockScript(){return"""localkey=KEYS[1]localvalue=ARGV[1]localtime_out=ARGV[2]localresult=redis.call('get',key)ifresult==valuethenreturn1;endlocallock_result=redis.call('setnx',key,value)iftonumber(lock_result)==1thenredis.call('expire',key,time_out)return1;elsereturn0;end""";}privateStringbuildLuaUnLockScript(){return"""localkey=KEYS[1]localvalue=ARGV[1]localresult=redis.call('get',key)ifresult~=valuethenreturn0;elseredis.call('del',key)endreturn1;""";}privateStringbuildLuaRenewScript(){return"""localkey=KEYS[1]localvalue=ARGV[1]localtimeout=ARGV[2]localresult=redis.call('get',key)ifresult~=valuethenreturn2;endlocalttl=redis.call('ttl',key)iftonumber(ttl)<tonumber(timeout)/2thenredis.call('expire',key,timeout)return1;elsereturn0;end""";}}
加锁逻辑:这里我把加锁逻辑合成成三步展现给大家
解锁逻辑:这里我把解锁逻辑合成成两步展现给大家
五、总结
其实本文得外围逻辑有许多都是参考Redission客户端而写,关于这些经常出现得坑点,博主联合自身思索,业界常识总结并自己成功一个散布式锁得工具类。宿愿大家看了有所收获,对日常业务中Redis散布式锁的经常使用能有更深的了解。
Kubernetes(K8S)入门与安装配置
Kubernetes是一个跨主机集群的开源的容器调度平台,它可以自动化应用容器的部署、扩展和操作,提供以容器为中心的基础架构。谷歌旗下开源软件,江湖人称K8S。
上图是一个通过K8S搭建的集群环境,采用三台物理机搭建(三台机器是K8S搭建集群的最低要求),我先简单介绍一下几个重点名词。
Centos7Master*1(注意必须是双核以上的CPU,否则无法初始化K8S)
Centos7Node*2
将文件上传至该目录
网盘地址:来自:网络网盘
K8S安装和创建集群终极教程(单master多worker)
本文会以 最简单 、 最直接 、 最完整 的方式记录kubernetes(下面统称K8S)单master多工作节点(worker nodes)的集群步骤
首先要简单了解一下本文的3个核心概念:
内存建议至少4G
问:如何查看主机名?
答:执行命令hostname
问:如何修改主机名?
答:永久生效的做法:执行命令vi /etc/hostname,把第一行去掉(不能注释掉,要去掉),然后重新写上自定义的主机名(注意命名规范),保存并重启后生效;
临时生效的做法:执行以下命令
问:如何查看MAC地址?
答:执行命令ip link,然后看你的第一网卡
问:如何查看product_uuid?
答:执行命令sudo cat /sys/class/dmi/id/product_uuid
注意-这个端口范围是我们创建服务的端口必须要设置的一个范围(如果设置范围以外的会有限制提示并创建失败),这是K8S规定的。
另外,如果你要直接关闭防火墙可以执行
⑥必须禁用Swap
Swap total大于0,说明Swap分区是开启的
问:如何关闭Swap?
答:编辑文件/etc/fstab,在swap行前面加上#号注释, 保存并重启服务器
再次查看分区状态,已生效
常见的容器引擎(Container runtime,简称runtime):
本文使用的容器引擎是Docker
安装完成后查看版本:
当出现可能跟Docker引擎相关的奇怪异常时可以尝试把Docker卸载干净并重新安装,但一定要注意镜像、容器、卷或配置文件这些是否需要备份。
下面记录卸载Docker引擎的步骤:
①卸载 Docker Engine、CLI 和 Containerd 包:
②主机上的映像、容器、卷或自定义配置文件不会自动删除。删除所有镜像、容器和卷:
③配置文件如果有不合法的字符时会导致启动失败,我们需要将其删除然后重建
此时Docker引擎已卸载干净
官网用的是谷歌的yum源,因为国内是连不上的,所以这里替换成阿里提供的yum源
①安装
从安装信息中可以看到版本号是1.22
Installing:
kubeadm x86_64 1.22.4-0 kubernetes 9.3 M
kubectl x86_64 1.22.4-0 kubernetes 9.7 M
kubelet x86_64 1.22.4-0 kubernetes 20 M
②启动
这就是一个驱动程序,注意cgroup和cgroupfs不要混淆了
引用官方的一段话
“由于 kubeadm 把 kubelet 视为一个系统服务来管理,所以对基于 kubeadm 的安装, 我们推荐使用 systemd 驱动,不推荐 cgroupfs 驱动。”
kubeadm默认是使用systemd 驱动,而我们的Docker默认驱动是cgroupfs(docker info可以查看),所以需要将Docker的驱动改成systemd
①编辑Docker配置文件
②重启Docker服务
再次docker info查看驱动信息已变成了systemd
工作节点(worker nodes)的最小配置就到这里了
①镜像源参数说明
默认情况下, kubeadm 会从 仓库拉取镜像,国内是拉不了的。官方文档明确表示允许你使用其他的 imageRepository 来代替 。
--image-repository 你的镜像仓库地址
接下来我找了一些国内的镜像源,并简单做了下分析
综合上述统计,我选择阿里云的镜像源
②ip地址范围参数说明
--pod-network-cidr =192.168.0.0/16
注意:如果192.168.0.0/16已经在您的网络中使用,您必须选择一个不同的pod网络CIDR,在上面的命令中替换192.168.0.0/16。
集群初始化命令:
因为我用的是展示机器,所以这里把完整的执行信息都贴出来方便查阅,平时工作中一定要注意保护好敏感的信息(我的ip地址范围是自定义的便于下面的功能展示,另外初次init需要下载镜像文件,一般需要等几分钟)
如上所示,集群初始化成功,此时一定要注意看上面执行结果最后的那部分操作提示,我已用标明了初始化成功后还需要执行的3个步骤
注意:如果init成功后发现参数需要调整,可以执行kubeadm reset,它的作用是尽最大努力恢复kubeadm init 或者 kubeadm join所做的更改。
To start using your cluster, you need to run the following as a regular user:
翻译:开始使用集群前,如果你是普通用户(非root),你需要执行以下的命令:
Alternatively, if you are the root user, you can run:
翻译:或者,如果你使用的是root,你可以执行以下命令:
(注意:export只是临时生效,意味着每次登录你都需要执行一次)
网络配置配的就是Pod的网络,我的网络插件选用calico
cidr就是ip地址范围,如果您使用 pod CIDR 192.168.0.0/16,请跳到下一步。
但本文中使用的pod CIDR是192.100.0.0/16,所以我需要取消对清单中的 CALICO_IPV4POOL_CIDR 变量的注释,并将其设置为与我选择的 pod CIDR 相同的值。(注意一定要注意好格式,注意对齐)
可根据需求自定义清单,一般不需要的就直接跳过这步
在所有的工作节点上执行join命令(复制之前初始化成功后返回的加入集群命令到所有的工作节点执行即可)
master上查看所有节点的状态
到这里集群已经创建完成
最后我再安装K8S的可视化界面kubernetes-dashboard,方便我们日常使用
①下载yaml文件
②修改yaml文件,新增type和nodePort,使服务能够被外部访问
③安装并查看运行情况
④新建用户
文件创建完成后保存并apply
⑤获取Token,用于界面登录
⑥登录dashboard
192.168.189.128是我的master服务器ip,另外要注意必须使用https,并且不能使用ie内核模式
复制⑤生成的token到输入框,点击登录
dashboard安装配置完成
问:如何在查看资源情况?
答:在master上执行以下命令可查看资源情况(-o wide是显示更详细的信息),
①查看所有节点
②查看所有命名空间
③查看命名空间下的pod
④查看所有命名空间的pod
⑤实时查看查看命名空间下的pod运行情况
问:kubeadm join 出现异常[ERROR Port-]: Port is in use,如何解决?
答:这是因为你之前join失败过了,需要先执行kubeadm reset再重新join
问:虚拟机上测试时网卡突然消失如何解决(题外问题记录)?
答:
①确认丢失的网卡信息,ens开头(可选步骤)
ifconfig -a
②执行以下命令解决
问:如何查看K8S版本?
答:kubectl version
问:join命令忘记或者过期了怎么办?
答:
生成永不过期的
生成时效24小时的
问:Pod不断重启并且无其它报错信息时怎么办?
答:这种情况通常是因为你的集群中只有master,没有worker节点,master的创建默认是有污点的,即不允许调度新的Pod,如果你需要(当然这并不推荐),就需要删除 master 上的污点。删除污点可以执行以下命令,
它应该返回以下内容。
免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。