首页 文章 精选 留言 我的

精选列表

搜索[快速],共10000篇文章
优秀的个人博客,低调大师

(73)java Spring Cloud+Spring boot+mybatis企业快速开发架构之Spring Cloud服务限流详解

高并发系统中有三把利器用来保护系统:缓存、降级和限流。限流的目的是为了保护系统不被大量请求冲垮,通过限制请求的速度来保护系统。在电商的秒杀活动中,限流是必不可少的一个环节。 推荐分布式架构源码地址 限流的方式也有多种,可以在 Nginx 层面限流,也可以在应用当中限流,比如在 API 网关中。 限​流算法 常见的限流算法有:令牌桶、漏桶。计数器也可以进行限流实现。 1)令牌桶 令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。可以控制流量也可以控制并发量,假如我们想要控制 API 网关的并发量最高为 1000,可以创建一个令牌桶,以固定的速度往桶里添加令牌,超出了 1000 则不添加。 当一个请求到达之后就从桶中获取一个令牌,如果能获取到令牌就可以继续往下请求,获取不到就说明令牌不够,并发量达到了最高,请求就被拦截。 2)漏桶 漏桶是一个固定容量的桶,按照固定的速率流出,可以以任意的速率流入到漏桶中,超出了漏桶的容量就被丢弃,总容量是不变的。但是输出的速率是固定的,无论你上面的水流入的多快,下面的出口只有这么大,就像水坝开闸放水一样,如图 1 所示。 单节点限流 单节点限流指的是只对这个节点的并发量进行控制,相对于集群限流来说单节点限流比较简单,稳定性也好,集群限流需要依赖第三方中间件来存储数据,单节点限流数据存储在本地内存中即可,风险性更低。 从应用的角度来说单节点的限流就够用了,如果我们的应用有 3 个节点,总共能扛住 9000 的并发,那么单个节点最大能扛住的量就是 3000,只要单个节点扛住了就没什么问题了。 我们可以用上面讲的令牌桶算法或者漏桶算法来进行单节点的限流操作,算法的实现可以使用 Google Guava 中提供的算法实现类。实际使用中令牌桶算法更适合一些,当然这个得参考业务需求,之所以选择令牌桶算法是因为它可以处理突发的流量,漏桶算法就不行,因为漏桶的速率是固定的。 首先需要依赖 Guava,其实也可以不用,在 Spring Cloud 中好多组件都依赖了 Guava,如果你的项目是 Spring Cloud 技术栈的话可以不用自己配置,间接就已经依赖了,代码如下所示。 <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency> 创建一个限流的过滤器,order 返回 0,执行优先级第一,代码如下所示。 public class LimitFilter extends ZuulFilter { public static volatile RateLimiter rateLimiter = RateLimiter.create(100.0); public LimitFilter() { super(); } @Override public boolean shouldFilter() { return true; } @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public Object run() { // 总体限流rateLimiter.acquire(); return null; } } 注册限流过滤器,代码如下所示。 @Bean public LimitFilter limitFilter() { return new LimitFilter(); } 上面的方案有一个致命的问题就是速率值是写死的,往往我们需要根据服务器的配置以及当时的并发量来设置一个合理的值,那么就需要速率这个值能够实时修改,并且生效,这时配置中心又派上用场了。 添加 Apollo 的配置,代码如下所示。 @Data @Configuration public class BasicConf { @Value("${limitRate:10}") private double limitRate; } 有一个问题是当这个值修改的时候需要重新初始化 RateLimiter,在配置类中实现修改回调的方法代码如下所示 @Data @Configuration public class BasicConf { @Value("${limitRate:10}") private double limitRate; @ApolloConfig private Config config; @ApolloConfigChangeListener public void onChange(ConfigChangeEvent changeEvent) { if (changeEvent.isChanged("limitRate")) { // 更 新 RateLimiter LimitFilter.rateLimiter = RateLimiter.create(config.getDoubleProperty("limitRate", 10.0)); } } } 我们可以用 ab 来测试一下接口,代码如下所示。 ab -n 1000 -c 30 http://192.168.10.170:2103/fsh-house/house/1 Benchmarking 192.168.10.170 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: Server Hostname: 192.168.10.170 Server Port: 2103 Document Path: /fsh-house/house/1 Document Length: 796 bytes Concurrency Level: 30 Time taken for tests: 98.989 seconds Complete requests: 1000 Failed requests: 6 (Connect: 0, Receive: 0, Length: 6, Exceptions: 0) Total transferred: 1001434 bytes HTML transferred: 833434 bytes Requests per second: 10.10 [#/sec] (mean) Time per request: 2969.679 [ms] (mean) Time per request: 98.989 [ms] (mean, across all concurrent requests) Transfer rate: 9.88 [Kbytes/sec] received -n 1000 表示总共请求 1000 次 -c 30 表示并发数量 我们可以看到执行完这 1000 次请求总共花费了 98 秒,Time taken for tests 就是请求所花费的总时间,这是在限流参数为 10 的情况下,我们还可以把参数调到 100 然后测试一下,代码如下所示。 ab -n 1000 -c 30 http://192.168.10.170:2103/fsh-house/house/1 Benchmarking 192.168.10.170 (be patient) Completed 100 requests Completed 200 requests Completed 300 requests Completed 400 requests Completed 500 requests Completed 600 requests Completed 700 requests Completed 800 requests Completed 900 requests Completed 1000 requests Finished 1000 requests Server Software: Server Hostname: 192.168.10.170 Server Port: 2103 Document Path: /fsh-house/house/1 Document Length: 7035 bytes Concurrency Level: 30 Time taken for tests: 9.061 seconds Complete requests: 1000 Failed requests: 30 (Connect: 0, Receive: 0, Length: 30, Exceptions: 0) Total transferred: 7015830 bytes HTML transferred: 6847830 bytes Requests per second: 110.37 [#/sec] (mean) Time per request: 271.821 [ms] (mean) Time per request: 9.061 [ms] (mean, across all concurrent requests) Transfer rate: 756.17 [Kbytes/sec] received 限流的参数调大后,请求 9 秒就完成了,这就证明我们的限流操作起作用了。 集群限流 集群限流可以借助Redis 来实现,至于实现的方式也有很多种,下面我们介绍一种比较简单的限流方式。 我们可以按秒来对并发量进行限制,比如整个集群中每秒只能访问 1000 次。我们可以利用计数器来判断,Redis 的 key 为当前秒的时间戳,value 就是访问次数的累加,当次数超出了我们限制的范围内,直接拒绝即可。需要注意的是集群中服务器的时间必须一致才能没有误差,下面我们来看代码。 首先在我们的 API 网关中集成 Redis 的操作,我们引入 Spring Data Redis 来操作 Redis,代码如下所示。 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> 属性配置文件中配置 Redis 的连接信息 spring.redis.host=192.168.10.47 spring.redis.port=6379 集成后就直接可以使用 RedisTemplate 来操作 Redis 了,这里配置了一个 RedisTemplate,key 为 String 类型,value 为 Long 类型,用来计数,代码如下所示。 @Configuration public class RedisConfig { @Bean(name = "longRedisTemplate") public RedisTemplate<String, Long> redisTemplate(RedisConnectionFactory jedisConnectionFactory) { RedisTemplate<String, Long> template = new RedisTemplate<String, Long>(); template.setConnectionFactory(jedisConnectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(new GenericToStringSerializer<Long>(Long.class)); template.setValueSerializer(new GenericToStringSerializer<Long>(Long.class)); return template; } } 在之前的限流类的配置中增加集群限流的速率配置,代码如下所示。 @Data @Configuration public class BasicConf { @Value("${clusterLimitRate:10}") private double clusterLimitRate; } 接下来我们改造之前单体限流用的过滤器 LimitFilter,采用 Redis 来进行限流操作,代码如下所示。 public class LimitFilter extends ZuulFilter { private Logger log = LoggerFactory.getLogger(LimitFilter.class); public static volatile RateLimiter rateLimiter = RateLimiter.create(100); @Autowired @Qualifier("longRedisTemplate") private RedisTemplate<String, Long> redisTemplate; @Autowired private BasicConf basicConf; public LimitFilter() { super(); } @Override public boolean shouldFilter() { return true; } @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 0; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); Long currentSecond = System.currentTimeMillis() / 1000; String key = "fsh-api-rate-limit-" + currentSecond; try { if (!redisTemplate.hasKey(key)) { redisTemplate.opsForValue().set(key, 0L, 100, TimeUnit.SECONDS); } int rate = basicConf.getClusterLimitRate(); // 当集群中当前秒的并发量达到了设定的值, 不进行处理 // 注意集群中的网关与所在服务器时间必须同步 if (redisTemplate.opsForValue().increment(key, 1) > rate) { ctx.setSendZuulResponse(false); ctx.set("isSuccess", false); ResponseData data = ResponseData.fail("当前负载太高,请稍后重试", ResponseCode.LIMIT_ERROR_CODE.getCode()); ctx.setResponseBody(JsonUtils.toJson(data)); ctx.getResponse().setContentType("application/json;charset=utf-8"); return null; } } catch (Exception e) { log.error("集群限流异常", e); // Redis 挂掉等异常处理,可以继续单节点限流 // 单节点限流 rateLimiter.acquire(); } return null; } } 我们来看看 run 方法里面的逻辑,首先我们是获取了当前时间的时间戳然后转换成秒,定义了一个 Redis 的 key。判断这个 key 是否存在,不存在则插入一个,初始值为 0,然后通过 increment 来为这个 key 累加计数,并获取累加之后的值,increment 是原子性的,不会有并发问题,如果当前秒的数量超出了我们设定的值那就说明当前的并发量已经达到了极限值,然后直接拒绝请求。 这里还需要进行异常处理,前文推荐用单节点限流的方式来进行就是因为集群性质的限流需要依赖第三方中间件,如果中间件挂了,那么就会影响现有的业务,这里需要处理的是如果操作 Redis 出异常了怎么办? 首先是进行集群的限流,如果 Redis 出现挂了之类的问题,捕获到异常之后立刻启用单节点限流,进行双重保护。当然必须有完整的监控系统,当 Redis 出现问题之后必须马上处理。Redis 在生产环境中必须用集群,当然集群也有可能会出问题,所以单节点限流是一种比较好的方案。 具体服务限流 前面我们学习了如何进行单节点的限流和集群的限流。虽然抗住了整体的并发量,但是会有一个弊端,如果这些并发量都是针对一个服务的,那么这个服务还是会扛不住的,针对具体的服务做具体的限制才是最好的选择。 基于前面的基础,要针对具体的服务做限制是比较简单的事情,针对单节点限流我们的做法如下。 之前是用一个 RateLimiter 来防止整体的并发量,针对具体服务的前期是需要知道当前的请求会被转发到哪个服务里去,知道了这个我们只需要为每个服务创建一个 RateLimiter,不同的服务用不同的 RateLimiter 就可以实现具体服务的限制了。 集群的限流是通过时间的秒作为 key 来计数实现的,如果是针对具体的服务,只需要把服务名称加到 key 中就可以了,即一个服务就是一个 key,限流的操作自然而然是针对具体的服务。 可以用 Zuul 提供的 Route Filter 来做,在 Route Filter 中可以直接获取当前请求是要转发到哪个服务,代码如下所示。 RequestContext ctx = RequestContext.getCurrentContext(); Object serviceId = ctx.get("serviceId"); serviceId 就是 Eureka 中注册的服务名称。 具体接口限流 即使我们做了整体的集群限流,如果某个服务的具体限流持续并发量很大且是同一个接口,那么还会影响到其他接口的使用,华章所有的资源都被这一个接口占用了,其他的接口请求过来只能等待或者抛弃,所以我们需要将限流做得更细,可以针对具体的 API 接口进行并发控制。 具体的接口控制并发量我们将这个控制放到具体的服务中,之所以不放到 API 网关去做控制是因为 API 的量太大了,如果统一到 API 网关来控制那么需要配置很多 API 的并发量信息,如果放到具体的服务上,我们可以通过注解的方式在接口的方法上做文章,添加一个注解就可以实现并发控制,还可以结合我们的 Apollo 来做动态修改,当然也可以在 API 网关做,笔者推荐在具体的服务上做。 首先我们定义一个注解,用来标识某个接口需要进行并发控制,这个注解是通用的,可以放在公共的库中。在注解中定义一个 confKey,这个 key 对应的是 Apollo 中的配置 key,也就是说我们这个并发的数字不写死,而是通过 Apollo 来做关联,到时候可以动态修改,实时生效,代码如下所示。 /** * 对 API 进行访问速度限制 * 限制的速度值在Apollo配置中通过 key 关联 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiRateLimit { /** * Apollo配置中的 key * * @return */ String confKey(); } 接下来我们定义一个启动监听器,这个也是通用的,可以放在公共库中。这个启动监听器的主要作用就是扫描所有的 API 接口类,也就是我们的 Controller。 获取 Controller 中所有加了 ApiRateLimit 注解的信息,然后进行初始化操作,控制并发我们这里用 JDK 自带的 Semaphore 来实现,当然你也可以用之前讲的 RateLimiter,代码如下所示。 @Component public class InitApiLimitRateListener implements ApplicationContextAware { public void setApplicationContext(ApplicationContext ctx) throws BeansException { Environment environment = ctx.getEnvironment(); String defaultLimit = environment.getProperty("open.api.defaultLimit"); Object rate = defaultLimit == null ? 100 : defaultLimit; ApiLimitAspect.semaphoreMap.put("open.api.defaultLimit", new Semaphore(Integer.parseInt(rate.toString()))); Map<String, Object> beanMap = ctx.getBeansWithAnnotation(RestController.class); Set<String> keys = beanMap.keySet(); for (String key : keys) { Class<?> clz = beanMap.get(key).getClass(); String fullName = beanMap.get(key).getClass().getName(); if (fullName.contains("EnhancerBySpringCGLIB") || fullName.contains("$$")) { fullName = fullName.substring(0, fullName.indexOf("$$")); try { clz = Class.forName(fullName); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } Method[] methods = clz.getMethods(); for (Method method : methods) { if (method.isAnnotationPresent(ApiRateLimit.class)) { String confKey = method.getAnnotation(ApiRateLimit.class).confKey(); if (environment.getProperty(confKey) != null) { int limit = Integer.parseInt(environment.getProperty(confKey)); ApiLimitAspect.semaphoreMap.put(confKey, new Semaphore(limit)); } } } } } } 上面的代码就是初始化的整个逻辑,在最开始的时候就是获取 open.api.defaultLimit 的值,那么这个值会配置在 Apollo 中,如果没有则给予一个默认值。open.api.defaultLimit 是考虑到并不是所有的接口都需要配置具体的限制并发的数量,所以给了一个默认的限制,也就是说没有加 ApiRateLimit 注解的接口就用这个默认的并发限制。 拿到所有的 Controller 类的信息,通过判断类上是否有 RestController 注解来确定这就是一个接口,然后获取类中所有的方法,获取方法上有 ApiRateLimit 注解的 key,通过 key获取配置的值,然后 new 一个 Semaphore 存入控制并发的切面的 map 中,切面下面会定义。 通过切面来对访问的接口进行并发控制,当然也可以用拦截器、过滤器之类的,切面也是共用的,可以放公共库中,代码如下所示。 /** * 具体 API 并发控制 */ @Aspect @Order(value = Ordered.HIGHEST_PRECEDENCE) public class ApiLimitAspect { public static Map<String, Semaphore> semaphoreMap = new ConcurrentHashMap<String, Semaphore>(); @Around("execution(*com.biancheng.*.*.controller.*.*(..))") public Object around(ProceedingJoinPoint joinPoint) { Object result = null; Semaphore semap = null; Class<?> clazz = joinPoint.getTarget().getClass(); String key = getRateLimitKey(clazz, joinPoint.getSignature().getName()); if (key != null) { semap = semaphoreMap.get(key); } else { semap = semaphoreMap.get("open.api.defaultLimit"); } try { semap.acquire(); result = joinPoint.proceed(); } catch (Throwable e) { throw new RuntimeException(e); } finally { semap.release(); } return result; } private String getRateLimitKey(Class<?> clazz, String methodName) { for (Method method : clazz.getDeclaredMethods()) { if (method.getName().equals(methodName)) { if (method.isAnnotationPresent(ApiRateLimit.class)) { String key = method.getAnnotation(ApiRateLimit.class).confKey(); return key; } } } return null; } } 整个切面中的代码量不多,但是作用非常大,所有接口的请求都将会经过它,这是一个环绕通知。 第一行是一个 ConcurrentHashMap,用来存储我们之前在监听器里面初始化好的 Semaphore 对象,需要重点关注的是 around 中的逻辑。 首先获取当前访问的目标对象以及方法名称,通过 getRateLimitKey 获取当前访问的方法是否有限制并发的 key,通过 key 从 semaphoreMap 中获取对应的 Semaphore 对象做并发限制。 配置进行并发控制的切面,代码如下所示。 @Configuration public class BeanConfig { /** * 具体的 API 并发控制 * * @return */ @Bean public ApiLimitAspect apiLimitAspect() { return new ApiLimitAspect(); } } 到这里整个限制的流程就结束了。启动服务,可以用并发测试工具 Apache ab 来测试效果。将并发数量配置为 1,测试请求 1000 次看需要多长时间,然后调大并发数量,再次请求,虽然比较耗时,但我们可以发现并发配置数量越小的耗时时间越长,这就证明并发控制生效了。 目前没有加我们自定义的注解,所有的接口都是用默认的并发控制数量,如果我们想对某个接口单独做并发控制,只需要在方法上加上 ApiRateLimit 注解即可,具体代码如下所示。 /** * 获取房产信息 * * @param houseId 房产编号 * @return */ @ApiRateLimit(confKey = "open.api.hosueInfo") @GetMapping("/{houseId}") public ResponseData hosueInfo(@PathVariable("houseId") Long houseId, HttpServletRequest request) { String uid = request.getHeader("uid"); System.err.println("===" + uid); return ResponseData.ok(houseService.getHouseInfo(houseId)); } ApiRateLimit 中配置的 confKey 要和 Apollo 配置中的 key 对应才行。目前限流的信号量对象是在启动时进行初始化的,如果需要实现在 Apollo 中动态新增或者修改配置也能生效的话,需要对配置的修改进行监听,然后动态创建信号量对象添加到 semaphoreMap 中。

优秀的个人博客,低调大师

中国私有云行业现状及竞争:市场规模快速增长 竞争格局较为激烈

私有云是为单一客户使用而构建的,由客户拥有基础设施,并可以控制在此基础设施上部署应用程序的方式,其核心特征是专属资源。在私有云技术发展历程共经历了三代。 私有云技术发展历程 数据来源:公开资料整理 根据数据显示,2018年私有云市场的应用场景细分及对应的市场份额中政府、制造、金融、医疗为私有云主要应用场景,合计占总规模的70%。 2018年私有云市场应用场景市场份额占比 数据来源:公开资料整理 私有云可以分为包含硬件产品、软件产品和服务。其中硬件产品指主要以物理形态支持私有云的IT基础设施;软件产品指私有云的软件形态产品;而服务贯穿私有云厂商为企业提供服务的全过程。根据中国信通院相关资料显示,目前在私有云细分市场中,硬件产品占比最大,达到70%左右。 私有云产业链分布 数据来源:公开资料整理 2016-2018年我国私有云细分市场变动情况 数据来源:公开资料整理 海外巨头技术路径各不相同,国内企业竞争激烈。综观当下各种厂商的混合云解决方案可以看到,由于厂商各自的主业务不同,自然地形成了不同技术路线:一类是以AWS、阿里、微软等公有云服务商为代表,力推的是将公有云技术架构延展到私有云中。另一类是以VMware、各OpenStack服务商为代表,方案是将自己的私有云与公有云集成,或者将私有云架构直接部署到公有云中。简而言之,国内外行业巨头纷纷布局混合云产品线,以便在未来市场发展中占取先机。 国内外云产品巨头产品与特点比对 数据来源:公开资料整理 从2018年中国云管理平台份额来看,按市场销售金额计算,新华三、华为、浪潮、华云处于市场领先位置,云管理平台参与商增加,私有云市场份额呈现分散化格局。 2018年我国私有云市场份额分布情况 数据来源:公开资料整理 得益于国内企业对于私有云部署的偏好,我国私有云市场仍具有较大的发展空间。数据显示,2020年中国私有云市场规模将达到788亿元,同比增长22.36%,年均增长保持20%左右。 2016-2022年我国私有云市场规模及增长情况 数据来源:公开资料整理(WYD) 【责任编辑: 未丽燕 TEL:(010)68476606】

优秀的个人博客,低调大师

[雪峰磁针石博客]数据分析工具pandas快速入门教程2-pandas数据结构

创建数据 Series和python的列表类似。DataFrame则类似值为Series的字典。 create.py #!/usr/bin/env python3 # -*- coding: utf-8 -*- # create.py import pandas as pd print("\n\n创建序列Series") s = pd.Series(['banana', 42]) print(s) print("\n\n指定索引index创建序列Series") s = pd.Series(['Wes McKinney', 'Creator of Pandas'], index=['Person', 'Who']) print(s) # 注意:列名未必为执行的顺序,通常为按字母排序 print("\n\n创建数据帧DataFrame") scientists = pd.DataFrame({ ' Name': ['Rosaline Franklin', 'William Gosset'], ' Occupation': ['Chemist', 'Statistician'], ' Born': ['1920-07-25', '1876-06-13'], ' Died': ['1958-04-16', '1937-10-16'], ' Age': [37, 61]}) print(scientists) print("\n\n指定顺序(index和columns)创建数据帧DataFrame") scientists = pd.DataFrame( data={'Occupation': ['Chemist', 'Statistician'], 'Born': ['1920-07-25', '1876-06-13'], 'Died': ['1958-04-16', '1937-10-16'], 'Age': [37, 61]}, index=['Rosaline Franklin', 'William Gosset'], columns=['Occupation', 'Born', 'Died', 'Age']) print(scientists) 执行结果: $ ./create.py 创建序列Series 0 banana 1 42 dtype: object 指定索引index创建序列Series Person Wes McKinney Who Creator of Pandas dtype: object 创建数据帧DataFrame Name Occupation Born Died Age 0 Rosaline Franklin Chemist 1920-07-25 1958-04-16 37 1 William Gosset Statistician 1876-06-13 1937-10-16 61 指定顺序(index和columns)创建数据帧DataFrame Occupation Born Died Age Rosaline Franklin Chemist 1920-07-25 1958-04-16 37 William Gosset Statistician 1876-06-13 1937-10-16 61 Series 官方文档:http://pandas.pydata.org/pandas-docs/stable/generated/pandas.Series.html Series的属性 属性 描述 loc 使用索引值获取子集 iloc 使用索引位置获取子集 dtype或dtypes 类型 T 转置 shape 数据的尺寸 size 元素的数量 values ndarray或类似ndarray的Series Series的方法 方法 描述 append 连接2个或更多系列 corr 计算与其他Series的关联 cov 与其他Series计算协方差 describe 计算汇总统计 drop duplicates 返回一个没有重复项的Series equals Series是否具有相同的元素 get values 获取Series的值,与values属性相同 hist 绘制直方图 min 返回最小值 max 返回最大值 mean 返回算术平均值 median 返回中位数 mode(s) 返回mode(s) replace 用指定值替换系列中的值 sample 返回Series中值的随机样本 sort values 排序 to frame 转换为数据帧 transpose 返回转置 unique 返回numpy.ndarray唯一值 series.py #!/usr/bin/python3 # -*- coding: utf-8 -*- # CreateDate: 2018-3-14 # series.py import pandas as pd import numpy as np scientists = pd.DataFrame( data={'Occupation': ['Chemist', 'Statistician'], 'Born': ['1920-07-25', '1876-06-13'], 'Died': ['1958-04-16', '1937-10-16'], 'Age': [37, 61]}, index=['Rosaline Franklin', 'William Gosset'], columns=['Occupation', 'Born', 'Died', 'Age']) print(scientists) # 从数据帧(DataFrame)获取的行或者列为Series first_row = scientists.loc['William Gosset'] print(type(first_row)) print(first_row) # index和keys是一样的 print(first_row.index) print(first_row.keys()) print(first_row.values) print(first_row.index[0]) print(first_row.keys()[0]) # Pandas.Series和numpy.ndarray很类似 ages = scientists['Age'] print(ages) # 统计,更多参考http://pandas.pydata.org/pandas-docs/stable/basics.html#descriptive-statistics print(ages.mean()) print(ages.min()) print(ages.max()) print(ages.std()) scientists = pd.read_csv('../data/scientists.csv') ages = scientists['Age'] print(ages) print(ages.mean()) print(ages.describe()) print(ages[ages > ages.mean()]) print(ages > ages.mean()) manual_bool_values = [True, True, False, False, True, True, False, False] print(ages[manual_bool_values]) print(ages + ages) print(ages * ages) print(ages + 100) print(ages * 2) print(ages + pd.Series([1, 100])) # print(ages + np.array([1, 100])) 会报错,不同类型相加,大小一定要一样 print(ages + np.array([1, 100, 1, 100, 1, 100, 1, 100])) # 排序: 默认有自动排序 print(ages) rev_ages = ages.sort_index(ascending=False) print(rev_ages) print(ages * 2) print(ages + rev_ages) 执行结果 $ python3 series.py Occupation Born Died Age Rosaline Franklin Chemist 1920-07-25 1958-04-16 37 William Gosset Statistician 1876-06-13 1937-10-16 61 <class 'pandas.core.series.Series'> Occupation Statistician Born 1876-06-13 Died 1937-10-16 Age 61 Name: William Gosset, dtype: object Index(['Occupation', 'Born', 'Died', 'Age'], dtype='object') Index(['Occupation', 'Born', 'Died', 'Age'], dtype='object') ['Statistician' '1876-06-13' '1937-10-16' 61] Occupation Occupation Rosaline Franklin 37 William Gosset 61 Name: Age, dtype: int64 49.0 37 61 16.97056274847714 0 37 1 61 2 90 3 66 4 56 5 45 6 41 7 77 Name: Age, dtype: int64 59.125 count 8.000000 mean 59.125000 std 18.325918 min 37.000000 25% 44.000000 50% 58.500000 75% 68.750000 max 90.000000 Name: Age, dtype: float64 1 61 2 90 3 66 7 77 Name: Age, dtype: int64 0 False 1 True 2 True 3 True 4 False 5 False 6 False 7 True Name: Age, dtype: bool 0 37 1 61 4 56 5 45 Name: Age, dtype: int64 0 74 1 122 2 180 3 132 4 112 5 90 6 82 7 154 Name: Age, dtype: int64 0 1369 1 3721 2 8100 3 4356 4 3136 5 2025 6 1681 7 5929 Name: Age, dtype: int64 0 137 1 161 2 190 3 166 4 156 5 145 6 141 7 177 Name: Age, dtype: int64 0 74 1 122 2 180 3 132 4 112 5 90 6 82 7 154 Name: Age, dtype: int64 0 38.0 1 161.0 2 NaN 3 NaN 4 NaN 5 NaN 6 NaN 7 NaN dtype: float64 0 38 1 161 2 91 3 166 4 57 5 145 6 42 7 177 Name: Age, dtype: int64 0 37 1 61 2 90 3 66 4 56 5 45 6 41 7 77 Name: Age, dtype: int64 7 77 6 41 5 45 4 56 3 66 2 90 1 61 0 37 Name: Age, dtype: int64 0 74 1 122 2 180 3 132 4 112 5 90 6 82 7 154 Name: Age, dtype: int64 0 74 1 122 2 180 3 132 4 112 5 90 6 82 7 154 Name: Age, dtype: int64 数据帧(DataFrame) DataFrame是最常见的Pandas对象,可认为是Python存储类似电子表格的数据的方式。Series多常见功能都包含在DataFrame中。 子集的方法 注意ix现在已经不推荐使用。 DataFrame常用的索引操作有: 方式 描述 df[val] 选择单个列 df [[ column1, column2, ... ]] 选择多个列 df.loc[val] 选择行 loc [[ label1 , label2 ,...]] | 选择多行 |df.loc[:, val] | 基于行index选择列 | df.loc[val1, val2] | 选择行列 |df.iloc[row number] | 基于行数选择行 | iloc [[ row1, row2, ...]] Multiple rows by row number | 基于行数选择多行 |df.iloc[:, where] | 选择列 | df.iloc[where_i, where_j] | 选择行列 |df.at[label_i, label_j] | 选择值 |df.iat[i, j] | 选择值 |reindex method | 通过label选择多行或列 |get_value, set_value | 通过label选择耽搁行或列 df[bool] | 选择行df [[ bool1, bool2, ...]] | 选择行df[ start :stop: step ] | 基于行数选择行 #!/usr/bin/python3 # -*- coding: utf-8 -*- # CreateDate: 2018-3-31 # df.py import pandas as pd import numpy as np scientists = pd.read_csv('../data/scientists.csv') print(scientists[scientists['Age'] > scientists['Age'].mean()]) first_half = scientists[: 4] second_half = scientists[ 4 :] print(first_half) print(second_half) print(first_half + second_half) print(scientists * 2) 执行结果 #!/usr/bin/python3 # -*- coding: utf-8 -*- # df.py import pandas as pd import numpy as np scientists = pd.read_csv('../data/scientists.csv') print(scientists[scientists['Age'] > scientists['Age'].mean()]) first_half = scientists[: 4] second_half = scientists[ 4 :] print(first_half) print(second_half) print(first_half + second_half) print(scientists * 2) 执行结果 $ python3 df.py Name Born Died Age Occupation 1 William Gosset 1876-06-13 1937-10-16 61 Statistician 2 Florence Nightingale 1820-05-12 1910-08-13 90 Nurse 3 Marie Curie 1867-11-07 1934-07-04 66 Chemist 7 Johann Gauss 1777-04-30 1855-02-23 77 Mathematician Name Born Died Age Occupation 0 Rosaline Franklin 1920-07-25 1958-04-16 37 Chemist 1 William Gosset 1876-06-13 1937-10-16 61 Statistician 2 Florence Nightingale 1820-05-12 1910-08-13 90 Nurse 3 Marie Curie 1867-11-07 1934-07-04 66 Chemist Name Born Died Age Occupation 4 Rachel Carson 1907-05-27 1964-04-14 56 Biologist 5 John Snow 1813-03-15 1858-06-16 45 Physician 6 Alan Turing 1912-06-23 1954-06-07 41 Computer Scientist 7 Johann Gauss 1777-04-30 1855-02-23 77 Mathematician Name Born Died Age Occupation 0 NaN NaN NaN NaN NaN 1 NaN NaN NaN NaN NaN 2 NaN NaN NaN NaN NaN 3 NaN NaN NaN NaN NaN 4 NaN NaN NaN NaN NaN 5 NaN NaN NaN NaN NaN 6 NaN NaN NaN NaN NaN 7 NaN NaN NaN NaN NaN Name Born \ 0 Rosaline FranklinRosaline Franklin 1920-07-251920-07-25 1 William GossetWilliam Gosset 1876-06-131876-06-13 2 Florence NightingaleFlorence Nightingale 1820-05-121820-05-12 3 Marie CurieMarie Curie 1867-11-071867-11-07 4 Rachel CarsonRachel Carson 1907-05-271907-05-27 5 John SnowJohn Snow 1813-03-151813-03-15 6 Alan TuringAlan Turing 1912-06-231912-06-23 7 Johann GaussJohann Gauss 1777-04-301777-04-30 Died Age Occupation 0 1958-04-161958-04-16 74 ChemistChemist 1 1937-10-161937-10-16 122 StatisticianStatistician 2 1910-08-131910-08-13 180 NurseNurse 3 1934-07-041934-07-04 132 ChemistChemist 4 1964-04-141964-04-14 112 BiologistBiologist 5 1858-06-161858-06-16 90 PhysicianPhysician 6 1954-06-071954-06-07 82 Computer ScientistComputer Scientist 7 1855-02-231855-02-23 154 MathematicianMathematician 修改列 #!/usr/bin/python3 # -*- coding: utf-8 -*- # Author: xurongzhong#126.com wechat:pythontesting qq:37391319 # qq群:144081101 591302926 567351477 # CreateDate: 2018-06-07 # change.py import pandas as pd import numpy as np import random scientists = pd.read_csv('../data/scientists.csv') print(scientists['Born'].dtype) print(scientists['Died'].dtype) print(scientists.head()) # 转为日期 参考:https://docs.python.org/3.5/library/datetime.html born_datetime = pd.to_datetime(scientists['Born'], format='%Y-%m-%d') died_datetime = pd.to_datetime(scientists['Died'], format='%Y-%m-%d') # 增加列 scientists['born_dt'], scientists['died_dt'] = (born_datetime, died_datetime) print(scientists.shape) print(scientists.head()) random.seed(42) random.shuffle(scientists['Age']) # 此修改会作用于scientists print(scientists.head()) scientists['age_days_dt'] = (scientists['died_dt'] - scientists['born_dt']) print(scientists.head()) 执行结果: $ python3 change.py object object Name Born Died Age Occupation 0 Rosaline Franklin 1920-07-25 1958-04-16 37 Chemist 1 William Gosset 1876-06-13 1937-10-16 61 Statistician 2 Florence Nightingale 1820-05-12 1910-08-13 90 Nurse 3 Marie Curie 1867-11-07 1934-07-04 66 Chemist 4 Rachel Carson 1907-05-27 1964-04-14 56 Biologist (8, 7) Name Born Died Age Occupation born_dt \ 0 Rosaline Franklin 1920-07-25 1958-04-16 37 Chemist 1920-07-25 1 William Gosset 1876-06-13 1937-10-16 61 Statistician 1876-06-13 2 Florence Nightingale 1820-05-12 1910-08-13 90 Nurse 1820-05-12 3 Marie Curie 1867-11-07 1934-07-04 66 Chemist 1867-11-07 4 Rachel Carson 1907-05-27 1964-04-14 56 Biologist 1907-05-27 died_dt 0 1958-04-16 1 1937-10-16 2 1910-08-13 3 1934-07-04 4 1964-04-14 /usr/lib/python3.5/random.py:272: SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy x[i], x[j] = x[j], x[i] Name Born Died Age Occupation born_dt \ 0 Rosaline Franklin 1920-07-25 1958-04-16 66 Chemist 1920-07-25 1 William Gosset 1876-06-13 1937-10-16 56 Statistician 1876-06-13 2 Florence Nightingale 1820-05-12 1910-08-13 41 Nurse 1820-05-12 3 Marie Curie 1867-11-07 1934-07-04 77 Chemist 1867-11-07 4 Rachel Carson 1907-05-27 1964-04-14 90 Biologist 1907-05-27 died_dt 0 1958-04-16 1 1937-10-16 2 1910-08-13 3 1934-07-04 4 1964-04-14 Name Born Died Age Occupation born_dt \ 0 Rosaline Franklin 1920-07-25 1958-04-16 66 Chemist 1920-07-25 1 William Gosset 1876-06-13 1937-10-16 56 Statistician 1876-06-13 2 Florence Nightingale 1820-05-12 1910-08-13 41 Nurse 1820-05-12 3 Marie Curie 1867-11-07 1934-07-04 77 Chemist 1867-11-07 4 Rachel Carson 1907-05-27 1964-04-14 90 Biologist 1907-05-27 died_dt age_days_dt 0 1958-04-16 13779 days 1 1937-10-16 22404 days 2 1910-08-13 32964 days 3 1934-07-04 24345 days 4 1964-04-14 20777 days 数据导入导出 out.py #!/usr/bin/python3 # -*- coding: utf-8 -*- # Author: china-testing#126.com wechat:pythontesting qq群:630011153 # CreateDate: 2018-3-31 # out.py import pandas as pd import numpy as np import random scientists = pd.read_csv('../data/scientists.csv') names = scientists['Name'] print(names) names.to_pickle('../output/scientists_names_series.pickle') scientists.to_pickle('../output/scientists_df.pickle') # .p, .pkl, .pickle 是常用的pickle文件扩展名 scientist_names_from_pickle = pd.read_pickle('../output/scientists_df.pickle') print(scientist_names_from_pickle) names.to_csv('../output/scientist_names_series.csv') scientists.to_csv('../output/scientists_df.tsv', sep='\t') # 不输出行号 scientists.to_csv('../output/scientists_df_no_index.csv', index=None) # Series可以转为df再输出成excel文件 names_df = names.to_frame() names_df.to_excel('../output/scientists_names_series_df.xls') names_df.to_excel('../output/scientists_names_series_df.xlsx') scientists.to_excel('../output/scientists_df.xlsx', sheet_name='scientists', index=False) 执行结果: $ python3 out.py 0 Rosaline Franklin 1 William Gosset 2 Florence Nightingale 3 Marie Curie 4 Rachel Carson 5 John Snow 6 Alan Turing 7 Johann Gauss Name: Name, dtype: object Name Born Died Age Occupation 0 Rosaline Franklin 1920-07-25 1958-04-16 37 Chemist 1 William Gosset 1876-06-13 1937-10-16 61 Statistician 2 Florence Nightingale 1820-05-12 1910-08-13 90 Nurse 3 Marie Curie 1867-11-07 1934-07-04 66 Chemist 4 Rachel Carson 1907-05-27 1964-04-14 56 Biologist 5 John Snow 1813-03-15 1858-06-16 45 Physician 6 Alan Turing 1912-06-23 1954-06-07 41 Computer Scientist 7 Johann Gauss 1777-04-30 1855-02-23 77 Mathematician 注意:序列一般是直接输出成excel文件 更多的输入输出方法: 方式 描述 to_clipboard 将数据保存到系统剪贴板进行粘贴 to_dense 将数据转换为常规“密集”DataFrame to_dict 将数据转换为Python字典 to_gbq 将数据转换为Google BigQuery表格 toJidf 将数据保存为分层数据格式(HDF) to_msgpack 将数据保存到可移植的类似JSON的二进制文件中 toJitml 将数据转换为HTML表格 tojson 将数据转换为JSON字符串 toJatex 将数据转换为LTEXtabular环境 to_records 将数据转换为记录数组 to_string 将DataFrame显示为stdout的字符串 to_sparse 将数据转换为SparceDataFrame to_sql 将数据保存到SQL数据库中 to_stata 将数据转换为Stata dta文件 读CSV文件 read_csv.py #!/usr/bin/python3 # -*- coding: utf-8 -*- # Author: china-testing#126.com wechat:pythontesting QQ群:630011153 # CreateDate: 2018-3-9 # read_csv.py import pandas as pd df = pd.read_csv("1.csv", header=None) # 不读取列名 print("df:") print(df) print("df.head():") print(df.head()) # head(self, n=5),默认为5行,类似的有tail print("df.tail():") print(df.tail()) df = pd.read_csv("1.csv") # 默认读取列名 print("df:") print(df) df = pd.read_csv("1.csv", names=['号码','群号']) # 自定义列名 print("df:") print(df) # 自定义列名,去掉第一行 df = pd.read_csv("1.csv", skiprows=[0], names=['号码','群号']) print("df:") print(df) 执行结果: df: 0 1 0 qq qqgroup 1 37391319 144081101 2 37391320 144081102 3 37391321 144081103 4 37391322 144081104 5 37391323 144081105 6 37391324 144081106 7 37391325 144081107 8 37391326 144081108 9 37391327 144081109 10 37391328 144081110 11 37391329 144081111 12 37391330 144081112 13 37391331 144081113 14 37391332 144081114 15 37391333 144081115 df.head(): 0 1 0 qq qqgroup 1 37391319 144081101 2 37391320 144081102 3 37391321 144081103 4 37391322 144081104 df.tail(): 0 1 11 37391329 144081111 12 37391330 144081112 13 37391331 144081113 14 37391332 144081114 15 37391333 144081115 df: qq qqgroup 0 37391319 144081101 1 37391320 144081102 2 37391321 144081103 3 37391322 144081104 4 37391323 144081105 5 37391324 144081106 6 37391325 144081107 7 37391326 144081108 8 37391327 144081109 9 37391328 144081110 10 37391329 144081111 11 37391330 144081112 12 37391331 144081113 13 37391332 144081114 14 37391333 144081115 df: 号码 群号 0 qq qqgroup 1 37391319 144081101 2 37391320 144081102 3 37391321 144081103 4 37391322 144081104 5 37391323 144081105 6 37391324 144081106 7 37391325 144081107 8 37391326 144081108 9 37391327 144081109 10 37391328 144081110 11 37391329 144081111 12 37391330 144081112 13 37391331 144081113 14 37391332 144081114 15 37391333 144081115 df: 号码 群号 0 37391319 144081101 1 37391320 144081102 2 37391321 144081103 3 37391322 144081104 4 37391323 144081105 5 37391324 144081106 6 37391325 144081107 7 37391326 144081108 8 37391327 144081109 9 37391328 144081110 10 37391329 144081111 11 37391330 144081112 12 37391331 144081113 13 37391332 144081114 14 37391333 144081115 写CSV文件 #!/usr/bin/python3 # -*- coding: utf-8 -*- # write_csv.py import pandas as pd data ={'qq': [37391319,37391320], 'group':[1,2]} df = pd.DataFrame(data=data, columns=['qq','group']) df.to_csv('2.csv',index=False) 读写excel和csv类似,不过要改用read_excel来读,excel_summary_demo, 提供了多个excel求和的功能,可以做为excel读写的实例,这里不再赘述。 参考资料 技术支持qq群144081101 591302926 567351477 钉钉免费群21745728 本文最新版本地址 本文涉及的python测试开发库 谢谢点赞! 本文相关海量书籍下载 源码下载 本文英文版书籍下载

优秀的个人博客,低调大师

十年云化之路 中国移动快速向下一代IT架构转型

中国移动一直致力于成为数字化创新的全球领先运营商,于2016年中开始推进实施“大连接”战略,从聚焦管道连接服务向平台级服务和垂直应用领域拓展,打造电信级的端到端信息基础设施体系和内容应用体系。 如果说过去中国移动通信基本完成了“沟通泛在”的要求和部分的“信息泛在”要求的话,那么,现在更高的“信息泛在”、“感官泛在”、“智能泛在”则要求移动通信网络走向敏捷化、开放化和软件化,这就是常说的“通信4.0”,而云计算是推进“大连接”战略和“通信4.0”亟需打造的关键能力之一。 实际上,云计算平台能力对于中国移动提升创新能力、推进网络转型和锻造人才队伍至关重要。云计算资源池是面向未来最重要的信息基础设施之一,是中国移动打造下一代网络和IT能力提升的关键。 砥砺前行 中国移动10年云化之路 中国移动积极发展云计算以提升网络智能化、业务生态化、运营智慧化,在中国移动通信集团公司党组成员、副总裁李正茂看来,“一方面中国移动可以利用云计算的技术、产品和理念,创新自身的IT架构、网络架构、商业模式和运营模式,提升体验和效率;另一方面中国移动可以发挥自身安全可信的品牌影响、高质量的连接与管道优势,通过打造统一云平台,面向企业以云服务的方式提供计算、存储、网络、通讯、IoT、视频等服务,重塑B2B商业模式,扩大市场空间。” 早在2007年,中国移动就启动了“大云”计划,正式开始了云计算、大数据的研究和应用,到现在已整整十年。十年间,“大云”产品已经在推动中国移动IT技术架构变革和业务创新,在支撑各行业企业智慧经营和服务提升方面做出了重要贡献,包括建成了中国移动一级私有云,有着全球最大的OpenStack集群、最大的SDN商用集群。中国移动还用“大云”建设了中国移动公众服务云,已上线3000个物理节点。 中国移动大云4.0 依托大云平台,中国移动打造的以大数据和云计算为核心能力的新一代IT平台正在成为中国移动数字化转型和对外赋能的新动力。如今,中国移动大云更新到4.0,覆盖I/Re/S/M/A五层架构,实现大规模实例、多场景服务、跨行业应用,为各行业提供公有云、私有云、混合云、专有云、行业云总体解决方案,大云4.0主要包含云计算、大数据技术和平台产品,包括Hadoop系统、搜索引擎、Pass平台、大云数据中心操作系统等26项核心产品,实现在大IT技术架构下的全新平台、服务和生态构建能力。 大云推动中国移动IT能力提升和数字化转型 中移软件副总经理孙少陵表示,移动公有云采用一云多池“5+X”两级架构体系。中国移动集团五大资源池构成核心层,省公司资源池作为边缘计算节点构成接入层。2016年,移动云完成南北资源池布局,Openstack集群规模超3000节点;2017年移动云新增湖南资源池节点,扩容北京节点,统一纳管云南等省级资源池节点,提供39项产品和7个支撑系统,已获得多项认证,可提供安全、可信的云服务。 对内方面,通过总部大规模集中新建一级平台,省公司开展多域、异地、异构资源池整合改造并纳管到一级平台,形成“一级平台、两级管理”的统一架构,逐步实现中国移动云计算资源的统一管理、统一运维和集中运营,有效支撑中国移动IT架构转型、业务创新和降本增效。 中国移动实现云计算资源集中化 作为全球第五个、中国首个OpenStack Superuser,“中国移动私有云应用范围超过27个省份、部署规模超过1万节点,拥有最大的OpenStack资源池和SDN商用集群(3000节点)、最大的物理机和虚拟机统一管理集群、首次实现异构SDN统一管理、物理机和虚拟机混合组网。” 孙少陵说。 由于中国移动OpenStack集群庞大,其还联合合作伙伴展开了OpenStack大规模物理机上的可伸缩性、可扩展性、动态扩容性和大规模服务能力的测试,在英特尔等合作伙伴的支持下,中国移动OpenStack性能得到显着提升。 电信云方面,为保障电信网络NFV转型,中国移动提出Novonet2020战略,旨在构建“资源可全局共享调度、容量可弹性伸缩、架构可灵活调整、能力可全面开放”的新一代网络。中国移动基于开源ONAP(OPEN-O+ECOMP)和Tacker研发NFV O+产品;基于Openstack开发云OS操作系统产品,在开源基础上优化实时内核、DPDK支持、RDMA支持等性能。由BC-Linux、KVM、Ceph、OVS等组成虚拟化层;BC-EPC提供资源管理、监控、告警等资源池运维功能,同时提供CI/CD能力构建NFV集成测试环境,支持NFV应用的敏捷开发和持续集成。 构建“三环一体”云计算产业生态 在推进云计算和大数据发展进程中,中国移动注重合作共赢,已初步构建了“三环一体”的云计算产业生态系统:与内环单位合作重点开展预研和核心技术研发、社区合作;中环重点开展产品和解决方案集成领域合作;外环重点开展售后服务类合作。中国移动近日揭牌成立了云计算共创中心,联手华为、浪潮、英特尔等国内外合作伙伴在开源发展、产品研发、解决方案提供、行业应用等方面共同打造云计算生态体系。 在内环开展预研和核心技术研发、开源社区类合作中,英特尔作为中国移动重要的合作伙伴,以顾问形式参与中国移动的各项合作,包括服务器定制化、AI、大数据、5G网络转型等。例如在无线网络虚拟化方面,英特尔支撑中国移动在C-RAN这种高实时性应用场景中,灵活运用英特尔资源分配技术,提高整体性能,在消除多线程调度干扰和缩短系统响应时间等方面获得巨大提升。 此外,Cloud Native架构越来越被运营商关注,英特尔针对其技术挑战和难点,与中国移动和中兴通讯一起进行了初步的研究和测试,并在其中提供了关键的解决方案和优化建议,包括借助DPDK加速容器网络连接、基于 Multus技术加速容器平台层、在 Kubernetes中通过节点功能发现(NFD)提供性能加速等,英特尔通过提供支撑和运行微服务化NFV应用的容器方案,帮助中国移动探索网络转型新路径。 在大云的落地中,以OpenStack开源社区为基础,英特尔针对中国移动大云OpenStack云计算产品进行性能优化。用英特尔中国运营商事业部总经理叶唯琛的话说,“英特尔和中国移动在IT领域是无处不在的合作关系。”在中国移动OpenStack集群、Hadoop集群中,英特尔提供了不限于CPU的端到端的软硬件解决方案。 今年,以英特尔和中国移动苏州研发中心为主体,双方还通过共建技术创新联合实验室,不断提升中国移动IT创新能力。 整体上,截止目前,中国移动已经通过战略合作、研发外协、模块外购、代理集成、服务支持等多种形式、多个领域的合作,与国内外30多家厂商建立合作关系,共建云计算产业生态。中国移动希望通过新型IT力量,不断挖掘数字化商业价值,加速数字化转型。 原文发布时间为: 2017年9月19日 本文作者:陈广成 本文来自云栖社区合作伙伴至顶网,了解相关信息可以关注至顶网。

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册