开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

(四)Nacos开发教程之SpringCloud集成服务注册

发表于 2021-11-17

这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战

前情回顾

我们在之前的文章中,已经讲过了Nacos的安装部署,对Nacos也有了初步的认识,从这篇文章中,我们就要开始在项目中去使用Nacos了。

接下来一起来学习一下SpringCloud项目中集成Nacos组件。

SpringCloud集成Nacos服务注册功能

我们需要先创建一个SpringCloud项目,随后再从这个项目基础上进行Nacos组件的扩展。

本次项目通过IDEA创建。

创建一个新项目

新建一个Maven项目,File-New Project。

image.png

转到这个页面,点击Next。

image.png

如图的项目路径。

image.png

引入Nacos相关jar包

创建完成项目,接下来就是要引入相应的jar包了。

修改pom文件,如下代码:

1
2
3
4
5
xml复制代码<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>Hoxton.SR9</version>
</dependency>

修改Application类,引入@EnableDiscoveryClient注解,开启nacos服务治理功能

1
2
3
4
5
6
7
8
9
less复制代码@EnableDiscoveryClient
@SpringBootApplication
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class);
}

}

配置Nacos相关参数

修改application.yml文件,增加Nacos的相关参数

127.0.0.1:8848;这个服务是指安装的nacos服务器。

1
2
3
4
5
6
7
8
9
10
yaml复制代码server:
port: 8080

spring:
application:
name: test
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848

启动项目

运行Application类。

查看服务是否被注册成功

让我们登录nacos服务网站上面查看,该服务是否被注册成功了。

在以下菜单中可以查看相应的服务是否注册成功。

image.png

总结

今天我们来学习了Nacos服务治理功能的基础使用,当然了,Nacos服务治理还有一些其他的配置项,这个大家可以去看看官网文档。

一定要学着如何看官方文档,这个很重要。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

学会这 10000 个段子,成为 IT 职场幽默达人。Pyt

发表于 2021-11-17

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」

现代职场达人,应该做到 有情、有趣、有用、有品。好了,去掉 “有” 字你就成了。那如何成为职场幽默达人呢,咱需要一定的素材也就是段子,多看段子才能多说段子,并且还能说高级段子。

爬取前的分析工作

本次爬取的目标网站为:www.wllxy.net/gxqmlist.as…

学会这 10000 个段子,成为 IT 职场幽默达人。Python 爬虫小课 8-9

爬取的难度整体不大,分析工作基本可以省略,毕竟对于已经学习到这里的你来说,requests 已经掌握了 7~8 成火候了。

本文重点在给你介绍一下 requests 中的代理相关内容。

爬虫基础知识普及时间

啥是代理

代理就是代理网络用户去取得网络信息。大白话就是把用户本身的 IP 和其他网络相关信息想办法隐藏起来,让目标站点获取不到。

代理的种类

高匿名代理
高匿名代理会将数据包原封不动的转发,从目标网站的服务器来看,就像是一个真实的普通用户在访问,而且使用的 IP 也是代理服务器的 IP 地址,可以完美的隐藏用户原 IP,所以高匿名代理是爬虫程序代理首选。

普通匿名代理
普通匿名代理会在数据包上做一些改动,加入 HTTP 头固定参数。正式因为固定参数的存在导致目标服务器可以追踪的用户的真实 IP,反爬度高的网站很容易判断用户是否是爬虫程序。

透明代理
这个就不用细说了,代了白代,目标服务器很容易检测出来。

在代理的种类上,有时还会按照 HTTP 和 HTTPS 区分,现在的情况下大多数网站已经升级到 HTTPS 协议了,但 HTTP 并没有被舍弃掉,一般还可以进行爬取。这里需要注意下 HTTPS 需要多次握手,速度比较慢,使用代理之后就会变的更慢,所以以后能爬 HTTP 协议的网站就尽量爬取 HTTP 协议,包括使用代理也是。

requests 使用代理

requests 支持多种代理方式,设置办法也非常简单,通过为任意请求方法提供 proxies 参数来配置单个请求,例如下述代码(关于代理本部分为大家做一下介绍即可,因为本案例实操中发现不需要代理也可以轻松完成目标数据获取)

1
2
3
4
5
6
7
8
python复制代码import requests

proxies = {
"http": "http://10.10.1.10:3128",
"https": "http://10.10.1.10:1080",
}

requests.get("http://example.org", proxies=proxies)

注意代理为一个字典参数,可以包含 HTTP 或者 HTTPS 中的任意一项。

还要注意 requests 是支持 SOCKS 代理的,知识点难度的问题,不做展开讲解。

代码编写时间

代理相关的知识已经介绍完毕,下面就进入实际编码环节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
python复制代码import requests
import re
import threading

headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36"}

flag_page = 0

# 正则表达式解析环节,最终需要将三个元组进行合并,使用 zip 函数
def anay(html):
# 正则表达式通过三次进行匹配。可以想办法提高效率,留给大家。
pattern = re.compile(
'<td class="diggtdright">[.\s]*<a href=".*?" target="_blank">\s*(.*?)</a>')
titles = pattern.findall(html)
times = re.findall('发布时间:(\d+[-]\d+[-]\d+)', html)
diggt = re.findall('得票:(\d+)人次', html)
return zip(titles, times, diggt)

def save(data):
with open("newdata.csv", "a+", encoding="utf-8-sig") as f:
f.write(f"{data[0]},{data[1]},{data[2]}\n")

def get_page():
global flag_page
while flag_page < 979:
flag_page += 1
url = f"http://www.wllxy.net/gxqmlist.aspx?p={flag_page}"
print(f"正在爬取{url}")
r = requests.get(url=url, headers=headers)

ok_data = anay(r.text)
for data in ok_data:
print(data)
# 保存到本地方法自行完成
# save(data)

if __name__ == "__main__":
for i in range(1, 6):
t = threading.Thread(target=get_page)
t.start()

注意,zip 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。zip 返回的是一个对象。如需展示列表,需手动 list() 转换。

如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同。

其余内容涉及数据保存部分,即上述代码中的 save 函数,可以自行编写。

最后在啰嗦两句

本系列爬虫小课主要介绍的是 requests 库,学习完毕,你就能对 requests 库有一个比较完善的认知。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

阿里IM技术分享(六):闲鱼亿级IM消息系统的离线推送到达率

发表于 2021-11-17

本文由阿里闲鱼技术团队逸昂分享,原题“消息链路优化之弱感知链路优化”,有修订和改动,感谢作者的分享。

1、引言

闲鱼的IM消息系统作为买家与卖家的沟通工具,增进理解、促进信任,对闲鱼的商品成交有重要的价值,是提升用户体验最关键的环节。

然而,随着业务体量的快速增长,当前这套消息系统正面临着诸多急待解决的问题。

以下几个问题典型最为典型:

  • 1)在线消息的体验提升;
  • 2)离线推送的到达率;
  • 3)消息玩法与消息底层系统的耦合过强。

经过评估,我们认为现阶段离线推送的到达率问题最为关键,对用户体验影响较大。

本文将要分享的是闲鱼IM消息在解决离线推送的到达率方面的技术实践,内容包括问题分析和技术优化思路等,希望能带给你启发。

2、系列文章

本文是系列文章的第6篇,总目录如下:

  • 《阿里IM技术分享(一):企业级IM王者——钉钉在后端架构上的过人之处》
  • 《阿里IM技术分享(二):闲鱼IM基于Flutter的移动端跨端改造实践》
  • 《阿里IM技术分享(三):闲鱼亿级IM消息系统的架构演进之路》
  • 《阿里IM技术分享(四):闲鱼亿级IM消息系统的可靠投递优化实践》
  • 《阿里IM技术分享(五):闲鱼亿级IM消息系统的及时性优化实践》
  • 《阿里IM技术分享(六):闲鱼亿级IM消息系统的离线推送到达率优化》(* 本文)

3、通信链路类型的划分

从数据通信链接的技术角度,我们根据闲鱼客户端是否在线,将整体消息链路大致分为强感知链路和弱感知链路。

强感知链路由以下子系统或模块:

  • 1)发送方客户端;
  • 2)idleapi-message(闲鱼的消息网关);
  • 3)heracles(闲鱼的消息底层服务);
  • 4)accs(阿里自研的长连接通道);
  • 5)接收方客户端组成。

整条链路的核心指标在于端到端延迟和消息到达率。

强感知链路中的双方都是在线的,消息到达客户端就可以保证接收方感知到。强感知链路的主要痛点在消息的端到端延迟。

弱感知链路与强感知链路的主要不同在于: 弱感知链路的接收方是离线的,需要依赖离线推送这样的方式送达。

因此弱感知链路的用户感知度不强,其核心指标在于消息的到达率,而非延迟。

所以当前阶段,优化弱感知链路的重点也就是提升离线消息的到达率。换句话说,提升离线消息到达率问题,也就是优化弱感知链路本身。

4、消息系统架构概览

下图一张整个IM消息系统的架构图,感受下整体链路:

如上图所示,各主要组件和子系统分工如下:

  • 1)HSF是一个远程服务框架,是dubbo的内部版本;
  • 2)tair是阿里自研的分布式缓存框架,支持 memcached、Redis、LevelDB 等不同存储引擎;
  • 3)agoo是阿里的离线推送中台,负责整合不同厂商的离线推送通道,向集团用户提供一个统一的离线推送服务;
  • 4)accs是阿里自研的长连接通道,为客户端、服务端的实时双向交互提供便利;
  • 5)lindorm是阿里自研的NoSQL产品,与HBase有异曲同工之妙;
  • 6)域环是闲鱼消息优化性能的核心结构,用来存储用户最新的若干条消息。

强感知链路和弱感知链路在通道选择上是不同的:

  • 1)强感知链路使用accs这个在线通道;
  • 2)弱感知链路使用agoo这个离线通道。

5、弱感知链路到底怎么定义

通俗了说,弱感知链路指的就是离线消息推送系统。

相比较于在线消息和端内推送(也就是上面说的强感知链路),离线推送难以确保被用户感知到。

典型的情况包括:

  • 1)未发送到用户设备:即推送未送达用户设备,这种情况可以从通道的返回分析;
  • 2)发送到用户设备但没有展示到系统通知栏:闲鱼曾遇到通道返回成功,但是用户未看到推送的案例;
  • 3)展示到通知栏,并被系统折叠:不同安卓厂商对推送的折叠策略不同,被折叠后,需用户主动展开才能看到内容,触达效果明显变差;
  • 4)展示到通知栏,并被用户忽略:离线推送的点击率相比于在线推送更低。

针对“1)未发送到用户设备”,原因有:

  • 1)离线通道的token失效;
  • 2)参数错误;
  • 3)用户关闭应用通知;
  • 4)用户已卸载等。

针对“3)展示到通知栏,并被系统折叠”,原因有:

  • 1)通知的点击率;
  • 2)应用在厂商处的权重;
  • 3)推送的数量等。

针对“4)展示到通知栏,并被用户忽略”,原因有:

  • 1)用户不愿意查看推送;
  • 2)用户看到了推送,但是对内容不感兴趣;
  • 3)用户在忙别的事,无暇处理。

**总之:**以上这些离线消息推送场景,对于用户来说感知度不高,我们也便称之为弱感知链路。

6、弱感知链路的逻辑构成

我们的弱感知链路分为3部分,即:

  • 1)系统;
  • 2)通道;
  • 3)用户。

共包含了Hermes、agoo、厂商、设备、用户、承接页这几个环节。具体如下图所示。

从推送的产生到用户最终进入APP,共分为如下几个步骤:

  • 步骤1:Hermes是闲鱼的用户触达系统,负责人群管理、内容管理、时机把控,是整个弱感知链路的起点。;
  • 步骤2:agoo是阿里内部承接离线推送的中台,是闲鱼离线推送能力的基础;
  • 步骤3:agoo实现离线推送依靠的是厂商的推送通道(如:苹果的apns通道、Google的fcm通道、及国内各厂商的自建通道;
  • 步骤4:通过厂商的通道,推送最终出现在用户的设备上,这是用户能感知到推送的前提条件;
  • 步骤5:如果用户刚巧看到这条推送,推送的内容也很有趣,在用户的主动点击下会唤起APP,打开承接页,进而给用户展示个性化的商品。

经过以上5个步骤,至此弱感知链路就完成了使命。

7、弱感知链路面临的具体问题

弱感知链路的核心问题在于:

  • 1)推送的消息是否投递给了用户;
  • 2)已投递到的消息用户是否有感知。

这对应推送的两个阶段:

  • 1)推送消息是否已到达设备;
  • 2)用户是否查看推送并点击。

其中: 到达设备这个阶段是最基础的,也是本次优化的核心。

我们可以将每一步的消息处理量依次平铺,展开为一张漏斗图,从而直观的查看链路的瓶颈。

漏斗图斜率最大的地方是优化的重点,差异小的地方不需要优化:

通过分析以上漏斗图,弱感知链路的优化重点在三个方面:

  • 1)agoo受理率:是指我们发送推送请到的数量到可以通过agoo(阿里承接离线推送的中台)转发到厂商通道的数量之间的漏斗;
  • 2)厂商受理率:是指agoo中台受理的量到厂商返回成功的量之间的漏斗;
  • 3)Push点击率:也就通过以上通道最终已送到到用户终端的消息,是否最终转化为用户的主动“点击”。

有了优化方向,我们来看看优化手段吧。

8、我们的技术优化手段

跟随推送的视角,顺着链路看一下我们是如何进行优化的。

8.1 agoo受理率优化

用户的推送,从 Hermes 站点搭乘“班车”,驶向下一站: agoo。

这是推送经历的第一站。到站一看,傻眼了,只有不到一半的推送到站下车了。这是咋回事嘞?

这就要先说说 agoo 了,调用 agoo 有两种方式:

  • 1)指定设备和客户端,agoo直接将推送投递到相应的设备;
  • 2)指定用户和客户端,agoo根据内部的转换表,找到用户对应的设备,再进行投递。

我们的系统不保存用户的设备信息。因此,是按照用户来调用agoo的。

同时: 由于没有用户的设备信息,并不知道用户是 iOS 客户端还是 Android 客户端。工程侧不得不向 iOS 和 Android 都发送一遍推送。虽然保证了到达,但是,一半的调用都是无效的。

为了解这个问题: 我们使用了agoo的设备信息。将用户转换设备这一阶段提前到了调用 agoo 之前,先明确用户对应的设备,再指定设备调用 agoo,从而避免无效调用。

agoo调用方式优化后,立刻剔除了无效调用,agoo受理率有了明显提升。

至此: 我们总算能对 agoo 受理失败的真正原因做一个高大上的分析了。

根据统计: 推送被 agoo 拒绝的主要原因是——用户关闭了通知权限。同时,我们对 agoo 调用数据的进一步分析发现——有部分用户找不到对应的设备。 优化到此,我们猛然发现多了两个问题。

那就继续优化呗:

  • 1)通知体验优化,引导打开通知权限;
  • 2)与agoo共建设备库,解决设备转换失败的问题。

这两个优化方向又是一片新天地,我们择日再聊。

8.2 厂商推送通道受理率优化

推送到达 agoo ,分机型搭乘厂商“专列”,驶向下一站:用户设备。

这是推送经历的第二站。出站查票,发现竟然超员了。

于是乎: 我们每天有大量推送因为超过厂商设定的限额被拦截。

为什么会这样呢?

实际上: 提供推送通道的厂商(没错,各手机厂商的自家推送通道良莠不齐),为了保证用户体验,会对每个应用能够推送的消息总量进行限制。

对于厂商而言,这个限制会根据推送的类型和应用的用户规模设定——推送主要分为产品类的推送和营销类的推送。

厂商推送通道对于不同类型消息的限制是:

  • 1)对于产品类推送,厂商会保证到达;
  • 2)对于营销类推送,厂商会进行额度限制;
  • 3)未标记的推送,默认作为营销类推送对待。

我们刚好没有对推送进行标记,因此触发了厂商的推送限制。

这对我们的用户来说,会带来困扰。闲鱼的交易,很依赖买卖家之间的消息互动。这部分消息是需要确保到达的。

同样: 订单类的消息、用户的关注,也需要保证推送给用户。

根据主流厂商的接口协议,我们将推送的消息分为以下几类,并进行相应标记:

  • 1)即时通讯消息;
  • 2)订单状态变化;
  • 3)用户关注内容;
  • 4)营销消息这几类。

同时,在业务上,我们也进行了推送的治理——将用户关注度不高的消息,取消推送,避免打扰。

经过这些优化,因为超过厂商限额而被拦截的推送实现了清零。

8.3 Push点击率优化

通过优化agoo受理率、厂商受理率,我们解决了推送到达量的瓶颈。但即使消息被最终送达,用户到底点击了没有?这才是消息推送的根本意义所在。

于是,在日常的开发测试过程中,我们发现了推送的两个体验问题:

  • 1)用户点击Push有开屏广告;
  • 2)营销Push也有权限校验,更换用户登陆后无法点击。

对于开屏广告功能,我们增加了Push点击跳过广告的能力。

针对Push的权限校验功能,闲鱼根据场景做了细分:

  • 1)涉及个人隐私的推送,保持权限校验不变;
  • 2)营销类的推送,放开权限校验。

以上是点击体验的优化,我们还需要考虑用户的点击意愿。

用户点击量与推送的曝光量、推送素材的有趣程度相关。推送的曝光量又和推送的到达量、推送的到达时机有关。

具体的优化手段是:

  • 1)在推送内容上:我们需要优化的是推送的时机和相应的素材;
  • 2)在推送时机上:算法会根据用户的偏好和个性化行为数据,计算每个用户的个性化推送时间,在用户空闲的时间推送(避免在不合适的时间打扰用户,同时也能提升用户看到推送的可能性)。
  • 3)在推送素材上:算法会根据素材的实时点击反馈,对素材做实时赛马。只发用户感兴趣的素材,提高用户点击意愿。

9、实际优化效果

通过以上我们的分析和技术优化手段,整体弱推送链路链路有了不错的提升,离线消息的到达率相对提升了两位数。

10、写在最后

本篇主要和大家聊的是只是IM消息系统链路中的一环——弱感知链路的优化,落地到到具体的业务也就是离线消息送达率问题。

整体IM消息系统,还是一个比较复杂的领域。

我们在消息系统的发展过程中,面临着如下问题:

  • 1)如何进行消息的链路追踪;
  • 2)如何保证IM消息的快速到达(见《闲鱼亿级IM消息系统的及时性优化实践》);
  • 3)如何将消息的玩法和底层能力分离;
  • 4)离线推送中如何通过用户找到对应的设备。

这些问题,我们在以前的文章中有所分享,以后也会陆续分享更多,敬请期待。(本文已同步发布于:www.52im.net/thread-3748… )

附录:相关资料

[1] Android P正式版即将到来:后台应用保活、消息推送的真正噩梦

[2] 一套高可用、易伸缩、高并发的IM群聊、单聊架构方案设计实践

[3] 一套亿级用户的IM架构技术干货(上篇):整体架构、服务拆分等

[4] 一套亿级用户的IM架构技术干货(下篇):可靠性、有序性、弱网优化等

[5] 从新手到专家:如何设计一套亿级消息量的分布式IM系统

[6] 企业微信的IM架构设计揭秘:消息模型、万人群、已读回执、消息撤回等

[7] 融云技术分享:全面揭秘亿级IM消息的可靠投递机制

[8] 移动端IM中大规模群消息的推送如何保证效率、实时性?

[9] 现代IM系统中聊天消息的同步和存储方案探讨

[10] 新手入门一篇就够:从零开发移动端IM

[11] 移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

[12] 移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

[13] IM消息送达保证机制实现(一):保证在线实时消息的可靠投递

[14] IM消息送达保证机制实现(二):保证离线消息的可靠投递

[15] 零基础IM开发入门(一):什么是IM系统?

[16] 零基础IM开发入门(二):什么是IM系统的实时性?

[17] 零基础IM开发入门(三):什么是IM系统的可靠性?

[18] 零基础IM开发入门(四):什么是IM系统的消息时序一致性?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【每日算法】NC65 斐波那契数列1

发表于 2021-11-17

这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战。

描述

大家都知道斐波那契数列,现在要求输入一个正整数 n ,请你输出斐波那契数列的第 n 项。

斐波那契数列是这样一个数列:1、1、2、3、5、8、13、21、34….

就是第一和第二位都是 1,后面的数都是前面两个数之和。

数据范围:1 ≤ n ≤ 39

要求:空间复杂度 O(1),时间复杂度 O(n) ,本题也有时间复杂度 O(logn)的解法

输入描述:

一个正整数n

返回值描述:

输出一个正整数。

做题

做这种题,肯定不用递归啦,递归🐕都不用。

肯定需要两个变量:former、latter,前者用来存储比较旧的数据,后者用来存储当前位置的数据,当要求出下一位斐波那契数,就只需要把这两个变量相加就可以了,所以这两个变量是肯定要创建的,创建数组就没必要了,我们只是要一个数而已。

两个变量相加的结果就直接存放到 latter,这里想要在保存原来 latter 的值,就只能再创建一个变量来存储了。

还有循环,直接从 2 开始循环,当 n 小于或等于 2 时,就不走循环,直接返回 latter(1)。

1
2
3
4
5
6
7
8
9
10
11
ini复制代码public int Fibonacci(int n) {
int former=1;
int latter=1;
int temp=0;
for(int i=2;i<n;i++){
temp=latter;
latter+=former;
former=temp;
}
return latter;
}

运行!

image.png

运行速度还是挺不错的,占用内存就比较拉跨了。

再试

网上说的斐波那契数组最佳的解的时间复杂度是 log(n),我们肯定要想最优的解看齐啦。

我也找了很多网上的博客,很多是跟线性代数有关的,但更多的是拿时间度为 n 的来凑数,搞得我看了半天,最后这不是我已经写出来的吗?!

未完待续。。。

这里是程序员徐小白,【每日算法】是我新开的一个专栏,在这里主要记录我学习算法的日常,也希望我能够坚持每日学习算法,不知道这样的文章风格您是否喜欢,不要吝啬您免费的赞,您的点赞、收藏以及评论都是我下班后坚持更文的动力。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

JVM基础入门(什么是JVM)

发表于 2021-11-17

1. java从编码到执行的过程

图片.png

  • 1
    vbnet复制代码Classloader:将class文件和我们java代码需要的类库装载到内存中,装载完成后,会调用字节码解释器或者用即使编译器来进行编译,然后由执行引擎执行。

Java程序最初是仅仅通过解释器解释执行的,即对字节码逐条解释执行,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。
于是后来在虚拟机中引入了JIT编译器(即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,就会把这些代码认定为“Hot Spot Code”(热点代码),为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是JIT编译器。
即时编译生成机器相关的中间码,可重复执行缓存效率高。解释执行直接执行字节码,重复执行需要重复解释。

2. JVM

  • java和jvm无关,只和class形式的文件有关系,任何语言只要可以编译成class文件,都可以扔在jvm中执行。
  • java虚拟机是一种规范
    图片.png
  • JDK&JRE&JVM 三者的关系
    图片.png
  1. JVM:java虚拟机
  2. JRE:JVM + java运行时环境
  3. JDK:JRE + 各种开发lib包

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

【Java入门100例】11==和equals()——值和

发表于 2021-11-17

这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战

点赞再看,养成习惯。微信搜索【一条coding】关注这个在互联网摸爬滚打的程序员。

本文收录于技术专家修炼,里面有我的学习路线、系列文章、面试题库、自学资料、电子书等。欢迎star⭐️

题目描述

难度:简单

写出下面代码的输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
> java复制代码public static void main(String[] args) {
> String s1,s2;
> s1=new String("we are students");
> s2=new String("we are students");
> System.out.println(s1.equals(s2));
> System.out.println(s1==s2);
> String s3,s4;
> s3="how are you";
> s4="how are you";
> System.out.println(s3.equals(s4));
> System.out.println(s3==s4);
> }
>
>

知识点

  • String
  • equals()
  • ==

解题思路

1.equals()和==

首先需要明确equals()比较的是值,也就是和我们肉眼看见的没区别,1和1就是true。

==比较的是地址,好比两件衣服虽然看着一样,但产地不一样。依然是false。

2.String

再就是String的创建过程,对于new String() 这种毫无疑问,肯定是创建一个新对象。

但是对于直接赋值s3="how are you";,如果已经有一个和他值相等的对像,就不再new新的,而是直接指向这个对象的地址。

代码实现

输出结果

扩展总结

由于String被final修饰,值无法修改,所以我们每次修改值都是重新new了一个对象,为了避免产生过多垃圾,对于需要经常修改的字符串建议使用StringBuilder或StringBuffer。

最后

独脚难行,孤掌难鸣,一个人的力量终究是有限的,一个人的旅途也注定是孤独的。当你定好计划,怀着满腔热血准备出发的时候,一定要找个伙伴,和唐僧西天取经一样,师徒四人团结一心才能通过九九八十一难。
所以,

如果你想学好Java

想进大厂

想拿高薪

想有一群志同道合的伙伴

请查看我的主页。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Go语言变量

发表于 2021-11-17

「这是我参与11月更文挑战的第17天,活动详情查看:2021最后一次更文挑战」。

1 标识符与关键字

标识符

在编程语言中标识符就是程序员定义的具有特殊意义的词,比如变量名、常量名、函数名等等。 Go语言中标识符由字母数字和_(下划线)组成,并且只能以字母和_开头。 举几个例子:abc, _, _123, a123。

关键字

关键字是指编程语言中预先定义好的具有特殊含义的标识符。 关键字和保留字都不建议用作变量名。

Go语言中有25个关键字:

1
2
3
4
5
go复制代码break        default      func         interface    select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var

此外,Go语言中还有37个保留字。

1
2
3
4
5
6
7
8
9
10
go复制代码Constants:    true  false  iota  nil

Types: int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex128 complex64
bool byte rune string error

Functions: make len cap new append copy close delete
complex real imag
panic recover

2 变量的来历

程序运行过程中的数据都是保存在内存中,我们想要在代码中操作某个数据时就需要去内存上找到这个变量,但是如果我们直接在代码中通过内存地址去操作变量的话,代码的可读性会非常差而且还容易出错,所以我们就利用变量将这个数据的内存地址保存起来,以后直接通过这个变量就能找到内存上对应的数据了。

3 变量类型

变量(Variable)的功能是存储数据。不同的变量保存的数据类型可能会不一样。经过半个多世纪的发展,编程语言已经基本形成了一套固定的类型,常见变量的数据类型有:整型、浮点型、布尔型等。

Go语言中的每一个变量都有自己的类型,并且变量必须经过声明才能开始使用。

4 变量声明

Go语言中的变量需要声明后才能使用,同一作用域内不支持重复声明。 并且Go语言的变量声明后必须使用。

4.1 标准声明

Go语言的变量声明格式为:

变量声明以关键字var开头,变量类型放在变量的后面,行尾无需分号。

1
go复制代码var 变量名 变量类型
举例如下
1
2
3
go复制代码var name string
var age int
var isOk bool

4.2 批量声明

1
2
3
4
5
6
go复制代码var (
a string
b int
c bool
d float32
)

5 变量的初始化

Go语言在声明变量的时候,会自动对变量对应的内存区域进行初始化操作。每个变量会被初始化成其类型的默认值,例如: 整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil。

当然我们也可在声明变量的时候为其指定初始值。变量初始化的标准格式如下:
1
go复制代码var 变量名 类型 = 表达式
举例如下
1
2
3
4
5
6
go复制代码# 单个变量初始化
var name string = "ourlang"
var age int = 18

#一次初始化多个变量
var name, age = "ourlang", 20

6 短变量声明

在函数内部,可以使用更简略的 := 方式声明并初始化变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
go复制代码package main

import (
"fmt"
)
// 全局变量m
var m = 100

func main() {
n := 10
m := 200 // 此处声明局部变量m
str:="what"
fmt.Println(str)

}

7 匿名变量

在编码过程中,可能会遇到没有名称的变量、类型或方法。虽然这不是必须的,但有时候这样做可以极大地增强代码的灵活性,这些变量被统称为匿名变量。
匿名变量的特点是一个下画线_,_本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。
匿名变量不占用内存空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。

举例如下:

1
2
3
4
5
6
7
8
go复制代码func GetData() (int, int) {
return 100, 200
}
func main(){
a, _ := GetData()
_, b := GetData()
fmt.Println(a, b) //100 200
}
1
2
3
4
5
SH复制代码注意事项:

- 函数外的每个语句都必须以关键字开始(var、const、func等)
- :=不能使用在函数外。
- _多用于占位,表示忽略值

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringCloud06——Ribbon负载均衡服务调用

发表于 2021-11-17

一、介绍

官网:github.com/Netflix/rib…

1、简介

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。

简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。

2、作用

简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高可用)。

常见的负载均衡有软件Nginx,LVS,硬件F5等。

Ribbon本地负载均衡客户端VS Nginx服务端负载均衡区别

Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现的。
Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。

集中式LB

即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方;

进程内LB

将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。

3、Ribbon的负载均衡和Rest调用

Ribbon其实就是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和Eureka结合只是其中的一个实例。

img
步骤:

  • 第一步先选择EurekaServer ,它优先选择在同一个区域内负载较少的server。
  • 第二步再根据用户指定的策略,再从server取到的服务注册列表中选择一个地址。

Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

Netflix就已经引入了Ribbon,所以就不用引入了。

1
2
3
4
xml复制代码<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

上面这个依赖点进去有Ribbon的依赖:

1
2
3
4
5
6
xml复制代码<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<version>2.2.1.RELEASE</version>
<scope>compile</scope>
</dependency>

RestTemplate看源码:RestTemplate (Spring Framework 5.2.2.RELEASE API)RestTemplate (Spring Framework 5.2.2.RELEASE API)

getForObject()和getForEntity()方法的区别

  • getForObject()返回对象就是响应体的数据转化成的对象,也就是Json格式的
  • getForEntity()返回的是ResponseEntity对象,其中还包含了更重要的信息,比如响应头、响应状态码和响应体等等

这次使用的以前的模块:

  • EurekaMain7001模块
  • EurekaMain7002模块
  • OrderMain80模块
  • PaymentMain8001模块
  • PaymentMain8001模块

这里演示一下能否使用getForEntity()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id,CommonResult.class);
}

@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment1(@PathVariable("id")Long id){
ResponseEntity<CommonResult> entity =
restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();
}else{
return new CommonResult<>(444,"操作失败");
}
}

二、Ribbon进阶

1、Ribbon有哪些负载规则

可以进入Irule接口查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/

public Server choose(Object key);

public void setLoadBalancer(ILoadBalancer lb);

public ILoadBalancer getLoadBalancer();
}

继承关系如图所示:

image-20211116171907776
上面这个按点上面IRule接口,右键Diagrams–>show diagram,然后再右键show Implementations可以选择显示哪个。

或者选中IRule,然后ctrl + H,显示它的所有子类。

image-20211116172231507
最常用的是:

名称 规则
RoundRobinRule 轮询
RandomRule 随机
RetryRule 先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重
WeightedResponseTimeRule 对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
BestAvailableRule 会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
AvailabilityFilteringRule 先过滤掉故障实例,再选择并发较小的实例
ZoneAvoidanceRule 默认规则,复合判断server所在区域的性能和server的可用性选择服务器

2、设置负载规则

官方文档明确给的警告:

这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,

否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。

所以修改comsumer80时,需要在springcloud包同级再创建新的包:

image-20211117095729036
在它的下面创建规则:

1
2
3
4
5
6
7
8
java复制代码@Configuration
public class MySelfRule {
//设置为随机
@Bean
public IRule myRule(){
return new RandomRule();
}
}

主配置类上加注解:

1
2
3
4
5
6
7
8
java复制代码@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}

再次测试:localhost/consumer/payment/get/1,选择的端口随机了。

3、负载均衡的算法

默认负载轮训算法: rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始。

List instances = discoveryClient.getInstances(“CLOUD-PAYMENT-SERVICE”);

List [0] instances = 127.0.0.1:8002
List [1] instances = 127.0.0.1:8001
8001+ 8002组合成为集群,它们共计2台机器,集群总数为2,按照轮询算法原理:

  • 当总请求数为1时:1%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
  • 当总请求数位2时:2%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002
  • 当总请求数位3时:3%2=1对应下标位置为1,则获得服务地址为127.0.0.1:8001
  • 当总请求数位4时:4%2=О对应下标位置为0,则获得服务地址为127.0.0.1:8002
  • 如此类推…

4、源码

首先进入IRule接口,点进去找RoundRobinRule类,这就是轮询的方式的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public class RoundRobinRule extends AbstractLoadBalancerRule {

private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;

private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);

public RoundRobinRule() {
nextServerCyclicCounter = new AtomicInteger(0);
}
//......其他代码
}

这里nextServerCyclicCounter是一个原子整型,类的构造要初始化该参数,默认为0。

这个类要实现接口的方法,接口是这么写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码public interface IRule{
/*
* choose one alive server from lb.allServers or
* lb.upServers according to key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/

public Server choose(Object key);

public void setLoadBalancer(ILoadBalancer lb);

public ILoadBalancer getLoadBalancer();
}

这里看RoundRobinRule类实现的choose方法,也就是选择一种负载均衡的算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
java复制代码public Server choose(ILoadBalancer lb, Object key) {
if (lb == null) {
log.warn("no load balancer");
return null;
}

Server server = null;
int count = 0;
while (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();

if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}

int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);

if (server == null) {
/* Transient. */
Thread.yield();
continue;
}

if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}

// Next.
server = null;
}

if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}

如果lb为null,那么报错;最开始设置服务器server=null,也就是不知道用哪个服务器去处理,count也设置为0。然后进入循环:

1
java复制代码while (server == null && count++ < 10) {}

上面就是要不断寻找用哪个server,而如果count数量过大,那么就报找了10次,根本就没有存活的server存在。

接下来就看看里面到底是怎么找的server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码while (server == null && count++ < 10) {
List<Server> reachableServers = lb.getReachableServers();
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();

if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}

int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);

if (server == null) {
/* Transient. */
Thread.yield();
continue;
}

if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}

// Next.
server = null;
}

getReachableServers就是查看哪些存活的server并返回,getAllServers就是找所有的server,然后分别获取它们的数量。获取到数量以后如果根本就没有存活的,或者根本就没有服务,报错返回,upCount说的就是存活的数量,可以看eureka注册中心:

image-20211117105024582
这上面就标注了up的是两个。

1
2
java复制代码int nextServerIndex = incrementAndGetModulo(serverCount);
server = allServers.get(nextServerIndex);

这个方法就是获取下一次server的下标,然后根据下标拿到server。

因为它是轮询方式,所以进入方法就可以发现,它就是按照前面所说的取余数的方式拿到下标的。

1
2
3
4
5
6
7
8
java复制代码private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}

compareAndSet==>CAS,自旋锁。可以看到就是 + 1然后取模,初始是1,下一次是2,再下一次是1……这样就拿到了server的下标,进而获取了server服务。

5、手写负载的算法

在8001和8002的controller下加如下代码:

1
2
3
4
java复制代码@GetMapping(value = "/payment/lb")
public String getPaymentLB() {
return serverPort;
}

order80模块取消负载均衡,到时候用自己的:

1
2
3
4
5
6
7
8
java复制代码@Configuration
public class ApplicationContextConfig {
@Bean
//@LoadBalanced 直接注掉
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}

和springcloud下创建个包lb,里面创建LoadBalanced接口。

1
2
3
java复制代码public interface LoadBalancer {
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}

创建它的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
java复制代码@Component
public class MyLB implements LoadBalancer{
//设置源自类默认值
private AtomicInteger atomicInteger = new AtomicInteger(0);

//拿到下标索引,并增加
public final int getAndIncrement(){
int current;
int next;
do{
//获取当前数字
current = this.atomicInteger.get();
//下一个数字
next = current >= Integer.MAX_VALUE ? 0 : current + 1;
}while (!this.atomicInteger.compareAndSet(current,next));
System.out.println("next = " + next);
return next;
}

@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}

修改80端口的controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
java复制代码@RestController
@Slf4j
public class OrderController {
public static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";

@Resource
private LoadBalancer loadBalancer;

@Autowired
private RestTemplate restTemplate;

@Resource
private DiscoveryClient discoveryClient;

@GetMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment){
return restTemplate.postForObject(PAYMENT_URL + "/payment/create",payment,CommonResult.class);
}

@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id")Long id){
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id,CommonResult.class);
}

@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPayment1(@PathVariable("id")Long id){
ResponseEntity<CommonResult> entity =
restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
if (entity.getStatusCode().is2xxSuccessful()){
return entity.getBody();
}else{
return new CommonResult<>(444,"操作失败");
}
}

@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB(){
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
if(instances == null || instances.size() <= 0){
return null;
}

ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/payment/lb",String.class);

}
}

测试:localhost/consumer/payment/lb,通过,8001和8002轮询。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

SpringBoot+Redis 集成验证码的简单案例

发表于 2021-11-17

SpringBoot+Redis 集成验证码的简单案例

一次学习过程中简单的记录

一、下载安装Redis

这里就不多说了,下载安装好Redis,最好是把Redis Desktop Manager一起安装了,可视化看的舒服一点。

二、代码部分

1.引入Redis依赖,这里我的springboot版本是2.4.2

1
2
3
4
5
6
7
8
9
xml复制代码<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.10.0</version>
</dependency>

验证码依赖

1
2
3
4
5
xml复制代码<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>

yml配置,这里就简单配置做示例,小伙伴们根据自己的业务需求来配置哈~

1
2
3
4
5
6
7
8
9
10
11
12
13
yaml复制代码server:
port: 8081
spring:
redis:
database: 3
jedis:
pool:
max-active: 8
max-wait: -1
lettuce:
pool:
min-idle: 0
timeout: 5000

2.编写RedisConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
java复制代码@Configuration
public class RedisConfig extends CachingConfigurerSupport {

@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
StringRedisSerializer keySerializer = new StringRedisSerializer();
GenericFastJsonRedisSerializer valueSerializer = new GenericFastJsonRedisSerializer();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 序列化 key 和 value
redisTemplate.setKeySerializer(keySerializer);
redisTemplate.setValueSerializer(valueSerializer);
redisTemplate.setHashKeySerializer(keySerializer);
redisTemplate.setHashValueSerializer(valueSerializer);
return redisTemplate;
}
}

3.编写RedisUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
java复制代码@Component
public class RedisClient {

@Resource
private RedisTemplate<String, Object> redisTemplate;

/*
* @ClassName RedisClient
* @Desc TODO 设置缓存(没有时间限制)
* @Date 2021-07-24 16:11
* @Version 1.0
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}

/*
* @ClassName RedisClient
* @Desc TODO 设置缓存(有时间限制,单位为 秒)
* @Date 2021-07-24 16:11
* @Version 1.0
*/
public void set(String key, Object value, long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}

/*
* @ClassName RedisClient
* @Desc TODO 删除缓存,并返回是否删除成功
* @Date 2021-07-24 16:11
* @Version 1.0
*/
public boolean delete(String key) {
redisTemplate.delete(key);
// 如果还存在这个 key 就证明删除失败
if (redisTemplate.hasKey(key)) {
return false;
// 不存在就证明删除成功
} else {
return true;
}
}

/*
* @ClassName RedisClient
* @Desc TODO 取出缓存
* @Date 2021-07-24 16:12
* @Version 1.0
*/
public Object get(String key) {
if (redisTemplate.hasKey(key)) {
return redisTemplate.opsForValue().get(key);
} else {
return null;
}
}

/*
* @ClassName RedisClient
* @Desc TODO 获取失效时间(-2:失效 / -1:没有时间限制)
* @Date 2021-07-24 16:15
* @Version 1.0
*/
public long getExpire(String key) {
// 判断是否存在
if (redisTemplate.hasKey(key)) {
return redisTemplate.getExpire(key);
} else {
return Long.parseLong(-2 + "");
}
}

4.编写Controller测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
java复制代码@Controller
@ResponseBody
public class CaptchaController {

@Resource
private RedisClient redisClient;

@GetMapping("/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
SpecCaptcha specCaptcha = new SpecCaptcha(130, 48, 4);
specCaptcha.setFont(Captcha.FONT_1);
String id = UUID.randomUUID().toString();
response.setHeader("id", id);
CaptchaUtil.out(specCaptcha, request, response);
String verCode = specCaptcha.text().toLowerCase();
redisClient.set(id, verCode);//可设置有效时间,单位为秒
}

@PostMapping(value = "/check")
public boolean check(@RequestBody String info) {
JSONObject jsonObject = JSON.parseObject(info);
String id = jsonObject.getString("id");
String code = jsonObject.getString("code");
String s = redisClient.get(id).toString();//id
return code.equalsIgnoreCase(s);
}
}

访问localhost:8081/captcha 别忘了启动Redis

在这里插入图片描述
这里的id就是存进Redis的键

在这里插入图片描述
我们拿到Redis的键再去请求 localhost:8081/check 验证我们的验证码是否是正确的
在这里插入图片描述

到这里就结束啦,小白的一次简单验证码的集成~,有不对的地方欢迎指正

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

云原生 DevOps,模型化应用交付能力很重要! 云原生带来

发表于 2021-11-17

简介: DevOps 文化及其支撑其落地实践的自动化工具与平台能力在云原生架构渐为普及的背后,发挥了关键的价值。

撰稿:溪洋

云原生正在成为企业业务创新和解决规模化挑战的加速器。

云原生带来的变革绝不限于基础设施和应用架构等技术层面,更是对于研发理念、交付流程和 IT 组织方式的变革,也在推进企业 IT 组织、流程和文化的变革。在云原生架构渐为普及的背后, DevOps 文化及其支撑其落地实践的自动化工具与平台能力,发挥了关键的价值。

云原生带来的研发与运维协作界面

相较于云原生,DevOps 并不是什么新鲜的事情,其实践早已深入企业现代应用程序架构。DevOps 强调团队间的沟通和快速反馈,通过构建自动化的持续交付(Continuous Delivery)及流水线的应用发布方式,达到快速响应业务需求、交付产品和提高交付质量的目的。随着容器技术在企业的规模化应用,云计算可编程基础设施和 Kubernetes 声明式的 API 等能力加速了开发和运维角色的融合。

云原生的大势所趋使上云成为企业标配,围绕云原生去定义下一代研发平台成为必然,也倒逼着 IT 组织方式进一步发生变化——新的平台工程团队开始浮现。在这样的背景下,如何在云原生的环境下更高效地实践 DevOps 成为一个新的课题和诉求。

下一代 DevOps 平台演进趋势

伴随着 Kubernetes 生态从底层到应用层能力的逐步完善,平台工程团队可以更方便地基于业务场景和终端用户实际需求来构建不同的应用平台,却也为上层应用开发者带来了挑战和困扰。

Kubernetes 生态本身的能力池固然丰富,但是社区里却并没有一个可扩展的、方便快捷的方式,为云原生架构下混合、分布式的部署环境引入一致的上层抽象来为应用交付进行建模。应用交付过程上层抽象能力的缺失,使 Kubernetes 的复杂性无法做到向应用开发人员屏蔽。

下图展示了一个云原生下的 DevOps 流水线的典型流程。首先是代码的开发,代码托管到 Github,再接入单元测试的工具 Jenkins,此时基本研发已完成。再接着到镜像的构建,涉及到配置、编排等。云原生中可以用 HELM 打包应用。打包好的应用部署到各个环境中。但整个过程中会面临很多挑战。

首先,在不同的环境需要不同的运维能力。其次,配置的过程中要创建云上数据库,需要另外打开一个控制台来创建数据库。还需要配置负载均衡。在应用启动以后还需要配置额外的功能,包括日志、策略、安全防护等等。可以发现,云资源和 DevOps 平台体验是割裂的,里面充斥着借助外部平台创建的过程。这对新手来说是非常痛苦的。

在容器出现之前的传统的 DevOps 模式需要不同的流程和工作流。容器技术是以 DevOps 的视角构建的。抽象的容器所提供的功能会影响我们如何看待 DevOps,因为随着微服务的出现,传统的架构开发将发生变化。这意味着要遵循在 Kubernetes 上运行容器的最佳实践,并将 DevOps 的理念向 GitOps、DevSecOps 扩展,使云原生下的 DevOps 更加高效、安全、稳定、可靠。

OAM(Open Application Model) 试图提供一种云原生应用的建模语言,以实现研发和运维的视角分离,使 Kubernetes 的复杂性无需透传至研发,运维通过提供模块化、可移植、可扩展的特性组件,支撑各种复杂的应用交付场景,从而实现云原生应用交付的敏捷性和平台无关性。其在 Kubernetes 上的完整实现 KubeVela,已经被业界认为是构建下一代持续交付方式及 DevOps 实践的核心工具。

最近,阿里云在 2021 云栖大会上发布了云效应用交付平台 AppStack ,旨在进一步加速企业云原生 DevOps 规模化落地。据云效应用交付平台 AppStack 研发团队介绍,其在设计之初就全面支持原生 Kubernetes 和 OAM/KubeVela ,以实现对应用部署架构无绑定、无侵入,使企业不用担心迁移以及技术改造成本。这也标志着 KubeVela 正在成为云原生时代应用交付领域的重要基石。

基于 KubeVela,构建以应用为中心的交付系统

在云原生理念迅速普及的今天,混合环境部署(混合云/多云/分布式云/边缘)已经成为了大多数企业应用、SaaS 服务、应用持续交付平台的必然选择,而云原生技术的发展趋势也正在朝着“一致的、跨云、跨环境的的应用交付”不断迈进。

KubeVela 作为一个开箱即用、面向现代微服务架构的应用交付与管理平台,已经正式发布 1.1 版本。在此版本中,KubeVela 更加聚焦面向混合环境的应用交付流程,带来了多集群交付、交付流程定义、灰度发布、公有云资源接入等多个开箱即用的能力和更加友好的用户体验,帮助开发者从“静态配置、模板、胶水代码”的初级阶段,直接升级至“自动化、声明式、统一模型、天然面向多环境”的下一代以工作流为核心的交付体验当中。

基于 KubeVela,用户可以非常轻松地处理以下场景:

多环境、多集群应用交付

面向 Kubernetes 的多环境、多集群交付已是一个标准性需求。从 1.1 版本开始,KubeVela 不仅实现了多集群的应用交付,并且既可以独立工作直接纳管多个集群,也可以集成 OCM、Karmada 等各类多集群管理工具来进行更复杂的交付动作。在多集群交付策略的基础上,用户还可以通过定义 Workflow 来控制交付到不同集群的顺序、条件等工作流步骤。

定义交付工作流(Workflow)

Workflow 的具体使用场景则很多,比如在多环境应用交付场景中,用户可以定义不同的环境交付的顺序和前置条件等 。KubeVela 的工作流是面向 CD 过程的,同时也是声明式的,所以它既可以作为 CD 系统直接同 CI 系统(比如 Jenkins 等)对接,也可以嵌入到现有 CI/CD 体系中作为增强和补充,落地方式非常灵活。

在模型上,Workflow 是由一系列 Step 组成的,而在实现上,每一个 Step 则是一个独立的能力模块,由其具体的类型和参数来决定其具体步骤的能力。在 1.1 版本中,KubeVela 内置的 Step 已经比较丰富,也非常容易扩展,帮助用户轻松对接已有的平台能力,做到无缝迁移。

以应用为中心的云资源交付

KubeVela 的设计是从“以应用为中心”的视角出发,因此可以帮助开发者以完全 Serverless 的方式更好、更方便的管理云资源,而不是疲于应付各种不同的云产品和控制台。在实现上,KubeVela 内置集成了 Terraform 来作为云资源的编排工具,并且能够以统一的应用模型支持各个云厂商上百种不同类型云服务的部署、绑定和管理。

在使用上,目前 KubeVela 将云资源分为以下三类:

  • 作为组件:比如数据库、中间件、SaaS 服务等。比如 KubeVela 中的 Alibaba-RDS 服务就属于这种
  • 作为运维特征:比如日志分析、监控可视化、监控报警等服务
  • 作为应用运行基础设施:比如 Kubernetes 托管集群、SLB 负载均衡、NAS文件存储服务等

更容易落地的 GitOps 持续交付实践

KubeVela 作为一个声明式的应用交付控制平面,天然就可以以 GitOps 的方式进行使用(可单独使用,也可配合 ArgoCD 等工具),并且能够为 GitOps 场景提供更多端到端的能力和增强、帮助 GitOps 理念以更加亲民和解决实际问题的方式在企业中落地。这些能力包括:

  • 定义应用交付工作流(CD 流水线)
  • 处理部署过程中的各种依赖关系和拓扑结构
  • 在现有各种 GitOps 工具的语义之上提供统一的上层抽象,简化应用交付与管理过程
  • 统一进行云服务的声明、部署和服务绑定
  • 提供开箱即用的交付策略(金丝雀、蓝绿发布等)
  • 提供开箱即用的混合环境/多集群部署策略(放置规则、集群过滤规则、跨环境Promotion 等)
  • 在多环境交付中提供 Kustomize 风格的 Patch 来描述部署差异,而用户无需学习任何 Kustomize 本身的细节
  • ……

KubeVela 1.2 版本即将重磅发布

持续打造天然面向混合环境的企业应用操作系统、让开发者享受交付应用的过程,这是 Kubevela 项目的目标和愿景。在接下来的 1.2 版本,KubeVela 将带来以应用为中心的控制面板 UI,实现便捷的企业应用组装、分发、交付流程,提供给开发者更简单的应用交付体验,同时覆盖边缘应用交付等更多的使用场景。

KubeVela 1.2 版本将在 2021 年 12 月举办的 KubeCon China 上重磅发布,敬请持续关注 KubVela 社区以及阿里巴巴云原生动态!

原文链接

本文为阿里云原创内容,未经允许不得转载。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…311312313…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%